sigs.k8s.io/kueue@v0.6.2/pkg/scheduler/preemption/preemption_test.go (about)

     1  /*
     2  Copyright 2023 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package preemption
    18  
    19  import (
    20  	"context"
    21  	"sort"
    22  	"sync"
    23  	"testing"
    24  	"time"
    25  
    26  	"github.com/google/go-cmp/cmp"
    27  	"github.com/google/go-cmp/cmp/cmpopts"
    28  	corev1 "k8s.io/api/core/v1"
    29  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    30  	"k8s.io/apimachinery/pkg/runtime"
    31  	"k8s.io/apimachinery/pkg/util/sets"
    32  	"k8s.io/client-go/tools/record"
    33  	"k8s.io/utils/ptr"
    34  
    35  	kueue "sigs.k8s.io/kueue/apis/kueue/v1beta1"
    36  	"sigs.k8s.io/kueue/pkg/cache"
    37  	"sigs.k8s.io/kueue/pkg/constants"
    38  	"sigs.k8s.io/kueue/pkg/features"
    39  	"sigs.k8s.io/kueue/pkg/scheduler/flavorassigner"
    40  	utiltesting "sigs.k8s.io/kueue/pkg/util/testing"
    41  	"sigs.k8s.io/kueue/pkg/workload"
    42  )
    43  
    44  var snapCmpOpts = []cmp.Option{
    45  	cmpopts.EquateEmpty(),
    46  	cmpopts.IgnoreUnexported(cache.ClusterQueue{}),
    47  	cmpopts.IgnoreFields(cache.Cohort{}, "AllocatableResourceGeneration"),
    48  	cmpopts.IgnoreFields(cache.ClusterQueue{}, "AllocatableResourceGeneration"),
    49  	cmp.Transformer("Cohort.Members", func(s sets.Set[*cache.ClusterQueue]) sets.Set[string] {
    50  		result := make(sets.Set[string], len(s))
    51  		for cq := range s {
    52  			result.Insert(cq.Name)
    53  		}
    54  		return result
    55  	}), // avoid recursion.
    56  }
    57  
    58  func TestPreemption(t *testing.T) {
    59  	flavors := []*kueue.ResourceFlavor{
    60  		utiltesting.MakeResourceFlavor("default").Obj(),
    61  		utiltesting.MakeResourceFlavor("alpha").Obj(),
    62  		utiltesting.MakeResourceFlavor("beta").Obj(),
    63  	}
    64  	clusterQueues := []*kueue.ClusterQueue{
    65  		utiltesting.MakeClusterQueue("standalone").
    66  			ResourceGroup(
    67  				*utiltesting.MakeFlavorQuotas("default").
    68  					Resource(corev1.ResourceCPU, "6").
    69  					Obj(),
    70  			).ResourceGroup(
    71  			*utiltesting.MakeFlavorQuotas("alpha").
    72  				Resource(corev1.ResourceMemory, "3Gi").
    73  				Obj(),
    74  			*utiltesting.MakeFlavorQuotas("beta").
    75  				Resource(corev1.ResourceMemory, "3Gi").
    76  				Obj(),
    77  		).
    78  			Preemption(kueue.ClusterQueuePreemption{
    79  				WithinClusterQueue: kueue.PreemptionPolicyLowerPriority,
    80  			}).
    81  			Obj(),
    82  		utiltesting.MakeClusterQueue("c1").
    83  			Cohort("cohort").
    84  			ResourceGroup(*utiltesting.MakeFlavorQuotas("default").
    85  				Resource(corev1.ResourceCPU, "6", "12").
    86  				Resource(corev1.ResourceMemory, "3Gi", "6Gi").
    87  				Obj(),
    88  			).
    89  			Preemption(kueue.ClusterQueuePreemption{
    90  				WithinClusterQueue:  kueue.PreemptionPolicyLowerPriority,
    91  				ReclaimWithinCohort: kueue.PreemptionPolicyLowerPriority,
    92  			}).
    93  			Obj(),
    94  		utiltesting.MakeClusterQueue("c2").
    95  			Cohort("cohort").
    96  			ResourceGroup(*utiltesting.MakeFlavorQuotas("default").
    97  				Resource(corev1.ResourceCPU, "6", "12").
    98  				Resource(corev1.ResourceMemory, "3Gi", "6Gi").
    99  				Obj(),
   100  			).
   101  			Preemption(kueue.ClusterQueuePreemption{
   102  				WithinClusterQueue:  kueue.PreemptionPolicyNever,
   103  				ReclaimWithinCohort: kueue.PreemptionPolicyAny,
   104  			}).
   105  			Obj(),
   106  		utiltesting.MakeClusterQueue("d1").
   107  			Cohort("cohort-no-limits").
   108  			ResourceGroup(*utiltesting.MakeFlavorQuotas("default").
   109  				Resource(corev1.ResourceCPU, "6").
   110  				Resource(corev1.ResourceMemory, "3Gi").
   111  				Obj(),
   112  			).
   113  			Preemption(kueue.ClusterQueuePreemption{
   114  				WithinClusterQueue:  kueue.PreemptionPolicyLowerPriority,
   115  				ReclaimWithinCohort: kueue.PreemptionPolicyLowerPriority,
   116  			}).
   117  			Obj(),
   118  		utiltesting.MakeClusterQueue("d2").
   119  			Cohort("cohort-no-limits").
   120  			ResourceGroup(*utiltesting.MakeFlavorQuotas("default").
   121  				Resource(corev1.ResourceCPU, "6").
   122  				Resource(corev1.ResourceMemory, "3Gi").
   123  				Obj(),
   124  			).
   125  			Preemption(kueue.ClusterQueuePreemption{
   126  				WithinClusterQueue:  kueue.PreemptionPolicyNever,
   127  				ReclaimWithinCohort: kueue.PreemptionPolicyAny,
   128  			}).
   129  			Obj(),
   130  		utiltesting.MakeClusterQueue("l1").
   131  			Cohort("legion").
   132  			ResourceGroup(*utiltesting.MakeFlavorQuotas("default").
   133  				Resource(corev1.ResourceCPU, "6", "12").
   134  				Resource(corev1.ResourceMemory, "3Gi", "6Gi").
   135  				Obj(),
   136  			).
   137  			Preemption(kueue.ClusterQueuePreemption{
   138  				WithinClusterQueue:  kueue.PreemptionPolicyLowerPriority,
   139  				ReclaimWithinCohort: kueue.PreemptionPolicyLowerPriority,
   140  			}).
   141  			Obj(),
   142  		utiltesting.MakeClusterQueue("preventStarvation").
   143  			ResourceGroup(*utiltesting.MakeFlavorQuotas("default").
   144  				Resource(corev1.ResourceCPU, "6").
   145  				Obj(),
   146  			).
   147  			Preemption(kueue.ClusterQueuePreemption{
   148  				WithinClusterQueue: kueue.PreemptionPolicyLowerOrNewerEqualPriority,
   149  			}).
   150  			Obj(),
   151  		utiltesting.MakeClusterQueue("a_standard").
   152  			Cohort("with_shared_cq").
   153  			ResourceGroup(
   154  				*utiltesting.MakeFlavorQuotas("default").
   155  					Resource(corev1.ResourceCPU, "1", "12").
   156  					Obj(),
   157  			).
   158  			Preemption(kueue.ClusterQueuePreemption{
   159  				WithinClusterQueue:  kueue.PreemptionPolicyNever,
   160  				ReclaimWithinCohort: kueue.PreemptionPolicyLowerPriority,
   161  				BorrowWithinCohort: &kueue.BorrowWithinCohort{
   162  					Policy:               kueue.BorrowWithinCohortPolicyLowerPriority,
   163  					MaxPriorityThreshold: ptr.To[int32](0),
   164  				},
   165  			}).
   166  			Obj(),
   167  		utiltesting.MakeClusterQueue("b_standard").
   168  			Cohort("with_shared_cq").
   169  			ResourceGroup(
   170  				*utiltesting.MakeFlavorQuotas("default").
   171  					Resource(corev1.ResourceCPU, "1", "12").
   172  					Obj(),
   173  			).
   174  			Preemption(kueue.ClusterQueuePreemption{
   175  				WithinClusterQueue:  kueue.PreemptionPolicyNever,
   176  				ReclaimWithinCohort: kueue.PreemptionPolicyLowerPriority,
   177  				BorrowWithinCohort: &kueue.BorrowWithinCohort{
   178  					Policy:               kueue.BorrowWithinCohortPolicyLowerPriority,
   179  					MaxPriorityThreshold: ptr.To[int32](0),
   180  				},
   181  			}).
   182  			Obj(),
   183  		utiltesting.MakeClusterQueue("a_best_effort").
   184  			Cohort("with_shared_cq").
   185  			ResourceGroup(*utiltesting.MakeFlavorQuotas("default").
   186  				Resource(corev1.ResourceCPU, "1", "12").
   187  				Obj(),
   188  			).
   189  			Preemption(kueue.ClusterQueuePreemption{
   190  				WithinClusterQueue:  kueue.PreemptionPolicyNever,
   191  				ReclaimWithinCohort: kueue.PreemptionPolicyLowerPriority,
   192  				BorrowWithinCohort: &kueue.BorrowWithinCohort{
   193  					Policy:               kueue.BorrowWithinCohortPolicyLowerPriority,
   194  					MaxPriorityThreshold: ptr.To[int32](0),
   195  				},
   196  			}).
   197  			Obj(),
   198  		utiltesting.MakeClusterQueue("shared").
   199  			Cohort("with_shared_cq").
   200  			ResourceGroup(*utiltesting.MakeFlavorQuotas("default").
   201  				Resource(corev1.ResourceCPU, "10").
   202  				Obj(),
   203  			).
   204  			Obj(),
   205  		utiltesting.MakeClusterQueue("lend1").
   206  			Cohort("cohort-lend").
   207  			ResourceGroup(*utiltesting.MakeFlavorQuotas("default").
   208  				Resource(corev1.ResourceCPU, "6", "", "4").
   209  				Obj(),
   210  			).
   211  			Preemption(kueue.ClusterQueuePreemption{
   212  				WithinClusterQueue:  kueue.PreemptionPolicyLowerPriority,
   213  				ReclaimWithinCohort: kueue.PreemptionPolicyLowerPriority,
   214  			}).
   215  			Obj(),
   216  		utiltesting.MakeClusterQueue("lend2").
   217  			Cohort("cohort-lend").
   218  			ResourceGroup(*utiltesting.MakeFlavorQuotas("default").
   219  				Resource(corev1.ResourceCPU, "6", "", "2").
   220  				Obj(),
   221  			).
   222  			Preemption(kueue.ClusterQueuePreemption{
   223  				WithinClusterQueue:  kueue.PreemptionPolicyLowerPriority,
   224  				ReclaimWithinCohort: kueue.PreemptionPolicyLowerPriority,
   225  			}).
   226  			Obj(),
   227  	}
   228  	cases := map[string]struct {
   229  		admitted           []kueue.Workload
   230  		incoming           *kueue.Workload
   231  		targetCQ           string
   232  		assignment         flavorassigner.Assignment
   233  		wantPreempted      sets.Set[string]
   234  		enableLendingLimit bool
   235  	}{
   236  		"preempt lowest priority": {
   237  			admitted: []kueue.Workload{
   238  				*utiltesting.MakeWorkload("low", "").
   239  					Priority(-1).
   240  					Request(corev1.ResourceCPU, "2").
   241  					ReserveQuota(utiltesting.MakeAdmission("standalone").Assignment(corev1.ResourceCPU, "default", "2000m").Obj()).
   242  					Obj(),
   243  				*utiltesting.MakeWorkload("mid", "").
   244  					Request(corev1.ResourceCPU, "2").
   245  					ReserveQuota(utiltesting.MakeAdmission("standalone").Assignment(corev1.ResourceCPU, "default", "2000m").Obj()).
   246  					Obj(),
   247  				*utiltesting.MakeWorkload("high", "").
   248  					Priority(1).
   249  					Request(corev1.ResourceCPU, "2").
   250  					ReserveQuota(utiltesting.MakeAdmission("standalone").Assignment(corev1.ResourceCPU, "default", "2000m").Obj()).
   251  					Obj(),
   252  			},
   253  			incoming: utiltesting.MakeWorkload("in", "").
   254  				Priority(1).
   255  				Request(corev1.ResourceCPU, "2").
   256  				Obj(),
   257  			targetCQ: "standalone",
   258  			assignment: singlePodSetAssignment(flavorassigner.ResourceAssignment{
   259  				corev1.ResourceCPU: &flavorassigner.FlavorAssignment{
   260  					Name: "default",
   261  					Mode: flavorassigner.Preempt,
   262  				},
   263  			}),
   264  			wantPreempted: sets.New("/low"),
   265  		},
   266  		"preempt multiple": {
   267  			admitted: []kueue.Workload{
   268  				*utiltesting.MakeWorkload("low", "").
   269  					Priority(-1).
   270  					Request(corev1.ResourceCPU, "2").
   271  					ReserveQuota(utiltesting.MakeAdmission("standalone").Assignment(corev1.ResourceCPU, "default", "2000m").Obj()).
   272  					Obj(),
   273  				*utiltesting.MakeWorkload("mid", "").
   274  					Request(corev1.ResourceCPU, "2").
   275  					ReserveQuota(utiltesting.MakeAdmission("standalone").Assignment(corev1.ResourceCPU, "default", "2000m").Obj()).
   276  					Obj(),
   277  				*utiltesting.MakeWorkload("high", "").
   278  					Priority(1).
   279  					Request(corev1.ResourceCPU, "2").
   280  					ReserveQuota(utiltesting.MakeAdmission("standalone").Assignment(corev1.ResourceCPU, "default", "2000m").Obj()).
   281  					Obj(),
   282  			},
   283  			incoming: utiltesting.MakeWorkload("in", "").
   284  				Priority(1).
   285  				Request(corev1.ResourceCPU, "3").
   286  				Obj(),
   287  			targetCQ: "standalone",
   288  			assignment: singlePodSetAssignment(flavorassigner.ResourceAssignment{
   289  				corev1.ResourceCPU: &flavorassigner.FlavorAssignment{
   290  					Name: "default",
   291  					Mode: flavorassigner.Preempt,
   292  				},
   293  			}),
   294  			wantPreempted: sets.New("/low", "/mid"),
   295  		},
   296  
   297  		"no preemption for low priority": {
   298  			admitted: []kueue.Workload{
   299  				*utiltesting.MakeWorkload("low", "").
   300  					Priority(-1).
   301  					Request(corev1.ResourceCPU, "3").
   302  					ReserveQuota(utiltesting.MakeAdmission("standalone").Assignment(corev1.ResourceCPU, "default", "3000m").Obj()).
   303  					Obj(),
   304  				*utiltesting.MakeWorkload("mid", "").
   305  					Request(corev1.ResourceCPU, "3").
   306  					ReserveQuota(utiltesting.MakeAdmission("standalone").Assignment(corev1.ResourceCPU, "default", "3000m").Obj()).
   307  					Obj(),
   308  			},
   309  			incoming: utiltesting.MakeWorkload("in", "").
   310  				Priority(-1).
   311  				Request(corev1.ResourceCPU, "1").
   312  				Obj(),
   313  			targetCQ: "standalone",
   314  			assignment: singlePodSetAssignment(flavorassigner.ResourceAssignment{
   315  				corev1.ResourceCPU: &flavorassigner.FlavorAssignment{
   316  					Name: "default",
   317  					Mode: flavorassigner.Preempt,
   318  				},
   319  			}),
   320  		},
   321  		"not enough low priority workloads": {
   322  			admitted: []kueue.Workload{
   323  				*utiltesting.MakeWorkload("low", "").
   324  					Priority(-1).
   325  					Request(corev1.ResourceCPU, "3").
   326  					ReserveQuota(utiltesting.MakeAdmission("standalone").Assignment(corev1.ResourceCPU, "default", "3000m").Obj()).
   327  					Obj(),
   328  				*utiltesting.MakeWorkload("mid", "").
   329  					Request(corev1.ResourceCPU, "3").
   330  					ReserveQuota(utiltesting.MakeAdmission("standalone").Assignment(corev1.ResourceCPU, "default", "3000m").Obj()).
   331  					Obj(),
   332  			},
   333  			incoming: utiltesting.MakeWorkload("in", "").
   334  				Request(corev1.ResourceCPU, "4").
   335  				Obj(),
   336  			targetCQ: "standalone",
   337  			assignment: singlePodSetAssignment(flavorassigner.ResourceAssignment{
   338  				corev1.ResourceCPU: &flavorassigner.FlavorAssignment{
   339  					Name: "default",
   340  					Mode: flavorassigner.Preempt,
   341  				},
   342  			}),
   343  		},
   344  		"some free quota, preempt low priority": {
   345  			admitted: []kueue.Workload{
   346  				*utiltesting.MakeWorkload("low", "").
   347  					Priority(-1).
   348  					Request(corev1.ResourceCPU, "1").
   349  					ReserveQuota(utiltesting.MakeAdmission("standalone").Assignment(corev1.ResourceCPU, "default", "1000m").Obj()).
   350  					Obj(),
   351  				*utiltesting.MakeWorkload("mid", "").
   352  					Request(corev1.ResourceCPU, "1").
   353  					ReserveQuota(utiltesting.MakeAdmission("standalone").Assignment(corev1.ResourceCPU, "default", "1000m").Obj()).
   354  					Obj(),
   355  				*utiltesting.MakeWorkload("high", "").
   356  					Priority(1).
   357  					Request(corev1.ResourceCPU, "3").
   358  					ReserveQuota(utiltesting.MakeAdmission("standalone").Assignment(corev1.ResourceCPU, "default", "3000m").Obj()).
   359  					Obj(),
   360  			},
   361  			incoming: utiltesting.MakeWorkload("in", "").
   362  				Priority(1).
   363  				Request(corev1.ResourceCPU, "2").
   364  				Obj(),
   365  			targetCQ: "standalone",
   366  			assignment: singlePodSetAssignment(flavorassigner.ResourceAssignment{
   367  				corev1.ResourceCPU: &flavorassigner.FlavorAssignment{
   368  					Name: "default",
   369  					Mode: flavorassigner.Preempt,
   370  				},
   371  			}),
   372  			wantPreempted: sets.New("/low"),
   373  		},
   374  		"minimal set excludes low priority": {
   375  			admitted: []kueue.Workload{
   376  				*utiltesting.MakeWorkload("low", "").
   377  					Priority(-1).
   378  					Request(corev1.ResourceCPU, "1").
   379  					ReserveQuota(utiltesting.MakeAdmission("standalone").Assignment(corev1.ResourceCPU, "default", "1000m").Obj()).
   380  					Obj(),
   381  				*utiltesting.MakeWorkload("mid", "").
   382  					Request(corev1.ResourceCPU, "2").
   383  					ReserveQuota(utiltesting.MakeAdmission("standalone").Assignment(corev1.ResourceCPU, "default", "2000m").Obj()).
   384  					Obj(),
   385  				*utiltesting.MakeWorkload("high", "").
   386  					Priority(1).
   387  					Request(corev1.ResourceCPU, "3").
   388  					ReserveQuota(utiltesting.MakeAdmission("standalone").Assignment(corev1.ResourceCPU, "default", "3000m").Obj()).
   389  					Obj(),
   390  			},
   391  			incoming: utiltesting.MakeWorkload("in", "").
   392  				Priority(1).
   393  				Request(corev1.ResourceCPU, "2").
   394  				Obj(),
   395  			targetCQ: "standalone",
   396  			assignment: singlePodSetAssignment(flavorassigner.ResourceAssignment{
   397  				corev1.ResourceCPU: &flavorassigner.FlavorAssignment{
   398  					Name: "default",
   399  					Mode: flavorassigner.Preempt,
   400  				},
   401  			}),
   402  			wantPreempted: sets.New("/mid"),
   403  		},
   404  		"only preempt workloads using the chosen flavor": {
   405  			admitted: []kueue.Workload{
   406  				*utiltesting.MakeWorkload("low", "").
   407  					Priority(-1).
   408  					Request(corev1.ResourceMemory, "2Gi").
   409  					ReserveQuota(utiltesting.MakeAdmission("standalone").Assignment(corev1.ResourceMemory, "alpha", "2Gi").Obj()).
   410  					Obj(),
   411  				*utiltesting.MakeWorkload("mid", "").
   412  					Request(corev1.ResourceMemory, "1Gi").
   413  					ReserveQuota(utiltesting.MakeAdmission("standalone").Assignment(corev1.ResourceMemory, "beta", "1Gi").Obj()).
   414  					Obj(),
   415  				*utiltesting.MakeWorkload("high", "").
   416  					Priority(1).
   417  					Request(corev1.ResourceMemory, "1Gi").
   418  					ReserveQuota(utiltesting.MakeAdmission("standalone").Assignment(corev1.ResourceMemory, "beta", "1Gi").Obj()).
   419  					Obj(),
   420  			},
   421  			incoming: utiltesting.MakeWorkload("in", "").
   422  				Priority(1).
   423  				Request(corev1.ResourceCPU, "1").
   424  				Request(corev1.ResourceMemory, "2Gi").
   425  				Obj(),
   426  			targetCQ: "standalone",
   427  			assignment: singlePodSetAssignment(flavorassigner.ResourceAssignment{
   428  				corev1.ResourceCPU: &flavorassigner.FlavorAssignment{
   429  					Name: "default",
   430  					Mode: flavorassigner.Fit,
   431  				},
   432  				corev1.ResourceMemory: &flavorassigner.FlavorAssignment{
   433  					Name: "beta",
   434  					Mode: flavorassigner.Preempt,
   435  				},
   436  			}),
   437  			wantPreempted: sets.New("/mid"),
   438  		},
   439  		"reclaim quota from borrower": {
   440  			admitted: []kueue.Workload{
   441  				*utiltesting.MakeWorkload("c1-low", "").
   442  					Priority(-1).
   443  					Request(corev1.ResourceCPU, "3").
   444  					ReserveQuota(utiltesting.MakeAdmission("c1").Assignment(corev1.ResourceCPU, "default", "3000m").Obj()).
   445  					Obj(),
   446  				*utiltesting.MakeWorkload("c2-mid", "").
   447  					Request(corev1.ResourceCPU, "3").
   448  					ReserveQuota(utiltesting.MakeAdmission("c2").Assignment(corev1.ResourceCPU, "default", "3000m").Obj()).
   449  					Obj(),
   450  				*utiltesting.MakeWorkload("c2-high", "").
   451  					Priority(1).
   452  					Request(corev1.ResourceCPU, "6").
   453  					ReserveQuota(utiltesting.MakeAdmission("c2").Assignment(corev1.ResourceCPU, "default", "6000m").Obj()).
   454  					Obj(),
   455  			},
   456  			incoming: utiltesting.MakeWorkload("in", "").
   457  				Priority(1).
   458  				Request(corev1.ResourceCPU, "3").
   459  				Obj(),
   460  			targetCQ: "c1",
   461  			assignment: singlePodSetAssignment(flavorassigner.ResourceAssignment{
   462  				corev1.ResourceCPU: &flavorassigner.FlavorAssignment{
   463  					Name: "default",
   464  					Mode: flavorassigner.Preempt,
   465  				},
   466  			}),
   467  			wantPreempted: sets.New("/c2-mid"),
   468  		},
   469  		"no workloads borrowing": {
   470  			admitted: []kueue.Workload{
   471  				*utiltesting.MakeWorkload("c1-high", "").
   472  					Priority(1).
   473  					Request(corev1.ResourceCPU, "4").
   474  					ReserveQuota(utiltesting.MakeAdmission("c1").Assignment(corev1.ResourceCPU, "default", "4000m").Obj()).
   475  					Obj(),
   476  				*utiltesting.MakeWorkload("c2-low-1", "").
   477  					Priority(-1).
   478  					Request(corev1.ResourceCPU, "4").
   479  					ReserveQuota(utiltesting.MakeAdmission("c2").Assignment(corev1.ResourceCPU, "default", "4000m").Obj()).
   480  					Obj(),
   481  			},
   482  			incoming: utiltesting.MakeWorkload("in", "").
   483  				Priority(1).
   484  				Request(corev1.ResourceCPU, "4").
   485  				Obj(),
   486  			targetCQ: "c1",
   487  			assignment: singlePodSetAssignment(flavorassigner.ResourceAssignment{
   488  				corev1.ResourceCPU: &flavorassigner.FlavorAssignment{
   489  					Name: "default",
   490  					Mode: flavorassigner.Preempt,
   491  				},
   492  			}),
   493  		},
   494  		"not enough workloads borrowing": {
   495  			admitted: []kueue.Workload{
   496  				*utiltesting.MakeWorkload("c1-high", "").
   497  					Priority(1).
   498  					Request(corev1.ResourceCPU, "4").
   499  					ReserveQuota(utiltesting.MakeAdmission("c1").Assignment(corev1.ResourceCPU, "default", "4000m").Obj()).
   500  					Obj(),
   501  				*utiltesting.MakeWorkload("c2-low-1", "").
   502  					Priority(-1).
   503  					Request(corev1.ResourceCPU, "4").
   504  					ReserveQuota(utiltesting.MakeAdmission("c2").Assignment(corev1.ResourceCPU, "default", "4000m").Obj()).
   505  					Obj(),
   506  				*utiltesting.MakeWorkload("c2-low-2", "").
   507  					Priority(-1).
   508  					Request(corev1.ResourceCPU, "4").
   509  					ReserveQuota(utiltesting.MakeAdmission("c2").Assignment(corev1.ResourceCPU, "default", "4000m").Obj()).
   510  					Obj(),
   511  			},
   512  			incoming: utiltesting.MakeWorkload("in", "").
   513  				Priority(1).
   514  				Request(corev1.ResourceCPU, "4").
   515  				Obj(),
   516  			targetCQ: "c1",
   517  			assignment: singlePodSetAssignment(flavorassigner.ResourceAssignment{
   518  				corev1.ResourceCPU: &flavorassigner.FlavorAssignment{
   519  					Name: "default",
   520  					Mode: flavorassigner.Preempt,
   521  				},
   522  			}),
   523  		},
   524  		"preempting locally and borrowing other resources in cohort, without cohort candidates": {
   525  			admitted: []kueue.Workload{
   526  				*utiltesting.MakeWorkload("c1-low", "").
   527  					Priority(-1).
   528  					Request(corev1.ResourceCPU, "4").
   529  					ReserveQuota(utiltesting.MakeAdmission("c1").Assignment(corev1.ResourceCPU, "default", "4000m").Obj()).
   530  					Obj(),
   531  				*utiltesting.MakeWorkload("c2-low-1", "").
   532  					Priority(-1).
   533  					Request(corev1.ResourceCPU, "4").
   534  					ReserveQuota(utiltesting.MakeAdmission("c2").Assignment(corev1.ResourceCPU, "default", "4000m").Obj()).
   535  					Obj(),
   536  				*utiltesting.MakeWorkload("c2-high-2", "").
   537  					Priority(1).
   538  					Request(corev1.ResourceCPU, "4").
   539  					ReserveQuota(utiltesting.MakeAdmission("c2").Assignment(corev1.ResourceCPU, "default", "4000m").Obj()).
   540  					Obj(),
   541  			},
   542  			incoming: utiltesting.MakeWorkload("in", "").
   543  				Priority(1).
   544  				Request(corev1.ResourceCPU, "4").
   545  				Request(corev1.ResourceMemory, "5Gi").
   546  				Obj(),
   547  			targetCQ: "c1",
   548  			assignment: singlePodSetAssignment(flavorassigner.ResourceAssignment{
   549  				corev1.ResourceCPU: &flavorassigner.FlavorAssignment{
   550  					Name: "default",
   551  					Mode: flavorassigner.Preempt,
   552  				},
   553  				corev1.ResourceMemory: &flavorassigner.FlavorAssignment{
   554  					Name: "alpha",
   555  					Mode: flavorassigner.Preempt,
   556  				},
   557  			}),
   558  			wantPreempted: sets.New("/c1-low"),
   559  		},
   560  		"preempting locally and borrowing same resource in cohort": {
   561  			admitted: []kueue.Workload{
   562  				*utiltesting.MakeWorkload("c1-med", "").
   563  					Priority(0).
   564  					Request(corev1.ResourceCPU, "4").
   565  					ReserveQuota(utiltesting.MakeAdmission("c1").Assignment(corev1.ResourceCPU, "default", "4000m").Obj()).
   566  					Obj(),
   567  				*utiltesting.MakeWorkload("c1-low", "").
   568  					Priority(-1).
   569  					Request(corev1.ResourceCPU, "4").
   570  					ReserveQuota(utiltesting.MakeAdmission("c1").Assignment(corev1.ResourceCPU, "default", "4000m").Obj()).
   571  					Obj(),
   572  				*utiltesting.MakeWorkload("c2-low-1", "").
   573  					Priority(-1).
   574  					Request(corev1.ResourceCPU, "4").
   575  					ReserveQuota(utiltesting.MakeAdmission("c2").Assignment(corev1.ResourceCPU, "default", "4000m").Obj()).
   576  					Obj(),
   577  			},
   578  			incoming: utiltesting.MakeWorkload("in", "").
   579  				Priority(1).
   580  				Request(corev1.ResourceCPU, "4").
   581  				Obj(),
   582  			targetCQ: "c1",
   583  			assignment: singlePodSetAssignment(flavorassigner.ResourceAssignment{
   584  				corev1.ResourceCPU: &flavorassigner.FlavorAssignment{
   585  					Name: "default",
   586  					Mode: flavorassigner.Preempt,
   587  				},
   588  			}),
   589  			wantPreempted: sets.New("/c1-low"),
   590  		},
   591  		"preempting locally and borrowing same resource in cohort; no borrowing limit in the cohort": {
   592  			admitted: []kueue.Workload{
   593  				*utiltesting.MakeWorkload("d1-med", "").
   594  					Priority(0).
   595  					Request(corev1.ResourceCPU, "4").
   596  					ReserveQuota(utiltesting.MakeAdmission("d1").Assignment(corev1.ResourceCPU, "default", "4").Obj()).
   597  					Obj(),
   598  				*utiltesting.MakeWorkload("d1-low", "").
   599  					Priority(-1).
   600  					Request(corev1.ResourceCPU, "4").
   601  					ReserveQuota(utiltesting.MakeAdmission("d1").Assignment(corev1.ResourceCPU, "default", "4").Obj()).
   602  					Obj(),
   603  				*utiltesting.MakeWorkload("d2-low-1", "").
   604  					Priority(-1).
   605  					Request(corev1.ResourceCPU, "4").
   606  					ReserveQuota(utiltesting.MakeAdmission("d2").Assignment(corev1.ResourceCPU, "default", "4").Obj()).
   607  					Obj(),
   608  			},
   609  			incoming: utiltesting.MakeWorkload("in", "").
   610  				Priority(1).
   611  				Request(corev1.ResourceCPU, "4").
   612  				Obj(),
   613  			targetCQ: "d1",
   614  			assignment: singlePodSetAssignment(flavorassigner.ResourceAssignment{
   615  				corev1.ResourceCPU: &flavorassigner.FlavorAssignment{
   616  					Name: "default",
   617  					Mode: flavorassigner.Preempt,
   618  				},
   619  			}),
   620  			wantPreempted: sets.New("/d1-low"),
   621  		},
   622  		"preempting locally and borrowing other resources in cohort, with cohort candidates": {
   623  			admitted: []kueue.Workload{
   624  				*utiltesting.MakeWorkload("c1-med", "").
   625  					Priority(0).
   626  					Request(corev1.ResourceCPU, "4").
   627  					ReserveQuota(utiltesting.MakeAdmission("c1").Assignment(corev1.ResourceCPU, "default", "4000m").Obj()).
   628  					Obj(),
   629  				*utiltesting.MakeWorkload("c2-low-1", "").
   630  					Priority(-1).
   631  					Request(corev1.ResourceCPU, "5").
   632  					ReserveQuota(utiltesting.MakeAdmission("c2").Assignment(corev1.ResourceCPU, "default", "5000m").Obj()).
   633  					Obj(),
   634  				*utiltesting.MakeWorkload("c2-low-2", "").
   635  					Priority(-1).
   636  					Request(corev1.ResourceCPU, "1").
   637  					ReserveQuota(utiltesting.MakeAdmission("c2").Assignment(corev1.ResourceCPU, "default", "1000m").Obj()).
   638  					Obj(),
   639  				*utiltesting.MakeWorkload("c2-low-3", "").
   640  					Priority(-1).
   641  					Request(corev1.ResourceCPU, "1").
   642  					ReserveQuota(utiltesting.MakeAdmission("c2").Assignment(corev1.ResourceCPU, "default", "1000m").Obj()).
   643  					Obj(),
   644  			},
   645  			incoming: utiltesting.MakeWorkload("in", "").
   646  				Priority(1).
   647  				Request(corev1.ResourceCPU, "2").
   648  				Request(corev1.ResourceMemory, "5Gi").
   649  				Obj(),
   650  			targetCQ: "c1",
   651  			assignment: singlePodSetAssignment(flavorassigner.ResourceAssignment{
   652  				corev1.ResourceCPU: &flavorassigner.FlavorAssignment{
   653  					Name: "default",
   654  					Mode: flavorassigner.Preempt,
   655  				},
   656  				corev1.ResourceMemory: &flavorassigner.FlavorAssignment{
   657  					Name: "default",
   658  					Mode: flavorassigner.Preempt,
   659  				},
   660  			}),
   661  			wantPreempted: sets.New("/c1-med"),
   662  		},
   663  		"preempting locally and not borrowing same resource in 1-queue cohort": {
   664  			admitted: []kueue.Workload{
   665  				*utiltesting.MakeWorkload("l1-med", "").
   666  					Priority(0).
   667  					Request(corev1.ResourceCPU, "4").
   668  					ReserveQuota(utiltesting.MakeAdmission("l1").Assignment(corev1.ResourceCPU, "default", "4000m").Obj()).
   669  					Obj(),
   670  				*utiltesting.MakeWorkload("l1-low", "").
   671  					Priority(-1).
   672  					Request(corev1.ResourceCPU, "2").
   673  					ReserveQuota(utiltesting.MakeAdmission("l1").Assignment(corev1.ResourceCPU, "default", "2000m").Obj()).
   674  					Obj(),
   675  			},
   676  			incoming: utiltesting.MakeWorkload("in", "").
   677  				Priority(1).
   678  				Request(corev1.ResourceCPU, "4").
   679  				Obj(),
   680  			targetCQ: "l1",
   681  			assignment: singlePodSetAssignment(flavorassigner.ResourceAssignment{
   682  				corev1.ResourceCPU: &flavorassigner.FlavorAssignment{
   683  					Name: "default",
   684  					Mode: flavorassigner.Preempt,
   685  				},
   686  			}),
   687  			wantPreempted: sets.New("/l1-med"),
   688  		},
   689  		"do not reclaim borrowed quota from same priority for withinCohort=ReclaimFromLowerPriority": {
   690  			admitted: []kueue.Workload{
   691  				*utiltesting.MakeWorkload("c1", "").
   692  					Request(corev1.ResourceCPU, "2").
   693  					ReserveQuota(utiltesting.MakeAdmission("c1").Assignment(corev1.ResourceCPU, "default", "2000m").Obj()).
   694  					Obj(),
   695  				*utiltesting.MakeWorkload("c2-1", "").
   696  					Request(corev1.ResourceCPU, "4").
   697  					ReserveQuota(utiltesting.MakeAdmission("c2").Assignment(corev1.ResourceCPU, "default", "4000m").Obj()).
   698  					Obj(),
   699  				*utiltesting.MakeWorkload("c2-2", "").
   700  					Request(corev1.ResourceCPU, "4").
   701  					ReserveQuota(utiltesting.MakeAdmission("c2").Assignment(corev1.ResourceCPU, "default", "4000m").Obj()).
   702  					Obj(),
   703  			},
   704  			incoming: utiltesting.MakeWorkload("in", "").
   705  				Request(corev1.ResourceCPU, "4").
   706  				Obj(),
   707  			targetCQ: "c1",
   708  			assignment: singlePodSetAssignment(flavorassigner.ResourceAssignment{
   709  				corev1.ResourceCPU: &flavorassigner.FlavorAssignment{
   710  					Name: "default",
   711  					Mode: flavorassigner.Preempt,
   712  				},
   713  			}),
   714  		},
   715  		"reclaim borrowed quota from same priority for withinCohort=ReclaimFromAny": {
   716  			admitted: []kueue.Workload{
   717  				*utiltesting.MakeWorkload("c1-1", "").
   718  					Request(corev1.ResourceCPU, "4").
   719  					ReserveQuota(utiltesting.MakeAdmission("c1").Assignment(corev1.ResourceCPU, "default", "4000m").Obj()).
   720  					Obj(),
   721  				*utiltesting.MakeWorkload("c1-2", "").
   722  					Priority(1).
   723  					Request(corev1.ResourceCPU, "4").
   724  					ReserveQuota(utiltesting.MakeAdmission("c1").Assignment(corev1.ResourceCPU, "default", "4000m").Obj()).
   725  					Obj(),
   726  				*utiltesting.MakeWorkload("c2", "").
   727  					Request(corev1.ResourceCPU, "2").
   728  					ReserveQuota(utiltesting.MakeAdmission("c2").Assignment(corev1.ResourceCPU, "default", "2000m").Obj()).
   729  					Obj(),
   730  			},
   731  			incoming: utiltesting.MakeWorkload("in", "").
   732  				Request(corev1.ResourceCPU, "4").
   733  				Obj(),
   734  			targetCQ: "c2",
   735  			assignment: singlePodSetAssignment(flavorassigner.ResourceAssignment{
   736  				corev1.ResourceCPU: &flavorassigner.FlavorAssignment{
   737  					Name: "default",
   738  					Mode: flavorassigner.Preempt,
   739  				},
   740  			}),
   741  			wantPreempted: sets.New("/c1-1"),
   742  		},
   743  		"preempt from all ClusterQueues in cohort": {
   744  			admitted: []kueue.Workload{
   745  				*utiltesting.MakeWorkload("c1-low", "").
   746  					Priority(-1).
   747  					Request(corev1.ResourceCPU, "3").
   748  					ReserveQuota(utiltesting.MakeAdmission("c1").Assignment(corev1.ResourceCPU, "default", "3000m").Obj()).
   749  					Obj(),
   750  				*utiltesting.MakeWorkload("c1-mid", "").
   751  					Request(corev1.ResourceCPU, "2").
   752  					ReserveQuota(utiltesting.MakeAdmission("c1").Assignment(corev1.ResourceCPU, "default", "2000m").Obj()).
   753  					Obj(),
   754  				*utiltesting.MakeWorkload("c2-low", "").
   755  					Priority(-1).
   756  					Request(corev1.ResourceCPU, "3").
   757  					ReserveQuota(utiltesting.MakeAdmission("c2").Assignment(corev1.ResourceCPU, "default", "3000m").Obj()).
   758  					Obj(),
   759  				*utiltesting.MakeWorkload("c2-mid", "").
   760  					Request(corev1.ResourceCPU, "4").
   761  					ReserveQuota(utiltesting.MakeAdmission("c2").Assignment(corev1.ResourceCPU, "default", "4000m").Obj()).
   762  					Obj(),
   763  			},
   764  			incoming: utiltesting.MakeWorkload("in", "").
   765  				Request(corev1.ResourceCPU, "4").
   766  				Obj(),
   767  			targetCQ: "c1",
   768  			assignment: singlePodSetAssignment(flavorassigner.ResourceAssignment{
   769  				corev1.ResourceCPU: &flavorassigner.FlavorAssignment{
   770  					Name: "default",
   771  					Mode: flavorassigner.Preempt,
   772  				},
   773  			}),
   774  			wantPreempted: sets.New("/c1-low", "/c2-low"),
   775  		},
   776  		"can't preempt workloads in ClusterQueue for withinClusterQueue=Never": {
   777  			admitted: []kueue.Workload{
   778  				*utiltesting.MakeWorkload("c2-low", "").
   779  					Priority(-1).
   780  					Request(corev1.ResourceCPU, "3").
   781  					ReserveQuota(utiltesting.MakeAdmission("c2").Assignment(corev1.ResourceCPU, "default", "3000m").Obj()).
   782  					Obj(),
   783  			},
   784  			incoming: utiltesting.MakeWorkload("in", "").
   785  				Priority(1).
   786  				Request(corev1.ResourceCPU, "4").
   787  				Obj(),
   788  			targetCQ: "c2",
   789  			assignment: singlePodSetAssignment(flavorassigner.ResourceAssignment{
   790  				corev1.ResourceCPU: &flavorassigner.FlavorAssignment{
   791  					Name: "default",
   792  					Mode: flavorassigner.Preempt,
   793  				},
   794  			}),
   795  		},
   796  		"each podset preempts a different flavor": {
   797  			admitted: []kueue.Workload{
   798  				*utiltesting.MakeWorkload("low-alpha", "").
   799  					Priority(-1).
   800  					Request(corev1.ResourceMemory, "2Gi").
   801  					ReserveQuota(utiltesting.MakeAdmission("standalone").Assignment(corev1.ResourceMemory, "alpha", "2Gi").Obj()).
   802  					Obj(),
   803  				*utiltesting.MakeWorkload("low-beta", "").
   804  					Priority(-1).
   805  					Request(corev1.ResourceMemory, "2Gi").
   806  					ReserveQuota(utiltesting.MakeAdmission("standalone").Assignment(corev1.ResourceMemory, "beta", "2Gi").Obj()).
   807  					Obj(),
   808  			},
   809  			incoming: utiltesting.MakeWorkload("in", "").
   810  				PodSets(
   811  					*utiltesting.MakePodSet("launcher", 1).
   812  						Request(corev1.ResourceMemory, "2Gi").Obj(),
   813  					*utiltesting.MakePodSet("workers", 2).
   814  						Request(corev1.ResourceMemory, "1Gi").Obj(),
   815  				).
   816  				Obj(),
   817  			targetCQ: "standalone",
   818  			assignment: flavorassigner.Assignment{
   819  				PodSets: []flavorassigner.PodSetAssignment{
   820  					{
   821  						Name: "launcher",
   822  						Flavors: flavorassigner.ResourceAssignment{
   823  							corev1.ResourceMemory: {
   824  								Name: "alpha",
   825  								Mode: flavorassigner.Preempt,
   826  							},
   827  						},
   828  					},
   829  					{
   830  						Name: "workers",
   831  						Flavors: flavorassigner.ResourceAssignment{
   832  							corev1.ResourceMemory: {
   833  								Name: "beta",
   834  								Mode: flavorassigner.Preempt,
   835  							},
   836  						},
   837  					},
   838  				},
   839  			},
   840  			wantPreempted: sets.New("/low-alpha", "/low-beta"),
   841  		},
   842  		"preempt newer workloads with the same priority": {
   843  			admitted: []kueue.Workload{
   844  				*utiltesting.MakeWorkload("wl1", "").
   845  					Priority(2).
   846  					Request(corev1.ResourceCPU, "2").
   847  					ReserveQuota(utiltesting.MakeAdmission("preventStarvation").Assignment(corev1.ResourceCPU, "default", "2").Obj()).
   848  					Obj(),
   849  				*utiltesting.MakeWorkload("wl2", "").
   850  					Priority(1).
   851  					Creation(time.Now()).
   852  					Request(corev1.ResourceCPU, "2").
   853  					ReserveQuota(utiltesting.MakeAdmission("preventStarvation").Assignment(corev1.ResourceCPU, "default", "2").Obj()).
   854  					SetOrReplaceCondition(metav1.Condition{
   855  						Type:               kueue.WorkloadQuotaReserved,
   856  						Status:             metav1.ConditionTrue,
   857  						LastTransitionTime: metav1.NewTime(time.Now().Add(time.Second)),
   858  					}).
   859  					Obj(),
   860  				*utiltesting.MakeWorkload("wl3", "").
   861  					Priority(1).
   862  					Creation(time.Now()).
   863  					Request(corev1.ResourceCPU, "2").
   864  					ReserveQuota(utiltesting.MakeAdmission("preventStarvation").Assignment(corev1.ResourceCPU, "default", "2").Obj()).
   865  					Obj(),
   866  			},
   867  			incoming: utiltesting.MakeWorkload("in", "").
   868  				Priority(1).
   869  				Creation(time.Now().Add(-15 * time.Second)).
   870  				PodSets(
   871  					*utiltesting.MakePodSet("launcher", 1).
   872  						Request(corev1.ResourceCPU, "2").Obj(),
   873  				).
   874  				Obj(),
   875  			targetCQ: "preventStarvation",
   876  			assignment: flavorassigner.Assignment{
   877  				PodSets: []flavorassigner.PodSetAssignment{
   878  					{
   879  						Name: "launcher",
   880  						Flavors: flavorassigner.ResourceAssignment{
   881  							corev1.ResourceCPU: {
   882  								Name: "default",
   883  								Mode: flavorassigner.Preempt,
   884  							},
   885  						},
   886  					},
   887  				},
   888  			},
   889  			wantPreempted: sets.New("/wl2"),
   890  		},
   891  		"use BorrowWithinCohort; allow preempting a lower-priority workload from another ClusterQueue while borrowing": {
   892  			admitted: []kueue.Workload{
   893  				*utiltesting.MakeWorkload("a_best_effort_low", "").
   894  					Priority(-1).
   895  					Request(corev1.ResourceCPU, "10").
   896  					ReserveQuota(utiltesting.MakeAdmission("a_best_effort").Assignment(corev1.ResourceCPU, "default", "10000m").Obj()).
   897  					Obj(),
   898  				*utiltesting.MakeWorkload("b_best_effort_low", "").
   899  					Priority(-1).
   900  					Request(corev1.ResourceCPU, "1").
   901  					ReserveQuota(utiltesting.MakeAdmission("b_best_effort").Assignment(corev1.ResourceCPU, "default", "1000m").Obj()).
   902  					Obj(),
   903  			},
   904  			incoming: utiltesting.MakeWorkload("in", "").
   905  				Request(corev1.ResourceCPU, "10").
   906  				Obj(),
   907  			targetCQ: "a_standard",
   908  			assignment: singlePodSetAssignment(flavorassigner.ResourceAssignment{
   909  				corev1.ResourceCPU: &flavorassigner.FlavorAssignment{
   910  					Name: "default",
   911  					Mode: flavorassigner.Preempt,
   912  				},
   913  			}),
   914  			wantPreempted: sets.New("/a_best_effort_low"),
   915  		},
   916  		"use BorrowWithinCohort; don't allow preempting a lower-priority workload with priority above MaxPriorityThreshold, if borrowing is required even after the preemption": {
   917  			admitted: []kueue.Workload{
   918  				*utiltesting.MakeWorkload("b_standard", "").
   919  					Priority(1).
   920  					Request(corev1.ResourceCPU, "10").
   921  					ReserveQuota(utiltesting.MakeAdmission("b_standard").Assignment(corev1.ResourceCPU, "default", "10000m").Obj()).
   922  					Obj(),
   923  			},
   924  			incoming: utiltesting.MakeWorkload("in", "").
   925  				Priority(2).
   926  				Request(corev1.ResourceCPU, "10").
   927  				Obj(),
   928  			targetCQ: "a_standard",
   929  			assignment: singlePodSetAssignment(flavorassigner.ResourceAssignment{
   930  				corev1.ResourceCPU: &flavorassigner.FlavorAssignment{
   931  					Name: "default",
   932  					Mode: flavorassigner.Preempt,
   933  				},
   934  			}),
   935  		},
   936  		"use BorrowWithinCohort; allow preempting a lower-priority workload with priority above MaxPriorityThreshold, if borrowing is not required after the preemption": {
   937  			admitted: []kueue.Workload{
   938  				// this admitted workload consumes all resources so it needs to be preempted to run a new workload
   939  				*utiltesting.MakeWorkload("b_standard", "").
   940  					Priority(1).
   941  					Request(corev1.ResourceCPU, "13").
   942  					ReserveQuota(utiltesting.MakeAdmission("b_standard").Assignment(corev1.ResourceCPU, "default", "13000m").Obj()).
   943  					Obj(),
   944  			},
   945  			incoming: utiltesting.MakeWorkload("in", "").
   946  				// this is a small workload which can be admitted without borrowing, if the b_standard workload is preempted
   947  				Priority(2).
   948  				Request(corev1.ResourceCPU, "1").
   949  				Obj(),
   950  			targetCQ: "a_standard",
   951  			assignment: singlePodSetAssignment(flavorassigner.ResourceAssignment{
   952  				corev1.ResourceCPU: &flavorassigner.FlavorAssignment{
   953  					Name: "default",
   954  					Mode: flavorassigner.Preempt,
   955  				},
   956  			}),
   957  			wantPreempted: sets.New("/b_standard"),
   958  		},
   959  		"use BorrowWithinCohort; don't allow for preemption of lower-priority workload from the same ClusterQueue": {
   960  			admitted: []kueue.Workload{
   961  				*utiltesting.MakeWorkload("a_standard", "").
   962  					Priority(1).
   963  					Request(corev1.ResourceCPU, "13").
   964  					ReserveQuota(utiltesting.MakeAdmission("a_standard").Assignment(corev1.ResourceCPU, "default", "13000m").Obj()).
   965  					Obj(),
   966  			},
   967  			incoming: utiltesting.MakeWorkload("in", "").
   968  				Priority(2).
   969  				Request(corev1.ResourceCPU, "1").
   970  				Obj(),
   971  			targetCQ: "a_standard",
   972  			assignment: singlePodSetAssignment(flavorassigner.ResourceAssignment{
   973  				corev1.ResourceCPU: &flavorassigner.FlavorAssignment{
   974  					Name: "default",
   975  					Mode: flavorassigner.Preempt,
   976  				},
   977  			}),
   978  		},
   979  		"reclaim quota from lender": {
   980  			admitted: []kueue.Workload{
   981  				*utiltesting.MakeWorkload("lend1-low", "").
   982  					Priority(-1).
   983  					Request(corev1.ResourceCPU, "3").
   984  					ReserveQuota(utiltesting.MakeAdmission("lend1").Assignment(corev1.ResourceCPU, "default", "3000m").Obj()).
   985  					Obj(),
   986  				*utiltesting.MakeWorkload("lend2-mid", "").
   987  					Request(corev1.ResourceCPU, "3").
   988  					ReserveQuota(utiltesting.MakeAdmission("lend2").Assignment(corev1.ResourceCPU, "default", "3000m").Obj()).
   989  					Obj(),
   990  				*utiltesting.MakeWorkload("lend2-high", "").
   991  					Priority(1).
   992  					Request(corev1.ResourceCPU, "4").
   993  					ReserveQuota(utiltesting.MakeAdmission("lend2").Assignment(corev1.ResourceCPU, "default", "4000m").Obj()).
   994  					Obj(),
   995  			},
   996  			incoming: utiltesting.MakeWorkload("in", "").
   997  				Priority(1).
   998  				Request(corev1.ResourceCPU, "3").
   999  				Obj(),
  1000  			targetCQ: "lend1",
  1001  			assignment: singlePodSetAssignment(flavorassigner.ResourceAssignment{
  1002  				corev1.ResourceCPU: &flavorassigner.FlavorAssignment{
  1003  					Name: "default",
  1004  					Mode: flavorassigner.Preempt,
  1005  				},
  1006  			}),
  1007  			wantPreempted:      sets.New("/lend2-mid"),
  1008  			enableLendingLimit: true,
  1009  		},
  1010  		"preempt from all ClusterQueues in cohort-lend": {
  1011  			admitted: []kueue.Workload{
  1012  				*utiltesting.MakeWorkload("lend1-low", "").
  1013  					Priority(-1).
  1014  					Request(corev1.ResourceCPU, "3").
  1015  					ReserveQuota(utiltesting.MakeAdmission("lend1").Assignment(corev1.ResourceCPU, "default", "3000m").Obj()).
  1016  					Obj(),
  1017  				*utiltesting.MakeWorkload("lend1-mid", "").
  1018  					Request(corev1.ResourceCPU, "2").
  1019  					ReserveQuota(utiltesting.MakeAdmission("lend1").Assignment(corev1.ResourceCPU, "default", "2000m").Obj()).
  1020  					Obj(),
  1021  				*utiltesting.MakeWorkload("lend2-low", "").
  1022  					Priority(-1).
  1023  					Request(corev1.ResourceCPU, "3").
  1024  					ReserveQuota(utiltesting.MakeAdmission("lend2").Assignment(corev1.ResourceCPU, "default", "3000m").Obj()).
  1025  					Obj(),
  1026  				*utiltesting.MakeWorkload("lend2-mid", "").
  1027  					Request(corev1.ResourceCPU, "4").
  1028  					ReserveQuota(utiltesting.MakeAdmission("lend2").Assignment(corev1.ResourceCPU, "default", "4000m").Obj()).
  1029  					Obj(),
  1030  			},
  1031  			incoming: utiltesting.MakeWorkload("in", "").
  1032  				Request(corev1.ResourceCPU, "4").
  1033  				Obj(),
  1034  			targetCQ: "lend1",
  1035  			assignment: singlePodSetAssignment(flavorassigner.ResourceAssignment{
  1036  				corev1.ResourceCPU: &flavorassigner.FlavorAssignment{
  1037  					Name: "default",
  1038  					Mode: flavorassigner.Preempt,
  1039  				},
  1040  			}),
  1041  			wantPreempted:      sets.New("/lend1-low", "/lend2-low"),
  1042  			enableLendingLimit: true,
  1043  		},
  1044  		"cannot preempt from other ClusterQueues if exceeds requestable quota including lending limit": {
  1045  			admitted: []kueue.Workload{
  1046  				*utiltesting.MakeWorkload("lend2-low", "").
  1047  					Priority(-1).
  1048  					Request(corev1.ResourceCPU, "10").
  1049  					ReserveQuota(utiltesting.MakeAdmission("lend2").Assignment(corev1.ResourceCPU, "default", "10").Obj()).
  1050  					Obj(),
  1051  			},
  1052  			incoming: utiltesting.MakeWorkload("in", "").
  1053  				Request(corev1.ResourceCPU, "9").
  1054  				Obj(),
  1055  			targetCQ: "lend1",
  1056  			assignment: singlePodSetAssignment(flavorassigner.ResourceAssignment{
  1057  				corev1.ResourceCPU: &flavorassigner.FlavorAssignment{
  1058  					Name: "default",
  1059  					Mode: flavorassigner.Preempt,
  1060  				},
  1061  			}),
  1062  			wantPreempted:      nil,
  1063  			enableLendingLimit: true,
  1064  		},
  1065  	}
  1066  	for name, tc := range cases {
  1067  		t.Run(name, func(t *testing.T) {
  1068  			defer features.SetFeatureGateDuringTest(t, features.LendingLimit, tc.enableLendingLimit)()
  1069  			ctx, _ := utiltesting.ContextWithLog(t)
  1070  			cl := utiltesting.NewClientBuilder().
  1071  				WithLists(&kueue.WorkloadList{Items: tc.admitted}).
  1072  				Build()
  1073  
  1074  			cqCache := cache.New(cl)
  1075  			for _, flv := range flavors {
  1076  				cqCache.AddOrUpdateResourceFlavor(flv)
  1077  			}
  1078  			for _, cq := range clusterQueues {
  1079  				if err := cqCache.AddClusterQueue(ctx, cq); err != nil {
  1080  					t.Fatalf("Couldn't add ClusterQueue to cache: %v", err)
  1081  				}
  1082  			}
  1083  
  1084  			var lock sync.Mutex
  1085  			gotPreempted := sets.New[string]()
  1086  			broadcaster := record.NewBroadcaster()
  1087  			scheme := runtime.NewScheme()
  1088  			recorder := broadcaster.NewRecorder(scheme, corev1.EventSource{Component: constants.AdmissionName})
  1089  			preemptor := New(cl, workload.Ordering{}, recorder)
  1090  			preemptor.applyPreemption = func(ctx context.Context, w *kueue.Workload) error {
  1091  				lock.Lock()
  1092  				gotPreempted.Insert(workload.Key(w))
  1093  				lock.Unlock()
  1094  				return nil
  1095  			}
  1096  
  1097  			startingSnapshot := cqCache.Snapshot()
  1098  			// make a working copy of the snapshot than preemption can temporarily modify
  1099  			snapshot := cqCache.Snapshot()
  1100  			wlInfo := workload.NewInfo(tc.incoming)
  1101  			wlInfo.ClusterQueue = tc.targetCQ
  1102  			targetClusterQueue := snapshot.ClusterQueues[wlInfo.ClusterQueue]
  1103  			targets := preemptor.GetTargets(*wlInfo, tc.assignment, &snapshot)
  1104  			preempted, err := preemptor.IssuePreemptions(ctx, targets, targetClusterQueue)
  1105  			if err != nil {
  1106  				t.Fatalf("Failed doing preemption")
  1107  			}
  1108  			if diff := cmp.Diff(tc.wantPreempted, gotPreempted, cmpopts.EquateEmpty()); diff != "" {
  1109  				t.Errorf("Issued preemptions (-want,+got):\n%s", diff)
  1110  			}
  1111  			if preempted != tc.wantPreempted.Len() {
  1112  				t.Errorf("Reported %d preemptions, want %d", preempted, tc.wantPreempted.Len())
  1113  			}
  1114  			if diff := cmp.Diff(startingSnapshot, snapshot, snapCmpOpts...); diff != "" {
  1115  				t.Errorf("Snapshot was modified (-initial,+end):\n%s", diff)
  1116  			}
  1117  		})
  1118  	}
  1119  }
  1120  
  1121  func TestCandidatesOrdering(t *testing.T) {
  1122  	now := time.Now()
  1123  	candidates := []*workload.Info{
  1124  		workload.NewInfo(utiltesting.MakeWorkload("high", "").
  1125  			ReserveQuota(utiltesting.MakeAdmission("self").Obj()).
  1126  			Priority(10).
  1127  			Obj()),
  1128  		workload.NewInfo(utiltesting.MakeWorkload("low", "").
  1129  			ReserveQuota(utiltesting.MakeAdmission("self").Obj()).
  1130  			Priority(-10).
  1131  			Obj()),
  1132  		workload.NewInfo(utiltesting.MakeWorkload("other", "").
  1133  			ReserveQuota(utiltesting.MakeAdmission("other").Obj()).
  1134  			Priority(10).
  1135  			Obj()),
  1136  		workload.NewInfo(utiltesting.MakeWorkload("evicted", "").
  1137  			SetOrReplaceCondition(metav1.Condition{
  1138  				Type:   kueue.WorkloadEvicted,
  1139  				Status: metav1.ConditionTrue,
  1140  			}).
  1141  			Obj()),
  1142  		workload.NewInfo(utiltesting.MakeWorkload("old-a", "").
  1143  			UID("old-a").
  1144  			ReserveQuotaAt(utiltesting.MakeAdmission("self").Obj(), now).
  1145  			Obj()),
  1146  		workload.NewInfo(utiltesting.MakeWorkload("old-b", "").
  1147  			UID("old-b").
  1148  			ReserveQuotaAt(utiltesting.MakeAdmission("self").Obj(), now).
  1149  			Obj()),
  1150  		workload.NewInfo(utiltesting.MakeWorkload("current", "").
  1151  			ReserveQuota(utiltesting.MakeAdmission("self").Obj()).
  1152  			SetOrReplaceCondition(metav1.Condition{
  1153  				Type:               kueue.WorkloadQuotaReserved,
  1154  				Status:             metav1.ConditionTrue,
  1155  				LastTransitionTime: metav1.NewTime(now.Add(time.Second)),
  1156  			}).
  1157  			Obj()),
  1158  	}
  1159  	sort.Slice(candidates, candidatesOrdering(candidates, "self", now))
  1160  	gotNames := make([]string, len(candidates))
  1161  	for i, c := range candidates {
  1162  		gotNames[i] = workload.Key(c.Obj)
  1163  	}
  1164  	wantCandidates := []string{"/evicted", "/other", "/low", "/current", "/old-a", "/old-b", "/high"}
  1165  	if diff := cmp.Diff(wantCandidates, gotNames); diff != "" {
  1166  		t.Errorf("Sorted with wrong order (-want,+got):\n%s", diff)
  1167  	}
  1168  }
  1169  
  1170  func singlePodSetAssignment(assignments flavorassigner.ResourceAssignment) flavorassigner.Assignment {
  1171  	return flavorassigner.Assignment{
  1172  		PodSets: []flavorassigner.PodSetAssignment{{
  1173  			Name:    kueue.DefaultPodSetName,
  1174  			Flavors: assignments,
  1175  		}},
  1176  	}
  1177  }