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