diff --git a/cmd/frostfs-rest-gw/integration_test.go b/cmd/frostfs-rest-gw/integration_test.go index 47f751a..83848a7 100644 --- a/cmd/frostfs-rest-gw/integration_test.go +++ b/cmd/frostfs-rest-gw/integration_test.go @@ -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) diff --git a/gen/models/storage_group.go b/gen/models/storage_group.go new file mode 100644 index 0000000..3f2b7b4 --- /dev/null +++ b/gen/models/storage_group.go @@ -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 +} diff --git a/gen/models/storage_group_base_info.go b/gen/models/storage_group_base_info.go new file mode 100644 index 0000000..0b2224a --- /dev/null +++ b/gen/models/storage_group_base_info.go @@ -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 +} diff --git a/gen/models/storage_group_list.go b/gen/models/storage_group_list.go new file mode 100644 index 0000000..8b4092b --- /dev/null +++ b/gen/models/storage_group_list.go @@ -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 +} diff --git a/gen/models/storage_group_put_body.go b/gen/models/storage_group_put_body.go new file mode 100644 index 0000000..1bc9b7e --- /dev/null +++ b/gen/models/storage_group_put_body.go @@ -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 +} diff --git a/gen/restapi/embedded_spec.go b/gen/restapi/embedded_spec.go index 95e7bd5..30b04f2 100644 --- a/gen/restapi/embedded_spec.go +++ b/gen/restapi/embedded_spec.go @@ -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": { diff --git a/gen/restapi/operations/delete_storage_group.go b/gen/restapi/operations/delete_storage_group.go new file mode 100644 index 0000000..b65b504 --- /dev/null +++ b/gen/restapi/operations/delete_storage_group.go @@ -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) + +} diff --git a/gen/restapi/operations/delete_storage_group_parameters.go b/gen/restapi/operations/delete_storage_group_parameters.go new file mode 100644 index 0000000..f85518f --- /dev/null +++ b/gen/restapi/operations/delete_storage_group_parameters.go @@ -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 +} diff --git a/gen/restapi/operations/delete_storage_group_responses.go b/gen/restapi/operations/delete_storage_group_responses.go new file mode 100644 index 0000000..5496f96 --- /dev/null +++ b/gen/restapi/operations/delete_storage_group_responses.go @@ -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 + } + } +} diff --git a/gen/restapi/operations/frostfs_rest_gw_api.go b/gen/restapi/operations/frostfs_rest_gw_api.go index fe9ddbc..2ce52a5 100644 --- a/gen/restapi/operations/frostfs_rest_gw_api.go +++ b/gen/restapi/operations/frostfs_rest_gw_api.go @@ -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) } diff --git a/gen/restapi/operations/get_storage_group.go b/gen/restapi/operations/get_storage_group.go new file mode 100644 index 0000000..67d9918 --- /dev/null +++ b/gen/restapi/operations/get_storage_group.go @@ -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) + +} diff --git a/gen/restapi/operations/get_storage_group_parameters.go b/gen/restapi/operations/get_storage_group_parameters.go new file mode 100644 index 0000000..965ff76 --- /dev/null +++ b/gen/restapi/operations/get_storage_group_parameters.go @@ -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 +} diff --git a/gen/restapi/operations/get_storage_group_responses.go b/gen/restapi/operations/get_storage_group_responses.go new file mode 100644 index 0000000..629e76f --- /dev/null +++ b/gen/restapi/operations/get_storage_group_responses.go @@ -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 + } + } +} diff --git a/gen/restapi/operations/list_storage_groups.go b/gen/restapi/operations/list_storage_groups.go new file mode 100644 index 0000000..4b6157a --- /dev/null +++ b/gen/restapi/operations/list_storage_groups.go @@ -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) + +} diff --git a/gen/restapi/operations/list_storage_groups_parameters.go b/gen/restapi/operations/list_storage_groups_parameters.go new file mode 100644 index 0000000..c94acfc --- /dev/null +++ b/gen/restapi/operations/list_storage_groups_parameters.go @@ -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 +} diff --git a/gen/restapi/operations/list_storage_groups_responses.go b/gen/restapi/operations/list_storage_groups_responses.go new file mode 100644 index 0000000..865e008 --- /dev/null +++ b/gen/restapi/operations/list_storage_groups_responses.go @@ -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 + } + } +} diff --git a/gen/restapi/operations/put_storage_group.go b/gen/restapi/operations/put_storage_group.go new file mode 100644 index 0000000..c908c21 --- /dev/null +++ b/gen/restapi/operations/put_storage_group.go @@ -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) + +} diff --git a/gen/restapi/operations/put_storage_group_parameters.go b/gen/restapi/operations/put_storage_group_parameters.go new file mode 100644 index 0000000..87cc67f --- /dev/null +++ b/gen/restapi/operations/put_storage_group_parameters.go @@ -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 +} diff --git a/gen/restapi/operations/put_storage_group_responses.go b/gen/restapi/operations/put_storage_group_responses.go new file mode 100644 index 0000000..d6e84eb --- /dev/null +++ b/gen/restapi/operations/put_storage_group_responses.go @@ -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 + } + } +} diff --git a/go.mod b/go.mod index 2516a16..5f16cf3 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/handlers/api.go b/handlers/api.go index 67af39f..98c66ae 100644 --- a/handlers/api.go +++ b/handlers/api.go @@ -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) { diff --git a/handlers/containers.go b/handlers/containers.go index 645cb31..89c8b14 100644 --- a/handlers/containers.go +++ b/handlers/containers.go @@ -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) diff --git a/handlers/storagegroup.go b/handlers/storagegroup.go new file mode 100644 index 0000000..93321a7 --- /dev/null +++ b/handlers/storagegroup.go @@ -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 +} diff --git a/spec/rest.yaml b/spec/rest.yaml index 5ddcba8..6909185 100644 --- a/spec/rest.yaml +++ b/spec/rest.yaml @@ -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 diff --git a/static/docs/docs.md b/static/docs/docs.md index e26c849..cb436f8 100644 --- a/static/docs/docs.md +++ b/static/docs/docs.md @@ -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