k8s.io/kubernetes@v1.29.3/pkg/scheduler/framework/runtime/framework.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 "fmt" 22 "reflect" 23 "sort" 24 "time" 25 26 v1 "k8s.io/api/core/v1" 27 "k8s.io/apimachinery/pkg/runtime" 28 "k8s.io/apimachinery/pkg/types" 29 "k8s.io/apimachinery/pkg/util/sets" 30 "k8s.io/client-go/informers" 31 clientset "k8s.io/client-go/kubernetes" 32 restclient "k8s.io/client-go/rest" 33 "k8s.io/client-go/tools/events" 34 "k8s.io/component-helpers/scheduling/corev1" 35 "k8s.io/klog/v2" 36 "k8s.io/kubernetes/pkg/scheduler/apis/config" 37 "k8s.io/kubernetes/pkg/scheduler/framework" 38 "k8s.io/kubernetes/pkg/scheduler/framework/parallelize" 39 "k8s.io/kubernetes/pkg/scheduler/metrics" 40 "k8s.io/kubernetes/pkg/util/slice" 41 ) 42 43 const ( 44 // Specifies the maximum timeout a permit plugin can return. 45 maxTimeout = 15 * time.Minute 46 ) 47 48 // frameworkImpl is the component responsible for initializing and running scheduler 49 // plugins. 50 type frameworkImpl struct { 51 registry Registry 52 snapshotSharedLister framework.SharedLister 53 waitingPods *waitingPodsMap 54 scorePluginWeight map[string]int 55 preEnqueuePlugins []framework.PreEnqueuePlugin 56 enqueueExtensions []framework.EnqueueExtensions 57 queueSortPlugins []framework.QueueSortPlugin 58 preFilterPlugins []framework.PreFilterPlugin 59 filterPlugins []framework.FilterPlugin 60 postFilterPlugins []framework.PostFilterPlugin 61 preScorePlugins []framework.PreScorePlugin 62 scorePlugins []framework.ScorePlugin 63 reservePlugins []framework.ReservePlugin 64 preBindPlugins []framework.PreBindPlugin 65 bindPlugins []framework.BindPlugin 66 postBindPlugins []framework.PostBindPlugin 67 permitPlugins []framework.PermitPlugin 68 69 clientSet clientset.Interface 70 kubeConfig *restclient.Config 71 eventRecorder events.EventRecorder 72 informerFactory informers.SharedInformerFactory 73 logger klog.Logger 74 75 metricsRecorder *metrics.MetricAsyncRecorder 76 profileName string 77 percentageOfNodesToScore *int32 78 79 extenders []framework.Extender 80 framework.PodNominator 81 82 parallelizer parallelize.Parallelizer 83 } 84 85 // extensionPoint encapsulates desired and applied set of plugins at a specific extension 86 // point. This is used to simplify iterating over all extension points supported by the 87 // frameworkImpl. 88 type extensionPoint struct { 89 // the set of plugins to be configured at this extension point. 90 plugins *config.PluginSet 91 // a pointer to the slice storing plugins implementations that will run at this 92 // extension point. 93 slicePtr interface{} 94 } 95 96 func (f *frameworkImpl) getExtensionPoints(plugins *config.Plugins) []extensionPoint { 97 return []extensionPoint{ 98 {&plugins.PreFilter, &f.preFilterPlugins}, 99 {&plugins.Filter, &f.filterPlugins}, 100 {&plugins.PostFilter, &f.postFilterPlugins}, 101 {&plugins.Reserve, &f.reservePlugins}, 102 {&plugins.PreScore, &f.preScorePlugins}, 103 {&plugins.Score, &f.scorePlugins}, 104 {&plugins.PreBind, &f.preBindPlugins}, 105 {&plugins.Bind, &f.bindPlugins}, 106 {&plugins.PostBind, &f.postBindPlugins}, 107 {&plugins.Permit, &f.permitPlugins}, 108 {&plugins.PreEnqueue, &f.preEnqueuePlugins}, 109 {&plugins.QueueSort, &f.queueSortPlugins}, 110 } 111 } 112 113 // Extenders returns the registered extenders. 114 func (f *frameworkImpl) Extenders() []framework.Extender { 115 return f.extenders 116 } 117 118 type frameworkOptions struct { 119 componentConfigVersion string 120 clientSet clientset.Interface 121 kubeConfig *restclient.Config 122 eventRecorder events.EventRecorder 123 informerFactory informers.SharedInformerFactory 124 snapshotSharedLister framework.SharedLister 125 metricsRecorder *metrics.MetricAsyncRecorder 126 podNominator framework.PodNominator 127 extenders []framework.Extender 128 captureProfile CaptureProfile 129 parallelizer parallelize.Parallelizer 130 logger *klog.Logger 131 } 132 133 // Option for the frameworkImpl. 134 type Option func(*frameworkOptions) 135 136 // WithComponentConfigVersion sets the component config version to the 137 // KubeSchedulerConfiguration version used. The string should be the full 138 // scheme group/version of the external type we converted from (for example 139 // "kubescheduler.config.k8s.io/v1") 140 func WithComponentConfigVersion(componentConfigVersion string) Option { 141 return func(o *frameworkOptions) { 142 o.componentConfigVersion = componentConfigVersion 143 } 144 } 145 146 // WithClientSet sets clientSet for the scheduling frameworkImpl. 147 func WithClientSet(clientSet clientset.Interface) Option { 148 return func(o *frameworkOptions) { 149 o.clientSet = clientSet 150 } 151 } 152 153 // WithKubeConfig sets kubeConfig for the scheduling frameworkImpl. 154 func WithKubeConfig(kubeConfig *restclient.Config) Option { 155 return func(o *frameworkOptions) { 156 o.kubeConfig = kubeConfig 157 } 158 } 159 160 // WithEventRecorder sets clientSet for the scheduling frameworkImpl. 161 func WithEventRecorder(recorder events.EventRecorder) Option { 162 return func(o *frameworkOptions) { 163 o.eventRecorder = recorder 164 } 165 } 166 167 // WithInformerFactory sets informer factory for the scheduling frameworkImpl. 168 func WithInformerFactory(informerFactory informers.SharedInformerFactory) Option { 169 return func(o *frameworkOptions) { 170 o.informerFactory = informerFactory 171 } 172 } 173 174 // WithSnapshotSharedLister sets the SharedLister of the snapshot. 175 func WithSnapshotSharedLister(snapshotSharedLister framework.SharedLister) Option { 176 return func(o *frameworkOptions) { 177 o.snapshotSharedLister = snapshotSharedLister 178 } 179 } 180 181 // WithPodNominator sets podNominator for the scheduling frameworkImpl. 182 func WithPodNominator(nominator framework.PodNominator) Option { 183 return func(o *frameworkOptions) { 184 o.podNominator = nominator 185 } 186 } 187 188 // WithExtenders sets extenders for the scheduling frameworkImpl. 189 func WithExtenders(extenders []framework.Extender) Option { 190 return func(o *frameworkOptions) { 191 o.extenders = extenders 192 } 193 } 194 195 // WithParallelism sets parallelism for the scheduling frameworkImpl. 196 func WithParallelism(parallelism int) Option { 197 return func(o *frameworkOptions) { 198 o.parallelizer = parallelize.NewParallelizer(parallelism) 199 } 200 } 201 202 // CaptureProfile is a callback to capture a finalized profile. 203 type CaptureProfile func(config.KubeSchedulerProfile) 204 205 // WithCaptureProfile sets a callback to capture the finalized profile. 206 func WithCaptureProfile(c CaptureProfile) Option { 207 return func(o *frameworkOptions) { 208 o.captureProfile = c 209 } 210 } 211 212 // WithMetricsRecorder sets metrics recorder for the scheduling frameworkImpl. 213 func WithMetricsRecorder(r *metrics.MetricAsyncRecorder) Option { 214 return func(o *frameworkOptions) { 215 o.metricsRecorder = r 216 } 217 } 218 219 // WithLogger overrides the default logger from k8s.io/klog. 220 func WithLogger(logger klog.Logger) Option { 221 return func(o *frameworkOptions) { 222 o.logger = &logger 223 } 224 } 225 226 // defaultFrameworkOptions are applied when no option corresponding to those fields exist. 227 func defaultFrameworkOptions(stopCh <-chan struct{}) frameworkOptions { 228 return frameworkOptions{ 229 metricsRecorder: metrics.NewMetricsAsyncRecorder(1000, time.Second, stopCh), 230 parallelizer: parallelize.NewParallelizer(parallelize.DefaultParallelism), 231 } 232 } 233 234 var _ framework.Framework = &frameworkImpl{} 235 236 // NewFramework initializes plugins given the configuration and the registry. 237 func NewFramework(ctx context.Context, r Registry, profile *config.KubeSchedulerProfile, opts ...Option) (framework.Framework, error) { 238 options := defaultFrameworkOptions(ctx.Done()) 239 for _, opt := range opts { 240 opt(&options) 241 } 242 243 logger := klog.FromContext(ctx) 244 if options.logger != nil { 245 logger = *options.logger 246 } 247 248 f := &frameworkImpl{ 249 registry: r, 250 snapshotSharedLister: options.snapshotSharedLister, 251 scorePluginWeight: make(map[string]int), 252 waitingPods: newWaitingPodsMap(), 253 clientSet: options.clientSet, 254 kubeConfig: options.kubeConfig, 255 eventRecorder: options.eventRecorder, 256 informerFactory: options.informerFactory, 257 metricsRecorder: options.metricsRecorder, 258 extenders: options.extenders, 259 PodNominator: options.podNominator, 260 parallelizer: options.parallelizer, 261 logger: logger, 262 } 263 264 if profile == nil { 265 return f, nil 266 } 267 268 f.profileName = profile.SchedulerName 269 f.percentageOfNodesToScore = profile.PercentageOfNodesToScore 270 if profile.Plugins == nil { 271 return f, nil 272 } 273 274 // get needed plugins from config 275 pg := f.pluginsNeeded(profile.Plugins) 276 277 pluginConfig := make(map[string]runtime.Object, len(profile.PluginConfig)) 278 for i := range profile.PluginConfig { 279 name := profile.PluginConfig[i].Name 280 if _, ok := pluginConfig[name]; ok { 281 return nil, fmt.Errorf("repeated config for plugin %s", name) 282 } 283 pluginConfig[name] = profile.PluginConfig[i].Args 284 } 285 outputProfile := config.KubeSchedulerProfile{ 286 SchedulerName: f.profileName, 287 PercentageOfNodesToScore: f.percentageOfNodesToScore, 288 Plugins: profile.Plugins, 289 PluginConfig: make([]config.PluginConfig, 0, len(pg)), 290 } 291 292 pluginsMap := make(map[string]framework.Plugin) 293 for name, factory := range r { 294 // initialize only needed plugins. 295 if !pg.Has(name) { 296 continue 297 } 298 299 args := pluginConfig[name] 300 if args != nil { 301 outputProfile.PluginConfig = append(outputProfile.PluginConfig, config.PluginConfig{ 302 Name: name, 303 Args: args, 304 }) 305 } 306 p, err := factory(ctx, args, f) 307 if err != nil { 308 return nil, fmt.Errorf("initializing plugin %q: %w", name, err) 309 } 310 pluginsMap[name] = p 311 312 f.fillEnqueueExtensions(p) 313 } 314 315 // initialize plugins per individual extension points 316 for _, e := range f.getExtensionPoints(profile.Plugins) { 317 if err := updatePluginList(e.slicePtr, *e.plugins, pluginsMap); err != nil { 318 return nil, err 319 } 320 } 321 322 // initialize multiPoint plugins to their expanded extension points 323 if len(profile.Plugins.MultiPoint.Enabled) > 0 { 324 if err := f.expandMultiPointPlugins(logger, profile, pluginsMap); err != nil { 325 return nil, err 326 } 327 } 328 329 if len(f.queueSortPlugins) != 1 { 330 return nil, fmt.Errorf("only one queue sort plugin required for profile with scheduler name %q, but got %d", profile.SchedulerName, len(f.queueSortPlugins)) 331 } 332 if len(f.bindPlugins) == 0 { 333 return nil, fmt.Errorf("at least one bind plugin is needed for profile with scheduler name %q", profile.SchedulerName) 334 } 335 336 if err := getScoreWeights(f, pluginsMap, append(profile.Plugins.Score.Enabled, profile.Plugins.MultiPoint.Enabled...)); err != nil { 337 return nil, err 338 } 339 340 // Verifying the score weights again since Plugin.Name() could return a different 341 // value from the one used in the configuration. 342 for _, scorePlugin := range f.scorePlugins { 343 if f.scorePluginWeight[scorePlugin.Name()] == 0 { 344 return nil, fmt.Errorf("score plugin %q is not configured with weight", scorePlugin.Name()) 345 } 346 } 347 348 if options.captureProfile != nil { 349 if len(outputProfile.PluginConfig) != 0 { 350 sort.Slice(outputProfile.PluginConfig, func(i, j int) bool { 351 return outputProfile.PluginConfig[i].Name < outputProfile.PluginConfig[j].Name 352 }) 353 } else { 354 outputProfile.PluginConfig = nil 355 } 356 options.captureProfile(outputProfile) 357 } 358 359 f.setInstrumentedPlugins() 360 return f, nil 361 } 362 363 // setInstrumentedPlugins initializes instrumented plugins from current plugins that frameworkImpl has. 364 func (f *frameworkImpl) setInstrumentedPlugins() { 365 // Cache metric streams for prefilter and filter plugins. 366 for i, pl := range f.preFilterPlugins { 367 f.preFilterPlugins[i] = &instrumentedPreFilterPlugin{ 368 PreFilterPlugin: f.preFilterPlugins[i], 369 metric: metrics.PluginEvaluationTotal.WithLabelValues(pl.Name(), metrics.PreFilter, f.profileName), 370 } 371 } 372 for i, pl := range f.filterPlugins { 373 f.filterPlugins[i] = &instrumentedFilterPlugin{ 374 FilterPlugin: f.filterPlugins[i], 375 metric: metrics.PluginEvaluationTotal.WithLabelValues(pl.Name(), metrics.Filter, f.profileName), 376 } 377 } 378 379 // Cache metric streams for prescore and score plugins. 380 for i, pl := range f.preScorePlugins { 381 f.preScorePlugins[i] = &instrumentedPreScorePlugin{ 382 PreScorePlugin: f.preScorePlugins[i], 383 metric: metrics.PluginEvaluationTotal.WithLabelValues(pl.Name(), metrics.PreScore, f.profileName), 384 } 385 } 386 for i, pl := range f.scorePlugins { 387 f.scorePlugins[i] = &instrumentedScorePlugin{ 388 ScorePlugin: f.scorePlugins[i], 389 metric: metrics.PluginEvaluationTotal.WithLabelValues(pl.Name(), metrics.Score, f.profileName), 390 } 391 } 392 } 393 394 func (f *frameworkImpl) SetPodNominator(n framework.PodNominator) { 395 f.PodNominator = n 396 } 397 398 // getScoreWeights makes sure that, between MultiPoint-Score plugin weights and individual Score 399 // plugin weights there is not an overflow of MaxTotalScore. 400 func getScoreWeights(f *frameworkImpl, pluginsMap map[string]framework.Plugin, plugins []config.Plugin) error { 401 var totalPriority int64 402 scorePlugins := reflect.ValueOf(&f.scorePlugins).Elem() 403 pluginType := scorePlugins.Type().Elem() 404 for _, e := range plugins { 405 pg := pluginsMap[e.Name] 406 if !reflect.TypeOf(pg).Implements(pluginType) { 407 continue 408 } 409 410 // We append MultiPoint plugins to the list of Score plugins. So if this plugin has already been 411 // encountered, let the individual Score weight take precedence. 412 if _, ok := f.scorePluginWeight[e.Name]; ok { 413 continue 414 } 415 // a weight of zero is not permitted, plugins can be disabled explicitly 416 // when configured. 417 f.scorePluginWeight[e.Name] = int(e.Weight) 418 if f.scorePluginWeight[e.Name] == 0 { 419 f.scorePluginWeight[e.Name] = 1 420 } 421 422 // Checks totalPriority against MaxTotalScore to avoid overflow 423 if int64(f.scorePluginWeight[e.Name])*framework.MaxNodeScore > framework.MaxTotalScore-totalPriority { 424 return fmt.Errorf("total score of Score plugins could overflow") 425 } 426 totalPriority += int64(f.scorePluginWeight[e.Name]) * framework.MaxNodeScore 427 } 428 return nil 429 } 430 431 type orderedSet struct { 432 set map[string]int 433 list []string 434 deletionCnt int 435 } 436 437 func newOrderedSet() *orderedSet { 438 return &orderedSet{set: make(map[string]int)} 439 } 440 441 func (os *orderedSet) insert(s string) { 442 if os.has(s) { 443 return 444 } 445 os.set[s] = len(os.list) 446 os.list = append(os.list, s) 447 } 448 449 func (os *orderedSet) has(s string) bool { 450 _, found := os.set[s] 451 return found 452 } 453 454 func (os *orderedSet) delete(s string) { 455 if i, found := os.set[s]; found { 456 delete(os.set, s) 457 os.list = append(os.list[:i-os.deletionCnt], os.list[i+1-os.deletionCnt:]...) 458 os.deletionCnt++ 459 } 460 } 461 462 func (f *frameworkImpl) expandMultiPointPlugins(logger klog.Logger, profile *config.KubeSchedulerProfile, pluginsMap map[string]framework.Plugin) error { 463 // initialize MultiPoint plugins 464 for _, e := range f.getExtensionPoints(profile.Plugins) { 465 plugins := reflect.ValueOf(e.slicePtr).Elem() 466 pluginType := plugins.Type().Elem() 467 // build enabledSet of plugins already registered via normal extension points 468 // to check double registration 469 enabledSet := newOrderedSet() 470 for _, plugin := range e.plugins.Enabled { 471 enabledSet.insert(plugin.Name) 472 } 473 474 disabledSet := sets.New[string]() 475 for _, disabledPlugin := range e.plugins.Disabled { 476 disabledSet.Insert(disabledPlugin.Name) 477 } 478 if disabledSet.Has("*") { 479 logger.V(4).Info("Skipped MultiPoint expansion because all plugins are disabled for extension point", "extension", pluginType) 480 continue 481 } 482 483 // track plugins enabled via multipoint separately from those enabled by specific extensions, 484 // so that we can distinguish between double-registration and explicit overrides 485 multiPointEnabled := newOrderedSet() 486 overridePlugins := newOrderedSet() 487 for _, ep := range profile.Plugins.MultiPoint.Enabled { 488 pg, ok := pluginsMap[ep.Name] 489 if !ok { 490 return fmt.Errorf("%s %q does not exist", pluginType.Name(), ep.Name) 491 } 492 493 // if this plugin doesn't implement the type for the current extension we're trying to expand, skip 494 if !reflect.TypeOf(pg).Implements(pluginType) { 495 continue 496 } 497 498 // a plugin that's enabled via MultiPoint can still be disabled for specific extension points 499 if disabledSet.Has(ep.Name) { 500 logger.V(4).Info("Skipped disabled plugin for extension point", "plugin", ep.Name, "extension", pluginType) 501 continue 502 } 503 504 // if this plugin has already been enabled by the specific extension point, 505 // the user intent is to override the default plugin or make some other explicit setting. 506 // Either way, discard the MultiPoint value for this plugin. 507 // This maintains expected behavior for overriding default plugins (see https://github.com/kubernetes/kubernetes/pull/99582) 508 if enabledSet.has(ep.Name) { 509 overridePlugins.insert(ep.Name) 510 logger.Info("MultiPoint plugin is explicitly re-configured; overriding", "plugin", ep.Name) 511 continue 512 } 513 514 // if this plugin is already registered via MultiPoint, then this is 515 // a double registration and an error in the config. 516 if multiPointEnabled.has(ep.Name) { 517 return fmt.Errorf("plugin %q already registered as %q", ep.Name, pluginType.Name()) 518 } 519 520 // we only need to update the multipoint set, since we already have the specific extension set from above 521 multiPointEnabled.insert(ep.Name) 522 } 523 524 // Reorder plugins. Here is the expected order: 525 // - part 1: overridePlugins. Their order stay intact as how they're specified in regular extension point. 526 // - part 2: multiPointEnabled - i.e., plugin defined in multipoint but not in regular extension point. 527 // - part 3: other plugins (excluded by part 1 & 2) in regular extension point. 528 newPlugins := reflect.New(reflect.TypeOf(e.slicePtr).Elem()).Elem() 529 // part 1 530 for _, name := range slice.CopyStrings(enabledSet.list) { 531 if overridePlugins.has(name) { 532 newPlugins = reflect.Append(newPlugins, reflect.ValueOf(pluginsMap[name])) 533 enabledSet.delete(name) 534 } 535 } 536 // part 2 537 for _, name := range multiPointEnabled.list { 538 newPlugins = reflect.Append(newPlugins, reflect.ValueOf(pluginsMap[name])) 539 } 540 // part 3 541 for _, name := range enabledSet.list { 542 newPlugins = reflect.Append(newPlugins, reflect.ValueOf(pluginsMap[name])) 543 } 544 plugins.Set(newPlugins) 545 } 546 return nil 547 } 548 549 func shouldHaveEnqueueExtensions(p framework.Plugin) bool { 550 switch p.(type) { 551 // Only PreEnqueue, PreFilter, Filter, Reserve, and Permit plugins can (should) have EnqueueExtensions. 552 // See the comment of EnqueueExtensions for more detailed reason here. 553 case framework.PreEnqueuePlugin, framework.PreFilterPlugin, framework.FilterPlugin, framework.ReservePlugin, framework.PermitPlugin: 554 return true 555 } 556 return false 557 } 558 559 func (f *frameworkImpl) fillEnqueueExtensions(p framework.Plugin) { 560 if !shouldHaveEnqueueExtensions(p) { 561 // Ignore EnqueueExtensions from plugin which isn't PreEnqueue, PreFilter, Filter, Reserve, and Permit. 562 return 563 } 564 565 ext, ok := p.(framework.EnqueueExtensions) 566 if !ok { 567 // If interface EnqueueExtensions is not implemented, register the default enqueue extensions 568 // to the plugin because we don't know which events the plugin is interested in. 569 // This is to ensure backward compatibility. 570 f.enqueueExtensions = append(f.enqueueExtensions, &defaultEnqueueExtension{pluginName: p.Name()}) 571 return 572 } 573 574 f.enqueueExtensions = append(f.enqueueExtensions, ext) 575 } 576 577 // defaultEnqueueExtension is used when a plugin does not implement EnqueueExtensions interface. 578 type defaultEnqueueExtension struct { 579 pluginName string 580 } 581 582 func (p *defaultEnqueueExtension) Name() string { return p.pluginName } 583 func (p *defaultEnqueueExtension) EventsToRegister() []framework.ClusterEventWithHint { 584 // need to return all specific cluster events with framework.All action instead of wildcard event 585 // because the returning values are used to register event handlers. 586 // If we return the wildcard here, it won't affect the event handlers registered by the plugin 587 // and some events may not be registered in the event handlers. 588 return framework.UnrollWildCardResource() 589 } 590 591 func updatePluginList(pluginList interface{}, pluginSet config.PluginSet, pluginsMap map[string]framework.Plugin) error { 592 plugins := reflect.ValueOf(pluginList).Elem() 593 pluginType := plugins.Type().Elem() 594 set := sets.New[string]() 595 for _, ep := range pluginSet.Enabled { 596 pg, ok := pluginsMap[ep.Name] 597 if !ok { 598 return fmt.Errorf("%s %q does not exist", pluginType.Name(), ep.Name) 599 } 600 601 if !reflect.TypeOf(pg).Implements(pluginType) { 602 return fmt.Errorf("plugin %q does not extend %s plugin", ep.Name, pluginType.Name()) 603 } 604 605 if set.Has(ep.Name) { 606 return fmt.Errorf("plugin %q already registered as %q", ep.Name, pluginType.Name()) 607 } 608 609 set.Insert(ep.Name) 610 611 newPlugins := reflect.Append(plugins, reflect.ValueOf(pg)) 612 plugins.Set(newPlugins) 613 } 614 return nil 615 } 616 617 // PreEnqueuePlugins returns the registered preEnqueue plugins. 618 func (f *frameworkImpl) PreEnqueuePlugins() []framework.PreEnqueuePlugin { 619 return f.preEnqueuePlugins 620 } 621 622 // EnqueueExtensions returns the registered reenqueue plugins. 623 func (f *frameworkImpl) EnqueueExtensions() []framework.EnqueueExtensions { 624 return f.enqueueExtensions 625 } 626 627 // QueueSortFunc returns the function to sort pods in scheduling queue 628 func (f *frameworkImpl) QueueSortFunc() framework.LessFunc { 629 if f == nil { 630 // If frameworkImpl is nil, simply keep their order unchanged. 631 // NOTE: this is primarily for tests. 632 return func(_, _ *framework.QueuedPodInfo) bool { return false } 633 } 634 635 if len(f.queueSortPlugins) == 0 { 636 panic("No QueueSort plugin is registered in the frameworkImpl.") 637 } 638 639 // Only one QueueSort plugin can be enabled. 640 return f.queueSortPlugins[0].Less 641 } 642 643 // RunPreFilterPlugins runs the set of configured PreFilter plugins. It returns 644 // *Status and its code is set to non-success if any of the plugins returns 645 // anything but Success/Skip. 646 // When it returns Skip status, returned PreFilterResult and other fields in status are just ignored, 647 // and coupled Filter plugin/PreFilterExtensions() will be skipped in this scheduling cycle. 648 // If a non-success status is returned, then the scheduling cycle is aborted. 649 func (f *frameworkImpl) RunPreFilterPlugins(ctx context.Context, state *framework.CycleState, pod *v1.Pod) (_ *framework.PreFilterResult, status *framework.Status) { 650 startTime := time.Now() 651 skipPlugins := sets.New[string]() 652 defer func() { 653 state.SkipFilterPlugins = skipPlugins 654 metrics.FrameworkExtensionPointDuration.WithLabelValues(metrics.PreFilter, status.Code().String(), f.profileName).Observe(metrics.SinceInSeconds(startTime)) 655 }() 656 var result *framework.PreFilterResult 657 var pluginsWithNodes []string 658 logger := klog.FromContext(ctx) 659 verboseLogs := logger.V(4).Enabled() 660 if verboseLogs { 661 logger = klog.LoggerWithName(logger, "PreFilter") 662 } 663 for _, pl := range f.preFilterPlugins { 664 ctx := ctx 665 if verboseLogs { 666 logger := klog.LoggerWithName(logger, pl.Name()) 667 ctx = klog.NewContext(ctx, logger) 668 } 669 r, s := f.runPreFilterPlugin(ctx, pl, state, pod) 670 if s.IsSkip() { 671 skipPlugins.Insert(pl.Name()) 672 continue 673 } 674 if !s.IsSuccess() { 675 s.SetPlugin(pl.Name()) 676 if s.IsRejected() { 677 return nil, s 678 } 679 return nil, framework.AsStatus(fmt.Errorf("running PreFilter plugin %q: %w", pl.Name(), s.AsError())).WithPlugin(pl.Name()) 680 } 681 if !r.AllNodes() { 682 pluginsWithNodes = append(pluginsWithNodes, pl.Name()) 683 } 684 result = result.Merge(r) 685 if !result.AllNodes() && len(result.NodeNames) == 0 { 686 msg := fmt.Sprintf("node(s) didn't satisfy plugin(s) %v simultaneously", pluginsWithNodes) 687 if len(pluginsWithNodes) == 1 { 688 msg = fmt.Sprintf("node(s) didn't satisfy plugin %v", pluginsWithNodes[0]) 689 } 690 return nil, framework.NewStatus(framework.Unschedulable, msg) 691 } 692 } 693 return result, nil 694 } 695 696 func (f *frameworkImpl) runPreFilterPlugin(ctx context.Context, pl framework.PreFilterPlugin, state *framework.CycleState, pod *v1.Pod) (*framework.PreFilterResult, *framework.Status) { 697 if !state.ShouldRecordPluginMetrics() { 698 return pl.PreFilter(ctx, state, pod) 699 } 700 startTime := time.Now() 701 result, status := pl.PreFilter(ctx, state, pod) 702 f.metricsRecorder.ObservePluginDurationAsync(metrics.PreFilter, pl.Name(), status.Code().String(), metrics.SinceInSeconds(startTime)) 703 return result, status 704 } 705 706 // RunPreFilterExtensionAddPod calls the AddPod interface for the set of configured 707 // PreFilter plugins. It returns directly if any of the plugins return any 708 // status other than Success. 709 func (f *frameworkImpl) RunPreFilterExtensionAddPod( 710 ctx context.Context, 711 state *framework.CycleState, 712 podToSchedule *v1.Pod, 713 podInfoToAdd *framework.PodInfo, 714 nodeInfo *framework.NodeInfo, 715 ) (status *framework.Status) { 716 logger := klog.FromContext(ctx) 717 verboseLogs := logger.V(4).Enabled() 718 if verboseLogs { 719 logger = klog.LoggerWithName(logger, "PreFilterExtension") 720 } 721 for _, pl := range f.preFilterPlugins { 722 if pl.PreFilterExtensions() == nil || state.SkipFilterPlugins.Has(pl.Name()) { 723 continue 724 } 725 ctx := ctx 726 if verboseLogs { 727 logger := klog.LoggerWithName(logger, pl.Name()) 728 ctx = klog.NewContext(ctx, logger) 729 } 730 status = f.runPreFilterExtensionAddPod(ctx, pl, state, podToSchedule, podInfoToAdd, nodeInfo) 731 if !status.IsSuccess() { 732 err := status.AsError() 733 logger.Error(err, "Plugin failed", "pod", klog.KObj(podToSchedule), "node", klog.KObj(nodeInfo.Node()), "operation", "addPod", "plugin", pl.Name()) 734 return framework.AsStatus(fmt.Errorf("running AddPod on PreFilter plugin %q: %w", pl.Name(), err)) 735 } 736 } 737 738 return nil 739 } 740 741 func (f *frameworkImpl) runPreFilterExtensionAddPod(ctx context.Context, pl framework.PreFilterPlugin, state *framework.CycleState, podToSchedule *v1.Pod, podInfoToAdd *framework.PodInfo, nodeInfo *framework.NodeInfo) *framework.Status { 742 if !state.ShouldRecordPluginMetrics() { 743 return pl.PreFilterExtensions().AddPod(ctx, state, podToSchedule, podInfoToAdd, nodeInfo) 744 } 745 startTime := time.Now() 746 status := pl.PreFilterExtensions().AddPod(ctx, state, podToSchedule, podInfoToAdd, nodeInfo) 747 f.metricsRecorder.ObservePluginDurationAsync(metrics.PreFilterExtensionAddPod, pl.Name(), status.Code().String(), metrics.SinceInSeconds(startTime)) 748 return status 749 } 750 751 // RunPreFilterExtensionRemovePod calls the RemovePod interface for the set of configured 752 // PreFilter plugins. It returns directly if any of the plugins return any 753 // status other than Success. 754 func (f *frameworkImpl) RunPreFilterExtensionRemovePod( 755 ctx context.Context, 756 state *framework.CycleState, 757 podToSchedule *v1.Pod, 758 podInfoToRemove *framework.PodInfo, 759 nodeInfo *framework.NodeInfo, 760 ) (status *framework.Status) { 761 logger := klog.FromContext(ctx) 762 verboseLogs := logger.V(4).Enabled() 763 if verboseLogs { 764 logger = klog.LoggerWithName(logger, "PreFilterExtension") 765 } 766 for _, pl := range f.preFilterPlugins { 767 if pl.PreFilterExtensions() == nil || state.SkipFilterPlugins.Has(pl.Name()) { 768 continue 769 } 770 ctx := ctx 771 if verboseLogs { 772 logger := klog.LoggerWithName(logger, pl.Name()) 773 ctx = klog.NewContext(ctx, logger) 774 } 775 status = f.runPreFilterExtensionRemovePod(ctx, pl, state, podToSchedule, podInfoToRemove, nodeInfo) 776 if !status.IsSuccess() { 777 err := status.AsError() 778 logger.Error(err, "Plugin failed", "node", klog.KObj(nodeInfo.Node()), "operation", "removePod", "plugin", pl.Name(), "pod", klog.KObj(podToSchedule)) 779 return framework.AsStatus(fmt.Errorf("running RemovePod on PreFilter plugin %q: %w", pl.Name(), err)) 780 } 781 } 782 783 return nil 784 } 785 786 func (f *frameworkImpl) runPreFilterExtensionRemovePod(ctx context.Context, pl framework.PreFilterPlugin, state *framework.CycleState, podToSchedule *v1.Pod, podInfoToRemove *framework.PodInfo, nodeInfo *framework.NodeInfo) *framework.Status { 787 if !state.ShouldRecordPluginMetrics() { 788 return pl.PreFilterExtensions().RemovePod(ctx, state, podToSchedule, podInfoToRemove, nodeInfo) 789 } 790 startTime := time.Now() 791 status := pl.PreFilterExtensions().RemovePod(ctx, state, podToSchedule, podInfoToRemove, nodeInfo) 792 f.metricsRecorder.ObservePluginDurationAsync(metrics.PreFilterExtensionRemovePod, pl.Name(), status.Code().String(), metrics.SinceInSeconds(startTime)) 793 return status 794 } 795 796 // RunFilterPlugins runs the set of configured Filter plugins for pod on 797 // the given node. If any of these plugins doesn't return "Success", the 798 // given node is not suitable for running pod. 799 // Meanwhile, the failure message and status are set for the given node. 800 func (f *frameworkImpl) RunFilterPlugins( 801 ctx context.Context, 802 state *framework.CycleState, 803 pod *v1.Pod, 804 nodeInfo *framework.NodeInfo, 805 ) *framework.Status { 806 logger := klog.FromContext(ctx) 807 verboseLogs := logger.V(4).Enabled() 808 if verboseLogs { 809 logger = klog.LoggerWithName(logger, "Filter") 810 } 811 812 for _, pl := range f.filterPlugins { 813 if state.SkipFilterPlugins.Has(pl.Name()) { 814 continue 815 } 816 ctx := ctx 817 if verboseLogs { 818 logger := klog.LoggerWithName(logger, pl.Name()) 819 ctx = klog.NewContext(ctx, logger) 820 } 821 if status := f.runFilterPlugin(ctx, pl, state, pod, nodeInfo); !status.IsSuccess() { 822 if !status.IsRejected() { 823 // Filter plugins are not supposed to return any status other than 824 // Success or Unschedulable. 825 status = framework.AsStatus(fmt.Errorf("running %q filter plugin: %w", pl.Name(), status.AsError())) 826 } 827 status.SetPlugin(pl.Name()) 828 return status 829 } 830 } 831 832 return nil 833 } 834 835 func (f *frameworkImpl) runFilterPlugin(ctx context.Context, pl framework.FilterPlugin, state *framework.CycleState, pod *v1.Pod, nodeInfo *framework.NodeInfo) *framework.Status { 836 if !state.ShouldRecordPluginMetrics() { 837 return pl.Filter(ctx, state, pod, nodeInfo) 838 } 839 startTime := time.Now() 840 status := pl.Filter(ctx, state, pod, nodeInfo) 841 f.metricsRecorder.ObservePluginDurationAsync(metrics.Filter, pl.Name(), status.Code().String(), metrics.SinceInSeconds(startTime)) 842 return status 843 } 844 845 // RunPostFilterPlugins runs the set of configured PostFilter plugins until the first 846 // Success, Error or UnschedulableAndUnresolvable is met; otherwise continues to execute all plugins. 847 func (f *frameworkImpl) RunPostFilterPlugins(ctx context.Context, state *framework.CycleState, pod *v1.Pod, filteredNodeStatusMap framework.NodeToStatusMap) (_ *framework.PostFilterResult, status *framework.Status) { 848 startTime := time.Now() 849 defer func() { 850 metrics.FrameworkExtensionPointDuration.WithLabelValues(metrics.PostFilter, status.Code().String(), f.profileName).Observe(metrics.SinceInSeconds(startTime)) 851 }() 852 853 logger := klog.FromContext(ctx) 854 verboseLogs := logger.V(4).Enabled() 855 if verboseLogs { 856 logger = klog.LoggerWithName(logger, "PostFilter") 857 } 858 859 // `result` records the last meaningful(non-noop) PostFilterResult. 860 var result *framework.PostFilterResult 861 var reasons []string 862 var rejectorPlugin string 863 for _, pl := range f.postFilterPlugins { 864 ctx := ctx 865 if verboseLogs { 866 logger := klog.LoggerWithName(logger, pl.Name()) 867 ctx = klog.NewContext(ctx, logger) 868 } 869 r, s := f.runPostFilterPlugin(ctx, pl, state, pod, filteredNodeStatusMap) 870 if s.IsSuccess() { 871 return r, s 872 } else if s.Code() == framework.UnschedulableAndUnresolvable { 873 return r, s.WithPlugin(pl.Name()) 874 } else if !s.IsRejected() { 875 // Any status other than Success, Unschedulable or UnschedulableAndUnresolvable is Error. 876 return nil, framework.AsStatus(s.AsError()).WithPlugin(pl.Name()) 877 } else if r != nil && r.Mode() != framework.ModeNoop { 878 result = r 879 } 880 881 reasons = append(reasons, s.Reasons()...) 882 // Record the first failed plugin unless we proved that 883 // the latter is more relevant. 884 if len(rejectorPlugin) == 0 { 885 rejectorPlugin = pl.Name() 886 } 887 } 888 889 return result, framework.NewStatus(framework.Unschedulable, reasons...).WithPlugin(rejectorPlugin) 890 } 891 892 func (f *frameworkImpl) runPostFilterPlugin(ctx context.Context, pl framework.PostFilterPlugin, state *framework.CycleState, pod *v1.Pod, filteredNodeStatusMap framework.NodeToStatusMap) (*framework.PostFilterResult, *framework.Status) { 893 if !state.ShouldRecordPluginMetrics() { 894 return pl.PostFilter(ctx, state, pod, filteredNodeStatusMap) 895 } 896 startTime := time.Now() 897 r, s := pl.PostFilter(ctx, state, pod, filteredNodeStatusMap) 898 f.metricsRecorder.ObservePluginDurationAsync(metrics.PostFilter, pl.Name(), s.Code().String(), metrics.SinceInSeconds(startTime)) 899 return r, s 900 } 901 902 // RunFilterPluginsWithNominatedPods runs the set of configured filter plugins 903 // for nominated pod on the given node. 904 // This function is called from two different places: Schedule and Preempt. 905 // When it is called from Schedule, we want to test whether the pod is 906 // schedulable on the node with all the existing pods on the node plus higher 907 // and equal priority pods nominated to run on the node. 908 // When it is called from Preempt, we should remove the victims of preemption 909 // and add the nominated pods. Removal of the victims is done by 910 // SelectVictimsOnNode(). Preempt removes victims from PreFilter state and 911 // NodeInfo before calling this function. 912 func (f *frameworkImpl) RunFilterPluginsWithNominatedPods(ctx context.Context, state *framework.CycleState, pod *v1.Pod, info *framework.NodeInfo) *framework.Status { 913 var status *framework.Status 914 915 podsAdded := false 916 // We run filters twice in some cases. If the node has greater or equal priority 917 // nominated pods, we run them when those pods are added to PreFilter state and nodeInfo. 918 // If all filters succeed in this pass, we run them again when these 919 // nominated pods are not added. This second pass is necessary because some 920 // filters such as inter-pod affinity may not pass without the nominated pods. 921 // If there are no nominated pods for the node or if the first run of the 922 // filters fail, we don't run the second pass. 923 // We consider only equal or higher priority pods in the first pass, because 924 // those are the current "pod" must yield to them and not take a space opened 925 // for running them. It is ok if the current "pod" take resources freed for 926 // lower priority pods. 927 // Requiring that the new pod is schedulable in both circumstances ensures that 928 // we are making a conservative decision: filters like resources and inter-pod 929 // anti-affinity are more likely to fail when the nominated pods are treated 930 // as running, while filters like pod affinity are more likely to fail when 931 // the nominated pods are treated as not running. We can't just assume the 932 // nominated pods are running because they are not running right now and in fact, 933 // they may end up getting scheduled to a different node. 934 logger := klog.FromContext(ctx) 935 logger = klog.LoggerWithName(logger, "FilterWithNominatedPods") 936 ctx = klog.NewContext(ctx, logger) 937 for i := 0; i < 2; i++ { 938 stateToUse := state 939 nodeInfoToUse := info 940 if i == 0 { 941 var err error 942 podsAdded, stateToUse, nodeInfoToUse, err = addNominatedPods(ctx, f, pod, state, info) 943 if err != nil { 944 return framework.AsStatus(err) 945 } 946 } else if !podsAdded || !status.IsSuccess() { 947 break 948 } 949 950 status = f.RunFilterPlugins(ctx, stateToUse, pod, nodeInfoToUse) 951 if !status.IsSuccess() && !status.IsRejected() { 952 return status 953 } 954 } 955 956 return status 957 } 958 959 // addNominatedPods adds pods with equal or greater priority which are nominated 960 // to run on the node. It returns 1) whether any pod was added, 2) augmented cycleState, 961 // 3) augmented nodeInfo. 962 func addNominatedPods(ctx context.Context, fh framework.Handle, pod *v1.Pod, state *framework.CycleState, nodeInfo *framework.NodeInfo) (bool, *framework.CycleState, *framework.NodeInfo, error) { 963 if fh == nil { 964 // This may happen only in tests. 965 return false, state, nodeInfo, nil 966 } 967 nominatedPodInfos := fh.NominatedPodsForNode(nodeInfo.Node().Name) 968 if len(nominatedPodInfos) == 0 { 969 return false, state, nodeInfo, nil 970 } 971 nodeInfoOut := nodeInfo.Snapshot() 972 stateOut := state.Clone() 973 podsAdded := false 974 for _, pi := range nominatedPodInfos { 975 if corev1.PodPriority(pi.Pod) >= corev1.PodPriority(pod) && pi.Pod.UID != pod.UID { 976 nodeInfoOut.AddPodInfo(pi) 977 status := fh.RunPreFilterExtensionAddPod(ctx, stateOut, pod, pi, nodeInfoOut) 978 if !status.IsSuccess() { 979 return false, state, nodeInfo, status.AsError() 980 } 981 podsAdded = true 982 } 983 } 984 return podsAdded, stateOut, nodeInfoOut, nil 985 } 986 987 // RunPreScorePlugins runs the set of configured pre-score plugins. If any 988 // of these plugins returns any status other than Success/Skip, the given pod is rejected. 989 // When it returns Skip status, other fields in status are just ignored, 990 // and coupled Score plugin will be skipped in this scheduling cycle. 991 func (f *frameworkImpl) RunPreScorePlugins( 992 ctx context.Context, 993 state *framework.CycleState, 994 pod *v1.Pod, 995 nodes []*v1.Node, 996 ) (status *framework.Status) { 997 startTime := time.Now() 998 skipPlugins := sets.New[string]() 999 defer func() { 1000 state.SkipScorePlugins = skipPlugins 1001 metrics.FrameworkExtensionPointDuration.WithLabelValues(metrics.PreScore, status.Code().String(), f.profileName).Observe(metrics.SinceInSeconds(startTime)) 1002 }() 1003 logger := klog.FromContext(ctx) 1004 verboseLogs := logger.V(4).Enabled() 1005 if verboseLogs { 1006 logger = klog.LoggerWithName(logger, "PreScore") 1007 } 1008 for _, pl := range f.preScorePlugins { 1009 ctx := ctx 1010 if verboseLogs { 1011 logger := klog.LoggerWithName(logger, pl.Name()) 1012 ctx = klog.NewContext(ctx, logger) 1013 } 1014 status = f.runPreScorePlugin(ctx, pl, state, pod, nodes) 1015 if status.IsSkip() { 1016 skipPlugins.Insert(pl.Name()) 1017 continue 1018 } 1019 if !status.IsSuccess() { 1020 return framework.AsStatus(fmt.Errorf("running PreScore plugin %q: %w", pl.Name(), status.AsError())) 1021 } 1022 } 1023 return nil 1024 } 1025 1026 func (f *frameworkImpl) runPreScorePlugin(ctx context.Context, pl framework.PreScorePlugin, state *framework.CycleState, pod *v1.Pod, nodes []*v1.Node) *framework.Status { 1027 if !state.ShouldRecordPluginMetrics() { 1028 return pl.PreScore(ctx, state, pod, nodes) 1029 } 1030 startTime := time.Now() 1031 status := pl.PreScore(ctx, state, pod, nodes) 1032 f.metricsRecorder.ObservePluginDurationAsync(metrics.PreScore, pl.Name(), status.Code().String(), metrics.SinceInSeconds(startTime)) 1033 return status 1034 } 1035 1036 // RunScorePlugins runs the set of configured scoring plugins. 1037 // It returns a list that stores scores from each plugin and total score for each Node. 1038 // It also returns *Status, which is set to non-success if any of the plugins returns 1039 // a non-success status. 1040 func (f *frameworkImpl) RunScorePlugins(ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodes []*v1.Node) (ns []framework.NodePluginScores, status *framework.Status) { 1041 startTime := time.Now() 1042 defer func() { 1043 metrics.FrameworkExtensionPointDuration.WithLabelValues(metrics.Score, status.Code().String(), f.profileName).Observe(metrics.SinceInSeconds(startTime)) 1044 }() 1045 allNodePluginScores := make([]framework.NodePluginScores, len(nodes)) 1046 numPlugins := len(f.scorePlugins) 1047 plugins := make([]framework.ScorePlugin, 0, numPlugins) 1048 pluginToNodeScores := make(map[string]framework.NodeScoreList, numPlugins) 1049 for _, pl := range f.scorePlugins { 1050 if state.SkipScorePlugins.Has(pl.Name()) { 1051 continue 1052 } 1053 plugins = append(plugins, pl) 1054 pluginToNodeScores[pl.Name()] = make(framework.NodeScoreList, len(nodes)) 1055 } 1056 ctx, cancel := context.WithCancel(ctx) 1057 defer cancel() 1058 errCh := parallelize.NewErrorChannel() 1059 1060 if len(plugins) > 0 { 1061 logger := klog.FromContext(ctx) 1062 verboseLogs := logger.V(4).Enabled() 1063 if verboseLogs { 1064 logger = klog.LoggerWithName(logger, "Score") 1065 } 1066 // Run Score method for each node in parallel. 1067 f.Parallelizer().Until(ctx, len(nodes), func(index int) { 1068 nodeName := nodes[index].Name 1069 logger := logger 1070 if verboseLogs { 1071 logger = klog.LoggerWithValues(logger, "node", klog.ObjectRef{Name: nodeName}) 1072 } 1073 for _, pl := range plugins { 1074 ctx := ctx 1075 if verboseLogs { 1076 logger := klog.LoggerWithName(logger, pl.Name()) 1077 ctx = klog.NewContext(ctx, logger) 1078 } 1079 s, status := f.runScorePlugin(ctx, pl, state, pod, nodeName) 1080 if !status.IsSuccess() { 1081 err := fmt.Errorf("plugin %q failed with: %w", pl.Name(), status.AsError()) 1082 errCh.SendErrorWithCancel(err, cancel) 1083 return 1084 } 1085 pluginToNodeScores[pl.Name()][index] = framework.NodeScore{ 1086 Name: nodeName, 1087 Score: s, 1088 } 1089 } 1090 }, metrics.Score) 1091 if err := errCh.ReceiveError(); err != nil { 1092 return nil, framework.AsStatus(fmt.Errorf("running Score plugins: %w", err)) 1093 } 1094 } 1095 1096 // Run NormalizeScore method for each ScorePlugin in parallel. 1097 f.Parallelizer().Until(ctx, len(plugins), func(index int) { 1098 pl := plugins[index] 1099 if pl.ScoreExtensions() == nil { 1100 return 1101 } 1102 nodeScoreList := pluginToNodeScores[pl.Name()] 1103 status := f.runScoreExtension(ctx, pl, state, pod, nodeScoreList) 1104 if !status.IsSuccess() { 1105 err := fmt.Errorf("plugin %q failed with: %w", pl.Name(), status.AsError()) 1106 errCh.SendErrorWithCancel(err, cancel) 1107 return 1108 } 1109 }, metrics.Score) 1110 if err := errCh.ReceiveError(); err != nil { 1111 return nil, framework.AsStatus(fmt.Errorf("running Normalize on Score plugins: %w", err)) 1112 } 1113 1114 // Apply score weight for each ScorePlugin in parallel, 1115 // and then, build allNodePluginScores. 1116 f.Parallelizer().Until(ctx, len(nodes), func(index int) { 1117 nodePluginScores := framework.NodePluginScores{ 1118 Name: nodes[index].Name, 1119 Scores: make([]framework.PluginScore, len(plugins)), 1120 } 1121 1122 for i, pl := range plugins { 1123 weight := f.scorePluginWeight[pl.Name()] 1124 nodeScoreList := pluginToNodeScores[pl.Name()] 1125 score := nodeScoreList[index].Score 1126 1127 if score > framework.MaxNodeScore || score < framework.MinNodeScore { 1128 err := fmt.Errorf("plugin %q returns an invalid score %v, it should in the range of [%v, %v] after normalizing", pl.Name(), score, framework.MinNodeScore, framework.MaxNodeScore) 1129 errCh.SendErrorWithCancel(err, cancel) 1130 return 1131 } 1132 weightedScore := score * int64(weight) 1133 nodePluginScores.Scores[i] = framework.PluginScore{ 1134 Name: pl.Name(), 1135 Score: weightedScore, 1136 } 1137 nodePluginScores.TotalScore += weightedScore 1138 } 1139 allNodePluginScores[index] = nodePluginScores 1140 }, metrics.Score) 1141 if err := errCh.ReceiveError(); err != nil { 1142 return nil, framework.AsStatus(fmt.Errorf("applying score defaultWeights on Score plugins: %w", err)) 1143 } 1144 1145 return allNodePluginScores, nil 1146 } 1147 1148 func (f *frameworkImpl) runScorePlugin(ctx context.Context, pl framework.ScorePlugin, state *framework.CycleState, pod *v1.Pod, nodeName string) (int64, *framework.Status) { 1149 if !state.ShouldRecordPluginMetrics() { 1150 return pl.Score(ctx, state, pod, nodeName) 1151 } 1152 startTime := time.Now() 1153 s, status := pl.Score(ctx, state, pod, nodeName) 1154 f.metricsRecorder.ObservePluginDurationAsync(metrics.Score, pl.Name(), status.Code().String(), metrics.SinceInSeconds(startTime)) 1155 return s, status 1156 } 1157 1158 func (f *frameworkImpl) runScoreExtension(ctx context.Context, pl framework.ScorePlugin, state *framework.CycleState, pod *v1.Pod, nodeScoreList framework.NodeScoreList) *framework.Status { 1159 if !state.ShouldRecordPluginMetrics() { 1160 return pl.ScoreExtensions().NormalizeScore(ctx, state, pod, nodeScoreList) 1161 } 1162 startTime := time.Now() 1163 status := pl.ScoreExtensions().NormalizeScore(ctx, state, pod, nodeScoreList) 1164 f.metricsRecorder.ObservePluginDurationAsync(metrics.ScoreExtensionNormalize, pl.Name(), status.Code().String(), metrics.SinceInSeconds(startTime)) 1165 return status 1166 } 1167 1168 // RunPreBindPlugins runs the set of configured prebind plugins. It returns a 1169 // failure (bool) if any of the plugins returns an error. It also returns an 1170 // error containing the rejection message or the error occurred in the plugin. 1171 func (f *frameworkImpl) RunPreBindPlugins(ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodeName string) (status *framework.Status) { 1172 startTime := time.Now() 1173 defer func() { 1174 metrics.FrameworkExtensionPointDuration.WithLabelValues(metrics.PreBind, status.Code().String(), f.profileName).Observe(metrics.SinceInSeconds(startTime)) 1175 }() 1176 logger := klog.FromContext(ctx) 1177 verboseLogs := logger.V(4).Enabled() 1178 if verboseLogs { 1179 logger = klog.LoggerWithName(logger, "PreBind") 1180 logger = klog.LoggerWithValues(logger, "node", klog.ObjectRef{Name: nodeName}) 1181 } 1182 for _, pl := range f.preBindPlugins { 1183 ctx := ctx 1184 if verboseLogs { 1185 logger := klog.LoggerWithName(logger, pl.Name()) 1186 ctx = klog.NewContext(ctx, logger) 1187 } 1188 status = f.runPreBindPlugin(ctx, pl, state, pod, nodeName) 1189 if !status.IsSuccess() { 1190 if status.IsRejected() { 1191 logger.V(4).Info("Pod rejected by PreBind plugin", "pod", klog.KObj(pod), "node", nodeName, "plugin", pl.Name(), "status", status.Message()) 1192 status.SetPlugin(pl.Name()) 1193 return status 1194 } 1195 err := status.AsError() 1196 logger.Error(err, "Plugin failed", "plugin", pl.Name(), "pod", klog.KObj(pod), "node", nodeName) 1197 return framework.AsStatus(fmt.Errorf("running PreBind plugin %q: %w", pl.Name(), err)) 1198 } 1199 } 1200 return nil 1201 } 1202 1203 func (f *frameworkImpl) runPreBindPlugin(ctx context.Context, pl framework.PreBindPlugin, state *framework.CycleState, pod *v1.Pod, nodeName string) *framework.Status { 1204 if !state.ShouldRecordPluginMetrics() { 1205 return pl.PreBind(ctx, state, pod, nodeName) 1206 } 1207 startTime := time.Now() 1208 status := pl.PreBind(ctx, state, pod, nodeName) 1209 f.metricsRecorder.ObservePluginDurationAsync(metrics.PreBind, pl.Name(), status.Code().String(), metrics.SinceInSeconds(startTime)) 1210 return status 1211 } 1212 1213 // RunBindPlugins runs the set of configured bind plugins until one returns a non `Skip` status. 1214 func (f *frameworkImpl) RunBindPlugins(ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodeName string) (status *framework.Status) { 1215 startTime := time.Now() 1216 defer func() { 1217 metrics.FrameworkExtensionPointDuration.WithLabelValues(metrics.Bind, status.Code().String(), f.profileName).Observe(metrics.SinceInSeconds(startTime)) 1218 }() 1219 if len(f.bindPlugins) == 0 { 1220 return framework.NewStatus(framework.Skip, "") 1221 } 1222 logger := klog.FromContext(ctx) 1223 verboseLogs := logger.V(4).Enabled() 1224 if verboseLogs { 1225 logger = klog.LoggerWithName(logger, "Bind") 1226 } 1227 for _, pl := range f.bindPlugins { 1228 ctx := ctx 1229 if verboseLogs { 1230 logger := klog.LoggerWithName(logger, pl.Name()) 1231 ctx = klog.NewContext(ctx, logger) 1232 } 1233 status = f.runBindPlugin(ctx, pl, state, pod, nodeName) 1234 if status.IsSkip() { 1235 continue 1236 } 1237 if !status.IsSuccess() { 1238 if status.IsRejected() { 1239 logger.V(4).Info("Pod rejected by Bind plugin", "pod", klog.KObj(pod), "node", nodeName, "plugin", pl.Name(), "status", status.Message()) 1240 status.SetPlugin(pl.Name()) 1241 return status 1242 } 1243 err := status.AsError() 1244 logger.Error(err, "Plugin Failed", "plugin", pl.Name(), "pod", klog.KObj(pod), "node", nodeName) 1245 return framework.AsStatus(fmt.Errorf("running Bind plugin %q: %w", pl.Name(), err)) 1246 } 1247 return status 1248 } 1249 return status 1250 } 1251 1252 func (f *frameworkImpl) runBindPlugin(ctx context.Context, bp framework.BindPlugin, state *framework.CycleState, pod *v1.Pod, nodeName string) *framework.Status { 1253 if !state.ShouldRecordPluginMetrics() { 1254 return bp.Bind(ctx, state, pod, nodeName) 1255 } 1256 startTime := time.Now() 1257 status := bp.Bind(ctx, state, pod, nodeName) 1258 f.metricsRecorder.ObservePluginDurationAsync(metrics.Bind, bp.Name(), status.Code().String(), metrics.SinceInSeconds(startTime)) 1259 return status 1260 } 1261 1262 // RunPostBindPlugins runs the set of configured postbind plugins. 1263 func (f *frameworkImpl) RunPostBindPlugins(ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodeName string) { 1264 startTime := time.Now() 1265 defer func() { 1266 metrics.FrameworkExtensionPointDuration.WithLabelValues(metrics.PostBind, framework.Success.String(), f.profileName).Observe(metrics.SinceInSeconds(startTime)) 1267 }() 1268 logger := klog.FromContext(ctx) 1269 verboseLogs := logger.V(4).Enabled() 1270 if verboseLogs { 1271 logger = klog.LoggerWithName(logger, "PostBind") 1272 } 1273 for _, pl := range f.postBindPlugins { 1274 ctx := ctx 1275 if verboseLogs { 1276 logger := klog.LoggerWithName(logger, pl.Name()) 1277 ctx = klog.NewContext(ctx, logger) 1278 } 1279 f.runPostBindPlugin(ctx, pl, state, pod, nodeName) 1280 } 1281 } 1282 1283 func (f *frameworkImpl) runPostBindPlugin(ctx context.Context, pl framework.PostBindPlugin, state *framework.CycleState, pod *v1.Pod, nodeName string) { 1284 if !state.ShouldRecordPluginMetrics() { 1285 pl.PostBind(ctx, state, pod, nodeName) 1286 return 1287 } 1288 startTime := time.Now() 1289 pl.PostBind(ctx, state, pod, nodeName) 1290 f.metricsRecorder.ObservePluginDurationAsync(metrics.PostBind, pl.Name(), framework.Success.String(), metrics.SinceInSeconds(startTime)) 1291 } 1292 1293 // RunReservePluginsReserve runs the Reserve method in the set of configured 1294 // reserve plugins. If any of these plugins returns an error, it does not 1295 // continue running the remaining ones and returns the error. In such a case, 1296 // the pod will not be scheduled and the caller will be expected to call 1297 // RunReservePluginsUnreserve. 1298 func (f *frameworkImpl) RunReservePluginsReserve(ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodeName string) (status *framework.Status) { 1299 startTime := time.Now() 1300 defer func() { 1301 metrics.FrameworkExtensionPointDuration.WithLabelValues(metrics.Reserve, status.Code().String(), f.profileName).Observe(metrics.SinceInSeconds(startTime)) 1302 }() 1303 logger := klog.FromContext(ctx) 1304 verboseLogs := logger.V(4).Enabled() 1305 if verboseLogs { 1306 logger = klog.LoggerWithName(logger, "Reserve") 1307 logger = klog.LoggerWithValues(logger, "node", klog.ObjectRef{Name: nodeName}) 1308 } 1309 for _, pl := range f.reservePlugins { 1310 ctx := ctx 1311 if verboseLogs { 1312 logger := klog.LoggerWithName(logger, pl.Name()) 1313 ctx = klog.NewContext(ctx, logger) 1314 } 1315 status = f.runReservePluginReserve(ctx, pl, state, pod, nodeName) 1316 if !status.IsSuccess() { 1317 if status.IsRejected() { 1318 logger.V(4).Info("Pod rejected by plugin", "pod", klog.KObj(pod), "plugin", pl.Name(), "status", status.Message()) 1319 status.SetPlugin(pl.Name()) 1320 return status 1321 } 1322 err := status.AsError() 1323 logger.Error(err, "Plugin failed", "plugin", pl.Name(), "pod", klog.KObj(pod)) 1324 return framework.AsStatus(fmt.Errorf("running Reserve plugin %q: %w", pl.Name(), err)) 1325 } 1326 } 1327 return nil 1328 } 1329 1330 func (f *frameworkImpl) runReservePluginReserve(ctx context.Context, pl framework.ReservePlugin, state *framework.CycleState, pod *v1.Pod, nodeName string) *framework.Status { 1331 if !state.ShouldRecordPluginMetrics() { 1332 return pl.Reserve(ctx, state, pod, nodeName) 1333 } 1334 startTime := time.Now() 1335 status := pl.Reserve(ctx, state, pod, nodeName) 1336 f.metricsRecorder.ObservePluginDurationAsync(metrics.Reserve, pl.Name(), status.Code().String(), metrics.SinceInSeconds(startTime)) 1337 return status 1338 } 1339 1340 // RunReservePluginsUnreserve runs the Unreserve method in the set of 1341 // configured reserve plugins. 1342 func (f *frameworkImpl) RunReservePluginsUnreserve(ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodeName string) { 1343 startTime := time.Now() 1344 defer func() { 1345 metrics.FrameworkExtensionPointDuration.WithLabelValues(metrics.Unreserve, framework.Success.String(), f.profileName).Observe(metrics.SinceInSeconds(startTime)) 1346 }() 1347 // Execute the Unreserve operation of each reserve plugin in the 1348 // *reverse* order in which the Reserve operation was executed. 1349 logger := klog.FromContext(ctx) 1350 verboseLogs := logger.V(4).Enabled() 1351 if verboseLogs { 1352 logger = klog.LoggerWithName(logger, "Unreserve") 1353 logger = klog.LoggerWithValues(logger, "node", klog.ObjectRef{Name: nodeName}) 1354 } 1355 for i := len(f.reservePlugins) - 1; i >= 0; i-- { 1356 pl := f.reservePlugins[i] 1357 ctx := ctx 1358 if verboseLogs { 1359 logger := klog.LoggerWithName(logger, pl.Name()) 1360 ctx = klog.NewContext(ctx, logger) 1361 } 1362 f.runReservePluginUnreserve(ctx, pl, state, pod, nodeName) 1363 } 1364 } 1365 1366 func (f *frameworkImpl) runReservePluginUnreserve(ctx context.Context, pl framework.ReservePlugin, state *framework.CycleState, pod *v1.Pod, nodeName string) { 1367 if !state.ShouldRecordPluginMetrics() { 1368 pl.Unreserve(ctx, state, pod, nodeName) 1369 return 1370 } 1371 startTime := time.Now() 1372 pl.Unreserve(ctx, state, pod, nodeName) 1373 f.metricsRecorder.ObservePluginDurationAsync(metrics.Unreserve, pl.Name(), framework.Success.String(), metrics.SinceInSeconds(startTime)) 1374 } 1375 1376 // RunPermitPlugins runs the set of configured permit plugins. If any of these 1377 // plugins returns a status other than "Success" or "Wait", it does not continue 1378 // running the remaining plugins and returns an error. Otherwise, if any of the 1379 // plugins returns "Wait", then this function will create and add waiting pod 1380 // to a map of currently waiting pods and return status with "Wait" code. 1381 // Pod will remain waiting pod for the minimum duration returned by the permit plugins. 1382 func (f *frameworkImpl) RunPermitPlugins(ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodeName string) (status *framework.Status) { 1383 startTime := time.Now() 1384 defer func() { 1385 metrics.FrameworkExtensionPointDuration.WithLabelValues(metrics.Permit, status.Code().String(), f.profileName).Observe(metrics.SinceInSeconds(startTime)) 1386 }() 1387 pluginsWaitTime := make(map[string]time.Duration) 1388 statusCode := framework.Success 1389 logger := klog.FromContext(ctx) 1390 verboseLogs := logger.V(4).Enabled() 1391 if verboseLogs { 1392 logger = klog.LoggerWithName(logger, "Permit") 1393 logger = klog.LoggerWithValues(logger, "node", klog.ObjectRef{Name: nodeName}) 1394 } 1395 for _, pl := range f.permitPlugins { 1396 ctx := ctx 1397 if verboseLogs { 1398 logger := klog.LoggerWithName(logger, pl.Name()) 1399 ctx = klog.NewContext(ctx, logger) 1400 } 1401 status, timeout := f.runPermitPlugin(ctx, pl, state, pod, nodeName) 1402 if !status.IsSuccess() { 1403 if status.IsRejected() { 1404 logger.V(4).Info("Pod rejected by plugin", "pod", klog.KObj(pod), "plugin", pl.Name(), "status", status.Message()) 1405 return status.WithPlugin(pl.Name()) 1406 } 1407 if status.IsWait() { 1408 // Not allowed to be greater than maxTimeout. 1409 if timeout > maxTimeout { 1410 timeout = maxTimeout 1411 } 1412 pluginsWaitTime[pl.Name()] = timeout 1413 statusCode = framework.Wait 1414 } else { 1415 err := status.AsError() 1416 logger.Error(err, "Plugin failed", "plugin", pl.Name(), "pod", klog.KObj(pod)) 1417 return framework.AsStatus(fmt.Errorf("running Permit plugin %q: %w", pl.Name(), err)).WithPlugin(pl.Name()) 1418 } 1419 } 1420 } 1421 if statusCode == framework.Wait { 1422 waitingPod := newWaitingPod(pod, pluginsWaitTime) 1423 f.waitingPods.add(waitingPod) 1424 msg := fmt.Sprintf("one or more plugins asked to wait and no plugin rejected pod %q", pod.Name) 1425 logger.V(4).Info("One or more plugins asked to wait and no plugin rejected pod", "pod", klog.KObj(pod)) 1426 return framework.NewStatus(framework.Wait, msg) 1427 } 1428 return nil 1429 } 1430 1431 func (f *frameworkImpl) runPermitPlugin(ctx context.Context, pl framework.PermitPlugin, state *framework.CycleState, pod *v1.Pod, nodeName string) (*framework.Status, time.Duration) { 1432 if !state.ShouldRecordPluginMetrics() { 1433 return pl.Permit(ctx, state, pod, nodeName) 1434 } 1435 startTime := time.Now() 1436 status, timeout := pl.Permit(ctx, state, pod, nodeName) 1437 f.metricsRecorder.ObservePluginDurationAsync(metrics.Permit, pl.Name(), status.Code().String(), metrics.SinceInSeconds(startTime)) 1438 return status, timeout 1439 } 1440 1441 // WaitOnPermit will block, if the pod is a waiting pod, until the waiting pod is rejected or allowed. 1442 func (f *frameworkImpl) WaitOnPermit(ctx context.Context, pod *v1.Pod) *framework.Status { 1443 waitingPod := f.waitingPods.get(pod.UID) 1444 if waitingPod == nil { 1445 return nil 1446 } 1447 defer f.waitingPods.remove(pod.UID) 1448 1449 logger := klog.FromContext(ctx) 1450 logger.V(4).Info("Pod waiting on permit", "pod", klog.KObj(pod)) 1451 1452 startTime := time.Now() 1453 s := <-waitingPod.s 1454 metrics.PermitWaitDuration.WithLabelValues(s.Code().String()).Observe(metrics.SinceInSeconds(startTime)) 1455 1456 if !s.IsSuccess() { 1457 if s.IsRejected() { 1458 logger.V(4).Info("Pod rejected while waiting on permit", "pod", klog.KObj(pod), "status", s.Message()) 1459 return s 1460 } 1461 err := s.AsError() 1462 logger.Error(err, "Failed waiting on permit for pod", "pod", klog.KObj(pod)) 1463 return framework.AsStatus(fmt.Errorf("waiting on permit for pod: %w", err)).WithPlugin(s.Plugin()) 1464 } 1465 return nil 1466 } 1467 1468 // SnapshotSharedLister returns the scheduler's SharedLister of the latest NodeInfo 1469 // snapshot. The snapshot is taken at the beginning of a scheduling cycle and remains 1470 // unchanged until a pod finishes "Reserve". There is no guarantee that the information 1471 // remains unchanged after "Reserve". 1472 func (f *frameworkImpl) SnapshotSharedLister() framework.SharedLister { 1473 return f.snapshotSharedLister 1474 } 1475 1476 // IterateOverWaitingPods acquires a read lock and iterates over the WaitingPods map. 1477 func (f *frameworkImpl) IterateOverWaitingPods(callback func(framework.WaitingPod)) { 1478 f.waitingPods.iterate(callback) 1479 } 1480 1481 // GetWaitingPod returns a reference to a WaitingPod given its UID. 1482 func (f *frameworkImpl) GetWaitingPod(uid types.UID) framework.WaitingPod { 1483 if wp := f.waitingPods.get(uid); wp != nil { 1484 return wp 1485 } 1486 return nil // Returning nil instead of *waitingPod(nil). 1487 } 1488 1489 // RejectWaitingPod rejects a WaitingPod given its UID. 1490 // The returned value indicates if the given pod is waiting or not. 1491 func (f *frameworkImpl) RejectWaitingPod(uid types.UID) bool { 1492 if waitingPod := f.waitingPods.get(uid); waitingPod != nil { 1493 waitingPod.Reject("", "removed") 1494 return true 1495 } 1496 return false 1497 } 1498 1499 // HasFilterPlugins returns true if at least one filter plugin is defined. 1500 func (f *frameworkImpl) HasFilterPlugins() bool { 1501 return len(f.filterPlugins) > 0 1502 } 1503 1504 // HasPostFilterPlugins returns true if at least one postFilter plugin is defined. 1505 func (f *frameworkImpl) HasPostFilterPlugins() bool { 1506 return len(f.postFilterPlugins) > 0 1507 } 1508 1509 // HasScorePlugins returns true if at least one score plugin is defined. 1510 func (f *frameworkImpl) HasScorePlugins() bool { 1511 return len(f.scorePlugins) > 0 1512 } 1513 1514 // ListPlugins returns a map of extension point name to plugin names configured at each extension 1515 // point. Returns nil if no plugins where configured. 1516 func (f *frameworkImpl) ListPlugins() *config.Plugins { 1517 m := config.Plugins{} 1518 1519 for _, e := range f.getExtensionPoints(&m) { 1520 plugins := reflect.ValueOf(e.slicePtr).Elem() 1521 extName := plugins.Type().Elem().Name() 1522 var cfgs []config.Plugin 1523 for i := 0; i < plugins.Len(); i++ { 1524 name := plugins.Index(i).Interface().(framework.Plugin).Name() 1525 p := config.Plugin{Name: name} 1526 if extName == "ScorePlugin" { 1527 // Weights apply only to score plugins. 1528 p.Weight = int32(f.scorePluginWeight[name]) 1529 } 1530 cfgs = append(cfgs, p) 1531 } 1532 if len(cfgs) > 0 { 1533 e.plugins.Enabled = cfgs 1534 } 1535 } 1536 return &m 1537 } 1538 1539 // ClientSet returns a kubernetes clientset. 1540 func (f *frameworkImpl) ClientSet() clientset.Interface { 1541 return f.clientSet 1542 } 1543 1544 // KubeConfig returns a kubernetes config. 1545 func (f *frameworkImpl) KubeConfig() *restclient.Config { 1546 return f.kubeConfig 1547 } 1548 1549 // EventRecorder returns an event recorder. 1550 func (f *frameworkImpl) EventRecorder() events.EventRecorder { 1551 return f.eventRecorder 1552 } 1553 1554 // SharedInformerFactory returns a shared informer factory. 1555 func (f *frameworkImpl) SharedInformerFactory() informers.SharedInformerFactory { 1556 return f.informerFactory 1557 } 1558 1559 func (f *frameworkImpl) pluginsNeeded(plugins *config.Plugins) sets.Set[string] { 1560 pgSet := sets.Set[string]{} 1561 1562 if plugins == nil { 1563 return pgSet 1564 } 1565 1566 find := func(pgs *config.PluginSet) { 1567 for _, pg := range pgs.Enabled { 1568 pgSet.Insert(pg.Name) 1569 } 1570 } 1571 1572 for _, e := range f.getExtensionPoints(plugins) { 1573 find(e.plugins) 1574 } 1575 // Parse MultiPoint separately since they are not returned by f.getExtensionPoints() 1576 find(&plugins.MultiPoint) 1577 1578 return pgSet 1579 } 1580 1581 // ProfileName returns the profile name associated to this framework. 1582 func (f *frameworkImpl) ProfileName() string { 1583 return f.profileName 1584 } 1585 1586 // PercentageOfNodesToScore returns percentageOfNodesToScore associated to a profile. 1587 func (f *frameworkImpl) PercentageOfNodesToScore() *int32 { 1588 return f.percentageOfNodesToScore 1589 } 1590 1591 // Parallelizer returns a parallelizer holding parallelism for scheduler. 1592 func (f *frameworkImpl) Parallelizer() parallelize.Parallelizer { 1593 return f.parallelizer 1594 }