Merge pull request #127221 from toVersus/test/restartable-init-termination

[Sidecar Containers] Expand test coverage for Node E2E tests on pod termination behavior
This commit is contained in:
Kubernetes Prow Robot 2024-10-10 02:48:23 +01:00 committed by GitHub
commit 582dcc2aca
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -50,6 +50,28 @@ func prefixedName(namePrefix string, name string) string {
return fmt.Sprintf("%s-%s", namePrefix, name) 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() { var _ = SIGDescribe(framework.WithNodeConformance(), "Containers Lifecycle", func() {
f := framework.NewDefaultFramework("containers-lifecycle-test") f := framework.NewDefaultFramework("containers-lifecycle-test")
addAfterEachForCleaningUpPods(f) addAfterEachForCleaningUpPods(f)
@ -3115,6 +3137,12 @@ var _ = SIGDescribe(nodefeature.SidecarContainers, "Containers Lifecycle", func(
pod, err = client.Get(context.TODO(), pod.Name, metav1.GetOptions{}) pod, err = client.Get(context.TODO(), pod.Name, metav1.GetOptions{})
framework.ExpectNoError(err) 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) results := parseOutput(context.TODO(), f, pod)
ginkgo.By("Analyzing results") 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{}) pod, err = client.Get(context.TODO(), pod.Name, metav1.GetOptions{})
framework.ExpectNoError(err) 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) results := parseOutput(context.TODO(), f, pod)
ginkgo.By("Analyzing results") 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{}) pod, err = client.Get(context.TODO(), pod.Name, metav1.GetOptions{})
framework.ExpectNoError(err) 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) results := parseOutput(context.TODO(), f, pod)
ginkgo.By("Analyzing results") 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") 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"},
})
})
})
}) })
}) })