From c2e2a0d0a2b528e7607d9b57f03b17730c50408f Mon Sep 17 00:00:00 2001 From: Aldo Culquicondor Date: Fri, 30 Oct 2020 18:58:41 -0400 Subject: [PATCH] Add runtime representation of v1.NodeSelector to be used for repeatedly matching nodes. Also provide a lazy version (that only reports errors if no terms match) for backwards compatibility of corev1.MatchNodeSelectorTerms Change-Id: Ib1a0866979ce6cf75d1d9668c4bf8f6fb57298b2 --- staging/publishing/import-restrictions.yaml | 1 + .../component-helpers/scheduling/corev1/BUILD | 11 +- .../scheduling/corev1/helpers.go | 107 +-------- .../scheduling/corev1/helpers_test.go | 70 +----- .../scheduling/corev1/nodeaffinity/BUILD | 42 ++++ .../corev1/nodeaffinity/nodeaffinity.go | 199 ++++++++++++++++ .../corev1/nodeaffinity/nodeaffinity_test.go | 215 ++++++++++++++++++ vendor/modules.txt | 1 + 8 files changed, 467 insertions(+), 179 deletions(-) create mode 100644 staging/src/k8s.io/component-helpers/scheduling/corev1/nodeaffinity/BUILD create mode 100644 staging/src/k8s.io/component-helpers/scheduling/corev1/nodeaffinity/nodeaffinity.go create mode 100644 staging/src/k8s.io/component-helpers/scheduling/corev1/nodeaffinity/nodeaffinity_test.go diff --git a/staging/publishing/import-restrictions.yaml b/staging/publishing/import-restrictions.yaml index 638062a564c..61c5a13ce52 100644 --- a/staging/publishing/import-restrictions.yaml +++ b/staging/publishing/import-restrictions.yaml @@ -255,5 +255,6 @@ - k8s.io/api - k8s.io/apimachinery - k8s.io/client-go + - k8s.io/component-helpers - k8s.io/klog - k8s.io/utils diff --git a/staging/src/k8s.io/component-helpers/scheduling/corev1/BUILD b/staging/src/k8s.io/component-helpers/scheduling/corev1/BUILD index 0ad20d7aa5d..61321c3a41f 100644 --- a/staging/src/k8s.io/component-helpers/scheduling/corev1/BUILD +++ b/staging/src/k8s.io/component-helpers/scheduling/corev1/BUILD @@ -11,10 +11,7 @@ go_library( visibility = ["//visibility:public"], deps = [ "//staging/src/k8s.io/api/core/v1:go_default_library", - "//staging/src/k8s.io/apimachinery/pkg/fields:go_default_library", - "//staging/src/k8s.io/apimachinery/pkg/labels:go_default_library", - "//staging/src/k8s.io/apimachinery/pkg/selection:go_default_library", - "//staging/src/k8s.io/apimachinery/pkg/util/errors:go_default_library", + "//staging/src/k8s.io/component-helpers/scheduling/corev1/nodeaffinity:go_default_library", ], ) @@ -26,7 +23,6 @@ go_test( "//staging/src/k8s.io/api/core/v1:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/api/equality:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//staging/src/k8s.io/apimachinery/pkg/labels:go_default_library", ], ) @@ -39,7 +35,10 @@ filegroup( filegroup( name = "all-srcs", - srcs = [":package-srcs"], + srcs = [ + ":package-srcs", + "//staging/src/k8s.io/component-helpers/scheduling/corev1/nodeaffinity:all-srcs", + ], tags = ["automanaged"], visibility = ["//visibility:public"], ) diff --git a/staging/src/k8s.io/component-helpers/scheduling/corev1/helpers.go b/staging/src/k8s.io/component-helpers/scheduling/corev1/helpers.go index 5159b892502..57f916c265b 100644 --- a/staging/src/k8s.io/component-helpers/scheduling/corev1/helpers.go +++ b/staging/src/k8s.io/component-helpers/scheduling/corev1/helpers.go @@ -17,13 +17,8 @@ limitations under the License. package corev1 import ( - "fmt" - v1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/fields" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/selection" - apierrors "k8s.io/apimachinery/pkg/util/errors" + "k8s.io/component-helpers/scheduling/corev1/nodeaffinity" ) // PodPriority returns priority of the given pod. @@ -46,103 +41,5 @@ func MatchNodeSelectorTerms( if node == nil { return false, nil } - var errors []error - for _, req := range nodeSelector.NodeSelectorTerms { - // nil or empty term selects no objects - if len(req.MatchExpressions) == 0 && len(req.MatchFields) == 0 { - continue - } - - if len(req.MatchExpressions) != 0 { - labelSelector, err := nodeSelectorRequirementsAsSelector(req.MatchExpressions) - if err != nil { - errors = append(errors, err) - continue - } - if labelSelector == nil || !labelSelector.Matches(labels.Set(node.Labels)) { - continue - } - } - - if len(req.MatchFields) != 0 && len(node.Name) > 0 { - fieldSelector, err := nodeSelectorRequirementsAsFieldSelector(req.MatchFields) - if err != nil { - errors = append(errors, err) - continue - } - if fieldSelector == nil || !fieldSelector.Matches(fields.Set{"metadata.name": node.Name}) { - continue - } - } - - return true, nil - } - - return false, apierrors.NewAggregate(errors) -} - -// nodeSelectorRequirementsAsSelector converts the []NodeSelectorRequirement api type into a struct that implements -// labels.Selector. -func nodeSelectorRequirementsAsSelector(nsm []v1.NodeSelectorRequirement) (labels.Selector, error) { - if len(nsm) == 0 { - return labels.Nothing(), nil - } - selector := labels.NewSelector() - for _, expr := range nsm { - var op selection.Operator - switch expr.Operator { - case v1.NodeSelectorOpIn: - op = selection.In - case v1.NodeSelectorOpNotIn: - op = selection.NotIn - case v1.NodeSelectorOpExists: - op = selection.Exists - case v1.NodeSelectorOpDoesNotExist: - op = selection.DoesNotExist - case v1.NodeSelectorOpGt: - op = selection.GreaterThan - case v1.NodeSelectorOpLt: - op = selection.LessThan - default: - return nil, fmt.Errorf("%q is not a valid node selector operator", expr.Operator) - } - r, err := labels.NewRequirement(expr.Key, op, expr.Values) - if err != nil { - return nil, err - } - selector = selector.Add(*r) - } - return selector, nil -} - -// nodeSelectorRequirementsAsFieldSelector converts the []NodeSelectorRequirement core type into a struct that implements -// fields.Selector. -func nodeSelectorRequirementsAsFieldSelector(nsm []v1.NodeSelectorRequirement) (fields.Selector, error) { - if len(nsm) == 0 { - return fields.Nothing(), nil - } - - selectors := []fields.Selector{} - for _, expr := range nsm { - switch expr.Operator { - case v1.NodeSelectorOpIn: - if len(expr.Values) != 1 { - return nil, fmt.Errorf("unexpected number of value (%d) for node field selector operator %q", - len(expr.Values), expr.Operator) - } - selectors = append(selectors, fields.OneTermEqualSelector(expr.Key, expr.Values[0])) - - case v1.NodeSelectorOpNotIn: - if len(expr.Values) != 1 { - return nil, fmt.Errorf("unexpected number of value (%d) for node field selector operator %q", - len(expr.Values), expr.Operator) - } - selectors = append(selectors, fields.OneTermNotEqualSelector(expr.Key, expr.Values[0])) - - default: - return nil, fmt.Errorf("%q is not a valid node field selector operator", expr.Operator) - } - } - - return fields.AndSelectors(selectors...), nil + return nodeaffinity.NewLazyErrorNodeSelector(nodeSelector).Match(node) } diff --git a/staging/src/k8s.io/component-helpers/scheduling/corev1/helpers_test.go b/staging/src/k8s.io/component-helpers/scheduling/corev1/helpers_test.go index 195f09532a4..8428038594d 100644 --- a/staging/src/k8s.io/component-helpers/scheduling/corev1/helpers_test.go +++ b/staging/src/k8s.io/component-helpers/scheduling/corev1/helpers_test.go @@ -17,13 +17,11 @@ limitations under the License. package corev1 import ( - "reflect" "testing" v1 "k8s.io/api/core/v1" apiequality "k8s.io/apimachinery/pkg/api/equality" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/labels" ) // TestPodPriority tests PodPriority function. @@ -121,7 +119,7 @@ func TestMatchNodeSelectorTerms(t *testing.T) { MatchFields: []v1.NodeSelectorRequirement{{ Key: "metadata.name", Operator: v1.NodeSelectorOpIn, - Values: []string{"host_1, host_2"}, + Values: []string{"host_1", "host_2"}, }}, }, }}, @@ -539,7 +537,7 @@ func TestMatchNodeSelectorTermsStateless(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - MatchNodeSelectorTerms(tt.args.node, tt.args.nodeSelector) + _, _ = MatchNodeSelectorTerms(tt.args.node, tt.args.nodeSelector) if !apiequality.Semantic.DeepEqual(tt.args.nodeSelector, tt.want) { // fail when tt.args.nodeSelector is deeply modified t.Errorf("MatchNodeSelectorTerms() got = %v, want %v", tt.args.nodeSelector, tt.want) @@ -547,67 +545,3 @@ func TestMatchNodeSelectorTermsStateless(t *testing.T) { }) } } - -func TestNodeSelectorRequirementsAsSelector(t *testing.T) { - matchExpressions := []v1.NodeSelectorRequirement{{ - Key: "foo", - Operator: v1.NodeSelectorOpIn, - Values: []string{"bar", "baz"}, - }} - mustParse := func(s string) labels.Selector { - out, e := labels.Parse(s) - if e != nil { - panic(e) - } - return out - } - tc := []struct { - in []v1.NodeSelectorRequirement - out labels.Selector - expectErr bool - }{ - {in: nil, out: labels.Nothing()}, - {in: []v1.NodeSelectorRequirement{}, out: labels.Nothing()}, - { - in: matchExpressions, - out: mustParse("foo in (baz,bar)"), - }, - { - in: []v1.NodeSelectorRequirement{{ - Key: "foo", - Operator: v1.NodeSelectorOpExists, - Values: []string{"bar", "baz"}, - }}, - expectErr: true, - }, - { - in: []v1.NodeSelectorRequirement{{ - Key: "foo", - Operator: v1.NodeSelectorOpGt, - Values: []string{"1"}, - }}, - out: mustParse("foo>1"), - }, - { - in: []v1.NodeSelectorRequirement{{ - Key: "bar", - Operator: v1.NodeSelectorOpLt, - Values: []string{"7"}, - }}, - out: mustParse("bar<7"), - }, - } - - for i, tc := range tc { - out, err := nodeSelectorRequirementsAsSelector(tc.in) - if err == nil && tc.expectErr { - t.Errorf("[%v]expected error but got none.", i) - } - if err != nil && !tc.expectErr { - t.Errorf("[%v]did not expect error but got: %v", i, err) - } - if !reflect.DeepEqual(out, tc.out) { - t.Errorf("[%v]expected:\n\t%+v\nbut got:\n\t%+v", i, tc.out, out) - } - } -} diff --git a/staging/src/k8s.io/component-helpers/scheduling/corev1/nodeaffinity/BUILD b/staging/src/k8s.io/component-helpers/scheduling/corev1/nodeaffinity/BUILD new file mode 100644 index 00000000000..76427553ee2 --- /dev/null +++ b/staging/src/k8s.io/component-helpers/scheduling/corev1/nodeaffinity/BUILD @@ -0,0 +1,42 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "go_default_library", + srcs = ["nodeaffinity.go"], + importmap = "k8s.io/kubernetes/vendor/k8s.io/component-helpers/scheduling/corev1/nodeaffinity", + importpath = "k8s.io/component-helpers/scheduling/corev1/nodeaffinity", + visibility = ["//visibility:public"], + deps = [ + "//staging/src/k8s.io/api/core/v1:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/fields:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/labels:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/selection:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/util/errors:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) + +go_test( + name = "go_default_test", + srcs = ["nodeaffinity_test.go"], + embed = [":go_default_library"], + deps = [ + "//staging/src/k8s.io/api/core/v1:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/labels:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/util/errors:go_default_library", + ], +) diff --git a/staging/src/k8s.io/component-helpers/scheduling/corev1/nodeaffinity/nodeaffinity.go b/staging/src/k8s.io/component-helpers/scheduling/corev1/nodeaffinity/nodeaffinity.go new file mode 100644 index 00000000000..81189c9ef3a --- /dev/null +++ b/staging/src/k8s.io/component-helpers/scheduling/corev1/nodeaffinity/nodeaffinity.go @@ -0,0 +1,199 @@ +/* +Copyright 2020 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package nodeaffinity + +import ( + "fmt" + + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/fields" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/selection" + "k8s.io/apimachinery/pkg/util/errors" +) + +// NodeSelector is a runtime representation of v1.NodeSelector. +type NodeSelector struct { + lazy LazyErrorNodeSelector +} + +// LazyErrorNodeSelector is a runtime representation of v1.NodeSelector that +// only reports parse errors when no terms match. +type LazyErrorNodeSelector struct { + terms []nodeSelectorTerm +} + +// NewNodeSelector returns a NodeSelector or all parsing errors found. +func NewNodeSelector(ns *v1.NodeSelector) (*NodeSelector, error) { + lazy := NewLazyErrorNodeSelector(ns) + var errs []error + for _, term := range lazy.terms { + if term.parseErr != nil { + errs = append(errs, term.parseErr) + } + } + if len(errs) != 0 { + return nil, errors.NewAggregate(errs) + } + return &NodeSelector{lazy: *lazy}, nil +} + +// NewLazyErrorNodeSelector creates a NodeSelector that only reports parse +// errors when no terms match. +func NewLazyErrorNodeSelector(ns *v1.NodeSelector) *LazyErrorNodeSelector { + parsedTerms := make([]nodeSelectorTerm, 0, len(ns.NodeSelectorTerms)) + for _, term := range ns.NodeSelectorTerms { + // nil or empty term selects no objects + if len(term.MatchExpressions) == 0 && len(term.MatchFields) == 0 { + continue + } + parsedTerms = append(parsedTerms, nodeSelectorTerm{}) + parsedTerm := &parsedTerms[len(parsedTerms)-1] + if len(term.MatchExpressions) != 0 { + parsedTerm.matchLabels, parsedTerm.parseErr = nodeSelectorRequirementsAsSelector(term.MatchExpressions) + if parsedTerm.parseErr != nil { + continue + } + } + if len(term.MatchFields) != 0 { + parsedTerm.matchFields, parsedTerm.parseErr = nodeSelectorRequirementsAsFieldSelector(term.MatchFields) + } + } + return &LazyErrorNodeSelector{ + terms: parsedTerms, + } +} + +// Match checks whether the node labels and fields match the selector terms, ORed; +// nil or empty term matches no objects. +func (ns *NodeSelector) Match(node *v1.Node) bool { + // parse errors are reported in NewNodeSelector. + match, _ := ns.lazy.Match(node) + return match +} + +// Match checks whether the node labels and fields match the selector terms, ORed; +// nil or empty term matches no objects. +// Parse errors are only returned if no terms matched. +func (ns *LazyErrorNodeSelector) Match(node *v1.Node) (bool, error) { + if node == nil { + return false, nil + } + nodeLabels := labels.Set(node.Labels) + nodeFields := make(fields.Set) + if len(node.Name) > 0 { + nodeFields["metadata.name"] = node.Name + } + + var errs []error + for _, term := range ns.terms { + match, err := term.match(nodeLabels, nodeFields) + if err != nil { + errs = append(errs, term.parseErr) + continue + } + if match { + return true, nil + } + } + return false, errors.NewAggregate(errs) +} + +type nodeSelectorTerm struct { + matchLabels labels.Selector + matchFields fields.Selector + parseErr error +} + +func (t *nodeSelectorTerm) match(nodeLabels labels.Set, nodeFields fields.Set) (bool, error) { + if t.parseErr != nil { + return false, t.parseErr + } + if t.matchLabels != nil && !t.matchLabels.Matches(nodeLabels) { + return false, nil + } + if t.matchFields != nil && len(nodeFields) > 0 && !t.matchFields.Matches(nodeFields) { + return false, nil + } + return true, nil +} + +// nodeSelectorRequirementsAsSelector converts the []NodeSelectorRequirement api type into a struct that implements +// labels.Selector. +func nodeSelectorRequirementsAsSelector(nsm []v1.NodeSelectorRequirement) (labels.Selector, error) { + if len(nsm) == 0 { + return labels.Nothing(), nil + } + selector := labels.NewSelector() + for _, expr := range nsm { + var op selection.Operator + switch expr.Operator { + case v1.NodeSelectorOpIn: + op = selection.In + case v1.NodeSelectorOpNotIn: + op = selection.NotIn + case v1.NodeSelectorOpExists: + op = selection.Exists + case v1.NodeSelectorOpDoesNotExist: + op = selection.DoesNotExist + case v1.NodeSelectorOpGt: + op = selection.GreaterThan + case v1.NodeSelectorOpLt: + op = selection.LessThan + default: + return nil, fmt.Errorf("%q is not a valid node selector operator", expr.Operator) + } + r, err := labels.NewRequirement(expr.Key, op, expr.Values) + if err != nil { + return nil, err + } + selector = selector.Add(*r) + } + return selector, nil +} + +// nodeSelectorRequirementsAsFieldSelector converts the []NodeSelectorRequirement core type into a struct that implements +// fields.Selector. +func nodeSelectorRequirementsAsFieldSelector(nsr []v1.NodeSelectorRequirement) (fields.Selector, error) { + if len(nsr) == 0 { + return fields.Nothing(), nil + } + + var selectors []fields.Selector + for _, expr := range nsr { + switch expr.Operator { + case v1.NodeSelectorOpIn: + if len(expr.Values) != 1 { + return nil, fmt.Errorf("unexpected number of value (%d) for node field selector operator %q", + len(expr.Values), expr.Operator) + } + selectors = append(selectors, fields.OneTermEqualSelector(expr.Key, expr.Values[0])) + + case v1.NodeSelectorOpNotIn: + if len(expr.Values) != 1 { + return nil, fmt.Errorf("unexpected number of value (%d) for node field selector operator %q", + len(expr.Values), expr.Operator) + } + selectors = append(selectors, fields.OneTermNotEqualSelector(expr.Key, expr.Values[0])) + + default: + return nil, fmt.Errorf("%q is not a valid node field selector operator", expr.Operator) + } + } + + return fields.AndSelectors(selectors...), nil +} diff --git a/staging/src/k8s.io/component-helpers/scheduling/corev1/nodeaffinity/nodeaffinity_test.go b/staging/src/k8s.io/component-helpers/scheduling/corev1/nodeaffinity/nodeaffinity_test.go new file mode 100644 index 00000000000..65879544bf9 --- /dev/null +++ b/staging/src/k8s.io/component-helpers/scheduling/corev1/nodeaffinity/nodeaffinity_test.go @@ -0,0 +1,215 @@ +/* +Copyright 2020 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package nodeaffinity + +import ( + "errors" + "reflect" + "testing" + + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + apierrors "k8s.io/apimachinery/pkg/util/errors" +) + +func TestNodeSelectorMatch(t *testing.T) { + tests := []struct { + name string + nodeSelector v1.NodeSelector + node *v1.Node + wantErr error + wantMatch bool + }{ + { + name: "nil node", + wantMatch: false, + }, + { + name: "invalid field selector and label selector", + nodeSelector: v1.NodeSelector{NodeSelectorTerms: []v1.NodeSelectorTerm{ + { + MatchFields: []v1.NodeSelectorRequirement{{ + Key: "metadata.name", + Operator: v1.NodeSelectorOpIn, + Values: []string{"host_1", "host_2"}, + }}, + }, + { + MatchExpressions: []v1.NodeSelectorRequirement{{ + Key: "label_1", + Operator: v1.NodeSelectorOpIn, + Values: []string{"label_1_val"}, + }}, + MatchFields: []v1.NodeSelectorRequirement{{ + Key: "metadata.name", + Operator: v1.NodeSelectorOpIn, + Values: []string{"host_1"}, + }}, + }, + { + MatchExpressions: []v1.NodeSelectorRequirement{{ + Key: "invalid key", + Operator: v1.NodeSelectorOpIn, + Values: []string{"label_value"}, + }}, + }, + }}, + wantErr: apierrors.NewAggregate([]error{ + errors.New(`unexpected number of value (2) for node field selector operator "In"`), + errors.New(`invalid label key "invalid key": name part must consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character (e.g. 'MyName', or 'my.name', or '123-abc', regex used for validation is '([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]')`), + }), + }, + { + name: "node matches field selector, but not labels", + nodeSelector: v1.NodeSelector{NodeSelectorTerms: []v1.NodeSelectorTerm{ + { + MatchExpressions: []v1.NodeSelectorRequirement{{ + Key: "label_1", + Operator: v1.NodeSelectorOpIn, + Values: []string{"label_1_val"}, + }}, + MatchFields: []v1.NodeSelectorRequirement{{ + Key: "metadata.name", + Operator: v1.NodeSelectorOpIn, + Values: []string{"host_1"}, + }}, + }, + }}, + node: &v1.Node{ObjectMeta: metav1.ObjectMeta{Name: "host_1"}}, + }, + { + name: "node matches field selector and label selector", + nodeSelector: v1.NodeSelector{NodeSelectorTerms: []v1.NodeSelectorTerm{ + { + MatchExpressions: []v1.NodeSelectorRequirement{{ + Key: "label_1", + Operator: v1.NodeSelectorOpIn, + Values: []string{"label_1_val"}, + }}, + MatchFields: []v1.NodeSelectorRequirement{{ + Key: "metadata.name", + Operator: v1.NodeSelectorOpIn, + Values: []string{"host_1"}, + }}, + }, + }}, + node: &v1.Node{ObjectMeta: metav1.ObjectMeta{Name: "host_1", Labels: map[string]string{"label_1": "label_1_val"}}}, + wantMatch: true, + }, + { + name: "second term matches", + nodeSelector: v1.NodeSelector{NodeSelectorTerms: []v1.NodeSelectorTerm{ + { + MatchExpressions: []v1.NodeSelectorRequirement{{ + Key: "label_1", + Operator: v1.NodeSelectorOpIn, + Values: []string{"label_1_val"}, + }}, + }, + { + MatchFields: []v1.NodeSelectorRequirement{{ + Key: "metadata.name", + Operator: v1.NodeSelectorOpIn, + Values: []string{"host_1"}, + }}, + }, + }}, + node: &v1.Node{ObjectMeta: metav1.ObjectMeta{Name: "host_1"}}, + wantMatch: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + nodeSelector, err := NewNodeSelector(&tt.nodeSelector) + if !reflect.DeepEqual(err, tt.wantErr) { + t.Fatalf("NewNodeSelector returned error %q, want %q", err, tt.wantErr) + } + if tt.wantErr != nil { + return + } + match := nodeSelector.Match(tt.node) + if match != tt.wantMatch { + t.Errorf("NodeSelector.Match returned %t, want %t", match, tt.wantMatch) + } + }) + } +} + +func TestNodeSelectorRequirementsAsSelector(t *testing.T) { + matchExpressions := []v1.NodeSelectorRequirement{{ + Key: "foo", + Operator: v1.NodeSelectorOpIn, + Values: []string{"bar", "baz"}, + }} + mustParse := func(s string) labels.Selector { + out, e := labels.Parse(s) + if e != nil { + panic(e) + } + return out + } + tc := []struct { + in []v1.NodeSelectorRequirement + out labels.Selector + expectErr bool + }{ + {in: nil, out: labels.Nothing()}, + {in: []v1.NodeSelectorRequirement{}, out: labels.Nothing()}, + { + in: matchExpressions, + out: mustParse("foo in (baz,bar)"), + }, + { + in: []v1.NodeSelectorRequirement{{ + Key: "foo", + Operator: v1.NodeSelectorOpExists, + Values: []string{"bar", "baz"}, + }}, + expectErr: true, + }, + { + in: []v1.NodeSelectorRequirement{{ + Key: "foo", + Operator: v1.NodeSelectorOpGt, + Values: []string{"1"}, + }}, + out: mustParse("foo>1"), + }, + { + in: []v1.NodeSelectorRequirement{{ + Key: "bar", + Operator: v1.NodeSelectorOpLt, + Values: []string{"7"}, + }}, + out: mustParse("bar<7"), + }, + } + + for i, tc := range tc { + out, err := nodeSelectorRequirementsAsSelector(tc.in) + if err == nil && tc.expectErr { + t.Errorf("[%v]expected error but got none.", i) + } + if err != nil && !tc.expectErr { + t.Errorf("[%v]did not expect error but got: %v", i, err) + } + if !reflect.DeepEqual(out, tc.out) { + t.Errorf("[%v]expected:\n\t%+v\nbut got:\n\t%+v", i, tc.out, out) + } + } +} diff --git a/vendor/modules.txt b/vendor/modules.txt index ae4508bce5e..4e4d4be967f 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -2193,6 +2193,7 @@ k8s.io/component-base/version/verflag # k8s.io/component-helpers => ./staging/src/k8s.io/component-helpers k8s.io/component-helpers/lease k8s.io/component-helpers/scheduling/corev1 +k8s.io/component-helpers/scheduling/corev1/nodeaffinity # k8s.io/controller-manager v0.0.0 => ./staging/src/k8s.io/controller-manager ## explicit # k8s.io/controller-manager => ./staging/src/k8s.io/controller-manager