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

scheduler: Make != constraints more flexible #4875

Merged
merged 4 commits into from
Nov 15, 2018
Merged

Conversation

endocrimes
Copy link
Contributor

@endocrimes endocrimes commented Nov 14, 2018

closes #1419

There are two main changes in this pull request, one of them is technically a change in existing behaviour:

The "!=" operator

The != 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.

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.

The "is_(not_)set" operators

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 to restore the previous behaviour of the != operator:

constraint {
      attribute = "${attrs.type}"
      operator  = "!="
      value     = "database"
}

constraint {
      attribute = "${attrs.type}"
      operator  = "is_set"
}

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.
@@ -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
Copy link
Contributor Author

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?

Copy link
Member

@preetapan preetapan Nov 14, 2018

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.

Copy link
Contributor

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

Copy link
Member

@preetapan preetapan Nov 15, 2018

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,
Copy link
Member

@preetapan preetapan Nov 14, 2018

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"
Copy link
Member

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?

Copy link
Contributor Author

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.

Copy link
Member

@preetapan preetapan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

left comments

@endocrimes
Copy link
Contributor Author

@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
Copy link
Contributor

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:

func checkAttributeConstraint(ctx Context, operand string, lVal, rVal *psstructs.Attribute) bool {

@@ -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:
Copy link
Contributor

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

Copy link
Contributor

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}"
}

Copy link
Member

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch 😬 🙈

Copy link
Member

@preetapan preetapan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

edited comment

@endocrimes
Copy link
Contributor Author

@dadgar Updated 👍

@@ -342,6 +348,16 @@ func TestCheckConstraint(t *testing.T) {
lVal: "foo", rVal: "foo",
result: true,
},
{
op: "==",
Copy link
Contributor

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

@@ -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
Copy link
Contributor

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

}

// 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)
Copy link
Contributor

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

@dadgar
Copy link
Contributor

dadgar commented Nov 15, 2018

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"
}
```
@endocrimes endocrimes merged commit 60c6cb8 into master Nov 15, 2018
@endocrimes endocrimes deleted the f-constraints branch November 15, 2018 19:04
@github-actions
Copy link

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.
If you have found a problem that seems related to this change, please open a new issue and complete the issue template so we can capture all the details necessary to investigate further.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Feb 24, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Constraints operator "is not", "not" and "!=" doesn't work as expected with missing meta tags
3 participants