frostfs-node/pkg/services/object_manager/placement/placement_test.go

412 lines
9.6 KiB
Go
Raw Normal View History

package placement
import (
"context"
"sort"
"strconv"
"strings"
"sync"
"testing"
"time"
"github.com/mr-tron/base58"
"github.com/multiformats/go-multiaddr"
"github.com/multiformats/go-multihash"
"github.com/nspcc-dev/neofs-api-go/bootstrap"
"github.com/nspcc-dev/neofs-api-go/refs"
2020-07-24 13:54:03 +00:00
crypto "github.com/nspcc-dev/neofs-crypto"
libcnr "github.com/nspcc-dev/neofs-node/pkg/core/container"
"github.com/nspcc-dev/neofs-node/pkg/core/container/acl/basic"
"github.com/nspcc-dev/neofs-node/pkg/core/container/storage"
netmapcore "github.com/nspcc-dev/neofs-node/pkg/core/netmap"
"github.com/nspcc-dev/neofs-node/pkg/network/peers"
testlogger "github.com/nspcc-dev/neofs-node/pkg/util/logger/test"
"github.com/nspcc-dev/neofs-node/pkg/util/test"
"github.com/nspcc-dev/netmap"
"github.com/pkg/errors"
"github.com/stretchr/testify/require"
)
type (
fakeDHT struct {
}
fakeContainerStorage struct {
2020-07-24 13:54:03 +00:00
storage.Storage
*sync.RWMutex
2020-07-24 13:54:03 +00:00
items map[refs.CID]*storage.Container
}
)
var (
testDHTCapacity = 100
)
// -- -- //
func testContainerStorage() *fakeContainerStorage {
return &fakeContainerStorage{
RWMutex: new(sync.RWMutex),
2020-07-24 13:54:03 +00:00
items: make(map[refs.CID]*storage.Container, testDHTCapacity),
}
}
2020-07-24 13:54:03 +00:00
func (f *fakeContainerStorage) Get(cid storage.CID) (*storage.Container, error) {
f.RLock()
2020-07-24 13:54:03 +00:00
val, ok := f.items[cid]
f.RUnlock()
if !ok {
return nil, errors.New("value for requested key not found in DHT")
}
2020-07-24 13:54:03 +00:00
return val, nil
}
2020-07-24 13:54:03 +00:00
func (f *fakeContainerStorage) Put(c *storage.Container) (*storage.CID, error) {
id, err := libcnr.CalculateID(c)
if err != nil {
2020-07-24 13:54:03 +00:00
return nil, err
}
f.Lock()
2020-07-24 13:54:03 +00:00
f.items[*id] = c
f.Unlock()
2020-07-24 13:54:03 +00:00
return id, nil
}
func (f *fakeDHT) UpdatePeers([]peers.ID) {
// do nothing
}
func (f *fakeDHT) GetValue(ctx context.Context, key string) ([]byte, error) {
panic("implement me")
}
func (f *fakeDHT) PutValue(ctx context.Context, key string, val []byte) error {
panic("implement me")
}
func (f *fakeDHT) Get(ctx context.Context, key string) ([]byte, error) {
panic("implement me")
}
func (f *fakeDHT) Put(ctx context.Context, key string, val []byte) error {
panic("implement me")
}
// -- -- //
2020-07-24 13:54:03 +00:00
func testNetmap(t *testing.T, nodes []bootstrap.NodeInfo) *NetMap {
nm := netmapcore.New()
for i := range nodes {
2020-07-24 13:54:03 +00:00
info := netmapcore.Info{}
info.SetAddress(nodes[i].Address)
info.SetOptions(nodes[i].Options)
info.SetPublicKey(crypto.MarshalPublicKey(&test.DecodeKey(i).PublicKey))
err := nm.AddNode(info)
require.NoError(t, err)
}
return nm
}
// -- -- //
func idFromString(t *testing.T, id string) string {
buf, err := multihash.Encode([]byte(id), multihash.ID)
require.NoError(t, err)
return (multihash.Multihash(buf)).B58String()
}
func idFromAddress(t *testing.T, addr multiaddr.Multiaddr) string {
id, err := addr.ValueForProtocol(multiaddr.P_P2P)
require.NoError(t, err)
buf, err := base58.Decode(id)
require.NoError(t, err)
hs, err := multihash.Decode(buf)
require.NoError(t, err)
return string(hs.Digest)
}
// -- -- //
func TestPlacement(t *testing.T) {
multiaddr.SwapToP2pMultiaddrs()
testAddress := "/ip4/0.0.0.0/tcp/0/p2p/"
key := test.DecodeKey(-1)
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
ids := map[string]struct{}{
"GRM1": {}, "GRM2": {}, "GRM3": {}, "GRM4": {},
"SPN1": {}, "SPN2": {}, "SPN3": {}, "SPN4": {},
}
nodes := []bootstrap.NodeInfo{
{Address: testAddress + idFromString(t, "USA1"), Options: []string{"/Location:Europe/Country:USA/City:NewYork"}},
{Address: testAddress + idFromString(t, "ITL1"), Options: []string{"/Location:Europe/Country:Italy/City:Rome"}},
{Address: testAddress + idFromString(t, "RUS1"), Options: []string{"/Location:Europe/Country:Russia/City:SPB"}},
}
for id := range ids {
var opts []string
switch {
case strings.Contains(id, "GRM"):
opts = append(opts, "/Location:Europe/Country:Germany/City:"+id)
case strings.Contains(id, "SPN"):
opts = append(opts, "/Location:Europe/Country:Spain/City:"+id)
}
for i := 0; i < 4; i++ {
id := id + strconv.Itoa(i)
nodes = append(nodes, bootstrap.NodeInfo{
Address: testAddress + idFromString(t, id),
Options: opts,
})
}
}
sort.Slice(nodes, func(i, j int) bool {
return strings.Compare(nodes[i].Address, nodes[j].Address) == -1
})
nm := testNetmap(t, nodes)
cnrStorage := testContainerStorage()
p := New(Params{
2020-07-24 13:54:03 +00:00
Log: testlogger.NewLogger(false),
Peerstore: testPeerstore(t),
Fetcher: cnrStorage,
})
require.NoError(t, p.Update(1, nm))
oid, err := refs.NewObjectID()
require.NoError(t, err)
// filter over oid
filter := func(group netmap.SFGroup, bucket *netmap.Bucket) *netmap.Bucket {
return bucket.GetSelection(group.Selectors, oid[:])
}
owner, err := refs.NewOwnerID(&key.PublicKey)
require.NoError(t, err)
2020-07-24 13:54:03 +00:00
cnr1 := new(storage.Container)
cnr1.SetOwnerID(owner)
cnr1.SetBasicACL(basic.FromUint32(0))
cnr1.SetPlacementRule(netmap.PlacementRule{
ReplFactor: 2,
SFGroups: []netmap.SFGroup{
{
Selectors: []netmap.Select{
{Key: "Country", Count: 1},
{Key: "City", Count: 2},
{Key: netmap.NodesBucket, Count: 1},
},
Filters: []netmap.Filter{
{Key: "Country", F: netmap.FilterIn("Germany", "Spain")},
},
},
},
})
2020-07-24 13:54:03 +00:00
cid1, err := cnrStorage.Put(cnr1)
require.NoError(t, err)
2020-07-24 13:54:03 +00:00
cnr2 := new(storage.Container)
cnr2.SetOwnerID(owner)
cnr2.SetBasicACL(basic.FromUint32(0))
cnr2.SetPlacementRule(netmap.PlacementRule{
ReplFactor: 2,
SFGroups: []netmap.SFGroup{
{
Selectors: []netmap.Select{
{Key: "Country", Count: 1},
{Key: netmap.NodesBucket, Count: 10},
},
Filters: []netmap.Filter{
{Key: "Country", F: netmap.FilterIn("Germany", "Spain")},
},
},
},
})
2020-07-24 13:54:03 +00:00
cid2, err := cnrStorage.Put(cnr2)
require.NoError(t, err)
2020-07-24 13:54:03 +00:00
cnr3 := new(storage.Container)
cnr3.SetOwnerID(owner)
cnr3.SetBasicACL(basic.FromUint32(0))
cnr3.SetPlacementRule(netmap.PlacementRule{
ReplFactor: 2,
SFGroups: []netmap.SFGroup{
{
Selectors: []netmap.Select{
{Key: "Country", Count: 1},
},
Filters: []netmap.Filter{
{Key: "Country", F: netmap.FilterIn("Germany", "Spain")},
},
},
},
})
2020-07-24 13:54:03 +00:00
cid3, err := cnrStorage.Put(cnr3)
require.NoError(t, err)
t.Run("Should fail on empty container", func(t *testing.T) {
2020-07-24 13:54:03 +00:00
_, err = p.Query(ctx, ContainerID(*cid2))
require.EqualError(t, errors.Cause(err), ErrEmptyContainer.Error())
})
t.Run("Should fail on Nodes Bucket is omitted in container", func(t *testing.T) {
2020-07-24 13:54:03 +00:00
_, err = p.Query(ctx, ContainerID(*cid3))
require.EqualError(t, errors.Cause(err), ErrNodesBucketOmitted.Error())
})
t.Run("Should fail on unknown container (dht error)", func(t *testing.T) {
_, err = p.Query(ctx, ContainerID(refs.CID{5}))
require.Error(t, err)
})
2020-07-24 13:54:03 +00:00
g, err := p.Query(ctx, ContainerID(*cid1))
require.NoError(t, err)
t.Run("Should return error on empty items", func(t *testing.T) {
_, err = g.Filter(func(netmap.SFGroup, *netmap.Bucket) *netmap.Bucket {
return &netmap.Bucket{}
}).NodeList()
require.EqualError(t, err, ErrEmptyNodes.Error())
})
t.Run("Should ignore some nodes", func(t *testing.T) {
2020-07-24 13:54:03 +00:00
g1, err := p.Query(ctx, ContainerID(*cid1))
require.NoError(t, err)
expect, err := g1.
Filter(filter).
NodeList()
require.NoError(t, err)
2020-07-24 13:54:03 +00:00
g2, err := p.Query(ctx, ContainerID(*cid1))
require.NoError(t, err)
actual, err := g2.
Filter(filter).
NodeList()
require.NoError(t, err)
require.Equal(t, expect, actual)
2020-07-24 13:54:03 +00:00
g3, err := p.Query(ctx, ContainerID(*cid1))
require.NoError(t, err)
actual, err = g3.
Exclude(expect).
Filter(filter).
NodeList()
require.NoError(t, err)
for _, item := range expect {
require.NotContains(t, actual, item)
}
g4, err := p.Query(ctx,
2020-07-24 13:54:03 +00:00
ContainerID(*cid1),
ExcludeNodes(expect))
require.NoError(t, err)
actual, err = g4.
Filter(filter).
NodeList()
require.NoError(t, err)
for _, item := range expect {
require.NotContains(t, actual, item)
}
})
t.Run("Should return error on nil Buckets", func(t *testing.T) {
_, err = g.Filter(func(netmap.SFGroup, *netmap.Bucket) *netmap.Bucket {
return nil
}).NodeList()
require.EqualError(t, err, ErrEmptyNodes.Error())
})
t.Run("Should return error on empty NodeInfo's", func(t *testing.T) {
cp := g.Filter(func(netmap.SFGroup, *netmap.Bucket) *netmap.Bucket {
return nil
})
cp.(*graph).items = nil
_, err := cp.NodeList()
require.EqualError(t, err, ErrEmptyNodes.Error())
})
t.Run("Should return error on unknown items", func(t *testing.T) {
cp := g.Filter(func(_ netmap.SFGroup, b *netmap.Bucket) *netmap.Bucket {
return b
})
cp.(*graph).items = cp.(*graph).items[:5]
_, err := cp.NodeList()
require.Error(t, err)
})
t.Run("Should return error on bad items", func(t *testing.T) {
cp := g.Filter(func(_ netmap.SFGroup, b *netmap.Bucket) *netmap.Bucket {
return b
})
for i := range cp.(*graph).items {
2020-07-24 13:54:03 +00:00
cp.(*graph).items[i].SetAddress("BadAddress")
}
_, err := cp.NodeList()
require.EqualError(t, errors.Cause(err), "failed to parse multiaddr \"BadAddress\": must begin with /")
})
list, err := g.
Filter(filter).
// must return same graph on empty filter
Filter(nil).
NodeList()
require.NoError(t, err)
// 1 Country, 2 Cities, 1 Node = 2 Nodes
require.Len(t, list, 2)
for _, item := range list {
id := idFromAddress(t, item)
require.Contains(t, ids, id[:4]) // exclude our postfix (0-4)
}
}
func TestContainerGraph(t *testing.T) {
t.Run("selectors index out-of-range", func(t *testing.T) {
rule := new(netmap.PlacementRule)
rule.SFGroups = append(rule.SFGroups, netmap.SFGroup{})
require.NotPanics(t, func() {
_, _ = ContainerGraph(
2020-07-24 13:54:03 +00:00
netmapcore.New(),
rule,
nil,
refs.CID{},
)
})
})
}