Repository: linkerd/linkerd2
Pull request #7455 (2021-12-10 11:14:59)

ci: Replace deprecated linters
Two of our `go` linters have been deprecated. Whenever we run `bin/lint` we get warning messages:

```
WARN [runner] The linter 'scopelint' is deprecated (since v1.39.0) due to: The repository of the linter has been deprecated by the owner.  Replaced by exportloopref.
WARN [runner] The linter 'golint' is deprecated (since v1.41.0) due to: The repository of the linter has been archived by the owner.  Replaced by revive.
```

PR updates `.golangci.yaml` to use `revive` instead of `golint` and `exportloopref` instead of `scopelint`. [Revive](https://github.com/mgechev/revive) apparently runs twice as fast as golint, although when I timed the script, numbers were the same as before. At a minimum, this should at least get us rid of the warning messages.

Signed-off-by: Matei David <matei@buoyant.io>

<!--  Thanks for sending a pull request!

If you already have a well-structured git commit message, chances are GitHub
set the title and description of this PR to the git commit message subject and
body, respectively. If so, you may delete these instructions and submit your PR.

If this is your first time, please read our contributor guide:
https://github.com/linkerd/linkerd2/blob/main/CONTRIBUTING.md

The title and description of your Pull Request should match the git commit
subject and body, respectively. Git commit messages are structured as follows:

```
Subject

Problem

Solution

Validation

Fixes #[GitHub issue ID]

DCO Sign off
```

Example git commit message:

```
Introduce Pull Request Template

GitHub's community guidelines recommend a pull request template, the repo was
lacking one.

Introduce a `PULL_REQUEST_TEMPLATE.md` file.

Once merged, the
[Community profile checklist](https://github.com/linkerd/linkerd2/community)
should indicate the repo now provides a pull request template.

Fixes #3321

Signed-off-by: Jane Smith <jane.smith@example.com>
```

Note the git commit message subject becomes the pull request title.

For more details around git commits, see the section on Committing in our
contributor guide:
https://github.com/linkerd/linkerd2/blob/main/CONTRIBUTING.md#committing
-->



Patch:
diff --git a/.golangci.yml b/.golangci.yml
index 6b2cf3f0c..3a31b984e 100644
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -16,14 +16,14 @@ linters:
   - bodyclose
   - deadcode
   - depguard
+  - exportloopref
   - goconst
-  - golint
   - gosimple
   - govet
   - ineffassign
   - misspell
   - nakedret
-  - scopelint
+  - revive
   - typecheck
   - unconvert
   - unparam
diff --git a/Cargo.lock b/Cargo.lock
index d398fa3d7..c2d86111a 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -40,9 +40,9 @@ dependencies = [
 
 [[package]]
 name = "async-trait"
-version = "0.1.51"
+version = "0.1.52"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "44318e776df68115a881de9a8fd1b9e53368d7a4a5ce4cc48517da3393233a5e"
+checksum = "061a7acccaa286c011ddc30970520b98fa40e00c9d644633fb26b5fc63a265e3"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -428,9 +428,9 @@ dependencies = [
 
 [[package]]
 name = "h2"
-version = "0.3.7"
+version = "0.3.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7fd819562fcebdac5afc5c113c3ec36f902840b70fd4fc458799c8ce4607ae55"
+checksum = "8f072413d126e57991455e0a922b31e4c8ba7c2ffbebf6b78b4f8521397d65cd"
 dependencies = [
  "bytes",
  "fnv",
@@ -530,9 +530,9 @@ checksum = "6456b8a6c8f33fee7d958fcd1b60d55b11940a79e63ae87013e6d22e26034440"
 
 [[package]]
 name = "hyper"
-version = "0.14.15"
+version = "0.14.16"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "436ec0091e4f20e655156a30a0df3770fe2900aa301e548e08446ec794b6953c"
+checksum = "b7ec3e62bdc98a2f0393a5048e4c30ef659440ea6e0e572965103e72bd836f55"
 dependencies = [
  "bytes",
  "futures-channel",
@@ -1861,9 +1861,9 @@ dependencies = [
 
 [[package]]
 name = "tonic"
-version = "0.6.1"
+version = "0.6.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "24203b79cf2d68909da91178db3026e77054effba0c5d93deb870d3ca7b35afa"
+checksum = "ff08f4649d10a70ffa3522ca559031285d8e421d727ac85c60825761818f5d0a"
 dependencies = [
  "async-stream",
  "async-trait",
diff --git a/jaeger/cmd/check.go b/jaeger/cmd/check.go
index 282c0572d..0a663cbfb 100644
--- a/jaeger/cmd/check.go
+++ b/jaeger/cmd/check.go
@@ -110,7 +110,7 @@ func jaegerCategory(hc *healthcheck.HealthChecker) *healthcheck.Category {
 					return err
 				}
 
-				return healthcheck.CheckPodsRunning(pods, "")
+				return healthcheck.CheckPodsRunning(pods, jaegerNamespace)
 
 			}))
 
diff --git a/pkg/healthcheck/healthcheck.go b/pkg/healthcheck/healthcheck.go
index f1a4dbd62..871e6b2ae 100644
--- a/pkg/healthcheck/healthcheck.go
+++ b/pkg/healthcheck/healthcheck.go
@@ -1312,8 +1312,7 @@ func (hc *HealthChecker) allCategories() []*Category {
 						if err != nil {
 							return err
 						}
-
-						return validateDataPlanePods(pods, hc.DataPlaneNamespace)
+						return CheckPodsRunning(pods, hc.DataPlaneNamespace)
 					},
 				},
 				{
@@ -1504,7 +1503,7 @@ func (hc *HealthChecker) CheckProxyHealth(ctx context.Context, controlPlaneNames
 	}
 
 	// Validate the status of the pods
-	err = validateDataPlanePods(podList.Items, controlPlaneNamespace)
+	err = CheckPodsRunning(podList.Items, controlPlaneNamespace)
 	if err != nil {
 		return err
 	}
@@ -2706,36 +2705,6 @@ func validateControlPlanePods(pods []corev1.Pod) error {
 	return nil
 }
 
-func validateDataPlanePods(pods []corev1.Pod, targetNamespace string) error {
-	if len(pods) == 0 {
-		msg := fmt.Sprintf("No \"%s\" containers found", k8s.ProxyContainerName)
-		if targetNamespace != "" {
-			msg += fmt.Sprintf(" in the \"%s\" namespace", targetNamespace)
-		}
-		return fmt.Errorf(msg)
-	}
-
-	for _, pod := range pods {
-		status := k8s.GetPodStatus(pod)
-		// Skip validating meshed pods that are in the `Completed` or `Shutdown` state
-		// as they do not have a running proxy
-		if status == "Completed" || status == "Shutdown" {
-			continue
-		}
-
-		if status != string(corev1.PodRunning) && status != "Evicted" {
-			return fmt.Errorf("The \"%s\" pod is not running", pod.Name)
-		}
-
-		if !k8s.GetProxyReady(pod) {
-			return fmt.Errorf("The \"%s\" container in the \"%s\" pod is not ready",
-				k8s.ProxyContainerName, pod.Name)
-		}
-	}
-
-	return nil
-}
-
 func checkUnschedulablePods(pods []corev1.Pod) error {
 	for _, pod := range pods {
 		for _, condition := range pod.Status.Conditions {
@@ -2787,17 +2756,25 @@ func CheckForPods(pods []corev1.Pod, deployNames []string) error {
 
 // CheckPodsRunning checks if the given pods are in running state
 // along with containers to be in ready state
-func CheckPodsRunning(pods []corev1.Pod, podsNotFoundMsg string) error {
-	if len(pods) == 0 && podsNotFoundMsg != "" {
-		return fmt.Errorf(podsNotFoundMsg)
+func CheckPodsRunning(pods []corev1.Pod, namespace string) error {
+	if len(pods) == 0 {
+		msg := fmt.Sprintf("no \"%s\" containers found", k8s.ProxyContainerName)
+		if namespace != "" {
+			msg += fmt.Sprintf(" in the \"%s\" namespace", namespace)
+		}
+		return fmt.Errorf(msg)
 	}
 	for _, pod := range pods {
-		if pod.Status.Phase != corev1.PodRunning && pod.Status.Phase != "Evicted" {
-			return fmt.Errorf("%s status is %s", pod.Name, pod.Status.Phase)
-		}
+		status := k8s.GetPodStatus(pod)
 
-		if !k8s.GetProxyReady(pod) {
-			return fmt.Errorf("container %s in pod %s is not ready", k8s.ProxyContainerName, pod.Name)
+		// Skip validating meshed pods that are in the `Completed` or
+		// `Shutdown` state as they do not have a running proxy
+		if status == "Completed" || status == "Shutdown" {
+			continue
+		} else if status != string(corev1.PodRunning) && status != "Evicted" {
+			return fmt.Errorf("pod \"%s\" status is %s", pod.Name, pod.Status.Phase)
+		} else if !k8s.GetProxyReady(pod) {
+			return fmt.Errorf("container \"%s\" in pod \"%s\" is not ready", k8s.ProxyContainerName, pod.Name)
 		}
 	}
 	return nil
diff --git a/viz/pkg/healthcheck/healthcheck.go b/viz/pkg/healthcheck/healthcheck.go
index 2b316970a..20e107fd9 100644
--- a/viz/pkg/healthcheck/healthcheck.go
+++ b/viz/pkg/healthcheck/healthcheck.go
@@ -164,7 +164,7 @@ func (hc *HealthChecker) VizCategory() *healthcheck.Category {
 					return err
 				}
 
-				return healthcheck.CheckPodsRunning(pods, "")
+				return healthcheck.CheckPodsRunning(pods, hc.vizNamespace)
 			}),
 		*healthcheck.NewChecker("viz extension proxies are healthy").
 			WithHintAnchor("l5d-viz-proxy-healthy").


Test patch:
diff --git a/pkg/healthcheck/healthcheck_test.go b/pkg/healthcheck/healthcheck_test.go
index 879be377c..7aa5a95d3 100644
--- a/pkg/healthcheck/healthcheck_test.go
+++ b/pkg/healthcheck/healthcheck_test.go
@@ -1491,14 +1491,14 @@ func TestValidateDataPlaneNamespace(t *testing.T) {
 	}
 }
 
-func TestValidateDataPlanePods(t *testing.T) {
+func TestCheckDataPlanePods(t *testing.T) {
 
 	t.Run("Returns an error if no inject pods were found", func(t *testing.T) {
-		err := validateDataPlanePods([]corev1.Pod{}, "emojivoto")
+		err := CheckPodsRunning([]corev1.Pod{}, "emojivoto")
 		if err == nil {
 			t.Fatal("Expected error, got nothing")
 		}
-		if err.Error() != "No \"linkerd-proxy\" containers found in the \"emojivoto\" namespace" {
+		if err.Error() != "no \"linkerd-proxy\" containers found in the \"emojivoto\" namespace" {
 			t.Fatalf("Unexpected error message: %s", err.Error())
 		}
 	})
@@ -1555,11 +1555,11 @@ func TestValidateDataPlanePods(t *testing.T) {
 			},
 		}
 
-		err := validateDataPlanePods(pods, "emojivoto")
+		err := CheckPodsRunning(pods, "emojivoto")
 		if err == nil {
 			t.Fatal("Expected error, got nothing")
 		}
-		if err.Error() != "The \"voting-65b9fffd77-rlwsd\" pod is not running" {
+		if err.Error() != "pod \"voting-65b9fffd77-rlwsd\" status is Failed" {
 			t.Fatalf("Unexpected error message: %s", err.Error())
 		}
 	})
@@ -1580,7 +1580,7 @@ func TestValidateDataPlanePods(t *testing.T) {
 			},
 		}
 
-		err := validateDataPlanePods(pods, "emojivoto")
+		err := CheckPodsRunning(pods, "emojivoto")
 		if err != nil {
 			t.Fatalf("Expected no error, got %s", err)
 		}
@@ -1597,7 +1597,7 @@ func TestValidateDataPlanePods(t *testing.T) {
 			},
 		}
 
-		err := validateDataPlanePods(pods, "emojivoto")
+		err := CheckPodsRunning(pods, "emojivoto")
 		if err != nil {
 			t.Fatalf("Expected no error, got %s", err)
 		}
@@ -1655,11 +1655,11 @@ func TestValidateDataPlanePods(t *testing.T) {
 			},
 		}
 
-		err := validateDataPlanePods(pods, "emojivoto")
+		err := CheckPodsRunning(pods, "emojivoto")
 		if err == nil {
 			t.Fatal("Expected error, got nothing")
 		}
-		if err.Error() != "The \"linkerd-proxy\" container in the \"vote-bot-644b8cb6b4-g8nlr\" pod is not ready" {
+		if err.Error() != "container \"linkerd-proxy\" in pod \"vote-bot-644b8cb6b4-g8nlr\" is not ready" {
 			t.Fatalf("Unexpected error message: %s", err.Error())
 		}
 	})
@@ -1716,7 +1716,7 @@ func TestValidateDataPlanePods(t *testing.T) {
 			},
 		}
 
-		err := validateDataPlanePods(pods, "emojivoto")
+		err := CheckPodsRunning(pods, "emojivoto")
 		if err != nil {
 			t.Fatalf("Unexpected error: %s", err)
 		}
@@ -1777,7 +1777,7 @@ func TestValidateDataPlanePods(t *testing.T) {
 			},
 		}
 
-		err := validateDataPlanePods(pods, "emojivoto")
+		err := CheckPodsRunning(pods, "emojivoto")
 		if err != nil {
 			t.Fatalf("Unexpected error: %s", err)
 		}
diff --git a/test/integration/endpoints/endpoints_test.go b/test/integration/endpoints/endpoints_test.go
index cc1d56ca2..e97670fa5 100644
--- a/test/integration/endpoints/endpoints_test.go
+++ b/test/integration/endpoints/endpoints_test.go
@@ -2,6 +2,7 @@ package endpoints
 
 import (
 	"bytes"
+	"context"
 	"fmt"
 	"os"
 	"regexp"
@@ -20,50 +21,57 @@ func TestMain(m *testing.M) {
 }
 
 func TestGoodEndpoints(t *testing.T) {
-	ns := TestHelper.GetLinkerdNamespace()
-	vizNs := TestHelper.GetVizNamespace()
+	ctx := context.Background()
+	controlNs := TestHelper.GetLinkerdNamespace()
 	testDataPath := "testdata"
 	cmd := []string{
 		"diagnostics",
 		"endpoints",
-		fmt.Sprintf("linkerd-dst.%s.svc.cluster.local:8086", ns),
-		fmt.Sprintf("grafana.%s.svc.cluster.local:3000", vizNs),
-		fmt.Sprintf("linkerd-identity.%s.svc.cluster.local:8080", ns),
-		fmt.Sprintf("linkerd-proxy-injector.%s.svc.cluster.local:443", ns),
-		fmt.Sprintf("tap.%s.svc.cluster.local:8088", vizNs),
-		fmt.Sprintf("web.%s.svc.cluster.local:8084", vizNs),
+		fmt.Sprintf("linkerd-dst.%s.svc.cluster.local:8086", controlNs),
+		fmt.Sprintf("linkerd-identity.%s.svc.cluster.local:8080", controlNs),
+		fmt.Sprintf("linkerd-proxy-injector.%s.svc.cluster.local:443", controlNs),
+		fmt.Sprintf("nginx.%s.svc.cluster.local:8080", "linkerd-endpoints-test"),
+		"-ojson",
 	}
 
-	if !TestHelper.ExternalPrometheus() {
-		cmd = append(cmd, fmt.Sprintf("prometheus.%s.svc.cluster.local:9090", vizNs))
-	} else {
-		cmd = append(cmd, "prometheus.external-prometheus.svc.cluster.local:9090")
-		testDataPath += "/external_prometheus"
-	}
+	TestHelper.WithDataPlaneNamespace(ctx, "endpoints-test", map[string]string{}, t, func(t *testing.T, ns string) {
+		out, err := TestHelper.Kubectl("", "apply", "-f", fmt.Sprintf("%s/%s", testDataPath, "nginx.yaml"), "-n", ns)
+		if err != nil {
+			testutil.AnnotatedFatalf(t, "unexpected error", "unexpected error: %v output:\n%s", err, out)
+		}
 
-	cmd = append(cmd, "-ojson")
-	out, err := TestHelper.LinkerdRun(cmd...)
-	if err != nil {
-		testutil.AnnotatedFatal(t, "unexpected error", err)
-	}
+		err = TestHelper.CheckPods(ctx, ns, "nginx", 1)
+		if err != nil {
+			if rce, ok := err.(*testutil.RestartCountError); ok {
+				testutil.AnnotatedWarn(t, "CheckPods timed-out", rce)
+			} else {
+				testutil.AnnotatedError(t, "CheckPods timed-out", err)
+			}
+		}
 
-	tpl := template.Must(template.ParseFiles(testDataPath + "/linkerd_endpoints.golden"))
-	vars := struct {
-		Ns    string
-		VizNs string
-	}{
-		ns,
-		vizNs,
-	}
-	var b bytes.Buffer
-	if err := tpl.Execute(&b, vars); err != nil {
-		testutil.AnnotatedFatalf(t, "failed to parse linkerd_endpoints.golden template", "failed to parse linkerd_endpoints.golden template: %s", err)
-	}
+		out, err = TestHelper.LinkerdRun(cmd...)
+		if err != nil {
+			testutil.AnnotatedFatal(t, "unexpected error", err)
+		}
 
-	r := regexp.MustCompile(b.String())
-	if !r.MatchString(out) {
-		testutil.AnnotatedErrorf(t, "unexpected output", "expected output:\n%s\nactual:\n%s", b.String(), out)
-	}
+		tpl := template.Must(template.ParseFiles(testDataPath + "/linkerd_endpoints.golden"))
+		vars := struct {
+			Ns         string
+			EndpointNs string
+		}{
+			controlNs,
+			ns,
+		}
+		var b bytes.Buffer
+		if err := tpl.Execute(&b, vars); err != nil {
+			testutil.AnnotatedFatalf(t, "failed to parse linkerd_endpoints.golden template", "failed to parse linkerd_endpoints.golden template: %s", err)
+		}
+
+		r := regexp.MustCompile(b.String())
+		if !r.MatchString(out) {
+			testutil.AnnotatedErrorf(t, "unexpected output", "expected output:\n%s\nactual:\n%s", b.String(), out)
+		}
+	})
 }
 
 // TODO: when #3004 gets fixed, add a negative test for mismatched ports
diff --git a/test/integration/endpoints/testdata/external_prometheus/linkerd_endpoints.golden b/test/integration/endpoints/testdata/external_prometheus/linkerd_endpoints.golden
index 1722b076a..85678ba98 100644
--- a/test/integration/endpoints/testdata/external_prometheus/linkerd_endpoints.golden
+++ b/test/integration/endpoints/testdata/external_prometheus/linkerd_endpoints.golden
@@ -26,26 +26,5 @@
     "port": 8443,
     "pod": "linkerd\-proxy\-injector-[a-f0-9]+\-[a-z0-9]+",
     "service": "linkerd\-proxy\-injector\.{{.Ns}}"
-  \},
-  \{
-    "namespace": "{{.VizNs}}",
-    "ip": "\d+\.\d+\.\d+\.\d+",
-    "port": 3000,
-    "pod": "grafana\-[a-f0-9]+\-[a-z0-9]+",
-    "service": "grafana\.{{.VizNs}}"
-  \},
-  \{
-    "namespace": "{{.VizNs}}",
-    "ip": "\d+\.\d+\.\d+\.\d+",
-    "port": 8088,
-    "pod": "tap\-[a-f0-9]+\-[a-z0-9]+",
-    "service": "tap\.{{.VizNs}}"
-  \},
-  \{
-    "namespace": "{{.VizNs}}",
-    "ip": "\d+\.\d+\.\d+\.\d+",
-    "port": 8084,
-    "pod": "web\-[a-f0-9]+\-[a-z0-9]+",
-    "service": "web\.{{.VizNs}}"
   \}
 \]
diff --git a/test/integration/endpoints/testdata/linkerd_endpoints.golden b/test/integration/endpoints/testdata/linkerd_endpoints.golden
index 640ba9a01..2d7bf22d5 100644
--- a/test/integration/endpoints/testdata/linkerd_endpoints.golden
+++ b/test/integration/endpoints/testdata/linkerd_endpoints.golden
@@ -21,31 +21,10 @@
     "service": "linkerd\-proxy\-injector\.{{.Ns}}"
   \},
   \{
-    "namespace": "{{.VizNs}}",
+    "namespace": "{{.EndpointNs}}",
     "ip": "\d+\.\d+\.\d+\.\d+",
-    "port": 3000,
-    "pod": "grafana\-[a-f0-9]+\-[a-z0-9]+",
-    "service": "grafana\.{{.VizNs}}"
-  \},
-  \{
-    "namespace": "{{.VizNs}}",
-    "ip": "\d+\.\d+\.\d+\.\d+",
-    "port": 9090,
-    "pod": "prometheus-[a-f0-9]+\-[a-z0-9]+",
-    "service": "prometheus\.{{.VizNs}}"
-  \},
-  \{
-    "namespace": "{{.VizNs}}",
-    "ip": "\d+\.\d+\.\d+\.\d+",
-    "port": 8088,
-    "pod": "tap\-[a-f0-9]+\-[a-z0-9]+",
-    "service": "tap\.{{.VizNs}}"
-  \},
-  \{
-    "namespace": "{{.VizNs}}",
-    "ip": "\d+\.\d+\.\d+\.\d+",
-    "port": 8084,
-    "pod": "web\-[a-f0-9]+\-[a-z0-9]+",
-    "service": "web\.{{.VizNs}}"
+    "port": 8080,
+    "pod": "nginx\-[a-f0-9]+\-[a-z0-9]+",
+    "service": "nginx\.{{.EndpointNs}}"
   \}
 \]
diff --git a/test/integration/endpoints/testdata/nginx.yaml b/test/integration/endpoints/testdata/nginx.yaml
new file mode 100644
index 000000000..3850c1ba6
--- /dev/null
+++ b/test/integration/endpoints/testdata/nginx.yaml
@@ -0,0 +1,32 @@
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: nginx
+  labels:
+    app: nginx
+spec:
+  selector:
+    matchLabels:
+      app: nginx
+  replicas: 1
+  template:
+    metadata:
+      annotations:
+        linkerd.io/inject: "enabled"
+      labels:
+        app: nginx
+    spec:
+      containers:
+        - name: nginx
+          image: nginx:alpine
+---
+apiVersion: v1
+kind: Service
+metadata:
+  name: nginx
+spec:
+  ports:
+  - name: service
+    port: 8080
+  selector:
+    app: nginx 
diff --git a/test/integration/localhost/localhost_test.go b/test/integration/localhost/localhost_test.go
index e870805e6..4a133dc40 100644
--- a/test/integration/localhost/localhost_test.go
+++ b/test/integration/localhost/localhost_test.go
@@ -2,23 +2,15 @@ package localhost
 
 import (
 	"context"
-	"encoding/json"
 	"fmt"
 	"os"
+	"regexp"
 	"testing"
 	"time"
 
 	"github.com/linkerd/linkerd2/testutil"
 )
 
-type jsonStats struct {
-	Namespace          string   `json:"namespace"`
-	Name               string   `json:"name"`
-	Success            *float64 `json:"success"`
-	Rps                *float64 `json:"rps"`
-	TCPOpenConnections *int     `json:"tcp_open_connections"`
-}
-
 var TestHelper *testutil.TestHelper
 
 func TestMain(m *testing.M) {
@@ -66,33 +58,33 @@ func TestLocalhostServer(t *testing.T) {
 		err = TestHelper.RetryFor(50*time.Second, func() error {
 			// Use a short time window so that transient errors at startup
 			// fall out of the window.
-			out, err := TestHelper.LinkerdRun("viz", "stat", "-n", ns, "deploy/slow-cooker", "--to", "deploy/nginx", "-t", "30s", "-o", "json")
-			if err != nil {
-				testutil.AnnotatedFatalf(t, "unexpected stat error",
-					"unexpected stat error: %s\n%s", err, out)
-			}
-
-			var stats []jsonStats
-			err = json.Unmarshal([]byte(out), &stats)
+			metrics, err := TestHelper.LinkerdRun("diagnostics", "proxy-metrics", "-n", ns, "deploy/slow-cooker")
 			if err != nil {
-				return fmt.Errorf("failed to parse json stat output: %s\n%s", err, out)
-			}
-
-			if len(stats) != 1 {
-				return fmt.Errorf("expected 1 row of stat output, got: %s", out)
+				testutil.AnnotatedFatalf(t, "unexpected diagnostics error",
+					"unexpected diagnostics error: %s\n%s", err, out)
 			}
 
-			if stats[0].Rps == nil || *stats[0].Rps == 0.0 {
-				return fmt.Errorf("expected non-zero RPS from slowcooker to nginx: %s", out)
+			rpsRE := regexp.MustCompile(
+				`request_total\{direction="outbound",authority="nginx\.linkerd-localhost-test\.svc\.cluster\.local:8080",.*,dst_deployment="nginx",dst_namespace="linkerd-localhost-test",dst_pod="nginx.*",dst_pod_template_hash=".*",dst_service="nginx",dst_serviceaccount="default"\} [1-9]\d*`,
+			)
+			if !rpsRE.MatchString(metrics) {
+				return fmt.Errorf("expected non-zero RPS from slowcooker to nginx\nexpected: %s, got: %s", rpsRE, metrics)
 			}
 
 			// Requests sent to a port which is only bound to localhost should
 			// fail.
-			if *stats[0].Success >= 1.0 {
-				return fmt.Errorf("expected zero success-rate from slowcooker to nginx: %s", out)
+			successRE := regexp.MustCompile(
+				`response_total\{direction="outbound",authority="nginx\.linkerd-localhost-test\.svc\.cluster\.local:8080",.*,classification="success"\} [1-9]\d*`,
+			)
+			if successRE.MatchString(metrics) {
+				return fmt.Errorf("expected zero success-rate from slowcooker to nginx\nexpected: %s, got: %s", successRE, metrics)
 			}
-			if *stats[0].TCPOpenConnections > 0 {
-				return fmt.Errorf("expected no tcp connection from slowcooker to nginx: %s", out)
+
+			tcpConnRE := regexp.MustCompile(
+				`tcp_open_connections\{direction="outbound",peer="dst",authority="nginx\.linkerd-localhost-test\.svc\.cluster\.local:8080",.*\} 0`,
+			)
+			if !tcpConnRE.MatchString(metrics) {
+				return fmt.Errorf("expected no tcp connections from slowcooker to nginx\nexpected: %s, got: %s", tcpConnRE, metrics)
 			}
 
 			return nil