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  }