Skip to content

Commit fd19685

Browse files
authored
Merge pull request #855 from googs1025/qos_sort
fix(QOSSort): refine queue sorting logic by adding inqueue timestamp
2 parents 1e1b6b0 + 3b77d02 commit fd19685

File tree

3 files changed

+95
-31
lines changed

3 files changed

+95
-31
lines changed

pkg/qos/queue_sort.go

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -41,22 +41,43 @@ func (pl *Sort) Name() string {
4141

4242
// Less is the function used by the activeQ heap algorithm to sort pods.
4343
// It sorts pods based on their priorities. When the priorities are equal, it uses
44-
// the Pod QoS classes to break the tie.
44+
// the Pod QoS classes to break the tie. If both the priority and QoS class are equal,
45+
// it uses PodQueueInfo.timestamp to determine the order.
4546
func (*Sort) Less(pInfo1, pInfo2 *framework.QueuedPodInfo) bool {
4647
p1 := corev1helpers.PodPriority(pInfo1.Pod)
4748
p2 := corev1helpers.PodPriority(pInfo2.Pod)
48-
return (p1 > p2) || (p1 == p2 && compQOS(pInfo1.Pod, pInfo2.Pod))
49+
50+
if p1 != p2 {
51+
return p1 > p2
52+
}
53+
qosResult := compQOS(pInfo1.Pod, pInfo2.Pod)
54+
if qosResult != 0 {
55+
return qosResult > 0
56+
}
57+
return pInfo1.Timestamp.Before(pInfo2.Timestamp)
4958
}
5059

51-
func compQOS(p1, p2 *v1.Pod) bool {
60+
// compQOS compares the QoS classes of two Pods and returns:
61+
//
62+
// 1 if p1 has a higher precedence QoS class than p2,
63+
// -1 if p2 has a higher precedence QoS class than p1,
64+
// 0 if both have the same QoS class.
65+
func compQOS(p1, p2 *v1.Pod) int {
5266
p1QOS, p2QOS := v1qos.GetPodQOS(p1), v1qos.GetPodQOS(p2)
53-
if p1QOS == v1.PodQOSGuaranteed {
54-
return true
67+
68+
// Define the precedence order of QoS classes using a map
69+
qosOrder := map[v1.PodQOSClass]int{
70+
v1.PodQOSBestEffort: 1,
71+
v1.PodQOSBurstable: 2,
72+
v1.PodQOSGuaranteed: 3,
5573
}
56-
if p1QOS == v1.PodQOSBurstable {
57-
return p2QOS != v1.PodQOSGuaranteed
74+
75+
if qosOrder[p1QOS] > qosOrder[p2QOS] {
76+
return 1
77+
} else if qosOrder[p1QOS] < qosOrder[p2QOS] {
78+
return -1
5879
}
59-
return p2QOS == v1.PodQOSBestEffort
80+
return 0
6081
}
6182

6283
// New initializes a new plugin and returns it.

pkg/qos/queue_sort_test.go

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package qos
1818

1919
import (
2020
"testing"
21+
"time"
2122

2223
v1 "k8s.io/api/core/v1"
2324
"k8s.io/apimachinery/pkg/api/resource"
@@ -31,6 +32,8 @@ func createPodInfo(pod *v1.Pod) *framework.PodInfo {
3132
}
3233

3334
func TestSortLess(t *testing.T) {
35+
earlierTime := time.Now()
36+
laterTime := earlierTime.Add(time.Second)
3437
tests := []struct {
3538
name string
3639
pInfo1 *framework.QueuedPodInfo
@@ -58,14 +61,16 @@ func TestSortLess(t *testing.T) {
5861
want: false,
5962
},
6063
{
61-
name: "p1 and p2 are both BestEfforts",
64+
name: "p1 and p2 are both BestEfforts, but p2 is added to schedulingQ earlier than p1",
6265
pInfo1: &framework.QueuedPodInfo{
63-
PodInfo: createPodInfo(makePod("p1", 0, nil, nil)),
66+
PodInfo: createPodInfo(makePod("p1", 0, nil, nil)),
67+
Timestamp: laterTime,
6468
},
6569
pInfo2: &framework.QueuedPodInfo{
66-
PodInfo: createPodInfo(makePod("p2", 0, nil, nil)),
70+
PodInfo: createPodInfo(makePod("p2", 0, nil, nil)),
71+
Timestamp: earlierTime,
6772
},
68-
want: true,
73+
want: false,
6974
},
7075
{
7176
name: "p1 is BestEfforts, p2 is Guaranteed",
@@ -88,14 +93,16 @@ func TestSortLess(t *testing.T) {
8893
want: false,
8994
},
9095
{
91-
name: "both p1 and p2 are Burstable",
96+
name: "both p1 and p2 are Burstable, but p2 is added to schedulingQ earlier than p1",
9297
pInfo1: &framework.QueuedPodInfo{
93-
PodInfo: createPodInfo(makePod("p1", 0, getResList("100m", "100Mi"), getResList("200m", "200Mi"))),
98+
PodInfo: createPodInfo(makePod("p1", 0, getResList("100m", "100Mi"), getResList("200m", "200Mi"))),
99+
Timestamp: laterTime,
94100
},
95101
pInfo2: &framework.QueuedPodInfo{
96-
PodInfo: createPodInfo(makePod("p2", 0, getResList("100m", "100Mi"), getResList("200m", "200Mi"))),
102+
PodInfo: createPodInfo(makePod("p2", 0, getResList("100m", "100Mi"), getResList("200m", "200Mi"))),
103+
Timestamp: earlierTime,
97104
},
98-
want: true,
105+
want: false,
99106
},
100107
{
101108
name: "p1 is Guaranteed, p2 is Burstable",
@@ -108,12 +115,26 @@ func TestSortLess(t *testing.T) {
108115
want: true,
109116
},
110117
{
111-
name: "both p1 and p2 are Guaranteed",
118+
name: "both p1 and p2 are Guaranteed, but p1 is added to schedulingQ earlier than p2",
112119
pInfo1: &framework.QueuedPodInfo{
113-
PodInfo: createPodInfo(makePod("p1", 0, getResList("100m", "100Mi"), getResList("100m", "100Mi"))),
120+
PodInfo: createPodInfo(makePod("p1", 0, getResList("100m", "100Mi"), getResList("100m", "100Mi"))),
121+
Timestamp: earlierTime,
114122
},
115123
pInfo2: &framework.QueuedPodInfo{
116-
PodInfo: createPodInfo(makePod("p2", 0, getResList("100m", "100Mi"), getResList("100m", "100Mi"))),
124+
PodInfo: createPodInfo(makePod("p2", 0, getResList("100m", "100Mi"), getResList("100m", "100Mi"))),
125+
Timestamp: laterTime,
126+
},
127+
want: true,
128+
},
129+
{
130+
name: "both p1 and p2 are Guaranteed, but p1 is added to schedulingQ earlier than p2",
131+
pInfo1: &framework.QueuedPodInfo{
132+
PodInfo: createPodInfo(makePod("p1", 0, getResList("100m", "100Mi"), getResList("100m", "100Mi"))),
133+
Timestamp: earlierTime,
134+
},
135+
pInfo2: &framework.QueuedPodInfo{
136+
PodInfo: createPodInfo(makePod("p2", 0, getResList("100m", "100Mi"), getResList("100m", "100Mi"))),
137+
Timestamp: laterTime,
117138
},
118139
want: true,
119140
},

test/integration/qos_test.go

Lines changed: 34 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ package integration
1919
import (
2020
"context"
2121
"fmt"
22+
"reflect"
23+
"strings"
24+
"sync"
2225
"testing"
2326
"time"
2427

@@ -114,19 +117,33 @@ func TestQOSPlugin(t *testing.T) {
114117
// Create 3 Pods with the order: BestEfforts, Burstable, Guaranteed.
115118
// We will expect them to be scheduled in a reversed order.
116119
t.Logf("Start to create 3 Pods.")
117-
for i := range pods {
118-
t.Logf("Creating Pod %q", pods[i].Name)
119-
_, err = cs.CoreV1().Pods(ns).Create(testCtx.Ctx, pods[i], metav1.CreateOptions{})
120-
if err != nil {
121-
t.Fatalf("Failed to create Pod %q: %v", pods[i].Name, err)
122-
}
120+
// Concurrently create all Pods.
121+
var wg sync.WaitGroup
122+
for _, pod := range pods {
123+
wg.Add(1)
124+
go func(p *v1.Pod) {
125+
defer wg.Done()
126+
_, err = cs.CoreV1().Pods(ns).Create(testCtx.Ctx, p, metav1.CreateOptions{})
127+
if err != nil {
128+
t.Errorf("Failed to create Pod %q: %v", p.Name, err)
129+
} else {
130+
t.Logf("Created Pod %q", p.Name)
131+
}
132+
}(pod)
123133
}
134+
wg.Wait()
124135
defer cleanupPods(t, testCtx, pods)
125136

126137
// Wait for all Pods are in the scheduling queue.
127138
err = wait.PollUntilContextTimeout(testCtx.Ctx, time.Millisecond*200, wait.ForeverTestTimeout, false, func(ctx context.Context) (bool, error) {
128139
pendingPods, _ := testCtx.Scheduler.SchedulingQueue.PendingPods()
129140
if len(pendingPods) == len(pods) {
141+
// Collect Pod names into a slice.
142+
podNames := make([]string, len(pendingPods))
143+
for i, podInfo := range pendingPods {
144+
podNames[i] = podInfo.Name
145+
}
146+
t.Logf("All Pods are in the pending queue: %v", strings.Join(podNames, ", "))
130147
return true, nil
131148
}
132149
return false, nil
@@ -137,12 +154,17 @@ func TestQOSPlugin(t *testing.T) {
137154

138155
// Expect Pods are popped in the QoS class order.
139156
logger := klog.FromContext(testCtx.Ctx)
140-
for i := len(podNames) - 1; i >= 0; i-- {
157+
expectedOrder := []string{"guaranteed", "burstable", "bestefforts"}
158+
actualOrder := make([]string, len(expectedOrder))
159+
for i := 0; i < len(expectedOrder); i++ {
141160
podInfo, _ := testCtx.Scheduler.NextPod(logger)
142-
if podInfo.Pod.Name != podNames[i] {
143-
t.Errorf("Expect Pod %q, but got %q", podNames[i], podInfo.Pod.Name)
144-
} else {
145-
t.Logf("Pod %q is popped out as expected.", podInfo.Pod.Name)
146-
}
161+
actualOrder[i] = podInfo.Pod.Name
162+
t.Logf("Popped Pod %q", podInfo.Pod.Name)
163+
}
164+
165+
if !reflect.DeepEqual(actualOrder, expectedOrder) {
166+
t.Errorf("Expected Pod order %v, but got %v", expectedOrder, actualOrder)
167+
} else {
168+
t.Logf("Pods were popped out in the expected order.")
147169
}
148170
}

0 commit comments

Comments
 (0)