Skip to content

Commit 8fa68ca

Browse files
authored
Merge pull request #6703 from hashicorp/b-affinity-constraint-inplace-update
Check for changes to affinity, constraints and spread during update
2 parents 387b016 + 4196b27 commit 8fa68ca

File tree

4 files changed

+307
-2
lines changed

4 files changed

+307
-2
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ BUG FIXES:
2424
* scheduler: Changes to devices in resource stanza should cause rescheduling [[GH-6644](https://github.com/hashicorp/nomad/issues/6644)]
2525
* vault: Allow overriding implicit Vault version constraint [[GH-6687](https://github.com/hashicorp/nomad/issues/6687)]
2626
* vault: Supported Vault auth role's new field, `token_period` [[GH-6574](https://github.com/hashicorp/nomad/issues/6574)]
27+
* scheduler: Fixed a bug that allowed inplace updates after a constraint, affinity, or spread was changed [[GH-6703](https://github.com/hashicorp/nomad/issues/6703)]
2728

2829
## 0.10.1 (November 4, 2019)
2930

scheduler/util.go

+104
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,8 @@ func shuffleNodes(nodes []*structs.Node) {
337337
// tasksUpdated does a diff between task groups to see if the
338338
// tasks, their drivers, environment variables or config have updated. The
339339
// inputs are the task group name to diff and two jobs to diff.
340+
// taskUpdated and functions called within assume that the given
341+
// taskGroup has already been checked to not be nil
340342
func tasksUpdated(jobA, jobB *structs.Job, taskGroup string) bool {
341343
a := jobA.LookupTaskGroup(taskGroup)
342344
b := jobB.LookupTaskGroup(taskGroup)
@@ -356,6 +358,21 @@ func tasksUpdated(jobA, jobB *structs.Job, taskGroup string) bool {
356358
return true
357359
}
358360

361+
// Check Affinities
362+
if affinitiesUpdated(jobA, jobB, taskGroup) {
363+
return true
364+
}
365+
366+
// Check Constraints
367+
if constraintsUpdated(jobA, jobB, taskGroup) {
368+
return true
369+
}
370+
371+
// Check Spreads
372+
if spreadsUpdated(jobA, jobB, taskGroup) {
373+
return true
374+
}
375+
359376
// Check each task
360377
for _, at := range a.Tasks {
361378
bt := b.LookupTask(at.Name)
@@ -442,6 +459,93 @@ func networkPortMap(n *structs.NetworkResource) map[string]int {
442459
return m
443460
}
444461

462+
func affinitiesUpdated(jobA, jobB *structs.Job, taskGroup string) bool {
463+
var aAffinities []*structs.Affinity
464+
var bAffinities []*structs.Affinity
465+
466+
tgA := jobA.LookupTaskGroup(taskGroup)
467+
tgB := jobB.LookupTaskGroup(taskGroup)
468+
469+
// Append jobA job and task group level affinities
470+
aAffinities = append(aAffinities, jobA.Affinities...)
471+
aAffinities = append(aAffinities, tgA.Affinities...)
472+
473+
// Append jobB job and task group level affinities
474+
bAffinities = append(bAffinities, jobB.Affinities...)
475+
bAffinities = append(bAffinities, tgB.Affinities...)
476+
477+
// append task affinities
478+
for _, task := range tgA.Tasks {
479+
aAffinities = append(aAffinities, task.Affinities...)
480+
}
481+
482+
for _, task := range tgB.Tasks {
483+
bAffinities = append(bAffinities, task.Affinities...)
484+
}
485+
486+
// Check for equality
487+
if len(aAffinities) != len(bAffinities) {
488+
return true
489+
}
490+
491+
return !reflect.DeepEqual(aAffinities, bAffinities)
492+
}
493+
494+
func constraintsUpdated(jobA, jobB *structs.Job, taskGroup string) bool {
495+
var aConstraints []*structs.Constraint
496+
var bConstraints []*structs.Constraint
497+
498+
tgA := jobA.LookupTaskGroup(taskGroup)
499+
tgB := jobB.LookupTaskGroup(taskGroup)
500+
501+
// Append jobA job and task group level constraints
502+
aConstraints = append(aConstraints, jobA.Constraints...)
503+
aConstraints = append(aConstraints, tgA.Constraints...)
504+
505+
// Append jobB job and task group level constraints
506+
bConstraints = append(bConstraints, jobB.Constraints...)
507+
bConstraints = append(bConstraints, tgB.Constraints...)
508+
509+
// Append task constraints
510+
for _, task := range tgA.Tasks {
511+
aConstraints = append(aConstraints, task.Constraints...)
512+
}
513+
514+
for _, task := range tgB.Tasks {
515+
bConstraints = append(bConstraints, task.Constraints...)
516+
}
517+
518+
// Check for equality
519+
if len(aConstraints) != len(bConstraints) {
520+
return true
521+
}
522+
523+
return !reflect.DeepEqual(aConstraints, bConstraints)
524+
}
525+
526+
func spreadsUpdated(jobA, jobB *structs.Job, taskGroup string) bool {
527+
var aSpreads []*structs.Spread
528+
var bSpreads []*structs.Spread
529+
530+
tgA := jobA.LookupTaskGroup(taskGroup)
531+
tgB := jobB.LookupTaskGroup(taskGroup)
532+
533+
// append jobA and task group level spreads
534+
aSpreads = append(aSpreads, jobA.Spreads...)
535+
aSpreads = append(aSpreads, tgA.Spreads...)
536+
537+
// append jobB and task group level spreads
538+
bSpreads = append(bSpreads, jobB.Spreads...)
539+
bSpreads = append(bSpreads, tgB.Spreads...)
540+
541+
// Check for equality
542+
if len(aSpreads) != len(bSpreads) {
543+
return true
544+
}
545+
546+
return !reflect.DeepEqual(aSpreads, bSpreads)
547+
}
548+
445549
// setStatus is used to update the status of the evaluation
446550
func setStatus(logger log.Logger, planner Planner,
447551
eval, nextEval, spawnedBlocked *structs.Evaluation,

scheduler/util_test.go

+202
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,208 @@ func TestShuffleNodes(t *testing.T) {
384384
require.False(t, reflect.DeepEqual(nodes, orig))
385385
}
386386

387+
func TestTaskUpdatedAffinity(t *testing.T) {
388+
j1 := mock.Job()
389+
j2 := mock.Job()
390+
name := j1.TaskGroups[0].Name
391+
392+
require.False(t, tasksUpdated(j1, j2, name))
393+
394+
// TaskGroup Affinity
395+
j2.TaskGroups[0].Affinities = []*structs.Affinity{
396+
{
397+
LTarget: "node.datacenter",
398+
RTarget: "dc1",
399+
Operand: "=",
400+
Weight: 100,
401+
},
402+
}
403+
require.True(t, tasksUpdated(j1, j2, name))
404+
405+
// TaskGroup Task Affinity
406+
j3 := mock.Job()
407+
j3.TaskGroups[0].Tasks[0].Affinities = []*structs.Affinity{
408+
{
409+
LTarget: "node.datacenter",
410+
RTarget: "dc1",
411+
Operand: "=",
412+
Weight: 100,
413+
},
414+
}
415+
416+
require.True(t, tasksUpdated(j1, j3, name))
417+
418+
j4 := mock.Job()
419+
j4.TaskGroups[0].Tasks[0].Affinities = []*structs.Affinity{
420+
{
421+
LTarget: "node.datacenter",
422+
RTarget: "dc1",
423+
Operand: "=",
424+
Weight: 100,
425+
},
426+
}
427+
428+
require.True(t, tasksUpdated(j1, j4, name))
429+
430+
// check different level of same constraint
431+
j5 := mock.Job()
432+
j5.Affinities = []*structs.Affinity{
433+
{
434+
LTarget: "node.datacenter",
435+
RTarget: "dc1",
436+
Operand: "=",
437+
Weight: 100,
438+
},
439+
}
440+
441+
j6 := mock.Job()
442+
j6.Affinities = make([]*structs.Affinity, 0)
443+
j6.TaskGroups[0].Affinities = []*structs.Affinity{
444+
{
445+
LTarget: "node.datacenter",
446+
RTarget: "dc1",
447+
Operand: "=",
448+
Weight: 100,
449+
},
450+
}
451+
452+
require.False(t, tasksUpdated(j5, j6, name))
453+
}
454+
455+
func TestTaskUpdated_Constraint(t *testing.T) {
456+
j1 := mock.Job()
457+
j1.Constraints = make([]*structs.Constraint, 0)
458+
459+
j2 := mock.Job()
460+
j2.Constraints = make([]*structs.Constraint, 0)
461+
462+
name := j1.TaskGroups[0].Name
463+
require.False(t, tasksUpdated(j1, j2, name))
464+
465+
// TaskGroup Constraint
466+
j2.TaskGroups[0].Constraints = []*structs.Constraint{
467+
{
468+
LTarget: "kernel",
469+
RTarget: "linux",
470+
Operand: "=",
471+
},
472+
}
473+
474+
// TaskGroup Task Constraint
475+
j3 := mock.Job()
476+
j3.Constraints = make([]*structs.Constraint, 0)
477+
478+
j3.TaskGroups[0].Tasks[0].Constraints = []*structs.Constraint{
479+
{
480+
LTarget: "kernel",
481+
RTarget: "linux",
482+
Operand: "=",
483+
},
484+
}
485+
486+
require.True(t, tasksUpdated(j1, j3, name))
487+
488+
j4 := mock.Job()
489+
j4.Constraints = make([]*structs.Constraint, 0)
490+
491+
j4.TaskGroups[0].Tasks[0].Constraints = []*structs.Constraint{
492+
{
493+
LTarget: "kernel",
494+
RTarget: "linux",
495+
Operand: "=",
496+
},
497+
}
498+
499+
require.True(t, tasksUpdated(j1, j4, name))
500+
501+
// check different level of same constraint
502+
j5 := mock.Job()
503+
j5.Constraints = []*structs.Constraint{
504+
{
505+
LTarget: "kernel",
506+
RTarget: "linux",
507+
Operand: "=",
508+
},
509+
}
510+
511+
j6 := mock.Job()
512+
j6.Constraints = make([]*structs.Constraint, 0)
513+
j6.TaskGroups[0].Constraints = []*structs.Constraint{
514+
{
515+
LTarget: "kernel",
516+
RTarget: "linux",
517+
Operand: "=",
518+
},
519+
}
520+
521+
require.False(t, tasksUpdated(j5, j6, name))
522+
}
523+
524+
func TestTaskUpdatedSpread(t *testing.T) {
525+
j1 := mock.Job()
526+
j2 := mock.Job()
527+
name := j1.TaskGroups[0].Name
528+
529+
require.False(t, tasksUpdated(j1, j2, name))
530+
531+
// TaskGroup Spread
532+
j2.TaskGroups[0].Spreads = []*structs.Spread{
533+
{
534+
Attribute: "node.datacenter",
535+
Weight: 100,
536+
SpreadTarget: []*structs.SpreadTarget{
537+
{
538+
Value: "r1",
539+
Percent: 50,
540+
},
541+
{
542+
Value: "r2",
543+
Percent: 50,
544+
},
545+
},
546+
},
547+
}
548+
require.True(t, tasksUpdated(j1, j2, name))
549+
550+
// check different level of same constraint
551+
j5 := mock.Job()
552+
j5.Spreads = []*structs.Spread{
553+
{
554+
Attribute: "node.datacenter",
555+
Weight: 100,
556+
SpreadTarget: []*structs.SpreadTarget{
557+
{
558+
Value: "r1",
559+
Percent: 50,
560+
},
561+
{
562+
Value: "r2",
563+
Percent: 50,
564+
},
565+
},
566+
},
567+
}
568+
569+
j6 := mock.Job()
570+
j6.TaskGroups[0].Spreads = []*structs.Spread{
571+
{
572+
Attribute: "node.datacenter",
573+
Weight: 100,
574+
SpreadTarget: []*structs.SpreadTarget{
575+
{
576+
Value: "r1",
577+
Percent: 50,
578+
},
579+
{
580+
Value: "r2",
581+
Percent: 50,
582+
},
583+
},
584+
},
585+
}
586+
587+
require.False(t, tasksUpdated(j5, j6, name))
588+
}
387589
func TestTasksUpdated(t *testing.T) {
388590
j1 := mock.Job()
389591
j2 := mock.Job()

website/source/docs/job-specification/spread.html.md

-2
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,6 @@ description: |-
1717
<code>job -> **spread**</code>
1818
<br>
1919
<code>job -> group -> **spread**</code>
20-
<br>
21-
<code>job -> group -> task -> **spread**</code>
2220
</td>
2321
</tr>
2422
</table>

0 commit comments

Comments
 (0)