Compare commits

...

7 commits

Author SHA1 Message Date
03f8cc8abf [#36] Fix frostfs dependencies
Signed-off-by: Denis Kirillov <d.kirillov@yadro.com>
2023-01-12 16:17:02 +03:00
Denis Kirillov
739447579a [#36] Fix needHash flag
Signed-off-by: Denis Kirillov <denis@nspcc.ru>
2023-01-12 15:48:06 +03:00
Denis Kirillov
ada830616b [#36] Add docs
Signed-off-by: Denis Kirillov <denis@nspcc.ru>
2023-01-12 15:47:54 +03:00
Denis Kirillov
6e8b6f8b51 [#36] Add test for storage group management
Signed-off-by: Denis Kirillov <denis@nspcc.ru>
2023-01-12 15:47:54 +03:00
Denis Kirillov
e83cef6d24 [#36] Add route to get and delete storage group
Signed-off-by: Denis Kirillov <denis@nspcc.ru>
2023-01-12 15:47:53 +03:00
Denis Kirillov
c20eed98f9 [#36] Add route to list storage groups
Signed-off-by: Denis Kirillov <denis@nspcc.ru>
2023-01-12 15:47:21 +03:00
Denis Kirillov
e5b9fd5f5a [#36] Add route to put storage group
Signed-off-by: Denis Kirillov <denis@nspcc.ru>
2023-01-12 15:46:06 +03:00
25 changed files with 3586 additions and 4 deletions

View file

@ -135,6 +135,8 @@ func runTests(ctx context.Context, t *testing.T, key *keys.PrivateKey, version s
t.Run("rest put container eacl "+version, func(t *testing.T) { restContainerEACLPut(ctx, t, clientPool, owner) })
t.Run("rest get container eacl "+version, func(t *testing.T) { restContainerEACLGet(ctx, t, clientPool, cnrID) })
t.Run("rest list containers "+version, func(t *testing.T) { restContainerList(ctx, t, clientPool, owner, cnrID) })
t.Run("rest manage storage group "+version, func(t *testing.T) { restManageStorageGroup(ctx, t, clientPool, owner, cnrID) })
}
func createDockerContainer(ctx context.Context, t *testing.T, image string) testcontainers.Container {
@ -443,6 +445,117 @@ func checkGWErrorResponse(t *testing.T, httpClient *http.Client, request *http.R
require.Equal(t, models.ErrorTypeGW, *resp.Type)
}
func restManageStorageGroup(ctx context.Context, t *testing.T, clientPool *pool.Pool, owner user.ID, cnrID cid.ID) {
attributes := map[string]string{object.AttributeFileName: "someFile"}
content := []byte("content")
objID := createObject(ctx, t, clientPool, &owner, cnrID, attributes, content)
bearer := &models.Bearer{Object: getAllowedRules()}
httpClient := defaultHTTPClient()
bearerTokens := makeAuthTokenRequest(ctx, t, []*models.Bearer{bearer}, httpClient)
bearerToken := bearerTokens[0]
query := make(url.Values)
query.Add(walletConnectQuery, strconv.FormatBool(useWalletConnect))
var storageGroupAddress models.Address
storageGroupName := "my-storage-group"
t.Run("put storage group", func(t *testing.T) {
req := &models.StorageGroupPutBody{
Lifetime: util.NewInteger(10),
Members: []string{objID.EncodeToString()},
Name: storageGroupName,
}
body, err := json.Marshal(req)
require.NoError(t, err)
request, err := http.NewRequest(http.MethodPut, testHost+"/v1/containers/"+cnrID.EncodeToString()+"/storagegroups?"+query.Encode(), bytes.NewReader(body))
require.NoError(t, err)
prepareCommonHeaders(request.Header, bearerToken)
doRequest(t, httpClient, request, http.StatusOK, &storageGroupAddress)
require.Equal(t, cnrID.EncodeToString(), *storageGroupAddress.ContainerID)
})
t.Run("list storage groups", func(t *testing.T) {
request, err := http.NewRequest(http.MethodGet, testHost+"/v1/containers/"+cnrID.EncodeToString()+"/storagegroups?"+query.Encode(), nil)
require.NoError(t, err)
prepareCommonHeaders(request.Header, bearerToken)
list := &models.StorageGroupList{}
doRequest(t, httpClient, request, http.StatusOK, list)
require.Equal(t, int64(1), *list.Size)
require.Equal(t, storageGroupAddress.ContainerID, list.StorageGroups[0].Address.ContainerID)
require.Equal(t, storageGroupAddress.ObjectID, list.StorageGroups[0].Address.ObjectID)
require.Equal(t, storageGroupName, list.StorageGroups[0].Name)
require.NotEmpty(t, list.StorageGroups[0].ExpirationEpoch)
})
t.Run("get storage group", func(t *testing.T) {
request, err := http.NewRequest(http.MethodGet, testHost+"/v1/containers/"+cnrID.EncodeToString()+"/storagegroups/"+*storageGroupAddress.ObjectID+"?"+query.Encode(), nil)
require.NoError(t, err)
prepareCommonHeaders(request.Header, bearerToken)
group := &models.StorageGroup{}
doRequest(t, httpClient, request, http.StatusOK, group)
require.Equal(t, strconv.Itoa(len(content)), *group.Size)
require.Equal(t, storageGroupName, group.Name)
require.Equal(t, storageGroupAddress.ContainerID, group.Address.ContainerID)
require.Equal(t, storageGroupAddress.ObjectID, group.Address.ObjectID)
require.NotEmpty(t, *group.ExpirationEpoch)
require.Equal(t, []string{objID.EncodeToString()}, group.Members)
})
t.Run("delete storage group", func(t *testing.T) {
request, err := http.NewRequest(http.MethodDelete, testHost+"/v1/containers/"+cnrID.EncodeToString()+"/storagegroups/"+*storageGroupAddress.ObjectID+"?"+query.Encode(), nil)
require.NoError(t, err)
prepareCommonHeaders(request.Header, bearerToken)
resp := &models.SuccessResponse{}
doRequest(t, httpClient, request, http.StatusOK, resp)
require.True(t, *resp.Success)
var sgObjID oid.ID
err = sgObjID.DecodeString(*storageGroupAddress.ObjectID)
require.NoError(t, err)
var addr oid.Address
addr.SetContainer(cnrID)
addr.SetObject(sgObjID)
var prm pool.PrmObjectHead
prm.SetAddress(addr)
_, err = clientPool.HeadObject(ctx, prm)
require.Error(t, err)
})
}
func getAllowedRules() []*models.Record {
var result []*models.Record
ops := []models.Operation{models.OperationGET, models.OperationHEAD, models.OperationPUT,
models.OperationDELETE, models.OperationSEARCH, models.OperationRANGE, models.OperationRANGEHASH}
for _, op := range ops {
rec := &models.Record{
Operation: models.NewOperation(op),
Action: models.NewAction(models.ActionALLOW),
Filters: []*models.Filter{},
Targets: []*models.Target{{
Role: models.NewRole(models.RoleOTHERS),
Keys: []string{},
}},
}
result = append(result, rec)
}
return result
}
func restObjectPut(ctx context.Context, t *testing.T, clientPool *pool.Pool, cnrID cid.ID) {
bearer := &models.Bearer{
Object: []*models.Record{{
@ -1282,7 +1395,7 @@ func createContainer(ctx context.Context, t *testing.T, clientPool *pool.Pool, o
var waitPrm pool.WaitParams
waitPrm.SetPollInterval(3 * time.Second)
waitPrm.SetTimeout(15 * time.Second)
waitPrm.SetTimeout(30 * time.Second)
var prm pool.PrmContainerPut
prm.SetContainer(cnr)
@ -1335,7 +1448,7 @@ func restrictByEACL(ctx context.Context, t *testing.T, clientPool *pool.Pool, cn
var waitPrm pool.WaitParams
waitPrm.SetPollInterval(3 * time.Second)
waitPrm.SetTimeout(15 * time.Second)
waitPrm.SetTimeout(30 * time.Second)
var prm pool.PrmContainerSetEACL
prm.SetTable(*table)

166
gen/models/storage_group.go Normal file
View file

@ -0,0 +1,166 @@
// Code generated by go-swagger; DO NOT EDIT.
package models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"github.com/go-openapi/errors"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
"github.com/go-openapi/validate"
)
// StorageGroup Storage group keeps verification information for Data Audit sessions.
// Example: {"address":{"containerId":"5HZTn5qkRnmgSz9gSrw22CEdPPk6nQhkwf2Mgzyvkikv","objectId":"9N3o7Dtr6T1xteCt6eRwhpmJ7JhME58Hyu1dvaswuTDd"},"expirationEpoch":5000,"members":["8N3o7Dtr6T1xteCt6eRwhpmJ7JhME58Hyu1dvaswuTDd"],"name":"my-storage-group","size":4096}
//
// swagger:model StorageGroup
type StorageGroup struct {
// Address of storage group object. Set by server.
// Required: true
// Read Only: true
Address *Address `json:"address"`
// Expiration epoch of storage group.
// Required: true
ExpirationEpoch *string `json:"expirationEpoch"`
// Homomorphic hash from the concatenation of the payloads of the storage group members. Empty means hashing is disabled.
Hash string `json:"hash,omitempty"`
// Object identifiers to be placed into storage group. Must be unique.
// Required: true
Members []string `json:"members"`
// Name of storage group. It will be the value of the `FileName` attribute in storage group object.
Name string `json:"name,omitempty"`
// Total size of the payloads of objects in the storage group.
// Required: true
Size *string `json:"size"`
}
// Validate validates this storage group
func (m *StorageGroup) Validate(formats strfmt.Registry) error {
var res []error
if err := m.validateAddress(formats); err != nil {
res = append(res, err)
}
if err := m.validateExpirationEpoch(formats); err != nil {
res = append(res, err)
}
if err := m.validateMembers(formats); err != nil {
res = append(res, err)
}
if err := m.validateSize(formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *StorageGroup) validateAddress(formats strfmt.Registry) error {
if err := validate.Required("address", "body", m.Address); err != nil {
return err
}
if m.Address != nil {
if err := m.Address.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("address")
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("address")
}
return err
}
}
return nil
}
func (m *StorageGroup) validateExpirationEpoch(formats strfmt.Registry) error {
if err := validate.Required("expirationEpoch", "body", m.ExpirationEpoch); err != nil {
return err
}
return nil
}
func (m *StorageGroup) validateMembers(formats strfmt.Registry) error {
if err := validate.Required("members", "body", m.Members); err != nil {
return err
}
return nil
}
func (m *StorageGroup) validateSize(formats strfmt.Registry) error {
if err := validate.Required("size", "body", m.Size); err != nil {
return err
}
return nil
}
// ContextValidate validate this storage group based on the context it is used
func (m *StorageGroup) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
var res []error
if err := m.contextValidateAddress(ctx, formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *StorageGroup) contextValidateAddress(ctx context.Context, formats strfmt.Registry) error {
if m.Address != nil {
if err := m.Address.ContextValidate(ctx, formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("address")
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("address")
}
return err
}
}
return nil
}
// MarshalBinary interface implementation
func (m *StorageGroup) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *StorageGroup) UnmarshalBinary(b []byte) error {
var res StorageGroup
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}

View file

@ -0,0 +1,128 @@
// Code generated by go-swagger; DO NOT EDIT.
package models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"github.com/go-openapi/errors"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
"github.com/go-openapi/validate"
)
// StorageGroupBaseInfo Storage group info for listing.
// Example: {"address":{"containerId":"5HZTn5qkRnmgSz9gSrw22CEdPPk6nQhkwf2Mgzyvkikv","objectId":"9N3o7Dtr6T1xteCt6eRwhpmJ7JhME58Hyu1dvaswuTDd"},"expirationEpoch":5000,"name":"my-storage-group"}
//
// swagger:model StorageGroupBaseInfo
type StorageGroupBaseInfo struct {
// address
// Required: true
Address *Address `json:"address"`
// expiration epoch
// Required: true
ExpirationEpoch *string `json:"expirationEpoch"`
// name
Name string `json:"name,omitempty"`
}
// Validate validates this storage group base info
func (m *StorageGroupBaseInfo) Validate(formats strfmt.Registry) error {
var res []error
if err := m.validateAddress(formats); err != nil {
res = append(res, err)
}
if err := m.validateExpirationEpoch(formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *StorageGroupBaseInfo) validateAddress(formats strfmt.Registry) error {
if err := validate.Required("address", "body", m.Address); err != nil {
return err
}
if m.Address != nil {
if err := m.Address.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("address")
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("address")
}
return err
}
}
return nil
}
func (m *StorageGroupBaseInfo) validateExpirationEpoch(formats strfmt.Registry) error {
if err := validate.Required("expirationEpoch", "body", m.ExpirationEpoch); err != nil {
return err
}
return nil
}
// ContextValidate validate this storage group base info based on the context it is used
func (m *StorageGroupBaseInfo) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
var res []error
if err := m.contextValidateAddress(ctx, formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *StorageGroupBaseInfo) contextValidateAddress(ctx context.Context, formats strfmt.Registry) error {
if m.Address != nil {
if err := m.Address.ContextValidate(ctx, formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("address")
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("address")
}
return err
}
}
return nil
}
// MarshalBinary interface implementation
func (m *StorageGroupBaseInfo) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *StorageGroupBaseInfo) UnmarshalBinary(b []byte) error {
var res StorageGroupBaseInfo
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}

View file

@ -0,0 +1,137 @@
// Code generated by go-swagger; DO NOT EDIT.
package models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"strconv"
"github.com/go-openapi/errors"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
"github.com/go-openapi/validate"
)
// StorageGroupList List of storage groups.
// Example: {"size":1,"storageGroups":[{"address":{"containerId":"5HZTn5qkRnmgSz9gSrw22CEdPPk6nQhkwf2Mgzyvkikv","objectId":"9N3o7Dtr6T1xteCt6eRwhpmJ7JhME58Hyu1dvaswuTDd"},"expirationEpoch":5000,"name":"my-storage-group"}]}
//
// swagger:model StorageGroupList
type StorageGroupList struct {
// size
// Required: true
Size *int64 `json:"size"`
// storage groups
// Required: true
StorageGroups []*StorageGroupBaseInfo `json:"storageGroups"`
}
// Validate validates this storage group list
func (m *StorageGroupList) Validate(formats strfmt.Registry) error {
var res []error
if err := m.validateSize(formats); err != nil {
res = append(res, err)
}
if err := m.validateStorageGroups(formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *StorageGroupList) validateSize(formats strfmt.Registry) error {
if err := validate.Required("size", "body", m.Size); err != nil {
return err
}
return nil
}
func (m *StorageGroupList) validateStorageGroups(formats strfmt.Registry) error {
if err := validate.Required("storageGroups", "body", m.StorageGroups); err != nil {
return err
}
for i := 0; i < len(m.StorageGroups); i++ {
if swag.IsZero(m.StorageGroups[i]) { // not required
continue
}
if m.StorageGroups[i] != nil {
if err := m.StorageGroups[i].Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("storageGroups" + "." + strconv.Itoa(i))
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("storageGroups" + "." + strconv.Itoa(i))
}
return err
}
}
}
return nil
}
// ContextValidate validate this storage group list based on the context it is used
func (m *StorageGroupList) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
var res []error
if err := m.contextValidateStorageGroups(ctx, formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *StorageGroupList) contextValidateStorageGroups(ctx context.Context, formats strfmt.Registry) error {
for i := 0; i < len(m.StorageGroups); i++ {
if m.StorageGroups[i] != nil {
if err := m.StorageGroups[i].ContextValidate(ctx, formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("storageGroups" + "." + strconv.Itoa(i))
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("storageGroups" + "." + strconv.Itoa(i))
}
return err
}
}
}
return nil
}
// MarshalBinary interface implementation
func (m *StorageGroupList) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *StorageGroupList) UnmarshalBinary(b []byte) error {
var res StorageGroupList
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}

View file

@ -0,0 +1,92 @@
// Code generated by go-swagger; DO NOT EDIT.
package models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"github.com/go-openapi/errors"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
"github.com/go-openapi/validate"
)
// StorageGroupPutBody storage group put body
// Example: {"lifetime":100,"members":["8N3o7Dtr6T1xteCt6eRwhpmJ7JhME58Hyu1dvaswuTDd"],"name":"my-storage-group"}
//
// swagger:model StorageGroupPutBody
type StorageGroupPutBody struct {
// Lifetime in epochs for storage group.
// Required: true
Lifetime *int64 `json:"lifetime"`
// Object identifiers to be placed into storage group. Must be unique.
// Required: true
Members []string `json:"members"`
// Name of storage group. It will be the value of the `FileName` attribute in storage group object.
Name string `json:"name,omitempty"`
}
// Validate validates this storage group put body
func (m *StorageGroupPutBody) Validate(formats strfmt.Registry) error {
var res []error
if err := m.validateLifetime(formats); err != nil {
res = append(res, err)
}
if err := m.validateMembers(formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *StorageGroupPutBody) validateLifetime(formats strfmt.Registry) error {
if err := validate.Required("lifetime", "body", m.Lifetime); err != nil {
return err
}
return nil
}
func (m *StorageGroupPutBody) validateMembers(formats strfmt.Registry) error {
if err := validate.Required("members", "body", m.Members); err != nil {
return err
}
return nil
}
// ContextValidate validates this storage group put body based on context it is used
func (m *StorageGroupPutBody) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
return nil
}
// MarshalBinary interface implementation
func (m *StorageGroupPutBody) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *StorageGroupPutBody) UnmarshalBinary(b []byte) error {
var res StorageGroupPutBody
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}

View file

@ -523,6 +523,160 @@ func init() {
}
]
},
"/containers/{containerId}/storagegroups": {
"get": {
"summary": "Find all storage groups in container.",
"operationId": "listStorageGroups",
"parameters": [
{
"$ref": "#/parameters/signatureParam"
},
{
"$ref": "#/parameters/signatureKeyParam"
},
{
"$ref": "#/parameters/signatureScheme"
},
{
"$ref": "#/parameters/fullBearerToken"
}
],
"responses": {
"200": {
"description": "List of storage groups.",
"schema": {
"$ref": "#/definitions/StorageGroupList"
}
},
"400": {
"description": "Bad request.",
"schema": {
"$ref": "#/definitions/ErrorResponse"
}
}
}
},
"put": {
"summary": "Create a new storage group in container.",
"operationId": "putStorageGroup",
"parameters": [
{
"$ref": "#/parameters/signatureParam"
},
{
"$ref": "#/parameters/signatureKeyParam"
},
{
"$ref": "#/parameters/signatureScheme"
},
{
"$ref": "#/parameters/fullBearerToken"
},
{
"description": "Storage group co create.",
"name": "storageGroup",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/StorageGroupPutBody"
}
}
],
"responses": {
"200": {
"description": "Address of uploaded storage group.",
"schema": {
"$ref": "#/definitions/Address"
}
},
"400": {
"description": "Bad request.",
"schema": {
"$ref": "#/definitions/ErrorResponse"
}
}
}
},
"parameters": [
{
"$ref": "#/parameters/containerId"
}
]
},
"/containers/{containerId}/storagegroups/{storageGroupId}": {
"get": {
"summary": "Get storage group info.",
"operationId": "getStorageGroup",
"parameters": [
{
"$ref": "#/parameters/signatureParam"
},
{
"$ref": "#/parameters/signatureKeyParam"
},
{
"$ref": "#/parameters/signatureScheme"
},
{
"$ref": "#/parameters/fullBearerToken"
}
],
"responses": {
"200": {
"description": "Storage group information.",
"schema": {
"$ref": "#/definitions/StorageGroup"
}
},
"400": {
"description": "Bad request.",
"schema": {
"$ref": "#/definitions/ErrorResponse"
}
}
}
},
"delete": {
"summary": "Delete storage group from container.",
"operationId": "deleteStorageGroup",
"parameters": [
{
"$ref": "#/parameters/signatureParam"
},
{
"$ref": "#/parameters/signatureKeyParam"
},
{
"$ref": "#/parameters/signatureScheme"
},
{
"$ref": "#/parameters/fullBearerToken"
}
],
"responses": {
"200": {
"description": "Successful deletion.",
"schema": {
"$ref": "#/definitions/SuccessResponse"
}
},
"400": {
"description": "Bad request.",
"schema": {
"$ref": "#/definitions/ErrorResponse"
}
}
}
},
"parameters": [
{
"$ref": "#/parameters/containerId"
},
{
"$ref": "#/parameters/storageGroupId"
}
]
},
"/objects": {
"put": {
"consumes": [
@ -1522,6 +1676,148 @@ func init() {
"MatchCommonPrefix"
]
},
"StorageGroup": {
"description": "Storage group keeps verification information for Data Audit sessions.",
"type": "object",
"required": [
"address",
"expirationEpoch",
"size",
"members"
],
"properties": {
"address": {
"description": "Address of storage group object. Set by server.",
"$ref": "#/definitions/Address",
"readOnly": true
},
"expirationEpoch": {
"description": "Expiration epoch of storage group.",
"type": "string"
},
"hash": {
"description": "Homomorphic hash from the concatenation of the payloads of the storage group members. Empty means hashing is disabled.",
"type": "string"
},
"members": {
"description": "Object identifiers to be placed into storage group. Must be unique.",
"type": "array",
"items": {
"type": "string"
}
},
"name": {
"description": "Name of storage group. It will be the value of the ` + "`" + `FileName` + "`" + ` attribute in storage group object.",
"type": "string"
},
"size": {
"description": "Total size of the payloads of objects in the storage group.",
"type": "string"
}
},
"example": {
"address": {
"containerId": "5HZTn5qkRnmgSz9gSrw22CEdPPk6nQhkwf2Mgzyvkikv",
"objectId": "9N3o7Dtr6T1xteCt6eRwhpmJ7JhME58Hyu1dvaswuTDd"
},
"expirationEpoch": 5000,
"members": [
"8N3o7Dtr6T1xteCt6eRwhpmJ7JhME58Hyu1dvaswuTDd"
],
"name": "my-storage-group",
"size": 4096
}
},
"StorageGroupBaseInfo": {
"description": "Storage group info for listing.",
"type": "object",
"required": [
"address",
"expirationEpoch"
],
"properties": {
"address": {
"$ref": "#/definitions/Address"
},
"expirationEpoch": {
"type": "string"
},
"name": {
"type": "string"
}
},
"example": {
"address": {
"containerId": "5HZTn5qkRnmgSz9gSrw22CEdPPk6nQhkwf2Mgzyvkikv",
"objectId": "9N3o7Dtr6T1xteCt6eRwhpmJ7JhME58Hyu1dvaswuTDd"
},
"expirationEpoch": 5000,
"name": "my-storage-group"
}
},
"StorageGroupList": {
"description": "List of storage groups.",
"type": "object",
"required": [
"size",
"storageGroups"
],
"properties": {
"size": {
"type": "integer"
},
"storageGroups": {
"type": "array",
"items": {
"$ref": "#/definitions/StorageGroupBaseInfo"
}
}
},
"example": {
"size": 1,
"storageGroups": [
{
"address": {
"containerId": "5HZTn5qkRnmgSz9gSrw22CEdPPk6nQhkwf2Mgzyvkikv",
"objectId": "9N3o7Dtr6T1xteCt6eRwhpmJ7JhME58Hyu1dvaswuTDd"
},
"expirationEpoch": 5000,
"name": "my-storage-group"
}
]
}
},
"StorageGroupPutBody": {
"type": "object",
"required": [
"lifetime",
"members"
],
"properties": {
"lifetime": {
"description": "Lifetime in epochs for storage group.",
"type": "integer"
},
"members": {
"description": "Object identifiers to be placed into storage group. Must be unique.",
"type": "array",
"items": {
"type": "string"
}
},
"name": {
"description": "Name of storage group. It will be the value of the ` + "`" + `FileName` + "`" + ` attribute in storage group object.",
"type": "string"
}
},
"example": {
"lifetime": 100,
"members": [
"8N3o7Dtr6T1xteCt6eRwhpmJ7JhME58Hyu1dvaswuTDd"
],
"name": "my-storage-group"
}
},
"SuccessResponse": {
"description": "Success response.",
"type": "object",
@ -1649,6 +1945,13 @@ func init() {
"description": "Use wallet connect signature scheme or native FrostFS signature.",
"name": "walletConnect",
"in": "query"
},
"storageGroupId": {
"type": "string",
"description": "Base58 encoded storage group id.",
"name": "storageGroupId",
"in": "path",
"required": true
}
},
"securityDefinitions": {
@ -2220,6 +2523,228 @@ func init() {
}
]
},
"/containers/{containerId}/storagegroups": {
"get": {
"summary": "Find all storage groups in container.",
"operationId": "listStorageGroups",
"parameters": [
{
"type": "string",
"description": "Base64 encoded signature for bearer token.",
"name": "X-Bearer-Signature",
"in": "header"
},
{
"type": "string",
"description": "Hex encoded the public part of the key that signed the bearer token.",
"name": "X-Bearer-Signature-Key",
"in": "header"
},
{
"type": "boolean",
"default": false,
"description": "Use wallet connect signature scheme or native FrostFS signature.",
"name": "walletConnect",
"in": "query"
},
{
"type": "boolean",
"default": false,
"description": "Provided bearer token is final or gate should assemble it using signature.",
"name": "fullBearer",
"in": "query"
}
],
"responses": {
"200": {
"description": "List of storage groups.",
"schema": {
"$ref": "#/definitions/StorageGroupList"
}
},
"400": {
"description": "Bad request.",
"schema": {
"$ref": "#/definitions/ErrorResponse"
}
}
}
},
"put": {
"summary": "Create a new storage group in container.",
"operationId": "putStorageGroup",
"parameters": [
{
"type": "string",
"description": "Base64 encoded signature for bearer token.",
"name": "X-Bearer-Signature",
"in": "header"
},
{
"type": "string",
"description": "Hex encoded the public part of the key that signed the bearer token.",
"name": "X-Bearer-Signature-Key",
"in": "header"
},
{
"type": "boolean",
"default": false,
"description": "Use wallet connect signature scheme or native FrostFS signature.",
"name": "walletConnect",
"in": "query"
},
{
"type": "boolean",
"default": false,
"description": "Provided bearer token is final or gate should assemble it using signature.",
"name": "fullBearer",
"in": "query"
},
{
"description": "Storage group co create.",
"name": "storageGroup",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/StorageGroupPutBody"
}
}
],
"responses": {
"200": {
"description": "Address of uploaded storage group.",
"schema": {
"$ref": "#/definitions/Address"
}
},
"400": {
"description": "Bad request.",
"schema": {
"$ref": "#/definitions/ErrorResponse"
}
}
}
},
"parameters": [
{
"type": "string",
"description": "Base58 encoded container id.",
"name": "containerId",
"in": "path",
"required": true
}
]
},
"/containers/{containerId}/storagegroups/{storageGroupId}": {
"get": {
"summary": "Get storage group info.",
"operationId": "getStorageGroup",
"parameters": [
{
"type": "string",
"description": "Base64 encoded signature for bearer token.",
"name": "X-Bearer-Signature",
"in": "header"
},
{
"type": "string",
"description": "Hex encoded the public part of the key that signed the bearer token.",
"name": "X-Bearer-Signature-Key",
"in": "header"
},
{
"type": "boolean",
"default": false,
"description": "Use wallet connect signature scheme or native FrostFS signature.",
"name": "walletConnect",
"in": "query"
},
{
"type": "boolean",
"default": false,
"description": "Provided bearer token is final or gate should assemble it using signature.",
"name": "fullBearer",
"in": "query"
}
],
"responses": {
"200": {
"description": "Storage group information.",
"schema": {
"$ref": "#/definitions/StorageGroup"
}
},
"400": {
"description": "Bad request.",
"schema": {
"$ref": "#/definitions/ErrorResponse"
}
}
}
},
"delete": {
"summary": "Delete storage group from container.",
"operationId": "deleteStorageGroup",
"parameters": [
{
"type": "string",
"description": "Base64 encoded signature for bearer token.",
"name": "X-Bearer-Signature",
"in": "header"
},
{
"type": "string",
"description": "Hex encoded the public part of the key that signed the bearer token.",
"name": "X-Bearer-Signature-Key",
"in": "header"
},
{
"type": "boolean",
"default": false,
"description": "Use wallet connect signature scheme or native FrostFS signature.",
"name": "walletConnect",
"in": "query"
},
{
"type": "boolean",
"default": false,
"description": "Provided bearer token is final or gate should assemble it using signature.",
"name": "fullBearer",
"in": "query"
}
],
"responses": {
"200": {
"description": "Successful deletion.",
"schema": {
"$ref": "#/definitions/SuccessResponse"
}
},
"400": {
"description": "Bad request.",
"schema": {
"$ref": "#/definitions/ErrorResponse"
}
}
}
},
"parameters": [
{
"type": "string",
"description": "Base58 encoded container id.",
"name": "containerId",
"in": "path",
"required": true
},
{
"type": "string",
"description": "Base58 encoded storage group id.",
"name": "storageGroupId",
"in": "path",
"required": true
}
]
},
"/objects": {
"put": {
"consumes": [
@ -3290,6 +3815,148 @@ func init() {
"MatchCommonPrefix"
]
},
"StorageGroup": {
"description": "Storage group keeps verification information for Data Audit sessions.",
"type": "object",
"required": [
"address",
"expirationEpoch",
"size",
"members"
],
"properties": {
"address": {
"description": "Address of storage group object. Set by server.",
"$ref": "#/definitions/Address",
"readOnly": true
},
"expirationEpoch": {
"description": "Expiration epoch of storage group.",
"type": "string"
},
"hash": {
"description": "Homomorphic hash from the concatenation of the payloads of the storage group members. Empty means hashing is disabled.",
"type": "string"
},
"members": {
"description": "Object identifiers to be placed into storage group. Must be unique.",
"type": "array",
"items": {
"type": "string"
}
},
"name": {
"description": "Name of storage group. It will be the value of the ` + "`" + `FileName` + "`" + ` attribute in storage group object.",
"type": "string"
},
"size": {
"description": "Total size of the payloads of objects in the storage group.",
"type": "string"
}
},
"example": {
"address": {
"containerId": "5HZTn5qkRnmgSz9gSrw22CEdPPk6nQhkwf2Mgzyvkikv",
"objectId": "9N3o7Dtr6T1xteCt6eRwhpmJ7JhME58Hyu1dvaswuTDd"
},
"expirationEpoch": 5000,
"members": [
"8N3o7Dtr6T1xteCt6eRwhpmJ7JhME58Hyu1dvaswuTDd"
],
"name": "my-storage-group",
"size": 4096
}
},
"StorageGroupBaseInfo": {
"description": "Storage group info for listing.",
"type": "object",
"required": [
"address",
"expirationEpoch"
],
"properties": {
"address": {
"$ref": "#/definitions/Address"
},
"expirationEpoch": {
"type": "string"
},
"name": {
"type": "string"
}
},
"example": {
"address": {
"containerId": "5HZTn5qkRnmgSz9gSrw22CEdPPk6nQhkwf2Mgzyvkikv",
"objectId": "9N3o7Dtr6T1xteCt6eRwhpmJ7JhME58Hyu1dvaswuTDd"
},
"expirationEpoch": 5000,
"name": "my-storage-group"
}
},
"StorageGroupList": {
"description": "List of storage groups.",
"type": "object",
"required": [
"size",
"storageGroups"
],
"properties": {
"size": {
"type": "integer"
},
"storageGroups": {
"type": "array",
"items": {
"$ref": "#/definitions/StorageGroupBaseInfo"
}
}
},
"example": {
"size": 1,
"storageGroups": [
{
"address": {
"containerId": "5HZTn5qkRnmgSz9gSrw22CEdPPk6nQhkwf2Mgzyvkikv",
"objectId": "9N3o7Dtr6T1xteCt6eRwhpmJ7JhME58Hyu1dvaswuTDd"
},
"expirationEpoch": 5000,
"name": "my-storage-group"
}
]
}
},
"StorageGroupPutBody": {
"type": "object",
"required": [
"lifetime",
"members"
],
"properties": {
"lifetime": {
"description": "Lifetime in epochs for storage group.",
"type": "integer"
},
"members": {
"description": "Object identifiers to be placed into storage group. Must be unique.",
"type": "array",
"items": {
"type": "string"
}
},
"name": {
"description": "Name of storage group. It will be the value of the ` + "`" + `FileName` + "`" + ` attribute in storage group object.",
"type": "string"
}
},
"example": {
"lifetime": 100,
"members": [
"8N3o7Dtr6T1xteCt6eRwhpmJ7JhME58Hyu1dvaswuTDd"
],
"name": "my-storage-group"
}
},
"SuccessResponse": {
"description": "Success response.",
"type": "object",
@ -3417,6 +4084,13 @@ func init() {
"description": "Use wallet connect signature scheme or native FrostFS signature.",
"name": "walletConnect",
"in": "query"
},
"storageGroupId": {
"type": "string",
"description": "Base58 encoded storage group id.",
"name": "storageGroupId",
"in": "path",
"required": true
}
},
"securityDefinitions": {

View file

@ -0,0 +1,71 @@
// Code generated by go-swagger; DO NOT EDIT.
package operations
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
import (
"net/http"
"github.com/go-openapi/runtime/middleware"
"github.com/TrueCloudLab/frostfs-rest-gw/gen/models"
)
// DeleteStorageGroupHandlerFunc turns a function with the right signature into a delete storage group handler
type DeleteStorageGroupHandlerFunc func(DeleteStorageGroupParams, *models.Principal) middleware.Responder
// Handle executing the request and returning a response
func (fn DeleteStorageGroupHandlerFunc) Handle(params DeleteStorageGroupParams, principal *models.Principal) middleware.Responder {
return fn(params, principal)
}
// DeleteStorageGroupHandler interface for that can handle valid delete storage group params
type DeleteStorageGroupHandler interface {
Handle(DeleteStorageGroupParams, *models.Principal) middleware.Responder
}
// NewDeleteStorageGroup creates a new http.Handler for the delete storage group operation
func NewDeleteStorageGroup(ctx *middleware.Context, handler DeleteStorageGroupHandler) *DeleteStorageGroup {
return &DeleteStorageGroup{Context: ctx, Handler: handler}
}
/*
DeleteStorageGroup swagger:route DELETE /containers/{containerId}/storagegroups/{storageGroupId} deleteStorageGroup
Delete storage group from container.
*/
type DeleteStorageGroup struct {
Context *middleware.Context
Handler DeleteStorageGroupHandler
}
func (o *DeleteStorageGroup) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
route, rCtx, _ := o.Context.RouteInfo(r)
if rCtx != nil {
*r = *rCtx
}
var Params = NewDeleteStorageGroupParams()
uprinc, aCtx, err := o.Context.Authorize(r, route)
if err != nil {
o.Context.Respond(rw, r, route.Produces, route, err)
return
}
if aCtx != nil {
*r = *aCtx
}
var principal *models.Principal
if uprinc != nil {
principal = uprinc.(*models.Principal) // this is really a models.Principal, I promise
}
if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params
o.Context.Respond(rw, r, route.Produces, route, err)
return
}
res := o.Handler.Handle(Params, principal) // actually handle the request
o.Context.Respond(rw, r, route.Produces, route, res)
}

View file

@ -0,0 +1,228 @@
// Code generated by go-swagger; DO NOT EDIT.
package operations
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"net/http"
"github.com/go-openapi/errors"
"github.com/go-openapi/runtime"
"github.com/go-openapi/runtime/middleware"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
)
// NewDeleteStorageGroupParams creates a new DeleteStorageGroupParams object
// with the default values initialized.
func NewDeleteStorageGroupParams() DeleteStorageGroupParams {
var (
// initialize parameters with default values
fullBearerDefault = bool(false)
walletConnectDefault = bool(false)
)
return DeleteStorageGroupParams{
FullBearer: &fullBearerDefault,
WalletConnect: &walletConnectDefault,
}
}
// DeleteStorageGroupParams contains all the bound params for the delete storage group operation
// typically these are obtained from a http.Request
//
// swagger:parameters deleteStorageGroup
type DeleteStorageGroupParams struct {
// HTTP Request Object
HTTPRequest *http.Request `json:"-"`
/*Base64 encoded signature for bearer token.
In: header
*/
XBearerSignature *string
/*Hex encoded the public part of the key that signed the bearer token.
In: header
*/
XBearerSignatureKey *string
/*Base58 encoded container id.
Required: true
In: path
*/
ContainerID string
/*Provided bearer token is final or gate should assemble it using signature.
In: query
Default: false
*/
FullBearer *bool
/*Base58 encoded storage group id.
Required: true
In: path
*/
StorageGroupID string
/*Use wallet connect signature scheme or native FrostFS signature.
In: query
Default: false
*/
WalletConnect *bool
}
// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface
// for simple values it will use straight method calls.
//
// To ensure default values, the struct must have been initialized with NewDeleteStorageGroupParams() beforehand.
func (o *DeleteStorageGroupParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error {
var res []error
o.HTTPRequest = r
qs := runtime.Values(r.URL.Query())
if err := o.bindXBearerSignature(r.Header[http.CanonicalHeaderKey("X-Bearer-Signature")], true, route.Formats); err != nil {
res = append(res, err)
}
if err := o.bindXBearerSignatureKey(r.Header[http.CanonicalHeaderKey("X-Bearer-Signature-Key")], true, route.Formats); err != nil {
res = append(res, err)
}
rContainerID, rhkContainerID, _ := route.Params.GetOK("containerId")
if err := o.bindContainerID(rContainerID, rhkContainerID, route.Formats); err != nil {
res = append(res, err)
}
qFullBearer, qhkFullBearer, _ := qs.GetOK("fullBearer")
if err := o.bindFullBearer(qFullBearer, qhkFullBearer, route.Formats); err != nil {
res = append(res, err)
}
rStorageGroupID, rhkStorageGroupID, _ := route.Params.GetOK("storageGroupId")
if err := o.bindStorageGroupID(rStorageGroupID, rhkStorageGroupID, route.Formats); err != nil {
res = append(res, err)
}
qWalletConnect, qhkWalletConnect, _ := qs.GetOK("walletConnect")
if err := o.bindWalletConnect(qWalletConnect, qhkWalletConnect, route.Formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
// bindXBearerSignature binds and validates parameter XBearerSignature from header.
func (o *DeleteStorageGroupParams) bindXBearerSignature(rawData []string, hasKey bool, formats strfmt.Registry) error {
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: false
if raw == "" { // empty values pass all other validations
return nil
}
o.XBearerSignature = &raw
return nil
}
// bindXBearerSignatureKey binds and validates parameter XBearerSignatureKey from header.
func (o *DeleteStorageGroupParams) bindXBearerSignatureKey(rawData []string, hasKey bool, formats strfmt.Registry) error {
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: false
if raw == "" { // empty values pass all other validations
return nil
}
o.XBearerSignatureKey = &raw
return nil
}
// bindContainerID binds and validates parameter ContainerID from path.
func (o *DeleteStorageGroupParams) bindContainerID(rawData []string, hasKey bool, formats strfmt.Registry) error {
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: true
// Parameter is provided by construction from the route
o.ContainerID = raw
return nil
}
// bindFullBearer binds and validates parameter FullBearer from query.
func (o *DeleteStorageGroupParams) bindFullBearer(rawData []string, hasKey bool, formats strfmt.Registry) error {
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: false
// AllowEmptyValue: false
if raw == "" { // empty values pass all other validations
// Default values have been previously initialized by NewDeleteStorageGroupParams()
return nil
}
value, err := swag.ConvertBool(raw)
if err != nil {
return errors.InvalidType("fullBearer", "query", "bool", raw)
}
o.FullBearer = &value
return nil
}
// bindStorageGroupID binds and validates parameter StorageGroupID from path.
func (o *DeleteStorageGroupParams) bindStorageGroupID(rawData []string, hasKey bool, formats strfmt.Registry) error {
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: true
// Parameter is provided by construction from the route
o.StorageGroupID = raw
return nil
}
// bindWalletConnect binds and validates parameter WalletConnect from query.
func (o *DeleteStorageGroupParams) bindWalletConnect(rawData []string, hasKey bool, formats strfmt.Registry) error {
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: false
// AllowEmptyValue: false
if raw == "" { // empty values pass all other validations
// Default values have been previously initialized by NewDeleteStorageGroupParams()
return nil
}
value, err := swag.ConvertBool(raw)
if err != nil {
return errors.InvalidType("walletConnect", "query", "bool", raw)
}
o.WalletConnect = &value
return nil
}

View file

@ -0,0 +1,104 @@
// Code generated by go-swagger; DO NOT EDIT.
package operations
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"net/http"
"github.com/go-openapi/runtime"
"github.com/TrueCloudLab/frostfs-rest-gw/gen/models"
)
// DeleteStorageGroupOKCode is the HTTP code returned for type DeleteStorageGroupOK
const DeleteStorageGroupOKCode int = 200
/*
DeleteStorageGroupOK Successful deletion.
swagger:response deleteStorageGroupOK
*/
type DeleteStorageGroupOK struct {
/*
In: Body
*/
Payload *models.SuccessResponse `json:"body,omitempty"`
}
// NewDeleteStorageGroupOK creates DeleteStorageGroupOK with default headers values
func NewDeleteStorageGroupOK() *DeleteStorageGroupOK {
return &DeleteStorageGroupOK{}
}
// WithPayload adds the payload to the delete storage group o k response
func (o *DeleteStorageGroupOK) WithPayload(payload *models.SuccessResponse) *DeleteStorageGroupOK {
o.Payload = payload
return o
}
// SetPayload sets the payload to the delete storage group o k response
func (o *DeleteStorageGroupOK) SetPayload(payload *models.SuccessResponse) {
o.Payload = payload
}
// WriteResponse to the client
func (o *DeleteStorageGroupOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.WriteHeader(200)
if o.Payload != nil {
payload := o.Payload
if err := producer.Produce(rw, payload); err != nil {
panic(err) // let the recovery middleware deal with this
}
}
}
// DeleteStorageGroupBadRequestCode is the HTTP code returned for type DeleteStorageGroupBadRequest
const DeleteStorageGroupBadRequestCode int = 400
/*
DeleteStorageGroupBadRequest Bad request.
swagger:response deleteStorageGroupBadRequest
*/
type DeleteStorageGroupBadRequest struct {
/*
In: Body
*/
Payload *models.ErrorResponse `json:"body,omitempty"`
}
// NewDeleteStorageGroupBadRequest creates DeleteStorageGroupBadRequest with default headers values
func NewDeleteStorageGroupBadRequest() *DeleteStorageGroupBadRequest {
return &DeleteStorageGroupBadRequest{}
}
// WithPayload adds the payload to the delete storage group bad request response
func (o *DeleteStorageGroupBadRequest) WithPayload(payload *models.ErrorResponse) *DeleteStorageGroupBadRequest {
o.Payload = payload
return o
}
// SetPayload sets the payload to the delete storage group bad request response
func (o *DeleteStorageGroupBadRequest) SetPayload(payload *models.ErrorResponse) {
o.Payload = payload
}
// WriteResponse to the client
func (o *DeleteStorageGroupBadRequest) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.WriteHeader(400)
if o.Payload != nil {
payload := o.Payload
if err := producer.Produce(rw, payload); err != nil {
panic(err) // let the recovery middleware deal with this
}
}
}

View file

@ -53,6 +53,9 @@ func NewFrostfsRestGwAPI(spec *loads.Document) *FrostfsRestGwAPI {
DeleteObjectHandler: DeleteObjectHandlerFunc(func(params DeleteObjectParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation DeleteObject has not yet been implemented")
}),
DeleteStorageGroupHandler: DeleteStorageGroupHandlerFunc(func(params DeleteStorageGroupParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation DeleteStorageGroup has not yet been implemented")
}),
FormBinaryBearerHandler: FormBinaryBearerHandlerFunc(func(params FormBinaryBearerParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation FormBinaryBearer has not yet been implemented")
}),
@ -68,9 +71,15 @@ func NewFrostfsRestGwAPI(spec *loads.Document) *FrostfsRestGwAPI {
GetObjectInfoHandler: GetObjectInfoHandlerFunc(func(params GetObjectInfoParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation GetObjectInfo has not yet been implemented")
}),
GetStorageGroupHandler: GetStorageGroupHandlerFunc(func(params GetStorageGroupParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation GetStorageGroup has not yet been implemented")
}),
ListContainersHandler: ListContainersHandlerFunc(func(params ListContainersParams) middleware.Responder {
return middleware.NotImplemented("operation ListContainers has not yet been implemented")
}),
ListStorageGroupsHandler: ListStorageGroupsHandlerFunc(func(params ListStorageGroupsParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation ListStorageGroups has not yet been implemented")
}),
OptionsAuthHandler: OptionsAuthHandlerFunc(func(params OptionsAuthParams) middleware.Responder {
return middleware.NotImplemented("operation OptionsAuth has not yet been implemented")
}),
@ -104,6 +113,9 @@ func NewFrostfsRestGwAPI(spec *loads.Document) *FrostfsRestGwAPI {
PutObjectHandler: PutObjectHandlerFunc(func(params PutObjectParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation PutObject has not yet been implemented")
}),
PutStorageGroupHandler: PutStorageGroupHandlerFunc(func(params PutStorageGroupParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation PutStorageGroup has not yet been implemented")
}),
SearchObjectsHandler: SearchObjectsHandlerFunc(func(params SearchObjectsParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation SearchObjects has not yet been implemented")
}),
@ -163,6 +175,8 @@ type FrostfsRestGwAPI struct {
DeleteContainerHandler DeleteContainerHandler
// DeleteObjectHandler sets the operation handler for the delete object operation
DeleteObjectHandler DeleteObjectHandler
// DeleteStorageGroupHandler sets the operation handler for the delete storage group operation
DeleteStorageGroupHandler DeleteStorageGroupHandler
// FormBinaryBearerHandler sets the operation handler for the form binary bearer operation
FormBinaryBearerHandler FormBinaryBearerHandler
// GetBalanceHandler sets the operation handler for the get balance operation
@ -173,8 +187,12 @@ type FrostfsRestGwAPI struct {
GetContainerEACLHandler GetContainerEACLHandler
// GetObjectInfoHandler sets the operation handler for the get object info operation
GetObjectInfoHandler GetObjectInfoHandler
// GetStorageGroupHandler sets the operation handler for the get storage group operation
GetStorageGroupHandler GetStorageGroupHandler
// ListContainersHandler sets the operation handler for the list containers operation
ListContainersHandler ListContainersHandler
// ListStorageGroupsHandler sets the operation handler for the list storage groups operation
ListStorageGroupsHandler ListStorageGroupsHandler
// OptionsAuthHandler sets the operation handler for the options auth operation
OptionsAuthHandler OptionsAuthHandler
// OptionsAuthBearerHandler sets the operation handler for the options auth bearer operation
@ -197,6 +215,8 @@ type FrostfsRestGwAPI struct {
PutContainerEACLHandler PutContainerEACLHandler
// PutObjectHandler sets the operation handler for the put object operation
PutObjectHandler PutObjectHandler
// PutStorageGroupHandler sets the operation handler for the put storage group operation
PutStorageGroupHandler PutStorageGroupHandler
// SearchObjectsHandler sets the operation handler for the search objects operation
SearchObjectsHandler SearchObjectsHandler
@ -289,6 +309,9 @@ func (o *FrostfsRestGwAPI) Validate() error {
if o.DeleteObjectHandler == nil {
unregistered = append(unregistered, "DeleteObjectHandler")
}
if o.DeleteStorageGroupHandler == nil {
unregistered = append(unregistered, "DeleteStorageGroupHandler")
}
if o.FormBinaryBearerHandler == nil {
unregistered = append(unregistered, "FormBinaryBearerHandler")
}
@ -304,9 +327,15 @@ func (o *FrostfsRestGwAPI) Validate() error {
if o.GetObjectInfoHandler == nil {
unregistered = append(unregistered, "GetObjectInfoHandler")
}
if o.GetStorageGroupHandler == nil {
unregistered = append(unregistered, "GetStorageGroupHandler")
}
if o.ListContainersHandler == nil {
unregistered = append(unregistered, "ListContainersHandler")
}
if o.ListStorageGroupsHandler == nil {
unregistered = append(unregistered, "ListStorageGroupsHandler")
}
if o.OptionsAuthHandler == nil {
unregistered = append(unregistered, "OptionsAuthHandler")
}
@ -340,6 +369,9 @@ func (o *FrostfsRestGwAPI) Validate() error {
if o.PutObjectHandler == nil {
unregistered = append(unregistered, "PutObjectHandler")
}
if o.PutStorageGroupHandler == nil {
unregistered = append(unregistered, "PutStorageGroupHandler")
}
if o.SearchObjectsHandler == nil {
unregistered = append(unregistered, "SearchObjectsHandler")
}
@ -454,6 +486,10 @@ func (o *FrostfsRestGwAPI) initHandlerCache() {
o.handlers["DELETE"] = make(map[string]http.Handler)
}
o.handlers["DELETE"]["/objects/{containerId}/{objectId}"] = NewDeleteObject(o.context, o.DeleteObjectHandler)
if o.handlers["DELETE"] == nil {
o.handlers["DELETE"] = make(map[string]http.Handler)
}
o.handlers["DELETE"]["/containers/{containerId}/storagegroups/{storageGroupId}"] = NewDeleteStorageGroup(o.context, o.DeleteStorageGroupHandler)
if o.handlers["GET"] == nil {
o.handlers["GET"] = make(map[string]http.Handler)
}
@ -477,7 +513,15 @@ func (o *FrostfsRestGwAPI) initHandlerCache() {
if o.handlers["GET"] == nil {
o.handlers["GET"] = make(map[string]http.Handler)
}
o.handlers["GET"]["/containers/{containerId}/storagegroups/{storageGroupId}"] = NewGetStorageGroup(o.context, o.GetStorageGroupHandler)
if o.handlers["GET"] == nil {
o.handlers["GET"] = make(map[string]http.Handler)
}
o.handlers["GET"]["/containers"] = NewListContainers(o.context, o.ListContainersHandler)
if o.handlers["GET"] == nil {
o.handlers["GET"] = make(map[string]http.Handler)
}
o.handlers["GET"]["/containers/{containerId}/storagegroups"] = NewListStorageGroups(o.context, o.ListStorageGroupsHandler)
if o.handlers["OPTIONS"] == nil {
o.handlers["OPTIONS"] = make(map[string]http.Handler)
}
@ -522,6 +566,10 @@ func (o *FrostfsRestGwAPI) initHandlerCache() {
o.handlers["PUT"] = make(map[string]http.Handler)
}
o.handlers["PUT"]["/objects"] = NewPutObject(o.context, o.PutObjectHandler)
if o.handlers["PUT"] == nil {
o.handlers["PUT"] = make(map[string]http.Handler)
}
o.handlers["PUT"]["/containers/{containerId}/storagegroups"] = NewPutStorageGroup(o.context, o.PutStorageGroupHandler)
if o.handlers["POST"] == nil {
o.handlers["POST"] = make(map[string]http.Handler)
}

View file

@ -0,0 +1,71 @@
// Code generated by go-swagger; DO NOT EDIT.
package operations
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
import (
"net/http"
"github.com/go-openapi/runtime/middleware"
"github.com/TrueCloudLab/frostfs-rest-gw/gen/models"
)
// GetStorageGroupHandlerFunc turns a function with the right signature into a get storage group handler
type GetStorageGroupHandlerFunc func(GetStorageGroupParams, *models.Principal) middleware.Responder
// Handle executing the request and returning a response
func (fn GetStorageGroupHandlerFunc) Handle(params GetStorageGroupParams, principal *models.Principal) middleware.Responder {
return fn(params, principal)
}
// GetStorageGroupHandler interface for that can handle valid get storage group params
type GetStorageGroupHandler interface {
Handle(GetStorageGroupParams, *models.Principal) middleware.Responder
}
// NewGetStorageGroup creates a new http.Handler for the get storage group operation
func NewGetStorageGroup(ctx *middleware.Context, handler GetStorageGroupHandler) *GetStorageGroup {
return &GetStorageGroup{Context: ctx, Handler: handler}
}
/*
GetStorageGroup swagger:route GET /containers/{containerId}/storagegroups/{storageGroupId} getStorageGroup
Get storage group info.
*/
type GetStorageGroup struct {
Context *middleware.Context
Handler GetStorageGroupHandler
}
func (o *GetStorageGroup) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
route, rCtx, _ := o.Context.RouteInfo(r)
if rCtx != nil {
*r = *rCtx
}
var Params = NewGetStorageGroupParams()
uprinc, aCtx, err := o.Context.Authorize(r, route)
if err != nil {
o.Context.Respond(rw, r, route.Produces, route, err)
return
}
if aCtx != nil {
*r = *aCtx
}
var principal *models.Principal
if uprinc != nil {
principal = uprinc.(*models.Principal) // this is really a models.Principal, I promise
}
if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params
o.Context.Respond(rw, r, route.Produces, route, err)
return
}
res := o.Handler.Handle(Params, principal) // actually handle the request
o.Context.Respond(rw, r, route.Produces, route, res)
}

View file

@ -0,0 +1,228 @@
// Code generated by go-swagger; DO NOT EDIT.
package operations
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"net/http"
"github.com/go-openapi/errors"
"github.com/go-openapi/runtime"
"github.com/go-openapi/runtime/middleware"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
)
// NewGetStorageGroupParams creates a new GetStorageGroupParams object
// with the default values initialized.
func NewGetStorageGroupParams() GetStorageGroupParams {
var (
// initialize parameters with default values
fullBearerDefault = bool(false)
walletConnectDefault = bool(false)
)
return GetStorageGroupParams{
FullBearer: &fullBearerDefault,
WalletConnect: &walletConnectDefault,
}
}
// GetStorageGroupParams contains all the bound params for the get storage group operation
// typically these are obtained from a http.Request
//
// swagger:parameters getStorageGroup
type GetStorageGroupParams struct {
// HTTP Request Object
HTTPRequest *http.Request `json:"-"`
/*Base64 encoded signature for bearer token.
In: header
*/
XBearerSignature *string
/*Hex encoded the public part of the key that signed the bearer token.
In: header
*/
XBearerSignatureKey *string
/*Base58 encoded container id.
Required: true
In: path
*/
ContainerID string
/*Provided bearer token is final or gate should assemble it using signature.
In: query
Default: false
*/
FullBearer *bool
/*Base58 encoded storage group id.
Required: true
In: path
*/
StorageGroupID string
/*Use wallet connect signature scheme or native FrostFS signature.
In: query
Default: false
*/
WalletConnect *bool
}
// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface
// for simple values it will use straight method calls.
//
// To ensure default values, the struct must have been initialized with NewGetStorageGroupParams() beforehand.
func (o *GetStorageGroupParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error {
var res []error
o.HTTPRequest = r
qs := runtime.Values(r.URL.Query())
if err := o.bindXBearerSignature(r.Header[http.CanonicalHeaderKey("X-Bearer-Signature")], true, route.Formats); err != nil {
res = append(res, err)
}
if err := o.bindXBearerSignatureKey(r.Header[http.CanonicalHeaderKey("X-Bearer-Signature-Key")], true, route.Formats); err != nil {
res = append(res, err)
}
rContainerID, rhkContainerID, _ := route.Params.GetOK("containerId")
if err := o.bindContainerID(rContainerID, rhkContainerID, route.Formats); err != nil {
res = append(res, err)
}
qFullBearer, qhkFullBearer, _ := qs.GetOK("fullBearer")
if err := o.bindFullBearer(qFullBearer, qhkFullBearer, route.Formats); err != nil {
res = append(res, err)
}
rStorageGroupID, rhkStorageGroupID, _ := route.Params.GetOK("storageGroupId")
if err := o.bindStorageGroupID(rStorageGroupID, rhkStorageGroupID, route.Formats); err != nil {
res = append(res, err)
}
qWalletConnect, qhkWalletConnect, _ := qs.GetOK("walletConnect")
if err := o.bindWalletConnect(qWalletConnect, qhkWalletConnect, route.Formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
// bindXBearerSignature binds and validates parameter XBearerSignature from header.
func (o *GetStorageGroupParams) bindXBearerSignature(rawData []string, hasKey bool, formats strfmt.Registry) error {
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: false
if raw == "" { // empty values pass all other validations
return nil
}
o.XBearerSignature = &raw
return nil
}
// bindXBearerSignatureKey binds and validates parameter XBearerSignatureKey from header.
func (o *GetStorageGroupParams) bindXBearerSignatureKey(rawData []string, hasKey bool, formats strfmt.Registry) error {
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: false
if raw == "" { // empty values pass all other validations
return nil
}
o.XBearerSignatureKey = &raw
return nil
}
// bindContainerID binds and validates parameter ContainerID from path.
func (o *GetStorageGroupParams) bindContainerID(rawData []string, hasKey bool, formats strfmt.Registry) error {
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: true
// Parameter is provided by construction from the route
o.ContainerID = raw
return nil
}
// bindFullBearer binds and validates parameter FullBearer from query.
func (o *GetStorageGroupParams) bindFullBearer(rawData []string, hasKey bool, formats strfmt.Registry) error {
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: false
// AllowEmptyValue: false
if raw == "" { // empty values pass all other validations
// Default values have been previously initialized by NewGetStorageGroupParams()
return nil
}
value, err := swag.ConvertBool(raw)
if err != nil {
return errors.InvalidType("fullBearer", "query", "bool", raw)
}
o.FullBearer = &value
return nil
}
// bindStorageGroupID binds and validates parameter StorageGroupID from path.
func (o *GetStorageGroupParams) bindStorageGroupID(rawData []string, hasKey bool, formats strfmt.Registry) error {
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: true
// Parameter is provided by construction from the route
o.StorageGroupID = raw
return nil
}
// bindWalletConnect binds and validates parameter WalletConnect from query.
func (o *GetStorageGroupParams) bindWalletConnect(rawData []string, hasKey bool, formats strfmt.Registry) error {
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: false
// AllowEmptyValue: false
if raw == "" { // empty values pass all other validations
// Default values have been previously initialized by NewGetStorageGroupParams()
return nil
}
value, err := swag.ConvertBool(raw)
if err != nil {
return errors.InvalidType("walletConnect", "query", "bool", raw)
}
o.WalletConnect = &value
return nil
}

View file

@ -0,0 +1,104 @@
// Code generated by go-swagger; DO NOT EDIT.
package operations
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"net/http"
"github.com/go-openapi/runtime"
"github.com/TrueCloudLab/frostfs-rest-gw/gen/models"
)
// GetStorageGroupOKCode is the HTTP code returned for type GetStorageGroupOK
const GetStorageGroupOKCode int = 200
/*
GetStorageGroupOK Storage group information.
swagger:response getStorageGroupOK
*/
type GetStorageGroupOK struct {
/*
In: Body
*/
Payload *models.StorageGroup `json:"body,omitempty"`
}
// NewGetStorageGroupOK creates GetStorageGroupOK with default headers values
func NewGetStorageGroupOK() *GetStorageGroupOK {
return &GetStorageGroupOK{}
}
// WithPayload adds the payload to the get storage group o k response
func (o *GetStorageGroupOK) WithPayload(payload *models.StorageGroup) *GetStorageGroupOK {
o.Payload = payload
return o
}
// SetPayload sets the payload to the get storage group o k response
func (o *GetStorageGroupOK) SetPayload(payload *models.StorageGroup) {
o.Payload = payload
}
// WriteResponse to the client
func (o *GetStorageGroupOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.WriteHeader(200)
if o.Payload != nil {
payload := o.Payload
if err := producer.Produce(rw, payload); err != nil {
panic(err) // let the recovery middleware deal with this
}
}
}
// GetStorageGroupBadRequestCode is the HTTP code returned for type GetStorageGroupBadRequest
const GetStorageGroupBadRequestCode int = 400
/*
GetStorageGroupBadRequest Bad request.
swagger:response getStorageGroupBadRequest
*/
type GetStorageGroupBadRequest struct {
/*
In: Body
*/
Payload *models.ErrorResponse `json:"body,omitempty"`
}
// NewGetStorageGroupBadRequest creates GetStorageGroupBadRequest with default headers values
func NewGetStorageGroupBadRequest() *GetStorageGroupBadRequest {
return &GetStorageGroupBadRequest{}
}
// WithPayload adds the payload to the get storage group bad request response
func (o *GetStorageGroupBadRequest) WithPayload(payload *models.ErrorResponse) *GetStorageGroupBadRequest {
o.Payload = payload
return o
}
// SetPayload sets the payload to the get storage group bad request response
func (o *GetStorageGroupBadRequest) SetPayload(payload *models.ErrorResponse) {
o.Payload = payload
}
// WriteResponse to the client
func (o *GetStorageGroupBadRequest) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.WriteHeader(400)
if o.Payload != nil {
payload := o.Payload
if err := producer.Produce(rw, payload); err != nil {
panic(err) // let the recovery middleware deal with this
}
}
}

View file

@ -0,0 +1,71 @@
// Code generated by go-swagger; DO NOT EDIT.
package operations
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
import (
"net/http"
"github.com/go-openapi/runtime/middleware"
"github.com/TrueCloudLab/frostfs-rest-gw/gen/models"
)
// ListStorageGroupsHandlerFunc turns a function with the right signature into a list storage groups handler
type ListStorageGroupsHandlerFunc func(ListStorageGroupsParams, *models.Principal) middleware.Responder
// Handle executing the request and returning a response
func (fn ListStorageGroupsHandlerFunc) Handle(params ListStorageGroupsParams, principal *models.Principal) middleware.Responder {
return fn(params, principal)
}
// ListStorageGroupsHandler interface for that can handle valid list storage groups params
type ListStorageGroupsHandler interface {
Handle(ListStorageGroupsParams, *models.Principal) middleware.Responder
}
// NewListStorageGroups creates a new http.Handler for the list storage groups operation
func NewListStorageGroups(ctx *middleware.Context, handler ListStorageGroupsHandler) *ListStorageGroups {
return &ListStorageGroups{Context: ctx, Handler: handler}
}
/*
ListStorageGroups swagger:route GET /containers/{containerId}/storagegroups listStorageGroups
Find all storage groups in container.
*/
type ListStorageGroups struct {
Context *middleware.Context
Handler ListStorageGroupsHandler
}
func (o *ListStorageGroups) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
route, rCtx, _ := o.Context.RouteInfo(r)
if rCtx != nil {
*r = *rCtx
}
var Params = NewListStorageGroupsParams()
uprinc, aCtx, err := o.Context.Authorize(r, route)
if err != nil {
o.Context.Respond(rw, r, route.Produces, route, err)
return
}
if aCtx != nil {
*r = *aCtx
}
var principal *models.Principal
if uprinc != nil {
principal = uprinc.(*models.Principal) // this is really a models.Principal, I promise
}
if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params
o.Context.Respond(rw, r, route.Produces, route, err)
return
}
res := o.Handler.Handle(Params, principal) // actually handle the request
o.Context.Respond(rw, r, route.Produces, route, res)
}

View file

@ -0,0 +1,203 @@
// Code generated by go-swagger; DO NOT EDIT.
package operations
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"net/http"
"github.com/go-openapi/errors"
"github.com/go-openapi/runtime"
"github.com/go-openapi/runtime/middleware"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
)
// NewListStorageGroupsParams creates a new ListStorageGroupsParams object
// with the default values initialized.
func NewListStorageGroupsParams() ListStorageGroupsParams {
var (
// initialize parameters with default values
fullBearerDefault = bool(false)
walletConnectDefault = bool(false)
)
return ListStorageGroupsParams{
FullBearer: &fullBearerDefault,
WalletConnect: &walletConnectDefault,
}
}
// ListStorageGroupsParams contains all the bound params for the list storage groups operation
// typically these are obtained from a http.Request
//
// swagger:parameters listStorageGroups
type ListStorageGroupsParams struct {
// HTTP Request Object
HTTPRequest *http.Request `json:"-"`
/*Base64 encoded signature for bearer token.
In: header
*/
XBearerSignature *string
/*Hex encoded the public part of the key that signed the bearer token.
In: header
*/
XBearerSignatureKey *string
/*Base58 encoded container id.
Required: true
In: path
*/
ContainerID string
/*Provided bearer token is final or gate should assemble it using signature.
In: query
Default: false
*/
FullBearer *bool
/*Use wallet connect signature scheme or native FrostFS signature.
In: query
Default: false
*/
WalletConnect *bool
}
// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface
// for simple values it will use straight method calls.
//
// To ensure default values, the struct must have been initialized with NewListStorageGroupsParams() beforehand.
func (o *ListStorageGroupsParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error {
var res []error
o.HTTPRequest = r
qs := runtime.Values(r.URL.Query())
if err := o.bindXBearerSignature(r.Header[http.CanonicalHeaderKey("X-Bearer-Signature")], true, route.Formats); err != nil {
res = append(res, err)
}
if err := o.bindXBearerSignatureKey(r.Header[http.CanonicalHeaderKey("X-Bearer-Signature-Key")], true, route.Formats); err != nil {
res = append(res, err)
}
rContainerID, rhkContainerID, _ := route.Params.GetOK("containerId")
if err := o.bindContainerID(rContainerID, rhkContainerID, route.Formats); err != nil {
res = append(res, err)
}
qFullBearer, qhkFullBearer, _ := qs.GetOK("fullBearer")
if err := o.bindFullBearer(qFullBearer, qhkFullBearer, route.Formats); err != nil {
res = append(res, err)
}
qWalletConnect, qhkWalletConnect, _ := qs.GetOK("walletConnect")
if err := o.bindWalletConnect(qWalletConnect, qhkWalletConnect, route.Formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
// bindXBearerSignature binds and validates parameter XBearerSignature from header.
func (o *ListStorageGroupsParams) bindXBearerSignature(rawData []string, hasKey bool, formats strfmt.Registry) error {
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: false
if raw == "" { // empty values pass all other validations
return nil
}
o.XBearerSignature = &raw
return nil
}
// bindXBearerSignatureKey binds and validates parameter XBearerSignatureKey from header.
func (o *ListStorageGroupsParams) bindXBearerSignatureKey(rawData []string, hasKey bool, formats strfmt.Registry) error {
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: false
if raw == "" { // empty values pass all other validations
return nil
}
o.XBearerSignatureKey = &raw
return nil
}
// bindContainerID binds and validates parameter ContainerID from path.
func (o *ListStorageGroupsParams) bindContainerID(rawData []string, hasKey bool, formats strfmt.Registry) error {
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: true
// Parameter is provided by construction from the route
o.ContainerID = raw
return nil
}
// bindFullBearer binds and validates parameter FullBearer from query.
func (o *ListStorageGroupsParams) bindFullBearer(rawData []string, hasKey bool, formats strfmt.Registry) error {
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: false
// AllowEmptyValue: false
if raw == "" { // empty values pass all other validations
// Default values have been previously initialized by NewListStorageGroupsParams()
return nil
}
value, err := swag.ConvertBool(raw)
if err != nil {
return errors.InvalidType("fullBearer", "query", "bool", raw)
}
o.FullBearer = &value
return nil
}
// bindWalletConnect binds and validates parameter WalletConnect from query.
func (o *ListStorageGroupsParams) bindWalletConnect(rawData []string, hasKey bool, formats strfmt.Registry) error {
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: false
// AllowEmptyValue: false
if raw == "" { // empty values pass all other validations
// Default values have been previously initialized by NewListStorageGroupsParams()
return nil
}
value, err := swag.ConvertBool(raw)
if err != nil {
return errors.InvalidType("walletConnect", "query", "bool", raw)
}
o.WalletConnect = &value
return nil
}

View file

@ -0,0 +1,104 @@
// Code generated by go-swagger; DO NOT EDIT.
package operations
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"net/http"
"github.com/go-openapi/runtime"
"github.com/TrueCloudLab/frostfs-rest-gw/gen/models"
)
// ListStorageGroupsOKCode is the HTTP code returned for type ListStorageGroupsOK
const ListStorageGroupsOKCode int = 200
/*
ListStorageGroupsOK List of storage groups.
swagger:response listStorageGroupsOK
*/
type ListStorageGroupsOK struct {
/*
In: Body
*/
Payload *models.StorageGroupList `json:"body,omitempty"`
}
// NewListStorageGroupsOK creates ListStorageGroupsOK with default headers values
func NewListStorageGroupsOK() *ListStorageGroupsOK {
return &ListStorageGroupsOK{}
}
// WithPayload adds the payload to the list storage groups o k response
func (o *ListStorageGroupsOK) WithPayload(payload *models.StorageGroupList) *ListStorageGroupsOK {
o.Payload = payload
return o
}
// SetPayload sets the payload to the list storage groups o k response
func (o *ListStorageGroupsOK) SetPayload(payload *models.StorageGroupList) {
o.Payload = payload
}
// WriteResponse to the client
func (o *ListStorageGroupsOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.WriteHeader(200)
if o.Payload != nil {
payload := o.Payload
if err := producer.Produce(rw, payload); err != nil {
panic(err) // let the recovery middleware deal with this
}
}
}
// ListStorageGroupsBadRequestCode is the HTTP code returned for type ListStorageGroupsBadRequest
const ListStorageGroupsBadRequestCode int = 400
/*
ListStorageGroupsBadRequest Bad request.
swagger:response listStorageGroupsBadRequest
*/
type ListStorageGroupsBadRequest struct {
/*
In: Body
*/
Payload *models.ErrorResponse `json:"body,omitempty"`
}
// NewListStorageGroupsBadRequest creates ListStorageGroupsBadRequest with default headers values
func NewListStorageGroupsBadRequest() *ListStorageGroupsBadRequest {
return &ListStorageGroupsBadRequest{}
}
// WithPayload adds the payload to the list storage groups bad request response
func (o *ListStorageGroupsBadRequest) WithPayload(payload *models.ErrorResponse) *ListStorageGroupsBadRequest {
o.Payload = payload
return o
}
// SetPayload sets the payload to the list storage groups bad request response
func (o *ListStorageGroupsBadRequest) SetPayload(payload *models.ErrorResponse) {
o.Payload = payload
}
// WriteResponse to the client
func (o *ListStorageGroupsBadRequest) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.WriteHeader(400)
if o.Payload != nil {
payload := o.Payload
if err := producer.Produce(rw, payload); err != nil {
panic(err) // let the recovery middleware deal with this
}
}
}

View file

@ -0,0 +1,71 @@
// Code generated by go-swagger; DO NOT EDIT.
package operations
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
import (
"net/http"
"github.com/go-openapi/runtime/middleware"
"github.com/TrueCloudLab/frostfs-rest-gw/gen/models"
)
// PutStorageGroupHandlerFunc turns a function with the right signature into a put storage group handler
type PutStorageGroupHandlerFunc func(PutStorageGroupParams, *models.Principal) middleware.Responder
// Handle executing the request and returning a response
func (fn PutStorageGroupHandlerFunc) Handle(params PutStorageGroupParams, principal *models.Principal) middleware.Responder {
return fn(params, principal)
}
// PutStorageGroupHandler interface for that can handle valid put storage group params
type PutStorageGroupHandler interface {
Handle(PutStorageGroupParams, *models.Principal) middleware.Responder
}
// NewPutStorageGroup creates a new http.Handler for the put storage group operation
func NewPutStorageGroup(ctx *middleware.Context, handler PutStorageGroupHandler) *PutStorageGroup {
return &PutStorageGroup{Context: ctx, Handler: handler}
}
/*
PutStorageGroup swagger:route PUT /containers/{containerId}/storagegroups putStorageGroup
Create a new storage group in container.
*/
type PutStorageGroup struct {
Context *middleware.Context
Handler PutStorageGroupHandler
}
func (o *PutStorageGroup) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
route, rCtx, _ := o.Context.RouteInfo(r)
if rCtx != nil {
*r = *rCtx
}
var Params = NewPutStorageGroupParams()
uprinc, aCtx, err := o.Context.Authorize(r, route)
if err != nil {
o.Context.Respond(rw, r, route.Produces, route, err)
return
}
if aCtx != nil {
*r = *aCtx
}
var principal *models.Principal
if uprinc != nil {
principal = uprinc.(*models.Principal) // this is really a models.Principal, I promise
}
if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params
o.Context.Respond(rw, r, route.Produces, route, err)
return
}
res := o.Handler.Handle(Params, principal) // actually handle the request
o.Context.Respond(rw, r, route.Produces, route, res)
}

View file

@ -0,0 +1,242 @@
// Code generated by go-swagger; DO NOT EDIT.
package operations
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"io"
"net/http"
"github.com/go-openapi/errors"
"github.com/go-openapi/runtime"
"github.com/go-openapi/runtime/middleware"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
"github.com/go-openapi/validate"
"github.com/TrueCloudLab/frostfs-rest-gw/gen/models"
)
// NewPutStorageGroupParams creates a new PutStorageGroupParams object
// with the default values initialized.
func NewPutStorageGroupParams() PutStorageGroupParams {
var (
// initialize parameters with default values
fullBearerDefault = bool(false)
walletConnectDefault = bool(false)
)
return PutStorageGroupParams{
FullBearer: &fullBearerDefault,
WalletConnect: &walletConnectDefault,
}
}
// PutStorageGroupParams contains all the bound params for the put storage group operation
// typically these are obtained from a http.Request
//
// swagger:parameters putStorageGroup
type PutStorageGroupParams struct {
// HTTP Request Object
HTTPRequest *http.Request `json:"-"`
/*Base64 encoded signature for bearer token.
In: header
*/
XBearerSignature *string
/*Hex encoded the public part of the key that signed the bearer token.
In: header
*/
XBearerSignatureKey *string
/*Base58 encoded container id.
Required: true
In: path
*/
ContainerID string
/*Provided bearer token is final or gate should assemble it using signature.
In: query
Default: false
*/
FullBearer *bool
/*Storage group co create.
Required: true
In: body
*/
StorageGroup *models.StorageGroupPutBody
/*Use wallet connect signature scheme or native FrostFS signature.
In: query
Default: false
*/
WalletConnect *bool
}
// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface
// for simple values it will use straight method calls.
//
// To ensure default values, the struct must have been initialized with NewPutStorageGroupParams() beforehand.
func (o *PutStorageGroupParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error {
var res []error
o.HTTPRequest = r
qs := runtime.Values(r.URL.Query())
if err := o.bindXBearerSignature(r.Header[http.CanonicalHeaderKey("X-Bearer-Signature")], true, route.Formats); err != nil {
res = append(res, err)
}
if err := o.bindXBearerSignatureKey(r.Header[http.CanonicalHeaderKey("X-Bearer-Signature-Key")], true, route.Formats); err != nil {
res = append(res, err)
}
rContainerID, rhkContainerID, _ := route.Params.GetOK("containerId")
if err := o.bindContainerID(rContainerID, rhkContainerID, route.Formats); err != nil {
res = append(res, err)
}
qFullBearer, qhkFullBearer, _ := qs.GetOK("fullBearer")
if err := o.bindFullBearer(qFullBearer, qhkFullBearer, route.Formats); err != nil {
res = append(res, err)
}
if runtime.HasBody(r) {
defer r.Body.Close()
var body models.StorageGroupPutBody
if err := route.Consumer.Consume(r.Body, &body); err != nil {
if err == io.EOF {
res = append(res, errors.Required("storageGroup", "body", ""))
} else {
res = append(res, errors.NewParseError("storageGroup", "body", "", err))
}
} else {
// validate body object
if err := body.Validate(route.Formats); err != nil {
res = append(res, err)
}
ctx := validate.WithOperationRequest(context.Background())
if err := body.ContextValidate(ctx, route.Formats); err != nil {
res = append(res, err)
}
if len(res) == 0 {
o.StorageGroup = &body
}
}
} else {
res = append(res, errors.Required("storageGroup", "body", ""))
}
qWalletConnect, qhkWalletConnect, _ := qs.GetOK("walletConnect")
if err := o.bindWalletConnect(qWalletConnect, qhkWalletConnect, route.Formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
// bindXBearerSignature binds and validates parameter XBearerSignature from header.
func (o *PutStorageGroupParams) bindXBearerSignature(rawData []string, hasKey bool, formats strfmt.Registry) error {
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: false
if raw == "" { // empty values pass all other validations
return nil
}
o.XBearerSignature = &raw
return nil
}
// bindXBearerSignatureKey binds and validates parameter XBearerSignatureKey from header.
func (o *PutStorageGroupParams) bindXBearerSignatureKey(rawData []string, hasKey bool, formats strfmt.Registry) error {
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: false
if raw == "" { // empty values pass all other validations
return nil
}
o.XBearerSignatureKey = &raw
return nil
}
// bindContainerID binds and validates parameter ContainerID from path.
func (o *PutStorageGroupParams) bindContainerID(rawData []string, hasKey bool, formats strfmt.Registry) error {
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: true
// Parameter is provided by construction from the route
o.ContainerID = raw
return nil
}
// bindFullBearer binds and validates parameter FullBearer from query.
func (o *PutStorageGroupParams) bindFullBearer(rawData []string, hasKey bool, formats strfmt.Registry) error {
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: false
// AllowEmptyValue: false
if raw == "" { // empty values pass all other validations
// Default values have been previously initialized by NewPutStorageGroupParams()
return nil
}
value, err := swag.ConvertBool(raw)
if err != nil {
return errors.InvalidType("fullBearer", "query", "bool", raw)
}
o.FullBearer = &value
return nil
}
// bindWalletConnect binds and validates parameter WalletConnect from query.
func (o *PutStorageGroupParams) bindWalletConnect(rawData []string, hasKey bool, formats strfmt.Registry) error {
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: false
// AllowEmptyValue: false
if raw == "" { // empty values pass all other validations
// Default values have been previously initialized by NewPutStorageGroupParams()
return nil
}
value, err := swag.ConvertBool(raw)
if err != nil {
return errors.InvalidType("walletConnect", "query", "bool", raw)
}
o.WalletConnect = &value
return nil
}

View file

@ -0,0 +1,104 @@
// Code generated by go-swagger; DO NOT EDIT.
package operations
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"net/http"
"github.com/go-openapi/runtime"
"github.com/TrueCloudLab/frostfs-rest-gw/gen/models"
)
// PutStorageGroupOKCode is the HTTP code returned for type PutStorageGroupOK
const PutStorageGroupOKCode int = 200
/*
PutStorageGroupOK Address of uploaded storage group.
swagger:response putStorageGroupOK
*/
type PutStorageGroupOK struct {
/*
In: Body
*/
Payload *models.Address `json:"body,omitempty"`
}
// NewPutStorageGroupOK creates PutStorageGroupOK with default headers values
func NewPutStorageGroupOK() *PutStorageGroupOK {
return &PutStorageGroupOK{}
}
// WithPayload adds the payload to the put storage group o k response
func (o *PutStorageGroupOK) WithPayload(payload *models.Address) *PutStorageGroupOK {
o.Payload = payload
return o
}
// SetPayload sets the payload to the put storage group o k response
func (o *PutStorageGroupOK) SetPayload(payload *models.Address) {
o.Payload = payload
}
// WriteResponse to the client
func (o *PutStorageGroupOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.WriteHeader(200)
if o.Payload != nil {
payload := o.Payload
if err := producer.Produce(rw, payload); err != nil {
panic(err) // let the recovery middleware deal with this
}
}
}
// PutStorageGroupBadRequestCode is the HTTP code returned for type PutStorageGroupBadRequest
const PutStorageGroupBadRequestCode int = 400
/*
PutStorageGroupBadRequest Bad request.
swagger:response putStorageGroupBadRequest
*/
type PutStorageGroupBadRequest struct {
/*
In: Body
*/
Payload *models.ErrorResponse `json:"body,omitempty"`
}
// NewPutStorageGroupBadRequest creates PutStorageGroupBadRequest with default headers values
func NewPutStorageGroupBadRequest() *PutStorageGroupBadRequest {
return &PutStorageGroupBadRequest{}
}
// WithPayload adds the payload to the put storage group bad request response
func (o *PutStorageGroupBadRequest) WithPayload(payload *models.ErrorResponse) *PutStorageGroupBadRequest {
o.Payload = payload
return o
}
// SetPayload sets the payload to the put storage group bad request response
func (o *PutStorageGroupBadRequest) SetPayload(payload *models.ErrorResponse) {
o.Payload = payload
}
// WriteResponse to the client
func (o *PutStorageGroupBadRequest) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.WriteHeader(400)
if o.Payload != nil {
payload := o.Payload
if err := producer.Produce(rw, payload); err != nil {
panic(err) // let the recovery middleware deal with this
}
}
}

4
go.mod
View file

@ -4,7 +4,9 @@ go 1.17
require (
github.com/TrueCloudLab/frostfs-api-go/v2 v2.0.0-20221212144048-1351b6656d68
github.com/TrueCloudLab/frostfs-crypto v0.5.0
github.com/TrueCloudLab/frostfs-sdk-go v0.0.0-20221214065929-4c779423f556
github.com/TrueCloudLab/tzhash v1.7.0
github.com/go-openapi/errors v0.20.2
github.com/go-openapi/loads v0.21.1
github.com/go-openapi/runtime v0.23.3
@ -27,10 +29,8 @@ require (
github.com/Microsoft/hcsshim v0.8.23 // indirect
github.com/PuerkitoBio/purell v1.1.1 // indirect
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
github.com/TrueCloudLab/frostfs-crypto v0.5.0
github.com/TrueCloudLab/hrw v1.1.0 // indirect
github.com/TrueCloudLab/rfc6979 v0.3.0 // indirect
github.com/TrueCloudLab/tzhash v1.7.0 // indirect
github.com/antlr/antlr4/runtime/Go/antlr v1.4.10 // indirect
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect
github.com/beorn7/perks v1.0.1 // indirect

View file

@ -126,6 +126,12 @@ func (a *API) Configure(api *operations.FrostfsRestGwAPI) http.Handler {
api.OptionsContainersEACLHandler = operations.OptionsContainersEACLHandlerFunc(a.OptionsContainersEACL)
api.PutContainerEACLHandler = operations.PutContainerEACLHandlerFunc(a.PutContainerEACL)
api.GetContainerEACLHandler = operations.GetContainerEACLHandlerFunc(a.GetContainerEACL)
api.ListContainersHandler = operations.ListContainersHandlerFunc(a.ListContainer)
api.PutStorageGroupHandler = operations.PutStorageGroupHandlerFunc(a.PutStorageGroup)
api.ListStorageGroupsHandler = operations.ListStorageGroupsHandlerFunc(a.ListStorageGroups)
api.GetStorageGroupHandler = operations.GetStorageGroupHandlerFunc(a.GetStorageGroup)
api.DeleteStorageGroupHandler = operations.DeleteStorageGroupHandlerFunc(a.DeleteStorageGroup)
api.BearerAuthAuth = func(s string) (*models.Principal, error) {
if !strings.HasPrefix(s, BearerPrefix) {

View file

@ -413,6 +413,10 @@ func createContainer(ctx context.Context, p *pool.Pool, stoken session.Container
container.WriteDomain(&cnr, domain)
}
if err = pool.SyncContainerWithNetwork(ctx, &cnr, p); err != nil {
return cid.ID{}, fmt.Errorf("sync container with network: %w", err)
}
var prm pool.PrmContainerPut
prm.SetContainer(cnr)
prm.WithinSession(stoken)

403
handlers/storagegroup.go Normal file
View file

@ -0,0 +1,403 @@
package handlers
import (
"bytes"
"context"
"encoding/hex"
"fmt"
"io"
"strconv"
objectv2 "github.com/TrueCloudLab/frostfs-api-go/v2/object"
"github.com/TrueCloudLab/frostfs-rest-gw/gen/models"
"github.com/TrueCloudLab/frostfs-rest-gw/gen/restapi/operations"
"github.com/TrueCloudLab/frostfs-rest-gw/internal/util"
"github.com/TrueCloudLab/frostfs-sdk-go/bearer"
"github.com/TrueCloudLab/frostfs-sdk-go/checksum"
"github.com/TrueCloudLab/frostfs-sdk-go/container"
cid "github.com/TrueCloudLab/frostfs-sdk-go/container/id"
"github.com/TrueCloudLab/frostfs-sdk-go/object"
oid "github.com/TrueCloudLab/frostfs-sdk-go/object/id"
"github.com/TrueCloudLab/frostfs-sdk-go/pool"
"github.com/TrueCloudLab/frostfs-sdk-go/storagegroup"
"github.com/TrueCloudLab/tzhash/tz"
"github.com/go-openapi/runtime/middleware"
)
// PutStorageGroup handler that create a new storage group.
func (a *API) PutStorageGroup(params operations.PutStorageGroupParams, principal *models.Principal) middleware.Responder {
ctx := params.HTTPRequest.Context()
cnrID, err := parseContainerID(params.ContainerID)
if err != nil {
resp := a.logAndGetErrorResponse("invalid container id", err)
return operations.NewPutStorageGroupBadRequest().WithPayload(resp)
}
btoken, err := getBearerToken(principal, params.XBearerSignature, params.XBearerSignatureKey, *params.WalletConnect, *params.FullBearer)
if err != nil {
resp := a.logAndGetErrorResponse("invalid bearer token", err)
return operations.NewPutStorageGroupBadRequest().WithPayload(resp)
}
sg, err := a.formStorageGroup(ctx, cnrID, btoken, params.StorageGroup)
if err != nil {
resp := a.logAndGetErrorResponse("form storage group", err)
return operations.NewPutStorageGroupBadRequest().WithPayload(resp)
}
objID, err := a.putStorageGroupObject(ctx, cnrID, btoken, params.StorageGroup.Name, *sg)
if err != nil {
resp := a.logAndGetErrorResponse("put storage group", err)
return operations.NewPutStorageGroupBadRequest().WithPayload(resp)
}
var resp models.Address
resp.ContainerID = util.NewString(params.ContainerID)
resp.ObjectID = util.NewString(objID.String())
return operations.NewPutStorageGroupOK().WithPayload(&resp)
}
// ListStorageGroups handler that create a new storage group.
func (a *API) ListStorageGroups(params operations.ListStorageGroupsParams, principal *models.Principal) middleware.Responder {
ctx := params.HTTPRequest.Context()
cnrID, err := parseContainerID(params.ContainerID)
if err != nil {
resp := a.logAndGetErrorResponse("invalid container id", err)
return operations.NewListStorageGroupsBadRequest().WithPayload(resp)
}
btoken, err := getBearerToken(principal, params.XBearerSignature, params.XBearerSignatureKey, *params.WalletConnect, *params.FullBearer)
if err != nil {
resp := a.logAndGetErrorResponse("invalid bearer token", err)
return operations.NewListStorageGroupsBadRequest().WithPayload(resp)
}
var filters object.SearchFilters
filters.AddTypeFilter(object.MatchStringEqual, object.TypeStorageGroup)
var prm pool.PrmObjectSearch
prm.SetContainerID(cnrID)
attachBearer(&prm, btoken)
prm.SetFilters(filters)
resSearch, err := a.pool.SearchObjects(ctx, prm)
if err != nil {
resp := a.logAndGetErrorResponse("failed to search objects", err)
return operations.NewListStorageGroupsBadRequest().WithPayload(resp)
}
var iterateErr error
var sgInfo *models.StorageGroupBaseInfo
var storageGroups []*models.StorageGroupBaseInfo
err = resSearch.Iterate(func(id oid.ID) bool {
if sgInfo, iterateErr = headObjectStorageGroupBaseInfo(ctx, a.pool, cnrID, id, btoken); iterateErr != nil {
return true
}
storageGroups = append(storageGroups, sgInfo)
return false
})
if err == nil {
err = iterateErr
}
if err != nil {
resp := a.logAndGetErrorResponse("failed to search storage groups", err)
return operations.NewListStorageGroupsBadRequest().WithPayload(resp)
}
resp := &models.StorageGroupList{
Size: util.NewInteger(int64(len(storageGroups))),
StorageGroups: storageGroups,
}
return operations.NewListStorageGroupsOK().WithPayload(resp)
}
func headObjectStorageGroupBaseInfo(ctx context.Context, p *pool.Pool, cnrID cid.ID, objID oid.ID, btoken *bearer.Token) (*models.StorageGroupBaseInfo, error) {
var addr oid.Address
addr.SetContainer(cnrID)
addr.SetObject(objID)
var prm pool.PrmObjectHead
prm.SetAddress(addr)
attachBearer(&prm, btoken)
objInfo, err := p.HeadObject(ctx, prm)
if err != nil {
return nil, fmt.Errorf("head object '%s': %w", objID.EncodeToString(), err)
}
resp := &models.StorageGroupBaseInfo{
Address: &models.Address{
ContainerID: util.NewString(cnrID.String()),
ObjectID: util.NewString(objID.String()),
},
}
expEpoch := "0"
for _, attr := range objInfo.Attributes() {
switch attr.Key() {
case object.AttributeFileName:
resp.Name = attr.Value()
case objectv2.SysAttributeExpEpoch:
if _, err = strconv.ParseUint(attr.Value(), 10, 64); err != nil {
return nil, fmt.Errorf("invalid expiration epoch '%s': %w", attr.Value(), err)
}
expEpoch = attr.Value()
}
}
resp.ExpirationEpoch = &expEpoch
return resp, nil
}
// DeleteStorageGroup handler that removes storage group from NeoFS.
func (a *API) DeleteStorageGroup(params operations.DeleteStorageGroupParams, principal *models.Principal) middleware.Responder {
ctx := params.HTTPRequest.Context()
addr, err := parseAddress(params.ContainerID, params.StorageGroupID)
if err != nil {
resp := a.logAndGetErrorResponse("invalid address", err)
return operations.NewDeleteStorageGroupBadRequest().WithPayload(resp)
}
btoken, err := getBearerToken(principal, params.XBearerSignature, params.XBearerSignatureKey, *params.WalletConnect, *params.FullBearer)
if err != nil {
resp := a.logAndGetErrorResponse("failed to get bearer token", err)
return operations.NewDeleteStorageGroupBadRequest().WithPayload(resp)
}
var prm pool.PrmObjectDelete
prm.SetAddress(addr)
attachBearer(&prm, btoken)
if err = a.pool.DeleteObject(ctx, prm); err != nil {
resp := a.logAndGetErrorResponse("failed to delete storage group", err)
return operations.NewDeleteStorageGroupBadRequest().WithPayload(resp)
}
return operations.NewDeleteStorageGroupOK().WithPayload(util.NewSuccessResponse())
}
// GetStorageGroup handler that get storage group info.
func (a *API) GetStorageGroup(params operations.GetStorageGroupParams, principal *models.Principal) middleware.Responder {
errorResponse := operations.NewGetObjectInfoBadRequest()
ctx := params.HTTPRequest.Context()
addr, err := parseAddress(params.ContainerID, params.StorageGroupID)
if err != nil {
resp := a.logAndGetErrorResponse("invalid address", err)
return errorResponse.WithPayload(resp)
}
btoken, err := getBearerToken(principal, params.XBearerSignature, params.XBearerSignatureKey, *params.WalletConnect, *params.FullBearer)
if err != nil {
resp := a.logAndGetErrorResponse("get bearer token", err)
return errorResponse.WithPayload(resp)
}
var prm pool.PrmObjectGet
prm.SetAddress(addr)
attachBearer(&prm, btoken)
objRes, err := a.pool.GetObject(ctx, prm)
if err != nil {
resp := a.logAndGetErrorResponse("get storage group object", err)
return errorResponse.WithPayload(resp)
}
sb, err := a.readStorageGroup(objRes)
if err != nil {
resp := a.logAndGetErrorResponse("read storage group", err)
return errorResponse.WithPayload(resp)
}
var sbHash string
cs, ok := sb.ValidationDataHash()
if ok {
sbHash = hex.EncodeToString(cs.Value())
}
members := make([]string, len(sb.Members()))
for i, objID := range sb.Members() {
members[i] = objID.EncodeToString()
}
resp := &models.StorageGroup{
Address: &models.Address{
ContainerID: util.NewString(addr.Container().String()),
ObjectID: util.NewString(addr.Object().String()),
},
ExpirationEpoch: util.NewString(strconv.FormatUint(sb.ExpirationEpoch(), 10)),
Size: util.NewString(strconv.FormatUint(sb.ValidationDataSize(), 10)),
Hash: sbHash,
Members: members,
Name: getStorageGroupName(objRes.Header),
}
return operations.NewGetStorageGroupOK().WithPayload(resp)
}
func getStorageGroupName(obj object.Object) string {
for _, attribute := range obj.Attributes() {
if attribute.Key() == object.AttributeFileName {
return attribute.Value()
}
}
return ""
}
func (a *API) readStorageGroup(objRes pool.ResGetObject) (*storagegroup.StorageGroup, error) {
buf := bytes.NewBuffer(nil)
if _, err := io.Copy(buf, objRes.Payload); err != nil {
return nil, fmt.Errorf("failed to copy storage group payload: %w", err)
}
obj := objRes.Header
obj.SetPayload(buf.Bytes())
var sb storagegroup.StorageGroup
if err := storagegroup.ReadFromObject(&sb, obj); err != nil {
return nil, fmt.Errorf("read storage group from object: %w", err)
}
return &sb, nil
}
func (a *API) formStorageGroup(ctx context.Context, cnrID cid.ID, btoken *bearer.Token, storageGroup *models.StorageGroupPutBody) (*storagegroup.StorageGroup, error) {
members, err := a.parseStorageGroupMembers(storageGroup)
if err != nil {
return nil, fmt.Errorf("parse storage group members: %w", err)
}
hashDisabled, err := isHomomorphicHashingDisabled(ctx, a.pool, cnrID)
if err != nil {
return nil, fmt.Errorf("check if homomorphic hash disabled: %w", err)
}
sgSize, cs, err := a.getStorageGroupSizeAndHash(ctx, cnrID, btoken, members, !hashDisabled)
if err != nil {
return nil, fmt.Errorf("get storage group size: %w", err)
}
networkInfo, err := a.pool.NetworkInfo(ctx)
if err != nil {
return nil, fmt.Errorf("get network info: %w", err)
}
var sg storagegroup.StorageGroup
sg.SetMembers(members)
sg.SetValidationDataSize(sgSize)
sg.SetExpirationEpoch(networkInfo.CurrentEpoch() + uint64(*storageGroup.Lifetime))
if !hashDisabled {
sg.SetValidationDataHash(*cs)
}
return &sg, nil
}
func (a *API) putStorageGroupObject(ctx context.Context, cnrID cid.ID, btoken *bearer.Token, fileName string, sg storagegroup.StorageGroup) (*oid.ID, error) {
var attrFileName object.Attribute
attrFileName.SetKey(object.AttributeFileName)
attrFileName.SetValue(fileName)
obj := object.New()
obj.SetContainerID(cnrID)
attachOwner(obj, btoken)
obj.SetAttributes(attrFileName)
storagegroup.WriteToObject(sg, obj)
var prmPut pool.PrmObjectPut
prmPut.SetHeader(*obj)
attachBearer(&prmPut, btoken)
objID, err := a.pool.PutObject(ctx, prmPut)
if err != nil {
return nil, fmt.Errorf("put object: %w", err)
}
return &objID, nil
}
func (a *API) getStorageGroupSizeAndHash(ctx context.Context, cnrID cid.ID, btoken *bearer.Token, members []oid.ID, needCalcHash bool) (uint64, *checksum.Checksum, error) {
var (
sgSize uint64
objHashes [][]byte
addr oid.Address
prm pool.PrmObjectHead
)
addr.SetContainer(cnrID)
attachBearer(&prm, btoken)
for _, objID := range members {
addr.SetObject(objID)
prm.SetAddress(addr)
objInfo, err := a.pool.HeadObject(ctx, prm)
if err != nil {
return 0, nil, fmt.Errorf("chead object from storage group members, id '%s': %w", objID.EncodeToString(), err)
}
sgSize += objInfo.PayloadSize()
if needCalcHash {
cs, _ := objInfo.PayloadHomomorphicHash()
objHashes = append(objHashes, cs.Value())
}
}
if needCalcHash {
sumHash, err := tz.Concat(objHashes)
if err != nil {
return 0, nil, fmt.Errorf("concat tz hashes: %w", err)
}
var cs checksum.Checksum
tzHash := [64]byte{}
copy(tzHash[:], sumHash)
cs.SetTillichZemor(tzHash)
return sgSize, &cs, nil
}
return sgSize, nil, nil
}
func (a *API) parseStorageGroupMembers(storageGroup *models.StorageGroupPutBody) ([]oid.ID, error) {
var err error
members := make([]oid.ID, len(storageGroup.Members))
uniqueFilter := make(map[oid.ID]struct{}, len(members))
for i, objIDStr := range storageGroup.Members {
if err = members[i].DecodeString(objIDStr); err != nil {
return nil, fmt.Errorf("invalid object id '%s': %w", objIDStr, err)
}
if _, ok := uniqueFilter[members[i]]; ok {
return nil, fmt.Errorf("invalid storage group members: duplicate id '%s': %w", objIDStr, err)
}
uniqueFilter[members[i]] = struct{}{}
}
return members, nil
}
func isHomomorphicHashingDisabled(ctx context.Context, p *pool.Pool, cnrID cid.ID) (bool, error) {
var prm pool.PrmContainerGet
prm.SetContainerID(cnrID)
cnr, err := p.GetContainer(ctx, prm)
if err != nil {
return false, fmt.Errorf("get container: %w", err)
}
return container.IsHomomorphicHashingDisabled(cnr), nil
}

View file

@ -56,6 +56,12 @@ parameters:
type: string
required: true
description: Base58 encoded object id.
storageGroupId:
in: path
name: storageGroupId
type: string
required: true
description: Base58 encoded storage group id.
paths:
/auth:
@ -557,6 +563,89 @@ paths:
schema:
$ref: '#/definitions/ErrorResponse'
/containers/{containerId}/storagegroups:
parameters:
- $ref: '#/parameters/containerId'
put:
operationId: putStorageGroup
summary: Create a new storage group in container.
parameters:
- $ref: '#/parameters/signatureParam'
- $ref: '#/parameters/signatureKeyParam'
- $ref: '#/parameters/signatureScheme'
- $ref: '#/parameters/fullBearerToken'
- in: body
name: storageGroup
required: true
description: Storage group co create.
schema:
$ref: '#/definitions/StorageGroupPutBody'
responses:
200:
description: Address of uploaded storage group.
schema:
$ref: '#/definitions/Address'
400:
description: Bad request.
schema:
$ref: '#/definitions/ErrorResponse'
get:
operationId: listStorageGroups
summary: Find all storage groups in container.
parameters:
- $ref: '#/parameters/signatureParam'
- $ref: '#/parameters/signatureKeyParam'
- $ref: '#/parameters/signatureScheme'
- $ref: '#/parameters/fullBearerToken'
responses:
200:
description: List of storage groups.
schema:
$ref: '#/definitions/StorageGroupList'
400:
description: Bad request.
schema:
$ref: '#/definitions/ErrorResponse'
/containers/{containerId}/storagegroups/{storageGroupId}:
parameters:
- $ref: '#/parameters/containerId'
- $ref: '#/parameters/storageGroupId'
get:
operationId: getStorageGroup
summary: Get storage group info.
parameters:
- $ref: '#/parameters/signatureParam'
- $ref: '#/parameters/signatureKeyParam'
- $ref: '#/parameters/signatureScheme'
- $ref: '#/parameters/fullBearerToken'
responses:
200:
description: Storage group information.
schema:
$ref: '#/definitions/StorageGroup'
400:
description: Bad request.
schema:
$ref: '#/definitions/ErrorResponse'
delete:
operationId: deleteStorageGroup
summary: Delete storage group from container.
parameters:
- $ref: '#/parameters/signatureParam'
- $ref: '#/parameters/signatureKeyParam'
- $ref: '#/parameters/signatureScheme'
- $ref: '#/parameters/fullBearerToken'
responses:
200:
description: Successful deletion.
schema:
$ref: '#/definitions/SuccessResponse'
400:
description: Bad request.
schema:
$ref: '#/definitions/ErrorResponse'
definitions:
BinaryBearer:
description: Bearer token for object operations that is represented in binary form.
@ -1030,6 +1119,107 @@ definitions:
value: myfile
targets:
- role: OTHERS
StorageGroupPutBody:
type: object
properties:
name:
description: Name of storage group. It will be the value of the `FileName` attribute in storage group object.
type: string
lifetime:
description: Lifetime in epochs for storage group.
type: integer
members:
description: Object identifiers to be placed into storage group. Must be unique.
type: array
items:
type: string
required:
- lifetime
- members
example:
name: my-storage-group
lifetime: 100
members:
- 8N3o7Dtr6T1xteCt6eRwhpmJ7JhME58Hyu1dvaswuTDd
StorageGroup:
description: Storage group keeps verification information for Data Audit sessions.
type: object
properties:
name:
description: Name of storage group. It will be the value of the `FileName` attribute in storage group object.
type: string
address:
description: Address of storage group object. Set by server.
readOnly: true
$ref: '#/definitions/Address'
expirationEpoch:
description: Expiration epoch of storage group.
type: string
hash:
description: Homomorphic hash from the concatenation of the payloads of the storage group members. Empty means hashing is disabled.
type: string
size:
description: Total size of the payloads of objects in the storage group.
type: string
members:
description: Object identifiers to be placed into storage group. Must be unique.
type: array
items:
type: string
required:
- address
- expirationEpoch
- size
- members
example:
name: my-storage-group
address:
objectId: 9N3o7Dtr6T1xteCt6eRwhpmJ7JhME58Hyu1dvaswuTDd
containerId: 5HZTn5qkRnmgSz9gSrw22CEdPPk6nQhkwf2Mgzyvkikv
expirationEpoch: 5000
size: 4096
members:
- 8N3o7Dtr6T1xteCt6eRwhpmJ7JhME58Hyu1dvaswuTDd
StorageGroupBaseInfo:
description: Storage group info for listing.
type: object
properties:
name:
type: string
address:
$ref: '#/definitions/Address'
expirationEpoch:
type: string
required:
- address
- expirationEpoch
example:
name: my-storage-group
address:
objectId: 9N3o7Dtr6T1xteCt6eRwhpmJ7JhME58Hyu1dvaswuTDd
containerId: 5HZTn5qkRnmgSz9gSrw22CEdPPk6nQhkwf2Mgzyvkikv
expirationEpoch: 5000
StorageGroupList:
description: List of storage groups.
type: object
properties:
size:
type: integer
storageGroups:
type: array
items:
$ref: '#/definitions/StorageGroupBaseInfo'
required:
- size
- storageGroups
example:
size: 1
storageGroups:
- name: my-storage-group
address:
objectId: 9N3o7Dtr6T1xteCt6eRwhpmJ7JhME58Hyu1dvaswuTDd
containerId: 5HZTn5qkRnmgSz9gSrw22CEdPPk6nQhkwf2Mgzyvkikv
expirationEpoch: 5000
Attribute:
description: Attribute is a pair of strings that can be attached to a container or an object.
type: object

View file

@ -75,4 +75,24 @@ Also, you can use this attribute to further object searching.
More about FrostFS status code you can
find [here](https://github.com/nspcc-dev/neofs-spec/blob/master/20-api-v2/status.md).
### Storage groups
The concept of a storage group has been introduced to reduce the dependence of the complexity of
the check on the number of stored objects in the system.
The consistency and availability of multiple objects on the network are achieved by validating the
storage group without saving meta information and performing validation on each object.
`StorageGroup` keeps verification information for Data Audit sessions. Objects that require paid storage
guaranties are gathered in `StorageGroups` with additional information used for proof of storage
checks. A `StorageGroup` can be created only for objects from the same container.
A `StorageGroup` are objects of a special type with the payload containing the serialized protobuf
structure. For the details on the format please refer to the [API specification](https://github.com/nspcc-dev/neofs-spec/blob/master/20-api-v2/storagegroup.md) in the corresponding section.
StorageGroup structure has information about:
* Total size of the payloads of objects in the storage group
* Homomorphic hash from the concatenation of the payloads of the storage group members. The order of concatenation is the same as the order of the members in the members field.
* Last NeoFS epoch number of the storage group lifetime
* Alpha-numerically sorted list of member objects