1 package k8s
2
3 import (
4 "context"
5 "errors"
6 "fmt"
7 "reflect"
8 "strings"
9 "testing"
10
11 "google.golang.org/grpc/codes"
12 "google.golang.org/grpc/status"
13 "k8s.io/apimachinery/pkg/labels"
14
15 "github.com/go-test/deep"
16 "github.com/linkerd/linkerd2/pkg/k8s"
17
18 corev1 "k8s.io/api/core/v1"
19 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
20 "k8s.io/apimachinery/pkg/runtime"
21 )
22
23 type resources struct {
24 results []string
25 misc []string
26 }
27
28
29
30 func newMockAPI(useInformer bool, res resources) (
31 *API,
32 *MetadataAPI,
33 []runtime.Object,
34 error,
35 ) {
36 k8sConfigs := []string{}
37 k8sResults := []runtime.Object{}
38
39 for _, config := range res.results {
40 obj, err := k8s.ToRuntimeObject(config)
41 if err != nil {
42 return nil, nil, nil, err
43 }
44 k8sConfigs = append(k8sConfigs, config)
45 k8sResults = append(k8sResults, obj)
46 }
47
48 k8sConfigs = append(k8sConfigs, res.misc...)
49
50 api, err := NewFakeAPI(k8sConfigs...)
51 if err != nil {
52 return nil, nil, nil, fmt.Errorf("NewFakeAPI returned an error: %w", err)
53 }
54
55 metadataAPI, err := NewFakeMetadataAPI(k8sConfigs)
56 if err != nil {
57 return nil, nil, nil, fmt.Errorf("NewFakeMetadataAPI returned an error: %w", err)
58 }
59
60 if useInformer {
61 api.Sync(nil)
62 metadataAPI.Sync(nil)
63 }
64
65 return api, metadataAPI, k8sResults, nil
66 }
67
68
69
70 func TestGetObjects(t *testing.T) {
71
72 type getObjectsExpected struct {
73 resources
74
75 err error
76 namespace string
77 resType string
78 name string
79 }
80
81 t.Run("Returns expected objects based on input", func(t *testing.T) {
82 expectations := []getObjectsExpected{
83 {
84 err: status.Errorf(codes.Unimplemented, "unimplemented resource type: bar"),
85 namespace: "foo",
86 resType: "bar",
87 name: "baz",
88 resources: resources{
89 results: []string{},
90 misc: []string{},
91 },
92 },
93 {
94 err: nil,
95 namespace: "my-ns",
96 resType: k8s.Pod,
97 name: "my-pod",
98 resources: resources{
99 results: []string{`
100 apiVersion: v1
101 kind: Pod
102 metadata:
103 name: my-pod
104 namespace: my-ns
105 spec:
106 containers:
107 - name: my-pod
108 status:
109 phase: Running`,
110 },
111 misc: []string{},
112 },
113 },
114 {
115 err: errors.New("\"my-pod\" not found"),
116 namespace: "not-my-ns",
117 resType: k8s.Pod,
118 name: "my-pod",
119 resources: resources{
120 results: []string{},
121 misc: []string{`
122 apiVersion: v1
123 kind: Pod
124 metadata:
125 name: my-pod
126 namespace: my-ns`,
127 },
128 },
129 },
130 {
131 err: nil,
132 namespace: "",
133 resType: k8s.ReplicationController,
134 name: "",
135 resources: resources{
136 results: []string{`
137 apiVersion: v1
138 kind: ReplicationController
139 metadata:
140 name: my-rc
141 namespace: my-ns`,
142 },
143 misc: []string{},
144 },
145 },
146 {
147 err: nil,
148 namespace: "my-ns",
149 resType: k8s.Deployment,
150 name: "",
151 resources: resources{
152 results: []string{`
153 apiVersion: apps/v1
154 kind: Deployment
155 metadata:
156 name: my-deploy
157 namespace: my-ns`,
158 },
159 misc: []string{`
160 apiVersion: apps/v1
161 kind: Deployment
162 metadata:
163 name: my-deploy
164 namespace: not-my-ns`,
165 },
166 },
167 },
168 {
169 err: nil,
170 namespace: "",
171 resType: k8s.DaemonSet,
172 name: "",
173 resources: resources{
174 results: []string{`
175 apiVersion: apps/v1
176 kind: DaemonSet
177 metadata:
178 name: my-ds
179 namespace: my-ns`,
180 },
181 },
182 },
183 {
184 err: nil,
185 namespace: "my-ns",
186 resType: k8s.DaemonSet,
187 name: "my-ds",
188 resources: resources{
189 results: []string{`
190 apiVersion: apps/v1
191 kind: DaemonSet
192 metadata:
193 name: my-ds
194 namespace: my-ns`,
195 },
196 misc: []string{`
197 apiVersion: apps/v1
198 kind: DaemonSet
199 metadata:
200 name: my-ds
201 namespace: not-my-ns`,
202 },
203 },
204 },
205 {
206 err: nil,
207 namespace: "my-ns",
208 resType: k8s.Job,
209 name: "my-job",
210 resources: resources{
211 results: []string{`
212 apiVersion: batch/v1
213 kind: Job
214 metadata:
215 name: my-job
216 namespace: my-ns`,
217 },
218 misc: []string{`
219 apiVersion: batch/v1
220 kind: Job
221 metadata:
222 name: my-job
223 namespace: not-my-ns`,
224 },
225 },
226 },
227 {
228 err: nil,
229 namespace: "my-ns",
230 resType: k8s.CronJob,
231 name: "my-cronjob",
232 resources: resources{
233 results: []string{`
234 apiVersion: batch/v1
235 kind: CronJob
236 metadata:
237 name: my-cronjob
238 namespace: my-ns`,
239 },
240 misc: []string{`
241 apiVersion: batch/v1
242 kind: CronJob
243 metadata:
244 name: my-cronjob
245 namespace: not-my-ns`,
246 },
247 },
248 },
249 {
250 err: nil,
251 namespace: "",
252 resType: k8s.StatefulSet,
253 name: "",
254 resources: resources{
255 results: []string{`
256 apiVersion: apps/v1
257 kind: StatefulSet
258 metadata:
259 name: my-ss
260 namespace: my-ns`,
261 },
262 },
263 },
264 {
265 err: nil,
266 namespace: "my-ns",
267 resType: k8s.StatefulSet,
268 name: "my-ss",
269 resources: resources{
270 results: []string{`
271 apiVersion: apps/v1
272 kind: StatefulSet
273 metadata:
274 name: my-ss
275 namespace: my-ns`,
276 },
277 misc: []string{`
278 apiVersion: apps/v1
279 kind: StatefulSet
280 metadata:
281 name: my-ss
282 namespace: not-my-ns`,
283 },
284 },
285 },
286 {
287 err: nil,
288 namespace: "",
289 resType: k8s.Namespace,
290 name: "",
291 resources: resources{
292 results: []string{`
293 apiVersion: v1
294 kind: Namespace
295 metadata:
296 name: my-ns`,
297 },
298 misc: []string{},
299 },
300 },
301 }
302
303 for _, exp := range expectations {
304 api, metadataAPI, k8sResults, err := newMockAPI(true, exp.resources)
305 if err != nil {
306 t.Fatalf("newMockAPI error: %s", err)
307 }
308
309 pods, err := api.GetObjects(exp.namespace, exp.resType, exp.name, labels.Everything())
310 if err != nil || exp.err != nil {
311 if unexpectedErrors(err, exp.err) {
312 t.Fatalf("api.GetObjects() unexpected error, expected [%s] got: [%s]", exp.err, err)
313 }
314 } else {
315 if diff := deep.Equal(pods, k8sResults); diff != nil {
316 t.Fatalf("Expected: %+v", diff)
317 }
318 }
319
320 var objMetas []*metav1.PartialObjectMetadata
321 res, err := GetAPIResource(exp.resType)
322 if err == nil {
323 objMetas, err = metadataAPI.GetByNamespaceFiltered(res, exp.namespace, exp.name, labels.Everything())
324 }
325 if err != nil || exp.err != nil {
326 if unexpectedErrors(err, exp.err) {
327 fmt.Printf("objMetas: %#v\n", objMetas)
328 t.Fatalf("metadataAPI.GetNamespaceFilteredCache() unexpected error, expected [%s] got: [%s]", exp.err, err)
329 }
330 } else {
331 expMetas := []*metav1.PartialObjectMetadata{}
332 for _, obj := range k8sResults {
333 objMeta, err := toPartialObjectMetadata(obj)
334 if err != nil {
335 t.Fatalf("error converting Object to PartialObjectMetadata: %s", err)
336 }
337 expMetas = append(expMetas, objMeta)
338 }
339 if diff := deep.Equal(objMetas, expMetas); diff != nil {
340 t.Fatalf("Expected: %+v", diff)
341 }
342 }
343 }
344 })
345
346 t.Run("If objects are pods", func(t *testing.T) {
347 t.Run("Return running or pending pods", func(t *testing.T) {
348 expectations := []getObjectsExpected{
349 {
350 err: nil,
351 namespace: "my-ns",
352 resType: k8s.Pod,
353 name: "my-pod",
354 resources: resources{
355 results: []string{`
356 apiVersion: v1
357 kind: Pod
358 metadata:
359 name: my-pod
360 namespace: my-ns
361 spec:
362 containers:
363 - name: my-pod
364 status:
365 phase: Running`,
366 },
367 },
368 },
369 {
370 err: nil,
371 namespace: "my-ns",
372 resType: k8s.Pod,
373 name: "my-pod",
374 resources: resources{
375 results: []string{`
376 apiVersion: v1
377 kind: Pod
378 metadata:
379 name: my-pod
380 namespace: my-ns
381 spec:
382 containers:
383 - name: my-pod
384 status:
385 phase: Pending`,
386 },
387 },
388 },
389 }
390
391 for _, exp := range expectations {
392 api, _, k8sResults, err := newMockAPI(true, exp.resources)
393 if err != nil {
394 t.Fatalf("newMockAPI error: %s", err)
395 }
396
397 pods, err := api.GetObjects(exp.namespace, exp.resType, exp.name, labels.Everything())
398 if err != nil {
399 t.Fatalf("api.GetObjects() unexpected error %s", err)
400 }
401
402 if diff := deep.Equal(pods, k8sResults); diff != nil {
403 t.Fatalf("%+v", diff)
404 }
405 }
406 })
407
408 t.Run("Don't return failed or succeeded pods", func(t *testing.T) {
409 expectations := []getObjectsExpected{
410 {
411 err: nil,
412 namespace: "my-ns",
413 resType: k8s.Pod,
414 name: "my-pod",
415 resources: resources{
416 results: []string{`
417 apiVersion: v1
418 kind: Pod
419 metadata:
420 name: my-pod
421 namespace: my-ns
422 spec:
423 containers:
424 - name: my-pod
425 status:
426 phase: Succeeded`,
427 },
428 },
429 },
430 {
431 err: nil,
432 namespace: "my-ns",
433 resType: k8s.Pod,
434 name: "my-pod",
435 resources: resources{
436 results: []string{`
437 apiVersion: v1
438 kind: Pod
439 metadata:
440 name: my-pod
441 namespace: my-ns
442 spec:
443 containers:
444 - name: my-pod
445 status:
446 phase: Failed`,
447 },
448 },
449 },
450 }
451
452 for _, exp := range expectations {
453 api, _, _, err := newMockAPI(true, exp.resources)
454 if err != nil {
455 t.Fatalf("newMockAPI error: %s", err)
456 }
457
458 pods, err := api.GetObjects(exp.namespace, exp.resType, exp.name, labels.Everything())
459 if err != nil {
460 t.Fatalf("api.GetObjects() unexpected error %s", err)
461 }
462
463 if len(pods) != 0 {
464 t.Errorf("Expected no terminating or failed pods to be returned but got %d pods", len(pods))
465 }
466 }
467
468 })
469 })
470 }
471
472 func TestGetPodsFor(t *testing.T) {
473
474 type getPodsForExpected struct {
475 resources
476
477 err error
478 k8sResInput string
479 }
480
481 t.Run("Returns expected pods based on input", func(t *testing.T) {
482 expectations := []getPodsForExpected{
483 {
484 err: nil,
485 k8sResInput: `
486 apiVersion: apps/v1
487 kind: Deployment
488 metadata:
489 name: emoji
490 namespace: emojivoto
491 spec:
492 selector:
493 matchLabels:
494 app: emoji-svc`,
495 resources: resources{
496 results: []string{},
497 misc: []string{`
498 apiVersion: v1
499 kind: Pod
500 metadata:
501 name: emojivoto-meshed-finished
502 namespace: emojivoto
503 labels:
504 app: emoji-svc
505 ownerReferences:
506 - apiVersion: apps/v1
507 status:
508 phase: Finished`,
509 },
510 },
511 },
512
513 {
514 err: nil,
515 k8sResInput: `
516 apiVersion: v1
517 kind: Service
518 metadata:
519 name: emoji-svc
520 namespace: emojivoto
521 uid: serviceUIDDoesNotMatter
522 spec:
523 type: ClusterIP
524 selector:
525 app: emoji-svc`,
526 resources: resources{
527 results: []string{`
528 apiVersion: v1
529 kind: Pod
530 metadata:
531 name: emojivoto-meshed-finished
532 namespace: emojivoto
533 labels:
534 app: emoji-svc
535 ownerReferences:
536 - apiVersion: apps/v1
537 status:
538 phase: Running`,
539 },
540 misc: []string{},
541 },
542 },
543
544 {
545 err: nil,
546 k8sResInput: `
547 apiVersion: v1
548 kind: Service
549 metadata:
550 name: emoji-svc
551 namespace: emojivoto
552 spec:
553 type: ExternalName
554 externalName: someapi.example.com`,
555 resources: resources{
556 results: []string{},
557 misc: []string{`
558 apiVersion: v1
559 kind: Pod
560 metadata:
561 name: emojivoto-meshed-finished
562 namespace: emojivoto
563 labels:
564 app: emoji-svc
565 status:
566 phase: Running`,
567 },
568 },
569 },
570
571 {
572 err: nil,
573 k8sResInput: `
574 apiVersion: batch/v1
575 kind: CronJob
576 metadata:
577 name: emoji
578 namespace: emojivoto
579 uid: cronjob`,
580 resources: resources{
581 results: []string{`
582 apiVersion: v1
583 kind: Pod
584 metadata:
585 name: emojivoto-meshed
586 namespace: emojivoto
587 labels:
588 app: emoji-svc
589 ownerReferences:
590 - apiVersion: batch/v1
591 uid: job
592 status:
593 phase: Running`,
594 },
595 misc: []string{`
596 apiVersion: batch/v1
597 kind: Job
598 metadata:
599 name: emoji
600 namespace: emojivoto
601 uid: job
602 ownerReferences:
603 - apiVersion: batch/v1
604 uid: cronjob
605 spec:
606 selector:
607 matchLabels:
608 app: emoji-svc`,
609 },
610 },
611 },
612
613 {
614 err: nil,
615 k8sResInput: `
616 apiVersion: apps/v1
617 kind: DaemonSet
618 metadata:
619 name: emoji
620 namespace: emojivoto
621 uid: daemonset
622 spec:
623 selector:
624 matchLabels:
625 app: emoji-svc`,
626 resources: resources{
627 results: []string{`
628 apiVersion: v1
629 kind: Pod
630 metadata:
631 name: emojivoto-meshed
632 namespace: emojivoto
633 labels:
634 app: emoji-svc
635 ownerReferences:
636 - apiVersion: apps/v1
637 uid: daemonset
638 status:
639 phase: Running`,
640 },
641 misc: []string{},
642 },
643 },
644
645 {
646 err: nil,
647 k8sResInput: `
648 apiVersion: apps/v1
649 kind: ReplicaSet
650 metadata:
651 name: emoji
652 namespace: emojivoto
653 uid: replicaset
654 spec:
655 selector:
656 matchLabels:
657 app: emoji-svc`,
658 resources: resources{
659 results: []string{`
660 apiVersion: v1
661 kind: Pod
662 metadata:
663 name: emojivoto-meshed
664 namespace: emojivoto
665 labels:
666 app: emoji-svc
667 ownerReferences:
668 - apiVersion: apps/v1
669 uid: replicaset
670 status:
671 phase: Running`,
672 },
673 misc: []string{`
674 apiVersion: v1
675 kind: Pod
676 metadata:
677 name: emojivoto-meshed-finished
678 namespace: emojivoto
679 labels:
680 app: emoji-svc
681 ownerReferences:
682 - apiVersion: apps/v1
683 uid: replicaset
684 status:
685 phase: Finished`,
686 },
687 },
688 },
689
690 {
691 err: nil,
692 k8sResInput: `
693 apiVersion: v1
694 kind: Pod
695 metadata:
696 name: emojivoto-meshed
697 namespace: emojivoto
698 labels:
699 app: emoji-svc
700 ownerReferences:
701 - apiVersion: apps/v1
702 uid: singlePod
703 status:
704 phase: Running`,
705 resources: resources{
706 results: []string{`
707 apiVersion: v1
708 kind: Pod
709 metadata:
710 name: emojivoto-meshed
711 namespace: emojivoto
712 labels:
713 app: emoji-svc
714 ownerReferences:
715 - apiVersion: apps/v1
716 uid: singlePod
717 status:
718 phase: Running`,
719 },
720 misc: []string{`
721 apiVersion: v1
722 kind: Pod
723 metadata:
724 name: emojivoto-meshed_2
725 namespace: emojivoto
726 labels:
727 app: emoji-svc
728 status:
729 phase: Running`,
730 },
731 },
732 },
733
734 {
735 err: nil,
736 k8sResInput: `
737 apiVersion: apps/v1
738 kind: Deployment
739 metadata:
740 annotations:
741 deployment.kubernetes.io/revision: "2"
742 name: emojivoto-meshed
743 namespace: emojivoto
744 uid: deployment
745 labels:
746 app: emoji-svc
747 spec:
748 selector:
749 matchLabels:
750 app: emoji-svc`,
751 resources: resources{
752 results: []string{`
753 apiVersion: v1
754 kind: Pod
755 metadata:
756 name: emojivoto-meshed
757 namespace: emojivoto
758 ownerReferences:
759 - apiVersion: apps/v1
760 uid: deploymentRS
761 labels:
762 app: emoji-svc
763 pod-template-hash: deploymentPod
764 status:
765 phase: Running`,
766 },
767 misc: []string{`
768 apiVersion: apps/v1
769 kind: ReplicaSet
770 metadata:
771 uid: deploymentRS
772 annotations:
773 deployment.kubernetes.io/revision: "2"
774 name: emojivoto-meshed_2
775 namespace: emojivoto
776 labels:
777 app: emoji-svc
778 pod-template-hash: deploymentPod
779 ownerReferences:
780 - apiVersion: apps/v1
781 uid: deployment
782 spec:
783 selector:
784 matchLabels:
785 app: emoji-svc
786 pod-template-hash: deploymentPod`,
787 `apiVersion: apps/v1
788 kind: ReplicaSet
789 metadata:
790 uid: deploymentRSOld
791 annotations:
792 deployment.kubernetes.io/revision: "1"
793 name: emojivoto-meshed_1
794 namespace: emojivoto
795 labels:
796 app: emoji-svc
797 pod-template-hash: deploymentPodOld
798 ownerReferences:
799 - apiVersion: apps/v1
800 uid: deployment
801 spec:
802 selector:
803 matchLabels:
804 app: emoji-svc
805 pod-template-hash: deploymentPodOld`,
806 },
807 },
808 },
809
810 {
811 err: nil,
812 k8sResInput: `
813 apiVersion: apps/v1
814 kind: Deployment
815 metadata:
816 annotations:
817 deployment.kubernetes.io/revision: "2"
818 name: emojivoto-meshed
819 namespace: emojivoto
820 uid: deploymentWithoutRS
821 labels:
822 app: emoji-svc
823 spec:
824 selector:
825 matchLabels:
826 app: emoji-svc`,
827 resources: resources{
828 results: []string{},
829 misc: []string{`
830 apiVersion: apps/v1
831 kind: ReplicaSet
832 metadata:
833 uid: AnotherRS
834 annotations:
835 deployment.kubernetes.io/revision: "2"
836 name: emojivoto-meshed_2
837 namespace: emojivoto
838 labels:
839 app: emoji-svc
840 pod-template-hash: doesntMatter
841 ownerReferences:
842 - apiVersion: apps/v1
843 uid: doesntMatch
844 spec:
845 selector:
846 matchLabels:
847 app: emoji-svc
848 pod-template-hash: doesntMatter`,
849 },
850 },
851 },
852
853 {
854 err: nil,
855 k8sResInput: `
856 apiVersion: apps/v1
857 kind: Deployment
858 metadata:
859 annotations:
860 deployment.kubernetes.io/revision: "2"
861 name: emojivoto-meshed
862 namespace: emojivoto
863 uid: deployment2RS
864 labels:
865 app: emoji-svc
866 spec:
867 selector:
868 matchLabels:
869 app: emoji-svc`,
870 resources: resources{
871 results: []string{`
872 apiVersion: v1
873 kind: Pod
874 metadata:
875 name: emojivoto-meshed-pod1
876 namespace: emojivoto
877 ownerReferences:
878 - apiVersion: apps/v1
879 uid: RS1
880 labels:
881 app: emoji-svc
882 pod-template-hash: pod1
883 status:
884 phase: Running`,
885 `apiVersion: v1
886 kind: Pod
887 metadata:
888 name: emojivoto-meshed-pod2
889 namespace: emojivoto
890 ownerReferences:
891 - apiVersion: apps/v1
892 uid: RS2
893 labels:
894 app: emoji-svc
895 pod-template-hash: pod2
896 status:
897 phase: Running`,
898 },
899 misc: []string{`
900 apiVersion: apps/v1
901 kind: ReplicaSet
902 metadata:
903 uid: RS1
904 annotations:
905 deployment.kubernetes.io/revision: "2"
906 name: emojivoto-meshed_2
907 namespace: emojivoto
908 labels:
909 app: emoji-svc
910 pod-template-hash: pod1
911 ownerReferences:
912 - apiVersion: apps/v1
913 uid: deployment2RS
914 spec:
915 selector:
916 matchLabels:
917 app: emoji-svc
918 pod-template-hash: pod1`,
919 `apiVersion: apps/v1
920 kind: ReplicaSet
921 metadata:
922 uid: RS2
923 annotations:
924 deployment.kubernetes.io/revision: "1"
925 name: emojivoto-meshed_1
926 namespace: emojivoto
927 labels:
928 app: emoji-svc
929 pod-template-hash: pod2
930 ownerReferences:
931 - apiVersion: apps/v1
932 uid: deployment2RS
933 spec:
934 selector:
935 matchLabels:
936 app: emoji-svc
937 pod-template-hash: pod2`,
938 },
939 },
940 },
941
942 {
943 err: nil,
944 k8sResInput: `
945 apiVersion: apps/v1
946 kind: Deployment
947 metadata:
948 annotations:
949 deployment.kubernetes.io/revision: "2"
950 name: emojivoto-meshed
951 namespace: emojivoto
952 uid: deployment2Pods
953 labels:
954 app: emoji-svc
955 spec:
956 selector:
957 matchLabels:
958 app: emoji-svc`,
959 resources: resources{
960 results: []string{`apiVersion: v1
961 kind: Pod
962 metadata:
963 name: emojivoto-meshed-with-RS
964 namespace: emojivoto
965 ownerReferences:
966 - apiVersion: apps/v1
967 uid: validRS
968 labels:
969 app: emoji-svc
970 pod-template-hash: podWithRS
971 status:
972 phase: Running`,
973 },
974 misc: []string{`
975 apiVersion: apps/v1
976 kind: ReplicaSet
977 metadata:
978 uid: validRS
979 annotations:
980 deployment.kubernetes.io/revision: "2"
981 name: emojivoto-meshed_2
982 namespace: emojivoto
983 labels:
984 app: emoji-svc
985 pod-template-hash: podWithRS
986 ownerReferences:
987 - apiVersion: apps/v1
988 uid: deployment2Pods
989 spec:
990 selector:
991 matchLabels:
992 app: emoji-svc
993 pod-template-hash: podWithRS`,
994 `apiVersion: v1
995 kind: Pod
996 metadata:
997 name: emojivoto-meshed-without-RS
998 namespace: emojivoto
999 ownerReferences:
1000 - apiVersion: apps/v1
1001 uid: notHere
1002 labels:
1003 app: emoji-svc
1004 pod-template-hash: invalidPod
1005 status:
1006 phase: Running`,
1007 },
1008 },
1009 },
1010 }
1011
1012 for _, exp := range expectations {
1013 k8sInputObj, err := k8s.ToRuntimeObject(exp.k8sResInput)
1014 if err != nil {
1015 t.Fatalf("could not decode yml: %s", err)
1016 }
1017
1018 api, _, k8sResults, err := newMockAPI(true, exp.resources)
1019 if err != nil {
1020 t.Fatalf("newMockAPI error: %s", err)
1021 }
1022
1023 k8sResultPods := []*corev1.Pod{}
1024 for _, obj := range k8sResults {
1025 k8sResultPods = append(k8sResultPods, obj.(*corev1.Pod))
1026 }
1027
1028 pods, err := api.GetPodsFor(k8sInputObj, false)
1029 if !errors.Is(err, exp.err) {
1030 t.Fatalf("api.GetPodsFor() unexpected error, expected [%s] got: [%s]", exp.err, err)
1031 }
1032
1033 if len(pods) != len(k8sResultPods) {
1034 t.Fatalf("Expected: %+v, Got: %+v", k8sResultPods, pods)
1035 }
1036
1037 for _, pod := range pods {
1038 found := false
1039 for _, resultPod := range k8sResultPods {
1040 if reflect.DeepEqual(pod, resultPod) {
1041 found = true
1042 break
1043 }
1044 }
1045 if !found {
1046 t.Fatalf("Expected: %+v, Got: %+v", k8sResultPods, pods)
1047 }
1048 }
1049 }
1050 })
1051 }
1052
1053
1054
1055
1056 func TestGetOwnerKindAndName(t *testing.T) {
1057 for i, tt := range []struct {
1058 resources
1059
1060 expectedOwnerKind string
1061 expectedOwnerName string
1062 }{
1063 {
1064 expectedOwnerKind: "deployment",
1065 expectedOwnerName: "t2",
1066 resources: resources{
1067 results: []string{`
1068 apiVersion: v1
1069 kind: Pod
1070 metadata:
1071 name: t2-5f79f964bc-d5jvf
1072 namespace: default
1073 ownerReferences:
1074 - apiVersion: apps/v1
1075 kind: ReplicaSet
1076 name: t2-5f79f964bc`,
1077 },
1078 misc: []string{`
1079 apiVersion: apps/v1
1080 kind: ReplicaSet
1081 metadata:
1082 name: t2-5f79f964bc
1083 namespace: default
1084 ownerReferences:
1085 - apiVersion: apps/v1
1086 kind: Deployment
1087 name: t2`,
1088 },
1089 },
1090 },
1091 {
1092 expectedOwnerKind: "replicaset",
1093 expectedOwnerName: "t1-b4f55d87f",
1094 resources: resources{
1095 results: []string{`
1096 apiVersion: v1
1097 kind: Pod
1098 metadata:
1099 name: t1-b4f55d87f-98dbz
1100 namespace: default
1101 ownerReferences:
1102 - apiVersion: apps/v1
1103 kind: ReplicaSet
1104 name: t1-b4f55d87f`,
1105 },
1106 },
1107 },
1108 {
1109 expectedOwnerKind: "job",
1110 expectedOwnerName: "slow-cooker",
1111 resources: resources{
1112 results: []string{`
1113 apiVersion: v1
1114 kind: Pod
1115 metadata:
1116 name: slow-cooker-bxtnq
1117 namespace: default
1118 ownerReferences:
1119 - apiVersion: batch/v1
1120 kind: Job
1121 name: slow-cooker`,
1122 },
1123 },
1124 },
1125 {
1126 expectedOwnerKind: "replicationcontroller",
1127 expectedOwnerName: "web",
1128 resources: resources{
1129 results: []string{`
1130 apiVersion: v1
1131 kind: Pod
1132 metadata:
1133 name: web-dcfq4
1134 namespace: default
1135 ownerReferences:
1136 - apiVersion: v1
1137 kind: ReplicationController
1138 name: web`,
1139 },
1140 },
1141 },
1142 {
1143 expectedOwnerKind: "pod",
1144 expectedOwnerName: "vote-bot",
1145 resources: resources{
1146 results: []string{`
1147 apiVersion: v1
1148 kind: Pod
1149 metadata:
1150 name: vote-bot
1151 namespace: default`,
1152 },
1153 },
1154 },
1155 {
1156 expectedOwnerKind: "cronjob",
1157 expectedOwnerName: "my-cronjob",
1158 resources: resources{
1159 results: []string{`
1160 apiVersion: v1
1161 kind: Pod
1162 metadata:
1163 name: my-pod
1164 namespace: my-ns
1165 ownerReferences:
1166 - apiVersion: batch/v1
1167 kind: Job
1168 name: my-job`,
1169 },
1170 misc: []string{`
1171 apiVersion: batch/v1
1172 kind: Job
1173 metadata:
1174 name: my-job
1175 namespace: my-ns
1176 ownerReferences:
1177 - apiVersion: batch/v1
1178 kind: CronJob
1179 name: my-cronjob`,
1180 },
1181 },
1182 },
1183 {
1184 expectedOwnerKind: "replicaset",
1185 expectedOwnerName: "invalid-rs-parent-2abdffa",
1186 resources: resources{
1187 results: []string{`
1188 apiVersion: v1
1189 kind: Pod
1190 metadata:
1191 name: invalid-rs-parent-dcfq4
1192 namespace: default
1193 ownerReferences:
1194 - apiVersion: v1
1195 kind: ReplicaSet
1196 name: invalid-rs-parent-2abdffa`,
1197 },
1198 misc: []string{`
1199 apiVersion: apps/v1
1200 kind: ReplicaSet
1201 metadata:
1202 name: invalid-rs-parent-2abdffa
1203 namespace: default
1204 ownerReferences:
1205 - apiVersion: invalidParent/v1
1206 kind: InvalidParentKind
1207 name: invalid-parent`,
1208 },
1209 },
1210 },
1211 } {
1212 tt := tt
1213 for _, retry := range []bool{
1214 false,
1215 true,
1216 } {
1217 retry := retry
1218 t.Run(fmt.Sprintf("%d/retry:%t", i, retry), func(t *testing.T) {
1219 api, metadataAPI, objs, err := newMockAPI(!retry, tt.resources)
1220 if err != nil {
1221 t.Fatalf("newMockAPI error: %s", err)
1222 }
1223
1224 pod := objs[0].(*corev1.Pod)
1225 ownerKind, ownerName := api.GetOwnerKindAndName(context.Background(), pod, retry)
1226
1227 if ownerKind != tt.expectedOwnerKind {
1228 t.Fatalf("Expected kind to be [%s], got [%s]", tt.expectedOwnerKind, ownerKind)
1229 }
1230
1231 if ownerName != tt.expectedOwnerName {
1232 t.Fatalf("Expected name to be [%s], got [%s]", tt.expectedOwnerName, ownerName)
1233 }
1234
1235 ownerKind, ownerName, err = metadataAPI.GetOwnerKindAndName(context.Background(), pod, retry)
1236 if err != nil {
1237 t.Fatalf("Unexpected error: %s", err)
1238 }
1239
1240 if ownerKind != tt.expectedOwnerKind {
1241 t.Fatalf("Expected kind to be [%s], got [%s]", tt.expectedOwnerKind, ownerKind)
1242 }
1243
1244 if ownerName != tt.expectedOwnerName {
1245 t.Fatalf("Expected name to be [%s], got [%s]", tt.expectedOwnerName, ownerName)
1246 }
1247 })
1248 }
1249 }
1250 }
1251
1252 func TestGetServiceProfileFor(t *testing.T) {
1253 for _, tt := range []struct {
1254 resources
1255
1256 expectedRouteNames []string
1257 }{
1258
1259 {
1260 expectedRouteNames: []string{},
1261 resources: resources{},
1262 },
1263
1264 {
1265 expectedRouteNames: []string{},
1266 resources: resources{
1267 results: []string{`
1268 apiVersion: linkerd.io/v1alpha2
1269 kind: ServiceProfile
1270 metadata:
1271 name: books.server.svc.cluster.local
1272 namespace: linkerd
1273 spec:
1274 routes:
1275 - condition:
1276 pathRegex: /server
1277 name: server`,
1278 },
1279 },
1280 },
1281
1282 {
1283 expectedRouteNames: []string{"server"},
1284 resources: resources{
1285 results: []string{`
1286 apiVersion: linkerd.io/v1alpha2
1287 kind: ServiceProfile
1288 metadata:
1289 name: books.server.svc.cluster.local
1290 namespace: server
1291 spec:
1292 routes:
1293 - condition:
1294 pathRegex: /server
1295 name: server`,
1296 },
1297 },
1298 },
1299
1300 {
1301 expectedRouteNames: []string{"client"},
1302 resources: resources{
1303 results: []string{`
1304 apiVersion: linkerd.io/v1alpha2
1305 kind: ServiceProfile
1306 metadata:
1307 name: books.server.svc.cluster.local
1308 namespace: client
1309 spec:
1310 routes:
1311 - condition:
1312 pathRegex: /client
1313 name: client`,
1314 },
1315 },
1316 },
1317
1318 {
1319 expectedRouteNames: []string{"client"},
1320 resources: resources{
1321 results: []string{`
1322 apiVersion: linkerd.io/v1alpha2
1323 kind: ServiceProfile
1324 metadata:
1325 name: books.server.svc.cluster.local
1326 namespace: server
1327 spec:
1328 routes:
1329 - condition:
1330 pathRegex: /server
1331 name: server`,
1332 `
1333 apiVersion: linkerd.io/v1alpha2
1334 kind: ServiceProfile
1335 metadata:
1336 name: books.server.svc.cluster.local
1337 namespace: client
1338 spec:
1339 routes:
1340 - condition:
1341 pathRegex: /client
1342 name: client`,
1343 },
1344 },
1345 },
1346 } {
1347 api, _, _, err := newMockAPI(true, tt.resources)
1348 if err != nil {
1349 t.Fatalf("newMockAPI error: %s", err)
1350 }
1351
1352 svc := corev1.Service{
1353 ObjectMeta: metav1.ObjectMeta{
1354 Name: "books",
1355 Namespace: "server",
1356 },
1357 }
1358
1359 sp := api.GetServiceProfileFor(&svc, "client", "cluster.local")
1360
1361 if len(sp.Spec.Routes) != len(tt.expectedRouteNames) {
1362 t.Fatalf("Expected %d routes, got %d", len(tt.expectedRouteNames), len(sp.Spec.Routes))
1363 }
1364
1365 for i, route := range sp.Spec.Routes {
1366 if tt.expectedRouteNames[i] != route.Name {
1367 t.Fatalf("Expected route [%s], got [%s]", tt.expectedRouteNames[i], route.Name)
1368 }
1369 }
1370 }
1371 }
1372
1373 func TestGetServicesFor(t *testing.T) {
1374
1375 type getServicesForExpected struct {
1376 resources
1377
1378 err error
1379 k8sResInput string
1380 }
1381
1382 t.Run("GetServicesFor", func(t *testing.T) {
1383 expectations := []getServicesForExpected{
1384
1385 {
1386 err: nil,
1387 k8sResInput: `
1388 apiVersion: v1
1389 kind: Pod
1390 metadata:
1391 name: my-pod
1392 namespace: emojivoto
1393 labels:
1394 app: my-pod
1395 status:
1396 phase: Running`,
1397 resources: resources{
1398 results: []string{`
1399 apiVersion: v1
1400 kind: Service
1401 metadata:
1402 name: my-svc
1403 namespace: emojivoto
1404 spec:
1405 type: ClusterIP
1406 selector:
1407 app: my-pod`,
1408 },
1409 misc: []string{},
1410 },
1411 },
1412 }
1413
1414 for _, exp := range expectations {
1415 k8sInputObj, err := k8s.ToRuntimeObject(exp.k8sResInput)
1416 if err != nil {
1417 t.Fatalf("could not decode yml: %s", err)
1418 }
1419
1420 exp.misc = append(exp.misc, exp.k8sResInput)
1421 api, _, k8sResults, err := newMockAPI(true, exp.resources)
1422 if err != nil {
1423 t.Fatalf("newMockAPI error: %s", err)
1424 }
1425
1426 k8sResultServices := []*corev1.Service{}
1427 for _, obj := range k8sResults {
1428 k8sResultServices = append(k8sResultServices, obj.(*corev1.Service))
1429 }
1430
1431 services, err := api.GetServicesFor(k8sInputObj, false)
1432 if !errors.Is(err, exp.err) {
1433 t.Fatalf("api.GetServicesFor() unexpected error, expected [%s] got: [%s]", exp.err, err)
1434 }
1435
1436 if len(services) != len(k8sResultServices) {
1437 t.Fatalf("Expected: %+v, Got: %+v", k8sResultServices, services)
1438 }
1439
1440 for _, service := range services {
1441 found := false
1442 for _, resultService := range k8sResultServices {
1443 if reflect.DeepEqual(service, resultService) {
1444 found = true
1445 break
1446 }
1447 }
1448 if !found {
1449 t.Fatalf("Expected: %+v, Got: %+v", k8sResultServices, services)
1450 }
1451 }
1452 }
1453
1454 })
1455 }
1456
1457 func unexpectedErrors(err, expErr error) bool {
1458 return (err == nil && expErr != nil) ||
1459 (err != nil && expErr == nil) ||
1460 !strings.Contains(err.Error(), expErr.Error())
1461 }
1462
View as plain text