k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/test/integration/scheduler/plugins/plugins_test.go (about)

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