From 1c6d37e821a23c5fda3e811c50ad51791b06d718 Mon Sep 17 00:00:00 2001 From: Leonard Lyubich Date: Thu, 28 Jan 2021 18:58:57 +0300 Subject: [PATCH] [#328] morph/container: Add client methods related to size estimations Implement API methods of the Container contracts client corresponding to the calls of the contract for storing the used space of the container ("putContainerSize", "getContainerSize", "listContainerSizes"). Extend the wrapper over the client with methods abstracted from sidechen calls. In particular, the method of storing the value will be used to record the estimates of the used space of containers calculated by the network participants. Signed-off-by: Leonard Lyubich --- pkg/morph/client/container/client.go | 56 +++++ pkg/morph/client/container/load.go | 207 ++++++++++++++++++ .../client/container/wrapper/container.go | 90 ++++++++ 3 files changed, 353 insertions(+) create mode 100644 pkg/morph/client/container/load.go diff --git a/pkg/morph/client/container/client.go b/pkg/morph/client/container/client.go index e6e10f5d..dd9fb5d1 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 00000000..00be0676 --- /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 d05f7cc5..aa4da0d0 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 +}