k8s.io/kubernetes@v1.29.3/test/e2e_node/deleted_pods_test.go (about)

     1  /*
     2  Copyright 2023 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package e2enode
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"strings"
    23  	"time"
    24  
    25  	v1 "k8s.io/api/core/v1"
    26  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    27  	"k8s.io/apimachinery/pkg/util/uuid"
    28  	"k8s.io/kubernetes/test/e2e/framework"
    29  	e2epod "k8s.io/kubernetes/test/e2e/framework/pod"
    30  	imageutils "k8s.io/kubernetes/test/utils/image"
    31  	admissionapi "k8s.io/pod-security-admission/api"
    32  
    33  	"github.com/onsi/ginkgo/v2"
    34  	"github.com/onsi/gomega"
    35  )
    36  
    37  const (
    38  	testFinalizer = "example.com/test-finalizer"
    39  )
    40  
    41  var _ = SIGDescribe("Deleted pods handling", framework.WithNodeConformance(), func() {
    42  	f := framework.NewDefaultFramework("deleted-pods-test")
    43  	f.NamespacePodSecurityLevel = admissionapi.LevelBaseline
    44  
    45  	ginkgo.It("Should transition to Failed phase a pod which is deleted while pending", func(ctx context.Context) {
    46  		podName := "deleted-pending-" + string(uuid.NewUUID())
    47  		podSpec := &v1.Pod{
    48  			ObjectMeta: metav1.ObjectMeta{
    49  				Name:       podName,
    50  				Finalizers: []string{testFinalizer},
    51  			},
    52  			Spec: v1.PodSpec{
    53  				RestartPolicy: v1.RestartPolicyAlways,
    54  				Containers: []v1.Container{
    55  					{
    56  						Name:            podName,
    57  						Image:           "non-existing-repo/non-existing-image:v1.0",
    58  						ImagePullPolicy: "Always",
    59  						Command:         []string{"bash"},
    60  						Args:            []string{"-c", `echo "Hello world"`},
    61  					},
    62  				},
    63  			},
    64  		}
    65  		ginkgo.By("creating the pod with invalid image reference and finalizer")
    66  		pod := e2epod.NewPodClient(f).Create(ctx, podSpec)
    67  
    68  		ginkgo.By("set up cleanup of the finalizer")
    69  		ginkgo.DeferCleanup(e2epod.NewPodClient(f).RemoveFinalizer, pod.Name, testFinalizer)
    70  
    71  		ginkgo.By("Waiting for the pod to be scheduled so that kubelet owns it")
    72  		err := e2epod.WaitForPodScheduled(ctx, f.ClientSet, pod.Namespace, pod.Name)
    73  		framework.ExpectNoError(err, "Failed to await for the pod to be scheduled: %q", pod.Name)
    74  
    75  		ginkgo.By(fmt.Sprintf("Deleting the pod (%v/%v) to set a deletion timestamp", pod.Namespace, pod.Name))
    76  		err = e2epod.NewPodClient(f).Delete(ctx, pod.Name, metav1.DeleteOptions{})
    77  		framework.ExpectNoError(err, "Failed to delete the pod: %q", pod.Name)
    78  
    79  		ginkgo.By(fmt.Sprintf("Waiting for the pod (%v/%v) to be transitioned into the Failed phase", pod.Namespace, pod.Name))
    80  		err = e2epod.WaitForPodTerminatedInNamespace(ctx, f.ClientSet, pod.Name, "", f.Namespace.Name)
    81  		framework.ExpectNoError(err, "Failed to await for the pod to be terminated: %q", pod.Name)
    82  
    83  		ginkgo.By(fmt.Sprintf("Fetch the end state of the pod (%v/%v)", pod.Namespace, pod.Name))
    84  		pod, err = e2epod.NewPodClient(f).Get(ctx, pod.Name, metav1.GetOptions{})
    85  		framework.ExpectNoError(err, "Failed to fetch the end state of the pod: %q", pod.Name)
    86  	})
    87  
    88  	ginkgo.DescribeTable("Should transition to Failed phase a deleted pod if non-zero exit codes",
    89  		func(ctx context.Context, policy v1.RestartPolicy) {
    90  			podName := "deleted-running-" + strings.ToLower(string(policy)) + "-" + string(uuid.NewUUID())
    91  			podSpec := e2epod.MustMixinRestrictedPodSecurity(&v1.Pod{
    92  				ObjectMeta: metav1.ObjectMeta{
    93  					Name:       podName,
    94  					Finalizers: []string{testFinalizer},
    95  				},
    96  				Spec: v1.PodSpec{
    97  					RestartPolicy: policy,
    98  					Containers: []v1.Container{
    99  						{
   100  							Name:    podName,
   101  							Image:   imageutils.GetE2EImage(imageutils.BusyBox),
   102  							Command: []string{"sleep", "1800"},
   103  						},
   104  					},
   105  				},
   106  			})
   107  			ginkgo.By(fmt.Sprintf("Creating a pod (%v/%v) with restart policy: %v", f.Namespace.Name, podSpec.Name, podSpec.Spec.RestartPolicy))
   108  			pod := e2epod.NewPodClient(f).Create(ctx, podSpec)
   109  
   110  			ginkgo.By("set up cleanup of the finalizer")
   111  			ginkgo.DeferCleanup(e2epod.NewPodClient(f).RemoveFinalizer, pod.Name, testFinalizer)
   112  
   113  			ginkgo.By("Waiting for the pod to be running")
   114  			err := e2epod.WaitForPodNameRunningInNamespace(ctx, f.ClientSet, pod.Name, f.Namespace.Name)
   115  			framework.ExpectNoError(err, "Failed to await for the pod to be running: %q", pod.Name)
   116  
   117  			ginkgo.By(fmt.Sprintf("Deleting the pod (%v/%v) to set a deletion timestamp", pod.Namespace, pod.Name))
   118  			err = e2epod.NewPodClient(f).Delete(ctx, pod.Name, *metav1.NewDeleteOptions(1))
   119  			framework.ExpectNoError(err, "Failed to delete the pod: %q", pod.Name)
   120  
   121  			ginkgo.By(fmt.Sprintf("Waiting for the pod (%v/%v) to be transitioned to the failed phase", pod.Namespace, pod.Name))
   122  			err = e2epod.WaitForPodTerminatedInNamespace(ctx, f.ClientSet, pod.Name, "", f.Namespace.Name)
   123  			framework.ExpectNoError(err, "Failed to await for the pod to be terminated: %q", pod.Name)
   124  
   125  			ginkgo.By(fmt.Sprintf("Fetching the end state of the pod (%v/%v)", pod.Namespace, pod.Name))
   126  			pod, err = e2epod.NewPodClient(f).Get(ctx, pod.Name, metav1.GetOptions{})
   127  			framework.ExpectNoError(err, "Failed to fetch the end state of the pod: %q", pod.Name)
   128  
   129  			ginkgo.By(fmt.Sprintf("Verify the pod (%v/%v) container is in the terminated state", pod.Namespace, pod.Name))
   130  			gomega.Expect(pod.Status.ContainerStatuses).Should(gomega.HaveLen(1))
   131  			containerStatus := pod.Status.ContainerStatuses[0]
   132  			gomega.Expect(containerStatus.State.Terminated).ToNot(gomega.BeNil(), "The pod container is in not in the Terminated state")
   133  
   134  			ginkgo.By(fmt.Sprintf("Verify the pod (%v/%v) container exit code is 137", pod.Namespace, pod.Name))
   135  			gomega.Expect(containerStatus.State.Terminated.ExitCode).Should(gomega.Equal(int32(137)))
   136  		},
   137  		ginkgo.Entry("Restart policy Always", v1.RestartPolicyAlways),
   138  		ginkgo.Entry("Restart policy OnFailure", v1.RestartPolicyOnFailure),
   139  		ginkgo.Entry("Restart policy Never", v1.RestartPolicyNever),
   140  	)
   141  
   142  	ginkgo.DescribeTable("Should transition to Succeeded phase a deleted pod when containers complete with 0 exit code",
   143  		func(ctx context.Context, policy v1.RestartPolicy) {
   144  			podName := "deleted-running-" + strings.ToLower(string(policy)) + "-" + string(uuid.NewUUID())
   145  			podSpec := e2epod.MustMixinRestrictedPodSecurity(&v1.Pod{
   146  				ObjectMeta: metav1.ObjectMeta{
   147  					Name:       podName,
   148  					Finalizers: []string{testFinalizer},
   149  				},
   150  				Spec: v1.PodSpec{
   151  					RestartPolicy: policy,
   152  					Containers: []v1.Container{
   153  						{
   154  							Name:    podName,
   155  							Image:   imageutils.GetE2EImage(imageutils.BusyBox),
   156  							Command: []string{"sh", "-c"},
   157  							Args: []string{`
   158  							sleep 9999999 &
   159  							PID=$!
   160  							_term() {
   161  								kill $PID
   162  								echo "Caught SIGTERM signal!"
   163  							}
   164  
   165  							trap _term SIGTERM
   166  							wait $PID
   167  
   168  							exit 0
   169  							`,
   170  							},
   171  						},
   172  					},
   173  				},
   174  			})
   175  			ginkgo.By(fmt.Sprintf("Creating a pod (%v/%v) with restart policy: %v", f.Namespace.Name, podSpec.Name, podSpec.Spec.RestartPolicy))
   176  			pod := e2epod.NewPodClient(f).Create(ctx, podSpec)
   177  
   178  			ginkgo.By("set up cleanup of the finalizer")
   179  			ginkgo.DeferCleanup(e2epod.NewPodClient(f).RemoveFinalizer, pod.Name, testFinalizer)
   180  
   181  			ginkgo.By("Waiting for the pod to be running")
   182  			err := e2epod.WaitForPodNameRunningInNamespace(ctx, f.ClientSet, pod.Name, f.Namespace.Name)
   183  			framework.ExpectNoError(err, "Failed to await for the pod to be running: %q", pod.Name)
   184  
   185  			ginkgo.By(fmt.Sprintf("Deleting the pod (%v/%v) to set a deletion timestamp", pod.Namespace, pod.Name))
   186  			// wait a little bit to make sure the we are inside the while and that the trap is registered
   187  			time.Sleep(time.Second)
   188  			err = e2epod.NewPodClient(f).Delete(ctx, pod.Name, metav1.DeleteOptions{})
   189  			framework.ExpectNoError(err, "Failed to delete the pod: %q", pod.Name)
   190  
   191  			ginkgo.By(fmt.Sprintf("Waiting for the pod (%v/%v) to be transitioned to the succeeded phase", pod.Namespace, pod.Name))
   192  			err = e2epod.WaitForPodSuccessInNamespace(ctx, f.ClientSet, pod.Name, f.Namespace.Name)
   193  			framework.ExpectNoError(err, "Failed to await for the pod to be succeeded: %q", pod.Name)
   194  
   195  			ginkgo.By(fmt.Sprintf("Fetching the end state of the pod (%v/%v)", pod.Namespace, pod.Name))
   196  			pod, err = e2epod.NewPodClient(f).Get(ctx, pod.Name, metav1.GetOptions{})
   197  			framework.ExpectNoError(err, "Failed to fetch the end state of the pod: %q", pod.Name)
   198  
   199  			ginkgo.By(fmt.Sprintf("Verify the pod (%v/%v) container is in the terminated state", pod.Namespace, pod.Name))
   200  			gomega.Expect(pod.Status.ContainerStatuses).Should(gomega.HaveLen(1))
   201  			containerStatus := pod.Status.ContainerStatuses[0]
   202  			gomega.Expect(containerStatus.State.Terminated).ShouldNot(gomega.BeNil(), "The pod container is in not in the Terminated state")
   203  
   204  			ginkgo.By(fmt.Sprintf("Verifying the exit code for the terminated container is 0 for pod (%v/%v)", pod.Namespace, pod.Name))
   205  			gomega.Expect(containerStatus.State.Terminated.ExitCode).Should(gomega.Equal(int32(0)))
   206  		},
   207  		ginkgo.Entry("Restart policy Always", v1.RestartPolicyAlways),
   208  		ginkgo.Entry("Restart policy OnFailure", v1.RestartPolicyOnFailure),
   209  		ginkgo.Entry("Restart policy Never", v1.RestartPolicyNever),
   210  	)
   211  
   212  })