sigs.k8s.io/kueue@v0.6.2/pkg/cache/cache_test.go (about) 1 /* 2 Copyright 2022 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 cache 18 19 import ( 20 "context" 21 "fmt" 22 "testing" 23 24 "github.com/google/go-cmp/cmp" 25 "github.com/google/go-cmp/cmp/cmpopts" 26 corev1 "k8s.io/api/core/v1" 27 apimeta "k8s.io/apimachinery/pkg/api/meta" 28 "k8s.io/apimachinery/pkg/api/resource" 29 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 30 "k8s.io/apimachinery/pkg/labels" 31 "k8s.io/apimachinery/pkg/util/sets" 32 "k8s.io/utils/ptr" 33 ctrl "sigs.k8s.io/controller-runtime" 34 "sigs.k8s.io/controller-runtime/pkg/client" 35 36 kueue "sigs.k8s.io/kueue/apis/kueue/v1beta1" 37 "sigs.k8s.io/kueue/pkg/features" 38 utiltesting "sigs.k8s.io/kueue/pkg/util/testing" 39 "sigs.k8s.io/kueue/pkg/workload" 40 ) 41 42 func TestCacheClusterQueueOperations(t *testing.T) { 43 initialClusterQueues := []kueue.ClusterQueue{ 44 *utiltesting.MakeClusterQueue("a"). 45 ResourceGroup( 46 *utiltesting.MakeFlavorQuotas("default"). 47 Resource(corev1.ResourceCPU, "10", "10").Obj()). 48 Cohort("one"). 49 NamespaceSelector(nil). 50 Obj(), 51 *utiltesting.MakeClusterQueue("b"). 52 ResourceGroup( 53 *utiltesting.MakeFlavorQuotas("default"). 54 Resource(corev1.ResourceCPU, "15").Obj()). 55 Cohort("one"). 56 NamespaceSelector(nil). 57 Obj(), 58 *utiltesting.MakeClusterQueue("c"). 59 Cohort("two"). 60 NamespaceSelector(nil). 61 Obj(), 62 *utiltesting.MakeClusterQueue("d"). 63 NamespaceSelector(nil). 64 Obj(), 65 *utiltesting.MakeClusterQueue("e"). 66 ResourceGroup( 67 *utiltesting.MakeFlavorQuotas("nonexistent-flavor"). 68 Resource(corev1.ResourceCPU, "15").Obj()). 69 Cohort("two"). 70 NamespaceSelector(nil). 71 Obj(), 72 *utiltesting.MakeClusterQueue("f"). 73 Cohort("two"). 74 NamespaceSelector(nil). 75 FlavorFungibility(kueue.FlavorFungibility{ 76 WhenCanBorrow: kueue.TryNextFlavor, 77 }). 78 Obj(), 79 } 80 setup := func(cache *Cache) error { 81 cache.AddOrUpdateResourceFlavor( 82 utiltesting.MakeResourceFlavor("default"). 83 Label("cpuType", "default"). 84 Obj()) 85 for _, c := range initialClusterQueues { 86 if err := cache.AddClusterQueue(context.Background(), &c); err != nil { 87 return fmt.Errorf("Failed adding ClusterQueue: %w", err) 88 } 89 } 90 return nil 91 } 92 cases := []struct { 93 name string 94 operation func(*Cache) error 95 clientObbjects []client.Object 96 wantClusterQueues map[string]*ClusterQueue 97 wantCohorts map[string]sets.Set[string] 98 enableLendingLimit bool 99 }{ 100 { 101 name: "add", 102 operation: func(cache *Cache) error { 103 return setup(cache) 104 }, 105 wantClusterQueues: map[string]*ClusterQueue{ 106 "a": { 107 Name: "a", 108 AllocatableResourceGeneration: 1, 109 ResourceGroups: []ResourceGroup{{ 110 CoveredResources: sets.New(corev1.ResourceCPU), 111 Flavors: []FlavorQuotas{{ 112 Name: "default", 113 Resources: map[corev1.ResourceName]*ResourceQuota{ 114 corev1.ResourceCPU: { 115 Nominal: 10_000, 116 BorrowingLimit: ptr.To[int64](10_000), 117 }, 118 }, 119 }}, 120 LabelKeys: sets.New("cpuType"), 121 }}, 122 NamespaceSelector: labels.Nothing(), 123 FlavorFungibility: defaultFlavorFungibility, 124 Usage: FlavorResourceQuantities{ 125 "default": {corev1.ResourceCPU: 0}, 126 }, 127 AdmittedUsage: FlavorResourceQuantities{ 128 "default": {corev1.ResourceCPU: 0}, 129 }, 130 Status: active, 131 Preemption: defaultPreemption, 132 }, 133 "b": { 134 Name: "b", 135 AllocatableResourceGeneration: 1, 136 ResourceGroups: []ResourceGroup{{ 137 CoveredResources: sets.New(corev1.ResourceCPU), 138 Flavors: []FlavorQuotas{{ 139 Name: "default", 140 Resources: map[corev1.ResourceName]*ResourceQuota{ 141 corev1.ResourceCPU: { 142 Nominal: 15_000, 143 }, 144 }, 145 }}, 146 LabelKeys: sets.New("cpuType"), 147 }}, 148 NamespaceSelector: labels.Nothing(), 149 FlavorFungibility: defaultFlavorFungibility, 150 Usage: FlavorResourceQuantities{ 151 "default": {corev1.ResourceCPU: 0}, 152 }, 153 AdmittedUsage: FlavorResourceQuantities{ 154 "default": {corev1.ResourceCPU: 0}, 155 }, 156 Status: active, 157 Preemption: defaultPreemption, 158 }, 159 "c": { 160 Name: "c", 161 AllocatableResourceGeneration: 1, 162 ResourceGroups: []ResourceGroup{}, 163 NamespaceSelector: labels.Nothing(), 164 FlavorFungibility: defaultFlavorFungibility, 165 Usage: FlavorResourceQuantities{}, 166 Status: active, 167 Preemption: defaultPreemption, 168 }, 169 "d": { 170 Name: "d", 171 AllocatableResourceGeneration: 1, 172 ResourceGroups: []ResourceGroup{}, 173 NamespaceSelector: labels.Nothing(), 174 FlavorFungibility: defaultFlavorFungibility, 175 Usage: FlavorResourceQuantities{}, 176 Status: active, 177 Preemption: defaultPreemption, 178 }, 179 "e": { 180 Name: "e", 181 AllocatableResourceGeneration: 1, 182 ResourceGroups: []ResourceGroup{{ 183 CoveredResources: sets.New(corev1.ResourceCPU), 184 Flavors: []FlavorQuotas{{ 185 Name: "nonexistent-flavor", 186 Resources: map[corev1.ResourceName]*ResourceQuota{ 187 corev1.ResourceCPU: { 188 Nominal: 15_000, 189 }, 190 }, 191 }}, 192 }}, 193 NamespaceSelector: labels.Nothing(), 194 FlavorFungibility: defaultFlavorFungibility, 195 Usage: FlavorResourceQuantities{ 196 "nonexistent-flavor": {corev1.ResourceCPU: 0}, 197 }, 198 AdmittedUsage: FlavorResourceQuantities{ 199 "nonexistent-flavor": {corev1.ResourceCPU: 0}, 200 }, 201 Status: pending, 202 Preemption: defaultPreemption, 203 }, 204 "f": { 205 Name: "f", 206 AllocatableResourceGeneration: 1, 207 ResourceGroups: []ResourceGroup{}, 208 NamespaceSelector: labels.Nothing(), 209 Usage: FlavorResourceQuantities{}, 210 Status: active, 211 Preemption: defaultPreemption, 212 FlavorFungibility: kueue.FlavorFungibility{ 213 WhenCanBorrow: kueue.TryNextFlavor, 214 WhenCanPreempt: kueue.TryNextFlavor, 215 }, 216 }, 217 }, 218 wantCohorts: map[string]sets.Set[string]{ 219 "one": sets.New("a", "b"), 220 "two": sets.New("c", "e", "f"), 221 }, 222 }, 223 { 224 name: "add ClusterQueue with preemption policies", 225 operation: func(cache *Cache) error { 226 cq := utiltesting.MakeClusterQueue("foo").Preemption(kueue.ClusterQueuePreemption{ 227 ReclaimWithinCohort: kueue.PreemptionPolicyLowerPriority, 228 WithinClusterQueue: kueue.PreemptionPolicyLowerPriority, 229 }).Obj() 230 if err := cache.AddClusterQueue(context.Background(), cq); err != nil { 231 return fmt.Errorf("Failed to add ClusterQueue: %w", err) 232 } 233 return nil 234 }, 235 wantClusterQueues: map[string]*ClusterQueue{ 236 "foo": { 237 Name: "foo", 238 AllocatableResourceGeneration: 1, 239 NamespaceSelector: labels.Everything(), 240 Status: active, 241 FlavorFungibility: defaultFlavorFungibility, 242 Preemption: kueue.ClusterQueuePreemption{ 243 ReclaimWithinCohort: kueue.PreemptionPolicyLowerPriority, 244 WithinClusterQueue: kueue.PreemptionPolicyLowerPriority, 245 }, 246 }, 247 }, 248 }, 249 { 250 name: "add flavors after queue capacities", 251 operation: func(cache *Cache) error { 252 for _, c := range initialClusterQueues { 253 if err := cache.AddClusterQueue(context.Background(), &c); err != nil { 254 return fmt.Errorf("Failed adding ClusterQueue: %w", err) 255 } 256 } 257 cache.AddOrUpdateResourceFlavor( 258 utiltesting.MakeResourceFlavor("default"). 259 Label("cpuType", "default"). 260 Obj()) 261 return nil 262 }, 263 wantClusterQueues: map[string]*ClusterQueue{ 264 "a": { 265 Name: "a", 266 AllocatableResourceGeneration: 1, 267 ResourceGroups: []ResourceGroup{{ 268 CoveredResources: sets.New(corev1.ResourceCPU), 269 Flavors: []FlavorQuotas{{ 270 Name: "default", 271 Resources: map[corev1.ResourceName]*ResourceQuota{ 272 corev1.ResourceCPU: { 273 Nominal: 10_000, 274 BorrowingLimit: ptr.To[int64](10_000), 275 }, 276 }, 277 }}, 278 LabelKeys: sets.New("cpuType"), 279 }}, 280 FlavorFungibility: defaultFlavorFungibility, 281 NamespaceSelector: labels.Nothing(), 282 Usage: FlavorResourceQuantities{ 283 "default": {corev1.ResourceCPU: 0}, 284 }, 285 AdmittedUsage: FlavorResourceQuantities{ 286 "default": {corev1.ResourceCPU: 0}, 287 }, 288 Status: active, 289 Preemption: defaultPreemption, 290 }, 291 "b": { 292 Name: "b", 293 AllocatableResourceGeneration: 1, 294 ResourceGroups: []ResourceGroup{{ 295 CoveredResources: sets.New(corev1.ResourceCPU), 296 Flavors: []FlavorQuotas{{ 297 Name: "default", 298 Resources: map[corev1.ResourceName]*ResourceQuota{ 299 corev1.ResourceCPU: { 300 Nominal: 15_000, 301 }, 302 }, 303 }}, 304 LabelKeys: sets.New("cpuType"), 305 }}, 306 FlavorFungibility: defaultFlavorFungibility, 307 NamespaceSelector: labels.Nothing(), 308 Usage: FlavorResourceQuantities{ 309 "default": {corev1.ResourceCPU: 0}, 310 }, 311 AdmittedUsage: FlavorResourceQuantities{ 312 "default": {corev1.ResourceCPU: 0}, 313 }, 314 Status: active, 315 Preemption: defaultPreemption, 316 }, 317 "c": { 318 Name: "c", 319 AllocatableResourceGeneration: 1, 320 ResourceGroups: []ResourceGroup{}, 321 NamespaceSelector: labels.Nothing(), 322 FlavorFungibility: defaultFlavorFungibility, 323 Usage: FlavorResourceQuantities{}, 324 Status: active, 325 Preemption: defaultPreemption, 326 }, 327 "d": { 328 Name: "d", 329 AllocatableResourceGeneration: 1, 330 ResourceGroups: []ResourceGroup{}, 331 NamespaceSelector: labels.Nothing(), 332 FlavorFungibility: defaultFlavorFungibility, 333 Usage: FlavorResourceQuantities{}, 334 Status: active, 335 Preemption: defaultPreemption, 336 }, 337 "e": { 338 Name: "e", 339 AllocatableResourceGeneration: 1, 340 ResourceGroups: []ResourceGroup{{ 341 CoveredResources: sets.New(corev1.ResourceCPU), 342 Flavors: []FlavorQuotas{ 343 { 344 Name: "nonexistent-flavor", 345 Resources: map[corev1.ResourceName]*ResourceQuota{ 346 corev1.ResourceCPU: { 347 Nominal: 15_000, 348 }, 349 }, 350 }, 351 }, 352 }}, 353 NamespaceSelector: labels.Nothing(), 354 FlavorFungibility: defaultFlavorFungibility, 355 Usage: FlavorResourceQuantities{ 356 "nonexistent-flavor": {corev1.ResourceCPU: 0}, 357 }, 358 AdmittedUsage: FlavorResourceQuantities{ 359 "nonexistent-flavor": {corev1.ResourceCPU: 0}, 360 }, 361 Status: pending, 362 Preemption: defaultPreemption, 363 }, 364 "f": { 365 Name: "f", 366 AllocatableResourceGeneration: 1, 367 ResourceGroups: []ResourceGroup{}, 368 NamespaceSelector: labels.Nothing(), 369 Usage: FlavorResourceQuantities{}, 370 Status: active, 371 Preemption: defaultPreemption, 372 FlavorFungibility: kueue.FlavorFungibility{ 373 WhenCanBorrow: kueue.TryNextFlavor, 374 WhenCanPreempt: kueue.TryNextFlavor, 375 }, 376 }, 377 }, 378 wantCohorts: map[string]sets.Set[string]{ 379 "one": sets.New("a", "b"), 380 "two": sets.New("c", "e", "f"), 381 }, 382 }, 383 { 384 name: "update", 385 operation: func(cache *Cache) error { 386 err := setup(cache) 387 if err != nil { 388 return err 389 } 390 clusterQueues := []kueue.ClusterQueue{ 391 *utiltesting.MakeClusterQueue("a"). 392 ResourceGroup( 393 *utiltesting.MakeFlavorQuotas("default"). 394 Resource(corev1.ResourceCPU, "5", "5").Obj()). 395 Cohort("two"). 396 NamespaceSelector(nil). 397 Obj(), 398 *utiltesting.MakeClusterQueue("b").Cohort("one").Obj(), // remove the only resource group and set a namespace selector. 399 *utiltesting.MakeClusterQueue("e"). 400 ResourceGroup( 401 *utiltesting.MakeFlavorQuotas("default"). 402 Resource(corev1.ResourceCPU, "5", "5", "4"). 403 Obj()). 404 Cohort("two"). 405 NamespaceSelector(nil). 406 Obj(), 407 } 408 for _, c := range clusterQueues { 409 if err := cache.UpdateClusterQueue(&c); err != nil { 410 return fmt.Errorf("Failed updating ClusterQueue: %w", err) 411 } 412 } 413 cache.AddOrUpdateResourceFlavor( 414 utiltesting.MakeResourceFlavor("default"). 415 Label("cpuType", "default"). 416 Label("region", "central"). 417 Obj()) 418 return nil 419 }, 420 wantClusterQueues: map[string]*ClusterQueue{ 421 "a": { 422 Name: "a", 423 AllocatableResourceGeneration: 2, 424 ResourceGroups: []ResourceGroup{{ 425 CoveredResources: sets.New(corev1.ResourceCPU), 426 Flavors: []FlavorQuotas{{ 427 Name: "default", 428 Resources: map[corev1.ResourceName]*ResourceQuota{ 429 corev1.ResourceCPU: { 430 Nominal: 5_000, 431 BorrowingLimit: ptr.To[int64](5_000), 432 }, 433 }, 434 }}, 435 LabelKeys: sets.New("cpuType", "region"), 436 }}, 437 NamespaceSelector: labels.Nothing(), 438 FlavorFungibility: defaultFlavorFungibility, 439 Usage: FlavorResourceQuantities{ 440 "default": {corev1.ResourceCPU: 0}, 441 }, 442 AdmittedUsage: FlavorResourceQuantities{ 443 "default": {corev1.ResourceCPU: 0}, 444 }, 445 Status: active, 446 Preemption: defaultPreemption, 447 }, 448 "b": { 449 Name: "b", 450 AllocatableResourceGeneration: 2, 451 ResourceGroups: []ResourceGroup{}, 452 NamespaceSelector: labels.Everything(), 453 FlavorFungibility: defaultFlavorFungibility, 454 Usage: FlavorResourceQuantities{}, 455 Status: active, 456 Preemption: defaultPreemption, 457 }, 458 "c": { 459 Name: "c", 460 AllocatableResourceGeneration: 1, 461 ResourceGroups: []ResourceGroup{}, 462 NamespaceSelector: labels.Nothing(), 463 FlavorFungibility: defaultFlavorFungibility, 464 Usage: FlavorResourceQuantities{}, 465 Status: active, 466 Preemption: defaultPreemption, 467 }, 468 "d": { 469 Name: "d", 470 AllocatableResourceGeneration: 1, 471 ResourceGroups: []ResourceGroup{}, 472 NamespaceSelector: labels.Nothing(), 473 FlavorFungibility: defaultFlavorFungibility, 474 Usage: FlavorResourceQuantities{}, 475 Status: active, 476 Preemption: defaultPreemption, 477 }, 478 "e": { 479 Name: "e", 480 AllocatableResourceGeneration: 2, 481 ResourceGroups: []ResourceGroup{{ 482 CoveredResources: sets.New(corev1.ResourceCPU), 483 Flavors: []FlavorQuotas{{ 484 Name: "default", 485 Resources: map[corev1.ResourceName]*ResourceQuota{ 486 corev1.ResourceCPU: { 487 Nominal: 5_000, 488 BorrowingLimit: ptr.To[int64](5_000), 489 LendingLimit: ptr.To[int64](4_000), 490 }, 491 }}, 492 }, 493 LabelKeys: sets.New("cpuType", "region"), 494 }}, 495 GuaranteedQuota: FlavorResourceQuantities{ 496 "default": { 497 "cpu": 1_000, 498 }, 499 }, 500 NamespaceSelector: labels.Nothing(), 501 FlavorFungibility: defaultFlavorFungibility, 502 Usage: FlavorResourceQuantities{ 503 "default": {corev1.ResourceCPU: 0}, 504 }, 505 AdmittedUsage: FlavorResourceQuantities{ 506 "default": {corev1.ResourceCPU: 0}, 507 }, 508 Status: active, 509 Preemption: defaultPreemption, 510 }, 511 "f": { 512 Name: "f", 513 AllocatableResourceGeneration: 1, 514 ResourceGroups: []ResourceGroup{}, 515 NamespaceSelector: labels.Nothing(), 516 Usage: FlavorResourceQuantities{}, 517 Status: active, 518 Preemption: defaultPreemption, 519 FlavorFungibility: kueue.FlavorFungibility{ 520 WhenCanBorrow: kueue.TryNextFlavor, 521 WhenCanPreempt: kueue.TryNextFlavor, 522 }, 523 }, 524 }, 525 wantCohorts: map[string]sets.Set[string]{ 526 "one": sets.New("b"), 527 "two": sets.New("a", "c", "e", "f"), 528 }, 529 enableLendingLimit: true, 530 }, 531 { 532 name: "delete", 533 operation: func(cache *Cache) error { 534 err := setup(cache) 535 if err != nil { 536 return err 537 } 538 clusterQueues := []kueue.ClusterQueue{ 539 {ObjectMeta: metav1.ObjectMeta{Name: "a"}}, 540 {ObjectMeta: metav1.ObjectMeta{Name: "d"}}, 541 } 542 for _, c := range clusterQueues { 543 cache.DeleteClusterQueue(&c) 544 } 545 return nil 546 }, 547 wantClusterQueues: map[string]*ClusterQueue{ 548 "b": { 549 Name: "b", 550 AllocatableResourceGeneration: 1, 551 ResourceGroups: []ResourceGroup{{ 552 CoveredResources: sets.New(corev1.ResourceCPU), 553 Flavors: []FlavorQuotas{{ 554 Name: "default", 555 Resources: map[corev1.ResourceName]*ResourceQuota{ 556 corev1.ResourceCPU: {Nominal: 15_000}, 557 }, 558 }}, 559 LabelKeys: sets.New("cpuType"), 560 }}, 561 NamespaceSelector: labels.Nothing(), 562 FlavorFungibility: defaultFlavorFungibility, 563 Usage: FlavorResourceQuantities{ 564 "default": {corev1.ResourceCPU: 0}, 565 }, 566 AdmittedUsage: FlavorResourceQuantities{ 567 "default": {corev1.ResourceCPU: 0}, 568 }, 569 Status: active, 570 Preemption: defaultPreemption, 571 }, 572 "c": { 573 Name: "c", 574 AllocatableResourceGeneration: 1, 575 ResourceGroups: []ResourceGroup{}, 576 NamespaceSelector: labels.Nothing(), 577 FlavorFungibility: defaultFlavorFungibility, 578 Usage: FlavorResourceQuantities{}, 579 Status: active, 580 Preemption: defaultPreemption, 581 }, 582 "e": { 583 Name: "e", 584 AllocatableResourceGeneration: 1, 585 ResourceGroups: []ResourceGroup{{ 586 CoveredResources: sets.New(corev1.ResourceCPU), 587 Flavors: []FlavorQuotas{ 588 { 589 Name: "nonexistent-flavor", 590 Resources: map[corev1.ResourceName]*ResourceQuota{ 591 corev1.ResourceCPU: { 592 Nominal: 15_000, 593 }, 594 }, 595 }, 596 }, 597 }}, 598 NamespaceSelector: labels.Nothing(), 599 FlavorFungibility: defaultFlavorFungibility, 600 Usage: FlavorResourceQuantities{ 601 "nonexistent-flavor": {corev1.ResourceCPU: 0}, 602 }, 603 AdmittedUsage: FlavorResourceQuantities{ 604 "nonexistent-flavor": {corev1.ResourceCPU: 0}, 605 }, 606 Status: pending, 607 Preemption: defaultPreemption, 608 }, 609 "f": { 610 Name: "f", 611 AllocatableResourceGeneration: 1, 612 ResourceGroups: []ResourceGroup{}, 613 NamespaceSelector: labels.Nothing(), 614 Usage: FlavorResourceQuantities{}, 615 Status: active, 616 Preemption: defaultPreemption, 617 FlavorFungibility: kueue.FlavorFungibility{ 618 WhenCanBorrow: kueue.TryNextFlavor, 619 WhenCanPreempt: kueue.TryNextFlavor, 620 }, 621 }, 622 }, 623 wantCohorts: map[string]sets.Set[string]{ 624 "one": sets.New("b"), 625 "two": sets.New("c", "e", "f"), 626 }, 627 }, 628 { 629 name: "add resource flavors", 630 operation: func(cache *Cache) error { 631 err := setup(cache) 632 if err != nil { 633 return err 634 } 635 cache.AddOrUpdateResourceFlavor(&kueue.ResourceFlavor{ 636 ObjectMeta: metav1.ObjectMeta{Name: "nonexistent-flavor"}, 637 }) 638 return nil 639 }, 640 wantClusterQueues: map[string]*ClusterQueue{ 641 "a": { 642 Name: "a", 643 AllocatableResourceGeneration: 1, 644 ResourceGroups: []ResourceGroup{{ 645 CoveredResources: sets.New(corev1.ResourceCPU), 646 Flavors: []FlavorQuotas{{ 647 Name: "default", 648 Resources: map[corev1.ResourceName]*ResourceQuota{ 649 corev1.ResourceCPU: { 650 Nominal: 10_000, 651 BorrowingLimit: ptr.To[int64](10_000), 652 }, 653 }, 654 }}, 655 LabelKeys: sets.New("cpuType"), 656 }}, 657 NamespaceSelector: labels.Nothing(), 658 FlavorFungibility: defaultFlavorFungibility, 659 Usage: FlavorResourceQuantities{ 660 "default": {corev1.ResourceCPU: 0}, 661 }, 662 AdmittedUsage: FlavorResourceQuantities{ 663 "default": {corev1.ResourceCPU: 0}, 664 }, 665 Status: active, 666 Preemption: defaultPreemption, 667 }, 668 "b": { 669 Name: "b", 670 AllocatableResourceGeneration: 1, 671 ResourceGroups: []ResourceGroup{{ 672 CoveredResources: sets.New(corev1.ResourceCPU), 673 Flavors: []FlavorQuotas{{ 674 Name: "default", 675 Resources: map[corev1.ResourceName]*ResourceQuota{ 676 corev1.ResourceCPU: { 677 Nominal: 15_000, 678 }, 679 }, 680 }}, 681 LabelKeys: sets.New("cpuType"), 682 }}, 683 NamespaceSelector: labels.Nothing(), 684 FlavorFungibility: defaultFlavorFungibility, 685 Usage: FlavorResourceQuantities{ 686 "default": {corev1.ResourceCPU: 0}, 687 }, 688 AdmittedUsage: FlavorResourceQuantities{ 689 "default": {corev1.ResourceCPU: 0}, 690 }, 691 Status: active, 692 Preemption: defaultPreemption, 693 }, 694 "c": { 695 Name: "c", 696 AllocatableResourceGeneration: 1, 697 ResourceGroups: []ResourceGroup{}, 698 NamespaceSelector: labels.Nothing(), 699 FlavorFungibility: defaultFlavorFungibility, 700 Usage: FlavorResourceQuantities{}, 701 Status: active, 702 Preemption: defaultPreemption, 703 }, 704 "d": { 705 Name: "d", 706 AllocatableResourceGeneration: 1, 707 ResourceGroups: []ResourceGroup{}, 708 NamespaceSelector: labels.Nothing(), 709 FlavorFungibility: defaultFlavorFungibility, 710 Usage: FlavorResourceQuantities{}, 711 Status: active, 712 Preemption: defaultPreemption, 713 }, 714 "e": { 715 Name: "e", 716 AllocatableResourceGeneration: 1, 717 ResourceGroups: []ResourceGroup{{ 718 CoveredResources: sets.New(corev1.ResourceCPU), 719 Flavors: []FlavorQuotas{{ 720 Name: "nonexistent-flavor", 721 Resources: map[corev1.ResourceName]*ResourceQuota{ 722 corev1.ResourceCPU: { 723 Nominal: 15_000, 724 }, 725 }, 726 }}, 727 }}, 728 NamespaceSelector: labels.Nothing(), 729 FlavorFungibility: defaultFlavorFungibility, 730 Usage: FlavorResourceQuantities{"nonexistent-flavor": {corev1.ResourceCPU: 0}}, 731 AdmittedUsage: FlavorResourceQuantities{"nonexistent-flavor": {corev1.ResourceCPU: 0}}, 732 Status: active, 733 Preemption: defaultPreemption, 734 }, 735 "f": { 736 Name: "f", 737 AllocatableResourceGeneration: 1, 738 ResourceGroups: []ResourceGroup{}, 739 NamespaceSelector: labels.Nothing(), 740 Usage: FlavorResourceQuantities{}, 741 Status: active, 742 Preemption: defaultPreemption, 743 FlavorFungibility: kueue.FlavorFungibility{ 744 WhenCanBorrow: kueue.TryNextFlavor, 745 WhenCanPreempt: kueue.TryNextFlavor, 746 }, 747 }, 748 }, 749 wantCohorts: map[string]sets.Set[string]{ 750 "one": sets.New("a", "b"), 751 "two": sets.New("c", "e", "f"), 752 }, 753 }, 754 { 755 name: "Add ClusterQueue with multiple resource groups", 756 operation: func(cache *Cache) error { 757 err := cache.AddClusterQueue(context.Background(), 758 utiltesting.MakeClusterQueue("foo"). 759 ResourceGroup( 760 *utiltesting.MakeFlavorQuotas("foo"). 761 Resource("cpu"). 762 Resource("memory"). 763 Obj(), 764 *utiltesting.MakeFlavorQuotas("bar"). 765 Resource("cpu"). 766 Resource("memory"). 767 Obj(), 768 ). 769 ResourceGroup( 770 *utiltesting.MakeFlavorQuotas("theta").Resource("example.com/gpu").Obj(), 771 *utiltesting.MakeFlavorQuotas("gamma").Resource("example.com/gpu").Obj(), 772 ). 773 Obj()) 774 if err != nil { 775 return fmt.Errorf("Adding ClusterQueue: %w", err) 776 } 777 return nil 778 }, 779 wantClusterQueues: map[string]*ClusterQueue{ 780 "foo": { 781 Name: "foo", 782 NamespaceSelector: labels.Everything(), 783 AllocatableResourceGeneration: 1, 784 ResourceGroups: []ResourceGroup{ 785 { 786 CoveredResources: sets.New[corev1.ResourceName]("cpu", "memory"), 787 Flavors: []FlavorQuotas{ 788 { 789 Name: "foo", 790 Resources: map[corev1.ResourceName]*ResourceQuota{ 791 "cpu": {}, 792 "memory": {}, 793 }, 794 }, 795 { 796 Name: "bar", 797 Resources: map[corev1.ResourceName]*ResourceQuota{ 798 "cpu": {}, 799 "memory": {}, 800 }, 801 }, 802 }, 803 }, 804 { 805 CoveredResources: sets.New[corev1.ResourceName]("example.com/gpu"), 806 Flavors: []FlavorQuotas{ 807 { 808 Name: "theta", 809 Resources: map[corev1.ResourceName]*ResourceQuota{ 810 "example.com/gpu": {}, 811 }, 812 }, 813 { 814 Name: "gamma", 815 Resources: map[corev1.ResourceName]*ResourceQuota{ 816 "example.com/gpu": {}, 817 }, 818 }, 819 }, 820 }, 821 }, 822 FlavorFungibility: defaultFlavorFungibility, 823 Usage: FlavorResourceQuantities{ 824 "foo": { 825 "cpu": 0, 826 "memory": 0, 827 }, 828 "bar": { 829 "cpu": 0, 830 "memory": 0, 831 }, 832 "theta": { 833 "example.com/gpu": 0, 834 }, 835 "gamma": { 836 "example.com/gpu": 0, 837 }, 838 }, 839 AdmittedUsage: FlavorResourceQuantities{ 840 "foo": { 841 "cpu": 0, 842 "memory": 0, 843 }, 844 "bar": { 845 "cpu": 0, 846 "memory": 0, 847 }, 848 "theta": { 849 "example.com/gpu": 0, 850 }, 851 "gamma": { 852 "example.com/gpu": 0, 853 }, 854 }, 855 Status: pending, 856 Preemption: defaultPreemption, 857 }, 858 }, 859 }, 860 { 861 name: "add cluster queue with missing check", 862 operation: func(cache *Cache) error { 863 err := cache.AddClusterQueue(context.Background(), 864 utiltesting.MakeClusterQueue("foo"). 865 AdmissionChecks("check1", "check2"). 866 Obj()) 867 if err != nil { 868 return fmt.Errorf("Adding ClusterQueue: %w", err) 869 } 870 return nil 871 }, 872 wantClusterQueues: map[string]*ClusterQueue{ 873 "foo": { 874 Name: "foo", 875 NamespaceSelector: labels.Everything(), 876 Status: pending, 877 Preemption: defaultPreemption, 878 AllocatableResourceGeneration: 1, 879 FlavorFungibility: defaultFlavorFungibility, 880 AdmissionChecks: sets.New("check1", "check2"), 881 }, 882 }, 883 wantCohorts: map[string]sets.Set[string]{}, 884 }, 885 { 886 name: "add check after queue creation", 887 operation: func(cache *Cache) error { 888 err := cache.AddClusterQueue(context.Background(), 889 utiltesting.MakeClusterQueue("foo"). 890 AdmissionChecks("check1", "check2"). 891 Obj()) 892 if err != nil { 893 return fmt.Errorf("Adding ClusterQueue: %w", err) 894 } 895 896 cache.AddOrUpdateAdmissionCheck(utiltesting.MakeAdmissionCheck("check1").Active(metav1.ConditionTrue).Obj()) 897 cache.AddOrUpdateAdmissionCheck(utiltesting.MakeAdmissionCheck("check2").Active(metav1.ConditionTrue).Obj()) 898 return nil 899 }, 900 wantClusterQueues: map[string]*ClusterQueue{ 901 "foo": { 902 Name: "foo", 903 NamespaceSelector: labels.Everything(), 904 Status: active, 905 Preemption: defaultPreemption, 906 AllocatableResourceGeneration: 1, 907 FlavorFungibility: defaultFlavorFungibility, 908 AdmissionChecks: sets.New("check1", "check2"), 909 }, 910 }, 911 wantCohorts: map[string]sets.Set[string]{}, 912 }, 913 { 914 name: "remove check after queue creation", 915 operation: func(cache *Cache) error { 916 cache.AddOrUpdateAdmissionCheck(utiltesting.MakeAdmissionCheck("check1").Active(metav1.ConditionTrue).Obj()) 917 cache.AddOrUpdateAdmissionCheck(utiltesting.MakeAdmissionCheck("check2").Active(metav1.ConditionTrue).Obj()) 918 err := cache.AddClusterQueue(context.Background(), 919 utiltesting.MakeClusterQueue("foo"). 920 AdmissionChecks("check1", "check2"). 921 Obj()) 922 if err != nil { 923 return fmt.Errorf("Adding ClusterQueue: %w", err) 924 } 925 926 cache.DeleteAdmissionCheck(utiltesting.MakeAdmissionCheck("check2").Obj()) 927 return nil 928 }, 929 wantClusterQueues: map[string]*ClusterQueue{ 930 "foo": { 931 Name: "foo", 932 NamespaceSelector: labels.Everything(), 933 Status: pending, 934 Preemption: defaultPreemption, 935 AllocatableResourceGeneration: 1, 936 FlavorFungibility: defaultFlavorFungibility, 937 AdmissionChecks: sets.New("check1", "check2"), 938 }, 939 }, 940 wantCohorts: map[string]sets.Set[string]{}, 941 }, 942 { 943 name: "inactivate check after queue creation", 944 operation: func(cache *Cache) error { 945 cache.AddOrUpdateAdmissionCheck(utiltesting.MakeAdmissionCheck("check1").Active(metav1.ConditionTrue).Obj()) 946 cache.AddOrUpdateAdmissionCheck(utiltesting.MakeAdmissionCheck("check2").Active(metav1.ConditionTrue).Obj()) 947 err := cache.AddClusterQueue(context.Background(), 948 utiltesting.MakeClusterQueue("foo"). 949 AdmissionChecks("check1", "check2"). 950 Obj()) 951 if err != nil { 952 return fmt.Errorf("Adding ClusterQueue: %w", err) 953 } 954 955 cache.AddOrUpdateAdmissionCheck(utiltesting.MakeAdmissionCheck("check2").Active(metav1.ConditionFalse).Obj()) 956 return nil 957 }, 958 wantClusterQueues: map[string]*ClusterQueue{ 959 "foo": { 960 Name: "foo", 961 NamespaceSelector: labels.Everything(), 962 Status: pending, 963 Preemption: defaultPreemption, 964 AllocatableResourceGeneration: 1, 965 FlavorFungibility: defaultFlavorFungibility, 966 AdmissionChecks: sets.New("check1", "check2"), 967 }, 968 }, 969 wantCohorts: map[string]sets.Set[string]{}, 970 }, 971 { 972 name: "add cluster queue after finished workloads", 973 clientObbjects: []client.Object{ 974 utiltesting.MakeLocalQueue("lq1", "ns").ClusterQueue("cq1").Obj(), 975 utiltesting.MakeWorkload("pending", "ns").Obj(), 976 utiltesting.MakeWorkload("reserving", "ns").ReserveQuota( 977 utiltesting.MakeAdmission("cq1").Assignment(corev1.ResourceCPU, "f1", "1").Obj(), 978 ).Obj(), 979 utiltesting.MakeWorkload("admitted", "ns").ReserveQuota( 980 utiltesting.MakeAdmission("cq1").Assignment(corev1.ResourceCPU, "f1", "1").Obj(), 981 ).Admitted(true).Obj(), 982 utiltesting.MakeWorkload("finished", "ns").ReserveQuota( 983 utiltesting.MakeAdmission("cq1").Assignment(corev1.ResourceCPU, "f1", "1").Obj(), 984 ).Admitted(true).Finished().Obj(), 985 }, 986 operation: func(cache *Cache) error { 987 cache.AddOrUpdateResourceFlavor(utiltesting.MakeResourceFlavor("f1").Obj()) 988 err := cache.AddClusterQueue(context.Background(), 989 utiltesting.MakeClusterQueue("cq1"). 990 ResourceGroup(kueue.FlavorQuotas{ 991 Name: "f1", 992 Resources: []kueue.ResourceQuota{ 993 { 994 Name: corev1.ResourceCPU, 995 NominalQuota: resource.MustParse("10"), 996 }, 997 }, 998 }). 999 Obj()) 1000 if err != nil { 1001 return fmt.Errorf("Adding ClusterQueue: %w", err) 1002 } 1003 return nil 1004 }, 1005 wantClusterQueues: map[string]*ClusterQueue{ 1006 "cq1": { 1007 Name: "cq1", 1008 NamespaceSelector: labels.Everything(), 1009 Status: active, 1010 Preemption: defaultPreemption, 1011 AllocatableResourceGeneration: 1, 1012 FlavorFungibility: defaultFlavorFungibility, 1013 Usage: FlavorResourceQuantities{"f1": {corev1.ResourceCPU: 2000}}, 1014 AdmittedUsage: FlavorResourceQuantities{"f1": {corev1.ResourceCPU: 1000}}, 1015 Workloads: map[string]*workload.Info{ 1016 "ns/reserving": { 1017 ClusterQueue: "cq1", 1018 TotalRequests: []workload.PodSetResources{ 1019 { 1020 Name: "main", 1021 Requests: workload.Requests{corev1.ResourceCPU: 1000}, 1022 Count: 1, 1023 Flavors: map[corev1.ResourceName]kueue.ResourceFlavorReference{ 1024 corev1.ResourceCPU: "f1", 1025 }, 1026 }, 1027 }, 1028 }, 1029 "ns/admitted": { 1030 ClusterQueue: "cq1", 1031 TotalRequests: []workload.PodSetResources{ 1032 { 1033 Name: "main", 1034 Requests: workload.Requests{corev1.ResourceCPU: 1000}, 1035 Count: 1, 1036 Flavors: map[corev1.ResourceName]kueue.ResourceFlavorReference{ 1037 corev1.ResourceCPU: "f1", 1038 }, 1039 }, 1040 }, 1041 }, 1042 }, 1043 }, 1044 }, 1045 wantCohorts: map[string]sets.Set[string]{}, 1046 }, 1047 } 1048 1049 for _, tc := range cases { 1050 t.Run(tc.name, func(t *testing.T) { 1051 defer features.SetFeatureGateDuringTest(t, features.LendingLimit, tc.enableLendingLimit)() 1052 cache := New(utiltesting.NewFakeClient(tc.clientObbjects...)) 1053 if err := tc.operation(cache); err != nil { 1054 t.Errorf("Unexpected error during test operation: %s", err) 1055 } 1056 if diff := cmp.Diff(tc.wantClusterQueues, cache.clusterQueues, 1057 cmpopts.IgnoreFields(ClusterQueue{}, "Cohort", "RGByResource", "ResourceGroups"), 1058 cmpopts.IgnoreFields(workload.Info{}, "Obj", "LastAssignment"), 1059 cmpopts.IgnoreUnexported(ClusterQueue{}), 1060 cmpopts.EquateEmpty()); diff != "" { 1061 t.Errorf("Unexpected clusterQueues (-want,+got):\n%s", diff) 1062 } 1063 for _, cq := range cache.clusterQueues { 1064 for i := range cq.ResourceGroups { 1065 rg := &cq.ResourceGroups[i] 1066 for rName := range rg.CoveredResources { 1067 if cq.RGByResource[rName] != rg { 1068 t.Errorf("RGByResource[%s] does not point to its resource group", rName) 1069 } 1070 } 1071 } 1072 } 1073 1074 gotCohorts := make(map[string]sets.Set[string], len(cache.cohorts)) 1075 for name, cohort := range cache.cohorts { 1076 gotCohort := sets.New[string]() 1077 for cq := range cohort.Members { 1078 gotCohort.Insert(cq.Name) 1079 } 1080 gotCohorts[name] = gotCohort 1081 } 1082 if diff := cmp.Diff(tc.wantCohorts, gotCohorts, cmpopts.EquateEmpty()); diff != "" { 1083 t.Errorf("Unexpected cohorts (-want,+got):\n%s", diff) 1084 } 1085 }) 1086 } 1087 } 1088 1089 func TestCacheWorkloadOperations(t *testing.T) { 1090 clusterQueues := []kueue.ClusterQueue{ 1091 *utiltesting.MakeClusterQueue("one"). 1092 ResourceGroup( 1093 *utiltesting.MakeFlavorQuotas("on-demand").Resource("cpu").Obj(), 1094 *utiltesting.MakeFlavorQuotas("spot").Resource("cpu").Obj(), 1095 ). 1096 NamespaceSelector(nil). 1097 Obj(), 1098 *utiltesting.MakeClusterQueue("two"). 1099 ResourceGroup( 1100 *utiltesting.MakeFlavorQuotas("on-demand").Resource("cpu").Obj(), 1101 *utiltesting.MakeFlavorQuotas("spot").Resource("cpu").Obj(), 1102 ). 1103 NamespaceSelector(nil). 1104 Obj(), 1105 } 1106 podSets := []kueue.PodSet{ 1107 *utiltesting.MakePodSet("driver", 1). 1108 Request(corev1.ResourceCPU, "10m"). 1109 Request(corev1.ResourceMemory, "512Ki"). 1110 Obj(), 1111 *utiltesting.MakePodSet("workers", 3). 1112 Request(corev1.ResourceCPU, "5m"). 1113 Obj(), 1114 } 1115 podSetFlavors := []kueue.PodSetAssignment{ 1116 { 1117 Name: "driver", 1118 Flavors: map[corev1.ResourceName]kueue.ResourceFlavorReference{ 1119 corev1.ResourceCPU: "on-demand", 1120 }, 1121 ResourceUsage: corev1.ResourceList{ 1122 corev1.ResourceCPU: resource.MustParse("10m"), 1123 }, 1124 }, 1125 { 1126 Name: "workers", 1127 Flavors: map[corev1.ResourceName]kueue.ResourceFlavorReference{ 1128 corev1.ResourceCPU: "spot", 1129 }, 1130 ResourceUsage: corev1.ResourceList{ 1131 corev1.ResourceCPU: resource.MustParse("15m"), 1132 }, 1133 }, 1134 } 1135 cl := utiltesting.NewFakeClient( 1136 utiltesting.MakeWorkload("a", "").PodSets(podSets...).ReserveQuota(&kueue.Admission{ 1137 ClusterQueue: "one", 1138 PodSetAssignments: podSetFlavors, 1139 }).Obj(), 1140 utiltesting.MakeWorkload("b", "").ReserveQuota(&kueue.Admission{ 1141 ClusterQueue: "one", 1142 }).Obj(), 1143 utiltesting.MakeWorkload("c", "").PodSets(podSets...).ReserveQuota(&kueue.Admission{ 1144 ClusterQueue: "two", 1145 }).Obj()) 1146 1147 type result struct { 1148 Workloads sets.Set[string] 1149 UsedResources FlavorResourceQuantities 1150 } 1151 1152 steps := []struct { 1153 name string 1154 operation func(cache *Cache) error 1155 wantResults map[string]result 1156 wantAssumedWorkloads map[string]string 1157 wantError string 1158 }{ 1159 { 1160 name: "add", 1161 operation: func(cache *Cache) error { 1162 workloads := []*kueue.Workload{ 1163 utiltesting.MakeWorkload("a", "").PodSets(podSets...).ReserveQuota(&kueue.Admission{ 1164 ClusterQueue: "one", 1165 PodSetAssignments: podSetFlavors, 1166 }).Obj(), 1167 utiltesting.MakeWorkload("d", "").ReserveQuota(&kueue.Admission{ 1168 ClusterQueue: "two", 1169 }).Obj(), 1170 utiltesting.MakeWorkload("pending", "").Obj(), 1171 } 1172 for i := range workloads { 1173 cache.AddOrUpdateWorkload(workloads[i]) 1174 } 1175 return nil 1176 }, 1177 wantResults: map[string]result{ 1178 "one": { 1179 Workloads: sets.New("/a", "/b"), 1180 UsedResources: FlavorResourceQuantities{ 1181 "on-demand": {corev1.ResourceCPU: 10}, 1182 "spot": {corev1.ResourceCPU: 15}, 1183 }, 1184 }, 1185 "two": { 1186 Workloads: sets.New("/c", "/d"), 1187 UsedResources: FlavorResourceQuantities{ 1188 "on-demand": {corev1.ResourceCPU: 0}, 1189 "spot": {corev1.ResourceCPU: 0}, 1190 }, 1191 }, 1192 }, 1193 }, 1194 { 1195 name: "add error clusterQueue doesn't exist", 1196 operation: func(cache *Cache) error { 1197 w := utiltesting.MakeWorkload("d", "").ReserveQuota(&kueue.Admission{ 1198 ClusterQueue: "three", 1199 }).Obj() 1200 if !cache.AddOrUpdateWorkload(w) { 1201 return fmt.Errorf("failed to add workload") 1202 } 1203 return nil 1204 }, 1205 wantError: "failed to add workload", 1206 wantResults: map[string]result{ 1207 "one": { 1208 Workloads: sets.New("/a", "/b"), 1209 UsedResources: FlavorResourceQuantities{ 1210 "on-demand": {corev1.ResourceCPU: 10}, 1211 "spot": {corev1.ResourceCPU: 15}, 1212 }, 1213 }, 1214 "two": { 1215 Workloads: sets.New("/c"), 1216 UsedResources: FlavorResourceQuantities{ 1217 "on-demand": {corev1.ResourceCPU: 0}, 1218 "spot": {corev1.ResourceCPU: 0}, 1219 }, 1220 }, 1221 }, 1222 }, 1223 { 1224 name: "add already exists", 1225 operation: func(cache *Cache) error { 1226 w := utiltesting.MakeWorkload("b", "").ReserveQuota(&kueue.Admission{ 1227 ClusterQueue: "one", 1228 }).Obj() 1229 if !cache.AddOrUpdateWorkload(w) { 1230 return fmt.Errorf("failed to add workload") 1231 } 1232 return nil 1233 }, 1234 wantResults: map[string]result{ 1235 "one": { 1236 Workloads: sets.New("/a", "/b"), 1237 UsedResources: FlavorResourceQuantities{ 1238 "on-demand": {corev1.ResourceCPU: 10}, 1239 "spot": {corev1.ResourceCPU: 15}, 1240 }, 1241 }, 1242 "two": { 1243 Workloads: sets.New("/c"), 1244 UsedResources: FlavorResourceQuantities{ 1245 "on-demand": {corev1.ResourceCPU: 0}, 1246 "spot": {corev1.ResourceCPU: 0}, 1247 }, 1248 }, 1249 }, 1250 }, 1251 { 1252 name: "update", 1253 operation: func(cache *Cache) error { 1254 old := utiltesting.MakeWorkload("a", "").ReserveQuota(&kueue.Admission{ 1255 ClusterQueue: "one", 1256 }).Obj() 1257 latest := utiltesting.MakeWorkload("a", "").PodSets(podSets...).ReserveQuota(&kueue.Admission{ 1258 ClusterQueue: "two", 1259 PodSetAssignments: podSetFlavors, 1260 }).Obj() 1261 return cache.UpdateWorkload(old, latest) 1262 }, 1263 wantResults: map[string]result{ 1264 "one": { 1265 Workloads: sets.New("/b"), 1266 UsedResources: FlavorResourceQuantities{ 1267 "on-demand": {corev1.ResourceCPU: 0}, 1268 "spot": {corev1.ResourceCPU: 0}, 1269 }, 1270 }, 1271 "two": { 1272 Workloads: sets.New("/a", "/c"), 1273 UsedResources: FlavorResourceQuantities{ 1274 "on-demand": {corev1.ResourceCPU: 10}, 1275 "spot": {corev1.ResourceCPU: 15}, 1276 }, 1277 }, 1278 }, 1279 }, 1280 { 1281 name: "update error old clusterQueue doesn't exist", 1282 operation: func(cache *Cache) error { 1283 old := utiltesting.MakeWorkload("d", "").ReserveQuota(&kueue.Admission{ 1284 ClusterQueue: "three", 1285 }).Obj() 1286 latest := utiltesting.MakeWorkload("d", "").ReserveQuota(&kueue.Admission{ 1287 ClusterQueue: "one", 1288 }).Obj() 1289 return cache.UpdateWorkload(old, latest) 1290 }, 1291 wantError: "old ClusterQueue doesn't exist", 1292 wantResults: map[string]result{ 1293 "one": { 1294 Workloads: sets.New("/a", "/b"), 1295 UsedResources: FlavorResourceQuantities{ 1296 "on-demand": {corev1.ResourceCPU: 10}, 1297 "spot": {corev1.ResourceCPU: 15}, 1298 }, 1299 }, 1300 "two": { 1301 Workloads: sets.New("/c"), 1302 UsedResources: FlavorResourceQuantities{ 1303 "on-demand": {corev1.ResourceCPU: 0}, 1304 "spot": {corev1.ResourceCPU: 0}, 1305 }, 1306 }, 1307 }, 1308 }, 1309 { 1310 name: "update error new clusterQueue doesn't exist", 1311 operation: func(cache *Cache) error { 1312 old := utiltesting.MakeWorkload("d", "").ReserveQuota(&kueue.Admission{ 1313 ClusterQueue: "one", 1314 }).Obj() 1315 latest := utiltesting.MakeWorkload("d", "").ReserveQuota(&kueue.Admission{ 1316 ClusterQueue: "three", 1317 }).Obj() 1318 return cache.UpdateWorkload(old, latest) 1319 }, 1320 wantError: "new ClusterQueue doesn't exist", 1321 wantResults: map[string]result{ 1322 "one": { 1323 Workloads: sets.New("/a", "/b"), 1324 UsedResources: FlavorResourceQuantities{ 1325 "on-demand": {corev1.ResourceCPU: 10}, 1326 "spot": {corev1.ResourceCPU: 15}, 1327 }, 1328 }, 1329 "two": { 1330 Workloads: sets.New("/c"), 1331 UsedResources: FlavorResourceQuantities{ 1332 "on-demand": {corev1.ResourceCPU: 0}, 1333 "spot": {corev1.ResourceCPU: 0}, 1334 }, 1335 }, 1336 }, 1337 }, 1338 { 1339 name: "update workload which doesn't exist.", 1340 operation: func(cache *Cache) error { 1341 old := utiltesting.MakeWorkload("d", "").ReserveQuota(&kueue.Admission{ 1342 ClusterQueue: "one", 1343 }).Obj() 1344 latest := utiltesting.MakeWorkload("d", "").ReserveQuota(&kueue.Admission{ 1345 ClusterQueue: "two", 1346 }).Obj() 1347 return cache.UpdateWorkload(old, latest) 1348 }, 1349 wantResults: map[string]result{ 1350 "one": { 1351 Workloads: sets.New("/a", "/b"), 1352 UsedResources: FlavorResourceQuantities{ 1353 "on-demand": {corev1.ResourceCPU: 10}, 1354 "spot": {corev1.ResourceCPU: 15}, 1355 }, 1356 }, 1357 "two": { 1358 Workloads: sets.New("/c", "/d"), 1359 UsedResources: FlavorResourceQuantities{ 1360 "on-demand": {corev1.ResourceCPU: 0}, 1361 "spot": {corev1.ResourceCPU: 0}, 1362 }, 1363 }, 1364 }, 1365 }, 1366 { 1367 name: "delete", 1368 operation: func(cache *Cache) error { 1369 w := utiltesting.MakeWorkload("a", "").ReserveQuota(&kueue.Admission{ 1370 ClusterQueue: "one", 1371 }).Obj() 1372 return cache.DeleteWorkload(w) 1373 }, 1374 wantResults: map[string]result{ 1375 "one": { 1376 Workloads: sets.New("/b"), 1377 UsedResources: FlavorResourceQuantities{ 1378 "on-demand": {corev1.ResourceCPU: 0}, 1379 "spot": {corev1.ResourceCPU: 0}, 1380 }, 1381 }, 1382 "two": { 1383 Workloads: sets.New("/c"), 1384 UsedResources: FlavorResourceQuantities{ 1385 "on-demand": {corev1.ResourceCPU: 0}, 1386 "spot": {corev1.ResourceCPU: 0}, 1387 }, 1388 }, 1389 }, 1390 }, 1391 { 1392 name: "delete workload with cancelled admission", 1393 operation: func(cache *Cache) error { 1394 w := utiltesting.MakeWorkload("a", "").Obj() 1395 return cache.DeleteWorkload(w) 1396 }, 1397 wantResults: map[string]result{ 1398 "one": { 1399 Workloads: sets.New("/b"), 1400 UsedResources: FlavorResourceQuantities{ 1401 "on-demand": {corev1.ResourceCPU: 0}, 1402 "spot": {corev1.ResourceCPU: 0}, 1403 }, 1404 }, 1405 "two": { 1406 Workloads: sets.New("/c"), 1407 UsedResources: FlavorResourceQuantities{ 1408 "on-demand": {corev1.ResourceCPU: 0}, 1409 "spot": {corev1.ResourceCPU: 0}, 1410 }, 1411 }, 1412 }, 1413 }, 1414 { 1415 name: "attempt deleting non-existing workload with cancelled admission", 1416 operation: func(cache *Cache) error { 1417 w := utiltesting.MakeWorkload("d", "").Obj() 1418 return cache.DeleteWorkload(w) 1419 }, 1420 wantError: "cluster queue not found", 1421 wantResults: map[string]result{ 1422 "one": { 1423 Workloads: sets.New("/a", "/b"), 1424 UsedResources: FlavorResourceQuantities{ 1425 "on-demand": {corev1.ResourceCPU: 10}, 1426 "spot": {corev1.ResourceCPU: 15}, 1427 }, 1428 }, 1429 "two": { 1430 Workloads: sets.New("/c"), 1431 UsedResources: FlavorResourceQuantities{ 1432 "on-demand": {corev1.ResourceCPU: 0}, 1433 "spot": {corev1.ResourceCPU: 0}, 1434 }, 1435 }, 1436 }, 1437 }, 1438 { 1439 name: "delete error clusterQueue doesn't exist", 1440 operation: func(cache *Cache) error { 1441 w := utiltesting.MakeWorkload("a", "").ReserveQuota(&kueue.Admission{ 1442 ClusterQueue: "three", 1443 }).Obj() 1444 return cache.DeleteWorkload(w) 1445 }, 1446 wantError: "cluster queue not found", 1447 wantResults: map[string]result{ 1448 "one": { 1449 Workloads: sets.New("/a", "/b"), 1450 UsedResources: FlavorResourceQuantities{ 1451 "on-demand": {corev1.ResourceCPU: 10}, 1452 "spot": {corev1.ResourceCPU: 15}, 1453 }, 1454 }, 1455 "two": { 1456 Workloads: sets.New("/c"), 1457 UsedResources: FlavorResourceQuantities{ 1458 "on-demand": {corev1.ResourceCPU: 0}, 1459 "spot": {corev1.ResourceCPU: 0}, 1460 }, 1461 }, 1462 }, 1463 }, 1464 { 1465 name: "delete workload which doesn't exist", 1466 operation: func(cache *Cache) error { 1467 w := utiltesting.MakeWorkload("d", "").ReserveQuota(&kueue.Admission{ 1468 ClusterQueue: "one", 1469 }).Obj() 1470 return cache.DeleteWorkload(w) 1471 }, 1472 wantResults: map[string]result{ 1473 "one": { 1474 Workloads: sets.New("/a", "/b"), 1475 UsedResources: FlavorResourceQuantities{ 1476 "on-demand": {corev1.ResourceCPU: 10}, 1477 "spot": {corev1.ResourceCPU: 15}, 1478 }, 1479 }, 1480 "two": { 1481 Workloads: sets.New("/c"), 1482 UsedResources: FlavorResourceQuantities{ 1483 "on-demand": {corev1.ResourceCPU: 0}, 1484 "spot": {corev1.ResourceCPU: 0}, 1485 }, 1486 }, 1487 }, 1488 }, 1489 { 1490 name: "assume", 1491 operation: func(cache *Cache) error { 1492 workloads := []*kueue.Workload{ 1493 utiltesting.MakeWorkload("d", "").PodSets(podSets...).ReserveQuota(&kueue.Admission{ 1494 ClusterQueue: "one", 1495 PodSetAssignments: podSetFlavors, 1496 }).Obj(), 1497 utiltesting.MakeWorkload("e", "").PodSets(podSets...).ReserveQuota(&kueue.Admission{ 1498 ClusterQueue: "two", 1499 PodSetAssignments: podSetFlavors, 1500 }).Obj(), 1501 } 1502 for i := range workloads { 1503 if err := cache.AssumeWorkload(workloads[i]); err != nil { 1504 return err 1505 } 1506 } 1507 return nil 1508 }, 1509 wantResults: map[string]result{ 1510 "one": { 1511 Workloads: sets.New("/a", "/b", "/d"), 1512 UsedResources: FlavorResourceQuantities{ 1513 "on-demand": {corev1.ResourceCPU: 20}, 1514 "spot": {corev1.ResourceCPU: 30}, 1515 }, 1516 }, 1517 "two": { 1518 Workloads: sets.New("/c", "/e"), 1519 UsedResources: FlavorResourceQuantities{ 1520 "on-demand": {corev1.ResourceCPU: 10}, 1521 "spot": {corev1.ResourceCPU: 15}, 1522 }, 1523 }, 1524 }, 1525 wantAssumedWorkloads: map[string]string{ 1526 "/d": "one", 1527 "/e": "two", 1528 }, 1529 }, 1530 { 1531 name: "assume error clusterQueue doesn't exist", 1532 operation: func(cache *Cache) error { 1533 w := utiltesting.MakeWorkload("d", "").PodSets(podSets...).ReserveQuota(&kueue.Admission{ 1534 ClusterQueue: "three", 1535 }).Obj() 1536 if err := cache.AssumeWorkload(w); err != nil { 1537 return err 1538 } 1539 return nil 1540 }, 1541 wantError: "cluster queue not found", 1542 wantResults: map[string]result{ 1543 "one": { 1544 Workloads: sets.New("/a", "/b"), 1545 UsedResources: FlavorResourceQuantities{ 1546 "on-demand": {corev1.ResourceCPU: 10}, 1547 "spot": {corev1.ResourceCPU: 15}, 1548 }, 1549 }, 1550 "two": { 1551 Workloads: sets.New("/c"), 1552 UsedResources: FlavorResourceQuantities{ 1553 "on-demand": {corev1.ResourceCPU: 0}, 1554 "spot": {corev1.ResourceCPU: 0}, 1555 }, 1556 }, 1557 }, 1558 wantAssumedWorkloads: map[string]string{}, 1559 }, 1560 { 1561 name: "forget", 1562 operation: func(cache *Cache) error { 1563 workloads := []*kueue.Workload{ 1564 utiltesting.MakeWorkload("d", "").PodSets(podSets...).ReserveQuota(&kueue.Admission{ 1565 ClusterQueue: "one", 1566 PodSetAssignments: podSetFlavors, 1567 }).Obj(), 1568 utiltesting.MakeWorkload("e", "").PodSets(podSets...).ReserveQuota(&kueue.Admission{ 1569 ClusterQueue: "two", 1570 PodSetAssignments: podSetFlavors, 1571 }).Obj(), 1572 } 1573 for i := range workloads { 1574 if err := cache.AssumeWorkload(workloads[i]); err != nil { 1575 return err 1576 } 1577 } 1578 1579 w := workloads[0] 1580 return cache.ForgetWorkload(w) 1581 }, 1582 wantResults: map[string]result{ 1583 "one": { 1584 Workloads: sets.New("/a", "/b"), 1585 UsedResources: FlavorResourceQuantities{ 1586 "on-demand": {corev1.ResourceCPU: 10}, 1587 "spot": {corev1.ResourceCPU: 15}, 1588 }, 1589 }, 1590 "two": { 1591 Workloads: sets.New("/c", "/e"), 1592 UsedResources: FlavorResourceQuantities{ 1593 "on-demand": {corev1.ResourceCPU: 10}, 1594 "spot": {corev1.ResourceCPU: 15}, 1595 }, 1596 }, 1597 }, 1598 wantAssumedWorkloads: map[string]string{ 1599 "/e": "two", 1600 }, 1601 }, 1602 { 1603 name: "forget error workload is not assumed", 1604 operation: func(cache *Cache) error { 1605 w := utiltesting.MakeWorkload("b", "").ReserveQuota(&kueue.Admission{ 1606 ClusterQueue: "one", 1607 }).Obj() 1608 if err := cache.ForgetWorkload(w); err != nil { 1609 return err 1610 } 1611 return nil 1612 }, 1613 wantError: "the workload is not assumed", 1614 wantResults: map[string]result{ 1615 "one": { 1616 Workloads: sets.New("/a", "/b"), 1617 UsedResources: FlavorResourceQuantities{ 1618 "on-demand": {corev1.ResourceCPU: 10}, 1619 "spot": {corev1.ResourceCPU: 15}, 1620 }, 1621 }, 1622 "two": { 1623 Workloads: sets.New("/c"), 1624 UsedResources: FlavorResourceQuantities{ 1625 "on-demand": {corev1.ResourceCPU: 0}, 1626 "spot": {corev1.ResourceCPU: 0}, 1627 }, 1628 }, 1629 }, 1630 }, 1631 { 1632 name: "add assumed workload", 1633 operation: func(cache *Cache) error { 1634 workloads := []*kueue.Workload{ 1635 utiltesting.MakeWorkload("d", "").PodSets(podSets...).ReserveQuota(&kueue.Admission{ 1636 ClusterQueue: "one", 1637 PodSetAssignments: podSetFlavors, 1638 }).Obj(), 1639 utiltesting.MakeWorkload("e", "").PodSets(podSets...).ReserveQuota(&kueue.Admission{ 1640 ClusterQueue: "two", 1641 PodSetAssignments: podSetFlavors, 1642 }).Obj(), 1643 } 1644 for i := range workloads { 1645 if err := cache.AssumeWorkload(workloads[i]); err != nil { 1646 return err 1647 } 1648 } 1649 1650 w := workloads[0] 1651 if !cache.AddOrUpdateWorkload(w) { 1652 return fmt.Errorf("failed to add workload") 1653 } 1654 return nil 1655 }, 1656 wantResults: map[string]result{ 1657 "one": { 1658 Workloads: sets.New("/a", "/b", "/d"), 1659 UsedResources: FlavorResourceQuantities{ 1660 "on-demand": {corev1.ResourceCPU: 20}, 1661 "spot": {corev1.ResourceCPU: 30}, 1662 }, 1663 }, 1664 "two": { 1665 Workloads: sets.New("/c", "/e"), 1666 UsedResources: FlavorResourceQuantities{ 1667 "on-demand": {corev1.ResourceCPU: 10}, 1668 "spot": {corev1.ResourceCPU: 15}, 1669 }, 1670 }, 1671 }, 1672 wantAssumedWorkloads: map[string]string{ 1673 "/e": "two", 1674 }, 1675 }, 1676 } 1677 for _, step := range steps { 1678 t.Run(step.name, func(t *testing.T) { 1679 cache := New(cl) 1680 1681 for _, c := range clusterQueues { 1682 if err := cache.AddClusterQueue(context.Background(), &c); err != nil { 1683 t.Fatalf("Failed adding clusterQueue: %v", err) 1684 } 1685 } 1686 1687 gotError := step.operation(cache) 1688 if diff := cmp.Diff(step.wantError, messageOrEmpty(gotError)); diff != "" { 1689 t.Errorf("Unexpected error (-want,+got):\n%s", diff) 1690 } 1691 gotWorkloads := make(map[string]result) 1692 for name, cq := range cache.clusterQueues { 1693 gotWorkloads[name] = result{Workloads: sets.KeySet(cq.Workloads), UsedResources: cq.Usage} 1694 } 1695 if diff := cmp.Diff(step.wantResults, gotWorkloads); diff != "" { 1696 t.Errorf("Unexpected clusterQueues (-want,+got):\n%s", diff) 1697 } 1698 if step.wantAssumedWorkloads == nil { 1699 step.wantAssumedWorkloads = map[string]string{} 1700 } 1701 if diff := cmp.Diff(step.wantAssumedWorkloads, cache.assumedWorkloads); diff != "" { 1702 t.Errorf("Unexpected assumed workloads (-want,+got):\n%s", diff) 1703 } 1704 }) 1705 } 1706 } 1707 1708 func TestClusterQueueUsage(t *testing.T) { 1709 cq := utiltesting.MakeClusterQueue("foo"). 1710 ResourceGroup( 1711 *utiltesting.MakeFlavorQuotas("default"). 1712 Resource(corev1.ResourceCPU, "10", "10"). 1713 Obj(), 1714 ). 1715 ResourceGroup( 1716 *utiltesting.MakeFlavorQuotas("model_a"). 1717 Resource("example.com/gpu", "5", "5"). 1718 Obj(), 1719 *utiltesting.MakeFlavorQuotas("model_b"). 1720 Resource("example.com/gpu", "5"). 1721 Obj(), 1722 ). 1723 ResourceGroup( 1724 *utiltesting.MakeFlavorQuotas("interconnect_a"). 1725 Resource("example.com/vf-0", "5", "5"). 1726 Resource("example.com/vf-1", "5", "5"). 1727 Resource("example.com/vf-2", "5", "5"). 1728 Obj(), 1729 ). 1730 Cohort("one").Obj() 1731 cqWithOutCohort := cq.DeepCopy() 1732 cqWithOutCohort.Spec.Cohort = "" 1733 workloads := []kueue.Workload{ 1734 *utiltesting.MakeWorkload("one", ""). 1735 Request(corev1.ResourceCPU, "8"). 1736 Request("example.com/gpu", "5"). 1737 ReserveQuota(utiltesting.MakeAdmission("foo").Assignment(corev1.ResourceCPU, "default", "8000m").Assignment("example.com/gpu", "model_a", "5").Obj()). 1738 Condition(metav1.Condition{Type: kueue.WorkloadAdmitted, Status: metav1.ConditionTrue}). 1739 Obj(), 1740 *utiltesting.MakeWorkload("two", ""). 1741 Request(corev1.ResourceCPU, "5"). 1742 Request("example.com/gpu", "6"). 1743 ReserveQuota(utiltesting.MakeAdmission("foo").Assignment(corev1.ResourceCPU, "default", "5000m").Assignment("example.com/gpu", "model_b", "6").Obj()). 1744 Obj(), 1745 } 1746 cases := map[string]struct { 1747 clusterQueue *kueue.ClusterQueue 1748 workloads []kueue.Workload 1749 wantReservedResources []kueue.FlavorUsage 1750 wantReservingWorkloads int 1751 wantUsedResources []kueue.FlavorUsage 1752 wantAdmittedWorkloads int 1753 }{ 1754 "clusterQueue without cohort; single, admitted no borrowing": { 1755 clusterQueue: cqWithOutCohort, 1756 workloads: workloads[:1], 1757 wantReservedResources: []kueue.FlavorUsage{ 1758 { 1759 Name: "default", 1760 Resources: []kueue.ResourceUsage{{ 1761 Name: corev1.ResourceCPU, 1762 Total: resource.MustParse("8"), 1763 }}, 1764 }, 1765 { 1766 Name: "model_a", 1767 Resources: []kueue.ResourceUsage{{ 1768 Name: "example.com/gpu", 1769 Total: resource.MustParse("5"), 1770 }}, 1771 }, 1772 { 1773 Name: "model_b", 1774 Resources: []kueue.ResourceUsage{{ 1775 Name: "example.com/gpu", 1776 }}, 1777 }, 1778 { 1779 Name: "interconnect_a", 1780 Resources: []kueue.ResourceUsage{ 1781 {Name: "example.com/vf-0"}, 1782 {Name: "example.com/vf-1"}, 1783 {Name: "example.com/vf-2"}, 1784 }, 1785 }, 1786 }, 1787 wantReservingWorkloads: 1, 1788 wantUsedResources: []kueue.FlavorUsage{ 1789 { 1790 Name: "default", 1791 Resources: []kueue.ResourceUsage{{ 1792 Name: corev1.ResourceCPU, 1793 Total: resource.MustParse("8"), 1794 }}, 1795 }, 1796 { 1797 Name: "model_a", 1798 Resources: []kueue.ResourceUsage{{ 1799 Name: "example.com/gpu", 1800 Total: resource.MustParse("5"), 1801 }}, 1802 }, 1803 { 1804 Name: "model_b", 1805 Resources: []kueue.ResourceUsage{{ 1806 Name: "example.com/gpu", 1807 }}, 1808 }, 1809 { 1810 Name: "interconnect_a", 1811 Resources: []kueue.ResourceUsage{ 1812 {Name: "example.com/vf-0"}, 1813 {Name: "example.com/vf-1"}, 1814 {Name: "example.com/vf-2"}, 1815 }, 1816 }, 1817 }, 1818 wantAdmittedWorkloads: 1, 1819 }, 1820 "clusterQueue with cohort; multiple borrowing": { 1821 clusterQueue: cq, 1822 workloads: workloads, 1823 wantReservedResources: []kueue.FlavorUsage{ 1824 { 1825 Name: "default", 1826 Resources: []kueue.ResourceUsage{{ 1827 Name: corev1.ResourceCPU, 1828 Total: resource.MustParse("13"), 1829 Borrowed: resource.MustParse("3"), 1830 }}, 1831 }, 1832 { 1833 Name: "model_a", 1834 Resources: []kueue.ResourceUsage{{ 1835 Name: "example.com/gpu", 1836 Total: resource.MustParse("5"), 1837 }}, 1838 }, 1839 { 1840 Name: "model_b", 1841 Resources: []kueue.ResourceUsage{{ 1842 Name: "example.com/gpu", 1843 Total: resource.MustParse("6"), 1844 Borrowed: resource.MustParse("1"), 1845 }}, 1846 }, 1847 { 1848 Name: "interconnect_a", 1849 Resources: []kueue.ResourceUsage{ 1850 {Name: "example.com/vf-0"}, 1851 {Name: "example.com/vf-1"}, 1852 {Name: "example.com/vf-2"}, 1853 }, 1854 }, 1855 }, 1856 wantReservingWorkloads: 2, 1857 wantUsedResources: []kueue.FlavorUsage{ 1858 { 1859 Name: "default", 1860 Resources: []kueue.ResourceUsage{{ 1861 Name: corev1.ResourceCPU, 1862 Total: resource.MustParse("8"), 1863 }}, 1864 }, 1865 { 1866 Name: "model_a", 1867 Resources: []kueue.ResourceUsage{{ 1868 Name: "example.com/gpu", 1869 Total: resource.MustParse("5"), 1870 }}, 1871 }, 1872 { 1873 Name: "model_b", 1874 Resources: []kueue.ResourceUsage{{ 1875 Name: "example.com/gpu", 1876 }}, 1877 }, 1878 { 1879 Name: "interconnect_a", 1880 Resources: []kueue.ResourceUsage{ 1881 {Name: "example.com/vf-0"}, 1882 {Name: "example.com/vf-1"}, 1883 {Name: "example.com/vf-2"}, 1884 }, 1885 }, 1886 }, 1887 wantAdmittedWorkloads: 1, 1888 }, 1889 "clusterQueue without cohort; multiple borrowing": { 1890 clusterQueue: cqWithOutCohort, 1891 workloads: workloads, 1892 wantReservedResources: []kueue.FlavorUsage{ 1893 { 1894 Name: "default", 1895 Resources: []kueue.ResourceUsage{{ 1896 Name: corev1.ResourceCPU, 1897 Total: resource.MustParse("13"), 1898 Borrowed: resource.MustParse("0"), 1899 }}, 1900 }, 1901 { 1902 Name: "model_a", 1903 Resources: []kueue.ResourceUsage{{ 1904 Name: "example.com/gpu", 1905 Total: resource.MustParse("5"), 1906 }}, 1907 }, 1908 { 1909 Name: "model_b", 1910 Resources: []kueue.ResourceUsage{{ 1911 Name: "example.com/gpu", 1912 Total: resource.MustParse("6"), 1913 Borrowed: resource.MustParse("0"), 1914 }}, 1915 }, 1916 { 1917 Name: "interconnect_a", 1918 Resources: []kueue.ResourceUsage{ 1919 {Name: "example.com/vf-0"}, 1920 {Name: "example.com/vf-1"}, 1921 {Name: "example.com/vf-2"}, 1922 }, 1923 }, 1924 }, 1925 wantReservingWorkloads: 2, 1926 wantUsedResources: []kueue.FlavorUsage{ 1927 { 1928 Name: "default", 1929 Resources: []kueue.ResourceUsage{{ 1930 Name: corev1.ResourceCPU, 1931 Total: resource.MustParse("8"), 1932 }}, 1933 }, 1934 { 1935 Name: "model_a", 1936 Resources: []kueue.ResourceUsage{{ 1937 Name: "example.com/gpu", 1938 Total: resource.MustParse("5"), 1939 }}, 1940 }, 1941 { 1942 Name: "model_b", 1943 Resources: []kueue.ResourceUsage{{ 1944 Name: "example.com/gpu", 1945 }}, 1946 }, 1947 { 1948 Name: "interconnect_a", 1949 Resources: []kueue.ResourceUsage{ 1950 {Name: "example.com/vf-0"}, 1951 {Name: "example.com/vf-1"}, 1952 {Name: "example.com/vf-2"}, 1953 }, 1954 }, 1955 }, 1956 wantAdmittedWorkloads: 1, 1957 }, 1958 } 1959 for name, tc := range cases { 1960 t.Run(name, func(t *testing.T) { 1961 cache := New(utiltesting.NewFakeClient()) 1962 ctx := context.Background() 1963 err := cache.AddClusterQueue(ctx, tc.clusterQueue) 1964 if err != nil { 1965 t.Fatalf("Adding ClusterQueue: %v", err) 1966 } 1967 for i := range tc.workloads { 1968 w := &tc.workloads[i] 1969 if added := cache.AddOrUpdateWorkload(w); !added { 1970 t.Fatalf("Workload %s was not added", workload.Key(w)) 1971 } 1972 } 1973 stats, err := cache.Usage(tc.clusterQueue) 1974 if err != nil { 1975 t.Fatalf("Couldn't get usage: %v", err) 1976 } 1977 1978 if diff := cmp.Diff(tc.wantReservedResources, stats.ReservedResources); diff != "" { 1979 t.Errorf("Unexpected used reserved resources (-want,+got):\n%s", diff) 1980 } 1981 if stats.ReservingWorkloads != tc.wantReservingWorkloads { 1982 t.Errorf("Got %d reserving workloads, want %d", stats.ReservingWorkloads, tc.wantReservingWorkloads) 1983 } 1984 1985 if diff := cmp.Diff(tc.wantUsedResources, stats.AdmittedResources); diff != "" { 1986 t.Errorf("Unexpected used resources (-want,+got):\n%s", diff) 1987 } 1988 if stats.AdmittedWorkloads != tc.wantAdmittedWorkloads { 1989 t.Errorf("Got %d admitted workloads, want %d", stats.AdmittedWorkloads, tc.wantAdmittedWorkloads) 1990 } 1991 }) 1992 } 1993 } 1994 1995 func TestLocalQueueUsage(t *testing.T) { 1996 cq := *utiltesting.MakeClusterQueue("foo"). 1997 ResourceGroup( 1998 *utiltesting.MakeFlavorQuotas("default"). 1999 Resource(corev1.ResourceCPU, "10", "10").Obj(), 2000 ). 2001 ResourceGroup( 2002 *utiltesting.MakeFlavorQuotas("model-a"). 2003 Resource("example.com/gpu", "5").Obj(), 2004 *utiltesting.MakeFlavorQuotas("model-b"). 2005 Resource("example.com/gpu", "5").Obj(), 2006 ). 2007 ResourceGroup( 2008 *utiltesting.MakeFlavorQuotas("interconnect-a"). 2009 Resource("example.com/vf-0", "5", "5"). 2010 Resource("example.com/vf-1", "5", "5"). 2011 Resource("example.com/vf-2", "5", "5"). 2012 Obj(), 2013 ). 2014 Obj() 2015 localQueue := *utiltesting.MakeLocalQueue("test", "ns1"). 2016 ClusterQueue("foo").Obj() 2017 cases := map[string]struct { 2018 cq *kueue.ClusterQueue 2019 wls []kueue.Workload 2020 wantUsage []kueue.LocalQueueFlavorUsage 2021 inAdmissibleWl sets.Set[string] 2022 }{ 2023 "clusterQueue is missing": { 2024 wls: []kueue.Workload{ 2025 *utiltesting.MakeWorkload("one", "ns1"). 2026 Queue("test"). 2027 Request(corev1.ResourceCPU, "5").Obj(), 2028 }, 2029 inAdmissibleWl: sets.New("one"), 2030 }, 2031 "workloads is nothing": { 2032 cq: &cq, 2033 wantUsage: []kueue.LocalQueueFlavorUsage{ 2034 { 2035 Name: "default", 2036 Resources: []kueue.LocalQueueResourceUsage{ 2037 { 2038 Name: corev1.ResourceCPU, 2039 Total: resource.MustParse("0"), 2040 }, 2041 }, 2042 }, 2043 { 2044 Name: "model-a", 2045 Resources: []kueue.LocalQueueResourceUsage{ 2046 { 2047 Name: "example.com/gpu", 2048 Total: resource.MustParse("0"), 2049 }, 2050 }, 2051 }, 2052 { 2053 Name: "model-b", 2054 Resources: []kueue.LocalQueueResourceUsage{ 2055 { 2056 Name: "example.com/gpu", 2057 Total: resource.MustParse("0"), 2058 }, 2059 }, 2060 }, 2061 { 2062 Name: "interconnect-a", 2063 Resources: []kueue.LocalQueueResourceUsage{ 2064 {Name: "example.com/vf-0"}, 2065 {Name: "example.com/vf-1"}, 2066 {Name: "example.com/vf-2"}, 2067 }, 2068 }, 2069 }, 2070 }, 2071 "all workloads are admitted": { 2072 cq: &cq, 2073 wls: []kueue.Workload{ 2074 *utiltesting.MakeWorkload("one", "ns1"). 2075 Queue("test"). 2076 Request(corev1.ResourceCPU, "5"). 2077 Request("example.com/gpu", "5"). 2078 ReserveQuota( 2079 utiltesting.MakeAdmission("foo"). 2080 Assignment(corev1.ResourceCPU, "default", "5000m"). 2081 Assignment("example.com/gpu", "model-a", "5").Obj(), 2082 ). 2083 Obj(), 2084 *utiltesting.MakeWorkload("two", "ns1"). 2085 Queue("test"). 2086 Request(corev1.ResourceCPU, "3"). 2087 Request("example.com/gpu", "3"). 2088 ReserveQuota( 2089 utiltesting.MakeAdmission("foo"). 2090 Assignment(corev1.ResourceCPU, "default", "3000m"). 2091 Assignment("example.com/gpu", "model-b", "3").Obj(), 2092 ). 2093 Obj(), 2094 }, 2095 wantUsage: []kueue.LocalQueueFlavorUsage{ 2096 { 2097 Name: "default", 2098 Resources: []kueue.LocalQueueResourceUsage{ 2099 { 2100 Name: corev1.ResourceCPU, 2101 Total: resource.MustParse("8"), 2102 }, 2103 }, 2104 }, 2105 { 2106 Name: "model-a", 2107 Resources: []kueue.LocalQueueResourceUsage{ 2108 { 2109 Name: "example.com/gpu", 2110 Total: resource.MustParse("5"), 2111 }, 2112 }, 2113 }, 2114 { 2115 Name: "model-b", 2116 Resources: []kueue.LocalQueueResourceUsage{ 2117 { 2118 Name: "example.com/gpu", 2119 Total: resource.MustParse("3"), 2120 }, 2121 }, 2122 }, 2123 { 2124 Name: "interconnect-a", 2125 Resources: []kueue.LocalQueueResourceUsage{ 2126 {Name: "example.com/vf-0"}, 2127 {Name: "example.com/vf-1"}, 2128 {Name: "example.com/vf-2"}, 2129 }, 2130 }, 2131 }, 2132 }, 2133 "some workloads are inadmissible": { 2134 cq: &cq, 2135 wls: []kueue.Workload{ 2136 *utiltesting.MakeWorkload("one", "ns1"). 2137 Queue("test"). 2138 Request(corev1.ResourceCPU, "5"). 2139 Request("example.com/gpu", "5"). 2140 ReserveQuota( 2141 utiltesting.MakeAdmission("foo"). 2142 Assignment(corev1.ResourceCPU, "default", "5000m"). 2143 Assignment("example.com/gpu", "model-a", "5").Obj(), 2144 ).Obj(), 2145 *utiltesting.MakeWorkload("two", "ns1"). 2146 Queue("test"). 2147 Request(corev1.ResourceCPU, "100000"). 2148 Request("example.com/gpu", "3").Obj(), 2149 }, 2150 inAdmissibleWl: sets.New("two"), 2151 wantUsage: []kueue.LocalQueueFlavorUsage{ 2152 { 2153 Name: "default", 2154 Resources: []kueue.LocalQueueResourceUsage{ 2155 { 2156 Name: corev1.ResourceCPU, 2157 Total: resource.MustParse("5"), 2158 }, 2159 }, 2160 }, 2161 { 2162 Name: "model-a", 2163 Resources: []kueue.LocalQueueResourceUsage{ 2164 { 2165 Name: "example.com/gpu", 2166 Total: resource.MustParse("5"), 2167 }, 2168 }, 2169 }, 2170 { 2171 Name: "model-b", 2172 Resources: []kueue.LocalQueueResourceUsage{ 2173 { 2174 Name: "example.com/gpu", 2175 Total: resource.MustParse("0"), 2176 }, 2177 }, 2178 }, 2179 { 2180 Name: "interconnect-a", 2181 Resources: []kueue.LocalQueueResourceUsage{ 2182 {Name: "example.com/vf-0"}, 2183 {Name: "example.com/vf-1"}, 2184 {Name: "example.com/vf-2"}, 2185 }, 2186 }, 2187 }, 2188 }, 2189 } 2190 for name, tc := range cases { 2191 t.Run(name, func(t *testing.T) { 2192 cache := New(utiltesting.NewFakeClient()) 2193 ctx := context.Background() 2194 if tc.cq != nil { 2195 if err := cache.AddClusterQueue(ctx, tc.cq); err != nil { 2196 t.Fatalf("Adding ClusterQueue: %v", err) 2197 } 2198 } 2199 if err := cache.AddLocalQueue(&localQueue); err != nil { 2200 t.Fatalf("Adding LocalQueue: %v", err) 2201 } 2202 for _, w := range tc.wls { 2203 if added := cache.AddOrUpdateWorkload(&w); !added && !tc.inAdmissibleWl.Has(w.Name) { 2204 t.Fatalf("Workload %s was not added", workload.Key(&w)) 2205 } 2206 } 2207 gotUsage, err := cache.LocalQueueUsage(&localQueue) 2208 if err != nil { 2209 t.Fatalf("Couldn't get usage for the queue: %v", err) 2210 } 2211 if diff := cmp.Diff(tc.wantUsage, gotUsage.ReservedResources); diff != "" { 2212 t.Errorf("Unexpected used resources for the queue (-want,+got):\n%s", diff) 2213 } 2214 }) 2215 } 2216 } 2217 2218 func TestCacheQueueOperations(t *testing.T) { 2219 cqs := []*kueue.ClusterQueue{ 2220 utiltesting.MakeClusterQueue("foo"). 2221 ResourceGroup( 2222 *utiltesting.MakeFlavorQuotas("spot"). 2223 Resource("cpu", "10", "10"). 2224 Resource("memory", "64Gi", "64Gi").Obj(), 2225 ).ResourceGroup( 2226 *utiltesting.MakeFlavorQuotas("model-a"). 2227 Resource("example.com/gpu", "10", "10").Obj(), 2228 ).Obj(), 2229 utiltesting.MakeClusterQueue("bar"). 2230 ResourceGroup( 2231 *utiltesting.MakeFlavorQuotas("ondemand"). 2232 Resource("cpu", "5", "5"). 2233 Resource("memory", "32Gi", "32Gi").Obj(), 2234 ).ResourceGroup( 2235 *utiltesting.MakeFlavorQuotas("model-b"). 2236 Resource("example.com/gpu", "5", "5").Obj(), 2237 ).Obj(), 2238 } 2239 queues := []*kueue.LocalQueue{ 2240 utiltesting.MakeLocalQueue("alpha", "ns1").ClusterQueue("foo").Obj(), 2241 utiltesting.MakeLocalQueue("beta", "ns2").ClusterQueue("foo").Obj(), 2242 utiltesting.MakeLocalQueue("gamma", "ns1").ClusterQueue("bar").Obj(), 2243 } 2244 workloads := []*kueue.Workload{ 2245 utiltesting.MakeWorkload("job1", "ns1"). 2246 Queue("alpha"). 2247 Request("cpu", "2"). 2248 Request("memory", "8Gi"). 2249 ReserveQuota( 2250 utiltesting.MakeAdmission("foo"). 2251 Assignment("cpu", "spot", "2"). 2252 Assignment("memory", "spot", "8Gi").Obj(), 2253 ). 2254 Condition(metav1.Condition{Type: kueue.WorkloadAdmitted, Status: metav1.ConditionTrue}). 2255 Obj(), 2256 utiltesting.MakeWorkload("job2", "ns2"). 2257 Queue("beta"). 2258 Request("example.com/gpu", "2"). 2259 ReserveQuota( 2260 utiltesting.MakeAdmission("foo"). 2261 Assignment("example.com/gpu", "model-a", "2").Obj(), 2262 ). 2263 Condition(metav1.Condition{Type: kueue.WorkloadAdmitted, Status: metav1.ConditionTrue}). 2264 Obj(), 2265 utiltesting.MakeWorkload("job3", "ns1"). 2266 Queue("gamma"). 2267 Request("cpu", "5"). 2268 Request("memory", "16Gi"). 2269 ReserveQuota( 2270 utiltesting.MakeAdmission("bar"). 2271 Assignment("cpu", "ondemand", "5"). 2272 Assignment("memory", "ondemand", "16Gi").Obj(), 2273 ).Obj(), 2274 utiltesting.MakeWorkload("job4", "ns2"). 2275 Queue("beta"). 2276 Request("example.com/gpu", "5"). 2277 ReserveQuota( 2278 utiltesting.MakeAdmission("foo"). 2279 Assignment("example.com/gpu", "model-a", "5").Obj(), 2280 ).Obj(), 2281 } 2282 insertAllClusterQueues := func(ctx context.Context, cl client.Client, cache *Cache) error { 2283 for _, cq := range cqs { 2284 cq := cq.DeepCopy() 2285 if err := cl.Create(ctx, cq); err != nil { 2286 return err 2287 } 2288 if err := cache.AddClusterQueue(ctx, cq); err != nil { 2289 return err 2290 } 2291 } 2292 return nil 2293 } 2294 insertAllQueues := func(ctx context.Context, cl client.Client, cache *Cache) error { 2295 for _, q := range queues { 2296 q := q.DeepCopy() 2297 if err := cl.Create(ctx, q.DeepCopy()); err != nil { 2298 return err 2299 } 2300 if err := cache.AddLocalQueue(q); err != nil { 2301 return err 2302 } 2303 } 2304 return nil 2305 } 2306 insertAllWorkloads := func(ctx context.Context, cl client.Client, cache *Cache) error { 2307 for _, wl := range workloads { 2308 wl := wl.DeepCopy() 2309 if err := cl.Create(ctx, wl); err != nil { 2310 return err 2311 } 2312 cache.AddOrUpdateWorkload(wl) 2313 } 2314 return nil 2315 } 2316 cacheLocalQueuesAfterInsertingAll := map[string]*queue{ 2317 "ns1/alpha": { 2318 key: "ns1/alpha", 2319 reservingWorkloads: 1, 2320 admittedWorkloads: 1, 2321 usage: FlavorResourceQuantities{ 2322 "spot": { 2323 corev1.ResourceCPU: workload.ResourceValue(corev1.ResourceCPU, resource.MustParse("2")), 2324 corev1.ResourceMemory: workload.ResourceValue(corev1.ResourceMemory, resource.MustParse("8Gi")), 2325 }, 2326 "model-a": { 2327 "example.com/gpu": workload.ResourceValue("example.com/gpu", resource.MustParse("0")), 2328 }, 2329 }, 2330 admittedUsage: FlavorResourceQuantities{ 2331 "spot": { 2332 corev1.ResourceCPU: workload.ResourceValue(corev1.ResourceCPU, resource.MustParse("2")), 2333 corev1.ResourceMemory: workload.ResourceValue(corev1.ResourceMemory, resource.MustParse("8Gi")), 2334 }, 2335 "model-a": { 2336 "example.com/gpu": workload.ResourceValue("example.com/gpu", resource.MustParse("0")), 2337 }, 2338 }, 2339 }, 2340 "ns2/beta": { 2341 key: "ns2/beta", 2342 reservingWorkloads: 2, 2343 admittedWorkloads: 1, 2344 usage: FlavorResourceQuantities{ 2345 "spot": { 2346 corev1.ResourceCPU: workload.ResourceValue(corev1.ResourceCPU, resource.MustParse("0")), 2347 corev1.ResourceMemory: workload.ResourceValue(corev1.ResourceMemory, resource.MustParse("0")), 2348 }, 2349 "model-a": { 2350 "example.com/gpu": workload.ResourceValue("example.com/gpu", resource.MustParse("7")), 2351 }, 2352 }, 2353 admittedUsage: FlavorResourceQuantities{ 2354 "spot": { 2355 corev1.ResourceCPU: workload.ResourceValue(corev1.ResourceCPU, resource.MustParse("0")), 2356 corev1.ResourceMemory: workload.ResourceValue(corev1.ResourceMemory, resource.MustParse("0")), 2357 }, 2358 "model-a": { 2359 "example.com/gpu": workload.ResourceValue("example.com/gpu", resource.MustParse("2")), 2360 }, 2361 }, 2362 }, 2363 "ns1/gamma": { 2364 key: "ns1/gamma", 2365 reservingWorkloads: 1, 2366 admittedWorkloads: 0, 2367 usage: FlavorResourceQuantities{ 2368 "ondemand": { 2369 corev1.ResourceCPU: workload.ResourceValue(corev1.ResourceCPU, resource.MustParse("5")), 2370 corev1.ResourceMemory: workload.ResourceValue(corev1.ResourceMemory, resource.MustParse("16Gi")), 2371 }, 2372 "model-b": { 2373 "example.com/gpu": workload.ResourceValue("example.com/gpu", resource.MustParse("0")), 2374 }, 2375 }, 2376 admittedUsage: FlavorResourceQuantities{ 2377 "ondemand": { 2378 corev1.ResourceCPU: workload.ResourceValue(corev1.ResourceCPU, resource.MustParse("0")), 2379 corev1.ResourceMemory: workload.ResourceValue(corev1.ResourceMemory, resource.MustParse("0")), 2380 }, 2381 "model-b": { 2382 "example.com/gpu": workload.ResourceValue("example.com/gpu", resource.MustParse("0")), 2383 }, 2384 }, 2385 }, 2386 } 2387 cacheLocalQueuesAfterInsertingCqAndQ := map[string]*queue{ 2388 "ns1/alpha": { 2389 key: "ns1/alpha", 2390 reservingWorkloads: 0, 2391 admittedWorkloads: 0, 2392 usage: FlavorResourceQuantities{ 2393 "spot": { 2394 corev1.ResourceCPU: workload.ResourceValue(corev1.ResourceCPU, resource.MustParse("0")), 2395 corev1.ResourceMemory: workload.ResourceValue(corev1.ResourceMemory, resource.MustParse("0")), 2396 }, 2397 "model-a": { 2398 "example.com/gpu": workload.ResourceValue("example.com/gpu", resource.MustParse("0")), 2399 }, 2400 }, 2401 admittedUsage: FlavorResourceQuantities{ 2402 "spot": { 2403 corev1.ResourceCPU: workload.ResourceValue(corev1.ResourceCPU, resource.MustParse("0")), 2404 corev1.ResourceMemory: workload.ResourceValue(corev1.ResourceMemory, resource.MustParse("0")), 2405 }, 2406 "model-a": { 2407 "example.com/gpu": workload.ResourceValue("example.com/gpu", resource.MustParse("0")), 2408 }, 2409 }, 2410 }, 2411 "ns2/beta": { 2412 key: "ns2/beta", 2413 reservingWorkloads: 0, 2414 admittedWorkloads: 0, 2415 usage: FlavorResourceQuantities{ 2416 "spot": { 2417 corev1.ResourceCPU: workload.ResourceValue(corev1.ResourceCPU, resource.MustParse("0")), 2418 corev1.ResourceMemory: workload.ResourceValue(corev1.ResourceMemory, resource.MustParse("0")), 2419 }, 2420 "model-a": { 2421 "example.com/gpu": workload.ResourceValue("example.com/gpu", resource.MustParse("0")), 2422 }, 2423 }, 2424 admittedUsage: FlavorResourceQuantities{ 2425 "spot": { 2426 corev1.ResourceCPU: workload.ResourceValue(corev1.ResourceCPU, resource.MustParse("0")), 2427 corev1.ResourceMemory: workload.ResourceValue(corev1.ResourceMemory, resource.MustParse("0")), 2428 }, 2429 "model-a": { 2430 "example.com/gpu": workload.ResourceValue("example.com/gpu", resource.MustParse("0")), 2431 }, 2432 }, 2433 }, 2434 "ns1/gamma": { 2435 key: "ns1/gamma", 2436 reservingWorkloads: 0, 2437 admittedWorkloads: 0, 2438 usage: FlavorResourceQuantities{ 2439 "ondemand": { 2440 corev1.ResourceCPU: workload.ResourceValue(corev1.ResourceCPU, resource.MustParse("0")), 2441 corev1.ResourceMemory: workload.ResourceValue(corev1.ResourceMemory, resource.MustParse("0")), 2442 }, 2443 "model-b": { 2444 "example.com/gpu": workload.ResourceValue("example.com/gpu", resource.MustParse("0")), 2445 }, 2446 }, 2447 admittedUsage: FlavorResourceQuantities{ 2448 "ondemand": { 2449 corev1.ResourceCPU: workload.ResourceValue(corev1.ResourceCPU, resource.MustParse("0")), 2450 corev1.ResourceMemory: workload.ResourceValue(corev1.ResourceMemory, resource.MustParse("0")), 2451 }, 2452 "model-b": { 2453 "example.com/gpu": workload.ResourceValue("example.com/gpu", resource.MustParse("0")), 2454 }, 2455 }, 2456 }, 2457 } 2458 cases := map[string]struct { 2459 ops []func(context.Context, client.Client, *Cache) error 2460 wantLocalQueues map[string]*queue 2461 }{ 2462 "insert cqs, queues, workloads": { 2463 ops: []func(ctx context.Context, cl client.Client, cache *Cache) error{ 2464 insertAllClusterQueues, 2465 insertAllQueues, 2466 insertAllWorkloads, 2467 }, 2468 wantLocalQueues: cacheLocalQueuesAfterInsertingAll, 2469 }, 2470 "insert cqs, workloads but no queues": { 2471 ops: []func(context.Context, client.Client, *Cache) error{ 2472 insertAllClusterQueues, 2473 insertAllWorkloads, 2474 }, 2475 wantLocalQueues: map[string]*queue{}, 2476 }, 2477 "insert queues, workloads but no cqs": { 2478 ops: []func(context.Context, client.Client, *Cache) error{ 2479 insertAllQueues, 2480 insertAllWorkloads, 2481 }, 2482 wantLocalQueues: map[string]*queue{}, 2483 }, 2484 "insert queues last": { 2485 ops: []func(context.Context, client.Client, *Cache) error{ 2486 insertAllClusterQueues, 2487 insertAllWorkloads, 2488 insertAllQueues, 2489 }, 2490 wantLocalQueues: cacheLocalQueuesAfterInsertingAll, 2491 }, 2492 "insert cqs last": { 2493 ops: []func(context.Context, client.Client, *Cache) error{ 2494 insertAllQueues, 2495 insertAllWorkloads, 2496 insertAllClusterQueues, 2497 }, 2498 wantLocalQueues: cacheLocalQueuesAfterInsertingAll, 2499 }, 2500 "assume": { 2501 ops: []func(context.Context, client.Client, *Cache) error{ 2502 insertAllClusterQueues, 2503 insertAllQueues, 2504 func(ctx context.Context, cl client.Client, cache *Cache) error { 2505 wl := workloads[0].DeepCopy() 2506 if err := cl.Create(ctx, wl); err != nil { 2507 return err 2508 } 2509 return cache.AssumeWorkload(wl) 2510 }, 2511 }, 2512 wantLocalQueues: map[string]*queue{ 2513 "ns1/alpha": cacheLocalQueuesAfterInsertingAll["ns1/alpha"], 2514 "ns2/beta": cacheLocalQueuesAfterInsertingCqAndQ["ns2/beta"], 2515 "ns1/gamma": cacheLocalQueuesAfterInsertingCqAndQ["ns1/gamma"], 2516 }, 2517 }, 2518 "assume and forget": { 2519 ops: []func(context.Context, client.Client, *Cache) error{ 2520 insertAllClusterQueues, 2521 insertAllQueues, 2522 func(ctx context.Context, cl client.Client, cache *Cache) error { 2523 wl := workloads[0].DeepCopy() 2524 if err := cl.Create(ctx, wl); err != nil { 2525 return err 2526 } 2527 if err := cache.AssumeWorkload(wl); err != nil { 2528 return err 2529 } 2530 return cache.ForgetWorkload(wl) 2531 }, 2532 }, 2533 wantLocalQueues: map[string]*queue{ 2534 "ns1/alpha": cacheLocalQueuesAfterInsertingCqAndQ["ns1/alpha"], 2535 "ns2/beta": cacheLocalQueuesAfterInsertingCqAndQ["ns2/beta"], 2536 "ns1/gamma": cacheLocalQueuesAfterInsertingCqAndQ["ns1/gamma"], 2537 }, 2538 }, 2539 "delete workload": { 2540 ops: []func(ctx context.Context, cl client.Client, cache *Cache) error{ 2541 insertAllClusterQueues, 2542 insertAllQueues, 2543 insertAllWorkloads, 2544 func(ctx context.Context, cl client.Client, cache *Cache) error { 2545 return cache.DeleteWorkload(workloads[0]) 2546 }, 2547 }, 2548 wantLocalQueues: map[string]*queue{ 2549 "ns1/alpha": cacheLocalQueuesAfterInsertingCqAndQ["ns1/alpha"], 2550 "ns2/beta": cacheLocalQueuesAfterInsertingAll["ns2/beta"], 2551 "ns1/gamma": cacheLocalQueuesAfterInsertingAll["ns1/gamma"], 2552 }, 2553 }, 2554 "delete cq": { 2555 ops: []func(ctx context.Context, cl client.Client, cache *Cache) error{ 2556 insertAllClusterQueues, 2557 insertAllQueues, 2558 insertAllWorkloads, 2559 func(ctx context.Context, cl client.Client, cache *Cache) error { 2560 cache.DeleteClusterQueue(cqs[0]) 2561 return nil 2562 }, 2563 }, 2564 wantLocalQueues: map[string]*queue{ 2565 "ns1/gamma": cacheLocalQueuesAfterInsertingAll["ns1/gamma"], 2566 }, 2567 }, 2568 "delete queue": { 2569 ops: []func(ctx context.Context, cl client.Client, cache *Cache) error{ 2570 insertAllClusterQueues, 2571 insertAllQueues, 2572 insertAllWorkloads, 2573 func(ctx context.Context, cl client.Client, cache *Cache) error { 2574 cache.DeleteLocalQueue(queues[0]) 2575 return nil 2576 }, 2577 }, 2578 wantLocalQueues: map[string]*queue{ 2579 "ns2/beta": cacheLocalQueuesAfterInsertingAll["ns2/beta"], 2580 "ns1/gamma": cacheLocalQueuesAfterInsertingAll["ns1/gamma"], 2581 }, 2582 }, 2583 // Not tested: changing a workload's queue and changing a queue's cluster queue. 2584 // These operations should not be allowed by the webhook. 2585 } 2586 for name, tc := range cases { 2587 t.Run(name, func(t *testing.T) { 2588 cl := utiltesting.NewFakeClient() 2589 cache := New(cl) 2590 ctx := context.Background() 2591 for i, op := range tc.ops { 2592 if err := op(ctx, cl, cache); err != nil { 2593 t.Fatalf("Running op %d: %v", i, err) 2594 } 2595 } 2596 cacheQueues := make(map[string]*queue) 2597 for _, cacheCQ := range cache.clusterQueues { 2598 for qKey, cacheQ := range cacheCQ.localQueues { 2599 if _, ok := cacheQueues[qKey]; ok { 2600 t.Fatalf("The cache have a duplicated localQueue %q across multiple clusterQueues", qKey) 2601 } 2602 cacheQueues[qKey] = cacheQ 2603 } 2604 } 2605 if diff := cmp.Diff(tc.wantLocalQueues, cacheQueues, cmp.AllowUnexported(queue{})); diff != "" { 2606 t.Errorf("Unexpected localQueues (-want,+got):\n%s", diff) 2607 } 2608 }) 2609 } 2610 } 2611 2612 func TestClusterQueuesUsingFlavor(t *testing.T) { 2613 x86Rf := utiltesting.MakeResourceFlavor("x86").Obj() 2614 aarch64Rf := utiltesting.MakeResourceFlavor("aarch64").Obj() 2615 fooCq := utiltesting.MakeClusterQueue("fooCq"). 2616 ResourceGroup( 2617 *utiltesting.MakeFlavorQuotas("x86").Resource("cpu", "5").Obj()). 2618 Obj() 2619 barCq := utiltesting.MakeClusterQueue("barCq").Obj() 2620 fizzCq := utiltesting.MakeClusterQueue("fizzCq"). 2621 ResourceGroup( 2622 *utiltesting.MakeFlavorQuotas("x86").Resource("cpu", "5").Obj(), 2623 *utiltesting.MakeFlavorQuotas("aarch64").Resource("cpu", "3").Obj(), 2624 ). 2625 Obj() 2626 2627 tests := []struct { 2628 name string 2629 clusterQueues []*kueue.ClusterQueue 2630 wantInUseClusterQueueNames []string 2631 }{ 2632 { 2633 name: "single clusterQueue with flavor in use", 2634 clusterQueues: []*kueue.ClusterQueue{ 2635 fooCq, 2636 }, 2637 wantInUseClusterQueueNames: []string{fooCq.Name}, 2638 }, 2639 { 2640 name: "single clusterQueue with no flavor", 2641 clusterQueues: []*kueue.ClusterQueue{ 2642 barCq, 2643 }, 2644 }, 2645 { 2646 name: "multiple clusterQueues with flavor in use", 2647 clusterQueues: []*kueue.ClusterQueue{ 2648 fooCq, 2649 barCq, 2650 fizzCq, 2651 }, 2652 wantInUseClusterQueueNames: []string{fooCq.Name, fizzCq.Name}, 2653 }, 2654 } 2655 for _, tc := range tests { 2656 t.Run(tc.name, func(t *testing.T) { 2657 cache := New(utiltesting.NewFakeClient()) 2658 cache.AddOrUpdateResourceFlavor(x86Rf) 2659 cache.AddOrUpdateResourceFlavor(aarch64Rf) 2660 2661 for _, cq := range tc.clusterQueues { 2662 if err := cache.AddClusterQueue(context.Background(), cq); err != nil { 2663 t.Errorf("failed to add clusterQueue %s", cq.Name) 2664 } 2665 } 2666 2667 cqs := cache.ClusterQueuesUsingFlavor("x86") 2668 if diff := cmp.Diff(tc.wantInUseClusterQueueNames, cqs, cmpopts.SortSlices(func(a, b string) bool { 2669 return a < b 2670 })); len(diff) != 0 { 2671 t.Errorf("Unexpected flavor is in use by clusterQueues (-want,+got):\n%s", diff) 2672 } 2673 }) 2674 } 2675 } 2676 2677 func TestMatchingClusterQueues(t *testing.T) { 2678 clusterQueues := []*kueue.ClusterQueue{ 2679 utiltesting.MakeClusterQueue("matching1"). 2680 NamespaceSelector(&metav1.LabelSelector{}).Obj(), 2681 utiltesting.MakeClusterQueue("not-matching"). 2682 NamespaceSelector(nil).Obj(), 2683 utiltesting.MakeClusterQueue("matching2"). 2684 NamespaceSelector(&metav1.LabelSelector{ 2685 MatchExpressions: []metav1.LabelSelectorRequirement{ 2686 { 2687 Key: "dep", 2688 Operator: metav1.LabelSelectorOpIn, 2689 Values: []string{"eng"}, 2690 }, 2691 }, 2692 }).Obj(), 2693 } 2694 wantCQs := sets.New("matching1", "matching2") 2695 2696 cache := New(utiltesting.NewFakeClient()) 2697 for _, cq := range clusterQueues { 2698 if err := cache.AddClusterQueue(context.Background(), cq); err != nil { 2699 t.Errorf("failed to add clusterQueue %s", cq.Name) 2700 } 2701 } 2702 2703 gotCQs := cache.MatchingClusterQueues(map[string]string{"dep": "eng"}) 2704 if diff := cmp.Diff(wantCQs, gotCQs); diff != "" { 2705 t.Errorf("Wrong ClusterQueues (-want,+got):\n%s", diff) 2706 } 2707 } 2708 2709 // TestWaitForPodsReadyCancelled ensures that the WaitForPodsReady call does not block when the context is closed. 2710 func TestWaitForPodsReadyCancelled(t *testing.T) { 2711 cache := New(utiltesting.NewFakeClient(), WithPodsReadyTracking(true)) 2712 ctx, cancel := context.WithCancel(context.Background()) 2713 log := ctrl.LoggerFrom(ctx) 2714 2715 go cache.CleanUpOnContext(ctx) 2716 2717 cq := kueue.ClusterQueue{ 2718 ObjectMeta: metav1.ObjectMeta{Name: "one"}, 2719 } 2720 if err := cache.AddClusterQueue(ctx, &cq); err != nil { 2721 t.Fatalf("Failed adding clusterQueue: %v", err) 2722 } 2723 2724 wl := utiltesting.MakeWorkload("a", "").ReserveQuota(&kueue.Admission{ 2725 ClusterQueue: "one", 2726 }).Obj() 2727 if err := cache.AssumeWorkload(wl); err != nil { 2728 t.Fatalf("Failed assuming the workload to block the further admission: %v", err) 2729 } 2730 2731 if cache.PodsReadyForAllAdmittedWorkloads(log) { 2732 t.Fatalf("Unexpected that all admitted workloads are in PodsReady condition") 2733 } 2734 2735 // cancel the context so that the WaitForPodsReady is returns 2736 go cancel() 2737 2738 cache.WaitForPodsReady(ctx) 2739 } 2740 2741 // TestCachePodsReadyForAllAdmittedWorkloads verifies the condition used to determine whether to wait 2742 func TestCachePodsReadyForAllAdmittedWorkloads(t *testing.T) { 2743 clusterQueues := []kueue.ClusterQueue{ 2744 { 2745 ObjectMeta: metav1.ObjectMeta{Name: "one"}, 2746 }, 2747 { 2748 ObjectMeta: metav1.ObjectMeta{Name: "two"}, 2749 }, 2750 } 2751 2752 cl := utiltesting.NewFakeClient() 2753 2754 tests := []struct { 2755 name string 2756 setup func(cache *Cache) error 2757 operation func(cache *Cache) error 2758 wantReady bool 2759 }{ 2760 { 2761 name: "empty cache", 2762 operation: func(cache *Cache) error { return nil }, 2763 wantReady: true, 2764 }, 2765 { 2766 name: "add Workload without PodsReady condition", 2767 operation: func(cache *Cache) error { 2768 wl := utiltesting.MakeWorkload("a", "").ReserveQuota(&kueue.Admission{ 2769 ClusterQueue: "one", 2770 }).Obj() 2771 cache.AddOrUpdateWorkload(wl) 2772 return nil 2773 }, 2774 wantReady: false, 2775 }, 2776 { 2777 name: "add Workload with PodsReady=False", 2778 operation: func(cache *Cache) error { 2779 wl := utiltesting.MakeWorkload("a", "").ReserveQuota(&kueue.Admission{ 2780 ClusterQueue: "one", 2781 }).Condition(metav1.Condition{ 2782 Type: kueue.WorkloadPodsReady, 2783 Status: metav1.ConditionFalse, 2784 }).Obj() 2785 cache.AddOrUpdateWorkload(wl) 2786 return nil 2787 }, 2788 wantReady: false, 2789 }, 2790 { 2791 name: "add Workload with PodsReady=True", 2792 operation: func(cache *Cache) error { 2793 wl := utiltesting.MakeWorkload("a", "").ReserveQuota(&kueue.Admission{ 2794 ClusterQueue: "one", 2795 }).Condition(metav1.Condition{ 2796 Type: kueue.WorkloadPodsReady, 2797 Status: metav1.ConditionTrue, 2798 }).Obj() 2799 cache.AddOrUpdateWorkload(wl) 2800 return nil 2801 }, 2802 wantReady: true, 2803 }, 2804 { 2805 name: "assume Workload without PodsReady condition", 2806 operation: func(cache *Cache) error { 2807 wl := utiltesting.MakeWorkload("a", "").ReserveQuota(&kueue.Admission{ 2808 ClusterQueue: "one", 2809 }).Obj() 2810 return cache.AssumeWorkload(wl) 2811 }, 2812 wantReady: false, 2813 }, 2814 { 2815 name: "assume Workload with PodsReady=False", 2816 operation: func(cache *Cache) error { 2817 wl := utiltesting.MakeWorkload("a", "").ReserveQuota(&kueue.Admission{ 2818 ClusterQueue: "one", 2819 }).Condition(metav1.Condition{ 2820 Type: kueue.WorkloadPodsReady, 2821 Status: metav1.ConditionFalse, 2822 }).Obj() 2823 return cache.AssumeWorkload(wl) 2824 }, 2825 wantReady: false, 2826 }, 2827 { 2828 name: "assume Workload with PodsReady=True", 2829 operation: func(cache *Cache) error { 2830 wl := utiltesting.MakeWorkload("a", "").ReserveQuota(&kueue.Admission{ 2831 ClusterQueue: "one", 2832 }).Condition(metav1.Condition{ 2833 Type: kueue.WorkloadPodsReady, 2834 Status: metav1.ConditionTrue, 2835 }).Obj() 2836 return cache.AssumeWorkload(wl) 2837 }, 2838 wantReady: true, 2839 }, 2840 { 2841 name: "update workload to have PodsReady=True", 2842 setup: func(cache *Cache) error { 2843 wl := utiltesting.MakeWorkload("a", "").ReserveQuota(&kueue.Admission{ 2844 ClusterQueue: "one", 2845 }).Obj() 2846 cache.AddOrUpdateWorkload(wl) 2847 return nil 2848 }, 2849 operation: func(cache *Cache) error { 2850 wl := cache.clusterQueues["one"].Workloads["/a"].Obj 2851 newWl := wl.DeepCopy() 2852 apimeta.SetStatusCondition(&newWl.Status.Conditions, metav1.Condition{ 2853 Type: kueue.WorkloadPodsReady, 2854 Status: metav1.ConditionTrue, 2855 }) 2856 return cache.UpdateWorkload(wl, newWl) 2857 }, 2858 wantReady: true, 2859 }, 2860 { 2861 name: "update workload to have PodsReady=False", 2862 setup: func(cache *Cache) error { 2863 wl := utiltesting.MakeWorkload("a", "").ReserveQuota(&kueue.Admission{ 2864 ClusterQueue: "one", 2865 }).Condition(metav1.Condition{ 2866 Type: kueue.WorkloadPodsReady, 2867 Status: metav1.ConditionTrue, 2868 }).Obj() 2869 cache.AddOrUpdateWorkload(wl) 2870 return nil 2871 }, 2872 operation: func(cache *Cache) error { 2873 wl := cache.clusterQueues["one"].Workloads["/a"].Obj 2874 newWl := wl.DeepCopy() 2875 apimeta.SetStatusCondition(&newWl.Status.Conditions, metav1.Condition{ 2876 Type: kueue.WorkloadPodsReady, 2877 Status: metav1.ConditionFalse, 2878 }) 2879 return cache.UpdateWorkload(wl, newWl) 2880 }, 2881 wantReady: false, 2882 }, 2883 { 2884 name: "assume second workload without PodsReady", 2885 setup: func(cache *Cache) error { 2886 wl1 := utiltesting.MakeWorkload("a", "").ReserveQuota(&kueue.Admission{ 2887 ClusterQueue: "one", 2888 }).Condition(metav1.Condition{ 2889 Type: kueue.WorkloadPodsReady, 2890 Status: metav1.ConditionTrue, 2891 }).Obj() 2892 cache.AddOrUpdateWorkload(wl1) 2893 return nil 2894 }, 2895 operation: func(cache *Cache) error { 2896 wl2 := utiltesting.MakeWorkload("b", "").ReserveQuota(&kueue.Admission{ 2897 ClusterQueue: "two", 2898 }).Obj() 2899 return cache.AssumeWorkload(wl2) 2900 }, 2901 wantReady: false, 2902 }, 2903 { 2904 name: "update second workload to have PodsReady=True", 2905 setup: func(cache *Cache) error { 2906 wl1 := utiltesting.MakeWorkload("a", "").ReserveQuota(&kueue.Admission{ 2907 ClusterQueue: "one", 2908 }).Condition(metav1.Condition{ 2909 Type: kueue.WorkloadPodsReady, 2910 Status: metav1.ConditionTrue, 2911 }).Obj() 2912 cache.AddOrUpdateWorkload(wl1) 2913 wl2 := utiltesting.MakeWorkload("b", "").ReserveQuota(&kueue.Admission{ 2914 ClusterQueue: "two", 2915 }).Obj() 2916 cache.AddOrUpdateWorkload(wl2) 2917 return nil 2918 }, 2919 operation: func(cache *Cache) error { 2920 wl2 := cache.clusterQueues["two"].Workloads["/b"].Obj 2921 newWl2 := wl2.DeepCopy() 2922 apimeta.SetStatusCondition(&newWl2.Status.Conditions, metav1.Condition{ 2923 Type: kueue.WorkloadPodsReady, 2924 Status: metav1.ConditionTrue, 2925 }) 2926 return cache.UpdateWorkload(wl2, newWl2) 2927 }, 2928 wantReady: true, 2929 }, 2930 { 2931 name: "delete workload with PodsReady=False", 2932 setup: func(cache *Cache) error { 2933 wl := utiltesting.MakeWorkload("a", "").ReserveQuota(&kueue.Admission{ 2934 ClusterQueue: "one", 2935 }).Condition(metav1.Condition{ 2936 Type: kueue.WorkloadPodsReady, 2937 Status: metav1.ConditionFalse, 2938 }).Obj() 2939 cache.AddOrUpdateWorkload(wl) 2940 return nil 2941 }, 2942 operation: func(cache *Cache) error { 2943 wl := cache.clusterQueues["one"].Workloads["/a"].Obj 2944 return cache.DeleteWorkload(wl) 2945 }, 2946 wantReady: true, 2947 }, 2948 { 2949 name: "forget workload with PodsReady=False", 2950 setup: func(cache *Cache) error { 2951 wl := utiltesting.MakeWorkload("a", "").ReserveQuota(&kueue.Admission{ 2952 ClusterQueue: "one", 2953 }).Condition(metav1.Condition{ 2954 Type: kueue.WorkloadPodsReady, 2955 Status: metav1.ConditionFalse, 2956 }).Obj() 2957 return cache.AssumeWorkload(wl) 2958 }, 2959 operation: func(cache *Cache) error { 2960 wl := cache.clusterQueues["one"].Workloads["/a"].Obj 2961 return cache.ForgetWorkload(wl) 2962 }, 2963 wantReady: true, 2964 }, 2965 } 2966 for _, tc := range tests { 2967 t.Run(tc.name, func(t *testing.T) { 2968 cache := New(cl, WithPodsReadyTracking(true)) 2969 ctx := context.Background() 2970 log := ctrl.LoggerFrom(ctx) 2971 2972 for _, c := range clusterQueues { 2973 if err := cache.AddClusterQueue(ctx, &c); err != nil { 2974 t.Fatalf("Failed adding clusterQueue: %v", err) 2975 } 2976 } 2977 if tc.setup != nil { 2978 if err := tc.setup(cache); err != nil { 2979 t.Errorf("Unexpected error during setup: %q", err) 2980 } 2981 } 2982 if err := tc.operation(cache); err != nil { 2983 t.Errorf("Unexpected error during operation: %q", err) 2984 } 2985 gotReady := cache.PodsReadyForAllAdmittedWorkloads(log) 2986 if diff := cmp.Diff(tc.wantReady, gotReady); diff != "" { 2987 t.Errorf("Unexpected response about workloads without pods ready (-want,+got):\n%s", diff) 2988 } 2989 // verify that the WaitForPodsReady is non-blocking when podsReadyForAllAdmittedWorkloads returns true 2990 if gotReady { 2991 cache.WaitForPodsReady(ctx) 2992 } 2993 }) 2994 } 2995 } 2996 2997 // TestIsAssumedOrAdmittedCheckWorkload verifies if workload is in Assumed map from cache or if it is Admitted in one ClusterQueue 2998 func TestIsAssumedOrAdmittedCheckWorkload(t *testing.T) { 2999 tests := []struct { 3000 name string 3001 cache *Cache 3002 workload workload.Info 3003 expected bool 3004 }{ 3005 { 3006 name: "Workload Is Assumed and not Admitted", 3007 cache: &Cache{ 3008 assumedWorkloads: map[string]string{"workload_namespace/workload_name": "test", "test2": "test2"}, 3009 }, 3010 workload: workload.Info{ 3011 ClusterQueue: "ClusterQueue1", 3012 Obj: &kueue.Workload{ 3013 ObjectMeta: metav1.ObjectMeta{ 3014 Name: "workload_name", 3015 Namespace: "workload_namespace", 3016 }, 3017 }, 3018 }, 3019 expected: true, 3020 }, { 3021 name: "Workload Is not Assumed but is Admitted", 3022 cache: &Cache{ 3023 clusterQueues: map[string]*ClusterQueue{ 3024 "ClusterQueue1": { 3025 Name: "ClusterQueue1", 3026 Workloads: map[string]*workload.Info{"workload_namespace/workload_name": { 3027 Obj: &kueue.Workload{ 3028 ObjectMeta: metav1.ObjectMeta{ 3029 Name: "workload_name", 3030 Namespace: "workload_namespace", 3031 }, 3032 }, 3033 }}, 3034 }}, 3035 }, 3036 3037 workload: workload.Info{ 3038 ClusterQueue: "ClusterQueue1", 3039 Obj: &kueue.Workload{ 3040 ObjectMeta: metav1.ObjectMeta{ 3041 Name: "workload_name", 3042 Namespace: "workload_namespace", 3043 }, 3044 }, 3045 }, 3046 expected: true, 3047 }, { 3048 name: "Workload Is Assumed and Admitted", 3049 cache: &Cache{ 3050 clusterQueues: map[string]*ClusterQueue{ 3051 "ClusterQueue1": { 3052 Name: "ClusterQueue1", 3053 Workloads: map[string]*workload.Info{"workload_namespace/workload_name": { 3054 Obj: &kueue.Workload{ 3055 ObjectMeta: metav1.ObjectMeta{ 3056 Name: "workload_name", 3057 Namespace: "workload_namespace", 3058 }, 3059 }, 3060 }}, 3061 }}, 3062 assumedWorkloads: map[string]string{"workload_namespace/workload_name": "test", "test2": "test2"}, 3063 }, 3064 workload: workload.Info{ 3065 ClusterQueue: "ClusterQueue1", 3066 Obj: &kueue.Workload{ 3067 ObjectMeta: metav1.ObjectMeta{ 3068 Name: "workload_name", 3069 Namespace: "workload_namespace", 3070 }, 3071 }, 3072 }, 3073 expected: true, 3074 }, { 3075 name: "Workload Is not Assumed and is not Admitted", 3076 cache: &Cache{ 3077 clusterQueues: map[string]*ClusterQueue{ 3078 "ClusterQueue1": { 3079 Name: "ClusterQueue1", 3080 Workloads: map[string]*workload.Info{"workload_namespace2/workload_name2": { 3081 Obj: &kueue.Workload{ 3082 ObjectMeta: metav1.ObjectMeta{ 3083 Name: "workload_name2", 3084 Namespace: "workload_namespace2", 3085 }, 3086 }, 3087 }}, 3088 }}, 3089 }, 3090 workload: workload.Info{ 3091 ClusterQueue: "ClusterQueue1", 3092 Obj: &kueue.Workload{ 3093 ObjectMeta: metav1.ObjectMeta{ 3094 Name: "workload_name", 3095 Namespace: "workload_namespace", 3096 }, 3097 }, 3098 }, 3099 expected: false, 3100 }, 3101 } 3102 for _, tc := range tests { 3103 t.Run(tc.name, func(t *testing.T) { 3104 if tc.cache.IsAssumedOrAdmittedWorkload(tc.workload) != tc.expected { 3105 t.Error("Unexpected response") 3106 } 3107 }) 3108 } 3109 } 3110 3111 func messageOrEmpty(err error) string { 3112 if err == nil { 3113 return "" 3114 } 3115 return err.Error() 3116 } 3117 3118 func TestClusterQueuesUsingAdmissionChecks(t *testing.T) { 3119 admissionCheck1 := utiltesting.MakeAdmissionCheck("ac1").Obj() 3120 admissionCheck2 := utiltesting.MakeAdmissionCheck("ac2").Obj() 3121 fooCq := utiltesting.MakeClusterQueue("fooCq"). 3122 AdmissionChecks(admissionCheck1.Name). 3123 Obj() 3124 barCq := utiltesting.MakeClusterQueue("barCq").Obj() 3125 fizzCq := utiltesting.MakeClusterQueue("fizzCq"). 3126 AdmissionChecks(admissionCheck1.Name, admissionCheck2.Name). 3127 Obj() 3128 3129 cases := map[string]struct { 3130 clusterQueues []*kueue.ClusterQueue 3131 wantInUseClusterQueueNames []string 3132 }{ 3133 "single clusterQueue with check in use": { 3134 clusterQueues: []*kueue.ClusterQueue{ 3135 fooCq, 3136 }, 3137 wantInUseClusterQueueNames: []string{fooCq.Name}, 3138 }, 3139 "single clusterQueue with no checks": { 3140 clusterQueues: []*kueue.ClusterQueue{ 3141 barCq, 3142 }, 3143 }, 3144 "multiple clusterQueues with checks in use": { 3145 clusterQueues: []*kueue.ClusterQueue{ 3146 fooCq, 3147 barCq, 3148 fizzCq, 3149 }, 3150 wantInUseClusterQueueNames: []string{fooCq.Name, fizzCq.Name}, 3151 }, 3152 } 3153 for name, tc := range cases { 3154 t.Run(name, func(t *testing.T) { 3155 cache := New(utiltesting.NewFakeClient()) 3156 cache.AddOrUpdateAdmissionCheck(admissionCheck1) 3157 cache.AddOrUpdateAdmissionCheck(admissionCheck2) 3158 3159 for _, cq := range tc.clusterQueues { 3160 if err := cache.AddClusterQueue(context.Background(), cq); err != nil { 3161 t.Errorf("failed to add clusterQueue %s", cq.Name) 3162 } 3163 } 3164 3165 cqs := cache.ClusterQueuesUsingAdmissionCheck(admissionCheck1.Name) 3166 if diff := cmp.Diff(tc.wantInUseClusterQueueNames, cqs, cmpopts.SortSlices(func(a, b string) bool { 3167 return a < b 3168 })); len(diff) != 0 { 3169 t.Errorf("Unexpected flavor is in use by clusterQueues (-want,+got):\n%s", diff) 3170 } 3171 }) 3172 } 3173 } 3174 3175 func TestClusterQueueReadiness(t *testing.T) { 3176 baseFalvor := utiltesting.MakeResourceFlavor("flavor1").Obj() 3177 baseCheck := utiltesting.MakeAdmissionCheck("check1").Active(metav1.ConditionTrue).Obj() 3178 baseQueue := utiltesting.MakeClusterQueue("queue1"). 3179 ResourceGroup( 3180 *utiltesting.MakeFlavorQuotas(baseFalvor.Name). 3181 Resource(corev1.ResourceCPU, "10", "10").Obj()). 3182 AdmissionChecks(baseCheck.Name). 3183 Obj() 3184 3185 cases := map[string]struct { 3186 clusterQueues []*kueue.ClusterQueue 3187 resourceFlavors []*kueue.ResourceFlavor 3188 admissionChecks []*kueue.AdmissionCheck 3189 clusterQueueName string 3190 terminate bool 3191 wantStatus metav1.ConditionStatus 3192 wantReason string 3193 wantMessage string 3194 wantActive bool 3195 wantTerminating bool 3196 }{ 3197 "queue not found": { 3198 clusterQueueName: "queue1", 3199 wantStatus: metav1.ConditionFalse, 3200 wantReason: "NotFound", 3201 wantMessage: "ClusterQueue not found", 3202 }, 3203 "flavor not found": { 3204 clusterQueues: []*kueue.ClusterQueue{baseQueue}, 3205 admissionChecks: []*kueue.AdmissionCheck{baseCheck}, 3206 clusterQueueName: "queue1", 3207 wantStatus: metav1.ConditionFalse, 3208 wantReason: "FlavorNotFound", 3209 wantMessage: "Can't admit new workloads: FlavorNotFound", 3210 }, 3211 "check not found": { 3212 clusterQueues: []*kueue.ClusterQueue{baseQueue}, 3213 resourceFlavors: []*kueue.ResourceFlavor{baseFalvor}, 3214 clusterQueueName: "queue1", 3215 wantStatus: metav1.ConditionFalse, 3216 wantReason: "CheckNotFoundOrInactive", 3217 wantMessage: "Can't admit new workloads: CheckNotFoundOrInactive", 3218 }, 3219 "check inactive": { 3220 clusterQueues: []*kueue.ClusterQueue{baseQueue}, 3221 resourceFlavors: []*kueue.ResourceFlavor{baseFalvor}, 3222 admissionChecks: []*kueue.AdmissionCheck{utiltesting.MakeAdmissionCheck("check1").Obj()}, 3223 clusterQueueName: "queue1", 3224 wantStatus: metav1.ConditionFalse, 3225 wantReason: "CheckNotFoundOrInactive", 3226 wantMessage: "Can't admit new workloads: CheckNotFoundOrInactive", 3227 }, 3228 "flavor and check not found": { 3229 clusterQueues: []*kueue.ClusterQueue{baseQueue}, 3230 clusterQueueName: "queue1", 3231 wantStatus: metav1.ConditionFalse, 3232 wantReason: "FlavorNotFound", 3233 wantMessage: "Can't admit new workloads: FlavorNotFound, CheckNotFoundOrInactive", 3234 }, 3235 "terminating": { 3236 clusterQueues: []*kueue.ClusterQueue{baseQueue}, 3237 admissionChecks: []*kueue.AdmissionCheck{baseCheck}, 3238 resourceFlavors: []*kueue.ResourceFlavor{baseFalvor}, 3239 clusterQueueName: "queue1", 3240 terminate: true, 3241 wantStatus: metav1.ConditionFalse, 3242 wantReason: "Terminating", 3243 wantMessage: "Can't admit new workloads; clusterQueue is terminating", 3244 wantTerminating: true, 3245 }, 3246 "ready": { 3247 clusterQueues: []*kueue.ClusterQueue{baseQueue}, 3248 admissionChecks: []*kueue.AdmissionCheck{baseCheck}, 3249 resourceFlavors: []*kueue.ResourceFlavor{baseFalvor}, 3250 clusterQueueName: "queue1", 3251 wantStatus: metav1.ConditionTrue, 3252 wantReason: "Ready", 3253 wantMessage: "Can admit new workloads", 3254 wantActive: true, 3255 }, 3256 "stopped": { 3257 clusterQueues: []*kueue.ClusterQueue{utiltesting.MakeClusterQueue("queue1").StopPolicy(kueue.HoldAndDrain).Obj()}, 3258 clusterQueueName: "queue1", 3259 wantStatus: metav1.ConditionFalse, 3260 wantReason: "Stopped", 3261 wantMessage: "Can't admit new workloads: Stopped", 3262 }, 3263 } 3264 3265 for name, tc := range cases { 3266 t.Run(name, func(t *testing.T) { 3267 cache := New(utiltesting.NewFakeClient()) 3268 for _, rf := range tc.resourceFlavors { 3269 cache.AddOrUpdateResourceFlavor(rf) 3270 } 3271 for _, ac := range tc.admissionChecks { 3272 cache.AddOrUpdateAdmissionCheck(ac) 3273 } 3274 for _, cq := range tc.clusterQueues { 3275 if err := cache.AddClusterQueue(context.Background(), cq); err != nil { 3276 t.Errorf("failed to add clusterQueue %q: %v", cq.Name, err) 3277 } 3278 } 3279 3280 if tc.terminate { 3281 cache.TerminateClusterQueue(tc.clusterQueueName) 3282 } 3283 3284 gotStatus, gotReason, gotMessage := cache.ClusterQueueReadiness(tc.clusterQueueName) 3285 3286 if diff := cmp.Diff(tc.wantStatus, gotStatus); len(diff) != 0 { 3287 t.Errorf("Unexpected status (-want,+got):\n%s", diff) 3288 } 3289 if diff := cmp.Diff(tc.wantReason, gotReason); len(diff) != 0 { 3290 t.Errorf("Unexpected reason (-want,+got):\n%s", diff) 3291 } 3292 if diff := cmp.Diff(tc.wantMessage, gotMessage); len(diff) != 0 { 3293 t.Errorf("Unexpected message (-want,+got):\n%s", diff) 3294 } 3295 3296 if gotActive := cache.ClusterQueueActive(tc.clusterQueueName); gotActive != tc.wantActive { 3297 t.Errorf("Unexpected active state %v", gotActive) 3298 } 3299 3300 if gotTerminating := cache.ClusterQueueTerminating(tc.clusterQueueName); gotTerminating != tc.wantTerminating { 3301 t.Errorf("Unexpected terminating state %v", gotTerminating) 3302 } 3303 }) 3304 } 3305 }