Merge pull request #137543 from kfess/bugfix/disable-eviction-dry-run

Fix: kubectl drain --disable-eviction --dry-run=server hanging indefinitely

Kubernetes-commit: 47f990437458a2b171f51b5e97a0c28c81d949d1
This commit is contained in:
Kubernetes Publisher 2026-05-05 15:12:30 +05:30
commit 97f0e109d6
4 changed files with 74 additions and 3 deletions

2
go.mod
View file

@ -30,7 +30,7 @@ require (
golang.org/x/sys v0.43.0
golang.org/x/text v0.36.0
gopkg.in/evanphx/json-patch.v4 v4.13.0
k8s.io/api v0.0.0-20260504204501-f40e8ff20019
k8s.io/api v0.0.0-20260505124447-9279b4068be3
k8s.io/apimachinery v0.0.0-20260504204121-2fca5de43c56
k8s.io/cli-runtime v0.0.0-20260504213337-8858f6b36386
k8s.io/client-go v0.0.0-20260504205003-535f2d0806f7

4
go.sum
View file

@ -205,8 +205,8 @@ gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
k8s.io/api v0.0.0-20260504204501-f40e8ff20019 h1:ldAUH3G96shgtYJbZwndQTKhRAMZ2dDrkC7Pw0u15uM=
k8s.io/api v0.0.0-20260504204501-f40e8ff20019/go.mod h1:3USlyQsXhfXGbJzZevPnCx4ZCGaYM/xzUYOlYcB/eUE=
k8s.io/api v0.0.0-20260505124447-9279b4068be3 h1:DhwY3OTw7UCwd88KVVm2jbnPFLTRy27ZvMSI2MBPGFk=
k8s.io/api v0.0.0-20260505124447-9279b4068be3/go.mod h1:3USlyQsXhfXGbJzZevPnCx4ZCGaYM/xzUYOlYcB/eUE=
k8s.io/apimachinery v0.0.0-20260504204121-2fca5de43c56 h1:JN0n7OEc9lF1Rnk+09tWPA9FC2v7+wxxR+3lgBo4uNU=
k8s.io/apimachinery v0.0.0-20260504204121-2fca5de43c56/go.mod h1:VERTuh5iDSri6+w9SKXTmWbqrGWrdBWrNaC/gYIcRTY=
k8s.io/cli-runtime v0.0.0-20260504213337-8858f6b36386 h1:71wIHWEqonNQNCV4fZUZfNy85N1fvFW7jw2Xc7DVk1A=

View file

@ -390,10 +390,18 @@ 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)
}
}
if d.DryRunStrategy == cmdutil.DryRunServer {
return nil
}
ctx := d.getContext()
params := waitForDeleteParams{
ctx: ctx,

View file

@ -17,12 +17,14 @@ limitations under the License.
package drain
import (
"bytes"
"context"
"errors"
"fmt"
"math"
"os"
"reflect"
"slices"
"sort"
"strconv"
"testing"
@ -38,6 +40,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 +464,66 @@ 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 <= 2; i++ {
pod := corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: fmt.Sprintf("mypod-%d", i),
Namespace: "default",
},
}
allPods = append(allPods, &pod)
podsToDelete = append(podsToDelete, pod)
}
k := fake.NewSimpleClientset(allPods...)
// 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 slices.Contains(deleteAction.GetDeleteOptions().DryRun, metav1.DryRunAll) {
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()
}