2021-10-27 10:00:35 +00:00
|
|
|
package netmap
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
2021-12-24 11:31:34 +00:00
|
|
|
"math/rand"
|
|
|
|
"sort"
|
|
|
|
"strconv"
|
2021-10-27 10:00:35 +00:00
|
|
|
"testing"
|
|
|
|
|
2021-12-24 11:31:34 +00:00
|
|
|
"github.com/nspcc-dev/hrw"
|
2021-10-27 10:00:35 +00:00
|
|
|
"github.com/nspcc-dev/neofs-api-go/v2/netmap"
|
|
|
|
testv2 "github.com/nspcc-dev/neofs-api-go/v2/netmap/test"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
)
|
|
|
|
|
2021-12-24 11:31:34 +00:00
|
|
|
func BenchmarkHRWSort(b *testing.B) {
|
|
|
|
const netmapSize = 1000
|
|
|
|
|
|
|
|
nodes := make([]Nodes, netmapSize)
|
|
|
|
weights := make([]float64, netmapSize)
|
|
|
|
for i := range nodes {
|
|
|
|
nodes[i] = Nodes{{
|
|
|
|
ID: rand.Uint64(),
|
|
|
|
Index: i,
|
|
|
|
Capacity: 100,
|
|
|
|
Price: 1,
|
|
|
|
AttrMap: nil,
|
|
|
|
}}
|
|
|
|
weights[i] = float64(rand.Uint32()%10) / 10.0
|
|
|
|
}
|
|
|
|
|
|
|
|
pivot := rand.Uint64()
|
|
|
|
b.Run("sort by index, no weight", func(b *testing.B) {
|
|
|
|
realNodes := make([]Nodes, netmapSize)
|
|
|
|
b.ResetTimer()
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
|
|
b.StopTimer()
|
|
|
|
copy(realNodes, nodes)
|
|
|
|
b.StartTimer()
|
|
|
|
|
|
|
|
hrw.SortSliceByIndex(realNodes, pivot)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
b.Run("sort by value, no weight", func(b *testing.B) {
|
|
|
|
realNodes := make([]Nodes, netmapSize)
|
|
|
|
b.ResetTimer()
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
|
|
b.StopTimer()
|
|
|
|
copy(realNodes, nodes)
|
|
|
|
b.StartTimer()
|
|
|
|
|
|
|
|
hrw.SortSliceByValue(realNodes, pivot)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
b.Run("only sort by index", func(b *testing.B) {
|
|
|
|
realNodes := make([]Nodes, netmapSize)
|
|
|
|
b.ResetTimer()
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
|
|
b.StopTimer()
|
|
|
|
copy(realNodes, nodes)
|
|
|
|
b.StartTimer()
|
|
|
|
|
|
|
|
hrw.SortSliceByWeightIndex(realNodes, weights, pivot)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
b.Run("sort by value", func(b *testing.B) {
|
|
|
|
realNodes := make([]Nodes, netmapSize)
|
|
|
|
b.ResetTimer()
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
|
|
b.StopTimer()
|
|
|
|
copy(realNodes, nodes)
|
|
|
|
b.StartTimer()
|
|
|
|
|
|
|
|
hrw.SortSliceByWeightValue(realNodes, weights, pivot)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
b.Run("sort by ID, then by index (deterministic)", func(b *testing.B) {
|
|
|
|
realNodes := make([]Nodes, netmapSize)
|
|
|
|
b.ResetTimer()
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
|
|
b.StopTimer()
|
|
|
|
copy(realNodes, nodes)
|
|
|
|
b.StartTimer()
|
|
|
|
|
|
|
|
sort.Slice(nodes, func(i, j int) bool {
|
|
|
|
return nodes[i][0].ID < nodes[j][0].ID
|
|
|
|
})
|
|
|
|
hrw.SortSliceByWeightIndex(realNodes, weights, pivot)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func BenchmarkPolicyHRWType(b *testing.B) {
|
|
|
|
const netmapSize = 100
|
|
|
|
|
|
|
|
p := newPlacementPolicy(1,
|
2022-03-11 09:13:08 +00:00
|
|
|
[]Replica{
|
2021-12-24 11:31:34 +00:00
|
|
|
newReplica(1, "loc1"),
|
|
|
|
newReplica(1, "loc2")},
|
2022-03-11 09:13:08 +00:00
|
|
|
[]Selector{
|
2021-12-24 11:31:34 +00:00
|
|
|
newSelector("loc1", "Location", ClauseSame, 1, "loc1"),
|
|
|
|
newSelector("loc2", "Location", ClauseSame, 1, "loc2")},
|
2022-03-11 09:13:08 +00:00
|
|
|
[]Filter{
|
2021-12-24 11:31:34 +00:00
|
|
|
newFilter("loc1", "Location", "Shanghai", OpEQ),
|
|
|
|
newFilter("loc2", "Location", "Shanghai", OpNE),
|
|
|
|
})
|
|
|
|
|
|
|
|
nodes := make([]NodeInfo, netmapSize)
|
|
|
|
for i := range nodes {
|
|
|
|
var loc string
|
|
|
|
switch i % 20 {
|
|
|
|
case 0:
|
|
|
|
loc = "Shanghai"
|
|
|
|
default:
|
|
|
|
loc = strconv.Itoa(i % 20)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Having the same price and capacity ensures equal weights for all nodes.
|
|
|
|
// This way placement is more dependent on the initial order.
|
|
|
|
nodes[i] = nodeInfoFromAttributes("Location", loc, "Price", "1", "Capacity", "10")
|
|
|
|
pub := make([]byte, 33)
|
|
|
|
pub[0] = byte(i)
|
|
|
|
nodes[i].SetPublicKey(pub)
|
|
|
|
}
|
|
|
|
|
|
|
|
nm, err := NewNetmap(NodesFromInfo(nodes))
|
|
|
|
require.NoError(b, err)
|
|
|
|
|
|
|
|
b.ResetTimer()
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
|
|
_, err := nm.GetContainerNodes(p, []byte{1})
|
|
|
|
if err != nil {
|
|
|
|
b.Fatal()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestPlacementPolicy_DeterministicOrder(t *testing.T) {
|
|
|
|
const netmapSize = 100
|
|
|
|
|
|
|
|
p := newPlacementPolicy(1,
|
2022-03-11 09:13:08 +00:00
|
|
|
[]Replica{
|
2021-12-24 11:31:34 +00:00
|
|
|
newReplica(1, "loc1"),
|
|
|
|
newReplica(1, "loc2")},
|
2022-03-11 09:13:08 +00:00
|
|
|
[]Selector{
|
2021-12-24 11:31:34 +00:00
|
|
|
newSelector("loc1", "Location", ClauseSame, 1, "loc1"),
|
|
|
|
newSelector("loc2", "Location", ClauseSame, 1, "loc2")},
|
2022-03-11 09:13:08 +00:00
|
|
|
[]Filter{
|
2021-12-24 11:31:34 +00:00
|
|
|
newFilter("loc1", "Location", "Shanghai", OpEQ),
|
|
|
|
newFilter("loc2", "Location", "Shanghai", OpNE),
|
|
|
|
})
|
|
|
|
|
|
|
|
nodes := make([]NodeInfo, netmapSize)
|
|
|
|
for i := range nodes {
|
|
|
|
var loc string
|
|
|
|
switch i % 20 {
|
|
|
|
case 0:
|
|
|
|
loc = "Shanghai"
|
|
|
|
default:
|
|
|
|
loc = strconv.Itoa(i % 20)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Having the same price and capacity ensures equal weights for all nodes.
|
|
|
|
// This way placement is more dependent on the initial order.
|
|
|
|
nodes[i] = nodeInfoFromAttributes("Location", loc, "Price", "1", "Capacity", "10")
|
|
|
|
pub := make([]byte, 33)
|
|
|
|
pub[0] = byte(i)
|
|
|
|
nodes[i].SetPublicKey(pub)
|
|
|
|
}
|
|
|
|
|
|
|
|
nm, err := NewNetmap(NodesFromInfo(nodes))
|
|
|
|
require.NoError(t, err)
|
|
|
|
getIndices := func(t *testing.T) (int, int) {
|
|
|
|
v, err := nm.GetContainerNodes(p, []byte{1})
|
|
|
|
require.NoError(t, err)
|
|
|
|
ns := v.Flatten()
|
|
|
|
require.Equal(t, 2, len(ns))
|
|
|
|
return ns[0].Index, ns[1].Index
|
|
|
|
}
|
|
|
|
|
|
|
|
a, b := getIndices(t)
|
|
|
|
for i := 0; i < 10; i++ {
|
|
|
|
x, y := getIndices(t)
|
|
|
|
require.Equal(t, a, x)
|
|
|
|
require.Equal(t, b, y)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-27 10:00:35 +00:00
|
|
|
func TestPlacementPolicy_ProcessSelectors(t *testing.T) {
|
|
|
|
p := newPlacementPolicy(2, nil,
|
2022-03-11 09:13:08 +00:00
|
|
|
[]Selector{
|
2021-10-27 10:00:35 +00:00
|
|
|
newSelector("SameRU", "City", ClauseSame, 2, "FromRU"),
|
|
|
|
newSelector("DistinctRU", "City", ClauseDistinct, 2, "FromRU"),
|
|
|
|
newSelector("Good", "Country", ClauseDistinct, 2, "Good"),
|
|
|
|
newSelector("Main", "Country", ClauseDistinct, 3, "*"),
|
|
|
|
},
|
2022-03-11 09:13:08 +00:00
|
|
|
[]Filter{
|
2021-10-27 10:00:35 +00:00
|
|
|
newFilter("FromRU", "Country", "Russia", OpEQ),
|
|
|
|
newFilter("Good", "Rating", "4", OpGE),
|
|
|
|
})
|
|
|
|
nodes := []NodeInfo{
|
|
|
|
nodeInfoFromAttributes("Country", "Russia", "Rating", "1", "City", "SPB"),
|
|
|
|
nodeInfoFromAttributes("Country", "Germany", "Rating", "5", "City", "Berlin"),
|
|
|
|
nodeInfoFromAttributes("Country", "Russia", "Rating", "6", "City", "Moscow"),
|
|
|
|
nodeInfoFromAttributes("Country", "France", "Rating", "4", "City", "Paris"),
|
|
|
|
nodeInfoFromAttributes("Country", "France", "Rating", "1", "City", "Lyon"),
|
|
|
|
nodeInfoFromAttributes("Country", "Russia", "Rating", "5", "City", "SPB"),
|
|
|
|
nodeInfoFromAttributes("Country", "Russia", "Rating", "7", "City", "Moscow"),
|
|
|
|
nodeInfoFromAttributes("Country", "Germany", "Rating", "3", "City", "Darmstadt"),
|
|
|
|
nodeInfoFromAttributes("Country", "Germany", "Rating", "7", "City", "Frankfurt"),
|
|
|
|
nodeInfoFromAttributes("Country", "Russia", "Rating", "9", "City", "SPB"),
|
|
|
|
nodeInfoFromAttributes("Country", "Russia", "Rating", "9", "City", "SPB"),
|
|
|
|
}
|
|
|
|
|
|
|
|
nm, err := NewNetmap(NodesFromInfo(nodes))
|
|
|
|
require.NoError(t, err)
|
|
|
|
c := NewContext(nm)
|
|
|
|
c.setCBF(p.ContainerBackupFactor())
|
|
|
|
require.NoError(t, c.processFilters(p))
|
|
|
|
require.NoError(t, c.processSelectors(p))
|
|
|
|
|
|
|
|
for _, s := range p.Selectors() {
|
|
|
|
sel := c.Selections[s.Name()]
|
|
|
|
s := c.Selectors[s.Name()]
|
|
|
|
bucketCount, nodesInBucket := GetNodesCount(p, s)
|
|
|
|
nodesInBucket *= int(c.cbf)
|
|
|
|
targ := fmt.Sprintf("selector '%s'", s.Name())
|
|
|
|
require.Equal(t, bucketCount, len(sel), targ)
|
|
|
|
for _, res := range sel {
|
|
|
|
require.Equal(t, nodesInBucket, len(res), targ)
|
|
|
|
for j := range res {
|
2022-03-11 09:13:08 +00:00
|
|
|
require.True(t, c.applyFilter(s.Filter(), &res[j]), targ)
|
2021-10-27 10:00:35 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func testSelector() *Selector {
|
|
|
|
s := new(Selector)
|
|
|
|
s.SetName("name")
|
|
|
|
s.SetCount(3)
|
|
|
|
s.SetFilter("filter")
|
|
|
|
s.SetAttribute("attribute")
|
|
|
|
s.SetClause(ClauseDistinct)
|
|
|
|
|
|
|
|
return s
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestSelector_Name(t *testing.T) {
|
|
|
|
s := NewSelector()
|
|
|
|
name := "some name"
|
|
|
|
|
|
|
|
s.SetName(name)
|
|
|
|
|
|
|
|
require.Equal(t, name, s.Name())
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestSelector_Count(t *testing.T) {
|
|
|
|
s := NewSelector()
|
|
|
|
c := uint32(3)
|
|
|
|
|
|
|
|
s.SetCount(c)
|
|
|
|
|
|
|
|
require.Equal(t, c, s.Count())
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestSelector_Clause(t *testing.T) {
|
|
|
|
s := NewSelector()
|
|
|
|
c := ClauseSame
|
|
|
|
|
|
|
|
s.SetClause(c)
|
|
|
|
|
|
|
|
require.Equal(t, c, s.Clause())
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestSelector_Attribute(t *testing.T) {
|
|
|
|
s := NewSelector()
|
|
|
|
a := "some attribute"
|
|
|
|
|
|
|
|
s.SetAttribute(a)
|
|
|
|
|
|
|
|
require.Equal(t, a, s.Attribute())
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestSelector_Filter(t *testing.T) {
|
|
|
|
s := NewSelector()
|
|
|
|
f := "some filter"
|
|
|
|
|
|
|
|
s.SetFilter(f)
|
|
|
|
|
|
|
|
require.Equal(t, f, s.Filter())
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestSelectorEncoding(t *testing.T) {
|
|
|
|
s := newSelector("name", "atte", ClauseSame, 1, "filter")
|
|
|
|
|
|
|
|
t.Run("binary", func(t *testing.T) {
|
|
|
|
data, err := s.Marshal()
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
2022-03-11 09:13:08 +00:00
|
|
|
s2 := *NewSelector()
|
2021-10-27 10:00:35 +00:00
|
|
|
require.NoError(t, s2.Unmarshal(data))
|
|
|
|
|
|
|
|
require.Equal(t, s, s2)
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("json", func(t *testing.T) {
|
|
|
|
data, err := s.MarshalJSON()
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
2022-03-11 09:13:08 +00:00
|
|
|
s2 := *NewSelector()
|
2021-10-27 10:00:35 +00:00
|
|
|
require.NoError(t, s2.UnmarshalJSON(data))
|
|
|
|
|
|
|
|
require.Equal(t, s, s2)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestSelector_ToV2(t *testing.T) {
|
|
|
|
t.Run("nil", func(t *testing.T) {
|
|
|
|
var x *Selector
|
|
|
|
|
|
|
|
require.Nil(t, x.ToV2())
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestNewSelectorFromV2(t *testing.T) {
|
|
|
|
t.Run("from nil", func(t *testing.T) {
|
|
|
|
var x *netmap.Selector
|
|
|
|
|
|
|
|
require.Nil(t, NewSelectorFromV2(x))
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("from non-nil", func(t *testing.T) {
|
|
|
|
sV2 := testv2.GenerateSelector(false)
|
|
|
|
|
|
|
|
s := NewSelectorFromV2(sV2)
|
|
|
|
|
|
|
|
require.Equal(t, sV2, s.ToV2())
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestNewSelector(t *testing.T) {
|
|
|
|
t.Run("default values", func(t *testing.T) {
|
|
|
|
s := NewSelector()
|
|
|
|
|
|
|
|
// check initial values
|
|
|
|
require.Zero(t, s.Count())
|
|
|
|
require.Equal(t, ClauseUnspecified, s.Clause())
|
|
|
|
require.Empty(t, s.Attribute())
|
|
|
|
require.Empty(t, s.Name())
|
|
|
|
require.Empty(t, s.Filter())
|
|
|
|
|
|
|
|
// convert to v2 message
|
|
|
|
sV2 := s.ToV2()
|
|
|
|
|
|
|
|
require.Zero(t, sV2.GetCount())
|
|
|
|
require.Equal(t, netmap.UnspecifiedClause, sV2.GetClause())
|
|
|
|
require.Empty(t, sV2.GetAttribute())
|
|
|
|
require.Empty(t, sV2.GetName())
|
|
|
|
require.Empty(t, sV2.GetFilter())
|
|
|
|
})
|
|
|
|
}
|