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