361 lines
5.9 KiB
Go
361 lines
5.9 KiB
Go
package scheduler
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/hashicorp/nomad/nomad/mock"
|
|
"github.com/hashicorp/nomad/nomad/structs"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestLimitIterator(t *testing.T) {
|
|
_, ctx := testContext(t)
|
|
nodes := []*RankedNode{
|
|
{
|
|
Node: mock.Node(),
|
|
FinalScore: 1,
|
|
},
|
|
{
|
|
Node: mock.Node(),
|
|
FinalScore: 2,
|
|
},
|
|
{
|
|
Node: mock.Node(),
|
|
FinalScore: 3,
|
|
},
|
|
}
|
|
static := NewStaticRankIterator(ctx, nodes)
|
|
|
|
limit := NewLimitIterator(ctx, static, 1, 0, 2)
|
|
limit.SetLimit(2)
|
|
|
|
out := collectRanked(limit)
|
|
if len(out) != 2 {
|
|
t.Fatalf("bad: %v", out)
|
|
}
|
|
if out[0] != nodes[0] && out[1] != nodes[1] {
|
|
t.Fatalf("bad: %v", out)
|
|
}
|
|
|
|
out = collectRanked(limit)
|
|
if len(out) != 0 {
|
|
t.Fatalf("bad: %v", out)
|
|
}
|
|
limit.Reset()
|
|
|
|
out = collectRanked(limit)
|
|
if len(out) != 2 {
|
|
t.Fatalf("bad: %v", out)
|
|
}
|
|
if out[0] != nodes[2] && out[1] != nodes[0] {
|
|
t.Fatalf("bad: %v", out)
|
|
}
|
|
}
|
|
|
|
func TestLimitIterator_ScoreThreshold(t *testing.T) {
|
|
_, ctx := testContext(t)
|
|
type testCase struct {
|
|
desc string
|
|
nodes []*RankedNode
|
|
expectedOut []*RankedNode
|
|
threshold float64
|
|
limit int
|
|
maxSkip int
|
|
}
|
|
|
|
var nodes []*structs.Node
|
|
for i := 0; i < 5; i++ {
|
|
nodes = append(nodes, mock.Node())
|
|
}
|
|
|
|
testCases := []testCase{
|
|
{
|
|
desc: "Skips one low scoring node",
|
|
nodes: []*RankedNode{
|
|
{
|
|
Node: nodes[0],
|
|
FinalScore: -1,
|
|
},
|
|
{
|
|
Node: nodes[1],
|
|
FinalScore: 2,
|
|
},
|
|
{
|
|
Node: nodes[2],
|
|
FinalScore: 3,
|
|
},
|
|
},
|
|
expectedOut: []*RankedNode{
|
|
{
|
|
Node: nodes[1],
|
|
FinalScore: 2,
|
|
},
|
|
{
|
|
Node: nodes[2],
|
|
FinalScore: 3,
|
|
},
|
|
},
|
|
threshold: -1,
|
|
limit: 2,
|
|
maxSkip: 2,
|
|
},
|
|
{
|
|
desc: "Skips maxSkip scoring nodes",
|
|
nodes: []*RankedNode{
|
|
{
|
|
Node: nodes[0],
|
|
FinalScore: -1,
|
|
},
|
|
{
|
|
Node: nodes[1],
|
|
FinalScore: -2,
|
|
},
|
|
{
|
|
Node: nodes[2],
|
|
FinalScore: 3,
|
|
},
|
|
{
|
|
Node: nodes[3],
|
|
FinalScore: 4,
|
|
},
|
|
},
|
|
expectedOut: []*RankedNode{
|
|
{
|
|
Node: nodes[2],
|
|
FinalScore: 3,
|
|
},
|
|
{
|
|
Node: nodes[3],
|
|
FinalScore: 4,
|
|
},
|
|
},
|
|
threshold: -1,
|
|
limit: 2,
|
|
maxSkip: 2,
|
|
},
|
|
{
|
|
desc: "maxSkip limit reached",
|
|
nodes: []*RankedNode{
|
|
{
|
|
Node: nodes[0],
|
|
FinalScore: -1,
|
|
},
|
|
{
|
|
Node: nodes[1],
|
|
FinalScore: -6,
|
|
},
|
|
{
|
|
Node: nodes[2],
|
|
FinalScore: -3,
|
|
},
|
|
{
|
|
Node: nodes[3],
|
|
FinalScore: -4,
|
|
},
|
|
},
|
|
expectedOut: []*RankedNode{
|
|
{
|
|
Node: nodes[2],
|
|
FinalScore: -3,
|
|
},
|
|
{
|
|
Node: nodes[3],
|
|
FinalScore: -4,
|
|
},
|
|
},
|
|
threshold: -1,
|
|
limit: 2,
|
|
maxSkip: 2,
|
|
},
|
|
{
|
|
desc: "draw both from skipped nodes",
|
|
nodes: []*RankedNode{
|
|
{
|
|
Node: nodes[0],
|
|
FinalScore: -1,
|
|
},
|
|
{
|
|
Node: nodes[1],
|
|
FinalScore: -6,
|
|
},
|
|
},
|
|
expectedOut: []*RankedNode{
|
|
{
|
|
Node: nodes[0],
|
|
FinalScore: -1,
|
|
},
|
|
{
|
|
Node: nodes[1],
|
|
FinalScore: -6,
|
|
},
|
|
},
|
|
threshold: -1,
|
|
limit: 2,
|
|
maxSkip: 2,
|
|
}, {
|
|
desc: "one node above threshold, one skipped node",
|
|
nodes: []*RankedNode{
|
|
{
|
|
Node: nodes[0],
|
|
FinalScore: -1,
|
|
},
|
|
{
|
|
Node: nodes[1],
|
|
FinalScore: 5,
|
|
},
|
|
},
|
|
expectedOut: []*RankedNode{
|
|
{
|
|
Node: nodes[1],
|
|
FinalScore: 5,
|
|
},
|
|
{
|
|
Node: nodes[0],
|
|
FinalScore: -1,
|
|
},
|
|
},
|
|
threshold: -1,
|
|
limit: 2,
|
|
maxSkip: 2,
|
|
},
|
|
{
|
|
desc: "low scoring nodes interspersed",
|
|
nodes: []*RankedNode{
|
|
{
|
|
Node: nodes[0],
|
|
FinalScore: -1,
|
|
},
|
|
{
|
|
Node: nodes[1],
|
|
FinalScore: 5,
|
|
},
|
|
{
|
|
Node: nodes[2],
|
|
FinalScore: -2,
|
|
},
|
|
{
|
|
Node: nodes[3],
|
|
FinalScore: 2,
|
|
},
|
|
},
|
|
expectedOut: []*RankedNode{
|
|
{
|
|
Node: nodes[1],
|
|
FinalScore: 5,
|
|
},
|
|
{
|
|
Node: nodes[3],
|
|
FinalScore: 2,
|
|
},
|
|
},
|
|
threshold: -1,
|
|
limit: 2,
|
|
maxSkip: 2,
|
|
},
|
|
{
|
|
desc: "only one node, score below threshold",
|
|
nodes: []*RankedNode{
|
|
{
|
|
Node: nodes[0],
|
|
FinalScore: -1,
|
|
},
|
|
},
|
|
expectedOut: []*RankedNode{
|
|
{
|
|
Node: nodes[0],
|
|
FinalScore: -1,
|
|
},
|
|
},
|
|
threshold: -1,
|
|
limit: 2,
|
|
maxSkip: 2,
|
|
},
|
|
{
|
|
desc: "maxSkip is more than available nodes",
|
|
nodes: []*RankedNode{
|
|
{
|
|
Node: nodes[0],
|
|
FinalScore: -2,
|
|
},
|
|
{
|
|
Node: nodes[1],
|
|
FinalScore: 1,
|
|
},
|
|
},
|
|
expectedOut: []*RankedNode{
|
|
{
|
|
Node: nodes[1],
|
|
FinalScore: 1,
|
|
},
|
|
{
|
|
Node: nodes[0],
|
|
FinalScore: -2,
|
|
},
|
|
},
|
|
threshold: -1,
|
|
limit: 2,
|
|
maxSkip: 10,
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.desc, func(t *testing.T) {
|
|
static := NewStaticRankIterator(ctx, tc.nodes)
|
|
|
|
limit := NewLimitIterator(ctx, static, 1, 0, 2)
|
|
limit.SetLimit(2)
|
|
out := collectRanked(limit)
|
|
require := require.New(t)
|
|
require.Equal(tc.expectedOut, out)
|
|
|
|
limit.Reset()
|
|
require.Equal(0, limit.skippedNodeIndex)
|
|
require.Equal(0, len(limit.skippedNodes))
|
|
})
|
|
}
|
|
|
|
}
|
|
|
|
func TestMaxScoreIterator(t *testing.T) {
|
|
_, ctx := testContext(t)
|
|
nodes := []*RankedNode{
|
|
{
|
|
Node: mock.Node(),
|
|
FinalScore: 1,
|
|
},
|
|
{
|
|
Node: mock.Node(),
|
|
FinalScore: 2,
|
|
},
|
|
{
|
|
Node: mock.Node(),
|
|
FinalScore: 3,
|
|
},
|
|
}
|
|
static := NewStaticRankIterator(ctx, nodes)
|
|
|
|
max := NewMaxScoreIterator(ctx, static)
|
|
|
|
out := collectRanked(max)
|
|
if len(out) != 1 {
|
|
t.Fatalf("bad: %v", out)
|
|
}
|
|
if out[0] != nodes[2] {
|
|
t.Fatalf("bad: %v", out)
|
|
}
|
|
|
|
out = collectRanked(max)
|
|
if len(out) != 0 {
|
|
t.Fatalf("bad: %v", out)
|
|
}
|
|
max.Reset()
|
|
|
|
out = collectRanked(max)
|
|
if len(out) != 1 {
|
|
t.Fatalf("bad: %v", out)
|
|
}
|
|
if out[0] != nodes[2] {
|
|
t.Fatalf("bad: %v", out)
|
|
}
|
|
}
|