Skip to content

Commit abdb7c3

Browse files
authored
Merge pull request #2939 from hashicorp/b-distinct
Fix incorrect destructive update with distinct_property constraint
2 parents 040d791 + dd4befb commit abdb7c3

File tree

2 files changed

+91
-1
lines changed

2 files changed

+91
-1
lines changed

scheduler/generic_sched_test.go

+79
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"github.com/hashicorp/nomad/helper"
1212
"github.com/hashicorp/nomad/nomad/mock"
1313
"github.com/hashicorp/nomad/nomad/structs"
14+
"github.com/stretchr/testify/assert"
1415
)
1516

1617
func TestServiceSched_JobRegister(t *testing.T) {
@@ -493,6 +494,84 @@ func TestServiceSched_JobRegister_DistinctProperty_TaskGroup(t *testing.T) {
493494
h.AssertEvalStatus(t, structs.EvalStatusComplete)
494495
}
495496

497+
func TestServiceSched_JobRegister_DistinctProperty_TaskGroup_Incr(t *testing.T) {
498+
h := NewHarness(t)
499+
assert := assert.New(t)
500+
501+
// Create a job that uses distinct property over the node-id
502+
job := mock.Job()
503+
job.TaskGroups[0].Count = 3
504+
job.TaskGroups[0].Constraints = append(job.TaskGroups[0].Constraints,
505+
&structs.Constraint{
506+
Operand: structs.ConstraintDistinctProperty,
507+
LTarget: "${node.unique.id}",
508+
})
509+
assert.Nil(h.State.UpsertJob(h.NextIndex(), job), "UpsertJob")
510+
511+
// Create some nodes
512+
var nodes []*structs.Node
513+
for i := 0; i < 6; i++ {
514+
node := mock.Node()
515+
nodes = append(nodes, node)
516+
assert.Nil(h.State.UpsertNode(h.NextIndex(), node), "UpsertNode")
517+
}
518+
519+
// Create some allocations
520+
var allocs []*structs.Allocation
521+
for i := 0; i < 3; i++ {
522+
alloc := mock.Alloc()
523+
alloc.Job = job
524+
alloc.JobID = job.ID
525+
alloc.NodeID = nodes[i].ID
526+
alloc.Name = fmt.Sprintf("my-job.web[%d]", i)
527+
allocs = append(allocs, alloc)
528+
}
529+
assert.Nil(h.State.UpsertAllocs(h.NextIndex(), allocs), "UpsertAllocs")
530+
531+
// Update the count
532+
job2 := job.Copy()
533+
job2.TaskGroups[0].Count = 6
534+
assert.Nil(h.State.UpsertJob(h.NextIndex(), job2), "UpsertJob")
535+
536+
// Create a mock evaluation to register the job
537+
eval := &structs.Evaluation{
538+
ID: structs.GenerateUUID(),
539+
Priority: job.Priority,
540+
TriggeredBy: structs.EvalTriggerJobRegister,
541+
JobID: job.ID,
542+
}
543+
544+
// Process the evaluation
545+
assert.Nil(h.Process(NewServiceScheduler, eval), "Process")
546+
547+
// Ensure a single plan
548+
assert.Len(h.Plans, 1, "Number of plans")
549+
plan := h.Plans[0]
550+
551+
// Ensure the plan doesn't have annotations.
552+
assert.Nil(plan.Annotations, "Plan.Annotations")
553+
554+
// Ensure the eval hasn't spawned blocked eval
555+
assert.Len(h.CreateEvals, 0, "Created Evals")
556+
557+
// Ensure the plan allocated
558+
var planned []*structs.Allocation
559+
for _, allocList := range plan.NodeAllocation {
560+
planned = append(planned, allocList...)
561+
}
562+
assert.Len(planned, 6, "Planned Allocations")
563+
564+
// Lookup the allocations by JobID
565+
ws := memdb.NewWatchSet()
566+
out, err := h.State.AllocsByJob(ws, job.ID, false)
567+
assert.Nil(err, "AllocsByJob")
568+
569+
// Ensure all allocations placed
570+
assert.Len(out, 6, "Placed Allocations")
571+
572+
h.AssertEvalStatus(t, structs.EvalStatusComplete)
573+
}
574+
496575
func TestServiceSched_JobRegister_Annotate(t *testing.T) {
497576
h := NewHarness(t)
498577

scheduler/propertyset.go

+12-1
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,12 @@ func (p *propertySet) SetJobConstraint(constraint *structs.Constraint) {
5656
// Store the constraint
5757
p.constraint = constraint
5858
p.populateExisting(constraint)
59+
60+
// Populate the proposed when setting the constraint. We do this because
61+
// when detecting if we can inplace update an allocation we stage an
62+
// eviction and then select. This means the plan has an eviction before a
63+
// single select has finished.
64+
p.PopulateProposed()
5965
}
6066

6167
// SetTGConstraint is used to parameterize the property set for a
@@ -67,8 +73,13 @@ func (p *propertySet) SetTGConstraint(constraint *structs.Constraint, taskGroup
6773

6874
// Store the constraint
6975
p.constraint = constraint
70-
7176
p.populateExisting(constraint)
77+
78+
// Populate the proposed when setting the constraint. We do this because
79+
// when detecting if we can inplace update an allocation we stage an
80+
// eviction and then select. This means the plan has an eviction before a
81+
// single select has finished.
82+
p.PopulateProposed()
7283
}
7384

7485
// populateExisting is a helper shared when setting the constraint to populate

0 commit comments

Comments
 (0)