k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/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  
    24  	v1 "k8s.io/api/core/v1"
    25  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    26  	"k8s.io/apimachinery/pkg/util/uuid"
    27  	"k8s.io/kubernetes/test/e2e/framework"
    28  	e2epod "k8s.io/kubernetes/test/e2e/framework/pod"
    29  	imageutils "k8s.io/kubernetes/test/utils/image"
    30  	admissionapi "k8s.io/pod-security-admission/api"
    31  
    32  	"github.com/onsi/ginkgo/v2"
    33  	"github.com/onsi/gomega"
    34  )
    35  
    36  const (
    37  	testFinalizer = "example.com/test-finalizer"
    38  )
    39  
    40  var _ = SIGDescribe("Deleted pods handling", framework.WithNodeConformance(), func() {
    41  	f := framework.NewDefaultFramework("deleted-pods-test")
    42  	f.NamespacePodSecurityLevel = admissionapi.LevelBaseline
    43  
    44  	ginkgo.It("Should transition to Failed phase a pod which is deleted while pending", func(ctx context.Context) {
    45  		podName := "deleted-pending-" + string(uuid.NewUUID())
    46  		podSpec := &v1.Pod{
    47  			ObjectMeta: metav1.ObjectMeta{
    48  				Name:       podName,
    49  				Finalizers: []string{testFinalizer},
    50  			},
    51  			Spec: v1.PodSpec{
    52  				RestartPolicy: v1.RestartPolicyAlways,
    53  				Containers: []v1.Container{
    54  					{
    55  						Name:            podName,
    56  						Image:           "non-existing-repo/non-existing-image:v1.0",
    57  						ImagePullPolicy: "Always",
    58  						Command:         []string{"bash"},
    59  						Args:            []string{"-c", `echo "Hello world"`},
    60  					},
    61  				},
    62  			},
    63  		}
    64  		ginkgo.By("creating the pod with invalid image reference and finalizer")
    65  		pod := e2epod.NewPodClient(f).Create(ctx, podSpec)
    66  
    67  		ginkgo.By("set up cleanup of the finalizer")
    68  		ginkgo.DeferCleanup(e2epod.NewPodClient(f).RemoveFinalizer, pod.Name, testFinalizer)
    69  
    70  		ginkgo.By("Waiting for the pod to be scheduled so that kubelet owns it")
    71  		err := e2epod.WaitForPodScheduled(ctx, f.ClientSet, pod.Namespace, pod.Name)
    72  		framework.ExpectNoError(err, "Failed to await for the pod to be scheduled: %q", pod.Name)
    73  
    74  		ginkgo.By(fmt.Sprintf("Deleting the pod (%v/%v) to set a deletion timestamp", pod.Namespace, pod.Name))
    75  		err = e2epod.NewPodClient(f).Delete(ctx, pod.Name, metav1.DeleteOptions{})
    76  		framework.ExpectNoError(err, "Failed to delete the pod: %q", pod.Name)
    77  
    78  		ginkgo.By(fmt.Sprintf("Waiting for the pod (%v/%v) to be transitioned into the Failed phase", pod.Namespace, pod.Name))
    79  		err = e2epod.WaitForPodTerminatedInNamespace(ctx, f.ClientSet, pod.Name, "", f.Namespace.Name)
    80  		framework.ExpectNoError(err, "Failed to await for the pod to be terminated: %q", pod.Name)
    81  
    82  		ginkgo.By(fmt.Sprintf("Fetch the end state of the pod (%v/%v)", pod.Namespace, pod.Name))
    83  		pod, err = e2epod.NewPodClient(f).Get(ctx, pod.Name, metav1.GetOptions{})
    84  		framework.ExpectNoError(err, "Failed to fetch the end state of the pod: %q", pod.Name)
    85  	})
    86  
    87  	ginkgo.DescribeTable("Should transition to Failed phase a deleted pod if non-zero exit codes",
    88  		func(ctx context.Context, policy v1.RestartPolicy) {
    89  			podName := "deleted-running-" + strings.ToLower(string(policy)) + "-" + string(uuid.NewUUID())
    90  			podSpec := e2epod.MustMixinRestrictedPodSecurity(&v1.Pod{
    91  				ObjectMeta: metav1.ObjectMeta{
    92  					Name:       podName,
    93  					Finalizers: []string{testFinalizer},
    94  				},
    95  				Spec: v1.PodSpec{
    96  					RestartPolicy: policy,
    97  					Containers: []v1.Container{
    98  						{
    99  							Name:    podName,
   100  							Image:   imageutils.GetE2EImage(imageutils.BusyBox),
   101  							Command: []string{"sleep", "1800"},
   102  						},
   103  					},
   104  				},
   105  			})
   106  			ginkgo.By(fmt.Sprintf("Creating a pod (%v/%v) with restart policy: %v", f.Namespace.Name, podSpec.Name, podSpec.Spec.RestartPolicy))
   107  			pod := e2epod.NewPodClient(f).Create(ctx, podSpec)
   108  
   109  			ginkgo.By("set up cleanup of the finalizer")
   110  			ginkgo.DeferCleanup(e2epod.NewPodClient(f).RemoveFinalizer, pod.Name, testFinalizer)
   111  
   112  			ginkgo.By("Waiting for the pod to be running")
   113  			err := e2epod.WaitForPodNameRunningInNamespace(ctx, f.ClientSet, pod.Name, f.Namespace.Name)
   114  			framework.ExpectNoError(err, "Failed to await for the pod to be running: %q", pod.Name)
   115  
   116  			ginkgo.By(fmt.Sprintf("Deleting the pod (%v/%v) to set a deletion timestamp", pod.Namespace, pod.Name))
   117  			err = e2epod.NewPodClient(f).Delete(ctx, pod.Name, *metav1.NewDeleteOptions(1))
   118  			framework.ExpectNoError(err, "Failed to delete the pod: %q", pod.Name)
   119  
   120  			ginkgo.By(fmt.Sprintf("Waiting for the pod (%v/%v) to be transitioned to the failed phase", pod.Namespace, pod.Name))
   121  			err = e2epod.WaitForPodTerminatedInNamespace(ctx, f.ClientSet, pod.Name, "", f.Namespace.Name)
   122  			framework.ExpectNoError(err, "Failed to await for the pod to be terminated: %q", pod.Name)
   123  
   124  			ginkgo.By(fmt.Sprintf("Fetching the end state of the pod (%v/%v)", pod.Namespace, pod.Name))
   125  			pod, err = e2epod.NewPodClient(f).Get(ctx, pod.Name, metav1.GetOptions{})
   126  			framework.ExpectNoError(err, "Failed to fetch the end state of the pod: %q", pod.Name)
   127  
   128  			ginkgo.By(fmt.Sprintf("Verify the pod (%v/%v) container is in the terminated state", pod.Namespace, pod.Name))
   129  			gomega.Expect(pod.Status.ContainerStatuses).Should(gomega.HaveLen(1))
   130  			containerStatus := pod.Status.ContainerStatuses[0]
   131  			gomega.Expect(containerStatus.State.Terminated).ToNot(gomega.BeNil(), "The pod container is in not in the Terminated state")
   132  
   133  			ginkgo.By(fmt.Sprintf("Verify the pod (%v/%v) container exit code is 137", pod.Namespace, pod.Name))
   134  			gomega.Expect(containerStatus.State.Terminated.ExitCode).Should(gomega.Equal(int32(137)))
   135  		},
   136  		ginkgo.Entry("Restart policy Always", v1.RestartPolicyAlways),
   137  		ginkgo.Entry("Restart policy OnFailure", v1.RestartPolicyOnFailure),
   138  		ginkgo.Entry("Restart policy Never", v1.RestartPolicyNever),
   139  	)
   140  
   141  	ginkgo.DescribeTable("Should transition to Succeeded phase a deleted pod when containers complete with 0 exit code",
   142  		func(ctx context.Context, policy v1.RestartPolicy) {
   143  			podName := "deleted-running-" + strings.ToLower(string(policy)) + "-" + string(uuid.NewUUID())
   144  			podSpec := e2epod.MustMixinRestrictedPodSecurity(&v1.Pod{
   145  				ObjectMeta: metav1.ObjectMeta{
   146  					Name:       podName,
   147  					Finalizers: []string{testFinalizer},
   148  				},
   149  				Spec: v1.PodSpec{
   150  					RestartPolicy: policy,
   151  					Containers: []v1.Container{
   152  						{
   153  							Name:    podName,
   154  							Image:   imageutils.GetE2EImage(imageutils.BusyBox),
   155  							Command: []string{"sh", "-c"},
   156  							Args: []string{`
   157  							sleep 9999999 &
   158  							PID=$!
   159  							_term() {
   160  								kill $PID
   161  								echo "Caught SIGTERM signal!"
   162  							}
   163  
   164  							trap _term SIGTERM
   165  							touch /tmp/trap-marker
   166  							wait $PID
   167  
   168  							exit 0
   169  							`,
   170  							},
   171  							ReadinessProbe: &v1.Probe{
   172  								PeriodSeconds: 1,
   173  								ProbeHandler: v1.ProbeHandler{
   174  									Exec: &v1.ExecAction{
   175  										Command: []string{"/bin/sh", "-c", "cat /tmp/trap-marker"},
   176  									},
   177  								},
   178  							},
   179  						},
   180  					},
   181  				},
   182  			})
   183  			ginkgo.By(fmt.Sprintf("Creating a pod (%v/%v) with restart policy: %v", f.Namespace.Name, podSpec.Name, podSpec.Spec.RestartPolicy))
   184  			pod := e2epod.NewPodClient(f).Create(ctx, podSpec)
   185  
   186  			ginkgo.By("set up cleanup of the finalizer")
   187  			ginkgo.DeferCleanup(e2epod.NewPodClient(f).RemoveFinalizer, pod.Name, testFinalizer)
   188  
   189  			ginkgo.By(fmt.Sprintf("Waiting for the pod (%v/%v) to be running and with the SIGTERM trap registered", pod.Namespace, pod.Name))
   190  			err := e2epod.WaitTimeoutForPodReadyInNamespace(ctx, f.ClientSet, pod.Name, f.Namespace.Name, f.Timeouts.PodStart)
   191  			framework.ExpectNoError(err, "Failed to await for the pod to be running: %q", pod.Name)
   192  
   193  			ginkgo.By(fmt.Sprintf("Deleting the pod (%v/%v) to set a deletion timestamp", pod.Namespace, pod.Name))
   194  			err = e2epod.NewPodClient(f).Delete(ctx, pod.Name, metav1.DeleteOptions{})
   195  			framework.ExpectNoError(err, "Failed to delete the pod: %q", pod.Name)
   196  
   197  			ginkgo.By(fmt.Sprintf("Waiting for the pod (%v/%v) to be transitioned to the succeeded phase", pod.Namespace, pod.Name))
   198  			err = e2epod.WaitForPodSuccessInNamespace(ctx, f.ClientSet, pod.Name, f.Namespace.Name)
   199  			framework.ExpectNoError(err, "Failed to await for the pod to be succeeded: %q", pod.Name)
   200  
   201  			ginkgo.By(fmt.Sprintf("Fetching the end state of the pod (%v/%v)", pod.Namespace, pod.Name))
   202  			pod, err = e2epod.NewPodClient(f).Get(ctx, pod.Name, metav1.GetOptions{})
   203  			framework.ExpectNoError(err, "Failed to fetch the end state of the pod: %q", pod.Name)
   204  
   205  			ginkgo.By(fmt.Sprintf("Verify the pod (%v/%v) container is in the terminated state", pod.Namespace, pod.Name))
   206  			gomega.Expect(pod.Status.ContainerStatuses).Should(gomega.HaveLen(1))
   207  			containerStatus := pod.Status.ContainerStatuses[0]
   208  			gomega.Expect(containerStatus.State.Terminated).ShouldNot(gomega.BeNil(), "The pod container is in not in the Terminated state")
   209  
   210  			ginkgo.By(fmt.Sprintf("Verifying the exit code for the terminated container is 0 for pod (%v/%v)", pod.Namespace, pod.Name))
   211  			gomega.Expect(containerStatus.State.Terminated.ExitCode).Should(gomega.Equal(int32(0)))
   212  		},
   213  		ginkgo.Entry("Restart policy Always", v1.RestartPolicyAlways),
   214  		ginkgo.Entry("Restart policy OnFailure", v1.RestartPolicyOnFailure),
   215  		ginkgo.Entry("Restart policy Never", v1.RestartPolicyNever),
   216  	)
   217  
   218  })