1 package trafficsplit
2
3 import (
4 "context"
5 "fmt"
6 "os"
7 "strings"
8 "testing"
9 "time"
10
11 "github.com/linkerd/linkerd2/testutil"
12 )
13
14 var TestHelper *testutil.TestHelper
15
16 func TestMain(m *testing.M) {
17 TestHelper = testutil.NewTestHelper()
18
19 TestHelper.WaitUntilDeployReady(testutil.LinkerdVizDeployReplicas)
20 os.Exit(m.Run())
21 }
22
23 func parseStatRows(out string, expectedRowCount, expectedColumnCount int) ([]*testutil.RowStat, error) {
24 rows, err := testutil.CheckRowCount(out, expectedRowCount)
25 if err != nil {
26 return nil, err
27 }
28
29 var statRows []*testutil.RowStat
30
31 for _, row := range rows {
32 fields := strings.Fields(row)
33
34 if len(fields) != expectedColumnCount {
35 return nil, fmt.Errorf(
36 "Expected [%d] columns in stat output, got [%d]; full output:\n%s",
37 expectedColumnCount, len(fields), row)
38 }
39
40 row := &testutil.RowStat{
41 Name: fields[0],
42 Meshed: fields[1],
43 Success: fields[2],
44 Rps: fields[3],
45 P50Latency: fields[4],
46 P95Latency: fields[5],
47 P99Latency: fields[6],
48 TCPOpenConnections: fields[7],
49 }
50
51 statRows = append(statRows, row)
52
53 }
54 return statRows, nil
55 }
56
57 func TestServiceProfileDstOverrides(t *testing.T) {
58 ctx := context.Background()
59 TestHelper.WithDataPlaneNamespace(ctx, "trafficsplit-test-sp", map[string]string{}, t, func(t *testing.T, prefixedNs string) {
60
61 initialSP := `---
62 apiVersion: linkerd.io/v1alpha2
63 kind: ServiceProfile
64 metadata:
65 name: backend-svc.linkerd-trafficsplit-test-sp.svc.cluster.local
66 spec:
67 dstOverrides:
68 - authority: backend-svc.linkerd-trafficsplit-test-sp.svc.cluster.local
69 weight: 1
70 - authority: failing-svc.linkerd-trafficsplit-test-sp.svc.cluster.local:8081
71 weight: 0
72 ...`
73 out, err := TestHelper.KubectlApply(initialSP, prefixedNs)
74 if err != nil {
75 testutil.AnnotatedFatalf(t, "Failed to apply ServiceProfile",
76 "Failed to apply ServiceProfile\n%s", out)
77 }
78
79
80 out, err = TestHelper.LinkerdRun("inject", "--manual",
81 "--proxy-log-level=linkerd=debug,linkerd_service_profiles::client=trace,info",
82 "testdata/applications-at-diff-ports.yaml")
83 if err != nil {
84 testutil.AnnotatedFatal(t, "'linkerd inject' command failed", err)
85 }
86 out, err = TestHelper.KubectlApply(out, prefixedNs)
87 if err != nil {
88 testutil.AnnotatedFatalf(t, "'kubectl apply' command failed",
89 "'kubectl apply' command failed\n%s", out)
90 }
91
92 for _, deploy := range []string{"backend", "failing", "slow-cooker"} {
93 if err := TestHelper.CheckPods(ctx, prefixedNs, deploy, 1); err != nil {
94
95 if rce, ok := err.(*testutil.RestartCountError); ok {
96 testutil.AnnotatedWarn(t, "CheckPods timed-out", rce)
97 } else {
98 testutil.AnnotatedError(t, "CheckPods timed-out", err)
99 }
100 }
101 }
102
103 t.Run("ensure traffic is sent to one backend only", func(t *testing.T) {
104 timeout := 40 * time.Second
105 expectedRows := []*testutil.RowStat{
106 {
107 Name: "backend",
108 Meshed: "1/1",
109 Success: "100.00%",
110 TCPOpenConnections: "1",
111 },
112 }
113 err := testutil.RetryFor(timeout, func() error {
114 out, err := TestHelper.LinkerdRun("viz", "stat", "deploy", "--namespace", prefixedNs,
115 "--from", "deploy/slow-cooker", "-t", "30s")
116 if err != nil {
117 return err
118 }
119 rows, err := parseStatRows(out, 1, 8)
120 if err != nil {
121 return err
122 }
123 if err := validateRowStats(expectedRows, rows); err != nil {
124 return err
125 }
126 return nil
127 })
128 if err != nil {
129 testutil.AnnotatedFatal(t, fmt.Sprintf("timed-out ensuring traffic is sent to one backend only (%s)", timeout), err)
130 }
131 })
132
133 t.Run("update traffic split resource with equal weights", func(t *testing.T) {
134 updatedSP := `---
135 apiVersion: linkerd.io/v1alpha2
136 kind: ServiceProfile
137 metadata:
138 name: backend-svc.linkerd-trafficsplit-test-sp.svc.cluster.local
139 spec:
140 dstOverrides:
141 - authority: backend-svc.linkerd-trafficsplit-test-sp.svc.cluster.local
142 weight: 1
143 - authority: failing-svc.linkerd-trafficsplit-test-sp.svc.cluster.local:8081
144 weight: 1
145 ...`
146 out, err := TestHelper.KubectlApply(updatedSP, prefixedNs)
147 if err != nil {
148 testutil.AnnotatedFatalf(t, "failed to update ServiceProfile",
149 "failed to update ServiceProfile: %s\n %s", err, out)
150 }
151 })
152
153 t.Run("ensure traffic is sent to both backends", func(t *testing.T) {
154 timeout := 40 * time.Second
155 expectedRows := []*testutil.RowStat{
156 {
157 Name: "backend",
158 Meshed: "1/1",
159 Success: "100.00%",
160 TCPOpenConnections: "1",
161 },
162 {
163 Name: "failing",
164 Meshed: "1/1",
165 Success: "0.00%",
166 TCPOpenConnections: "1",
167 },
168 }
169 err := testutil.RetryFor(timeout, func() error {
170 out, err := TestHelper.LinkerdRun("viz", "stat", "deploy", "-n", prefixedNs,
171 "--from", "deploy/slow-cooker", "-t", "30s")
172 if err != nil {
173 return err
174 }
175 rows, err := parseStatRows(out, 2, 8)
176 if err != nil {
177 return err
178 }
179 if err := validateRowStats(expectedRows, rows); err != nil {
180 return err
181 }
182 return nil
183 })
184 if err != nil {
185 out, lerr := TestHelper.LinkerdRun("viz", "stat", "deploy", "-n", prefixedNs,
186 "--from", "deploy/slow-cooker", "-t", "30s")
187 if lerr == nil {
188 fmt.Fprintf(os.Stderr, "----------- linkerd viz stat\n")
189 fmt.Fprintf(os.Stderr, "%s", out)
190 }
191 out, lerr = TestHelper.Kubectl("", "logs", "--tail=1000", "-n", prefixedNs,
192 "-l", "app=slow-cooker", "-c", "linkerd-proxy")
193 if lerr != nil {
194 fmt.Fprintf(os.Stderr, "Failed to run kubectl logs: %s", lerr)
195 } else {
196 fmt.Fprintf(os.Stderr, "----------- kubectl logs\n")
197 fmt.Fprintf(os.Stderr, "%s", out)
198 }
199 testutil.AnnotatedFatal(t, fmt.Sprintf("timed-out ensuring traffic is sent to both backends (%s)", timeout), err)
200 }
201 })
202 })
203 }
204
205 func validateRowStats(expectedRowStats, actualRowStats []*testutil.RowStat) error {
206 if len(expectedRowStats) != len(actualRowStats) {
207 return fmt.Errorf("Expected number of rows to be %d, but found %d", len(expectedRowStats), len(actualRowStats))
208 }
209 for i := 0; i < len(expectedRowStats); i++ {
210 err := compareRowStat(expectedRowStats[i], actualRowStats[i])
211 if err != nil {
212 return err
213 }
214 }
215 return nil
216 }
217
218 func compareRowStat(expectedRow, actualRow *testutil.RowStat) error {
219 if actualRow.Name != expectedRow.Name {
220 return fmt.Errorf("Expected name to be '%s', got '%s'",
221 expectedRow.Name, actualRow.Name)
222 }
223 if actualRow.Meshed != expectedRow.Meshed {
224 return fmt.Errorf("Expected meshed to be '%s', got '%s'",
225 expectedRow.Meshed, actualRow.Meshed)
226 }
227 if !strings.HasSuffix(actualRow.Rps, "rps") {
228 return fmt.Errorf("Unexpected rps for [%s], got [%s]",
229 actualRow.Name, actualRow.Rps)
230 }
231 if !strings.HasSuffix(actualRow.P50Latency, "ms") {
232 return fmt.Errorf("Unexpected p50 latency for [%s], got [%s]",
233 actualRow.Name, actualRow.P50Latency)
234 }
235 if !strings.HasSuffix(actualRow.P95Latency, "ms") {
236 return fmt.Errorf("Unexpected p95 latency for [%s], got [%s]",
237 actualRow.Name, actualRow.P95Latency)
238 }
239 if !strings.HasSuffix(actualRow.P99Latency, "ms") {
240 return fmt.Errorf("Unexpected p99 latency for [%s], got [%s]",
241 actualRow.Name, actualRow.P99Latency)
242 }
243 if actualRow.Success != expectedRow.Success {
244 return fmt.Errorf("Expected success to be '%s', got '%s'",
245 expectedRow.Success, actualRow.Success)
246 }
247 if actualRow.TCPOpenConnections != expectedRow.TCPOpenConnections {
248 return fmt.Errorf("Expected tcp to be '%s', got '%s'",
249 expectedRow.TCPOpenConnections, actualRow.TCPOpenConnections)
250 }
251 return nil
252 }
253
View as plain text