diff --git a/test/e2e_node/container_lifecycle_test.go b/test/e2e_node/container_lifecycle_test.go index 6e52bd4f57b..0c6150f9c61 100644 --- a/test/e2e_node/container_lifecycle_test.go +++ b/test/e2e_node/container_lifecycle_test.go @@ -50,6 +50,28 @@ func prefixedName(namePrefix string, name string) string { return fmt.Sprintf("%s-%s", namePrefix, name) } +type podTerminationContainerStatus struct { + exitCode int32 + reason string +} + +func expectPodTerminationContainerStatuses(statuses []v1.ContainerStatus, to map[string]podTerminationContainerStatus) { + ginkgo.GinkgoHelper() + + if len(statuses) != len(to) { + ginkgo.Fail(fmt.Sprintf("mismatched lengths in pod termination container statuses. got %d, expected %d", len(statuses), len(to))) + } + for _, status := range statuses { + expected, ok := to[status.Name] + if !ok { + ginkgo.Fail(fmt.Sprintf("container %q not found in expected pod termination container statuses", status.Name)) + } + gomega.Expect(status.State.Terminated).NotTo(gomega.BeNil()) + gomega.Expect(status.State.Terminated.ExitCode).To(gomega.Equal(expected.exitCode)) + gomega.Expect(status.State.Terminated.Reason).To(gomega.Equal(expected.reason)) + } +} + var _ = SIGDescribe(framework.WithNodeConformance(), "Containers Lifecycle", func() { f := framework.NewDefaultFramework("containers-lifecycle-test") addAfterEachForCleaningUpPods(f) @@ -3115,6 +3137,12 @@ var _ = SIGDescribe(nodefeature.SidecarContainers, "Containers Lifecycle", func( pod, err = client.Get(context.TODO(), pod.Name, metav1.GetOptions{}) framework.ExpectNoError(err) + expectPodTerminationContainerStatuses(pod.Status.InitContainerStatuses, map[string]podTerminationContainerStatus{ + restartableInit1: {exitCode: int32(0), reason: "Completed"}, + restartableInit2: {exitCode: int32(0), reason: "Completed"}, + restartableInit3: {exitCode: int32(0), reason: "Completed"}, + }) + results := parseOutput(context.TODO(), f, pod) ginkgo.By("Analyzing results") @@ -3221,6 +3249,13 @@ var _ = SIGDescribe(nodefeature.SidecarContainers, "Containers Lifecycle", func( pod, err = client.Get(context.TODO(), pod.Name, metav1.GetOptions{}) framework.ExpectNoError(err) + // all restartable init containers are sigkilled with exit code 137 + expectPodTerminationContainerStatuses(pod.Status.InitContainerStatuses, map[string]podTerminationContainerStatus{ + restartableInit1: {exitCode: int32(137), reason: "Error"}, + restartableInit2: {exitCode: int32(137), reason: "Error"}, + restartableInit3: {exitCode: int32(137), reason: "Error"}, + }) + results := parseOutput(context.TODO(), f, pod) ginkgo.By("Analyzing results") @@ -3352,6 +3387,12 @@ var _ = SIGDescribe(nodefeature.SidecarContainers, "Containers Lifecycle", func( pod, err = client.Get(context.TODO(), pod.Name, metav1.GetOptions{}) framework.ExpectNoError(err) + expectPodTerminationContainerStatuses(pod.Status.InitContainerStatuses, map[string]podTerminationContainerStatus{ + restartableInit1: {exitCode: int32(0), reason: "Completed"}, + restartableInit2: {exitCode: int32(0), reason: "Completed"}, + restartableInit3: {exitCode: int32(0), reason: "Completed"}, + }) + results := parseOutput(context.TODO(), f, pod) ginkgo.By("Analyzing results") @@ -3610,6 +3651,654 @@ var _ = SIGDescribe(nodefeature.SidecarContainers, "Containers Lifecycle", func( framework.ExpectNoError(err, "the pod should be deleted before its terminationGracePeriodSeconds if the restartalbe init containers get termination signal correctly") }) }) + + ginkgo.When("The restartable init containers exit with non-zero exit code", func() { + ginkgo.It("should mark pod as succeeded if any of the restartable init containers have terminated with non-zero exit code", func(ctx context.Context) { + restartableInit1 := "restartable-init-1" + restartableInit2 := "restartable-init-2" + restartableInit3 := "restartable-init-3" + regular1 := "regular-1" + + podTerminationGracePeriodSeconds := int64(30) + + pod := &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "restartable-init-terminated-with-non-zero-exit-code", + Finalizers: []string{testFinalizer}, + }, + Spec: v1.PodSpec{ + RestartPolicy: v1.RestartPolicyNever, + TerminationGracePeriodSeconds: ptr.To(podTerminationGracePeriodSeconds), + InitContainers: []v1.Container{ + { + Name: restartableInit1, + Image: busyboxImage, + RestartPolicy: &containerRestartPolicyAlways, + Command: ExecCommand(restartableInit1, execCommand{ + Delay: 600, + TerminationSeconds: 1, + ExitCode: 0, + }), + }, + { + Name: restartableInit2, + Image: busyboxImage, + RestartPolicy: &containerRestartPolicyAlways, + Command: ExecCommand(restartableInit2, execCommand{ + Delay: 600, + TerminationSeconds: 1, + ExitCode: 1, + }), + }, + { + Name: restartableInit3, + Image: busyboxImage, + RestartPolicy: &containerRestartPolicyAlways, + Command: ExecCommand(restartableInit3, execCommand{ + Delay: 600, + TerminationSeconds: 1, + ExitCode: 0, + }), + }, + }, + Containers: []v1.Container{ + { + Name: regular1, + Image: busyboxImage, + Command: ExecCommand(regular1, execCommand{ + Delay: 600, + TerminationSeconds: 1, + ExitCode: 0, + }), + }, + }, + }, + } + + preparePod(pod) + + ginkgo.By("Creating the pod with finalizer") + client := e2epod.NewPodClient(f) + pod = client.Create(ctx, pod) + defer client.RemoveFinalizer(ctx, pod.Name, testFinalizer) + + ginkgo.By("Waiting for the pod to be initialized and run") + err := e2epod.WaitForPodRunningInNamespace(ctx, f.ClientSet, pod) + framework.ExpectNoError(err) + + ginkgo.By("Deleting the pod") + err = client.Delete(ctx, pod.Name, metav1.DeleteOptions{GracePeriodSeconds: &podTerminationGracePeriodSeconds}) + framework.ExpectNoError(err) + + ginkgo.By(fmt.Sprintf("Waiting for the pod (%s/%s) to be transitioned into the Succeeded phase", pod.Namespace, pod.Name)) + err = e2epod.WaitForPodSuccessInNamespace(ctx, f.ClientSet, pod.Name, f.Namespace.Name) + framework.ExpectNoError(err, "Failed to await for the pod to be terminated: %q", pod.Name) + + ginkgo.By(fmt.Sprintf("Fetch the end state of the pod (%s/%s)", pod.Namespace, pod.Name)) + pod, err = client.Get(ctx, pod.Name, metav1.GetOptions{}) + framework.ExpectNoError(err, "Failed to fetch the end state of the pod: %q", pod.Name) + + // regular container is gracefully terminated + expectPodTerminationContainerStatuses(pod.Status.ContainerStatuses, map[string]podTerminationContainerStatus{ + regular1: {exitCode: int32(0), reason: "Completed"}, + }) + + // restartable-init-2 that terminated with non-zero exit code is marked as error + expectPodTerminationContainerStatuses(pod.Status.InitContainerStatuses, map[string]podTerminationContainerStatus{ + restartableInit1: {exitCode: int32(0), reason: "Completed"}, + restartableInit2: {exitCode: int32(1), reason: "Error"}, + restartableInit3: {exitCode: int32(0), reason: "Completed"}, + }) + }) + }) + + ginkgo.When("The restartable init containers exit with non-zero exit code by prestop hook", func() { + ginkgo.It("should mark pod as succeeded if any of the restartable init containers have terminated with non-zero exit code by prestop hook", func(ctx context.Context) { + restartableInit1 := "restartable-init-1" + restartableInit2 := "restartable-init-2" + restartableInit3 := "restartable-init-3" + regular1 := "regular-1" + + podTerminationGracePeriodSeconds := int64(30) + + makePrestop := func(containerName string, exitCode int) *v1.Lifecycle { + return &v1.Lifecycle{ + PreStop: &v1.LifecycleHandler{ + Exec: &v1.ExecAction{ + Command: ExecCommand(containerName, execCommand{ + Delay: 1, + ExitCode: exitCode, + }), + }, + }, + } + } + + pod := &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "restartable-init-terminated-with-non-zero-exit-code", + Finalizers: []string{testFinalizer}, + }, + Spec: v1.PodSpec{ + RestartPolicy: v1.RestartPolicyNever, + TerminationGracePeriodSeconds: ptr.To(podTerminationGracePeriodSeconds), + InitContainers: []v1.Container{ + { + Name: restartableInit1, + Image: busyboxImage, + RestartPolicy: &containerRestartPolicyAlways, + Command: ExecCommand(restartableInit1, execCommand{ + Delay: 600, + TerminationSeconds: 1, + ExitCode: 0, + }), + Lifecycle: makePrestop(restartableInit1, 0), + }, + { + Name: restartableInit2, + Image: busyboxImage, + RestartPolicy: &containerRestartPolicyAlways, + Command: ExecCommand(restartableInit2, execCommand{ + Delay: 600, + TerminationSeconds: 1, + ExitCode: 0, + }), + Lifecycle: makePrestop(restartableInit2, 1), + }, + { + Name: restartableInit3, + Image: busyboxImage, + RestartPolicy: &containerRestartPolicyAlways, + Command: ExecCommand(restartableInit3, execCommand{ + Delay: 600, + TerminationSeconds: 1, + ExitCode: 0, + }), + Lifecycle: makePrestop(restartableInit3, 0), + }, + }, + Containers: []v1.Container{ + { + Name: regular1, + Image: busyboxImage, + Command: ExecCommand(regular1, execCommand{ + Delay: 600, + TerminationSeconds: 1, + ExitCode: 0, + }), + }, + }, + }, + } + + preparePod(pod) + + ginkgo.By("Creating the pod with finalizer") + client := e2epod.NewPodClient(f) + pod = client.Create(ctx, pod) + defer client.RemoveFinalizer(ctx, pod.Name, testFinalizer) + + ginkgo.By("Waiting for the pod to be initialized and run") + err := e2epod.WaitForPodRunningInNamespace(ctx, f.ClientSet, pod) + framework.ExpectNoError(err) + + ginkgo.By("Deleting the pod") + err = client.Delete(ctx, pod.Name, metav1.DeleteOptions{GracePeriodSeconds: &podTerminationGracePeriodSeconds}) + framework.ExpectNoError(err) + + ginkgo.By(fmt.Sprintf("Waiting for the pod (%s/%s) to be transitioned into the Succeeded phase", pod.Namespace, pod.Name)) + err = e2epod.WaitForPodSuccessInNamespace(ctx, f.ClientSet, pod.Name, f.Namespace.Name) + framework.ExpectNoError(err, "Failed to await for the pod to be terminated: %q", pod.Name) + + ginkgo.By(fmt.Sprintf("Fetch the end state of the pod (%s/%s)", pod.Namespace, pod.Name)) + pod, err = client.Get(ctx, pod.Name, metav1.GetOptions{}) + framework.ExpectNoError(err, "Failed to fetch the end state of the pod: %q", pod.Name) + + // regular container is gracefully terminated + expectPodTerminationContainerStatuses(pod.Status.ContainerStatuses, map[string]podTerminationContainerStatus{ + regular1: {exitCode: int32(0), reason: "Completed"}, + }) + + // restartable init containers are marked as completed if their prestop hooks are failed + expectPodTerminationContainerStatuses(pod.Status.InitContainerStatuses, map[string]podTerminationContainerStatus{ + restartableInit1: {exitCode: int32(0), reason: "Completed"}, + restartableInit2: {exitCode: int32(0), reason: "Completed"}, + restartableInit3: {exitCode: int32(0), reason: "Completed"}, + }) + }) + }) + + ginkgo.When("The regular container has exceeded its termination grace period seconds", func() { + ginkgo.It("should mark pod as failed if regular container has exceeded its termination grace period seconds", func(ctx context.Context) { + restartableInit1 := "restartable-init-1" + restartableInit2 := "restartable-init-2" + restartableInit3 := "restartable-init-3" + regular1 := "regular-1" + + podTerminationGracePeriodSeconds := int64(5) + + pod := &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "regular-exceeded-termination-grace-period", + Finalizers: []string{testFinalizer}, + }, + Spec: v1.PodSpec{ + RestartPolicy: v1.RestartPolicyNever, + TerminationGracePeriodSeconds: ptr.To(podTerminationGracePeriodSeconds), + InitContainers: []v1.Container{ + { + Name: restartableInit1, + Image: busyboxImage, + RestartPolicy: &containerRestartPolicyAlways, + Command: ExecCommand(restartableInit1, execCommand{ + Delay: 600, + TerminationSeconds: 20, + ExitCode: 0, + }), + }, + { + Name: restartableInit2, + Image: busyboxImage, + RestartPolicy: &containerRestartPolicyAlways, + Command: ExecCommand(restartableInit2, execCommand{ + Delay: 600, + // SIGKILL won't be sent because it only gets triggered 2 seconds after SIGTERM. + TerminationSeconds: 1, + ExitCode: 0, + }), + }, + { + Name: restartableInit3, + Image: busyboxImage, + RestartPolicy: &containerRestartPolicyAlways, + Command: ExecCommand(restartableInit3, execCommand{ + Delay: 600, + TerminationSeconds: 20, + ExitCode: 0, + }), + }, + }, + Containers: []v1.Container{ + { + Name: regular1, + Image: busyboxImage, + Command: ExecCommand(regular1, execCommand{ + Delay: 600, + TerminationSeconds: 20, + ExitCode: 0, + }), + }, + }, + }, + } + + preparePod(pod) + + ginkgo.By("Creating the pod with finalizer") + client := e2epod.NewPodClient(f) + pod = client.Create(ctx, pod) + defer client.RemoveFinalizer(ctx, pod.Name, testFinalizer) + + ginkgo.By("Waiting for the pod to be initialized and run") + err := e2epod.WaitForPodRunningInNamespace(ctx, f.ClientSet, pod) + framework.ExpectNoError(err) + + ginkgo.By("Deleting the pod") + err = client.Delete(ctx, pod.Name, metav1.DeleteOptions{GracePeriodSeconds: &podTerminationGracePeriodSeconds}) + framework.ExpectNoError(err) + + ginkgo.By(fmt.Sprintf("Waiting for the pod (%s/%s) to be transitioned into the Failed phase", pod.Namespace, pod.Name)) + err = e2epod.WaitForPodTerminatedInNamespace(ctx, f.ClientSet, pod.Name, "", f.Namespace.Name) + framework.ExpectNoError(err, "Failed to await for the pod to be terminated: %q", pod.Name) + + ginkgo.By(fmt.Sprintf("Fetch the end state of the pod (%s/%s)", pod.Namespace, pod.Name)) + pod, err = client.Get(ctx, pod.Name, metav1.GetOptions{}) + framework.ExpectNoError(err, "Failed to fetch the end state of the pod: %q", pod.Name) + + // regular container that exceeds its termination grace period seconds is sigkilled with exit code 137 + expectPodTerminationContainerStatuses(pod.Status.ContainerStatuses, map[string]podTerminationContainerStatus{ + regular1: {exitCode: int32(137), reason: "Error"}, + }) + + // restartable-init-2 is gracefully terminated within 2 seconds after receiving SIGTERM. + // The other containers that exceed 2 seconds after receiving SIGTERM are sigkilled with exit code 137. + expectPodTerminationContainerStatuses(pod.Status.InitContainerStatuses, map[string]podTerminationContainerStatus{ + restartableInit1: {exitCode: int32(137), reason: "Error"}, + restartableInit2: {exitCode: int32(0), reason: "Completed"}, + restartableInit3: {exitCode: int32(137), reason: "Error"}, + }) + }) + }) + + ginkgo.When("The restartable init containers have exceeded its termination grace period seconds", func() { + ginkgo.It("should mark pod as succeeded if any of the restartable init containers have exceeded its termination grace period seconds", func(ctx context.Context) { + restartableInit1 := "restartable-init-1" + restartableInit2 := "restartable-init-2" + restartableInit3 := "restartable-init-3" + regular1 := "regular-1" + + podTerminationGracePeriodSeconds := int64(5) + + pod := &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "restartable-init-exceeded-termination-grace-period", + Finalizers: []string{testFinalizer}, + }, + Spec: v1.PodSpec{ + RestartPolicy: v1.RestartPolicyNever, + TerminationGracePeriodSeconds: ptr.To(podTerminationGracePeriodSeconds), + InitContainers: []v1.Container{ + { + Name: restartableInit1, + Image: busyboxImage, + RestartPolicy: &containerRestartPolicyAlways, + Command: ExecCommand(restartableInit1, execCommand{ + Delay: 600, + TerminationSeconds: 1, + ExitCode: 0, + }), + }, + { + Name: restartableInit2, + Image: busyboxImage, + RestartPolicy: &containerRestartPolicyAlways, + Command: ExecCommand(restartableInit2, execCommand{ + Delay: 600, + TerminationSeconds: 20, + ExitCode: 0, + }), + }, + { + Name: restartableInit3, + Image: busyboxImage, + RestartPolicy: &containerRestartPolicyAlways, + Command: ExecCommand(restartableInit3, execCommand{ + Delay: 600, + TerminationSeconds: 1, + ExitCode: 0, + }), + }, + }, + Containers: []v1.Container{ + { + Name: regular1, + Image: busyboxImage, + Command: ExecCommand(regular1, execCommand{ + Delay: 600, + TerminationSeconds: 1, + ExitCode: 0, + }), + }, + }, + }, + } + + preparePod(pod) + + ginkgo.By("Creating the pod with finalizer") + client := e2epod.NewPodClient(f) + pod = client.Create(ctx, pod) + defer client.RemoveFinalizer(ctx, pod.Name, testFinalizer) + + ginkgo.By("Waiting for the pod to be initialized and run") + err := e2epod.WaitForPodRunningInNamespace(ctx, f.ClientSet, pod) + framework.ExpectNoError(err) + + ginkgo.By("Deleting the pod") + err = client.Delete(ctx, pod.Name, metav1.DeleteOptions{GracePeriodSeconds: &podTerminationGracePeriodSeconds}) + framework.ExpectNoError(err) + + ginkgo.By(fmt.Sprintf("Waiting for the pod (%s/%s) to be transitioned into the Succeeded phase", pod.Namespace, pod.Name)) + err = e2epod.WaitForPodSuccessInNamespace(ctx, f.ClientSet, pod.Name, f.Namespace.Name) + framework.ExpectNoError(err, "Failed to await for the pod to be terminated: %q", pod.Name) + + ginkgo.By(fmt.Sprintf("Fetch the end state of the pod (%s/%s)", pod.Namespace, pod.Name)) + pod, err = client.Get(ctx, pod.Name, metav1.GetOptions{}) + framework.ExpectNoError(err, "Failed to fetch the end state of the pod: %q", pod.Name) + + // regular container is gracefully terminated + expectPodTerminationContainerStatuses(pod.Status.ContainerStatuses, map[string]podTerminationContainerStatus{ + regular1: {exitCode: int32(0), reason: "Completed"}, + }) + + // restartable-init-2 that exceeds its termination grace period seconds is sigkilled with exit code 137. + // The other containers are gracefully terminated within 2 seconds after receiving SIGTERM + expectPodTerminationContainerStatuses(pod.Status.InitContainerStatuses, map[string]podTerminationContainerStatus{ + restartableInit1: {exitCode: int32(0), reason: "Completed"}, + restartableInit2: {exitCode: int32(137), reason: "Error"}, + restartableInit3: {exitCode: int32(0), reason: "Completed"}, + }) + }) + }) + + ginkgo.When("The regular containers have exceeded its termination grace period seconds by prestop hook", func() { + ginkgo.It("should mark pod as failed if any of the prestop hook in regular container has exceeded its termination grace period seconds", func(ctx context.Context) { + restartableInit1 := "restartable-init-1" + restartableInit2 := "restartable-init-2" + restartableInit3 := "restartable-init-3" + regular1 := "regular-1" + + podTerminationGracePeriodSeconds := int64(5) + + pod := &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "regular-prestop-exceeded-termination-grace-period", + Finalizers: []string{testFinalizer}, + }, + Spec: v1.PodSpec{ + RestartPolicy: v1.RestartPolicyNever, + TerminationGracePeriodSeconds: ptr.To(podTerminationGracePeriodSeconds), + InitContainers: []v1.Container{ + { + Name: restartableInit1, + Image: busyboxImage, + RestartPolicy: &containerRestartPolicyAlways, + Command: ExecCommand(restartableInit1, execCommand{ + Delay: 600, + TerminationSeconds: 1, + ExitCode: 0, + }), + }, + { + Name: restartableInit2, + Image: busyboxImage, + RestartPolicy: &containerRestartPolicyAlways, + Command: ExecCommand(restartableInit2, execCommand{ + Delay: 600, + TerminationSeconds: 20, + ExitCode: 0, + }), + }, + { + Name: restartableInit3, + Image: busyboxImage, + RestartPolicy: &containerRestartPolicyAlways, + Command: ExecCommand(restartableInit3, execCommand{ + Delay: 600, + TerminationSeconds: 1, + ExitCode: 0, + }), + }, + }, + Containers: []v1.Container{ + { + Name: regular1, + Image: busyboxImage, + Command: ExecCommand(regular1, execCommand{ + Delay: 600, + TerminationSeconds: 20, + ExitCode: 0, + }), + Lifecycle: &v1.Lifecycle{ + PreStop: &v1.LifecycleHandler{ + Exec: &v1.ExecAction{ + Command: ExecCommand(regular1, execCommand{ + Delay: 20, + ExitCode: 0, + }), + }, + }, + }, + }, + }, + }, + } + + preparePod(pod) + + ginkgo.By("Creating the pod with finalizer") + client := e2epod.NewPodClient(f) + pod = client.Create(ctx, pod) + defer client.RemoveFinalizer(ctx, pod.Name, testFinalizer) + + ginkgo.By("Waiting for the pod to be initialized and run") + err := e2epod.WaitForPodRunningInNamespace(ctx, f.ClientSet, pod) + framework.ExpectNoError(err) + + ginkgo.By("Deleting the pod") + err = client.Delete(ctx, pod.Name, metav1.DeleteOptions{GracePeriodSeconds: &podTerminationGracePeriodSeconds}) + framework.ExpectNoError(err) + + ginkgo.By(fmt.Sprintf("Waiting for the pod (%s/%s) to be transitioned into the Failed phase", pod.Namespace, pod.Name)) + err = e2epod.WaitForPodTerminatedInNamespace(ctx, f.ClientSet, pod.Name, "", f.Namespace.Name) + framework.ExpectNoError(err, "Failed to await for the pod to be terminated: %q", pod.Name) + + ginkgo.By(fmt.Sprintf("Fetch the end state of the pod (%s/%s)", pod.Namespace, pod.Name)) + pod, err = client.Get(ctx, pod.Name, metav1.GetOptions{}) + framework.ExpectNoError(err, "Failed to fetch the end state of the pod: %q", pod.Name) + + // regular container that exceeds its termination grace period seconds is sigkilled with exit code 137 + expectPodTerminationContainerStatuses(pod.Status.ContainerStatuses, map[string]podTerminationContainerStatus{ + regular1: {exitCode: int32(137), reason: "Error"}, + }) + + // restartable-init-2 that exceed 2 seconds after receiving SIGTERM is sigkilled with exit code 137. + // The other containers are gracefully terminated within 2 seconds after receiving SIGTERM + expectPodTerminationContainerStatuses(pod.Status.InitContainerStatuses, map[string]podTerminationContainerStatus{ + restartableInit1: {exitCode: int32(0), reason: "Completed"}, + restartableInit2: {exitCode: int32(137), reason: "Error"}, + restartableInit3: {exitCode: int32(0), reason: "Completed"}, + }) + }) + }) + + ginkgo.When("The restartable init containers have exceeded its termination grace period seconds by prestop hook", func() { + ginkgo.It("should mark pod as succeeded if any of the prestop hook in restartable init containers have exceeded its termination grace period seconds", func(ctx context.Context) { + restartableInit1 := "restartable-init-1" + restartableInit2 := "restartable-init-2" + restartableInit3 := "restartable-init-3" + regular1 := "regular-1" + + podTerminationGracePeriodSeconds := int64(5) + + makePrestop := func(containerName string, delay int) *v1.Lifecycle { + return &v1.Lifecycle{ + PreStop: &v1.LifecycleHandler{ + Exec: &v1.ExecAction{ + Command: ExecCommand(containerName, execCommand{ + Delay: delay, + ExitCode: 0, + }), + }, + }, + } + } + + pod := &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "restartable-init-prestop-exceeded-termination-grace-period", + Finalizers: []string{testFinalizer}, + }, + Spec: v1.PodSpec{ + RestartPolicy: v1.RestartPolicyNever, + TerminationGracePeriodSeconds: ptr.To(podTerminationGracePeriodSeconds), + InitContainers: []v1.Container{ + { + Name: restartableInit1, + Image: busyboxImage, + RestartPolicy: &containerRestartPolicyAlways, + Command: ExecCommand(restartableInit1, execCommand{ + Delay: 600, + TerminationSeconds: 1, + ExitCode: 0, + }), + Lifecycle: makePrestop(restartableInit1, 1), + }, + { + Name: restartableInit2, + Image: busyboxImage, + RestartPolicy: &containerRestartPolicyAlways, + Command: ExecCommand(restartableInit2, execCommand{ + Delay: 600, + TerminationSeconds: 20, + ExitCode: 0, + }), + Lifecycle: makePrestop(restartableInit1, 30), + }, + { + Name: restartableInit3, + Image: busyboxImage, + RestartPolicy: &containerRestartPolicyAlways, + Command: ExecCommand(restartableInit3, execCommand{ + Delay: 600, + TerminationSeconds: 1, + ExitCode: 0, + }), + Lifecycle: makePrestop(restartableInit1, 1), + }, + }, + Containers: []v1.Container{ + { + Name: regular1, + Image: busyboxImage, + Command: ExecCommand(regular1, execCommand{ + Delay: 600, + TerminationSeconds: 1, + ExitCode: 0, + }), + }, + }, + }, + } + + preparePod(pod) + + ginkgo.By("Creating the pod with finalizer") + client := e2epod.NewPodClient(f) + pod = client.Create(ctx, pod) + defer client.RemoveFinalizer(ctx, pod.Name, testFinalizer) + + ginkgo.By("Waiting for the pod to be initialized and run") + err := e2epod.WaitForPodRunningInNamespace(ctx, f.ClientSet, pod) + framework.ExpectNoError(err) + + ginkgo.By("Deleting the pod") + err = client.Delete(ctx, pod.Name, metav1.DeleteOptions{GracePeriodSeconds: &podTerminationGracePeriodSeconds}) + framework.ExpectNoError(err) + + ginkgo.By(fmt.Sprintf("Waiting for the pod (%s/%s) to be transitioned into the Succeeded phase", pod.Namespace, pod.Name)) + err = e2epod.WaitForPodSuccessInNamespace(ctx, f.ClientSet, pod.Name, f.Namespace.Name) + framework.ExpectNoError(err, "Failed to await for the pod to be terminated: %q", pod.Name) + + ginkgo.By(fmt.Sprintf("Fetch the end state of the pod (%s/%s)", pod.Namespace, pod.Name)) + pod, err = client.Get(ctx, pod.Name, metav1.GetOptions{}) + framework.ExpectNoError(err, "Failed to fetch the end state of the pod: %q", pod.Name) + + // regular container is gracefully terminated + expectPodTerminationContainerStatuses(pod.Status.ContainerStatuses, map[string]podTerminationContainerStatus{ + regular1: {exitCode: int32(0), reason: "Completed"}, + }) + + // restartable-init-2 that exceed its termination grace period seconds by prestop hook is sigkilled + // with exit code 137. + // The other containers are gracefully terminated within their termination grace period seconds + expectPodTerminationContainerStatuses(pod.Status.InitContainerStatuses, map[string]podTerminationContainerStatus{ + restartableInit1: {exitCode: int32(0), reason: "Completed"}, + restartableInit2: {exitCode: int32(137), reason: "Error"}, + restartableInit3: {exitCode: int32(0), reason: "Completed"}, + }) + }) + }) }) })