[#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-multihash v0.0.13 // indirect
|
||||
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/tzhash v1.4.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