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