[#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 <leonard@nspcc.ru>
This commit is contained in:
Leonard Lyubich 2021-01-28 18:58:57 +03:00 committed by Leonard Lyubich
parent 4415f8dc5b
commit 1c6d37e821
3 changed files with 353 additions and 0 deletions

View file

@ -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
}
}
}

View file

@ -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
}

View file

@ -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
}