2017-10-24 14:14:29 -04:00
/ *
Copyright 2017 The Kubernetes Authors .
Licensed under the Apache License , Version 2.0 ( the "License" ) ;
you may not use this file except in compliance with the License .
You may obtain a copy of the License at
http : //www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing , software
distributed under the License is distributed on an "AS IS" BASIS ,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied .
See the License for the specific language governing permissions and
limitations under the License .
* /
2018-09-21 19:18:48 -04:00
package queue
2017-10-24 14:14:29 -04:00
import (
2021-03-04 07:30:24 -05:00
"context"
2018-09-14 19:59:54 -04:00
"fmt"
2019-09-29 22:05:50 -04:00
"strings"
2017-11-28 04:40:35 -05:00
"sync"
2017-10-24 14:14:29 -04:00
"testing"
2019-01-15 23:08:19 -05:00
"time"
2017-10-24 14:14:29 -04:00
2021-03-11 15:31:33 -05:00
"github.com/google/go-cmp/cmp"
2021-06-04 14:04:44 -04:00
"github.com/google/go-cmp/cmp/cmpopts"
2021-03-09 06:42:20 -05:00
v1 "k8s.io/api/core/v1"
2017-10-24 14:14:29 -04:00
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2021-09-18 22:09:06 -04:00
"k8s.io/apimachinery/pkg/runtime"
2018-11-27 19:46:24 -05:00
"k8s.io/apimachinery/pkg/types"
2021-01-29 01:29:10 -05:00
"k8s.io/apimachinery/pkg/util/sets"
2025-05-26 13:56:10 -04:00
"k8s.io/apimachinery/pkg/util/version"
2023-07-17 18:53:07 -04:00
utilfeature "k8s.io/apiserver/pkg/util/feature"
2021-06-04 14:04:44 -04:00
"k8s.io/client-go/informers"
"k8s.io/client-go/kubernetes/fake"
2023-07-17 18:53:07 -04:00
featuregatetesting "k8s.io/component-base/featuregate/testing"
2019-09-29 22:05:50 -04:00
"k8s.io/component-base/metrics/testutil"
2023-03-22 07:48:04 -04:00
"k8s.io/klog/v2"
"k8s.io/klog/v2/ktesting"
2025-06-26 11:06:29 -04:00
fwk "k8s.io/kube-scheduler/framework"
2018-11-27 19:46:24 -05:00
podutil "k8s.io/kubernetes/pkg/api/v1/pod"
2023-07-17 18:53:07 -04:00
"k8s.io/kubernetes/pkg/features"
2020-10-09 10:41:44 -04:00
"k8s.io/kubernetes/pkg/scheduler/framework"
2023-12-08 03:12:13 -05:00
plfeature "k8s.io/kubernetes/pkg/scheduler/framework/plugins/feature"
2024-06-15 18:36:42 -04:00
"k8s.io/kubernetes/pkg/scheduler/framework/plugins/names"
2020-02-13 11:08:46 -05:00
"k8s.io/kubernetes/pkg/scheduler/framework/plugins/queuesort"
2024-05-02 06:41:19 -04:00
"k8s.io/kubernetes/pkg/scheduler/framework/plugins/schedulinggates"
2019-03-20 02:53:33 -04:00
"k8s.io/kubernetes/pkg/scheduler/metrics"
2022-04-18 21:06:58 -04:00
st "k8s.io/kubernetes/pkg/scheduler/testing"
2021-09-17 05:48:22 -04:00
testingclock "k8s.io/utils/clock/testing"
2017-10-24 14:14:29 -04:00
)
2019-10-07 13:29:53 -04:00
const queueMetricMetadata = `
2021-11-09 09:08:18 -05:00
# HELP scheduler_queue_incoming_pods_total [ STABLE ] Number of pods added to scheduling queues by event and queue type .
2019-10-07 13:29:53 -04:00
# TYPE scheduler_queue_incoming_pods_total counter
`
2021-03-16 18:08:19 -04:00
var (
2024-10-01 23:24:03 -04:00
// nodeAdd is the event when a new node is added to the cluster.
2025-06-26 11:06:29 -04:00
nodeAdd = fwk . ClusterEvent { Resource : fwk . Node , ActionType : fwk . Add }
2024-10-01 23:24:03 -04:00
// pvAdd is the event when a persistent volume is added in the cluster.
2025-06-26 11:06:29 -04:00
pvAdd = fwk . ClusterEvent { Resource : fwk . PersistentVolume , ActionType : fwk . Add }
2024-10-01 23:24:03 -04:00
// pvUpdate is the event when a persistent volume is updated in the cluster.
2025-06-26 11:06:29 -04:00
pvUpdate = fwk . ClusterEvent { Resource : fwk . PersistentVolume , ActionType : fwk . Update }
2024-10-01 23:24:03 -04:00
// pvcAdd is the event when a persistent volume claim is added in the cluster.
2025-06-26 11:06:29 -04:00
pvcAdd = fwk . ClusterEvent { Resource : fwk . PersistentVolumeClaim , ActionType : fwk . Add }
2024-10-01 23:24:03 -04:00
// csiNodeUpdate is the event when a CSI node is updated in the cluster.
2025-06-26 11:06:29 -04:00
csiNodeUpdate = fwk . ClusterEvent { Resource : fwk . CSINode , ActionType : fwk . Update }
2021-03-16 18:08:19 -04:00
2022-04-01 15:21:10 -04:00
lowPriority , midPriority , highPriority = int32 ( 0 ) , int32 ( 100 ) , int32 ( 1000 )
mediumPriority = ( lowPriority + highPriority ) / 2
2022-10-12 10:11:04 -04:00
highPriorityPodInfo = mustNewPodInfo (
2022-04-18 21:06:58 -04:00
st . MakePod ( ) . Name ( "hpp" ) . Namespace ( "ns1" ) . UID ( "hppns1" ) . Priority ( highPriority ) . Obj ( ) ,
)
2022-10-12 10:11:04 -04:00
highPriNominatedPodInfo = mustNewPodInfo (
2022-04-18 21:06:58 -04:00
st . MakePod ( ) . Name ( "hpp" ) . Namespace ( "ns1" ) . UID ( "hppns1" ) . Priority ( highPriority ) . NominatedNodeName ( "node1" ) . Obj ( ) ,
)
2022-10-12 10:11:04 -04:00
medPriorityPodInfo = mustNewPodInfo (
2022-04-18 21:06:58 -04:00
st . MakePod ( ) . Name ( "mpp" ) . Namespace ( "ns2" ) . UID ( "mppns2" ) . Annotation ( "annot2" , "val2" ) . Priority ( mediumPriority ) . NominatedNodeName ( "node1" ) . Obj ( ) ,
)
2022-10-12 10:11:04 -04:00
unschedulablePodInfo = mustNewPodInfo (
2022-04-18 21:06:58 -04:00
st . MakePod ( ) . Name ( "up" ) . Namespace ( "ns1" ) . UID ( "upns1" ) . Annotation ( "annot2" , "val2" ) . Priority ( lowPriority ) . NominatedNodeName ( "node1" ) . Condition ( v1 . PodScheduled , v1 . ConditionFalse , v1 . PodReasonUnschedulable ) . Obj ( ) ,
)
2022-10-12 10:11:04 -04:00
nonExistentPodInfo = mustNewPodInfo (
2022-04-18 21:06:58 -04:00
st . MakePod ( ) . Name ( "ne" ) . Namespace ( "ns1" ) . UID ( "nens1" ) . Obj ( ) ,
)
2022-10-12 10:11:04 -04:00
scheduledPodInfo = mustNewPodInfo (
2022-04-18 21:06:58 -04:00
st . MakePod ( ) . Name ( "sp" ) . Namespace ( "ns1" ) . UID ( "spns1" ) . Node ( "foo" ) . Obj ( ) ,
)
2023-03-08 16:18:36 -05:00
nominatorCmpOpts = [ ] cmp . Option {
2024-07-22 07:56:01 -04:00
cmp . AllowUnexported ( nominator { } , podRef { } ) ,
2024-07-22 07:55:18 -04:00
cmpopts . IgnoreFields ( nominator { } , "podLister" , "nLock" ) ,
2023-03-08 16:18:36 -05:00
}
2023-06-08 00:54:30 -04:00
2025-06-26 11:06:29 -04:00
queueHintReturnQueue = func ( logger klog . Logger , pod * v1 . Pod , oldObj , newObj interface { } ) ( fwk . QueueingHint , error ) {
return fwk . Queue , nil
2023-06-08 00:54:30 -04:00
}
2025-06-26 11:06:29 -04:00
queueHintReturnSkip = func ( logger klog . Logger , pod * v1 . Pod , oldObj , newObj interface { } ) ( fwk . QueueingHint , error ) {
return fwk . QueueSkip , nil
2023-06-08 00:54:30 -04:00
}
2022-04-01 15:21:10 -04:00
)
2017-10-24 14:14:29 -04:00
2024-10-31 11:29:29 -04:00
func init ( ) {
metrics . Register ( )
}
2025-06-26 11:06:29 -04:00
func setQueuedPodInfoGated ( queuedPodInfo * framework . QueuedPodInfo , gatingPlugin string , gatingPluginEvents [ ] fwk . ClusterEvent ) * framework . QueuedPodInfo {
2024-09-05 22:19:49 -04:00
queuedPodInfo . GatingPlugin = gatingPlugin
// GatingPlugin should also be registered in UnschedulablePlugins.
queuedPodInfo . UnschedulablePlugins = sets . New ( gatingPlugin )
queuedPodInfo . GatingPluginEvents = gatingPluginEvents
2024-04-22 13:00:58 -04:00
return queuedPodInfo
}
2019-01-17 15:24:18 -05:00
func getUnschedulablePod ( p * PriorityQueue , pod * v1 . Pod ) * v1 . Pod {
2022-03-24 05:38:49 -04:00
pInfo := p . unschedulablePods . get ( pod )
2019-02-21 20:46:18 -05:00
if pInfo != nil {
2019-05-06 21:03:00 -04:00
return pInfo . Pod
2019-02-21 20:46:18 -05:00
}
return nil
2019-01-17 15:24:18 -05:00
}
2023-06-08 00:54:30 -04:00
// makeEmptyQueueingHintMapPerProfile initializes an empty QueueingHintMapPerProfile for "" profile name.
func makeEmptyQueueingHintMapPerProfile ( ) QueueingHintMapPerProfile {
m := make ( QueueingHintMapPerProfile )
m [ "" ] = make ( QueueingHintMap )
return m
}
2017-10-24 14:14:29 -04:00
func TestPriorityQueue_Add ( t * testing . T ) {
2021-06-05 14:30:17 -04:00
objs := [ ] runtime . Object { medPriorityPodInfo . Pod , unschedulablePodInfo . Pod , highPriorityPodInfo . Pod }
2023-03-22 07:48:04 -04:00
logger , ctx := ktesting . NewTestContext ( t )
ctx , cancel := context . WithCancel ( ctx )
2022-01-22 14:00:56 -05:00
defer cancel ( )
q := NewTestQueueWithObjects ( ctx , newDefaultQueueSort ( ) , objs )
2024-07-17 07:25:31 -04:00
q . Add ( logger , medPriorityPodInfo . Pod )
q . Add ( logger , unschedulablePodInfo . Pod )
q . Add ( logger , highPriorityPodInfo . Pod )
2021-06-05 14:30:17 -04:00
expectedNominatedPods := & nominator {
2018-12-18 02:41:53 -05:00
nominatedPodToNode : map [ types . UID ] string {
2021-02-22 09:00:23 -05:00
medPriorityPodInfo . Pod . UID : "node1" ,
unschedulablePodInfo . Pod . UID : "node1" ,
2018-12-18 02:41:53 -05:00
} ,
2024-07-22 07:56:01 -04:00
nominatedPods : map [ string ] [ ] podRef {
"node1" : { podToRef ( medPriorityPodInfo . Pod ) , podToRef ( unschedulablePodInfo . Pod ) } ,
2018-12-18 02:41:53 -05:00
} ,
2018-02-08 21:19:31 -05:00
}
2023-03-08 16:18:36 -05:00
if diff := cmp . Diff ( q . nominator , expectedNominatedPods , nominatorCmpOpts ... ) ; diff != "" {
2021-06-04 14:04:44 -04:00
t . Errorf ( "Unexpected diff after adding pods (-want, +got):\n%s" , diff )
2018-02-08 21:19:31 -05:00
}
2023-09-28 22:59:22 -04:00
if p , err := q . Pop ( logger ) ; err != nil || p . Pod != highPriorityPodInfo . Pod {
2021-02-22 09:00:23 -05:00
t . Errorf ( "Expected: %v after Pop, but got: %v" , highPriorityPodInfo . Pod . Name , p . Pod . Name )
2017-10-24 14:14:29 -04:00
}
2023-09-28 22:59:22 -04:00
if p , err := q . Pop ( logger ) ; err != nil || p . Pod != medPriorityPodInfo . Pod {
2021-02-22 09:00:23 -05:00
t . Errorf ( "Expected: %v after Pop, but got: %v" , medPriorityPodInfo . Pod . Name , p . Pod . Name )
2017-10-24 14:14:29 -04:00
}
2023-09-28 22:59:22 -04:00
if p , err := q . Pop ( logger ) ; err != nil || p . Pod != unschedulablePodInfo . Pod {
2021-02-22 09:00:23 -05:00
t . Errorf ( "Expected: %v after Pop, but got: %v" , unschedulablePodInfo . Pod . Name , p . Pod . Name )
2017-10-24 14:14:29 -04:00
}
2023-03-08 16:18:36 -05:00
if len ( q . nominator . nominatedPods [ "node1" ] ) != 2 {
2024-07-01 07:37:53 -04:00
t . Errorf ( "Expected medPriorityPodInfo and unschedulablePodInfo to be still present in nominatedPods: %v" , q . nominator . nominatedPods [ "node1" ] )
2018-02-08 21:19:31 -05:00
}
}
2025-07-24 07:48:07 -04:00
func newDefaultQueueSort ( ) fwk . LessFunc {
2020-02-13 11:08:46 -05:00
sort := & queuesort . PrioritySort { }
return sort . Less
2020-01-07 22:22:51 -05:00
}
2019-05-06 21:03:00 -04:00
func TestPriorityQueue_AddWithReversePriorityLessFunc ( t * testing . T ) {
2021-06-05 14:30:17 -04:00
objs := [ ] runtime . Object { medPriorityPodInfo . Pod , highPriorityPodInfo . Pod }
2023-03-22 07:48:04 -04:00
logger , ctx := ktesting . NewTestContext ( t )
ctx , cancel := context . WithCancel ( ctx )
2022-01-22 14:00:56 -05:00
defer cancel ( )
q := NewTestQueueWithObjects ( ctx , newDefaultQueueSort ( ) , objs )
2024-07-17 07:25:31 -04:00
q . Add ( logger , medPriorityPodInfo . Pod )
q . Add ( logger , highPriorityPodInfo . Pod )
2023-09-28 22:59:22 -04:00
if p , err := q . Pop ( logger ) ; err != nil || p . Pod != highPriorityPodInfo . Pod {
2021-02-22 09:00:23 -05:00
t . Errorf ( "Expected: %v after Pop, but got: %v" , highPriorityPodInfo . Pod . Name , p . Pod . Name )
2019-05-06 21:03:00 -04:00
}
2023-09-28 22:59:22 -04:00
if p , err := q . Pop ( logger ) ; err != nil || p . Pod != medPriorityPodInfo . Pod {
2021-02-22 09:00:23 -05:00
t . Errorf ( "Expected: %v after Pop, but got: %v" , medPriorityPodInfo . Pod . Name , p . Pod . Name )
2020-01-15 15:26:22 -05:00
}
2019-05-06 21:03:00 -04:00
}
2023-07-17 18:53:07 -04:00
func Test_InFlightPods ( t * testing . T ) {
2023-09-28 22:59:22 -04:00
logger , _ := ktesting . NewTestContext ( t )
2024-08-30 02:37:09 -04:00
pod1 := st . MakePod ( ) . Name ( "targetpod" ) . UID ( "pod1" ) . Obj ( )
2023-07-17 18:53:07 -04:00
pod2 := st . MakePod ( ) . Name ( "targetpod2" ) . UID ( "pod2" ) . Obj ( )
pod3 := st . MakePod ( ) . Name ( "targetpod3" ) . UID ( "pod3" ) . Obj ( )
2023-09-05 14:21:40 -04:00
var poppedPod , poppedPod2 * framework . QueuedPodInfo
2023-07-17 18:53:07 -04:00
type action struct {
// ONLY ONE of the following should be set.
2025-06-26 11:06:29 -04:00
eventHappens * fwk . ClusterEvent
2023-07-17 18:53:07 -04:00
podPopped * v1 . Pod
2024-08-30 02:37:09 -04:00
// podCreated is the Pod that is created and inserted into the activeQ.
podCreated * v1 . Pod
// podEnqueued is the Pod that is enqueued back to activeQ.
podEnqueued * framework . QueuedPodInfo
callback func ( t * testing . T , q * PriorityQueue )
2023-09-05 14:21:40 -04:00
}
2023-07-17 18:53:07 -04:00
tests := [ ] struct {
name string
queueingHintMap QueueingHintMapPerProfile
// initialPods is the initial Pods in the activeQ.
initialPods [ ] * v1 . Pod
actions [ ] action
2023-09-04 09:48:45 -04:00
wantInFlightPods [ ] * v1 . Pod
wantInFlightEvents [ ] interface { }
2023-07-17 18:53:07 -04:00
wantActiveQPodNames [ ] string
wantBackoffQPodNames [ ] string
wantUnschedPodPoolPodNames [ ] string
isSchedulingQueueHintEnabled bool
} {
{
2023-09-04 09:48:45 -04:00
name : "when SchedulingQueueHint is disabled, inFlightPods and inFlightEvents should be empty" ,
2024-08-30 02:37:09 -04:00
initialPods : [ ] * v1 . Pod { pod1 } ,
2023-07-17 18:53:07 -04:00
actions : [ ] action {
// This Pod shouldn't be added to inFlightPods because SchedulingQueueHint is disabled.
2024-08-30 02:37:09 -04:00
{ podPopped : pod1 } ,
2023-09-04 09:48:45 -04:00
// This event shouldn't be added to inFlightEvents because SchedulingQueueHint is disabled.
2024-10-01 23:24:03 -04:00
{ eventHappens : & pvAdd } ,
2023-07-17 18:53:07 -04:00
} ,
2023-09-04 09:48:45 -04:00
wantInFlightPods : nil ,
wantInFlightEvents : nil ,
2023-07-17 18:53:07 -04:00
queueingHintMap : QueueingHintMapPerProfile {
"" : {
2024-10-01 23:24:03 -04:00
pvAdd : {
2023-07-17 18:53:07 -04:00
{
PluginName : "fooPlugin1" ,
2024-01-07 00:43:21 -05:00
QueueingHintFn : queueHintReturnQueue ,
2023-07-17 18:53:07 -04:00
} ,
} ,
} ,
} ,
} ,
{
2024-01-07 00:43:21 -05:00
name : "Pod and interested events are registered in inFlightPods/inFlightEvents" ,
2023-07-17 18:53:07 -04:00
isSchedulingQueueHintEnabled : true ,
2024-08-30 02:37:09 -04:00
initialPods : [ ] * v1 . Pod { pod1 } ,
2023-07-17 18:53:07 -04:00
actions : [ ] action {
2023-09-04 09:48:45 -04:00
// This won't be added to inFlightEvents because no inFlightPods at this point.
2024-10-01 23:24:03 -04:00
{ eventHappens : & pvcAdd } ,
2024-08-30 02:37:09 -04:00
{ podPopped : pod1 } ,
2023-09-04 09:48:45 -04:00
// This gets added for the pod.
2024-10-01 23:24:03 -04:00
{ eventHappens : & pvAdd } ,
// This doesn't get added because no plugin is interested in PvUpdate.
{ eventHappens : & pvUpdate } ,
2023-07-17 18:53:07 -04:00
} ,
2024-08-30 02:37:09 -04:00
wantInFlightPods : [ ] * v1 . Pod { pod1 } ,
2024-10-01 23:24:03 -04:00
wantInFlightEvents : [ ] interface { } { pod1 , pvAdd } ,
2024-01-07 00:43:21 -05:00
queueingHintMap : QueueingHintMapPerProfile {
"" : {
2024-10-01 23:24:03 -04:00
pvAdd : {
2024-01-07 00:43:21 -05:00
{
PluginName : "fooPlugin1" ,
QueueingHintFn : queueHintReturnQueue ,
} ,
} ,
} ,
} ,
2023-07-17 18:53:07 -04:00
} ,
{
2024-11-11 16:29:19 -05:00
name : "Pod, registered in inFlightPods, is enqueued back to backoffQ" ,
2023-07-17 18:53:07 -04:00
isSchedulingQueueHintEnabled : true ,
2024-08-30 02:37:09 -04:00
initialPods : [ ] * v1 . Pod { pod1 , pod2 } ,
2023-07-17 18:53:07 -04:00
actions : [ ] action {
2023-09-04 09:48:45 -04:00
// This won't be added to inFlightEvents because no inFlightPods at this point.
2024-10-01 23:24:03 -04:00
{ eventHappens : & pvcAdd } ,
2024-08-30 02:37:09 -04:00
{ podPopped : pod1 } ,
2024-10-01 23:24:03 -04:00
{ eventHappens : & pvAdd } ,
2023-07-17 18:53:07 -04:00
{ podPopped : pod2 } ,
2024-10-01 23:24:03 -04:00
{ eventHappens : & nodeAdd } ,
2024-11-11 16:29:19 -05:00
// This pod will be requeued to backoffQ immediately because no plugin is registered as unschedulable plugin,
// which means the pod encountered an unexpected error (e.g., a network error).
2024-08-30 02:37:09 -04:00
{ podEnqueued : newQueuedPodInfoForLookup ( pod1 ) } ,
2023-07-17 18:53:07 -04:00
} ,
wantBackoffQPodNames : [ ] string { "targetpod" } ,
2024-01-07 00:43:21 -05:00
wantInFlightPods : [ ] * v1 . Pod { pod2 } , // only pod2 is registered because pod is already enqueued back.
2024-10-01 23:24:03 -04:00
wantInFlightEvents : [ ] interface { } { pod2 , nodeAdd } ,
2024-01-07 00:43:21 -05:00
queueingHintMap : QueueingHintMapPerProfile {
"" : {
2024-10-01 23:24:03 -04:00
pvAdd : {
2024-01-07 00:43:21 -05:00
{
PluginName : "fooPlugin1" ,
QueueingHintFn : queueHintReturnQueue ,
} ,
} ,
2024-10-01 23:24:03 -04:00
nodeAdd : {
2024-01-07 00:43:21 -05:00
{
PluginName : "fooPlugin1" ,
QueueingHintFn : queueHintReturnQueue ,
} ,
} ,
2024-10-01 23:24:03 -04:00
pvcAdd : {
2024-01-07 00:43:21 -05:00
{
PluginName : "fooPlugin1" ,
QueueingHintFn : queueHintReturnQueue ,
} ,
} ,
} ,
} ,
2023-07-17 18:53:07 -04:00
} ,
{
2023-09-04 09:48:45 -04:00
name : "All Pods registered in inFlightPods are enqueued back to activeQ" ,
2023-07-17 18:53:07 -04:00
isSchedulingQueueHintEnabled : true ,
2024-08-30 02:37:09 -04:00
initialPods : [ ] * v1 . Pod { pod1 , pod2 } ,
2023-07-17 18:53:07 -04:00
actions : [ ] action {
2023-09-04 09:48:45 -04:00
// This won't be added to inFlightEvents because no inFlightPods at this point.
2024-10-01 23:24:03 -04:00
{ eventHappens : & pvcAdd } ,
2024-08-30 02:37:09 -04:00
{ podPopped : pod1 } ,
2024-10-01 23:24:03 -04:00
{ eventHappens : & pvAdd } ,
2023-07-17 18:53:07 -04:00
{ podPopped : pod2 } ,
2024-10-01 23:24:03 -04:00
{ eventHappens : & nodeAdd } ,
2024-11-11 16:29:19 -05:00
// This pod will be requeued to backoffQ immediately because no plugin is registered as unschedulable plugin,
// which means the pod encountered an unexpected error (e.g., a network error).
2024-08-30 02:37:09 -04:00
{ podEnqueued : newQueuedPodInfoForLookup ( pod1 ) } ,
2024-10-01 23:24:03 -04:00
{ eventHappens : & csiNodeUpdate } ,
2024-11-11 16:29:19 -05:00
// This pod will be requeued to backoffQ immediately because no plugin is registered as unschedulable plugin,
// which means the pod encountered an unexpected error (e.g., a network error).
2023-07-17 18:53:07 -04:00
{ podEnqueued : newQueuedPodInfoForLookup ( pod2 ) } ,
} ,
wantBackoffQPodNames : [ ] string { "targetpod" , "targetpod2" } ,
2024-01-07 00:43:21 -05:00
wantInFlightPods : nil , // empty
queueingHintMap : QueueingHintMapPerProfile {
"" : {
2024-10-01 23:24:03 -04:00
pvAdd : {
2024-01-07 00:43:21 -05:00
{
PluginName : "fooPlugin1" ,
QueueingHintFn : queueHintReturnQueue ,
} ,
} ,
2024-10-01 23:24:03 -04:00
nodeAdd : {
2024-01-07 00:43:21 -05:00
{
PluginName : "fooPlugin1" ,
QueueingHintFn : queueHintReturnQueue ,
} ,
} ,
2024-10-01 23:24:03 -04:00
pvcAdd : {
2024-01-07 00:43:21 -05:00
{
PluginName : "fooPlugin1" ,
QueueingHintFn : queueHintReturnQueue ,
} ,
} ,
2024-10-01 23:24:03 -04:00
csiNodeUpdate : {
2024-01-07 00:43:21 -05:00
{
PluginName : "fooPlugin1" ,
QueueingHintFn : queueHintReturnQueue ,
} ,
} ,
} ,
} ,
2023-07-17 18:53:07 -04:00
} ,
{
2023-09-04 09:48:45 -04:00
name : "One intermediate Pod registered in inFlightPods is enqueued back to activeQ" ,
2023-07-17 18:53:07 -04:00
isSchedulingQueueHintEnabled : true ,
2024-08-30 02:37:09 -04:00
initialPods : [ ] * v1 . Pod { pod1 , pod2 , pod3 } ,
2023-07-17 18:53:07 -04:00
actions : [ ] action {
2023-09-04 09:48:45 -04:00
// This won't be added to inFlightEvents because no inFlightPods at this point.
2024-10-01 23:24:03 -04:00
{ eventHappens : & pvcAdd } ,
2024-08-30 02:37:09 -04:00
{ podPopped : pod1 } ,
2024-10-01 23:24:03 -04:00
{ eventHappens : & pvAdd } ,
2023-07-17 18:53:07 -04:00
{ podPopped : pod2 } ,
2024-10-01 23:24:03 -04:00
{ eventHappens : & nodeAdd } ,
2023-07-17 18:53:07 -04:00
// This Pod won't be requeued again.
{ podPopped : pod3 } ,
2024-10-01 23:24:03 -04:00
{ eventHappens : & framework . EventAssignedPodAdd } ,
2024-11-11 16:29:19 -05:00
// This pod will be requeued to backoffQ immediately because no plugin is registered as unschedulable plugin,
// which means the pod encountered an unexpected error (e.g., a network error).
2023-07-17 18:53:07 -04:00
{ podEnqueued : newQueuedPodInfoForLookup ( pod2 ) } ,
} ,
wantBackoffQPodNames : [ ] string { "targetpod2" } ,
2024-08-30 02:37:09 -04:00
wantInFlightPods : [ ] * v1 . Pod { pod1 , pod3 } ,
2024-10-01 23:24:03 -04:00
wantInFlightEvents : [ ] interface { } { pod1 , pvAdd , nodeAdd , pod3 , framework . EventAssignedPodAdd } ,
2024-01-07 00:43:21 -05:00
queueingHintMap : QueueingHintMapPerProfile {
"" : {
2024-10-01 23:24:03 -04:00
pvAdd : {
2024-01-07 00:43:21 -05:00
{
PluginName : "fooPlugin1" ,
QueueingHintFn : queueHintReturnQueue ,
} ,
} ,
2024-10-01 23:24:03 -04:00
nodeAdd : {
2024-01-07 00:43:21 -05:00
{
PluginName : "fooPlugin1" ,
QueueingHintFn : queueHintReturnQueue ,
} ,
} ,
2024-10-01 23:24:03 -04:00
framework . EventAssignedPodAdd : {
2024-01-07 00:43:21 -05:00
{
PluginName : "fooPlugin1" ,
QueueingHintFn : queueHintReturnQueue ,
} ,
} ,
} ,
} ,
2023-07-17 18:53:07 -04:00
} ,
{
2024-01-07 00:43:21 -05:00
name : "pod is enqueued to queue without QueueingHint when SchedulingQueueHint is disabled" ,
2024-08-30 02:37:09 -04:00
initialPods : [ ] * v1 . Pod { pod1 } ,
2024-01-07 00:43:21 -05:00
actions : [ ] action {
2024-08-30 02:37:09 -04:00
{ podPopped : pod1 } ,
2024-10-01 23:24:03 -04:00
{ eventHappens : & framework . EventAssignedPodAdd } ,
2024-08-30 02:37:09 -04:00
{ podEnqueued : newQueuedPodInfoForLookup ( pod1 , "fooPlugin1" ) } ,
2024-01-07 00:43:21 -05:00
} ,
wantBackoffQPodNames : [ ] string { "targetpod" } ,
wantInFlightPods : nil ,
wantInFlightEvents : nil ,
queueingHintMap : QueueingHintMapPerProfile {
"" : {
// This hint fn tells that this event doesn't make a Pod schedulable.
// However, this QueueingHintFn will be ignored actually because SchedulingQueueHint is disabled.
2024-10-01 23:24:03 -04:00
framework . EventAssignedPodAdd : {
2024-01-07 00:43:21 -05:00
{
PluginName : "fooPlugin1" ,
QueueingHintFn : queueHintReturnSkip ,
} ,
} ,
} ,
} ,
} ,
{
name : "events before popping Pod are ignored when Pod is enqueued back to queue" ,
2023-07-17 18:53:07 -04:00
isSchedulingQueueHintEnabled : true ,
2024-08-30 02:37:09 -04:00
initialPods : [ ] * v1 . Pod { pod1 } ,
2023-07-17 18:53:07 -04:00
actions : [ ] action {
2024-10-01 23:24:03 -04:00
{ eventHappens : & framework . EventUnschedulableTimeout } ,
2024-08-30 02:37:09 -04:00
{ podPopped : pod1 } ,
2024-10-01 23:24:03 -04:00
{ eventHappens : & framework . EventAssignedPodAdd } ,
2024-01-07 00:43:21 -05:00
// This Pod won't be requeued to activeQ/backoffQ because fooPlugin1 returns QueueSkip.
2024-08-30 02:37:09 -04:00
{ podEnqueued : newQueuedPodInfoForLookup ( pod1 , "fooPlugin1" ) } ,
2023-07-17 18:53:07 -04:00
} ,
wantUnschedPodPoolPodNames : [ ] string { "targetpod" } ,
2023-09-04 09:48:45 -04:00
wantInFlightPods : nil ,
wantInFlightEvents : nil ,
2023-07-17 18:53:07 -04:00
queueingHintMap : QueueingHintMapPerProfile {
"" : {
2024-07-18 22:40:35 -04:00
// fooPlugin1 has a queueing hint function for framework.AssignedPodAdd,
2023-07-17 18:53:07 -04:00
// but hint fn tells that this event doesn't make a Pod scheudlable.
2024-10-01 23:24:03 -04:00
framework . EventAssignedPodAdd : {
2023-07-17 18:53:07 -04:00
{
PluginName : "fooPlugin1" ,
2023-10-19 07:02:11 -04:00
QueueingHintFn : queueHintReturnSkip ,
2023-07-17 18:53:07 -04:00
} ,
} ,
} ,
} ,
} ,
{
name : "pod is enqueued to backoff if no failed plugin" ,
isSchedulingQueueHintEnabled : true ,
2024-08-30 02:37:09 -04:00
initialPods : [ ] * v1 . Pod { pod1 } ,
2023-07-17 18:53:07 -04:00
actions : [ ] action {
2024-08-30 02:37:09 -04:00
{ podPopped : pod1 } ,
2024-10-01 23:24:03 -04:00
{ eventHappens : & framework . EventAssignedPodAdd } ,
2024-11-11 16:29:19 -05:00
// This pod will be requeued to backoffQ immediately because no plugin is registered as unschedulable plugin,
// which means the pod encountered an unexpected error (e.g., a network error).
2024-08-30 02:37:09 -04:00
{ podEnqueued : newQueuedPodInfoForLookup ( pod1 ) } ,
2023-07-17 18:53:07 -04:00
} ,
wantBackoffQPodNames : [ ] string { "targetpod" } ,
2023-09-04 09:48:45 -04:00
wantInFlightPods : nil ,
wantInFlightEvents : nil ,
2023-07-17 18:53:07 -04:00
queueingHintMap : QueueingHintMapPerProfile {
"" : {
// It will be ignored because no failed plugin.
2024-10-01 23:24:03 -04:00
framework . EventAssignedPodAdd : {
2023-07-17 18:53:07 -04:00
{
PluginName : "fooPlugin1" ,
2023-10-19 07:02:11 -04:00
QueueingHintFn : queueHintReturnQueue ,
2023-07-17 18:53:07 -04:00
} ,
} ,
} ,
} ,
} ,
{
name : "pod is enqueued to unschedulable pod pool if no events that can make the pod schedulable" ,
isSchedulingQueueHintEnabled : true ,
2024-08-30 02:37:09 -04:00
initialPods : [ ] * v1 . Pod { pod1 } ,
2023-07-17 18:53:07 -04:00
actions : [ ] action {
2024-08-30 02:37:09 -04:00
{ podPopped : pod1 } ,
2024-10-01 23:24:03 -04:00
{ eventHappens : & nodeAdd } ,
2024-08-30 02:37:09 -04:00
{ podEnqueued : newQueuedPodInfoForLookup ( pod1 , "fooPlugin1" ) } ,
2023-07-17 18:53:07 -04:00
} ,
wantUnschedPodPoolPodNames : [ ] string { "targetpod" } ,
2023-09-04 09:48:45 -04:00
wantInFlightPods : nil ,
wantInFlightEvents : nil ,
2023-07-17 18:53:07 -04:00
queueingHintMap : QueueingHintMapPerProfile {
"" : {
2024-10-01 23:24:03 -04:00
// fooPlugin1 has no queueing hint function for NodeAdd.
framework . EventAssignedPodAdd : {
2023-07-17 18:53:07 -04:00
{
2024-10-01 23:24:03 -04:00
// It will be ignored because the event is not NodeAdd.
2023-07-17 18:53:07 -04:00
PluginName : "fooPlugin1" ,
2023-10-19 07:02:11 -04:00
QueueingHintFn : queueHintReturnQueue ,
2023-07-17 18:53:07 -04:00
} ,
} ,
} ,
} ,
} ,
{
2023-10-19 07:02:11 -04:00
name : "pod is enqueued to unschedulable pod pool because the failed plugin has a hint fn but it returns Skip" ,
2023-07-17 18:53:07 -04:00
isSchedulingQueueHintEnabled : true ,
2024-08-30 02:37:09 -04:00
initialPods : [ ] * v1 . Pod { pod1 } ,
2023-07-17 18:53:07 -04:00
actions : [ ] action {
2024-08-30 02:37:09 -04:00
{ podPopped : pod1 } ,
2024-10-01 23:24:03 -04:00
{ eventHappens : & framework . EventAssignedPodAdd } ,
2024-08-30 02:37:09 -04:00
{ podEnqueued : newQueuedPodInfoForLookup ( pod1 , "fooPlugin1" ) } ,
2023-07-17 18:53:07 -04:00
} ,
wantUnschedPodPoolPodNames : [ ] string { "targetpod" } ,
2023-09-04 09:48:45 -04:00
wantInFlightPods : nil ,
wantInFlightEvents : nil ,
2023-07-17 18:53:07 -04:00
queueingHintMap : QueueingHintMapPerProfile {
"" : {
2024-07-18 22:40:35 -04:00
// fooPlugin1 has a queueing hint function for framework.AssignedPodAdd,
2023-07-17 18:53:07 -04:00
// but hint fn tells that this event doesn't make a Pod scheudlable.
2024-10-01 23:24:03 -04:00
framework . EventAssignedPodAdd : {
2023-07-17 18:53:07 -04:00
{
PluginName : "fooPlugin1" ,
2023-10-19 07:02:11 -04:00
QueueingHintFn : queueHintReturnSkip ,
2023-07-17 18:53:07 -04:00
} ,
} ,
} ,
} ,
} ,
{
2023-10-19 07:02:11 -04:00
name : "pod is enqueued to activeQ because the Pending plugins has a hint fn and it returns Queue" ,
2023-07-17 18:53:07 -04:00
isSchedulingQueueHintEnabled : true ,
2024-08-30 02:37:09 -04:00
initialPods : [ ] * v1 . Pod { pod1 } ,
2023-07-17 18:53:07 -04:00
actions : [ ] action {
2024-08-30 02:37:09 -04:00
{ podPopped : pod1 } ,
2024-10-01 23:24:03 -04:00
{ eventHappens : & framework . EventAssignedPodAdd } ,
2023-10-19 07:02:11 -04:00
{ podEnqueued : & framework . QueuedPodInfo {
2024-08-30 02:37:09 -04:00
PodInfo : mustNewPodInfo ( pod1 ) ,
2023-10-19 07:02:11 -04:00
UnschedulablePlugins : sets . New ( "fooPlugin2" , "fooPlugin3" ) ,
PendingPlugins : sets . New ( "fooPlugin1" ) ,
} } ,
2023-07-17 18:53:07 -04:00
} ,
wantActiveQPodNames : [ ] string { "targetpod" } ,
2023-09-04 09:48:45 -04:00
wantInFlightPods : nil ,
wantInFlightEvents : nil ,
2023-07-17 18:53:07 -04:00
queueingHintMap : QueueingHintMapPerProfile {
"" : {
2024-10-01 23:24:03 -04:00
framework . EventAssignedPodAdd : {
2023-07-17 18:53:07 -04:00
{
PluginName : "fooPlugin3" ,
2023-10-19 07:02:11 -04:00
QueueingHintFn : queueHintReturnSkip ,
2023-07-17 18:53:07 -04:00
} ,
{
PluginName : "fooPlugin2" ,
2023-10-19 07:02:11 -04:00
QueueingHintFn : queueHintReturnQueue ,
2023-07-17 18:53:07 -04:00
} ,
{
// The hint fn tells that this event makes a Pod scheudlable immediately.
PluginName : "fooPlugin1" ,
2023-10-19 07:02:11 -04:00
QueueingHintFn : queueHintReturnQueue ,
2023-07-17 18:53:07 -04:00
} ,
} ,
} ,
} ,
} ,
{
2023-10-19 07:02:11 -04:00
name : "pod is enqueued to backoffQ because the failed plugin has a hint fn and it returns Queue" ,
2023-07-17 18:53:07 -04:00
isSchedulingQueueHintEnabled : true ,
2024-08-30 02:37:09 -04:00
initialPods : [ ] * v1 . Pod { pod1 } ,
2023-07-17 18:53:07 -04:00
actions : [ ] action {
2024-08-30 02:37:09 -04:00
{ podPopped : pod1 } ,
2024-10-01 23:24:03 -04:00
{ eventHappens : & framework . EventAssignedPodAdd } ,
2024-08-30 02:37:09 -04:00
{ podEnqueued : newQueuedPodInfoForLookup ( pod1 , "fooPlugin1" , "fooPlugin2" ) } ,
2023-07-17 18:53:07 -04:00
} ,
wantBackoffQPodNames : [ ] string { "targetpod" } ,
2023-09-04 09:48:45 -04:00
wantInFlightPods : nil ,
wantInFlightEvents : nil ,
2023-07-17 18:53:07 -04:00
queueingHintMap : QueueingHintMapPerProfile {
"" : {
2024-10-01 23:24:03 -04:00
framework . EventAssignedPodAdd : {
2023-07-17 18:53:07 -04:00
{
2023-10-19 07:02:11 -04:00
// it will be ignored because the hint fn returns Skip that is weaker than queueHintReturnQueue from fooPlugin1.
2023-07-17 18:53:07 -04:00
PluginName : "fooPlugin2" ,
2023-10-19 07:02:11 -04:00
QueueingHintFn : queueHintReturnSkip ,
2023-07-17 18:53:07 -04:00
} ,
{
2023-10-19 07:02:11 -04:00
// The hint fn tells that this event makes a Pod schedulable.
2023-07-17 18:53:07 -04:00
PluginName : "fooPlugin1" ,
2023-10-19 07:02:11 -04:00
QueueingHintFn : queueHintReturnQueue ,
2023-07-17 18:53:07 -04:00
} ,
} ,
} ,
} ,
} ,
2023-09-05 14:21:40 -04:00
{
2024-11-11 16:29:19 -05:00
name : "pod is enqueued to activeQ because the pending plugin has a hint fn and it returns Queue for a concurrent event that was received while some other pod was in flight" ,
2023-09-05 14:21:40 -04:00
isSchedulingQueueHintEnabled : true ,
2024-08-30 02:37:09 -04:00
initialPods : [ ] * v1 . Pod { pod1 , pod2 } ,
2023-09-05 14:21:40 -04:00
actions : [ ] action {
2024-08-30 02:37:09 -04:00
{ callback : func ( t * testing . T , q * PriorityQueue ) { poppedPod = popPod ( t , logger , q , pod1 ) } } ,
2024-10-01 23:24:03 -04:00
{ eventHappens : & nodeAdd } ,
2023-09-28 22:59:22 -04:00
{ callback : func ( t * testing . T , q * PriorityQueue ) { poppedPod2 = popPod ( t , logger , q , pod2 ) } } ,
2024-10-01 23:24:03 -04:00
{ eventHappens : & framework . EventAssignedPodAdd } ,
2023-09-05 14:21:40 -04:00
{ callback : func ( t * testing . T , q * PriorityQueue ) {
logger , _ := ktesting . NewTestContext ( t )
2024-11-11 16:29:19 -05:00
// This pod will be requeued to backoffQ immediately because no plugin is registered as unschedulable plugin,
// which means the pod encountered an unexpected error (e.g., a network error).
2023-09-11 12:30:11 -04:00
err := q . AddUnschedulableIfNotPresent ( logger , poppedPod , q . SchedulingCycle ( ) )
if err != nil {
t . Fatalf ( "unexpected error from AddUnschedulableIfNotPresent: %v" , err )
2023-09-05 14:21:40 -04:00
}
} } ,
{ callback : func ( t * testing . T , q * PriorityQueue ) {
logger , _ := ktesting . NewTestContext ( t )
2023-10-19 07:02:11 -04:00
poppedPod2 . UnschedulablePlugins = sets . New ( "fooPlugin2" , "fooPlugin3" )
poppedPod2 . PendingPlugins = sets . New ( "fooPlugin1" )
2023-09-11 12:30:11 -04:00
err := q . AddUnschedulableIfNotPresent ( logger , poppedPod2 , q . SchedulingCycle ( ) )
if err != nil {
t . Fatalf ( "unexpected error from AddUnschedulableIfNotPresent: %v" , err )
2023-09-05 14:21:40 -04:00
}
} } ,
} ,
2024-11-11 16:29:19 -05:00
wantActiveQPodNames : [ ] string { pod2 . Name } ,
wantBackoffQPodNames : [ ] string { pod1 . Name } ,
wantInFlightPods : nil ,
wantInFlightEvents : nil ,
2023-09-05 14:21:40 -04:00
queueingHintMap : QueueingHintMapPerProfile {
"" : {
2024-10-01 23:24:03 -04:00
framework . EventAssignedPodAdd : {
2023-09-05 14:21:40 -04:00
{
// it will be ignored because the hint fn returns QueueSkip that is weaker than queueHintReturnQueueImmediately from fooPlugin1.
PluginName : "fooPlugin3" ,
2023-10-19 07:02:11 -04:00
QueueingHintFn : queueHintReturnSkip ,
2023-09-05 14:21:40 -04:00
} ,
{
2023-10-19 07:02:11 -04:00
// it will be ignored because the fooPlugin2 is registered in UnschedulablePlugins and it's interpret as Queue that is weaker than QueueImmediately from fooPlugin1.
2023-09-05 14:21:40 -04:00
PluginName : "fooPlugin2" ,
2023-10-19 07:02:11 -04:00
QueueingHintFn : queueHintReturnQueue ,
2023-09-05 14:21:40 -04:00
} ,
{
2023-10-19 07:02:11 -04:00
// The hint fn tells that this event makes a Pod scheudlable.
// Given fooPlugin1 is registered as Pendings, we interpret Queue as queueImmediately.
2023-09-05 14:21:40 -04:00
PluginName : "fooPlugin1" ,
2023-10-19 07:02:11 -04:00
QueueingHintFn : queueHintReturnQueue ,
2023-09-05 14:21:40 -04:00
} ,
} ,
} ,
} ,
} ,
2023-09-01 01:55:31 -04:00
{
2025-11-24 14:32:41 -05:00
name : "popped pod preserves UnschedulablePlugins and PendingPlugins" ,
2023-09-01 01:55:31 -04:00
isSchedulingQueueHintEnabled : true ,
2024-08-30 02:37:09 -04:00
initialPods : [ ] * v1 . Pod { pod1 } ,
2023-09-01 01:55:31 -04:00
actions : [ ] action {
2024-08-30 02:37:09 -04:00
{ callback : func ( t * testing . T , q * PriorityQueue ) { poppedPod = popPod ( t , logger , q , pod1 ) } } ,
2023-09-01 01:55:31 -04:00
{ callback : func ( t * testing . T , q * PriorityQueue ) {
logger , _ := ktesting . NewTestContext ( t )
2023-10-19 07:02:11 -04:00
// Unschedulable due to PendingPlugins.
poppedPod . PendingPlugins = sets . New ( "fooPlugin1" )
poppedPod . UnschedulablePlugins = sets . New ( "fooPlugin2" )
2023-09-01 01:55:31 -04:00
if err := q . AddUnschedulableIfNotPresent ( logger , poppedPod , q . SchedulingCycle ( ) ) ; err != nil {
t . Errorf ( "Unexpected error from AddUnschedulableIfNotPresent: %v" , err )
}
} } ,
2024-10-01 23:24:03 -04:00
{ eventHappens : & pvAdd } , // Active again.
2023-09-01 01:55:31 -04:00
{ callback : func ( t * testing . T , q * PriorityQueue ) {
2024-08-30 02:37:09 -04:00
poppedPod = popPod ( t , logger , q , pod1 )
2025-11-14 13:57:24 -05:00
// UnschedulablePlugins should be preserved for logging/debugging
if ! poppedPod . UnschedulablePlugins . Equal ( sets . New ( "fooPlugin2" ) ) {
t . Errorf ( "QueuedPodInfo from Pop should preserve UnschedulablePlugins, expected fooPlugin2, got: %+v" , poppedPod . UnschedulablePlugins )
}
2025-11-24 14:32:41 -05:00
// PendingPlugins are preserved after Pop() for logging
if ! poppedPod . PendingPlugins . Equal ( sets . New ( "fooPlugin1" ) ) {
t . Errorf ( "QueuedPodInfo from Pop should preserve PendingPlugins, expected fooPlugin1, got: %+v" , poppedPod . PendingPlugins )
2023-09-01 01:55:31 -04:00
}
} } ,
{ callback : func ( t * testing . T , q * PriorityQueue ) {
logger , _ := ktesting . NewTestContext ( t )
// Failed (i.e. no UnschedulablePlugins). Should go to backoff.
if err := q . AddUnschedulableIfNotPresent ( logger , poppedPod , q . SchedulingCycle ( ) ) ; err != nil {
t . Errorf ( "Unexpected error from AddUnschedulableIfNotPresent: %v" , err )
}
} } ,
} ,
queueingHintMap : QueueingHintMapPerProfile {
"" : {
2024-10-01 23:24:03 -04:00
pvAdd : {
2023-09-01 01:55:31 -04:00
{
// The hint fn tells that this event makes a Pod scheudlable immediately.
PluginName : "fooPlugin1" ,
2023-10-19 07:02:11 -04:00
QueueingHintFn : queueHintReturnQueue ,
2023-09-01 01:55:31 -04:00
} ,
} ,
} ,
} ,
} ,
2024-08-30 02:37:09 -04:00
{
// This scenario shouldn't happen unless we make the similar bug like https://github.com/kubernetes/kubernetes/issues/118226.
// But, given the bug could make a serious memory leak and likely would be hard to detect,
// we should have a safe guard from the same bug so that, at least, we can prevent the memory leak.
name : "Pop is made twice for the same Pod, but the cleanup still happen correctly" ,
isSchedulingQueueHintEnabled : true ,
initialPods : [ ] * v1 . Pod { pod1 , pod2 } ,
actions : [ ] action {
// This won't be added to inFlightEvents because no inFlightPods at this point.
2024-10-01 23:24:03 -04:00
{ eventHappens : & pvcAdd } ,
2024-08-30 02:37:09 -04:00
{ podPopped : pod1 } ,
2024-10-01 23:24:03 -04:00
{ eventHappens : & pvAdd } ,
2024-08-30 02:37:09 -04:00
{ podPopped : pod2 } ,
// Simulate a bug, putting pod into activeQ, while pod is being scheduled.
{ callback : func ( t * testing . T , q * PriorityQueue ) {
q . activeQ . underLock ( func ( unlocked unlockedActiveQueuer ) {
2025-09-02 05:35:22 -04:00
unlocked . add ( logger , newQueuedPodInfoForLookup ( pod1 ) , framework . EventUnscheduledPodAdd . Label ( ) )
2024-08-30 02:37:09 -04:00
} )
} } ,
// At this point, in the activeQ, we have pod1 and pod3 in this order.
{ podCreated : pod3 } ,
// pod3 is poped, not pod1.
// In detail, this Pop() first tries to pop pod1, but it's already being scheduled and hence discarded.
// Then, it pops the next pod, pod3.
{ podPopped : pod3 } ,
{ callback : func ( t * testing . T , q * PriorityQueue ) {
// Make sure that pod1 is discarded and hence no pod in activeQ.
if len ( q . activeQ . list ( ) ) != 0 {
t . Fatalf ( "activeQ should be empty, but got: %v" , q . activeQ . list ( ) )
}
} } ,
2024-10-01 23:24:03 -04:00
{ eventHappens : & nodeAdd } ,
2024-11-11 16:29:19 -05:00
// This pod will be requeued to backoffQ immediately because no plugin is registered as unschedulable plugin,
// which means the pod encountered an unexpected error (e.g., a network error).
2024-08-30 02:37:09 -04:00
{ podEnqueued : newQueuedPodInfoForLookup ( pod1 ) } ,
2024-10-01 23:24:03 -04:00
{ eventHappens : & csiNodeUpdate } ,
2024-11-11 16:29:19 -05:00
// This pod will be requeued to backoffQ immediately because no plugin is registered as unschedulable plugin,
// which means the pod encountered an unexpected error (e.g., a network error).
2024-08-30 02:37:09 -04:00
{ podEnqueued : newQueuedPodInfoForLookup ( pod2 ) } ,
2024-11-11 16:29:19 -05:00
// This pod will be requeued to backoffQ immediately because no plugin is registered as unschedulable plugin,
// which means the pod encountered an unexpected error (e.g., a network error).
2024-08-30 02:37:09 -04:00
{ podEnqueued : newQueuedPodInfoForLookup ( pod3 ) } ,
} ,
wantBackoffQPodNames : [ ] string { "targetpod" , "targetpod2" , "targetpod3" } ,
wantInFlightPods : nil , // should be empty
queueingHintMap : QueueingHintMapPerProfile {
"" : {
2024-10-01 23:24:03 -04:00
pvAdd : {
2024-08-30 02:37:09 -04:00
{
PluginName : "fooPlugin1" ,
QueueingHintFn : queueHintReturnQueue ,
} ,
} ,
2024-10-01 23:24:03 -04:00
nodeAdd : {
2024-08-30 02:37:09 -04:00
{
PluginName : "fooPlugin1" ,
QueueingHintFn : queueHintReturnQueue ,
} ,
} ,
2024-10-01 23:24:03 -04:00
pvcAdd : {
2024-08-30 02:37:09 -04:00
{
PluginName : "fooPlugin1" ,
QueueingHintFn : queueHintReturnQueue ,
} ,
} ,
2024-10-01 23:24:03 -04:00
csiNodeUpdate : {
2024-08-30 02:37:09 -04:00
{
PluginName : "fooPlugin1" ,
QueueingHintFn : queueHintReturnQueue ,
} ,
} ,
} ,
} ,
} ,
2023-07-17 18:53:07 -04:00
}
for _ , test := range tests {
t . Run ( test . name , func ( t * testing . T ) {
2025-05-26 13:56:10 -04:00
if ! test . isSchedulingQueueHintEnabled {
featuregatetesting . SetFeatureGateEmulationVersionDuringTest ( t , utilfeature . DefaultFeatureGate , version . MustParse ( "1.33" ) )
featuregatetesting . SetFeatureGateDuringTest ( t , utilfeature . DefaultFeatureGate , features . SchedulerQueueingHints , false )
}
2023-07-17 18:53:07 -04:00
logger , ctx := ktesting . NewTestContext ( t )
ctx , cancel := context . WithCancel ( ctx )
defer cancel ( )
obj := make ( [ ] runtime . Object , 0 , len ( test . initialPods ) )
for _ , p := range test . initialPods {
obj = append ( obj , p )
}
2023-07-19 08:22:18 -04:00
fakeClock := testingclock . NewFakeClock ( time . Now ( ) )
q := NewTestQueueWithObjects ( ctx , newDefaultQueueSort ( ) , obj , WithQueueingHintMapPerProfile ( test . queueingHintMap ) , WithClock ( fakeClock ) )
2024-08-19 21:07:48 -04:00
sortOpt := cmpopts . SortSlices ( func ( a , b string ) bool { return a < b } )
2023-07-17 18:53:07 -04:00
2023-07-19 08:22:18 -04:00
// When a Pod is added to the queue, the QueuedPodInfo will have a new timestamp.
// On Windows, time.Now() is not as precise, 2 consecutive calls may return the same timestamp.
// Thus, all the QueuedPodInfos can have the same timestamps, which can be an issue
// when we're expecting them to be popped in a certain order (the Less function
// sorts them by Timestamps if they have the same Pod Priority).
// Using a fake clock for the queue and incrementing it after each added Pod will
// solve this issue on Windows unit test runs.
// For more details on the Windows clock resolution issue, see: https://github.com/golang/go/issues/8687
2023-07-17 18:53:07 -04:00
for _ , p := range test . initialPods {
q . Add ( logger , p )
2023-07-19 08:22:18 -04:00
fakeClock . Step ( time . Second )
2023-07-17 18:53:07 -04:00
}
for _ , action := range test . actions {
switch {
2024-08-30 02:37:09 -04:00
case action . podCreated != nil :
q . Add ( logger , action . podCreated )
2023-07-17 18:53:07 -04:00
case action . podPopped != nil :
2023-09-28 22:59:22 -04:00
popPod ( t , logger , q , action . podPopped )
2023-07-17 18:53:07 -04:00
case action . eventHappens != nil :
q . MoveAllToActiveOrBackoffQueue ( logger , * action . eventHappens , nil , nil , nil )
case action . podEnqueued != nil :
2024-11-11 16:29:19 -05:00
err := q . AddUnschedulableIfNotPresent ( logger , action . podEnqueued , q . SchedulingCycle ( ) )
2023-09-11 12:30:11 -04:00
if err != nil {
t . Fatalf ( "unexpected error from AddUnschedulableIfNotPresent: %v" , err )
}
2023-09-05 14:21:40 -04:00
case action . callback != nil :
action . callback ( t , q )
2023-07-17 18:53:07 -04:00
}
}
2023-09-04 09:48:45 -04:00
actualInFlightPods := make ( map [ types . UID ] * v1 . Pod )
2024-07-22 06:13:13 -04:00
for _ , pod := range q . activeQ . listInFlightPods ( ) {
actualInFlightPods [ pod . UID ] = pod
2023-09-04 09:48:45 -04:00
}
wantInFlightPods := make ( map [ types . UID ] * v1 . Pod )
for _ , pod := range test . wantInFlightPods {
wantInFlightPods [ pod . UID ] = pod
}
if diff := cmp . Diff ( wantInFlightPods , actualInFlightPods ) ; diff != "" {
2023-07-17 18:53:07 -04:00
t . Errorf ( "Unexpected diff in inFlightPods (-want, +got):\n%s" , diff )
}
2023-09-04 09:48:45 -04:00
var wantInFlightEvents [ ] interface { }
for _ , value := range test . wantInFlightEvents {
2025-06-26 11:06:29 -04:00
if event , ok := value . ( fwk . ClusterEvent ) ; ok {
2023-09-04 09:48:45 -04:00
value = & clusterEvent { event : event }
}
wantInFlightEvents = append ( wantInFlightEvents , value )
}
2025-06-26 11:06:29 -04:00
if diff := cmp . Diff ( wantInFlightEvents , q . activeQ . listInFlightEvents ( ) , cmp . AllowUnexported ( clusterEvent { } ) , cmpopts . EquateComparable ( fwk . ClusterEvent { } ) ) ; diff != "" {
2023-09-04 09:48:45 -04:00
t . Errorf ( "Unexpected diff in inFlightEvents (-want, +got):\n%s" , diff )
2023-07-17 18:53:07 -04:00
}
if test . wantActiveQPodNames != nil {
2024-07-22 06:13:13 -04:00
pods := q . activeQ . list ( )
var podNames [ ] string
for _ , pod := range pods {
podNames = append ( podNames , pod . Name )
}
2024-08-19 21:07:48 -04:00
if diff := cmp . Diff ( test . wantActiveQPodNames , podNames , sortOpt ) ; diff != "" {
2024-07-22 06:13:13 -04:00
t . Fatalf ( "Unexpected diff of activeQ pod names (-want, +got):\n%s" , diff )
2023-07-17 18:53:07 -04:00
}
wantPodNames := sets . New ( test . wantActiveQPodNames ... )
2024-07-22 06:13:13 -04:00
for _ , pod := range pods {
if ! wantPodNames . Has ( pod . Name ) {
t . Fatalf ( "Pod %v was not expected to be in the activeQ." , pod . Name )
2023-07-17 18:53:07 -04:00
}
}
}
if test . wantBackoffQPodNames != nil {
2025-03-07 05:22:36 -05:00
pods := q . backoffQ . list ( )
2024-07-22 06:13:13 -04:00
var podNames [ ] string
2025-03-07 05:22:36 -05:00
for _ , pod := range pods {
podNames = append ( podNames , pod . Name )
2024-07-22 06:13:13 -04:00
}
2024-08-19 21:07:48 -04:00
if diff := cmp . Diff ( test . wantBackoffQPodNames , podNames , sortOpt ) ; diff != "" {
2024-07-22 06:13:13 -04:00
t . Fatalf ( "Unexpected diff of backoffQ pod names (-want, +got):\n%s" , diff )
2023-07-17 18:53:07 -04:00
}
wantPodNames := sets . New ( test . wantBackoffQPodNames ... )
2025-03-07 05:22:36 -05:00
for _ , podGotFromBackoffQ := range pods {
2023-07-17 18:53:07 -04:00
if ! wantPodNames . Has ( podGotFromBackoffQ . Name ) {
t . Fatalf ( "Pod %v was not expected to be in the backoffQ." , podGotFromBackoffQ . Name )
}
}
}
for _ , podName := range test . wantUnschedPodPoolPodNames {
p := getUnschedulablePod ( q , & st . MakePod ( ) . Name ( podName ) . Pod )
if p == nil {
t . Fatalf ( "Pod %v was not found in the unschedulablePods." , podName )
}
}
} )
}
}
2023-09-28 22:59:22 -04:00
func popPod ( t * testing . T , logger klog . Logger , q * PriorityQueue , pod * v1 . Pod ) * framework . QueuedPodInfo {
p , err := q . Pop ( logger )
2023-09-01 01:55:31 -04:00
if err != nil {
t . Fatalf ( "Pop failed: %v" , err )
}
if p . Pod . UID != pod . UID {
t . Errorf ( "Unexpected popped pod: %v" , p )
}
return p
}
func TestPop ( t * testing . T ) {
pod := st . MakePod ( ) . Name ( "targetpod" ) . UID ( "pod1" ) . Obj ( )
queueingHintMap := QueueingHintMapPerProfile {
"" : {
2024-10-01 23:24:03 -04:00
pvAdd : {
2023-09-01 01:55:31 -04:00
{
2023-10-19 07:02:11 -04:00
// The hint fn tells that this event makes a Pod scheudlable.
2023-09-01 01:55:31 -04:00
PluginName : "fooPlugin1" ,
2023-10-19 07:02:11 -04:00
QueueingHintFn : queueHintReturnQueue ,
2023-09-01 01:55:31 -04:00
} ,
} ,
} ,
}
for name , isSchedulingQueueHintEnabled := range map [ string ] bool { "with-hints" : true , "without-hints" : false } {
t . Run ( name , func ( t * testing . T ) {
2025-05-26 13:56:10 -04:00
if ! isSchedulingQueueHintEnabled {
featuregatetesting . SetFeatureGateEmulationVersionDuringTest ( t , utilfeature . DefaultFeatureGate , version . MustParse ( "1.33" ) )
featuregatetesting . SetFeatureGateDuringTest ( t , utilfeature . DefaultFeatureGate , features . SchedulerQueueingHints , false )
}
2023-09-01 01:55:31 -04:00
logger , ctx := ktesting . NewTestContext ( t )
ctx , cancel := context . WithCancel ( ctx )
defer cancel ( )
q := NewTestQueueWithObjects ( ctx , newDefaultQueueSort ( ) , [ ] runtime . Object { pod } , WithQueueingHintMapPerProfile ( queueingHintMap ) )
q . Add ( logger , pod )
// Simulate failed attempt that makes the pod unschedulable.
2023-09-28 22:59:22 -04:00
poppedPod := popPod ( t , logger , q , pod )
2023-10-19 07:02:11 -04:00
// We put register the plugin to PendingPlugins so that it's interpreted as queueImmediately and skip backoff.
poppedPod . PendingPlugins = sets . New ( "fooPlugin1" )
2023-09-01 01:55:31 -04:00
if err := q . AddUnschedulableIfNotPresent ( logger , poppedPod , q . SchedulingCycle ( ) ) ; err != nil {
t . Errorf ( "Unexpected error from AddUnschedulableIfNotPresent: %v" , err )
}
// Activate it again.
2024-10-01 23:24:03 -04:00
q . MoveAllToActiveOrBackoffQueue ( logger , pvAdd , nil , nil , nil )
2023-09-01 01:55:31 -04:00
// Now check result of Pop.
2023-09-28 22:59:22 -04:00
poppedPod = popPod ( t , logger , q , pod )
2025-11-24 14:32:41 -05:00
// PendingPlugins are preserved after Pop() so they can be logged if scheduling
// succeeds, or cleared in handleSchedulingFailure() if it fails.
2025-11-26 08:08:05 -05:00
if ! poppedPod . PendingPlugins . Equal ( sets . New ( "fooPlugin1" ) ) {
2025-11-24 14:32:41 -05:00
t . Errorf ( "QueuedPodInfo from Pop should preserve PendingPlugins, expected fooPlugin1, got instead: %+v" , poppedPod )
2023-09-01 01:55:31 -04:00
}
} )
}
}
2018-02-08 21:19:31 -05:00
func TestPriorityQueue_AddUnschedulableIfNotPresent ( t * testing . T ) {
2021-06-05 14:30:17 -04:00
objs := [ ] runtime . Object { highPriNominatedPodInfo . Pod , unschedulablePodInfo . Pod }
2023-03-22 07:48:04 -04:00
logger , ctx := ktesting . NewTestContext ( t )
ctx , cancel := context . WithCancel ( ctx )
2022-01-22 14:00:56 -05:00
defer cancel ( )
q := NewTestQueueWithObjects ( ctx , newDefaultQueueSort ( ) , objs )
2023-07-17 18:53:07 -04:00
// insert unschedulablePodInfo and pop right after that
// because the scheduling queue records unschedulablePod as in-flight Pod.
q . Add ( logger , unschedulablePodInfo . Pod )
2023-09-28 22:59:22 -04:00
if p , err := q . Pop ( logger ) ; err != nil || p . Pod != unschedulablePodInfo . Pod {
2023-07-17 18:53:07 -04:00
t . Errorf ( "Expected: %v after Pop, but got: %v" , unschedulablePodInfo . Pod . Name , p . Pod . Name )
}
2023-03-22 07:48:04 -04:00
q . Add ( logger , highPriNominatedPodInfo . Pod )
2023-09-11 12:30:11 -04:00
err := q . AddUnschedulableIfNotPresent ( logger , newQueuedPodInfoForLookup ( unschedulablePodInfo . Pod , "plugin" ) , q . SchedulingCycle ( ) )
if err != nil {
t . Fatalf ( "unexpected error from AddUnschedulableIfNotPresent: %v" , err )
}
2023-09-28 22:59:22 -04:00
if p , err := q . Pop ( logger ) ; err != nil || p . Pod != highPriNominatedPodInfo . Pod {
2021-02-22 09:00:23 -05:00
t . Errorf ( "Expected: %v after Pop, but got: %v" , highPriNominatedPodInfo . Pod . Name , p . Pod . Name )
2018-02-08 21:19:31 -05:00
}
2023-03-08 16:18:36 -05:00
if len ( q . nominator . nominatedPods ) != 1 {
2024-07-01 07:37:53 -04:00
t . Errorf ( "Expected nominatedPods to have one element: %v" , q . nominator )
2018-02-08 21:19:31 -05:00
}
2023-07-17 18:53:07 -04:00
// unschedulablePodInfo is inserted to unschedulable pod pool because no events happened during scheduling.
2021-02-22 09:00:23 -05:00
if getUnschedulablePod ( q , unschedulablePodInfo . Pod ) != unschedulablePodInfo . Pod {
2022-03-24 05:38:49 -04:00
t . Errorf ( "Pod %v was not found in the unschedulablePods." , unschedulablePodInfo . Pod . Name )
2018-02-08 21:19:31 -05:00
}
2017-10-24 14:14:29 -04:00
}
2019-07-01 20:29:34 -04:00
// TestPriorityQueue_AddUnschedulableIfNotPresent_Backoff tests the scenarios when
// AddUnschedulableIfNotPresent is called asynchronously.
// Pods in and before current scheduling cycle will be put back to activeQueue
// if we were trying to schedule them when we received move request.
2019-02-15 20:43:08 -05:00
func TestPriorityQueue_AddUnschedulableIfNotPresent_Backoff ( t * testing . T ) {
2023-03-22 07:48:04 -04:00
logger , ctx := ktesting . NewTestContext ( t )
ctx , cancel := context . WithCancel ( ctx )
2022-01-22 14:00:56 -05:00
defer cancel ( )
q := NewTestQueue ( ctx , newDefaultQueueSort ( ) , WithClock ( testingclock . NewFakeClock ( time . Now ( ) ) ) )
2019-01-25 05:39:53 -05:00
totalNum := 10
expectedPods := make ( [ ] v1 . Pod , 0 , totalNum )
for i := 0 ; i < totalNum ; i ++ {
priority := int32 ( i )
2022-04-18 21:06:58 -04:00
p := st . MakePod ( ) . Name ( fmt . Sprintf ( "pod%d" , i ) ) . Namespace ( fmt . Sprintf ( "ns%d" , i ) ) . UID ( fmt . Sprintf ( "upns%d" , i ) ) . Priority ( priority ) . Obj ( )
expectedPods = append ( expectedPods , * p )
2019-01-25 05:39:53 -05:00
// priority is to make pods ordered in the PriorityQueue
2023-03-22 07:48:04 -04:00
q . Add ( logger , p )
2019-01-25 05:39:53 -05:00
}
// Pop all pods except for the first one
for i := totalNum - 1 ; i > 0 ; i -- {
2023-09-28 22:59:22 -04:00
p , _ := q . Pop ( logger )
2023-06-29 03:28:42 -04:00
if diff := cmp . Diff ( & expectedPods [ i ] , p . Pod ) ; diff != "" {
t . Errorf ( "Unexpected pod (-want, +got):\n%s" , diff )
2019-01-25 05:39:53 -05:00
}
}
// move all pods to active queue when we were trying to schedule them
2024-10-01 23:24:03 -04:00
q . MoveAllToActiveOrBackoffQueue ( logger , framework . EventUnschedulableTimeout , nil , nil , nil )
2019-02-15 20:43:08 -05:00
oldCycle := q . SchedulingCycle ( )
2023-09-28 22:59:22 -04:00
firstPod , _ := q . Pop ( logger )
2023-06-29 03:28:42 -04:00
if diff := cmp . Diff ( & expectedPods [ 0 ] , firstPod . Pod ) ; diff != "" {
t . Errorf ( "Unexpected pod (-want, +got):\n%s" , diff )
2019-02-15 20:43:08 -05:00
}
// mark pods[1] ~ pods[totalNum-1] as unschedulable and add them back
2019-01-25 05:39:53 -05:00
for i := 1 ; i < totalNum ; i ++ {
unschedulablePod := expectedPods [ i ] . DeepCopy ( )
unschedulablePod . Status = v1 . PodStatus {
Conditions : [ ] v1 . PodCondition {
{
Type : v1 . PodScheduled ,
Status : v1 . ConditionFalse ,
Reason : v1 . PodReasonUnschedulable ,
} ,
} ,
}
2019-02-15 20:43:08 -05:00
2024-11-11 16:29:19 -05:00
err := q . AddUnschedulableIfNotPresent ( logger , newQueuedPodInfoForLookup ( unschedulablePod , "plugin" ) , oldCycle )
2023-09-11 12:30:11 -04:00
if err != nil {
t . Fatalf ( "unexpected error from AddUnschedulableIfNotPresent: %v" , err )
2019-07-01 20:29:34 -04:00
}
2019-01-25 05:39:53 -05:00
}
2019-02-15 20:43:08 -05:00
// Since there was a move request at the same cycle as "oldCycle", these pods
// should be in the backoff queue.
2019-01-25 05:39:53 -05:00
for i := 1 ; i < totalNum ; i ++ {
2025-02-18 03:23:37 -05:00
if ! q . backoffQ . has ( newQueuedPodInfoForLookup ( & expectedPods [ i ] ) ) {
t . Errorf ( "Expected %v to be added to backoffQ." , expectedPods [ i ] . Name )
2019-01-25 05:39:53 -05:00
}
}
}
2025-03-10 04:56:21 -04:00
// tryPop tries to pop one pod from the queue and returns it.
// It waits 5 seconds before timing out, assuming the queue is then empty.
func tryPop ( t * testing . T , logger klog . Logger , q * PriorityQueue ) * framework . QueuedPodInfo {
t . Helper ( )
var gotPod * framework . QueuedPodInfo
popped := make ( chan struct { } , 1 )
2017-10-24 14:14:29 -04:00
go func ( ) {
2025-03-10 04:56:21 -04:00
pod , err := q . Pop ( logger )
if err != nil {
t . Errorf ( "Failed to pop pod from scheduling queue: %s" , err )
2018-02-08 21:19:31 -05:00
}
2025-03-10 04:56:21 -04:00
if pod != nil {
gotPod = pod
2017-10-24 14:14:29 -04:00
}
2025-03-10 04:56:21 -04:00
popped <- struct { } { }
2017-10-24 14:14:29 -04:00
} ( )
2025-03-10 04:56:21 -04:00
timer := time . NewTimer ( 5 * time . Second )
select {
case <- timer . C :
q . Close ( )
case <- popped :
timer . Stop ( )
}
return gotPod
}
func TestPriorityQueue_Pop ( t * testing . T ) {
highPriorityPodInfo2 := mustNewPodInfo (
st . MakePod ( ) . Name ( "hpp2" ) . Namespace ( "ns1" ) . UID ( "hpp2ns1" ) . Priority ( highPriority ) . Obj ( ) ,
)
objs := [ ] runtime . Object { medPriorityPodInfo . Pod , highPriorityPodInfo . Pod , highPriorityPodInfo2 . Pod , unschedulablePodInfo . Pod }
tests := [ ] struct {
name string
popFromBackoffQEnabled bool
wantPods [ ] string
} {
{
name : "Pop pods from both activeQ and backoffQ when PopFromBackoffQ is enabled" ,
popFromBackoffQEnabled : true ,
wantPods : [ ] string { medPriorityPodInfo . Pod . Name , highPriorityPodInfo . Pod . Name } ,
} ,
{
name : "Pop pod only from activeQ when PopFromBackoffQ is disabled" ,
popFromBackoffQEnabled : false ,
wantPods : [ ] string { medPriorityPodInfo . Pod . Name } ,
} ,
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
featuregatetesting . SetFeatureGateDuringTest ( t , utilfeature . DefaultFeatureGate , features . SchedulerPopFromBackoffQ , tt . popFromBackoffQEnabled )
logger , ctx := ktesting . NewTestContext ( t )
ctx , cancel := context . WithCancel ( ctx )
defer cancel ( )
q := NewTestQueueWithObjects ( ctx , newDefaultQueueSort ( ) , objs )
// Add medium priority pod to the activeQ
q . Add ( logger , medPriorityPodInfo . Pod )
// Add high priority pod to the backoffQ
backoffPodInfo := q . newQueuedPodInfo ( highPriorityPodInfo . Pod , "plugin" )
q . backoffQ . add ( logger , backoffPodInfo , framework . EventUnscheduledPodAdd . Label ( ) )
// Add high priority pod to the errorBackoffQ
errorBackoffPodInfo := q . newQueuedPodInfo ( highPriorityPodInfo2 . Pod )
q . backoffQ . add ( logger , errorBackoffPodInfo , framework . EventUnscheduledPodAdd . Label ( ) )
// Add pod to the unschedulablePods
unschedulablePodInfo := q . newQueuedPodInfo ( unschedulablePodInfo . Pod , "plugin" )
2025-11-20 06:21:44 -05:00
q . unschedulablePods . addOrUpdate ( unschedulablePodInfo , false , framework . EventUnscheduledPodAdd . Label ( ) )
2025-03-10 04:56:21 -04:00
var gotPods [ ] string
for i := 0 ; i < len ( tt . wantPods ) + 1 ; i ++ {
gotPod := tryPop ( t , logger , q )
if gotPod == nil {
break
}
gotPods = append ( gotPods , gotPod . Pod . Name )
}
if diff := cmp . Diff ( tt . wantPods , gotPods ) ; diff != "" {
t . Errorf ( "Unexpected popped pods (-want, +got): %s" , diff )
}
} )
}
2017-10-24 14:14:29 -04:00
}
func TestPriorityQueue_Update ( t * testing . T ) {
2021-09-17 05:48:22 -04:00
c := testingclock . NewFakeClock ( time . Now ( ) )
2021-02-06 04:08:16 -05:00
2023-12-08 03:12:13 -05:00
queuePlugin := "queuePlugin"
skipPlugin := "skipPlugin"
queueingHintMap := QueueingHintMapPerProfile {
"" : {
2024-10-01 23:24:03 -04:00
framework . EventUnscheduledPodUpdate : {
2023-12-08 03:12:13 -05:00
{
PluginName : queuePlugin ,
QueueingHintFn : queueHintReturnQueue ,
} ,
{
PluginName : skipPlugin ,
QueueingHintFn : queueHintReturnSkip ,
} ,
} ,
} ,
}
2024-06-18 23:25:32 -04:00
notInAnyQueue := "NotInAnyQueue"
2023-12-29 01:50:21 -05:00
tests := [ ] struct {
name string
wantQ string
// wantAddedToNominated is whether a Pod from the test case should be registered as a nominated Pod in the nominator.
wantAddedToNominated bool
// prepareFunc is the function called to prepare pods in the queue before the test case calls Update().
// This function returns three values;
// - oldPod/newPod: each test will call Update() with these oldPod and newPod.
prepareFunc func ( t * testing . T , logger klog . Logger , q * PriorityQueue ) ( oldPod , newPod * v1 . Pod )
2023-12-08 03:12:13 -05:00
// schedulingHintsEnablement shows which value of QHint feature gate we test a test case with.
schedulingHintsEnablement [ ] bool
2023-12-29 01:50:21 -05:00
} {
{
name : "Update pod that didn't exist in the queue" ,
wantQ : activeQ ,
prepareFunc : func ( t * testing . T , logger klog . Logger , q * PriorityQueue ) ( oldPod , newPod * v1 . Pod ) {
updatedPod := medPriorityPodInfo . Pod . DeepCopy ( )
updatedPod . Annotations [ "foo" ] = "test"
return medPriorityPodInfo . Pod , updatedPod
} ,
2023-12-08 03:12:13 -05:00
schedulingHintsEnablement : [ ] bool { false , true } ,
2023-12-29 01:50:21 -05:00
} ,
{
name : "Update highPriorityPodInfo and add a nominatedNodeName to it" ,
wantQ : activeQ ,
wantAddedToNominated : true ,
prepareFunc : func ( t * testing . T , logger klog . Logger , q * PriorityQueue ) ( oldPod , newPod * v1 . Pod ) {
return highPriorityPodInfo . Pod , highPriNominatedPodInfo . Pod
} ,
2023-12-08 03:12:13 -05:00
schedulingHintsEnablement : [ ] bool { false , true } ,
2023-12-29 01:50:21 -05:00
} ,
{
name : "When updating a pod that is already in activeQ, the pod should remain in activeQ after Update()" ,
wantQ : activeQ ,
prepareFunc : func ( t * testing . T , logger klog . Logger , q * PriorityQueue ) ( oldPod , newPod * v1 . Pod ) {
2024-10-01 23:24:03 -04:00
q . Add ( logger , highPriorityPodInfo . Pod )
2023-12-29 01:50:21 -05:00
return highPriorityPodInfo . Pod , highPriorityPodInfo . Pod
} ,
2023-12-08 03:12:13 -05:00
schedulingHintsEnablement : [ ] bool { false , true } ,
2023-12-29 01:50:21 -05:00
} ,
{
name : "When updating a pod that is in backoff queue and is still backing off, it will be updated in backoff queue" ,
wantQ : backoffQ ,
prepareFunc : func ( t * testing . T , logger klog . Logger , q * PriorityQueue ) ( oldPod , newPod * v1 . Pod ) {
podInfo := q . newQueuedPodInfo ( medPriorityPodInfo . Pod )
2025-02-25 08:05:33 -05:00
q . backoffQ . add ( logger , podInfo , framework . EventUnscheduledPodAdd . Label ( ) )
2023-12-29 01:50:21 -05:00
return podInfo . Pod , podInfo . Pod
} ,
2023-12-08 03:12:13 -05:00
schedulingHintsEnablement : [ ] bool { false , true } ,
2023-12-29 01:50:21 -05:00
} ,
{
2025-11-20 06:21:44 -05:00
name : "when updating a pod in unschedulablePods, if its backoff timer has not yet expired, it moves to backoffQ" ,
2023-12-29 01:50:21 -05:00
wantQ : backoffQ ,
prepareFunc : func ( t * testing . T , logger klog . Logger , q * PriorityQueue ) ( oldPod , newPod * v1 . Pod ) {
2024-11-11 16:29:19 -05:00
pInfo := q . newQueuedPodInfo ( medPriorityPodInfo . Pod , queuePlugin )
// needs to increment to make the pod backing off
pInfo . UnschedulableCount ++
2025-11-20 06:21:44 -05:00
q . unschedulablePods . addOrUpdate ( pInfo , false , framework . EventUnscheduledPodAdd . Label ( ) )
2023-12-29 01:50:21 -05:00
updatedPod := medPriorityPodInfo . Pod . DeepCopy ( )
updatedPod . Annotations [ "foo" ] = "test"
return medPriorityPodInfo . Pod , updatedPod
} ,
2023-12-08 03:12:13 -05:00
schedulingHintsEnablement : [ ] bool { false , true } ,
2023-12-29 01:50:21 -05:00
} ,
{
2025-11-20 06:21:44 -05:00
name : "when updating a pod in unschedulablePods, if its backoff timer has expired, it moves to activeQ" ,
2023-12-29 01:50:21 -05:00
wantQ : activeQ ,
prepareFunc : func ( t * testing . T , logger klog . Logger , q * PriorityQueue ) ( oldPod , newPod * v1 . Pod ) {
2024-11-11 16:29:19 -05:00
pInfo := q . newQueuedPodInfo ( medPriorityPodInfo . Pod , queuePlugin )
// needs to increment to make the pod backing off
pInfo . UnschedulableCount ++
2025-11-20 06:21:44 -05:00
q . unschedulablePods . addOrUpdate ( pInfo , false , framework . EventUnscheduledPodAdd . Label ( ) )
2023-12-29 01:50:21 -05:00
updatedPod := medPriorityPodInfo . Pod . DeepCopy ( )
updatedPod . Annotations [ "foo" ] = "test1"
2025-03-07 05:22:36 -05:00
// Move clock by podMaxBackoffDuration, so that pods in the unschedulablePods would pass the backing off,
2023-12-29 01:50:21 -05:00
// and the pods will be moved into activeQ.
2025-03-07 05:22:36 -05:00
c . Step ( q . backoffQ . podMaxBackoffDuration ( ) )
2023-12-29 01:50:21 -05:00
return medPriorityPodInfo . Pod , updatedPod
} ,
2023-12-08 03:12:13 -05:00
schedulingHintsEnablement : [ ] bool { false , true } ,
} ,
{
2025-11-20 06:21:44 -05:00
name : "when updating a pod in unschedulablePods, if the scheduling hint returns QueueSkip, it remains in unschedulablePods" ,
2025-07-04 07:17:24 -04:00
wantQ : unschedulableQ ,
2023-12-08 03:12:13 -05:00
prepareFunc : func ( t * testing . T , logger klog . Logger , q * PriorityQueue ) ( oldPod , newPod * v1 . Pod ) {
2025-11-20 06:21:44 -05:00
q . unschedulablePods . addOrUpdate ( q . newQueuedPodInfo ( medPriorityPodInfo . Pod , skipPlugin ) , false , framework . EventUnscheduledPodAdd . Label ( ) )
2023-12-08 03:12:13 -05:00
updatedPod := medPriorityPodInfo . Pod . DeepCopy ( )
updatedPod . Annotations [ "foo" ] = "test1"
return medPriorityPodInfo . Pod , updatedPod
} ,
schedulingHintsEnablement : [ ] bool { true } ,
2023-12-29 01:50:21 -05:00
} ,
2024-06-18 23:25:32 -04:00
{
name : "when updating a pod which is in flightPods, the pod will not be added to any queue" ,
wantQ : notInAnyQueue ,
prepareFunc : func ( t * testing . T , logger klog . Logger , q * PriorityQueue ) ( oldPod , newPod * v1 . Pod ) {
// We need to once add this Pod to activeQ and Pop() it so that this Pod is registered correctly in inFlightPods.
2024-08-21 09:40:41 -04:00
q . Add ( logger , medPriorityPodInfo . Pod )
2024-06-18 23:25:32 -04:00
if p , err := q . Pop ( logger ) ; err != nil || p . Pod != medPriorityPodInfo . Pod {
t . Errorf ( "Expected: %v after Pop, but got: %v" , medPriorityPodInfo . Pod . Name , p . Pod . Name )
}
updatedPod := medPriorityPodInfo . Pod . DeepCopy ( )
updatedPod . Annotations [ "foo" ] = "bar"
return medPriorityPodInfo . Pod , updatedPod
} ,
schedulingHintsEnablement : [ ] bool { true } ,
} ,
2021-02-06 04:08:16 -05:00
}
2023-12-29 01:50:21 -05:00
for _ , tt := range tests {
2023-12-08 03:12:13 -05:00
for _ , qHintEnabled := range tt . schedulingHintsEnablement {
t . Run ( fmt . Sprintf ( "%s, with queuehint(%v)" , tt . name , qHintEnabled ) , func ( t * testing . T ) {
2025-05-26 13:56:10 -04:00
if ! qHintEnabled {
featuregatetesting . SetFeatureGateEmulationVersionDuringTest ( t , utilfeature . DefaultFeatureGate , version . MustParse ( "1.33" ) )
featuregatetesting . SetFeatureGateDuringTest ( t , utilfeature . DefaultFeatureGate , features . SchedulerQueueingHints , false )
}
2023-12-08 03:12:13 -05:00
logger , ctx := ktesting . NewTestContext ( t )
objs := [ ] runtime . Object { highPriorityPodInfo . Pod , unschedulablePodInfo . Pod , medPriorityPodInfo . Pod }
ctx , cancel := context . WithCancel ( ctx )
defer cancel ( )
q := NewTestQueueWithObjects ( ctx , newDefaultQueueSort ( ) , objs , WithClock ( c ) , WithQueueingHintMapPerProfile ( queueingHintMap ) )
oldPod , newPod := tt . prepareFunc ( t , logger , q )
2024-07-17 07:25:31 -04:00
q . Update ( logger , oldPod , newPod )
2023-12-29 01:50:21 -05:00
2023-12-08 03:12:13 -05:00
var pInfo * framework . QueuedPodInfo
2023-12-29 01:50:21 -05:00
2023-12-08 03:12:13 -05:00
// validate expected queue
2025-02-18 03:23:37 -05:00
if pInfoFromBackoff , exists := q . backoffQ . get ( newQueuedPodInfoForLookup ( newPod ) ) ; exists {
2023-12-08 03:12:13 -05:00
if tt . wantQ != backoffQ {
t . Errorf ( "expected pod %s not to be queued to backoffQ, but it was" , newPod . Name )
}
2024-07-17 07:25:31 -04:00
pInfo = pInfoFromBackoff
2023-12-29 01:50:21 -05:00
}
2024-08-20 05:21:06 -04:00
q . activeQ . underRLock ( func ( unlockedActiveQ unlockedActiveQueueReader ) {
2025-02-25 08:05:33 -05:00
if pInfoFromActive , exists := unlockedActiveQ . get ( newQueuedPodInfoForLookup ( newPod ) ) ; exists {
2024-08-20 04:51:21 -04:00
if tt . wantQ != activeQ {
t . Errorf ( "expected pod %s not to be queued to activeQ, but it was" , newPod . Name )
}
pInfo = pInfoFromActive
2023-12-08 03:12:13 -05:00
}
2024-08-20 04:51:21 -04:00
} )
2023-12-29 01:50:21 -05:00
2023-12-08 03:12:13 -05:00
if pInfoFromUnsched := q . unschedulablePods . get ( newPod ) ; pInfoFromUnsched != nil {
2025-07-04 07:17:24 -04:00
if tt . wantQ != unschedulableQ {
2023-12-08 03:12:13 -05:00
t . Errorf ( "expected pod %s to not be queued to unschedulablePods, but it was" , newPod . Name )
}
pInfo = pInfoFromUnsched
2023-12-29 01:50:21 -05:00
}
2024-06-18 23:25:32 -04:00
if tt . wantQ == notInAnyQueue {
// skip the rest of the test if pod is not expected to be in any of the queues.
return
}
2023-12-08 03:12:13 -05:00
if diff := cmp . Diff ( newPod , pInfo . PodInfo . Pod ) ; diff != "" {
t . Errorf ( "Unexpected updated pod diff (-want, +got): %s" , diff )
}
2023-12-29 01:50:21 -05:00
2023-12-08 03:12:13 -05:00
if tt . wantAddedToNominated && len ( q . nominator . nominatedPods ) != 1 {
2024-07-01 07:37:53 -04:00
t . Errorf ( "Expected one item in nominatedPods map: %v" , q . nominator )
2023-12-08 03:12:13 -05:00
}
2023-12-29 01:50:21 -05:00
2023-12-08 03:12:13 -05:00
} )
}
2020-02-13 19:31:07 -05:00
}
2017-10-24 14:14:29 -04:00
}
2024-06-18 23:25:32 -04:00
// TestPriorityQueue_UpdateWhenInflight ensures to requeue a Pod back to activeQ/backoffQ
// if it actually got an update that may make it schedulable while being scheduled.
// See https://github.com/kubernetes/kubernetes/pull/125578#discussion_r1648338033 for more context.
func TestPriorityQueue_UpdateWhenInflight ( t * testing . T ) {
logger , ctx := ktesting . NewTestContext ( t )
ctx , cancel := context . WithCancel ( ctx )
defer cancel ( )
m := makeEmptyQueueingHintMapPerProfile ( )
// fakePlugin could change its scheduling result by any updates in Pods.
2024-10-01 23:24:03 -04:00
m [ "" ] [ framework . EventUnscheduledPodUpdate ] = [ ] * QueueingHintFunction {
2024-06-18 23:25:32 -04:00
{
PluginName : "fakePlugin" ,
QueueingHintFn : queueHintReturnQueue ,
} ,
}
c := testingclock . NewFakeClock ( time . Now ( ) )
q := NewTestQueue ( ctx , newDefaultQueueSort ( ) , WithQueueingHintMapPerProfile ( m ) , WithClock ( c ) )
// test-pod is created and popped out from the queue
testPod := st . MakePod ( ) . Name ( "test-pod" ) . Namespace ( "test-ns" ) . UID ( "test-uid" ) . Obj ( )
2024-07-17 07:25:31 -04:00
q . Add ( logger , testPod )
2024-06-18 23:25:32 -04:00
if p , err := q . Pop ( logger ) ; err != nil || p . Pod != testPod {
t . Errorf ( "Expected: %v after Pop, but got: %v" , testPod . Name , p . Pod . Name )
}
// testPod is updated while being scheduled.
updatedPod := testPod . DeepCopy ( )
updatedPod . Spec . Tolerations = [ ] v1 . Toleration {
{
Key : "foo" ,
Effect : v1 . TaintEffectNoSchedule ,
} ,
}
2024-07-17 07:25:31 -04:00
q . Update ( logger , testPod , updatedPod )
2024-06-18 23:25:32 -04:00
// test-pod got rejected by fakePlugin,
// but the update event that it just got may change this scheduling result,
// and hence we should put this pod to activeQ/backoffQ.
2024-11-11 16:29:19 -05:00
err := q . AddUnschedulableIfNotPresent ( logger , newQueuedPodInfoForLookup ( updatedPod , "fakePlugin" ) , q . SchedulingCycle ( ) )
2024-06-18 23:25:32 -04:00
if err != nil {
t . Fatalf ( "unexpected error from AddUnschedulableIfNotPresent: %v" , err )
}
2025-02-18 03:23:37 -05:00
pInfo , exists := q . backoffQ . get ( newQueuedPodInfoForLookup ( updatedPod ) )
2024-07-17 07:25:31 -04:00
if ! exists {
2024-06-18 23:25:32 -04:00
t . Fatalf ( "expected pod %s to be queued to backoffQ, but it wasn't." , updatedPod . Name )
}
if diff := cmp . Diff ( updatedPod , pInfo . PodInfo . Pod ) ; diff != "" {
t . Errorf ( "Unexpected updated pod diff (-want, +got): %s" , diff )
}
}
2017-10-24 14:14:29 -04:00
func TestPriorityQueue_Delete ( t * testing . T ) {
2021-06-05 14:30:17 -04:00
objs := [ ] runtime . Object { highPriorityPodInfo . Pod , unschedulablePodInfo . Pod }
2023-03-22 07:48:04 -04:00
logger , ctx := ktesting . NewTestContext ( t )
ctx , cancel := context . WithCancel ( ctx )
2022-01-22 14:00:56 -05:00
defer cancel ( )
q := NewTestQueueWithObjects ( ctx , newDefaultQueueSort ( ) , objs )
2023-03-22 07:48:04 -04:00
q . Update ( logger , highPriorityPodInfo . Pod , highPriNominatedPodInfo . Pod )
q . Add ( logger , unschedulablePodInfo . Pod )
2024-07-17 07:25:31 -04:00
q . Delete ( highPriNominatedPodInfo . Pod )
2024-08-20 04:51:21 -04:00
if ! q . activeQ . has ( newQueuedPodInfoForLookup ( unschedulablePodInfo . Pod ) ) {
2021-02-22 09:00:23 -05:00
t . Errorf ( "Expected %v to be in activeQ." , unschedulablePodInfo . Pod . Name )
2017-10-24 14:14:29 -04:00
}
2024-08-20 04:51:21 -04:00
if q . activeQ . has ( newQueuedPodInfoForLookup ( highPriNominatedPodInfo . Pod ) ) {
2021-02-22 09:00:23 -05:00
t . Errorf ( "Didn't expect %v to be in activeQ." , highPriorityPodInfo . Pod . Name )
2017-10-24 14:14:29 -04:00
}
2023-03-08 16:18:36 -05:00
if len ( q . nominator . nominatedPods ) != 1 {
2024-07-01 07:37:53 -04:00
t . Errorf ( "Expected nominatedPods to have only 'unschedulablePodInfo': %v" , q . nominator . nominatedPods )
2018-02-08 21:19:31 -05:00
}
2024-07-17 07:25:31 -04:00
q . Delete ( unschedulablePodInfo . Pod )
2023-03-08 16:18:36 -05:00
if len ( q . nominator . nominatedPods ) != 0 {
2024-07-01 07:37:53 -04:00
t . Errorf ( "Expected nominatedPods to be empty: %v" , q . nominator )
2018-02-08 21:19:31 -05:00
}
2017-10-24 14:14:29 -04:00
}
2021-12-18 09:12:46 -05:00
func TestPriorityQueue_Activate ( t * testing . T ) {
2024-11-05 08:48:00 -05:00
metrics . Register ( )
2021-12-18 09:12:46 -05:00
tests := [ ] struct {
2022-03-24 05:38:49 -04:00
name string
qPodInfoInUnschedulablePods [ ] * framework . QueuedPodInfo
2025-02-18 03:23:37 -05:00
qPodInfoInBackoffQ [ ] * framework . QueuedPodInfo
2024-08-21 09:40:41 -04:00
qPodInActiveQ [ ] * v1 . Pod
2022-03-24 05:38:49 -04:00
qPodInfoToActivate * framework . QueuedPodInfo
2024-11-05 08:48:00 -05:00
qPodInInFlightPod * v1 . Pod
expectedInFlightEvent * clusterEvent
2022-03-24 05:38:49 -04:00
want [ ] * framework . QueuedPodInfo
2024-11-05 08:48:00 -05:00
qHintEnabled bool
2021-12-18 09:12:46 -05:00
} {
{
name : "pod already in activeQ" ,
2024-08-21 09:40:41 -04:00
qPodInActiveQ : [ ] * v1 . Pod { highPriNominatedPodInfo . Pod } ,
2021-12-18 09:12:46 -05:00
qPodInfoToActivate : & framework . QueuedPodInfo { PodInfo : highPriNominatedPodInfo } ,
2022-03-24 07:56:10 -04:00
want : [ ] * framework . QueuedPodInfo { { PodInfo : highPriNominatedPodInfo } } , // 1 already active
2021-12-18 09:12:46 -05:00
} ,
{
2025-02-18 03:23:37 -05:00
name : "pod not in unschedulablePods/backoffQ" ,
2021-12-18 09:12:46 -05:00
qPodInfoToActivate : & framework . QueuedPodInfo { PodInfo : highPriNominatedPodInfo } ,
want : [ ] * framework . QueuedPodInfo { } ,
} ,
2024-11-05 08:48:00 -05:00
{
2025-02-18 03:23:37 -05:00
name : "[QHint] pod not in unschedulablePods/backoffQ but in-flight" ,
2024-11-05 08:48:00 -05:00
qPodInfoToActivate : & framework . QueuedPodInfo { PodInfo : highPriNominatedPodInfo } ,
qPodInInFlightPod : highPriNominatedPodInfo . Pod ,
expectedInFlightEvent : & clusterEvent { oldObj : ( * v1 . Pod ) ( nil ) , newObj : highPriNominatedPodInfo . Pod , event : framework . EventForceActivate } ,
want : [ ] * framework . QueuedPodInfo { } ,
qHintEnabled : true ,
} ,
{
2025-02-18 03:23:37 -05:00
name : "[QHint] pod not in unschedulablePods/backoffQ and not in-flight" ,
2024-11-05 08:48:00 -05:00
qPodInfoToActivate : & framework . QueuedPodInfo { PodInfo : highPriNominatedPodInfo } ,
qPodInInFlightPod : medPriorityPodInfo . Pod , // different pod is in-flight
want : [ ] * framework . QueuedPodInfo { } ,
qHintEnabled : true ,
} ,
2021-12-18 09:12:46 -05:00
{
2022-03-24 05:38:49 -04:00
name : "pod in unschedulablePods" ,
qPodInfoInUnschedulablePods : [ ] * framework . QueuedPodInfo { { PodInfo : highPriNominatedPodInfo } } ,
qPodInfoToActivate : & framework . QueuedPodInfo { PodInfo : highPriNominatedPodInfo } ,
want : [ ] * framework . QueuedPodInfo { { PodInfo : highPriNominatedPodInfo } } ,
2021-12-18 09:12:46 -05:00
} ,
{
2025-02-18 03:23:37 -05:00
name : "pod in backoffQ" ,
qPodInfoInBackoffQ : [ ] * framework . QueuedPodInfo { { PodInfo : highPriNominatedPodInfo } } ,
qPodInfoToActivate : & framework . QueuedPodInfo { PodInfo : highPriNominatedPodInfo } ,
want : [ ] * framework . QueuedPodInfo { { PodInfo : highPriNominatedPodInfo } } ,
2021-12-18 09:12:46 -05:00
} ,
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
2025-05-26 13:56:10 -04:00
if ! tt . qHintEnabled {
featuregatetesting . SetFeatureGateEmulationVersionDuringTest ( t , utilfeature . DefaultFeatureGate , version . MustParse ( "1.33" ) )
featuregatetesting . SetFeatureGateDuringTest ( t , utilfeature . DefaultFeatureGate , features . SchedulerQueueingHints , false )
}
2021-12-18 09:12:46 -05:00
var objs [ ] runtime . Object
2023-03-22 07:48:04 -04:00
logger , ctx := ktesting . NewTestContext ( t )
ctx , cancel := context . WithCancel ( ctx )
2022-01-22 14:00:56 -05:00
defer cancel ( )
q := NewTestQueueWithObjects ( ctx , newDefaultQueueSort ( ) , objs )
2021-12-18 09:12:46 -05:00
2024-11-05 08:48:00 -05:00
if tt . qPodInInFlightPod != nil {
// Put -> Pop the Pod to make it registered in inFlightPods.
q . activeQ . underLock ( func ( unlockedActiveQ unlockedActiveQueuer ) {
2025-09-02 05:35:22 -04:00
unlockedActiveQ . add ( logger , newQueuedPodInfoForLookup ( tt . qPodInInFlightPod ) , framework . EventUnscheduledPodAdd . Label ( ) )
2024-11-05 08:48:00 -05:00
} )
p , err := q . activeQ . pop ( logger )
if err != nil {
t . Fatalf ( "Pop failed: %v" , err )
}
if p . Pod . Name != tt . qPodInInFlightPod . Name {
t . Errorf ( "Unexpected popped pod: %v" , p . Pod . Name )
}
if len ( q . activeQ . listInFlightEvents ( ) ) != 1 {
t . Fatal ( "Expected the pod to be recorded in in-flight events, but it doesn't" )
}
}
2025-02-18 03:23:37 -05:00
// Prepare activeQ/unschedulablePods/backoffQ according to the table
2024-08-21 09:40:41 -04:00
for _ , qPod := range tt . qPodInActiveQ {
q . Add ( logger , qPod )
2021-12-18 09:12:46 -05:00
}
2022-03-24 05:38:49 -04:00
for _ , qPodInfo := range tt . qPodInfoInUnschedulablePods {
2025-11-20 06:21:44 -05:00
q . unschedulablePods . addOrUpdate ( qPodInfo , false , framework . EventUnscheduledPodAdd . Label ( ) )
2021-12-18 09:12:46 -05:00
}
2025-02-18 03:23:37 -05:00
for _ , qPodInfo := range tt . qPodInfoInBackoffQ {
2025-02-25 08:05:33 -05:00
q . backoffQ . add ( logger , qPodInfo , framework . EventUnscheduledPodAdd . Label ( ) )
2021-12-18 09:12:46 -05:00
}
// Activate specific pod according to the table
2023-03-22 07:48:04 -04:00
q . Activate ( logger , map [ string ] * v1 . Pod { "test_pod" : tt . qPodInfoToActivate . PodInfo . Pod } )
2021-12-18 09:12:46 -05:00
// Check the result after activation by the length of activeQ
2024-07-22 06:13:13 -04:00
if wantLen := len ( tt . want ) ; q . activeQ . len ( ) != wantLen {
2024-11-05 08:48:00 -05:00
t . Fatalf ( "length compare: want %v, got %v" , wantLen , q . activeQ . len ( ) )
}
if tt . expectedInFlightEvent != nil {
if len ( q . activeQ . listInFlightEvents ( ) ) != 2 {
t . Fatalf ( "Expected two in-flight event to be recorded, but got %v events" , len ( q . activeQ . listInFlightEvents ( ) ) )
}
found := false
for _ , e := range q . activeQ . listInFlightEvents ( ) {
event , ok := e . ( * clusterEvent )
if ! ok {
continue
}
if d := cmp . Diff ( tt . expectedInFlightEvent , event , cmpopts . EquateComparable ( clusterEvent { } ) ) ; d != "" {
t . Fatalf ( "Unexpected in-flight event (-want, +got):\n%s" , d )
}
found = true
}
if ! found {
t . Fatalf ( "Expected in-flight event to be recorded, but it wasn't." )
}
2021-12-18 09:12:46 -05:00
}
// Check if the specific pod exists in activeQ
for _ , want := range tt . want {
2024-08-20 04:51:21 -04:00
if ! q . activeQ . has ( newQueuedPodInfoForLookup ( want . PodInfo . Pod ) ) {
2021-12-18 09:12:46 -05:00
t . Errorf ( "podInfo not exist in activeQ: want %v" , want . PodInfo . Pod . Name )
}
}
} )
}
}
2022-11-07 17:02:22 -05:00
type preEnqueuePlugin struct {
allowlists [ ] string
2026-01-09 10:35:48 -05:00
name string
2022-11-07 17:02:22 -05:00
}
func ( pl * preEnqueuePlugin ) Name ( ) string {
2026-01-09 10:35:48 -05:00
if pl . name != "" {
return pl . name
}
2022-11-07 17:02:22 -05:00
return "preEnqueuePlugin"
}
2025-06-03 18:59:50 -04:00
func ( pl * preEnqueuePlugin ) PreEnqueue ( ctx context . Context , p * v1 . Pod ) * fwk . Status {
2022-11-07 17:02:22 -05:00
for _ , allowed := range pl . allowlists {
2022-11-22 00:33:16 -05:00
for label := range p . Labels {
if label == allowed {
return nil
}
2022-11-07 17:02:22 -05:00
}
}
2025-06-03 18:59:50 -04:00
return fwk . NewStatus ( fwk . UnschedulableAndUnresolvable , "pod name not in allowlists" )
2022-11-07 17:02:22 -05:00
}
2025-02-28 07:18:37 -05:00
func TestPriorityQueue_moveToActiveQ ( t * testing . T ) {
2022-11-07 17:02:22 -05:00
tests := [ ] struct {
2025-02-28 07:18:37 -05:00
name string
2025-07-24 07:48:07 -04:00
plugins [ ] fwk . PreEnqueuePlugin
2025-02-28 07:18:37 -05:00
pod * v1 . Pod
event string
2025-08-14 03:14:40 -04:00
movesFromBackoffQ bool
2025-02-28 07:18:37 -05:00
popFromBackoffQEnabled [ ] bool
wantUnschedulablePods int
wantSuccess bool
2022-11-07 17:02:22 -05:00
} {
{
name : "no plugins registered" ,
2022-11-22 00:33:16 -05:00
pod : st . MakePod ( ) . Name ( "p" ) . Label ( "p" , "" ) . Obj ( ) ,
2025-02-28 07:18:37 -05:00
event : framework . EventUnscheduledPodAdd . Label ( ) ,
2022-11-07 17:02:22 -05:00
wantUnschedulablePods : 0 ,
wantSuccess : true ,
} ,
{
name : "preEnqueue plugin registered, pod name not in allowlists" ,
2025-07-24 07:48:07 -04:00
plugins : [ ] fwk . PreEnqueuePlugin { & preEnqueuePlugin { } , & preEnqueuePlugin { } } ,
2022-11-22 00:33:16 -05:00
pod : st . MakePod ( ) . Name ( "p" ) . Label ( "p" , "" ) . Obj ( ) ,
2025-02-28 07:18:37 -05:00
event : framework . EventUnscheduledPodAdd . Label ( ) ,
2022-11-07 17:02:22 -05:00
wantUnschedulablePods : 1 ,
wantSuccess : false ,
} ,
{
name : "preEnqueue plugin registered, pod failed one preEnqueue plugin" ,
2025-07-24 07:48:07 -04:00
plugins : [ ] fwk . PreEnqueuePlugin {
2022-11-07 17:02:22 -05:00
& preEnqueuePlugin { allowlists : [ ] string { "foo" , "bar" } } ,
& preEnqueuePlugin { allowlists : [ ] string { "foo" } } ,
} ,
2022-11-22 00:33:16 -05:00
pod : st . MakePod ( ) . Name ( "bar" ) . Label ( "bar" , "" ) . Obj ( ) ,
2025-02-28 07:18:37 -05:00
event : framework . EventUnscheduledPodAdd . Label ( ) ,
2022-11-07 17:02:22 -05:00
wantUnschedulablePods : 1 ,
wantSuccess : false ,
} ,
2025-02-28 07:18:37 -05:00
{
2025-08-14 03:14:40 -04:00
name : "preEnqueue plugin registered, preEnqueue rejects the pod, even if it is after backoff" ,
2025-07-24 07:48:07 -04:00
plugins : [ ] fwk . PreEnqueuePlugin {
2025-02-28 07:18:37 -05:00
& preEnqueuePlugin { allowlists : [ ] string { "foo" , "bar" } } ,
& preEnqueuePlugin { allowlists : [ ] string { "foo" } } ,
} ,
pod : st . MakePod ( ) . Name ( "bar" ) . Label ( "bar" , "" ) . Obj ( ) ,
event : framework . BackoffComplete ,
popFromBackoffQEnabled : [ ] bool { false } ,
wantUnschedulablePods : 1 ,
wantSuccess : false ,
} ,
{
2025-08-14 03:14:40 -04:00
// With SchedulerPopFromBackoffQ enabled, the queue assumes the pod has already passed PreEnqueue,
// and it doesn't run PreEnqueue again, always puts the pod to activeQ.
name : "preEnqueue plugin registered, pod would fail one preEnqueue plugin, but it is moved from backoffQ after completing backoff, so preEnqueue is not executed" ,
2025-07-24 07:48:07 -04:00
plugins : [ ] fwk . PreEnqueuePlugin {
2025-02-28 07:18:37 -05:00
& preEnqueuePlugin { allowlists : [ ] string { "foo" , "bar" } } ,
& preEnqueuePlugin { allowlists : [ ] string { "foo" } } ,
} ,
pod : st . MakePod ( ) . Name ( "bar" ) . Label ( "bar" , "" ) . Obj ( ) ,
event : framework . BackoffComplete ,
2025-08-14 03:14:40 -04:00
movesFromBackoffQ : true ,
popFromBackoffQEnabled : [ ] bool { true } ,
wantUnschedulablePods : 0 ,
wantSuccess : true ,
} ,
{
name : "preEnqueue plugin registered, pod failed one preEnqueue plugin when activated from unschedulablePods" ,
2025-07-24 07:48:07 -04:00
plugins : [ ] fwk . PreEnqueuePlugin {
2025-08-14 03:14:40 -04:00
& preEnqueuePlugin { allowlists : [ ] string { "foo" , "bar" } } ,
& preEnqueuePlugin { allowlists : [ ] string { "foo" } } ,
} ,
pod : st . MakePod ( ) . Name ( "bar" ) . Label ( "bar" , "" ) . Obj ( ) ,
event : framework . ForceActivate ,
movesFromBackoffQ : false ,
popFromBackoffQEnabled : [ ] bool { true } ,
wantUnschedulablePods : 1 ,
wantSuccess : false ,
} ,
{
name : "preEnqueue plugin registered, pod would fail one preEnqueue plugin, but was activated from backoffQ, so preEnqueue is not executed" ,
2025-07-24 07:48:07 -04:00
plugins : [ ] fwk . PreEnqueuePlugin {
2025-08-14 03:14:40 -04:00
& preEnqueuePlugin { allowlists : [ ] string { "foo" , "bar" } } ,
& preEnqueuePlugin { allowlists : [ ] string { "foo" } } ,
} ,
pod : st . MakePod ( ) . Name ( "bar" ) . Label ( "bar" , "" ) . Obj ( ) ,
event : framework . ForceActivate ,
movesFromBackoffQ : true ,
2025-02-28 07:18:37 -05:00
popFromBackoffQEnabled : [ ] bool { true } ,
wantUnschedulablePods : 0 ,
wantSuccess : true ,
} ,
2022-11-07 17:02:22 -05:00
{
name : "preEnqueue plugin registered, pod passed all preEnqueue plugins" ,
2025-07-24 07:48:07 -04:00
plugins : [ ] fwk . PreEnqueuePlugin {
2022-11-07 17:02:22 -05:00
& preEnqueuePlugin { allowlists : [ ] string { "foo" , "bar" } } ,
& preEnqueuePlugin { allowlists : [ ] string { "bar" } } ,
} ,
2022-11-22 00:33:16 -05:00
pod : st . MakePod ( ) . Name ( "bar" ) . Label ( "bar" , "" ) . Obj ( ) ,
2025-02-28 07:18:37 -05:00
event : framework . EventUnscheduledPodAdd . Label ( ) ,
2022-11-07 17:02:22 -05:00
wantUnschedulablePods : 0 ,
wantSuccess : true ,
} ,
}
for _ , tt := range tests {
2025-02-28 07:18:37 -05:00
if tt . popFromBackoffQEnabled == nil {
tt . popFromBackoffQEnabled = [ ] bool { true , false }
}
for _ , popFromBackoffQEnabled := range tt . popFromBackoffQEnabled {
t . Run ( fmt . Sprintf ( "%s popFromBackoffQEnabled(%v)" , tt . name , popFromBackoffQEnabled ) , func ( t * testing . T ) {
featuregatetesting . SetFeatureGateDuringTest ( t , utilfeature . DefaultFeatureGate , features . SchedulerPopFromBackoffQ , popFromBackoffQEnabled )
logger , ctx := ktesting . NewTestContext ( t )
ctx , cancel := context . WithCancel ( ctx )
defer cancel ( )
2022-11-07 17:02:22 -05:00
2025-07-24 07:48:07 -04:00
m := map [ string ] map [ string ] fwk . PreEnqueuePlugin { "" : make ( map [ string ] fwk . PreEnqueuePlugin , len ( tt . plugins ) ) }
2024-09-05 22:19:49 -04:00
for _ , plugin := range tt . plugins {
m [ "" ] [ plugin . Name ( ) ] = plugin
}
2025-02-28 07:18:37 -05:00
q := NewTestQueueWithObjects ( ctx , newDefaultQueueSort ( ) , [ ] runtime . Object { tt . pod } , WithPreEnqueuePluginMap ( m ) ,
WithPodInitialBackoffDuration ( time . Second * 30 ) , WithPodMaxBackoffDuration ( time . Second * 60 ) )
2025-08-14 03:14:40 -04:00
got := q . moveToActiveQ ( logger , q . newQueuedPodInfo ( tt . pod ) , tt . event , tt . movesFromBackoffQ )
2025-02-28 07:18:37 -05:00
if got != tt . wantSuccess {
t . Errorf ( "Unexpected result: want %v, but got %v" , tt . wantSuccess , got )
}
if tt . wantUnschedulablePods != len ( q . unschedulablePods . podInfoMap ) {
t . Errorf ( "Unexpected unschedulablePods: want %v, but got %v" , tt . wantUnschedulablePods , len ( q . unschedulablePods . podInfoMap ) )
}
2022-11-08 02:11:34 -05:00
2025-02-28 07:18:37 -05:00
// Simulate an update event.
clone := tt . pod . DeepCopy ( )
metav1 . SetMetaDataAnnotation ( & clone . ObjectMeta , "foo" , "" )
q . Update ( logger , tt . pod , clone )
// Ensure the pod is still located in unschedulablePods.
if tt . wantUnschedulablePods != len ( q . unschedulablePods . podInfoMap ) {
t . Errorf ( "Unexpected unschedulablePods: want %v, but got %v" , tt . wantUnschedulablePods , len ( q . unschedulablePods . podInfoMap ) )
}
} )
}
}
}
func TestPriorityQueue_moveToBackoffQ ( t * testing . T ) {
tests := [ ] struct {
name string
2025-07-24 07:48:07 -04:00
plugins [ ] fwk . PreEnqueuePlugin
2025-02-28 07:18:37 -05:00
pod * v1 . Pod
popFromBackoffQEnabled [ ] bool
wantSuccess bool
} {
{
name : "no plugins registered" ,
pod : st . MakePod ( ) . Name ( "p" ) . Label ( "p" , "" ) . Obj ( ) ,
wantSuccess : true ,
} ,
{
name : "preEnqueue plugin registered, pod name would not be in allowlists" ,
2025-07-24 07:48:07 -04:00
plugins : [ ] fwk . PreEnqueuePlugin { & preEnqueuePlugin { } , & preEnqueuePlugin { } } ,
2025-02-28 07:18:37 -05:00
pod : st . MakePod ( ) . Name ( "p" ) . Label ( "p" , "" ) . Obj ( ) ,
popFromBackoffQEnabled : [ ] bool { false } ,
wantSuccess : true ,
} ,
{
name : "preEnqueue plugin registered, pod name not in allowlists" ,
2025-07-24 07:48:07 -04:00
plugins : [ ] fwk . PreEnqueuePlugin { & preEnqueuePlugin { } , & preEnqueuePlugin { } } ,
2025-02-28 07:18:37 -05:00
pod : st . MakePod ( ) . Name ( "p" ) . Label ( "p" , "" ) . Obj ( ) ,
popFromBackoffQEnabled : [ ] bool { true } ,
wantSuccess : false ,
} ,
{
name : "preEnqueue plugin registered, preEnqueue plugin would reject the pod, but isn't run" ,
2025-07-24 07:48:07 -04:00
plugins : [ ] fwk . PreEnqueuePlugin {
2025-02-28 07:18:37 -05:00
& preEnqueuePlugin { allowlists : [ ] string { "foo" , "bar" } } ,
& preEnqueuePlugin { allowlists : [ ] string { "foo" } } ,
} ,
pod : st . MakePod ( ) . Name ( "bar" ) . Label ( "bar" , "" ) . Obj ( ) ,
popFromBackoffQEnabled : [ ] bool { false } ,
wantSuccess : true ,
} ,
{
name : "preEnqueue plugin registered, pod failed one preEnqueue plugin" ,
2025-07-24 07:48:07 -04:00
plugins : [ ] fwk . PreEnqueuePlugin {
2025-02-28 07:18:37 -05:00
& preEnqueuePlugin { allowlists : [ ] string { "foo" , "bar" } } ,
& preEnqueuePlugin { allowlists : [ ] string { "foo" } } ,
} ,
pod : st . MakePod ( ) . Name ( "bar" ) . Label ( "bar" , "" ) . Obj ( ) ,
popFromBackoffQEnabled : [ ] bool { true } ,
wantSuccess : false ,
} ,
{
name : "preEnqueue plugin registered, pod passed all preEnqueue plugins" ,
2025-07-24 07:48:07 -04:00
plugins : [ ] fwk . PreEnqueuePlugin {
2025-02-28 07:18:37 -05:00
& preEnqueuePlugin { allowlists : [ ] string { "foo" , "bar" } } ,
& preEnqueuePlugin { allowlists : [ ] string { "bar" } } ,
} ,
pod : st . MakePod ( ) . Name ( "bar" ) . Label ( "bar" , "" ) . Obj ( ) ,
wantSuccess : true ,
} ,
}
for _ , tt := range tests {
if tt . popFromBackoffQEnabled == nil {
tt . popFromBackoffQEnabled = [ ] bool { true , false }
}
for _ , popFromBackoffQEnabled := range tt . popFromBackoffQEnabled {
t . Run ( fmt . Sprintf ( "%s popFromBackoffQEnabled(%v)" , tt . name , popFromBackoffQEnabled ) , func ( t * testing . T ) {
featuregatetesting . SetFeatureGateDuringTest ( t , utilfeature . DefaultFeatureGate , features . SchedulerPopFromBackoffQ , popFromBackoffQEnabled )
logger , ctx := ktesting . NewTestContext ( t )
ctx , cancel := context . WithCancel ( ctx )
defer cancel ( )
2025-07-24 07:48:07 -04:00
m := map [ string ] map [ string ] fwk . PreEnqueuePlugin { "" : make ( map [ string ] fwk . PreEnqueuePlugin , len ( tt . plugins ) ) }
2024-09-05 22:19:49 -04:00
for _ , plugin := range tt . plugins {
m [ "" ] [ plugin . Name ( ) ] = plugin
}
2025-02-28 07:18:37 -05:00
q := NewTestQueueWithObjects ( ctx , newDefaultQueueSort ( ) , [ ] runtime . Object { tt . pod } , WithPreEnqueuePluginMap ( m ) ,
WithPodInitialBackoffDuration ( time . Second * 30 ) , WithPodMaxBackoffDuration ( time . Second * 60 ) )
pInfo := q . newQueuedPodInfo ( tt . pod )
got := q . moveToBackoffQ ( logger , pInfo , framework . EventUnscheduledPodAdd . Label ( ) )
if got != tt . wantSuccess {
t . Errorf ( "Unexpected result: want %v, but got %v" , tt . wantSuccess , got )
}
if tt . wantSuccess {
if ! q . backoffQ . has ( pInfo ) {
t . Errorf ( "Expected pod to be in backoffQ, but it isn't" )
}
if q . unschedulablePods . get ( pInfo . Pod ) != nil {
t . Errorf ( "Expected pod not to be in unschedulablePods, but it is" )
}
} else {
if q . backoffQ . has ( pInfo ) {
t . Errorf ( "Expected pod not to be in backoffQ, but it is" )
}
if q . unschedulablePods . get ( pInfo . Pod ) == nil {
t . Errorf ( "Expected pod to be in unschedulablePods, but it isn't" )
}
}
} )
}
2022-11-07 17:02:22 -05:00
}
}
2021-02-19 22:18:33 -05:00
func BenchmarkMoveAllToActiveOrBackoffQueue ( b * testing . B ) {
tests := [ ] struct {
name string
2025-06-26 11:06:29 -04:00
moveEvent fwk . ClusterEvent
2021-02-19 22:18:33 -05:00
} {
{
name : "baseline" ,
2024-10-01 23:24:03 -04:00
moveEvent : framework . EventUnschedulableTimeout ,
2021-02-19 22:18:33 -05:00
} ,
{
name : "worst" ,
2024-10-01 23:24:03 -04:00
moveEvent : nodeAdd ,
2021-02-19 22:18:33 -05:00
} ,
{
name : "random" ,
// leave "moveEvent" unspecified
} ,
}
podTemplates := [ ] * v1 . Pod {
highPriorityPodInfo . Pod , highPriNominatedPodInfo . Pod ,
medPriorityPodInfo . Pod , unschedulablePodInfo . Pod ,
}
2021-03-16 18:08:19 -04:00
2025-06-26 11:06:29 -04:00
events := [ ] fwk . ClusterEvent {
{ Resource : fwk . Node , ActionType : fwk . Add } ,
{ Resource : fwk . Node , ActionType : fwk . UpdateNodeTaint } ,
{ Resource : fwk . Node , ActionType : fwk . UpdateNodeAllocatable } ,
{ Resource : fwk . Node , ActionType : fwk . UpdateNodeCondition } ,
{ Resource : fwk . Node , ActionType : fwk . UpdateNodeLabel } ,
{ Resource : fwk . Node , ActionType : fwk . UpdateNodeAnnotation } ,
{ Resource : fwk . PersistentVolumeClaim , ActionType : fwk . Add } ,
{ Resource : fwk . PersistentVolumeClaim , ActionType : fwk . Update } ,
{ Resource : fwk . PersistentVolume , ActionType : fwk . Add } ,
{ Resource : fwk . PersistentVolume , ActionType : fwk . Update } ,
{ Resource : fwk . StorageClass , ActionType : fwk . Add } ,
{ Resource : fwk . StorageClass , ActionType : fwk . Update } ,
{ Resource : fwk . CSINode , ActionType : fwk . Add } ,
{ Resource : fwk . CSINode , ActionType : fwk . Update } ,
{ Resource : fwk . CSIDriver , ActionType : fwk . Add } ,
{ Resource : fwk . CSIDriver , ActionType : fwk . Update } ,
{ Resource : fwk . CSIStorageCapacity , ActionType : fwk . Add } ,
{ Resource : fwk . CSIStorageCapacity , ActionType : fwk . Update } ,
2021-03-16 18:08:19 -04:00
}
2021-02-19 22:18:33 -05:00
pluginNum := 20
var plugins [ ] string
// Mimic that we have 20 plugins loaded in runtime.
for i := 0 ; i < pluginNum ; i ++ {
plugins = append ( plugins , fmt . Sprintf ( "fake-plugin-%v" , i ) )
}
for _ , tt := range tests {
2022-03-24 05:38:49 -04:00
for _ , podsInUnschedulablePods := range [ ] int { 1000 , 5000 } {
b . Run ( fmt . Sprintf ( "%v-%v" , tt . name , podsInUnschedulablePods ) , func ( b * testing . B ) {
2024-09-09 17:59:24 -04:00
logger , ctx := ktesting . NewTestContext ( b )
2021-02-19 22:18:33 -05:00
for i := 0 ; i < b . N ; i ++ {
b . StopTimer ( )
2021-09-17 05:48:22 -04:00
c := testingclock . NewFakeClock ( time . Now ( ) )
2021-02-19 22:18:33 -05:00
2023-06-08 00:54:30 -04:00
m := makeEmptyQueueingHintMapPerProfile ( )
2024-10-01 23:24:03 -04:00
// - All plugins registered for events[0], which is NodeAdd.
2021-02-19 22:18:33 -05:00
// - 1/2 of plugins registered for events[1]
// - 1/3 of plugins registered for events[2]
// - ...
for j := 0 ; j < len ( events ) ; j ++ {
for k := 0 ; k < len ( plugins ) ; k ++ {
if ( k + 1 ) % ( j + 1 ) == 0 {
2023-06-08 00:54:30 -04:00
m [ "" ] [ events [ j ] ] = append ( m [ "" ] [ events [ j ] ] , & QueueingHintFunction {
PluginName : plugins [ k ] ,
2023-10-19 07:02:11 -04:00
QueueingHintFn : queueHintReturnQueue ,
2023-06-08 00:54:30 -04:00
} )
2021-02-19 22:18:33 -05:00
}
}
}
2024-09-09 17:59:24 -04:00
ctx , cancel := context . WithCancel ( ctx )
2022-01-22 14:00:56 -05:00
defer cancel ( )
2023-06-08 00:54:30 -04:00
q := NewTestQueue ( ctx , newDefaultQueueSort ( ) , WithClock ( c ) , WithQueueingHintMapPerProfile ( m ) )
2021-02-19 22:18:33 -05:00
2022-03-24 05:38:49 -04:00
// Init pods in unschedulablePods.
for j := 0 ; j < podsInUnschedulablePods ; j ++ {
2021-02-19 22:18:33 -05:00
p := podTemplates [ j % len ( podTemplates ) ] . DeepCopy ( )
p . Name , p . UID = fmt . Sprintf ( "%v-%v" , p . Name , j ) , types . UID ( fmt . Sprintf ( "%v-%v" , p . UID , j ) )
var podInfo * framework . QueuedPodInfo
// The ultimate goal of composing each PodInfo is to cover the path that intersects
// (unschedulable) plugin names with the plugins that register the moveEvent,
// here the rational is:
// - in baseline case, don't inject unschedulable plugin names, so podMatchesEvent()
// never gets executed.
// - in worst case, make both ends (of the intersection) a big number,i.e.,
// M intersected with N instead of M with 1 (or 1 with N)
// - in random case, each pod failed by a random plugin, and also the moveEvent
// is randomized.
if tt . name == "baseline" {
podInfo = q . newQueuedPodInfo ( p )
} else if tt . name == "worst" {
// Each pod failed by all plugins.
podInfo = q . newQueuedPodInfo ( p , plugins ... )
} else {
// Random case.
podInfo = q . newQueuedPodInfo ( p , plugins [ j % len ( plugins ) ] )
}
2023-09-11 12:30:11 -04:00
err := q . AddUnschedulableIfNotPresent ( logger , podInfo , q . SchedulingCycle ( ) )
if err != nil {
b . Fatalf ( "unexpected error from AddUnschedulableIfNotPresent: %v" , err )
}
2021-02-19 22:18:33 -05:00
}
b . StartTimer ( )
2021-03-16 18:08:19 -04:00
if tt . moveEvent . Resource != "" {
2023-06-08 00:54:30 -04:00
q . MoveAllToActiveOrBackoffQueue ( logger , tt . moveEvent , nil , nil , nil )
2021-02-19 22:18:33 -05:00
} else {
// Random case.
2023-06-08 00:54:30 -04:00
q . MoveAllToActiveOrBackoffQueue ( logger , events [ i % len ( events ) ] , nil , nil , nil )
2021-02-19 22:18:33 -05:00
}
}
} )
}
}
}
2023-06-08 00:54:30 -04:00
func TestPriorityQueue_MoveAllToActiveOrBackoffQueueWithQueueingHint ( t * testing . T ) {
2024-11-11 16:29:19 -05:00
now := time . Date ( 2025 , 1 , 1 , 0 , 0 , 0 , 0 , time . UTC )
2024-09-05 22:19:49 -04:00
p := st . MakePod ( ) . Name ( "pod1" ) . Namespace ( "ns1" ) . UID ( "1" ) . Label ( "foo" , "bar" ) . Obj ( )
2023-06-08 00:54:30 -04:00
tests := [ ] struct {
name string
podInfo * framework . QueuedPodInfo
2025-06-26 11:06:29 -04:00
hint fwk . QueueingHintFn
2023-06-08 00:54:30 -04:00
// duration is the duration that the Pod has been in the unschedulable queue.
duration time . Duration
// expectedQ is the queue name (activeQ, backoffQ, or unschedulablePods) that this Pod should be quened to.
expectedQ string
} {
{
2024-11-11 16:29:19 -05:00
name : "Queue queues pod to activeQ" ,
// This pod has PendingPlugins and hence will be pushed directly to activeQ
2023-10-19 07:02:11 -04:00
podInfo : & framework . QueuedPodInfo { PodInfo : mustNewPodInfo ( p ) , PendingPlugins : sets . New ( "foo" ) } ,
hint : queueHintReturnQueue ,
2023-06-08 00:54:30 -04:00
expectedQ : activeQ ,
} ,
{
2024-11-11 16:29:19 -05:00
name : "Queue queues pod to backoffQ if Pod is backing off" ,
// needs UnschedulableCount to make it backing off.
podInfo : & framework . QueuedPodInfo { PodInfo : mustNewPodInfo ( p ) , UnschedulablePlugins : sets . New ( "foo" ) } ,
2023-10-19 07:02:11 -04:00
hint : queueHintReturnQueue ,
2023-06-08 00:54:30 -04:00
expectedQ : backoffQ ,
} ,
{
2024-11-11 16:29:19 -05:00
name : "Queue queues pod to activeQ if Pod is not backing off" ,
podInfo : & framework . QueuedPodInfo { PodInfo : mustNewPodInfo ( p ) , UnschedulablePlugins : sets . New ( "foo" ) } ,
hint : queueHintReturnQueue ,
// The pod is assumed to failed the scheduling cycle once, which would get DefaultPodInitialBackoffDuration as the penalty.
// To finish the backoff, waiting for DefaultPodInitialBackoffDuration isn't enough, need to wait for +1
// because the pod is determined to be still backing off if `{backoff expiration time} == trancate({current time})`
duration : DefaultPodInitialBackoffDuration + time . Second ,
2023-06-08 00:54:30 -04:00
expectedQ : activeQ ,
} ,
{
2023-10-19 07:02:11 -04:00
name : "Skip queues pod to unschedulablePods" ,
podInfo : & framework . QueuedPodInfo { PodInfo : mustNewPodInfo ( p ) , UnschedulablePlugins : sets . New ( "foo" ) } ,
hint : queueHintReturnSkip ,
2025-07-04 07:17:24 -04:00
expectedQ : unschedulableQ ,
2023-06-08 00:54:30 -04:00
} ,
2024-04-22 13:00:58 -04:00
{
2024-09-05 22:19:49 -04:00
name : "QueueHintFunction is not called when Pod is gated by the plugin that isn't interested in the event" ,
2025-06-26 11:06:29 -04:00
podInfo : setQueuedPodInfoGated ( & framework . QueuedPodInfo { PodInfo : mustNewPodInfo ( p ) } , names . SchedulingGates , [ ] fwk . ClusterEvent { framework . EventUnscheduledPodUpdate } ) ,
2024-09-05 22:19:49 -04:00
// The hintFn should not be called as the pod is gated by SchedulingGates plugin,
// the scheduling gate isn't interested in the node add event,
// and the queue should keep this Pod in the unschedQ without calling the hintFn.
2025-06-26 11:06:29 -04:00
hint : func ( logger klog . Logger , pod * v1 . Pod , oldObj , newObj interface { } ) ( fwk . QueueingHint , error ) {
return fwk . Queue , fmt . Errorf ( "QueueingHintFn should not be called as pod is gated" )
2024-04-22 13:00:58 -04:00
} ,
2025-07-04 07:17:24 -04:00
expectedQ : unschedulableQ ,
2024-04-22 13:00:58 -04:00
} ,
2024-06-15 18:36:42 -04:00
{
2024-09-05 22:19:49 -04:00
name : "QueueHintFunction is called when Pod is gated by the plugin that is interested in the event" ,
2025-06-26 11:06:29 -04:00
podInfo : setQueuedPodInfoGated ( & framework . QueuedPodInfo { PodInfo : mustNewPodInfo ( p ) } , "foo" , [ ] fwk . ClusterEvent { nodeAdd } ) ,
2024-09-05 22:19:49 -04:00
// In this case, the hintFn should be called as the pod is gated by foo plugin that is interested in the NodeAdd event.
hint : queueHintReturnQueue ,
2024-11-11 16:29:19 -05:00
// and, as a result, this pod should be queued to backoffQ.
2024-07-11 07:41:08 -04:00
expectedQ : backoffQ ,
} ,
2023-06-08 00:54:30 -04:00
}
for _ , test := range tests {
t . Run ( test . name , func ( t * testing . T ) {
logger , ctx := ktesting . NewTestContext ( t )
m := makeEmptyQueueingHintMapPerProfile ( )
2024-10-01 23:24:03 -04:00
m [ "" ] [ nodeAdd ] = [ ] * QueueingHintFunction {
2023-06-08 00:54:30 -04:00
{
PluginName : "foo" ,
QueueingHintFn : test . hint ,
} ,
}
2024-09-05 22:19:49 -04:00
m [ "" ] [ framework . EventUnscheduledPodUpdate ] = [ ] * QueueingHintFunction {
{
PluginName : names . SchedulingGates ,
QueueingHintFn : queueHintReturnQueue ,
} ,
}
2023-06-08 00:54:30 -04:00
cl := testingclock . NewFakeClock ( now )
2024-09-05 22:19:49 -04:00
plugin , _ := schedulinggates . New ( ctx , nil , nil , plfeature . Features { } )
2025-07-24 07:48:07 -04:00
preEnqM := map [ string ] map [ string ] fwk . PreEnqueuePlugin { "" : {
names . SchedulingGates : plugin . ( fwk . PreEnqueuePlugin ) ,
2024-09-05 22:19:49 -04:00
"foo" : & preEnqueuePlugin { allowlists : [ ] string { "foo" } } ,
} }
q := NewTestQueue ( ctx , newDefaultQueueSort ( ) , WithQueueingHintMapPerProfile ( m ) , WithClock ( cl ) , WithPreEnqueuePluginMap ( preEnqM ) )
2024-08-21 09:40:41 -04:00
q . Add ( logger , test . podInfo . Pod )
2023-09-28 22:59:22 -04:00
if p , err := q . Pop ( logger ) ; err != nil || p . Pod != test . podInfo . Pod {
2023-07-17 18:53:07 -04:00
t . Errorf ( "Expected: %v after Pop, but got: %v" , test . podInfo . Pod . Name , p . Pod . Name )
}
2024-06-15 18:36:42 -04:00
// add to unsched pod pool
2023-09-11 12:30:11 -04:00
err := q . AddUnschedulableIfNotPresent ( logger , test . podInfo , q . SchedulingCycle ( ) )
if err != nil {
t . Fatalf ( "unexpected error from AddUnschedulableIfNotPresent: %v" , err )
}
2023-06-08 00:54:30 -04:00
cl . Step ( test . duration )
2024-10-01 23:24:03 -04:00
q . MoveAllToActiveOrBackoffQueue ( logger , nodeAdd , nil , nil , nil )
2023-06-08 00:54:30 -04:00
2025-02-18 03:23:37 -05:00
if q . backoffQ . len ( ) == 0 && test . expectedQ == backoffQ {
2023-06-08 00:54:30 -04:00
t . Fatalf ( "expected pod to be queued to backoffQ, but it was not" )
}
2024-07-22 06:13:13 -04:00
if q . activeQ . len ( ) == 0 && test . expectedQ == activeQ {
2023-06-08 00:54:30 -04:00
t . Fatalf ( "expected pod to be queued to activeQ, but it was not" )
}
2025-07-04 07:17:24 -04:00
if q . unschedulablePods . get ( test . podInfo . Pod ) == nil && test . expectedQ == unschedulableQ {
2023-06-08 00:54:30 -04:00
t . Fatalf ( "expected pod to be queued to unschedulablePods, but it was not" )
}
} )
}
}
2020-02-13 19:31:07 -05:00
func TestPriorityQueue_MoveAllToActiveOrBackoffQueue ( t * testing . T ) {
2021-09-17 05:48:22 -04:00
c := testingclock . NewFakeClock ( time . Now ( ) )
2023-03-22 07:48:04 -04:00
logger , ctx := ktesting . NewTestContext ( t )
ctx , cancel := context . WithCancel ( ctx )
2022-01-22 14:00:56 -05:00
defer cancel ( )
2023-06-08 00:54:30 -04:00
m := makeEmptyQueueingHintMapPerProfile ( )
2023-12-12 22:59:48 -05:00
2024-10-01 23:24:03 -04:00
m [ "" ] [ nodeAdd ] = [ ] * QueueingHintFunction {
2023-06-08 00:54:30 -04:00
{
PluginName : "fooPlugin" ,
2023-10-19 07:02:11 -04:00
QueueingHintFn : queueHintReturnQueue ,
2023-06-08 00:54:30 -04:00
} ,
}
q := NewTestQueue ( ctx , newDefaultQueueSort ( ) , WithClock ( c ) , WithQueueingHintMapPerProfile ( m ) )
2023-07-17 18:53:07 -04:00
// To simulate the pod is failed in scheduling in the real world, Pop() the pod from activeQ before AddUnschedulableIfNotPresent()s below.
2024-08-21 09:40:41 -04:00
q . Add ( logger , unschedulablePodInfo . Pod )
2023-09-28 22:59:22 -04:00
if p , err := q . Pop ( logger ) ; err != nil || p . Pod != unschedulablePodInfo . Pod {
2023-07-17 18:53:07 -04:00
t . Errorf ( "Expected: %v after Pop, but got: %v" , unschedulablePodInfo . Pod . Name , p . Pod . Name )
}
2023-09-04 09:48:45 -04:00
expectInFlightPods ( t , q , unschedulablePodInfo . Pod . UID )
2024-08-21 09:40:41 -04:00
q . Add ( logger , highPriorityPodInfo . Pod )
2023-09-28 22:59:22 -04:00
if p , err := q . Pop ( logger ) ; err != nil || p . Pod != highPriorityPodInfo . Pod {
2023-07-17 18:53:07 -04:00
t . Errorf ( "Expected: %v after Pop, but got: %v" , highPriorityPodInfo . Pod . Name , p . Pod . Name )
}
2023-09-04 09:48:45 -04:00
expectInFlightPods ( t , q , unschedulablePodInfo . Pod . UID , highPriorityPodInfo . Pod . UID )
2024-11-11 16:29:19 -05:00
err := q . AddUnschedulableIfNotPresent ( logger , q . newQueuedPodInfo ( unschedulablePodInfo . Pod , "fooPlugin" ) , q . SchedulingCycle ( ) )
2023-09-11 12:30:11 -04:00
if err != nil {
t . Fatalf ( "unexpected error from AddUnschedulableIfNotPresent: %v" , err )
}
2024-11-11 16:29:19 -05:00
err = q . AddUnschedulableIfNotPresent ( logger , q . newQueuedPodInfo ( highPriorityPodInfo . Pod , "fooPlugin" ) , q . SchedulingCycle ( ) )
2023-09-11 12:30:11 -04:00
if err != nil {
t . Fatalf ( "unexpected error from AddUnschedulableIfNotPresent: %v" , err )
}
2023-09-04 09:48:45 -04:00
expectInFlightPods ( t , q )
2021-01-29 01:29:10 -05:00
// Construct a Pod, but don't associate its scheduler failure to any plugin
2023-09-04 09:48:45 -04:00
hpp1 := clonePod ( highPriorityPodInfo . Pod , "hpp1" )
2024-08-21 09:40:41 -04:00
q . Add ( logger , hpp1 )
2023-09-28 22:59:22 -04:00
if p , err := q . Pop ( logger ) ; err != nil || p . Pod != hpp1 {
2023-07-17 18:53:07 -04:00
t . Errorf ( "Expected: %v after Pop, but got: %v" , hpp1 , p . Pod . Name )
}
2023-09-04 09:48:45 -04:00
expectInFlightPods ( t , q , hpp1 . UID )
2023-09-11 12:30:11 -04:00
// This Pod will go to backoffQ because no failure plugin is associated with it.
2024-11-11 16:29:19 -05:00
hpp1PodInfo := q . newQueuedPodInfo ( hpp1 )
hpp1PodInfo . UnschedulableCount ++
err = q . AddUnschedulableIfNotPresent ( logger , hpp1PodInfo , q . SchedulingCycle ( ) )
2023-09-11 12:30:11 -04:00
if err != nil {
t . Fatalf ( "unexpected error from AddUnschedulableIfNotPresent: %v" , err )
}
2023-09-04 09:48:45 -04:00
expectInFlightPods ( t , q )
2021-01-29 01:29:10 -05:00
// Construct another Pod, and associate its scheduler failure to plugin "barPlugin".
2023-09-04 09:48:45 -04:00
hpp2 := clonePod ( highPriorityPodInfo . Pod , "hpp2" )
2024-08-21 09:40:41 -04:00
q . Add ( logger , hpp2 )
2023-09-28 22:59:22 -04:00
if p , err := q . Pop ( logger ) ; err != nil || p . Pod != hpp2 {
2023-07-17 18:53:07 -04:00
t . Errorf ( "Expected: %v after Pop, but got: %v" , hpp2 , p . Pod . Name )
}
2023-09-04 09:48:45 -04:00
expectInFlightPods ( t , q , hpp2 . UID )
2023-09-11 12:30:11 -04:00
// This Pod will go to the unschedulable Pod pool.
2024-11-11 16:29:19 -05:00
err = q . AddUnschedulableIfNotPresent ( logger , q . newQueuedPodInfo ( hpp2 , "barPlugin" ) , q . SchedulingCycle ( ) )
2023-09-11 12:30:11 -04:00
if err != nil {
t . Fatalf ( "unexpected error from AddUnschedulableIfNotPresent: %v" , err )
}
2023-09-04 09:48:45 -04:00
expectInFlightPods ( t , q )
2023-09-11 12:30:11 -04:00
// This NodeAdd event moves unschedulablePodInfo and highPriorityPodInfo to the backoffQ,
// because of the queueing hint function registered for NodeAdd/fooPlugin.
2024-10-01 23:24:03 -04:00
q . MoveAllToActiveOrBackoffQueue ( logger , nodeAdd , nil , nil , nil )
2023-07-17 18:53:07 -04:00
q . Add ( logger , medPriorityPodInfo . Pod )
2024-07-22 06:13:13 -04:00
if q . activeQ . len ( ) != 1 {
t . Errorf ( "Expected 1 item to be in activeQ, but got: %v" , q . activeQ . len ( ) )
2020-02-13 19:31:07 -05:00
}
2023-07-17 18:53:07 -04:00
// Pop out the medPriorityPodInfo in activeQ.
2023-09-28 22:59:22 -04:00
if p , err := q . Pop ( logger ) ; err != nil || p . Pod != medPriorityPodInfo . Pod {
2023-07-17 18:53:07 -04:00
t . Errorf ( "Expected: %v after Pop, but got: %v" , medPriorityPodInfo . Pod , p . Pod . Name )
}
2023-09-04 09:48:45 -04:00
expectInFlightPods ( t , q , medPriorityPodInfo . Pod . UID )
2021-01-29 01:29:10 -05:00
// hpp2 won't be moved.
2025-02-18 03:23:37 -05:00
if q . backoffQ . len ( ) != 3 {
t . Fatalf ( "Expected 3 items to be in backoffQ, but got: %v" , q . backoffQ . len ( ) )
2020-09-02 01:28:45 -04:00
}
// pop out the pods in the backoffQ.
2023-09-04 09:48:45 -04:00
// This doesn't make them in-flight pods.
2025-02-18 03:23:37 -05:00
c . Step ( q . backoffQ . podMaxBackoffDuration ( ) )
2025-03-10 04:56:21 -04:00
_ = q . backoffQ . popAllBackoffCompleted ( logger )
2023-09-04 09:48:45 -04:00
expectInFlightPods ( t , q , medPriorityPodInfo . Pod . UID )
2020-09-02 01:28:45 -04:00
2024-08-21 09:40:41 -04:00
q . Add ( logger , unschedulablePodInfo . Pod )
2023-09-28 22:59:22 -04:00
if p , err := q . Pop ( logger ) ; err != nil || p . Pod != unschedulablePodInfo . Pod {
2023-07-17 18:53:07 -04:00
t . Errorf ( "Expected: %v after Pop, but got: %v" , unschedulablePodInfo . Pod . Name , p . Pod . Name )
}
2023-09-04 09:48:45 -04:00
expectInFlightPods ( t , q , medPriorityPodInfo . Pod . UID , unschedulablePodInfo . Pod . UID )
2024-08-21 09:40:41 -04:00
q . Add ( logger , highPriorityPodInfo . Pod )
2023-09-28 22:59:22 -04:00
if p , err := q . Pop ( logger ) ; err != nil || p . Pod != highPriorityPodInfo . Pod {
2023-07-17 18:53:07 -04:00
t . Errorf ( "Expected: %v after Pop, but got: %v" , highPriorityPodInfo . Pod . Name , p . Pod . Name )
}
2023-09-04 09:48:45 -04:00
expectInFlightPods ( t , q , medPriorityPodInfo . Pod . UID , unschedulablePodInfo . Pod . UID , highPriorityPodInfo . Pod . UID )
2024-08-21 09:40:41 -04:00
q . Add ( logger , hpp1 )
2023-09-28 22:59:22 -04:00
if p , err := q . Pop ( logger ) ; err != nil || p . Pod != hpp1 {
2023-07-17 18:53:07 -04:00
t . Errorf ( "Expected: %v after Pop, but got: %v" , hpp1 , p . Pod . Name )
}
2024-11-11 16:29:19 -05:00
unschedulableQueuedPodInfo := q . newQueuedPodInfo ( unschedulablePodInfo . Pod , "fooPlugin" )
highPriorityQueuedPodInfo := q . newQueuedPodInfo ( highPriorityPodInfo . Pod , "fooPlugin" )
hpp1QueuedPodInfo := q . newQueuedPodInfo ( hpp1 )
2023-09-04 09:48:45 -04:00
expectInFlightPods ( t , q , medPriorityPodInfo . Pod . UID , unschedulablePodInfo . Pod . UID , highPriorityPodInfo . Pod . UID , hpp1 . UID )
2023-09-11 12:30:11 -04:00
err = q . AddUnschedulableIfNotPresent ( logger , unschedulableQueuedPodInfo , q . SchedulingCycle ( ) )
if err != nil {
t . Fatalf ( "unexpected error from AddUnschedulableIfNotPresent: %v" , err )
}
2023-09-04 09:48:45 -04:00
expectInFlightPods ( t , q , medPriorityPodInfo . Pod . UID , highPriorityPodInfo . Pod . UID , hpp1 . UID )
2023-09-11 12:30:11 -04:00
err = q . AddUnschedulableIfNotPresent ( logger , highPriorityQueuedPodInfo , q . SchedulingCycle ( ) )
if err != nil {
t . Fatalf ( "unexpected error from AddUnschedulableIfNotPresent: %v" , err )
}
2023-09-04 09:48:45 -04:00
expectInFlightPods ( t , q , medPriorityPodInfo . Pod . UID , hpp1 . UID )
2023-09-11 12:30:11 -04:00
err = q . AddUnschedulableIfNotPresent ( logger , hpp1QueuedPodInfo , q . SchedulingCycle ( ) )
if err != nil {
t . Fatalf ( "unexpected error from AddUnschedulableIfNotPresent: %v" , err )
}
2023-09-04 09:48:45 -04:00
expectInFlightPods ( t , q , medPriorityPodInfo . Pod . UID )
2023-07-17 18:53:07 -04:00
q . Add ( logger , medPriorityPodInfo . Pod )
2023-09-11 12:30:11 -04:00
// hpp1 will go to backoffQ because no failure plugin is associated with it.
// All plugins other than hpp1 are enqueued to the unschedulable Pod pool.
for _ , pod := range [ ] * v1 . Pod { unschedulablePodInfo . Pod , highPriorityPodInfo . Pod , hpp2 } {
2022-03-24 05:38:49 -04:00
if q . unschedulablePods . get ( pod ) == nil {
t . Errorf ( "Expected %v in the unschedulablePods" , pod . Name )
2021-01-29 01:29:10 -05:00
}
2020-09-02 01:28:45 -04:00
}
2025-02-18 03:23:37 -05:00
if ! q . backoffQ . has ( hpp1QueuedPodInfo ) {
t . Errorf ( "Expected %v in the backoffQ" , hpp1 . Name )
2023-09-11 12:30:11 -04:00
}
2024-11-11 16:29:19 -05:00
// all the remaining Pods should be in activeQ.
if q . activeQ . len ( ) != 1 {
t . Errorf ( "Expected %v in the activeQ" , medPriorityPodInfo . Pod . Name )
}
2023-09-11 12:30:11 -04:00
2025-03-07 05:22:36 -05:00
// Move clock by podMaxBackoffDuration, so that pods in the unschedulablePods would pass the backing off,
2020-09-02 01:28:45 -04:00
// and the pods will be moved into activeQ.
2025-03-07 05:22:36 -05:00
c . Step ( q . backoffQ . podMaxBackoffDuration ( ) )
2023-09-11 12:30:11 -04:00
q . flushBackoffQCompleted ( logger ) // flush the completed backoffQ to move hpp1 to activeQ.
2024-10-01 23:24:03 -04:00
q . MoveAllToActiveOrBackoffQueue ( logger , nodeAdd , nil , nil , nil )
2024-07-22 06:13:13 -04:00
if q . activeQ . len ( ) != 4 {
t . Errorf ( "Expected 4 items to be in activeQ, but got: %v" , q . activeQ . len ( ) )
2017-10-24 14:14:29 -04:00
}
2025-02-18 03:23:37 -05:00
if q . backoffQ . len ( ) != 0 {
t . Errorf ( "Expected 0 item to be in backoffQ, but got: %v" , q . backoffQ . len ( ) )
2020-09-02 01:28:45 -04:00
}
2023-09-04 09:48:45 -04:00
expectInFlightPods ( t , q , medPriorityPodInfo . Pod . UID )
2023-09-11 12:30:11 -04:00
if len ( q . unschedulablePods . podInfoMap ) != 1 {
// hpp2 won't be moved regardless of its backoff timer.
t . Errorf ( "Expected 1 item to be in unschedulablePods, but got: %v" , len ( q . unschedulablePods . podInfoMap ) )
}
2023-09-04 09:48:45 -04:00
}
2024-11-11 16:29:19 -05:00
func TestPriorityQueue_MoveAllToActiveOrBackoffQueueWithoutQueueingHint ( t * testing . T ) {
2023-12-14 01:34:03 -05:00
c := testingclock . NewFakeClock ( time . Now ( ) )
logger , ctx := ktesting . NewTestContext ( t )
ctx , cancel := context . WithCancel ( ctx )
defer cancel ( )
m := makeEmptyQueueingHintMapPerProfile ( )
2025-05-26 13:56:10 -04:00
featuregatetesting . SetFeatureGateEmulationVersionDuringTest ( t , utilfeature . DefaultFeatureGate , version . MustParse ( "1.33" ) )
2024-04-23 04:39:47 -04:00
featuregatetesting . SetFeatureGateDuringTest ( t , utilfeature . DefaultFeatureGate , features . SchedulerQueueingHints , false )
2024-10-01 23:24:03 -04:00
m [ "" ] [ nodeAdd ] = [ ] * QueueingHintFunction {
2023-12-14 01:34:03 -05:00
{
PluginName : "fooPlugin" ,
QueueingHintFn : queueHintReturnQueue ,
} ,
}
q := NewTestQueue ( ctx , newDefaultQueueSort ( ) , WithClock ( c ) , WithQueueingHintMapPerProfile ( m ) )
// To simulate the pod is failed in scheduling in the real world, Pop() the pod from activeQ before AddUnschedulableIfNotPresent()s below.
2024-07-17 07:25:31 -04:00
q . Add ( logger , medPriorityPodInfo . Pod )
2023-12-14 01:34:03 -05:00
2024-11-11 16:29:19 -05:00
err := q . AddUnschedulableIfNotPresent ( logger , q . newQueuedPodInfo ( unschedulablePodInfo . Pod , "fooPlugin" ) , q . SchedulingCycle ( ) )
2023-12-14 01:34:03 -05:00
if err != nil {
t . Fatalf ( "unexpected error from AddUnschedulableIfNotPresent: %v" , err )
}
2024-11-11 16:29:19 -05:00
err = q . AddUnschedulableIfNotPresent ( logger , q . newQueuedPodInfo ( highPriorityPodInfo . Pod , "fooPlugin" ) , q . SchedulingCycle ( ) )
2023-12-14 01:34:03 -05:00
if err != nil {
t . Fatalf ( "unexpected error from AddUnschedulableIfNotPresent: %v" , err )
}
// Construct a Pod, but don't associate its scheduler failure to any plugin
hpp1 := clonePod ( highPriorityPodInfo . Pod , "hpp1" )
// This Pod will go to backoffQ because no failure plugin is associated with it.
2024-11-11 16:29:19 -05:00
hpp1PodInfo := q . newQueuedPodInfo ( hpp1 )
hpp1PodInfo . UnschedulableCount ++
err = q . AddUnschedulableIfNotPresent ( logger , hpp1PodInfo , q . SchedulingCycle ( ) )
2023-12-14 01:34:03 -05:00
if err != nil {
t . Fatalf ( "unexpected error from AddUnschedulableIfNotPresent: %v" , err )
}
// Construct another Pod, and associate its scheduler failure to plugin "barPlugin".
hpp2 := clonePod ( highPriorityPodInfo . Pod , "hpp2" )
// This Pod will go to the unschedulable Pod pool.
2024-11-11 16:29:19 -05:00
err = q . AddUnschedulableIfNotPresent ( logger , q . newQueuedPodInfo ( hpp2 , "barPlugin" ) , q . SchedulingCycle ( ) )
2023-12-14 01:34:03 -05:00
if err != nil {
t . Fatalf ( "unexpected error from AddUnschedulableIfNotPresent: %v" , err )
}
// This NodeAdd event moves unschedulablePodInfo and highPriorityPodInfo to the backoffQ,
// because of the queueing hint function registered for NodeAdd/fooPlugin.
2024-10-01 23:24:03 -04:00
q . MoveAllToActiveOrBackoffQueue ( logger , nodeAdd , nil , nil , nil )
2024-07-22 06:13:13 -04:00
if q . activeQ . len ( ) != 1 {
t . Errorf ( "Expected 1 item to be in activeQ, but got: %v" , q . activeQ . len ( ) )
2023-12-14 01:34:03 -05:00
}
// Pop out the medPriorityPodInfo in activeQ.
if p , err := q . Pop ( logger ) ; err != nil || p . Pod != medPriorityPodInfo . Pod {
t . Errorf ( "Expected: %v after Pop, but got: %v" , medPriorityPodInfo . Pod , p . Pod . Name )
}
// hpp2 won't be moved.
2025-02-18 03:23:37 -05:00
if q . backoffQ . len ( ) != 3 {
t . Fatalf ( "Expected 3 items to be in backoffQ, but got: %v" , q . backoffQ . len ( ) )
2023-12-14 01:34:03 -05:00
}
// pop out the pods in the backoffQ.
// This doesn't make them in-flight pods.
2025-02-18 03:23:37 -05:00
c . Step ( q . backoffQ . podMaxBackoffDuration ( ) )
2025-03-10 04:56:21 -04:00
_ = q . backoffQ . popAllBackoffCompleted ( logger )
2023-12-14 01:34:03 -05:00
2024-11-11 16:29:19 -05:00
unschedulableQueuedPodInfo := q . newQueuedPodInfo ( unschedulablePodInfo . Pod , "fooPlugin" )
highPriorityQueuedPodInfo := q . newQueuedPodInfo ( highPriorityPodInfo . Pod , "fooPlugin" )
hpp1QueuedPodInfo := q . newQueuedPodInfo ( hpp1 )
2023-12-14 01:34:03 -05:00
err = q . AddUnschedulableIfNotPresent ( logger , unschedulableQueuedPodInfo , q . SchedulingCycle ( ) )
if err != nil {
t . Fatalf ( "unexpected error from AddUnschedulableIfNotPresent: %v" , err )
}
err = q . AddUnschedulableIfNotPresent ( logger , highPriorityQueuedPodInfo , q . SchedulingCycle ( ) )
if err != nil {
t . Fatalf ( "unexpected error from AddUnschedulableIfNotPresent: %v" , err )
}
err = q . AddUnschedulableIfNotPresent ( logger , hpp1QueuedPodInfo , q . SchedulingCycle ( ) )
if err != nil {
t . Fatalf ( "unexpected error from AddUnschedulableIfNotPresent: %v" , err )
}
2024-07-17 07:25:31 -04:00
q . Add ( logger , medPriorityPodInfo . Pod )
2023-12-14 01:34:03 -05:00
// hpp1 will go to backoffQ because no failure plugin is associated with it.
// All plugins other than hpp1 are enqueued to the unschedulable Pod pool.
for _ , pod := range [ ] * v1 . Pod { unschedulablePodInfo . Pod , highPriorityPodInfo . Pod , hpp2 } {
if q . unschedulablePods . get ( pod ) == nil {
t . Errorf ( "Expected %v in the unschedulablePods" , pod . Name )
}
}
2025-02-18 03:23:37 -05:00
if ! q . backoffQ . has ( hpp1QueuedPodInfo ) {
t . Errorf ( "Expected %v in the backoffQ" , hpp1 . Name )
}
2023-12-14 01:34:03 -05:00
2024-11-11 16:29:19 -05:00
// the remaining Pods should be in activeQ.
if q . activeQ . len ( ) != 1 {
t . Errorf ( "Expected %v in the activeQ" , medPriorityPodInfo . Pod . Name )
}
2025-03-07 05:22:36 -05:00
// Move clock by podMaxBackoffDuration, so that pods in the unschedulablePods would pass the backing off,
2023-12-14 01:34:03 -05:00
// and the pods will be moved into activeQ.
2025-03-07 05:22:36 -05:00
c . Step ( q . backoffQ . podMaxBackoffDuration ( ) )
2023-12-14 01:34:03 -05:00
q . flushBackoffQCompleted ( logger ) // flush the completed backoffQ to move hpp1 to activeQ.
2024-10-01 23:24:03 -04:00
q . MoveAllToActiveOrBackoffQueue ( logger , nodeAdd , nil , nil , nil )
2024-07-22 06:13:13 -04:00
if q . activeQ . len ( ) != 4 {
t . Errorf ( "Expected 4 items to be in activeQ, but got: %v" , q . activeQ . len ( ) )
2023-12-14 01:34:03 -05:00
}
2025-02-18 03:23:37 -05:00
if q . backoffQ . len ( ) != 0 {
t . Errorf ( "Expected 0 item to be in backoffQ, but got: %v" , q . backoffQ . len ( ) )
2023-12-14 01:34:03 -05:00
}
if len ( q . unschedulablePods . podInfoMap ) != 1 {
// hpp2 won't be moved regardless of its backoff timer.
t . Errorf ( "Expected 1 item to be in unschedulablePods, but got: %v" , len ( q . unschedulablePods . podInfoMap ) )
}
}
2023-09-04 09:48:45 -04:00
func clonePod ( pod * v1 . Pod , newName string ) * v1 . Pod {
pod = pod . DeepCopy ( )
pod . Name = newName
pod . UID = types . UID ( pod . Name + pod . Namespace )
return pod
}
func expectInFlightPods ( t * testing . T , q * PriorityQueue , uids ... types . UID ) {
t . Helper ( )
var actualUIDs [ ] types . UID
2024-07-22 06:13:13 -04:00
for _ , pod := range q . activeQ . listInFlightPods ( ) {
actualUIDs = append ( actualUIDs , pod . UID )
2023-09-04 09:48:45 -04:00
}
sortUIDs := cmpopts . SortSlices ( func ( a , b types . UID ) bool { return a < b } )
if diff := cmp . Diff ( uids , actualUIDs , sortUIDs ) ; diff != "" {
t . Fatalf ( "Unexpected content of inFlightPods (-want, +have):\n%s" , diff )
}
actualUIDs = nil
2024-07-22 06:13:13 -04:00
events := q . activeQ . listInFlightEvents ( )
for _ , e := range events {
if pod , ok := e . ( * v1 . Pod ) ; ok {
2023-09-04 09:48:45 -04:00
actualUIDs = append ( actualUIDs , pod . UID )
}
}
if diff := cmp . Diff ( uids , actualUIDs , sortUIDs ) ; diff != "" {
t . Fatalf ( "Unexpected pods in inFlightEvents (-want, +have):\n%s" , diff )
}
2017-10-24 14:14:29 -04:00
}
// TestPriorityQueue_AssignedPodAdded tests AssignedPodAdded. It checks that
2022-03-24 05:38:49 -04:00
// when a pod with pod affinity is in unschedulablePods and another pod with a
2017-10-24 14:14:29 -04:00
// matching label is added, the unschedulable pod is moved to activeQ.
2024-06-27 20:15:43 -04:00
func TestPriorityQueue_AssignedPodAdded_ ( t * testing . T ) {
tests := [ ] struct {
name string
unschedPod * v1 . Pod
unschedPlugin string
updatedAssignedPod * v1 . Pod
wantToRequeue bool
} {
2023-06-08 00:54:30 -04:00
{
2024-06-27 20:15:43 -04:00
name : "Pod rejected by pod affinity is requeued with matching Pod's update" ,
unschedPod : st . MakePod ( ) . Name ( "afp" ) . Namespace ( "ns1" ) . UID ( "afp" ) . Annotation ( "annot2" , "val2" ) . PodAffinityExists ( "service" , "region" , st . PodAffinityWithRequiredReq ) . Obj ( ) ,
unschedPlugin : names . InterPodAffinity ,
updatedAssignedPod : st . MakePod ( ) . Name ( "lbp" ) . Namespace ( "ns1" ) . Label ( "service" , "securityscan" ) . Node ( "node1" ) . Obj ( ) ,
wantToRequeue : true ,
2023-06-08 00:54:30 -04:00
} ,
2024-01-06 23:05:34 -05:00
{
2024-06-27 20:15:43 -04:00
name : "Pod rejected by pod affinity isn't requeued with unrelated Pod's update" ,
unschedPod : st . MakePod ( ) . Name ( "afp" ) . Namespace ( "ns1" ) . UID ( "afp" ) . Annotation ( "annot2" , "val2" ) . PodAffinityExists ( "service" , "region" , st . PodAffinityWithRequiredReq ) . Obj ( ) ,
unschedPlugin : names . InterPodAffinity ,
updatedAssignedPod : st . MakePod ( ) . Name ( "lbp" ) . Namespace ( "unrelated" ) . Label ( "unrelated" , "unrelated" ) . Node ( "node1" ) . Obj ( ) ,
wantToRequeue : false ,
} ,
{
name : "Pod rejected by pod topology spread is requeued with Pod's update in the same namespace" ,
unschedPod : st . MakePod ( ) . Name ( "tsp" ) . Namespace ( "ns1" ) . UID ( "tsp" ) . SpreadConstraint ( 1 , "node" , v1 . DoNotSchedule , nil , nil , nil , nil , nil ) . Obj ( ) ,
unschedPlugin : names . PodTopologySpread ,
updatedAssignedPod : st . MakePod ( ) . Name ( "lbp" ) . Namespace ( "ns1" ) . Label ( "service" , "securityscan" ) . Node ( "node1" ) . Obj ( ) ,
wantToRequeue : true ,
} ,
{
name : "Pod rejected by pod topology spread isn't requeued with unrelated Pod's update" ,
unschedPod : st . MakePod ( ) . Name ( "afp" ) . Namespace ( "ns1" ) . UID ( "afp" ) . Annotation ( "annot2" , "val2" ) . PodAffinityExists ( "service" , "region" , st . PodAffinityWithRequiredReq ) . Obj ( ) ,
unschedPlugin : names . PodTopologySpread ,
updatedAssignedPod : st . MakePod ( ) . Name ( "lbp" ) . Namespace ( "unrelated" ) . Label ( "unrelated" , "unrelated" ) . Node ( "node1" ) . Obj ( ) ,
wantToRequeue : false ,
} ,
{
name : "Pod rejected by other plugins isn't requeued with any Pod's update" ,
unschedPod : st . MakePod ( ) . Name ( "afp" ) . Namespace ( "ns1" ) . UID ( "afp" ) . Annotation ( "annot2" , "val2" ) . Obj ( ) ,
unschedPlugin : "fakePlugin" ,
updatedAssignedPod : st . MakePod ( ) . Name ( "lbp" ) . Namespace ( "unrelated" ) . Label ( "unrelated" , "unrelated" ) . Node ( "node1" ) . Obj ( ) ,
wantToRequeue : false ,
2024-01-06 23:05:34 -05:00
} ,
}
2020-02-13 19:31:07 -05:00
2024-06-27 20:15:43 -04:00
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
logger , ctx := ktesting . NewTestContext ( t )
ctx , cancel := context . WithCancel ( ctx )
defer cancel ( )
c := testingclock . NewFakeClock ( time . Now ( ) )
m := makeEmptyQueueingHintMapPerProfile ( )
2024-10-01 23:24:03 -04:00
m [ "" ] [ framework . EventAssignedPodAdd ] = [ ] * QueueingHintFunction {
2024-06-27 20:15:43 -04:00
{
PluginName : "fakePlugin" ,
QueueingHintFn : queueHintReturnQueue ,
} ,
{
PluginName : names . InterPodAffinity ,
QueueingHintFn : queueHintReturnQueue ,
} ,
{
PluginName : names . PodTopologySpread ,
QueueingHintFn : queueHintReturnQueue ,
} ,
}
q := NewTestQueue ( ctx , newDefaultQueueSort ( ) , WithClock ( c ) , WithQueueingHintMapPerProfile ( m ) )
// To simulate the pod is failed in scheduling in the real world, Pop() the pod from activeQ before AddUnschedulableIfNotPresent()s below.
2024-08-21 09:40:41 -04:00
q . Add ( logger , tt . unschedPod )
2024-06-27 20:15:43 -04:00
if p , err := q . Pop ( logger ) ; err != nil || p . Pod != tt . unschedPod {
t . Errorf ( "Expected: %v after Pop, but got: %v" , tt . unschedPod . Name , p . Pod . Name )
}
err := q . AddUnschedulableIfNotPresent ( logger , q . newQueuedPodInfo ( tt . unschedPod , tt . unschedPlugin ) , q . SchedulingCycle ( ) )
if err != nil {
t . Fatalf ( "unexpected error from AddUnschedulableIfNotPresent: %v" , err )
}
// Move clock to make the unschedulable pods complete backoff.
c . Step ( DefaultPodInitialBackoffDuration + time . Second )
q . AssignedPodAdded ( logger , tt . updatedAssignedPod )
2024-08-20 04:51:21 -04:00
if q . activeQ . has ( newQueuedPodInfoForLookup ( tt . unschedPod ) ) != tt . wantToRequeue {
2024-10-21 21:01:20 -04:00
t . Fatalf ( "unexpected Pod move: Pod should be requeued: %v. Pod is actually requeued: %v" , tt . wantToRequeue , ! tt . wantToRequeue )
}
} )
}
}
func TestPriorityQueue_AssignedPodUpdated ( t * testing . T ) {
tests := [ ] struct {
name string
unschedPod * v1 . Pod
unschedPlugin string
updatedAssignedPod * v1 . Pod
2025-06-26 11:06:29 -04:00
event fwk . ClusterEvent
2024-10-21 21:01:20 -04:00
wantToRequeue bool
} {
{
name : "Pod rejected by pod affinity is requeued with matching Pod's update" ,
unschedPod : st . MakePod ( ) . Name ( "afp" ) . Namespace ( "ns1" ) . UID ( "afp" ) . Annotation ( "annot2" , "val2" ) . PodAffinityExists ( "service" , "region" , st . PodAffinityWithRequiredReq ) . Obj ( ) ,
unschedPlugin : names . InterPodAffinity ,
2025-06-26 11:06:29 -04:00
event : fwk . ClusterEvent { Resource : fwk . Pod , ActionType : fwk . UpdatePodLabel } ,
2024-10-21 21:01:20 -04:00
updatedAssignedPod : st . MakePod ( ) . Name ( "lbp" ) . Namespace ( "ns1" ) . Label ( "service" , "securityscan" ) . Node ( "node1" ) . Obj ( ) ,
wantToRequeue : true ,
} ,
{
name : "Pod rejected by pod affinity isn't requeued with unrelated Pod's update" ,
unschedPod : st . MakePod ( ) . Name ( "afp" ) . Namespace ( "ns1" ) . UID ( "afp" ) . Annotation ( "annot2" , "val2" ) . PodAffinityExists ( "service" , "region" , st . PodAffinityWithRequiredReq ) . Obj ( ) ,
unschedPlugin : names . InterPodAffinity ,
2025-06-26 11:06:29 -04:00
event : fwk . ClusterEvent { Resource : fwk . Pod , ActionType : fwk . UpdatePodLabel } ,
2024-10-21 21:01:20 -04:00
updatedAssignedPod : st . MakePod ( ) . Name ( "lbp" ) . Namespace ( "unrelated" ) . Label ( "unrelated" , "unrelated" ) . Node ( "node1" ) . Obj ( ) ,
wantToRequeue : false ,
} ,
{
name : "Pod rejected by pod topology spread is requeued with Pod's update in the same namespace" ,
unschedPod : st . MakePod ( ) . Name ( "tsp" ) . Namespace ( "ns1" ) . UID ( "tsp" ) . SpreadConstraint ( 1 , "node" , v1 . DoNotSchedule , nil , nil , nil , nil , nil ) . Obj ( ) ,
unschedPlugin : names . PodTopologySpread ,
2025-06-26 11:06:29 -04:00
event : fwk . ClusterEvent { Resource : fwk . Pod , ActionType : fwk . UpdatePodLabel } ,
2024-10-21 21:01:20 -04:00
updatedAssignedPod : st . MakePod ( ) . Name ( "lbp" ) . Namespace ( "ns1" ) . Label ( "service" , "securityscan" ) . Node ( "node1" ) . Obj ( ) ,
wantToRequeue : true ,
} ,
{
name : "Pod rejected by pod topology spread isn't requeued with unrelated Pod's update" ,
unschedPod : st . MakePod ( ) . Name ( "afp" ) . Namespace ( "ns1" ) . UID ( "afp" ) . Annotation ( "annot2" , "val2" ) . PodAffinityExists ( "service" , "region" , st . PodAffinityWithRequiredReq ) . Obj ( ) ,
unschedPlugin : names . PodTopologySpread ,
2025-06-26 11:06:29 -04:00
event : fwk . ClusterEvent { Resource : fwk . Pod , ActionType : fwk . UpdatePodLabel } ,
2024-10-21 21:01:20 -04:00
updatedAssignedPod : st . MakePod ( ) . Name ( "lbp" ) . Namespace ( "unrelated" ) . Label ( "unrelated" , "unrelated" ) . Node ( "node1" ) . Obj ( ) ,
wantToRequeue : false ,
} ,
{
name : "Pod rejected by resource fit is requeued with assigned Pod's scale down" ,
unschedPod : st . MakePod ( ) . Name ( "rp" ) . Namespace ( "ns1" ) . UID ( "afp" ) . Annotation ( "annot2" , "val2" ) . Req ( map [ v1 . ResourceName ] string { v1 . ResourceCPU : "1" } ) . Obj ( ) ,
unschedPlugin : names . NodeResourcesFit ,
2025-06-26 11:06:29 -04:00
event : fwk . ClusterEvent { Resource : fwk . EventResource ( "AssignedPod" ) , ActionType : fwk . UpdatePodScaleDown } ,
2024-10-21 21:01:20 -04:00
updatedAssignedPod : st . MakePod ( ) . Name ( "lbp" ) . Namespace ( "ns2" ) . Node ( "node1" ) . Obj ( ) ,
wantToRequeue : true ,
} ,
{
name : "Pod rejected by other plugins isn't requeued with any Pod's update" ,
unschedPod : st . MakePod ( ) . Name ( "afp" ) . Namespace ( "ns1" ) . UID ( "afp" ) . Annotation ( "annot2" , "val2" ) . Obj ( ) ,
unschedPlugin : "fakePlugin" ,
2025-06-26 11:06:29 -04:00
event : fwk . ClusterEvent { Resource : fwk . Pod , ActionType : fwk . UpdatePodLabel } ,
2024-10-21 21:01:20 -04:00
updatedAssignedPod : st . MakePod ( ) . Name ( "lbp" ) . Namespace ( "unrelated" ) . Label ( "unrelated" , "unrelated" ) . Node ( "node1" ) . Obj ( ) ,
wantToRequeue : false ,
} ,
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
logger , ctx := ktesting . NewTestContext ( t )
ctx , cancel := context . WithCancel ( ctx )
defer cancel ( )
c := testingclock . NewFakeClock ( time . Now ( ) )
m := makeEmptyQueueingHintMapPerProfile ( )
2025-06-26 11:06:29 -04:00
m [ "" ] = map [ fwk . ClusterEvent ] [ ] * QueueingHintFunction {
{ Resource : fwk . Pod , ActionType : fwk . UpdatePodLabel } : {
2024-10-21 21:01:20 -04:00
{
PluginName : "fakePlugin" ,
QueueingHintFn : queueHintReturnQueue ,
} ,
{
PluginName : names . InterPodAffinity ,
QueueingHintFn : queueHintReturnQueue ,
} ,
{
PluginName : names . PodTopologySpread ,
QueueingHintFn : queueHintReturnQueue ,
} ,
} ,
2025-06-26 11:06:29 -04:00
{ Resource : fwk . Pod , ActionType : fwk . UpdatePodScaleDown } : {
2024-10-21 21:01:20 -04:00
{
PluginName : names . NodeResourcesFit ,
QueueingHintFn : queueHintReturnQueue ,
} ,
} ,
}
q := NewTestQueue ( ctx , newDefaultQueueSort ( ) , WithClock ( c ) , WithQueueingHintMapPerProfile ( m ) )
// To simulate the pod is failed in scheduling in the real world, Pop() the pod from activeQ before AddUnschedulableIfNotPresent()s below.
q . Add ( logger , tt . unschedPod )
if p , err := q . Pop ( logger ) ; err != nil || p . Pod != tt . unschedPod {
t . Errorf ( "Expected: %v after Pop, but got: %v" , tt . unschedPod . Name , p . Pod . Name )
}
err := q . AddUnschedulableIfNotPresent ( logger , q . newQueuedPodInfo ( tt . unschedPod , tt . unschedPlugin ) , q . SchedulingCycle ( ) )
if err != nil {
t . Fatalf ( "unexpected error from AddUnschedulableIfNotPresent: %v" , err )
}
// Move clock to make the unschedulable pods complete backoff.
c . Step ( DefaultPodInitialBackoffDuration + time . Second )
q . AssignedPodUpdated ( logger , nil , tt . updatedAssignedPod , tt . event )
if q . activeQ . has ( newQueuedPodInfoForLookup ( tt . unschedPod ) ) != tt . wantToRequeue {
2024-07-17 07:25:31 -04:00
t . Fatalf ( "unexpected Pod move: Pod should be requeued: %v. Pod is actually requeued: %v" , tt . wantToRequeue , ! tt . wantToRequeue )
2024-06-27 20:15:43 -04:00
}
} )
2017-10-24 14:14:29 -04:00
}
}
2018-12-18 02:41:53 -05:00
func TestPriorityQueue_NominatedPodsForNode ( t * testing . T ) {
2021-06-05 14:30:17 -04:00
objs := [ ] runtime . Object { medPriorityPodInfo . Pod , unschedulablePodInfo . Pod , highPriorityPodInfo . Pod }
2023-03-22 07:48:04 -04:00
logger , ctx := ktesting . NewTestContext ( t )
ctx , cancel := context . WithCancel ( ctx )
2022-01-22 14:00:56 -05:00
defer cancel ( )
q := NewTestQueueWithObjects ( ctx , newDefaultQueueSort ( ) , objs )
2023-03-22 07:48:04 -04:00
q . Add ( logger , medPriorityPodInfo . Pod )
q . Add ( logger , unschedulablePodInfo . Pod )
q . Add ( logger , highPriorityPodInfo . Pod )
2023-09-28 22:59:22 -04:00
if p , err := q . Pop ( logger ) ; err != nil || p . Pod != highPriorityPodInfo . Pod {
2021-02-22 09:00:23 -05:00
t . Errorf ( "Expected: %v after Pop, but got: %v" , highPriorityPodInfo . Pod . Name , p . Pod . Name )
2018-02-08 21:19:31 -05:00
}
2025-06-11 05:56:18 -04:00
expectedList := [ ] fwk . PodInfo { medPriorityPodInfo , unschedulablePodInfo }
2021-07-08 23:56:43 -04:00
podInfos := q . NominatedPodsForNode ( "node1" )
2025-01-02 07:47:57 -05:00
if diff := cmp . Diff ( expectedList , podInfos , cmpopts . IgnoreUnexported ( framework . PodInfo { } ) ) ; diff != "" {
2021-07-08 23:56:43 -04:00
t . Errorf ( "Unexpected list of nominated Pods for node: (-want, +got):\n%s" , diff )
2018-02-08 21:19:31 -05:00
}
2025-06-11 05:56:18 -04:00
podInfos [ 0 ] . GetPod ( ) . Name = "not mpp"
2025-01-02 07:47:57 -05:00
if diff := cmp . Diff ( podInfos , q . NominatedPodsForNode ( "node1" ) , cmpopts . IgnoreUnexported ( framework . PodInfo { } ) ) ; diff == "" {
2021-07-08 23:56:43 -04:00
t . Error ( "Expected list of nominated Pods for node2 is different from podInfos" )
}
if len ( q . NominatedPodsForNode ( "node2" ) ) != 0 {
2018-02-08 21:19:31 -05:00
t . Error ( "Expected list of nominated Pods for node2 to be empty." )
}
}
2021-06-04 14:04:44 -04:00
func TestPriorityQueue_NominatedPodDeleted ( t * testing . T ) {
tests := [ ] struct {
name string
podInfo * framework . PodInfo
deletePod bool
2024-07-01 07:37:53 -04:00
wantLen int
2021-06-04 14:04:44 -04:00
} {
{
name : "alive pod gets added into PodNominator" ,
podInfo : medPriorityPodInfo ,
2024-07-01 07:37:53 -04:00
wantLen : 1 ,
2021-06-04 14:04:44 -04:00
} ,
{
name : "deleted pod shouldn't be added into PodNominator" ,
podInfo : highPriNominatedPodInfo ,
deletePod : true ,
2024-07-01 07:37:53 -04:00
wantLen : 0 ,
2021-06-04 14:04:44 -04:00
} ,
{
name : "pod without .status.nominatedPodName specified shouldn't be added into PodNominator" ,
podInfo : highPriorityPodInfo ,
2024-07-01 07:37:53 -04:00
wantLen : 0 ,
2021-06-04 14:04:44 -04:00
} ,
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
2024-09-09 17:59:24 -04:00
logger , ctx := ktesting . NewTestContext ( t )
2024-07-03 02:18:25 -04:00
cs := fake . NewClientset ( tt . podInfo . Pod )
2021-06-04 14:04:44 -04:00
informerFactory := informers . NewSharedInformerFactory ( cs , 0 )
podLister := informerFactory . Core ( ) . V1 ( ) . Pods ( ) . Lister ( )
// Build a PriorityQueue.
2023-03-08 16:18:36 -05:00
q := NewPriorityQueue ( newDefaultQueueSort ( ) , informerFactory , WithPodLister ( podLister ) )
2024-09-09 17:59:24 -04:00
ctx , cancel := context . WithCancel ( ctx )
2022-01-22 14:00:56 -05:00
defer cancel ( )
2021-06-04 14:04:44 -04:00
informerFactory . Start ( ctx . Done ( ) )
informerFactory . WaitForCacheSync ( ctx . Done ( ) )
if tt . deletePod {
// Simulate that the test pod gets deleted physically.
informerFactory . Core ( ) . V1 ( ) . Pods ( ) . Informer ( ) . GetStore ( ) . Delete ( tt . podInfo . Pod )
}
2023-03-22 07:48:04 -04:00
q . AddNominatedPod ( logger , tt . podInfo , nil )
2021-06-04 14:04:44 -04:00
2024-07-01 07:37:53 -04:00
if got := len ( q . NominatedPodsForNode ( tt . podInfo . Pod . Status . NominatedNodeName ) ) ; got != tt . wantLen {
t . Errorf ( "Expected %v nominated pods for node, but got %v" , tt . wantLen , got )
2021-06-04 14:04:44 -04:00
}
} )
}
}
2018-12-20 20:28:23 -05:00
func TestPriorityQueue_PendingPods ( t * testing . T ) {
2019-02-11 19:38:26 -05:00
makeSet := func ( pods [ ] * v1 . Pod ) map [ * v1 . Pod ] struct { } {
pendingSet := map [ * v1 . Pod ] struct { } { }
for _ , p := range pods {
pendingSet [ p ] = struct { } { }
}
return pendingSet
}
2023-03-22 07:48:04 -04:00
logger , ctx := ktesting . NewTestContext ( t )
ctx , cancel := context . WithCancel ( ctx )
2022-01-22 14:00:56 -05:00
defer cancel ( )
q := NewTestQueue ( ctx , newDefaultQueueSort ( ) )
2023-07-17 18:53:07 -04:00
// To simulate the pod is failed in scheduling in the real world, Pop() the pod from activeQ before AddUnschedulableIfNotPresent()s below.
2024-08-21 09:40:41 -04:00
q . Add ( logger , unschedulablePodInfo . Pod )
2023-09-28 22:59:22 -04:00
if p , err := q . Pop ( logger ) ; err != nil || p . Pod != unschedulablePodInfo . Pod {
2023-07-17 18:53:07 -04:00
t . Errorf ( "Expected: %v after Pop, but got: %v" , unschedulablePodInfo . Pod . Name , p . Pod . Name )
}
2024-08-21 09:40:41 -04:00
q . Add ( logger , highPriorityPodInfo . Pod )
2023-09-28 22:59:22 -04:00
if p , err := q . Pop ( logger ) ; err != nil || p . Pod != highPriorityPodInfo . Pod {
2023-07-17 18:53:07 -04:00
t . Errorf ( "Expected: %v after Pop, but got: %v" , highPriorityPodInfo . Pod . Name , p . Pod . Name )
}
2023-03-22 07:48:04 -04:00
q . Add ( logger , medPriorityPodInfo . Pod )
2024-11-11 16:29:19 -05:00
err := q . AddUnschedulableIfNotPresent ( logger , q . newQueuedPodInfo ( unschedulablePodInfo . Pod , "plugin" ) , q . SchedulingCycle ( ) )
2023-09-11 12:30:11 -04:00
if err != nil {
t . Fatalf ( "unexpected error from AddUnschedulableIfNotPresent: %v" , err )
}
2024-11-11 16:29:19 -05:00
err = q . AddUnschedulableIfNotPresent ( logger , q . newQueuedPodInfo ( highPriorityPodInfo . Pod , "plugin" ) , q . SchedulingCycle ( ) )
2023-09-11 12:30:11 -04:00
if err != nil {
t . Fatalf ( "unexpected error from AddUnschedulableIfNotPresent: %v" , err )
}
2020-02-13 19:31:07 -05:00
2021-02-22 09:00:23 -05:00
expectedSet := makeSet ( [ ] * v1 . Pod { medPriorityPodInfo . Pod , unschedulablePodInfo . Pod , highPriorityPodInfo . Pod } )
2022-08-05 21:30:09 -04:00
gotPods , gotSummary := q . PendingPods ( )
2023-06-29 03:28:42 -04:00
if diff := cmp . Diff ( expectedSet , makeSet ( gotPods ) ) ; diff != "" {
t . Errorf ( "Unexpected list of pending Pods (-want, +got):\n%s" , diff )
2018-12-20 20:28:23 -05:00
}
2022-08-05 21:30:09 -04:00
if wantSummary := fmt . Sprintf ( pendingPodsSummary , 1 , 0 , 2 ) ; wantSummary != gotSummary {
t . Errorf ( "Unexpected pending pods summary: want %v, but got %v." , wantSummary , gotSummary )
}
2018-12-20 20:28:23 -05:00
// Move all to active queue. We should still see the same set of pods.
2024-10-01 23:24:03 -04:00
q . MoveAllToActiveOrBackoffQueue ( logger , framework . EventUnschedulableTimeout , nil , nil , nil )
2022-08-05 21:30:09 -04:00
gotPods , gotSummary = q . PendingPods ( )
2023-06-29 03:28:42 -04:00
if diff := cmp . Diff ( expectedSet , makeSet ( gotPods ) ) ; diff != "" {
t . Errorf ( "Unexpected list of pending Pods (-want, +got):\n%s" , diff )
2022-08-05 21:30:09 -04:00
}
if wantSummary := fmt . Sprintf ( pendingPodsSummary , 1 , 2 , 0 ) ; wantSummary != gotSummary {
t . Errorf ( "Unexpected pending pods summary: want %v, but got %v." , wantSummary , gotSummary )
2018-12-20 20:28:23 -05:00
}
}
2018-12-18 02:41:53 -05:00
func TestPriorityQueue_UpdateNominatedPodForNode ( t * testing . T ) {
2023-03-22 07:48:04 -04:00
logger , ctx := ktesting . NewTestContext ( t )
ctx , cancel := context . WithCancel ( ctx )
2022-01-22 14:00:56 -05:00
defer cancel ( )
2023-03-22 07:48:04 -04:00
objs := [ ] runtime . Object { medPriorityPodInfo . Pod , unschedulablePodInfo . Pod , highPriorityPodInfo . Pod , scheduledPodInfo . Pod }
2022-01-22 14:00:56 -05:00
q := NewTestQueueWithObjects ( ctx , newDefaultQueueSort ( ) , objs )
2024-07-17 07:25:31 -04:00
q . Add ( logger , medPriorityPodInfo . Pod )
2021-02-22 09:00:23 -05:00
// Update unschedulablePodInfo on a different node than specified in the pod.
2023-03-22 07:48:04 -04:00
q . AddNominatedPod ( logger , mustNewTestPodInfo ( t , unschedulablePodInfo . Pod ) ,
2025-07-24 07:48:07 -04:00
& fwk . NominatingInfo { NominatingMode : fwk . ModeOverride , NominatedNodeName : "node5" } )
2018-12-18 02:41:53 -05:00
// Update nominated node name of a pod on a node that is not specified in the pod object.
2023-03-22 07:48:04 -04:00
q . AddNominatedPod ( logger , mustNewTestPodInfo ( t , highPriorityPodInfo . Pod ) ,
2025-07-24 07:48:07 -04:00
& fwk . NominatingInfo { NominatingMode : fwk . ModeOverride , NominatedNodeName : "node2" } )
2021-06-05 14:30:17 -04:00
expectedNominatedPods := & nominator {
2018-12-18 02:41:53 -05:00
nominatedPodToNode : map [ types . UID ] string {
2021-02-22 09:00:23 -05:00
medPriorityPodInfo . Pod . UID : "node1" ,
highPriorityPodInfo . Pod . UID : "node2" ,
unschedulablePodInfo . Pod . UID : "node5" ,
2018-12-18 02:41:53 -05:00
} ,
2024-07-22 07:56:01 -04:00
nominatedPods : map [ string ] [ ] podRef {
"node1" : { podToRef ( medPriorityPodInfo . Pod ) } ,
"node2" : { podToRef ( highPriorityPodInfo . Pod ) } ,
"node5" : { podToRef ( unschedulablePodInfo . Pod ) } ,
2018-12-18 02:41:53 -05:00
} ,
}
2023-03-08 16:18:36 -05:00
if diff := cmp . Diff ( q . nominator , expectedNominatedPods , nominatorCmpOpts ... ) ; diff != "" {
2021-06-04 14:04:44 -04:00
t . Errorf ( "Unexpected diff after adding pods (-want, +got):\n%s" , diff )
2018-12-18 02:41:53 -05:00
}
2023-09-28 22:59:22 -04:00
if p , err := q . Pop ( logger ) ; err != nil || p . Pod != medPriorityPodInfo . Pod {
2021-02-22 09:00:23 -05:00
t . Errorf ( "Expected: %v after Pop, but got: %v" , medPriorityPodInfo . Pod . Name , p . Pod . Name )
2018-12-18 02:41:53 -05:00
}
// List of nominated pods shouldn't change after popping them from the queue.
2023-03-08 16:18:36 -05:00
if diff := cmp . Diff ( q . nominator , expectedNominatedPods , nominatorCmpOpts ... ) ; diff != "" {
2021-06-04 14:04:44 -04:00
t . Errorf ( "Unexpected diff after popping pods (-want, +got):\n%s" , diff )
2018-12-18 02:41:53 -05:00
}
// Update one of the nominated pods that doesn't have nominatedNodeName in the
// pod object. It should be updated correctly.
2025-07-24 07:48:07 -04:00
q . AddNominatedPod ( logger , highPriorityPodInfo , & fwk . NominatingInfo { NominatingMode : fwk . ModeOverride , NominatedNodeName : "node4" } )
2021-06-05 14:30:17 -04:00
expectedNominatedPods = & nominator {
2018-12-18 02:41:53 -05:00
nominatedPodToNode : map [ types . UID ] string {
2021-02-22 09:00:23 -05:00
medPriorityPodInfo . Pod . UID : "node1" ,
highPriorityPodInfo . Pod . UID : "node4" ,
unschedulablePodInfo . Pod . UID : "node5" ,
2018-12-18 02:41:53 -05:00
} ,
2024-07-22 07:56:01 -04:00
nominatedPods : map [ string ] [ ] podRef {
"node1" : { podToRef ( medPriorityPodInfo . Pod ) } ,
"node4" : { podToRef ( highPriorityPodInfo . Pod ) } ,
"node5" : { podToRef ( unschedulablePodInfo . Pod ) } ,
2018-12-18 02:41:53 -05:00
} ,
}
2023-03-08 16:18:36 -05:00
if diff := cmp . Diff ( q . nominator , expectedNominatedPods , nominatorCmpOpts ... ) ; diff != "" {
2021-06-04 14:04:44 -04:00
t . Errorf ( "Unexpected diff after updating pods (-want, +got):\n%s" , diff )
2018-12-18 02:41:53 -05:00
}
2022-04-01 15:21:10 -04:00
// Attempt to nominate a pod that was deleted from the informer cache.
// Nothing should change.
2025-07-24 07:48:07 -04:00
q . AddNominatedPod ( logger , nonExistentPodInfo , & fwk . NominatingInfo { NominatingMode : fwk . ModeOverride , NominatedNodeName : "node1" } )
2023-03-08 16:18:36 -05:00
if diff := cmp . Diff ( q . nominator , expectedNominatedPods , nominatorCmpOpts ... ) ; diff != "" {
2022-04-01 15:21:10 -04:00
t . Errorf ( "Unexpected diff after nominating a deleted pod (-want, +got):\n%s" , diff )
}
// Attempt to nominate a pod that was already scheduled in the informer cache.
// Nothing should change.
scheduledPodCopy := scheduledPodInfo . Pod . DeepCopy ( )
scheduledPodInfo . Pod . Spec . NodeName = ""
2025-07-24 07:48:07 -04:00
q . AddNominatedPod ( logger , mustNewTestPodInfo ( t , scheduledPodCopy ) , & fwk . NominatingInfo { NominatingMode : fwk . ModeOverride , NominatedNodeName : "node1" } )
2023-03-08 16:18:36 -05:00
if diff := cmp . Diff ( q . nominator , expectedNominatedPods , nominatorCmpOpts ... ) ; diff != "" {
2022-04-01 15:21:10 -04:00
t . Errorf ( "Unexpected diff after nominating a scheduled pod (-want, +got):\n%s" , diff )
}
2018-12-18 02:41:53 -05:00
// Delete a nominated pod that doesn't have nominatedNodeName in the pod
// object. It should be deleted.
2021-02-22 09:00:23 -05:00
q . DeleteNominatedPodIfExists ( highPriorityPodInfo . Pod )
2021-06-05 14:30:17 -04:00
expectedNominatedPods = & nominator {
2018-12-18 02:41:53 -05:00
nominatedPodToNode : map [ types . UID ] string {
2021-02-22 09:00:23 -05:00
medPriorityPodInfo . Pod . UID : "node1" ,
unschedulablePodInfo . Pod . UID : "node5" ,
2018-12-18 02:41:53 -05:00
} ,
2024-07-22 07:56:01 -04:00
nominatedPods : map [ string ] [ ] podRef {
"node1" : { podToRef ( medPriorityPodInfo . Pod ) } ,
"node5" : { podToRef ( unschedulablePodInfo . Pod ) } ,
2018-12-18 02:41:53 -05:00
} ,
}
2023-03-08 16:18:36 -05:00
if diff := cmp . Diff ( q . nominator , expectedNominatedPods , nominatorCmpOpts ... ) ; diff != "" {
2021-06-04 14:04:44 -04:00
t . Errorf ( "Unexpected diff after deleting pods (-want, +got):\n%s" , diff )
2018-12-18 02:41:53 -05:00
}
}
2019-08-11 20:26:32 -04:00
func TestPriorityQueue_NewWithOptions ( t * testing . T ) {
2024-09-09 17:59:24 -04:00
_ , ctx := ktesting . NewTestContext ( t )
ctx , cancel := context . WithCancel ( ctx )
2022-01-22 14:00:56 -05:00
defer cancel ( )
q := NewTestQueue ( ctx ,
2020-02-13 11:08:46 -05:00
newDefaultQueueSort ( ) ,
2019-08-11 20:26:32 -04:00
WithPodInitialBackoffDuration ( 2 * time . Second ) ,
WithPodMaxBackoffDuration ( 20 * time . Second ) ,
)
2025-02-18 03:23:37 -05:00
if q . backoffQ . podInitialBackoffDuration ( ) != 2 * time . Second {
t . Errorf ( "Unexpected pod backoff initial duration. Expected: %v, got: %v" , 2 * time . Second , q . backoffQ . podInitialBackoffDuration ( ) )
2019-08-11 20:26:32 -04:00
}
2025-02-18 03:23:37 -05:00
if q . backoffQ . podMaxBackoffDuration ( ) != 20 * time . Second {
t . Errorf ( "Unexpected pod backoff max duration. Expected: %v, got: %v" , 2 * time . Second , q . backoffQ . podMaxBackoffDuration ( ) )
2019-08-11 20:26:32 -04:00
}
}
2018-09-14 19:59:54 -04:00
func TestSchedulingQueue_Close ( t * testing . T ) {
2023-09-28 22:59:22 -04:00
logger , ctx := ktesting . NewTestContext ( t )
ctx , cancel := context . WithCancel ( ctx )
2022-01-22 14:00:56 -05:00
defer cancel ( )
q := NewTestQueue ( ctx , newDefaultQueueSort ( ) )
2020-06-07 09:52:16 -04:00
wg := sync . WaitGroup { }
wg . Add ( 1 )
go func ( ) {
defer wg . Done ( )
2023-09-28 22:59:22 -04:00
pod , err := q . Pop ( logger )
2023-03-14 08:22:22 -04:00
if err != nil {
t . Errorf ( "Expected nil err from Pop() if queue is closed, but got %q" , err . Error ( ) )
2020-06-07 09:52:16 -04:00
}
if pod != nil {
t . Errorf ( "Expected pod nil from Pop() if queue is closed, but got: %v" , pod )
}
} ( )
q . Close ( )
wg . Wait ( )
2018-09-14 19:59:54 -04:00
}
2018-11-27 19:46:24 -05:00
// TestRecentlyTriedPodsGoBack tests that pods which are recently tried and are
// unschedulable go behind other pods with the same priority. This behavior
// ensures that an unschedulable pod does not block head of the queue when there
// are frequent events that move pods to the active queue.
func TestRecentlyTriedPodsGoBack ( t * testing . T ) {
2021-09-17 05:48:22 -04:00
c := testingclock . NewFakeClock ( time . Now ( ) )
2023-03-22 07:48:04 -04:00
logger , ctx := ktesting . NewTestContext ( t )
ctx , cancel := context . WithCancel ( ctx )
2022-01-22 14:00:56 -05:00
defer cancel ( )
q := NewTestQueue ( ctx , newDefaultQueueSort ( ) , WithClock ( c ) )
2018-11-27 19:46:24 -05:00
// Add a few pods to priority queue.
for i := 0 ; i < 5 ; i ++ {
2022-04-18 21:06:58 -04:00
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 ( )
2023-03-22 07:48:04 -04:00
q . Add ( logger , p )
2018-11-27 19:46:24 -05:00
}
2020-05-05 03:11:04 -04:00
c . Step ( time . Microsecond )
2018-11-27 19:46:24 -05:00
// Simulate a pod being popped by the scheduler, determined unschedulable, and
// then moved back to the active queue.
2023-09-28 22:59:22 -04:00
p1 , err := q . Pop ( logger )
2018-11-27 19:46:24 -05:00
if err != nil {
t . Errorf ( "Error while popping the head of the queue: %v" , err )
}
// Update pod condition to unschedulable.
2021-02-22 09:00:23 -05:00
podutil . UpdatePodCondition ( & p1 . PodInfo . Pod . Status , & v1 . PodCondition {
2018-12-29 09:24:51 -05:00
Type : v1 . PodScheduled ,
Status : v1 . ConditionFalse ,
Reason : v1 . PodReasonUnschedulable ,
Message : "fake scheduling failure" ,
LastProbeTime : metav1 . Now ( ) ,
2018-11-27 19:46:24 -05:00
} )
2023-09-11 12:30:11 -04:00
p1 . UnschedulablePlugins = sets . New ( "plugin" )
2018-11-27 19:46:24 -05:00
// Put in the unschedulable queue.
2023-09-11 12:30:11 -04:00
err = q . AddUnschedulableIfNotPresent ( logger , p1 , q . SchedulingCycle ( ) )
if err != nil {
t . Fatalf ( "unexpected error from AddUnschedulableIfNotPresent: %v" , err )
}
2025-03-07 05:22:36 -05:00
c . Step ( q . backoffQ . podMaxBackoffDuration ( ) )
2018-11-27 19:46:24 -05:00
// Move all unschedulable pods to the active queue.
2024-10-01 23:24:03 -04:00
q . MoveAllToActiveOrBackoffQueue ( logger , framework . EventUnschedulableTimeout , nil , nil , nil )
2018-11-27 19:46:24 -05:00
// Simulation is over. Now let's pop all pods. The pod popped first should be
// the last one we pop here.
for i := 0 ; i < 5 ; i ++ {
2023-09-28 22:59:22 -04:00
p , err := q . Pop ( logger )
2018-11-27 19:46:24 -05:00
if err != nil {
t . Errorf ( "Error while popping pods from the queue: %v" , err )
}
if ( i == 4 ) != ( p1 == p ) {
2021-02-22 09:00:23 -05:00
t . Errorf ( "A pod tried before is not the last pod popped: i: %v, pod name: %v" , i , p . PodInfo . Pod . Name )
2018-11-27 19:46:24 -05:00
}
}
}
2018-07-24 16:46:40 -04:00
2018-12-29 09:24:51 -05:00
// TestPodFailedSchedulingMultipleTimesDoesNotBlockNewerPod tests
// that a pod determined as unschedulable multiple times doesn't block any newer pod.
// This behavior ensures that an unschedulable pod does not block head of the queue when there
// are frequent events that move pods to the active queue.
func TestPodFailedSchedulingMultipleTimesDoesNotBlockNewerPod ( t * testing . T ) {
2021-09-17 05:48:22 -04:00
c := testingclock . NewFakeClock ( time . Now ( ) )
2023-03-22 07:48:04 -04:00
logger , ctx := ktesting . NewTestContext ( t )
ctx , cancel := context . WithCancel ( ctx )
2022-01-22 14:00:56 -05:00
defer cancel ( )
q := NewTestQueue ( ctx , newDefaultQueueSort ( ) , WithClock ( c ) )
2018-12-29 09:24:51 -05:00
// Add an unschedulable pod to a priority queue.
// This makes a situation that the pod was tried to schedule
2019-09-17 14:19:56 -04:00
// and had been determined unschedulable so far
2023-03-08 16:18:36 -05:00
unschedulablePod := st . MakePod ( ) . Name ( "test-pod-unscheduled" ) . Namespace ( "ns1" ) . UID ( "tp001" ) . Priority ( highPriority ) . NominatedNodeName ( "node1" ) . Obj ( )
2019-02-21 20:46:18 -05:00
2018-12-29 09:24:51 -05:00
// Update pod condition to unschedulable.
podutil . UpdatePodCondition ( & unschedulablePod . Status , & v1 . PodCondition {
2019-02-21 20:46:18 -05:00
Type : v1 . PodScheduled ,
Status : v1 . ConditionFalse ,
Reason : v1 . PodReasonUnschedulable ,
Message : "fake scheduling failure" ,
2018-12-29 09:24:51 -05:00
} )
2019-02-21 20:46:18 -05:00
2023-07-17 18:53:07 -04:00
// To simulate the pod is failed in scheduling in the real world, Pop() the pod from activeQ before AddUnschedulableIfNotPresent() below.
2024-08-21 09:40:41 -04:00
q . Add ( logger , unschedulablePod )
2023-09-28 22:59:22 -04:00
if p , err := q . Pop ( logger ) ; err != nil || p . Pod != unschedulablePod {
2023-07-17 18:53:07 -04:00
t . Errorf ( "Expected: %v after Pop, but got: %v" , unschedulablePod . Name , p . Pod . Name )
}
2018-12-29 09:24:51 -05:00
// Put in the unschedulable queue
2023-09-11 12:30:11 -04:00
err := q . AddUnschedulableIfNotPresent ( logger , newQueuedPodInfoForLookup ( unschedulablePod , "plugin" ) , q . SchedulingCycle ( ) )
if err != nil {
t . Fatalf ( "unexpected error from AddUnschedulableIfNotPresent: %v" , err )
}
2020-02-13 19:31:07 -05:00
// Move clock to make the unschedulable pods complete backoff.
c . Step ( DefaultPodInitialBackoffDuration + time . Second )
2018-12-29 09:24:51 -05:00
// Move all unschedulable pods to the active queue.
2024-10-01 23:24:03 -04:00
q . MoveAllToActiveOrBackoffQueue ( logger , framework . EventUnschedulableTimeout , nil , nil , nil )
2018-12-29 09:24:51 -05:00
// Simulate a pod being popped by the scheduler,
// At this time, unschedulable pod should be popped.
2023-09-28 22:59:22 -04:00
p1 , err := q . Pop ( logger )
2018-12-29 09:24:51 -05:00
if err != nil {
t . Errorf ( "Error while popping the head of the queue: %v" , err )
}
2022-04-18 21:06:58 -04:00
if p1 . Pod != unschedulablePod {
2019-10-07 19:06:00 -04:00
t . Errorf ( "Expected that test-pod-unscheduled was popped, got %v" , p1 . Pod . Name )
2018-12-29 09:24:51 -05:00
}
// Assume newer pod was added just after unschedulable pod
// being popped and before being pushed back to the queue.
2022-04-18 21:06:58 -04:00
newerPod := st . MakePod ( ) . Name ( "test-newer-pod" ) . Namespace ( "ns1" ) . UID ( "tp002" ) . CreationTimestamp ( metav1 . Now ( ) ) . Priority ( highPriority ) . NominatedNodeName ( "node1" ) . Obj ( )
2023-03-22 07:48:04 -04:00
q . Add ( logger , newerPod )
2018-12-29 09:24:51 -05:00
2021-02-22 09:00:23 -05:00
// And then unschedulablePodInfo was determined as unschedulable AGAIN.
2018-12-29 09:24:51 -05:00
podutil . UpdatePodCondition ( & unschedulablePod . Status , & v1 . PodCondition {
2019-02-21 20:46:18 -05:00
Type : v1 . PodScheduled ,
Status : v1 . ConditionFalse ,
Reason : v1 . PodReasonUnschedulable ,
Message : "fake scheduling failure" ,
2018-12-29 09:24:51 -05:00
} )
2019-02-21 20:46:18 -05:00
2018-12-29 09:24:51 -05:00
// And then, put unschedulable pod to the unschedulable queue
2023-09-11 12:30:11 -04:00
err = q . AddUnschedulableIfNotPresent ( logger , newQueuedPodInfoForLookup ( unschedulablePod , "plugin" ) , q . SchedulingCycle ( ) )
if err != nil {
t . Fatalf ( "unexpected error from AddUnschedulableIfNotPresent: %v" , err )
}
2020-02-13 19:31:07 -05:00
// Move clock to make the unschedulable pods complete backoff.
c . Step ( DefaultPodInitialBackoffDuration + time . Second )
2018-12-29 09:24:51 -05:00
// Move all unschedulable pods to the active queue.
2024-10-01 23:24:03 -04:00
q . MoveAllToActiveOrBackoffQueue ( logger , framework . EventUnschedulableTimeout , nil , nil , nil )
2018-12-29 09:24:51 -05:00
// At this time, newerPod should be popped
// because it is the oldest tried pod.
2023-09-28 22:59:22 -04:00
p2 , err2 := q . Pop ( logger )
2018-12-29 09:24:51 -05:00
if err2 != nil {
t . Errorf ( "Error while popping the head of the queue: %v" , err2 )
}
2022-04-18 21:06:58 -04:00
if p2 . Pod != newerPod {
2019-10-07 19:06:00 -04:00
t . Errorf ( "Expected that test-newer-pod was popped, got %v" , p2 . Pod . Name )
2018-12-29 09:24:51 -05:00
}
}
2018-07-24 16:46:40 -04:00
// TestHighPriorityBackoff tests that a high priority pod does not block
// other pods if it is unschedulable
2019-06-05 16:04:22 -04:00
func TestHighPriorityBackoff ( t * testing . T ) {
2023-03-22 07:48:04 -04:00
logger , ctx := ktesting . NewTestContext ( t )
ctx , cancel := context . WithCancel ( ctx )
2022-01-22 14:00:56 -05:00
defer cancel ( )
q := NewTestQueue ( ctx , newDefaultQueueSort ( ) )
2018-07-24 16:46:40 -04:00
2022-04-18 21:06:58 -04:00
midPod := st . MakePod ( ) . Name ( "test-midpod" ) . Namespace ( "ns1" ) . UID ( "tp-mid" ) . Priority ( midPriority ) . NominatedNodeName ( "node1" ) . Obj ( )
highPod := st . MakePod ( ) . Name ( "test-highpod" ) . Namespace ( "ns1" ) . UID ( "tp-high" ) . Priority ( highPriority ) . NominatedNodeName ( "node1" ) . Obj ( )
2023-03-22 07:48:04 -04:00
q . Add ( logger , midPod )
q . Add ( logger , highPod )
2018-07-24 16:46:40 -04:00
// Simulate a pod being popped by the scheduler, determined unschedulable, and
// then moved back to the active queue.
2023-09-28 22:59:22 -04:00
p , err := q . Pop ( logger )
2018-07-24 16:46:40 -04:00
if err != nil {
t . Errorf ( "Error while popping the head of the queue: %v" , err )
}
2022-04-18 21:06:58 -04:00
if p . Pod != highPod {
2019-06-05 16:04:22 -04:00
t . Errorf ( "Expected to get high priority pod, got: %v" , p )
2018-07-24 16:46:40 -04:00
}
// Update pod condition to unschedulable.
2019-10-07 19:06:00 -04:00
podutil . UpdatePodCondition ( & p . Pod . Status , & v1 . PodCondition {
2018-07-24 16:46:40 -04:00
Type : v1 . PodScheduled ,
Status : v1 . ConditionFalse ,
Reason : v1 . PodReasonUnschedulable ,
Message : "fake scheduling failure" ,
} )
// Put in the unschedulable queue.
2024-11-11 16:29:19 -05:00
err = q . AddUnschedulableIfNotPresent ( logger , newQueuedPodInfoForLookup ( p . Pod , "fooPlugin" ) , q . SchedulingCycle ( ) )
2023-09-11 12:30:11 -04:00
if err != nil {
t . Fatalf ( "unexpected error from AddUnschedulableIfNotPresent: %v" , err )
}
2024-11-11 16:29:19 -05:00
// Move all unschedulable pods to the active/backoff queue.
2024-10-01 23:24:03 -04:00
q . MoveAllToActiveOrBackoffQueue ( logger , framework . EventUnschedulableTimeout , nil , nil , nil )
2018-07-24 16:46:40 -04:00
2023-09-28 22:59:22 -04:00
p , err = q . Pop ( logger )
2018-07-24 16:46:40 -04:00
if err != nil {
t . Errorf ( "Error while popping the head of the queue: %v" , err )
}
2022-04-18 21:06:58 -04:00
if p . Pod != midPod {
2024-11-11 16:29:19 -05:00
// high pod should be in backoffQ
t . Errorf ( "Expected to get mid priority pod, got: %v" , p . Pod . Name )
2018-07-24 16:46:40 -04:00
}
}
2019-01-15 23:08:19 -05:00
2022-03-24 05:38:49 -04:00
// TestHighPriorityFlushUnschedulablePodsLeftover tests that pods will be moved to
// activeQ after one minutes if it is in unschedulablePods.
func TestHighPriorityFlushUnschedulablePodsLeftover ( t * testing . T ) {
2021-09-17 05:48:22 -04:00
c := testingclock . NewFakeClock ( time . Now ( ) )
2023-06-08 00:54:30 -04:00
m := makeEmptyQueueingHintMapPerProfile ( )
2024-10-01 23:24:03 -04:00
m [ "" ] [ nodeAdd ] = [ ] * QueueingHintFunction {
2023-06-08 00:54:30 -04:00
{
PluginName : "fakePlugin" ,
2023-10-19 07:02:11 -04:00
QueueingHintFn : queueHintReturnQueue ,
2023-06-08 00:54:30 -04:00
} ,
2021-01-29 01:29:10 -05:00
}
2023-03-22 07:48:04 -04:00
logger , ctx := ktesting . NewTestContext ( t )
ctx , cancel := context . WithCancel ( ctx )
2022-01-22 14:00:56 -05:00
defer cancel ( )
2023-06-08 00:54:30 -04:00
q := NewTestQueue ( ctx , newDefaultQueueSort ( ) , WithClock ( c ) , WithQueueingHintMapPerProfile ( m ) )
2022-04-18 21:06:58 -04:00
midPod := st . MakePod ( ) . Name ( "test-midpod" ) . Namespace ( "ns1" ) . UID ( "tp-mid" ) . Priority ( midPriority ) . NominatedNodeName ( "node1" ) . Obj ( )
highPod := st . MakePod ( ) . Name ( "test-highpod" ) . Namespace ( "ns1" ) . UID ( "tp-high" ) . Priority ( highPriority ) . NominatedNodeName ( "node1" ) . Obj ( )
2019-01-15 23:08:19 -05:00
// Update pod condition to highPod.
podutil . UpdatePodCondition ( & highPod . Status , & v1 . PodCondition {
2019-02-21 20:46:18 -05:00
Type : v1 . PodScheduled ,
Status : v1 . ConditionFalse ,
Reason : v1 . PodReasonUnschedulable ,
Message : "fake scheduling failure" ,
2019-01-15 23:08:19 -05:00
} )
// Update pod condition to midPod.
podutil . UpdatePodCondition ( & midPod . Status , & v1 . PodCondition {
2019-02-21 20:46:18 -05:00
Type : v1 . PodScheduled ,
Status : v1 . ConditionFalse ,
Reason : v1 . PodReasonUnschedulable ,
Message : "fake scheduling failure" ,
2019-01-15 23:08:19 -05:00
} )
2023-07-17 18:53:07 -04:00
// To simulate the pod is failed in scheduling in the real world, Pop() the pod from activeQ before AddUnschedulableIfNotPresent()s below.
2024-08-21 09:40:41 -04:00
q . Add ( logger , highPod )
2023-09-28 22:59:22 -04:00
if p , err := q . Pop ( logger ) ; err != nil || p . Pod != highPod {
2023-07-17 18:53:07 -04:00
t . Errorf ( "Expected: %v after Pop, but got: %v" , highPod . Name , p . Pod . Name )
}
2024-08-21 09:40:41 -04:00
q . Add ( logger , midPod )
2023-09-28 22:59:22 -04:00
if p , err := q . Pop ( logger ) ; err != nil || p . Pod != midPod {
2023-07-17 18:53:07 -04:00
t . Errorf ( "Expected: %v after Pop, but got: %v" , midPod . Name , p . Pod . Name )
}
2023-09-11 12:30:11 -04:00
err := q . AddUnschedulableIfNotPresent ( logger , q . newQueuedPodInfo ( highPod , "fakePlugin" ) , q . SchedulingCycle ( ) )
if err != nil {
t . Fatalf ( "unexpected error from AddUnschedulableIfNotPresent: %v" , err )
}
err = q . AddUnschedulableIfNotPresent ( logger , q . newQueuedPodInfo ( midPod , "fakePlugin" ) , q . SchedulingCycle ( ) )
if err != nil {
t . Fatalf ( "unexpected error from AddUnschedulableIfNotPresent: %v" , err )
}
2022-03-24 05:38:49 -04:00
c . Step ( DefaultPodMaxInUnschedulablePodsDuration + time . Second )
2023-03-22 07:48:04 -04:00
q . flushUnschedulablePodsLeftover ( logger )
2019-02-21 20:46:18 -05:00
2023-09-28 22:59:22 -04:00
if p , err := q . Pop ( logger ) ; err != nil || p . Pod != highPod {
2021-02-22 09:00:23 -05:00
t . Errorf ( "Expected: %v after Pop, but got: %v" , highPriorityPodInfo . Pod . Name , p . Pod . Name )
2019-01-15 23:08:19 -05:00
}
2023-09-28 22:59:22 -04:00
if p , err := q . Pop ( logger ) ; err != nil || p . Pod != midPod {
2021-02-22 09:00:23 -05:00
t . Errorf ( "Expected: %v after Pop, but got: %v" , medPriorityPodInfo . Pod . Name , p . Pod . Name )
2019-01-15 23:08:19 -05:00
}
}
2019-02-21 20:46:18 -05:00
2025-11-14 03:28:45 -05:00
// TestFlushUnschedulablePodsLeftoverSetsFlag verifies that the WasFlushedFromUnschedulable
// flag is correctly set when pods are flushed and cleared when they return to the queue.
func TestFlushUnschedulablePodsLeftoverSetsFlag ( t * testing . T ) {
c := testingclock . NewFakeClock ( time . Now ( ) )
m := makeEmptyQueueingHintMapPerProfile ( )
logger , ctx := ktesting . NewTestContext ( t )
ctx , cancel := context . WithCancel ( ctx )
defer cancel ( )
q := NewTestQueue ( ctx , newDefaultQueueSort ( ) , WithClock ( c ) , WithQueueingHintMapPerProfile ( m ) )
pod := st . MakePod ( ) . Name ( "test-pod" ) . Namespace ( "ns1" ) . UID ( "tp-1" ) . Priority ( midPriority ) . NominatedNodeName ( "node1" ) . Obj ( )
// Add pod to activeQ and pop it to simulate a scheduling attempt
q . Add ( logger , pod )
pInfo , err := q . Pop ( logger )
if err != nil {
2025-11-14 13:57:24 -05:00
t . Fatalf ( "Unexpected error from Pop: %v" , err )
2025-11-14 03:28:45 -05:00
}
// Verify flag is initially false
if pInfo . WasFlushedFromUnschedulable {
t . Errorf ( "Expected WasFlushedFromUnschedulable to be false initially, but got true" )
}
// Add pod to unschedulablePods (simulating failed scheduling)
err = q . AddUnschedulableIfNotPresent ( logger , q . newQueuedPodInfo ( pod , "fakePlugin" ) , q . SchedulingCycle ( ) )
if err != nil {
2025-11-14 13:57:24 -05:00
t . Fatalf ( "Unexpected error from AddUnschedulableIfNotPresent: %v" , err )
2025-11-14 03:28:45 -05:00
}
// Advance time past the flush duration and flush
c . Step ( DefaultPodMaxInUnschedulablePodsDuration + time . Second )
q . flushUnschedulablePodsLeftover ( logger )
// Pop the pod and verify flag is now true
pInfo , err = q . Pop ( logger )
if err != nil {
2025-11-14 13:57:24 -05:00
t . Fatalf ( "Unexpected error from Pop after flush: %v" , err )
2025-11-14 03:28:45 -05:00
}
if ! pInfo . WasFlushedFromUnschedulable {
t . Errorf ( "Expected WasFlushedFromUnschedulable to be true after flush, but got false" )
}
// Simulate pod failing to schedule again and returning to queue
2025-11-14 13:57:24 -05:00
err = q . AddUnschedulableIfNotPresent ( logger , q . newQueuedPodInfo ( pInfo . Pod , "fakePlugin" ) , q . SchedulingCycle ( ) )
2025-11-14 03:28:45 -05:00
if err != nil {
2025-11-14 13:57:24 -05:00
t . Fatalf ( "Unexpected error from AddUnschedulableIfNotPresent: %v" , err )
2025-11-14 03:28:45 -05:00
}
// Verify flag is cleared when pod returns to queue
2025-11-14 13:57:24 -05:00
internalPInfo := q . unschedulablePods . get ( pod )
2025-11-14 03:28:45 -05:00
if internalPInfo == nil {
2025-11-14 13:57:24 -05:00
t . Fatalf ( "pod should be in unschedulablePods" )
2025-11-14 03:28:45 -05:00
}
if internalPInfo . WasFlushedFromUnschedulable {
t . Errorf ( "Expected WasFlushedFromUnschedulable to be cleared (false) after returning to queue, but got true" )
}
}
2022-03-24 05:38:49 -04:00
func TestPriorityQueue_initPodMaxInUnschedulablePodsDuration ( t * testing . T ) {
2022-04-18 21:06:58 -04:00
pod1 := st . MakePod ( ) . Name ( "test-pod-1" ) . Namespace ( "ns1" ) . UID ( "tp-1" ) . NominatedNodeName ( "node1" ) . Obj ( )
pod2 := st . MakePod ( ) . Name ( "test-pod-2" ) . Namespace ( "ns2" ) . UID ( "tp-2" ) . NominatedNodeName ( "node2" ) . Obj ( )
2022-02-15 22:05:52 -05:00
var timestamp = time . Now ( )
pInfo1 := & framework . QueuedPodInfo {
2022-10-12 10:11:04 -04:00
PodInfo : mustNewTestPodInfo ( t , pod1 ) ,
2022-02-15 22:05:52 -05:00
Timestamp : timestamp . Add ( - time . Second ) ,
}
pInfo2 := & framework . QueuedPodInfo {
2022-10-12 10:11:04 -04:00
PodInfo : mustNewTestPodInfo ( t , pod2 ) ,
2022-02-15 22:05:52 -05:00
Timestamp : timestamp . Add ( - 2 * time . Second ) ,
}
tests := [ ] struct {
2022-03-24 05:38:49 -04:00
name string
podMaxInUnschedulablePodsDuration time . Duration
operations [ ] operation
operands [ ] * framework . QueuedPodInfo
expected [ ] * framework . QueuedPodInfo
2022-02-15 22:05:52 -05:00
} {
{
2022-03-24 05:38:49 -04:00
name : "New priority queue by the default value of podMaxInUnschedulablePodsDuration" ,
2022-02-15 22:05:52 -05:00
operations : [ ] operation {
2022-03-24 05:38:49 -04:00
addPodUnschedulablePods ,
addPodUnschedulablePods ,
2024-07-22 06:13:13 -04:00
flushUnscheduledQ ,
2022-02-15 22:05:52 -05:00
} ,
operands : [ ] * framework . QueuedPodInfo { pInfo1 , pInfo2 , nil } ,
expected : [ ] * framework . QueuedPodInfo { pInfo2 , pInfo1 } ,
} ,
{
2022-03-24 05:38:49 -04:00
name : "New priority queue by user-defined value of podMaxInUnschedulablePodsDuration" ,
podMaxInUnschedulablePodsDuration : 30 * time . Second ,
2022-02-15 22:05:52 -05:00
operations : [ ] operation {
2022-03-24 05:38:49 -04:00
addPodUnschedulablePods ,
addPodUnschedulablePods ,
2024-07-22 06:13:13 -04:00
flushUnscheduledQ ,
2022-02-15 22:05:52 -05:00
} ,
operands : [ ] * framework . QueuedPodInfo { pInfo1 , pInfo2 , nil } ,
expected : [ ] * framework . QueuedPodInfo { pInfo2 , pInfo1 } ,
} ,
}
for _ , test := range tests {
t . Run ( test . name , func ( t * testing . T ) {
2023-03-22 07:48:04 -04:00
logger , ctx := ktesting . NewTestContext ( t )
ctx , cancel := context . WithCancel ( ctx )
2022-02-15 22:05:52 -05:00
defer cancel ( )
var queue * PriorityQueue
2022-03-24 05:38:49 -04:00
if test . podMaxInUnschedulablePodsDuration > 0 {
2022-02-15 22:05:52 -05:00
queue = NewTestQueue ( ctx , newDefaultQueueSort ( ) ,
WithClock ( testingclock . NewFakeClock ( timestamp ) ) ,
2022-03-24 05:38:49 -04:00
WithPodMaxInUnschedulablePodsDuration ( test . podMaxInUnschedulablePodsDuration ) )
2022-02-15 22:05:52 -05:00
} else {
queue = NewTestQueue ( ctx , newDefaultQueueSort ( ) ,
WithClock ( testingclock . NewFakeClock ( timestamp ) ) )
}
var podInfoList [ ] * framework . QueuedPodInfo
for i , op := range test . operations {
2023-09-05 14:46:33 -04:00
op ( t , logger , queue , test . operands [ i ] )
2022-02-15 22:05:52 -05:00
}
expectedLen := len ( test . expected )
2024-07-22 06:13:13 -04:00
if queue . activeQ . len ( ) != expectedLen {
t . Fatalf ( "Expected %v items to be in activeQ, but got: %v" , expectedLen , queue . activeQ . len ( ) )
2022-02-15 22:05:52 -05:00
}
for i := 0 ; i < expectedLen ; i ++ {
2024-07-22 06:13:13 -04:00
if pInfo , err := queue . activeQ . pop ( logger ) ; err != nil {
2022-02-15 22:05:52 -05:00
t . Errorf ( "Error while popping the head of the queue: %v" , err )
} else {
2024-07-17 07:25:31 -04:00
podInfoList = append ( podInfoList , pInfo )
2024-07-22 06:13:13 -04:00
// Cleanup attempts counter incremented in activeQ.pop()
pInfo . Attempts = 0
2022-02-15 22:05:52 -05:00
}
}
2025-01-02 07:47:57 -05:00
if diff := cmp . Diff ( test . expected , podInfoList , cmpopts . IgnoreUnexported ( framework . PodInfo { } ) ) ; diff != "" {
2022-02-15 22:05:52 -05:00
t . Errorf ( "Unexpected QueuedPodInfo list (-want, +got):\n%s" , diff )
}
} )
}
}
2023-09-05 14:46:33 -04:00
type operation func ( t * testing . T , logger klog . Logger , queue * PriorityQueue , pInfo * framework . QueuedPodInfo )
2019-03-19 20:02:45 -04:00
var (
2023-09-05 14:46:33 -04:00
add = func ( t * testing . T , logger klog . Logger , queue * PriorityQueue , pInfo * framework . QueuedPodInfo ) {
2024-07-17 07:25:31 -04:00
queue . Add ( logger , pInfo . Pod )
2019-10-07 13:29:53 -04:00
}
2026-01-09 10:35:48 -05:00
pop = func ( t * testing . T , logger klog . Logger , queue * PriorityQueue , _ * framework . QueuedPodInfo ) {
_ , err := queue . Pop ( logger )
if err != nil {
t . Fatalf ( "Unexpected error during Pop: %v" , err )
}
2025-12-30 10:18:20 -05:00
}
2023-09-05 14:46:33 -04:00
popAndRequeueAsUnschedulable = func ( t * testing . T , logger klog . Logger , queue * PriorityQueue , pInfo * framework . QueuedPodInfo ) {
2023-07-17 18:53:07 -04:00
// To simulate the pod is failed in scheduling in the real world, Pop() the pod from activeQ before AddUnschedulableIfNotPresent() below.
2023-09-05 14:46:33 -04:00
// UnschedulablePlugins will get cleared by Pop, so make a copy first.
unschedulablePlugins := pInfo . UnschedulablePlugins . Clone ( )
2024-08-21 09:40:41 -04:00
queue . Add ( logger , pInfo . Pod )
2023-09-28 22:59:22 -04:00
p , err := queue . Pop ( logger )
2023-09-05 14:46:33 -04:00
if err != nil {
t . Fatalf ( "Unexpected error during Pop: %v" , err )
}
if p . Pod != pInfo . Pod {
t . Fatalf ( "Expected: %v after Pop, but got: %v" , pInfo . Pod . Name , p . Pod . Name )
}
// Simulate plugins that are waiting for some events.
p . UnschedulablePlugins = unschedulablePlugins
if err := queue . AddUnschedulableIfNotPresent ( logger , p , 1 ) ; err != nil {
t . Fatalf ( "Unexpected error during AddUnschedulableIfNotPresent: %v" , err )
2023-07-17 18:53:07 -04:00
}
2019-10-07 13:29:53 -04:00
}
2023-09-05 14:46:33 -04:00
popAndRequeueAsBackoff = func ( t * testing . T , logger klog . Logger , queue * PriorityQueue , pInfo * framework . QueuedPodInfo ) {
// To simulate the pod is failed in scheduling in the real world, Pop() the pod from activeQ before AddUnschedulableIfNotPresent() below.
2024-08-21 09:40:41 -04:00
queue . Add ( logger , pInfo . Pod )
2023-09-28 22:59:22 -04:00
p , err := queue . Pop ( logger )
2023-09-05 14:46:33 -04:00
if err != nil {
t . Fatalf ( "Unexpected error during Pop: %v" , err )
}
if p . Pod != pInfo . Pod {
t . Fatalf ( "Expected: %v after Pop, but got: %v" , pInfo . Pod . Name , p . Pod . Name )
}
2024-11-11 16:29:19 -05:00
// needs to increment it to make it backoff
p . UnschedulableCount ++
2023-09-05 14:46:33 -04:00
// When there is no known unschedulable plugin, pods always go to the backoff queue.
if err := queue . AddUnschedulableIfNotPresent ( logger , p , 1 ) ; err != nil {
t . Fatalf ( "Unexpected error during AddUnschedulableIfNotPresent: %v" , err )
}
2019-10-07 13:29:53 -04:00
}
2023-09-05 14:46:33 -04:00
addPodActiveQ = func ( t * testing . T , logger klog . Logger , queue * PriorityQueue , pInfo * framework . QueuedPodInfo ) {
2024-08-21 09:40:41 -04:00
queue . Add ( logger , pInfo . Pod )
}
addPodActiveQDirectly = func ( t * testing . T , logger klog . Logger , queue * PriorityQueue , pInfo * framework . QueuedPodInfo ) {
2024-08-20 04:51:21 -04:00
queue . activeQ . underLock ( func ( unlockedActiveQ unlockedActiveQueuer ) {
2025-09-02 05:35:22 -04:00
unlockedActiveQ . add ( logger , pInfo , framework . EventUnscheduledPodAdd . Label ( ) )
2024-08-20 04:51:21 -04:00
} )
2019-03-19 20:02:45 -04:00
}
2023-09-05 14:46:33 -04:00
addPodUnschedulablePods = func ( t * testing . T , logger klog . Logger , queue * PriorityQueue , pInfo * framework . QueuedPodInfo ) {
2024-09-05 22:19:49 -04:00
if ! pInfo . Gated ( ) {
2022-11-07 17:02:22 -05:00
// Update pod condition to unschedulable.
podutil . UpdatePodCondition ( & pInfo . Pod . Status , & v1 . PodCondition {
Type : v1 . PodScheduled ,
Status : v1 . ConditionFalse ,
Reason : v1 . PodReasonUnschedulable ,
Message : "fake scheduling failure" ,
} )
2024-11-11 16:29:19 -05:00
// needs to increment it to make it backoff
pInfo . UnschedulableCount ++
2022-11-07 17:02:22 -05:00
}
2025-11-20 06:21:44 -05:00
queue . unschedulablePods . addOrUpdate ( pInfo , false , framework . EventUnscheduledPodAdd . Label ( ) )
2019-03-19 20:02:45 -04:00
}
2023-09-05 14:46:33 -04:00
deletePod = func ( t * testing . T , _ klog . Logger , queue * PriorityQueue , pInfo * framework . QueuedPodInfo ) {
2022-11-22 00:33:16 -05:00
queue . Delete ( pInfo . Pod )
}
2023-09-05 14:46:33 -04:00
updatePodQueueable = func ( t * testing . T , logger klog . Logger , queue * PriorityQueue , pInfo * framework . QueuedPodInfo ) {
2022-11-22 00:33:16 -05:00
newPod := pInfo . Pod . DeepCopy ( )
newPod . Labels = map [ string ] string { "queueable" : "" }
2023-03-22 07:48:04 -04:00
queue . Update ( logger , pInfo . Pod , newPod )
2022-11-22 00:33:16 -05:00
}
2023-09-05 14:46:33 -04:00
addPodBackoffQ = func ( t * testing . T , logger klog . Logger , queue * PriorityQueue , pInfo * framework . QueuedPodInfo ) {
2025-02-25 08:05:33 -05:00
queue . backoffQ . add ( logger , pInfo , framework . EventUnscheduledPodAdd . Label ( ) )
2019-03-19 20:02:45 -04:00
}
2023-09-05 14:46:33 -04:00
moveAllToActiveOrBackoffQ = func ( t * testing . T , logger klog . Logger , queue * PriorityQueue , _ * framework . QueuedPodInfo ) {
2024-10-01 23:24:03 -04:00
queue . MoveAllToActiveOrBackoffQueue ( logger , framework . EventUnschedulableTimeout , nil , nil , nil )
2019-03-19 20:02:45 -04:00
}
2023-09-05 14:46:33 -04:00
flushBackoffQ = func ( t * testing . T , logger klog . Logger , queue * PriorityQueue , _ * framework . QueuedPodInfo ) {
2025-03-07 05:22:36 -05:00
queue . clock . ( * testingclock . FakeClock ) . Step ( 3 * time . Second )
2023-03-22 07:48:04 -04:00
queue . flushBackoffQCompleted ( logger )
2019-03-19 20:02:45 -04:00
}
2023-09-05 14:46:33 -04:00
moveClockForward = func ( t * testing . T , logger klog . Logger , queue * PriorityQueue , _ * framework . QueuedPodInfo ) {
2025-03-07 05:22:36 -05:00
queue . clock . ( * testingclock . FakeClock ) . Step ( 3 * time . Second )
2019-10-07 13:29:53 -04:00
}
2024-07-22 06:13:13 -04:00
flushUnscheduledQ = func ( t * testing . T , logger klog . Logger , queue * PriorityQueue , _ * framework . QueuedPodInfo ) {
2022-03-24 05:38:49 -04:00
queue . clock . ( * testingclock . FakeClock ) . Step ( queue . podMaxInUnschedulablePodsDuration )
2023-03-22 07:48:04 -04:00
queue . flushUnschedulablePodsLeftover ( logger )
2022-02-15 22:05:52 -05:00
}
2025-11-20 06:21:44 -05:00
updatePluginToGateAllPods = func ( t * testing . T , logger klog . Logger , queue * PriorityQueue , _ * framework . QueuedPodInfo ) {
queue . preEnqueuePluginMap [ "" ] [ "preEnqueuePlugin" ] = & preEnqueuePlugin { allowlists : [ ] string { "" } }
}
updatePluginToUngateAllPods = func ( t * testing . T , logger klog . Logger , queue * PriorityQueue , _ * framework . QueuedPodInfo ) {
queue . preEnqueuePluginMap [ "" ] [ "preEnqueuePlugin" ] = & preEnqueuePlugin { allowlists : [ ] string { "queueable" } }
}
2019-03-19 20:02:45 -04:00
)
2020-04-20 20:36:26 -04:00
// TestPodTimestamp tests the operations related to QueuedPodInfo.
2019-02-21 20:46:18 -05:00
func TestPodTimestamp ( t * testing . T ) {
2022-04-18 21:06:58 -04:00
pod1 := st . MakePod ( ) . Name ( "test-pod-1" ) . Namespace ( "ns1" ) . UID ( "tp-1" ) . NominatedNodeName ( "node1" ) . Obj ( )
pod2 := st . MakePod ( ) . Name ( "test-pod-2" ) . Namespace ( "ns2" ) . UID ( "tp-2" ) . NominatedNodeName ( "node2" ) . Obj ( )
2019-02-21 20:46:18 -05:00
2019-02-27 18:23:27 -05:00
var timestamp = time . Now ( )
2020-04-20 20:36:26 -04:00
pInfo1 := & framework . QueuedPodInfo {
2022-10-12 10:11:04 -04:00
PodInfo : mustNewTestPodInfo ( t , pod1 ) ,
2019-05-06 21:03:00 -04:00
Timestamp : timestamp ,
2019-02-21 20:46:18 -05:00
}
2020-04-20 20:36:26 -04:00
pInfo2 := & framework . QueuedPodInfo {
2022-10-12 10:11:04 -04:00
PodInfo : mustNewTestPodInfo ( t , pod2 ) ,
2019-05-06 21:03:00 -04:00
Timestamp : timestamp . Add ( time . Second ) ,
2019-02-21 20:46:18 -05:00
}
tests := [ ] struct {
name string
operations [ ] operation
2020-04-20 20:36:26 -04:00
operands [ ] * framework . QueuedPodInfo
expected [ ] * framework . QueuedPodInfo
2019-02-21 20:46:18 -05:00
} {
{
name : "add two pod to activeQ and sort them by the timestamp" ,
operations : [ ] operation {
2024-08-21 09:40:41 -04:00
// Need to add the pods directly to the activeQ to override the timestamps.
addPodActiveQDirectly ,
addPodActiveQDirectly ,
2019-02-21 20:46:18 -05:00
} ,
2020-04-20 20:36:26 -04:00
operands : [ ] * framework . QueuedPodInfo { pInfo2 , pInfo1 } ,
expected : [ ] * framework . QueuedPodInfo { pInfo1 , pInfo2 } ,
2019-02-21 20:46:18 -05:00
} ,
{
2022-03-24 05:38:49 -04:00
name : "add two pod to unschedulablePods then move them to activeQ and sort them by the timestamp" ,
2019-02-21 20:46:18 -05:00
operations : [ ] operation {
2022-03-24 05:38:49 -04:00
addPodUnschedulablePods ,
addPodUnschedulablePods ,
2020-02-13 19:31:07 -05:00
moveClockForward ,
2019-10-07 13:29:53 -04:00
moveAllToActiveOrBackoffQ ,
2019-02-21 20:46:18 -05:00
} ,
2020-04-20 20:36:26 -04:00
operands : [ ] * framework . QueuedPodInfo { pInfo2 , pInfo1 , nil , nil } ,
expected : [ ] * framework . QueuedPodInfo { pInfo1 , pInfo2 } ,
2019-02-21 20:46:18 -05:00
} ,
{
name : "add one pod to BackoffQ and move it to activeQ" ,
operations : [ ] operation {
2024-08-21 09:40:41 -04:00
// Need to add the pods directly to activeQ to override the timestamps.
addPodActiveQDirectly ,
2019-03-19 20:02:45 -04:00
addPodBackoffQ ,
flushBackoffQ ,
2019-10-07 13:29:53 -04:00
moveAllToActiveOrBackoffQ ,
2019-02-21 20:46:18 -05:00
} ,
2020-04-20 20:36:26 -04:00
operands : [ ] * framework . QueuedPodInfo { pInfo2 , pInfo1 , nil , nil } ,
expected : [ ] * framework . QueuedPodInfo { pInfo1 , pInfo2 } ,
2019-02-21 20:46:18 -05:00
} ,
}
for _ , test := range tests {
t . Run ( test . name , func ( t * testing . T ) {
2023-03-22 07:48:04 -04:00
logger , ctx := ktesting . NewTestContext ( t )
ctx , cancel := context . WithCancel ( ctx )
2022-01-22 14:00:56 -05:00
defer cancel ( )
queue := NewTestQueue ( ctx , newDefaultQueueSort ( ) , WithClock ( testingclock . NewFakeClock ( timestamp ) ) )
2020-04-20 20:36:26 -04:00
var podInfoList [ ] * framework . QueuedPodInfo
2019-02-21 20:46:18 -05:00
2019-03-19 20:02:45 -04:00
for i , op := range test . operations {
2023-09-05 14:46:33 -04:00
op ( t , logger , queue , test . operands [ i ] )
2019-02-21 20:46:18 -05:00
}
2022-02-08 04:40:45 -05:00
expectedLen := len ( test . expected )
2024-07-22 06:13:13 -04:00
if queue . activeQ . len ( ) != expectedLen {
t . Fatalf ( "Expected %v items to be in activeQ, but got: %v" , expectedLen , queue . activeQ . len ( ) )
2022-02-08 04:40:45 -05:00
}
for i := 0 ; i < expectedLen ; i ++ {
2024-07-22 06:13:13 -04:00
if pInfo , err := queue . activeQ . pop ( logger ) ; err != nil {
2019-02-21 20:46:18 -05:00
t . Errorf ( "Error while popping the head of the queue: %v" , err )
} else {
2024-07-17 07:25:31 -04:00
podInfoList = append ( podInfoList , pInfo )
2024-07-22 06:13:13 -04:00
// Cleanup attempts counter incremented in activeQ.pop()
pInfo . Attempts = 0
2019-02-21 20:46:18 -05:00
}
}
2025-01-02 07:47:57 -05:00
if diff := cmp . Diff ( test . expected , podInfoList , cmpopts . IgnoreUnexported ( framework . PodInfo { } ) ) ; diff != "" {
2023-06-29 03:28:42 -04:00
t . Errorf ( "Unexpected QueuedPodInfo list (-want, +got):\n%s" , diff )
2019-02-21 20:46:18 -05:00
}
} )
}
}
2019-03-20 02:53:33 -04:00
2025-12-30 10:18:20 -05:00
// TestSchedulerPodsMetric tests Prometheus metrics
func TestSchedulerPodsMetric ( t * testing . T ) {
2019-03-20 02:53:33 -04:00
timestamp := time . Now ( )
2024-09-05 22:19:49 -04:00
preenqueuePluginName := "preEnqueuePlugin"
metrics . Register ( )
2022-11-07 17:02:22 -05:00
total := 60
queueableNum := 50
2022-11-22 00:33:16 -05:00
queueable , failme := "queueable" , "failme"
2022-11-07 17:02:22 -05:00
// First 50 Pods are queueable.
2022-11-22 00:33:16 -05:00
pInfos := makeQueuedPodInfos ( queueableNum , "x" , queueable , timestamp )
2022-11-07 17:02:22 -05:00
// The last 10 Pods are not queueable.
2022-11-22 00:33:16 -05:00
gated := makeQueuedPodInfos ( total - queueableNum , "y" , failme , timestamp )
2022-11-07 17:02:22 -05:00
// Manually mark them as gated=true.
for _ , pInfo := range gated {
2025-06-26 11:06:29 -04:00
setQueuedPodInfoGated ( pInfo , preenqueuePluginName , [ ] fwk . ClusterEvent { framework . EventUnscheduledPodUpdate } )
2022-11-07 17:02:22 -05:00
}
pInfos = append ( pInfos , gated ... )
2020-02-13 19:31:07 -05:00
totalWithDelay := 20
2022-11-22 00:33:16 -05:00
pInfosWithDelay := makeQueuedPodInfos ( totalWithDelay , "z" , queueable , timestamp . Add ( 2 * time . Second ) )
2020-02-13 19:31:07 -05:00
2024-07-11 07:41:08 -04:00
resetPodInfos := func ( ) {
// reset PodInfo's Attempts because they influence the backoff time calculation.
for i := range pInfos {
pInfos [ i ] . Attempts = 0
2024-11-11 16:29:19 -05:00
pInfos [ i ] . UnschedulableCount = 0
2024-07-11 07:41:08 -04:00
}
for i := range pInfosWithDelay {
pInfosWithDelay [ i ] . Attempts = 0
2024-11-11 16:29:19 -05:00
pInfosWithDelay [ i ] . UnschedulableCount = 0
2024-07-11 07:41:08 -04:00
}
}
2019-03-20 02:53:33 -04:00
tests := [ ] struct {
2023-03-01 21:57:50 -05:00
name string
operations [ ] operation
operands [ ] [ ] * framework . QueuedPodInfo
metricsName string
pluginMetricsSamplePercent int
2025-11-20 06:21:44 -05:00
disablePopFromBackoffQ bool
2023-03-01 21:57:50 -05:00
wants string
2019-03-20 02:53:33 -04:00
} {
{
2022-03-24 05:38:49 -04:00
name : "add pods to activeQ and unschedulablePods" ,
2019-03-20 02:53:33 -04:00
operations : [ ] operation {
addPodActiveQ ,
2022-03-24 05:38:49 -04:00
addPodUnschedulablePods ,
2019-03-20 02:53:33 -04:00
} ,
2020-04-20 20:36:26 -04:00
operands : [ ] [ ] * framework . QueuedPodInfo {
2019-03-20 02:53:33 -04:00
pInfos [ : 30 ] ,
pInfos [ 30 : ] ,
} ,
2019-09-29 22:05:50 -04:00
metricsName : "scheduler_pending_pods" ,
wants : `
2022-11-07 17:02:22 -05:00
# 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 .
2019-09-29 22:05:50 -04:00
# TYPE scheduler_pending_pods gauge
scheduler_pending_pods { queue = "active" } 30
scheduler_pending_pods { queue = "backoff" } 0
2022-11-07 17:02:22 -05:00
scheduler_pending_pods { queue = "gated" } 10
2019-09-29 22:05:50 -04:00
scheduler_pending_pods { queue = "unschedulable" } 20
` ,
2019-03-20 02:53:33 -04:00
} ,
{
name : "add pods to all kinds of queues" ,
operations : [ ] operation {
addPodActiveQ ,
addPodBackoffQ ,
2022-03-24 05:38:49 -04:00
addPodUnschedulablePods ,
2019-03-20 02:53:33 -04:00
} ,
2020-04-20 20:36:26 -04:00
operands : [ ] [ ] * framework . QueuedPodInfo {
2019-03-20 02:53:33 -04:00
pInfos [ : 15 ] ,
pInfos [ 15 : 40 ] ,
pInfos [ 40 : ] ,
} ,
2019-09-29 22:05:50 -04:00
metricsName : "scheduler_pending_pods" ,
wants : `
2022-11-07 17:02:22 -05:00
# 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 .
2019-09-29 22:05:50 -04:00
# TYPE scheduler_pending_pods gauge
scheduler_pending_pods { queue = "active" } 15
scheduler_pending_pods { queue = "backoff" } 25
2022-11-07 17:02:22 -05:00
scheduler_pending_pods { queue = "gated" } 10
2019-09-29 22:05:50 -04:00
scheduler_pending_pods { queue = "unschedulable" } 10
` ,
2019-03-20 02:53:33 -04:00
} ,
{
2022-03-24 05:38:49 -04:00
name : "add pods to unschedulablePods and then move all to activeQ" ,
2019-03-20 02:53:33 -04:00
operations : [ ] operation {
2022-03-24 05:38:49 -04:00
addPodUnschedulablePods ,
2020-02-13 19:31:07 -05:00
moveClockForward ,
2019-10-07 13:29:53 -04:00
moveAllToActiveOrBackoffQ ,
2019-03-20 02:53:33 -04:00
} ,
2020-04-20 20:36:26 -04:00
operands : [ ] [ ] * framework . QueuedPodInfo {
2019-03-20 02:53:33 -04:00
pInfos [ : total ] ,
{ nil } ,
2020-02-13 19:31:07 -05:00
{ nil } ,
2019-03-20 02:53:33 -04:00
} ,
2019-09-29 22:05:50 -04:00
metricsName : "scheduler_pending_pods" ,
wants : `
2022-11-07 17:02:22 -05:00
# 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 .
2019-09-29 22:05:50 -04:00
# TYPE scheduler_pending_pods gauge
scheduler_pending_pods { queue = "active" } 50
scheduler_pending_pods { queue = "backoff" } 0
2022-11-07 17:02:22 -05:00
scheduler_pending_pods { queue = "gated" } 10
2019-09-29 22:05:50 -04:00
scheduler_pending_pods { queue = "unschedulable" } 0
` ,
2019-03-20 02:53:33 -04:00
} ,
{
2022-03-24 05:38:49 -04:00
name : "make some pods subject to backoff, add pods to unschedulablePods, and then move all to activeQ" ,
2019-03-20 02:53:33 -04:00
operations : [ ] operation {
2022-03-24 05:38:49 -04:00
addPodUnschedulablePods ,
2020-02-13 19:31:07 -05:00
moveClockForward ,
2022-03-24 05:38:49 -04:00
addPodUnschedulablePods ,
2019-10-07 13:29:53 -04:00
moveAllToActiveOrBackoffQ ,
2019-03-20 02:53:33 -04:00
} ,
2020-04-20 20:36:26 -04:00
operands : [ ] [ ] * framework . QueuedPodInfo {
2020-02-13 19:31:07 -05:00
pInfos [ 20 : total ] ,
{ nil } ,
pInfosWithDelay [ : 20 ] ,
2019-03-20 02:53:33 -04:00
{ nil } ,
} ,
2019-09-29 22:05:50 -04:00
metricsName : "scheduler_pending_pods" ,
wants : `
2022-11-07 17:02:22 -05:00
# 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 .
2019-09-29 22:05:50 -04:00
# TYPE scheduler_pending_pods gauge
scheduler_pending_pods { queue = "active" } 30
scheduler_pending_pods { queue = "backoff" } 20
2022-11-07 17:02:22 -05:00
scheduler_pending_pods { queue = "gated" } 10
2019-09-29 22:05:50 -04:00
scheduler_pending_pods { queue = "unschedulable" } 0
` ,
2019-03-20 02:53:33 -04:00
} ,
{
2022-03-24 05:38:49 -04:00
name : "make some pods subject to backoff, add pods to unschedulablePods/activeQ, move all to activeQ, and finally flush backoffQ" ,
2019-03-20 02:53:33 -04:00
operations : [ ] operation {
2022-03-24 05:38:49 -04:00
addPodUnschedulablePods ,
2019-03-20 02:53:33 -04:00
addPodActiveQ ,
2019-10-07 13:29:53 -04:00
moveAllToActiveOrBackoffQ ,
2019-03-20 02:53:33 -04:00
flushBackoffQ ,
} ,
2020-04-20 20:36:26 -04:00
operands : [ ] [ ] * framework . QueuedPodInfo {
2019-03-20 02:53:33 -04:00
pInfos [ : 40 ] ,
2022-11-07 17:02:22 -05:00
pInfos [ 40 : 50 ] ,
2019-03-20 02:53:33 -04:00
{ nil } ,
{ nil } ,
} ,
2019-09-29 22:05:50 -04:00
metricsName : "scheduler_pending_pods" ,
wants : `
2022-11-07 17:02:22 -05:00
# 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 .
2019-09-29 22:05:50 -04:00
# TYPE scheduler_pending_pods gauge
scheduler_pending_pods { queue = "active" } 50
scheduler_pending_pods { queue = "backoff" } 0
2022-11-07 17:02:22 -05:00
scheduler_pending_pods { queue = "gated" } 0
2019-09-29 22:05:50 -04:00
scheduler_pending_pods { queue = "unschedulable" } 0
2022-11-22 00:33:16 -05:00
` ,
} ,
{
name : "add pods to activeQ/unschedulablePods and then delete some Pods" ,
operations : [ ] operation {
addPodActiveQ ,
addPodUnschedulablePods ,
deletePod ,
deletePod ,
deletePod ,
} ,
operands : [ ] [ ] * framework . QueuedPodInfo {
pInfos [ : 30 ] ,
pInfos [ 30 : ] ,
pInfos [ : 2 ] ,
pInfos [ 30 : 33 ] ,
pInfos [ 50 : 54 ] ,
} ,
metricsName : "scheduler_pending_pods" ,
wants : `
# 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 .
# TYPE scheduler_pending_pods gauge
scheduler_pending_pods { queue = "active" } 28
scheduler_pending_pods { queue = "backoff" } 0
scheduler_pending_pods { queue = "gated" } 6
scheduler_pending_pods { queue = "unschedulable" } 17
` ,
} ,
{
name : "add pods to activeQ/unschedulablePods and then update some Pods as queueable" ,
operations : [ ] operation {
addPodActiveQ ,
addPodUnschedulablePods ,
updatePodQueueable ,
} ,
operands : [ ] [ ] * framework . QueuedPodInfo {
pInfos [ : 30 ] ,
pInfos [ 30 : ] ,
pInfos [ 50 : 55 ] ,
} ,
metricsName : "scheduler_pending_pods" ,
wants : `
# 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 .
# TYPE scheduler_pending_pods gauge
scheduler_pending_pods { queue = "active" } 35
scheduler_pending_pods { queue = "backoff" } 0
scheduler_pending_pods { queue = "gated" } 5
scheduler_pending_pods { queue = "unschedulable" } 20
2019-09-29 22:05:50 -04:00
` ,
2019-03-20 02:53:33 -04:00
} ,
2023-03-01 21:57:50 -05:00
{
name : "the metrics should not be recorded (pluginMetricsSamplePercent=0)" ,
operations : [ ] operation {
add ,
} ,
operands : [ ] [ ] * framework . QueuedPodInfo {
pInfos [ : 1 ] ,
} ,
metricsName : "scheduler_plugin_execution_duration_seconds" ,
pluginMetricsSamplePercent : 0 ,
wants : `
# HELP scheduler_plugin_execution_duration_seconds [ ALPHA ] Duration for running a plugin at a specific extension point .
# TYPE scheduler_plugin_execution_duration_seconds histogram
` , // the observed value will always be 0, because we don't proceed the fake clock.
} ,
{
name : "the metrics should be recorded (pluginMetricsSamplePercent=100)" ,
operations : [ ] operation {
add ,
} ,
operands : [ ] [ ] * framework . QueuedPodInfo {
pInfos [ : 1 ] ,
} ,
metricsName : "scheduler_plugin_execution_duration_seconds" ,
pluginMetricsSamplePercent : 100 ,
wants : `
# HELP scheduler_plugin_execution_duration_seconds [ ALPHA ] Duration for running a plugin at a specific extension point .
# TYPE scheduler_plugin_execution_duration_seconds histogram
scheduler_plugin_execution_duration_seconds_bucket { extension_point = "PreEnqueue" , plugin = "preEnqueuePlugin" , status = "Success" , le = "1e-05" } 1
scheduler_plugin_execution_duration_seconds_bucket { extension_point = "PreEnqueue" , plugin = "preEnqueuePlugin" , status = "Success" , le = "1.5000000000000002e-05" } 1
scheduler_plugin_execution_duration_seconds_bucket { extension_point = "PreEnqueue" , plugin = "preEnqueuePlugin" , status = "Success" , le = "2.2500000000000005e-05" } 1
scheduler_plugin_execution_duration_seconds_bucket { extension_point = "PreEnqueue" , plugin = "preEnqueuePlugin" , status = "Success" , le = "3.375000000000001e-05" } 1
scheduler_plugin_execution_duration_seconds_bucket { extension_point = "PreEnqueue" , plugin = "preEnqueuePlugin" , status = "Success" , le = "5.062500000000001e-05" } 1
scheduler_plugin_execution_duration_seconds_bucket { extension_point = "PreEnqueue" , plugin = "preEnqueuePlugin" , status = "Success" , le = "7.593750000000002e-05" } 1
scheduler_plugin_execution_duration_seconds_bucket { extension_point = "PreEnqueue" , plugin = "preEnqueuePlugin" , status = "Success" , le = "0.00011390625000000003" } 1
scheduler_plugin_execution_duration_seconds_bucket { extension_point = "PreEnqueue" , plugin = "preEnqueuePlugin" , status = "Success" , le = "0.00017085937500000006" } 1
scheduler_plugin_execution_duration_seconds_bucket { extension_point = "PreEnqueue" , plugin = "preEnqueuePlugin" , status = "Success" , le = "0.0002562890625000001" } 1
scheduler_plugin_execution_duration_seconds_bucket { extension_point = "PreEnqueue" , plugin = "preEnqueuePlugin" , status = "Success" , le = "0.00038443359375000017" } 1
scheduler_plugin_execution_duration_seconds_bucket { extension_point = "PreEnqueue" , plugin = "preEnqueuePlugin" , status = "Success" , le = "0.0005766503906250003" } 1
scheduler_plugin_execution_duration_seconds_bucket { extension_point = "PreEnqueue" , plugin = "preEnqueuePlugin" , status = "Success" , le = "0.0008649755859375004" } 1
scheduler_plugin_execution_duration_seconds_bucket { extension_point = "PreEnqueue" , plugin = "preEnqueuePlugin" , status = "Success" , le = "0.0012974633789062506" } 1
scheduler_plugin_execution_duration_seconds_bucket { extension_point = "PreEnqueue" , plugin = "preEnqueuePlugin" , status = "Success" , le = "0.0019461950683593758" } 1
scheduler_plugin_execution_duration_seconds_bucket { extension_point = "PreEnqueue" , plugin = "preEnqueuePlugin" , status = "Success" , le = "0.0029192926025390638" } 1
scheduler_plugin_execution_duration_seconds_bucket { extension_point = "PreEnqueue" , plugin = "preEnqueuePlugin" , status = "Success" , le = "0.004378938903808595" } 1
scheduler_plugin_execution_duration_seconds_bucket { extension_point = "PreEnqueue" , plugin = "preEnqueuePlugin" , status = "Success" , le = "0.006568408355712893" } 1
scheduler_plugin_execution_duration_seconds_bucket { extension_point = "PreEnqueue" , plugin = "preEnqueuePlugin" , status = "Success" , le = "0.009852612533569338" } 1
scheduler_plugin_execution_duration_seconds_bucket { extension_point = "PreEnqueue" , plugin = "preEnqueuePlugin" , status = "Success" , le = "0.014778918800354007" } 1
scheduler_plugin_execution_duration_seconds_bucket { extension_point = "PreEnqueue" , plugin = "preEnqueuePlugin" , status = "Success" , le = "0.02216837820053101" } 1
scheduler_plugin_execution_duration_seconds_bucket { extension_point = "PreEnqueue" , plugin = "preEnqueuePlugin" , status = "Success" , le = "+Inf" } 1
scheduler_plugin_execution_duration_seconds_sum { extension_point = "PreEnqueue" , plugin = "preEnqueuePlugin" , status = "Success" } 0
scheduler_plugin_execution_duration_seconds_count { extension_point = "PreEnqueue" , plugin = "preEnqueuePlugin" , status = "Success" } 1
` , // the observed value will always be 0, because we don't proceed the fake clock.
} ,
2025-11-20 06:21:44 -05:00
{
name : "Gated metric should be 1 when Ungated to Gated transition into moveToActiveQ" ,
operations : [ ] operation {
addPodUnschedulablePods ,
moveClockForward ,
updatePluginToGateAllPods ,
updatePodQueueable ,
} ,
operands : [ ] [ ] * framework . QueuedPodInfo {
pInfos [ : 1 ] ,
{ nil } ,
{ nil } ,
pInfos [ : 1 ] ,
} ,
metricsName : "scheduler_pending_pods" ,
pluginMetricsSamplePercent : 100 ,
wants : `
# 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 .
# TYPE scheduler_pending_pods gauge
scheduler_pending_pods { queue = "active" } 0
scheduler_pending_pods { queue = "backoff" } 0
scheduler_pending_pods { queue = "gated" } 1
scheduler_pending_pods { queue = "unschedulable" } 0
` ,
} ,
{
name : "Gated metric should be 1 when Ungated to Gated transition into moveToBackoffQ" ,
operations : [ ] operation {
addPodUnschedulablePods ,
updatePluginToGateAllPods ,
updatePodQueueable ,
} ,
operands : [ ] [ ] * framework . QueuedPodInfo {
pInfos [ : 1 ] ,
{ nil } ,
pInfos [ : 1 ] ,
} ,
metricsName : "scheduler_pending_pods" ,
pluginMetricsSamplePercent : 100 ,
wants : `
# 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 .
# TYPE scheduler_pending_pods gauge
scheduler_pending_pods { queue = "active" } 0
scheduler_pending_pods { queue = "backoff" } 0
scheduler_pending_pods { queue = "gated" } 1
scheduler_pending_pods { queue = "unschedulable" } 0
` ,
} ,
{
name : "Gated metric should be 1 when Ungated to Gated transition when popFromBackoffQ is disabled" ,
operations : [ ] operation {
addPodUnschedulablePods ,
moveClockForward ,
updatePluginToGateAllPods ,
updatePodQueueable ,
} ,
operands : [ ] [ ] * framework . QueuedPodInfo {
pInfos [ : 1 ] ,
{ nil } ,
{ nil } ,
pInfos [ : 1 ] ,
} ,
metricsName : "scheduler_pending_pods" ,
pluginMetricsSamplePercent : 100 ,
disablePopFromBackoffQ : true ,
wants : `
# 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 .
# TYPE scheduler_pending_pods gauge
scheduler_pending_pods { queue = "active" } 0
scheduler_pending_pods { queue = "backoff" } 0
scheduler_pending_pods { queue = "gated" } 1
scheduler_pending_pods { queue = "unschedulable" } 0
` ,
} ,
{
name : "Gated metric should be 0 when Ungated -> Gated -> Ungated (ActiveQ) transition" ,
operations : [ ] operation {
addPodUnschedulablePods ,
moveClockForward ,
updatePluginToGateAllPods ,
updatePodQueueable ,
updatePluginToUngateAllPods ,
updatePodQueueable ,
} ,
operands : [ ] [ ] * framework . QueuedPodInfo {
pInfos [ : 1 ] ,
{ nil } ,
{ nil } ,
pInfos [ : 1 ] ,
{ nil } ,
pInfos [ : 1 ] ,
} ,
pluginMetricsSamplePercent : 100 ,
metricsName : "scheduler_pending_pods" ,
wants : `
# 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 .
# TYPE scheduler_pending_pods gauge
scheduler_pending_pods { queue = "active" } 1
scheduler_pending_pods { queue = "backoff" } 0
scheduler_pending_pods { queue = "gated" } 0
scheduler_pending_pods { queue = "unschedulable" } 0
` ,
} ,
{
name : "Gated metric should be 0 when Ungated -> Gated -> Ungated (BackoffQ) transition" ,
operations : [ ] operation {
addPodUnschedulablePods ,
updatePluginToGateAllPods ,
updatePodQueueable ,
updatePluginToUngateAllPods ,
updatePodQueueable ,
} ,
operands : [ ] [ ] * framework . QueuedPodInfo {
pInfos [ : 1 ] ,
{ nil } ,
pInfos [ : 1 ] ,
{ nil } ,
pInfos [ : 1 ] ,
} ,
pluginMetricsSamplePercent : 100 ,
metricsName : "scheduler_pending_pods" ,
wants : `
# 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 .
# TYPE scheduler_pending_pods gauge
scheduler_pending_pods { queue = "active" } 0
scheduler_pending_pods { queue = "backoff" } 1
scheduler_pending_pods { queue = "gated" } 0
scheduler_pending_pods { queue = "unschedulable" } 0
` ,
} ,
{
name : "Gated metric should be 0 when Ungated -> Gated -> Ungated transition, when popFromBackoffQ is disabled" ,
operations : [ ] operation {
addPodUnschedulablePods ,
moveClockForward ,
updatePluginToGateAllPods ,
updatePodQueueable ,
updatePluginToUngateAllPods ,
updatePodQueueable ,
} ,
operands : [ ] [ ] * framework . QueuedPodInfo {
pInfos [ : 1 ] ,
{ nil } ,
{ nil } ,
pInfos [ : 1 ] ,
{ nil } ,
pInfos [ : 1 ] ,
} ,
pluginMetricsSamplePercent : 100 ,
disablePopFromBackoffQ : true ,
wants : `
# 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 .
# TYPE scheduler_pending_pods gauge
scheduler_pending_pods { queue = "active" } 1
scheduler_pending_pods { queue = "backoff" } 0
scheduler_pending_pods { queue = "gated" } 0
scheduler_pending_pods { queue = "unschedulable" } 0
` ,
} ,
2019-03-20 02:53:33 -04:00
}
resetMetrics := func ( ) {
2019-08-22 21:00:06 -04:00
metrics . ActivePods ( ) . Set ( 0 )
metrics . BackoffPods ( ) . Set ( 0 )
metrics . UnschedulablePods ( ) . Set ( 0 )
2022-11-07 17:02:22 -05:00
metrics . GatedPods ( ) . Set ( 0 )
2023-03-01 21:57:50 -05:00
metrics . PluginExecutionDuration . Reset ( )
2019-03-20 02:53:33 -04:00
}
for _ , test := range tests {
t . Run ( test . name , func ( t * testing . T ) {
2023-03-22 07:48:04 -04:00
logger , ctx := ktesting . NewTestContext ( t )
ctx , cancel := context . WithCancel ( ctx )
2022-01-22 14:00:56 -05:00
defer cancel ( )
2024-08-17 19:45:32 -04:00
resetMetrics ( )
resetPodInfos ( )
2022-11-07 17:02:22 -05:00
2024-09-05 22:19:49 -04:00
m := makeEmptyQueueingHintMapPerProfile ( )
m [ "" ] [ framework . EventUnscheduledPodUpdate ] = [ ] * QueueingHintFunction {
{
PluginName : preenqueuePluginName ,
QueueingHintFn : queueHintReturnQueue ,
} ,
}
2025-07-24 07:48:07 -04:00
preenq := map [ string ] map [ string ] fwk . PreEnqueuePlugin { "" : { ( & preEnqueuePlugin { } ) . Name ( ) : & preEnqueuePlugin { allowlists : [ ] string { queueable } } } }
2023-03-01 21:57:50 -05:00
recorder := metrics . NewMetricsAsyncRecorder ( 3 , 20 * time . Microsecond , ctx . Done ( ) )
2025-08-06 03:35:46 -04:00
queue := NewTestQueue ( ctx , newDefaultQueueSort ( ) , WithClock ( testingclock . NewFakeClock ( timestamp ) ) , WithPreEnqueuePluginMap ( preenq ) , WithPluginMetricsSamplePercent ( test . pluginMetricsSamplePercent ) , WithMetricsRecorder ( recorder ) , WithQueueingHintMapPerProfile ( m ) )
2025-11-20 06:21:44 -05:00
queue . isPopFromBackoffQEnabled = ! test . disablePopFromBackoffQ
2019-03-20 02:53:33 -04:00
for i , op := range test . operations {
for _ , pInfo := range test . operands [ i ] {
2023-09-05 14:46:33 -04:00
op ( t , logger , queue , pInfo )
2019-03-20 02:53:33 -04:00
}
}
2023-03-01 21:57:50 -05:00
recorder . FlushMetrics ( )
2019-09-29 22:05:50 -04:00
if err := testutil . GatherAndCompare ( metrics . GetGather ( ) , strings . NewReader ( test . wants ) , test . metricsName ) ; err != nil {
t . Fatal ( err )
2019-03-20 02:53:33 -04:00
}
} )
}
}
2019-10-07 19:06:00 -04:00
// TestPerPodSchedulingMetrics makes sure pod schedule attempts is updated correctly while
// initialAttemptTimestamp stays the same during multiple add/pop operations.
func TestPerPodSchedulingMetrics ( t * testing . T ) {
timestamp := time . Now ( )
2023-03-22 07:48:04 -04:00
logger , ctx := ktesting . NewTestContext ( t )
ctx , cancel := context . WithCancel ( ctx )
2022-01-22 14:00:56 -05:00
defer cancel ( )
2019-10-07 19:06:00 -04:00
2023-06-23 20:18:32 -04:00
tests := [ ] struct {
name string
perPodSchedulingMetricsScenario func ( * testingclock . FakeClock , * PriorityQueue , * v1 . Pod )
wantAttempts int
wantInitialAttemptTs time . Time
} {
{
// The queue operations are Add -> Pop.
name : "pod is created and scheduled after 1 attempt" ,
perPodSchedulingMetricsScenario : func ( c * testingclock . FakeClock , queue * PriorityQueue , pod * v1 . Pod ) {
queue . Add ( logger , pod )
} ,
wantAttempts : 1 ,
wantInitialAttemptTs : timestamp ,
} ,
{
// The queue operations are Add -> Pop -> AddUnschedulableIfNotPresent -> flushUnschedulablePodsLeftover -> Pop.
name : "pod is created and scheduled after 2 attempts" ,
perPodSchedulingMetricsScenario : func ( c * testingclock . FakeClock , queue * PriorityQueue , pod * v1 . Pod ) {
queue . Add ( logger , pod )
2023-09-28 22:59:22 -04:00
pInfo , err := queue . Pop ( logger )
2023-06-23 20:18:32 -04:00
if err != nil {
t . Fatalf ( "Failed to pop a pod %v" , err )
}
2019-10-07 19:06:00 -04:00
2023-09-11 12:30:11 -04:00
pInfo . UnschedulablePlugins = sets . New ( "plugin" )
2023-06-23 20:18:32 -04:00
queue . AddUnschedulableIfNotPresent ( logger , pInfo , 1 )
// Override clock to exceed the DefaultPodMaxInUnschedulablePodsDuration so that unschedulable pods
// will be moved to activeQ
c . SetTime ( timestamp . Add ( DefaultPodMaxInUnschedulablePodsDuration + 1 ) )
queue . flushUnschedulablePodsLeftover ( logger )
} ,
wantAttempts : 2 ,
wantInitialAttemptTs : timestamp ,
} ,
{
// The queue operations are Add -> Pop -> AddUnschedulableIfNotPresent -> flushUnschedulablePodsLeftover -> Update -> Pop.
name : "pod is created and scheduled after 2 attempts but before the second pop, call update" ,
perPodSchedulingMetricsScenario : func ( c * testingclock . FakeClock , queue * PriorityQueue , pod * v1 . Pod ) {
queue . Add ( logger , pod )
2023-09-28 22:59:22 -04:00
pInfo , err := queue . Pop ( logger )
2023-06-23 20:18:32 -04:00
if err != nil {
t . Fatalf ( "Failed to pop a pod %v" , err )
}
2023-05-15 15:20:44 -04:00
2023-09-11 12:30:11 -04:00
pInfo . UnschedulablePlugins = sets . New ( "plugin" )
2023-06-23 20:18:32 -04:00
queue . AddUnschedulableIfNotPresent ( logger , pInfo , 1 )
// Override clock to exceed the DefaultPodMaxInUnschedulablePodsDuration so that unschedulable pods
// will be moved to activeQ
updatedTimestamp := timestamp
c . SetTime ( updatedTimestamp . Add ( DefaultPodMaxInUnschedulablePodsDuration + 1 ) )
queue . flushUnschedulablePodsLeftover ( logger )
newPod := pod . DeepCopy ( )
newPod . Generation = 1
queue . Update ( logger , pod , newPod )
} ,
wantAttempts : 2 ,
wantInitialAttemptTs : timestamp ,
} ,
{
// The queue operations are Add gated pod -> check unschedulablePods -> lift gate & update pod -> Pop.
name : "A gated pod is created and scheduled after lifting gate" ,
perPodSchedulingMetricsScenario : func ( c * testingclock . FakeClock , queue * PriorityQueue , pod * v1 . Pod ) {
// Create a queue with PreEnqueuePlugin
2025-07-24 07:48:07 -04:00
queue . preEnqueuePluginMap = map [ string ] map [ string ] fwk . PreEnqueuePlugin { "" : { ( & preEnqueuePlugin { } ) . Name ( ) : & preEnqueuePlugin { allowlists : [ ] string { "foo" } } } }
2023-06-23 20:18:32 -04:00
queue . pluginMetricsSamplePercent = 0
queue . Add ( logger , pod )
// Check pod is added to the unschedulablePods queue.
if getUnschedulablePod ( queue , pod ) != pod {
t . Errorf ( "Pod %v was not found in the unschedulablePods." , pod . Name )
}
// Override clock to get different InitialAttemptTimestamp
c . Step ( 1 * time . Minute )
2023-05-15 15:20:44 -04:00
2023-06-23 20:18:32 -04:00
// Update pod with the required label to get it out of unschedulablePods queue.
updateGatedPod := pod . DeepCopy ( )
updateGatedPod . Labels = map [ string ] string { "foo" : "" }
queue . Update ( logger , pod , updateGatedPod )
} ,
wantAttempts : 1 ,
wantInitialAttemptTs : timestamp . Add ( 1 * time . Minute ) ,
} ,
2023-05-15 15:20:44 -04:00
}
2023-06-23 20:18:32 -04:00
for _ , test := range tests {
t . Run ( test . name , func ( t * testing . T ) {
2023-05-15 15:20:44 -04:00
2023-06-23 20:18:32 -04:00
c := testingclock . NewFakeClock ( timestamp )
pod := st . MakePod ( ) . Name ( "test-pod" ) . Namespace ( "test-ns" ) . UID ( "test-uid" ) . Obj ( )
queue := NewTestQueue ( ctx , newDefaultQueueSort ( ) , WithClock ( c ) )
2023-05-15 15:20:44 -04:00
2023-06-23 20:18:32 -04:00
test . perPodSchedulingMetricsScenario ( c , queue , pod )
2023-09-28 22:59:22 -04:00
podInfo , err := queue . Pop ( logger )
2023-06-23 20:18:32 -04:00
if err != nil {
t . Fatal ( err )
}
if podInfo . Attempts != test . wantAttempts {
t . Errorf ( "Pod schedule attempt unexpected, got %v, want %v" , podInfo . Attempts , test . wantAttempts )
}
if * podInfo . InitialAttemptTimestamp != test . wantInitialAttemptTs {
t . Errorf ( "Pod initial schedule attempt timestamp unexpected, got %v, want %v" , * podInfo . InitialAttemptTimestamp , test . wantInitialAttemptTs )
}
} )
2023-05-15 15:20:44 -04:00
}
2019-10-07 19:06:00 -04:00
}
2019-10-07 13:29:53 -04:00
func TestIncomingPodsMetrics ( t * testing . T ) {
timestamp := time . Now ( )
2023-07-17 18:53:07 -04:00
unschedulablePlg := "unschedulable_plugin"
2020-04-20 20:36:26 -04:00
var pInfos = make ( [ ] * framework . QueuedPodInfo , 0 , 3 )
2024-10-31 11:29:29 -04:00
2019-10-07 13:29:53 -04:00
for i := 1 ; i <= 3 ; i ++ {
2020-04-20 20:36:26 -04:00
p := & framework . QueuedPodInfo {
2022-10-12 10:11:04 -04:00
PodInfo : mustNewTestPodInfo ( t ,
2022-04-18 21:06:58 -04:00
st . MakePod ( ) . Name ( fmt . Sprintf ( "test-pod-%d" , i ) ) . Namespace ( fmt . Sprintf ( "ns%d" , i ) ) . UID ( fmt . Sprintf ( "tp-%d" , i ) ) . Obj ( ) ) ,
2023-07-17 18:53:07 -04:00
Timestamp : timestamp ,
UnschedulablePlugins : sets . New ( unschedulablePlg ) ,
2019-10-07 13:29:53 -04:00
}
pInfos = append ( pInfos , p )
}
tests := [ ] struct {
name string
operations [ ] operation
want string
} {
{
name : "add pods to activeQ" ,
operations : [ ] operation {
add ,
} ,
want : `
2024-10-01 23:24:03 -04:00
scheduler_queue_incoming_pods_total { event = "UnschedulablePodAdd" , queue = "active" } 3
2019-10-07 13:29:53 -04:00
` ,
} ,
{
2022-03-24 05:38:49 -04:00
name : "add pods to unschedulablePods" ,
2019-10-07 13:29:53 -04:00
operations : [ ] operation {
2023-09-05 14:46:33 -04:00
popAndRequeueAsUnschedulable ,
2019-10-07 13:29:53 -04:00
} ,
2024-10-01 23:24:03 -04:00
want : ` scheduler_queue_incoming_pods_total { event = "UnschedulablePodAdd" , queue = "active" } 3
2019-10-07 13:29:53 -04:00
scheduler_queue_incoming_pods_total { event = "ScheduleAttemptFailure" , queue = "unschedulable" } 3
` ,
} ,
{
2022-03-24 05:38:49 -04:00
name : "add pods to unschedulablePods and then move all to backoffQ" ,
2019-10-07 13:29:53 -04:00
operations : [ ] operation {
2023-09-05 14:46:33 -04:00
popAndRequeueAsUnschedulable ,
2019-10-07 13:29:53 -04:00
moveAllToActiveOrBackoffQ ,
} ,
2024-10-01 23:24:03 -04:00
want : ` scheduler_queue_incoming_pods_total { event = "UnschedulablePodAdd" , queue = "active" } 3
2024-08-21 09:40:41 -04:00
scheduler_queue_incoming_pods_total { event = "ScheduleAttemptFailure" , queue = "unschedulable" } 3
2021-01-29 01:29:10 -05:00
scheduler_queue_incoming_pods_total { event = "UnschedulableTimeout" , queue = "backoff" } 3
2019-10-07 13:29:53 -04:00
` ,
} ,
{
2022-03-24 05:38:49 -04:00
name : "add pods to unschedulablePods and then move all to activeQ" ,
2019-10-07 13:29:53 -04:00
operations : [ ] operation {
2023-09-05 14:46:33 -04:00
popAndRequeueAsUnschedulable ,
2019-10-07 13:29:53 -04:00
moveClockForward ,
moveAllToActiveOrBackoffQ ,
} ,
2024-10-01 23:24:03 -04:00
want : ` scheduler_queue_incoming_pods_total { event = "UnschedulablePodAdd" , queue = "active" } 3
2024-08-21 09:40:41 -04:00
scheduler_queue_incoming_pods_total { event = "ScheduleAttemptFailure" , queue = "unschedulable" } 3
2021-01-29 01:29:10 -05:00
scheduler_queue_incoming_pods_total { event = "UnschedulableTimeout" , queue = "active" } 3
2019-10-07 13:29:53 -04:00
` ,
} ,
{
name : "make some pods subject to backoff and add them to backoffQ, then flush backoffQ" ,
operations : [ ] operation {
2023-09-05 14:46:33 -04:00
popAndRequeueAsBackoff ,
2019-10-07 13:29:53 -04:00
moveClockForward ,
flushBackoffQ ,
} ,
2024-10-01 23:24:03 -04:00
want : ` scheduler_queue_incoming_pods_total { event = "UnschedulablePodAdd" , queue = "active" } 3
2024-08-21 09:40:41 -04:00
scheduler_queue_incoming_pods_total { event = "BackoffComplete" , queue = "active" } 3
2019-10-07 13:29:53 -04:00
scheduler_queue_incoming_pods_total { event = "ScheduleAttemptFailure" , queue = "backoff" } 3
` ,
} ,
}
for _ , test := range tests {
t . Run ( test . name , func ( t * testing . T ) {
2023-03-22 07:48:04 -04:00
logger , ctx := ktesting . NewTestContext ( t )
ctx , cancel := context . WithCancel ( ctx )
2022-01-22 14:00:56 -05:00
defer cancel ( )
2024-08-17 19:45:32 -04:00
metrics . SchedulerQueueIncomingPods . Reset ( )
2022-01-22 14:00:56 -05:00
queue := NewTestQueue ( ctx , newDefaultQueueSort ( ) , WithClock ( testingclock . NewFakeClock ( timestamp ) ) )
2019-10-07 13:29:53 -04:00
for _ , op := range test . operations {
for _ , pInfo := range pInfos {
2023-09-05 14:46:33 -04:00
op ( t , logger , queue , pInfo )
2019-10-07 13:29:53 -04:00
}
}
metricName := metrics . SchedulerSubsystem + "_" + metrics . SchedulerQueueIncomingPods . Name
if err := testutil . CollectAndCompare ( metrics . SchedulerQueueIncomingPods , strings . NewReader ( queueMetricMetadata + test . want ) , metricName ) ; err != nil {
t . Errorf ( "unexpected collecting result:\n%s" , err )
}
} )
}
}
2020-01-28 13:29:44 -05:00
func TestBackOffFlow ( t * testing . T ) {
2021-09-17 05:48:22 -04:00
cl := testingclock . NewFakeClock ( time . Now ( ) )
2023-03-22 07:48:04 -04:00
logger , ctx := ktesting . NewTestContext ( t )
ctx , cancel := context . WithCancel ( ctx )
2022-01-22 14:00:56 -05:00
defer cancel ( )
2020-01-28 13:29:44 -05:00
steps := [ ] struct {
wantBackoff time . Duration
} {
{ wantBackoff : time . Second } ,
{ wantBackoff : 2 * time . Second } ,
{ wantBackoff : 4 * time . Second } ,
{ wantBackoff : 8 * time . Second } ,
{ wantBackoff : 10 * time . Second } ,
{ wantBackoff : 10 * time . Second } ,
{ wantBackoff : 10 * time . Second } ,
}
2025-03-07 05:22:36 -05:00
for _ , popFromBackoffQEnabled := range [ ] bool { true , false } {
featuregatetesting . SetFeatureGateDuringTest ( t , utilfeature . DefaultFeatureGate , features . SchedulerPopFromBackoffQ , popFromBackoffQEnabled )
2021-01-25 02:07:39 -05:00
2025-03-07 05:22:36 -05:00
q := NewTestQueue ( ctx , newDefaultQueueSort ( ) , WithClock ( cl ) )
2020-01-28 13:29:44 -05:00
2025-03-07 05:22:36 -05:00
pod := st . MakePod ( ) . Name ( "test-pod" ) . Namespace ( "test-ns" ) . UID ( "test-uid" ) . Obj ( )
podID := types . NamespacedName {
Namespace : pod . Namespace ,
Name : pod . Name ,
}
q . Add ( logger , pod )
for i , step := range steps {
t . Run ( fmt . Sprintf ( "step %d popFromBackoffQEnabled(%v)" , i , popFromBackoffQEnabled ) , func ( t * testing . T ) {
timestamp := cl . Now ( )
// Simulate schedule attempt.
podInfo , err := q . Pop ( logger )
if err != nil {
t . Fatal ( err )
}
if podInfo . Attempts != i + 1 {
t . Errorf ( "got attempts %d, want %d" , podInfo . Attempts , i + 1 )
}
2024-11-11 16:29:19 -05:00
podInfo . UnschedulablePlugins . Insert ( "unsched-plugin" )
2025-03-07 05:22:36 -05:00
err = q . AddUnschedulableIfNotPresent ( logger , podInfo , int64 ( i ) )
if err != nil {
t . Fatalf ( "unexpected error from AddUnschedulableIfNotPresent: %v" , err )
}
2020-01-28 13:29:44 -05:00
2025-03-07 05:22:36 -05:00
// An event happens.
q . MoveAllToActiveOrBackoffQueue ( logger , framework . EventUnschedulableTimeout , nil , nil , nil )
2020-01-28 13:29:44 -05:00
2025-03-07 05:22:36 -05:00
if ! q . backoffQ . has ( podInfo ) {
t . Errorf ( "pod %v is not in the backoff queue" , podID )
}
2020-01-28 13:29:44 -05:00
2025-03-07 05:22:36 -05:00
// Check backoff duration.
deadline := podInfo . BackoffExpiration
backoff := deadline . Sub ( timestamp )
if popFromBackoffQEnabled {
// If popFromBackoffQEnabled, the actual backoff can be calculated by rounding up to the ordering window duration.
backoff = backoff . Truncate ( backoffQOrderingWindowDuration ) + backoffQOrderingWindowDuration
}
if backoff != step . wantBackoff {
t . Errorf ( "got backoff %s, want %s" , backoff , step . wantBackoff )
}
2020-01-28 13:29:44 -05:00
2025-03-07 05:22:36 -05:00
// Simulate routine that continuously flushes the backoff queue.
cl . Step ( backoffQOrderingWindowDuration )
q . flushBackoffQCompleted ( logger )
// Still in backoff queue after an early flush.
if ! q . backoffQ . has ( podInfo ) {
t . Errorf ( "pod %v is not in the backoff queue" , podID )
}
// Moved out of the backoff queue after timeout.
cl . Step ( backoff )
q . flushBackoffQCompleted ( logger )
if q . backoffQ . has ( podInfo ) {
t . Errorf ( "pod %v is still in the backoff queue" , podID )
}
} )
}
2020-01-28 13:29:44 -05:00
}
2020-02-13 19:31:07 -05:00
}
2021-03-11 15:31:33 -05:00
func TestMoveAllToActiveOrBackoffQueue_PreEnqueueChecks ( t * testing . T ) {
var podInfos [ ] * framework . QueuedPodInfo
for i := 0 ; i < 5 ; i ++ {
2022-04-18 21:06:58 -04:00
pInfo := newQueuedPodInfoForLookup (
st . MakePod ( ) . Name ( fmt . Sprintf ( "p%d" , i ) ) . Priority ( int32 ( i ) ) . Obj ( ) ,
)
2021-03-11 15:31:33 -05:00
podInfos = append ( podInfos , pInfo )
}
tests := [ ] struct {
name string
preEnqueueCheck PreEnqueueCheck
podInfos [ ] * framework . QueuedPodInfo
2025-06-26 11:06:29 -04:00
event fwk . ClusterEvent
2025-03-07 05:22:36 -05:00
want sets . Set [ string ]
2021-03-11 15:31:33 -05:00
} {
{
name : "nil PreEnqueueCheck" ,
podInfos : podInfos ,
2024-10-01 23:24:03 -04:00
event : framework . EventUnschedulableTimeout ,
2025-03-07 05:22:36 -05:00
want : sets . New ( "p0" , "p1" , "p2" , "p3" , "p4" ) ,
2021-03-11 15:31:33 -05:00
} ,
{
name : "move Pods with priority greater than 2" ,
podInfos : podInfos ,
2024-10-01 23:24:03 -04:00
event : framework . EventUnschedulableTimeout ,
2021-03-11 15:31:33 -05:00
preEnqueueCheck : func ( pod * v1 . Pod ) bool { return * pod . Spec . Priority >= 2 } ,
2025-03-07 05:22:36 -05:00
want : sets . New ( "p2" , "p3" , "p4" ) ,
2021-03-11 15:31:33 -05:00
} ,
{
name : "move Pods with even priority and greater than 2" ,
podInfos : podInfos ,
2024-10-01 23:24:03 -04:00
event : framework . EventUnschedulableTimeout ,
2021-03-11 15:31:33 -05:00
preEnqueueCheck : func ( pod * v1 . Pod ) bool {
return * pod . Spec . Priority % 2 == 0 && * pod . Spec . Priority >= 2
} ,
2025-03-07 05:22:36 -05:00
want : sets . New ( "p2" , "p4" ) ,
2021-03-11 15:31:33 -05:00
} ,
{
name : "move Pods with even and negative priority" ,
podInfos : podInfos ,
2024-10-01 23:24:03 -04:00
event : framework . EventUnschedulableTimeout ,
2021-03-11 15:31:33 -05:00
preEnqueueCheck : func ( pod * v1 . Pod ) bool {
return * pod . Spec . Priority % 2 == 0 && * pod . Spec . Priority < 0
} ,
} ,
2024-01-07 00:43:21 -05:00
{
name : "preCheck isn't called if the event is not interested by any plugins" ,
podInfos : podInfos ,
2024-10-01 23:24:03 -04:00
event : pvAdd , // No plugin is interested in this event.
2024-01-07 00:43:21 -05:00
preEnqueueCheck : func ( pod * v1 . Pod ) bool {
panic ( "preCheck shouldn't be called" )
} ,
} ,
2021-03-11 15:31:33 -05:00
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
2025-03-07 05:22:36 -05:00
c := testingclock . NewFakeClock ( time . Now ( ) . Truncate ( backoffQOrderingWindowDuration ) )
2023-03-22 07:48:04 -04:00
logger , ctx := ktesting . NewTestContext ( t )
ctx , cancel := context . WithCancel ( ctx )
2022-01-22 14:00:56 -05:00
defer cancel ( )
2025-02-18 03:23:37 -05:00
q := NewTestQueue ( ctx , newDefaultQueueSort ( ) , WithClock ( c ) )
2025-03-07 05:22:36 -05:00
for _ , podInfo := range tt . podInfos {
2023-07-17 18:53:07 -04:00
// To simulate the pod is failed in scheduling in the real world, Pop() the pod from activeQ before AddUnschedulableIfNotPresent() below.
2024-08-21 09:40:41 -04:00
q . Add ( logger , podInfo . Pod )
2023-09-28 22:59:22 -04:00
if p , err := q . Pop ( logger ) ; err != nil || p . Pod != podInfo . Pod {
2023-07-17 18:53:07 -04:00
t . Errorf ( "Expected: %v after Pop, but got: %v" , podInfo . Pod . Name , p . Pod . Name )
}
2023-09-11 12:30:11 -04:00
podInfo . UnschedulablePlugins = sets . New ( "plugin" )
2024-11-11 16:29:19 -05:00
err := q . AddUnschedulableIfNotPresent ( logger , podInfo , q . activeQ . schedulingCycle ( ) )
2023-09-11 12:30:11 -04:00
if err != nil {
t . Fatalf ( "unexpected error from AddUnschedulableIfNotPresent: %v" , err )
}
2021-03-11 15:31:33 -05:00
}
2024-01-07 00:43:21 -05:00
q . MoveAllToActiveOrBackoffQueue ( logger , tt . event , nil , nil , tt . preEnqueueCheck )
2025-03-07 05:22:36 -05:00
got := sets . New [ string ] ( )
2025-02-18 03:23:37 -05:00
c . Step ( 2 * q . backoffQ . podMaxBackoffDuration ( ) )
2025-03-10 04:56:21 -04:00
gotPodInfos := q . backoffQ . popAllBackoffCompleted ( logger )
for _ , pInfo := range gotPodInfos {
2025-03-07 05:22:36 -05:00
got . Insert ( pInfo . Pod . Name )
2025-03-10 04:56:21 -04:00
}
2021-03-11 15:31:33 -05:00
if diff := cmp . Diff ( tt . want , got ) ; diff != "" {
t . Errorf ( "Unexpected diff (-want, +got):\n%s" , diff )
}
} )
}
}
2022-11-22 00:33:16 -05:00
func makeQueuedPodInfos ( num int , namePrefix , label string , timestamp time . Time ) [ ] * framework . QueuedPodInfo {
2020-04-20 20:36:26 -04:00
var pInfos = make ( [ ] * framework . QueuedPodInfo , 0 , num )
2020-02-13 19:31:07 -05:00
for i := 1 ; i <= num ; i ++ {
2020-04-20 20:36:26 -04:00
p := & framework . QueuedPodInfo {
2022-11-22 00:33:16 -05:00
PodInfo : mustNewPodInfo (
st . MakePod ( ) . Name ( fmt . Sprintf ( "%v-%d" , namePrefix , i ) ) . Namespace ( fmt . Sprintf ( "ns%d" , i ) ) . Label ( label , "" ) . UID ( fmt . Sprintf ( "tp-%d" , i ) ) . Obj ( ) ) ,
2022-11-07 17:02:22 -05:00
Timestamp : timestamp ,
2023-03-27 03:46:13 -04:00
UnschedulablePlugins : sets . New [ string ] ( ) ,
2020-02-13 19:31:07 -05:00
}
pInfos = append ( pInfos , p )
2020-01-28 13:29:44 -05:00
}
2020-02-13 19:31:07 -05:00
return pInfos
2020-01-28 13:29:44 -05:00
}
2021-09-18 22:09:06 -04:00
2022-10-12 10:11:04 -04:00
func mustNewTestPodInfo ( t * testing . T , pod * v1 . Pod ) * framework . PodInfo {
podInfo , err := framework . NewPodInfo ( pod )
if err != nil {
t . Fatal ( err )
}
return podInfo
}
func mustNewPodInfo ( pod * v1 . Pod ) * framework . PodInfo {
podInfo , err := framework . NewPodInfo ( pod )
if err != nil {
panic ( err )
}
return podInfo
}
2023-06-08 00:54:30 -04:00
// Test_isPodWorthRequeuing tests isPodWorthRequeuing function.
func Test_isPodWorthRequeuing ( t * testing . T ) {
2024-11-05 23:19:49 -05:00
metrics . Register ( )
2023-06-08 00:54:30 -04:00
count := 0
2025-06-26 11:06:29 -04:00
queueHintReturnQueue := func ( logger klog . Logger , pod * v1 . Pod , oldObj , newObj interface { } ) ( fwk . QueueingHint , error ) {
2023-06-08 00:54:30 -04:00
count ++
2025-06-26 11:06:29 -04:00
return fwk . Queue , nil
2023-06-08 00:54:30 -04:00
}
2025-06-26 11:06:29 -04:00
queueHintReturnSkip := func ( logger klog . Logger , pod * v1 . Pod , oldObj , newObj interface { } ) ( fwk . QueueingHint , error ) {
2023-06-08 00:54:30 -04:00
count ++
2025-06-26 11:06:29 -04:00
return fwk . QueueSkip , nil
2023-06-08 00:54:30 -04:00
}
2025-06-26 11:06:29 -04:00
queueHintReturnErr := func ( logger klog . Logger , pod * v1 . Pod , oldObj , newObj interface { } ) ( fwk . QueueingHint , error ) {
2023-07-13 09:45:26 -04:00
count ++
2025-06-26 11:06:29 -04:00
return fwk . QueueSkip , fmt . Errorf ( "unexpected error" )
2023-06-08 00:54:30 -04:00
}
tests := [ ] struct {
name string
podInfo * framework . QueuedPodInfo
2025-06-26 11:06:29 -04:00
event fwk . ClusterEvent
2023-06-08 00:54:30 -04:00
oldObj interface { }
newObj interface { }
2023-10-19 07:02:11 -04:00
expected queueingStrategy
2023-06-08 00:54:30 -04:00
expectedExecutionCount int // expected total execution count of queueing hint function
queueingHintMap QueueingHintMapPerProfile
} {
{
2023-10-19 07:02:11 -04:00
name : "return Queue when no queueing hint function is registered for the event" ,
2023-06-08 00:54:30 -04:00
podInfo : & framework . QueuedPodInfo {
UnschedulablePlugins : sets . New ( "fooPlugin1" ) ,
PodInfo : mustNewPodInfo ( st . MakePod ( ) . Name ( "pod1" ) . Namespace ( "ns1" ) . UID ( "1" ) . Obj ( ) ) ,
} ,
2024-10-01 23:24:03 -04:00
event : nodeAdd ,
2023-06-08 00:54:30 -04:00
oldObj : nil ,
2023-07-13 09:45:26 -04:00
newObj : st . MakeNode ( ) . Obj ( ) ,
2023-10-19 07:02:11 -04:00
expected : queueSkip ,
2023-06-08 00:54:30 -04:00
expectedExecutionCount : 0 ,
queueingHintMap : QueueingHintMapPerProfile {
"" : {
2024-10-01 23:24:03 -04:00
// no queueing hint function for NodeAdd.
framework . EventAssignedPodAdd : {
2023-06-08 00:54:30 -04:00
{
2024-10-01 23:24:03 -04:00
// It will be ignored because the event is not NodeAdd.
2023-06-08 00:54:30 -04:00
PluginName : "fooPlugin1" ,
2023-10-19 07:02:11 -04:00
QueueingHintFn : queueHintReturnQueue ,
2023-06-08 00:54:30 -04:00
} ,
} ,
} ,
} ,
} ,
2023-07-13 09:45:26 -04:00
{
2023-10-19 07:02:11 -04:00
name : "Treat the event as Queue when QueueHintFn returns error" ,
2023-07-13 09:45:26 -04:00
podInfo : & framework . QueuedPodInfo {
UnschedulablePlugins : sets . New ( "fooPlugin1" ) ,
PodInfo : mustNewPodInfo ( st . MakePod ( ) . Name ( "pod1" ) . Namespace ( "ns1" ) . UID ( "1" ) . Obj ( ) ) ,
} ,
2024-10-01 23:24:03 -04:00
event : nodeAdd ,
2023-07-13 09:45:26 -04:00
oldObj : nil ,
newObj : st . MakeNode ( ) . Obj ( ) ,
2023-10-19 07:02:11 -04:00
expected : queueAfterBackoff ,
2023-07-13 09:45:26 -04:00
expectedExecutionCount : 1 ,
queueingHintMap : QueueingHintMapPerProfile {
"" : {
2024-10-01 23:24:03 -04:00
nodeAdd : {
2023-07-13 09:45:26 -04:00
{
PluginName : "fooPlugin1" ,
QueueingHintFn : queueHintReturnErr ,
} ,
} ,
} ,
} ,
} ,
2023-06-08 00:54:30 -04:00
{
2023-10-19 07:02:11 -04:00
name : "return Queue when the event is wildcard" ,
2023-06-08 00:54:30 -04:00
podInfo : & framework . QueuedPodInfo {
UnschedulablePlugins : sets . New ( "fooPlugin1" ) ,
PodInfo : mustNewPodInfo ( st . MakePod ( ) . Name ( "pod1" ) . Namespace ( "ns1" ) . UID ( "1" ) . Obj ( ) ) ,
} ,
2024-10-01 23:24:03 -04:00
event : framework . EventUnschedulableTimeout ,
2023-06-08 00:54:30 -04:00
oldObj : nil ,
2024-11-05 23:19:49 -05:00
newObj : nil ,
2023-10-19 07:02:11 -04:00
expected : queueAfterBackoff ,
2023-06-08 00:54:30 -04:00
expectedExecutionCount : 0 ,
queueingHintMap : QueueingHintMapPerProfile { } ,
} ,
2024-11-05 23:19:49 -05:00
{
name : "return Queue when the event is wildcard and the wildcard targets the pod to be requeued right now" ,
podInfo : & framework . QueuedPodInfo {
UnschedulablePlugins : sets . New ( "fooPlugin1" ) ,
PodInfo : mustNewPodInfo ( st . MakePod ( ) . Name ( "pod1" ) . Namespace ( "ns1" ) . UID ( "1" ) . Obj ( ) ) ,
} ,
event : framework . EventForceActivate ,
oldObj : nil ,
newObj : st . MakePod ( ) . Name ( "pod1" ) . Namespace ( "ns1" ) . UID ( "1" ) . Obj ( ) ,
2023-10-19 07:02:11 -04:00
expected : queueAfterBackoff ,
2023-06-08 00:54:30 -04:00
expectedExecutionCount : 0 ,
queueingHintMap : QueueingHintMapPerProfile { } ,
} ,
2024-11-05 23:19:49 -05:00
{
name : "return Skip when the event is wildcard, but the wildcard targets a different pod" ,
podInfo : & framework . QueuedPodInfo {
UnschedulablePlugins : sets . New ( "fooPlugin1" ) ,
PodInfo : mustNewPodInfo ( st . MakePod ( ) . Name ( "pod1" ) . Namespace ( "ns1" ) . UID ( "1" ) . Obj ( ) ) ,
} ,
event : framework . EventForceActivate ,
oldObj : nil ,
newObj : st . MakePod ( ) . Name ( "pod-different" ) . Namespace ( "ns2" ) . UID ( "2" ) . Obj ( ) ,
expected : queueSkip ,
expectedExecutionCount : 0 ,
queueingHintMap : QueueingHintMapPerProfile { } ,
} ,
2023-06-08 00:54:30 -04:00
{
2023-10-19 07:02:11 -04:00
name : "interprets Queue from the Pending plugin as queueImmediately" ,
2023-06-08 00:54:30 -04:00
podInfo : & framework . QueuedPodInfo {
2023-10-19 07:02:11 -04:00
UnschedulablePlugins : sets . New ( "fooPlugin1" , "fooPlugin3" ) ,
PendingPlugins : sets . New ( "fooPlugin2" ) ,
2023-06-08 00:54:30 -04:00
PodInfo : mustNewPodInfo ( st . MakePod ( ) . Name ( "pod1" ) . Namespace ( "ns1" ) . UID ( "1" ) . Obj ( ) ) ,
} ,
2024-10-01 23:24:03 -04:00
event : nodeAdd ,
2023-06-08 00:54:30 -04:00
oldObj : nil ,
2023-10-19 07:02:11 -04:00
newObj : st . MakeNode ( ) . Node ,
expected : queueImmediately ,
expectedExecutionCount : 2 ,
2023-06-08 00:54:30 -04:00
queueingHintMap : QueueingHintMapPerProfile {
"" : {
2024-10-01 23:24:03 -04:00
nodeAdd : {
2023-06-08 00:54:30 -04:00
{
2023-10-19 07:02:11 -04:00
PluginName : "fooPlugin1" ,
// It returns Queue and it's interpreted as queueAfterBackoff.
// But, the function continues to run other hints because the Pod has PendingPlugins, which can result in queueImmediately.
QueueingHintFn : queueHintReturnQueue ,
2023-06-08 00:54:30 -04:00
} ,
2023-07-13 09:45:26 -04:00
{
2023-10-19 07:02:11 -04:00
PluginName : "fooPlugin2" ,
// It's interpreted as queueImmediately.
// The function doesn't run other hints because queueImmediately is the highest priority.
QueueingHintFn : queueHintReturnQueue ,
2023-07-13 09:45:26 -04:00
} ,
2023-06-08 00:54:30 -04:00
{
2023-07-13 09:45:26 -04:00
PluginName : "fooPlugin3" ,
2023-10-19 07:02:11 -04:00
QueueingHintFn : queueHintReturnQueue ,
2023-06-08 00:54:30 -04:00
} ,
{
2023-07-13 09:45:26 -04:00
PluginName : "fooPlugin4" ,
2023-10-19 07:02:11 -04:00
QueueingHintFn : queueHintReturnErr ,
2023-06-08 00:54:30 -04:00
} ,
} ,
} ,
} ,
} ,
{
2023-10-19 07:02:11 -04:00
name : "interprets Queue from the Unschedulable plugin as queueAfterBackoff" ,
2023-06-08 00:54:30 -04:00
podInfo : & framework . QueuedPodInfo {
2023-10-19 07:02:11 -04:00
UnschedulablePlugins : sets . New ( "fooPlugin1" , "fooPlugin2" ) ,
2023-06-08 00:54:30 -04:00
PodInfo : mustNewPodInfo ( st . MakePod ( ) . Name ( "pod1" ) . Namespace ( "ns1" ) . UID ( "1" ) . Obj ( ) ) ,
} ,
2024-10-01 23:24:03 -04:00
event : nodeAdd ,
2023-06-08 00:54:30 -04:00
oldObj : nil ,
2023-07-13 09:45:26 -04:00
newObj : st . MakeNode ( ) . Obj ( ) ,
2023-10-19 07:02:11 -04:00
expected : queueAfterBackoff ,
expectedExecutionCount : 2 ,
2023-06-08 00:54:30 -04:00
queueingHintMap : QueueingHintMapPerProfile {
"" : {
2024-10-01 23:24:03 -04:00
nodeAdd : {
2023-06-08 00:54:30 -04:00
{
2023-10-19 07:02:11 -04:00
// Skip will be ignored
2023-06-08 00:54:30 -04:00
PluginName : "fooPlugin1" ,
2023-10-19 07:02:11 -04:00
QueueingHintFn : queueHintReturnSkip ,
2023-06-08 00:54:30 -04:00
} ,
{
2023-10-19 07:02:11 -04:00
// Skip will be ignored
2023-06-08 00:54:30 -04:00
PluginName : "fooPlugin2" ,
2023-10-19 07:02:11 -04:00
QueueingHintFn : queueHintReturnQueue ,
2023-07-13 09:45:26 -04:00
} ,
2023-06-08 00:54:30 -04:00
} ,
} ,
} ,
} ,
{
2023-10-19 07:02:11 -04:00
name : "Queueing hint function that isn't from the plugin in UnschedulablePlugins/PendingPlugins is ignored" ,
2023-06-08 00:54:30 -04:00
podInfo : & framework . QueuedPodInfo {
UnschedulablePlugins : sets . New ( "fooPlugin1" , "fooPlugin2" ) ,
PodInfo : mustNewPodInfo ( st . MakePod ( ) . Name ( "pod1" ) . Namespace ( "ns1" ) . UID ( "1" ) . Obj ( ) ) ,
} ,
2024-10-01 23:24:03 -04:00
event : nodeAdd ,
2023-06-08 00:54:30 -04:00
oldObj : nil ,
2023-10-19 07:02:11 -04:00
newObj : st . MakeNode ( ) . Node ,
expected : queueSkip ,
2023-06-08 00:54:30 -04:00
expectedExecutionCount : 2 ,
queueingHintMap : QueueingHintMapPerProfile {
"" : {
2024-10-01 23:24:03 -04:00
nodeAdd : {
2023-06-08 00:54:30 -04:00
{
PluginName : "fooPlugin1" ,
2023-10-19 07:02:11 -04:00
QueueingHintFn : queueHintReturnSkip ,
2023-06-08 00:54:30 -04:00
} ,
{
PluginName : "fooPlugin2" ,
2023-10-19 07:02:11 -04:00
QueueingHintFn : queueHintReturnSkip ,
2023-06-08 00:54:30 -04:00
} ,
{
PluginName : "fooPlugin3" ,
2023-10-19 07:02:11 -04:00
QueueingHintFn : queueHintReturnQueue , // It'll be ignored.
2023-06-08 00:54:30 -04:00
} ,
} ,
} ,
} ,
} ,
{
2023-10-19 07:02:11 -04:00
name : "If event is specific Node update event, queueing hint function for NodeUpdate/UpdateNodeLabel is also executed" ,
2023-06-08 00:54:30 -04:00
podInfo : & framework . QueuedPodInfo {
UnschedulablePlugins : sets . New ( "fooPlugin1" , "fooPlugin2" ) ,
PodInfo : mustNewPodInfo ( st . MakePod ( ) . Name ( "pod1" ) . Namespace ( "ns1" ) . UID ( "1" ) . Obj ( ) ) ,
} ,
2025-06-26 11:06:29 -04:00
event : fwk . ClusterEvent { Resource : fwk . Node , ActionType : fwk . UpdateNodeLabel } ,
2023-06-08 00:54:30 -04:00
oldObj : nil ,
2023-07-13 09:45:26 -04:00
newObj : st . MakeNode ( ) . Obj ( ) ,
2023-10-19 07:02:11 -04:00
expected : queueAfterBackoff ,
expectedExecutionCount : 1 ,
2023-06-08 00:54:30 -04:00
queueingHintMap : QueueingHintMapPerProfile {
"" : {
2025-06-26 11:06:29 -04:00
fwk . ClusterEvent { Resource : fwk . Node , ActionType : fwk . UpdateNodeLabel } : {
2023-06-08 00:54:30 -04:00
{
2023-10-19 07:02:11 -04:00
PluginName : "fooPlugin1" ,
// It's only executed and interpreted as queueAfterBackoff.
// The function doesn't run other hints because this Pod doesn't have PendingPlugins.
QueueingHintFn : queueHintReturnQueue ,
2023-06-08 00:54:30 -04:00
} ,
{
PluginName : "fooPlugin2" ,
2023-10-19 07:02:11 -04:00
QueueingHintFn : queueHintReturnQueue ,
2023-06-08 00:54:30 -04:00
} ,
} ,
2025-06-26 11:06:29 -04:00
fwk . ClusterEvent { Resource : fwk . Node , ActionType : fwk . Update } : {
2023-06-08 00:54:30 -04:00
{
PluginName : "fooPlugin1" ,
2023-10-19 07:02:11 -04:00
QueueingHintFn : queueHintReturnQueue ,
2023-06-08 00:54:30 -04:00
} ,
} ,
2024-10-01 23:24:03 -04:00
nodeAdd : { // not executed because NodeAdd is unrelated.
2023-06-08 00:54:30 -04:00
{
PluginName : "fooPlugin1" ,
2023-10-19 07:02:11 -04:00
QueueingHintFn : queueHintReturnQueue ,
2023-06-08 00:54:30 -04:00
} ,
} ,
} ,
} ,
} ,
2024-02-04 01:59:26 -05:00
{
name : "If event with '*' Resource, queueing hint function for specified Resource is also executed" ,
podInfo : & framework . QueuedPodInfo {
UnschedulablePlugins : sets . New ( "fooPlugin1" ) ,
PodInfo : mustNewPodInfo ( st . MakePod ( ) . Name ( "pod1" ) . Namespace ( "ns1" ) . UID ( "1" ) . Obj ( ) ) ,
} ,
2025-06-26 11:06:29 -04:00
event : fwk . ClusterEvent { Resource : fwk . Node , ActionType : fwk . Add } ,
2024-02-04 01:59:26 -05:00
oldObj : nil ,
newObj : st . MakeNode ( ) . Obj ( ) ,
expected : queueAfterBackoff ,
expectedExecutionCount : 1 ,
queueingHintMap : QueueingHintMapPerProfile {
"" : {
2025-06-26 11:06:29 -04:00
fwk . ClusterEvent { Resource : fwk . WildCard , ActionType : fwk . Add } : {
2024-02-04 01:59:26 -05:00
{
PluginName : "fooPlugin1" ,
QueueingHintFn : queueHintReturnQueue ,
} ,
} ,
} ,
} ,
} ,
{
name : "If event is a wildcard one, queueing hint function for all kinds of events is executed" ,
podInfo : & framework . QueuedPodInfo {
UnschedulablePlugins : sets . New ( "fooPlugin1" ) ,
PodInfo : mustNewPodInfo ( st . MakePod ( ) . Name ( "pod1" ) . Namespace ( "ns1" ) . UID ( "1" ) . Obj ( ) ) ,
} ,
2025-06-26 11:06:29 -04:00
event : fwk . ClusterEvent { Resource : fwk . Node , ActionType : fwk . UpdateNodeLabel | fwk . UpdateNodeTaint } ,
2024-02-04 01:59:26 -05:00
oldObj : nil ,
newObj : st . MakeNode ( ) . Obj ( ) ,
expected : queueAfterBackoff ,
expectedExecutionCount : 1 ,
queueingHintMap : QueueingHintMapPerProfile {
"" : {
2025-06-26 11:06:29 -04:00
fwk . ClusterEvent { Resource : fwk . WildCard , ActionType : fwk . All } : {
2024-02-04 01:59:26 -05:00
{
PluginName : "fooPlugin1" ,
QueueingHintFn : queueHintReturnQueue ,
} ,
} ,
} ,
} ,
} ,
2023-06-08 00:54:30 -04:00
}
for _ , test := range tests {
t . Run ( test . name , func ( t * testing . T ) {
count = 0 // reset count every time
logger , ctx := ktesting . NewTestContext ( t )
q := NewTestQueue ( ctx , newDefaultQueueSort ( ) , WithQueueingHintMapPerProfile ( test . queueingHintMap ) )
actual := q . isPodWorthRequeuing ( logger , test . podInfo , test . event , test . oldObj , test . newObj )
if actual != test . expected {
t . Errorf ( "isPodWorthRequeuing() = %v, want %v" , actual , test . expected )
}
if count != test . expectedExecutionCount {
t . Errorf ( "isPodWorthRequeuing() executed queueing hint functions %v times, expected: %v" , count , test . expectedExecutionCount )
}
} )
}
}
2024-05-02 06:41:19 -04:00
func Test_queuedPodInfo_gatedSetUponCreationAndUnsetUponUpdate ( t * testing . T ) {
logger , ctx := ktesting . NewTestContext ( t )
2023-12-08 03:12:13 -05:00
plugin , _ := schedulinggates . New ( ctx , nil , nil , plfeature . Features { } )
2025-07-24 07:48:07 -04:00
m := map [ string ] map [ string ] fwk . PreEnqueuePlugin { "" : { names . SchedulingGates : plugin . ( fwk . PreEnqueuePlugin ) } }
2024-05-02 06:41:19 -04:00
q := NewTestQueue ( ctx , newDefaultQueueSort ( ) , WithPreEnqueuePluginMap ( m ) )
gatedPod := st . MakePod ( ) . SchedulingGates ( [ ] string { "hello world" } ) . Obj ( )
2024-07-17 07:25:31 -04:00
q . Add ( logger , gatedPod )
2024-05-02 06:41:19 -04:00
2024-09-05 22:19:49 -04:00
if ! q . unschedulablePods . get ( gatedPod ) . Gated ( ) {
2024-07-01 07:37:53 -04:00
t . Error ( "Expected pod to be gated" )
2024-05-02 06:41:19 -04:00
}
ungatedPod := gatedPod . DeepCopy ( )
ungatedPod . Spec . SchedulingGates = nil
2024-07-17 07:25:31 -04:00
q . Update ( logger , gatedPod , ungatedPod )
2024-05-02 06:41:19 -04:00
ungatedPodInfo , _ := q . Pop ( logger )
2024-09-05 22:19:49 -04:00
if ungatedPodInfo . Gated ( ) {
2024-07-01 07:37:53 -04:00
t . Error ( "Expected pod to be ungated" )
2024-05-02 06:41:19 -04:00
}
}
2024-07-11 07:41:08 -04:00
2024-09-03 06:19:51 -04:00
func TestPriorityQueue_GetPod ( t * testing . T ) {
activeQPod := & v1 . Pod {
ObjectMeta : metav1 . ObjectMeta {
Name : "pod1" ,
Namespace : "default" ,
} ,
}
backoffQPod := & v1 . Pod {
ObjectMeta : metav1 . ObjectMeta {
Name : "pod2" ,
Namespace : "default" ,
} ,
}
unschedPod := & v1 . Pod {
ObjectMeta : metav1 . ObjectMeta {
Name : "pod3" ,
Namespace : "default" ,
} ,
}
2025-02-18 03:23:37 -05:00
logger , ctx := ktesting . NewTestContext ( t )
2024-09-03 06:19:51 -04:00
q := NewTestQueue ( ctx , newDefaultQueueSort ( ) )
q . activeQ . underLock ( func ( unlockedActiveQ unlockedActiveQueuer ) {
2025-09-02 05:35:22 -04:00
unlockedActiveQ . add ( logger , newQueuedPodInfoForLookup ( activeQPod ) , framework . EventUnscheduledPodAdd . Label ( ) )
2024-09-03 06:19:51 -04:00
} )
2025-02-25 08:05:33 -05:00
q . backoffQ . add ( logger , newQueuedPodInfoForLookup ( backoffQPod ) , framework . EventUnscheduledPodAdd . Label ( ) )
2025-11-20 06:21:44 -05:00
q . unschedulablePods . addOrUpdate ( newQueuedPodInfoForLookup ( unschedPod ) , false , framework . EventUnscheduledPodAdd . Label ( ) )
2024-09-03 06:19:51 -04:00
tests := [ ] struct {
name string
podName string
namespace string
expectedPod * v1 . Pod
expectedOK bool
} {
{
name : "pod is found in activeQ" ,
podName : "pod1" ,
namespace : "default" ,
expectedPod : activeQPod ,
expectedOK : true ,
} ,
{
name : "pod is found in backoffQ" ,
podName : "pod2" ,
namespace : "default" ,
expectedPod : backoffQPod ,
expectedOK : true ,
} ,
{
name : "pod is found in unschedulablePods" ,
podName : "pod3" ,
namespace : "default" ,
expectedPod : unschedPod ,
expectedOK : true ,
} ,
{
name : "pod is not found" ,
podName : "pod4" ,
namespace : "default" ,
expectedPod : nil ,
expectedOK : false ,
} ,
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
pInfo , ok := q . GetPod ( tt . podName , tt . namespace )
if ok != tt . expectedOK {
t . Errorf ( "Expected ok=%v, but got ok=%v" , tt . expectedOK , ok )
}
if tt . expectedPod == nil {
if pInfo == nil {
return
}
t . Fatalf ( "Expected pod is empty, but got pod=%v" , pInfo . Pod )
}
if ! cmp . Equal ( pInfo . Pod , tt . expectedPod ) {
t . Errorf ( "Expected pod=%v, but got pod=%v" , tt . expectedPod , pInfo . Pod )
}
} )
}
}
2025-12-30 10:18:20 -05:00
func TestUnschedulablePodsMetric ( t * testing . T ) {
2026-01-09 10:35:48 -05:00
type step func ( t * testing . T , logger klog . Logger , q * PriorityQueue )
addPod := func ( pInfo * framework . QueuedPodInfo ) step {
return func ( t * testing . T , logger klog . Logger , q * PriorityQueue ) {
add ( t , logger , q , pInfo )
}
}
deletePod := func ( pInfo * framework . QueuedPodInfo ) step {
return func ( t * testing . T , logger klog . Logger , q * PriorityQueue ) {
deletePod ( t , logger , q , pInfo )
}
}
popPod := func ( ) step {
return func ( t * testing . T , logger klog . Logger , q * PriorityQueue ) {
pop ( t , logger , q , nil )
}
}
moveAllToActiveOrBackoffQ := func ( ) step {
return func ( t * testing . T , logger klog . Logger , q * PriorityQueue ) {
moveAllToActiveOrBackoffQ ( t , logger , q , nil )
}
}
updatePluginAllowList := func ( pluginName string , list [ ] string ) step {
return func ( t * testing . T , logger klog . Logger , q * PriorityQueue ) {
q . preEnqueuePluginMap [ "" ] [ pluginName ] . ( * preEnqueuePlugin ) . allowlists = list
}
}
pluginName1 := "plugin1"
pluginName2 := "plugin2"
2025-12-30 10:18:20 -05:00
queueable := "queueable"
timestamp := time . Now ( )
2026-01-09 10:35:48 -05:00
pod := & framework . QueuedPodInfo {
PodInfo : mustNewPodInfo (
st . MakePod ( ) . Name ( "podA" ) . Namespace ( "namespaceA" ) . Label ( queueable , "" ) . UID ( "someUid" ) . Obj ( ) ) ,
Timestamp : timestamp ,
UnschedulablePlugins : sets . New [ string ] ( ) ,
}
2025-12-30 10:18:20 -05:00
2026-01-09 10:35:48 -05:00
resetMetrics := func ( ) {
metrics . UnschedulableReason ( pluginName1 , "" ) . Set ( 0 )
metrics . UnschedulableReason ( pluginName2 , "" ) . Set ( 0 )
}
makeGated := func ( pInfo * framework . QueuedPodInfo ) * framework . QueuedPodInfo {
return setQueuedPodInfoGated ( pInfo . DeepCopy ( ) , pluginName1 , [ ] fwk . ClusterEvent { framework . EventUnschedulableTimeout } )
2025-12-30 10:18:20 -05:00
}
tests := [ ] struct {
2026-01-09 10:35:48 -05:00
name string
steps [ ] step
expectedMetrics [ ] int
2025-12-30 10:18:20 -05:00
} {
{
name : "Unschedulable pods metric must be 0 after a pod is gated, ungated, re-queued, and eventually popped from the scheduling queue" ,
2026-01-09 10:35:48 -05:00
steps : [ ] step {
updatePluginAllowList ( pluginName1 , [ ] string { } ) ,
addPod ( pod ) ,
moveAllToActiveOrBackoffQ ( ) ,
updatePluginAllowList ( pluginName1 , [ ] string { queueable } ) ,
moveAllToActiveOrBackoffQ ( ) ,
popPod ( ) ,
2025-12-30 10:18:20 -05:00
} ,
2026-01-09 10:35:48 -05:00
expectedMetrics : [ ] int { 0 , 0 } ,
2025-12-30 10:18:20 -05:00
} ,
{
name : "Unschedulable pods metric must be 0 after pod is gated and then deleted" ,
2026-01-09 10:35:48 -05:00
steps : [ ] step {
updatePluginAllowList ( pluginName1 , [ ] string { } ) ,
addPod ( pod ) ,
moveAllToActiveOrBackoffQ ( ) ,
deletePod ( pod ) ,
2025-12-30 10:18:20 -05:00
} ,
2026-01-09 10:35:48 -05:00
expectedMetrics : [ ] int { 0 , 0 } ,
2025-12-30 10:18:20 -05:00
} ,
{
2026-01-09 10:35:48 -05:00
name : "Unschedulable pods metric must be 1 after pod is gated multiple time by the same plugin" ,
steps : [ ] step {
updatePluginAllowList ( pluginName1 , [ ] string { } ) ,
addPod ( pod ) ,
moveAllToActiveOrBackoffQ ( ) ,
2025-12-30 10:18:20 -05:00
} ,
2026-01-09 10:35:48 -05:00
expectedMetrics : [ ] int { 1 , 0 } ,
} ,
{
name : "Unschedulable pods metric must be 0 after non gated pods is added and then deleted" ,
steps : [ ] step {
addPod ( pod ) ,
deletePod ( pod ) ,
2025-12-30 10:18:20 -05:00
} ,
2026-01-09 10:35:48 -05:00
expectedMetrics : [ ] int { 0 , 0 } ,
2025-12-30 10:18:20 -05:00
} ,
{
2026-01-09 10:35:48 -05:00
name : "Unschedulable pods metric should not be duplicate if gated pods added and then gated with the same plugin again" ,
steps : [ ] step {
updatePluginAllowList ( pluginName1 , [ ] string { } ) ,
addPod ( makeGated ( pod ) ) ,
moveAllToActiveOrBackoffQ ( ) ,
2025-12-30 10:18:20 -05:00
} ,
2026-01-09 10:35:48 -05:00
expectedMetrics : [ ] int { 1 , 0 } ,
} ,
{
name : "Unschedulable pods metric should be 0 if pod was gated by two plugins sequentially and then ungated and popped" ,
steps : [ ] step {
updatePluginAllowList ( pluginName1 , [ ] string { } ) ,
addPod ( pod ) ,
updatePluginAllowList ( pluginName1 , [ ] string { queueable } ) ,
updatePluginAllowList ( pluginName2 , [ ] string { } ) ,
moveAllToActiveOrBackoffQ ( ) ,
updatePluginAllowList ( pluginName2 , [ ] string { queueable } ) ,
moveAllToActiveOrBackoffQ ( ) ,
popPod ( ) ,
2025-12-30 10:18:20 -05:00
} ,
2026-01-09 10:35:48 -05:00
expectedMetrics : [ ] int { 0 , 0 } ,
2025-12-30 10:18:20 -05:00
} ,
{
2026-01-09 10:35:48 -05:00
name : "Unschedulable pods metric should be 0 if pod was gated by two plugins sequentially and then deleted" ,
steps : [ ] step {
updatePluginAllowList ( pluginName1 , [ ] string { } ) ,
addPod ( pod ) ,
updatePluginAllowList ( pluginName1 , [ ] string { queueable } ) ,
updatePluginAllowList ( pluginName2 , [ ] string { } ) ,
moveAllToActiveOrBackoffQ ( ) ,
deletePod ( pod ) ,
2025-12-30 10:18:20 -05:00
} ,
2026-01-09 10:35:48 -05:00
expectedMetrics : [ ] int { 0 , 0 } ,
} ,
{
name : "Unschedulable pods metric should be 1 for both plugins if pod was gated by two plugins sequentially" ,
steps : [ ] step {
updatePluginAllowList ( pluginName1 , [ ] string { } ) ,
addPod ( pod ) ,
updatePluginAllowList ( pluginName1 , [ ] string { queueable } ) ,
updatePluginAllowList ( pluginName2 , [ ] string { } ) ,
moveAllToActiveOrBackoffQ ( ) ,
2025-12-30 10:18:20 -05:00
} ,
2026-01-09 10:35:48 -05:00
expectedMetrics : [ ] int { 1 , 1 } ,
2025-12-30 10:18:20 -05:00
} ,
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
logger , ctx := ktesting . NewTestContext ( t )
ctx , cancel := context . WithCancel ( ctx )
defer cancel ( )
resetMetrics ( )
m := makeEmptyQueueingHintMapPerProfile ( )
m [ "" ] [ framework . EventUnschedulableTimeout ] = [ ] * QueueingHintFunction {
{
2026-01-09 10:35:48 -05:00
PluginName : pluginName1 ,
QueueingHintFn : queueHintReturnQueue ,
} ,
{
PluginName : pluginName2 ,
2025-12-30 10:18:20 -05:00
QueueingHintFn : queueHintReturnQueue ,
} ,
}
2026-01-09 10:35:48 -05:00
plugin1 := preEnqueuePlugin { name : pluginName1 , allowlists : [ ] string { queueable } }
plugin2 := preEnqueuePlugin { name : pluginName2 , allowlists : [ ] string { queueable } }
preenq := map [ string ] map [ string ] fwk . PreEnqueuePlugin { "" : { pluginName1 : & plugin1 , pluginName2 : & plugin2 } }
2025-12-30 10:18:20 -05:00
recorder := metrics . NewMetricsAsyncRecorder ( 3 , 20 * time . Microsecond , ctx . Done ( ) )
2026-01-09 10:35:48 -05:00
q := NewTestQueue ( ctx , newDefaultQueueSort ( ) , WithClock ( testingclock . NewFakeClock ( timestamp ) ) , WithPreEnqueuePluginMap ( preenq ) , WithMetricsRecorder ( recorder ) , WithQueueingHintMapPerProfile ( m ) )
2025-12-30 10:18:20 -05:00
2026-01-09 10:35:48 -05:00
for _ , step := range tt . steps {
step ( t , logger , q )
2025-12-30 10:18:20 -05:00
}
2026-01-09 10:35:48 -05:00
for i , pluginName := range [ ] string { pluginName1 , pluginName2 } {
val , err := testutil . GetGaugeMetricValue ( metrics . UnschedulableReason ( pluginName , "" ) )
2025-12-30 10:18:20 -05:00
2026-01-09 10:35:48 -05:00
if err != nil {
t . Errorf ( "Error while collection metric value:\n%s" , err )
}
if int ( val ) != tt . expectedMetrics [ i ] {
t . Errorf ( "Unexpected metric for plugin %s result expected %d, actual %d" , pluginName , tt . expectedMetrics [ i ] , int ( val ) )
}
2025-12-30 10:18:20 -05:00
}
2026-01-09 10:35:48 -05:00
2025-12-30 10:18:20 -05:00
} )
}
}