Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Show more detailed pod status on pod list page #1308

Merged
merged 8 commits into from
Oct 7, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions i18n/messages-en.xtb
Original file line number Diff line number Diff line change
Expand Up @@ -665,4 +665,6 @@
<translation id="494424795601874379" key="MSG_WORKLOADS_PODS_LABEL" source="/usr/local/google/home/bryk/src/github.com/kubernetes/dashboard/.tmp/serve/app-dev.js" desc="Label &quot;Pods&quot;, which appears above the pods list on the workloads page.">Pods</translation>
<translation id="1611578389032486450" key="MSG_WORKLOADS_CPU_GRAPH_CARD_TITLE" source="/usr/local/google/home/bryk/src/github.com/kubernetes/dashboard/.tmp/serve/app-dev.js" desc="Title for graph card displaying CPU metric of one all resources.">CPU usage history</translation>
<translation id="2764620581378008580" key="MSG_WORKLOADS_MEMORY_GRAPH_CARD_TITLE" source="/usr/local/google/home/bryk/src/github.com/kubernetes/dashboard/.tmp/serve/app-dev.js" desc="Title for graph card displaying memory metric of one all resources.">Memory usage history</translation>
<translation id="3724043031520273690" key="MSG_POD_LIST_POD_WAITING_STATUS" source="/Users/ianlewis/src/dashboard/.tmp/serve/app-dev.js" desc="Status message showing a waiting status with [reason].">Waiting: <ph name="REASON" /></translation>
<translation id="1014088790164786434" key="MSG_POD_LIST_POD_TERMINATED_STATUS" source="/Users/ianlewis/src/dashboard/.tmp/serve/app-dev.js" desc="Status message showing a terminated status with [reason].">Terminated: <ph name="REASON" /></translation>
</translationbundle>
2 changes: 2 additions & 0 deletions i18n/messages-ja.xtb
Original file line number Diff line number Diff line change
Expand Up @@ -1009,4 +1009,6 @@
<translation id="5229153540672717672" key="MSG_RESOURCE_DETAIL_INFO_CARD_LABELS_LABEL" source="/usr/local/google/home/bryk/src/github.com/kubernetes/dashboard/.tmp/serve/app-dev.js" desc="Label 'Labels' for a resource.">Labels</translation>
<translation id="5837164744700950654" key="MSG_RESOURCE_DETAIL_INFO_CARD_ANNOTATIONS_LABEL" source="/usr/local/google/home/bryk/src/github.com/kubernetes/dashboard/.tmp/serve/app-dev.js" desc="Label 'annotations' for a resource.">Annotations</translation>
<translation id="7410674269510116677" key="MSG_REPLICA_SET_DETAIL_SELECOTR_LABEL" source="/usr/local/google/home/bryk/src/github.com/kubernetes/dashboard/.tmp/serve/app-dev.js" desc="Label 'selector' for replica sets">Selector</translation>
<translation id="3724043031520273690" key="MSG_POD_LIST_POD_WAITING_STATUS" source="/Users/ianlewis/src/dashboard/.tmp/serve/app-dev.js" desc="Status message showing a waiting status with [reason].">待機: <ph name="REASON" /></translation>
<translation id="1014088790164786434" key="MSG_POD_LIST_POD_TERMINATED_STATUS" source="/Users/ianlewis/src/dashboard/.tmp/serve/app-dev.js" desc="Status message showing a terminated status with [reason].">終了: <ph name="REASON" /></translation>
</translationbundle>
15 changes: 14 additions & 1 deletion src/app/backend/resource/pod/podcommon.go
Original file line number Diff line number Diff line change
Expand Up @@ -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),
}
Expand Down
11 changes: 9 additions & 2 deletions src/app/backend/resource/pod/podlist.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,22 @@ 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).
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"`
Expand Down
34 changes: 33 additions & 1 deletion src/app/externs/backendapi.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.ContainerState>
* }}
*/
backendApi.PodStatus;

/**
* @typedef {{
* objectMeta: !backendApi.ObjectMeta,
* typeMeta: !backendApi.TypeMeta,
* podPhase: string,
* podStatus: !backendApi.PodStatus,
* podIP: string,
* restartCount: number,
* metrics: backendApi.PodMetrics
Expand Down
2 changes: 1 addition & 1 deletion src/app/frontend/podlist/podcardlist.html
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@
</kd-middle-ellipsis>
</div>
</kd-resource-card-column>
<kd-resource-card-column>{{::pod.podPhase}}</kd-resource-card-column>
<kd-resource-card-column>{{::$ctrl.getDisplayStatus(pod)}}</kd-resource-card-column>
<kd-resource-card-column>{{::pod.restartCount}}</kd-resource-card-column>
<kd-resource-card-column>
<div ng-if="::pod.objectMeta.creationTimestamp">
Expand Down
50 changes: 47 additions & 3 deletions src/app/frontend/podlist/podcardlist_component.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,14 +108,58 @@ 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
* @return {boolean}
* @export
*/
isStatusSuccessful(pod) {
return pod.podPhase === 'Running' || pod.podPhase === 'Succeeded';
return pod.podStatus.podPhase === 'Running' || pod.podStatus.podPhase === 'Succeeded';
}

/**
Expand All @@ -125,7 +169,7 @@ export class PodCardListController {
* @export
*/
isStatusPending(pod) {
return pod.podPhase === 'Pending';
return pod.podStatus.podPhase === 'Pending';
}

/**
Expand All @@ -135,7 +179,7 @@ export class PodCardListController {
* @export
*/
isStatusFailed(pod) {
return pod.podPhase === 'Failed';
return pod.podStatus.podPhase === 'Failed';
}

/**
Expand Down
50 changes: 50 additions & 0 deletions src/test/backend/resource/pod/podcommon_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
88 changes: 60 additions & 28 deletions src/test/frontend/podlist/podcardlist_component_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down