1 package policy
2
3 import (
4 "context"
5 "fmt"
6 "os"
7 "strconv"
8 "strings"
9 "testing"
10 "time"
11
12 "github.com/linkerd/linkerd2/testutil"
13 )
14
15 var TestHelper *testutil.TestHelper
16
17 func TestMain(m *testing.M) {
18 TestHelper = testutil.NewTestHelper()
19
20 TestHelper.WaitUntilDeployReady(testutil.LinkerdVizDeployReplicas)
21 os.Exit(m.Run())
22 }
23
24
25
26
27
28 func TestPolicy(t *testing.T) {
29 ctx := context.Background()
30
31
32 TestHelper.WithDataPlaneNamespace(ctx, "stat-authz-test", map[string]string{}, t, func(t *testing.T, prefixedNs string) {
33 emojivotoYaml, err := testutil.ReadFile("testdata/emojivoto.yaml")
34 if err != nil {
35 testutil.AnnotatedFatalf(t, "failed to read emojivoto yaml",
36 "failed to read emojivoto yaml\n%s\n", err)
37 }
38 emojivotoYaml = strings.ReplaceAll(emojivotoYaml, "___NS___", prefixedNs)
39 out, stderr, err := TestHelper.PipeToLinkerdRun(emojivotoYaml, "inject", "-")
40 if err != nil {
41 testutil.AnnotatedFatalf(t, "'linkerd inject' command failed",
42 "'linkerd inject' command failed\n%s\n%s", out, stderr)
43 }
44
45 out, err = TestHelper.KubectlApply(out, prefixedNs)
46 if err != nil {
47 testutil.AnnotatedFatalf(t, "failed to apply emojivoto resources",
48 "failed to apply emojivoto resources: %s\n %s", err, out)
49 }
50
51 emojivotoPolicy, err := testutil.ReadFile("testdata/emoji-policy.yaml")
52 if err != nil {
53 testutil.AnnotatedFatalf(t, "failed to read emoji-policy yaml",
54 "failed to read emoji-policy yaml\n%s\n", err)
55 }
56
57 out, err = TestHelper.KubectlApply(emojivotoPolicy, prefixedNs)
58 if err != nil {
59 testutil.AnnotatedFatalf(t, "failed to apply emojivoto policy resources",
60 "failed to apply emojivoto policy resources: %s\n %s", err, out)
61 }
62
63
64 for _, deploy := range []string{"web", "emoji", "vote-bot", "voting"} {
65 if err := TestHelper.CheckPods(ctx, prefixedNs, deploy, 1); err != nil {
66
67 if rce, ok := err.(*testutil.RestartCountError); ok {
68 testutil.AnnotatedWarn(t, "CheckPods timed-out", rce)
69 } else {
70 testutil.AnnotatedError(t, "CheckPods timed-out", err)
71 }
72 }
73 }
74
75 testCases := []struct {
76 args []string
77 expectedRows []string
78 isServer bool
79 }{
80 {
81 args: []string{"viz", "stat", "srv", "-n", prefixedNs},
82 expectedRows: []string{
83 "emoji-grpc",
84 "voting-grpc",
85 "web-http",
86 },
87 isServer: true,
88 },
89 {
90 args: []string{"viz", "stat", "srv/emoji-grpc", "-n", prefixedNs},
91 expectedRows: []string{
92 "emoji-grpc",
93 },
94 isServer: true,
95 },
96 {
97 args: []string{"viz", "stat", "saz", "-n", prefixedNs},
98 expectedRows: []string{
99 "emoji-grpc",
100 "voting-grpc",
101 "web-public",
102 },
103 isServer: false,
104 },
105 {
106 args: []string{"viz", "stat", "saz/emoji-grpc", "-n", prefixedNs},
107 expectedRows: []string{
108 "emoji-grpc",
109 },
110 isServer: false,
111 },
112 }
113
114 for _, tt := range testCases {
115 tt := tt
116 timeout := 3 * time.Minute
117 t.Run("linkerd "+strings.Join(tt.args, " "), func(t *testing.T) {
118 err := testutil.RetryFor(timeout, func() error {
119
120
121 tt.args = append(tt.args, "-t", "30s")
122 out, err := TestHelper.LinkerdRun(tt.args...)
123 if err != nil {
124 testutil.AnnotatedFatalf(t, "unexpected stat error",
125 "unexpected stat error: %s\n%s", err, out)
126 }
127
128 var expectedColumnCount int
129 if tt.isServer {
130 expectedColumnCount = 8
131 } else {
132 expectedColumnCount = 6
133 }
134
135 rowStats, err := ParseAuthzRows(out, len(tt.expectedRows), expectedColumnCount, tt.isServer)
136 if err != nil {
137 return err
138 }
139
140 for _, name := range tt.expectedRows {
141 if err := validateAuthzRows(name, rowStats, tt.isServer); err != nil {
142 return err
143 }
144 }
145
146 return nil
147 })
148 if err != nil {
149 testutil.AnnotatedFatal(t, fmt.Sprintf("timed-out checking policy stats (%s)", timeout), err)
150 }
151 })
152 }
153 })
154 }
155
156 type noSuccess struct{ name string }
157
158 func (e noSuccess) Error() string {
159 return fmt.Sprintf("no success rate reported for %s", e.name)
160 }
161
162 func validateAuthzRows(name string, rowStats map[string]*testutil.RowStat, isServer bool) error {
163 stat, ok := rowStats[name]
164 if !ok {
165 return fmt.Errorf("No stats found for [%s]", name)
166 }
167
168
169
170 if stat.Success == "-" {
171 return noSuccess{name}
172 }
173 if !strings.HasSuffix(stat.Success, "%") {
174 return fmt.Errorf("Unexpected success rate for [%s], got [%s]",
175 name, stat.Success)
176 }
177
178 if isServer {
179 if !strings.HasSuffix(stat.UnauthorizedRPS, "rps") {
180 return fmt.Errorf("Unexpected Unauthorized RPS for [%s], got [%s]",
181 name, stat.UnauthorizedRPS)
182 }
183 }
184
185 if !strings.HasSuffix(stat.Rps, "rps") {
186 return fmt.Errorf("Unexpected rps for [%s], got [%s]",
187 name, stat.Rps)
188 }
189
190 if !strings.HasSuffix(stat.P50Latency, "ms") {
191 return fmt.Errorf("Unexpected p50 latency for [%s], got [%s]",
192 name, stat.P50Latency)
193 }
194
195 if !strings.HasSuffix(stat.P95Latency, "ms") {
196 return fmt.Errorf("Unexpected p95 latency for [%s], got [%s]",
197 name, stat.P95Latency)
198 }
199
200 if !strings.HasSuffix(stat.P99Latency, "ms") {
201 return fmt.Errorf("Unexpected p99 latency for [%s], got [%s]",
202 name, stat.P99Latency)
203 }
204
205 if isServer {
206 _, err := strconv.Atoi(stat.TCPOpenConnections)
207 if err != nil {
208 return fmt.Errorf("Error parsing number of TCP connections [%s]: %w", stat.TCPOpenConnections, err)
209 }
210 }
211
212 return nil
213 }
214
215
216 func ParseAuthzRows(out string, expectedRowCount, expectedColumnCount int, isServer bool) (map[string]*testutil.RowStat, error) {
217 rows, err := testutil.CheckRowCount(out, expectedRowCount)
218 if err != nil {
219 return nil, err
220 }
221
222 rowStats := make(map[string]*testutil.RowStat)
223 for _, row := range rows {
224 fields := strings.Fields(row)
225
226 if len(fields) != expectedColumnCount {
227 return nil, fmt.Errorf(
228 "Expected [%d] columns in stat output, got [%d]; full output:\n%s",
229 expectedColumnCount, len(fields), row)
230 }
231
232 i := 0
233 rowStats[fields[0]] = &testutil.RowStat{
234 Name: fields[0],
235 }
236
237 if isServer {
238 rowStats[fields[0]].UnauthorizedRPS = fields[1+i]
239 rowStats[fields[0]].Success = fields[2+i]
240 rowStats[fields[0]].Rps = fields[3+i]
241 rowStats[fields[0]].P50Latency = fields[4+i]
242 rowStats[fields[0]].P95Latency = fields[5+i]
243 rowStats[fields[0]].P99Latency = fields[6+i]
244 rowStats[fields[0]].TCPOpenConnections = fields[7+i]
245 } else {
246 rowStats[fields[0]].Success = fields[1+i]
247 rowStats[fields[0]].Rps = fields[2+i]
248 rowStats[fields[0]].P50Latency = fields[3+i]
249 rowStats[fields[0]].P95Latency = fields[4+i]
250 rowStats[fields[0]].P99Latency = fields[5+i]
251 }
252
253 }
254
255 return rowStats, nil
256 }
257
View as plain text