k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/pkg/registry/core/pod/strategy_test.go (about) 1 /* 2 Copyright 2014 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 "context" 21 "fmt" 22 "net/http" 23 "net/url" 24 "reflect" 25 "strings" 26 "testing" 27 28 "github.com/google/go-cmp/cmp" 29 "github.com/google/go-cmp/cmp/cmpopts" 30 "github.com/stretchr/testify/assert" 31 apiv1 "k8s.io/api/core/v1" 32 "k8s.io/apimachinery/pkg/api/errors" 33 "k8s.io/apimachinery/pkg/api/resource" 34 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 35 "k8s.io/apimachinery/pkg/fields" 36 "k8s.io/apimachinery/pkg/labels" 37 "k8s.io/apimachinery/pkg/runtime" 38 "k8s.io/apimachinery/pkg/types" 39 genericapirequest "k8s.io/apiserver/pkg/endpoints/request" 40 utilfeature "k8s.io/apiserver/pkg/util/feature" 41 "k8s.io/apiserver/pkg/warning" 42 "k8s.io/client-go/tools/cache" 43 featuregatetesting "k8s.io/component-base/featuregate/testing" 44 ptr "k8s.io/utils/ptr" 45 46 apitesting "k8s.io/kubernetes/pkg/api/testing" 47 api "k8s.io/kubernetes/pkg/apis/core" 48 "k8s.io/kubernetes/pkg/features" 49 "k8s.io/kubernetes/pkg/kubelet/client" 50 51 // ensure types are installed 52 _ "k8s.io/kubernetes/pkg/apis/core/install" 53 ) 54 55 func TestMatchPod(t *testing.T) { 56 testCases := []struct { 57 in *api.Pod 58 fieldSelector fields.Selector 59 expectMatch bool 60 }{ 61 { 62 in: &api.Pod{ 63 Spec: api.PodSpec{NodeName: "nodeA"}, 64 }, 65 fieldSelector: fields.ParseSelectorOrDie("spec.nodeName=nodeA"), 66 expectMatch: true, 67 }, 68 { 69 in: &api.Pod{ 70 Spec: api.PodSpec{NodeName: "nodeB"}, 71 }, 72 fieldSelector: fields.ParseSelectorOrDie("spec.nodeName=nodeA"), 73 expectMatch: false, 74 }, 75 { 76 in: &api.Pod{ 77 Spec: api.PodSpec{RestartPolicy: api.RestartPolicyAlways}, 78 }, 79 fieldSelector: fields.ParseSelectorOrDie("spec.restartPolicy=Always"), 80 expectMatch: true, 81 }, 82 { 83 in: &api.Pod{ 84 Spec: api.PodSpec{RestartPolicy: api.RestartPolicyAlways}, 85 }, 86 fieldSelector: fields.ParseSelectorOrDie("spec.restartPolicy=Never"), 87 expectMatch: false, 88 }, 89 { 90 in: &api.Pod{ 91 Spec: api.PodSpec{SchedulerName: "scheduler1"}, 92 }, 93 fieldSelector: fields.ParseSelectorOrDie("spec.schedulerName=scheduler1"), 94 expectMatch: true, 95 }, 96 { 97 in: &api.Pod{ 98 Spec: api.PodSpec{SchedulerName: "scheduler1"}, 99 }, 100 fieldSelector: fields.ParseSelectorOrDie("spec.schedulerName=scheduler2"), 101 expectMatch: false, 102 }, 103 { 104 in: &api.Pod{ 105 Spec: api.PodSpec{ServiceAccountName: "serviceAccount1"}, 106 }, 107 fieldSelector: fields.ParseSelectorOrDie("spec.serviceAccountName=serviceAccount1"), 108 expectMatch: true, 109 }, 110 { 111 in: &api.Pod{ 112 Spec: api.PodSpec{SchedulerName: "serviceAccount1"}, 113 }, 114 fieldSelector: fields.ParseSelectorOrDie("spec.serviceAccountName=serviceAccount2"), 115 expectMatch: false, 116 }, 117 { 118 in: &api.Pod{ 119 Status: api.PodStatus{Phase: api.PodRunning}, 120 }, 121 fieldSelector: fields.ParseSelectorOrDie("status.phase=Running"), 122 expectMatch: true, 123 }, 124 { 125 in: &api.Pod{ 126 Status: api.PodStatus{Phase: api.PodRunning}, 127 }, 128 fieldSelector: fields.ParseSelectorOrDie("status.phase=Pending"), 129 expectMatch: false, 130 }, 131 { 132 in: &api.Pod{ 133 Status: api.PodStatus{ 134 PodIPs: []api.PodIP{ 135 {IP: "1.2.3.4"}, 136 }, 137 }, 138 }, 139 fieldSelector: fields.ParseSelectorOrDie("status.podIP=1.2.3.4"), 140 expectMatch: true, 141 }, 142 { 143 in: &api.Pod{ 144 Status: api.PodStatus{ 145 PodIPs: []api.PodIP{ 146 {IP: "1.2.3.4"}, 147 }, 148 }, 149 }, 150 fieldSelector: fields.ParseSelectorOrDie("status.podIP=4.3.2.1"), 151 expectMatch: false, 152 }, 153 { 154 in: &api.Pod{ 155 Status: api.PodStatus{NominatedNodeName: "node1"}, 156 }, 157 fieldSelector: fields.ParseSelectorOrDie("status.nominatedNodeName=node1"), 158 expectMatch: true, 159 }, 160 { 161 in: &api.Pod{ 162 Status: api.PodStatus{NominatedNodeName: "node1"}, 163 }, 164 fieldSelector: fields.ParseSelectorOrDie("status.nominatedNodeName=node2"), 165 expectMatch: false, 166 }, 167 { 168 in: &api.Pod{ 169 Status: api.PodStatus{ 170 PodIPs: []api.PodIP{ 171 {IP: "2001:db8::"}, 172 }, 173 }, 174 }, 175 fieldSelector: fields.ParseSelectorOrDie("status.podIP=2001:db8::"), 176 expectMatch: true, 177 }, 178 { 179 in: &api.Pod{ 180 Status: api.PodStatus{ 181 PodIPs: []api.PodIP{ 182 {IP: "2001:db8::"}, 183 }, 184 }, 185 }, 186 fieldSelector: fields.ParseSelectorOrDie("status.podIP=2001:db7::"), 187 expectMatch: false, 188 }, 189 { 190 in: &api.Pod{ 191 Spec: api.PodSpec{ 192 SecurityContext: &api.PodSecurityContext{ 193 HostNetwork: true, 194 }, 195 }, 196 }, 197 fieldSelector: fields.ParseSelectorOrDie("spec.hostNetwork=true"), 198 expectMatch: true, 199 }, 200 { 201 in: &api.Pod{ 202 Spec: api.PodSpec{ 203 SecurityContext: &api.PodSecurityContext{ 204 HostNetwork: true, 205 }, 206 }, 207 }, 208 fieldSelector: fields.ParseSelectorOrDie("spec.hostNetwork=false"), 209 expectMatch: false, 210 }, 211 { 212 in: &api.Pod{ 213 Spec: api.PodSpec{ 214 SecurityContext: &api.PodSecurityContext{ 215 HostNetwork: false, 216 }, 217 }, 218 }, 219 fieldSelector: fields.ParseSelectorOrDie("spec.hostNetwork=false"), 220 expectMatch: true, 221 }, 222 { 223 in: &api.Pod{ 224 Spec: api.PodSpec{}, 225 }, 226 fieldSelector: fields.ParseSelectorOrDie("spec.hostNetwork=false"), 227 expectMatch: true, 228 }, 229 { 230 in: &api.Pod{ 231 Spec: api.PodSpec{}, 232 }, 233 fieldSelector: fields.ParseSelectorOrDie("spec.hostNetwork=true"), 234 expectMatch: false, 235 }, 236 } 237 for _, testCase := range testCases { 238 m := MatchPod(labels.Everything(), testCase.fieldSelector) 239 result, err := m.Matches(testCase.in) 240 if err != nil { 241 t.Errorf("Unexpected error %v", err) 242 } 243 if result != testCase.expectMatch { 244 t.Errorf("Result %v, Expected %v, Selector: %v, Pod: %v", result, testCase.expectMatch, testCase.fieldSelector.String(), testCase.in) 245 } 246 } 247 } 248 249 func getResourceList(cpu, memory string) api.ResourceList { 250 res := api.ResourceList{} 251 if cpu != "" { 252 res[api.ResourceCPU] = resource.MustParse(cpu) 253 } 254 if memory != "" { 255 res[api.ResourceMemory] = resource.MustParse(memory) 256 } 257 return res 258 } 259 260 func getResourceRequirements(requests, limits api.ResourceList) api.ResourceRequirements { 261 res := api.ResourceRequirements{} 262 res.Requests = requests 263 res.Limits = limits 264 return res 265 } 266 267 func newContainer(name string, requests api.ResourceList, limits api.ResourceList) api.Container { 268 return api.Container{ 269 Name: name, 270 Resources: getResourceRequirements(requests, limits), 271 } 272 } 273 274 func newPod(name string, containers []api.Container) *api.Pod { 275 return &api.Pod{ 276 ObjectMeta: metav1.ObjectMeta{ 277 Name: name, 278 }, 279 Spec: api.PodSpec{ 280 Containers: containers, 281 }, 282 } 283 } 284 285 func TestGetPodQOS(t *testing.T) { 286 testCases := []struct { 287 pod *api.Pod 288 expected api.PodQOSClass 289 }{ 290 { 291 pod: newPod("guaranteed", []api.Container{ 292 newContainer("guaranteed", getResourceList("100m", "100Mi"), getResourceList("100m", "100Mi")), 293 }), 294 expected: api.PodQOSGuaranteed, 295 }, 296 { 297 pod: newPod("best-effort", []api.Container{ 298 newContainer("best-effort", getResourceList("", ""), getResourceList("", "")), 299 }), 300 expected: api.PodQOSBestEffort, 301 }, 302 { 303 pod: newPod("burstable", []api.Container{ 304 newContainer("burstable", getResourceList("100m", "100Mi"), getResourceList("", "")), 305 }), 306 expected: api.PodQOSBurstable, 307 }, 308 } 309 for id, testCase := range testCases { 310 Strategy.PrepareForCreate(genericapirequest.NewContext(), testCase.pod) 311 actual := testCase.pod.Status.QOSClass 312 if actual != testCase.expected { 313 t.Errorf("[%d]: invalid qos pod %s, expected: %s, actual: %s", id, testCase.pod.Name, testCase.expected, actual) 314 } 315 } 316 } 317 318 func TestSchedulingGatedCondition(t *testing.T) { 319 tests := []struct { 320 name string 321 pod *api.Pod 322 want api.PodCondition 323 }{ 324 { 325 name: "pod without .spec.schedulingGates", 326 pod: &api.Pod{}, 327 want: api.PodCondition{}, 328 }, 329 { 330 name: "pod with .spec.schedulingGates", 331 pod: &api.Pod{ 332 Spec: api.PodSpec{ 333 SchedulingGates: []api.PodSchedulingGate{{Name: "foo"}}, 334 }, 335 }, 336 want: api.PodCondition{ 337 Type: api.PodScheduled, 338 Status: api.ConditionFalse, 339 Reason: apiv1.PodReasonSchedulingGated, 340 Message: "Scheduling is blocked due to non-empty scheduling gates", 341 }, 342 }, 343 } 344 345 for _, tt := range tests { 346 t.Run(tt.name, func(t *testing.T) { 347 Strategy.PrepareForCreate(genericapirequest.NewContext(), tt.pod) 348 var got api.PodCondition 349 for _, condition := range tt.pod.Status.Conditions { 350 if condition.Type == api.PodScheduled { 351 got = condition 352 break 353 } 354 } 355 356 if diff := cmp.Diff(tt.want, got); diff != "" { 357 t.Errorf("unexpected field errors (-want, +got):\n%s", diff) 358 } 359 }) 360 } 361 } 362 363 func TestCheckGracefulDelete(t *testing.T) { 364 defaultGracePeriod := int64(30) 365 tcs := []struct { 366 name string 367 pod *api.Pod 368 deleteGracePeriod *int64 369 gracePeriod int64 370 }{ 371 { 372 name: "in pending phase with has node name", 373 pod: &api.Pod{ 374 Spec: api.PodSpec{NodeName: "something"}, 375 Status: api.PodStatus{Phase: api.PodPending}, 376 }, 377 deleteGracePeriod: &defaultGracePeriod, 378 gracePeriod: defaultGracePeriod, 379 }, 380 { 381 name: "in failed phase with has node name", 382 pod: &api.Pod{ 383 Spec: api.PodSpec{NodeName: "something"}, 384 Status: api.PodStatus{Phase: api.PodFailed}, 385 }, 386 deleteGracePeriod: &defaultGracePeriod, 387 gracePeriod: 0, 388 }, 389 { 390 name: "in failed phase", 391 pod: &api.Pod{ 392 Spec: api.PodSpec{}, 393 Status: api.PodStatus{Phase: api.PodPending}, 394 }, 395 deleteGracePeriod: &defaultGracePeriod, 396 gracePeriod: 0, 397 }, 398 { 399 name: "in succeeded phase", 400 pod: &api.Pod{ 401 Spec: api.PodSpec{}, 402 Status: api.PodStatus{Phase: api.PodSucceeded}, 403 }, 404 deleteGracePeriod: &defaultGracePeriod, 405 gracePeriod: 0, 406 }, 407 { 408 name: "no phase", 409 pod: &api.Pod{ 410 Spec: api.PodSpec{}, 411 Status: api.PodStatus{}, 412 }, 413 deleteGracePeriod: &defaultGracePeriod, 414 gracePeriod: 0, 415 }, 416 { 417 name: "has negative grace period", 418 pod: &api.Pod{ 419 Spec: api.PodSpec{ 420 NodeName: "something", 421 TerminationGracePeriodSeconds: ptr.To[int64](-1), 422 }, 423 Status: api.PodStatus{}, 424 }, 425 gracePeriod: 1, 426 }, 427 } 428 for _, tc := range tcs { 429 t.Run(tc.name, func(t *testing.T) { 430 out := &metav1.DeleteOptions{} 431 if tc.deleteGracePeriod != nil { 432 out.GracePeriodSeconds = ptr.To[int64](*tc.deleteGracePeriod) 433 } 434 Strategy.CheckGracefulDelete(genericapirequest.NewContext(), tc.pod, out) 435 if out.GracePeriodSeconds == nil { 436 t.Errorf("out grace period was nil but supposed to be %v", tc.gracePeriod) 437 } 438 if *(out.GracePeriodSeconds) != tc.gracePeriod { 439 t.Errorf("out grace period was %v but was expected to be %v", *out, tc.gracePeriod) 440 } 441 }) 442 } 443 } 444 445 type mockPodGetter struct { 446 pod *api.Pod 447 } 448 449 func (g mockPodGetter) Get(context.Context, string, *metav1.GetOptions) (runtime.Object, error) { 450 return g.pod, nil 451 } 452 453 func TestCheckLogLocation(t *testing.T) { 454 ctx := genericapirequest.NewDefaultContext() 455 fakePodName := "test" 456 tcs := []struct { 457 name string 458 in *api.Pod 459 opts *api.PodLogOptions 460 expectedErr error 461 expectedTransport http.RoundTripper 462 }{ 463 { 464 name: "simple", 465 in: &api.Pod{ 466 ObjectMeta: metav1.ObjectMeta{Name: fakePodName}, 467 Spec: api.PodSpec{ 468 Containers: []api.Container{ 469 {Name: "mycontainer"}, 470 }, 471 NodeName: "foo", 472 }, 473 Status: api.PodStatus{}, 474 }, 475 opts: &api.PodLogOptions{}, 476 expectedErr: nil, 477 expectedTransport: fakeSecureRoundTripper, 478 }, 479 { 480 name: "insecure", 481 in: &api.Pod{ 482 ObjectMeta: metav1.ObjectMeta{Name: fakePodName}, 483 Spec: api.PodSpec{ 484 Containers: []api.Container{ 485 {Name: "mycontainer"}, 486 }, 487 NodeName: "foo", 488 }, 489 Status: api.PodStatus{}, 490 }, 491 opts: &api.PodLogOptions{ 492 InsecureSkipTLSVerifyBackend: true, 493 }, 494 expectedErr: nil, 495 expectedTransport: fakeInsecureRoundTripper, 496 }, 497 { 498 name: "missing container", 499 in: &api.Pod{ 500 ObjectMeta: metav1.ObjectMeta{Name: fakePodName}, 501 Spec: api.PodSpec{}, 502 Status: api.PodStatus{}, 503 }, 504 opts: &api.PodLogOptions{}, 505 expectedErr: errors.NewBadRequest("a container name must be specified for pod test"), 506 expectedTransport: nil, 507 }, 508 { 509 name: "choice of two containers", 510 in: &api.Pod{ 511 ObjectMeta: metav1.ObjectMeta{Name: fakePodName}, 512 Spec: api.PodSpec{ 513 Containers: []api.Container{ 514 {Name: "container1"}, 515 {Name: "container2"}, 516 }, 517 }, 518 Status: api.PodStatus{}, 519 }, 520 opts: &api.PodLogOptions{}, 521 expectedErr: errors.NewBadRequest("a container name must be specified for pod test, choose one of: [container1 container2]"), 522 expectedTransport: nil, 523 }, 524 { 525 name: "initcontainers", 526 in: &api.Pod{ 527 ObjectMeta: metav1.ObjectMeta{Name: fakePodName}, 528 Spec: api.PodSpec{ 529 Containers: []api.Container{ 530 {Name: "container1"}, 531 {Name: "container2"}, 532 }, 533 InitContainers: []api.Container{ 534 {Name: "initcontainer1"}, 535 }, 536 }, 537 Status: api.PodStatus{}, 538 }, 539 opts: &api.PodLogOptions{}, 540 expectedErr: errors.NewBadRequest("a container name must be specified for pod test, choose one of: [initcontainer1 container1 container2]"), 541 expectedTransport: nil, 542 }, 543 { 544 in: &api.Pod{ 545 ObjectMeta: metav1.ObjectMeta{Name: fakePodName}, 546 Spec: api.PodSpec{ 547 Containers: []api.Container{ 548 {Name: "container1"}, 549 {Name: "container2"}, 550 }, 551 InitContainers: []api.Container{ 552 {Name: "initcontainer1"}, 553 }, 554 EphemeralContainers: []api.EphemeralContainer{ 555 {EphemeralContainerCommon: api.EphemeralContainerCommon{Name: "debugger"}}, 556 }, 557 }, 558 Status: api.PodStatus{}, 559 }, 560 opts: &api.PodLogOptions{}, 561 expectedErr: errors.NewBadRequest("a container name must be specified for pod test, choose one of: [initcontainer1 container1 container2 debugger]"), 562 expectedTransport: nil, 563 }, 564 { 565 name: "bad container", 566 in: &api.Pod{ 567 ObjectMeta: metav1.ObjectMeta{Name: fakePodName}, 568 Spec: api.PodSpec{ 569 Containers: []api.Container{ 570 {Name: "container1"}, 571 {Name: "container2"}, 572 }, 573 }, 574 Status: api.PodStatus{}, 575 }, 576 opts: &api.PodLogOptions{ 577 Container: "unknown", 578 }, 579 expectedErr: errors.NewBadRequest("container unknown is not valid for pod test"), 580 expectedTransport: nil, 581 }, 582 { 583 name: "good with two containers", 584 in: &api.Pod{ 585 ObjectMeta: metav1.ObjectMeta{Name: fakePodName}, 586 Spec: api.PodSpec{ 587 Containers: []api.Container{ 588 {Name: "container1"}, 589 {Name: "container2"}, 590 }, 591 NodeName: "foo", 592 }, 593 Status: api.PodStatus{}, 594 }, 595 opts: &api.PodLogOptions{ 596 Container: "container2", 597 }, 598 expectedErr: nil, 599 expectedTransport: fakeSecureRoundTripper, 600 }, 601 } 602 for _, tc := range tcs { 603 t.Run(tc.name, func(t *testing.T) { 604 getter := &mockPodGetter{tc.in} 605 connectionGetter := &mockConnectionInfoGetter{&client.ConnectionInfo{ 606 Transport: fakeSecureRoundTripper, 607 InsecureSkipTLSVerifyTransport: fakeInsecureRoundTripper, 608 }} 609 610 _, actualTransport, err := LogLocation(ctx, getter, connectionGetter, fakePodName, tc.opts) 611 if !reflect.DeepEqual(err, tc.expectedErr) { 612 t.Errorf("expected %q, got %q", tc.expectedErr, err) 613 } 614 if actualTransport != tc.expectedTransport { 615 t.Errorf("expected %q, got %q", tc.expectedTransport, actualTransport) 616 } 617 }) 618 } 619 } 620 621 func TestSelectableFieldLabelConversions(t *testing.T) { 622 apitesting.TestSelectableFieldLabelConversionsOfKind(t, 623 "v1", 624 "Pod", 625 ToSelectableFields(&api.Pod{}), 626 nil, 627 ) 628 } 629 630 type mockConnectionInfoGetter struct { 631 info *client.ConnectionInfo 632 } 633 634 func (g mockConnectionInfoGetter) GetConnectionInfo(ctx context.Context, nodeName types.NodeName) (*client.ConnectionInfo, error) { 635 return g.info, nil 636 } 637 638 func TestPortForwardLocation(t *testing.T) { 639 ctx := genericapirequest.NewDefaultContext() 640 tcs := []struct { 641 in *api.Pod 642 info *client.ConnectionInfo 643 opts *api.PodPortForwardOptions 644 expectedErr error 645 expectedURL *url.URL 646 }{ 647 { 648 in: &api.Pod{ 649 Spec: api.PodSpec{}, 650 }, 651 opts: &api.PodPortForwardOptions{}, 652 expectedErr: errors.NewBadRequest("pod test does not have a host assigned"), 653 }, 654 { 655 in: &api.Pod{ 656 ObjectMeta: metav1.ObjectMeta{ 657 Namespace: "ns", 658 Name: "pod1", 659 }, 660 Spec: api.PodSpec{ 661 NodeName: "node1", 662 }, 663 }, 664 info: &client.ConnectionInfo{}, 665 opts: &api.PodPortForwardOptions{}, 666 expectedURL: &url.URL{Host: ":", Path: "/portForward/ns/pod1"}, 667 }, 668 { 669 in: &api.Pod{ 670 ObjectMeta: metav1.ObjectMeta{ 671 Namespace: "ns", 672 Name: "pod1", 673 }, 674 Spec: api.PodSpec{ 675 NodeName: "node1", 676 }, 677 }, 678 info: &client.ConnectionInfo{}, 679 opts: &api.PodPortForwardOptions{Ports: []int32{80}}, 680 expectedURL: &url.URL{Host: ":", Path: "/portForward/ns/pod1", RawQuery: "port=80"}, 681 }, 682 } 683 for _, tc := range tcs { 684 getter := &mockPodGetter{tc.in} 685 connectionGetter := &mockConnectionInfoGetter{tc.info} 686 loc, _, err := PortForwardLocation(ctx, getter, connectionGetter, "test", tc.opts) 687 if !reflect.DeepEqual(err, tc.expectedErr) { 688 t.Errorf("expected %v, got %v", tc.expectedErr, err) 689 } 690 if !reflect.DeepEqual(loc, tc.expectedURL) { 691 t.Errorf("expected %v, got %v", tc.expectedURL, loc) 692 } 693 } 694 } 695 696 func TestGetPodIP(t *testing.T) { 697 testCases := []struct { 698 name string 699 pod *api.Pod 700 expectedIP string 701 }{ 702 { 703 name: "nil pod", 704 pod: nil, 705 expectedIP: "", 706 }, 707 { 708 name: "no status object", 709 pod: &api.Pod{ 710 ObjectMeta: metav1.ObjectMeta{Namespace: "ns", Name: "pod1"}, 711 Spec: api.PodSpec{}, 712 }, 713 expectedIP: "", 714 }, 715 { 716 name: "no pod ips", 717 pod: &api.Pod{ 718 ObjectMeta: metav1.ObjectMeta{Namespace: "ns", Name: "pod1"}, 719 Spec: api.PodSpec{}, 720 Status: api.PodStatus{}, 721 }, 722 expectedIP: "", 723 }, 724 { 725 name: "empty list", 726 pod: &api.Pod{ 727 ObjectMeta: metav1.ObjectMeta{Namespace: "ns", Name: "pod1"}, 728 Spec: api.PodSpec{}, 729 Status: api.PodStatus{ 730 PodIPs: []api.PodIP{}, 731 }, 732 }, 733 expectedIP: "", 734 }, 735 { 736 name: "1 ip", 737 pod: &api.Pod{ 738 ObjectMeta: metav1.ObjectMeta{Namespace: "ns", Name: "pod1"}, 739 Spec: api.PodSpec{}, 740 Status: api.PodStatus{ 741 PodIPs: []api.PodIP{ 742 {IP: "10.0.0.10"}, 743 }, 744 }, 745 }, 746 expectedIP: "10.0.0.10", 747 }, 748 { 749 name: "multiple ips", 750 pod: &api.Pod{ 751 ObjectMeta: metav1.ObjectMeta{Namespace: "ns", Name: "pod1"}, 752 Spec: api.PodSpec{}, 753 Status: api.PodStatus{ 754 PodIPs: []api.PodIP{ 755 {IP: "10.0.0.10"}, 756 {IP: "10.0.0.20"}, 757 }, 758 }, 759 }, 760 expectedIP: "10.0.0.10", 761 }, 762 } 763 for _, tc := range testCases { 764 t.Run(tc.name, func(t *testing.T) { 765 podIP := getPodIP(tc.pod) 766 if podIP != tc.expectedIP { 767 t.Errorf("expected pod ip:%v does not match actual %v", tc.expectedIP, podIP) 768 } 769 }) 770 } 771 } 772 773 type fakeTransport struct { 774 val string 775 } 776 777 func (f fakeTransport) RoundTrip(*http.Request) (*http.Response, error) { 778 return nil, nil 779 } 780 781 var ( 782 fakeSecureRoundTripper = fakeTransport{val: "secure"} 783 fakeInsecureRoundTripper = fakeTransport{val: "insecure"} 784 ) 785 786 func TestPodIndexFunc(t *testing.T) { 787 tcs := []struct { 788 name string 789 indexFunc cache.IndexFunc 790 pod interface{} 791 expectedValue string 792 expectedErr error 793 }{ 794 { 795 name: "node name index", 796 indexFunc: NodeNameIndexFunc, 797 pod: &api.Pod{ 798 Spec: api.PodSpec{ 799 NodeName: "test-pod", 800 }, 801 }, 802 expectedValue: "test-pod", 803 expectedErr: nil, 804 }, 805 { 806 name: "not a pod failed", 807 indexFunc: NodeNameIndexFunc, 808 pod: "not a pod object", 809 expectedValue: "test-pod", 810 expectedErr: fmt.Errorf("not a pod"), 811 }, 812 } 813 814 for _, tc := range tcs { 815 indexValues, err := tc.indexFunc(tc.pod) 816 if !reflect.DeepEqual(err, tc.expectedErr) { 817 t.Errorf("name %v, expected %v, got %v", tc.name, tc.expectedErr, err) 818 } 819 if err == nil && len(indexValues) != 1 && indexValues[0] != tc.expectedValue { 820 t.Errorf("name %v, expected %v, got %v", tc.name, tc.expectedValue, indexValues) 821 } 822 823 } 824 } 825 826 func newPodWithHugePageValue(resourceName api.ResourceName, value resource.Quantity) *api.Pod { 827 return &api.Pod{ 828 ObjectMeta: metav1.ObjectMeta{ 829 Namespace: "default", 830 Name: "foo", 831 ResourceVersion: "1", 832 }, 833 Spec: api.PodSpec{ 834 RestartPolicy: api.RestartPolicyAlways, 835 DNSPolicy: api.DNSDefault, 836 Containers: []api.Container{{ 837 Name: "foo", 838 Image: "image", 839 ImagePullPolicy: "IfNotPresent", 840 TerminationMessagePolicy: "File", 841 Resources: api.ResourceRequirements{ 842 Requests: api.ResourceList{ 843 api.ResourceCPU: resource.MustParse("10"), 844 resourceName: value, 845 }, 846 Limits: api.ResourceList{ 847 api.ResourceCPU: resource.MustParse("10"), 848 resourceName: value, 849 }, 850 }}, 851 }, 852 }, 853 } 854 } 855 856 func TestPodStrategyValidate(t *testing.T) { 857 const containerName = "container" 858 859 tests := []struct { 860 name string 861 pod *api.Pod 862 wantErr bool 863 }{ 864 { 865 name: "a new pod setting container with indivisible hugepages values", 866 pod: newPodWithHugePageValue(api.ResourceHugePagesPrefix+"1Mi", resource.MustParse("1.1Mi")), 867 wantErr: true, 868 }, 869 { 870 name: "a new pod setting init-container with indivisible hugepages values", 871 pod: &api.Pod{ 872 ObjectMeta: metav1.ObjectMeta{ 873 Namespace: "default", 874 Name: "foo", 875 }, 876 Spec: api.PodSpec{ 877 RestartPolicy: api.RestartPolicyAlways, 878 DNSPolicy: api.DNSDefault, 879 InitContainers: []api.Container{{ 880 Name: containerName, 881 Image: "image", 882 ImagePullPolicy: "IfNotPresent", 883 TerminationMessagePolicy: "File", 884 Resources: api.ResourceRequirements{ 885 Requests: api.ResourceList{ 886 api.ResourceName(api.ResourceHugePagesPrefix + "64Ki"): resource.MustParse("127Ki"), 887 }, 888 Limits: api.ResourceList{ 889 api.ResourceName(api.ResourceHugePagesPrefix + "64Ki"): resource.MustParse("127Ki"), 890 }, 891 }}, 892 }, 893 }, 894 }, 895 wantErr: true, 896 }, 897 { 898 name: "a new pod setting init-container with indivisible hugepages values while container with divisible hugepages values", 899 pod: &api.Pod{ 900 ObjectMeta: metav1.ObjectMeta{ 901 Namespace: "default", 902 Name: "foo", 903 }, 904 Spec: api.PodSpec{ 905 RestartPolicy: api.RestartPolicyAlways, 906 DNSPolicy: api.DNSDefault, 907 InitContainers: []api.Container{{ 908 Name: containerName, 909 Image: "image", 910 ImagePullPolicy: "IfNotPresent", 911 TerminationMessagePolicy: "File", 912 Resources: api.ResourceRequirements{ 913 Requests: api.ResourceList{ 914 api.ResourceName(api.ResourceHugePagesPrefix + "2Mi"): resource.MustParse("5.1Mi"), 915 }, 916 Limits: api.ResourceList{ 917 api.ResourceName(api.ResourceHugePagesPrefix + "2Mi"): resource.MustParse("5.1Mi"), 918 }, 919 }}, 920 }, 921 Containers: []api.Container{{ 922 Name: containerName, 923 Image: "image", 924 ImagePullPolicy: "IfNotPresent", 925 TerminationMessagePolicy: "File", 926 Resources: api.ResourceRequirements{ 927 Requests: api.ResourceList{ 928 api.ResourceName(api.ResourceHugePagesPrefix + "1Gi"): resource.MustParse("2Gi"), 929 }, 930 Limits: api.ResourceList{ 931 api.ResourceName(api.ResourceHugePagesPrefix + "1Gi"): resource.MustParse("2Gi"), 932 }, 933 }}, 934 }, 935 }, 936 }, 937 wantErr: true, 938 }, 939 { 940 name: "a new pod setting container with divisible hugepages values", 941 pod: &api.Pod{ 942 ObjectMeta: metav1.ObjectMeta{ 943 Namespace: "default", 944 Name: "foo", 945 }, 946 Spec: api.PodSpec{ 947 RestartPolicy: api.RestartPolicyAlways, 948 DNSPolicy: api.DNSDefault, 949 Containers: []api.Container{{ 950 Name: containerName, 951 Image: "image", 952 ImagePullPolicy: "IfNotPresent", 953 TerminationMessagePolicy: "File", 954 Resources: api.ResourceRequirements{ 955 Requests: api.ResourceList{ 956 api.ResourceName(api.ResourceCPU): resource.MustParse("10"), 957 api.ResourceName(api.ResourceHugePagesPrefix + "1Mi"): resource.MustParse("2Mi"), 958 }, 959 Limits: api.ResourceList{ 960 api.ResourceName(api.ResourceCPU): resource.MustParse("10"), 961 api.ResourceName(api.ResourceHugePagesPrefix + "1Mi"): resource.MustParse("2Mi"), 962 }, 963 }}, 964 }, 965 }, 966 }, 967 }, 968 } 969 970 for _, tc := range tests { 971 t.Run(tc.name, func(t *testing.T) { 972 errs := Strategy.Validate(genericapirequest.NewContext(), tc.pod) 973 if tc.wantErr && len(errs) == 0 { 974 t.Errorf("expected errors but got none") 975 } 976 if !tc.wantErr && len(errs) != 0 { 977 t.Errorf("unexpected errors: %v", errs.ToAggregate()) 978 } 979 }) 980 } 981 } 982 983 func TestEphemeralContainerStrategyValidateUpdate(t *testing.T) { 984 985 test := []struct { 986 name string 987 newPod *api.Pod 988 oldPod *api.Pod 989 }{ 990 { 991 name: "add ephemeral container to regular pod and expect success", 992 oldPod: &api.Pod{ 993 ObjectMeta: metav1.ObjectMeta{ 994 Name: "test-pod", 995 Namespace: "test-ns", 996 ResourceVersion: "1", 997 }, 998 Spec: api.PodSpec{ 999 RestartPolicy: api.RestartPolicyAlways, 1000 DNSPolicy: api.DNSDefault, 1001 Containers: []api.Container{ 1002 { 1003 Name: "container", 1004 Image: "image", 1005 ImagePullPolicy: "IfNotPresent", 1006 TerminationMessagePolicy: "File", 1007 }, 1008 }, 1009 }, 1010 }, 1011 newPod: &api.Pod{ 1012 ObjectMeta: metav1.ObjectMeta{ 1013 Name: "test-pod", 1014 Namespace: "test-ns", 1015 ResourceVersion: "1", 1016 }, 1017 Spec: api.PodSpec{ 1018 RestartPolicy: api.RestartPolicyAlways, 1019 DNSPolicy: api.DNSDefault, 1020 Containers: []api.Container{ 1021 { 1022 Name: "container", 1023 Image: "image", 1024 ImagePullPolicy: "IfNotPresent", 1025 TerminationMessagePolicy: "File", 1026 }, 1027 }, 1028 EphemeralContainers: []api.EphemeralContainer{ 1029 { 1030 EphemeralContainerCommon: api.EphemeralContainerCommon{ 1031 Name: "debugger", 1032 Image: "image", 1033 ImagePullPolicy: "IfNotPresent", 1034 TerminationMessagePolicy: "File", 1035 }, 1036 }, 1037 }, 1038 }, 1039 }, 1040 }, 1041 } 1042 1043 // expect no errors 1044 for _, tc := range test { 1045 t.Run(tc.name, func(t *testing.T) { 1046 if errs := EphemeralContainersStrategy.ValidateUpdate(genericapirequest.NewContext(), tc.newPod, tc.oldPod); len(errs) != 0 { 1047 t.Errorf("unexpected error:%v", errs) 1048 } 1049 }) 1050 } 1051 1052 test = []struct { 1053 name string 1054 newPod *api.Pod 1055 oldPod *api.Pod 1056 }{ 1057 { 1058 name: "add ephemeral container to static pod and expect failure", 1059 oldPod: &api.Pod{ 1060 ObjectMeta: metav1.ObjectMeta{ 1061 Name: "test-pod", 1062 Namespace: "test-ns", 1063 ResourceVersion: "1", 1064 Annotations: map[string]string{api.MirrorPodAnnotationKey: "someVal"}, 1065 }, 1066 Spec: api.PodSpec{ 1067 RestartPolicy: api.RestartPolicyAlways, 1068 DNSPolicy: api.DNSDefault, 1069 Containers: []api.Container{ 1070 { 1071 Name: "container", 1072 Image: "image", 1073 ImagePullPolicy: "IfNotPresent", 1074 TerminationMessagePolicy: "File", 1075 }, 1076 }, 1077 NodeName: "example.com", 1078 }, 1079 }, 1080 newPod: &api.Pod{ 1081 ObjectMeta: metav1.ObjectMeta{ 1082 Name: "test-pod", 1083 Namespace: "test-ns", 1084 ResourceVersion: "1", 1085 Annotations: map[string]string{api.MirrorPodAnnotationKey: "someVal"}, 1086 }, 1087 Spec: api.PodSpec{ 1088 RestartPolicy: api.RestartPolicyAlways, 1089 DNSPolicy: api.DNSDefault, 1090 Containers: []api.Container{ 1091 { 1092 Name: "container", 1093 Image: "image", 1094 ImagePullPolicy: "IfNotPresent", 1095 TerminationMessagePolicy: "File", 1096 }, 1097 }, 1098 EphemeralContainers: []api.EphemeralContainer{ 1099 { 1100 EphemeralContainerCommon: api.EphemeralContainerCommon{ 1101 Name: "debugger", 1102 Image: "image", 1103 ImagePullPolicy: "IfNotPresent", 1104 TerminationMessagePolicy: "File", 1105 }, 1106 }, 1107 }, 1108 NodeName: "example.com", 1109 }, 1110 }, 1111 }, 1112 { 1113 name: "remove ephemeral container from regular pod and expect failure", 1114 newPod: &api.Pod{ 1115 ObjectMeta: metav1.ObjectMeta{ 1116 Name: "test-pod", 1117 Namespace: "test-ns", 1118 ResourceVersion: "1", 1119 }, 1120 Spec: api.PodSpec{ 1121 RestartPolicy: api.RestartPolicyAlways, 1122 DNSPolicy: api.DNSDefault, 1123 Containers: []api.Container{ 1124 { 1125 Name: "container", 1126 Image: "image", 1127 ImagePullPolicy: "IfNotPresent", 1128 TerminationMessagePolicy: "File", 1129 }, 1130 }, 1131 }, 1132 }, 1133 oldPod: &api.Pod{ 1134 ObjectMeta: metav1.ObjectMeta{ 1135 Name: "test-pod", 1136 Namespace: "test-ns", 1137 ResourceVersion: "1", 1138 }, 1139 Spec: api.PodSpec{ 1140 RestartPolicy: api.RestartPolicyAlways, 1141 DNSPolicy: api.DNSDefault, 1142 Containers: []api.Container{ 1143 { 1144 Name: "container", 1145 Image: "image", 1146 ImagePullPolicy: "IfNotPresent", 1147 TerminationMessagePolicy: "File", 1148 }, 1149 }, 1150 EphemeralContainers: []api.EphemeralContainer{ 1151 { 1152 EphemeralContainerCommon: api.EphemeralContainerCommon{ 1153 Name: "debugger", 1154 Image: "image", 1155 ImagePullPolicy: "IfNotPresent", 1156 TerminationMessagePolicy: "File", 1157 }, 1158 }, 1159 }, 1160 }, 1161 }, 1162 }, 1163 { 1164 name: "change ephemeral container from regular pod and expect failure", 1165 newPod: &api.Pod{ 1166 ObjectMeta: metav1.ObjectMeta{ 1167 Name: "test-pod", 1168 Namespace: "test-ns", 1169 ResourceVersion: "1", 1170 }, 1171 Spec: api.PodSpec{ 1172 RestartPolicy: api.RestartPolicyAlways, 1173 DNSPolicy: api.DNSDefault, 1174 Containers: []api.Container{ 1175 { 1176 Name: "container", 1177 Image: "image", 1178 ImagePullPolicy: "IfNotPresent", 1179 TerminationMessagePolicy: "File", 1180 }, 1181 }, 1182 EphemeralContainers: []api.EphemeralContainer{ 1183 { 1184 EphemeralContainerCommon: api.EphemeralContainerCommon{ 1185 Name: "debugger", 1186 Image: "image2", 1187 ImagePullPolicy: "IfNotPresent", 1188 TerminationMessagePolicy: "File", 1189 }, 1190 }, 1191 }, 1192 }, 1193 }, 1194 oldPod: &api.Pod{ 1195 ObjectMeta: metav1.ObjectMeta{ 1196 Name: "test-pod", 1197 Namespace: "test-ns", 1198 ResourceVersion: "1", 1199 }, 1200 Spec: api.PodSpec{ 1201 RestartPolicy: api.RestartPolicyAlways, 1202 DNSPolicy: api.DNSDefault, 1203 Containers: []api.Container{ 1204 { 1205 Name: "container", 1206 Image: "image", 1207 ImagePullPolicy: "IfNotPresent", 1208 TerminationMessagePolicy: "File", 1209 }, 1210 }, 1211 EphemeralContainers: []api.EphemeralContainer{ 1212 { 1213 EphemeralContainerCommon: api.EphemeralContainerCommon{ 1214 Name: "debugger", 1215 Image: "image", 1216 ImagePullPolicy: "IfNotPresent", 1217 TerminationMessagePolicy: "File", 1218 }, 1219 }, 1220 }, 1221 }, 1222 }, 1223 }, 1224 } 1225 1226 // expect one error 1227 for _, tc := range test { 1228 t.Run(tc.name, func(t *testing.T) { 1229 errs := EphemeralContainersStrategy.ValidateUpdate(genericapirequest.NewContext(), tc.newPod, tc.oldPod) 1230 if len(errs) == 0 { 1231 t.Errorf("unexpected success:ephemeral containers are not supported for static pods") 1232 } else if len(errs) != 1 { 1233 t.Errorf("unexpected errors:expected one error about ephemeral containers are not supported for static pods:got:%v:", errs) 1234 } 1235 }) 1236 } 1237 } 1238 1239 func TestPodStrategyValidateUpdate(t *testing.T) { 1240 test := []struct { 1241 name string 1242 newPod *api.Pod 1243 oldPod *api.Pod 1244 }{ 1245 { 1246 name: "an existing pod with indivisible hugepages values to a new pod with indivisible hugepages values", 1247 newPod: newPodWithHugePageValue(api.ResourceHugePagesPrefix+"2Mi", resource.MustParse("2.1Mi")), 1248 oldPod: newPodWithHugePageValue(api.ResourceHugePagesPrefix+"2Mi", resource.MustParse("2.1Mi")), 1249 }, 1250 } 1251 1252 for _, tc := range test { 1253 t.Run(tc.name, func(t *testing.T) { 1254 if errs := Strategy.ValidateUpdate(genericapirequest.NewContext(), tc.newPod, tc.oldPod); len(errs) != 0 { 1255 t.Errorf("unexpected error:%v", errs) 1256 } 1257 }) 1258 } 1259 } 1260 1261 func TestDropNonEphemeralContainerUpdates(t *testing.T) { 1262 tests := []struct { 1263 name string 1264 oldPod, newPod, wantPod *api.Pod 1265 }{ 1266 { 1267 name: "simple ephemeral container append", 1268 oldPod: &api.Pod{ 1269 ObjectMeta: metav1.ObjectMeta{ 1270 Name: "test-pod", 1271 Namespace: "test-ns", 1272 ResourceVersion: "1", 1273 }, 1274 Spec: api.PodSpec{ 1275 Containers: []api.Container{ 1276 { 1277 Name: "container", 1278 Image: "image", 1279 }, 1280 }, 1281 }, 1282 }, 1283 newPod: &api.Pod{ 1284 ObjectMeta: metav1.ObjectMeta{ 1285 Name: "test-pod", 1286 Namespace: "test-ns", 1287 ResourceVersion: "1", 1288 }, 1289 Spec: api.PodSpec{ 1290 Containers: []api.Container{ 1291 { 1292 Name: "container", 1293 Image: "image", 1294 }, 1295 }, 1296 EphemeralContainers: []api.EphemeralContainer{ 1297 { 1298 EphemeralContainerCommon: api.EphemeralContainerCommon{ 1299 Name: "container", 1300 Image: "image", 1301 }, 1302 }, 1303 }, 1304 }, 1305 }, 1306 wantPod: &api.Pod{ 1307 ObjectMeta: metav1.ObjectMeta{ 1308 Name: "test-pod", 1309 Namespace: "test-ns", 1310 ResourceVersion: "1", 1311 }, 1312 Spec: api.PodSpec{ 1313 Containers: []api.Container{ 1314 { 1315 Name: "container", 1316 Image: "image", 1317 }, 1318 }, 1319 EphemeralContainers: []api.EphemeralContainer{ 1320 { 1321 EphemeralContainerCommon: api.EphemeralContainerCommon{ 1322 Name: "container", 1323 Image: "image", 1324 }, 1325 }, 1326 }, 1327 }, 1328 }, 1329 }, 1330 { 1331 name: "whoops wrong pod", 1332 oldPod: &api.Pod{ 1333 ObjectMeta: metav1.ObjectMeta{ 1334 Name: "test-pod", 1335 Namespace: "test-ns", 1336 ResourceVersion: "1", 1337 UID: "blue", 1338 }, 1339 }, 1340 newPod: &api.Pod{ 1341 ObjectMeta: metav1.ObjectMeta{ 1342 Name: "new-pod", 1343 Namespace: "new-ns", 1344 ResourceVersion: "1", 1345 UID: "green", 1346 }, 1347 }, 1348 wantPod: &api.Pod{ 1349 ObjectMeta: metav1.ObjectMeta{ 1350 Name: "new-pod", 1351 Namespace: "new-ns", 1352 ResourceVersion: "1", 1353 UID: "green", 1354 }, 1355 }, 1356 }, 1357 { 1358 name: "resource conflict during update", 1359 oldPod: &api.Pod{ 1360 ObjectMeta: metav1.ObjectMeta{ 1361 Name: "test-pod", 1362 Namespace: "test-ns", 1363 ResourceVersion: "2", 1364 UID: "blue", 1365 }, 1366 }, 1367 newPod: &api.Pod{ 1368 ObjectMeta: metav1.ObjectMeta{ 1369 Name: "test-pod", 1370 Namespace: "test-ns", 1371 ResourceVersion: "1", 1372 UID: "blue", 1373 }, 1374 }, 1375 wantPod: &api.Pod{ 1376 ObjectMeta: metav1.ObjectMeta{ 1377 Name: "test-pod", 1378 Namespace: "test-ns", 1379 ResourceVersion: "1", 1380 UID: "blue", 1381 }, 1382 }, 1383 }, 1384 { 1385 name: "drop non-ephemeral container changes", 1386 oldPod: &api.Pod{ 1387 ObjectMeta: metav1.ObjectMeta{ 1388 Name: "test-pod", 1389 Namespace: "test-ns", 1390 ResourceVersion: "1", 1391 Annotations: map[string]string{"foo": "bar"}, 1392 }, 1393 Spec: api.PodSpec{ 1394 Containers: []api.Container{ 1395 { 1396 Name: "container", 1397 Image: "image", 1398 }, 1399 }, 1400 }, 1401 }, 1402 newPod: &api.Pod{ 1403 ObjectMeta: metav1.ObjectMeta{ 1404 Name: "test-pod", 1405 Namespace: "test-ns", 1406 ResourceVersion: "1", 1407 Annotations: map[string]string{"foo": "bar", "whiz": "pop"}, 1408 Finalizers: []string{"milo"}, 1409 }, 1410 Spec: api.PodSpec{ 1411 Containers: []api.Container{ 1412 { 1413 Name: "container", 1414 Image: "newimage", 1415 }, 1416 }, 1417 EphemeralContainers: []api.EphemeralContainer{ 1418 { 1419 EphemeralContainerCommon: api.EphemeralContainerCommon{ 1420 Name: "container1", 1421 Image: "image", 1422 }, 1423 }, 1424 { 1425 EphemeralContainerCommon: api.EphemeralContainerCommon{ 1426 Name: "container2", 1427 Image: "image", 1428 }, 1429 }, 1430 }, 1431 }, 1432 Status: api.PodStatus{ 1433 Message: "hi.", 1434 }, 1435 }, 1436 wantPod: &api.Pod{ 1437 ObjectMeta: metav1.ObjectMeta{ 1438 Name: "test-pod", 1439 Namespace: "test-ns", 1440 ResourceVersion: "1", 1441 Annotations: map[string]string{"foo": "bar"}, 1442 }, 1443 Spec: api.PodSpec{ 1444 Containers: []api.Container{ 1445 { 1446 Name: "container", 1447 Image: "image", 1448 }, 1449 }, 1450 EphemeralContainers: []api.EphemeralContainer{ 1451 { 1452 EphemeralContainerCommon: api.EphemeralContainerCommon{ 1453 Name: "container1", 1454 Image: "image", 1455 }, 1456 }, 1457 { 1458 EphemeralContainerCommon: api.EphemeralContainerCommon{ 1459 Name: "container2", 1460 Image: "image", 1461 }, 1462 }, 1463 }, 1464 }, 1465 }, 1466 }, 1467 } 1468 1469 for _, tc := range tests { 1470 t.Run(tc.name, func(t *testing.T) { 1471 gotPod := dropNonEphemeralContainerUpdates(tc.newPod, tc.oldPod) 1472 if diff := cmp.Diff(tc.wantPod, gotPod); diff != "" { 1473 t.Errorf("unexpected diff when dropping fields (-want, +got):\n%s", diff) 1474 } 1475 }) 1476 } 1477 } 1478 1479 func TestNodeInclusionPolicyEnablementInCreating(t *testing.T) { 1480 var ( 1481 honor = api.NodeInclusionPolicyHonor 1482 ignore = api.NodeInclusionPolicyIgnore 1483 emptyConstraints = []api.TopologySpreadConstraint{ 1484 { 1485 WhenUnsatisfiable: api.DoNotSchedule, 1486 TopologyKey: "kubernetes.io/hostname", 1487 MaxSkew: 1, 1488 }, 1489 } 1490 defaultConstraints = []api.TopologySpreadConstraint{ 1491 { 1492 NodeAffinityPolicy: &honor, 1493 NodeTaintsPolicy: &ignore, 1494 WhenUnsatisfiable: api.DoNotSchedule, 1495 TopologyKey: "kubernetes.io/hostname", 1496 MaxSkew: 1, 1497 }, 1498 } 1499 ) 1500 1501 tests := []struct { 1502 name string 1503 topologySpreadConstraints []api.TopologySpreadConstraint 1504 wantTopologySpreadConstraints []api.TopologySpreadConstraint 1505 enableNodeInclusionPolicy bool 1506 }{ 1507 { 1508 name: "nodeInclusionPolicy enabled with topology unset", 1509 topologySpreadConstraints: emptyConstraints, 1510 wantTopologySpreadConstraints: emptyConstraints, 1511 enableNodeInclusionPolicy: true, 1512 }, 1513 { 1514 name: "nodeInclusionPolicy enabled with topology configured", 1515 topologySpreadConstraints: defaultConstraints, 1516 wantTopologySpreadConstraints: defaultConstraints, 1517 enableNodeInclusionPolicy: true, 1518 }, 1519 { 1520 name: "nodeInclusionPolicy disabled with topology configured", 1521 topologySpreadConstraints: defaultConstraints, 1522 wantTopologySpreadConstraints: emptyConstraints, 1523 }, 1524 } 1525 1526 for _, tc := range tests { 1527 t.Run(tc.name, func(t *testing.T) { 1528 featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.NodeInclusionPolicyInPodTopologySpread, tc.enableNodeInclusionPolicy) 1529 1530 pod := &api.Pod{ 1531 ObjectMeta: metav1.ObjectMeta{ 1532 Namespace: "default", 1533 Name: "foo", 1534 }, 1535 Spec: api.PodSpec{ 1536 RestartPolicy: api.RestartPolicyAlways, 1537 DNSPolicy: api.DNSDefault, 1538 Containers: []api.Container{ 1539 { 1540 Name: "container", 1541 Image: "image", 1542 ImagePullPolicy: "IfNotPresent", 1543 TerminationMessagePolicy: "File", 1544 }, 1545 }, 1546 }, 1547 } 1548 wantPod := pod.DeepCopy() 1549 pod.Spec.TopologySpreadConstraints = append(pod.Spec.TopologySpreadConstraints, tc.topologySpreadConstraints...) 1550 1551 errs := Strategy.Validate(genericapirequest.NewContext(), pod) 1552 if len(errs) != 0 { 1553 t.Errorf("Unexpected error: %v", errs.ToAggregate()) 1554 } 1555 1556 Strategy.PrepareForCreate(genericapirequest.NewContext(), pod) 1557 wantPod.Spec.TopologySpreadConstraints = append(wantPod.Spec.TopologySpreadConstraints, tc.wantTopologySpreadConstraints...) 1558 if diff := cmp.Diff(wantPod, pod, cmpopts.IgnoreFields(pod.Status, "Phase", "QOSClass")); diff != "" { 1559 t.Errorf("%s unexpected result (-want, +got): %s", tc.name, diff) 1560 } 1561 }) 1562 } 1563 } 1564 1565 func TestNodeInclusionPolicyEnablementInUpdating(t *testing.T) { 1566 var ( 1567 honor = api.NodeInclusionPolicyHonor 1568 ignore = api.NodeInclusionPolicyIgnore 1569 ) 1570 1571 // Enable the Feature Gate during the first rule creation 1572 featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.NodeInclusionPolicyInPodTopologySpread, true) 1573 ctx := genericapirequest.NewDefaultContext() 1574 1575 pod := &api.Pod{ 1576 ObjectMeta: metav1.ObjectMeta{ 1577 Namespace: "default", 1578 Name: "foo", 1579 ResourceVersion: "1", 1580 }, 1581 Spec: api.PodSpec{ 1582 RestartPolicy: api.RestartPolicyAlways, 1583 DNSPolicy: api.DNSDefault, 1584 Containers: []api.Container{ 1585 { 1586 Name: "container", 1587 Image: "image", 1588 ImagePullPolicy: "IfNotPresent", 1589 TerminationMessagePolicy: "File", 1590 }, 1591 }, 1592 TopologySpreadConstraints: []api.TopologySpreadConstraint{ 1593 { 1594 NodeAffinityPolicy: &ignore, 1595 NodeTaintsPolicy: &honor, 1596 WhenUnsatisfiable: api.DoNotSchedule, 1597 TopologyKey: "kubernetes.io/hostname", 1598 MaxSkew: 1, 1599 }, 1600 }, 1601 }, 1602 } 1603 1604 errs := Strategy.Validate(ctx, pod) 1605 if len(errs) != 0 { 1606 t.Errorf("Unexpected error: %v", errs.ToAggregate()) 1607 } 1608 1609 createdPod := pod.DeepCopy() 1610 Strategy.PrepareForCreate(ctx, createdPod) 1611 1612 if len(createdPod.Spec.TopologySpreadConstraints) != 1 || 1613 *createdPod.Spec.TopologySpreadConstraints[0].NodeAffinityPolicy != ignore || 1614 *createdPod.Spec.TopologySpreadConstraints[0].NodeTaintsPolicy != honor { 1615 t.Error("NodeInclusionPolicy created with unexpected result") 1616 } 1617 1618 // Disable the Feature Gate and expect these fields still exist after updating. 1619 featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.NodeInclusionPolicyInPodTopologySpread, false) 1620 1621 updatedPod := createdPod.DeepCopy() 1622 updatedPod.Labels = map[string]string{"foo": "bar"} 1623 updatedPod.ResourceVersion = "2" 1624 1625 errs = Strategy.ValidateUpdate(ctx, updatedPod, createdPod) 1626 if len(errs) != 0 { 1627 t.Errorf("Unexpected error: %v", errs.ToAggregate()) 1628 } 1629 1630 Strategy.PrepareForUpdate(ctx, updatedPod, createdPod) 1631 1632 if len(updatedPod.Spec.TopologySpreadConstraints) != 1 || 1633 *updatedPod.Spec.TopologySpreadConstraints[0].NodeAffinityPolicy != ignore || 1634 *updatedPod.Spec.TopologySpreadConstraints[0].NodeTaintsPolicy != honor { 1635 t.Error("NodeInclusionPolicy updated with unexpected result") 1636 } 1637 1638 // Enable the Feature Gate again to check whether configured fields still exist after updating. 1639 featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.NodeInclusionPolicyInPodTopologySpread, true) 1640 1641 updatedPod2 := updatedPod.DeepCopy() 1642 updatedPod2.Labels = map[string]string{"foo": "fuz"} 1643 updatedPod2.ResourceVersion = "3" 1644 1645 errs = Strategy.ValidateUpdate(ctx, updatedPod2, updatedPod) 1646 if len(errs) != 0 { 1647 t.Errorf("Unexpected error: %v", errs.ToAggregate()) 1648 } 1649 1650 Strategy.PrepareForUpdate(ctx, updatedPod2, updatedPod) 1651 if len(updatedPod2.Spec.TopologySpreadConstraints) != 1 || 1652 *updatedPod2.Spec.TopologySpreadConstraints[0].NodeAffinityPolicy != ignore || 1653 *updatedPod2.Spec.TopologySpreadConstraints[0].NodeTaintsPolicy != honor { 1654 t.Error("NodeInclusionPolicy updated with unexpected result") 1655 } 1656 } 1657 1658 func Test_mutatePodAffinity(t *testing.T) { 1659 tests := []struct { 1660 name string 1661 pod *api.Pod 1662 wantPod *api.Pod 1663 featureGateEnabled bool 1664 }{ 1665 { 1666 name: "matchLabelKeys are merged into labelSelector with In and mismatchLabelKeys are merged with NotIn", 1667 featureGateEnabled: true, 1668 pod: &api.Pod{ 1669 ObjectMeta: metav1.ObjectMeta{ 1670 Labels: map[string]string{ 1671 "country": "Japan", 1672 "city": "Kyoto", 1673 }, 1674 }, 1675 Spec: api.PodSpec{ 1676 Affinity: &api.Affinity{ 1677 PodAffinity: &api.PodAffinity{ 1678 RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{ 1679 { 1680 LabelSelector: &metav1.LabelSelector{ 1681 MatchLabels: map[string]string{ 1682 "region": "Asia", 1683 }, 1684 }, 1685 MatchLabelKeys: []string{"country"}, 1686 MismatchLabelKeys: []string{"city"}, 1687 }, 1688 }, 1689 PreferredDuringSchedulingIgnoredDuringExecution: []api.WeightedPodAffinityTerm{ 1690 { 1691 PodAffinityTerm: api.PodAffinityTerm{ 1692 LabelSelector: &metav1.LabelSelector{ 1693 MatchLabels: map[string]string{ 1694 "region": "Asia", 1695 }, 1696 }, 1697 MatchLabelKeys: []string{"country"}, 1698 MismatchLabelKeys: []string{"city"}, 1699 }, 1700 }, 1701 }, 1702 }, 1703 PodAntiAffinity: &api.PodAntiAffinity{ 1704 RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{ 1705 { 1706 LabelSelector: &metav1.LabelSelector{ 1707 MatchLabels: map[string]string{ 1708 "region": "Asia", 1709 }, 1710 }, 1711 MatchLabelKeys: []string{"country"}, 1712 MismatchLabelKeys: []string{"city"}, 1713 }, 1714 }, 1715 PreferredDuringSchedulingIgnoredDuringExecution: []api.WeightedPodAffinityTerm{ 1716 { 1717 PodAffinityTerm: api.PodAffinityTerm{ 1718 LabelSelector: &metav1.LabelSelector{ 1719 MatchLabels: map[string]string{ 1720 "region": "Asia", 1721 }, 1722 }, 1723 MatchLabelKeys: []string{"country"}, 1724 MismatchLabelKeys: []string{"city"}, 1725 }, 1726 }, 1727 }, 1728 }, 1729 }, 1730 }, 1731 }, 1732 wantPod: &api.Pod{ 1733 ObjectMeta: metav1.ObjectMeta{ 1734 Labels: map[string]string{ 1735 "country": "Japan", 1736 "city": "Kyoto", 1737 }, 1738 }, 1739 Spec: api.PodSpec{ 1740 Affinity: &api.Affinity{ 1741 PodAffinity: &api.PodAffinity{ 1742 RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{ 1743 { 1744 LabelSelector: &metav1.LabelSelector{ 1745 MatchLabels: map[string]string{ 1746 "region": "Asia", 1747 }, 1748 MatchExpressions: []metav1.LabelSelectorRequirement{ 1749 { 1750 Key: "country", 1751 Operator: metav1.LabelSelectorOpIn, 1752 Values: []string{"Japan"}, 1753 }, 1754 { 1755 Key: "city", 1756 Operator: metav1.LabelSelectorOpNotIn, 1757 Values: []string{"Kyoto"}, 1758 }, 1759 }, 1760 }, 1761 MatchLabelKeys: []string{"country"}, 1762 MismatchLabelKeys: []string{"city"}, 1763 }, 1764 }, 1765 PreferredDuringSchedulingIgnoredDuringExecution: []api.WeightedPodAffinityTerm{ 1766 { 1767 PodAffinityTerm: api.PodAffinityTerm{ 1768 LabelSelector: &metav1.LabelSelector{ 1769 MatchLabels: map[string]string{ 1770 "region": "Asia", 1771 }, 1772 MatchExpressions: []metav1.LabelSelectorRequirement{ 1773 { 1774 Key: "country", 1775 Operator: metav1.LabelSelectorOpIn, 1776 Values: []string{"Japan"}, 1777 }, 1778 { 1779 Key: "city", 1780 Operator: metav1.LabelSelectorOpNotIn, 1781 Values: []string{"Kyoto"}, 1782 }, 1783 }, 1784 }, 1785 MatchLabelKeys: []string{"country"}, 1786 MismatchLabelKeys: []string{"city"}, 1787 }, 1788 }, 1789 }, 1790 }, 1791 PodAntiAffinity: &api.PodAntiAffinity{ 1792 RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{ 1793 { 1794 LabelSelector: &metav1.LabelSelector{ 1795 MatchLabels: map[string]string{ 1796 "region": "Asia", 1797 }, 1798 MatchExpressions: []metav1.LabelSelectorRequirement{ 1799 { 1800 Key: "country", 1801 Operator: metav1.LabelSelectorOpIn, 1802 Values: []string{"Japan"}, 1803 }, 1804 { 1805 Key: "city", 1806 Operator: metav1.LabelSelectorOpNotIn, 1807 Values: []string{"Kyoto"}, 1808 }, 1809 }, 1810 }, 1811 MatchLabelKeys: []string{"country"}, 1812 MismatchLabelKeys: []string{"city"}, 1813 }, 1814 }, 1815 PreferredDuringSchedulingIgnoredDuringExecution: []api.WeightedPodAffinityTerm{ 1816 { 1817 PodAffinityTerm: api.PodAffinityTerm{ 1818 LabelSelector: &metav1.LabelSelector{ 1819 MatchLabels: map[string]string{ 1820 "region": "Asia", 1821 }, 1822 MatchExpressions: []metav1.LabelSelectorRequirement{ 1823 { 1824 Key: "country", 1825 Operator: metav1.LabelSelectorOpIn, 1826 Values: []string{"Japan"}, 1827 }, 1828 { 1829 Key: "city", 1830 Operator: metav1.LabelSelectorOpNotIn, 1831 Values: []string{"Kyoto"}, 1832 }, 1833 }, 1834 }, 1835 MatchLabelKeys: []string{"country"}, 1836 MismatchLabelKeys: []string{"city"}, 1837 }, 1838 }, 1839 }, 1840 }, 1841 }, 1842 }, 1843 }, 1844 }, 1845 { 1846 name: "keys, which are not found in Pod labels, are ignored", 1847 featureGateEnabled: true, 1848 pod: &api.Pod{ 1849 ObjectMeta: metav1.ObjectMeta{ 1850 Labels: map[string]string{ 1851 "country": "Japan", 1852 "city": "Kyoto", 1853 }, 1854 }, 1855 Spec: api.PodSpec{ 1856 Affinity: &api.Affinity{ 1857 PodAffinity: &api.PodAffinity{ 1858 RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{ 1859 { 1860 LabelSelector: &metav1.LabelSelector{ 1861 MatchLabels: map[string]string{ 1862 "region": "Asia", 1863 }, 1864 }, 1865 MatchLabelKeys: []string{"city", "not-found"}, 1866 }, 1867 }, 1868 }, 1869 }, 1870 }, 1871 }, 1872 wantPod: &api.Pod{ 1873 ObjectMeta: metav1.ObjectMeta{ 1874 Labels: map[string]string{ 1875 "country": "Japan", 1876 "city": "Kyoto", 1877 }, 1878 }, 1879 Spec: api.PodSpec{ 1880 Affinity: &api.Affinity{ 1881 PodAffinity: &api.PodAffinity{ 1882 RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{ 1883 { 1884 LabelSelector: &metav1.LabelSelector{ 1885 MatchLabels: map[string]string{ 1886 "region": "Asia", 1887 }, 1888 MatchExpressions: []metav1.LabelSelectorRequirement{ 1889 // "city" should be added correctly even if matchLabelKey has "not-found" key. 1890 { 1891 Key: "city", 1892 Operator: metav1.LabelSelectorOpIn, 1893 Values: []string{"Kyoto"}, 1894 }, 1895 }, 1896 }, 1897 MatchLabelKeys: []string{"city", "not-found"}, 1898 }, 1899 }, 1900 }, 1901 }, 1902 }, 1903 }, 1904 }, 1905 { 1906 name: "matchLabelKeys is ignored if the labelSelector is nil", 1907 featureGateEnabled: true, 1908 pod: &api.Pod{ 1909 ObjectMeta: metav1.ObjectMeta{ 1910 Labels: map[string]string{ 1911 "country": "Japan", 1912 "city": "Kyoto", 1913 }, 1914 }, 1915 Spec: api.PodSpec{ 1916 Affinity: &api.Affinity{ 1917 PodAffinity: &api.PodAffinity{ 1918 RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{ 1919 { 1920 MatchLabelKeys: []string{"country"}, 1921 MismatchLabelKeys: []string{"city"}, 1922 }, 1923 }, 1924 }, 1925 }, 1926 }, 1927 }, 1928 wantPod: &api.Pod{ 1929 ObjectMeta: metav1.ObjectMeta{ 1930 Labels: map[string]string{ 1931 "country": "Japan", 1932 "city": "Kyoto", 1933 }, 1934 }, 1935 Spec: api.PodSpec{ 1936 Affinity: &api.Affinity{ 1937 PodAffinity: &api.PodAffinity{ 1938 RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{ 1939 { 1940 MatchLabelKeys: []string{"country"}, 1941 MismatchLabelKeys: []string{"city"}, 1942 }, 1943 }, 1944 }, 1945 }, 1946 }, 1947 }, 1948 }, 1949 { 1950 name: "the feature gate is disabled and matchLabelKeys is ignored", 1951 pod: &api.Pod{ 1952 ObjectMeta: metav1.ObjectMeta{ 1953 Labels: map[string]string{ 1954 "country": "Japan", 1955 "city": "Kyoto", 1956 }, 1957 }, 1958 Spec: api.PodSpec{ 1959 Affinity: &api.Affinity{ 1960 PodAffinity: &api.PodAffinity{ 1961 RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{ 1962 { 1963 LabelSelector: &metav1.LabelSelector{ 1964 MatchLabels: map[string]string{ 1965 "region": "Asia", 1966 }, 1967 }, 1968 MatchLabelKeys: []string{"country"}, 1969 MismatchLabelKeys: []string{"city"}, 1970 }, 1971 }, 1972 }, 1973 }, 1974 }, 1975 }, 1976 wantPod: &api.Pod{ 1977 ObjectMeta: metav1.ObjectMeta{ 1978 Labels: map[string]string{ 1979 "country": "Japan", 1980 "city": "Kyoto", 1981 }, 1982 }, 1983 Spec: api.PodSpec{ 1984 Affinity: &api.Affinity{ 1985 PodAffinity: &api.PodAffinity{ 1986 RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{ 1987 { 1988 LabelSelector: &metav1.LabelSelector{ 1989 MatchLabels: map[string]string{ 1990 "region": "Asia", 1991 }, 1992 }, 1993 MatchLabelKeys: []string{"country"}, 1994 MismatchLabelKeys: []string{"city"}, 1995 }, 1996 }, 1997 }, 1998 }, 1999 }, 2000 }, 2001 }, 2002 } 2003 2004 for _, tc := range tests { 2005 t.Run(tc.name, func(t *testing.T) { 2006 featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.MatchLabelKeysInPodAffinity, tc.featureGateEnabled) 2007 2008 pod := tc.pod 2009 mutatePodAffinity(pod) 2010 if diff := cmp.Diff(tc.wantPod.Spec.Affinity, pod.Spec.Affinity); diff != "" { 2011 t.Errorf("unexpected affinity (-want, +got): %s\n", diff) 2012 } 2013 }) 2014 } 2015 } 2016 2017 func TestPodLifecycleSleepActionEnablement(t *testing.T) { 2018 getLifecycle := func(pod *api.Pod) *api.Lifecycle { 2019 return pod.Spec.Containers[0].Lifecycle 2020 } 2021 2022 defaultTerminationGracePeriodSeconds := int64(30) 2023 2024 podWithHandler := func() *api.Pod { 2025 return &api.Pod{ 2026 ObjectMeta: metav1.ObjectMeta{ 2027 Namespace: "default", 2028 Name: "foo", 2029 ResourceVersion: "1", 2030 }, 2031 Spec: api.PodSpec{ 2032 RestartPolicy: api.RestartPolicyAlways, 2033 DNSPolicy: api.DNSDefault, 2034 Containers: []api.Container{ 2035 { 2036 Name: "container", 2037 Image: "image", 2038 ImagePullPolicy: "IfNotPresent", 2039 TerminationMessagePolicy: "File", 2040 Lifecycle: &api.Lifecycle{ 2041 PreStop: &api.LifecycleHandler{ 2042 Sleep: &api.SleepAction{Seconds: 1}, 2043 }, 2044 }, 2045 }, 2046 }, 2047 TerminationGracePeriodSeconds: &defaultTerminationGracePeriodSeconds, 2048 }, 2049 } 2050 } 2051 2052 podWithoutHandler := func() *api.Pod { 2053 return &api.Pod{ 2054 ObjectMeta: metav1.ObjectMeta{ 2055 Namespace: "default", 2056 Name: "foo", 2057 ResourceVersion: "1", 2058 }, 2059 Spec: api.PodSpec{ 2060 RestartPolicy: api.RestartPolicyAlways, 2061 DNSPolicy: api.DNSDefault, 2062 Containers: []api.Container{ 2063 { 2064 Name: "container", 2065 Image: "image", 2066 ImagePullPolicy: "IfNotPresent", 2067 TerminationMessagePolicy: "File", 2068 }, 2069 }, 2070 TerminationGracePeriodSeconds: &defaultTerminationGracePeriodSeconds, 2071 }, 2072 } 2073 } 2074 2075 testCases := []struct { 2076 description string 2077 gateEnabled bool 2078 newPod *api.Pod 2079 wantPod *api.Pod 2080 }{ 2081 { 2082 description: "gate enabled, creating pods with sleep action", 2083 gateEnabled: true, 2084 newPod: podWithHandler(), 2085 wantPod: podWithHandler(), 2086 }, 2087 { 2088 description: "gate disabled, creating pods with sleep action", 2089 gateEnabled: false, 2090 newPod: podWithHandler(), 2091 wantPod: podWithoutHandler(), 2092 }, 2093 } 2094 2095 for _, tc := range testCases { 2096 t.Run(tc.description, func(t *testing.T) { 2097 featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PodLifecycleSleepAction, tc.gateEnabled) 2098 2099 newPod := tc.newPod 2100 2101 Strategy.PrepareForCreate(genericapirequest.NewContext(), newPod) 2102 if errs := Strategy.Validate(genericapirequest.NewContext(), newPod); len(errs) != 0 { 2103 t.Errorf("Unexpected error: %v", errs.ToAggregate()) 2104 } 2105 2106 if diff := cmp.Diff(getLifecycle(newPod), getLifecycle(tc.wantPod)); diff != "" { 2107 t.Fatalf("Unexpected modification to life cycle; diff (-got +want)\n%s", diff) 2108 } 2109 }) 2110 } 2111 } 2112 2113 func TestApplyAppArmorVersionSkew(t *testing.T) { 2114 testProfile := "test" 2115 2116 tests := []struct { 2117 description string 2118 pod *api.Pod 2119 validation func(*testing.T, *api.Pod) 2120 expectWarning bool 2121 }{{ 2122 description: "Security context nil", 2123 pod: &api.Pod{ 2124 Spec: api.PodSpec{ 2125 InitContainers: []api.Container{{Name: "init"}}, 2126 Containers: []api.Container{{Name: "ctr"}}, 2127 }, 2128 }, 2129 validation: func(t *testing.T, pod *api.Pod) { 2130 assert.Empty(t, pod.Annotations) 2131 assert.Nil(t, pod.Spec.SecurityContext) 2132 }, 2133 }, { 2134 description: "Security context not nil", 2135 pod: &api.Pod{ 2136 Spec: api.PodSpec{ 2137 SecurityContext: &api.PodSecurityContext{}, 2138 InitContainers: []api.Container{{Name: "init"}}, 2139 Containers: []api.Container{{Name: "ctr"}}, 2140 }, 2141 }, 2142 validation: func(t *testing.T, pod *api.Pod) { 2143 assert.Empty(t, pod.Annotations) 2144 assert.Nil(t, pod.Spec.SecurityContext.AppArmorProfile) 2145 }, 2146 }, { 2147 description: "Pod field unconfined and no annotation present", 2148 pod: &api.Pod{ 2149 Spec: api.PodSpec{ 2150 SecurityContext: &api.PodSecurityContext{ 2151 AppArmorProfile: &api.AppArmorProfile{ 2152 Type: api.AppArmorProfileTypeUnconfined, 2153 }, 2154 }, 2155 InitContainers: []api.Container{{Name: "init"}}, 2156 Containers: []api.Container{{Name: "ctr"}}, 2157 }, 2158 }, 2159 validation: func(t *testing.T, pod *api.Pod) { 2160 assert.Equal(t, map[string]string{ 2161 api.DeprecatedAppArmorAnnotationKeyPrefix + "init": api.DeprecatedAppArmorAnnotationValueUnconfined, 2162 api.DeprecatedAppArmorAnnotationKeyPrefix + "ctr": api.DeprecatedAppArmorAnnotationValueUnconfined, 2163 }, pod.Annotations) 2164 }, 2165 }, { 2166 description: "Pod field default and no annotation present", 2167 pod: &api.Pod{ 2168 Spec: api.PodSpec{ 2169 SecurityContext: &api.PodSecurityContext{ 2170 AppArmorProfile: &api.AppArmorProfile{ 2171 Type: api.AppArmorProfileTypeRuntimeDefault, 2172 }, 2173 }, 2174 InitContainers: []api.Container{{Name: "init"}}, 2175 Containers: []api.Container{{Name: "ctr"}}, 2176 }, 2177 }, 2178 validation: func(t *testing.T, pod *api.Pod) { 2179 assert.Equal(t, map[string]string{ 2180 api.DeprecatedAppArmorAnnotationKeyPrefix + "init": api.DeprecatedAppArmorAnnotationValueRuntimeDefault, 2181 api.DeprecatedAppArmorAnnotationKeyPrefix + "ctr": api.DeprecatedAppArmorAnnotationValueRuntimeDefault, 2182 }, pod.Annotations) 2183 }, 2184 }, { 2185 description: "Pod field localhost and no annotation present", 2186 pod: &api.Pod{ 2187 Spec: api.PodSpec{ 2188 SecurityContext: &api.PodSecurityContext{ 2189 AppArmorProfile: &api.AppArmorProfile{ 2190 Type: api.AppArmorProfileTypeLocalhost, 2191 LocalhostProfile: &testProfile, 2192 }, 2193 }, 2194 InitContainers: []api.Container{{Name: "init"}}, 2195 Containers: []api.Container{{Name: "ctr"}}, 2196 }, 2197 }, 2198 validation: func(t *testing.T, pod *api.Pod) { 2199 assert.Equal(t, map[string]string{ 2200 api.DeprecatedAppArmorAnnotationKeyPrefix + "init": api.DeprecatedAppArmorAnnotationValueLocalhostPrefix + testProfile, 2201 api.DeprecatedAppArmorAnnotationKeyPrefix + "ctr": api.DeprecatedAppArmorAnnotationValueLocalhostPrefix + testProfile, 2202 }, pod.Annotations) 2203 }, 2204 }, { 2205 description: "Pod field localhost but profile is nil", 2206 pod: &api.Pod{ 2207 Spec: api.PodSpec{ 2208 SecurityContext: &api.PodSecurityContext{ 2209 AppArmorProfile: &api.AppArmorProfile{ 2210 Type: api.AppArmorProfileTypeLocalhost, 2211 }, 2212 }, 2213 InitContainers: []api.Container{{Name: "init"}}, 2214 Containers: []api.Container{{Name: "ctr"}}, 2215 }, 2216 }, 2217 validation: func(t *testing.T, pod *api.Pod) { 2218 assert.Len(t, pod.Annotations, 0) 2219 }, 2220 }, { 2221 description: "Container security context not nil", 2222 pod: &api.Pod{ 2223 Spec: api.PodSpec{ 2224 Containers: []api.Container{{ 2225 Name: "ctr", 2226 SecurityContext: &api.SecurityContext{}, 2227 }}, 2228 }, 2229 }, 2230 validation: func(t *testing.T, pod *api.Pod) { 2231 assert.Len(t, pod.Annotations, 0) 2232 }, 2233 }, { 2234 description: "Container field RuntimeDefault and no annotation present", 2235 pod: &api.Pod{ 2236 Spec: api.PodSpec{ 2237 Containers: []api.Container{{ 2238 Name: "ctr", 2239 SecurityContext: &api.SecurityContext{ 2240 AppArmorProfile: &api.AppArmorProfile{ 2241 Type: api.AppArmorProfileTypeRuntimeDefault, 2242 }, 2243 }, 2244 }}, 2245 }, 2246 }, 2247 validation: func(t *testing.T, pod *api.Pod) { 2248 assert.Equal(t, map[string]string{ 2249 api.DeprecatedAppArmorAnnotationKeyPrefix + "ctr": api.DeprecatedAppArmorAnnotationValueRuntimeDefault, 2250 }, pod.Annotations) 2251 assert.Nil(t, pod.Spec.SecurityContext) 2252 assert.Equal(t, api.AppArmorProfileTypeRuntimeDefault, pod.Spec.Containers[0].SecurityContext.AppArmorProfile.Type) 2253 }, 2254 }, { 2255 description: "Container field localhost and no annotation present", 2256 pod: &api.Pod{ 2257 Spec: api.PodSpec{ 2258 Containers: []api.Container{{ 2259 Name: "ctr", 2260 SecurityContext: &api.SecurityContext{ 2261 AppArmorProfile: &api.AppArmorProfile{ 2262 Type: api.AppArmorProfileTypeLocalhost, 2263 LocalhostProfile: &testProfile, 2264 }, 2265 }, 2266 }}, 2267 }, 2268 }, 2269 validation: func(t *testing.T, pod *api.Pod) { 2270 assert.Equal(t, map[string]string{ 2271 api.DeprecatedAppArmorAnnotationKeyPrefix + "ctr": api.DeprecatedAppArmorAnnotationValueLocalhostPrefix + testProfile, 2272 }, pod.Annotations) 2273 assert.Nil(t, pod.Spec.SecurityContext) 2274 assert.Equal(t, api.AppArmorProfileTypeLocalhost, pod.Spec.Containers[0].SecurityContext.AppArmorProfile.Type) 2275 }, 2276 }, { 2277 description: "Container overrides pod profile", 2278 pod: &api.Pod{ 2279 Spec: api.PodSpec{ 2280 SecurityContext: &api.PodSecurityContext{ 2281 AppArmorProfile: &api.AppArmorProfile{ 2282 Type: api.AppArmorProfileTypeRuntimeDefault, 2283 }, 2284 }, 2285 Containers: []api.Container{{ 2286 Name: "ctr", 2287 SecurityContext: &api.SecurityContext{ 2288 AppArmorProfile: &api.AppArmorProfile{ 2289 Type: api.AppArmorProfileTypeUnconfined, 2290 }, 2291 }, 2292 }}, 2293 }, 2294 }, 2295 validation: func(t *testing.T, pod *api.Pod) { 2296 assert.Equal(t, map[string]string{ 2297 api.DeprecatedAppArmorAnnotationKeyPrefix + "ctr": api.DeprecatedAppArmorAnnotationValueUnconfined, 2298 }, pod.Annotations) 2299 assert.Equal(t, api.AppArmorProfileTypeRuntimeDefault, pod.Spec.SecurityContext.AppArmorProfile.Type) 2300 assert.Equal(t, api.AppArmorProfileTypeUnconfined, pod.Spec.Containers[0].SecurityContext.AppArmorProfile.Type) 2301 }, 2302 }, { 2303 description: "Multiple containers with fields (container)", 2304 pod: &api.Pod{ 2305 Spec: api.PodSpec{ 2306 InitContainers: []api.Container{{ 2307 Name: "init", 2308 SecurityContext: &api.SecurityContext{ 2309 AppArmorProfile: &api.AppArmorProfile{ 2310 Type: api.AppArmorProfileTypeLocalhost, 2311 LocalhostProfile: &testProfile, 2312 }, 2313 }, 2314 }}, 2315 Containers: []api.Container{{ 2316 Name: "a", 2317 SecurityContext: &api.SecurityContext{ 2318 AppArmorProfile: &api.AppArmorProfile{ 2319 Type: api.AppArmorProfileTypeUnconfined, 2320 }, 2321 }, 2322 }, { 2323 Name: "b", 2324 }, { 2325 Name: "c", 2326 SecurityContext: &api.SecurityContext{ 2327 AppArmorProfile: &api.AppArmorProfile{ 2328 Type: api.AppArmorProfileTypeRuntimeDefault, 2329 }, 2330 }, 2331 }}, 2332 }, 2333 }, 2334 validation: func(t *testing.T, pod *api.Pod) { 2335 assert.Equal(t, map[string]string{ 2336 api.DeprecatedAppArmorAnnotationKeyPrefix + "init": api.DeprecatedAppArmorAnnotationValueLocalhostPrefix + testProfile, 2337 api.DeprecatedAppArmorAnnotationKeyPrefix + "a": api.DeprecatedAppArmorAnnotationValueUnconfined, 2338 api.DeprecatedAppArmorAnnotationKeyPrefix + "c": api.DeprecatedAppArmorAnnotationValueRuntimeDefault, 2339 }, pod.Annotations) 2340 assert.Nil(t, pod.Spec.SecurityContext) 2341 assert.Equal(t, api.AppArmorProfileTypeLocalhost, pod.Spec.InitContainers[0].SecurityContext.AppArmorProfile.Type) 2342 assert.Equal(t, api.AppArmorProfileTypeUnconfined, pod.Spec.Containers[0].SecurityContext.AppArmorProfile.Type) 2343 assert.Nil(t, pod.Spec.Containers[1].SecurityContext) 2344 assert.Equal(t, api.AppArmorProfileTypeRuntimeDefault, pod.Spec.Containers[2].SecurityContext.AppArmorProfile.Type) 2345 }, 2346 }, { 2347 description: "Annotation 'unconfined' and no fields present", 2348 pod: &api.Pod{ 2349 ObjectMeta: metav1.ObjectMeta{ 2350 Annotations: map[string]string{ 2351 api.DeprecatedAppArmorAnnotationKeyPrefix + "ctr": api.DeprecatedAppArmorAnnotationValueUnconfined, 2352 }, 2353 }, 2354 Spec: api.PodSpec{ 2355 Containers: []api.Container{{Name: "ctr"}}, 2356 }, 2357 }, 2358 validation: func(t *testing.T, pod *api.Pod) { 2359 assert.Equal(t, map[string]string{ 2360 api.DeprecatedAppArmorAnnotationKeyPrefix + "ctr": api.DeprecatedAppArmorAnnotationValueUnconfined, 2361 }, pod.Annotations) 2362 assert.Equal(t, api.AppArmorProfileTypeUnconfined, pod.Spec.Containers[0].SecurityContext.AppArmorProfile.Type) 2363 assert.Nil(t, pod.Spec.Containers[0].SecurityContext.AppArmorProfile.LocalhostProfile) 2364 assert.Nil(t, pod.Spec.SecurityContext) 2365 }, 2366 expectWarning: true, 2367 }, { 2368 description: "Annotation for non-existent container", 2369 pod: &api.Pod{ 2370 ObjectMeta: metav1.ObjectMeta{ 2371 Annotations: map[string]string{ 2372 api.DeprecatedAppArmorAnnotationKeyPrefix + "foo-bar": api.DeprecatedAppArmorAnnotationValueUnconfined, 2373 }, 2374 }, 2375 Spec: api.PodSpec{ 2376 Containers: []api.Container{{Name: "ctr"}}, 2377 }, 2378 }, 2379 validation: func(t *testing.T, pod *api.Pod) { 2380 assert.Equal(t, map[string]string{ 2381 api.DeprecatedAppArmorAnnotationKeyPrefix + "foo-bar": api.DeprecatedAppArmorAnnotationValueUnconfined, 2382 }, pod.Annotations) 2383 assert.Nil(t, pod.Spec.Containers[0].SecurityContext) 2384 assert.Nil(t, pod.Spec.SecurityContext) 2385 }, 2386 }, { 2387 description: "Annotation 'runtime/default' and no fields present", 2388 pod: &api.Pod{ 2389 ObjectMeta: metav1.ObjectMeta{ 2390 Annotations: map[string]string{ 2391 api.DeprecatedAppArmorAnnotationKeyPrefix + "ctr": api.DeprecatedAppArmorAnnotationValueRuntimeDefault, 2392 }, 2393 }, 2394 Spec: api.PodSpec{ 2395 SecurityContext: &api.PodSecurityContext{ 2396 AppArmorProfile: &api.AppArmorProfile{ 2397 Type: api.AppArmorProfileTypeUnconfined, 2398 }, 2399 }, 2400 Containers: []api.Container{{ 2401 Name: "ctr", 2402 SecurityContext: &api.SecurityContext{}, 2403 }}, 2404 }, 2405 }, 2406 validation: func(t *testing.T, pod *api.Pod) { 2407 assert.Equal(t, map[string]string{ 2408 api.DeprecatedAppArmorAnnotationKeyPrefix + "ctr": api.DeprecatedAppArmorAnnotationValueRuntimeDefault, 2409 }, pod.Annotations) 2410 assert.Equal(t, api.AppArmorProfileTypeRuntimeDefault, pod.Spec.Containers[0].SecurityContext.AppArmorProfile.Type) 2411 assert.Nil(t, pod.Spec.Containers[0].SecurityContext.AppArmorProfile.LocalhostProfile) 2412 assert.Equal(t, api.AppArmorProfileTypeUnconfined, pod.Spec.SecurityContext.AppArmorProfile.Type) 2413 }, 2414 expectWarning: true, 2415 }, { 2416 description: "Multiple containers by annotations", 2417 pod: &api.Pod{ 2418 ObjectMeta: metav1.ObjectMeta{ 2419 Annotations: map[string]string{ 2420 api.DeprecatedAppArmorAnnotationKeyPrefix + "init": api.DeprecatedAppArmorAnnotationValueUnconfined, 2421 api.DeprecatedAppArmorAnnotationKeyPrefix + "a": api.DeprecatedAppArmorAnnotationValueLocalhostPrefix + testProfile, 2422 api.DeprecatedAppArmorAnnotationKeyPrefix + "c": api.DeprecatedAppArmorAnnotationValueRuntimeDefault, 2423 }, 2424 }, 2425 Spec: api.PodSpec{ 2426 SecurityContext: &api.PodSecurityContext{ 2427 AppArmorProfile: &api.AppArmorProfile{ 2428 Type: api.AppArmorProfileTypeRuntimeDefault, 2429 }, 2430 }, 2431 InitContainers: []api.Container{{Name: "init"}}, 2432 Containers: []api.Container{ 2433 {Name: "a"}, 2434 {Name: "b"}, 2435 {Name: "c"}, 2436 }, 2437 }, 2438 }, 2439 validation: func(t *testing.T, pod *api.Pod) { 2440 assert.Equal(t, map[string]string{ 2441 api.DeprecatedAppArmorAnnotationKeyPrefix + "init": api.DeprecatedAppArmorAnnotationValueUnconfined, 2442 api.DeprecatedAppArmorAnnotationKeyPrefix + "a": api.DeprecatedAppArmorAnnotationValueLocalhostPrefix + testProfile, 2443 api.DeprecatedAppArmorAnnotationKeyPrefix + "b": api.DeprecatedAppArmorAnnotationValueRuntimeDefault, 2444 api.DeprecatedAppArmorAnnotationKeyPrefix + "c": api.DeprecatedAppArmorAnnotationValueRuntimeDefault, 2445 }, pod.Annotations) 2446 assert.Equal(t, api.AppArmorProfileTypeUnconfined, pod.Spec.InitContainers[0].SecurityContext.AppArmorProfile.Type) 2447 assert.Equal(t, api.AppArmorProfileTypeLocalhost, pod.Spec.Containers[0].SecurityContext.AppArmorProfile.Type) 2448 assert.Equal(t, testProfile, *pod.Spec.Containers[0].SecurityContext.AppArmorProfile.LocalhostProfile) 2449 assert.Nil(t, pod.Spec.Containers[1].SecurityContext) 2450 assert.Nil(t, pod.Spec.Containers[2].SecurityContext) 2451 assert.Equal(t, api.AppArmorProfileTypeRuntimeDefault, pod.Spec.SecurityContext.AppArmorProfile.Type) 2452 }, 2453 expectWarning: true, 2454 }, { 2455 description: "Conflicting field and annotations", 2456 pod: &api.Pod{ 2457 ObjectMeta: metav1.ObjectMeta{ 2458 Annotations: map[string]string{ 2459 api.DeprecatedAppArmorAnnotationKeyPrefix + "ctr": api.DeprecatedAppArmorAnnotationValueLocalhostPrefix + testProfile, 2460 }, 2461 }, 2462 Spec: api.PodSpec{ 2463 Containers: []api.Container{{ 2464 Name: "ctr", 2465 SecurityContext: &api.SecurityContext{ 2466 AppArmorProfile: &api.AppArmorProfile{ 2467 Type: api.AppArmorProfileTypeRuntimeDefault, 2468 }, 2469 }, 2470 }}, 2471 }, 2472 }, 2473 validation: func(t *testing.T, pod *api.Pod) { 2474 assert.Equal(t, map[string]string{ 2475 api.DeprecatedAppArmorAnnotationKeyPrefix + "ctr": api.DeprecatedAppArmorAnnotationValueLocalhostPrefix + testProfile, 2476 }, pod.Annotations) 2477 assert.Equal(t, api.AppArmorProfileTypeRuntimeDefault, pod.Spec.Containers[0].SecurityContext.AppArmorProfile.Type) 2478 assert.Nil(t, pod.Spec.Containers[0].SecurityContext.AppArmorProfile.LocalhostProfile) 2479 assert.Nil(t, pod.Spec.SecurityContext) 2480 }, 2481 }, { 2482 description: "Pod field and matching annotations", 2483 pod: &api.Pod{ 2484 ObjectMeta: metav1.ObjectMeta{ 2485 Annotations: map[string]string{ 2486 api.DeprecatedAppArmorAnnotationKeyPrefix + "ctr": api.DeprecatedAppArmorAnnotationValueRuntimeDefault, 2487 }, 2488 }, 2489 Spec: api.PodSpec{ 2490 SecurityContext: &api.PodSecurityContext{ 2491 AppArmorProfile: &api.AppArmorProfile{ 2492 Type: api.AppArmorProfileTypeRuntimeDefault, 2493 }, 2494 }, 2495 Containers: []api.Container{{ 2496 Name: "ctr", 2497 }}, 2498 }, 2499 }, 2500 validation: func(t *testing.T, pod *api.Pod) { 2501 assert.Equal(t, map[string]string{ 2502 api.DeprecatedAppArmorAnnotationKeyPrefix + "ctr": api.DeprecatedAppArmorAnnotationValueRuntimeDefault, 2503 }, pod.Annotations) 2504 assert.Equal(t, api.AppArmorProfileTypeRuntimeDefault, pod.Spec.SecurityContext.AppArmorProfile.Type) 2505 // Annotation shouldn't be synced to container security context 2506 assert.Nil(t, pod.Spec.Containers[0].SecurityContext) 2507 }, 2508 }, { 2509 description: "Annotation overrides pod field", 2510 pod: &api.Pod{ 2511 ObjectMeta: metav1.ObjectMeta{ 2512 Annotations: map[string]string{ 2513 api.DeprecatedAppArmorAnnotationKeyPrefix + "ctr": api.DeprecatedAppArmorAnnotationValueUnconfined, 2514 }, 2515 }, 2516 Spec: api.PodSpec{ 2517 SecurityContext: &api.PodSecurityContext{ 2518 AppArmorProfile: &api.AppArmorProfile{ 2519 Type: api.AppArmorProfileTypeRuntimeDefault, 2520 }, 2521 }, 2522 Containers: []api.Container{{ 2523 Name: "ctr", 2524 }}, 2525 }, 2526 }, 2527 validation: func(t *testing.T, pod *api.Pod) { 2528 assert.Equal(t, map[string]string{ 2529 api.DeprecatedAppArmorAnnotationKeyPrefix + "ctr": api.DeprecatedAppArmorAnnotationValueUnconfined, 2530 }, pod.Annotations) 2531 assert.Equal(t, api.AppArmorProfileTypeRuntimeDefault, pod.Spec.SecurityContext.AppArmorProfile.Type) 2532 assert.Equal(t, api.AppArmorProfileTypeUnconfined, pod.Spec.Containers[0].SecurityContext.AppArmorProfile.Type) 2533 }, 2534 expectWarning: true, 2535 }, { 2536 description: "Mixed annotations and fields", 2537 pod: &api.Pod{ 2538 ObjectMeta: metav1.ObjectMeta{ 2539 Annotations: map[string]string{ 2540 api.DeprecatedAppArmorAnnotationKeyPrefix + "unconf-annot": api.DeprecatedAppArmorAnnotationValueUnconfined, 2541 }, 2542 }, 2543 Spec: api.PodSpec{ 2544 SecurityContext: &api.PodSecurityContext{ 2545 AppArmorProfile: &api.AppArmorProfile{ 2546 Type: api.AppArmorProfileTypeRuntimeDefault, 2547 }, 2548 }, 2549 Containers: []api.Container{{ 2550 Name: "unconf-annot", 2551 }, { 2552 Name: "unconf-field", 2553 SecurityContext: &api.SecurityContext{ 2554 AppArmorProfile: &api.AppArmorProfile{ 2555 Type: api.AppArmorProfileTypeUnconfined, 2556 }, 2557 }, 2558 }, { 2559 Name: "default-pod", 2560 }}, 2561 }, 2562 }, 2563 validation: func(t *testing.T, pod *api.Pod) { 2564 assert.Equal(t, map[string]string{ 2565 api.DeprecatedAppArmorAnnotationKeyPrefix + "unconf-annot": api.DeprecatedAppArmorAnnotationValueUnconfined, 2566 api.DeprecatedAppArmorAnnotationKeyPrefix + "unconf-field": api.DeprecatedAppArmorAnnotationValueUnconfined, 2567 api.DeprecatedAppArmorAnnotationKeyPrefix + "default-pod": api.DeprecatedAppArmorAnnotationValueRuntimeDefault, 2568 }, pod.Annotations) 2569 assert.Equal(t, api.AppArmorProfileTypeRuntimeDefault, pod.Spec.SecurityContext.AppArmorProfile.Type) 2570 assert.Equal(t, api.AppArmorProfileTypeUnconfined, pod.Spec.Containers[0].SecurityContext.AppArmorProfile.Type) 2571 assert.Equal(t, api.AppArmorProfileTypeUnconfined, pod.Spec.Containers[1].SecurityContext.AppArmorProfile.Type) 2572 assert.Nil(t, pod.Spec.Containers[2].SecurityContext) 2573 }, 2574 expectWarning: true, 2575 }, { 2576 description: "Invalid annotation value", 2577 pod: &api.Pod{ 2578 ObjectMeta: metav1.ObjectMeta{ 2579 Annotations: map[string]string{ 2580 api.DeprecatedAppArmorAnnotationKeyPrefix + "ctr": "localhost/", 2581 }, 2582 }, 2583 Spec: api.PodSpec{ 2584 Containers: []api.Container{{Name: "ctr"}}, 2585 }, 2586 }, 2587 validation: func(t *testing.T, pod *api.Pod) { 2588 assert.Equal(t, map[string]string{ 2589 api.DeprecatedAppArmorAnnotationKeyPrefix + "ctr": "localhost/", 2590 }, pod.Annotations) 2591 assert.Nil(t, pod.Spec.Containers[0].SecurityContext) 2592 assert.Nil(t, pod.Spec.SecurityContext) 2593 }, 2594 expectWarning: true, 2595 }, { 2596 description: "Invalid localhost annotation", 2597 pod: &api.Pod{ 2598 ObjectMeta: metav1.ObjectMeta{ 2599 Annotations: map[string]string{ 2600 api.DeprecatedAppArmorAnnotationKeyPrefix + "ctr": api.DeprecatedAppArmorAnnotationValueLocalhostPrefix + strings.Repeat("a", 4096), 2601 }, 2602 }, 2603 Spec: api.PodSpec{ 2604 Containers: []api.Container{{Name: "ctr"}}, 2605 }, 2606 }, 2607 validation: func(t *testing.T, pod *api.Pod) { 2608 assert.Contains(t, pod.Annotations, api.DeprecatedAppArmorAnnotationKeyPrefix+"ctr") 2609 assert.Nil(t, pod.Spec.Containers[0].SecurityContext) 2610 assert.Nil(t, pod.Spec.SecurityContext) 2611 }, 2612 expectWarning: true, 2613 }, { 2614 description: "Invalid field type", 2615 pod: &api.Pod{ 2616 Spec: api.PodSpec{ 2617 SecurityContext: &api.PodSecurityContext{ 2618 AppArmorProfile: &api.AppArmorProfile{ 2619 Type: "invalid-type", 2620 }, 2621 }, 2622 Containers: []api.Container{{Name: "ctr"}}, 2623 }, 2624 }, 2625 validation: func(t *testing.T, pod *api.Pod) { 2626 assert.Empty(t, pod.Annotations) 2627 assert.Nil(t, pod.Spec.Containers[0].SecurityContext) 2628 }, 2629 }, { 2630 description: "Ignore annotations on windows", 2631 pod: &api.Pod{ 2632 ObjectMeta: metav1.ObjectMeta{ 2633 Annotations: map[string]string{ 2634 api.DeprecatedAppArmorAnnotationKeyPrefix + "ctr": api.DeprecatedAppArmorAnnotationValueRuntimeDefault, 2635 }, 2636 }, 2637 Spec: api.PodSpec{ 2638 OS: &api.PodOS{Name: api.Windows}, 2639 Containers: []api.Container{{Name: "ctr"}}, 2640 }, 2641 }, 2642 validation: func(t *testing.T, pod *api.Pod) { 2643 assert.Equal(t, map[string]string{ 2644 api.DeprecatedAppArmorAnnotationKeyPrefix + "ctr": api.DeprecatedAppArmorAnnotationValueRuntimeDefault, 2645 }, pod.Annotations) 2646 assert.Nil(t, pod.Spec.Containers[0].SecurityContext) 2647 }, 2648 }} 2649 2650 for _, test := range tests { 2651 t.Run(test.description, func(t *testing.T) { 2652 warnings := &warningRecorder{} 2653 ctx := warning.WithWarningRecorder(context.Background(), warnings) 2654 applyAppArmorVersionSkew(ctx, test.pod) 2655 test.validation(t, test.pod) 2656 2657 if test.expectWarning { 2658 if assert.NotEmpty(t, warnings.warnings, "expect warnings") { 2659 assert.Contains(t, warnings.warnings[0], `deprecated since v1.30; use the "appArmorProfile" field instead`) 2660 } 2661 } else { 2662 assert.Empty(t, warnings.warnings, "shouldn't emit a warning") 2663 } 2664 }) 2665 } 2666 } 2667 2668 type warningRecorder struct { 2669 warnings []string 2670 } 2671 2672 var _ warning.Recorder = &warningRecorder{} 2673 2674 func (w *warningRecorder) AddWarning(_, text string) { 2675 w.warnings = append(w.warnings, text) 2676 }