k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/test/integration/pods/pods_test.go (about) 1 /* 2 Copyright 2015 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 pods 18 19 import ( 20 "context" 21 "fmt" 22 "strings" 23 "testing" 24 25 v1 "k8s.io/api/core/v1" 26 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 27 "k8s.io/apimachinery/pkg/types" 28 clientset "k8s.io/client-go/kubernetes" 29 typedv1 "k8s.io/client-go/kubernetes/typed/core/v1" 30 kubeapiservertesting "k8s.io/kubernetes/cmd/kube-apiserver/app/testing" 31 "k8s.io/kubernetes/test/integration" 32 "k8s.io/kubernetes/test/integration/framework" 33 ) 34 35 func TestPodUpdateActiveDeadlineSeconds(t *testing.T) { 36 // Disable ServiceAccount admission plugin as we don't have serviceaccount controller running. 37 server := kubeapiservertesting.StartTestServerOrDie(t, nil, []string{"--disable-admission-plugins=ServiceAccount"}, framework.SharedEtcd()) 38 defer server.TearDownFn() 39 40 client := clientset.NewForConfigOrDie(server.ClientConfig) 41 42 ns := framework.CreateNamespaceOrDie(client, "pod-activedeadline-update", t) 43 defer framework.DeleteNamespaceOrDie(client, ns, t) 44 45 var ( 46 iZero = int64(0) 47 i30 = int64(30) 48 i60 = int64(60) 49 iNeg = int64(-1) 50 ) 51 52 prototypePod := func() *v1.Pod { 53 return &v1.Pod{ 54 ObjectMeta: metav1.ObjectMeta{ 55 Name: "xxx", 56 }, 57 Spec: v1.PodSpec{ 58 Containers: []v1.Container{ 59 { 60 Name: "fake-name", 61 Image: "fakeimage", 62 }, 63 }, 64 }, 65 } 66 } 67 68 cases := []struct { 69 name string 70 original *int64 71 update *int64 72 valid bool 73 }{ 74 { 75 name: "no change, nil", 76 original: nil, 77 update: nil, 78 valid: true, 79 }, 80 { 81 name: "no change, set", 82 original: &i30, 83 update: &i30, 84 valid: true, 85 }, 86 { 87 name: "change to positive from nil", 88 original: nil, 89 update: &i60, 90 valid: true, 91 }, 92 { 93 name: "change to smaller positive", 94 original: &i60, 95 update: &i30, 96 valid: true, 97 }, 98 { 99 name: "change to larger positive", 100 original: &i30, 101 update: &i60, 102 valid: false, 103 }, 104 { 105 name: "change to negative from positive", 106 original: &i30, 107 update: &iNeg, 108 valid: false, 109 }, 110 { 111 name: "change to negative from nil", 112 original: nil, 113 update: &iNeg, 114 valid: false, 115 }, 116 // zero is not allowed, must be a positive integer 117 { 118 name: "change to zero from positive", 119 original: &i30, 120 update: &iZero, 121 valid: false, 122 }, 123 { 124 name: "change to nil from positive", 125 original: &i30, 126 update: nil, 127 valid: false, 128 }, 129 } 130 131 for i, tc := range cases { 132 pod := prototypePod() 133 pod.Spec.ActiveDeadlineSeconds = tc.original 134 pod.ObjectMeta.Name = fmt.Sprintf("activedeadlineseconds-test-%v", i) 135 136 if _, err := client.CoreV1().Pods(ns.Name).Create(context.TODO(), pod, metav1.CreateOptions{}); err != nil { 137 t.Errorf("Failed to create pod: %v", err) 138 } 139 140 pod.Spec.ActiveDeadlineSeconds = tc.update 141 142 _, err := client.CoreV1().Pods(ns.Name).Update(context.TODO(), pod, metav1.UpdateOptions{}) 143 if tc.valid && err != nil { 144 t.Errorf("%v: failed to update pod: %v", tc.name, err) 145 } else if !tc.valid && err == nil { 146 t.Errorf("%v: unexpected allowed update to pod", tc.name) 147 } 148 149 integration.DeletePodOrErrorf(t, client, ns.Name, pod.Name) 150 } 151 } 152 153 func TestPodReadOnlyFilesystem(t *testing.T) { 154 // Disable ServiceAccount admission plugin as we don't have serviceaccount controller running. 155 server := kubeapiservertesting.StartTestServerOrDie(t, nil, []string{"--disable-admission-plugins=ServiceAccount"}, framework.SharedEtcd()) 156 defer server.TearDownFn() 157 158 client := clientset.NewForConfigOrDie(server.ClientConfig) 159 160 isReadOnly := true 161 ns := framework.CreateNamespaceOrDie(client, "pod-readonly-root", t) 162 defer framework.DeleteNamespaceOrDie(client, ns, t) 163 164 pod := &v1.Pod{ 165 ObjectMeta: metav1.ObjectMeta{ 166 Name: "xxx", 167 }, 168 Spec: v1.PodSpec{ 169 Containers: []v1.Container{ 170 { 171 Name: "fake-name", 172 Image: "fakeimage", 173 SecurityContext: &v1.SecurityContext{ 174 ReadOnlyRootFilesystem: &isReadOnly, 175 }, 176 }, 177 }, 178 }, 179 } 180 181 if _, err := client.CoreV1().Pods(ns.Name).Create(context.TODO(), pod, metav1.CreateOptions{}); err != nil { 182 t.Errorf("Failed to create pod: %v", err) 183 } 184 185 integration.DeletePodOrErrorf(t, client, ns.Name, pod.Name) 186 } 187 188 func TestPodCreateEphemeralContainers(t *testing.T) { 189 // Disable ServiceAccount admission plugin as we don't have serviceaccount controller running. 190 server := kubeapiservertesting.StartTestServerOrDie(t, nil, []string{"--disable-admission-plugins=ServiceAccount"}, framework.SharedEtcd()) 191 defer server.TearDownFn() 192 193 client := clientset.NewForConfigOrDie(server.ClientConfig) 194 195 ns := framework.CreateNamespaceOrDie(client, "pod-create-ephemeral-containers", t) 196 defer framework.DeleteNamespaceOrDie(client, ns, t) 197 198 pod := &v1.Pod{ 199 ObjectMeta: metav1.ObjectMeta{ 200 Name: "xxx", 201 }, 202 Spec: v1.PodSpec{ 203 Containers: []v1.Container{ 204 { 205 Name: "fake-name", 206 Image: "fakeimage", 207 ImagePullPolicy: "Always", 208 TerminationMessagePolicy: "File", 209 }, 210 }, 211 EphemeralContainers: []v1.EphemeralContainer{ 212 { 213 EphemeralContainerCommon: v1.EphemeralContainerCommon{ 214 Name: "debugger", 215 Image: "debugimage", 216 ImagePullPolicy: "Always", 217 TerminationMessagePolicy: "File", 218 }, 219 }, 220 }, 221 }, 222 } 223 224 if _, err := client.CoreV1().Pods(ns.Name).Create(context.TODO(), pod, metav1.CreateOptions{}); err == nil { 225 t.Errorf("Unexpected allowed creation of pod with ephemeral containers") 226 integration.DeletePodOrErrorf(t, client, ns.Name, pod.Name) 227 } else if !strings.HasSuffix(err.Error(), "spec.ephemeralContainers: Forbidden: cannot be set on create") { 228 t.Errorf("Unexpected error when creating pod with ephemeral containers: %v", err) 229 } 230 } 231 232 // setUpEphemeralContainers creates a pod that has Ephemeral Containers. This is a two step 233 // process because Ephemeral Containers are not allowed during pod creation. 234 func setUpEphemeralContainers(podsClient typedv1.PodInterface, pod *v1.Pod, containers []v1.EphemeralContainer) (*v1.Pod, error) { 235 result, err := podsClient.Create(context.TODO(), pod, metav1.CreateOptions{}) 236 if err != nil { 237 return nil, fmt.Errorf("failed to create pod: %v", err) 238 } 239 240 if len(containers) == 0 { 241 return result, nil 242 } 243 244 pod.Spec.EphemeralContainers = containers 245 if _, err := podsClient.Update(context.TODO(), pod, metav1.UpdateOptions{}); err == nil { 246 return nil, fmt.Errorf("unexpected allowed direct update of ephemeral containers during set up: %v", err) 247 } 248 249 result, err = podsClient.UpdateEphemeralContainers(context.TODO(), pod.Name, pod, metav1.UpdateOptions{}) 250 if err != nil { 251 return nil, fmt.Errorf("failed to update ephemeral containers for test case set up: %v", err) 252 } 253 254 return result, nil 255 } 256 257 func TestPodPatchEphemeralContainers(t *testing.T) { 258 // Disable ServiceAccount admission plugin as we don't have serviceaccount controller running. 259 server := kubeapiservertesting.StartTestServerOrDie(t, nil, []string{"--disable-admission-plugins=ServiceAccount"}, framework.SharedEtcd()) 260 defer server.TearDownFn() 261 262 client := clientset.NewForConfigOrDie(server.ClientConfig) 263 264 ns := framework.CreateNamespaceOrDie(client, "pod-patch-ephemeral-containers", t) 265 defer framework.DeleteNamespaceOrDie(client, ns, t) 266 267 testPod := func(name string) *v1.Pod { 268 return &v1.Pod{ 269 ObjectMeta: metav1.ObjectMeta{ 270 Name: name, 271 }, 272 Spec: v1.PodSpec{ 273 Containers: []v1.Container{ 274 { 275 Name: "fake-name", 276 Image: "fakeimage", 277 ImagePullPolicy: "Always", 278 TerminationMessagePolicy: "File", 279 }, 280 }, 281 }, 282 } 283 } 284 285 cases := []struct { 286 name string 287 original []v1.EphemeralContainer 288 patchType types.PatchType 289 patchBody []byte 290 valid bool 291 }{ 292 { 293 name: "create single container (strategic)", 294 original: nil, 295 patchType: types.StrategicMergePatchType, 296 patchBody: []byte(`{ 297 "spec": { 298 "ephemeralContainers": [{ 299 "name": "debugger1", 300 "image": "debugimage", 301 "imagePullPolicy": "Always", 302 "terminationMessagePolicy": "File" 303 }] 304 } 305 }`), 306 valid: true, 307 }, 308 { 309 name: "create single container (merge)", 310 original: nil, 311 patchType: types.MergePatchType, 312 patchBody: []byte(`{ 313 "spec": { 314 "ephemeralContainers":[{ 315 "name": "debugger1", 316 "image": "debugimage", 317 "imagePullPolicy": "Always", 318 "terminationMessagePolicy": "File" 319 }] 320 } 321 }`), 322 valid: true, 323 }, 324 { 325 name: "create single container (JSON)", 326 original: nil, 327 patchType: types.JSONPatchType, 328 // Because ephemeralContainers is optional, a JSON patch of an empty ephemeralContainers must add the 329 // list rather than simply appending to it. 330 patchBody: []byte(`[{ 331 "op":"add", 332 "path":"/spec/ephemeralContainers", 333 "value":[{ 334 "name":"debugger1", 335 "image":"debugimage", 336 "imagePullPolicy": "Always", 337 "terminationMessagePolicy": "File" 338 }] 339 }]`), 340 valid: true, 341 }, 342 { 343 name: "add single container (strategic)", 344 original: []v1.EphemeralContainer{ 345 { 346 EphemeralContainerCommon: v1.EphemeralContainerCommon{ 347 Name: "debugger1", 348 Image: "debugimage", 349 ImagePullPolicy: "Always", 350 TerminationMessagePolicy: "File", 351 }, 352 }, 353 }, 354 patchType: types.StrategicMergePatchType, 355 patchBody: []byte(`{ 356 "spec": { 357 "ephemeralContainers":[{ 358 "name": "debugger2", 359 "image": "debugimage", 360 "imagePullPolicy": "Always", 361 "terminationMessagePolicy": "File" 362 }] 363 } 364 }`), 365 valid: true, 366 }, 367 { 368 name: "add single container (merge)", 369 original: []v1.EphemeralContainer{ 370 { 371 EphemeralContainerCommon: v1.EphemeralContainerCommon{ 372 Name: "debugger1", 373 Image: "debugimage", 374 ImagePullPolicy: "Always", 375 TerminationMessagePolicy: "File", 376 }, 377 }, 378 }, 379 patchType: types.MergePatchType, 380 patchBody: []byte(`{ 381 "spec": { 382 "ephemeralContainers":[{ 383 "name": "debugger1", 384 "image": "debugimage", 385 "imagePullPolicy": "Always", 386 "terminationMessagePolicy": "File" 387 },{ 388 "name": "debugger2", 389 "image": "debugimage", 390 "imagePullPolicy": "Always", 391 "terminationMessagePolicy": "File" 392 }] 393 } 394 }`), 395 valid: true, 396 }, 397 { 398 name: "add single container (JSON)", 399 original: []v1.EphemeralContainer{ 400 { 401 EphemeralContainerCommon: v1.EphemeralContainerCommon{ 402 Name: "debugger1", 403 Image: "debugimage", 404 ImagePullPolicy: "Always", 405 TerminationMessagePolicy: "File", 406 }, 407 }, 408 }, 409 patchType: types.JSONPatchType, 410 patchBody: []byte(`[{ 411 "op":"add", 412 "path":"/spec/ephemeralContainers/-", 413 "value":{ 414 "name":"debugger2", 415 "image":"debugimage", 416 "imagePullPolicy": "Always", 417 "terminationMessagePolicy": "File" 418 } 419 }]`), 420 valid: true, 421 }, 422 { 423 name: "remove all containers (merge)", 424 original: []v1.EphemeralContainer{ 425 { 426 EphemeralContainerCommon: v1.EphemeralContainerCommon{ 427 Name: "debugger1", 428 Image: "debugimage", 429 ImagePullPolicy: "Always", 430 TerminationMessagePolicy: "File", 431 }, 432 }, 433 }, 434 patchType: types.MergePatchType, 435 patchBody: []byte(`{"spec": {"ephemeralContainers":[]}}`), 436 valid: false, 437 }, 438 { 439 name: "remove the single container (JSON)", 440 original: []v1.EphemeralContainer{ 441 { 442 EphemeralContainerCommon: v1.EphemeralContainerCommon{ 443 Name: "debugger1", 444 Image: "debugimage", 445 ImagePullPolicy: "Always", 446 TerminationMessagePolicy: "File", 447 }, 448 }, 449 }, 450 patchType: types.JSONPatchType, 451 patchBody: []byte(`[{"op":"remove","path":"/spec/ephemeralContainers/0"}]`), 452 valid: false, // disallowed by policy rather than patch semantics 453 }, 454 { 455 name: "remove all containers (JSON)", 456 original: []v1.EphemeralContainer{ 457 { 458 EphemeralContainerCommon: v1.EphemeralContainerCommon{ 459 Name: "debugger1", 460 Image: "debugimage", 461 ImagePullPolicy: "Always", 462 TerminationMessagePolicy: "File", 463 }, 464 }, 465 }, 466 patchType: types.JSONPatchType, 467 patchBody: []byte(`[{"op":"remove","path":"/spec/ephemeralContainers"}]`), 468 valid: false, // disallowed by policy rather than patch semantics 469 }, 470 } 471 472 for i, tc := range cases { 473 pod := testPod(fmt.Sprintf("ephemeral-container-test-%v", i)) 474 if _, err := setUpEphemeralContainers(client.CoreV1().Pods(ns.Name), pod, tc.original); err != nil { 475 t.Errorf("%v: %v", tc.name, err) 476 } 477 478 if _, err := client.CoreV1().Pods(ns.Name).Patch(context.TODO(), pod.Name, tc.patchType, tc.patchBody, metav1.PatchOptions{}, "ephemeralcontainers"); tc.valid && err != nil { 479 t.Errorf("%v: failed to update ephemeral containers: %v", tc.name, err) 480 } else if !tc.valid && err == nil { 481 t.Errorf("%v: unexpected allowed update to ephemeral containers", tc.name) 482 } 483 484 integration.DeletePodOrErrorf(t, client, ns.Name, pod.Name) 485 } 486 } 487 488 func TestPodUpdateEphemeralContainers(t *testing.T) { 489 // Disable ServiceAccount admission plugin as we don't have serviceaccount controller running. 490 server := kubeapiservertesting.StartTestServerOrDie(t, nil, []string{"--disable-admission-plugins=ServiceAccount"}, framework.SharedEtcd()) 491 defer server.TearDownFn() 492 493 client := clientset.NewForConfigOrDie(server.ClientConfig) 494 495 ns := framework.CreateNamespaceOrDie(client, "pod-update-ephemeral-containers", t) 496 defer framework.DeleteNamespaceOrDie(client, ns, t) 497 498 testPod := func(name string) *v1.Pod { 499 return &v1.Pod{ 500 ObjectMeta: metav1.ObjectMeta{ 501 Name: name, 502 }, 503 Spec: v1.PodSpec{ 504 Containers: []v1.Container{ 505 { 506 Name: "fake-name", 507 Image: "fakeimage", 508 }, 509 }, 510 }, 511 } 512 } 513 514 cases := []struct { 515 name string 516 original []v1.EphemeralContainer 517 update []v1.EphemeralContainer 518 valid bool 519 }{ 520 { 521 name: "no change, nil", 522 original: nil, 523 update: nil, 524 valid: true, 525 }, 526 { 527 name: "no change, set", 528 original: []v1.EphemeralContainer{ 529 { 530 EphemeralContainerCommon: v1.EphemeralContainerCommon{ 531 Name: "debugger", 532 Image: "debugimage", 533 ImagePullPolicy: "Always", 534 TerminationMessagePolicy: "File", 535 }, 536 }, 537 }, 538 update: []v1.EphemeralContainer{ 539 { 540 EphemeralContainerCommon: v1.EphemeralContainerCommon{ 541 Name: "debugger", 542 Image: "debugimage", 543 ImagePullPolicy: "Always", 544 TerminationMessagePolicy: "File", 545 }, 546 }, 547 }, 548 valid: true, 549 }, 550 { 551 name: "add single container", 552 original: nil, 553 update: []v1.EphemeralContainer{ 554 { 555 EphemeralContainerCommon: v1.EphemeralContainerCommon{ 556 Name: "debugger", 557 Image: "debugimage", 558 ImagePullPolicy: "Always", 559 TerminationMessagePolicy: "File", 560 }, 561 }, 562 }, 563 valid: true, 564 }, 565 { 566 name: "remove all containers, nil", 567 original: []v1.EphemeralContainer{ 568 { 569 EphemeralContainerCommon: v1.EphemeralContainerCommon{ 570 Name: "debugger", 571 Image: "debugimage", 572 ImagePullPolicy: "Always", 573 TerminationMessagePolicy: "File", 574 }, 575 }, 576 }, 577 update: nil, 578 valid: false, 579 }, 580 { 581 name: "remove all containers, empty", 582 original: []v1.EphemeralContainer{ 583 { 584 EphemeralContainerCommon: v1.EphemeralContainerCommon{ 585 Name: "debugger", 586 Image: "debugimage", 587 ImagePullPolicy: "Always", 588 TerminationMessagePolicy: "File", 589 }, 590 }, 591 }, 592 update: []v1.EphemeralContainer{}, 593 valid: false, 594 }, 595 { 596 name: "increase number of containers", 597 original: []v1.EphemeralContainer{ 598 { 599 EphemeralContainerCommon: v1.EphemeralContainerCommon{ 600 Name: "debugger1", 601 Image: "debugimage", 602 ImagePullPolicy: "Always", 603 TerminationMessagePolicy: "File", 604 }, 605 }, 606 }, 607 update: []v1.EphemeralContainer{ 608 { 609 EphemeralContainerCommon: v1.EphemeralContainerCommon{ 610 Name: "debugger1", 611 Image: "debugimage", 612 ImagePullPolicy: "Always", 613 TerminationMessagePolicy: "File", 614 }, 615 }, 616 { 617 EphemeralContainerCommon: v1.EphemeralContainerCommon{ 618 Name: "debugger2", 619 Image: "debugimage", 620 ImagePullPolicy: "Always", 621 TerminationMessagePolicy: "File", 622 }, 623 }, 624 }, 625 valid: true, 626 }, 627 { 628 name: "decrease number of containers", 629 original: []v1.EphemeralContainer{ 630 { 631 EphemeralContainerCommon: v1.EphemeralContainerCommon{ 632 Name: "debugger1", 633 Image: "debugimage", 634 ImagePullPolicy: "Always", 635 TerminationMessagePolicy: "File", 636 }, 637 }, 638 { 639 EphemeralContainerCommon: v1.EphemeralContainerCommon{ 640 Name: "debugger2", 641 Image: "debugimage", 642 ImagePullPolicy: "Always", 643 TerminationMessagePolicy: "File", 644 }, 645 }, 646 }, 647 update: []v1.EphemeralContainer{ 648 { 649 EphemeralContainerCommon: v1.EphemeralContainerCommon{ 650 Name: "debugger1", 651 Image: "debugimage", 652 ImagePullPolicy: "Always", 653 TerminationMessagePolicy: "File", 654 }, 655 }, 656 }, 657 valid: false, 658 }, 659 } 660 661 for i, tc := range cases { 662 pod, err := setUpEphemeralContainers(client.CoreV1().Pods(ns.Name), testPod(fmt.Sprintf("ephemeral-container-test-%v", i)), tc.original) 663 if err != nil { 664 t.Errorf("%v: %v", tc.name, err) 665 } 666 667 pod.Spec.EphemeralContainers = tc.update 668 if _, err := client.CoreV1().Pods(ns.Name).UpdateEphemeralContainers(context.TODO(), pod.Name, pod, metav1.UpdateOptions{}); tc.valid && err != nil { 669 t.Errorf("%v: failed to update ephemeral containers: %v", tc.name, err) 670 } else if !tc.valid && err == nil { 671 t.Errorf("%v: unexpected allowed update to ephemeral containers", tc.name) 672 } 673 674 integration.DeletePodOrErrorf(t, client, ns.Name, pod.Name) 675 } 676 } 677 678 func TestMutablePodSchedulingDirectives(t *testing.T) { 679 // Disable ServiceAccount admission plugin as we don't have serviceaccount controller running. 680 server := kubeapiservertesting.StartTestServerOrDie(t, nil, []string{"--disable-admission-plugins=ServiceAccount"}, framework.SharedEtcd()) 681 defer server.TearDownFn() 682 683 client := clientset.NewForConfigOrDie(server.ClientConfig) 684 685 ns := framework.CreateNamespaceOrDie(client, "mutable-pod-scheduling-directives", t) 686 defer framework.DeleteNamespaceOrDie(client, ns, t) 687 688 cases := []struct { 689 name string 690 create *v1.Pod 691 update *v1.Pod 692 err string 693 }{ 694 { 695 name: "adding node selector is allowed for gated pods", 696 create: &v1.Pod{ 697 ObjectMeta: metav1.ObjectMeta{ 698 Name: "test-pod", 699 }, 700 Spec: v1.PodSpec{ 701 Containers: []v1.Container{ 702 { 703 Name: "fake-name", 704 Image: "fakeimage", 705 }, 706 }, 707 SchedulingGates: []v1.PodSchedulingGate{{Name: "baz"}}, 708 }, 709 }, 710 update: &v1.Pod{ 711 ObjectMeta: metav1.ObjectMeta{ 712 Name: "test-pod", 713 }, 714 Spec: v1.PodSpec{ 715 Containers: []v1.Container{ 716 { 717 Name: "fake-name", 718 Image: "fakeimage", 719 }, 720 }, 721 NodeSelector: map[string]string{ 722 "foo": "bar", 723 }, 724 SchedulingGates: []v1.PodSchedulingGate{{Name: "baz"}}, 725 }, 726 }, 727 }, 728 { 729 name: "addition to nodeAffinity is allowed for gated pods", 730 create: &v1.Pod{ 731 ObjectMeta: metav1.ObjectMeta{ 732 Name: "test-pod", 733 }, 734 Spec: v1.PodSpec{ 735 Containers: []v1.Container{ 736 { 737 Name: "fake-name", 738 Image: "fakeimage", 739 }, 740 }, 741 Affinity: &v1.Affinity{ 742 NodeAffinity: &v1.NodeAffinity{ 743 RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{ 744 NodeSelectorTerms: []v1.NodeSelectorTerm{ 745 { 746 MatchExpressions: []v1.NodeSelectorRequirement{ 747 { 748 Key: "expr", 749 Operator: v1.NodeSelectorOpIn, 750 Values: []string{"foo"}, 751 }, 752 }, 753 }, 754 }, 755 }, 756 }, 757 }, 758 SchedulingGates: []v1.PodSchedulingGate{{Name: "baz"}}, 759 }, 760 }, 761 update: &v1.Pod{ 762 ObjectMeta: metav1.ObjectMeta{ 763 Name: "test-pod", 764 }, 765 Spec: v1.PodSpec{ 766 Containers: []v1.Container{ 767 { 768 Name: "fake-name", 769 Image: "fakeimage", 770 }, 771 }, 772 Affinity: &v1.Affinity{ 773 NodeAffinity: &v1.NodeAffinity{ 774 RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{ 775 // Add 1 MatchExpression and 1 MatchField. 776 NodeSelectorTerms: []v1.NodeSelectorTerm{ 777 { 778 MatchExpressions: []v1.NodeSelectorRequirement{ 779 { 780 Key: "expr", 781 Operator: v1.NodeSelectorOpIn, 782 Values: []string{"foo"}, 783 }, 784 { 785 Key: "expr2", 786 Operator: v1.NodeSelectorOpIn, 787 Values: []string{"foo2"}, 788 }, 789 }, 790 MatchFields: []v1.NodeSelectorRequirement{ 791 { 792 Key: "metadata.name", 793 Operator: v1.NodeSelectorOpIn, 794 Values: []string{"foo"}, 795 }, 796 }, 797 }, 798 }, 799 }, 800 }, 801 }, 802 SchedulingGates: []v1.PodSchedulingGate{{Name: "baz"}}, 803 }, 804 }, 805 }, 806 { 807 name: "addition to nodeAffinity is allowed for gated pods with nil affinity", 808 create: &v1.Pod{ 809 ObjectMeta: metav1.ObjectMeta{ 810 Name: "test-pod", 811 }, 812 Spec: v1.PodSpec{ 813 Containers: []v1.Container{ 814 { 815 Name: "fake-name", 816 Image: "fakeimage", 817 }, 818 }, 819 SchedulingGates: []v1.PodSchedulingGate{{Name: "baz"}}, 820 }, 821 }, 822 update: &v1.Pod{ 823 ObjectMeta: metav1.ObjectMeta{ 824 Name: "test-pod", 825 }, 826 Spec: v1.PodSpec{ 827 Containers: []v1.Container{ 828 { 829 Name: "fake-name", 830 Image: "fakeimage", 831 }, 832 }, 833 Affinity: &v1.Affinity{ 834 NodeAffinity: &v1.NodeAffinity{ 835 RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{ 836 // Add 1 MatchExpression and 1 MatchField. 837 NodeSelectorTerms: []v1.NodeSelectorTerm{ 838 { 839 MatchExpressions: []v1.NodeSelectorRequirement{ 840 { 841 Key: "expr", 842 Operator: v1.NodeSelectorOpIn, 843 Values: []string{"foo"}, 844 }, 845 }, 846 MatchFields: []v1.NodeSelectorRequirement{ 847 { 848 Key: "metadata.name", 849 Operator: v1.NodeSelectorOpIn, 850 Values: []string{"foo"}, 851 }, 852 }, 853 }, 854 }, 855 }, 856 }, 857 }, 858 SchedulingGates: []v1.PodSchedulingGate{{Name: "baz"}}, 859 }, 860 }, 861 }, 862 } 863 for _, tc := range cases { 864 if _, err := client.CoreV1().Pods(ns.Name).Create(context.TODO(), tc.create, metav1.CreateOptions{}); err != nil { 865 t.Errorf("Failed to create pod: %v", err) 866 } 867 868 _, err := client.CoreV1().Pods(ns.Name).Update(context.TODO(), tc.update, metav1.UpdateOptions{}) 869 if (tc.err == "" && err != nil) || (tc.err != "" && err != nil && !strings.Contains(err.Error(), tc.err)) { 870 t.Errorf("Unexpected error: got %q, want %q", err.Error(), err) 871 } 872 integration.DeletePodOrErrorf(t, client, ns.Name, tc.update.Name) 873 } 874 }