Merge pull request #119256 from brianpursley/kubectl-1409

Prefer pods without a deletion timestamp over pods with a deletion timestamp when getting an ordered list of active pods
This commit is contained in:
Kubernetes Prow Robot 2023-07-18 10:05:09 -07:00 committed by GitHub
commit 4575facd23
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 400 additions and 3 deletions

View File

@ -47,6 +47,10 @@ func IsPodReady(pod *corev1.Pod) bool {
return isPodReadyConditionTrue(pod.Status)
}
func isPodDeleting(pod *corev1.Pod) bool {
return pod.DeletionTimestamp != nil
}
// IsPodReadyConditionTrue returns true if a pod is ready; false otherwise.
func isPodReadyConditionTrue(status corev1.PodStatus) bool {
condition := getPodReadyCondition(status)
@ -142,18 +146,26 @@ func (s ActivePods) Less(i, j int) bool {
if IsPodReady(s[i]) != IsPodReady(s[j]) {
return !IsPodReady(s[i])
}
// 4. Deleting < Not deleting
if isPodDeleting(s[i]) != isPodDeleting(s[j]) {
return isPodDeleting(s[i])
}
// 5. Older deletion timestamp < newer deletion timestamp
if isPodDeleting(s[i]) && isPodDeleting(s[j]) && !s[i].ObjectMeta.DeletionTimestamp.Equal(s[j].ObjectMeta.DeletionTimestamp) {
return s[i].ObjectMeta.DeletionTimestamp.Before(s[j].ObjectMeta.DeletionTimestamp)
}
// 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
// 6. 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
// 7. 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
// 8. Empty creation time pods < newer pods < older pods
if !s[i].CreationTimestamp.Equal(&s[j].CreationTimestamp) {
return afterOrZero(&s[i].CreationTimestamp, &s[j].CreationTimestamp)
}

View File

@ -0,0 +1,385 @@
/*
Copyright 2023 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 (
"sort"
"testing"
"time"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func TestActivePods(t *testing.T) {
time1 := metav1.Now()
time2 := metav1.NewTime(time1.Add(1 * time.Second))
time3 := metav1.NewTime(time1.Add(2 * time.Second))
tests := []struct {
name string
pod1 *corev1.Pod
pod2 *corev1.Pod
}{
{
name: "unassigned pod should sort before assigned pod",
pod1: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "unassignedPod",
Namespace: "default",
},
},
pod2: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "assignedPod",
Namespace: "default",
},
Spec: corev1.PodSpec{
NodeName: "node1",
},
},
},
{
name: "pending pod should sort before unknown pod",
pod1: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "pendingPod",
Namespace: "default",
},
Spec: corev1.PodSpec{
NodeName: "node1",
},
Status: corev1.PodStatus{
Phase: corev1.PodPending,
},
},
pod2: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "unknownPod",
Namespace: "default",
CreationTimestamp: time1,
},
Spec: corev1.PodSpec{
NodeName: "node1",
},
Status: corev1.PodStatus{
Phase: corev1.PodUnknown,
},
},
},
{
name: "unknown pod should sort before running pod",
pod1: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "unknownPod",
Namespace: "default",
CreationTimestamp: time1,
},
Spec: corev1.PodSpec{
NodeName: "node1",
},
Status: corev1.PodStatus{
Phase: corev1.PodUnknown,
},
},
pod2: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "runningPod",
Namespace: "default",
CreationTimestamp: time1,
},
Spec: corev1.PodSpec{
NodeName: "node1",
},
Status: corev1.PodStatus{
Phase: corev1.PodRunning,
},
},
},
{
name: "unready pod should sort before ready pod",
pod1: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "unreadyPod",
Namespace: "default",
},
Spec: corev1.PodSpec{
NodeName: "node1",
},
Status: corev1.PodStatus{
Phase: corev1.PodRunning,
},
},
pod2: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "readyPod",
Namespace: "default",
},
Spec: corev1.PodSpec{
NodeName: "node1",
},
Status: corev1.PodStatus{
Phase: corev1.PodRunning,
Conditions: []corev1.PodCondition{
{
Type: corev1.PodReady,
Status: corev1.ConditionTrue,
LastTransitionTime: time1,
},
},
},
},
},
{
name: "pod with deletion timestamp should sort before pod without deletion timestamp",
pod1: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "readyPodDeleting",
Namespace: "default",
DeletionTimestamp: &time2,
},
Spec: corev1.PodSpec{
NodeName: "node1",
},
Status: corev1.PodStatus{
Phase: corev1.PodRunning,
Conditions: []corev1.PodCondition{
{
Type: corev1.PodReady,
Status: corev1.ConditionTrue,
LastTransitionTime: time1,
},
},
},
},
pod2: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "readyPod",
Namespace: "default",
},
Spec: corev1.PodSpec{
NodeName: "node1",
},
Status: corev1.PodStatus{
Phase: corev1.PodRunning,
Conditions: []corev1.PodCondition{
{
Type: corev1.PodReady,
Status: corev1.ConditionTrue,
LastTransitionTime: time1,
},
},
},
},
},
{
name: "older deletion timestamp should sort before newer deletion timestamp",
pod1: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "readyPodDeletingOlder",
Namespace: "default",
DeletionTimestamp: &time2,
},
Spec: corev1.PodSpec{
NodeName: "node1",
},
Status: corev1.PodStatus{
Phase: corev1.PodRunning,
Conditions: []corev1.PodCondition{
{
Type: corev1.PodReady,
Status: corev1.ConditionTrue,
LastTransitionTime: time1,
},
},
},
},
pod2: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "readyPodDeletingNewer",
Namespace: "default",
DeletionTimestamp: &time3,
},
Spec: corev1.PodSpec{
NodeName: "node1",
},
Status: corev1.PodStatus{
Phase: corev1.PodRunning,
Conditions: []corev1.PodCondition{
{
Type: corev1.PodReady,
Status: corev1.ConditionTrue,
LastTransitionTime: time1,
},
},
},
},
},
{
name: "newer ready timestamp should sort before older ready timestamp",
pod1: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "newerReadyPod",
Namespace: "default",
},
Spec: corev1.PodSpec{
NodeName: "node1",
},
Status: corev1.PodStatus{
Phase: corev1.PodRunning,
Conditions: []corev1.PodCondition{
{
Type: corev1.PodReady,
Status: corev1.ConditionTrue,
LastTransitionTime: time3,
},
},
},
},
pod2: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "olderReadyPod",
Namespace: "default",
},
Spec: corev1.PodSpec{
NodeName: "node1",
},
Status: corev1.PodStatus{
Phase: corev1.PodRunning,
Conditions: []corev1.PodCondition{
{
Type: corev1.PodReady,
Status: corev1.ConditionTrue,
LastTransitionTime: time2,
},
},
},
},
},
{
name: "higher restart count should sort before lower restart count",
pod1: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "podWithMoreRestarts",
Namespace: "default",
},
Spec: corev1.PodSpec{
NodeName: "node1",
},
Status: corev1.PodStatus{
Phase: corev1.PodRunning,
Conditions: []corev1.PodCondition{
{
Type: corev1.PodReady,
Status: corev1.ConditionTrue,
LastTransitionTime: time1,
},
},
ContainerStatuses: []corev1.ContainerStatus{
{
RestartCount: 3,
},
},
},
},
pod2: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "podWithLessRestarts",
Namespace: "default",
},
Spec: corev1.PodSpec{
NodeName: "node1",
},
Status: corev1.PodStatus{
Phase: corev1.PodRunning,
Conditions: []corev1.PodCondition{
{
Type: corev1.PodReady,
Status: corev1.ConditionTrue,
LastTransitionTime: time1,
},
},
ContainerStatuses: []corev1.ContainerStatus{
{
RestartCount: 2,
},
},
},
},
},
{
name: "newer creation timestamp should sort before older creation timestamp",
pod1: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "newerCreationPod",
Namespace: "default",
CreationTimestamp: time3,
},
Spec: corev1.PodSpec{
NodeName: "node1",
},
Status: corev1.PodStatus{
Phase: corev1.PodRunning,
Conditions: []corev1.PodCondition{
{
Type: corev1.PodReady,
Status: corev1.ConditionTrue,
LastTransitionTime: time1,
},
},
},
},
pod2: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "olderCreationPod",
Namespace: "default",
CreationTimestamp: time2,
},
Spec: corev1.PodSpec{
NodeName: "node1",
},
Status: corev1.PodStatus{
Phase: corev1.PodRunning,
Conditions: []corev1.PodCondition{
{
Type: corev1.PodReady,
Status: corev1.ConditionTrue,
LastTransitionTime: time1,
},
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Test that the pods are sorted in the correct order when pod1 is first and pod2 is second.
pods := ActivePods{tt.pod1, tt.pod2}
sort.Sort(pods)
if pods[0] != tt.pod1 || pods[1] != tt.pod2 {
t.Errorf("Incorrect ActivePods sorting, expected pod1 to be first")
}
// Test that the pods are sorted in the correct order when pod2 is first and pod1 is second.
pods = ActivePods{tt.pod2, tt.pod1}
sort.Sort(pods)
if pods[0] != tt.pod1 || pods[1] != tt.pod2 {
t.Errorf("Incorrect ActivePods sorting, expected pod1 to be first")
}
})
}
}