...

Source file src/github.com/linkerd/linkerd2/viz/metrics-api/stat_summary_test.go

Documentation: github.com/linkerd/linkerd2/viz/metrics-api

     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  // the request we would like to test
    20  	expectedResponse *pb.StatSummaryResponse // the stat response we expect
    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_{ // https://github.com/golang/protobuf/issues/205
    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_{ // https://github.com/golang/protobuf/issues/205
  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