diff --git a/api/swagger-spec/v1.json b/api/swagger-spec/v1.json index 146bfb2eaf8..d3dacec1056 100644 --- a/api/swagger-spec/v1.json +++ b/api/swagger-spec/v1.json @@ -13137,6 +13137,18 @@ "status": { "type": "string", "description": "Status is the status of the condition. Can be True, False, Unknown. More info: http://releases.k8s.io/HEAD/docs/user-guide/pod-states.md#pod-conditions" + }, + "lastProbeTime": { + "type": "string" + }, + "lastTransitionTime": { + "type": "string" + }, + "reason": { + "type": "string" + }, + "message": { + "type": "string" } } }, diff --git a/pkg/api/deep_copy_generated.go b/pkg/api/deep_copy_generated.go index 0b050d8996e..a41067bf834 100644 --- a/pkg/api/deep_copy_generated.go +++ b/pkg/api/deep_copy_generated.go @@ -1344,6 +1344,14 @@ func deepCopy_api_PodAttachOptions(in PodAttachOptions, out *PodAttachOptions, c func deepCopy_api_PodCondition(in PodCondition, out *PodCondition, c *conversion.Cloner) error { out.Type = in.Type out.Status = in.Status + if err := deepCopy_unversioned_Time(in.LastProbeTime, &out.LastProbeTime, c); err != nil { + return err + } + if err := deepCopy_unversioned_Time(in.LastTransitionTime, &out.LastTransitionTime, c); err != nil { + return err + } + out.Reason = in.Reason + out.Message = in.Message return nil } diff --git a/pkg/api/resource_helpers.go b/pkg/api/resource_helpers.go index 2dee56870b8..0192eae028e 100644 --- a/pkg/api/resource_helpers.go +++ b/pkg/api/resource_helpers.go @@ -68,10 +68,22 @@ func GetExistingContainerStatus(statuses []ContainerStatus, name string) Contain // IsPodReady retruns true if a pod is ready; false otherwise. func IsPodReady(pod *Pod) bool { - for _, c := range pod.Status.Conditions { - if c.Type == PodReady && c.Status == ConditionTrue { - return true + return IsPodReadyConditionTrue(pod.Status) +} + +// IsPodReady retruns true if a pod is ready; false otherwise. +func IsPodReadyConditionTrue(status PodStatus) bool { + condition := GetPodReadyCondition(status) + return condition != nil && condition.Status == ConditionTrue +} + +// Extracts the pod ready condition from the given status and returns that. +// Returns nil if the condition is not present. +func GetPodReadyCondition(status PodStatus) *PodCondition { + for _, c := range status.Conditions { + if c.Type == PodReady { + return &c } } - return false + return nil } diff --git a/pkg/api/types.go b/pkg/api/types.go index 012f456ce1e..210b966099b 100644 --- a/pkg/api/types.go +++ b/pkg/api/types.go @@ -883,10 +883,13 @@ const ( PodReady PodConditionType = "Ready" ) -// TODO: add LastTransitionTime, Reason, Message to match NodeCondition api. type PodCondition struct { - Type PodConditionType `json:"type"` - Status ConditionStatus `json:"status"` + Type PodConditionType `json:"type"` + Status ConditionStatus `json:"status"` + LastProbeTime unversioned.Time `json:"lastProbeTime,omitempty"` + LastTransitionTime unversioned.Time `json:"lastTransitionTime,omitempty"` + Reason string `json:"reason,omitempty"` + Message string `json:"message,omitempty"` } // RestartPolicy describes how the container should be restarted. diff --git a/pkg/api/v1/conversion_generated.go b/pkg/api/v1/conversion_generated.go index ce87e44140d..e5db8b23505 100644 --- a/pkg/api/v1/conversion_generated.go +++ b/pkg/api/v1/conversion_generated.go @@ -1550,6 +1550,14 @@ func convert_api_PodCondition_To_v1_PodCondition(in *api.PodCondition, out *PodC } out.Type = PodConditionType(in.Type) out.Status = ConditionStatus(in.Status) + if err := s.Convert(&in.LastProbeTime, &out.LastProbeTime, 0); err != nil { + return err + } + if err := s.Convert(&in.LastTransitionTime, &out.LastTransitionTime, 0); err != nil { + return err + } + out.Reason = in.Reason + out.Message = in.Message return nil } @@ -3912,6 +3920,14 @@ func convert_v1_PodCondition_To_api_PodCondition(in *PodCondition, out *api.PodC } out.Type = api.PodConditionType(in.Type) out.Status = api.ConditionStatus(in.Status) + if err := s.Convert(&in.LastProbeTime, &out.LastProbeTime, 0); err != nil { + return err + } + if err := s.Convert(&in.LastTransitionTime, &out.LastTransitionTime, 0); err != nil { + return err + } + out.Reason = in.Reason + out.Message = in.Message return nil } diff --git a/pkg/api/v1/deep_copy_generated.go b/pkg/api/v1/deep_copy_generated.go index 9592cfa00e8..0815a4817a7 100644 --- a/pkg/api/v1/deep_copy_generated.go +++ b/pkg/api/v1/deep_copy_generated.go @@ -1364,6 +1364,14 @@ func deepCopy_v1_PodAttachOptions(in PodAttachOptions, out *PodAttachOptions, c func deepCopy_v1_PodCondition(in PodCondition, out *PodCondition, c *conversion.Cloner) error { out.Type = in.Type out.Status = in.Status + if err := deepCopy_unversioned_Time(in.LastProbeTime, &out.LastProbeTime, c); err != nil { + return err + } + if err := deepCopy_unversioned_Time(in.LastTransitionTime, &out.LastTransitionTime, c); err != nil { + return err + } + out.Reason = in.Reason + out.Message = in.Message return nil } diff --git a/pkg/api/v1/types.go b/pkg/api/v1/types.go index 62cebcd3d5e..210750c5009 100644 --- a/pkg/api/v1/types.go +++ b/pkg/api/v1/types.go @@ -1102,7 +1102,6 @@ const ( ) // PodCondition contains details for the current condition of this pod. -// TODO: add LastTransitionTime, Reason, Message to match NodeCondition api. type PodCondition struct { // Type is the type of the condition. // Currently only Ready. @@ -1112,6 +1111,14 @@ type PodCondition struct { // Can be True, False, Unknown. // More info: http://releases.k8s.io/HEAD/docs/user-guide/pod-states.md#pod-conditions Status ConditionStatus `json:"status"` + // Last time we probed the condition. + LastProbeTime unversioned.Time `json:"lastProbeTime,omitempty"` + // Last time the condition transitioned from one status to another. + LastTransitionTime unversioned.Time `json:"lastTransitionTime,omitempty"` + // Unique, one-word, CamelCase reason for the condition's last transition. + Reason string `json:"reason,omitempty"` + // Human-readable message indicating details about last transition. + Message string `json:"message,omitempty"` } // RestartPolicy describes how the container should be restarted. diff --git a/pkg/kubelet/kubelet.go b/pkg/kubelet/kubelet.go index 1728e4c63f1..8ee8e283e72 100644 --- a/pkg/kubelet/kubelet.go +++ b/pkg/kubelet/kubelet.go @@ -2126,29 +2126,65 @@ func GetPhase(spec *api.PodSpec, info []api.ContainerStatus) api.PodPhase { } } -// getPodReadyCondition returns ready condition if all containers in a pod are ready, else it returns an notReady condition. -func getPodReadyCondition(spec *api.PodSpec, statuses []api.ContainerStatus) []api.PodCondition { - ready := []api.PodCondition{{ - Type: api.PodReady, - Status: api.ConditionTrue, - }} - notReady := []api.PodCondition{{ - Type: api.PodReady, - Status: api.ConditionFalse, - }} - if statuses == nil { - return notReady +func readyPodCondition(isPodReady bool, reason, message string, existingStatus *api.PodStatus) []api.PodCondition { + currentTime := unversioned.Now() + condition := api.PodCondition{ + Type: api.PodReady, } - for _, container := range spec.Containers { - if containerStatus, ok := api.GetContainerStatus(statuses, container.Name); ok { - if !containerStatus.Ready { - return notReady - } + if isPodReady { + condition.Status = api.ConditionTrue + } else { + condition.Status = api.ConditionFalse + } + condition.LastProbeTime = currentTime + condition.Reason = reason + condition.Message = message + if existingStatus != nil { + if api.IsPodReadyConditionTrue(*existingStatus) != isPodReady { + // condition has transitioned, update transition time. + condition.LastTransitionTime = currentTime } else { - return notReady + // retain the existing transition time. + existingCondition := api.GetPodReadyCondition(*existingStatus) + if existingCondition != nil { + condition.LastTransitionTime = existingCondition.LastTransitionTime + } } } - return ready + return []api.PodCondition{condition} +} + +// getPodReadyCondition returns ready condition if all containers in a pod are ready, else it returns an unready condition. +func getPodReadyCondition(spec *api.PodSpec, containerStatuses []api.ContainerStatus, existingStatus *api.PodStatus) []api.PodCondition { + // Find if all containers are ready or not. + if containerStatuses == nil { + return readyPodCondition(false, "UnknownContainerStatuses", "", existingStatus) + } + unknownContainers := []string{} + unreadyContainers := []string{} + for _, container := range spec.Containers { + if containerStatus, ok := api.GetContainerStatus(containerStatuses, container.Name); ok { + if !containerStatus.Ready { + unreadyContainers = append(unreadyContainers, container.Name) + } + } else { + unknownContainers = append(unknownContainers, container.Name) + } + } + unreadyMessages := []string{} + if len(unknownContainers) > 0 { + unreadyMessages = append(unreadyMessages, fmt.Sprintf("containers with unknown status: %s", unknownContainers)) + } + if len(unreadyContainers) > 0 { + unreadyMessages = append(unreadyMessages, fmt.Sprintf("containers with unready status: %s", unreadyContainers)) + } + unreadyMessage := strings.Join(unreadyMessages, ", ") + if unreadyMessage != "" { + // return unready status. + return readyPodCondition(false, fmt.Sprint("ContainersNotReady"), unreadyMessage, existingStatus) + } + // return ready status. + return readyPodCondition(true, "", "", existingStatus) } // By passing the pod directly, this method avoids pod lookup, which requires @@ -2162,8 +2198,8 @@ func (kl *Kubelet) generatePodStatus(pod *api.Pod) (api.PodStatus, error) { podFullName := kubecontainer.GetPodFullName(pod) glog.V(3).Infof("Generating status for %q", podFullName) - - if existingStatus, ok := kl.statusManager.GetPodStatus(pod.UID); ok { + existingStatus, hasExistingStatus := kl.statusManager.GetPodStatus(pod.UID) + if hasExistingStatus { // This is a hacky fix to ensure container restart counts increment // monotonically. Normally, we should not modify given pod. In this // case, we check if there are cached status for this pod, and update @@ -2215,8 +2251,11 @@ func (kl *Kubelet) generatePodStatus(pod *api.Pod) (api.PodStatus, error) { } } } - - podStatus.Conditions = append(podStatus.Conditions, getPodReadyCondition(spec, podStatus.ContainerStatuses)...) + if hasExistingStatus { + podStatus.Conditions = append(podStatus.Conditions, getPodReadyCondition(spec, podStatus.ContainerStatuses, &existingStatus)...) + } else { + podStatus.Conditions = append(podStatus.Conditions, getPodReadyCondition(spec, podStatus.ContainerStatuses, nil)...) + } if !kl.standaloneMode { hostIP, err := kl.nodeManager.GetHostIP() diff --git a/pkg/kubelet/kubelet_test.go b/pkg/kubelet/kubelet_test.go index fd1882af0cf..088338c9310 100644 --- a/pkg/kubelet/kubelet_test.go +++ b/pkg/kubelet/kubelet_test.go @@ -1732,30 +1732,47 @@ func getNotReadyStatus(cName string) api.ContainerStatus { Ready: false, } } +func getReadyCondition(status api.ConditionStatus, transitionTime unversioned.Time, reason, message string) []api.PodCondition { + return []api.PodCondition{{ + Type: api.PodReady, + Status: status, + LastTransitionTime: transitionTime, + Reason: reason, + Message: message, + }} +} func TestGetPodReadyCondition(t *testing.T) { - ready := []api.PodCondition{{ - Type: api.PodReady, - Status: api.ConditionTrue, - }} - notReady := []api.PodCondition{{ - Type: api.PodReady, - Status: api.ConditionFalse, - }} + transitionTime := unversioned.Now() tests := []struct { - spec *api.PodSpec - info []api.ContainerStatus - expected []api.PodCondition + spec *api.PodSpec + containerStatuses []api.ContainerStatus + existingStatus *api.PodStatus + expected []api.PodCondition + clearTimestamp bool }{ { - spec: nil, - info: nil, - expected: notReady, + spec: nil, + containerStatuses: nil, + existingStatus: nil, + expected: getReadyCondition(api.ConditionFalse, transitionTime, "UnknownContainerStatuses", ""), + clearTimestamp: true, }, { - spec: &api.PodSpec{}, - info: []api.ContainerStatus{}, - expected: ready, + spec: nil, + containerStatuses: nil, + existingStatus: &api.PodStatus{ + Conditions: getReadyCondition(api.ConditionFalse, transitionTime, "", ""), + }, + expected: getReadyCondition(api.ConditionFalse, transitionTime, "UnknownContainerStatuses", ""), + clearTimestamp: false, + }, + { + spec: &api.PodSpec{}, + containerStatuses: []api.ContainerStatus{}, + existingStatus: nil, + expected: getReadyCondition(api.ConditionTrue, transitionTime, "", ""), + clearTimestamp: true, }, { spec: &api.PodSpec{ @@ -1763,8 +1780,10 @@ func TestGetPodReadyCondition(t *testing.T) { {Name: "1234"}, }, }, - info: []api.ContainerStatus{}, - expected: notReady, + containerStatuses: []api.ContainerStatus{}, + existingStatus: nil, + expected: getReadyCondition(api.ConditionFalse, transitionTime, "ContainersNotReady", "containers with unknown status: [1234]"), + clearTimestamp: true, }, { spec: &api.PodSpec{ @@ -1772,10 +1791,12 @@ func TestGetPodReadyCondition(t *testing.T) { {Name: "1234"}, }, }, - info: []api.ContainerStatus{ + containerStatuses: []api.ContainerStatus{ getReadyStatus("1234"), }, - expected: ready, + existingStatus: nil, + expected: getReadyCondition(api.ConditionTrue, transitionTime, "", ""), + clearTimestamp: true, }, { spec: &api.PodSpec{ @@ -1784,11 +1805,13 @@ func TestGetPodReadyCondition(t *testing.T) { {Name: "5678"}, }, }, - info: []api.ContainerStatus{ + containerStatuses: []api.ContainerStatus{ getReadyStatus("1234"), getReadyStatus("5678"), }, - expected: ready, + existingStatus: nil, + expected: getReadyCondition(api.ConditionTrue, transitionTime, "", ""), + clearTimestamp: true, }, { spec: &api.PodSpec{ @@ -1797,10 +1820,12 @@ func TestGetPodReadyCondition(t *testing.T) { {Name: "5678"}, }, }, - info: []api.ContainerStatus{ + containerStatuses: []api.ContainerStatus{ getReadyStatus("1234"), }, - expected: notReady, + existingStatus: nil, + expected: getReadyCondition(api.ConditionFalse, transitionTime, "ContainersNotReady", "containers with unknown status: [5678]"), + clearTimestamp: true, }, { spec: &api.PodSpec{ @@ -1809,16 +1834,22 @@ func TestGetPodReadyCondition(t *testing.T) { {Name: "5678"}, }, }, - info: []api.ContainerStatus{ + containerStatuses: []api.ContainerStatus{ getReadyStatus("1234"), getNotReadyStatus("5678"), }, - expected: notReady, + expected: getReadyCondition(api.ConditionFalse, transitionTime, "ContainersNotReady", "containers with unready status: [5678]"), + clearTimestamp: true, }, } for i, test := range tests { - condition := getPodReadyCondition(test.spec, test.info) + condition := getPodReadyCondition(test.spec, test.containerStatuses, test.existingStatus) + if test.clearTimestamp { + condition[0].LastTransitionTime = transitionTime + test.expected[0].LastTransitionTime = transitionTime + } + condition[0].LastProbeTime = unversioned.Time{} if !reflect.DeepEqual(condition, test.expected) { t.Errorf("On test case %v, expected:\n%+v\ngot\n%+v\n", i, test.expected, condition) }