frostfs-node/services/public/object/traverse_test.go
alexvanin dadfd90dcd Initial commit
Initial public review release v0.10.0
2020-07-10 17:45:00 +03:00

378 lines
9.1 KiB
Go

package object
import (
"context"
"strconv"
"sync"
"testing"
"github.com/multiformats/go-multiaddr"
"github.com/nspcc-dev/neofs-node/internal"
"github.com/stretchr/testify/require"
"go.uber.org/zap"
)
type (
// Entity for mocking interfaces.
// Implementation of any interface intercepts arguments via f (if not nil).
// If err is not nil, it returns as it is. Otherwise, casted to needed type res returns w/o error.
testTraverseEntity struct {
// Set of interfaces which testCommonEntity must implement, but some methods from those does not call.
serviceRequest
Placer
// Argument interceptor. Used for ascertain of correct parameter passage between components.
f func(...interface{})
// Mocked result of any interface.
res interface{}
// Mocked error of any interface.
err error
}
)
var (
_ Placer = (*testTraverseEntity)(nil)
_ placementBuilder = (*testTraverseEntity)(nil)
)
func (s *testTraverseEntity) GetNodes(ctx context.Context, a Address, p bool, e ...multiaddr.Multiaddr) ([]multiaddr.Multiaddr, error) {
if s.f != nil {
s.f(a, p, e)
}
if s.err != nil {
return nil, s.err
}
return s.res.([]multiaddr.Multiaddr), nil
}
func (s *testTraverseEntity) buildPlacement(_ context.Context, addr Address, excl ...multiaddr.Multiaddr) ([]multiaddr.Multiaddr, error) {
if s.f != nil {
s.f(addr, excl)
}
if s.err != nil {
return nil, s.err
}
return s.res.([]multiaddr.Multiaddr), nil
}
func Test_coreCnrAffChecker_buildPlacement(t *testing.T) {
ctx := context.TODO()
addr := testObjectAddress(t)
nodes := testNodeList(t, 2)
t.Run("correct placer params", func(t *testing.T) {
s := &corePlacementUtil{
prevNetMap: true,
placementBuilder: &testTraverseEntity{
f: func(items ...interface{}) {
require.Equal(t, addr, items[0].(Address))
require.True(t, items[1].(bool))
require.Equal(t, nodes, items[2].([]multiaddr.Multiaddr))
},
err: internal.Error(""), // just to prevent panic
},
log: zap.L(),
}
s.buildPlacement(ctx, addr, nodes...)
})
t.Run("correct result", func(t *testing.T) {
t.Run("placer error", func(t *testing.T) {
s := &corePlacementUtil{
placementBuilder: &testTraverseEntity{
err: internal.Error(""), // force Placer to return some error
},
log: zap.L(),
}
res, err := s.buildPlacement(ctx, addr)
require.Error(t, err)
require.Empty(t, res)
})
t.Run("placer success", func(t *testing.T) {
s := &corePlacementUtil{
placementBuilder: &testTraverseEntity{
res: nodes, // force Placer to return nodes
},
log: zap.L(),
}
res, err := s.buildPlacement(ctx, addr)
require.NoError(t, err)
require.Equal(t, nodes, res)
})
})
}
func Test_coreTraverser(t *testing.T) {
ctx := context.TODO()
t.Run("new", func(t *testing.T) {
addr := testObjectAddress(t)
pl := new(testTraverseEntity)
v := newContainerTraverser(&traverseParams{
tryPrevNM: true,
addr: addr,
curPlacementBuilder: pl,
prevPlacementBuilder: pl,
maxRecycleCount: 10,
})
res := v.(*coreTraverser)
require.NotNil(t, res.RWMutex)
require.Equal(t, addr, res.addr)
require.True(t, res.tryPrevNM)
require.False(t, res.usePrevNM)
require.NotNil(t, res.mDone)
require.Empty(t, res.mDone)
require.Empty(t, res.failed)
require.Equal(t, 10, res.maxRecycleCount)
require.Equal(t, pl, res.curPlacementBuilder)
require.Equal(t, pl, res.prevPlacementBuilder)
require.Equal(t, 0, res.stopCount)
})
t.Run("close", func(t *testing.T) {
v := newContainerTraverser(&traverseParams{
curPlacementBuilder: &testTraverseEntity{
res: make([]multiaddr.Multiaddr, 1),
},
})
v.close()
require.Empty(t, v.Next(ctx))
require.True(t, v.(*coreTraverser).isClosed())
})
t.Run("done", func(t *testing.T) {
nodes := testNodeList(t, 3)
v := newContainerTraverser(&traverseParams{})
v.add(nodes[0], true)
require.True(t, v.done(nodes[0]))
v.add(nodes[1], false)
require.False(t, v.done(nodes[1]))
require.False(t, v.done(nodes[2]))
})
t.Run("finished", func(t *testing.T) {
t.Run("zero stop count", func(t *testing.T) {
containerTraverser := &coreTraverser{
RWMutex: new(sync.RWMutex),
traverseParams: traverseParams{stopCount: 0},
}
require.False(t, containerTraverser.finished())
})
t.Run("positive stop count", func(t *testing.T) {
containerTraverser := &coreTraverser{
RWMutex: new(sync.RWMutex),
mDone: make(map[string]struct{}),
traverseParams: traverseParams{stopCount: 3},
}
for i := 0; i < containerTraverser.stopCount-1; i++ {
containerTraverser.mDone[strconv.Itoa(i)] = struct{}{}
}
require.False(t, containerTraverser.finished())
containerTraverser.mDone["last node"] = struct{}{}
require.True(t, containerTraverser.finished())
})
})
t.Run("add result", func(t *testing.T) {
mAddr := testNode(t, 0)
containerTraverser := &coreTraverser{
RWMutex: new(sync.RWMutex),
mDone: make(map[string]struct{}),
}
containerTraverser.add(mAddr, true)
_, ok := containerTraverser.mDone[mAddr.String()]
require.True(t, ok)
containerTraverser.add(mAddr, false)
require.Contains(t, containerTraverser.failed, mAddr)
})
t.Run("reset", func(t *testing.T) {
initRecycleNum := 1
s := &coreTraverser{
failed: testNodeList(t, 1),
usePrevNM: true,
recycleNum: initRecycleNum,
}
s.reset()
require.Empty(t, s.failed)
require.False(t, s.usePrevNM)
require.Equal(t, initRecycleNum+1, s.recycleNum)
})
t.Run("next", func(t *testing.T) {
t.Run("exclude done nodes from result", func(t *testing.T) {
nodes := testNodeList(t, 5)
done := make([]multiaddr.Multiaddr, 2)
copy(done, nodes)
pl := &testTraverseEntity{res: nodes}
tr := newContainerTraverser(&traverseParams{curPlacementBuilder: pl})
for i := range done {
tr.add(done[i], true)
}
res := tr.Next(ctx)
for i := range done {
require.NotContains(t, res, done[i])
}
})
t.Run("stop count initialization", func(t *testing.T) {
nodes := testNodeList(t, 5)
pl := &testTraverseEntity{res: nodes}
tr := newContainerTraverser(&traverseParams{curPlacementBuilder: pl})
_ = tr.Next(ctx)
require.Equal(t, len(nodes), tr.(*coreTraverser).stopCount)
})
t.Run("all nodes are done", func(t *testing.T) {
nodes := testNodeList(t, 5)
pl := &testTraverseEntity{res: nodes}
tr := newContainerTraverser(&traverseParams{curPlacementBuilder: pl})
require.Equal(t, nodes, tr.Next(ctx))
for i := range nodes {
tr.add(nodes[i], true)
}
require.Empty(t, tr.Next(ctx))
})
t.Run("failed nodes accounting", func(t *testing.T) {
nodes := testNodeList(t, 5)
failed := nodes[:len(nodes)-2]
_ = failed
addr := testObjectAddress(t)
pl := &testTraverseEntity{
f: func(items ...interface{}) {
t.Run("correct placer params", func(t *testing.T) {
require.Equal(t, addr, items[0].(Address))
require.Equal(t, failed, items[1].([]multiaddr.Multiaddr))
})
},
res: nodes,
}
tr := newContainerTraverser(&traverseParams{
addr: addr,
curPlacementBuilder: pl,
})
for i := range failed {
tr.add(failed[i], false)
}
_ = tr.Next(ctx)
})
t.Run("placement build failure", func(t *testing.T) {
t.Run("forbid previous network map", func(t *testing.T) {
pl := &testTraverseEntity{res: make([]multiaddr.Multiaddr, 0)}
tr := newContainerTraverser(&traverseParams{curPlacementBuilder: pl})
require.Empty(t, tr.Next(ctx))
})
t.Run("allow previous network map", func(t *testing.T) {
t.Run("failure", func(t *testing.T) {
pl := &testTraverseEntity{
res: make([]multiaddr.Multiaddr, 0),
}
tr := newContainerTraverser(&traverseParams{
tryPrevNM: true,
curPlacementBuilder: pl,
prevPlacementBuilder: pl,
})
require.Empty(t, tr.Next(ctx))
})
t.Run("success", func(t *testing.T) {
nodes := testNodeList(t, 5)
tr := newContainerTraverser(&traverseParams{
tryPrevNM: true,
curPlacementBuilder: &testTraverseEntity{
res: make([]multiaddr.Multiaddr, 0),
},
prevPlacementBuilder: &testTraverseEntity{
res: nodes,
},
})
require.Equal(t, nodes, tr.Next(ctx))
})
})
t.Run("recycle", func(t *testing.T) {
recycleCount := 5
curNetMapCallCounter, prevNetMapCallCounter := 0, 0
tr := newContainerTraverser(&traverseParams{
tryPrevNM: true,
curPlacementBuilder: &testTraverseEntity{
f: func(items ...interface{}) {
curNetMapCallCounter++
},
res: make([]multiaddr.Multiaddr, 0),
},
prevPlacementBuilder: &testTraverseEntity{
f: func(items ...interface{}) {
prevNetMapCallCounter++
},
res: make([]multiaddr.Multiaddr, 0),
},
maxRecycleCount: recycleCount,
})
_ = tr.Next(ctx)
require.Equal(t, recycleCount+1, prevNetMapCallCounter)
require.Equal(t, recycleCount+1, curNetMapCallCounter)
})
})
})
}
func testNodeList(t *testing.T, count int) (res []multiaddr.Multiaddr) {
for i := 0; i < count; i++ {
res = append(res, testNode(t, i))
}
return
}