storj/satellite/nodeselection/selector_test.go
Egon Elbre 08692aef90 satellite/nodeselection: node selection with proper bias
Currently node selection cache is biased towards the same subnet. This
implements static node selection for distinct such that it selects with
equal probability subnets rather than id-s.

This is mostly a copy paste + modifications from previous node selection
state.

Change-Id: Ia5c0aaf68e7feca78fbbd7352ad369fcb77c3a05
2020-05-18 18:09:15 +00:00

224 lines
6.9 KiB
Go

// Copyright (C) 2020 Storj Labs, Incache.
// See LICENSE for copying information.
package nodeselection_test
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"storj.io/common/pb"
"storj.io/common/storj"
"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.Node{
ID: testrand.NodeID(),
LastNet: lastNetDuplicate,
LastIPPort: lastNetDuplicate + ".4:8080",
Address: &pb.NodeAddress{Address: lastNetDuplicate + ".4:8080"},
}
subnetA2 := &nodeselection.Node{
ID: testrand.NodeID(),
LastNet: lastNetDuplicate,
LastIPPort: lastNetDuplicate + ".5:8080",
Address: &pb.NodeAddress{Address: lastNetDuplicate + ".5:8080"},
}
lastNetSingle := "1.0.2"
subnetB1 := &nodeselection.Node{
ID: testrand.NodeID(),
LastNet: lastNetSingle,
LastIPPort: lastNetSingle + ".5:8080",
Address: &pb.NodeAddress{Address: lastNetSingle + ".5:8080"},
}
nodes := []*nodeselection.Node{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, nil, nil)
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.Node{
ID: testrand.NodeID(),
LastNet: lastNetDuplicate,
LastIPPort: lastNetDuplicate + ".4:8080",
Address: &pb.NodeAddress{Address: lastNetDuplicate + ".4:8080"},
}
subnetA2 := &nodeselection.Node{
ID: testrand.NodeID(),
LastNet: lastNetDuplicate,
LastIPPort: lastNetDuplicate + ".5:8080",
Address: &pb.NodeAddress{Address: lastNetDuplicate + ".5:8080"},
}
lastNetSingle := "1.0.2"
subnetB1 := &nodeselection.Node{
ID: testrand.NodeID(),
LastNet: lastNetSingle,
LastIPPort: lastNetSingle + ".5:8080",
Address: &pb.NodeAddress{Address: lastNetSingle + ".5:8080"},
}
nodes := []*nodeselection.Node{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, nil, map[string]struct{}{})
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.Node{
ID: testrand.NodeID(),
LastNet: lastNetDuplicate,
LastIPPort: lastNetDuplicate + ".4:8080",
Address: &pb.NodeAddress{Address: lastNetDuplicate + ".4:8080"},
}
subnetA2 := &nodeselection.Node{
ID: testrand.NodeID(),
LastNet: lastNetDuplicate,
LastIPPort: lastNetDuplicate + ".5:8080",
Address: &pb.NodeAddress{Address: lastNetDuplicate + ".5:8080"},
}
lastNetSingle := "1.0.2"
subnetB1 := &nodeselection.Node{
ID: testrand.NodeID(),
LastNet: lastNetSingle,
LastIPPort: lastNetSingle + ".5:8080",
Address: &pb.NodeAddress{Address: lastNetSingle + ".5:8080"},
}
nodes := []*nodeselection.Node{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, nil, map[string]struct{}{})
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)
}