diff --git a/pkg/registry/pod/rest.go b/pkg/registry/pod/rest.go index 133ca0302b1..51f34c1ce39 100644 --- a/pkg/registry/pod/rest.go +++ b/pkg/registry/pod/rest.go @@ -294,8 +294,12 @@ func getPodStatus(pod *api.Pod, nodes client.NodeInterface) (api.PodPhase, error if pod.Status.Info == nil { return api.PodPending, nil } + // TODO(dchen1107): move the entire logic to kubelet? running := 0 + waiting := 0 stopped := 0 + failed := 0 + succeeded := 0 unknown := 0 for _, container := range pod.Spec.Containers { if containerStatus, ok := pod.Status.Info[container.Name]; ok { @@ -303,6 +307,13 @@ func getPodStatus(pod *api.Pod, nodes client.NodeInterface) (api.PodPhase, error running++ } else if containerStatus.State.Termination != nil { stopped++ + if containerStatus.State.Termination.ExitCode == 0 { + succeeded++ + } else { + failed++ + } + } else if containerStatus.State.Waiting != nil { + waiting++ } else { unknown++ } @@ -311,12 +322,32 @@ func getPodStatus(pod *api.Pod, nodes client.NodeInterface) (api.PodPhase, error } } switch { + case waiting > 0: + // One or more containers has not been started + return api.PodPending, nil case running > 0 && unknown == 0: + // All containers have been started, and at least + // one container is running return api.PodRunning, nil case running == 0 && stopped > 0 && unknown == 0: - return api.PodFailed, nil - case running == 0 && stopped == 0 && unknown > 0: - return api.PodPending, nil + // All containers are terminated + if pod.Spec.RestartPolicy.Always != nil { + // All containers are in the process of restarting + return api.PodRunning, nil + } + if stopped == succeeded { + // RestartPolicy is not Always, and all + // containers are terminated in success + return api.PodSucceeded, nil + } + if pod.Spec.RestartPolicy.Never != nil { + // RestartPolicy is Never, and all containers are + // terminated with at least one in failure + return api.PodFailed, nil + } + // RestartPolicy is OnFailure, and at least one in failure + // and in the process of restarting + return api.PodRunning, nil default: return api.PodPending, nil } diff --git a/pkg/registry/pod/rest_test.go b/pkg/registry/pod/rest_test.go index a74e3f6ec41..177af00c515 100644 --- a/pkg/registry/pod/rest_test.go +++ b/pkg/registry/pod/rest_test.go @@ -368,7 +368,7 @@ func TestGetPodCloud(t *testing.T) { } } -func TestMakePodStatus(t *testing.T) { +func TestPodStatusWithBadNode(t *testing.T) { fakeClient := client.Fake{ MinionsList: api.NodeList{ Items: []api.Node{ @@ -383,6 +383,89 @@ func TestMakePodStatus(t *testing.T) { {Name: "containerA"}, {Name: "containerB"}, }, + RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}}, + } + runningState := api.ContainerStatus{ + State: api.ContainerState{ + Running: &api.ContainerStateRunning{}, + }, + } + stoppedState := api.ContainerStatus{ + State: api.ContainerState{ + Termination: &api.ContainerStateTerminated{}, + }, + } + + tests := []struct { + pod *api.Pod + status api.PodPhase + test string + }{ + { + &api.Pod{ + Spec: desiredState, + Status: api.PodStatus{ + Host: "machine-2", + }, + }, + api.PodFailed, + "no info, but bad machine", + }, + { + &api.Pod{ + Spec: desiredState, + Status: api.PodStatus{ + Info: map[string]api.ContainerStatus{ + "containerA": runningState, + "containerB": runningState, + }, + Host: "machine-two", + }, + }, + api.PodFailed, + "all running but minion is missing", + }, + { + &api.Pod{ + Spec: desiredState, + Status: api.PodStatus{ + Info: map[string]api.ContainerStatus{ + "containerA": stoppedState, + "containerB": stoppedState, + }, + Host: "machine-two", + }, + }, + api.PodFailed, + "all stopped but minion missing", + }, + } + for _, test := range tests { + if status, err := getPodStatus(test.pod, fakeClient.Nodes()); status != test.status { + t.Errorf("In test %s, expected %v, got %v", test.test, test.status, status) + if err != nil { + t.Errorf("In test %s, unexpected error: %v", test.test, err) + } + } + } +} + +func TestPodStatusWithRestartAlways(t *testing.T) { + fakeClient := client.Fake{ + MinionsList: api.NodeList{ + Items: []api.Node{ + { + ObjectMeta: api.ObjectMeta{Name: "machine"}, + }, + }, + }, + } + desiredState := api.PodSpec{ + Containers: []api.Container{ + {Name: "containerA"}, + {Name: "containerB"}, + }, + RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}}, } currentState := api.PodStatus{ Host: "machine", @@ -404,16 +487,6 @@ func TestMakePodStatus(t *testing.T) { test string }{ {&api.Pod{Spec: desiredState, Status: currentState}, api.PodPending, "waiting"}, - { - &api.Pod{ - Spec: desiredState, - Status: api.PodStatus{ - Host: "machine-2", - }, - }, - api.PodFailed, - "no info, but bad machine", - }, { &api.Pod{ Spec: desiredState, @@ -428,20 +501,6 @@ func TestMakePodStatus(t *testing.T) { api.PodRunning, "all running", }, - { - &api.Pod{ - Spec: desiredState, - Status: api.PodStatus{ - Info: map[string]api.ContainerStatus{ - "containerA": runningState, - "containerB": runningState, - }, - Host: "machine-two", - }, - }, - api.PodFailed, - "all running but minion is missing", - }, { &api.Pod{ Spec: desiredState, @@ -453,22 +512,8 @@ func TestMakePodStatus(t *testing.T) { Host: "machine", }, }, - api.PodFailed, - "all stopped", - }, - { - &api.Pod{ - Spec: desiredState, - Status: api.PodStatus{ - Info: map[string]api.ContainerStatus{ - "containerA": stoppedState, - "containerB": stoppedState, - }, - Host: "machine-two", - }, - }, - api.PodFailed, - "all stopped but minion missing", + api.PodRunning, + "all stopped with restart always", }, { &api.Pod{ @@ -482,7 +527,7 @@ func TestMakePodStatus(t *testing.T) { }, }, api.PodRunning, - "mixed state #1", + "mixed state #1 with restart always", }, { &api.Pod{ @@ -495,7 +540,259 @@ func TestMakePodStatus(t *testing.T) { }, }, api.PodPending, - "mixed state #2", + "mixed state #2 with restart always", + }, + } + for _, test := range tests { + if status, err := getPodStatus(test.pod, fakeClient.Nodes()); status != test.status { + t.Errorf("In test %s, expected %v, got %v", test.test, test.status, status) + if err != nil { + t.Errorf("In test %s, unexpected error: %v", test.test, err) + } + } + } +} + +func TestPodStatusWithRestartNever(t *testing.T) { + fakeClient := client.Fake{ + MinionsList: api.NodeList{ + Items: []api.Node{ + { + ObjectMeta: api.ObjectMeta{Name: "machine"}, + }, + }, + }, + } + desiredState := api.PodSpec{ + Containers: []api.Container{ + {Name: "containerA"}, + {Name: "containerB"}, + }, + RestartPolicy: api.RestartPolicy{Never: &api.RestartPolicyNever{}}, + } + currentState := api.PodStatus{ + Host: "machine", + } + runningState := api.ContainerStatus{ + State: api.ContainerState{ + Running: &api.ContainerStateRunning{}, + }, + } + succeededState := api.ContainerStatus{ + State: api.ContainerState{ + Termination: &api.ContainerStateTerminated{ + ExitCode: 0, + }, + }, + } + failedState := api.ContainerStatus{ + State: api.ContainerState{ + Termination: &api.ContainerStateTerminated{ + ExitCode: -1, + }, + }, + } + + tests := []struct { + pod *api.Pod + status api.PodPhase + test string + }{ + {&api.Pod{Spec: desiredState, Status: currentState}, api.PodPending, "waiting"}, + { + &api.Pod{ + Spec: desiredState, + Status: api.PodStatus{ + Info: map[string]api.ContainerStatus{ + "containerA": runningState, + "containerB": runningState, + }, + Host: "machine", + }, + }, + api.PodRunning, + "all running with restart never", + }, + { + &api.Pod{ + Spec: desiredState, + Status: api.PodStatus{ + Info: map[string]api.ContainerStatus{ + "containerA": succeededState, + "containerB": succeededState, + }, + Host: "machine", + }, + }, + api.PodSucceeded, + "all succeeded with restart never", + }, + { + &api.Pod{ + Spec: desiredState, + Status: api.PodStatus{ + Info: map[string]api.ContainerStatus{ + "containerA": failedState, + "containerB": failedState, + }, + Host: "machine", + }, + }, + api.PodFailed, + "all failed with restart never", + }, + { + &api.Pod{ + Spec: desiredState, + Status: api.PodStatus{ + Info: map[string]api.ContainerStatus{ + "containerA": runningState, + "containerB": succeededState, + }, + Host: "machine", + }, + }, + api.PodRunning, + "mixed state #1 with restart never", + }, + { + &api.Pod{ + Spec: desiredState, + Status: api.PodStatus{ + Info: map[string]api.ContainerStatus{ + "containerA": runningState, + }, + Host: "machine", + }, + }, + api.PodPending, + "mixed state #2 with restart never", + }, + } + for _, test := range tests { + if status, err := getPodStatus(test.pod, fakeClient.Nodes()); status != test.status { + t.Errorf("In test %s, expected %v, got %v", test.test, test.status, status) + if err != nil { + t.Errorf("In test %s, unexpected error: %v", test.test, err) + } + } + } +} + +func TestPodStatusWithRestartOnFailure(t *testing.T) { + fakeClient := client.Fake{ + MinionsList: api.NodeList{ + Items: []api.Node{ + { + ObjectMeta: api.ObjectMeta{Name: "machine"}, + }, + }, + }, + } + desiredState := api.PodSpec{ + Containers: []api.Container{ + {Name: "containerA"}, + {Name: "containerB"}, + }, + RestartPolicy: api.RestartPolicy{OnFailure: &api.RestartPolicyOnFailure{}}, + } + currentState := api.PodStatus{ + Host: "machine", + } + runningState := api.ContainerStatus{ + State: api.ContainerState{ + Running: &api.ContainerStateRunning{}, + }, + } + succeededState := api.ContainerStatus{ + State: api.ContainerState{ + Termination: &api.ContainerStateTerminated{ + ExitCode: 0, + }, + }, + } + failedState := api.ContainerStatus{ + State: api.ContainerState{ + Termination: &api.ContainerStateTerminated{ + ExitCode: -1, + }, + }, + } + + tests := []struct { + pod *api.Pod + status api.PodPhase + test string + }{ + {&api.Pod{Spec: desiredState, Status: currentState}, api.PodPending, "waiting"}, + { + &api.Pod{ + Spec: desiredState, + Status: api.PodStatus{ + Info: map[string]api.ContainerStatus{ + "containerA": runningState, + "containerB": runningState, + }, + Host: "machine", + }, + }, + api.PodRunning, + "all running with restart onfailure", + }, + { + &api.Pod{ + Spec: desiredState, + Status: api.PodStatus{ + Info: map[string]api.ContainerStatus{ + "containerA": succeededState, + "containerB": succeededState, + }, + Host: "machine", + }, + }, + api.PodSucceeded, + "all succeeded with restart onfailure", + }, + { + &api.Pod{ + Spec: desiredState, + Status: api.PodStatus{ + Info: map[string]api.ContainerStatus{ + "containerA": failedState, + "containerB": failedState, + }, + Host: "machine", + }, + }, + api.PodRunning, + "all failed with restart never", + }, + { + &api.Pod{ + Spec: desiredState, + Status: api.PodStatus{ + Info: map[string]api.ContainerStatus{ + "containerA": runningState, + "containerB": succeededState, + }, + Host: "machine", + }, + }, + api.PodRunning, + "mixed state #1 with restart onfailure", + }, + { + &api.Pod{ + Spec: desiredState, + Status: api.PodStatus{ + Info: map[string]api.ContainerStatus{ + "containerA": runningState, + }, + Host: "machine", + }, + }, + api.PodPending, + "mixed state #2 with restart onfailure", }, } for _, test := range tests {