-
Notifications
You must be signed in to change notification settings - Fork 2k
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
scheduler: Make != constraints more flexible #4875
Conversation
This commit allows the ConstraintChecker to test values that do not exist. This is useful when wanting to _exclude_ given nodes from executing a job, for example, if you wanted to give canary nodes an attribute, and not run critical services on them, you may specify something like the below, but not want to tag all other nodes with the inverse. ```hcl constraint { attribute = "${node.attr.canary} operator = "!=" value = "1" } ``` This also requires all constraint checkers to allow for nil target values, as they will no longer be short circuited by resolving a target.
981dd2e
to
44bd0c9
Compare
scheduler/feasible.go
Outdated
@@ -480,7 +480,8 @@ func checkConstraint(ctx Context, operand string, lVal, rVal interface{}) bool { | |||
|
|||
// checkAffinity checks if a specific affinity is satisfied | |||
func checkAffinity(ctx Context, operand string, lVal, rVal interface{}) bool { | |||
return checkConstraint(ctx, operand, lVal, rVal) | |||
// We pass ok here for both values, because matchesAffinity prevalidates these |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@preetapan I'm not too familiar with the affinity system yet, but do we want to support similar is_set mechanics there too? - Right now, affinities are probably inheriting the new != matching behaviour?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yup, they share the same set of operators (except for distincthost/distinctproperty) and the underlying matching code so this should just work for affinities too
EDIT - Affinities already inherit the new != behavior like you pointed out.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@preetapan Doesn't this need to change to pass through the lOk, rOk values: https://github.com/hashicorp/nomad/blob/master/scheduler/rank.go#L577
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
since affinities are optional/best effort with weights it didn't make sense to me to support lok/rok values. ie creating two affinities one for the value being set and another for the value matching a value isn't meaningful when calculating a final score. So I wanted to preserve the existing implementation (only match if both the values are set)
@@ -196,6 +198,12 @@ constraint { | |||
} | |||
``` | |||
|
|||
- `"is_set"` - Specifies that a given attribute must be present. This can be | |||
used with features like `!=` to require that an attribute has been configured, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggested rewrite : This can be combined with the
!=operator to require that an attribute has been set before checking for equality. The default behavior for
!=is to include nodes that don't have that attribute set.
@@ -202,7 +202,8 @@ func TestConstraintChecker(t *testing.T) { | |||
|
|||
nodes[0].Attributes["kernel.name"] = "freebsd" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can you add test cases with constraints using the is_set/is_not_set operators?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh yep, sorry - thought I'd already added those.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
left comments
@preetapan Updated - PTAL then I'll squash the fixups into the respective commits. |
@@ -448,8 +443,9 @@ func resolveTarget(target string, node *structs.Node) (interface{}, bool) { | |||
} | |||
} | |||
|
|||
// checkConstraint checks if a constraint is satisfied | |||
func checkConstraint(ctx Context, operand string, lVal, rVal interface{}) bool { | |||
// checkConstraint checks if a constraint is satisfied. The lVal and rVal |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Need to do the same here too: https://github.com/hashicorp/nomad/blob/136dace45c34223bb0a63ba7a0019792d6e7d797/scheduler/feasible.go#L984
@@ -465,6 +461,10 @@ func checkConstraint(ctx Context, operand string, lVal, rVal interface{}) bool { | |||
return !reflect.DeepEqual(lVal, rVal) | |||
case "<", "<=", ">", ">=": | |||
return checkLexicalOrder(operand, lVal, rVal) | |||
case structs.ConstraintAttributeIsSet: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't all the existing except !=
and not
require lFound && rFound
to be true
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Otherwise this is true and that is a change in behavior and seemingly undesirable:
constraint {
attribute = "${meta.my-non-existing}"
operator = "=="
value = "${meta.my-other-non-existing}"
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch.. first I thought the existing methods for other operators already handled nils inside by returning false after casting fails. But reflect.deepequal(nil, nil) will return true, so we should capture that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch 😬 🙈
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
edited comment
d7f7b13
to
f450b28
Compare
@dadgar Updated 👍 |
@@ -342,6 +348,16 @@ func TestCheckConstraint(t *testing.T) { | |||
lVal: "foo", rVal: "foo", | |||
result: true, | |||
}, | |||
{ | |||
op: "==", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do a nil nil case
scheduler/feasible.go
Outdated
@@ -480,7 +480,8 @@ func checkConstraint(ctx Context, operand string, lVal, rVal interface{}) bool { | |||
|
|||
// checkAffinity checks if a specific affinity is satisfied | |||
func checkAffinity(ctx Context, operand string, lVal, rVal interface{}) bool { | |||
return checkConstraint(ctx, operand, lVal, rVal) | |||
// We pass ok here for both values, because matchesAffinity prevalidates these |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@preetapan Doesn't this need to change to pass through the lOk, rOk values: https://github.com/hashicorp/nomad/blob/master/scheduler/rank.go#L577
scheduler/feasible.go
Outdated
} | ||
|
||
// checkAttributeAffinity checks if an affinity is satisfied | ||
func checkAttributeAffinity(ctx Context, operand string, lVal, rVal *psstructs.Attribute) bool { | ||
return checkAttributeConstraint(ctx, operand, lVal, rVal) | ||
return checkAttributeConstraint(ctx, operand, lVal, rVal, true, true) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here should pass through as well the lFound/rFound values
Once merged add to changelog please |
This adds constraints for asserting that a given attribute or value exists, or does not exist. This acts as a companion to =, or != operators, e.g: ```hcl constraint { attribute = "${attrs.type}" operator = "!=" value = "database" } constraint { attribute = "${attrs.type}" operator = "is_set" } ```
343377e
to
5aac304
Compare
I'm going to lock this pull request because it has been closed for 120 days ⏳. This helps our maintainers find and focus on the active contributions. |
closes #1419
There are two main changes in this pull request, one of them is technically a change in existing behaviour:
The
"!="
operatorThe != operator will be allowed to evaluate nil or empty values. This is useful when wanting to exclude given nodes from executing a job, for example, if you wanted to give canary nodes an attribute, and not run critical services on them, you may specify something like the below, but not want to tag all other nodes with the inverse.
This also requires all constraint checkers to allow for nil target values, as they will no longer be short circuited by resolving a target.
The
"is_(not_)set"
operatorsThis adds constraints for asserting that a given attribute or value exists, or does not exist. This acts as a companion to =, or != operators, e.g to restore the previous behaviour of the != operator: