| /* |
| Copyright 2014 The Kubernetes Authors. |
| |
| Licensed under the Apache License, Version 2.0 (the "License"); |
| you may not use this file except in compliance with the License. |
| You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| */ |
| |
| package podutils |
| |
| import ( |
| "time" |
| |
| corev1 "k8s.io/api/core/v1" |
| metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" |
| "k8s.io/client-go/util/integer" |
| ) |
| |
| // IsPodAvailable returns true if a pod is available; false otherwise. |
| // Precondition for an available pod is that it must be ready. On top |
| // of that, there are two cases when a pod can be considered available: |
| // 1. minReadySeconds == 0, or |
| // 2. LastTransitionTime (is set) + minReadySeconds < current time |
| func IsPodAvailable(pod *corev1.Pod, minReadySeconds int32, now metav1.Time) bool { |
| if !IsPodReady(pod) { |
| return false |
| } |
| |
| c := getPodReadyCondition(pod.Status) |
| minReadySecondsDuration := time.Duration(minReadySeconds) * time.Second |
| if minReadySeconds == 0 || !c.LastTransitionTime.IsZero() && c.LastTransitionTime.Add(minReadySecondsDuration).Before(now.Time) { |
| return true |
| } |
| return false |
| } |
| |
| // IsPodReady returns true if a pod is ready; false otherwise. |
| func IsPodReady(pod *corev1.Pod) bool { |
| return isPodReadyConditionTrue(pod.Status) |
| } |
| |
| // IsPodReadyConditionTrue returns true if a pod is ready; false otherwise. |
| func isPodReadyConditionTrue(status corev1.PodStatus) bool { |
| condition := getPodReadyCondition(status) |
| return condition != nil && condition.Status == corev1.ConditionTrue |
| } |
| |
| // GetPodReadyCondition extracts the pod ready condition from the given status and returns that. |
| // Returns nil if the condition is not present. |
| func getPodReadyCondition(status corev1.PodStatus) *corev1.PodCondition { |
| _, condition := getPodCondition(&status, corev1.PodReady) |
| return condition |
| } |
| |
| // GetPodCondition extracts the provided condition from the given status and returns that. |
| // Returns nil and -1 if the condition is not present, and the index of the located condition. |
| func getPodCondition(status *corev1.PodStatus, conditionType corev1.PodConditionType) (int, *corev1.PodCondition) { |
| if status == nil { |
| return -1, nil |
| } |
| return getPodConditionFromList(status.Conditions, conditionType) |
| } |
| |
| // GetPodConditionFromList extracts the provided condition from the given list of condition and |
| // returns the index of the condition and the condition. Returns -1 and nil if the condition is not present. |
| func getPodConditionFromList(conditions []corev1.PodCondition, conditionType corev1.PodConditionType) (int, *corev1.PodCondition) { |
| if conditions == nil { |
| return -1, nil |
| } |
| for i := range conditions { |
| if conditions[i].Type == conditionType { |
| return i, &conditions[i] |
| } |
| } |
| return -1, nil |
| } |
| |
| // ByLogging allows custom sorting of pods so the best one can be picked for getting its logs. |
| type ByLogging []*corev1.Pod |
| |
| func (s ByLogging) Len() int { return len(s) } |
| func (s ByLogging) Swap(i, j int) { s[i], s[j] = s[j], s[i] } |
| |
| func (s ByLogging) Less(i, j int) bool { |
| // 1. assigned < unassigned |
| if s[i].Spec.NodeName != s[j].Spec.NodeName && (len(s[i].Spec.NodeName) == 0 || len(s[j].Spec.NodeName) == 0) { |
| return len(s[i].Spec.NodeName) > 0 |
| } |
| // 2. PodRunning < PodUnknown < PodPending |
| m := map[corev1.PodPhase]int{corev1.PodRunning: 0, corev1.PodUnknown: 1, corev1.PodPending: 2} |
| if m[s[i].Status.Phase] != m[s[j].Status.Phase] { |
| return m[s[i].Status.Phase] < m[s[j].Status.Phase] |
| } |
| // 3. ready < not ready |
| if IsPodReady(s[i]) != IsPodReady(s[j]) { |
| return IsPodReady(s[i]) |
| } |
| // TODO: take availability into account when we push minReadySeconds information from deployment into pods, |
| // see https://github.com/kubernetes/kubernetes/issues/22065 |
| // 4. Been ready for more time < less time < empty time |
| if IsPodReady(s[i]) && IsPodReady(s[j]) && !podReadyTime(s[i]).Equal(podReadyTime(s[j])) { |
| return afterOrZero(podReadyTime(s[j]), podReadyTime(s[i])) |
| } |
| // 5. Pods with containers with higher restart counts < lower restart counts |
| if maxContainerRestarts(s[i]) != maxContainerRestarts(s[j]) { |
| return maxContainerRestarts(s[i]) > maxContainerRestarts(s[j]) |
| } |
| // 6. older pods < newer pods < empty timestamp pods |
| if !s[i].CreationTimestamp.Equal(&s[j].CreationTimestamp) { |
| return afterOrZero(&s[j].CreationTimestamp, &s[i].CreationTimestamp) |
| } |
| return false |
| } |
| |
| // ActivePods type allows custom sorting of pods so a controller can pick the best ones to delete. |
| type ActivePods []*corev1.Pod |
| |
| func (s ActivePods) Len() int { return len(s) } |
| func (s ActivePods) Swap(i, j int) { s[i], s[j] = s[j], s[i] } |
| |
| func (s ActivePods) Less(i, j int) bool { |
| // 1. Unassigned < assigned |
| // If only one of the pods is unassigned, the unassigned one is smaller |
| if s[i].Spec.NodeName != s[j].Spec.NodeName && (len(s[i].Spec.NodeName) == 0 || len(s[j].Spec.NodeName) == 0) { |
| return len(s[i].Spec.NodeName) == 0 |
| } |
| // 2. PodPending < PodUnknown < PodRunning |
| m := map[corev1.PodPhase]int{corev1.PodPending: 0, corev1.PodUnknown: 1, corev1.PodRunning: 2} |
| if m[s[i].Status.Phase] != m[s[j].Status.Phase] { |
| return m[s[i].Status.Phase] < m[s[j].Status.Phase] |
| } |
| // 3. Not ready < ready |
| // If only one of the pods is not ready, the not ready one is smaller |
| if IsPodReady(s[i]) != IsPodReady(s[j]) { |
| return !IsPodReady(s[i]) |
| } |
| // TODO: take availability into account when we push minReadySeconds information from deployment into pods, |
| // see https://github.com/kubernetes/kubernetes/issues/22065 |
| // 4. Been ready for empty time < less time < more time |
| // If both pods are ready, the latest ready one is smaller |
| if IsPodReady(s[i]) && IsPodReady(s[j]) && !podReadyTime(s[i]).Equal(podReadyTime(s[j])) { |
| return afterOrZero(podReadyTime(s[i]), podReadyTime(s[j])) |
| } |
| // 5. Pods with containers with higher restart counts < lower restart counts |
| if maxContainerRestarts(s[i]) != maxContainerRestarts(s[j]) { |
| return maxContainerRestarts(s[i]) > maxContainerRestarts(s[j]) |
| } |
| // 6. Empty creation time pods < newer pods < older pods |
| if !s[i].CreationTimestamp.Equal(&s[j].CreationTimestamp) { |
| return afterOrZero(&s[i].CreationTimestamp, &s[j].CreationTimestamp) |
| } |
| return false |
| } |
| |
| // afterOrZero checks if time t1 is after time t2; if one of them |
| // is zero, the zero time is seen as after non-zero time. |
| func afterOrZero(t1, t2 *metav1.Time) bool { |
| if t1.Time.IsZero() || t2.Time.IsZero() { |
| return t1.Time.IsZero() |
| } |
| return t1.After(t2.Time) |
| } |
| |
| func podReadyTime(pod *corev1.Pod) *metav1.Time { |
| if IsPodReady(pod) { |
| for _, c := range pod.Status.Conditions { |
| // we only care about pod ready conditions |
| if c.Type == corev1.PodReady && c.Status == corev1.ConditionTrue { |
| return &c.LastTransitionTime |
| } |
| } |
| } |
| return &metav1.Time{} |
| } |
| |
| func maxContainerRestarts(pod *corev1.Pod) int { |
| maxRestarts := 0 |
| for _, c := range pod.Status.ContainerStatuses { |
| maxRestarts = integer.IntMax(maxRestarts, int(c.RestartCount)) |
| } |
| return maxRestarts |
| } |