sigs.k8s.io/kueue@v0.6.2/pkg/queue/manager_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 queue 18 19 import ( 20 "context" 21 "errors" 22 "strings" 23 "sync" 24 "testing" 25 "time" 26 27 "github.com/google/go-cmp/cmp" 28 "github.com/google/go-cmp/cmp/cmpopts" 29 corev1 "k8s.io/api/core/v1" 30 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 31 "k8s.io/apimachinery/pkg/runtime" 32 "k8s.io/apimachinery/pkg/util/sets" 33 "sigs.k8s.io/controller-runtime/pkg/client" 34 35 kueue "sigs.k8s.io/kueue/apis/kueue/v1beta1" 36 utiltesting "sigs.k8s.io/kueue/pkg/util/testing" 37 "sigs.k8s.io/kueue/pkg/workload" 38 ) 39 40 const headsTimeout = 3 * time.Second 41 42 var cmpDump = []cmp.Option{ 43 cmpopts.SortSlices(func(a, b string) bool { return a < b }), 44 cmpopts.IgnoreFields(metav1.Condition{}, "LastTransitionTime"), 45 } 46 47 // TestAddLocalQueueOrphans verifies that pods added before adding the queue are 48 // present when the queue is added. 49 func TestAddLocalQueueOrphans(t *testing.T) { 50 kClient := utiltesting.NewFakeClient( 51 utiltesting.MakeWorkload("a", "earth").Queue("foo").Obj(), 52 utiltesting.MakeWorkload("b", "earth").Queue("bar").Obj(), 53 utiltesting.MakeWorkload("c", "earth").Queue("foo").Obj(), 54 utiltesting.MakeWorkload("d", "earth").Queue("foo"). 55 ReserveQuota(utiltesting.MakeAdmission("cq").Obj()).Obj(), 56 utiltesting.MakeWorkload("a", "moon").Queue("foo").Obj(), 57 ) 58 manager := NewManager(kClient, nil) 59 q := utiltesting.MakeLocalQueue("foo", "earth").Obj() 60 if err := manager.AddLocalQueue(context.Background(), q); err != nil { 61 t.Fatalf("Failed adding queue: %v", err) 62 } 63 qImpl := manager.localQueues[Key(q)] 64 workloadNames := workloadNamesFromLQ(qImpl) 65 if diff := cmp.Diff(sets.New("earth/a", "earth/c"), workloadNames); diff != "" { 66 t.Errorf("Unexpected items in queue foo (-want,+got):\n%s", diff) 67 } 68 } 69 70 // TestAddClusterQueueOrphans verifies that when a ClusterQueue is recreated, 71 // it adopts the existing workloads. 72 func TestAddClusterQueueOrphans(t *testing.T) { 73 scheme := runtime.NewScheme() 74 if err := kueue.AddToScheme(scheme); err != nil { 75 t.Fatalf("Failed adding kueue scheme: %v", err) 76 } 77 now := time.Now() 78 queues := []*kueue.LocalQueue{ 79 utiltesting.MakeLocalQueue("foo", "").ClusterQueue("cq").Obj(), 80 utiltesting.MakeLocalQueue("bar", "").ClusterQueue("cq").Obj(), 81 } 82 kClient := utiltesting.NewFakeClient( 83 utiltesting.MakeWorkload("a", "").Queue("foo").Creation(now.Add(time.Second)).Obj(), 84 utiltesting.MakeWorkload("b", "").Queue("bar").Creation(now).Obj(), 85 utiltesting.MakeWorkload("c", "").Queue("foo"). 86 ReserveQuota(utiltesting.MakeAdmission("cq").Obj()).Obj(), 87 utiltesting.MakeWorkload("d", "").Queue("baz").Obj(), 88 queues[0], 89 queues[1], 90 ) 91 ctx := context.Background() 92 manager := NewManager(kClient, nil) 93 cq := utiltesting.MakeClusterQueue("cq").Obj() 94 if err := manager.AddClusterQueue(ctx, cq); err != nil { 95 t.Fatalf("Failed adding cluster queue %s: %v", cq.Name, err) 96 } 97 for _, q := range queues { 98 if err := manager.AddLocalQueue(ctx, q); err != nil { 99 t.Fatalf("Failed adding queue %s: %v", q.Name, err) 100 } 101 } 102 103 wantActiveWorkloads := map[string][]string{ 104 "cq": {"/a", "/b"}, 105 } 106 if diff := cmp.Diff(wantActiveWorkloads, manager.Dump(), cmpDump...); diff != "" { 107 t.Errorf("Unexpected active workloads after creating all objects (-want,+got):\n%s", diff) 108 } 109 110 // Recreating the ClusterQueue. 111 manager.DeleteClusterQueue(cq) 112 wantActiveWorkloads = nil 113 if diff := cmp.Diff(wantActiveWorkloads, manager.Dump(), cmpDump...); diff != "" { 114 t.Errorf("Unexpected active workloads after deleting ClusterQueue (-want,+got):\n%s", diff) 115 } 116 117 if err := manager.AddClusterQueue(ctx, cq); err != nil { 118 t.Fatalf("Could not re-add ClusterQueue: %v", err) 119 } 120 workloads := popNamesFromCQ(manager.clusterQueues["cq"]) 121 wantWorkloads := []string{"/b", "/a"} 122 if diff := cmp.Diff(wantWorkloads, workloads); diff != "" { 123 t.Errorf("Workloads popped in the wrong order from clusterQueue:\n%s", diff) 124 } 125 } 126 127 // TestUpdateClusterQueue tests that a ClusterQueue transfers cohorts on update. 128 // Inadmissible workloads should become active. 129 func TestUpdateClusterQueue(t *testing.T) { 130 clusterQueues := []*kueue.ClusterQueue{ 131 utiltesting.MakeClusterQueue("cq1").Cohort("alpha").Obj(), 132 utiltesting.MakeClusterQueue("cq2").Cohort("beta").Obj(), 133 } 134 queues := []*kueue.LocalQueue{ 135 utiltesting.MakeLocalQueue("foo", defaultNamespace).ClusterQueue("cq1").Obj(), 136 utiltesting.MakeLocalQueue("bar", defaultNamespace).ClusterQueue("cq2").Obj(), 137 } 138 now := time.Now() 139 workloads := []*kueue.Workload{ 140 utiltesting.MakeWorkload("a", defaultNamespace).Queue("foo").Creation(now.Add(time.Second)).Obj(), 141 utiltesting.MakeWorkload("b", defaultNamespace).Queue("bar").Creation(now).Obj(), 142 } 143 // Setup. 144 ctx := context.Background() 145 cl := utiltesting.NewFakeClient( 146 &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: defaultNamespace}}, 147 ) 148 manager := NewManager(cl, nil) 149 for _, cq := range clusterQueues { 150 if err := manager.AddClusterQueue(ctx, cq); err != nil { 151 t.Fatalf("Failed adding clusterQueue %s: %v", cq.Name, err) 152 } 153 } 154 for _, q := range queues { 155 if err := manager.AddLocalQueue(ctx, q); err != nil { 156 t.Fatalf("Failed adding queue %s: %v", q.Name, err) 157 } 158 } 159 // Add inadmissible workloads. 160 for _, w := range workloads { 161 if err := cl.Create(ctx, w); err != nil { 162 t.Fatalf("Failed adding workload to client: %v", err) 163 } 164 manager.RequeueWorkload(ctx, workload.NewInfo(w), RequeueReasonGeneric) 165 } 166 167 // Put cq2 in the same cohort as cq1. 168 clusterQueues[1].Spec.Cohort = clusterQueues[0].Spec.Cohort 169 if err := manager.UpdateClusterQueue(ctx, clusterQueues[1], true); err != nil { 170 t.Fatalf("Failed to update ClusterQueue: %v", err) 171 } 172 173 wantCohorts := map[string]sets.Set[string]{ 174 "alpha": sets.New("cq1", "cq2"), 175 } 176 if diff := cmp.Diff(manager.cohorts, wantCohorts); diff != "" { 177 t.Errorf("Unexpected ClusterQueues in cohorts (-want,+got):\n%s", diff) 178 } 179 180 // Verify all workloads are active after the update. 181 activeWorkloads := manager.Dump() 182 wantActiveWorkloads := map[string][]string{ 183 "cq1": []string{"default/a"}, 184 "cq2": []string{"default/b"}, 185 } 186 if diff := cmp.Diff(wantActiveWorkloads, activeWorkloads); diff != "" { 187 t.Errorf("Unexpected active workloads (-want +got):\n%s", diff) 188 } 189 } 190 191 // TestClusterQueueToActive tests that managers cond gets a broadcast when 192 // a cluster queue becomes active. 193 func TestClusterQueueToActive(t *testing.T) { 194 stoppedCq := utiltesting.MakeClusterQueue("cq1").Cohort("alpha").Condition(kueue.ClusterQueueActive, metav1.ConditionFalse, "ByTest", "by test").Obj() 195 runningCq := utiltesting.MakeClusterQueue("cq1").Cohort("alpha").Condition(kueue.ClusterQueueActive, metav1.ConditionTrue, "ByTest", "by test").Obj() 196 ctx := context.Background() 197 cl := utiltesting.NewFakeClient( 198 &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: defaultNamespace}}, 199 ) 200 manager := NewManager(cl, nil) 201 202 wgCounterStart := sync.WaitGroup{} 203 wgCounterStart.Add(1) 204 wgCounterEnd := sync.WaitGroup{} 205 wgCounterEnd.Add(1) 206 condRec := make(chan struct{}) 207 counterCtx, counterCancel := context.WithCancel(ctx) 208 go func() { 209 manager.cond.L.Lock() 210 defer manager.cond.L.Unlock() 211 wgCounterStart.Done() 212 defer wgCounterEnd.Done() 213 manager.cond.Wait() 214 select { 215 case <-counterCtx.Done(): 216 // the context was canceled before cond.Wait() 217 default: 218 condRec <- struct{}{} 219 } 220 }() 221 wgCounterStart.Wait() 222 go manager.CleanUpOnContext(counterCtx) 223 224 if err := manager.AddClusterQueue(ctx, stoppedCq); err != nil { 225 t.Fatalf("Failed adding clusterQueue %v", err) 226 } 227 228 if err := manager.UpdateClusterQueue(ctx, runningCq, false); err != nil { 229 t.Fatalf("Failed to update ClusterQueue: %v", err) 230 } 231 232 gotCondBeforeCleanup := false 233 select { 234 case <-condRec: 235 gotCondBeforeCleanup = true 236 case <-time.After(100 * time.Millisecond): 237 //nothing 238 } 239 240 counterCancel() 241 wgCounterEnd.Wait() 242 243 if !gotCondBeforeCleanup { 244 t.Fatalf("m.Broadcast was not called before cleanup") 245 } 246 } 247 248 // TestUpdateLocalQueue tests that workloads are transferred between clusterQueues 249 // when the queue points to a different clusterQueue. 250 func TestUpdateLocalQueue(t *testing.T) { 251 clusterQueues := []*kueue.ClusterQueue{ 252 utiltesting.MakeClusterQueue("cq1").Obj(), 253 utiltesting.MakeClusterQueue("cq2").Obj(), 254 } 255 queues := []*kueue.LocalQueue{ 256 utiltesting.MakeLocalQueue("foo", "").ClusterQueue("cq1").Obj(), 257 utiltesting.MakeLocalQueue("bar", "").ClusterQueue("cq2").Obj(), 258 } 259 now := time.Now() 260 workloads := []*kueue.Workload{ 261 utiltesting.MakeWorkload("a", "").Queue("foo").Creation(now.Add(time.Second)).Obj(), 262 utiltesting.MakeWorkload("b", "").Queue("bar").Creation(now).Obj(), 263 } 264 // Setup. 265 scheme := runtime.NewScheme() 266 if err := kueue.AddToScheme(scheme); err != nil { 267 t.Fatalf("Failed adding kueue scheme: %s", err) 268 } 269 ctx := context.Background() 270 manager := NewManager(utiltesting.NewFakeClient(), nil) 271 for _, cq := range clusterQueues { 272 if err := manager.AddClusterQueue(ctx, cq); err != nil { 273 t.Fatalf("Failed adding clusterQueue %s: %v", cq.Name, err) 274 } 275 } 276 for _, q := range queues { 277 if err := manager.AddLocalQueue(ctx, q); err != nil { 278 t.Fatalf("Failed adding queue %s: %v", q.Name, err) 279 } 280 } 281 for _, w := range workloads { 282 manager.AddOrUpdateWorkload(w) 283 } 284 285 // Update cluster queue of first queue. 286 queues[0].Spec.ClusterQueue = "cq2" 287 if err := manager.UpdateLocalQueue(queues[0]); err != nil { 288 t.Fatalf("Failed updating queue: %v", err) 289 } 290 291 // Verification. 292 workloadOrders := make(map[string][]string) 293 for name, cq := range manager.clusterQueues { 294 workloadOrders[name] = popNamesFromCQ(cq) 295 } 296 wantWorkloadOrders := map[string][]string{ 297 "cq1": nil, 298 "cq2": {"/b", "/a"}, 299 } 300 if diff := cmp.Diff(wantWorkloadOrders, workloadOrders); diff != "" { 301 t.Errorf("workloads popped in the wrong order from clusterQueues:\n%s", diff) 302 } 303 } 304 305 // TestDeleteLocalQueue tests that when a LocalQueue is deleted, all its 306 // workloads are not listed in the ClusterQueue. 307 func TestDeleteLocalQueue(t *testing.T) { 308 cq := utiltesting.MakeClusterQueue("cq").Obj() 309 q := utiltesting.MakeLocalQueue("foo", "").ClusterQueue("cq").Obj() 310 wl := utiltesting.MakeWorkload("a", "").Queue("foo").Obj() 311 312 ctx := context.Background() 313 cl := utiltesting.NewFakeClient(wl) 314 manager := NewManager(cl, nil) 315 316 if err := manager.AddClusterQueue(ctx, cq); err != nil { 317 t.Fatalf("Could not create ClusterQueue: %v", err) 318 } 319 if err := manager.AddLocalQueue(ctx, q); err != nil { 320 t.Fatalf("Could not create LocalQueue: %v", err) 321 } 322 323 wantActiveWorkloads := map[string][]string{ 324 "cq": {"/a"}, 325 } 326 if diff := cmp.Diff(wantActiveWorkloads, manager.Dump(), cmpDump...); diff != "" { 327 t.Errorf("Unexpected workloads after setup (-want,+got):\n%s", diff) 328 } 329 330 manager.DeleteLocalQueue(q) 331 wantActiveWorkloads = nil 332 if diff := cmp.Diff(wantActiveWorkloads, manager.Dump(), cmpDump...); diff != "" { 333 t.Errorf("Unexpected workloads after deleting LocalQueue (-want,+got):\n%s", diff) 334 } 335 } 336 337 func TestAddWorkload(t *testing.T) { 338 manager := NewManager(utiltesting.NewFakeClient(), nil) 339 cq := utiltesting.MakeClusterQueue("cq").Obj() 340 if err := manager.AddClusterQueue(context.Background(), cq); err != nil { 341 t.Fatalf("Failed adding clusterQueue %s: %v", cq.Name, err) 342 } 343 queues := []*kueue.LocalQueue{ 344 utiltesting.MakeLocalQueue("foo", "earth").ClusterQueue("cq").Obj(), 345 utiltesting.MakeLocalQueue("bar", "mars").Obj(), 346 } 347 for _, q := range queues { 348 if err := manager.AddLocalQueue(context.Background(), q); err != nil { 349 t.Fatalf("Failed adding queue %s: %v", q.Name, err) 350 } 351 } 352 cases := []struct { 353 workload *kueue.Workload 354 wantAdded bool 355 }{ 356 { 357 workload: &kueue.Workload{ 358 ObjectMeta: metav1.ObjectMeta{ 359 Namespace: "earth", 360 Name: "existing_queue", 361 }, 362 Spec: kueue.WorkloadSpec{QueueName: "foo"}, 363 }, 364 wantAdded: true, 365 }, 366 { 367 workload: &kueue.Workload{ 368 ObjectMeta: metav1.ObjectMeta{ 369 Namespace: "earth", 370 Name: "non_existing_queue", 371 }, 372 Spec: kueue.WorkloadSpec{QueueName: "baz"}, 373 }, 374 }, 375 { 376 workload: &kueue.Workload{ 377 ObjectMeta: metav1.ObjectMeta{ 378 Namespace: "mars", 379 Name: "non_existing_cluster_queue", 380 }, 381 Spec: kueue.WorkloadSpec{QueueName: "bar"}, 382 }, 383 }, 384 { 385 workload: &kueue.Workload{ 386 ObjectMeta: metav1.ObjectMeta{ 387 Namespace: "mars", 388 Name: "wrong_namespace", 389 }, 390 Spec: kueue.WorkloadSpec{QueueName: "foo"}, 391 }, 392 }, 393 } 394 for _, tc := range cases { 395 t.Run(tc.workload.Name, func(t *testing.T) { 396 if added := manager.AddOrUpdateWorkload(tc.workload); added != tc.wantAdded { 397 t.Errorf("AddWorkload returned %t, want %t", added, tc.wantAdded) 398 } 399 }) 400 } 401 } 402 403 func TestStatus(t *testing.T) { 404 ctx := context.Background() 405 scheme := runtime.NewScheme() 406 if err := kueue.AddToScheme(scheme); err != nil { 407 t.Fatalf("Failed adding kueue scheme: %s", err) 408 } 409 now := time.Now().Truncate(time.Second) 410 411 queues := []kueue.LocalQueue{ 412 { 413 ObjectMeta: metav1.ObjectMeta{Name: "foo"}, 414 Spec: kueue.LocalQueueSpec{ 415 ClusterQueue: "fooCq", 416 }, 417 }, 418 { 419 ObjectMeta: metav1.ObjectMeta{Name: "bar"}, 420 Spec: kueue.LocalQueueSpec{ 421 ClusterQueue: "barCq", 422 }, 423 }, 424 } 425 workloads := []kueue.Workload{ 426 { 427 ObjectMeta: metav1.ObjectMeta{ 428 Name: "a", 429 CreationTimestamp: metav1.NewTime(now.Add(time.Hour)), 430 }, 431 Spec: kueue.WorkloadSpec{QueueName: "foo"}, 432 }, 433 { 434 ObjectMeta: metav1.ObjectMeta{ 435 Name: "b", 436 CreationTimestamp: metav1.NewTime(now), 437 }, 438 Spec: kueue.WorkloadSpec{QueueName: "bar"}, 439 }, 440 { 441 ObjectMeta: metav1.ObjectMeta{ 442 Name: "c", 443 CreationTimestamp: metav1.NewTime(now), 444 }, 445 Spec: kueue.WorkloadSpec{QueueName: "foo"}, 446 }, 447 { 448 ObjectMeta: metav1.ObjectMeta{ 449 Name: "d", 450 CreationTimestamp: metav1.NewTime(now), 451 }, 452 Spec: kueue.WorkloadSpec{QueueName: "foo"}, 453 }, 454 } 455 456 manager := NewManager(utiltesting.NewFakeClient(), nil) 457 for _, q := range queues { 458 if err := manager.AddLocalQueue(ctx, &q); err != nil { 459 t.Errorf("Failed adding queue: %s", err) 460 } 461 } 462 for _, wl := range workloads { 463 wl := wl 464 manager.AddOrUpdateWorkload(&wl) 465 } 466 467 cases := map[string]struct { 468 queue *kueue.LocalQueue 469 wantStatus int32 470 wantErr error 471 }{ 472 "foo": { 473 queue: &queues[0], 474 wantStatus: 3, 475 wantErr: nil, 476 }, 477 "bar": { 478 queue: &queues[1], 479 wantStatus: 1, 480 wantErr: nil, 481 }, 482 "fake": { 483 queue: &kueue.LocalQueue{ObjectMeta: metav1.ObjectMeta{Name: "fake"}}, 484 wantStatus: 0, 485 wantErr: errQueueDoesNotExist, 486 }, 487 } 488 for name, tc := range cases { 489 t.Run(name, func(t *testing.T) { 490 status, err := manager.PendingWorkloads(tc.queue) 491 if !errors.Is(err, tc.wantErr) { 492 t.Errorf("Should have failed with: %s", err) 493 } 494 if diff := cmp.Diff(tc.wantStatus, status); diff != "" { 495 t.Errorf("Status func returned wrong queue status: %s", diff) 496 } 497 }) 498 } 499 } 500 501 func TestRequeueWorkloadStrictFIFO(t *testing.T) { 502 cq := utiltesting.MakeClusterQueue("cq").Obj() 503 queues := []*kueue.LocalQueue{ 504 utiltesting.MakeLocalQueue("foo", "").ClusterQueue("cq").Obj(), 505 utiltesting.MakeLocalQueue("bar", "").Obj(), 506 } 507 cases := []struct { 508 workload *kueue.Workload 509 inClient bool 510 inQueue bool 511 wantRequeued bool 512 }{ 513 { 514 workload: &kueue.Workload{ 515 ObjectMeta: metav1.ObjectMeta{Name: "existing_queue_and_obj"}, 516 Spec: kueue.WorkloadSpec{QueueName: "foo"}, 517 }, 518 inClient: true, 519 wantRequeued: true, 520 }, 521 { 522 workload: &kueue.Workload{ 523 ObjectMeta: metav1.ObjectMeta{Name: "non_existing_queue"}, 524 Spec: kueue.WorkloadSpec{QueueName: "baz"}, 525 }, 526 inClient: true, 527 }, 528 { 529 workload: &kueue.Workload{ 530 ObjectMeta: metav1.ObjectMeta{Name: "non_existing_cluster_queue"}, 531 Spec: kueue.WorkloadSpec{QueueName: "bar"}, 532 }, 533 inClient: true, 534 }, 535 { 536 workload: &kueue.Workload{ 537 ObjectMeta: metav1.ObjectMeta{Name: "not_in_client"}, 538 Spec: kueue.WorkloadSpec{QueueName: "foo"}, 539 }, 540 }, 541 { 542 workload: &kueue.Workload{ 543 ObjectMeta: metav1.ObjectMeta{Name: "already_in_queue"}, 544 Spec: kueue.WorkloadSpec{QueueName: "foo"}, 545 }, 546 inClient: true, 547 inQueue: true, 548 }, 549 { 550 workload: &kueue.Workload{ 551 ObjectMeta: metav1.ObjectMeta{Name: "already_admitted"}, 552 Spec: kueue.WorkloadSpec{ 553 QueueName: "foo", 554 }, 555 Status: kueue.WorkloadStatus{ 556 Admission: &kueue.Admission{}, 557 }, 558 }, 559 inClient: true, 560 inQueue: true, 561 }, 562 } 563 for _, tc := range cases { 564 t.Run(tc.workload.Name, func(t *testing.T) { 565 cl := utiltesting.NewFakeClient() 566 manager := NewManager(cl, nil) 567 ctx := context.Background() 568 if err := manager.AddClusterQueue(ctx, cq); err != nil { 569 t.Fatalf("Failed adding cluster queue %s: %v", cq.Name, err) 570 } 571 for _, q := range queues { 572 if err := manager.AddLocalQueue(ctx, q); err != nil { 573 t.Fatalf("Failed adding queue %s: %v", q.Name, err) 574 } 575 } 576 // Adding workload to client after the queues are created, otherwise it 577 // will be in the queue. 578 if tc.inClient { 579 if err := cl.Create(ctx, tc.workload); err != nil { 580 t.Fatalf("Failed adding workload to client: %v", err) 581 } 582 } 583 if tc.inQueue { 584 _ = manager.AddOrUpdateWorkload(tc.workload) 585 } 586 info := workload.NewInfo(tc.workload) 587 if requeued := manager.RequeueWorkload(ctx, info, RequeueReasonGeneric); requeued != tc.wantRequeued { 588 t.Errorf("RequeueWorkload returned %t, want %t", requeued, tc.wantRequeued) 589 } 590 }) 591 } 592 } 593 594 func TestUpdateWorkload(t *testing.T) { 595 scheme := runtime.NewScheme() 596 if err := kueue.AddToScheme(scheme); err != nil { 597 t.Fatalf("Failed adding kueue scheme: %s", err) 598 } 599 now := time.Now() 600 cases := map[string]struct { 601 clusterQueues []*kueue.ClusterQueue 602 queues []*kueue.LocalQueue 603 workloads []*kueue.Workload 604 update func(*kueue.Workload) 605 wantUpdated bool 606 wantQueueOrder map[string][]string 607 wantQueueMembers map[string]sets.Set[string] 608 }{ 609 "in queue": { 610 clusterQueues: []*kueue.ClusterQueue{ 611 utiltesting.MakeClusterQueue("cq").Obj(), 612 }, 613 queues: []*kueue.LocalQueue{ 614 utiltesting.MakeLocalQueue("foo", "").ClusterQueue("cq").Obj(), 615 }, 616 workloads: []*kueue.Workload{ 617 utiltesting.MakeWorkload("a", "").Queue("foo").Creation(now).Obj(), 618 utiltesting.MakeWorkload("b", "").Queue("foo").Creation(now.Add(time.Second)).Obj(), 619 }, 620 update: func(w *kueue.Workload) { 621 w.CreationTimestamp = metav1.NewTime(now.Add(time.Minute)) 622 }, 623 wantUpdated: true, 624 wantQueueOrder: map[string][]string{ 625 "cq": {"/b", "/a"}, 626 }, 627 wantQueueMembers: map[string]sets.Set[string]{ 628 "/foo": sets.New("/a", "/b"), 629 }, 630 }, 631 "between queues": { 632 clusterQueues: []*kueue.ClusterQueue{ 633 utiltesting.MakeClusterQueue("cq").Obj(), 634 }, 635 queues: []*kueue.LocalQueue{ 636 utiltesting.MakeLocalQueue("foo", "").ClusterQueue("cq").Obj(), 637 utiltesting.MakeLocalQueue("bar", "").ClusterQueue("cq").Obj(), 638 }, 639 workloads: []*kueue.Workload{ 640 utiltesting.MakeWorkload("a", "").Queue("foo").Obj(), 641 }, 642 update: func(w *kueue.Workload) { 643 w.Spec.QueueName = "bar" 644 }, 645 wantUpdated: true, 646 wantQueueOrder: map[string][]string{ 647 "cq": {"/a"}, 648 }, 649 wantQueueMembers: map[string]sets.Set[string]{ 650 "/foo": nil, 651 "/bar": sets.New("/a"), 652 }, 653 }, 654 "between cluster queues": { 655 clusterQueues: []*kueue.ClusterQueue{ 656 utiltesting.MakeClusterQueue("cq1").Obj(), 657 utiltesting.MakeClusterQueue("cq2").Obj(), 658 }, 659 queues: []*kueue.LocalQueue{ 660 utiltesting.MakeLocalQueue("foo", "").ClusterQueue("cq1").Obj(), 661 utiltesting.MakeLocalQueue("bar", "").ClusterQueue("cq2").Obj(), 662 }, 663 workloads: []*kueue.Workload{ 664 utiltesting.MakeWorkload("a", "").Queue("foo").Obj(), 665 }, 666 update: func(w *kueue.Workload) { 667 w.Spec.QueueName = "bar" 668 }, 669 wantUpdated: true, 670 wantQueueOrder: map[string][]string{ 671 "cq1": nil, 672 "cq2": {"/a"}, 673 }, 674 wantQueueMembers: map[string]sets.Set[string]{ 675 "/foo": nil, 676 "/bar": sets.New("/a"), 677 }, 678 }, 679 "to non existent queue": { 680 clusterQueues: []*kueue.ClusterQueue{ 681 utiltesting.MakeClusterQueue("cq").Obj(), 682 }, 683 queues: []*kueue.LocalQueue{ 684 utiltesting.MakeLocalQueue("foo", "").ClusterQueue("cq").Obj(), 685 }, 686 workloads: []*kueue.Workload{ 687 utiltesting.MakeWorkload("a", "").Queue("foo").Obj(), 688 }, 689 update: func(w *kueue.Workload) { 690 w.Spec.QueueName = "bar" 691 }, 692 wantQueueOrder: map[string][]string{ 693 "cq": nil, 694 }, 695 wantQueueMembers: map[string]sets.Set[string]{ 696 "/foo": nil, 697 }, 698 }, 699 "from non existing queue": { 700 clusterQueues: []*kueue.ClusterQueue{ 701 utiltesting.MakeClusterQueue("cq").Obj(), 702 }, 703 queues: []*kueue.LocalQueue{ 704 utiltesting.MakeLocalQueue("foo", "").ClusterQueue("cq").Obj(), 705 }, 706 workloads: []*kueue.Workload{ 707 utiltesting.MakeWorkload("a", "").Queue("bar").Obj(), 708 }, 709 update: func(w *kueue.Workload) { 710 w.Spec.QueueName = "foo" 711 }, 712 wantUpdated: true, 713 wantQueueOrder: map[string][]string{ 714 "cq": {"/a"}, 715 }, 716 wantQueueMembers: map[string]sets.Set[string]{ 717 "/foo": sets.New("/a"), 718 }, 719 }, 720 } 721 for name, tc := range cases { 722 t.Run(name, func(t *testing.T) { 723 manager := NewManager(utiltesting.NewFakeClient(), nil) 724 ctx := context.Background() 725 for _, cq := range tc.clusterQueues { 726 if err := manager.AddClusterQueue(ctx, cq); err != nil { 727 t.Fatalf("Adding cluster queue %s: %v", cq.Name, err) 728 } 729 } 730 for _, q := range tc.queues { 731 if err := manager.AddLocalQueue(ctx, q); err != nil { 732 t.Fatalf("Adding queue %q: %v", q.Name, err) 733 } 734 } 735 for _, w := range tc.workloads { 736 manager.AddOrUpdateWorkload(w) 737 } 738 wl := tc.workloads[0].DeepCopy() 739 tc.update(wl) 740 if updated := manager.UpdateWorkload(tc.workloads[0], wl); updated != tc.wantUpdated { 741 t.Errorf("UpdatedWorkload returned %t, want %t", updated, tc.wantUpdated) 742 } 743 q := manager.localQueues[workload.QueueKey(wl)] 744 if q != nil { 745 key := workload.Key(wl) 746 item := q.items[key] 747 if item == nil { 748 t.Errorf("Object not stored in queue") 749 } else if diff := cmp.Diff(wl, item.Obj); diff != "" { 750 t.Errorf("Object stored in queue differs (-want,+got):\n%s", diff) 751 } 752 cq := manager.clusterQueues[q.ClusterQueue] 753 if cq != nil { 754 item := cq.Info(key) 755 if item == nil { 756 t.Errorf("Object not stored in clusterQueue") 757 } else if diff := cmp.Diff(wl, item.Obj); diff != "" { 758 t.Errorf("Object stored in clusterQueue differs (-want,+got):\n%s", diff) 759 } 760 } 761 } 762 queueOrder := make(map[string][]string) 763 for name, cq := range manager.clusterQueues { 764 queueOrder[name] = popNamesFromCQ(cq) 765 } 766 if diff := cmp.Diff(tc.wantQueueOrder, queueOrder); diff != "" { 767 t.Errorf("Elements popped in the wrong order from clusterQueues (-want,+got):\n%s", diff) 768 } 769 queueMembers := make(map[string]sets.Set[string]) 770 for name, q := range manager.localQueues { 771 queueMembers[name] = workloadNamesFromLQ(q) 772 } 773 if diff := cmp.Diff(tc.wantQueueMembers, queueMembers); diff != "" { 774 t.Errorf("Elements present in wrong queues (-want,+got):\n%s", diff) 775 } 776 }) 777 } 778 } 779 780 func TestHeads(t *testing.T) { 781 scheme := runtime.NewScheme() 782 if err := kueue.AddToScheme(scheme); err != nil { 783 t.Fatalf("Failed adding kueue scheme: %s", err) 784 } 785 now := time.Now().Truncate(time.Second) 786 787 clusterQueues := []*kueue.ClusterQueue{ 788 utiltesting.MakeClusterQueue("active-fooCq").Obj(), 789 utiltesting.MakeClusterQueue("active-barCq").Obj(), 790 utiltesting.MakeClusterQueue("pending-bazCq").Obj(), 791 } 792 queues := []*kueue.LocalQueue{ 793 utiltesting.MakeLocalQueue("foo", "").ClusterQueue("active-fooCq").Obj(), 794 utiltesting.MakeLocalQueue("bar", "").ClusterQueue("active-barCq").Obj(), 795 utiltesting.MakeLocalQueue("baz", "").ClusterQueue("pending-bazCq").Obj(), 796 } 797 tests := []struct { 798 name string 799 workloads []*kueue.Workload 800 wantWorkloads sets.Set[string] 801 }{ 802 { 803 name: "empty clusterQueues", 804 workloads: []*kueue.Workload{}, 805 wantWorkloads: sets.Set[string]{}, 806 }, 807 { 808 name: "active clusterQueues", 809 workloads: []*kueue.Workload{ 810 utiltesting.MakeWorkload("a", "").Creation(now).Queue("foo").Obj(), 811 utiltesting.MakeWorkload("b", "").Creation(now).Queue("bar").Obj(), 812 }, 813 wantWorkloads: sets.New("a", "b"), 814 }, 815 { 816 name: "active clusterQueues with multiple workloads", 817 workloads: []*kueue.Workload{ 818 utiltesting.MakeWorkload("a1", "").Creation(now).Queue("foo").Obj(), 819 utiltesting.MakeWorkload("a2", "").Creation(now.Add(time.Hour)).Queue("foo").Obj(), 820 utiltesting.MakeWorkload("b", "").Creation(now).Queue("bar").Obj(), 821 }, 822 wantWorkloads: sets.New("a1", "b"), 823 }, 824 { 825 name: "inactive clusterQueues", 826 workloads: []*kueue.Workload{ 827 utiltesting.MakeWorkload("a", "").Creation(now).Queue("foo").Obj(), 828 utiltesting.MakeWorkload("b", "").Creation(now).Queue("bar").Obj(), 829 utiltesting.MakeWorkload("c", "").Creation(now.Add(time.Hour)).Queue("baz").Obj(), 830 }, 831 wantWorkloads: sets.New("a", "b"), 832 }, 833 } 834 for _, tc := range tests { 835 t.Run(tc.name, func(t *testing.T) { 836 ctx, cancel := context.WithTimeout(context.Background(), headsTimeout) 837 defer cancel() 838 fakeC := &fakeStatusChecker{} 839 manager := NewManager(utiltesting.NewFakeClient(), fakeC) 840 for _, cq := range clusterQueues { 841 if err := manager.AddClusterQueue(ctx, cq); err != nil { 842 t.Fatalf("Failed adding clusterQueue %s to manager: %v", cq.Name, err) 843 } 844 } 845 for _, q := range queues { 846 if err := manager.AddLocalQueue(ctx, q); err != nil { 847 t.Fatalf("Failed adding queue %s: %s", q.Name, err) 848 } 849 } 850 851 go manager.CleanUpOnContext(ctx) 852 for _, wl := range tc.workloads { 853 manager.AddOrUpdateWorkload(wl) 854 } 855 856 wlNames := sets.New[string]() 857 heads := manager.Heads(ctx) 858 for _, h := range heads { 859 wlNames.Insert(h.Obj.Name) 860 } 861 if diff := cmp.Diff(tc.wantWorkloads, wlNames); diff != "" { 862 t.Errorf("GetHeads returned wrong heads (-want,+got):\n%s", diff) 863 } 864 }) 865 } 866 } 867 868 var ignoreTypeMeta = cmpopts.IgnoreTypes(metav1.TypeMeta{}) 869 870 // TestHeadAsync ensures that Heads call is blocked until the queues are filled 871 // asynchronously. 872 func TestHeadsAsync(t *testing.T) { 873 now := time.Now().Truncate(time.Second) 874 clusterQueues := []*kueue.ClusterQueue{ 875 utiltesting.MakeClusterQueue("fooCq").Obj(), 876 utiltesting.MakeClusterQueue("barCq").Obj(), 877 } 878 wl := kueue.Workload{ 879 ObjectMeta: metav1.ObjectMeta{ 880 Name: "a", 881 CreationTimestamp: metav1.NewTime(now), 882 }, 883 Spec: kueue.WorkloadSpec{QueueName: "foo"}, 884 } 885 var newWl kueue.Workload 886 queues := []kueue.LocalQueue{ 887 { 888 ObjectMeta: metav1.ObjectMeta{Name: "foo"}, 889 Spec: kueue.LocalQueueSpec{ 890 ClusterQueue: "fooCq", 891 }, 892 }, 893 { 894 ObjectMeta: metav1.ObjectMeta{Name: "bar"}, 895 Spec: kueue.LocalQueueSpec{ 896 ClusterQueue: "barCq", 897 }, 898 }, 899 } 900 cases := map[string]struct { 901 initialObjs []client.Object 902 op func(context.Context, *Manager) 903 wantHeads []workload.Info 904 }{ 905 "AddClusterQueue": { 906 initialObjs: []client.Object{&wl, &queues[0]}, 907 op: func(ctx context.Context, mgr *Manager) { 908 if err := mgr.AddLocalQueue(ctx, &queues[0]); err != nil { 909 t.Errorf("Failed adding queue: %s", err) 910 } 911 mgr.AddOrUpdateWorkload(&wl) 912 go func() { 913 if err := mgr.AddClusterQueue(ctx, clusterQueues[0]); err != nil { 914 t.Errorf("Failed adding clusterQueue: %v", err) 915 } 916 }() 917 }, 918 wantHeads: []workload.Info{ 919 { 920 Obj: &wl, 921 ClusterQueue: "fooCq", 922 }, 923 }, 924 }, 925 "AddLocalQueue": { 926 initialObjs: []client.Object{&wl}, 927 op: func(ctx context.Context, mgr *Manager) { 928 if err := mgr.AddClusterQueue(ctx, clusterQueues[0]); err != nil { 929 t.Errorf("Failed adding clusterQueue: %v", err) 930 } 931 go func() { 932 if err := mgr.AddLocalQueue(ctx, &queues[0]); err != nil { 933 t.Errorf("Failed adding queue: %s", err) 934 } 935 }() 936 }, 937 wantHeads: []workload.Info{ 938 { 939 Obj: &wl, 940 ClusterQueue: "fooCq", 941 }, 942 }, 943 }, 944 "AddWorkload": { 945 op: func(ctx context.Context, mgr *Manager) { 946 if err := mgr.AddClusterQueue(ctx, clusterQueues[0]); err != nil { 947 t.Errorf("Failed adding clusterQueue: %v", err) 948 } 949 if err := mgr.AddLocalQueue(ctx, &queues[0]); err != nil { 950 t.Errorf("Failed adding queue: %s", err) 951 } 952 go func() { 953 mgr.AddOrUpdateWorkload(&wl) 954 }() 955 }, 956 wantHeads: []workload.Info{ 957 { 958 Obj: &wl, 959 ClusterQueue: "fooCq", 960 }, 961 }, 962 }, 963 "UpdateWorkload": { 964 op: func(ctx context.Context, mgr *Manager) { 965 if err := mgr.AddClusterQueue(ctx, clusterQueues[0]); err != nil { 966 t.Errorf("Failed adding clusterQueue: %v", err) 967 } 968 if err := mgr.AddLocalQueue(ctx, &queues[0]); err != nil { 969 t.Errorf("Failed adding queue: %s", err) 970 } 971 go func() { 972 wlCopy := wl.DeepCopy() 973 wlCopy.ResourceVersion = "old" 974 mgr.UpdateWorkload(wlCopy, &wl) 975 }() 976 }, 977 wantHeads: []workload.Info{ 978 { 979 Obj: &wl, 980 ClusterQueue: "fooCq", 981 }, 982 }, 983 }, 984 "RequeueWorkload": { 985 initialObjs: []client.Object{&wl}, 986 op: func(ctx context.Context, mgr *Manager) { 987 if err := mgr.AddClusterQueue(ctx, clusterQueues[0]); err != nil { 988 t.Errorf("Failed adding clusterQueue: %v", err) 989 } 990 if err := mgr.AddLocalQueue(ctx, &queues[0]); err != nil { 991 t.Errorf("Failed adding queue: %s", err) 992 } 993 // Remove the initial workload from the manager. 994 mgr.Heads(ctx) 995 go func() { 996 mgr.RequeueWorkload(ctx, workload.NewInfo(&wl), RequeueReasonFailedAfterNomination) 997 }() 998 }, 999 wantHeads: []workload.Info{ 1000 { 1001 Obj: &wl, 1002 ClusterQueue: "fooCq", 1003 }, 1004 }, 1005 }, 1006 "RequeueWithOutOfDateWorkload": { 1007 initialObjs: []client.Object{&wl}, 1008 op: func(ctx context.Context, mgr *Manager) { 1009 if err := mgr.AddClusterQueue(ctx, clusterQueues[0]); err != nil { 1010 t.Errorf("Failed adding clusterQueue: %v", err) 1011 } 1012 if err := mgr.AddLocalQueue(ctx, &queues[0]); err != nil { 1013 t.Errorf("Failed adding queue: %s", err) 1014 } 1015 1016 newWl = wl 1017 newWl.Annotations = map[string]string{"foo": "bar"} 1018 if err := mgr.client.Update(ctx, &newWl, &client.UpdateOptions{}); err != nil { 1019 t.Errorf("Failed to update the workload; %s", err) 1020 } 1021 // Remove the initial workload from the manager. 1022 mgr.Heads(ctx) 1023 go func() { 1024 mgr.RequeueWorkload(ctx, workload.NewInfo(&wl), RequeueReasonFailedAfterNomination) 1025 }() 1026 }, 1027 wantHeads: []workload.Info{ 1028 { 1029 Obj: &newWl, 1030 ClusterQueue: "fooCq", 1031 }, 1032 }, 1033 }, 1034 "RequeueWithQueueChangedWorkload": { 1035 initialObjs: []client.Object{&wl}, 1036 op: func(ctx context.Context, mgr *Manager) { 1037 for _, cq := range clusterQueues { 1038 if err := mgr.AddClusterQueue(ctx, cq); err != nil { 1039 t.Errorf("Failed adding clusterQueue: %v", err) 1040 } 1041 } 1042 for _, q := range queues { 1043 if err := mgr.AddLocalQueue(ctx, &q); err != nil { 1044 t.Errorf("Failed adding queue: %s", err) 1045 } 1046 } 1047 1048 newWl = wl 1049 newWl.Spec.QueueName = "bar" 1050 if err := mgr.client.Update(ctx, &newWl, &client.UpdateOptions{}); err != nil { 1051 t.Errorf("Failed to update the workload; %s", err) 1052 } 1053 // Remove the initial workload from the manager. 1054 mgr.Heads(ctx) 1055 go func() { 1056 mgr.RequeueWorkload(ctx, workload.NewInfo(&wl), RequeueReasonFailedAfterNomination) 1057 }() 1058 }, 1059 wantHeads: []workload.Info{ 1060 { 1061 Obj: &newWl, 1062 ClusterQueue: "barCq", 1063 }, 1064 }, 1065 }, 1066 } 1067 for name, tc := range cases { 1068 t.Run(name, func(t *testing.T) { 1069 ctx, cancel := context.WithTimeout(context.Background(), headsTimeout) 1070 defer cancel() 1071 client := utiltesting.NewFakeClient(tc.initialObjs...) 1072 manager := NewManager(client, nil) 1073 go manager.CleanUpOnContext(ctx) 1074 tc.op(ctx, manager) 1075 heads := manager.Heads(ctx) 1076 if diff := cmp.Diff(tc.wantHeads, heads, ignoreTypeMeta); diff != "" { 1077 t.Errorf("GetHeads returned wrong heads (-want,+got):\n%s", diff) 1078 } 1079 }) 1080 } 1081 } 1082 1083 // TestHeadsCancelled ensures that the Heads call returns when the context is closed. 1084 func TestHeadsCancelled(t *testing.T) { 1085 manager := NewManager(utiltesting.NewFakeClient(), nil) 1086 ctx, cancel := context.WithCancel(context.Background()) 1087 go func() { 1088 cancel() 1089 }() 1090 manager.CleanUpOnContext(ctx) 1091 heads := manager.Heads(ctx) 1092 if len(heads) != 0 { 1093 t.Errorf("GetHeads returned elements, expected none") 1094 } 1095 } 1096 1097 // popNamesFromCQ pops all the workloads from the clusterQueue and returns 1098 // the keyed names in the order they are popped. 1099 func popNamesFromCQ(cq ClusterQueue) []string { 1100 var names []string 1101 for w := cq.Pop(); w != nil; w = cq.Pop() { 1102 names = append(names, workload.Key(w.Obj)) 1103 } 1104 return names 1105 } 1106 1107 // workloadNamesFromLQ returns all the names of the workloads in a localQueue. 1108 func workloadNamesFromLQ(q *LocalQueue) sets.Set[string] { 1109 names := sets.New[string]() 1110 for k := range q.items { 1111 names.Insert(k) 1112 } 1113 return names 1114 } 1115 1116 type fakeStatusChecker struct{} 1117 1118 func (c *fakeStatusChecker) ClusterQueueActive(name string) bool { 1119 return strings.Contains(name, "active-") 1120 } 1121 1122 func TestGetPendingWorkloadsInfo(t *testing.T) { 1123 now := time.Now().Truncate(time.Second) 1124 1125 clusterQueues := []*kueue.ClusterQueue{ 1126 utiltesting.MakeClusterQueue("cq").Obj(), 1127 } 1128 queues := []*kueue.LocalQueue{ 1129 utiltesting.MakeLocalQueue("foo", "").ClusterQueue("cq").Obj(), 1130 } 1131 workloads := []*kueue.Workload{ 1132 utiltesting.MakeWorkload("a", "").Queue("foo").Creation(now).Obj(), 1133 utiltesting.MakeWorkload("b", "").Queue("foo").Creation(now.Add(time.Second)).Obj(), 1134 } 1135 1136 // Setup. 1137 scheme := runtime.NewScheme() 1138 if err := kueue.AddToScheme(scheme); err != nil { 1139 t.Fatalf("Failed adding kueue scheme: %s", err) 1140 } 1141 ctx := context.Background() 1142 manager := NewManager(utiltesting.NewFakeClient(), nil) 1143 for _, cq := range clusterQueues { 1144 if err := manager.AddClusterQueue(ctx, cq); err != nil { 1145 t.Fatalf("Failed adding clusterQueue %s: %v", cq.Name, err) 1146 } 1147 } 1148 for _, q := range queues { 1149 if err := manager.AddLocalQueue(ctx, q); err != nil { 1150 t.Fatalf("Failed adding queue %s: %v", q.Name, err) 1151 } 1152 } 1153 for _, w := range workloads { 1154 manager.AddOrUpdateWorkload(w) 1155 } 1156 1157 cases := map[string]struct { 1158 cqName string 1159 wantPendingWorkloadsInfo []*workload.Info 1160 }{ 1161 "Invalid ClusterQueue name": { 1162 cqName: "invalid", 1163 wantPendingWorkloadsInfo: nil, 1164 }, 1165 "ClusterQueue with 2 pending workloads": { 1166 cqName: "cq", 1167 wantPendingWorkloadsInfo: []*workload.Info{ 1168 { 1169 Obj: &kueue.Workload{ 1170 ObjectMeta: metav1.ObjectMeta{ 1171 Name: "a", 1172 Namespace: "", 1173 }, 1174 Spec: kueue.WorkloadSpec{ 1175 QueueName: "foo", 1176 }, 1177 }, 1178 }, 1179 { 1180 Obj: &kueue.Workload{ 1181 ObjectMeta: metav1.ObjectMeta{ 1182 Name: "b", 1183 Namespace: "", 1184 }, 1185 Spec: kueue.WorkloadSpec{ 1186 QueueName: "foo", 1187 }, 1188 }, 1189 }, 1190 }, 1191 }, 1192 } 1193 for name, tc := range cases { 1194 t.Run(name, func(t *testing.T) { 1195 pendingWorkloadsInfo := manager.PendingWorkloadsInfo(tc.cqName) 1196 if diff := cmp.Diff(tc.wantPendingWorkloadsInfo, pendingWorkloadsInfo, 1197 ignoreTypeMeta, 1198 cmpopts.IgnoreFields(metav1.ObjectMeta{}, "CreationTimestamp"), 1199 cmpopts.IgnoreFields(kueue.WorkloadSpec{}, "PodSets"), 1200 cmpopts.IgnoreFields(workload.Info{}, "TotalRequests"), 1201 ); diff != "" { 1202 t.Errorf("GetPendingWorkloadsInfo returned wrong heads (-want,+got):\n%s", diff) 1203 } 1204 }) 1205 } 1206 }