mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-23 11:50:44 +00:00
Merge pull request #100034 from AliceZhang2016/nodeaffinity-cleanup-only-scheduler
move nodeaffinity helpers to component-helpers package (only impact s…
This commit is contained in:
commit
f17004981b
@ -21,7 +21,6 @@ import (
|
||||
"fmt"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/component-helpers/scheduling/corev1/nodeaffinity"
|
||||
"k8s.io/kubernetes/pkg/scheduler/apis/config"
|
||||
@ -63,8 +62,7 @@ func (pl *NodeAffinity) Name() string {
|
||||
}
|
||||
|
||||
type preFilterState struct {
|
||||
requiredNodeSelector labels.Selector
|
||||
requiredNodeAffinity *nodeaffinity.LazyErrorNodeSelector
|
||||
requiredNodeSelectorAndAffinity nodeaffinity.RequiredNodeAffinity
|
||||
}
|
||||
|
||||
// Clone just returns the same state because it is not affected by pod additions or deletions.
|
||||
@ -74,7 +72,7 @@ func (s *preFilterState) Clone() framework.StateData {
|
||||
|
||||
// PreFilter builds and writes cycle state used by Filter.
|
||||
func (pl *NodeAffinity) PreFilter(ctx context.Context, cycleState *framework.CycleState, pod *v1.Pod) *framework.Status {
|
||||
state := getPodRequiredNodeSelectorAndAffinity(pod)
|
||||
state := &preFilterState{requiredNodeSelectorAndAffinity: nodeaffinity.GetRequiredNodeAffinity(pod)}
|
||||
cycleState.Write(preFilterStateKey, state)
|
||||
return nil
|
||||
}
|
||||
@ -99,21 +97,15 @@ func (pl *NodeAffinity) Filter(ctx context.Context, state *framework.CycleState,
|
||||
if err != nil {
|
||||
// Fallback to calculate requiredNodeSelector and requiredNodeAffinity
|
||||
// here when PreFilter is disabled.
|
||||
s = getPodRequiredNodeSelectorAndAffinity(pod)
|
||||
s = &preFilterState{requiredNodeSelectorAndAffinity: nodeaffinity.GetRequiredNodeAffinity(pod)}
|
||||
}
|
||||
|
||||
if s.requiredNodeSelector != nil {
|
||||
if !s.requiredNodeSelector.Matches(labels.Set(node.Labels)) {
|
||||
return framework.NewStatus(framework.UnschedulableAndUnresolvable, ErrReasonPod)
|
||||
}
|
||||
}
|
||||
if s.requiredNodeAffinity != nil {
|
||||
// Ignore parsing errors for backwards compatibility.
|
||||
matches, _ := s.requiredNodeAffinity.Match(node)
|
||||
if !matches {
|
||||
return framework.NewStatus(framework.UnschedulableAndUnresolvable, ErrReasonPod)
|
||||
}
|
||||
// Ignore parsing errors for backwards compatibility.
|
||||
match, _ := s.requiredNodeSelectorAndAffinity.Match(node)
|
||||
if !match {
|
||||
return framework.NewStatus(framework.UnschedulableAndUnresolvable, ErrReasonPod)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -248,21 +240,6 @@ func getPreScoreState(cycleState *framework.CycleState) (*preScoreState, error)
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func getPodRequiredNodeSelectorAndAffinity(pod *v1.Pod) *preFilterState {
|
||||
var selector labels.Selector
|
||||
if len(pod.Spec.NodeSelector) > 0 {
|
||||
selector = labels.SelectorFromSet(pod.Spec.NodeSelector)
|
||||
}
|
||||
// Use LazyErrorNodeSelector for backwards compatibility of parsing errors.
|
||||
var affinity *nodeaffinity.LazyErrorNodeSelector
|
||||
if pod.Spec.Affinity != nil &&
|
||||
pod.Spec.Affinity.NodeAffinity != nil &&
|
||||
pod.Spec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution != nil {
|
||||
affinity = nodeaffinity.NewLazyErrorNodeSelector(pod.Spec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution)
|
||||
}
|
||||
return &preFilterState{requiredNodeSelector: selector, requiredNodeAffinity: affinity}
|
||||
}
|
||||
|
||||
func getPreFilterState(cycleState *framework.CycleState) (*preFilterState, error) {
|
||||
c, err := cycleState.Read(preFilterStateKey)
|
||||
if err != nil {
|
||||
|
@ -24,9 +24,9 @@ import (
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/component-helpers/scheduling/corev1/nodeaffinity"
|
||||
"k8s.io/klog/v2"
|
||||
"k8s.io/kubernetes/pkg/scheduler/framework"
|
||||
"k8s.io/kubernetes/pkg/scheduler/framework/plugins/helper"
|
||||
)
|
||||
|
||||
const preFilterStateKey = "PreFilter" + Name
|
||||
@ -222,6 +222,7 @@ func (pl *PodTopologySpread) calPreFilterState(pod *v1.Pod) (*preFilterState, er
|
||||
TpKeyToCriticalPaths: make(map[string]*criticalPaths, len(constraints)),
|
||||
TpPairToMatchNum: make(map[topologyPair]*int32, sizeHeuristic(len(allNodes), constraints)),
|
||||
}
|
||||
requiredSchedulingTerm := nodeaffinity.GetRequiredNodeAffinity(pod)
|
||||
for _, n := range allNodes {
|
||||
node := n.Node()
|
||||
if node == nil {
|
||||
@ -230,7 +231,9 @@ func (pl *PodTopologySpread) calPreFilterState(pod *v1.Pod) (*preFilterState, er
|
||||
}
|
||||
// In accordance to design, if NodeAffinity or NodeSelector is defined,
|
||||
// spreading is applied to nodes that pass those filters.
|
||||
if !helper.PodMatchesNodeSelectorAndAffinityTerms(pod, node) {
|
||||
// Ignore parsing errors for backwards compatibility.
|
||||
match, _ := requiredSchedulingTerm.Match(node)
|
||||
if !match {
|
||||
continue
|
||||
}
|
||||
// Ensure current node's labels contains all topologyKeys in 'Constraints'.
|
||||
|
@ -24,8 +24,8 @@ import (
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/component-helpers/scheduling/corev1/nodeaffinity"
|
||||
"k8s.io/kubernetes/pkg/scheduler/framework"
|
||||
pluginhelper "k8s.io/kubernetes/pkg/scheduler/framework/plugins/helper"
|
||||
)
|
||||
|
||||
const preScoreStateKey = "PreScore" + Name
|
||||
@ -136,6 +136,8 @@ func (pl *PodTopologySpread) PreScore(
|
||||
return nil
|
||||
}
|
||||
|
||||
// Ignore parsing errors for backwards compatibility.
|
||||
requiredNodeAffinity := nodeaffinity.GetRequiredNodeAffinity(pod)
|
||||
processAllNode := func(i int) {
|
||||
nodeInfo := allNodes[i]
|
||||
node := nodeInfo.Node()
|
||||
@ -144,8 +146,8 @@ func (pl *PodTopologySpread) PreScore(
|
||||
}
|
||||
// (1) `node` should satisfy incoming pod's NodeSelector/NodeAffinity
|
||||
// (2) All topologyKeys need to be present in `node`
|
||||
if !pluginhelper.PodMatchesNodeSelectorAndAffinityTerms(pod, node) ||
|
||||
!nodeLabelsMatchSpreadConstraints(node.Labels, state.Constraints) {
|
||||
match, _ := requiredNodeAffinity.Match(node)
|
||||
if !match || !nodeLabelsMatchSpreadConstraints(node.Labels, state.Constraints) {
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -287,3 +287,38 @@ type preferredSchedulingTerm struct {
|
||||
nodeSelectorTerm
|
||||
weight int
|
||||
}
|
||||
|
||||
type RequiredNodeAffinity struct {
|
||||
labelSelector labels.Selector
|
||||
nodeSelector *LazyErrorNodeSelector
|
||||
}
|
||||
|
||||
// GetRequiredNodeAffinity returns the parsing result of pod's nodeSelector and nodeAffinity.
|
||||
func GetRequiredNodeAffinity(pod *v1.Pod) RequiredNodeAffinity {
|
||||
var selector labels.Selector
|
||||
if len(pod.Spec.NodeSelector) > 0 {
|
||||
selector = labels.SelectorFromSet(pod.Spec.NodeSelector)
|
||||
}
|
||||
// Use LazyErrorNodeSelector for backwards compatibility of parsing errors.
|
||||
var affinity *LazyErrorNodeSelector
|
||||
if pod.Spec.Affinity != nil &&
|
||||
pod.Spec.Affinity.NodeAffinity != nil &&
|
||||
pod.Spec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution != nil {
|
||||
affinity = NewLazyErrorNodeSelector(pod.Spec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution)
|
||||
}
|
||||
return RequiredNodeAffinity{labelSelector: selector, nodeSelector: affinity}
|
||||
}
|
||||
|
||||
// Match checks whether the pod is schedulable onto nodes according to
|
||||
// the requirements in both nodeSelector and nodeAffinity.
|
||||
func (s RequiredNodeAffinity) Match(node *v1.Node) (bool, error) {
|
||||
if s.labelSelector != nil {
|
||||
if !s.labelSelector.Matches(labels.Set(node.Labels)) {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
if s.nodeSelector != nil {
|
||||
return s.nodeSelector.Match(node)
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
@ -358,3 +358,690 @@ func TestNodeSelectorRequirementsAsSelector(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPodMatchesNodeSelectorAndAffinityTerms(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
pod *v1.Pod
|
||||
labels map[string]string
|
||||
nodeName string
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "no selector",
|
||||
pod: &v1.Pod{},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "missing labels",
|
||||
pod: &v1.Pod{
|
||||
Spec: v1.PodSpec{
|
||||
NodeSelector: map[string]string{
|
||||
"foo": "bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "same labels",
|
||||
pod: &v1.Pod{
|
||||
Spec: v1.PodSpec{
|
||||
NodeSelector: map[string]string{
|
||||
"foo": "bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
labels: map[string]string{
|
||||
"foo": "bar",
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "node labels are superset",
|
||||
pod: &v1.Pod{
|
||||
Spec: v1.PodSpec{
|
||||
NodeSelector: map[string]string{
|
||||
"foo": "bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
labels: map[string]string{
|
||||
"foo": "bar",
|
||||
"baz": "blah",
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "node labels are subset",
|
||||
pod: &v1.Pod{
|
||||
Spec: v1.PodSpec{
|
||||
NodeSelector: map[string]string{
|
||||
"foo": "bar",
|
||||
"baz": "blah",
|
||||
},
|
||||
},
|
||||
},
|
||||
labels: map[string]string{
|
||||
"foo": "bar",
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "Pod with matchExpressions using In operator that matches the existing node",
|
||||
pod: &v1.Pod{
|
||||
Spec: v1.PodSpec{
|
||||
Affinity: &v1.Affinity{
|
||||
NodeAffinity: &v1.NodeAffinity{
|
||||
RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{
|
||||
NodeSelectorTerms: []v1.NodeSelectorTerm{
|
||||
{
|
||||
MatchExpressions: []v1.NodeSelectorRequirement{
|
||||
{
|
||||
Key: "foo",
|
||||
Operator: v1.NodeSelectorOpIn,
|
||||
Values: []string{"bar", "value2"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
labels: map[string]string{
|
||||
"foo": "bar",
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "Pod with matchExpressions using Gt operator that matches the existing node",
|
||||
pod: &v1.Pod{
|
||||
Spec: v1.PodSpec{
|
||||
Affinity: &v1.Affinity{
|
||||
NodeAffinity: &v1.NodeAffinity{
|
||||
RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{
|
||||
NodeSelectorTerms: []v1.NodeSelectorTerm{
|
||||
{
|
||||
MatchExpressions: []v1.NodeSelectorRequirement{
|
||||
{
|
||||
Key: "kernel-version",
|
||||
Operator: v1.NodeSelectorOpGt,
|
||||
Values: []string{"0204"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
labels: map[string]string{
|
||||
// We use two digit to denote major version and two digit for minor version.
|
||||
"kernel-version": "0206",
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "Pod with matchExpressions using NotIn operator that matches the existing node",
|
||||
pod: &v1.Pod{
|
||||
Spec: v1.PodSpec{
|
||||
Affinity: &v1.Affinity{
|
||||
NodeAffinity: &v1.NodeAffinity{
|
||||
RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{
|
||||
NodeSelectorTerms: []v1.NodeSelectorTerm{
|
||||
{
|
||||
MatchExpressions: []v1.NodeSelectorRequirement{
|
||||
{
|
||||
Key: "mem-type",
|
||||
Operator: v1.NodeSelectorOpNotIn,
|
||||
Values: []string{"DDR", "DDR2"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
labels: map[string]string{
|
||||
"mem-type": "DDR3",
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "Pod with matchExpressions using Exists operator that matches the existing node",
|
||||
pod: &v1.Pod{
|
||||
Spec: v1.PodSpec{
|
||||
Affinity: &v1.Affinity{
|
||||
NodeAffinity: &v1.NodeAffinity{
|
||||
RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{
|
||||
NodeSelectorTerms: []v1.NodeSelectorTerm{
|
||||
{
|
||||
MatchExpressions: []v1.NodeSelectorRequirement{
|
||||
{
|
||||
Key: "GPU",
|
||||
Operator: v1.NodeSelectorOpExists,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
labels: map[string]string{
|
||||
"GPU": "NVIDIA-GRID-K1",
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
pod: &v1.Pod{
|
||||
Spec: v1.PodSpec{
|
||||
Affinity: &v1.Affinity{
|
||||
NodeAffinity: &v1.NodeAffinity{
|
||||
RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{
|
||||
NodeSelectorTerms: []v1.NodeSelectorTerm{
|
||||
{
|
||||
MatchExpressions: []v1.NodeSelectorRequirement{
|
||||
{
|
||||
Key: "foo",
|
||||
Operator: v1.NodeSelectorOpIn,
|
||||
Values: []string{"value1", "value2"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
labels: map[string]string{
|
||||
"foo": "bar",
|
||||
},
|
||||
want: false,
|
||||
name: "Pod with affinity that don't match node's labels won't schedule onto the node",
|
||||
},
|
||||
{
|
||||
pod: &v1.Pod{
|
||||
Spec: v1.PodSpec{
|
||||
Affinity: &v1.Affinity{
|
||||
NodeAffinity: &v1.NodeAffinity{
|
||||
RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{
|
||||
NodeSelectorTerms: nil,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
labels: map[string]string{
|
||||
"foo": "bar",
|
||||
},
|
||||
want: false,
|
||||
name: "Pod with a nil []NodeSelectorTerm in affinity, can't match the node's labels and won't schedule onto the node",
|
||||
},
|
||||
{
|
||||
pod: &v1.Pod{
|
||||
Spec: v1.PodSpec{
|
||||
Affinity: &v1.Affinity{
|
||||
NodeAffinity: &v1.NodeAffinity{
|
||||
RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{
|
||||
NodeSelectorTerms: []v1.NodeSelectorTerm{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
labels: map[string]string{
|
||||
"foo": "bar",
|
||||
},
|
||||
want: false,
|
||||
name: "Pod with an empty []NodeSelectorTerm in affinity, can't match the node's labels and won't schedule onto the node",
|
||||
},
|
||||
{
|
||||
name: "Pod with empty MatchExpressions is not a valid value will match no objects and won't schedule onto the node",
|
||||
pod: &v1.Pod{
|
||||
Spec: v1.PodSpec{
|
||||
Affinity: &v1.Affinity{
|
||||
NodeAffinity: &v1.NodeAffinity{
|
||||
RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{
|
||||
NodeSelectorTerms: []v1.NodeSelectorTerm{
|
||||
{
|
||||
MatchExpressions: []v1.NodeSelectorRequirement{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
labels: map[string]string{
|
||||
"foo": "bar",
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "Pod with no Affinity will schedule onto a node",
|
||||
pod: &v1.Pod{},
|
||||
labels: map[string]string{
|
||||
"foo": "bar",
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "Pod with Affinity but nil NodeSelector will schedule onto a node",
|
||||
pod: &v1.Pod{
|
||||
Spec: v1.PodSpec{
|
||||
Affinity: &v1.Affinity{
|
||||
NodeAffinity: &v1.NodeAffinity{
|
||||
RequiredDuringSchedulingIgnoredDuringExecution: nil,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
labels: map[string]string{
|
||||
"foo": "bar",
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "Pod with multiple matchExpressions ANDed that matches the existing node",
|
||||
pod: &v1.Pod{
|
||||
Spec: v1.PodSpec{
|
||||
Affinity: &v1.Affinity{
|
||||
NodeAffinity: &v1.NodeAffinity{
|
||||
RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{
|
||||
NodeSelectorTerms: []v1.NodeSelectorTerm{
|
||||
{
|
||||
MatchExpressions: []v1.NodeSelectorRequirement{
|
||||
{
|
||||
Key: "GPU",
|
||||
Operator: v1.NodeSelectorOpExists,
|
||||
}, {
|
||||
Key: "GPU",
|
||||
Operator: v1.NodeSelectorOpNotIn,
|
||||
Values: []string{"AMD", "INTER"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
labels: map[string]string{
|
||||
"GPU": "NVIDIA-GRID-K1",
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "Pod with multiple matchExpressions ANDed that doesn't match the existing node",
|
||||
pod: &v1.Pod{
|
||||
Spec: v1.PodSpec{
|
||||
Affinity: &v1.Affinity{
|
||||
NodeAffinity: &v1.NodeAffinity{
|
||||
RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{
|
||||
NodeSelectorTerms: []v1.NodeSelectorTerm{
|
||||
{
|
||||
MatchExpressions: []v1.NodeSelectorRequirement{
|
||||
{
|
||||
Key: "GPU",
|
||||
Operator: v1.NodeSelectorOpExists,
|
||||
}, {
|
||||
Key: "GPU",
|
||||
Operator: v1.NodeSelectorOpIn,
|
||||
Values: []string{"AMD", "INTER"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
labels: map[string]string{
|
||||
"GPU": "NVIDIA-GRID-K1",
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "Pod with multiple NodeSelectorTerms ORed in affinity, matches the node's labels and will schedule onto the node",
|
||||
pod: &v1.Pod{
|
||||
Spec: v1.PodSpec{
|
||||
Affinity: &v1.Affinity{
|
||||
NodeAffinity: &v1.NodeAffinity{
|
||||
RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{
|
||||
NodeSelectorTerms: []v1.NodeSelectorTerm{
|
||||
{
|
||||
MatchExpressions: []v1.NodeSelectorRequirement{
|
||||
{
|
||||
Key: "foo",
|
||||
Operator: v1.NodeSelectorOpIn,
|
||||
Values: []string{"bar", "value2"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
MatchExpressions: []v1.NodeSelectorRequirement{
|
||||
{
|
||||
Key: "diffkey",
|
||||
Operator: v1.NodeSelectorOpIn,
|
||||
Values: []string{"wrong", "value2"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
labels: map[string]string{
|
||||
"foo": "bar",
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "Pod with an Affinity and a PodSpec.NodeSelector(the old thing that we are deprecating) " +
|
||||
"both are satisfied, will schedule onto the node",
|
||||
pod: &v1.Pod{
|
||||
Spec: v1.PodSpec{
|
||||
NodeSelector: map[string]string{
|
||||
"foo": "bar",
|
||||
},
|
||||
Affinity: &v1.Affinity{
|
||||
NodeAffinity: &v1.NodeAffinity{
|
||||
RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{
|
||||
NodeSelectorTerms: []v1.NodeSelectorTerm{
|
||||
{
|
||||
MatchExpressions: []v1.NodeSelectorRequirement{
|
||||
{
|
||||
Key: "foo",
|
||||
Operator: v1.NodeSelectorOpExists,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
labels: map[string]string{
|
||||
"foo": "bar",
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "Pod with an Affinity matches node's labels but the PodSpec.NodeSelector(the old thing that we are deprecating) " +
|
||||
"is not satisfied, won't schedule onto the node",
|
||||
pod: &v1.Pod{
|
||||
Spec: v1.PodSpec{
|
||||
NodeSelector: map[string]string{
|
||||
"foo": "bar",
|
||||
},
|
||||
Affinity: &v1.Affinity{
|
||||
NodeAffinity: &v1.NodeAffinity{
|
||||
RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{
|
||||
NodeSelectorTerms: []v1.NodeSelectorTerm{
|
||||
{
|
||||
MatchExpressions: []v1.NodeSelectorRequirement{
|
||||
{
|
||||
Key: "foo",
|
||||
Operator: v1.NodeSelectorOpExists,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
labels: map[string]string{
|
||||
"foo": "barrrrrr",
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "Pod with an invalid value in Affinity term won't be scheduled onto the node",
|
||||
pod: &v1.Pod{
|
||||
Spec: v1.PodSpec{
|
||||
Affinity: &v1.Affinity{
|
||||
NodeAffinity: &v1.NodeAffinity{
|
||||
RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{
|
||||
NodeSelectorTerms: []v1.NodeSelectorTerm{
|
||||
{
|
||||
MatchExpressions: []v1.NodeSelectorRequirement{
|
||||
{
|
||||
Key: "foo",
|
||||
Operator: v1.NodeSelectorOpNotIn,
|
||||
Values: []string{"invalid value: ___@#$%^"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
labels: map[string]string{
|
||||
"foo": "bar",
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "Pod with matchFields using In operator that matches the existing node",
|
||||
pod: &v1.Pod{
|
||||
Spec: v1.PodSpec{
|
||||
Affinity: &v1.Affinity{
|
||||
NodeAffinity: &v1.NodeAffinity{
|
||||
RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{
|
||||
NodeSelectorTerms: []v1.NodeSelectorTerm{
|
||||
{
|
||||
MatchFields: []v1.NodeSelectorRequirement{
|
||||
{
|
||||
Key: metav1.ObjectNameField,
|
||||
Operator: v1.NodeSelectorOpIn,
|
||||
Values: []string{"node_1"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
nodeName: "node_1",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "Pod with matchFields using In operator that does not match the existing node",
|
||||
pod: &v1.Pod{
|
||||
Spec: v1.PodSpec{
|
||||
Affinity: &v1.Affinity{
|
||||
NodeAffinity: &v1.NodeAffinity{
|
||||
RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{
|
||||
NodeSelectorTerms: []v1.NodeSelectorTerm{
|
||||
{
|
||||
MatchFields: []v1.NodeSelectorRequirement{
|
||||
{
|
||||
Key: metav1.ObjectNameField,
|
||||
Operator: v1.NodeSelectorOpIn,
|
||||
Values: []string{"node_1"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
nodeName: "node_2",
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "Pod with two terms: matchFields does not match, but matchExpressions matches",
|
||||
pod: &v1.Pod{
|
||||
Spec: v1.PodSpec{
|
||||
Affinity: &v1.Affinity{
|
||||
NodeAffinity: &v1.NodeAffinity{
|
||||
RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{
|
||||
NodeSelectorTerms: []v1.NodeSelectorTerm{
|
||||
{
|
||||
MatchFields: []v1.NodeSelectorRequirement{
|
||||
{
|
||||
Key: metav1.ObjectNameField,
|
||||
Operator: v1.NodeSelectorOpIn,
|
||||
Values: []string{"node_1"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
MatchExpressions: []v1.NodeSelectorRequirement{
|
||||
{
|
||||
Key: "foo",
|
||||
Operator: v1.NodeSelectorOpIn,
|
||||
Values: []string{"bar"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
nodeName: "node_2",
|
||||
labels: map[string]string{"foo": "bar"},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "Pod with one term: matchFields does not match, but matchExpressions matches",
|
||||
pod: &v1.Pod{
|
||||
Spec: v1.PodSpec{
|
||||
Affinity: &v1.Affinity{
|
||||
NodeAffinity: &v1.NodeAffinity{
|
||||
RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{
|
||||
NodeSelectorTerms: []v1.NodeSelectorTerm{
|
||||
{
|
||||
MatchFields: []v1.NodeSelectorRequirement{
|
||||
{
|
||||
Key: metav1.ObjectNameField,
|
||||
Operator: v1.NodeSelectorOpIn,
|
||||
Values: []string{"node_1"},
|
||||
},
|
||||
},
|
||||
MatchExpressions: []v1.NodeSelectorRequirement{
|
||||
{
|
||||
Key: "foo",
|
||||
Operator: v1.NodeSelectorOpIn,
|
||||
Values: []string{"bar"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
nodeName: "node_2",
|
||||
labels: map[string]string{"foo": "bar"},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "Pod with one term: both matchFields and matchExpressions match",
|
||||
pod: &v1.Pod{
|
||||
Spec: v1.PodSpec{
|
||||
Affinity: &v1.Affinity{
|
||||
NodeAffinity: &v1.NodeAffinity{
|
||||
RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{
|
||||
NodeSelectorTerms: []v1.NodeSelectorTerm{
|
||||
{
|
||||
MatchFields: []v1.NodeSelectorRequirement{
|
||||
{
|
||||
Key: metav1.ObjectNameField,
|
||||
Operator: v1.NodeSelectorOpIn,
|
||||
Values: []string{"node_1"},
|
||||
},
|
||||
},
|
||||
MatchExpressions: []v1.NodeSelectorRequirement{
|
||||
{
|
||||
Key: "foo",
|
||||
Operator: v1.NodeSelectorOpIn,
|
||||
Values: []string{"bar"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
nodeName: "node_1",
|
||||
labels: map[string]string{"foo": "bar"},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "Pod with two terms: both matchFields and matchExpressions do not match",
|
||||
pod: &v1.Pod{
|
||||
Spec: v1.PodSpec{
|
||||
Affinity: &v1.Affinity{
|
||||
NodeAffinity: &v1.NodeAffinity{
|
||||
RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{
|
||||
NodeSelectorTerms: []v1.NodeSelectorTerm{
|
||||
{
|
||||
MatchFields: []v1.NodeSelectorRequirement{
|
||||
{
|
||||
Key: metav1.ObjectNameField,
|
||||
Operator: v1.NodeSelectorOpIn,
|
||||
Values: []string{"node_1"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
MatchExpressions: []v1.NodeSelectorRequirement{
|
||||
{
|
||||
Key: "foo",
|
||||
Operator: v1.NodeSelectorOpIn,
|
||||
Values: []string{"not-match-to-bar"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
nodeName: "node_2",
|
||||
labels: map[string]string{"foo": "bar"},
|
||||
want: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
node := v1.Node{ObjectMeta: metav1.ObjectMeta{
|
||||
Name: test.nodeName,
|
||||
Labels: test.labels,
|
||||
}}
|
||||
got, _ := GetRequiredNodeAffinity(test.pod).Match(&node)
|
||||
if test.want != got {
|
||||
t.Errorf("expected: %v got %v", test.want, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user