diff --git a/scheduler/context_test.go b/scheduler/context_test.go index 75bc7ed36868..8187e9cbf903 100644 --- a/scheduler/context_test.go +++ b/scheduler/context_test.go @@ -14,6 +14,7 @@ import ( func testContext(t testing.TB) (*state.StateStore, *EvalContext) { state := state.TestStateStore(t) plan := &structs.Plan{ + EvalID: uuid.Generate(), NodeUpdate: make(map[string][]*structs.Allocation), NodeAllocation: make(map[string][]*structs.Allocation), NodePreemptions: make(map[string][]*structs.Allocation), diff --git a/scheduler/feasible.go b/scheduler/feasible.go index f78ee6180add..555da9dd882b 100644 --- a/scheduler/feasible.go +++ b/scheduler/feasible.go @@ -122,7 +122,7 @@ func (iter *StaticIterator) SetNodes(nodes []*structs.Node) { // is applied in-place func NewRandomIterator(ctx Context, nodes []*structs.Node) *StaticIterator { // shuffle with the Fisher-Yates algorithm - shuffleNodes(ctx.Plan().EvalID, nodes) + shuffleNodes(ctx.Plan(), nodes) // Create a static iterator return NewStaticIterator(ctx, nodes) diff --git a/scheduler/stack.go b/scheduler/stack.go index d5221c7c65f1..29cd4ad7a3e8 100644 --- a/scheduler/stack.go +++ b/scheduler/stack.go @@ -70,7 +70,7 @@ type GenericStack struct { func (s *GenericStack) SetNodes(baseNodes []*structs.Node) { // Shuffle base nodes - shuffleNodes(s.ctx.Plan().EvalID, baseNodes) + shuffleNodes(s.ctx.Plan(), baseNodes) // Update the set of base nodes s.source.SetNodes(baseNodes) diff --git a/scheduler/util.go b/scheduler/util.go index 2ad49a2a8b13..91078a2c2eb1 100644 --- a/scheduler/util.go +++ b/scheduler/util.go @@ -381,9 +381,20 @@ func taintedNodes(state State, allocs []*structs.Allocation) (map[string]*struct // algorithm. We seed the random source with the eval ID (which is // random) to aid in postmortem debuggging of specific evaluations and // state snapshots. -func shuffleNodes(evalID string, nodes []*structs.Node) { +func shuffleNodes(plan *structs.Plan, nodes []*structs.Node) { + + // use the last 4 bytes because those are the random bits + // if we have sortable IDs + buf := []byte(plan.EvalID) + seed := int64(binary.BigEndian.Uint32(buf[len(buf)-4:])) + + // include the plan progress so that we don't retry with + // the exact same shuffle + seed = seed | + int64(len(plan.NodeAllocation)) | + int64(len(plan.NodeUpdate)) | + int64(len(plan.DeploymentUpdates)) - seed, _ := binary.Varint([]byte(evalID)) r := rand.New(rand.NewSource(seed)) n := len(nodes) diff --git a/scheduler/util_test.go b/scheduler/util_test.go index 9a31d6afe9aa..b2bfd8e4a742 100644 --- a/scheduler/util_test.go +++ b/scheduler/util_test.go @@ -507,7 +507,9 @@ func TestShuffleNodes(t *testing.T) { } orig := make([]*structs.Node, len(nodes)) copy(orig, nodes) - shuffleNodes(uuid.Generate(), nodes) + eval := mock.Eval() // will have random EvalID + plan := eval.MakePlan(mock.Job()) + shuffleNodes(plan, nodes) require.False(t, reflect.DeepEqual(nodes, orig)) }