1 package tracing
2
3 import (
4 "bytes"
5 "context"
6 "encoding/json"
7 "errors"
8 "fmt"
9 "html/template"
10 "os"
11 "testing"
12 "time"
13
14 "github.com/linkerd/linkerd2/jaeger/pkg/labels"
15 "github.com/linkerd/linkerd2/pkg/healthcheck"
16 "github.com/linkerd/linkerd2/pkg/version"
17 "github.com/linkerd/linkerd2/testutil"
18 )
19
20 type (
21 traces struct {
22 Data []trace `json:"data"`
23 }
24
25 trace struct {
26 Processes map[string]process `json:"processes"`
27 }
28
29 process struct {
30 ServiceName string `json:"serviceName"`
31 }
32 )
33
34
35
36
37
38 var TestHelper *testutil.TestHelper
39
40 func TestMain(m *testing.M) {
41 TestHelper = testutil.NewTestHelper()
42
43 TestHelper.WaitUntilDeployReady(testutil.LinkerdVizDeployReplicas)
44 os.Exit(m.Run())
45 }
46
47
48
49
50
51 func TestTracing(t *testing.T) {
52 ctx := context.Background()
53
54
55 tracingNs := "linkerd-jaeger"
56 out, err := TestHelper.LinkerdRun("jaeger", "install")
57 if err != nil {
58 testutil.AnnotatedFatal(t, "'linkerd jaeger install' command failed", err)
59 }
60
61 out, err = TestHelper.KubectlApply(out, "")
62 if err != nil {
63 testutil.AnnotatedFatalf(t, "'kubectl apply' command failed",
64 "'kubectl apply' command failed\n%s", out)
65 }
66
67
68 checkCmd := []string{"jaeger", "check", "--wait=0"}
69 golden := "check.jaeger.golden"
70 timeout := time.Minute
71 err = testutil.RetryFor(timeout, func() error {
72 out, err := TestHelper.LinkerdRun(checkCmd...)
73 if err != nil {
74 return fmt.Errorf("'linkerd jaeger check' command failed\n%w\n%s", err, out)
75 }
76
77 pods, err := TestHelper.KubernetesHelper.GetPods(context.Background(), tracingNs, nil)
78 if err != nil {
79 testutil.AnnotatedFatal(t, fmt.Sprintf("failed to retrieve pods: %s", err), err)
80 }
81
82 tpl := template.Must(template.ParseFiles("testdata" + "/" + golden))
83 chs, err := version.NewChannels("test-99.88.77")
84 if err != nil {
85 panic(err.Error())
86 }
87 versionErr := healthcheck.CheckProxyVersionsUpToDate(pods, chs)
88 versionErrMsg := ""
89 if versionErr != nil {
90 versionErrMsg = versionErr.Error()
91 }
92 vars := struct {
93 ProxyVersionErr string
94 HintURL string
95 }{
96 versionErrMsg,
97 healthcheck.HintBaseURL(TestHelper.GetVersion()),
98 }
99
100 var expected bytes.Buffer
101 if err := tpl.Execute(&expected, vars); err != nil {
102 testutil.AnnotatedFatal(t, fmt.Sprintf("failed to parse check.viz.golden template: %s", err), err)
103 }
104
105 if out != expected.String() {
106 return fmt.Errorf(
107 "Expected:\n%s\nActual:\n%s", expected.String(), out)
108 }
109 return nil
110 })
111 if err != nil {
112 testutil.AnnotatedFatal(t, fmt.Sprintf("'linkerd jaeger check' command timed-out (%s)", timeout), err)
113 }
114
115 TestHelper.WithDataPlaneNamespace(ctx, "tracing-test", map[string]string{}, t, func(t *testing.T, namespace string) {
116 emojivotoYaml, err := testutil.ReadFile("testdata/emojivoto.yaml")
117 if err != nil {
118 testutil.AnnotatedFatalf(t, "failed to read emojivoto yaml",
119 "failed to read emojivoto yaml\n%s\n", err)
120 }
121
122 out, err = TestHelper.KubectlApply(emojivotoYaml, namespace)
123 if err != nil {
124 testutil.AnnotatedFatalf(t, "'kubectl apply' command failed",
125 "'kubectl apply' command failed\n%s", out)
126 }
127
128
129 for _, deploy := range []struct {
130 ns string
131 name string
132 }{
133 {ns: namespace, name: "vote-bot"},
134 {ns: namespace, name: "web"},
135 {ns: namespace, name: "emoji"},
136 {ns: namespace, name: "voting"},
137 {ns: tracingNs, name: "collector"},
138 {ns: tracingNs, name: "jaeger"},
139 } {
140 if err := TestHelper.CheckPods(ctx, deploy.ns, deploy.name, 1); err != nil {
141
142 if rce, ok := err.(*testutil.RestartCountError); ok {
143 testutil.AnnotatedWarn(t, "CheckPods timed-out", rce)
144 } else {
145 testutil.AnnotatedError(t, "CheckPods timed-out", err)
146 }
147 }
148
149 pods, err := TestHelper.GetPodsForDeployment(ctx, deploy.ns, deploy.name)
150 if err != nil {
151 testutil.AnnotatedWarn(t, "Failed to get pods", err)
152 }
153 for _, pod := range pods {
154 pod := pod
155 if !labels.IsTracingEnabled(&pod) {
156 testutil.AnnotatedWarn(t, "Tracing annotation not found on pod", pod.Namespace, pod.Name)
157
158
159 t.SkipNow()
160 }
161 }
162 }
163
164 t.Run("expect full trace", func(t *testing.T) {
165 timeout := 3 * time.Minute
166 err = testutil.RetryFor(timeout, func() error {
167 url, err := TestHelper.URLFor(ctx, tracingNs, "jaeger", 16686)
168 if err != nil {
169 return err
170 }
171
172 tracesJSON, err := TestHelper.HTTPGetURL(url + "/jaeger/api/traces?lookback=1h&service=linkerd-proxy")
173 if err != nil {
174 return err
175 }
176 traces := traces{}
177
178 err = json.Unmarshal([]byte(tracesJSON), &traces)
179 if err != nil {
180 return err
181 }
182
183 if !hasTraceWithProcess(&traces, "linkerd-proxy") {
184 return noProxyTraceFound{}
185 }
186 return nil
187 })
188 if err != nil {
189
190
191 var npte noProxyTraceFound
192 if errors.As(err, &npte) {
193 testutil.AnnotatedWarn(t, fmt.Sprintf("timed-out checking trace (%s)", timeout), err)
194 t.SkipNow()
195 }
196 testutil.AnnotatedFatal(t, fmt.Sprintf("timed-out checking trace (%s)", timeout), err)
197 }
198 })
199 })
200 }
201
202 func hasTraceWithProcess(traces *traces, ps string) bool {
203 for _, trace := range traces.Data {
204 for _, process := range trace.Processes {
205 if process.ServiceName == ps {
206 return true
207 }
208 }
209 }
210 return false
211 }
212
213 type noProxyTraceFound struct{}
214
215 func (e noProxyTraceFound) Error() string {
216 return "no trace found with processes: linkerd-proxy"
217 }
218
View as plain text