diff --git a/pkg/scheduler/framework/plugins/interpodaffinity/plugin.go b/pkg/scheduler/framework/plugins/interpodaffinity/plugin.go index e5d8463c724..60125861eed 100644 --- a/pkg/scheduler/framework/plugins/interpodaffinity/plugin.go +++ b/pkg/scheduler/framework/plugins/interpodaffinity/plugin.go @@ -220,15 +220,27 @@ func (pl *InterPodAffinity) isSchedulableAfterPodChange(logger klog.Logger, pod return fwk.QueueSkip, nil } - // Pod is deleted. Return Queue when the deleted pod matching the target pod's anti-affinity. - if !podMatchesAllAffinityTerms(antiTerms, originalPod) { - logger.V(5).Info("a scheduled pod was deleted but it doesn't match the target pod's anti-affinity", - "pod", klog.KObj(pod), "modifiedPod", klog.KObj(modifiedPod)) - return fwk.QueueSkip, nil + // Pod is deleted. Return Queue when the deleted pod matches the target pod's anti-affinity or vice versa. + + if podMatchesAllAffinityTerms(antiTerms, originalPod) { + logger.V(5).Info("a scheduled pod was deleted and it matches the target pod's anti-affinity. The pod may be schedulable now", + "pod", klog.KObj(pod), "originalPod", klog.KObj(originalPod)) + return fwk.Queue, nil } - logger.V(5).Info("a scheduled pod was deleted and it matches the target pod's anti-affinity. The pod may be schedulable now", - "pod", klog.KObj(pod), "modifiedPod", klog.KObj(modifiedPod)) - return fwk.Queue, nil + + originalPodAntiTerms, err := fwk.GetAffinityTerms(originalPod, fwk.GetPodAntiAffinityTerms(originalPod.Spec.Affinity)) + if err != nil { + return fwk.Queue, err + } + if podMatchesAllAffinityTerms(originalPodAntiTerms, pod) { + logger.V(5).Info("a scheduled pod was deleted and the target pod matches the deleted pod's anti-affinity. The pod may be schedulable now", + "pod", klog.KObj(pod), "originalPod", klog.KObj(originalPod)) + return fwk.Queue, nil + } + + logger.V(5).Info("a scheduled pod was deleted but it doesn't match the target pod's anti-affinity, nor vice versa", + "pod", klog.KObj(pod), "originalPod", klog.KObj(originalPod)) + return fwk.QueueSkip, nil } func (pl *InterPodAffinity) isSchedulableAfterNodeChange(logger klog.Logger, pod *v1.Pod, oldObj, newObj interface{}) (fwk.QueueingHint, error) { diff --git a/pkg/scheduler/framework/plugins/interpodaffinity/plugin_test.go b/pkg/scheduler/framework/plugins/interpodaffinity/plugin_test.go index 7e7753a4b92..b46e9e06c17 100644 --- a/pkg/scheduler/framework/plugins/interpodaffinity/plugin_test.go +++ b/pkg/scheduler/framework/plugins/interpodaffinity/plugin_test.go @@ -129,6 +129,24 @@ func Test_isSchedulableAfterPodChange(t *testing.T) { oldPod: st.MakePod().Node("fake-node").Label("service", "securityscan").Obj(), expectedHint: fwk.Queue, }, + { + name: "delete a pod with anti-affinity that matches pending pod", + pod: st.MakePod().Name("p").Label("service", "securityscan").Obj(), + oldPod: st.MakePod().Node("fake-node").PodAntiAffinityIn("service", "region", []string{"securityscan", "value2"}, st.PodAntiAffinityWithRequiredReq).Obj(), + expectedHint: fwk.Queue, + }, + { + name: "delete a pod with anti-affinity that doesn't match pending pod", + pod: st.MakePod().Name("p").Label("service", "foo").Obj(), + oldPod: st.MakePod().Node("fake-node").PodAntiAffinityIn("service", "region", []string{"securityscan", "value2"}, st.PodAntiAffinityWithRequiredReq).Obj(), + expectedHint: fwk.QueueSkip, + }, + { + name: "delete a pod which doesn't match pending pod's anti-affinity and has anti-affinity that doesn't match pending pod", + pod: st.MakePod().Name("p").PodAntiAffinityIn("service", "region", []string{"securityscan", "value2"}, st.PodAntiAffinityWithRequiredReq).Label("service", "foo").Obj(), + oldPod: st.MakePod().Node("fake-node").PodAntiAffinityIn("service", "region", []string{"securityscan", "value2"}, st.PodAntiAffinityWithRequiredReq).Label("service", "foo").Obj(), + expectedHint: fwk.QueueSkip, + }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { diff --git a/test/integration/scheduler/queueing/queue.go b/test/integration/scheduler/queueing/queue.go index dfb1ac7067c..052dc10438d 100644 --- a/test/integration/scheduler/queueing/queue.go +++ b/test/integration/scheduler/queueing/queue.go @@ -682,6 +682,54 @@ var CoreResourceEnqueueTestCases = []*CoreResourceEnqueueTestCase{ WantRequeuedPods: sets.New("pod2"), EnableSchedulingQueueHint: sets.New(true), }, + { + Name: "Pod rejected by the InterPodAffinity plugin is requeued when deleting the existing pod that matches the target podAntiAffinity", + EnablePlugins: []string{names.InterPodAffinity}, + InitialNodes: []*v1.Node{st.MakeNode().Name("fake-node").Label("node", "fake-node").Obj()}, + InitialPods: []*v1.Pod{ + st.MakePod().Name("pod1").Label("anti1", "anti1").Container("image").Node("fake-node").Obj(), + st.MakePod().Name("pod2").Label("anti2", "anti2").Container("image").Node("fake-node").Obj(), + }, + Pods: []*v1.Pod{ + // - Pod3 and pod4 will be rejected by the PodAffinity plugin. + st.MakePod().Name("pod3").Label("anti1", "anti1").PodAntiAffinityExists("anti1", "node", st.PodAntiAffinityWithRequiredReq).Container("image").Obj(), + st.MakePod().Name("pod4").Label("anti2", "anti2").PodAntiAffinityExists("anti2", "node", st.PodAntiAffinityWithRequiredReq).Container("image").Obj(), + }, + + TriggerFn: func(testCtx *testutils.TestContext) (map[fwk.ClusterEvent]uint64, error) { + // Delete pod1 which will make pod3 schedulable because pod3's antiAffinity won't match pod1 anymore. + if err := testCtx.ClientSet.CoreV1().Pods(testCtx.NS.Name).Delete(testCtx.Ctx, "pod1", metav1.DeleteOptions{GracePeriodSeconds: new(int64)}); err != nil { + return nil, fmt.Errorf("failed to delete pod1: %w", err) + } + return map[fwk.ClusterEvent]uint64{{Resource: assignedPod, ActionType: fwk.Delete}: 1}, nil + }, + WantRequeuedPods: sets.New("pod3"), + EnableSchedulingQueueHint: sets.New(true), + }, + { + Name: "Pod rejected by the InterPodAffinity plugin is requeued when deleting the existing pod with podAntiAffinity that matches the target pod", + EnablePlugins: []string{names.InterPodAffinity}, + InitialNodes: []*v1.Node{st.MakeNode().Name("fake-node").Label("node", "fake-node").Obj()}, + InitialPods: []*v1.Pod{ + st.MakePod().Name("pod1").PodAntiAffinityExists("anti1", "node", st.PodAntiAffinityWithRequiredReq).Container("image").Node("fake-node").Obj(), + st.MakePod().Name("pod2").PodAntiAffinityExists("anti2", "node", st.PodAntiAffinityWithRequiredReq).Container("image").Node("fake-node").Obj(), + }, + Pods: []*v1.Pod{ + // - Pod3 and pod4 will be rejected by the PodAffinity plugin. + st.MakePod().Name("pod3").Label("anti1", "anti1").Container("image").Obj(), + st.MakePod().Name("pod4").Label("anti2", "anti2").Container("image").Obj(), + }, + + TriggerFn: func(testCtx *testutils.TestContext) (map[fwk.ClusterEvent]uint64, error) { + // Delete pod1 which will make pod3 schedulable because pod1's antiAffinity won't match pod3 anymore. + if err := testCtx.ClientSet.CoreV1().Pods(testCtx.NS.Name).Delete(testCtx.Ctx, "pod1", metav1.DeleteOptions{GracePeriodSeconds: new(int64)}); err != nil { + return nil, fmt.Errorf("failed to delete pod1: %w", err) + } + return map[fwk.ClusterEvent]uint64{{Resource: assignedPod, ActionType: fwk.Delete}: 1}, nil + }, + WantRequeuedPods: sets.New("pod3"), + EnableSchedulingQueueHint: sets.New(true), + }, { Name: "Pod rejected by the PodAffinity plugin is requeued when updating the existed pod's label to make it match the pod's podAffinity", EnablePlugins: []string{names.InterPodAffinity},