k8s.io/kubernetes@v1.29.3/pkg/scheduler/framework/runtime/framework_test.go (about) 1 /* 2 Copyright 2019 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 runtime 18 19 import ( 20 "context" 21 "errors" 22 "fmt" 23 "reflect" 24 "strings" 25 "testing" 26 "time" 27 28 "github.com/google/go-cmp/cmp" 29 v1 "k8s.io/api/core/v1" 30 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 31 "k8s.io/apimachinery/pkg/runtime" 32 "k8s.io/apimachinery/pkg/types" 33 "k8s.io/apimachinery/pkg/util/sets" 34 "k8s.io/component-base/metrics/testutil" 35 "k8s.io/klog/v2/ktesting" 36 "k8s.io/kubernetes/pkg/scheduler/apis/config" 37 "k8s.io/kubernetes/pkg/scheduler/framework" 38 internalqueue "k8s.io/kubernetes/pkg/scheduler/internal/queue" 39 "k8s.io/kubernetes/pkg/scheduler/metrics" 40 "k8s.io/utils/ptr" 41 ) 42 43 const ( 44 preEnqueuePlugin = "preEnqueue-plugin" 45 queueSortPlugin = "no-op-queue-sort-plugin" 46 scoreWithNormalizePlugin1 = "score-with-normalize-plugin-1" 47 scoreWithNormalizePlugin2 = "score-with-normalize-plugin-2" 48 scorePlugin1 = "score-plugin-1" 49 scorePlugin2 = "score-plugin-2" 50 pluginNotImplementingScore = "plugin-not-implementing-score" 51 preFilterPluginName = "prefilter-plugin" 52 preFilterWithExtensionsPluginName = "prefilter-with-extensions-plugin" 53 duplicatePluginName = "duplicate-plugin" 54 testPlugin = "test-plugin" 55 permitPlugin = "permit-plugin" 56 bindPlugin = "bind-plugin" 57 58 testProfileName = "test-profile" 59 testPercentageOfNodesToScore = 35 60 nodeName = "testNode" 61 62 injectReason = "injected status" 63 injectFilterReason = "injected filter status" 64 ) 65 66 // TestScoreWithNormalizePlugin implements ScoreWithNormalizePlugin interface. 67 // TestScorePlugin only implements ScorePlugin interface. 68 var _ framework.ScorePlugin = &TestScoreWithNormalizePlugin{} 69 var _ framework.ScorePlugin = &TestScorePlugin{} 70 71 var cmpOpts = []cmp.Option{ 72 cmp.Comparer(func(s1 *framework.Status, s2 *framework.Status) bool { 73 if s1 == nil || s2 == nil { 74 return s1.IsSuccess() && s2.IsSuccess() 75 } 76 return s1.Code() == s2.Code() && s1.Plugin() == s2.Plugin() && s1.Message() == s2.Message() 77 }), 78 } 79 80 func newScoreWithNormalizePlugin1(_ context.Context, injArgs runtime.Object, f framework.Handle) (framework.Plugin, error) { 81 var inj injectedResult 82 if err := DecodeInto(injArgs, &inj); err != nil { 83 return nil, err 84 } 85 return &TestScoreWithNormalizePlugin{scoreWithNormalizePlugin1, inj}, nil 86 } 87 88 func newScoreWithNormalizePlugin2(_ context.Context, injArgs runtime.Object, f framework.Handle) (framework.Plugin, error) { 89 var inj injectedResult 90 if err := DecodeInto(injArgs, &inj); err != nil { 91 return nil, err 92 } 93 return &TestScoreWithNormalizePlugin{scoreWithNormalizePlugin2, inj}, nil 94 } 95 96 func newScorePlugin1(_ context.Context, injArgs runtime.Object, f framework.Handle) (framework.Plugin, error) { 97 var inj injectedResult 98 if err := DecodeInto(injArgs, &inj); err != nil { 99 return nil, err 100 } 101 return &TestScorePlugin{scorePlugin1, inj}, nil 102 } 103 104 func newScorePlugin2(_ context.Context, injArgs runtime.Object, f framework.Handle) (framework.Plugin, error) { 105 var inj injectedResult 106 if err := DecodeInto(injArgs, &inj); err != nil { 107 return nil, err 108 } 109 return &TestScorePlugin{scorePlugin2, inj}, nil 110 } 111 112 func newPluginNotImplementingScore(_ context.Context, _ runtime.Object, _ framework.Handle) (framework.Plugin, error) { 113 return &PluginNotImplementingScore{}, nil 114 } 115 116 type TestScoreWithNormalizePlugin struct { 117 name string 118 inj injectedResult 119 } 120 121 func (pl *TestScoreWithNormalizePlugin) Name() string { 122 return pl.name 123 } 124 125 func (pl *TestScoreWithNormalizePlugin) NormalizeScore(ctx context.Context, state *framework.CycleState, pod *v1.Pod, scores framework.NodeScoreList) *framework.Status { 126 return injectNormalizeRes(pl.inj, scores) 127 } 128 129 func (pl *TestScoreWithNormalizePlugin) Score(ctx context.Context, state *framework.CycleState, p *v1.Pod, nodeName string) (int64, *framework.Status) { 130 return setScoreRes(pl.inj) 131 } 132 133 func (pl *TestScoreWithNormalizePlugin) ScoreExtensions() framework.ScoreExtensions { 134 return pl 135 } 136 137 // TestScorePlugin only implements ScorePlugin interface. 138 type TestScorePlugin struct { 139 name string 140 inj injectedResult 141 } 142 143 func (pl *TestScorePlugin) Name() string { 144 return pl.name 145 } 146 147 func (pl *TestScorePlugin) PreScore(ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodes []*v1.Node) *framework.Status { 148 return framework.NewStatus(framework.Code(pl.inj.PreScoreStatus), injectReason) 149 } 150 151 func (pl *TestScorePlugin) Score(ctx context.Context, state *framework.CycleState, p *v1.Pod, nodeName string) (int64, *framework.Status) { 152 return setScoreRes(pl.inj) 153 } 154 155 func (pl *TestScorePlugin) ScoreExtensions() framework.ScoreExtensions { 156 return nil 157 } 158 159 // PluginNotImplementingScore doesn't implement the ScorePlugin interface. 160 type PluginNotImplementingScore struct{} 161 162 func (pl *PluginNotImplementingScore) Name() string { 163 return pluginNotImplementingScore 164 } 165 166 func newTestPlugin(_ context.Context, injArgs runtime.Object, f framework.Handle) (framework.Plugin, error) { 167 return &TestPlugin{name: testPlugin}, nil 168 } 169 170 // TestPlugin implements all Plugin interfaces. 171 type TestPlugin struct { 172 name string 173 inj injectedResult 174 } 175 176 func (pl *TestPlugin) AddPod(ctx context.Context, state *framework.CycleState, podToSchedule *v1.Pod, podInfoToAdd *framework.PodInfo, nodeInfo *framework.NodeInfo) *framework.Status { 177 return framework.NewStatus(framework.Code(pl.inj.PreFilterAddPodStatus), injectReason) 178 } 179 func (pl *TestPlugin) RemovePod(ctx context.Context, state *framework.CycleState, podToSchedule *v1.Pod, podInfoToRemove *framework.PodInfo, nodeInfo *framework.NodeInfo) *framework.Status { 180 return framework.NewStatus(framework.Code(pl.inj.PreFilterRemovePodStatus), injectReason) 181 } 182 183 func (pl *TestPlugin) Name() string { 184 return pl.name 185 } 186 187 func (pl *TestPlugin) Less(*framework.QueuedPodInfo, *framework.QueuedPodInfo) bool { 188 return false 189 } 190 191 func (pl *TestPlugin) Score(ctx context.Context, state *framework.CycleState, p *v1.Pod, nodeName string) (int64, *framework.Status) { 192 return 0, framework.NewStatus(framework.Code(pl.inj.ScoreStatus), injectReason) 193 } 194 195 func (pl *TestPlugin) ScoreExtensions() framework.ScoreExtensions { 196 return nil 197 } 198 199 func (pl *TestPlugin) PreFilter(ctx context.Context, state *framework.CycleState, p *v1.Pod) (*framework.PreFilterResult, *framework.Status) { 200 return nil, framework.NewStatus(framework.Code(pl.inj.PreFilterStatus), injectReason) 201 } 202 203 func (pl *TestPlugin) PreFilterExtensions() framework.PreFilterExtensions { 204 return pl 205 } 206 207 func (pl *TestPlugin) Filter(ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodeInfo *framework.NodeInfo) *framework.Status { 208 return framework.NewStatus(framework.Code(pl.inj.FilterStatus), injectFilterReason) 209 } 210 211 func (pl *TestPlugin) PostFilter(_ context.Context, _ *framework.CycleState, _ *v1.Pod, _ framework.NodeToStatusMap) (*framework.PostFilterResult, *framework.Status) { 212 return nil, framework.NewStatus(framework.Code(pl.inj.PostFilterStatus), injectReason) 213 } 214 215 func (pl *TestPlugin) PreScore(ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodes []*v1.Node) *framework.Status { 216 return framework.NewStatus(framework.Code(pl.inj.PreScoreStatus), injectReason) 217 } 218 219 func (pl *TestPlugin) Reserve(ctx context.Context, state *framework.CycleState, p *v1.Pod, nodeName string) *framework.Status { 220 return framework.NewStatus(framework.Code(pl.inj.ReserveStatus), injectReason) 221 } 222 223 func (pl *TestPlugin) Unreserve(ctx context.Context, state *framework.CycleState, p *v1.Pod, nodeName string) { 224 } 225 226 func (pl *TestPlugin) PreBind(ctx context.Context, state *framework.CycleState, p *v1.Pod, nodeName string) *framework.Status { 227 return framework.NewStatus(framework.Code(pl.inj.PreBindStatus), injectReason) 228 } 229 230 func (pl *TestPlugin) PostBind(ctx context.Context, state *framework.CycleState, p *v1.Pod, nodeName string) { 231 } 232 233 func (pl *TestPlugin) Permit(ctx context.Context, state *framework.CycleState, p *v1.Pod, nodeName string) (*framework.Status, time.Duration) { 234 return framework.NewStatus(framework.Code(pl.inj.PermitStatus), injectReason), time.Duration(0) 235 } 236 237 func (pl *TestPlugin) Bind(ctx context.Context, state *framework.CycleState, p *v1.Pod, nodeName string) *framework.Status { 238 return framework.NewStatus(framework.Code(pl.inj.BindStatus), injectReason) 239 } 240 241 // TestPreFilterPlugin only implements PreFilterPlugin interface. 242 type TestPreFilterPlugin struct { 243 PreFilterCalled int 244 } 245 246 func (pl *TestPreFilterPlugin) Name() string { 247 return preFilterPluginName 248 } 249 250 func (pl *TestPreFilterPlugin) PreFilter(ctx context.Context, state *framework.CycleState, p *v1.Pod) (*framework.PreFilterResult, *framework.Status) { 251 pl.PreFilterCalled++ 252 return nil, nil 253 } 254 255 func (pl *TestPreFilterPlugin) PreFilterExtensions() framework.PreFilterExtensions { 256 return nil 257 } 258 259 // TestPreFilterWithExtensionsPlugin implements Add/Remove interfaces. 260 type TestPreFilterWithExtensionsPlugin struct { 261 PreFilterCalled int 262 AddCalled int 263 RemoveCalled int 264 } 265 266 func (pl *TestPreFilterWithExtensionsPlugin) Name() string { 267 return preFilterWithExtensionsPluginName 268 } 269 270 func (pl *TestPreFilterWithExtensionsPlugin) PreFilter(ctx context.Context, state *framework.CycleState, p *v1.Pod) (*framework.PreFilterResult, *framework.Status) { 271 pl.PreFilterCalled++ 272 return nil, nil 273 } 274 275 func (pl *TestPreFilterWithExtensionsPlugin) AddPod(ctx context.Context, state *framework.CycleState, podToSchedule *v1.Pod, 276 podInfoToAdd *framework.PodInfo, nodeInfo *framework.NodeInfo) *framework.Status { 277 pl.AddCalled++ 278 return nil 279 } 280 281 func (pl *TestPreFilterWithExtensionsPlugin) RemovePod(ctx context.Context, state *framework.CycleState, podToSchedule *v1.Pod, 282 podInfoToRemove *framework.PodInfo, nodeInfo *framework.NodeInfo) *framework.Status { 283 pl.RemoveCalled++ 284 return nil 285 } 286 287 func (pl *TestPreFilterWithExtensionsPlugin) PreFilterExtensions() framework.PreFilterExtensions { 288 return pl 289 } 290 291 type TestDuplicatePlugin struct { 292 } 293 294 func (dp *TestDuplicatePlugin) Name() string { 295 return duplicatePluginName 296 } 297 298 func (dp *TestDuplicatePlugin) PreFilter(ctx context.Context, state *framework.CycleState, p *v1.Pod) (*framework.PreFilterResult, *framework.Status) { 299 return nil, nil 300 } 301 302 func (dp *TestDuplicatePlugin) PreFilterExtensions() framework.PreFilterExtensions { 303 return nil 304 } 305 306 var _ framework.PreFilterPlugin = &TestDuplicatePlugin{} 307 308 func newDuplicatePlugin(_ context.Context, _ runtime.Object, _ framework.Handle) (framework.Plugin, error) { 309 return &TestDuplicatePlugin{}, nil 310 } 311 312 // TestPermitPlugin only implements PermitPlugin interface. 313 type TestPermitPlugin struct { 314 PreFilterCalled int 315 } 316 317 func (pp *TestPermitPlugin) Name() string { 318 return permitPlugin 319 } 320 func (pp *TestPermitPlugin) Permit(ctx context.Context, state *framework.CycleState, p *v1.Pod, nodeName string) (*framework.Status, time.Duration) { 321 return framework.NewStatus(framework.Wait), 10 * time.Second 322 } 323 324 var _ framework.PreEnqueuePlugin = &TestPreEnqueuePlugin{} 325 326 type TestPreEnqueuePlugin struct{} 327 328 func (pl *TestPreEnqueuePlugin) Name() string { 329 return preEnqueuePlugin 330 } 331 332 func (pl *TestPreEnqueuePlugin) PreEnqueue(ctx context.Context, p *v1.Pod) *framework.Status { 333 return nil 334 } 335 336 var _ framework.QueueSortPlugin = &TestQueueSortPlugin{} 337 338 func newQueueSortPlugin(_ context.Context, _ runtime.Object, _ framework.Handle) (framework.Plugin, error) { 339 return &TestQueueSortPlugin{}, nil 340 } 341 342 // TestQueueSortPlugin is a no-op implementation for QueueSort extension point. 343 type TestQueueSortPlugin struct{} 344 345 func (pl *TestQueueSortPlugin) Name() string { 346 return queueSortPlugin 347 } 348 349 func (pl *TestQueueSortPlugin) Less(_, _ *framework.QueuedPodInfo) bool { 350 return false 351 } 352 353 var _ framework.BindPlugin = &TestBindPlugin{} 354 355 func newBindPlugin(_ context.Context, _ runtime.Object, _ framework.Handle) (framework.Plugin, error) { 356 return &TestBindPlugin{}, nil 357 } 358 359 // TestBindPlugin is a no-op implementation for Bind extension point. 360 type TestBindPlugin struct{} 361 362 func (t TestBindPlugin) Name() string { 363 return bindPlugin 364 } 365 366 func (t TestBindPlugin) Bind(ctx context.Context, state *framework.CycleState, p *v1.Pod, nodeName string) *framework.Status { 367 return nil 368 } 369 370 // nolint:errcheck // Ignore the error returned by Register as before 371 var registry = func() Registry { 372 r := make(Registry) 373 r.Register(scoreWithNormalizePlugin1, newScoreWithNormalizePlugin1) 374 r.Register(scoreWithNormalizePlugin2, newScoreWithNormalizePlugin2) 375 r.Register(scorePlugin1, newScorePlugin1) 376 r.Register(scorePlugin2, newScorePlugin2) 377 r.Register(pluginNotImplementingScore, newPluginNotImplementingScore) 378 r.Register(duplicatePluginName, newDuplicatePlugin) 379 r.Register(testPlugin, newTestPlugin) 380 r.Register(queueSortPlugin, newQueueSortPlugin) 381 r.Register(bindPlugin, newBindPlugin) 382 return r 383 }() 384 385 var defaultWeights = map[string]int32{ 386 scoreWithNormalizePlugin1: 1, 387 scoreWithNormalizePlugin2: 2, 388 scorePlugin1: 1, 389 } 390 391 var state = &framework.CycleState{} 392 393 // Pod is only used for logging errors. 394 var pod = &v1.Pod{} 395 var node = &v1.Node{ 396 ObjectMeta: metav1.ObjectMeta{ 397 Name: nodeName, 398 }, 399 } 400 var lowPriority, highPriority = int32(0), int32(1000) 401 var lowPriorityPod = &v1.Pod{ 402 ObjectMeta: metav1.ObjectMeta{UID: "low"}, 403 Spec: v1.PodSpec{Priority: &lowPriority}, 404 } 405 var highPriorityPod = &v1.Pod{ 406 ObjectMeta: metav1.ObjectMeta{UID: "high"}, 407 Spec: v1.PodSpec{Priority: &highPriority}, 408 } 409 var nodes = []*v1.Node{ 410 {ObjectMeta: metav1.ObjectMeta{Name: "node1"}}, 411 {ObjectMeta: metav1.ObjectMeta{Name: "node2"}}, 412 } 413 414 var ( 415 errInjectedStatus = errors.New(injectReason) 416 errInjectedFilterStatus = errors.New(injectFilterReason) 417 ) 418 419 func newFrameworkWithQueueSortAndBind(ctx context.Context, r Registry, profile config.KubeSchedulerProfile, opts ...Option) (framework.Framework, error) { 420 if _, ok := r[queueSortPlugin]; !ok { 421 r[queueSortPlugin] = newQueueSortPlugin 422 } 423 if _, ok := r[bindPlugin]; !ok { 424 r[bindPlugin] = newBindPlugin 425 } 426 427 if len(profile.Plugins.QueueSort.Enabled) == 0 { 428 profile.Plugins.QueueSort.Enabled = append(profile.Plugins.QueueSort.Enabled, config.Plugin{Name: queueSortPlugin}) 429 } 430 if len(profile.Plugins.Bind.Enabled) == 0 { 431 profile.Plugins.Bind.Enabled = append(profile.Plugins.Bind.Enabled, config.Plugin{Name: bindPlugin}) 432 } 433 return NewFramework(ctx, r, &profile, opts...) 434 } 435 436 func TestInitFrameworkWithScorePlugins(t *testing.T) { 437 tests := []struct { 438 name string 439 plugins *config.Plugins 440 // If initErr is true, we expect framework initialization to fail. 441 initErr bool 442 }{ 443 { 444 name: "enabled Score plugin doesn't exist in registry", 445 plugins: buildScoreConfigDefaultWeights("notExist"), 446 initErr: true, 447 }, 448 { 449 name: "enabled Score plugin doesn't extend the ScorePlugin interface", 450 plugins: buildScoreConfigDefaultWeights(pluginNotImplementingScore), 451 initErr: true, 452 }, 453 { 454 name: "Score plugins are nil", 455 plugins: &config.Plugins{}, 456 }, 457 { 458 name: "enabled Score plugin list is empty", 459 plugins: buildScoreConfigDefaultWeights(), 460 }, 461 { 462 name: "enabled plugin only implements ScorePlugin interface", 463 plugins: buildScoreConfigDefaultWeights(scorePlugin1), 464 }, 465 { 466 name: "enabled plugin implements ScoreWithNormalizePlugin interface", 467 plugins: buildScoreConfigDefaultWeights(scoreWithNormalizePlugin1), 468 }, 469 } 470 471 for _, tt := range tests { 472 t.Run(tt.name, func(t *testing.T) { 473 profile := config.KubeSchedulerProfile{Plugins: tt.plugins} 474 _, ctx := ktesting.NewTestContext(t) 475 ctx, cancel := context.WithCancel(ctx) 476 defer cancel() 477 _, err := newFrameworkWithQueueSortAndBind(ctx, registry, profile) 478 if tt.initErr && err == nil { 479 t.Fatal("Framework initialization should fail") 480 } 481 if !tt.initErr && err != nil { 482 t.Fatalf("Failed to create framework for testing: %v", err) 483 } 484 }) 485 } 486 } 487 488 func TestNewFrameworkErrors(t *testing.T) { 489 tests := []struct { 490 name string 491 plugins *config.Plugins 492 pluginCfg []config.PluginConfig 493 wantErr string 494 }{ 495 { 496 name: "duplicate plugin name", 497 plugins: &config.Plugins{ 498 PreFilter: config.PluginSet{ 499 Enabled: []config.Plugin{ 500 {Name: duplicatePluginName, Weight: 1}, 501 {Name: duplicatePluginName, Weight: 1}, 502 }, 503 }, 504 }, 505 pluginCfg: []config.PluginConfig{ 506 {Name: duplicatePluginName}, 507 }, 508 wantErr: "already registered", 509 }, 510 { 511 name: "duplicate plugin config", 512 plugins: &config.Plugins{ 513 PreFilter: config.PluginSet{ 514 Enabled: []config.Plugin{ 515 {Name: duplicatePluginName, Weight: 1}, 516 }, 517 }, 518 }, 519 pluginCfg: []config.PluginConfig{ 520 {Name: duplicatePluginName}, 521 {Name: duplicatePluginName}, 522 }, 523 wantErr: "repeated config for plugin", 524 }, 525 } 526 527 for _, tc := range tests { 528 t.Run(tc.name, func(t *testing.T) { 529 _, ctx := ktesting.NewTestContext(t) 530 ctx, cancel := context.WithCancel(ctx) 531 defer cancel() 532 profile := &config.KubeSchedulerProfile{ 533 Plugins: tc.plugins, 534 PluginConfig: tc.pluginCfg, 535 } 536 _, err := NewFramework(ctx, registry, profile) 537 if err == nil || !strings.Contains(err.Error(), tc.wantErr) { 538 t.Errorf("Unexpected error, got %v, expect: %s", err, tc.wantErr) 539 } 540 }) 541 } 542 } 543 544 func TestNewFrameworkMultiPointExpansion(t *testing.T) { 545 tests := []struct { 546 name string 547 plugins *config.Plugins 548 wantPlugins *config.Plugins 549 wantErr string 550 }{ 551 { 552 name: "plugin expansion", 553 plugins: &config.Plugins{ 554 MultiPoint: config.PluginSet{ 555 Enabled: []config.Plugin{ 556 {Name: testPlugin, Weight: 5}, 557 }, 558 }, 559 }, 560 wantPlugins: &config.Plugins{ 561 QueueSort: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, 562 PreFilter: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, 563 Filter: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, 564 PostFilter: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, 565 PreScore: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, 566 Score: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin, Weight: 5}}}, 567 Reserve: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, 568 Permit: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, 569 PreBind: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, 570 Bind: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, 571 PostBind: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, 572 }, 573 }, 574 { 575 name: "disable MultiPoint plugin at some extension points", 576 plugins: &config.Plugins{ 577 MultiPoint: config.PluginSet{ 578 Enabled: []config.Plugin{ 579 {Name: testPlugin}, 580 }, 581 }, 582 PreScore: config.PluginSet{ 583 Disabled: []config.Plugin{ 584 {Name: testPlugin}, 585 }, 586 }, 587 Score: config.PluginSet{ 588 Disabled: []config.Plugin{ 589 {Name: testPlugin}, 590 }, 591 }, 592 }, 593 wantPlugins: &config.Plugins{ 594 QueueSort: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, 595 PreFilter: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, 596 Filter: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, 597 PostFilter: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, 598 Reserve: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, 599 Permit: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, 600 PreBind: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, 601 Bind: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, 602 PostBind: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, 603 }, 604 }, 605 { 606 name: "Multiple MultiPoint plugins", 607 plugins: &config.Plugins{ 608 MultiPoint: config.PluginSet{ 609 Enabled: []config.Plugin{ 610 {Name: testPlugin}, 611 {Name: scorePlugin1}, 612 }, 613 }, 614 }, 615 wantPlugins: &config.Plugins{ 616 QueueSort: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, 617 PreFilter: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, 618 Filter: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, 619 PostFilter: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, 620 PreScore: config.PluginSet{Enabled: []config.Plugin{ 621 {Name: testPlugin}, 622 {Name: scorePlugin1}, 623 }}, 624 Score: config.PluginSet{Enabled: []config.Plugin{ 625 {Name: testPlugin, Weight: 1}, 626 {Name: scorePlugin1, Weight: 1}, 627 }}, 628 Reserve: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, 629 Permit: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, 630 PreBind: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, 631 Bind: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, 632 PostBind: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, 633 }, 634 }, 635 { 636 name: "disable MultiPoint extension", 637 plugins: &config.Plugins{ 638 MultiPoint: config.PluginSet{ 639 Enabled: []config.Plugin{ 640 {Name: testPlugin}, 641 {Name: scorePlugin1}, 642 }, 643 }, 644 PreScore: config.PluginSet{ 645 Disabled: []config.Plugin{ 646 {Name: "*"}, 647 }, 648 }, 649 }, 650 wantPlugins: &config.Plugins{ 651 QueueSort: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, 652 PreFilter: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, 653 Filter: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, 654 PostFilter: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, 655 Score: config.PluginSet{Enabled: []config.Plugin{ 656 {Name: testPlugin, Weight: 1}, 657 {Name: scorePlugin1, Weight: 1}, 658 }}, 659 Reserve: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, 660 Permit: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, 661 PreBind: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, 662 Bind: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, 663 PostBind: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, 664 }, 665 }, 666 { 667 name: "Reorder MultiPoint plugins (specified extension takes precedence)", 668 plugins: &config.Plugins{ 669 MultiPoint: config.PluginSet{ 670 Enabled: []config.Plugin{ 671 {Name: scoreWithNormalizePlugin1}, 672 {Name: testPlugin}, 673 {Name: scorePlugin1}, 674 }, 675 }, 676 Score: config.PluginSet{ 677 Enabled: []config.Plugin{ 678 {Name: scorePlugin1}, 679 {Name: testPlugin}, 680 }, 681 }, 682 }, 683 wantPlugins: &config.Plugins{ 684 QueueSort: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, 685 PreFilter: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, 686 Filter: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, 687 PostFilter: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, 688 PreScore: config.PluginSet{Enabled: []config.Plugin{ 689 {Name: testPlugin}, 690 {Name: scorePlugin1}, 691 }}, 692 Score: config.PluginSet{Enabled: []config.Plugin{ 693 {Name: scorePlugin1, Weight: 1}, 694 {Name: testPlugin, Weight: 1}, 695 {Name: scoreWithNormalizePlugin1, Weight: 1}, 696 }}, 697 Reserve: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, 698 Permit: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, 699 PreBind: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, 700 Bind: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, 701 PostBind: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, 702 }, 703 }, 704 { 705 name: "Reorder MultiPoint plugins (specified extension only takes precedence when it exists in MultiPoint)", 706 plugins: &config.Plugins{ 707 MultiPoint: config.PluginSet{ 708 Enabled: []config.Plugin{ 709 {Name: testPlugin}, 710 {Name: scorePlugin1}, 711 }, 712 }, 713 Score: config.PluginSet{ 714 Enabled: []config.Plugin{ 715 {Name: scoreWithNormalizePlugin1}, 716 {Name: scorePlugin1}, 717 {Name: testPlugin}, 718 }, 719 }, 720 }, 721 wantPlugins: &config.Plugins{ 722 QueueSort: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, 723 PreFilter: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, 724 Filter: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, 725 PostFilter: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, 726 PreScore: config.PluginSet{Enabled: []config.Plugin{ 727 {Name: testPlugin}, 728 {Name: scorePlugin1}, 729 }}, 730 Score: config.PluginSet{Enabled: []config.Plugin{ 731 {Name: scorePlugin1, Weight: 1}, 732 {Name: testPlugin, Weight: 1}, 733 {Name: scoreWithNormalizePlugin1, Weight: 1}, 734 }}, 735 Reserve: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, 736 Permit: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, 737 PreBind: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, 738 Bind: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, 739 PostBind: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, 740 }, 741 }, 742 { 743 name: "Override MultiPoint plugins weights", 744 plugins: &config.Plugins{ 745 MultiPoint: config.PluginSet{ 746 Enabled: []config.Plugin{ 747 {Name: testPlugin}, 748 {Name: scorePlugin1}, 749 }, 750 }, 751 Score: config.PluginSet{ 752 Enabled: []config.Plugin{ 753 {Name: scorePlugin1, Weight: 5}, 754 {Name: testPlugin, Weight: 3}, 755 }, 756 }, 757 }, 758 wantPlugins: &config.Plugins{ 759 QueueSort: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, 760 PreFilter: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, 761 Filter: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, 762 PostFilter: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, 763 PreScore: config.PluginSet{Enabled: []config.Plugin{ 764 {Name: testPlugin}, 765 {Name: scorePlugin1}, 766 }}, 767 Score: config.PluginSet{Enabled: []config.Plugin{ 768 {Name: scorePlugin1, Weight: 5}, 769 {Name: testPlugin, Weight: 3}, 770 }}, 771 Reserve: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, 772 Permit: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, 773 PreBind: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, 774 Bind: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, 775 PostBind: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, 776 }, 777 }, 778 { 779 name: "disable and enable MultiPoint plugins with '*'", 780 plugins: &config.Plugins{ 781 MultiPoint: config.PluginSet{ 782 Enabled: []config.Plugin{ 783 {Name: queueSortPlugin}, 784 {Name: bindPlugin}, 785 {Name: scorePlugin1}, 786 }, 787 Disabled: []config.Plugin{ 788 {Name: "*"}, 789 }, 790 }, 791 }, 792 wantPlugins: &config.Plugins{ 793 QueueSort: config.PluginSet{Enabled: []config.Plugin{{Name: queueSortPlugin}}}, 794 PreScore: config.PluginSet{Enabled: []config.Plugin{ 795 {Name: scorePlugin1}, 796 }}, 797 Score: config.PluginSet{Enabled: []config.Plugin{ 798 {Name: scorePlugin1, Weight: 1}, 799 }}, 800 Bind: config.PluginSet{Enabled: []config.Plugin{{Name: bindPlugin}}}, 801 }, 802 }, 803 { 804 name: "disable and enable MultiPoint plugin by name", 805 plugins: &config.Plugins{ 806 MultiPoint: config.PluginSet{ 807 Enabled: []config.Plugin{ 808 {Name: bindPlugin}, 809 {Name: queueSortPlugin}, 810 {Name: scorePlugin1}, 811 }, 812 Disabled: []config.Plugin{ 813 {Name: scorePlugin1}, 814 }, 815 }, 816 }, 817 wantPlugins: &config.Plugins{ 818 QueueSort: config.PluginSet{Enabled: []config.Plugin{{Name: queueSortPlugin}}}, 819 PreScore: config.PluginSet{Enabled: []config.Plugin{ 820 {Name: scorePlugin1}, 821 }}, 822 Score: config.PluginSet{Enabled: []config.Plugin{ 823 {Name: scorePlugin1, Weight: 1}, 824 }}, 825 Bind: config.PluginSet{Enabled: []config.Plugin{{Name: bindPlugin}}}, 826 }, 827 }, 828 { 829 name: "Expect 'already registered' error", 830 plugins: &config.Plugins{ 831 MultiPoint: config.PluginSet{ 832 Enabled: []config.Plugin{ 833 {Name: testPlugin}, 834 {Name: testPlugin}, 835 }, 836 }, 837 }, 838 wantErr: "already registered", 839 }, 840 { 841 name: "Override MultiPoint plugins weights and avoid a plugin being loaded multiple times", 842 plugins: &config.Plugins{ 843 MultiPoint: config.PluginSet{ 844 Enabled: []config.Plugin{ 845 {Name: testPlugin}, 846 {Name: scorePlugin1}, 847 }, 848 }, 849 Score: config.PluginSet{ 850 Enabled: []config.Plugin{ 851 {Name: scorePlugin1, Weight: 5}, 852 {Name: scorePlugin2, Weight: 5}, 853 {Name: testPlugin, Weight: 3}, 854 }, 855 }, 856 }, 857 wantPlugins: &config.Plugins{ 858 QueueSort: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, 859 PreFilter: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, 860 Filter: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, 861 PostFilter: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, 862 PreScore: config.PluginSet{Enabled: []config.Plugin{ 863 {Name: testPlugin}, 864 {Name: scorePlugin1}, 865 }}, 866 Score: config.PluginSet{Enabled: []config.Plugin{ 867 {Name: scorePlugin1, Weight: 5}, 868 {Name: testPlugin, Weight: 3}, 869 {Name: scorePlugin2, Weight: 5}, 870 }}, 871 Reserve: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, 872 Permit: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, 873 PreBind: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, 874 Bind: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, 875 PostBind: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin}}}, 876 }, 877 }, 878 } 879 880 for _, tc := range tests { 881 t.Run(tc.name, func(t *testing.T) { 882 _, ctx := ktesting.NewTestContext(t) 883 ctx, cancel := context.WithCancel(ctx) 884 defer cancel() 885 fw, err := NewFramework(ctx, registry, &config.KubeSchedulerProfile{Plugins: tc.plugins}) 886 if err != nil { 887 if tc.wantErr == "" || !strings.Contains(err.Error(), tc.wantErr) { 888 t.Fatalf("Unexpected error, got %v, expect: %s", err, tc.wantErr) 889 } 890 } else { 891 if tc.wantErr != "" { 892 t.Fatalf("Unexpected error, got %v, expect: %s", err, tc.wantErr) 893 } 894 } 895 896 if tc.wantErr == "" { 897 if diff := cmp.Diff(tc.wantPlugins, fw.ListPlugins()); diff != "" { 898 t.Fatalf("Unexpected eventToPlugin map (-want,+got):%s", diff) 899 } 900 } 901 }) 902 } 903 } 904 905 func TestPreEnqueuePlugins(t *testing.T) { 906 tests := []struct { 907 name string 908 plugins []framework.Plugin 909 want []framework.PreEnqueuePlugin 910 }{ 911 { 912 name: "no PreEnqueuePlugin registered", 913 }, 914 { 915 name: "one PreEnqueuePlugin registered", 916 plugins: []framework.Plugin{ 917 &TestPreEnqueuePlugin{}, 918 }, 919 want: []framework.PreEnqueuePlugin{ 920 &TestPreEnqueuePlugin{}, 921 }, 922 }, 923 } 924 925 for _, tt := range tests { 926 t.Run(tt.name, func(t *testing.T) { 927 registry := Registry{} 928 cfgPls := &config.Plugins{} 929 for _, pl := range tt.plugins { 930 // register all plugins 931 tmpPl := pl 932 if err := registry.Register(pl.Name(), 933 func(_ context.Context, _ runtime.Object, _ framework.Handle) (framework.Plugin, error) { 934 return tmpPl, nil 935 }); err != nil { 936 t.Fatalf("fail to register preEnqueue plugin (%s)", pl.Name()) 937 } 938 // append plugins to filter pluginset 939 cfgPls.PreEnqueue.Enabled = append( 940 cfgPls.PreEnqueue.Enabled, 941 config.Plugin{Name: pl.Name()}, 942 ) 943 } 944 profile := config.KubeSchedulerProfile{Plugins: cfgPls} 945 ctx, cancel := context.WithCancel(context.Background()) 946 defer cancel() 947 f, err := newFrameworkWithQueueSortAndBind(ctx, registry, profile) 948 if err != nil { 949 t.Fatalf("fail to create framework: %s", err) 950 } 951 952 got := f.PreEnqueuePlugins() 953 if !reflect.DeepEqual(got, tt.want) { 954 t.Errorf("PreEnqueuePlugins(): want %v, but got %v", tt.want, got) 955 } 956 }) 957 } 958 } 959 960 func TestRunPreScorePlugins(t *testing.T) { 961 tests := []struct { 962 name string 963 plugins []*TestPlugin 964 wantSkippedPlugins sets.Set[string] 965 wantStatusCode framework.Code 966 }{ 967 { 968 name: "all PreScorePlugins returned success", 969 plugins: []*TestPlugin{ 970 { 971 name: "success1", 972 }, 973 { 974 name: "success2", 975 }, 976 }, 977 wantStatusCode: framework.Success, 978 }, 979 { 980 name: "one PreScore plugin returned success, but another PreScore plugin returned non-success", 981 plugins: []*TestPlugin{ 982 { 983 name: "success", 984 }, 985 { 986 name: "error", 987 inj: injectedResult{PreScoreStatus: int(framework.Error)}, 988 }, 989 }, 990 wantStatusCode: framework.Error, 991 }, 992 { 993 name: "one PreScore plugin returned skip, but another PreScore plugin returned non-success", 994 plugins: []*TestPlugin{ 995 { 996 name: "skip", 997 inj: injectedResult{PreScoreStatus: int(framework.Skip)}, 998 }, 999 { 1000 name: "error", 1001 inj: injectedResult{PreScoreStatus: int(framework.Error)}, 1002 }, 1003 }, 1004 wantSkippedPlugins: sets.New("skip"), 1005 wantStatusCode: framework.Error, 1006 }, 1007 { 1008 name: "all PreScore plugins returned skip", 1009 plugins: []*TestPlugin{ 1010 { 1011 name: "skip1", 1012 inj: injectedResult{PreScoreStatus: int(framework.Skip)}, 1013 }, 1014 { 1015 name: "skip2", 1016 inj: injectedResult{PreScoreStatus: int(framework.Skip)}, 1017 }, 1018 { 1019 name: "skip3", 1020 inj: injectedResult{PreScoreStatus: int(framework.Skip)}, 1021 }, 1022 }, 1023 wantSkippedPlugins: sets.New("skip1", "skip2", "skip3"), 1024 wantStatusCode: framework.Success, 1025 }, 1026 { 1027 name: "some PreScore plugins returned skip", 1028 plugins: []*TestPlugin{ 1029 { 1030 name: "skip1", 1031 inj: injectedResult{PreScoreStatus: int(framework.Skip)}, 1032 }, 1033 { 1034 name: "success1", 1035 }, 1036 { 1037 name: "skip2", 1038 inj: injectedResult{PreScoreStatus: int(framework.Skip)}, 1039 }, 1040 { 1041 name: "success2", 1042 }, 1043 }, 1044 wantSkippedPlugins: sets.New("skip1", "skip2"), 1045 wantStatusCode: framework.Success, 1046 }, 1047 } 1048 1049 for _, tt := range tests { 1050 t.Run(tt.name, func(t *testing.T) { 1051 r := make(Registry) 1052 enabled := make([]config.Plugin, len(tt.plugins)) 1053 for i, p := range tt.plugins { 1054 p := p 1055 enabled[i].Name = p.name 1056 if err := r.Register(p.name, func(_ context.Context, _ runtime.Object, fh framework.Handle) (framework.Plugin, error) { 1057 return p, nil 1058 }); err != nil { 1059 t.Fatalf("fail to register PreScorePlugins plugin (%s)", p.Name()) 1060 } 1061 } 1062 1063 ctx, cancel := context.WithCancel(context.Background()) 1064 defer cancel() 1065 1066 f, err := newFrameworkWithQueueSortAndBind( 1067 ctx, 1068 r, 1069 config.KubeSchedulerProfile{Plugins: &config.Plugins{PreScore: config.PluginSet{Enabled: enabled}}}, 1070 ) 1071 if err != nil { 1072 t.Fatalf("Failed to create framework for testing: %v", err) 1073 } 1074 1075 state := framework.NewCycleState() 1076 status := f.RunPreScorePlugins(ctx, state, nil, nil) 1077 if status.Code() != tt.wantStatusCode { 1078 t.Errorf("wrong status code. got: %v, want: %v", status, tt.wantStatusCode) 1079 } 1080 skipped := state.SkipScorePlugins 1081 if d := cmp.Diff(skipped, tt.wantSkippedPlugins); d != "" { 1082 t.Errorf("wrong skip score plugins. got: %v, want: %v, diff: %s", skipped, tt.wantSkippedPlugins, d) 1083 } 1084 }) 1085 } 1086 } 1087 1088 func TestRunScorePlugins(t *testing.T) { 1089 tests := []struct { 1090 name string 1091 registry Registry 1092 plugins *config.Plugins 1093 pluginConfigs []config.PluginConfig 1094 want []framework.NodePluginScores 1095 skippedPlugins sets.Set[string] 1096 // If err is true, we expect RunScorePlugin to fail. 1097 err bool 1098 }{ 1099 { 1100 name: "no Score plugins", 1101 plugins: buildScoreConfigDefaultWeights(), 1102 want: []framework.NodePluginScores{ 1103 { 1104 Name: "node1", 1105 Scores: []framework.PluginScore{}, 1106 }, 1107 { 1108 Name: "node2", 1109 Scores: []framework.PluginScore{}, 1110 }, 1111 }, 1112 }, 1113 { 1114 name: "single Score plugin", 1115 plugins: buildScoreConfigDefaultWeights(scorePlugin1), 1116 pluginConfigs: []config.PluginConfig{ 1117 { 1118 Name: scorePlugin1, 1119 Args: &runtime.Unknown{ 1120 Raw: []byte(`{ "scoreRes": 1 }`), 1121 }, 1122 }, 1123 }, 1124 // scorePlugin1 Score returns 1, weight=1, so want=1. 1125 want: []framework.NodePluginScores{ 1126 { 1127 Name: "node1", 1128 Scores: []framework.PluginScore{ 1129 { 1130 Name: scorePlugin1, 1131 Score: 1, 1132 }, 1133 }, 1134 TotalScore: 1, 1135 }, 1136 { 1137 Name: "node2", 1138 Scores: []framework.PluginScore{ 1139 { 1140 Name: scorePlugin1, 1141 Score: 1, 1142 }, 1143 }, 1144 TotalScore: 1, 1145 }, 1146 }, 1147 }, 1148 { 1149 name: "single ScoreWithNormalize plugin", 1150 // registry: registry, 1151 plugins: buildScoreConfigDefaultWeights(scoreWithNormalizePlugin1), 1152 pluginConfigs: []config.PluginConfig{ 1153 { 1154 Name: scoreWithNormalizePlugin1, 1155 Args: &runtime.Unknown{ 1156 Raw: []byte(`{ "scoreRes": 10, "normalizeRes": 5 }`), 1157 }, 1158 }, 1159 }, 1160 // scoreWithNormalizePlugin1 Score returns 10, but NormalizeScore overrides to 5, weight=1, so want=5 1161 want: []framework.NodePluginScores{ 1162 { 1163 Name: "node1", 1164 Scores: []framework.PluginScore{ 1165 { 1166 Name: scoreWithNormalizePlugin1, 1167 Score: 5, 1168 }, 1169 }, 1170 TotalScore: 5, 1171 }, 1172 { 1173 Name: "node2", 1174 Scores: []framework.PluginScore{ 1175 { 1176 Name: scoreWithNormalizePlugin1, 1177 Score: 5, 1178 }, 1179 }, 1180 TotalScore: 5, 1181 }, 1182 }, 1183 }, 1184 { 1185 name: "3 Score plugins, 2 NormalizeScore plugins", 1186 plugins: buildScoreConfigDefaultWeights(scorePlugin1, scoreWithNormalizePlugin1, scoreWithNormalizePlugin2), 1187 pluginConfigs: []config.PluginConfig{ 1188 { 1189 Name: scorePlugin1, 1190 Args: &runtime.Unknown{ 1191 Raw: []byte(`{ "scoreRes": 1 }`), 1192 }, 1193 }, 1194 { 1195 Name: scoreWithNormalizePlugin1, 1196 Args: &runtime.Unknown{ 1197 Raw: []byte(`{ "scoreRes": 3, "normalizeRes": 4}`), 1198 }, 1199 }, 1200 { 1201 Name: scoreWithNormalizePlugin2, 1202 Args: &runtime.Unknown{ 1203 Raw: []byte(`{ "scoreRes": 4, "normalizeRes": 5}`), 1204 }, 1205 }, 1206 }, 1207 // scorePlugin1 Score returns 1, weight =1, so want=1. 1208 // scoreWithNormalizePlugin1 Score returns 3, but NormalizeScore overrides to 4, weight=1, so want=4. 1209 // scoreWithNormalizePlugin2 Score returns 4, but NormalizeScore overrides to 5, weight=2, so want=10. 1210 want: []framework.NodePluginScores{ 1211 { 1212 Name: "node1", 1213 Scores: []framework.PluginScore{ 1214 { 1215 Name: scorePlugin1, 1216 Score: 1, 1217 }, 1218 { 1219 Name: scoreWithNormalizePlugin1, 1220 Score: 4, 1221 }, 1222 { 1223 Name: scoreWithNormalizePlugin2, 1224 Score: 10, 1225 }, 1226 }, 1227 TotalScore: 15, 1228 }, 1229 { 1230 Name: "node2", 1231 Scores: []framework.PluginScore{ 1232 { 1233 Name: scorePlugin1, 1234 Score: 1, 1235 }, 1236 { 1237 Name: scoreWithNormalizePlugin1, 1238 Score: 4, 1239 }, 1240 { 1241 Name: scoreWithNormalizePlugin2, 1242 Score: 10, 1243 }, 1244 }, 1245 TotalScore: 15, 1246 }, 1247 }, 1248 }, 1249 { 1250 name: "score fails", 1251 pluginConfigs: []config.PluginConfig{ 1252 { 1253 Name: scoreWithNormalizePlugin1, 1254 Args: &runtime.Unknown{ 1255 Raw: []byte(`{ "scoreStatus": 1 }`), 1256 }, 1257 }, 1258 }, 1259 plugins: buildScoreConfigDefaultWeights(scorePlugin1, scoreWithNormalizePlugin1), 1260 err: true, 1261 }, 1262 { 1263 name: "normalize fails", 1264 pluginConfigs: []config.PluginConfig{ 1265 { 1266 Name: scoreWithNormalizePlugin1, 1267 Args: &runtime.Unknown{ 1268 Raw: []byte(`{ "normalizeStatus": 1 }`), 1269 }, 1270 }, 1271 }, 1272 plugins: buildScoreConfigDefaultWeights(scorePlugin1, scoreWithNormalizePlugin1), 1273 err: true, 1274 }, 1275 { 1276 name: "Score plugin return score greater than MaxNodeScore", 1277 plugins: buildScoreConfigDefaultWeights(scorePlugin1), 1278 pluginConfigs: []config.PluginConfig{ 1279 { 1280 Name: scorePlugin1, 1281 Args: &runtime.Unknown{ 1282 Raw: []byte(fmt.Sprintf(`{ "scoreRes": %d }`, framework.MaxNodeScore+1)), 1283 }, 1284 }, 1285 }, 1286 err: true, 1287 }, 1288 { 1289 name: "Score plugin return score less than MinNodeScore", 1290 plugins: buildScoreConfigDefaultWeights(scorePlugin1), 1291 pluginConfigs: []config.PluginConfig{ 1292 { 1293 Name: scorePlugin1, 1294 Args: &runtime.Unknown{ 1295 Raw: []byte(fmt.Sprintf(`{ "scoreRes": %d }`, framework.MinNodeScore-1)), 1296 }, 1297 }, 1298 }, 1299 err: true, 1300 }, 1301 { 1302 name: "ScoreWithNormalize plugin return score greater than MaxNodeScore", 1303 plugins: buildScoreConfigDefaultWeights(scoreWithNormalizePlugin1), 1304 pluginConfigs: []config.PluginConfig{ 1305 { 1306 Name: scoreWithNormalizePlugin1, 1307 Args: &runtime.Unknown{ 1308 Raw: []byte(fmt.Sprintf(`{ "normalizeRes": %d }`, framework.MaxNodeScore+1)), 1309 }, 1310 }, 1311 }, 1312 err: true, 1313 }, 1314 { 1315 name: "ScoreWithNormalize plugin return score less than MinNodeScore", 1316 plugins: buildScoreConfigDefaultWeights(scoreWithNormalizePlugin1), 1317 pluginConfigs: []config.PluginConfig{ 1318 { 1319 Name: scoreWithNormalizePlugin1, 1320 Args: &runtime.Unknown{ 1321 Raw: []byte(fmt.Sprintf(`{ "normalizeRes": %d }`, framework.MinNodeScore-1)), 1322 }, 1323 }, 1324 }, 1325 err: true, 1326 }, 1327 { 1328 name: "single Score plugin with MultiPointExpansion", 1329 plugins: &config.Plugins{ 1330 MultiPoint: config.PluginSet{ 1331 Enabled: []config.Plugin{ 1332 {Name: scorePlugin1}, 1333 }, 1334 }, 1335 Score: config.PluginSet{ 1336 Enabled: []config.Plugin{ 1337 {Name: scorePlugin1, Weight: 3}, 1338 }, 1339 }, 1340 }, 1341 pluginConfigs: []config.PluginConfig{ 1342 { 1343 Name: scorePlugin1, 1344 Args: &runtime.Unknown{ 1345 Raw: []byte(`{ "scoreRes": 1 }`), 1346 }, 1347 }, 1348 }, 1349 // scorePlugin1 Score returns 1, weight=3, so want=3. 1350 want: []framework.NodePluginScores{ 1351 { 1352 Name: "node1", 1353 Scores: []framework.PluginScore{ 1354 { 1355 Name: scorePlugin1, 1356 Score: 3, 1357 }, 1358 }, 1359 TotalScore: 3, 1360 }, 1361 { 1362 Name: "node2", 1363 Scores: []framework.PluginScore{ 1364 { 1365 Name: scorePlugin1, 1366 Score: 3, 1367 }, 1368 }, 1369 TotalScore: 3, 1370 }, 1371 }, 1372 }, 1373 { 1374 name: "one success plugin, one skip plugin", 1375 plugins: buildScoreConfigDefaultWeights(scorePlugin1, scoreWithNormalizePlugin1), 1376 pluginConfigs: []config.PluginConfig{ 1377 { 1378 Name: scorePlugin1, 1379 Args: &runtime.Unknown{ 1380 Raw: []byte(`{ "scoreRes": 1 }`), 1381 }, 1382 }, 1383 { 1384 Name: scoreWithNormalizePlugin1, 1385 Args: &runtime.Unknown{ 1386 Raw: []byte(`{ "scoreStatus": 1 }`), // To make sure this plugin isn't called, set error as an injected result. 1387 }, 1388 }, 1389 }, 1390 skippedPlugins: sets.New(scoreWithNormalizePlugin1), 1391 want: []framework.NodePluginScores{ 1392 { 1393 Name: "node1", 1394 Scores: []framework.PluginScore{ 1395 { 1396 Name: scorePlugin1, 1397 Score: 1, 1398 }, 1399 }, 1400 TotalScore: 1, 1401 }, 1402 { 1403 Name: "node2", 1404 Scores: []framework.PluginScore{ 1405 { 1406 Name: scorePlugin1, 1407 Score: 1, 1408 }, 1409 }, 1410 TotalScore: 1, 1411 }, 1412 }, 1413 }, 1414 { 1415 name: "all plugins are skipped in prescore", 1416 plugins: buildScoreConfigDefaultWeights(scorePlugin1), 1417 pluginConfigs: []config.PluginConfig{ 1418 { 1419 Name: scorePlugin1, 1420 Args: &runtime.Unknown{ 1421 Raw: []byte(`{ "scoreStatus": 1 }`), // To make sure this plugin isn't called, set error as an injected result. 1422 }, 1423 }, 1424 }, 1425 skippedPlugins: sets.New(scorePlugin1), 1426 want: []framework.NodePluginScores{ 1427 { 1428 Name: "node1", 1429 Scores: []framework.PluginScore{}, 1430 }, 1431 { 1432 Name: "node2", 1433 Scores: []framework.PluginScore{}, 1434 }, 1435 }, 1436 }, 1437 { 1438 name: "skipped prescore plugin number greater than the number of score plugins", 1439 plugins: buildScoreConfigDefaultWeights(scorePlugin1), 1440 pluginConfigs: nil, 1441 skippedPlugins: sets.New(scorePlugin1, "score-plugin-unknown"), 1442 want: []framework.NodePluginScores{ 1443 { 1444 Name: "node1", 1445 Scores: []framework.PluginScore{}, 1446 }, 1447 { 1448 Name: "node2", 1449 Scores: []framework.PluginScore{}, 1450 }, 1451 }, 1452 }, 1453 } 1454 1455 for _, tt := range tests { 1456 t.Run(tt.name, func(t *testing.T) { 1457 // Inject the results via Args in PluginConfig. 1458 profile := config.KubeSchedulerProfile{ 1459 Plugins: tt.plugins, 1460 PluginConfig: tt.pluginConfigs, 1461 } 1462 ctx, cancel := context.WithCancel(context.Background()) 1463 defer cancel() 1464 f, err := newFrameworkWithQueueSortAndBind(ctx, registry, profile) 1465 if err != nil { 1466 t.Fatalf("Failed to create framework for testing: %v", err) 1467 } 1468 1469 state := framework.NewCycleState() 1470 state.SkipScorePlugins = tt.skippedPlugins 1471 res, status := f.RunScorePlugins(ctx, state, pod, nodes) 1472 1473 if tt.err { 1474 if status.IsSuccess() { 1475 t.Errorf("Expected status to be non-success. got: %v", status.Code().String()) 1476 } 1477 return 1478 } 1479 1480 if !status.IsSuccess() { 1481 t.Errorf("Expected status to be success.") 1482 } 1483 if !reflect.DeepEqual(res, tt.want) { 1484 t.Errorf("Score map after RunScorePlugin. got: %+v, want: %+v.", res, tt.want) 1485 } 1486 }) 1487 } 1488 } 1489 1490 func TestPreFilterPlugins(t *testing.T) { 1491 preFilter1 := &TestPreFilterPlugin{} 1492 preFilter2 := &TestPreFilterWithExtensionsPlugin{} 1493 r := make(Registry) 1494 r.Register(preFilterPluginName, 1495 func(_ context.Context, _ runtime.Object, fh framework.Handle) (framework.Plugin, error) { 1496 return preFilter1, nil 1497 }) 1498 r.Register(preFilterWithExtensionsPluginName, 1499 func(_ context.Context, _ runtime.Object, fh framework.Handle) (framework.Plugin, error) { 1500 return preFilter2, nil 1501 }) 1502 plugins := &config.Plugins{PreFilter: config.PluginSet{Enabled: []config.Plugin{{Name: preFilterWithExtensionsPluginName}, {Name: preFilterPluginName}}}} 1503 t.Run("TestPreFilterPlugin", func(t *testing.T) { 1504 profile := config.KubeSchedulerProfile{Plugins: plugins} 1505 ctx, cancel := context.WithCancel(context.Background()) 1506 defer cancel() 1507 1508 f, err := newFrameworkWithQueueSortAndBind(ctx, r, profile) 1509 if err != nil { 1510 t.Fatalf("Failed to create framework for testing: %v", err) 1511 } 1512 state := framework.NewCycleState() 1513 1514 f.RunPreFilterPlugins(ctx, state, nil) 1515 f.RunPreFilterExtensionAddPod(ctx, state, nil, nil, nil) 1516 f.RunPreFilterExtensionRemovePod(ctx, state, nil, nil, nil) 1517 1518 if preFilter1.PreFilterCalled != 1 { 1519 t.Errorf("preFilter1 called %v, expected: 1", preFilter1.PreFilterCalled) 1520 } 1521 if preFilter2.PreFilterCalled != 1 { 1522 t.Errorf("preFilter2 called %v, expected: 1", preFilter2.PreFilterCalled) 1523 } 1524 if preFilter2.AddCalled != 1 { 1525 t.Errorf("AddPod called %v, expected: 1", preFilter2.AddCalled) 1526 } 1527 if preFilter2.RemoveCalled != 1 { 1528 t.Errorf("AddPod called %v, expected: 1", preFilter2.RemoveCalled) 1529 } 1530 }) 1531 } 1532 1533 func TestRunPreFilterPlugins(t *testing.T) { 1534 tests := []struct { 1535 name string 1536 plugins []*TestPlugin 1537 wantPreFilterResult *framework.PreFilterResult 1538 wantSkippedPlugins sets.Set[string] 1539 wantStatusCode framework.Code 1540 }{ 1541 { 1542 name: "all PreFilter returned success", 1543 plugins: []*TestPlugin{ 1544 { 1545 name: "success1", 1546 }, 1547 { 1548 name: "success2", 1549 }, 1550 }, 1551 wantPreFilterResult: nil, 1552 wantStatusCode: framework.Success, 1553 }, 1554 { 1555 name: "one PreFilter plugin returned success, but another PreFilter plugin returned non-success", 1556 plugins: []*TestPlugin{ 1557 { 1558 name: "success", 1559 }, 1560 { 1561 name: "error", 1562 inj: injectedResult{PreFilterStatus: int(framework.Error)}, 1563 }, 1564 }, 1565 wantPreFilterResult: nil, 1566 wantStatusCode: framework.Error, 1567 }, 1568 { 1569 name: "one PreFilter plugin returned skip, but another PreFilter plugin returned non-success", 1570 plugins: []*TestPlugin{ 1571 { 1572 name: "skip", 1573 inj: injectedResult{PreFilterStatus: int(framework.Skip)}, 1574 }, 1575 { 1576 name: "error", 1577 inj: injectedResult{PreFilterStatus: int(framework.Error)}, 1578 }, 1579 }, 1580 wantSkippedPlugins: sets.New("skip"), 1581 wantStatusCode: framework.Error, 1582 }, 1583 { 1584 name: "all PreFilter plugins returned skip", 1585 plugins: []*TestPlugin{ 1586 { 1587 name: "skip1", 1588 inj: injectedResult{PreFilterStatus: int(framework.Skip)}, 1589 }, 1590 { 1591 name: "skip2", 1592 inj: injectedResult{PreFilterStatus: int(framework.Skip)}, 1593 }, 1594 { 1595 name: "skip3", 1596 inj: injectedResult{PreFilterStatus: int(framework.Skip)}, 1597 }, 1598 }, 1599 wantPreFilterResult: nil, 1600 wantSkippedPlugins: sets.New("skip1", "skip2", "skip3"), 1601 wantStatusCode: framework.Success, 1602 }, 1603 { 1604 name: "some PreFilter plugins returned skip", 1605 plugins: []*TestPlugin{ 1606 { 1607 name: "skip1", 1608 inj: injectedResult{PreFilterStatus: int(framework.Skip)}, 1609 }, 1610 { 1611 name: "success1", 1612 }, 1613 { 1614 name: "skip2", 1615 inj: injectedResult{PreFilterStatus: int(framework.Skip)}, 1616 }, 1617 { 1618 name: "success2", 1619 }, 1620 }, 1621 wantPreFilterResult: nil, 1622 wantSkippedPlugins: sets.New("skip1", "skip2"), 1623 wantStatusCode: framework.Success, 1624 }, 1625 } 1626 for _, tt := range tests { 1627 t.Run(tt.name, func(t *testing.T) { 1628 r := make(Registry) 1629 enabled := make([]config.Plugin, len(tt.plugins)) 1630 for i, p := range tt.plugins { 1631 p := p 1632 enabled[i].Name = p.name 1633 if err := r.Register(p.name, func(_ context.Context, _ runtime.Object, fh framework.Handle) (framework.Plugin, error) { 1634 return p, nil 1635 }); err != nil { 1636 t.Fatalf("fail to register PreFilter plugin (%s)", p.Name()) 1637 } 1638 } 1639 1640 ctx, cancel := context.WithCancel(context.Background()) 1641 defer cancel() 1642 1643 f, err := newFrameworkWithQueueSortAndBind( 1644 ctx, 1645 r, 1646 config.KubeSchedulerProfile{Plugins: &config.Plugins{PreFilter: config.PluginSet{Enabled: enabled}}}, 1647 ) 1648 if err != nil { 1649 t.Fatalf("Failed to create framework for testing: %v", err) 1650 } 1651 1652 state := framework.NewCycleState() 1653 result, status := f.RunPreFilterPlugins(ctx, state, nil) 1654 if d := cmp.Diff(result, tt.wantPreFilterResult); d != "" { 1655 t.Errorf("wrong status. got: %v, want: %v, diff: %s", result, tt.wantPreFilterResult, d) 1656 } 1657 if status.Code() != tt.wantStatusCode { 1658 t.Errorf("wrong status code. got: %v, want: %v", status, tt.wantStatusCode) 1659 } 1660 skipped := state.SkipFilterPlugins 1661 if d := cmp.Diff(skipped, tt.wantSkippedPlugins); d != "" { 1662 t.Errorf("wrong skip filter plugins. got: %v, want: %v, diff: %s", skipped, tt.wantSkippedPlugins, d) 1663 } 1664 }) 1665 } 1666 } 1667 1668 func TestRunPreFilterExtensionRemovePod(t *testing.T) { 1669 tests := []struct { 1670 name string 1671 plugins []*TestPlugin 1672 skippedPluginNames sets.Set[string] 1673 wantStatusCode framework.Code 1674 }{ 1675 { 1676 name: "no plugins are skipped and all RemovePod() returned success", 1677 plugins: []*TestPlugin{ 1678 { 1679 name: "success1", 1680 }, 1681 { 1682 name: "success2", 1683 }, 1684 }, 1685 wantStatusCode: framework.Success, 1686 }, 1687 { 1688 name: "one RemovePod() returned error", 1689 plugins: []*TestPlugin{ 1690 { 1691 name: "success1", 1692 }, 1693 { 1694 name: "error1", 1695 inj: injectedResult{PreFilterRemovePodStatus: int(framework.Error)}, 1696 }, 1697 }, 1698 wantStatusCode: framework.Error, 1699 }, 1700 { 1701 name: "one RemovePod() is skipped", 1702 plugins: []*TestPlugin{ 1703 { 1704 name: "success1", 1705 }, 1706 { 1707 name: "skipped", 1708 // To confirm it's skipped, return error so that this test case will fail when it isn't skipped. 1709 inj: injectedResult{PreFilterRemovePodStatus: int(framework.Error)}, 1710 }, 1711 }, 1712 skippedPluginNames: sets.New("skipped"), 1713 wantStatusCode: framework.Success, 1714 }, 1715 } 1716 for _, tt := range tests { 1717 t.Run(tt.name, func(t *testing.T) { 1718 r := make(Registry) 1719 enabled := make([]config.Plugin, len(tt.plugins)) 1720 for i, p := range tt.plugins { 1721 p := p 1722 enabled[i].Name = p.name 1723 if err := r.Register(p.name, func(_ context.Context, _ runtime.Object, fh framework.Handle) (framework.Plugin, error) { 1724 return p, nil 1725 }); err != nil { 1726 t.Fatalf("fail to register PreFilterExtension plugin (%s)", p.Name()) 1727 } 1728 } 1729 1730 ctx, cancel := context.WithCancel(context.Background()) 1731 defer cancel() 1732 1733 f, err := newFrameworkWithQueueSortAndBind( 1734 ctx, 1735 r, 1736 config.KubeSchedulerProfile{Plugins: &config.Plugins{PreFilter: config.PluginSet{Enabled: enabled}}}, 1737 ) 1738 if err != nil { 1739 t.Fatalf("Failed to create framework for testing: %v", err) 1740 } 1741 1742 state := framework.NewCycleState() 1743 state.SkipFilterPlugins = tt.skippedPluginNames 1744 status := f.RunPreFilterExtensionRemovePod(ctx, state, nil, nil, nil) 1745 if status.Code() != tt.wantStatusCode { 1746 t.Errorf("wrong status code. got: %v, want: %v", status, tt.wantStatusCode) 1747 } 1748 }) 1749 } 1750 } 1751 1752 func TestRunPreFilterExtensionAddPod(t *testing.T) { 1753 tests := []struct { 1754 name string 1755 plugins []*TestPlugin 1756 skippedPluginNames sets.Set[string] 1757 wantStatusCode framework.Code 1758 }{ 1759 { 1760 name: "no plugins are skipped and all AddPod() returned success", 1761 plugins: []*TestPlugin{ 1762 { 1763 name: "success1", 1764 }, 1765 { 1766 name: "success2", 1767 }, 1768 }, 1769 wantStatusCode: framework.Success, 1770 }, 1771 { 1772 name: "one AddPod() returned error", 1773 plugins: []*TestPlugin{ 1774 { 1775 name: "success1", 1776 }, 1777 { 1778 name: "error1", 1779 inj: injectedResult{PreFilterAddPodStatus: int(framework.Error)}, 1780 }, 1781 }, 1782 wantStatusCode: framework.Error, 1783 }, 1784 { 1785 name: "one AddPod() is skipped", 1786 plugins: []*TestPlugin{ 1787 { 1788 name: "success1", 1789 }, 1790 { 1791 name: "skipped", 1792 // To confirm it's skipped, return error so that this test case will fail when it isn't skipped. 1793 inj: injectedResult{PreFilterAddPodStatus: int(framework.Error)}, 1794 }, 1795 }, 1796 skippedPluginNames: sets.New("skipped"), 1797 wantStatusCode: framework.Success, 1798 }, 1799 } 1800 for _, tt := range tests { 1801 t.Run(tt.name, func(t *testing.T) { 1802 r := make(Registry) 1803 enabled := make([]config.Plugin, len(tt.plugins)) 1804 for i, p := range tt.plugins { 1805 p := p 1806 enabled[i].Name = p.name 1807 if err := r.Register(p.name, func(_ context.Context, _ runtime.Object, fh framework.Handle) (framework.Plugin, error) { 1808 return p, nil 1809 }); err != nil { 1810 t.Fatalf("fail to register PreFilterExtension plugin (%s)", p.Name()) 1811 } 1812 } 1813 1814 ctx, cancel := context.WithCancel(context.Background()) 1815 defer cancel() 1816 1817 f, err := newFrameworkWithQueueSortAndBind( 1818 ctx, 1819 r, 1820 config.KubeSchedulerProfile{Plugins: &config.Plugins{PreFilter: config.PluginSet{Enabled: enabled}}}, 1821 ) 1822 if err != nil { 1823 t.Fatalf("Failed to create framework for testing: %v", err) 1824 } 1825 1826 state := framework.NewCycleState() 1827 state.SkipFilterPlugins = tt.skippedPluginNames 1828 status := f.RunPreFilterExtensionAddPod(ctx, state, nil, nil, nil) 1829 if status.Code() != tt.wantStatusCode { 1830 t.Errorf("wrong status code. got: %v, want: %v", status, tt.wantStatusCode) 1831 } 1832 }) 1833 } 1834 } 1835 1836 func TestFilterPlugins(t *testing.T) { 1837 tests := []struct { 1838 name string 1839 plugins []*TestPlugin 1840 skippedPlugins sets.Set[string] 1841 wantStatus *framework.Status 1842 }{ 1843 { 1844 name: "SuccessFilter", 1845 plugins: []*TestPlugin{ 1846 { 1847 name: "TestPlugin", 1848 inj: injectedResult{FilterStatus: int(framework.Success)}, 1849 }, 1850 }, 1851 wantStatus: nil, 1852 }, 1853 { 1854 name: "ErrorFilter", 1855 plugins: []*TestPlugin{ 1856 { 1857 name: "TestPlugin", 1858 inj: injectedResult{FilterStatus: int(framework.Error)}, 1859 }, 1860 }, 1861 wantStatus: framework.AsStatus(fmt.Errorf(`running "TestPlugin" filter plugin: %w`, errInjectedFilterStatus)).WithPlugin("TestPlugin"), 1862 }, 1863 { 1864 name: "UnschedulableFilter", 1865 plugins: []*TestPlugin{ 1866 { 1867 name: "TestPlugin", 1868 inj: injectedResult{FilterStatus: int(framework.Unschedulable)}, 1869 }, 1870 }, 1871 wantStatus: framework.NewStatus(framework.Unschedulable, injectFilterReason).WithPlugin("TestPlugin"), 1872 }, 1873 { 1874 name: "UnschedulableAndUnresolvableFilter", 1875 plugins: []*TestPlugin{ 1876 { 1877 name: "TestPlugin", 1878 inj: injectedResult{ 1879 FilterStatus: int(framework.UnschedulableAndUnresolvable)}, 1880 }, 1881 }, 1882 wantStatus: framework.NewStatus(framework.UnschedulableAndUnresolvable, injectFilterReason).WithPlugin("TestPlugin"), 1883 }, 1884 // following tests cover multiple-plugins scenarios 1885 { 1886 name: "ErrorAndErrorFilters", 1887 plugins: []*TestPlugin{ 1888 { 1889 name: "TestPlugin1", 1890 inj: injectedResult{FilterStatus: int(framework.Error)}, 1891 }, 1892 { 1893 name: "TestPlugin2", 1894 inj: injectedResult{FilterStatus: int(framework.Error)}, 1895 }, 1896 }, 1897 wantStatus: framework.AsStatus(fmt.Errorf(`running "TestPlugin1" filter plugin: %w`, errInjectedFilterStatus)).WithPlugin("TestPlugin1"), 1898 }, 1899 { 1900 name: "UnschedulableAndUnschedulableFilters", 1901 plugins: []*TestPlugin{ 1902 { 1903 name: "TestPlugin1", 1904 inj: injectedResult{FilterStatus: int(framework.Unschedulable)}, 1905 }, 1906 { 1907 name: "TestPlugin2", 1908 inj: injectedResult{FilterStatus: int(framework.Unschedulable)}, 1909 }, 1910 }, 1911 wantStatus: framework.NewStatus(framework.Unschedulable, injectFilterReason).WithPlugin("TestPlugin1"), 1912 }, 1913 { 1914 name: "UnschedulableAndUnschedulableAndUnresolvableFilters", 1915 plugins: []*TestPlugin{ 1916 { 1917 name: "TestPlugin1", 1918 inj: injectedResult{FilterStatus: int(framework.UnschedulableAndUnresolvable)}, 1919 }, 1920 { 1921 name: "TestPlugin2", 1922 inj: injectedResult{FilterStatus: int(framework.Unschedulable)}, 1923 }, 1924 }, 1925 wantStatus: framework.NewStatus(framework.UnschedulableAndUnresolvable, injectFilterReason).WithPlugin("TestPlugin1"), 1926 }, 1927 { 1928 name: "SuccessAndSuccessFilters", 1929 plugins: []*TestPlugin{ 1930 { 1931 name: "TestPlugin1", 1932 inj: injectedResult{FilterStatus: int(framework.Success)}, 1933 }, 1934 { 1935 name: "TestPlugin2", 1936 inj: injectedResult{FilterStatus: int(framework.Success)}, 1937 }, 1938 }, 1939 wantStatus: nil, 1940 }, 1941 { 1942 name: "SuccessAndSkipFilters", 1943 plugins: []*TestPlugin{ 1944 { 1945 name: "TestPlugin1", 1946 inj: injectedResult{FilterStatus: int(framework.Success)}, 1947 }, 1948 1949 { 1950 name: "TestPlugin2", 1951 inj: injectedResult{FilterStatus: int(framework.Error)}, // To make sure this plugins isn't called, set error as an injected result. 1952 }, 1953 }, 1954 wantStatus: nil, 1955 skippedPlugins: sets.New("TestPlugin2"), 1956 }, 1957 { 1958 name: "ErrorAndSuccessFilters", 1959 plugins: []*TestPlugin{ 1960 { 1961 name: "TestPlugin1", 1962 inj: injectedResult{FilterStatus: int(framework.Error)}, 1963 }, 1964 { 1965 name: "TestPlugin2", 1966 inj: injectedResult{FilterStatus: int(framework.Success)}, 1967 }, 1968 }, 1969 wantStatus: framework.AsStatus(fmt.Errorf(`running "TestPlugin1" filter plugin: %w`, errInjectedFilterStatus)).WithPlugin("TestPlugin1"), 1970 }, 1971 { 1972 name: "SuccessAndErrorFilters", 1973 plugins: []*TestPlugin{ 1974 { 1975 1976 name: "TestPlugin1", 1977 inj: injectedResult{FilterStatus: int(framework.Success)}, 1978 }, 1979 { 1980 name: "TestPlugin2", 1981 inj: injectedResult{FilterStatus: int(framework.Error)}, 1982 }, 1983 }, 1984 wantStatus: framework.AsStatus(fmt.Errorf(`running "TestPlugin2" filter plugin: %w`, errInjectedFilterStatus)).WithPlugin("TestPlugin2"), 1985 }, 1986 { 1987 name: "SuccessAndUnschedulableFilters", 1988 plugins: []*TestPlugin{ 1989 { 1990 name: "TestPlugin1", 1991 inj: injectedResult{FilterStatus: int(framework.Success)}, 1992 }, 1993 { 1994 name: "TestPlugin2", 1995 inj: injectedResult{FilterStatus: int(framework.Unschedulable)}, 1996 }, 1997 }, 1998 wantStatus: framework.NewStatus(framework.Unschedulable, injectFilterReason).WithPlugin("TestPlugin2"), 1999 }, 2000 } 2001 2002 for _, tt := range tests { 2003 t.Run(tt.name, func(t *testing.T) { 2004 registry := Registry{} 2005 cfgPls := &config.Plugins{} 2006 for _, pl := range tt.plugins { 2007 // register all plugins 2008 tmpPl := pl 2009 if err := registry.Register(pl.name, 2010 func(_ context.Context, _ runtime.Object, _ framework.Handle) (framework.Plugin, error) { 2011 return tmpPl, nil 2012 }); err != nil { 2013 t.Fatalf("fail to register filter plugin (%s)", pl.name) 2014 } 2015 // append plugins to filter pluginset 2016 cfgPls.Filter.Enabled = append( 2017 cfgPls.Filter.Enabled, 2018 config.Plugin{Name: pl.name}) 2019 } 2020 profile := config.KubeSchedulerProfile{Plugins: cfgPls} 2021 ctx, cancel := context.WithCancel(context.Background()) 2022 defer cancel() 2023 2024 f, err := newFrameworkWithQueueSortAndBind(ctx, registry, profile) 2025 if err != nil { 2026 t.Fatalf("fail to create framework: %s", err) 2027 } 2028 state := framework.NewCycleState() 2029 state.SkipFilterPlugins = tt.skippedPlugins 2030 gotStatus := f.RunFilterPlugins(ctx, state, pod, nil) 2031 if diff := cmp.Diff(gotStatus, tt.wantStatus, cmpOpts...); diff != "" { 2032 t.Errorf("Unexpected status: (-got, +want):\n%s", diff) 2033 } 2034 }) 2035 } 2036 } 2037 2038 func TestPostFilterPlugins(t *testing.T) { 2039 tests := []struct { 2040 name string 2041 plugins []*TestPlugin 2042 wantStatus *framework.Status 2043 }{ 2044 { 2045 name: "a single plugin makes a Pod schedulable", 2046 plugins: []*TestPlugin{ 2047 { 2048 name: "TestPlugin", 2049 inj: injectedResult{PostFilterStatus: int(framework.Success)}, 2050 }, 2051 }, 2052 wantStatus: framework.NewStatus(framework.Success, injectReason), 2053 }, 2054 { 2055 name: "plugin1 failed to make a Pod schedulable, followed by plugin2 which makes the Pod schedulable", 2056 plugins: []*TestPlugin{ 2057 { 2058 name: "TestPlugin1", 2059 inj: injectedResult{PostFilterStatus: int(framework.Unschedulable)}, 2060 }, 2061 { 2062 name: "TestPlugin2", 2063 inj: injectedResult{PostFilterStatus: int(framework.Success)}, 2064 }, 2065 }, 2066 wantStatus: framework.NewStatus(framework.Success, injectReason), 2067 }, 2068 { 2069 name: "plugin1 makes a Pod schedulable, followed by plugin2 which cannot make the Pod schedulable", 2070 plugins: []*TestPlugin{ 2071 { 2072 name: "TestPlugin1", 2073 inj: injectedResult{PostFilterStatus: int(framework.Success)}, 2074 }, 2075 { 2076 name: "TestPlugin2", 2077 inj: injectedResult{PostFilterStatus: int(framework.Unschedulable)}, 2078 }, 2079 }, 2080 wantStatus: framework.NewStatus(framework.Success, injectReason), 2081 }, 2082 { 2083 name: "plugin1 failed to make a Pod schedulable, followed by plugin2 which makes the Pod schedulable", 2084 plugins: []*TestPlugin{ 2085 { 2086 name: "TestPlugin1", 2087 inj: injectedResult{PostFilterStatus: int(framework.Error)}, 2088 }, 2089 { 2090 name: "TestPlugin2", 2091 inj: injectedResult{PostFilterStatus: int(framework.Success)}, 2092 }, 2093 }, 2094 wantStatus: framework.AsStatus(fmt.Errorf(injectReason)).WithPlugin("TestPlugin1"), 2095 }, 2096 { 2097 name: "plugin1 failed to make a Pod schedulable, followed by plugin2 which makes the Pod unresolvable", 2098 plugins: []*TestPlugin{ 2099 { 2100 name: "TestPlugin1", 2101 inj: injectedResult{PostFilterStatus: int(framework.Unschedulable)}, 2102 }, 2103 { 2104 name: "TestPlugin2", 2105 inj: injectedResult{PostFilterStatus: int(framework.UnschedulableAndUnresolvable)}, 2106 }, 2107 }, 2108 wantStatus: framework.NewStatus(framework.UnschedulableAndUnresolvable, injectReason).WithPlugin("TestPlugin2"), 2109 }, 2110 { 2111 name: "both plugins failed to make a Pod schedulable", 2112 plugins: []*TestPlugin{ 2113 { 2114 name: "TestPlugin1", 2115 inj: injectedResult{PostFilterStatus: int(framework.Unschedulable)}, 2116 }, 2117 { 2118 name: "TestPlugin2", 2119 inj: injectedResult{PostFilterStatus: int(framework.Unschedulable)}, 2120 }, 2121 }, 2122 wantStatus: framework.NewStatus(framework.Unschedulable, []string{injectReason, injectReason}...).WithPlugin("TestPlugin1"), 2123 }, 2124 } 2125 2126 for _, tt := range tests { 2127 t.Run(tt.name, func(t *testing.T) { 2128 registry := Registry{} 2129 cfgPls := &config.Plugins{} 2130 for _, pl := range tt.plugins { 2131 // register all plugins 2132 tmpPl := pl 2133 if err := registry.Register(pl.name, 2134 func(_ context.Context, _ runtime.Object, _ framework.Handle) (framework.Plugin, error) { 2135 return tmpPl, nil 2136 }); err != nil { 2137 t.Fatalf("fail to register postFilter plugin (%s)", pl.name) 2138 } 2139 // append plugins to filter pluginset 2140 cfgPls.PostFilter.Enabled = append( 2141 cfgPls.PostFilter.Enabled, 2142 config.Plugin{Name: pl.name}, 2143 ) 2144 } 2145 profile := config.KubeSchedulerProfile{Plugins: cfgPls} 2146 ctx, cancel := context.WithCancel(context.Background()) 2147 defer cancel() 2148 f, err := newFrameworkWithQueueSortAndBind(ctx, registry, profile) 2149 if err != nil { 2150 t.Fatalf("fail to create framework: %s", err) 2151 } 2152 _, gotStatus := f.RunPostFilterPlugins(ctx, nil, pod, nil) 2153 if !reflect.DeepEqual(gotStatus, tt.wantStatus) { 2154 t.Errorf("Unexpected status. got: %v, want: %v", gotStatus, tt.wantStatus) 2155 } 2156 }) 2157 } 2158 } 2159 2160 func TestFilterPluginsWithNominatedPods(t *testing.T) { 2161 tests := []struct { 2162 name string 2163 preFilterPlugin *TestPlugin 2164 filterPlugin *TestPlugin 2165 pod *v1.Pod 2166 nominatedPod *v1.Pod 2167 node *v1.Node 2168 nodeInfo *framework.NodeInfo 2169 wantStatus *framework.Status 2170 }{ 2171 { 2172 name: "node has no nominated pod", 2173 preFilterPlugin: nil, 2174 filterPlugin: nil, 2175 pod: lowPriorityPod, 2176 nominatedPod: nil, 2177 node: node, 2178 nodeInfo: framework.NewNodeInfo(pod), 2179 wantStatus: nil, 2180 }, 2181 { 2182 name: "node has a high-priority nominated pod and all filters succeed", 2183 preFilterPlugin: &TestPlugin{ 2184 name: "TestPlugin1", 2185 inj: injectedResult{ 2186 PreFilterAddPodStatus: int(framework.Success), 2187 }, 2188 }, 2189 filterPlugin: &TestPlugin{ 2190 name: "TestPlugin2", 2191 inj: injectedResult{ 2192 FilterStatus: int(framework.Success), 2193 }, 2194 }, 2195 pod: lowPriorityPod, 2196 nominatedPod: highPriorityPod, 2197 node: node, 2198 nodeInfo: framework.NewNodeInfo(pod), 2199 wantStatus: nil, 2200 }, 2201 { 2202 name: "node has a high-priority nominated pod and pre filters fail", 2203 preFilterPlugin: &TestPlugin{ 2204 name: "TestPlugin1", 2205 inj: injectedResult{ 2206 PreFilterAddPodStatus: int(framework.Error), 2207 }, 2208 }, 2209 filterPlugin: nil, 2210 pod: lowPriorityPod, 2211 nominatedPod: highPriorityPod, 2212 node: node, 2213 nodeInfo: framework.NewNodeInfo(pod), 2214 wantStatus: framework.AsStatus(fmt.Errorf(`running AddPod on PreFilter plugin "TestPlugin1": %w`, errInjectedStatus)), 2215 }, 2216 { 2217 name: "node has a high-priority nominated pod and filters fail", 2218 preFilterPlugin: &TestPlugin{ 2219 name: "TestPlugin1", 2220 inj: injectedResult{ 2221 PreFilterAddPodStatus: int(framework.Success), 2222 }, 2223 }, 2224 filterPlugin: &TestPlugin{ 2225 name: "TestPlugin2", 2226 inj: injectedResult{ 2227 FilterStatus: int(framework.Error), 2228 }, 2229 }, 2230 pod: lowPriorityPod, 2231 nominatedPod: highPriorityPod, 2232 node: node, 2233 nodeInfo: framework.NewNodeInfo(pod), 2234 wantStatus: framework.AsStatus(fmt.Errorf(`running "TestPlugin2" filter plugin: %w`, errInjectedFilterStatus)).WithPlugin("TestPlugin2"), 2235 }, 2236 { 2237 name: "node has a low-priority nominated pod and pre filters return unschedulable", 2238 preFilterPlugin: &TestPlugin{ 2239 name: "TestPlugin1", 2240 inj: injectedResult{ 2241 PreFilterAddPodStatus: int(framework.Unschedulable), 2242 }, 2243 }, 2244 filterPlugin: &TestPlugin{ 2245 name: "TestPlugin2", 2246 inj: injectedResult{ 2247 FilterStatus: int(framework.Success), 2248 }, 2249 }, 2250 pod: highPriorityPod, 2251 nominatedPod: lowPriorityPod, 2252 node: node, 2253 nodeInfo: framework.NewNodeInfo(pod), 2254 wantStatus: nil, 2255 }, 2256 } 2257 2258 for _, tt := range tests { 2259 t.Run(tt.name, func(t *testing.T) { 2260 logger, _ := ktesting.NewTestContext(t) 2261 registry := Registry{} 2262 cfgPls := &config.Plugins{} 2263 2264 if tt.preFilterPlugin != nil { 2265 if err := registry.Register(tt.preFilterPlugin.name, 2266 func(_ context.Context, _ runtime.Object, _ framework.Handle) (framework.Plugin, error) { 2267 return tt.preFilterPlugin, nil 2268 }); err != nil { 2269 t.Fatalf("fail to register preFilter plugin (%s)", tt.preFilterPlugin.name) 2270 } 2271 cfgPls.PreFilter.Enabled = append( 2272 cfgPls.PreFilter.Enabled, 2273 config.Plugin{Name: tt.preFilterPlugin.name}, 2274 ) 2275 } 2276 if tt.filterPlugin != nil { 2277 if err := registry.Register(tt.filterPlugin.name, 2278 func(_ context.Context, _ runtime.Object, _ framework.Handle) (framework.Plugin, error) { 2279 return tt.filterPlugin, nil 2280 }); err != nil { 2281 t.Fatalf("fail to register filter plugin (%s)", tt.filterPlugin.name) 2282 } 2283 cfgPls.Filter.Enabled = append( 2284 cfgPls.Filter.Enabled, 2285 config.Plugin{Name: tt.filterPlugin.name}, 2286 ) 2287 } 2288 2289 podNominator := internalqueue.NewPodNominator(nil) 2290 if tt.nominatedPod != nil { 2291 podNominator.AddNominatedPod( 2292 logger, 2293 mustNewPodInfo(t, tt.nominatedPod), 2294 &framework.NominatingInfo{NominatingMode: framework.ModeOverride, NominatedNodeName: nodeName}) 2295 } 2296 profile := config.KubeSchedulerProfile{Plugins: cfgPls} 2297 ctx, cancel := context.WithCancel(context.Background()) 2298 defer cancel() 2299 f, err := newFrameworkWithQueueSortAndBind(ctx, registry, profile, WithPodNominator(podNominator)) 2300 if err != nil { 2301 t.Fatalf("fail to create framework: %s", err) 2302 } 2303 tt.nodeInfo.SetNode(tt.node) 2304 gotStatus := f.RunFilterPluginsWithNominatedPods(ctx, framework.NewCycleState(), tt.pod, tt.nodeInfo) 2305 if diff := cmp.Diff(gotStatus, tt.wantStatus, cmpOpts...); diff != "" { 2306 t.Errorf("Unexpected status: (-got, +want):\n%s", diff) 2307 } 2308 }) 2309 } 2310 } 2311 2312 func TestPreBindPlugins(t *testing.T) { 2313 tests := []struct { 2314 name string 2315 plugins []*TestPlugin 2316 wantStatus *framework.Status 2317 }{ 2318 { 2319 name: "NoPreBindPlugin", 2320 plugins: []*TestPlugin{}, 2321 wantStatus: nil, 2322 }, 2323 { 2324 name: "SuccessPreBindPlugins", 2325 plugins: []*TestPlugin{ 2326 { 2327 name: "TestPlugin", 2328 inj: injectedResult{PreBindStatus: int(framework.Success)}, 2329 }, 2330 }, 2331 wantStatus: nil, 2332 }, 2333 { 2334 name: "UnschedulablePreBindPlugin", 2335 plugins: []*TestPlugin{ 2336 { 2337 name: "TestPlugin", 2338 inj: injectedResult{PreBindStatus: int(framework.Unschedulable)}, 2339 }, 2340 }, 2341 wantStatus: framework.NewStatus(framework.Unschedulable, injectReason).WithPlugin("TestPlugin"), 2342 }, 2343 { 2344 name: "ErrorPreBindPlugin", 2345 plugins: []*TestPlugin{ 2346 { 2347 name: "TestPlugin", 2348 inj: injectedResult{PreBindStatus: int(framework.Error)}, 2349 }, 2350 }, 2351 wantStatus: framework.AsStatus(fmt.Errorf(`running PreBind plugin "TestPlugin": %w`, errInjectedStatus)), 2352 }, 2353 { 2354 name: "UnschedulablePreBindPlugin", 2355 plugins: []*TestPlugin{ 2356 { 2357 name: "TestPlugin", 2358 inj: injectedResult{PreBindStatus: int(framework.UnschedulableAndUnresolvable)}, 2359 }, 2360 }, 2361 wantStatus: framework.NewStatus(framework.UnschedulableAndUnresolvable, injectReason).WithPlugin("TestPlugin"), 2362 }, 2363 { 2364 name: "SuccessErrorPreBindPlugins", 2365 plugins: []*TestPlugin{ 2366 { 2367 name: "TestPlugin", 2368 inj: injectedResult{PreBindStatus: int(framework.Success)}, 2369 }, 2370 { 2371 name: "TestPlugin 1", 2372 inj: injectedResult{PreBindStatus: int(framework.Error)}, 2373 }, 2374 }, 2375 wantStatus: framework.AsStatus(fmt.Errorf(`running PreBind plugin "TestPlugin 1": %w`, errInjectedStatus)), 2376 }, 2377 { 2378 name: "ErrorSuccessPreBindPlugin", 2379 plugins: []*TestPlugin{ 2380 { 2381 name: "TestPlugin", 2382 inj: injectedResult{PreBindStatus: int(framework.Error)}, 2383 }, 2384 { 2385 name: "TestPlugin 1", 2386 inj: injectedResult{PreBindStatus: int(framework.Success)}, 2387 }, 2388 }, 2389 wantStatus: framework.AsStatus(fmt.Errorf(`running PreBind plugin "TestPlugin": %w`, errInjectedStatus)), 2390 }, 2391 { 2392 name: "SuccessSuccessPreBindPlugin", 2393 plugins: []*TestPlugin{ 2394 { 2395 name: "TestPlugin", 2396 inj: injectedResult{PreBindStatus: int(framework.Success)}, 2397 }, 2398 { 2399 name: "TestPlugin 1", 2400 inj: injectedResult{PreBindStatus: int(framework.Success)}, 2401 }, 2402 }, 2403 wantStatus: nil, 2404 }, 2405 { 2406 name: "ErrorAndErrorPlugins", 2407 plugins: []*TestPlugin{ 2408 { 2409 name: "TestPlugin", 2410 inj: injectedResult{PreBindStatus: int(framework.Error)}, 2411 }, 2412 { 2413 name: "TestPlugin 1", 2414 inj: injectedResult{PreBindStatus: int(framework.Error)}, 2415 }, 2416 }, 2417 wantStatus: framework.AsStatus(fmt.Errorf(`running PreBind plugin "TestPlugin": %w`, errInjectedStatus)), 2418 }, 2419 { 2420 name: "UnschedulableAndSuccessPreBindPlugin", 2421 plugins: []*TestPlugin{ 2422 { 2423 name: "TestPlugin", 2424 inj: injectedResult{PreBindStatus: int(framework.Unschedulable)}, 2425 }, 2426 { 2427 name: "TestPlugin 1", 2428 inj: injectedResult{PreBindStatus: int(framework.Success)}, 2429 }, 2430 }, 2431 wantStatus: framework.NewStatus(framework.Unschedulable, injectReason).WithPlugin("TestPlugin"), 2432 }, 2433 } 2434 2435 for _, tt := range tests { 2436 t.Run(tt.name, func(t *testing.T) { 2437 registry := Registry{} 2438 configPlugins := &config.Plugins{} 2439 2440 for _, pl := range tt.plugins { 2441 tmpPl := pl 2442 if err := registry.Register(pl.name, func(_ context.Context, _ runtime.Object, _ framework.Handle) (framework.Plugin, error) { 2443 return tmpPl, nil 2444 }); err != nil { 2445 t.Fatalf("Unable to register pre bind plugins: %s", pl.name) 2446 } 2447 2448 configPlugins.PreBind.Enabled = append( 2449 configPlugins.PreBind.Enabled, 2450 config.Plugin{Name: pl.name}, 2451 ) 2452 } 2453 profile := config.KubeSchedulerProfile{Plugins: configPlugins} 2454 ctx, cancel := context.WithCancel(context.Background()) 2455 defer cancel() 2456 f, err := newFrameworkWithQueueSortAndBind(ctx, registry, profile) 2457 if err != nil { 2458 t.Fatalf("fail to create framework: %s", err) 2459 } 2460 2461 status := f.RunPreBindPlugins(ctx, nil, pod, "") 2462 2463 if !reflect.DeepEqual(status, tt.wantStatus) { 2464 t.Errorf("wrong status code. got %v, want %v", status, tt.wantStatus) 2465 } 2466 }) 2467 } 2468 } 2469 2470 func TestReservePlugins(t *testing.T) { 2471 tests := []struct { 2472 name string 2473 plugins []*TestPlugin 2474 wantStatus *framework.Status 2475 }{ 2476 { 2477 name: "NoReservePlugin", 2478 plugins: []*TestPlugin{}, 2479 wantStatus: nil, 2480 }, 2481 { 2482 name: "SuccessReservePlugins", 2483 plugins: []*TestPlugin{ 2484 { 2485 name: "TestPlugin", 2486 inj: injectedResult{ReserveStatus: int(framework.Success)}, 2487 }, 2488 }, 2489 wantStatus: nil, 2490 }, 2491 { 2492 name: "UnschedulableReservePlugin", 2493 plugins: []*TestPlugin{ 2494 { 2495 name: "TestPlugin", 2496 inj: injectedResult{ReserveStatus: int(framework.Unschedulable)}, 2497 }, 2498 }, 2499 wantStatus: framework.NewStatus(framework.Unschedulable, injectReason).WithPlugin("TestPlugin"), 2500 }, 2501 { 2502 name: "ErrorReservePlugin", 2503 plugins: []*TestPlugin{ 2504 { 2505 name: "TestPlugin", 2506 inj: injectedResult{ReserveStatus: int(framework.Error)}, 2507 }, 2508 }, 2509 wantStatus: framework.AsStatus(fmt.Errorf(`running Reserve plugin "TestPlugin": %w`, errInjectedStatus)), 2510 }, 2511 { 2512 name: "UnschedulableReservePlugin", 2513 plugins: []*TestPlugin{ 2514 { 2515 name: "TestPlugin", 2516 inj: injectedResult{ReserveStatus: int(framework.UnschedulableAndUnresolvable)}, 2517 }, 2518 }, 2519 wantStatus: framework.NewStatus(framework.UnschedulableAndUnresolvable, injectReason).WithPlugin("TestPlugin"), 2520 }, 2521 { 2522 name: "SuccessSuccessReservePlugins", 2523 plugins: []*TestPlugin{ 2524 { 2525 name: "TestPlugin", 2526 inj: injectedResult{ReserveStatus: int(framework.Success)}, 2527 }, 2528 { 2529 name: "TestPlugin 1", 2530 inj: injectedResult{ReserveStatus: int(framework.Success)}, 2531 }, 2532 }, 2533 wantStatus: nil, 2534 }, 2535 { 2536 name: "ErrorErrorReservePlugins", 2537 plugins: []*TestPlugin{ 2538 { 2539 name: "TestPlugin", 2540 inj: injectedResult{ReserveStatus: int(framework.Error)}, 2541 }, 2542 { 2543 name: "TestPlugin 1", 2544 inj: injectedResult{ReserveStatus: int(framework.Error)}, 2545 }, 2546 }, 2547 wantStatus: framework.AsStatus(fmt.Errorf(`running Reserve plugin "TestPlugin": %w`, errInjectedStatus)), 2548 }, 2549 { 2550 name: "SuccessErrorReservePlugins", 2551 plugins: []*TestPlugin{ 2552 { 2553 name: "TestPlugin", 2554 inj: injectedResult{ReserveStatus: int(framework.Success)}, 2555 }, 2556 { 2557 name: "TestPlugin 1", 2558 inj: injectedResult{ReserveStatus: int(framework.Error)}, 2559 }, 2560 }, 2561 wantStatus: framework.AsStatus(fmt.Errorf(`running Reserve plugin "TestPlugin 1": %w`, errInjectedStatus)), 2562 }, 2563 { 2564 name: "ErrorSuccessReservePlugin", 2565 plugins: []*TestPlugin{ 2566 { 2567 name: "TestPlugin", 2568 inj: injectedResult{ReserveStatus: int(framework.Error)}, 2569 }, 2570 { 2571 name: "TestPlugin 1", 2572 inj: injectedResult{ReserveStatus: int(framework.Success)}, 2573 }, 2574 }, 2575 wantStatus: framework.AsStatus(fmt.Errorf(`running Reserve plugin "TestPlugin": %w`, errInjectedStatus)), 2576 }, 2577 { 2578 name: "UnschedulableAndSuccessReservePlugin", 2579 plugins: []*TestPlugin{ 2580 { 2581 name: "TestPlugin", 2582 inj: injectedResult{ReserveStatus: int(framework.Unschedulable)}, 2583 }, 2584 { 2585 name: "TestPlugin 1", 2586 inj: injectedResult{ReserveStatus: int(framework.Success)}, 2587 }, 2588 }, 2589 wantStatus: framework.NewStatus(framework.Unschedulable, injectReason).WithPlugin("TestPlugin"), 2590 }, 2591 } 2592 2593 for _, tt := range tests { 2594 t.Run(tt.name, func(t *testing.T) { 2595 registry := Registry{} 2596 configPlugins := &config.Plugins{} 2597 2598 for _, pl := range tt.plugins { 2599 tmpPl := pl 2600 if err := registry.Register(pl.name, func(_ context.Context, _ runtime.Object, _ framework.Handle) (framework.Plugin, error) { 2601 return tmpPl, nil 2602 }); err != nil { 2603 t.Fatalf("Unable to register pre bind plugins: %s", pl.name) 2604 } 2605 2606 configPlugins.Reserve.Enabled = append( 2607 configPlugins.Reserve.Enabled, 2608 config.Plugin{Name: pl.name}, 2609 ) 2610 } 2611 profile := config.KubeSchedulerProfile{Plugins: configPlugins} 2612 ctx, cancel := context.WithCancel(context.Background()) 2613 defer cancel() 2614 f, err := newFrameworkWithQueueSortAndBind(ctx, registry, profile) 2615 if err != nil { 2616 t.Fatalf("fail to create framework: %s", err) 2617 } 2618 2619 status := f.RunReservePluginsReserve(ctx, nil, pod, "") 2620 2621 if !reflect.DeepEqual(status, tt.wantStatus) { 2622 t.Errorf("wrong status code. got %v, want %v", status, tt.wantStatus) 2623 } 2624 }) 2625 } 2626 } 2627 2628 func TestPermitPlugins(t *testing.T) { 2629 tests := []struct { 2630 name string 2631 plugins []*TestPlugin 2632 want *framework.Status 2633 }{ 2634 { 2635 name: "NilPermitPlugin", 2636 plugins: []*TestPlugin{}, 2637 want: nil, 2638 }, 2639 { 2640 name: "SuccessPermitPlugin", 2641 plugins: []*TestPlugin{ 2642 { 2643 name: "TestPlugin", 2644 inj: injectedResult{PermitStatus: int(framework.Success)}, 2645 }, 2646 }, 2647 want: nil, 2648 }, 2649 { 2650 name: "UnschedulablePermitPlugin", 2651 plugins: []*TestPlugin{ 2652 { 2653 name: "TestPlugin", 2654 inj: injectedResult{PermitStatus: int(framework.Unschedulable)}, 2655 }, 2656 }, 2657 want: framework.NewStatus(framework.Unschedulable, injectReason).WithPlugin("TestPlugin"), 2658 }, 2659 { 2660 name: "ErrorPermitPlugin", 2661 plugins: []*TestPlugin{ 2662 { 2663 name: "TestPlugin", 2664 inj: injectedResult{PermitStatus: int(framework.Error)}, 2665 }, 2666 }, 2667 want: framework.AsStatus(fmt.Errorf(`running Permit plugin "TestPlugin": %w`, errInjectedStatus)).WithPlugin("TestPlugin"), 2668 }, 2669 { 2670 name: "UnschedulableAndUnresolvablePermitPlugin", 2671 plugins: []*TestPlugin{ 2672 { 2673 name: "TestPlugin", 2674 inj: injectedResult{PermitStatus: int(framework.UnschedulableAndUnresolvable)}, 2675 }, 2676 }, 2677 want: framework.NewStatus(framework.UnschedulableAndUnresolvable, injectReason).WithPlugin("TestPlugin"), 2678 }, 2679 { 2680 name: "WaitPermitPlugin", 2681 plugins: []*TestPlugin{ 2682 { 2683 name: "TestPlugin", 2684 inj: injectedResult{PermitStatus: int(framework.Wait)}, 2685 }, 2686 }, 2687 want: framework.NewStatus(framework.Wait, `one or more plugins asked to wait and no plugin rejected pod ""`), 2688 }, 2689 { 2690 name: "SuccessSuccessPermitPlugin", 2691 plugins: []*TestPlugin{ 2692 { 2693 name: "TestPlugin", 2694 inj: injectedResult{PermitStatus: int(framework.Success)}, 2695 }, 2696 { 2697 name: "TestPlugin 1", 2698 inj: injectedResult{PermitStatus: int(framework.Success)}, 2699 }, 2700 }, 2701 want: nil, 2702 }, 2703 { 2704 name: "ErrorAndErrorPlugins", 2705 plugins: []*TestPlugin{ 2706 { 2707 name: "TestPlugin", 2708 inj: injectedResult{PermitStatus: int(framework.Error)}, 2709 }, 2710 { 2711 name: "TestPlugin 1", 2712 inj: injectedResult{PermitStatus: int(framework.Error)}, 2713 }, 2714 }, 2715 want: framework.AsStatus(fmt.Errorf(`running Permit plugin "TestPlugin": %w`, errInjectedStatus)).WithPlugin("TestPlugin"), 2716 }, 2717 } 2718 2719 for _, tt := range tests { 2720 t.Run(tt.name, func(t *testing.T) { 2721 registry := Registry{} 2722 configPlugins := &config.Plugins{} 2723 2724 for _, pl := range tt.plugins { 2725 tmpPl := pl 2726 if err := registry.Register(pl.name, func(_ context.Context, _ runtime.Object, _ framework.Handle) (framework.Plugin, error) { 2727 return tmpPl, nil 2728 }); err != nil { 2729 t.Fatalf("Unable to register Permit plugin: %s", pl.name) 2730 } 2731 2732 configPlugins.Permit.Enabled = append( 2733 configPlugins.Permit.Enabled, 2734 config.Plugin{Name: pl.name}, 2735 ) 2736 } 2737 profile := config.KubeSchedulerProfile{Plugins: configPlugins} 2738 ctx, cancel := context.WithCancel(context.Background()) 2739 defer cancel() 2740 f, err := newFrameworkWithQueueSortAndBind(ctx, registry, profile) 2741 if err != nil { 2742 t.Fatalf("fail to create framework: %s", err) 2743 } 2744 2745 status := f.RunPermitPlugins(ctx, nil, pod, "") 2746 if !reflect.DeepEqual(status, tt.want) { 2747 t.Errorf("wrong status code. got %v, want %v", status, tt.want) 2748 } 2749 }) 2750 } 2751 } 2752 2753 // withMetricsRecorder set metricsRecorder for the scheduling frameworkImpl. 2754 func withMetricsRecorder(recorder *metrics.MetricAsyncRecorder) Option { 2755 return func(o *frameworkOptions) { 2756 o.metricsRecorder = recorder 2757 } 2758 } 2759 2760 func TestRecordingMetrics(t *testing.T) { 2761 state := &framework.CycleState{} 2762 state.SetRecordPluginMetrics(true) 2763 2764 tests := []struct { 2765 name string 2766 action func(f framework.Framework) 2767 inject injectedResult 2768 wantExtensionPoint string 2769 wantStatus framework.Code 2770 }{ 2771 { 2772 name: "PreFilter - Success", 2773 action: func(f framework.Framework) { f.RunPreFilterPlugins(context.Background(), state, pod) }, 2774 wantExtensionPoint: "PreFilter", 2775 wantStatus: framework.Success, 2776 }, 2777 { 2778 name: "PreScore - Success", 2779 action: func(f framework.Framework) { f.RunPreScorePlugins(context.Background(), state, pod, nil) }, 2780 wantExtensionPoint: "PreScore", 2781 wantStatus: framework.Success, 2782 }, 2783 { 2784 name: "Score - Success", 2785 action: func(f framework.Framework) { f.RunScorePlugins(context.Background(), state, pod, nodes) }, 2786 wantExtensionPoint: "Score", 2787 wantStatus: framework.Success, 2788 }, 2789 { 2790 name: "Reserve - Success", 2791 action: func(f framework.Framework) { f.RunReservePluginsReserve(context.Background(), state, pod, "") }, 2792 wantExtensionPoint: "Reserve", 2793 wantStatus: framework.Success, 2794 }, 2795 { 2796 name: "Unreserve - Success", 2797 action: func(f framework.Framework) { f.RunReservePluginsUnreserve(context.Background(), state, pod, "") }, 2798 wantExtensionPoint: "Unreserve", 2799 wantStatus: framework.Success, 2800 }, 2801 { 2802 name: "PreBind - Success", 2803 action: func(f framework.Framework) { f.RunPreBindPlugins(context.Background(), state, pod, "") }, 2804 wantExtensionPoint: "PreBind", 2805 wantStatus: framework.Success, 2806 }, 2807 { 2808 name: "Bind - Success", 2809 action: func(f framework.Framework) { f.RunBindPlugins(context.Background(), state, pod, "") }, 2810 wantExtensionPoint: "Bind", 2811 wantStatus: framework.Success, 2812 }, 2813 { 2814 name: "PostBind - Success", 2815 action: func(f framework.Framework) { f.RunPostBindPlugins(context.Background(), state, pod, "") }, 2816 wantExtensionPoint: "PostBind", 2817 wantStatus: framework.Success, 2818 }, 2819 { 2820 name: "Permit - Success", 2821 action: func(f framework.Framework) { f.RunPermitPlugins(context.Background(), state, pod, "") }, 2822 wantExtensionPoint: "Permit", 2823 wantStatus: framework.Success, 2824 }, 2825 2826 { 2827 name: "PreFilter - Error", 2828 action: func(f framework.Framework) { f.RunPreFilterPlugins(context.Background(), state, pod) }, 2829 inject: injectedResult{PreFilterStatus: int(framework.Error)}, 2830 wantExtensionPoint: "PreFilter", 2831 wantStatus: framework.Error, 2832 }, 2833 { 2834 name: "PreScore - Error", 2835 action: func(f framework.Framework) { f.RunPreScorePlugins(context.Background(), state, pod, nil) }, 2836 inject: injectedResult{PreScoreStatus: int(framework.Error)}, 2837 wantExtensionPoint: "PreScore", 2838 wantStatus: framework.Error, 2839 }, 2840 { 2841 name: "Score - Error", 2842 action: func(f framework.Framework) { f.RunScorePlugins(context.Background(), state, pod, nodes) }, 2843 inject: injectedResult{ScoreStatus: int(framework.Error)}, 2844 wantExtensionPoint: "Score", 2845 wantStatus: framework.Error, 2846 }, 2847 { 2848 name: "Reserve - Error", 2849 action: func(f framework.Framework) { f.RunReservePluginsReserve(context.Background(), state, pod, "") }, 2850 inject: injectedResult{ReserveStatus: int(framework.Error)}, 2851 wantExtensionPoint: "Reserve", 2852 wantStatus: framework.Error, 2853 }, 2854 { 2855 name: "PreBind - Error", 2856 action: func(f framework.Framework) { f.RunPreBindPlugins(context.Background(), state, pod, "") }, 2857 inject: injectedResult{PreBindStatus: int(framework.Error)}, 2858 wantExtensionPoint: "PreBind", 2859 wantStatus: framework.Error, 2860 }, 2861 { 2862 name: "Bind - Error", 2863 action: func(f framework.Framework) { f.RunBindPlugins(context.Background(), state, pod, "") }, 2864 inject: injectedResult{BindStatus: int(framework.Error)}, 2865 wantExtensionPoint: "Bind", 2866 wantStatus: framework.Error, 2867 }, 2868 { 2869 name: "Permit - Error", 2870 action: func(f framework.Framework) { f.RunPermitPlugins(context.Background(), state, pod, "") }, 2871 inject: injectedResult{PermitStatus: int(framework.Error)}, 2872 wantExtensionPoint: "Permit", 2873 wantStatus: framework.Error, 2874 }, 2875 { 2876 name: "Permit - Wait", 2877 action: func(f framework.Framework) { f.RunPermitPlugins(context.Background(), state, pod, "") }, 2878 inject: injectedResult{PermitStatus: int(framework.Wait)}, 2879 wantExtensionPoint: "Permit", 2880 wantStatus: framework.Wait, 2881 }, 2882 } 2883 2884 for _, tt := range tests { 2885 t.Run(tt.name, func(t *testing.T) { 2886 metrics.Register() 2887 metrics.FrameworkExtensionPointDuration.Reset() 2888 metrics.PluginExecutionDuration.Reset() 2889 2890 plugin := &TestPlugin{name: testPlugin, inj: tt.inject} 2891 r := make(Registry) 2892 r.Register(testPlugin, 2893 func(_ context.Context, _ runtime.Object, fh framework.Handle) (framework.Plugin, error) { 2894 return plugin, nil 2895 }) 2896 pluginSet := config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin, Weight: 1}}} 2897 plugins := &config.Plugins{ 2898 Score: pluginSet, 2899 PreFilter: pluginSet, 2900 Filter: pluginSet, 2901 PreScore: pluginSet, 2902 Reserve: pluginSet, 2903 Permit: pluginSet, 2904 PreBind: pluginSet, 2905 Bind: pluginSet, 2906 PostBind: pluginSet, 2907 } 2908 2909 _, ctx := ktesting.NewTestContext(t) 2910 ctx, cancel := context.WithCancel(ctx) 2911 2912 recorder := metrics.NewMetricsAsyncRecorder(100, time.Nanosecond, ctx.Done()) 2913 profile := config.KubeSchedulerProfile{ 2914 PercentageOfNodesToScore: ptr.To[int32](testPercentageOfNodesToScore), 2915 SchedulerName: testProfileName, 2916 Plugins: plugins, 2917 } 2918 f, err := newFrameworkWithQueueSortAndBind(ctx, r, profile, withMetricsRecorder(recorder)) 2919 if err != nil { 2920 cancel() 2921 t.Fatalf("Failed to create framework for testing: %v", err) 2922 } 2923 2924 tt.action(f) 2925 2926 // Stop the goroutine which records metrics and ensure it's stopped. 2927 cancel() 2928 <-recorder.IsStoppedCh 2929 // Try to clean up the metrics buffer again in case it's not empty. 2930 recorder.FlushMetrics() 2931 2932 collectAndCompareFrameworkMetrics(t, tt.wantExtensionPoint, tt.wantStatus) 2933 collectAndComparePluginMetrics(t, tt.wantExtensionPoint, testPlugin, tt.wantStatus) 2934 }) 2935 } 2936 } 2937 2938 func TestRunBindPlugins(t *testing.T) { 2939 tests := []struct { 2940 name string 2941 injects []framework.Code 2942 wantStatus framework.Code 2943 }{ 2944 { 2945 name: "simple success", 2946 injects: []framework.Code{framework.Success}, 2947 wantStatus: framework.Success, 2948 }, 2949 { 2950 name: "error on second", 2951 injects: []framework.Code{framework.Skip, framework.Error, framework.Success}, 2952 wantStatus: framework.Error, 2953 }, 2954 { 2955 name: "all skip", 2956 injects: []framework.Code{framework.Skip, framework.Skip, framework.Skip}, 2957 wantStatus: framework.Skip, 2958 }, 2959 { 2960 name: "error on third, but not reached", 2961 injects: []framework.Code{framework.Skip, framework.Success, framework.Error}, 2962 wantStatus: framework.Success, 2963 }, 2964 { 2965 name: "no bind plugin, returns default binder", 2966 injects: []framework.Code{}, 2967 wantStatus: framework.Success, 2968 }, 2969 { 2970 name: "invalid status", 2971 injects: []framework.Code{framework.Unschedulable}, 2972 wantStatus: framework.Unschedulable, 2973 }, 2974 { 2975 name: "simple error", 2976 injects: []framework.Code{framework.Error}, 2977 wantStatus: framework.Error, 2978 }, 2979 { 2980 name: "success on second, returns success", 2981 injects: []framework.Code{framework.Skip, framework.Success}, 2982 wantStatus: framework.Success, 2983 }, 2984 { 2985 name: "invalid status, returns error", 2986 injects: []framework.Code{framework.Skip, framework.UnschedulableAndUnresolvable}, 2987 wantStatus: framework.UnschedulableAndUnresolvable, 2988 }, 2989 { 2990 name: "error after success status, returns success", 2991 injects: []framework.Code{framework.Success, framework.Error}, 2992 wantStatus: framework.Success, 2993 }, 2994 { 2995 name: "success before invalid status, returns success", 2996 injects: []framework.Code{framework.Success, framework.Error}, 2997 wantStatus: framework.Success, 2998 }, 2999 { 3000 name: "success after error status, returns error", 3001 injects: []framework.Code{framework.Error, framework.Success}, 3002 wantStatus: framework.Error, 3003 }, 3004 } 3005 for _, tt := range tests { 3006 t.Run(tt.name, func(t *testing.T) { 3007 metrics.Register() 3008 metrics.FrameworkExtensionPointDuration.Reset() 3009 metrics.PluginExecutionDuration.Reset() 3010 3011 pluginSet := config.PluginSet{} 3012 r := make(Registry) 3013 for i, inj := range tt.injects { 3014 name := fmt.Sprintf("bind-%d", i) 3015 plugin := &TestPlugin{name: name, inj: injectedResult{BindStatus: int(inj)}} 3016 r.Register(name, 3017 func(_ context.Context, _ runtime.Object, fh framework.Handle) (framework.Plugin, error) { 3018 return plugin, nil 3019 }) 3020 pluginSet.Enabled = append(pluginSet.Enabled, config.Plugin{Name: name}) 3021 } 3022 plugins := &config.Plugins{Bind: pluginSet} 3023 _, ctx := ktesting.NewTestContext(t) 3024 ctx, cancel := context.WithCancel(ctx) 3025 recorder := metrics.NewMetricsAsyncRecorder(100, time.Nanosecond, ctx.Done()) 3026 profile := config.KubeSchedulerProfile{ 3027 SchedulerName: testProfileName, 3028 PercentageOfNodesToScore: ptr.To[int32](testPercentageOfNodesToScore), 3029 Plugins: plugins, 3030 } 3031 fwk, err := newFrameworkWithQueueSortAndBind(ctx, r, profile, withMetricsRecorder(recorder)) 3032 if err != nil { 3033 cancel() 3034 t.Fatal(err) 3035 } 3036 3037 st := fwk.RunBindPlugins(context.Background(), state, pod, "") 3038 if st.Code() != tt.wantStatus { 3039 t.Errorf("got status code %s, want %s", st.Code(), tt.wantStatus) 3040 } 3041 3042 // Stop the goroutine which records metrics and ensure it's stopped. 3043 cancel() 3044 <-recorder.IsStoppedCh 3045 // Try to clean up the metrics buffer again in case it's not empty. 3046 recorder.FlushMetrics() 3047 collectAndCompareFrameworkMetrics(t, "Bind", tt.wantStatus) 3048 }) 3049 } 3050 } 3051 3052 func TestPermitWaitDurationMetric(t *testing.T) { 3053 tests := []struct { 3054 name string 3055 inject injectedResult 3056 wantRes string 3057 }{ 3058 { 3059 name: "WaitOnPermit - No Wait", 3060 }, 3061 { 3062 name: "WaitOnPermit - Wait Timeout", 3063 inject: injectedResult{PermitStatus: int(framework.Wait)}, 3064 wantRes: "Unschedulable", 3065 }, 3066 } 3067 3068 for _, tt := range tests { 3069 t.Run(tt.name, func(t *testing.T) { 3070 metrics.Register() 3071 metrics.PermitWaitDuration.Reset() 3072 3073 plugin := &TestPlugin{name: testPlugin, inj: tt.inject} 3074 r := make(Registry) 3075 err := r.Register(testPlugin, 3076 func(_ context.Context, _ runtime.Object, fh framework.Handle) (framework.Plugin, error) { 3077 return plugin, nil 3078 }) 3079 if err != nil { 3080 t.Fatal(err) 3081 } 3082 plugins := &config.Plugins{ 3083 Permit: config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin, Weight: 1}}}, 3084 } 3085 profile := config.KubeSchedulerProfile{Plugins: plugins} 3086 ctx, cancel := context.WithCancel(context.Background()) 3087 defer cancel() 3088 f, err := newFrameworkWithQueueSortAndBind(ctx, r, profile) 3089 if err != nil { 3090 t.Fatalf("Failed to create framework for testing: %v", err) 3091 } 3092 3093 f.RunPermitPlugins(ctx, nil, pod, "") 3094 f.WaitOnPermit(ctx, pod) 3095 3096 collectAndComparePermitWaitDuration(t, tt.wantRes) 3097 }) 3098 } 3099 } 3100 3101 func TestWaitOnPermit(t *testing.T) { 3102 pod := &v1.Pod{ 3103 ObjectMeta: metav1.ObjectMeta{ 3104 Name: "pod", 3105 UID: types.UID("pod"), 3106 }, 3107 } 3108 3109 tests := []struct { 3110 name string 3111 action func(f framework.Framework) 3112 want *framework.Status 3113 }{ 3114 { 3115 name: "Reject Waiting Pod", 3116 action: func(f framework.Framework) { 3117 f.GetWaitingPod(pod.UID).Reject(permitPlugin, "reject message") 3118 }, 3119 want: framework.NewStatus(framework.Unschedulable, "reject message").WithPlugin(permitPlugin), 3120 }, 3121 { 3122 name: "Allow Waiting Pod", 3123 action: func(f framework.Framework) { 3124 f.GetWaitingPod(pod.UID).Allow(permitPlugin) 3125 }, 3126 want: nil, 3127 }, 3128 } 3129 3130 for _, tt := range tests { 3131 t.Run(tt.name, func(t *testing.T) { 3132 testPermitPlugin := &TestPermitPlugin{} 3133 r := make(Registry) 3134 r.Register(permitPlugin, 3135 func(_ context.Context, _ runtime.Object, fh framework.Handle) (framework.Plugin, error) { 3136 return testPermitPlugin, nil 3137 }) 3138 plugins := &config.Plugins{ 3139 Permit: config.PluginSet{Enabled: []config.Plugin{{Name: permitPlugin, Weight: 1}}}, 3140 } 3141 profile := config.KubeSchedulerProfile{Plugins: plugins} 3142 ctx, cancel := context.WithCancel(context.Background()) 3143 defer cancel() 3144 f, err := newFrameworkWithQueueSortAndBind(ctx, r, profile) 3145 if err != nil { 3146 t.Fatalf("Failed to create framework for testing: %v", err) 3147 } 3148 3149 runPermitPluginsStatus := f.RunPermitPlugins(ctx, nil, pod, "") 3150 if runPermitPluginsStatus.Code() != framework.Wait { 3151 t.Fatalf("Expected RunPermitPlugins to return status %v, but got %v", 3152 framework.Wait, runPermitPluginsStatus.Code()) 3153 } 3154 3155 go tt.action(f) 3156 3157 got := f.WaitOnPermit(ctx, pod) 3158 if !reflect.DeepEqual(tt.want, got) { 3159 t.Errorf("Unexpected status: want %v, but got %v", tt.want, got) 3160 } 3161 }) 3162 } 3163 } 3164 3165 func TestListPlugins(t *testing.T) { 3166 tests := []struct { 3167 name string 3168 plugins *config.Plugins 3169 want *config.Plugins 3170 }{ 3171 { 3172 name: "Add empty plugin", 3173 plugins: &config.Plugins{}, 3174 want: &config.Plugins{ 3175 QueueSort: config.PluginSet{Enabled: []config.Plugin{{Name: queueSortPlugin}}}, 3176 Bind: config.PluginSet{Enabled: []config.Plugin{{Name: bindPlugin}}}, 3177 }, 3178 }, 3179 { 3180 name: "Add multiple plugins", 3181 plugins: &config.Plugins{ 3182 Score: config.PluginSet{Enabled: []config.Plugin{{Name: scorePlugin1, Weight: 3}, {Name: scoreWithNormalizePlugin1}}}, 3183 }, 3184 want: &config.Plugins{ 3185 QueueSort: config.PluginSet{Enabled: []config.Plugin{{Name: queueSortPlugin}}}, 3186 Bind: config.PluginSet{Enabled: []config.Plugin{{Name: bindPlugin}}}, 3187 Score: config.PluginSet{Enabled: []config.Plugin{{Name: scorePlugin1, Weight: 3}, {Name: scoreWithNormalizePlugin1, Weight: 1}}}, 3188 }, 3189 }, 3190 } 3191 3192 for _, tt := range tests { 3193 t.Run(tt.name, func(t *testing.T) { 3194 profile := config.KubeSchedulerProfile{Plugins: tt.plugins} 3195 _, ctx := ktesting.NewTestContext(t) 3196 ctx, cancel := context.WithCancel(ctx) 3197 defer cancel() 3198 f, err := newFrameworkWithQueueSortAndBind(ctx, registry, profile) 3199 if err != nil { 3200 t.Fatalf("Failed to create framework for testing: %v", err) 3201 } 3202 got := f.ListPlugins() 3203 if diff := cmp.Diff(tt.want, got); diff != "" { 3204 t.Errorf("unexpected plugins (-want,+got):\n%s", diff) 3205 } 3206 }) 3207 } 3208 } 3209 3210 func buildScoreConfigDefaultWeights(ps ...string) *config.Plugins { 3211 return buildScoreConfigWithWeights(defaultWeights, ps...) 3212 } 3213 3214 func buildScoreConfigWithWeights(weights map[string]int32, ps ...string) *config.Plugins { 3215 var plugins []config.Plugin 3216 for _, p := range ps { 3217 plugins = append(plugins, config.Plugin{Name: p, Weight: weights[p]}) 3218 } 3219 return &config.Plugins{Score: config.PluginSet{Enabled: plugins}} 3220 } 3221 3222 type injectedResult struct { 3223 ScoreRes int64 `json:"scoreRes,omitempty"` 3224 NormalizeRes int64 `json:"normalizeRes,omitempty"` 3225 ScoreStatus int `json:"scoreStatus,omitempty"` 3226 NormalizeStatus int `json:"normalizeStatus,omitempty"` 3227 PreFilterStatus int `json:"preFilterStatus,omitempty"` 3228 PreFilterAddPodStatus int `json:"preFilterAddPodStatus,omitempty"` 3229 PreFilterRemovePodStatus int `json:"preFilterRemovePodStatus,omitempty"` 3230 FilterStatus int `json:"filterStatus,omitempty"` 3231 PostFilterStatus int `json:"postFilterStatus,omitempty"` 3232 PreScoreStatus int `json:"preScoreStatus,omitempty"` 3233 ReserveStatus int `json:"reserveStatus,omitempty"` 3234 PreBindStatus int `json:"preBindStatus,omitempty"` 3235 BindStatus int `json:"bindStatus,omitempty"` 3236 PermitStatus int `json:"permitStatus,omitempty"` 3237 } 3238 3239 func setScoreRes(inj injectedResult) (int64, *framework.Status) { 3240 if framework.Code(inj.ScoreStatus) != framework.Success { 3241 return 0, framework.NewStatus(framework.Code(inj.ScoreStatus), "injecting failure.") 3242 } 3243 return inj.ScoreRes, nil 3244 } 3245 3246 func injectNormalizeRes(inj injectedResult, scores framework.NodeScoreList) *framework.Status { 3247 if framework.Code(inj.NormalizeStatus) != framework.Success { 3248 return framework.NewStatus(framework.Code(inj.NormalizeStatus), "injecting failure.") 3249 } 3250 for i := range scores { 3251 scores[i].Score = inj.NormalizeRes 3252 } 3253 return nil 3254 } 3255 3256 func collectAndComparePluginMetrics(t *testing.T, wantExtensionPoint, wantPlugin string, wantStatus framework.Code) { 3257 t.Helper() 3258 m := metrics.PluginExecutionDuration.WithLabelValues(wantPlugin, wantExtensionPoint, wantStatus.String()) 3259 3260 count, err := testutil.GetHistogramMetricCount(m) 3261 if err != nil { 3262 t.Errorf("Failed to get %s sampleCount, err: %v", metrics.PluginExecutionDuration.Name, err) 3263 } 3264 if count == 0 { 3265 t.Error("Expect at least 1 sample") 3266 } 3267 value, err := testutil.GetHistogramMetricValue(m) 3268 if err != nil { 3269 t.Errorf("Failed to get %s value, err: %v", metrics.PluginExecutionDuration.Name, err) 3270 } 3271 checkLatency(t, value) 3272 } 3273 3274 func collectAndCompareFrameworkMetrics(t *testing.T, wantExtensionPoint string, wantStatus framework.Code) { 3275 t.Helper() 3276 m := metrics.FrameworkExtensionPointDuration.WithLabelValues(wantExtensionPoint, wantStatus.String(), testProfileName) 3277 3278 count, err := testutil.GetHistogramMetricCount(m) 3279 if err != nil { 3280 t.Errorf("Failed to get %s sampleCount, err: %v", metrics.FrameworkExtensionPointDuration.Name, err) 3281 } 3282 if count != 1 { 3283 t.Errorf("Expect 1 sample, got: %v", count) 3284 } 3285 value, err := testutil.GetHistogramMetricValue(m) 3286 if err != nil { 3287 t.Errorf("Failed to get %s value, err: %v", metrics.FrameworkExtensionPointDuration.Name, err) 3288 } 3289 checkLatency(t, value) 3290 } 3291 3292 func collectAndComparePermitWaitDuration(t *testing.T, wantRes string) { 3293 m := metrics.PermitWaitDuration.WithLabelValues(wantRes) 3294 count, err := testutil.GetHistogramMetricCount(m) 3295 if err != nil { 3296 t.Errorf("Failed to get %s sampleCount, err: %v", metrics.PermitWaitDuration.Name, err) 3297 } 3298 if wantRes == "" { 3299 if count != 0 { 3300 t.Errorf("Expect 0 sample, got: %v", count) 3301 } 3302 } else { 3303 if count != 1 { 3304 t.Errorf("Expect 1 sample, got: %v", count) 3305 } 3306 value, err := testutil.GetHistogramMetricValue(m) 3307 if err != nil { 3308 t.Errorf("Failed to get %s value, err: %v", metrics.PermitWaitDuration.Name, err) 3309 } 3310 checkLatency(t, value) 3311 } 3312 } 3313 3314 func mustNewPodInfo(t *testing.T, pod *v1.Pod) *framework.PodInfo { 3315 podInfo, err := framework.NewPodInfo(pod) 3316 if err != nil { 3317 t.Fatal(err) 3318 } 3319 return podInfo 3320 }