1 package util
2
3 import (
4 "testing"
5
6 "github.com/go-test/deep"
7 "github.com/linkerd/linkerd2/pkg/k8s"
8 pb "github.com/linkerd/linkerd2/viz/metrics-api/gen/viz"
9 corev1 "k8s.io/api/core/v1"
10 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
11 )
12
13 func TestBuildStatSummaryRequest(t *testing.T) {
14 t.Run("Maps Kubernetes friendly names to canonical names", func(t *testing.T) {
15 expectations := map[string]string{
16 "deployments": k8s.Deployment,
17 "deployment": k8s.Deployment,
18 "deploy": k8s.Deployment,
19 "pods": k8s.Pod,
20 "pod": k8s.Pod,
21 "po": k8s.Pod,
22 }
23
24 for friendly, canonical := range expectations {
25 statSummaryRequest, err := BuildStatSummaryRequest(
26 StatsSummaryRequestParams{
27 StatsBaseRequestParams: StatsBaseRequestParams{
28 ResourceType: friendly,
29 },
30 },
31 )
32 if err != nil {
33 t.Fatalf("Unexpected error from BuildStatSummaryRequest [%s => %s]: %s", friendly, canonical, err)
34 }
35 if statSummaryRequest.Selector.Resource.Type != canonical {
36 t.Fatalf("Unexpected resource type from BuildStatSummaryRequest [%s => %s]: %s", friendly, canonical, statSummaryRequest.Selector.Resource.Type)
37 }
38 }
39 })
40
41 t.Run("Parses valid time windows", func(t *testing.T) {
42 expectations := []string{
43 "1m",
44 "60s",
45 "1m",
46 }
47
48 for _, timeWindow := range expectations {
49 statSummaryRequest, err := BuildStatSummaryRequest(
50 StatsSummaryRequestParams{
51 StatsBaseRequestParams: StatsBaseRequestParams{
52 TimeWindow: timeWindow,
53 ResourceType: k8s.Deployment,
54 },
55 },
56 )
57 if err != nil {
58 t.Fatalf("Unexpected error from BuildStatSummaryRequest [%s => %s]", timeWindow, err)
59 }
60 if statSummaryRequest.TimeWindow != timeWindow {
61 t.Fatalf("Unexpected TimeWindow from BuildStatSummaryRequest [%s => %s]", timeWindow, statSummaryRequest.TimeWindow)
62 }
63 }
64 })
65
66 t.Run("Rejects invalid time windows", func(t *testing.T) {
67 expectations := map[string]string{
68 "1": "time: missing unit in duration \"1\"",
69 "s": "time: invalid duration \"s\"",
70 }
71
72 for timeWindow, msg := range expectations {
73 _, err := BuildStatSummaryRequest(
74 StatsSummaryRequestParams{
75 StatsBaseRequestParams: StatsBaseRequestParams{
76 TimeWindow: timeWindow,
77 },
78 },
79 )
80 if err == nil {
81 t.Fatalf("BuildStatSummaryRequest(%s) unexpectedly succeeded, should have returned %s", timeWindow, msg)
82 }
83 if err.Error() != msg {
84 t.Fatalf("BuildStatSummaryRequest(%s) should have returned: %s but got unexpected message: %s", timeWindow, msg, err)
85 }
86 }
87 })
88
89 t.Run("Rejects invalid Kubernetes resource types", func(t *testing.T) {
90 expectations := map[string]string{
91 "foo": "cannot find Kubernetes canonical name from friendly name [foo]",
92 "": "cannot find Kubernetes canonical name from friendly name []",
93 }
94
95 for input, msg := range expectations {
96 _, err := BuildStatSummaryRequest(
97 StatsSummaryRequestParams{
98 StatsBaseRequestParams: StatsBaseRequestParams{
99 ResourceType: input,
100 },
101 },
102 )
103 if err == nil {
104 t.Fatalf("BuildStatSummaryRequest(%s) unexpectedly succeeded, should have returned %s", input, msg)
105 }
106 if err.Error() != msg {
107 t.Fatalf("BuildStatSummaryRequest(%s) should have returned: %s but got unexpected message: %s", input, msg, err)
108 }
109 }
110 })
111 }
112
113 func TestBuildTopRoutesRequest(t *testing.T) {
114 t.Run("Parses valid time windows", func(t *testing.T) {
115 expectations := []string{
116 "1m",
117 "60s",
118 "1m",
119 }
120
121 for _, timeWindow := range expectations {
122 topRoutesRequest, err := BuildTopRoutesRequest(
123 TopRoutesRequestParams{
124 StatsBaseRequestParams: StatsBaseRequestParams{
125 TimeWindow: timeWindow,
126 ResourceType: k8s.Deployment,
127 },
128 },
129 )
130 if err != nil {
131 t.Fatalf("Unexpected error from BuildTopRoutesRequest [%s => %s]", timeWindow, err)
132 }
133 if topRoutesRequest.TimeWindow != timeWindow {
134 t.Fatalf("Unexpected TimeWindow from BuildTopRoutesRequest [%s => %s]", timeWindow, topRoutesRequest.TimeWindow)
135 }
136 }
137 })
138
139 t.Run("Rejects invalid time windows", func(t *testing.T) {
140 expectations := map[string]string{
141 "1": "time: missing unit in duration \"1\"",
142 "s": "time: invalid duration \"s\"",
143 }
144
145 for timeWindow, msg := range expectations {
146 _, err := BuildTopRoutesRequest(
147 TopRoutesRequestParams{
148 StatsBaseRequestParams: StatsBaseRequestParams{
149 TimeWindow: timeWindow,
150 ResourceType: k8s.Deployment,
151 },
152 },
153 )
154 if err == nil {
155 t.Fatalf("BuildTopRoutesRequest(%s) unexpectedly succeeded, should have returned %s", timeWindow, msg)
156 }
157 if err.Error() != msg {
158 t.Fatalf("BuildTopRoutesRequest(%s) should have returned: %s but got unexpected message: %s", timeWindow, msg, err)
159 }
160 }
161 })
162 }
163
164 func TestK8sPodToPublicPod(t *testing.T) {
165 type podExp struct {
166 k8sPod corev1.Pod
167 ownerKind string
168 ownerName string
169 publicPod *pb.Pod
170 }
171
172 t.Run("Returns expected pods", func(t *testing.T) {
173 expectations := []podExp{
174 {
175 k8sPod: corev1.Pod{},
176 publicPod: &pb.Pod{
177 Name: "/",
178 },
179 },
180 {
181 k8sPod: corev1.Pod{
182 ObjectMeta: metav1.ObjectMeta{
183 Namespace: "ns",
184 Name: "name",
185 ResourceVersion: "resource-version",
186 Labels: map[string]string{
187 k8s.ControllerComponentLabel: "controller-component",
188 k8s.ControllerNSLabel: "controller-ns",
189 },
190 },
191 Spec: corev1.PodSpec{
192 Containers: []corev1.Container{
193 {
194 Name: k8s.ProxyContainerName,
195 Image: "linkerd-proxy:test-version",
196 },
197 },
198 },
199 Status: corev1.PodStatus{
200 PodIP: "pod-ip",
201 Phase: "status",
202 ContainerStatuses: []corev1.ContainerStatus{
203 {
204 Name: k8s.ProxyContainerName,
205 Ready: true,
206 },
207 },
208 },
209 },
210 ownerKind: k8s.Deployment,
211 ownerName: "owner-name",
212 publicPod: &pb.Pod{
213 Name: "ns/name",
214 Owner: &pb.Pod_Deployment{Deployment: "ns/owner-name"},
215 ResourceVersion: "resource-version",
216 ControlPlane: true,
217 ControllerNamespace: "controller-ns",
218 Status: "status",
219 ProxyReady: true,
220 ProxyVersion: "test-version",
221 PodIP: "pod-ip",
222 },
223 },
224 {
225 k8sPod: corev1.Pod{
226 Status: corev1.PodStatus{
227 Phase: "Failed",
228 Reason: "Evicted",
229 ContainerStatuses: []corev1.ContainerStatus{
230 {
231 Name: k8s.ProxyContainerName,
232 Ready: true,
233 },
234 },
235 },
236 },
237 ownerName: "owner-name",
238 publicPod: &pb.Pod{
239 Name: "/",
240 Status: "Evicted",
241 ProxyReady: true,
242 },
243 },
244 }
245
246 for _, exp := range expectations {
247 res := K8sPodToPublicPod(exp.k8sPod, exp.ownerKind, exp.ownerName)
248 if diff := deep.Equal(exp.publicPod, res); diff != nil {
249 t.Errorf("%v", diff)
250 }
251 }
252 })
253 }
254
View as plain text