k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/pkg/scheduler/internal/queue/scheduling_queue_test.go (about)

     1  /*
     2  Copyright 2017 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 queue
    18  
    19  import (
    20  	"container/list"
    21  	"context"
    22  	"fmt"
    23  	"math"
    24  	"strings"
    25  	"sync"
    26  	"testing"
    27  	"time"
    28  
    29  	"github.com/google/go-cmp/cmp"
    30  	"github.com/google/go-cmp/cmp/cmpopts"
    31  	v1 "k8s.io/api/core/v1"
    32  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    33  	"k8s.io/apimachinery/pkg/runtime"
    34  	"k8s.io/apimachinery/pkg/types"
    35  	"k8s.io/apimachinery/pkg/util/sets"
    36  	utilfeature "k8s.io/apiserver/pkg/util/feature"
    37  	"k8s.io/client-go/informers"
    38  	"k8s.io/client-go/kubernetes/fake"
    39  	featuregatetesting "k8s.io/component-base/featuregate/testing"
    40  	"k8s.io/component-base/metrics/testutil"
    41  	"k8s.io/klog/v2"
    42  	"k8s.io/klog/v2/ktesting"
    43  	podutil "k8s.io/kubernetes/pkg/api/v1/pod"
    44  	"k8s.io/kubernetes/pkg/features"
    45  	"k8s.io/kubernetes/pkg/scheduler/framework"
    46  	"k8s.io/kubernetes/pkg/scheduler/framework/plugins/queuesort"
    47  	"k8s.io/kubernetes/pkg/scheduler/framework/plugins/schedulinggates"
    48  	"k8s.io/kubernetes/pkg/scheduler/metrics"
    49  	st "k8s.io/kubernetes/pkg/scheduler/testing"
    50  	"k8s.io/kubernetes/pkg/scheduler/util"
    51  	testingclock "k8s.io/utils/clock/testing"
    52  )
    53  
    54  const queueMetricMetadata = `
    55  		# HELP scheduler_queue_incoming_pods_total [STABLE] Number of pods added to scheduling queues by event and queue type.
    56  		# TYPE scheduler_queue_incoming_pods_total counter
    57  	`
    58  
    59  var (
    60  	NodeAllEvent = framework.ClusterEvent{Resource: framework.Node, ActionType: framework.All}
    61  
    62  	lowPriority, midPriority, highPriority = int32(0), int32(100), int32(1000)
    63  	mediumPriority                         = (lowPriority + highPriority) / 2
    64  
    65  	highPriorityPodInfo = mustNewPodInfo(
    66  		st.MakePod().Name("hpp").Namespace("ns1").UID("hppns1").Priority(highPriority).Obj(),
    67  	)
    68  	highPriNominatedPodInfo = mustNewPodInfo(
    69  		st.MakePod().Name("hpp").Namespace("ns1").UID("hppns1").Priority(highPriority).NominatedNodeName("node1").Obj(),
    70  	)
    71  	medPriorityPodInfo = mustNewPodInfo(
    72  		st.MakePod().Name("mpp").Namespace("ns2").UID("mppns2").Annotation("annot2", "val2").Priority(mediumPriority).NominatedNodeName("node1").Obj(),
    73  	)
    74  	unschedulablePodInfo = mustNewPodInfo(
    75  		st.MakePod().Name("up").Namespace("ns1").UID("upns1").Annotation("annot2", "val2").Priority(lowPriority).NominatedNodeName("node1").Condition(v1.PodScheduled, v1.ConditionFalse, v1.PodReasonUnschedulable).Obj(),
    76  	)
    77  	nonExistentPodInfo = mustNewPodInfo(
    78  		st.MakePod().Name("ne").Namespace("ns1").UID("nens1").Obj(),
    79  	)
    80  	scheduledPodInfo = mustNewPodInfo(
    81  		st.MakePod().Name("sp").Namespace("ns1").UID("spns1").Node("foo").Obj(),
    82  	)
    83  
    84  	nominatorCmpOpts = []cmp.Option{
    85  		cmp.AllowUnexported(nominator{}),
    86  		cmpopts.IgnoreFields(nominator{}, "podLister", "lock"),
    87  	}
    88  
    89  	queueHintReturnQueue = func(logger klog.Logger, pod *v1.Pod, oldObj, newObj interface{}) (framework.QueueingHint, error) {
    90  		return framework.Queue, nil
    91  	}
    92  	queueHintReturnSkip = func(logger klog.Logger, pod *v1.Pod, oldObj, newObj interface{}) (framework.QueueingHint, error) {
    93  		return framework.QueueSkip, nil
    94  	}
    95  )
    96  
    97  func setQueuedPodInfoGated(queuedPodInfo *framework.QueuedPodInfo) *framework.QueuedPodInfo {
    98  	queuedPodInfo.Gated = true
    99  	return queuedPodInfo
   100  }
   101  
   102  func getUnschedulablePod(p *PriorityQueue, pod *v1.Pod) *v1.Pod {
   103  	pInfo := p.unschedulablePods.get(pod)
   104  	if pInfo != nil {
   105  		return pInfo.Pod
   106  	}
   107  	return nil
   108  }
   109  
   110  // makeEmptyQueueingHintMapPerProfile initializes an empty QueueingHintMapPerProfile for "" profile name.
   111  func makeEmptyQueueingHintMapPerProfile() QueueingHintMapPerProfile {
   112  	m := make(QueueingHintMapPerProfile)
   113  	m[""] = make(QueueingHintMap)
   114  	return m
   115  }
   116  
   117  func TestPriorityQueue_Add(t *testing.T) {
   118  	objs := []runtime.Object{medPriorityPodInfo.Pod, unschedulablePodInfo.Pod, highPriorityPodInfo.Pod}
   119  	logger, ctx := ktesting.NewTestContext(t)
   120  	ctx, cancel := context.WithCancel(ctx)
   121  	defer cancel()
   122  	q := NewTestQueueWithObjects(ctx, newDefaultQueueSort(), objs)
   123  	if err := q.Add(logger, medPriorityPodInfo.Pod); err != nil {
   124  		t.Errorf("add failed: %v", err)
   125  	}
   126  	if err := q.Add(logger, unschedulablePodInfo.Pod); err != nil {
   127  		t.Errorf("add failed: %v", err)
   128  	}
   129  	if err := q.Add(logger, highPriorityPodInfo.Pod); err != nil {
   130  		t.Errorf("add failed: %v", err)
   131  	}
   132  	expectedNominatedPods := &nominator{
   133  		nominatedPodToNode: map[types.UID]string{
   134  			medPriorityPodInfo.Pod.UID:   "node1",
   135  			unschedulablePodInfo.Pod.UID: "node1",
   136  		},
   137  		nominatedPods: map[string][]*framework.PodInfo{
   138  			"node1": {medPriorityPodInfo, unschedulablePodInfo},
   139  		},
   140  	}
   141  	if diff := cmp.Diff(q.nominator, expectedNominatedPods, nominatorCmpOpts...); diff != "" {
   142  		t.Errorf("Unexpected diff after adding pods (-want, +got):\n%s", diff)
   143  	}
   144  	if p, err := q.Pop(logger); err != nil || p.Pod != highPriorityPodInfo.Pod {
   145  		t.Errorf("Expected: %v after Pop, but got: %v", highPriorityPodInfo.Pod.Name, p.Pod.Name)
   146  	}
   147  	if p, err := q.Pop(logger); err != nil || p.Pod != medPriorityPodInfo.Pod {
   148  		t.Errorf("Expected: %v after Pop, but got: %v", medPriorityPodInfo.Pod.Name, p.Pod.Name)
   149  	}
   150  	if p, err := q.Pop(logger); err != nil || p.Pod != unschedulablePodInfo.Pod {
   151  		t.Errorf("Expected: %v after Pop, but got: %v", unschedulablePodInfo.Pod.Name, p.Pod.Name)
   152  	}
   153  	if len(q.nominator.nominatedPods["node1"]) != 2 {
   154  		t.Errorf("Expected medPriorityPodInfo and unschedulablePodInfo to be still present in nomindatePods: %v", q.nominator.nominatedPods["node1"])
   155  	}
   156  }
   157  
   158  func newDefaultQueueSort() framework.LessFunc {
   159  	sort := &queuesort.PrioritySort{}
   160  	return sort.Less
   161  }
   162  
   163  func TestPriorityQueue_AddWithReversePriorityLessFunc(t *testing.T) {
   164  	objs := []runtime.Object{medPriorityPodInfo.Pod, highPriorityPodInfo.Pod}
   165  	logger, ctx := ktesting.NewTestContext(t)
   166  	ctx, cancel := context.WithCancel(ctx)
   167  	defer cancel()
   168  	q := NewTestQueueWithObjects(ctx, newDefaultQueueSort(), objs)
   169  	if err := q.Add(logger, medPriorityPodInfo.Pod); err != nil {
   170  		t.Errorf("add failed: %v", err)
   171  	}
   172  	if err := q.Add(logger, highPriorityPodInfo.Pod); err != nil {
   173  		t.Errorf("add failed: %v", err)
   174  	}
   175  	if p, err := q.Pop(logger); err != nil || p.Pod != highPriorityPodInfo.Pod {
   176  		t.Errorf("Expected: %v after Pop, but got: %v", highPriorityPodInfo.Pod.Name, p.Pod.Name)
   177  	}
   178  	if p, err := q.Pop(logger); err != nil || p.Pod != medPriorityPodInfo.Pod {
   179  		t.Errorf("Expected: %v after Pop, but got: %v", medPriorityPodInfo.Pod.Name, p.Pod.Name)
   180  	}
   181  }
   182  
   183  func listToValues(l *list.List) []interface{} {
   184  	var values []interface{}
   185  	for e := l.Front(); e != nil; e = e.Next() {
   186  		values = append(values, e.Value)
   187  	}
   188  	return values
   189  }
   190  
   191  func Test_InFlightPods(t *testing.T) {
   192  	logger, _ := ktesting.NewTestContext(t)
   193  	pod := st.MakePod().Name("targetpod").UID("pod1").Obj()
   194  	pod2 := st.MakePod().Name("targetpod2").UID("pod2").Obj()
   195  	pod3 := st.MakePod().Name("targetpod3").UID("pod3").Obj()
   196  	var poppedPod, poppedPod2 *framework.QueuedPodInfo
   197  
   198  	type action struct {
   199  		// ONLY ONE of the following should be set.
   200  		eventHappens *framework.ClusterEvent
   201  		podPopped    *v1.Pod
   202  		podEnqueued  *framework.QueuedPodInfo
   203  		callback     func(t *testing.T, q *PriorityQueue)
   204  	}
   205  
   206  	tests := []struct {
   207  		name            string
   208  		queueingHintMap QueueingHintMapPerProfile
   209  		// initialPods is the initial Pods in the activeQ.
   210  		initialPods                  []*v1.Pod
   211  		actions                      []action
   212  		wantInFlightPods             []*v1.Pod
   213  		wantInFlightEvents           []interface{}
   214  		wantActiveQPodNames          []string
   215  		wantBackoffQPodNames         []string
   216  		wantUnschedPodPoolPodNames   []string
   217  		isSchedulingQueueHintEnabled bool
   218  	}{
   219  		{
   220  			name:        "when SchedulingQueueHint is disabled, inFlightPods and inFlightEvents should be empty",
   221  			initialPods: []*v1.Pod{pod},
   222  			actions: []action{
   223  				// This Pod shouldn't be added to inFlightPods because SchedulingQueueHint is disabled.
   224  				{podPopped: pod},
   225  				// This event shouldn't be added to inFlightEvents because SchedulingQueueHint is disabled.
   226  				{eventHappens: &PvAdd},
   227  			},
   228  			wantInFlightPods:   nil,
   229  			wantInFlightEvents: nil,
   230  			queueingHintMap: QueueingHintMapPerProfile{
   231  				"": {
   232  					PvAdd: {
   233  						{
   234  							PluginName:     "fooPlugin1",
   235  							QueueingHintFn: queueHintReturnQueue,
   236  						},
   237  					},
   238  				},
   239  			},
   240  		},
   241  		{
   242  			name:                         "Pod and interested events are registered in inFlightPods/inFlightEvents",
   243  			isSchedulingQueueHintEnabled: true,
   244  			initialPods:                  []*v1.Pod{pod},
   245  			actions: []action{
   246  				// This won't be added to inFlightEvents because no inFlightPods at this point.
   247  				{eventHappens: &PvcAdd},
   248  				{podPopped: pod},
   249  				// This gets added for the pod.
   250  				{eventHappens: &PvAdd},
   251  				// This doesn't get added because no plugin is interested in PvUpdate.
   252  				{eventHappens: &PvUpdate},
   253  			},
   254  			wantInFlightPods:   []*v1.Pod{pod},
   255  			wantInFlightEvents: []interface{}{pod, PvAdd},
   256  			queueingHintMap: QueueingHintMapPerProfile{
   257  				"": {
   258  					PvAdd: {
   259  						{
   260  							PluginName:     "fooPlugin1",
   261  							QueueingHintFn: queueHintReturnQueue,
   262  						},
   263  					},
   264  				},
   265  			},
   266  		},
   267  		{
   268  			name:                         "Pod, registered in inFlightPods, is enqueued back to activeQ",
   269  			isSchedulingQueueHintEnabled: true,
   270  			initialPods:                  []*v1.Pod{pod, pod2},
   271  			actions: []action{
   272  				// This won't be added to inFlightEvents because no inFlightPods at this point.
   273  				{eventHappens: &PvcAdd},
   274  				{podPopped: pod},
   275  				{eventHappens: &PvAdd},
   276  				{podPopped: pod2},
   277  				{eventHappens: &NodeAdd},
   278  				// This pod will be requeued to backoffQ because no plugin is registered as unschedulable plugin.
   279  				{podEnqueued: newQueuedPodInfoForLookup(pod)},
   280  			},
   281  			wantBackoffQPodNames: []string{"targetpod"},
   282  			wantInFlightPods:     []*v1.Pod{pod2}, // only pod2 is registered because pod is already enqueued back.
   283  			wantInFlightEvents:   []interface{}{pod2, NodeAdd},
   284  			queueingHintMap: QueueingHintMapPerProfile{
   285  				"": {
   286  					PvAdd: {
   287  						{
   288  							PluginName:     "fooPlugin1",
   289  							QueueingHintFn: queueHintReturnQueue,
   290  						},
   291  					},
   292  					NodeAdd: {
   293  						{
   294  							PluginName:     "fooPlugin1",
   295  							QueueingHintFn: queueHintReturnQueue,
   296  						},
   297  					},
   298  					PvcAdd: {
   299  						{
   300  							PluginName:     "fooPlugin1",
   301  							QueueingHintFn: queueHintReturnQueue,
   302  						},
   303  					},
   304  				},
   305  			},
   306  		},
   307  		{
   308  			name:                         "All Pods registered in inFlightPods are enqueued back to activeQ",
   309  			isSchedulingQueueHintEnabled: true,
   310  			initialPods:                  []*v1.Pod{pod, pod2},
   311  			actions: []action{
   312  				// This won't be added to inFlightEvents because no inFlightPods at this point.
   313  				{eventHappens: &PvcAdd},
   314  				{podPopped: pod},
   315  				{eventHappens: &PvAdd},
   316  				{podPopped: pod2},
   317  				{eventHappens: &NodeAdd},
   318  				// This pod will be requeued to backoffQ because no plugin is registered as unschedulable plugin.
   319  				{podEnqueued: newQueuedPodInfoForLookup(pod)},
   320  				{eventHappens: &CSINodeUpdate},
   321  				// This pod will be requeued to backoffQ because no plugin is registered as unschedulable plugin.
   322  				{podEnqueued: newQueuedPodInfoForLookup(pod2)},
   323  			},
   324  			wantBackoffQPodNames: []string{"targetpod", "targetpod2"},
   325  			wantInFlightPods:     nil, // empty
   326  			queueingHintMap: QueueingHintMapPerProfile{
   327  				"": {
   328  					PvAdd: {
   329  						{
   330  							PluginName:     "fooPlugin1",
   331  							QueueingHintFn: queueHintReturnQueue,
   332  						},
   333  					},
   334  					NodeAdd: {
   335  						{
   336  							PluginName:     "fooPlugin1",
   337  							QueueingHintFn: queueHintReturnQueue,
   338  						},
   339  					},
   340  					PvcAdd: {
   341  						{
   342  							PluginName:     "fooPlugin1",
   343  							QueueingHintFn: queueHintReturnQueue,
   344  						},
   345  					},
   346  					CSINodeUpdate: {
   347  						{
   348  							PluginName:     "fooPlugin1",
   349  							QueueingHintFn: queueHintReturnQueue,
   350  						},
   351  					},
   352  				},
   353  			},
   354  		},
   355  		{
   356  			name:                         "One intermediate Pod registered in inFlightPods is enqueued back to activeQ",
   357  			isSchedulingQueueHintEnabled: true,
   358  			initialPods:                  []*v1.Pod{pod, pod2, pod3},
   359  			actions: []action{
   360  				// This won't be added to inFlightEvents because no inFlightPods at this point.
   361  				{eventHappens: &PvcAdd},
   362  				{podPopped: pod},
   363  				{eventHappens: &PvAdd},
   364  				{podPopped: pod2},
   365  				{eventHappens: &NodeAdd},
   366  				// This Pod won't be requeued again.
   367  				{podPopped: pod3},
   368  				{eventHappens: &AssignedPodAdd},
   369  				{podEnqueued: newQueuedPodInfoForLookup(pod2)},
   370  			},
   371  			wantBackoffQPodNames: []string{"targetpod2"},
   372  			wantInFlightPods:     []*v1.Pod{pod, pod3},
   373  			wantInFlightEvents:   []interface{}{pod, PvAdd, NodeAdd, pod3, AssignedPodAdd},
   374  			queueingHintMap: QueueingHintMapPerProfile{
   375  				"": {
   376  					PvAdd: {
   377  						{
   378  							PluginName:     "fooPlugin1",
   379  							QueueingHintFn: queueHintReturnQueue,
   380  						},
   381  					},
   382  					NodeAdd: {
   383  						{
   384  							PluginName:     "fooPlugin1",
   385  							QueueingHintFn: queueHintReturnQueue,
   386  						},
   387  					},
   388  					AssignedPodAdd: {
   389  						{
   390  							PluginName:     "fooPlugin1",
   391  							QueueingHintFn: queueHintReturnQueue,
   392  						},
   393  					},
   394  				},
   395  			},
   396  		},
   397  		{
   398  			name:        "pod is enqueued to queue without QueueingHint when SchedulingQueueHint is disabled",
   399  			initialPods: []*v1.Pod{pod},
   400  			actions: []action{
   401  				{podPopped: pod},
   402  				{eventHappens: &AssignedPodAdd},
   403  				{podEnqueued: newQueuedPodInfoForLookup(pod, "fooPlugin1")},
   404  			},
   405  			wantBackoffQPodNames: []string{"targetpod"},
   406  			wantInFlightPods:     nil,
   407  			wantInFlightEvents:   nil,
   408  			queueingHintMap: QueueingHintMapPerProfile{
   409  				"": {
   410  					// This hint fn tells that this event doesn't make a Pod schedulable.
   411  					// However, this QueueingHintFn will be ignored actually because SchedulingQueueHint is disabled.
   412  					AssignedPodAdd: {
   413  						{
   414  							PluginName:     "fooPlugin1",
   415  							QueueingHintFn: queueHintReturnSkip,
   416  						},
   417  					},
   418  				},
   419  			},
   420  		},
   421  		{
   422  			name:                         "events before popping Pod are ignored when Pod is enqueued back to queue",
   423  			isSchedulingQueueHintEnabled: true,
   424  			initialPods:                  []*v1.Pod{pod},
   425  			actions: []action{
   426  				{eventHappens: &WildCardEvent},
   427  				{podPopped: pod},
   428  				{eventHappens: &AssignedPodAdd},
   429  				// This Pod won't be requeued to activeQ/backoffQ because fooPlugin1 returns QueueSkip.
   430  				{podEnqueued: newQueuedPodInfoForLookup(pod, "fooPlugin1")},
   431  			},
   432  			wantUnschedPodPoolPodNames: []string{"targetpod"},
   433  			wantInFlightPods:           nil,
   434  			wantInFlightEvents:         nil,
   435  			queueingHintMap: QueueingHintMapPerProfile{
   436  				"": {
   437  					// fooPlugin1 has a queueing hint function for AssignedPodAdd,
   438  					// but hint fn tells that this event doesn't make a Pod scheudlable.
   439  					AssignedPodAdd: {
   440  						{
   441  							PluginName:     "fooPlugin1",
   442  							QueueingHintFn: queueHintReturnSkip,
   443  						},
   444  					},
   445  				},
   446  			},
   447  		},
   448  		{
   449  			name:                         "pod is enqueued to backoff if no failed plugin",
   450  			isSchedulingQueueHintEnabled: true,
   451  			initialPods:                  []*v1.Pod{pod},
   452  			actions: []action{
   453  				{podPopped: pod},
   454  				{eventHappens: &AssignedPodAdd},
   455  				{podEnqueued: newQueuedPodInfoForLookup(pod)},
   456  			},
   457  			wantBackoffQPodNames: []string{"targetpod"},
   458  			wantInFlightPods:     nil,
   459  			wantInFlightEvents:   nil,
   460  			queueingHintMap: QueueingHintMapPerProfile{
   461  				"": {
   462  					// It will be ignored because no failed plugin.
   463  					AssignedPodAdd: {
   464  						{
   465  							PluginName:     "fooPlugin1",
   466  							QueueingHintFn: queueHintReturnQueue,
   467  						},
   468  					},
   469  				},
   470  			},
   471  		},
   472  		{
   473  			name:                         "pod is enqueued to unschedulable pod pool if no events that can make the pod schedulable",
   474  			isSchedulingQueueHintEnabled: true,
   475  			initialPods:                  []*v1.Pod{pod},
   476  			actions: []action{
   477  				{podPopped: pod},
   478  				{eventHappens: &NodeAdd},
   479  				{podEnqueued: newQueuedPodInfoForLookup(pod, "fooPlugin1")},
   480  			},
   481  			wantUnschedPodPoolPodNames: []string{"targetpod"},
   482  			wantInFlightPods:           nil,
   483  			wantInFlightEvents:         nil,
   484  			queueingHintMap: QueueingHintMapPerProfile{
   485  				"": {
   486  					// fooPlugin1 has no queueing hint function for NodeAdd.
   487  					AssignedPodAdd: {
   488  						{
   489  							// It will be ignored because the event is not NodeAdd.
   490  							PluginName:     "fooPlugin1",
   491  							QueueingHintFn: queueHintReturnQueue,
   492  						},
   493  					},
   494  				},
   495  			},
   496  		},
   497  		{
   498  			name:                         "pod is enqueued to unschedulable pod pool because the failed plugin has a hint fn but it returns Skip",
   499  			isSchedulingQueueHintEnabled: true,
   500  			initialPods:                  []*v1.Pod{pod},
   501  			actions: []action{
   502  				{podPopped: pod},
   503  				{eventHappens: &AssignedPodAdd},
   504  				{podEnqueued: newQueuedPodInfoForLookup(pod, "fooPlugin1")},
   505  			},
   506  			wantUnschedPodPoolPodNames: []string{"targetpod"},
   507  			wantInFlightPods:           nil,
   508  			wantInFlightEvents:         nil,
   509  			queueingHintMap: QueueingHintMapPerProfile{
   510  				"": {
   511  					// fooPlugin1 has a queueing hint function for AssignedPodAdd,
   512  					// but hint fn tells that this event doesn't make a Pod scheudlable.
   513  					AssignedPodAdd: {
   514  						{
   515  							PluginName:     "fooPlugin1",
   516  							QueueingHintFn: queueHintReturnSkip,
   517  						},
   518  					},
   519  				},
   520  			},
   521  		},
   522  		{
   523  			name:                         "pod is enqueued to activeQ because the Pending plugins has a hint fn and it returns Queue",
   524  			isSchedulingQueueHintEnabled: true,
   525  			initialPods:                  []*v1.Pod{pod},
   526  			actions: []action{
   527  				{podPopped: pod},
   528  				{eventHappens: &AssignedPodAdd},
   529  				{podEnqueued: &framework.QueuedPodInfo{
   530  					PodInfo:              mustNewPodInfo(pod),
   531  					UnschedulablePlugins: sets.New("fooPlugin2", "fooPlugin3"),
   532  					PendingPlugins:       sets.New("fooPlugin1"),
   533  				}},
   534  			},
   535  			wantActiveQPodNames: []string{"targetpod"},
   536  			wantInFlightPods:    nil,
   537  			wantInFlightEvents:  nil,
   538  			queueingHintMap: QueueingHintMapPerProfile{
   539  				"": {
   540  					AssignedPodAdd: {
   541  						{
   542  							PluginName:     "fooPlugin3",
   543  							QueueingHintFn: queueHintReturnSkip,
   544  						},
   545  						{
   546  							PluginName:     "fooPlugin2",
   547  							QueueingHintFn: queueHintReturnQueue,
   548  						},
   549  						{
   550  							// The hint fn tells that this event makes a Pod scheudlable immediately.
   551  							PluginName:     "fooPlugin1",
   552  							QueueingHintFn: queueHintReturnQueue,
   553  						},
   554  					},
   555  				},
   556  			},
   557  		},
   558  		{
   559  			name:                         "pod is enqueued to backoffQ because the failed plugin has a hint fn and it returns Queue",
   560  			isSchedulingQueueHintEnabled: true,
   561  			initialPods:                  []*v1.Pod{pod},
   562  			actions: []action{
   563  				{podPopped: pod},
   564  				{eventHappens: &AssignedPodAdd},
   565  				{podEnqueued: newQueuedPodInfoForLookup(pod, "fooPlugin1", "fooPlugin2")},
   566  			},
   567  			wantBackoffQPodNames: []string{"targetpod"},
   568  			wantInFlightPods:     nil,
   569  			wantInFlightEvents:   nil,
   570  			queueingHintMap: QueueingHintMapPerProfile{
   571  				"": {
   572  					AssignedPodAdd: {
   573  						{
   574  							// it will be ignored because the hint fn returns Skip that is weaker than queueHintReturnQueue from fooPlugin1.
   575  							PluginName:     "fooPlugin2",
   576  							QueueingHintFn: queueHintReturnSkip,
   577  						},
   578  						{
   579  							// The hint fn tells that this event makes a Pod schedulable.
   580  							PluginName:     "fooPlugin1",
   581  							QueueingHintFn: queueHintReturnQueue,
   582  						},
   583  					},
   584  				},
   585  			},
   586  		},
   587  		{
   588  			name:                         "pod is enqueued to activeQ because the failed plugin has a hint fn and it returns Queue for a concurrent event that was received while some other pod was in flight",
   589  			isSchedulingQueueHintEnabled: true,
   590  			initialPods:                  []*v1.Pod{pod, pod2},
   591  			actions: []action{
   592  				{callback: func(t *testing.T, q *PriorityQueue) { poppedPod = popPod(t, logger, q, pod) }},
   593  				{eventHappens: &NodeAdd},
   594  				{callback: func(t *testing.T, q *PriorityQueue) { poppedPod2 = popPod(t, logger, q, pod2) }},
   595  				{eventHappens: &AssignedPodAdd},
   596  				{callback: func(t *testing.T, q *PriorityQueue) {
   597  					logger, _ := ktesting.NewTestContext(t)
   598  					err := q.AddUnschedulableIfNotPresent(logger, poppedPod, q.SchedulingCycle())
   599  					if err != nil {
   600  						t.Fatalf("unexpected error from AddUnschedulableIfNotPresent: %v", err)
   601  					}
   602  				}},
   603  				{callback: func(t *testing.T, q *PriorityQueue) {
   604  					logger, _ := ktesting.NewTestContext(t)
   605  					poppedPod2.UnschedulablePlugins = sets.New("fooPlugin2", "fooPlugin3")
   606  					poppedPod2.PendingPlugins = sets.New("fooPlugin1")
   607  					err := q.AddUnschedulableIfNotPresent(logger, poppedPod2, q.SchedulingCycle())
   608  					if err != nil {
   609  						t.Fatalf("unexpected error from AddUnschedulableIfNotPresent: %v", err)
   610  					}
   611  				}},
   612  			},
   613  			wantActiveQPodNames: []string{pod2.Name},
   614  			wantInFlightPods:    nil,
   615  			wantInFlightEvents:  nil,
   616  			queueingHintMap: QueueingHintMapPerProfile{
   617  				"": {
   618  					AssignedPodAdd: {
   619  						{
   620  							// it will be ignored because the hint fn returns QueueSkip that is weaker than queueHintReturnQueueImmediately from fooPlugin1.
   621  							PluginName:     "fooPlugin3",
   622  							QueueingHintFn: queueHintReturnSkip,
   623  						},
   624  						{
   625  							// it will be ignored because the fooPlugin2 is registered in UnschedulablePlugins and it's interpret as Queue that is weaker than QueueImmediately from fooPlugin1.
   626  							PluginName:     "fooPlugin2",
   627  							QueueingHintFn: queueHintReturnQueue,
   628  						},
   629  						{
   630  							// The hint fn tells that this event makes a Pod scheudlable.
   631  							// Given fooPlugin1 is registered as Pendings, we interpret Queue as queueImmediately.
   632  							PluginName:     "fooPlugin1",
   633  							QueueingHintFn: queueHintReturnQueue,
   634  						},
   635  					},
   636  				},
   637  			},
   638  		},
   639  		{
   640  			name:                         "popped pod must have empty UnschedulablePlugins and PendingPlugins",
   641  			isSchedulingQueueHintEnabled: true,
   642  			initialPods:                  []*v1.Pod{pod},
   643  			actions: []action{
   644  				{callback: func(t *testing.T, q *PriorityQueue) { poppedPod = popPod(t, logger, q, pod) }},
   645  				{callback: func(t *testing.T, q *PriorityQueue) {
   646  					logger, _ := ktesting.NewTestContext(t)
   647  					// Unschedulable due to PendingPlugins.
   648  					poppedPod.PendingPlugins = sets.New("fooPlugin1")
   649  					poppedPod.UnschedulablePlugins = sets.New("fooPlugin2")
   650  					if err := q.AddUnschedulableIfNotPresent(logger, poppedPod, q.SchedulingCycle()); err != nil {
   651  						t.Errorf("Unexpected error from AddUnschedulableIfNotPresent: %v", err)
   652  					}
   653  				}},
   654  				{eventHappens: &PvAdd}, // Active again.
   655  				{callback: func(t *testing.T, q *PriorityQueue) {
   656  					poppedPod = popPod(t, logger, q, pod)
   657  					if len(poppedPod.UnschedulablePlugins) > 0 {
   658  						t.Errorf("QueuedPodInfo from Pop should have empty UnschedulablePlugins, got instead: %+v", poppedPod)
   659  					}
   660  				}},
   661  				{callback: func(t *testing.T, q *PriorityQueue) {
   662  					logger, _ := ktesting.NewTestContext(t)
   663  					// Failed (i.e. no UnschedulablePlugins). Should go to backoff.
   664  					if err := q.AddUnschedulableIfNotPresent(logger, poppedPod, q.SchedulingCycle()); err != nil {
   665  						t.Errorf("Unexpected error from AddUnschedulableIfNotPresent: %v", err)
   666  					}
   667  				}},
   668  			},
   669  			queueingHintMap: QueueingHintMapPerProfile{
   670  				"": {
   671  					PvAdd: {
   672  						{
   673  							// The hint fn tells that this event makes a Pod scheudlable immediately.
   674  							PluginName:     "fooPlugin1",
   675  							QueueingHintFn: queueHintReturnQueue,
   676  						},
   677  					},
   678  				},
   679  			},
   680  		},
   681  	}
   682  
   683  	for _, test := range tests {
   684  		t.Run(test.name, func(t *testing.T) {
   685  			featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.SchedulerQueueingHints, test.isSchedulingQueueHintEnabled)
   686  			logger, ctx := ktesting.NewTestContext(t)
   687  			ctx, cancel := context.WithCancel(ctx)
   688  			defer cancel()
   689  			obj := make([]runtime.Object, 0, len(test.initialPods))
   690  			for _, p := range test.initialPods {
   691  				obj = append(obj, p)
   692  			}
   693  			fakeClock := testingclock.NewFakeClock(time.Now())
   694  			q := NewTestQueueWithObjects(ctx, newDefaultQueueSort(), obj, WithQueueingHintMapPerProfile(test.queueingHintMap), WithClock(fakeClock))
   695  
   696  			// When a Pod is added to the queue, the QueuedPodInfo will have a new timestamp.
   697  			// On Windows, time.Now() is not as precise, 2 consecutive calls may return the same timestamp.
   698  			// Thus, all the QueuedPodInfos can have the same timestamps, which can be an issue
   699  			// when we're expecting them to be popped in a certain order (the Less function
   700  			// sorts them by Timestamps if they have the same Pod Priority).
   701  			// Using a fake clock for the queue and incrementing it after each added Pod will
   702  			// solve this issue on Windows unit test runs.
   703  			// For more details on the Windows clock resolution issue, see: https://github.com/golang/go/issues/8687
   704  			for _, p := range test.initialPods {
   705  				q.Add(logger, p)
   706  				fakeClock.Step(time.Second)
   707  			}
   708  
   709  			for _, action := range test.actions {
   710  				switch {
   711  				case action.podPopped != nil:
   712  					popPod(t, logger, q, action.podPopped)
   713  				case action.eventHappens != nil:
   714  					q.MoveAllToActiveOrBackoffQueue(logger, *action.eventHappens, nil, nil, nil)
   715  				case action.podEnqueued != nil:
   716  					err := q.AddUnschedulableIfNotPresent(logger, action.podEnqueued, q.SchedulingCycle())
   717  					if err != nil {
   718  						t.Fatalf("unexpected error from AddUnschedulableIfNotPresent: %v", err)
   719  					}
   720  				case action.callback != nil:
   721  					action.callback(t, q)
   722  				}
   723  			}
   724  
   725  			actualInFlightPods := make(map[types.UID]*v1.Pod)
   726  			for uid, element := range q.inFlightPods {
   727  				actualInFlightPods[uid] = element.Value.(*v1.Pod)
   728  			}
   729  			wantInFlightPods := make(map[types.UID]*v1.Pod)
   730  			for _, pod := range test.wantInFlightPods {
   731  				wantInFlightPods[pod.UID] = pod
   732  			}
   733  			if diff := cmp.Diff(wantInFlightPods, actualInFlightPods); diff != "" {
   734  				t.Errorf("Unexpected diff in inFlightPods (-want, +got):\n%s", diff)
   735  			}
   736  
   737  			var wantInFlightEvents []interface{}
   738  			for _, value := range test.wantInFlightEvents {
   739  				if event, ok := value.(framework.ClusterEvent); ok {
   740  					value = &clusterEvent{event: event}
   741  				}
   742  				wantInFlightEvents = append(wantInFlightEvents, value)
   743  			}
   744  			if diff := cmp.Diff(wantInFlightEvents, listToValues(q.inFlightEvents), cmp.AllowUnexported(clusterEvent{})); diff != "" {
   745  				t.Errorf("Unexpected diff in inFlightEvents (-want, +got):\n%s", diff)
   746  			}
   747  
   748  			if test.wantActiveQPodNames != nil {
   749  				rawPodInfos := q.activeQ.List()
   750  				if len(rawPodInfos) != len(test.wantActiveQPodNames) {
   751  					diff := cmp.Diff(test.wantActiveQPodNames, rawPodInfos, cmpopts.SortSlices(func(a, b interface{}) bool {
   752  						return a.(framework.PodInfo).Pod.Name < b.(framework.PodInfo).Pod.Name
   753  					}))
   754  					t.Fatalf("Length of activeQ is not expected. Got %v, want %v.\n%s", len(rawPodInfos), len(test.wantActiveQPodNames), diff)
   755  				}
   756  
   757  				wantPodNames := sets.New(test.wantActiveQPodNames...)
   758  				for _, rawPodInfo := range rawPodInfos {
   759  					podGotFromActiveQ := rawPodInfo.(*framework.QueuedPodInfo).Pod
   760  					if !wantPodNames.Has(podGotFromActiveQ.Name) {
   761  						t.Fatalf("Pod %v was not expected to be in the activeQ.", podGotFromActiveQ.Name)
   762  					}
   763  				}
   764  			}
   765  
   766  			if test.wantBackoffQPodNames != nil {
   767  				rawPodInfos := q.podBackoffQ.List()
   768  				if len(rawPodInfos) != len(test.wantBackoffQPodNames) {
   769  					diff := cmp.Diff(test.wantBackoffQPodNames, rawPodInfos, cmpopts.SortSlices(func(a, b interface{}) bool {
   770  						return a.(framework.PodInfo).Pod.Name < b.(framework.PodInfo).Pod.Name
   771  					}))
   772  					t.Fatalf("Length of backoffQ is not expected. Got %v, want %v.\n%s", len(rawPodInfos), len(test.wantBackoffQPodNames), diff)
   773  				}
   774  
   775  				wantPodNames := sets.New(test.wantBackoffQPodNames...)
   776  				for _, rawPodInfo := range rawPodInfos {
   777  					podGotFromBackoffQ := rawPodInfo.(*framework.QueuedPodInfo).Pod
   778  					if !wantPodNames.Has(podGotFromBackoffQ.Name) {
   779  						t.Fatalf("Pod %v was not expected to be in the backoffQ.", podGotFromBackoffQ.Name)
   780  					}
   781  				}
   782  			}
   783  
   784  			for _, podName := range test.wantUnschedPodPoolPodNames {
   785  				p := getUnschedulablePod(q, &st.MakePod().Name(podName).Pod)
   786  				if p == nil {
   787  					t.Fatalf("Pod %v was not found in the unschedulablePods.", podName)
   788  				}
   789  			}
   790  		})
   791  	}
   792  }
   793  
   794  func popPod(t *testing.T, logger klog.Logger, q *PriorityQueue, pod *v1.Pod) *framework.QueuedPodInfo {
   795  	p, err := q.Pop(logger)
   796  	if err != nil {
   797  		t.Fatalf("Pop failed: %v", err)
   798  	}
   799  	if p.Pod.UID != pod.UID {
   800  		t.Errorf("Unexpected popped pod: %v", p)
   801  	}
   802  	return p
   803  }
   804  
   805  func TestPop(t *testing.T) {
   806  	pod := st.MakePod().Name("targetpod").UID("pod1").Obj()
   807  	queueingHintMap := QueueingHintMapPerProfile{
   808  		"": {
   809  			PvAdd: {
   810  				{
   811  					// The hint fn tells that this event makes a Pod scheudlable.
   812  					PluginName:     "fooPlugin1",
   813  					QueueingHintFn: queueHintReturnQueue,
   814  				},
   815  			},
   816  		},
   817  	}
   818  
   819  	for name, isSchedulingQueueHintEnabled := range map[string]bool{"with-hints": true, "without-hints": false} {
   820  		t.Run(name, func(t *testing.T) {
   821  			featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.SchedulerQueueingHints, isSchedulingQueueHintEnabled)
   822  			logger, ctx := ktesting.NewTestContext(t)
   823  			ctx, cancel := context.WithCancel(ctx)
   824  			defer cancel()
   825  			q := NewTestQueueWithObjects(ctx, newDefaultQueueSort(), []runtime.Object{pod}, WithQueueingHintMapPerProfile(queueingHintMap))
   826  			q.Add(logger, pod)
   827  
   828  			// Simulate failed attempt that makes the pod unschedulable.
   829  			poppedPod := popPod(t, logger, q, pod)
   830  			// We put register the plugin to PendingPlugins so that it's interpreted as queueImmediately and skip backoff.
   831  			poppedPod.PendingPlugins = sets.New("fooPlugin1")
   832  			if err := q.AddUnschedulableIfNotPresent(logger, poppedPod, q.SchedulingCycle()); err != nil {
   833  				t.Errorf("Unexpected error from AddUnschedulableIfNotPresent: %v", err)
   834  			}
   835  
   836  			// Activate it again.
   837  			q.MoveAllToActiveOrBackoffQueue(logger, PvAdd, nil, nil, nil)
   838  
   839  			// Now check result of Pop.
   840  			poppedPod = popPod(t, logger, q, pod)
   841  			if len(poppedPod.PendingPlugins) > 0 {
   842  				t.Errorf("QueuedPodInfo from Pop should have empty PendingPlugins, got instead: %+v", poppedPod)
   843  			}
   844  		})
   845  	}
   846  }
   847  
   848  func TestPriorityQueue_AddUnschedulableIfNotPresent(t *testing.T) {
   849  	objs := []runtime.Object{highPriNominatedPodInfo.Pod, unschedulablePodInfo.Pod}
   850  	logger, ctx := ktesting.NewTestContext(t)
   851  	ctx, cancel := context.WithCancel(ctx)
   852  	defer cancel()
   853  	q := NewTestQueueWithObjects(ctx, newDefaultQueueSort(), objs)
   854  	// insert unschedulablePodInfo and pop right after that
   855  	// because the scheduling queue records unschedulablePod as in-flight Pod.
   856  	q.Add(logger, unschedulablePodInfo.Pod)
   857  	if p, err := q.Pop(logger); err != nil || p.Pod != unschedulablePodInfo.Pod {
   858  		t.Errorf("Expected: %v after Pop, but got: %v", unschedulablePodInfo.Pod.Name, p.Pod.Name)
   859  	}
   860  
   861  	q.Add(logger, highPriNominatedPodInfo.Pod)
   862  	err := q.AddUnschedulableIfNotPresent(logger, newQueuedPodInfoForLookup(unschedulablePodInfo.Pod, "plugin"), q.SchedulingCycle())
   863  	if err != nil {
   864  		t.Fatalf("unexpected error from AddUnschedulableIfNotPresent: %v", err)
   865  	}
   866  	expectedNominatedPods := &nominator{
   867  		nominatedPodToNode: map[types.UID]string{
   868  			unschedulablePodInfo.Pod.UID:    "node1",
   869  			highPriNominatedPodInfo.Pod.UID: "node1",
   870  		},
   871  		nominatedPods: map[string][]*framework.PodInfo{
   872  			"node1": {highPriNominatedPodInfo, unschedulablePodInfo},
   873  		},
   874  	}
   875  	if diff := cmp.Diff(q.nominator, expectedNominatedPods, nominatorCmpOpts...); diff != "" {
   876  		t.Errorf("Unexpected diff after adding pods (-want, +got):\n%s", diff)
   877  	}
   878  	if p, err := q.Pop(logger); err != nil || p.Pod != highPriNominatedPodInfo.Pod {
   879  		t.Errorf("Expected: %v after Pop, but got: %v", highPriNominatedPodInfo.Pod.Name, p.Pod.Name)
   880  	}
   881  	if len(q.nominator.nominatedPods) != 1 {
   882  		t.Errorf("Expected nomindatePods to have one element: %v", q.nominator)
   883  	}
   884  	// unschedulablePodInfo is inserted to unschedulable pod pool because no events happened during scheduling.
   885  	if getUnschedulablePod(q, unschedulablePodInfo.Pod) != unschedulablePodInfo.Pod {
   886  		t.Errorf("Pod %v was not found in the unschedulablePods.", unschedulablePodInfo.Pod.Name)
   887  	}
   888  }
   889  
   890  // TestPriorityQueue_AddUnschedulableIfNotPresent_Backoff tests the scenarios when
   891  // AddUnschedulableIfNotPresent is called asynchronously.
   892  // Pods in and before current scheduling cycle will be put back to activeQueue
   893  // if we were trying to schedule them when we received move request.
   894  func TestPriorityQueue_AddUnschedulableIfNotPresent_Backoff(t *testing.T) {
   895  	logger, ctx := ktesting.NewTestContext(t)
   896  	ctx, cancel := context.WithCancel(ctx)
   897  	defer cancel()
   898  	q := NewTestQueue(ctx, newDefaultQueueSort(), WithClock(testingclock.NewFakeClock(time.Now())))
   899  	totalNum := 10
   900  	expectedPods := make([]v1.Pod, 0, totalNum)
   901  	for i := 0; i < totalNum; i++ {
   902  		priority := int32(i)
   903  		p := st.MakePod().Name(fmt.Sprintf("pod%d", i)).Namespace(fmt.Sprintf("ns%d", i)).UID(fmt.Sprintf("upns%d", i)).Priority(priority).Obj()
   904  		expectedPods = append(expectedPods, *p)
   905  		// priority is to make pods ordered in the PriorityQueue
   906  		q.Add(logger, p)
   907  	}
   908  
   909  	// Pop all pods except for the first one
   910  	for i := totalNum - 1; i > 0; i-- {
   911  		p, _ := q.Pop(logger)
   912  		if diff := cmp.Diff(&expectedPods[i], p.Pod); diff != "" {
   913  			t.Errorf("Unexpected pod (-want, +got):\n%s", diff)
   914  		}
   915  	}
   916  
   917  	// move all pods to active queue when we were trying to schedule them
   918  	q.MoveAllToActiveOrBackoffQueue(logger, WildCardEvent, nil, nil, nil)
   919  	oldCycle := q.SchedulingCycle()
   920  
   921  	firstPod, _ := q.Pop(logger)
   922  	if diff := cmp.Diff(&expectedPods[0], firstPod.Pod); diff != "" {
   923  		t.Errorf("Unexpected pod (-want, +got):\n%s", diff)
   924  	}
   925  
   926  	// mark pods[1] ~ pods[totalNum-1] as unschedulable and add them back
   927  	for i := 1; i < totalNum; i++ {
   928  		unschedulablePod := expectedPods[i].DeepCopy()
   929  		unschedulablePod.Status = v1.PodStatus{
   930  			Conditions: []v1.PodCondition{
   931  				{
   932  					Type:   v1.PodScheduled,
   933  					Status: v1.ConditionFalse,
   934  					Reason: v1.PodReasonUnschedulable,
   935  				},
   936  			},
   937  		}
   938  
   939  		err := q.AddUnschedulableIfNotPresent(logger, newQueuedPodInfoForLookup(unschedulablePod, "plugin"), oldCycle)
   940  		if err != nil {
   941  			t.Fatalf("unexpected error from AddUnschedulableIfNotPresent: %v", err)
   942  		}
   943  	}
   944  
   945  	// Since there was a move request at the same cycle as "oldCycle", these pods
   946  	// should be in the backoff queue.
   947  	for i := 1; i < totalNum; i++ {
   948  		if _, exists, _ := q.podBackoffQ.Get(newQueuedPodInfoForLookup(&expectedPods[i])); !exists {
   949  			t.Errorf("Expected %v to be added to podBackoffQ.", expectedPods[i].Name)
   950  		}
   951  	}
   952  }
   953  
   954  func TestPriorityQueue_Pop(t *testing.T) {
   955  	objs := []runtime.Object{medPriorityPodInfo.Pod}
   956  	logger, ctx := ktesting.NewTestContext(t)
   957  	ctx, cancel := context.WithCancel(ctx)
   958  	defer cancel()
   959  	q := NewTestQueueWithObjects(ctx, newDefaultQueueSort(), objs)
   960  	wg := sync.WaitGroup{}
   961  	wg.Add(1)
   962  	go func() {
   963  		defer wg.Done()
   964  		if p, err := q.Pop(logger); err != nil || p.Pod != medPriorityPodInfo.Pod {
   965  			t.Errorf("Expected: %v after Pop, but got: %v", medPriorityPodInfo.Pod.Name, p.Pod.Name)
   966  		}
   967  		if len(q.nominator.nominatedPods["node1"]) != 1 {
   968  			t.Errorf("Expected medPriorityPodInfo to be present in nomindatePods: %v", q.nominator.nominatedPods["node1"])
   969  		}
   970  	}()
   971  	q.Add(logger, medPriorityPodInfo.Pod)
   972  	wg.Wait()
   973  }
   974  
   975  func TestPriorityQueue_Update(t *testing.T) {
   976  	c := testingclock.NewFakeClock(time.Now())
   977  
   978  	tests := []struct {
   979  		name  string
   980  		wantQ string
   981  		// wantAddedToNominated is whether a Pod from the test case should be registered as a nominated Pod in the nominator.
   982  		wantAddedToNominated bool
   983  		// prepareFunc is the function called to prepare pods in the queue before the test case calls Update().
   984  		// This function returns three values;
   985  		// - oldPod/newPod: each test will call Update() with these oldPod and newPod.
   986  		prepareFunc func(t *testing.T, logger klog.Logger, q *PriorityQueue) (oldPod, newPod *v1.Pod)
   987  	}{
   988  		{
   989  			name:  "add highPriorityPodInfo to activeQ",
   990  			wantQ: activeQ,
   991  			prepareFunc: func(t *testing.T, logger klog.Logger, q *PriorityQueue) (oldPod, newPod *v1.Pod) {
   992  				return nil, highPriorityPodInfo.Pod
   993  			},
   994  		},
   995  		{
   996  			name:  "Update pod that didn't exist in the queue",
   997  			wantQ: activeQ,
   998  			prepareFunc: func(t *testing.T, logger klog.Logger, q *PriorityQueue) (oldPod, newPod *v1.Pod) {
   999  				updatedPod := medPriorityPodInfo.Pod.DeepCopy()
  1000  				updatedPod.Annotations["foo"] = "test"
  1001  				return medPriorityPodInfo.Pod, updatedPod
  1002  			},
  1003  		},
  1004  		{
  1005  			name:                 "Update highPriorityPodInfo and add a nominatedNodeName to it",
  1006  			wantQ:                activeQ,
  1007  			wantAddedToNominated: true,
  1008  			prepareFunc: func(t *testing.T, logger klog.Logger, q *PriorityQueue) (oldPod, newPod *v1.Pod) {
  1009  				return highPriorityPodInfo.Pod, highPriNominatedPodInfo.Pod
  1010  			},
  1011  		},
  1012  		{
  1013  			name:  "When updating a pod that is already in activeQ, the pod should remain in activeQ after Update()",
  1014  			wantQ: activeQ,
  1015  			prepareFunc: func(t *testing.T, logger klog.Logger, q *PriorityQueue) (oldPod, newPod *v1.Pod) {
  1016  				err := q.Update(logger, nil, highPriorityPodInfo.Pod)
  1017  				if err != nil {
  1018  					t.Errorf("add pod %s error: %v", highPriorityPodInfo.Pod.Name, err)
  1019  				}
  1020  				return highPriorityPodInfo.Pod, highPriorityPodInfo.Pod
  1021  			},
  1022  		},
  1023  		{
  1024  			name:  "When updating a pod that is in backoff queue and is still backing off, it will be updated in backoff queue",
  1025  			wantQ: backoffQ,
  1026  			prepareFunc: func(t *testing.T, logger klog.Logger, q *PriorityQueue) (oldPod, newPod *v1.Pod) {
  1027  				podInfo := q.newQueuedPodInfo(medPriorityPodInfo.Pod)
  1028  				if err := q.podBackoffQ.Add(podInfo); err != nil {
  1029  					t.Errorf("adding pod to backoff queue error: %v", err)
  1030  				}
  1031  				return podInfo.Pod, podInfo.Pod
  1032  			},
  1033  		},
  1034  		{
  1035  			name:  "when updating a pod which is in unschedulable queue and is backing off, it will be moved to backoff queue",
  1036  			wantQ: backoffQ,
  1037  			prepareFunc: func(t *testing.T, logger klog.Logger, q *PriorityQueue) (oldPod, newPod *v1.Pod) {
  1038  				q.unschedulablePods.addOrUpdate(q.newQueuedPodInfo(medPriorityPodInfo.Pod, "plugin"))
  1039  				updatedPod := medPriorityPodInfo.Pod.DeepCopy()
  1040  				updatedPod.Annotations["foo"] = "test"
  1041  				return medPriorityPodInfo.Pod, updatedPod
  1042  			},
  1043  		},
  1044  		{
  1045  			name:  "when updating a pod which is in unschedulable queue and is not backing off, it will be moved to active queue",
  1046  			wantQ: activeQ,
  1047  			prepareFunc: func(t *testing.T, logger klog.Logger, q *PriorityQueue) (oldPod, newPod *v1.Pod) {
  1048  				q.unschedulablePods.addOrUpdate(q.newQueuedPodInfo(medPriorityPodInfo.Pod, "plugin"))
  1049  				updatedPod := medPriorityPodInfo.Pod.DeepCopy()
  1050  				updatedPod.Annotations["foo"] = "test1"
  1051  				// Move clock by podInitialBackoffDuration, so that pods in the unschedulablePods would pass the backing off,
  1052  				// and the pods will be moved into activeQ.
  1053  				c.Step(q.podInitialBackoffDuration)
  1054  				return medPriorityPodInfo.Pod, updatedPod
  1055  			},
  1056  		},
  1057  	}
  1058  
  1059  	for _, tt := range tests {
  1060  		t.Run(tt.name, func(t *testing.T) {
  1061  			logger, ctx := ktesting.NewTestContext(t)
  1062  			objs := []runtime.Object{highPriorityPodInfo.Pod, unschedulablePodInfo.Pod, medPriorityPodInfo.Pod}
  1063  			ctx, cancel := context.WithCancel(ctx)
  1064  			defer cancel()
  1065  			q := NewTestQueueWithObjects(ctx, newDefaultQueueSort(), objs, WithClock(c))
  1066  
  1067  			oldPod, newPod := tt.prepareFunc(t, logger, q)
  1068  
  1069  			if err := q.Update(logger, oldPod, newPod); err != nil {
  1070  				t.Fatalf("unexpected error from Update: %v", err)
  1071  			}
  1072  
  1073  			var pInfo *framework.QueuedPodInfo
  1074  
  1075  			// validate expected queue
  1076  			if obj, exists, _ := q.podBackoffQ.Get(newQueuedPodInfoForLookup(newPod)); exists {
  1077  				if tt.wantQ != backoffQ {
  1078  					t.Errorf("expected pod %s not to be queued to backoffQ, but it was", newPod.Name)
  1079  				}
  1080  				pInfo = obj.(*framework.QueuedPodInfo)
  1081  			}
  1082  
  1083  			if obj, exists, _ := q.activeQ.Get(newQueuedPodInfoForLookup(newPod)); exists {
  1084  				if tt.wantQ != activeQ {
  1085  					t.Errorf("expected pod %s not to be queued to activeQ, but it was", newPod.Name)
  1086  				}
  1087  				pInfo = obj.(*framework.QueuedPodInfo)
  1088  			}
  1089  
  1090  			if pInfoFromUnsched := q.unschedulablePods.get(newPod); pInfoFromUnsched != nil {
  1091  				if tt.wantQ != unschedulablePods {
  1092  					t.Errorf("expected pod %s to not be queued to unschedulablePods, but it was", newPod.Name)
  1093  				}
  1094  				pInfo = pInfoFromUnsched
  1095  			}
  1096  
  1097  			if diff := cmp.Diff(newPod, pInfo.PodInfo.Pod); diff != "" {
  1098  				t.Errorf("Unexpected updated pod diff (-want, +got): %s", diff)
  1099  			}
  1100  
  1101  			if tt.wantAddedToNominated && len(q.nominator.nominatedPods) != 1 {
  1102  				t.Errorf("Expected one item in nomindatePods map: %v", q.nominator)
  1103  			}
  1104  
  1105  		})
  1106  	}
  1107  }
  1108  
  1109  func TestPriorityQueue_Delete(t *testing.T) {
  1110  	objs := []runtime.Object{highPriorityPodInfo.Pod, unschedulablePodInfo.Pod}
  1111  	logger, ctx := ktesting.NewTestContext(t)
  1112  	ctx, cancel := context.WithCancel(ctx)
  1113  	defer cancel()
  1114  	q := NewTestQueueWithObjects(ctx, newDefaultQueueSort(), objs)
  1115  	q.Update(logger, highPriorityPodInfo.Pod, highPriNominatedPodInfo.Pod)
  1116  	q.Add(logger, unschedulablePodInfo.Pod)
  1117  	if err := q.Delete(highPriNominatedPodInfo.Pod); err != nil {
  1118  		t.Errorf("delete failed: %v", err)
  1119  	}
  1120  	if _, exists, _ := q.activeQ.Get(newQueuedPodInfoForLookup(unschedulablePodInfo.Pod)); !exists {
  1121  		t.Errorf("Expected %v to be in activeQ.", unschedulablePodInfo.Pod.Name)
  1122  	}
  1123  	if _, exists, _ := q.activeQ.Get(newQueuedPodInfoForLookup(highPriNominatedPodInfo.Pod)); exists {
  1124  		t.Errorf("Didn't expect %v to be in activeQ.", highPriorityPodInfo.Pod.Name)
  1125  	}
  1126  	if len(q.nominator.nominatedPods) != 1 {
  1127  		t.Errorf("Expected nomindatePods to have only 'unschedulablePodInfo': %v", q.nominator.nominatedPods)
  1128  	}
  1129  	if err := q.Delete(unschedulablePodInfo.Pod); err != nil {
  1130  		t.Errorf("delete failed: %v", err)
  1131  	}
  1132  	if len(q.nominator.nominatedPods) != 0 {
  1133  		t.Errorf("Expected nomindatePods to be empty: %v", q.nominator)
  1134  	}
  1135  }
  1136  
  1137  func TestPriorityQueue_Activate(t *testing.T) {
  1138  	tests := []struct {
  1139  		name                        string
  1140  		qPodInfoInUnschedulablePods []*framework.QueuedPodInfo
  1141  		qPodInfoInPodBackoffQ       []*framework.QueuedPodInfo
  1142  		qPodInfoInActiveQ           []*framework.QueuedPodInfo
  1143  		qPodInfoToActivate          *framework.QueuedPodInfo
  1144  		want                        []*framework.QueuedPodInfo
  1145  	}{
  1146  		{
  1147  			name:               "pod already in activeQ",
  1148  			qPodInfoInActiveQ:  []*framework.QueuedPodInfo{{PodInfo: highPriNominatedPodInfo}},
  1149  			qPodInfoToActivate: &framework.QueuedPodInfo{PodInfo: highPriNominatedPodInfo},
  1150  			want:               []*framework.QueuedPodInfo{{PodInfo: highPriNominatedPodInfo}}, // 1 already active
  1151  		},
  1152  		{
  1153  			name:               "pod not in unschedulablePods/podBackoffQ",
  1154  			qPodInfoToActivate: &framework.QueuedPodInfo{PodInfo: highPriNominatedPodInfo},
  1155  			want:               []*framework.QueuedPodInfo{},
  1156  		},
  1157  		{
  1158  			name:                        "pod in unschedulablePods",
  1159  			qPodInfoInUnschedulablePods: []*framework.QueuedPodInfo{{PodInfo: highPriNominatedPodInfo}},
  1160  			qPodInfoToActivate:          &framework.QueuedPodInfo{PodInfo: highPriNominatedPodInfo},
  1161  			want:                        []*framework.QueuedPodInfo{{PodInfo: highPriNominatedPodInfo}},
  1162  		},
  1163  		{
  1164  			name:                  "pod in backoffQ",
  1165  			qPodInfoInPodBackoffQ: []*framework.QueuedPodInfo{{PodInfo: highPriNominatedPodInfo}},
  1166  			qPodInfoToActivate:    &framework.QueuedPodInfo{PodInfo: highPriNominatedPodInfo},
  1167  			want:                  []*framework.QueuedPodInfo{{PodInfo: highPriNominatedPodInfo}},
  1168  		},
  1169  	}
  1170  
  1171  	for _, tt := range tests {
  1172  		t.Run(tt.name, func(t *testing.T) {
  1173  			var objs []runtime.Object
  1174  			logger, ctx := ktesting.NewTestContext(t)
  1175  			ctx, cancel := context.WithCancel(ctx)
  1176  			defer cancel()
  1177  			q := NewTestQueueWithObjects(ctx, newDefaultQueueSort(), objs)
  1178  
  1179  			// Prepare activeQ/unschedulablePods/podBackoffQ according to the table
  1180  			for _, qPodInfo := range tt.qPodInfoInActiveQ {
  1181  				q.activeQ.Add(qPodInfo)
  1182  			}
  1183  
  1184  			for _, qPodInfo := range tt.qPodInfoInUnschedulablePods {
  1185  				q.unschedulablePods.addOrUpdate(qPodInfo)
  1186  			}
  1187  
  1188  			for _, qPodInfo := range tt.qPodInfoInPodBackoffQ {
  1189  				q.podBackoffQ.Add(qPodInfo)
  1190  			}
  1191  
  1192  			// Activate specific pod according to the table
  1193  			q.Activate(logger, map[string]*v1.Pod{"test_pod": tt.qPodInfoToActivate.PodInfo.Pod})
  1194  
  1195  			// Check the result after activation by the length of activeQ
  1196  			if wantLen := len(tt.want); q.activeQ.Len() != wantLen {
  1197  				t.Errorf("length compare: want %v, got %v", wantLen, q.activeQ.Len())
  1198  			}
  1199  
  1200  			// Check if the specific pod exists in activeQ
  1201  			for _, want := range tt.want {
  1202  				if _, exist, _ := q.activeQ.Get(newQueuedPodInfoForLookup(want.PodInfo.Pod)); !exist {
  1203  					t.Errorf("podInfo not exist in activeQ: want %v", want.PodInfo.Pod.Name)
  1204  				}
  1205  			}
  1206  		})
  1207  	}
  1208  }
  1209  
  1210  type preEnqueuePlugin struct {
  1211  	allowlists []string
  1212  }
  1213  
  1214  func (pl *preEnqueuePlugin) Name() string {
  1215  	return "preEnqueuePlugin"
  1216  }
  1217  
  1218  func (pl *preEnqueuePlugin) PreEnqueue(ctx context.Context, p *v1.Pod) *framework.Status {
  1219  	for _, allowed := range pl.allowlists {
  1220  		for label := range p.Labels {
  1221  			if label == allowed {
  1222  				return nil
  1223  			}
  1224  		}
  1225  	}
  1226  	return framework.NewStatus(framework.UnschedulableAndUnresolvable, "pod name not in allowlists")
  1227  }
  1228  
  1229  func TestPriorityQueue_addToActiveQ(t *testing.T) {
  1230  	tests := []struct {
  1231  		name                  string
  1232  		plugins               []framework.PreEnqueuePlugin
  1233  		pod                   *v1.Pod
  1234  		wantUnschedulablePods int
  1235  		wantSuccess           bool
  1236  	}{
  1237  		{
  1238  			name:                  "no plugins registered",
  1239  			pod:                   st.MakePod().Name("p").Label("p", "").Obj(),
  1240  			wantUnschedulablePods: 0,
  1241  			wantSuccess:           true,
  1242  		},
  1243  		{
  1244  			name:                  "preEnqueue plugin registered, pod name not in allowlists",
  1245  			plugins:               []framework.PreEnqueuePlugin{&preEnqueuePlugin{}, &preEnqueuePlugin{}},
  1246  			pod:                   st.MakePod().Name("p").Label("p", "").Obj(),
  1247  			wantUnschedulablePods: 1,
  1248  			wantSuccess:           false,
  1249  		},
  1250  		{
  1251  			name: "preEnqueue plugin registered, pod failed one preEnqueue plugin",
  1252  			plugins: []framework.PreEnqueuePlugin{
  1253  				&preEnqueuePlugin{allowlists: []string{"foo", "bar"}},
  1254  				&preEnqueuePlugin{allowlists: []string{"foo"}},
  1255  			},
  1256  			pod:                   st.MakePod().Name("bar").Label("bar", "").Obj(),
  1257  			wantUnschedulablePods: 1,
  1258  			wantSuccess:           false,
  1259  		},
  1260  		{
  1261  			name: "preEnqueue plugin registered, pod passed all preEnqueue plugins",
  1262  			plugins: []framework.PreEnqueuePlugin{
  1263  				&preEnqueuePlugin{allowlists: []string{"foo", "bar"}},
  1264  				&preEnqueuePlugin{allowlists: []string{"bar"}},
  1265  			},
  1266  			pod:                   st.MakePod().Name("bar").Label("bar", "").Obj(),
  1267  			wantUnschedulablePods: 0,
  1268  			wantSuccess:           true,
  1269  		},
  1270  	}
  1271  
  1272  	for _, tt := range tests {
  1273  		t.Run(tt.name, func(t *testing.T) {
  1274  			ctx, cancel := context.WithCancel(context.Background())
  1275  			logger := klog.FromContext(ctx)
  1276  			defer cancel()
  1277  
  1278  			m := map[string][]framework.PreEnqueuePlugin{"": tt.plugins}
  1279  			q := NewTestQueueWithObjects(ctx, newDefaultQueueSort(), []runtime.Object{tt.pod}, WithPreEnqueuePluginMap(m),
  1280  				WithPodInitialBackoffDuration(time.Second*30), WithPodMaxBackoffDuration(time.Second*60))
  1281  			got, _ := q.addToActiveQ(logger, q.newQueuedPodInfo(tt.pod))
  1282  			if got != tt.wantSuccess {
  1283  				t.Errorf("Unexpected result: want %v, but got %v", tt.wantSuccess, got)
  1284  			}
  1285  			if tt.wantUnschedulablePods != len(q.unschedulablePods.podInfoMap) {
  1286  				t.Errorf("Unexpected unschedulablePods: want %v, but got %v", tt.wantUnschedulablePods, len(q.unschedulablePods.podInfoMap))
  1287  			}
  1288  
  1289  			// Simulate an update event.
  1290  			clone := tt.pod.DeepCopy()
  1291  			metav1.SetMetaDataAnnotation(&clone.ObjectMeta, "foo", "")
  1292  			q.Update(logger, tt.pod, clone)
  1293  			// Ensure the pod is still located in unschedulablePods.
  1294  			if tt.wantUnschedulablePods != len(q.unschedulablePods.podInfoMap) {
  1295  				t.Errorf("Unexpected unschedulablePods: want %v, but got %v", tt.wantUnschedulablePods, len(q.unschedulablePods.podInfoMap))
  1296  			}
  1297  		})
  1298  	}
  1299  }
  1300  
  1301  func BenchmarkMoveAllToActiveOrBackoffQueue(b *testing.B) {
  1302  	tests := []struct {
  1303  		name      string
  1304  		moveEvent framework.ClusterEvent
  1305  	}{
  1306  		{
  1307  			name:      "baseline",
  1308  			moveEvent: UnschedulableTimeout,
  1309  		},
  1310  		{
  1311  			name:      "worst",
  1312  			moveEvent: NodeAdd,
  1313  		},
  1314  		{
  1315  			name: "random",
  1316  			// leave "moveEvent" unspecified
  1317  		},
  1318  	}
  1319  
  1320  	podTemplates := []*v1.Pod{
  1321  		highPriorityPodInfo.Pod, highPriNominatedPodInfo.Pod,
  1322  		medPriorityPodInfo.Pod, unschedulablePodInfo.Pod,
  1323  	}
  1324  
  1325  	events := []framework.ClusterEvent{
  1326  		NodeAdd,
  1327  		NodeTaintChange,
  1328  		NodeAllocatableChange,
  1329  		NodeConditionChange,
  1330  		NodeLabelChange,
  1331  		NodeAnnotationChange,
  1332  		PvcAdd,
  1333  		PvcUpdate,
  1334  		PvAdd,
  1335  		PvUpdate,
  1336  		StorageClassAdd,
  1337  		StorageClassUpdate,
  1338  		CSINodeAdd,
  1339  		CSINodeUpdate,
  1340  		CSIDriverAdd,
  1341  		CSIDriverUpdate,
  1342  		CSIStorageCapacityAdd,
  1343  		CSIStorageCapacityUpdate,
  1344  	}
  1345  
  1346  	pluginNum := 20
  1347  	var plugins []string
  1348  	// Mimic that we have 20 plugins loaded in runtime.
  1349  	for i := 0; i < pluginNum; i++ {
  1350  		plugins = append(plugins, fmt.Sprintf("fake-plugin-%v", i))
  1351  	}
  1352  
  1353  	for _, tt := range tests {
  1354  		for _, podsInUnschedulablePods := range []int{1000, 5000} {
  1355  			b.Run(fmt.Sprintf("%v-%v", tt.name, podsInUnschedulablePods), func(b *testing.B) {
  1356  				logger, _ := ktesting.NewTestContext(b)
  1357  				for i := 0; i < b.N; i++ {
  1358  					b.StopTimer()
  1359  					c := testingclock.NewFakeClock(time.Now())
  1360  
  1361  					m := makeEmptyQueueingHintMapPerProfile()
  1362  					// - All plugins registered for events[0], which is NodeAdd.
  1363  					// - 1/2 of plugins registered for events[1]
  1364  					// - 1/3 of plugins registered for events[2]
  1365  					// - ...
  1366  					for j := 0; j < len(events); j++ {
  1367  						for k := 0; k < len(plugins); k++ {
  1368  							if (k+1)%(j+1) == 0 {
  1369  								m[""][events[j]] = append(m[""][events[j]], &QueueingHintFunction{
  1370  									PluginName:     plugins[k],
  1371  									QueueingHintFn: queueHintReturnQueue,
  1372  								})
  1373  							}
  1374  						}
  1375  					}
  1376  
  1377  					ctx, cancel := context.WithCancel(context.Background())
  1378  					defer cancel()
  1379  					q := NewTestQueue(ctx, newDefaultQueueSort(), WithClock(c), WithQueueingHintMapPerProfile(m))
  1380  
  1381  					// Init pods in unschedulablePods.
  1382  					for j := 0; j < podsInUnschedulablePods; j++ {
  1383  						p := podTemplates[j%len(podTemplates)].DeepCopy()
  1384  						p.Name, p.UID = fmt.Sprintf("%v-%v", p.Name, j), types.UID(fmt.Sprintf("%v-%v", p.UID, j))
  1385  						var podInfo *framework.QueuedPodInfo
  1386  						// The ultimate goal of composing each PodInfo is to cover the path that intersects
  1387  						// (unschedulable) plugin names with the plugins that register the moveEvent,
  1388  						// here the rational is:
  1389  						// - in baseline case, don't inject unschedulable plugin names, so podMatchesEvent()
  1390  						//   never gets executed.
  1391  						// - in worst case, make both ends (of the intersection) a big number,i.e.,
  1392  						//   M intersected with N instead of M with 1 (or 1 with N)
  1393  						// - in random case, each pod failed by a random plugin, and also the moveEvent
  1394  						//   is randomized.
  1395  						if tt.name == "baseline" {
  1396  							podInfo = q.newQueuedPodInfo(p)
  1397  						} else if tt.name == "worst" {
  1398  							// Each pod failed by all plugins.
  1399  							podInfo = q.newQueuedPodInfo(p, plugins...)
  1400  						} else {
  1401  							// Random case.
  1402  							podInfo = q.newQueuedPodInfo(p, plugins[j%len(plugins)])
  1403  						}
  1404  						err := q.AddUnschedulableIfNotPresent(logger, podInfo, q.SchedulingCycle())
  1405  						if err != nil {
  1406  							b.Fatalf("unexpected error from AddUnschedulableIfNotPresent: %v", err)
  1407  						}
  1408  					}
  1409  
  1410  					b.StartTimer()
  1411  					if tt.moveEvent.Resource != "" {
  1412  						q.MoveAllToActiveOrBackoffQueue(logger, tt.moveEvent, nil, nil, nil)
  1413  					} else {
  1414  						// Random case.
  1415  						q.MoveAllToActiveOrBackoffQueue(logger, events[i%len(events)], nil, nil, nil)
  1416  					}
  1417  				}
  1418  			})
  1419  		}
  1420  	}
  1421  }
  1422  
  1423  func TestPriorityQueue_MoveAllToActiveOrBackoffQueueWithQueueingHint(t *testing.T) {
  1424  	now := time.Now()
  1425  	p := st.MakePod().Name("pod1").Namespace("ns1").UID("1").Obj()
  1426  	tests := []struct {
  1427  		name    string
  1428  		podInfo *framework.QueuedPodInfo
  1429  		hint    framework.QueueingHintFn
  1430  		// duration is the duration that the Pod has been in the unschedulable queue.
  1431  		duration time.Duration
  1432  		// expectedQ is the queue name (activeQ, backoffQ, or unschedulablePods) that this Pod should be quened to.
  1433  		expectedQ string
  1434  	}{
  1435  		{
  1436  			name:      "Queue queues pod to activeQ",
  1437  			podInfo:   &framework.QueuedPodInfo{PodInfo: mustNewPodInfo(p), PendingPlugins: sets.New("foo")},
  1438  			hint:      queueHintReturnQueue,
  1439  			expectedQ: activeQ,
  1440  		},
  1441  		{
  1442  			name:      "Queue queues pod to backoffQ if Pod is backing off",
  1443  			podInfo:   &framework.QueuedPodInfo{PodInfo: mustNewPodInfo(p), UnschedulablePlugins: sets.New("foo")},
  1444  			hint:      queueHintReturnQueue,
  1445  			expectedQ: backoffQ,
  1446  		},
  1447  		{
  1448  			name:      "Queue queues pod to activeQ if Pod is not backing off",
  1449  			podInfo:   &framework.QueuedPodInfo{PodInfo: mustNewPodInfo(p), UnschedulablePlugins: sets.New("foo")},
  1450  			hint:      queueHintReturnQueue,
  1451  			duration:  DefaultPodInitialBackoffDuration, // backoff is finished
  1452  			expectedQ: activeQ,
  1453  		},
  1454  		{
  1455  			name:      "Skip queues pod to unschedulablePods",
  1456  			podInfo:   &framework.QueuedPodInfo{PodInfo: mustNewPodInfo(p), UnschedulablePlugins: sets.New("foo")},
  1457  			hint:      queueHintReturnSkip,
  1458  			expectedQ: unschedulablePods,
  1459  		},
  1460  		{
  1461  			name:    "QueueHintFunction is not called when Pod is gated",
  1462  			podInfo: setQueuedPodInfoGated(&framework.QueuedPodInfo{PodInfo: mustNewPodInfo(p), UnschedulablePlugins: sets.New("foo")}),
  1463  			hint: func(logger klog.Logger, pod *v1.Pod, oldObj, newObj interface{}) (framework.QueueingHint, error) {
  1464  				return framework.Queue, fmt.Errorf("QueueingHintFn should not be called as pod is gated")
  1465  			},
  1466  			expectedQ: unschedulablePods,
  1467  		},
  1468  	}
  1469  
  1470  	for _, test := range tests {
  1471  		t.Run(test.name, func(t *testing.T) {
  1472  			logger, ctx := ktesting.NewTestContext(t)
  1473  			m := makeEmptyQueueingHintMapPerProfile()
  1474  			m[""][NodeAdd] = []*QueueingHintFunction{
  1475  				{
  1476  					PluginName:     "foo",
  1477  					QueueingHintFn: test.hint,
  1478  				},
  1479  			}
  1480  			test.podInfo.UnschedulablePlugins = sets.New("foo")
  1481  			cl := testingclock.NewFakeClock(now)
  1482  			q := NewTestQueue(ctx, newDefaultQueueSort(), WithQueueingHintMapPerProfile(m), WithClock(cl))
  1483  			// add to unsched pod pool
  1484  			q.activeQ.Add(q.newQueuedPodInfo(test.podInfo.Pod))
  1485  			if p, err := q.Pop(logger); err != nil || p.Pod != test.podInfo.Pod {
  1486  				t.Errorf("Expected: %v after Pop, but got: %v", test.podInfo.Pod.Name, p.Pod.Name)
  1487  			}
  1488  			err := q.AddUnschedulableIfNotPresent(logger, test.podInfo, q.SchedulingCycle())
  1489  			if err != nil {
  1490  				t.Fatalf("unexpected error from AddUnschedulableIfNotPresent: %v", err)
  1491  			}
  1492  			cl.Step(test.duration)
  1493  
  1494  			q.MoveAllToActiveOrBackoffQueue(logger, NodeAdd, nil, nil, nil)
  1495  
  1496  			if q.podBackoffQ.Len() == 0 && test.expectedQ == backoffQ {
  1497  				t.Fatalf("expected pod to be queued to backoffQ, but it was not")
  1498  			}
  1499  
  1500  			if q.activeQ.Len() == 0 && test.expectedQ == activeQ {
  1501  				t.Fatalf("expected pod to be queued to activeQ, but it was not")
  1502  			}
  1503  
  1504  			if q.unschedulablePods.get(test.podInfo.Pod) == nil && test.expectedQ == unschedulablePods {
  1505  				t.Fatalf("expected pod to be queued to unschedulablePods, but it was not")
  1506  			}
  1507  		})
  1508  	}
  1509  }
  1510  
  1511  func TestPriorityQueue_MoveAllToActiveOrBackoffQueue(t *testing.T) {
  1512  	c := testingclock.NewFakeClock(time.Now())
  1513  	logger, ctx := ktesting.NewTestContext(t)
  1514  	ctx, cancel := context.WithCancel(ctx)
  1515  	defer cancel()
  1516  	m := makeEmptyQueueingHintMapPerProfile()
  1517  	featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.SchedulerQueueingHints, true)
  1518  
  1519  	m[""][NodeAdd] = []*QueueingHintFunction{
  1520  		{
  1521  			PluginName:     "fooPlugin",
  1522  			QueueingHintFn: queueHintReturnQueue,
  1523  		},
  1524  	}
  1525  	q := NewTestQueue(ctx, newDefaultQueueSort(), WithClock(c), WithQueueingHintMapPerProfile(m))
  1526  	// To simulate the pod is failed in scheduling in the real world, Pop() the pod from activeQ before AddUnschedulableIfNotPresent()s below.
  1527  	q.activeQ.Add(q.newQueuedPodInfo(unschedulablePodInfo.Pod))
  1528  	if p, err := q.Pop(logger); err != nil || p.Pod != unschedulablePodInfo.Pod {
  1529  		t.Errorf("Expected: %v after Pop, but got: %v", unschedulablePodInfo.Pod.Name, p.Pod.Name)
  1530  	}
  1531  	expectInFlightPods(t, q, unschedulablePodInfo.Pod.UID)
  1532  	q.activeQ.Add(q.newQueuedPodInfo(highPriorityPodInfo.Pod))
  1533  	if p, err := q.Pop(logger); err != nil || p.Pod != highPriorityPodInfo.Pod {
  1534  		t.Errorf("Expected: %v after Pop, but got: %v", highPriorityPodInfo.Pod.Name, p.Pod.Name)
  1535  	}
  1536  	expectInFlightPods(t, q, unschedulablePodInfo.Pod.UID, highPriorityPodInfo.Pod.UID)
  1537  	err := q.AddUnschedulableIfNotPresent(logger, q.newQueuedPodInfo(unschedulablePodInfo.Pod, "fooPlugin"), q.SchedulingCycle())
  1538  	if err != nil {
  1539  		t.Fatalf("unexpected error from AddUnschedulableIfNotPresent: %v", err)
  1540  	}
  1541  	err = q.AddUnschedulableIfNotPresent(logger, q.newQueuedPodInfo(highPriorityPodInfo.Pod, "fooPlugin"), q.SchedulingCycle())
  1542  	if err != nil {
  1543  		t.Fatalf("unexpected error from AddUnschedulableIfNotPresent: %v", err)
  1544  	}
  1545  	expectInFlightPods(t, q)
  1546  	// Construct a Pod, but don't associate its scheduler failure to any plugin
  1547  	hpp1 := clonePod(highPriorityPodInfo.Pod, "hpp1")
  1548  	q.activeQ.Add(q.newQueuedPodInfo(hpp1))
  1549  	if p, err := q.Pop(logger); err != nil || p.Pod != hpp1 {
  1550  		t.Errorf("Expected: %v after Pop, but got: %v", hpp1, p.Pod.Name)
  1551  	}
  1552  	expectInFlightPods(t, q, hpp1.UID)
  1553  	// This Pod will go to backoffQ because no failure plugin is associated with it.
  1554  	err = q.AddUnschedulableIfNotPresent(logger, q.newQueuedPodInfo(hpp1), q.SchedulingCycle())
  1555  	if err != nil {
  1556  		t.Fatalf("unexpected error from AddUnschedulableIfNotPresent: %v", err)
  1557  	}
  1558  	expectInFlightPods(t, q)
  1559  	// Construct another Pod, and associate its scheduler failure to plugin "barPlugin".
  1560  	hpp2 := clonePod(highPriorityPodInfo.Pod, "hpp2")
  1561  	q.activeQ.Add(q.newQueuedPodInfo(hpp2))
  1562  	if p, err := q.Pop(logger); err != nil || p.Pod != hpp2 {
  1563  		t.Errorf("Expected: %v after Pop, but got: %v", hpp2, p.Pod.Name)
  1564  	}
  1565  	expectInFlightPods(t, q, hpp2.UID)
  1566  	// This Pod will go to the unschedulable Pod pool.
  1567  	err = q.AddUnschedulableIfNotPresent(logger, q.newQueuedPodInfo(hpp2, "barPlugin"), q.SchedulingCycle())
  1568  	if err != nil {
  1569  		t.Fatalf("unexpected error from AddUnschedulableIfNotPresent: %v", err)
  1570  	}
  1571  	expectInFlightPods(t, q)
  1572  	// This NodeAdd event moves unschedulablePodInfo and highPriorityPodInfo to the backoffQ,
  1573  	// because of the queueing hint function registered for NodeAdd/fooPlugin.
  1574  	q.MoveAllToActiveOrBackoffQueue(logger, NodeAdd, nil, nil, nil)
  1575  	q.Add(logger, medPriorityPodInfo.Pod)
  1576  	if q.activeQ.Len() != 1 {
  1577  		t.Errorf("Expected 1 item to be in activeQ, but got: %v", q.activeQ.Len())
  1578  	}
  1579  	// Pop out the medPriorityPodInfo in activeQ.
  1580  	if p, err := q.Pop(logger); err != nil || p.Pod != medPriorityPodInfo.Pod {
  1581  		t.Errorf("Expected: %v after Pop, but got: %v", medPriorityPodInfo.Pod, p.Pod.Name)
  1582  	}
  1583  	expectInFlightPods(t, q, medPriorityPodInfo.Pod.UID)
  1584  	// hpp2 won't be moved.
  1585  	if q.podBackoffQ.Len() != 3 {
  1586  		t.Fatalf("Expected 3 items to be in podBackoffQ, but got: %v", q.podBackoffQ.Len())
  1587  	}
  1588  
  1589  	// pop out the pods in the backoffQ.
  1590  	// This doesn't make them in-flight pods.
  1591  	for q.podBackoffQ.Len() != 0 {
  1592  		q.podBackoffQ.Pop()
  1593  	}
  1594  	expectInFlightPods(t, q, medPriorityPodInfo.Pod.UID)
  1595  
  1596  	q.schedulingCycle++
  1597  	q.activeQ.Add(q.newQueuedPodInfo(unschedulablePodInfo.Pod))
  1598  	if p, err := q.Pop(logger); err != nil || p.Pod != unschedulablePodInfo.Pod {
  1599  		t.Errorf("Expected: %v after Pop, but got: %v", unschedulablePodInfo.Pod.Name, p.Pod.Name)
  1600  	}
  1601  	expectInFlightPods(t, q, medPriorityPodInfo.Pod.UID, unschedulablePodInfo.Pod.UID)
  1602  	q.activeQ.Add(q.newQueuedPodInfo(highPriorityPodInfo.Pod))
  1603  	if p, err := q.Pop(logger); err != nil || p.Pod != highPriorityPodInfo.Pod {
  1604  		t.Errorf("Expected: %v after Pop, but got: %v", highPriorityPodInfo.Pod.Name, p.Pod.Name)
  1605  	}
  1606  	expectInFlightPods(t, q, medPriorityPodInfo.Pod.UID, unschedulablePodInfo.Pod.UID, highPriorityPodInfo.Pod.UID)
  1607  	q.activeQ.Add(q.newQueuedPodInfo(hpp1))
  1608  	if p, err := q.Pop(logger); err != nil || p.Pod != hpp1 {
  1609  		t.Errorf("Expected: %v after Pop, but got: %v", hpp1, p.Pod.Name)
  1610  	}
  1611  	unschedulableQueuedPodInfo := q.newQueuedPodInfo(unschedulablePodInfo.Pod, "fooPlugin")
  1612  	highPriorityQueuedPodInfo := q.newQueuedPodInfo(highPriorityPodInfo.Pod, "fooPlugin")
  1613  	hpp1QueuedPodInfo := q.newQueuedPodInfo(hpp1)
  1614  	expectInFlightPods(t, q, medPriorityPodInfo.Pod.UID, unschedulablePodInfo.Pod.UID, highPriorityPodInfo.Pod.UID, hpp1.UID)
  1615  	err = q.AddUnschedulableIfNotPresent(logger, unschedulableQueuedPodInfo, q.SchedulingCycle())
  1616  	if err != nil {
  1617  		t.Fatalf("unexpected error from AddUnschedulableIfNotPresent: %v", err)
  1618  	}
  1619  	expectInFlightPods(t, q, medPriorityPodInfo.Pod.UID, highPriorityPodInfo.Pod.UID, hpp1.UID)
  1620  	err = q.AddUnschedulableIfNotPresent(logger, highPriorityQueuedPodInfo, q.SchedulingCycle())
  1621  	if err != nil {
  1622  		t.Fatalf("unexpected error from AddUnschedulableIfNotPresent: %v", err)
  1623  	}
  1624  	expectInFlightPods(t, q, medPriorityPodInfo.Pod.UID, hpp1.UID)
  1625  	err = q.AddUnschedulableIfNotPresent(logger, hpp1QueuedPodInfo, q.SchedulingCycle())
  1626  	if err != nil {
  1627  		t.Fatalf("unexpected error from AddUnschedulableIfNotPresent: %v", err)
  1628  	}
  1629  	expectInFlightPods(t, q, medPriorityPodInfo.Pod.UID)
  1630  	q.Add(logger, medPriorityPodInfo.Pod)
  1631  	// hpp1 will go to backoffQ because no failure plugin is associated with it.
  1632  	// All plugins other than hpp1 are enqueued to the unschedulable Pod pool.
  1633  	for _, pod := range []*v1.Pod{unschedulablePodInfo.Pod, highPriorityPodInfo.Pod, hpp2} {
  1634  		if q.unschedulablePods.get(pod) == nil {
  1635  			t.Errorf("Expected %v in the unschedulablePods", pod.Name)
  1636  		}
  1637  	}
  1638  	if _, ok, _ := q.podBackoffQ.Get(hpp1QueuedPodInfo); !ok {
  1639  		t.Errorf("Expected %v in the podBackoffQ", hpp1.Name)
  1640  	}
  1641  
  1642  	// Move clock by podInitialBackoffDuration, so that pods in the unschedulablePods would pass the backing off,
  1643  	// and the pods will be moved into activeQ.
  1644  	c.Step(q.podInitialBackoffDuration)
  1645  	q.flushBackoffQCompleted(logger) // flush the completed backoffQ to move hpp1 to activeQ.
  1646  	q.MoveAllToActiveOrBackoffQueue(logger, NodeAdd, nil, nil, nil)
  1647  	if q.activeQ.Len() != 4 {
  1648  		t.Errorf("Expected 4 items to be in activeQ, but got: %v", q.activeQ.Len())
  1649  	}
  1650  	if q.podBackoffQ.Len() != 0 {
  1651  		t.Errorf("Expected 0 item to be in podBackoffQ, but got: %v", q.podBackoffQ.Len())
  1652  	}
  1653  	expectInFlightPods(t, q, medPriorityPodInfo.Pod.UID)
  1654  	if len(q.unschedulablePods.podInfoMap) != 1 {
  1655  		// hpp2 won't be moved regardless of its backoff timer.
  1656  		t.Errorf("Expected 1 item to be in unschedulablePods, but got: %v", len(q.unschedulablePods.podInfoMap))
  1657  	}
  1658  }
  1659  
  1660  func TestPriorityQueue_MoveAllToActiveOrBackoffQueueWithOutQueueingHint(t *testing.T) {
  1661  	c := testingclock.NewFakeClock(time.Now())
  1662  	logger, ctx := ktesting.NewTestContext(t)
  1663  	ctx, cancel := context.WithCancel(ctx)
  1664  	defer cancel()
  1665  	m := makeEmptyQueueingHintMapPerProfile()
  1666  	featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.SchedulerQueueingHints, false)
  1667  	m[""][NodeAdd] = []*QueueingHintFunction{
  1668  		{
  1669  			PluginName:     "fooPlugin",
  1670  			QueueingHintFn: queueHintReturnQueue,
  1671  		},
  1672  	}
  1673  	q := NewTestQueue(ctx, newDefaultQueueSort(), WithClock(c), WithQueueingHintMapPerProfile(m))
  1674  	// To simulate the pod is failed in scheduling in the real world, Pop() the pod from activeQ before AddUnschedulableIfNotPresent()s below.
  1675  	if err := q.Add(logger, medPriorityPodInfo.Pod); err != nil {
  1676  		t.Errorf("add failed: %v", err)
  1677  	}
  1678  
  1679  	err := q.AddUnschedulableIfNotPresent(logger, q.newQueuedPodInfo(unschedulablePodInfo.Pod, "fooPlugin"), q.SchedulingCycle())
  1680  	if err != nil {
  1681  		t.Fatalf("unexpected error from AddUnschedulableIfNotPresent: %v", err)
  1682  	}
  1683  	err = q.AddUnschedulableIfNotPresent(logger, q.newQueuedPodInfo(highPriorityPodInfo.Pod, "fooPlugin"), q.SchedulingCycle())
  1684  	if err != nil {
  1685  		t.Fatalf("unexpected error from AddUnschedulableIfNotPresent: %v", err)
  1686  	}
  1687  	// Construct a Pod, but don't associate its scheduler failure to any plugin
  1688  	hpp1 := clonePod(highPriorityPodInfo.Pod, "hpp1")
  1689  	// This Pod will go to backoffQ because no failure plugin is associated with it.
  1690  	err = q.AddUnschedulableIfNotPresent(logger, q.newQueuedPodInfo(hpp1), q.SchedulingCycle())
  1691  	if err != nil {
  1692  		t.Fatalf("unexpected error from AddUnschedulableIfNotPresent: %v", err)
  1693  	}
  1694  	// Construct another Pod, and associate its scheduler failure to plugin "barPlugin".
  1695  	hpp2 := clonePod(highPriorityPodInfo.Pod, "hpp2")
  1696  	// This Pod will go to the unschedulable Pod pool.
  1697  	err = q.AddUnschedulableIfNotPresent(logger, q.newQueuedPodInfo(hpp2, "barPlugin"), q.SchedulingCycle())
  1698  	if err != nil {
  1699  		t.Fatalf("unexpected error from AddUnschedulableIfNotPresent: %v", err)
  1700  	}
  1701  	// This NodeAdd event moves unschedulablePodInfo and highPriorityPodInfo to the backoffQ,
  1702  	// because of the queueing hint function registered for NodeAdd/fooPlugin.
  1703  	q.MoveAllToActiveOrBackoffQueue(logger, NodeAdd, nil, nil, nil)
  1704  	if q.activeQ.Len() != 1 {
  1705  		t.Errorf("Expected 1 item to be in activeQ, but got: %v", q.activeQ.Len())
  1706  	}
  1707  	// Pop out the medPriorityPodInfo in activeQ.
  1708  	if p, err := q.Pop(logger); err != nil || p.Pod != medPriorityPodInfo.Pod {
  1709  		t.Errorf("Expected: %v after Pop, but got: %v", medPriorityPodInfo.Pod, p.Pod.Name)
  1710  	}
  1711  	// hpp2 won't be moved.
  1712  	if q.podBackoffQ.Len() != 3 {
  1713  		t.Fatalf("Expected 3 items to be in podBackoffQ, but got: %v", q.podBackoffQ.Len())
  1714  	}
  1715  
  1716  	// pop out the pods in the backoffQ.
  1717  	// This doesn't make them in-flight pods.
  1718  	for q.podBackoffQ.Len() != 0 {
  1719  		_, err = q.podBackoffQ.Pop()
  1720  		if err != nil {
  1721  			t.Errorf("pop failed: %v", err)
  1722  		}
  1723  	}
  1724  
  1725  	q.schedulingCycle++
  1726  	unschedulableQueuedPodInfo := q.newQueuedPodInfo(unschedulablePodInfo.Pod, "fooPlugin")
  1727  	highPriorityQueuedPodInfo := q.newQueuedPodInfo(highPriorityPodInfo.Pod, "fooPlugin")
  1728  	hpp1QueuedPodInfo := q.newQueuedPodInfo(hpp1)
  1729  	err = q.AddUnschedulableIfNotPresent(logger, unschedulableQueuedPodInfo, q.SchedulingCycle())
  1730  	if err != nil {
  1731  		t.Fatalf("unexpected error from AddUnschedulableIfNotPresent: %v", err)
  1732  	}
  1733  	err = q.AddUnschedulableIfNotPresent(logger, highPriorityQueuedPodInfo, q.SchedulingCycle())
  1734  	if err != nil {
  1735  		t.Fatalf("unexpected error from AddUnschedulableIfNotPresent: %v", err)
  1736  	}
  1737  	err = q.AddUnschedulableIfNotPresent(logger, hpp1QueuedPodInfo, q.SchedulingCycle())
  1738  	if err != nil {
  1739  		t.Fatalf("unexpected error from AddUnschedulableIfNotPresent: %v", err)
  1740  	}
  1741  	if err = q.Add(logger, medPriorityPodInfo.Pod); err != nil {
  1742  		t.Errorf("add failed: %v", err)
  1743  	}
  1744  	// hpp1 will go to backoffQ because no failure plugin is associated with it.
  1745  	// All plugins other than hpp1 are enqueued to the unschedulable Pod pool.
  1746  	for _, pod := range []*v1.Pod{unschedulablePodInfo.Pod, highPriorityPodInfo.Pod, hpp2} {
  1747  		if q.unschedulablePods.get(pod) == nil {
  1748  			t.Errorf("Expected %v in the unschedulablePods", pod.Name)
  1749  		}
  1750  	}
  1751  	if _, ok, _ := q.podBackoffQ.Get(hpp1QueuedPodInfo); !ok {
  1752  		t.Errorf("Expected %v in the podBackoffQ", hpp1.Name)
  1753  	}
  1754  
  1755  	// Move clock by podInitialBackoffDuration, so that pods in the unschedulablePods would pass the backing off,
  1756  	// and the pods will be moved into activeQ.
  1757  	c.Step(q.podInitialBackoffDuration)
  1758  	q.flushBackoffQCompleted(logger) // flush the completed backoffQ to move hpp1 to activeQ.
  1759  	q.MoveAllToActiveOrBackoffQueue(logger, NodeAdd, nil, nil, nil)
  1760  	if q.activeQ.Len() != 4 {
  1761  		t.Errorf("Expected 4 items to be in activeQ, but got: %v", q.activeQ.Len())
  1762  	}
  1763  	if q.podBackoffQ.Len() != 0 {
  1764  		t.Errorf("Expected 0 item to be in podBackoffQ, but got: %v", q.podBackoffQ.Len())
  1765  	}
  1766  	if len(q.unschedulablePods.podInfoMap) != 1 {
  1767  		// hpp2 won't be moved regardless of its backoff timer.
  1768  		t.Errorf("Expected 1 item to be in unschedulablePods, but got: %v", len(q.unschedulablePods.podInfoMap))
  1769  	}
  1770  }
  1771  
  1772  func clonePod(pod *v1.Pod, newName string) *v1.Pod {
  1773  	pod = pod.DeepCopy()
  1774  	pod.Name = newName
  1775  	pod.UID = types.UID(pod.Name + pod.Namespace)
  1776  	return pod
  1777  }
  1778  
  1779  func expectInFlightPods(t *testing.T, q *PriorityQueue, uids ...types.UID) {
  1780  	t.Helper()
  1781  	var actualUIDs []types.UID
  1782  	for uid := range q.inFlightPods {
  1783  		actualUIDs = append(actualUIDs, uid)
  1784  	}
  1785  	sortUIDs := cmpopts.SortSlices(func(a, b types.UID) bool { return a < b })
  1786  	if diff := cmp.Diff(uids, actualUIDs, sortUIDs); diff != "" {
  1787  		t.Fatalf("Unexpected content of inFlightPods (-want, +have):\n%s", diff)
  1788  	}
  1789  	actualUIDs = nil
  1790  	for e := q.inFlightEvents.Front(); e != nil; e = e.Next() {
  1791  		if pod, ok := e.Value.(*v1.Pod); ok {
  1792  			actualUIDs = append(actualUIDs, pod.UID)
  1793  		}
  1794  	}
  1795  	if diff := cmp.Diff(uids, actualUIDs, sortUIDs); diff != "" {
  1796  		t.Fatalf("Unexpected pods in inFlightEvents (-want, +have):\n%s", diff)
  1797  	}
  1798  }
  1799  
  1800  // TestPriorityQueue_AssignedPodAdded tests AssignedPodAdded. It checks that
  1801  // when a pod with pod affinity is in unschedulablePods and another pod with a
  1802  // matching label is added, the unschedulable pod is moved to activeQ.
  1803  func TestPriorityQueue_AssignedPodAdded(t *testing.T) {
  1804  	logger, ctx := ktesting.NewTestContext(t)
  1805  	ctx, cancel := context.WithCancel(ctx)
  1806  	defer cancel()
  1807  
  1808  	affinityPod := st.MakePod().Name("afp").Namespace("ns1").UID("afp").Annotation("annot2", "val2").Priority(mediumPriority).NominatedNodeName("node1").PodAffinityExists("service", "region", st.PodAffinityWithRequiredReq).Obj()
  1809  	labelPod := st.MakePod().Name("lbp").Namespace(affinityPod.Namespace).Label("service", "securityscan").Node("node1").Obj()
  1810  
  1811  	c := testingclock.NewFakeClock(time.Now())
  1812  	m := makeEmptyQueueingHintMapPerProfile()
  1813  	m[""][AssignedPodAdd] = []*QueueingHintFunction{
  1814  		{
  1815  			PluginName:     "fakePlugin",
  1816  			QueueingHintFn: queueHintReturnQueue,
  1817  		},
  1818  	}
  1819  	q := NewTestQueue(ctx, newDefaultQueueSort(), WithClock(c), WithQueueingHintMapPerProfile(m))
  1820  	// To simulate the pod is failed in scheduling in the real world, Pop() the pod from activeQ before AddUnschedulableIfNotPresent()s below.
  1821  	q.activeQ.Add(q.newQueuedPodInfo(unschedulablePodInfo.Pod))
  1822  	if p, err := q.Pop(logger); err != nil || p.Pod != unschedulablePodInfo.Pod {
  1823  		t.Errorf("Expected: %v after Pop, but got: %v", unschedulablePodInfo.Pod.Name, p.Pod.Name)
  1824  	}
  1825  	q.activeQ.Add(q.newQueuedPodInfo(affinityPod))
  1826  	if p, err := q.Pop(logger); err != nil || p.Pod != affinityPod {
  1827  		t.Errorf("Expected: %v after Pop, but got: %v", affinityPod.Name, p.Pod.Name)
  1828  	}
  1829  	q.Add(logger, medPriorityPodInfo.Pod)
  1830  	err := q.AddUnschedulableIfNotPresent(logger, q.newQueuedPodInfo(unschedulablePodInfo.Pod, "fakePlugin"), q.SchedulingCycle())
  1831  	if err != nil {
  1832  		t.Fatalf("unexpected error from AddUnschedulableIfNotPresent: %v", err)
  1833  	}
  1834  	err = q.AddUnschedulableIfNotPresent(logger, q.newQueuedPodInfo(affinityPod, "fakePlugin"), q.SchedulingCycle())
  1835  	if err != nil {
  1836  		t.Fatalf("unexpected error from AddUnschedulableIfNotPresent: %v", err)
  1837  	}
  1838  
  1839  	// Move clock to make the unschedulable pods complete backoff.
  1840  	c.Step(DefaultPodInitialBackoffDuration + time.Second)
  1841  	// Simulate addition of an assigned pod. The pod has matching labels for
  1842  	// affinityPod. So, affinityPod should go to activeQ.
  1843  	q.AssignedPodAdded(logger, labelPod)
  1844  	if getUnschedulablePod(q, affinityPod) != nil {
  1845  		t.Error("affinityPod is still in the unschedulablePods.")
  1846  	}
  1847  	if _, exists, _ := q.activeQ.Get(newQueuedPodInfoForLookup(affinityPod)); !exists {
  1848  		t.Error("affinityPod is not moved to activeQ.")
  1849  	}
  1850  	// Check that the other pod is still in the unschedulablePods.
  1851  	if getUnschedulablePod(q, unschedulablePodInfo.Pod) == nil {
  1852  		t.Error("unschedulablePodInfo is not in the unschedulablePods.")
  1853  	}
  1854  }
  1855  
  1856  func TestPriorityQueue_NominatedPodsForNode(t *testing.T) {
  1857  	objs := []runtime.Object{medPriorityPodInfo.Pod, unschedulablePodInfo.Pod, highPriorityPodInfo.Pod}
  1858  	logger, ctx := ktesting.NewTestContext(t)
  1859  	ctx, cancel := context.WithCancel(ctx)
  1860  	defer cancel()
  1861  	q := NewTestQueueWithObjects(ctx, newDefaultQueueSort(), objs)
  1862  	q.Add(logger, medPriorityPodInfo.Pod)
  1863  	q.Add(logger, unschedulablePodInfo.Pod)
  1864  	q.Add(logger, highPriorityPodInfo.Pod)
  1865  	if p, err := q.Pop(logger); err != nil || p.Pod != highPriorityPodInfo.Pod {
  1866  		t.Errorf("Expected: %v after Pop, but got: %v", highPriorityPodInfo.Pod.Name, p.Pod.Name)
  1867  	}
  1868  	expectedList := []*framework.PodInfo{medPriorityPodInfo, unschedulablePodInfo}
  1869  	podInfos := q.NominatedPodsForNode("node1")
  1870  	if diff := cmp.Diff(expectedList, podInfos); diff != "" {
  1871  		t.Errorf("Unexpected list of nominated Pods for node: (-want, +got):\n%s", diff)
  1872  	}
  1873  	podInfos[0].Pod.Name = "not mpp"
  1874  	if diff := cmp.Diff(podInfos, q.NominatedPodsForNode("node1")); diff == "" {
  1875  		t.Error("Expected list of nominated Pods for node2 is different from podInfos")
  1876  	}
  1877  	if len(q.NominatedPodsForNode("node2")) != 0 {
  1878  		t.Error("Expected list of nominated Pods for node2 to be empty.")
  1879  	}
  1880  }
  1881  
  1882  func TestPriorityQueue_NominatedPodDeleted(t *testing.T) {
  1883  	tests := []struct {
  1884  		name      string
  1885  		podInfo   *framework.PodInfo
  1886  		deletePod bool
  1887  		want      bool
  1888  	}{
  1889  		{
  1890  			name:    "alive pod gets added into PodNominator",
  1891  			podInfo: medPriorityPodInfo,
  1892  			want:    true,
  1893  		},
  1894  		{
  1895  			name:      "deleted pod shouldn't be added into PodNominator",
  1896  			podInfo:   highPriNominatedPodInfo,
  1897  			deletePod: true,
  1898  			want:      false,
  1899  		},
  1900  		{
  1901  			name:    "pod without .status.nominatedPodName specified shouldn't be added into PodNominator",
  1902  			podInfo: highPriorityPodInfo,
  1903  			want:    false,
  1904  		},
  1905  	}
  1906  
  1907  	for _, tt := range tests {
  1908  		t.Run(tt.name, func(t *testing.T) {
  1909  			logger, _ := ktesting.NewTestContext(t)
  1910  			cs := fake.NewSimpleClientset(tt.podInfo.Pod)
  1911  			informerFactory := informers.NewSharedInformerFactory(cs, 0)
  1912  			podLister := informerFactory.Core().V1().Pods().Lister()
  1913  
  1914  			// Build a PriorityQueue.
  1915  			q := NewPriorityQueue(newDefaultQueueSort(), informerFactory, WithPodLister(podLister))
  1916  			ctx, cancel := context.WithCancel(context.Background())
  1917  			defer cancel()
  1918  			informerFactory.Start(ctx.Done())
  1919  			informerFactory.WaitForCacheSync(ctx.Done())
  1920  
  1921  			if tt.deletePod {
  1922  				// Simulate that the test pod gets deleted physically.
  1923  				informerFactory.Core().V1().Pods().Informer().GetStore().Delete(tt.podInfo.Pod)
  1924  			}
  1925  
  1926  			q.AddNominatedPod(logger, tt.podInfo, nil)
  1927  
  1928  			if got := len(q.NominatedPodsForNode(tt.podInfo.Pod.Status.NominatedNodeName)) == 1; got != tt.want {
  1929  				t.Errorf("Want %v, but got %v", tt.want, got)
  1930  			}
  1931  		})
  1932  	}
  1933  }
  1934  
  1935  func TestPriorityQueue_PendingPods(t *testing.T) {
  1936  	makeSet := func(pods []*v1.Pod) map[*v1.Pod]struct{} {
  1937  		pendingSet := map[*v1.Pod]struct{}{}
  1938  		for _, p := range pods {
  1939  			pendingSet[p] = struct{}{}
  1940  		}
  1941  		return pendingSet
  1942  	}
  1943  
  1944  	logger, ctx := ktesting.NewTestContext(t)
  1945  	ctx, cancel := context.WithCancel(ctx)
  1946  	defer cancel()
  1947  	q := NewTestQueue(ctx, newDefaultQueueSort())
  1948  	// To simulate the pod is failed in scheduling in the real world, Pop() the pod from activeQ before AddUnschedulableIfNotPresent()s below.
  1949  	q.activeQ.Add(q.newQueuedPodInfo(unschedulablePodInfo.Pod))
  1950  	if p, err := q.Pop(logger); err != nil || p.Pod != unschedulablePodInfo.Pod {
  1951  		t.Errorf("Expected: %v after Pop, but got: %v", unschedulablePodInfo.Pod.Name, p.Pod.Name)
  1952  	}
  1953  	q.activeQ.Add(q.newQueuedPodInfo(highPriorityPodInfo.Pod))
  1954  	if p, err := q.Pop(logger); err != nil || p.Pod != highPriorityPodInfo.Pod {
  1955  		t.Errorf("Expected: %v after Pop, but got: %v", highPriorityPodInfo.Pod.Name, p.Pod.Name)
  1956  	}
  1957  	q.Add(logger, medPriorityPodInfo.Pod)
  1958  	err := q.AddUnschedulableIfNotPresent(logger, q.newQueuedPodInfo(unschedulablePodInfo.Pod, "plugin"), q.SchedulingCycle())
  1959  	if err != nil {
  1960  		t.Fatalf("unexpected error from AddUnschedulableIfNotPresent: %v", err)
  1961  	}
  1962  	err = q.AddUnschedulableIfNotPresent(logger, q.newQueuedPodInfo(highPriorityPodInfo.Pod, "plugin"), q.SchedulingCycle())
  1963  	if err != nil {
  1964  		t.Fatalf("unexpected error from AddUnschedulableIfNotPresent: %v", err)
  1965  	}
  1966  
  1967  	expectedSet := makeSet([]*v1.Pod{medPriorityPodInfo.Pod, unschedulablePodInfo.Pod, highPriorityPodInfo.Pod})
  1968  	gotPods, gotSummary := q.PendingPods()
  1969  	if diff := cmp.Diff(expectedSet, makeSet(gotPods)); diff != "" {
  1970  		t.Errorf("Unexpected list of pending Pods (-want, +got):\n%s", diff)
  1971  	}
  1972  	if wantSummary := fmt.Sprintf(pendingPodsSummary, 1, 0, 2); wantSummary != gotSummary {
  1973  		t.Errorf("Unexpected pending pods summary: want %v, but got %v.", wantSummary, gotSummary)
  1974  	}
  1975  	// Move all to active queue. We should still see the same set of pods.
  1976  	q.MoveAllToActiveOrBackoffQueue(logger, WildCardEvent, nil, nil, nil)
  1977  	gotPods, gotSummary = q.PendingPods()
  1978  	if diff := cmp.Diff(expectedSet, makeSet(gotPods)); diff != "" {
  1979  		t.Errorf("Unexpected list of pending Pods (-want, +got):\n%s", diff)
  1980  	}
  1981  	if wantSummary := fmt.Sprintf(pendingPodsSummary, 1, 2, 0); wantSummary != gotSummary {
  1982  		t.Errorf("Unexpected pending pods summary: want %v, but got %v.", wantSummary, gotSummary)
  1983  	}
  1984  }
  1985  
  1986  func TestPriorityQueue_UpdateNominatedPodForNode(t *testing.T) {
  1987  	logger, ctx := ktesting.NewTestContext(t)
  1988  	ctx, cancel := context.WithCancel(ctx)
  1989  	defer cancel()
  1990  	objs := []runtime.Object{medPriorityPodInfo.Pod, unschedulablePodInfo.Pod, highPriorityPodInfo.Pod, scheduledPodInfo.Pod}
  1991  	q := NewTestQueueWithObjects(ctx, newDefaultQueueSort(), objs)
  1992  	if err := q.Add(logger, medPriorityPodInfo.Pod); err != nil {
  1993  		t.Errorf("add failed: %v", err)
  1994  	}
  1995  	// Update unschedulablePodInfo on a different node than specified in the pod.
  1996  	q.AddNominatedPod(logger, mustNewTestPodInfo(t, unschedulablePodInfo.Pod),
  1997  		&framework.NominatingInfo{NominatingMode: framework.ModeOverride, NominatedNodeName: "node5"})
  1998  
  1999  	// Update nominated node name of a pod on a node that is not specified in the pod object.
  2000  	q.AddNominatedPod(logger, mustNewTestPodInfo(t, highPriorityPodInfo.Pod),
  2001  		&framework.NominatingInfo{NominatingMode: framework.ModeOverride, NominatedNodeName: "node2"})
  2002  	expectedNominatedPods := &nominator{
  2003  		nominatedPodToNode: map[types.UID]string{
  2004  			medPriorityPodInfo.Pod.UID:   "node1",
  2005  			highPriorityPodInfo.Pod.UID:  "node2",
  2006  			unschedulablePodInfo.Pod.UID: "node5",
  2007  		},
  2008  		nominatedPods: map[string][]*framework.PodInfo{
  2009  			"node1": {medPriorityPodInfo},
  2010  			"node2": {highPriorityPodInfo},
  2011  			"node5": {unschedulablePodInfo},
  2012  		},
  2013  	}
  2014  	if diff := cmp.Diff(q.nominator, expectedNominatedPods, nominatorCmpOpts...); diff != "" {
  2015  		t.Errorf("Unexpected diff after adding pods (-want, +got):\n%s", diff)
  2016  	}
  2017  	if p, err := q.Pop(logger); err != nil || p.Pod != medPriorityPodInfo.Pod {
  2018  		t.Errorf("Expected: %v after Pop, but got: %v", medPriorityPodInfo.Pod.Name, p.Pod.Name)
  2019  	}
  2020  	// List of nominated pods shouldn't change after popping them from the queue.
  2021  	if diff := cmp.Diff(q.nominator, expectedNominatedPods, nominatorCmpOpts...); diff != "" {
  2022  		t.Errorf("Unexpected diff after popping pods (-want, +got):\n%s", diff)
  2023  	}
  2024  	// Update one of the nominated pods that doesn't have nominatedNodeName in the
  2025  	// pod object. It should be updated correctly.
  2026  	q.AddNominatedPod(logger, highPriorityPodInfo, &framework.NominatingInfo{NominatingMode: framework.ModeOverride, NominatedNodeName: "node4"})
  2027  	expectedNominatedPods = &nominator{
  2028  		nominatedPodToNode: map[types.UID]string{
  2029  			medPriorityPodInfo.Pod.UID:   "node1",
  2030  			highPriorityPodInfo.Pod.UID:  "node4",
  2031  			unschedulablePodInfo.Pod.UID: "node5",
  2032  		},
  2033  		nominatedPods: map[string][]*framework.PodInfo{
  2034  			"node1": {medPriorityPodInfo},
  2035  			"node4": {highPriorityPodInfo},
  2036  			"node5": {unschedulablePodInfo},
  2037  		},
  2038  	}
  2039  	if diff := cmp.Diff(q.nominator, expectedNominatedPods, nominatorCmpOpts...); diff != "" {
  2040  		t.Errorf("Unexpected diff after updating pods (-want, +got):\n%s", diff)
  2041  	}
  2042  
  2043  	// Attempt to nominate a pod that was deleted from the informer cache.
  2044  	// Nothing should change.
  2045  	q.AddNominatedPod(logger, nonExistentPodInfo, &framework.NominatingInfo{NominatingMode: framework.ModeOverride, NominatedNodeName: "node1"})
  2046  	if diff := cmp.Diff(q.nominator, expectedNominatedPods, nominatorCmpOpts...); diff != "" {
  2047  		t.Errorf("Unexpected diff after nominating a deleted pod (-want, +got):\n%s", diff)
  2048  	}
  2049  	// Attempt to nominate a pod that was already scheduled in the informer cache.
  2050  	// Nothing should change.
  2051  	scheduledPodCopy := scheduledPodInfo.Pod.DeepCopy()
  2052  	scheduledPodInfo.Pod.Spec.NodeName = ""
  2053  	q.AddNominatedPod(logger, mustNewTestPodInfo(t, scheduledPodCopy), &framework.NominatingInfo{NominatingMode: framework.ModeOverride, NominatedNodeName: "node1"})
  2054  	if diff := cmp.Diff(q.nominator, expectedNominatedPods, nominatorCmpOpts...); diff != "" {
  2055  		t.Errorf("Unexpected diff after nominating a scheduled pod (-want, +got):\n%s", diff)
  2056  	}
  2057  
  2058  	// Delete a nominated pod that doesn't have nominatedNodeName in the pod
  2059  	// object. It should be deleted.
  2060  	q.DeleteNominatedPodIfExists(highPriorityPodInfo.Pod)
  2061  	expectedNominatedPods = &nominator{
  2062  		nominatedPodToNode: map[types.UID]string{
  2063  			medPriorityPodInfo.Pod.UID:   "node1",
  2064  			unschedulablePodInfo.Pod.UID: "node5",
  2065  		},
  2066  		nominatedPods: map[string][]*framework.PodInfo{
  2067  			"node1": {medPriorityPodInfo},
  2068  			"node5": {unschedulablePodInfo},
  2069  		},
  2070  	}
  2071  	if diff := cmp.Diff(q.nominator, expectedNominatedPods, nominatorCmpOpts...); diff != "" {
  2072  		t.Errorf("Unexpected diff after deleting pods (-want, +got):\n%s", diff)
  2073  	}
  2074  }
  2075  
  2076  func TestPriorityQueue_NewWithOptions(t *testing.T) {
  2077  	ctx, cancel := context.WithCancel(context.Background())
  2078  	defer cancel()
  2079  	q := NewTestQueue(ctx,
  2080  		newDefaultQueueSort(),
  2081  		WithPodInitialBackoffDuration(2*time.Second),
  2082  		WithPodMaxBackoffDuration(20*time.Second),
  2083  	)
  2084  
  2085  	if q.podInitialBackoffDuration != 2*time.Second {
  2086  		t.Errorf("Unexpected pod backoff initial duration. Expected: %v, got: %v", 2*time.Second, q.podInitialBackoffDuration)
  2087  	}
  2088  
  2089  	if q.podMaxBackoffDuration != 20*time.Second {
  2090  		t.Errorf("Unexpected pod backoff max duration. Expected: %v, got: %v", 2*time.Second, q.podMaxBackoffDuration)
  2091  	}
  2092  }
  2093  
  2094  func TestUnschedulablePodsMap(t *testing.T) {
  2095  	var pods = []*v1.Pod{
  2096  		st.MakePod().Name("p0").Namespace("ns1").Annotation("annot1", "val1").NominatedNodeName("node1").Obj(),
  2097  		st.MakePod().Name("p1").Namespace("ns1").Annotation("annot", "val").Obj(),
  2098  		st.MakePod().Name("p2").Namespace("ns2").Annotation("annot2", "val2").Annotation("annot3", "val3").NominatedNodeName("node3").Obj(),
  2099  		st.MakePod().Name("p3").Namespace("ns4").Annotation("annot2", "val2").Annotation("annot3", "val3").NominatedNodeName("node1").Obj(),
  2100  	}
  2101  	var updatedPods = make([]*v1.Pod, len(pods))
  2102  	updatedPods[0] = pods[0].DeepCopy()
  2103  	updatedPods[1] = pods[1].DeepCopy()
  2104  	updatedPods[3] = pods[3].DeepCopy()
  2105  
  2106  	tests := []struct {
  2107  		name                   string
  2108  		podsToAdd              []*v1.Pod
  2109  		expectedMapAfterAdd    map[string]*framework.QueuedPodInfo
  2110  		podsToUpdate           []*v1.Pod
  2111  		expectedMapAfterUpdate map[string]*framework.QueuedPodInfo
  2112  		podsToDelete           []*v1.Pod
  2113  		expectedMapAfterDelete map[string]*framework.QueuedPodInfo
  2114  	}{
  2115  		{
  2116  			name:      "create, update, delete subset of pods",
  2117  			podsToAdd: []*v1.Pod{pods[0], pods[1], pods[2], pods[3]},
  2118  			expectedMapAfterAdd: map[string]*framework.QueuedPodInfo{
  2119  				util.GetPodFullName(pods[0]): {PodInfo: mustNewTestPodInfo(t, pods[0]), UnschedulablePlugins: sets.New[string]()},
  2120  				util.GetPodFullName(pods[1]): {PodInfo: mustNewTestPodInfo(t, pods[1]), UnschedulablePlugins: sets.New[string]()},
  2121  				util.GetPodFullName(pods[2]): {PodInfo: mustNewTestPodInfo(t, pods[2]), UnschedulablePlugins: sets.New[string]()},
  2122  				util.GetPodFullName(pods[3]): {PodInfo: mustNewTestPodInfo(t, pods[3]), UnschedulablePlugins: sets.New[string]()},
  2123  			},
  2124  			podsToUpdate: []*v1.Pod{updatedPods[0]},
  2125  			expectedMapAfterUpdate: map[string]*framework.QueuedPodInfo{
  2126  				util.GetPodFullName(pods[0]): {PodInfo: mustNewTestPodInfo(t, updatedPods[0]), UnschedulablePlugins: sets.New[string]()},
  2127  				util.GetPodFullName(pods[1]): {PodInfo: mustNewTestPodInfo(t, pods[1]), UnschedulablePlugins: sets.New[string]()},
  2128  				util.GetPodFullName(pods[2]): {PodInfo: mustNewTestPodInfo(t, pods[2]), UnschedulablePlugins: sets.New[string]()},
  2129  				util.GetPodFullName(pods[3]): {PodInfo: mustNewTestPodInfo(t, pods[3]), UnschedulablePlugins: sets.New[string]()},
  2130  			},
  2131  			podsToDelete: []*v1.Pod{pods[0], pods[1]},
  2132  			expectedMapAfterDelete: map[string]*framework.QueuedPodInfo{
  2133  				util.GetPodFullName(pods[2]): {PodInfo: mustNewTestPodInfo(t, pods[2]), UnschedulablePlugins: sets.New[string]()},
  2134  				util.GetPodFullName(pods[3]): {PodInfo: mustNewTestPodInfo(t, pods[3]), UnschedulablePlugins: sets.New[string]()},
  2135  			},
  2136  		},
  2137  		{
  2138  			name:      "create, update, delete all",
  2139  			podsToAdd: []*v1.Pod{pods[0], pods[3]},
  2140  			expectedMapAfterAdd: map[string]*framework.QueuedPodInfo{
  2141  				util.GetPodFullName(pods[0]): {PodInfo: mustNewTestPodInfo(t, pods[0]), UnschedulablePlugins: sets.New[string]()},
  2142  				util.GetPodFullName(pods[3]): {PodInfo: mustNewTestPodInfo(t, pods[3]), UnschedulablePlugins: sets.New[string]()},
  2143  			},
  2144  			podsToUpdate: []*v1.Pod{updatedPods[3]},
  2145  			expectedMapAfterUpdate: map[string]*framework.QueuedPodInfo{
  2146  				util.GetPodFullName(pods[0]): {PodInfo: mustNewTestPodInfo(t, pods[0]), UnschedulablePlugins: sets.New[string]()},
  2147  				util.GetPodFullName(pods[3]): {PodInfo: mustNewTestPodInfo(t, updatedPods[3]), UnschedulablePlugins: sets.New[string]()},
  2148  			},
  2149  			podsToDelete:           []*v1.Pod{pods[0], pods[3]},
  2150  			expectedMapAfterDelete: map[string]*framework.QueuedPodInfo{},
  2151  		},
  2152  		{
  2153  			name:      "delete non-existing and existing pods",
  2154  			podsToAdd: []*v1.Pod{pods[1], pods[2]},
  2155  			expectedMapAfterAdd: map[string]*framework.QueuedPodInfo{
  2156  				util.GetPodFullName(pods[1]): {PodInfo: mustNewTestPodInfo(t, pods[1]), UnschedulablePlugins: sets.New[string]()},
  2157  				util.GetPodFullName(pods[2]): {PodInfo: mustNewTestPodInfo(t, pods[2]), UnschedulablePlugins: sets.New[string]()},
  2158  			},
  2159  			podsToUpdate: []*v1.Pod{updatedPods[1]},
  2160  			expectedMapAfterUpdate: map[string]*framework.QueuedPodInfo{
  2161  				util.GetPodFullName(pods[1]): {PodInfo: mustNewTestPodInfo(t, updatedPods[1]), UnschedulablePlugins: sets.New[string]()},
  2162  				util.GetPodFullName(pods[2]): {PodInfo: mustNewTestPodInfo(t, pods[2]), UnschedulablePlugins: sets.New[string]()},
  2163  			},
  2164  			podsToDelete: []*v1.Pod{pods[2], pods[3]},
  2165  			expectedMapAfterDelete: map[string]*framework.QueuedPodInfo{
  2166  				util.GetPodFullName(pods[1]): {PodInfo: mustNewTestPodInfo(t, updatedPods[1]), UnschedulablePlugins: sets.New[string]()},
  2167  			},
  2168  		},
  2169  	}
  2170  
  2171  	for _, test := range tests {
  2172  		t.Run(test.name, func(t *testing.T) {
  2173  			upm := newUnschedulablePods(nil, nil)
  2174  			for _, p := range test.podsToAdd {
  2175  				upm.addOrUpdate(newQueuedPodInfoForLookup(p))
  2176  			}
  2177  			if diff := cmp.Diff(test.expectedMapAfterAdd, upm.podInfoMap); diff != "" {
  2178  				t.Errorf("Unexpected map after adding pods(-want, +got):\n%s", diff)
  2179  			}
  2180  
  2181  			if len(test.podsToUpdate) > 0 {
  2182  				for _, p := range test.podsToUpdate {
  2183  					upm.addOrUpdate(newQueuedPodInfoForLookup(p))
  2184  				}
  2185  				if diff := cmp.Diff(test.expectedMapAfterUpdate, upm.podInfoMap); diff != "" {
  2186  					t.Errorf("Unexpected map after updating pods (-want, +got):\n%s", diff)
  2187  				}
  2188  			}
  2189  			for _, p := range test.podsToDelete {
  2190  				upm.delete(p, false)
  2191  			}
  2192  			if diff := cmp.Diff(test.expectedMapAfterDelete, upm.podInfoMap); diff != "" {
  2193  				t.Errorf("Unexpected map after deleting pods (-want, +got):\n%s", diff)
  2194  			}
  2195  			upm.clear()
  2196  			if len(upm.podInfoMap) != 0 {
  2197  				t.Errorf("Expected the map to be empty, but has %v elements.", len(upm.podInfoMap))
  2198  			}
  2199  		})
  2200  	}
  2201  }
  2202  
  2203  func TestSchedulingQueue_Close(t *testing.T) {
  2204  	logger, ctx := ktesting.NewTestContext(t)
  2205  	ctx, cancel := context.WithCancel(ctx)
  2206  	defer cancel()
  2207  	q := NewTestQueue(ctx, newDefaultQueueSort())
  2208  	wg := sync.WaitGroup{}
  2209  	wg.Add(1)
  2210  	go func() {
  2211  		defer wg.Done()
  2212  		pod, err := q.Pop(logger)
  2213  		if err != nil {
  2214  			t.Errorf("Expected nil err from Pop() if queue is closed, but got %q", err.Error())
  2215  		}
  2216  		if pod != nil {
  2217  			t.Errorf("Expected pod nil from Pop() if queue is closed, but got: %v", pod)
  2218  		}
  2219  	}()
  2220  	q.Close()
  2221  	wg.Wait()
  2222  }
  2223  
  2224  // TestRecentlyTriedPodsGoBack tests that pods which are recently tried and are
  2225  // unschedulable go behind other pods with the same priority. This behavior
  2226  // ensures that an unschedulable pod does not block head of the queue when there
  2227  // are frequent events that move pods to the active queue.
  2228  func TestRecentlyTriedPodsGoBack(t *testing.T) {
  2229  	c := testingclock.NewFakeClock(time.Now())
  2230  	logger, ctx := ktesting.NewTestContext(t)
  2231  	ctx, cancel := context.WithCancel(ctx)
  2232  	defer cancel()
  2233  	q := NewTestQueue(ctx, newDefaultQueueSort(), WithClock(c))
  2234  	// Add a few pods to priority queue.
  2235  	for i := 0; i < 5; i++ {
  2236  		p := st.MakePod().Name(fmt.Sprintf("test-pod-%v", i)).Namespace("ns1").UID(fmt.Sprintf("tp00%v", i)).Priority(highPriority).Node("node1").NominatedNodeName("node1").Obj()
  2237  		q.Add(logger, p)
  2238  	}
  2239  	c.Step(time.Microsecond)
  2240  	// Simulate a pod being popped by the scheduler, determined unschedulable, and
  2241  	// then moved back to the active queue.
  2242  	p1, err := q.Pop(logger)
  2243  	if err != nil {
  2244  		t.Errorf("Error while popping the head of the queue: %v", err)
  2245  	}
  2246  	// Update pod condition to unschedulable.
  2247  	podutil.UpdatePodCondition(&p1.PodInfo.Pod.Status, &v1.PodCondition{
  2248  		Type:          v1.PodScheduled,
  2249  		Status:        v1.ConditionFalse,
  2250  		Reason:        v1.PodReasonUnschedulable,
  2251  		Message:       "fake scheduling failure",
  2252  		LastProbeTime: metav1.Now(),
  2253  	})
  2254  	p1.UnschedulablePlugins = sets.New("plugin")
  2255  	// Put in the unschedulable queue.
  2256  	err = q.AddUnschedulableIfNotPresent(logger, p1, q.SchedulingCycle())
  2257  	if err != nil {
  2258  		t.Fatalf("unexpected error from AddUnschedulableIfNotPresent: %v", err)
  2259  	}
  2260  	c.Step(DefaultPodInitialBackoffDuration)
  2261  	// Move all unschedulable pods to the active queue.
  2262  	q.MoveAllToActiveOrBackoffQueue(logger, UnschedulableTimeout, nil, nil, nil)
  2263  	// Simulation is over. Now let's pop all pods. The pod popped first should be
  2264  	// the last one we pop here.
  2265  	for i := 0; i < 5; i++ {
  2266  		p, err := q.Pop(logger)
  2267  		if err != nil {
  2268  			t.Errorf("Error while popping pods from the queue: %v", err)
  2269  		}
  2270  		if (i == 4) != (p1 == p) {
  2271  			t.Errorf("A pod tried before is not the last pod popped: i: %v, pod name: %v", i, p.PodInfo.Pod.Name)
  2272  		}
  2273  	}
  2274  }
  2275  
  2276  // TestPodFailedSchedulingMultipleTimesDoesNotBlockNewerPod tests
  2277  // that a pod determined as unschedulable multiple times doesn't block any newer pod.
  2278  // This behavior ensures that an unschedulable pod does not block head of the queue when there
  2279  // are frequent events that move pods to the active queue.
  2280  func TestPodFailedSchedulingMultipleTimesDoesNotBlockNewerPod(t *testing.T) {
  2281  	c := testingclock.NewFakeClock(time.Now())
  2282  	logger, ctx := ktesting.NewTestContext(t)
  2283  	ctx, cancel := context.WithCancel(ctx)
  2284  	defer cancel()
  2285  	q := NewTestQueue(ctx, newDefaultQueueSort(), WithClock(c))
  2286  
  2287  	// Add an unschedulable pod to a priority queue.
  2288  	// This makes a situation that the pod was tried to schedule
  2289  	// and had been determined unschedulable so far
  2290  	unschedulablePod := st.MakePod().Name("test-pod-unscheduled").Namespace("ns1").UID("tp001").Priority(highPriority).NominatedNodeName("node1").Obj()
  2291  
  2292  	// Update pod condition to unschedulable.
  2293  	podutil.UpdatePodCondition(&unschedulablePod.Status, &v1.PodCondition{
  2294  		Type:    v1.PodScheduled,
  2295  		Status:  v1.ConditionFalse,
  2296  		Reason:  v1.PodReasonUnschedulable,
  2297  		Message: "fake scheduling failure",
  2298  	})
  2299  
  2300  	// To simulate the pod is failed in scheduling in the real world, Pop() the pod from activeQ before AddUnschedulableIfNotPresent() below.
  2301  	q.activeQ.Add(q.newQueuedPodInfo(unschedulablePod))
  2302  	if p, err := q.Pop(logger); err != nil || p.Pod != unschedulablePod {
  2303  		t.Errorf("Expected: %v after Pop, but got: %v", unschedulablePod.Name, p.Pod.Name)
  2304  	}
  2305  	// Put in the unschedulable queue
  2306  	err := q.AddUnschedulableIfNotPresent(logger, newQueuedPodInfoForLookup(unschedulablePod, "plugin"), q.SchedulingCycle())
  2307  	if err != nil {
  2308  		t.Fatalf("unexpected error from AddUnschedulableIfNotPresent: %v", err)
  2309  	}
  2310  	// Move clock to make the unschedulable pods complete backoff.
  2311  	c.Step(DefaultPodInitialBackoffDuration + time.Second)
  2312  	// Move all unschedulable pods to the active queue.
  2313  	q.MoveAllToActiveOrBackoffQueue(logger, UnschedulableTimeout, nil, nil, nil)
  2314  
  2315  	// Simulate a pod being popped by the scheduler,
  2316  	// At this time, unschedulable pod should be popped.
  2317  	p1, err := q.Pop(logger)
  2318  	if err != nil {
  2319  		t.Errorf("Error while popping the head of the queue: %v", err)
  2320  	}
  2321  	if p1.Pod != unschedulablePod {
  2322  		t.Errorf("Expected that test-pod-unscheduled was popped, got %v", p1.Pod.Name)
  2323  	}
  2324  
  2325  	// Assume newer pod was added just after unschedulable pod
  2326  	// being popped and before being pushed back to the queue.
  2327  	newerPod := st.MakePod().Name("test-newer-pod").Namespace("ns1").UID("tp002").CreationTimestamp(metav1.Now()).Priority(highPriority).NominatedNodeName("node1").Obj()
  2328  	q.Add(logger, newerPod)
  2329  
  2330  	// And then unschedulablePodInfo was determined as unschedulable AGAIN.
  2331  	podutil.UpdatePodCondition(&unschedulablePod.Status, &v1.PodCondition{
  2332  		Type:    v1.PodScheduled,
  2333  		Status:  v1.ConditionFalse,
  2334  		Reason:  v1.PodReasonUnschedulable,
  2335  		Message: "fake scheduling failure",
  2336  	})
  2337  
  2338  	// And then, put unschedulable pod to the unschedulable queue
  2339  	err = q.AddUnschedulableIfNotPresent(logger, newQueuedPodInfoForLookup(unschedulablePod, "plugin"), q.SchedulingCycle())
  2340  	if err != nil {
  2341  		t.Fatalf("unexpected error from AddUnschedulableIfNotPresent: %v", err)
  2342  	}
  2343  	// Move clock to make the unschedulable pods complete backoff.
  2344  	c.Step(DefaultPodInitialBackoffDuration + time.Second)
  2345  	// Move all unschedulable pods to the active queue.
  2346  	q.MoveAllToActiveOrBackoffQueue(logger, UnschedulableTimeout, nil, nil, nil)
  2347  
  2348  	// At this time, newerPod should be popped
  2349  	// because it is the oldest tried pod.
  2350  	p2, err2 := q.Pop(logger)
  2351  	if err2 != nil {
  2352  		t.Errorf("Error while popping the head of the queue: %v", err2)
  2353  	}
  2354  	if p2.Pod != newerPod {
  2355  		t.Errorf("Expected that test-newer-pod was popped, got %v", p2.Pod.Name)
  2356  	}
  2357  }
  2358  
  2359  // TestHighPriorityBackoff tests that a high priority pod does not block
  2360  // other pods if it is unschedulable
  2361  func TestHighPriorityBackoff(t *testing.T) {
  2362  	logger, ctx := ktesting.NewTestContext(t)
  2363  	ctx, cancel := context.WithCancel(ctx)
  2364  	defer cancel()
  2365  	q := NewTestQueue(ctx, newDefaultQueueSort())
  2366  
  2367  	midPod := st.MakePod().Name("test-midpod").Namespace("ns1").UID("tp-mid").Priority(midPriority).NominatedNodeName("node1").Obj()
  2368  	highPod := st.MakePod().Name("test-highpod").Namespace("ns1").UID("tp-high").Priority(highPriority).NominatedNodeName("node1").Obj()
  2369  	q.Add(logger, midPod)
  2370  	q.Add(logger, highPod)
  2371  	// Simulate a pod being popped by the scheduler, determined unschedulable, and
  2372  	// then moved back to the active queue.
  2373  	p, err := q.Pop(logger)
  2374  	if err != nil {
  2375  		t.Errorf("Error while popping the head of the queue: %v", err)
  2376  	}
  2377  	if p.Pod != highPod {
  2378  		t.Errorf("Expected to get high priority pod, got: %v", p)
  2379  	}
  2380  	// Update pod condition to unschedulable.
  2381  	podutil.UpdatePodCondition(&p.Pod.Status, &v1.PodCondition{
  2382  		Type:    v1.PodScheduled,
  2383  		Status:  v1.ConditionFalse,
  2384  		Reason:  v1.PodReasonUnschedulable,
  2385  		Message: "fake scheduling failure",
  2386  	})
  2387  	// Put in the unschedulable queue.
  2388  	err = q.AddUnschedulableIfNotPresent(logger, p, q.SchedulingCycle())
  2389  	if err != nil {
  2390  		t.Fatalf("unexpected error from AddUnschedulableIfNotPresent: %v", err)
  2391  	}
  2392  	// Move all unschedulable pods to the active queue.
  2393  	q.MoveAllToActiveOrBackoffQueue(logger, WildCardEvent, nil, nil, nil)
  2394  
  2395  	p, err = q.Pop(logger)
  2396  	if err != nil {
  2397  		t.Errorf("Error while popping the head of the queue: %v", err)
  2398  	}
  2399  	if p.Pod != midPod {
  2400  		t.Errorf("Expected to get mid priority pod, got: %v", p)
  2401  	}
  2402  }
  2403  
  2404  // TestHighPriorityFlushUnschedulablePodsLeftover tests that pods will be moved to
  2405  // activeQ after one minutes if it is in unschedulablePods.
  2406  func TestHighPriorityFlushUnschedulablePodsLeftover(t *testing.T) {
  2407  	c := testingclock.NewFakeClock(time.Now())
  2408  	m := makeEmptyQueueingHintMapPerProfile()
  2409  	m[""][NodeAdd] = []*QueueingHintFunction{
  2410  		{
  2411  			PluginName:     "fakePlugin",
  2412  			QueueingHintFn: queueHintReturnQueue,
  2413  		},
  2414  	}
  2415  	logger, ctx := ktesting.NewTestContext(t)
  2416  	ctx, cancel := context.WithCancel(ctx)
  2417  	defer cancel()
  2418  	q := NewTestQueue(ctx, newDefaultQueueSort(), WithClock(c), WithQueueingHintMapPerProfile(m))
  2419  	midPod := st.MakePod().Name("test-midpod").Namespace("ns1").UID("tp-mid").Priority(midPriority).NominatedNodeName("node1").Obj()
  2420  	highPod := st.MakePod().Name("test-highpod").Namespace("ns1").UID("tp-high").Priority(highPriority).NominatedNodeName("node1").Obj()
  2421  
  2422  	// Update pod condition to highPod.
  2423  	podutil.UpdatePodCondition(&highPod.Status, &v1.PodCondition{
  2424  		Type:    v1.PodScheduled,
  2425  		Status:  v1.ConditionFalse,
  2426  		Reason:  v1.PodReasonUnschedulable,
  2427  		Message: "fake scheduling failure",
  2428  	})
  2429  
  2430  	// Update pod condition to midPod.
  2431  	podutil.UpdatePodCondition(&midPod.Status, &v1.PodCondition{
  2432  		Type:    v1.PodScheduled,
  2433  		Status:  v1.ConditionFalse,
  2434  		Reason:  v1.PodReasonUnschedulable,
  2435  		Message: "fake scheduling failure",
  2436  	})
  2437  
  2438  	// To simulate the pod is failed in scheduling in the real world, Pop() the pod from activeQ before AddUnschedulableIfNotPresent()s below.
  2439  	q.activeQ.Add(q.newQueuedPodInfo(highPod))
  2440  	if p, err := q.Pop(logger); err != nil || p.Pod != highPod {
  2441  		t.Errorf("Expected: %v after Pop, but got: %v", highPod.Name, p.Pod.Name)
  2442  	}
  2443  	q.activeQ.Add(q.newQueuedPodInfo(midPod))
  2444  	if p, err := q.Pop(logger); err != nil || p.Pod != midPod {
  2445  		t.Errorf("Expected: %v after Pop, but got: %v", midPod.Name, p.Pod.Name)
  2446  	}
  2447  	err := q.AddUnschedulableIfNotPresent(logger, q.newQueuedPodInfo(highPod, "fakePlugin"), q.SchedulingCycle())
  2448  	if err != nil {
  2449  		t.Fatalf("unexpected error from AddUnschedulableIfNotPresent: %v", err)
  2450  	}
  2451  	err = q.AddUnschedulableIfNotPresent(logger, q.newQueuedPodInfo(midPod, "fakePlugin"), q.SchedulingCycle())
  2452  	if err != nil {
  2453  		t.Fatalf("unexpected error from AddUnschedulableIfNotPresent: %v", err)
  2454  	}
  2455  	c.Step(DefaultPodMaxInUnschedulablePodsDuration + time.Second)
  2456  	q.flushUnschedulablePodsLeftover(logger)
  2457  
  2458  	if p, err := q.Pop(logger); err != nil || p.Pod != highPod {
  2459  		t.Errorf("Expected: %v after Pop, but got: %v", highPriorityPodInfo.Pod.Name, p.Pod.Name)
  2460  	}
  2461  	if p, err := q.Pop(logger); err != nil || p.Pod != midPod {
  2462  		t.Errorf("Expected: %v after Pop, but got: %v", medPriorityPodInfo.Pod.Name, p.Pod.Name)
  2463  	}
  2464  }
  2465  
  2466  func TestPriorityQueue_initPodMaxInUnschedulablePodsDuration(t *testing.T) {
  2467  	pod1 := st.MakePod().Name("test-pod-1").Namespace("ns1").UID("tp-1").NominatedNodeName("node1").Obj()
  2468  	pod2 := st.MakePod().Name("test-pod-2").Namespace("ns2").UID("tp-2").NominatedNodeName("node2").Obj()
  2469  
  2470  	var timestamp = time.Now()
  2471  	pInfo1 := &framework.QueuedPodInfo{
  2472  		PodInfo:   mustNewTestPodInfo(t, pod1),
  2473  		Timestamp: timestamp.Add(-time.Second),
  2474  	}
  2475  	pInfo2 := &framework.QueuedPodInfo{
  2476  		PodInfo:   mustNewTestPodInfo(t, pod2),
  2477  		Timestamp: timestamp.Add(-2 * time.Second),
  2478  	}
  2479  
  2480  	tests := []struct {
  2481  		name                              string
  2482  		podMaxInUnschedulablePodsDuration time.Duration
  2483  		operations                        []operation
  2484  		operands                          []*framework.QueuedPodInfo
  2485  		expected                          []*framework.QueuedPodInfo
  2486  	}{
  2487  		{
  2488  			name: "New priority queue by the default value of podMaxInUnschedulablePodsDuration",
  2489  			operations: []operation{
  2490  				addPodUnschedulablePods,
  2491  				addPodUnschedulablePods,
  2492  				flushUnschedulerQ,
  2493  			},
  2494  			operands: []*framework.QueuedPodInfo{pInfo1, pInfo2, nil},
  2495  			expected: []*framework.QueuedPodInfo{pInfo2, pInfo1},
  2496  		},
  2497  		{
  2498  			name:                              "New priority queue by user-defined value of podMaxInUnschedulablePodsDuration",
  2499  			podMaxInUnschedulablePodsDuration: 30 * time.Second,
  2500  			operations: []operation{
  2501  				addPodUnschedulablePods,
  2502  				addPodUnschedulablePods,
  2503  				flushUnschedulerQ,
  2504  			},
  2505  			operands: []*framework.QueuedPodInfo{pInfo1, pInfo2, nil},
  2506  			expected: []*framework.QueuedPodInfo{pInfo2, pInfo1},
  2507  		},
  2508  	}
  2509  
  2510  	for _, test := range tests {
  2511  		t.Run(test.name, func(t *testing.T) {
  2512  			logger, ctx := ktesting.NewTestContext(t)
  2513  			ctx, cancel := context.WithCancel(ctx)
  2514  			defer cancel()
  2515  			var queue *PriorityQueue
  2516  			if test.podMaxInUnschedulablePodsDuration > 0 {
  2517  				queue = NewTestQueue(ctx, newDefaultQueueSort(),
  2518  					WithClock(testingclock.NewFakeClock(timestamp)),
  2519  					WithPodMaxInUnschedulablePodsDuration(test.podMaxInUnschedulablePodsDuration))
  2520  			} else {
  2521  				queue = NewTestQueue(ctx, newDefaultQueueSort(),
  2522  					WithClock(testingclock.NewFakeClock(timestamp)))
  2523  			}
  2524  
  2525  			var podInfoList []*framework.QueuedPodInfo
  2526  
  2527  			for i, op := range test.operations {
  2528  				op(t, logger, queue, test.operands[i])
  2529  			}
  2530  
  2531  			expectedLen := len(test.expected)
  2532  			if queue.activeQ.Len() != expectedLen {
  2533  				t.Fatalf("Expected %v items to be in activeQ, but got: %v", expectedLen, queue.activeQ.Len())
  2534  			}
  2535  
  2536  			for i := 0; i < expectedLen; i++ {
  2537  				if pInfo, err := queue.activeQ.Pop(); err != nil {
  2538  					t.Errorf("Error while popping the head of the queue: %v", err)
  2539  				} else {
  2540  					podInfoList = append(podInfoList, pInfo.(*framework.QueuedPodInfo))
  2541  				}
  2542  			}
  2543  
  2544  			if diff := cmp.Diff(test.expected, podInfoList); diff != "" {
  2545  				t.Errorf("Unexpected QueuedPodInfo list (-want, +got):\n%s", diff)
  2546  			}
  2547  		})
  2548  	}
  2549  }
  2550  
  2551  type operation func(t *testing.T, logger klog.Logger, queue *PriorityQueue, pInfo *framework.QueuedPodInfo)
  2552  
  2553  var (
  2554  	add = func(t *testing.T, logger klog.Logger, queue *PriorityQueue, pInfo *framework.QueuedPodInfo) {
  2555  		if err := queue.Add(logger, pInfo.Pod); err != nil {
  2556  			t.Fatalf("Unexpected error during Add: %v", err)
  2557  		}
  2558  	}
  2559  	popAndRequeueAsUnschedulable = func(t *testing.T, logger klog.Logger, queue *PriorityQueue, pInfo *framework.QueuedPodInfo) {
  2560  		// To simulate the pod is failed in scheduling in the real world, Pop() the pod from activeQ before AddUnschedulableIfNotPresent() below.
  2561  		// UnschedulablePlugins will get cleared by Pop, so make a copy first.
  2562  		unschedulablePlugins := pInfo.UnschedulablePlugins.Clone()
  2563  		if err := queue.activeQ.Add(queue.newQueuedPodInfo(pInfo.Pod)); err != nil {
  2564  			t.Fatalf("Unexpected error during Add: %v", err)
  2565  		}
  2566  		p, err := queue.Pop(logger)
  2567  		if err != nil {
  2568  			t.Fatalf("Unexpected error during Pop: %v", err)
  2569  		}
  2570  		if p.Pod != pInfo.Pod {
  2571  			t.Fatalf("Expected: %v after Pop, but got: %v", pInfo.Pod.Name, p.Pod.Name)
  2572  		}
  2573  		// Simulate plugins that are waiting for some events.
  2574  		p.UnschedulablePlugins = unschedulablePlugins
  2575  		if err := queue.AddUnschedulableIfNotPresent(logger, p, 1); err != nil {
  2576  			t.Fatalf("Unexpected error during AddUnschedulableIfNotPresent: %v", err)
  2577  		}
  2578  	}
  2579  	popAndRequeueAsBackoff = func(t *testing.T, logger klog.Logger, queue *PriorityQueue, pInfo *framework.QueuedPodInfo) {
  2580  		// To simulate the pod is failed in scheduling in the real world, Pop() the pod from activeQ before AddUnschedulableIfNotPresent() below.
  2581  		if err := queue.activeQ.Add(queue.newQueuedPodInfo(pInfo.Pod)); err != nil {
  2582  			t.Fatalf("Unexpected error during Add: %v", err)
  2583  		}
  2584  		p, err := queue.Pop(logger)
  2585  		if err != nil {
  2586  			t.Fatalf("Unexpected error during Pop: %v", err)
  2587  		}
  2588  		if p.Pod != pInfo.Pod {
  2589  			t.Fatalf("Expected: %v after Pop, but got: %v", pInfo.Pod.Name, p.Pod.Name)
  2590  		}
  2591  		// When there is no known unschedulable plugin, pods always go to the backoff queue.
  2592  		if err := queue.AddUnschedulableIfNotPresent(logger, p, 1); err != nil {
  2593  			t.Fatalf("Unexpected error during AddUnschedulableIfNotPresent: %v", err)
  2594  		}
  2595  	}
  2596  	addPodActiveQ = func(t *testing.T, logger klog.Logger, queue *PriorityQueue, pInfo *framework.QueuedPodInfo) {
  2597  		queue.activeQ.Add(pInfo)
  2598  	}
  2599  	updatePodActiveQ = func(t *testing.T, logger klog.Logger, queue *PriorityQueue, pInfo *framework.QueuedPodInfo) {
  2600  		queue.activeQ.Update(pInfo)
  2601  	}
  2602  	addPodUnschedulablePods = func(t *testing.T, logger klog.Logger, queue *PriorityQueue, pInfo *framework.QueuedPodInfo) {
  2603  		if !pInfo.Gated {
  2604  			// Update pod condition to unschedulable.
  2605  			podutil.UpdatePodCondition(&pInfo.Pod.Status, &v1.PodCondition{
  2606  				Type:    v1.PodScheduled,
  2607  				Status:  v1.ConditionFalse,
  2608  				Reason:  v1.PodReasonUnschedulable,
  2609  				Message: "fake scheduling failure",
  2610  			})
  2611  		}
  2612  		queue.unschedulablePods.addOrUpdate(pInfo)
  2613  	}
  2614  	deletePod = func(t *testing.T, _ klog.Logger, queue *PriorityQueue, pInfo *framework.QueuedPodInfo) {
  2615  		queue.Delete(pInfo.Pod)
  2616  	}
  2617  	updatePodQueueable = func(t *testing.T, logger klog.Logger, queue *PriorityQueue, pInfo *framework.QueuedPodInfo) {
  2618  		newPod := pInfo.Pod.DeepCopy()
  2619  		newPod.Labels = map[string]string{"queueable": ""}
  2620  		queue.Update(logger, pInfo.Pod, newPod)
  2621  	}
  2622  	addPodBackoffQ = func(t *testing.T, logger klog.Logger, queue *PriorityQueue, pInfo *framework.QueuedPodInfo) {
  2623  		queue.podBackoffQ.Add(pInfo)
  2624  	}
  2625  	moveAllToActiveOrBackoffQ = func(t *testing.T, logger klog.Logger, queue *PriorityQueue, _ *framework.QueuedPodInfo) {
  2626  		queue.MoveAllToActiveOrBackoffQueue(logger, UnschedulableTimeout, nil, nil, nil)
  2627  	}
  2628  	flushBackoffQ = func(t *testing.T, logger klog.Logger, queue *PriorityQueue, _ *framework.QueuedPodInfo) {
  2629  		queue.clock.(*testingclock.FakeClock).Step(2 * time.Second)
  2630  		queue.flushBackoffQCompleted(logger)
  2631  	}
  2632  	moveClockForward = func(t *testing.T, logger klog.Logger, queue *PriorityQueue, _ *framework.QueuedPodInfo) {
  2633  		queue.clock.(*testingclock.FakeClock).Step(2 * time.Second)
  2634  	}
  2635  	flushUnschedulerQ = func(t *testing.T, logger klog.Logger, queue *PriorityQueue, _ *framework.QueuedPodInfo) {
  2636  		queue.clock.(*testingclock.FakeClock).Step(queue.podMaxInUnschedulablePodsDuration)
  2637  		queue.flushUnschedulablePodsLeftover(logger)
  2638  	}
  2639  )
  2640  
  2641  // TestPodTimestamp tests the operations related to QueuedPodInfo.
  2642  func TestPodTimestamp(t *testing.T) {
  2643  	pod1 := st.MakePod().Name("test-pod-1").Namespace("ns1").UID("tp-1").NominatedNodeName("node1").Obj()
  2644  	pod2 := st.MakePod().Name("test-pod-2").Namespace("ns2").UID("tp-2").NominatedNodeName("node2").Obj()
  2645  
  2646  	var timestamp = time.Now()
  2647  	pInfo1 := &framework.QueuedPodInfo{
  2648  		PodInfo:   mustNewTestPodInfo(t, pod1),
  2649  		Timestamp: timestamp,
  2650  	}
  2651  	pInfo2 := &framework.QueuedPodInfo{
  2652  		PodInfo:   mustNewTestPodInfo(t, pod2),
  2653  		Timestamp: timestamp.Add(time.Second),
  2654  	}
  2655  
  2656  	tests := []struct {
  2657  		name       string
  2658  		operations []operation
  2659  		operands   []*framework.QueuedPodInfo
  2660  		expected   []*framework.QueuedPodInfo
  2661  	}{
  2662  		{
  2663  			name: "add two pod to activeQ and sort them by the timestamp",
  2664  			operations: []operation{
  2665  				addPodActiveQ,
  2666  				addPodActiveQ,
  2667  			},
  2668  			operands: []*framework.QueuedPodInfo{pInfo2, pInfo1},
  2669  			expected: []*framework.QueuedPodInfo{pInfo1, pInfo2},
  2670  		},
  2671  		{
  2672  			name: "update two pod to activeQ and sort them by the timestamp",
  2673  			operations: []operation{
  2674  				updatePodActiveQ,
  2675  				updatePodActiveQ,
  2676  			},
  2677  			operands: []*framework.QueuedPodInfo{pInfo2, pInfo1},
  2678  			expected: []*framework.QueuedPodInfo{pInfo1, pInfo2},
  2679  		},
  2680  		{
  2681  			name: "add two pod to unschedulablePods then move them to activeQ and sort them by the timestamp",
  2682  			operations: []operation{
  2683  				addPodUnschedulablePods,
  2684  				addPodUnschedulablePods,
  2685  				moveClockForward,
  2686  				moveAllToActiveOrBackoffQ,
  2687  			},
  2688  			operands: []*framework.QueuedPodInfo{pInfo2, pInfo1, nil, nil},
  2689  			expected: []*framework.QueuedPodInfo{pInfo1, pInfo2},
  2690  		},
  2691  		{
  2692  			name: "add one pod to BackoffQ and move it to activeQ",
  2693  			operations: []operation{
  2694  				addPodActiveQ,
  2695  				addPodBackoffQ,
  2696  				flushBackoffQ,
  2697  				moveAllToActiveOrBackoffQ,
  2698  			},
  2699  			operands: []*framework.QueuedPodInfo{pInfo2, pInfo1, nil, nil},
  2700  			expected: []*framework.QueuedPodInfo{pInfo1, pInfo2},
  2701  		},
  2702  	}
  2703  
  2704  	for _, test := range tests {
  2705  		t.Run(test.name, func(t *testing.T) {
  2706  			logger, ctx := ktesting.NewTestContext(t)
  2707  			ctx, cancel := context.WithCancel(ctx)
  2708  			defer cancel()
  2709  			queue := NewTestQueue(ctx, newDefaultQueueSort(), WithClock(testingclock.NewFakeClock(timestamp)))
  2710  			var podInfoList []*framework.QueuedPodInfo
  2711  
  2712  			for i, op := range test.operations {
  2713  				op(t, logger, queue, test.operands[i])
  2714  			}
  2715  
  2716  			expectedLen := len(test.expected)
  2717  			if queue.activeQ.Len() != expectedLen {
  2718  				t.Fatalf("Expected %v items to be in activeQ, but got: %v", expectedLen, queue.activeQ.Len())
  2719  			}
  2720  
  2721  			for i := 0; i < expectedLen; i++ {
  2722  				if pInfo, err := queue.activeQ.Pop(); err != nil {
  2723  					t.Errorf("Error while popping the head of the queue: %v", err)
  2724  				} else {
  2725  					podInfoList = append(podInfoList, pInfo.(*framework.QueuedPodInfo))
  2726  				}
  2727  			}
  2728  
  2729  			if diff := cmp.Diff(test.expected, podInfoList); diff != "" {
  2730  				t.Errorf("Unexpected QueuedPodInfo list (-want, +got):\n%s", diff)
  2731  			}
  2732  		})
  2733  	}
  2734  }
  2735  
  2736  // TestPendingPodsMetric tests Prometheus metrics related with pending pods
  2737  func TestPendingPodsMetric(t *testing.T) {
  2738  	timestamp := time.Now()
  2739  	metrics.Register()
  2740  	total := 60
  2741  	queueableNum := 50
  2742  	queueable, failme := "queueable", "failme"
  2743  	// First 50 Pods are queueable.
  2744  	pInfos := makeQueuedPodInfos(queueableNum, "x", queueable, timestamp)
  2745  	// The last 10 Pods are not queueable.
  2746  	gated := makeQueuedPodInfos(total-queueableNum, "y", failme, timestamp)
  2747  	// Manually mark them as gated=true.
  2748  	for _, pInfo := range gated {
  2749  		setQueuedPodInfoGated(pInfo)
  2750  	}
  2751  	pInfos = append(pInfos, gated...)
  2752  	totalWithDelay := 20
  2753  	pInfosWithDelay := makeQueuedPodInfos(totalWithDelay, "z", queueable, timestamp.Add(2*time.Second))
  2754  
  2755  	tests := []struct {
  2756  		name                       string
  2757  		operations                 []operation
  2758  		operands                   [][]*framework.QueuedPodInfo
  2759  		metricsName                string
  2760  		pluginMetricsSamplePercent int
  2761  		wants                      string
  2762  	}{
  2763  		{
  2764  			name: "add pods to activeQ and unschedulablePods",
  2765  			operations: []operation{
  2766  				addPodActiveQ,
  2767  				addPodUnschedulablePods,
  2768  			},
  2769  			operands: [][]*framework.QueuedPodInfo{
  2770  				pInfos[:30],
  2771  				pInfos[30:],
  2772  			},
  2773  			metricsName: "scheduler_pending_pods",
  2774  			wants: `
  2775  # HELP scheduler_pending_pods [STABLE] Number of pending pods, by the queue type. 'active' means number of pods in activeQ; 'backoff' means number of pods in backoffQ; 'unschedulable' means number of pods in unschedulablePods that the scheduler attempted to schedule and failed; 'gated' is the number of unschedulable pods that the scheduler never attempted to schedule because they are gated.
  2776  # TYPE scheduler_pending_pods gauge
  2777  scheduler_pending_pods{queue="active"} 30
  2778  scheduler_pending_pods{queue="backoff"} 0
  2779  scheduler_pending_pods{queue="gated"} 10
  2780  scheduler_pending_pods{queue="unschedulable"} 20
  2781  `,
  2782  		},
  2783  		{
  2784  			name: "add pods to all kinds of queues",
  2785  			operations: []operation{
  2786  				addPodActiveQ,
  2787  				addPodBackoffQ,
  2788  				addPodUnschedulablePods,
  2789  			},
  2790  			operands: [][]*framework.QueuedPodInfo{
  2791  				pInfos[:15],
  2792  				pInfos[15:40],
  2793  				pInfos[40:],
  2794  			},
  2795  			metricsName: "scheduler_pending_pods",
  2796  			wants: `
  2797  # HELP scheduler_pending_pods [STABLE] Number of pending pods, by the queue type. 'active' means number of pods in activeQ; 'backoff' means number of pods in backoffQ; 'unschedulable' means number of pods in unschedulablePods that the scheduler attempted to schedule and failed; 'gated' is the number of unschedulable pods that the scheduler never attempted to schedule because they are gated.
  2798  # TYPE scheduler_pending_pods gauge
  2799  scheduler_pending_pods{queue="active"} 15
  2800  scheduler_pending_pods{queue="backoff"} 25
  2801  scheduler_pending_pods{queue="gated"} 10
  2802  scheduler_pending_pods{queue="unschedulable"} 10
  2803  `,
  2804  		},
  2805  		{
  2806  			name: "add pods to unschedulablePods and then move all to activeQ",
  2807  			operations: []operation{
  2808  				addPodUnschedulablePods,
  2809  				moveClockForward,
  2810  				moveAllToActiveOrBackoffQ,
  2811  			},
  2812  			operands: [][]*framework.QueuedPodInfo{
  2813  				pInfos[:total],
  2814  				{nil},
  2815  				{nil},
  2816  			},
  2817  			metricsName: "scheduler_pending_pods",
  2818  			wants: `
  2819  # HELP scheduler_pending_pods [STABLE] Number of pending pods, by the queue type. 'active' means number of pods in activeQ; 'backoff' means number of pods in backoffQ; 'unschedulable' means number of pods in unschedulablePods that the scheduler attempted to schedule and failed; 'gated' is the number of unschedulable pods that the scheduler never attempted to schedule because they are gated.
  2820  # TYPE scheduler_pending_pods gauge
  2821  scheduler_pending_pods{queue="active"} 50
  2822  scheduler_pending_pods{queue="backoff"} 0
  2823  scheduler_pending_pods{queue="gated"} 10
  2824  scheduler_pending_pods{queue="unschedulable"} 0
  2825  `,
  2826  		},
  2827  		{
  2828  			name: "make some pods subject to backoff, add pods to unschedulablePods, and then move all to activeQ",
  2829  			operations: []operation{
  2830  				addPodUnschedulablePods,
  2831  				moveClockForward,
  2832  				addPodUnschedulablePods,
  2833  				moveAllToActiveOrBackoffQ,
  2834  			},
  2835  			operands: [][]*framework.QueuedPodInfo{
  2836  				pInfos[20:total],
  2837  				{nil},
  2838  				pInfosWithDelay[:20],
  2839  				{nil},
  2840  			},
  2841  			metricsName: "scheduler_pending_pods",
  2842  			wants: `
  2843  # HELP scheduler_pending_pods [STABLE] Number of pending pods, by the queue type. 'active' means number of pods in activeQ; 'backoff' means number of pods in backoffQ; 'unschedulable' means number of pods in unschedulablePods that the scheduler attempted to schedule and failed; 'gated' is the number of unschedulable pods that the scheduler never attempted to schedule because they are gated.
  2844  # TYPE scheduler_pending_pods gauge
  2845  scheduler_pending_pods{queue="active"} 30
  2846  scheduler_pending_pods{queue="backoff"} 20
  2847  scheduler_pending_pods{queue="gated"} 10
  2848  scheduler_pending_pods{queue="unschedulable"} 0
  2849  `,
  2850  		},
  2851  		{
  2852  			name: "make some pods subject to backoff, add pods to unschedulablePods/activeQ, move all to activeQ, and finally flush backoffQ",
  2853  			operations: []operation{
  2854  				addPodUnschedulablePods,
  2855  				addPodActiveQ,
  2856  				moveAllToActiveOrBackoffQ,
  2857  				flushBackoffQ,
  2858  			},
  2859  			operands: [][]*framework.QueuedPodInfo{
  2860  				pInfos[:40],
  2861  				pInfos[40:50],
  2862  				{nil},
  2863  				{nil},
  2864  			},
  2865  			metricsName: "scheduler_pending_pods",
  2866  			wants: `
  2867  # HELP scheduler_pending_pods [STABLE] Number of pending pods, by the queue type. 'active' means number of pods in activeQ; 'backoff' means number of pods in backoffQ; 'unschedulable' means number of pods in unschedulablePods that the scheduler attempted to schedule and failed; 'gated' is the number of unschedulable pods that the scheduler never attempted to schedule because they are gated.
  2868  # TYPE scheduler_pending_pods gauge
  2869  scheduler_pending_pods{queue="active"} 50
  2870  scheduler_pending_pods{queue="backoff"} 0
  2871  scheduler_pending_pods{queue="gated"} 0
  2872  scheduler_pending_pods{queue="unschedulable"} 0
  2873  `,
  2874  		},
  2875  		{
  2876  			name: "add pods to activeQ/unschedulablePods and then delete some Pods",
  2877  			operations: []operation{
  2878  				addPodActiveQ,
  2879  				addPodUnschedulablePods,
  2880  				deletePod,
  2881  				deletePod,
  2882  				deletePod,
  2883  			},
  2884  			operands: [][]*framework.QueuedPodInfo{
  2885  				pInfos[:30],
  2886  				pInfos[30:],
  2887  				pInfos[:2],
  2888  				pInfos[30:33],
  2889  				pInfos[50:54],
  2890  			},
  2891  			metricsName: "scheduler_pending_pods",
  2892  			wants: `
  2893  # HELP scheduler_pending_pods [STABLE] Number of pending pods, by the queue type. 'active' means number of pods in activeQ; 'backoff' means number of pods in backoffQ; 'unschedulable' means number of pods in unschedulablePods that the scheduler attempted to schedule and failed; 'gated' is the number of unschedulable pods that the scheduler never attempted to schedule because they are gated.
  2894  # TYPE scheduler_pending_pods gauge
  2895  scheduler_pending_pods{queue="active"} 28
  2896  scheduler_pending_pods{queue="backoff"} 0
  2897  scheduler_pending_pods{queue="gated"} 6
  2898  scheduler_pending_pods{queue="unschedulable"} 17
  2899  `,
  2900  		},
  2901  		{
  2902  			name: "add pods to activeQ/unschedulablePods and then update some Pods as queueable",
  2903  			operations: []operation{
  2904  				addPodActiveQ,
  2905  				addPodUnschedulablePods,
  2906  				updatePodQueueable,
  2907  			},
  2908  			operands: [][]*framework.QueuedPodInfo{
  2909  				pInfos[:30],
  2910  				pInfos[30:],
  2911  				pInfos[50:55],
  2912  			},
  2913  			metricsName: "scheduler_pending_pods",
  2914  			wants: `
  2915  # HELP scheduler_pending_pods [STABLE] Number of pending pods, by the queue type. 'active' means number of pods in activeQ; 'backoff' means number of pods in backoffQ; 'unschedulable' means number of pods in unschedulablePods that the scheduler attempted to schedule and failed; 'gated' is the number of unschedulable pods that the scheduler never attempted to schedule because they are gated.
  2916  # TYPE scheduler_pending_pods gauge
  2917  scheduler_pending_pods{queue="active"} 35
  2918  scheduler_pending_pods{queue="backoff"} 0
  2919  scheduler_pending_pods{queue="gated"} 5
  2920  scheduler_pending_pods{queue="unschedulable"} 20
  2921  `,
  2922  		},
  2923  		{
  2924  			name: "the metrics should not be recorded (pluginMetricsSamplePercent=0)",
  2925  			operations: []operation{
  2926  				add,
  2927  			},
  2928  			operands: [][]*framework.QueuedPodInfo{
  2929  				pInfos[:1],
  2930  			},
  2931  			metricsName:                "scheduler_plugin_execution_duration_seconds",
  2932  			pluginMetricsSamplePercent: 0,
  2933  			wants: `
  2934  # HELP scheduler_plugin_execution_duration_seconds [ALPHA] Duration for running a plugin at a specific extension point.
  2935  # TYPE scheduler_plugin_execution_duration_seconds histogram
  2936  `, // the observed value will always be 0, because we don't proceed the fake clock.
  2937  		},
  2938  		{
  2939  			name: "the metrics should be recorded (pluginMetricsSamplePercent=100)",
  2940  			operations: []operation{
  2941  				add,
  2942  			},
  2943  			operands: [][]*framework.QueuedPodInfo{
  2944  				pInfos[:1],
  2945  			},
  2946  			metricsName:                "scheduler_plugin_execution_duration_seconds",
  2947  			pluginMetricsSamplePercent: 100,
  2948  			wants: `
  2949  # HELP scheduler_plugin_execution_duration_seconds [ALPHA] Duration for running a plugin at a specific extension point.
  2950  # TYPE scheduler_plugin_execution_duration_seconds histogram
  2951  scheduler_plugin_execution_duration_seconds_bucket{extension_point="PreEnqueue",plugin="preEnqueuePlugin",status="Success",le="1e-05"} 1
  2952  scheduler_plugin_execution_duration_seconds_bucket{extension_point="PreEnqueue",plugin="preEnqueuePlugin",status="Success",le="1.5000000000000002e-05"} 1
  2953  scheduler_plugin_execution_duration_seconds_bucket{extension_point="PreEnqueue",plugin="preEnqueuePlugin",status="Success",le="2.2500000000000005e-05"} 1
  2954  scheduler_plugin_execution_duration_seconds_bucket{extension_point="PreEnqueue",plugin="preEnqueuePlugin",status="Success",le="3.375000000000001e-05"} 1
  2955  scheduler_plugin_execution_duration_seconds_bucket{extension_point="PreEnqueue",plugin="preEnqueuePlugin",status="Success",le="5.062500000000001e-05"} 1
  2956  scheduler_plugin_execution_duration_seconds_bucket{extension_point="PreEnqueue",plugin="preEnqueuePlugin",status="Success",le="7.593750000000002e-05"} 1
  2957  scheduler_plugin_execution_duration_seconds_bucket{extension_point="PreEnqueue",plugin="preEnqueuePlugin",status="Success",le="0.00011390625000000003"} 1
  2958  scheduler_plugin_execution_duration_seconds_bucket{extension_point="PreEnqueue",plugin="preEnqueuePlugin",status="Success",le="0.00017085937500000006"} 1
  2959  scheduler_plugin_execution_duration_seconds_bucket{extension_point="PreEnqueue",plugin="preEnqueuePlugin",status="Success",le="0.0002562890625000001"} 1
  2960  scheduler_plugin_execution_duration_seconds_bucket{extension_point="PreEnqueue",plugin="preEnqueuePlugin",status="Success",le="0.00038443359375000017"} 1
  2961  scheduler_plugin_execution_duration_seconds_bucket{extension_point="PreEnqueue",plugin="preEnqueuePlugin",status="Success",le="0.0005766503906250003"} 1
  2962  scheduler_plugin_execution_duration_seconds_bucket{extension_point="PreEnqueue",plugin="preEnqueuePlugin",status="Success",le="0.0008649755859375004"} 1
  2963  scheduler_plugin_execution_duration_seconds_bucket{extension_point="PreEnqueue",plugin="preEnqueuePlugin",status="Success",le="0.0012974633789062506"} 1
  2964  scheduler_plugin_execution_duration_seconds_bucket{extension_point="PreEnqueue",plugin="preEnqueuePlugin",status="Success",le="0.0019461950683593758"} 1
  2965  scheduler_plugin_execution_duration_seconds_bucket{extension_point="PreEnqueue",plugin="preEnqueuePlugin",status="Success",le="0.0029192926025390638"} 1
  2966  scheduler_plugin_execution_duration_seconds_bucket{extension_point="PreEnqueue",plugin="preEnqueuePlugin",status="Success",le="0.004378938903808595"} 1
  2967  scheduler_plugin_execution_duration_seconds_bucket{extension_point="PreEnqueue",plugin="preEnqueuePlugin",status="Success",le="0.006568408355712893"} 1
  2968  scheduler_plugin_execution_duration_seconds_bucket{extension_point="PreEnqueue",plugin="preEnqueuePlugin",status="Success",le="0.009852612533569338"} 1
  2969  scheduler_plugin_execution_duration_seconds_bucket{extension_point="PreEnqueue",plugin="preEnqueuePlugin",status="Success",le="0.014778918800354007"} 1
  2970  scheduler_plugin_execution_duration_seconds_bucket{extension_point="PreEnqueue",plugin="preEnqueuePlugin",status="Success",le="0.02216837820053101"} 1
  2971  scheduler_plugin_execution_duration_seconds_bucket{extension_point="PreEnqueue",plugin="preEnqueuePlugin",status="Success",le="+Inf"} 1
  2972  scheduler_plugin_execution_duration_seconds_sum{extension_point="PreEnqueue",plugin="preEnqueuePlugin",status="Success"} 0
  2973  scheduler_plugin_execution_duration_seconds_count{extension_point="PreEnqueue",plugin="preEnqueuePlugin",status="Success"} 1
  2974  `, // the observed value will always be 0, because we don't proceed the fake clock.
  2975  		},
  2976  	}
  2977  
  2978  	resetMetrics := func() {
  2979  		metrics.ActivePods().Set(0)
  2980  		metrics.BackoffPods().Set(0)
  2981  		metrics.UnschedulablePods().Set(0)
  2982  		metrics.GatedPods().Set(0)
  2983  		metrics.PluginExecutionDuration.Reset()
  2984  	}
  2985  
  2986  	for _, test := range tests {
  2987  		t.Run(test.name, func(t *testing.T) {
  2988  			resetMetrics()
  2989  			logger, ctx := ktesting.NewTestContext(t)
  2990  			ctx, cancel := context.WithCancel(ctx)
  2991  			defer cancel()
  2992  
  2993  			m := map[string][]framework.PreEnqueuePlugin{"": {&preEnqueuePlugin{allowlists: []string{queueable}}}}
  2994  			recorder := metrics.NewMetricsAsyncRecorder(3, 20*time.Microsecond, ctx.Done())
  2995  			queue := NewTestQueue(ctx, newDefaultQueueSort(), WithClock(testingclock.NewFakeClock(timestamp)), WithPreEnqueuePluginMap(m), WithPluginMetricsSamplePercent(test.pluginMetricsSamplePercent), WithMetricsRecorder(*recorder))
  2996  			for i, op := range test.operations {
  2997  				for _, pInfo := range test.operands[i] {
  2998  					op(t, logger, queue, pInfo)
  2999  				}
  3000  			}
  3001  
  3002  			recorder.FlushMetrics()
  3003  
  3004  			if err := testutil.GatherAndCompare(metrics.GetGather(), strings.NewReader(test.wants), test.metricsName); err != nil {
  3005  				t.Fatal(err)
  3006  			}
  3007  		})
  3008  	}
  3009  }
  3010  
  3011  // TestPerPodSchedulingMetrics makes sure pod schedule attempts is updated correctly while
  3012  // initialAttemptTimestamp stays the same during multiple add/pop operations.
  3013  func TestPerPodSchedulingMetrics(t *testing.T) {
  3014  	timestamp := time.Now()
  3015  
  3016  	logger, ctx := ktesting.NewTestContext(t)
  3017  	ctx, cancel := context.WithCancel(ctx)
  3018  	defer cancel()
  3019  
  3020  	tests := []struct {
  3021  		name                            string
  3022  		perPodSchedulingMetricsScenario func(*testingclock.FakeClock, *PriorityQueue, *v1.Pod)
  3023  		wantAttempts                    int
  3024  		wantInitialAttemptTs            time.Time
  3025  	}{
  3026  		{
  3027  			// The queue operations are Add -> Pop.
  3028  			name: "pod is created and scheduled after 1 attempt",
  3029  			perPodSchedulingMetricsScenario: func(c *testingclock.FakeClock, queue *PriorityQueue, pod *v1.Pod) {
  3030  				queue.Add(logger, pod)
  3031  			},
  3032  			wantAttempts:         1,
  3033  			wantInitialAttemptTs: timestamp,
  3034  		},
  3035  		{
  3036  			// The queue operations are Add -> Pop -> AddUnschedulableIfNotPresent -> flushUnschedulablePodsLeftover -> Pop.
  3037  			name: "pod is created and scheduled after 2 attempts",
  3038  			perPodSchedulingMetricsScenario: func(c *testingclock.FakeClock, queue *PriorityQueue, pod *v1.Pod) {
  3039  				queue.Add(logger, pod)
  3040  				pInfo, err := queue.Pop(logger)
  3041  				if err != nil {
  3042  					t.Fatalf("Failed to pop a pod %v", err)
  3043  				}
  3044  
  3045  				pInfo.UnschedulablePlugins = sets.New("plugin")
  3046  				queue.AddUnschedulableIfNotPresent(logger, pInfo, 1)
  3047  				// Override clock to exceed the DefaultPodMaxInUnschedulablePodsDuration so that unschedulable pods
  3048  				// will be moved to activeQ
  3049  				c.SetTime(timestamp.Add(DefaultPodMaxInUnschedulablePodsDuration + 1))
  3050  				queue.flushUnschedulablePodsLeftover(logger)
  3051  			},
  3052  			wantAttempts:         2,
  3053  			wantInitialAttemptTs: timestamp,
  3054  		},
  3055  		{
  3056  			// The queue operations are Add -> Pop -> AddUnschedulableIfNotPresent -> flushUnschedulablePodsLeftover -> Update -> Pop.
  3057  			name: "pod is created and scheduled after 2 attempts but before the second pop, call update",
  3058  			perPodSchedulingMetricsScenario: func(c *testingclock.FakeClock, queue *PriorityQueue, pod *v1.Pod) {
  3059  				queue.Add(logger, pod)
  3060  				pInfo, err := queue.Pop(logger)
  3061  				if err != nil {
  3062  					t.Fatalf("Failed to pop a pod %v", err)
  3063  				}
  3064  
  3065  				pInfo.UnschedulablePlugins = sets.New("plugin")
  3066  				queue.AddUnschedulableIfNotPresent(logger, pInfo, 1)
  3067  				// Override clock to exceed the DefaultPodMaxInUnschedulablePodsDuration so that unschedulable pods
  3068  				// will be moved to activeQ
  3069  				updatedTimestamp := timestamp
  3070  				c.SetTime(updatedTimestamp.Add(DefaultPodMaxInUnschedulablePodsDuration + 1))
  3071  				queue.flushUnschedulablePodsLeftover(logger)
  3072  				newPod := pod.DeepCopy()
  3073  				newPod.Generation = 1
  3074  				queue.Update(logger, pod, newPod)
  3075  			},
  3076  			wantAttempts:         2,
  3077  			wantInitialAttemptTs: timestamp,
  3078  		},
  3079  		{
  3080  			// The queue operations are Add gated pod -> check unschedulablePods -> lift gate & update pod -> Pop.
  3081  			name: "A gated pod is created and scheduled after lifting gate",
  3082  			perPodSchedulingMetricsScenario: func(c *testingclock.FakeClock, queue *PriorityQueue, pod *v1.Pod) {
  3083  				// Create a queue with PreEnqueuePlugin
  3084  				queue.preEnqueuePluginMap = map[string][]framework.PreEnqueuePlugin{"": {&preEnqueuePlugin{allowlists: []string{"foo"}}}}
  3085  				queue.pluginMetricsSamplePercent = 0
  3086  				queue.Add(logger, pod)
  3087  				// Check pod is added to the unschedulablePods queue.
  3088  				if getUnschedulablePod(queue, pod) != pod {
  3089  					t.Errorf("Pod %v was not found in the unschedulablePods.", pod.Name)
  3090  				}
  3091  				// Override clock to get different InitialAttemptTimestamp
  3092  				c.Step(1 * time.Minute)
  3093  
  3094  				// Update pod with the required label to get it out of unschedulablePods queue.
  3095  				updateGatedPod := pod.DeepCopy()
  3096  				updateGatedPod.Labels = map[string]string{"foo": ""}
  3097  				queue.Update(logger, pod, updateGatedPod)
  3098  			},
  3099  			wantAttempts:         1,
  3100  			wantInitialAttemptTs: timestamp.Add(1 * time.Minute),
  3101  		},
  3102  	}
  3103  	for _, test := range tests {
  3104  		t.Run(test.name, func(t *testing.T) {
  3105  
  3106  			c := testingclock.NewFakeClock(timestamp)
  3107  			pod := st.MakePod().Name("test-pod").Namespace("test-ns").UID("test-uid").Obj()
  3108  			queue := NewTestQueue(ctx, newDefaultQueueSort(), WithClock(c))
  3109  
  3110  			test.perPodSchedulingMetricsScenario(c, queue, pod)
  3111  			podInfo, err := queue.Pop(logger)
  3112  			if err != nil {
  3113  				t.Fatal(err)
  3114  			}
  3115  			if podInfo.Attempts != test.wantAttempts {
  3116  				t.Errorf("Pod schedule attempt unexpected, got %v, want %v", podInfo.Attempts, test.wantAttempts)
  3117  			}
  3118  			if *podInfo.InitialAttemptTimestamp != test.wantInitialAttemptTs {
  3119  				t.Errorf("Pod initial schedule attempt timestamp unexpected, got %v, want %v", *podInfo.InitialAttemptTimestamp, test.wantInitialAttemptTs)
  3120  			}
  3121  		})
  3122  	}
  3123  }
  3124  
  3125  func TestIncomingPodsMetrics(t *testing.T) {
  3126  	timestamp := time.Now()
  3127  	unschedulablePlg := "unschedulable_plugin"
  3128  	metrics.Register()
  3129  	var pInfos = make([]*framework.QueuedPodInfo, 0, 3)
  3130  	for i := 1; i <= 3; i++ {
  3131  		p := &framework.QueuedPodInfo{
  3132  			PodInfo: mustNewTestPodInfo(t,
  3133  				st.MakePod().Name(fmt.Sprintf("test-pod-%d", i)).Namespace(fmt.Sprintf("ns%d", i)).UID(fmt.Sprintf("tp-%d", i)).Obj()),
  3134  			Timestamp:            timestamp,
  3135  			UnschedulablePlugins: sets.New(unschedulablePlg),
  3136  		}
  3137  		pInfos = append(pInfos, p)
  3138  	}
  3139  	tests := []struct {
  3140  		name       string
  3141  		operations []operation
  3142  		want       string
  3143  	}{
  3144  		{
  3145  			name: "add pods to activeQ",
  3146  			operations: []operation{
  3147  				add,
  3148  			},
  3149  			want: `
  3150              scheduler_queue_incoming_pods_total{event="PodAdd",queue="active"} 3
  3151  `,
  3152  		},
  3153  		{
  3154  			name: "add pods to unschedulablePods",
  3155  			operations: []operation{
  3156  				popAndRequeueAsUnschedulable,
  3157  			},
  3158  			want: `
  3159               scheduler_queue_incoming_pods_total{event="ScheduleAttemptFailure",queue="unschedulable"} 3
  3160  `,
  3161  		},
  3162  		{
  3163  			name: "add pods to unschedulablePods and then move all to backoffQ",
  3164  			operations: []operation{
  3165  				popAndRequeueAsUnschedulable,
  3166  				moveAllToActiveOrBackoffQ,
  3167  			},
  3168  			want: ` scheduler_queue_incoming_pods_total{event="ScheduleAttemptFailure",queue="unschedulable"} 3
  3169              scheduler_queue_incoming_pods_total{event="UnschedulableTimeout",queue="backoff"} 3
  3170  `,
  3171  		},
  3172  		{
  3173  			name: "add pods to unschedulablePods and then move all to activeQ",
  3174  			operations: []operation{
  3175  				popAndRequeueAsUnschedulable,
  3176  				moveClockForward,
  3177  				moveAllToActiveOrBackoffQ,
  3178  			},
  3179  			want: ` scheduler_queue_incoming_pods_total{event="ScheduleAttemptFailure",queue="unschedulable"} 3
  3180              scheduler_queue_incoming_pods_total{event="UnschedulableTimeout",queue="active"} 3
  3181  `,
  3182  		},
  3183  		{
  3184  			name: "make some pods subject to backoff and add them to backoffQ, then flush backoffQ",
  3185  			operations: []operation{
  3186  				popAndRequeueAsBackoff,
  3187  				moveClockForward,
  3188  				flushBackoffQ,
  3189  			},
  3190  			want: ` scheduler_queue_incoming_pods_total{event="BackoffComplete",queue="active"} 3
  3191              scheduler_queue_incoming_pods_total{event="ScheduleAttemptFailure",queue="backoff"} 3
  3192  `,
  3193  		},
  3194  	}
  3195  
  3196  	for _, test := range tests {
  3197  		t.Run(test.name, func(t *testing.T) {
  3198  			metrics.SchedulerQueueIncomingPods.Reset()
  3199  			logger, ctx := ktesting.NewTestContext(t)
  3200  			ctx, cancel := context.WithCancel(ctx)
  3201  			defer cancel()
  3202  			queue := NewTestQueue(ctx, newDefaultQueueSort(), WithClock(testingclock.NewFakeClock(timestamp)))
  3203  			for _, op := range test.operations {
  3204  				for _, pInfo := range pInfos {
  3205  					op(t, logger, queue, pInfo)
  3206  				}
  3207  			}
  3208  			metricName := metrics.SchedulerSubsystem + "_" + metrics.SchedulerQueueIncomingPods.Name
  3209  			if err := testutil.CollectAndCompare(metrics.SchedulerQueueIncomingPods, strings.NewReader(queueMetricMetadata+test.want), metricName); err != nil {
  3210  				t.Errorf("unexpected collecting result:\n%s", err)
  3211  			}
  3212  
  3213  		})
  3214  	}
  3215  }
  3216  
  3217  func TestBackOffFlow(t *testing.T) {
  3218  	cl := testingclock.NewFakeClock(time.Now())
  3219  	logger, ctx := ktesting.NewTestContext(t)
  3220  	ctx, cancel := context.WithCancel(ctx)
  3221  	defer cancel()
  3222  	q := NewTestQueue(ctx, newDefaultQueueSort(), WithClock(cl))
  3223  	steps := []struct {
  3224  		wantBackoff time.Duration
  3225  	}{
  3226  		{wantBackoff: time.Second},
  3227  		{wantBackoff: 2 * time.Second},
  3228  		{wantBackoff: 4 * time.Second},
  3229  		{wantBackoff: 8 * time.Second},
  3230  		{wantBackoff: 10 * time.Second},
  3231  		{wantBackoff: 10 * time.Second},
  3232  		{wantBackoff: 10 * time.Second},
  3233  	}
  3234  	pod := st.MakePod().Name("test-pod").Namespace("test-ns").UID("test-uid").Obj()
  3235  
  3236  	podID := types.NamespacedName{
  3237  		Namespace: pod.Namespace,
  3238  		Name:      pod.Name,
  3239  	}
  3240  	if err := q.Add(logger, pod); err != nil {
  3241  		t.Fatal(err)
  3242  	}
  3243  
  3244  	for i, step := range steps {
  3245  		t.Run(fmt.Sprintf("step %d", i), func(t *testing.T) {
  3246  			timestamp := cl.Now()
  3247  			// Simulate schedule attempt.
  3248  			podInfo, err := q.Pop(logger)
  3249  			if err != nil {
  3250  				t.Fatal(err)
  3251  			}
  3252  			if podInfo.Attempts != i+1 {
  3253  				t.Errorf("got attempts %d, want %d", podInfo.Attempts, i+1)
  3254  			}
  3255  			err = q.AddUnschedulableIfNotPresent(logger, podInfo, int64(i))
  3256  			if err != nil {
  3257  				t.Fatalf("unexpected error from AddUnschedulableIfNotPresent: %v", err)
  3258  			}
  3259  
  3260  			// An event happens.
  3261  			q.MoveAllToActiveOrBackoffQueue(logger, UnschedulableTimeout, nil, nil, nil)
  3262  
  3263  			if _, ok, _ := q.podBackoffQ.Get(podInfo); !ok {
  3264  				t.Errorf("pod %v is not in the backoff queue", podID)
  3265  			}
  3266  
  3267  			// Check backoff duration.
  3268  			deadline := q.getBackoffTime(podInfo)
  3269  			backoff := deadline.Sub(timestamp)
  3270  			if backoff != step.wantBackoff {
  3271  				t.Errorf("got backoff %s, want %s", backoff, step.wantBackoff)
  3272  			}
  3273  
  3274  			// Simulate routine that continuously flushes the backoff queue.
  3275  			cl.Step(time.Millisecond)
  3276  			q.flushBackoffQCompleted(logger)
  3277  			// Still in backoff queue after an early flush.
  3278  			if _, ok, _ := q.podBackoffQ.Get(podInfo); !ok {
  3279  				t.Errorf("pod %v is not in the backoff queue", podID)
  3280  			}
  3281  			// Moved out of the backoff queue after timeout.
  3282  			cl.Step(backoff)
  3283  			q.flushBackoffQCompleted(logger)
  3284  			if _, ok, _ := q.podBackoffQ.Get(podInfo); ok {
  3285  				t.Errorf("pod %v is still in the backoff queue", podID)
  3286  			}
  3287  		})
  3288  	}
  3289  }
  3290  
  3291  func TestMoveAllToActiveOrBackoffQueue_PreEnqueueChecks(t *testing.T) {
  3292  	var podInfos []*framework.QueuedPodInfo
  3293  	for i := 0; i < 5; i++ {
  3294  		pInfo := newQueuedPodInfoForLookup(
  3295  			st.MakePod().Name(fmt.Sprintf("p%d", i)).Priority(int32(i)).Obj(),
  3296  		)
  3297  		podInfos = append(podInfos, pInfo)
  3298  	}
  3299  
  3300  	tests := []struct {
  3301  		name            string
  3302  		preEnqueueCheck PreEnqueueCheck
  3303  		podInfos        []*framework.QueuedPodInfo
  3304  		event           framework.ClusterEvent
  3305  		want            []string
  3306  	}{
  3307  		{
  3308  			name:     "nil PreEnqueueCheck",
  3309  			podInfos: podInfos,
  3310  			event:    WildCardEvent,
  3311  			want:     []string{"p0", "p1", "p2", "p3", "p4"},
  3312  		},
  3313  		{
  3314  			name:            "move Pods with priority greater than 2",
  3315  			podInfos:        podInfos,
  3316  			event:           WildCardEvent,
  3317  			preEnqueueCheck: func(pod *v1.Pod) bool { return *pod.Spec.Priority >= 2 },
  3318  			want:            []string{"p2", "p3", "p4"},
  3319  		},
  3320  		{
  3321  			name:     "move Pods with even priority and greater than 2",
  3322  			podInfos: podInfos,
  3323  			event:    WildCardEvent,
  3324  			preEnqueueCheck: func(pod *v1.Pod) bool {
  3325  				return *pod.Spec.Priority%2 == 0 && *pod.Spec.Priority >= 2
  3326  			},
  3327  			want: []string{"p2", "p4"},
  3328  		},
  3329  		{
  3330  			name:     "move Pods with even and negative priority",
  3331  			podInfos: podInfos,
  3332  			event:    WildCardEvent,
  3333  			preEnqueueCheck: func(pod *v1.Pod) bool {
  3334  				return *pod.Spec.Priority%2 == 0 && *pod.Spec.Priority < 0
  3335  			},
  3336  		},
  3337  		{
  3338  			name:     "preCheck isn't called if the event is not interested by any plugins",
  3339  			podInfos: podInfos,
  3340  			event:    PvAdd, // No plugin is interested in this event.
  3341  			preEnqueueCheck: func(pod *v1.Pod) bool {
  3342  				panic("preCheck shouldn't be called")
  3343  			},
  3344  		},
  3345  	}
  3346  
  3347  	for _, tt := range tests {
  3348  		t.Run(tt.name, func(t *testing.T) {
  3349  			logger, ctx := ktesting.NewTestContext(t)
  3350  			ctx, cancel := context.WithCancel(ctx)
  3351  			defer cancel()
  3352  			q := NewTestQueue(ctx, newDefaultQueueSort())
  3353  			for i, podInfo := range tt.podInfos {
  3354  				// To simulate the pod is failed in scheduling in the real world, Pop() the pod from activeQ before AddUnschedulableIfNotPresent() below.
  3355  				q.activeQ.Add(q.newQueuedPodInfo(podInfo.Pod))
  3356  				if p, err := q.Pop(logger); err != nil || p.Pod != podInfo.Pod {
  3357  					t.Errorf("Expected: %v after Pop, but got: %v", podInfo.Pod.Name, p.Pod.Name)
  3358  				}
  3359  				podInfo.UnschedulablePlugins = sets.New("plugin")
  3360  				err := q.AddUnschedulableIfNotPresent(logger, podInfo, q.schedulingCycle)
  3361  				if err != nil {
  3362  					t.Fatalf("unexpected error from AddUnschedulableIfNotPresent: %v", err)
  3363  				}
  3364  				// NOTE: On Windows, time.Now() is not as precise, 2 consecutive calls may return the same timestamp,
  3365  				// resulting in 0 time delta / latency. This will cause the pods to be backed off in a random
  3366  				// order, which would cause this test to fail, since the expectation is for them to be backed off
  3367  				// in a certain order.
  3368  				// See: https://github.com/golang/go/issues/8687
  3369  				podInfo.Timestamp = podInfo.Timestamp.Add(time.Duration((i - len(tt.podInfos))) * time.Millisecond)
  3370  			}
  3371  			q.MoveAllToActiveOrBackoffQueue(logger, tt.event, nil, nil, tt.preEnqueueCheck)
  3372  			var got []string
  3373  			for q.podBackoffQ.Len() != 0 {
  3374  				obj, err := q.podBackoffQ.Pop()
  3375  				if err != nil {
  3376  					t.Fatalf("Fail to pop pod from backoffQ: %v", err)
  3377  				}
  3378  				queuedPodInfo, ok := obj.(*framework.QueuedPodInfo)
  3379  				if !ok {
  3380  					t.Fatalf("Fail to convert popped obj (type %T) to *framework.QueuedPodInfo", obj)
  3381  				}
  3382  				got = append(got, queuedPodInfo.Pod.Name)
  3383  			}
  3384  			if diff := cmp.Diff(tt.want, got); diff != "" {
  3385  				t.Errorf("Unexpected diff (-want, +got):\n%s", diff)
  3386  			}
  3387  		})
  3388  	}
  3389  }
  3390  
  3391  func makeQueuedPodInfos(num int, namePrefix, label string, timestamp time.Time) []*framework.QueuedPodInfo {
  3392  	var pInfos = make([]*framework.QueuedPodInfo, 0, num)
  3393  	for i := 1; i <= num; i++ {
  3394  		p := &framework.QueuedPodInfo{
  3395  			PodInfo: mustNewPodInfo(
  3396  				st.MakePod().Name(fmt.Sprintf("%v-%d", namePrefix, i)).Namespace(fmt.Sprintf("ns%d", i)).Label(label, "").UID(fmt.Sprintf("tp-%d", i)).Obj()),
  3397  			Timestamp:            timestamp,
  3398  			UnschedulablePlugins: sets.New[string](),
  3399  		}
  3400  		pInfos = append(pInfos, p)
  3401  	}
  3402  	return pInfos
  3403  }
  3404  
  3405  func TestPriorityQueue_calculateBackoffDuration(t *testing.T) {
  3406  	tests := []struct {
  3407  		name                   string
  3408  		initialBackoffDuration time.Duration
  3409  		maxBackoffDuration     time.Duration
  3410  		podInfo                *framework.QueuedPodInfo
  3411  		want                   time.Duration
  3412  	}{
  3413  		{
  3414  			name:                   "normal",
  3415  			initialBackoffDuration: 1 * time.Nanosecond,
  3416  			maxBackoffDuration:     32 * time.Nanosecond,
  3417  			podInfo:                &framework.QueuedPodInfo{Attempts: 16},
  3418  			want:                   32 * time.Nanosecond,
  3419  		},
  3420  		{
  3421  			name:                   "overflow_32bit",
  3422  			initialBackoffDuration: 1 * time.Nanosecond,
  3423  			maxBackoffDuration:     math.MaxInt32 * time.Nanosecond,
  3424  			podInfo:                &framework.QueuedPodInfo{Attempts: 32},
  3425  			want:                   math.MaxInt32 * time.Nanosecond,
  3426  		},
  3427  		{
  3428  			name:                   "overflow_64bit",
  3429  			initialBackoffDuration: 1 * time.Nanosecond,
  3430  			maxBackoffDuration:     math.MaxInt64 * time.Nanosecond,
  3431  			podInfo:                &framework.QueuedPodInfo{Attempts: 64},
  3432  			want:                   math.MaxInt64 * time.Nanosecond,
  3433  		},
  3434  	}
  3435  	for _, tt := range tests {
  3436  		t.Run(tt.name, func(t *testing.T) {
  3437  			ctx, cancel := context.WithCancel(context.Background())
  3438  			defer cancel()
  3439  			q := NewTestQueue(ctx, newDefaultQueueSort(), WithPodInitialBackoffDuration(tt.initialBackoffDuration), WithPodMaxBackoffDuration(tt.maxBackoffDuration))
  3440  			if got := q.calculateBackoffDuration(tt.podInfo); got != tt.want {
  3441  				t.Errorf("PriorityQueue.calculateBackoffDuration() = %v, want %v", got, tt.want)
  3442  			}
  3443  		})
  3444  	}
  3445  }
  3446  
  3447  func mustNewTestPodInfo(t *testing.T, pod *v1.Pod) *framework.PodInfo {
  3448  	podInfo, err := framework.NewPodInfo(pod)
  3449  	if err != nil {
  3450  		t.Fatal(err)
  3451  	}
  3452  	return podInfo
  3453  }
  3454  
  3455  func mustNewPodInfo(pod *v1.Pod) *framework.PodInfo {
  3456  	podInfo, err := framework.NewPodInfo(pod)
  3457  	if err != nil {
  3458  		panic(err)
  3459  	}
  3460  	return podInfo
  3461  }
  3462  
  3463  // Test_isPodWorthRequeuing tests isPodWorthRequeuing function.
  3464  func Test_isPodWorthRequeuing(t *testing.T) {
  3465  	count := 0
  3466  	queueHintReturnQueue := func(logger klog.Logger, pod *v1.Pod, oldObj, newObj interface{}) (framework.QueueingHint, error) {
  3467  		count++
  3468  		return framework.Queue, nil
  3469  	}
  3470  	queueHintReturnSkip := func(logger klog.Logger, pod *v1.Pod, oldObj, newObj interface{}) (framework.QueueingHint, error) {
  3471  		count++
  3472  		return framework.QueueSkip, nil
  3473  	}
  3474  	queueHintReturnErr := func(logger klog.Logger, pod *v1.Pod, oldObj, newObj interface{}) (framework.QueueingHint, error) {
  3475  		count++
  3476  		return framework.QueueSkip, fmt.Errorf("unexpected error")
  3477  	}
  3478  
  3479  	tests := []struct {
  3480  		name                   string
  3481  		podInfo                *framework.QueuedPodInfo
  3482  		event                  framework.ClusterEvent
  3483  		oldObj                 interface{}
  3484  		newObj                 interface{}
  3485  		expected               queueingStrategy
  3486  		expectedExecutionCount int // expected total execution count of queueing hint function
  3487  		queueingHintMap        QueueingHintMapPerProfile
  3488  	}{
  3489  		{
  3490  			name: "return Queue when no queueing hint function is registered for the event",
  3491  			podInfo: &framework.QueuedPodInfo{
  3492  				UnschedulablePlugins: sets.New("fooPlugin1"),
  3493  				PodInfo:              mustNewPodInfo(st.MakePod().Name("pod1").Namespace("ns1").UID("1").Obj()),
  3494  			},
  3495  			event:                  NodeAdd,
  3496  			oldObj:                 nil,
  3497  			newObj:                 st.MakeNode().Obj(),
  3498  			expected:               queueSkip,
  3499  			expectedExecutionCount: 0,
  3500  			queueingHintMap: QueueingHintMapPerProfile{
  3501  				"": {
  3502  					// no queueing hint function for NodeAdd.
  3503  					AssignedPodAdd: {
  3504  						{
  3505  							// It will be ignored because the event is not NodeAdd.
  3506  							PluginName:     "fooPlugin1",
  3507  							QueueingHintFn: queueHintReturnQueue,
  3508  						},
  3509  					},
  3510  				},
  3511  			},
  3512  		},
  3513  		{
  3514  			name: "Treat the event as Queue when QueueHintFn returns error",
  3515  			podInfo: &framework.QueuedPodInfo{
  3516  				UnschedulablePlugins: sets.New("fooPlugin1"),
  3517  				PodInfo:              mustNewPodInfo(st.MakePod().Name("pod1").Namespace("ns1").UID("1").Obj()),
  3518  			},
  3519  			event:                  NodeAdd,
  3520  			oldObj:                 nil,
  3521  			newObj:                 st.MakeNode().Obj(),
  3522  			expected:               queueAfterBackoff,
  3523  			expectedExecutionCount: 1,
  3524  			queueingHintMap: QueueingHintMapPerProfile{
  3525  				"": {
  3526  					NodeAdd: {
  3527  						{
  3528  							PluginName:     "fooPlugin1",
  3529  							QueueingHintFn: queueHintReturnErr,
  3530  						},
  3531  					},
  3532  				},
  3533  			},
  3534  		},
  3535  		{
  3536  			name: "return Queue when the event is wildcard",
  3537  			podInfo: &framework.QueuedPodInfo{
  3538  				UnschedulablePlugins: sets.New("fooPlugin1"),
  3539  				PodInfo:              mustNewPodInfo(st.MakePod().Name("pod1").Namespace("ns1").UID("1").Obj()),
  3540  			},
  3541  			event:                  WildCardEvent,
  3542  			oldObj:                 nil,
  3543  			newObj:                 st.MakeNode().Obj(),
  3544  			expected:               queueAfterBackoff,
  3545  			expectedExecutionCount: 0,
  3546  			queueingHintMap:        QueueingHintMapPerProfile{},
  3547  		},
  3548  		{
  3549  			name: "interprets Queue from the Pending plugin as queueImmediately",
  3550  			podInfo: &framework.QueuedPodInfo{
  3551  				UnschedulablePlugins: sets.New("fooPlugin1", "fooPlugin3"),
  3552  				PendingPlugins:       sets.New("fooPlugin2"),
  3553  				PodInfo:              mustNewPodInfo(st.MakePod().Name("pod1").Namespace("ns1").UID("1").Obj()),
  3554  			},
  3555  			event:                  NodeAdd,
  3556  			oldObj:                 nil,
  3557  			newObj:                 st.MakeNode().Node,
  3558  			expected:               queueImmediately,
  3559  			expectedExecutionCount: 2,
  3560  			queueingHintMap: QueueingHintMapPerProfile{
  3561  				"": {
  3562  					NodeAdd: {
  3563  						{
  3564  							PluginName: "fooPlugin1",
  3565  							// It returns Queue and it's interpreted as queueAfterBackoff.
  3566  							// But, the function continues to run other hints because the Pod has PendingPlugins, which can result in queueImmediately.
  3567  							QueueingHintFn: queueHintReturnQueue,
  3568  						},
  3569  						{
  3570  							PluginName: "fooPlugin2",
  3571  							// It's interpreted as queueImmediately.
  3572  							// The function doesn't run other hints because queueImmediately is the highest priority.
  3573  							QueueingHintFn: queueHintReturnQueue,
  3574  						},
  3575  						{
  3576  							PluginName:     "fooPlugin3",
  3577  							QueueingHintFn: queueHintReturnQueue,
  3578  						},
  3579  						{
  3580  							PluginName:     "fooPlugin4",
  3581  							QueueingHintFn: queueHintReturnErr,
  3582  						},
  3583  					},
  3584  				},
  3585  			},
  3586  		},
  3587  		{
  3588  			name: "interprets Queue from the Unschedulable plugin as queueAfterBackoff",
  3589  			podInfo: &framework.QueuedPodInfo{
  3590  				UnschedulablePlugins: sets.New("fooPlugin1", "fooPlugin2"),
  3591  				PodInfo:              mustNewPodInfo(st.MakePod().Name("pod1").Namespace("ns1").UID("1").Obj()),
  3592  			},
  3593  			event:                  NodeAdd,
  3594  			oldObj:                 nil,
  3595  			newObj:                 st.MakeNode().Obj(),
  3596  			expected:               queueAfterBackoff,
  3597  			expectedExecutionCount: 2,
  3598  			queueingHintMap: QueueingHintMapPerProfile{
  3599  				"": {
  3600  					NodeAdd: {
  3601  						{
  3602  							// Skip will be ignored
  3603  							PluginName:     "fooPlugin1",
  3604  							QueueingHintFn: queueHintReturnSkip,
  3605  						},
  3606  						{
  3607  							// Skip will be ignored
  3608  							PluginName:     "fooPlugin2",
  3609  							QueueingHintFn: queueHintReturnQueue,
  3610  						},
  3611  					},
  3612  				},
  3613  			},
  3614  		},
  3615  		{
  3616  			name: "Queueing hint function that isn't from the plugin in UnschedulablePlugins/PendingPlugins is ignored",
  3617  			podInfo: &framework.QueuedPodInfo{
  3618  				UnschedulablePlugins: sets.New("fooPlugin1", "fooPlugin2"),
  3619  				PodInfo:              mustNewPodInfo(st.MakePod().Name("pod1").Namespace("ns1").UID("1").Obj()),
  3620  			},
  3621  			event:                  NodeAdd,
  3622  			oldObj:                 nil,
  3623  			newObj:                 st.MakeNode().Node,
  3624  			expected:               queueSkip,
  3625  			expectedExecutionCount: 2,
  3626  			queueingHintMap: QueueingHintMapPerProfile{
  3627  				"": {
  3628  					NodeAdd: {
  3629  						{
  3630  							PluginName:     "fooPlugin1",
  3631  							QueueingHintFn: queueHintReturnSkip,
  3632  						},
  3633  						{
  3634  							PluginName:     "fooPlugin2",
  3635  							QueueingHintFn: queueHintReturnSkip,
  3636  						},
  3637  						{
  3638  							PluginName:     "fooPlugin3",
  3639  							QueueingHintFn: queueHintReturnQueue, // It'll be ignored.
  3640  						},
  3641  					},
  3642  				},
  3643  			},
  3644  		},
  3645  		{
  3646  			name: "If event is specific Node update event, queueing hint function for NodeUpdate/UpdateNodeLabel is also executed",
  3647  			podInfo: &framework.QueuedPodInfo{
  3648  				UnschedulablePlugins: sets.New("fooPlugin1", "fooPlugin2"),
  3649  				PodInfo:              mustNewPodInfo(st.MakePod().Name("pod1").Namespace("ns1").UID("1").Obj()),
  3650  			},
  3651  			event:                  framework.ClusterEvent{Resource: framework.Node, ActionType: framework.UpdateNodeLabel},
  3652  			oldObj:                 nil,
  3653  			newObj:                 st.MakeNode().Obj(),
  3654  			expected:               queueAfterBackoff,
  3655  			expectedExecutionCount: 1,
  3656  			queueingHintMap: QueueingHintMapPerProfile{
  3657  				"": {
  3658  					framework.ClusterEvent{Resource: framework.Node, ActionType: framework.UpdateNodeLabel}: {
  3659  						{
  3660  							PluginName: "fooPlugin1",
  3661  							// It's only executed and interpreted as queueAfterBackoff.
  3662  							// The function doesn't run other hints because this Pod doesn't have PendingPlugins.
  3663  							QueueingHintFn: queueHintReturnQueue,
  3664  						},
  3665  						{
  3666  							PluginName:     "fooPlugin2",
  3667  							QueueingHintFn: queueHintReturnQueue,
  3668  						},
  3669  					},
  3670  					framework.ClusterEvent{Resource: framework.Node, ActionType: framework.Update}: {
  3671  						{
  3672  							PluginName:     "fooPlugin1",
  3673  							QueueingHintFn: queueHintReturnQueue,
  3674  						},
  3675  					},
  3676  					NodeAdd: { // not executed because NodeAdd is unrelated.
  3677  						{
  3678  							PluginName:     "fooPlugin1",
  3679  							QueueingHintFn: queueHintReturnQueue,
  3680  						},
  3681  					},
  3682  				},
  3683  			},
  3684  		},
  3685  		{
  3686  			name: "If event with '*' Resource, queueing hint function for specified Resource is also executed",
  3687  			podInfo: &framework.QueuedPodInfo{
  3688  				UnschedulablePlugins: sets.New("fooPlugin1"),
  3689  				PodInfo:              mustNewPodInfo(st.MakePod().Name("pod1").Namespace("ns1").UID("1").Obj()),
  3690  			},
  3691  			event:                  framework.ClusterEvent{Resource: framework.Node, ActionType: framework.Add},
  3692  			oldObj:                 nil,
  3693  			newObj:                 st.MakeNode().Obj(),
  3694  			expected:               queueAfterBackoff,
  3695  			expectedExecutionCount: 1,
  3696  			queueingHintMap: QueueingHintMapPerProfile{
  3697  				"": {
  3698  					framework.ClusterEvent{Resource: framework.WildCard, ActionType: framework.Add}: {
  3699  						{
  3700  							PluginName:     "fooPlugin1",
  3701  							QueueingHintFn: queueHintReturnQueue,
  3702  						},
  3703  					},
  3704  				},
  3705  			},
  3706  		},
  3707  		{
  3708  			name: "If event is a wildcard one, queueing hint function for all kinds of events is executed",
  3709  			podInfo: &framework.QueuedPodInfo{
  3710  				UnschedulablePlugins: sets.New("fooPlugin1"),
  3711  				PodInfo:              mustNewPodInfo(st.MakePod().Name("pod1").Namespace("ns1").UID("1").Obj()),
  3712  			},
  3713  			event:                  framework.ClusterEvent{Resource: framework.Node, ActionType: framework.UpdateNodeLabel | framework.UpdateNodeTaint},
  3714  			oldObj:                 nil,
  3715  			newObj:                 st.MakeNode().Obj(),
  3716  			expected:               queueAfterBackoff,
  3717  			expectedExecutionCount: 1,
  3718  			queueingHintMap: QueueingHintMapPerProfile{
  3719  				"": {
  3720  					framework.ClusterEvent{Resource: framework.WildCard, ActionType: framework.All}: {
  3721  						{
  3722  							PluginName:     "fooPlugin1",
  3723  							QueueingHintFn: queueHintReturnQueue,
  3724  						},
  3725  					},
  3726  				},
  3727  			},
  3728  		},
  3729  	}
  3730  
  3731  	for _, test := range tests {
  3732  		t.Run(test.name, func(t *testing.T) {
  3733  			count = 0 // reset count every time
  3734  			logger, ctx := ktesting.NewTestContext(t)
  3735  			q := NewTestQueue(ctx, newDefaultQueueSort(), WithQueueingHintMapPerProfile(test.queueingHintMap))
  3736  			actual := q.isPodWorthRequeuing(logger, test.podInfo, test.event, test.oldObj, test.newObj)
  3737  			if actual != test.expected {
  3738  				t.Errorf("isPodWorthRequeuing() = %v, want %v", actual, test.expected)
  3739  			}
  3740  			if count != test.expectedExecutionCount {
  3741  				t.Errorf("isPodWorthRequeuing() executed queueing hint functions %v times, expected: %v", count, test.expectedExecutionCount)
  3742  			}
  3743  		})
  3744  	}
  3745  }
  3746  
  3747  func Test_queuedPodInfo_gatedSetUponCreationAndUnsetUponUpdate(t *testing.T) {
  3748  	logger, ctx := ktesting.NewTestContext(t)
  3749  	plugin, _ := schedulinggates.New(ctx, nil, nil)
  3750  	m := map[string][]framework.PreEnqueuePlugin{"": {plugin.(framework.PreEnqueuePlugin)}}
  3751  	q := NewTestQueue(ctx, newDefaultQueueSort(), WithPreEnqueuePluginMap(m))
  3752  
  3753  	gatedPod := st.MakePod().SchedulingGates([]string{"hello world"}).Obj()
  3754  	if err := q.Add(logger, gatedPod); err != nil {
  3755  		t.Error("Error calling Add")
  3756  	}
  3757  
  3758  	if !q.unschedulablePods.get(gatedPod).Gated {
  3759  		t.Error("expected pod to be gated")
  3760  	}
  3761  
  3762  	ungatedPod := gatedPod.DeepCopy()
  3763  	ungatedPod.Spec.SchedulingGates = nil
  3764  	if err := q.Update(logger, gatedPod, ungatedPod); err != nil {
  3765  		t.Error("Error calling Update")
  3766  	}
  3767  
  3768  	ungatedPodInfo, _ := q.Pop(logger)
  3769  	if ungatedPodInfo.Gated {
  3770  		t.Error("expected pod to be ungated")
  3771  	}
  3772  }