1 package api
2
3 import (
4 "context"
5 "errors"
6 "sort"
7 "testing"
8
9 "github.com/linkerd/linkerd2/controller/k8s"
10 pkgK8s "github.com/linkerd/linkerd2/pkg/k8s"
11 "github.com/linkerd/linkerd2/pkg/prometheus"
12 pb "github.com/linkerd/linkerd2/viz/metrics-api/gen/viz"
13 "github.com/prometheus/common/model"
14 "google.golang.org/protobuf/proto"
15 )
16
17 type statSumExpected struct {
18 expectedStatRPC
19 req *pb.StatSummaryRequest
20 expectedResponse *pb.StatSummaryResponse
21 }
22
23 func prometheusMetric(resName string, resType string) model.Vector {
24 return model.Vector{
25 genPromSample(resName, resType, "emojivoto", false),
26 }
27 }
28
29 func genPromSample(resName string, resType string, resNs string, isDst bool) *model.Sample {
30 labelName := model.LabelName(resType)
31 namespaceLabel := model.LabelName("namespace")
32
33 if isDst {
34 labelName = "dst_" + labelName
35 namespaceLabel = "dst_" + namespaceLabel
36 }
37
38 return &model.Sample{
39 Metric: model.Metric{
40 labelName: model.LabelValue(resName),
41 namespaceLabel: model.LabelValue(resNs),
42 "classification": model.LabelValue("success"),
43 "tls": model.LabelValue("true"),
44 },
45 Value: 123,
46 Timestamp: 456,
47 }
48 }
49
50 func genEmptyResponse() *pb.StatSummaryResponse {
51 return &pb.StatSummaryResponse{
52 Response: &pb.StatSummaryResponse_Ok_{
53 Ok: &pb.StatSummaryResponse_Ok{
54 StatTables: []*pb.StatTable{
55 {
56 Table: &pb.StatTable_PodGroup_{
57 PodGroup: &pb.StatTable_PodGroup{},
58 },
59 },
60 },
61 },
62 },
63 }
64 }
65
66 func testStatSummary(t *testing.T, expectations []statSumExpected) {
67 for _, exp := range expectations {
68 mockProm, fakeGrpcServer, err := newMockGrpcServer(exp.expectedStatRPC)
69 if err != nil {
70 t.Fatalf("Error creating mock grpc server: %s", err)
71 }
72
73 rsp, err := fakeGrpcServer.StatSummary(context.TODO(), exp.req)
74 if !errors.Is(err, exp.err) {
75 t.Fatalf("Expected error: %s, Got: %s", exp.err, err)
76 }
77
78 err = exp.verifyPromQueries(mockProm)
79 if err != nil {
80 t.Fatal(err)
81 }
82
83 rspStatTables := rsp.GetOk().StatTables
84 sort.Sort(byStatResult(rspStatTables))
85
86 if len(rspStatTables) != len(exp.expectedResponse.GetOk().StatTables) {
87 t.Fatalf(
88 "Expected [%d] stat tables, got [%d].\nExpected:\n%s\nGot:\n%s",
89 len(exp.expectedResponse.GetOk().StatTables),
90 len(rspStatTables),
91 exp.expectedResponse.GetOk().StatTables,
92 rspStatTables,
93 )
94 }
95
96 statOkRsp := &pb.StatSummaryResponse_Ok{
97 StatTables: rspStatTables,
98 }
99
100 for i, st := range rspStatTables {
101 expected := exp.expectedResponse.GetOk().StatTables[i]
102 if !proto.Equal(st, expected) {
103 t.Fatalf("Expected: %+v\n Got: %+v", expected, st)
104 }
105 }
106
107 if !proto.Equal(exp.expectedResponse.GetOk(), statOkRsp) {
108 t.Fatalf("Expected: %+v\n Got: %+v", &exp.expectedResponse, rsp)
109 }
110 }
111 }
112
113 type byStatResult []*pb.StatTable
114
115 func (s byStatResult) Len() int {
116 return len(s)
117 }
118
119 func (s byStatResult) Swap(i, j int) {
120 s[i], s[j] = s[j], s[i]
121 }
122
123 func (s byStatResult) Less(i, j int) bool {
124 if len(s[i].GetPodGroup().Rows) == 0 {
125 return true
126 }
127 if len(s[j].GetPodGroup().Rows) == 0 {
128 return false
129 }
130
131 return s[i].GetPodGroup().Rows[0].Resource.Type < s[j].GetPodGroup().Rows[0].Resource.Type
132 }
133
134 func TestStatSummary(t *testing.T) {
135 t.Run("Successfully performs a query based on resource type Pod", func(t *testing.T) {
136 expectations := []statSumExpected{
137 {
138 expectedStatRPC: expectedStatRPC{
139 err: nil,
140 k8sConfigs: []string{`
141 apiVersion: v1
142 kind: Pod
143 metadata:
144 name: emoji
145 namespace: emojivoto
146 labels:
147 app: emoji-svc
148 linkerd.io/control-plane-ns: linkerd
149 status:
150 phase: Running
151 `,
152 },
153 mockPromResponse: prometheusMetric("emoji", "pod"),
154 },
155 req: &pb.StatSummaryRequest{
156 Selector: &pb.ResourceSelection{
157 Resource: &pb.Resource{
158 Namespace: "emojivoto",
159 Type: pkgK8s.Pod,
160 },
161 },
162 TimeWindow: "1m",
163 },
164 expectedResponse: GenStatSummaryResponse("emoji", pkgK8s.Pod, []string{"emojivoto"}, &PodCounts{
165 Status: "Running",
166 MeshedPods: 1,
167 RunningPods: 1,
168 FailedPods: 0,
169 }, true, false),
170 },
171 }
172
173 testStatSummary(t, expectations)
174 })
175
176 t.Run("Successfully performs a query based on resource type Pod when pod Reason is filled", func(t *testing.T) {
177 expectations := []statSumExpected{
178 {
179 expectedStatRPC: expectedStatRPC{
180 err: nil,
181 k8sConfigs: []string{`
182 apiVersion: v1
183 kind: Pod
184 metadata:
185 name: emoji
186 namespace: emojivoto
187 labels:
188 app: emoji-svc
189 linkerd.io/control-plane-ns: linkerd
190 status:
191 phase: Pending
192 reason: podReason
193 `,
194 },
195 mockPromResponse: prometheusMetric("emoji", "pod"),
196 },
197 req: &pb.StatSummaryRequest{
198 Selector: &pb.ResourceSelection{
199 Resource: &pb.Resource{
200 Namespace: "emojivoto",
201 Type: pkgK8s.Pod,
202 },
203 },
204 TimeWindow: "1m",
205 },
206 expectedResponse: GenStatSummaryResponse("emoji", pkgK8s.Pod, []string{"emojivoto"}, &PodCounts{
207 Status: "podReason",
208 MeshedPods: 1,
209 RunningPods: 1,
210 FailedPods: 0,
211 }, true, false),
212 },
213 }
214
215 testStatSummary(t, expectations)
216 })
217
218 t.Run("Successfully performs a query based on resource type Pod when pod init container is initializing", func(t *testing.T) {
219 expectations := []statSumExpected{
220 {
221 expectedStatRPC: expectedStatRPC{
222 err: nil,
223 k8sConfigs: []string{`
224 apiVersion: v1
225 kind: Pod
226 metadata:
227 name: emoji
228 namespace: emojivoto
229 labels:
230 app: emoji-svc
231 linkerd.io/control-plane-ns: linkerd
232 spec:
233 initContainers:
234 - name: foo
235 status:
236 phase: Pending
237 initContainerStatuses:
238 - name: foo
239 state:
240 waiting:
241 reason: PodInitializing
242 `,
243 },
244 mockPromResponse: prometheusMetric("emoji", "pod"),
245 },
246 req: &pb.StatSummaryRequest{
247 Selector: &pb.ResourceSelection{
248 Resource: &pb.Resource{
249 Namespace: "emojivoto",
250 Type: pkgK8s.Pod,
251 },
252 },
253 TimeWindow: "1m",
254 },
255 expectedResponse: GenStatSummaryResponse("emoji", pkgK8s.Pod, []string{"emojivoto"}, &PodCounts{
256 Status: "Init:0/1",
257 MeshedPods: 1,
258 RunningPods: 1,
259 FailedPods: 0,
260 Errors: map[string]*pb.PodErrors{
261 "emoji": {
262 Errors: []*pb.PodErrors_PodError{
263 {
264 Error: &pb.PodErrors_PodError_Container{
265 Container: &pb.PodErrors_PodError_ContainerError{
266 Container: "foo",
267 Reason: "PodInitializing",
268 },
269 },
270 },
271 },
272 },
273 },
274 }, true, false),
275 },
276 }
277
278 testStatSummary(t, expectations)
279 })
280
281 t.Run("Successfully performs a query based on resource type Deployment", func(t *testing.T) {
282 expectations := []statSumExpected{
283 {
284 expectedStatRPC: expectedStatRPC{
285 err: nil,
286 k8sConfigs: []string{`
287 apiVersion: apps/v1
288 kind: Deployment
289 metadata:
290 name: emoji
291 namespace: emojivoto
292 uid: a1b2c3
293 spec:
294 selector:
295 matchLabels:
296 app: emoji-svc
297 strategy: {}
298 template:
299 spec:
300 containers:
301 - image: buoyantio/emojivoto-emoji-svc:v10
302 `, `
303 apiVersion: apps/v1
304 kind: ReplicaSet
305 metadata:
306 uid: a1b2c3d4
307 annotations:
308 deployment.kubernetes.io/revision: "2"
309 name: emojivoto-meshed_2
310 namespace: emojivoto
311 labels:
312 app: emoji-svc
313 pod-template-hash: 3c2b1a
314 ownerReferences:
315 - apiVersion: apps/v1
316 uid: a1b2c3
317 spec:
318 selector:
319 matchLabels:
320 app: emoji-svc
321 pod-template-hash: 3c2b1a
322 `, `
323 apiVersion: v1
324 kind: Pod
325 metadata:
326 name: emojivoto-meshed
327 namespace: emojivoto
328 labels:
329 app: emoji-svc
330 linkerd.io/control-plane-ns: linkerd
331 pod-template-hash: 3c2b1a
332 ownerReferences:
333 - apiVersion: apps/v1
334 uid: a1b2c3d4
335 status:
336 phase: Running
337 `, `
338 apiVersion: v1
339 kind: Pod
340 metadata:
341 name: emojivoto-not-meshed
342 namespace: emojivoto
343 labels:
344 app: emoji-svc
345 pod-template-hash: 3c2b1a
346 ownerReferences:
347 - apiVersion: apps/v1
348 uid: a1b2c3d4
349 status:
350 phase: Running
351 `, `
352 apiVersion: v1
353 kind: Pod
354 metadata:
355 name: emojivoto-meshed-not-running
356 namespace: emojivoto
357 labels:
358 app: emoji-svc
359 linkerd.io/control-plane-ns: linkerd
360 pod-template-hash: 3c2b1a
361 ownerReferences:
362 - apiVersion: apps/v1
363 uid: a1b2c3d4
364 status:
365 phase: Completed
366 `,
367 },
368 mockPromResponse: prometheusMetric("emoji", "deployment"),
369 },
370 req: &pb.StatSummaryRequest{
371 Selector: &pb.ResourceSelection{
372 Resource: &pb.Resource{
373 Namespace: "emojivoto",
374 Type: pkgK8s.Deployment,
375 },
376 },
377 TimeWindow: "1m",
378 },
379 expectedResponse: GenStatSummaryResponse("emoji", pkgK8s.Deployment, []string{"emojivoto"}, &PodCounts{
380 MeshedPods: 1,
381 RunningPods: 2,
382 FailedPods: 0,
383 }, true, false),
384 },
385 }
386
387 testStatSummary(t, expectations)
388 })
389
390 t.Run("Successfully performs a query based on resource type DaemonSet", func(t *testing.T) {
391 expectations := []statSumExpected{
392 {
393 expectedStatRPC: expectedStatRPC{
394 err: nil,
395 k8sConfigs: []string{`
396 apiVersion: apps/v1
397 kind: DaemonSet
398 metadata:
399 name: emoji
400 namespace: emojivoto
401 spec:
402 selector:
403 matchLabels:
404 app: emoji-svc
405 strategy: {}
406 template:
407 spec:
408 containers:
409 - image: buoyantio/emojivoto-emoji-svc:v10
410 `, `
411 apiVersion: v1
412 kind: Pod
413 metadata:
414 name: emojivoto-meshed
415 namespace: emojivoto
416 labels:
417 app: emoji-svc
418 linkerd.io/control-plane-ns: linkerd
419 status:
420 phase: Running
421 `, `
422 apiVersion: v1
423 kind: Pod
424 metadata:
425 name: emojivoto-not-meshed
426 namespace: emojivoto
427 labels:
428 app: emoji-svc
429 status:
430 phase: Running
431 `, `
432 apiVersion: v1
433 kind: Pod
434 metadata:
435 name: emojivoto-meshed-not-running
436 namespace: emojivoto
437 labels:
438 app: emoji-svc
439 linkerd.io/control-plane-ns: linkerd
440 status:
441 phase: Completed
442 `,
443 },
444 mockPromResponse: prometheusMetric("emoji", "daemonset"),
445 },
446 req: &pb.StatSummaryRequest{
447 Selector: &pb.ResourceSelection{
448 Resource: &pb.Resource{
449 Namespace: "emojivoto",
450 Type: pkgK8s.DaemonSet,
451 },
452 },
453 TimeWindow: "1m",
454 },
455 expectedResponse: GenStatSummaryResponse("emoji", pkgK8s.DaemonSet, []string{"emojivoto"}, &PodCounts{
456 MeshedPods: 1,
457 RunningPods: 2,
458 FailedPods: 0,
459 }, true, false),
460 },
461 }
462
463 testStatSummary(t, expectations)
464 })
465
466 t.Run("Successfully performs a query based on resource type Job", func(t *testing.T) {
467 expectations := []statSumExpected{
468 {
469 expectedStatRPC: expectedStatRPC{
470 err: nil,
471 k8sConfigs: []string{`
472 apiVersion: batch/v1
473 kind: Job
474 metadata:
475 name: emoji
476 namespace: emojivoto
477 spec:
478 selector:
479 matchLabels:
480 app: emoji-job
481 strategy: {}
482 template:
483 spec:
484 containers:
485 - image: buoyantio/emojivoto-emoji-svc:v10
486 `, `
487 apiVersion: v1
488 kind: Pod
489 metadata:
490 name: emojivoto-meshed
491 namespace: emojivoto
492 labels:
493 app: emoji-job
494 linkerd.io/control-plane-ns: linkerd
495 status:
496 phase: Running
497 `, `
498 apiVersion: v1
499 kind: Pod
500 metadata:
501 name: emojivoto-not-meshed
502 namespace: emojivoto
503 labels:
504 app: emoji-job
505 status:
506 phase: Running
507 `, `
508 apiVersion: v1
509 kind: Pod
510 metadata:
511 name: emojivoto-meshed-not-running
512 namespace: emojivoto
513 labels:
514 app: emoji-job
515 linkerd.io/control-plane-ns: linkerd
516 status:
517 phase: Completed
518 `,
519 },
520 mockPromResponse: prometheusMetric("emoji", "k8s_job"),
521 },
522 req: &pb.StatSummaryRequest{
523 Selector: &pb.ResourceSelection{
524 Resource: &pb.Resource{
525 Namespace: "emojivoto",
526 Type: pkgK8s.Job,
527 },
528 },
529 TimeWindow: "1m",
530 },
531 expectedResponse: GenStatSummaryResponse("emoji", pkgK8s.Job, []string{"emojivoto"}, &PodCounts{
532 MeshedPods: 1,
533 RunningPods: 2,
534 FailedPods: 0,
535 }, true, false),
536 },
537 }
538
539 testStatSummary(t, expectations)
540 })
541
542 t.Run("Successfully performs a query based on resource type StatefulSet", func(t *testing.T) {
543 expectations := []statSumExpected{
544 {
545 expectedStatRPC: expectedStatRPC{
546 err: nil,
547 k8sConfigs: []string{`
548 apiVersion: apps/v1
549 kind: StatefulSet
550 metadata:
551 name: redis
552 namespace: emojivoto
553 labels:
554 app: redis
555 linkerd.io/control-plane-ns: linkerd
556 spec:
557 replicas: 3
558 serviceName: redis
559 selector:
560 matchLabels:
561 app: redis
562 template:
563 metadata:
564 labels:
565 app: redis
566 spec:
567 containers:
568 - image: redis
569 volumeMounts:
570 - name: data
571 mountPath: /var/lib/redis
572 volumeClaimTemplates:
573 - metadata:
574 name: data
575 spec:
576 accessModes: ["ReadWriteOnce"]
577 resources:
578 requests:
579 storage: 10Gi
580 `, `
581 apiVersion: v1
582 kind: Pod
583 metadata:
584 name: redis-0
585 namespace: emojivoto
586 labels:
587 app: redis
588 linkerd.io/control-plane-ns: linkerd
589 status:
590 phase: Running
591 `, `
592 apiVersion: v1
593 kind: Pod
594 metadata:
595 name: redis-1
596 namespace: emojivoto
597 labels:
598 app: redis
599 linkerd.io/control-plane-ns: linkerd
600 status:
601 phase: Running
602 `, `
603 apiVersion: v1
604 kind: Pod
605 metadata:
606 name: redis-2
607 namespace: emojivoto
608 labels:
609 app: redis
610 linkerd.io/control-plane-ns: linkerd
611 status:
612 phase: Running
613 `,
614 },
615 mockPromResponse: prometheusMetric("redis", "statefulset"),
616 },
617 req: &pb.StatSummaryRequest{
618 Selector: &pb.ResourceSelection{
619 Resource: &pb.Resource{
620 Namespace: "emojivoto",
621 Type: pkgK8s.StatefulSet,
622 },
623 },
624 TimeWindow: "1m",
625 },
626 expectedResponse: GenStatSummaryResponse("redis", pkgK8s.StatefulSet, []string{"emojivoto"}, &PodCounts{
627 MeshedPods: 3,
628 RunningPods: 3,
629 FailedPods: 0,
630 }, true, false),
631 },
632 }
633
634 testStatSummary(t, expectations)
635 })
636
637 t.Run("Queries prometheus for TCP stats when requested", func(t *testing.T) {
638
639 expectations := []statSumExpected{
640 {
641 expectedStatRPC: expectedStatRPC{
642 err: nil,
643 k8sConfigs: []string{`
644 apiVersion: v1
645 kind: Pod
646 metadata:
647 name: emojivoto-1
648 namespace: emojivoto
649 labels:
650 app: emoji-svc
651 linkerd.io/control-plane-ns: linkerd
652 status:
653 phase: Running
654 `,
655 },
656 mockPromResponse: prometheusMetric("emojivoto-1", "pod"),
657 expectedPrometheusQueries: []string{
658 `histogram_quantile(0.5, sum(irate(response_latency_ms_bucket{direction="inbound", namespace="emojivoto", pod="emojivoto-1"}[1m])) by (le, namespace, pod))`,
659 `histogram_quantile(0.95, sum(irate(response_latency_ms_bucket{direction="inbound", namespace="emojivoto", pod="emojivoto-1"}[1m])) by (le, namespace, pod))`,
660 `histogram_quantile(0.99, sum(irate(response_latency_ms_bucket{direction="inbound", namespace="emojivoto", pod="emojivoto-1"}[1m])) by (le, namespace, pod))`,
661 `sum(increase(response_total{direction="inbound", namespace="emojivoto", pod="emojivoto-1"}[1m])) by (namespace, pod, classification, tls)`,
662 `sum(tcp_open_connections{direction="inbound", namespace="emojivoto", pod="emojivoto-1"}) by (namespace, pod)`,
663 `sum(increase(tcp_read_bytes_total{direction="inbound", namespace="emojivoto", peer="src", pod="emojivoto-1"}[1m])) by (namespace, pod)`,
664 `sum(increase(tcp_write_bytes_total{direction="inbound", namespace="emojivoto", peer="src", pod="emojivoto-1"}[1m])) by (namespace, pod)`,
665 },
666 },
667 req: &pb.StatSummaryRequest{
668 Selector: &pb.ResourceSelection{
669 Resource: &pb.Resource{
670 Name: "emojivoto-1",
671 Namespace: "emojivoto",
672 Type: pkgK8s.Pod,
673 },
674 },
675 TimeWindow: "1m",
676 TcpStats: true,
677 },
678 expectedResponse: GenStatSummaryResponse("emojivoto-1", pkgK8s.Pod, []string{"emojivoto"}, &PodCounts{
679 Status: "Running",
680 MeshedPods: 1,
681 RunningPods: 1,
682 FailedPods: 0,
683 }, true, true),
684 },
685 }
686
687 testStatSummary(t, expectations)
688 })
689
690 t.Run("Queries prometheus for outbound TCP stats if --to resource is specified", func(t *testing.T) {
691
692 expectations := []statSumExpected{
693 {
694
695 expectedStatRPC: expectedStatRPC{
696 err: nil,
697 k8sConfigs: []string{`
698 apiVersion: v1
699 kind: Pod
700 metadata:
701 name: emojivoto-1
702 namespace: emojivoto
703 labels:
704 app: emoji-svc
705 linkerd.io/control-plane-ns: linkerd
706 status:
707 phase: Running
708 `,
709 },
710 mockPromResponse: prometheusMetric("emojivoto-1", "pod"),
711 expectedPrometheusQueries: []string{
712 `histogram_quantile(0.5, sum(irate(response_latency_ms_bucket{direction="outbound", dst_namespace="emojivoto", dst_pod="emojivoto-2", namespace="emojivoto", pod="emojivoto-1"}[1m])) by (le, namespace, pod))`,
713 `histogram_quantile(0.95, sum(irate(response_latency_ms_bucket{direction="outbound", dst_namespace="emojivoto", dst_pod="emojivoto-2", namespace="emojivoto", pod="emojivoto-1"}[1m])) by (le, namespace, pod))`,
714 `histogram_quantile(0.99, sum(irate(response_latency_ms_bucket{direction="outbound", dst_namespace="emojivoto", dst_pod="emojivoto-2", namespace="emojivoto", pod="emojivoto-1"}[1m])) by (le, namespace, pod))`,
715 `sum(increase(response_total{direction="outbound", dst_namespace="emojivoto", dst_pod="emojivoto-2", namespace="emojivoto", pod="emojivoto-1"}[1m])) by (namespace, pod, classification, tls)`,
716 `sum(tcp_open_connections{direction="outbound", dst_namespace="emojivoto", dst_pod="emojivoto-2", namespace="emojivoto", pod="emojivoto-1"}) by (namespace, pod)`,
717 `sum(increase(tcp_read_bytes_total{direction="outbound", dst_namespace="emojivoto", dst_pod="emojivoto-2", namespace="emojivoto", peer="dst", pod="emojivoto-1"}[1m])) by (namespace, pod)`,
718 `sum(increase(tcp_write_bytes_total{direction="outbound", dst_namespace="emojivoto", dst_pod="emojivoto-2", namespace="emojivoto", peer="dst", pod="emojivoto-1"}[1m])) by (namespace, pod)`,
719 },
720 },
721 req: &pb.StatSummaryRequest{
722 Selector: &pb.ResourceSelection{
723 Resource: &pb.Resource{
724 Name: "emojivoto-1",
725 Namespace: "emojivoto",
726 Type: pkgK8s.Pod,
727 },
728 },
729 TimeWindow: "1m",
730 TcpStats: true,
731 Outbound: &pb.StatSummaryRequest_ToResource{
732 ToResource: &pb.Resource{
733 Name: "emojivoto-2",
734 Namespace: "emojivoto",
735 Type: pkgK8s.Pod,
736 },
737 },
738 },
739 expectedResponse: GenStatSummaryResponse("emojivoto-1", pkgK8s.Pod, []string{"emojivoto"}, &PodCounts{
740 Status: "Running",
741 MeshedPods: 1,
742 RunningPods: 1,
743 FailedPods: 0,
744 }, true, true),
745 },
746 }
747
748 testStatSummary(t, expectations)
749 })
750
751 t.Run("Queries prometheus for a specific resource if name is specified", func(t *testing.T) {
752 expectations := []statSumExpected{
753 {
754 expectedStatRPC: expectedStatRPC{
755 err: nil,
756 k8sConfigs: []string{`
757 apiVersion: v1
758 kind: Pod
759 metadata:
760 name: emojivoto-1
761 namespace: emojivoto
762 labels:
763 app: emoji-svc
764 linkerd.io/control-plane-ns: linkerd
765 status:
766 phase: Running
767 `,
768 },
769 mockPromResponse: prometheusMetric("emojivoto-1", "pod"),
770 expectedPrometheusQueries: []string{
771 `histogram_quantile(0.5, sum(irate(response_latency_ms_bucket{direction="inbound", namespace="emojivoto", pod="emojivoto-1"}[1m])) by (le, namespace, pod))`,
772 `histogram_quantile(0.95, sum(irate(response_latency_ms_bucket{direction="inbound", namespace="emojivoto", pod="emojivoto-1"}[1m])) by (le, namespace, pod))`,
773 `histogram_quantile(0.99, sum(irate(response_latency_ms_bucket{direction="inbound", namespace="emojivoto", pod="emojivoto-1"}[1m])) by (le, namespace, pod))`,
774 `sum(increase(response_total{direction="inbound", namespace="emojivoto", pod="emojivoto-1"}[1m])) by (namespace, pod, classification, tls)`,
775 },
776 },
777 req: &pb.StatSummaryRequest{
778 Selector: &pb.ResourceSelection{
779 Resource: &pb.Resource{
780 Name: "emojivoto-1",
781 Namespace: "emojivoto",
782 Type: pkgK8s.Pod,
783 },
784 },
785 TimeWindow: "1m",
786 },
787 expectedResponse: GenStatSummaryResponse("emojivoto-1", pkgK8s.Pod, []string{"emojivoto"}, &PodCounts{
788 Status: "Running",
789 MeshedPods: 1,
790 RunningPods: 1,
791 FailedPods: 0,
792 }, true, false),
793 },
794 }
795
796 testStatSummary(t, expectations)
797 })
798
799 t.Run("Queries prometheus for outbound metrics if from resource is specified, ignores resource name", func(t *testing.T) {
800 expectations := []statSumExpected{
801 {
802 expectedStatRPC: expectedStatRPC{
803 err: nil,
804 k8sConfigs: []string{`
805 apiVersion: v1
806 kind: Pod
807 metadata:
808 name: emojivoto-1
809 namespace: emojivoto
810 labels:
811 app: emoji-svc
812 linkerd.io/control-plane-ns: linkerd
813 status:
814 phase: Running
815 `,
816 },
817 mockPromResponse: prometheusMetric("emojivoto-2", "pod"),
818 expectedPrometheusQueries: []string{
819 `histogram_quantile(0.5, sum(irate(response_latency_ms_bucket{direction="outbound", dst_namespace="emojivoto", dst_pod="emojivoto-1", namespace="emojivoto", pod="emojivoto-2"}[1m])) by (le, dst_namespace, dst_pod))`,
820 `histogram_quantile(0.95, sum(irate(response_latency_ms_bucket{direction="outbound", dst_namespace="emojivoto", dst_pod="emojivoto-1", namespace="emojivoto", pod="emojivoto-2"}[1m])) by (le, dst_namespace, dst_pod))`,
821 `histogram_quantile(0.99, sum(irate(response_latency_ms_bucket{direction="outbound", dst_namespace="emojivoto", dst_pod="emojivoto-1", namespace="emojivoto", pod="emojivoto-2"}[1m])) by (le, dst_namespace, dst_pod))`,
822 `sum(increase(response_total{direction="outbound", dst_namespace="emojivoto", dst_pod="emojivoto-1", namespace="emojivoto", pod="emojivoto-2"}[1m])) by (dst_namespace, dst_pod, classification, tls)`,
823 },
824 },
825 req: &pb.StatSummaryRequest{
826 Selector: &pb.ResourceSelection{
827 Resource: &pb.Resource{
828 Name: "emojivoto-1",
829 Namespace: "emojivoto",
830 Type: pkgK8s.Pod,
831 },
832 },
833 TimeWindow: "1m",
834 Outbound: &pb.StatSummaryRequest_FromResource{
835 FromResource: &pb.Resource{
836 Name: "emojivoto-2",
837 Namespace: "emojivoto",
838 Type: pkgK8s.Pod,
839 },
840 },
841 },
842 expectedResponse: genEmptyResponse(),
843 },
844 }
845
846 testStatSummary(t, expectations)
847 })
848
849 t.Run("Queries prometheus for outbound metrics if --to resource is specified", func(t *testing.T) {
850 expectations := []statSumExpected{
851 {
852 expectedStatRPC: expectedStatRPC{
853 err: nil,
854 k8sConfigs: []string{`
855 apiVersion: v1
856 kind: Pod
857 metadata:
858 name: emojivoto-1
859 namespace: emojivoto
860 labels:
861 app: emoji-svc
862 linkerd.io/control-plane-ns: linkerd
863 status:
864 phase: Running
865 `,
866 },
867 mockPromResponse: model.Vector{
868 genPromSample("emojivoto-1", "pod", "emojivoto", false),
869 },
870 expectedPrometheusQueries: []string{
871 `histogram_quantile(0.5, sum(irate(response_latency_ms_bucket{direction="outbound", dst_namespace="emojivoto", dst_pod="emojivoto-2", namespace="emojivoto", pod="emojivoto-1"}[1m])) by (le, namespace, pod))`,
872 `histogram_quantile(0.95, sum(irate(response_latency_ms_bucket{direction="outbound", dst_namespace="emojivoto", dst_pod="emojivoto-2", namespace="emojivoto", pod="emojivoto-1"}[1m])) by (le, namespace, pod))`,
873 `histogram_quantile(0.99, sum(irate(response_latency_ms_bucket{direction="outbound", dst_namespace="emojivoto", dst_pod="emojivoto-2", namespace="emojivoto", pod="emojivoto-1"}[1m])) by (le, namespace, pod))`,
874 `sum(increase(response_total{direction="outbound", dst_namespace="emojivoto", dst_pod="emojivoto-2", namespace="emojivoto", pod="emojivoto-1"}[1m])) by (namespace, pod, classification, tls)`,
875 },
876 },
877 req: &pb.StatSummaryRequest{
878 Selector: &pb.ResourceSelection{
879 Resource: &pb.Resource{
880 Name: "emojivoto-1",
881 Namespace: "emojivoto",
882 Type: pkgK8s.Pod,
883 },
884 },
885 TimeWindow: "1m",
886 Outbound: &pb.StatSummaryRequest_ToResource{
887 ToResource: &pb.Resource{
888 Name: "emojivoto-2",
889 Namespace: "emojivoto",
890 Type: pkgK8s.Pod,
891 },
892 },
893 },
894 expectedResponse: GenStatSummaryResponse("emojivoto-1", pkgK8s.Pod, []string{"emojivoto"}, &PodCounts{
895 Status: "Running",
896 MeshedPods: 1,
897 RunningPods: 1,
898 FailedPods: 0,
899 }, true, false),
900 },
901 }
902
903 testStatSummary(t, expectations)
904 })
905
906 t.Run("Queries prometheus for outbound metrics if --to resource is specified and --to-namespace is different from the resource namespace", func(t *testing.T) {
907 expectations := []statSumExpected{
908 {
909 expectedStatRPC: expectedStatRPC{
910 err: nil,
911 k8sConfigs: []string{`
912 apiVersion: v1
913 kind: Pod
914 metadata:
915 name: emojivoto-1
916 namespace: emojivoto
917 labels:
918 app: emoji-svc
919 linkerd.io/control-plane-ns: linkerd
920 status:
921 phase: Running
922 `,
923 },
924 mockPromResponse: model.Vector{
925 genPromSample("emojivoto-1", "pod", "emojivoto", false),
926 },
927 expectedPrometheusQueries: []string{
928 `histogram_quantile(0.5, sum(irate(response_latency_ms_bucket{direction="outbound", dst_namespace="totallydifferent", dst_pod="emojivoto-2", namespace="emojivoto", pod="emojivoto-1"}[1m])) by (le, namespace, pod))`,
929 `histogram_quantile(0.95, sum(irate(response_latency_ms_bucket{direction="outbound", dst_namespace="totallydifferent", dst_pod="emojivoto-2", namespace="emojivoto", pod="emojivoto-1"}[1m])) by (le, namespace, pod))`,
930 `histogram_quantile(0.99, sum(irate(response_latency_ms_bucket{direction="outbound", dst_namespace="totallydifferent", dst_pod="emojivoto-2", namespace="emojivoto", pod="emojivoto-1"}[1m])) by (le, namespace, pod))`,
931 `sum(increase(response_total{direction="outbound", dst_namespace="totallydifferent", dst_pod="emojivoto-2", namespace="emojivoto", pod="emojivoto-1"}[1m])) by (namespace, pod, classification, tls)`,
932 },
933 },
934 req: &pb.StatSummaryRequest{
935 Selector: &pb.ResourceSelection{
936 Resource: &pb.Resource{
937 Name: "emojivoto-1",
938 Namespace: "emojivoto",
939 Type: pkgK8s.Pod,
940 },
941 },
942 TimeWindow: "1m",
943 Outbound: &pb.StatSummaryRequest_ToResource{
944 ToResource: &pb.Resource{
945 Name: "emojivoto-2",
946 Namespace: "totallydifferent",
947 Type: pkgK8s.Pod,
948 },
949 },
950 },
951 expectedResponse: GenStatSummaryResponse("emojivoto-1", pkgK8s.Pod, []string{"emojivoto"}, &PodCounts{
952 Status: "Running",
953 MeshedPods: 1,
954 RunningPods: 1,
955 FailedPods: 0,
956 }, true, false),
957 },
958 }
959
960 testStatSummary(t, expectations)
961 })
962
963 t.Run("Queries prometheus for outbound metrics if --from resource is specified", func(t *testing.T) {
964 expectations := []statSumExpected{
965 {
966 expectedStatRPC: expectedStatRPC{
967 err: nil,
968 k8sConfigs: []string{`
969 apiVersion: v1
970 kind: Pod
971 metadata:
972 name: emojivoto-1
973 namespace: emojivoto
974 labels:
975 app: emoji-svc
976 linkerd.io/control-plane-ns: linkerd
977 status:
978 phase: Running
979 `, `
980 apiVersion: v1
981 kind: Pod
982 metadata:
983 name: emojivoto-2
984 namespace: totallydifferent
985 labels:
986 app: emoji-svc
987 linkerd.io/control-plane-ns: linkerd
988 status:
989 phase: Running
990 `,
991 },
992 mockPromResponse: model.Vector{
993 genPromSample("emojivoto-1", "pod", "emojivoto", true),
994 },
995 expectedPrometheusQueries: []string{
996 `histogram_quantile(0.5, sum(irate(response_latency_ms_bucket{direction="outbound", pod="emojivoto-2"}[1m])) by (le, dst_namespace, dst_pod))`,
997 `histogram_quantile(0.95, sum(irate(response_latency_ms_bucket{direction="outbound", pod="emojivoto-2"}[1m])) by (le, dst_namespace, dst_pod))`,
998 `histogram_quantile(0.99, sum(irate(response_latency_ms_bucket{direction="outbound", pod="emojivoto-2"}[1m])) by (le, dst_namespace, dst_pod))`,
999 `sum(increase(response_total{direction="outbound", pod="emojivoto-2"}[1m])) by (dst_namespace, dst_pod, classification, tls)`,
1000 },
1001 },
1002 req: &pb.StatSummaryRequest{
1003 Selector: &pb.ResourceSelection{
1004 Resource: &pb.Resource{
1005 Name: "",
1006 Namespace: "emojivoto",
1007 Type: pkgK8s.Pod,
1008 },
1009 },
1010 TimeWindow: "1m",
1011 Outbound: &pb.StatSummaryRequest_FromResource{
1012 FromResource: &pb.Resource{
1013 Name: "emojivoto-2",
1014 Namespace: "",
1015 Type: pkgK8s.Pod,
1016 },
1017 },
1018 },
1019 expectedResponse: GenStatSummaryResponse("emojivoto-1", pkgK8s.Pod, []string{"emojivoto"}, &PodCounts{
1020 Status: "Running",
1021 MeshedPods: 1,
1022 RunningPods: 1,
1023 FailedPods: 0,
1024 }, true, false),
1025 },
1026 }
1027
1028 testStatSummary(t, expectations)
1029 })
1030
1031 t.Run("Queries prometheus for outbound metrics if --from resource is specified and --from-namespace is different from the resource namespace", func(t *testing.T) {
1032 expectations := []statSumExpected{
1033 {
1034 expectedStatRPC: expectedStatRPC{
1035 err: nil,
1036 k8sConfigs: []string{`
1037 apiVersion: v1
1038 kind: Pod
1039 metadata:
1040 name: emojivoto-1
1041 namespace: emojivoto
1042 labels:
1043 app: emoji-svc
1044 linkerd.io/control-plane-ns: linkerd
1045 status:
1046 phase: Running
1047 `, `
1048 apiVersion: v1
1049 kind: Pod
1050 metadata:
1051 name: emojivoto-2
1052 namespace: totallydifferent
1053 labels:
1054 app: emoji-svc
1055 linkerd.io/control-plane-ns: linkerd
1056 status:
1057 phase: Running
1058 `,
1059 },
1060 mockPromResponse: model.Vector{
1061 genPromSample("emojivoto-1", "pod", "emojivoto", true),
1062 },
1063 expectedPrometheusQueries: []string{
1064 `histogram_quantile(0.5, sum(irate(response_latency_ms_bucket{direction="outbound", dst_namespace="emojivoto", dst_pod="emojivoto-1", namespace="totallydifferent", pod="emojivoto-2"}[1m])) by (le, dst_namespace, dst_pod))`,
1065 `histogram_quantile(0.95, sum(irate(response_latency_ms_bucket{direction="outbound", dst_namespace="emojivoto", dst_pod="emojivoto-1", namespace="totallydifferent", pod="emojivoto-2"}[1m])) by (le, dst_namespace, dst_pod))`,
1066 `histogram_quantile(0.99, sum(irate(response_latency_ms_bucket{direction="outbound", dst_namespace="emojivoto", dst_pod="emojivoto-1", namespace="totallydifferent", pod="emojivoto-2"}[1m])) by (le, dst_namespace, dst_pod))`,
1067 `sum(increase(response_total{direction="outbound", dst_namespace="emojivoto", dst_pod="emojivoto-1", namespace="totallydifferent", pod="emojivoto-2"}[1m])) by (dst_namespace, dst_pod, classification, tls)`,
1068 },
1069 },
1070 req: &pb.StatSummaryRequest{
1071 Selector: &pb.ResourceSelection{
1072 Resource: &pb.Resource{
1073 Name: "emojivoto-1",
1074 Namespace: "emojivoto",
1075 Type: pkgK8s.Pod,
1076 },
1077 },
1078 TimeWindow: "1m",
1079 Outbound: &pb.StatSummaryRequest_FromResource{
1080 FromResource: &pb.Resource{
1081 Name: "emojivoto-2",
1082 Namespace: "totallydifferent",
1083 Type: pkgK8s.Pod,
1084 },
1085 },
1086 },
1087 expectedResponse: GenStatSummaryResponse("emojivoto-1", pkgK8s.Pod, []string{"emojivoto"}, &PodCounts{
1088 Status: "Running",
1089 MeshedPods: 1,
1090 RunningPods: 1,
1091 FailedPods: 0,
1092 }, true, false),
1093 },
1094 }
1095
1096 testStatSummary(t, expectations)
1097 })
1098
1099 t.Run("Successfully queries for resource type 'all'", func(t *testing.T) {
1100 expectations := []statSumExpected{
1101 {
1102 expectedStatRPC: expectedStatRPC{
1103 err: nil,
1104 k8sConfigs: []string{`
1105 apiVersion: apps/v1
1106 kind: Deployment
1107 metadata:
1108 name: emoji-deploy
1109 namespace: emojivoto
1110 uid: a1b2c3
1111 spec:
1112 selector:
1113 matchLabels:
1114 app: emoji-svc
1115 strategy: {}
1116 template:
1117 spec:
1118 containers:
1119 - image: buoyantio/emojivoto-emoji-svc:v10
1120 `, `
1121 apiVersion: apps/v1
1122 kind: ReplicaSet
1123 metadata:
1124 uid: a1b2c3d4
1125 annotations:
1126 deployment.kubernetes.io/revision: "2"
1127 name: emojivoto-meshed_2
1128 namespace: emojivoto
1129 labels:
1130 app: emoji-svc
1131 pod-template-hash: 3c2b1a
1132 ownerReferences:
1133 - apiVersion: apps/v1
1134 uid: a1b2c3
1135 spec:
1136 selector:
1137 matchLabels:
1138 app: emoji-svc
1139 pod-template-hash: 3c2b1a
1140 `, `
1141 apiVersion: v1
1142 kind: Service
1143 metadata:
1144 name: emoji-svc
1145 namespace: emojivoto
1146 spec:
1147 clusterIP: None
1148 selector:
1149 app: emoji-svc
1150 `, `
1151 apiVersion: v1
1152 kind: Pod
1153 metadata:
1154 name: emojivoto-pod-1
1155 namespace: not-right-emojivoto-namespace
1156 labels:
1157 app: emoji-svc
1158 linkerd.io/control-plane-ns: linkerd
1159 status:
1160 phase: Running
1161 `, `
1162 apiVersion: v1
1163 kind: Pod
1164 metadata:
1165 name: emojivoto-pod-2
1166 namespace: emojivoto
1167 labels:
1168 app: emoji-svc
1169 linkerd.io/control-plane-ns: linkerd
1170 pod-template-hash: 3c2b1a
1171 ownerReferences:
1172 - apiVersion: apps/v1
1173 uid: a1b2c3d4
1174 status:
1175 phase: Running
1176 `,
1177 },
1178 mockPromResponse: prometheusMetric("emoji-deploy", "deployment"),
1179 },
1180 req: &pb.StatSummaryRequest{
1181 Selector: &pb.ResourceSelection{
1182 Resource: &pb.Resource{
1183 Namespace: "emojivoto",
1184 Type: pkgK8s.All,
1185 },
1186 },
1187 TimeWindow: "1m",
1188 },
1189
1190 expectedResponse: &pb.StatSummaryResponse{
1191 Response: &pb.StatSummaryResponse_Ok_{
1192 Ok: &pb.StatSummaryResponse_Ok{
1193 StatTables: []*pb.StatTable{
1194 {
1195 Table: &pb.StatTable_PodGroup_{
1196 PodGroup: &pb.StatTable_PodGroup{
1197 Rows: []*pb.StatTable_PodGroup_Row{},
1198 },
1199 },
1200 },
1201 {
1202 Table: &pb.StatTable_PodGroup_{
1203 PodGroup: &pb.StatTable_PodGroup{
1204 Rows: []*pb.StatTable_PodGroup_Row{},
1205 },
1206 },
1207 },
1208 {
1209 Table: &pb.StatTable_PodGroup_{
1210 PodGroup: &pb.StatTable_PodGroup{
1211 Rows: []*pb.StatTable_PodGroup_Row{},
1212 },
1213 },
1214 },
1215 {
1216 Table: &pb.StatTable_PodGroup_{
1217 PodGroup: &pb.StatTable_PodGroup{
1218 Rows: []*pb.StatTable_PodGroup_Row{},
1219 },
1220 },
1221 },
1222 {
1223 Table: &pb.StatTable_PodGroup_{
1224 PodGroup: &pb.StatTable_PodGroup{
1225 Rows: []*pb.StatTable_PodGroup_Row{},
1226 },
1227 },
1228 },
1229 {
1230 Table: &pb.StatTable_PodGroup_{
1231 PodGroup: &pb.StatTable_PodGroup{
1232 Rows: []*pb.StatTable_PodGroup_Row{
1233 {
1234 Resource: &pb.Resource{
1235 Namespace: "emojivoto",
1236 Type: pkgK8s.Authority,
1237 },
1238 TimeWindow: "1m",
1239 Stats: &pb.BasicStats{
1240 SuccessCount: 123,
1241 FailureCount: 0,
1242 LatencyMsP50: 123,
1243 LatencyMsP95: 123,
1244 LatencyMsP99: 123,
1245 },
1246 },
1247 },
1248 },
1249 },
1250 },
1251 {
1252 Table: &pb.StatTable_PodGroup_{
1253 PodGroup: &pb.StatTable_PodGroup{
1254 Rows: []*pb.StatTable_PodGroup_Row{
1255 {
1256 Resource: &pb.Resource{
1257 Namespace: "emojivoto",
1258 Type: pkgK8s.Deployment,
1259 Name: "emoji-deploy",
1260 },
1261 Stats: &pb.BasicStats{
1262 SuccessCount: 123,
1263 FailureCount: 0,
1264 LatencyMsP50: 123,
1265 LatencyMsP95: 123,
1266 LatencyMsP99: 123,
1267 },
1268 TimeWindow: "1m",
1269 MeshedPodCount: 1,
1270 RunningPodCount: 1,
1271 },
1272 },
1273 },
1274 },
1275 },
1276 {
1277 Table: &pb.StatTable_PodGroup_{
1278 PodGroup: &pb.StatTable_PodGroup{
1279 Rows: []*pb.StatTable_PodGroup_Row{
1280 {
1281 Resource: &pb.Resource{
1282 Namespace: "emojivoto",
1283 Type: pkgK8s.Pod,
1284 Name: "emojivoto-pod-2",
1285 },
1286 Status: "Running",
1287 TimeWindow: "1m",
1288 MeshedPodCount: 1,
1289 RunningPodCount: 1,
1290 },
1291 },
1292 },
1293 },
1294 },
1295 {
1296 Table: &pb.StatTable_PodGroup_{
1297 PodGroup: &pb.StatTable_PodGroup{
1298 Rows: []*pb.StatTable_PodGroup_Row{
1299 {
1300 Resource: &pb.Resource{
1301 Namespace: "emojivoto",
1302 Type: pkgK8s.ReplicaSet,
1303 Name: "emojivoto-meshed_2",
1304 },
1305 TimeWindow: "1m",
1306 MeshedPodCount: 1,
1307 RunningPodCount: 1,
1308 },
1309 },
1310 },
1311 },
1312 },
1313 {
1314 Table: &pb.StatTable_PodGroup_{
1315 PodGroup: &pb.StatTable_PodGroup{
1316 Rows: []*pb.StatTable_PodGroup_Row{
1317 {
1318 Resource: &pb.Resource{
1319 Type: pkgK8s.Service,
1320 },
1321 TimeWindow: "1m",
1322 Stats: &pb.BasicStats{
1323 SuccessCount: 123,
1324 FailureCount: 0,
1325 LatencyMsP50: 123,
1326 LatencyMsP95: 123,
1327 LatencyMsP99: 123,
1328 },
1329 },
1330 },
1331 },
1332 },
1333 },
1334 },
1335 },
1336 },
1337 },
1338 },
1339 }
1340
1341 testStatSummary(t, expectations)
1342 })
1343
1344 t.Run("Given an invalid resource type, returns error", func(t *testing.T) {
1345 k8sAPI, err := k8s.NewFakeAPI()
1346 if err != nil {
1347 t.Fatalf("NewFakeAPI returned an error: %s", err)
1348 }
1349
1350 expectations := []statSumExpected{
1351 {
1352 expectedStatRPC: expectedStatRPC{
1353 err: errors.New("rpc error: code = Unimplemented desc = unimplemented resource type: badtype"),
1354 },
1355 req: &pb.StatSummaryRequest{
1356 Selector: &pb.ResourceSelection{
1357 Resource: &pb.Resource{
1358 Type: "badtype",
1359 },
1360 },
1361 },
1362 },
1363 {
1364 expectedStatRPC: expectedStatRPC{
1365 err: errors.New("rpc error: code = Unimplemented desc = unimplemented resource type: deployments"),
1366 },
1367 req: &pb.StatSummaryRequest{
1368 Selector: &pb.ResourceSelection{
1369 Resource: &pb.Resource{
1370 Type: "deployments",
1371 },
1372 },
1373 },
1374 },
1375 {
1376 expectedStatRPC: expectedStatRPC{
1377 err: errors.New("rpc error: code = Unimplemented desc = unimplemented resource type: po"),
1378 },
1379 req: &pb.StatSummaryRequest{
1380 Selector: &pb.ResourceSelection{
1381 Resource: &pb.Resource{
1382 Type: "po",
1383 },
1384 },
1385 },
1386 },
1387 }
1388
1389 for _, exp := range expectations {
1390 fakeGrpcServer := grpcServer{
1391 prometheusAPI: &prometheus.MockProm{Res: exp.mockPromResponse},
1392 k8sAPI: k8sAPI,
1393 controllerNamespace: "linkerd",
1394 clusterDomain: "mycluster.local",
1395 ignoredNamespaces: []string{},
1396 }
1397
1398 _, err := fakeGrpcServer.StatSummary(context.TODO(), exp.req)
1399 if err != nil || exp.err != nil {
1400 if (err == nil && exp.err != nil) ||
1401 (err != nil && exp.err == nil) ||
1402 (err.Error() != exp.err.Error()) {
1403 t.Fatalf("Unexpected error (Expected: %s, Got: %s)", exp.err, err)
1404 }
1405 }
1406 }
1407 })
1408
1409 t.Run("Validates service stat requests", func(t *testing.T) {
1410 k8sAPI, err := k8s.NewFakeAPI()
1411 if err != nil {
1412 t.Fatalf("NewFakeAPI returned an error: %s", err)
1413 }
1414 fakeGrpcServer := grpcServer{
1415 prometheusAPI: &prometheus.MockProm{Res: model.Vector{}},
1416 k8sAPI: k8sAPI,
1417 controllerNamespace: "linkerd",
1418 clusterDomain: "mycluster.local",
1419 ignoredNamespaces: []string{},
1420 }
1421
1422 invalidRequests := []statSumExpected{
1423 {
1424 req: &pb.StatSummaryRequest{},
1425 },
1426 {
1427 req: &pb.StatSummaryRequest{
1428 Selector: &pb.ResourceSelection{
1429 Resource: &pb.Resource{
1430 Type: pkgK8s.Pod,
1431 },
1432 },
1433 Outbound: &pb.StatSummaryRequest_FromResource{
1434 FromResource: &pb.Resource{
1435 Type: pkgK8s.Service,
1436 },
1437 },
1438 },
1439 },
1440 }
1441
1442 for _, invalid := range invalidRequests {
1443 rsp, err := fakeGrpcServer.StatSummary(context.TODO(), invalid.req)
1444
1445 if err != nil || rsp.GetError() == nil {
1446 t.Fatalf("Expected validation error on StatSummaryResponse, got %v, %v", rsp, err)
1447 }
1448 }
1449
1450 validRequests := []statSumExpected{
1451 {
1452 req: &pb.StatSummaryRequest{
1453 Selector: &pb.ResourceSelection{
1454 Resource: &pb.Resource{
1455 Type: pkgK8s.Pod,
1456 },
1457 },
1458 Outbound: &pb.StatSummaryRequest_ToResource{
1459 ToResource: &pb.Resource{
1460 Type: pkgK8s.Service,
1461 },
1462 },
1463 },
1464 },
1465 {
1466 req: &pb.StatSummaryRequest{
1467 Selector: &pb.ResourceSelection{
1468 Resource: &pb.Resource{
1469 Type: pkgK8s.Service,
1470 },
1471 },
1472 },
1473 },
1474 {
1475 req: &pb.StatSummaryRequest{
1476 Selector: &pb.ResourceSelection{
1477 Resource: &pb.Resource{
1478 Type: pkgK8s.Service,
1479 },
1480 },
1481 Outbound: &pb.StatSummaryRequest_ToResource{
1482 ToResource: &pb.Resource{
1483 Type: pkgK8s.Pod,
1484 },
1485 },
1486 },
1487 },
1488 {
1489 req: &pb.StatSummaryRequest{
1490 Selector: &pb.ResourceSelection{
1491 Resource: &pb.Resource{
1492 Type: pkgK8s.Service,
1493 },
1494 },
1495 Outbound: &pb.StatSummaryRequest_FromResource{
1496 FromResource: &pb.Resource{
1497 Type: pkgK8s.Pod,
1498 },
1499 },
1500 },
1501 },
1502 }
1503
1504 for _, valid := range validRequests {
1505 rsp, err := fakeGrpcServer.StatSummary(context.TODO(), valid.req)
1506
1507 if err != nil || rsp.GetError() != nil {
1508 t.Fatalf("Did not expect validation error on StatSummaryResponse, got %v, %v", rsp, err)
1509 }
1510 }
1511 })
1512
1513 t.Run("Return empty stats summary response", func(t *testing.T) {
1514 t.Run("when pod phase is succeeded or failed", func(t *testing.T) {
1515 expectations := []statSumExpected{
1516 {
1517 expectedStatRPC: expectedStatRPC{
1518 err: nil,
1519 k8sConfigs: []string{`
1520 apiVersion: v1
1521 kind: Pod
1522 metadata:
1523 name: emojivoto-00
1524 namespace: emojivoto
1525 labels:
1526 app: emoji-svc
1527 linkerd.io/control-plane-ns: linkerd
1528 status:
1529 phase: Succeeded
1530 `, `
1531 apiVersion: v1
1532 kind: Pod
1533 metadata:
1534 name: emojivoto-01
1535 namespace: emojivoto
1536 labels:
1537 app: emoji-svc
1538 linkerd.io/control-plane-ns: linkerd
1539 status:
1540 phase: Failed
1541 `},
1542 mockPromResponse: model.Vector{},
1543 expectedPrometheusQueries: []string{
1544 `histogram_quantile(0.5, sum(irate(response_latency_ms_bucket{direction="inbound", namespace="emojivoto"}[])) by (le, namespace, pod))`,
1545 `histogram_quantile(0.95, sum(irate(response_latency_ms_bucket{direction="inbound", namespace="emojivoto"}[])) by (le, namespace, pod))`,
1546 `histogram_quantile(0.99, sum(irate(response_latency_ms_bucket{direction="inbound", namespace="emojivoto"}[])) by (le, namespace, pod))`,
1547 `sum(increase(response_total{direction="inbound", namespace="emojivoto"}[])) by (namespace, pod, classification, tls)`,
1548 },
1549 },
1550 req: &pb.StatSummaryRequest{
1551 Selector: &pb.ResourceSelection{
1552 Resource: &pb.Resource{
1553 Namespace: "emojivoto",
1554 Type: pkgK8s.Pod,
1555 },
1556 },
1557 },
1558 expectedResponse: genEmptyResponse(),
1559 },
1560 }
1561
1562 testStatSummary(t, expectations)
1563 })
1564
1565 t.Run("for succeeded or failed replicas of a deployment", func(t *testing.T) {
1566 expectations := []statSumExpected{
1567 {
1568 expectedStatRPC: expectedStatRPC{
1569 err: nil,
1570 k8sConfigs: []string{`
1571 apiVersion: apps/v1
1572 kind: Deployment
1573 metadata:
1574 name: emoji
1575 namespace: emojivoto
1576 uid: a1b2c3
1577 spec:
1578 selector:
1579 matchLabels:
1580 app: emoji-svc
1581 strategy: {}
1582 template:
1583 spec:
1584 containers:
1585 - image: buoyantio/emojivoto-emoji-svc:v10
1586 `, `
1587 apiVersion: apps/v1
1588 kind: ReplicaSet
1589 metadata:
1590 uid: a1b2c3d4
1591 annotations:
1592 deployment.kubernetes.io/revision: "2"
1593 name: emojivoto-meshed_2
1594 namespace: emojivoto
1595 labels:
1596 app: emoji-svc
1597 pod-template-hash: 3c2b1a
1598 ownerReferences:
1599 - apiVersion: apps/v1
1600 uid: a1b2c3
1601 spec:
1602 selector:
1603 matchLabels:
1604 app: emoji-svc
1605 pod-template-hash: 3c2b1a
1606 `, `
1607 apiVersion: v1
1608 kind: Pod
1609 metadata:
1610 name: emojivoto-00
1611 namespace: emojivoto
1612 labels:
1613 app: emoji-svc
1614 linkerd.io/control-plane-ns: linkerd
1615 pod-template-hash: 3c2b1a
1616 ownerReferences:
1617 - apiVersion: apps/v1
1618 uid: a1b2c3d4
1619 status:
1620 phase: Running
1621 `, `
1622 apiVersion: v1
1623 kind: Pod
1624 metadata:
1625 name: emojivoto-01
1626 namespace: emojivoto
1627 labels:
1628 app: emoji-svc
1629 pod-template-hash: 3c2b1a
1630 ownerReferences:
1631 - apiVersion: apps/v1
1632 uid: a1b2c3d4
1633 status:
1634 phase: Running
1635 `, `
1636 apiVersion: v1
1637 kind: Pod
1638 metadata:
1639 name: emojivoto-02
1640 namespace: emojivoto
1641 labels:
1642 app: emoji-svc
1643 linkerd.io/control-plane-ns: linkerd
1644 pod-template-hash: 3c2b1a
1645 ownerReferences:
1646 - apiVersion: apps/v1
1647 uid: a1b2c3d4
1648 status:
1649 phase: Failed
1650 `, `
1651 apiVersion: v1
1652 kind: Pod
1653 metadata:
1654 name: emojivoto-03
1655 namespace: emojivoto
1656 labels:
1657 app: emoji-svc
1658 linkerd.io/control-plane-ns: linkerd
1659 pod-template-hash: 3c2b1a
1660 ownerReferences:
1661 - apiVersion: apps/v1
1662 uid: a1b2c3d4
1663 status:
1664 phase: Succeeded
1665 `},
1666 mockPromResponse: prometheusMetric("emoji", "deployment"),
1667 },
1668 req: &pb.StatSummaryRequest{
1669 Selector: &pb.ResourceSelection{
1670 Resource: &pb.Resource{
1671 Namespace: "emojivoto",
1672 Type: pkgK8s.Deployment,
1673 },
1674 },
1675 TimeWindow: "1m",
1676 },
1677 expectedResponse: GenStatSummaryResponse("emoji", pkgK8s.Deployment, []string{"emojivoto"}, &PodCounts{
1678 MeshedPods: 1,
1679 RunningPods: 2,
1680 FailedPods: 1,
1681 }, true, false),
1682 },
1683 }
1684
1685 testStatSummary(t, expectations)
1686 })
1687 })
1688
1689 t.Run("Queries prometheus for authority stats", func(t *testing.T) {
1690 expectations := []statSumExpected{
1691 {
1692 expectedStatRPC: expectedStatRPC{
1693 err: nil,
1694 k8sConfigs: []string{`
1695 apiVersion: v1
1696 kind: Pod
1697 metadata:
1698 name: emojivoto-1
1699 namespace: emojivoto
1700 labels:
1701 app: emoji-svc
1702 linkerd.io/control-plane-ns: linkerd
1703 status:
1704 phase: Running
1705 `,
1706 },
1707 mockPromResponse: model.Vector{
1708 genPromSample("10.1.1.239:9995", "authority", "linkerd", false),
1709 },
1710 expectedPrometheusQueries: []string{
1711 `histogram_quantile(0.5, sum(irate(response_latency_ms_bucket{direction="inbound", namespace="linkerd"}[1m])) by (le, namespace, authority))`,
1712 `histogram_quantile(0.95, sum(irate(response_latency_ms_bucket{direction="inbound", namespace="linkerd"}[1m])) by (le, namespace, authority))`,
1713 `histogram_quantile(0.99, sum(irate(response_latency_ms_bucket{direction="inbound", namespace="linkerd"}[1m])) by (le, namespace, authority))`,
1714 `sum(increase(response_total{direction="inbound", namespace="linkerd"}[1m])) by (namespace, authority, classification, tls)`,
1715 },
1716 },
1717 req: &pb.StatSummaryRequest{
1718 Selector: &pb.ResourceSelection{
1719 Resource: &pb.Resource{
1720 Namespace: "linkerd",
1721 Type: pkgK8s.Authority,
1722 },
1723 },
1724 TimeWindow: "1m",
1725 },
1726 expectedResponse: GenStatSummaryResponse("10.1.1.239:9995", pkgK8s.Authority, []string{"linkerd"}, nil, true, false),
1727 },
1728 }
1729
1730 testStatSummary(t, expectations)
1731 })
1732
1733 t.Run("Queries prometheus for authority stats when --from deployment is used", func(t *testing.T) {
1734 expectations := []statSumExpected{
1735 {
1736 expectedStatRPC: expectedStatRPC{
1737 err: nil,
1738 k8sConfigs: []string{`
1739 apiVersion: v1
1740 kind: Pod
1741 metadata:
1742 name: emojivoto-1
1743 namespace: emojivoto
1744 labels:
1745 app: emoji-svc
1746 linkerd.io/control-plane-ns: linkerd
1747 status:
1748 phase: Running
1749 `,
1750 },
1751 mockPromResponse: model.Vector{
1752 genPromSample("10.1.1.239:9995", "authority", "linkerd", false),
1753 },
1754 expectedPrometheusQueries: []string{
1755 `histogram_quantile(0.5, sum(irate(response_latency_ms_bucket{deployment="emojivoto", direction="outbound"}[1m])) by (le, dst_namespace, authority))`,
1756 `histogram_quantile(0.95, sum(irate(response_latency_ms_bucket{deployment="emojivoto", direction="outbound"}[1m])) by (le, dst_namespace, authority))`,
1757 `histogram_quantile(0.99, sum(irate(response_latency_ms_bucket{deployment="emojivoto", direction="outbound"}[1m])) by (le, dst_namespace, authority))`,
1758 `sum(increase(response_total{deployment="emojivoto", direction="outbound"}[1m])) by (dst_namespace, authority, classification, tls)`,
1759 },
1760 },
1761 req: &pb.StatSummaryRequest{
1762 Selector: &pb.ResourceSelection{
1763 Resource: &pb.Resource{
1764 Namespace: "linkerd",
1765 Type: pkgK8s.Authority,
1766 },
1767 },
1768 TimeWindow: "1m",
1769 Outbound: &pb.StatSummaryRequest_FromResource{
1770 FromResource: &pb.Resource{
1771 Name: "emojivoto",
1772 Namespace: "",
1773 Type: pkgK8s.Deployment,
1774 },
1775 },
1776 },
1777 expectedResponse: GenStatSummaryResponse("10.1.1.239:9995", pkgK8s.Authority, []string{""}, nil, true, false),
1778 },
1779 }
1780
1781 testStatSummary(t, expectations)
1782 })
1783
1784 t.Run("Queries prometheus for a named authority", func(t *testing.T) {
1785 expectations := []statSumExpected{
1786 {
1787 expectedStatRPC: expectedStatRPC{
1788 err: nil,
1789 k8sConfigs: []string{`
1790 apiVersion: v1
1791 kind: Pod
1792 metadata:
1793 name: emojivoto-1
1794 namespace: emojivoto
1795 labels:
1796 app: emoji-svc
1797 linkerd.io/control-plane-ns: linkerd
1798 status:
1799 phase: Running
1800 `,
1801 },
1802 mockPromResponse: model.Vector{
1803 genPromSample("10.1.1.239:9995", "authority", "linkerd", false),
1804 },
1805 expectedPrometheusQueries: []string{
1806 `histogram_quantile(0.5, sum(irate(response_latency_ms_bucket{authority="10.1.1.239:9995", direction="inbound", namespace="linkerd"}[1m])) by (le, namespace, authority))`,
1807 `histogram_quantile(0.95, sum(irate(response_latency_ms_bucket{authority="10.1.1.239:9995", direction="inbound", namespace="linkerd"}[1m])) by (le, namespace, authority))`,
1808 `histogram_quantile(0.99, sum(irate(response_latency_ms_bucket{authority="10.1.1.239:9995", direction="inbound", namespace="linkerd"}[1m])) by (le, namespace, authority))`,
1809 `sum(increase(response_total{authority="10.1.1.239:9995", direction="inbound", namespace="linkerd"}[1m])) by (namespace, authority, classification, tls)`,
1810 },
1811 },
1812 req: &pb.StatSummaryRequest{
1813 Selector: &pb.ResourceSelection{
1814 Resource: &pb.Resource{
1815 Namespace: "linkerd",
1816 Type: pkgK8s.Authority,
1817 Name: "10.1.1.239:9995",
1818 },
1819 },
1820 TimeWindow: "1m",
1821 },
1822 expectedResponse: GenStatSummaryResponse("10.1.1.239:9995", pkgK8s.Authority, []string{"linkerd"}, nil, true, false),
1823 },
1824 }
1825
1826 testStatSummary(t, expectations)
1827 })
1828
1829 t.Run("Stats returned are nil when SkipStats is true", func(t *testing.T) {
1830 expectations := []statSumExpected{
1831 {
1832 expectedStatRPC: expectedStatRPC{
1833 err: nil,
1834 k8sConfigs: []string{`
1835 apiVersion: v1
1836 kind: Pod
1837 metadata:
1838 name: emojivoto-1
1839 namespace: emojivoto
1840 labels:
1841 app: emoji-svc
1842 linkerd.io/control-plane-ns: linkerd
1843 status:
1844 phase: Running
1845 `,
1846 },
1847 mockPromResponse: model.Vector{},
1848 expectedPrometheusQueries: []string{},
1849 },
1850 req: &pb.StatSummaryRequest{
1851 Selector: &pb.ResourceSelection{
1852 Resource: &pb.Resource{
1853 Namespace: "emojivoto",
1854 Type: pkgK8s.Pod,
1855 },
1856 },
1857 TimeWindow: "1m",
1858 SkipStats: true,
1859 },
1860 expectedResponse: GenStatSummaryResponse("emojivoto-1", pkgK8s.Pod, []string{"emojivoto"}, &PodCounts{
1861 Status: "Running",
1862 MeshedPods: 1,
1863 RunningPods: 1,
1864 FailedPods: 0,
1865 }, false, false),
1866 },
1867 }
1868
1869 testStatSummary(t, expectations)
1870 })
1871 }
1872
View as plain text