66 "errors"
77 "fmt"
88 "os"
9+ "reflect"
910 "testing"
1011 "time"
1112
@@ -19,10 +20,24 @@ type (
1920
2021 trace struct {
2122 Processes map [string ]process `json:"processes"`
23+ Spans []span `json:"spans"`
2224 }
2325
2426 process struct {
2527 ServiceName string `json:"serviceName"`
28+ Tags []tag `json:"tags"`
29+ }
30+
31+ span struct {
32+ OperationName string `json:"operationName"`
33+ Tags []tag `json:"tags"`
34+ ProcessId string `json:"processID"`
35+ }
36+
37+ tag struct {
38+ Key string `json:"key"`
39+ Type string `json:"type"`
40+ Value interface {} `json:"value"`
2641 }
2742)
2843
@@ -101,8 +116,46 @@ func TestTracing(t *testing.T) {
101116 return err
102117 }
103118
104- if ! hasTraceWithProcess (& traces , "linkerd-proxy" ) {
105- return noProxyTraceFound {}
119+ expected := []expectedTrace {
120+ {
121+ serviceName : "linkerd-proxy" ,
122+ app : "web-svc" ,
123+ processTags : map [string ]tagMatcher {
124+ "host.name" : anyStringMatcher {},
125+ "k8s.container.name" : stringMatcher ("linkerd-proxy" ),
126+ "k8s.pod.ip" : anyStringMatcher {},
127+ "k8s.pod.uid" : anyStringMatcher {},
128+ "linkerd.io/control-plane-ns" : stringMatcher ("linkerd" ),
129+ "linkerd.io/proxy-deployment" : stringMatcher ("web" ),
130+ "linkerd.io/workload-ns" : stringMatcher (namespace ),
131+ "pod-template-hash" : anyStringMatcher {},
132+ "process.pid" : anyMatcher {},
133+ "process.start_timestamp" : anyMatcher {},
134+ },
135+ operation : "/api/vote" ,
136+ spanKind : "server" ,
137+ spanTags : map [string ]tagMatcher {
138+ "http.request.method" : stringMatcher ("GET" ),
139+ "url.scheme" : stringMatcher ("http" ),
140+ "url.path" : stringMatcher ("/api/vote" ),
141+ "url.query" : anyStringMatcher {},
142+ "url.full" : anyStringMatcher {},
143+ "network.transport" : stringMatcher ("tcp" ),
144+ "server.address" : stringMatcher ("web-svc" ),
145+ "server.port" : stringMatcher ("80" ),
146+ "user_agent.original" : anyStringMatcher {},
147+ "http.request.header.l5d-orig-proto" : stringMatcher ("HTTP/1.1" ),
148+ "direction" : stringMatcher ("inbound" ),
149+ "http.response.status_code" : stringMatcher ("200" ),
150+ },
151+ },
152+ }
153+
154+ for _ , e := range expected {
155+ err := inspectTraces (t , & traces , e )
156+ if err != nil {
157+ return err
158+ }
106159 }
107160 return nil
108161 })
@@ -144,19 +197,133 @@ func installTracing(t *testing.T, namespace string) {
144197 }
145198}
146199
147- func hasTraceWithProcess (traces * traces , ps string ) bool {
200+ type expectedTrace struct {
201+ serviceName string
202+ app string
203+ processTags map [string ]tagMatcher
204+ operation string
205+ spanKind string
206+ spanTags map [string ]tagMatcher
207+ }
208+
209+ type tagMatcher interface {
210+ assertMatches (t * testing.T , key string , value interface {})
211+ }
212+
213+ type stringMatcher string
214+
215+ func (expected stringMatcher ) assertMatches (t * testing.T , key string , actual interface {}) {
216+ anyStringMatcher {}.assertMatches (t , key , actual )
217+ if string (expected ) != actual .(string ) {
218+ t .Fatalf ("Tag %s found with incorrect value\n Expected %s, found %s" , key , string (expected ), actual .(string ))
219+ }
220+ }
221+
222+ type anyStringMatcher struct {}
223+
224+ func (_ anyStringMatcher ) assertMatches (t * testing.T , key string , value interface {}) {
225+ _ , ok := value .(string )
226+ if ! ok {
227+ t .Fatalf ("Tag %s has incorrect type\n expected string, found %s" , key , reflect .TypeOf (value ).Name ())
228+ }
229+ }
230+
231+ type anyMatcher struct {}
232+
233+ func (_ anyMatcher ) assertMatches (_ * testing.T , _ string , _ interface {}) {}
234+
235+ func inspectTraces (t * testing.T , traces * traces , expected expectedTrace ) error {
148236 for _ , trace := range traces .Data {
149- for _ , process := range trace .Processes {
150- if process .ServiceName == ps {
151- return true
237+ var matchedProcess = ""
238+ for id , process := range trace .Processes {
239+ if process .ServiceName != expected .serviceName {
240+ continue
241+ }
242+
243+ var appMatches = false
244+ for _ , tag := range process .Tags {
245+ if tag .Key != "app" {
246+ continue
247+ }
248+ value , ok := tag .Value .(string )
249+ if ! ok {
250+ continue
251+ }
252+ if value != expected .app {
253+ break
254+ }
255+ appMatches = true
256+ break
152257 }
258+ if ! appMatches {
259+ continue
260+ }
261+
262+ assertContainsTags (t , process .Tags , expected .processTags )
263+
264+ matchedProcess = id
265+ break
266+ }
267+ if matchedProcess == "" {
268+ continue
153269 }
270+
271+ for _ , span := range trace .Spans {
272+ if span .ProcessId != matchedProcess {
273+ continue
274+ }
275+
276+ if span .OperationName != expected .operation {
277+ continue
278+ }
279+
280+ var kindMatches = false
281+ for _ , tag := range span .Tags {
282+ value , ok := tag .Value .(string )
283+ if tag .Key != "span.kind" {
284+ continue
285+ }
286+ if ! ok {
287+ continue
288+ }
289+ if value != expected .spanKind {
290+ break
291+ }
292+ kindMatches = true
293+ break
294+ }
295+ if ! kindMatches {
296+ continue
297+ }
298+
299+ assertContainsTags (t , span .Tags , expected .spanTags )
300+
301+ return nil
302+ }
303+ }
304+
305+ return noProxyTraceFound {traces }
306+ }
307+
308+ func assertContainsTags (t * testing.T , rawTags []tag , expected map [string ]tagMatcher ) {
309+ tags := map [string ]interface {}{}
310+ for _ , tag := range rawTags {
311+ tags [tag .Key ] = tag .Value
312+ }
313+
314+ for key , expectedValue := range expected {
315+ actual , ok := tags [key ]
316+ if ! ok {
317+ t .Fatalf ("Tag %s not found in tags\n Tags: %v" , key , tags )
318+ }
319+ expectedValue .assertMatches (t , key , actual )
154320 }
155- return false
156321}
157322
158- type noProxyTraceFound struct {}
323+ type noProxyTraceFound struct {
324+ traces * traces
325+ }
159326
160327func (e noProxyTraceFound ) Error () string {
161- return "no trace found with processes: linkerd-proxy"
328+ return fmt . Sprintf ( "no trace found with processes: linkerd-proxy\n %v" , e . traces . Data )
162329}
0 commit comments