mirror of https://github.com/linkerd/linkerd2.git
1781 lines
46 KiB
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)
|
|
})
|
|
}
|