[#36] Add route to put storage group

Signed-off-by: Denis Kirillov <denis@nspcc.ru>
This commit is contained in:
Denis Kirillov 2022-08-02 16:14:19 +03:00 committed by Denis Kirillov
parent ba464ddfa3
commit e5b9fd5f5a
10 changed files with 950 additions and 0 deletions

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

@ -0,0 +1,130 @@
// 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.
//
// swagger:model StorageGroup
type StorageGroup struct {
// Container id to which storage group is belong. Set by server.
// Read Only: true
ContainerID string `json:"containerId,omitempty"`
// 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"`
// Object id of storage group. Set by server.
// Read Only: true
ObjectID string `json:"objectId,omitempty"`
}
// Validate validates this storage group
func (m *StorageGroup) 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 *StorageGroup) validateLifetime(formats strfmt.Registry) error {
if err := validate.Required("lifetime", "body", m.Lifetime); 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
}
// 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.contextValidateContainerID(ctx, formats); err != nil {
res = append(res, err)
}
if err := m.contextValidateObjectID(ctx, formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *StorageGroup) contextValidateContainerID(ctx context.Context, formats strfmt.Registry) error {
if err := validate.ReadOnly(ctx, "containerId", "body", string(m.ContainerID)); err != nil {
return err
}
return nil
}
func (m *StorageGroup) contextValidateObjectID(ctx context.Context, formats strfmt.Registry) error {
if err := validate.ReadOnly(ctx, "objectId", "body", string(m.ObjectID)); err != nil {
return err
}
return nil
}
// MarshalBinary interface implementation
func (m *StorageGroup) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *StorageGroup) UnmarshalBinary(b []byte) error {
var res StorageGroup
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}

View file

