diff --git a/pkg/morph/client/container/client.go b/pkg/morph/client/container/client.go index e6e10f5de..dd9fb5d1f 100644 --- a/pkg/morph/client/container/client.go +++ b/pkg/morph/client/container/client.go @@ -29,6 +29,9 @@ type Option func(*cfg) type cfg struct { putMethod, // put container method name for invocation + putSizeMethod, // put container size method name for invocation + listSizesMethod, // list container sizes method name for invocation + getSizeMethod, // get container size method name for invocation deleteMethod, // delete container method name for invocation getMethod, // get container method name for invocation listMethod, // list container method name for invocation @@ -48,6 +51,10 @@ const ( defaultStartEstimation = "startContainerEstimation" defaultStopEstimation = "stopContainerEstimation" + + defaultPutSizeMethod = "putContainerSize" // default "put container size" method name + defaultListSizesMethod = "listContainerSizes" // default "list container sizes" method name + defaultGetSizeMethod = "getContainerSize" // default "get container size" method name ) func defaultConfig() *cfg { @@ -60,6 +67,10 @@ func defaultConfig() *cfg { eaclMethod: defaultEACLMethod, startEstimation: defaultStartEstimation, stopEstimation: defaultStopEstimation, + + putSizeMethod: defaultPutSizeMethod, + listSizesMethod: defaultListSizesMethod, + getSizeMethod: defaultGetSizeMethod, } } @@ -76,6 +87,9 @@ func defaultConfig() *cfg { // * get eACL method name: EACL. // * start estimation method name: startContainerEstimation // * stop estimation method name: stopContainerEstimation +// * put container size method name: putContainerSize +// * get container size method name: getContainerSize +// * list container sizes method name: listContainerSizes // // If desired option satisfies the default value, it can be omitted. // If multiple options of the same config value are supplied, @@ -209,3 +223,45 @@ func WithStopEstimationMethod(n string) Option { } } } + +// WithPutSizeMethod returns a client constructor option that +// specifies the method name of "put container size" operation. +// +// Ignores empty value. +// +// If option not provided, "putContainerSize" is used. +func WithPutSizeMethod(n string) Option { + return func(c *cfg) { + if n != "" { + c.putSizeMethod = n + } + } +} + +// WithListSizesMethod returns a client constructor option that +// specifies the method name of "list container sizes" operation. +// +// Ignores empty value. +// +// If option not provided, "listContainerSizes" is used. +func WithListSizesMethod(n string) Option { + return func(c *cfg) { + if n != "" { + c.listSizesMethod = n + } + } +} + +// WithGetSizeMethod returns a client constructor option that +// specifies the method name of "get container size" operation. +// +// Ignores empty value. +// +// If option not provided, "getContainerSize" is used. +func WithGetSizeMethod(n string) Option { + return func(c *cfg) { + if n != "" { + c.getSizeMethod = n + } + } +} diff --git a/pkg/morph/client/container/load.go b/pkg/morph/client/container/load.go new file mode 100644 index 000000000..00be06762 --- /dev/null +++ b/pkg/morph/client/container/load.go @@ -0,0 +1,207 @@ +package container + +import ( + "github.com/nspcc-dev/neofs-node/pkg/morph/client" + "github.com/pkg/errors" +) + +// PutSizeArgs groups the arguments +// of "put container size" invocation call. +type PutSizeArgs struct { + epoch int64 + + size int64 + + cid []byte + + reporterKey []byte +} + +// SetEpoch sets the number of the epoch when +// size was estimated. +func (p *PutSizeArgs) SetEpoch(v uint64) { + p.epoch = int64(v) +} + +// SetSize sets estimation of the container size. +func (p *PutSizeArgs) SetSize(v uint64) { + p.size = int64(v) +} + +// SetContainerID sets identifier of the container +// being evaluated. +func (p *PutSizeArgs) SetContainerID(v []byte) { + p.cid = v +} + +// SetReporterKey ั‹ัƒะตั‹ public key of the storage node +// that collected size estimation. +func (p *PutSizeArgs) SetReporterKey(v []byte) { + p.reporterKey = v +} + +// Put invokes the call of put container method +// of NeoFS Container contract. +func (c *Client) PutSize(args PutSizeArgs) error { + return errors.Wrapf(c.client.Invoke( + c.putSizeMethod, + args.epoch, + args.cid, + args.size, + args.reporterKey, + ), "could not invoke method (%s)", c.putSizeMethod) +} + +// ListSizesArgs groups the arguments +// of "list container sizes" test invoke call.. +type ListSizesArgs struct { + epoch int64 +} + +// SetEpoch sets the number of the epoch to select +// the estimations. +func (p *ListSizesArgs) SetEpoch(v uint64) { + p.epoch = int64(v) +} + +// ListSizesValues groups the stack items +// returned by "list container sizes" test invoke. +type ListSizesValues struct { + ids [][]byte +} + +// Announcements returns list of identifiers of the +// container load estimations. +func (v *ListSizesValues) IDList() [][]byte { + return v.ids +} + +// List performs the test invoke of "list container sizes" +// method of NeoFS Container contract. +func (c *Client) ListSizes(args ListSizesArgs) (*ListSizesValues, error) { + prms, err := c.client.TestInvoke( + c.listSizesMethod, + args.epoch, + ) + if err != nil { + return nil, errors.Wrapf(err, "could not perform test invocation (%s)", c.listSizesMethod) + } else if ln := len(prms); ln != 1 { + return nil, errors.Errorf("unexpected stack item count (%s): %d", c.listSizesMethod, ln) + } + + prms, err = client.ArrayFromStackItem(prms[0]) + if err != nil { + return nil, errors.Wrapf(err, "could not get stack item array from stack item (%s)", c.listSizesMethod) + } + + res := &ListSizesValues{ + ids: make([][]byte, 0, len(prms)), + } + + for i := range prms { + id, err := client.BytesFromStackItem(prms[i]) + if err != nil { + return nil, errors.Wrapf(err, "could not get ID byte array from stack item (%s)", c.listSizesMethod) + } + + res.ids = append(res.ids, id) + } + + return res, nil +} + +// GetSizeArgs groups the arguments +// of "get container size" test invoke call.. +type GetSizeArgs struct { + id []byte +} + +// SetID sets identifier of the container estimation. +func (p *GetSizeArgs) SetID(v []byte) { + p.id = v +} + +type Estimation struct { + Size int64 + + Reporter []byte +} + +type Estimations struct { + ContainerID []byte + + Estimations []Estimation +} + +// GetSizeValues groups the stack items +// returned by "get container size" test invoke. +type GetSizeValues struct { + est Estimations +} + +// Estimations returns list of the container load estimations. +func (v *GetSizeValues) Estimations() Estimations { + return v.est +} + +// GetContainerSize performs the test invoke of "get container size" +// method of NeoFS Container contract. +func (c *Client) GetContainerSize(args GetSizeArgs) (*GetSizeValues, error) { + prms, err := c.client.TestInvoke( + c.getSizeMethod, + args.id, + ) + if err != nil { + return nil, errors.Wrapf(err, "could not perform test invocation (%s)", c.getSizeMethod) + } else if ln := len(prms); ln != 1 { + return nil, errors.Errorf("unexpected stack item count (%s): %d", c.getSizeMethod, ln) + } + + prms, err = client.ArrayFromStackItem(prms[0]) + if err != nil { + return nil, errors.Wrapf(err, "could not get stack items of estimation fields from stack item (%s)", c.getSizeMethod) + } else if ln := len(prms); ln != 2 { + return nil, errors.Errorf("unexpected stack item count of estimations fields (%s)", c.getSizeMethod) + } + + es := Estimations{} + + es.ContainerID, err = client.BytesFromStackItem(prms[0]) + if err != nil { + return nil, errors.Wrapf(err, "could not get container ID byte array from stack item (%s)", c.getSizeMethod) + } + + prms, err = client.ArrayFromStackItem(prms[1]) + if err != nil { + return nil, errors.Wrapf(err, "could not get estimation list array from stack item (%s)", c.getSizeMethod) + } + + es.Estimations = make([]Estimation, 0, len(prms)) + + for i := range prms { + arr, err := client.ArrayFromStackItem(prms[i]) + if err != nil { + return nil, errors.Wrapf(err, "could not get estimation struct from stack item (%s)", c.getSizeMethod) + } else if ln := len(arr); ln != 2 { + return nil, errors.Errorf("unexpected stack item count of estimation fields (%s)", c.getSizeMethod) + } + + e := Estimation{} + + e.Reporter, err = client.BytesFromStackItem(arr[0]) + if err != nil { + return nil, errors.Wrapf(err, "could not get reporter byte array from stack item (%s)", c.getSizeMethod) + } + + e.Size, err = client.IntFromStackItem(arr[1]) + if err != nil { + return nil, errors.Wrapf(err, "could not get estimation size from stack item (%s)", c.getSizeMethod) + } + + es.Estimations = append(es.Estimations, e) + } + + return &GetSizeValues{ + est: es, + }, nil +} diff --git a/pkg/morph/client/container/wrapper/container.go b/pkg/morph/client/container/wrapper/container.go index d05f7cc5e..aa4da0d08 100644 --- a/pkg/morph/client/container/wrapper/container.go +++ b/pkg/morph/client/container/wrapper/container.go @@ -145,3 +145,93 @@ func (w *Wrapper) List(ownerID *owner.ID) ([]*container.ID, error) { return result, nil } + +// AnnounceLoad saves container size estimation calculated by storage node +// with key in NeoFS system through Container contract call. +// +// Returns any error encountered that caused the saving to interrupt. +func (w *Wrapper) AnnounceLoad(a container.UsedSpaceAnnouncement, key []byte) error { + v2 := a.ContainerID().ToV2() + if v2 == nil { + return errUnsupported // use other major version if there any + } + + args := client.PutSizeArgs{} + args.SetContainerID(v2.GetValue()) + args.SetEpoch(a.Epoch()) + args.SetSize(a.UsedSpace()) + args.SetReporterKey(key) + + return w.client.PutSize(args) +} + +// EstimationID is an identity of container load estimation inside Container contract. +type EstimationID []byte + +// ListLoadEstimationsByEpoch returns a list of container load estimations for to the specified epoch. +// The list is composed through Container contract call. +func (w *Wrapper) ListLoadEstimationsByEpoch(epoch uint64) ([]EstimationID, error) { + args := client.ListSizesArgs{} + args.SetEpoch(epoch) + + // ask RPC neo node to get serialized container + rpcAnswer, err := w.client.ListSizes(args) + if err != nil { + return nil, err + } + + rawIDs := rpcAnswer.IDList() + result := make([]EstimationID, 0, len(rawIDs)) + + for i := range rawIDs { + result = append(result, rawIDs[i]) + } + + return result, nil +} + +// Estimation is a structure of single container load estimation +// reported by storage node. +type Estimation struct { + Size uint64 + + Reporter []byte +} + +// Estimation is a structure of grouped container load estimation inside Container contract. +type Estimations struct { + ContainerID *container.ID + + Values []Estimation +} + +// GetUsedSpaceEstimations returns a list of container load estimations by ID. +// The list is composed through Container contract call. +func (w *Wrapper) GetUsedSpaceEstimations(id EstimationID) (*Estimations, error) { + args := client.GetSizeArgs{} + args.SetID(id) + + rpcAnswer, err := w.client.GetContainerSize(args) + if err != nil { + return nil, err + } + + es := rpcAnswer.Estimations() + + v2 := new(v2refs.ContainerID) + v2.SetValue(es.ContainerID) + + res := &Estimations{ + ContainerID: container.NewIDFromV2(v2), + Values: make([]Estimation, 0, len(es.Estimations)), + } + + for i := range es.Estimations { + res.Values = append(res.Values, Estimation{ + Size: uint64(es.Estimations[i].Size), + Reporter: es.Estimations[i].Reporter, + }) + } + + return res, nil +}