Skip to content

Commit 0f31bef

Browse files
committed
Default grace period to 0 when --force is used to delete an object
1 parent ca23b07 commit 0f31bef

File tree

3 files changed

+149
-41
lines changed

3 files changed

+149
-41
lines changed

staging/src/k8s.io/kubectl/pkg/cmd/delete/delete.go

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,7 @@ var (
5252
the --grace-period flag, or pass --now to set a grace-period of 1. Because these resources often
5353
represent entities in the cluster, deletion may not be acknowledged immediately. If the node
5454
hosting a pod is down or cannot reach the API server, termination may take significantly longer
55-
than the grace period. To force delete a resource, you must pass a grace period of 0 and specify
56-
the --force flag.
55+
than the grace period. To force delete a resource, you must specify the --force flag.
5756
Note: only a subset of resources support graceful deletion. In absence of the support, --grace-period is ignored.
5857
5958
IMPORTANT: Force deleting pods does not wait for confirmation that the pod's processes have been
@@ -90,7 +89,7 @@ var (
9089
kubectl delete pod foo --now
9190
9291
# Force delete a pod on a dead node
93-
kubectl delete pod foo --grace-period=0 --force
92+
kubectl delete pod foo --force
9493
9594
# Delete all pods
9695
kubectl delete pods --all`))
@@ -176,6 +175,9 @@ func (o *DeleteOptions) Complete(f cmdutil.Factory, args []string, cmd *cobra.Co
176175
// into --grace-period=1. Users may provide --force to bypass this conversion.
177176
o.GracePeriod = 1
178177
}
178+
if o.ForceDeletion && o.GracePeriod < 0 {
179+
o.GracePeriod = 0
180+
}
179181

180182
o.DryRunStrategy, err = cmdutil.GetDryRunStrategy(cmd)
181183
if err != nil {
@@ -239,8 +241,8 @@ func (o *DeleteOptions) Validate() error {
239241
switch {
240242
case o.GracePeriod == 0 && o.ForceDeletion:
241243
fmt.Fprintf(o.ErrOut, "warning: Immediate deletion does not wait for confirmation that the running resource has been terminated. The resource may continue to run on the cluster indefinitely.\n")
242-
case o.ForceDeletion:
243-
fmt.Fprintf(o.ErrOut, "warning: --force is ignored because --grace-period is not 0.\n")
244+
case o.GracePeriod > 0 && o.ForceDeletion:
245+
return fmt.Errorf("--force and --grace-period greater than 0 cannot be specified together")
244246
}
245247

246248
if len(o.Raw) > 0 {

staging/src/k8s.io/kubectl/pkg/cmd/delete/delete_flags.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ func (f *DeleteFlags) AddFlags(cmd *cobra.Command) {
116116
cmd.Flags().BoolVarP(f.AllNamespaces, "all-namespaces", "A", *f.AllNamespaces, "If present, list the requested object(s) across all namespaces. Namespace in current context is ignored even if specified with --namespace.")
117117
}
118118
if f.Force != nil {
119-
cmd.Flags().BoolVar(f.Force, "force", *f.Force, "Only used when grace-period=0. If true, immediately remove resources from API and bypass graceful deletion. Note that immediate deletion of some resources may result in inconsistency or data loss and requires confirmation.")
119+
cmd.Flags().BoolVar(f.Force, "force", *f.Force, "If true, immediately remove resources from API and bypass graceful deletion. Note that immediate deletion of some resources may result in inconsistency or data loss and requires confirmation.")
120120
}
121121
if f.Cascade != nil {
122122
cmd.Flags().BoolVar(f.Cascade, "cascade", *f.Cascade, "If true, cascade the deletion of the resources managed by this resource (e.g. Pods created by a ReplicationController). Default true.")

staging/src/k8s.io/kubectl/pkg/cmd/delete/delete_test.go

Lines changed: 141 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"io"
2222
"io/ioutil"
2323
"net/http"
24+
"strconv"
2425
"strings"
2526
"testing"
2627

@@ -250,52 +251,157 @@ func TestDeleteObject(t *testing.T) {
250251
}
251252
}
252253

253-
func TestDeleteObjectGraceZero(t *testing.T) {
254-
cmdtesting.InitTestErrorHandler(t)
254+
func TestGracePeriodScenarios(t *testing.T) {
255255
pods, _, _ := cmdtesting.TestData()
256256

257-
count := 0
258257
tf := cmdtesting.NewTestFactory().WithNamespace("test")
259258
defer tf.Cleanup()
260259

261260
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
262261

263-
tf.UnstructuredClient = &fake.RESTClient{
264-
NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer,
265-
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
266-
t.Logf("got request %s %s", req.Method, req.URL.Path)
267-
switch p, m := req.URL.Path, req.Method; {
268-
case p == "/namespaces/test/pods/nginx" && m == "GET":
269-
count++
270-
switch count {
271-
case 1, 2, 3:
272-
return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &pods.Items[0])}, nil
273-
default:
274-
return &http.Response{StatusCode: http.StatusNotFound, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &metav1.Status{})}, nil
262+
tc := []struct {
263+
name string
264+
cmdArgs []string
265+
forceFlag bool
266+
nowFlag bool
267+
gracePeriodFlag string
268+
expectedGracePeriod string
269+
expectedOut string
270+
expectedErrOut string
271+
expectedDeleteRequestPath string
272+
expectedExitCode int
273+
}{
274+
{
275+
name: "Deleting an object with --force should use grace period = 0",
276+
cmdArgs: []string{"pods/foo"},
277+
forceFlag: true,
278+
expectedGracePeriod: "0",
279+
expectedOut: "pod/foo\n",
280+
expectedErrOut: "warning: Immediate deletion does not wait for confirmation that the running resource has been terminated. The resource may continue to run on the cluster indefinitely.\n",
281+
expectedDeleteRequestPath: "/namespaces/test/pods/foo",
282+
},
283+
{
284+
name: "Deleting an object with --force and --grace-period 0 should use grade period = 0",
285+
cmdArgs: []string{"pods/foo"},
286+
forceFlag: true,
287+
gracePeriodFlag: "0",
288+
expectedGracePeriod: "0",
289+
expectedOut: "pod/foo\n",
290+
expectedErrOut: "warning: Immediate deletion does not wait for confirmation that the running resource has been terminated. The resource may continue to run on the cluster indefinitely.\n",
291+
expectedDeleteRequestPath: "/namespaces/test/pods/foo",
292+
},
293+
{
294+
name: "Deleting an object with --force and --grace-period > 0 should fail",
295+
cmdArgs: []string{"pods/foo"},
296+
forceFlag: true,
297+
gracePeriodFlag: "10",
298+
expectedErrOut: "error: --force and --grace-period greater than 0 cannot be specified together",
299+
expectedExitCode: 1,
300+
},
301+
{
302+
name: "Deleting an object with --grace-period 0 should use a grace period of 1",
303+
cmdArgs: []string{"pods/foo"},
304+
gracePeriodFlag: "0",
305+
expectedGracePeriod: "1",
306+
expectedOut: "pod/foo\n",
307+
expectedDeleteRequestPath: "/namespaces/test/pods/foo",
308+
},
309+
{
310+
name: "Deleting an object with --grace-period > 0 should use the specified grace period",
311+
cmdArgs: []string{"pods/foo"},
312+
gracePeriodFlag: "10",
313+
expectedGracePeriod: "10",
314+
expectedOut: "pod/foo\n",
315+
expectedDeleteRequestPath: "/namespaces/test/pods/foo",
316+
},
317+
{
318+
name: "Deleting an object with the --now flag should use grace period = 1",
319+
cmdArgs: []string{"pods/foo"},
320+
nowFlag: true,
321+
expectedGracePeriod: "1",
322+
expectedOut: "pod/foo\n",
323+
expectedDeleteRequestPath: "/namespaces/test/pods/foo",
324+
},
325+
{
326+
name: "Deleting an object with --now and --grace-period should fail",
327+
cmdArgs: []string{"pods/foo"},
328+
nowFlag: true,
329+
gracePeriodFlag: "10",
330+
expectedErrOut: "error: --now and --grace-period cannot be specified together",
331+
expectedExitCode: 1,
332+
},
333+
}
334+
335+
for _, test := range tc {
336+
t.Run(test.name, func(t *testing.T) {
337+
338+
// Use a custom fatal behavior with panic/recover so that we can test failure scenarios where
339+
// os.Exit() would normally be called
340+
cmdutil.BehaviorOnFatal(func(actualErrOut string, actualExitCode int) {
341+
if test.expectedExitCode != actualExitCode {
342+
t.Errorf("unexpected exit code:\n\tExpected: %d\n\tActual: %d\n", test.expectedExitCode, actualExitCode)
275343
}
276-
case p == "/api/v1/namespaces/test" && m == "GET":
277-
return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &corev1.Namespace{})}, nil
278-
case p == "/namespaces/test/pods/nginx" && m == "DELETE":
279-
return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &pods.Items[0])}, nil
280-
default:
281-
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
282-
return nil, nil
344+
if test.expectedErrOut != actualErrOut {
345+
t.Errorf("unexpected error:\n\tExpected: %s\n\tActual: %s\n", test.expectedErrOut, actualErrOut)
346+
}
347+
panic(nil)
348+
})
349+
defer func() {
350+
if test.expectedExitCode != 0 {
351+
recover()
352+
}
353+
}()
354+
355+
// Setup a fake HTTP Client to capture whether a delete request was made or not and if so,
356+
// the actual grace period that was used.
357+
actualGracePeriod := ""
358+
deleteOccurred := false
359+
tf.UnstructuredClient = &fake.RESTClient{
360+
NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer,
361+
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
362+
switch p, m := req.URL.Path, req.Method; {
363+
case m == "DELETE" && p == test.expectedDeleteRequestPath:
364+
data := make(map[string]interface{})
365+
_ = json.NewDecoder(req.Body).Decode(&data)
366+
actualGracePeriod = strconv.FormatFloat(data["gracePeriodSeconds"].(float64), 'f', 0, 64)
367+
deleteOccurred = true
368+
return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &pods.Items[0])}, nil
369+
default:
370+
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
371+
return nil, nil
372+
}
373+
}),
283374
}
284-
}),
285-
}
286375

287-
streams, _, buf, errBuf := genericclioptions.NewTestIOStreams()
288-
cmd := NewCmdDelete(tf, streams)
289-
cmd.Flags().Set("output", "name")
290-
cmd.Flags().Set("grace-period", "0")
291-
cmd.Run(cmd, []string{"pods/nginx"})
376+
// Test the command using the flags specified in the test case
377+
streams, _, out, errOut := genericclioptions.NewTestIOStreams()
378+
cmd := NewCmdDelete(tf, streams)
379+
cmd.Flags().Set("output", "name")
380+
if test.forceFlag {
381+
cmd.Flags().Set("force", "true")
382+
}
383+
if test.nowFlag {
384+
cmd.Flags().Set("now", "true")
385+
}
386+
if len(test.gracePeriodFlag) > 0 {
387+
cmd.Flags().Set("grace-period", test.gracePeriodFlag)
388+
}
389+
cmd.Run(cmd, test.cmdArgs)
292390

293-
// uses the name from the file, not the response
294-
if buf.String() != "pod/nginx\n" {
295-
t.Errorf("unexpected output: %s\n---\n%s", buf.String(), errBuf.String())
296-
}
297-
if count != 0 {
298-
t.Errorf("unexpected calls to GET: %d", count)
391+
// Check actual vs expected conditions
392+
if len(test.expectedDeleteRequestPath) > 0 && !deleteOccurred {
393+
t.Errorf("expected http delete request to %s but it did not occur", test.expectedDeleteRequestPath)
394+
}
395+
if test.expectedGracePeriod != actualGracePeriod {
396+
t.Errorf("unexpected grace period:\n\tExpected: %s\n\tActual: %s\n", test.expectedGracePeriod, actualGracePeriod)
397+
}
398+
if out.String() != test.expectedOut {
399+
t.Errorf("unexpected output:\n\tExpected: %s\n\tActual: %s\n", test.expectedOut, out.String())
400+
}
401+
if errOut.String() != test.expectedErrOut {
402+
t.Errorf("unexpected error output:\n\tExpected: %s\n\tActual: %s\n", test.expectedErrOut, errOut.String())
403+
}
404+
})
299405
}
300406
}
301407

0 commit comments

Comments
 (0)