mirror of https://github.com/linkerd/linkerd2.git
integrationtests: check for stat output of policy resources (#6890)
This PR adds a new `policy` integration tests suite to check the output of for the `srv` and `saz` commands and make sure they have the expected format. This is majorly needed to make sure things work as expected with stat cmd output without having to do manual testing. Signed-off-by: Tarun Pothulapati <tarunpothulapati@outlook.com>
This commit is contained in:
parent
d9470b3b35
commit
174c50a235
|
|
@ -0,0 +1,244 @@
|
|||
package policy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/linkerd/linkerd2/testutil"
|
||||
)
|
||||
|
||||
var TestHelper *testutil.TestHelper
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
TestHelper = testutil.NewTestHelper()
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
//////////////////////
|
||||
/// TEST EXECUTION ///
|
||||
//////////////////////
|
||||
|
||||
func TestPolicy(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
// Test authorization stats
|
||||
TestHelper.WithDataPlaneNamespace(ctx, "stat-authz-test", map[string]string{}, t, func(t *testing.T, prefixedNs string) {
|
||||
emojivotoYaml, err := testutil.ReadFile("testdata/emojivoto.yaml")
|
||||
if err != nil {
|
||||
testutil.AnnotatedFatalf(t, "failed to read emojivoto yaml",
|
||||
"failed to read emojivoto yaml\n%s\n", err)
|
||||
}
|
||||
emojivotoYaml = strings.ReplaceAll(emojivotoYaml, "___NS___", prefixedNs)
|
||||
out, stderr, err := TestHelper.PipeToLinkerdRun(emojivotoYaml, "inject", "-")
|
||||
if err != nil {
|
||||
testutil.AnnotatedFatalf(t, "'linkerd inject' command failed",
|
||||
"'linkerd inject' command failed\n%s\n%s", out, stderr)
|
||||
}
|
||||
|
||||
out, err = TestHelper.KubectlApply(out, prefixedNs)
|
||||
if err != nil {
|
||||
testutil.AnnotatedFatalf(t, "failed to apply emojivoto resources",
|
||||
"failed to apply emojivoto resources: %s\n %s", err, out)
|
||||
}
|
||||
|
||||
emojivotoPolicy, err := testutil.ReadFile("testdata/emoji-policy.yaml")
|
||||
if err != nil {
|
||||
testutil.AnnotatedFatalf(t, "failed to read emoji-policy yaml",
|
||||
"failed to read emoji-policy yaml\n%s\n", err)
|
||||
}
|
||||
|
||||
out, err = TestHelper.KubectlApply(emojivotoPolicy, prefixedNs)
|
||||
if err != nil {
|
||||
testutil.AnnotatedFatalf(t, "failed to apply emojivoto policy resources",
|
||||
"failed to apply emojivoto policy resources: %s\n %s", err, out)
|
||||
}
|
||||
|
||||
// wait for deployments to start
|
||||
for _, deploy := range []string{"web", "emoji", "vote-bot", "voting"} {
|
||||
if err := TestHelper.CheckPods(ctx, prefixedNs, deploy, 1); err != nil {
|
||||
if rce, ok := err.(*testutil.RestartCountError); ok {
|
||||
testutil.AnnotatedWarn(t, "CheckPods timed-out", rce)
|
||||
} else {
|
||||
testutil.AnnotatedError(t, "CheckPods timed-out", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
args []string
|
||||
expectedRows []string
|
||||
isServer bool
|
||||
}{
|
||||
{
|
||||
args: []string{"viz", "stat", "srv", "-n", prefixedNs},
|
||||
expectedRows: []string{
|
||||
"emoji-grpc",
|
||||
"voting-grpc",
|
||||
"web-http",
|
||||
},
|
||||
isServer: true,
|
||||
},
|
||||
{
|
||||
args: []string{"viz", "stat", "srv/emoji-grpc", "-n", prefixedNs},
|
||||
expectedRows: []string{
|
||||
"emoji-grpc",
|
||||
},
|
||||
isServer: true,
|
||||
},
|
||||
{
|
||||
args: []string{"viz", "stat", "saz", "-n", prefixedNs},
|
||||
expectedRows: []string{
|
||||
"emoji-grpc",
|
||||
"voting-grpc",
|
||||
"web-public",
|
||||
},
|
||||
isServer: false,
|
||||
},
|
||||
{
|
||||
args: []string{"viz", "stat", "saz/emoji-grpc", "-n", prefixedNs},
|
||||
expectedRows: []string{
|
||||
"emoji-grpc",
|
||||
},
|
||||
isServer: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range testCases {
|
||||
tt := tt // pin
|
||||
timeout := 3 * time.Minute
|
||||
t.Run("linkerd "+strings.Join(tt.args, " "), func(t *testing.T) {
|
||||
err := TestHelper.RetryFor(timeout, func() error {
|
||||
// Use a short time window so that transient errors at startup
|
||||
// fall out of the window.
|
||||
tt.args = append(tt.args, "-t", "30s")
|
||||
out, err := TestHelper.LinkerdRun(tt.args...)
|
||||
if err != nil {
|
||||
testutil.AnnotatedFatalf(t, "unexpected stat error",
|
||||
"unexpected stat error: %s\n%s", err, out)
|
||||
}
|
||||
|
||||
var expectedColumnCount int
|
||||
if tt.isServer {
|
||||
expectedColumnCount = 8
|
||||
} else {
|
||||
expectedColumnCount = 6
|
||||
}
|
||||
|
||||
rowStats, err := ParseAuthzRows(out, len(tt.expectedRows), expectedColumnCount, tt.isServer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, name := range tt.expectedRows {
|
||||
if err := validateAuthzRows(name, rowStats, tt.isServer); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
testutil.AnnotatedFatal(t, fmt.Sprintf("timed-out checking stats (%s)", timeout), err)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func validateAuthzRows(name string, rowStats map[string]*testutil.RowStat, isServer bool) error {
|
||||
stat, ok := rowStats[name]
|
||||
if !ok {
|
||||
return fmt.Errorf("No stats found for [%s]", name)
|
||||
}
|
||||
|
||||
// Check for suffix only, as the value will not be 100% always with
|
||||
// the normal emojivoto sample
|
||||
if !strings.HasSuffix(stat.Success, "%") {
|
||||
return fmt.Errorf("Unexpected success rate for [%s], got [%s]",
|
||||
name, stat.Success)
|
||||
}
|
||||
|
||||
if isServer {
|
||||
if !strings.HasSuffix(stat.UnauthorizedRPS, "rps") {
|
||||
return fmt.Errorf("Unexpected Unauthorized RPS for [%s], got [%s]",
|
||||
name, stat.UnauthorizedRPS)
|
||||
}
|
||||
}
|
||||
|
||||
if !strings.HasSuffix(stat.Rps, "rps") {
|
||||
return fmt.Errorf("Unexpected rps for [%s], got [%s]",
|
||||
name, stat.Rps)
|
||||
}
|
||||
|
||||
if !strings.HasSuffix(stat.P50Latency, "ms") {
|
||||
return fmt.Errorf("Unexpected p50 latency for [%s], got [%s]",
|
||||
name, stat.P50Latency)
|
||||
}
|
||||
|
||||
if !strings.HasSuffix(stat.P95Latency, "ms") {
|
||||
return fmt.Errorf("Unexpected p95 latency for [%s], got [%s]",
|
||||
name, stat.P95Latency)
|
||||
}
|
||||
|
||||
if !strings.HasSuffix(stat.P99Latency, "ms") {
|
||||
return fmt.Errorf("Unexpected p99 latency for [%s], got [%s]",
|
||||
name, stat.P99Latency)
|
||||
}
|
||||
|
||||
if isServer {
|
||||
_, err := strconv.Atoi(stat.TCPOpenConnections)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error parsing number of TCP connections [%s]: %s", stat.TCPOpenConnections, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ParseRows parses the output of linkerd stat on a policy resource
|
||||
func ParseAuthzRows(out string, expectedRowCount, expectedColumnCount int, isServer bool) (map[string]*testutil.RowStat, error) {
|
||||
rows, err := testutil.CheckRowCount(out, expectedRowCount)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rowStats := make(map[string]*testutil.RowStat)
|
||||
for _, row := range rows {
|
||||
fields := strings.Fields(row)
|
||||
|
||||
if len(fields) != expectedColumnCount {
|
||||
return nil, fmt.Errorf(
|
||||
"Expected [%d] columns in stat output, got [%d]; full output:\n%s",
|
||||
expectedColumnCount, len(fields), row)
|
||||
}
|
||||
|
||||
i := 0
|
||||
rowStats[fields[0]] = &testutil.RowStat{
|
||||
Name: fields[0],
|
||||
}
|
||||
|
||||
if isServer {
|
||||
rowStats[fields[0]].UnauthorizedRPS = fields[1+i]
|
||||
rowStats[fields[0]].Success = fields[2+i]
|
||||
rowStats[fields[0]].Rps = fields[3+i]
|
||||
rowStats[fields[0]].P50Latency = fields[4+i]
|
||||
rowStats[fields[0]].P95Latency = fields[5+i]
|
||||
rowStats[fields[0]].P99Latency = fields[6+i]
|
||||
rowStats[fields[0]].TCPOpenConnections = fields[7+i]
|
||||
} else {
|
||||
rowStats[fields[0]].Success = fields[1+i]
|
||||
rowStats[fields[0]].Rps = fields[2+i]
|
||||
rowStats[fields[0]].P50Latency = fields[3+i]
|
||||
rowStats[fields[0]].P95Latency = fields[4+i]
|
||||
rowStats[fields[0]].P99Latency = fields[5+i]
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return rowStats, nil
|
||||
}
|
||||
|
|
@ -0,0 +1,97 @@
|
|||
---
|
||||
apiVersion: policy.linkerd.io/v1alpha1
|
||||
kind: Server
|
||||
metadata:
|
||||
name: emoji-grpc
|
||||
labels:
|
||||
app.kubernetes.io/part-of: emojivoto
|
||||
app.kubernetes.io/name: emoji
|
||||
app.kubernetes.io/version: v11
|
||||
spec:
|
||||
podSelector:
|
||||
matchLabels:
|
||||
app: emoji-svc
|
||||
port: grpc
|
||||
proxyProtocol: gRPC
|
||||
---
|
||||
apiVersion: policy.linkerd.io/v1alpha1
|
||||
kind: ServerAuthorization
|
||||
metadata:
|
||||
name: emoji-grpc
|
||||
labels:
|
||||
app.kubernetes.io/part-of: emojivoto
|
||||
app.kubernetes.io/name: emoji
|
||||
app.kubernetes.io/version: v11
|
||||
spec:
|
||||
# Allow all authenticated clients to access the (read-only) emoji service.
|
||||
server:
|
||||
name: emoji-grpc
|
||||
client:
|
||||
meshTLS:
|
||||
identities:
|
||||
- "*.linkerd-stat-authz-test.serviceaccount.identity.linkerd.cluster.local"
|
||||
---
|
||||
apiVersion: policy.linkerd.io/v1alpha1
|
||||
kind: Server
|
||||
metadata:
|
||||
name: voting-grpc
|
||||
labels:
|
||||
app: voting-svc
|
||||
spec:
|
||||
podSelector:
|
||||
matchLabels:
|
||||
app: voting-svc
|
||||
port: grpc
|
||||
proxyProtocol: gRPC
|
||||
---
|
||||
apiVersion: policy.linkerd.io/v1alpha1
|
||||
kind: ServerAuthorization
|
||||
metadata:
|
||||
name: voting-grpc
|
||||
labels:
|
||||
app.kubernetes.io/part-of: emojivoto
|
||||
app.kubernetes.io/name: voting
|
||||
app.kubernetes.io/version: v11
|
||||
spec:
|
||||
server:
|
||||
name: voting-grpc
|
||||
# The voting service only allows requests from the web service.
|
||||
client:
|
||||
meshTLS:
|
||||
serviceAccounts:
|
||||
- name: web
|
||||
---
|
||||
apiVersion: policy.linkerd.io/v1alpha1
|
||||
kind: Server
|
||||
metadata:
|
||||
name: web-http
|
||||
labels:
|
||||
app.kubernetes.io/part-of: emojivoto
|
||||
app.kubernetes.io/name: web
|
||||
app.kubernetes.io/version: v11
|
||||
spec:
|
||||
podSelector:
|
||||
matchLabels:
|
||||
app: web-svc
|
||||
port: http
|
||||
proxyProtocol: HTTP/1
|
||||
---
|
||||
apiVersion: policy.linkerd.io/v1alpha1
|
||||
kind: ServerAuthorization
|
||||
metadata:
|
||||
name: web-public
|
||||
labels:
|
||||
app.kubernetes.io/part-of: emojivoto
|
||||
app.kubernetes.io/name: web
|
||||
app.kubernetes.io/version: v11
|
||||
spec:
|
||||
server:
|
||||
name: web-http
|
||||
# Allow all clients to access the web HTTP port without regard for
|
||||
# authentication. If unauthenticated connections are permitted, there is no
|
||||
# need to describe authenticated clients.
|
||||
client:
|
||||
unauthenticated: true
|
||||
networks:
|
||||
- cidr: 0.0.0.0/0
|
||||
- cidr: ::/0
|
||||
|
|
@ -0,0 +1,205 @@
|
|||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: emoji
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: voting
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: web
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: emoji-svc
|
||||
spec:
|
||||
ports:
|
||||
- name: grpc
|
||||
port: 8080
|
||||
targetPort: 8080
|
||||
- name: prom
|
||||
port: 8801
|
||||
targetPort: 8801
|
||||
selector:
|
||||
app: emoji-svc
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: voting-svc
|
||||
spec:
|
||||
ports:
|
||||
- name: grpc
|
||||
port: 8080
|
||||
targetPort: 8080
|
||||
- name: prom
|
||||
port: 8801
|
||||
targetPort: 8801
|
||||
selector:
|
||||
app: voting-svc
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: web-svc
|
||||
spec:
|
||||
ports:
|
||||
- name: http
|
||||
port: 80
|
||||
targetPort: 8080
|
||||
selector:
|
||||
app: web-svc
|
||||
type: ClusterIP
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: emoji
|
||||
app.kubernetes.io/part-of: emojivoto
|
||||
app.kubernetes.io/version: v11
|
||||
name: emoji
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: emoji-svc
|
||||
version: v11
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: emoji-svc
|
||||
version: v11
|
||||
spec:
|
||||
containers:
|
||||
- env:
|
||||
- name: GRPC_PORT
|
||||
value: "8080"
|
||||
- name: PROM_PORT
|
||||
value: "8801"
|
||||
image: docker.l5d.io/buoyantio/emojivoto-emoji-svc:v11
|
||||
name: emoji-svc
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
name: grpc
|
||||
- containerPort: 8801
|
||||
name: prom
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
serviceAccountName: emoji
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: vote-bot
|
||||
app.kubernetes.io/part-of: emojivoto
|
||||
app.kubernetes.io/version: v11
|
||||
name: vote-bot
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: vote-bot
|
||||
version: v11
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: vote-bot
|
||||
version: v11
|
||||
spec:
|
||||
containers:
|
||||
- command:
|
||||
- emojivoto-vote-bot
|
||||
env:
|
||||
- name: WEB_HOST
|
||||
value: web-svc.___NS___:80
|
||||
image: docker.l5d.io/buoyantio/emojivoto-web:v11
|
||||
name: vote-bot
|
||||
resources:
|
||||
requests:
|
||||
cpu: 10m
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: voting
|
||||
app.kubernetes.io/part-of: emojivoto
|
||||
app.kubernetes.io/version: v11
|
||||
name: voting
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: voting-svc
|
||||
version: v11
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: voting-svc
|
||||
version: v11
|
||||
spec:
|
||||
containers:
|
||||
- env:
|
||||
- name: GRPC_PORT
|
||||
value: "8080"
|
||||
- name: PROM_PORT
|
||||
value: "8801"
|
||||
image: docker.l5d.io/buoyantio/emojivoto-voting-svc:v11
|
||||
name: voting-svc
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
name: grpc
|
||||
- containerPort: 8801
|
||||
name: prom
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
serviceAccountName: voting
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: web
|
||||
app.kubernetes.io/part-of: emojivoto
|
||||
app.kubernetes.io/version: v11
|
||||
name: web
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: web-svc
|
||||
version: v11
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: web-svc
|
||||
version: v11
|
||||
spec:
|
||||
containers:
|
||||
- env:
|
||||
- name: WEB_PORT
|
||||
value: "8080"
|
||||
- name: EMOJISVC_HOST
|
||||
value: emoji-svc.___NS___:8080
|
||||
- name: VOTINGSVC_HOST
|
||||
value: voting-svc.___NS___:8080
|
||||
- name: INDEX_BUNDLE
|
||||
value: dist/index_bundle.js
|
||||
image: docker.l5d.io/buoyantio/emojivoto-web:v11
|
||||
name: web-svc
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
name: http
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
serviceAccountName: web
|
||||
|
|
@ -672,6 +672,7 @@ type RowStat struct {
|
|||
P95Latency string
|
||||
P99Latency string
|
||||
TCPOpenConnections string
|
||||
UnauthorizedRPS string
|
||||
}
|
||||
|
||||
// CheckRowCount checks that expectedRowCount rows have been returned
|
||||
|
|
|
|||
Loading…
Reference in New Issue