forked from TrueCloudLab/frostfs-node
378 lines
9.1 KiB
Go
378 lines
9.1 KiB
Go
package object
|
|
|
|
import (
|
|
"context"
|
|
"strconv"
|
|
"sync"
|
|
"testing"
|
|
|
|
"github.com/multiformats/go-multiaddr"
|
|
"github.com/pkg/errors"
|
|
"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: errors.New(""), // 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: errors.New(""), // 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
|
|
}
|