From d8d997a25d68bd7d67690b07ed70e6872833211a Mon Sep 17 00:00:00 2001 From: Jan Chaloupka Date: Fri, 12 Dec 2025 14:17:41 +0100 Subject: [PATCH 1/9] refactor(TestPodLifeTime): extract helper functions to package level --- .../plugins/podlifetime/pod_lifetime_test.go | 73 ++++++++++--------- 1 file changed, 38 insertions(+), 35 deletions(-) diff --git a/pkg/framework/plugins/podlifetime/pod_lifetime_test.go b/pkg/framework/plugins/podlifetime/pod_lifetime_test.go index 5de9cc769..cffea6788 100644 --- a/pkg/framework/plugins/podlifetime/pod_lifetime_test.go +++ b/pkg/framework/plugins/podlifetime/pod_lifetime_test.go @@ -35,42 +35,45 @@ import ( "sigs.k8s.io/descheduler/test" ) +const nodeName1 = "n1" + +var ( + olderPodCreationTime = metav1.NewTime(time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC)) + newerPodCreationTime = metav1.NewTime(time.Now()) +) + +func buildTestNode1() *v1.Node { + return test.BuildTestNode(nodeName1, 2000, 3000, 10, nil) +} + +func buildTestPodForNode1(name string, creationTime metav1.Time, apply func(*v1.Pod)) *v1.Pod { + return test.BuildTestPod(name, 100, 0, nodeName1, func(pod *v1.Pod) { + pod.ObjectMeta.CreationTimestamp = creationTime + if apply != nil { + apply(pod) + } + }) +} + +func buildTestPodWithRSOwnerRefForNode1(name string, creationTime metav1.Time, apply func(*v1.Pod)) *v1.Pod { + return buildTestPodForNode1(name, creationTime, func(pod *v1.Pod) { + test.SetRSOwnerRef(pod) + if apply != nil { + apply(pod) + } + }) +} + +func buildTestPodWithRSOwnerRefWithPendingPhaseForNode1(name string, creationTime metav1.Time, apply func(*v1.Pod)) *v1.Pod { + return buildTestPodWithRSOwnerRefForNode1(name, creationTime, func(pod *v1.Pod) { + pod.Status.Phase = "Pending" + if apply != nil { + apply(pod) + } + }) +} + func TestPodLifeTime(t *testing.T) { - const nodeName1 = "n1" - buildTestNode1 := func() *v1.Node { - return test.BuildTestNode(nodeName1, 2000, 3000, 10, nil) - } - - olderPodCreationTime := metav1.NewTime(time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC)) - newerPodCreationTime := metav1.NewTime(time.Now()) - - buildTestPodForNode1 := func(name string, creationTime metav1.Time, apply func(*v1.Pod)) *v1.Pod { - return test.BuildTestPod(name, 100, 0, nodeName1, func(pod *v1.Pod) { - pod.ObjectMeta.CreationTimestamp = creationTime - if apply != nil { - apply(pod) - } - }) - } - - buildTestPodWithRSOwnerRefForNode1 := func(name string, creationTime metav1.Time, apply func(*v1.Pod)) *v1.Pod { - return buildTestPodForNode1(name, creationTime, func(pod *v1.Pod) { - test.SetRSOwnerRef(pod) - if apply != nil { - apply(pod) - } - }) - } - - buildTestPodWithRSOwnerRefWithPendingPhaseForNode1 := func(name string, creationTime metav1.Time, apply func(*v1.Pod)) *v1.Pod { - return buildTestPodWithRSOwnerRefForNode1(name, creationTime, func(pod *v1.Pod) { - pod.Status.Phase = "Pending" - if apply != nil { - apply(pod) - } - }) - } - var maxLifeTime uint = 600 testCases := []struct { description string From 286f2848fcde47a5fd8105c3d385a04209a529d9 Mon Sep 17 00:00:00 2001 From: Jan Chaloupka Date: Fri, 12 Dec 2025 14:20:49 +0100 Subject: [PATCH 2/9] refactor(TestPodLifeTime): add shared test infrastructure --- .../plugins/podlifetime/pod_lifetime_test.go | 132 +++++++++--------- 1 file changed, 69 insertions(+), 63 deletions(-) diff --git a/pkg/framework/plugins/podlifetime/pod_lifetime_test.go b/pkg/framework/plugins/podlifetime/pod_lifetime_test.go index cffea6788..07bcfc204 100644 --- a/pkg/framework/plugins/podlifetime/pod_lifetime_test.go +++ b/pkg/framework/plugins/podlifetime/pod_lifetime_test.go @@ -73,20 +73,76 @@ func buildTestPodWithRSOwnerRefWithPendingPhaseForNode1(name string, creationTim }) } +type podLifeTimeTestCase struct { + description string + args *PodLifeTimeArgs + pods []*v1.Pod + nodes []*v1.Node + expectedEvictedPods []string // if specified, will assert specific pods were evicted + expectedEvictedPodCount uint + ignorePvcPods bool + maxPodsToEvictPerNode *uint + maxPodsToEvictPerNamespace *uint + maxPodsToEvictTotal *uint +} + +func runPodLifeTimeTest(t *testing.T, tc podLifeTimeTestCase) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + var objs []runtime.Object + for _, node := range tc.nodes { + objs = append(objs, node) + } + for _, pod := range tc.pods { + objs = append(objs, pod) + } + fakeClient := fake.NewSimpleClientset(objs...) + + handle, podEvictor, err := frameworktesting.InitFrameworkHandle( + ctx, + fakeClient, + evictions.NewOptions(). + WithMaxPodsToEvictPerNode(tc.maxPodsToEvictPerNode). + WithMaxPodsToEvictPerNamespace(tc.maxPodsToEvictPerNamespace). + WithMaxPodsToEvictTotal(tc.maxPodsToEvictTotal), + defaultevictor.DefaultEvictorArgs{IgnorePvcPods: tc.ignorePvcPods}, + nil, + ) + if err != nil { + t.Fatalf("Unable to initialize a framework handle: %v", err) + } + + var evictedPods []string + test.RegisterEvictedPodsCollector(fakeClient, &evictedPods) + + plugin, err := New(ctx, tc.args, handle) + if err != nil { + t.Fatalf("Unable to initialize the plugin: %v", err) + } + + plugin.(frameworktypes.DeschedulePlugin).Deschedule(ctx, tc.nodes) + podsEvicted := podEvictor.TotalEvicted() + if podsEvicted != tc.expectedEvictedPodCount { + t.Errorf("Test error for description: %s. Expected evicted pods count %v, got %v", tc.description, tc.expectedEvictedPodCount, podsEvicted) + } + + if tc.expectedEvictedPods != nil { + diff := sets.New(tc.expectedEvictedPods...).Difference(sets.New(evictedPods...)) + if diff.Len() > 0 { + t.Errorf( + "Expected pods %v to be evicted but %v were not evicted. Actual pods evicted: %v", + tc.expectedEvictedPods, + diff.UnsortedList(), + evictedPods, + ) + } + } +} + func TestPodLifeTime(t *testing.T) { var maxLifeTime uint = 600 - testCases := []struct { - description string - args *PodLifeTimeArgs - pods []*v1.Pod - nodes []*v1.Node - expectedEvictedPods []string // if specified, will assert specific pods were evicted - expectedEvictedPodCount uint - ignorePvcPods bool - maxPodsToEvictPerNode *uint - maxPodsToEvictPerNamespace *uint - maxPodsToEvictTotal *uint - }{ + testCases := []podLifeTimeTestCase{ { description: "Two pods in the default namespace, 1 is new and 1 very is old. 1 should be evicted.", args: &PodLifeTimeArgs{ @@ -660,57 +716,7 @@ func TestPodLifeTime(t *testing.T) { for _, tc := range testCases { t.Run(tc.description, func(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - var objs []runtime.Object - for _, node := range tc.nodes { - objs = append(objs, node) - } - for _, pod := range tc.pods { - objs = append(objs, pod) - } - fakeClient := fake.NewSimpleClientset(objs...) - - handle, podEvictor, err := frameworktesting.InitFrameworkHandle( - ctx, - fakeClient, - evictions.NewOptions(). - WithMaxPodsToEvictPerNode(tc.maxPodsToEvictPerNode). - WithMaxPodsToEvictPerNamespace(tc.maxPodsToEvictPerNamespace). - WithMaxPodsToEvictTotal(tc.maxPodsToEvictTotal), - defaultevictor.DefaultEvictorArgs{IgnorePvcPods: tc.ignorePvcPods}, - nil, - ) - if err != nil { - t.Fatalf("Unable to initialize a framework handle: %v", err) - } - - var evictedPods []string - test.RegisterEvictedPodsCollector(fakeClient, &evictedPods) - - plugin, err := New(ctx, tc.args, handle) - if err != nil { - t.Fatalf("Unable to initialize the plugin: %v", err) - } - - plugin.(frameworktypes.DeschedulePlugin).Deschedule(ctx, tc.nodes) - podsEvicted := podEvictor.TotalEvicted() - if podsEvicted != tc.expectedEvictedPodCount { - t.Errorf("Test error for description: %s. Expected evicted pods count %v, got %v", tc.description, tc.expectedEvictedPodCount, podsEvicted) - } - - if tc.expectedEvictedPods != nil { - diff := sets.New(tc.expectedEvictedPods...).Difference(sets.New(evictedPods...)) - if diff.Len() > 0 { - t.Errorf( - "Expected pods %v to be evicted but %v were not evicted. Actual pods evicted: %v", - tc.expectedEvictedPods, - diff.UnsortedList(), - evictedPods, - ) - } - } + runPodLifeTimeTest(t, tc) }) } } From fb0bddf85d97681e1ea7ddb7ab94591dd59e0bf8 Mon Sep 17 00:00:00 2001 From: Jan Chaloupka Date: Fri, 12 Dec 2025 14:31:32 +0100 Subject: [PATCH 3/9] refactor(TestPodLifeTime): extract age threshold tests --- .../plugins/podlifetime/pod_lifetime_test.go | 112 ++++++++++-------- 1 file changed, 62 insertions(+), 50 deletions(-) diff --git a/pkg/framework/plugins/podlifetime/pod_lifetime_test.go b/pkg/framework/plugins/podlifetime/pod_lifetime_test.go index 07bcfc204..b35db0b1f 100644 --- a/pkg/framework/plugins/podlifetime/pod_lifetime_test.go +++ b/pkg/framework/plugins/podlifetime/pod_lifetime_test.go @@ -143,56 +143,6 @@ func runPodLifeTimeTest(t *testing.T, tc podLifeTimeTestCase) { func TestPodLifeTime(t *testing.T) { var maxLifeTime uint = 600 testCases := []podLifeTimeTestCase{ - { - description: "Two pods in the default namespace, 1 is new and 1 very is old. 1 should be evicted.", - args: &PodLifeTimeArgs{ - MaxPodLifeTimeSeconds: &maxLifeTime, - }, - pods: []*v1.Pod{ - buildTestPodWithRSOwnerRefForNode1("p1", newerPodCreationTime, nil), - buildTestPodWithRSOwnerRefForNode1("p2", olderPodCreationTime, nil), - }, - nodes: []*v1.Node{buildTestNode1()}, - expectedEvictedPodCount: 1, - expectedEvictedPods: []string{"p2"}, - }, - { - description: "Two pods in the default namespace, 2 are new and 0 are old. 0 should be evicted.", - args: &PodLifeTimeArgs{ - MaxPodLifeTimeSeconds: &maxLifeTime, - }, - pods: []*v1.Pod{ - buildTestPodWithRSOwnerRefForNode1("p3", newerPodCreationTime, nil), - buildTestPodWithRSOwnerRefForNode1("p4", newerPodCreationTime, nil), - }, - nodes: []*v1.Node{buildTestNode1()}, - expectedEvictedPodCount: 0, - }, - { - description: "Two pods in the default namespace, 1 created 605 seconds ago. 1 should be evicted.", - args: &PodLifeTimeArgs{ - MaxPodLifeTimeSeconds: &maxLifeTime, - }, - pods: []*v1.Pod{ - buildTestPodWithRSOwnerRefForNode1("p5", newerPodCreationTime, nil), - buildTestPodWithRSOwnerRefForNode1("p6", metav1.NewTime(time.Now().Add(-time.Second*605)), nil), - }, - nodes: []*v1.Node{buildTestNode1()}, - expectedEvictedPodCount: 1, - expectedEvictedPods: []string{"p6"}, - }, - { - description: "Two pods in the default namespace, 1 created 595 seconds ago. 0 should be evicted.", - args: &PodLifeTimeArgs{ - MaxPodLifeTimeSeconds: &maxLifeTime, - }, - pods: []*v1.Pod{ - buildTestPodForNode1("p7", newerPodCreationTime, nil), - buildTestPodForNode1("p8", metav1.NewTime(time.Now().Add(-time.Second*595)), nil), - }, - nodes: []*v1.Node{buildTestNode1()}, - expectedEvictedPodCount: 0, - }, { description: "Two pods, one with ContainerCreating state. 1 should be evicted.", args: &PodLifeTimeArgs{ @@ -720,3 +670,65 @@ func TestPodLifeTime(t *testing.T) { }) } } + +func TestPodLifeTime_AgeThreshold(t *testing.T) { + var maxLifeTime uint = 600 + testCases := []podLifeTimeTestCase{ + { + description: "Two pods in the default namespace, 1 is new and 1 very is old. 1 should be evicted.", + args: &PodLifeTimeArgs{ + MaxPodLifeTimeSeconds: &maxLifeTime, + }, + pods: []*v1.Pod{ + buildTestPodWithRSOwnerRefForNode1("p1", newerPodCreationTime, nil), + buildTestPodWithRSOwnerRefForNode1("p2", olderPodCreationTime, nil), + }, + nodes: []*v1.Node{buildTestNode1()}, + expectedEvictedPodCount: 1, + expectedEvictedPods: []string{"p2"}, + }, + { + description: "Two pods in the default namespace, 2 are new and 0 are old. 0 should be evicted.", + args: &PodLifeTimeArgs{ + MaxPodLifeTimeSeconds: &maxLifeTime, + }, + pods: []*v1.Pod{ + buildTestPodWithRSOwnerRefForNode1("p3", newerPodCreationTime, nil), + buildTestPodWithRSOwnerRefForNode1("p4", newerPodCreationTime, nil), + }, + nodes: []*v1.Node{buildTestNode1()}, + expectedEvictedPodCount: 0, + }, + { + description: "Two pods in the default namespace, 1 created 605 seconds ago. 1 should be evicted.", + args: &PodLifeTimeArgs{ + MaxPodLifeTimeSeconds: &maxLifeTime, + }, + pods: []*v1.Pod{ + buildTestPodWithRSOwnerRefForNode1("p5", newerPodCreationTime, nil), + buildTestPodWithRSOwnerRefForNode1("p6", metav1.NewTime(time.Now().Add(-time.Second*605)), nil), + }, + nodes: []*v1.Node{buildTestNode1()}, + expectedEvictedPodCount: 1, + expectedEvictedPods: []string{"p6"}, + }, + { + description: "Two pods in the default namespace, 1 created 595 seconds ago. 0 should be evicted.", + args: &PodLifeTimeArgs{ + MaxPodLifeTimeSeconds: &maxLifeTime, + }, + pods: []*v1.Pod{ + buildTestPodForNode1("p7", newerPodCreationTime, nil), + buildTestPodForNode1("p8", metav1.NewTime(time.Now().Add(-time.Second*595)), nil), + }, + nodes: []*v1.Node{buildTestNode1()}, + expectedEvictedPodCount: 0, + }, + } + + for _, tc := range testCases { + t.Run(tc.description, func(t *testing.T) { + runPodLifeTimeTest(t, tc) + }) + } +} From 83151219e7d429ca94b527c4f7a759ef1881fd74 Mon Sep 17 00:00:00 2001 From: Jan Chaloupka Date: Fri, 12 Dec 2025 14:36:37 +0100 Subject: [PATCH 4/9] refactor(TestPodLifeTime): extract pod phase state tests --- .../plugins/podlifetime/pod_lifetime_test.go | 128 ++++++++++-------- 1 file changed, 70 insertions(+), 58 deletions(-) diff --git a/pkg/framework/plugins/podlifetime/pod_lifetime_test.go b/pkg/framework/plugins/podlifetime/pod_lifetime_test.go index b35db0b1f..e81c19eb0 100644 --- a/pkg/framework/plugins/podlifetime/pod_lifetime_test.go +++ b/pkg/framework/plugins/podlifetime/pod_lifetime_test.go @@ -189,22 +189,6 @@ func TestPodLifeTime(t *testing.T) { nodes: []*v1.Node{buildTestNode1()}, expectedEvictedPodCount: 1, }, - { - description: "Two old pods with different states. 1 should be evicted.", - args: &PodLifeTimeArgs{ - MaxPodLifeTimeSeconds: &maxLifeTime, - States: []string{"Pending"}, - }, - pods: []*v1.Pod{ - buildTestPodWithRSOwnerRefWithPendingPhaseForNode1("p9", olderPodCreationTime, nil), - buildTestPodWithRSOwnerRefForNode1("p10", olderPodCreationTime, func(pod *v1.Pod) { - pod.Status.Phase = "Running" - }), - }, - nodes: []*v1.Node{buildTestNode1()}, - expectedEvictedPodCount: 1, - expectedEvictedPods: []string{"p9"}, - }, { description: "Does not evict pvc pods with ignorePvcPods set to true", args: &PodLifeTimeArgs{ @@ -600,48 +584,6 @@ func TestPodLifeTime(t *testing.T) { nodes: []*v1.Node{buildTestNode1()}, expectedEvictedPodCount: 1, }, - { - description: "1 pod with pod status phase v1.PodSucceeded should be evicted", - args: &PodLifeTimeArgs{ - MaxPodLifeTimeSeconds: &maxLifeTime, - States: []string{string(v1.PodSucceeded)}, - }, - pods: []*v1.Pod{ - buildTestPodWithRSOwnerRefForNode1("p16", olderPodCreationTime, func(pod *v1.Pod) { - pod.Status.Phase = v1.PodSucceeded - }), - }, - nodes: []*v1.Node{buildTestNode1()}, - expectedEvictedPodCount: 1, - }, - { - description: "1 pod with pod status phase v1.PodFailed should be evicted", - args: &PodLifeTimeArgs{ - MaxPodLifeTimeSeconds: &maxLifeTime, - States: []string{string(v1.PodFailed)}, - }, - pods: []*v1.Pod{ - buildTestPodWithRSOwnerRefForNode1("p16", olderPodCreationTime, func(pod *v1.Pod) { - pod.Status.Phase = v1.PodFailed - }), - }, - nodes: []*v1.Node{buildTestNode1()}, - expectedEvictedPodCount: 1, - }, - { - description: "1 pod with pod status phase v1.PodUnknown should be evicted", - args: &PodLifeTimeArgs{ - MaxPodLifeTimeSeconds: &maxLifeTime, - States: []string{string(v1.PodUnknown)}, - }, - pods: []*v1.Pod{ - buildTestPodWithRSOwnerRefForNode1("p16", olderPodCreationTime, func(pod *v1.Pod) { - pod.Status.Phase = v1.PodUnknown - }), - }, - nodes: []*v1.Node{buildTestNode1()}, - expectedEvictedPodCount: 1, - }, { description: "1 pod with ImagePullBackOff status should be ignored when States filter is ContainerCreating", args: &PodLifeTimeArgs{ @@ -732,3 +674,73 @@ func TestPodLifeTime_AgeThreshold(t *testing.T) { }) } } + +func TestPodLifeTime_PodPhaseStates(t *testing.T) { + var maxLifeTime uint = 600 + testCases := []podLifeTimeTestCase{ + { + description: "Two old pods with different states. 1 should be evicted.", + args: &PodLifeTimeArgs{ + MaxPodLifeTimeSeconds: &maxLifeTime, + States: []string{"Pending"}, + }, + pods: []*v1.Pod{ + buildTestPodWithRSOwnerRefWithPendingPhaseForNode1("p9", olderPodCreationTime, nil), + buildTestPodWithRSOwnerRefForNode1("p10", olderPodCreationTime, func(pod *v1.Pod) { + pod.Status.Phase = "Running" + }), + }, + nodes: []*v1.Node{buildTestNode1()}, + expectedEvictedPodCount: 1, + expectedEvictedPods: []string{"p9"}, + }, + { + description: "1 pod with pod status phase v1.PodSucceeded should be evicted", + args: &PodLifeTimeArgs{ + MaxPodLifeTimeSeconds: &maxLifeTime, + States: []string{string(v1.PodSucceeded)}, + }, + pods: []*v1.Pod{ + buildTestPodWithRSOwnerRefForNode1("p16", olderPodCreationTime, func(pod *v1.Pod) { + pod.Status.Phase = v1.PodSucceeded + }), + }, + nodes: []*v1.Node{buildTestNode1()}, + expectedEvictedPodCount: 1, + }, + { + description: "1 pod with pod status phase v1.PodFailed should be evicted", + args: &PodLifeTimeArgs{ + MaxPodLifeTimeSeconds: &maxLifeTime, + States: []string{string(v1.PodFailed)}, + }, + pods: []*v1.Pod{ + buildTestPodWithRSOwnerRefForNode1("p16", olderPodCreationTime, func(pod *v1.Pod) { + pod.Status.Phase = v1.PodFailed + }), + }, + nodes: []*v1.Node{buildTestNode1()}, + expectedEvictedPodCount: 1, + }, + { + description: "1 pod with pod status phase v1.PodUnknown should be evicted", + args: &PodLifeTimeArgs{ + MaxPodLifeTimeSeconds: &maxLifeTime, + States: []string{string(v1.PodUnknown)}, + }, + pods: []*v1.Pod{ + buildTestPodWithRSOwnerRefForNode1("p16", olderPodCreationTime, func(pod *v1.Pod) { + pod.Status.Phase = v1.PodUnknown + }), + }, + nodes: []*v1.Node{buildTestNode1()}, + expectedEvictedPodCount: 1, + }, + } + + for _, tc := range testCases { + t.Run(tc.description, func(t *testing.T) { + runPodLifeTimeTest(t, tc) + }) + } +} From 293a9ca4b7334a6a64e3f49b5cf6dc8e22c5d91d Mon Sep 17 00:00:00 2001 From: Jan Chaloupka Date: Sat, 13 Dec 2025 13:36:32 +0100 Subject: [PATCH 5/9] refactor(TestPodLifeTime): extract container waiting reason tests --- .../plugins/podlifetime/pod_lifetime_test.go | 216 +++++++++--------- 1 file changed, 114 insertions(+), 102 deletions(-) diff --git a/pkg/framework/plugins/podlifetime/pod_lifetime_test.go b/pkg/framework/plugins/podlifetime/pod_lifetime_test.go index e81c19eb0..b211708f2 100644 --- a/pkg/framework/plugins/podlifetime/pod_lifetime_test.go +++ b/pkg/framework/plugins/podlifetime/pod_lifetime_test.go @@ -143,52 +143,6 @@ func runPodLifeTimeTest(t *testing.T, tc podLifeTimeTestCase) { func TestPodLifeTime(t *testing.T) { var maxLifeTime uint = 600 testCases := []podLifeTimeTestCase{ - { - description: "Two pods, one with ContainerCreating state. 1 should be evicted.", - args: &PodLifeTimeArgs{ - MaxPodLifeTimeSeconds: &maxLifeTime, - States: []string{"ContainerCreating"}, - }, - pods: []*v1.Pod{ - buildTestPodWithRSOwnerRefWithPendingPhaseForNode1("p9", olderPodCreationTime, nil), - test.BuildTestPod("container-creating-stuck", 0, 0, nodeName1, func(pod *v1.Pod) { - pod.Status.ContainerStatuses = []v1.ContainerStatus{ - { - State: v1.ContainerState{ - Waiting: &v1.ContainerStateWaiting{Reason: "ContainerCreating"}, - }, - }, - } - test.SetRSOwnerRef(pod) - pod.ObjectMeta.CreationTimestamp = olderPodCreationTime - }), - }, - nodes: []*v1.Node{buildTestNode1()}, - expectedEvictedPodCount: 1, - }, - { - description: "Two pods, one with PodInitializing state. 1 should be evicted.", - args: &PodLifeTimeArgs{ - MaxPodLifeTimeSeconds: &maxLifeTime, - States: []string{"PodInitializing"}, - }, - pods: []*v1.Pod{ - buildTestPodWithRSOwnerRefWithPendingPhaseForNode1("p9", olderPodCreationTime, nil), - test.BuildTestPod("pod-initializing-stuck", 0, 0, nodeName1, func(pod *v1.Pod) { - pod.Status.ContainerStatuses = []v1.ContainerStatus{ - { - State: v1.ContainerState{ - Waiting: &v1.ContainerStateWaiting{Reason: "PodInitializing"}, - }, - }, - } - test.SetRSOwnerRef(pod) - pod.ObjectMeta.CreationTimestamp = olderPodCreationTime - }), - }, - nodes: []*v1.Node{buildTestNode1()}, - expectedEvictedPodCount: 1, - }, { description: "Does not evict pvc pods with ignorePvcPods set to true", args: &PodLifeTimeArgs{ @@ -326,6 +280,120 @@ func TestPodLifeTime(t *testing.T) { expectedEvictedPodCount: 1, expectedEvictedPods: []string{"p2"}, }, + { + description: "1 pod with pod status reason NodeLost should be evicted", + args: &PodLifeTimeArgs{ + MaxPodLifeTimeSeconds: &maxLifeTime, + States: []string{"NodeLost"}, + }, + pods: []*v1.Pod{ + buildTestPodWithRSOwnerRefWithPendingPhaseForNode1("p9", olderPodCreationTime, func(pod *v1.Pod) { + pod.Status.Reason = "NodeLost" + }), + }, + nodes: []*v1.Node{buildTestNode1()}, + expectedEvictedPodCount: 1, + }, + { + description: "1 pod with pod status reason NodeAffinity should be evicted", + args: &PodLifeTimeArgs{ + MaxPodLifeTimeSeconds: &maxLifeTime, + States: []string{"NodeAffinity"}, + }, + pods: []*v1.Pod{ + buildTestPodWithRSOwnerRefWithPendingPhaseForNode1("p9", olderPodCreationTime, func(pod *v1.Pod) { + pod.Status.Reason = "NodeAffinity" + }), + }, + nodes: []*v1.Node{buildTestNode1()}, + expectedEvictedPodCount: 1, + }, + { + description: "1 pod with pod status reason Shutdown should be evicted", + args: &PodLifeTimeArgs{ + MaxPodLifeTimeSeconds: &maxLifeTime, + States: []string{"Shutdown"}, + }, + pods: []*v1.Pod{ + buildTestPodWithRSOwnerRefWithPendingPhaseForNode1("p9", olderPodCreationTime, func(pod *v1.Pod) { + pod.Status.Reason = "Shutdown" + }), + }, + nodes: []*v1.Node{buildTestNode1()}, + expectedEvictedPodCount: 1, + }, + { + description: "1 pod with pod status reason UnexpectedAdmissionError should be evicted", + args: &PodLifeTimeArgs{ + MaxPodLifeTimeSeconds: &maxLifeTime, + States: []string{"UnexpectedAdmissionError"}, + }, + pods: []*v1.Pod{ + buildTestPodWithRSOwnerRefWithPendingPhaseForNode1("p9", olderPodCreationTime, func(pod *v1.Pod) { + pod.Status.Reason = "UnexpectedAdmissionError" + }), + }, + nodes: []*v1.Node{buildTestNode1()}, + expectedEvictedPodCount: 1, + }, + } + + for _, tc := range testCases { + t.Run(tc.description, func(t *testing.T) { + runPodLifeTimeTest(t, tc) + }) + } +} + +func TestPodLifeTime_ContainerWaitingReasons(t *testing.T) { + var maxLifeTime uint = 600 + testCases := []podLifeTimeTestCase{ + { + description: "Two pods, one with ContainerCreating state. 1 should be evicted.", + args: &PodLifeTimeArgs{ + MaxPodLifeTimeSeconds: &maxLifeTime, + States: []string{"ContainerCreating"}, + }, + pods: []*v1.Pod{ + buildTestPodWithRSOwnerRefWithPendingPhaseForNode1("p9", olderPodCreationTime, nil), + test.BuildTestPod("container-creating-stuck", 0, 0, nodeName1, func(pod *v1.Pod) { + pod.Status.ContainerStatuses = []v1.ContainerStatus{ + { + State: v1.ContainerState{ + Waiting: &v1.ContainerStateWaiting{Reason: "ContainerCreating"}, + }, + }, + } + test.SetRSOwnerRef(pod) + pod.ObjectMeta.CreationTimestamp = olderPodCreationTime + }), + }, + nodes: []*v1.Node{buildTestNode1()}, + expectedEvictedPodCount: 1, + }, + { + description: "Two pods, one with PodInitializing state. 1 should be evicted.", + args: &PodLifeTimeArgs{ + MaxPodLifeTimeSeconds: &maxLifeTime, + States: []string{"PodInitializing"}, + }, + pods: []*v1.Pod{ + buildTestPodWithRSOwnerRefWithPendingPhaseForNode1("p9", olderPodCreationTime, nil), + test.BuildTestPod("pod-initializing-stuck", 0, 0, nodeName1, func(pod *v1.Pod) { + pod.Status.ContainerStatuses = []v1.ContainerStatus{ + { + State: v1.ContainerState{ + Waiting: &v1.ContainerStateWaiting{Reason: "PodInitializing"}, + }, + }, + } + test.SetRSOwnerRef(pod) + pod.ObjectMeta.CreationTimestamp = olderPodCreationTime + }), + }, + nodes: []*v1.Node{buildTestNode1()}, + expectedEvictedPodCount: 1, + }, { description: "1 pod with container status ImagePullBackOff should be evicted", args: &PodLifeTimeArgs{ @@ -528,62 +596,6 @@ func TestPodLifeTime(t *testing.T) { nodes: []*v1.Node{buildTestNode1()}, expectedEvictedPodCount: 1, }, - { - description: "1 pod with pod status reason NodeLost should be evicted", - args: &PodLifeTimeArgs{ - MaxPodLifeTimeSeconds: &maxLifeTime, - States: []string{"NodeLost"}, - }, - pods: []*v1.Pod{ - buildTestPodWithRSOwnerRefWithPendingPhaseForNode1("p9", olderPodCreationTime, func(pod *v1.Pod) { - pod.Status.Reason = "NodeLost" - }), - }, - nodes: []*v1.Node{buildTestNode1()}, - expectedEvictedPodCount: 1, - }, - { - description: "1 pod with pod status reason NodeAffinity should be evicted", - args: &PodLifeTimeArgs{ - MaxPodLifeTimeSeconds: &maxLifeTime, - States: []string{"NodeAffinity"}, - }, - pods: []*v1.Pod{ - buildTestPodWithRSOwnerRefWithPendingPhaseForNode1("p9", olderPodCreationTime, func(pod *v1.Pod) { - pod.Status.Reason = "NodeAffinity" - }), - }, - nodes: []*v1.Node{buildTestNode1()}, - expectedEvictedPodCount: 1, - }, - { - description: "1 pod with pod status reason Shutdown should be evicted", - args: &PodLifeTimeArgs{ - MaxPodLifeTimeSeconds: &maxLifeTime, - States: []string{"Shutdown"}, - }, - pods: []*v1.Pod{ - buildTestPodWithRSOwnerRefWithPendingPhaseForNode1("p9", olderPodCreationTime, func(pod *v1.Pod) { - pod.Status.Reason = "Shutdown" - }), - }, - nodes: []*v1.Node{buildTestNode1()}, - expectedEvictedPodCount: 1, - }, - { - description: "1 pod with pod status reason UnexpectedAdmissionError should be evicted", - args: &PodLifeTimeArgs{ - MaxPodLifeTimeSeconds: &maxLifeTime, - States: []string{"UnexpectedAdmissionError"}, - }, - pods: []*v1.Pod{ - buildTestPodWithRSOwnerRefWithPendingPhaseForNode1("p9", olderPodCreationTime, func(pod *v1.Pod) { - pod.Status.Reason = "UnexpectedAdmissionError" - }), - }, - nodes: []*v1.Node{buildTestNode1()}, - expectedEvictedPodCount: 1, - }, { description: "1 pod with ImagePullBackOff status should be ignored when States filter is ContainerCreating", args: &PodLifeTimeArgs{ From e05de87368ac82f800c14baca62511f454864d64 Mon Sep 17 00:00:00 2001 From: Jan Chaloupka Date: Sat, 13 Dec 2025 13:46:24 +0100 Subject: [PATCH 6/9] refactor(TestPodLifeTime): extract pod status reason tests --- .../plugins/podlifetime/pod_lifetime_test.go | 124 ++++++++++-------- 1 file changed, 68 insertions(+), 56 deletions(-) diff --git a/pkg/framework/plugins/podlifetime/pod_lifetime_test.go b/pkg/framework/plugins/podlifetime/pod_lifetime_test.go index b211708f2..5486bc014 100644 --- a/pkg/framework/plugins/podlifetime/pod_lifetime_test.go +++ b/pkg/framework/plugins/podlifetime/pod_lifetime_test.go @@ -280,62 +280,6 @@ func TestPodLifeTime(t *testing.T) { expectedEvictedPodCount: 1, expectedEvictedPods: []string{"p2"}, }, - { - description: "1 pod with pod status reason NodeLost should be evicted", - args: &PodLifeTimeArgs{ - MaxPodLifeTimeSeconds: &maxLifeTime, - States: []string{"NodeLost"}, - }, - pods: []*v1.Pod{ - buildTestPodWithRSOwnerRefWithPendingPhaseForNode1("p9", olderPodCreationTime, func(pod *v1.Pod) { - pod.Status.Reason = "NodeLost" - }), - }, - nodes: []*v1.Node{buildTestNode1()}, - expectedEvictedPodCount: 1, - }, - { - description: "1 pod with pod status reason NodeAffinity should be evicted", - args: &PodLifeTimeArgs{ - MaxPodLifeTimeSeconds: &maxLifeTime, - States: []string{"NodeAffinity"}, - }, - pods: []*v1.Pod{ - buildTestPodWithRSOwnerRefWithPendingPhaseForNode1("p9", olderPodCreationTime, func(pod *v1.Pod) { - pod.Status.Reason = "NodeAffinity" - }), - }, - nodes: []*v1.Node{buildTestNode1()}, - expectedEvictedPodCount: 1, - }, - { - description: "1 pod with pod status reason Shutdown should be evicted", - args: &PodLifeTimeArgs{ - MaxPodLifeTimeSeconds: &maxLifeTime, - States: []string{"Shutdown"}, - }, - pods: []*v1.Pod{ - buildTestPodWithRSOwnerRefWithPendingPhaseForNode1("p9", olderPodCreationTime, func(pod *v1.Pod) { - pod.Status.Reason = "Shutdown" - }), - }, - nodes: []*v1.Node{buildTestNode1()}, - expectedEvictedPodCount: 1, - }, - { - description: "1 pod with pod status reason UnexpectedAdmissionError should be evicted", - args: &PodLifeTimeArgs{ - MaxPodLifeTimeSeconds: &maxLifeTime, - States: []string{"UnexpectedAdmissionError"}, - }, - pods: []*v1.Pod{ - buildTestPodWithRSOwnerRefWithPendingPhaseForNode1("p9", olderPodCreationTime, func(pod *v1.Pod) { - pod.Status.Reason = "UnexpectedAdmissionError" - }), - }, - nodes: []*v1.Node{buildTestNode1()}, - expectedEvictedPodCount: 1, - }, } for _, tc := range testCases { @@ -625,6 +569,74 @@ func TestPodLifeTime_ContainerWaitingReasons(t *testing.T) { } } +func TestPodLifeTime_PodStatusReasons(t *testing.T) { + var maxLifeTime uint = 600 + testCases := []podLifeTimeTestCase{ + { + description: "1 pod with pod status reason NodeLost should be evicted", + args: &PodLifeTimeArgs{ + MaxPodLifeTimeSeconds: &maxLifeTime, + States: []string{"NodeLost"}, + }, + pods: []*v1.Pod{ + buildTestPodWithRSOwnerRefWithPendingPhaseForNode1("p9", olderPodCreationTime, func(pod *v1.Pod) { + pod.Status.Reason = "NodeLost" + }), + }, + nodes: []*v1.Node{buildTestNode1()}, + expectedEvictedPodCount: 1, + }, + { + description: "1 pod with pod status reason NodeAffinity should be evicted", + args: &PodLifeTimeArgs{ + MaxPodLifeTimeSeconds: &maxLifeTime, + States: []string{"NodeAffinity"}, + }, + pods: []*v1.Pod{ + buildTestPodWithRSOwnerRefWithPendingPhaseForNode1("p9", olderPodCreationTime, func(pod *v1.Pod) { + pod.Status.Reason = "NodeAffinity" + }), + }, + nodes: []*v1.Node{buildTestNode1()}, + expectedEvictedPodCount: 1, + }, + { + description: "1 pod with pod status reason Shutdown should be evicted", + args: &PodLifeTimeArgs{ + MaxPodLifeTimeSeconds: &maxLifeTime, + States: []string{"Shutdown"}, + }, + pods: []*v1.Pod{ + buildTestPodWithRSOwnerRefWithPendingPhaseForNode1("p9", olderPodCreationTime, func(pod *v1.Pod) { + pod.Status.Reason = "Shutdown" + }), + }, + nodes: []*v1.Node{buildTestNode1()}, + expectedEvictedPodCount: 1, + }, + { + description: "1 pod with pod status reason UnexpectedAdmissionError should be evicted", + args: &PodLifeTimeArgs{ + MaxPodLifeTimeSeconds: &maxLifeTime, + States: []string{"UnexpectedAdmissionError"}, + }, + pods: []*v1.Pod{ + buildTestPodWithRSOwnerRefWithPendingPhaseForNode1("p9", olderPodCreationTime, func(pod *v1.Pod) { + pod.Status.Reason = "UnexpectedAdmissionError" + }), + }, + nodes: []*v1.Node{buildTestNode1()}, + expectedEvictedPodCount: 1, + }, + } + + for _, tc := range testCases { + t.Run(tc.description, func(t *testing.T) { + runPodLifeTimeTest(t, tc) + }) + } +} + func TestPodLifeTime_AgeThreshold(t *testing.T) { var maxLifeTime uint = 600 testCases := []podLifeTimeTestCase{ From 2658864ac0b00036d0653303742fe37296be5e8b Mon Sep 17 00:00:00 2001 From: Jan Chaloupka Date: Sat, 13 Dec 2025 13:51:12 +0100 Subject: [PATCH 7/9] refactor(TestPodLifeTime): extract eviction limits tests --- .../plugins/podlifetime/pod_lifetime_test.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/pkg/framework/plugins/podlifetime/pod_lifetime_test.go b/pkg/framework/plugins/podlifetime/pod_lifetime_test.go index 5486bc014..62ce00491 100644 --- a/pkg/framework/plugins/podlifetime/pod_lifetime_test.go +++ b/pkg/framework/plugins/podlifetime/pod_lifetime_test.go @@ -221,6 +221,18 @@ func TestPodLifeTime(t *testing.T) { nodes: []*v1.Node{buildTestNode1()}, expectedEvictedPodCount: 0, }, + } + + for _, tc := range testCases { + t.Run(tc.description, func(t *testing.T) { + runPodLifeTimeTest(t, tc) + }) + } +} + +func TestPodLifeTime_EvictionLimits(t *testing.T) { + var maxLifeTime uint = 600 + testCases := []podLifeTimeTestCase{ { description: "2 Oldest pods should be evicted when maxPodsToEvictPerNode and maxPodsToEvictPerNamespace are not set", args: &PodLifeTimeArgs{ From 2710fd3781d77c44db23c455a17ffbc0632c233d Mon Sep 17 00:00:00 2001 From: Jan Chaloupka Date: Sat, 13 Dec 2025 13:53:59 +0100 Subject: [PATCH 8/9] refactor(TestPodLifeTime): extract evictor configuration tests --- .../plugins/podlifetime/pod_lifetime_test.go | 90 +++++++++++-------- 1 file changed, 51 insertions(+), 39 deletions(-) diff --git a/pkg/framework/plugins/podlifetime/pod_lifetime_test.go b/pkg/framework/plugins/podlifetime/pod_lifetime_test.go index 62ce00491..def1cf800 100644 --- a/pkg/framework/plugins/podlifetime/pod_lifetime_test.go +++ b/pkg/framework/plugins/podlifetime/pod_lifetime_test.go @@ -143,45 +143,6 @@ func runPodLifeTimeTest(t *testing.T, tc podLifeTimeTestCase) { func TestPodLifeTime(t *testing.T) { var maxLifeTime uint = 600 testCases := []podLifeTimeTestCase{ - { - description: "Does not evict pvc pods with ignorePvcPods set to true", - args: &PodLifeTimeArgs{ - MaxPodLifeTimeSeconds: &maxLifeTime, - }, - pods: []*v1.Pod{ - buildTestPodWithRSOwnerRefForNode1("p11", olderPodCreationTime, func(pod *v1.Pod) { - pod.Spec.Volumes = []v1.Volume{ - { - Name: "pvc", VolumeSource: v1.VolumeSource{ - PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{ClaimName: "foo"}, - }, - }, - } - }), - }, - nodes: []*v1.Node{buildTestNode1()}, - expectedEvictedPodCount: 0, - ignorePvcPods: true, - }, - { - description: "Evicts pvc pods with ignorePvcPods set to false (or unset)", - args: &PodLifeTimeArgs{ - MaxPodLifeTimeSeconds: &maxLifeTime, - }, - pods: []*v1.Pod{ - buildTestPodWithRSOwnerRefForNode1("p11", olderPodCreationTime, func(pod *v1.Pod) { - pod.Spec.Volumes = []v1.Volume{ - { - Name: "pvc", VolumeSource: v1.VolumeSource{ - PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{ClaimName: "foo"}, - }, - }, - } - }), - }, - nodes: []*v1.Node{buildTestNode1()}, - expectedEvictedPodCount: 1, - }, { description: "1 pod matching label selector should be evicted", args: &PodLifeTimeArgs{ @@ -230,6 +191,57 @@ func TestPodLifeTime(t *testing.T) { } } +func TestPodLifeTime_EvictorConfiguration(t *testing.T) { + var maxLifeTime uint = 600 + testCases := []podLifeTimeTestCase{ + { + description: "Does not evict pvc pods with ignorePvcPods set to true", + args: &PodLifeTimeArgs{ + MaxPodLifeTimeSeconds: &maxLifeTime, + }, + pods: []*v1.Pod{ + buildTestPodWithRSOwnerRefForNode1("p11", olderPodCreationTime, func(pod *v1.Pod) { + pod.Spec.Volumes = []v1.Volume{ + { + Name: "pvc", VolumeSource: v1.VolumeSource{ + PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{ClaimName: "foo"}, + }, + }, + } + }), + }, + nodes: []*v1.Node{buildTestNode1()}, + expectedEvictedPodCount: 0, + ignorePvcPods: true, + }, + { + description: "Evicts pvc pods with ignorePvcPods set to false (or unset)", + args: &PodLifeTimeArgs{ + MaxPodLifeTimeSeconds: &maxLifeTime, + }, + pods: []*v1.Pod{ + buildTestPodWithRSOwnerRefForNode1("p11", olderPodCreationTime, func(pod *v1.Pod) { + pod.Spec.Volumes = []v1.Volume{ + { + Name: "pvc", VolumeSource: v1.VolumeSource{ + PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{ClaimName: "foo"}, + }, + }, + } + }), + }, + nodes: []*v1.Node{buildTestNode1()}, + expectedEvictedPodCount: 1, + }, + } + + for _, tc := range testCases { + t.Run(tc.description, func(t *testing.T) { + runPodLifeTimeTest(t, tc) + }) + } +} + func TestPodLifeTime_EvictionLimits(t *testing.T) { var maxLifeTime uint = 600 testCases := []podLifeTimeTestCase{ From e5bbedb6020afa880cb47060ab789486c63a30dd Mon Sep 17 00:00:00 2001 From: Jan Chaloupka Date: Sat, 13 Dec 2025 13:56:26 +0100 Subject: [PATCH 9/9] refactor(TestPodLifeTime): extract generic filtering tests --- .../plugins/podlifetime/pod_lifetime_test.go | 52 ++++++++++++------- 1 file changed, 32 insertions(+), 20 deletions(-) diff --git a/pkg/framework/plugins/podlifetime/pod_lifetime_test.go b/pkg/framework/plugins/podlifetime/pod_lifetime_test.go index def1cf800..318f92541 100644 --- a/pkg/framework/plugins/podlifetime/pod_lifetime_test.go +++ b/pkg/framework/plugins/podlifetime/pod_lifetime_test.go @@ -143,26 +143,6 @@ func runPodLifeTimeTest(t *testing.T, tc podLifeTimeTestCase) { func TestPodLifeTime(t *testing.T) { var maxLifeTime uint = 600 testCases := []podLifeTimeTestCase{ - { - description: "1 pod matching label selector should be evicted", - args: &PodLifeTimeArgs{ - MaxPodLifeTimeSeconds: &maxLifeTime, - LabelSelector: &metav1.LabelSelector{ - MatchLabels: map[string]string{"foo": "bar"}, - }, - }, - pods: []*v1.Pod{ - buildTestPodWithRSOwnerRefForNode1("p12", olderPodCreationTime, func(pod *v1.Pod) { - pod.ObjectMeta.Labels = map[string]string{"foo": "bar"} - }), - buildTestPodWithRSOwnerRefForNode1("p13", olderPodCreationTime, func(pod *v1.Pod) { - pod.ObjectMeta.Labels = map[string]string{"foo": "bar1"} - }), - }, - nodes: []*v1.Node{buildTestNode1()}, - expectedEvictedPodCount: 1, - expectedEvictedPods: []string{"p12"}, - }, { description: "No pod should be evicted since pod terminating", args: &PodLifeTimeArgs{ @@ -242,6 +222,38 @@ func TestPodLifeTime_EvictorConfiguration(t *testing.T) { } } +func TestPodLifeTime_GenericFiltering(t *testing.T) { + var maxLifeTime uint = 600 + testCases := []podLifeTimeTestCase{ + { + description: "1 pod matching label selector should be evicted", + args: &PodLifeTimeArgs{ + MaxPodLifeTimeSeconds: &maxLifeTime, + LabelSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"foo": "bar"}, + }, + }, + pods: []*v1.Pod{ + buildTestPodWithRSOwnerRefForNode1("p12", olderPodCreationTime, func(pod *v1.Pod) { + pod.ObjectMeta.Labels = map[string]string{"foo": "bar"} + }), + buildTestPodWithRSOwnerRefForNode1("p13", olderPodCreationTime, func(pod *v1.Pod) { + pod.ObjectMeta.Labels = map[string]string{"foo": "bar1"} + }), + }, + nodes: []*v1.Node{buildTestNode1()}, + expectedEvictedPodCount: 1, + expectedEvictedPods: []string{"p12"}, + }, + } + + for _, tc := range testCases { + t.Run(tc.description, func(t *testing.T) { + runPodLifeTimeTest(t, tc) + }) + } +} + func TestPodLifeTime_EvictionLimits(t *testing.T) { var maxLifeTime uint = 600 testCases := []podLifeTimeTestCase{