-
Notifications
You must be signed in to change notification settings - Fork 39.6k
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
Added MatchFields to NodeSelectorTerm #62002
Conversation
/retest |
/retest |
pkg/apis/core/types.go
Outdated
@@ -2274,6 +2274,8 @@ type NodeSelector struct { | |||
type NodeSelectorTerm struct { | |||
//Required. A list of node selector requirements. The requirements are ANDed. | |||
MatchExpressions []NodeSelectorRequirement | |||
//Optional. A list of node selector requirements by node's field. The requirements are ANDed. | |||
MatchFields []NodeSelectorRequirement |
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.
does this change MatchExpressions to be optional (at least one of MatchExpressions and MatchFields must be specified?), or would you expect this to be paired with a MatchExpression that selected everything (is that even possible)?
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.
at least one of MatchExpressions and MatchFields must be specified
This one :) And the requirements are ANDed; if both are not empty, they also are ANDed.
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.
Done; updated comments and added validation. Does not check the key of MatchFields
, will handle it when adding related logic in scheduler.
ping for comments :) /retest |
/sig scheduling |
/retest |
@@ -3008,12 +3008,19 @@ func ValidateNodeSelectorRequirement(rq core.NodeSelectorRequirement, fldPath *f | |||
func ValidateNodeSelectorTerm(term core.NodeSelectorTerm, fldPath *field.Path) field.ErrorList { | |||
allErrs := field.ErrorList{} | |||
|
|||
if len(term.MatchExpressions) == 0 { | |||
return append(allErrs, field.Required(fldPath.Child("matchExpressions"), "must have at least one node selector requirement")) | |||
if len(term.MatchExpressions) == 0 && len(term.MatchFields) == 0 { |
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.
I think this loosening of validation makes sense, but can cause grief for a 1.10-level apiserver (during rolling upgrade or downgrade from running 1.11):
- an object with a podspec is submitted with MatchFields set and MatchExpressions unset, 1.11 apiserver validates and persists it
- during rolling HA upgrade or after downgrade to 1.10, a 1.10 apiserver will not know about the MatchFields field, and would consider the object invalid and disallow updates to it (and possibly disallow deleting it)
The doc on the NodeSelectorTerm
type says // A null or empty node selector term matches no objects.
which would be acceptable for the 1.10 scheduler to do (would fail safe with "unscheduleable" error), but the actual API validation code in 1.10 prevents an empty node selector term from being allowed.
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.
The doc on the NodeSelectorTerm type says
// A null or empty node selector term matches no objects
. which would be acceptable for the 1.10 scheduler to do (would fail safe with "unscheduleable" error), but the actual API validation code in 1.10 prevents an empty node selector term from being allowed.
Good point; the implementation of schedule match this doc/comment. Scheduler will let pod pending with 'unscheduleable' error. I think we should remove this validation in 1.11; for 1.10, not sure, cherry-pick??
I'm a little confused about this; another meaning maybe: the NodeSelectorTerms []NodeSelectorTerm
in NodeSelector
; for each single term, the MatchExpressions
can not be nil or empty.
But both cases (NodeSelectorTerms
is nill and MatchExpressions
is nill ) are handled by scheduler (nodeMatchesNodeSelectorTerms
and NodeSelectorRequirementsAsSelector
); so that's safe to remove it in 1.11, for 1.10, not sure, cherry-pick it?
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.
I'm a little confused about this; another meaning maybe: the NodeSelectorTerms []NodeSelectorTerm in NodeSelector; for each single term, the MatchExpressions can not be nil or empty.
the documentation is pretty clear, and the implementation actually behaves as documented if it is given a NodeSelectorTerm that is empty (v1helper.NodeSelectorRequirementsAsSelector(req.MatchExpressions)
returns labels.Nothing()
if there are no match expressions)
I would pick a fix to validation in 1.10 to tolerate missing MatchExpressions:
func ValidateNodeSelectorTerm(term core.NodeSelectorTerm, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
- if len(term.MatchExpressions) == 0 {
- return append(allErrs, field.Required(fldPath.Child("matchExpressions"), "must have at least one node selector requirement"))
- }
for j, req := range term.MatchExpressions {
allErrs = append(allErrs, ValidateNodeSelectorRequirement(req, fldPath.Child("matchExpressions").Index(j))...)
}
cc @msau42 since this is called from validateStorageNodeAffinityAnnotation as well
if len(term.MatchExpressions) == 0 { | ||
return append(allErrs, field.Required(fldPath.Child("matchExpressions"), "must have at least one node selector requirement")) | ||
if len(term.MatchExpressions) == 0 && len(term.MatchFields) == 0 { | ||
return append(allErrs, field.Required(fldPath.Child("matchExpressions/matchFields"), |
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.
this isn't a valid field path... what we typically do in situations like this is put the required error on the parent struct with a message. for example:
if numVolumes == 0 {
allErrs = append(allErrs, field.Required(fldPath, "must specify a volume type"))
}
for j, req := range term.MatchExpressions { | ||
allErrs = append(allErrs, ValidateNodeSelectorRequirement(req, fldPath.Child("matchExpressions").Index(j))...) | ||
} | ||
|
||
for j, req := range term.MatchFields { | ||
allErrs = append(allErrs, ValidateNodeSelectorRequirement(req, fldPath.Child("matchFields").Index(j))...) |
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.
is the plan to allow the API to be expressive, and just result in errors by the scheduler or the kubelet if unsupported keys or operations are expressed?
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.
prefer to let scheduler/kubelet the handle the specific key; as fields are supported by k8s version, e.g. metadata.name in 1.11 :)
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.
Is the validation done in ValidateNodeSelectorRequirement valid for keys/values that are not limited to valid label keys/values (given these MatchField terms are referencing non-label field names and values)
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.
Other field references in the API (like the downward API) validate specific field reference names. Are we sure we want to allow arbitrarily complex expressions here, when the intent is just to support metadata.name=foo? We can't reasonably tighten validation on this field in the future, so just calling it out here.
pkg/apis/core/types.go
Outdated
@@ -2099,10 +2099,14 @@ type NodeSelector struct { | |||
NodeSelectorTerms []NodeSelectorTerm | |||
} | |||
|
|||
// A null or empty node selector term matches no objects. | |||
// A null or empty node selector term matches no objects. At least one of |
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.
remove At least one of 'MatchExpressions' and 'MatchFields' must be specified
, since that conflicts with A null or empty node selector term matches no objects.
, and we want a 1.10 server to tolerate a NodeSelectorTerm
containing no MatchExpressions field.
@@ -3008,12 +3008,19 @@ func ValidateNodeSelectorRequirement(rq core.NodeSelectorRequirement, fldPath *f | |||
func ValidateNodeSelectorTerm(term core.NodeSelectorTerm, fldPath *field.Path) field.ErrorList { | |||
allErrs := field.ErrorList{} | |||
|
|||
if len(term.MatchExpressions) == 0 { | |||
return append(allErrs, field.Required(fldPath.Child("matchExpressions"), "must have at least one node selector requirement")) | |||
if len(term.MatchExpressions) == 0 && len(term.MatchFields) == 0 { |
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.
I would actually remove this requirement completely... an empty term matches no objects, as documented. Stick to validating MatchExpressions or MatchFields if they provide them
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.
sure, will handle it in #62448
Automatic merge from submit-queue (batch tested with PRs 62448, 59317, 59947, 62418, 62352). If you want to cherry-pick this change to another branch, please follow the instructions <a href="https://github.com/kubernetes/community/blob/master/contributors/devel/cherry-picks.md">here</a>. Removed no-empty validation of nodeSelectorTerm.matchExpressions. Signed-off-by: Da K. Ma <klaus1982.cn@gmail.com> **Which issue(s) this PR fixes** *(optional, in `fixes #<issue number>(, fixes #<issue_number>, ...)` format, will close the issue(s) when PR gets merged)*: part of #62002 **Release note**: ```release-note Pod affinity `nodeSelectorTerm.matchExpressions` may now be empty, and works as previously documented: nil or empty `matchExpressions` matches no objects in scheduler. ```
type NodeSelectorTerm struct { | ||
//Required. A list of node selector requirements. The requirements are ANDed. | ||
// A list of node selector requirements by node's labels. |
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.
Please add // +optional
to both fields.
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.
and omitempty
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.
done
pkg/apis/core/v1/helper/helpers.go
Outdated
labelSelector, err := NodeSelectorRequirementsAsSelector(req.MatchExpressions) | ||
if err != nil { | ||
glog.V(10).Infof("Failed to parse MatchExpressions: %+v,regarding as not match.", req.MatchExpressions) | ||
continue |
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.
Why do we continue here? Shouldn't we return false instead?
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.
because the terms are ORed, a failure in one should not short circuit
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.
It should be ORed between terms, and ANDed within a terms (matchExpressions and matchFields).
pkg/apis/core/v1/helper/helpers.go
Outdated
fieldSelector, err := NodeFieldSelectorRequirementsAsSelector(req.MatchFields) | ||
if err != nil { | ||
glog.V(10).Infof("Failed to parse MatcheFields: %+v,regarding as not match.", req.MatchFields) | ||
continue |
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.
Same as above. Shouldn't we return false?
pkg/apis/core/v1/helper/helpers.go
Outdated
return selector, nil | ||
} | ||
|
||
// MatchNodeSelectorTerms checks whether the node labels and fields match node selector terms in ORed; |
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.
In most of our code base (or maybe all of it), we AND terms when there are multiple of them. Why are we ORing here?
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.
that was what was designed and documented previously, so for compatibility.
the reason that design makes sense is that each NodeSelectorTerm can already express everything that ANDing together multiple terms could.
the only reason to have a list of multiple terms is to let you select distinct sets of nodes matching non-overlapping selectors, which requires ORing the terms together.
// of characters. See validateLabelKey for more details. | ||
// | ||
// The empty string is a valid value in the input values set. | ||
func NewFieldRequirement(key string, op selection.Operator, vals []string) (*Requirement, error) { |
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.
pkg/fields/selector.go
contains field-selector related things... I didn't really expect this added to the labels package
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.
Moved to fields/selector.go
; but for now, we only support equals
and not equals
for matchFields
which is enough for scheduling DaemoSet pods by default scheduler. For the other advance features, e.g. IN/NotIN for a set, will be handled in a separate issue. If any comments/suggestion please let me know.
586bb2b
to
2cc53dc
Compare
@@ -82,19 +83,19 @@ func NewSelector() Selector { | |||
|
|||
type internalSelector []Requirement | |||
|
|||
func (s internalSelector) DeepCopy() internalSelector { |
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.
revert this bit?
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.
+1. Let's not add unnecessary changes to the PR.
|
||
func (n nothingSelector) Matches(_ Fields) bool { return false } | ||
func (n nothingSelector) Empty() bool { return false } | ||
func (n nothingSelector) String() string { return "" } |
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.
is the expectation that ParseSelector(s.String())
would return a selector with equivalent behavior? If so, this doesn't hold
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.
I guess labels.Nothing()
has the same issue... worth noting that the Nothing()
selector is not appropriate for serializing to string for persistence and later parsing
a couple nits, lgtm otherwise will leave to @kubernetes/sig-scheduling-api-reviews for final LGTM /approve |
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.
Just one nit and then LGTM
@@ -82,19 +83,19 @@ func NewSelector() Selector { | |||
|
|||
type internalSelector []Requirement | |||
|
|||
func (s internalSelector) DeepCopy() internalSelector { |
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.
+1. Let's not add unnecessary changes to the PR.
Signed-off-by: Da K. Ma <klaus1982.cn@gmail.com>
Signed-off-by: Da K. Ma <klaus1982.cn@gmail.com>
/lgtm |
[APPROVALNOTIFIER] This PR is APPROVED This pull-request has been approved by: k82cn, liggitt The full list of commands accepted by this bot can be found here. The pull request process is described here
Needs approval from an approver in each of these files:
Approvers can indicate their approval by writing |
Automatic merge from submit-queue (batch tested with PRs 62495, 63003, 62829, 62151, 62002). If you want to cherry-pick this change to another branch, please follow the instructions here. |
This seems different from what's discussed in #61410 (just having a specific term for |
here's suggestion: #61410 (comment) . And current term are label based: 1.) the validation for field maybe different with label's, 2.) the name of 'specific term' maybe conflict with existed label. |
MatchFields: []v1.NodeSelectorRequirement{{ | ||
Key: "metadata.name", | ||
Operator: v1.NodeSelectorOpIn, | ||
Values: []string{"host_1, host_2"}, |
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.
that test confused me a bit as I guess its still an array with only 1 element ? (guess the test is supposed to check that only one element is allowed by providing something like []string{"host1", "host2"}
instead.
Sorry, to be picky, but I ended up here as I found no other real documentation for the "machtFields" selector (and I think the official documentation at https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.11/#nodeselectorterm-v1-core is a bit misleading as it does not mention that only 'In' and 'NotIn' are allowed, with only one value allowed in the list of terms.
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.
@rhuss This PR was merged in April 2018. It is too late to leave review comments. If you have ideas to improve or fix the code, please go ahead and send a PR.
"must be only one value when `operator` is 'In' or 'NotIn' for node field selector")) | ||
} | ||
default: | ||
allErrs = append(allErrs, field.Invalid(fldPath.Child("operator"), req.Operator, "not a valid selector operator")) |
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.
would change to 'not a valid node field selector operator' to make clear which kind of selector is meant.
} | ||
|
||
if vf, found := nodeFieldSelectorValidators[req.Key]; !found { | ||
allErrs = append(allErrs, field.Invalid(fldPath.Child("key"), req.Key, "not a valid field selector key")) |
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.
"node field selector" to harmonize with the other error messages.
What this PR does / why we need it:
Which issue(s) this PR fixes (optional, in
fixes #<issue number>(, fixes #<issue_number>, ...)
format, will close the issue(s) when PR gets merged):part of #61410
Special notes for your reviewer:
According to the discussion at #61410 , we'd like to introduce a new selector term for node's field.
Release note: