Skip to content

Commit ce15823

Browse files
committed
scheduler: account for affinity value of zero in score normalization
If there are no affinities on a job, we don't want to count an affinity score of zero in the number of scores we divide the normalized score by. This is how we handle other scoring components like node reschedule penalties on nodes that weren't running the previous allocation. But we also exclude counting the affinity in the case where we have affinity but the value is zero. In pathological cases, this can result in a node with a low affinity being picked over a node with no affinity, because the denominator is 1 larger. Include zero-value affinities in the count of scores if the job has affinities but the value just happens to be zero. Fixes: #25621
1 parent 21fd0bb commit ce15823

File tree

2 files changed

+17
-14
lines changed

2 files changed

+17
-14
lines changed

scheduler/rank.go

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -972,10 +972,8 @@ func (iter *NodeAffinityIterator) Next() *RankedNode {
972972
}
973973
}
974974
normScore := totalAffinityScore / sumWeight
975-
if totalAffinityScore != 0.0 {
976-
option.Scores = append(option.Scores, normScore)
977-
iter.ctx.Metrics().ScoreNode(option.Node, "node-affinity", normScore)
978-
}
975+
option.Scores = append(option.Scores, normScore)
976+
iter.ctx.Metrics().ScoreNode(option.Node, "node-affinity", normScore)
979977
return option
980978
}
981979

scheduler/rank_test.go

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"github.com/hashicorp/nomad/helper/uuid"
1414
"github.com/hashicorp/nomad/nomad/mock"
1515
"github.com/hashicorp/nomad/nomad/structs"
16+
"github.com/shoenig/test"
1617
"github.com/shoenig/test/must"
1718
"github.com/stretchr/testify/require"
1819
)
@@ -2267,13 +2268,19 @@ func TestNodeAffinityIterator(t *testing.T) {
22672268
{Node: mock.Node()},
22682269
{Node: mock.Node()},
22692270
{Node: mock.Node()},
2271+
{Node: mock.Node()},
22702272
}
22712273

22722274
nodes[0].Node.Attributes["kernel.version"] = "4.9"
22732275
nodes[1].Node.Datacenter = "dc2"
22742276
nodes[2].Node.Datacenter = "dc2"
22752277
nodes[2].Node.NodeClass = "large"
22762278

2279+
// this node should have zero affinity
2280+
nodes[4].Node.Attributes["kernel.version"] = "2.6"
2281+
nodes[4].Node.Datacenter = "dc3"
2282+
nodes[4].Node.NodeClass = "weird"
2283+
22772284
affinities := []*structs.Affinity{
22782285
{
22792286
Operand: "=",
@@ -2314,23 +2321,21 @@ func TestNodeAffinityIterator(t *testing.T) {
23142321
scoreNorm := NewScoreNormalizationIterator(ctx, nodeAffinity)
23152322

23162323
out := collectRanked(scoreNorm)
2317-
expectedScores := make(map[string]float64)
2324+
23182325
// Total weight = 300
23192326
// Node 0 matches two affinities(dc and kernel version), total weight = 150
2320-
expectedScores[nodes[0].Node.ID] = 0.5
2327+
test.Eq(t, 0.5, out[0].FinalScore)
23212328

23222329
// Node 1 matches an anti affinity, weight = -100
2323-
expectedScores[nodes[1].Node.ID] = -(1.0 / 3.0)
2330+
test.Eq(t, -(1.0 / 3.0), out[1].FinalScore)
23242331

23252332
// Node 2 matches one affinity(node class) with weight 50
2326-
expectedScores[nodes[2].Node.ID] = -(1.0 / 6.0)
2333+
test.Eq(t, -(1.0 / 6.0), out[2].FinalScore)
23272334

23282335
// Node 3 matches one affinity (dc) with weight = 100
2329-
expectedScores[nodes[3].Node.ID] = 1.0 / 3.0
2330-
2331-
require := require.New(t)
2332-
for _, n := range out {
2333-
require.Equal(expectedScores[n.Node.ID], n.FinalScore)
2334-
}
2336+
test.Eq(t, 1.0/3.0, out[3].FinalScore)
23352337

2338+
// Node 4 matches no affinities but should still generate a score
2339+
test.Eq(t, 0, out[4].FinalScore)
2340+
test.NotNil(t, out[4].Scores)
23362341
}

0 commit comments

Comments
 (0)