sigs.k8s.io/kueue@v0.6.2/pkg/cache/snapshot_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 "testing" 22 23 "github.com/google/go-cmp/cmp" 24 "github.com/google/go-cmp/cmp/cmpopts" 25 corev1 "k8s.io/api/core/v1" 26 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 27 "k8s.io/apimachinery/pkg/labels" 28 "k8s.io/apimachinery/pkg/util/sets" 29 "k8s.io/utils/ptr" 30 31 kueue "sigs.k8s.io/kueue/apis/kueue/v1beta1" 32 "sigs.k8s.io/kueue/pkg/features" 33 utiltesting "sigs.k8s.io/kueue/pkg/util/testing" 34 "sigs.k8s.io/kueue/pkg/workload" 35 ) 36 37 var snapCmpOpts = []cmp.Option{ 38 cmpopts.EquateEmpty(), 39 cmpopts.IgnoreUnexported(ClusterQueue{}), 40 cmpopts.IgnoreFields(ClusterQueue{}, "RGByResource"), 41 cmpopts.IgnoreFields(Cohort{}, "Members"), // avoid recursion. 42 cmpopts.IgnoreFields(metav1.Condition{}, "LastTransitionTime"), 43 } 44 45 func TestSnapshot(t *testing.T) { 46 testCases := map[string]struct { 47 cqs []*kueue.ClusterQueue 48 rfs []*kueue.ResourceFlavor 49 wls []*kueue.Workload 50 wantSnapshot Snapshot 51 enableLendingLimit bool 52 }{ 53 "empty": {}, 54 "independent clusterQueues": { 55 cqs: []*kueue.ClusterQueue{ 56 utiltesting.MakeClusterQueue("a").Obj(), 57 utiltesting.MakeClusterQueue("b").Obj(), 58 }, 59 wls: []*kueue.Workload{ 60 utiltesting.MakeWorkload("alpha", ""). 61 ReserveQuota(&kueue.Admission{ClusterQueue: "a"}).Obj(), 62 utiltesting.MakeWorkload("beta", ""). 63 ReserveQuota(&kueue.Admission{ClusterQueue: "b"}).Obj(), 64 }, 65 wantSnapshot: Snapshot{ 66 ClusterQueues: map[string]*ClusterQueue{ 67 "a": { 68 Name: "a", 69 NamespaceSelector: labels.Everything(), 70 Status: active, 71 FlavorFungibility: defaultFlavorFungibility, 72 AllocatableResourceGeneration: 1, 73 Workloads: map[string]*workload.Info{ 74 "/alpha": workload.NewInfo( 75 utiltesting.MakeWorkload("alpha", ""). 76 ReserveQuota(&kueue.Admission{ClusterQueue: "a"}).Obj()), 77 }, 78 Preemption: defaultPreemption, 79 }, 80 "b": { 81 Name: "b", 82 NamespaceSelector: labels.Everything(), 83 Status: active, 84 FlavorFungibility: defaultFlavorFungibility, 85 AllocatableResourceGeneration: 1, 86 Workloads: map[string]*workload.Info{ 87 "/beta": workload.NewInfo( 88 utiltesting.MakeWorkload("beta", ""). 89 ReserveQuota(&kueue.Admission{ClusterQueue: "b"}).Obj()), 90 }, 91 Preemption: defaultPreemption, 92 }, 93 }, 94 }, 95 }, 96 "inactive clusterQueues": { 97 cqs: []*kueue.ClusterQueue{ 98 utiltesting.MakeClusterQueue("flavor-nonexistent-cq"). 99 ResourceGroup(*utiltesting.MakeFlavorQuotas("nonexistent-flavor"). 100 Resource(corev1.ResourceCPU, "100").Obj()). 101 Obj(), 102 }, 103 wantSnapshot: Snapshot{ 104 InactiveClusterQueueSets: sets.New("flavor-nonexistent-cq"), 105 }, 106 }, 107 "resourceFlavors": { 108 rfs: []*kueue.ResourceFlavor{ 109 utiltesting.MakeResourceFlavor("demand"). 110 Label("a", "b"). 111 Label("instance", "demand"). 112 Obj(), 113 utiltesting.MakeResourceFlavor("spot"). 114 Label("c", "d"). 115 Label("instance", "spot"). 116 Obj(), 117 utiltesting.MakeResourceFlavor("default").Obj(), 118 }, 119 wantSnapshot: Snapshot{ 120 ClusterQueues: map[string]*ClusterQueue{}, 121 ResourceFlavors: map[kueue.ResourceFlavorReference]*kueue.ResourceFlavor{ 122 "demand": utiltesting.MakeResourceFlavor("demand"). 123 Label("a", "b"). 124 Label("instance", "demand"). 125 Obj(), 126 "spot": utiltesting.MakeResourceFlavor("spot"). 127 Label("c", "d"). 128 Label("instance", "spot"). 129 Obj(), 130 "default": utiltesting.MakeResourceFlavor("default").Obj(), 131 }, 132 }, 133 }, 134 "cohort": { 135 cqs: []*kueue.ClusterQueue{ 136 utiltesting.MakeClusterQueue("a"). 137 Cohort("borrowing"). 138 ResourceGroup( 139 *utiltesting.MakeFlavorQuotas("demand").Resource(corev1.ResourceCPU, "100").Obj(), 140 *utiltesting.MakeFlavorQuotas("spot").Resource(corev1.ResourceCPU, "200").Obj(), 141 ). 142 Obj(), 143 utiltesting.MakeClusterQueue("b"). 144 Cohort("borrowing"). 145 ResourceGroup( 146 *utiltesting.MakeFlavorQuotas("spot").Resource(corev1.ResourceCPU, "100").Obj(), 147 ). 148 ResourceGroup( 149 *utiltesting.MakeFlavorQuotas("default").Resource("example.com/gpu", "50").Obj(), 150 ). 151 Obj(), 152 utiltesting.MakeClusterQueue("c"). 153 ResourceGroup( 154 *utiltesting.MakeFlavorQuotas("default").Resource(corev1.ResourceCPU, "100").Obj(), 155 ). 156 Obj(), 157 }, 158 rfs: []*kueue.ResourceFlavor{ 159 utiltesting.MakeResourceFlavor("demand").Label("instance", "demand").Obj(), 160 utiltesting.MakeResourceFlavor("spot").Label("instance", "spot").Obj(), 161 utiltesting.MakeResourceFlavor("default").Obj(), 162 }, 163 wls: []*kueue.Workload{ 164 utiltesting.MakeWorkload("alpha", ""). 165 PodSets(*utiltesting.MakePodSet("main", 5). 166 Request(corev1.ResourceCPU, "2").Obj()). 167 ReserveQuota(utiltesting.MakeAdmission("a", "main").Assignment(corev1.ResourceCPU, "demand", "10000m").AssignmentPodCount(5).Obj()). 168 Obj(), 169 utiltesting.MakeWorkload("beta", ""). 170 PodSets(*utiltesting.MakePodSet("main", 5). 171 Request(corev1.ResourceCPU, "1"). 172 Request("example.com/gpu", "2"). 173 Obj(), 174 ). 175 ReserveQuota(utiltesting.MakeAdmission("b", "main"). 176 Assignment(corev1.ResourceCPU, "spot", "5000m"). 177 Assignment("example.com/gpu", "default", "10"). 178 AssignmentPodCount(5). 179 Obj()). 180 Obj(), 181 utiltesting.MakeWorkload("gamma", ""). 182 PodSets(*utiltesting.MakePodSet("main", 5). 183 Request(corev1.ResourceCPU, "1"). 184 Request("example.com/gpu", "1"). 185 Obj(), 186 ). 187 ReserveQuota(utiltesting.MakeAdmission("b", "main"). 188 Assignment(corev1.ResourceCPU, "spot", "5000m"). 189 Assignment("example.com/gpu", "default", "5"). 190 AssignmentPodCount(5). 191 Obj()). 192 Obj(), 193 utiltesting.MakeWorkload("sigma", ""). 194 PodSets(*utiltesting.MakePodSet("main", 5). 195 Request(corev1.ResourceCPU, "1"). 196 Obj(), 197 ). 198 Obj(), 199 }, 200 wantSnapshot: func() Snapshot { 201 cohort := &Cohort{ 202 Name: "borrowing", 203 AllocatableResourceGeneration: 2, 204 RequestableResources: FlavorResourceQuantities{ 205 "demand": { 206 corev1.ResourceCPU: 100_000, 207 }, 208 "spot": { 209 corev1.ResourceCPU: 300_000, 210 }, 211 "default": { 212 "example.com/gpu": 50, 213 }, 214 }, 215 Usage: FlavorResourceQuantities{ 216 "demand": { 217 corev1.ResourceCPU: 10_000, 218 }, 219 "spot": { 220 corev1.ResourceCPU: 10_000, 221 }, 222 "default": { 223 "example.com/gpu": 15, 224 }, 225 }, 226 } 227 return Snapshot{ 228 ClusterQueues: map[string]*ClusterQueue{ 229 "a": { 230 Name: "a", 231 Cohort: cohort, 232 AllocatableResourceGeneration: 1, 233 ResourceGroups: []ResourceGroup{ 234 { 235 CoveredResources: sets.New(corev1.ResourceCPU), 236 Flavors: []FlavorQuotas{ 237 { 238 Name: "demand", 239 Resources: map[corev1.ResourceName]*ResourceQuota{ 240 corev1.ResourceCPU: {Nominal: 100_000}, 241 }, 242 }, 243 { 244 Name: "spot", 245 Resources: map[corev1.ResourceName]*ResourceQuota{ 246 corev1.ResourceCPU: {Nominal: 200_000}, 247 }, 248 }, 249 }, 250 LabelKeys: sets.New("instance"), 251 }, 252 }, 253 FlavorFungibility: defaultFlavorFungibility, 254 Usage: FlavorResourceQuantities{ 255 "demand": {corev1.ResourceCPU: 10_000}, 256 "spot": {corev1.ResourceCPU: 0}, 257 }, 258 Workloads: map[string]*workload.Info{ 259 "/alpha": workload.NewInfo(utiltesting.MakeWorkload("alpha", ""). 260 PodSets(*utiltesting.MakePodSet("main", 5). 261 Request(corev1.ResourceCPU, "2").Obj()). 262 ReserveQuota(utiltesting.MakeAdmission("a", "main"). 263 Assignment(corev1.ResourceCPU, "demand", "10000m"). 264 AssignmentPodCount(5). 265 Obj()). 266 Obj()), 267 }, 268 Preemption: defaultPreemption, 269 NamespaceSelector: labels.Everything(), 270 Status: active, 271 }, 272 "b": { 273 Name: "b", 274 Cohort: cohort, 275 AllocatableResourceGeneration: 1, 276 ResourceGroups: []ResourceGroup{ 277 { 278 CoveredResources: sets.New(corev1.ResourceCPU), 279 Flavors: []FlavorQuotas{{ 280 Name: "spot", 281 Resources: map[corev1.ResourceName]*ResourceQuota{ 282 corev1.ResourceCPU: {Nominal: 100_000}, 283 }, 284 }}, 285 LabelKeys: sets.New("instance"), 286 }, 287 { 288 CoveredResources: sets.New[corev1.ResourceName]("example.com/gpu"), 289 Flavors: []FlavorQuotas{{ 290 Name: "default", 291 Resources: map[corev1.ResourceName]*ResourceQuota{ 292 "example.com/gpu": {Nominal: 50}, 293 }, 294 }}, 295 }, 296 }, 297 FlavorFungibility: defaultFlavorFungibility, 298 Usage: FlavorResourceQuantities{ 299 "spot": { 300 corev1.ResourceCPU: 10_000, 301 }, 302 "default": { 303 "example.com/gpu": 15, 304 }, 305 }, 306 Workloads: map[string]*workload.Info{ 307 "/beta": workload.NewInfo(utiltesting.MakeWorkload("beta", ""). 308 PodSets(*utiltesting.MakePodSet("main", 5). 309 Request(corev1.ResourceCPU, "1"). 310 Request("example.com/gpu", "2"). 311 Obj()). 312 ReserveQuota(utiltesting.MakeAdmission("b", "main"). 313 Assignment(corev1.ResourceCPU, "spot", "5000m"). 314 Assignment("example.com/gpu", "default", "10"). 315 AssignmentPodCount(5). 316 Obj()). 317 Obj()), 318 "/gamma": workload.NewInfo(utiltesting.MakeWorkload("gamma", ""). 319 PodSets(*utiltesting.MakePodSet("main", 5). 320 Request(corev1.ResourceCPU, "1"). 321 Request("example.com/gpu", "1"). 322 Obj(), 323 ). 324 ReserveQuota(utiltesting.MakeAdmission("b", "main"). 325 Assignment(corev1.ResourceCPU, "spot", "5000m"). 326 Assignment("example.com/gpu", "default", "5"). 327 AssignmentPodCount(5). 328 Obj()). 329 Obj()), 330 }, 331 Preemption: defaultPreemption, 332 NamespaceSelector: labels.Everything(), 333 Status: active, 334 }, 335 "c": { 336 Name: "c", 337 AllocatableResourceGeneration: 1, 338 ResourceGroups: []ResourceGroup{ 339 { 340 CoveredResources: sets.New(corev1.ResourceCPU), 341 Flavors: []FlavorQuotas{{ 342 Name: "default", 343 Resources: map[corev1.ResourceName]*ResourceQuota{ 344 corev1.ResourceCPU: {Nominal: 100_000}, 345 }, 346 }}, 347 }, 348 }, 349 FlavorFungibility: defaultFlavorFungibility, 350 Usage: FlavorResourceQuantities{ 351 "default": { 352 corev1.ResourceCPU: 0, 353 }, 354 }, 355 Preemption: defaultPreemption, 356 NamespaceSelector: labels.Everything(), 357 Status: active, 358 }, 359 }, 360 ResourceFlavors: map[kueue.ResourceFlavorReference]*kueue.ResourceFlavor{ 361 "demand": utiltesting.MakeResourceFlavor("demand").Label("instance", "demand").Obj(), 362 "spot": utiltesting.MakeResourceFlavor("spot").Label("instance", "spot").Obj(), 363 "default": utiltesting.MakeResourceFlavor("default").Obj(), 364 }, 365 } 366 }(), 367 }, 368 "clusterQueues with preemption": { 369 cqs: []*kueue.ClusterQueue{ 370 utiltesting.MakeClusterQueue("with-preemption"). 371 Preemption(kueue.ClusterQueuePreemption{ 372 ReclaimWithinCohort: kueue.PreemptionPolicyAny, 373 WithinClusterQueue: kueue.PreemptionPolicyLowerPriority, 374 }).Obj(), 375 }, 376 wantSnapshot: Snapshot{ 377 ClusterQueues: map[string]*ClusterQueue{ 378 "with-preemption": { 379 Name: "with-preemption", 380 NamespaceSelector: labels.Everything(), 381 AllocatableResourceGeneration: 1, 382 Status: active, 383 Workloads: map[string]*workload.Info{}, 384 FlavorFungibility: defaultFlavorFungibility, 385 Preemption: kueue.ClusterQueuePreemption{ 386 ReclaimWithinCohort: kueue.PreemptionPolicyAny, 387 WithinClusterQueue: kueue.PreemptionPolicyLowerPriority, 388 }, 389 }, 390 }, 391 }, 392 }, 393 "lendingLimit with 2 clusterQueues and 2 flavors(whenCanBorrow: Borrow)": { 394 cqs: []*kueue.ClusterQueue{ 395 utiltesting.MakeClusterQueue("a"). 396 Cohort("lending"). 397 ResourceGroup( 398 *utiltesting.MakeFlavorQuotas("arm").Resource(corev1.ResourceCPU, "10", "", "5").Obj(), 399 *utiltesting.MakeFlavorQuotas("x86").Resource(corev1.ResourceCPU, "20", "", "10").Obj(), 400 ). 401 FlavorFungibility(defaultFlavorFungibility). 402 Obj(), 403 utiltesting.MakeClusterQueue("b"). 404 Cohort("lending"). 405 ResourceGroup( 406 *utiltesting.MakeFlavorQuotas("arm").Resource(corev1.ResourceCPU, "10", "", "5").Obj(), 407 *utiltesting.MakeFlavorQuotas("x86").Resource(corev1.ResourceCPU, "20", "", "10").Obj(), 408 ). 409 Obj(), 410 }, 411 rfs: []*kueue.ResourceFlavor{ 412 utiltesting.MakeResourceFlavor("arm").Label("arch", "arm").Obj(), 413 utiltesting.MakeResourceFlavor("x86").Label("arch", "x86").Obj(), 414 }, 415 wls: []*kueue.Workload{ 416 utiltesting.MakeWorkload("alpha", ""). 417 PodSets(*utiltesting.MakePodSet("main", 5). 418 Request(corev1.ResourceCPU, "2").Obj()). 419 ReserveQuota(utiltesting.MakeAdmission("a", "main"). 420 Assignment(corev1.ResourceCPU, "arm", "10000m"). 421 AssignmentPodCount(5).Obj()). 422 Obj(), 423 utiltesting.MakeWorkload("beta", ""). 424 PodSets(*utiltesting.MakePodSet("main", 5). 425 Request(corev1.ResourceCPU, "1").Obj()). 426 ReserveQuota(utiltesting.MakeAdmission("a", "main"). 427 Assignment(corev1.ResourceCPU, "arm", "5000m"). 428 AssignmentPodCount(5).Obj()). 429 Obj(), 430 utiltesting.MakeWorkload("gamma", ""). 431 PodSets(*utiltesting.MakePodSet("main", 5). 432 Request(corev1.ResourceCPU, "2").Obj()). 433 ReserveQuota(utiltesting.MakeAdmission("a", "main"). 434 Assignment(corev1.ResourceCPU, "x86", "10000m"). 435 AssignmentPodCount(5).Obj()). 436 Obj(), 437 }, 438 wantSnapshot: func() Snapshot { 439 cohort := &Cohort{ 440 Name: "lending", 441 AllocatableResourceGeneration: 2, 442 RequestableResources: FlavorResourceQuantities{ 443 "arm": { 444 corev1.ResourceCPU: 10_000, 445 }, 446 "x86": { 447 corev1.ResourceCPU: 20_000, 448 }, 449 }, 450 Usage: FlavorResourceQuantities{ 451 "arm": { 452 corev1.ResourceCPU: 10_000, 453 }, 454 "x86": { 455 corev1.ResourceCPU: 0, 456 }, 457 }, 458 } 459 return Snapshot{ 460 ClusterQueues: map[string]*ClusterQueue{ 461 "a": { 462 Name: "a", 463 Cohort: cohort, 464 AllocatableResourceGeneration: 1, 465 ResourceGroups: []ResourceGroup{ 466 { 467 CoveredResources: sets.New(corev1.ResourceCPU), 468 Flavors: []FlavorQuotas{ 469 { 470 Name: "arm", 471 Resources: map[corev1.ResourceName]*ResourceQuota{ 472 corev1.ResourceCPU: { 473 Nominal: 10_000, 474 BorrowingLimit: nil, 475 LendingLimit: ptr.To[int64](5_000), 476 }, 477 }, 478 }, 479 { 480 Name: "x86", 481 Resources: map[corev1.ResourceName]*ResourceQuota{ 482 corev1.ResourceCPU: { 483 Nominal: 20_000, 484 BorrowingLimit: nil, 485 LendingLimit: ptr.To[int64](10_000), 486 }, 487 }, 488 }, 489 }, 490 LabelKeys: sets.New("arch"), 491 }, 492 }, 493 FlavorFungibility: defaultFlavorFungibility, 494 Usage: FlavorResourceQuantities{ 495 "arm": {corev1.ResourceCPU: 15_000}, 496 "x86": {corev1.ResourceCPU: 10_000}, 497 }, 498 Workloads: map[string]*workload.Info{ 499 "/alpha": workload.NewInfo(utiltesting.MakeWorkload("alpha", ""). 500 PodSets(*utiltesting.MakePodSet("main", 5). 501 Request(corev1.ResourceCPU, "2").Obj()). 502 ReserveQuota(utiltesting.MakeAdmission("a", "main"). 503 Assignment(corev1.ResourceCPU, "arm", "10000m"). 504 AssignmentPodCount(5).Obj()). 505 Obj()), 506 "/beta": workload.NewInfo(utiltesting.MakeWorkload("beta", ""). 507 PodSets(*utiltesting.MakePodSet("main", 5). 508 Request(corev1.ResourceCPU, "1").Obj()). 509 ReserveQuota(utiltesting.MakeAdmission("a", "main"). 510 Assignment(corev1.ResourceCPU, "arm", "5000m"). 511 AssignmentPodCount(5).Obj()). 512 Obj()), 513 "/gamma": workload.NewInfo(utiltesting.MakeWorkload("gamma", ""). 514 PodSets(*utiltesting.MakePodSet("main", 5). 515 Request(corev1.ResourceCPU, "2").Obj()). 516 ReserveQuota(utiltesting.MakeAdmission("a", "main"). 517 Assignment(corev1.ResourceCPU, "x86", "10000m"). 518 AssignmentPodCount(5).Obj()). 519 Obj()), 520 }, 521 Preemption: defaultPreemption, 522 NamespaceSelector: labels.Everything(), 523 Status: active, 524 GuaranteedQuota: FlavorResourceQuantities{ 525 "arm": { 526 corev1.ResourceCPU: 5_000, 527 }, 528 "x86": { 529 corev1.ResourceCPU: 10_000, 530 }, 531 }, 532 }, 533 "b": { 534 Name: "b", 535 Cohort: cohort, 536 AllocatableResourceGeneration: 1, 537 ResourceGroups: []ResourceGroup{ 538 { 539 CoveredResources: sets.New(corev1.ResourceCPU), 540 Flavors: []FlavorQuotas{ 541 { 542 Name: "arm", 543 Resources: map[corev1.ResourceName]*ResourceQuota{ 544 corev1.ResourceCPU: { 545 Nominal: 10_000, 546 BorrowingLimit: nil, 547 LendingLimit: ptr.To[int64](5_000), 548 }, 549 }, 550 }, 551 { 552 Name: "x86", 553 Resources: map[corev1.ResourceName]*ResourceQuota{ 554 corev1.ResourceCPU: { 555 Nominal: 20_000, 556 BorrowingLimit: nil, 557 LendingLimit: ptr.To[int64](10_000), 558 }, 559 }, 560 }, 561 }, 562 LabelKeys: sets.New("arch"), 563 }, 564 }, 565 FlavorFungibility: defaultFlavorFungibility, 566 Usage: FlavorResourceQuantities{ 567 "arm": {corev1.ResourceCPU: 0}, 568 "x86": {corev1.ResourceCPU: 0}, 569 }, 570 Preemption: defaultPreemption, 571 NamespaceSelector: labels.Everything(), 572 Status: active, 573 GuaranteedQuota: FlavorResourceQuantities{ 574 "arm": { 575 corev1.ResourceCPU: 5_000, 576 }, 577 "x86": { 578 corev1.ResourceCPU: 10_000, 579 }, 580 }, 581 }, 582 }, 583 ResourceFlavors: map[kueue.ResourceFlavorReference]*kueue.ResourceFlavor{ 584 "arm": utiltesting.MakeResourceFlavor("arm").Label("arch", "arm").Obj(), 585 "x86": utiltesting.MakeResourceFlavor("x86").Label("arch", "x86").Obj(), 586 }, 587 } 588 }(), 589 enableLendingLimit: true, 590 }, 591 } 592 593 for name, tc := range testCases { 594 t.Run(name, func(t *testing.T) { 595 defer features.SetFeatureGateDuringTest(t, features.LendingLimit, tc.enableLendingLimit)() 596 cache := New(utiltesting.NewFakeClient()) 597 for _, cq := range tc.cqs { 598 // Purposely do not make a copy of clusterQueues. Clones of necessary fields are 599 // done in AddClusterQueue. 600 if err := cache.AddClusterQueue(context.Background(), cq); err != nil { 601 t.Fatalf("Failed adding ClusterQueue: %v", err) 602 } 603 } 604 for _, rf := range tc.rfs { 605 cache.AddOrUpdateResourceFlavor(rf) 606 } 607 for _, wl := range tc.wls { 608 cache.AddOrUpdateWorkload(wl) 609 } 610 snapshot := cache.Snapshot() 611 if diff := cmp.Diff(tc.wantSnapshot, snapshot, snapCmpOpts...); len(diff) != 0 { 612 t.Errorf("Unexpected Snapshot (-want,+got):\n%s", diff) 613 } 614 for _, cq := range snapshot.ClusterQueues { 615 for i := range cq.ResourceGroups { 616 rg := &cq.ResourceGroups[i] 617 for rName := range rg.CoveredResources { 618 if cq.RGByResource[rName] != rg { 619 t.Errorf("RGByResource[%s] does not point to its resource group", rName) 620 } 621 } 622 } 623 } 624 }) 625 } 626 } 627 628 func TestSnapshotAddRemoveWorkload(t *testing.T) { 629 flavors := []*kueue.ResourceFlavor{ 630 utiltesting.MakeResourceFlavor("default").Obj(), 631 utiltesting.MakeResourceFlavor("alpha").Obj(), 632 utiltesting.MakeResourceFlavor("beta").Obj(), 633 } 634 clusterQueues := []*kueue.ClusterQueue{ 635 utiltesting.MakeClusterQueue("c1"). 636 Cohort("cohort"). 637 ResourceGroup( 638 *utiltesting.MakeFlavorQuotas("default").Resource(corev1.ResourceCPU, "6").Obj(), 639 ). 640 ResourceGroup( 641 *utiltesting.MakeFlavorQuotas("alpha").Resource(corev1.ResourceMemory, "6Gi").Obj(), 642 *utiltesting.MakeFlavorQuotas("beta").Resource(corev1.ResourceMemory, "6Gi").Obj(), 643 ). 644 Obj(), 645 utiltesting.MakeClusterQueue("c2"). 646 Cohort("cohort"). 647 ResourceGroup( 648 *utiltesting.MakeFlavorQuotas("default").Resource(corev1.ResourceCPU, "6").Obj(), 649 ). 650 Obj(), 651 } 652 workloads := []kueue.Workload{ 653 *utiltesting.MakeWorkload("c1-cpu", ""). 654 Request(corev1.ResourceCPU, "1"). 655 ReserveQuota(utiltesting.MakeAdmission("c1").Assignment(corev1.ResourceCPU, "default", "1000m").Obj()). 656 Obj(), 657 *utiltesting.MakeWorkload("c1-memory-alpha", ""). 658 Request(corev1.ResourceMemory, "1Gi"). 659 ReserveQuota(utiltesting.MakeAdmission("c1").Assignment(corev1.ResourceMemory, "alpha", "1Gi").Obj()). 660 Obj(), 661 *utiltesting.MakeWorkload("c1-memory-beta", ""). 662 Request(corev1.ResourceMemory, "1Gi"). 663 ReserveQuota(utiltesting.MakeAdmission("c1").Assignment(corev1.ResourceMemory, "beta", "1Gi").Obj()). 664 Obj(), 665 *utiltesting.MakeWorkload("c2-cpu-1", ""). 666 Request(corev1.ResourceCPU, "1"). 667 ReserveQuota(utiltesting.MakeAdmission("c2").Assignment(corev1.ResourceCPU, "default", "1000m").Obj()). 668 Obj(), 669 *utiltesting.MakeWorkload("c2-cpu-2", ""). 670 Request(corev1.ResourceCPU, "1"). 671 ReserveQuota(utiltesting.MakeAdmission("c2").Assignment(corev1.ResourceCPU, "default", "1000m").Obj()). 672 Obj(), 673 } 674 675 ctx := context.Background() 676 cl := utiltesting.NewClientBuilder().WithLists(&kueue.WorkloadList{Items: workloads}).Build() 677 678 cqCache := New(cl) 679 for _, flv := range flavors { 680 cqCache.AddOrUpdateResourceFlavor(flv) 681 } 682 for _, cq := range clusterQueues { 683 if err := cqCache.AddClusterQueue(ctx, cq); err != nil { 684 t.Fatalf("Couldn't add ClusterQueue to cache: %v", err) 685 } 686 } 687 wlInfos := make(map[string]*workload.Info, len(workloads)) 688 for _, cq := range cqCache.clusterQueues { 689 for _, wl := range cq.Workloads { 690 wlInfos[workload.Key(wl.Obj)] = wl 691 } 692 } 693 initialSnapshot := cqCache.Snapshot() 694 initialCohortResources := initialSnapshot.ClusterQueues["c1"].Cohort.RequestableResources 695 cases := map[string]struct { 696 remove []string 697 add []string 698 want Snapshot 699 }{ 700 "no-op remove add": { 701 remove: []string{"/c1-cpu", "/c2-cpu-1"}, 702 add: []string{"/c1-cpu", "/c2-cpu-1"}, 703 want: initialSnapshot, 704 }, 705 "remove all": { 706 remove: []string{"/c1-cpu", "/c1-memory-alpha", "/c1-memory-beta", "/c2-cpu-1", "/c2-cpu-2"}, 707 want: func() Snapshot { 708 cohort := &Cohort{ 709 Name: "cohort", 710 AllocatableResourceGeneration: 2, 711 RequestableResources: initialCohortResources, 712 Usage: FlavorResourceQuantities{ 713 "default": {corev1.ResourceCPU: 0}, 714 "alpha": {corev1.ResourceMemory: 0}, 715 "beta": {corev1.ResourceMemory: 0}, 716 }, 717 } 718 return Snapshot{ 719 ClusterQueues: map[string]*ClusterQueue{ 720 "c1": { 721 Name: "c1", 722 Cohort: cohort, 723 Workloads: make(map[string]*workload.Info), 724 ResourceGroups: cqCache.clusterQueues["c1"].ResourceGroups, 725 FlavorFungibility: defaultFlavorFungibility, 726 AllocatableResourceGeneration: 1, 727 Usage: FlavorResourceQuantities{ 728 "default": {corev1.ResourceCPU: 0}, 729 "alpha": {corev1.ResourceMemory: 0}, 730 "beta": {corev1.ResourceMemory: 0}, 731 }, 732 }, 733 "c2": { 734 Name: "c2", 735 Cohort: cohort, 736 Workloads: make(map[string]*workload.Info), 737 ResourceGroups: cqCache.clusterQueues["c2"].ResourceGroups, 738 FlavorFungibility: defaultFlavorFungibility, 739 AllocatableResourceGeneration: 1, 740 Usage: FlavorResourceQuantities{ 741 "default": {corev1.ResourceCPU: 0}, 742 }, 743 }, 744 }, 745 } 746 }(), 747 }, 748 "remove c1-cpu": { 749 remove: []string{"/c1-cpu"}, 750 want: func() Snapshot { 751 cohort := &Cohort{ 752 Name: "cohort", 753 AllocatableResourceGeneration: 2, 754 RequestableResources: initialCohortResources, 755 Usage: FlavorResourceQuantities{ 756 "default": {corev1.ResourceCPU: 2_000}, 757 "alpha": {corev1.ResourceMemory: utiltesting.Gi}, 758 "beta": {corev1.ResourceMemory: utiltesting.Gi}, 759 }, 760 } 761 return Snapshot{ 762 ClusterQueues: map[string]*ClusterQueue{ 763 "c1": { 764 Name: "c1", 765 Cohort: cohort, 766 Workloads: map[string]*workload.Info{ 767 "/c1-memory-alpha": nil, 768 "/c1-memory-beta": nil, 769 }, 770 AllocatableResourceGeneration: 1, 771 ResourceGroups: cqCache.clusterQueues["c1"].ResourceGroups, 772 FlavorFungibility: defaultFlavorFungibility, 773 Usage: FlavorResourceQuantities{ 774 "default": {corev1.ResourceCPU: 0}, 775 "alpha": {corev1.ResourceMemory: utiltesting.Gi}, 776 "beta": {corev1.ResourceMemory: utiltesting.Gi}, 777 }, 778 }, 779 "c2": { 780 Name: "c2", 781 Cohort: cohort, 782 Workloads: map[string]*workload.Info{ 783 "/c2-cpu-1": nil, 784 "/c2-cpu-2": nil, 785 }, 786 ResourceGroups: cqCache.clusterQueues["c2"].ResourceGroups, 787 FlavorFungibility: defaultFlavorFungibility, 788 AllocatableResourceGeneration: 1, 789 Usage: FlavorResourceQuantities{ 790 "default": {corev1.ResourceCPU: 2_000}, 791 }, 792 }, 793 }, 794 } 795 }(), 796 }, 797 "remove c1-memory-alpha": { 798 remove: []string{"/c1-memory-alpha"}, 799 want: func() Snapshot { 800 cohort := &Cohort{ 801 Name: "cohort", 802 AllocatableResourceGeneration: 2, 803 RequestableResources: initialCohortResources, 804 Usage: FlavorResourceQuantities{ 805 "default": {corev1.ResourceCPU: 3_000}, 806 "alpha": {corev1.ResourceMemory: 0}, 807 "beta": {corev1.ResourceMemory: utiltesting.Gi}, 808 }, 809 } 810 return Snapshot{ 811 ClusterQueues: map[string]*ClusterQueue{ 812 "c1": { 813 Name: "c1", 814 Cohort: cohort, 815 Workloads: map[string]*workload.Info{ 816 "/c1-memory-alpha": nil, 817 "/c1-memory-beta": nil, 818 }, 819 AllocatableResourceGeneration: 1, 820 ResourceGroups: cqCache.clusterQueues["c1"].ResourceGroups, 821 FlavorFungibility: defaultFlavorFungibility, 822 Usage: FlavorResourceQuantities{ 823 "default": {corev1.ResourceCPU: 1_000}, 824 "alpha": {corev1.ResourceMemory: 0}, 825 "beta": {corev1.ResourceMemory: utiltesting.Gi}, 826 }, 827 }, 828 "c2": { 829 Name: "c2", 830 Cohort: cohort, 831 Workloads: map[string]*workload.Info{ 832 "/c2-cpu-1": nil, 833 "/c2-cpu-2": nil, 834 }, 835 AllocatableResourceGeneration: 1, 836 ResourceGroups: cqCache.clusterQueues["c2"].ResourceGroups, 837 FlavorFungibility: defaultFlavorFungibility, 838 Usage: FlavorResourceQuantities{ 839 "default": {corev1.ResourceCPU: 2_000}, 840 }, 841 }, 842 }, 843 } 844 }(), 845 }, 846 } 847 cmpOpts := append(snapCmpOpts, 848 cmpopts.IgnoreFields(ClusterQueue{}, "NamespaceSelector", "Preemption", "Status"), 849 cmpopts.IgnoreFields(Cohort{}), 850 cmpopts.IgnoreFields(Snapshot{}, "ResourceFlavors"), 851 cmpopts.IgnoreTypes(&workload.Info{})) 852 for name, tc := range cases { 853 t.Run(name, func(t *testing.T) { 854 snap := cqCache.Snapshot() 855 for _, name := range tc.remove { 856 snap.RemoveWorkload(wlInfos[name]) 857 } 858 for _, name := range tc.add { 859 snap.AddWorkload(wlInfos[name]) 860 } 861 if diff := cmp.Diff(tc.want, snap, cmpOpts...); diff != "" { 862 t.Errorf("Unexpected snapshot state after operations (-want,+got):\n%s", diff) 863 } 864 }) 865 } 866 } 867 868 func TestSnapshotAddRemoveWorkloadWithLendingLimit(t *testing.T) { 869 _ = features.SetEnable(features.LendingLimit, true) 870 flavors := []*kueue.ResourceFlavor{ 871 utiltesting.MakeResourceFlavor("default").Obj(), 872 } 873 clusterQueues := []*kueue.ClusterQueue{ 874 utiltesting.MakeClusterQueue("lend-a"). 875 Cohort("lend"). 876 ResourceGroup( 877 *utiltesting.MakeFlavorQuotas("default").Resource(corev1.ResourceCPU, "10", "", "4").Obj(), 878 ). 879 Preemption(kueue.ClusterQueuePreemption{ 880 WithinClusterQueue: kueue.PreemptionPolicyLowerPriority, 881 ReclaimWithinCohort: kueue.PreemptionPolicyLowerPriority, 882 }). 883 Obj(), 884 utiltesting.MakeClusterQueue("lend-b"). 885 Cohort("lend"). 886 ResourceGroup( 887 *utiltesting.MakeFlavorQuotas("default").Resource(corev1.ResourceCPU, "10", "", "6").Obj(), 888 ). 889 Preemption(kueue.ClusterQueuePreemption{ 890 WithinClusterQueue: kueue.PreemptionPolicyNever, 891 ReclaimWithinCohort: kueue.PreemptionPolicyAny, 892 }). 893 Obj(), 894 } 895 workloads := []kueue.Workload{ 896 *utiltesting.MakeWorkload("lend-a-1", ""). 897 Request(corev1.ResourceCPU, "1"). 898 ReserveQuota(utiltesting.MakeAdmission("lend-a").Assignment(corev1.ResourceCPU, "default", "1").Obj()). 899 Obj(), 900 *utiltesting.MakeWorkload("lend-a-2", ""). 901 Request(corev1.ResourceCPU, "9"). 902 ReserveQuota(utiltesting.MakeAdmission("lend-a").Assignment(corev1.ResourceCPU, "default", "9").Obj()). 903 Obj(), 904 *utiltesting.MakeWorkload("lend-a-3", ""). 905 Request(corev1.ResourceCPU, "6"). 906 ReserveQuota(utiltesting.MakeAdmission("lend-a").Assignment(corev1.ResourceCPU, "default", "6").Obj()). 907 Obj(), 908 *utiltesting.MakeWorkload("lend-b-1", ""). 909 Request(corev1.ResourceCPU, "4"). 910 ReserveQuota(utiltesting.MakeAdmission("lend-b").Assignment(corev1.ResourceCPU, "default", "4").Obj()). 911 Obj(), 912 } 913 914 ctx := context.Background() 915 cl := utiltesting.NewClientBuilder().WithLists(&kueue.WorkloadList{Items: workloads}).Build() 916 917 cqCache := New(cl) 918 for _, flv := range flavors { 919 cqCache.AddOrUpdateResourceFlavor(flv) 920 } 921 for _, cq := range clusterQueues { 922 if err := cqCache.AddClusterQueue(ctx, cq); err != nil { 923 t.Fatalf("Couldn't add ClusterQueue to cache: %v", err) 924 } 925 } 926 wlInfos := make(map[string]*workload.Info, len(workloads)) 927 for _, cq := range cqCache.clusterQueues { 928 for _, wl := range cq.Workloads { 929 wlInfos[workload.Key(wl.Obj)] = wl 930 } 931 } 932 initialSnapshot := cqCache.Snapshot() 933 initialCohortResources := initialSnapshot.ClusterQueues["lend-a"].Cohort.RequestableResources 934 cases := map[string]struct { 935 remove []string 936 add []string 937 want Snapshot 938 }{ 939 "remove all then add all": { 940 remove: []string{"/lend-a-1", "/lend-a-2", "/lend-a-3", "/lend-b-1"}, 941 add: []string{"/lend-a-1", "/lend-a-2", "/lend-a-3", "/lend-b-1"}, 942 want: initialSnapshot, 943 }, 944 "remove all": { 945 remove: []string{"/lend-a-1", "/lend-a-2", "/lend-a-3", "/lend-b-1"}, 946 want: func() Snapshot { 947 cohort := &Cohort{ 948 Name: "lend", 949 AllocatableResourceGeneration: 2, 950 RequestableResources: initialCohortResources, 951 Usage: FlavorResourceQuantities{ 952 "default": {corev1.ResourceCPU: 0}, 953 }, 954 } 955 return Snapshot{ 956 ClusterQueues: map[string]*ClusterQueue{ 957 "lend-a": { 958 Name: "lend-a", 959 Cohort: cohort, 960 Workloads: make(map[string]*workload.Info), 961 ResourceGroups: cqCache.clusterQueues["lend-a"].ResourceGroups, 962 FlavorFungibility: defaultFlavorFungibility, 963 AllocatableResourceGeneration: 1, 964 Usage: FlavorResourceQuantities{ 965 "default": {corev1.ResourceCPU: 0}, 966 }, 967 GuaranteedQuota: FlavorResourceQuantities{ 968 "default": { 969 corev1.ResourceCPU: 6_000, 970 }, 971 }, 972 }, 973 "lend-b": { 974 Name: "lend-b", 975 Cohort: cohort, 976 Workloads: make(map[string]*workload.Info), 977 ResourceGroups: cqCache.clusterQueues["lend-b"].ResourceGroups, 978 FlavorFungibility: defaultFlavorFungibility, 979 AllocatableResourceGeneration: 1, 980 Usage: FlavorResourceQuantities{ 981 "default": {corev1.ResourceCPU: 0}, 982 }, 983 GuaranteedQuota: FlavorResourceQuantities{ 984 "default": { 985 corev1.ResourceCPU: 4_000, 986 }, 987 }, 988 }, 989 }, 990 } 991 }(), 992 }, 993 "remove workload, but still using quota over GuaranteedQuota": { 994 remove: []string{"/lend-a-2"}, 995 want: func() Snapshot { 996 cohort := &Cohort{ 997 Name: "lend", 998 AllocatableResourceGeneration: 2, 999 RequestableResources: initialCohortResources, 1000 Usage: FlavorResourceQuantities{ 1001 "default": {corev1.ResourceCPU: 1_000}, 1002 }, 1003 } 1004 return Snapshot{ 1005 ClusterQueues: map[string]*ClusterQueue{ 1006 "lend-a": { 1007 Name: "lend-a", 1008 Cohort: cohort, 1009 Workloads: make(map[string]*workload.Info), 1010 ResourceGroups: cqCache.clusterQueues["lend-a"].ResourceGroups, 1011 FlavorFungibility: defaultFlavorFungibility, 1012 AllocatableResourceGeneration: 1, 1013 Usage: FlavorResourceQuantities{ 1014 "default": {corev1.ResourceCPU: 7_000}, 1015 }, 1016 GuaranteedQuota: FlavorResourceQuantities{ 1017 "default": { 1018 corev1.ResourceCPU: 6_000, 1019 }, 1020 }, 1021 }, 1022 "lend-b": { 1023 Name: "lend-b", 1024 Cohort: cohort, 1025 Workloads: make(map[string]*workload.Info), 1026 ResourceGroups: cqCache.clusterQueues["lend-b"].ResourceGroups, 1027 FlavorFungibility: defaultFlavorFungibility, 1028 AllocatableResourceGeneration: 1, 1029 Usage: FlavorResourceQuantities{ 1030 "default": {corev1.ResourceCPU: 4_000}, 1031 }, 1032 GuaranteedQuota: FlavorResourceQuantities{ 1033 "default": { 1034 corev1.ResourceCPU: 4_000, 1035 }, 1036 }, 1037 }, 1038 }, 1039 } 1040 }(), 1041 }, 1042 "remove wokload, using same quota as GuaranteedQuota": { 1043 remove: []string{"/lend-a-1", "/lend-a-2"}, 1044 want: func() Snapshot { 1045 cohort := &Cohort{ 1046 Name: "lend", 1047 AllocatableResourceGeneration: 2, 1048 RequestableResources: initialCohortResources, 1049 Usage: FlavorResourceQuantities{ 1050 "default": {corev1.ResourceCPU: 0}, 1051 }, 1052 } 1053 return Snapshot{ 1054 ClusterQueues: map[string]*ClusterQueue{ 1055 "lend-a": { 1056 Name: "lend-a", 1057 Cohort: cohort, 1058 Workloads: make(map[string]*workload.Info), 1059 ResourceGroups: cqCache.clusterQueues["lend-a"].ResourceGroups, 1060 FlavorFungibility: defaultFlavorFungibility, 1061 AllocatableResourceGeneration: 1, 1062 Usage: FlavorResourceQuantities{ 1063 "default": {corev1.ResourceCPU: 6_000}, 1064 }, 1065 GuaranteedQuota: FlavorResourceQuantities{ 1066 "default": { 1067 corev1.ResourceCPU: 6_000, 1068 }, 1069 }, 1070 }, 1071 "lend-b": { 1072 Name: "lend-b", 1073 Cohort: cohort, 1074 Workloads: make(map[string]*workload.Info), 1075 ResourceGroups: cqCache.clusterQueues["lend-b"].ResourceGroups, 1076 FlavorFungibility: defaultFlavorFungibility, 1077 AllocatableResourceGeneration: 1, 1078 Usage: FlavorResourceQuantities{ 1079 "default": {corev1.ResourceCPU: 4_000}, 1080 }, 1081 GuaranteedQuota: FlavorResourceQuantities{ 1082 "default": { 1083 corev1.ResourceCPU: 4_000, 1084 }, 1085 }, 1086 }, 1087 }, 1088 } 1089 }(), 1090 }, 1091 "remove workload, using less quota than GuaranteedQuota": { 1092 remove: []string{"/lend-a-2", "/lend-a-3"}, 1093 want: func() Snapshot { 1094 cohort := &Cohort{ 1095 Name: "lend", 1096 AllocatableResourceGeneration: 2, 1097 RequestableResources: initialCohortResources, 1098 Usage: FlavorResourceQuantities{ 1099 "default": {corev1.ResourceCPU: 0}, 1100 }, 1101 } 1102 return Snapshot{ 1103 ClusterQueues: map[string]*ClusterQueue{ 1104 "lend-a": { 1105 Name: "lend-a", 1106 Cohort: cohort, 1107 Workloads: make(map[string]*workload.Info), 1108 ResourceGroups: cqCache.clusterQueues["lend-a"].ResourceGroups, 1109 FlavorFungibility: defaultFlavorFungibility, 1110 AllocatableResourceGeneration: 1, 1111 Usage: FlavorResourceQuantities{ 1112 "default": {corev1.ResourceCPU: 1_000}, 1113 }, 1114 GuaranteedQuota: FlavorResourceQuantities{ 1115 "default": { 1116 corev1.ResourceCPU: 6_000, 1117 }, 1118 }, 1119 }, 1120 "lend-b": { 1121 Name: "lend-b", 1122 Cohort: cohort, 1123 Workloads: make(map[string]*workload.Info), 1124 ResourceGroups: cqCache.clusterQueues["lend-b"].ResourceGroups, 1125 FlavorFungibility: defaultFlavorFungibility, 1126 AllocatableResourceGeneration: 1, 1127 Usage: FlavorResourceQuantities{ 1128 "default": {corev1.ResourceCPU: 4_000}, 1129 }, 1130 GuaranteedQuota: FlavorResourceQuantities{ 1131 "default": { 1132 corev1.ResourceCPU: 4_000, 1133 }, 1134 }, 1135 }, 1136 }, 1137 } 1138 }(), 1139 }, 1140 "remove all then add workload, using less quota than GuaranteedQuota": { 1141 remove: []string{"/lend-a-1", "/lend-a-2", "/lend-a-3", "/lend-b-1"}, 1142 add: []string{"/lend-a-1"}, 1143 want: func() Snapshot { 1144 cohort := &Cohort{ 1145 Name: "lend", 1146 AllocatableResourceGeneration: 2, 1147 RequestableResources: initialCohortResources, 1148 Usage: FlavorResourceQuantities{ 1149 "default": {corev1.ResourceCPU: 0}, 1150 }, 1151 } 1152 return Snapshot{ 1153 ClusterQueues: map[string]*ClusterQueue{ 1154 "lend-a": { 1155 Name: "lend-a", 1156 Cohort: cohort, 1157 Workloads: make(map[string]*workload.Info), 1158 ResourceGroups: cqCache.clusterQueues["lend-a"].ResourceGroups, 1159 FlavorFungibility: defaultFlavorFungibility, 1160 AllocatableResourceGeneration: 1, 1161 Usage: FlavorResourceQuantities{ 1162 "default": {corev1.ResourceCPU: 1_000}, 1163 }, 1164 GuaranteedQuota: FlavorResourceQuantities{ 1165 "default": { 1166 corev1.ResourceCPU: 6_000, 1167 }, 1168 }, 1169 }, 1170 "lend-b": { 1171 Name: "lend-b", 1172 Cohort: cohort, 1173 Workloads: make(map[string]*workload.Info), 1174 ResourceGroups: cqCache.clusterQueues["lend-b"].ResourceGroups, 1175 FlavorFungibility: defaultFlavorFungibility, 1176 AllocatableResourceGeneration: 1, 1177 Usage: FlavorResourceQuantities{ 1178 "default": {corev1.ResourceCPU: 0}, 1179 }, 1180 GuaranteedQuota: FlavorResourceQuantities{ 1181 "default": { 1182 corev1.ResourceCPU: 4_000, 1183 }, 1184 }, 1185 }, 1186 }, 1187 } 1188 }(), 1189 }, 1190 "remove all then add workload, using same quota as GuaranteedQuota": { 1191 remove: []string{"/lend-a-1", "/lend-a-2", "/lend-a-3", "/lend-b-1"}, 1192 add: []string{"/lend-a-3"}, 1193 want: func() Snapshot { 1194 cohort := &Cohort{ 1195 Name: "lend", 1196 AllocatableResourceGeneration: 2, 1197 RequestableResources: initialCohortResources, 1198 Usage: FlavorResourceQuantities{ 1199 "default": {corev1.ResourceCPU: 0}, 1200 }, 1201 } 1202 return Snapshot{ 1203 ClusterQueues: map[string]*ClusterQueue{ 1204 "lend-a": { 1205 Name: "lend-a", 1206 Cohort: cohort, 1207 Workloads: make(map[string]*workload.Info), 1208 ResourceGroups: cqCache.clusterQueues["lend-a"].ResourceGroups, 1209 FlavorFungibility: defaultFlavorFungibility, 1210 AllocatableResourceGeneration: 1, 1211 Usage: FlavorResourceQuantities{ 1212 "default": {corev1.ResourceCPU: 6_000}, 1213 }, 1214 GuaranteedQuota: FlavorResourceQuantities{ 1215 "default": { 1216 corev1.ResourceCPU: 6_000, 1217 }, 1218 }, 1219 }, 1220 "lend-b": { 1221 Name: "lend-b", 1222 Cohort: cohort, 1223 Workloads: make(map[string]*workload.Info), 1224 ResourceGroups: cqCache.clusterQueues["lend-b"].ResourceGroups, 1225 FlavorFungibility: defaultFlavorFungibility, 1226 AllocatableResourceGeneration: 1, 1227 Usage: FlavorResourceQuantities{ 1228 "default": {corev1.ResourceCPU: 0}, 1229 }, 1230 GuaranteedQuota: FlavorResourceQuantities{ 1231 "default": { 1232 corev1.ResourceCPU: 4_000, 1233 }, 1234 }, 1235 }, 1236 }, 1237 } 1238 }(), 1239 }, 1240 "remove all then add workload, using quota over GuaranteedQuota": { 1241 remove: []string{"/lend-a-1", "/lend-a-2", "/lend-a-3", "/lend-b-1"}, 1242 add: []string{"/lend-a-2"}, 1243 want: func() Snapshot { 1244 cohort := &Cohort{ 1245 Name: "lend", 1246 AllocatableResourceGeneration: 2, 1247 RequestableResources: initialCohortResources, 1248 Usage: FlavorResourceQuantities{ 1249 "default": {corev1.ResourceCPU: 3_000}, 1250 }, 1251 } 1252 return Snapshot{ 1253 ClusterQueues: map[string]*ClusterQueue{ 1254 "lend-a": { 1255 Name: "lend-a", 1256 Cohort: cohort, 1257 Workloads: make(map[string]*workload.Info), 1258 ResourceGroups: cqCache.clusterQueues["lend-a"].ResourceGroups, 1259 FlavorFungibility: defaultFlavorFungibility, 1260 AllocatableResourceGeneration: 1, 1261 Usage: FlavorResourceQuantities{ 1262 "default": {corev1.ResourceCPU: 9_000}, 1263 }, 1264 GuaranteedQuota: FlavorResourceQuantities{ 1265 "default": { 1266 corev1.ResourceCPU: 6_000, 1267 }, 1268 }, 1269 }, 1270 "lend-b": { 1271 Name: "lend-b", 1272 Cohort: cohort, 1273 Workloads: make(map[string]*workload.Info), 1274 ResourceGroups: cqCache.clusterQueues["lend-b"].ResourceGroups, 1275 FlavorFungibility: defaultFlavorFungibility, 1276 AllocatableResourceGeneration: 1, 1277 Usage: FlavorResourceQuantities{ 1278 "default": {corev1.ResourceCPU: 0}, 1279 }, 1280 GuaranteedQuota: FlavorResourceQuantities{ 1281 "default": { 1282 corev1.ResourceCPU: 4_000, 1283 }, 1284 }, 1285 }, 1286 }, 1287 } 1288 }(), 1289 }, 1290 } 1291 cmpOpts := append(snapCmpOpts, 1292 cmpopts.IgnoreFields(ClusterQueue{}, "NamespaceSelector", "Preemption", "Status"), 1293 cmpopts.IgnoreFields(Snapshot{}, "ResourceFlavors"), 1294 cmpopts.IgnoreTypes(&workload.Info{})) 1295 for name, tc := range cases { 1296 t.Run(name, func(t *testing.T) { 1297 snap := cqCache.Snapshot() 1298 for _, name := range tc.remove { 1299 snap.RemoveWorkload(wlInfos[name]) 1300 } 1301 for _, name := range tc.add { 1302 snap.AddWorkload(wlInfos[name]) 1303 } 1304 if diff := cmp.Diff(tc.want, snap, cmpOpts...); diff != "" { 1305 t.Errorf("Unexpected snapshot state after operations (-want,+got):\n%s", diff) 1306 } 1307 }) 1308 } 1309 }