k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/test/integration/scheduler/plugins/plugins_test.go (about) 1 /* 2 Copyright 2018 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 plugins contains functional tests for scheduler plugin support. 18 // Beware that the plugins in this directory are not meant to be used in 19 // performance tests because they don't behave like real plugins. 20 package plugins 21 22 import ( 23 "context" 24 "fmt" 25 "sync" 26 "testing" 27 "time" 28 29 v1 "k8s.io/api/core/v1" 30 "k8s.io/apimachinery/pkg/api/errors" 31 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 32 "k8s.io/apimachinery/pkg/labels" 33 "k8s.io/apimachinery/pkg/runtime" 34 "k8s.io/apimachinery/pkg/types" 35 "k8s.io/apimachinery/pkg/util/sets" 36 "k8s.io/apimachinery/pkg/util/wait" 37 clientset "k8s.io/client-go/kubernetes" 38 listersv1 "k8s.io/client-go/listers/core/v1" 39 configv1 "k8s.io/kube-scheduler/config/v1" 40 "k8s.io/kubernetes/pkg/scheduler" 41 schedulerconfig "k8s.io/kubernetes/pkg/scheduler/apis/config" 42 configtesting "k8s.io/kubernetes/pkg/scheduler/apis/config/testing" 43 "k8s.io/kubernetes/pkg/scheduler/framework" 44 "k8s.io/kubernetes/pkg/scheduler/framework/plugins/defaultbinder" 45 "k8s.io/kubernetes/pkg/scheduler/framework/plugins/noderesources" 46 "k8s.io/kubernetes/pkg/scheduler/framework/plugins/schedulinggates" 47 frameworkruntime "k8s.io/kubernetes/pkg/scheduler/framework/runtime" 48 st "k8s.io/kubernetes/pkg/scheduler/testing" 49 schedulerutils "k8s.io/kubernetes/test/integration/scheduler" 50 testutils "k8s.io/kubernetes/test/integration/util" 51 imageutils "k8s.io/kubernetes/test/utils/image" 52 "k8s.io/utils/pointer" 53 ) 54 55 // imported from testutils 56 var ( 57 initRegistryAndConfig = func(t *testing.T, plugins ...framework.Plugin) (frameworkruntime.Registry, schedulerconfig.KubeSchedulerProfile) { 58 return schedulerutils.InitRegistryAndConfig(t, newPlugin, plugins...) 59 } 60 ) 61 62 // newPlugin returns a plugin factory with specified Plugin. 63 func newPlugin(plugin framework.Plugin) frameworkruntime.PluginFactory { 64 return func(_ context.Context, _ runtime.Object, fh framework.Handle) (framework.Plugin, error) { 65 switch pl := plugin.(type) { 66 case *PermitPlugin: 67 pl.fh = fh 68 case *PostFilterPlugin: 69 pl.fh = fh 70 } 71 return plugin, nil 72 } 73 } 74 75 type PreEnqueuePlugin struct { 76 called int 77 admit bool 78 } 79 80 type PreFilterPlugin struct { 81 numPreFilterCalled int 82 failPreFilter bool 83 rejectPreFilter bool 84 preFilterResultNodes sets.Set[string] 85 } 86 87 type ScorePlugin struct { 88 mutex sync.Mutex 89 failScore bool 90 numScoreCalled int 91 highScoreNode string 92 } 93 94 func (sp *ScorePlugin) deepCopy() *ScorePlugin { 95 sp.mutex.Lock() 96 defer sp.mutex.Unlock() 97 98 return &ScorePlugin{ 99 failScore: sp.failScore, 100 numScoreCalled: sp.numScoreCalled, 101 highScoreNode: sp.highScoreNode, 102 } 103 } 104 105 type ScoreWithNormalizePlugin struct { 106 mutex sync.Mutex 107 numScoreCalled int 108 numNormalizeScoreCalled int 109 } 110 111 func (sp *ScoreWithNormalizePlugin) deepCopy() *ScoreWithNormalizePlugin { 112 sp.mutex.Lock() 113 defer sp.mutex.Unlock() 114 115 return &ScoreWithNormalizePlugin{ 116 numScoreCalled: sp.numScoreCalled, 117 numNormalizeScoreCalled: sp.numNormalizeScoreCalled, 118 } 119 } 120 121 type FilterPlugin struct { 122 mutex sync.Mutex 123 numFilterCalled int 124 failFilter bool 125 rejectFilter bool 126 127 numCalledPerPod map[string]int 128 } 129 130 func (fp *FilterPlugin) deepCopy() *FilterPlugin { 131 fp.mutex.Lock() 132 defer fp.mutex.Unlock() 133 134 clone := &FilterPlugin{ 135 numFilterCalled: fp.numFilterCalled, 136 failFilter: fp.failFilter, 137 rejectFilter: fp.rejectFilter, 138 numCalledPerPod: make(map[string]int), 139 } 140 for pod, counter := range fp.numCalledPerPod { 141 clone.numCalledPerPod[pod] = counter 142 } 143 return clone 144 } 145 146 type PostFilterPlugin struct { 147 name string 148 fh framework.Handle 149 numPostFilterCalled int 150 failPostFilter bool 151 rejectPostFilter bool 152 breakPostFilter bool 153 } 154 155 type ReservePlugin struct { 156 name string 157 numReserveCalled int 158 failReserve bool 159 numUnreserveCalled int 160 pluginInvokeEventChan chan pluginInvokeEvent 161 } 162 163 type PreScorePlugin struct { 164 numPreScoreCalled int 165 failPreScore bool 166 } 167 168 type PreBindPlugin struct { 169 mutex sync.Mutex 170 numPreBindCalled int 171 failPreBind bool 172 rejectPreBind bool 173 // If set to true, always succeed on non-first scheduling attempt. 174 succeedOnRetry bool 175 // Record the pod UIDs that have been tried scheduling. 176 podUIDs map[types.UID]struct{} 177 } 178 179 func (pp *PreBindPlugin) set(fail, reject, succeed bool) { 180 pp.mutex.Lock() 181 defer pp.mutex.Unlock() 182 183 pp.failPreBind = fail 184 pp.rejectPreBind = reject 185 pp.succeedOnRetry = succeed 186 } 187 188 func (pp *PreBindPlugin) deepCopy() *PreBindPlugin { 189 pp.mutex.Lock() 190 defer pp.mutex.Unlock() 191 192 clone := &PreBindPlugin{ 193 numPreBindCalled: pp.numPreBindCalled, 194 failPreBind: pp.failPreBind, 195 rejectPreBind: pp.rejectPreBind, 196 succeedOnRetry: pp.succeedOnRetry, 197 podUIDs: make(map[types.UID]struct{}), 198 } 199 for uid := range pp.podUIDs { 200 clone.podUIDs[uid] = struct{}{} 201 } 202 return clone 203 } 204 205 type BindPlugin struct { 206 mutex sync.Mutex 207 name string 208 numBindCalled int 209 bindStatus *framework.Status 210 client clientset.Interface 211 pluginInvokeEventChan chan pluginInvokeEvent 212 } 213 214 func (bp *BindPlugin) deepCopy() *BindPlugin { 215 bp.mutex.Lock() 216 defer bp.mutex.Unlock() 217 218 return &BindPlugin{ 219 name: bp.name, 220 numBindCalled: bp.numBindCalled, 221 bindStatus: bp.bindStatus, 222 client: bp.client, 223 pluginInvokeEventChan: bp.pluginInvokeEventChan, 224 } 225 } 226 227 type PostBindPlugin struct { 228 mutex sync.Mutex 229 name string 230 numPostBindCalled int 231 pluginInvokeEventChan chan pluginInvokeEvent 232 } 233 234 func (pp *PostBindPlugin) deepCopy() *PostBindPlugin { 235 pp.mutex.Lock() 236 defer pp.mutex.Unlock() 237 238 return &PostBindPlugin{ 239 name: pp.name, 240 numPostBindCalled: pp.numPostBindCalled, 241 pluginInvokeEventChan: pp.pluginInvokeEventChan, 242 } 243 } 244 245 type PermitPlugin struct { 246 mutex sync.Mutex 247 name string 248 numPermitCalled int 249 failPermit bool 250 rejectPermit bool 251 timeoutPermit bool 252 waitAndRejectPermit bool 253 waitAndAllowPermit bool 254 cancelled bool 255 waitingPod string 256 rejectingPod string 257 allowingPod string 258 fh framework.Handle 259 } 260 261 func (pp *PermitPlugin) deepCopy() *PermitPlugin { 262 pp.mutex.Lock() 263 defer pp.mutex.Unlock() 264 265 return &PermitPlugin{ 266 name: pp.name, 267 numPermitCalled: pp.numPermitCalled, 268 failPermit: pp.failPermit, 269 rejectPermit: pp.rejectPermit, 270 timeoutPermit: pp.timeoutPermit, 271 waitAndRejectPermit: pp.waitAndRejectPermit, 272 waitAndAllowPermit: pp.waitAndAllowPermit, 273 cancelled: pp.cancelled, 274 waitingPod: pp.waitingPod, 275 rejectingPod: pp.rejectingPod, 276 allowingPod: pp.allowingPod, 277 fh: pp.fh, 278 } 279 } 280 281 const ( 282 enqueuePluginName = "enqueue-plugin" 283 prefilterPluginName = "prefilter-plugin" 284 postfilterPluginName = "postfilter-plugin" 285 scorePluginName = "score-plugin" 286 scoreWithNormalizePluginName = "score-with-normalize-plugin" 287 filterPluginName = "filter-plugin" 288 preScorePluginName = "prescore-plugin" 289 reservePluginName = "reserve-plugin" 290 preBindPluginName = "prebind-plugin" 291 postBindPluginName = "postbind-plugin" 292 permitPluginName = "permit-plugin" 293 ) 294 295 var _ framework.PreEnqueuePlugin = &PreEnqueuePlugin{} 296 var _ framework.PreFilterPlugin = &PreFilterPlugin{} 297 var _ framework.PostFilterPlugin = &PostFilterPlugin{} 298 var _ framework.ScorePlugin = &ScorePlugin{} 299 var _ framework.FilterPlugin = &FilterPlugin{} 300 var _ framework.ScorePlugin = &ScorePlugin{} 301 var _ framework.ScorePlugin = &ScoreWithNormalizePlugin{} 302 var _ framework.ReservePlugin = &ReservePlugin{} 303 var _ framework.PreScorePlugin = &PreScorePlugin{} 304 var _ framework.PreBindPlugin = &PreBindPlugin{} 305 var _ framework.BindPlugin = &BindPlugin{} 306 var _ framework.PostBindPlugin = &PostBindPlugin{} 307 var _ framework.PermitPlugin = &PermitPlugin{} 308 309 func (ep *PreEnqueuePlugin) Name() string { 310 return enqueuePluginName 311 } 312 313 func (ep *PreEnqueuePlugin) PreEnqueue(ctx context.Context, p *v1.Pod) *framework.Status { 314 ep.called++ 315 if ep.admit { 316 return nil 317 } 318 return framework.NewStatus(framework.UnschedulableAndUnresolvable, "not ready for scheduling") 319 } 320 321 // Name returns name of the score plugin. 322 func (sp *ScorePlugin) Name() string { 323 return scorePluginName 324 } 325 326 // Score returns the score of scheduling a pod on a specific node. 327 func (sp *ScorePlugin) Score(ctx context.Context, state *framework.CycleState, p *v1.Pod, nodeName string) (int64, *framework.Status) { 328 sp.mutex.Lock() 329 defer sp.mutex.Unlock() 330 331 sp.numScoreCalled++ 332 if sp.failScore { 333 return 0, framework.NewStatus(framework.Error, fmt.Sprintf("injecting failure for pod %v", p.Name)) 334 } 335 336 score := int64(1) 337 if sp.numScoreCalled == 1 { 338 // The first node is scored the highest, the rest is scored lower. 339 sp.highScoreNode = nodeName 340 score = framework.MaxNodeScore 341 } 342 return score, nil 343 } 344 345 func (sp *ScorePlugin) ScoreExtensions() framework.ScoreExtensions { 346 return nil 347 } 348 349 // Name returns name of the score plugin. 350 func (sp *ScoreWithNormalizePlugin) Name() string { 351 return scoreWithNormalizePluginName 352 } 353 354 // Score returns the score of scheduling a pod on a specific node. 355 func (sp *ScoreWithNormalizePlugin) Score(ctx context.Context, state *framework.CycleState, p *v1.Pod, nodeName string) (int64, *framework.Status) { 356 sp.mutex.Lock() 357 defer sp.mutex.Unlock() 358 359 sp.numScoreCalled++ 360 score := int64(10) 361 return score, nil 362 } 363 364 func (sp *ScoreWithNormalizePlugin) NormalizeScore(ctx context.Context, state *framework.CycleState, pod *v1.Pod, scores framework.NodeScoreList) *framework.Status { 365 sp.numNormalizeScoreCalled++ 366 return nil 367 } 368 369 func (sp *ScoreWithNormalizePlugin) ScoreExtensions() framework.ScoreExtensions { 370 return sp 371 } 372 373 // Name returns name of the plugin. 374 func (fp *FilterPlugin) Name() string { 375 return filterPluginName 376 } 377 378 // Filter is a test function that returns an error or nil, depending on the 379 // value of "failFilter". 380 func (fp *FilterPlugin) Filter(ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodeInfo *framework.NodeInfo) *framework.Status { 381 fp.mutex.Lock() 382 defer fp.mutex.Unlock() 383 384 fp.numFilterCalled++ 385 if fp.numCalledPerPod != nil { 386 fp.numCalledPerPod[fmt.Sprintf("%v/%v", pod.Namespace, pod.Name)]++ 387 } 388 389 if fp.failFilter { 390 return framework.NewStatus(framework.Error, fmt.Sprintf("injecting failure for pod %v", pod.Name)) 391 } 392 if fp.rejectFilter { 393 return framework.NewStatus(framework.Unschedulable, fmt.Sprintf("reject pod %v", pod.Name)) 394 } 395 396 return nil 397 } 398 399 // Name returns name of the plugin. 400 func (rp *ReservePlugin) Name() string { 401 return rp.name 402 } 403 404 // Reserve is a test function that increments an intenral counter and returns 405 // an error or nil, depending on the value of "failReserve". 406 func (rp *ReservePlugin) Reserve(ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodeName string) *framework.Status { 407 rp.numReserveCalled++ 408 if rp.failReserve { 409 return framework.NewStatus(framework.Error, fmt.Sprintf("injecting failure for pod %v", pod.Name)) 410 } 411 return nil 412 } 413 414 // Unreserve is a test function that increments an internal counter and emits 415 // an event to a channel. While Unreserve implementations should normally be 416 // idempotent, we relax that requirement here for testing purposes. 417 func (rp *ReservePlugin) Unreserve(ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodeName string) { 418 rp.numUnreserveCalled++ 419 if rp.pluginInvokeEventChan != nil { 420 rp.pluginInvokeEventChan <- pluginInvokeEvent{pluginName: rp.Name(), val: rp.numUnreserveCalled} 421 } 422 } 423 424 // Name returns name of the plugin. 425 func (*PreScorePlugin) Name() string { 426 return preScorePluginName 427 } 428 429 // PreScore is a test function. 430 func (pfp *PreScorePlugin) PreScore(ctx context.Context, _ *framework.CycleState, pod *v1.Pod, _ []*framework.NodeInfo) *framework.Status { 431 pfp.numPreScoreCalled++ 432 if pfp.failPreScore { 433 return framework.NewStatus(framework.Error, fmt.Sprintf("injecting failure for pod %v", pod.Name)) 434 } 435 436 return nil 437 } 438 439 // Name returns name of the plugin. 440 func (pp *PreBindPlugin) Name() string { 441 return preBindPluginName 442 } 443 444 // PreBind is a test function that returns (true, nil) or errors for testing. 445 func (pp *PreBindPlugin) PreBind(ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodeName string) *framework.Status { 446 pp.mutex.Lock() 447 defer pp.mutex.Unlock() 448 449 pp.numPreBindCalled++ 450 if _, tried := pp.podUIDs[pod.UID]; tried && pp.succeedOnRetry { 451 return nil 452 } 453 pp.podUIDs[pod.UID] = struct{}{} 454 if pp.failPreBind { 455 return framework.NewStatus(framework.Error, fmt.Sprintf("injecting failure for pod %v", pod.Name)) 456 } 457 if pp.rejectPreBind { 458 return framework.NewStatus(framework.Unschedulable, fmt.Sprintf("reject pod %v", pod.Name)) 459 } 460 return nil 461 } 462 463 const bindPluginAnnotation = "bindPluginName" 464 465 func (bp *BindPlugin) Name() string { 466 return bp.name 467 } 468 469 func (bp *BindPlugin) Bind(ctx context.Context, state *framework.CycleState, p *v1.Pod, nodeName string) *framework.Status { 470 bp.mutex.Lock() 471 defer bp.mutex.Unlock() 472 473 bp.numBindCalled++ 474 if bp.pluginInvokeEventChan != nil { 475 bp.pluginInvokeEventChan <- pluginInvokeEvent{pluginName: bp.Name(), val: bp.numBindCalled} 476 } 477 if bp.bindStatus.IsSuccess() { 478 if err := bp.client.CoreV1().Pods(p.Namespace).Bind(ctx, &v1.Binding{ 479 ObjectMeta: metav1.ObjectMeta{Namespace: p.Namespace, Name: p.Name, UID: p.UID, Annotations: map[string]string{bindPluginAnnotation: bp.Name()}}, 480 Target: v1.ObjectReference{ 481 Kind: "Node", 482 Name: nodeName, 483 }, 484 }, metav1.CreateOptions{}); err != nil { 485 return framework.NewStatus(framework.Error, fmt.Sprintf("bind failed: %v", err)) 486 } 487 } 488 return bp.bindStatus 489 } 490 491 // Name returns name of the plugin. 492 func (pp *PostBindPlugin) Name() string { 493 return pp.name 494 } 495 496 // PostBind is a test function, which counts the number of times called. 497 func (pp *PostBindPlugin) PostBind(ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodeName string) { 498 pp.mutex.Lock() 499 defer pp.mutex.Unlock() 500 501 pp.numPostBindCalled++ 502 if pp.pluginInvokeEventChan != nil { 503 pp.pluginInvokeEventChan <- pluginInvokeEvent{pluginName: pp.Name(), val: pp.numPostBindCalled} 504 } 505 } 506 507 // Name returns name of the plugin. 508 func (pp *PreFilterPlugin) Name() string { 509 return prefilterPluginName 510 } 511 512 // Extensions returns the PreFilterExtensions interface. 513 func (pp *PreFilterPlugin) PreFilterExtensions() framework.PreFilterExtensions { 514 return nil 515 } 516 517 // PreFilter is a test function that returns (true, nil) or errors for testing. 518 func (pp *PreFilterPlugin) PreFilter(ctx context.Context, state *framework.CycleState, pod *v1.Pod) (*framework.PreFilterResult, *framework.Status) { 519 pp.numPreFilterCalled++ 520 if pp.failPreFilter { 521 return nil, framework.NewStatus(framework.Error, fmt.Sprintf("injecting failure for pod %v", pod.Name)) 522 } 523 if pp.rejectPreFilter { 524 return nil, framework.NewStatus(framework.Unschedulable, fmt.Sprintf("reject pod %v", pod.Name)) 525 } 526 if len(pp.preFilterResultNodes) != 0 { 527 return &framework.PreFilterResult{NodeNames: pp.preFilterResultNodes}, nil 528 } 529 return nil, nil 530 } 531 532 // Name returns name of the plugin. 533 func (pp *PostFilterPlugin) Name() string { 534 return pp.name 535 } 536 537 func (pp *PostFilterPlugin) PostFilter(ctx context.Context, state *framework.CycleState, pod *v1.Pod, _ framework.NodeToStatusMap) (*framework.PostFilterResult, *framework.Status) { 538 pp.numPostFilterCalled++ 539 nodeInfos, err := pp.fh.SnapshotSharedLister().NodeInfos().List() 540 if err != nil { 541 return nil, framework.NewStatus(framework.Error, err.Error()) 542 } 543 544 for _, nodeInfo := range nodeInfos { 545 pp.fh.RunFilterPlugins(ctx, state, pod, nodeInfo) 546 } 547 pp.fh.RunScorePlugins(ctx, state, pod, nodeInfos) 548 549 if pp.failPostFilter { 550 return nil, framework.NewStatus(framework.Error, fmt.Sprintf("injecting failure for pod %v", pod.Name)) 551 } 552 if pp.rejectPostFilter { 553 return nil, framework.NewStatus(framework.Unschedulable, fmt.Sprintf("injecting unschedulable for pod %v", pod.Name)) 554 } 555 if pp.breakPostFilter { 556 return nil, framework.NewStatus(framework.UnschedulableAndUnresolvable, fmt.Sprintf("injecting unresolvable for pod %v", pod.Name)) 557 } 558 559 return nil, framework.NewStatus(framework.Success, fmt.Sprintf("make room for pod %v to be schedulable", pod.Name)) 560 } 561 562 // Name returns name of the plugin. 563 func (pp *PermitPlugin) Name() string { 564 return pp.name 565 } 566 567 // Permit implements the permit test plugin. 568 func (pp *PermitPlugin) Permit(ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodeName string) (*framework.Status, time.Duration) { 569 pp.mutex.Lock() 570 defer pp.mutex.Unlock() 571 572 pp.numPermitCalled++ 573 if pp.failPermit { 574 return framework.NewStatus(framework.Error, fmt.Sprintf("injecting failure for pod %v", pod.Name)), 0 575 } 576 if pp.rejectPermit { 577 return framework.NewStatus(framework.Unschedulable, fmt.Sprintf("reject pod %v", pod.Name)), 0 578 } 579 if pp.timeoutPermit { 580 go func() { 581 select { 582 case <-ctx.Done(): 583 pp.mutex.Lock() 584 defer pp.mutex.Unlock() 585 pp.cancelled = true 586 } 587 }() 588 return framework.NewStatus(framework.Wait, ""), 3 * time.Second 589 } 590 if pp.waitAndRejectPermit || pp.waitAndAllowPermit { 591 if pp.waitingPod == "" || pp.waitingPod == pod.Name { 592 pp.waitingPod = pod.Name 593 return framework.NewStatus(framework.Wait, ""), 30 * time.Second 594 } 595 if pp.waitAndRejectPermit { 596 pp.rejectingPod = pod.Name 597 pp.fh.IterateOverWaitingPods(func(wp framework.WaitingPod) { 598 wp.Reject(pp.name, fmt.Sprintf("reject pod %v", wp.GetPod().Name)) 599 }) 600 return framework.NewStatus(framework.Unschedulable, fmt.Sprintf("reject pod %v", pod.Name)), 0 601 } 602 if pp.waitAndAllowPermit { 603 pp.allowingPod = pod.Name 604 pp.allowAllPods() 605 return nil, 0 606 } 607 } 608 return nil, 0 609 } 610 611 // allowAllPods allows all waiting pods. 612 func (pp *PermitPlugin) allowAllPods() { 613 pp.fh.IterateOverWaitingPods(func(wp framework.WaitingPod) { wp.Allow(pp.name) }) 614 } 615 616 // rejectAllPods rejects all waiting pods. 617 func (pp *PermitPlugin) rejectAllPods() { 618 pp.mutex.Lock() 619 defer pp.mutex.Unlock() 620 pp.fh.IterateOverWaitingPods(func(wp framework.WaitingPod) { wp.Reject(pp.name, "rejectAllPods") }) 621 } 622 623 // TestPreFilterPlugin tests invocation of prefilter plugins. 624 func TestPreFilterPlugin(t *testing.T) { 625 testContext := testutils.InitTestAPIServer(t, "prefilter-plugin", nil) 626 627 tests := []struct { 628 name string 629 fail bool 630 reject bool 631 preFilterResultNodes sets.Set[string] 632 }{ 633 { 634 name: "disable fail and reject flags", 635 fail: false, 636 reject: false, 637 }, 638 { 639 name: "enable fail and disable reject flags", 640 fail: true, 641 reject: false, 642 }, 643 { 644 name: "disable fail and enable reject flags", 645 fail: false, 646 reject: true, 647 }, 648 { 649 name: "inject legal node names in PreFilterResult", 650 fail: false, 651 reject: false, 652 preFilterResultNodes: sets.New[string]("test-node-0", "test-node-1"), 653 }, 654 { 655 name: "inject legal and illegal node names in PreFilterResult", 656 fail: false, 657 reject: false, 658 preFilterResultNodes: sets.New[string]("test-node-0", "non-existent-node"), 659 }, 660 } 661 662 for _, test := range tests { 663 t.Run(test.name, func(t *testing.T) { 664 // Create a plugin registry for testing. Register only a pre-filter plugin. 665 preFilterPlugin := &PreFilterPlugin{} 666 registry, prof := initRegistryAndConfig(t, preFilterPlugin) 667 668 testCtx, teardown := schedulerutils.InitTestSchedulerForFrameworkTest(t, testContext, 2, 669 scheduler.WithProfiles(prof), 670 scheduler.WithFrameworkOutOfTreeRegistry(registry)) 671 defer teardown() 672 673 preFilterPlugin.failPreFilter = test.fail 674 preFilterPlugin.rejectPreFilter = test.reject 675 preFilterPlugin.preFilterResultNodes = test.preFilterResultNodes 676 // Create a best effort pod. 677 pod, err := testutils.CreatePausePod(testCtx.ClientSet, 678 testutils.InitPausePod(&testutils.PausePodConfig{Name: "test-pod", Namespace: testCtx.NS.Name})) 679 if err != nil { 680 t.Errorf("Error while creating a test pod: %v", err) 681 } 682 683 if test.reject { 684 if err = testutils.WaitForPodUnschedulable(testCtx.ClientSet, pod); err != nil { 685 t.Errorf("Didn't expect the pod to be scheduled. error: %v", err) 686 } 687 } else if test.fail { 688 if err = wait.PollUntilContextTimeout(testCtx.Ctx, 10*time.Millisecond, 30*time.Second, false, 689 testutils.PodSchedulingError(testCtx.ClientSet, pod.Namespace, pod.Name)); err != nil { 690 t.Errorf("Expected a scheduling error, but got: %v", err) 691 } 692 } else { 693 if err = testutils.WaitForPodToSchedule(testCtx.ClientSet, pod); err != nil { 694 t.Errorf("Expected the pod to be scheduled. error: %v", err) 695 } 696 } 697 698 if preFilterPlugin.numPreFilterCalled == 0 { 699 t.Errorf("Expected the prefilter plugin to be called.") 700 } 701 }) 702 } 703 } 704 705 // TestPostFilterPlugin tests invocation of postFilter plugins. 706 func TestPostFilterPlugin(t *testing.T) { 707 numNodes := 1 708 tests := []struct { 709 name string 710 numNodes int 711 rejectFilter bool 712 failScore bool 713 rejectPostFilter bool 714 rejectPostFilter2 bool 715 breakPostFilter bool 716 breakPostFilter2 bool 717 expectFilterNumCalled int 718 expectScoreNumCalled int 719 expectPostFilterNumCalled int 720 }{ 721 { 722 name: "Filter passed and Score success", 723 numNodes: 30, 724 rejectFilter: false, 725 failScore: false, 726 rejectPostFilter: false, 727 expectFilterNumCalled: 30, 728 expectScoreNumCalled: 30, 729 expectPostFilterNumCalled: 0, 730 }, 731 { 732 name: "Filter failed and PostFilter passed", 733 numNodes: numNodes, 734 rejectFilter: true, 735 failScore: false, 736 rejectPostFilter: false, 737 expectFilterNumCalled: numNodes * 2, 738 expectScoreNumCalled: 1, 739 expectPostFilterNumCalled: 1, 740 }, 741 { 742 name: "Filter failed and PostFilter failed", 743 numNodes: numNodes, 744 rejectFilter: true, 745 failScore: false, 746 rejectPostFilter: true, 747 expectFilterNumCalled: numNodes * 2, 748 expectScoreNumCalled: 1, 749 expectPostFilterNumCalled: 2, 750 }, 751 { 752 name: "Score failed and PostFilter failed", 753 numNodes: numNodes, 754 rejectFilter: true, 755 failScore: true, 756 rejectPostFilter: true, 757 expectFilterNumCalled: numNodes * 2, 758 expectScoreNumCalled: 1, 759 expectPostFilterNumCalled: 2, 760 }, 761 { 762 name: "Filter failed and first PostFilter broken", 763 numNodes: numNodes, 764 rejectFilter: true, 765 breakPostFilter: true, 766 expectFilterNumCalled: numNodes * 2, 767 expectScoreNumCalled: 0, 768 expectPostFilterNumCalled: 1, 769 }, 770 { 771 name: "Filter failed and second PostFilter broken", 772 numNodes: numNodes, 773 rejectFilter: true, 774 rejectPostFilter: true, 775 rejectPostFilter2: true, 776 breakPostFilter2: true, 777 expectFilterNumCalled: numNodes * 2, 778 expectScoreNumCalled: 0, 779 expectPostFilterNumCalled: 2, 780 }, 781 } 782 783 var postFilterPluginName2 = postfilterPluginName + "2" 784 testContext := testutils.InitTestAPIServer(t, "post-filter", nil) 785 786 for _, tt := range tests { 787 t.Run(tt.name, func(t *testing.T) { 788 // Create a plugin registry for testing. Register a combination of filter and postFilter plugin. 789 var ( 790 filterPlugin = &FilterPlugin{} 791 scorePlugin = &ScorePlugin{} 792 postFilterPlugin = &PostFilterPlugin{name: postfilterPluginName} 793 postFilterPlugin2 = &PostFilterPlugin{name: postFilterPluginName2} 794 ) 795 filterPlugin.rejectFilter = tt.rejectFilter 796 scorePlugin.failScore = tt.failScore 797 postFilterPlugin.rejectPostFilter = tt.rejectPostFilter 798 postFilterPlugin2.rejectPostFilter = tt.rejectPostFilter2 799 postFilterPlugin.breakPostFilter = tt.breakPostFilter 800 postFilterPlugin2.breakPostFilter = tt.breakPostFilter2 801 802 registry := frameworkruntime.Registry{ 803 filterPluginName: newPlugin(filterPlugin), 804 scorePluginName: newPlugin(scorePlugin), 805 postfilterPluginName: newPlugin(postFilterPlugin), 806 postFilterPluginName2: newPlugin(postFilterPlugin2), 807 } 808 809 // Setup plugins for testing. 810 cfg := configtesting.V1ToInternalWithDefaults(t, configv1.KubeSchedulerConfiguration{ 811 Profiles: []configv1.KubeSchedulerProfile{{ 812 SchedulerName: pointer.String(v1.DefaultSchedulerName), 813 Plugins: &configv1.Plugins{ 814 Filter: configv1.PluginSet{ 815 Enabled: []configv1.Plugin{ 816 {Name: filterPluginName}, 817 }, 818 }, 819 PreScore: configv1.PluginSet{ 820 Disabled: []configv1.Plugin{ 821 {Name: "*"}, 822 }, 823 }, 824 Score: configv1.PluginSet{ 825 Enabled: []configv1.Plugin{ 826 {Name: scorePluginName}, 827 }, 828 // disable default in-tree Score plugins 829 // to make it easy to control configured ScorePlugins failure 830 Disabled: []configv1.Plugin{ 831 {Name: "*"}, 832 }, 833 }, 834 PostFilter: configv1.PluginSet{ 835 Enabled: []configv1.Plugin{ 836 {Name: postfilterPluginName}, 837 {Name: postFilterPluginName2}, 838 }, 839 // Need to disable default in-tree PostFilter plugins, as they will 840 // call RunPostFilterPlugins and hence impact the "numPostFilterCalled". 841 Disabled: []configv1.Plugin{ 842 {Name: "*"}, 843 }, 844 }, 845 }, 846 }}}) 847 848 testCtx, teardown := schedulerutils.InitTestSchedulerForFrameworkTest(t, testContext, int(tt.numNodes), 849 scheduler.WithProfiles(cfg.Profiles...), 850 scheduler.WithFrameworkOutOfTreeRegistry(registry), 851 ) 852 defer teardown() 853 854 // Create a best effort pod. 855 pod, err := testutils.CreatePausePod(testCtx.ClientSet, testutils.InitPausePod(&testutils.PausePodConfig{Name: "test-pod", Namespace: testCtx.NS.Name})) 856 if err != nil { 857 t.Errorf("Error while creating a test pod: %v", err) 858 } 859 860 if tt.rejectFilter { 861 if err = wait.PollUntilContextTimeout(testCtx.Ctx, 10*time.Millisecond, 10*time.Second, false, 862 testutils.PodUnschedulable(testCtx.ClientSet, pod.Namespace, pod.Name)); err != nil { 863 t.Errorf("Didn't expect the pod to be scheduled.") 864 } 865 866 if numFilterCalled := filterPlugin.deepCopy().numFilterCalled; numFilterCalled < tt.expectFilterNumCalled { 867 t.Errorf("Expected the filter plugin to be called at least %v times, but got %v.", tt.expectFilterNumCalled, numFilterCalled) 868 } 869 if numScoreCalled := scorePlugin.deepCopy().numScoreCalled; numScoreCalled < tt.expectScoreNumCalled { 870 t.Errorf("Expected the score plugin to be called at least %v times, but got %v.", tt.expectScoreNumCalled, numScoreCalled) 871 } 872 } else { 873 if err = testutils.WaitForPodToSchedule(testCtx.ClientSet, pod); err != nil { 874 t.Errorf("Expected the pod to be scheduled. error: %v", err) 875 } 876 if numFilterCalled := filterPlugin.deepCopy().numFilterCalled; numFilterCalled != tt.expectFilterNumCalled { 877 t.Errorf("Expected the filter plugin to be called %v times, but got %v.", tt.expectFilterNumCalled, numFilterCalled) 878 } 879 if numScoreCalled := scorePlugin.deepCopy().numScoreCalled; numScoreCalled != tt.expectScoreNumCalled { 880 t.Errorf("Expected the score plugin to be called %v times, but got %v.", tt.expectScoreNumCalled, numScoreCalled) 881 } 882 } 883 884 numPostFilterCalled := postFilterPlugin.numPostFilterCalled + postFilterPlugin2.numPostFilterCalled 885 if numPostFilterCalled != tt.expectPostFilterNumCalled { 886 t.Errorf("Expected the postfilter plugin to be called %v times, but got %v.", tt.expectPostFilterNumCalled, numPostFilterCalled) 887 } 888 }) 889 } 890 } 891 892 // TestScorePlugin tests invocation of score plugins. 893 func TestScorePlugin(t *testing.T) { 894 testContext := testutils.InitTestAPIServer(t, "score-plugin", nil) 895 896 tests := []struct { 897 name string 898 fail bool 899 }{ 900 { 901 name: "fail score plugin", 902 fail: true, 903 }, 904 { 905 name: "do not fail score plugin", 906 fail: false, 907 }, 908 } 909 910 for _, test := range tests { 911 t.Run(test.name, func(t *testing.T) { 912 // Create a plugin registry for testing. Register only a score plugin. 913 scorePlugin := &ScorePlugin{} 914 registry, prof := initRegistryAndConfig(t, scorePlugin) 915 916 testCtx, teardown := schedulerutils.InitTestSchedulerForFrameworkTest(t, testContext, 10, 917 scheduler.WithProfiles(prof), 918 scheduler.WithFrameworkOutOfTreeRegistry(registry)) 919 defer teardown() 920 921 scorePlugin.failScore = test.fail 922 // Create a best effort pod. 923 pod, err := testutils.CreatePausePod(testCtx.ClientSet, 924 testutils.InitPausePod(&testutils.PausePodConfig{Name: "test-pod", Namespace: testCtx.NS.Name})) 925 if err != nil { 926 t.Fatalf("Error while creating a test pod: %v", err) 927 } 928 929 if test.fail { 930 if err = wait.PollUntilContextTimeout(testCtx.Ctx, 10*time.Millisecond, 30*time.Second, false, 931 testutils.PodSchedulingError(testCtx.ClientSet, pod.Namespace, pod.Name)); err != nil { 932 t.Errorf("Expected a scheduling error, but got: %v", err) 933 } 934 } else { 935 if err = testutils.WaitForPodToSchedule(testCtx.ClientSet, pod); err != nil { 936 t.Errorf("Expected the pod to be scheduled. error: %v", err) 937 } else { 938 p, err := testutils.GetPod(testCtx.ClientSet, pod.Name, pod.Namespace) 939 if err != nil { 940 t.Errorf("Failed to retrieve the pod. error: %v", err) 941 } else if p.Spec.NodeName != scorePlugin.highScoreNode { 942 t.Errorf("Expected the pod to be scheduled on node %q, got %q", scorePlugin.highScoreNode, p.Spec.NodeName) 943 } 944 } 945 } 946 947 if numScoreCalled := scorePlugin.deepCopy().numScoreCalled; numScoreCalled == 0 { 948 t.Errorf("Expected the score plugin to be called.") 949 } 950 }) 951 } 952 } 953 954 // TestNormalizeScorePlugin tests invocation of normalize score plugins. 955 func TestNormalizeScorePlugin(t *testing.T) { 956 // Create a plugin registry for testing. Register only a normalize score plugin. 957 scoreWithNormalizePlugin := &ScoreWithNormalizePlugin{} 958 registry, prof := initRegistryAndConfig(t, scoreWithNormalizePlugin) 959 960 testCtx, _ := schedulerutils.InitTestSchedulerForFrameworkTest(t, testutils.InitTestAPIServer(t, "score-plugin", nil), 10, 961 scheduler.WithProfiles(prof), 962 scheduler.WithFrameworkOutOfTreeRegistry(registry)) 963 964 // Create a best effort pod. 965 pod, err := testutils.CreatePausePod(testCtx.ClientSet, 966 testutils.InitPausePod(&testutils.PausePodConfig{Name: "test-pod", Namespace: testCtx.NS.Name})) 967 if err != nil { 968 t.Fatalf("Error while creating a test pod: %v", err) 969 } 970 971 if err = testutils.WaitForPodToSchedule(testCtx.ClientSet, pod); err != nil { 972 t.Errorf("Expected the pod to be scheduled. error: %v", err) 973 } 974 975 p := scoreWithNormalizePlugin.deepCopy() 976 if p.numScoreCalled == 0 { 977 t.Errorf("Expected the score plugin to be called.") 978 } 979 if p.numNormalizeScoreCalled == 0 { 980 t.Error("Expected the normalize score plugin to be called") 981 } 982 } 983 984 // TestReservePlugin tests invocation of reserve plugins. 985 func TestReservePluginReserve(t *testing.T) { 986 testContext := testutils.InitTestAPIServer(t, "reserve-plugin-reserve", nil) 987 988 tests := []struct { 989 name string 990 fail bool 991 }{ 992 { 993 name: "fail reserve plugin", 994 fail: true, 995 }, 996 { 997 name: "do not fail reserve plugin", 998 fail: false, 999 }, 1000 } 1001 1002 for _, test := range tests { 1003 t.Run(test.name, func(t *testing.T) { 1004 // Create a plugin registry for testing. Register only a reserve plugin. 1005 reservePlugin := &ReservePlugin{} 1006 registry, prof := initRegistryAndConfig(t, reservePlugin) 1007 1008 testCtx, teardown := schedulerutils.InitTestSchedulerForFrameworkTest(t, testContext, 2, 1009 scheduler.WithProfiles(prof), 1010 scheduler.WithFrameworkOutOfTreeRegistry(registry)) 1011 defer teardown() 1012 1013 reservePlugin.failReserve = test.fail 1014 // Create a best effort pod. 1015 pod, err := testutils.CreatePausePod(testCtx.ClientSet, 1016 testutils.InitPausePod(&testutils.PausePodConfig{Name: "test-pod", Namespace: testCtx.NS.Name})) 1017 if err != nil { 1018 t.Errorf("Error while creating a test pod: %v", err) 1019 } 1020 1021 if test.fail { 1022 if err = wait.PollUntilContextTimeout(testCtx.Ctx, 10*time.Millisecond, 30*time.Second, false, 1023 testutils.PodSchedulingError(testCtx.ClientSet, pod.Namespace, pod.Name)); err != nil { 1024 t.Errorf("Didn't expect the pod to be scheduled. error: %v", err) 1025 } 1026 } else { 1027 if err = testutils.WaitForPodToSchedule(testCtx.ClientSet, pod); err != nil { 1028 t.Errorf("Expected the pod to be scheduled. error: %v", err) 1029 } 1030 } 1031 1032 if reservePlugin.numReserveCalled == 0 { 1033 t.Errorf("Expected the reserve plugin to be called.") 1034 } 1035 }) 1036 } 1037 } 1038 1039 // TestPrebindPlugin tests invocation of prebind plugins. 1040 func TestPrebindPlugin(t *testing.T) { 1041 testContext := testutils.InitTestAPIServer(t, "prebind-plugin", nil) 1042 1043 nodesNum := 2 1044 1045 tests := []struct { 1046 name string 1047 fail bool 1048 reject bool 1049 succeedOnRetry bool 1050 unschedulablePod *v1.Pod 1051 }{ 1052 { 1053 name: "disable fail and reject flags", 1054 fail: false, 1055 reject: false, 1056 }, 1057 { 1058 name: "enable fail and disable reject flags", 1059 fail: true, 1060 reject: false, 1061 }, 1062 { 1063 name: "disable fail and enable reject flags", 1064 fail: false, 1065 reject: true, 1066 }, 1067 { 1068 name: "enable fail and reject flags", 1069 fail: true, 1070 reject: true, 1071 }, 1072 { 1073 name: "fail on 1st try but succeed on retry", 1074 fail: true, 1075 reject: false, 1076 succeedOnRetry: true, 1077 }, 1078 { 1079 name: "failure on preBind moves unschedulable pods", 1080 fail: true, 1081 unschedulablePod: st.MakePod().Name("unschedulable-pod").Namespace(testContext.NS.Name).Container(imageutils.GetPauseImageName()).Obj(), 1082 }, 1083 } 1084 1085 for _, test := range tests { 1086 t.Run(test.name, func(t *testing.T) { 1087 // Create a plugin registry for testing. Register a prebind and a filter plugin. 1088 preBindPlugin := &PreBindPlugin{podUIDs: make(map[types.UID]struct{})} 1089 filterPlugin := &FilterPlugin{} 1090 registry := frameworkruntime.Registry{ 1091 preBindPluginName: newPlugin(preBindPlugin), 1092 filterPluginName: newPlugin(filterPlugin), 1093 } 1094 1095 // Setup initial prebind and filter plugin in different profiles. 1096 // The second profile ensures the embedded filter plugin is exclusively called, and hence 1097 // we can use its internal `numFilterCalled` to perform some precise checking logic. 1098 cfg := configtesting.V1ToInternalWithDefaults(t, configv1.KubeSchedulerConfiguration{ 1099 Profiles: []configv1.KubeSchedulerProfile{ 1100 { 1101 SchedulerName: pointer.String(v1.DefaultSchedulerName), 1102 Plugins: &configv1.Plugins{ 1103 PreBind: configv1.PluginSet{ 1104 Enabled: []configv1.Plugin{ 1105 {Name: preBindPluginName}, 1106 }, 1107 }, 1108 }, 1109 }, 1110 { 1111 SchedulerName: pointer.String("2nd-scheduler"), 1112 Plugins: &configv1.Plugins{ 1113 Filter: configv1.PluginSet{ 1114 Enabled: []configv1.Plugin{ 1115 {Name: filterPluginName}, 1116 }, 1117 }, 1118 }, 1119 }, 1120 }, 1121 }) 1122 1123 testCtx, teardown := schedulerutils.InitTestSchedulerForFrameworkTest(t, testContext, nodesNum, 1124 scheduler.WithProfiles(cfg.Profiles...), 1125 scheduler.WithFrameworkOutOfTreeRegistry(registry)) 1126 defer teardown() 1127 1128 if p := test.unschedulablePod; p != nil { 1129 p.Spec.SchedulerName = "2nd-scheduler" 1130 filterPlugin.rejectFilter = true 1131 if _, err := testutils.CreatePausePod(testCtx.ClientSet, p); err != nil { 1132 t.Fatalf("Error while creating an unschedulable pod: %v", err) 1133 } 1134 } 1135 1136 preBindPlugin.set(test.fail, test.reject, test.succeedOnRetry) 1137 1138 // Create a best effort pod. 1139 pod, err := testutils.CreatePausePod(testCtx.ClientSet, 1140 testutils.InitPausePod(&testutils.PausePodConfig{Name: "test-pod", Namespace: testCtx.NS.Name})) 1141 if err != nil { 1142 t.Errorf("Error while creating a test pod: %v", err) 1143 } 1144 1145 if test.fail { 1146 if test.succeedOnRetry { 1147 if err = testutils.WaitForPodToScheduleWithTimeout(testCtx.ClientSet, pod, 10*time.Second); err != nil { 1148 t.Errorf("Expected the pod to be schedulable on retry, but got an error: %v", err) 1149 } 1150 } else if err = wait.PollUntilContextTimeout(testCtx.Ctx, 10*time.Millisecond, 30*time.Second, false, 1151 testutils.PodSchedulingError(testCtx.ClientSet, pod.Namespace, pod.Name)); err != nil { 1152 t.Errorf("Expected a scheduling error, but didn't get it. error: %v", err) 1153 } 1154 } else if test.reject { 1155 if err = testutils.WaitForPodUnschedulable(testCtx.ClientSet, pod); err != nil { 1156 t.Errorf("Expected the pod to be unschedulable") 1157 } 1158 } else if err = testutils.WaitForPodToSchedule(testCtx.ClientSet, pod); err != nil { 1159 t.Errorf("Expected the pod to be scheduled. error: %v", err) 1160 } 1161 1162 p := preBindPlugin.deepCopy() 1163 if p.numPreBindCalled == 0 { 1164 t.Errorf("Expected the prebind plugin to be called.") 1165 } 1166 1167 if test.unschedulablePod != nil { 1168 if err := wait.PollUntilContextTimeout(testCtx.Ctx, 10*time.Millisecond, 15*time.Second, false, func(ctx context.Context) (bool, error) { 1169 // 2 means the unschedulable pod is expected to be retried at least twice. 1170 // (one initial attempt plus the one moved by the preBind pod) 1171 return filterPlugin.deepCopy().numFilterCalled >= 2*nodesNum, nil 1172 }); err != nil { 1173 t.Errorf("Timed out waiting for the unschedulable Pod to be retried at least twice.") 1174 } 1175 } 1176 }) 1177 } 1178 } 1179 1180 // TestUnReserveReservePlugins tests invocation of the Unreserve operation in 1181 // reserve plugins through failures in execution points such as pre-bind. Also 1182 // tests that the order of invocation of Unreserve operation is executed in the 1183 // reverse order of invocation of the Reserve operation. 1184 func TestUnReserveReservePlugins(t *testing.T) { 1185 tests := []struct { 1186 name string 1187 plugins []*ReservePlugin 1188 fail bool 1189 failPluginIdx int 1190 }{ 1191 { 1192 name: "The first Reserve plugin fails", 1193 failPluginIdx: 0, 1194 plugins: []*ReservePlugin{ 1195 { 1196 name: "failedReservePlugin1", 1197 failReserve: true, 1198 }, 1199 { 1200 name: "succeedReservePlugin", 1201 failReserve: false, 1202 }, 1203 { 1204 name: "failedReservePlugin2", 1205 failReserve: true, 1206 }, 1207 }, 1208 fail: true, 1209 }, 1210 { 1211 name: "The middle Reserve plugin fails", 1212 failPluginIdx: 1, 1213 plugins: []*ReservePlugin{ 1214 { 1215 name: "succeedReservePlugin1", 1216 failReserve: false, 1217 }, 1218 { 1219 name: "failedReservePlugin1", 1220 failReserve: true, 1221 }, 1222 { 1223 name: "succeedReservePlugin2", 1224 failReserve: false, 1225 }, 1226 }, 1227 fail: true, 1228 }, 1229 { 1230 name: "The last Reserve plugin fails", 1231 failPluginIdx: 2, 1232 plugins: []*ReservePlugin{ 1233 { 1234 name: "succeedReservePlugin1", 1235 failReserve: false, 1236 }, 1237 { 1238 name: "succeedReservePlugin2", 1239 failReserve: false, 1240 }, 1241 { 1242 name: "failedReservePlugin1", 1243 failReserve: true, 1244 }, 1245 }, 1246 fail: true, 1247 }, 1248 { 1249 name: "The Reserve plugins succeed", 1250 failPluginIdx: -1, 1251 plugins: []*ReservePlugin{ 1252 { 1253 name: "succeedReservePlugin1", 1254 failReserve: false, 1255 }, 1256 { 1257 name: "succeedReservePlugin2", 1258 failReserve: false, 1259 }, 1260 { 1261 name: "succeedReservePlugin3", 1262 failReserve: false, 1263 }, 1264 }, 1265 fail: false, 1266 }, 1267 } 1268 1269 testContext := testutils.InitTestAPIServer(t, "unreserve-reserve-plugin", nil) 1270 1271 for _, test := range tests { 1272 t.Run(test.name, func(t *testing.T) { 1273 var pls []framework.Plugin 1274 for _, pl := range test.plugins { 1275 pls = append(pls, pl) 1276 } 1277 registry, prof := initRegistryAndConfig(t, pls...) 1278 1279 testCtx, teardown := schedulerutils.InitTestSchedulerForFrameworkTest(t, testContext, 2, 1280 scheduler.WithProfiles(prof), 1281 scheduler.WithFrameworkOutOfTreeRegistry(registry)) 1282 defer teardown() 1283 1284 // Create a best effort pod. 1285 podName := "test-pod" 1286 pod, err := testutils.CreatePausePod(testCtx.ClientSet, 1287 testutils.InitPausePod(&testutils.PausePodConfig{Name: podName, Namespace: testCtx.NS.Name})) 1288 if err != nil { 1289 t.Errorf("Error while creating a test pod: %v", err) 1290 } 1291 1292 if test.fail { 1293 if err = wait.PollUntilContextTimeout(testCtx.Ctx, 10*time.Millisecond, 30*time.Second, false, 1294 testutils.PodSchedulingError(testCtx.ClientSet, pod.Namespace, pod.Name)); err != nil { 1295 t.Errorf("Expected a reasons other than Unschedulable, but got: %v", err) 1296 } 1297 1298 for i, pl := range test.plugins { 1299 if i <= test.failPluginIdx { 1300 if pl.numReserveCalled != 1 { 1301 t.Errorf("Reserve Plugins %s numReserveCalled = %d, want 1.", pl.name, pl.numReserveCalled) 1302 } 1303 } else { 1304 if pl.numReserveCalled != 0 { 1305 t.Errorf("Reserve Plugins %s numReserveCalled = %d, want 0.", pl.name, pl.numReserveCalled) 1306 } 1307 } 1308 1309 if pl.numUnreserveCalled != 1 { 1310 t.Errorf("Reserve Plugin %s numUnreserveCalled = %d, want 1.", pl.name, pl.numUnreserveCalled) 1311 } 1312 } 1313 } else { 1314 if err = testutils.WaitForPodToSchedule(testCtx.ClientSet, pod); err != nil { 1315 t.Errorf("Expected the pod to be scheduled. error: %v", err) 1316 } 1317 1318 for _, pl := range test.plugins { 1319 if pl.numReserveCalled != 1 { 1320 t.Errorf("Reserve Plugin %s numReserveCalled = %d, want 1.", pl.name, pl.numReserveCalled) 1321 } 1322 if pl.numUnreserveCalled != 0 { 1323 t.Errorf("Reserve Plugin %s numUnreserveCalled = %d, want 0.", pl.name, pl.numUnreserveCalled) 1324 } 1325 } 1326 } 1327 }) 1328 } 1329 } 1330 1331 // TestUnReservePermitPlugins tests unreserve of Permit plugins. 1332 func TestUnReservePermitPlugins(t *testing.T) { 1333 testContext := testutils.InitTestAPIServer(t, "unreserve-reserve-plugin", nil) 1334 1335 tests := []struct { 1336 name string 1337 plugin *PermitPlugin 1338 reject bool 1339 }{ 1340 { 1341 name: "All Reserve plugins passed, but a Permit plugin was rejected", 1342 reject: true, 1343 plugin: &PermitPlugin{ 1344 name: "rejectedPermitPlugin", 1345 rejectPermit: true, 1346 }, 1347 }, 1348 { 1349 name: "All Reserve plugins passed, but a Permit plugin timeout in waiting", 1350 reject: true, 1351 plugin: &PermitPlugin{ 1352 name: "timeoutPermitPlugin", 1353 timeoutPermit: true, 1354 }, 1355 }, 1356 { 1357 name: "The Permit plugin succeed", 1358 reject: false, 1359 plugin: &PermitPlugin{ 1360 name: "succeedPermitPlugin", 1361 }, 1362 }, 1363 } 1364 1365 for _, test := range tests { 1366 t.Run(test.name, func(t *testing.T) { 1367 reservePlugin := &ReservePlugin{ 1368 name: "reservePlugin", 1369 failReserve: false, 1370 } 1371 registry, profile := initRegistryAndConfig(t, []framework.Plugin{test.plugin, reservePlugin}...) 1372 1373 testCtx, teardown := schedulerutils.InitTestSchedulerForFrameworkTest(t, testContext, 2, 1374 scheduler.WithProfiles(profile), 1375 scheduler.WithFrameworkOutOfTreeRegistry(registry)) 1376 defer teardown() 1377 1378 // Create a best effort pod. 1379 podName := "test-pod" 1380 pod, err := testutils.CreatePausePod(testCtx.ClientSet, 1381 testutils.InitPausePod(&testutils.PausePodConfig{Name: podName, Namespace: testCtx.NS.Name})) 1382 if err != nil { 1383 t.Errorf("Error while creating a test pod: %v", err) 1384 } 1385 1386 if test.reject { 1387 if err = testutils.WaitForPodUnschedulable(testCtx.ClientSet, pod); err != nil { 1388 t.Errorf("Didn't expect the pod to be scheduled. error: %v", err) 1389 } 1390 1391 // Verify the Reserve Plugins 1392 if reservePlugin.numUnreserveCalled != 1 { 1393 t.Errorf("Reserve Plugin %s numUnreserveCalled = %d, want 1.", reservePlugin.name, reservePlugin.numUnreserveCalled) 1394 } 1395 } else { 1396 if err = testutils.WaitForPodToSchedule(testCtx.ClientSet, pod); err != nil { 1397 t.Errorf("Expected the pod to be scheduled. error: %v", err) 1398 } 1399 1400 // Verify the Reserve Plugins 1401 if reservePlugin.numUnreserveCalled != 0 { 1402 t.Errorf("Reserve Plugin %s numUnreserveCalled = %d, want 0.", reservePlugin.name, reservePlugin.numUnreserveCalled) 1403 } 1404 } 1405 1406 if test.plugin.numPermitCalled != 1 { 1407 t.Errorf("Expected the Permit plugin to be called.") 1408 } 1409 }) 1410 } 1411 } 1412 1413 // TestUnReservePreBindPlugins tests unreserve of Prebind plugins. 1414 func TestUnReservePreBindPlugins(t *testing.T) { 1415 testContext := testutils.InitTestAPIServer(t, "unreserve-prebind-plugin", nil) 1416 1417 tests := []struct { 1418 name string 1419 plugin *PreBindPlugin 1420 wantReject bool 1421 }{ 1422 { 1423 name: "All Reserve plugins passed, but a PreBind plugin failed", 1424 wantReject: true, 1425 plugin: &PreBindPlugin{ 1426 podUIDs: make(map[types.UID]struct{}), 1427 rejectPreBind: true, 1428 }, 1429 }, 1430 { 1431 name: "All Reserve plugins passed, and PreBind plugin succeed", 1432 wantReject: false, 1433 plugin: &PreBindPlugin{podUIDs: make(map[types.UID]struct{})}, 1434 }, 1435 } 1436 1437 for _, test := range tests { 1438 t.Run(test.name, func(t *testing.T) { 1439 reservePlugin := &ReservePlugin{ 1440 name: "reservePlugin", 1441 failReserve: false, 1442 } 1443 registry, profile := initRegistryAndConfig(t, []framework.Plugin{test.plugin, reservePlugin}...) 1444 1445 testCtx, teardown := schedulerutils.InitTestSchedulerForFrameworkTest(t, testContext, 2, 1446 scheduler.WithProfiles(profile), 1447 scheduler.WithFrameworkOutOfTreeRegistry(registry)) 1448 defer teardown() 1449 1450 // Create a pause pod. 1451 podName := "test-pod" 1452 pod, err := testutils.CreatePausePod(testCtx.ClientSet, 1453 testutils.InitPausePod(&testutils.PausePodConfig{Name: podName, Namespace: testCtx.NS.Name})) 1454 if err != nil { 1455 t.Errorf("Error while creating a test pod: %v", err) 1456 } 1457 1458 if test.wantReject { 1459 if err = testutils.WaitForPodUnschedulable(testCtx.ClientSet, pod); err != nil { 1460 t.Errorf("Expected a reasons other than Unschedulable, but got: %v", err) 1461 } 1462 1463 // Verify the Reserve Plugins 1464 if reservePlugin.numUnreserveCalled != 1 { 1465 t.Errorf("Reserve Plugin %s numUnreserveCalled = %d, want 1.", reservePlugin.name, reservePlugin.numUnreserveCalled) 1466 } 1467 } else { 1468 if err = testutils.WaitForPodToSchedule(testCtx.ClientSet, pod); err != nil { 1469 t.Errorf("Expected the pod to be scheduled. error: %v", err) 1470 } 1471 1472 // Verify the Reserve Plugins 1473 if reservePlugin.numUnreserveCalled != 0 { 1474 t.Errorf("Reserve Plugin %s numUnreserveCalled = %d, want 0.", reservePlugin.name, reservePlugin.numUnreserveCalled) 1475 } 1476 } 1477 1478 if test.plugin.numPreBindCalled != 1 { 1479 t.Errorf("Expected the Prebind plugin to be called.") 1480 } 1481 }) 1482 } 1483 } 1484 1485 // TestUnReserveBindPlugins tests unreserve of Bind plugins. 1486 func TestUnReserveBindPlugins(t *testing.T) { 1487 testContext := testutils.InitTestAPIServer(t, "unreserve-bind-plugin", nil) 1488 1489 tests := []struct { 1490 name string 1491 plugin *BindPlugin 1492 fail bool 1493 }{ 1494 { 1495 name: "All Reserve plugins passed, and Bind plugin succeed", 1496 fail: false, 1497 plugin: &BindPlugin{name: "SucceedBindPlugin"}, 1498 }, 1499 { 1500 name: "All Reserve plugins passed, but a Bind plugin failed", 1501 fail: false, 1502 plugin: &BindPlugin{name: "FailedBindPlugin"}, 1503 }, 1504 } 1505 1506 for _, test := range tests { 1507 t.Run(test.name, func(t *testing.T) { 1508 reservePlugin := &ReservePlugin{ 1509 name: "reservePlugin", 1510 failReserve: false, 1511 } 1512 registry, profile := initRegistryAndConfig(t, []framework.Plugin{test.plugin, reservePlugin}...) 1513 1514 test.plugin.client = testContext.ClientSet 1515 1516 testCtx, teardown := schedulerutils.InitTestSchedulerForFrameworkTest(t, testContext, 2, 1517 scheduler.WithProfiles(profile), 1518 scheduler.WithFrameworkOutOfTreeRegistry(registry)) 1519 defer teardown() 1520 1521 // Create a pause pod. 1522 podName := "test-pod" 1523 pod, err := testutils.CreatePausePod(testCtx.ClientSet, 1524 testutils.InitPausePod(&testutils.PausePodConfig{Name: podName, Namespace: testCtx.NS.Name})) 1525 if err != nil { 1526 t.Errorf("Error while creating a test pod: %v", err) 1527 } 1528 1529 if test.fail { 1530 if err = wait.PollUntilContextTimeout(testCtx.Ctx, 10*time.Millisecond, 30*time.Second, false, 1531 testutils.PodSchedulingError(testCtx.ClientSet, pod.Namespace, pod.Name)); err != nil { 1532 t.Errorf("Expected a reasons other than Unschedulable, but got: %v", err) 1533 } 1534 1535 // Verify the Reserve Plugins 1536 if reservePlugin.numUnreserveCalled != 1 { 1537 t.Errorf("Reserve Plugin %s numUnreserveCalled = %d, want 1.", reservePlugin.name, reservePlugin.numUnreserveCalled) 1538 } 1539 } else { 1540 if err = testutils.WaitForPodToSchedule(testCtx.ClientSet, pod); err != nil { 1541 t.Errorf("Expected the pod to be scheduled. error: %v", err) 1542 } 1543 1544 // Verify the Reserve Plugins 1545 if reservePlugin.numUnreserveCalled != 0 { 1546 t.Errorf("Reserve Plugin %s numUnreserveCalled = %d, want 0.", reservePlugin.name, reservePlugin.numUnreserveCalled) 1547 } 1548 } 1549 1550 if test.plugin.numBindCalled != 1 { 1551 t.Errorf("Expected the Bind plugin to be called.") 1552 } 1553 }) 1554 } 1555 } 1556 1557 type pluginInvokeEvent struct { 1558 pluginName string 1559 val int 1560 } 1561 1562 func TestBindPlugin(t *testing.T) { 1563 1564 var ( 1565 bindPlugin1Name = "bind-plugin-1" 1566 bindPlugin2Name = "bind-plugin-2" 1567 reservePluginName = "mock-reserve-plugin" 1568 postBindPluginName = "mock-post-bind-plugin" 1569 ) 1570 1571 testContext := testutils.InitTestAPIServer(t, "bind-plugin", nil) 1572 1573 tests := []struct { 1574 name string 1575 enabledBindPlugins []configv1.Plugin 1576 bindPluginStatuses []*framework.Status 1577 expectBoundByScheduler bool // true means this test case expecting scheduler would bind pods 1578 expectBoundByPlugin bool // true means this test case expecting a plugin would bind pods 1579 expectBindFailed bool // true means this test case expecting a plugin binding pods with error 1580 expectBindPluginName string // expecting plugin name to bind pods 1581 expectInvokeEvents []pluginInvokeEvent 1582 }{ 1583 { 1584 name: "bind plugins skipped to bind the pod and scheduler bond the pod", 1585 enabledBindPlugins: []configv1.Plugin{{Name: bindPlugin1Name}, {Name: bindPlugin2Name}, {Name: defaultbinder.Name}}, 1586 bindPluginStatuses: []*framework.Status{framework.NewStatus(framework.Skip, ""), framework.NewStatus(framework.Skip, "")}, 1587 expectBoundByScheduler: true, 1588 expectInvokeEvents: []pluginInvokeEvent{{pluginName: bindPlugin1Name, val: 1}, {pluginName: bindPlugin2Name, val: 1}, {pluginName: postBindPluginName, val: 1}}, 1589 }, 1590 { 1591 name: "bindplugin2 succeeded to bind the pod", 1592 enabledBindPlugins: []configv1.Plugin{{Name: bindPlugin1Name}, {Name: bindPlugin2Name}, {Name: defaultbinder.Name}}, 1593 bindPluginStatuses: []*framework.Status{framework.NewStatus(framework.Skip, ""), framework.NewStatus(framework.Success, "")}, 1594 expectBoundByPlugin: true, 1595 expectBindPluginName: bindPlugin2Name, 1596 expectInvokeEvents: []pluginInvokeEvent{{pluginName: bindPlugin1Name, val: 1}, {pluginName: bindPlugin2Name, val: 1}, {pluginName: postBindPluginName, val: 1}}, 1597 }, 1598 { 1599 name: "bindplugin1 succeeded to bind the pod", 1600 enabledBindPlugins: []configv1.Plugin{{Name: bindPlugin1Name}, {Name: bindPlugin2Name}, {Name: defaultbinder.Name}}, 1601 bindPluginStatuses: []*framework.Status{framework.NewStatus(framework.Success, ""), framework.NewStatus(framework.Success, "")}, 1602 expectBoundByPlugin: true, 1603 expectBindPluginName: bindPlugin1Name, 1604 expectInvokeEvents: []pluginInvokeEvent{{pluginName: bindPlugin1Name, val: 1}, {pluginName: postBindPluginName, val: 1}}, 1605 }, 1606 { 1607 name: "bind plugin fails to bind the pod", 1608 enabledBindPlugins: []configv1.Plugin{{Name: bindPlugin1Name}, {Name: bindPlugin2Name}, {Name: defaultbinder.Name}}, 1609 expectBindFailed: true, 1610 bindPluginStatuses: []*framework.Status{framework.NewStatus(framework.Error, "failed to bind"), framework.NewStatus(framework.Success, "")}, 1611 expectInvokeEvents: []pluginInvokeEvent{{pluginName: bindPlugin1Name, val: 1}, {pluginName: reservePluginName, val: 1}}, 1612 }, 1613 { 1614 name: "all bind plugins will be skipped(this should not happen for most of the cases)", 1615 enabledBindPlugins: []configv1.Plugin{{Name: bindPlugin1Name}, {Name: bindPlugin2Name}}, 1616 bindPluginStatuses: []*framework.Status{framework.NewStatus(framework.Skip, ""), framework.NewStatus(framework.Skip, "")}, 1617 expectInvokeEvents: []pluginInvokeEvent{{pluginName: bindPlugin1Name, val: 1}, {pluginName: bindPlugin2Name, val: 1}}, 1618 }, 1619 } 1620 1621 var pluginInvokeEventChan chan pluginInvokeEvent 1622 for _, test := range tests { 1623 t.Run(test.name, func(t *testing.T) { 1624 bindPlugin1 := &BindPlugin{name: bindPlugin1Name, client: testContext.ClientSet} 1625 bindPlugin2 := &BindPlugin{name: bindPlugin2Name, client: testContext.ClientSet} 1626 reservePlugin := &ReservePlugin{name: reservePluginName} 1627 postBindPlugin := &PostBindPlugin{name: postBindPluginName} 1628 1629 // Create a plugin registry for testing. Register reserve, bind, and 1630 // postBind plugins. 1631 registry := frameworkruntime.Registry{ 1632 reservePlugin.Name(): newPlugin(reservePlugin), 1633 bindPlugin1.Name(): newPlugin(bindPlugin1), 1634 bindPlugin2.Name(): newPlugin(bindPlugin2), 1635 postBindPlugin.Name(): newPlugin(postBindPlugin), 1636 } 1637 1638 // Setup initial unreserve and bind plugins for testing. 1639 cfg := configtesting.V1ToInternalWithDefaults(t, configv1.KubeSchedulerConfiguration{ 1640 Profiles: []configv1.KubeSchedulerProfile{{ 1641 SchedulerName: pointer.String(v1.DefaultSchedulerName), 1642 Plugins: &configv1.Plugins{ 1643 MultiPoint: configv1.PluginSet{ 1644 Disabled: []configv1.Plugin{ 1645 {Name: defaultbinder.Name}, 1646 }, 1647 }, 1648 Reserve: configv1.PluginSet{ 1649 Enabled: []configv1.Plugin{{Name: reservePlugin.Name()}}, 1650 }, 1651 Bind: configv1.PluginSet{ 1652 // Put DefaultBinder last. 1653 Enabled: test.enabledBindPlugins, 1654 Disabled: []configv1.Plugin{{Name: defaultbinder.Name}}, 1655 }, 1656 PostBind: configv1.PluginSet{ 1657 Enabled: []configv1.Plugin{{Name: postBindPlugin.Name()}}, 1658 }, 1659 }, 1660 }}, 1661 }) 1662 1663 testCtx, teardown := schedulerutils.InitTestSchedulerForFrameworkTest(t, testContext, 2, 1664 scheduler.WithProfiles(cfg.Profiles...), 1665 scheduler.WithFrameworkOutOfTreeRegistry(registry), 1666 ) 1667 defer teardown() 1668 1669 pluginInvokeEventChan = make(chan pluginInvokeEvent, 10) 1670 1671 bindPlugin1.bindStatus = test.bindPluginStatuses[0] 1672 bindPlugin2.bindStatus = test.bindPluginStatuses[1] 1673 1674 bindPlugin1.pluginInvokeEventChan = pluginInvokeEventChan 1675 bindPlugin2.pluginInvokeEventChan = pluginInvokeEventChan 1676 reservePlugin.pluginInvokeEventChan = pluginInvokeEventChan 1677 postBindPlugin.pluginInvokeEventChan = pluginInvokeEventChan 1678 1679 // Create a best effort pod. 1680 pod, err := testutils.CreatePausePod(testCtx.ClientSet, 1681 testutils.InitPausePod(&testutils.PausePodConfig{Name: "test-pod", Namespace: testCtx.NS.Name})) 1682 if err != nil { 1683 t.Errorf("Error while creating a test pod: %v", err) 1684 } 1685 1686 if test.expectBoundByScheduler || test.expectBoundByPlugin { 1687 // bind plugins skipped to bind the pod 1688 if err = testutils.WaitForPodToSchedule(testCtx.ClientSet, pod); err != nil { 1689 t.Fatalf("Expected the pod to be scheduled. error: %v", err) 1690 } 1691 pod, err = testCtx.ClientSet.CoreV1().Pods(pod.Namespace).Get(context.TODO(), pod.Name, metav1.GetOptions{}) 1692 if err != nil { 1693 t.Errorf("can't get pod: %v", err) 1694 } 1695 p1 := bindPlugin1.deepCopy() 1696 p2 := bindPlugin2.deepCopy() 1697 if test.expectBoundByScheduler { 1698 if pod.Annotations[bindPluginAnnotation] != "" { 1699 t.Errorf("Expected the pod to be bound by scheduler instead of by bindplugin %s", pod.Annotations[bindPluginAnnotation]) 1700 } 1701 if p1.numBindCalled != 1 || p2.numBindCalled != 1 { 1702 t.Errorf("Expected each bind plugin to be called once, was called %d and %d times.", p1.numBindCalled, p2.numBindCalled) 1703 } 1704 } else { 1705 if pod.Annotations[bindPluginAnnotation] != test.expectBindPluginName { 1706 t.Errorf("Expected the pod to be bound by bindplugin %s instead of by bindplugin %s", test.expectBindPluginName, pod.Annotations[bindPluginAnnotation]) 1707 } 1708 if p1.numBindCalled != 1 { 1709 t.Errorf("Expected %s to be called once, was called %d times.", p1.Name(), p1.numBindCalled) 1710 } 1711 if test.expectBindPluginName == p1.Name() && p2.numBindCalled > 0 { 1712 // expect bindplugin1 succeeded to bind the pod and bindplugin2 should not be called. 1713 t.Errorf("Expected %s not to be called, was called %d times.", p2.Name(), p2.numBindCalled) 1714 } 1715 } 1716 if err = wait.PollUntilContextTimeout(testCtx.Ctx, 10*time.Millisecond, 30*time.Second, false, func(ctx context.Context) (done bool, err error) { 1717 p := postBindPlugin.deepCopy() 1718 return p.numPostBindCalled == 1, nil 1719 }); err != nil { 1720 t.Errorf("Expected the postbind plugin to be called once, was called %d times.", postBindPlugin.numPostBindCalled) 1721 } 1722 if reservePlugin.numUnreserveCalled != 0 { 1723 t.Errorf("Expected unreserve to not be called, was called %d times.", reservePlugin.numUnreserveCalled) 1724 } 1725 } else if test.expectBindFailed { 1726 // bind plugin fails to bind the pod 1727 if err = wait.PollUntilContextTimeout(testCtx.Ctx, 10*time.Millisecond, 30*time.Second, false, 1728 testutils.PodSchedulingError(testCtx.ClientSet, pod.Namespace, pod.Name)); err != nil { 1729 t.Errorf("Expected a scheduling error, but didn't get it. error: %v", err) 1730 } 1731 p := postBindPlugin.deepCopy() 1732 if p.numPostBindCalled > 0 { 1733 t.Errorf("Didn't expect the postbind plugin to be called %d times.", p.numPostBindCalled) 1734 } 1735 } else if postBindPlugin.numPostBindCalled > 0 { 1736 // all bind plugins are skipped 1737 t.Errorf("Didn't expect the postbind plugin to be called %d times.", postBindPlugin.numPostBindCalled) 1738 } 1739 1740 for j := range test.expectInvokeEvents { 1741 expectEvent := test.expectInvokeEvents[j] 1742 select { 1743 case event := <-pluginInvokeEventChan: 1744 if event.pluginName != expectEvent.pluginName { 1745 t.Errorf("Expect invoke event %d from plugin %s instead of %s", j, expectEvent.pluginName, event.pluginName) 1746 } 1747 if event.val != expectEvent.val { 1748 t.Errorf("Expect val of invoke event %d to be %d instead of %d", j, expectEvent.val, event.val) 1749 } 1750 case <-time.After(time.Second * 30): 1751 t.Errorf("Waiting for invoke event %d timeout.", j) 1752 } 1753 } 1754 }) 1755 } 1756 } 1757 1758 // TestPostBindPlugin tests invocation of postbind plugins. 1759 func TestPostBindPlugin(t *testing.T) { 1760 testContext := testutils.InitTestAPIServer(t, "postbind-plugin", nil) 1761 1762 tests := []struct { 1763 name string 1764 preBindFail bool 1765 }{ 1766 { 1767 name: "plugin preBind fail", 1768 preBindFail: true, 1769 }, 1770 { 1771 name: "plugin preBind do not fail", 1772 preBindFail: false, 1773 }, 1774 } 1775 1776 for _, test := range tests { 1777 t.Run(test.name, func(t *testing.T) { 1778 // Create a plugin registry for testing. Register a prebind and a postbind plugin. 1779 preBindPlugin := &PreBindPlugin{ 1780 failPreBind: test.preBindFail, 1781 podUIDs: make(map[types.UID]struct{}), 1782 } 1783 postBindPlugin := &PostBindPlugin{ 1784 name: postBindPluginName, 1785 pluginInvokeEventChan: make(chan pluginInvokeEvent, 1), 1786 } 1787 1788 registry, prof := initRegistryAndConfig(t, preBindPlugin, postBindPlugin) 1789 testCtx, teardown := schedulerutils.InitTestSchedulerForFrameworkTest(t, testContext, 2, 1790 scheduler.WithProfiles(prof), 1791 scheduler.WithFrameworkOutOfTreeRegistry(registry)) 1792 defer teardown() 1793 1794 // Create a best effort pod. 1795 pod, err := testutils.CreatePausePod(testCtx.ClientSet, 1796 testutils.InitPausePod(&testutils.PausePodConfig{Name: "test-pod", Namespace: testCtx.NS.Name})) 1797 if err != nil { 1798 t.Errorf("Error while creating a test pod: %v", err) 1799 } 1800 1801 if test.preBindFail { 1802 if err = wait.PollUntilContextTimeout(testCtx.Ctx, 10*time.Millisecond, 30*time.Second, false, 1803 testutils.PodSchedulingError(testCtx.ClientSet, pod.Namespace, pod.Name)); err != nil { 1804 t.Errorf("Expected a scheduling error, but didn't get it. error: %v", err) 1805 } 1806 if postBindPlugin.numPostBindCalled > 0 { 1807 t.Errorf("Didn't expect the postbind plugin to be called %d times.", postBindPlugin.numPostBindCalled) 1808 } 1809 } else { 1810 if err = testutils.WaitForPodToSchedule(testCtx.ClientSet, pod); err != nil { 1811 t.Errorf("Expected the pod to be scheduled. error: %v", err) 1812 } 1813 select { 1814 case <-postBindPlugin.pluginInvokeEventChan: 1815 case <-time.After(time.Second * 15): 1816 t.Errorf("pluginInvokeEventChan timed out") 1817 } 1818 if postBindPlugin.numPostBindCalled == 0 { 1819 t.Errorf("Expected the postbind plugin to be called, was called %d times.", postBindPlugin.numPostBindCalled) 1820 } 1821 } 1822 }) 1823 } 1824 } 1825 1826 // TestPermitPlugin tests invocation of permit plugins. 1827 func TestPermitPlugin(t *testing.T) { 1828 testContext := testutils.InitTestAPIServer(t, "permit-plugin", nil) 1829 1830 tests := []struct { 1831 name string 1832 fail bool 1833 reject bool 1834 timeout bool 1835 }{ 1836 { 1837 name: "disable fail, reject and timeout flags", 1838 fail: false, 1839 reject: false, 1840 timeout: false, 1841 }, 1842 { 1843 name: "enable fail, disable reject and timeout flags", 1844 fail: true, 1845 reject: false, 1846 timeout: false, 1847 }, 1848 { 1849 name: "disable fail and timeout, enable reject flags", 1850 fail: false, 1851 reject: true, 1852 timeout: false, 1853 }, 1854 { 1855 name: "enable fail and reject, disable timeout flags", 1856 fail: true, 1857 reject: true, 1858 timeout: false, 1859 }, 1860 { 1861 name: "disable fail and reject, disable timeout flags", 1862 fail: false, 1863 reject: false, 1864 timeout: true, 1865 }, 1866 { 1867 name: "disable fail and reject, enable timeout flags", 1868 fail: false, 1869 reject: false, 1870 timeout: true, 1871 }, 1872 } 1873 1874 for _, test := range tests { 1875 t.Run(test.name, func(t *testing.T) { 1876 1877 // Create a plugin registry for testing. Register only a permit plugin. 1878 perPlugin := &PermitPlugin{name: permitPluginName} 1879 registry, prof := initRegistryAndConfig(t, perPlugin) 1880 1881 testCtx, teardown := schedulerutils.InitTestSchedulerForFrameworkTest(t, testContext, 2, 1882 scheduler.WithProfiles(prof), 1883 scheduler.WithFrameworkOutOfTreeRegistry(registry)) 1884 defer teardown() 1885 1886 perPlugin.failPermit = test.fail 1887 perPlugin.rejectPermit = test.reject 1888 perPlugin.timeoutPermit = test.timeout 1889 perPlugin.waitAndRejectPermit = false 1890 perPlugin.waitAndAllowPermit = false 1891 1892 // Create a best effort pod. 1893 pod, err := testutils.CreatePausePod(testCtx.ClientSet, 1894 testutils.InitPausePod(&testutils.PausePodConfig{Name: "test-pod", Namespace: testCtx.NS.Name})) 1895 if err != nil { 1896 t.Errorf("Error while creating a test pod: %v", err) 1897 } 1898 if test.fail { 1899 if err = wait.PollUntilContextTimeout(testCtx.Ctx, 10*time.Millisecond, 30*time.Second, false, 1900 testutils.PodSchedulingError(testCtx.ClientSet, pod.Namespace, pod.Name)); err != nil { 1901 t.Errorf("Expected a scheduling error, but didn't get it. error: %v", err) 1902 } 1903 } else { 1904 if test.reject || test.timeout { 1905 if err = testutils.WaitForPodUnschedulable(testCtx.ClientSet, pod); err != nil { 1906 t.Errorf("Didn't expect the pod to be scheduled. error: %v", err) 1907 } 1908 } else { 1909 if err = testutils.WaitForPodToSchedule(testCtx.ClientSet, pod); err != nil { 1910 t.Errorf("Expected the pod to be scheduled. error: %v", err) 1911 } 1912 } 1913 } 1914 1915 p := perPlugin.deepCopy() 1916 if p.numPermitCalled == 0 { 1917 t.Errorf("Expected the permit plugin to be called.") 1918 } 1919 }) 1920 } 1921 } 1922 1923 // TestMultiplePermitPlugins tests multiple permit plugins returning wait for a same pod. 1924 func TestMultiplePermitPlugins(t *testing.T) { 1925 // Create a plugin registry for testing. 1926 perPlugin1 := &PermitPlugin{name: "permit-plugin-1"} 1927 perPlugin2 := &PermitPlugin{name: "permit-plugin-2"} 1928 registry, prof := initRegistryAndConfig(t, perPlugin1, perPlugin2) 1929 1930 // Create the API server and the scheduler with the test plugin set. 1931 testCtx, _ := schedulerutils.InitTestSchedulerForFrameworkTest(t, testutils.InitTestAPIServer(t, "multi-permit-plugin", nil), 2, 1932 scheduler.WithProfiles(prof), 1933 scheduler.WithFrameworkOutOfTreeRegistry(registry)) 1934 1935 // Both permit plugins will return Wait for permitting 1936 perPlugin1.timeoutPermit = true 1937 perPlugin2.timeoutPermit = true 1938 1939 // Create a test pod. 1940 podName := "test-pod" 1941 pod, err := testutils.CreatePausePod(testCtx.ClientSet, 1942 testutils.InitPausePod(&testutils.PausePodConfig{Name: podName, Namespace: testCtx.NS.Name})) 1943 if err != nil { 1944 t.Errorf("Error while creating a test pod: %v", err) 1945 } 1946 1947 var waitingPod framework.WaitingPod 1948 // Wait until the test pod is actually waiting. 1949 wait.PollUntilContextTimeout(testCtx.Ctx, 10*time.Millisecond, 30*time.Second, false, func(ctx context.Context) (bool, error) { 1950 waitingPod = perPlugin1.fh.GetWaitingPod(pod.UID) 1951 return waitingPod != nil, nil 1952 }) 1953 1954 // Check the number of pending permits 1955 if l := len(waitingPod.GetPendingPlugins()); l != 2 { 1956 t.Errorf("Expected the number of pending plugins is 2, but got %d", l) 1957 } 1958 1959 perPlugin1.allowAllPods() 1960 // Check the number of pending permits 1961 if l := len(waitingPod.GetPendingPlugins()); l != 1 { 1962 t.Errorf("Expected the number of pending plugins is 1, but got %d", l) 1963 } 1964 1965 perPlugin2.allowAllPods() 1966 if err = testutils.WaitForPodToSchedule(testCtx.ClientSet, pod); err != nil { 1967 t.Errorf("Expected the pod to be scheduled. error: %v", err) 1968 } 1969 1970 if perPlugin1.numPermitCalled == 0 || perPlugin2.numPermitCalled == 0 { 1971 t.Errorf("Expected the permit plugin to be called.") 1972 } 1973 } 1974 1975 // TestPermitPluginsCancelled tests whether all permit plugins are cancelled when pod is rejected. 1976 func TestPermitPluginsCancelled(t *testing.T) { 1977 // Create a plugin registry for testing. 1978 perPlugin1 := &PermitPlugin{name: "permit-plugin-1"} 1979 perPlugin2 := &PermitPlugin{name: "permit-plugin-2"} 1980 registry, prof := initRegistryAndConfig(t, perPlugin1, perPlugin2) 1981 1982 // Create the API server and the scheduler with the test plugin set. 1983 testCtx, _ := schedulerutils.InitTestSchedulerForFrameworkTest(t, testutils.InitTestAPIServer(t, "permit-plugins", nil), 2, 1984 scheduler.WithProfiles(prof), 1985 scheduler.WithFrameworkOutOfTreeRegistry(registry)) 1986 1987 // Both permit plugins will return Wait for permitting 1988 perPlugin1.timeoutPermit = true 1989 perPlugin2.timeoutPermit = true 1990 1991 // Create a test pod. 1992 podName := "test-pod" 1993 pod, err := testutils.CreatePausePod(testCtx.ClientSet, 1994 testutils.InitPausePod(&testutils.PausePodConfig{Name: podName, Namespace: testCtx.NS.Name})) 1995 if err != nil { 1996 t.Errorf("Error while creating a test pod: %v", err) 1997 } 1998 1999 var waitingPod framework.WaitingPod 2000 // Wait until the test pod is actually waiting. 2001 wait.PollUntilContextTimeout(testCtx.Ctx, 10*time.Millisecond, 30*time.Second, false, func(ctx context.Context) (bool, error) { 2002 waitingPod = perPlugin1.fh.GetWaitingPod(pod.UID) 2003 return waitingPod != nil, nil 2004 }) 2005 2006 perPlugin1.rejectAllPods() 2007 // Wait some time for the permit plugins to be cancelled 2008 err = wait.PollUntilContextTimeout(testCtx.Ctx, 10*time.Millisecond, 30*time.Second, false, func(ctx context.Context) (bool, error) { 2009 p1 := perPlugin1.deepCopy() 2010 p2 := perPlugin2.deepCopy() 2011 return p1.cancelled && p2.cancelled, nil 2012 }) 2013 if err != nil { 2014 t.Errorf("Expected all permit plugins to be cancelled") 2015 } 2016 } 2017 2018 // TestCoSchedulingWithPermitPlugin tests invocation of permit plugins. 2019 func TestCoSchedulingWithPermitPlugin(t *testing.T) { 2020 testContext := testutils.InitTestAPIServer(t, "permit-plugin", nil) 2021 2022 tests := []struct { 2023 name string 2024 waitReject bool 2025 waitAllow bool 2026 }{ 2027 { 2028 name: "having wait reject true and wait allow false", 2029 waitReject: true, 2030 waitAllow: false, 2031 }, 2032 { 2033 name: "having wait reject false and wait allow true", 2034 waitReject: false, 2035 waitAllow: true, 2036 }, 2037 } 2038 2039 for _, test := range tests { 2040 t.Run(test.name, func(t *testing.T) { 2041 2042 // Create a plugin registry for testing. Register only a permit plugin. 2043 permitPlugin := &PermitPlugin{name: permitPluginName} 2044 registry, prof := initRegistryAndConfig(t, permitPlugin) 2045 2046 testCtx, teardown := schedulerutils.InitTestSchedulerForFrameworkTest(t, testContext, 2, 2047 scheduler.WithProfiles(prof), 2048 scheduler.WithFrameworkOutOfTreeRegistry(registry)) 2049 defer teardown() 2050 2051 permitPlugin.failPermit = false 2052 permitPlugin.rejectPermit = false 2053 permitPlugin.timeoutPermit = false 2054 permitPlugin.waitAndRejectPermit = test.waitReject 2055 permitPlugin.waitAndAllowPermit = test.waitAllow 2056 2057 // Create two pods. First pod to enter Permit() will wait and a second one will either 2058 // reject or allow first one. 2059 podA, err := testutils.CreatePausePod(testCtx.ClientSet, 2060 testutils.InitPausePod(&testutils.PausePodConfig{Name: "pod-a", Namespace: testCtx.NS.Name})) 2061 if err != nil { 2062 t.Errorf("Error while creating the first pod: %v", err) 2063 } 2064 podB, err := testutils.CreatePausePod(testCtx.ClientSet, 2065 testutils.InitPausePod(&testutils.PausePodConfig{Name: "pod-b", Namespace: testCtx.NS.Name})) 2066 if err != nil { 2067 t.Errorf("Error while creating the second pod: %v", err) 2068 } 2069 2070 if test.waitReject { 2071 if err = testutils.WaitForPodUnschedulable(testCtx.ClientSet, podA); err != nil { 2072 t.Errorf("Didn't expect the first pod to be scheduled. error: %v", err) 2073 } 2074 if err = testutils.WaitForPodUnschedulable(testCtx.ClientSet, podB); err != nil { 2075 t.Errorf("Didn't expect the second pod to be scheduled. error: %v", err) 2076 } 2077 if !((permitPlugin.waitingPod == podA.Name && permitPlugin.rejectingPod == podB.Name) || 2078 (permitPlugin.waitingPod == podB.Name && permitPlugin.rejectingPod == podA.Name)) { 2079 t.Errorf("Expect one pod to wait and another pod to reject instead %s waited and %s rejected.", 2080 permitPlugin.waitingPod, permitPlugin.rejectingPod) 2081 } 2082 } else { 2083 if err = testutils.WaitForPodToSchedule(testCtx.ClientSet, podA); err != nil { 2084 t.Errorf("Expected the first pod to be scheduled. error: %v", err) 2085 } 2086 if err = testutils.WaitForPodToSchedule(testCtx.ClientSet, podB); err != nil { 2087 t.Errorf("Expected the second pod to be scheduled. error: %v", err) 2088 } 2089 if !((permitPlugin.waitingPod == podA.Name && permitPlugin.allowingPod == podB.Name) || 2090 (permitPlugin.waitingPod == podB.Name && permitPlugin.allowingPod == podA.Name)) { 2091 t.Errorf("Expect one pod to wait and another pod to allow instead %s waited and %s allowed.", 2092 permitPlugin.waitingPod, permitPlugin.allowingPod) 2093 } 2094 } 2095 2096 p := permitPlugin.deepCopy() 2097 if p.numPermitCalled == 0 { 2098 t.Errorf("Expected the permit plugin to be called.") 2099 } 2100 }) 2101 } 2102 } 2103 2104 // TestFilterPlugin tests invocation of filter plugins. 2105 func TestFilterPlugin(t *testing.T) { 2106 testContext := testutils.InitTestAPIServer(t, "filter-plugin", nil) 2107 2108 tests := []struct { 2109 name string 2110 fail bool 2111 }{ 2112 { 2113 name: "fail filter plugin", 2114 fail: true, 2115 }, 2116 { 2117 name: "do not fail filter plugin", 2118 fail: false, 2119 }, 2120 } 2121 2122 for _, test := range tests { 2123 t.Run(test.name, func(t *testing.T) { 2124 // Create a plugin registry for testing. Register only a filter plugin. 2125 filterPlugin := &FilterPlugin{} 2126 registry, prof := initRegistryAndConfig(t, filterPlugin) 2127 2128 testCtx, teardown := schedulerutils.InitTestSchedulerForFrameworkTest(t, testContext, 1, 2129 scheduler.WithProfiles(prof), 2130 scheduler.WithFrameworkOutOfTreeRegistry(registry)) 2131 defer teardown() 2132 2133 filterPlugin.failFilter = test.fail 2134 // Create a best effort pod. 2135 pod, err := testutils.CreatePausePod(testCtx.ClientSet, 2136 testutils.InitPausePod(&testutils.PausePodConfig{Name: "test-pod", Namespace: testCtx.NS.Name})) 2137 if err != nil { 2138 t.Errorf("Error while creating a test pod: %v", err) 2139 } 2140 2141 if test.fail { 2142 if err = wait.PollUntilContextTimeout(testCtx.Ctx, 10*time.Millisecond, 30*time.Second, false, 2143 testutils.PodSchedulingError(testCtx.ClientSet, pod.Namespace, pod.Name)); err != nil { 2144 t.Errorf("Expected a scheduling error, but got: %v", err) 2145 } 2146 if filterPlugin.numFilterCalled < 1 { 2147 t.Errorf("Expected the filter plugin to be called at least 1 time, but got %v.", filterPlugin.numFilterCalled) 2148 } 2149 } else { 2150 if err = testutils.WaitForPodToSchedule(testCtx.ClientSet, pod); err != nil { 2151 t.Errorf("Expected the pod to be scheduled. error: %v", err) 2152 } 2153 if filterPlugin.numFilterCalled != 1 { 2154 t.Errorf("Expected the filter plugin to be called 1 time, but got %v.", filterPlugin.numFilterCalled) 2155 } 2156 } 2157 }) 2158 } 2159 } 2160 2161 // TestPreScorePlugin tests invocation of pre-score plugins. 2162 func TestPreScorePlugin(t *testing.T) { 2163 testContext := testutils.InitTestAPIServer(t, "pre-score-plugin", nil) 2164 2165 tests := []struct { 2166 name string 2167 fail bool 2168 }{ 2169 { 2170 name: "fail preScore plugin", 2171 fail: true, 2172 }, 2173 { 2174 name: "do not fail preScore plugin", 2175 fail: false, 2176 }, 2177 } 2178 2179 for _, test := range tests { 2180 t.Run(test.name, func(t *testing.T) { 2181 // Create a plugin registry for testing. Register only a pre-score plugin. 2182 preScorePlugin := &PreScorePlugin{} 2183 registry, prof := initRegistryAndConfig(t, preScorePlugin) 2184 2185 testCtx, teardown := schedulerutils.InitTestSchedulerForFrameworkTest(t, testContext, 2, 2186 scheduler.WithProfiles(prof), 2187 scheduler.WithFrameworkOutOfTreeRegistry(registry)) 2188 defer teardown() 2189 2190 preScorePlugin.failPreScore = test.fail 2191 // Create a best effort pod. 2192 pod, err := testutils.CreatePausePod(testCtx.ClientSet, 2193 testutils.InitPausePod(&testutils.PausePodConfig{Name: "test-pod", Namespace: testCtx.NS.Name})) 2194 if err != nil { 2195 t.Errorf("Error while creating a test pod: %v", err) 2196 } 2197 2198 if test.fail { 2199 if err = wait.PollUntilContextTimeout(testCtx.Ctx, 10*time.Millisecond, 30*time.Second, false, 2200 testutils.PodSchedulingError(testCtx.ClientSet, pod.Namespace, pod.Name)); err != nil { 2201 t.Errorf("Expected a scheduling error, but got: %v", err) 2202 } 2203 } else { 2204 if err = testutils.WaitForPodToSchedule(testCtx.ClientSet, pod); err != nil { 2205 t.Errorf("Expected the pod to be scheduled. error: %v", err) 2206 } 2207 } 2208 2209 if preScorePlugin.numPreScoreCalled == 0 { 2210 t.Errorf("Expected the pre-score plugin to be called.") 2211 } 2212 }) 2213 } 2214 } 2215 2216 // TestPreEnqueuePlugin tests invocation of enqueue plugins. 2217 func TestPreEnqueuePlugin(t *testing.T) { 2218 testContext := testutils.InitTestAPIServer(t, "enqueue-plugin", nil) 2219 2220 tests := []struct { 2221 name string 2222 pod *v1.Pod 2223 admitEnqueue bool 2224 }{ 2225 { 2226 name: "pod is admitted to enqueue", 2227 pod: st.MakePod().Name("p").Namespace(testContext.NS.Name).Container("pause").Obj(), 2228 admitEnqueue: true, 2229 }, 2230 { 2231 name: "pod is not admitted to enqueue", 2232 pod: st.MakePod().Name("p").Namespace(testContext.NS.Name).SchedulingGates([]string{"foo"}).Container("pause").Obj(), 2233 admitEnqueue: false, 2234 }, 2235 } 2236 2237 for _, tt := range tests { 2238 t.Run(tt.name, func(t *testing.T) { 2239 // Create a plugin registry for testing. Register only a filter plugin. 2240 enqueuePlugin := &PreEnqueuePlugin{} 2241 // Plumb a preFilterPlugin to verify if it's called or not. 2242 preFilterPlugin := &PreFilterPlugin{} 2243 registry, prof := initRegistryAndConfig(t, enqueuePlugin, preFilterPlugin) 2244 2245 testCtx, teardown := schedulerutils.InitTestSchedulerForFrameworkTest(t, testContext, 1, 2246 scheduler.WithProfiles(prof), 2247 scheduler.WithFrameworkOutOfTreeRegistry(registry)) 2248 defer teardown() 2249 2250 enqueuePlugin.admit = tt.admitEnqueue 2251 // Create a best effort pod. 2252 pod, err := testutils.CreatePausePod(testCtx.ClientSet, tt.pod) 2253 if err != nil { 2254 t.Errorf("Error while creating a test pod: %v", err) 2255 } 2256 2257 if tt.admitEnqueue { 2258 if err := testutils.WaitForPodToScheduleWithTimeout(testCtx.ClientSet, pod, 10*time.Second); err != nil { 2259 t.Errorf("Expected the pod to be schedulable, but got: %v", err) 2260 } 2261 // Also verify enqueuePlugin is called. 2262 if enqueuePlugin.called == 0 { 2263 t.Errorf("Expected the enqueuePlugin plugin to be called at least once, but got 0") 2264 } 2265 } else { 2266 if err := testutils.WaitForPodSchedulingGated(testCtx.ClientSet, pod, 10*time.Second); err != nil { 2267 t.Errorf("Expected the pod to be scheduling waiting, but got: %v", err) 2268 } 2269 // Also verify preFilterPlugin is not called. 2270 if preFilterPlugin.numPreFilterCalled != 0 { 2271 t.Errorf("Expected the preFilter plugin not to be called, but got %v", preFilterPlugin.numPreFilterCalled) 2272 } 2273 } 2274 }) 2275 } 2276 } 2277 2278 // TestPreemptWithPermitPlugin tests preempt with permit plugins. 2279 // It verifies how waitingPods behave in different scenarios: 2280 // - when waitingPods get preempted 2281 // - they should be removed from internal waitingPods map, but not physically deleted 2282 // - it'd trigger moving unschedulable Pods, but not the waitingPods themselves 2283 // 2284 // - when waitingPods get deleted externally, it'd trigger moving unschedulable Pods 2285 func TestPreemptWithPermitPlugin(t *testing.T) { 2286 testContext := testutils.InitTestAPIServer(t, "preempt-with-permit-plugin", nil) 2287 2288 ns := testContext.NS.Name 2289 lowPriority, highPriority := int32(100), int32(300) 2290 resReq := map[v1.ResourceName]string{ 2291 v1.ResourceCPU: "200m", 2292 v1.ResourceMemory: "200", 2293 } 2294 preemptorReq := map[v1.ResourceName]string{ 2295 v1.ResourceCPU: "400m", 2296 v1.ResourceMemory: "400", 2297 } 2298 2299 nodeRes := map[v1.ResourceName]string{ 2300 v1.ResourcePods: "32", 2301 v1.ResourceCPU: "500m", 2302 v1.ResourceMemory: "500", 2303 } 2304 2305 tests := []struct { 2306 name string 2307 deleteWaitingPod bool 2308 maxNumWaitingPodCalled int 2309 runningPod *v1.Pod 2310 waitingPod *v1.Pod 2311 preemptor *v1.Pod 2312 }{ 2313 { 2314 name: "waiting pod is not physically deleted upon preemption", 2315 maxNumWaitingPodCalled: 2, 2316 runningPod: st.MakePod().Name("running-pod").Namespace(ns).Priority(lowPriority).Req(resReq).ZeroTerminationGracePeriod().Obj(), 2317 waitingPod: st.MakePod().Name("waiting-pod").Namespace(ns).Priority(lowPriority).Req(resReq).ZeroTerminationGracePeriod().Obj(), 2318 preemptor: st.MakePod().Name("preemptor-pod").Namespace(ns).Priority(highPriority).Req(preemptorReq).ZeroTerminationGracePeriod().Obj(), 2319 }, 2320 { 2321 // The waiting Pod has once gone through the scheduling cycle, 2322 // and we don't know if it's schedulable or not after it's preempted. 2323 // So, we should retry the scheduling of it so that it won't stuck in the unschedulable Pod pool. 2324 name: "rejecting a waiting pod to trigger retrying unschedulable pods immediately, and the waiting pod itself will be retried", 2325 maxNumWaitingPodCalled: 2, 2326 waitingPod: st.MakePod().Name("waiting-pod").Namespace(ns).Priority(lowPriority).Req(resReq).ZeroTerminationGracePeriod().Obj(), 2327 preemptor: st.MakePod().Name("preemptor-pod").Namespace(ns).Priority(highPriority).Req(preemptorReq).ZeroTerminationGracePeriod().Obj(), 2328 }, 2329 { 2330 name: "deleting a waiting pod to trigger retrying unschedulable pods immediately", 2331 deleteWaitingPod: true, 2332 maxNumWaitingPodCalled: 1, 2333 waitingPod: st.MakePod().Name("waiting-pod").Namespace(ns).Priority(lowPriority).Req(resReq).ZeroTerminationGracePeriod().Obj(), 2334 preemptor: st.MakePod().Name("preemptor-pod").Namespace(ns).Priority(lowPriority).Req(preemptorReq).ZeroTerminationGracePeriod().Obj(), 2335 }, 2336 } 2337 2338 for _, tt := range tests { 2339 t.Run(tt.name, func(t *testing.T) { 2340 // Create a plugin registry for testing. Register a permit and a filter plugin. 2341 permitPlugin := &PermitPlugin{} 2342 // Inject a fake filter plugin to use its internal `numFilterCalled` to verify 2343 // how many times a Pod gets tried scheduling. 2344 filterPlugin := &FilterPlugin{numCalledPerPod: make(map[string]int)} 2345 registry := frameworkruntime.Registry{ 2346 permitPluginName: newPlugin(permitPlugin), 2347 filterPluginName: newPlugin(filterPlugin), 2348 } 2349 2350 // Setup initial permit and filter plugins in the profile. 2351 cfg := configtesting.V1ToInternalWithDefaults(t, configv1.KubeSchedulerConfiguration{ 2352 Profiles: []configv1.KubeSchedulerProfile{ 2353 { 2354 SchedulerName: pointer.String(v1.DefaultSchedulerName), 2355 Plugins: &configv1.Plugins{ 2356 Permit: configv1.PluginSet{ 2357 Enabled: []configv1.Plugin{ 2358 {Name: permitPluginName}, 2359 }, 2360 }, 2361 Filter: configv1.PluginSet{ 2362 // Ensure the fake filter plugin is always called; otherwise noderesources 2363 // would fail first and exit the Filter phase. 2364 Enabled: []configv1.Plugin{ 2365 {Name: filterPluginName}, 2366 {Name: noderesources.Name}, 2367 }, 2368 Disabled: []configv1.Plugin{ 2369 {Name: noderesources.Name}, 2370 }, 2371 }, 2372 }, 2373 }, 2374 }, 2375 }) 2376 2377 testCtx, teardown := schedulerutils.InitTestSchedulerForFrameworkTest(t, testContext, 0, 2378 scheduler.WithProfiles(cfg.Profiles...), 2379 scheduler.WithFrameworkOutOfTreeRegistry(registry), 2380 ) 2381 defer teardown() 2382 2383 _, err := testutils.CreateAndWaitForNodesInCache(testCtx, "test-node", st.MakeNode().Capacity(nodeRes), 1) 2384 if err != nil { 2385 t.Fatal(err) 2386 } 2387 2388 permitPlugin.waitAndAllowPermit = true 2389 permitPlugin.waitingPod = "waiting-pod" 2390 2391 if r := tt.runningPod; r != nil { 2392 if _, err := testutils.CreatePausePod(testCtx.ClientSet, r); err != nil { 2393 t.Fatalf("Error while creating the running pod: %v", err) 2394 } 2395 // Wait until the pod to be scheduled. 2396 if err = testutils.WaitForPodToSchedule(testCtx.ClientSet, r); err != nil { 2397 t.Fatalf("The running pod is expected to be scheduled: %v", err) 2398 } 2399 } 2400 2401 if w := tt.waitingPod; w != nil { 2402 if _, err := testutils.CreatePausePod(testCtx.ClientSet, w); err != nil { 2403 t.Fatalf("Error while creating the waiting pod: %v", err) 2404 } 2405 // Wait until the waiting-pod is actually waiting. 2406 if err := wait.PollUntilContextTimeout(testCtx.Ctx, 10*time.Millisecond, 30*time.Second, false, func(ctx context.Context) (bool, error) { 2407 w := false 2408 permitPlugin.fh.IterateOverWaitingPods(func(wp framework.WaitingPod) { w = true }) 2409 return w, nil 2410 }); err != nil { 2411 t.Fatalf("The waiting pod is expected to be waiting: %v", err) 2412 } 2413 } 2414 2415 if p := tt.preemptor; p != nil { 2416 if _, err := testutils.CreatePausePod(testCtx.ClientSet, p); err != nil { 2417 t.Fatalf("Error while creating the preemptor pod: %v", err) 2418 } 2419 // Delete the waiting pod if specified. 2420 if w := tt.waitingPod; w != nil && tt.deleteWaitingPod { 2421 if err := testutils.DeletePod(testCtx.ClientSet, w.Name, w.Namespace); err != nil { 2422 t.Fatalf("Error while deleting the waiting pod: %v", err) 2423 } 2424 } 2425 if err = testutils.WaitForPodToSchedule(testCtx.ClientSet, p); err != nil { 2426 t.Fatalf("Expected the preemptor pod to be scheduled. error: %v", err) 2427 } 2428 } 2429 2430 if w := tt.waitingPod; w != nil { 2431 if err := wait.PollUntilContextTimeout(testCtx.Ctx, 200*time.Millisecond, wait.ForeverTestTimeout, false, func(ctx context.Context) (bool, error) { 2432 w := false 2433 permitPlugin.fh.IterateOverWaitingPods(func(wp framework.WaitingPod) { w = true }) 2434 return !w, nil 2435 }); err != nil { 2436 t.Fatalf("Expected the waiting pod to get preempted.") 2437 } 2438 2439 p := filterPlugin.deepCopy() 2440 waitingPodCalled := p.numCalledPerPod[fmt.Sprintf("%v/%v", w.Namespace, w.Name)] 2441 if waitingPodCalled > tt.maxNumWaitingPodCalled { 2442 t.Fatalf("Expected the waiting pod to be called %v times at most, but got %v", tt.maxNumWaitingPodCalled, waitingPodCalled) 2443 } 2444 2445 if !tt.deleteWaitingPod { 2446 // Expect the waitingPod to be still present. 2447 if _, err := testutils.GetPod(testCtx.ClientSet, w.Name, w.Namespace); err != nil { 2448 t.Error("Get waiting pod in waiting pod failed.") 2449 } 2450 } 2451 2452 if permitPlugin.numPermitCalled == 0 { 2453 t.Errorf("Expected the permit plugin to be called.") 2454 } 2455 } 2456 2457 if r := tt.runningPod; r != nil { 2458 // Expect the runningPod to be deleted physically. 2459 if _, err = testutils.GetPod(testCtx.ClientSet, r.Name, r.Namespace); err == nil { 2460 t.Error("The running pod still exists.") 2461 } else if !errors.IsNotFound(err) { 2462 t.Errorf("Get running pod failed: %v", err) 2463 } 2464 } 2465 }) 2466 } 2467 } 2468 2469 const ( 2470 jobPluginName = "job plugin" 2471 ) 2472 2473 var _ framework.PreFilterPlugin = &JobPlugin{} 2474 var _ framework.PostBindPlugin = &PostBindPlugin{} 2475 2476 type JobPlugin struct { 2477 podLister listersv1.PodLister 2478 podsActivated bool 2479 } 2480 2481 func (j *JobPlugin) Name() string { 2482 return jobPluginName 2483 } 2484 2485 func (j *JobPlugin) PreFilter(_ context.Context, _ *framework.CycleState, p *v1.Pod) (*framework.PreFilterResult, *framework.Status) { 2486 labelSelector := labels.SelectorFromSet(labels.Set{"driver": ""}) 2487 driverPods, err := j.podLister.Pods(p.Namespace).List(labelSelector) 2488 if err != nil { 2489 return nil, framework.AsStatus(err) 2490 } 2491 if len(driverPods) == 0 { 2492 return nil, framework.NewStatus(framework.UnschedulableAndUnresolvable, "unable to find driver pod") 2493 } 2494 return nil, nil 2495 } 2496 2497 func (j *JobPlugin) PreFilterExtensions() framework.PreFilterExtensions { 2498 return nil 2499 } 2500 2501 func (j *JobPlugin) PostBind(_ context.Context, state *framework.CycleState, p *v1.Pod, nodeName string) { 2502 if _, ok := p.Labels["driver"]; !ok { 2503 return 2504 } 2505 2506 // If it's a driver pod, move other executor pods proactively to accelerating the scheduling. 2507 labelSelector := labels.SelectorFromSet(labels.Set{"executor": ""}) 2508 podsToActivate, err := j.podLister.Pods(p.Namespace).List(labelSelector) 2509 if err == nil && len(podsToActivate) != 0 { 2510 c, err := state.Read(framework.PodsToActivateKey) 2511 if err == nil { 2512 if s, ok := c.(*framework.PodsToActivate); ok { 2513 s.Lock() 2514 for _, pod := range podsToActivate { 2515 namespacedName := fmt.Sprintf("%v/%v", pod.Namespace, pod.Name) 2516 s.Map[namespacedName] = pod 2517 } 2518 s.Unlock() 2519 j.podsActivated = true 2520 } 2521 } 2522 } 2523 } 2524 2525 // This test simulates a typical spark job workflow. 2526 // - N executor pods are created, but kept pending due to missing the driver pod 2527 // - when the driver pod gets created and scheduled, proactively move the executors to activeQ 2528 // and thus accelerate the entire job workflow. 2529 func TestActivatePods(t *testing.T) { 2530 var jobPlugin *JobPlugin 2531 // Create a plugin registry for testing. Register a Job plugin. 2532 registry := frameworkruntime.Registry{jobPluginName: func(_ context.Context, _ runtime.Object, fh framework.Handle) (framework.Plugin, error) { 2533 jobPlugin = &JobPlugin{podLister: fh.SharedInformerFactory().Core().V1().Pods().Lister()} 2534 return jobPlugin, nil 2535 }} 2536 2537 // Setup initial filter plugin for testing. 2538 cfg := configtesting.V1ToInternalWithDefaults(t, configv1.KubeSchedulerConfiguration{ 2539 Profiles: []configv1.KubeSchedulerProfile{{ 2540 SchedulerName: pointer.String(v1.DefaultSchedulerName), 2541 Plugins: &configv1.Plugins{ 2542 PreFilter: configv1.PluginSet{ 2543 Enabled: []configv1.Plugin{ 2544 {Name: jobPluginName}, 2545 }, 2546 }, 2547 PostBind: configv1.PluginSet{ 2548 Enabled: []configv1.Plugin{ 2549 {Name: jobPluginName}, 2550 }, 2551 }, 2552 }, 2553 }}, 2554 }) 2555 2556 // Create the API server and the scheduler with the test plugin set. 2557 testCtx, _ := schedulerutils.InitTestSchedulerForFrameworkTest(t, testutils.InitTestAPIServer(t, "job-plugin", nil), 1, 2558 scheduler.WithProfiles(cfg.Profiles...), 2559 scheduler.WithFrameworkOutOfTreeRegistry(registry)) 2560 2561 cs := testCtx.ClientSet 2562 ns := testCtx.NS.Name 2563 pause := imageutils.GetPauseImageName() 2564 2565 // Firstly create 2 executor pods. 2566 var pods []*v1.Pod 2567 for i := 1; i <= 2; i++ { 2568 name := fmt.Sprintf("executor-%v", i) 2569 executor := st.MakePod().Name(name).Namespace(ns).Label("executor", "").Container(pause).Obj() 2570 pods = append(pods, executor) 2571 if _, err := cs.CoreV1().Pods(executor.Namespace).Create(context.TODO(), executor, metav1.CreateOptions{}); err != nil { 2572 t.Fatalf("Failed to create pod %v: %v", executor.Name, err) 2573 } 2574 } 2575 2576 // Wait for the 2 executor pods to be unschedulable. 2577 for _, pod := range pods { 2578 if err := testutils.WaitForPodUnschedulable(cs, pod); err != nil { 2579 t.Errorf("Failed to wait for Pod %v to be unschedulable: %v", pod.Name, err) 2580 } 2581 } 2582 2583 // Create a driver pod. 2584 driver := st.MakePod().Name("driver").Namespace(ns).Label("driver", "").Container(pause).Obj() 2585 pods = append(pods, driver) 2586 if _, err := cs.CoreV1().Pods(driver.Namespace).Create(context.TODO(), driver, metav1.CreateOptions{}); err != nil { 2587 t.Fatalf("Failed to create pod %v: %v", driver.Name, err) 2588 } 2589 2590 // Verify all pods to be scheduled. 2591 for _, pod := range pods { 2592 if err := testutils.WaitForPodToScheduleWithTimeout(cs, pod, wait.ForeverTestTimeout); err != nil { 2593 t.Fatalf("Failed to wait for Pod %v to be schedulable: %v", pod.Name, err) 2594 } 2595 } 2596 2597 // Lastly verify the pods activation logic is really called. 2598 if jobPlugin.podsActivated == false { 2599 t.Errorf("JobPlugin's pods activation logic is not called") 2600 } 2601 } 2602 2603 var _ framework.PreEnqueuePlugin = &SchedulingGatesPluginWithEvents{} 2604 var _ framework.EnqueueExtensions = &SchedulingGatesPluginWithEvents{} 2605 var _ framework.PreEnqueuePlugin = &SchedulingGatesPluginWOEvents{} 2606 var _ framework.EnqueueExtensions = &SchedulingGatesPluginWOEvents{} 2607 2608 const ( 2609 schedulingGatesPluginWithEvents = "scheduling-gates-with-events" 2610 schedulingGatesPluginWOEvents = "scheduling-gates-without-events" 2611 ) 2612 2613 type SchedulingGatesPluginWithEvents struct { 2614 called int 2615 schedulinggates.SchedulingGates 2616 } 2617 2618 func (pl *SchedulingGatesPluginWithEvents) Name() string { 2619 return schedulingGatesPluginWithEvents 2620 } 2621 2622 func (pl *SchedulingGatesPluginWithEvents) PreEnqueue(ctx context.Context, p *v1.Pod) *framework.Status { 2623 pl.called++ 2624 return pl.SchedulingGates.PreEnqueue(ctx, p) 2625 } 2626 2627 func (pl *SchedulingGatesPluginWithEvents) EventsToRegister() []framework.ClusterEventWithHint { 2628 return []framework.ClusterEventWithHint{ 2629 {Event: framework.ClusterEvent{Resource: framework.Pod, ActionType: framework.Update}}, 2630 } 2631 } 2632 2633 type SchedulingGatesPluginWOEvents struct { 2634 called int 2635 schedulinggates.SchedulingGates 2636 } 2637 2638 func (pl *SchedulingGatesPluginWOEvents) Name() string { 2639 return schedulingGatesPluginWOEvents 2640 } 2641 2642 func (pl *SchedulingGatesPluginWOEvents) PreEnqueue(ctx context.Context, p *v1.Pod) *framework.Status { 2643 pl.called++ 2644 return pl.SchedulingGates.PreEnqueue(ctx, p) 2645 } 2646 2647 func (pl *SchedulingGatesPluginWOEvents) EventsToRegister() []framework.ClusterEventWithHint { 2648 return nil 2649 } 2650 2651 // This test helps to verify registering nil events for schedulingGates plugin works as expected. 2652 func TestSchedulingGatesPluginEventsToRegister(t *testing.T) { 2653 testContext := testutils.InitTestAPIServer(t, "preenqueue-plugin", nil) 2654 2655 num := func(pl framework.Plugin) int { 2656 switch item := pl.(type) { 2657 case *SchedulingGatesPluginWithEvents: 2658 return item.called 2659 case *SchedulingGatesPluginWOEvents: 2660 return item.called 2661 default: 2662 t.Error("unsupported plugin") 2663 } 2664 return 0 2665 } 2666 2667 tests := []struct { 2668 name string 2669 enqueuePlugin framework.PreEnqueuePlugin 2670 count int 2671 }{ 2672 { 2673 name: "preEnqueue plugin without event registered", 2674 enqueuePlugin: &SchedulingGatesPluginWOEvents{SchedulingGates: schedulinggates.SchedulingGates{}}, 2675 count: 2, 2676 }, 2677 { 2678 name: "preEnqueue plugin with event registered", 2679 enqueuePlugin: &SchedulingGatesPluginWithEvents{SchedulingGates: schedulinggates.SchedulingGates{}}, 2680 count: 2, 2681 }, 2682 } 2683 2684 for _, tt := range tests { 2685 t.Run(tt.name, func(t *testing.T) { 2686 registry := frameworkruntime.Registry{ 2687 tt.enqueuePlugin.Name(): newPlugin(tt.enqueuePlugin), 2688 } 2689 2690 // Setup plugins for testing. 2691 cfg := configtesting.V1ToInternalWithDefaults(t, configv1.KubeSchedulerConfiguration{ 2692 Profiles: []configv1.KubeSchedulerProfile{{ 2693 SchedulerName: pointer.String(v1.DefaultSchedulerName), 2694 Plugins: &configv1.Plugins{ 2695 PreEnqueue: configv1.PluginSet{ 2696 Enabled: []configv1.Plugin{ 2697 {Name: tt.enqueuePlugin.Name()}, 2698 }, 2699 Disabled: []configv1.Plugin{ 2700 {Name: "*"}, 2701 }, 2702 }, 2703 }, 2704 }}, 2705 }) 2706 2707 testCtx, teardown := schedulerutils.InitTestSchedulerForFrameworkTest(t, testContext, 2, 2708 scheduler.WithProfiles(cfg.Profiles...), 2709 scheduler.WithFrameworkOutOfTreeRegistry(registry), 2710 ) 2711 defer teardown() 2712 2713 // Create a pod with schedulingGates. 2714 gatedPod := st.MakePod().Name("p").Namespace(testContext.NS.Name). 2715 SchedulingGates([]string{"foo"}). 2716 PodAffinity("kubernetes.io/hostname", &metav1.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}}, st.PodAffinityWithRequiredReq). 2717 Container("pause").Obj() 2718 gatedPod, err := testutils.CreatePausePod(testCtx.ClientSet, gatedPod) 2719 if err != nil { 2720 t.Errorf("Error while creating a gated pod: %v", err) 2721 return 2722 } 2723 2724 if err := testutils.WaitForPodSchedulingGated(testCtx.ClientSet, gatedPod, 10*time.Second); err != nil { 2725 t.Errorf("Expected the pod to be gated, but got: %v", err) 2726 return 2727 } 2728 if num(tt.enqueuePlugin) != 1 { 2729 t.Errorf("Expected the preEnqueue plugin to be called once, but got %v", num(tt.enqueuePlugin)) 2730 return 2731 } 2732 2733 // Create a best effort pod. 2734 pausePod, err := testutils.CreatePausePod(testCtx.ClientSet, testutils.InitPausePod(&testutils.PausePodConfig{ 2735 Name: "pause-pod", 2736 Namespace: testCtx.NS.Name, 2737 Labels: map[string]string{"foo": "bar"}, 2738 })) 2739 if err != nil { 2740 t.Errorf("Error while creating a pod: %v", err) 2741 return 2742 } 2743 2744 // Wait for the pod schedulabled. 2745 if err := testutils.WaitForPodToScheduleWithTimeout(testCtx.ClientSet, pausePod, 10*time.Second); err != nil { 2746 t.Errorf("Expected the pod to be schedulable, but got: %v", err) 2747 return 2748 } 2749 2750 // Update the pod which will trigger the requeue logic if plugin registers the events. 2751 pausePod, err = testCtx.ClientSet.CoreV1().Pods(pausePod.Namespace).Get(testCtx.Ctx, pausePod.Name, metav1.GetOptions{}) 2752 if err != nil { 2753 t.Errorf("Error while getting a pod: %v", err) 2754 return 2755 } 2756 pausePod.Annotations = map[string]string{"foo": "bar"} 2757 _, err = testCtx.ClientSet.CoreV1().Pods(pausePod.Namespace).Update(testCtx.Ctx, pausePod, metav1.UpdateOptions{}) 2758 if err != nil { 2759 t.Errorf("Error while updating a pod: %v", err) 2760 return 2761 } 2762 2763 // Pod should still be unschedulable because scheduling gates still exist, theoretically, it's a waste rescheduling. 2764 if err := testutils.WaitForPodSchedulingGated(testCtx.ClientSet, gatedPod, 10*time.Second); err != nil { 2765 t.Errorf("Expected the pod to be gated, but got: %v", err) 2766 return 2767 } 2768 if num(tt.enqueuePlugin) != tt.count { 2769 t.Errorf("Expected the preEnqueue plugin to be called %v, but got %v", tt.count, num(tt.enqueuePlugin)) 2770 return 2771 } 2772 2773 // Remove gated pod's scheduling gates. 2774 gatedPod, err = testCtx.ClientSet.CoreV1().Pods(gatedPod.Namespace).Get(testCtx.Ctx, gatedPod.Name, metav1.GetOptions{}) 2775 if err != nil { 2776 t.Errorf("Error while getting a pod: %v", err) 2777 return 2778 } 2779 gatedPod.Spec.SchedulingGates = nil 2780 _, err = testCtx.ClientSet.CoreV1().Pods(gatedPod.Namespace).Update(testCtx.Ctx, gatedPod, metav1.UpdateOptions{}) 2781 if err != nil { 2782 t.Errorf("Error while updating a pod: %v", err) 2783 return 2784 } 2785 2786 // Ungated pod should be schedulable now. 2787 if err := testutils.WaitForPodToScheduleWithTimeout(testCtx.ClientSet, gatedPod, 10*time.Second); err != nil { 2788 t.Errorf("Expected the pod to be schedulable, but got: %v", err) 2789 return 2790 } 2791 }) 2792 } 2793 }