forked from TrueCloudLab/frostfs-node
[#31] placement: Implement container placement traverser
Signed-off-by: Leonard Lyubich <leonard@nspcc.ru>
This commit is contained in:
parent
44def45ff4
commit
e7925fbc1c
5 changed files with 335 additions and 1 deletions
2
go.mod
2
go.mod
|
@ -13,7 +13,7 @@ require (
|
||||||
github.com/multiformats/go-multiaddr-net v0.1.2 // v0.1.1 => v0.1.2
|
github.com/multiformats/go-multiaddr-net v0.1.2 // v0.1.1 => v0.1.2
|
||||||
github.com/multiformats/go-multihash v0.0.13 // indirect
|
github.com/multiformats/go-multihash v0.0.13 // indirect
|
||||||
github.com/nspcc-dev/neo-go v0.91.1-pre.0.20200827184617-7560aa345a78
|
github.com/nspcc-dev/neo-go v0.91.1-pre.0.20200827184617-7560aa345a78
|
||||||
github.com/nspcc-dev/neofs-api-go v1.3.1-0.20200916115135-ff325b877023
|
github.com/nspcc-dev/neofs-api-go v1.3.1-0.20200917104527-95ae0a649608
|
||||||
github.com/nspcc-dev/neofs-crypto v0.3.0
|
github.com/nspcc-dev/neofs-crypto v0.3.0
|
||||||
github.com/nspcc-dev/tzhash v1.4.0
|
github.com/nspcc-dev/tzhash v1.4.0
|
||||||
github.com/panjf2000/ants/v2 v2.3.0
|
github.com/panjf2000/ants/v2 v2.3.0
|
||||||
|
|
BIN
go.sum
BIN
go.sum
Binary file not shown.
32
pkg/services/object_manager/placement/netmap.go
Normal file
32
pkg/services/object_manager/placement/netmap.go
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
package placement
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/nspcc-dev/neofs-api-go/pkg/netmap"
|
||||||
|
"github.com/nspcc-dev/neofs-api-go/pkg/object"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type netMapBuilder struct {
|
||||||
|
nm *netmap.Netmap
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *netMapBuilder) BuildPlacement(a *object.Address, p *netmap.PlacementPolicy) ([]netmap.Nodes, error) {
|
||||||
|
aV2 := a.ToV2()
|
||||||
|
|
||||||
|
cn, err := b.nm.GetContainerNodes(p, aV2.GetContainerID().GetValue())
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "could not get container nodes")
|
||||||
|
}
|
||||||
|
|
||||||
|
oid := aV2.GetObjectID()
|
||||||
|
if oid == nil {
|
||||||
|
return cn.Replicas(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
on, err := b.nm.GetPlacementVectors(cn, oid.GetValue())
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "could not get placement vectors for object")
|
||||||
|
}
|
||||||
|
|
||||||
|
return on, nil
|
||||||
|
}
|
182
pkg/services/object_manager/placement/traverser.go
Normal file
182
pkg/services/object_manager/placement/traverser.go
Normal file
|
@ -0,0 +1,182 @@
|
||||||
|
package placement
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neofs-api-go/pkg/container"
|
||||||
|
"github.com/nspcc-dev/neofs-api-go/pkg/netmap"
|
||||||
|
"github.com/nspcc-dev/neofs-api-go/pkg/object"
|
||||||
|
"github.com/nspcc-dev/neofs-node/pkg/network"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Builder is an interface of the
|
||||||
|
// object placement vector builder.
|
||||||
|
type Builder interface {
|
||||||
|
// BuildPlacement returns the list of placement vectors
|
||||||
|
// for object according to the placement policy.
|
||||||
|
//
|
||||||
|
// Must return all container nodes if object identifier
|
||||||
|
// is nil.
|
||||||
|
BuildPlacement(*object.Address, *netmap.PlacementPolicy) ([]netmap.Nodes, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Option represents placement traverser option.
|
||||||
|
type Option func(*cfg)
|
||||||
|
|
||||||
|
// Traverser represents utility for controlling
|
||||||
|
// traversal of object placement vectors.
|
||||||
|
type Traverser struct {
|
||||||
|
mtx *sync.RWMutex
|
||||||
|
|
||||||
|
rem int
|
||||||
|
|
||||||
|
nextI, nextJ int
|
||||||
|
|
||||||
|
vectors []netmap.Nodes
|
||||||
|
}
|
||||||
|
|
||||||
|
type cfg struct {
|
||||||
|
rem int
|
||||||
|
|
||||||
|
addr *object.Address
|
||||||
|
|
||||||
|
policy *netmap.PlacementPolicy
|
||||||
|
|
||||||
|
builder Builder
|
||||||
|
}
|
||||||
|
|
||||||
|
var errNilBuilder = errors.New("placement builder is nil")
|
||||||
|
|
||||||
|
func defaultCfg() *cfg {
|
||||||
|
return &cfg{
|
||||||
|
rem: 1,
|
||||||
|
addr: object.NewAddress(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTraverser creates, initializes with options and returns Traverser instance.
|
||||||
|
func NewTraverser(opts ...Option) (*Traverser, error) {
|
||||||
|
cfg := defaultCfg()
|
||||||
|
|
||||||
|
for i := range opts {
|
||||||
|
if opts[i] != nil {
|
||||||
|
opts[i](cfg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.builder == nil {
|
||||||
|
return nil, errors.Wrap(errNilBuilder, "incomplete traverser options")
|
||||||
|
}
|
||||||
|
|
||||||
|
ns, err := cfg.builder.BuildPlacement(cfg.addr, cfg.policy)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "could not build placement")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Traverser{
|
||||||
|
mtx: new(sync.RWMutex),
|
||||||
|
rem: cfg.rem,
|
||||||
|
vectors: ns,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next returns next unprocessed address of the object placement.
|
||||||
|
//
|
||||||
|
// Returns nil if no nodes left or traversal operation succeeded.
|
||||||
|
func (t *Traverser) Next() *network.Address {
|
||||||
|
t.mtx.Lock()
|
||||||
|
defer t.mtx.Unlock()
|
||||||
|
|
||||||
|
if t.rem == 0 || t.nextI == len(t.vectors) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
addr, err := network.AddressFromString(t.vectors[t.nextI][t.nextJ].NetworkAddress())
|
||||||
|
if err != nil {
|
||||||
|
// TODO: log error
|
||||||
|
}
|
||||||
|
|
||||||
|
if t.nextJ++; t.nextJ == len(t.vectors[t.nextI]) {
|
||||||
|
t.nextJ = 0
|
||||||
|
t.nextI++
|
||||||
|
}
|
||||||
|
|
||||||
|
return addr
|
||||||
|
}
|
||||||
|
|
||||||
|
// SubmitSuccess writes single succeeded node operation.
|
||||||
|
func (t *Traverser) SubmitSuccess() {
|
||||||
|
t.mtx.Lock()
|
||||||
|
t.rem--
|
||||||
|
t.mtx.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Success returns true if traversal operation succeeded.
|
||||||
|
func (t *Traverser) Success() bool {
|
||||||
|
t.mtx.RLock()
|
||||||
|
s := t.rem <= 0
|
||||||
|
t.mtx.RUnlock()
|
||||||
|
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// UseBuilder is a placement builder setting option.
|
||||||
|
//
|
||||||
|
// Overlaps UseNetworkMap option.
|
||||||
|
func UseBuilder(b Builder) Option {
|
||||||
|
return func(c *cfg) {
|
||||||
|
c.builder = b
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// UseNetworkMap is a placement builder based on network
|
||||||
|
// map setting option.
|
||||||
|
//
|
||||||
|
// Overlaps UseBuilder option.
|
||||||
|
func UseNetworkMap(nm *netmap.Netmap) Option {
|
||||||
|
return func(c *cfg) {
|
||||||
|
c.builder = &netMapBuilder{
|
||||||
|
nm: nm,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ForContainer is a traversal container setting option.
|
||||||
|
func ForContainer(cnr *container.Container) Option {
|
||||||
|
return func(c *cfg) {
|
||||||
|
c.policy = cnr.GetPlacementPolicy()
|
||||||
|
|
||||||
|
c.rem = 0
|
||||||
|
for _, r := range c.policy.GetReplicas() {
|
||||||
|
c.rem += int(r.GetCount())
|
||||||
|
}
|
||||||
|
|
||||||
|
c.addr.SetContainerID(container.CalculateID(cnr))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ForObject is a processing object setting option.
|
||||||
|
func ForObject(id *object.ID) Option {
|
||||||
|
return func(c *cfg) {
|
||||||
|
c.addr.SetObjectID(id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SuccessAfter is a success number setting option.
|
||||||
|
//
|
||||||
|
// Option has no effect if the number is not positive.
|
||||||
|
func SuccessAfter(v int) Option {
|
||||||
|
return func(c *cfg) {
|
||||||
|
if v > 0 {
|
||||||
|
c.rem = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithoutSuccessTracking disables success tracking in traversal.
|
||||||
|
func WithoutSuccessTracking() Option {
|
||||||
|
return func(c *cfg) {
|
||||||
|
c.rem = -1
|
||||||
|
}
|
||||||
|
}
|
120
pkg/services/object_manager/placement/traverser_test.go
Normal file
120
pkg/services/object_manager/placement/traverser_test.go
Normal file
|
@ -0,0 +1,120 @@
|
||||||
|
package placement
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neofs-api-go/pkg/netmap"
|
||||||
|
"github.com/nspcc-dev/neofs-api-go/pkg/object"
|
||||||
|
netmapV2 "github.com/nspcc-dev/neofs-node/pkg/morph/client/netmap"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
type testBuilder struct {
|
||||||
|
vectors []netmap.Nodes
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b testBuilder) BuildPlacement(*object.Address, *netmap.PlacementPolicy) ([]netmap.Nodes, error) {
|
||||||
|
return b.vectors, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func testNode(v uint32) netmapV2.NodeInfo {
|
||||||
|
n := netmapV2.NodeInfo{}
|
||||||
|
n.SetAddress("/ip4/0.0.0.0/tcp/" + strconv.Itoa(int(v)))
|
||||||
|
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
func flattenVectors(vs []netmap.Nodes) netmap.Nodes {
|
||||||
|
v := make(netmap.Nodes, 0)
|
||||||
|
|
||||||
|
for i := range vs {
|
||||||
|
v = append(v, vs[i]...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
func testPlacement(t *testing.T, sz []int) []netmap.Nodes {
|
||||||
|
res := make([]netmap.Nodes, 0, len(sz))
|
||||||
|
num := uint32(0)
|
||||||
|
|
||||||
|
for i := range sz {
|
||||||
|
ns := make([]netmapV2.NodeInfo, 0, sz[i])
|
||||||
|
|
||||||
|
for j := 0; j < sz[i]; j++ {
|
||||||
|
ns = append(ns, testNode(num))
|
||||||
|
num++
|
||||||
|
}
|
||||||
|
|
||||||
|
res = append(res, netmap.NodesFromV2(ns))
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTraverserObjectScenarios(t *testing.T) {
|
||||||
|
t.Run("search scenario", func(t *testing.T) {
|
||||||
|
nodes := testPlacement(t, []int{2, 3})
|
||||||
|
|
||||||
|
allNodes := flattenVectors(nodes)
|
||||||
|
|
||||||
|
tr, err := NewTraverser(
|
||||||
|
UseBuilder(&testBuilder{vectors: nodes}),
|
||||||
|
WithoutSuccessTracking(),
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.True(t, tr.Success())
|
||||||
|
|
||||||
|
for i := range allNodes {
|
||||||
|
require.Equal(t, allNodes[i].NetworkAddress(), tr.Next().String())
|
||||||
|
}
|
||||||
|
|
||||||
|
require.Nil(t, tr.Next())
|
||||||
|
require.True(t, tr.Success())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("read scenario", func(t *testing.T) {
|
||||||
|
nodes := testPlacement(t, []int{5, 3, 4})
|
||||||
|
|
||||||
|
allNodes := flattenVectors(nodes)
|
||||||
|
|
||||||
|
tr, err := NewTraverser(
|
||||||
|
UseBuilder(&testBuilder{vectors: nodes}),
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
for i := range allNodes[:len(allNodes)-3] {
|
||||||
|
require.Equal(t, allNodes[i].NetworkAddress(), tr.Next().String())
|
||||||
|
}
|
||||||
|
|
||||||
|
require.False(t, tr.Success())
|
||||||
|
|
||||||
|
tr.SubmitSuccess()
|
||||||
|
|
||||||
|
require.True(t, tr.Success())
|
||||||
|
|
||||||
|
require.Nil(t, tr.Next())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("put scenario", func(t *testing.T) {
|
||||||
|
nodes := testPlacement(t, []int{3, 3, 3})
|
||||||
|
sucCount := 3
|
||||||
|
|
||||||
|
tr, err := NewTraverser(
|
||||||
|
UseBuilder(&testBuilder{vectors: nodes}),
|
||||||
|
SuccessAfter(sucCount),
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
for i := 0; i < sucCount; i++ {
|
||||||
|
require.NotNil(t, tr.Next())
|
||||||
|
require.False(t, tr.Success())
|
||||||
|
tr.SubmitSuccess()
|
||||||
|
}
|
||||||
|
|
||||||
|
require.Nil(t, tr.Next())
|
||||||
|
require.True(t, tr.Success())
|
||||||
|
})
|
||||||
|
}
|
Loading…
Reference in a new issue