sigs.k8s.io/kueue@v0.6.2/pkg/cache/clusterqueue_test.go (about) 1 /* 2 Copyright 2023 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package cache 18 19 import ( 20 "testing" 21 22 "github.com/google/go-cmp/cmp" 23 corev1 "k8s.io/api/core/v1" 24 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 25 26 kueue "sigs.k8s.io/kueue/apis/kueue/v1beta1" 27 "sigs.k8s.io/kueue/pkg/features" 28 "sigs.k8s.io/kueue/pkg/metrics" 29 utiltesting "sigs.k8s.io/kueue/pkg/util/testing" 30 ) 31 32 func TestClusterQueueUpdateWithFlavors(t *testing.T) { 33 rf := utiltesting.MakeResourceFlavor("x86").Obj() 34 cq := utiltesting.MakeClusterQueue("cq"). 35 ResourceGroup(*utiltesting.MakeFlavorQuotas("x86").Resource("cpu", "5").Obj()). 36 Obj() 37 38 testcases := []struct { 39 name string 40 curStatus metrics.ClusterQueueStatus 41 flavors map[kueue.ResourceFlavorReference]*kueue.ResourceFlavor 42 wantStatus metrics.ClusterQueueStatus 43 }{ 44 { 45 name: "Pending clusterQueue updated existent flavors", 46 curStatus: pending, 47 flavors: map[kueue.ResourceFlavorReference]*kueue.ResourceFlavor{ 48 kueue.ResourceFlavorReference(rf.Name): rf, 49 }, 50 wantStatus: active, 51 }, 52 { 53 name: "Active clusterQueue updated with not found flavors", 54 curStatus: active, 55 flavors: map[kueue.ResourceFlavorReference]*kueue.ResourceFlavor{}, 56 wantStatus: pending, 57 }, 58 { 59 name: "Terminating clusterQueue updated with existent flavors", 60 curStatus: terminating, 61 flavors: map[kueue.ResourceFlavorReference]*kueue.ResourceFlavor{ 62 kueue.ResourceFlavorReference(rf.Name): rf, 63 }, 64 wantStatus: terminating, 65 }, 66 { 67 name: "Terminating clusterQueue updated with not found flavors", 68 curStatus: terminating, 69 wantStatus: terminating, 70 }, 71 } 72 73 for _, tc := range testcases { 74 t.Run(tc.name, func(t *testing.T) { 75 cache := New(utiltesting.NewFakeClient()) 76 cq, err := cache.newClusterQueue(cq) 77 if err != nil { 78 t.Fatalf("failed to new clusterQueue %v", err) 79 } 80 81 cq.Status = tc.curStatus 82 cq.UpdateWithFlavors(tc.flavors) 83 84 if cq.Status != tc.wantStatus { 85 t.Fatalf("got different status, want: %v, got: %v", tc.wantStatus, cq.Status) 86 } 87 }) 88 } 89 } 90 91 func TestFitInCohort(t *testing.T) { 92 cases := map[string]struct { 93 request FlavorResourceQuantities 94 wantFit bool 95 cq *ClusterQueue 96 enableLendingLimit bool 97 }{ 98 "full cohort, empty request": { 99 request: FlavorResourceQuantities{}, 100 wantFit: true, 101 cq: &ClusterQueue{ 102 Name: "CQ", 103 Cohort: &Cohort{ 104 Name: "C", 105 RequestableResources: FlavorResourceQuantities{ 106 "f1": map[corev1.ResourceName]int64{ 107 corev1.ResourceCPU: 5, 108 corev1.ResourceMemory: 5, 109 }, 110 "f2": map[corev1.ResourceName]int64{ 111 corev1.ResourceCPU: 5, 112 corev1.ResourceMemory: 5, 113 }, 114 }, 115 Usage: FlavorResourceQuantities{ 116 "f1": map[corev1.ResourceName]int64{ 117 corev1.ResourceCPU: 5, 118 corev1.ResourceMemory: 5, 119 }, 120 "f2": map[corev1.ResourceName]int64{ 121 corev1.ResourceCPU: 5, 122 corev1.ResourceMemory: 5, 123 }, 124 }, 125 }, 126 ResourceGroups: nil, 127 }, 128 }, 129 "can fit": { 130 request: FlavorResourceQuantities{ 131 "f2": map[corev1.ResourceName]int64{ 132 corev1.ResourceCPU: 1, 133 corev1.ResourceMemory: 1, 134 }, 135 }, 136 wantFit: true, 137 cq: &ClusterQueue{ 138 Name: "CQ", 139 Cohort: &Cohort{ 140 Name: "C", 141 RequestableResources: FlavorResourceQuantities{ 142 "f1": map[corev1.ResourceName]int64{ 143 corev1.ResourceCPU: 5, 144 corev1.ResourceMemory: 5, 145 }, 146 "f2": map[corev1.ResourceName]int64{ 147 corev1.ResourceCPU: 5, 148 corev1.ResourceMemory: 5, 149 }, 150 }, 151 Usage: FlavorResourceQuantities{ 152 "f1": map[corev1.ResourceName]int64{ 153 corev1.ResourceCPU: 5, 154 corev1.ResourceMemory: 5, 155 }, 156 "f2": map[corev1.ResourceName]int64{ 157 corev1.ResourceCPU: 4, 158 corev1.ResourceMemory: 4, 159 }, 160 }, 161 }, 162 ResourceGroups: nil, 163 }, 164 }, 165 "full cohort, none fit": { 166 request: FlavorResourceQuantities{ 167 "f1": map[corev1.ResourceName]int64{ 168 corev1.ResourceCPU: 1, 169 corev1.ResourceMemory: 1, 170 }, 171 "f2": map[corev1.ResourceName]int64{ 172 corev1.ResourceCPU: 1, 173 corev1.ResourceMemory: 1, 174 }, 175 }, 176 wantFit: false, 177 cq: &ClusterQueue{ 178 Name: "CQ", 179 Cohort: &Cohort{ 180 Name: "C", 181 RequestableResources: FlavorResourceQuantities{ 182 "f1": map[corev1.ResourceName]int64{ 183 corev1.ResourceCPU: 5, 184 corev1.ResourceMemory: 5, 185 }, 186 "f2": map[corev1.ResourceName]int64{ 187 corev1.ResourceCPU: 5, 188 corev1.ResourceMemory: 5, 189 }, 190 }, 191 Usage: FlavorResourceQuantities{ 192 "f1": map[corev1.ResourceName]int64{ 193 corev1.ResourceCPU: 5, 194 corev1.ResourceMemory: 5, 195 }, 196 "f2": map[corev1.ResourceName]int64{ 197 corev1.ResourceCPU: 5, 198 corev1.ResourceMemory: 5, 199 }, 200 }, 201 }, 202 ResourceGroups: nil, 203 }, 204 }, 205 "one cannot fit": { 206 request: FlavorResourceQuantities{ 207 "f1": map[corev1.ResourceName]int64{ 208 corev1.ResourceCPU: 1, 209 corev1.ResourceMemory: 1, 210 }, 211 "f2": map[corev1.ResourceName]int64{ 212 corev1.ResourceCPU: 2, 213 corev1.ResourceMemory: 1, 214 }, 215 }, 216 wantFit: false, 217 cq: &ClusterQueue{ 218 Name: "CQ", 219 Cohort: &Cohort{ 220 Name: "C", 221 RequestableResources: FlavorResourceQuantities{ 222 "f1": map[corev1.ResourceName]int64{ 223 corev1.ResourceCPU: 5, 224 corev1.ResourceMemory: 5, 225 }, 226 "f2": map[corev1.ResourceName]int64{ 227 corev1.ResourceCPU: 5, 228 corev1.ResourceMemory: 5, 229 }, 230 }, 231 Usage: FlavorResourceQuantities{ 232 "f1": map[corev1.ResourceName]int64{ 233 corev1.ResourceCPU: 4, 234 corev1.ResourceMemory: 4, 235 }, 236 "f2": map[corev1.ResourceName]int64{ 237 corev1.ResourceCPU: 4, 238 corev1.ResourceMemory: 4, 239 }, 240 }, 241 }, 242 ResourceGroups: nil, 243 }, 244 }, 245 "missing flavor": { 246 request: FlavorResourceQuantities{ 247 "f2": map[corev1.ResourceName]int64{ 248 corev1.ResourceCPU: 1, 249 corev1.ResourceMemory: 1, 250 }, 251 }, 252 wantFit: false, 253 cq: &ClusterQueue{ 254 Name: "CQ", 255 Cohort: &Cohort{ 256 Name: "C", 257 RequestableResources: FlavorResourceQuantities{ 258 "f1": map[corev1.ResourceName]int64{ 259 corev1.ResourceCPU: 5, 260 corev1.ResourceMemory: 5, 261 }, 262 }, 263 Usage: FlavorResourceQuantities{ 264 "f1": map[corev1.ResourceName]int64{ 265 corev1.ResourceCPU: 5, 266 corev1.ResourceMemory: 5, 267 }, 268 }, 269 }, 270 ResourceGroups: nil, 271 }, 272 }, 273 "missing resource": { 274 request: FlavorResourceQuantities{ 275 "f1": map[corev1.ResourceName]int64{ 276 corev1.ResourceCPU: 1, 277 corev1.ResourceMemory: 1, 278 }, 279 }, 280 wantFit: false, 281 cq: &ClusterQueue{ 282 Name: "CQ", 283 Cohort: &Cohort{ 284 Name: "C", 285 RequestableResources: FlavorResourceQuantities{ 286 "f1": map[corev1.ResourceName]int64{ 287 corev1.ResourceCPU: 5, 288 }, 289 }, 290 Usage: FlavorResourceQuantities{ 291 "f1": map[corev1.ResourceName]int64{ 292 corev1.ResourceCPU: 3, 293 }, 294 }, 295 }, 296 ResourceGroups: nil, 297 }, 298 }, 299 "lendingLimit enabled can't fit": { 300 request: FlavorResourceQuantities{ 301 "f1": map[corev1.ResourceName]int64{ 302 corev1.ResourceCPU: 3, 303 }, 304 }, 305 wantFit: false, 306 cq: &ClusterQueue{ 307 Name: "CQ-A", 308 Cohort: &Cohort{ 309 Name: "C", 310 RequestableResources: FlavorResourceQuantities{ 311 "f1": map[corev1.ResourceName]int64{ 312 // CQ-A has 2 nominal cpu, CQ-B has 3 nominal cpu and 2 lendingLimit, 313 // so when lendingLimit enabled, the cohort's RequestableResources is 4 cpu. 314 corev1.ResourceCPU: 4, 315 }, 316 }, 317 Usage: FlavorResourceQuantities{ 318 "f1": map[corev1.ResourceName]int64{ 319 corev1.ResourceCPU: 2, 320 }, 321 }, 322 }, 323 GuaranteedQuota: FlavorResourceQuantities{ 324 "f1": { 325 corev1.ResourceCPU: 0, 326 }, 327 }, 328 }, 329 enableLendingLimit: true, 330 }, 331 "lendingLimit enabled can fit": { 332 request: FlavorResourceQuantities{ 333 "f1": map[corev1.ResourceName]int64{ 334 corev1.ResourceCPU: 3, 335 }, 336 }, 337 wantFit: true, 338 cq: &ClusterQueue{ 339 Name: "CQ-A", 340 Cohort: &Cohort{ 341 Name: "C", 342 RequestableResources: FlavorResourceQuantities{ 343 "f1": map[corev1.ResourceName]int64{ 344 // CQ-A has 2 nominal cpu, CQ-B has 3 nominal cpu and 2 lendingLimit, 345 // so when lendingLimit enabled, the cohort's RequestableResources is 4 cpu. 346 corev1.ResourceCPU: 4, 347 }, 348 }, 349 Usage: FlavorResourceQuantities{ 350 "f1": map[corev1.ResourceName]int64{ 351 // CQ-B has admitted a workload with 2 cpus, but with 1 GuaranteedQuota, 352 // so when lendingLimit enabled, Cohort.Usage should be 2 - 1 = 1. 353 corev1.ResourceCPU: 1, 354 }, 355 }, 356 }, 357 GuaranteedQuota: FlavorResourceQuantities{ 358 "f1": { 359 corev1.ResourceCPU: 2, 360 }, 361 }, 362 }, 363 enableLendingLimit: true, 364 }, 365 } 366 367 for name, tc := range cases { 368 t.Run(name, func(t *testing.T) { 369 defer features.SetFeatureGateDuringTest(t, features.LendingLimit, tc.enableLendingLimit)() 370 got := tc.cq.FitInCohort(tc.request) 371 if got != tc.wantFit { 372 t.Errorf("Unexpected result, %v", got) 373 } 374 375 }) 376 } 377 378 } 379 380 func TestClusterQueueUpdate(t *testing.T) { 381 resourceFlavors := []*kueue.ResourceFlavor{ 382 {ObjectMeta: metav1.ObjectMeta{Name: "on-demand"}}, 383 {ObjectMeta: metav1.ObjectMeta{Name: "spot"}}, 384 } 385 clusterQueue := 386 *utiltesting.MakeClusterQueue("eng-alpha"). 387 QueueingStrategy(kueue.StrictFIFO). 388 Preemption(kueue.ClusterQueuePreemption{ 389 WithinClusterQueue: kueue.PreemptionPolicyLowerPriority, 390 }). 391 FlavorFungibility(kueue.FlavorFungibility{ 392 WhenCanPreempt: kueue.Preempt, 393 }). 394 ResourceGroup( 395 *utiltesting.MakeFlavorQuotas("on-demand"). 396 Resource(corev1.ResourceCPU, "50", "50").Obj(), 397 *utiltesting.MakeFlavorQuotas("spot"). 398 Resource(corev1.ResourceCPU, "100", "0").Obj(), 399 ).Obj() 400 newClusterQueue := 401 *utiltesting.MakeClusterQueue("eng-alpha"). 402 QueueingStrategy(kueue.StrictFIFO). 403 Preemption(kueue.ClusterQueuePreemption{ 404 WithinClusterQueue: kueue.PreemptionPolicyLowerPriority, 405 }). 406 FlavorFungibility(kueue.FlavorFungibility{ 407 WhenCanPreempt: kueue.Preempt, 408 }). 409 ResourceGroup( 410 *utiltesting.MakeFlavorQuotas("on-demand"). 411 Resource(corev1.ResourceCPU, "100", "50").Obj(), 412 *utiltesting.MakeFlavorQuotas("spot"). 413 Resource(corev1.ResourceCPU, "100", "0").Obj(), 414 ).Obj() 415 cases := []struct { 416 name string 417 cq *kueue.ClusterQueue 418 newcq *kueue.ClusterQueue 419 wantLastAssignmentGeneration int64 420 }{ 421 { 422 name: "RGs not change", 423 cq: &clusterQueue, 424 newcq: clusterQueue.DeepCopy(), 425 wantLastAssignmentGeneration: 1, 426 }, 427 { 428 name: "RGs changed", 429 cq: &clusterQueue, 430 newcq: &newClusterQueue, 431 wantLastAssignmentGeneration: 2, 432 }, 433 } 434 for _, tc := range cases { 435 t.Run(tc.name, func(t *testing.T) { 436 ctx, _ := utiltesting.ContextWithLog(t) 437 clientBuilder := utiltesting.NewClientBuilder(). 438 WithObjects( 439 &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "default"}}, 440 tc.cq, 441 ) 442 cl := clientBuilder.Build() 443 cqCache := New(cl) 444 // Workloads are loaded into queues or clusterQueues as we add them. 445 for _, rf := range resourceFlavors { 446 cqCache.AddOrUpdateResourceFlavor(rf) 447 } 448 if err := cqCache.AddClusterQueue(ctx, tc.cq); err != nil { 449 t.Fatalf("Inserting clusterQueue %s in cache: %v", tc.cq.Name, err) 450 } 451 if err := cqCache.UpdateClusterQueue(tc.newcq); err != nil { 452 t.Fatalf("Updating clusterQueue %s in cache: %v", tc.newcq.Name, err) 453 } 454 snapshot := cqCache.Snapshot() 455 if diff := cmp.Diff( 456 tc.wantLastAssignmentGeneration, 457 snapshot.ClusterQueues["eng-alpha"].AllocatableResourceGeneration); diff != "" { 458 t.Errorf("Unexpected assigned clusterQueues in cache (-want,+got):\n%s", diff) 459 } 460 }) 461 } 462 }