linkerd2/controller/api/public/stat_summary_test.go

1781 lines
46 KiB
Go

package public
import (
"context"
"errors"
"sort"
"testing"
"github.com/golang/protobuf/proto"
pb "github.com/linkerd/linkerd2/controller/gen/public"
"github.com/linkerd/linkerd2/controller/k8s"
pkgK8s "github.com/linkerd/linkerd2/pkg/k8s"
"github.com/prometheus/common/model"
)
type statSumExpected struct {
expectedStatRPC
req pb.StatSummaryRequest // the request we would like to test
expectedResponse pb.StatSummaryResponse // the stat response we expect
}
func prometheusMetric(resName string, resType string) model.Vector {
return model.Vector{
genPromSample(resName, resType, "emojivoto", false),
}
}
func genPromSample(resName string, resType string, resNs string, isDst bool) *model.Sample {
labelName := model.LabelName(resType)
namespaceLabel := model.LabelName("namespace")
if isDst {
labelName = "dst_" + labelName
namespaceLabel = "dst_" + namespaceLabel
}
return &model.Sample{
Metric: model.Metric{
labelName: model.LabelValue(resName),
namespaceLabel: model.LabelValue(resNs),
"classification": model.LabelValue("success"),
"tls": model.LabelValue("true"),
},
Value: 123,
Timestamp: 456,
}
}
func genEmptyResponse() pb.StatSummaryResponse {
return pb.StatSummaryResponse{
Response: &pb.StatSummaryResponse_Ok_{ // https://github.com/golang/protobuf/issues/205
Ok: &pb.StatSummaryResponse_Ok{
StatTables: []*pb.StatTable{
{
Table: &pb.StatTable_PodGroup_{
PodGroup: &pb.StatTable_PodGroup{},
},
},
},
},
},
}
}
func testStatSummary(t *testing.T, expectations []statSumExpected) {
for _, exp := range expectations {
mockProm, fakeGrpcServer, err := newMockGrpcServer(exp.expectedStatRPC)
if err != nil {
t.Fatalf("Error creating mock grpc server: %s", err)
}
rsp, err := fakeGrpcServer.StatSummary(context.TODO(), &exp.req)
if err != exp.err {
t.Fatalf("Expected error: %s, Got: %s", exp.err, err)
}
err = exp.verifyPromQueries(mockProm)
if err != nil {
t.Fatal(err)
}
rspStatTables := rsp.GetOk().StatTables
sort.Sort(byStatResult(rspStatTables))
if len(rspStatTables) != len(exp.expectedResponse.GetOk().StatTables) {
t.Fatalf(
"Expected [%d] stat tables, got [%d].\nExpected:\n%s\nGot:\n%s",
len(exp.expectedResponse.GetOk().StatTables),
len(rspStatTables),
exp.expectedResponse.GetOk().StatTables,
rspStatTables,
)
}
statOkRsp := &pb.StatSummaryResponse_Ok{
StatTables: rspStatTables,
}
for i, st := range rspStatTables {
expected := exp.expectedResponse.GetOk().StatTables[i]
if !proto.Equal(st, expected) {
t.Fatalf("Expected: %+v\n Got: %+v", expected, st)
}
}
if !proto.Equal(exp.expectedResponse.GetOk(), statOkRsp) {
t.Fatalf("Expected: %+v\n Got: %+v", &exp.expectedResponse, rsp)
}
}
}
type byStatResult []*pb.StatTable
func (s byStatResult) Len() int {
return len(s)
}
func (s byStatResult) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}
func (s byStatResult) Less(i, j int) bool {
if len(s[i].GetPodGroup().Rows) == 0 {
return true
}
if len(s[j].GetPodGroup().Rows) == 0 {
return false
}
return s[i].GetPodGroup().Rows[0].Resource.Type < s[j].GetPodGroup().Rows[0].Resource.Type
}
func TestStatSummary(t *testing.T) {
t.Run("Successfully performs a query based on resource type Pod", func(t *testing.T) {
expectations := []statSumExpected{
{
expectedStatRPC: expectedStatRPC{
err: nil,
k8sConfigs: []string{`
apiVersion: v1
kind: Pod
metadata:
name: emoji
namespace: emojivoto
labels:
app: emoji-svc
linkerd.io/control-plane-ns: linkerd
status:
phase: Running
`,
},
mockPromResponse: prometheusMetric("emoji", "pod"),
},
req: pb.StatSummaryRequest{
Selector: &pb.ResourceSelection{
Resource: &pb.Resource{
Namespace: "emojivoto",
Type: pkgK8s.Pod,
},
},
TimeWindow: "1m",
},
expectedResponse: GenStatSummaryResponse("emoji", pkgK8s.Pod, []string{"emojivoto"}, &PodCounts{
Status: "Running",
MeshedPods: 1,
RunningPods: 1,
FailedPods: 0,
}, true, false),
},
}
testStatSummary(t, expectations)
})
t.Run("Successfully performs a query based on resource type Pod when pod Reason is filled", func(t *testing.T) {
expectations := []statSumExpected{
{
expectedStatRPC: expectedStatRPC{
err: nil,
k8sConfigs: []string{`
apiVersion: v1
kind: Pod
metadata:
name: emoji
namespace: emojivoto
labels:
app: emoji-svc
linkerd.io/control-plane-ns: linkerd
status:
phase: Pending
reason: podReason
`,
},
mockPromResponse: prometheusMetric("emoji", "pod"),
},
req: pb.StatSummaryRequest{
Selector: &pb.ResourceSelection{
Resource: &pb.Resource{
Namespace: "emojivoto",
Type: pkgK8s.Pod,
},
},
TimeWindow: "1m",
},
expectedResponse: GenStatSummaryResponse("emoji", pkgK8s.Pod, []string{"emojivoto"}, &PodCounts{
Status: "podReason",
MeshedPods: 1,
RunningPods: 1,
FailedPods: 0,
}, true, false),
},
}
testStatSummary(t, expectations)
})
t.Run("Successfully performs a query based on resource type Pod when pod init container is initializing", func(t *testing.T) {
expectations := []statSumExpected{
{
expectedStatRPC: expectedStatRPC{
err: nil,
k8sConfigs: []string{`
apiVersion: v1
kind: Pod
metadata:
name: emoji
namespace: emojivoto
labels:
app: emoji-svc
linkerd.io/control-plane-ns: linkerd
status:
phase: Pending
initContainerStatuses:
- state:
waiting:
reason: PodInitializing
`,
},
mockPromResponse: prometheusMetric("emoji", "pod"),
},
req: pb.StatSummaryRequest{
Selector: &pb.ResourceSelection{
Resource: &pb.Resource{
Namespace: "emojivoto",
Type: pkgK8s.Pod,
},
},
TimeWindow: "1m",
},
expectedResponse: GenStatSummaryResponse("emoji", pkgK8s.Pod, []string{"emojivoto"}, &PodCounts{
Status: "Init:0/0",
MeshedPods: 1,
RunningPods: 1,
FailedPods: 0,
Errors: map[string]*pb.PodErrors{
"emoji": {
Errors: []*pb.PodErrors_PodError{
{
Error: &pb.PodErrors_PodError_Container{
Container: &pb.PodErrors_PodError_ContainerError{
Reason: "PodInitializing",
},
},
},
},
},
},
}, true, false),
},
}
testStatSummary(t, expectations)
})
t.Run("Successfully performs a query based on resource type Deployment", func(t *testing.T) {
expectations := []statSumExpected{
{
expectedStatRPC: expectedStatRPC{
err: nil,
k8sConfigs: []string{`
apiVersion: apps/v1beta2
kind: Deployment
metadata:
name: emoji
namespace: emojivoto
uid: a1b2c3
spec:
selector:
matchLabels:
app: emoji-svc
strategy: {}
template:
spec:
containers:
- image: buoyantio/emojivoto-emoji-svc:v3
`, `
apiVersion: apps/v1beta2
kind: ReplicaSet
metadata:
uid: a1b2c3d4
annotations:
deployment.kubernetes.io/revision: "2"
name: emojivoto-meshed_2
namespace: emojivoto
labels:
app: emoji-svc
pod-template-hash: 3c2b1a
ownerReferences:
- apiVersion: apps/v1
uid: a1b2c3
spec:
selector:
matchLabels:
app: emoji-svc
pod-template-hash: 3c2b1a
`, `
apiVersion: v1
kind: Pod
metadata:
name: emojivoto-meshed
namespace: emojivoto
labels:
app: emoji-svc
linkerd.io/control-plane-ns: linkerd
pod-template-hash: 3c2b1a
ownerReferences:
- apiVersion: apps/v1
uid: a1b2c3d4
status:
phase: Running
`, `
apiVersion: v1
kind: Pod
metadata:
name: emojivoto-not-meshed
namespace: emojivoto
labels:
app: emoji-svc
pod-template-hash: 3c2b1a
ownerReferences:
- apiVersion: apps/v1
uid: a1b2c3d4
status:
phase: Running
`, `
apiVersion: v1
kind: Pod
metadata:
name: emojivoto-meshed-not-running
namespace: emojivoto
labels:
app: emoji-svc
linkerd.io/control-plane-ns: linkerd
pod-template-hash: 3c2b1a
ownerReferences:
- apiVersion: apps/v1
uid: a1b2c3d4
status:
phase: Completed
`,
},
mockPromResponse: prometheusMetric("emoji", "deployment"),
},
req: pb.StatSummaryRequest{
Selector: &pb.ResourceSelection{
Resource: &pb.Resource{
Namespace: "emojivoto",
Type: pkgK8s.Deployment,
},
},
TimeWindow: "1m",
},
expectedResponse: GenStatSummaryResponse("emoji", pkgK8s.Deployment, []string{"emojivoto"}, &PodCounts{
MeshedPods: 1,
RunningPods: 2,
FailedPods: 0,
}, true, false),
},
}
testStatSummary(t, expectations)
})
t.Run("Successfully performs a query based on resource type DaemonSet", func(t *testing.T) {
expectations := []statSumExpected{
{
expectedStatRPC: expectedStatRPC{
err: nil,
k8sConfigs: []string{`
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: emoji
namespace: emojivoto
spec:
selector:
matchLabels:
app: emoji-svc
strategy: {}
template:
spec:
containers:
- image: buoyantio/emojivoto-emoji-svc:v3
`, `
apiVersion: v1
kind: Pod
metadata:
name: emojivoto-meshed
namespace: emojivoto
labels:
app: emoji-svc
linkerd.io/control-plane-ns: linkerd
status:
phase: Running
`, `
apiVersion: v1
kind: Pod
metadata:
name: emojivoto-not-meshed
namespace: emojivoto
labels:
app: emoji-svc
status:
phase: Running
`, `
apiVersion: v1
kind: Pod
metadata:
name: emojivoto-meshed-not-running
namespace: emojivoto
labels:
app: emoji-svc
linkerd.io/control-plane-ns: linkerd
status:
phase: Completed
`,
},
mockPromResponse: prometheusMetric("emoji", "daemonset"),
},
req: pb.StatSummaryRequest{
Selector: &pb.ResourceSelection{
Resource: &pb.Resource{
Namespace: "emojivoto",
Type: pkgK8s.DaemonSet,
},
},
TimeWindow: "1m",
},
expectedResponse: GenStatSummaryResponse("emoji", pkgK8s.DaemonSet, []string{"emojivoto"}, &PodCounts{
MeshedPods: 1,
RunningPods: 2,
FailedPods: 0,
}, true, false),
},
}
testStatSummary(t, expectations)
})
t.Run("Successfully performs a query based on resource type Job", func(t *testing.T) {
expectations := []statSumExpected{
{
expectedStatRPC: expectedStatRPC{
err: nil,
k8sConfigs: []string{`
apiVersion: batch/v1
kind: Job
metadata:
name: emoji
namespace: emojivoto
spec:
selector:
matchLabels:
app: emoji-job
strategy: {}
template:
spec:
containers:
- image: buoyantio/emojivoto-emoji-svc:v3
`, `
apiVersion: v1
kind: Pod
metadata:
name: emojivoto-meshed
namespace: emojivoto
labels:
app: emoji-job
linkerd.io/control-plane-ns: linkerd
status:
phase: Running
`, `
apiVersion: v1
kind: Pod
metadata:
name: emojivoto-not-meshed
namespace: emojivoto
labels:
app: emoji-job
status:
phase: Running
`, `
apiVersion: v1
kind: Pod
metadata:
name: emojivoto-meshed-not-running
namespace: emojivoto
labels:
app: emoji-job
linkerd.io/control-plane-ns: linkerd
status:
phase: Completed
`,
},
mockPromResponse: prometheusMetric("emoji", "k8s_job"),
},
req: pb.StatSummaryRequest{
Selector: &pb.ResourceSelection{
Resource: &pb.Resource{
Namespace: "emojivoto",
Type: pkgK8s.Job,
},
},
TimeWindow: "1m",
},
expectedResponse: GenStatSummaryResponse("emoji", pkgK8s.Job, []string{"emojivoto"}, &PodCounts{
MeshedPods: 1,
RunningPods: 2,
FailedPods: 0,
}, true, false),
},
}
testStatSummary(t, expectations)
})
t.Run("Successfully performs a query based on resource type StatefulSet", func(t *testing.T) {
expectations := []statSumExpected{
{
expectedStatRPC: expectedStatRPC{
err: nil,
k8sConfigs: []string{`
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: redis
namespace: emojivoto
labels:
app: redis
linkerd.io/control-plane-ns: linkerd
spec:
replicas: 3
serviceName: redis
selector:
matchLabels:
app: redis
template:
metadata:
labels:
app: redis
spec:
containers:
- image: redis
volumeMounts:
- name: data
mountPath: /var/lib/redis
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 10Gi
`, `
apiVersion: v1
kind: Pod
metadata:
name: redis-0
namespace: emojivoto
labels:
app: redis
linkerd.io/control-plane-ns: linkerd
status:
phase: Running
`, `
apiVersion: v1
kind: Pod
metadata:
name: redis-1
namespace: emojivoto
labels:
app: redis
linkerd.io/control-plane-ns: linkerd
status:
phase: Running
`, `
apiVersion: v1
kind: Pod
metadata:
name: redis-2
namespace: emojivoto
labels:
app: redis
linkerd.io/control-plane-ns: linkerd
status:
phase: Running
`,
},
mockPromResponse: prometheusMetric("redis", "statefulset"),
},
req: pb.StatSummaryRequest{
Selector: &pb.ResourceSelection{
Resource: &pb.Resource{
Namespace: "emojivoto",
Type: pkgK8s.StatefulSet,
},
},
TimeWindow: "1m",
},
expectedResponse: GenStatSummaryResponse("redis", pkgK8s.StatefulSet, []string{"emojivoto"}, &PodCounts{
MeshedPods: 3,
RunningPods: 3,
FailedPods: 0,
}, true, false),
},
}
testStatSummary(t, expectations)
})
t.Run("Queries prometheus for TCP stats when requested", func(t *testing.T) {
expectations := []statSumExpected{
{
expectedStatRPC: expectedStatRPC{
err: nil,
k8sConfigs: []string{`
apiVersion: v1
kind: Pod
metadata:
name: emojivoto-1
namespace: emojivoto
labels:
app: emoji-svc
linkerd.io/control-plane-ns: linkerd
status:
phase: Running
`,
},
mockPromResponse: prometheusMetric("emojivoto-1", "pod"),
expectedPrometheusQueries: []string{
`histogram_quantile(0.5, sum(irate(response_latency_ms_bucket{direction="inbound", namespace="emojivoto", pod="emojivoto-1"}[1m])) by (le, namespace, pod))`,
`histogram_quantile(0.95, sum(irate(response_latency_ms_bucket{direction="inbound", namespace="emojivoto", pod="emojivoto-1"}[1m])) by (le, namespace, pod))`,
`histogram_quantile(0.99, sum(irate(response_latency_ms_bucket{direction="inbound", namespace="emojivoto", pod="emojivoto-1"}[1m])) by (le, namespace, pod))`,
`sum(increase(response_total{direction="inbound", namespace="emojivoto", pod="emojivoto-1"}[1m])) by (namespace, pod, classification, tls)`,
`sum(tcp_open_connections{direction="inbound", namespace="emojivoto", pod="emojivoto-1"}) by (namespace, pod)`,
`sum(increase(tcp_read_bytes_total{direction="inbound", namespace="emojivoto", pod="emojivoto-1"}[1m])) by (namespace, pod)`,
`sum(increase(tcp_write_bytes_total{direction="inbound", namespace="emojivoto", pod="emojivoto-1"}[1m])) by (namespace, pod)`,
},
},
req: pb.StatSummaryRequest{
Selector: &pb.ResourceSelection{
Resource: &pb.Resource{
Name: "emojivoto-1",
Namespace: "emojivoto",
Type: pkgK8s.Pod,
},
},
TimeWindow: "1m",
TcpStats: true,
},
expectedResponse: GenStatSummaryResponse("emojivoto-1", pkgK8s.Pod, []string{"emojivoto"}, &PodCounts{
Status: "Running",
MeshedPods: 1,
RunningPods: 1,
FailedPods: 0,
}, true, true),
},
}
testStatSummary(t, expectations)
})
t.Run("Queries prometheus for a specific resource if name is specified", func(t *testing.T) {
expectations := []statSumExpected{
{
expectedStatRPC: expectedStatRPC{
err: nil,
k8sConfigs: []string{`
apiVersion: v1
kind: Pod
metadata:
name: emojivoto-1
namespace: emojivoto
labels:
app: emoji-svc
linkerd.io/control-plane-ns: linkerd
status:
phase: Running
`,
},
mockPromResponse: prometheusMetric("emojivoto-1", "pod"),
expectedPrometheusQueries: []string{
`histogram_quantile(0.5, sum(irate(response_latency_ms_bucket{direction="inbound", namespace="emojivoto", pod="emojivoto-1"}[1m])) by (le, namespace, pod))`,
`histogram_quantile(0.95, sum(irate(response_latency_ms_bucket{direction="inbound", namespace="emojivoto", pod="emojivoto-1"}[1m])) by (le, namespace, pod))`,
`histogram_quantile(0.99, sum(irate(response_latency_ms_bucket{direction="inbound", namespace="emojivoto", pod="emojivoto-1"}[1m])) by (le, namespace, pod))`,
`sum(increase(response_total{direction="inbound", namespace="emojivoto", pod="emojivoto-1"}[1m])) by (namespace, pod, classification, tls)`,
},
},
req: pb.StatSummaryRequest{
Selector: &pb.ResourceSelection{
Resource: &pb.Resource{
Name: "emojivoto-1",
Namespace: "emojivoto",
Type: pkgK8s.Pod,
},
},
TimeWindow: "1m",
},
expectedResponse: GenStatSummaryResponse("emojivoto-1", pkgK8s.Pod, []string{"emojivoto"}, &PodCounts{
Status: "Running",
MeshedPods: 1,
RunningPods: 1,
FailedPods: 0,
}, true, false),
},
}
testStatSummary(t, expectations)
})
t.Run("Queries prometheus for outbound metrics if from resource is specified, ignores resource name", func(t *testing.T) {
expectations := []statSumExpected{
{
expectedStatRPC: expectedStatRPC{
err: nil,
k8sConfigs: []string{`
apiVersion: v1
kind: Pod
metadata:
name: emojivoto-1
namespace: emojivoto
labels:
app: emoji-svc
linkerd.io/control-plane-ns: linkerd
status:
phase: Running
`,
},
mockPromResponse: prometheusMetric("emojivoto-2", "pod"),
expectedPrometheusQueries: []string{
`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))`,
`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))`,
`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))`,
`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)`,
},
},
req: pb.StatSummaryRequest{
Selector: &pb.ResourceSelection{
Resource: &pb.Resource{
Name: "emojivoto-1",
Namespace: "emojivoto",
Type: pkgK8s.Pod,
},
},
TimeWindow: "1m",
Outbound: &pb.StatSummaryRequest_FromResource{
FromResource: &pb.Resource{
Name: "emojivoto-2",
Namespace: "emojivoto",
Type: pkgK8s.Pod,
},
},
},
expectedResponse: genEmptyResponse(),
},
}
testStatSummary(t, expectations)
})
t.Run("Queries prometheus for outbound metrics if --to resource is specified", func(t *testing.T) {
expectations := []statSumExpected{
{
expectedStatRPC: expectedStatRPC{
err: nil,
k8sConfigs: []string{`
apiVersion: v1
kind: Pod
metadata:
name: emojivoto-1
namespace: emojivoto
labels:
app: emoji-svc
linkerd.io/control-plane-ns: linkerd
status:
phase: Running
`,
},
mockPromResponse: model.Vector{
genPromSample("emojivoto-1", "pod", "emojivoto", false),
},
expectedPrometheusQueries: []string{
`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))`,
`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))`,
`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))`,
`sum(increase(response_total{direction="outbound", dst_namespace="emojivoto", dst_pod="emojivoto-2", namespace="emojivoto", pod="emojivoto-1"}[1m])) by (namespace, pod, classification, tls)`,
},
},
req: pb.StatSummaryRequest{
Selector: &pb.ResourceSelection{
Resource: &pb.Resource{
Name: "emojivoto-1",
Namespace: "emojivoto",
Type: pkgK8s.Pod,
},
},
TimeWindow: "1m",
Outbound: &pb.StatSummaryRequest_ToResource{
ToResource: &pb.Resource{
Name: "emojivoto-2",
Namespace: "emojivoto",
Type: pkgK8s.Pod,
},
},
},
expectedResponse: GenStatSummaryResponse("emojivoto-1", pkgK8s.Pod, []string{"emojivoto"}, &PodCounts{
Status: "Running",
MeshedPods: 1,
RunningPods: 1,
FailedPods: 0,
}, true, false),
},
}
testStatSummary(t, expectations)
})
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) {
expectations := []statSumExpected{
{
expectedStatRPC: expectedStatRPC{
err: nil,
k8sConfigs: []string{`
apiVersion: v1
kind: Pod
metadata:
name: emojivoto-1
namespace: emojivoto
labels:
app: emoji-svc
linkerd.io/control-plane-ns: linkerd
status:
phase: Running
`,
},
mockPromResponse: model.Vector{
genPromSample("emojivoto-1", "pod", "emojivoto", false),
},
expectedPrometheusQueries: []string{
`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))`,
`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))`,
`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))`,
`sum(increase(response_total{direction="outbound", dst_namespace="totallydifferent", dst_pod="emojivoto-2", namespace="emojivoto", pod="emojivoto-1"}[1m])) by (namespace, pod, classification, tls)`,
},
},
req: pb.StatSummaryRequest{
Selector: &pb.ResourceSelection{
Resource: &pb.Resource{
Name: "emojivoto-1",
Namespace: "emojivoto",
Type: pkgK8s.Pod,
},
},
TimeWindow: "1m",
Outbound: &pb.StatSummaryRequest_ToResource{
ToResource: &pb.Resource{
Name: "emojivoto-2",
Namespace: "totallydifferent",
Type: pkgK8s.Pod,
},
},
},
expectedResponse: GenStatSummaryResponse("emojivoto-1", pkgK8s.Pod, []string{"emojivoto"}, &PodCounts{
Status: "Running",
MeshedPods: 1,
RunningPods: 1,
FailedPods: 0,
}, true, false),
},
}
testStatSummary(t, expectations)
})
t.Run("Queries prometheus for outbound metrics if --from resource is specified", func(t *testing.T) {
expectations := []statSumExpected{
{
expectedStatRPC: expectedStatRPC{
err: nil,
k8sConfigs: []string{`
apiVersion: v1
kind: Pod
metadata:
name: emojivoto-1
namespace: emojivoto
labels:
app: emoji-svc
linkerd.io/control-plane-ns: linkerd
status:
phase: Running
`, `
apiVersion: v1
kind: Pod
metadata:
name: emojivoto-2
namespace: totallydifferent
labels:
app: emoji-svc
linkerd.io/control-plane-ns: linkerd
status:
phase: Running
`,
},
mockPromResponse: model.Vector{
genPromSample("emojivoto-1", "pod", "emojivoto", true),
},
expectedPrometheusQueries: []string{
`histogram_quantile(0.5, sum(irate(response_latency_ms_bucket{direction="outbound", pod="emojivoto-2"}[1m])) by (le, dst_namespace, dst_pod))`,
`histogram_quantile(0.95, sum(irate(response_latency_ms_bucket{direction="outbound", pod="emojivoto-2"}[1m])) by (le, dst_namespace, dst_pod))`,
`histogram_quantile(0.99, sum(irate(response_latency_ms_bucket{direction="outbound", pod="emojivoto-2"}[1m])) by (le, dst_namespace, dst_pod))`,
`sum(increase(response_total{direction="outbound", pod="emojivoto-2"}[1m])) by (dst_namespace, dst_pod, classification, tls)`,
},
},
req: pb.StatSummaryRequest{
Selector: &pb.ResourceSelection{
Resource: &pb.Resource{
Name: "",
Namespace: "emojivoto",
Type: pkgK8s.Pod,
},
},
TimeWindow: "1m",
Outbound: &pb.StatSummaryRequest_FromResource{
FromResource: &pb.Resource{
Name: "emojivoto-2",
Namespace: "",
Type: pkgK8s.Pod,
},
},
},
expectedResponse: GenStatSummaryResponse("emojivoto-1", pkgK8s.Pod, []string{"emojivoto"}, &PodCounts{
Status: "Running",
MeshedPods: 1,
RunningPods: 1,
FailedPods: 0,
}, true, false),
},
}
testStatSummary(t, expectations)
})
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) {
expectations := []statSumExpected{
{
expectedStatRPC: expectedStatRPC{
err: nil,
k8sConfigs: []string{`
apiVersion: v1
kind: Pod
metadata:
name: emojivoto-1
namespace: emojivoto
labels:
app: emoji-svc
linkerd.io/control-plane-ns: linkerd
status:
phase: Running
`, `
apiVersion: v1
kind: Pod
metadata:
name: emojivoto-2
namespace: totallydifferent
labels:
app: emoji-svc
linkerd.io/control-plane-ns: linkerd
status:
phase: Running
`,
},
mockPromResponse: model.Vector{
genPromSample("emojivoto-1", "pod", "emojivoto", true),
},
expectedPrometheusQueries: []string{
`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))`,
`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))`,
`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))`,
`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)`,
},
},
req: pb.StatSummaryRequest{
Selector: &pb.ResourceSelection{
Resource: &pb.Resource{
Name: "emojivoto-1",
Namespace: "emojivoto",
Type: pkgK8s.Pod,
},
},
TimeWindow: "1m",
Outbound: &pb.StatSummaryRequest_FromResource{
FromResource: &pb.Resource{
Name: "emojivoto-2",
Namespace: "totallydifferent",
Type: pkgK8s.Pod,
},
},
},
expectedResponse: GenStatSummaryResponse("emojivoto-1", pkgK8s.Pod, []string{"emojivoto"}, &PodCounts{
Status: "Running",
MeshedPods: 1,
RunningPods: 1,
FailedPods: 0,
}, true, false),
},
}
testStatSummary(t, expectations)
})
t.Run("Successfully queries for resource type 'all'", func(t *testing.T) {
expectations := []statSumExpected{
{
expectedStatRPC: expectedStatRPC{
err: nil,
k8sConfigs: []string{`
apiVersion: apps/v1beta2
kind: Deployment
metadata:
name: emoji-deploy
namespace: emojivoto
uid: a1b2c3
spec:
selector:
matchLabels:
app: emoji-svc
strategy: {}
template:
spec:
containers:
- image: buoyantio/emojivoto-emoji-svc:v3
`, `
apiVersion: apps/v1beta2
kind: ReplicaSet
metadata:
uid: a1b2c3d4
annotations:
deployment.kubernetes.io/revision: "2"
name: emojivoto-meshed_2
namespace: emojivoto
labels:
app: emoji-svc
pod-template-hash: 3c2b1a
ownerReferences:
- apiVersion: apps/v1
uid: a1b2c3
spec:
selector:
matchLabels:
app: emoji-svc
pod-template-hash: 3c2b1a
`, `
apiVersion: v1
kind: Service
metadata:
name: emoji-svc
namespace: emojivoto
spec:
clusterIP: None
selector:
app: emoji-svc
`, `
apiVersion: v1
kind: Pod
metadata:
name: emojivoto-pod-1
namespace: not-right-emojivoto-namespace
labels:
app: emoji-svc
linkerd.io/control-plane-ns: linkerd
status:
phase: Running
`, `
apiVersion: v1
kind: Pod
metadata:
name: emojivoto-pod-2
namespace: emojivoto
labels:
app: emoji-svc
linkerd.io/control-plane-ns: linkerd
pod-template-hash: 3c2b1a
ownerReferences:
- apiVersion: apps/v1
uid: a1b2c3d4
status:
phase: Running
`,
},
mockPromResponse: prometheusMetric("emoji-deploy", "deployment"),
},
req: pb.StatSummaryRequest{
Selector: &pb.ResourceSelection{
Resource: &pb.Resource{
Namespace: "emojivoto",
Type: pkgK8s.All,
},
},
TimeWindow: "1m",
},
expectedResponse: pb.StatSummaryResponse{
Response: &pb.StatSummaryResponse_Ok_{ // https://github.com/golang/protobuf/issues/205
Ok: &pb.StatSummaryResponse_Ok{
StatTables: []*pb.StatTable{
{
Table: &pb.StatTable_PodGroup_{
PodGroup: &pb.StatTable_PodGroup{
Rows: []*pb.StatTable_PodGroup_Row{},
},
},
},
{
Table: &pb.StatTable_PodGroup_{
PodGroup: &pb.StatTable_PodGroup{
Rows: []*pb.StatTable_PodGroup_Row{},
},
},
},
{
Table: &pb.StatTable_PodGroup_{
PodGroup: &pb.StatTable_PodGroup{
Rows: []*pb.StatTable_PodGroup_Row{},
},
},
},
{
Table: &pb.StatTable_PodGroup_{
PodGroup: &pb.StatTable_PodGroup{
Rows: []*pb.StatTable_PodGroup_Row{},
},
},
},
{
Table: &pb.StatTable_PodGroup_{
PodGroup: &pb.StatTable_PodGroup{
Rows: []*pb.StatTable_PodGroup_Row{
{
Resource: &pb.Resource{
Namespace: "emojivoto",
Type: pkgK8s.Authority,
},
TimeWindow: "1m",
Stats: &pb.BasicStats{
SuccessCount: 123,
FailureCount: 0,
LatencyMsP50: 123,
LatencyMsP95: 123,
LatencyMsP99: 123,
},
},
},
},
},
},
{
Table: &pb.StatTable_PodGroup_{
PodGroup: &pb.StatTable_PodGroup{
Rows: []*pb.StatTable_PodGroup_Row{
{
Resource: &pb.Resource{
Namespace: "emojivoto",
Type: pkgK8s.Deployment,
Name: "emoji-deploy",
},
Stats: &pb.BasicStats{
SuccessCount: 123,
FailureCount: 0,
LatencyMsP50: 123,
LatencyMsP95: 123,
LatencyMsP99: 123,
},
TimeWindow: "1m",
MeshedPodCount: 1,
RunningPodCount: 1,
},
},
},
},
},
{
Table: &pb.StatTable_PodGroup_{
PodGroup: &pb.StatTable_PodGroup{
Rows: []*pb.StatTable_PodGroup_Row{
{
Resource: &pb.Resource{
Namespace: "emojivoto",
Type: pkgK8s.Pod,
Name: "emojivoto-pod-2",
},
Status: "Running",
TimeWindow: "1m",
MeshedPodCount: 1,
RunningPodCount: 1,
},
},
},
},
},
{
Table: &pb.StatTable_PodGroup_{
PodGroup: &pb.StatTable_PodGroup{
Rows: []*pb.StatTable_PodGroup_Row{
{
Resource: &pb.Resource{
Namespace: "emojivoto",
Type: pkgK8s.Service,
Name: "emoji-svc",
},
TimeWindow: "1m",
MeshedPodCount: 1,
RunningPodCount: 1,
},
},
},
},
},
},
},
},
},
},
}
testStatSummary(t, expectations)
})
t.Run("Given an invalid resource type, returns error", func(t *testing.T) {
k8sAPI, err := k8s.NewFakeAPI()
if err != nil {
t.Fatalf("NewFakeAPI returned an error: %s", err)
}
expectations := []statSumExpected{
{
expectedStatRPC: expectedStatRPC{
err: errors.New("rpc error: code = Unimplemented desc = unimplemented resource type: badtype"),
},
req: pb.StatSummaryRequest{
Selector: &pb.ResourceSelection{
Resource: &pb.Resource{
Type: "badtype",
},
},
},
},
{
expectedStatRPC: expectedStatRPC{
err: errors.New("rpc error: code = Unimplemented desc = unimplemented resource type: deployments"),
},
req: pb.StatSummaryRequest{
Selector: &pb.ResourceSelection{
Resource: &pb.Resource{
Type: "deployments",
},
},
},
},
{
expectedStatRPC: expectedStatRPC{
err: errors.New("rpc error: code = Unimplemented desc = unimplemented resource type: po"),
},
req: pb.StatSummaryRequest{
Selector: &pb.ResourceSelection{
Resource: &pb.Resource{
Type: "po",
},
},
},
},
}
for _, exp := range expectations {
fakeGrpcServer := newGrpcServer(
&MockProm{Res: exp.mockPromResponse},
nil,
nil,
nil,
k8sAPI,
"linkerd",
[]string{},
)
_, err := fakeGrpcServer.StatSummary(context.TODO(), &exp.req)
if err != nil || exp.err != nil {
if (err == nil && exp.err != nil) ||
(err != nil && exp.err == nil) ||
(err.Error() != exp.err.Error()) {
t.Fatalf("Unexpected error (Expected: %s, Got: %s)", exp.err, err)
}
}
}
})
t.Run("Validates service stat requests", func(t *testing.T) {
k8sAPI, err := k8s.NewFakeAPI()
if err != nil {
t.Fatalf("NewFakeAPI returned an error: %s", err)
}
fakeGrpcServer := newGrpcServer(
&MockProm{Res: model.Vector{}},
nil,
nil,
nil,
k8sAPI,
"linkerd",
[]string{},
)
invalidRequests := []statSumExpected{
{
req: pb.StatSummaryRequest{},
},
{
req: pb.StatSummaryRequest{
Selector: &pb.ResourceSelection{
Resource: &pb.Resource{
Type: pkgK8s.Service,
},
},
},
},
{
req: pb.StatSummaryRequest{
Selector: &pb.ResourceSelection{
Resource: &pb.Resource{
Type: pkgK8s.Service,
},
},
Outbound: &pb.StatSummaryRequest_ToResource{
ToResource: &pb.Resource{
Type: pkgK8s.Pod,
},
},
},
},
{
req: pb.StatSummaryRequest{
Selector: &pb.ResourceSelection{
Resource: &pb.Resource{
Type: pkgK8s.Pod,
},
},
Outbound: &pb.StatSummaryRequest_FromResource{
FromResource: &pb.Resource{
Type: pkgK8s.Service,
},
},
},
},
}
for _, invalid := range invalidRequests {
rsp, err := fakeGrpcServer.StatSummary(context.TODO(), &invalid.req)
if err != nil || rsp.GetError() == nil {
t.Fatalf("Expected validation error on StatSummaryResponse, got %v, %v", rsp, err)
}
}
validRequests := []statSumExpected{
{
req: pb.StatSummaryRequest{
Selector: &pb.ResourceSelection{
Resource: &pb.Resource{
Type: pkgK8s.Pod,
},
},
Outbound: &pb.StatSummaryRequest_ToResource{
ToResource: &pb.Resource{
Type: pkgK8s.Service,
},
},
},
},
{
req: pb.StatSummaryRequest{
Selector: &pb.ResourceSelection{
Resource: &pb.Resource{
Type: pkgK8s.Service,
},
},
Outbound: &pb.StatSummaryRequest_FromResource{
FromResource: &pb.Resource{
Type: pkgK8s.Pod,
},
},
},
},
}
for _, valid := range validRequests {
rsp, err := fakeGrpcServer.StatSummary(context.TODO(), &valid.req)
if err != nil || rsp.GetError() != nil {
t.Fatalf("Did not expect validation error on StatSummaryResponse, got %v, %v", rsp, err)
}
}
})
t.Run("Return empty stats summary response", func(t *testing.T) {
t.Run("when pod phase is succeeded or failed", func(t *testing.T) {
expectations := []statSumExpected{
{
expectedStatRPC: expectedStatRPC{
err: nil,
k8sConfigs: []string{`
apiVersion: v1
kind: Pod
metadata:
name: emojivoto-00
namespace: emojivoto
labels:
app: emoji-svc
linkerd.io/control-plane-ns: linkerd
status:
phase: Succeeded
`, `
apiVersion: v1
kind: Pod
metadata:
name: emojivoto-01
namespace: emojivoto
labels:
app: emoji-svc
linkerd.io/control-plane-ns: linkerd
status:
phase: Failed
`},
mockPromResponse: model.Vector{},
expectedPrometheusQueries: []string{
`histogram_quantile(0.5, sum(irate(response_latency_ms_bucket{direction="inbound", namespace="emojivoto"}[])) by (le, namespace, pod))`,
`histogram_quantile(0.95, sum(irate(response_latency_ms_bucket{direction="inbound", namespace="emojivoto"}[])) by (le, namespace, pod))`,
`histogram_quantile(0.99, sum(irate(response_latency_ms_bucket{direction="inbound", namespace="emojivoto"}[])) by (le, namespace, pod))`,
`sum(increase(response_total{direction="inbound", namespace="emojivoto"}[])) by (namespace, pod, classification, tls)`,
},
},
req: pb.StatSummaryRequest{
Selector: &pb.ResourceSelection{
Resource: &pb.Resource{
Namespace: "emojivoto",
Type: pkgK8s.Pod,
},
},
},
expectedResponse: genEmptyResponse(),
},
}
testStatSummary(t, expectations)
})
t.Run("for succeeded or failed replicas of a deployment", func(t *testing.T) {
expectations := []statSumExpected{
{
expectedStatRPC: expectedStatRPC{
err: nil,
k8sConfigs: []string{`
apiVersion: apps/v1beta2
kind: Deployment
metadata:
name: emoji
namespace: emojivoto
uid: a1b2c3
spec:
selector:
matchLabels:
app: emoji-svc
strategy: {}
template:
spec:
containers:
- image: buoyantio/emojivoto-emoji-svc:v3
`, `
apiVersion: apps/v1beta2
kind: ReplicaSet
metadata:
uid: a1b2c3d4
annotations:
deployment.kubernetes.io/revision: "2"
name: emojivoto-meshed_2
namespace: emojivoto
labels:
app: emoji-svc
pod-template-hash: 3c2b1a
ownerReferences:
- apiVersion: apps/v1
uid: a1b2c3
spec:
selector:
matchLabels:
app: emoji-svc
pod-template-hash: 3c2b1a
`, `
apiVersion: v1
kind: Pod
metadata:
name: emojivoto-00
namespace: emojivoto
labels:
app: emoji-svc
linkerd.io/control-plane-ns: linkerd
pod-template-hash: 3c2b1a
ownerReferences:
- apiVersion: apps/v1
uid: a1b2c3d4
status:
phase: Running
`, `
apiVersion: v1
kind: Pod
metadata:
name: emojivoto-01
namespace: emojivoto
labels:
app: emoji-svc
pod-template-hash: 3c2b1a
ownerReferences:
- apiVersion: apps/v1
uid: a1b2c3d4
status:
phase: Running
`, `
apiVersion: v1
kind: Pod
metadata:
name: emojivoto-02
namespace: emojivoto
labels:
app: emoji-svc
linkerd.io/control-plane-ns: linkerd
pod-template-hash: 3c2b1a
ownerReferences:
- apiVersion: apps/v1
uid: a1b2c3d4
status:
phase: Failed
`, `
apiVersion: v1
kind: Pod
metadata:
name: emojivoto-03
namespace: emojivoto
labels:
app: emoji-svc
linkerd.io/control-plane-ns: linkerd
pod-template-hash: 3c2b1a
ownerReferences:
- apiVersion: apps/v1
uid: a1b2c3d4
status:
phase: Succeeded
`},
mockPromResponse: prometheusMetric("emoji", "deployment"),
},
req: pb.StatSummaryRequest{
Selector: &pb.ResourceSelection{
Resource: &pb.Resource{
Namespace: "emojivoto",
Type: pkgK8s.Deployment,
},
},
TimeWindow: "1m",
},
expectedResponse: GenStatSummaryResponse("emoji", pkgK8s.Deployment, []string{"emojivoto"}, &PodCounts{
MeshedPods: 1,
RunningPods: 2,
FailedPods: 1,
}, true, false),
},
}
testStatSummary(t, expectations)
})
})
t.Run("Queries prometheus for authority stats", func(t *testing.T) {
expectations := []statSumExpected{
{
expectedStatRPC: expectedStatRPC{
err: nil,
k8sConfigs: []string{`
apiVersion: v1
kind: Pod
metadata:
name: emojivoto-1
namespace: emojivoto
labels:
app: emoji-svc
linkerd.io/control-plane-ns: linkerd
status:
phase: Running
`,
},
mockPromResponse: model.Vector{
genPromSample("10.1.1.239:9995", "authority", "linkerd", false),
},
expectedPrometheusQueries: []string{
`histogram_quantile(0.5, sum(irate(response_latency_ms_bucket{direction="inbound", namespace="linkerd"}[1m])) by (le, namespace, authority))`,
`histogram_quantile(0.95, sum(irate(response_latency_ms_bucket{direction="inbound", namespace="linkerd"}[1m])) by (le, namespace, authority))`,
`histogram_quantile(0.99, sum(irate(response_latency_ms_bucket{direction="inbound", namespace="linkerd"}[1m])) by (le, namespace, authority))`,
`sum(increase(response_total{direction="inbound", namespace="linkerd"}[1m])) by (namespace, authority, classification, tls)`,
},
},
req: pb.StatSummaryRequest{
Selector: &pb.ResourceSelection{
Resource: &pb.Resource{
Namespace: "linkerd",
Type: pkgK8s.Authority,
},
},
TimeWindow: "1m",
},
expectedResponse: GenStatSummaryResponse("10.1.1.239:9995", pkgK8s.Authority, []string{"linkerd"}, nil, true, false),
},
}
testStatSummary(t, expectations)
})
t.Run("Queries prometheus for authority stats when --from deployment is used", func(t *testing.T) {
expectations := []statSumExpected{
{
expectedStatRPC: expectedStatRPC{
err: nil,
k8sConfigs: []string{`
apiVersion: v1
kind: Pod
metadata:
name: emojivoto-1
namespace: emojivoto
labels:
app: emoji-svc
linkerd.io/control-plane-ns: linkerd
status:
phase: Running
`,
},
mockPromResponse: model.Vector{
genPromSample("10.1.1.239:9995", "authority", "linkerd", false),
},
expectedPrometheusQueries: []string{
`histogram_quantile(0.5, sum(irate(response_latency_ms_bucket{deployment="emojivoto", direction="outbound"}[1m])) by (le, dst_namespace, authority))`,
`histogram_quantile(0.95, sum(irate(response_latency_ms_bucket{deployment="emojivoto", direction="outbound"}[1m])) by (le, dst_namespace, authority))`,
`histogram_quantile(0.99, sum(irate(response_latency_ms_bucket{deployment="emojivoto", direction="outbound"}[1m])) by (le, dst_namespace, authority))`,
`sum(increase(response_total{deployment="emojivoto", direction="outbound"}[1m])) by (dst_namespace, authority, classification, tls)`,
},
},
req: pb.StatSummaryRequest{
Selector: &pb.ResourceSelection{
Resource: &pb.Resource{
Namespace: "linkerd",
Type: pkgK8s.Authority,
},
},
TimeWindow: "1m",
Outbound: &pb.StatSummaryRequest_FromResource{
FromResource: &pb.Resource{
Name: "emojivoto",
Namespace: "",
Type: pkgK8s.Deployment,
},
},
},
expectedResponse: GenStatSummaryResponse("10.1.1.239:9995", pkgK8s.Authority, []string{""}, nil, true, false),
},
}
testStatSummary(t, expectations)
})
t.Run("Queries prometheus for a named authority", func(t *testing.T) {
expectations := []statSumExpected{
{
expectedStatRPC: expectedStatRPC{
err: nil,
k8sConfigs: []string{`
apiVersion: v1
kind: Pod
metadata:
name: emojivoto-1
namespace: emojivoto
labels:
app: emoji-svc
linkerd.io/control-plane-ns: linkerd
status:
phase: Running
`,
},
mockPromResponse: model.Vector{
genPromSample("10.1.1.239:9995", "authority", "linkerd", false),
},
expectedPrometheusQueries: []string{
`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))`,
`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))`,
`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))`,
`sum(increase(response_total{authority="10.1.1.239:9995", direction="inbound", namespace="linkerd"}[1m])) by (namespace, authority, classification, tls)`,
},
},
req: pb.StatSummaryRequest{
Selector: &pb.ResourceSelection{
Resource: &pb.Resource{
Namespace: "linkerd",
Type: pkgK8s.Authority,
Name: "10.1.1.239:9995",
},
},
TimeWindow: "1m",
},
expectedResponse: GenStatSummaryResponse("10.1.1.239:9995", pkgK8s.Authority, []string{"linkerd"}, nil, true, false),
},
}
testStatSummary(t, expectations)
})
t.Run("Stats returned are nil when SkipStats is true", func(t *testing.T) {
expectations := []statSumExpected{
{
expectedStatRPC: expectedStatRPC{
err: nil,
k8sConfigs: []string{`
apiVersion: v1
kind: Pod
metadata:
name: emojivoto-1
namespace: emojivoto
labels:
app: emoji-svc
linkerd.io/control-plane-ns: linkerd
status:
phase: Running
`,
},
mockPromResponse: model.Vector{},
expectedPrometheusQueries: []string{},
},
req: pb.StatSummaryRequest{
Selector: &pb.ResourceSelection{
Resource: &pb.Resource{
Namespace: "emojivoto",
Type: pkgK8s.Pod,
},
},
TimeWindow: "1m",
SkipStats: true,
},
expectedResponse: GenStatSummaryResponse("emojivoto-1", pkgK8s.Pod, []string{"emojivoto"}, &PodCounts{
Status: "Running",
MeshedPods: 1,
RunningPods: 1,
FailedPods: 0,
}, false, false),
},
}
testStatSummary(t, expectations)
})
}