diff --git a/worker/server/pod.go b/worker/server/pod.go index aa75e1de3..89134bbf6 100644 --- a/worker/server/pod.go +++ b/worker/server/pod.go @@ -39,7 +39,9 @@ func (r *RuntimeServer) GetPodDetail(ctx context.Context, req *pb.GetPodDetailRe // describe pod podDetail = &pb.PodDetail{} podDetail.Name = pod.Name - podDetail.StartTime = pod.Status.StartTime.Time.Format(time.RFC3339) + if pod.Status.StartTime != nil { + podDetail.StartTime = pod.Status.StartTime.Time.Format(time.RFC3339) + } podDetail.InitContainers = make([]*pb.PodContainer, len(pod.Spec.InitContainers)) podDetail.Containers = make([]*pb.PodContainer, len(pod.Spec.Containers)) podDetail.Status = &pb.PodStatus{} @@ -58,7 +60,7 @@ func (r *RuntimeServer) GetPodDetail(ctx context.Context, req *pb.GetPodDetailRe podDetail.Events = append(podDetail.Events, events...) } - if len(pod.Spec.InitContainers) == 0 { + if len(pod.Spec.InitContainers) != 0 { describeContainers(pod.Spec.InitContainers, pod.Status.InitContainerStatuses, &podDetail.InitContainers) } describeContainers(pod.Spec.Containers, pod.Status.ContainerStatuses, &podDetail.Containers) @@ -126,8 +128,7 @@ func describeContainers(containers []corev1.Container, containerStatuses []corev statuses[status.Name] = status } - var pcs []*pb.PodContainer - for _, container := range containers { + for idx, container := range containers { status, ok := statuses[container.Name] pc := &pb.PodContainer{ Image: container.Image, @@ -136,9 +137,9 @@ func describeContainers(containers []corev1.Container, containerStatuses []corev describeContainerState(status, pc) } describeContainerResource(container, pc) - pcs = append(pcs, pc) + pcs := *podContainers + pcs[idx] = pc } - *podContainers = pcs } func describeContainerState(status corev1.ContainerStatus, podContainer *pb.PodContainer) { diff --git a/worker/server/server.go b/worker/server/server.go index 056824f4e..b154aa0c0 100644 --- a/worker/server/server.go +++ b/worker/server/server.go @@ -35,6 +35,7 @@ import ( "github.com/goodrain/rainbond/worker/appm/thirdparty/discovery" v1 "github.com/goodrain/rainbond/worker/appm/types/v1" "github.com/goodrain/rainbond/worker/server/pb" + wutil "github.com/goodrain/rainbond/worker/util" "google.golang.org/grpc" "google.golang.org/grpc/reflection" corev1 "k8s.io/api/core/v1" @@ -157,9 +158,11 @@ func (r *RuntimeServer) GetAppPods(ctx context.Context, re *pb.ServiceRequest) ( sapod := &pb.ServiceAppPod{ PodIp: pod.Status.PodIP, PodName: pod.Name, - PodStatus: string(pod.Status.Phase), Containers: containers, } + podStatus := &pb.PodStatus{} + wutil.DescribePodStatus(pod, podStatus) + sapod.PodStatus = podStatus.Type.String() if app.DistinguishPod(pod) { newpods = append(newpods, sapod) } else { diff --git a/worker/util/pod.go b/worker/util/pod.go index 5ff4e2ea7..58f026da2 100644 --- a/worker/util/pod.go +++ b/worker/util/pod.go @@ -2,6 +2,7 @@ package util import ( "fmt" + "sort" "github.com/goodrain/rainbond/worker/server/pb" corev1 "k8s.io/api/core/v1" @@ -18,6 +19,7 @@ var podStatusTbl = map[string]pb.PodStatus_Type{ string(corev1.PodScheduled): pb.PodStatus_SCHEDULING, } +// DescribePodStatus - func DescribePodStatus(pod *corev1.Pod, podStatus *pb.PodStatus) { if pod.DeletionTimestamp != nil { podStatus.Type = pb.PodStatus_TEMINATING @@ -34,7 +36,7 @@ func DescribePodStatus(pod *corev1.Pod, podStatus *pb.PodStatus) { } else { // schedule, ready, init podStatus.Type = pb.PodStatus_RUNNING - // TODO: sort important. + sort.Sort(SortableConditionType(pod.Status.Conditions)) for _, condition := range pod.Status.Conditions { if condition.Status == corev1.ConditionTrue { continue @@ -47,3 +49,24 @@ func DescribePodStatus(pod *corev1.Pod, podStatus *pb.PodStatus) { } podStatus.TypeStr = podStatus.Type.String() } + +// SortableConditionType implements sort.Interface for []PodCondition based on +// the Type field. +type SortableConditionType []corev1.PodCondition + +var podConditionTbl = map[corev1.PodConditionType]int{ + corev1.PodScheduled: 0, + corev1.PodInitialized: 1, + corev1.PodReady: 2, +} + +func (s SortableConditionType) Len() int { + return len(s) +} +func (s SortableConditionType) Swap(i, j int) { + s[i], s[j] = s[j], s[i] +} + +func (s SortableConditionType) Less(i, j int) bool { + return podConditionTbl[s[i].Type] > podConditionTbl[s[j].Type] +} diff --git a/worker/util/pod_test.go b/worker/util/pod_test.go new file mode 100644 index 000000000..04782f510 --- /dev/null +++ b/worker/util/pod_test.go @@ -0,0 +1,39 @@ +package util + +import ( + "encoding/json" + "io/ioutil" + "testing" + + "github.com/goodrain/rainbond/worker/server/pb" + corev1 "k8s.io/api/core/v1" +) + +func TestDescribePodStatus(t *testing.T) { + tests := []struct { + filename string + expstatus pb.PodStatus_Type + }{ + {filename: "testdata/insufficient-memory.json", expstatus: pb.PodStatus_SCHEDULING}, + {filename: "testdata/abnormal.json", expstatus: pb.PodStatus_ABNORMAL}, + {filename: "testdata/initiating.json", expstatus: pb.PodStatus_INITIATING}, + } + for idx := range tests { + tc := tests[idx] + jsonfile, err := ioutil.ReadFile(tc.filename) + if err != nil { + t.Errorf("failed to read file '%s': %v", tc.filename, err) + } + + var pod corev1.Pod + if err := json.Unmarshal(jsonfile, &pod); err != nil { + t.Fatalf("file: %s; failed to unmarshalling json: %v", tc.filename, err) + } + + podStatus := &pb.PodStatus{} + DescribePodStatus(&pod, podStatus) + if podStatus.Type != tc.expstatus { + t.Errorf("Expected %s for pod status type, but returned %s", tc.expstatus.String(), podStatus.Type.String()) + } + } +} diff --git a/worker/util/testdata/abnormal.json b/worker/util/testdata/abnormal.json new file mode 100644 index 000000000..171eed880 --- /dev/null +++ b/worker/util/testdata/abnormal.json @@ -0,0 +1,55 @@ +{ + "apiVersion": "v1", + "kind": "Pod", + "status": { + "conditions": [ + { + "lastProbeTime": null, + "lastTransitionTime": "2019-08-23T11:04:08Z", + "status": "True", + "type": "Initialized" + }, + { + "lastProbeTime": null, + "lastTransitionTime": "2019-08-23T13:07:21Z", + "message": "containers with unready status: [fe7b4268912c884718620f7a6a7e4f47]", + "reason": "ContainersNotReady", + "status": "False", + "type": "Ready" + }, + { + "lastProbeTime": null, + "lastTransitionTime": "2019-08-23T11:04:08Z", + "status": "True", + "type": "PodScheduled" + } + ], + "containerStatuses": [ + { + "containerID": "docker://10e359afca9fd975d800af0abfa989607309b8f239ff432db3cf48b65c8f0259", + "image": "nginx:latest", + "imageID": "docker-pullable://nginx@sha256:53ddb41e46de3d63376579acf46f9a41a8d7de33645db47a486de9769201fec9", + "lastState": { + "terminated": { + "containerID": "docker://10e359afca9fd975d800af0abfa989607309b8f239ff432db3cf48b65c8f0259", + "exitCode": 0, + "finishedAt": "2019-08-23T13:07:20Z", + "reason": "Completed", + "startedAt": "2019-08-23T13:07:15Z" + } + }, + "name": "fe7b4268912c884718620f7a6a7e4f47", + "ready": false, + "restartCount": 1, + "state": { + "waiting": { + "message": "Back-off 20s restarting failed container=fe7b4268912c884718620f7a6a7e4f47 pod=fe7b4268912c884718620f7a6a7e4f47-deployment-5bfcf6d5c9-rwzhb_6e22adb70c114b1d9a46d17d8146ba37(baaa186f-c595-11e9-a0de-468210d133b9)", + "reason": "CrashLoopBackOff" + } + } + } + ], + "phase": "Running", + "startTime": "2019-08-23T11:04:08Z" + } +} \ No newline at end of file diff --git a/worker/util/testdata/initiating.json b/worker/util/testdata/initiating.json new file mode 100644 index 000000000..09e8dee66 --- /dev/null +++ b/worker/util/testdata/initiating.json @@ -0,0 +1,77 @@ +{ + "apiVersion": "v1", + "kind": "Pod", + "status": { + "conditions": [ + { + "lastProbeTime": null, + "lastTransitionTime": "2019-08-23T13:24:22Z", + "message": "containers with incomplete status: [probe-mesh-c562a912c4bd7e335853]", + "reason": "ContainersNotInitialized", + "status": "False", + "type": "Initialized" + }, + { + "lastProbeTime": null, + "lastTransitionTime": "2019-08-23T13:24:22Z", + "message": "containers with unready status: [921c102de870c562a912c4bd7e335853 default-tcpmesh-c562a912c4bd7e335853]", + "reason": "ContainersNotReady", + "status": "False", + "type": "Ready" + }, + { + "lastProbeTime": null, + "lastTransitionTime": "2019-08-23T13:24:22Z", + "status": "True", + "type": "PodScheduled" + } + ], + "containerStatuses": [ + { + "image": "goodrain.me/nginx:20190823212118", + "imageID": "", + "lastState": {}, + "name": "921c102de870c562a912c4bd7e335853", + "ready": false, + "restartCount": 0, + "state": { + "waiting": { + "reason": "PodInitializing" + } + } + }, + { + "image": "goodrain.me/rbd-mesh-data-panel", + "imageID": "", + "lastState": {}, + "name": "default-tcpmesh-c562a912c4bd7e335853", + "ready": false, + "restartCount": 0, + "state": { + "waiting": { + "reason": "PodInitializing" + } + } + } + ], + "initContainerStatuses": [ + { + "containerID": "docker://aa61eb6a60ea01ffebdef1a0377b55988cce04e38f833d41dc04f48307c70357", + "image": "goodrain.me/rbd-init-probe:latest", + "imageID": "docker-pullable://goodrain.me/rbd-init-probe@sha256:797658b83de62c22578670d50c7e4cd8758ee0826d23eb9b6cec334eaf7fd48e", + "lastState": {}, + "name": "probe-mesh-c562a912c4bd7e335853", + "ready": false, + "restartCount": 0, + "state": { + "running": { + "startedAt": "2019-08-23T13:24:23Z" + } + } + } + ], + "phase": "Pending", + "qosClass": "Burstable", + "startTime": "2019-08-23T13:24:22Z" + } +} \ No newline at end of file diff --git a/worker/util/testdata/insufficient-memory.json b/worker/util/testdata/insufficient-memory.json new file mode 100644 index 000000000..a59a67a2e --- /dev/null +++ b/worker/util/testdata/insufficient-memory.json @@ -0,0 +1,18 @@ +{ + "apiVersion": "v1", + "kind": "Pod", + "status": { + "conditions": [ + { + "lastProbeTime": null, + "lastTransitionTime": "2019-08-23T10:44:34Z", + "message": "0/1 nodes are available: 1 Insufficient cpu, 1 Insufficient memory.", + "reason": "Unschedulable", + "status": "False", + "type": "PodScheduled" + } + ], + "phase": "Pending", + "qosClass": "Burstable" + } +} \ No newline at end of file