204 lines
5.1 KiB
Go
204 lines
5.1 KiB
Go
|
// Copyright (C) 2020 Storj Labs, Incache.
|
||
|
// See LICENSE for copying information.
|
||
|
|
||
|
package nodeselection_test
|
||
|
|
||
|
import (
|
||
|
"strconv"
|
||
|
"testing"
|
||
|
|
||
|
"github.com/stretchr/testify/require"
|
||
|
"golang.org/x/sync/errgroup"
|
||
|
|
||
|
"storj.io/common/pb"
|
||
|
"storj.io/common/testcontext"
|
||
|
"storj.io/common/testrand"
|
||
|
"storj.io/storj/satellite/nodeselection"
|
||
|
)
|
||
|
|
||
|
func TestState_Select(t *testing.T) {
|
||
|
ctx := testcontext.New(t)
|
||
|
defer ctx.Cleanup()
|
||
|
|
||
|
reputableNodes := joinNodes(
|
||
|
createRandomNodes(2, "1.0.1"),
|
||
|
createRandomNodes(3, "1.0.2"),
|
||
|
)
|
||
|
newNodes := joinNodes(
|
||
|
createRandomNodes(2, "1.0.3"),
|
||
|
createRandomNodes(3, "1.0.4"),
|
||
|
)
|
||
|
|
||
|
state := nodeselection.NewState(reputableNodes, newNodes)
|
||
|
require.Equal(t, nodeselection.Stats{
|
||
|
New: 5,
|
||
|
Reputable: 5,
|
||
|
NewDistinct: 2,
|
||
|
ReputableDistinct: 2,
|
||
|
}, state.Stats())
|
||
|
|
||
|
{ // select 5 non-distinct subnet reputable nodes
|
||
|
const selectCount = 5
|
||
|
selected, err := state.Select(ctx, nodeselection.Request{
|
||
|
Count: selectCount,
|
||
|
NewFraction: 0,
|
||
|
Distinct: false,
|
||
|
ExcludedIDs: nil,
|
||
|
})
|
||
|
require.NoError(t, err)
|
||
|
require.Len(t, selected, selectCount)
|
||
|
}
|
||
|
|
||
|
{ // select 2 distinct subnet reputable nodes
|
||
|
const selectCount = 2
|
||
|
selected, err := state.Select(ctx, nodeselection.Request{
|
||
|
Count: selectCount,
|
||
|
NewFraction: 0,
|
||
|
Distinct: true,
|
||
|
ExcludedIDs: nil,
|
||
|
})
|
||
|
require.NoError(t, err)
|
||
|
require.Len(t, selected, selectCount)
|
||
|
}
|
||
|
|
||
|
{ // try to select 5 distinct subnet reputable nodes, but there are only two 2 in the state
|
||
|
const selectCount = 5
|
||
|
selected, err := state.Select(ctx, nodeselection.Request{
|
||
|
Count: selectCount,
|
||
|
NewFraction: 0,
|
||
|
Distinct: true,
|
||
|
ExcludedIDs: nil,
|
||
|
})
|
||
|
require.Error(t, err)
|
||
|
require.Len(t, selected, 2)
|
||
|
}
|
||
|
|
||
|
{ // select 6 non-distinct subnet reputable and new nodes (50%)
|
||
|
const selectCount = 6
|
||
|
const newFraction = 0.5
|
||
|
selected, err := state.Select(ctx, nodeselection.Request{
|
||
|
Count: selectCount,
|
||
|
NewFraction: newFraction,
|
||
|
Distinct: false,
|
||
|
ExcludedIDs: nil,
|
||
|
})
|
||
|
require.NoError(t, err)
|
||
|
require.Len(t, selected, selectCount)
|
||
|
require.Len(t, intersectLists(selected, reputableNodes), selectCount*(1-newFraction))
|
||
|
require.Len(t, intersectLists(selected, newNodes), selectCount*newFraction)
|
||
|
}
|
||
|
|
||
|
{ // select 4 distinct subnet reputable and new nodes (50%)
|
||
|
const selectCount = 4
|
||
|
const newFraction = 0.5
|
||
|
selected, err := state.Select(ctx, nodeselection.Request{
|
||
|
Count: selectCount,
|
||
|
NewFraction: newFraction,
|
||
|
Distinct: true,
|
||
|
ExcludedIDs: nil,
|
||
|
})
|
||
|
require.NoError(t, err)
|
||
|
require.Len(t, selected, selectCount)
|
||
|
require.Len(t, intersectLists(selected, reputableNodes), selectCount*(1-newFraction))
|
||
|
require.Len(t, intersectLists(selected, newNodes), selectCount*newFraction)
|
||
|
}
|
||
|
|
||
|
{ // select 10 distinct subnet reputable and new nodes (100%), falling back to 5 reputable
|
||
|
const selectCount = 10
|
||
|
const newFraction = 1.0
|
||
|
selected, err := state.Select(ctx, nodeselection.Request{
|
||
|
Count: selectCount,
|
||
|
NewFraction: newFraction,
|
||
|
Distinct: false,
|
||
|
ExcludedIDs: nil,
|
||
|
})
|
||
|
require.NoError(t, err)
|
||
|
require.Len(t, selected, selectCount)
|
||
|
require.Len(t, intersectLists(selected, reputableNodes), 5)
|
||
|
require.Len(t, intersectLists(selected, newNodes), 5)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestState_Select_Concurrent(t *testing.T) {
|
||
|
ctx := testcontext.New(t)
|
||
|
defer ctx.Cleanup()
|
||
|
|
||
|
reputableNodes := joinNodes(
|
||
|
createRandomNodes(2, "1.0.1"),
|
||
|
createRandomNodes(3, "1.0.2"),
|
||
|
)
|
||
|
newNodes := joinNodes(
|
||
|
createRandomNodes(2, "1.0.3"),
|
||
|
createRandomNodes(3, "1.0.4"),
|
||
|
)
|
||
|
|
||
|
state := nodeselection.NewState(reputableNodes, newNodes)
|
||
|
|
||
|
var group errgroup.Group
|
||
|
group.Go(func() error {
|
||
|
const selectCount = 5
|
||
|
nodes, err := state.Select(ctx, nodeselection.Request{
|
||
|
Count: selectCount,
|
||
|
NewFraction: 0.5,
|
||
|
Distinct: false,
|
||
|
ExcludedIDs: nil,
|
||
|
})
|
||
|
require.Len(t, nodes, selectCount)
|
||
|
return err
|
||
|
})
|
||
|
|
||
|
group.Go(func() error {
|
||
|
const selectCount = 4
|
||
|
nodes, err := state.Select(ctx, nodeselection.Request{
|
||
|
Count: selectCount,
|
||
|
NewFraction: 0.5,
|
||
|
Distinct: true,
|
||
|
ExcludedIDs: nil,
|
||
|
})
|
||
|
require.Len(t, nodes, selectCount)
|
||
|
return err
|
||
|
})
|
||
|
require.NoError(t, group.Wait())
|
||
|
}
|
||
|
|
||
|
// createRandomNodes creates n random nodes all in the subnet.
|
||
|
func createRandomNodes(n int, subnet string) []*nodeselection.Node {
|
||
|
xs := make([]*nodeselection.Node, n)
|
||
|
for i := range xs {
|
||
|
addr := subnet + "." + strconv.Itoa(i) + ":8080"
|
||
|
xs[i] = &nodeselection.Node{
|
||
|
ID: testrand.NodeID(),
|
||
|
LastNet: subnet,
|
||
|
LastIPPort: addr,
|
||
|
Address: &pb.NodeAddress{Address: addr},
|
||
|
}
|
||
|
}
|
||
|
return xs
|
||
|
}
|
||
|
|
||
|
// joinNodes appends all slices into a single slice.
|
||
|
func joinNodes(lists ...[]*nodeselection.Node) []*nodeselection.Node {
|
||
|
xs := []*nodeselection.Node{}
|
||
|
for _, list := range lists {
|
||
|
xs = append(xs, list...)
|
||
|
}
|
||
|
return xs
|
||
|
}
|
||
|
|
||
|
// intersectLists returns nodes that exist in both lists compared by ID.
|
||
|
func intersectLists(as, bs []*nodeselection.Node) []*nodeselection.Node {
|
||
|
var xs []*nodeselection.Node
|
||
|
|
||
|
next:
|
||
|
for _, a := range as {
|
||
|
for _, b := range bs {
|
||
|
if a.ID == b.ID {
|
||
|
xs = append(xs, a)
|
||
|
continue next
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return xs
|
||
|
}
|