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 })