diff --git a/pkg/client/container.go b/pkg/client/container.go index 29b9871..6c7ef73 100644 --- a/pkg/client/container.go +++ b/pkg/client/container.go @@ -153,6 +153,20 @@ func (c Client) SetEACL(ctx context.Context, eacl *eacl.Table, opts ...CallOptio } } +// AnnounceContainerUsedSpace used by storage nodes to estimate their container +// sizes during lifetime. Use it only in storage node applications. +func (c Client) AnnounceContainerUsedSpace( + ctx context.Context, + announce []container.UsedSpaceAnnouncement, + opts ...CallOption) error { + switch c.remoteNode.Version.Major() { + case 2: + return c.announceContainerUsedSpaceV2(ctx, announce, opts...) + default: + return errUnsupportedProtocol + } +} + func (c Client) putContainerV2(ctx context.Context, cnr *container.Container, opts ...CallOption) (*container.ID, error) { // apply all available options callOptions := c.defaultCallOptions() @@ -495,6 +509,60 @@ func (c Client) setEACLV2(ctx context.Context, eacl *eacl.Table, opts ...CallOpt } } +func (c Client) announceContainerUsedSpaceV2( + ctx context.Context, + announce []container.UsedSpaceAnnouncement, + opts ...CallOption) error { + callOptions := c.defaultCallOptions() // apply all available options + + for i := range opts { + opts[i].apply(&callOptions) + } + + // convert list of SDK announcement structures into NeoFS-API v2 list + v2announce := make([]*v2container.UsedSpaceAnnouncement, 0, len(announce)) + for i := range announce { + v2announce = append(v2announce, announce[i].ToV2()) + } + + // prepare body of the NeoFS-API v2 request and request itself + reqBody := new(v2container.AnnounceUsedSpaceRequestBody) + reqBody.SetAnnouncements(v2announce) + + req := new(v2container.AnnounceUsedSpaceRequest) + req.SetBody(reqBody) + req.SetMetaHeader(v2MetaHeaderFromOpts(callOptions)) + + // sign the request + err := v2signature.SignServiceMessage(c.key, req) + if err != nil { + return err + } + + // choose underline transport protocol and send message over it + switch c.remoteNode.Protocol { + case GRPC: + cli, err := v2ContainerClientFromOptions(c.opts) + if err != nil { + return errors.Wrap(err, "can't create grpc client") + } + + resp, err := cli.AnnounceUsedSpace(ctx, req) + if err != nil { + return errors.Wrap(err, "transport error") + } + + err = v2signature.VerifyServiceMessage(resp) + if err != nil { + return errors.Wrap(err, "can't verify response message") + } + + return nil + default: + return errUnsupportedProtocol + } +} + func v2ContainerClientFromOptions(opts *clientOptions) (cli *v2container.Client, err error) { switch { case opts.grpcOpts.v2ContainerClient != nil: diff --git a/pkg/container/announcement.go b/pkg/container/announcement.go new file mode 100644 index 0000000..412272b --- /dev/null +++ b/pkg/container/announcement.go @@ -0,0 +1,47 @@ +package container + +import ( + "github.com/nspcc-dev/neofs-api-go/v2/container" +) + +// UsedSpaceAnnouncement is an announcement message used by storage nodes to +// estimate actual container sizes. +type UsedSpaceAnnouncement container.UsedSpaceAnnouncement + +// NewAnnouncement initialize empty UsedSpaceAnnouncement message. +func NewAnnouncement() *UsedSpaceAnnouncement { + return NewAnnouncementFromV2(new(container.UsedSpaceAnnouncement)) +} + +// NewAnnouncementFromV2 wraps protocol dependent version of +// UsedSpaceAnnouncement message. +func NewAnnouncementFromV2(v *container.UsedSpaceAnnouncement) *UsedSpaceAnnouncement { + return (*UsedSpaceAnnouncement)(v) +} + +// ContainerID of the announcement. +func (a *UsedSpaceAnnouncement) ContainerID() *ID { + return NewIDFromV2( + (*container.UsedSpaceAnnouncement)(a).GetContainerID(), + ) +} + +// SetContainerID sets announcement container value. +func (a *UsedSpaceAnnouncement) SetContainerID(cid *ID) { + (*container.UsedSpaceAnnouncement)(a).SetContainerID(cid.ToV2()) +} + +// UsedSpace in container. +func (a *UsedSpaceAnnouncement) UsedSpace() uint64 { + return (*container.UsedSpaceAnnouncement)(a).GetUsedSpace() +} + +// SetUsedSpace sets used space value by specified container. +func (a *UsedSpaceAnnouncement) SetUsedSpace(value uint64) { + (*container.UsedSpaceAnnouncement)(a).SetUsedSpace(value) +} + +// ToV2 returns protocol dependent version of UsedSpaceAnnouncement message. +func (a *UsedSpaceAnnouncement) ToV2() *container.UsedSpaceAnnouncement { + return (*container.UsedSpaceAnnouncement)(a) +} diff --git a/pkg/container/announcement_test.go b/pkg/container/announcement_test.go new file mode 100644 index 0000000..3fca102 --- /dev/null +++ b/pkg/container/announcement_test.go @@ -0,0 +1,44 @@ +package container_test + +import ( + "testing" + + "github.com/nspcc-dev/neofs-api-go/pkg/container" + "github.com/nspcc-dev/neofs-api-go/v2/refs" + "github.com/stretchr/testify/require" +) + +func TestAnnouncement(t *testing.T) { + const usedSpace uint64 = 100 + + cidValue := [32]byte{1, 2, 3} + cid := container.NewID() + cid.SetSHA256(cidValue) + + a := container.NewAnnouncement() + a.SetContainerID(cid) + a.SetUsedSpace(usedSpace) + + require.Equal(t, usedSpace, a.UsedSpace()) + require.Equal(t, cid, a.ContainerID()) + + t.Run("test v2", func(t *testing.T) { + const newUsedSpace uint64 = 200 + + newCidValue := [32]byte{4, 5, 6} + newCID := new(refs.ContainerID) + newCID.SetValue(newCidValue[:]) + + v2 := a.ToV2() + require.Equal(t, usedSpace, v2.GetUsedSpace()) + require.Equal(t, cidValue[:], v2.GetContainerID().GetValue()) + + v2.SetUsedSpace(newUsedSpace) + v2.SetContainerID(newCID) + + newA := container.NewAnnouncementFromV2(v2) + + require.Equal(t, newUsedSpace, newA.UsedSpace()) + require.Equal(t, container.NewIDFromV2(newCID), newA.ContainerID()) + }) +}