From 0a9870791289f9aa638fc0fc891b41d56068daf2 Mon Sep 17 00:00:00 2001 From: Gunju Kim Date: Wed, 10 May 2023 02:00:47 +0900 Subject: [PATCH] Sidecar: Update printPod to show restartable init container information --- pkg/printers/internalversion/printers.go | 52 ++++- pkg/printers/internalversion/printers_test.go | 196 ++++++++++++++++++ 2 files changed, 245 insertions(+), 3 deletions(-) diff --git a/pkg/printers/internalversion/printers.go b/pkg/printers/internalversion/printers.go index 3509a55f157..3d309dce3bb 100644 --- a/pkg/printers/internalversion/printers.go +++ b/pkg/printers/internalversion/printers.go @@ -815,9 +815,11 @@ func printPodList(podList *api.PodList, options printers.GenerateOptions) ([]met func printPod(pod *api.Pod, options printers.GenerateOptions) ([]metav1.TableRow, error) { restarts := 0 + restartableInitContainerRestarts := 0 totalContainers := len(pod.Spec.Containers) readyContainers := 0 lastRestartDate := metav1.NewTime(time.Time{}) + lastRestartableInitContainerRestartDate := metav1.NewTime(time.Time{}) reason := string(pod.Status.Phase) if pod.Status.Reason != "" { @@ -842,6 +844,14 @@ func printPod(pod *api.Pod, options printers.GenerateOptions) ([]metav1.TableRow row.Conditions = podFailedConditions } + initContainers := make(map[string]*api.Container) + for i := range pod.Spec.InitContainers { + initContainers[pod.Spec.InitContainers[i].Name] = &pod.Spec.InitContainers[i] + if isRestartableInitContainer(&pod.Spec.InitContainers[i]) { + totalContainers++ + } + } + initializing := false for i := range pod.Status.InitContainerStatuses { container := pod.Status.InitContainerStatuses[i] @@ -852,9 +862,24 @@ func printPod(pod *api.Pod, options printers.GenerateOptions) ([]metav1.TableRow lastRestartDate = terminatedDate } } + if isRestartableInitContainer(initContainers[container.Name]) { + restartableInitContainerRestarts += int(container.RestartCount) + if container.LastTerminationState.Terminated != nil { + terminatedDate := container.LastTerminationState.Terminated.FinishedAt + if lastRestartableInitContainerRestartDate.Before(&terminatedDate) { + lastRestartableInitContainerRestartDate = terminatedDate + } + } + } switch { case container.State.Terminated != nil && container.State.Terminated.ExitCode == 0: continue + case isRestartableInitContainer(initContainers[container.Name]) && + container.Started != nil && *container.Started: + if container.Ready { + readyContainers++ + } + continue case container.State.Terminated != nil: // initialization is failed if len(container.State.Terminated.Reason) == 0 { @@ -876,8 +901,10 @@ func printPod(pod *api.Pod, options printers.GenerateOptions) ([]metav1.TableRow } break } - if !initializing { - restarts = 0 + + if !initializing || isPodInitializedConditionTrue(&pod.Status) { + restarts = restartableInitContainerRestarts + lastRestartDate = lastRestartableInitContainerRestartDate hasRunning := false for i := len(pod.Status.ContainerStatuses) - 1; i >= 0; i-- { container := pod.Status.ContainerStatuses[i] @@ -922,7 +949,7 @@ func printPod(pod *api.Pod, options printers.GenerateOptions) ([]metav1.TableRow } restartsStr := strconv.Itoa(restarts) - if !lastRestartDate.IsZero() { + if restarts != 0 && !lastRestartDate.IsZero() { restartsStr = fmt.Sprintf("%d (%s ago)", restarts, translateTimestampSince(lastRestartDate)) } @@ -2996,3 +3023,22 @@ func (list SortableResourceNames) Swap(i, j int) { func (list SortableResourceNames) Less(i, j int) bool { return list[i] < list[j] } + +func isRestartableInitContainer(initContainer *api.Container) bool { + if initContainer.RestartPolicy == nil { + return false + } + + return *initContainer.RestartPolicy == api.ContainerRestartPolicyAlways +} + +func isPodInitializedConditionTrue(status *api.PodStatus) bool { + for _, condition := range status.Conditions { + if condition.Type != api.PodInitialized { + continue + } + + return condition.Status == api.ConditionTrue + } + return false +} diff --git a/pkg/printers/internalversion/printers_test.go b/pkg/printers/internalversion/printers_test.go index 8f4a7f0ee86..4f3feaab592 100644 --- a/pkg/printers/internalversion/printers_test.go +++ b/pkg/printers/internalversion/printers_test.go @@ -50,6 +50,8 @@ import ( utilpointer "k8s.io/utils/pointer" ) +var containerRestartPolicyAlways = api.ContainerRestartPolicyAlways + func TestFormatResourceName(t *testing.T) { tests := []struct { kind schema.GroupKind @@ -1536,6 +1538,200 @@ func TestPrintPod(t *testing.T) { } } +func TestPrintPodWithRestartableInitContainer(t *testing.T) { + tests := []struct { + pod api.Pod + expect []metav1.TableRow + }{ + { + // Test pod has 2 restartable init containers, the first one running but not started. + api.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "test1"}, + Spec: api.PodSpec{ + InitContainers: []api.Container{ + {Name: "restartable-init-1", RestartPolicy: &containerRestartPolicyAlways}, + {Name: "restartable-init-2", RestartPolicy: &containerRestartPolicyAlways}, + }, Containers: make([]api.Container, 1)}, + Status: api.PodStatus{ + Phase: "Pending", + InitContainerStatuses: []api.ContainerStatus{ + { + Name: "restartable-init-1", + Ready: false, + RestartCount: 3, + State: api.ContainerState{Running: &api.ContainerStateRunning{}}, + Started: utilpointer.Bool(false), + LastTerminationState: api.ContainerState{Terminated: &api.ContainerStateTerminated{FinishedAt: metav1.NewTime(time.Now().Add(-10 * time.Second))}}, + }, + { + Name: "restartable-init-2", + Ready: false, + State: api.ContainerState{Waiting: &api.ContainerStateWaiting{}}, + Started: utilpointer.Bool(false), + }, + }, + ContainerStatuses: []api.ContainerStatus{ + { + Ready: false, + RestartCount: 0, + State: api.ContainerState{Waiting: &api.ContainerStateWaiting{}}, + }, + }, + Conditions: []api.PodCondition{ + {Type: api.PodInitialized, Status: api.ConditionFalse}, + }, + }, + }, + []metav1.TableRow{{Cells: []interface{}{"test1", "0/3", "Init:0/2", "3 (10s ago)", ""}}}, + }, + { + // Test pod has 2 restartable init containers, the first one started and the second one running but not started. + api.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "test1"}, + Spec: api.PodSpec{ + InitContainers: []api.Container{ + {Name: "restartable-init-1", RestartPolicy: &containerRestartPolicyAlways}, + {Name: "restartable-init-2", RestartPolicy: &containerRestartPolicyAlways}, + }, Containers: make([]api.Container, 1)}, + Status: api.PodStatus{ + Phase: "Pending", + InitContainerStatuses: []api.ContainerStatus{ + { + Name: "restartable-init-1", + Ready: false, + RestartCount: 3, + State: api.ContainerState{Running: &api.ContainerStateRunning{}}, + Started: utilpointer.Bool(true), + LastTerminationState: api.ContainerState{Terminated: &api.ContainerStateTerminated{FinishedAt: metav1.NewTime(time.Now().Add(-10 * time.Second))}}, + }, + { + Name: "restartable-init-2", + Ready: false, + State: api.ContainerState{Running: &api.ContainerStateRunning{}}, + Started: utilpointer.Bool(false), + }, + }, + ContainerStatuses: []api.ContainerStatus{ + { + Ready: false, + RestartCount: 0, + State: api.ContainerState{Waiting: &api.ContainerStateWaiting{}}, + }, + }, + Conditions: []api.PodCondition{ + {Type: api.PodInitialized, Status: api.ConditionFalse}, + }, + }, + }, + []metav1.TableRow{{Cells: []interface{}{"test1", "0/3", "Init:1/2", "3 (10s ago)", ""}}}, + }, + { + // Test pod has 2 restartable init containers started and 1 container running + api.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "test2"}, + Spec: api.PodSpec{ + InitContainers: []api.Container{ + {Name: "restartable-init-1", RestartPolicy: &containerRestartPolicyAlways}, + {Name: "restartable-init-2", RestartPolicy: &containerRestartPolicyAlways}, + }, Containers: make([]api.Container, 1)}, + Status: api.PodStatus{ + Phase: "Running", + InitContainerStatuses: []api.ContainerStatus{ + { + Name: "restartable-init-1", + Ready: false, + RestartCount: 3, + State: api.ContainerState{Running: &api.ContainerStateRunning{}}, + Started: utilpointer.Bool(true), + LastTerminationState: api.ContainerState{Terminated: &api.ContainerStateTerminated{FinishedAt: metav1.NewTime(time.Now().Add(-10 * time.Second))}}, + }, + { + Name: "restartable-init-2", + Ready: false, + State: api.ContainerState{Running: &api.ContainerStateRunning{}}, + Started: utilpointer.Bool(true), + }, + }, + ContainerStatuses: []api.ContainerStatus{ + { + Ready: true, + RestartCount: 4, + State: api.ContainerState{Running: &api.ContainerStateRunning{}}, + LastTerminationState: api.ContainerState{Terminated: &api.ContainerStateTerminated{FinishedAt: metav1.NewTime(time.Now().Add(-20 * time.Second))}}, + }, + }, + Conditions: []api.PodCondition{ + {Type: api.PodInitialized, Status: api.ConditionTrue}, + }, + }, + }, + []metav1.TableRow{{Cells: []interface{}{"test2", "1/3", "Running", "7 (10s ago)", ""}}}, + }, + { + // Test pod has 2 restartable init containers completed with non-zero and 1 container completed + api.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "test3"}, + Spec: api.PodSpec{ + InitContainers: []api.Container{ + {Name: "restartable-init-1", RestartPolicy: &containerRestartPolicyAlways}, + {Name: "restartable-init-2", RestartPolicy: &containerRestartPolicyAlways}, + }, Containers: make([]api.Container, 1)}, + Status: api.PodStatus{ + Phase: "Succeeded", + InitContainerStatuses: []api.ContainerStatus{ + { + Name: "restartable-init-1", + Ready: false, + RestartCount: 3, + State: api.ContainerState{Terminated: &api.ContainerStateTerminated{Reason: "Error", ExitCode: 137}}, + Started: utilpointer.Bool(false), + LastTerminationState: api.ContainerState{Terminated: &api.ContainerStateTerminated{FinishedAt: metav1.NewTime(time.Now().Add(-10 * time.Second))}}, + }, + { + Name: "restartable-init-2", + Ready: false, + State: api.ContainerState{Terminated: &api.ContainerStateTerminated{Reason: "Error", ExitCode: 137}}, + Started: utilpointer.Bool(false), + }, + }, + ContainerStatuses: []api.ContainerStatus{ + { + Ready: false, + RestartCount: 4, + State: api.ContainerState{Terminated: &api.ContainerStateTerminated{Reason: "Completed", ExitCode: 0}}, + LastTerminationState: api.ContainerState{Terminated: &api.ContainerStateTerminated{FinishedAt: metav1.NewTime(time.Now().Add(-20 * time.Second))}}, + }, + }, + Conditions: []api.PodCondition{ + {Type: api.PodInitialized, Status: api.ConditionTrue}, + }, + }, + }, + []metav1.TableRow{ + { + Cells: []interface{}{"test3", "0/3", "Completed", "7 (10s ago)", ""}, + Conditions: []metav1.TableRowCondition{ + {Type: metav1.RowCompleted, Status: metav1.ConditionTrue, Reason: "Succeeded", Message: "The pod has completed successfully."}, + }, + }, + }, + }, + } + + for i, test := range tests { + rows, err := printPod(&test.pod, printers.GenerateOptions{}) + if err != nil { + t.Fatal(err) + } + for i := range rows { + rows[i].Object.Object = nil + } + if !reflect.DeepEqual(test.expect, rows) { + t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expect, rows)) + } + } +} + func TestPrintPodwide(t *testing.T) { condition1 := "condition1" condition2 := "condition2"