未验证 提交 bd29ee0d 编写于 作者: S Sebastian Florek 提交者: GitHub

Sync pod status logic with kubectl and simplify API (#5660)

* Sync pod status logic with kubectl and simplify API

* Fix unit tests
上级 819a4a24
......@@ -15,12 +15,15 @@
package pod
import (
"fmt"
v1 "k8s.io/api/core/v1"
"github.com/kubernetes/dashboard/src/app/backend/api"
metricapi "github.com/kubernetes/dashboard/src/app/backend/integration/metric/api"
"github.com/kubernetes/dashboard/src/app/backend/resource/common"
"github.com/kubernetes/dashboard/src/app/backend/resource/dataselect"
"github.com/kubernetes/dashboard/src/app/backend/resource/event"
v1 "k8s.io/api/core/v1"
)
// getRestartCount return the restart count of given pod (total number of its containers restarts).
......@@ -32,18 +35,98 @@ func getRestartCount(pod v1.Pod) int32 {
return restartCount
}
// getPodStatus returns a PodStatus object containing a summary of the pod's status.
func getPodStatus(pod v1.Pod, warnings []common.Event) PodStatus {
var states []v1.ContainerState
for _, containerStatus := range pod.Status.ContainerStatuses {
states = append(states, containerStatus.State)
// getPodStatus returns status string calculated based on the same logic as kubectl
// Base code: https://github.com/kubernetes/kubernetes/blob/master/pkg/printers/internalversion/printers.go#L734
func getPodStatus(pod v1.Pod) string {
restarts := 0
readyContainers := 0
reason := string(pod.Status.Phase)
if pod.Status.Reason != "" {
reason = pod.Status.Reason
}
return PodStatus{
Status: string(getPodStatusPhase(pod, warnings)),
PodPhase: pod.Status.Phase,
ContainerStates: states,
initializing := false
for i := range pod.Status.InitContainerStatuses {
container := pod.Status.InitContainerStatuses[i]
restarts += int(container.RestartCount)
switch {
case container.State.Terminated != nil && container.State.Terminated.ExitCode == 0:
continue
case container.State.Terminated != nil:
// initialization is failed
if len(container.State.Terminated.Reason) == 0 {
if container.State.Terminated.Signal != 0 {
reason = fmt.Sprintf("Init: Signal %d", container.State.Terminated.Signal)
} else {
reason = fmt.Sprintf("Init: ExitCode %d", container.State.Terminated.ExitCode)
}
} else {
reason = "Init:" + container.State.Terminated.Reason
}
initializing = true
case container.State.Waiting != nil && len(container.State.Waiting.Reason) > 0 && container.State.Waiting.Reason != "PodInitializing":
reason = fmt.Sprintf("Init: %s", container.State.Waiting.Reason)
initializing = true
default:
reason = fmt.Sprintf("Init: %d/%d", i, len(pod.Spec.InitContainers))
initializing = true
}
break
}
if !initializing {
restarts = 0
hasRunning := false
for i := len(pod.Status.ContainerStatuses) - 1; i >= 0; i-- {
container := pod.Status.ContainerStatuses[i]
restarts += int(container.RestartCount)
if container.State.Waiting != nil && container.State.Waiting.Reason != "" {
reason = container.State.Waiting.Reason
} else if container.State.Terminated != nil && container.State.Terminated.Reason != "" {
reason = container.State.Terminated.Reason
} else if container.State.Terminated != nil && container.State.Terminated.Reason == "" {
if container.State.Terminated.Signal != 0 {
reason = fmt.Sprintf("Signal: %d", container.State.Terminated.Signal)
} else {
reason = fmt.Sprintf("ExitCode: %d", container.State.Terminated.ExitCode)
}
} else if container.Ready && container.State.Running != nil {
hasRunning = true
readyContainers++
}
}
// change pod status back to "Running" if there is at least one container still reporting as "Running" status
if reason == "Completed" && hasRunning {
if hasPodReadyCondition(pod.Status.Conditions) {
reason = string(v1.PodRunning)
} else {
reason = "NotReady"
}
}
}
if pod.DeletionTimestamp != nil && pod.Status.Reason == "NodeLost" {
reason = string(v1.PodUnknown)
} else if pod.DeletionTimestamp != nil {
reason = "Terminating"
}
if len(reason) == 0 {
reason = string(v1.PodUnknown)
}
return reason
}
func hasPodReadyCondition(conditions []v1.PodCondition) bool {
for _, condition := range conditions {
if condition.Type == v1.PodReady && condition.Status == v1.ConditionTrue {
return true
}
}
return false
}
// getPodStatusPhase returns one of four pod status phases (Pending, Running, Succeeded, Failed, Unknown, Terminating)
......
......@@ -40,10 +40,7 @@ func TestToPodPodStatusFailed(t *testing.T) {
expected := Pod{
TypeMeta: api.TypeMeta{Kind: api.ResourceKindPod},
PodStatus: PodStatus{
Status: string(v1.PodFailed),
PodPhase: v1.PodFailed,
},
Status: string(v1.PodFailed),
Warnings: []common.Event{},
}
......@@ -70,10 +67,7 @@ func TestToPodPodStatusSucceeded(t *testing.T) {
expected := Pod{
TypeMeta: api.TypeMeta{Kind: api.ResourceKindPod},
PodStatus: PodStatus{
Status: string(v1.PodSucceeded),
PodPhase: v1.PodSucceeded,
},
Status: string(v1.PodSucceeded),
Warnings: []common.Event{},
}
......@@ -104,10 +98,7 @@ func TestToPodPodStatusRunning(t *testing.T) {
expected := Pod{
TypeMeta: api.TypeMeta{Kind: api.ResourceKindPod},
PodStatus: PodStatus{
Status: string(v1.PodRunning),
PodPhase: v1.PodRunning,
},
Status: string(v1.PodRunning),
Warnings: []common.Event{},
}
......@@ -134,10 +125,7 @@ func TestToPodPodStatusPending(t *testing.T) {
expected := Pod{
TypeMeta: api.TypeMeta{Kind: api.ResourceKindPod},
PodStatus: PodStatus{
Status: string(v1.PodPending),
PodPhase: v1.PodPending,
},
Status: string(v1.PodPending),
Warnings: []common.Event{},
}
......@@ -157,14 +145,14 @@ func TestToPodContainerStates(t *testing.T) {
{
State: v1.ContainerState{
Terminated: &v1.ContainerStateTerminated{
Reason: "Terminated Test Reason",
Reason: "Terminated",
},
},
},
{
State: v1.ContainerState{
Waiting: &v1.ContainerStateWaiting{
Reason: "Waiting Test Reason",
Reason: "Waiting",
},
},
},
......@@ -174,22 +162,7 @@ func TestToPodContainerStates(t *testing.T) {
expected := Pod{
TypeMeta: api.TypeMeta{Kind: api.ResourceKindPod},
PodStatus: PodStatus{
PodPhase: v1.PodRunning,
Status: string(v1.PodPending),
ContainerStates: []v1.ContainerState{
{
Terminated: &v1.ContainerStateTerminated{
Reason: "Terminated Test Reason",
},
},
{
Waiting: &v1.ContainerStateWaiting{
Reason: "Waiting Test Reason",
},
},
},
},
Status: "Terminated",
Warnings: []common.Event{},
}
......@@ -211,9 +184,7 @@ func TestToPod(t *testing.T) {
pod: &v1.Pod{}, metrics: &MetricsByPod{},
expected: Pod{
TypeMeta: api.TypeMeta{Kind: api.ResourceKindPod},
PodStatus: PodStatus{
Status: string(v1.PodPending),
},
Status: string(v1.PodUnknown),
Warnings: []common.Event{},
},
}, {
......@@ -228,9 +199,7 @@ func TestToPod(t *testing.T) {
Name: "test-pod",
Namespace: "test-namespace",
},
PodStatus: PodStatus{
Status: string(v1.PodPending),
},
Status: string(v1.PodUnknown),
Warnings: []common.Event{},
},
},
......
......@@ -24,6 +24,12 @@ import (
"k8s.io/apimachinery/pkg/runtime/schema"
v1 "k8s.io/api/core/v1"
res "k8s.io/apimachinery/pkg/api/resource"
metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/kubernetes"
"github.com/kubernetes/dashboard/src/app/backend/api"
errorHandler "github.com/kubernetes/dashboard/src/app/backend/errors"
metricapi "github.com/kubernetes/dashboard/src/app/backend/integration/metric/api"
......@@ -31,18 +37,13 @@ import (
"github.com/kubernetes/dashboard/src/app/backend/resource/controller"
"github.com/kubernetes/dashboard/src/app/backend/resource/dataselect"
"github.com/kubernetes/dashboard/src/app/backend/resource/persistentvolumeclaim"
v1 "k8s.io/api/core/v1"
res "k8s.io/apimachinery/pkg/api/resource"
metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/kubernetes"
)
// PodDetail is a presentation layer view of Kubernetes Pod resource.
type PodDetail struct {
ObjectMeta api.ObjectMeta `json:"objectMeta"`
TypeMeta api.TypeMeta `json:"typeMeta"`
PodPhase v1.PodPhase `json:"podPhase"`
PodPhase string `json:"podPhase"`
PodIP string `json:"podIP"`
NodeName string `json:"nodeName"`
RestartCount int32 `json:"restartCount"`
......@@ -213,7 +214,7 @@ func toPodDetail(pod *v1.Pod, metrics []metricapi.Metric, configMaps *v1.ConfigM
return PodDetail{
ObjectMeta: api.NewObjectMeta(pod.ObjectMeta),
TypeMeta: api.NewTypeMeta(api.ResourceKindPod),
PodPhase: pod.Status.Phase,
PodPhase: getPodStatus(*pod),
PodIP: pod.Status.PodIP,
RestartCount: getRestartCount(*pod),
QOSClass: string(pod.Status.QOSClass),
......
......@@ -43,6 +43,7 @@ func TestGetPodDetail(t *testing.T) {
}}}},
expected: &PodDetail{
TypeMeta: api.TypeMeta{Kind: api.ResourceKindPod},
PodPhase: string(v1.PodUnknown),
ObjectMeta: api.ObjectMeta{
Name: "test-pod",
Namespace: "test-namespace",
......
......@@ -55,10 +55,10 @@ type Pod struct {
ObjectMeta api.ObjectMeta `json:"objectMeta"`
TypeMeta api.TypeMeta `json:"typeMeta"`
// More info on pod status
PodStatus PodStatus `json:"podStatus"`
// Status determined based on the same logic as kubectl.
Status string `json:"status"`
// Count of containers restarts.
// RestartCount of containers restarts.
RestartCount int32 `json:"restartCount"`
// Pod metrics.
......@@ -67,7 +67,7 @@ type Pod struct {
// Pod warning events
Warnings []common.Event `json:"warnings"`
// Name of the Node this Pod runs on.
// NodeName of the Node this Pod runs on.
NodeName string `json:"nodeName"`
}
......@@ -154,7 +154,7 @@ func toPod(pod *v1.Pod, metrics *MetricsByPod, warnings []common.Event) Pod {
ObjectMeta: api.NewObjectMeta(pod.ObjectMeta),
TypeMeta: api.NewTypeMeta(api.ResourceKindPod),
Warnings: warnings,
PodStatus: getPodStatus(*pod, warnings),
Status: getPodStatus(*pod),
RestartCount: getRestartCount(*pod),
NodeName: pod.Spec.NodeName,
}
......
......@@ -102,9 +102,9 @@ func TestGetPodListFromChannels(t *testing.T) {
Labels: map[string]string{"key": "value"},
CreationTimestamp: metav1.Unix(111, 222),
},
TypeMeta: api.TypeMeta{Kind: api.ResourceKindPod},
PodStatus: pod.PodStatus{Status: string(v1.PodPending)},
Warnings: []common.Event{},
TypeMeta: api.TypeMeta{Kind: api.ResourceKindPod},
Status: string(v1.PodUnknown),
Warnings: []common.Event{},
}},
Errors: []error{},
},
......
......@@ -57,9 +57,9 @@ func TestGetServicePods(t *testing.T) {
Name: "pod-1",
UID: "test-uid",
Namespace: "ns-1"},
TypeMeta: api.TypeMeta{Kind: api.ResourceKindPod},
PodStatus: pod.PodStatus{Status: string(v1.PodPending)},
Warnings: []common.Event{},
TypeMeta: api.TypeMeta{Kind: api.ResourceKindPod},
Status: string(v1.PodUnknown),
Warnings: []common.Event{},
},
},
Errors: []error{},
......
......@@ -108,6 +108,10 @@
background-color: lighten(map-get($colors, indicator-error), 33%);
}
.kd-help {
color: $muted;
}
.kd-muted {
color: $muted;
}
......
......@@ -150,9 +150,9 @@ export class NamespaceSelectorComponent implements OnInit, OnDestroy {
private loadNamespaces_(): void {
this.namespaceUpdate_
.pipe(takeUntil(this.unsubscribe_))
.pipe(startWith({}))
.pipe(switchMap(() => this.namespace_.get(this.endpoint_.list())))
.pipe(takeUntil(this.unsubscribe_))
.subscribe(
namespaceList => {
this.namespaces = namespaceList.namespaces.map(n => n.objectMeta.name);
......
......@@ -16,7 +16,6 @@ import {HttpParams} from '@angular/common/http';
import {ChangeDetectionStrategy, ChangeDetectorRef, Component, Input} from '@angular/core';
import {Event, Metric, Pod, PodList} from '@api/backendapi';
import {Observable} from 'rxjs';
import {ResourceListWithStatuses} from '../../../resources/list';
import {NotificationsService} from '../../../services/global/notifications';
import {EndpointManager, Resource} from '../../../services/resource/endpoint';
......@@ -24,6 +23,19 @@ import {NamespacedResourceService} from '../../../services/resource/resource';
import {MenuComponent} from '../../list/column/menu/component';
import {ListGroupIdentifier, ListIdentifier} from '../groupids';
enum Status {
Pending = 'Pending',
ContainerCreating = 'ContainerCreating',
Running = 'Running',
Succeeded = 'Succeeded',
Completed = 'Completed',
Failed = 'Failed',
Unknown = 'Unknown',
NotReady = 'NotReady',
Terminating = 'Terminating',
Error = 'Error',
}
@Component({
selector: 'kd-pod-list',
templateUrl: './template.html',
......@@ -65,23 +77,21 @@ export class PodListComponent extends ResourceListWithStatuses<PodList, Pod> {
}
isInErrorState(resource: Pod): boolean {
return resource.podStatus.status === 'Failed';
return (
[Status.Failed, Status.Error].some(s => resource.status === s) ||
(resource.warnings.length > 0 &&
![Status.Pending, Status.NotReady, Status.Terminating, Status.Unknown, Status.ContainerCreating].some(
s => resource.status === s
))
);
}
isInPendingState(resource: Pod): boolean {
return resource.podStatus.status === 'Pending';
return [Status.Pending, Status.ContainerCreating].some(s => resource.status === s);
}
isInSuccessState(resource: Pod): boolean {
return resource.podStatus.status === 'Succeeded' || resource.podStatus.status === 'Running';
}
protected getDisplayColumns(): string[] {
return ['statusicon', 'name', 'labels', 'node', 'status', 'restarts', 'cpu', 'mem', 'created'];
}
private shouldShowNamespaceColumn_(): boolean {
return this.namespaceService_.areMultipleNamespacesSelected();
return [Status.Succeeded, Status.Running, Status.Completed].some(s => resource.status === s);
}
hasErrors(pod: Pod): boolean {
......@@ -93,43 +103,14 @@ export class PodListComponent extends ResourceListWithStatuses<PodList, Pod> {
}
getDisplayStatus(pod: Pod): string {
// See kubectl printers.go for logic in kubectl:
// https://github.com/kubernetes/kubernetes/blob/39857f486511bd8db81868185674e8b674b1aeb9/pkg/printers/internalversion/printers.go
let msgState = 'running';
let reason = undefined;
// Init container statuses are currently not taken into account.
// However, init containers with errors will still show as failed because of warnings.
if (pod.podStatus.containerStates) {
// Container states array may be null when no containers have started yet.
for (let i = pod.podStatus.containerStates.length - 1; i >= 0; i--) {
const 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}`;
}
}
}
}
}
if (msgState === 'waiting') {
return `Waiting: ${reason}`;
}
if (msgState === 'terminated') {
return `Terminated: ${reason}`;
}
return pod.podStatus.podPhase;
return pod.status;
}
protected getDisplayColumns(): string[] {
return ['statusicon', 'name', 'labels', 'node', 'status', 'restarts', 'cpu', 'mem', 'created'];
}
private shouldShowNamespaceColumn_(): boolean {
return this.namespaceService_.areMultipleNamespacesSelected();
}
}
......@@ -366,7 +366,7 @@ export abstract class ResourceListWithStatuses<T extends ResourceList, R extends
private lastHash_: number;
private readonly unknownStatus: StatusIcon = {
iconName: 'help',
iconClass: {'': true},
iconClass: {'kd-help': true},
};
protected icon = IconName;
......
......@@ -14,7 +14,7 @@
import {HttpClientTestingModule, HttpTestingController} from '@angular/common/http/testing';
import {Component, CUSTOM_ELEMENTS_SCHEMA} from '@angular/core';
import {waitForAsync, TestBed} from '@angular/core/testing';
import {TestBed, waitForAsync} from '@angular/core/testing';
import {MatCardModule} from '@angular/material/card';
import {MatChipsModule} from '@angular/material/chips';
import {MatDialogModule} from '@angular/material/dialog';
......@@ -74,11 +74,7 @@ class MaxiTestComponent {
podList: {
pods: [
{
podStatus: {
podPhase: 'phase1',
status: 'Ready',
containerStates: [{waiting: {reason: 'Still starting'}}],
},
status: 'Running',
restartCount: 1,
metrics: {
cpuUsage: 10,
......
......@@ -347,7 +347,7 @@ export interface PersistentVolumeClaim extends Resource {
}
export interface Pod extends Resource {
podStatus: PodStatus;
status: string;
podIP?: string;
restartCount: number;
qosClass?: string;
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册