k8s.io/kubernetes@v1.29.3/pkg/scheduler/scheduler_test.go (about)

     1  /*
     2  Copyright 2014 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 scheduler
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"sort"
    23  	"strings"
    24  	"testing"
    25  	"time"
    26  
    27  	"github.com/google/go-cmp/cmp"
    28  	v1 "k8s.io/api/core/v1"
    29  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    30  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    31  	"k8s.io/apimachinery/pkg/runtime"
    32  	"k8s.io/apimachinery/pkg/util/sets"
    33  	utilfeature "k8s.io/apiserver/pkg/util/feature"
    34  	"k8s.io/client-go/informers"
    35  	"k8s.io/client-go/kubernetes"
    36  	"k8s.io/client-go/kubernetes/fake"
    37  	"k8s.io/client-go/kubernetes/scheme"
    38  	"k8s.io/client-go/tools/cache"
    39  	"k8s.io/client-go/tools/events"
    40  	featuregatetesting "k8s.io/component-base/featuregate/testing"
    41  	"k8s.io/klog/v2"
    42  	"k8s.io/klog/v2/ktesting"
    43  	"k8s.io/kubernetes/pkg/features"
    44  	schedulerapi "k8s.io/kubernetes/pkg/scheduler/apis/config"
    45  	"k8s.io/kubernetes/pkg/scheduler/apis/config/testing/defaults"
    46  	"k8s.io/kubernetes/pkg/scheduler/framework"
    47  	"k8s.io/kubernetes/pkg/scheduler/framework/plugins"
    48  	"k8s.io/kubernetes/pkg/scheduler/framework/plugins/defaultbinder"
    49  	"k8s.io/kubernetes/pkg/scheduler/framework/plugins/queuesort"
    50  	frameworkruntime "k8s.io/kubernetes/pkg/scheduler/framework/runtime"
    51  	internalcache "k8s.io/kubernetes/pkg/scheduler/internal/cache"
    52  	internalqueue "k8s.io/kubernetes/pkg/scheduler/internal/queue"
    53  	"k8s.io/kubernetes/pkg/scheduler/profile"
    54  	st "k8s.io/kubernetes/pkg/scheduler/testing"
    55  	tf "k8s.io/kubernetes/pkg/scheduler/testing/framework"
    56  	testingclock "k8s.io/utils/clock/testing"
    57  	"k8s.io/utils/ptr"
    58  )
    59  
    60  func TestSchedulerCreation(t *testing.T) {
    61  	invalidRegistry := map[string]frameworkruntime.PluginFactory{
    62  		defaultbinder.Name: defaultbinder.New,
    63  	}
    64  	validRegistry := map[string]frameworkruntime.PluginFactory{
    65  		"Foo": defaultbinder.New,
    66  	}
    67  	cases := []struct {
    68  		name          string
    69  		opts          []Option
    70  		wantErr       string
    71  		wantProfiles  []string
    72  		wantExtenders []string
    73  	}{
    74  		{
    75  			name: "valid out-of-tree registry",
    76  			opts: []Option{
    77  				WithFrameworkOutOfTreeRegistry(validRegistry),
    78  				WithProfiles(
    79  					schedulerapi.KubeSchedulerProfile{
    80  						SchedulerName: "default-scheduler",
    81  						Plugins: &schedulerapi.Plugins{
    82  							QueueSort: schedulerapi.PluginSet{Enabled: []schedulerapi.Plugin{{Name: "PrioritySort"}}},
    83  							Bind:      schedulerapi.PluginSet{Enabled: []schedulerapi.Plugin{{Name: "DefaultBinder"}}},
    84  						},
    85  					},
    86  				)},
    87  			wantProfiles: []string{"default-scheduler"},
    88  		},
    89  		{
    90  			name: "repeated plugin name in out-of-tree plugin",
    91  			opts: []Option{
    92  				WithFrameworkOutOfTreeRegistry(invalidRegistry),
    93  				WithProfiles(
    94  					schedulerapi.KubeSchedulerProfile{
    95  						SchedulerName: "default-scheduler",
    96  						Plugins: &schedulerapi.Plugins{
    97  							QueueSort: schedulerapi.PluginSet{Enabled: []schedulerapi.Plugin{{Name: "PrioritySort"}}},
    98  							Bind:      schedulerapi.PluginSet{Enabled: []schedulerapi.Plugin{{Name: "DefaultBinder"}}},
    99  						},
   100  					},
   101  				)},
   102  			wantProfiles: []string{"default-scheduler"},
   103  			wantErr:      "a plugin named DefaultBinder already exists",
   104  		},
   105  		{
   106  			name: "multiple profiles",
   107  			opts: []Option{
   108  				WithProfiles(
   109  					schedulerapi.KubeSchedulerProfile{
   110  						SchedulerName: "foo",
   111  						Plugins: &schedulerapi.Plugins{
   112  							QueueSort: schedulerapi.PluginSet{Enabled: []schedulerapi.Plugin{{Name: "PrioritySort"}}},
   113  							Bind:      schedulerapi.PluginSet{Enabled: []schedulerapi.Plugin{{Name: "DefaultBinder"}}},
   114  						},
   115  					},
   116  					schedulerapi.KubeSchedulerProfile{
   117  						SchedulerName: "bar",
   118  						Plugins: &schedulerapi.Plugins{
   119  							QueueSort: schedulerapi.PluginSet{Enabled: []schedulerapi.Plugin{{Name: "PrioritySort"}}},
   120  							Bind:      schedulerapi.PluginSet{Enabled: []schedulerapi.Plugin{{Name: "DefaultBinder"}}},
   121  						},
   122  					},
   123  				)},
   124  			wantProfiles: []string{"bar", "foo"},
   125  		},
   126  		{
   127  			name: "Repeated profiles",
   128  			opts: []Option{
   129  				WithProfiles(
   130  					schedulerapi.KubeSchedulerProfile{
   131  						SchedulerName: "foo",
   132  						Plugins: &schedulerapi.Plugins{
   133  							QueueSort: schedulerapi.PluginSet{Enabled: []schedulerapi.Plugin{{Name: "PrioritySort"}}},
   134  							Bind:      schedulerapi.PluginSet{Enabled: []schedulerapi.Plugin{{Name: "DefaultBinder"}}},
   135  						},
   136  					},
   137  					schedulerapi.KubeSchedulerProfile{
   138  						SchedulerName: "bar",
   139  						Plugins: &schedulerapi.Plugins{
   140  							QueueSort: schedulerapi.PluginSet{Enabled: []schedulerapi.Plugin{{Name: "PrioritySort"}}},
   141  							Bind:      schedulerapi.PluginSet{Enabled: []schedulerapi.Plugin{{Name: "DefaultBinder"}}},
   142  						},
   143  					},
   144  					schedulerapi.KubeSchedulerProfile{
   145  						SchedulerName: "foo",
   146  						Plugins: &schedulerapi.Plugins{
   147  							QueueSort: schedulerapi.PluginSet{Enabled: []schedulerapi.Plugin{{Name: "PrioritySort"}}},
   148  							Bind:      schedulerapi.PluginSet{Enabled: []schedulerapi.Plugin{{Name: "DefaultBinder"}}},
   149  						},
   150  					},
   151  				)},
   152  			wantErr: "duplicate profile with scheduler name \"foo\"",
   153  		},
   154  		{
   155  			name: "With extenders",
   156  			opts: []Option{
   157  				WithProfiles(
   158  					schedulerapi.KubeSchedulerProfile{
   159  						SchedulerName: "default-scheduler",
   160  						Plugins: &schedulerapi.Plugins{
   161  							QueueSort: schedulerapi.PluginSet{Enabled: []schedulerapi.Plugin{{Name: "PrioritySort"}}},
   162  							Bind:      schedulerapi.PluginSet{Enabled: []schedulerapi.Plugin{{Name: "DefaultBinder"}}},
   163  						},
   164  					},
   165  				),
   166  				WithExtenders(
   167  					schedulerapi.Extender{
   168  						URLPrefix: "http://extender.kube-system/",
   169  					},
   170  				),
   171  			},
   172  			wantProfiles:  []string{"default-scheduler"},
   173  			wantExtenders: []string{"http://extender.kube-system/"},
   174  		},
   175  	}
   176  
   177  	for _, tc := range cases {
   178  		t.Run(tc.name, func(t *testing.T) {
   179  			client := fake.NewSimpleClientset()
   180  			informerFactory := informers.NewSharedInformerFactory(client, 0)
   181  
   182  			eventBroadcaster := events.NewBroadcaster(&events.EventSinkImpl{Interface: client.EventsV1()})
   183  
   184  			_, ctx := ktesting.NewTestContext(t)
   185  			ctx, cancel := context.WithCancel(ctx)
   186  			defer cancel()
   187  			s, err := New(
   188  				ctx,
   189  				client,
   190  				informerFactory,
   191  				nil,
   192  				profile.NewRecorderFactory(eventBroadcaster),
   193  				tc.opts...,
   194  			)
   195  
   196  			// Errors
   197  			if len(tc.wantErr) != 0 {
   198  				if err == nil || !strings.Contains(err.Error(), tc.wantErr) {
   199  					t.Errorf("got error %q, want %q", err, tc.wantErr)
   200  				}
   201  				return
   202  			}
   203  			if err != nil {
   204  				t.Fatalf("Failed to create scheduler: %v", err)
   205  			}
   206  
   207  			// Profiles
   208  			profiles := make([]string, 0, len(s.Profiles))
   209  			for name := range s.Profiles {
   210  				profiles = append(profiles, name)
   211  			}
   212  			sort.Strings(profiles)
   213  			if diff := cmp.Diff(tc.wantProfiles, profiles); diff != "" {
   214  				t.Errorf("unexpected profiles (-want, +got):\n%s", diff)
   215  			}
   216  
   217  			// Extenders
   218  			if len(tc.wantExtenders) != 0 {
   219  				// Scheduler.Extenders
   220  				extenders := make([]string, 0, len(s.Extenders))
   221  				for _, e := range s.Extenders {
   222  					extenders = append(extenders, e.Name())
   223  				}
   224  				if diff := cmp.Diff(tc.wantExtenders, extenders); diff != "" {
   225  					t.Errorf("unexpected extenders (-want, +got):\n%s", diff)
   226  				}
   227  
   228  				// framework.Handle.Extenders()
   229  				for _, p := range s.Profiles {
   230  					extenders := make([]string, 0, len(p.Extenders()))
   231  					for _, e := range p.Extenders() {
   232  						extenders = append(extenders, e.Name())
   233  					}
   234  					if diff := cmp.Diff(tc.wantExtenders, extenders); diff != "" {
   235  						t.Errorf("unexpected extenders (-want, +got):\n%s", diff)
   236  					}
   237  				}
   238  			}
   239  		})
   240  	}
   241  }
   242  
   243  func TestFailureHandler(t *testing.T) {
   244  	testPod := st.MakePod().Name("test-pod").Namespace(v1.NamespaceDefault).Obj()
   245  	testPodUpdated := testPod.DeepCopy()
   246  	testPodUpdated.Labels = map[string]string{"foo": ""}
   247  
   248  	tests := []struct {
   249  		name                       string
   250  		podUpdatedDuringScheduling bool // pod is updated during a scheduling cycle
   251  		podDeletedDuringScheduling bool // pod is deleted during a scheduling cycle
   252  		expect                     *v1.Pod
   253  	}{
   254  		{
   255  			name:                       "pod is updated during a scheduling cycle",
   256  			podUpdatedDuringScheduling: true,
   257  			expect:                     testPodUpdated,
   258  		},
   259  		{
   260  			name:   "pod is not updated during a scheduling cycle",
   261  			expect: testPod,
   262  		},
   263  		{
   264  			name:                       "pod is deleted during a scheduling cycle",
   265  			podDeletedDuringScheduling: true,
   266  			expect:                     nil,
   267  		},
   268  	}
   269  
   270  	for _, tt := range tests {
   271  		t.Run(tt.name, func(t *testing.T) {
   272  			logger, ctx := ktesting.NewTestContext(t)
   273  			ctx, cancel := context.WithCancel(ctx)
   274  			defer cancel()
   275  
   276  			client := fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{*testPod}})
   277  			informerFactory := informers.NewSharedInformerFactory(client, 0)
   278  			podInformer := informerFactory.Core().V1().Pods()
   279  			// Need to add/update/delete testPod to the store.
   280  			podInformer.Informer().GetStore().Add(testPod)
   281  
   282  			queue := internalqueue.NewPriorityQueue(nil, informerFactory, internalqueue.WithClock(testingclock.NewFakeClock(time.Now())))
   283  			schedulerCache := internalcache.New(ctx, 30*time.Second)
   284  
   285  			if err := queue.Add(logger, testPod); err != nil {
   286  				t.Fatalf("Add failed: %v", err)
   287  			}
   288  
   289  			if _, err := queue.Pop(logger); err != nil {
   290  				t.Fatalf("Pop failed: %v", err)
   291  			}
   292  
   293  			if tt.podUpdatedDuringScheduling {
   294  				podInformer.Informer().GetStore().Update(testPodUpdated)
   295  				queue.Update(logger, testPod, testPodUpdated)
   296  			}
   297  			if tt.podDeletedDuringScheduling {
   298  				podInformer.Informer().GetStore().Delete(testPod)
   299  				queue.Delete(testPod)
   300  			}
   301  
   302  			s, fwk, err := initScheduler(ctx, schedulerCache, queue, client, informerFactory)
   303  			if err != nil {
   304  				t.Fatal(err)
   305  			}
   306  
   307  			testPodInfo := &framework.QueuedPodInfo{PodInfo: mustNewPodInfo(t, testPod)}
   308  			s.FailureHandler(ctx, fwk, testPodInfo, framework.NewStatus(framework.Unschedulable), nil, time.Now())
   309  
   310  			var got *v1.Pod
   311  			if tt.podUpdatedDuringScheduling {
   312  				head, e := queue.Pop(logger)
   313  				if e != nil {
   314  					t.Fatalf("Cannot pop pod from the activeQ: %v", e)
   315  				}
   316  				got = head.Pod
   317  			} else {
   318  				got = getPodFromPriorityQueue(queue, testPod)
   319  			}
   320  
   321  			if diff := cmp.Diff(tt.expect, got); diff != "" {
   322  				t.Errorf("Unexpected pod (-want, +got): %s", diff)
   323  			}
   324  		})
   325  	}
   326  }
   327  
   328  func TestFailureHandler_NodeNotFound(t *testing.T) {
   329  	nodeFoo := &v1.Node{ObjectMeta: metav1.ObjectMeta{Name: "foo"}}
   330  	nodeBar := &v1.Node{ObjectMeta: metav1.ObjectMeta{Name: "bar"}}
   331  	testPod := st.MakePod().Name("test-pod").Namespace(v1.NamespaceDefault).Obj()
   332  	tests := []struct {
   333  		name             string
   334  		nodes            []v1.Node
   335  		nodeNameToDelete string
   336  		injectErr        error
   337  		expectNodeNames  sets.Set[string]
   338  	}{
   339  		{
   340  			name:             "node is deleted during a scheduling cycle",
   341  			nodes:            []v1.Node{*nodeFoo, *nodeBar},
   342  			nodeNameToDelete: "foo",
   343  			injectErr:        apierrors.NewNotFound(v1.Resource("node"), nodeFoo.Name),
   344  			expectNodeNames:  sets.New("bar"),
   345  		},
   346  		{
   347  			name:            "node is not deleted but NodeNotFound is received incorrectly",
   348  			nodes:           []v1.Node{*nodeFoo, *nodeBar},
   349  			injectErr:       apierrors.NewNotFound(v1.Resource("node"), nodeFoo.Name),
   350  			expectNodeNames: sets.New("foo", "bar"),
   351  		},
   352  	}
   353  
   354  	for _, tt := range tests {
   355  		t.Run(tt.name, func(t *testing.T) {
   356  			logger, ctx := ktesting.NewTestContext(t)
   357  			ctx, cancel := context.WithCancel(ctx)
   358  			defer cancel()
   359  
   360  			client := fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{*testPod}}, &v1.NodeList{Items: tt.nodes})
   361  			informerFactory := informers.NewSharedInformerFactory(client, 0)
   362  			podInformer := informerFactory.Core().V1().Pods()
   363  			// Need to add testPod to the store.
   364  			podInformer.Informer().GetStore().Add(testPod)
   365  
   366  			queue := internalqueue.NewPriorityQueue(nil, informerFactory, internalqueue.WithClock(testingclock.NewFakeClock(time.Now())))
   367  			schedulerCache := internalcache.New(ctx, 30*time.Second)
   368  
   369  			for i := range tt.nodes {
   370  				node := tt.nodes[i]
   371  				// Add node to schedulerCache no matter it's deleted in API server or not.
   372  				schedulerCache.AddNode(logger, &node)
   373  				if node.Name == tt.nodeNameToDelete {
   374  					client.CoreV1().Nodes().Delete(ctx, node.Name, metav1.DeleteOptions{})
   375  				}
   376  			}
   377  
   378  			s, fwk, err := initScheduler(ctx, schedulerCache, queue, client, informerFactory)
   379  			if err != nil {
   380  				t.Fatal(err)
   381  			}
   382  
   383  			testPodInfo := &framework.QueuedPodInfo{PodInfo: mustNewPodInfo(t, testPod)}
   384  			s.FailureHandler(ctx, fwk, testPodInfo, framework.NewStatus(framework.Unschedulable).WithError(tt.injectErr), nil, time.Now())
   385  
   386  			gotNodes := schedulerCache.Dump().Nodes
   387  			gotNodeNames := sets.New[string]()
   388  			for _, nodeInfo := range gotNodes {
   389  				gotNodeNames.Insert(nodeInfo.Node().Name)
   390  			}
   391  			if diff := cmp.Diff(tt.expectNodeNames, gotNodeNames); diff != "" {
   392  				t.Errorf("Unexpected nodes (-want, +got): %s", diff)
   393  			}
   394  		})
   395  	}
   396  }
   397  
   398  func TestFailureHandler_PodAlreadyBound(t *testing.T) {
   399  	logger, ctx := ktesting.NewTestContext(t)
   400  	ctx, cancel := context.WithCancel(ctx)
   401  	defer cancel()
   402  
   403  	nodeFoo := v1.Node{ObjectMeta: metav1.ObjectMeta{Name: "foo"}}
   404  	testPod := st.MakePod().Name("test-pod").Namespace(v1.NamespaceDefault).Node("foo").Obj()
   405  
   406  	client := fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{*testPod}}, &v1.NodeList{Items: []v1.Node{nodeFoo}})
   407  	informerFactory := informers.NewSharedInformerFactory(client, 0)
   408  	podInformer := informerFactory.Core().V1().Pods()
   409  	// Need to add testPod to the store.
   410  	podInformer.Informer().GetStore().Add(testPod)
   411  
   412  	queue := internalqueue.NewPriorityQueue(nil, informerFactory, internalqueue.WithClock(testingclock.NewFakeClock(time.Now())))
   413  	schedulerCache := internalcache.New(ctx, 30*time.Second)
   414  
   415  	// Add node to schedulerCache no matter it's deleted in API server or not.
   416  	schedulerCache.AddNode(logger, &nodeFoo)
   417  
   418  	s, fwk, err := initScheduler(ctx, schedulerCache, queue, client, informerFactory)
   419  	if err != nil {
   420  		t.Fatal(err)
   421  	}
   422  
   423  	testPodInfo := &framework.QueuedPodInfo{PodInfo: mustNewPodInfo(t, testPod)}
   424  	s.FailureHandler(ctx, fwk, testPodInfo, framework.NewStatus(framework.Unschedulable).WithError(fmt.Errorf("binding rejected: timeout")), nil, time.Now())
   425  
   426  	pod := getPodFromPriorityQueue(queue, testPod)
   427  	if pod != nil {
   428  		t.Fatalf("Unexpected pod: %v should not be in PriorityQueue when the NodeName of pod is not empty", pod.Name)
   429  	}
   430  }
   431  
   432  // TestWithPercentageOfNodesToScore tests scheduler's PercentageOfNodesToScore is set correctly.
   433  func TestWithPercentageOfNodesToScore(t *testing.T) {
   434  	tests := []struct {
   435  		name                           string
   436  		percentageOfNodesToScoreConfig *int32
   437  		wantedPercentageOfNodesToScore int32
   438  	}{
   439  		{
   440  			name:                           "percentageOfNodesScore is nil",
   441  			percentageOfNodesToScoreConfig: nil,
   442  			wantedPercentageOfNodesToScore: schedulerapi.DefaultPercentageOfNodesToScore,
   443  		},
   444  		{
   445  			name:                           "percentageOfNodesScore is not nil",
   446  			percentageOfNodesToScoreConfig: ptr.To[int32](10),
   447  			wantedPercentageOfNodesToScore: 10,
   448  		},
   449  	}
   450  
   451  	for _, tt := range tests {
   452  		t.Run(tt.name, func(t *testing.T) {
   453  			client := fake.NewSimpleClientset()
   454  			informerFactory := informers.NewSharedInformerFactory(client, 0)
   455  			eventBroadcaster := events.NewBroadcaster(&events.EventSinkImpl{Interface: client.EventsV1()})
   456  			_, ctx := ktesting.NewTestContext(t)
   457  			ctx, cancel := context.WithCancel(ctx)
   458  			defer cancel()
   459  			sched, err := New(
   460  				ctx,
   461  				client,
   462  				informerFactory,
   463  				nil,
   464  				profile.NewRecorderFactory(eventBroadcaster),
   465  				WithPercentageOfNodesToScore(tt.percentageOfNodesToScoreConfig),
   466  			)
   467  			if err != nil {
   468  				t.Fatalf("Failed to create scheduler: %v", err)
   469  			}
   470  			if sched.percentageOfNodesToScore != tt.wantedPercentageOfNodesToScore {
   471  				t.Errorf("scheduler.percercentageOfNodesToScore = %v, want %v", sched.percentageOfNodesToScore, tt.wantedPercentageOfNodesToScore)
   472  			}
   473  		})
   474  	}
   475  }
   476  
   477  // getPodFromPriorityQueue is the function used in the TestDefaultErrorFunc test to get
   478  // the specific pod from the given priority queue. It returns the found pod in the priority queue.
   479  func getPodFromPriorityQueue(queue *internalqueue.PriorityQueue, pod *v1.Pod) *v1.Pod {
   480  	podList, _ := queue.PendingPods()
   481  	if len(podList) == 0 {
   482  		return nil
   483  	}
   484  
   485  	queryPodKey, err := cache.MetaNamespaceKeyFunc(pod)
   486  	if err != nil {
   487  		return nil
   488  	}
   489  
   490  	for _, foundPod := range podList {
   491  		foundPodKey, err := cache.MetaNamespaceKeyFunc(foundPod)
   492  		if err != nil {
   493  			return nil
   494  		}
   495  
   496  		if foundPodKey == queryPodKey {
   497  			return foundPod
   498  		}
   499  	}
   500  
   501  	return nil
   502  }
   503  
   504  func initScheduler(ctx context.Context, cache internalcache.Cache, queue internalqueue.SchedulingQueue,
   505  	client kubernetes.Interface, informerFactory informers.SharedInformerFactory) (*Scheduler, framework.Framework, error) {
   506  	logger := klog.FromContext(ctx)
   507  	registerPluginFuncs := []tf.RegisterPluginFunc{
   508  		tf.RegisterQueueSortPlugin(queuesort.Name, queuesort.New),
   509  		tf.RegisterBindPlugin(defaultbinder.Name, defaultbinder.New),
   510  	}
   511  	eventBroadcaster := events.NewBroadcaster(&events.EventSinkImpl{Interface: client.EventsV1()})
   512  	fwk, err := tf.NewFramework(ctx,
   513  		registerPluginFuncs,
   514  		testSchedulerName,
   515  		frameworkruntime.WithClientSet(client),
   516  		frameworkruntime.WithInformerFactory(informerFactory),
   517  		frameworkruntime.WithEventRecorder(eventBroadcaster.NewRecorder(scheme.Scheme, testSchedulerName)),
   518  	)
   519  	if err != nil {
   520  		return nil, nil, err
   521  	}
   522  
   523  	s := &Scheduler{
   524  		Cache:           cache,
   525  		client:          client,
   526  		StopEverything:  ctx.Done(),
   527  		SchedulingQueue: queue,
   528  		Profiles:        profile.Map{testSchedulerName: fwk},
   529  		logger:          logger,
   530  	}
   531  	s.applyDefaultHandlers()
   532  
   533  	return s, fwk, nil
   534  }
   535  
   536  func TestInitPluginsWithIndexers(t *testing.T) {
   537  	tests := []struct {
   538  		name string
   539  		// the plugin registration ordering must not matter, being map traversal random
   540  		entrypoints map[string]frameworkruntime.PluginFactory
   541  		wantErr     string
   542  	}{
   543  		{
   544  			name: "register indexer, no conflicts",
   545  			entrypoints: map[string]frameworkruntime.PluginFactory{
   546  				"AddIndexer": func(ctx context.Context, obj runtime.Object, handle framework.Handle) (framework.Plugin, error) {
   547  					podInformer := handle.SharedInformerFactory().Core().V1().Pods()
   548  					err := podInformer.Informer().AddIndexers(cache.Indexers{
   549  						"nodeName": indexByPodSpecNodeName,
   550  					})
   551  					return &TestPlugin{name: "AddIndexer"}, err
   552  				},
   553  			},
   554  		},
   555  		{
   556  			name: "register the same indexer name multiple times, conflict",
   557  			// order of registration doesn't matter
   558  			entrypoints: map[string]frameworkruntime.PluginFactory{
   559  				"AddIndexer1": func(ctx context.Context, obj runtime.Object, handle framework.Handle) (framework.Plugin, error) {
   560  					podInformer := handle.SharedInformerFactory().Core().V1().Pods()
   561  					err := podInformer.Informer().AddIndexers(cache.Indexers{
   562  						"nodeName": indexByPodSpecNodeName,
   563  					})
   564  					return &TestPlugin{name: "AddIndexer1"}, err
   565  				},
   566  				"AddIndexer2": func(ctx context.Context, obj runtime.Object, handle framework.Handle) (framework.Plugin, error) {
   567  					podInformer := handle.SharedInformerFactory().Core().V1().Pods()
   568  					err := podInformer.Informer().AddIndexers(cache.Indexers{
   569  						"nodeName": indexByPodAnnotationNodeName,
   570  					})
   571  					return &TestPlugin{name: "AddIndexer1"}, err
   572  				},
   573  			},
   574  			wantErr: "indexer conflict",
   575  		},
   576  		{
   577  			name: "register the same indexer body with different names, no conflicts",
   578  			// order of registration doesn't matter
   579  			entrypoints: map[string]frameworkruntime.PluginFactory{
   580  				"AddIndexer1": func(ctx context.Context, obj runtime.Object, handle framework.Handle) (framework.Plugin, error) {
   581  					podInformer := handle.SharedInformerFactory().Core().V1().Pods()
   582  					err := podInformer.Informer().AddIndexers(cache.Indexers{
   583  						"nodeName1": indexByPodSpecNodeName,
   584  					})
   585  					return &TestPlugin{name: "AddIndexer1"}, err
   586  				},
   587  				"AddIndexer2": func(ctx context.Context, obj runtime.Object, handle framework.Handle) (framework.Plugin, error) {
   588  					podInformer := handle.SharedInformerFactory().Core().V1().Pods()
   589  					err := podInformer.Informer().AddIndexers(cache.Indexers{
   590  						"nodeName2": indexByPodAnnotationNodeName,
   591  					})
   592  					return &TestPlugin{name: "AddIndexer2"}, err
   593  				},
   594  			},
   595  		},
   596  	}
   597  
   598  	for _, tt := range tests {
   599  		t.Run(tt.name, func(t *testing.T) {
   600  			fakeInformerFactory := NewInformerFactory(&fake.Clientset{}, 0*time.Second)
   601  
   602  			var registerPluginFuncs []tf.RegisterPluginFunc
   603  			for name, entrypoint := range tt.entrypoints {
   604  				registerPluginFuncs = append(registerPluginFuncs,
   605  					// anything supported by TestPlugin is fine
   606  					tf.RegisterFilterPlugin(name, entrypoint),
   607  				)
   608  			}
   609  			// we always need this
   610  			registerPluginFuncs = append(registerPluginFuncs,
   611  				tf.RegisterQueueSortPlugin(queuesort.Name, queuesort.New),
   612  				tf.RegisterBindPlugin(defaultbinder.Name, defaultbinder.New),
   613  			)
   614  			_, ctx := ktesting.NewTestContext(t)
   615  			ctx, cancel := context.WithCancel(ctx)
   616  			defer cancel()
   617  			_, err := tf.NewFramework(ctx, registerPluginFuncs, "test", frameworkruntime.WithInformerFactory(fakeInformerFactory))
   618  
   619  			if len(tt.wantErr) > 0 {
   620  				if err == nil || !strings.Contains(err.Error(), tt.wantErr) {
   621  					t.Errorf("got error %q, want %q", err, tt.wantErr)
   622  				}
   623  				return
   624  			}
   625  			if err != nil {
   626  				t.Fatalf("Failed to create scheduler: %v", err)
   627  			}
   628  		})
   629  	}
   630  }
   631  
   632  func indexByPodSpecNodeName(obj interface{}) ([]string, error) {
   633  	pod, ok := obj.(*v1.Pod)
   634  	if !ok {
   635  		return []string{}, nil
   636  	}
   637  	if len(pod.Spec.NodeName) == 0 {
   638  		return []string{}, nil
   639  	}
   640  	return []string{pod.Spec.NodeName}, nil
   641  }
   642  
   643  func indexByPodAnnotationNodeName(obj interface{}) ([]string, error) {
   644  	pod, ok := obj.(*v1.Pod)
   645  	if !ok {
   646  		return []string{}, nil
   647  	}
   648  	if len(pod.Annotations) == 0 {
   649  		return []string{}, nil
   650  	}
   651  	nodeName, ok := pod.Annotations["node-name"]
   652  	if !ok {
   653  		return []string{}, nil
   654  	}
   655  	return []string{nodeName}, nil
   656  }
   657  
   658  const (
   659  	filterWithoutEnqueueExtensions = "filterWithoutEnqueueExtensions"
   660  	fakeNode                       = "fakeNode"
   661  	fakePod                        = "fakePod"
   662  	emptyEventsToRegister          = "emptyEventsToRegister"
   663  	queueSort                      = "no-op-queue-sort-plugin"
   664  	fakeBind                       = "bind-plugin"
   665  	emptyEventExtensions           = "emptyEventExtensions"
   666  )
   667  
   668  func Test_buildQueueingHintMap(t *testing.T) {
   669  	tests := []struct {
   670  		name                string
   671  		plugins             []framework.Plugin
   672  		want                map[framework.ClusterEvent][]*internalqueue.QueueingHintFunction
   673  		featuregateDisabled bool
   674  	}{
   675  		{
   676  			name:    "filter without EnqueueExtensions plugin",
   677  			plugins: []framework.Plugin{&filterWithoutEnqueueExtensionsPlugin{}},
   678  			want: map[framework.ClusterEvent][]*internalqueue.QueueingHintFunction{
   679  				{Resource: framework.Pod, ActionType: framework.All}: {
   680  					{PluginName: filterWithoutEnqueueExtensions, QueueingHintFn: defaultQueueingHintFn},
   681  				},
   682  				{Resource: framework.Node, ActionType: framework.All}: {
   683  					{PluginName: filterWithoutEnqueueExtensions, QueueingHintFn: defaultQueueingHintFn},
   684  				},
   685  				{Resource: framework.CSINode, ActionType: framework.All}: {
   686  					{PluginName: filterWithoutEnqueueExtensions, QueueingHintFn: defaultQueueingHintFn},
   687  				},
   688  				{Resource: framework.CSIDriver, ActionType: framework.All}: {
   689  					{PluginName: filterWithoutEnqueueExtensions, QueueingHintFn: defaultQueueingHintFn},
   690  				},
   691  				{Resource: framework.CSIStorageCapacity, ActionType: framework.All}: {
   692  					{PluginName: filterWithoutEnqueueExtensions, QueueingHintFn: defaultQueueingHintFn},
   693  				},
   694  				{Resource: framework.PersistentVolume, ActionType: framework.All}: {
   695  					{PluginName: filterWithoutEnqueueExtensions, QueueingHintFn: defaultQueueingHintFn},
   696  				},
   697  				{Resource: framework.StorageClass, ActionType: framework.All}: {
   698  					{PluginName: filterWithoutEnqueueExtensions, QueueingHintFn: defaultQueueingHintFn},
   699  				},
   700  				{Resource: framework.PersistentVolumeClaim, ActionType: framework.All}: {
   701  					{PluginName: filterWithoutEnqueueExtensions, QueueingHintFn: defaultQueueingHintFn},
   702  				},
   703  				{Resource: framework.PodSchedulingContext, ActionType: framework.All}: {
   704  					{PluginName: filterWithoutEnqueueExtensions, QueueingHintFn: defaultQueueingHintFn},
   705  				},
   706  			},
   707  		},
   708  		{
   709  			name:    "node and pod plugin",
   710  			plugins: []framework.Plugin{&fakeNodePlugin{}, &fakePodPlugin{}},
   711  			want: map[framework.ClusterEvent][]*internalqueue.QueueingHintFunction{
   712  				{Resource: framework.Pod, ActionType: framework.Add}: {
   713  					{PluginName: fakePod, QueueingHintFn: fakePodPluginQueueingFn},
   714  				},
   715  				{Resource: framework.Node, ActionType: framework.Add}: {
   716  					{PluginName: fakeNode, QueueingHintFn: fakeNodePluginQueueingFn},
   717  				},
   718  			},
   719  		},
   720  		{
   721  			name:                "node and pod plugin (featuregate is disabled)",
   722  			plugins:             []framework.Plugin{&fakeNodePlugin{}, &fakePodPlugin{}},
   723  			featuregateDisabled: true,
   724  			want: map[framework.ClusterEvent][]*internalqueue.QueueingHintFunction{
   725  				{Resource: framework.Pod, ActionType: framework.Add}: {
   726  					{PluginName: fakePod, QueueingHintFn: defaultQueueingHintFn}, // default queueing hint due to disabled feature gate.
   727  				},
   728  				{Resource: framework.Node, ActionType: framework.Add}: {
   729  					{PluginName: fakeNode, QueueingHintFn: defaultQueueingHintFn}, // default queueing hint due to disabled feature gate.
   730  				},
   731  			},
   732  		},
   733  		{
   734  			name:    "register plugin with empty event",
   735  			plugins: []framework.Plugin{&emptyEventPlugin{}},
   736  			want:    map[framework.ClusterEvent][]*internalqueue.QueueingHintFunction{},
   737  		},
   738  		{
   739  			name:    "register plugins including emptyEventPlugin",
   740  			plugins: []framework.Plugin{&emptyEventPlugin{}, &fakeNodePlugin{}},
   741  			want: map[framework.ClusterEvent][]*internalqueue.QueueingHintFunction{
   742  				{Resource: framework.Pod, ActionType: framework.Add}: {
   743  					{PluginName: fakePod, QueueingHintFn: fakePodPluginQueueingFn},
   744  				},
   745  				{Resource: framework.Node, ActionType: framework.Add}: {
   746  					{PluginName: fakeNode, QueueingHintFn: fakeNodePluginQueueingFn},
   747  				},
   748  			},
   749  		},
   750  	}
   751  
   752  	for _, tt := range tests {
   753  		t.Run(tt.name, func(t *testing.T) {
   754  			defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.SchedulerQueueingHints, !tt.featuregateDisabled)()
   755  			logger, ctx := ktesting.NewTestContext(t)
   756  			ctx, cancel := context.WithCancel(ctx)
   757  			defer cancel()
   758  			registry := frameworkruntime.Registry{}
   759  			cfgPls := &schedulerapi.Plugins{}
   760  			plugins := append(tt.plugins, &fakebindPlugin{}, &fakeQueueSortPlugin{})
   761  			for _, pl := range plugins {
   762  				tmpPl := pl
   763  				if err := registry.Register(pl.Name(), func(_ context.Context, _ runtime.Object, _ framework.Handle) (framework.Plugin, error) {
   764  					return tmpPl, nil
   765  				}); err != nil {
   766  					t.Fatalf("fail to register filter plugin (%s)", pl.Name())
   767  				}
   768  				cfgPls.MultiPoint.Enabled = append(cfgPls.MultiPoint.Enabled, schedulerapi.Plugin{Name: pl.Name()})
   769  			}
   770  
   771  			profile := schedulerapi.KubeSchedulerProfile{Plugins: cfgPls}
   772  			fwk, err := newFramework(ctx, registry, profile)
   773  			if err != nil {
   774  				t.Fatal(err)
   775  			}
   776  
   777  			exts := fwk.EnqueueExtensions()
   778  			// need to sort to make the test result stable.
   779  			sort.Slice(exts, func(i, j int) bool {
   780  				return exts[i].Name() < exts[j].Name()
   781  			})
   782  
   783  			got := buildQueueingHintMap(exts)
   784  
   785  			for e, fns := range got {
   786  				wantfns, ok := tt.want[e]
   787  				if !ok {
   788  					t.Errorf("got unexpected event %v", e)
   789  					continue
   790  				}
   791  				if len(fns) != len(wantfns) {
   792  					t.Errorf("got %v queueing hint functions, want %v", len(fns), len(wantfns))
   793  					continue
   794  				}
   795  				for i, fn := range fns {
   796  					if fn.PluginName != wantfns[i].PluginName {
   797  						t.Errorf("got plugin name %v, want %v", fn.PluginName, wantfns[i].PluginName)
   798  						continue
   799  					}
   800  					got, gotErr := fn.QueueingHintFn(logger, nil, nil, nil)
   801  					want, wantErr := wantfns[i].QueueingHintFn(logger, nil, nil, nil)
   802  					if got != want || gotErr != wantErr {
   803  						t.Errorf("got queueing hint function (%v) returning (%v, %v), expect it to return (%v, %v)", fn.PluginName, got, gotErr, want, wantErr)
   804  						continue
   805  					}
   806  				}
   807  			}
   808  		})
   809  	}
   810  }
   811  
   812  // Test_UnionedGVKs tests UnionedGVKs worked with buildQueueingHintMap.
   813  func Test_UnionedGVKs(t *testing.T) {
   814  	tests := []struct {
   815  		name    string
   816  		plugins schedulerapi.PluginSet
   817  		want    map[framework.GVK]framework.ActionType
   818  	}{
   819  		{
   820  			name: "filter without EnqueueExtensions plugin",
   821  			plugins: schedulerapi.PluginSet{
   822  				Enabled: []schedulerapi.Plugin{
   823  					{Name: filterWithoutEnqueueExtensions},
   824  					{Name: queueSort},
   825  					{Name: fakeBind},
   826  				},
   827  				Disabled: []schedulerapi.Plugin{{Name: "*"}}, // disable default plugins
   828  			},
   829  			want: map[framework.GVK]framework.ActionType{
   830  				framework.Pod:                   framework.All,
   831  				framework.Node:                  framework.All,
   832  				framework.CSINode:               framework.All,
   833  				framework.CSIDriver:             framework.All,
   834  				framework.CSIStorageCapacity:    framework.All,
   835  				framework.PersistentVolume:      framework.All,
   836  				framework.PersistentVolumeClaim: framework.All,
   837  				framework.StorageClass:          framework.All,
   838  				framework.PodSchedulingContext:  framework.All,
   839  			},
   840  		},
   841  		{
   842  			name: "node plugin",
   843  			plugins: schedulerapi.PluginSet{
   844  				Enabled: []schedulerapi.Plugin{
   845  					{Name: fakeNode},
   846  					{Name: queueSort},
   847  					{Name: fakeBind},
   848  				},
   849  				Disabled: []schedulerapi.Plugin{{Name: "*"}}, // disable default plugins
   850  			},
   851  			want: map[framework.GVK]framework.ActionType{
   852  				framework.Node: framework.Add,
   853  			},
   854  		},
   855  		{
   856  			name: "pod plugin",
   857  			plugins: schedulerapi.PluginSet{
   858  				Enabled: []schedulerapi.Plugin{
   859  					{Name: fakePod},
   860  					{Name: queueSort},
   861  					{Name: fakeBind},
   862  				},
   863  				Disabled: []schedulerapi.Plugin{{Name: "*"}}, // disable default plugins
   864  			},
   865  			want: map[framework.GVK]framework.ActionType{
   866  				framework.Pod: framework.Add,
   867  			},
   868  		},
   869  		{
   870  			name: "node and pod plugin",
   871  			plugins: schedulerapi.PluginSet{
   872  				Enabled: []schedulerapi.Plugin{
   873  					{Name: fakePod},
   874  					{Name: fakeNode},
   875  					{Name: queueSort},
   876  					{Name: fakeBind},
   877  				},
   878  				Disabled: []schedulerapi.Plugin{{Name: "*"}}, // disable default plugins
   879  			},
   880  			want: map[framework.GVK]framework.ActionType{
   881  				framework.Pod:  framework.Add,
   882  				framework.Node: framework.Add,
   883  			},
   884  		},
   885  		{
   886  			name: "empty EventsToRegister plugin",
   887  			plugins: schedulerapi.PluginSet{
   888  				Enabled: []schedulerapi.Plugin{
   889  					{Name: emptyEventsToRegister},
   890  					{Name: queueSort},
   891  					{Name: fakeBind},
   892  				},
   893  				Disabled: []schedulerapi.Plugin{{Name: "*"}}, // disable default plugins
   894  			},
   895  			want: map[framework.GVK]framework.ActionType{},
   896  		},
   897  		{
   898  			name:    "plugins with default profile",
   899  			plugins: schedulerapi.PluginSet{Enabled: defaults.PluginsV1.MultiPoint.Enabled},
   900  			want: map[framework.GVK]framework.ActionType{
   901  				framework.Pod:                   framework.All,
   902  				framework.Node:                  framework.All,
   903  				framework.CSINode:               framework.All - framework.Delete,
   904  				framework.CSIDriver:             framework.All - framework.Delete,
   905  				framework.CSIStorageCapacity:    framework.All - framework.Delete,
   906  				framework.PersistentVolume:      framework.All - framework.Delete,
   907  				framework.PersistentVolumeClaim: framework.All - framework.Delete,
   908  				framework.StorageClass:          framework.All - framework.Delete,
   909  			},
   910  		},
   911  	}
   912  	for _, tt := range tests {
   913  		t.Run(tt.name, func(t *testing.T) {
   914  			_, ctx := ktesting.NewTestContext(t)
   915  			ctx, cancel := context.WithCancel(ctx)
   916  			defer cancel()
   917  			registry := plugins.NewInTreeRegistry()
   918  
   919  			cfgPls := &schedulerapi.Plugins{MultiPoint: tt.plugins}
   920  			plugins := []framework.Plugin{&fakeNodePlugin{}, &fakePodPlugin{}, &filterWithoutEnqueueExtensionsPlugin{}, &emptyEventsToRegisterPlugin{}, &fakeQueueSortPlugin{}, &fakebindPlugin{}}
   921  			for _, pl := range plugins {
   922  				tmpPl := pl
   923  				if err := registry.Register(pl.Name(), func(_ context.Context, _ runtime.Object, _ framework.Handle) (framework.Plugin, error) {
   924  					return tmpPl, nil
   925  				}); err != nil {
   926  					t.Fatalf("fail to register filter plugin (%s)", pl.Name())
   927  				}
   928  			}
   929  
   930  			profile := schedulerapi.KubeSchedulerProfile{Plugins: cfgPls, PluginConfig: defaults.PluginConfigsV1}
   931  			fwk, err := newFramework(ctx, registry, profile)
   932  			if err != nil {
   933  				t.Fatal(err)
   934  			}
   935  
   936  			queueingHintsPerProfile := internalqueue.QueueingHintMapPerProfile{
   937  				"default": buildQueueingHintMap(fwk.EnqueueExtensions()),
   938  			}
   939  			got := unionedGVKs(queueingHintsPerProfile)
   940  
   941  			if diff := cmp.Diff(tt.want, got); diff != "" {
   942  				t.Errorf("Unexpected eventToPlugin map (-want,+got):%s", diff)
   943  			}
   944  		})
   945  	}
   946  }
   947  
   948  func newFramework(ctx context.Context, r frameworkruntime.Registry, profile schedulerapi.KubeSchedulerProfile) (framework.Framework, error) {
   949  	return frameworkruntime.NewFramework(ctx, r, &profile,
   950  		frameworkruntime.WithSnapshotSharedLister(internalcache.NewSnapshot(nil, nil)),
   951  		frameworkruntime.WithInformerFactory(informers.NewSharedInformerFactory(fake.NewSimpleClientset(), 0)),
   952  	)
   953  }
   954  
   955  var _ framework.QueueSortPlugin = &fakeQueueSortPlugin{}
   956  
   957  // fakeQueueSortPlugin is a no-op implementation for QueueSort extension point.
   958  type fakeQueueSortPlugin struct{}
   959  
   960  func (pl *fakeQueueSortPlugin) Name() string {
   961  	return queueSort
   962  }
   963  
   964  func (pl *fakeQueueSortPlugin) Less(_, _ *framework.QueuedPodInfo) bool {
   965  	return false
   966  }
   967  
   968  var _ framework.BindPlugin = &fakebindPlugin{}
   969  
   970  // fakebindPlugin is a no-op implementation for Bind extension point.
   971  type fakebindPlugin struct{}
   972  
   973  func (t *fakebindPlugin) Name() string {
   974  	return fakeBind
   975  }
   976  
   977  func (t *fakebindPlugin) Bind(ctx context.Context, state *framework.CycleState, p *v1.Pod, nodeName string) *framework.Status {
   978  	return nil
   979  }
   980  
   981  // filterWithoutEnqueueExtensionsPlugin implements Filter, but doesn't implement EnqueueExtensions.
   982  type filterWithoutEnqueueExtensionsPlugin struct{}
   983  
   984  func (*filterWithoutEnqueueExtensionsPlugin) Name() string { return filterWithoutEnqueueExtensions }
   985  
   986  func (*filterWithoutEnqueueExtensionsPlugin) Filter(_ context.Context, _ *framework.CycleState, _ *v1.Pod, _ *framework.NodeInfo) *framework.Status {
   987  	return nil
   988  }
   989  
   990  var hintFromFakeNode = framework.QueueingHint(100)
   991  
   992  type fakeNodePlugin struct{}
   993  
   994  var fakeNodePluginQueueingFn = func(_ klog.Logger, _ *v1.Pod, _, _ interface{}) (framework.QueueingHint, error) {
   995  	return hintFromFakeNode, nil
   996  }
   997  
   998  func (*fakeNodePlugin) Name() string { return fakeNode }
   999  
  1000  func (*fakeNodePlugin) Filter(_ context.Context, _ *framework.CycleState, _ *v1.Pod, _ *framework.NodeInfo) *framework.Status {
  1001  	return nil
  1002  }
  1003  
  1004  func (pl *fakeNodePlugin) EventsToRegister() []framework.ClusterEventWithHint {
  1005  	return []framework.ClusterEventWithHint{
  1006  		{Event: framework.ClusterEvent{Resource: framework.Node, ActionType: framework.Add}, QueueingHintFn: fakeNodePluginQueueingFn},
  1007  	}
  1008  }
  1009  
  1010  var hintFromFakePod = framework.QueueingHint(101)
  1011  
  1012  type fakePodPlugin struct{}
  1013  
  1014  var fakePodPluginQueueingFn = func(_ klog.Logger, _ *v1.Pod, _, _ interface{}) (framework.QueueingHint, error) {
  1015  	return hintFromFakePod, nil
  1016  }
  1017  
  1018  func (*fakePodPlugin) Name() string { return fakePod }
  1019  
  1020  func (*fakePodPlugin) Filter(_ context.Context, _ *framework.CycleState, _ *v1.Pod, _ *framework.NodeInfo) *framework.Status {
  1021  	return nil
  1022  }
  1023  
  1024  func (pl *fakePodPlugin) EventsToRegister() []framework.ClusterEventWithHint {
  1025  	return []framework.ClusterEventWithHint{
  1026  		{Event: framework.ClusterEvent{Resource: framework.Pod, ActionType: framework.Add}, QueueingHintFn: fakePodPluginQueueingFn},
  1027  	}
  1028  }
  1029  
  1030  type emptyEventPlugin struct{}
  1031  
  1032  func (*emptyEventPlugin) Name() string { return emptyEventExtensions }
  1033  
  1034  func (*emptyEventPlugin) Filter(_ context.Context, _ *framework.CycleState, _ *v1.Pod, _ *framework.NodeInfo) *framework.Status {
  1035  	return nil
  1036  }
  1037  
  1038  func (pl *emptyEventPlugin) EventsToRegister() []framework.ClusterEventWithHint {
  1039  	return nil
  1040  }
  1041  
  1042  // emptyEventsToRegisterPlugin implement interface framework.EnqueueExtensions, but returns nil from EventsToRegister.
  1043  // This can simulate a plugin registered at scheduler setup, but does nothing
  1044  // due to some disabled feature gate.
  1045  type emptyEventsToRegisterPlugin struct{}
  1046  
  1047  func (*emptyEventsToRegisterPlugin) Name() string { return emptyEventsToRegister }
  1048  
  1049  func (*emptyEventsToRegisterPlugin) Filter(_ context.Context, _ *framework.CycleState, _ *v1.Pod, _ *framework.NodeInfo) *framework.Status {
  1050  	return nil
  1051  }
  1052  
  1053  func (*emptyEventsToRegisterPlugin) EventsToRegister() []framework.ClusterEventWithHint { return nil }