diff --git a/pkg/kubelet/kubelet_pods.go b/pkg/kubelet/kubelet_pods.go index 4b5e59f48f7..115161003cf 100644 --- a/pkg/kubelet/kubelet_pods.go +++ b/pkg/kubelet/kubelet_pods.go @@ -1447,6 +1447,20 @@ func (kl *Kubelet) generateAPIPodStatus(pod *v1.Pod, podStatus *kubecontainer.Po allStatus := append(append([]v1.ContainerStatus{}, s.ContainerStatuses...), s.InitContainerStatuses...) s.Phase = getPhase(&pod.Spec, allStatus) klog.V(4).InfoS("Got phase for pod", "pod", klog.KObj(pod), "oldPhase", oldPodStatus.Phase, "phase", s.Phase) + + // Perform a three-way merge between the statuses from the status manager, + // runtime, and generated status to ensure terminal status is correctly set. + if s.Phase != v1.PodFailed && s.Phase != v1.PodSucceeded { + switch { + case oldPodStatus.Phase == v1.PodFailed || oldPodStatus.Phase == v1.PodSucceeded: + klog.V(4).InfoS("Status manager phase was terminal, updating phase to match", "pod", klog.KObj(pod), "phase", oldPodStatus.Phase) + s.Phase = oldPodStatus.Phase + case pod.Status.Phase == v1.PodFailed || pod.Status.Phase == v1.PodSucceeded: + klog.V(4).InfoS("API phase was terminal, updating phase to match", "pod", klog.KObj(pod), "phase", pod.Status.Phase) + s.Phase = pod.Status.Phase + } + } + if s.Phase == oldPodStatus.Phase { // preserve the reason and message which is associated with the phase s.Reason = oldPodStatus.Reason diff --git a/pkg/kubelet/kubelet_pods_test.go b/pkg/kubelet/kubelet_pods_test.go index 655cb04d115..491a0a284b9 100644 --- a/pkg/kubelet/kubelet_pods_test.go +++ b/pkg/kubelet/kubelet_pods_test.go @@ -2595,6 +2595,95 @@ func Test_generateAPIPodStatus(t *testing.T) { }, }, }, + { + name: "terminal phase from previous status must remain terminal, restartAlways", + pod: &v1.Pod{ + Spec: desiredState, + Status: v1.PodStatus{ + Phase: v1.PodRunning, + ContainerStatuses: []v1.ContainerStatus{ + runningState("containerA"), + runningState("containerB"), + }, + }, + }, + currentStatus: &kubecontainer.PodStatus{}, + previousStatus: v1.PodStatus{ + Phase: v1.PodSucceeded, + ContainerStatuses: []v1.ContainerStatus{ + runningState("containerA"), + runningState("containerB"), + }, + // Reason and message should be preserved + Reason: "Test", + Message: "test", + }, + expected: v1.PodStatus{ + Phase: v1.PodSucceeded, + HostIP: "127.0.0.1", + QOSClass: v1.PodQOSBestEffort, + Conditions: []v1.PodCondition{ + {Type: v1.PodInitialized, Status: v1.ConditionTrue, Reason: "PodCompleted"}, + {Type: v1.PodReady, Status: v1.ConditionFalse, Reason: "PodCompleted"}, + {Type: v1.ContainersReady, Status: v1.ConditionFalse, Reason: "PodCompleted"}, + {Type: v1.PodScheduled, Status: v1.ConditionTrue}, + }, + ContainerStatuses: []v1.ContainerStatus{ + ready(waitingWithLastTerminationUnknown("containerA", 1)), + ready(waitingWithLastTerminationUnknown("containerB", 1)), + }, + Reason: "Test", + Message: "test", + }, + }, + { + name: "terminal phase from previous status must remain terminal, restartNever", + pod: &v1.Pod{ + Spec: v1.PodSpec{ + NodeName: "machine", + Containers: []v1.Container{ + {Name: "containerA"}, + {Name: "containerB"}, + }, + RestartPolicy: v1.RestartPolicyNever, + }, + Status: v1.PodStatus{ + Phase: v1.PodRunning, + ContainerStatuses: []v1.ContainerStatus{ + runningState("containerA"), + runningState("containerB"), + }, + }, + }, + currentStatus: &kubecontainer.PodStatus{}, + previousStatus: v1.PodStatus{ + Phase: v1.PodSucceeded, + ContainerStatuses: []v1.ContainerStatus{ + succeededState("containerA"), + succeededState("containerB"), + }, + // Reason and message should be preserved + Reason: "Test", + Message: "test", + }, + expected: v1.PodStatus{ + Phase: v1.PodSucceeded, + HostIP: "127.0.0.1", + QOSClass: v1.PodQOSBestEffort, + Conditions: []v1.PodCondition{ + {Type: v1.PodInitialized, Status: v1.ConditionTrue, Reason: "PodCompleted"}, + {Type: v1.PodReady, Status: v1.ConditionFalse, Reason: "PodCompleted"}, + {Type: v1.ContainersReady, Status: v1.ConditionFalse, Reason: "PodCompleted"}, + {Type: v1.PodScheduled, Status: v1.ConditionTrue}, + }, + ContainerStatuses: []v1.ContainerStatus{ + ready(succeededState("containerA")), + ready(succeededState("containerB")), + }, + Reason: "Test", + Message: "test", + }, + }, { name: "running can revert to pending", pod: &v1.Pod{