[#210] subnet: Refactor and document package functionality

Signed-off-by: Leonard Lyubich <leonard@nspcc.ru>
This commit is contained in:
Leonard Lyubich 2022-06-15 10:12:35 +03:00 committed by LeL
parent 6709b00c89
commit eb3b990812
13 changed files with 276 additions and 216 deletions

View file

@ -36,13 +36,31 @@ func (p *PlacementPolicy) ToV2() *netmap.PlacementPolicy {
// SubnetID returns subnet to select nodes from.
func (p *PlacementPolicy) SubnetID() *subnetid.ID {
return (*subnetid.ID)(
(*netmap.PlacementPolicy)(p).GetSubnetID())
idv2 := (*netmap.PlacementPolicy)(p).GetSubnetID()
if idv2 == nil {
return nil
}
var id subnetid.ID
err := id.ReadFromV2(*idv2)
if err != nil {
panic(err) // will disappear after netmap package refactor
}
return &id
}
// SetSubnetID sets subnet to select nodes from.
func (p *PlacementPolicy) SetSubnetID(subnet *subnetid.ID) {
(*netmap.PlacementPolicy)(p).SetSubnetID((*refs.SubnetID)(subnet))
var idv2 *refs.SubnetID
if subnet != nil {
idv2 = new(refs.SubnetID)
subnet.WriteToV2(idv2)
}
(*netmap.PlacementPolicy)(p).SetSubnetID(idv2)
}
// Replicas returns list of object replica descriptors.

View file

@ -2,6 +2,7 @@ package netmap
import (
"errors"
"fmt"
"github.com/nspcc-dev/neofs-api-go/v2/netmap"
"github.com/nspcc-dev/neofs-api-go/v2/refs"
@ -48,9 +49,12 @@ func (i *NodeInfo) IterateSubnets(f func(subnetid.ID) error) error {
var id subnetid.ID
return netmap.IterateSubnets((*netmap.NodeInfo)(i), func(idv2 refs.SubnetID) error {
id.FromV2(idv2)
err := id.ReadFromV2(idv2)
if err != nil {
return fmt.Errorf("invalid subnet: %w", err)
}
err := f(id)
err = f(id)
if errors.Is(err, ErrRemoveSubnet) {
return netmap.ErrRemoveSubnet
}
@ -66,7 +70,7 @@ var errAbortSubnetIter = errors.New("abort subnet iterator")
// Function is NPE-safe: nil NodeInfo always belongs to zero subnet only.
func BelongsToSubnet(node *NodeInfo, id subnetid.ID) bool {
err := node.IterateSubnets(func(id_ subnetid.ID) error {
if id.Equals(&id_) {
if id.Equals(id_) {
return errAbortSubnetIter
}

View file

@ -12,7 +12,7 @@ func TestNodeInfoSubnets(t *testing.T) {
t.Run("enter subnet", func(t *testing.T) {
var id subnetid.ID
id.SetNumber(13)
id.SetNumeric(13)
var node netmap.NodeInfo
@ -35,8 +35,8 @@ func TestNodeInfoSubnets(t *testing.T) {
t.Run("not last", func(t *testing.T) {
var id, idrm subnetid.ID
id.SetNumber(13)
idrm.SetNumber(23)
id.SetNumeric(13)
idrm.SetNumeric(23)
var node netmap.NodeInfo
@ -44,7 +44,7 @@ func TestNodeInfoSubnets(t *testing.T) {
node.EnterSubnet(idrm)
err := node.IterateSubnets(func(id subnetid.ID) error {
if subnetid.IsZero(id) || id.Equals(&idrm) {
if subnetid.IsZero(id) || id.Equals(idrm) {
return netmap.ErrRemoveSubnet
}
@ -100,7 +100,7 @@ func TestEnterSubnet(t *testing.T) {
node.ExitSubnet(id)
require.False(t, netmap.BelongsToSubnet(&node, id))
id.SetNumber(10)
id.SetNumeric(10)
node.EnterSubnet(id)
require.True(t, netmap.BelongsToSubnet(&node, id))
require.False(t, netmap.BelongsToSubnet(&node, subnetid.ID{}))
@ -113,8 +113,8 @@ func TestEnterSubnet(t *testing.T) {
func TestBelongsToSubnet(t *testing.T) {
var id, idMiss, idZero subnetid.ID
id.SetNumber(13)
idMiss.SetNumber(23)
id.SetNumeric(13)
idMiss.SetNumeric(23)
var node netmap.NodeInfo

10
subnet/doc.go Normal file
View file

@ -0,0 +1,10 @@
/*
Package subnet collects functionality related to the NeoFS subnets.
Subnet of a particular NeoFS network consists of a subset of the storage nodes
of that network. Subnet of the whole network is called zero. Info type acts as
a subnet descriptor. Each subnet is owned by the user who created it. Information
about all subnets is stored in the Subnet contract of the NeoFS Sidechain.
*/
package subnet

10
subnet/id/doc.go Normal file
View file

@ -0,0 +1,10 @@
/*
Package subnetid provides primitives to work with subnet identification in NeoFS.
ID type is used for global subnet identity inside the NeoFS network.
Using package types in an application is recommended to potentially work with
different protocol versions with which these types are compatible.
*/
package subnetid

View file

@ -2,95 +2,104 @@ package subnetid
import (
"fmt"
"strconv"
"github.com/nspcc-dev/neofs-api-go/v2/refs"
)
// ID represents NeoFS subnet identifier.
// ID represents unique identifier of the subnet in the NeoFS network.
//
// The type is compatible with the corresponding message from NeoFS API V2 protocol.
// ID is mutually compatible with github.com/nspcc-dev/neofs-api-go/v2/refs.SubnetID
// message. See ReadFromV2 / WriteToV2 methods.
//
// Zero value and nil pointer is equivalent to zero subnet ID.
type ID refs.SubnetID
// FromV2 initializes ID from refs.SubnetID message structure. Must not be called on nil.
//
// Note: nil refs.SubnetID corresponds to zero ID value or nil pointer to it.
func (x *ID) FromV2(msg refs.SubnetID) {
*x = ID(msg)
// Instances can be created using built-in var declaration. Zero value is
// equivalent to identifier of the zero subnet (whole NeoFS network).
type ID struct {
m refs.SubnetID
}
// WriteToV2 writes ID to refs.SubnetID message structure. The message must not be nil.
// ReadFromV2 reads ID from the refs.SubnetID message. Checks if the
// message conforms to NeoFS API V2 protocol.
//
// Note: nil ID corresponds to zero refs.SubnetID value or nil pointer to it.
// See also WriteToV2.
func (x *ID) ReadFromV2(msg refs.SubnetID) error {
x.m = msg
return nil
}
// WriteToV2 writes ID to refs.SubnetID message structure. The message MUST NOT
// be nil.
//
// See also ReadFromV2.
func (x ID) WriteToV2(msg *refs.SubnetID) {
*msg = refs.SubnetID(x)
*msg = x.m
}
// Equals returns true iff both instances identify the same subnet.
// Equals defines a comparison relation between two ID instances.
//
// Method is NPE-safe: nil pointer equals to pointer to zero value.
func (x *ID) Equals(x2 *ID) bool {
return (*refs.SubnetID)(x).GetValue() == (*refs.SubnetID)(x2).GetValue()
// Note that comparison using '==' operator is not recommended since it MAY result
// in loss of compatibility.
func (x ID) Equals(x2 ID) bool {
return x.m.GetValue() == x2.m.GetValue()
}
// MarshalText encodes ID into text format according to particular NeoFS API protocol.
// Supported versions:
// * V2 (see refs.SubnetID type).
// EncodeToString encodes ID into NeoFS API protocol string (base10 encoding).
//
// Implements encoding.TextMarshaler.
func (x *ID) MarshalText() ([]byte, error) {
return (*refs.SubnetID)(x).MarshalText()
// See also DecodeString.
func (x ID) EncodeToString() string {
return strconv.FormatUint(uint64(x.m.GetValue()), 10)
}
// UnmarshalText decodes ID from the text according to particular NeoFS API protocol.
// Must not be called on nil. Supported versions:
// * V2 (see refs.SubnetID type).
//
// Implements encoding.TextUnmarshaler.
func (x *ID) UnmarshalText(text []byte) error {
return (*refs.SubnetID)(x).UnmarshalText(text)
}
// String returns string representation of ID using MarshalText.
// Returns string with message on error.
//
// Implements fmt.Stringer.
func (x *ID) String() string {
text, err := x.MarshalText()
// DecodeString decodes string calculated using EncodeToString. Returns
// an error if s is malformed.
func (x *ID) DecodeString(s string) error {
num, err := strconv.ParseUint(s, 10, 32)
if err != nil {
return fmt.Sprintf("<invalid> %v", err)
return fmt.Errorf("invalid numeric value: %w", err)
}
return string(text)
x.m.SetValue(uint32(num))
return nil
}
// Marshal encodes ID into a binary format of NeoFS API V2 protocol (Protocol Buffers with direct field order).
func (x *ID) Marshal() ([]byte, error) {
return (*refs.SubnetID)(x).StableMarshal(nil), nil
}
// Unmarshal decodes ID from NeoFS API V2 binary format (see Marshal). Must not be called on nil.
// String implements fmt.Stringer.
//
// Note: empty data corresponds to zero ID value or nil pointer to it.
// String is designed to be human-readable, and its format MAY differ between
// SDK versions. String MAY return same result as EncodeToString. String MUST NOT
// be used to encode ID into NeoFS protocol string.
func (x ID) String() string {
return "#" + strconv.FormatUint(uint64(x.m.GetValue()), 10)
}
// Marshal encodes ID into a binary format of the NeoFS API protocol
// (Protocol Buffers with direct field order).
//
// See also Unmarshal.
func (x ID) Marshal() []byte {
return x.m.StableMarshal(nil)
}
// Unmarshal decodes binary ID calculated using Marshal. Returns an error
// describing a format violation.
func (x *ID) Unmarshal(data []byte) error {
return (*refs.SubnetID)(x).Unmarshal(data)
return x.m.Unmarshal(data)
}
// SetNumber sets ID value in uint32 format. Must not be called on nil.
// By default, number is 0 which refers to zero subnet.
func (x *ID) SetNumber(num uint32) {
(*refs.SubnetID)(x).SetValue(num)
// SetNumeric sets ID value in numeric format. By default, number is 0 which
// refers to the zero subnet.
func (x *ID) SetNumeric(num uint32) {
x.m.SetValue(num)
}
// IsZero returns true iff the ID refers to zero subnet.
// IsZero compares id with zero subnet ID.
func IsZero(id ID) bool {
return id.Equals(nil)
return id.Equals(ID{})
}
// MakeZero makes ID to refer to zero subnet. Arg must not be nil (it is already zero).
// MakeZero makes ID to refer to zero subnet.
//
// Makes no sense to call on zero value (e.g. declared using var).
func MakeZero(id *ID) {
id.SetNumber(0)
id.SetNumeric(0)
}

View file

@ -14,26 +14,26 @@ func TestIsZero(t *testing.T) {
require.True(t, subnetid.IsZero(id))
id.SetNumber(13)
id.SetNumeric(13)
require.False(t, subnetid.IsZero(id))
id.SetNumber(0)
id.SetNumeric(0)
require.True(t, subnetid.IsZero(id))
}
func TestID_FromV2(t *testing.T) {
func TestID_ReadFromV2(t *testing.T) {
const num = 13
var id1 subnetid.ID
id1.SetNumber(num)
id1.SetNumeric(num)
var idv2 refs.SubnetID
idv2.SetValue(num)
var id2 subnetid.ID
id2.FromV2(idv2)
require.NoError(t, id2.ReadFromV2(idv2))
require.True(t, id1.Equals(&id2))
require.True(t, id1.Equals(id2))
}
func TestID_WriteToV2(t *testing.T) {
@ -47,7 +47,7 @@ func TestID_WriteToV2(t *testing.T) {
id.WriteToV2(&idv2)
require.Zero(t, idv2.GetValue())
id.SetNumber(num)
id.SetNumeric(num)
id.WriteToV2(&idv2)
require.EqualValues(t, num, idv2.GetValue())
@ -58,36 +58,30 @@ func TestID_Equals(t *testing.T) {
var id1, id2, idOther, id0 subnetid.ID
id0.Equals(nil)
id0.Equals(subnetid.ID{})
id1.SetNumber(num)
id2.SetNumber(num)
idOther.SetNumber(num + 1)
id1.SetNumeric(num)
id2.SetNumeric(num)
idOther.SetNumeric(num + 1)
require.True(t, id1.Equals(&id2))
require.False(t, id1.Equals(&idOther))
require.False(t, id2.Equals(&idOther))
require.True(t, id1.Equals(id2))
require.False(t, id1.Equals(idOther))
require.False(t, id2.Equals(idOther))
}
func TestSubnetIDEncoding(t *testing.T) {
id := subnetidtest.ID()
t.Run("binary", func(t *testing.T) {
data, err := id.Marshal()
require.NoError(t, err)
var id2 subnetid.ID
require.NoError(t, id2.Unmarshal(data))
require.NoError(t, id2.Unmarshal(id.Marshal()))
require.True(t, id2.Equals(id))
})
t.Run("text", func(t *testing.T) {
data, err := id.MarshalText()
require.NoError(t, err)
var id2 subnetid.ID
require.NoError(t, id2.UnmarshalText(data))
require.NoError(t, id2.DecodeString(id.EncodeToString()))
require.True(t, id2.Equals(id))
})
@ -95,12 +89,12 @@ func TestSubnetIDEncoding(t *testing.T) {
func TestMakeZero(t *testing.T) {
var id subnetid.ID
id.SetNumber(13)
id.SetNumeric(13)
require.False(t, subnetid.IsZero(id))
subnetid.MakeZero(&id)
require.True(t, subnetid.IsZero(id))
require.Equal(t, subnetid.ID{}, id)
}

13
subnet/id/test/doc.go Normal file
View file

@ -0,0 +1,13 @@
/*
Package subnetidtest provides functions for convenient testing of subnetid package API.
Note that importing the package into source files is highly discouraged.
Random instance generation functions can be useful when testing expects any value, e.g.:
import subnetidtest "github.com/nspcc-dev/neofs-sdk-go/suibnet/id/test"
value := subnetidtest.ID()
// test the value
*/
package subnetidtest

View file

@ -6,11 +6,8 @@ import (
subnetid "github.com/nspcc-dev/neofs-sdk-go/subnet/id"
)
// ID generates and returns random subnetid.ID using math/rand.Uint32.
func ID() *subnetid.ID {
var id subnetid.ID
id.SetNumber(rand.Uint32())
return &id
// ID generates and returns random subnetid.ID.
func ID() (x subnetid.ID) {
x.SetNumeric(rand.Uint32())
return
}

View file

@ -1,6 +1,8 @@
package subnet
import (
"fmt"
"github.com/nspcc-dev/neofs-api-go/v2/refs"
"github.com/nspcc-dev/neofs-api-go/v2/subnet"
subnetid "github.com/nspcc-dev/neofs-sdk-go/subnet/id"
@ -9,102 +11,99 @@ import (
// Info represents information about NeoFS subnet.
//
// The type is compatible with the corresponding message from NeoFS API V2 protocol.
// Instances can be created using built-in var declaration.
type Info struct {
id subnetid.ID
owner user.ID
}
// Marshal encodes Info into a binary format of the NeoFS API protocol
// (Protocol Buffers with direct field order).
//
// Zero value and nil pointer to it represents zero subnet w/o an owner.
type Info subnet.Info
// See also Unmarshal.
func (x Info) Marshal() []byte {
var id refs.SubnetID
x.id.WriteToV2(&id)
// FromV2 initializes Info from subnet.Info message structure. Must not be called on nil.
func (x *Info) FromV2(msg subnet.Info) {
*x = Info(msg)
var owner refs.OwnerID
x.owner.WriteToV2(&owner)
var m subnet.Info
m.SetID(&id)
m.SetOwner(&owner)
return m.StableMarshal(nil)
}
// WriteToV2 writes Info to subnet.Info message structure. The message must not be nil.
func (x Info) WriteToV2(msg *subnet.Info) {
*msg = subnet.Info(x)
}
// Marshal encodes Info into a binary format of NeoFS API V2 protocol (Protocol Buffers with direct field order).
func (x *Info) Marshal() ([]byte, error) {
return (*subnet.Info)(x).StableMarshal(nil), nil
}
// Unmarshal decodes Info from NeoFS API V2 binary format (see Marshal). Must not be called on nil.
//
// Note: empty data corresponds to zero Info value or nil pointer to it.
// Unmarshal decodes binary Info calculated using Marshal. Returns an error
// describing a format violation.
func (x *Info) Unmarshal(data []byte) error {
return (*subnet.Info)(x).Unmarshal(data)
var m subnet.Info
err := m.Unmarshal(data)
if err != nil {
return err
}
id := m.ID()
if id != nil {
err = x.id.ReadFromV2(*id)
if err != nil {
return fmt.Errorf("invalid ID: %w", err)
}
} else {
subnetid.MakeZero(&x.id)
}
owner := m.Owner()
if owner != nil {
err = x.owner.ReadFromV2(*owner)
if err != nil {
return fmt.Errorf("invalid owner: %w", err)
}
} else {
x.owner = user.ID{}
}
return nil
}
// SetID sets the identifier of the subnet that Info describes.
//
// See also ID.
func (x *Info) SetID(id subnetid.ID) {
infov2 := (*subnet.Info)(x)
idv2 := infov2.ID()
if idv2 == nil {
idv2 = new(refs.SubnetID)
infov2.SetID(idv2)
}
id.WriteToV2(idv2)
x.id = id
}
// ReadID reads the identifier of the subnet that Info describes. Arg must not be nil.
func (x Info) ReadID(id *subnetid.ID) {
infov2 := (subnet.Info)(x)
idv2 := infov2.ID()
if idv2 == nil {
subnetid.MakeZero(id)
return
}
id.FromV2(*idv2)
// ID returns subnet identifier set using SetID.
//
// Zero Info refers to the zero subnet.
func (x Info) ID() subnetid.ID {
return x.id
}
// SetOwner sets subnet owner ID.
// SetOwner sets identifier of the subnet owner.
func (x *Info) SetOwner(id user.ID) {
infov2 := (*subnet.Info)(x)
idv2 := infov2.Owner()
if idv2 == nil {
idv2 = new(refs.OwnerID)
infov2.SetOwner(idv2)
}
id.WriteToV2(idv2)
x.owner = id
}
// ReadOwner reads the identifier of the subnet that Info describes.
// Must be called only if owner is set (see HasOwner). Arg must not be nil.
func (x Info) ReadOwner(id *user.ID) {
infov2 := (subnet.Info)(x)
id2 := infov2.Owner()
if id2 == nil {
*id = user.ID{}
return
}
if ownerV2 := infov2.Owner(); ownerV2 != nil {
_ = id.ReadFromV2(*ownerV2)
}
// Owner returns subnet owner set using SetOwner.
//
// Zero Info has no owner which is incorrect according to the
// NeoFS API protocol.
func (x Info) Owner() user.ID {
return x.owner
}
// IsOwner checks subnet ownership.
func IsOwner(info Info, id user.ID) bool {
var id2 user.ID
info.ReadOwner(&id2)
return id.Equals(id2)
// AssertOwnership checks if the given info describes the subnet owned by the
// given user.
func AssertOwnership(info Info, id user.ID) bool {
return id.Equals(info.Owner())
}
// IDEquals checks if ID refers to subnet that Info describes.
func IDEquals(info Info, id subnetid.ID) bool {
id2 := new(subnetid.ID)
info.ReadID(id2)
return id.Equals(id2)
// AssertReference checks if the given info describes the subnet referenced by
// the given id.
func AssertReference(info Info, id subnetid.ID) bool {
return id.Equals(info.ID())
}

View file

@ -3,11 +3,10 @@ package subnet_test
import (
"testing"
subnetv2 "github.com/nspcc-dev/neofs-api-go/v2/subnet"
subnettest "github.com/nspcc-dev/neofs-api-go/v2/subnet/test"
. "github.com/nspcc-dev/neofs-sdk-go/subnet"
subnetid "github.com/nspcc-dev/neofs-sdk-go/subnet/id"
"github.com/nspcc-dev/neofs-sdk-go/user"
subnetidtest "github.com/nspcc-dev/neofs-sdk-go/subnet/id/test"
subnettest "github.com/nspcc-dev/neofs-sdk-go/subnet/test"
usertest "github.com/nspcc-dev/neofs-sdk-go/user/test"
"github.com/stretchr/testify/require"
)
@ -15,55 +14,35 @@ import (
func TestInfoZero(t *testing.T) {
var info Info
var id subnetid.ID
info.ReadID(&id)
require.True(t, subnetid.IsZero(id))
require.Zero(t, info.ID())
require.True(t, subnetid.IsZero(info.ID()))
}
func TestInfo_SetID(t *testing.T) {
var (
id subnetid.ID
info Info
)
id.SetNumber(222)
id := subnetidtest.ID()
var info Info
info.SetID(id)
require.True(t, IDEquals(info, id))
require.Equal(t, id, info.ID())
require.True(t, AssertReference(info, id))
}
func TestInfo_SetOwner(t *testing.T) {
var (
id user.ID
info Info
)
id = *usertest.ID()
require.False(t, IsOwner(info, id))
id := *usertest.ID()
var info Info
info.SetOwner(id)
require.True(t, IsOwner(info, id))
require.Equal(t, id, info.Owner())
require.True(t, AssertOwnership(info, id))
}
func TestInfo_WriteToV2(t *testing.T) {
var (
infoTo, infoFrom Info
func TestInfo_Marshal(t *testing.T) {
info := subnettest.Info()
infoV2From, infoV2To subnetv2.Info
)
var info2 Info
require.NoError(t, info2.Unmarshal(info.Marshal()))
infoV2From = *subnettest.GenerateSubnetInfo(false)
infoFrom.FromV2(infoV2From)
infoFrom.WriteToV2(&infoV2To)
infoTo.FromV2(infoV2To)
require.Equal(t, infoV2From, infoV2To)
require.Equal(t, infoFrom, infoTo)
require.Equal(t, info, info2)
}

13
subnet/test/doc.go Normal file
View file

@ -0,0 +1,13 @@
/*
Package subnettest provides functions for convenient testing of subnet package API.
Note that importing the package into source files is highly discouraged.
Random instance generation functions can be useful when testing expects any value, e.g.:
import subnettest "github.com/nspcc-dev/neofs-sdk-go/suibnet/test"
value := subnettest.Info()
// test the value
*/
package subnettest

14
subnet/test/subnet.go Normal file
View file

@ -0,0 +1,14 @@
package subnettest
import (
"github.com/nspcc-dev/neofs-sdk-go/subnet"
subnetidtest "github.com/nspcc-dev/neofs-sdk-go/subnet/id/test"
usertest "github.com/nspcc-dev/neofs-sdk-go/user/test"
)
// Info generates and returns random subnet.Info.
func Info() (x subnet.Info) {
x.SetID(subnetidtest.ID())
x.SetOwner(*usertest.ID())
return
}