0b02a48a10
Current node selection logic (in case of using SelectBySubnet): 1. selects one subnet randomly 2. selects one node randomly from the subnet 3. applies the placement NodeFilters to the node and ignore it, if doesn't match This logic is wrong: 1. Imagine that we have a subnet with two DE and one GB nodes. 2. We would like to select DE nodes 2. In case of GB node is selected (randomly) in step2, step3 will ignore the subnet, even if there are good (DE) nodes in there. Change-Id: I7673f52c89b46e0cc7b20a9b74137dc689d6c17e
284 lines
8.7 KiB
Go
284 lines
8.7 KiB
Go
// Copyright (C) 2020 Storj Labs, Inc.
|
|
// See LICENSE for copying information.
|
|
|
|
package nodeselection_test
|
|
|
|
import (
|
|
"fmt"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"storj.io/common/identity/testidentity"
|
|
"storj.io/common/storj"
|
|
"storj.io/common/storj/location"
|
|
"storj.io/common/testcontext"
|
|
"storj.io/common/testrand"
|
|
"storj.io/storj/satellite/nodeselection"
|
|
)
|
|
|
|
func TestSelectByID(t *testing.T) {
|
|
// create 3 nodes, 2 with same subnet
|
|
// perform many node selections that selects 2 nodes
|
|
// expect that the all node are selected ~33% of the time.
|
|
ctx := testcontext.New(t)
|
|
defer ctx.Cleanup()
|
|
|
|
// create 3 nodes, 2 with same subnet
|
|
lastNetDuplicate := "1.0.1"
|
|
subnetA1 := &nodeselection.SelectedNode{
|
|
ID: testrand.NodeID(),
|
|
LastNet: lastNetDuplicate,
|
|
LastIPPort: lastNetDuplicate + ".4:8080",
|
|
}
|
|
subnetA2 := &nodeselection.SelectedNode{
|
|
ID: testrand.NodeID(),
|
|
LastNet: lastNetDuplicate,
|
|
LastIPPort: lastNetDuplicate + ".5:8080",
|
|
}
|
|
|
|
lastNetSingle := "1.0.2"
|
|
subnetB1 := &nodeselection.SelectedNode{
|
|
ID: testrand.NodeID(),
|
|
LastNet: lastNetSingle,
|
|
LastIPPort: lastNetSingle + ".5:8080",
|
|
}
|
|
|
|
nodes := []*nodeselection.SelectedNode{subnetA1, subnetA2, subnetB1}
|
|
selector := nodeselection.SelectByID(nodes)
|
|
|
|
const (
|
|
reqCount = 2
|
|
executionCount = 10000
|
|
)
|
|
|
|
var selectedNodeCount = map[storj.NodeID]int{}
|
|
|
|
// perform many node selections that selects 2 nodes
|
|
for i := 0; i < executionCount; i++ {
|
|
selectedNodes := selector.Select(reqCount, nodeselection.NodeFilters{})
|
|
require.Len(t, selectedNodes, reqCount)
|
|
for _, node := range selectedNodes {
|
|
selectedNodeCount[node.ID]++
|
|
}
|
|
}
|
|
|
|
subnetA1Count := float64(selectedNodeCount[subnetA1.ID])
|
|
subnetA2Count := float64(selectedNodeCount[subnetA2.ID])
|
|
subnetB1Count := float64(selectedNodeCount[subnetB1.ID])
|
|
total := subnetA1Count + subnetA2Count + subnetB1Count
|
|
assert.Equal(t, total, float64(reqCount*executionCount))
|
|
|
|
const selectionEpsilon = 0.1
|
|
const percent = 1.0 / 3.0
|
|
assert.InDelta(t, subnetA1Count/total, percent, selectionEpsilon)
|
|
assert.InDelta(t, subnetA2Count/total, percent, selectionEpsilon)
|
|
assert.InDelta(t, subnetB1Count/total, percent, selectionEpsilon)
|
|
}
|
|
|
|
func TestSelectBySubnet(t *testing.T) {
|
|
// create 3 nodes, 2 with same subnet
|
|
// perform many node selections that selects 2 nodes
|
|
// expect that the single node is selected 50% of the time
|
|
// expect the 2 nodes with same subnet should each be selected 25% of time
|
|
ctx := testcontext.New(t)
|
|
defer ctx.Cleanup()
|
|
|
|
// create 3 nodes, 2 with same subnet
|
|
lastNetDuplicate := "1.0.1"
|
|
subnetA1 := &nodeselection.SelectedNode{
|
|
ID: testrand.NodeID(),
|
|
LastNet: lastNetDuplicate,
|
|
LastIPPort: lastNetDuplicate + ".4:8080",
|
|
}
|
|
subnetA2 := &nodeselection.SelectedNode{
|
|
ID: testrand.NodeID(),
|
|
LastNet: lastNetDuplicate,
|
|
LastIPPort: lastNetDuplicate + ".5:8080",
|
|
}
|
|
|
|
lastNetSingle := "1.0.2"
|
|
subnetB1 := &nodeselection.SelectedNode{
|
|
ID: testrand.NodeID(),
|
|
LastNet: lastNetSingle,
|
|
LastIPPort: lastNetSingle + ".5:8080",
|
|
}
|
|
|
|
nodes := []*nodeselection.SelectedNode{subnetA1, subnetA2, subnetB1}
|
|
selector := nodeselection.SelectBySubnetFromNodes(nodes)
|
|
|
|
const (
|
|
reqCount = 2
|
|
executionCount = 1000
|
|
)
|
|
|
|
var selectedNodeCount = map[storj.NodeID]int{}
|
|
|
|
// perform many node selections that selects 2 nodes
|
|
for i := 0; i < executionCount; i++ {
|
|
selectedNodes := selector.Select(reqCount, nodeselection.NodeFilters{})
|
|
require.Len(t, selectedNodes, reqCount)
|
|
for _, node := range selectedNodes {
|
|
selectedNodeCount[node.ID]++
|
|
}
|
|
}
|
|
|
|
subnetA1Count := float64(selectedNodeCount[subnetA1.ID])
|
|
subnetA2Count := float64(selectedNodeCount[subnetA2.ID])
|
|
subnetB1Count := float64(selectedNodeCount[subnetB1.ID])
|
|
total := subnetA1Count + subnetA2Count + subnetB1Count
|
|
assert.Equal(t, total, float64(reqCount*executionCount))
|
|
|
|
// expect that the single node is selected 50% of the time
|
|
// expect the 2 nodes with same subnet should each be selected 25% of time
|
|
nodeID1total := subnetA1Count / total
|
|
nodeID2total := subnetA2Count / total
|
|
|
|
const (
|
|
selectionEpsilon = 0.1
|
|
uniqueSubnet = 0.5
|
|
)
|
|
|
|
// we expect that the 2 nodes from the same subnet should be
|
|
// selected roughly the same percent of the time
|
|
assert.InDelta(t, nodeID2total, nodeID1total, selectionEpsilon)
|
|
|
|
// the node from the unique subnet should be selected exactly half of the time
|
|
nodeID3total := subnetB1Count / total
|
|
assert.Equal(t, nodeID3total, uniqueSubnet)
|
|
}
|
|
|
|
func TestSelectBySubnetOneAtATime(t *testing.T) {
|
|
// create 3 nodes, 2 with same subnet
|
|
// perform many node selections that selects 1 node
|
|
// expect that the single node is selected 50% of the time
|
|
// expect the 2 nodes with same subnet should each be selected 25% of time
|
|
ctx := testcontext.New(t)
|
|
defer ctx.Cleanup()
|
|
|
|
// create 3 nodes, 2 with same subnet
|
|
lastNetDuplicate := "1.0.1"
|
|
subnetA1 := &nodeselection.SelectedNode{
|
|
ID: testrand.NodeID(),
|
|
LastNet: lastNetDuplicate,
|
|
LastIPPort: lastNetDuplicate + ".4:8080",
|
|
}
|
|
subnetA2 := &nodeselection.SelectedNode{
|
|
ID: testrand.NodeID(),
|
|
LastNet: lastNetDuplicate,
|
|
LastIPPort: lastNetDuplicate + ".5:8080",
|
|
}
|
|
|
|
lastNetSingle := "1.0.2"
|
|
subnetB1 := &nodeselection.SelectedNode{
|
|
ID: testrand.NodeID(),
|
|
LastNet: lastNetSingle,
|
|
LastIPPort: lastNetSingle + ".5:8080",
|
|
}
|
|
|
|
nodes := []*nodeselection.SelectedNode{subnetA1, subnetA2, subnetB1}
|
|
selector := nodeselection.SelectBySubnetFromNodes(nodes)
|
|
|
|
const (
|
|
reqCount = 1
|
|
executionCount = 1000
|
|
)
|
|
|
|
var selectedNodeCount = map[storj.NodeID]int{}
|
|
|
|
// perform many node selections that selects 1 node
|
|
for i := 0; i < executionCount; i++ {
|
|
selectedNodes := selector.Select(reqCount, nodeselection.NodeFilters{})
|
|
require.Len(t, selectedNodes, reqCount)
|
|
for _, node := range selectedNodes {
|
|
selectedNodeCount[node.ID]++
|
|
}
|
|
}
|
|
|
|
subnetA1Count := float64(selectedNodeCount[subnetA1.ID])
|
|
subnetA2Count := float64(selectedNodeCount[subnetA2.ID])
|
|
subnetB1Count := float64(selectedNodeCount[subnetB1.ID])
|
|
total := subnetA1Count + subnetA2Count + subnetB1Count
|
|
assert.Equal(t, total, float64(reqCount*executionCount))
|
|
|
|
const (
|
|
selectionEpsilon = 0.1
|
|
uniqueSubnet = 0.5
|
|
)
|
|
|
|
// we expect that the 2 nodes from the same subnet should be
|
|
// selected roughly the same ~25% percent of the time
|
|
assert.InDelta(t, subnetA2Count/total, subnetA1Count/total, selectionEpsilon)
|
|
|
|
// expect that the single node is selected ~50% of the time
|
|
assert.InDelta(t, subnetB1Count/total, uniqueSubnet, selectionEpsilon)
|
|
}
|
|
|
|
func TestSelectFiltered(t *testing.T) {
|
|
|
|
ctx := testcontext.New(t)
|
|
defer ctx.Cleanup()
|
|
|
|
// create 3 nodes, 2 with same subnet
|
|
lastNetDuplicate := "1.0.1"
|
|
firstID := testrand.NodeID()
|
|
subnetA1 := &nodeselection.SelectedNode{
|
|
ID: firstID,
|
|
LastNet: lastNetDuplicate,
|
|
LastIPPort: lastNetDuplicate + ".4:8080",
|
|
}
|
|
|
|
secondID := testrand.NodeID()
|
|
subnetA2 := &nodeselection.SelectedNode{
|
|
ID: secondID,
|
|
LastNet: lastNetDuplicate,
|
|
LastIPPort: lastNetDuplicate + ".5:8080",
|
|
}
|
|
|
|
thirdID := testrand.NodeID()
|
|
lastNetSingle := "1.0.2"
|
|
subnetB1 := &nodeselection.SelectedNode{
|
|
ID: thirdID,
|
|
LastNet: lastNetSingle,
|
|
LastIPPort: lastNetSingle + ".5:8080",
|
|
}
|
|
|
|
nodes := []*nodeselection.SelectedNode{subnetA1, subnetA2, subnetB1}
|
|
selector := nodeselection.SelectByID(nodes)
|
|
|
|
assert.Len(t, selector.Select(3, nodeselection.NodeFilters{}), 3)
|
|
assert.Len(t, selector.Select(3, nodeselection.NodeFilters{}.WithAutoExcludeSubnets()), 2)
|
|
assert.Len(t, selector.Select(3, nodeselection.NodeFilters{}), 3)
|
|
|
|
assert.Len(t, selector.Select(3, nodeselection.NodeFilters{}.WithExcludedIDs([]storj.NodeID{firstID, secondID})), 1)
|
|
assert.Len(t, selector.Select(3, nodeselection.NodeFilters{}.WithAutoExcludeSubnets()), 2)
|
|
assert.Len(t, selector.Select(3, nodeselection.NodeFilters{}.WithExcludedIDs([]storj.NodeID{thirdID}).WithAutoExcludeSubnets()), 1)
|
|
}
|
|
|
|
func TestSelectFilteredMulti(t *testing.T) {
|
|
// four subnets with 3 nodes in each. Only one per subnet is located in Germany.
|
|
// Algorithm should pick the German one from each subnet, and 4 nodes should be possible to be picked.
|
|
|
|
ctx := testcontext.New(t)
|
|
defer ctx.Cleanup()
|
|
|
|
var nodes []*nodeselection.SelectedNode
|
|
|
|
for i := 0; i < 12; i++ {
|
|
nodes = append(nodes, &nodeselection.SelectedNode{
|
|
ID: testidentity.MustPregeneratedIdentity(i, storj.LatestIDVersion()).ID,
|
|
LastNet: fmt.Sprintf("68.0.%d", i/3),
|
|
LastIPPort: fmt.Sprintf("68.0.%d.%d:1000", i/3, i),
|
|
CountryCode: location.Germany + location.CountryCode(i%3),
|
|
})
|
|
|
|
}
|
|
|
|
selector := nodeselection.SelectBySubnetFromNodes(nodes)
|
|
for i := 0; i < 100; i++ {
|
|
assert.Len(t, selector.Select(4, nodeselection.NodeFilters{}.WithCountryFilter(location.NewSet(location.Germany))), 4)
|
|
}
|
|
|
|
}
|