k8s.io/kubernetes@v1.29.3/test/e2e_node/pods_lifecycle_termination_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  
    23  	v1 "k8s.io/api/core/v1"
    24  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    25  	"k8s.io/apimachinery/pkg/util/uuid"
    26  	"k8s.io/kubernetes/test/e2e/framework"
    27  	e2epod "k8s.io/kubernetes/test/e2e/framework/pod"
    28  	testutils "k8s.io/kubernetes/test/utils"
    29  	admissionapi "k8s.io/pod-security-admission/api"
    30  
    31  	"github.com/onsi/ginkgo/v2"
    32  	"github.com/onsi/gomega"
    33  )
    34  
    35  // Pod sigkill test will cover pods with graceful termination period set but failed
    36  // to terminate and forcefully killed by kubelet. This test examine pod's container's
    37  // exit code is 137 and the exit reason is `Error`
    38  var _ = SIGDescribe("Pod SIGKILL [LinuxOnly]", framework.WithNodeConformance(), func() {
    39  	f := framework.NewDefaultFramework("sigkill-test")
    40  	f.NamespacePodSecurityLevel = admissionapi.LevelBaseline
    41  
    42  	podName := "sigkill-pod-" + string(uuid.NewUUID())
    43  	containerName := "sigkill-target-container"
    44  	podSpec := getSigkillTargetPod(podName, containerName)
    45  	ginkgo.Context("", func() {
    46  		ginkgo.BeforeEach(func() {
    47  			ginkgo.By("setting up the pod to be used in the test")
    48  			e2epod.NewPodClient(f).Create(context.TODO(), podSpec)
    49  		})
    50  
    51  		ginkgo.It("The containers terminated forcefully by Sigkill should have the correct exit code(137) and reason (Error)", func() {
    52  
    53  			ginkgo.By(fmt.Sprintf("Waiting for the pod (%v/%v) to be running", f.Namespace.Name, podSpec.Name))
    54  			err := e2epod.WaitForPodNameRunningInNamespace(context.TODO(), f.ClientSet, podSpec.Name, f.Namespace.Name)
    55  			framework.ExpectNoError(err, "Failed to await for the pod to be running: %q", podSpec.Name)
    56  
    57  			// Checking pod's readiness to confirm the signal handler has registered successfully.
    58  			err = e2epod.WaitForPodCondition(context.TODO(), f.ClientSet, f.Namespace.Name, podSpec.Name, "Ready", f.Timeouts.PodStart, testutils.PodRunningReady)
    59  			framework.ExpectNoError(err, "Failed to await Pod (%v/%v) become ready after registering signal handler: %v", f.Namespace.Name, podSpec.Name, err)
    60  
    61  			ginkgo.By(fmt.Sprintf("Deleting the pod (%v/%v) to set a deletion timestamp", f.Namespace.Name, podSpec.Name))
    62  			err = e2epod.NewPodClient(f).Delete(context.TODO(), podSpec.Name, metav1.DeleteOptions{})
    63  			framework.ExpectNoError(err, "Failed to delete the pod: %q", podSpec.Name)
    64  
    65  			ginkgo.By(fmt.Sprintf("Waiting for the pod (%v/%v) to be transitioned to the terminated phase", f.Namespace.Name, podSpec.Name))
    66  			err = e2epod.WaitForPodTerminatedInNamespace(context.TODO(), f.ClientSet, podSpec.Name, "", f.Namespace.Name)
    67  			framework.ExpectNoError(err, "Failed to await for the pod to be terminated: %q", podSpec.Name)
    68  
    69  			ginkgo.By(fmt.Sprintf("Fetching the end state of the pod (%v/%v)", f.Namespace.Name, podSpec.Name))
    70  			pod, err := e2epod.NewPodClient(f).Get(context.TODO(), podSpec.Name, metav1.GetOptions{})
    71  			framework.ExpectNoError(err, "Failed to fetch the end state of the pod: %q", podSpec.Name)
    72  
    73  			ginkgo.By(fmt.Sprintf("Verify the pod (%v/%v) container is in the terminated state", pod.Namespace, podSpec.Name))
    74  			gomega.Expect(pod.Status.ContainerStatuses).Should(gomega.HaveLen(1), "The pod container has %v status", len(pod.Status.ContainerStatuses))
    75  			containerStatus := pod.Status.ContainerStatuses[0]
    76  			gomega.Expect(containerStatus.State.Terminated).ShouldNot(gomega.BeNil(), "The pod container is in not in the Terminated state")
    77  
    78  			ginkgo.By(fmt.Sprintf("Verifying the exit code for the terminated container is 137 for pod (%v/%v)", pod.Namespace, podSpec.Name))
    79  			gomega.Expect(containerStatus.State.Terminated.ExitCode).Should(gomega.Equal(int32(137)))
    80  
    81  			ginkgo.By(fmt.Sprintf("Verify exit reason of the pod (%v/%v) container", f.Namespace.Name, podSpec.Name))
    82  			gomega.Expect(containerStatus.State.Terminated.Reason).Should(gomega.Equal("Error"), "Container terminated by sigkill expect Error but got %v", containerStatus.State.Terminated.Reason)
    83  		})
    84  
    85  		ginkgo.AfterEach(func() {
    86  			ginkgo.By(fmt.Sprintf("Deleting pod by removing finalizers: %s", podSpec.Name))
    87  			e2epod.NewPodClient(f).RemoveFinalizer(context.TODO(), podSpec.Name, testFinalizer)
    88  
    89  			ginkgo.By(fmt.Sprintf("Confirm the pod was successfully deleted: %s", podSpec.Name))
    90  			e2epod.WaitForPodNotFoundInNamespace(context.TODO(), f.ClientSet, podSpec.Name, f.Namespace.Name, f.Timeouts.PodDelete)
    91  		})
    92  	})
    93  })
    94  
    95  func getSigkillTargetPod(podName string, ctnName string) *v1.Pod {
    96  	gracePeriod := int64(5)
    97  	return &v1.Pod{
    98  		ObjectMeta: metav1.ObjectMeta{
    99  			Name: podName,
   100  			// Using default test finalizer to keep exit status and code can be
   101  			// preserved after deleting the pod
   102  			Finalizers: []string{testFinalizer},
   103  		},
   104  		Spec: v1.PodSpec{
   105  			RestartPolicy: v1.RestartPolicyNever,
   106  			Containers: []v1.Container{
   107  				{
   108  					Name:  ctnName,
   109  					Image: busyboxImage,
   110  					// In the main container, SIGTERM was trapped and later /tmp/healthy
   111  					// will be created for readiness probe to verify if the trap was
   112  					// executed successfully
   113  					Command: []string{
   114  						"sh",
   115  						"-c",
   116  						"trap \"echo SIGTERM caught\" SIGTERM SIGINT; touch /tmp/healthy; /bin/sleep 1000",
   117  					},
   118  					// Using readiness probe to guarantee signal handler registering finished
   119  					ReadinessProbe: &v1.Probe{
   120  						InitialDelaySeconds: 1,
   121  						TimeoutSeconds:      2,
   122  						ProbeHandler: v1.ProbeHandler{
   123  							Exec: &v1.ExecAction{
   124  								Command: []string{"/bin/sh", "-c", "cat /tmp/healthy"},
   125  							},
   126  						},
   127  					},
   128  				},
   129  			},
   130  			TerminationGracePeriodSeconds: &gracePeriod,
   131  		},
   132  	}
   133  }