Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Check for changes to affinity, constraints and spread during update #6703

Merged
merged 3 commits into from
Nov 19, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ BUG FIXES:
* scheduler: Changes to devices in resource stanza should cause rescheduling [[GH-6644](https://github.com/hashicorp/nomad/issues/6644)]
* vault: Allow overriding implicit Vault version constraint [[GH-6687](https://github.com/hashicorp/nomad/issues/6687)]
* vault: Supported Vault auth role's new field, `token_period` [[GH-6574](https://github.com/hashicorp/nomad/issues/6574)]
* 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)]

## 0.10.1 (November 4, 2019)

Expand Down
104 changes: 104 additions & 0 deletions scheduler/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,8 @@ func shuffleNodes(nodes []*structs.Node) {
// tasksUpdated does a diff between task groups to see if the
// tasks, their drivers, environment variables or config have updated. The
// inputs are the task group name to diff and two jobs to diff.
// taskUpdated and functions called within assume that the given
// taskGroup has already been checked to not be nil
func tasksUpdated(jobA, jobB *structs.Job, taskGroup string) bool {
a := jobA.LookupTaskGroup(taskGroup)
b := jobB.LookupTaskGroup(taskGroup)
Expand All @@ -356,6 +358,21 @@ func tasksUpdated(jobA, jobB *structs.Job, taskGroup string) bool {
return true
}

// Check Affinities
if affinitiesUpdated(jobA, jobB, taskGroup) {
return true
}

// Check Constraints
if constraintsUpdated(jobA, jobB, taskGroup) {
return true
}

// Check Spreads
if spreadsUpdated(jobA, jobB, taskGroup) {
return true
}

// Check each task
for _, at := range a.Tasks {
bt := b.LookupTask(at.Name)
Expand Down Expand Up @@ -442,6 +459,93 @@ func networkPortMap(n *structs.NetworkResource) map[string]int {
return m
}

func affinitiesUpdated(jobA, jobB *structs.Job, taskGroup string) bool {
var aAffinities []*structs.Affinity
var bAffinities []*structs.Affinity

tgA := jobA.LookupTaskGroup(taskGroup)
tgB := jobB.LookupTaskGroup(taskGroup)

// Append jobA job and task group level affinities
aAffinities = append(aAffinities, jobA.Affinities...)
aAffinities = append(aAffinities, tgA.Affinities...)
drewbailey marked this conversation as resolved.
Show resolved Hide resolved

// Append jobB job and task group level affinities
bAffinities = append(bAffinities, jobB.Affinities...)
bAffinities = append(bAffinities, tgB.Affinities...)

// append task affinities
for _, task := range tgA.Tasks {
aAffinities = append(aAffinities, task.Affinities...)
}

for _, task := range tgB.Tasks {
bAffinities = append(bAffinities, task.Affinities...)
}

// Check for equality
if len(aAffinities) != len(bAffinities) {
return true
}

return !reflect.DeepEqual(aAffinities, bAffinities)
}

func constraintsUpdated(jobA, jobB *structs.Job, taskGroup string) bool {
var aConstraints []*structs.Constraint
var bConstraints []*structs.Constraint

tgA := jobA.LookupTaskGroup(taskGroup)
tgB := jobB.LookupTaskGroup(taskGroup)

// Append jobA job and task group level constraints
aConstraints = append(aConstraints, jobA.Constraints...)
aConstraints = append(aConstraints, tgA.Constraints...)

// Append jobB job and task group level constraints
bConstraints = append(bConstraints, jobB.Constraints...)
bConstraints = append(bConstraints, tgB.Constraints...)

// Append task constraints
for _, task := range tgA.Tasks {
aConstraints = append(aConstraints, task.Constraints...)
}

for _, task := range tgB.Tasks {
bConstraints = append(bConstraints, task.Constraints...)
}

// Check for equality
if len(aConstraints) != len(bConstraints) {
return true
}

return !reflect.DeepEqual(aConstraints, bConstraints)
}

func spreadsUpdated(jobA, jobB *structs.Job, taskGroup string) bool {
var aSpreads []*structs.Spread
var bSpreads []*structs.Spread

tgA := jobA.LookupTaskGroup(taskGroup)
tgB := jobB.LookupTaskGroup(taskGroup)

// append jobA and task group level spreads
aSpreads = append(aSpreads, jobA.Spreads...)
aSpreads = append(aSpreads, tgA.Spreads...)

// append jobB and task group level spreads
bSpreads = append(bSpreads, jobB.Spreads...)
bSpreads = append(bSpreads, tgB.Spreads...)

// Check for equality
if len(aSpreads) != len(bSpreads) {
return true
}

return !reflect.DeepEqual(aSpreads, bSpreads)
}

// setStatus is used to update the status of the evaluation
func setStatus(logger log.Logger, planner Planner,
eval, nextEval, spawnedBlocked *structs.Evaluation,
Expand Down
202 changes: 202 additions & 0 deletions scheduler/util_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,208 @@ func TestShuffleNodes(t *testing.T) {
require.False(t, reflect.DeepEqual(nodes, orig))
}

func TestTaskUpdatedAffinity(t *testing.T) {
j1 := mock.Job()
j2 := mock.Job()
name := j1.TaskGroups[0].Name

require.False(t, tasksUpdated(j1, j2, name))

// TaskGroup Affinity
j2.TaskGroups[0].Affinities = []*structs.Affinity{
{
LTarget: "node.datacenter",
RTarget: "dc1",
Operand: "=",
Weight: 100,
},
}
require.True(t, tasksUpdated(j1, j2, name))

// TaskGroup Task Affinity
j3 := mock.Job()
j3.TaskGroups[0].Tasks[0].Affinities = []*structs.Affinity{
{
LTarget: "node.datacenter",
RTarget: "dc1",
Operand: "=",
Weight: 100,
},
}

require.True(t, tasksUpdated(j1, j3, name))

j4 := mock.Job()
j4.TaskGroups[0].Tasks[0].Affinities = []*structs.Affinity{
{
LTarget: "node.datacenter",
RTarget: "dc1",
Operand: "=",
Weight: 100,
},
}

require.True(t, tasksUpdated(j1, j4, name))

// check different level of same constraint
j5 := mock.Job()
j5.Affinities = []*structs.Affinity{
{
LTarget: "node.datacenter",
RTarget: "dc1",
Operand: "=",
Weight: 100,
},
}

j6 := mock.Job()
j6.Affinities = make([]*structs.Affinity, 0)
j6.TaskGroups[0].Affinities = []*structs.Affinity{
{
LTarget: "node.datacenter",
RTarget: "dc1",
Operand: "=",
Weight: 100,
},
}

require.False(t, tasksUpdated(j5, j6, name))
}

func TestTaskUpdated_Constraint(t *testing.T) {
j1 := mock.Job()
j1.Constraints = make([]*structs.Constraint, 0)

j2 := mock.Job()
j2.Constraints = make([]*structs.Constraint, 0)

name := j1.TaskGroups[0].Name
require.False(t, tasksUpdated(j1, j2, name))

// TaskGroup Constraint
j2.TaskGroups[0].Constraints = []*structs.Constraint{
{
LTarget: "kernel",
RTarget: "linux",
Operand: "=",
},
}

// TaskGroup Task Constraint
j3 := mock.Job()
j3.Constraints = make([]*structs.Constraint, 0)

j3.TaskGroups[0].Tasks[0].Constraints = []*structs.Constraint{
{
LTarget: "kernel",
RTarget: "linux",
Operand: "=",
},
}

require.True(t, tasksUpdated(j1, j3, name))

j4 := mock.Job()
j4.Constraints = make([]*structs.Constraint, 0)

j4.TaskGroups[0].Tasks[0].Constraints = []*structs.Constraint{
{
LTarget: "kernel",
RTarget: "linux",
Operand: "=",
},
}

require.True(t, tasksUpdated(j1, j4, name))

// check different level of same constraint
j5 := mock.Job()
j5.Constraints = []*structs.Constraint{
{
LTarget: "kernel",
RTarget: "linux",
Operand: "=",
},
}

j6 := mock.Job()
j6.Constraints = make([]*structs.Constraint, 0)
j6.TaskGroups[0].Constraints = []*structs.Constraint{
{
LTarget: "kernel",
RTarget: "linux",
Operand: "=",
},
}

require.False(t, tasksUpdated(j5, j6, name))
}

func TestTaskUpdatedSpread(t *testing.T) {
j1 := mock.Job()
j2 := mock.Job()
name := j1.TaskGroups[0].Name

require.False(t, tasksUpdated(j1, j2, name))

// TaskGroup Spread
j2.TaskGroups[0].Spreads = []*structs.Spread{
{
Attribute: "node.datacenter",
Weight: 100,
SpreadTarget: []*structs.SpreadTarget{
{
Value: "r1",
Percent: 50,
},
{
Value: "r2",
Percent: 50,
},
},
},
}
require.True(t, tasksUpdated(j1, j2, name))

// check different level of same constraint
j5 := mock.Job()
j5.Spreads = []*structs.Spread{
{
Attribute: "node.datacenter",
Weight: 100,
SpreadTarget: []*structs.SpreadTarget{
{
Value: "r1",
Percent: 50,
},
{
Value: "r2",
Percent: 50,
},
},
},
}

j6 := mock.Job()
j6.TaskGroups[0].Spreads = []*structs.Spread{
{
Attribute: "node.datacenter",
Weight: 100,
SpreadTarget: []*structs.SpreadTarget{
{
Value: "r1",
Percent: 50,
},
{
Value: "r2",
Percent: 50,
},
},
},
}

require.False(t, tasksUpdated(j5, j6, name))
}
func TestTasksUpdated(t *testing.T) {
j1 := mock.Job()
j2 := mock.Job()
Expand Down
2 changes: 0 additions & 2 deletions website/source/docs/job-specification/spread.html.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@ description: |-
<code>job -> **spread**</code>
<br>
<code>job -> group -> **spread**</code>
<br>
<code>job -> group -> task -> **spread**</code>
</td>
</tr>
</table>
Expand Down