1
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 utilpointer "k8s.io/utils/pointer"
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
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: utilpointer.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 = utilpointer.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
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
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 defer 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
1572 defer 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
1619 defer 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
1639 defer 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
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 defer 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 defer 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
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 }
2677
View as plain text