mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-21 19:01:49 +00:00
Merge pull request #96064 from alculquicondor/parse-node-affinity
Add runtime representation of v1.NodeSelector
This commit is contained in:
commit
aa79d78c7e
@ -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"],
|
||||
)
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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",
|
||||
],
|
||||
)
|
@ -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
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
1
vendor/modules.txt
vendored
1
vendor/modules.txt
vendored
@ -2198,6 +2198,7 @@ k8s.io/component-helpers/auth/rbac/reconciliation
|
||||
k8s.io/component-helpers/auth/rbac/validation
|
||||
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
|
||||
|
Loading…
Reference in New Issue
Block a user