From b7ba77c25b6a2c2c6bd0ed9fee5b125f98672946 Mon Sep 17 00:00:00 2001 From: Mike Dame Date: Thu, 29 Oct 2020 13:39:13 -0400 Subject: [PATCH] Move MatchNodeSelectorTerms to k8s.io/component-helpers --- pkg/apis/core/v1/helper/BUILD | 3 - pkg/apis/core/v1/helper/helpers.go | 112 ---- pkg/apis/core/v1/helper/helpers_test.go | 551 ----------------- pkg/scheduler/framework/plugins/helper/BUILD | 2 +- .../framework/plugins/helper/node_affinity.go | 4 +- .../framework/plugins/nodeaffinity/BUILD | 2 +- .../plugins/nodeaffinity/node_affinity.go | 4 +- pkg/volume/util/BUILD | 1 + pkg/volume/util/util.go | 3 +- .../component-helpers/scheduling/corev1/BUILD | 15 +- .../scheduling/corev1/helpers.go | 116 ++++ .../scheduling/corev1/helpers_test.go | 554 ++++++++++++++++++ 12 files changed, 692 insertions(+), 675 deletions(-) diff --git a/pkg/apis/core/v1/helper/BUILD b/pkg/apis/core/v1/helper/BUILD index 0a5c0649606..0067d3d5270 100644 --- a/pkg/apis/core/v1/helper/BUILD +++ b/pkg/apis/core/v1/helper/BUILD @@ -12,7 +12,6 @@ go_test( embed = [":go_default_library"], deps = [ "//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/api/resource: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", @@ -27,10 +26,8 @@ go_library( "//pkg/apis/core/helper:go_default_library", "//staging/src/k8s.io/api/core/v1:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/api/resource: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/apimachinery/pkg/util/validation:go_default_library", ], ) diff --git a/pkg/apis/core/v1/helper/helpers.go b/pkg/apis/core/v1/helper/helpers.go index 0d737e0ade3..a004af4f291 100644 --- a/pkg/apis/core/v1/helper/helpers.go +++ b/pkg/apis/core/v1/helper/helpers.go @@ -23,10 +23,8 @@ import ( v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" - "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/selection" - apierrors "k8s.io/apimachinery/pkg/util/errors" "k8s.io/apimachinery/pkg/util/validation" "k8s.io/kubernetes/pkg/apis/core/helper" ) @@ -233,72 +231,6 @@ func containsAccessMode(modes []v1.PersistentVolumeAccessMode, mode v1.Persisten return false } -// 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 -} - // NodeSelectorRequirementKeysExistInNodeSelectorTerms checks if a NodeSelectorTerm with key is already specified in terms func NodeSelectorRequirementKeysExistInNodeSelectorTerms(reqs []v1.NodeSelectorRequirement, terms []v1.NodeSelectorTerm) bool { for _, req := range reqs { @@ -313,50 +245,6 @@ func NodeSelectorRequirementKeysExistInNodeSelectorTerms(reqs []v1.NodeSelectorR return false } -// MatchNodeSelectorTerms checks whether the node labels and fields match node selector terms in ORed; -// nil or empty term matches no objects. -func MatchNodeSelectorTerms( - node *v1.Node, - nodeSelector *v1.NodeSelector, -) (bool, error) { - 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) -} - // TopologySelectorRequirementsAsSelector converts the []TopologySelectorLabelRequirement api type into a struct // that implements labels.Selector. func TopologySelectorRequirementsAsSelector(tsm []v1.TopologySelectorLabelRequirement) (labels.Selector, error) { diff --git a/pkg/apis/core/v1/helper/helpers_test.go b/pkg/apis/core/v1/helper/helpers_test.go index 6063a9950a5..dcd4e8b175a 100644 --- a/pkg/apis/core/v1/helper/helpers_test.go +++ b/pkg/apis/core/v1/helper/helpers_test.go @@ -22,7 +22,6 @@ import ( "testing" v1 "k8s.io/api/core/v1" - apiequality "k8s.io/apimachinery/pkg/api/equality" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" @@ -246,70 +245,6 @@ func TestRemoveDuplicateAccessModes(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) - } - } -} - func TestTopologySelectorRequirementsAsSelector(t *testing.T) { mustParse := func(s string) labels.Selector { out, e := labels.Parse(s) @@ -591,492 +526,6 @@ func TestGetAvoidPodsFromNode(t *testing.T) { } } -func TestMatchNodeSelectorTerms(t *testing.T) { - type args struct { - nodeSelector *v1.NodeSelector - node *v1.Node - } - - tests := []struct { - name string - args args - want bool - }{ - { - name: "nil terms", - args: args{ - nodeSelector: nil, - node: nil, - }, - want: false, - }, - { - name: "node label matches matchExpressions terms", - args: args{ - nodeSelector: &v1.NodeSelector{NodeSelectorTerms: []v1.NodeSelectorTerm{ - { - MatchExpressions: []v1.NodeSelectorRequirement{{ - Key: "label_1", - Operator: v1.NodeSelectorOpIn, - Values: []string{"label_1_val"}, - }}, - }, - }}, - node: &v1.Node{ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"label_1": "label_1_val"}}}, - }, - want: true, - }, - { - name: "node field matches matchFields terms", - args: args{ - nodeSelector: &v1.NodeSelector{NodeSelectorTerms: []v1.NodeSelectorTerm{ - { - MatchFields: []v1.NodeSelectorRequirement{{ - Key: "metadata.name", - Operator: v1.NodeSelectorOpIn, - Values: []string{"host_1"}, - }}, - }, - }}, - node: &v1.Node{ObjectMeta: metav1.ObjectMeta{Name: "host_1"}}, - }, - want: true, - }, - { - name: "invalid node field requirement", - args: args{ - nodeSelector: &v1.NodeSelector{NodeSelectorTerms: []v1.NodeSelectorTerm{ - { - MatchFields: []v1.NodeSelectorRequirement{{ - Key: "metadata.name", - Operator: v1.NodeSelectorOpIn, - Values: []string{"host_1, host_2"}, - }}, - }, - }}, - node: &v1.Node{ObjectMeta: metav1.ObjectMeta{Name: "host_1"}}, - }, - want: false, - }, - { - name: "fieldSelectorTerm with node labels", - args: args{ - nodeSelector: &v1.NodeSelector{NodeSelectorTerms: []v1.NodeSelectorTerm{ - { - MatchFields: []v1.NodeSelectorRequirement{{ - Key: "metadata.name", - Operator: v1.NodeSelectorOpIn, - Values: []string{"host_1"}, - }}, - }, - }}, - node: &v1.Node{ObjectMeta: metav1.ObjectMeta{Name: "not_host_1", Labels: map[string]string{ - "metadata.name": "host_1", - }}}, - }, - want: false, - }, - { - name: "labelSelectorTerm with node fields", - args: args{ - nodeSelector: &v1.NodeSelector{NodeSelectorTerms: []v1.NodeSelectorTerm{ - { - MatchExpressions: []v1.NodeSelectorRequirement{{ - Key: "metadata.name", - Operator: v1.NodeSelectorOpIn, - Values: []string{"host_1"}, - }}, - }, - }}, - node: &v1.Node{ObjectMeta: metav1.ObjectMeta{Name: "host_1"}}, - }, - want: false, - }, - { - name: "labelSelectorTerm and fieldSelectorTerm was set, but only node fields", - args: args{ - 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"}}, - }, - want: false, - }, - { - name: "labelSelectorTerm and fieldSelectorTerm was set, both node fields and labels (both matched)", - args: args{ - 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", - }}}, - }, - want: true, - }, - { - name: "labelSelectorTerm and fieldSelectorTerm was set, both node fields and labels (one mismatched)", - args: args{ - 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-failed", - }}}, - }, - want: false, - }, - { - name: "multi-selector was set, both node fields and labels (one mismatched)", - args: args{ - 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-failed", - }}}, - }, - want: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got, _ := MatchNodeSelectorTerms(tt.args.node, tt.args.nodeSelector); got != tt.want { - t.Errorf("MatchNodeSelectorTermsORed() = %v, want %v", got, tt.want) - } - }) - } -} - -// TestMatchNodeSelectorTermsStateless ensures MatchNodeSelectorTerms() -// is invoked in a "stateless" manner, i.e. nodeSelector should NOT -// be deeply modified after invoking -func TestMatchNodeSelectorTermsStateless(t *testing.T) { - type args struct { - nodeSelector *v1.NodeSelector - node *v1.Node - } - - tests := []struct { - name string - args args - want *v1.NodeSelector - }{ - { - name: "nil terms", - args: args{ - nodeSelector: nil, - node: nil, - }, - want: nil, - }, - { - name: "nodeLabels: preordered matchExpressions and nil matchFields", - args: args{ - nodeSelector: &v1.NodeSelector{NodeSelectorTerms: []v1.NodeSelectorTerm{ - { - MatchExpressions: []v1.NodeSelectorRequirement{{ - Key: "label_1", - Operator: v1.NodeSelectorOpIn, - Values: []string{"label_1_val", "label_2_val"}, - }}, - }, - }}, - node: &v1.Node{ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"label_1": "label_1_val"}}}, - }, - want: &v1.NodeSelector{NodeSelectorTerms: []v1.NodeSelectorTerm{ - { - MatchExpressions: []v1.NodeSelectorRequirement{{ - Key: "label_1", - Operator: v1.NodeSelectorOpIn, - Values: []string{"label_1_val", "label_2_val"}, - }}, - }, - }}, - }, - { - name: "nodeLabels: unordered matchExpressions and nil matchFields", - args: args{ - nodeSelector: &v1.NodeSelector{NodeSelectorTerms: []v1.NodeSelectorTerm{ - { - MatchExpressions: []v1.NodeSelectorRequirement{{ - Key: "label_1", - Operator: v1.NodeSelectorOpIn, - Values: []string{"label_2_val", "label_1_val"}, - }}, - }, - }}, - node: &v1.Node{ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"label_1": "label_1_val"}}}, - }, - want: &v1.NodeSelector{NodeSelectorTerms: []v1.NodeSelectorTerm{ - { - MatchExpressions: []v1.NodeSelectorRequirement{{ - Key: "label_1", - Operator: v1.NodeSelectorOpIn, - Values: []string{"label_2_val", "label_1_val"}, - }}, - }, - }}, - }, - { - name: "nodeFields: nil matchExpressions and preordered matchFields", - args: args{ - nodeSelector: &v1.NodeSelector{NodeSelectorTerms: []v1.NodeSelectorTerm{ - { - MatchFields: []v1.NodeSelectorRequirement{{ - Key: "metadata.name", - Operator: v1.NodeSelectorOpIn, - Values: []string{"host_1", "host_2"}, - }}, - }, - }}, - node: &v1.Node{ObjectMeta: metav1.ObjectMeta{Name: "host_1"}}, - }, - want: &v1.NodeSelector{NodeSelectorTerms: []v1.NodeSelectorTerm{ - { - MatchFields: []v1.NodeSelectorRequirement{{ - Key: "metadata.name", - Operator: v1.NodeSelectorOpIn, - Values: []string{"host_1", "host_2"}, - }}, - }, - }}, - }, - { - name: "nodeFields: nil matchExpressions and unordered matchFields", - args: args{ - nodeSelector: &v1.NodeSelector{NodeSelectorTerms: []v1.NodeSelectorTerm{ - { - MatchFields: []v1.NodeSelectorRequirement{{ - Key: "metadata.name", - Operator: v1.NodeSelectorOpIn, - Values: []string{"host_2", "host_1"}, - }}, - }, - }}, - node: &v1.Node{ObjectMeta: metav1.ObjectMeta{Name: "host_1"}}, - }, - want: &v1.NodeSelector{NodeSelectorTerms: []v1.NodeSelectorTerm{ - { - MatchFields: []v1.NodeSelectorRequirement{{ - Key: "metadata.name", - Operator: v1.NodeSelectorOpIn, - Values: []string{"host_2", "host_1"}, - }}, - }, - }}, - }, - { - name: "nodeLabels and nodeFields: ordered matchExpressions and ordered matchFields", - args: args{ - nodeSelector: &v1.NodeSelector{NodeSelectorTerms: []v1.NodeSelectorTerm{ - { - MatchExpressions: []v1.NodeSelectorRequirement{{ - Key: "label_1", - Operator: v1.NodeSelectorOpIn, - Values: []string{"label_1_val", "label_2_val"}, - }}, - MatchFields: []v1.NodeSelectorRequirement{{ - Key: "metadata.name", - Operator: v1.NodeSelectorOpIn, - Values: []string{"host_1", "host_2"}, - }}, - }, - }}, - node: &v1.Node{ObjectMeta: metav1.ObjectMeta{Name: "host_1", - Labels: map[string]string{ - "label_1": "label_1_val", - }}}, - }, - want: &v1.NodeSelector{NodeSelectorTerms: []v1.NodeSelectorTerm{ - { - MatchExpressions: []v1.NodeSelectorRequirement{{ - Key: "label_1", - Operator: v1.NodeSelectorOpIn, - Values: []string{"label_1_val", "label_2_val"}, - }}, - MatchFields: []v1.NodeSelectorRequirement{{ - Key: "metadata.name", - Operator: v1.NodeSelectorOpIn, - Values: []string{"host_1", "host_2"}, - }}, - }, - }}, - }, - { - name: "nodeLabels and nodeFields: ordered matchExpressions and unordered matchFields", - args: args{ - nodeSelector: &v1.NodeSelector{NodeSelectorTerms: []v1.NodeSelectorTerm{ - { - MatchExpressions: []v1.NodeSelectorRequirement{{ - Key: "label_1", - Operator: v1.NodeSelectorOpIn, - Values: []string{"label_1_val", "label_2_val"}, - }}, - MatchFields: []v1.NodeSelectorRequirement{{ - Key: "metadata.name", - Operator: v1.NodeSelectorOpIn, - Values: []string{"host_2", "host_1"}, - }}, - }, - }}, - node: &v1.Node{ObjectMeta: metav1.ObjectMeta{Name: "host_1", - Labels: map[string]string{ - "label_1": "label_1_val", - }}}, - }, - want: &v1.NodeSelector{NodeSelectorTerms: []v1.NodeSelectorTerm{ - { - MatchExpressions: []v1.NodeSelectorRequirement{{ - Key: "label_1", - Operator: v1.NodeSelectorOpIn, - Values: []string{"label_1_val", "label_2_val"}, - }}, - MatchFields: []v1.NodeSelectorRequirement{{ - Key: "metadata.name", - Operator: v1.NodeSelectorOpIn, - Values: []string{"host_2", "host_1"}, - }}, - }, - }}, - }, - { - name: "nodeLabels and nodeFields: unordered matchExpressions and ordered matchFields", - args: args{ - nodeSelector: &v1.NodeSelector{NodeSelectorTerms: []v1.NodeSelectorTerm{ - { - MatchExpressions: []v1.NodeSelectorRequirement{{ - Key: "label_1", - Operator: v1.NodeSelectorOpIn, - Values: []string{"label_2_val", "label_1_val"}, - }}, - MatchFields: []v1.NodeSelectorRequirement{{ - Key: "metadata.name", - Operator: v1.NodeSelectorOpIn, - Values: []string{"host_1", "host_2"}, - }}, - }, - }}, - node: &v1.Node{ObjectMeta: metav1.ObjectMeta{Name: "host_1", - Labels: map[string]string{ - "label_1": "label_1_val", - }}}, - }, - want: &v1.NodeSelector{NodeSelectorTerms: []v1.NodeSelectorTerm{ - { - MatchExpressions: []v1.NodeSelectorRequirement{{ - Key: "label_1", - Operator: v1.NodeSelectorOpIn, - Values: []string{"label_2_val", "label_1_val"}, - }}, - MatchFields: []v1.NodeSelectorRequirement{{ - Key: "metadata.name", - Operator: v1.NodeSelectorOpIn, - Values: []string{"host_1", "host_2"}, - }}, - }, - }}, - }, - { - name: "nodeLabels and nodeFields: unordered matchExpressions and unordered matchFields", - args: args{ - nodeSelector: &v1.NodeSelector{NodeSelectorTerms: []v1.NodeSelectorTerm{ - { - MatchExpressions: []v1.NodeSelectorRequirement{{ - Key: "label_1", - Operator: v1.NodeSelectorOpIn, - Values: []string{"label_2_val", "label_1_val"}, - }}, - MatchFields: []v1.NodeSelectorRequirement{{ - Key: "metadata.name", - Operator: v1.NodeSelectorOpIn, - Values: []string{"host_2", "host_1"}, - }}, - }, - }}, - node: &v1.Node{ObjectMeta: metav1.ObjectMeta{Name: "host_1", - Labels: map[string]string{ - "label_1": "label_1_val", - }}}, - }, - want: &v1.NodeSelector{NodeSelectorTerms: []v1.NodeSelectorTerm{ - { - MatchExpressions: []v1.NodeSelectorRequirement{{ - Key: "label_1", - Operator: v1.NodeSelectorOpIn, - Values: []string{"label_2_val", "label_1_val"}, - }}, - MatchFields: []v1.NodeSelectorRequirement{{ - Key: "metadata.name", - Operator: v1.NodeSelectorOpIn, - Values: []string{"host_2", "host_1"}, - }}, - }, - }}, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - 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) - } - }) - } -} - func TestMatchTopologySelectorTerms(t *testing.T) { type args struct { topologySelectorTerms []v1.TopologySelectorTerm diff --git a/pkg/scheduler/framework/plugins/helper/BUILD b/pkg/scheduler/framework/plugins/helper/BUILD index 07ab13eeeb7..7a99d65d061 100644 --- a/pkg/scheduler/framework/plugins/helper/BUILD +++ b/pkg/scheduler/framework/plugins/helper/BUILD @@ -10,13 +10,13 @@ go_library( importpath = "k8s.io/kubernetes/pkg/scheduler/framework/plugins/helper", visibility = ["//visibility:public"], deps = [ - "//pkg/apis/core/v1/helper:go_default_library", "//pkg/scheduler/framework:go_default_library", "//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/client-go/listers/apps/v1:go_default_library", "//staging/src/k8s.io/client-go/listers/core/v1:go_default_library", + "//staging/src/k8s.io/component-helpers/scheduling/corev1:go_default_library", ], ) diff --git a/pkg/scheduler/framework/plugins/helper/node_affinity.go b/pkg/scheduler/framework/plugins/helper/node_affinity.go index d013f534f1c..e5b3f1f885d 100644 --- a/pkg/scheduler/framework/plugins/helper/node_affinity.go +++ b/pkg/scheduler/framework/plugins/helper/node_affinity.go @@ -19,7 +19,7 @@ package helper import ( "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/labels" - v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper" + "k8s.io/component-helpers/scheduling/corev1" ) // PodMatchesNodeSelectorAndAffinityTerms checks whether the pod is schedulable onto nodes according to @@ -71,6 +71,6 @@ func PodMatchesNodeSelectorAndAffinityTerms(pod *v1.Pod, node *v1.Node) bool { // terms are ORed, and an empty list of terms will match nothing. func nodeMatchesNodeSelectorTerms(node *v1.Node, nodeSelector *v1.NodeSelector) bool { // TODO(@alculquicondor, #95738): parse this error earlier in the plugin so we only need to do it once per pod - matches, _ := v1helper.MatchNodeSelectorTerms(node, nodeSelector) + matches, _ := corev1.MatchNodeSelectorTerms(node, nodeSelector) return matches } diff --git a/pkg/scheduler/framework/plugins/nodeaffinity/BUILD b/pkg/scheduler/framework/plugins/nodeaffinity/BUILD index 51fc205f819..ea313c522d2 100644 --- a/pkg/scheduler/framework/plugins/nodeaffinity/BUILD +++ b/pkg/scheduler/framework/plugins/nodeaffinity/BUILD @@ -6,11 +6,11 @@ go_library( importpath = "k8s.io/kubernetes/pkg/scheduler/framework/plugins/nodeaffinity", visibility = ["//visibility:public"], deps = [ - "//pkg/apis/core/v1/helper:go_default_library", "//pkg/scheduler/framework:go_default_library", "//pkg/scheduler/framework/plugins/helper:go_default_library", "//staging/src/k8s.io/api/core/v1:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//staging/src/k8s.io/component-helpers/scheduling/corev1:go_default_library", ], ) diff --git a/pkg/scheduler/framework/plugins/nodeaffinity/node_affinity.go b/pkg/scheduler/framework/plugins/nodeaffinity/node_affinity.go index 6dabcd2496d..466a2594f2c 100644 --- a/pkg/scheduler/framework/plugins/nodeaffinity/node_affinity.go +++ b/pkg/scheduler/framework/plugins/nodeaffinity/node_affinity.go @@ -22,7 +22,7 @@ import ( v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime" - v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper" + "k8s.io/component-helpers/scheduling/corev1" "k8s.io/kubernetes/pkg/scheduler/framework" pluginhelper "k8s.io/kubernetes/pkg/scheduler/framework/plugins/helper" ) @@ -87,7 +87,7 @@ func (pl *NodeAffinity) Score(ctx context.Context, state *framework.CycleState, } // TODO: Avoid computing it for all nodes if this becomes a performance problem. - matches, err := v1helper.MatchNodeSelectorTerms(node, &v1.NodeSelector{NodeSelectorTerms: []v1.NodeSelectorTerm{preferredSchedulingTerm.Preference}}) + matches, err := corev1.MatchNodeSelectorTerms(node, &v1.NodeSelector{NodeSelectorTerms: []v1.NodeSelectorTerm{preferredSchedulingTerm.Preference}}) if err != nil { return 0, framework.AsStatus(err) } diff --git a/pkg/volume/util/BUILD b/pkg/volume/util/BUILD index e08e17b6a3f..9a80b86728c 100644 --- a/pkg/volume/util/BUILD +++ b/pkg/volume/util/BUILD @@ -40,6 +40,7 @@ go_library( "//staging/src/k8s.io/client-go/kubernetes:go_default_library", "//staging/src/k8s.io/component-base/metrics:go_default_library", "//staging/src/k8s.io/component-base/metrics/legacyregistry:go_default_library", + "//staging/src/k8s.io/component-helpers/scheduling/corev1:go_default_library", "//staging/src/k8s.io/mount-utils:go_default_library", "//vendor/k8s.io/klog/v2:go_default_library", "//vendor/k8s.io/utils/exec:go_default_library", diff --git a/pkg/volume/util/util.go b/pkg/volume/util/util.go index a4538769f9f..367e05a0d1a 100644 --- a/pkg/volume/util/util.go +++ b/pkg/volume/util/util.go @@ -26,6 +26,7 @@ import ( "runtime" "strings" + "k8s.io/component-helpers/scheduling/corev1" "k8s.io/klog/v2" "k8s.io/mount-utils" utilexec "k8s.io/utils/exec" @@ -175,7 +176,7 @@ func checkVolumeNodeAffinity(pv *v1.PersistentVolume, node *v1.Node) error { if pv.Spec.NodeAffinity.Required != nil { terms := pv.Spec.NodeAffinity.Required klog.V(10).Infof("Match for Required node selector terms %+v", terms) - if matches, err := v1helper.MatchNodeSelectorTerms(node, terms); err != nil { + if matches, err := corev1.MatchNodeSelectorTerms(node, terms); err != nil { return err } else if !matches { return fmt.Errorf("no matching NodeSelectorTerms") diff --git a/staging/src/k8s.io/component-helpers/scheduling/corev1/BUILD b/staging/src/k8s.io/component-helpers/scheduling/corev1/BUILD index acc8f9c23c0..0ad20d7aa5d 100644 --- a/staging/src/k8s.io/component-helpers/scheduling/corev1/BUILD +++ b/staging/src/k8s.io/component-helpers/scheduling/corev1/BUILD @@ -9,14 +9,25 @@ go_library( importmap = "k8s.io/kubernetes/vendor/k8s.io/component-helpers/scheduling/corev1", importpath = "k8s.io/component-helpers/scheduling/corev1", visibility = ["//visibility:public"], - deps = ["//staging/src/k8s.io/api/core/v1:go_default_library"], + 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", + ], ) go_test( name = "go_default_test", srcs = ["helpers_test.go"], embed = [":go_default_library"], - deps = ["//staging/src/k8s.io/api/core/v1:go_default_library"], + deps = [ + "//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", + ], ) filegroup( 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 688a9218cc5..5159b892502 100644 --- a/staging/src/k8s.io/component-helpers/scheduling/corev1/helpers.go +++ b/staging/src/k8s.io/component-helpers/scheduling/corev1/helpers.go @@ -17,7 +17,13 @@ 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" ) // PodPriority returns priority of the given pod. @@ -30,3 +36,113 @@ func PodPriority(pod *v1.Pod) int32 { // name of the pod was empty. So, we resolve to the static default priority. return 0 } + +// MatchNodeSelectorTerms checks whether the node labels and fields match node selector terms in ORed; +// nil or empty term matches no objects. +func MatchNodeSelectorTerms( + node *v1.Node, + nodeSelector *v1.NodeSelector, +) (bool, error) { + 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 +} 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 3a3249c03d3..195f09532a4 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,9 +17,13 @@ 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. @@ -57,3 +61,553 @@ func TestPodPriority(t *testing.T) { } } + +func TestMatchNodeSelectorTerms(t *testing.T) { + type args struct { + nodeSelector *v1.NodeSelector + node *v1.Node + } + + tests := []struct { + name string + args args + want bool + }{ + { + name: "nil terms", + args: args{ + nodeSelector: nil, + node: nil, + }, + want: false, + }, + { + name: "node label matches matchExpressions terms", + args: args{ + nodeSelector: &v1.NodeSelector{NodeSelectorTerms: []v1.NodeSelectorTerm{ + { + MatchExpressions: []v1.NodeSelectorRequirement{{ + Key: "label_1", + Operator: v1.NodeSelectorOpIn, + Values: []string{"label_1_val"}, + }}, + }, + }}, + node: &v1.Node{ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"label_1": "label_1_val"}}}, + }, + want: true, + }, + { + name: "node field matches matchFields terms", + args: args{ + nodeSelector: &v1.NodeSelector{NodeSelectorTerms: []v1.NodeSelectorTerm{ + { + MatchFields: []v1.NodeSelectorRequirement{{ + Key: "metadata.name", + Operator: v1.NodeSelectorOpIn, + Values: []string{"host_1"}, + }}, + }, + }}, + node: &v1.Node{ObjectMeta: metav1.ObjectMeta{Name: "host_1"}}, + }, + want: true, + }, + { + name: "invalid node field requirement", + args: args{ + nodeSelector: &v1.NodeSelector{NodeSelectorTerms: []v1.NodeSelectorTerm{ + { + MatchFields: []v1.NodeSelectorRequirement{{ + Key: "metadata.name", + Operator: v1.NodeSelectorOpIn, + Values: []string{"host_1, host_2"}, + }}, + }, + }}, + node: &v1.Node{ObjectMeta: metav1.ObjectMeta{Name: "host_1"}}, + }, + want: false, + }, + { + name: "fieldSelectorTerm with node labels", + args: args{ + nodeSelector: &v1.NodeSelector{NodeSelectorTerms: []v1.NodeSelectorTerm{ + { + MatchFields: []v1.NodeSelectorRequirement{{ + Key: "metadata.name", + Operator: v1.NodeSelectorOpIn, + Values: []string{"host_1"}, + }}, + }, + }}, + node: &v1.Node{ObjectMeta: metav1.ObjectMeta{Name: "not_host_1", Labels: map[string]string{ + "metadata.name": "host_1", + }}}, + }, + want: false, + }, + { + name: "labelSelectorTerm with node fields", + args: args{ + nodeSelector: &v1.NodeSelector{NodeSelectorTerms: []v1.NodeSelectorTerm{ + { + MatchExpressions: []v1.NodeSelectorRequirement{{ + Key: "metadata.name", + Operator: v1.NodeSelectorOpIn, + Values: []string{"host_1"}, + }}, + }, + }}, + node: &v1.Node{ObjectMeta: metav1.ObjectMeta{Name: "host_1"}}, + }, + want: false, + }, + { + name: "labelSelectorTerm and fieldSelectorTerm was set, but only node fields", + args: args{ + 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"}}, + }, + want: false, + }, + { + name: "labelSelectorTerm and fieldSelectorTerm was set, both node fields and labels (both matched)", + args: args{ + 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", + }}}, + }, + want: true, + }, + { + name: "labelSelectorTerm and fieldSelectorTerm was set, both node fields and labels (one mismatched)", + args: args{ + 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-failed", + }}}, + }, + want: false, + }, + { + name: "multi-selector was set, both node fields and labels (one mismatched)", + args: args{ + 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-failed", + }}}, + }, + want: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got, _ := MatchNodeSelectorTerms(tt.args.node, tt.args.nodeSelector); got != tt.want { + t.Errorf("MatchNodeSelectorTermsORed() = %v, want %v", got, tt.want) + } + }) + } +} + +// TestMatchNodeSelectorTermsStateless ensures MatchNodeSelectorTerms() +// is invoked in a "stateless" manner, i.e. nodeSelector should NOT +// be deeply modified after invoking +func TestMatchNodeSelectorTermsStateless(t *testing.T) { + type args struct { + nodeSelector *v1.NodeSelector + node *v1.Node + } + + tests := []struct { + name string + args args + want *v1.NodeSelector + }{ + { + name: "nil terms", + args: args{ + nodeSelector: nil, + node: nil, + }, + want: nil, + }, + { + name: "nodeLabels: preordered matchExpressions and nil matchFields", + args: args{ + nodeSelector: &v1.NodeSelector{NodeSelectorTerms: []v1.NodeSelectorTerm{ + { + MatchExpressions: []v1.NodeSelectorRequirement{{ + Key: "label_1", + Operator: v1.NodeSelectorOpIn, + Values: []string{"label_1_val", "label_2_val"}, + }}, + }, + }}, + node: &v1.Node{ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"label_1": "label_1_val"}}}, + }, + want: &v1.NodeSelector{NodeSelectorTerms: []v1.NodeSelectorTerm{ + { + MatchExpressions: []v1.NodeSelectorRequirement{{ + Key: "label_1", + Operator: v1.NodeSelectorOpIn, + Values: []string{"label_1_val", "label_2_val"}, + }}, + }, + }}, + }, + { + name: "nodeLabels: unordered matchExpressions and nil matchFields", + args: args{ + nodeSelector: &v1.NodeSelector{NodeSelectorTerms: []v1.NodeSelectorTerm{ + { + MatchExpressions: []v1.NodeSelectorRequirement{{ + Key: "label_1", + Operator: v1.NodeSelectorOpIn, + Values: []string{"label_2_val", "label_1_val"}, + }}, + }, + }}, + node: &v1.Node{ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"label_1": "label_1_val"}}}, + }, + want: &v1.NodeSelector{NodeSelectorTerms: []v1.NodeSelectorTerm{ + { + MatchExpressions: []v1.NodeSelectorRequirement{{ + Key: "label_1", + Operator: v1.NodeSelectorOpIn, + Values: []string{"label_2_val", "label_1_val"}, + }}, + }, + }}, + }, + { + name: "nodeFields: nil matchExpressions and preordered matchFields", + args: args{ + nodeSelector: &v1.NodeSelector{NodeSelectorTerms: []v1.NodeSelectorTerm{ + { + MatchFields: []v1.NodeSelectorRequirement{{ + Key: "metadata.name", + Operator: v1.NodeSelectorOpIn, + Values: []string{"host_1", "host_2"}, + }}, + }, + }}, + node: &v1.Node{ObjectMeta: metav1.ObjectMeta{Name: "host_1"}}, + }, + want: &v1.NodeSelector{NodeSelectorTerms: []v1.NodeSelectorTerm{ + { + MatchFields: []v1.NodeSelectorRequirement{{ + Key: "metadata.name", + Operator: v1.NodeSelectorOpIn, + Values: []string{"host_1", "host_2"}, + }}, + }, + }}, + }, + { + name: "nodeFields: nil matchExpressions and unordered matchFields", + args: args{ + nodeSelector: &v1.NodeSelector{NodeSelectorTerms: []v1.NodeSelectorTerm{ + { + MatchFields: []v1.NodeSelectorRequirement{{ + Key: "metadata.name", + Operator: v1.NodeSelectorOpIn, + Values: []string{"host_2", "host_1"}, + }}, + }, + }}, + node: &v1.Node{ObjectMeta: metav1.ObjectMeta{Name: "host_1"}}, + }, + want: &v1.NodeSelector{NodeSelectorTerms: []v1.NodeSelectorTerm{ + { + MatchFields: []v1.NodeSelectorRequirement{{ + Key: "metadata.name", + Operator: v1.NodeSelectorOpIn, + Values: []string{"host_2", "host_1"}, + }}, + }, + }}, + }, + { + name: "nodeLabels and nodeFields: ordered matchExpressions and ordered matchFields", + args: args{ + nodeSelector: &v1.NodeSelector{NodeSelectorTerms: []v1.NodeSelectorTerm{ + { + MatchExpressions: []v1.NodeSelectorRequirement{{ + Key: "label_1", + Operator: v1.NodeSelectorOpIn, + Values: []string{"label_1_val", "label_2_val"}, + }}, + MatchFields: []v1.NodeSelectorRequirement{{ + Key: "metadata.name", + Operator: v1.NodeSelectorOpIn, + Values: []string{"host_1", "host_2"}, + }}, + }, + }}, + node: &v1.Node{ObjectMeta: metav1.ObjectMeta{Name: "host_1", + Labels: map[string]string{ + "label_1": "label_1_val", + }}}, + }, + want: &v1.NodeSelector{NodeSelectorTerms: []v1.NodeSelectorTerm{ + { + MatchExpressions: []v1.NodeSelectorRequirement{{ + Key: "label_1", + Operator: v1.NodeSelectorOpIn, + Values: []string{"label_1_val", "label_2_val"}, + }}, + MatchFields: []v1.NodeSelectorRequirement{{ + Key: "metadata.name", + Operator: v1.NodeSelectorOpIn, + Values: []string{"host_1", "host_2"}, + }}, + }, + }}, + }, + { + name: "nodeLabels and nodeFields: ordered matchExpressions and unordered matchFields", + args: args{ + nodeSelector: &v1.NodeSelector{NodeSelectorTerms: []v1.NodeSelectorTerm{ + { + MatchExpressions: []v1.NodeSelectorRequirement{{ + Key: "label_1", + Operator: v1.NodeSelectorOpIn, + Values: []string{"label_1_val", "label_2_val"}, + }}, + MatchFields: []v1.NodeSelectorRequirement{{ + Key: "metadata.name", + Operator: v1.NodeSelectorOpIn, + Values: []string{"host_2", "host_1"}, + }}, + }, + }}, + node: &v1.Node{ObjectMeta: metav1.ObjectMeta{Name: "host_1", + Labels: map[string]string{ + "label_1": "label_1_val", + }}}, + }, + want: &v1.NodeSelector{NodeSelectorTerms: []v1.NodeSelectorTerm{ + { + MatchExpressions: []v1.NodeSelectorRequirement{{ + Key: "label_1", + Operator: v1.NodeSelectorOpIn, + Values: []string{"label_1_val", "label_2_val"}, + }}, + MatchFields: []v1.NodeSelectorRequirement{{ + Key: "metadata.name", + Operator: v1.NodeSelectorOpIn, + Values: []string{"host_2", "host_1"}, + }}, + }, + }}, + }, + { + name: "nodeLabels and nodeFields: unordered matchExpressions and ordered matchFields", + args: args{ + nodeSelector: &v1.NodeSelector{NodeSelectorTerms: []v1.NodeSelectorTerm{ + { + MatchExpressions: []v1.NodeSelectorRequirement{{ + Key: "label_1", + Operator: v1.NodeSelectorOpIn, + Values: []string{"label_2_val", "label_1_val"}, + }}, + MatchFields: []v1.NodeSelectorRequirement{{ + Key: "metadata.name", + Operator: v1.NodeSelectorOpIn, + Values: []string{"host_1", "host_2"}, + }}, + }, + }}, + node: &v1.Node{ObjectMeta: metav1.ObjectMeta{Name: "host_1", + Labels: map[string]string{ + "label_1": "label_1_val", + }}}, + }, + want: &v1.NodeSelector{NodeSelectorTerms: []v1.NodeSelectorTerm{ + { + MatchExpressions: []v1.NodeSelectorRequirement{{ + Key: "label_1", + Operator: v1.NodeSelectorOpIn, + Values: []string{"label_2_val", "label_1_val"}, + }}, + MatchFields: []v1.NodeSelectorRequirement{{ + Key: "metadata.name", + Operator: v1.NodeSelectorOpIn, + Values: []string{"host_1", "host_2"}, + }}, + }, + }}, + }, + { + name: "nodeLabels and nodeFields: unordered matchExpressions and unordered matchFields", + args: args{ + nodeSelector: &v1.NodeSelector{NodeSelectorTerms: []v1.NodeSelectorTerm{ + { + MatchExpressions: []v1.NodeSelectorRequirement{{ + Key: "label_1", + Operator: v1.NodeSelectorOpIn, + Values: []string{"label_2_val", "label_1_val"}, + }}, + MatchFields: []v1.NodeSelectorRequirement{{ + Key: "metadata.name", + Operator: v1.NodeSelectorOpIn, + Values: []string{"host_2", "host_1"}, + }}, + }, + }}, + node: &v1.Node{ObjectMeta: metav1.ObjectMeta{Name: "host_1", + Labels: map[string]string{ + "label_1": "label_1_val", + }}}, + }, + want: &v1.NodeSelector{NodeSelectorTerms: []v1.NodeSelectorTerm{ + { + MatchExpressions: []v1.NodeSelectorRequirement{{ + Key: "label_1", + Operator: v1.NodeSelectorOpIn, + Values: []string{"label_2_val", "label_1_val"}, + }}, + MatchFields: []v1.NodeSelectorRequirement{{ + Key: "metadata.name", + Operator: v1.NodeSelectorOpIn, + Values: []string{"host_2", "host_1"}, + }}, + }, + }}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + 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) + } + }) + } +} + +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) + } + } +}