mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-23 11:50:44 +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"],
|
visibility = ["//visibility:public"],
|
||||||
deps = [
|
deps = [
|
||||||
"//staging/src/k8s.io/api/core/v1:go_default_library",
|
"//staging/src/k8s.io/api/core/v1:go_default_library",
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/fields:go_default_library",
|
"//staging/src/k8s.io/component-helpers/scheduling/corev1/nodeaffinity: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",
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -26,7 +23,6 @@ go_test(
|
|||||||
"//staging/src/k8s.io/api/core/v1:go_default_library",
|
"//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/equality:go_default_library",
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/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",
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -39,7 +35,10 @@ filegroup(
|
|||||||
|
|
||||||
filegroup(
|
filegroup(
|
||||||
name = "all-srcs",
|
name = "all-srcs",
|
||||||
srcs = [":package-srcs"],
|
srcs = [
|
||||||
|
":package-srcs",
|
||||||
|
"//staging/src/k8s.io/component-helpers/scheduling/corev1/nodeaffinity:all-srcs",
|
||||||
|
],
|
||||||
tags = ["automanaged"],
|
tags = ["automanaged"],
|
||||||
visibility = ["//visibility:public"],
|
visibility = ["//visibility:public"],
|
||||||
)
|
)
|
||||||
|
@ -17,13 +17,8 @@ limitations under the License.
|
|||||||
package corev1
|
package corev1
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
|
|
||||||
v1 "k8s.io/api/core/v1"
|
v1 "k8s.io/api/core/v1"
|
||||||
"k8s.io/apimachinery/pkg/fields"
|
"k8s.io/component-helpers/scheduling/corev1/nodeaffinity"
|
||||||
"k8s.io/apimachinery/pkg/labels"
|
|
||||||
"k8s.io/apimachinery/pkg/selection"
|
|
||||||
apierrors "k8s.io/apimachinery/pkg/util/errors"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// PodPriority returns priority of the given pod.
|
// PodPriority returns priority of the given pod.
|
||||||
@ -46,103 +41,5 @@ func MatchNodeSelectorTerms(
|
|||||||
if node == nil {
|
if node == nil {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
var errors []error
|
return nodeaffinity.NewLazyErrorNodeSelector(nodeSelector).Match(node)
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
@ -17,13 +17,11 @@ limitations under the License.
|
|||||||
package corev1
|
package corev1
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"reflect"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
v1 "k8s.io/api/core/v1"
|
v1 "k8s.io/api/core/v1"
|
||||||
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/labels"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// TestPodPriority tests PodPriority function.
|
// TestPodPriority tests PodPriority function.
|
||||||
@ -121,7 +119,7 @@ func TestMatchNodeSelectorTerms(t *testing.T) {
|
|||||||
MatchFields: []v1.NodeSelectorRequirement{{
|
MatchFields: []v1.NodeSelectorRequirement{{
|
||||||
Key: "metadata.name",
|
Key: "metadata.name",
|
||||||
Operator: v1.NodeSelectorOpIn,
|
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 {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
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) {
|
if !apiequality.Semantic.DeepEqual(tt.args.nodeSelector, tt.want) {
|
||||||
// fail when tt.args.nodeSelector is deeply modified
|
// fail when tt.args.nodeSelector is deeply modified
|
||||||
t.Errorf("MatchNodeSelectorTerms() got = %v, want %v", tt.args.nodeSelector, tt.want)
|
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/auth/rbac/validation
|
||||||
k8s.io/component-helpers/lease
|
k8s.io/component-helpers/lease
|
||||||
k8s.io/component-helpers/scheduling/corev1
|
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
|
# k8s.io/controller-manager v0.0.0 => ./staging/src/k8s.io/controller-manager
|
||||||
## explicit
|
## explicit
|
||||||
# k8s.io/controller-manager => ./staging/src/k8s.io/controller-manager
|
# k8s.io/controller-manager => ./staging/src/k8s.io/controller-manager
|
||||||
|
Loading…
Reference in New Issue
Block a user