diff --git a/i18n/messages-en.xtb b/i18n/messages-en.xtb index 460e8410f346..fff538475130 100644 --- a/i18n/messages-en.xtb +++ b/i18n/messages-en.xtb @@ -665,4 +665,6 @@ Pods CPU usage history Memory usage history + Waiting: + Terminated: \ No newline at end of file diff --git a/i18n/messages-ja.xtb b/i18n/messages-ja.xtb index 3af6085eaea5..fe86ed0053d7 100644 --- a/i18n/messages-ja.xtb +++ b/i18n/messages-ja.xtb @@ -1009,4 +1009,6 @@ Labels Annotations Selector + 待機: + 終了: \ No newline at end of file diff --git a/src/app/backend/resource/pod/podcommon.go b/src/app/backend/resource/pod/podcommon.go index b3dd7a663909..ea54fa4d2254 100644 --- a/src/app/backend/resource/pod/podcommon.go +++ b/src/app/backend/resource/pod/podcommon.go @@ -30,12 +30,25 @@ func getRestartCount(pod api.Pod) int32 { return restartCount } +// getPodStatus returns a PodStatus object containing a summary of the pod's status. +func getPodStatus(pod api.Pod) PodStatus { + var states []api.ContainerState + for _, containerStatus := range pod.Status.ContainerStatuses { + states = append(states, containerStatus.State) + } + + return PodStatus{ + PodPhase: pod.Status.Phase, + ContainerStates: states, + } +} + // ToPod transforms Kubernetes pod object into object returned by API. func ToPod(pod *api.Pod, metrics *common.MetricsByPod) Pod { podDetail := Pod{ ObjectMeta: common.NewObjectMeta(pod.ObjectMeta), TypeMeta: common.NewTypeMeta(common.ResourceKindPod), - PodPhase: pod.Status.Phase, + PodStatus: getPodStatus(*pod), PodIP: pod.Status.PodIP, RestartCount: getRestartCount(*pod), } diff --git a/src/app/backend/resource/pod/podlist.go b/src/app/backend/resource/pod/podlist.go index 40208225bf3a..fe975d83d566 100644 --- a/src/app/backend/resource/pod/podlist.go +++ b/src/app/backend/resource/pod/podlist.go @@ -35,6 +35,13 @@ type PodList struct { CumulativeMetrics []metric.Metric `json:"cumulativeMetrics"` } +type PodStatus struct { + // Status of the Pod. See Kubernetes API for reference. + PodPhase api.PodPhase `json:"podPhase"` + + ContainerStates []api.ContainerState `json:"containerStates"` +} + // Pod is a presentation layer view of Kubernetes Pod resource. This means // it is Pod plus additional augumented data we can get from other sources // (like services that target it). @@ -42,8 +49,8 @@ type Pod struct { ObjectMeta common.ObjectMeta `json:"objectMeta"` TypeMeta common.TypeMeta `json:"typeMeta"` - // Status of the Pod. See Kubernetes API for reference. - PodPhase api.PodPhase `json:"podPhase"` + // More info on pod status + PodStatus PodStatus `json:"podStatus"` // IP address of the Pod. PodIP string `json:"podIP"` diff --git a/src/app/externs/backendapi.js b/src/app/externs/backendapi.js index 1ed469b1abff..17bb2cab3ea9 100644 --- a/src/app/externs/backendapi.js +++ b/src/app/externs/backendapi.js @@ -658,11 +658,43 @@ backendApi.ReplicationControllerSpec; */ backendApi.DeleteReplicationControllerSpec; +/** + * @typedef {{ + * reason: string + * }} + */ +backendApi.ContainerStateWaiting; + +/** + * @typedef {{ + * reason: string, + * signal: number, + * exitCode: number + * }} + */ +backendApi.ContainerStateTerminated; + +/** + * @typedef {{ + * waiting: !backendApi.ContainerStateWaiting, + * terminated: !backendApi.ContainerStateTerminated + * }} + */ +backendApi.ContainerState; + +/** + * @typedef {{ + * podPhase: string, + * containerStates: !Array + * }} + */ +backendApi.PodStatus; + /** * @typedef {{ * objectMeta: !backendApi.ObjectMeta, * typeMeta: !backendApi.TypeMeta, - * podPhase: string, + * podStatus: !backendApi.PodStatus, * podIP: string, * restartCount: number, * metrics: backendApi.PodMetrics diff --git a/src/app/frontend/podlist/podcardlist.html b/src/app/frontend/podlist/podcardlist.html index 518dc0572d8d..f594160c288e 100644 --- a/src/app/frontend/podlist/podcardlist.html +++ b/src/app/frontend/podlist/podcardlist.html @@ -73,7 +73,7 @@ - {{::pod.podPhase}} + {{::$ctrl.getDisplayStatus(pod)}} {{::pod.restartCount}}
diff --git a/src/app/frontend/podlist/podcardlist_component.js b/src/app/frontend/podlist/podcardlist_component.js index e1544b207380..2a687f3e240b 100644 --- a/src/app/frontend/podlist/podcardlist_component.js +++ b/src/app/frontend/podlist/podcardlist_component.js @@ -108,6 +108,50 @@ export class PodCardListController { stateName, new StateParams(pod.objectMeta.namespace, pod.objectMeta.name)); } + /** + * Returns a displayable status message for the pod. + * @param {!backendApi.Pod} pod + * @return {string} + * @export + */ + getDisplayStatus(pod) { + let msgState = 'running'; + let reason = undefined; + for (let i = pod.podStatus.containerStates.length - 1; i >= 0; i--) { + let state = pod.podStatus.containerStates[i]; + + if (state.waiting) { + msgState = 'waiting'; + reason = state.waiting.reason; + } + if (state.terminated) { + msgState = 'terminated'; + reason = state.terminated.reason; + if (!reason) { + if (state.terminated.signal) { + reason = 'Signal:${state.terminated.signal}'; + } else { + reason = 'ExitCode:${state.terminated.exitCode}'; + } + } + } + } + + /** @type {string} @desc Status message showing a waiting status with [reason].*/ + let MSG_POD_LIST_POD_WAITING_STATUS = goog.getMsg('Waiting: {$reason}', {'reason': reason}); + /** @type {string} @desc Status message showing a terminated status with [reason].*/ + let MSG_POD_LIST_POD_TERMINATED_STATUS = + goog.getMsg('Terminated: {$reason}', {'reason': reason}); + + if (msgState === 'waiting') { + return MSG_POD_LIST_POD_WAITING_STATUS; + } + if (msgState === 'terminated') { + return MSG_POD_LIST_POD_TERMINATED_STATUS; + } + return pod.podStatus.podPhase; + } + /** * Checks if pod status is successful, i.e. running or succeeded. * @param pod @@ -115,7 +159,7 @@ export class PodCardListController { * @export */ isStatusSuccessful(pod) { - return pod.podPhase === 'Running' || pod.podPhase === 'Succeeded'; + return pod.podStatus.podPhase === 'Running' || pod.podStatus.podPhase === 'Succeeded'; } /** @@ -125,7 +169,7 @@ export class PodCardListController { * @export */ isStatusPending(pod) { - return pod.podPhase === 'Pending'; + return pod.podStatus.podPhase === 'Pending'; } /** @@ -135,7 +179,7 @@ export class PodCardListController { * @export */ isStatusFailed(pod) { - return pod.podPhase === 'Failed'; + return pod.podStatus.podPhase === 'Failed'; } /** diff --git a/src/test/backend/resource/pod/podcommon_test.go b/src/test/backend/resource/pod/podcommon_test.go index 01b906f2c967..d93464ad7e22 100644 --- a/src/test/backend/resource/pod/podcommon_test.go +++ b/src/test/backend/resource/pod/podcommon_test.go @@ -23,6 +23,56 @@ import ( "github.com/kubernetes/dashboard/src/app/backend/resource/common" ) +// TestToPodContainerStates tests that ToPod returns the correct container states +func TestToPodContainerStates(t *testing.T) { + pod := &api.Pod{ + Status: api.PodStatus{ + Phase: api.PodRunning, + ContainerStatuses: []api.ContainerStatus{ + api.ContainerStatus{ + State: api.ContainerState{ + Terminated: &api.ContainerStateTerminated{ + Reason: "Terminated Test Reason", + }, + }, + }, + api.ContainerStatus{ + State: api.ContainerState{ + Waiting: &api.ContainerStateWaiting{ + Reason: "Waiting Test Reason", + }, + }, + }, + }, + }, + } + + expected := Pod{ + TypeMeta: common.TypeMeta{Kind: common.ResourceKindPod}, + PodStatus: PodStatus{ + PodPhase: api.PodRunning, + ContainerStates: []api.ContainerState{ + api.ContainerState{ + Terminated: &api.ContainerStateTerminated{ + Reason: "Terminated Test Reason", + }, + }, + api.ContainerState{ + Waiting: &api.ContainerStateWaiting{ + Reason: "Waiting Test Reason", + }, + }, + }, + }, + } + + actual := ToPod(pod, &common.MetricsByPod{}) + + if !reflect.DeepEqual(actual, expected) { + t.Errorf("ToPod(%#v) == \ngot %#v, \nexpected %#v", pod, actual, expected) + } +} + func TestGetPodDetail(t *testing.T) { cases := []struct { pod *api.Pod diff --git a/src/test/frontend/podlist/podcardlist_component_test.js b/src/test/frontend/podlist/podcardlist_component_test.js index bc6d68cde028..b6e721dac327 100644 --- a/src/test/frontend/podlist/podcardlist_component_test.js +++ b/src/test/frontend/podlist/podcardlist_component_test.js @@ -55,53 +55,85 @@ describe('Pod card list controller', () => { })).toBe('#/pod/foo-namespace/foo-pod'); }); + it('should show display status correctly (running container)', () => { + // Output is expected to be equal to the podPhase. + expect(ctrl.getDisplayStatus({ + podStatus: {podPhase: 'Test Phase', containerStates: [{running: {}}]}, + })).toBe('Test Phase'); + }); + + it('should show display status correctly (waiting container)', () => { + expect(ctrl.getDisplayStatus({ + podStatus: { + podPhase: 'Test Phase', + containerStates: [{ + waiting: { + reason: 'Test Reason', + }, + }], + }, + })).toBe('Waiting: Test Reason'); + }); + + it('should show display status correctly (terminated container)', () => { + expect(ctrl.getDisplayStatus({ + podStatus: { + podPhase: 'Test Phase', + containerStates: [{ + terminated: { + reason: 'Test Reason', + }, + }], + }, + })).toBe('Terminated: Test Reason'); + }); + + it('should show display status correctly (multi container)', () => { + expect(ctrl.getDisplayStatus({ + podStatus: { + podPhase: 'Test Phase', + containerStates: [ + {running: {}}, + { + terminated: { + reason: 'Test Terminated Reason', + }, + }, + {waiting: {reason: 'Test Waiting Reason'}}, + ], + }, + })).toBe('Terminated: Test Terminated Reason'); + }); + it('should check pod status correctly (succeeded is successful)', () => { - expect(ctrl.isStatusSuccessful({ - name: 'test-pod', - podPhase: 'Succeeded', - })).toBeTruthy(); + expect(ctrl.isStatusSuccessful({name: 'test-pod', podStatus: {podPhase: 'Succeeded'}})) + .toBeTruthy(); }); it('should check pod status correctly (running is successful)', () => { - expect(ctrl.isStatusSuccessful({ - name: 'test-pod', - podPhase: 'Running', - })).toBeTruthy(); + expect(ctrl.isStatusSuccessful({name: 'test-pod', podStatus: {podPhase: 'Running'}})) + .toBeTruthy(); }); it('should check pod status correctly (failed isn\'t successful)', () => { - expect(ctrl.isStatusSuccessful({ - name: 'test-pod', - podPhase: 'Failed', - })).toBeFalsy(); + expect(ctrl.isStatusSuccessful({name: 'test-pod', podStatus: {podPhase: 'Failed'}})) + .toBeFalsy(); }); it('should check pod status correctly (pending is pending)', () => { - expect(ctrl.isStatusPending({ - name: 'test-pod', - podPhase: 'Pending', - })).toBeTruthy(); + expect(ctrl.isStatusPending({name: 'test-pod', podStatus: {podPhase: 'Pending'}})).toBeTruthy(); }); it('should check pod status correctly (failed isn\'t pending)', () => { - expect(ctrl.isStatusPending({ - name: 'test-pod', - podPhase: 'Failed', - })).toBeFalsy(); + expect(ctrl.isStatusPending({name: 'test-pod', podStatus: {podPhase: 'Failed'}})).toBeFalsy(); }); it('should check pod status correctly (failed is failed)', () => { - expect(ctrl.isStatusFailed({ - name: 'test-pod', - podPhase: 'Failed', - })).toBeTruthy(); + expect(ctrl.isStatusFailed({name: 'test-pod', podStatus: {podPhase: 'Failed'}})).toBeTruthy(); }); it('should check pod status correctly (running isn\'t failed)', () => { - expect(ctrl.isStatusFailed({ - name: 'test-pod', - podPhase: 'Running', - })).toBeFalsy(); + expect(ctrl.isStatusFailed({name: 'test-pod', podStatus: {podPhase: 'Running'}})).toBeFalsy(); }); it('should format the "pod start date" tooltip correctly', () => {