@ -523,6 +523,51 @@ func init() {
} }
] ]
}, },
"/containers/{containerId}/storagegroups": {
"put": {
"summary": "Create a new storage group in container.",
"operationId": "putStorageGroup",
"parameters": [
{
"$ref": "#/parameters/signatureParam"
},
{
"$ref": "#/parameters/signatureKeyParam"
},
{
"$ref": "#/parameters/signatureScheme"
},
{
"description": "Storage group co create.",
"name": "storageGroup",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/StorageGroup"
}
}
],
"responses": {
"200": {
"description": "Address of uploaded storage group.",
"schema": {
"$ref": "#/definitions/Address"
}
},
"400": {
"description": "Bad request.",
"schema": {
"$ref": "#/definitions/ErrorResponse"
}
}
}
},
"parameters": [
{
"$ref": "#/parameters/containerId"
}
]
},
"/objects": { "/objects": {
"put": { "put": {
"consumes": [ "consumes": [
@ -1522,6 +1567,41 @@ func init() {
"MatchCommonPrefix" "MatchCommonPrefix"
] ]
}, },
"StorageGroup": {
"description": "Storage group keeps verification information for Data Audit sessions.",
"type": "object",
"required": [
"lifetime",
"members"
],
"properties": {
"containerId": {
"description": "Container id to which storage group is belong. Set by server.",
"type": "string",
"readOnly": true
},
"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"
},
"objectId": {
"description": "Object id of storage group. Set by server.",
"type": "string",
"readOnly": true
}
}
},
"SuccessResponse": { "SuccessResponse": {
"description": "Success response.", "description": "Success response.",
"type": "object", "type": "object",
@ -2220,6 +2300,67 @@ func init() {
} }
] ]
}, },
"/containers/{containerId}/storagegroups": {
"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",
"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"
},
{
"description": "Storage group co create.",
"name": "storageGroup",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/StorageGroup"
}
}
],
"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
}
]
},
"/objects": { "/objects": {
"put": { "put": {
"consumes": [ "consumes": [
@ -3290,6 +3431,41 @@ func init() {
"MatchCommonPrefix" "MatchCommonPrefix"
] ]
}, },
"StorageGroup": {
"description": "Storage group keeps verification information for Data Audit sessions.",
"type": "object",
"required": [
"lifetime",
"members"
],
"properties": {
"containerId": {
"description": "Container id to which storage group is belong. Set by server.",
"type": "string",
"readOnly": true
},
"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"
},
"objectId": {
"description": "Object id of storage group. Set by server.",
"type": "string",
"readOnly": true
}
}
},
"SuccessResponse": { "SuccessResponse": {
"description": "Success response.", "description": "Success response.",
"type": "object", "type": "object",

View file

@ -104,6 +104,9 @@ func NewFrostfsRestGwAPI(spec *loads.Document) *FrostfsRestGwAPI {
PutObjectHandler: PutObjectHandlerFunc(func(params PutObjectParams, principal *models.Principal) middleware.Responder { PutObjectHandler: PutObjectHandlerFunc(func(params PutObjectParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation PutObject has not yet been implemented") 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 { SearchObjectsHandler: SearchObjectsHandlerFunc(func(params SearchObjectsParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation SearchObjects has not yet been implemented") return middleware.NotImplemented("operation SearchObjects has not yet been implemented")
}), }),
@ -197,6 +200,8 @@ type FrostfsRestGwAPI struct {
PutContainerEACLHandler PutContainerEACLHandler PutContainerEACLHandler PutContainerEACLHandler
// PutObjectHandler sets the operation handler for the put object operation // PutObjectHandler sets the operation handler for the put object operation
PutObjectHandler PutObjectHandler 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 sets the operation handler for the search objects operation
SearchObjectsHandler SearchObjectsHandler SearchObjectsHandler SearchObjectsHandler
@ -340,6 +345,9 @@ func (o *FrostfsRestGwAPI) Validate() error {
if o.PutObjectHandler == nil { if o.PutObjectHandler == nil {
unregistered = append(unregistered, "PutObjectHandler") unregistered = append(unregistered, "PutObjectHandler")
} }
if o.PutStorageGroupHandler == nil {
unregistered = append(unregistered, "PutStorageGroupHandler")
}
if o.SearchObjectsHandler == nil { if o.SearchObjectsHandler == nil {
unregistered = append(unregistered, "SearchObjectsHandler") unregistered = append(unregistered, "SearchObjectsHandler")
} }
@ -522,6 +530,10 @@ func (o *FrostfsRestGwAPI) initHandlerCache() {
o.handlers["PUT"] = make(map[string]http.Handler) o.handlers["PUT"] = make(map[string]http.Handler)
} }
o.handlers["PUT"]["/objects"] = NewPutObject(o.context, o.PutObjectHandler) 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 { if o.handlers["POST"] == nil {
o.handlers["POST"] = make(map[string]http.Handler) o.handlers["POST"] = make(map[string]http.Handler)
} }

View file

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

View file

@ -0,0 +1,212 @@
// 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/nspcc-dev/neofs-rest-gw/gen/models"
)
// NewPutStorageGroupParams creates a new PutStorageGroupParams object
// with the default values initialized.
func NewPutStorageGroupParams() PutStorageGroupParams {
var (
// initialize parameters with default values
walletConnectDefault = bool(false)
)
return PutStorageGroupParams{
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.
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
/*Storage group co create.
Required: true
In: body
*/
StorageGroup *models.StorageGroup
/*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 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)
}
if runtime.HasBody(r) {
defer r.Body.Close()
var body models.StorageGroup
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 {
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 *PutStorageGroupParams) 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 *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
}
// bindWalletConnect binds and validates parameter WalletConnect from query.
func (o *PutStorageGroupParams) bindWalletConnect(rawData []string, hasKey bool, formats strfmt.Registry) error {
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: false
// AllowEmptyValue: false
if raw == "" { // empty values pass all other validations
// Default values have been previously initialized by NewPutStorageGroupParams()
return nil
}
value, err := swag.ConvertBool(raw)
if err != nil {
return errors.InvalidType("walletConnect", "query", "bool", raw)
}
o.WalletConnect = &value
return nil
}

View file

@ -0,0 +1,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"
)
// 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
}
}
}

View file

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

View file

@ -413,6 +413,10 @@ func createContainer(ctx context.Context, p *pool.Pool, stoken session.Container
container.WriteDomain(&cnr, domain) 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 var prm pool.PrmContainerPut
prm.SetContainer(cnr) prm.SetContainer(cnr)
prm.WithinSession(stoken) prm.WithinSession(stoken)

190
handlers/storagegroup.go Normal file
View file

@ -0,0 +1,190 @@
package handlers
import (
"context"
"fmt"
"github.com/go-openapi/runtime/middleware"
"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"
"github.com/nspcc-dev/neofs-sdk-go/bearer"
"github.com/nspcc-dev/neofs-sdk-go/checksum"
"github.com/nspcc-dev/neofs-sdk-go/container"
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
"github.com/nspcc-dev/neofs-sdk-go/object"
oid "github.com/nspcc-dev/neofs-sdk-go/object/id"
"github.com/nspcc-dev/neofs-sdk-go/pool"
"github.com/nspcc-dev/neofs-sdk-go/storagegroup"
"github.com/nspcc-dev/tzhash/tz"
)
// 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)
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)
}
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 {
return nil, fmt.Errorf("parse storage group members: %w", err)
}
needCalcHash, 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, needCalcHash)
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 needCalcHash {
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) {
owner := bearer.ResolveIssuer(btoken)
var attrFileName object.Attribute
attrFileName.SetKey(object.AttributeFileName)
attrFileName.SetValue(fileName)
obj := object.New()
obj.SetContainerID(cnrID)
obj.SetOwnerID(&owner)
obj.SetAttributes(attrFileName)
storagegroup.WriteToObject(sg, obj)
var prmPut pool.PrmObjectPut
prmPut.SetHeader(*obj)
prmPut.UseBearer(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)
prm.UseBearer(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.StorageGroup) ([]oid.ID, error) {
var err error
members := make([]oid.ID, len(storageGroup.Members))
uniqueFilter := make(map[oid.ID]struct{}, len(members))
for i, objIDStr := range storageGroup.Members {
if err = members[i].DecodeString(objIDStr); err != nil {
return nil, fmt.Errorf("invalid object id '%s': %w", objIDStr, err)
}
if _, ok := uniqueFilter[members[i]]; ok {
return nil, fmt.Errorf("invalid storage group members: duplicate id '%s': %w", objIDStr, err)
}
uniqueFilter[members[i]] = struct{}{}
}
return members, nil
}
func isHomomorphicHashingDisabled(ctx context.Context, p *pool.Pool, cnrID cid.ID) (bool, error) {
var prm pool.PrmContainerGet
prm.SetContainerID(cnrID)
cnr, err := p.GetContainer(ctx, prm)
if err != nil {
return false, fmt.Errorf("get container: %w", err)
}
return container.IsHomomorphicHashingDisabled(*cnr), nil
}

View file

@ -556,6 +556,31 @@ paths:
description: Bad request. description: Bad request.
schema: schema:
$ref: '#/definitions/ErrorResponse' $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'
- in: body
name: storageGroup
required: true
description: Storage group co create.
schema:
$ref: '#/definitions/StorageGroup'
responses:
200:
description: Address of uploaded storage group.
schema:
$ref: '#/definitions/Address'
400:
description: Bad request.
schema:
$ref: '#/definitions/ErrorResponse'
definitions: definitions:
BinaryBearer: BinaryBearer:
@ -1030,6 +1055,32 @@ definitions:
value: myfile value: myfile
targets: targets:
- role: OTHERS - role: OTHERS
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
containerId:
description: Container id to which storage group is belong. Set by server.
type: string
readOnly: true
objectId:
description: Object id of storage group. Set by server.
type: string
readOnly: true
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
Attribute: Attribute:
description: Attribute is a pair of strings that can be attached to a container or an object. description: Attribute is a pair of strings that can be attached to a container or an object.
type: object type: object