sigs.k8s.io/kueue@v0.6.2/test/integration/scheduler/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 scheduler
    18  
    19  import (
    20  	"fmt"
    21  	"time"
    22  
    23  	"github.com/onsi/ginkgo/v2"
    24  	"github.com/onsi/gomega"
    25  	corev1 "k8s.io/api/core/v1"
    26  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    27  	"k8s.io/utils/ptr"
    28  
    29  	kueue "sigs.k8s.io/kueue/apis/kueue/v1beta1"
    30  	"sigs.k8s.io/kueue/pkg/features"
    31  	"sigs.k8s.io/kueue/pkg/util/testing"
    32  	"sigs.k8s.io/kueue/test/util"
    33  )
    34  
    35  const (
    36  	lowPriority int32 = iota - 1
    37  	midPriority
    38  	highPriority
    39  	veryHighPriority
    40  )
    41  
    42  var _ = ginkgo.Describe("Preemption", func() {
    43  	var (
    44  		alphaFlavor *kueue.ResourceFlavor
    45  		ns          *corev1.Namespace
    46  	)
    47  
    48  	ginkgo.BeforeEach(func() {
    49  		ns = &corev1.Namespace{
    50  			ObjectMeta: metav1.ObjectMeta{
    51  				GenerateName: "preemption-",
    52  			},
    53  		}
    54  		gomega.Expect(k8sClient.Create(ctx, ns)).To(gomega.Succeed())
    55  		alphaFlavor = testing.MakeResourceFlavor("alpha").Obj()
    56  		gomega.Expect(k8sClient.Create(ctx, alphaFlavor)).To(gomega.Succeed())
    57  	})
    58  
    59  	ginkgo.AfterEach(func() {
    60  		gomega.Expect(util.DeleteNamespace(ctx, k8sClient, ns)).To(gomega.Succeed())
    61  		util.ExpectResourceFlavorToBeDeleted(ctx, k8sClient, alphaFlavor, true)
    62  	})
    63  
    64  	ginkgo.Context("In a single ClusterQueue", func() {
    65  		var (
    66  			cq *kueue.ClusterQueue
    67  			q  *kueue.LocalQueue
    68  		)
    69  
    70  		ginkgo.BeforeEach(func() {
    71  			cq = testing.MakeClusterQueue("cq").
    72  				ResourceGroup(*testing.MakeFlavorQuotas("alpha").Resource(corev1.ResourceCPU, "4").Obj()).
    73  				Preemption(kueue.ClusterQueuePreemption{
    74  					WithinClusterQueue: kueue.PreemptionPolicyLowerOrNewerEqualPriority,
    75  				}).
    76  				Obj()
    77  			gomega.Expect(k8sClient.Create(ctx, cq)).To(gomega.Succeed())
    78  
    79  			q = testing.MakeLocalQueue("q", ns.Name).ClusterQueue(cq.Name).Obj()
    80  			gomega.Expect(k8sClient.Create(ctx, q)).To(gomega.Succeed())
    81  		})
    82  
    83  		ginkgo.AfterEach(func() {
    84  			gomega.Expect(util.DeleteWorkloadsInNamespace(ctx, k8sClient, ns)).To(gomega.Succeed())
    85  			util.ExpectClusterQueueToBeDeleted(ctx, k8sClient, cq, true)
    86  		})
    87  
    88  		ginkgo.It("Should preempt Workloads with lower priority when there is not enough quota", func() {
    89  			ginkgo.By("Creating initial Workloads with different priorities")
    90  			lowWl1 := testing.MakeWorkload("low-wl-1", ns.Name).
    91  				Queue(q.Name).
    92  				Priority(lowPriority).
    93  				Request(corev1.ResourceCPU, "1").
    94  				Obj()
    95  			lowWl2 := testing.MakeWorkload("low-wl-2", ns.Name).
    96  				Queue(q.Name).
    97  				Priority(lowPriority).
    98  				Request(corev1.ResourceCPU, "1").
    99  				Obj()
   100  			midWl := testing.MakeWorkload("mid-wl", ns.Name).
   101  				Queue(q.Name).
   102  				Priority(midPriority).
   103  				Request(corev1.ResourceCPU, "1").
   104  				Obj()
   105  			highWl1 := testing.MakeWorkload("high-wl-1", ns.Name).
   106  				Queue(q.Name).
   107  				Priority(highPriority).
   108  				Request(corev1.ResourceCPU, "1").
   109  				Obj()
   110  			gomega.Expect(k8sClient.Create(ctx, lowWl1)).To(gomega.Succeed())
   111  			gomega.Expect(k8sClient.Create(ctx, lowWl2)).To(gomega.Succeed())
   112  			gomega.Expect(k8sClient.Create(ctx, midWl)).To(gomega.Succeed())
   113  			gomega.Expect(k8sClient.Create(ctx, highWl1)).To(gomega.Succeed())
   114  
   115  			util.ExpectWorkloadsToHaveQuotaReservation(ctx, k8sClient, cq.Name, lowWl1, lowWl2, midWl, highWl1)
   116  
   117  			ginkgo.By("Creating a low priority Workload")
   118  			lowWl3 := testing.MakeWorkload("low-wl-3", ns.Name).
   119  				Queue(q.Name).
   120  				Priority(lowPriority).
   121  				Request(corev1.ResourceCPU, "1").
   122  				Obj()
   123  			gomega.Expect(k8sClient.Create(ctx, lowWl3)).To(gomega.Succeed())
   124  
   125  			util.ExpectWorkloadsToBePending(ctx, k8sClient, lowWl3)
   126  
   127  			ginkgo.By("Creating a high priority Workload")
   128  			highWl2 := testing.MakeWorkload("high-wl-2", ns.Name).
   129  				Queue(q.Name).
   130  				Priority(highPriority).
   131  				Request(corev1.ResourceCPU, "2").
   132  				Obj()
   133  			gomega.Expect(k8sClient.Create(ctx, highWl2)).To(gomega.Succeed())
   134  
   135  			util.FinishEvictionForWorkloads(ctx, k8sClient, lowWl1, lowWl2)
   136  
   137  			util.ExpectWorkloadsToHaveQuotaReservation(ctx, k8sClient, cq.Name, highWl2)
   138  			util.ExpectWorkloadsToBePending(ctx, k8sClient, lowWl1, lowWl2)
   139  		})
   140  
   141  		ginkgo.It("Should preempt newer Workloads with the same priority when there is not enough quota", func() {
   142  			ginkgo.By("Creating initial Workloads")
   143  			wl1 := testing.MakeWorkload("wl-1", ns.Name).
   144  				Queue(q.Name).
   145  				Priority(lowPriority).
   146  				Request(corev1.ResourceCPU, "1").
   147  				Obj()
   148  			wl2 := testing.MakeWorkload("wl-2", ns.Name).
   149  				Queue(q.Name).
   150  				Priority(lowPriority).
   151  				Request(corev1.ResourceCPU, "1").
   152  				Obj()
   153  			wl3 := testing.MakeWorkload("wl-3", ns.Name).
   154  				Queue(q.Name).
   155  				Priority(lowPriority).
   156  				Request(corev1.ResourceCPU, "3").
   157  				Obj()
   158  			gomega.Expect(k8sClient.Create(ctx, wl1)).To(gomega.Succeed())
   159  			gomega.Expect(k8sClient.Create(ctx, wl2)).To(gomega.Succeed())
   160  			util.ExpectWorkloadsToHaveQuotaReservation(ctx, k8sClient, cq.Name, wl1, wl2)
   161  
   162  			gomega.Expect(k8sClient.Create(ctx, wl3)).To(gomega.Succeed())
   163  			util.ExpectWorkloadsToBePending(ctx, k8sClient, wl3)
   164  
   165  			ginkgo.By("Waiting one second, to ensure that the new workload has a later creation time")
   166  			time.Sleep(time.Second)
   167  
   168  			ginkgo.By("Creating a new Workload")
   169  			wl4 := testing.MakeWorkload("wl-4", ns.Name).
   170  				Queue(q.Name).
   171  				Priority(lowPriority).
   172  				Request(corev1.ResourceCPU, "1").
   173  				Obj()
   174  			gomega.Expect(k8sClient.Create(ctx, wl4)).To(gomega.Succeed())
   175  
   176  			util.ExpectWorkloadsToHaveQuotaReservation(ctx, k8sClient, cq.Name, wl1, wl2, wl4)
   177  			util.ExpectWorkloadsToBePending(ctx, k8sClient, wl3)
   178  
   179  			ginkgo.By("Finishing the first workload")
   180  			util.FinishWorkloads(ctx, k8sClient, wl1)
   181  
   182  			ginkgo.By("Finishing eviction for wl4")
   183  			util.FinishEvictionForWorkloads(ctx, k8sClient, wl4)
   184  
   185  			util.ExpectWorkloadsToHaveQuotaReservation(ctx, k8sClient, cq.Name, wl3)
   186  			util.ExpectWorkloadsToBePending(ctx, k8sClient, wl4)
   187  		})
   188  	})
   189  
   190  	ginkgo.Context("In a ClusterQueue that is part of a cohort", func() {
   191  		var (
   192  			alphaCQ, betaCQ, gammaCQ *kueue.ClusterQueue
   193  			alphaQ, betaQ, gammaQ    *kueue.LocalQueue
   194  		)
   195  
   196  		ginkgo.BeforeEach(func() {
   197  			alphaCQ = testing.MakeClusterQueue("alpha-cq").
   198  				Cohort("all").
   199  				ResourceGroup(*testing.MakeFlavorQuotas("alpha").Resource(corev1.ResourceCPU, "2").Obj()).
   200  				Preemption(kueue.ClusterQueuePreemption{
   201  					WithinClusterQueue:  kueue.PreemptionPolicyLowerPriority,
   202  					ReclaimWithinCohort: kueue.PreemptionPolicyAny,
   203  				}).
   204  				Obj()
   205  			gomega.Expect(k8sClient.Create(ctx, alphaCQ)).To(gomega.Succeed())
   206  			alphaQ = testing.MakeLocalQueue("alpha-q", ns.Name).ClusterQueue(alphaCQ.Name).Obj()
   207  			gomega.Expect(k8sClient.Create(ctx, alphaQ)).To(gomega.Succeed())
   208  
   209  			betaCQ = testing.MakeClusterQueue("beta-cq").
   210  				Cohort("all").
   211  				ResourceGroup(*testing.MakeFlavorQuotas("alpha").Resource(corev1.ResourceCPU, "2").Obj()).
   212  				Obj()
   213  			gomega.Expect(k8sClient.Create(ctx, betaCQ)).To(gomega.Succeed())
   214  			betaQ = testing.MakeLocalQueue("beta-q", ns.Name).ClusterQueue(betaCQ.Name).Obj()
   215  			gomega.Expect(k8sClient.Create(ctx, betaQ)).To(gomega.Succeed())
   216  
   217  			gammaCQ = testing.MakeClusterQueue("gamma-cq").
   218  				Cohort("all").
   219  				ResourceGroup(*testing.MakeFlavorQuotas("alpha").Resource(corev1.ResourceCPU, "2").Obj()).
   220  				Preemption(kueue.ClusterQueuePreemption{
   221  					WithinClusterQueue:  kueue.PreemptionPolicyLowerPriority,
   222  					ReclaimWithinCohort: kueue.PreemptionPolicyAny,
   223  				}).
   224  				Obj()
   225  			gomega.Expect(k8sClient.Create(ctx, gammaCQ)).To(gomega.Succeed())
   226  			gammaQ = testing.MakeLocalQueue("gamma-q", ns.Name).ClusterQueue(gammaCQ.Name).Obj()
   227  			gomega.Expect(k8sClient.Create(ctx, gammaQ)).To(gomega.Succeed())
   228  		})
   229  
   230  		ginkgo.AfterEach(func() {
   231  			gomega.Expect(util.DeleteWorkloadsInNamespace(ctx, k8sClient, ns)).To(gomega.Succeed())
   232  			util.ExpectClusterQueueToBeDeleted(ctx, k8sClient, alphaCQ, true)
   233  			util.ExpectClusterQueueToBeDeleted(ctx, k8sClient, betaCQ, true)
   234  			util.ExpectClusterQueueToBeDeleted(ctx, k8sClient, gammaCQ, true)
   235  		})
   236  
   237  		ginkgo.It("Should preempt Workloads in the cohort borrowing quota, when the ClusterQueue is using less than nominal quota", func() {
   238  			ginkgo.By("Creating workloads in beta-cq that borrow quota")
   239  
   240  			alphaLowWl := testing.MakeWorkload("alpha-low", ns.Name).
   241  				Queue(alphaQ.Name).
   242  				Priority(lowPriority).
   243  				Request(corev1.ResourceCPU, "1").
   244  				Obj()
   245  			gomega.Expect(k8sClient.Create(ctx, alphaLowWl)).To(gomega.Succeed())
   246  
   247  			betaMidWl := testing.MakeWorkload("beta-mid", ns.Name).
   248  				Queue(betaQ.Name).
   249  				Priority(midPriority).
   250  				Request(corev1.ResourceCPU, "1").
   251  				Obj()
   252  			gomega.Expect(k8sClient.Create(ctx, betaMidWl)).To(gomega.Succeed())
   253  			betaHighWl := testing.MakeWorkload("beta-high", ns.Name).
   254  				Queue(betaQ.Name).
   255  				Priority(highPriority).
   256  				Request(corev1.ResourceCPU, "4").
   257  				Obj()
   258  			gomega.Expect(k8sClient.Create(ctx, betaHighWl)).To(gomega.Succeed())
   259  
   260  			util.ExpectWorkloadsToHaveQuotaReservation(ctx, k8sClient, alphaCQ.Name, alphaLowWl)
   261  			util.ExpectWorkloadsToHaveQuotaReservation(ctx, k8sClient, betaCQ.Name, betaMidWl, betaHighWl)
   262  
   263  			ginkgo.By("Creating workload in alpha-cq to preempt workloads in both ClusterQueues")
   264  			alphaMidWl := testing.MakeWorkload("alpha-mid", ns.Name).
   265  				Queue(alphaQ.Name).
   266  				Priority(midPriority).
   267  				Request(corev1.ResourceCPU, "2").
   268  				Obj()
   269  			gomega.Expect(k8sClient.Create(ctx, alphaMidWl)).To(gomega.Succeed())
   270  
   271  			util.FinishEvictionForWorkloads(ctx, k8sClient, alphaLowWl, betaMidWl)
   272  
   273  			util.ExpectWorkloadsToHaveQuotaReservation(ctx, k8sClient, alphaCQ.Name, alphaMidWl)
   274  			util.ExpectWorkloadsToBePending(ctx, k8sClient, alphaLowWl, betaMidWl)
   275  		})
   276  
   277  		ginkgo.It("Should not preempt Workloads in the cohort, if the ClusterQueue requires borrowing", func() {
   278  			ginkgo.By("Creating workloads in beta-cq that borrow quota")
   279  
   280  			alphaHighWl1 := testing.MakeWorkload("alpha-high-1", ns.Name).
   281  				Queue(alphaQ.Name).
   282  				Priority(highPriority).
   283  				Request(corev1.ResourceCPU, "2").
   284  				Obj()
   285  			gomega.Expect(k8sClient.Create(ctx, alphaHighWl1)).To(gomega.Succeed())
   286  			betaLowWl := testing.MakeWorkload("beta-low", ns.Name).
   287  				Queue(betaQ.Name).
   288  				Priority(lowPriority).
   289  				Request(corev1.ResourceCPU, "4").
   290  				Obj()
   291  			gomega.Expect(k8sClient.Create(ctx, betaLowWl)).To(gomega.Succeed())
   292  
   293  			util.ExpectWorkloadsToHaveQuotaReservation(ctx, k8sClient, alphaCQ.Name, alphaHighWl1)
   294  			util.ExpectWorkloadsToHaveQuotaReservation(ctx, k8sClient, betaCQ.Name, betaLowWl)
   295  
   296  			ginkgo.By("Creating high priority workload in alpha-cq that doesn't fit without borrowing")
   297  			alphaHighWl2 := testing.MakeWorkload("alpha-high-2", ns.Name).
   298  				Queue(alphaQ.Name).
   299  				Priority(highPriority).
   300  				Request(corev1.ResourceCPU, "2").
   301  				Obj()
   302  			gomega.Expect(k8sClient.Create(ctx, alphaHighWl2)).To(gomega.Succeed())
   303  
   304  			// No preemptions happen.
   305  			util.ExpectWorkloadsToBePending(ctx, k8sClient, alphaHighWl2)
   306  			util.ExpectWorkloadsToHaveQuotaReservation(ctx, k8sClient, alphaCQ.Name, alphaHighWl1)
   307  			util.ExpectWorkloadsToHaveQuotaReservation(ctx, k8sClient, betaCQ.Name, betaLowWl)
   308  		})
   309  
   310  		ginkgo.It("Should preempt all necessary workloads in concurrent scheduling with different priorities", func() {
   311  			ginkgo.By("Creating workloads in beta-cq that borrow quota")
   312  
   313  			betaMidWl := testing.MakeWorkload("beta-mid", ns.Name).
   314  				Queue(betaQ.Name).
   315  				Priority(midPriority).
   316  				Request(corev1.ResourceCPU, "3").
   317  				Obj()
   318  			gomega.Expect(k8sClient.Create(ctx, betaMidWl)).To(gomega.Succeed())
   319  			betaHighWl := testing.MakeWorkload("beta-high", ns.Name).
   320  				Queue(betaQ.Name).
   321  				Priority(highPriority).
   322  				Request(corev1.ResourceCPU, "3").
   323  				Obj()
   324  			gomega.Expect(k8sClient.Create(ctx, betaHighWl)).To(gomega.Succeed())
   325  
   326  			util.ExpectWorkloadsToHaveQuotaReservation(ctx, k8sClient, betaCQ.Name, betaMidWl, betaHighWl)
   327  
   328  			ginkgo.By("Creating workload in alpha-cq and gamma-cq that need to preempt")
   329  			alphaMidWl := testing.MakeWorkload("alpha-mid", ns.Name).
   330  				Queue(alphaQ.Name).
   331  				Priority(midPriority).
   332  				Request(corev1.ResourceCPU, "2").
   333  				Obj()
   334  
   335  			gammaMidWl := testing.MakeWorkload("gamma-mid", ns.Name).
   336  				Queue(gammaQ.Name).
   337  				Priority(midPriority).
   338  				Request(corev1.ResourceCPU, "2").
   339  				Obj()
   340  
   341  			gomega.Expect(k8sClient.Create(ctx, alphaMidWl)).To(gomega.Succeed())
   342  			gomega.Expect(k8sClient.Create(ctx, gammaMidWl)).To(gomega.Succeed())
   343  
   344  			// since the two pending workloads are not aware of each other both of them
   345  			// will request the eviction of betaMidWl only
   346  			util.FinishEvictionForWorkloads(ctx, k8sClient, betaMidWl)
   347  
   348  			// one of alpha-mid and gamma-mid should be admitted
   349  			util.ExpectWorkloadsToBeAdmittedCount(ctx, k8sClient, 1, alphaMidWl, gammaMidWl)
   350  
   351  			// betaHighWl remains admitted
   352  			util.ExpectWorkloadsToHaveQuotaReservation(ctx, k8sClient, betaCQ.Name, betaHighWl)
   353  
   354  			// the last one should request the preemption of betaHighWl
   355  			util.FinishEvictionForWorkloads(ctx, k8sClient, betaHighWl)
   356  
   357  			util.ExpectWorkloadsToHaveQuotaReservation(ctx, k8sClient, alphaCQ.Name, alphaMidWl)
   358  			util.ExpectWorkloadsToHaveQuotaReservation(ctx, k8sClient, gammaCQ.Name, gammaMidWl)
   359  		})
   360  
   361  		ginkgo.It("Should preempt all necessary workloads in concurrent scheduling with the same priority", func() {
   362  			var betaWls []*kueue.Workload
   363  			for i := 0; i < 3; i++ {
   364  				wl := testing.MakeWorkload(fmt.Sprintf("beta-%d", i), ns.Name).
   365  					Queue(betaQ.Name).
   366  					Request(corev1.ResourceCPU, "2").
   367  					Obj()
   368  				gomega.Expect(k8sClient.Create(ctx, wl)).To(gomega.Succeed())
   369  				betaWls = append(betaWls, wl)
   370  			}
   371  			util.ExpectWorkloadsToBeAdmitted(ctx, k8sClient, betaWls...)
   372  
   373  			ginkgo.By("Creating preempting pods")
   374  
   375  			alphaWl := testing.MakeWorkload("alpha", ns.Name).
   376  				Queue(alphaQ.Name).
   377  				Request(corev1.ResourceCPU, "2").
   378  				Obj()
   379  			gomega.Expect(k8sClient.Create(ctx, alphaWl)).To(gomega.Succeed())
   380  
   381  			gammaWl := testing.MakeWorkload("gamma", ns.Name).
   382  				Queue(gammaQ.Name).
   383  				Request(corev1.ResourceCPU, "2").
   384  				Obj()
   385  			gomega.Expect(k8sClient.Create(ctx, gammaWl)).To(gomega.Succeed())
   386  
   387  			var evictedWorkloads []*kueue.Workload
   388  			gomega.Eventually(func() int {
   389  				evictedWorkloads = util.FilterEvictedWorkloads(ctx, k8sClient, betaWls...)
   390  				return len(evictedWorkloads)
   391  			}, util.Timeout, util.Interval).Should(gomega.Equal(1), "Number of evicted workloads")
   392  
   393  			ginkgo.By("Finishing eviction for first set of preempted workloads")
   394  			util.FinishEvictionForWorkloads(ctx, k8sClient, evictedWorkloads...)
   395  			util.ExpectWorkloadsToBeAdmittedCount(ctx, k8sClient, 1, alphaWl, gammaWl)
   396  
   397  			gomega.Eventually(func() int {
   398  				evictedWorkloads = util.FilterEvictedWorkloads(ctx, k8sClient, betaWls...)
   399  				return len(evictedWorkloads)
   400  			}, util.Timeout, util.Interval).Should(gomega.Equal(2), "Number of evicted workloads")
   401  
   402  			ginkgo.By("Finishing eviction for second set of preempted workloads")
   403  			util.FinishEvictionForWorkloads(ctx, k8sClient, evictedWorkloads...)
   404  			util.ExpectWorkloadsToBeAdmitted(ctx, k8sClient, alphaWl, gammaWl)
   405  			util.ExpectWorkloadsToBeAdmittedCount(ctx, k8sClient, 1, betaWls...)
   406  		})
   407  	})
   408  
   409  	ginkgo.Context("In a cohort with StrictFIFO", func() {
   410  		var (
   411  			alphaCQ, betaCQ *kueue.ClusterQueue
   412  			alphaLQ, betaLQ *kueue.LocalQueue
   413  			oneFlavor       *kueue.ResourceFlavor
   414  		)
   415  
   416  		ginkgo.BeforeEach(func() {
   417  			oneFlavor = testing.MakeResourceFlavor("one").Obj()
   418  			gomega.Expect(k8sClient.Create(ctx, oneFlavor)).To(gomega.Succeed())
   419  
   420  			alphaCQ = testing.MakeClusterQueue("alpha-cq").
   421  				Cohort("all").
   422  				QueueingStrategy(kueue.StrictFIFO).
   423  				ResourceGroup(*testing.MakeFlavorQuotas("one").Resource(corev1.ResourceCPU, "2").Obj()).
   424  				Preemption(kueue.ClusterQueuePreemption{
   425  					WithinClusterQueue:  kueue.PreemptionPolicyLowerPriority,
   426  					ReclaimWithinCohort: kueue.PreemptionPolicyAny,
   427  				}).
   428  				Obj()
   429  			gomega.Expect(k8sClient.Create(ctx, alphaCQ)).To(gomega.Succeed())
   430  			alphaLQ = testing.MakeLocalQueue("alpha-lq", ns.Name).ClusterQueue("alpha-cq").Obj()
   431  			gomega.Expect(k8sClient.Create(ctx, alphaLQ)).To(gomega.Succeed())
   432  			betaCQ = testing.MakeClusterQueue("beta-cq").
   433  				Cohort("all").
   434  				QueueingStrategy(kueue.StrictFIFO).
   435  				ResourceGroup(*testing.MakeFlavorQuotas("one").Resource(corev1.ResourceCPU, "2").Obj()).
   436  				Preemption(kueue.ClusterQueuePreemption{
   437  					WithinClusterQueue:  kueue.PreemptionPolicyLowerPriority,
   438  					ReclaimWithinCohort: kueue.PreemptionPolicyAny,
   439  				}).
   440  				Obj()
   441  			gomega.Expect(k8sClient.Create(ctx, betaCQ)).To(gomega.Succeed())
   442  			betaLQ = testing.MakeLocalQueue("beta-lq", ns.Name).ClusterQueue("beta-cq").Obj()
   443  			gomega.Expect(k8sClient.Create(ctx, betaLQ)).To(gomega.Succeed())
   444  		})
   445  
   446  		ginkgo.AfterEach(func() {
   447  			gomega.Expect(util.DeleteWorkloadsInNamespace(ctx, k8sClient, ns)).To(gomega.Succeed())
   448  			gomega.Expect(util.DeleteLocalQueue(ctx, k8sClient, alphaLQ)).To(gomega.Succeed())
   449  			gomega.Expect(util.DeleteClusterQueue(ctx, k8sClient, alphaCQ)).To(gomega.Succeed())
   450  			gomega.Expect(util.DeleteLocalQueue(ctx, k8sClient, betaLQ)).To(gomega.Succeed())
   451  			gomega.Expect(util.DeleteClusterQueue(ctx, k8sClient, betaCQ)).To(gomega.Succeed())
   452  		})
   453  
   454  		ginkgo.It("Should reclaim from cohort even if another CQ has pending workloads", func() {
   455  			useAllAlphaWl := testing.MakeWorkload("use-all", ns.Name).
   456  				Queue("alpha-lq").
   457  				Priority(1).
   458  				Request(corev1.ResourceCPU, "4").
   459  				Obj()
   460  			gomega.Expect(k8sClient.Create(ctx, useAllAlphaWl)).To(gomega.Succeed())
   461  			util.ExpectWorkloadsToBeAdmitted(ctx, k8sClient, useAllAlphaWl)
   462  
   463  			pendingAlphaWl := testing.MakeWorkload("pending", ns.Name).
   464  				Queue("alpha-lq").
   465  				Priority(0).
   466  				Request(corev1.ResourceCPU, "1").
   467  				Obj()
   468  			gomega.Expect(k8sClient.Create(ctx, pendingAlphaWl)).To(gomega.Succeed())
   469  			util.ExpectWorkloadsToBePending(ctx, k8sClient, pendingAlphaWl)
   470  
   471  			ginkgo.By("Creating a workload to reclaim quota")
   472  
   473  			preemptorBetaWl := testing.MakeWorkload("preemptor", ns.Name).
   474  				Queue("beta-lq").
   475  				Priority(-1).
   476  				Request(corev1.ResourceCPU, "1").
   477  				Obj()
   478  			gomega.Expect(k8sClient.Create(ctx, preemptorBetaWl)).To(gomega.Succeed())
   479  			util.ExpectWorkloadsToBePreempted(ctx, k8sClient, useAllAlphaWl)
   480  			util.FinishEvictionForWorkloads(ctx, k8sClient, useAllAlphaWl)
   481  			util.ExpectWorkloadsToBeAdmitted(ctx, k8sClient, preemptorBetaWl)
   482  			util.ExpectWorkloadsToBePending(ctx, k8sClient, useAllAlphaWl, pendingAlphaWl)
   483  		})
   484  
   485  	})
   486  
   487  	ginkgo.Context("When most quota is in a shared ClusterQueue in a cohort", func() {
   488  		var (
   489  			aStandardCQ, aBestEffortCQ, bStandardCQ, bBestEffortCQ, sharedCQ *kueue.ClusterQueue
   490  			aStandardLQ, aBestEffortLQ, bStandardLQ, bBestEffortLQ           *kueue.LocalQueue
   491  			oneFlavor, fallbackFlavor                                        *kueue.ResourceFlavor
   492  		)
   493  
   494  		ginkgo.BeforeEach(func() {
   495  			oneFlavor = testing.MakeResourceFlavor("one").Obj()
   496  			gomega.Expect(k8sClient.Create(ctx, oneFlavor)).To(gomega.Succeed())
   497  
   498  			fallbackFlavor = testing.MakeResourceFlavor("fallback").Obj()
   499  			gomega.Expect(k8sClient.Create(ctx, fallbackFlavor)).To(gomega.Succeed())
   500  
   501  			aStandardCQ = testing.MakeClusterQueue("a-standard-cq").
   502  				Cohort("all").
   503  				ResourceGroup(
   504  					*testing.MakeFlavorQuotas("one").Resource(corev1.ResourceCPU, "1", "10").Obj(),
   505  					*testing.MakeFlavorQuotas("fallback").Resource(corev1.ResourceCPU, "10", "0").Obj(),
   506  				).
   507  				FlavorFungibility(kueue.FlavorFungibility{
   508  					WhenCanBorrow:  kueue.Borrow,
   509  					WhenCanPreempt: kueue.Preempt,
   510  				}).
   511  				Preemption(kueue.ClusterQueuePreemption{
   512  					ReclaimWithinCohort: kueue.PreemptionPolicyLowerPriority,
   513  					BorrowWithinCohort: &kueue.BorrowWithinCohort{
   514  						Policy:               kueue.BorrowWithinCohortPolicyLowerPriority,
   515  						MaxPriorityThreshold: ptr.To(midPriority),
   516  					},
   517  				}).
   518  				Obj()
   519  			gomega.Expect(k8sClient.Create(ctx, aStandardCQ)).To(gomega.Succeed())
   520  			aStandardLQ = testing.MakeLocalQueue("a-standard-lq", ns.Name).ClusterQueue(aStandardCQ.Name).Obj()
   521  			gomega.Expect(k8sClient.Create(ctx, aStandardLQ)).To(gomega.Succeed())
   522  
   523  			aBestEffortCQ = testing.MakeClusterQueue("a-best-effort-cq").
   524  				Cohort("all").
   525  				ResourceGroup(
   526  					*testing.MakeFlavorQuotas("one").Resource(corev1.ResourceCPU, "1", "10").Obj(),
   527  					*testing.MakeFlavorQuotas("fallback").Resource(corev1.ResourceCPU, "10", "0").Obj(),
   528  				).
   529  				FlavorFungibility(kueue.FlavorFungibility{
   530  					WhenCanBorrow:  kueue.Borrow,
   531  					WhenCanPreempt: kueue.Preempt,
   532  				}).
   533  				Preemption(kueue.ClusterQueuePreemption{
   534  					ReclaimWithinCohort: kueue.PreemptionPolicyLowerPriority,
   535  					BorrowWithinCohort: &kueue.BorrowWithinCohort{
   536  						Policy: kueue.BorrowWithinCohortPolicyLowerPriority,
   537  					},
   538  				}).
   539  				Obj()
   540  			gomega.Expect(k8sClient.Create(ctx, aBestEffortCQ)).To(gomega.Succeed())
   541  			aBestEffortLQ = testing.MakeLocalQueue("a-best-effort-lq", ns.Name).ClusterQueue(aBestEffortCQ.Name).Obj()
   542  			gomega.Expect(k8sClient.Create(ctx, aBestEffortLQ)).To(gomega.Succeed())
   543  
   544  			bBestEffortCQ = testing.MakeClusterQueue("b-best-effort-cq").
   545  				Cohort("all").
   546  				ResourceGroup(
   547  					*testing.MakeFlavorQuotas("one").Resource(corev1.ResourceCPU, "1", "10").Obj(),
   548  					*testing.MakeFlavorQuotas("fallback").Resource(corev1.ResourceCPU, "10", "0").Obj(),
   549  				).
   550  				FlavorFungibility(kueue.FlavorFungibility{
   551  					WhenCanBorrow:  kueue.Borrow,
   552  					WhenCanPreempt: kueue.Preempt,
   553  				}).
   554  				Preemption(kueue.ClusterQueuePreemption{
   555  					ReclaimWithinCohort: kueue.PreemptionPolicyLowerPriority,
   556  					BorrowWithinCohort: &kueue.BorrowWithinCohort{
   557  						Policy: kueue.BorrowWithinCohortPolicyLowerPriority,
   558  					},
   559  				}).
   560  				Obj()
   561  			gomega.Expect(k8sClient.Create(ctx, bBestEffortCQ)).To(gomega.Succeed())
   562  			bBestEffortLQ = testing.MakeLocalQueue("b-best-effort-lq", ns.Name).ClusterQueue(bBestEffortCQ.Name).Obj()
   563  			gomega.Expect(k8sClient.Create(ctx, bBestEffortLQ)).To(gomega.Succeed())
   564  
   565  			bStandardCQ = testing.MakeClusterQueue("b-standard-cq").
   566  				Cohort("all").
   567  				ResourceGroup(
   568  					*testing.MakeFlavorQuotas("one").Resource(corev1.ResourceCPU, "1", "10").Obj(),
   569  					*testing.MakeFlavorQuotas("fallback").Resource(corev1.ResourceCPU, "10", "0").Obj(),
   570  				).
   571  				FlavorFungibility(kueue.FlavorFungibility{
   572  					WhenCanBorrow:  kueue.Borrow,
   573  					WhenCanPreempt: kueue.Preempt,
   574  				}).
   575  				Preemption(kueue.ClusterQueuePreemption{
   576  					ReclaimWithinCohort: kueue.PreemptionPolicyLowerPriority,
   577  					BorrowWithinCohort: &kueue.BorrowWithinCohort{
   578  						Policy:               kueue.BorrowWithinCohortPolicyLowerPriority,
   579  						MaxPriorityThreshold: ptr.To(midPriority),
   580  					},
   581  				}).
   582  				Obj()
   583  			gomega.Expect(k8sClient.Create(ctx, bStandardCQ)).To(gomega.Succeed())
   584  			bStandardLQ = testing.MakeLocalQueue("b-standard-lq", ns.Name).ClusterQueue(bStandardCQ.Name).Obj()
   585  			gomega.Expect(k8sClient.Create(ctx, bStandardLQ)).To(gomega.Succeed())
   586  
   587  			sharedCQ = testing.MakeClusterQueue("shared-cq").
   588  				Cohort("all").
   589  				ResourceGroup(*testing.MakeFlavorQuotas("one").Resource(corev1.ResourceCPU, "10").Obj()).
   590  				Obj()
   591  			gomega.Expect(k8sClient.Create(ctx, sharedCQ)).To(gomega.Succeed())
   592  		})
   593  
   594  		ginkgo.AfterEach(func() {
   595  			gomega.Expect(util.DeleteWorkloadsInNamespace(ctx, k8sClient, ns)).To(gomega.Succeed())
   596  			util.ExpectClusterQueueToBeDeleted(ctx, k8sClient, aStandardCQ, true)
   597  			util.ExpectClusterQueueToBeDeleted(ctx, k8sClient, aBestEffortCQ, true)
   598  			util.ExpectClusterQueueToBeDeleted(ctx, k8sClient, bBestEffortCQ, true)
   599  			util.ExpectClusterQueueToBeDeleted(ctx, k8sClient, bStandardCQ, true)
   600  			util.ExpectClusterQueueToBeDeleted(ctx, k8sClient, sharedCQ, true)
   601  			util.ExpectResourceFlavorToBeDeleted(ctx, k8sClient, oneFlavor, true)
   602  			util.ExpectResourceFlavorToBeDeleted(ctx, k8sClient, fallbackFlavor, true)
   603  		})
   604  
   605  		ginkgo.It("should allow preempting workloads while borrowing", func() {
   606  			ginkgo.By("Create a low priority workload which requires borrowing")
   607  			aBestEffortLowWl := testing.MakeWorkload("a-best-effort-low", ns.Name).
   608  				Queue(aBestEffortLQ.Name).
   609  				Priority(lowPriority).
   610  				Request(corev1.ResourceCPU, "5").
   611  				Obj()
   612  			gomega.Expect(k8sClient.Create(ctx, aBestEffortLowWl)).To(gomega.Succeed())
   613  
   614  			ginkgo.By("Await for the a-best-effort-low workload to be admitted")
   615  			util.ExpectWorkloadToBeAdmittedAs(ctx, k8sClient, aBestEffortLowWl,
   616  				testing.MakeAdmission(aBestEffortCQ.Name).Assignment(corev1.ResourceCPU, "one", "5").Obj(),
   617  			)
   618  
   619  			ginkgo.By("Create a low priority workload which is not borrowing")
   620  			bBestEffortLowWl := testing.MakeWorkload("b-best-effort-low", ns.Name).
   621  				Queue(bBestEffortLQ.Name).
   622  				Priority(lowPriority).
   623  				Request(corev1.ResourceCPU, "1").
   624  				Obj()
   625  			gomega.Expect(k8sClient.Create(ctx, bBestEffortLowWl)).To(gomega.Succeed())
   626  
   627  			ginkgo.By("Await for the b-best-effort-low workload to be admitted")
   628  			util.ExpectWorkloadToBeAdmittedAs(ctx, k8sClient, bBestEffortLowWl,
   629  				testing.MakeAdmission(bBestEffortCQ.Name).Assignment(corev1.ResourceCPU, "one", "1").Obj(),
   630  			)
   631  
   632  			ginkgo.By("Create a high priority workload (above MaxPriorityThreshold) which requires borrowing")
   633  			bStandardWl := testing.MakeWorkload("b-standard-high", ns.Name).
   634  				Queue(bStandardLQ.Name).
   635  				Priority(highPriority).
   636  				Request(corev1.ResourceCPU, "5").
   637  				Obj()
   638  			gomega.Expect(k8sClient.Create(ctx, bStandardWl)).To(gomega.Succeed())
   639  
   640  			ginkgo.By("Await for the b-standard-high workload to be admitted")
   641  			util.ExpectWorkloadToBeAdmittedAs(ctx, k8sClient, bStandardWl,
   642  				testing.MakeAdmission(bStandardCQ.Name).Assignment(corev1.ResourceCPU, "one", "5").Obj(),
   643  			)
   644  
   645  			ginkgo.By("Create the a-standard-very-high workload")
   646  			aStandardVeryHighWl := testing.MakeWorkload("a-standard-very-high", ns.Name).
   647  				Queue(aStandardLQ.Name).
   648  				Priority(veryHighPriority).
   649  				Request(corev1.ResourceCPU, "7").
   650  				Obj()
   651  			gomega.Expect(k8sClient.Create(ctx, aStandardVeryHighWl)).To(gomega.Succeed())
   652  
   653  			ginkgo.By("Finish eviction fo the a-best-effort-low workload")
   654  			util.FinishEvictionForWorkloads(ctx, k8sClient, aBestEffortLowWl)
   655  
   656  			ginkgo.By("Verify the a-standard-very-high workload is admitted")
   657  			util.ExpectWorkloadToBeAdmittedAs(ctx, k8sClient, aStandardVeryHighWl,
   658  				testing.MakeAdmission(aStandardCQ.Name).Assignment(corev1.ResourceCPU, "one", "7").Obj(),
   659  			)
   660  
   661  			ginkgo.By("Verify the a-best-effort-low workload is re-admitted, but using flavor 2")
   662  			util.ExpectWorkloadToBeAdmittedAs(ctx, k8sClient, aBestEffortLowWl,
   663  				testing.MakeAdmission(aBestEffortCQ.Name).Assignment(corev1.ResourceCPU, "fallback", "5").Obj(),
   664  			)
   665  
   666  			ginkgo.By("Verify the b-standard-high workload remains admitted")
   667  			util.ExpectWorkloadToBeAdmittedAs(ctx, k8sClient, bStandardWl,
   668  				testing.MakeAdmission(bStandardCQ.Name).Assignment(corev1.ResourceCPU, "one", "5").Obj(),
   669  			)
   670  
   671  			ginkgo.By("Verify for the b-best-effort-low workload remains admitted")
   672  			util.ExpectWorkloadToBeAdmittedAs(ctx, k8sClient, bBestEffortLowWl,
   673  				testing.MakeAdmission(bBestEffortCQ.Name).Assignment(corev1.ResourceCPU, "one", "1").Obj(),
   674  			)
   675  
   676  		})
   677  	})
   678  
   679  	ginkgo.Context("When lending limit enabled", func() {
   680  		var (
   681  			prodCQ *kueue.ClusterQueue
   682  			devCQ  *kueue.ClusterQueue
   683  		)
   684  
   685  		ginkgo.BeforeEach(func() {
   686  			_ = features.SetEnable(features.LendingLimit, true)
   687  		})
   688  
   689  		ginkgo.AfterEach(func() {
   690  			_ = features.SetEnable(features.LendingLimit, false)
   691  			gomega.Expect(util.DeleteNamespace(ctx, k8sClient, ns)).To(gomega.Succeed())
   692  			util.ExpectClusterQueueToBeDeleted(ctx, k8sClient, prodCQ, true)
   693  			if devCQ != nil {
   694  				util.ExpectClusterQueueToBeDeleted(ctx, k8sClient, devCQ, true)
   695  			}
   696  		})
   697  
   698  		ginkgo.It("Should be able to preempt when lending limit enabled", func() {
   699  			prodCQ = testing.MakeClusterQueue("prod-cq").
   700  				Cohort("all").
   701  				ResourceGroup(
   702  					*testing.MakeFlavorQuotas("alpha").Resource(corev1.ResourceCPU, "5", "", "4").Obj(),
   703  				).
   704  				Preemption(kueue.ClusterQueuePreemption{
   705  					ReclaimWithinCohort: kueue.PreemptionPolicyAny,
   706  				}).
   707  				Obj()
   708  			gomega.Expect(k8sClient.Create(ctx, prodCQ)).Should(gomega.Succeed())
   709  
   710  			devCQ = testing.MakeClusterQueue("dev-cq").
   711  				Cohort("all").
   712  				ResourceGroup(*testing.MakeFlavorQuotas("alpha").Resource(corev1.ResourceCPU, "5").Obj()).
   713  				Obj()
   714  			gomega.Expect(k8sClient.Create(ctx, devCQ)).Should(gomega.Succeed())
   715  
   716  			prodQueue := testing.MakeLocalQueue("prod-queue", ns.Name).ClusterQueue(prodCQ.Name).Obj()
   717  			gomega.Expect(k8sClient.Create(ctx, prodQueue)).Should(gomega.Succeed())
   718  
   719  			devQueue := testing.MakeLocalQueue("dev-queue", ns.Name).ClusterQueue(devCQ.Name).Obj()
   720  			gomega.Expect(k8sClient.Create(ctx, devQueue)).Should(gomega.Succeed())
   721  
   722  			ginkgo.By("Creating two workloads")
   723  			wl1 := testing.MakeWorkload("wl-1", ns.Name).Priority(0).Queue(devQueue.Name).Request(corev1.ResourceCPU, "4").Obj()
   724  			wl2 := testing.MakeWorkload("wl-2", ns.Name).Priority(1).Queue(devQueue.Name).Request(corev1.ResourceCPU, "5").Obj()
   725  			gomega.Expect(k8sClient.Create(ctx, wl1)).Should(gomega.Succeed())
   726  			gomega.Expect(k8sClient.Create(ctx, wl2)).Should(gomega.Succeed())
   727  			util.ExpectWorkloadsToBeAdmitted(ctx, k8sClient, wl1, wl2)
   728  
   729  			ginkgo.By("Creating another workload")
   730  			wl3 := testing.MakeWorkload("wl-3", ns.Name).Queue(prodQueue.Name).Request(corev1.ResourceCPU, "4").Obj()
   731  			gomega.Expect(k8sClient.Create(ctx, wl3)).Should(gomega.Succeed())
   732  			util.ExpectWorkloadsToBePreempted(ctx, k8sClient, wl1)
   733  
   734  			util.FinishEvictionForWorkloads(ctx, k8sClient, wl1)
   735  
   736  			util.ExpectWorkloadToBeAdmittedAs(ctx, k8sClient, wl3,
   737  				testing.MakeAdmission(prodCQ.Name).Assignment(corev1.ResourceCPU, "alpha", "4").Obj())
   738  		})
   739  	})
   740  
   741  })