[#225] container: Refactor and document package functionality
Signed-off-by: Leonard Lyubich <leonard@nspcc.ru>
This commit is contained in:
parent
86a447bc80
commit
70845147f6
16 changed files with 1047 additions and 866 deletions
|
@ -2,6 +2,7 @@ package client
|
|||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
v2container "github.com/nspcc-dev/neofs-api-go/v2/container"
|
||||
|
@ -98,13 +99,14 @@ func (c *Client) ContainerPut(ctx context.Context, prm PrmContainerPut) (*ResCon
|
|||
|
||||
// TODO: check private key is set before forming the request
|
||||
// sign container
|
||||
cnr := prm.cnr.ToV2()
|
||||
var cnr v2container.Container
|
||||
prm.cnr.WriteToV2(&cnr)
|
||||
|
||||
var sig neofscrypto.Signature
|
||||
|
||||
err := sig.Calculate(neofsecdsa.SignerRFC6979(c.prm.key), cnr.StableMarshal(nil))
|
||||
err := container.CalculateSignature(&sig, prm.cnr, c.prm.key)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("calculate signature: %w", err)
|
||||
return nil, fmt.Errorf("calculate container signature: %w", err)
|
||||
}
|
||||
|
||||
var sigv2 refs.Signature
|
||||
|
@ -113,7 +115,7 @@ func (c *Client) ContainerPut(ctx context.Context, prm PrmContainerPut) (*ResCon
|
|||
|
||||
// form request body
|
||||
reqBody := new(v2container.PutRequestBody)
|
||||
reqBody.SetContainer(prm.cnr.ToV2())
|
||||
reqBody.SetContainer(&cnr)
|
||||
reqBody.SetSignature(&sigv2)
|
||||
|
||||
// form meta header
|
||||
|
@ -188,17 +190,17 @@ func (x *PrmContainerGet) SetContainer(id cid.ID) {
|
|||
type ResContainerGet struct {
|
||||
statusRes
|
||||
|
||||
cnr *container.Container
|
||||
cnr container.Container
|
||||
}
|
||||
|
||||
// Container returns structured information about the requested container.
|
||||
//
|
||||
// Client doesn't retain value so modification is safe.
|
||||
func (x ResContainerGet) Container() *container.Container {
|
||||
func (x ResContainerGet) Container() container.Container {
|
||||
return x.cnr
|
||||
}
|
||||
|
||||
func (x *ResContainerGet) setContainer(cnr *container.Container) {
|
||||
func (x *ResContainerGet) setContainer(cnr container.Container) {
|
||||
x.cnr = cnr
|
||||
}
|
||||
|
||||
|
@ -253,9 +255,19 @@ func (c *Client) ContainerGet(ctx context.Context, prm PrmContainerGet) (*ResCon
|
|||
cc.result = func(r responseV2) {
|
||||
resp := r.(*v2container.GetResponse)
|
||||
|
||||
body := resp.GetBody()
|
||||
cnrV2 := resp.GetBody().GetContainer()
|
||||
if cnrV2 == nil {
|
||||
cc.err = errors.New("missing container in response")
|
||||
return
|
||||
}
|
||||
|
||||
cnr := container.NewContainerFromV2(body.GetContainer())
|
||||
var cnr container.Container
|
||||
|
||||
err := cnr.ReadFromV2(*cnrV2)
|
||||
if err != nil {
|
||||
cc.err = fmt.Errorf("invalid container in response: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
res.setContainer(cnr)
|
||||
}
|
||||
|
@ -724,15 +736,15 @@ func (c *Client) ContainerSetEACL(ctx context.Context, prm PrmContainerSetEACL)
|
|||
type PrmAnnounceSpace struct {
|
||||
prmCommonMeta
|
||||
|
||||
announcements []container.UsedSpaceAnnouncement
|
||||
announcements []container.SizeEstimation
|
||||
}
|
||||
|
||||
// SetValues sets values describing volume of space that is used for the container objects.
|
||||
// Required parameter. Must not be empty.
|
||||
//
|
||||
// Must not be mutated before the end of the operation.
|
||||
func (x *PrmAnnounceSpace) SetValues(announcements []container.UsedSpaceAnnouncement) {
|
||||
x.announcements = announcements
|
||||
func (x *PrmAnnounceSpace) SetValues(vs []container.SizeEstimation) {
|
||||
x.announcements = vs
|
||||
}
|
||||
|
||||
// ResAnnounceSpace groups resulting values of ContainerAnnounceUsedSpace operation.
|
||||
|
@ -770,7 +782,7 @@ func (c *Client) ContainerAnnounceUsedSpace(ctx context.Context, prm PrmAnnounce
|
|||
// convert list of SDK announcement structures into NeoFS-API v2 list
|
||||
v2announce := make([]v2container.UsedSpaceAnnouncement, len(prm.announcements))
|
||||
for i := range prm.announcements {
|
||||
v2announce[i] = *prm.announcements[i].ToV2()
|
||||
prm.announcements[i].WriteToV2(&v2announce[i])
|
||||
}
|
||||
|
||||
// prepare body of the NeoFS-API v2 request and request itself
|
||||
|
|
|
@ -1,113 +0,0 @@
|
|||
package container
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/nspcc-dev/neofs-api-go/v2/container"
|
||||
"github.com/nspcc-dev/neofs-api-go/v2/refs"
|
||||
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
|
||||
)
|
||||
|
||||
// UsedSpaceAnnouncement is an announcement message used by storage nodes to
|
||||
// estimate actual container sizes.
|
||||
type UsedSpaceAnnouncement container.UsedSpaceAnnouncement
|
||||
|
||||
// NewAnnouncement initialize empty UsedSpaceAnnouncement message.
|
||||
//
|
||||
// Defaults:
|
||||
// - epoch: 0;
|
||||
// - usedSpace: 0;
|
||||
// - cid: nil.
|
||||
func NewAnnouncement() *UsedSpaceAnnouncement {
|
||||
return NewAnnouncementFromV2(new(container.UsedSpaceAnnouncement))
|
||||
}
|
||||
|
||||
// NewAnnouncementFromV2 wraps protocol dependent version of
|
||||
// UsedSpaceAnnouncement message.
|
||||
//
|
||||
// Nil container.UsedSpaceAnnouncement converts to nil.
|
||||
func NewAnnouncementFromV2(v *container.UsedSpaceAnnouncement) *UsedSpaceAnnouncement {
|
||||
return (*UsedSpaceAnnouncement)(v)
|
||||
}
|
||||
|
||||
// Epoch of the announcement.
|
||||
func (a *UsedSpaceAnnouncement) Epoch() uint64 {
|
||||
return (*container.UsedSpaceAnnouncement)(a).GetEpoch()
|
||||
}
|
||||
|
||||
// SetEpoch sets announcement epoch value.
|
||||
func (a *UsedSpaceAnnouncement) SetEpoch(epoch uint64) {
|
||||
(*container.UsedSpaceAnnouncement)(a).SetEpoch(epoch)
|
||||
}
|
||||
|
||||
// ContainerID of the announcement.
|
||||
func (a *UsedSpaceAnnouncement) ContainerID() (cID cid.ID, isSet bool) {
|
||||
v2 := (*container.UsedSpaceAnnouncement)(a)
|
||||
|
||||
cidV2 := v2.GetContainerID()
|
||||
if cidV2 == nil {
|
||||
return
|
||||
}
|
||||
|
||||
_ = cID.ReadFromV2(*cidV2)
|
||||
isSet = true
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// SetContainerID sets announcement container value.
|
||||
func (a *UsedSpaceAnnouncement) SetContainerID(cnr cid.ID) {
|
||||
var cidV2 refs.ContainerID
|
||||
cnr.WriteToV2(&cidV2)
|
||||
|
||||
(*container.UsedSpaceAnnouncement)(a).SetContainerID(&cidV2)
|
||||
}
|
||||
|
||||
// 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.
|
||||
//
|
||||
// Nil UsedSpaceAnnouncement converts to nil.
|
||||
func (a *UsedSpaceAnnouncement) ToV2() *container.UsedSpaceAnnouncement {
|
||||
return (*container.UsedSpaceAnnouncement)(a)
|
||||
}
|
||||
|
||||
// Marshal marshals UsedSpaceAnnouncement into a protobuf binary form.
|
||||
func (a *UsedSpaceAnnouncement) Marshal() ([]byte, error) {
|
||||
return a.ToV2().StableMarshal(nil), nil
|
||||
}
|
||||
|
||||
var errCIDNotSet = errors.New("container ID is not set")
|
||||
|
||||
// Unmarshal unmarshals protobuf binary representation of UsedSpaceAnnouncement.
|
||||
func (a *UsedSpaceAnnouncement) Unmarshal(data []byte) error {
|
||||
err := a.ToV2().Unmarshal(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// format checks
|
||||
|
||||
var cID cid.ID
|
||||
|
||||
cidV2 := a.ToV2().GetContainerID()
|
||||
if cidV2 == nil {
|
||||
return errCIDNotSet
|
||||
}
|
||||
|
||||
err = cID.ReadFromV2(*cidV2)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not convert V2 container ID: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -1,107 +0,0 @@
|
|||
package container_test
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"testing"
|
||||
|
||||
containerv2 "github.com/nspcc-dev/neofs-api-go/v2/container"
|
||||
"github.com/nspcc-dev/neofs-api-go/v2/refs"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/container"
|
||||
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
|
||||
cidtest "github.com/nspcc-dev/neofs-sdk-go/container/id/test"
|
||||
containertest "github.com/nspcc-dev/neofs-sdk-go/container/test"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestAnnouncement(t *testing.T) {
|
||||
const epoch, usedSpace uint64 = 10, 100
|
||||
|
||||
cidValue := [sha256.Size]byte{1, 2, 3}
|
||||
id := cidtest.IDWithChecksum(cidValue)
|
||||
|
||||
a := container.NewAnnouncement()
|
||||
a.SetEpoch(epoch)
|
||||
a.SetContainerID(id)
|
||||
a.SetUsedSpace(usedSpace)
|
||||
|
||||
require.Equal(t, epoch, a.Epoch())
|
||||
require.Equal(t, usedSpace, a.UsedSpace())
|
||||
cID, set := a.ContainerID()
|
||||
require.True(t, set)
|
||||
require.Equal(t, id, cID)
|
||||
|
||||
t.Run("test v2", func(t *testing.T) {
|
||||
const newEpoch, newUsedSpace uint64 = 20, 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, epoch, v2.GetEpoch())
|
||||
require.Equal(t, cidValue[:], v2.GetContainerID().GetValue())
|
||||
|
||||
v2.SetEpoch(newEpoch)
|
||||
v2.SetUsedSpace(newUsedSpace)
|
||||
v2.SetContainerID(newCID)
|
||||
|
||||
newA := container.NewAnnouncementFromV2(v2)
|
||||
|
||||
var cID cid.ID
|
||||
_ = cID.ReadFromV2(*newCID)
|
||||
|
||||
require.Equal(t, newEpoch, newA.Epoch())
|
||||
require.Equal(t, newUsedSpace, newA.UsedSpace())
|
||||
cIDNew, set := newA.ContainerID()
|
||||
require.True(t, set)
|
||||
require.Equal(t, cID, cIDNew)
|
||||
})
|
||||
}
|
||||
|
||||
func TestUsedSpaceEncoding(t *testing.T) {
|
||||
a := containertest.UsedSpaceAnnouncement()
|
||||
|
||||
t.Run("binary", func(t *testing.T) {
|
||||
data, err := a.Marshal()
|
||||
require.NoError(t, err)
|
||||
|
||||
a2 := container.NewAnnouncement()
|
||||
require.NoError(t, a2.Unmarshal(data))
|
||||
|
||||
require.Equal(t, a, a2)
|
||||
})
|
||||
}
|
||||
|
||||
func TestUsedSpaceAnnouncement_ToV2(t *testing.T) {
|
||||
t.Run("nil", func(t *testing.T) {
|
||||
var x *container.UsedSpaceAnnouncement
|
||||
|
||||
require.Nil(t, x.ToV2())
|
||||
})
|
||||
|
||||
t.Run("default values", func(t *testing.T) {
|
||||
announcement := container.NewAnnouncement()
|
||||
|
||||
// check initial values
|
||||
require.Zero(t, announcement.Epoch())
|
||||
require.Zero(t, announcement.UsedSpace())
|
||||
_, set := announcement.ContainerID()
|
||||
require.False(t, set)
|
||||
|
||||
// convert to v2 message
|
||||
announcementV2 := announcement.ToV2()
|
||||
|
||||
require.Zero(t, announcementV2.GetEpoch())
|
||||
require.Zero(t, announcementV2.GetUsedSpace())
|
||||
require.Nil(t, announcementV2.GetContainerID())
|
||||
})
|
||||
}
|
||||
|
||||
func TestNewAnnouncementFromV2(t *testing.T) {
|
||||
t.Run("from nil", func(t *testing.T) {
|
||||
var x *containerv2.UsedSpaceAnnouncement
|
||||
|
||||
require.Nil(t, container.NewAnnouncementFromV2(x))
|
||||
})
|
||||
}
|
|
@ -1,139 +0,0 @@
|
|||
package container
|
||||
|
||||
import (
|
||||
"github.com/nspcc-dev/neofs-api-go/v2/container"
|
||||
)
|
||||
|
||||
type (
|
||||
Attribute container.Attribute
|
||||
Attributes []Attribute
|
||||
)
|
||||
|
||||
// NewAttribute creates and initializes blank Attribute.
|
||||
//
|
||||
// Defaults:
|
||||
// - key: "";
|
||||
// - value: "".
|
||||
func NewAttribute() *Attribute {
|
||||
return NewAttributeFromV2(new(container.Attribute))
|
||||
}
|
||||
|
||||
func (a *Attribute) SetKey(v string) {
|
||||
(*container.Attribute)(a).SetKey(v)
|
||||
}
|
||||
|
||||
func (a *Attribute) SetValue(v string) {
|
||||
(*container.Attribute)(a).SetValue(v)
|
||||
}
|
||||
|
||||
func (a *Attribute) Key() string {
|
||||
return (*container.Attribute)(a).GetKey()
|
||||
}
|
||||
|
||||
func (a *Attribute) Value() string {
|
||||
return (*container.Attribute)(a).GetValue()
|
||||
}
|
||||
|
||||
// NewAttributeFromV2 wraps protocol dependent version of
|
||||
// Attribute message.
|
||||
//
|
||||
// Nil container.Attribute converts to nil.
|
||||
func NewAttributeFromV2(v *container.Attribute) *Attribute {
|
||||
return (*Attribute)(v)
|
||||
}
|
||||
|
||||
// ToV2 converts Attribute to v2 Attribute message.
|
||||
//
|
||||
// Nil Attribute converts to nil.
|
||||
func (a *Attribute) ToV2() *container.Attribute {
|
||||
return (*container.Attribute)(a)
|
||||
}
|
||||
|
||||
func NewAttributesFromV2(v []container.Attribute) Attributes {
|
||||
if v == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
attrs := make(Attributes, len(v))
|
||||
for i := range v {
|
||||
attrs[i] = *NewAttributeFromV2(&v[i])
|
||||
}
|
||||
|
||||
return attrs
|
||||
}
|
||||
|
||||
func (a Attributes) ToV2() []container.Attribute {
|
||||
if a == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
attrs := make([]container.Attribute, len(a))
|
||||
for i := range a {
|
||||
attrs[i] = *a[i].ToV2()
|
||||
}
|
||||
|
||||
return attrs
|
||||
}
|
||||
|
||||
// sets value of the attribute by key.
|
||||
func setAttribute(c *Container, key, value string) {
|
||||
attrs := c.Attributes()
|
||||
found := false
|
||||
|
||||
for i := range attrs {
|
||||
if attrs[i].Key() == key {
|
||||
attrs[i].SetValue(value)
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
index := len(attrs)
|
||||
attrs = append(attrs, Attribute{})
|
||||
attrs[index].SetKey(key)
|
||||
attrs[index].SetValue(value)
|
||||
}
|
||||
|
||||
c.SetAttributes(attrs)
|
||||
}
|
||||
|
||||
// iterates over container attributes. Stops at f's true return.
|
||||
//
|
||||
// Handler must not be nil.
|
||||
func iterateAttributes(c *Container, f func(*Attribute) bool) {
|
||||
attrs := c.Attributes()
|
||||
for i := range attrs {
|
||||
if f(&attrs[i]) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SetNativeNameWithZone sets container native name and its zone.
|
||||
//
|
||||
// Use SetNativeName to set default zone.
|
||||
func SetNativeNameWithZone(c *Container, name, zone string) {
|
||||
setAttribute(c, container.SysAttributeName, name)
|
||||
setAttribute(c, container.SysAttributeZone, zone)
|
||||
}
|
||||
|
||||
// SetNativeName sets container native name with default zone (container).
|
||||
func SetNativeName(c *Container, name string) {
|
||||
SetNativeNameWithZone(c, name, container.SysAttributeZoneDefault)
|
||||
}
|
||||
|
||||
// GetNativeNameWithZone returns container native name and its zone.
|
||||
func GetNativeNameWithZone(c *Container) (name string, zone string) {
|
||||
iterateAttributes(c, func(a *Attribute) bool {
|
||||
if key := a.Key(); key == container.SysAttributeName {
|
||||
name = a.Value()
|
||||
} else if key == container.SysAttributeZone {
|
||||
zone = a.Value()
|
||||
}
|
||||
|
||||
return name != "" && zone != ""
|
||||
})
|
||||
|
||||
return
|
||||
}
|
|
@ -1,152 +0,0 @@
|
|||
package container_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
containerv2 "github.com/nspcc-dev/neofs-api-go/v2/container"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/container"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestAttribute(t *testing.T) {
|
||||
t.Run("nil", func(t *testing.T) {
|
||||
var x *container.Attribute
|
||||
|
||||
require.Nil(t, x.ToV2())
|
||||
})
|
||||
|
||||
t.Run("default values", func(t *testing.T) {
|
||||
attr := container.NewAttribute()
|
||||
|
||||
// check initial values
|
||||
require.Empty(t, attr.Key())
|
||||
require.Empty(t, attr.Value())
|
||||
|
||||
// convert to v2 message
|
||||
attrV2 := attr.ToV2()
|
||||
require.Empty(t, attrV2.GetKey())
|
||||
require.Empty(t, attrV2.GetValue())
|
||||
})
|
||||
|
||||
const (
|
||||
key = "key"
|
||||
value = "value"
|
||||
)
|
||||
|
||||
attr := container.NewAttribute()
|
||||
attr.SetKey(key)
|
||||
attr.SetValue(value)
|
||||
|
||||
require.Equal(t, key, attr.Key())
|
||||
require.Equal(t, value, attr.Value())
|
||||
|
||||
t.Run("test v2", func(t *testing.T) {
|
||||
const (
|
||||
newKey = "newKey"
|
||||
newValue = "newValue"
|
||||
)
|
||||
|
||||
v2 := attr.ToV2()
|
||||
require.Equal(t, key, v2.GetKey())
|
||||
require.Equal(t, value, v2.GetValue())
|
||||
|
||||
v2.SetKey(newKey)
|
||||
v2.SetValue(newValue)
|
||||
|
||||
newAttr := container.NewAttributeFromV2(v2)
|
||||
|
||||
require.Equal(t, newKey, newAttr.Key())
|
||||
require.Equal(t, newValue, newAttr.Value())
|
||||
})
|
||||
}
|
||||
|
||||
func TestAttributes(t *testing.T) {
|
||||
t.Run("nil", func(t *testing.T) {
|
||||
var x container.Attributes
|
||||
|
||||
require.Nil(t, x.ToV2())
|
||||
|
||||
require.Nil(t, container.NewAttributesFromV2(nil))
|
||||
})
|
||||
|
||||
var (
|
||||
keys = []string{"key1", "key2", "key3"}
|
||||
vals = []string{"val1", "val2", "val3"}
|
||||
)
|
||||
|
||||
attrs := make(container.Attributes, len(keys))
|
||||
|
||||
for i := range keys {
|
||||
attrs[i].SetKey(keys[i])
|
||||
attrs[i].SetValue(vals[i])
|
||||
}
|
||||
|
||||
t.Run("test v2", func(t *testing.T) {
|
||||
const postfix = "x"
|
||||
|
||||
v2 := attrs.ToV2()
|
||||
require.Len(t, v2, len(keys))
|
||||
|
||||
for i := range v2 {
|
||||
k := v2[i].GetKey()
|
||||
v := v2[i].GetValue()
|
||||
|
||||
require.Equal(t, keys[i], k)
|
||||
require.Equal(t, vals[i], v)
|
||||
|
||||
v2[i].SetKey(k + postfix)
|
||||
v2[i].SetValue(v + postfix)
|
||||
}
|
||||
|
||||
newAttrs := container.NewAttributesFromV2(v2)
|
||||
require.Len(t, newAttrs, len(keys))
|
||||
|
||||
for i := range newAttrs {
|
||||
require.Equal(t, keys[i]+postfix, newAttrs[i].Key())
|
||||
require.Equal(t, vals[i]+postfix, newAttrs[i].Value())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestNewAttributeFromV2(t *testing.T) {
|
||||
t.Run("from nil", func(t *testing.T) {
|
||||
var x *containerv2.Attribute
|
||||
|
||||
require.Nil(t, container.NewAttributeFromV2(x))
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetNameWithZone(t *testing.T) {
|
||||
c := container.New()
|
||||
|
||||
for _, item := range [...]struct {
|
||||
name, zone string
|
||||
}{
|
||||
{"name1", ""},
|
||||
{"name1", "zone1"},
|
||||
{"name2", "zone1"},
|
||||
{"name2", "zone2"},
|
||||
{"", "zone2"},
|
||||
{"", ""},
|
||||
} {
|
||||
container.SetNativeNameWithZone(c, item.name, item.zone)
|
||||
|
||||
name, zone := container.GetNativeNameWithZone(c)
|
||||
|
||||
require.Equal(t, item.name, name, item.name)
|
||||
require.Equal(t, item.zone, zone, item.zone)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetNativeName(t *testing.T) {
|
||||
c := container.New()
|
||||
|
||||
const nameDefZone = "some name"
|
||||
|
||||
container.SetNativeName(c, nameDefZone)
|
||||
|
||||
name, zone := container.GetNativeNameWithZone(c)
|
||||
|
||||
require.Equal(t, nameDefZone, name)
|
||||
require.Equal(t, containerv2.SysAttributeZoneDefault, zone)
|
||||
}
|
|
@ -1,7 +1,12 @@
|
|||
package container
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/sha256"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/nspcc-dev/neofs-api-go/v2/container"
|
||||
|
@ -9,193 +14,507 @@ import (
|
|||
"github.com/nspcc-dev/neofs-api-go/v2/refs"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/container/acl"
|
||||
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
|
||||
neofscrypto "github.com/nspcc-dev/neofs-sdk-go/crypto"
|
||||
neofsecdsa "github.com/nspcc-dev/neofs-sdk-go/crypto/ecdsa"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/netmap"
|
||||
subnetid "github.com/nspcc-dev/neofs-sdk-go/subnet/id"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/user"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/version"
|
||||
)
|
||||
|
||||
// Container represents descriptor of the NeoFS container. Container logically
|
||||
// stores NeoFS objects. Container is one of the basic and at the same time
|
||||
// necessary data storage units in the NeoFS. Container includes data about the
|
||||
// owner, rules for placing objects and other information necessary for the
|
||||
// system functioning.
|
||||
//
|
||||
// Container type instances can represent different container states in the
|
||||
// system, depending on the context. To create new container in NeoFS zero
|
||||
// instance be created, initialized using Init method and filled using
|
||||
// dedicated methods. Once container is saved in the NeoFS network, it can't be
|
||||
// changed: containers stored in the system are immutable, and NeoFS is a CAS
|
||||
// of containers that are identified by a fixed length value (see cid.ID type).
|
||||
// Instances for existing containers can be initialized using decoding methods
|
||||
// (e.g Unmarshal).
|
||||
//
|
||||
// Container is mutually compatible with github.com/nspcc-dev/neofs-api-go/v2/container.Container
|
||||
// message. See ReadFromV2 / WriteToV2 methods.
|
||||
type Container struct {
|
||||
v2 container.Container
|
||||
}
|
||||
|
||||
// New creates, initializes and returns blank Container instance.
|
||||
const (
|
||||
attributeName = "Name"
|
||||
attributeTimestamp = "Timestamp"
|
||||
)
|
||||
|
||||
// reads Container from the container.Container message. If checkFieldPresence is set,
|
||||
// returns an error on absence of any protocol-required field.
|
||||
func (x *Container) readFromV2(m container.Container, checkFieldPresence bool) error {
|
||||
var err error
|
||||
|
||||
ownerV2 := m.GetOwnerID()
|
||||
if ownerV2 != nil {
|
||||
var owner user.ID
|
||||
|
||||
err = owner.ReadFromV2(*ownerV2)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid owner: %w", err)
|
||||
}
|
||||
} else if checkFieldPresence {
|
||||
return errors.New("missing owner")
|
||||
}
|
||||
|
||||
binNonce := m.GetNonce()
|
||||
if len(binNonce) > 0 {
|
||||
var nonce uuid.UUID
|
||||
|
||||
err = nonce.UnmarshalBinary(binNonce)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid nonce: %w", err)
|
||||
} else if ver := nonce.Version(); ver != 4 {
|
||||
return fmt.Errorf("invalid nonce UUID version %d", ver)
|
||||
}
|
||||
} else if checkFieldPresence {
|
||||
return errors.New("missing nonce")
|
||||
}
|
||||
|
||||
ver := m.GetVersion()
|
||||
if checkFieldPresence && ver == nil {
|
||||
return errors.New("missing version")
|
||||
}
|
||||
|
||||
policyV2 := m.GetPlacementPolicy()
|
||||
if policyV2 != nil {
|
||||
var policy netmap.PlacementPolicy
|
||||
|
||||
err = policy.ReadFromV2(*policyV2)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid placement policy: %w", err)
|
||||
}
|
||||
} else if checkFieldPresence {
|
||||
return errors.New("missing placement policy")
|
||||
}
|
||||
|
||||
attrs := m.GetAttributes()
|
||||
mAttr := make(map[string]struct{}, len(attrs))
|
||||
var key, val string
|
||||
var was bool
|
||||
|
||||
for i := range attrs {
|
||||
key = attrs[i].GetKey()
|
||||
if key == "" {
|
||||
return errors.New("empty attribute key")
|
||||
}
|
||||
|
||||
_, was = mAttr[key]
|
||||
if was {
|
||||
return fmt.Errorf("duplicated attribute %s", key)
|
||||
}
|
||||
|
||||
val = attrs[i].GetValue()
|
||||
if val == "" {
|
||||
return fmt.Errorf("empty attribute value %s", key)
|
||||
}
|
||||
|
||||
switch key {
|
||||
case container.SysAttributeSubnet:
|
||||
err = new(subnetid.ID).DecodeString(val)
|
||||
case attributeTimestamp:
|
||||
_, err = strconv.ParseInt(val, 10, 64)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid attribute value %s: %s (%w)", key, val, err)
|
||||
}
|
||||
|
||||
mAttr[key] = struct{}{}
|
||||
}
|
||||
|
||||
x.v2 = m
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReadFromV2 reads Container from the container.Container message. Checks if the
|
||||
// message conforms to NeoFS API V2 protocol.
|
||||
//
|
||||
// Defaults:
|
||||
// - token: nil;
|
||||
// - sig: nil;
|
||||
// - basicACL: Private;
|
||||
// - version: version.Current;
|
||||
// - nonce: random UUID;
|
||||
// - attr: nil;
|
||||
// - policy: nil;
|
||||
// - ownerID: nil.
|
||||
func New(opts ...Option) *Container {
|
||||
cnrOptions := defaultContainerOptions()
|
||||
|
||||
for i := range opts {
|
||||
opts[i](&cnrOptions)
|
||||
}
|
||||
|
||||
cnr := new(Container)
|
||||
cnr.SetNonceUUID(cnrOptions.nonce)
|
||||
cnr.SetBasicACL(cnrOptions.acl)
|
||||
|
||||
if cnrOptions.owner != nil {
|
||||
cnr.SetOwnerID(cnrOptions.owner)
|
||||
}
|
||||
|
||||
if cnrOptions.policy != nil {
|
||||
cnr.SetPlacementPolicy(cnrOptions.policy)
|
||||
}
|
||||
|
||||
cnr.SetAttributes(cnrOptions.attributes)
|
||||
ver := version.Current()
|
||||
cnr.SetVersion(&ver)
|
||||
|
||||
return cnr
|
||||
// See also WriteToV2.
|
||||
func (x *Container) ReadFromV2(m container.Container) error {
|
||||
return x.readFromV2(m, true)
|
||||
}
|
||||
|
||||
// ToV2 returns the v2 Container message.
|
||||
// WriteToV2 writes Container into the container.Container message.
|
||||
// The message MUST NOT be nil.
|
||||
//
|
||||
// Nil Container converts to nil.
|
||||
func (c *Container) ToV2() *container.Container {
|
||||
if c == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &c.v2
|
||||
// See also ReadFromV2.
|
||||
func (x Container) WriteToV2(m *container.Container) {
|
||||
*m = x.v2
|
||||
}
|
||||
|
||||
// NewVerifiedFromV2 constructs Container from NeoFS API V2 Container message.
|
||||
// Marshal encodes Container into a binary format of the NeoFS API protocol
|
||||
// (Protocol Buffers with direct field order).
|
||||
//
|
||||
// Does not perform if message meets NeoFS API V2 specification. To do this
|
||||
// use NewVerifiedFromV2 constructor.
|
||||
func NewContainerFromV2(c *container.Container) *Container {
|
||||
cnr := new(Container)
|
||||
// See also Unmarshal.
|
||||
func (x Container) Marshal() []byte {
|
||||
return x.v2.StableMarshal(nil)
|
||||
}
|
||||
|
||||
if c != nil {
|
||||
cnr.v2 = *c
|
||||
// Unmarshal decodes NeoFS API protocol binary format into the Container
|
||||
// (Protocol Buffers with direct field order). Returns an error describing
|
||||
// a format violation.
|
||||
//
|
||||
// See also Marshal.
|
||||
func (x *Container) Unmarshal(data []byte) error {
|
||||
var m container.Container
|
||||
|
||||
err := m.Unmarshal(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return cnr
|
||||
return x.readFromV2(m, false)
|
||||
}
|
||||
|
||||
// CalculateID calculates container identifier
|
||||
// based on its structure.
|
||||
func CalculateID(c *Container) cid.ID {
|
||||
var id cid.ID
|
||||
id.SetSHA256(sha256.Sum256(c.ToV2().StableMarshal(nil)))
|
||||
|
||||
return id
|
||||
// MarshalJSON encodes Container into a JSON format of the NeoFS API protocol
|
||||
// (Protocol Buffers JSON).
|
||||
//
|
||||
// See also UnmarshalJSON.
|
||||
func (x Container) MarshalJSON() ([]byte, error) {
|
||||
return x.v2.MarshalJSON()
|
||||
}
|
||||
|
||||
func (c *Container) Version() *version.Version {
|
||||
var ver version.Version
|
||||
if v2ver := c.v2.GetVersion(); v2ver != nil {
|
||||
ver.ReadFromV2(*c.v2.GetVersion())
|
||||
}
|
||||
return &ver
|
||||
// UnmarshalJSON decodes NeoFS API protocol JSON format into the Container
|
||||
// (Protocol Buffers JSON). Returns an error describing a format violation.
|
||||
//
|
||||
// See also MarshalJSON.
|
||||
func (x *Container) UnmarshalJSON(data []byte) error {
|
||||
return x.v2.UnmarshalJSON(data)
|
||||
}
|
||||
|
||||
func (c *Container) SetVersion(v *version.Version) {
|
||||
var verV2 refs.Version
|
||||
v.WriteToV2(&verV2)
|
||||
c.v2.SetVersion(&verV2)
|
||||
}
|
||||
// Init initializes all internal data of the Container required by NeoFS API
|
||||
// protocol. Init MUST be called when creating a new container. Init SHOULD NOT
|
||||
// be called multiple times. Init SHOULD NOT be called if the Container instance
|
||||
// is used for decoding only.
|
||||
func (x *Container) Init() {
|
||||
var ver refs.Version
|
||||
version.Current().WriteToV2(&ver)
|
||||
|
||||
func (c *Container) OwnerID() *user.ID {
|
||||
m := c.v2.GetOwnerID()
|
||||
if m == nil {
|
||||
return nil
|
||||
x.v2.SetVersion(&ver)
|
||||
|
||||
nonce, err := uuid.New().MarshalBinary()
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("unexpected error from UUID.MarshalBinary: %v", err))
|
||||
}
|
||||
|
||||
var id user.ID
|
||||
|
||||
_ = id.ReadFromV2(*m)
|
||||
|
||||
return &id
|
||||
x.v2.SetNonce(nonce)
|
||||
}
|
||||
|
||||
func (c *Container) SetOwnerID(v *user.ID) {
|
||||
// SetOwner specifies the owner of the Container. Each Container has exactly
|
||||
// one owner, so SetOwner MUST be called for instances to be saved in the
|
||||
// NeoFS.
|
||||
//
|
||||
// See also Owner.
|
||||
func (x *Container) SetOwner(owner user.ID) {
|
||||
var m refs.OwnerID
|
||||
v.WriteToV2(&m)
|
||||
owner.WriteToV2(&m)
|
||||
|
||||
c.v2.SetOwnerID(&m)
|
||||
x.v2.SetOwnerID(&m)
|
||||
}
|
||||
|
||||
// Returns container nonce in UUID format.
|
||||
// Owner returns owner of the Container set using SetOwner.
|
||||
//
|
||||
// Returns error if container nonce is not a valid UUID.
|
||||
func (c *Container) NonceUUID() (uuid.UUID, error) {
|
||||
return uuid.FromBytes(c.v2.GetNonce())
|
||||
}
|
||||
// Zero Container has no owner which is incorrect according to NeoFS API
|
||||
// protocol.
|
||||
func (x Container) Owner() (res user.ID) {
|
||||
m := x.v2.GetOwnerID()
|
||||
if m != nil {
|
||||
err := res.ReadFromV2(*m)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("unexpected error from user.ID.ReadFromV2: %v", err))
|
||||
}
|
||||
}
|
||||
|
||||
// SetNonceUUID sets container nonce as UUID.
|
||||
func (c *Container) SetNonceUUID(v uuid.UUID) {
|
||||
data, _ := v.MarshalBinary()
|
||||
c.v2.SetNonce(data)
|
||||
}
|
||||
|
||||
func (c *Container) BasicACL() (res acl.Basic) {
|
||||
res.FromBits(c.v2.GetBasicACL())
|
||||
return
|
||||
}
|
||||
|
||||
func (c *Container) SetBasicACL(v acl.Basic) {
|
||||
c.v2.SetBasicACL(v.Bits())
|
||||
// SetBasicACL specifies basic part of the Container ACL. Basic ACL is used
|
||||
// to control access inside container storage.
|
||||
//
|
||||
// See also BasicACL.
|
||||
func (x *Container) SetBasicACL(basicACL acl.Basic) {
|
||||
x.v2.SetBasicACL(basicACL.Bits())
|
||||
}
|
||||
|
||||
func (c *Container) Attributes() Attributes {
|
||||
return NewAttributesFromV2(c.v2.GetAttributes())
|
||||
// BasicACL returns basic ACL set using SetBasicACL.
|
||||
//
|
||||
// Zero Container has zero basic ACL which structurally correct but doesn't
|
||||
// make sense since it denies any access to any party.
|
||||
func (x Container) BasicACL() (res acl.Basic) {
|
||||
res.FromBits(x.v2.GetBasicACL())
|
||||
return
|
||||
}
|
||||
|
||||
func (c *Container) SetAttributes(v Attributes) {
|
||||
c.v2.SetAttributes(v.ToV2())
|
||||
// SetPlacementPolicy sets placement policy for the objects within the Container.
|
||||
// NeoFS storage layer strives to follow the specified policy.
|
||||
//
|
||||
// See also PlacementPolicy.
|
||||
func (x *Container) SetPlacementPolicy(policy netmap.PlacementPolicy) {
|
||||
var m v2netmap.PlacementPolicy
|
||||
policy.WriteToV2(&m)
|
||||
|
||||
x.v2.SetPlacementPolicy(&m)
|
||||
}
|
||||
|
||||
func (c *Container) PlacementPolicy() *netmap.PlacementPolicy {
|
||||
m := c.v2.GetPlacementPolicy()
|
||||
if m == nil {
|
||||
return nil
|
||||
// PlacementPolicy returns placement policy set using SetPlacementPolicy.
|
||||
//
|
||||
// Zero Container has no placement policy which is incorrect according to
|
||||
// NeoFS API protocol.
|
||||
func (x Container) PlacementPolicy() (res netmap.PlacementPolicy) {
|
||||
m := x.v2.GetPlacementPolicy()
|
||||
if m != nil {
|
||||
err := res.ReadFromV2(*m)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("unexpected error from PlacementPolicy.ReadFromV2: %v", err))
|
||||
}
|
||||
}
|
||||
|
||||
var p netmap.PlacementPolicy
|
||||
// FIXME(@cthulhu-rider): #225 handle error
|
||||
err := p.ReadFromV2(*m)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return
|
||||
}
|
||||
|
||||
// SetAttribute sets Container attribute value by key. Both key and value
|
||||
// MUST NOT be empty. Attributes set by the creator (owner) are most commonly
|
||||
// ignored by the NeoFS system and used for application layer. Some attributes
|
||||
// are so-called system or well-known attributes: they are reserved for system
|
||||
// needs. System attributes SHOULD NOT be modified using SetAttribute, use
|
||||
// corresponding methods/functions. List of the reserved keys is documented
|
||||
// in the particular protocol version.
|
||||
//
|
||||
// SetAttribute overwrites existing attribute value.
|
||||
//
|
||||
// See also Attribute, IterateAttributes.
|
||||
func (x *Container) SetAttribute(key, value string) {
|
||||
if key == "" {
|
||||
panic("empty attribute key")
|
||||
} else if value == "" {
|
||||
panic("empty attribute value")
|
||||
}
|
||||
|
||||
return &p
|
||||
}
|
||||
attrs := x.v2.GetAttributes()
|
||||
ln := len(attrs)
|
||||
|
||||
func (c *Container) SetPlacementPolicy(v *netmap.PlacementPolicy) {
|
||||
var m *v2netmap.PlacementPolicy
|
||||
|
||||
if v != nil {
|
||||
m = new(v2netmap.PlacementPolicy)
|
||||
v.WriteToV2(m)
|
||||
for i := 0; i < ln; i++ {
|
||||
if attrs[i].GetKey() == key {
|
||||
attrs[i].SetValue(value)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
c.v2.SetPlacementPolicy(m)
|
||||
attrs = append(attrs, container.Attribute{})
|
||||
attrs[ln].SetKey(key)
|
||||
attrs[ln].SetValue(value)
|
||||
|
||||
x.v2.SetAttributes(attrs)
|
||||
}
|
||||
|
||||
// Marshal marshals Container into a protobuf binary form.
|
||||
func (c *Container) Marshal() ([]byte, error) {
|
||||
return c.v2.StableMarshal(nil), nil
|
||||
// Attribute reads value of the Container attribute by key. Empty result means
|
||||
// attribute absence.
|
||||
//
|
||||
// See also SetAttribute, IterateAttributes.
|
||||
func (x Container) Attribute(key string) string {
|
||||
attrs := x.v2.GetAttributes()
|
||||
for i := range attrs {
|
||||
if attrs[i].GetKey() == key {
|
||||
return attrs[i].GetValue()
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// Unmarshal unmarshals protobuf binary representation of Container.
|
||||
func (c *Container) Unmarshal(data []byte) error {
|
||||
return c.v2.Unmarshal(data)
|
||||
// IterateAttributes iterates over all Container attributes and passes them
|
||||
// into f. The handler MUST NOT be nil.
|
||||
//
|
||||
// See also SetAttribute, Attribute.
|
||||
func (x Container) IterateAttributes(f func(key, val string)) {
|
||||
attrs := x.v2.GetAttributes()
|
||||
for i := range attrs {
|
||||
f(attrs[i].GetKey(), attrs[i].GetValue())
|
||||
}
|
||||
}
|
||||
|
||||
// MarshalJSON encodes Container to protobuf JSON format.
|
||||
func (c *Container) MarshalJSON() ([]byte, error) {
|
||||
return c.v2.MarshalJSON()
|
||||
// SetName sets human-readable name of the Container. Name MUST NOT be empty.
|
||||
//
|
||||
// See also Name.
|
||||
func SetName(cnr *Container, name string) {
|
||||
cnr.SetAttribute(attributeName, name)
|
||||
}
|
||||
|
||||
// UnmarshalJSON decodes Container from protobuf JSON format.
|
||||
func (c *Container) UnmarshalJSON(data []byte) error {
|
||||
return c.v2.UnmarshalJSON(data)
|
||||
// Name returns container name set using SetName.
|
||||
//
|
||||
// Zero Container has no name.
|
||||
func Name(cnr Container) string {
|
||||
return cnr.Attribute(attributeName)
|
||||
}
|
||||
|
||||
// SetCreationTime writes container's creation time in Unix Timestamp format.
|
||||
//
|
||||
// See also CreatedAt.
|
||||
func SetCreationTime(cnr *Container, t time.Time) {
|
||||
cnr.SetAttribute(attributeTimestamp, strconv.FormatInt(t.Unix(), 10))
|
||||
}
|
||||
|
||||
// CreatedAt returns container's creation time set using SetCreationTime.
|
||||
//
|
||||
// Zero Container has zero timestamp (in seconds).
|
||||
func CreatedAt(cnr Container) time.Time {
|
||||
var sec int64
|
||||
|
||||
attr := cnr.Attribute(attributeTimestamp)
|
||||
if attr != "" {
|
||||
var err error
|
||||
|
||||
sec, err = strconv.ParseInt(cnr.Attribute(attributeTimestamp), 10, 64)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("parse container timestamp: %v", err))
|
||||
}
|
||||
}
|
||||
|
||||
return time.Unix(sec, 0)
|
||||
}
|
||||
|
||||
// SetSubnet places the Container on the specified NeoFS subnet. If called,
|
||||
// container nodes will only be selected from the given subnet, otherwise from
|
||||
// the entire network.
|
||||
func SetSubnet(cnr *Container, subNet subnetid.ID) {
|
||||
cnr.SetAttribute(container.SysAttributeSubnet, subNet.EncodeToString())
|
||||
}
|
||||
|
||||
// Subnet return container subnet set using SetSubnet.
|
||||
//
|
||||
// Zero Container is bound to zero subnet.
|
||||
func Subnet(cnr Container) (res subnetid.ID) {
|
||||
val := cnr.Attribute(container.SysAttributeSubnet)
|
||||
if val != "" {
|
||||
err := res.DecodeString(val)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("invalid subnet attribute: %s (%v)", val, err))
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
const attributeHomoHashEnabled = "true"
|
||||
|
||||
// DisableHomomorphicHashing sets flag to disable homomorphic hashing of the
|
||||
// Container data.
|
||||
//
|
||||
// See also IsHomomorphicHashingDisabled.
|
||||
func DisableHomomorphicHashing(cnr *Container) {
|
||||
cnr.SetAttribute(container.SysAttributeHomomorphicHashing, attributeHomoHashEnabled)
|
||||
}
|
||||
|
||||
// IsHomomorphicHashingDisabled checks if DisableHomomorphicHashing was called.
|
||||
//
|
||||
// Zero Container has enabled hashing.
|
||||
func IsHomomorphicHashingDisabled(cnr Container) bool {
|
||||
return cnr.Attribute(container.SysAttributeHomomorphicHashing) == attributeHomoHashEnabled
|
||||
}
|
||||
|
||||
// Domain represents information about container domain registered in the NNS
|
||||
// contract deployed in the NeoFS network.
|
||||
type Domain struct {
|
||||
name, zone string
|
||||
}
|
||||
|
||||
// SetName sets human-friendly container domain name.
|
||||
func (x *Domain) SetName(name string) {
|
||||
x.name = name
|
||||
}
|
||||
|
||||
// Name returns name set using SetName.
|
||||
//
|
||||
// Zero Domain has zero name.
|
||||
func (x Domain) Name() string {
|
||||
return x.name
|
||||
}
|
||||
|
||||
// SetZone sets zone which is used as a TLD of a domain name in NNS contract.
|
||||
func (x *Domain) SetZone(zone string) {
|
||||
x.zone = zone
|
||||
}
|
||||
|
||||
// Zone returns domain zone set using SetZone.
|
||||
//
|
||||
// Zero Domain has "container" zone.
|
||||
func (x Domain) Zone() string {
|
||||
if x.zone != "" {
|
||||
return x.zone
|
||||
}
|
||||
|
||||
return "container"
|
||||
}
|
||||
|
||||
// WriteDomain writes Domain into the Container. Name MUST NOT be empty.
|
||||
func WriteDomain(cnr *Container, domain Domain) {
|
||||
cnr.SetAttribute(container.SysAttributeName, domain.Name())
|
||||
cnr.SetAttribute(container.SysAttributeZone, domain.Zone())
|
||||
}
|
||||
|
||||
// ReadDomain reads Domain from the Container. Returns value with empty name
|
||||
// if domain is not specified.
|
||||
func ReadDomain(cnr Container) (res Domain) {
|
||||
name := cnr.Attribute(container.SysAttributeName)
|
||||
if name != "" {
|
||||
res.SetName(name)
|
||||
res.SetZone(cnr.Attribute(container.SysAttributeZone))
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// CalculateSignature calculates signature of the Container using provided signer
|
||||
// and writes it into dst. Signature instance MUST NOT be nil. CalculateSignature
|
||||
// is expected to be called after all the Container data is filled and before
|
||||
// saving the Container in the NeoFS network. Note that мany subsequent change
|
||||
// will most likely break the signature.
|
||||
//
|
||||
// See also VerifySignature.
|
||||
func CalculateSignature(dst *neofscrypto.Signature, cnr Container, signer ecdsa.PrivateKey) error {
|
||||
return dst.Calculate(neofsecdsa.SignerRFC6979(signer), cnr.Marshal())
|
||||
}
|
||||
|
||||
// VerifySignature verifies Container signature calculated using CalculateSignature.
|
||||
// Result means signature correctness.
|
||||
func VerifySignature(sig neofscrypto.Signature, cnr Container) bool {
|
||||
return sig.Verify(cnr.Marshal())
|
||||
}
|
||||
|
||||
// CalculateIDFromBinary calculates identifier of the binary-encoded container
|
||||
// in CAS of the NeoFS containers and writes it into dst. ID instance MUST NOT
|
||||
// be nil.
|
||||
//
|
||||
// See also CalculateID, AssertID.
|
||||
func CalculateIDFromBinary(dst *cid.ID, cnr []byte) {
|
||||
dst.SetSHA256(sha256.Sum256(cnr))
|
||||
}
|
||||
|
||||
// CalculateID encodes the given Container and passes the result into
|
||||
// CalculateIDFromBinary.
|
||||
//
|
||||
// See also Container.Marshal, AssertID.
|
||||
func CalculateID(dst *cid.ID, cnr Container) {
|
||||
CalculateIDFromBinary(dst, cnr.Marshal())
|
||||
}
|
||||
|
||||
// AssertID checks if the given Container matches its identifier in CAS of the
|
||||
// NeoFS containers.
|
||||
//
|
||||
// See also CalculateID.
|
||||
func AssertID(id cid.ID, cnr Container) bool {
|
||||
var id2 cid.ID
|
||||
CalculateID(&id2, cnr)
|
||||
|
||||
return id2.Equals(id)
|
||||
}
|
||||
|
|
|
@ -1,117 +1,351 @@
|
|||
package container_test
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
v2container "github.com/nspcc-dev/neofs-api-go/v2/container"
|
||||
v2netmap "github.com/nspcc-dev/neofs-api-go/v2/netmap"
|
||||
"github.com/nspcc-dev/neofs-api-go/v2/refs"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/container"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/container/acl"
|
||||
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
|
||||
cidtest "github.com/nspcc-dev/neofs-sdk-go/container/id/test"
|
||||
containertest "github.com/nspcc-dev/neofs-sdk-go/container/test"
|
||||
neofscrypto "github.com/nspcc-dev/neofs-sdk-go/crypto"
|
||||
netmaptest "github.com/nspcc-dev/neofs-sdk-go/netmap/test"
|
||||
subnetid "github.com/nspcc-dev/neofs-sdk-go/subnet/id"
|
||||
subnetidtest "github.com/nspcc-dev/neofs-sdk-go/subnet/id/test"
|
||||
usertest "github.com/nspcc-dev/neofs-sdk-go/user/test"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/version"
|
||||
versiontest "github.com/nspcc-dev/neofs-sdk-go/version/test"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestNewContainer(t *testing.T) {
|
||||
c := container.New()
|
||||
|
||||
nonce := uuid.New()
|
||||
|
||||
ownerID := usertest.ID()
|
||||
policy := netmaptest.PlacementPolicy()
|
||||
|
||||
c.SetBasicACL(acl.PublicRW)
|
||||
|
||||
attrs := containertest.Attributes()
|
||||
c.SetAttributes(attrs)
|
||||
|
||||
c.SetPlacementPolicy(&policy)
|
||||
c.SetNonceUUID(nonce)
|
||||
c.SetOwnerID(ownerID)
|
||||
|
||||
ver := versiontest.Version()
|
||||
c.SetVersion(&ver)
|
||||
|
||||
v2 := c.ToV2()
|
||||
newContainer := container.NewContainerFromV2(v2)
|
||||
|
||||
require.EqualValues(t, newContainer.PlacementPolicy(), &policy)
|
||||
require.EqualValues(t, newContainer.Attributes(), attrs)
|
||||
require.EqualValues(t, newContainer.BasicACL(), acl.PublicRW)
|
||||
|
||||
newNonce, err := newContainer.NonceUUID()
|
||||
require.NoError(t, err)
|
||||
|
||||
require.EqualValues(t, newNonce, nonce)
|
||||
require.EqualValues(t, newContainer.OwnerID(), ownerID)
|
||||
require.EqualValues(t, *newContainer.Version(), ver)
|
||||
}
|
||||
|
||||
func TestContainerEncoding(t *testing.T) {
|
||||
c := containertest.Container()
|
||||
func TestPlacementPolicyEncoding(t *testing.T) {
|
||||
v := containertest.Container()
|
||||
|
||||
t.Run("binary", func(t *testing.T) {
|
||||
data, err := c.Marshal()
|
||||
require.NoError(t, err)
|
||||
var v2 container.Container
|
||||
require.NoError(t, v2.Unmarshal(v.Marshal()))
|
||||
|
||||
c2 := container.New()
|
||||
require.NoError(t, c2.Unmarshal(data))
|
||||
|
||||
require.Equal(t, c, c2)
|
||||
require.Equal(t, v, v2)
|
||||
})
|
||||
|
||||
t.Run("json", func(t *testing.T) {
|
||||
data, err := c.MarshalJSON()
|
||||
data, err := v.MarshalJSON()
|
||||
require.NoError(t, err)
|
||||
|
||||
c2 := container.New()
|
||||
require.NoError(t, c2.UnmarshalJSON(data))
|
||||
var v2 container.Container
|
||||
require.NoError(t, v2.UnmarshalJSON(data))
|
||||
|
||||
require.Equal(t, c, c2)
|
||||
require.Equal(t, v, v2)
|
||||
})
|
||||
}
|
||||
|
||||
func TestContainer_ToV2(t *testing.T) {
|
||||
t.Run("nil", func(t *testing.T) {
|
||||
var x *container.Container
|
||||
func TestContainer_Init(t *testing.T) {
|
||||
val := containertest.Container()
|
||||
|
||||
require.Nil(t, x.ToV2())
|
||||
})
|
||||
val.Init()
|
||||
|
||||
t.Run("default values", func(t *testing.T) {
|
||||
cnt := container.New()
|
||||
var msg v2container.Container
|
||||
val.WriteToV2(&msg)
|
||||
|
||||
// check initial values
|
||||
require.Nil(t, cnt.Attributes())
|
||||
require.Nil(t, cnt.PlacementPolicy())
|
||||
require.Nil(t, cnt.OwnerID())
|
||||
binNonce := msg.GetNonce()
|
||||
|
||||
require.EqualValues(t, acl.Private, cnt.BasicACL())
|
||||
require.Equal(t, version.Current(), *cnt.Version())
|
||||
var nonce uuid.UUID
|
||||
require.NoError(t, nonce.UnmarshalBinary(binNonce))
|
||||
require.EqualValues(t, 4, nonce.Version())
|
||||
|
||||
nonce, err := cnt.NonceUUID()
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, nonce)
|
||||
verV2 := msg.GetVersion()
|
||||
require.NotNil(t, verV2)
|
||||
|
||||
// convert to v2 message
|
||||
cntV2 := cnt.ToV2()
|
||||
var ver version.Version
|
||||
ver.ReadFromV2(*verV2)
|
||||
|
||||
nonceV2, err := uuid.FromBytes(cntV2.GetNonce())
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, version.Current(), ver)
|
||||
|
||||
require.Equal(t, nonce.String(), nonceV2.String())
|
||||
var val2 container.Container
|
||||
require.NoError(t, val2.ReadFromV2(msg))
|
||||
|
||||
require.Nil(t, cntV2.GetAttributes())
|
||||
require.Nil(t, cntV2.GetPlacementPolicy())
|
||||
require.Nil(t, cntV2.GetOwnerID())
|
||||
|
||||
require.EqualValues(t, 0x1C8C8CCC, cntV2.GetBasicACL())
|
||||
|
||||
var verV2 refs.Version
|
||||
version.Current().WriteToV2(&verV2)
|
||||
require.Equal(t, verV2, *cntV2.GetVersion())
|
||||
})
|
||||
require.Equal(t, val, val2)
|
||||
}
|
||||
|
||||
func TestContainer_Owner(t *testing.T) {
|
||||
var val container.Container
|
||||
|
||||
require.Zero(t, val.Owner())
|
||||
|
||||
val = containertest.Container()
|
||||
|
||||
owner := *usertest.ID()
|
||||
|
||||
val.SetOwner(owner)
|
||||
|
||||
var msg v2container.Container
|
||||
val.WriteToV2(&msg)
|
||||
|
||||
var msgOwner refs.OwnerID
|
||||
owner.WriteToV2(&msgOwner)
|
||||
|
||||
require.Equal(t, &msgOwner, msg.GetOwnerID())
|
||||
|
||||
var val2 container.Container
|
||||
require.NoError(t, val2.ReadFromV2(msg))
|
||||
|
||||
require.True(t, val2.Owner().Equals(owner))
|
||||
}
|
||||
|
||||
func TestContainer_BasicACL(t *testing.T) {
|
||||
var val container.Container
|
||||
|
||||
require.Zero(t, val.BasicACL())
|
||||
|
||||
val = containertest.Container()
|
||||
|
||||
basicACL := containertest.BasicACL()
|
||||
val.SetBasicACL(basicACL)
|
||||
|
||||
var msg v2container.Container
|
||||
val.WriteToV2(&msg)
|
||||
|
||||
require.EqualValues(t, basicACL.Bits(), msg.GetBasicACL())
|
||||
|
||||
var val2 container.Container
|
||||
require.NoError(t, val2.ReadFromV2(msg))
|
||||
|
||||
require.Equal(t, basicACL, val2.BasicACL())
|
||||
}
|
||||
|
||||
func TestContainer_PlacementPolicy(t *testing.T) {
|
||||
var val container.Container
|
||||
|
||||
require.Zero(t, val.PlacementPolicy())
|
||||
|
||||
val = containertest.Container()
|
||||
|
||||
pp := netmaptest.PlacementPolicy()
|
||||
val.SetPlacementPolicy(pp)
|
||||
|
||||
var msgPolicy v2netmap.PlacementPolicy
|
||||
pp.WriteToV2(&msgPolicy)
|
||||
|
||||
var msg v2container.Container
|
||||
val.WriteToV2(&msg)
|
||||
|
||||
require.Equal(t, &msgPolicy, msg.GetPlacementPolicy())
|
||||
|
||||
var val2 container.Container
|
||||
require.NoError(t, val2.ReadFromV2(msg))
|
||||
|
||||
require.Equal(t, pp, val2.PlacementPolicy())
|
||||
}
|
||||
|
||||
func assertContainsAttribute(t *testing.T, m v2container.Container, key, val string) {
|
||||
var msgAttr v2container.Attribute
|
||||
|
||||
msgAttr.SetKey(key)
|
||||
msgAttr.SetValue(val)
|
||||
require.Contains(t, m.GetAttributes(), msgAttr)
|
||||
}
|
||||
|
||||
func TestContainer_Attribute(t *testing.T) {
|
||||
const attrKey1, attrKey2 = "key1", "key2"
|
||||
const attrVal1, attrVal2 = "val1", "val2"
|
||||
|
||||
val := containertest.Container()
|
||||
|
||||
val.SetAttribute(attrKey1, attrVal1)
|
||||
val.SetAttribute(attrKey2, attrVal2)
|
||||
|
||||
var msg v2container.Container
|
||||
val.WriteToV2(&msg)
|
||||
|
||||
require.GreaterOrEqual(t, len(msg.GetAttributes()), 2)
|
||||
assertContainsAttribute(t, msg, attrKey1, attrVal1)
|
||||
assertContainsAttribute(t, msg, attrKey2, attrVal2)
|
||||
|
||||
var val2 container.Container
|
||||
require.NoError(t, val2.ReadFromV2(msg))
|
||||
|
||||
require.Equal(t, attrVal1, val2.Attribute(attrKey1))
|
||||
require.Equal(t, attrVal2, val2.Attribute(attrKey2))
|
||||
|
||||
m := map[string]string{}
|
||||
|
||||
val2.IterateAttributes(func(key, val string) {
|
||||
m[key] = val
|
||||
})
|
||||
|
||||
require.GreaterOrEqual(t, len(m), 2)
|
||||
require.Equal(t, attrVal1, m[attrKey1])
|
||||
require.Equal(t, attrVal2, m[attrKey2])
|
||||
|
||||
val2.SetAttribute(attrKey1, attrVal1+"_")
|
||||
require.Equal(t, attrVal1+"_", val2.Attribute(attrKey1))
|
||||
}
|
||||
|
||||
func TestSetName(t *testing.T) {
|
||||
var val container.Container
|
||||
|
||||
require.Panics(t, func() {
|
||||
container.SetName(&val, "")
|
||||
})
|
||||
|
||||
val = containertest.Container()
|
||||
|
||||
const name = "some name"
|
||||
|
||||
container.SetName(&val, name)
|
||||
|
||||
var msg v2container.Container
|
||||
val.WriteToV2(&msg)
|
||||
|
||||
assertContainsAttribute(t, msg, "Name", name)
|
||||
|
||||
var val2 container.Container
|
||||
require.NoError(t, val2.ReadFromV2(msg))
|
||||
|
||||
require.Equal(t, name, container.Name(val2))
|
||||
}
|
||||
|
||||
func TestSetCreationTime(t *testing.T) {
|
||||
var val container.Container
|
||||
|
||||
require.Zero(t, container.CreatedAt(val).Unix())
|
||||
|
||||
val = containertest.Container()
|
||||
|
||||
creat := time.Now()
|
||||
|
||||
container.SetCreationTime(&val, creat)
|
||||
|
||||
var msg v2container.Container
|
||||
val.WriteToV2(&msg)
|
||||
|
||||
assertContainsAttribute(t, msg, "Timestamp", strconv.FormatInt(creat.Unix(), 10))
|
||||
|
||||
var val2 container.Container
|
||||
require.NoError(t, val2.ReadFromV2(msg))
|
||||
|
||||
require.Equal(t, creat.Unix(), container.CreatedAt(val2).Unix())
|
||||
}
|
||||
|
||||
func TestSetSubnet(t *testing.T) {
|
||||
var val container.Container
|
||||
|
||||
require.True(t, subnetid.IsZero(container.Subnet(val)))
|
||||
|
||||
val = containertest.Container()
|
||||
|
||||
sub := subnetidtest.ID()
|
||||
|
||||
container.SetSubnet(&val, sub)
|
||||
|
||||
var msg v2container.Container
|
||||
val.WriteToV2(&msg)
|
||||
|
||||
assertContainsAttribute(t, msg, v2container.SysAttributeSubnet, sub.EncodeToString())
|
||||
|
||||
var val2 container.Container
|
||||
require.NoError(t, val2.ReadFromV2(msg))
|
||||
|
||||
require.Equal(t, sub, container.Subnet(val))
|
||||
}
|
||||
|
||||
func TestDisableHomomorphicHashing(t *testing.T) {
|
||||
var val container.Container
|
||||
|
||||
require.False(t, container.IsHomomorphicHashingDisabled(val))
|
||||
|
||||
val = containertest.Container()
|
||||
|
||||
container.DisableHomomorphicHashing(&val)
|
||||
|
||||
var msg v2container.Container
|
||||
val.WriteToV2(&msg)
|
||||
|
||||
assertContainsAttribute(t, msg, v2container.SysAttributePrefix+"DISABLE_HOMOMORPHIC_HASHING", "true")
|
||||
|
||||
var val2 container.Container
|
||||
require.NoError(t, val2.ReadFromV2(msg))
|
||||
|
||||
require.True(t, container.IsHomomorphicHashingDisabled(val2))
|
||||
}
|
||||
|
||||
func TestWriteDomain(t *testing.T) {
|
||||
var val container.Container
|
||||
|
||||
require.Zero(t, container.ReadDomain(val).Name())
|
||||
|
||||
val = containertest.Container()
|
||||
|
||||
const name = "domain name"
|
||||
|
||||
var d container.Domain
|
||||
d.SetName(name)
|
||||
|
||||
container.WriteDomain(&val, d)
|
||||
|
||||
var msg v2container.Container
|
||||
val.WriteToV2(&msg)
|
||||
|
||||
assertContainsAttribute(t, msg, v2container.SysAttributeName, name)
|
||||
assertContainsAttribute(t, msg, v2container.SysAttributeZone, "container")
|
||||
|
||||
const zone = "domain zone"
|
||||
|
||||
d.SetZone(zone)
|
||||
|
||||
container.WriteDomain(&val, d)
|
||||
|
||||
val.WriteToV2(&msg)
|
||||
|
||||
assertContainsAttribute(t, msg, v2container.SysAttributeZone, zone)
|
||||
|
||||
var val2 container.Container
|
||||
require.NoError(t, val2.ReadFromV2(msg))
|
||||
|
||||
require.Equal(t, d, container.ReadDomain(val2))
|
||||
}
|
||||
|
||||
func TestCalculateID(t *testing.T) {
|
||||
val := containertest.Container()
|
||||
|
||||
require.False(t, container.AssertID(cidtest.ID(), val))
|
||||
|
||||
var id cid.ID
|
||||
container.CalculateID(&id, val)
|
||||
|
||||
var msg refs.ContainerID
|
||||
id.WriteToV2(&msg)
|
||||
|
||||
h := sha256.Sum256(val.Marshal())
|
||||
require.Equal(t, h[:], msg.GetValue())
|
||||
|
||||
var id2 cid.ID
|
||||
require.NoError(t, id2.ReadFromV2(msg))
|
||||
|
||||
require.True(t, container.AssertID(id2, val))
|
||||
}
|
||||
|
||||
func TestCalculateSignature(t *testing.T) {
|
||||
key, err := keys.NewPrivateKey()
|
||||
require.NoError(t, err)
|
||||
|
||||
val := containertest.Container()
|
||||
|
||||
var sig neofscrypto.Signature
|
||||
|
||||
require.NoError(t, container.CalculateSignature(&sig, val, key.PrivateKey))
|
||||
|
||||
var msg refs.Signature
|
||||
sig.WriteToV2(&msg)
|
||||
|
||||
var sig2 neofscrypto.Signature
|
||||
sig2.ReadFromV2(msg)
|
||||
|
||||
require.True(t, container.VerifySignature(sig2, val))
|
||||
}
|
||||
|
|
46
container/doc.go
Normal file
46
container/doc.go
Normal file
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
Package container provides functionality related to the NeoFS containers.
|
||||
|
||||
The base type is Container. To create new container in the NeoFS network
|
||||
Container instance should be initialized
|
||||
var cnr Container
|
||||
cnr.Init()
|
||||
// fill all the fields
|
||||
|
||||
// encode cnr and send
|
||||
|
||||
After the container is persisted in the NeoFS network, applications can process
|
||||
it using the instance of Container types
|
||||
// recv binary container
|
||||
|
||||
var cnr Container
|
||||
|
||||
err := cnr.Unmarshal(bin)
|
||||
// ...
|
||||
|
||||
// process the container data
|
||||
|
||||
Instances can be also used to process NeoFS API V2 protocol messages
|
||||
(see neo.fs.v2.container package in https://github.com/nspcc-dev/neofs-api).
|
||||
|
||||
On client side:
|
||||
import "github.com/nspcc-dev/neofs-api-go/v2/container"
|
||||
|
||||
var msg container.Container
|
||||
cnr.WriteToV2(&msg)
|
||||
|
||||
// send msg
|
||||
|
||||
On server side:
|
||||
// recv msg
|
||||
|
||||
var cnr Container
|
||||
cnr.ReadFromV2(msg)
|
||||
|
||||
// process cnr
|
||||
|
||||
Using package types in an application is recommended to potentially work with
|
||||
different protocol versions with which these types are compatible.
|
||||
|
||||
*/
|
||||
package container
|
|
@ -1,89 +0,0 @@
|
|||
package container
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/container/acl"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/netmap"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/user"
|
||||
)
|
||||
|
||||
type (
|
||||
Option func(*containerOptions)
|
||||
|
||||
containerOptions struct {
|
||||
acl acl.Basic
|
||||
policy *netmap.PlacementPolicy
|
||||
attributes Attributes
|
||||
owner *user.ID
|
||||
nonce uuid.UUID
|
||||
}
|
||||
)
|
||||
|
||||
func defaultContainerOptions() containerOptions {
|
||||
rand, err := uuid.NewRandom()
|
||||
if err != nil {
|
||||
panic("can't create new random " + err.Error())
|
||||
}
|
||||
|
||||
return containerOptions{
|
||||
acl: acl.Private,
|
||||
nonce: rand,
|
||||
}
|
||||
}
|
||||
|
||||
func WithPublicBasicACL() Option {
|
||||
return func(option *containerOptions) {
|
||||
option.acl = acl.PublicRW
|
||||
}
|
||||
}
|
||||
|
||||
func WithReadOnlyBasicACL() Option {
|
||||
return func(option *containerOptions) {
|
||||
option.acl = acl.PublicRO
|
||||
}
|
||||
}
|
||||
|
||||
func WithCustomBasicACL(acl acl.Basic) Option {
|
||||
return func(option *containerOptions) {
|
||||
option.acl = acl
|
||||
}
|
||||
}
|
||||
|
||||
func WithNonce(nonce uuid.UUID) Option {
|
||||
return func(option *containerOptions) {
|
||||
option.nonce = nonce
|
||||
}
|
||||
}
|
||||
|
||||
func WithOwnerID(id *user.ID) Option {
|
||||
return func(option *containerOptions) {
|
||||
option.owner = id
|
||||
}
|
||||
}
|
||||
|
||||
func WithOwnerPublicKey(pub *ecdsa.PublicKey) Option {
|
||||
return func(option *containerOptions) {
|
||||
if option.owner == nil {
|
||||
option.owner = new(user.ID)
|
||||
}
|
||||
|
||||
user.IDFromKey(option.owner, *pub)
|
||||
}
|
||||
}
|
||||
|
||||
func WithPolicy(policy *netmap.PlacementPolicy) Option {
|
||||
return func(option *containerOptions) {
|
||||
option.policy = policy
|
||||
}
|
||||
}
|
||||
|
||||
func WithAttribute(key, value string) Option {
|
||||
return func(option *containerOptions) {
|
||||
index := len(option.attributes)
|
||||
option.attributes = append(option.attributes, Attribute{})
|
||||
option.attributes[index].SetKey(key)
|
||||
option.attributes[index].SetValue(value)
|
||||
}
|
||||
}
|
104
container/size.go
Normal file
104
container/size.go
Normal file
|
@ -0,0 +1,104 @@
|
|||
package container
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/nspcc-dev/neofs-api-go/v2/container"
|
||||
"github.com/nspcc-dev/neofs-api-go/v2/refs"
|
||||
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
|
||||
)
|
||||
|
||||
// SizeEstimation groups information about estimation of the size of the data
|
||||
// stored in the NeoFS container.
|
||||
//
|
||||
// SizeEstimation is mutually compatible with github.com/nspcc-dev/neofs-api-go/v2/container.UsedSpaceAnnouncement
|
||||
// message. See ReadFromV2 / WriteToV2 methods.
|
||||
type SizeEstimation struct {
|
||||
m container.UsedSpaceAnnouncement
|
||||
}
|
||||
|
||||
// ReadFromV2 reads SizeEstimation from the container.UsedSpaceAnnouncement message.
|
||||
// Checks if the message conforms to NeoFS API V2 protocol.
|
||||
//
|
||||
// See also WriteToV2.
|
||||
func (x *SizeEstimation) ReadFromV2(m container.UsedSpaceAnnouncement) error {
|
||||
cnrV2 := m.GetContainerID()
|
||||
if cnrV2 == nil {
|
||||
return errors.New("missing container")
|
||||
}
|
||||
|
||||
var cnr cid.ID
|
||||
|
||||
err := cnr.ReadFromV2(*cnrV2)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid container: %w", err)
|
||||
}
|
||||
|
||||
x.m = m
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// WriteToV2 writes SizeEstimation into the container.UsedSpaceAnnouncement message.
|
||||
// The message MUST NOT be nil.
|
||||
//
|
||||
// See also ReadFromV2.
|
||||
func (x SizeEstimation) WriteToV2(m *container.UsedSpaceAnnouncement) {
|
||||
*m = x.m
|
||||
}
|
||||
|
||||
// SetEpoch sets epoch when estimation of the container data size was calculated.
|
||||
//
|
||||
// See also Epoch.
|
||||
func (x *SizeEstimation) SetEpoch(epoch uint64) {
|
||||
x.m.SetEpoch(epoch)
|
||||
}
|
||||
|
||||
// Epoch return epoch set using SetEpoch.
|
||||
//
|
||||
// Zero SizeEstimation represents estimation in zero epoch.
|
||||
func (x SizeEstimation) Epoch() uint64 {
|
||||
return x.m.GetEpoch()
|
||||
}
|
||||
|
||||
// SetContainer specifies the container for which the amount of data is estimated.
|
||||
// Required by the NeoFS API protocol.
|
||||
//
|
||||
// See also Container.
|
||||
func (x *SizeEstimation) SetContainer(cnr cid.ID) {
|
||||
var cidV2 refs.ContainerID
|
||||
cnr.WriteToV2(&cidV2)
|
||||
|
||||
x.m.SetContainerID(&cidV2)
|
||||
}
|
||||
|
||||
// Container returns container set using SetContainer.
|
||||
//
|
||||
// Zero SizeEstimation is not bound to any container (returns zero) which is
|
||||
// incorrect according to NeoFS API protocol.
|
||||
func (x SizeEstimation) Container() (res cid.ID) {
|
||||
m := x.m.GetContainerID()
|
||||
if m != nil {
|
||||
err := res.ReadFromV2(*m)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("unexpected error from cid.ID.ReadFromV2: %w", err))
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// SetValue sets estimated amount of data (in bytes) in the specified container.
|
||||
//
|
||||
// See also Value.
|
||||
func (x *SizeEstimation) SetValue(value uint64) {
|
||||
x.m.SetUsedSpace(value)
|
||||
}
|
||||
|
||||
// Value returns data size estimation set using SetValue.
|
||||
//
|
||||
// Zero SizeEstimation has zero value.
|
||||
func (x SizeEstimation) Value() uint64 {
|
||||
return x.m.GetUsedSpace()
|
||||
}
|
94
container/size_test.go
Normal file
94
container/size_test.go
Normal file
|
@ -0,0 +1,94 @@
|
|||
package container_test
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"testing"
|
||||
|
||||
v2container "github.com/nspcc-dev/neofs-api-go/v2/container"
|
||||
"github.com/nspcc-dev/neofs-api-go/v2/refs"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/container"
|
||||
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
|
||||
cidtest "github.com/nspcc-dev/neofs-sdk-go/container/id/test"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestSizeEstimation_Epoch(t *testing.T) {
|
||||
var val container.SizeEstimation
|
||||
|
||||
require.Zero(t, val.Epoch())
|
||||
|
||||
const epoch = 123
|
||||
|
||||
val.SetEpoch(epoch)
|
||||
require.EqualValues(t, epoch, val.Epoch())
|
||||
|
||||
var msg v2container.UsedSpaceAnnouncement
|
||||
val.WriteToV2(&msg)
|
||||
|
||||
require.EqualValues(t, epoch, msg.GetEpoch())
|
||||
}
|
||||
|
||||
func TestSizeEstimation_Container(t *testing.T) {
|
||||
var val container.SizeEstimation
|
||||
|
||||
require.Zero(t, val.Container())
|
||||
|
||||
cnr := cidtest.ID()
|
||||
|
||||
val.SetContainer(cnr)
|
||||
require.True(t, val.Container().Equals(cnr))
|
||||
|
||||
var msg v2container.UsedSpaceAnnouncement
|
||||
val.WriteToV2(&msg)
|
||||
|
||||
var msgCnr refs.ContainerID
|
||||
cnr.WriteToV2(&msgCnr)
|
||||
|
||||
require.Equal(t, &msgCnr, msg.GetContainerID())
|
||||
}
|
||||
|
||||
func TestSizeEstimation_Value(t *testing.T) {
|
||||
var val container.SizeEstimation
|
||||
|
||||
require.Zero(t, val.Value())
|
||||
|
||||
const value = 876
|
||||
|
||||
val.SetValue(value)
|
||||
require.EqualValues(t, value, val.Value())
|
||||
|
||||
var msg v2container.UsedSpaceAnnouncement
|
||||
val.WriteToV2(&msg)
|
||||
|
||||
require.EqualValues(t, value, msg.GetUsedSpace())
|
||||
}
|
||||
|
||||
func TestSizeEstimation_ReadFromV2(t *testing.T) {
|
||||
const epoch = 654
|
||||
const value = 903
|
||||
var cnrMsg refs.ContainerID
|
||||
|
||||
var msg v2container.UsedSpaceAnnouncement
|
||||
|
||||
var val container.SizeEstimation
|
||||
|
||||
require.Error(t, val.ReadFromV2(msg))
|
||||
|
||||
msg.SetContainerID(&cnrMsg)
|
||||
|
||||
require.Error(t, val.ReadFromV2(msg))
|
||||
|
||||
cnrMsg.SetValue(make([]byte, sha256.Size))
|
||||
|
||||
var cnr cid.ID
|
||||
require.NoError(t, cnr.ReadFromV2(cnrMsg))
|
||||
|
||||
msg.SetEpoch(epoch)
|
||||
msg.SetUsedSpace(value)
|
||||
|
||||
require.NoError(t, val.ReadFromV2(msg))
|
||||
|
||||
require.EqualValues(t, epoch, val.Epoch())
|
||||
require.EqualValues(t, value, val.Value())
|
||||
require.EqualValues(t, cnr, val.Container())
|
||||
}
|
|
@ -8,46 +8,26 @@ import (
|
|||
cidtest "github.com/nspcc-dev/neofs-sdk-go/container/id/test"
|
||||
netmaptest "github.com/nspcc-dev/neofs-sdk-go/netmap/test"
|
||||
usertest "github.com/nspcc-dev/neofs-sdk-go/user/test"
|
||||
versiontest "github.com/nspcc-dev/neofs-sdk-go/version/test"
|
||||
)
|
||||
|
||||
// Attribute returns random container.Attribute.
|
||||
func Attribute() *container.Attribute {
|
||||
x := container.NewAttribute()
|
||||
|
||||
x.SetKey("key")
|
||||
x.SetValue("value")
|
||||
|
||||
return x
|
||||
}
|
||||
|
||||
// Attributes returns random container.Attributes.
|
||||
func Attributes() container.Attributes {
|
||||
return container.Attributes{*Attribute(), *Attribute()}
|
||||
}
|
||||
|
||||
// Container returns random container.Container.
|
||||
func Container() *container.Container {
|
||||
x := container.New()
|
||||
ver := versiontest.Version()
|
||||
func Container() (x container.Container) {
|
||||
owner := usertest.ID()
|
||||
|
||||
x.SetVersion(&ver)
|
||||
x.SetAttributes(Attributes())
|
||||
x.SetOwnerID(usertest.ID())
|
||||
x.Init()
|
||||
x.SetAttribute("some attribute", "value")
|
||||
x.SetOwner(*owner)
|
||||
x.SetBasicACL(BasicACL())
|
||||
p := netmaptest.PlacementPolicy()
|
||||
x.SetPlacementPolicy(&p)
|
||||
x.SetPlacementPolicy(netmaptest.PlacementPolicy())
|
||||
|
||||
return x
|
||||
}
|
||||
|
||||
// UsedSpaceAnnouncement returns random container.UsedSpaceAnnouncement.
|
||||
func UsedSpaceAnnouncement() *container.UsedSpaceAnnouncement {
|
||||
x := container.NewAnnouncement()
|
||||
|
||||
x.SetContainerID(cidtest.ID())
|
||||
x.SetEpoch(55)
|
||||
x.SetUsedSpace(999)
|
||||
// SizeEstimation returns random container.SizeEstimation.
|
||||
func SizeEstimation() (x container.SizeEstimation) {
|
||||
x.SetContainer(cidtest.ID())
|
||||
x.SetEpoch(rand.Uint64())
|
||||
x.SetValue(rand.Uint64())
|
||||
|
||||
return x
|
||||
}
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
package container
|
||||
|
||||
const (
|
||||
// AttributeName is an attribute key that is commonly used to denote
|
||||
// human-friendly name.
|
||||
AttributeName = "Name"
|
||||
|
||||
// AttributeTimestamp is an attribute key that is commonly used to denote
|
||||
// user-defined local time of container creation in Unix Timestamp format.
|
||||
AttributeTimestamp = "Timestamp"
|
||||
)
|
2
go.mod
2
go.mod
|
@ -10,7 +10,7 @@ require (
|
|||
github.com/mr-tron/base58 v1.2.0
|
||||
github.com/nspcc-dev/hrw v1.0.9
|
||||
github.com/nspcc-dev/neo-go v0.98.2
|
||||
github.com/nspcc-dev/neofs-api-go/v2 v2.12.3-0.20220621170933-dd233c3fbc84
|
||||
github.com/nspcc-dev/neofs-api-go/v2 v2.12.3-0.20220630100506-c6f7ab3ef1bf
|
||||
github.com/nspcc-dev/neofs-contract v0.15.1
|
||||
github.com/nspcc-dev/tzhash v1.5.2
|
||||
github.com/stretchr/testify v1.7.0
|
||||
|
|
BIN
go.sum
BIN
go.sum
Binary file not shown.
|
@ -133,7 +133,10 @@ func (c *clientWrapper) containerGet(ctx context.Context, prm PrmContainerGet) (
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return res.Container(), nil
|
||||
|
||||
cnr := res.Container()
|
||||
|
||||
return &cnr, nil
|
||||
}
|
||||
|
||||
func (c *clientWrapper) containerList(ctx context.Context, prm PrmContainerList) ([]cid.ID, error) {
|
||||
|
|
Loading…
Reference in a new issue