sigs.k8s.io/kueue@v0.6.2/pkg/controller/jobs/pod/pod_webhook_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 pod 18 19 import ( 20 "testing" 21 22 "github.com/google/go-cmp/cmp" 23 "github.com/google/go-cmp/cmp/cmpopts" 24 rayjobapi "github.com/ray-project/kuberay/ray-operator/apis/ray/v1alpha1" 25 batchv1 "k8s.io/api/batch/v1" 26 corev1 "k8s.io/api/core/v1" 27 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 28 "k8s.io/apimachinery/pkg/runtime/schema" 29 "k8s.io/apimachinery/pkg/util/validation/field" 30 "sigs.k8s.io/controller-runtime/pkg/client" 31 "sigs.k8s.io/controller-runtime/pkg/webhook/admission" 32 33 configapi "sigs.k8s.io/kueue/apis/config/v1beta1" 34 _ "sigs.k8s.io/kueue/pkg/controller/jobs/kubeflow/jobs" 35 _ "sigs.k8s.io/kueue/pkg/controller/jobs/mpijob" 36 utiltesting "sigs.k8s.io/kueue/pkg/util/testing" 37 testingpod "sigs.k8s.io/kueue/pkg/util/testingjobs/pod" 38 ) 39 40 func TestDefault(t *testing.T) { 41 defaultNamespace := &corev1.Namespace{ 42 ObjectMeta: metav1.ObjectMeta{ 43 Name: "test-ns", 44 Labels: map[string]string{ 45 "kubernetes.io/metadata.name": "test-ns", 46 }, 47 }, 48 } 49 50 defaultNamespaceSelector := &metav1.LabelSelector{ 51 MatchExpressions: []metav1.LabelSelectorRequirement{ 52 { 53 Key: "kubernetes.io/metadata.name", 54 Operator: metav1.LabelSelectorOpNotIn, 55 Values: []string{"kube-system"}, 56 }, 57 }, 58 } 59 60 testCases := map[string]struct { 61 initObjects []client.Object 62 pod *corev1.Pod 63 manageJobsWithoutQueueName bool 64 namespaceSelector *metav1.LabelSelector 65 podSelector *metav1.LabelSelector 66 want *corev1.Pod 67 }{ 68 "pod with queue nil ns selector": { 69 initObjects: []client.Object{defaultNamespace}, 70 pod: testingpod.MakePod("test-pod", defaultNamespace.Name). 71 Queue("test-queue"). 72 Obj(), 73 want: testingpod.MakePod("test-pod", defaultNamespace.Name). 74 Queue("test-queue"). 75 Obj(), 76 }, 77 "pod with queue matching ns selector": { 78 initObjects: []client.Object{defaultNamespace}, 79 pod: testingpod.MakePod("test-pod", defaultNamespace.Name). 80 Queue("test-queue"). 81 Obj(), 82 namespaceSelector: defaultNamespaceSelector, 83 podSelector: &metav1.LabelSelector{}, 84 want: testingpod.MakePod("test-pod", defaultNamespace.Name). 85 Queue("test-queue"). 86 Label("kueue.x-k8s.io/managed", "true"). 87 KueueSchedulingGate(). 88 KueueFinalizer(). 89 Obj(), 90 }, 91 "pod without queue matching ns selector manage jobs without queue name": { 92 initObjects: []client.Object{defaultNamespace}, 93 pod: testingpod.MakePod("test-pod", defaultNamespace.Name). 94 Obj(), 95 manageJobsWithoutQueueName: true, 96 namespaceSelector: defaultNamespaceSelector, 97 podSelector: &metav1.LabelSelector{}, 98 want: testingpod.MakePod("test-pod", defaultNamespace.Name). 99 Label("kueue.x-k8s.io/managed", "true"). 100 KueueSchedulingGate(). 101 KueueFinalizer(). 102 Obj(), 103 }, 104 "pod with owner managed by kueue (Job)": { 105 initObjects: []client.Object{defaultNamespace}, 106 podSelector: &metav1.LabelSelector{}, 107 namespaceSelector: defaultNamespaceSelector, 108 pod: testingpod.MakePod("test-pod", defaultNamespace.Name). 109 Queue("test-queue"). 110 OwnerReference("parent-job", batchv1.SchemeGroupVersion.WithKind("Job")). 111 Obj(), 112 want: testingpod.MakePod("test-pod", defaultNamespace.Name). 113 Queue("test-queue"). 114 OwnerReference("parent-job", batchv1.SchemeGroupVersion.WithKind("Job")). 115 Obj(), 116 }, 117 "pod with owner managed by kueue (RayCluster)": { 118 initObjects: []client.Object{defaultNamespace}, 119 podSelector: &metav1.LabelSelector{}, 120 namespaceSelector: defaultNamespaceSelector, 121 pod: testingpod.MakePod("test-pod", defaultNamespace.Name). 122 Queue("test-queue"). 123 OwnerReference("parent-ray-cluster", rayjobapi.GroupVersion.WithKind("RayCluster")). 124 Obj(), 125 want: testingpod.MakePod("test-pod", defaultNamespace.Name). 126 Queue("test-queue"). 127 OwnerReference("parent-ray-cluster", rayjobapi.GroupVersion.WithKind("RayCluster")). 128 Obj(), 129 }, 130 "pod with owner managed by kueue (MPIJob)": { 131 initObjects: []client.Object{defaultNamespace}, 132 podSelector: &metav1.LabelSelector{}, 133 namespaceSelector: defaultNamespaceSelector, 134 pod: testingpod.MakePod("test-pod", defaultNamespace.Name). 135 Queue("test-queue"). 136 OwnerReference( 137 "parent-mpi-job", 138 schema.GroupVersionKind{Group: "kubeflow.org", Version: "v2beta1", Kind: "MPIJob"}, 139 ). 140 Obj(), 141 want: testingpod.MakePod("test-pod", defaultNamespace.Name). 142 Queue("test-queue"). 143 OwnerReference( 144 "parent-mpi-job", 145 schema.GroupVersionKind{Group: "kubeflow.org", Version: "v2beta1", Kind: "MPIJob"}, 146 ). 147 Obj(), 148 }, 149 "pod with owner managed by kueue (PyTorchJob)": { 150 initObjects: []client.Object{defaultNamespace}, 151 podSelector: &metav1.LabelSelector{}, 152 namespaceSelector: defaultNamespaceSelector, 153 pod: testingpod.MakePod("test-pod", defaultNamespace.Name). 154 Queue("test-queue"). 155 OwnerReference( 156 "parent-pytorch-job", 157 schema.GroupVersionKind{Group: "kubeflow.org", Version: "v1", Kind: "PyTorchJob"}, 158 ). 159 Obj(), 160 want: testingpod.MakePod("test-pod", defaultNamespace.Name). 161 Queue("test-queue"). 162 OwnerReference( 163 "parent-pytorch-job", 164 schema.GroupVersionKind{Group: "kubeflow.org", Version: "v1", Kind: "PyTorchJob"}, 165 ). 166 Obj(), 167 }, 168 "pod with owner managed by kueue (TFJob)": { 169 initObjects: []client.Object{defaultNamespace}, 170 podSelector: &metav1.LabelSelector{}, 171 namespaceSelector: defaultNamespaceSelector, 172 pod: testingpod.MakePod("test-pod", defaultNamespace.Name). 173 Queue("test-queue"). 174 OwnerReference( 175 "parent-tf-job", 176 schema.GroupVersionKind{Group: "kubeflow.org", Version: "v1", Kind: "TFJob"}, 177 ). 178 Obj(), 179 want: testingpod.MakePod("test-pod", defaultNamespace.Name). 180 Queue("test-queue"). 181 OwnerReference( 182 "parent-tf-job", 183 schema.GroupVersionKind{Group: "kubeflow.org", Version: "v1", Kind: "TFJob"}, 184 ). 185 Obj(), 186 }, 187 "pod with owner managed by kueue (XGBoostJob)": { 188 initObjects: []client.Object{defaultNamespace}, 189 podSelector: &metav1.LabelSelector{}, 190 namespaceSelector: defaultNamespaceSelector, 191 pod: testingpod.MakePod("test-pod", defaultNamespace.Name). 192 Queue("test-queue"). 193 OwnerReference( 194 "parent-xgboost-job", 195 schema.GroupVersionKind{Group: "kubeflow.org", Version: "v1", Kind: "XGBoostJob"}, 196 ). 197 Obj(), 198 want: testingpod.MakePod("test-pod", defaultNamespace.Name). 199 Queue("test-queue"). 200 OwnerReference( 201 "parent-xgboost-job", 202 schema.GroupVersionKind{Group: "kubeflow.org", Version: "v1", Kind: "XGBoostJob"}, 203 ). 204 Obj(), 205 }, 206 "pod with owner managed by kueue (PaddleJob)": { 207 initObjects: []client.Object{defaultNamespace}, 208 podSelector: &metav1.LabelSelector{}, 209 namespaceSelector: defaultNamespaceSelector, 210 pod: testingpod.MakePod("test-pod", defaultNamespace.Name). 211 Queue("test-queue"). 212 OwnerReference( 213 "parent-paddle-job", 214 schema.GroupVersionKind{Group: "kubeflow.org", Version: "v1", Kind: "PaddleJob"}, 215 ). 216 Obj(), 217 want: testingpod.MakePod("test-pod", defaultNamespace.Name). 218 Queue("test-queue"). 219 OwnerReference( 220 "parent-paddle-job", 221 schema.GroupVersionKind{Group: "kubeflow.org", Version: "v1", Kind: "PaddleJob"}, 222 ). 223 Obj(), 224 }, 225 "pod with a group name label, but without group total count label": { 226 initObjects: []client.Object{defaultNamespace}, 227 podSelector: &metav1.LabelSelector{}, 228 namespaceSelector: defaultNamespaceSelector, 229 pod: testingpod.MakePod("test-pod", defaultNamespace.Name). 230 Queue("test-queue"). 231 Group("test-group"). 232 Obj(), 233 want: testingpod.MakePod("test-pod", defaultNamespace.Name). 234 Queue("test-queue"). 235 Group("test-group"). 236 RoleHash("a9f06f3a"). 237 Label("kueue.x-k8s.io/managed", "true"). 238 KueueSchedulingGate(). 239 KueueFinalizer(). 240 Obj(), 241 }, 242 "pod with a group name label": { 243 initObjects: []client.Object{defaultNamespace}, 244 podSelector: &metav1.LabelSelector{}, 245 namespaceSelector: defaultNamespaceSelector, 246 pod: testingpod.MakePod("test-pod", defaultNamespace.Name). 247 Queue("test-queue"). 248 Group("test-group"). 249 Obj(), 250 want: testingpod.MakePod("test-pod", defaultNamespace.Name). 251 Queue("test-queue"). 252 Group("test-group"). 253 RoleHash("a9f06f3a"). 254 Label("kueue.x-k8s.io/managed", "true"). 255 KueueSchedulingGate(). 256 KueueFinalizer(). 257 Obj(), 258 }, 259 } 260 261 for name, tc := range testCases { 262 t.Run(name, func(t *testing.T) { 263 builder := utiltesting.NewClientBuilder() 264 builder = builder.WithObjects(tc.initObjects...) 265 cli := builder.Build() 266 267 w := &PodWebhook{ 268 client: cli, 269 manageJobsWithoutQueueName: tc.manageJobsWithoutQueueName, 270 namespaceSelector: tc.namespaceSelector, 271 podSelector: tc.podSelector, 272 } 273 274 ctx, _ := utiltesting.ContextWithLog(t) 275 276 if err := w.Default(ctx, tc.pod); err != nil { 277 t.Errorf("failed to set defaults for v1/pod: %s", err) 278 } 279 if diff := cmp.Diff(tc.want, tc.pod); len(diff) != 0 { 280 t.Errorf("Default() mismatch (-want,+got):\n%s", diff) 281 } 282 }) 283 } 284 } 285 286 func TestGetRoleHash(t *testing.T) { 287 testCases := map[string]struct { 288 pods []*Pod 289 // If true, hash for all the pods in test should be equal 290 wantEqualHash bool 291 wantErr error 292 }{ 293 "kueue.x-k8s.io/* labels shouldn't affect the role": { 294 pods: []*Pod{ 295 {pod: *testingpod.MakePod("pod1", "test-ns"). 296 Label("kueue.x-k8s.io/managed", "true"). 297 Obj()}, 298 {pod: *testingpod.MakePod("pod2", "test-ns"). 299 Obj()}, 300 }, 301 wantEqualHash: true, 302 }, 303 "volume name shouldn't affect the role": { 304 pods: []*Pod{ 305 {pod: *testingpod.MakePod("pod1", "test-ns"). 306 Volume(corev1.Volume{ 307 Name: "volume1", 308 }). 309 Obj()}, 310 {pod: *testingpod.MakePod("pod1", "test-ns"). 311 Volume(corev1.Volume{ 312 Name: "volume2", 313 }). 314 Obj()}, 315 }, 316 wantEqualHash: true, 317 }, 318 // NOTE: volumes used to be included in the role hash. 319 // https://github.com/kubernetes-sigs/kueue/issues/1697 320 "volumes with different claims shouldn't affect the role": { 321 pods: []*Pod{ 322 {pod: *testingpod.MakePod("pod1", "test-ns"). 323 Volume(corev1.Volume{ 324 Name: "volume", 325 VolumeSource: corev1.VolumeSource{ 326 PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ 327 ClaimName: "claim1", 328 }, 329 }, 330 }). 331 Obj()}, 332 {pod: *testingpod.MakePod("pod1", "test-ns"). 333 Volume(corev1.Volume{ 334 Name: "volume", 335 VolumeSource: corev1.VolumeSource{ 336 PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ 337 ClaimName: "claim2", 338 }, 339 }, 340 }). 341 Obj()}, 342 }, 343 wantEqualHash: true, 344 }, 345 } 346 347 for name, tc := range testCases { 348 t.Run(name, func(t *testing.T) { 349 350 var previousHash string 351 for i := range tc.pods { 352 hash, err := getRoleHash(tc.pods[i].pod) 353 354 if diff := cmp.Diff(tc.wantErr, err); diff != "" { 355 t.Errorf("Unexpected error (-want,+got):\n%s", diff) 356 } 357 358 if previousHash != "" { 359 if tc.wantEqualHash { 360 if previousHash != hash { 361 t.Errorf("Hash of pod shapes shouldn't be different %s!=%s", previousHash, hash) 362 } 363 } else { 364 if previousHash == hash { 365 t.Errorf("Hash of pod shapes shouldn't be equal %s==%s", previousHash, hash) 366 } 367 } 368 } 369 370 previousHash = hash 371 } 372 }) 373 } 374 } 375 376 func TestValidateCreate(t *testing.T) { 377 testCases := map[string]struct { 378 pod *corev1.Pod 379 wantErr error 380 wantWarns admission.Warnings 381 }{ 382 "pod owner is managed by kueue": { 383 pod: testingpod.MakePod("test-pod", "test-ns"). 384 Label("kueue.x-k8s.io/managed", "true"). 385 OwnerReference("parent-job", batchv1.SchemeGroupVersion.WithKind("Job")). 386 Obj(), 387 wantWarns: admission.Warnings{ 388 "pod owner is managed by kueue, label 'kueue.x-k8s.io/managed=true' might lead to unexpected behaviour", 389 }, 390 }, 391 "pod with group name and no group total count": { 392 pod: testingpod.MakePod("test-pod", "test-ns"). 393 Label("kueue.x-k8s.io/managed", "true"). 394 Group("test-group"). 395 Obj(), 396 wantErr: field.ErrorList{ 397 &field.Error{ 398 Type: field.ErrorTypeRequired, 399 Field: "metadata.annotations[kueue.x-k8s.io/pod-group-total-count]", 400 }, 401 }.ToAggregate(), 402 }, 403 "pod with group total count and no group name": { 404 pod: testingpod.MakePod("test-pod", "test-ns"). 405 Label("kueue.x-k8s.io/managed", "true"). 406 GroupTotalCount("3"). 407 Obj(), 408 wantErr: field.ErrorList{ 409 &field.Error{ 410 Type: field.ErrorTypeRequired, 411 Field: "metadata.labels[kueue.x-k8s.io/pod-group-name]", 412 }, 413 }.ToAggregate(), 414 }, 415 "pod with 0 group total count": { 416 pod: testingpod.MakePod("test-pod", "test-ns"). 417 Label("kueue.x-k8s.io/managed", "true"). 418 Group("test-group"). 419 GroupTotalCount("0"). 420 Obj(), 421 wantErr: field.ErrorList{ 422 &field.Error{ 423 Type: field.ErrorTypeInvalid, 424 Field: "metadata.annotations[kueue.x-k8s.io/pod-group-total-count]", 425 }, 426 }.ToAggregate(), 427 }, 428 "pod with empty group total count": { 429 pod: testingpod.MakePod("test-pod", "test-ns"). 430 Label("kueue.x-k8s.io/managed", "true"). 431 Group("test-group"). 432 GroupTotalCount(""). 433 Obj(), 434 wantErr: field.ErrorList{ 435 &field.Error{ 436 Type: field.ErrorTypeInvalid, 437 Field: "metadata.annotations[kueue.x-k8s.io/pod-group-total-count]", 438 }, 439 }.ToAggregate(), 440 }, 441 "pod with incorrect group name": { 442 pod: testingpod.MakePod("test-pod", "test-ns"). 443 Label("kueue.x-k8s.io/managed", "true"). 444 Group("notAdns1123Subdomain*"). 445 GroupTotalCount("2"). 446 Obj(), 447 wantErr: field.ErrorList{ 448 &field.Error{ 449 Type: field.ErrorTypeInvalid, 450 Field: "metadata.labels[kueue.x-k8s.io/pod-group-name]", 451 }, 452 }.ToAggregate(), 453 }, 454 } 455 456 for name, tc := range testCases { 457 t.Run(name, func(t *testing.T) { 458 builder := utiltesting.NewClientBuilder() 459 cli := builder.Build() 460 461 w := &PodWebhook{ 462 client: cli, 463 } 464 465 ctx, _ := utiltesting.ContextWithLog(t) 466 467 warns, err := w.ValidateCreate(ctx, tc.pod) 468 if diff := cmp.Diff(tc.wantErr, err, cmpopts.IgnoreFields(field.Error{}, "BadValue", "Detail")); diff != "" { 469 t.Errorf("Unexpected error (-want,+got):\n%s", diff) 470 } 471 if diff := cmp.Diff(warns, tc.wantWarns); diff != "" { 472 t.Errorf("Expected different list of warnings (-want,+got):\n%s", diff) 473 } 474 }) 475 } 476 } 477 478 func TestValidateUpdate(t *testing.T) { 479 testCases := map[string]struct { 480 oldPod *corev1.Pod 481 newPod *corev1.Pod 482 wantErr error 483 wantWarns admission.Warnings 484 }{ 485 "pods owner is managed by kueue, managed label is set for both pods": { 486 oldPod: testingpod.MakePod("test-pod", "test-ns"). 487 Label("kueue.x-k8s.io/managed", "true"). 488 OwnerReference("parent-job", batchv1.SchemeGroupVersion.WithKind("Job")). 489 Obj(), 490 newPod: testingpod.MakePod("test-pod", "test-ns"). 491 Label("kueue.x-k8s.io/managed", "true"). 492 OwnerReference("parent-job", batchv1.SchemeGroupVersion.WithKind("Job")). 493 Obj(), 494 wantWarns: admission.Warnings{ 495 "pod owner is managed by kueue, label 'kueue.x-k8s.io/managed=true' might lead to unexpected behaviour", 496 }, 497 }, 498 "pod owner is managed by kueue, managed label is set for new pod": { 499 oldPod: testingpod.MakePod("test-pod", "test-ns"). 500 OwnerReference("parent-job", batchv1.SchemeGroupVersion.WithKind("Job")). 501 Obj(), 502 newPod: testingpod.MakePod("test-pod", "test-ns"). 503 Label("kueue.x-k8s.io/managed", "true"). 504 OwnerReference("parent-job", batchv1.SchemeGroupVersion.WithKind("Job")). 505 Obj(), 506 wantWarns: admission.Warnings{ 507 "pod owner is managed by kueue, label 'kueue.x-k8s.io/managed=true' might lead to unexpected behaviour", 508 }, 509 }, 510 "pod with group name and no group total count": { 511 oldPod: testingpod.MakePod("test-pod", "test-ns").Group("test-group").Obj(), 512 newPod: testingpod.MakePod("test-pod", "test-ns"). 513 Label("kueue.x-k8s.io/managed", "true"). 514 Group("test-group"). 515 Obj(), 516 wantErr: field.ErrorList{ 517 &field.Error{ 518 Type: field.ErrorTypeRequired, 519 Field: "metadata.annotations[kueue.x-k8s.io/pod-group-total-count]", 520 }, 521 }.ToAggregate(), 522 }, 523 "pod with 0 group total count": { 524 oldPod: testingpod.MakePod("test-pod", "test-ns").Group("test-group").Obj(), 525 newPod: testingpod.MakePod("test-pod", "test-ns"). 526 Label("kueue.x-k8s.io/managed", "true"). 527 Group("test-group"). 528 GroupTotalCount("0"). 529 Obj(), 530 wantErr: field.ErrorList{ 531 &field.Error{ 532 Type: field.ErrorTypeInvalid, 533 Field: "metadata.annotations[kueue.x-k8s.io/pod-group-total-count]", 534 }, 535 }.ToAggregate(), 536 }, 537 "pod with empty group total count": { 538 oldPod: testingpod.MakePod("test-pod", "test-ns").Group("test-group").Obj(), 539 newPod: testingpod.MakePod("test-pod", "test-ns"). 540 Label("kueue.x-k8s.io/managed", "true"). 541 Group("test-group"). 542 GroupTotalCount(""). 543 Obj(), 544 wantErr: field.ErrorList{ 545 &field.Error{ 546 Type: field.ErrorTypeInvalid, 547 Field: "metadata.annotations[kueue.x-k8s.io/pod-group-total-count]", 548 }, 549 }.ToAggregate(), 550 }, 551 "pod group name is changed": { 552 oldPod: testingpod.MakePod("test-pod", "test-ns"). 553 Group("test-group"). 554 GroupTotalCount("2"). 555 Obj(), 556 newPod: testingpod.MakePod("test-pod", "test-ns"). 557 Group("test-group-new"). 558 GroupTotalCount("2"). 559 Obj(), 560 wantErr: field.ErrorList{ 561 &field.Error{ 562 Type: field.ErrorTypeInvalid, 563 Field: "metadata.labels[kueue.x-k8s.io/pod-group-name]", 564 }, 565 }.ToAggregate(), 566 }, 567 "retriable in group annotation is removed": { 568 oldPod: testingpod.MakePod("test-pod", "test-ns"). 569 Group("test-group"). 570 GroupTotalCount("2"). 571 Annotation("kueue.x-k8s.io/retriable-in-group", "false"). 572 Obj(), 573 newPod: testingpod.MakePod("test-pod", "test-ns"). 574 Group("test-group"). 575 GroupTotalCount("2"). 576 Obj(), 577 wantErr: field.ErrorList{ 578 &field.Error{ 579 Type: field.ErrorTypeForbidden, 580 Field: "metadata.annotations[kueue.x-k8s.io/retriable-in-group]", 581 }, 582 }.ToAggregate(), 583 }, 584 "retriable in group annotation is changed from false to true": { 585 oldPod: testingpod.MakePod("test-pod", "test-ns"). 586 Group("test-group"). 587 GroupTotalCount("2"). 588 Annotation("kueue.x-k8s.io/retriable-in-group", "false"). 589 Obj(), 590 newPod: testingpod.MakePod("test-pod", "test-ns"). 591 Group("test-group"). 592 GroupTotalCount("2"). 593 Annotation("kueue.x-k8s.io/retriable-in-group", "true"). 594 Obj(), 595 wantErr: field.ErrorList{ 596 &field.Error{ 597 Type: field.ErrorTypeForbidden, 598 Field: "metadata.annotations[kueue.x-k8s.io/retriable-in-group]", 599 }, 600 }.ToAggregate(), 601 }, 602 } 603 604 for name, tc := range testCases { 605 t.Run(name, func(t *testing.T) { 606 builder := utiltesting.NewClientBuilder() 607 cli := builder.Build() 608 609 w := &PodWebhook{ 610 client: cli, 611 } 612 613 ctx, _ := utiltesting.ContextWithLog(t) 614 615 warns, err := w.ValidateUpdate(ctx, tc.oldPod, tc.newPod) 616 if diff := cmp.Diff(tc.wantErr, err, cmpopts.IgnoreFields(field.Error{}, "BadValue", "Detail")); diff != "" { 617 t.Errorf("Unexpected error (-want,+got):\n%s", diff) 618 } 619 if diff := cmp.Diff(warns, tc.wantWarns); diff != "" { 620 t.Errorf("Expected different list of warnings (-want,+got):\n%s", diff) 621 } 622 }) 623 } 624 } 625 626 func TestGetPodOptions(t *testing.T) { 627 cases := map[string]struct { 628 integrationOpts map[string]any 629 wantOpts configapi.PodIntegrationOptions 630 wantError error 631 }{ 632 "proper podIntegrationOptions exists": { 633 integrationOpts: map[string]any{ 634 corev1.SchemeGroupVersion.WithKind("Pod").String(): &configapi.PodIntegrationOptions{ 635 PodSelector: &metav1.LabelSelector{ 636 MatchLabels: map[string]string{"podKey": "podValue"}, 637 }, 638 NamespaceSelector: &metav1.LabelSelector{ 639 MatchLabels: map[string]string{"nsKey": "nsValue"}, 640 }, 641 }, 642 batchv1.SchemeGroupVersion.WithKind("Job").String(): nil, 643 }, 644 wantOpts: configapi.PodIntegrationOptions{ 645 PodSelector: &metav1.LabelSelector{ 646 MatchLabels: map[string]string{"podKey": "podValue"}, 647 }, 648 NamespaceSelector: &metav1.LabelSelector{ 649 MatchLabels: map[string]string{"nsKey": "nsValue"}, 650 }, 651 }, 652 }, 653 "integrationOptions doesn't have podIntegrationOptions": { 654 integrationOpts: map[string]any{ 655 batchv1.SchemeGroupVersion.WithKind("Job").String(): nil, 656 }, 657 wantError: errPodOptsNotFound, 658 }, 659 "podIntegrationOptions isn't of type PodIntegrationOptions": { 660 integrationOpts: map[string]any{ 661 corev1.SchemeGroupVersion.WithKind("Pod").String(): &configapi.WaitForPodsReady{}, 662 }, 663 wantError: errPodOptsTypeAssertion, 664 }, 665 } 666 for name, tc := range cases { 667 t.Run(name, func(t *testing.T) { 668 gotOpts, gotError := getPodOptions(tc.integrationOpts) 669 if diff := cmp.Diff(tc.wantError, gotError, cmpopts.EquateErrors()); len(diff) != 0 { 670 t.Errorf("Unexpected error from getPodOptions (-want,+got):\n%s", diff) 671 } 672 if diff := cmp.Diff(tc.wantOpts, gotOpts, cmpopts.EquateEmpty()); len(diff) != 0 { 673 t.Errorf("Unexpected podIntegrationOptions from gotPodOptions (-want,+got):\n%s", diff) 674 } 675 }) 676 } 677 }