From 2e7c87194445eff9e0a7a73427d26177f2bbe0df Mon Sep 17 00:00:00 2001 From: kita456 Date: Sun, 8 Mar 2026 12:05:32 +0900 Subject: [PATCH 1/4] fix: kubectl drain --disable-eviction --dry-run=server hanging indefinitely Kubernetes-commit: bc5302ec93b280d2816522b8a4b9b41e0b24f965 --- pkg/drain/drain.go | 5 ++++ pkg/drain/drain_test.go | 65 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+) diff --git a/pkg/drain/drain.go b/pkg/drain/drain.go index b280d20ba..061972493 100644 --- a/pkg/drain/drain.go +++ b/pkg/drain/drain.go @@ -394,6 +394,11 @@ func (d *Helper) deletePods(pods []corev1.Pod, getPodFn func(namespace, name str d.OnPodDeletionOrEvictionStarted(&pod, false) } } + + if d.DryRunStrategy == cmdutil.DryRunServer { + return nil + } + ctx := d.getContext() params := waitForDeleteParams{ ctx: ctx, diff --git a/pkg/drain/drain_test.go b/pkg/drain/drain_test.go index 60fabb9f0..42aa9d502 100644 --- a/pkg/drain/drain_test.go +++ b/pkg/drain/drain_test.go @@ -17,6 +17,7 @@ limitations under the License. package drain import ( + "bytes" "context" "errors" "fmt" @@ -38,6 +39,7 @@ import ( "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/kubernetes/fake" ktest "k8s.io/client-go/testing" + cmdutil "k8s.io/kubectl/pkg/cmd/util" ) func TestDeletePods(t *testing.T) { @@ -461,6 +463,69 @@ func TestDeleteOrEvict(t *testing.T) { } } +func TestDeleteOrEvictWithDryRunServer(t *testing.T) { + testCases := []struct { + description string + disableEviction bool + }{ + { + description: "Eviction enabled with server dry-run", + disableEviction: false, + }, + { + description: "Eviction disabled with server dry-run", + disableEviction: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.description, func(t *testing.T) { + var buf bytes.Buffer + h := &Helper{ + Out: &buf, + GracePeriodSeconds: 10, + DisableEviction: tc.disableEviction, + DryRunStrategy: cmdutil.DryRunServer, + } + + var allPods []runtime.Object + var podsToDelete []corev1.Pod + + for i := 1; i <= 4; i++ { + pod := corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf("mypod-%d", i), + Namespace: "default", + }, + } + allPods = append(allPods, &pod) + if i <= 2 { + podsToDelete = append(podsToDelete, pod) + } + } + + k := fake.NewSimpleClientset(allPods...) + + // fake clientset will actually delete objects from the in-memory store. + // This reactor intercepts delete requests with DryRun set and returns success without + // removing the object, simulating real API server dry-run behavior. + k.PrependReactor("delete", "pods", func(actions ktest.Action) (bool, runtime.Object, error) { + deleteAction := actions.(ktest.DeleteAction) + if len(deleteAction.GetDeleteOptions().DryRun) > 0 { + return true, nil, nil + } + return false, nil, nil + }) + addEvictionSupport(t, k, "v1") + h.Client = k + + if err := h.DeleteOrEvictPods(podsToDelete); err != nil { + t.Fatalf("error from DeleteOrEvictPods: %v", err) + } + }) + } +} + func mockFilterSkip(_ corev1.Pod) PodDeleteStatus { return MakePodDeleteStatusSkip() } From c388805377a82096fb54c8d1e2c4e817e28f4d73 Mon Sep 17 00:00:00 2001 From: kfess Date: Thu, 30 Apr 2026 13:59:58 +0900 Subject: [PATCH 2/4] apply review comment Kubernetes-commit: 54fa811dfd8f0fab63eab030a1ffd5464ecbc499 --- pkg/drain/drain_test.go | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/pkg/drain/drain_test.go b/pkg/drain/drain_test.go index 42aa9d502..49418b156 100644 --- a/pkg/drain/drain_test.go +++ b/pkg/drain/drain_test.go @@ -491,7 +491,7 @@ func TestDeleteOrEvictWithDryRunServer(t *testing.T) { var allPods []runtime.Object var podsToDelete []corev1.Pod - for i := 1; i <= 4; i++ { + for i := 1; i <= 2; i++ { pod := corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ Name: fmt.Sprintf("mypod-%d", i), @@ -499,20 +499,19 @@ func TestDeleteOrEvictWithDryRunServer(t *testing.T) { }, } allPods = append(allPods, &pod) - if i <= 2 { - podsToDelete = append(podsToDelete, pod) - } + podsToDelete = append(podsToDelete, pod) } k := fake.NewSimpleClientset(allPods...) - // fake clientset will actually delete objects from the in-memory store. // This reactor intercepts delete requests with DryRun set and returns success without // removing the object, simulating real API server dry-run behavior. k.PrependReactor("delete", "pods", func(actions ktest.Action) (bool, runtime.Object, error) { deleteAction := actions.(ktest.DeleteAction) - if len(deleteAction.GetDeleteOptions().DryRun) > 0 { - return true, nil, nil + for _, v := range deleteAction.GetDeleteOptions().DryRun { + if v == metav1.DryRunAll { + return true, nil, nil + } } return false, nil, nil }) From 484c6b6f548640ab223c3c733c1c85d6c0f27c52 Mon Sep 17 00:00:00 2001 From: kfess Date: Thu, 30 Apr 2026 14:37:02 +0900 Subject: [PATCH 3/4] fix linter Kubernetes-commit: 2063a580434fb0b182b2a7e4deaef967ec497c4f --- pkg/drain/drain_test.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/pkg/drain/drain_test.go b/pkg/drain/drain_test.go index 49418b156..ac3e2cb42 100644 --- a/pkg/drain/drain_test.go +++ b/pkg/drain/drain_test.go @@ -24,6 +24,7 @@ import ( "math" "os" "reflect" + "slices" "sort" "strconv" "testing" @@ -508,10 +509,8 @@ func TestDeleteOrEvictWithDryRunServer(t *testing.T) { // removing the object, simulating real API server dry-run behavior. k.PrependReactor("delete", "pods", func(actions ktest.Action) (bool, runtime.Object, error) { deleteAction := actions.(ktest.DeleteAction) - for _, v := range deleteAction.GetDeleteOptions().DryRun { - if v == metav1.DryRunAll { - return true, nil, nil - } + if slices.Contains(deleteAction.GetDeleteOptions().DryRun, metav1.DryRunAll) { + return true, nil, nil } return false, nil, nil }) From d337fe1f2a9627b361af4870a5ff539b0c5f0cc2 Mon Sep 17 00:00:00 2001 From: kfess Date: Thu, 30 Apr 2026 23:10:48 +0900 Subject: [PATCH 4/4] apply code review Kubernetes-commit: 0060a62b6fdbfb8a7f4c1902b3bc58d31140f105 --- pkg/drain/drain.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/drain/drain.go b/pkg/drain/drain.go index 061972493..9f2e36b37 100644 --- a/pkg/drain/drain.go +++ b/pkg/drain/drain.go @@ -390,6 +390,9 @@ func (d *Helper) deletePods(pods []corev1.Pod, getPodFn func(namespace, name str if err != nil && !apierrors.IsNotFound(err) { return err } + if d.DryRunStrategy == cmdutil.DryRunServer { + continue + } if d.OnPodDeletionOrEvictionStarted != nil { d.OnPodDeletionOrEvictionStarted(&pod, false) }