From 2fdf1fa3c14b54dcae43bc2c5622fbcbb4857c17 Mon Sep 17 00:00:00 2001 From: Abdullah Gharaibeh Date: Thu, 12 Dec 2019 14:03:50 -0500 Subject: [PATCH] inter-pod affinity prefilter --- .../algorithm/predicates/metadata.go | 36 +- .../algorithm/predicates/metadata_test.go | 224 +- .../algorithm/predicates/predicates.go | 31 +- .../algorithm/predicates/predicates_test.go | 2071 ----------------- .../apis/config/testing/compatibility_test.go | 30 + .../framework/plugins/default_registry.go | 1 + .../framework/plugins/interpodaffinity/BUILD | 2 + .../interpodaffinity/interpod_affinity.go | 116 +- .../interpod_affinity_test.go | 302 ++- test/integration/scheduler/scheduler_test.go | 6 + 10 files changed, 486 insertions(+), 2333 deletions(-) diff --git a/pkg/scheduler/algorithm/predicates/metadata.go b/pkg/scheduler/algorithm/predicates/metadata.go index d5b4b90c87b..3a026d38432 100644 --- a/pkg/scheduler/algorithm/predicates/metadata.go +++ b/pkg/scheduler/algorithm/predicates/metadata.go @@ -179,7 +179,8 @@ func (m *serviceAffinityMetadata) clone() *serviceAffinityMetadata { return © } -type podAffinityMetadata struct { +// PodAffinityMetadata pre-computed state for inter-pod affinity predicate. +type PodAffinityMetadata struct { // A map of topology pairs to the number of existing pods that has anti-affinity terms that match the "pod". topologyToMatchedExistingAntiAffinityTerms topologyToMatchedTermCount // A map of topology pairs to the number of existing pods that match the affinity terms of the "pod". @@ -225,7 +226,8 @@ func (m topologyToMatchedTermCount) updateWithAntiAffinityTerms(targetPod *v1.Po } } -func (m *podAffinityMetadata) updatePod(updatedPod, pod *v1.Pod, node *v1.Node, multiplier int64) error { +// UpdateWithPod updates the metadata counters with the (anti)affinity matches for the given pod. +func (m *PodAffinityMetadata) UpdateWithPod(updatedPod, pod *v1.Pod, node *v1.Node, multiplier int64) error { if m == nil { return nil } @@ -265,12 +267,13 @@ func (m *podAffinityMetadata) updatePod(updatedPod, pod *v1.Pod, node *v1.Node, return nil } -func (m *podAffinityMetadata) clone() *podAffinityMetadata { +// Clone makes a deep copy of PodAffinityMetadata. +func (m *PodAffinityMetadata) Clone() *PodAffinityMetadata { if m == nil { return nil } - copy := podAffinityMetadata{} + copy := PodAffinityMetadata{} copy.topologyToMatchedAffinityTerms = m.topologyToMatchedAffinityTerms.clone() copy.topologyToMatchedAntiAffinityTerms = m.topologyToMatchedAntiAffinityTerms.clone() copy.topologyToMatchedExistingAntiAffinityTerms = m.topologyToMatchedExistingAntiAffinityTerms.clone() @@ -327,7 +330,6 @@ type predicateMetadata struct { evenPodsSpreadMetadata *evenPodsSpreadMetadata serviceAffinityMetadata *serviceAffinityMetadata - podAffinityMetadata *podAffinityMetadata podFitsResourcesMetadata *podFitsResourcesMetadata podFitsHostPortsMetadata *podFitsHostPortsMetadata } @@ -373,7 +375,6 @@ func (f *MetadataProducerFactory) GetPredicateMetadata(pod *v1.Pod, sharedLister } var allNodes []*schedulernodeinfo.NodeInfo - var havePodsWithAffinityNodes []*schedulernodeinfo.NodeInfo if sharedLister != nil { var err error allNodes, err = sharedLister.NodeInfos().List() @@ -381,12 +382,6 @@ func (f *MetadataProducerFactory) GetPredicateMetadata(pod *v1.Pod, sharedLister klog.Errorf("failed to list NodeInfos: %v", err) return nil } - havePodsWithAffinityNodes, err = sharedLister.NodeInfos().HavePodsWithAffinityList() - if err != nil { - klog.Errorf("failed to list NodeInfos: %v", err) - return nil - } - } // evenPodsSpreadMetadata represents how existing pods match "pod" @@ -397,16 +392,9 @@ func (f *MetadataProducerFactory) GetPredicateMetadata(pod *v1.Pod, sharedLister return nil } - podAffinityMetadata, err := getPodAffinityMetadata(pod, allNodes, havePodsWithAffinityNodes) - if err != nil { - klog.Errorf("Error calculating podAffinityMetadata: %v", err) - return nil - } - predicateMetadata := &predicateMetadata{ pod: pod, evenPodsSpreadMetadata: evenPodsSpreadMetadata, - podAffinityMetadata: podAffinityMetadata, podFitsResourcesMetadata: getPodFitsResourcesMetedata(pod), podFitsHostPortsMetadata: getPodFitsHostPortsMetadata(pod), } @@ -429,7 +417,8 @@ func getPodFitsResourcesMetedata(pod *v1.Pod) *podFitsResourcesMetadata { } } -func getPodAffinityMetadata(pod *v1.Pod, allNodes []*schedulernodeinfo.NodeInfo, havePodsWithAffinityNodes []*schedulernodeinfo.NodeInfo) (*podAffinityMetadata, error) { +// GetPodAffinityMetadata computes inter-pod affinity metadata. +func GetPodAffinityMetadata(pod *v1.Pod, allNodes []*schedulernodeinfo.NodeInfo, havePodsWithAffinityNodes []*schedulernodeinfo.NodeInfo) (*PodAffinityMetadata, error) { // existingPodAntiAffinityMap will be used later for efficient check on existing pods' anti-affinity existingPodAntiAffinityMap, err := getTPMapMatchingExistingAntiAffinity(pod, havePodsWithAffinityNodes) if err != nil { @@ -442,7 +431,7 @@ func getPodAffinityMetadata(pod *v1.Pod, allNodes []*schedulernodeinfo.NodeInfo, return nil, err } - return &podAffinityMetadata{ + return &PodAffinityMetadata{ topologyToMatchedAffinityTerms: incomingPodAffinityMap, topologyToMatchedAntiAffinityTerms: incomingPodAntiAffinityMap, topologyToMatchedExistingAntiAffinityTerms: existingPodAntiAffinityMap, @@ -619,7 +608,6 @@ func (meta *predicateMetadata) RemovePod(deletedPod *v1.Pod, node *v1.Node) erro if deletedPodFullName == schedutil.GetPodFullName(meta.pod) { return fmt.Errorf("deletedPod and meta.pod must not be the same") } - meta.podAffinityMetadata.updatePod(deletedPod, meta.pod, node, -1) meta.evenPodsSpreadMetadata.removePod(deletedPod, meta.pod, node) meta.serviceAffinityMetadata.removePod(deletedPod, node) @@ -637,9 +625,6 @@ func (meta *predicateMetadata) AddPod(addedPod *v1.Pod, node *v1.Node) error { return fmt.Errorf("node not found") } - if err := meta.podAffinityMetadata.updatePod(addedPod, meta.pod, node, 1); err != nil { - return err - } // Update meta.evenPodsSpreadMetadata if meta.pod has hard spread constraints // and addedPod matches that meta.evenPodsSpreadMetadata.addPod(addedPod, meta.pod, node) @@ -657,7 +642,6 @@ func (meta *predicateMetadata) ShallowCopy() Metadata { podBestEffort: meta.podBestEffort, } newPredMeta.podFitsHostPortsMetadata = meta.podFitsHostPortsMetadata.clone() - newPredMeta.podAffinityMetadata = meta.podAffinityMetadata.clone() newPredMeta.evenPodsSpreadMetadata = meta.evenPodsSpreadMetadata.clone() newPredMeta.serviceAffinityMetadata = meta.serviceAffinityMetadata.clone() newPredMeta.podFitsResourcesMetadata = meta.podFitsResourcesMetadata.clone() diff --git a/pkg/scheduler/algorithm/predicates/metadata_test.go b/pkg/scheduler/algorithm/predicates/metadata_test.go index 25203e6d8fe..7f02b00e398 100644 --- a/pkg/scheduler/algorithm/predicates/metadata_test.go +++ b/pkg/scheduler/algorithm/predicates/metadata_test.go @@ -70,16 +70,7 @@ func predicateMetadataEquivalent(meta1, meta2 *predicateMetadata) error { for !reflect.DeepEqual(meta1.podFitsHostPortsMetadata.podPorts, meta2.podFitsHostPortsMetadata.podPorts) { return fmt.Errorf("podPorts are not equal") } - if !reflect.DeepEqual(meta1.podAffinityMetadata.topologyToMatchedAffinityTerms, meta2.podAffinityMetadata.topologyToMatchedAffinityTerms) { - return fmt.Errorf("topologyToMatchedAffinityTerms are not equal") - } - if !reflect.DeepEqual(meta1.podAffinityMetadata.topologyToMatchedAntiAffinityTerms, meta2.podAffinityMetadata.topologyToMatchedAntiAffinityTerms) { - return fmt.Errorf("topologyToMatchedAntiAffinityTerms are not equal") - } - if !reflect.DeepEqual(meta1.podAffinityMetadata.topologyToMatchedExistingAntiAffinityTerms, - meta2.podAffinityMetadata.topologyToMatchedExistingAntiAffinityTerms) { - return fmt.Errorf("topologyToMatchedExistingAntiAffinityTerms are not equal, got: %v, want: %v", meta1.podAffinityMetadata.topologyToMatchedExistingAntiAffinityTerms, meta2.podAffinityMetadata.topologyToMatchedExistingAntiAffinityTerms) - } + if meta1.serviceAffinityMetadata != nil { sortablePods1 := sortablePods(meta1.serviceAffinityMetadata.matchingPodList) sort.Sort(sortablePods1) @@ -114,78 +105,6 @@ func TestPredicateMetadata_AddRemovePod(t *testing.T) { "zone": "z21", } selector1 := map[string]string{"foo": "bar"} - antiAffinityFooBar := &v1.PodAntiAffinity{ - RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{ - { - LabelSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "foo", - Operator: metav1.LabelSelectorOpIn, - Values: []string{"bar"}, - }, - }, - }, - TopologyKey: "region", - }, - }, - } - antiAffinityComplex := &v1.PodAntiAffinity{ - RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{ - { - LabelSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "foo", - Operator: metav1.LabelSelectorOpIn, - Values: []string{"bar", "buzz"}, - }, - }, - }, - TopologyKey: "region", - }, - { - LabelSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "service", - Operator: metav1.LabelSelectorOpNotIn, - Values: []string{"bar", "security", "test"}, - }, - }, - }, - TopologyKey: "zone", - }, - }, - } - affinityComplex := &v1.PodAffinity{ - RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{ - { - LabelSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "foo", - Operator: metav1.LabelSelectorOpIn, - Values: []string{"bar", "buzz"}, - }, - }, - }, - TopologyKey: "region", - }, - { - LabelSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "service", - Operator: metav1.LabelSelectorOpNotIn, - Values: []string{"bar", "security", "test"}, - }, - }, - }, - TopologyKey: "zone", - }, - }, - } tests := []struct { name string @@ -218,39 +137,6 @@ func TestPredicateMetadata_AddRemovePod(t *testing.T) { {ObjectMeta: metav1.ObjectMeta{Name: "nodeC", Labels: label3}}, }, }, - { - name: "metadata anti-affinity terms are updated correctly after adding and removing a pod", - pendingPod: &v1.Pod{ - ObjectMeta: metav1.ObjectMeta{Name: "pending", Labels: selector1}, - }, - existingPods: []*v1.Pod{ - {ObjectMeta: metav1.ObjectMeta{Name: "p1", Labels: selector1}, - Spec: v1.PodSpec{NodeName: "nodeA"}, - }, - {ObjectMeta: metav1.ObjectMeta{Name: "p2"}, - Spec: v1.PodSpec{ - NodeName: "nodeC", - Affinity: &v1.Affinity{ - PodAntiAffinity: antiAffinityFooBar, - }, - }, - }, - }, - addedPod: &v1.Pod{ - ObjectMeta: metav1.ObjectMeta{Name: "addedPod", Labels: selector1}, - Spec: v1.PodSpec{ - NodeName: "nodeB", - Affinity: &v1.Affinity{ - PodAntiAffinity: antiAffinityFooBar, - }, - }, - }, - nodes: []*v1.Node{ - {ObjectMeta: metav1.ObjectMeta{Name: "nodeA", Labels: label1}}, - {ObjectMeta: metav1.ObjectMeta{Name: "nodeB", Labels: label2}}, - {ObjectMeta: metav1.ObjectMeta{Name: "nodeC", Labels: label3}}, - }, - }, { name: "metadata service-affinity data are updated correctly after adding and removing a pod", pendingPod: &v1.Pod{ @@ -275,75 +161,6 @@ func TestPredicateMetadata_AddRemovePod(t *testing.T) { {ObjectMeta: metav1.ObjectMeta{Name: "nodeC", Labels: label3}}, }, }, - { - name: "metadata anti-affinity terms and service affinity data are updated correctly after adding and removing a pod", - pendingPod: &v1.Pod{ - ObjectMeta: metav1.ObjectMeta{Name: "pending", Labels: selector1}, - }, - existingPods: []*v1.Pod{ - {ObjectMeta: metav1.ObjectMeta{Name: "p1", Labels: selector1}, - Spec: v1.PodSpec{NodeName: "nodeA"}, - }, - {ObjectMeta: metav1.ObjectMeta{Name: "p2"}, - Spec: v1.PodSpec{ - NodeName: "nodeC", - Affinity: &v1.Affinity{ - PodAntiAffinity: antiAffinityFooBar, - }, - }, - }, - }, - addedPod: &v1.Pod{ - ObjectMeta: metav1.ObjectMeta{Name: "addedPod", Labels: selector1}, - Spec: v1.PodSpec{ - NodeName: "nodeA", - Affinity: &v1.Affinity{ - PodAntiAffinity: antiAffinityComplex, - }, - }, - }, - services: []*v1.Service{{Spec: v1.ServiceSpec{Selector: selector1}}}, - nodes: []*v1.Node{ - {ObjectMeta: metav1.ObjectMeta{Name: "nodeA", Labels: label1}}, - {ObjectMeta: metav1.ObjectMeta{Name: "nodeB", Labels: label2}}, - {ObjectMeta: metav1.ObjectMeta{Name: "nodeC", Labels: label3}}, - }, - }, - { - name: "metadata matching pod affinity and anti-affinity are updated correctly after adding and removing a pod", - pendingPod: &v1.Pod{ - ObjectMeta: metav1.ObjectMeta{Name: "pending", Labels: selector1}, - }, - existingPods: []*v1.Pod{ - {ObjectMeta: metav1.ObjectMeta{Name: "p1", Labels: selector1}, - Spec: v1.PodSpec{NodeName: "nodeA"}, - }, - {ObjectMeta: metav1.ObjectMeta{Name: "p2"}, - Spec: v1.PodSpec{ - NodeName: "nodeC", - Affinity: &v1.Affinity{ - PodAntiAffinity: antiAffinityFooBar, - PodAffinity: affinityComplex, - }, - }, - }, - }, - addedPod: &v1.Pod{ - ObjectMeta: metav1.ObjectMeta{Name: "addedPod", Labels: selector1}, - Spec: v1.PodSpec{ - NodeName: "nodeA", - Affinity: &v1.Affinity{ - PodAntiAffinity: antiAffinityComplex, - }, - }, - }, - services: []*v1.Service{{Spec: v1.ServiceSpec{Selector: selector1}}}, - nodes: []*v1.Node{ - {ObjectMeta: metav1.ObjectMeta{Name: "nodeA", Labels: label1}}, - {ObjectMeta: metav1.ObjectMeta{Name: "nodeB", Labels: label2}}, - {ObjectMeta: metav1.ObjectMeta{Name: "nodeC", Labels: label3}}, - }, - }, } for _, test := range tests { @@ -385,6 +202,31 @@ func TestPredicateMetadata_AddRemovePod(t *testing.T) { } } +func TestPodAffinityMetadata_Clone(t *testing.T) { + source := &PodAffinityMetadata{ + topologyToMatchedExistingAntiAffinityTerms: topologyToMatchedTermCount{ + {key: "name", value: "machine1"}: 1, + {key: "name", value: "machine2"}: 1, + }, + topologyToMatchedAffinityTerms: topologyToMatchedTermCount{ + {key: "name", value: "nodeA"}: 1, + {key: "name", value: "nodeC"}: 2, + }, + topologyToMatchedAntiAffinityTerms: topologyToMatchedTermCount{ + {key: "name", value: "nodeN"}: 3, + {key: "name", value: "nodeM"}: 1, + }, + } + + clone := source.Clone() + if clone == source { + t.Errorf("Clone returned the exact same object!") + } + if !reflect.DeepEqual(clone, source) { + t.Errorf("Copy is not equal to source!") + } +} + // TestPredicateMetadata_ShallowCopy tests the ShallowCopy function. It is based // on the idea that shallow-copy should produce an object that is deep-equal to the original // object. @@ -415,20 +257,6 @@ func TestPredicateMetadata_ShallowCopy(t *testing.T) { }, }, }, - podAffinityMetadata: &podAffinityMetadata{ - topologyToMatchedExistingAntiAffinityTerms: topologyToMatchedTermCount{ - {key: "name", value: "machine1"}: 1, - {key: "name", value: "machine2"}: 1, - }, - topologyToMatchedAffinityTerms: topologyToMatchedTermCount{ - {key: "name", value: "nodeA"}: 1, - {key: "name", value: "nodeC"}: 2, - }, - topologyToMatchedAntiAffinityTerms: topologyToMatchedTermCount{ - {key: "name", value: "nodeN"}: 3, - {key: "name", value: "nodeM"}: 1, - }, - }, evenPodsSpreadMetadata: &evenPodsSpreadMetadata{ tpKeyToCriticalPaths: map[string]*criticalPaths{ "name": {{"nodeA", 1}, {"nodeC", 2}}, diff --git a/pkg/scheduler/algorithm/predicates/predicates.go b/pkg/scheduler/algorithm/predicates/predicates.go index 152dac7f9e7..689d71abe37 100644 --- a/pkg/scheduler/algorithm/predicates/predicates.go +++ b/pkg/scheduler/algorithm/predicates/predicates.go @@ -1194,23 +1194,30 @@ type PodAffinityChecker struct { podLister schedulerlisters.PodLister } +// NewPodAffinityChecker returns a PodAffinityChecker. +func NewPodAffinityChecker(sharedLister schedulerlisters.SharedLister) *PodAffinityChecker { + return &PodAffinityChecker{ + nodeInfoLister: sharedLister.NodeInfos(), + podLister: sharedLister.Pods(), + } +} + // NewPodAffinityPredicate creates a PodAffinityChecker. func NewPodAffinityPredicate(nodeInfoLister schedulerlisters.NodeInfoLister, podLister schedulerlisters.PodLister) FitPredicate { - checker := &PodAffinityChecker{ - nodeInfoLister: nodeInfoLister, - podLister: podLister, + return func(pod *v1.Pod, meta Metadata, nodeInfo *schedulernodeinfo.NodeInfo) (bool, []PredicateFailureReason, error) { + return false, nil, fmt.Errorf("This function should never be called") } - return checker.InterPodAffinityMatches } // InterPodAffinityMatches checks if a pod can be scheduled on the specified node with pod affinity/anti-affinity configuration. // First return value indicates whether a pod can be scheduled on the specified node while the second return value indicates the // predicate failure reasons if the pod cannot be scheduled on the specified node. -func (c *PodAffinityChecker) InterPodAffinityMatches(pod *v1.Pod, meta Metadata, nodeInfo *schedulernodeinfo.NodeInfo) (bool, []PredicateFailureReason, error) { +func (c *PodAffinityChecker) InterPodAffinityMatches(pod *v1.Pod, meta *PodAffinityMetadata, nodeInfo *schedulernodeinfo.NodeInfo) (bool, []PredicateFailureReason, error) { node := nodeInfo.Node() if node == nil { return false, nil, fmt.Errorf("node not found") } + if failedPredicates, error := c.satisfiesExistingPodsAntiAffinity(pod, meta, nodeInfo); failedPredicates != nil { failedPredicates := append([]PredicateFailureReason{ErrPodAffinityNotMatch}, failedPredicates) return false, failedPredicates, error @@ -1341,14 +1348,14 @@ func (c *PodAffinityChecker) getMatchingAntiAffinityTopologyPairsOfPods(pod *v1. // Checks if scheduling the pod onto this node would break any anti-affinity // terms indicated by the existing pods. -func (c *PodAffinityChecker) satisfiesExistingPodsAntiAffinity(pod *v1.Pod, meta Metadata, nodeInfo *schedulernodeinfo.NodeInfo) (PredicateFailureReason, error) { +func (c *PodAffinityChecker) satisfiesExistingPodsAntiAffinity(pod *v1.Pod, meta *PodAffinityMetadata, nodeInfo *schedulernodeinfo.NodeInfo) (PredicateFailureReason, error) { node := nodeInfo.Node() if node == nil { return ErrExistingPodsAntiAffinityRulesNotMatch, fmt.Errorf("node not found") } var topologyMap topologyToMatchedTermCount - if predicateMeta, ok := meta.(*predicateMetadata); ok { - topologyMap = predicateMeta.podAffinityMetadata.topologyToMatchedExistingAntiAffinityTerms + if meta != nil { + topologyMap = meta.topologyToMatchedExistingAntiAffinityTerms } else { // Filter out pods whose nodeName is equal to nodeInfo.node.Name, but are not // present in nodeInfo. Pods on other nodes pass the filter. @@ -1416,15 +1423,15 @@ func (c *PodAffinityChecker) nodeMatchesAnyTopologyTerm(pod *v1.Pod, topologyPai // satisfiesPodsAffinityAntiAffinity checks if scheduling the pod onto this node would break any term of this pod. func (c *PodAffinityChecker) satisfiesPodsAffinityAntiAffinity(pod *v1.Pod, - meta Metadata, nodeInfo *schedulernodeinfo.NodeInfo, + predicateMeta *PodAffinityMetadata, nodeInfo *schedulernodeinfo.NodeInfo, affinity *v1.Affinity) (PredicateFailureReason, error) { node := nodeInfo.Node() if node == nil { return ErrPodAffinityRulesNotMatch, fmt.Errorf("node not found") } - if predicateMeta, ok := meta.(*predicateMetadata); ok { + if predicateMeta != nil { // Check all affinity terms. - topologyToMatchedAffinityTerms := predicateMeta.podAffinityMetadata.topologyToMatchedAffinityTerms + topologyToMatchedAffinityTerms := predicateMeta.topologyToMatchedAffinityTerms if affinityTerms := GetPodAffinityTerms(affinity.PodAffinity); len(affinityTerms) > 0 { matchExists := c.nodeMatchesAllTopologyTerms(pod, topologyToMatchedAffinityTerms, nodeInfo, affinityTerms) if !matchExists { @@ -1441,7 +1448,7 @@ func (c *PodAffinityChecker) satisfiesPodsAffinityAntiAffinity(pod *v1.Pod, } // Check all anti-affinity terms. - topologyToMatchedAntiAffinityTerms := predicateMeta.podAffinityMetadata.topologyToMatchedAntiAffinityTerms + topologyToMatchedAntiAffinityTerms := predicateMeta.topologyToMatchedAntiAffinityTerms if antiAffinityTerms := GetPodAntiAffinityTerms(affinity.PodAntiAffinity); len(antiAffinityTerms) > 0 { matchExists := c.nodeMatchesAnyTopologyTerm(pod, topologyToMatchedAntiAffinityTerms, nodeInfo, antiAffinityTerms) if matchExists { diff --git a/pkg/scheduler/algorithm/predicates/predicates_test.go b/pkg/scheduler/algorithm/predicates/predicates_test.go index fcbc7080ccb..cf04c51116e 100644 --- a/pkg/scheduler/algorithm/predicates/predicates_test.go +++ b/pkg/scheduler/algorithm/predicates/predicates_test.go @@ -1994,2077 +1994,6 @@ func TestRunGeneralPredicates(t *testing.T) { } } -// TODO: Add test case for RequiredDuringSchedulingRequiredDuringExecution after it's implemented. -func TestInterPodAffinity(t *testing.T) { - podLabel := map[string]string{"service": "securityscan"} - labels1 := map[string]string{ - "region": "r1", - "zone": "z11", - } - podLabel2 := map[string]string{"security": "S1"} - node1 := v1.Node{ObjectMeta: metav1.ObjectMeta{Name: "machine1", Labels: labels1}} - tests := []struct { - pod *v1.Pod - pods []*v1.Pod - node *v1.Node - fits bool - name string - expectFailureReasons []PredicateFailureReason - }{ - { - pod: new(v1.Pod), - node: &node1, - fits: true, - name: "A pod that has no required pod affinity scheduling rules can schedule onto a node with no existing pods", - }, - { - pod: &v1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Labels: podLabel2, - }, - Spec: v1.PodSpec{ - Affinity: &v1.Affinity{ - PodAffinity: &v1.PodAffinity{ - RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{ - { - LabelSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "service", - Operator: metav1.LabelSelectorOpIn, - Values: []string{"securityscan", "value2"}, - }, - }, - }, - TopologyKey: "region", - }, - }, - }, - }, - }, - }, - pods: []*v1.Pod{{Spec: v1.PodSpec{NodeName: "machine1"}, ObjectMeta: metav1.ObjectMeta{Labels: podLabel}}}, - node: &node1, - fits: true, - name: "satisfies with requiredDuringSchedulingIgnoredDuringExecution in PodAffinity using In operator that matches the existing pod", - }, - { - pod: &v1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Labels: podLabel2, - }, - Spec: v1.PodSpec{ - Affinity: &v1.Affinity{ - PodAffinity: &v1.PodAffinity{ - RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{ - { - LabelSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "service", - Operator: metav1.LabelSelectorOpNotIn, - Values: []string{"securityscan3", "value3"}, - }, - }, - }, - TopologyKey: "region", - }, - }, - }, - }, - }, - }, - pods: []*v1.Pod{{Spec: v1.PodSpec{NodeName: "machine1"}, ObjectMeta: metav1.ObjectMeta{Labels: podLabel}}}, - node: &node1, - fits: true, - name: "satisfies the pod with requiredDuringSchedulingIgnoredDuringExecution in PodAffinity using not in operator in labelSelector that matches the existing pod", - }, - { - pod: &v1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Labels: podLabel2, - }, - Spec: v1.PodSpec{ - Affinity: &v1.Affinity{ - PodAffinity: &v1.PodAffinity{ - RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{ - { - LabelSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "service", - Operator: metav1.LabelSelectorOpIn, - Values: []string{"securityscan", "value2"}, - }, - }, - }, - Namespaces: []string{"DiffNameSpace"}, - }, - }, - }, - }, - }, - }, - pods: []*v1.Pod{{Spec: v1.PodSpec{NodeName: "machine1"}, ObjectMeta: metav1.ObjectMeta{Labels: podLabel, Namespace: "ns"}}}, - node: &node1, - fits: false, - name: "Does not satisfy the PodAffinity with labelSelector because of diff Namespace", - expectFailureReasons: []PredicateFailureReason{ErrPodAffinityNotMatch, ErrPodAffinityRulesNotMatch}, - }, - { - pod: &v1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Labels: podLabel, - }, - Spec: v1.PodSpec{ - Affinity: &v1.Affinity{ - PodAffinity: &v1.PodAffinity{ - RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{ - { - LabelSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "service", - Operator: metav1.LabelSelectorOpIn, - Values: []string{"antivirusscan", "value2"}, - }, - }, - }, - }, - }, - }, - }, - }, - }, - pods: []*v1.Pod{{Spec: v1.PodSpec{NodeName: "machine1"}, ObjectMeta: metav1.ObjectMeta{Labels: podLabel}}}, - node: &node1, - fits: false, - name: "Doesn't satisfy the PodAffinity because of unmatching labelSelector with the existing pod", - expectFailureReasons: []PredicateFailureReason{ErrPodAffinityNotMatch, ErrPodAffinityRulesNotMatch}, - }, - { - pod: &v1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Labels: podLabel2, - }, - Spec: v1.PodSpec{ - Affinity: &v1.Affinity{ - PodAffinity: &v1.PodAffinity{ - RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{ - { - LabelSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "service", - Operator: metav1.LabelSelectorOpExists, - }, { - Key: "wrongkey", - Operator: metav1.LabelSelectorOpDoesNotExist, - }, - }, - }, - TopologyKey: "region", - }, { - LabelSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "service", - Operator: metav1.LabelSelectorOpIn, - Values: []string{"securityscan"}, - }, { - Key: "service", - Operator: metav1.LabelSelectorOpNotIn, - Values: []string{"WrongValue"}, - }, - }, - }, - TopologyKey: "region", - }, - }, - }, - }, - }, - }, - pods: []*v1.Pod{{Spec: v1.PodSpec{NodeName: "machine1"}, ObjectMeta: metav1.ObjectMeta{Labels: podLabel}}}, - node: &node1, - fits: true, - name: "satisfies the PodAffinity with different label Operators in multiple RequiredDuringSchedulingIgnoredDuringExecution ", - }, - { - pod: &v1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Labels: podLabel2, - }, - Spec: v1.PodSpec{ - Affinity: &v1.Affinity{ - PodAffinity: &v1.PodAffinity{ - RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{ - { - LabelSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "service", - Operator: metav1.LabelSelectorOpExists, - }, { - Key: "wrongkey", - Operator: metav1.LabelSelectorOpDoesNotExist, - }, - }, - }, - TopologyKey: "region", - }, { - LabelSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "service", - Operator: metav1.LabelSelectorOpIn, - Values: []string{"securityscan2"}, - }, { - Key: "service", - Operator: metav1.LabelSelectorOpNotIn, - Values: []string{"WrongValue"}, - }, - }, - }, - TopologyKey: "region", - }, - }, - }, - }, - }, - }, - pods: []*v1.Pod{{Spec: v1.PodSpec{NodeName: "machine1"}, ObjectMeta: metav1.ObjectMeta{Labels: podLabel}}}, - node: &node1, - fits: false, - name: "The labelSelector requirements(items of matchExpressions) are ANDed, the pod cannot schedule onto the node because one of the matchExpression item don't match.", - expectFailureReasons: []PredicateFailureReason{ErrPodAffinityNotMatch, ErrPodAffinityRulesNotMatch}, - }, - { - pod: &v1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Labels: podLabel2, - }, - Spec: v1.PodSpec{ - Affinity: &v1.Affinity{ - PodAffinity: &v1.PodAffinity{ - RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{ - { - LabelSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "service", - Operator: metav1.LabelSelectorOpIn, - Values: []string{"securityscan", "value2"}, - }, - }, - }, - TopologyKey: "region", - }, - }, - }, - PodAntiAffinity: &v1.PodAntiAffinity{ - RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{ - { - LabelSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "service", - Operator: metav1.LabelSelectorOpIn, - Values: []string{"antivirusscan", "value2"}, - }, - }, - }, - TopologyKey: "node", - }, - }, - }, - }, - }, - }, - pods: []*v1.Pod{{Spec: v1.PodSpec{NodeName: "machine1"}, ObjectMeta: metav1.ObjectMeta{Labels: podLabel}}}, - node: &node1, - fits: true, - name: "satisfies the PodAffinity and PodAntiAffinity with the existing pod", - }, - { - pod: &v1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Labels: podLabel2, - }, - Spec: v1.PodSpec{ - Affinity: &v1.Affinity{ - PodAffinity: &v1.PodAffinity{ - RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{ - { - LabelSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "service", - Operator: metav1.LabelSelectorOpIn, - Values: []string{"securityscan", "value2"}, - }, - }, - }, - TopologyKey: "region", - }, - }, - }, - PodAntiAffinity: &v1.PodAntiAffinity{ - RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{ - { - LabelSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "service", - Operator: metav1.LabelSelectorOpIn, - Values: []string{"antivirusscan", "value2"}, - }, - }, - }, - TopologyKey: "node", - }, - }, - }, - }, - }, - }, - pods: []*v1.Pod{ - { - Spec: v1.PodSpec{ - NodeName: "machine1", - Affinity: &v1.Affinity{ - PodAntiAffinity: &v1.PodAntiAffinity{ - RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{ - { - LabelSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "service", - Operator: metav1.LabelSelectorOpIn, - Values: []string{"antivirusscan", "value2"}, - }, - }, - }, - TopologyKey: "node", - }, - }, - }, - }, - }, - ObjectMeta: metav1.ObjectMeta{Labels: podLabel}, - }, - }, - node: &node1, - fits: true, - name: "satisfies the PodAffinity and PodAntiAffinity and PodAntiAffinity symmetry with the existing pod", - }, - { - pod: &v1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Labels: podLabel2, - }, - Spec: v1.PodSpec{ - Affinity: &v1.Affinity{ - PodAffinity: &v1.PodAffinity{ - RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{ - { - LabelSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "service", - Operator: metav1.LabelSelectorOpIn, - Values: []string{"securityscan", "value2"}, - }, - }, - }, - TopologyKey: "region", - }, - }, - }, - PodAntiAffinity: &v1.PodAntiAffinity{ - RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{ - { - LabelSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "service", - Operator: metav1.LabelSelectorOpIn, - Values: []string{"securityscan", "value2"}, - }, - }, - }, - TopologyKey: "zone", - }, - }, - }, - }, - }, - }, - pods: []*v1.Pod{{Spec: v1.PodSpec{NodeName: "machine1"}, ObjectMeta: metav1.ObjectMeta{Labels: podLabel}}}, - node: &node1, - fits: false, - name: "satisfies the PodAffinity but doesn't satisfy the PodAntiAffinity with the existing pod", - expectFailureReasons: []PredicateFailureReason{ErrPodAffinityNotMatch, ErrPodAntiAffinityRulesNotMatch}, - }, - { - pod: &v1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Labels: podLabel, - }, - Spec: v1.PodSpec{ - Affinity: &v1.Affinity{ - PodAffinity: &v1.PodAffinity{ - RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{ - { - LabelSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "service", - Operator: metav1.LabelSelectorOpIn, - Values: []string{"securityscan", "value2"}, - }, - }, - }, - TopologyKey: "region", - }, - }, - }, - PodAntiAffinity: &v1.PodAntiAffinity{ - RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{ - { - LabelSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "service", - Operator: metav1.LabelSelectorOpIn, - Values: []string{"antivirusscan", "value2"}, - }, - }, - }, - TopologyKey: "node", - }, - }, - }, - }, - }, - }, - pods: []*v1.Pod{ - { - Spec: v1.PodSpec{ - NodeName: "machine1", - Affinity: &v1.Affinity{ - PodAntiAffinity: &v1.PodAntiAffinity{ - RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{ - { - LabelSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "service", - Operator: metav1.LabelSelectorOpIn, - Values: []string{"securityscan", "value2"}, - }, - }, - }, - TopologyKey: "zone", - }, - }, - }, - }, - }, - ObjectMeta: metav1.ObjectMeta{Labels: podLabel}, - }, - }, - node: &node1, - fits: false, - name: "satisfies the PodAffinity and PodAntiAffinity but doesn't satisfy PodAntiAffinity symmetry with the existing pod", - expectFailureReasons: []PredicateFailureReason{ErrPodAffinityNotMatch, ErrExistingPodsAntiAffinityRulesNotMatch}, - }, - { - pod: &v1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Labels: podLabel, - }, - Spec: v1.PodSpec{ - Affinity: &v1.Affinity{ - PodAffinity: &v1.PodAffinity{ - RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{ - { - LabelSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "service", - Operator: metav1.LabelSelectorOpNotIn, - Values: []string{"securityscan", "value2"}, - }, - }, - }, - TopologyKey: "region", - }, - }, - }, - }, - }, - }, - pods: []*v1.Pod{{Spec: v1.PodSpec{NodeName: "machine2"}, ObjectMeta: metav1.ObjectMeta{Labels: podLabel}}}, - node: &node1, - fits: false, - name: "pod matches its own Label in PodAffinity and that matches the existing pod Labels", - expectFailureReasons: []PredicateFailureReason{ErrPodAffinityNotMatch, ErrPodAffinityRulesNotMatch}, - }, - { - pod: &v1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Labels: podLabel, - }, - }, - pods: []*v1.Pod{ - { - Spec: v1.PodSpec{NodeName: "machine1", - Affinity: &v1.Affinity{ - PodAntiAffinity: &v1.PodAntiAffinity{ - RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{ - { - LabelSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "service", - Operator: metav1.LabelSelectorOpIn, - Values: []string{"securityscan", "value2"}, - }, - }, - }, - TopologyKey: "zone", - }, - }, - }, - }, - }, - ObjectMeta: metav1.ObjectMeta{Labels: podLabel}, - }, - }, - node: &node1, - fits: false, - name: "verify that PodAntiAffinity from existing pod is respected when pod has no AntiAffinity constraints. doesn't satisfy PodAntiAffinity symmetry with the existing pod", - expectFailureReasons: []PredicateFailureReason{ErrPodAffinityNotMatch, ErrExistingPodsAntiAffinityRulesNotMatch}, - }, - { - pod: &v1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Labels: podLabel, - }, - }, - pods: []*v1.Pod{ - { - Spec: v1.PodSpec{NodeName: "machine1", - Affinity: &v1.Affinity{ - PodAntiAffinity: &v1.PodAntiAffinity{ - RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{ - { - LabelSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "service", - Operator: metav1.LabelSelectorOpNotIn, - Values: []string{"securityscan", "value2"}, - }, - }, - }, - TopologyKey: "zone", - }, - }, - }, - }, - }, - ObjectMeta: metav1.ObjectMeta{Labels: podLabel}, - }, - }, - node: &node1, - fits: true, - name: "verify that PodAntiAffinity from existing pod is respected when pod has no AntiAffinity constraints. satisfy PodAntiAffinity symmetry with the existing pod", - }, - { - pod: &v1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Labels: podLabel, - }, - Spec: v1.PodSpec{ - Affinity: &v1.Affinity{ - PodAntiAffinity: &v1.PodAntiAffinity{ - RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{ - { - LabelSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "service", - Operator: metav1.LabelSelectorOpExists, - }, - }, - }, - TopologyKey: "region", - }, - { - LabelSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "security", - Operator: metav1.LabelSelectorOpExists, - }, - }, - }, - TopologyKey: "region", - }, - }, - }, - }, - }, - }, - pods: []*v1.Pod{ - { - ObjectMeta: metav1.ObjectMeta{Labels: podLabel2}, - Spec: v1.PodSpec{NodeName: "machine1", - Affinity: &v1.Affinity{ - PodAntiAffinity: &v1.PodAntiAffinity{ - RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{ - { - LabelSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "security", - Operator: metav1.LabelSelectorOpExists, - }, - }, - }, - TopologyKey: "zone", - }, - }, - }, - }, - }, - }, - }, - node: &node1, - fits: false, - name: "satisfies the PodAntiAffinity with existing pod but doesn't satisfy PodAntiAffinity symmetry with incoming pod", - expectFailureReasons: []PredicateFailureReason{ErrPodAffinityNotMatch, ErrPodAntiAffinityRulesNotMatch}, - }, - { - pod: &v1.Pod{ - ObjectMeta: metav1.ObjectMeta{Labels: podLabel}, - Spec: v1.PodSpec{ - Affinity: &v1.Affinity{ - PodAntiAffinity: &v1.PodAntiAffinity{ - RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{ - { - LabelSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "service", - Operator: metav1.LabelSelectorOpExists, - }, - }, - }, - TopologyKey: "zone", - }, - { - LabelSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "security", - Operator: metav1.LabelSelectorOpExists, - }, - }, - }, - TopologyKey: "zone", - }, - }, - }, - }, - }, - }, - pods: []*v1.Pod{ - { - ObjectMeta: metav1.ObjectMeta{Labels: podLabel2}, - Spec: v1.PodSpec{ - NodeName: "machine1", - Affinity: &v1.Affinity{ - PodAntiAffinity: &v1.PodAntiAffinity{ - RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{ - { - LabelSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "security", - Operator: metav1.LabelSelectorOpExists, - }, - }, - }, - TopologyKey: "zone", - }, - }, - }, - }, - }, - }, - }, - node: &node1, - fits: false, - expectFailureReasons: []PredicateFailureReason{ErrPodAffinityNotMatch, ErrPodAntiAffinityRulesNotMatch}, - name: "PodAntiAffinity symmetry check a1: incoming pod and existing pod partially match each other on AffinityTerms", - }, - { - pod: &v1.Pod{ - ObjectMeta: metav1.ObjectMeta{Labels: podLabel2}, - Spec: v1.PodSpec{ - Affinity: &v1.Affinity{ - PodAntiAffinity: &v1.PodAntiAffinity{ - RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{ - { - LabelSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "security", - Operator: metav1.LabelSelectorOpExists, - }, - }, - }, - TopologyKey: "zone", - }, - }, - }, - }, - }, - }, - pods: []*v1.Pod{ - { - ObjectMeta: metav1.ObjectMeta{Labels: podLabel}, - Spec: v1.PodSpec{ - NodeName: "machine1", - Affinity: &v1.Affinity{ - PodAntiAffinity: &v1.PodAntiAffinity{ - RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{ - { - LabelSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "service", - Operator: metav1.LabelSelectorOpExists, - }, - }, - }, - TopologyKey: "zone", - }, - { - LabelSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "security", - Operator: metav1.LabelSelectorOpExists, - }, - }, - }, - TopologyKey: "zone", - }, - }, - }, - }, - }, - }, - }, - node: &node1, - fits: false, - expectFailureReasons: []PredicateFailureReason{ErrPodAffinityNotMatch, ErrExistingPodsAntiAffinityRulesNotMatch}, - name: "PodAntiAffinity symmetry check a2: incoming pod and existing pod partially match each other on AffinityTerms", - }, - { - pod: &v1.Pod{ - ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"abc": "", "xyz": ""}}, - Spec: v1.PodSpec{ - Affinity: &v1.Affinity{ - PodAntiAffinity: &v1.PodAntiAffinity{ - RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{ - { - LabelSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "abc", - Operator: metav1.LabelSelectorOpExists, - }, - }, - }, - TopologyKey: "zone", - }, - { - LabelSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "def", - Operator: metav1.LabelSelectorOpExists, - }, - }, - }, - TopologyKey: "zone", - }, - }, - }, - }, - }, - }, - pods: []*v1.Pod{ - { - ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"def": "", "xyz": ""}}, - Spec: v1.PodSpec{ - NodeName: "machine1", - Affinity: &v1.Affinity{ - PodAntiAffinity: &v1.PodAntiAffinity{ - RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{ - { - LabelSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "abc", - Operator: metav1.LabelSelectorOpExists, - }, - }, - }, - TopologyKey: "zone", - }, - { - LabelSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "def", - Operator: metav1.LabelSelectorOpExists, - }, - }, - }, - TopologyKey: "zone", - }, - }, - }, - }, - }, - }, - }, - node: &node1, - fits: false, - expectFailureReasons: []PredicateFailureReason{ErrPodAffinityNotMatch, ErrExistingPodsAntiAffinityRulesNotMatch}, - name: "PodAntiAffinity symmetry check b1: incoming pod and existing pod partially match each other on AffinityTerms", - }, - { - pod: &v1.Pod{ - ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"def": "", "xyz": ""}}, - Spec: v1.PodSpec{ - Affinity: &v1.Affinity{ - PodAntiAffinity: &v1.PodAntiAffinity{ - RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{ - { - LabelSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "abc", - Operator: metav1.LabelSelectorOpExists, - }, - }, - }, - TopologyKey: "zone", - }, - { - LabelSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "def", - Operator: metav1.LabelSelectorOpExists, - }, - }, - }, - TopologyKey: "zone", - }, - }, - }, - }, - }, - }, - pods: []*v1.Pod{ - { - ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"abc": "", "xyz": ""}}, - Spec: v1.PodSpec{ - NodeName: "machine1", - Affinity: &v1.Affinity{ - PodAntiAffinity: &v1.PodAntiAffinity{ - RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{ - { - LabelSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "abc", - Operator: metav1.LabelSelectorOpExists, - }, - }, - }, - TopologyKey: "zone", - }, - { - LabelSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "def", - Operator: metav1.LabelSelectorOpExists, - }, - }, - }, - TopologyKey: "zone", - }, - }, - }, - }, - }, - }, - }, - node: &node1, - fits: false, - expectFailureReasons: []PredicateFailureReason{ErrPodAffinityNotMatch, ErrExistingPodsAntiAffinityRulesNotMatch}, - name: "PodAntiAffinity symmetry check b2: incoming pod and existing pod partially match each other on AffinityTerms", - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - s := nodeinfosnapshot.NewSnapshot(nodeinfosnapshot.CreateNodeInfoMap(test.pods, []*v1.Node{test.node})) - - fit := PodAffinityChecker{ - nodeInfoLister: s.NodeInfos(), - podLister: fakelisters.PodLister(test.pods), - } - factory := &MetadataProducerFactory{} - fits, reasons, _ := fit.InterPodAffinityMatches(test.pod, factory.GetPredicateMetadata(test.pod, s), s.NodeInfoMap[test.node.Name]) - if !fits && !reflect.DeepEqual(reasons, test.expectFailureReasons) { - t.Errorf("unexpected failure reasons: %v, want: %v", reasons, test.expectFailureReasons) - } - if fits != test.fits { - t.Errorf("expected %v got %v", test.fits, fits) - } - }) - } -} - -func TestInterPodAffinityWithMultipleNodes(t *testing.T) { - podLabelA := map[string]string{ - "foo": "bar", - } - labelRgChina := map[string]string{ - "region": "China", - } - labelRgChinaAzAz1 := map[string]string{ - "region": "China", - "az": "az1", - } - labelRgIndia := map[string]string{ - "region": "India", - } - labelRgUS := map[string]string{ - "region": "US", - } - - tests := []struct { - pod *v1.Pod - pods []*v1.Pod - nodes []*v1.Node - nodesExpectAffinityFailureReasons [][]PredicateFailureReason - fits map[string]bool - name string - nometa bool - }{ - { - pod: &v1.Pod{ - Spec: v1.PodSpec{ - Affinity: &v1.Affinity{ - PodAffinity: &v1.PodAffinity{ - RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{ - { - LabelSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "foo", - Operator: metav1.LabelSelectorOpIn, - Values: []string{"bar"}, - }, - }, - }, - TopologyKey: "region", - }, - }, - }, - }, - }, - }, - pods: []*v1.Pod{ - {Spec: v1.PodSpec{NodeName: "machine1"}, ObjectMeta: metav1.ObjectMeta{Name: "p1", Labels: podLabelA}}, - }, - nodes: []*v1.Node{ - {ObjectMeta: metav1.ObjectMeta{Name: "machine1", Labels: labelRgChina}}, - {ObjectMeta: metav1.ObjectMeta{Name: "machine2", Labels: labelRgChinaAzAz1}}, - {ObjectMeta: metav1.ObjectMeta{Name: "machine3", Labels: labelRgIndia}}, - }, - fits: map[string]bool{ - "machine1": true, - "machine2": true, - "machine3": false, - }, - nodesExpectAffinityFailureReasons: [][]PredicateFailureReason{nil, nil, {ErrPodAffinityNotMatch, ErrPodAffinityRulesNotMatch}}, - name: "A pod can be scheduled onto all the nodes that have the same topology key & label value with one of them has an existing pod that matches the affinity rules", - }, - { - pod: &v1.Pod{ - Spec: v1.PodSpec{ - Affinity: &v1.Affinity{ - NodeAffinity: &v1.NodeAffinity{ - RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{ - NodeSelectorTerms: []v1.NodeSelectorTerm{ - { - MatchExpressions: []v1.NodeSelectorRequirement{ - { - Key: "hostname", - Operator: v1.NodeSelectorOpNotIn, - Values: []string{"h1"}, - }, - }, - }, - }, - }, - }, - PodAffinity: &v1.PodAffinity{ - RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{ - { - LabelSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "foo", - Operator: metav1.LabelSelectorOpIn, - Values: []string{"abc"}, - }, - }, - }, - TopologyKey: "region", - }, - }, - }, - }, - }, - }, - pods: []*v1.Pod{ - {Spec: v1.PodSpec{NodeName: "nodeA"}, ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"foo": "abc"}}}, - {Spec: v1.PodSpec{NodeName: "nodeB"}, ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"foo": "def"}}}, - }, - nodes: []*v1.Node{ - {ObjectMeta: metav1.ObjectMeta{Name: "nodeA", Labels: map[string]string{"region": "r1", "hostname": "h1"}}}, - {ObjectMeta: metav1.ObjectMeta{Name: "nodeB", Labels: map[string]string{"region": "r1", "hostname": "h2"}}}, - }, - nodesExpectAffinityFailureReasons: [][]PredicateFailureReason{nil, nil}, - fits: map[string]bool{ - "nodeA": false, - "nodeB": true, - }, - name: "NodeA and nodeB have same topologyKey and label value. NodeA does not satisfy node affinity rule, but has an existing pod that matches the inter pod affinity rule. The pod can be scheduled onto nodeB.", - }, - { - pod: &v1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{ - "foo": "bar", - "service": "securityscan", - }, - }, - Spec: v1.PodSpec{ - Affinity: &v1.Affinity{ - PodAffinity: &v1.PodAffinity{ - RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{ - { - LabelSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "foo", - Operator: metav1.LabelSelectorOpIn, - Values: []string{"bar"}, - }, - }, - }, - TopologyKey: "zone", - }, - { - LabelSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "service", - Operator: metav1.LabelSelectorOpIn, - Values: []string{"securityscan"}, - }, - }, - }, - TopologyKey: "zone", - }, - }, - }, - }, - }, - }, - pods: []*v1.Pod{{Spec: v1.PodSpec{NodeName: "nodeA"}, ObjectMeta: metav1.ObjectMeta{Name: "p1", Labels: map[string]string{"foo": "bar"}}}}, - nodes: []*v1.Node{ - {ObjectMeta: metav1.ObjectMeta{Name: "nodeA", Labels: map[string]string{"zone": "az1", "hostname": "h1"}}}, - {ObjectMeta: metav1.ObjectMeta{Name: "nodeB", Labels: map[string]string{"zone": "az2", "hostname": "h2"}}}, - }, - nodesExpectAffinityFailureReasons: [][]PredicateFailureReason{nil, nil}, - fits: map[string]bool{ - "nodeA": true, - "nodeB": true, - }, - name: "The affinity rule is to schedule all of the pods of this collection to the same zone. The first pod of the collection " + - "should not be blocked from being scheduled onto any node, even there's no existing pod that matches the rule anywhere.", - }, - { - pod: &v1.Pod{ - Spec: v1.PodSpec{ - Affinity: &v1.Affinity{ - PodAntiAffinity: &v1.PodAntiAffinity{ - RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{ - { - LabelSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "foo", - Operator: metav1.LabelSelectorOpIn, - Values: []string{"abc"}, - }, - }, - }, - TopologyKey: "region", - }, - }, - }, - }, - }, - }, - pods: []*v1.Pod{ - {Spec: v1.PodSpec{NodeName: "nodeA"}, ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"foo": "abc"}}}, - }, - nodes: []*v1.Node{ - {ObjectMeta: metav1.ObjectMeta{Name: "nodeA", Labels: map[string]string{"region": "r1", "hostname": "nodeA"}}}, - {ObjectMeta: metav1.ObjectMeta{Name: "nodeB", Labels: map[string]string{"region": "r1", "hostname": "nodeB"}}}, - }, - nodesExpectAffinityFailureReasons: [][]PredicateFailureReason{{ErrPodAffinityNotMatch, ErrPodAntiAffinityRulesNotMatch}, {ErrPodAffinityNotMatch, ErrPodAntiAffinityRulesNotMatch}}, - fits: map[string]bool{ - "nodeA": false, - "nodeB": false, - }, - name: "NodeA and nodeB have same topologyKey and label value. NodeA has an existing pod that matches the inter pod affinity rule. The pod can not be scheduled onto nodeA and nodeB.", - }, - { - pod: &v1.Pod{ - Spec: v1.PodSpec{ - Affinity: &v1.Affinity{ - PodAntiAffinity: &v1.PodAntiAffinity{ - RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{ - { - LabelSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "foo", - Operator: metav1.LabelSelectorOpIn, - Values: []string{"abc"}, - }, - }, - }, - TopologyKey: "region", - }, - { - LabelSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "service", - Operator: metav1.LabelSelectorOpIn, - Values: []string{"securityscan"}, - }, - }, - }, - TopologyKey: "zone", - }, - }, - }, - }, - }, - }, - pods: []*v1.Pod{ - {Spec: v1.PodSpec{NodeName: "nodeA"}, ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"foo": "abc", "service": "securityscan"}}}, - }, - nodes: []*v1.Node{ - {ObjectMeta: metav1.ObjectMeta{Name: "nodeA", Labels: map[string]string{"region": "r1", "zone": "z1", "hostname": "nodeA"}}}, - {ObjectMeta: metav1.ObjectMeta{Name: "nodeB", Labels: map[string]string{"region": "r1", "zone": "z2", "hostname": "nodeB"}}}, - }, - nodesExpectAffinityFailureReasons: [][]PredicateFailureReason{ - {ErrPodAffinityNotMatch, ErrPodAntiAffinityRulesNotMatch}, - {ErrPodAffinityNotMatch, ErrPodAntiAffinityRulesNotMatch}, - }, - fits: map[string]bool{ - "nodeA": false, - "nodeB": false, - }, - name: "This test ensures that anti-affinity matches a pod when any term of the anti-affinity rule matches a pod.", - }, - { - pod: &v1.Pod{ - Spec: v1.PodSpec{ - Affinity: &v1.Affinity{ - PodAntiAffinity: &v1.PodAntiAffinity{ - RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{ - { - LabelSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "foo", - Operator: metav1.LabelSelectorOpIn, - Values: []string{"abc"}, - }, - }, - }, - TopologyKey: "region", - }, - }, - }, - }, - }, - }, - pods: []*v1.Pod{ - {Spec: v1.PodSpec{NodeName: "nodeA"}, ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"foo": "abc"}}}, - }, - nodes: []*v1.Node{ - {ObjectMeta: metav1.ObjectMeta{Name: "nodeA", Labels: labelRgChina}}, - {ObjectMeta: metav1.ObjectMeta{Name: "nodeB", Labels: labelRgChinaAzAz1}}, - {ObjectMeta: metav1.ObjectMeta{Name: "nodeC", Labels: labelRgIndia}}, - }, - nodesExpectAffinityFailureReasons: [][]PredicateFailureReason{{ErrPodAffinityNotMatch, ErrPodAntiAffinityRulesNotMatch}, {ErrPodAffinityNotMatch, ErrPodAntiAffinityRulesNotMatch}, nil}, - fits: map[string]bool{ - "nodeA": false, - "nodeB": false, - "nodeC": true, - }, - name: "NodeA and nodeB have same topologyKey and label value. NodeA has an existing pod that matches the inter pod affinity rule. The pod can not be scheduled onto nodeA and nodeB but can be scheduled onto nodeC", - }, - { - pod: &v1.Pod{ - ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"foo": "123"}}, - Spec: v1.PodSpec{ - Affinity: &v1.Affinity{ - PodAntiAffinity: &v1.PodAntiAffinity{ - RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{ - { - LabelSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "foo", - Operator: metav1.LabelSelectorOpIn, - Values: []string{"bar"}, - }, - }, - }, - TopologyKey: "region", - }, - }, - }, - }, - }, - }, - pods: []*v1.Pod{ - {Spec: v1.PodSpec{NodeName: "nodeA"}, ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"foo": "bar"}}}, - { - Spec: v1.PodSpec{ - NodeName: "nodeC", - Affinity: &v1.Affinity{ - PodAntiAffinity: &v1.PodAntiAffinity{ - RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{ - { - LabelSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "foo", - Operator: metav1.LabelSelectorOpIn, - Values: []string{"123"}, - }, - }, - }, - TopologyKey: "region", - }, - }, - }, - }, - }, - }, - }, - nodes: []*v1.Node{ - {ObjectMeta: metav1.ObjectMeta{Name: "nodeA", Labels: labelRgChina}}, - {ObjectMeta: metav1.ObjectMeta{Name: "nodeB", Labels: labelRgChinaAzAz1}}, - {ObjectMeta: metav1.ObjectMeta{Name: "nodeC", Labels: labelRgIndia}}, - {ObjectMeta: metav1.ObjectMeta{Name: "nodeD", Labels: labelRgUS}}, - }, - nodesExpectAffinityFailureReasons: [][]PredicateFailureReason{ - {ErrPodAffinityNotMatch, ErrPodAntiAffinityRulesNotMatch}, - {ErrPodAffinityNotMatch, ErrPodAntiAffinityRulesNotMatch}, - {ErrPodAffinityNotMatch, ErrExistingPodsAntiAffinityRulesNotMatch}, - nil, - }, - fits: map[string]bool{ - "nodeA": false, - "nodeB": false, - "nodeC": false, - "nodeD": true, - }, - name: "NodeA and nodeB have same topologyKey and label value. NodeA has an existing pod that matches the inter pod affinity rule. NodeC has an existing pod that match the inter pod affinity rule. The pod can not be scheduled onto nodeA, nodeB and nodeC but can be schedulerd onto nodeD", - nometa: true, - }, - { - pod: &v1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{"foo": "123"}, - Namespace: "NS1", - }, - Spec: v1.PodSpec{ - Affinity: &v1.Affinity{ - PodAntiAffinity: &v1.PodAntiAffinity{ - RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{ - { - LabelSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "foo", - Operator: metav1.LabelSelectorOpIn, - Values: []string{"bar"}, - }, - }, - }, - TopologyKey: "region", - }, - }, - }, - }, - }, - }, - pods: []*v1.Pod{ - { - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{"foo": "bar"}, - Namespace: "NS1", - }, - Spec: v1.PodSpec{NodeName: "nodeA"}, - }, - { - ObjectMeta: metav1.ObjectMeta{Namespace: "NS2"}, - Spec: v1.PodSpec{ - NodeName: "nodeC", - Affinity: &v1.Affinity{ - PodAntiAffinity: &v1.PodAntiAffinity{ - RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{ - { - LabelSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "foo", - Operator: metav1.LabelSelectorOpIn, - Values: []string{"123"}, - }, - }, - }, - TopologyKey: "region", - }, - }, - }, - }, - }, - }, - }, - nodes: []*v1.Node{ - {ObjectMeta: metav1.ObjectMeta{Name: "nodeA", Labels: labelRgChina}}, - {ObjectMeta: metav1.ObjectMeta{Name: "nodeB", Labels: labelRgChinaAzAz1}}, - {ObjectMeta: metav1.ObjectMeta{Name: "nodeC", Labels: labelRgIndia}}, - }, - nodesExpectAffinityFailureReasons: [][]PredicateFailureReason{ - {ErrPodAffinityNotMatch, ErrPodAntiAffinityRulesNotMatch}, - {ErrPodAffinityNotMatch, ErrPodAntiAffinityRulesNotMatch}, - nil, - }, - fits: map[string]bool{ - "nodeA": false, - "nodeB": false, - "nodeC": true, - }, - name: "NodeA and nodeB have same topologyKey and label value. NodeA has an existing pod that matches the inter pod affinity rule. The pod can not be scheduled onto nodeA, nodeB, but can be scheduled onto nodeC (NodeC has an existing pod that match the inter pod affinity rule but in different namespace)", - }, - { - pod: &v1.Pod{ - ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"foo": ""}}, - }, - pods: []*v1.Pod{ - { - Spec: v1.PodSpec{ - NodeName: "nodeA", - Affinity: &v1.Affinity{ - PodAntiAffinity: &v1.PodAntiAffinity{ - RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{ - { - LabelSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "foo", - Operator: metav1.LabelSelectorOpExists, - }, - }, - }, - TopologyKey: "invalid-node-label", - }, - }, - }, - }, - }, - }, - }, - nodes: []*v1.Node{ - {ObjectMeta: metav1.ObjectMeta{Name: "nodeA", Labels: map[string]string{"region": "r1", "zone": "z1", "hostname": "nodeA"}}}, - {ObjectMeta: metav1.ObjectMeta{Name: "nodeB", Labels: map[string]string{"region": "r1", "zone": "z1", "hostname": "nodeB"}}}, - }, - nodesExpectAffinityFailureReasons: [][]PredicateFailureReason{}, - fits: map[string]bool{ - "nodeA": true, - "nodeB": true, - }, - name: "Test existing pod's anti-affinity: if an existing pod has a term with invalid topologyKey, labelSelector of the term is firstly checked, and then topologyKey of the term is also checked", - }, - { - pod: &v1.Pod{ - Spec: v1.PodSpec{ - Affinity: &v1.Affinity{ - PodAntiAffinity: &v1.PodAntiAffinity{ - RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{ - { - LabelSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "foo", - Operator: metav1.LabelSelectorOpExists, - }, - }, - }, - TopologyKey: "invalid-node-label", - }, - }, - }, - }, - }, - }, - pods: []*v1.Pod{ - { - ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"foo": ""}}, - Spec: v1.PodSpec{ - NodeName: "nodeA", - }, - }, - }, - nodes: []*v1.Node{ - {ObjectMeta: metav1.ObjectMeta{Name: "nodeA", Labels: map[string]string{"region": "r1", "zone": "z1", "hostname": "nodeA"}}}, - {ObjectMeta: metav1.ObjectMeta{Name: "nodeB", Labels: map[string]string{"region": "r1", "zone": "z1", "hostname": "nodeB"}}}, - }, - nodesExpectAffinityFailureReasons: [][]PredicateFailureReason{}, - fits: map[string]bool{ - "nodeA": true, - "nodeB": true, - }, - name: "Test incoming pod's anti-affinity: even if labelSelector matches, we still check if topologyKey matches", - }, - { - pod: &v1.Pod{ - ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"foo": "", "bar": ""}}, - }, - pods: []*v1.Pod{ - { - ObjectMeta: metav1.ObjectMeta{Name: "pod1"}, - Spec: v1.PodSpec{ - NodeName: "nodeA", - Affinity: &v1.Affinity{ - PodAntiAffinity: &v1.PodAntiAffinity{ - RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{ - { - LabelSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "foo", - Operator: metav1.LabelSelectorOpExists, - }, - }, - }, - TopologyKey: "zone", - }, - }, - }, - }, - }, - }, - { - ObjectMeta: metav1.ObjectMeta{Name: "pod2"}, - Spec: v1.PodSpec{ - NodeName: "nodeA", - Affinity: &v1.Affinity{ - PodAntiAffinity: &v1.PodAntiAffinity{ - RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{ - { - LabelSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "bar", - Operator: metav1.LabelSelectorOpExists, - }, - }, - }, - TopologyKey: "region", - }, - }, - }, - }, - }, - }, - }, - nodes: []*v1.Node{ - {ObjectMeta: metav1.ObjectMeta{Name: "nodeA", Labels: map[string]string{"region": "r1", "zone": "z1", "hostname": "nodeA"}}}, - {ObjectMeta: metav1.ObjectMeta{Name: "nodeB", Labels: map[string]string{"region": "r1", "zone": "z2", "hostname": "nodeB"}}}, - }, - nodesExpectAffinityFailureReasons: [][]PredicateFailureReason{ - {ErrPodAffinityNotMatch, ErrExistingPodsAntiAffinityRulesNotMatch}, - {ErrPodAffinityNotMatch, ErrExistingPodsAntiAffinityRulesNotMatch}, - }, - fits: map[string]bool{ - "nodeA": false, - "nodeB": false, - }, - name: "Test existing pod's anti-affinity: incoming pod wouldn't considered as a fit as it violates each existingPod's terms on all nodes", - }, - { - pod: &v1.Pod{ - Spec: v1.PodSpec{ - Affinity: &v1.Affinity{ - PodAntiAffinity: &v1.PodAntiAffinity{ - RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{ - { - LabelSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "foo", - Operator: metav1.LabelSelectorOpExists, - }, - }, - }, - TopologyKey: "zone", - }, - { - LabelSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "bar", - Operator: metav1.LabelSelectorOpExists, - }, - }, - }, - TopologyKey: "region", - }, - }, - }, - }, - }, - }, - pods: []*v1.Pod{ - { - ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"foo": ""}}, - Spec: v1.PodSpec{ - NodeName: "nodeA", - }, - }, - { - ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"bar": ""}}, - Spec: v1.PodSpec{ - NodeName: "nodeB", - }, - }, - }, - nodes: []*v1.Node{ - {ObjectMeta: metav1.ObjectMeta{Name: "nodeA", Labels: map[string]string{"region": "r1", "zone": "z1", "hostname": "nodeA"}}}, - {ObjectMeta: metav1.ObjectMeta{Name: "nodeB", Labels: map[string]string{"region": "r1", "zone": "z2", "hostname": "nodeB"}}}, - }, - nodesExpectAffinityFailureReasons: [][]PredicateFailureReason{ - {ErrPodAffinityNotMatch, ErrPodAntiAffinityRulesNotMatch}, - {ErrPodAffinityNotMatch, ErrPodAntiAffinityRulesNotMatch}, - }, - fits: map[string]bool{ - "nodeA": false, - "nodeB": false, - }, - name: "Test incoming pod's anti-affinity: incoming pod wouldn't considered as a fit as it at least violates one anti-affinity rule of existingPod", - }, - { - pod: &v1.Pod{ - ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"foo": "", "bar": ""}}, - }, - pods: []*v1.Pod{ - { - Spec: v1.PodSpec{ - NodeName: "nodeA", - Affinity: &v1.Affinity{ - PodAntiAffinity: &v1.PodAntiAffinity{ - RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{ - { - LabelSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "foo", - Operator: metav1.LabelSelectorOpExists, - }, - }, - }, - TopologyKey: "invalid-node-label", - }, - { - LabelSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "bar", - Operator: metav1.LabelSelectorOpExists, - }, - }, - }, - TopologyKey: "zone", - }, - }, - }, - }, - }, - }, - }, - nodes: []*v1.Node{ - {ObjectMeta: metav1.ObjectMeta{Name: "nodeA", Labels: map[string]string{"region": "r1", "zone": "z1", "hostname": "nodeA"}}}, - {ObjectMeta: metav1.ObjectMeta{Name: "nodeB", Labels: map[string]string{"region": "r1", "zone": "z2", "hostname": "nodeB"}}}, - }, - nodesExpectAffinityFailureReasons: [][]PredicateFailureReason{ - {ErrPodAffinityNotMatch, ErrExistingPodsAntiAffinityRulesNotMatch}, - }, - fits: map[string]bool{ - "nodeA": false, - "nodeB": true, - }, - name: "Test existing pod's anti-affinity: only when labelSelector and topologyKey both match, it's counted as a single term match - case when one term has invalid topologyKey", - }, - { - pod: &v1.Pod{ - Spec: v1.PodSpec{ - Affinity: &v1.Affinity{ - PodAntiAffinity: &v1.PodAntiAffinity{ - RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{ - { - LabelSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "foo", - Operator: metav1.LabelSelectorOpExists, - }, - }, - }, - TopologyKey: "invalid-node-label", - }, - { - LabelSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "bar", - Operator: metav1.LabelSelectorOpExists, - }, - }, - }, - TopologyKey: "zone", - }, - }, - }, - }, - }, - }, - pods: []*v1.Pod{ - { - ObjectMeta: metav1.ObjectMeta{Name: "podA", Labels: map[string]string{"foo": "", "bar": ""}}, - Spec: v1.PodSpec{ - NodeName: "nodeA", - }, - }, - }, - nodes: []*v1.Node{ - {ObjectMeta: metav1.ObjectMeta{Name: "nodeA", Labels: map[string]string{"region": "r1", "zone": "z1", "hostname": "nodeA"}}}, - {ObjectMeta: metav1.ObjectMeta{Name: "nodeB", Labels: map[string]string{"region": "r1", "zone": "z2", "hostname": "nodeB"}}}, - }, - nodesExpectAffinityFailureReasons: [][]PredicateFailureReason{ - {ErrPodAffinityNotMatch, ErrPodAntiAffinityRulesNotMatch}, - }, - fits: map[string]bool{ - "nodeA": false, - "nodeB": true, - }, - name: "Test incoming pod's anti-affinity: only when labelSelector and topologyKey both match, it's counted as a single term match - case when one term has invalid topologyKey", - }, - { - pod: &v1.Pod{ - ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"foo": "", "bar": ""}}, - }, - pods: []*v1.Pod{ - { - Spec: v1.PodSpec{ - NodeName: "nodeA", - Affinity: &v1.Affinity{ - PodAntiAffinity: &v1.PodAntiAffinity{ - RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{ - { - LabelSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "foo", - Operator: metav1.LabelSelectorOpExists, - }, - }, - }, - TopologyKey: "region", - }, - { - LabelSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "bar", - Operator: metav1.LabelSelectorOpExists, - }, - }, - }, - TopologyKey: "zone", - }, - }, - }, - }, - }, - }, - }, - nodes: []*v1.Node{ - {ObjectMeta: metav1.ObjectMeta{Name: "nodeA", Labels: map[string]string{"region": "r1", "zone": "z1", "hostname": "nodeA"}}}, - {ObjectMeta: metav1.ObjectMeta{Name: "nodeB", Labels: map[string]string{"region": "r1", "zone": "z2", "hostname": "nodeB"}}}, - }, - nodesExpectAffinityFailureReasons: [][]PredicateFailureReason{ - {ErrPodAffinityNotMatch, ErrExistingPodsAntiAffinityRulesNotMatch}, - {ErrPodAffinityNotMatch, ErrExistingPodsAntiAffinityRulesNotMatch}, - }, - fits: map[string]bool{ - "nodeA": false, - "nodeB": false, - }, - name: "Test existing pod's anti-affinity: only when labelSelector and topologyKey both match, it's counted as a single term match - case when all terms have valid topologyKey", - }, - { - pod: &v1.Pod{ - Spec: v1.PodSpec{ - Affinity: &v1.Affinity{ - PodAntiAffinity: &v1.PodAntiAffinity{ - RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{ - { - LabelSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "foo", - Operator: metav1.LabelSelectorOpExists, - }, - }, - }, - TopologyKey: "region", - }, - { - LabelSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "bar", - Operator: metav1.LabelSelectorOpExists, - }, - }, - }, - TopologyKey: "zone", - }, - }, - }, - }, - }, - }, - pods: []*v1.Pod{ - { - ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"foo": "", "bar": ""}}, - Spec: v1.PodSpec{ - NodeName: "nodeA", - }, - }, - }, - nodes: []*v1.Node{ - {ObjectMeta: metav1.ObjectMeta{Name: "nodeA", Labels: map[string]string{"region": "r1", "zone": "z1", "hostname": "nodeA"}}}, - {ObjectMeta: metav1.ObjectMeta{Name: "nodeB", Labels: map[string]string{"region": "r1", "zone": "z2", "hostname": "nodeB"}}}, - }, - nodesExpectAffinityFailureReasons: [][]PredicateFailureReason{ - {ErrPodAffinityNotMatch, ErrPodAntiAffinityRulesNotMatch}, - {ErrPodAffinityNotMatch, ErrPodAntiAffinityRulesNotMatch}, - }, - fits: map[string]bool{ - "nodeA": false, - "nodeB": false, - }, - name: "Test incoming pod's anti-affinity: only when labelSelector and topologyKey both match, it's counted as a single term match - case when all terms have valid topologyKey", - }, - { - pod: &v1.Pod{ - ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"foo": "", "bar": ""}}, - }, - pods: []*v1.Pod{ - { - Spec: v1.PodSpec{ - NodeName: "nodeA", - Affinity: &v1.Affinity{ - PodAntiAffinity: &v1.PodAntiAffinity{ - RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{ - { - LabelSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "foo", - Operator: metav1.LabelSelectorOpExists, - }, - }, - }, - TopologyKey: "zone", - }, - { - LabelSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "labelA", - Operator: metav1.LabelSelectorOpExists, - }, - }, - }, - TopologyKey: "zone", - }, - }, - }, - }, - }, - }, - { - Spec: v1.PodSpec{ - NodeName: "nodeB", - Affinity: &v1.Affinity{ - PodAntiAffinity: &v1.PodAntiAffinity{ - RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{ - { - LabelSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "bar", - Operator: metav1.LabelSelectorOpExists, - }, - }, - }, - TopologyKey: "zone", - }, - { - LabelSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "labelB", - Operator: metav1.LabelSelectorOpExists, - }, - }, - }, - TopologyKey: "zone", - }, - }, - }, - }, - }, - }, - }, - nodes: []*v1.Node{ - {ObjectMeta: metav1.ObjectMeta{Name: "nodeA", Labels: map[string]string{"region": "r1", "zone": "z1", "hostname": "nodeA"}}}, - {ObjectMeta: metav1.ObjectMeta{Name: "nodeB", Labels: map[string]string{"region": "r1", "zone": "z2", "hostname": "nodeB"}}}, - {ObjectMeta: metav1.ObjectMeta{Name: "nodeC", Labels: map[string]string{"region": "r1", "zone": "z3", "hostname": "nodeC"}}}, - }, - nodesExpectAffinityFailureReasons: [][]PredicateFailureReason{ - {ErrPodAffinityNotMatch, ErrExistingPodsAntiAffinityRulesNotMatch}, - {ErrPodAffinityNotMatch, ErrExistingPodsAntiAffinityRulesNotMatch}, - }, - fits: map[string]bool{ - "nodeA": false, - "nodeB": false, - "nodeC": true, - }, - name: "Test existing pod's anti-affinity: existingPod on nodeA and nodeB has at least one anti-affinity term matches incoming pod, so incoming pod can only be scheduled to nodeC", - }, - { - pod: &v1.Pod{ - Spec: v1.PodSpec{ - Affinity: &v1.Affinity{ - PodAffinity: &v1.PodAffinity{ - RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{ - { - LabelSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "foo", - Operator: metav1.LabelSelectorOpExists, - }, - }, - }, - TopologyKey: "region", - }, - { - LabelSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "bar", - Operator: metav1.LabelSelectorOpExists, - }, - }, - }, - TopologyKey: "zone", - }, - }, - }, - }, - }, - }, - pods: []*v1.Pod{ - { - ObjectMeta: metav1.ObjectMeta{Name: "pod1", Labels: map[string]string{"foo": "", "bar": ""}}, - Spec: v1.PodSpec{ - NodeName: "nodeA", - }, - }, - }, - nodes: []*v1.Node{ - {ObjectMeta: metav1.ObjectMeta{Name: "nodeA", Labels: map[string]string{"region": "r1", "zone": "z1", "hostname": "nodeA"}}}, - {ObjectMeta: metav1.ObjectMeta{Name: "nodeB", Labels: map[string]string{"region": "r1", "zone": "z1", "hostname": "nodeB"}}}, - }, - nodesExpectAffinityFailureReasons: [][]PredicateFailureReason{ - {}, - {ErrPodAffinityNotMatch, ErrPodAffinityRulesNotMatch}, - }, - fits: map[string]bool{ - "nodeA": true, - "nodeB": true, - }, - name: "Test incoming pod's affinity: firstly check if all affinityTerms match, and then check if all topologyKeys match", - }, - { - pod: &v1.Pod{ - Spec: v1.PodSpec{ - Affinity: &v1.Affinity{ - PodAffinity: &v1.PodAffinity{ - RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{ - { - LabelSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "foo", - Operator: metav1.LabelSelectorOpExists, - }, - }, - }, - TopologyKey: "region", - }, - { - LabelSelector: &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "bar", - Operator: metav1.LabelSelectorOpExists, - }, - }, - }, - TopologyKey: "zone", - }, - }, - }, - }, - }, - }, - pods: []*v1.Pod{ - { - ObjectMeta: metav1.ObjectMeta{Name: "pod1", Labels: map[string]string{"foo": ""}}, - Spec: v1.PodSpec{ - NodeName: "nodeA", - }, - }, - { - ObjectMeta: metav1.ObjectMeta{Name: "pod2", Labels: map[string]string{"bar": ""}}, - Spec: v1.PodSpec{ - NodeName: "nodeB", - }, - }, - }, - nodes: []*v1.Node{ - {ObjectMeta: metav1.ObjectMeta{Name: "nodeA", Labels: map[string]string{"region": "r1", "zone": "z1", "hostname": "nodeA"}}}, - {ObjectMeta: metav1.ObjectMeta{Name: "nodeB", Labels: map[string]string{"region": "r1", "zone": "z2", "hostname": "nodeB"}}}, - }, - nodesExpectAffinityFailureReasons: [][]PredicateFailureReason{ - {ErrPodAffinityNotMatch, ErrPodAffinityRulesNotMatch}, - {ErrPodAffinityNotMatch, ErrPodAffinityRulesNotMatch}, - }, - fits: map[string]bool{ - "nodeA": false, - "nodeB": false, - }, - name: "Test incoming pod's affinity: firstly check if all affinityTerms match, and then check if all topologyKeys match, and the match logic should be satisfied on the same pod", - }, - } - - for indexTest, test := range tests { - t.Run(test.name, func(t *testing.T) { - snapshot := nodeinfosnapshot.NewSnapshot(nodeinfosnapshot.CreateNodeInfoMap(test.pods, test.nodes)) - for indexNode, node := range test.nodes { - testFit := PodAffinityChecker{ - nodeInfoLister: snapshot.NodeInfos(), - podLister: snapshot.Pods(), - } - - var meta Metadata - if !test.nometa { - factory := &MetadataProducerFactory{} - meta = factory.GetPredicateMetadata(test.pod, snapshot) - } - - fits, reasons, _ := testFit.InterPodAffinityMatches(test.pod, meta, snapshot.NodeInfoMap[node.Name]) - if !fits && !reflect.DeepEqual(reasons, test.nodesExpectAffinityFailureReasons[indexNode]) { - t.Errorf("index: %d unexpected failure reasons: %v expect: %v", indexTest, reasons, test.nodesExpectAffinityFailureReasons[indexNode]) - } - affinity := test.pod.Spec.Affinity - if affinity != nil && affinity.NodeAffinity != nil { - s := nodeinfosnapshot.NewSnapshot(nodeinfosnapshot.CreateNodeInfoMap(nil, []*v1.Node{node})) - factory := &MetadataProducerFactory{} - fits2, reasons, err := PodMatchNodeSelector(test.pod, factory.GetPredicateMetadata(test.pod, s), s.NodeInfoMap[node.Name]) - if err != nil { - t.Errorf("unexpected error: %v", err) - } - selectorExpectedFailureReasons := []PredicateFailureReason{ErrNodeSelectorNotMatch} - if !fits2 && !reflect.DeepEqual(reasons, selectorExpectedFailureReasons) { - t.Errorf("unexpected failure reasons: %v, want: %v", reasons, selectorExpectedFailureReasons) - } - fits = fits && fits2 - } - - if fits != test.fits[node.Name] { - t.Errorf("expected %v for %s got %v", test.fits[node.Name], node.Name, fits) - } - } - }) - } -} - func TestPodToleratesTaints(t *testing.T) { podTolerateTaintsTests := []struct { pod *v1.Pod diff --git a/pkg/scheduler/apis/config/testing/compatibility_test.go b/pkg/scheduler/apis/config/testing/compatibility_test.go index 6ae9954c5fc..8c331da2a5c 100644 --- a/pkg/scheduler/apis/config/testing/compatibility_test.go +++ b/pkg/scheduler/apis/config/testing/compatibility_test.go @@ -262,6 +262,9 @@ func TestCompatibility_v1_Scheduler(t *testing.T) { wantPredicates: sets.NewString(), wantPrioritizers: sets.NewString(), wantPlugins: map[string][]config.Plugin{ + "PreFilterPlugin": { + {Name: "InterPodAffinity"}, + }, "FilterPlugin": { {Name: "NodeUnschedulable"}, {Name: "NodeName"}, @@ -327,6 +330,9 @@ func TestCompatibility_v1_Scheduler(t *testing.T) { wantPredicates: sets.NewString(), wantPrioritizers: sets.NewString(), wantPlugins: map[string][]config.Plugin{ + "PreFilterPlugin": { + {Name: "InterPodAffinity"}, + }, "FilterPlugin": { {Name: "NodeUnschedulable"}, {Name: "NodeName"}, @@ -403,6 +409,9 @@ func TestCompatibility_v1_Scheduler(t *testing.T) { wantPredicates: sets.NewString(), wantPrioritizers: sets.NewString(), wantPlugins: map[string][]config.Plugin{ + "PreFilterPlugin": { + {Name: "InterPodAffinity"}, + }, "FilterPlugin": { {Name: "NodeUnschedulable"}, {Name: "NodeName"}, @@ -490,6 +499,9 @@ func TestCompatibility_v1_Scheduler(t *testing.T) { wantPredicates: sets.NewString(), wantPrioritizers: sets.NewString(), wantPlugins: map[string][]config.Plugin{ + "PreFilterPlugin": { + {Name: "InterPodAffinity"}, + }, "FilterPlugin": { {Name: "NodeUnschedulable"}, {Name: "NodeName"}, @@ -578,6 +590,9 @@ func TestCompatibility_v1_Scheduler(t *testing.T) { wantPredicates: sets.NewString(), wantPrioritizers: sets.NewString(), wantPlugins: map[string][]config.Plugin{ + "PreFilterPlugin": { + {Name: "InterPodAffinity"}, + }, "FilterPlugin": { {Name: "NodeUnschedulable"}, {Name: "NodeName"}, @@ -670,6 +685,9 @@ func TestCompatibility_v1_Scheduler(t *testing.T) { wantPredicates: sets.NewString(), wantPrioritizers: sets.NewString(), wantPlugins: map[string][]config.Plugin{ + "PreFilterPlugin": { + {Name: "InterPodAffinity"}, + }, "FilterPlugin": { {Name: "NodeUnschedulable"}, {Name: "NodeName"}, @@ -774,6 +792,9 @@ func TestCompatibility_v1_Scheduler(t *testing.T) { wantPredicates: sets.NewString(), wantPrioritizers: sets.NewString(), wantPlugins: map[string][]config.Plugin{ + "PreFilterPlugin": { + {Name: "InterPodAffinity"}, + }, "FilterPlugin": { {Name: "NodeUnschedulable"}, {Name: "NodeName"}, @@ -880,6 +901,9 @@ func TestCompatibility_v1_Scheduler(t *testing.T) { wantPredicates: sets.NewString(), wantPrioritizers: sets.NewString(), wantPlugins: map[string][]config.Plugin{ + "PreFilterPlugin": { + {Name: "InterPodAffinity"}, + }, "FilterPlugin": { {Name: "NodeUnschedulable"}, {Name: "NodeName"}, @@ -986,6 +1010,9 @@ func TestCompatibility_v1_Scheduler(t *testing.T) { wantPredicates: sets.NewString(), wantPrioritizers: sets.NewString(), wantPlugins: map[string][]config.Plugin{ + "PreFilterPlugin": { + {Name: "InterPodAffinity"}, + }, "FilterPlugin": { {Name: "NodeUnschedulable"}, {Name: "NodeName"}, @@ -1097,6 +1124,9 @@ func TestCompatibility_v1_Scheduler(t *testing.T) { wantPredicates: sets.NewString(), wantPrioritizers: sets.NewString(), wantPlugins: map[string][]config.Plugin{ + "PreFilterPlugin": { + {Name: "InterPodAffinity"}, + }, "FilterPlugin": { {Name: "NodeUnschedulable"}, {Name: "NodeName"}, diff --git a/pkg/scheduler/framework/plugins/default_registry.go b/pkg/scheduler/framework/plugins/default_registry.go index d5811fb634d..4580e979c24 100644 --- a/pkg/scheduler/framework/plugins/default_registry.go +++ b/pkg/scheduler/framework/plugins/default_registry.go @@ -200,6 +200,7 @@ func NewDefaultConfigProducerRegistry() *ConfigProducerRegistry { registry.RegisterPredicate(predicates.MatchInterPodAffinityPred, func(_ ConfigProducerArgs) (plugins config.Plugins, pluginConfig []config.PluginConfig) { plugins.Filter = appendToPluginSet(plugins.Filter, interpodaffinity.Name, nil) + plugins.PreFilter = appendToPluginSet(plugins.PreFilter, interpodaffinity.Name, nil) return }) registry.RegisterPredicate(predicates.EvenPodsSpreadPred, diff --git a/pkg/scheduler/framework/plugins/interpodaffinity/BUILD b/pkg/scheduler/framework/plugins/interpodaffinity/BUILD index d9861323b94..558818e4029 100644 --- a/pkg/scheduler/framework/plugins/interpodaffinity/BUILD +++ b/pkg/scheduler/framework/plugins/interpodaffinity/BUILD @@ -10,9 +10,11 @@ go_library( "//pkg/scheduler/algorithm/priorities:go_default_library", "//pkg/scheduler/framework/plugins/migration:go_default_library", "//pkg/scheduler/framework/v1alpha1:go_default_library", + "//pkg/scheduler/listers:go_default_library", "//pkg/scheduler/nodeinfo:go_default_library", "//staging/src/k8s.io/api/core/v1:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/klog:go_default_library", ], ) diff --git a/pkg/scheduler/framework/plugins/interpodaffinity/interpod_affinity.go b/pkg/scheduler/framework/plugins/interpodaffinity/interpod_affinity.go index d6b5bfe610d..8060b6092ef 100644 --- a/pkg/scheduler/framework/plugins/interpodaffinity/interpod_affinity.go +++ b/pkg/scheduler/framework/plugins/interpodaffinity/interpod_affinity.go @@ -20,39 +20,126 @@ import ( "context" "fmt" - v1 "k8s.io/api/core/v1" + "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/klog" + "k8s.io/kubernetes/pkg/scheduler/algorithm/predicates" "k8s.io/kubernetes/pkg/scheduler/algorithm/priorities" "k8s.io/kubernetes/pkg/scheduler/framework/plugins/migration" framework "k8s.io/kubernetes/pkg/scheduler/framework/v1alpha1" + schedulerlisters "k8s.io/kubernetes/pkg/scheduler/listers" "k8s.io/kubernetes/pkg/scheduler/nodeinfo" ) // InterPodAffinity is a plugin that checks inter pod affinity type InterPodAffinity struct { - handle framework.FrameworkHandle - predicate predicates.FitPredicate + snapshotSharedLister schedulerlisters.SharedLister + podAffinityChecker *predicates.PodAffinityChecker } +var _ framework.PreFilterPlugin = &InterPodAffinity{} var _ framework.FilterPlugin = &InterPodAffinity{} var _ framework.ScorePlugin = &InterPodAffinity{} -// Name is the name of the plugin used in the plugin registry and configurations. -const Name = "InterPodAffinity" +const ( + // Name is the name of the plugin used in the plugin registry and configurations. + Name = "InterPodAffinity" + + // preFilterStateKey is the key in CycleState to InterPodAffinity pre-computed data. + // Using the name of the plugin will likely help us avoid collisions with other plugins. + preFilterStateKey = "PreFilter" + Name +) + +// preFilterState computed at PreFilter and used at Filter. +type preFilterState struct { + meta *predicates.PodAffinityMetadata +} + +// Clone the prefilter state. +func (s *preFilterState) Clone() framework.StateData { + copy := &preFilterState{ + meta: s.meta.Clone(), + } + return copy +} // Name returns name of the plugin. It is used in logs, etc. func (pl *InterPodAffinity) Name() string { return Name } +// PreFilter invoked at the prefilter extension point. +func (pl *InterPodAffinity) PreFilter(ctx context.Context, cycleState *framework.CycleState, pod *v1.Pod) *framework.Status { + var meta *predicates.PodAffinityMetadata + var allNodes []*nodeinfo.NodeInfo + var havePodsWithAffinityNodes []*nodeinfo.NodeInfo + var err error + if allNodes, err = pl.snapshotSharedLister.NodeInfos().List(); err != nil { + return framework.NewStatus(framework.Error, fmt.Sprintf("failed to list NodeInfos: %v", err)) + } + if havePodsWithAffinityNodes, err = pl.snapshotSharedLister.NodeInfos().HavePodsWithAffinityList(); err != nil { + return framework.NewStatus(framework.Error, fmt.Sprintf("failed to list NodeInfos with pods with affinity: %v", err)) + } + if meta, err = predicates.GetPodAffinityMetadata(pod, allNodes, havePodsWithAffinityNodes); err != nil { + return framework.NewStatus(framework.Error, fmt.Sprintf("Error calculating podAffinityMetadata: %v", err)) + } + + s := &preFilterState{ + meta: meta, + } + cycleState.Write(preFilterStateKey, s) + return nil +} + +// PreFilterExtensions returns prefilter extensions, pod add and remove. +func (pl *InterPodAffinity) PreFilterExtensions() framework.PreFilterExtensions { + return pl +} + +// AddPod from pre-computed data in cycleState. +func (pl *InterPodAffinity) AddPod(ctx context.Context, cycleState *framework.CycleState, podToSchedule *v1.Pod, podToAdd *v1.Pod, nodeInfo *nodeinfo.NodeInfo) *framework.Status { + meta, err := getPodAffinityMetadata(cycleState) + if err != nil { + return framework.NewStatus(framework.Error, err.Error()) + } + meta.UpdateWithPod(podToAdd, podToSchedule, nodeInfo.Node(), 1) + return nil +} + +// RemovePod from pre-computed data in cycleState. +func (pl *InterPodAffinity) RemovePod(ctx context.Context, cycleState *framework.CycleState, podToSchedule *v1.Pod, podToRemove *v1.Pod, nodeInfo *nodeinfo.NodeInfo) *framework.Status { + meta, err := getPodAffinityMetadata(cycleState) + if err != nil { + return framework.NewStatus(framework.Error, err.Error()) + } + meta.UpdateWithPod(podToRemove, podToSchedule, nodeInfo.Node(), -1) + return nil +} + +func getPodAffinityMetadata(cycleState *framework.CycleState) (*predicates.PodAffinityMetadata, error) { + c, err := cycleState.Read(preFilterStateKey) + if err != nil { + // The metadata wasn't pre-computed in prefilter. We ignore the error for now since + // Filter is able to handle that by computing it again. + klog.Error(err) + return nil, nil + } + + s, ok := c.(*preFilterState) + if !ok { + return nil, fmt.Errorf("%+v convert to interpodaffinity.state error", c) + } + return s.meta, nil +} + // Filter invoked at the filter extension point. func (pl *InterPodAffinity) Filter(ctx context.Context, cycleState *framework.CycleState, pod *v1.Pod, nodeInfo *nodeinfo.NodeInfo) *framework.Status { - meta, ok := migration.CovertStateRefToPredMeta(migration.PredicateMetadata(cycleState)) - if !ok { - return migration.ErrorToFrameworkStatus(fmt.Errorf("%+v convert to predicates.Metadata error", cycleState)) + meta, err := getPodAffinityMetadata(cycleState) + if err != nil { + return framework.NewStatus(framework.Error, err.Error()) } - _, reasons, err := pl.predicate(pod, meta, nodeInfo) + _, reasons, err := pl.podAffinityChecker.InterPodAffinityMatches(pod, meta, nodeInfo) return migration.PredicateResultToFrameworkStatus(reasons, err) } @@ -60,7 +147,7 @@ func (pl *InterPodAffinity) Filter(ctx context.Context, cycleState *framework.Cy // The "score" returned in this function is the matching number of pods on the `nodeName`, // it is normalized later. func (pl *InterPodAffinity) Score(ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodeName string) (int64, *framework.Status) { - nodeInfo, err := pl.handle.SnapshotSharedLister().NodeInfos().Get(nodeName) + nodeInfo, err := pl.snapshotSharedLister.NodeInfos().Get(nodeName) if err != nil { return 0, framework.NewStatus(framework.Error, fmt.Sprintf("getting node %q from Snapshot: %v", nodeName, err)) } @@ -73,7 +160,7 @@ func (pl *InterPodAffinity) Score(ctx context.Context, state *framework.CycleSta // NormalizeScore invoked after scoring all nodes. func (pl *InterPodAffinity) NormalizeScore(ctx context.Context, state *framework.CycleState, pod *v1.Pod, scores framework.NodeScoreList) *framework.Status { meta := migration.PriorityMetadata(state) - err := priorities.CalculateInterPodAffinityPriorityReduce(pod, meta, pl.handle.SnapshotSharedLister(), scores) + err := priorities.CalculateInterPodAffinityPriorityReduce(pod, meta, pl.snapshotSharedLister, scores) return migration.ErrorToFrameworkStatus(err) } @@ -84,8 +171,11 @@ func (pl *InterPodAffinity) ScoreExtensions() framework.ScoreExtensions { // New initializes a new plugin and returns it. func New(_ *runtime.Unknown, h framework.FrameworkHandle) (framework.Plugin, error) { + if h.SnapshotSharedLister() == nil { + return nil, fmt.Errorf("SnapshotSharedlister is nil") + } return &InterPodAffinity{ - handle: h, - predicate: predicates.NewPodAffinityPredicate(h.SnapshotSharedLister().NodeInfos(), h.SnapshotSharedLister().Pods()), + snapshotSharedLister: h.SnapshotSharedLister(), + podAffinityChecker: predicates.NewPodAffinityChecker(h.SnapshotSharedLister()), }, nil } diff --git a/pkg/scheduler/framework/plugins/interpodaffinity/interpod_affinity_test.go b/pkg/scheduler/framework/plugins/interpodaffinity/interpod_affinity_test.go index d3e83e361c8..cd2017671a4 100644 --- a/pkg/scheduler/framework/plugins/interpodaffinity/interpod_affinity_test.go +++ b/pkg/scheduler/framework/plugins/interpodaffinity/interpod_affinity_test.go @@ -21,7 +21,7 @@ import ( "reflect" "testing" - v1 "k8s.io/api/core/v1" + "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/informers" clientsetfake "k8s.io/client-go/kubernetes/fake" @@ -782,13 +782,14 @@ func TestSingleNode(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { snapshot := nodeinfosnapshot.NewSnapshot(nodeinfosnapshot.CreateNodeInfoMap(test.pods, []*v1.Node{test.node})) - factory := &predicates.MetadataProducerFactory{} - meta := factory.GetPredicateMetadata(test.pod, snapshot) - state := framework.NewCycleState() - state.Write(migration.PredicatesStateKey, &migration.PredicatesStateData{Reference: meta}) - p := &InterPodAffinity{ - predicate: predicates.NewPodAffinityPredicate(snapshot.NodeInfos(), snapshot.Pods()), + snapshotSharedLister: snapshot, + podAffinityChecker: predicates.NewPodAffinityChecker(snapshot), + } + state := framework.NewCycleState() + preFilterStatus := p.PreFilter(context.Background(), state, test.pod) + if !preFilterStatus.IsSuccess() { + t.Errorf("prefilter failed with status: %v", preFilterStatus) } gotStatus := p.Filter(context.Background(), state, test.pod, snapshot.NodeInfoMap[test.node.Name]) if !reflect.DeepEqual(gotStatus, test.wantStatus) { @@ -1619,13 +1620,14 @@ func TestMultipleNodes(t *testing.T) { t.Run(test.name, func(t *testing.T) { snapshot := nodeinfosnapshot.NewSnapshot(nodeinfosnapshot.CreateNodeInfoMap(test.pods, test.nodes)) for indexNode, node := range test.nodes { - factory := &predicates.MetadataProducerFactory{} - meta := factory.GetPredicateMetadata(test.pod, snapshot) - state := framework.NewCycleState() - state.Write(migration.PredicatesStateKey, &migration.PredicatesStateData{Reference: meta}) - p := &InterPodAffinity{ - predicate: predicates.NewPodAffinityPredicate(snapshot.NodeInfos(), snapshot.Pods()), + snapshotSharedLister: snapshot, + podAffinityChecker: predicates.NewPodAffinityChecker(snapshot), + } + state := framework.NewCycleState() + preFilterStatus := p.PreFilter(context.Background(), state, test.pod) + if !preFilterStatus.IsSuccess() { + t.Errorf("prefilter failed with status: %v", preFilterStatus) } gotStatus := p.Filter(context.Background(), state, test.pod, snapshot.NodeInfoMap[node.Name]) if !reflect.DeepEqual(gotStatus, test.wantStatuses[indexNode]) { @@ -2279,3 +2281,277 @@ func TestHardPodAffinitySymmetricWeight(t *testing.T) { }) } } + +func TestStateAddRemovePod(t *testing.T) { + var label1 = map[string]string{ + "region": "r1", + "zone": "z11", + } + var label2 = map[string]string{ + "region": "r1", + "zone": "z12", + } + var label3 = map[string]string{ + "region": "r2", + "zone": "z21", + } + selector1 := map[string]string{"foo": "bar"} + antiAffinityFooBar := &v1.PodAntiAffinity{ + RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{ + { + LabelSelector: &metav1.LabelSelector{ + MatchExpressions: []metav1.LabelSelectorRequirement{ + { + Key: "foo", + Operator: metav1.LabelSelectorOpIn, + Values: []string{"bar"}, + }, + }, + }, + TopologyKey: "region", + }, + }, + } + antiAffinityComplex := &v1.PodAntiAffinity{ + RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{ + { + LabelSelector: &metav1.LabelSelector{ + MatchExpressions: []metav1.LabelSelectorRequirement{ + { + Key: "foo", + Operator: metav1.LabelSelectorOpIn, + Values: []string{"bar", "buzz"}, + }, + }, + }, + TopologyKey: "region", + }, + { + LabelSelector: &metav1.LabelSelector{ + MatchExpressions: []metav1.LabelSelectorRequirement{ + { + Key: "service", + Operator: metav1.LabelSelectorOpNotIn, + Values: []string{"bar", "security", "test"}, + }, + }, + }, + TopologyKey: "zone", + }, + }, + } + affinityComplex := &v1.PodAffinity{ + RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{ + { + LabelSelector: &metav1.LabelSelector{ + MatchExpressions: []metav1.LabelSelectorRequirement{ + { + Key: "foo", + Operator: metav1.LabelSelectorOpIn, + Values: []string{"bar", "buzz"}, + }, + }, + }, + TopologyKey: "region", + }, + { + LabelSelector: &metav1.LabelSelector{ + MatchExpressions: []metav1.LabelSelectorRequirement{ + { + Key: "service", + Operator: metav1.LabelSelectorOpNotIn, + Values: []string{"bar", "security", "test"}, + }, + }, + }, + TopologyKey: "zone", + }, + }, + } + + tests := []struct { + name string + pendingPod *v1.Pod + addedPod *v1.Pod + existingPods []*v1.Pod + nodes []*v1.Node + services []*v1.Service + }{ + { + name: "no affinity exist", + pendingPod: &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "pending", Labels: selector1}, + }, + existingPods: []*v1.Pod{ + {ObjectMeta: metav1.ObjectMeta{Name: "p1", Labels: selector1}, + Spec: v1.PodSpec{NodeName: "nodeA"}, + }, + {ObjectMeta: metav1.ObjectMeta{Name: "p2"}, + Spec: v1.PodSpec{NodeName: "nodeC"}, + }, + }, + addedPod: &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "addedPod", Labels: selector1}, + Spec: v1.PodSpec{NodeName: "nodeB"}, + }, + nodes: []*v1.Node{ + {ObjectMeta: metav1.ObjectMeta{Name: "nodeA", Labels: label1}}, + {ObjectMeta: metav1.ObjectMeta{Name: "nodeB", Labels: label2}}, + {ObjectMeta: metav1.ObjectMeta{Name: "nodeC", Labels: label3}}, + }, + }, + { + name: "metadata anti-affinity terms are updated correctly after adding and removing a pod", + pendingPod: &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "pending", Labels: selector1}, + }, + existingPods: []*v1.Pod{ + {ObjectMeta: metav1.ObjectMeta{Name: "p1", Labels: selector1}, + Spec: v1.PodSpec{NodeName: "nodeA"}, + }, + {ObjectMeta: metav1.ObjectMeta{Name: "p2"}, + Spec: v1.PodSpec{ + NodeName: "nodeC", + Affinity: &v1.Affinity{ + PodAntiAffinity: antiAffinityFooBar, + }, + }, + }, + }, + addedPod: &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "addedPod", Labels: selector1}, + Spec: v1.PodSpec{ + NodeName: "nodeB", + Affinity: &v1.Affinity{ + PodAntiAffinity: antiAffinityFooBar, + }, + }, + }, + nodes: []*v1.Node{ + {ObjectMeta: metav1.ObjectMeta{Name: "nodeA", Labels: label1}}, + {ObjectMeta: metav1.ObjectMeta{Name: "nodeB", Labels: label2}}, + {ObjectMeta: metav1.ObjectMeta{Name: "nodeC", Labels: label3}}, + }, + }, + { + name: "metadata anti-affinity terms are updated correctly after adding and removing a pod", + pendingPod: &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "pending", Labels: selector1}, + }, + existingPods: []*v1.Pod{ + {ObjectMeta: metav1.ObjectMeta{Name: "p1", Labels: selector1}, + Spec: v1.PodSpec{NodeName: "nodeA"}, + }, + {ObjectMeta: metav1.ObjectMeta{Name: "p2"}, + Spec: v1.PodSpec{ + NodeName: "nodeC", + Affinity: &v1.Affinity{ + PodAntiAffinity: antiAffinityFooBar, + }, + }, + }, + }, + addedPod: &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "addedPod", Labels: selector1}, + Spec: v1.PodSpec{ + NodeName: "nodeA", + Affinity: &v1.Affinity{ + PodAntiAffinity: antiAffinityComplex, + }, + }, + }, + services: []*v1.Service{{Spec: v1.ServiceSpec{Selector: selector1}}}, + nodes: []*v1.Node{ + {ObjectMeta: metav1.ObjectMeta{Name: "nodeA", Labels: label1}}, + {ObjectMeta: metav1.ObjectMeta{Name: "nodeB", Labels: label2}}, + {ObjectMeta: metav1.ObjectMeta{Name: "nodeC", Labels: label3}}, + }, + }, + { + name: "metadata matching pod affinity and anti-affinity are updated correctly after adding and removing a pod", + pendingPod: &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "pending", Labels: selector1}, + }, + existingPods: []*v1.Pod{ + {ObjectMeta: metav1.ObjectMeta{Name: "p1", Labels: selector1}, + Spec: v1.PodSpec{NodeName: "nodeA"}, + }, + {ObjectMeta: metav1.ObjectMeta{Name: "p2"}, + Spec: v1.PodSpec{ + NodeName: "nodeC", + Affinity: &v1.Affinity{ + PodAntiAffinity: antiAffinityFooBar, + PodAffinity: affinityComplex, + }, + }, + }, + }, + addedPod: &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "addedPod", Labels: selector1}, + Spec: v1.PodSpec{ + NodeName: "nodeA", + Affinity: &v1.Affinity{ + PodAntiAffinity: antiAffinityComplex, + }, + }, + }, + services: []*v1.Service{{Spec: v1.ServiceSpec{Selector: selector1}}}, + nodes: []*v1.Node{ + {ObjectMeta: metav1.ObjectMeta{Name: "nodeA", Labels: label1}}, + {ObjectMeta: metav1.ObjectMeta{Name: "nodeB", Labels: label2}}, + {ObjectMeta: metav1.ObjectMeta{Name: "nodeC", Labels: label3}}, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + // getMeta creates predicate meta data given the list of pods. + getState := func(pods []*v1.Pod) (*InterPodAffinity, *framework.CycleState, *predicates.PodAffinityMetadata, *nodeinfosnapshot.Snapshot) { + snapshot := nodeinfosnapshot.NewSnapshot(nodeinfosnapshot.CreateNodeInfoMap(pods, test.nodes)) + + p := &InterPodAffinity{ + snapshotSharedLister: snapshot, + podAffinityChecker: predicates.NewPodAffinityChecker(snapshot), + } + cycleState := framework.NewCycleState() + preFilterStatus := p.PreFilter(context.Background(), cycleState, test.pendingPod) + if !preFilterStatus.IsSuccess() { + t.Errorf("prefilter failed with status: %v", preFilterStatus) + } + + meta, err := getPodAffinityMetadata(cycleState) + if err != nil { + t.Errorf("failed to get metadata from cycleState: %v", err) + } + + return p, cycleState, meta, snapshot + } + + // allPodsState is the state produced when all pods, including test.addedPod are given to prefilter. + _, _, allPodsMeta, _ := getState(append(test.existingPods, test.addedPod)) + + // state is produced for test.existingPods (without test.addedPod). + ipa, state, meta, snapshot := getState(test.existingPods) + // clone the state so that we can compare it later when performing Remove. + originalMeta := meta.Clone() + + // Add test.addedPod to state1 and verify it is equal to allPodsState. + if err := ipa.AddPod(context.Background(), state, test.pendingPod, test.addedPod, snapshot.NodeInfoMap[test.addedPod.Spec.NodeName]); err != nil { + t.Errorf("error adding pod to meta: %v", err) + } + + if !reflect.DeepEqual(allPodsMeta, meta) { + t.Errorf("State is not equal, got: %v, want: %v", meta, allPodsMeta) + } + + // Remove the added pod pod and make sure it is equal to the original state. + if err := ipa.RemovePod(context.Background(), state, test.pendingPod, test.addedPod, snapshot.NodeInfoMap[test.addedPod.Spec.NodeName]); err != nil { + t.Errorf("error removing pod from meta: %v", err) + } + if !reflect.DeepEqual(originalMeta, meta) { + t.Errorf("State is not equal, got: %v, want: %v", meta, originalMeta) + } + }) + } +} diff --git a/test/integration/scheduler/scheduler_test.go b/test/integration/scheduler/scheduler_test.go index f24da7af021..74beb20a833 100644 --- a/test/integration/scheduler/scheduler_test.go +++ b/test/integration/scheduler/scheduler_test.go @@ -131,6 +131,9 @@ func TestSchedulerCreationFromConfigMap(t *testing.T) { }`, expectedPrioritizers: sets.NewString(), expectedPlugins: map[string][]kubeschedulerconfig.Plugin{ + "PreFilterPlugin": { + {Name: "InterPodAffinity"}, + }, "FilterPlugin": { {Name: "NodeUnschedulable"}, {Name: "NodeResourcesFit"}, @@ -208,6 +211,9 @@ kind: Policy `, expectedPrioritizers: sets.NewString(), expectedPlugins: map[string][]kubeschedulerconfig.Plugin{ + "PreFilterPlugin": { + {Name: "InterPodAffinity"}, + }, "FilterPlugin": { {Name: "NodeUnschedulable"}, {Name: "NodeResourcesFit"},