sigs.k8s.io/kueue@v0.6.2/pkg/webhooks/workload_webhook_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 webhooks 18 19 import ( 20 "context" 21 "testing" 22 "time" 23 24 "github.com/google/go-cmp/cmp" 25 "github.com/google/go-cmp/cmp/cmpopts" 26 corev1 "k8s.io/api/core/v1" 27 "k8s.io/apimachinery/pkg/api/resource" 28 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 29 "k8s.io/apimachinery/pkg/util/validation/field" 30 "k8s.io/utils/ptr" 31 32 kueue "sigs.k8s.io/kueue/apis/kueue/v1beta1" 33 "sigs.k8s.io/kueue/pkg/constants" 34 testingutil "sigs.k8s.io/kueue/pkg/util/testing" 35 ) 36 37 const ( 38 testWorkloadName = "test-workload" 39 testWorkloadNamespace = "test-ns" 40 ) 41 42 func TestWorkloadWebhookDefault(t *testing.T) { 43 cases := map[string]struct { 44 wl kueue.Workload 45 wantWl kueue.Workload 46 }{ 47 "add default podSet name": { 48 wl: kueue.Workload{ 49 Spec: kueue.WorkloadSpec{ 50 PodSets: []kueue.PodSet{ 51 {}, 52 }, 53 }, 54 }, 55 wantWl: kueue.Workload{ 56 Spec: kueue.WorkloadSpec{ 57 PodSets: []kueue.PodSet{ 58 {Name: "main"}, 59 }, 60 }, 61 }, 62 }, 63 "don't set podSetName if multiple": { 64 wl: kueue.Workload{ 65 Spec: kueue.WorkloadSpec{ 66 PodSets: []kueue.PodSet{ 67 {}, 68 {}, 69 }, 70 }, 71 }, 72 wantWl: kueue.Workload{ 73 Spec: kueue.WorkloadSpec{ 74 PodSets: []kueue.PodSet{ 75 {}, 76 {}, 77 }, 78 }, 79 }, 80 }, 81 } 82 for name, tc := range cases { 83 t.Run(name, func(t *testing.T) { 84 wh := &WorkloadWebhook{} 85 wlCopy := tc.wl.DeepCopy() 86 if err := wh.Default(context.Background(), wlCopy); err != nil { 87 t.Fatalf("Could not apply defaults: %v", err) 88 } 89 if diff := cmp.Diff(tc.wantWl, *wlCopy, 90 cmpopts.IgnoreFields(metav1.Condition{}, "LastTransitionTime")); diff != "" { 91 t.Errorf("Obtained wrong defaults (-want,+got):\n%s", diff) 92 } 93 }) 94 } 95 } 96 97 func TestValidateWorkload(t *testing.T) { 98 specPath := field.NewPath("spec") 99 podSetsPath := specPath.Child("podSets") 100 statusPath := field.NewPath("status") 101 firstAdmissionChecksPath := statusPath.Child("admissionChecks").Index(0) 102 podSetUpdatePath := firstAdmissionChecksPath.Child("podSetUpdates") 103 firstPodSetSpecPath := podSetsPath.Index(0).Child("template", "spec") 104 testCases := map[string]struct { 105 workload *kueue.Workload 106 wantErr field.ErrorList 107 }{ 108 "valid": { 109 workload: testingutil.MakeWorkload(testWorkloadName, testWorkloadNamespace).PodSets( 110 kueue.PodSet{ 111 Name: "driver", 112 Count: 1, 113 }, 114 kueue.PodSet{ 115 Name: "workers", 116 Count: 100, 117 }, 118 ).Obj(), 119 }, 120 "should have a valid podSet name": { 121 workload: testingutil.MakeWorkload(testWorkloadName, testWorkloadNamespace).PodSets( 122 kueue.PodSet{ 123 Name: "@driver", 124 Count: 1, 125 }, 126 ).Obj(), 127 wantErr: field.ErrorList{field.Invalid(podSetsPath.Index(0).Child("name"), nil, "")}, 128 }, 129 "should have valid priorityClassName": { 130 workload: testingutil.MakeWorkload(testWorkloadName, testWorkloadNamespace). 131 PriorityClass("invalid_class"). 132 Priority(0). 133 Obj(), 134 wantErr: field.ErrorList{ 135 field.Invalid(specPath.Child("priorityClassName"), nil, ""), 136 }, 137 }, 138 "should pass validation when priorityClassName is empty": { 139 workload: testingutil.MakeWorkload(testWorkloadName, testWorkloadNamespace).Obj(), 140 wantErr: nil, 141 }, 142 "should have priority once priorityClassName is set": { 143 workload: testingutil.MakeWorkload(testWorkloadName, testWorkloadNamespace). 144 PriorityClass("priority"). 145 Obj(), 146 wantErr: field.ErrorList{ 147 field.Invalid(specPath.Child("priority"), nil, ""), 148 }, 149 }, 150 "should have a valid queueName": { 151 workload: testingutil.MakeWorkload(testWorkloadName, testWorkloadNamespace). 152 Queue("@invalid"). 153 Obj(), 154 wantErr: field.ErrorList{ 155 field.Invalid(specPath.Child("queueName"), nil, ""), 156 }, 157 }, 158 "should have a valid clusterQueue name": { 159 workload: testingutil.MakeWorkload(testWorkloadName, testWorkloadNamespace). 160 ReserveQuota(testingutil.MakeAdmission("@invalid").Obj()). 161 Obj(), 162 wantErr: field.ErrorList{ 163 field.Invalid(statusPath.Child("admission", "clusterQueue"), nil, ""), 164 }, 165 }, 166 "should have a valid podSet name in status assigment": { 167 workload: testingutil.MakeWorkload(testWorkloadName, testWorkloadNamespace). 168 ReserveQuota(testingutil.MakeAdmission("cluster-queue", "@invalid").Obj()). 169 Obj(), 170 wantErr: field.ErrorList{ 171 field.NotFound(statusPath.Child("admission", "podSetAssignments").Index(0).Child("name"), nil), 172 }, 173 }, 174 "should have same podSets in admission": { 175 workload: testingutil.MakeWorkload(testWorkloadName, testWorkloadNamespace). 176 PodSets( 177 kueue.PodSet{ 178 Name: "main2", 179 Count: 1, 180 }, 181 kueue.PodSet{ 182 Name: "main1", 183 Count: 1, 184 }, 185 ). 186 ReserveQuota(testingutil.MakeAdmission("cluster-queue", "main1", "main2", "main3").Obj()). 187 Obj(), 188 wantErr: field.ErrorList{ 189 field.Invalid(statusPath.Child("admission", "podSetAssignments"), nil, ""), 190 field.NotFound(statusPath.Child("admission", "podSetAssignments").Index(2).Child("name"), nil), 191 }, 192 }, 193 "assignment usage should be divisible by count": { 194 workload: testingutil.MakeWorkload(testWorkloadName, testWorkloadNamespace). 195 PodSets(*testingutil.MakePodSet("main", 3). 196 Request(corev1.ResourceCPU, "1"). 197 Obj()). 198 ReserveQuota(testingutil.MakeAdmission("cluster-queue"). 199 Assignment(corev1.ResourceCPU, "flv", "1"). 200 AssignmentPodCount(3). 201 Obj()). 202 Obj(), 203 wantErr: field.ErrorList{ 204 field.Invalid(statusPath.Child("admission", "podSetAssignments").Index(0).Child("resourceUsage").Key(string(corev1.ResourceCPU)), nil, ""), 205 }, 206 }, 207 "should not request num-pods resource": { 208 workload: testingutil.MakeWorkload(testWorkloadName, testWorkloadNamespace). 209 PodSets(kueue.PodSet{ 210 Name: "bad", 211 Count: 1, 212 Template: corev1.PodTemplateSpec{ 213 Spec: corev1.PodSpec{ 214 InitContainers: []corev1.Container{ 215 { 216 Resources: corev1.ResourceRequirements{ 217 Requests: corev1.ResourceList{ 218 corev1.ResourcePods: resource.MustParse("1"), 219 }, 220 }, 221 }, 222 }, 223 Containers: []corev1.Container{ 224 { 225 Resources: corev1.ResourceRequirements{ 226 Requests: corev1.ResourceList{ 227 corev1.ResourcePods: resource.MustParse("1"), 228 }, 229 }, 230 }, 231 }, 232 }, 233 }, 234 }). 235 Obj(), 236 wantErr: field.ErrorList{ 237 field.Invalid(firstPodSetSpecPath.Child("initContainers").Index(0).Child("resources", "requests").Key(string(corev1.ResourcePods)), nil, ""), 238 field.Invalid(firstPodSetSpecPath.Child("containers").Index(0).Child("resources", "requests").Key(string(corev1.ResourcePods)), nil, ""), 239 }, 240 }, 241 "empty podSetUpdates": { 242 workload: testingutil.MakeWorkload(testWorkloadName, testWorkloadNamespace).AdmissionChecks(kueue.AdmissionCheckState{}).Obj(), 243 wantErr: nil, 244 }, 245 "should podSetUpdates have the same number of podSets": { 246 workload: testingutil.MakeWorkload(testWorkloadName, testWorkloadNamespace).PodSets( 247 *testingutil.MakePodSet("first", 1).Obj(), 248 *testingutil.MakePodSet("second", 1).Obj(), 249 ).AdmissionChecks( 250 kueue.AdmissionCheckState{PodSetUpdates: []kueue.PodSetUpdate{{Name: "first"}}}, 251 ).Obj(), 252 wantErr: field.ErrorList{ 253 field.Invalid(podSetUpdatePath, nil, "must have the same number of podSetUpdates as the podSets"), 254 }, 255 }, 256 "mismatched names in podSetUpdates with names in podSets": { 257 workload: testingutil.MakeWorkload(testWorkloadName, testWorkloadNamespace).PodSets( 258 *testingutil.MakePodSet("first", 1).Obj(), 259 *testingutil.MakePodSet("second", 1).Obj(), 260 ).AdmissionChecks( 261 kueue.AdmissionCheckState{PodSetUpdates: []kueue.PodSetUpdate{{Name: "first"}, {Name: "third"}}}, 262 ).Obj(), 263 wantErr: field.ErrorList{ 264 field.NotSupported(firstAdmissionChecksPath.Child("podSetUpdates").Index(1).Child("name"), nil, []string{}), 265 }, 266 }, 267 "matched names in podSetUpdates with names in podSets": { 268 workload: testingutil.MakeWorkload(testWorkloadName, testWorkloadNamespace).PodSets( 269 *testingutil.MakePodSet("first", 1).Obj(), 270 *testingutil.MakePodSet("second", 1).Obj(), 271 ).AdmissionChecks( 272 kueue.AdmissionCheckState{ 273 PodSetUpdates: []kueue.PodSetUpdate{ 274 { 275 Name: "first", 276 Labels: map[string]string{"l1": "first"}, 277 Annotations: map[string]string{"foo": "bar"}, 278 Tolerations: []corev1.Toleration{ 279 { 280 Key: "t1", 281 Operator: corev1.TolerationOpEqual, 282 Value: "t1v", 283 Effect: corev1.TaintEffectNoExecute, 284 TolerationSeconds: ptr.To[int64](5), 285 }, 286 }, 287 NodeSelector: map[string]string{"type": "first"}, 288 }, 289 { 290 Name: "second", 291 Labels: map[string]string{"l2": "second"}, 292 Annotations: map[string]string{"foo": "baz"}, 293 Tolerations: []corev1.Toleration{ 294 { 295 Key: "t2", 296 Operator: corev1.TolerationOpEqual, 297 Value: "t2v", 298 Effect: corev1.TaintEffectNoExecute, 299 TolerationSeconds: ptr.To[int64](10), 300 }, 301 }, 302 NodeSelector: map[string]string{"type": "second"}, 303 }, 304 }, 305 }, 306 ).Obj(), 307 }, 308 "invalid label name of podSetUpdate": { 309 workload: testingutil.MakeWorkload(testWorkloadName, testWorkloadNamespace). 310 AdmissionChecks( 311 kueue.AdmissionCheckState{PodSetUpdates: []kueue.PodSetUpdate{{Name: "main", Labels: map[string]string{"@abc": "foo"}}}}, 312 ). 313 Obj(), 314 wantErr: field.ErrorList{ 315 field.Invalid(podSetUpdatePath.Index(0).Child("labels"), "@abc", ""), 316 }, 317 }, 318 "invalid node selector name of podSetUpdate": { 319 workload: testingutil.MakeWorkload(testWorkloadName, testWorkloadNamespace). 320 AdmissionChecks( 321 kueue.AdmissionCheckState{PodSetUpdates: []kueue.PodSetUpdate{{Name: "main", NodeSelector: map[string]string{"@abc": "foo"}}}}, 322 ). 323 Obj(), 324 wantErr: field.ErrorList{ 325 field.Invalid(podSetUpdatePath.Index(0).Child("nodeSelector"), "@abc", ""), 326 }, 327 }, 328 "invalid label value of podSetUpdate": { 329 workload: testingutil.MakeWorkload(testWorkloadName, testWorkloadNamespace). 330 AdmissionChecks( 331 kueue.AdmissionCheckState{PodSetUpdates: []kueue.PodSetUpdate{{Name: "main", Labels: map[string]string{"foo": "@abc"}}}}, 332 ). 333 Obj(), 334 wantErr: field.ErrorList{ 335 field.Invalid(podSetUpdatePath.Index(0).Child("labels"), "@abc", ""), 336 }, 337 }, 338 "invalid reclaimablePods": { 339 workload: testingutil.MakeWorkload(testWorkloadName, testWorkloadNamespace). 340 PodSets( 341 *testingutil.MakePodSet("ps1", 3).Obj(), 342 ). 343 ReclaimablePods( 344 kueue.ReclaimablePod{Name: "ps1", Count: 4}, 345 kueue.ReclaimablePod{Name: "ps2", Count: 1}, 346 ). 347 Obj(), 348 wantErr: field.ErrorList{ 349 field.Invalid(statusPath.Child("reclaimablePods").Key("ps1").Child("count"), nil, ""), 350 field.NotSupported(statusPath.Child("reclaimablePods").Key("ps2").Child("name"), nil, []string{}), 351 }, 352 }, 353 "invalid podSet minCount (negative)": { 354 workload: testingutil.MakeWorkload(testWorkloadName, testWorkloadNamespace). 355 PodSets( 356 *testingutil.MakePodSet("ps1", 3).SetMinimumCount(-1).Obj(), 357 ). 358 Obj(), 359 wantErr: field.ErrorList{ 360 field.Forbidden(podSetsPath.Index(0).Child("minCount"), ""), 361 }, 362 }, 363 "invalid podSet minCount (too big)": { 364 workload: testingutil.MakeWorkload(testWorkloadName, testWorkloadNamespace). 365 PodSets( 366 *testingutil.MakePodSet("ps1", 3).SetMinimumCount(4).Obj(), 367 ). 368 Obj(), 369 wantErr: field.ErrorList{ 370 field.Forbidden(podSetsPath.Index(0).Child("minCount"), ""), 371 }, 372 }, 373 "too many variable count podSets": { 374 workload: testingutil.MakeWorkload(testWorkloadName, testWorkloadNamespace). 375 PodSets( 376 *testingutil.MakePodSet("ps1", 3).SetMinimumCount(2).Obj(), 377 *testingutil.MakePodSet("ps2", 3).SetMinimumCount(1).Obj(), 378 ). 379 Obj(), 380 wantErr: field.ErrorList{ 381 field.Invalid(podSetsPath, nil, ""), 382 }, 383 }, 384 } 385 for name, tc := range testCases { 386 t.Run(name, func(t *testing.T) { 387 gotErr := ValidateWorkload(tc.workload) 388 if diff := cmp.Diff(tc.wantErr, gotErr, cmpopts.IgnoreFields(field.Error{}, "Detail", "BadValue")); diff != "" { 389 t.Errorf("ValidateWorkload() mismatch (-want +got):\n%s", diff) 390 } 391 }) 392 } 393 } 394 395 func TestValidateWorkloadUpdate(t *testing.T) { 396 testCases := map[string]struct { 397 before, after *kueue.Workload 398 wantErr field.ErrorList 399 }{ 400 "podSets should not be updated when has quota reservation: count": { 401 before: testingutil.MakeWorkload(testWorkloadName, testWorkloadNamespace).ReserveQuota(testingutil.MakeAdmission("cq").Obj()).Obj(), 402 after: testingutil.MakeWorkload(testWorkloadName, testWorkloadNamespace).PodSets( 403 *testingutil.MakePodSet("main", 2).Obj(), 404 ).Obj(), 405 wantErr: field.ErrorList{ 406 field.Invalid(field.NewPath("spec").Child("podSets"), nil, ""), 407 }, 408 }, 409 "podSets should not be updated: podSpec": { 410 before: testingutil.MakeWorkload(testWorkloadName, testWorkloadNamespace).ReserveQuota(testingutil.MakeAdmission("cq").Obj()).Obj(), 411 after: testingutil.MakeWorkload(testWorkloadName, testWorkloadNamespace).PodSets( 412 kueue.PodSet{ 413 Name: "main", 414 Count: 1, 415 Template: corev1.PodTemplateSpec{ 416 Spec: corev1.PodSpec{ 417 Containers: []corev1.Container{ 418 { 419 Name: "c-after", 420 Resources: corev1.ResourceRequirements{ 421 Requests: make(corev1.ResourceList), 422 }, 423 }, 424 }, 425 }, 426 }, 427 }, 428 ).Obj(), 429 wantErr: field.ErrorList{ 430 field.Invalid(field.NewPath("spec").Child("podSets"), nil, ""), 431 }, 432 }, 433 "queueName can be updated when not admitted": { 434 before: testingutil.MakeWorkload(testWorkloadName, testWorkloadNamespace).Queue("q1").Obj(), 435 after: testingutil.MakeWorkload(testWorkloadName, testWorkloadNamespace).Queue("q2").Obj(), 436 wantErr: nil, 437 }, 438 "queueName can be updated when admitting": { 439 before: testingutil.MakeWorkload(testWorkloadName, testWorkloadNamespace).Obj(), 440 after: testingutil.MakeWorkload(testWorkloadName, testWorkloadNamespace).Queue("q"). 441 ReserveQuota(testingutil.MakeAdmission("cq").Obj()).Obj(), 442 }, 443 "queueName should not be updated once admitted": { 444 before: testingutil.MakeWorkload(testWorkloadName, testWorkloadNamespace).Queue("q1"). 445 ReserveQuota(testingutil.MakeAdmission("cq").Obj()).Obj(), 446 after: testingutil.MakeWorkload(testWorkloadName, testWorkloadNamespace).Queue("q2"). 447 ReserveQuota(testingutil.MakeAdmission("cq").Obj()).Obj(), 448 wantErr: field.ErrorList{ 449 field.Invalid(field.NewPath("spec").Child("queueName"), nil, ""), 450 }, 451 }, 452 "queueName can be updated when admission is reset": { 453 before: testingutil.MakeWorkload(testWorkloadName, testWorkloadNamespace).Queue("q1"). 454 ReserveQuota(testingutil.MakeAdmission("cq").Obj()).Obj(), 455 after: testingutil.MakeWorkload(testWorkloadName, testWorkloadNamespace).Queue("q2").Obj(), 456 }, 457 "admission can be set": { 458 before: testingutil.MakeWorkload(testWorkloadName, testWorkloadNamespace).Obj(), 459 after: testingutil.MakeWorkload(testWorkloadName, testWorkloadNamespace).ReserveQuota( 460 testingutil.MakeAdmission("cluster-queue").Assignment("on-demand", "5", "1").Obj(), 461 ).Obj(), 462 wantErr: nil, 463 }, 464 "admission can be unset": { 465 before: testingutil.MakeWorkload(testWorkloadName, testWorkloadNamespace).ReserveQuota( 466 testingutil.MakeAdmission("cluster-queue").Assignment("on-demand", "5", "1").Obj(), 467 ).Obj(), 468 after: testingutil.MakeWorkload(testWorkloadName, testWorkloadNamespace).Obj(), 469 wantErr: nil, 470 }, 471 "admission should not be updated once set": { 472 before: testingutil.MakeWorkload(testWorkloadName, testWorkloadNamespace).ReserveQuota( 473 testingutil.MakeAdmission("cluster-queue").Obj(), 474 ).Obj(), 475 after: testingutil.MakeWorkload(testWorkloadName, testWorkloadNamespace).ReserveQuota( 476 testingutil.MakeAdmission("cluster-queue").Assignment("on-demand", "5", "1").Obj(), 477 ).Obj(), 478 wantErr: field.ErrorList{ 479 field.Invalid(field.NewPath("status", "admission"), nil, ""), 480 }, 481 }, 482 483 "reclaimable pod count can change up": { 484 before: testingutil.MakeWorkload(testWorkloadName, testWorkloadNamespace). 485 PodSets( 486 *testingutil.MakePodSet("ps1", 3).Obj(), 487 *testingutil.MakePodSet("ps2", 3).Obj(), 488 ). 489 ReserveQuota( 490 testingutil.MakeAdmission("cluster-queue"). 491 PodSets(kueue.PodSetAssignment{Name: "ps1"}, kueue.PodSetAssignment{Name: "ps2"}). 492 Obj(), 493 ). 494 ReclaimablePods( 495 kueue.ReclaimablePod{Name: "ps1", Count: 1}, 496 ). 497 Obj(), 498 after: testingutil.MakeWorkload(testWorkloadName, testWorkloadNamespace). 499 PodSets( 500 *testingutil.MakePodSet("ps1", 3).Obj(), 501 *testingutil.MakePodSet("ps2", 3).Obj(), 502 ). 503 ReserveQuota( 504 testingutil.MakeAdmission("cluster-queue"). 505 PodSets(kueue.PodSetAssignment{Name: "ps1"}, kueue.PodSetAssignment{Name: "ps2"}). 506 Obj(), 507 ). 508 ReclaimablePods( 509 kueue.ReclaimablePod{Name: "ps1", Count: 2}, 510 kueue.ReclaimablePod{Name: "ps2", Count: 1}, 511 ). 512 Obj(), 513 wantErr: nil, 514 }, 515 "reclaimable pod count cannot change down": { 516 before: testingutil.MakeWorkload(testWorkloadName, testWorkloadNamespace). 517 PodSets( 518 *testingutil.MakePodSet("ps1", 3).Obj(), 519 *testingutil.MakePodSet("ps2", 3).Obj(), 520 ). 521 ReserveQuota( 522 testingutil.MakeAdmission("cluster-queue"). 523 PodSets(kueue.PodSetAssignment{Name: "ps1"}, kueue.PodSetAssignment{Name: "ps2"}). 524 Obj(), 525 ). 526 ReclaimablePods( 527 kueue.ReclaimablePod{Name: "ps1", Count: 2}, 528 kueue.ReclaimablePod{Name: "ps2", Count: 1}, 529 ). 530 Obj(), 531 after: testingutil.MakeWorkload(testWorkloadName, testWorkloadNamespace). 532 PodSets( 533 *testingutil.MakePodSet("ps1", 3).Obj(), 534 *testingutil.MakePodSet("ps2", 3).Obj(), 535 ). 536 ReserveQuota( 537 testingutil.MakeAdmission("cluster-queue"). 538 PodSets(kueue.PodSetAssignment{Name: "ps1"}, kueue.PodSetAssignment{Name: "ps2"}). 539 Obj(), 540 ). 541 ReclaimablePods( 542 kueue.ReclaimablePod{Name: "ps1", Count: 1}, 543 ). 544 Obj(), 545 wantErr: field.ErrorList{ 546 field.Invalid(field.NewPath("status", "reclaimablePods").Key("ps1").Child("count"), nil, ""), 547 field.Required(field.NewPath("status", "reclaimablePods").Key("ps2"), ""), 548 }, 549 }, 550 "reclaimable pod count can go to 0 if the job is suspended": { 551 before: testingutil.MakeWorkload(testWorkloadName, testWorkloadNamespace). 552 PodSets( 553 *testingutil.MakePodSet("ps1", 3).Obj(), 554 *testingutil.MakePodSet("ps2", 3).Obj(), 555 ). 556 ReserveQuota( 557 testingutil.MakeAdmission("cluster-queue"). 558 PodSets(kueue.PodSetAssignment{Name: "ps1"}, kueue.PodSetAssignment{Name: "ps2"}). 559 Obj(), 560 ). 561 ReclaimablePods( 562 kueue.ReclaimablePod{Name: "ps1", Count: 2}, 563 kueue.ReclaimablePod{Name: "ps2", Count: 1}, 564 ). 565 Obj(), 566 after: testingutil.MakeWorkload(testWorkloadName, testWorkloadNamespace). 567 PodSets( 568 *testingutil.MakePodSet("ps1", 3).Obj(), 569 *testingutil.MakePodSet("ps2", 3).Obj(), 570 ). 571 AdmissionChecks(kueue.AdmissionCheckState{ 572 PodSetUpdates: []kueue.PodSetUpdate{{Name: "ps1"}, {Name: "ps2"}}, 573 State: kueue.CheckStateReady, 574 }). 575 ReclaimablePods( 576 kueue.ReclaimablePod{Name: "ps1", Count: 0}, 577 kueue.ReclaimablePod{Name: "ps2", Count: 1}, 578 ). 579 Obj(), 580 wantErr: nil, 581 }, 582 "priorityClassSource should not be updated": { 583 before: testingutil.MakeWorkload(testWorkloadName, testWorkloadNamespace).Queue("q"). 584 PriorityClass("test-class").PriorityClassSource(constants.PodPriorityClassSource). 585 Priority(10).ReserveQuota(testingutil.MakeAdmission("cq").Obj()).Obj(), 586 after: testingutil.MakeWorkload(testWorkloadName, testWorkloadNamespace).Queue("q"). 587 PriorityClass("test-class").PriorityClassSource(constants.WorkloadPriorityClassSource). 588 Priority(10).Obj(), 589 wantErr: field.ErrorList{ 590 field.Invalid(field.NewPath("spec").Child("priorityClassSource"), nil, ""), 591 }, 592 }, 593 "priorityClassName should not be updated": { 594 before: testingutil.MakeWorkload(testWorkloadName, testWorkloadNamespace).Queue("q"). 595 PriorityClass("test-class-1").PriorityClassSource(constants.PodPriorityClassSource). 596 Priority(10).ReserveQuota(testingutil.MakeAdmission("cq").Obj()).Obj(), 597 after: testingutil.MakeWorkload(testWorkloadName, testWorkloadNamespace).Queue("q"). 598 PriorityClass("test-class-2").PriorityClassSource(constants.PodPriorityClassSource). 599 Priority(10).Obj(), 600 wantErr: field.ErrorList{ 601 field.Invalid(field.NewPath("spec").Child("priorityClassName"), nil, ""), 602 }, 603 }, 604 "podSetUpdates should be immutable when state is ready": { 605 before: testingutil.MakeWorkload(testWorkloadName, testWorkloadNamespace).PodSets( 606 *testingutil.MakePodSet("first", 1).Obj(), 607 *testingutil.MakePodSet("second", 1).Obj(), 608 ).AdmissionChecks(kueue.AdmissionCheckState{ 609 PodSetUpdates: []kueue.PodSetUpdate{{Name: "first", Labels: map[string]string{"foo": "bar"}}, {Name: "second"}}, 610 State: kueue.CheckStateReady, 611 }).Obj(), 612 after: testingutil.MakeWorkload(testWorkloadName, testWorkloadNamespace).PodSets( 613 *testingutil.MakePodSet("first", 1).Obj(), 614 *testingutil.MakePodSet("second", 1).Obj(), 615 ).AdmissionChecks(kueue.AdmissionCheckState{ 616 PodSetUpdates: []kueue.PodSetUpdate{{Name: "first", Labels: map[string]string{"foo": "baz"}}, {Name: "second"}}, 617 State: kueue.CheckStateReady, 618 }).Obj(), 619 wantErr: field.ErrorList{ 620 field.Invalid(field.NewPath("status").Child("admissionChecks").Index(0).Child("podSetUpdates"), nil, ""), 621 }, 622 }, 623 "should change other fields of admissionchecks when podSetUpdates is immutable": { 624 before: testingutil.MakeWorkload(testWorkloadName, testWorkloadNamespace).PodSets( 625 *testingutil.MakePodSet("first", 1).Obj(), 626 *testingutil.MakePodSet("second", 1).Obj(), 627 ).AdmissionChecks(kueue.AdmissionCheckState{ 628 Name: "ac1", 629 Message: "old", 630 PodSetUpdates: []kueue.PodSetUpdate{{Name: "first", Labels: map[string]string{"foo": "bar"}}, {Name: "second"}}, 631 State: kueue.CheckStateReady, 632 }).Obj(), 633 after: testingutil.MakeWorkload(testWorkloadName, testWorkloadNamespace).PodSets( 634 *testingutil.MakePodSet("first", 1).Obj(), 635 *testingutil.MakePodSet("second", 1).Obj(), 636 ).AdmissionChecks(kueue.AdmissionCheckState{ 637 Name: "ac1", 638 Message: "new", 639 LastTransitionTime: metav1.NewTime(time.Now()), 640 PodSetUpdates: []kueue.PodSetUpdate{{Name: "first", Labels: map[string]string{"foo": "bar"}}, {Name: "second"}}, 641 State: kueue.CheckStateReady, 642 }).Obj(), 643 }, 644 "updating priorityClassName before setting reserve quota for workload": { 645 before: testingutil.MakeWorkload(testWorkloadName, testWorkloadNamespace).Queue("q"). 646 PriorityClass("test-class-1").PriorityClassSource(constants.PodPriorityClassSource). 647 Priority(10).Obj(), 648 after: testingutil.MakeWorkload(testWorkloadName, testWorkloadNamespace).Queue("q"). 649 PriorityClass("test-class-2").PriorityClassSource(constants.PodPriorityClassSource). 650 Priority(10).Obj(), 651 wantErr: nil, 652 }, 653 "updating priorityClassSource before setting reserve quota for workload": { 654 before: testingutil.MakeWorkload(testWorkloadName, testWorkloadNamespace).Queue("q"). 655 PriorityClass("test-class").PriorityClassSource(constants.PodPriorityClassSource). 656 Priority(10).Obj(), 657 after: testingutil.MakeWorkload(testWorkloadName, testWorkloadNamespace).Queue("q"). 658 PriorityClass("test-class").PriorityClassSource(constants.WorkloadPriorityClassSource). 659 Priority(10).Obj(), 660 wantErr: nil, 661 }, 662 "updating podSets before setting reserve quota for workload": { 663 before: testingutil.MakeWorkload(testWorkloadName, testWorkloadNamespace).Obj(), 664 after: testingutil.MakeWorkload(testWorkloadName, testWorkloadNamespace).PodSets( 665 kueue.PodSet{ 666 Name: "main", 667 Count: 1, 668 Template: corev1.PodTemplateSpec{ 669 Spec: corev1.PodSpec{ 670 Containers: []corev1.Container{ 671 { 672 Name: "c-after", 673 Resources: corev1.ResourceRequirements{ 674 Requests: make(corev1.ResourceList), 675 }, 676 }, 677 }, 678 }, 679 }, 680 }, 681 ).Obj(), 682 wantErr: nil, 683 }, 684 } 685 for name, tc := range testCases { 686 t.Run(name, func(t *testing.T) { 687 errList := ValidateWorkloadUpdate(tc.after, tc.before) 688 if diff := cmp.Diff(tc.wantErr, errList, cmpopts.IgnoreFields(field.Error{}, "Detail", "BadValue")); diff != "" { 689 t.Errorf("ValidateWorkloadUpdate() mismatch (-want +got):\n%s", diff) 690 } 691 }) 692 } 693 }