viz: Prohibit authority resource targets in stat commands (#13578)

There are plans to remove the authority label in inbound proxy metrics. When that happens we would not longer be able to use the viz stat/top commands to query by `authority`. This is a change to disable being able to invoke these commands with an `authority` resource target.

Signed-off-by: Zahari Dichev <zaharidichev@gmail.com>
This commit is contained in:
Zahari Dichev 2025-01-23 10:13:11 +02:00 committed by GitHub
parent a726757fb1
commit 31a580683e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 39 additions and 421 deletions

View File

@ -12,7 +12,6 @@ import (
// These constants are string representations of Kubernetes resource types.
const (
All = "all"
Authority = "authority"
ConfigMap = "configmap"
CronJob = "cronjob"
DaemonSet = "daemonset"
@ -73,7 +72,6 @@ type resourceName struct {
// AllResources is a sorted list of all resources defined as constants above.
var AllResources = []string{
Authority,
AuthorizationPolicy,
CronJob,
DaemonSet,
@ -100,7 +98,6 @@ var StatAllResourceTypes = []string{
ReplicationController,
Pod,
Service,
Authority,
CronJob,
ReplicaSet,
}
@ -115,13 +112,11 @@ var CompletionResourceTypes = []string{
ReplicationController,
Pod,
Service,
Authority,
CronJob,
ReplicaSet,
}
var resourceNames = []resourceName{
{"au", "authority", "authorities"},
{"cj", "cronjob", "cronjobs"},
{"ds", "daemonset", "daemonsets"},
{"deploy", "deployment", "deployments"},
@ -190,8 +185,6 @@ func PluralResourceNameFromFriendlyName(friendlyName string) (string, error) {
// Essentially the reverse of CanonicalResourceNameFromFriendlyName
func ShortNameFromCanonicalResourceName(canonicalName string) string {
switch canonicalName {
case Authority:
return "au"
case CronJob:
return "cj"
case DaemonSet:

View File

@ -32,8 +32,6 @@ func TestCanonicalResourceNameFromFriendlyName(t *testing.T) {
"pod": Pod,
"deployment": Deployment,
"deployments": Deployment,
"au": Authority,
"authorities": Authority,
"cj": CronJob,
"cronjob": CronJob,
"serverauthz": ServerAuthorization,

View File

@ -4,7 +4,6 @@ import (
"context"
"fmt"
"os"
"strconv"
"strings"
"testing"
"time"
@ -37,7 +36,7 @@ func TestMain(m *testing.M) {
// requesting, and the test will pass.
func TestCliStatForLinkerdNamespace(t *testing.T) {
ctx := context.Background()
var prometheusPod, prometheusAuthority, prometheusNamespace, prometheusDeployment, metricsPod string
var prometheusPod, prometheusNamespace, prometheusDeployment, metricsPod string
// Get Metrics Pod
pods, err := TestHelper.GetPodNamesForDeployment(ctx, TestHelper.GetVizNamespace(), "metrics-api")
if err != nil {
@ -62,13 +61,11 @@ func TestCliStatForLinkerdNamespace(t *testing.T) {
testutil.Fatalf(t, "expected 1 pod for prometheus, got %d", len(pods))
}
prometheusPod = pods[0]
prometheusAuthority = prometheusDeployment + "." + prometheusNamespace + ".svc.cluster.local:9090"
testCases := []struct {
args []string
expectedRows map[string]string
status string
isAuthority bool
}{
{
args: []string{"viz", "stat", "deploy", "-n", TestHelper.GetLinkerdNamespace()},
@ -103,27 +100,6 @@ func TestCliStatForLinkerdNamespace(t *testing.T) {
"metrics-api": "1/1",
},
},
{
args: []string{"viz", "stat", "po", "-n", TestHelper.GetVizNamespace(), "--to", fmt.Sprintf("au/%s", prometheusAuthority), "--to-namespace", prometheusNamespace},
expectedRows: map[string]string{
metricsPod: "1/1",
},
status: "Running",
},
{
args: []string{"viz", "stat", "au", "-n", TestHelper.GetVizNamespace(), "--to", fmt.Sprintf("po/%s", prometheusPod), "--to-namespace", prometheusNamespace},
expectedRows: map[string]string{
prometheusAuthority: "-",
},
isAuthority: true,
},
{
args: []string{"viz", "stat", "au", "-n", TestHelper.GetVizNamespace(), "--to", fmt.Sprintf("po/%s", prometheusPod), "--to-namespace", prometheusNamespace},
expectedRows: map[string]string{
prometheusAuthority: "-",
},
isAuthority: true,
},
}
if !TestHelper.ExternalPrometheus() {
@ -131,7 +107,6 @@ func TestCliStatForLinkerdNamespace(t *testing.T) {
args []string
expectedRows map[string]string
status string
isAuthority bool
}{
{
args: []string{"viz", "stat", "deploy", "-n", TestHelper.GetVizNamespace()},
@ -162,7 +137,6 @@ func TestCliStatForLinkerdNamespace(t *testing.T) {
args []string
expectedRows map[string]string
status string
isAuthority bool
}{
{
args: []string{"viz", "stat", "deploy", "-n", TestHelper.GetVizNamespace()},
@ -212,7 +186,6 @@ func TestCliStatForLinkerdNamespace(t *testing.T) {
args []string
expectedRows map[string]string
status string
isAuthority bool
}{
{
args: []string{"viz", "stat", "svc", "-n", prefixedNs},
@ -244,9 +217,6 @@ func TestCliStatForLinkerdNamespace(t *testing.T) {
}
expectedColumnCount := 8
if tt.isAuthority {
expectedColumnCount = 7
}
if tt.status != "" {
expectedColumnCount++
}
@ -256,7 +226,7 @@ func TestCliStatForLinkerdNamespace(t *testing.T) {
}
for name, meshed := range tt.expectedRows {
if err := validateRowStats(name, meshed, tt.status, rowStats, tt.isAuthority); err != nil {
if err := validateRowStats(name, meshed, tt.status, rowStats); err != nil {
return err
}
}
@ -271,7 +241,7 @@ func TestCliStatForLinkerdNamespace(t *testing.T) {
})
}
func validateRowStats(name, expectedMeshCount, expectedStatus string, rowStats map[string]*testutil.RowStat, isAuthority bool) error {
func validateRowStats(name, expectedMeshCount, expectedStatus string, rowStats map[string]*testutil.RowStat) error {
stat, ok := rowStats[name]
if !ok {
return fmt.Errorf("no stats found for [%s]", name)
@ -313,12 +283,5 @@ func validateRowStats(name, expectedMeshCount, expectedStatus string, rowStats m
name, stat.P99Latency)
}
if stat.TCPOpenConnections != "-" && !isAuthority {
_, err := strconv.Atoi(stat.TCPOpenConnections)
if err != nil {
return fmt.Errorf("error parsing number of TCP connections [%s]: %w", stat.TCPOpenConnections, err)
}
}
return nil
}

View File

@ -4,7 +4,6 @@ import (
"context"
"fmt"
"os"
"strconv"
"strings"
"testing"
"time"
@ -37,7 +36,7 @@ func TestMain(m *testing.M) {
// requesting, and the test will pass.
func TestCliStatForLinkerdNamespace(t *testing.T) {
ctx := context.Background()
var prometheusPod, prometheusAuthority, prometheusNamespace, prometheusDeployment, metricsPod string
var prometheusPod, prometheusNamespace, prometheusDeployment, metricsPod string
// Get Metrics Pod
pods, err := TestHelper.GetPodNamesForDeployment(ctx, TestHelper.GetVizNamespace(), "metrics-api")
if err != nil {
@ -62,13 +61,11 @@ func TestCliStatForLinkerdNamespace(t *testing.T) {
testutil.Fatalf(t, "expected 1 pod for prometheus, got %d", len(pods))
}
prometheusPod = pods[0]
prometheusAuthority = prometheusDeployment + "." + prometheusNamespace + ".svc.cluster.local:9090"
testCases := []struct {
args []string
expectedRows map[string]string
status string
isAuthority bool
}{
{
args: []string{"viz", "stat", "deploy", "-n", TestHelper.GetLinkerdNamespace()},
@ -103,27 +100,6 @@ func TestCliStatForLinkerdNamespace(t *testing.T) {
"metrics-api": "1/1",
},
},
{
args: []string{"viz", "stat", "po", "-n", TestHelper.GetVizNamespace(), "--to", fmt.Sprintf("au/%s", prometheusAuthority), "--to-namespace", prometheusNamespace},
expectedRows: map[string]string{
metricsPod: "1/1",
},
status: "Running",
},
{
args: []string{"viz", "stat", "au", "-n", TestHelper.GetVizNamespace(), "--to", fmt.Sprintf("po/%s", prometheusPod), "--to-namespace", prometheusNamespace},
expectedRows: map[string]string{
prometheusAuthority: "-",
},
isAuthority: true,
},
{
args: []string{"viz", "stat", "au", "-n", TestHelper.GetVizNamespace(), "--to", fmt.Sprintf("po/%s", prometheusPod), "--to-namespace", prometheusNamespace},
expectedRows: map[string]string{
prometheusAuthority: "-",
},
isAuthority: true,
},
}
if !TestHelper.ExternalPrometheus() {
@ -131,7 +107,6 @@ func TestCliStatForLinkerdNamespace(t *testing.T) {
args []string
expectedRows map[string]string
status string
isAuthority bool
}{
{
args: []string{"viz", "stat", "deploy", "-n", TestHelper.GetVizNamespace()},
@ -162,7 +137,6 @@ func TestCliStatForLinkerdNamespace(t *testing.T) {
args []string
expectedRows map[string]string
status string
isAuthority bool
}{
{
args: []string{"viz", "stat", "deploy", "-n", TestHelper.GetVizNamespace()},
@ -212,7 +186,6 @@ func TestCliStatForLinkerdNamespace(t *testing.T) {
args []string
expectedRows map[string]string
status string
isAuthority bool
}{
{
args: []string{"viz", "stat", "svc", "-n", prefixedNs},
@ -244,9 +217,6 @@ func TestCliStatForLinkerdNamespace(t *testing.T) {
}
expectedColumnCount := 8
if tt.isAuthority {
expectedColumnCount = 7
}
if tt.status != "" {
expectedColumnCount++
}
@ -256,7 +226,7 @@ func TestCliStatForLinkerdNamespace(t *testing.T) {
}
for name, meshed := range tt.expectedRows {
if err := validateRowStats(name, meshed, tt.status, rowStats, tt.isAuthority); err != nil {
if err := validateRowStats(name, meshed, tt.status, rowStats); err != nil {
return err
}
}
@ -271,7 +241,7 @@ func TestCliStatForLinkerdNamespace(t *testing.T) {
})
}
func validateRowStats(name, expectedMeshCount, expectedStatus string, rowStats map[string]*testutil.RowStat, isAuthority bool) error {
func validateRowStats(name, expectedMeshCount, expectedStatus string, rowStats map[string]*testutil.RowStat) error {
stat, ok := rowStats[name]
if !ok {
return fmt.Errorf("No stats found for [%s]", name)
@ -313,12 +283,5 @@ func validateRowStats(name, expectedMeshCount, expectedStatus string, rowStats m
name, stat.P99Latency)
}
if stat.TCPOpenConnections != "-" && !isAuthority {
_, err := strconv.Atoi(stat.TCPOpenConnections)
if err != nil {
return fmt.Errorf("Error parsing number of TCP connections [%s]: %w", stat.TCPOpenConnections, err)
}
}
return nil
}

View File

@ -67,7 +67,7 @@ func TestEdges(t *testing.T) {
t.Run("Returns an error if request is for authority", func(t *testing.T) {
options.outputFormat = tableOutput
args := []string{"authority"}
expectedError := "Resource type is not supported: authority"
expectedError := "cannot find Kubernetes canonical name from friendly name [authority]"
_, err := buildEdgesRequests(args, options)
if err == nil || err.Error() != expectedError {

View File

@ -14,7 +14,6 @@ import (
pkgcmd "github.com/linkerd/linkerd2/pkg/cmd"
"github.com/linkerd/linkerd2/pkg/healthcheck"
"github.com/linkerd/linkerd2/pkg/k8s"
pb "github.com/linkerd/linkerd2/viz/metrics-api/gen/viz"
"github.com/linkerd/linkerd2/viz/metrics-api/util"
"github.com/linkerd/linkerd2/viz/pkg/api"
@ -379,7 +378,7 @@ func buildTopRoutesRequest(resource string, options *routesOptions) (*pb.TopRout
LabelSelector: options.labelSelector,
}
options.dstIsService = target.GetType() != k8s.Authority
options.dstIsService = true
if options.toResource != "" {
if options.toNamespace == "" {
@ -390,7 +389,7 @@ func buildTopRoutesRequest(resource string, options *routesOptions) (*pb.TopRout
return nil, err
}
options.dstIsService = toRes.GetType() != k8s.Authority
options.dstIsService = true
requestParams.ToName = toRes.Name
requestParams.ToNamespace = toRes.Namespace

View File

@ -372,7 +372,7 @@ func statHasRequestData(stat *pb.BasicStats) bool {
}
func isPodOwnerResource(typ string) bool {
return typ != k8s.Authority && typ != k8s.Service && typ != k8s.Server && typ != k8s.ServerAuthorization && typ != k8s.AuthorizationPolicy && typ != k8s.HTTPRoute
return typ != k8s.Service && typ != k8s.Server && typ != k8s.ServerAuthorization && typ != k8s.AuthorizationPolicy && typ != k8s.HTTPRoute
}
func writeStatsToBuffer(rows []*pb.StatTable_PodGroup_Row, w *tabwriter.Writer, options *statOptions) {
@ -433,7 +433,7 @@ func writeStatsToBuffer(rows []*pb.StatTable_PodGroup_Row, w *tabwriter.Writer,
statTables[resourceKey][key] = &row{}
if resourceKey != k8s.Server && resourceKey != k8s.ServerAuthorization {
meshedCount := fmt.Sprintf("%d/%d", r.MeshedPodCount, r.RunningPodCount)
if resourceKey == k8s.Authority || resourceKey == k8s.Service {
if resourceKey == k8s.Service {
meshedCount = "-"
}
statTables[resourceKey][key] = &row{
@ -503,7 +503,7 @@ func showTCPBytes(options *statOptions, resourceType string) bool {
}
func showTCPConns(resourceType string) bool {
return resourceType != k8s.Authority && resourceType != k8s.ServerAuthorization && resourceType != k8s.AuthorizationPolicy && resourceType != k8s.HTTPRoute
return resourceType != k8s.ServerAuthorization && resourceType != k8s.AuthorizationPolicy && resourceType != k8s.HTTPRoute
}
func printSingleStatTable(stats map[string]*row, resourceTypeLabel, resourceType string, w *tabwriter.Writer, maxNameLength, maxNamespaceLength, maxLeafLength, maxApexLength, maxDstLength, maxWeightLength int, options *statOptions) {

View File

@ -125,9 +125,7 @@ func (s *grpcServer) StatSummary(ctx context.Context, req *pb.StatSummaryRequest
statReq.Selector.Resource.Type = resource
go func() {
if prometheus.IsNonK8sResourceQuery(statReq.GetSelector().GetResource().GetType()) {
resultChan <- s.nonK8sResourceQuery(ctx, statReq)
} else if statReq.GetSelector().GetResource().GetType() == k8s.Service {
if statReq.GetSelector().GetResource().GetType() == k8s.Service {
resultChan <- s.serviceResourceQuery(ctx, statReq)
} else if isPolicyResource(statReq.GetSelector().GetResource()) {
resultChan <- s.policyResourceQuery(ctx, statReq)
@ -362,42 +360,6 @@ func sortTrafficSplitRows(rows []*pb.StatTable_PodGroup_Row) []*pb.StatTable_Pod
return rows
}
func (s *grpcServer) nonK8sResourceQuery(ctx context.Context, req *pb.StatSummaryRequest) resourceResult {
var requestMetrics map[rKey]*pb.BasicStats
if !req.SkipStats {
var err error
requestMetrics, _, err = s.getStatMetrics(ctx, req, req.TimeWindow)
if err != nil {
return resourceResult{res: nil, err: err}
}
}
rows := make([]*pb.StatTable_PodGroup_Row, 0)
for rkey, metrics := range requestMetrics {
rkey.Type = req.GetSelector().GetResource().GetType()
row := pb.StatTable_PodGroup_Row{
Resource: &pb.Resource{
Type: rkey.Type,
Namespace: rkey.Namespace,
Name: rkey.Name,
},
TimeWindow: req.TimeWindow,
Stats: metrics,
}
rows = append(rows, &row)
}
rsp := pb.StatTable{
Table: &pb.StatTable_PodGroup_{
PodGroup: &pb.StatTable_PodGroup{
Rows: rows,
},
},
}
return resourceResult{res: &rsp, err: nil}
}
// get the list of objects for which we want to return results
func getResultKeys(
req *pb.StatSummaryRequest,

View File

@ -22,11 +22,11 @@ type statSumExpected struct {
func prometheusMetric(resName string, resType string) model.Vector {
return model.Vector{
genPromSample(resName, resType, "emojivoto", false),
genPromSample(resName, resType, false),
}
}
func genPromSample(resName string, resType string, resNs string, isDst bool) *model.Sample {
func genPromSample(resName string, resType string, isDst bool) *model.Sample {
labelName := model.LabelName(resType)
namespaceLabel := model.LabelName("namespace")
@ -38,7 +38,7 @@ func genPromSample(resName string, resType string, resNs string, isDst bool) *mo
return &model.Sample{
Metric: model.Metric{
labelName: model.LabelValue(resName),
namespaceLabel: model.LabelValue(resNs),
namespaceLabel: model.LabelValue("emojivoto"),
"classification": model.LabelValue("success"),
"tls": model.LabelValue("true"),
},
@ -865,7 +865,7 @@ status:
`,
},
mockPromResponse: model.Vector{
genPromSample("emojivoto-1", "pod", "emojivoto", false),
genPromSample("emojivoto-1", "pod", 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))`,
@ -922,7 +922,7 @@ status:
`,
},
mockPromResponse: model.Vector{
genPromSample("emojivoto-1", "pod", "emojivoto", false),
genPromSample("emojivoto-1", "pod", 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))`,
@ -990,7 +990,7 @@ status:
`,
},
mockPromResponse: model.Vector{
genPromSample("emojivoto-1", "pod", "emojivoto", true),
genPromSample("emojivoto-1", "pod", 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))`,
@ -1058,7 +1058,7 @@ status:
`,
},
mockPromResponse: model.Vector{
genPromSample("emojivoto-1", "pod", "emojivoto", true),
genPromSample("emojivoto-1", "pod", 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))`,
@ -1226,28 +1226,6 @@ status:
},
},
},
{
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{
@ -1686,146 +1664,6 @@ status:
})
})
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{
{

View File

@ -2,7 +2,6 @@ package api
import (
"context"
"errors"
"fmt"
"sort"
"strings"
@ -58,11 +57,6 @@ func (s *grpcServer) TopRoutes(ctx context.Context, req *pb.TopRoutesRequest) (*
if err != nil {
return nil, err
}
if targetResource.GetType() == k8s.Authority {
// Authority cannot be the target because authorities don't have namespaces,
// therefore there is no namespace in which to look for a service profile.
return topRoutesError(req, "Authority cannot be the target of a routes query; try using an authority in the --to flag instead"), nil
}
err = s.validateTimeWindow(ctx, req.TimeWindow)
if err != nil {
@ -139,31 +133,22 @@ func (s *grpcServer) topRoutesFor(ctx context.Context, req *pb.TopRoutesRequest,
profiles := make(map[string]*sp.ServiceProfile)
if requestedResource.GetType() == k8s.Authority {
// Authorities may not be a source, so we know this is a ToResource.
profiles, err = s.getProfilesForAuthority(requestedResource.GetName(), clientNs, labelSelector)
// Lookup individual resource objects.
objects, err := s.k8sAPI.GetObjects(requestedResource.Namespace, requestedResource.Type, requestedResource.Name, labelSelector)
if err != nil {
return nil, err
}
// Find service profiles for all services in all objects in the resource.
for _, obj := range objects {
// Lookup services for each object.
services, err := s.k8sAPI.GetServicesFor(obj, false)
if err != nil {
return nil, err
}
} else {
// Non-authority resource.
// Lookup individual resource objects.
objects, err := s.k8sAPI.GetObjects(requestedResource.Namespace, requestedResource.Type, requestedResource.Name, labelSelector)
if err != nil {
return nil, err
}
// Find service profiles for all services in all objects in the resource.
for _, obj := range objects {
// Lookup services for each object.
services, err := s.k8sAPI.GetServicesFor(obj, false)
if err != nil {
return nil, err
}
for _, svc := range services {
p := s.k8sAPI.GetServiceProfileFor(svc, clientNs, s.clusterDomain)
profiles[svc.GetName()] = p
}
for _, svc := range services {
p := s.k8sAPI.GetServiceProfileFor(svc, clientNs, s.clusterDomain)
profiles[svc.GetName()] = p
}
}
@ -197,43 +182,13 @@ func validateRequest(req *pb.TopRoutesRequest) *pb.TopRoutesResponse {
if req.GetNone() == nil {
// This is an outbound (--to) request.
targetType := req.GetSelector().GetResource().GetType()
if targetType == k8s.Service || targetType == k8s.Authority {
if targetType == k8s.Service {
return topRoutesError(req, fmt.Sprintf("The %s resource type is not supported with 'to' queries", targetType))
}
}
return nil
}
func (s *grpcServer) getProfilesForAuthority(authority string, clientNs string, labelSelector labels.Selector) (map[string]*sp.ServiceProfile, error) {
if authority == "" {
// All authorities
ps, err := s.k8sAPI.SP().Lister().ServiceProfiles(clientNs).List(labelSelector)
if err != nil {
return nil, err
}
if len(ps) == 0 {
return nil, errors.New("No ServiceProfiles found")
}
profiles := make(map[string]*sp.ServiceProfile)
for _, p := range ps {
profiles[p.Name] = p
}
return profiles, nil
}
// Specific authority
p, err := s.k8sAPI.SP().Lister().ServiceProfiles(clientNs).Get(authority)
if err != nil {
return nil, err
}
return map[string]*sp.ServiceProfile{
p.Name: p,
}, nil
}
func (s *grpcServer) getRouteMetrics(ctx context.Context, req *pb.TopRoutesRequest, profiles map[string]*sp.ServiceProfile, resource *pb.Resource) (indexedTable, error) {
timeWindow := req.TimeWindow

View File

@ -460,44 +460,4 @@ func TestTopRoutes(t *testing.T) {
testTopRoutes(t, expectations)
})
t.Run("Successfully performs an outbound authority query", func(t *testing.T) {
routes := []string{"/a"}
counts := []uint64{123}
expectations := []topRoutesExpected{
{
expectedStatRPC: expectedStatRPC{
err: nil,
mockPromResponse: routesMetric([]string{"/a"}),
expectedPrometheusQueries: []string{
`histogram_quantile(0.5, sum(irate(route_response_latency_ms_bucket{deployment="books", direction="outbound", dst=~"(books.default.svc.cluster.local)(:\\d+)?", namespace="default"}[1m])) by (le, dst, rt_route))`,
`histogram_quantile(0.95, sum(irate(route_response_latency_ms_bucket{deployment="books", direction="outbound", dst=~"(books.default.svc.cluster.local)(:\\d+)?", namespace="default"}[1m])) by (le, dst, rt_route))`,
`histogram_quantile(0.99, sum(irate(route_response_latency_ms_bucket{deployment="books", direction="outbound", dst=~"(books.default.svc.cluster.local)(:\\d+)?", namespace="default"}[1m])) by (le, dst, rt_route))`,
`sum(increase(route_response_total{deployment="books", direction="outbound", dst=~"(books.default.svc.cluster.local)(:\\d+)?", namespace="default"}[1m])) by (rt_route, dst, classification)`,
`sum(increase(route_actual_response_total{deployment="books", direction="outbound", dst=~"(books.default.svc.cluster.local)(:\\d+)?", namespace="default"}[1m])) by (rt_route, dst, classification)`,
},
k8sConfigs: booksConfig,
},
req: &pb.TopRoutesRequest{
Selector: &pb.ResourceSelection{
Resource: &pb.Resource{
Namespace: "default",
Type: pkgK8s.Deployment,
Name: "books",
},
},
Outbound: &pb.TopRoutesRequest_ToResource{
ToResource: &pb.Resource{
Type: pkgK8s.Authority,
Name: "books.default.svc.cluster.local",
},
},
TimeWindow: "1m",
},
expectedResponse: GenTopRoutesResponse(routes, counts, true, "books.default.svc.cluster.local"),
},
}
testTopRoutes(t, expectations)
})
}

View File

@ -274,9 +274,7 @@ func validateFromResourceType(resourceType string) (string, error) {
if err != nil {
return "", err
}
if name == k8s.Authority {
return "", errors.New("cannot query traffic --from an authority")
}
return name, nil
}

View File

@ -63,10 +63,7 @@ func QueryLabels(resource *pb.Resource) model.LabelSet {
// note that metricToKey assumes the label ordering (namespace, name)
func DstGroupByLabelNames(resource *pb.Resource) model.LabelNames {
names := model.LabelNames{DstNamespaceLabel}
if IsNonK8sResourceQuery(resource.GetType()) {
names = append(names, ResourceType(resource))
} else if resource.Type != k8s.Namespace {
if resource.Type != k8s.Namespace {
names = append(names, "dst_"+ResourceType(resource))
}
return names
@ -76,14 +73,11 @@ func DstGroupByLabelNames(resource *pb.Resource) model.LabelNames {
func DstQueryLabels(resource *pb.Resource) model.LabelSet {
set := model.LabelSet{}
if resource.Name != "" {
if IsNonK8sResourceQuery(resource.GetType()) {
set[ResourceType(resource)] = model.LabelValue(resource.Name)
} else {
set["dst_"+ResourceType(resource)] = model.LabelValue(resource.Name)
if shouldAddNamespaceLabel(resource) {
set[DstNamespaceLabel] = model.LabelValue(resource.Namespace)
}
set["dst_"+ResourceType(resource)] = model.LabelValue(resource.Name)
if shouldAddNamespaceLabel(resource) {
set[DstNamespaceLabel] = model.LabelValue(resource.Namespace)
}
}
return set
@ -98,7 +92,3 @@ func ResourceType(resource *pb.Resource) model.LabelName {
func shouldAddNamespaceLabel(resource *pb.Resource) bool {
return resource.Type != k8s.Namespace && resource.Namespace != ""
}
func IsNonK8sResourceQuery(resourceType string) bool {
return resourceType == k8s.Authority
}

View File

@ -18,7 +18,6 @@ import (
// - target resource on an outbound 'to' query
// - destination resource on an outbound 'from' query
var ValidTargets = []string{
k8s.Authority,
k8s.CronJob,
k8s.DaemonSet,
k8s.Deployment,