From c20eed98f9fa0e68e39ff1fd52593ba4dce5216a Mon Sep 17 00:00:00 2001 From: Denis Kirillov Date: Tue, 2 Aug 2022 16:48:26 +0300 Subject: [PATCH] [#36] Add route to list storage groups Signed-off-by: Denis Kirillov --- gen/models/storage_group_base_info.go | 127 +++++++++++++ gen/models/storage_group_list.go | 136 ++++++++++++++ gen/restapi/embedded_spec.go | 146 +++++++++++++++ gen/restapi/operations/frostfs_rest_gw_api.go | 12 ++ gen/restapi/operations/list_storage_groups.go | 71 +++++++ .../list_storage_groups_parameters.go | 175 ++++++++++++++++++ .../list_storage_groups_responses.go | 102 ++++++++++ handlers/api.go | 2 + handlers/storagegroup.go | 99 ++++++++++ spec/rest.yaml | 43 +++++ 10 files changed, 913 insertions(+) create mode 100644 gen/models/storage_group_base_info.go create mode 100644 gen/models/storage_group_list.go create mode 100644 gen/restapi/operations/list_storage_groups.go create mode 100644 gen/restapi/operations/list_storage_groups_parameters.go create mode 100644 gen/restapi/operations/list_storage_groups_responses.go diff --git a/gen/models/storage_group_base_info.go b/gen/models/storage_group_base_info.go new file mode 100644 index 0000000..1cdcb25 --- /dev/null +++ b/gen/models/storage_group_base_info.go @@ -0,0 +1,127 @@ +// 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. +// +// 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..57f3c3b --- /dev/null +++ b/gen/models/storage_group_list.go @@ -0,0 +1,136 @@ +// 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. +// +// 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/restapi/embedded_spec.go b/gen/restapi/embedded_spec.go index c90344c..cea86d5 100644 --- a/gen/restapi/embedded_spec.go +++ b/gen/restapi/embedded_spec.go @@ -524,6 +524,35 @@ 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" + } + ], + "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", @@ -1602,6 +1631,44 @@ func init() { } } }, + "StorageGroupBaseInfo": { + "description": "Storage group info for listing.", + "type": "object", + "required": [ + "address", + "expirationEpoch" + ], + "properties": { + "address": { + "$ref": "#/definitions/Address" + }, + "expirationEpoch": { + "type": "string" + }, + "name": { + "type": "string" + } + } + }, + "StorageGroupList": { + "description": "List of storage groups.", + "type": "object", + "required": [ + "size", + "storageGroups" + ], + "properties": { + "size": { + "type": "integer" + }, + "storageGroups": { + "type": "array", + "items": { + "$ref": "#/definitions/StorageGroupBaseInfo" + } + } + } + }, "SuccessResponse": { "description": "Success response.", "type": "object", @@ -2301,6 +2368,47 @@ 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", + "required": true + }, + { + "type": "string", + "description": "Hex encoded the public part of the key that signed the bearer token.", + "name": "X-Bearer-Signature-Key", + "in": "header", + "required": true + }, + { + "type": "boolean", + "default": false, + "description": "Use wallet connect signature scheme or native NeoFS signature.", + "name": "walletConnect", + "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", @@ -3466,6 +3574,44 @@ func init() { } } }, + "StorageGroupBaseInfo": { + "description": "Storage group info for listing.", + "type": "object", + "required": [ + "address", + "expirationEpoch" + ], + "properties": { + "address": { + "$ref": "#/definitions/Address" + }, + "expirationEpoch": { + "type": "string" + }, + "name": { + "type": "string" + } + } + }, + "StorageGroupList": { + "description": "List of storage groups.", + "type": "object", + "required": [ + "size", + "storageGroups" + ], + "properties": { + "size": { + "type": "integer" + }, + "storageGroups": { + "type": "array", + "items": { + "$ref": "#/definitions/StorageGroupBaseInfo" + } + } + } + }, "SuccessResponse": { "description": "Success response.", "type": "object", diff --git a/gen/restapi/operations/frostfs_rest_gw_api.go b/gen/restapi/operations/frostfs_rest_gw_api.go index d7e8a9f..b5b5726 100644 --- a/gen/restapi/operations/frostfs_rest_gw_api.go +++ b/gen/restapi/operations/frostfs_rest_gw_api.go @@ -71,6 +71,9 @@ func NewFrostfsRestGwAPI(spec *loads.Document) *FrostfsRestGwAPI { 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") }), @@ -178,6 +181,8 @@ type FrostfsRestGwAPI struct { GetObjectInfoHandler GetObjectInfoHandler // 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 @@ -312,6 +317,9 @@ func (o *FrostfsRestGwAPI) Validate() error { if o.ListContainersHandler == nil { unregistered = append(unregistered, "ListContainersHandler") } + if o.ListStorageGroupsHandler == nil { + unregistered = append(unregistered, "ListStorageGroupsHandler") + } if o.OptionsAuthHandler == nil { unregistered = append(unregistered, "OptionsAuthHandler") } @@ -486,6 +494,10 @@ func (o *FrostfsRestGwAPI) initHandlerCache() { 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) } diff --git a/gen/restapi/operations/list_storage_groups.go b/gen/restapi/operations/list_storage_groups.go new file mode 100644 index 0000000..f02a4df --- /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/nspcc-dev/neofs-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..0a4505c --- /dev/null +++ b/gen/restapi/operations/list_storage_groups_parameters.go @@ -0,0 +1,175 @@ +// 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" + "github.com/go-openapi/validate" +) + +// NewListStorageGroupsParams creates a new ListStorageGroupsParams object +// with the default values initialized. +func NewListStorageGroupsParams() ListStorageGroupsParams { + + var ( + // initialize parameters with default values + + walletConnectDefault = bool(false) + ) + + return ListStorageGroupsParams{ + 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. + Required: true + In: header + */ + XBearerSignature string + /*Hex encoded the public part of the key that signed the bearer token. + Required: true + In: header + */ + XBearerSignatureKey string + /*Base58 encoded container id. + Required: true + In: path + */ + ContainerID string + /*Use wallet connect signature scheme or native NeoFS 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) + } + + 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 { + if !hasKey { + return errors.Required("X-Bearer-Signature", "header", rawData) + } + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: true + + if err := validate.RequiredString("X-Bearer-Signature", "header", raw); err != nil { + return err + } + 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 { + if !hasKey { + return errors.Required("X-Bearer-Signature-Key", "header", rawData) + } + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: true + + if err := validate.RequiredString("X-Bearer-Signature-Key", "header", raw); err != nil { + return err + } + 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 +} + +// 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..9e8541d --- /dev/null +++ b/gen/restapi/operations/list_storage_groups_responses.go @@ -0,0 +1,102 @@ +// 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/nspcc-dev/neofs-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/handlers/api.go b/handlers/api.go index ce9055c..be60c01 100644 --- a/handlers/api.go +++ b/handlers/api.go @@ -127,7 +127,9 @@ func (a *API) Configure(api *operations.FrostfsRestGwAPI) http.Handler { 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.BearerAuthAuth = func(s string) (*models.Principal, error) { if !strings.HasPrefix(s, BearerPrefix) { diff --git a/handlers/storagegroup.go b/handlers/storagegroup.go index 7ff5e96..2fb90d1 100644 --- a/handlers/storagegroup.go +++ b/handlers/storagegroup.go @@ -3,8 +3,10 @@ package handlers import ( "context" "fmt" + "strconv" "github.com/go-openapi/runtime/middleware" + objectv2 "github.com/nspcc-dev/neofs-api-go/v2/object" "github.com/nspcc-dev/neofs-rest-gw/gen/models" "github.com/nspcc-dev/neofs-rest-gw/gen/restapi/operations" "github.com/nspcc-dev/neofs-rest-gw/internal/util" @@ -54,6 +56,103 @@ func (a *API) PutStorageGroup(params operations.PutStorageGroupParams, principal 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) + 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) + prm.UseBearer(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) + prm.UseBearer(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 +} + func (a *API) formStorageGroup(ctx context.Context, cnrID cid.ID, btoken bearer.Token, storageGroup *models.StorageGroup) (*storagegroup.StorageGroup, error) { members, err := a.parseStorageGroupMembers(storageGroup) if err != nil { diff --git a/spec/rest.yaml b/spec/rest.yaml index 8e6c603..1224813 100644 --- a/spec/rest.yaml +++ b/spec/rest.yaml @@ -556,6 +556,7 @@ paths: description: Bad request. schema: $ref: '#/definitions/ErrorResponse' + /containers/{containerId}/storagegroups: parameters: - $ref: '#/parameters/containerId' @@ -581,6 +582,22 @@ paths: 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' + responses: + 200: + description: List of storage groups. + schema: + $ref: '#/definitions/StorageGroupList' + 400: + description: Bad request. + schema: + $ref: '#/definitions/ErrorResponse' definitions: BinaryBearer: @@ -1081,6 +1098,32 @@ definitions: required: - lifetime - members + StorageGroupBaseInfo: + description: Storage group info for listing. + type: object + properties: + name: + type: string + address: + $ref: '#/definitions/Address' + expirationEpoch: + type: string + required: + - address + - expirationEpoch + StorageGroupList: + description: List of storage groups. + type: object + properties: + size: + type: integer + storageGroups: + type: array + items: + $ref: '#/definitions/StorageGroupBaseInfo' + required: + - size + - storageGroups Attribute: description: Attribute is a pair of strings that can be attached to a container or an object. type: object