k8s.io/kubernetes@v1.29.3/pkg/scheduler/internal/queue/scheduling_queue_test.go (about) 1 /* 2 Copyright 2017 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package queue 18 19 import ( 20 "container/list" 21 "context" 22 "fmt" 23 "math" 24 "strings" 25 "sync" 26 "testing" 27 "time" 28 29 "github.com/google/go-cmp/cmp" 30 "github.com/google/go-cmp/cmp/cmpopts" 31 v1 "k8s.io/api/core/v1" 32 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 33 "k8s.io/apimachinery/pkg/runtime" 34 "k8s.io/apimachinery/pkg/types" 35 "k8s.io/apimachinery/pkg/util/sets" 36 utilfeature "k8s.io/apiserver/pkg/util/feature" 37 "k8s.io/client-go/informers" 38 "k8s.io/client-go/kubernetes/fake" 39 featuregatetesting "k8s.io/component-base/featuregate/testing" 40 "k8s.io/component-base/metrics/testutil" 41 "k8s.io/klog/v2" 42 "k8s.io/klog/v2/ktesting" 43 podutil "k8s.io/kubernetes/pkg/api/v1/pod" 44 "k8s.io/kubernetes/pkg/features" 45 "k8s.io/kubernetes/pkg/scheduler/framework" 46 "k8s.io/kubernetes/pkg/scheduler/framework/plugins/queuesort" 47 "k8s.io/kubernetes/pkg/scheduler/metrics" 48 st "k8s.io/kubernetes/pkg/scheduler/testing" 49 "k8s.io/kubernetes/pkg/scheduler/util" 50 testingclock "k8s.io/utils/clock/testing" 51 ) 52 53 const queueMetricMetadata = ` 54 # HELP scheduler_queue_incoming_pods_total [STABLE] Number of pods added to scheduling queues by event and queue type. 55 # TYPE scheduler_queue_incoming_pods_total counter 56 ` 57 58 var ( 59 TestEvent = framework.ClusterEvent{Resource: "test"} 60 NodeAllEvent = framework.ClusterEvent{Resource: framework.Node, ActionType: framework.All} 61 EmptyEvent = framework.ClusterEvent{} 62 63 lowPriority, midPriority, highPriority = int32(0), int32(100), int32(1000) 64 mediumPriority = (lowPriority + highPriority) / 2 65 66 highPriorityPodInfo = mustNewPodInfo( 67 st.MakePod().Name("hpp").Namespace("ns1").UID("hppns1").Priority(highPriority).Obj(), 68 ) 69 highPriNominatedPodInfo = mustNewPodInfo( 70 st.MakePod().Name("hpp").Namespace("ns1").UID("hppns1").Priority(highPriority).NominatedNodeName("node1").Obj(), 71 ) 72 medPriorityPodInfo = mustNewPodInfo( 73 st.MakePod().Name("mpp").Namespace("ns2").UID("mppns2").Annotation("annot2", "val2").Priority(mediumPriority).NominatedNodeName("node1").Obj(), 74 ) 75 unschedulablePodInfo = mustNewPodInfo( 76 st.MakePod().Name("up").Namespace("ns1").UID("upns1").Annotation("annot2", "val2").Priority(lowPriority).NominatedNodeName("node1").Condition(v1.PodScheduled, v1.ConditionFalse, v1.PodReasonUnschedulable).Obj(), 77 ) 78 nonExistentPodInfo = mustNewPodInfo( 79 st.MakePod().Name("ne").Namespace("ns1").UID("nens1").Obj(), 80 ) 81 scheduledPodInfo = mustNewPodInfo( 82 st.MakePod().Name("sp").Namespace("ns1").UID("spns1").Node("foo").Obj(), 83 ) 84 85 nominatorCmpOpts = []cmp.Option{ 86 cmp.AllowUnexported(nominator{}), 87 cmpopts.IgnoreFields(nominator{}, "podLister", "lock"), 88 } 89 90 queueHintReturnQueue = func(logger klog.Logger, pod *v1.Pod, oldObj, newObj interface{}) (framework.QueueingHint, error) { 91 return framework.Queue, nil 92 } 93 queueHintReturnSkip = func(logger klog.Logger, pod *v1.Pod, oldObj, newObj interface{}) (framework.QueueingHint, error) { 94 return framework.QueueSkip, nil 95 } 96 ) 97 98 func getUnschedulablePod(p *PriorityQueue, pod *v1.Pod) *v1.Pod { 99 pInfo := p.unschedulablePods.get(pod) 100 if pInfo != nil { 101 return pInfo.Pod 102 } 103 return nil 104 } 105 106 // makeEmptyQueueingHintMapPerProfile initializes an empty QueueingHintMapPerProfile for "" profile name. 107 func makeEmptyQueueingHintMapPerProfile() QueueingHintMapPerProfile { 108 m := make(QueueingHintMapPerProfile) 109 m[""] = make(QueueingHintMap) 110 return m 111 } 112 113 func TestPriorityQueue_Add(t *testing.T) { 114 objs := []runtime.Object{medPriorityPodInfo.Pod, unschedulablePodInfo.Pod, highPriorityPodInfo.Pod} 115 logger, ctx := ktesting.NewTestContext(t) 116 ctx, cancel := context.WithCancel(ctx) 117 defer cancel() 118 q := NewTestQueueWithObjects(ctx, newDefaultQueueSort(), objs) 119 if err := q.Add(logger, medPriorityPodInfo.Pod); err != nil { 120 t.Errorf("add failed: %v", err) 121 } 122 if err := q.Add(logger, unschedulablePodInfo.Pod); err != nil { 123 t.Errorf("add failed: %v", err) 124 } 125 if err := q.Add(logger, highPriorityPodInfo.Pod); err != nil { 126 t.Errorf("add failed: %v", err) 127 } 128 expectedNominatedPods := &nominator{ 129 nominatedPodToNode: map[types.UID]string{ 130 medPriorityPodInfo.Pod.UID: "node1", 131 unschedulablePodInfo.Pod.UID: "node1", 132 }, 133 nominatedPods: map[string][]*framework.PodInfo{ 134 "node1": {medPriorityPodInfo, unschedulablePodInfo}, 135 }, 136 } 137 if diff := cmp.Diff(q.nominator, expectedNominatedPods, nominatorCmpOpts...); diff != "" { 138 t.Errorf("Unexpected diff after adding pods (-want, +got):\n%s", diff) 139 } 140 if p, err := q.Pop(logger); err != nil || p.Pod != highPriorityPodInfo.Pod { 141 t.Errorf("Expected: %v after Pop, but got: %v", highPriorityPodInfo.Pod.Name, p.Pod.Name) 142 } 143 if p, err := q.Pop(logger); err != nil || p.Pod != medPriorityPodInfo.Pod { 144 t.Errorf("Expected: %v after Pop, but got: %v", medPriorityPodInfo.Pod.Name, p.Pod.Name) 145 } 146 if p, err := q.Pop(logger); err != nil || p.Pod != unschedulablePodInfo.Pod { 147 t.Errorf("Expected: %v after Pop, but got: %v", unschedulablePodInfo.Pod.Name, p.Pod.Name) 148 } 149 if len(q.nominator.nominatedPods["node1"]) != 2 { 150 t.Errorf("Expected medPriorityPodInfo and unschedulablePodInfo to be still present in nomindatePods: %v", q.nominator.nominatedPods["node1"]) 151 } 152 } 153 154 func newDefaultQueueSort() framework.LessFunc { 155 sort := &queuesort.PrioritySort{} 156 return sort.Less 157 } 158 159 func TestPriorityQueue_AddWithReversePriorityLessFunc(t *testing.T) { 160 objs := []runtime.Object{medPriorityPodInfo.Pod, highPriorityPodInfo.Pod} 161 logger, ctx := ktesting.NewTestContext(t) 162 ctx, cancel := context.WithCancel(ctx) 163 defer cancel() 164 q := NewTestQueueWithObjects(ctx, newDefaultQueueSort(), objs) 165 if err := q.Add(logger, medPriorityPodInfo.Pod); err != nil { 166 t.Errorf("add failed: %v", err) 167 } 168 if err := q.Add(logger, highPriorityPodInfo.Pod); err != nil { 169 t.Errorf("add failed: %v", err) 170 } 171 if p, err := q.Pop(logger); err != nil || p.Pod != highPriorityPodInfo.Pod { 172 t.Errorf("Expected: %v after Pop, but got: %v", highPriorityPodInfo.Pod.Name, p.Pod.Name) 173 } 174 if p, err := q.Pop(logger); err != nil || p.Pod != medPriorityPodInfo.Pod { 175 t.Errorf("Expected: %v after Pop, but got: %v", medPriorityPodInfo.Pod.Name, p.Pod.Name) 176 } 177 } 178 179 func listToValues(l *list.List) []interface{} { 180 var values []interface{} 181 for e := l.Front(); e != nil; e = e.Next() { 182 values = append(values, e.Value) 183 } 184 return values 185 } 186 187 func Test_InFlightPods(t *testing.T) { 188 logger, _ := ktesting.NewTestContext(t) 189 pod := st.MakePod().Name("targetpod").UID("pod1").Obj() 190 pod2 := st.MakePod().Name("targetpod2").UID("pod2").Obj() 191 pod3 := st.MakePod().Name("targetpod3").UID("pod3").Obj() 192 var poppedPod, poppedPod2 *framework.QueuedPodInfo 193 194 type action struct { 195 // ONLY ONE of the following should be set. 196 eventHappens *framework.ClusterEvent 197 podPopped *v1.Pod 198 podEnqueued *framework.QueuedPodInfo 199 callback func(t *testing.T, q *PriorityQueue) 200 } 201 202 tests := []struct { 203 name string 204 queueingHintMap QueueingHintMapPerProfile 205 // initialPods is the initial Pods in the activeQ. 206 initialPods []*v1.Pod 207 actions []action 208 wantInFlightPods []*v1.Pod 209 wantInFlightEvents []interface{} 210 wantActiveQPodNames []string 211 wantBackoffQPodNames []string 212 wantUnschedPodPoolPodNames []string 213 isSchedulingQueueHintEnabled bool 214 }{ 215 { 216 name: "when SchedulingQueueHint is disabled, inFlightPods and inFlightEvents should be empty", 217 initialPods: []*v1.Pod{pod}, 218 actions: []action{ 219 // This Pod shouldn't be added to inFlightPods because SchedulingQueueHint is disabled. 220 {podPopped: pod}, 221 // This event shouldn't be added to inFlightEvents because SchedulingQueueHint is disabled. 222 {eventHappens: &PvAdd}, 223 }, 224 wantInFlightPods: nil, 225 wantInFlightEvents: nil, 226 }, 227 { 228 name: "when SchedulingQueueHint is disabled, which queue to enqueue Pod should be decided without SchedulingQueueHint", 229 initialPods: []*v1.Pod{pod}, 230 actions: []action{ 231 {podPopped: pod}, 232 {eventHappens: &AssignedPodAdd}, 233 {podEnqueued: newQueuedPodInfoForLookup(pod, "fooPlugin1")}, 234 }, 235 wantBackoffQPodNames: []string{"targetpod"}, 236 wantInFlightPods: nil, 237 wantInFlightEvents: nil, 238 queueingHintMap: QueueingHintMapPerProfile{ 239 "": { 240 // This hint fn tells that this event doesn't make a Pod schedulable. 241 // However, this QueueingHintFn will be ignored actually because SchedulingQueueHint is disabled. 242 AssignedPodAdd: { 243 { 244 PluginName: "fooPlugin1", 245 QueueingHintFn: queueHintReturnSkip, 246 }, 247 }, 248 }, 249 }, 250 }, 251 { 252 name: "Pod is registered in inFlightPods when Pod is popped from activeQ", 253 isSchedulingQueueHintEnabled: true, 254 initialPods: []*v1.Pod{pod}, 255 actions: []action{ 256 // This won't be added to inFlightEvents because no inFlightPods at this point. 257 {eventHappens: &PvcAdd}, 258 {podPopped: pod}, 259 // This gets added for the pod. 260 {eventHappens: &PvAdd}, 261 }, 262 wantInFlightPods: []*v1.Pod{pod}, 263 wantInFlightEvents: []interface{}{pod, PvAdd}, 264 }, 265 { 266 name: "Pod, registered in inFlightPods, is enqueued back to activeQ", 267 isSchedulingQueueHintEnabled: true, 268 initialPods: []*v1.Pod{pod, pod2}, 269 actions: []action{ 270 // This won't be added to inFlightEvents because no inFlightPods at this point. 271 {eventHappens: &PvcAdd}, 272 {podPopped: pod}, 273 {eventHappens: &PvAdd}, 274 {podPopped: pod2}, 275 {eventHappens: &NodeAdd}, 276 {podEnqueued: newQueuedPodInfoForLookup(pod)}, 277 }, 278 wantBackoffQPodNames: []string{"targetpod"}, 279 wantInFlightPods: []*v1.Pod{pod2}, 280 wantInFlightEvents: []interface{}{pod2, NodeAdd}, 281 }, 282 { 283 name: "All Pods registered in inFlightPods are enqueued back to activeQ", 284 isSchedulingQueueHintEnabled: true, 285 initialPods: []*v1.Pod{pod, pod2}, 286 actions: []action{ 287 // This won't be added to inFlightEvents because no inFlightPods at this point. 288 {eventHappens: &PvcAdd}, 289 {podPopped: pod}, 290 {eventHappens: &PvAdd}, 291 {podPopped: pod2}, 292 {eventHappens: &NodeAdd}, 293 {podEnqueued: newQueuedPodInfoForLookup(pod)}, 294 {eventHappens: &CSINodeUpdate}, 295 {podEnqueued: newQueuedPodInfoForLookup(pod2)}, 296 }, 297 wantBackoffQPodNames: []string{"targetpod", "targetpod2"}, 298 wantInFlightPods: nil, 299 wantInFlightEvents: nil, 300 }, 301 { 302 name: "One intermediate Pod registered in inFlightPods is enqueued back to activeQ", 303 isSchedulingQueueHintEnabled: true, 304 initialPods: []*v1.Pod{pod, pod2, pod3}, 305 actions: []action{ 306 // This won't be added to inFlightEvents because no inFlightPods at this point. 307 {eventHappens: &PvcAdd}, 308 {podPopped: pod}, 309 {eventHappens: &PvAdd}, 310 {podPopped: pod2}, 311 {eventHappens: &NodeAdd}, 312 // This Pod won't be requeued again. 313 {podPopped: pod3}, 314 {eventHappens: &AssignedPodAdd}, 315 {podEnqueued: newQueuedPodInfoForLookup(pod2)}, 316 }, 317 wantBackoffQPodNames: []string{"targetpod2"}, 318 wantInFlightPods: []*v1.Pod{pod, pod3}, 319 wantInFlightEvents: []interface{}{pod, PvAdd, NodeAdd, pod3, AssignedPodAdd}, 320 }, 321 { 322 name: "events before popping Pod are ignored", 323 isSchedulingQueueHintEnabled: true, 324 initialPods: []*v1.Pod{pod}, 325 actions: []action{ 326 {eventHappens: &WildCardEvent}, 327 {podPopped: pod}, 328 {eventHappens: &AssignedPodAdd}, 329 {podEnqueued: newQueuedPodInfoForLookup(pod, "fooPlugin1")}, 330 }, 331 wantUnschedPodPoolPodNames: []string{"targetpod"}, 332 wantInFlightPods: nil, 333 wantInFlightEvents: nil, 334 queueingHintMap: QueueingHintMapPerProfile{ 335 "": { 336 // fooPlugin1 has a queueing hint function for AssignedPodAdd, 337 // but hint fn tells that this event doesn't make a Pod scheudlable. 338 AssignedPodAdd: { 339 { 340 PluginName: "fooPlugin1", 341 QueueingHintFn: queueHintReturnSkip, 342 }, 343 }, 344 }, 345 }, 346 }, 347 { 348 name: "pod is enqueued to backoff if no failed plugin", 349 isSchedulingQueueHintEnabled: true, 350 initialPods: []*v1.Pod{pod}, 351 actions: []action{ 352 {podPopped: pod}, 353 {eventHappens: &AssignedPodAdd}, 354 {podEnqueued: newQueuedPodInfoForLookup(pod)}, 355 }, 356 wantBackoffQPodNames: []string{"targetpod"}, 357 wantInFlightPods: nil, 358 wantInFlightEvents: nil, 359 queueingHintMap: QueueingHintMapPerProfile{ 360 "": { 361 // It will be ignored because no failed plugin. 362 AssignedPodAdd: { 363 { 364 PluginName: "fooPlugin1", 365 QueueingHintFn: queueHintReturnQueue, 366 }, 367 }, 368 }, 369 }, 370 }, 371 { 372 name: "pod is enqueued to unschedulable pod pool if no events that can make the pod schedulable", 373 isSchedulingQueueHintEnabled: true, 374 initialPods: []*v1.Pod{pod}, 375 actions: []action{ 376 {podPopped: pod}, 377 {eventHappens: &NodeAdd}, 378 {podEnqueued: newQueuedPodInfoForLookup(pod, "fooPlugin1")}, 379 }, 380 wantUnschedPodPoolPodNames: []string{"targetpod"}, 381 wantInFlightPods: nil, 382 wantInFlightEvents: nil, 383 queueingHintMap: QueueingHintMapPerProfile{ 384 "": { 385 // fooPlugin1 has no queueing hint function for NodeAdd. 386 AssignedPodAdd: { 387 { 388 // It will be ignored because the event is not NodeAdd. 389 PluginName: "fooPlugin1", 390 QueueingHintFn: queueHintReturnQueue, 391 }, 392 }, 393 }, 394 }, 395 }, 396 { 397 name: "pod is enqueued to unschedulable pod pool because the failed plugin has a hint fn but it returns Skip", 398 isSchedulingQueueHintEnabled: true, 399 initialPods: []*v1.Pod{pod}, 400 actions: []action{ 401 {podPopped: pod}, 402 {eventHappens: &AssignedPodAdd}, 403 {podEnqueued: newQueuedPodInfoForLookup(pod, "fooPlugin1")}, 404 }, 405 wantUnschedPodPoolPodNames: []string{"targetpod"}, 406 wantInFlightPods: nil, 407 wantInFlightEvents: nil, 408 queueingHintMap: QueueingHintMapPerProfile{ 409 "": { 410 // fooPlugin1 has a queueing hint function for AssignedPodAdd, 411 // but hint fn tells that this event doesn't make a Pod scheudlable. 412 AssignedPodAdd: { 413 { 414 PluginName: "fooPlugin1", 415 QueueingHintFn: queueHintReturnSkip, 416 }, 417 }, 418 }, 419 }, 420 }, 421 { 422 name: "pod is enqueued to activeQ because the Pending plugins has a hint fn and it returns Queue", 423 isSchedulingQueueHintEnabled: true, 424 initialPods: []*v1.Pod{pod}, 425 actions: []action{ 426 {podPopped: pod}, 427 {eventHappens: &AssignedPodAdd}, 428 {podEnqueued: &framework.QueuedPodInfo{ 429 PodInfo: mustNewPodInfo(pod), 430 UnschedulablePlugins: sets.New("fooPlugin2", "fooPlugin3"), 431 PendingPlugins: sets.New("fooPlugin1"), 432 }}, 433 }, 434 wantActiveQPodNames: []string{"targetpod"}, 435 wantInFlightPods: nil, 436 wantInFlightEvents: nil, 437 queueingHintMap: QueueingHintMapPerProfile{ 438 "": { 439 AssignedPodAdd: { 440 { 441 PluginName: "fooPlugin3", 442 QueueingHintFn: queueHintReturnSkip, 443 }, 444 { 445 PluginName: "fooPlugin2", 446 QueueingHintFn: queueHintReturnQueue, 447 }, 448 { 449 // The hint fn tells that this event makes a Pod scheudlable immediately. 450 PluginName: "fooPlugin1", 451 QueueingHintFn: queueHintReturnQueue, 452 }, 453 }, 454 }, 455 }, 456 }, 457 { 458 name: "pod is enqueued to backoffQ because the failed plugin has a hint fn and it returns Queue", 459 isSchedulingQueueHintEnabled: true, 460 initialPods: []*v1.Pod{pod}, 461 actions: []action{ 462 {podPopped: pod}, 463 {eventHappens: &AssignedPodAdd}, 464 {podEnqueued: newQueuedPodInfoForLookup(pod, "fooPlugin1", "fooPlugin2")}, 465 }, 466 wantBackoffQPodNames: []string{"targetpod"}, 467 wantInFlightPods: nil, 468 wantInFlightEvents: nil, 469 queueingHintMap: QueueingHintMapPerProfile{ 470 "": { 471 AssignedPodAdd: { 472 { 473 // it will be ignored because the hint fn returns Skip that is weaker than queueHintReturnQueue from fooPlugin1. 474 PluginName: "fooPlugin2", 475 QueueingHintFn: queueHintReturnSkip, 476 }, 477 { 478 // The hint fn tells that this event makes a Pod schedulable. 479 PluginName: "fooPlugin1", 480 QueueingHintFn: queueHintReturnQueue, 481 }, 482 }, 483 }, 484 }, 485 }, 486 { 487 name: "pod is enqueued to activeQ because the failed plugin has a hint fn and it returns Queue for a concurrent event that was received while some other pod was in flight", 488 isSchedulingQueueHintEnabled: true, 489 initialPods: []*v1.Pod{pod, pod2}, 490 actions: []action{ 491 {callback: func(t *testing.T, q *PriorityQueue) { poppedPod = popPod(t, logger, q, pod) }}, 492 {eventHappens: &NodeAdd}, 493 {callback: func(t *testing.T, q *PriorityQueue) { poppedPod2 = popPod(t, logger, q, pod2) }}, 494 {eventHappens: &AssignedPodAdd}, 495 {callback: func(t *testing.T, q *PriorityQueue) { 496 logger, _ := ktesting.NewTestContext(t) 497 err := q.AddUnschedulableIfNotPresent(logger, poppedPod, q.SchedulingCycle()) 498 if err != nil { 499 t.Fatalf("unexpected error from AddUnschedulableIfNotPresent: %v", err) 500 } 501 }}, 502 {callback: func(t *testing.T, q *PriorityQueue) { 503 logger, _ := ktesting.NewTestContext(t) 504 poppedPod2.UnschedulablePlugins = sets.New("fooPlugin2", "fooPlugin3") 505 poppedPod2.PendingPlugins = sets.New("fooPlugin1") 506 err := q.AddUnschedulableIfNotPresent(logger, poppedPod2, q.SchedulingCycle()) 507 if err != nil { 508 t.Fatalf("unexpected error from AddUnschedulableIfNotPresent: %v", err) 509 } 510 }}, 511 }, 512 wantActiveQPodNames: []string{pod2.Name}, 513 wantInFlightPods: nil, 514 wantInFlightEvents: nil, 515 queueingHintMap: QueueingHintMapPerProfile{ 516 "": { 517 AssignedPodAdd: { 518 { 519 // it will be ignored because the hint fn returns QueueSkip that is weaker than queueHintReturnQueueImmediately from fooPlugin1. 520 PluginName: "fooPlugin3", 521 QueueingHintFn: queueHintReturnSkip, 522 }, 523 { 524 // it will be ignored because the fooPlugin2 is registered in UnschedulablePlugins and it's interpret as Queue that is weaker than QueueImmediately from fooPlugin1. 525 PluginName: "fooPlugin2", 526 QueueingHintFn: queueHintReturnQueue, 527 }, 528 { 529 // The hint fn tells that this event makes a Pod scheudlable. 530 // Given fooPlugin1 is registered as Pendings, we interpret Queue as queueImmediately. 531 PluginName: "fooPlugin1", 532 QueueingHintFn: queueHintReturnQueue, 533 }, 534 }, 535 }, 536 }, 537 }, 538 { 539 name: "popped pod must have empty UnschedulablePlugins and PendingPlugins", 540 isSchedulingQueueHintEnabled: true, 541 initialPods: []*v1.Pod{pod}, 542 actions: []action{ 543 {callback: func(t *testing.T, q *PriorityQueue) { poppedPod = popPod(t, logger, q, pod) }}, 544 {callback: func(t *testing.T, q *PriorityQueue) { 545 logger, _ := ktesting.NewTestContext(t) 546 // Unschedulable due to PendingPlugins. 547 poppedPod.PendingPlugins = sets.New("fooPlugin1") 548 poppedPod.UnschedulablePlugins = sets.New("fooPlugin2") 549 if err := q.AddUnschedulableIfNotPresent(logger, poppedPod, q.SchedulingCycle()); err != nil { 550 t.Errorf("Unexpected error from AddUnschedulableIfNotPresent: %v", err) 551 } 552 }}, 553 {eventHappens: &PvAdd}, // Active again. 554 {callback: func(t *testing.T, q *PriorityQueue) { 555 poppedPod = popPod(t, logger, q, pod) 556 if len(poppedPod.UnschedulablePlugins) > 0 { 557 t.Errorf("QueuedPodInfo from Pop should have empty UnschedulablePlugins, got instead: %+v", poppedPod) 558 } 559 }}, 560 {callback: func(t *testing.T, q *PriorityQueue) { 561 logger, _ := ktesting.NewTestContext(t) 562 // Failed (i.e. no UnschedulablePlugins). Should go to backoff. 563 if err := q.AddUnschedulableIfNotPresent(logger, poppedPod, q.SchedulingCycle()); err != nil { 564 t.Errorf("Unexpected error from AddUnschedulableIfNotPresent: %v", err) 565 } 566 }}, 567 }, 568 queueingHintMap: QueueingHintMapPerProfile{ 569 "": { 570 PvAdd: { 571 { 572 // The hint fn tells that this event makes a Pod scheudlable immediately. 573 PluginName: "fooPlugin1", 574 QueueingHintFn: queueHintReturnQueue, 575 }, 576 }, 577 }, 578 }, 579 }, 580 } 581 582 for _, test := range tests { 583 t.Run(test.name, func(t *testing.T) { 584 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.SchedulerQueueingHints, test.isSchedulingQueueHintEnabled)() 585 logger, ctx := ktesting.NewTestContext(t) 586 ctx, cancel := context.WithCancel(ctx) 587 defer cancel() 588 obj := make([]runtime.Object, 0, len(test.initialPods)) 589 for _, p := range test.initialPods { 590 obj = append(obj, p) 591 } 592 q := NewTestQueueWithObjects(ctx, newDefaultQueueSort(), obj, WithQueueingHintMapPerProfile(test.queueingHintMap)) 593 594 for _, p := range test.initialPods { 595 q.Add(logger, p) 596 } 597 598 for _, action := range test.actions { 599 switch { 600 case action.podPopped != nil: 601 popPod(t, logger, q, action.podPopped) 602 case action.eventHappens != nil: 603 q.MoveAllToActiveOrBackoffQueue(logger, *action.eventHappens, nil, nil, nil) 604 case action.podEnqueued != nil: 605 err := q.AddUnschedulableIfNotPresent(logger, action.podEnqueued, q.SchedulingCycle()) 606 if err != nil { 607 t.Fatalf("unexpected error from AddUnschedulableIfNotPresent: %v", err) 608 } 609 case action.callback != nil: 610 action.callback(t, q) 611 } 612 } 613 614 actualInFlightPods := make(map[types.UID]*v1.Pod) 615 for uid, element := range q.inFlightPods { 616 actualInFlightPods[uid] = element.Value.(*v1.Pod) 617 } 618 wantInFlightPods := make(map[types.UID]*v1.Pod) 619 for _, pod := range test.wantInFlightPods { 620 wantInFlightPods[pod.UID] = pod 621 } 622 if diff := cmp.Diff(wantInFlightPods, actualInFlightPods); diff != "" { 623 t.Errorf("Unexpected diff in inFlightPods (-want, +got):\n%s", diff) 624 } 625 626 var wantInFlightEvents []interface{} 627 for _, value := range test.wantInFlightEvents { 628 if event, ok := value.(framework.ClusterEvent); ok { 629 value = &clusterEvent{event: event} 630 } 631 wantInFlightEvents = append(wantInFlightEvents, value) 632 } 633 if diff := cmp.Diff(wantInFlightEvents, listToValues(q.inFlightEvents), cmp.AllowUnexported(clusterEvent{})); diff != "" { 634 t.Errorf("Unexpected diff in inFlightEvents (-want, +got):\n%s", diff) 635 } 636 637 if test.wantActiveQPodNames != nil { 638 rawPodInfos := q.activeQ.List() 639 if len(rawPodInfos) != len(test.wantActiveQPodNames) { 640 diff := cmp.Diff(test.wantActiveQPodNames, rawPodInfos, cmpopts.SortSlices(func(a, b interface{}) bool { 641 return a.(framework.PodInfo).Pod.Name < b.(framework.PodInfo).Pod.Name 642 })) 643 t.Fatalf("Length of activeQ is not expected. Got %v, want %v.\n%s", len(rawPodInfos), len(test.wantActiveQPodNames), diff) 644 } 645 646 wantPodNames := sets.New(test.wantActiveQPodNames...) 647 for _, rawPodInfo := range rawPodInfos { 648 podGotFromActiveQ := rawPodInfo.(*framework.QueuedPodInfo).Pod 649 if !wantPodNames.Has(podGotFromActiveQ.Name) { 650 t.Fatalf("Pod %v was not expected to be in the activeQ.", podGotFromActiveQ.Name) 651 } 652 } 653 } 654 655 if test.wantBackoffQPodNames != nil { 656 rawPodInfos := q.podBackoffQ.List() 657 if len(rawPodInfos) != len(test.wantBackoffQPodNames) { 658 diff := cmp.Diff(test.wantBackoffQPodNames, rawPodInfos, cmpopts.SortSlices(func(a, b interface{}) bool { 659 return a.(framework.PodInfo).Pod.Name < b.(framework.PodInfo).Pod.Name 660 })) 661 t.Fatalf("Length of backoffQ is not expected. Got %v, want %v.\n%s", len(rawPodInfos), len(test.wantBackoffQPodNames), diff) 662 } 663 664 wantPodNames := sets.New(test.wantBackoffQPodNames...) 665 for _, rawPodInfo := range rawPodInfos { 666 podGotFromBackoffQ := rawPodInfo.(*framework.QueuedPodInfo).Pod 667 if !wantPodNames.Has(podGotFromBackoffQ.Name) { 668 t.Fatalf("Pod %v was not expected to be in the backoffQ.", podGotFromBackoffQ.Name) 669 } 670 } 671 } 672 673 for _, podName := range test.wantUnschedPodPoolPodNames { 674 p := getUnschedulablePod(q, &st.MakePod().Name(podName).Pod) 675 if p == nil { 676 t.Fatalf("Pod %v was not found in the unschedulablePods.", podName) 677 } 678 } 679 }) 680 } 681 } 682 683 func popPod(t *testing.T, logger klog.Logger, q *PriorityQueue, pod *v1.Pod) *framework.QueuedPodInfo { 684 p, err := q.Pop(logger) 685 if err != nil { 686 t.Fatalf("Pop failed: %v", err) 687 } 688 if p.Pod.UID != pod.UID { 689 t.Errorf("Unexpected popped pod: %v", p) 690 } 691 return p 692 } 693 694 func TestPop(t *testing.T) { 695 pod := st.MakePod().Name("targetpod").UID("pod1").Obj() 696 queueingHintMap := QueueingHintMapPerProfile{ 697 "": { 698 PvAdd: { 699 { 700 // The hint fn tells that this event makes a Pod scheudlable. 701 PluginName: "fooPlugin1", 702 QueueingHintFn: queueHintReturnQueue, 703 }, 704 }, 705 }, 706 } 707 708 for name, isSchedulingQueueHintEnabled := range map[string]bool{"with-hints": true, "without-hints": false} { 709 t.Run(name, func(t *testing.T) { 710 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.SchedulerQueueingHints, isSchedulingQueueHintEnabled)() 711 logger, ctx := ktesting.NewTestContext(t) 712 ctx, cancel := context.WithCancel(ctx) 713 defer cancel() 714 q := NewTestQueueWithObjects(ctx, newDefaultQueueSort(), []runtime.Object{pod}, WithQueueingHintMapPerProfile(queueingHintMap)) 715 q.Add(logger, pod) 716 717 // Simulate failed attempt that makes the pod unschedulable. 718 poppedPod := popPod(t, logger, q, pod) 719 // We put register the plugin to PendingPlugins so that it's interpreted as queueImmediately and skip backoff. 720 poppedPod.PendingPlugins = sets.New("fooPlugin1") 721 if err := q.AddUnschedulableIfNotPresent(logger, poppedPod, q.SchedulingCycle()); err != nil { 722 t.Errorf("Unexpected error from AddUnschedulableIfNotPresent: %v", err) 723 } 724 725 // Activate it again. 726 q.MoveAllToActiveOrBackoffQueue(logger, PvAdd, nil, nil, nil) 727 728 // Now check result of Pop. 729 poppedPod = popPod(t, logger, q, pod) 730 if len(poppedPod.PendingPlugins) > 0 { 731 t.Errorf("QueuedPodInfo from Pop should have empty PendingPlugins, got instead: %+v", poppedPod) 732 } 733 }) 734 } 735 } 736 737 func TestPriorityQueue_AddUnschedulableIfNotPresent(t *testing.T) { 738 objs := []runtime.Object{highPriNominatedPodInfo.Pod, unschedulablePodInfo.Pod} 739 logger, ctx := ktesting.NewTestContext(t) 740 ctx, cancel := context.WithCancel(ctx) 741 defer cancel() 742 q := NewTestQueueWithObjects(ctx, newDefaultQueueSort(), objs) 743 // insert unschedulablePodInfo and pop right after that 744 // because the scheduling queue records unschedulablePod as in-flight Pod. 745 q.Add(logger, unschedulablePodInfo.Pod) 746 if p, err := q.Pop(logger); err != nil || p.Pod != unschedulablePodInfo.Pod { 747 t.Errorf("Expected: %v after Pop, but got: %v", unschedulablePodInfo.Pod.Name, p.Pod.Name) 748 } 749 750 q.Add(logger, highPriNominatedPodInfo.Pod) 751 err := q.AddUnschedulableIfNotPresent(logger, newQueuedPodInfoForLookup(unschedulablePodInfo.Pod, "plugin"), q.SchedulingCycle()) 752 if err != nil { 753 t.Fatalf("unexpected error from AddUnschedulableIfNotPresent: %v", err) 754 } 755 expectedNominatedPods := &nominator{ 756 nominatedPodToNode: map[types.UID]string{ 757 unschedulablePodInfo.Pod.UID: "node1", 758 highPriNominatedPodInfo.Pod.UID: "node1", 759 }, 760 nominatedPods: map[string][]*framework.PodInfo{ 761 "node1": {highPriNominatedPodInfo, unschedulablePodInfo}, 762 }, 763 } 764 if diff := cmp.Diff(q.nominator, expectedNominatedPods, nominatorCmpOpts...); diff != "" { 765 t.Errorf("Unexpected diff after adding pods (-want, +got):\n%s", diff) 766 } 767 if p, err := q.Pop(logger); err != nil || p.Pod != highPriNominatedPodInfo.Pod { 768 t.Errorf("Expected: %v after Pop, but got: %v", highPriNominatedPodInfo.Pod.Name, p.Pod.Name) 769 } 770 if len(q.nominator.nominatedPods) != 1 { 771 t.Errorf("Expected nomindatePods to have one element: %v", q.nominator) 772 } 773 // unschedulablePodInfo is inserted to unschedulable pod pool because no events happened during scheduling. 774 if getUnschedulablePod(q, unschedulablePodInfo.Pod) != unschedulablePodInfo.Pod { 775 t.Errorf("Pod %v was not found in the unschedulablePods.", unschedulablePodInfo.Pod.Name) 776 } 777 } 778 779 // TestPriorityQueue_AddUnschedulableIfNotPresent_Backoff tests the scenarios when 780 // AddUnschedulableIfNotPresent is called asynchronously. 781 // Pods in and before current scheduling cycle will be put back to activeQueue 782 // if we were trying to schedule them when we received move request. 783 func TestPriorityQueue_AddUnschedulableIfNotPresent_Backoff(t *testing.T) { 784 logger, ctx := ktesting.NewTestContext(t) 785 ctx, cancel := context.WithCancel(ctx) 786 defer cancel() 787 q := NewTestQueue(ctx, newDefaultQueueSort(), WithClock(testingclock.NewFakeClock(time.Now()))) 788 totalNum := 10 789 expectedPods := make([]v1.Pod, 0, totalNum) 790 for i := 0; i < totalNum; i++ { 791 priority := int32(i) 792 p := st.MakePod().Name(fmt.Sprintf("pod%d", i)).Namespace(fmt.Sprintf("ns%d", i)).UID(fmt.Sprintf("upns%d", i)).Priority(priority).Obj() 793 expectedPods = append(expectedPods, *p) 794 // priority is to make pods ordered in the PriorityQueue 795 q.Add(logger, p) 796 } 797 798 // Pop all pods except for the first one 799 for i := totalNum - 1; i > 0; i-- { 800 p, _ := q.Pop(logger) 801 if diff := cmp.Diff(&expectedPods[i], p.Pod); diff != "" { 802 t.Errorf("Unexpected pod (-want, +got):\n%s", diff) 803 } 804 } 805 806 // move all pods to active queue when we were trying to schedule them 807 q.MoveAllToActiveOrBackoffQueue(logger, WildCardEvent, nil, nil, nil) 808 oldCycle := q.SchedulingCycle() 809 810 firstPod, _ := q.Pop(logger) 811 if diff := cmp.Diff(&expectedPods[0], firstPod.Pod); diff != "" { 812 t.Errorf("Unexpected pod (-want, +got):\n%s", diff) 813 } 814 815 // mark pods[1] ~ pods[totalNum-1] as unschedulable and add them back 816 for i := 1; i < totalNum; i++ { 817 unschedulablePod := expectedPods[i].DeepCopy() 818 unschedulablePod.Status = v1.PodStatus{ 819 Conditions: []v1.PodCondition{ 820 { 821 Type: v1.PodScheduled, 822 Status: v1.ConditionFalse, 823 Reason: v1.PodReasonUnschedulable, 824 }, 825 }, 826 } 827 828 err := q.AddUnschedulableIfNotPresent(logger, newQueuedPodInfoForLookup(unschedulablePod, "plugin"), oldCycle) 829 if err != nil { 830 t.Fatalf("unexpected error from AddUnschedulableIfNotPresent: %v", err) 831 } 832 } 833 834 // Since there was a move request at the same cycle as "oldCycle", these pods 835 // should be in the backoff queue. 836 for i := 1; i < totalNum; i++ { 837 if _, exists, _ := q.podBackoffQ.Get(newQueuedPodInfoForLookup(&expectedPods[i])); !exists { 838 t.Errorf("Expected %v to be added to podBackoffQ.", expectedPods[i].Name) 839 } 840 } 841 } 842 843 func TestPriorityQueue_Pop(t *testing.T) { 844 objs := []runtime.Object{medPriorityPodInfo.Pod} 845 logger, ctx := ktesting.NewTestContext(t) 846 ctx, cancel := context.WithCancel(ctx) 847 defer cancel() 848 q := NewTestQueueWithObjects(ctx, newDefaultQueueSort(), objs) 849 wg := sync.WaitGroup{} 850 wg.Add(1) 851 go func() { 852 defer wg.Done() 853 if p, err := q.Pop(logger); err != nil || p.Pod != medPriorityPodInfo.Pod { 854 t.Errorf("Expected: %v after Pop, but got: %v", medPriorityPodInfo.Pod.Name, p.Pod.Name) 855 } 856 if len(q.nominator.nominatedPods["node1"]) != 1 { 857 t.Errorf("Expected medPriorityPodInfo to be present in nomindatePods: %v", q.nominator.nominatedPods["node1"]) 858 } 859 }() 860 q.Add(logger, medPriorityPodInfo.Pod) 861 wg.Wait() 862 } 863 864 func TestPriorityQueue_Update(t *testing.T) { 865 objs := []runtime.Object{highPriorityPodInfo.Pod, unschedulablePodInfo.Pod, medPriorityPodInfo.Pod} 866 c := testingclock.NewFakeClock(time.Now()) 867 logger, ctx := ktesting.NewTestContext(t) 868 ctx, cancel := context.WithCancel(ctx) 869 defer cancel() 870 q := NewTestQueueWithObjects(ctx, newDefaultQueueSort(), objs, WithClock(c)) 871 // add highPriorityPodInfo to activeQ. 872 q.Update(logger, nil, highPriorityPodInfo.Pod) 873 if _, exists, _ := q.activeQ.Get(newQueuedPodInfoForLookup(highPriorityPodInfo.Pod)); !exists { 874 t.Errorf("Expected %v to be added to activeQ.", highPriorityPodInfo.Pod.Name) 875 } 876 if len(q.nominator.nominatedPods) != 0 { 877 t.Errorf("Expected nomindatePods to be empty: %v", q.nominator) 878 } 879 // Update highPriorityPodInfo and add a nominatedNodeName to it. 880 q.Update(logger, highPriorityPodInfo.Pod, highPriNominatedPodInfo.Pod) 881 if q.activeQ.Len() != 1 { 882 t.Error("Expected only one item in activeQ.") 883 } 884 if len(q.nominator.nominatedPods) != 1 { 885 t.Errorf("Expected one item in nomindatePods map: %v", q.nominator) 886 } 887 // Updating an unschedulable pod which is not in any of the two queues, should 888 // add the pod to activeQ. 889 q.Update(logger, unschedulablePodInfo.Pod, unschedulablePodInfo.Pod) 890 if _, exists, _ := q.activeQ.Get(newQueuedPodInfoForLookup(unschedulablePodInfo.Pod)); !exists { 891 t.Errorf("Expected %v to be added to activeQ.", unschedulablePodInfo.Pod.Name) 892 } 893 // Updating a pod that is already in activeQ, should not change it. 894 q.Update(logger, unschedulablePodInfo.Pod, unschedulablePodInfo.Pod) 895 if len(q.unschedulablePods.podInfoMap) != 0 { 896 t.Error("Expected unschedulablePods to be empty.") 897 } 898 if _, exists, _ := q.activeQ.Get(newQueuedPodInfoForLookup(unschedulablePodInfo.Pod)); !exists { 899 t.Errorf("Expected: %v to be added to activeQ.", unschedulablePodInfo.Pod.Name) 900 } 901 if p, err := q.Pop(logger); err != nil || p.Pod != highPriNominatedPodInfo.Pod { 902 t.Errorf("Expected: %v after Pop, but got: %v", highPriorityPodInfo.Pod.Name, p.Pod.Name) 903 } 904 905 // Updating a pod that is in backoff queue and it is still backing off 906 // pod will not be moved to active queue, and it will be updated in backoff queue 907 podInfo := q.newQueuedPodInfo(medPriorityPodInfo.Pod) 908 if err := q.podBackoffQ.Add(podInfo); err != nil { 909 t.Errorf("adding pod to backoff queue error: %v", err) 910 } 911 q.Update(logger, podInfo.Pod, podInfo.Pod) 912 rawPodInfo, err := q.podBackoffQ.Pop() 913 podGotFromBackoffQ := rawPodInfo.(*framework.QueuedPodInfo).Pod 914 if err != nil || podGotFromBackoffQ != medPriorityPodInfo.Pod { 915 t.Errorf("Expected: %v after Pop, but got: %v", medPriorityPodInfo.Pod.Name, podGotFromBackoffQ.Name) 916 } 917 918 // To simulate the pod is failed in scheduling in the real world, Pop() the pod from activeQ before testing AddUnschedulableIfNotPresent. 919 q.activeQ.Add(podInfo) 920 if p, err := q.Pop(logger); err != nil || p.Pod != medPriorityPodInfo.Pod { 921 t.Errorf("Expected: %v after Pop, but got: %v", medPriorityPodInfo.Pod.Name, p.Pod.Name) 922 } 923 err = q.AddUnschedulableIfNotPresent(logger, q.newQueuedPodInfo(medPriorityPodInfo.Pod, "plugin"), q.SchedulingCycle()) 924 if err != nil { 925 t.Fatalf("unexpected error from AddUnschedulableIfNotPresent: %v", err) 926 } 927 if len(q.unschedulablePods.podInfoMap) != 1 { 928 t.Error("Expected unschedulablePods to be 1.") 929 } 930 updatedPod := medPriorityPodInfo.Pod.DeepCopy() 931 updatedPod.Annotations["foo"] = "test" 932 q.Update(logger, medPriorityPodInfo.Pod, updatedPod) 933 rawPodInfo, err = q.podBackoffQ.Pop() 934 podGotFromBackoffQ = rawPodInfo.(*framework.QueuedPodInfo).Pod 935 if err != nil || podGotFromBackoffQ != updatedPod { 936 t.Errorf("Expected: %v after Pop, but got: %v", updatedPod.Name, podGotFromBackoffQ.Name) 937 } 938 939 // To simulate the pod is failed in scheduling in the real world, Pop() the pod from activeQ before testing AddUnschedulableIfNotPresent. 940 err = q.activeQ.Add(podInfo) 941 if err != nil { 942 t.Fatalf("unexpected error from activeQ.Add: %v", err) 943 } 944 if p, err := q.Pop(logger); err != nil || p.Pod != medPriorityPodInfo.Pod { 945 t.Errorf("Expected: %v after Pop, but got: %v", medPriorityPodInfo.Pod.Name, p.Pod.Name) 946 } 947 // updating a pod which is in unschedulable queue, and it is not backing off, 948 // we will move it to active queue 949 err = q.AddUnschedulableIfNotPresent(logger, q.newQueuedPodInfo(medPriorityPodInfo.Pod, "plugin"), q.SchedulingCycle()) 950 if err != nil { 951 t.Fatalf("unexpected error from AddUnschedulableIfNotPresent: %v", err) 952 } 953 if len(q.unschedulablePods.podInfoMap) != 1 { 954 t.Error("Expected unschedulablePods to be 1.") 955 } 956 updatedPod = medPriorityPodInfo.Pod.DeepCopy() 957 updatedPod.Annotations["foo"] = "test1" 958 // Move clock by podInitialBackoffDuration, so that pods in the unschedulablePods would pass the backing off, 959 // and the pods will be moved into activeQ. 960 c.Step(q.podInitialBackoffDuration) 961 q.Update(logger, medPriorityPodInfo.Pod, updatedPod) 962 if p, err := q.Pop(logger); err != nil || p.Pod != updatedPod { 963 t.Errorf("Expected: %v after Pop, but got: %v", updatedPod.Name, p.Pod.Name) 964 } 965 } 966 967 func TestPriorityQueue_Delete(t *testing.T) { 968 objs := []runtime.Object{highPriorityPodInfo.Pod, unschedulablePodInfo.Pod} 969 logger, ctx := ktesting.NewTestContext(t) 970 ctx, cancel := context.WithCancel(ctx) 971 defer cancel() 972 q := NewTestQueueWithObjects(ctx, newDefaultQueueSort(), objs) 973 q.Update(logger, highPriorityPodInfo.Pod, highPriNominatedPodInfo.Pod) 974 q.Add(logger, unschedulablePodInfo.Pod) 975 if err := q.Delete(highPriNominatedPodInfo.Pod); err != nil { 976 t.Errorf("delete failed: %v", err) 977 } 978 if _, exists, _ := q.activeQ.Get(newQueuedPodInfoForLookup(unschedulablePodInfo.Pod)); !exists { 979 t.Errorf("Expected %v to be in activeQ.", unschedulablePodInfo.Pod.Name) 980 } 981 if _, exists, _ := q.activeQ.Get(newQueuedPodInfoForLookup(highPriNominatedPodInfo.Pod)); exists { 982 t.Errorf("Didn't expect %v to be in activeQ.", highPriorityPodInfo.Pod.Name) 983 } 984 if len(q.nominator.nominatedPods) != 1 { 985 t.Errorf("Expected nomindatePods to have only 'unschedulablePodInfo': %v", q.nominator.nominatedPods) 986 } 987 if err := q.Delete(unschedulablePodInfo.Pod); err != nil { 988 t.Errorf("delete failed: %v", err) 989 } 990 if len(q.nominator.nominatedPods) != 0 { 991 t.Errorf("Expected nomindatePods to be empty: %v", q.nominator) 992 } 993 } 994 995 func TestPriorityQueue_Activate(t *testing.T) { 996 tests := []struct { 997 name string 998 qPodInfoInUnschedulablePods []*framework.QueuedPodInfo 999 qPodInfoInPodBackoffQ []*framework.QueuedPodInfo 1000 qPodInfoInActiveQ []*framework.QueuedPodInfo 1001 qPodInfoToActivate *framework.QueuedPodInfo 1002 want []*framework.QueuedPodInfo 1003 }{ 1004 { 1005 name: "pod already in activeQ", 1006 qPodInfoInActiveQ: []*framework.QueuedPodInfo{{PodInfo: highPriNominatedPodInfo}}, 1007 qPodInfoToActivate: &framework.QueuedPodInfo{PodInfo: highPriNominatedPodInfo}, 1008 want: []*framework.QueuedPodInfo{{PodInfo: highPriNominatedPodInfo}}, // 1 already active 1009 }, 1010 { 1011 name: "pod not in unschedulablePods/podBackoffQ", 1012 qPodInfoToActivate: &framework.QueuedPodInfo{PodInfo: highPriNominatedPodInfo}, 1013 want: []*framework.QueuedPodInfo{}, 1014 }, 1015 { 1016 name: "pod in unschedulablePods", 1017 qPodInfoInUnschedulablePods: []*framework.QueuedPodInfo{{PodInfo: highPriNominatedPodInfo}}, 1018 qPodInfoToActivate: &framework.QueuedPodInfo{PodInfo: highPriNominatedPodInfo}, 1019 want: []*framework.QueuedPodInfo{{PodInfo: highPriNominatedPodInfo}}, 1020 }, 1021 { 1022 name: "pod in backoffQ", 1023 qPodInfoInPodBackoffQ: []*framework.QueuedPodInfo{{PodInfo: highPriNominatedPodInfo}}, 1024 qPodInfoToActivate: &framework.QueuedPodInfo{PodInfo: highPriNominatedPodInfo}, 1025 want: []*framework.QueuedPodInfo{{PodInfo: highPriNominatedPodInfo}}, 1026 }, 1027 } 1028 1029 for _, tt := range tests { 1030 t.Run(tt.name, func(t *testing.T) { 1031 var objs []runtime.Object 1032 logger, ctx := ktesting.NewTestContext(t) 1033 ctx, cancel := context.WithCancel(ctx) 1034 defer cancel() 1035 q := NewTestQueueWithObjects(ctx, newDefaultQueueSort(), objs) 1036 1037 // Prepare activeQ/unschedulablePods/podBackoffQ according to the table 1038 for _, qPodInfo := range tt.qPodInfoInActiveQ { 1039 q.activeQ.Add(qPodInfo) 1040 } 1041 1042 for _, qPodInfo := range tt.qPodInfoInUnschedulablePods { 1043 q.unschedulablePods.addOrUpdate(qPodInfo) 1044 } 1045 1046 for _, qPodInfo := range tt.qPodInfoInPodBackoffQ { 1047 q.podBackoffQ.Add(qPodInfo) 1048 } 1049 1050 // Activate specific pod according to the table 1051 q.Activate(logger, map[string]*v1.Pod{"test_pod": tt.qPodInfoToActivate.PodInfo.Pod}) 1052 1053 // Check the result after activation by the length of activeQ 1054 if wantLen := len(tt.want); q.activeQ.Len() != wantLen { 1055 t.Errorf("length compare: want %v, got %v", wantLen, q.activeQ.Len()) 1056 } 1057 1058 // Check if the specific pod exists in activeQ 1059 for _, want := range tt.want { 1060 if _, exist, _ := q.activeQ.Get(newQueuedPodInfoForLookup(want.PodInfo.Pod)); !exist { 1061 t.Errorf("podInfo not exist in activeQ: want %v", want.PodInfo.Pod.Name) 1062 } 1063 } 1064 }) 1065 } 1066 } 1067 1068 type preEnqueuePlugin struct { 1069 allowlists []string 1070 } 1071 1072 func (pl *preEnqueuePlugin) Name() string { 1073 return "preEnqueuePlugin" 1074 } 1075 1076 func (pl *preEnqueuePlugin) PreEnqueue(ctx context.Context, p *v1.Pod) *framework.Status { 1077 for _, allowed := range pl.allowlists { 1078 for label := range p.Labels { 1079 if label == allowed { 1080 return nil 1081 } 1082 } 1083 } 1084 return framework.NewStatus(framework.UnschedulableAndUnresolvable, "pod name not in allowlists") 1085 } 1086 1087 func TestPriorityQueue_addToActiveQ(t *testing.T) { 1088 tests := []struct { 1089 name string 1090 plugins []framework.PreEnqueuePlugin 1091 pod *v1.Pod 1092 wantUnschedulablePods int 1093 wantSuccess bool 1094 }{ 1095 { 1096 name: "no plugins registered", 1097 pod: st.MakePod().Name("p").Label("p", "").Obj(), 1098 wantUnschedulablePods: 0, 1099 wantSuccess: true, 1100 }, 1101 { 1102 name: "preEnqueue plugin registered, pod name not in allowlists", 1103 plugins: []framework.PreEnqueuePlugin{&preEnqueuePlugin{}, &preEnqueuePlugin{}}, 1104 pod: st.MakePod().Name("p").Label("p", "").Obj(), 1105 wantUnschedulablePods: 1, 1106 wantSuccess: false, 1107 }, 1108 { 1109 name: "preEnqueue plugin registered, pod failed one preEnqueue plugin", 1110 plugins: []framework.PreEnqueuePlugin{ 1111 &preEnqueuePlugin{allowlists: []string{"foo", "bar"}}, 1112 &preEnqueuePlugin{allowlists: []string{"foo"}}, 1113 }, 1114 pod: st.MakePod().Name("bar").Label("bar", "").Obj(), 1115 wantUnschedulablePods: 1, 1116 wantSuccess: false, 1117 }, 1118 { 1119 name: "preEnqueue plugin registered, pod passed all preEnqueue plugins", 1120 plugins: []framework.PreEnqueuePlugin{ 1121 &preEnqueuePlugin{allowlists: []string{"foo", "bar"}}, 1122 &preEnqueuePlugin{allowlists: []string{"bar"}}, 1123 }, 1124 pod: st.MakePod().Name("bar").Label("bar", "").Obj(), 1125 wantUnschedulablePods: 0, 1126 wantSuccess: true, 1127 }, 1128 } 1129 1130 for _, tt := range tests { 1131 t.Run(tt.name, func(t *testing.T) { 1132 ctx, cancel := context.WithCancel(context.Background()) 1133 logger := klog.FromContext(ctx) 1134 defer cancel() 1135 1136 m := map[string][]framework.PreEnqueuePlugin{"": tt.plugins} 1137 q := NewTestQueueWithObjects(ctx, newDefaultQueueSort(), []runtime.Object{tt.pod}, WithPreEnqueuePluginMap(m), 1138 WithPodInitialBackoffDuration(time.Second*30), WithPodMaxBackoffDuration(time.Second*60)) 1139 got, _ := q.addToActiveQ(logger, q.newQueuedPodInfo(tt.pod)) 1140 if got != tt.wantSuccess { 1141 t.Errorf("Unexpected result: want %v, but got %v", tt.wantSuccess, got) 1142 } 1143 if tt.wantUnschedulablePods != len(q.unschedulablePods.podInfoMap) { 1144 t.Errorf("Unexpected unschedulablePods: want %v, but got %v", tt.wantUnschedulablePods, len(q.unschedulablePods.podInfoMap)) 1145 } 1146 1147 // Simulate an update event. 1148 clone := tt.pod.DeepCopy() 1149 metav1.SetMetaDataAnnotation(&clone.ObjectMeta, "foo", "") 1150 q.Update(logger, tt.pod, clone) 1151 // Ensure the pod is still located in unschedulablePods. 1152 if tt.wantUnschedulablePods != len(q.unschedulablePods.podInfoMap) { 1153 t.Errorf("Unexpected unschedulablePods: want %v, but got %v", tt.wantUnschedulablePods, len(q.unschedulablePods.podInfoMap)) 1154 } 1155 }) 1156 } 1157 } 1158 1159 func BenchmarkMoveAllToActiveOrBackoffQueue(b *testing.B) { 1160 tests := []struct { 1161 name string 1162 moveEvent framework.ClusterEvent 1163 }{ 1164 { 1165 name: "baseline", 1166 moveEvent: UnschedulableTimeout, 1167 }, 1168 { 1169 name: "worst", 1170 moveEvent: NodeAdd, 1171 }, 1172 { 1173 name: "random", 1174 // leave "moveEvent" unspecified 1175 }, 1176 } 1177 1178 podTemplates := []*v1.Pod{ 1179 highPriorityPodInfo.Pod, highPriNominatedPodInfo.Pod, 1180 medPriorityPodInfo.Pod, unschedulablePodInfo.Pod, 1181 } 1182 1183 events := []framework.ClusterEvent{ 1184 NodeAdd, 1185 NodeTaintChange, 1186 NodeAllocatableChange, 1187 NodeConditionChange, 1188 NodeLabelChange, 1189 PvcAdd, 1190 PvcUpdate, 1191 PvAdd, 1192 PvUpdate, 1193 StorageClassAdd, 1194 StorageClassUpdate, 1195 CSINodeAdd, 1196 CSINodeUpdate, 1197 CSIDriverAdd, 1198 CSIDriverUpdate, 1199 CSIStorageCapacityAdd, 1200 CSIStorageCapacityUpdate, 1201 } 1202 1203 pluginNum := 20 1204 var plugins []string 1205 // Mimic that we have 20 plugins loaded in runtime. 1206 for i := 0; i < pluginNum; i++ { 1207 plugins = append(plugins, fmt.Sprintf("fake-plugin-%v", i)) 1208 } 1209 1210 for _, tt := range tests { 1211 for _, podsInUnschedulablePods := range []int{1000, 5000} { 1212 b.Run(fmt.Sprintf("%v-%v", tt.name, podsInUnschedulablePods), func(b *testing.B) { 1213 logger, _ := ktesting.NewTestContext(b) 1214 for i := 0; i < b.N; i++ { 1215 b.StopTimer() 1216 c := testingclock.NewFakeClock(time.Now()) 1217 1218 m := makeEmptyQueueingHintMapPerProfile() 1219 // - All plugins registered for events[0], which is NodeAdd. 1220 // - 1/2 of plugins registered for events[1] 1221 // - 1/3 of plugins registered for events[2] 1222 // - ... 1223 for j := 0; j < len(events); j++ { 1224 for k := 0; k < len(plugins); k++ { 1225 if (k+1)%(j+1) == 0 { 1226 m[""][events[j]] = append(m[""][events[j]], &QueueingHintFunction{ 1227 PluginName: plugins[k], 1228 QueueingHintFn: queueHintReturnQueue, 1229 }) 1230 } 1231 } 1232 } 1233 1234 ctx, cancel := context.WithCancel(context.Background()) 1235 defer cancel() 1236 q := NewTestQueue(ctx, newDefaultQueueSort(), WithClock(c), WithQueueingHintMapPerProfile(m)) 1237 1238 // Init pods in unschedulablePods. 1239 for j := 0; j < podsInUnschedulablePods; j++ { 1240 p := podTemplates[j%len(podTemplates)].DeepCopy() 1241 p.Name, p.UID = fmt.Sprintf("%v-%v", p.Name, j), types.UID(fmt.Sprintf("%v-%v", p.UID, j)) 1242 var podInfo *framework.QueuedPodInfo 1243 // The ultimate goal of composing each PodInfo is to cover the path that intersects 1244 // (unschedulable) plugin names with the plugins that register the moveEvent, 1245 // here the rational is: 1246 // - in baseline case, don't inject unschedulable plugin names, so podMatchesEvent() 1247 // never gets executed. 1248 // - in worst case, make both ends (of the intersection) a big number,i.e., 1249 // M intersected with N instead of M with 1 (or 1 with N) 1250 // - in random case, each pod failed by a random plugin, and also the moveEvent 1251 // is randomized. 1252 if tt.name == "baseline" { 1253 podInfo = q.newQueuedPodInfo(p) 1254 } else if tt.name == "worst" { 1255 // Each pod failed by all plugins. 1256 podInfo = q.newQueuedPodInfo(p, plugins...) 1257 } else { 1258 // Random case. 1259 podInfo = q.newQueuedPodInfo(p, plugins[j%len(plugins)]) 1260 } 1261 err := q.AddUnschedulableIfNotPresent(logger, podInfo, q.SchedulingCycle()) 1262 if err != nil { 1263 b.Fatalf("unexpected error from AddUnschedulableIfNotPresent: %v", err) 1264 } 1265 } 1266 1267 b.StartTimer() 1268 if tt.moveEvent.Resource != "" { 1269 q.MoveAllToActiveOrBackoffQueue(logger, tt.moveEvent, nil, nil, nil) 1270 } else { 1271 // Random case. 1272 q.MoveAllToActiveOrBackoffQueue(logger, events[i%len(events)], nil, nil, nil) 1273 } 1274 } 1275 }) 1276 } 1277 } 1278 } 1279 1280 func TestPriorityQueue_MoveAllToActiveOrBackoffQueueWithQueueingHint(t *testing.T) { 1281 now := time.Now() 1282 p := st.MakePod().Name("pod1").Namespace("ns1").UID("1").Obj() 1283 tests := []struct { 1284 name string 1285 podInfo *framework.QueuedPodInfo 1286 hint framework.QueueingHintFn 1287 // duration is the duration that the Pod has been in the unschedulable queue. 1288 duration time.Duration 1289 // expectedQ is the queue name (activeQ, backoffQ, or unschedulablePods) that this Pod should be quened to. 1290 expectedQ string 1291 }{ 1292 { 1293 name: "Queue queues pod to activeQ", 1294 podInfo: &framework.QueuedPodInfo{PodInfo: mustNewPodInfo(p), PendingPlugins: sets.New("foo")}, 1295 hint: queueHintReturnQueue, 1296 expectedQ: activeQ, 1297 }, 1298 { 1299 name: "Queue queues pod to backoffQ if Pod is backing off", 1300 podInfo: &framework.QueuedPodInfo{PodInfo: mustNewPodInfo(p), UnschedulablePlugins: sets.New("foo")}, 1301 hint: queueHintReturnQueue, 1302 expectedQ: backoffQ, 1303 }, 1304 { 1305 name: "Queue queues pod to activeQ if Pod is not backing off", 1306 podInfo: &framework.QueuedPodInfo{PodInfo: mustNewPodInfo(p), UnschedulablePlugins: sets.New("foo")}, 1307 hint: queueHintReturnQueue, 1308 duration: DefaultPodInitialBackoffDuration, // backoff is finished 1309 expectedQ: activeQ, 1310 }, 1311 { 1312 name: "Skip queues pod to unschedulablePods", 1313 podInfo: &framework.QueuedPodInfo{PodInfo: mustNewPodInfo(p), UnschedulablePlugins: sets.New("foo")}, 1314 hint: queueHintReturnSkip, 1315 expectedQ: unschedulablePods, 1316 }, 1317 } 1318 1319 for _, test := range tests { 1320 t.Run(test.name, func(t *testing.T) { 1321 logger, ctx := ktesting.NewTestContext(t) 1322 m := makeEmptyQueueingHintMapPerProfile() 1323 m[""][NodeAdd] = []*QueueingHintFunction{ 1324 { 1325 PluginName: "foo", 1326 QueueingHintFn: test.hint, 1327 }, 1328 } 1329 test.podInfo.UnschedulablePlugins = sets.New("foo") 1330 cl := testingclock.NewFakeClock(now) 1331 q := NewTestQueue(ctx, newDefaultQueueSort(), WithQueueingHintMapPerProfile(m), WithClock(cl)) 1332 // add to unsched pod pool 1333 q.activeQ.Add(q.newQueuedPodInfo(test.podInfo.Pod)) 1334 if p, err := q.Pop(logger); err != nil || p.Pod != test.podInfo.Pod { 1335 t.Errorf("Expected: %v after Pop, but got: %v", test.podInfo.Pod.Name, p.Pod.Name) 1336 } 1337 err := q.AddUnschedulableIfNotPresent(logger, test.podInfo, q.SchedulingCycle()) 1338 if err != nil { 1339 t.Fatalf("unexpected error from AddUnschedulableIfNotPresent: %v", err) 1340 } 1341 cl.Step(test.duration) 1342 1343 q.MoveAllToActiveOrBackoffQueue(logger, NodeAdd, nil, nil, nil) 1344 1345 if q.podBackoffQ.Len() == 0 && test.expectedQ == backoffQ { 1346 t.Fatalf("expected pod to be queued to backoffQ, but it was not") 1347 } 1348 1349 if q.activeQ.Len() == 0 && test.expectedQ == activeQ { 1350 t.Fatalf("expected pod to be queued to activeQ, but it was not") 1351 } 1352 1353 if q.unschedulablePods.get(test.podInfo.Pod) == nil && test.expectedQ == unschedulablePods { 1354 t.Fatalf("expected pod to be queued to unschedulablePods, but it was not") 1355 } 1356 }) 1357 } 1358 } 1359 1360 func TestPriorityQueue_MoveAllToActiveOrBackoffQueue(t *testing.T) { 1361 c := testingclock.NewFakeClock(time.Now()) 1362 logger, ctx := ktesting.NewTestContext(t) 1363 ctx, cancel := context.WithCancel(ctx) 1364 defer cancel() 1365 m := makeEmptyQueueingHintMapPerProfile() 1366 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.SchedulerQueueingHints, true)() 1367 1368 m[""][NodeAdd] = []*QueueingHintFunction{ 1369 { 1370 PluginName: "fooPlugin", 1371 QueueingHintFn: queueHintReturnQueue, 1372 }, 1373 } 1374 q := NewTestQueue(ctx, newDefaultQueueSort(), WithClock(c), WithQueueingHintMapPerProfile(m)) 1375 // To simulate the pod is failed in scheduling in the real world, Pop() the pod from activeQ before AddUnschedulableIfNotPresent()s below. 1376 q.activeQ.Add(q.newQueuedPodInfo(unschedulablePodInfo.Pod)) 1377 if p, err := q.Pop(logger); err != nil || p.Pod != unschedulablePodInfo.Pod { 1378 t.Errorf("Expected: %v after Pop, but got: %v", unschedulablePodInfo.Pod.Name, p.Pod.Name) 1379 } 1380 expectInFlightPods(t, q, unschedulablePodInfo.Pod.UID) 1381 q.activeQ.Add(q.newQueuedPodInfo(highPriorityPodInfo.Pod)) 1382 if p, err := q.Pop(logger); err != nil || p.Pod != highPriorityPodInfo.Pod { 1383 t.Errorf("Expected: %v after Pop, but got: %v", highPriorityPodInfo.Pod.Name, p.Pod.Name) 1384 } 1385 expectInFlightPods(t, q, unschedulablePodInfo.Pod.UID, highPriorityPodInfo.Pod.UID) 1386 err := q.AddUnschedulableIfNotPresent(logger, q.newQueuedPodInfo(unschedulablePodInfo.Pod, "fooPlugin"), q.SchedulingCycle()) 1387 if err != nil { 1388 t.Fatalf("unexpected error from AddUnschedulableIfNotPresent: %v", err) 1389 } 1390 err = q.AddUnschedulableIfNotPresent(logger, q.newQueuedPodInfo(highPriorityPodInfo.Pod, "fooPlugin"), q.SchedulingCycle()) 1391 if err != nil { 1392 t.Fatalf("unexpected error from AddUnschedulableIfNotPresent: %v", err) 1393 } 1394 expectInFlightPods(t, q) 1395 // Construct a Pod, but don't associate its scheduler failure to any plugin 1396 hpp1 := clonePod(highPriorityPodInfo.Pod, "hpp1") 1397 q.activeQ.Add(q.newQueuedPodInfo(hpp1)) 1398 if p, err := q.Pop(logger); err != nil || p.Pod != hpp1 { 1399 t.Errorf("Expected: %v after Pop, but got: %v", hpp1, p.Pod.Name) 1400 } 1401 expectInFlightPods(t, q, hpp1.UID) 1402 // This Pod will go to backoffQ because no failure plugin is associated with it. 1403 err = q.AddUnschedulableIfNotPresent(logger, q.newQueuedPodInfo(hpp1), q.SchedulingCycle()) 1404 if err != nil { 1405 t.Fatalf("unexpected error from AddUnschedulableIfNotPresent: %v", err) 1406 } 1407 expectInFlightPods(t, q) 1408 // Construct another Pod, and associate its scheduler failure to plugin "barPlugin". 1409 hpp2 := clonePod(highPriorityPodInfo.Pod, "hpp2") 1410 q.activeQ.Add(q.newQueuedPodInfo(hpp2)) 1411 if p, err := q.Pop(logger); err != nil || p.Pod != hpp2 { 1412 t.Errorf("Expected: %v after Pop, but got: %v", hpp2, p.Pod.Name) 1413 } 1414 expectInFlightPods(t, q, hpp2.UID) 1415 // This Pod will go to the unschedulable Pod pool. 1416 err = q.AddUnschedulableIfNotPresent(logger, q.newQueuedPodInfo(hpp2, "barPlugin"), q.SchedulingCycle()) 1417 if err != nil { 1418 t.Fatalf("unexpected error from AddUnschedulableIfNotPresent: %v", err) 1419 } 1420 expectInFlightPods(t, q) 1421 // This NodeAdd event moves unschedulablePodInfo and highPriorityPodInfo to the backoffQ, 1422 // because of the queueing hint function registered for NodeAdd/fooPlugin. 1423 q.MoveAllToActiveOrBackoffQueue(logger, NodeAdd, nil, nil, nil) 1424 q.Add(logger, medPriorityPodInfo.Pod) 1425 if q.activeQ.Len() != 1 { 1426 t.Errorf("Expected 1 item to be in activeQ, but got: %v", q.activeQ.Len()) 1427 } 1428 // Pop out the medPriorityPodInfo in activeQ. 1429 if p, err := q.Pop(logger); err != nil || p.Pod != medPriorityPodInfo.Pod { 1430 t.Errorf("Expected: %v after Pop, but got: %v", medPriorityPodInfo.Pod, p.Pod.Name) 1431 } 1432 expectInFlightPods(t, q, medPriorityPodInfo.Pod.UID) 1433 // hpp2 won't be moved. 1434 if q.podBackoffQ.Len() != 3 { 1435 t.Fatalf("Expected 3 items to be in podBackoffQ, but got: %v", q.podBackoffQ.Len()) 1436 } 1437 1438 // pop out the pods in the backoffQ. 1439 // This doesn't make them in-flight pods. 1440 for q.podBackoffQ.Len() != 0 { 1441 q.podBackoffQ.Pop() 1442 } 1443 expectInFlightPods(t, q, medPriorityPodInfo.Pod.UID) 1444 1445 q.schedulingCycle++ 1446 q.activeQ.Add(q.newQueuedPodInfo(unschedulablePodInfo.Pod)) 1447 if p, err := q.Pop(logger); err != nil || p.Pod != unschedulablePodInfo.Pod { 1448 t.Errorf("Expected: %v after Pop, but got: %v", unschedulablePodInfo.Pod.Name, p.Pod.Name) 1449 } 1450 expectInFlightPods(t, q, medPriorityPodInfo.Pod.UID, unschedulablePodInfo.Pod.UID) 1451 q.activeQ.Add(q.newQueuedPodInfo(highPriorityPodInfo.Pod)) 1452 if p, err := q.Pop(logger); err != nil || p.Pod != highPriorityPodInfo.Pod { 1453 t.Errorf("Expected: %v after Pop, but got: %v", highPriorityPodInfo.Pod.Name, p.Pod.Name) 1454 } 1455 expectInFlightPods(t, q, medPriorityPodInfo.Pod.UID, unschedulablePodInfo.Pod.UID, highPriorityPodInfo.Pod.UID) 1456 q.activeQ.Add(q.newQueuedPodInfo(hpp1)) 1457 if p, err := q.Pop(logger); err != nil || p.Pod != hpp1 { 1458 t.Errorf("Expected: %v after Pop, but got: %v", hpp1, p.Pod.Name) 1459 } 1460 unschedulableQueuedPodInfo := q.newQueuedPodInfo(unschedulablePodInfo.Pod, "fooPlugin") 1461 highPriorityQueuedPodInfo := q.newQueuedPodInfo(highPriorityPodInfo.Pod, "fooPlugin") 1462 hpp1QueuedPodInfo := q.newQueuedPodInfo(hpp1) 1463 expectInFlightPods(t, q, medPriorityPodInfo.Pod.UID, unschedulablePodInfo.Pod.UID, highPriorityPodInfo.Pod.UID, hpp1.UID) 1464 err = q.AddUnschedulableIfNotPresent(logger, unschedulableQueuedPodInfo, q.SchedulingCycle()) 1465 if err != nil { 1466 t.Fatalf("unexpected error from AddUnschedulableIfNotPresent: %v", err) 1467 } 1468 expectInFlightPods(t, q, medPriorityPodInfo.Pod.UID, highPriorityPodInfo.Pod.UID, hpp1.UID) 1469 err = q.AddUnschedulableIfNotPresent(logger, highPriorityQueuedPodInfo, q.SchedulingCycle()) 1470 if err != nil { 1471 t.Fatalf("unexpected error from AddUnschedulableIfNotPresent: %v", err) 1472 } 1473 expectInFlightPods(t, q, medPriorityPodInfo.Pod.UID, hpp1.UID) 1474 err = q.AddUnschedulableIfNotPresent(logger, hpp1QueuedPodInfo, q.SchedulingCycle()) 1475 if err != nil { 1476 t.Fatalf("unexpected error from AddUnschedulableIfNotPresent: %v", err) 1477 } 1478 expectInFlightPods(t, q, medPriorityPodInfo.Pod.UID) 1479 q.Add(logger, medPriorityPodInfo.Pod) 1480 // hpp1 will go to backoffQ because no failure plugin is associated with it. 1481 // All plugins other than hpp1 are enqueued to the unschedulable Pod pool. 1482 for _, pod := range []*v1.Pod{unschedulablePodInfo.Pod, highPriorityPodInfo.Pod, hpp2} { 1483 if q.unschedulablePods.get(pod) == nil { 1484 t.Errorf("Expected %v in the unschedulablePods", pod.Name) 1485 } 1486 } 1487 if _, ok, _ := q.podBackoffQ.Get(hpp1QueuedPodInfo); !ok { 1488 t.Errorf("Expected %v in the podBackoffQ", hpp1.Name) 1489 } 1490 1491 // Move clock by podInitialBackoffDuration, so that pods in the unschedulablePods would pass the backing off, 1492 // and the pods will be moved into activeQ. 1493 c.Step(q.podInitialBackoffDuration) 1494 q.flushBackoffQCompleted(logger) // flush the completed backoffQ to move hpp1 to activeQ. 1495 q.MoveAllToActiveOrBackoffQueue(logger, NodeAdd, nil, nil, nil) 1496 if q.activeQ.Len() != 4 { 1497 t.Errorf("Expected 4 items to be in activeQ, but got: %v", q.activeQ.Len()) 1498 } 1499 if q.podBackoffQ.Len() != 0 { 1500 t.Errorf("Expected 0 item to be in podBackoffQ, but got: %v", q.podBackoffQ.Len()) 1501 } 1502 expectInFlightPods(t, q, medPriorityPodInfo.Pod.UID) 1503 if len(q.unschedulablePods.podInfoMap) != 1 { 1504 // hpp2 won't be moved regardless of its backoff timer. 1505 t.Errorf("Expected 1 item to be in unschedulablePods, but got: %v", len(q.unschedulablePods.podInfoMap)) 1506 } 1507 } 1508 1509 func clonePod(pod *v1.Pod, newName string) *v1.Pod { 1510 pod = pod.DeepCopy() 1511 pod.Name = newName 1512 pod.UID = types.UID(pod.Name + pod.Namespace) 1513 return pod 1514 } 1515 1516 func expectInFlightPods(t *testing.T, q *PriorityQueue, uids ...types.UID) { 1517 t.Helper() 1518 var actualUIDs []types.UID 1519 for uid := range q.inFlightPods { 1520 actualUIDs = append(actualUIDs, uid) 1521 } 1522 sortUIDs := cmpopts.SortSlices(func(a, b types.UID) bool { return a < b }) 1523 if diff := cmp.Diff(uids, actualUIDs, sortUIDs); diff != "" { 1524 t.Fatalf("Unexpected content of inFlightPods (-want, +have):\n%s", diff) 1525 } 1526 actualUIDs = nil 1527 for e := q.inFlightEvents.Front(); e != nil; e = e.Next() { 1528 if pod, ok := e.Value.(*v1.Pod); ok { 1529 actualUIDs = append(actualUIDs, pod.UID) 1530 } 1531 } 1532 if diff := cmp.Diff(uids, actualUIDs, sortUIDs); diff != "" { 1533 t.Fatalf("Unexpected pods in inFlightEvents (-want, +have):\n%s", diff) 1534 } 1535 } 1536 1537 // TestPriorityQueue_AssignedPodAdded tests AssignedPodAdded. It checks that 1538 // when a pod with pod affinity is in unschedulablePods and another pod with a 1539 // matching label is added, the unschedulable pod is moved to activeQ. 1540 func TestPriorityQueue_AssignedPodAdded(t *testing.T) { 1541 logger, ctx := ktesting.NewTestContext(t) 1542 ctx, cancel := context.WithCancel(ctx) 1543 defer cancel() 1544 1545 affinityPod := st.MakePod().Name("afp").Namespace("ns1").UID("afp").Annotation("annot2", "val2").Priority(mediumPriority).NominatedNodeName("node1").PodAffinityExists("service", "region", st.PodAffinityWithRequiredReq).Obj() 1546 labelPod := st.MakePod().Name("lbp").Namespace(affinityPod.Namespace).Label("service", "securityscan").Node("node1").Obj() 1547 1548 c := testingclock.NewFakeClock(time.Now()) 1549 m := makeEmptyQueueingHintMapPerProfile() 1550 m[""][AssignedPodAdd] = []*QueueingHintFunction{ 1551 { 1552 PluginName: "fakePlugin", 1553 QueueingHintFn: queueHintReturnQueue, 1554 }, 1555 } 1556 q := NewTestQueue(ctx, newDefaultQueueSort(), WithClock(c), WithQueueingHintMapPerProfile(m)) 1557 // To simulate the pod is failed in scheduling in the real world, Pop() the pod from activeQ before AddUnschedulableIfNotPresent()s below. 1558 q.activeQ.Add(q.newQueuedPodInfo(unschedulablePodInfo.Pod)) 1559 if p, err := q.Pop(logger); err != nil || p.Pod != unschedulablePodInfo.Pod { 1560 t.Errorf("Expected: %v after Pop, but got: %v", unschedulablePodInfo.Pod.Name, p.Pod.Name) 1561 } 1562 q.activeQ.Add(q.newQueuedPodInfo(affinityPod)) 1563 if p, err := q.Pop(logger); err != nil || p.Pod != affinityPod { 1564 t.Errorf("Expected: %v after Pop, but got: %v", affinityPod.Name, p.Pod.Name) 1565 } 1566 q.Add(logger, medPriorityPodInfo.Pod) 1567 err := q.AddUnschedulableIfNotPresent(logger, q.newQueuedPodInfo(unschedulablePodInfo.Pod, "fakePlugin"), q.SchedulingCycle()) 1568 if err != nil { 1569 t.Fatalf("unexpected error from AddUnschedulableIfNotPresent: %v", err) 1570 } 1571 err = q.AddUnschedulableIfNotPresent(logger, q.newQueuedPodInfo(affinityPod, "fakePlugin"), q.SchedulingCycle()) 1572 if err != nil { 1573 t.Fatalf("unexpected error from AddUnschedulableIfNotPresent: %v", err) 1574 } 1575 1576 // Move clock to make the unschedulable pods complete backoff. 1577 c.Step(DefaultPodInitialBackoffDuration + time.Second) 1578 // Simulate addition of an assigned pod. The pod has matching labels for 1579 // affinityPod. So, affinityPod should go to activeQ. 1580 q.AssignedPodAdded(logger, labelPod) 1581 if getUnschedulablePod(q, affinityPod) != nil { 1582 t.Error("affinityPod is still in the unschedulablePods.") 1583 } 1584 if _, exists, _ := q.activeQ.Get(newQueuedPodInfoForLookup(affinityPod)); !exists { 1585 t.Error("affinityPod is not moved to activeQ.") 1586 } 1587 // Check that the other pod is still in the unschedulablePods. 1588 if getUnschedulablePod(q, unschedulablePodInfo.Pod) == nil { 1589 t.Error("unschedulablePodInfo is not in the unschedulablePods.") 1590 } 1591 } 1592 1593 func TestPriorityQueue_NominatedPodsForNode(t *testing.T) { 1594 objs := []runtime.Object{medPriorityPodInfo.Pod, unschedulablePodInfo.Pod, highPriorityPodInfo.Pod} 1595 logger, ctx := ktesting.NewTestContext(t) 1596 ctx, cancel := context.WithCancel(ctx) 1597 defer cancel() 1598 q := NewTestQueueWithObjects(ctx, newDefaultQueueSort(), objs) 1599 q.Add(logger, medPriorityPodInfo.Pod) 1600 q.Add(logger, unschedulablePodInfo.Pod) 1601 q.Add(logger, highPriorityPodInfo.Pod) 1602 if p, err := q.Pop(logger); err != nil || p.Pod != highPriorityPodInfo.Pod { 1603 t.Errorf("Expected: %v after Pop, but got: %v", highPriorityPodInfo.Pod.Name, p.Pod.Name) 1604 } 1605 expectedList := []*framework.PodInfo{medPriorityPodInfo, unschedulablePodInfo} 1606 podInfos := q.NominatedPodsForNode("node1") 1607 if diff := cmp.Diff(expectedList, podInfos); diff != "" { 1608 t.Errorf("Unexpected list of nominated Pods for node: (-want, +got):\n%s", diff) 1609 } 1610 podInfos[0].Pod.Name = "not mpp" 1611 if diff := cmp.Diff(podInfos, q.NominatedPodsForNode("node1")); diff == "" { 1612 t.Error("Expected list of nominated Pods for node2 is different from podInfos") 1613 } 1614 if len(q.NominatedPodsForNode("node2")) != 0 { 1615 t.Error("Expected list of nominated Pods for node2 to be empty.") 1616 } 1617 } 1618 1619 func TestPriorityQueue_NominatedPodDeleted(t *testing.T) { 1620 tests := []struct { 1621 name string 1622 podInfo *framework.PodInfo 1623 deletePod bool 1624 want bool 1625 }{ 1626 { 1627 name: "alive pod gets added into PodNominator", 1628 podInfo: medPriorityPodInfo, 1629 want: true, 1630 }, 1631 { 1632 name: "deleted pod shouldn't be added into PodNominator", 1633 podInfo: highPriNominatedPodInfo, 1634 deletePod: true, 1635 want: false, 1636 }, 1637 { 1638 name: "pod without .status.nominatedPodName specified shouldn't be added into PodNominator", 1639 podInfo: highPriorityPodInfo, 1640 want: false, 1641 }, 1642 } 1643 1644 for _, tt := range tests { 1645 t.Run(tt.name, func(t *testing.T) { 1646 logger, _ := ktesting.NewTestContext(t) 1647 cs := fake.NewSimpleClientset(tt.podInfo.Pod) 1648 informerFactory := informers.NewSharedInformerFactory(cs, 0) 1649 podLister := informerFactory.Core().V1().Pods().Lister() 1650 1651 // Build a PriorityQueue. 1652 q := NewPriorityQueue(newDefaultQueueSort(), informerFactory, WithPodLister(podLister)) 1653 ctx, cancel := context.WithCancel(context.Background()) 1654 defer cancel() 1655 informerFactory.Start(ctx.Done()) 1656 informerFactory.WaitForCacheSync(ctx.Done()) 1657 1658 if tt.deletePod { 1659 // Simulate that the test pod gets deleted physically. 1660 informerFactory.Core().V1().Pods().Informer().GetStore().Delete(tt.podInfo.Pod) 1661 } 1662 1663 q.AddNominatedPod(logger, tt.podInfo, nil) 1664 1665 if got := len(q.NominatedPodsForNode(tt.podInfo.Pod.Status.NominatedNodeName)) == 1; got != tt.want { 1666 t.Errorf("Want %v, but got %v", tt.want, got) 1667 } 1668 }) 1669 } 1670 } 1671 1672 func TestPriorityQueue_PendingPods(t *testing.T) { 1673 makeSet := func(pods []*v1.Pod) map[*v1.Pod]struct{} { 1674 pendingSet := map[*v1.Pod]struct{}{} 1675 for _, p := range pods { 1676 pendingSet[p] = struct{}{} 1677 } 1678 return pendingSet 1679 } 1680 1681 logger, ctx := ktesting.NewTestContext(t) 1682 ctx, cancel := context.WithCancel(ctx) 1683 defer cancel() 1684 q := NewTestQueue(ctx, newDefaultQueueSort()) 1685 // To simulate the pod is failed in scheduling in the real world, Pop() the pod from activeQ before AddUnschedulableIfNotPresent()s below. 1686 q.activeQ.Add(q.newQueuedPodInfo(unschedulablePodInfo.Pod)) 1687 if p, err := q.Pop(logger); err != nil || p.Pod != unschedulablePodInfo.Pod { 1688 t.Errorf("Expected: %v after Pop, but got: %v", unschedulablePodInfo.Pod.Name, p.Pod.Name) 1689 } 1690 q.activeQ.Add(q.newQueuedPodInfo(highPriorityPodInfo.Pod)) 1691 if p, err := q.Pop(logger); err != nil || p.Pod != highPriorityPodInfo.Pod { 1692 t.Errorf("Expected: %v after Pop, but got: %v", highPriorityPodInfo.Pod.Name, p.Pod.Name) 1693 } 1694 q.Add(logger, medPriorityPodInfo.Pod) 1695 err := q.AddUnschedulableIfNotPresent(logger, q.newQueuedPodInfo(unschedulablePodInfo.Pod, "plugin"), q.SchedulingCycle()) 1696 if err != nil { 1697 t.Fatalf("unexpected error from AddUnschedulableIfNotPresent: %v", err) 1698 } 1699 err = q.AddUnschedulableIfNotPresent(logger, q.newQueuedPodInfo(highPriorityPodInfo.Pod, "plugin"), q.SchedulingCycle()) 1700 if err != nil { 1701 t.Fatalf("unexpected error from AddUnschedulableIfNotPresent: %v", err) 1702 } 1703 1704 expectedSet := makeSet([]*v1.Pod{medPriorityPodInfo.Pod, unschedulablePodInfo.Pod, highPriorityPodInfo.Pod}) 1705 gotPods, gotSummary := q.PendingPods() 1706 if diff := cmp.Diff(expectedSet, makeSet(gotPods)); diff != "" { 1707 t.Errorf("Unexpected list of pending Pods (-want, +got):\n%s", diff) 1708 } 1709 if wantSummary := fmt.Sprintf(pendingPodsSummary, 1, 0, 2); wantSummary != gotSummary { 1710 t.Errorf("Unexpected pending pods summary: want %v, but got %v.", wantSummary, gotSummary) 1711 } 1712 // Move all to active queue. We should still see the same set of pods. 1713 q.MoveAllToActiveOrBackoffQueue(logger, TestEvent, nil, nil, nil) 1714 gotPods, gotSummary = q.PendingPods() 1715 if diff := cmp.Diff(expectedSet, makeSet(gotPods)); diff != "" { 1716 t.Errorf("Unexpected list of pending Pods (-want, +got):\n%s", diff) 1717 } 1718 if wantSummary := fmt.Sprintf(pendingPodsSummary, 1, 2, 0); wantSummary != gotSummary { 1719 t.Errorf("Unexpected pending pods summary: want %v, but got %v.", wantSummary, gotSummary) 1720 } 1721 } 1722 1723 func TestPriorityQueue_UpdateNominatedPodForNode(t *testing.T) { 1724 logger, ctx := ktesting.NewTestContext(t) 1725 ctx, cancel := context.WithCancel(ctx) 1726 defer cancel() 1727 objs := []runtime.Object{medPriorityPodInfo.Pod, unschedulablePodInfo.Pod, highPriorityPodInfo.Pod, scheduledPodInfo.Pod} 1728 q := NewTestQueueWithObjects(ctx, newDefaultQueueSort(), objs) 1729 if err := q.Add(logger, medPriorityPodInfo.Pod); err != nil { 1730 t.Errorf("add failed: %v", err) 1731 } 1732 // Update unschedulablePodInfo on a different node than specified in the pod. 1733 q.AddNominatedPod(logger, mustNewTestPodInfo(t, unschedulablePodInfo.Pod), 1734 &framework.NominatingInfo{NominatingMode: framework.ModeOverride, NominatedNodeName: "node5"}) 1735 1736 // Update nominated node name of a pod on a node that is not specified in the pod object. 1737 q.AddNominatedPod(logger, mustNewTestPodInfo(t, highPriorityPodInfo.Pod), 1738 &framework.NominatingInfo{NominatingMode: framework.ModeOverride, NominatedNodeName: "node2"}) 1739 expectedNominatedPods := &nominator{ 1740 nominatedPodToNode: map[types.UID]string{ 1741 medPriorityPodInfo.Pod.UID: "node1", 1742 highPriorityPodInfo.Pod.UID: "node2", 1743 unschedulablePodInfo.Pod.UID: "node5", 1744 }, 1745 nominatedPods: map[string][]*framework.PodInfo{ 1746 "node1": {medPriorityPodInfo}, 1747 "node2": {highPriorityPodInfo}, 1748 "node5": {unschedulablePodInfo}, 1749 }, 1750 } 1751 if diff := cmp.Diff(q.nominator, expectedNominatedPods, nominatorCmpOpts...); diff != "" { 1752 t.Errorf("Unexpected diff after adding pods (-want, +got):\n%s", diff) 1753 } 1754 if p, err := q.Pop(logger); err != nil || p.Pod != medPriorityPodInfo.Pod { 1755 t.Errorf("Expected: %v after Pop, but got: %v", medPriorityPodInfo.Pod.Name, p.Pod.Name) 1756 } 1757 // List of nominated pods shouldn't change after popping them from the queue. 1758 if diff := cmp.Diff(q.nominator, expectedNominatedPods, nominatorCmpOpts...); diff != "" { 1759 t.Errorf("Unexpected diff after popping pods (-want, +got):\n%s", diff) 1760 } 1761 // Update one of the nominated pods that doesn't have nominatedNodeName in the 1762 // pod object. It should be updated correctly. 1763 q.AddNominatedPod(logger, highPriorityPodInfo, &framework.NominatingInfo{NominatingMode: framework.ModeOverride, NominatedNodeName: "node4"}) 1764 expectedNominatedPods = &nominator{ 1765 nominatedPodToNode: map[types.UID]string{ 1766 medPriorityPodInfo.Pod.UID: "node1", 1767 highPriorityPodInfo.Pod.UID: "node4", 1768 unschedulablePodInfo.Pod.UID: "node5", 1769 }, 1770 nominatedPods: map[string][]*framework.PodInfo{ 1771 "node1": {medPriorityPodInfo}, 1772 "node4": {highPriorityPodInfo}, 1773 "node5": {unschedulablePodInfo}, 1774 }, 1775 } 1776 if diff := cmp.Diff(q.nominator, expectedNominatedPods, nominatorCmpOpts...); diff != "" { 1777 t.Errorf("Unexpected diff after updating pods (-want, +got):\n%s", diff) 1778 } 1779 1780 // Attempt to nominate a pod that was deleted from the informer cache. 1781 // Nothing should change. 1782 q.AddNominatedPod(logger, nonExistentPodInfo, &framework.NominatingInfo{NominatingMode: framework.ModeOverride, NominatedNodeName: "node1"}) 1783 if diff := cmp.Diff(q.nominator, expectedNominatedPods, nominatorCmpOpts...); diff != "" { 1784 t.Errorf("Unexpected diff after nominating a deleted pod (-want, +got):\n%s", diff) 1785 } 1786 // Attempt to nominate a pod that was already scheduled in the informer cache. 1787 // Nothing should change. 1788 scheduledPodCopy := scheduledPodInfo.Pod.DeepCopy() 1789 scheduledPodInfo.Pod.Spec.NodeName = "" 1790 q.AddNominatedPod(logger, mustNewTestPodInfo(t, scheduledPodCopy), &framework.NominatingInfo{NominatingMode: framework.ModeOverride, NominatedNodeName: "node1"}) 1791 if diff := cmp.Diff(q.nominator, expectedNominatedPods, nominatorCmpOpts...); diff != "" { 1792 t.Errorf("Unexpected diff after nominating a scheduled pod (-want, +got):\n%s", diff) 1793 } 1794 1795 // Delete a nominated pod that doesn't have nominatedNodeName in the pod 1796 // object. It should be deleted. 1797 q.DeleteNominatedPodIfExists(highPriorityPodInfo.Pod) 1798 expectedNominatedPods = &nominator{ 1799 nominatedPodToNode: map[types.UID]string{ 1800 medPriorityPodInfo.Pod.UID: "node1", 1801 unschedulablePodInfo.Pod.UID: "node5", 1802 }, 1803 nominatedPods: map[string][]*framework.PodInfo{ 1804 "node1": {medPriorityPodInfo}, 1805 "node5": {unschedulablePodInfo}, 1806 }, 1807 } 1808 if diff := cmp.Diff(q.nominator, expectedNominatedPods, nominatorCmpOpts...); diff != "" { 1809 t.Errorf("Unexpected diff after deleting pods (-want, +got):\n%s", diff) 1810 } 1811 } 1812 1813 func TestPriorityQueue_NewWithOptions(t *testing.T) { 1814 ctx, cancel := context.WithCancel(context.Background()) 1815 defer cancel() 1816 q := NewTestQueue(ctx, 1817 newDefaultQueueSort(), 1818 WithPodInitialBackoffDuration(2*time.Second), 1819 WithPodMaxBackoffDuration(20*time.Second), 1820 ) 1821 1822 if q.podInitialBackoffDuration != 2*time.Second { 1823 t.Errorf("Unexpected pod backoff initial duration. Expected: %v, got: %v", 2*time.Second, q.podInitialBackoffDuration) 1824 } 1825 1826 if q.podMaxBackoffDuration != 20*time.Second { 1827 t.Errorf("Unexpected pod backoff max duration. Expected: %v, got: %v", 2*time.Second, q.podMaxBackoffDuration) 1828 } 1829 } 1830 1831 func TestUnschedulablePodsMap(t *testing.T) { 1832 var pods = []*v1.Pod{ 1833 st.MakePod().Name("p0").Namespace("ns1").Annotation("annot1", "val1").NominatedNodeName("node1").Obj(), 1834 st.MakePod().Name("p1").Namespace("ns1").Annotation("annot", "val").Obj(), 1835 st.MakePod().Name("p2").Namespace("ns2").Annotation("annot2", "val2").Annotation("annot3", "val3").NominatedNodeName("node3").Obj(), 1836 st.MakePod().Name("p3").Namespace("ns4").Annotation("annot2", "val2").Annotation("annot3", "val3").NominatedNodeName("node1").Obj(), 1837 } 1838 var updatedPods = make([]*v1.Pod, len(pods)) 1839 updatedPods[0] = pods[0].DeepCopy() 1840 updatedPods[1] = pods[1].DeepCopy() 1841 updatedPods[3] = pods[3].DeepCopy() 1842 1843 tests := []struct { 1844 name string 1845 podsToAdd []*v1.Pod 1846 expectedMapAfterAdd map[string]*framework.QueuedPodInfo 1847 podsToUpdate []*v1.Pod 1848 expectedMapAfterUpdate map[string]*framework.QueuedPodInfo 1849 podsToDelete []*v1.Pod 1850 expectedMapAfterDelete map[string]*framework.QueuedPodInfo 1851 }{ 1852 { 1853 name: "create, update, delete subset of pods", 1854 podsToAdd: []*v1.Pod{pods[0], pods[1], pods[2], pods[3]}, 1855 expectedMapAfterAdd: map[string]*framework.QueuedPodInfo{ 1856 util.GetPodFullName(pods[0]): {PodInfo: mustNewTestPodInfo(t, pods[0]), UnschedulablePlugins: sets.New[string]()}, 1857 util.GetPodFullName(pods[1]): {PodInfo: mustNewTestPodInfo(t, pods[1]), UnschedulablePlugins: sets.New[string]()}, 1858 util.GetPodFullName(pods[2]): {PodInfo: mustNewTestPodInfo(t, pods[2]), UnschedulablePlugins: sets.New[string]()}, 1859 util.GetPodFullName(pods[3]): {PodInfo: mustNewTestPodInfo(t, pods[3]), UnschedulablePlugins: sets.New[string]()}, 1860 }, 1861 podsToUpdate: []*v1.Pod{updatedPods[0]}, 1862 expectedMapAfterUpdate: map[string]*framework.QueuedPodInfo{ 1863 util.GetPodFullName(pods[0]): {PodInfo: mustNewTestPodInfo(t, updatedPods[0]), UnschedulablePlugins: sets.New[string]()}, 1864 util.GetPodFullName(pods[1]): {PodInfo: mustNewTestPodInfo(t, pods[1]), UnschedulablePlugins: sets.New[string]()}, 1865 util.GetPodFullName(pods[2]): {PodInfo: mustNewTestPodInfo(t, pods[2]), UnschedulablePlugins: sets.New[string]()}, 1866 util.GetPodFullName(pods[3]): {PodInfo: mustNewTestPodInfo(t, pods[3]), UnschedulablePlugins: sets.New[string]()}, 1867 }, 1868 podsToDelete: []*v1.Pod{pods[0], pods[1]}, 1869 expectedMapAfterDelete: map[string]*framework.QueuedPodInfo{ 1870 util.GetPodFullName(pods[2]): {PodInfo: mustNewTestPodInfo(t, pods[2]), UnschedulablePlugins: sets.New[string]()}, 1871 util.GetPodFullName(pods[3]): {PodInfo: mustNewTestPodInfo(t, pods[3]), UnschedulablePlugins: sets.New[string]()}, 1872 }, 1873 }, 1874 { 1875 name: "create, update, delete all", 1876 podsToAdd: []*v1.Pod{pods[0], pods[3]}, 1877 expectedMapAfterAdd: map[string]*framework.QueuedPodInfo{ 1878 util.GetPodFullName(pods[0]): {PodInfo: mustNewTestPodInfo(t, pods[0]), UnschedulablePlugins: sets.New[string]()}, 1879 util.GetPodFullName(pods[3]): {PodInfo: mustNewTestPodInfo(t, pods[3]), UnschedulablePlugins: sets.New[string]()}, 1880 }, 1881 podsToUpdate: []*v1.Pod{updatedPods[3]}, 1882 expectedMapAfterUpdate: map[string]*framework.QueuedPodInfo{ 1883 util.GetPodFullName(pods[0]): {PodInfo: mustNewTestPodInfo(t, pods[0]), UnschedulablePlugins: sets.New[string]()}, 1884 util.GetPodFullName(pods[3]): {PodInfo: mustNewTestPodInfo(t, updatedPods[3]), UnschedulablePlugins: sets.New[string]()}, 1885 }, 1886 podsToDelete: []*v1.Pod{pods[0], pods[3]}, 1887 expectedMapAfterDelete: map[string]*framework.QueuedPodInfo{}, 1888 }, 1889 { 1890 name: "delete non-existing and existing pods", 1891 podsToAdd: []*v1.Pod{pods[1], pods[2]}, 1892 expectedMapAfterAdd: map[string]*framework.QueuedPodInfo{ 1893 util.GetPodFullName(pods[1]): {PodInfo: mustNewTestPodInfo(t, pods[1]), UnschedulablePlugins: sets.New[string]()}, 1894 util.GetPodFullName(pods[2]): {PodInfo: mustNewTestPodInfo(t, pods[2]), UnschedulablePlugins: sets.New[string]()}, 1895 }, 1896 podsToUpdate: []*v1.Pod{updatedPods[1]}, 1897 expectedMapAfterUpdate: map[string]*framework.QueuedPodInfo{ 1898 util.GetPodFullName(pods[1]): {PodInfo: mustNewTestPodInfo(t, updatedPods[1]), UnschedulablePlugins: sets.New[string]()}, 1899 util.GetPodFullName(pods[2]): {PodInfo: mustNewTestPodInfo(t, pods[2]), UnschedulablePlugins: sets.New[string]()}, 1900 }, 1901 podsToDelete: []*v1.Pod{pods[2], pods[3]}, 1902 expectedMapAfterDelete: map[string]*framework.QueuedPodInfo{ 1903 util.GetPodFullName(pods[1]): {PodInfo: mustNewTestPodInfo(t, updatedPods[1]), UnschedulablePlugins: sets.New[string]()}, 1904 }, 1905 }, 1906 } 1907 1908 for _, test := range tests { 1909 t.Run(test.name, func(t *testing.T) { 1910 upm := newUnschedulablePods(nil, nil) 1911 for _, p := range test.podsToAdd { 1912 upm.addOrUpdate(newQueuedPodInfoForLookup(p)) 1913 } 1914 if diff := cmp.Diff(test.expectedMapAfterAdd, upm.podInfoMap); diff != "" { 1915 t.Errorf("Unexpected map after adding pods(-want, +got):\n%s", diff) 1916 } 1917 1918 if len(test.podsToUpdate) > 0 { 1919 for _, p := range test.podsToUpdate { 1920 upm.addOrUpdate(newQueuedPodInfoForLookup(p)) 1921 } 1922 if diff := cmp.Diff(test.expectedMapAfterUpdate, upm.podInfoMap); diff != "" { 1923 t.Errorf("Unexpected map after updating pods (-want, +got):\n%s", diff) 1924 } 1925 } 1926 for _, p := range test.podsToDelete { 1927 upm.delete(p, false) 1928 } 1929 if diff := cmp.Diff(test.expectedMapAfterDelete, upm.podInfoMap); diff != "" { 1930 t.Errorf("Unexpected map after deleting pods (-want, +got):\n%s", diff) 1931 } 1932 upm.clear() 1933 if len(upm.podInfoMap) != 0 { 1934 t.Errorf("Expected the map to be empty, but has %v elements.", len(upm.podInfoMap)) 1935 } 1936 }) 1937 } 1938 } 1939 1940 func TestSchedulingQueue_Close(t *testing.T) { 1941 logger, ctx := ktesting.NewTestContext(t) 1942 ctx, cancel := context.WithCancel(ctx) 1943 defer cancel() 1944 q := NewTestQueue(ctx, newDefaultQueueSort()) 1945 wg := sync.WaitGroup{} 1946 wg.Add(1) 1947 go func() { 1948 defer wg.Done() 1949 pod, err := q.Pop(logger) 1950 if err != nil { 1951 t.Errorf("Expected nil err from Pop() if queue is closed, but got %q", err.Error()) 1952 } 1953 if pod != nil { 1954 t.Errorf("Expected pod nil from Pop() if queue is closed, but got: %v", pod) 1955 } 1956 }() 1957 q.Close() 1958 wg.Wait() 1959 } 1960 1961 // TestRecentlyTriedPodsGoBack tests that pods which are recently tried and are 1962 // unschedulable go behind other pods with the same priority. This behavior 1963 // ensures that an unschedulable pod does not block head of the queue when there 1964 // are frequent events that move pods to the active queue. 1965 func TestRecentlyTriedPodsGoBack(t *testing.T) { 1966 c := testingclock.NewFakeClock(time.Now()) 1967 logger, ctx := ktesting.NewTestContext(t) 1968 ctx, cancel := context.WithCancel(ctx) 1969 defer cancel() 1970 q := NewTestQueue(ctx, newDefaultQueueSort(), WithClock(c)) 1971 // Add a few pods to priority queue. 1972 for i := 0; i < 5; i++ { 1973 p := st.MakePod().Name(fmt.Sprintf("test-pod-%v", i)).Namespace("ns1").UID(fmt.Sprintf("tp00%v", i)).Priority(highPriority).Node("node1").NominatedNodeName("node1").Obj() 1974 q.Add(logger, p) 1975 } 1976 c.Step(time.Microsecond) 1977 // Simulate a pod being popped by the scheduler, determined unschedulable, and 1978 // then moved back to the active queue. 1979 p1, err := q.Pop(logger) 1980 if err != nil { 1981 t.Errorf("Error while popping the head of the queue: %v", err) 1982 } 1983 // Update pod condition to unschedulable. 1984 podutil.UpdatePodCondition(&p1.PodInfo.Pod.Status, &v1.PodCondition{ 1985 Type: v1.PodScheduled, 1986 Status: v1.ConditionFalse, 1987 Reason: v1.PodReasonUnschedulable, 1988 Message: "fake scheduling failure", 1989 LastProbeTime: metav1.Now(), 1990 }) 1991 p1.UnschedulablePlugins = sets.New("plugin") 1992 // Put in the unschedulable queue. 1993 err = q.AddUnschedulableIfNotPresent(logger, p1, q.SchedulingCycle()) 1994 if err != nil { 1995 t.Fatalf("unexpected error from AddUnschedulableIfNotPresent: %v", err) 1996 } 1997 c.Step(DefaultPodInitialBackoffDuration) 1998 // Move all unschedulable pods to the active queue. 1999 q.MoveAllToActiveOrBackoffQueue(logger, UnschedulableTimeout, nil, nil, nil) 2000 // Simulation is over. Now let's pop all pods. The pod popped first should be 2001 // the last one we pop here. 2002 for i := 0; i < 5; i++ { 2003 p, err := q.Pop(logger) 2004 if err != nil { 2005 t.Errorf("Error while popping pods from the queue: %v", err) 2006 } 2007 if (i == 4) != (p1 == p) { 2008 t.Errorf("A pod tried before is not the last pod popped: i: %v, pod name: %v", i, p.PodInfo.Pod.Name) 2009 } 2010 } 2011 } 2012 2013 // TestPodFailedSchedulingMultipleTimesDoesNotBlockNewerPod tests 2014 // that a pod determined as unschedulable multiple times doesn't block any newer pod. 2015 // This behavior ensures that an unschedulable pod does not block head of the queue when there 2016 // are frequent events that move pods to the active queue. 2017 func TestPodFailedSchedulingMultipleTimesDoesNotBlockNewerPod(t *testing.T) { 2018 c := testingclock.NewFakeClock(time.Now()) 2019 logger, ctx := ktesting.NewTestContext(t) 2020 ctx, cancel := context.WithCancel(ctx) 2021 defer cancel() 2022 q := NewTestQueue(ctx, newDefaultQueueSort(), WithClock(c)) 2023 2024 // Add an unschedulable pod to a priority queue. 2025 // This makes a situation that the pod was tried to schedule 2026 // and had been determined unschedulable so far 2027 unschedulablePod := st.MakePod().Name("test-pod-unscheduled").Namespace("ns1").UID("tp001").Priority(highPriority).NominatedNodeName("node1").Obj() 2028 2029 // Update pod condition to unschedulable. 2030 podutil.UpdatePodCondition(&unschedulablePod.Status, &v1.PodCondition{ 2031 Type: v1.PodScheduled, 2032 Status: v1.ConditionFalse, 2033 Reason: v1.PodReasonUnschedulable, 2034 Message: "fake scheduling failure", 2035 }) 2036 2037 // To simulate the pod is failed in scheduling in the real world, Pop() the pod from activeQ before AddUnschedulableIfNotPresent() below. 2038 q.activeQ.Add(q.newQueuedPodInfo(unschedulablePod)) 2039 if p, err := q.Pop(logger); err != nil || p.Pod != unschedulablePod { 2040 t.Errorf("Expected: %v after Pop, but got: %v", unschedulablePod.Name, p.Pod.Name) 2041 } 2042 // Put in the unschedulable queue 2043 err := q.AddUnschedulableIfNotPresent(logger, newQueuedPodInfoForLookup(unschedulablePod, "plugin"), q.SchedulingCycle()) 2044 if err != nil { 2045 t.Fatalf("unexpected error from AddUnschedulableIfNotPresent: %v", err) 2046 } 2047 // Move clock to make the unschedulable pods complete backoff. 2048 c.Step(DefaultPodInitialBackoffDuration + time.Second) 2049 // Move all unschedulable pods to the active queue. 2050 q.MoveAllToActiveOrBackoffQueue(logger, UnschedulableTimeout, nil, nil, nil) 2051 2052 // Simulate a pod being popped by the scheduler, 2053 // At this time, unschedulable pod should be popped. 2054 p1, err := q.Pop(logger) 2055 if err != nil { 2056 t.Errorf("Error while popping the head of the queue: %v", err) 2057 } 2058 if p1.Pod != unschedulablePod { 2059 t.Errorf("Expected that test-pod-unscheduled was popped, got %v", p1.Pod.Name) 2060 } 2061 2062 // Assume newer pod was added just after unschedulable pod 2063 // being popped and before being pushed back to the queue. 2064 newerPod := st.MakePod().Name("test-newer-pod").Namespace("ns1").UID("tp002").CreationTimestamp(metav1.Now()).Priority(highPriority).NominatedNodeName("node1").Obj() 2065 q.Add(logger, newerPod) 2066 2067 // And then unschedulablePodInfo was determined as unschedulable AGAIN. 2068 podutil.UpdatePodCondition(&unschedulablePod.Status, &v1.PodCondition{ 2069 Type: v1.PodScheduled, 2070 Status: v1.ConditionFalse, 2071 Reason: v1.PodReasonUnschedulable, 2072 Message: "fake scheduling failure", 2073 }) 2074 2075 // And then, put unschedulable pod to the unschedulable queue 2076 err = q.AddUnschedulableIfNotPresent(logger, newQueuedPodInfoForLookup(unschedulablePod, "plugin"), q.SchedulingCycle()) 2077 if err != nil { 2078 t.Fatalf("unexpected error from AddUnschedulableIfNotPresent: %v", err) 2079 } 2080 // Move clock to make the unschedulable pods complete backoff. 2081 c.Step(DefaultPodInitialBackoffDuration + time.Second) 2082 // Move all unschedulable pods to the active queue. 2083 q.MoveAllToActiveOrBackoffQueue(logger, UnschedulableTimeout, nil, nil, nil) 2084 2085 // At this time, newerPod should be popped 2086 // because it is the oldest tried pod. 2087 p2, err2 := q.Pop(logger) 2088 if err2 != nil { 2089 t.Errorf("Error while popping the head of the queue: %v", err2) 2090 } 2091 if p2.Pod != newerPod { 2092 t.Errorf("Expected that test-newer-pod was popped, got %v", p2.Pod.Name) 2093 } 2094 } 2095 2096 // TestHighPriorityBackoff tests that a high priority pod does not block 2097 // other pods if it is unschedulable 2098 func TestHighPriorityBackoff(t *testing.T) { 2099 logger, ctx := ktesting.NewTestContext(t) 2100 ctx, cancel := context.WithCancel(ctx) 2101 defer cancel() 2102 q := NewTestQueue(ctx, newDefaultQueueSort()) 2103 2104 midPod := st.MakePod().Name("test-midpod").Namespace("ns1").UID("tp-mid").Priority(midPriority).NominatedNodeName("node1").Obj() 2105 highPod := st.MakePod().Name("test-highpod").Namespace("ns1").UID("tp-high").Priority(highPriority).NominatedNodeName("node1").Obj() 2106 q.Add(logger, midPod) 2107 q.Add(logger, highPod) 2108 // Simulate a pod being popped by the scheduler, determined unschedulable, and 2109 // then moved back to the active queue. 2110 p, err := q.Pop(logger) 2111 if err != nil { 2112 t.Errorf("Error while popping the head of the queue: %v", err) 2113 } 2114 if p.Pod != highPod { 2115 t.Errorf("Expected to get high priority pod, got: %v", p) 2116 } 2117 // Update pod condition to unschedulable. 2118 podutil.UpdatePodCondition(&p.Pod.Status, &v1.PodCondition{ 2119 Type: v1.PodScheduled, 2120 Status: v1.ConditionFalse, 2121 Reason: v1.PodReasonUnschedulable, 2122 Message: "fake scheduling failure", 2123 }) 2124 // Put in the unschedulable queue. 2125 err = q.AddUnschedulableIfNotPresent(logger, p, q.SchedulingCycle()) 2126 if err != nil { 2127 t.Fatalf("unexpected error from AddUnschedulableIfNotPresent: %v", err) 2128 } 2129 // Move all unschedulable pods to the active queue. 2130 q.MoveAllToActiveOrBackoffQueue(logger, TestEvent, nil, nil, nil) 2131 2132 p, err = q.Pop(logger) 2133 if err != nil { 2134 t.Errorf("Error while popping the head of the queue: %v", err) 2135 } 2136 if p.Pod != midPod { 2137 t.Errorf("Expected to get mid priority pod, got: %v", p) 2138 } 2139 } 2140 2141 // TestHighPriorityFlushUnschedulablePodsLeftover tests that pods will be moved to 2142 // activeQ after one minutes if it is in unschedulablePods. 2143 func TestHighPriorityFlushUnschedulablePodsLeftover(t *testing.T) { 2144 c := testingclock.NewFakeClock(time.Now()) 2145 m := makeEmptyQueueingHintMapPerProfile() 2146 m[""][NodeAdd] = []*QueueingHintFunction{ 2147 { 2148 PluginName: "fakePlugin", 2149 QueueingHintFn: queueHintReturnQueue, 2150 }, 2151 } 2152 logger, ctx := ktesting.NewTestContext(t) 2153 ctx, cancel := context.WithCancel(ctx) 2154 defer cancel() 2155 q := NewTestQueue(ctx, newDefaultQueueSort(), WithClock(c), WithQueueingHintMapPerProfile(m)) 2156 midPod := st.MakePod().Name("test-midpod").Namespace("ns1").UID("tp-mid").Priority(midPriority).NominatedNodeName("node1").Obj() 2157 highPod := st.MakePod().Name("test-highpod").Namespace("ns1").UID("tp-high").Priority(highPriority).NominatedNodeName("node1").Obj() 2158 2159 // Update pod condition to highPod. 2160 podutil.UpdatePodCondition(&highPod.Status, &v1.PodCondition{ 2161 Type: v1.PodScheduled, 2162 Status: v1.ConditionFalse, 2163 Reason: v1.PodReasonUnschedulable, 2164 Message: "fake scheduling failure", 2165 }) 2166 2167 // Update pod condition to midPod. 2168 podutil.UpdatePodCondition(&midPod.Status, &v1.PodCondition{ 2169 Type: v1.PodScheduled, 2170 Status: v1.ConditionFalse, 2171 Reason: v1.PodReasonUnschedulable, 2172 Message: "fake scheduling failure", 2173 }) 2174 2175 // To simulate the pod is failed in scheduling in the real world, Pop() the pod from activeQ before AddUnschedulableIfNotPresent()s below. 2176 q.activeQ.Add(q.newQueuedPodInfo(highPod)) 2177 if p, err := q.Pop(logger); err != nil || p.Pod != highPod { 2178 t.Errorf("Expected: %v after Pop, but got: %v", highPod.Name, p.Pod.Name) 2179 } 2180 q.activeQ.Add(q.newQueuedPodInfo(midPod)) 2181 if p, err := q.Pop(logger); err != nil || p.Pod != midPod { 2182 t.Errorf("Expected: %v after Pop, but got: %v", midPod.Name, p.Pod.Name) 2183 } 2184 err := q.AddUnschedulableIfNotPresent(logger, q.newQueuedPodInfo(highPod, "fakePlugin"), q.SchedulingCycle()) 2185 if err != nil { 2186 t.Fatalf("unexpected error from AddUnschedulableIfNotPresent: %v", err) 2187 } 2188 err = q.AddUnschedulableIfNotPresent(logger, q.newQueuedPodInfo(midPod, "fakePlugin"), q.SchedulingCycle()) 2189 if err != nil { 2190 t.Fatalf("unexpected error from AddUnschedulableIfNotPresent: %v", err) 2191 } 2192 c.Step(DefaultPodMaxInUnschedulablePodsDuration + time.Second) 2193 q.flushUnschedulablePodsLeftover(logger) 2194 2195 if p, err := q.Pop(logger); err != nil || p.Pod != highPod { 2196 t.Errorf("Expected: %v after Pop, but got: %v", highPriorityPodInfo.Pod.Name, p.Pod.Name) 2197 } 2198 if p, err := q.Pop(logger); err != nil || p.Pod != midPod { 2199 t.Errorf("Expected: %v after Pop, but got: %v", medPriorityPodInfo.Pod.Name, p.Pod.Name) 2200 } 2201 } 2202 2203 func TestPriorityQueue_initPodMaxInUnschedulablePodsDuration(t *testing.T) { 2204 pod1 := st.MakePod().Name("test-pod-1").Namespace("ns1").UID("tp-1").NominatedNodeName("node1").Obj() 2205 pod2 := st.MakePod().Name("test-pod-2").Namespace("ns2").UID("tp-2").NominatedNodeName("node2").Obj() 2206 2207 var timestamp = time.Now() 2208 pInfo1 := &framework.QueuedPodInfo{ 2209 PodInfo: mustNewTestPodInfo(t, pod1), 2210 Timestamp: timestamp.Add(-time.Second), 2211 } 2212 pInfo2 := &framework.QueuedPodInfo{ 2213 PodInfo: mustNewTestPodInfo(t, pod2), 2214 Timestamp: timestamp.Add(-2 * time.Second), 2215 } 2216 2217 tests := []struct { 2218 name string 2219 podMaxInUnschedulablePodsDuration time.Duration 2220 operations []operation 2221 operands []*framework.QueuedPodInfo 2222 expected []*framework.QueuedPodInfo 2223 }{ 2224 { 2225 name: "New priority queue by the default value of podMaxInUnschedulablePodsDuration", 2226 operations: []operation{ 2227 addPodUnschedulablePods, 2228 addPodUnschedulablePods, 2229 flushUnschedulerQ, 2230 }, 2231 operands: []*framework.QueuedPodInfo{pInfo1, pInfo2, nil}, 2232 expected: []*framework.QueuedPodInfo{pInfo2, pInfo1}, 2233 }, 2234 { 2235 name: "New priority queue by user-defined value of podMaxInUnschedulablePodsDuration", 2236 podMaxInUnschedulablePodsDuration: 30 * time.Second, 2237 operations: []operation{ 2238 addPodUnschedulablePods, 2239 addPodUnschedulablePods, 2240 flushUnschedulerQ, 2241 }, 2242 operands: []*framework.QueuedPodInfo{pInfo1, pInfo2, nil}, 2243 expected: []*framework.QueuedPodInfo{pInfo2, pInfo1}, 2244 }, 2245 } 2246 2247 for _, test := range tests { 2248 t.Run(test.name, func(t *testing.T) { 2249 logger, ctx := ktesting.NewTestContext(t) 2250 ctx, cancel := context.WithCancel(ctx) 2251 defer cancel() 2252 var queue *PriorityQueue 2253 if test.podMaxInUnschedulablePodsDuration > 0 { 2254 queue = NewTestQueue(ctx, newDefaultQueueSort(), 2255 WithClock(testingclock.NewFakeClock(timestamp)), 2256 WithPodMaxInUnschedulablePodsDuration(test.podMaxInUnschedulablePodsDuration)) 2257 } else { 2258 queue = NewTestQueue(ctx, newDefaultQueueSort(), 2259 WithClock(testingclock.NewFakeClock(timestamp))) 2260 } 2261 2262 var podInfoList []*framework.QueuedPodInfo 2263 2264 for i, op := range test.operations { 2265 op(t, logger, queue, test.operands[i]) 2266 } 2267 2268 expectedLen := len(test.expected) 2269 if queue.activeQ.Len() != expectedLen { 2270 t.Fatalf("Expected %v items to be in activeQ, but got: %v", expectedLen, queue.activeQ.Len()) 2271 } 2272 2273 for i := 0; i < expectedLen; i++ { 2274 if pInfo, err := queue.activeQ.Pop(); err != nil { 2275 t.Errorf("Error while popping the head of the queue: %v", err) 2276 } else { 2277 podInfoList = append(podInfoList, pInfo.(*framework.QueuedPodInfo)) 2278 } 2279 } 2280 2281 if diff := cmp.Diff(test.expected, podInfoList); diff != "" { 2282 t.Errorf("Unexpected QueuedPodInfo list (-want, +got):\n%s", diff) 2283 } 2284 }) 2285 } 2286 } 2287 2288 type operation func(t *testing.T, logger klog.Logger, queue *PriorityQueue, pInfo *framework.QueuedPodInfo) 2289 2290 var ( 2291 add = func(t *testing.T, logger klog.Logger, queue *PriorityQueue, pInfo *framework.QueuedPodInfo) { 2292 if err := queue.Add(logger, pInfo.Pod); err != nil { 2293 t.Fatalf("Unexpected error during Add: %v", err) 2294 } 2295 } 2296 popAndRequeueAsUnschedulable = func(t *testing.T, logger klog.Logger, queue *PriorityQueue, pInfo *framework.QueuedPodInfo) { 2297 // To simulate the pod is failed in scheduling in the real world, Pop() the pod from activeQ before AddUnschedulableIfNotPresent() below. 2298 // UnschedulablePlugins will get cleared by Pop, so make a copy first. 2299 unschedulablePlugins := pInfo.UnschedulablePlugins.Clone() 2300 if err := queue.activeQ.Add(queue.newQueuedPodInfo(pInfo.Pod)); err != nil { 2301 t.Fatalf("Unexpected error during Add: %v", err) 2302 } 2303 p, err := queue.Pop(logger) 2304 if err != nil { 2305 t.Fatalf("Unexpected error during Pop: %v", err) 2306 } 2307 if p.Pod != pInfo.Pod { 2308 t.Fatalf("Expected: %v after Pop, but got: %v", pInfo.Pod.Name, p.Pod.Name) 2309 } 2310 // Simulate plugins that are waiting for some events. 2311 p.UnschedulablePlugins = unschedulablePlugins 2312 if err := queue.AddUnschedulableIfNotPresent(logger, p, 1); err != nil { 2313 t.Fatalf("Unexpected error during AddUnschedulableIfNotPresent: %v", err) 2314 } 2315 } 2316 popAndRequeueAsBackoff = func(t *testing.T, logger klog.Logger, queue *PriorityQueue, pInfo *framework.QueuedPodInfo) { 2317 // To simulate the pod is failed in scheduling in the real world, Pop() the pod from activeQ before AddUnschedulableIfNotPresent() below. 2318 if err := queue.activeQ.Add(queue.newQueuedPodInfo(pInfo.Pod)); err != nil { 2319 t.Fatalf("Unexpected error during Add: %v", err) 2320 } 2321 p, err := queue.Pop(logger) 2322 if err != nil { 2323 t.Fatalf("Unexpected error during Pop: %v", err) 2324 } 2325 if p.Pod != pInfo.Pod { 2326 t.Fatalf("Expected: %v after Pop, but got: %v", pInfo.Pod.Name, p.Pod.Name) 2327 } 2328 // When there is no known unschedulable plugin, pods always go to the backoff queue. 2329 if err := queue.AddUnschedulableIfNotPresent(logger, p, 1); err != nil { 2330 t.Fatalf("Unexpected error during AddUnschedulableIfNotPresent: %v", err) 2331 } 2332 } 2333 addPodActiveQ = func(t *testing.T, logger klog.Logger, queue *PriorityQueue, pInfo *framework.QueuedPodInfo) { 2334 queue.activeQ.Add(pInfo) 2335 } 2336 updatePodActiveQ = func(t *testing.T, logger klog.Logger, queue *PriorityQueue, pInfo *framework.QueuedPodInfo) { 2337 queue.activeQ.Update(pInfo) 2338 } 2339 addPodUnschedulablePods = func(t *testing.T, logger klog.Logger, queue *PriorityQueue, pInfo *framework.QueuedPodInfo) { 2340 if !pInfo.Gated { 2341 // Update pod condition to unschedulable. 2342 podutil.UpdatePodCondition(&pInfo.Pod.Status, &v1.PodCondition{ 2343 Type: v1.PodScheduled, 2344 Status: v1.ConditionFalse, 2345 Reason: v1.PodReasonUnschedulable, 2346 Message: "fake scheduling failure", 2347 }) 2348 } 2349 queue.unschedulablePods.addOrUpdate(pInfo) 2350 } 2351 deletePod = func(t *testing.T, _ klog.Logger, queue *PriorityQueue, pInfo *framework.QueuedPodInfo) { 2352 queue.Delete(pInfo.Pod) 2353 } 2354 updatePodQueueable = func(t *testing.T, logger klog.Logger, queue *PriorityQueue, pInfo *framework.QueuedPodInfo) { 2355 newPod := pInfo.Pod.DeepCopy() 2356 newPod.Labels = map[string]string{"queueable": ""} 2357 queue.Update(logger, pInfo.Pod, newPod) 2358 } 2359 addPodBackoffQ = func(t *testing.T, logger klog.Logger, queue *PriorityQueue, pInfo *framework.QueuedPodInfo) { 2360 queue.podBackoffQ.Add(pInfo) 2361 } 2362 moveAllToActiveOrBackoffQ = func(t *testing.T, logger klog.Logger, queue *PriorityQueue, _ *framework.QueuedPodInfo) { 2363 queue.MoveAllToActiveOrBackoffQueue(logger, UnschedulableTimeout, nil, nil, nil) 2364 } 2365 flushBackoffQ = func(t *testing.T, logger klog.Logger, queue *PriorityQueue, _ *framework.QueuedPodInfo) { 2366 queue.clock.(*testingclock.FakeClock).Step(2 * time.Second) 2367 queue.flushBackoffQCompleted(logger) 2368 } 2369 moveClockForward = func(t *testing.T, logger klog.Logger, queue *PriorityQueue, _ *framework.QueuedPodInfo) { 2370 queue.clock.(*testingclock.FakeClock).Step(2 * time.Second) 2371 } 2372 flushUnschedulerQ = func(t *testing.T, logger klog.Logger, queue *PriorityQueue, _ *framework.QueuedPodInfo) { 2373 queue.clock.(*testingclock.FakeClock).Step(queue.podMaxInUnschedulablePodsDuration) 2374 queue.flushUnschedulablePodsLeftover(logger) 2375 } 2376 ) 2377 2378 // TestPodTimestamp tests the operations related to QueuedPodInfo. 2379 func TestPodTimestamp(t *testing.T) { 2380 pod1 := st.MakePod().Name("test-pod-1").Namespace("ns1").UID("tp-1").NominatedNodeName("node1").Obj() 2381 pod2 := st.MakePod().Name("test-pod-2").Namespace("ns2").UID("tp-2").NominatedNodeName("node2").Obj() 2382 2383 var timestamp = time.Now() 2384 pInfo1 := &framework.QueuedPodInfo{ 2385 PodInfo: mustNewTestPodInfo(t, pod1), 2386 Timestamp: timestamp, 2387 } 2388 pInfo2 := &framework.QueuedPodInfo{ 2389 PodInfo: mustNewTestPodInfo(t, pod2), 2390 Timestamp: timestamp.Add(time.Second), 2391 } 2392 2393 tests := []struct { 2394 name string 2395 operations []operation 2396 operands []*framework.QueuedPodInfo 2397 expected []*framework.QueuedPodInfo 2398 }{ 2399 { 2400 name: "add two pod to activeQ and sort them by the timestamp", 2401 operations: []operation{ 2402 addPodActiveQ, 2403 addPodActiveQ, 2404 }, 2405 operands: []*framework.QueuedPodInfo{pInfo2, pInfo1}, 2406 expected: []*framework.QueuedPodInfo{pInfo1, pInfo2}, 2407 }, 2408 { 2409 name: "update two pod to activeQ and sort them by the timestamp", 2410 operations: []operation{ 2411 updatePodActiveQ, 2412 updatePodActiveQ, 2413 }, 2414 operands: []*framework.QueuedPodInfo{pInfo2, pInfo1}, 2415 expected: []*framework.QueuedPodInfo{pInfo1, pInfo2}, 2416 }, 2417 { 2418 name: "add two pod to unschedulablePods then move them to activeQ and sort them by the timestamp", 2419 operations: []operation{ 2420 addPodUnschedulablePods, 2421 addPodUnschedulablePods, 2422 moveClockForward, 2423 moveAllToActiveOrBackoffQ, 2424 }, 2425 operands: []*framework.QueuedPodInfo{pInfo2, pInfo1, nil, nil}, 2426 expected: []*framework.QueuedPodInfo{pInfo1, pInfo2}, 2427 }, 2428 { 2429 name: "add one pod to BackoffQ and move it to activeQ", 2430 operations: []operation{ 2431 addPodActiveQ, 2432 addPodBackoffQ, 2433 flushBackoffQ, 2434 moveAllToActiveOrBackoffQ, 2435 }, 2436 operands: []*framework.QueuedPodInfo{pInfo2, pInfo1, nil, nil}, 2437 expected: []*framework.QueuedPodInfo{pInfo1, pInfo2}, 2438 }, 2439 } 2440 2441 for _, test := range tests { 2442 t.Run(test.name, func(t *testing.T) { 2443 logger, ctx := ktesting.NewTestContext(t) 2444 ctx, cancel := context.WithCancel(ctx) 2445 defer cancel() 2446 queue := NewTestQueue(ctx, newDefaultQueueSort(), WithClock(testingclock.NewFakeClock(timestamp))) 2447 var podInfoList []*framework.QueuedPodInfo 2448 2449 for i, op := range test.operations { 2450 op(t, logger, queue, test.operands[i]) 2451 } 2452 2453 expectedLen := len(test.expected) 2454 if queue.activeQ.Len() != expectedLen { 2455 t.Fatalf("Expected %v items to be in activeQ, but got: %v", expectedLen, queue.activeQ.Len()) 2456 } 2457 2458 for i := 0; i < expectedLen; i++ { 2459 if pInfo, err := queue.activeQ.Pop(); err != nil { 2460 t.Errorf("Error while popping the head of the queue: %v", err) 2461 } else { 2462 podInfoList = append(podInfoList, pInfo.(*framework.QueuedPodInfo)) 2463 } 2464 } 2465 2466 if diff := cmp.Diff(test.expected, podInfoList); diff != "" { 2467 t.Errorf("Unexpected QueuedPodInfo list (-want, +got):\n%s", diff) 2468 } 2469 }) 2470 } 2471 } 2472 2473 // TestPendingPodsMetric tests Prometheus metrics related with pending pods 2474 func TestPendingPodsMetric(t *testing.T) { 2475 timestamp := time.Now() 2476 metrics.Register() 2477 total := 60 2478 queueableNum := 50 2479 queueable, failme := "queueable", "failme" 2480 // First 50 Pods are queueable. 2481 pInfos := makeQueuedPodInfos(queueableNum, "x", queueable, timestamp) 2482 // The last 10 Pods are not queueable. 2483 gated := makeQueuedPodInfos(total-queueableNum, "y", failme, timestamp) 2484 // Manually mark them as gated=true. 2485 for _, pInfo := range gated { 2486 pInfo.Gated = true 2487 } 2488 pInfos = append(pInfos, gated...) 2489 totalWithDelay := 20 2490 pInfosWithDelay := makeQueuedPodInfos(totalWithDelay, "z", queueable, timestamp.Add(2*time.Second)) 2491 2492 tests := []struct { 2493 name string 2494 operations []operation 2495 operands [][]*framework.QueuedPodInfo 2496 metricsName string 2497 pluginMetricsSamplePercent int 2498 wants string 2499 }{ 2500 { 2501 name: "add pods to activeQ and unschedulablePods", 2502 operations: []operation{ 2503 addPodActiveQ, 2504 addPodUnschedulablePods, 2505 }, 2506 operands: [][]*framework.QueuedPodInfo{ 2507 pInfos[:30], 2508 pInfos[30:], 2509 }, 2510 metricsName: "scheduler_pending_pods", 2511 wants: ` 2512 # HELP scheduler_pending_pods [STABLE] Number of pending pods, by the queue type. 'active' means number of pods in activeQ; 'backoff' means number of pods in backoffQ; 'unschedulable' means number of pods in unschedulablePods that the scheduler attempted to schedule and failed; 'gated' is the number of unschedulable pods that the scheduler never attempted to schedule because they are gated. 2513 # TYPE scheduler_pending_pods gauge 2514 scheduler_pending_pods{queue="active"} 30 2515 scheduler_pending_pods{queue="backoff"} 0 2516 scheduler_pending_pods{queue="gated"} 10 2517 scheduler_pending_pods{queue="unschedulable"} 20 2518 `, 2519 }, 2520 { 2521 name: "add pods to all kinds of queues", 2522 operations: []operation{ 2523 addPodActiveQ, 2524 addPodBackoffQ, 2525 addPodUnschedulablePods, 2526 }, 2527 operands: [][]*framework.QueuedPodInfo{ 2528 pInfos[:15], 2529 pInfos[15:40], 2530 pInfos[40:], 2531 }, 2532 metricsName: "scheduler_pending_pods", 2533 wants: ` 2534 # HELP scheduler_pending_pods [STABLE] Number of pending pods, by the queue type. 'active' means number of pods in activeQ; 'backoff' means number of pods in backoffQ; 'unschedulable' means number of pods in unschedulablePods that the scheduler attempted to schedule and failed; 'gated' is the number of unschedulable pods that the scheduler never attempted to schedule because they are gated. 2535 # TYPE scheduler_pending_pods gauge 2536 scheduler_pending_pods{queue="active"} 15 2537 scheduler_pending_pods{queue="backoff"} 25 2538 scheduler_pending_pods{queue="gated"} 10 2539 scheduler_pending_pods{queue="unschedulable"} 10 2540 `, 2541 }, 2542 { 2543 name: "add pods to unschedulablePods and then move all to activeQ", 2544 operations: []operation{ 2545 addPodUnschedulablePods, 2546 moveClockForward, 2547 moveAllToActiveOrBackoffQ, 2548 }, 2549 operands: [][]*framework.QueuedPodInfo{ 2550 pInfos[:total], 2551 {nil}, 2552 {nil}, 2553 }, 2554 metricsName: "scheduler_pending_pods", 2555 wants: ` 2556 # HELP scheduler_pending_pods [STABLE] Number of pending pods, by the queue type. 'active' means number of pods in activeQ; 'backoff' means number of pods in backoffQ; 'unschedulable' means number of pods in unschedulablePods that the scheduler attempted to schedule and failed; 'gated' is the number of unschedulable pods that the scheduler never attempted to schedule because they are gated. 2557 # TYPE scheduler_pending_pods gauge 2558 scheduler_pending_pods{queue="active"} 50 2559 scheduler_pending_pods{queue="backoff"} 0 2560 scheduler_pending_pods{queue="gated"} 10 2561 scheduler_pending_pods{queue="unschedulable"} 0 2562 `, 2563 }, 2564 { 2565 name: "make some pods subject to backoff, add pods to unschedulablePods, and then move all to activeQ", 2566 operations: []operation{ 2567 addPodUnschedulablePods, 2568 moveClockForward, 2569 addPodUnschedulablePods, 2570 moveAllToActiveOrBackoffQ, 2571 }, 2572 operands: [][]*framework.QueuedPodInfo{ 2573 pInfos[20:total], 2574 {nil}, 2575 pInfosWithDelay[:20], 2576 {nil}, 2577 }, 2578 metricsName: "scheduler_pending_pods", 2579 wants: ` 2580 # HELP scheduler_pending_pods [STABLE] Number of pending pods, by the queue type. 'active' means number of pods in activeQ; 'backoff' means number of pods in backoffQ; 'unschedulable' means number of pods in unschedulablePods that the scheduler attempted to schedule and failed; 'gated' is the number of unschedulable pods that the scheduler never attempted to schedule because they are gated. 2581 # TYPE scheduler_pending_pods gauge 2582 scheduler_pending_pods{queue="active"} 30 2583 scheduler_pending_pods{queue="backoff"} 20 2584 scheduler_pending_pods{queue="gated"} 10 2585 scheduler_pending_pods{queue="unschedulable"} 0 2586 `, 2587 }, 2588 { 2589 name: "make some pods subject to backoff, add pods to unschedulablePods/activeQ, move all to activeQ, and finally flush backoffQ", 2590 operations: []operation{ 2591 addPodUnschedulablePods, 2592 addPodActiveQ, 2593 moveAllToActiveOrBackoffQ, 2594 flushBackoffQ, 2595 }, 2596 operands: [][]*framework.QueuedPodInfo{ 2597 pInfos[:40], 2598 pInfos[40:50], 2599 {nil}, 2600 {nil}, 2601 }, 2602 metricsName: "scheduler_pending_pods", 2603 wants: ` 2604 # HELP scheduler_pending_pods [STABLE] Number of pending pods, by the queue type. 'active' means number of pods in activeQ; 'backoff' means number of pods in backoffQ; 'unschedulable' means number of pods in unschedulablePods that the scheduler attempted to schedule and failed; 'gated' is the number of unschedulable pods that the scheduler never attempted to schedule because they are gated. 2605 # TYPE scheduler_pending_pods gauge 2606 scheduler_pending_pods{queue="active"} 50 2607 scheduler_pending_pods{queue="backoff"} 0 2608 scheduler_pending_pods{queue="gated"} 0 2609 scheduler_pending_pods{queue="unschedulable"} 0 2610 `, 2611 }, 2612 { 2613 name: "add pods to activeQ/unschedulablePods and then delete some Pods", 2614 operations: []operation{ 2615 addPodActiveQ, 2616 addPodUnschedulablePods, 2617 deletePod, 2618 deletePod, 2619 deletePod, 2620 }, 2621 operands: [][]*framework.QueuedPodInfo{ 2622 pInfos[:30], 2623 pInfos[30:], 2624 pInfos[:2], 2625 pInfos[30:33], 2626 pInfos[50:54], 2627 }, 2628 metricsName: "scheduler_pending_pods", 2629 wants: ` 2630 # HELP scheduler_pending_pods [STABLE] Number of pending pods, by the queue type. 'active' means number of pods in activeQ; 'backoff' means number of pods in backoffQ; 'unschedulable' means number of pods in unschedulablePods that the scheduler attempted to schedule and failed; 'gated' is the number of unschedulable pods that the scheduler never attempted to schedule because they are gated. 2631 # TYPE scheduler_pending_pods gauge 2632 scheduler_pending_pods{queue="active"} 28 2633 scheduler_pending_pods{queue="backoff"} 0 2634 scheduler_pending_pods{queue="gated"} 6 2635 scheduler_pending_pods{queue="unschedulable"} 17 2636 `, 2637 }, 2638 { 2639 name: "add pods to activeQ/unschedulablePods and then update some Pods as queueable", 2640 operations: []operation{ 2641 addPodActiveQ, 2642 addPodUnschedulablePods, 2643 updatePodQueueable, 2644 }, 2645 operands: [][]*framework.QueuedPodInfo{ 2646 pInfos[:30], 2647 pInfos[30:], 2648 pInfos[50:55], 2649 }, 2650 metricsName: "scheduler_pending_pods", 2651 wants: ` 2652 # HELP scheduler_pending_pods [STABLE] Number of pending pods, by the queue type. 'active' means number of pods in activeQ; 'backoff' means number of pods in backoffQ; 'unschedulable' means number of pods in unschedulablePods that the scheduler attempted to schedule and failed; 'gated' is the number of unschedulable pods that the scheduler never attempted to schedule because they are gated. 2653 # TYPE scheduler_pending_pods gauge 2654 scheduler_pending_pods{queue="active"} 35 2655 scheduler_pending_pods{queue="backoff"} 0 2656 scheduler_pending_pods{queue="gated"} 5 2657 scheduler_pending_pods{queue="unschedulable"} 20 2658 `, 2659 }, 2660 { 2661 name: "the metrics should not be recorded (pluginMetricsSamplePercent=0)", 2662 operations: []operation{ 2663 add, 2664 }, 2665 operands: [][]*framework.QueuedPodInfo{ 2666 pInfos[:1], 2667 }, 2668 metricsName: "scheduler_plugin_execution_duration_seconds", 2669 pluginMetricsSamplePercent: 0, 2670 wants: ` 2671 # HELP scheduler_plugin_execution_duration_seconds [ALPHA] Duration for running a plugin at a specific extension point. 2672 # TYPE scheduler_plugin_execution_duration_seconds histogram 2673 `, // the observed value will always be 0, because we don't proceed the fake clock. 2674 }, 2675 { 2676 name: "the metrics should be recorded (pluginMetricsSamplePercent=100)", 2677 operations: []operation{ 2678 add, 2679 }, 2680 operands: [][]*framework.QueuedPodInfo{ 2681 pInfos[:1], 2682 }, 2683 metricsName: "scheduler_plugin_execution_duration_seconds", 2684 pluginMetricsSamplePercent: 100, 2685 wants: ` 2686 # HELP scheduler_plugin_execution_duration_seconds [ALPHA] Duration for running a plugin at a specific extension point. 2687 # TYPE scheduler_plugin_execution_duration_seconds histogram 2688 scheduler_plugin_execution_duration_seconds_bucket{extension_point="PreEnqueue",plugin="preEnqueuePlugin",status="Success",le="1e-05"} 1 2689 scheduler_plugin_execution_duration_seconds_bucket{extension_point="PreEnqueue",plugin="preEnqueuePlugin",status="Success",le="1.5000000000000002e-05"} 1 2690 scheduler_plugin_execution_duration_seconds_bucket{extension_point="PreEnqueue",plugin="preEnqueuePlugin",status="Success",le="2.2500000000000005e-05"} 1 2691 scheduler_plugin_execution_duration_seconds_bucket{extension_point="PreEnqueue",plugin="preEnqueuePlugin",status="Success",le="3.375000000000001e-05"} 1 2692 scheduler_plugin_execution_duration_seconds_bucket{extension_point="PreEnqueue",plugin="preEnqueuePlugin",status="Success",le="5.062500000000001e-05"} 1 2693 scheduler_plugin_execution_duration_seconds_bucket{extension_point="PreEnqueue",plugin="preEnqueuePlugin",status="Success",le="7.593750000000002e-05"} 1 2694 scheduler_plugin_execution_duration_seconds_bucket{extension_point="PreEnqueue",plugin="preEnqueuePlugin",status="Success",le="0.00011390625000000003"} 1 2695 scheduler_plugin_execution_duration_seconds_bucket{extension_point="PreEnqueue",plugin="preEnqueuePlugin",status="Success",le="0.00017085937500000006"} 1 2696 scheduler_plugin_execution_duration_seconds_bucket{extension_point="PreEnqueue",plugin="preEnqueuePlugin",status="Success",le="0.0002562890625000001"} 1 2697 scheduler_plugin_execution_duration_seconds_bucket{extension_point="PreEnqueue",plugin="preEnqueuePlugin",status="Success",le="0.00038443359375000017"} 1 2698 scheduler_plugin_execution_duration_seconds_bucket{extension_point="PreEnqueue",plugin="preEnqueuePlugin",status="Success",le="0.0005766503906250003"} 1 2699 scheduler_plugin_execution_duration_seconds_bucket{extension_point="PreEnqueue",plugin="preEnqueuePlugin",status="Success",le="0.0008649755859375004"} 1 2700 scheduler_plugin_execution_duration_seconds_bucket{extension_point="PreEnqueue",plugin="preEnqueuePlugin",status="Success",le="0.0012974633789062506"} 1 2701 scheduler_plugin_execution_duration_seconds_bucket{extension_point="PreEnqueue",plugin="preEnqueuePlugin",status="Success",le="0.0019461950683593758"} 1 2702 scheduler_plugin_execution_duration_seconds_bucket{extension_point="PreEnqueue",plugin="preEnqueuePlugin",status="Success",le="0.0029192926025390638"} 1 2703 scheduler_plugin_execution_duration_seconds_bucket{extension_point="PreEnqueue",plugin="preEnqueuePlugin",status="Success",le="0.004378938903808595"} 1 2704 scheduler_plugin_execution_duration_seconds_bucket{extension_point="PreEnqueue",plugin="preEnqueuePlugin",status="Success",le="0.006568408355712893"} 1 2705 scheduler_plugin_execution_duration_seconds_bucket{extension_point="PreEnqueue",plugin="preEnqueuePlugin",status="Success",le="0.009852612533569338"} 1 2706 scheduler_plugin_execution_duration_seconds_bucket{extension_point="PreEnqueue",plugin="preEnqueuePlugin",status="Success",le="0.014778918800354007"} 1 2707 scheduler_plugin_execution_duration_seconds_bucket{extension_point="PreEnqueue",plugin="preEnqueuePlugin",status="Success",le="0.02216837820053101"} 1 2708 scheduler_plugin_execution_duration_seconds_bucket{extension_point="PreEnqueue",plugin="preEnqueuePlugin",status="Success",le="+Inf"} 1 2709 scheduler_plugin_execution_duration_seconds_sum{extension_point="PreEnqueue",plugin="preEnqueuePlugin",status="Success"} 0 2710 scheduler_plugin_execution_duration_seconds_count{extension_point="PreEnqueue",plugin="preEnqueuePlugin",status="Success"} 1 2711 `, // the observed value will always be 0, because we don't proceed the fake clock. 2712 }, 2713 } 2714 2715 resetMetrics := func() { 2716 metrics.ActivePods().Set(0) 2717 metrics.BackoffPods().Set(0) 2718 metrics.UnschedulablePods().Set(0) 2719 metrics.GatedPods().Set(0) 2720 metrics.PluginExecutionDuration.Reset() 2721 } 2722 2723 for _, test := range tests { 2724 t.Run(test.name, func(t *testing.T) { 2725 resetMetrics() 2726 logger, ctx := ktesting.NewTestContext(t) 2727 ctx, cancel := context.WithCancel(ctx) 2728 defer cancel() 2729 2730 m := map[string][]framework.PreEnqueuePlugin{"": {&preEnqueuePlugin{allowlists: []string{queueable}}}} 2731 recorder := metrics.NewMetricsAsyncRecorder(3, 20*time.Microsecond, ctx.Done()) 2732 queue := NewTestQueue(ctx, newDefaultQueueSort(), WithClock(testingclock.NewFakeClock(timestamp)), WithPreEnqueuePluginMap(m), WithPluginMetricsSamplePercent(test.pluginMetricsSamplePercent), WithMetricsRecorder(*recorder)) 2733 for i, op := range test.operations { 2734 for _, pInfo := range test.operands[i] { 2735 op(t, logger, queue, pInfo) 2736 } 2737 } 2738 2739 recorder.FlushMetrics() 2740 2741 if err := testutil.GatherAndCompare(metrics.GetGather(), strings.NewReader(test.wants), test.metricsName); err != nil { 2742 t.Fatal(err) 2743 } 2744 }) 2745 } 2746 } 2747 2748 // TestPerPodSchedulingMetrics makes sure pod schedule attempts is updated correctly while 2749 // initialAttemptTimestamp stays the same during multiple add/pop operations. 2750 func TestPerPodSchedulingMetrics(t *testing.T) { 2751 timestamp := time.Now() 2752 2753 logger, ctx := ktesting.NewTestContext(t) 2754 ctx, cancel := context.WithCancel(ctx) 2755 defer cancel() 2756 2757 tests := []struct { 2758 name string 2759 perPodSchedulingMetricsScenario func(*testingclock.FakeClock, *PriorityQueue, *v1.Pod) 2760 wantAttempts int 2761 wantInitialAttemptTs time.Time 2762 }{ 2763 { 2764 // The queue operations are Add -> Pop. 2765 name: "pod is created and scheduled after 1 attempt", 2766 perPodSchedulingMetricsScenario: func(c *testingclock.FakeClock, queue *PriorityQueue, pod *v1.Pod) { 2767 queue.Add(logger, pod) 2768 }, 2769 wantAttempts: 1, 2770 wantInitialAttemptTs: timestamp, 2771 }, 2772 { 2773 // The queue operations are Add -> Pop -> AddUnschedulableIfNotPresent -> flushUnschedulablePodsLeftover -> Pop. 2774 name: "pod is created and scheduled after 2 attempts", 2775 perPodSchedulingMetricsScenario: func(c *testingclock.FakeClock, queue *PriorityQueue, pod *v1.Pod) { 2776 queue.Add(logger, pod) 2777 pInfo, err := queue.Pop(logger) 2778 if err != nil { 2779 t.Fatalf("Failed to pop a pod %v", err) 2780 } 2781 2782 pInfo.UnschedulablePlugins = sets.New("plugin") 2783 queue.AddUnschedulableIfNotPresent(logger, pInfo, 1) 2784 // Override clock to exceed the DefaultPodMaxInUnschedulablePodsDuration so that unschedulable pods 2785 // will be moved to activeQ 2786 c.SetTime(timestamp.Add(DefaultPodMaxInUnschedulablePodsDuration + 1)) 2787 queue.flushUnschedulablePodsLeftover(logger) 2788 }, 2789 wantAttempts: 2, 2790 wantInitialAttemptTs: timestamp, 2791 }, 2792 { 2793 // The queue operations are Add -> Pop -> AddUnschedulableIfNotPresent -> flushUnschedulablePodsLeftover -> Update -> Pop. 2794 name: "pod is created and scheduled after 2 attempts but before the second pop, call update", 2795 perPodSchedulingMetricsScenario: func(c *testingclock.FakeClock, queue *PriorityQueue, pod *v1.Pod) { 2796 queue.Add(logger, pod) 2797 pInfo, err := queue.Pop(logger) 2798 if err != nil { 2799 t.Fatalf("Failed to pop a pod %v", err) 2800 } 2801 2802 pInfo.UnschedulablePlugins = sets.New("plugin") 2803 queue.AddUnschedulableIfNotPresent(logger, pInfo, 1) 2804 // Override clock to exceed the DefaultPodMaxInUnschedulablePodsDuration so that unschedulable pods 2805 // will be moved to activeQ 2806 updatedTimestamp := timestamp 2807 c.SetTime(updatedTimestamp.Add(DefaultPodMaxInUnschedulablePodsDuration + 1)) 2808 queue.flushUnschedulablePodsLeftover(logger) 2809 newPod := pod.DeepCopy() 2810 newPod.Generation = 1 2811 queue.Update(logger, pod, newPod) 2812 }, 2813 wantAttempts: 2, 2814 wantInitialAttemptTs: timestamp, 2815 }, 2816 { 2817 // The queue operations are Add gated pod -> check unschedulablePods -> lift gate & update pod -> Pop. 2818 name: "A gated pod is created and scheduled after lifting gate", 2819 perPodSchedulingMetricsScenario: func(c *testingclock.FakeClock, queue *PriorityQueue, pod *v1.Pod) { 2820 // Create a queue with PreEnqueuePlugin 2821 queue.preEnqueuePluginMap = map[string][]framework.PreEnqueuePlugin{"": {&preEnqueuePlugin{allowlists: []string{"foo"}}}} 2822 queue.pluginMetricsSamplePercent = 0 2823 queue.Add(logger, pod) 2824 // Check pod is added to the unschedulablePods queue. 2825 if getUnschedulablePod(queue, pod) != pod { 2826 t.Errorf("Pod %v was not found in the unschedulablePods.", pod.Name) 2827 } 2828 // Override clock to get different InitialAttemptTimestamp 2829 c.Step(1 * time.Minute) 2830 2831 // Update pod with the required label to get it out of unschedulablePods queue. 2832 updateGatedPod := pod.DeepCopy() 2833 updateGatedPod.Labels = map[string]string{"foo": ""} 2834 queue.Update(logger, pod, updateGatedPod) 2835 }, 2836 wantAttempts: 1, 2837 wantInitialAttemptTs: timestamp.Add(1 * time.Minute), 2838 }, 2839 } 2840 for _, test := range tests { 2841 t.Run(test.name, func(t *testing.T) { 2842 2843 c := testingclock.NewFakeClock(timestamp) 2844 pod := st.MakePod().Name("test-pod").Namespace("test-ns").UID("test-uid").Obj() 2845 queue := NewTestQueue(ctx, newDefaultQueueSort(), WithClock(c)) 2846 2847 test.perPodSchedulingMetricsScenario(c, queue, pod) 2848 podInfo, err := queue.Pop(logger) 2849 if err != nil { 2850 t.Fatal(err) 2851 } 2852 if podInfo.Attempts != test.wantAttempts { 2853 t.Errorf("Pod schedule attempt unexpected, got %v, want %v", podInfo.Attempts, test.wantAttempts) 2854 } 2855 if *podInfo.InitialAttemptTimestamp != test.wantInitialAttemptTs { 2856 t.Errorf("Pod initial schedule attempt timestamp unexpected, got %v, want %v", *podInfo.InitialAttemptTimestamp, test.wantInitialAttemptTs) 2857 } 2858 }) 2859 } 2860 } 2861 2862 func TestIncomingPodsMetrics(t *testing.T) { 2863 timestamp := time.Now() 2864 unschedulablePlg := "unschedulable_plugin" 2865 metrics.Register() 2866 var pInfos = make([]*framework.QueuedPodInfo, 0, 3) 2867 for i := 1; i <= 3; i++ { 2868 p := &framework.QueuedPodInfo{ 2869 PodInfo: mustNewTestPodInfo(t, 2870 st.MakePod().Name(fmt.Sprintf("test-pod-%d", i)).Namespace(fmt.Sprintf("ns%d", i)).UID(fmt.Sprintf("tp-%d", i)).Obj()), 2871 Timestamp: timestamp, 2872 UnschedulablePlugins: sets.New(unschedulablePlg), 2873 } 2874 pInfos = append(pInfos, p) 2875 } 2876 tests := []struct { 2877 name string 2878 operations []operation 2879 want string 2880 }{ 2881 { 2882 name: "add pods to activeQ", 2883 operations: []operation{ 2884 add, 2885 }, 2886 want: ` 2887 scheduler_queue_incoming_pods_total{event="PodAdd",queue="active"} 3 2888 `, 2889 }, 2890 { 2891 name: "add pods to unschedulablePods", 2892 operations: []operation{ 2893 popAndRequeueAsUnschedulable, 2894 }, 2895 want: ` 2896 scheduler_queue_incoming_pods_total{event="ScheduleAttemptFailure",queue="unschedulable"} 3 2897 `, 2898 }, 2899 { 2900 name: "add pods to unschedulablePods and then move all to backoffQ", 2901 operations: []operation{ 2902 popAndRequeueAsUnschedulable, 2903 moveAllToActiveOrBackoffQ, 2904 }, 2905 want: ` scheduler_queue_incoming_pods_total{event="ScheduleAttemptFailure",queue="unschedulable"} 3 2906 scheduler_queue_incoming_pods_total{event="UnschedulableTimeout",queue="backoff"} 3 2907 `, 2908 }, 2909 { 2910 name: "add pods to unschedulablePods and then move all to activeQ", 2911 operations: []operation{ 2912 popAndRequeueAsUnschedulable, 2913 moveClockForward, 2914 moveAllToActiveOrBackoffQ, 2915 }, 2916 want: ` scheduler_queue_incoming_pods_total{event="ScheduleAttemptFailure",queue="unschedulable"} 3 2917 scheduler_queue_incoming_pods_total{event="UnschedulableTimeout",queue="active"} 3 2918 `, 2919 }, 2920 { 2921 name: "make some pods subject to backoff and add them to backoffQ, then flush backoffQ", 2922 operations: []operation{ 2923 popAndRequeueAsBackoff, 2924 moveClockForward, 2925 flushBackoffQ, 2926 }, 2927 want: ` scheduler_queue_incoming_pods_total{event="BackoffComplete",queue="active"} 3 2928 scheduler_queue_incoming_pods_total{event="ScheduleAttemptFailure",queue="backoff"} 3 2929 `, 2930 }, 2931 } 2932 2933 for _, test := range tests { 2934 t.Run(test.name, func(t *testing.T) { 2935 metrics.SchedulerQueueIncomingPods.Reset() 2936 logger, ctx := ktesting.NewTestContext(t) 2937 ctx, cancel := context.WithCancel(ctx) 2938 defer cancel() 2939 queue := NewTestQueue(ctx, newDefaultQueueSort(), WithClock(testingclock.NewFakeClock(timestamp))) 2940 for _, op := range test.operations { 2941 for _, pInfo := range pInfos { 2942 op(t, logger, queue, pInfo) 2943 } 2944 } 2945 metricName := metrics.SchedulerSubsystem + "_" + metrics.SchedulerQueueIncomingPods.Name 2946 if err := testutil.CollectAndCompare(metrics.SchedulerQueueIncomingPods, strings.NewReader(queueMetricMetadata+test.want), metricName); err != nil { 2947 t.Errorf("unexpected collecting result:\n%s", err) 2948 } 2949 2950 }) 2951 } 2952 } 2953 2954 func TestBackOffFlow(t *testing.T) { 2955 cl := testingclock.NewFakeClock(time.Now()) 2956 logger, ctx := ktesting.NewTestContext(t) 2957 ctx, cancel := context.WithCancel(ctx) 2958 defer cancel() 2959 q := NewTestQueue(ctx, newDefaultQueueSort(), WithClock(cl)) 2960 steps := []struct { 2961 wantBackoff time.Duration 2962 }{ 2963 {wantBackoff: time.Second}, 2964 {wantBackoff: 2 * time.Second}, 2965 {wantBackoff: 4 * time.Second}, 2966 {wantBackoff: 8 * time.Second}, 2967 {wantBackoff: 10 * time.Second}, 2968 {wantBackoff: 10 * time.Second}, 2969 {wantBackoff: 10 * time.Second}, 2970 } 2971 pod := st.MakePod().Name("test-pod").Namespace("test-ns").UID("test-uid").Obj() 2972 2973 podID := types.NamespacedName{ 2974 Namespace: pod.Namespace, 2975 Name: pod.Name, 2976 } 2977 if err := q.Add(logger, pod); err != nil { 2978 t.Fatal(err) 2979 } 2980 2981 for i, step := range steps { 2982 t.Run(fmt.Sprintf("step %d", i), func(t *testing.T) { 2983 timestamp := cl.Now() 2984 // Simulate schedule attempt. 2985 podInfo, err := q.Pop(logger) 2986 if err != nil { 2987 t.Fatal(err) 2988 } 2989 if podInfo.Attempts != i+1 { 2990 t.Errorf("got attempts %d, want %d", podInfo.Attempts, i+1) 2991 } 2992 err = q.AddUnschedulableIfNotPresent(logger, podInfo, int64(i)) 2993 if err != nil { 2994 t.Fatalf("unexpected error from AddUnschedulableIfNotPresent: %v", err) 2995 } 2996 2997 // An event happens. 2998 q.MoveAllToActiveOrBackoffQueue(logger, UnschedulableTimeout, nil, nil, nil) 2999 3000 if _, ok, _ := q.podBackoffQ.Get(podInfo); !ok { 3001 t.Errorf("pod %v is not in the backoff queue", podID) 3002 } 3003 3004 // Check backoff duration. 3005 deadline := q.getBackoffTime(podInfo) 3006 backoff := deadline.Sub(timestamp) 3007 if backoff != step.wantBackoff { 3008 t.Errorf("got backoff %s, want %s", backoff, step.wantBackoff) 3009 } 3010 3011 // Simulate routine that continuously flushes the backoff queue. 3012 cl.Step(time.Millisecond) 3013 q.flushBackoffQCompleted(logger) 3014 // Still in backoff queue after an early flush. 3015 if _, ok, _ := q.podBackoffQ.Get(podInfo); !ok { 3016 t.Errorf("pod %v is not in the backoff queue", podID) 3017 } 3018 // Moved out of the backoff queue after timeout. 3019 cl.Step(backoff) 3020 q.flushBackoffQCompleted(logger) 3021 if _, ok, _ := q.podBackoffQ.Get(podInfo); ok { 3022 t.Errorf("pod %v is still in the backoff queue", podID) 3023 } 3024 }) 3025 } 3026 } 3027 3028 func TestMoveAllToActiveOrBackoffQueue_PreEnqueueChecks(t *testing.T) { 3029 var podInfos []*framework.QueuedPodInfo 3030 for i := 0; i < 5; i++ { 3031 pInfo := newQueuedPodInfoForLookup( 3032 st.MakePod().Name(fmt.Sprintf("p%d", i)).Priority(int32(i)).Obj(), 3033 ) 3034 podInfos = append(podInfos, pInfo) 3035 } 3036 3037 tests := []struct { 3038 name string 3039 preEnqueueCheck PreEnqueueCheck 3040 podInfos []*framework.QueuedPodInfo 3041 want []string 3042 }{ 3043 { 3044 name: "nil PreEnqueueCheck", 3045 podInfos: podInfos, 3046 want: []string{"p0", "p1", "p2", "p3", "p4"}, 3047 }, 3048 { 3049 name: "move Pods with priority greater than 2", 3050 podInfos: podInfos, 3051 preEnqueueCheck: func(pod *v1.Pod) bool { return *pod.Spec.Priority >= 2 }, 3052 want: []string{"p2", "p3", "p4"}, 3053 }, 3054 { 3055 name: "move Pods with even priority and greater than 2", 3056 podInfos: podInfos, 3057 preEnqueueCheck: func(pod *v1.Pod) bool { 3058 return *pod.Spec.Priority%2 == 0 && *pod.Spec.Priority >= 2 3059 }, 3060 want: []string{"p2", "p4"}, 3061 }, 3062 { 3063 name: "move Pods with even and negative priority", 3064 podInfos: podInfos, 3065 preEnqueueCheck: func(pod *v1.Pod) bool { 3066 return *pod.Spec.Priority%2 == 0 && *pod.Spec.Priority < 0 3067 }, 3068 }, 3069 } 3070 3071 for _, tt := range tests { 3072 t.Run(tt.name, func(t *testing.T) { 3073 logger, ctx := ktesting.NewTestContext(t) 3074 ctx, cancel := context.WithCancel(ctx) 3075 defer cancel() 3076 q := NewTestQueue(ctx, newDefaultQueueSort()) 3077 for i, podInfo := range tt.podInfos { 3078 // To simulate the pod is failed in scheduling in the real world, Pop() the pod from activeQ before AddUnschedulableIfNotPresent() below. 3079 q.activeQ.Add(q.newQueuedPodInfo(podInfo.Pod)) 3080 if p, err := q.Pop(logger); err != nil || p.Pod != podInfo.Pod { 3081 t.Errorf("Expected: %v after Pop, but got: %v", podInfo.Pod.Name, p.Pod.Name) 3082 } 3083 podInfo.UnschedulablePlugins = sets.New("plugin") 3084 err := q.AddUnschedulableIfNotPresent(logger, podInfo, q.schedulingCycle) 3085 if err != nil { 3086 t.Fatalf("unexpected error from AddUnschedulableIfNotPresent: %v", err) 3087 } 3088 // NOTE: On Windows, time.Now() is not as precise, 2 consecutive calls may return the same timestamp, 3089 // resulting in 0 time delta / latency. This will cause the pods to be backed off in a random 3090 // order, which would cause this test to fail, since the expectation is for them to be backed off 3091 // in a certain order. 3092 // See: https://github.com/golang/go/issues/8687 3093 podInfo.Timestamp = podInfo.Timestamp.Add(time.Duration((i - len(tt.podInfos))) * time.Millisecond) 3094 } 3095 q.MoveAllToActiveOrBackoffQueue(logger, TestEvent, nil, nil, tt.preEnqueueCheck) 3096 var got []string 3097 for q.podBackoffQ.Len() != 0 { 3098 obj, err := q.podBackoffQ.Pop() 3099 if err != nil { 3100 t.Fatalf("Fail to pop pod from backoffQ: %v", err) 3101 } 3102 queuedPodInfo, ok := obj.(*framework.QueuedPodInfo) 3103 if !ok { 3104 t.Fatalf("Fail to convert popped obj (type %T) to *framework.QueuedPodInfo", obj) 3105 } 3106 got = append(got, queuedPodInfo.Pod.Name) 3107 } 3108 if diff := cmp.Diff(tt.want, got); diff != "" { 3109 t.Errorf("Unexpected diff (-want, +got):\n%s", diff) 3110 } 3111 }) 3112 } 3113 } 3114 3115 func makeQueuedPodInfos(num int, namePrefix, label string, timestamp time.Time) []*framework.QueuedPodInfo { 3116 var pInfos = make([]*framework.QueuedPodInfo, 0, num) 3117 for i := 1; i <= num; i++ { 3118 p := &framework.QueuedPodInfo{ 3119 PodInfo: mustNewPodInfo( 3120 st.MakePod().Name(fmt.Sprintf("%v-%d", namePrefix, i)).Namespace(fmt.Sprintf("ns%d", i)).Label(label, "").UID(fmt.Sprintf("tp-%d", i)).Obj()), 3121 Timestamp: timestamp, 3122 UnschedulablePlugins: sets.New[string](), 3123 } 3124 pInfos = append(pInfos, p) 3125 } 3126 return pInfos 3127 } 3128 3129 func TestPriorityQueue_calculateBackoffDuration(t *testing.T) { 3130 tests := []struct { 3131 name string 3132 initialBackoffDuration time.Duration 3133 maxBackoffDuration time.Duration 3134 podInfo *framework.QueuedPodInfo 3135 want time.Duration 3136 }{ 3137 { 3138 name: "normal", 3139 initialBackoffDuration: 1 * time.Nanosecond, 3140 maxBackoffDuration: 32 * time.Nanosecond, 3141 podInfo: &framework.QueuedPodInfo{Attempts: 16}, 3142 want: 32 * time.Nanosecond, 3143 }, 3144 { 3145 name: "overflow_32bit", 3146 initialBackoffDuration: 1 * time.Nanosecond, 3147 maxBackoffDuration: math.MaxInt32 * time.Nanosecond, 3148 podInfo: &framework.QueuedPodInfo{Attempts: 32}, 3149 want: math.MaxInt32 * time.Nanosecond, 3150 }, 3151 { 3152 name: "overflow_64bit", 3153 initialBackoffDuration: 1 * time.Nanosecond, 3154 maxBackoffDuration: math.MaxInt64 * time.Nanosecond, 3155 podInfo: &framework.QueuedPodInfo{Attempts: 64}, 3156 want: math.MaxInt64 * time.Nanosecond, 3157 }, 3158 } 3159 for _, tt := range tests { 3160 t.Run(tt.name, func(t *testing.T) { 3161 ctx, cancel := context.WithCancel(context.Background()) 3162 defer cancel() 3163 q := NewTestQueue(ctx, newDefaultQueueSort(), WithPodInitialBackoffDuration(tt.initialBackoffDuration), WithPodMaxBackoffDuration(tt.maxBackoffDuration)) 3164 if got := q.calculateBackoffDuration(tt.podInfo); got != tt.want { 3165 t.Errorf("PriorityQueue.calculateBackoffDuration() = %v, want %v", got, tt.want) 3166 } 3167 }) 3168 } 3169 } 3170 3171 func mustNewTestPodInfo(t *testing.T, pod *v1.Pod) *framework.PodInfo { 3172 podInfo, err := framework.NewPodInfo(pod) 3173 if err != nil { 3174 t.Fatal(err) 3175 } 3176 return podInfo 3177 } 3178 3179 func mustNewPodInfo(pod *v1.Pod) *framework.PodInfo { 3180 podInfo, err := framework.NewPodInfo(pod) 3181 if err != nil { 3182 panic(err) 3183 } 3184 return podInfo 3185 } 3186 3187 // Test_isPodWorthRequeuing tests isPodWorthRequeuing function. 3188 func Test_isPodWorthRequeuing(t *testing.T) { 3189 count := 0 3190 queueHintReturnQueue := func(logger klog.Logger, pod *v1.Pod, oldObj, newObj interface{}) (framework.QueueingHint, error) { 3191 count++ 3192 return framework.Queue, nil 3193 } 3194 queueHintReturnSkip := func(logger klog.Logger, pod *v1.Pod, oldObj, newObj interface{}) (framework.QueueingHint, error) { 3195 count++ 3196 return framework.QueueSkip, nil 3197 } 3198 queueHintReturnErr := func(logger klog.Logger, pod *v1.Pod, oldObj, newObj interface{}) (framework.QueueingHint, error) { 3199 count++ 3200 return framework.QueueSkip, fmt.Errorf("unexpected error") 3201 } 3202 3203 tests := []struct { 3204 name string 3205 podInfo *framework.QueuedPodInfo 3206 event framework.ClusterEvent 3207 oldObj interface{} 3208 newObj interface{} 3209 expected queueingStrategy 3210 expectedExecutionCount int // expected total execution count of queueing hint function 3211 queueingHintMap QueueingHintMapPerProfile 3212 }{ 3213 { 3214 name: "return Queue when no queueing hint function is registered for the event", 3215 podInfo: &framework.QueuedPodInfo{ 3216 UnschedulablePlugins: sets.New("fooPlugin1"), 3217 PodInfo: mustNewPodInfo(st.MakePod().Name("pod1").Namespace("ns1").UID("1").Obj()), 3218 }, 3219 event: NodeAdd, 3220 oldObj: nil, 3221 newObj: st.MakeNode().Obj(), 3222 expected: queueSkip, 3223 expectedExecutionCount: 0, 3224 queueingHintMap: QueueingHintMapPerProfile{ 3225 "": { 3226 // no queueing hint function for NodeAdd. 3227 AssignedPodAdd: { 3228 { 3229 // It will be ignored because the event is not NodeAdd. 3230 PluginName: "fooPlugin1", 3231 QueueingHintFn: queueHintReturnQueue, 3232 }, 3233 }, 3234 }, 3235 }, 3236 }, 3237 { 3238 name: "Treat the event as Queue when QueueHintFn returns error", 3239 podInfo: &framework.QueuedPodInfo{ 3240 UnschedulablePlugins: sets.New("fooPlugin1"), 3241 PodInfo: mustNewPodInfo(st.MakePod().Name("pod1").Namespace("ns1").UID("1").Obj()), 3242 }, 3243 event: NodeAdd, 3244 oldObj: nil, 3245 newObj: st.MakeNode().Obj(), 3246 expected: queueAfterBackoff, 3247 expectedExecutionCount: 1, 3248 queueingHintMap: QueueingHintMapPerProfile{ 3249 "": { 3250 NodeAdd: { 3251 { 3252 PluginName: "fooPlugin1", 3253 QueueingHintFn: queueHintReturnErr, 3254 }, 3255 }, 3256 }, 3257 }, 3258 }, 3259 { 3260 name: "return Queue when the event is wildcard", 3261 podInfo: &framework.QueuedPodInfo{ 3262 UnschedulablePlugins: sets.New("fooPlugin1"), 3263 PodInfo: mustNewPodInfo(st.MakePod().Name("pod1").Namespace("ns1").UID("1").Obj()), 3264 }, 3265 event: WildCardEvent, 3266 oldObj: nil, 3267 newObj: st.MakeNode().Obj(), 3268 expected: queueAfterBackoff, 3269 expectedExecutionCount: 0, 3270 queueingHintMap: QueueingHintMapPerProfile{}, 3271 }, 3272 { 3273 name: "interprets Queue from the Pending plugin as queueImmediately", 3274 podInfo: &framework.QueuedPodInfo{ 3275 UnschedulablePlugins: sets.New("fooPlugin1", "fooPlugin3"), 3276 PendingPlugins: sets.New("fooPlugin2"), 3277 PodInfo: mustNewPodInfo(st.MakePod().Name("pod1").Namespace("ns1").UID("1").Obj()), 3278 }, 3279 event: NodeAdd, 3280 oldObj: nil, 3281 newObj: st.MakeNode().Node, 3282 expected: queueImmediately, 3283 expectedExecutionCount: 2, 3284 queueingHintMap: QueueingHintMapPerProfile{ 3285 "": { 3286 NodeAdd: { 3287 { 3288 PluginName: "fooPlugin1", 3289 // It returns Queue and it's interpreted as queueAfterBackoff. 3290 // But, the function continues to run other hints because the Pod has PendingPlugins, which can result in queueImmediately. 3291 QueueingHintFn: queueHintReturnQueue, 3292 }, 3293 { 3294 PluginName: "fooPlugin2", 3295 // It's interpreted as queueImmediately. 3296 // The function doesn't run other hints because queueImmediately is the highest priority. 3297 QueueingHintFn: queueHintReturnQueue, 3298 }, 3299 { 3300 PluginName: "fooPlugin3", 3301 QueueingHintFn: queueHintReturnQueue, 3302 }, 3303 { 3304 PluginName: "fooPlugin4", 3305 QueueingHintFn: queueHintReturnErr, 3306 }, 3307 }, 3308 }, 3309 }, 3310 }, 3311 { 3312 name: "interprets Queue from the Unschedulable plugin as queueAfterBackoff", 3313 podInfo: &framework.QueuedPodInfo{ 3314 UnschedulablePlugins: sets.New("fooPlugin1", "fooPlugin2"), 3315 PodInfo: mustNewPodInfo(st.MakePod().Name("pod1").Namespace("ns1").UID("1").Obj()), 3316 }, 3317 event: NodeAdd, 3318 oldObj: nil, 3319 newObj: st.MakeNode().Obj(), 3320 expected: queueAfterBackoff, 3321 expectedExecutionCount: 2, 3322 queueingHintMap: QueueingHintMapPerProfile{ 3323 "": { 3324 NodeAdd: { 3325 { 3326 // Skip will be ignored 3327 PluginName: "fooPlugin1", 3328 QueueingHintFn: queueHintReturnSkip, 3329 }, 3330 { 3331 // Skip will be ignored 3332 PluginName: "fooPlugin2", 3333 QueueingHintFn: queueHintReturnQueue, 3334 }, 3335 }, 3336 }, 3337 }, 3338 }, 3339 { 3340 name: "Queueing hint function that isn't from the plugin in UnschedulablePlugins/PendingPlugins is ignored", 3341 podInfo: &framework.QueuedPodInfo{ 3342 UnschedulablePlugins: sets.New("fooPlugin1", "fooPlugin2"), 3343 PodInfo: mustNewPodInfo(st.MakePod().Name("pod1").Namespace("ns1").UID("1").Obj()), 3344 }, 3345 event: NodeAdd, 3346 oldObj: nil, 3347 newObj: st.MakeNode().Node, 3348 expected: queueSkip, 3349 expectedExecutionCount: 2, 3350 queueingHintMap: QueueingHintMapPerProfile{ 3351 "": { 3352 NodeAdd: { 3353 { 3354 PluginName: "fooPlugin1", 3355 QueueingHintFn: queueHintReturnSkip, 3356 }, 3357 { 3358 PluginName: "fooPlugin2", 3359 QueueingHintFn: queueHintReturnSkip, 3360 }, 3361 { 3362 PluginName: "fooPlugin3", 3363 QueueingHintFn: queueHintReturnQueue, // It'll be ignored. 3364 }, 3365 }, 3366 }, 3367 }, 3368 }, 3369 { 3370 name: "If event is specific Node update event, queueing hint function for NodeUpdate/UpdateNodeLabel is also executed", 3371 podInfo: &framework.QueuedPodInfo{ 3372 UnschedulablePlugins: sets.New("fooPlugin1", "fooPlugin2"), 3373 PodInfo: mustNewPodInfo(st.MakePod().Name("pod1").Namespace("ns1").UID("1").Obj()), 3374 }, 3375 event: framework.ClusterEvent{Resource: framework.Node, ActionType: framework.UpdateNodeLabel}, 3376 oldObj: nil, 3377 newObj: st.MakeNode().Obj(), 3378 expected: queueAfterBackoff, 3379 expectedExecutionCount: 1, 3380 queueingHintMap: QueueingHintMapPerProfile{ 3381 "": { 3382 framework.ClusterEvent{Resource: framework.Node, ActionType: framework.UpdateNodeLabel}: { 3383 { 3384 PluginName: "fooPlugin1", 3385 // It's only executed and interpreted as queueAfterBackoff. 3386 // The function doesn't run other hints because this Pod doesn't have PendingPlugins. 3387 QueueingHintFn: queueHintReturnQueue, 3388 }, 3389 { 3390 PluginName: "fooPlugin2", 3391 QueueingHintFn: queueHintReturnQueue, 3392 }, 3393 }, 3394 framework.ClusterEvent{Resource: framework.Node, ActionType: framework.Update}: { 3395 { 3396 PluginName: "fooPlugin1", 3397 QueueingHintFn: queueHintReturnQueue, 3398 }, 3399 }, 3400 NodeAdd: { // not executed because NodeAdd is unrelated. 3401 { 3402 PluginName: "fooPlugin1", 3403 QueueingHintFn: queueHintReturnQueue, 3404 }, 3405 }, 3406 }, 3407 }, 3408 }, 3409 } 3410 3411 for _, test := range tests { 3412 t.Run(test.name, func(t *testing.T) { 3413 count = 0 // reset count every time 3414 logger, ctx := ktesting.NewTestContext(t) 3415 q := NewTestQueue(ctx, newDefaultQueueSort(), WithQueueingHintMapPerProfile(test.queueingHintMap)) 3416 actual := q.isPodWorthRequeuing(logger, test.podInfo, test.event, test.oldObj, test.newObj) 3417 if actual != test.expected { 3418 t.Errorf("isPodWorthRequeuing() = %v, want %v", actual, test.expected) 3419 } 3420 if count != test.expectedExecutionCount { 3421 t.Errorf("isPodWorthRequeuing() executed queueing hint functions %v times, expected: %v", count, test.expectedExecutionCount) 3422 } 3423 }) 3424 } 3425 }