1// Copyright The OpenTelemetry Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package zipkin
16
17import (
18	"fmt"
19	"net"
20	"strconv"
21	"testing"
22	"time"
23
24	"github.com/google/go-cmp/cmp"
25	zkmodel "github.com/openzipkin/zipkin-go/model"
26	"github.com/stretchr/testify/assert"
27	"github.com/stretchr/testify/require"
28
29	"go.opentelemetry.io/otel/attribute"
30	"go.opentelemetry.io/otel/codes"
31	"go.opentelemetry.io/otel/sdk/instrumentation"
32	"go.opentelemetry.io/otel/sdk/resource"
33	tracesdk "go.opentelemetry.io/otel/sdk/trace"
34	"go.opentelemetry.io/otel/semconv"
35	"go.opentelemetry.io/otel/trace"
36)
37
38func TestModelConversion(t *testing.T) {
39	resource := resource.NewWithAttributes(
40		semconv.ServiceNameKey.String("model-test"),
41	)
42
43	inputBatch := []*tracesdk.SpanSnapshot{
44		// typical span data
45		{
46			SpanContext: trace.NewSpanContext(trace.SpanContextConfig{
47				TraceID: trace.TraceID{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F},
48				SpanID:  trace.SpanID{0xFF, 0xFE, 0xFD, 0xFC, 0xFB, 0xFA, 0xF9, 0xF8},
49			}),
50			Parent: trace.NewSpanContext(trace.SpanContextConfig{
51				TraceID: trace.TraceID{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F},
52				SpanID:  trace.SpanID{0x3F, 0x3E, 0x3D, 0x3C, 0x3B, 0x3A, 0x39, 0x38},
53			}),
54			SpanKind:  trace.SpanKindServer,
55			Name:      "foo",
56			StartTime: time.Date(2020, time.March, 11, 19, 24, 0, 0, time.UTC),
57			EndTime:   time.Date(2020, time.March, 11, 19, 25, 0, 0, time.UTC),
58			Attributes: []attribute.KeyValue{
59				attribute.Int64("attr1", 42),
60				attribute.String("attr2", "bar"),
61				attribute.Array("attr3", []int{0, 1, 2}),
62			},
63			MessageEvents: []trace.Event{
64				{
65					Time: time.Date(2020, time.March, 11, 19, 24, 30, 0, time.UTC),
66					Name: "ev1",
67					Attributes: []attribute.KeyValue{
68						attribute.Int64("eventattr1", 123),
69					},
70				},
71				{
72					Time:       time.Date(2020, time.March, 11, 19, 24, 45, 0, time.UTC),
73					Name:       "ev2",
74					Attributes: nil,
75				},
76			},
77			StatusCode:    codes.Error,
78			StatusMessage: "404, file not found",
79			Resource:      resource,
80		},
81		// span data with no parent (same as typical, but has
82		// invalid parent)
83		{
84			SpanContext: trace.NewSpanContext(trace.SpanContextConfig{
85				TraceID: trace.TraceID{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F},
86				SpanID:  trace.SpanID{0xFF, 0xFE, 0xFD, 0xFC, 0xFB, 0xFA, 0xF9, 0xF8},
87			}),
88			SpanKind:  trace.SpanKindServer,
89			Name:      "foo",
90			StartTime: time.Date(2020, time.March, 11, 19, 24, 0, 0, time.UTC),
91			EndTime:   time.Date(2020, time.March, 11, 19, 25, 0, 0, time.UTC),
92			Attributes: []attribute.KeyValue{
93				attribute.Int64("attr1", 42),
94				attribute.String("attr2", "bar"),
95			},
96			MessageEvents: []trace.Event{
97				{
98					Time: time.Date(2020, time.March, 11, 19, 24, 30, 0, time.UTC),
99					Name: "ev1",
100					Attributes: []attribute.KeyValue{
101						attribute.Int64("eventattr1", 123),
102					},
103				},
104				{
105					Time:       time.Date(2020, time.March, 11, 19, 24, 45, 0, time.UTC),
106					Name:       "ev2",
107					Attributes: nil,
108				},
109			},
110			StatusCode:    codes.Error,
111			StatusMessage: "404, file not found",
112			Resource:      resource,
113		},
114		// span data of unspecified kind
115		{
116			SpanContext: trace.NewSpanContext(trace.SpanContextConfig{
117				TraceID: trace.TraceID{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F},
118				SpanID:  trace.SpanID{0xFF, 0xFE, 0xFD, 0xFC, 0xFB, 0xFA, 0xF9, 0xF8},
119			}),
120			Parent: trace.NewSpanContext(trace.SpanContextConfig{
121				TraceID: trace.TraceID{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F},
122				SpanID:  trace.SpanID{0x3F, 0x3E, 0x3D, 0x3C, 0x3B, 0x3A, 0x39, 0x38},
123			}),
124			SpanKind:  trace.SpanKindUnspecified,
125			Name:      "foo",
126			StartTime: time.Date(2020, time.March, 11, 19, 24, 0, 0, time.UTC),
127			EndTime:   time.Date(2020, time.March, 11, 19, 25, 0, 0, time.UTC),
128			Attributes: []attribute.KeyValue{
129				attribute.Int64("attr1", 42),
130				attribute.String("attr2", "bar"),
131			},
132			MessageEvents: []trace.Event{
133				{
134					Time: time.Date(2020, time.March, 11, 19, 24, 30, 0, time.UTC),
135					Name: "ev1",
136					Attributes: []attribute.KeyValue{
137						attribute.Int64("eventattr1", 123),
138					},
139				},
140				{
141					Time:       time.Date(2020, time.March, 11, 19, 24, 45, 0, time.UTC),
142					Name:       "ev2",
143					Attributes: nil,
144				},
145			},
146			StatusCode:    codes.Error,
147			StatusMessage: "404, file not found",
148			Resource:      resource,
149		},
150		// span data of internal kind
151		{
152			SpanContext: trace.NewSpanContext(trace.SpanContextConfig{
153				TraceID: trace.TraceID{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F},
154				SpanID:  trace.SpanID{0xFF, 0xFE, 0xFD, 0xFC, 0xFB, 0xFA, 0xF9, 0xF8},
155			}),
156			Parent: trace.NewSpanContext(trace.SpanContextConfig{
157				TraceID: trace.TraceID{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F},
158				SpanID:  trace.SpanID{0x3F, 0x3E, 0x3D, 0x3C, 0x3B, 0x3A, 0x39, 0x38},
159			}),
160			SpanKind:  trace.SpanKindInternal,
161			Name:      "foo",
162			StartTime: time.Date(2020, time.March, 11, 19, 24, 0, 0, time.UTC),
163			EndTime:   time.Date(2020, time.March, 11, 19, 25, 0, 0, time.UTC),
164			Attributes: []attribute.KeyValue{
165				attribute.Int64("attr1", 42),
166				attribute.String("attr2", "bar"),
167			},
168			MessageEvents: []trace.Event{
169				{
170					Time: time.Date(2020, time.March, 11, 19, 24, 30, 0, time.UTC),
171					Name: "ev1",
172					Attributes: []attribute.KeyValue{
173						attribute.Int64("eventattr1", 123),
174					},
175				},
176				{
177					Time:       time.Date(2020, time.March, 11, 19, 24, 45, 0, time.UTC),
178					Name:       "ev2",
179					Attributes: nil,
180				},
181			},
182			StatusCode:    codes.Error,
183			StatusMessage: "404, file not found",
184			Resource:      resource,
185		},
186		// span data of client kind
187		{
188			SpanContext: trace.NewSpanContext(trace.SpanContextConfig{
189				TraceID: trace.TraceID{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F},
190				SpanID:  trace.SpanID{0xFF, 0xFE, 0xFD, 0xFC, 0xFB, 0xFA, 0xF9, 0xF8},
191			}),
192			Parent: trace.NewSpanContext(trace.SpanContextConfig{
193				TraceID: trace.TraceID{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F},
194				SpanID:  trace.SpanID{0x3F, 0x3E, 0x3D, 0x3C, 0x3B, 0x3A, 0x39, 0x38},
195			}),
196			SpanKind:  trace.SpanKindClient,
197			Name:      "foo",
198			StartTime: time.Date(2020, time.March, 11, 19, 24, 0, 0, time.UTC),
199			EndTime:   time.Date(2020, time.March, 11, 19, 25, 0, 0, time.UTC),
200			Attributes: []attribute.KeyValue{
201				attribute.Int64("attr1", 42),
202				attribute.String("attr2", "bar"),
203				attribute.String("peer.hostname", "test-peer-hostname"),
204				attribute.String("net.peer.ip", "1.2.3.4"),
205				attribute.Int64("net.peer.port", 9876),
206			},
207			MessageEvents: []trace.Event{
208				{
209					Time: time.Date(2020, time.March, 11, 19, 24, 30, 0, time.UTC),
210					Name: "ev1",
211					Attributes: []attribute.KeyValue{
212						attribute.Int64("eventattr1", 123),
213					},
214				},
215				{
216					Time:       time.Date(2020, time.March, 11, 19, 24, 45, 0, time.UTC),
217					Name:       "ev2",
218					Attributes: nil,
219				},
220			},
221			StatusCode:    codes.Error,
222			StatusMessage: "404, file not found",
223			Resource:      resource,
224		},
225		// span data of producer kind
226		{
227			SpanContext: trace.NewSpanContext(trace.SpanContextConfig{
228				TraceID: trace.TraceID{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F},
229				SpanID:  trace.SpanID{0xFF, 0xFE, 0xFD, 0xFC, 0xFB, 0xFA, 0xF9, 0xF8},
230			}),
231			Parent: trace.NewSpanContext(trace.SpanContextConfig{
232				TraceID: trace.TraceID{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F},
233				SpanID:  trace.SpanID{0x3F, 0x3E, 0x3D, 0x3C, 0x3B, 0x3A, 0x39, 0x38},
234			}),
235			SpanKind:  trace.SpanKindProducer,
236			Name:      "foo",
237			StartTime: time.Date(2020, time.March, 11, 19, 24, 0, 0, time.UTC),
238			EndTime:   time.Date(2020, time.March, 11, 19, 25, 0, 0, time.UTC),
239			Attributes: []attribute.KeyValue{
240				attribute.Int64("attr1", 42),
241				attribute.String("attr2", "bar"),
242			},
243			MessageEvents: []trace.Event{
244				{
245					Time: time.Date(2020, time.March, 11, 19, 24, 30, 0, time.UTC),
246					Name: "ev1",
247					Attributes: []attribute.KeyValue{
248						attribute.Int64("eventattr1", 123),
249					},
250				},
251				{
252					Time:       time.Date(2020, time.March, 11, 19, 24, 45, 0, time.UTC),
253					Name:       "ev2",
254					Attributes: nil,
255				},
256			},
257			StatusCode:    codes.Error,
258			StatusMessage: "404, file not found",
259			Resource:      resource,
260		},
261		// span data of consumer kind
262		{
263			SpanContext: trace.NewSpanContext(trace.SpanContextConfig{
264				TraceID: trace.TraceID{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F},
265				SpanID:  trace.SpanID{0xFF, 0xFE, 0xFD, 0xFC, 0xFB, 0xFA, 0xF9, 0xF8},
266			}),
267			Parent: trace.NewSpanContext(trace.SpanContextConfig{
268				TraceID: trace.TraceID{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F},
269				SpanID:  trace.SpanID{0x3F, 0x3E, 0x3D, 0x3C, 0x3B, 0x3A, 0x39, 0x38},
270			}),
271			SpanKind:  trace.SpanKindConsumer,
272			Name:      "foo",
273			StartTime: time.Date(2020, time.March, 11, 19, 24, 0, 0, time.UTC),
274			EndTime:   time.Date(2020, time.March, 11, 19, 25, 0, 0, time.UTC),
275			Attributes: []attribute.KeyValue{
276				attribute.Int64("attr1", 42),
277				attribute.String("attr2", "bar"),
278			},
279			MessageEvents: []trace.Event{
280				{
281					Time: time.Date(2020, time.March, 11, 19, 24, 30, 0, time.UTC),
282					Name: "ev1",
283					Attributes: []attribute.KeyValue{
284						attribute.Int64("eventattr1", 123),
285					},
286				},
287				{
288					Time:       time.Date(2020, time.March, 11, 19, 24, 45, 0, time.UTC),
289					Name:       "ev2",
290					Attributes: nil,
291				},
292			},
293			StatusCode:    codes.Error,
294			StatusMessage: "404, file not found",
295			Resource:      resource,
296		},
297		// span data with no events
298		{
299			SpanContext: trace.NewSpanContext(trace.SpanContextConfig{
300				TraceID: trace.TraceID{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F},
301				SpanID:  trace.SpanID{0xFF, 0xFE, 0xFD, 0xFC, 0xFB, 0xFA, 0xF9, 0xF8},
302			}),
303			Parent: trace.NewSpanContext(trace.SpanContextConfig{
304				TraceID: trace.TraceID{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F},
305				SpanID:  trace.SpanID{0x3F, 0x3E, 0x3D, 0x3C, 0x3B, 0x3A, 0x39, 0x38},
306			}),
307			SpanKind:  trace.SpanKindServer,
308			Name:      "foo",
309			StartTime: time.Date(2020, time.March, 11, 19, 24, 0, 0, time.UTC),
310			EndTime:   time.Date(2020, time.March, 11, 19, 25, 0, 0, time.UTC),
311			Attributes: []attribute.KeyValue{
312				attribute.Int64("attr1", 42),
313				attribute.String("attr2", "bar"),
314			},
315			MessageEvents: nil,
316			StatusCode:    codes.Error,
317			StatusMessage: "404, file not found",
318			Resource:      resource,
319		},
320		// span data with an "error" attribute set to "false"
321		{
322			SpanContext: trace.NewSpanContext(trace.SpanContextConfig{
323				TraceID: trace.TraceID{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F},
324				SpanID:  trace.SpanID{0xFF, 0xFE, 0xFD, 0xFC, 0xFB, 0xFA, 0xF9, 0xF8},
325			}),
326			Parent: trace.NewSpanContext(trace.SpanContextConfig{
327				TraceID: trace.TraceID{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F},
328				SpanID:  trace.SpanID{0x3F, 0x3E, 0x3D, 0x3C, 0x3B, 0x3A, 0x39, 0x38},
329			}),
330			SpanKind:  trace.SpanKindServer,
331			Name:      "foo",
332			StartTime: time.Date(2020, time.March, 11, 19, 24, 0, 0, time.UTC),
333			EndTime:   time.Date(2020, time.March, 11, 19, 25, 0, 0, time.UTC),
334			Attributes: []attribute.KeyValue{
335				attribute.String("error", "false"),
336			},
337			MessageEvents: []trace.Event{
338				{
339					Time: time.Date(2020, time.March, 11, 19, 24, 30, 0, time.UTC),
340					Name: "ev1",
341					Attributes: []attribute.KeyValue{
342						attribute.Int64("eventattr1", 123),
343					},
344				},
345				{
346					Time:       time.Date(2020, time.March, 11, 19, 24, 45, 0, time.UTC),
347					Name:       "ev2",
348					Attributes: nil,
349				},
350			},
351			StatusCode: codes.Unset,
352			Resource:   resource,
353		},
354	}
355
356	expectedOutputBatch := []zkmodel.SpanModel{
357		// model for typical span data
358		{
359			SpanContext: zkmodel.SpanContext{
360				TraceID: zkmodel.TraceID{
361					High: 0x001020304050607,
362					Low:  0x8090a0b0c0d0e0f,
363				},
364				ID:       zkmodel.ID(0xfffefdfcfbfaf9f8),
365				ParentID: zkmodelIDPtr(0x3f3e3d3c3b3a3938),
366				Debug:    false,
367				Sampled:  nil,
368				Err:      nil,
369			},
370			Name:      "foo",
371			Kind:      "SERVER",
372			Timestamp: time.Date(2020, time.March, 11, 19, 24, 0, 0, time.UTC),
373			Duration:  time.Minute,
374			Shared:    false,
375			LocalEndpoint: &zkmodel.Endpoint{
376				ServiceName: "model-test",
377			},
378			RemoteEndpoint: nil,
379			Annotations: []zkmodel.Annotation{
380				{
381					Timestamp: time.Date(2020, time.March, 11, 19, 24, 30, 0, time.UTC),
382					Value:     `ev1: {"eventattr1":123}`,
383				},
384				{
385					Timestamp: time.Date(2020, time.March, 11, 19, 24, 45, 0, time.UTC),
386					Value:     "ev2",
387				},
388			},
389			Tags: map[string]string{
390				"attr1":            "42",
391				"attr2":            "bar",
392				"attr3":            "[0,1,2]",
393				"otel.status_code": "Error",
394				"error":            "404, file not found",
395			},
396		},
397		// model for span data with no parent
398		{
399			SpanContext: zkmodel.SpanContext{
400				TraceID: zkmodel.TraceID{
401					High: 0x001020304050607,
402					Low:  0x8090a0b0c0d0e0f,
403				},
404				ID:       zkmodel.ID(0xfffefdfcfbfaf9f8),
405				ParentID: nil,
406				Debug:    false,
407				Sampled:  nil,
408				Err:      nil,
409			},
410			Name:      "foo",
411			Kind:      "SERVER",
412			Timestamp: time.Date(2020, time.March, 11, 19, 24, 0, 0, time.UTC),
413			Duration:  time.Minute,
414			Shared:    false,
415			LocalEndpoint: &zkmodel.Endpoint{
416				ServiceName: "model-test",
417			},
418			RemoteEndpoint: nil,
419			Annotations: []zkmodel.Annotation{
420				{
421					Timestamp: time.Date(2020, time.March, 11, 19, 24, 30, 0, time.UTC),
422					Value:     `ev1: {"eventattr1":123}`,
423				},
424				{
425					Timestamp: time.Date(2020, time.March, 11, 19, 24, 45, 0, time.UTC),
426					Value:     "ev2",
427				},
428			},
429			Tags: map[string]string{
430				"attr1":            "42",
431				"attr2":            "bar",
432				"otel.status_code": "Error",
433				"error":            "404, file not found",
434			},
435		},
436		// model for span data of unspecified kind
437		{
438			SpanContext: zkmodel.SpanContext{
439				TraceID: zkmodel.TraceID{
440					High: 0x001020304050607,
441					Low:  0x8090a0b0c0d0e0f,
442				},
443				ID:       zkmodel.ID(0xfffefdfcfbfaf9f8),
444				ParentID: zkmodelIDPtr(0x3f3e3d3c3b3a3938),
445				Debug:    false,
446				Sampled:  nil,
447				Err:      nil,
448			},
449			Name:      "foo",
450			Kind:      "",
451			Timestamp: time.Date(2020, time.March, 11, 19, 24, 0, 0, time.UTC),
452			Duration:  time.Minute,
453			Shared:    false,
454			LocalEndpoint: &zkmodel.Endpoint{
455				ServiceName: "model-test",
456			},
457			RemoteEndpoint: nil,
458			Annotations: []zkmodel.Annotation{
459				{
460					Timestamp: time.Date(2020, time.March, 11, 19, 24, 30, 0, time.UTC),
461					Value:     `ev1: {"eventattr1":123}`,
462				},
463				{
464					Timestamp: time.Date(2020, time.March, 11, 19, 24, 45, 0, time.UTC),
465					Value:     "ev2",
466				},
467			},
468			Tags: map[string]string{
469				"attr1":            "42",
470				"attr2":            "bar",
471				"otel.status_code": "Error",
472				"error":            "404, file not found",
473			},
474		},
475		// model for span data of internal kind
476		{
477			SpanContext: zkmodel.SpanContext{
478				TraceID: zkmodel.TraceID{
479					High: 0x001020304050607,
480					Low:  0x8090a0b0c0d0e0f,
481				},
482				ID:       zkmodel.ID(0xfffefdfcfbfaf9f8),
483				ParentID: zkmodelIDPtr(0x3f3e3d3c3b3a3938),
484				Debug:    false,
485				Sampled:  nil,
486				Err:      nil,
487			},
488			Name:      "foo",
489			Kind:      "",
490			Timestamp: time.Date(2020, time.March, 11, 19, 24, 0, 0, time.UTC),
491			Duration:  time.Minute,
492			Shared:    false,
493			LocalEndpoint: &zkmodel.Endpoint{
494				ServiceName: "model-test",
495			},
496			RemoteEndpoint: nil,
497			Annotations: []zkmodel.Annotation{
498				{
499					Timestamp: time.Date(2020, time.March, 11, 19, 24, 30, 0, time.UTC),
500					Value:     `ev1: {"eventattr1":123}`,
501				},
502				{
503					Timestamp: time.Date(2020, time.March, 11, 19, 24, 45, 0, time.UTC),
504					Value:     "ev2",
505				},
506			},
507			Tags: map[string]string{
508				"attr1":            "42",
509				"attr2":            "bar",
510				"otel.status_code": "Error",
511				"error":            "404, file not found",
512			},
513		},
514		// model for span data of client kind
515		{
516			SpanContext: zkmodel.SpanContext{
517				TraceID: zkmodel.TraceID{
518					High: 0x001020304050607,
519					Low:  0x8090a0b0c0d0e0f,
520				},
521				ID:       zkmodel.ID(0xfffefdfcfbfaf9f8),
522				ParentID: zkmodelIDPtr(0x3f3e3d3c3b3a3938),
523				Debug:    false,
524				Sampled:  nil,
525				Err:      nil,
526			},
527			Name:      "foo",
528			Kind:      "CLIENT",
529			Timestamp: time.Date(2020, time.March, 11, 19, 24, 0, 0, time.UTC),
530			Duration:  time.Minute,
531			Shared:    false,
532			LocalEndpoint: &zkmodel.Endpoint{
533				ServiceName: "model-test",
534			},
535			RemoteEndpoint: &zkmodel.Endpoint{
536				IPv4: net.ParseIP("1.2.3.4"),
537				Port: 9876,
538			},
539			Annotations: []zkmodel.Annotation{
540				{
541					Timestamp: time.Date(2020, time.March, 11, 19, 24, 30, 0, time.UTC),
542					Value:     `ev1: {"eventattr1":123}`,
543				},
544				{
545					Timestamp: time.Date(2020, time.March, 11, 19, 24, 45, 0, time.UTC),
546					Value:     "ev2",
547				},
548			},
549			Tags: map[string]string{
550				"attr1":            "42",
551				"attr2":            "bar",
552				"net.peer.ip":      "1.2.3.4",
553				"net.peer.port":    "9876",
554				"peer.hostname":    "test-peer-hostname",
555				"otel.status_code": "Error",
556				"error":            "404, file not found",
557			},
558		},
559		// model for span data of producer kind
560		{
561			SpanContext: zkmodel.SpanContext{
562				TraceID: zkmodel.TraceID{
563					High: 0x001020304050607,
564					Low:  0x8090a0b0c0d0e0f,
565				},
566				ID:       zkmodel.ID(0xfffefdfcfbfaf9f8),
567				ParentID: zkmodelIDPtr(0x3f3e3d3c3b3a3938),
568				Debug:    false,
569				Sampled:  nil,
570				Err:      nil,
571			},
572			Name:      "foo",
573			Kind:      "PRODUCER",
574			Timestamp: time.Date(2020, time.March, 11, 19, 24, 0, 0, time.UTC),
575			Duration:  time.Minute,
576			Shared:    false,
577			LocalEndpoint: &zkmodel.Endpoint{
578				ServiceName: "model-test",
579			},
580			RemoteEndpoint: nil,
581			Annotations: []zkmodel.Annotation{
582				{
583					Timestamp: time.Date(2020, time.March, 11, 19, 24, 30, 0, time.UTC),
584					Value:     `ev1: {"eventattr1":123}`,
585				},
586				{
587					Timestamp: time.Date(2020, time.March, 11, 19, 24, 45, 0, time.UTC),
588					Value:     "ev2",
589				},
590			},
591			Tags: map[string]string{
592				"attr1":            "42",
593				"attr2":            "bar",
594				"otel.status_code": "Error",
595				"error":            "404, file not found",
596			},
597		},
598		// model for span data of consumer kind
599		{
600			SpanContext: zkmodel.SpanContext{
601				TraceID: zkmodel.TraceID{
602					High: 0x001020304050607,
603					Low:  0x8090a0b0c0d0e0f,
604				},
605				ID:       zkmodel.ID(0xfffefdfcfbfaf9f8),
606				ParentID: zkmodelIDPtr(0x3f3e3d3c3b3a3938),
607				Debug:    false,
608				Sampled:  nil,
609				Err:      nil,
610			},
611			Name:      "foo",
612			Kind:      "CONSUMER",
613			Timestamp: time.Date(2020, time.March, 11, 19, 24, 0, 0, time.UTC),
614			Duration:  time.Minute,
615			Shared:    false,
616			LocalEndpoint: &zkmodel.Endpoint{
617				ServiceName: "model-test",
618			},
619			RemoteEndpoint: nil,
620			Annotations: []zkmodel.Annotation{
621				{
622					Timestamp: time.Date(2020, time.March, 11, 19, 24, 30, 0, time.UTC),
623					Value:     `ev1: {"eventattr1":123}`,
624				},
625				{
626					Timestamp: time.Date(2020, time.March, 11, 19, 24, 45, 0, time.UTC),
627					Value:     "ev2",
628				},
629			},
630			Tags: map[string]string{
631				"attr1":            "42",
632				"attr2":            "bar",
633				"otel.status_code": "Error",
634				"error":            "404, file not found",
635			},
636		},
637		// model for span data with no events
638		{
639			SpanContext: zkmodel.SpanContext{
640				TraceID: zkmodel.TraceID{
641					High: 0x001020304050607,
642					Low:  0x8090a0b0c0d0e0f,
643				},
644				ID:       zkmodel.ID(0xfffefdfcfbfaf9f8),
645				ParentID: zkmodelIDPtr(0x3f3e3d3c3b3a3938),
646				Debug:    false,
647				Sampled:  nil,
648				Err:      nil,
649			},
650			Name:      "foo",
651			Kind:      "SERVER",
652			Timestamp: time.Date(2020, time.March, 11, 19, 24, 0, 0, time.UTC),
653			Duration:  time.Minute,
654			Shared:    false,
655			LocalEndpoint: &zkmodel.Endpoint{
656				ServiceName: "model-test",
657			},
658			RemoteEndpoint: nil,
659			Annotations:    nil,
660			Tags: map[string]string{
661				"attr1":            "42",
662				"attr2":            "bar",
663				"otel.status_code": "Error",
664				"error":            "404, file not found",
665			},
666		},
667		// model for span data with an "error" attribute set to "false"
668		{
669			SpanContext: zkmodel.SpanContext{
670				TraceID: zkmodel.TraceID{
671					High: 0x001020304050607,
672					Low:  0x8090a0b0c0d0e0f,
673				},
674				ID:       zkmodel.ID(0xfffefdfcfbfaf9f8),
675				ParentID: zkmodelIDPtr(0x3f3e3d3c3b3a3938),
676				Debug:    false,
677				Sampled:  nil,
678				Err:      nil,
679			},
680			Name:      "foo",
681			Kind:      "SERVER",
682			Timestamp: time.Date(2020, time.March, 11, 19, 24, 0, 0, time.UTC),
683			Duration:  time.Minute,
684			Shared:    false,
685			LocalEndpoint: &zkmodel.Endpoint{
686				ServiceName: "model-test",
687			},
688			RemoteEndpoint: nil,
689			Annotations: []zkmodel.Annotation{
690				{
691					Timestamp: time.Date(2020, time.March, 11, 19, 24, 30, 0, time.UTC),
692					Value:     `ev1: {"eventattr1":123}`,
693				},
694				{
695					Timestamp: time.Date(2020, time.March, 11, 19, 24, 45, 0, time.UTC),
696					Value:     "ev2",
697				},
698			},
699			Tags: nil, // should be omitted
700		},
701	}
702	gottenOutputBatch := toZipkinSpanModels(inputBatch)
703	require.Equal(t, expectedOutputBatch, gottenOutputBatch)
704}
705
706func zkmodelIDPtr(n uint64) *zkmodel.ID {
707	id := zkmodel.ID(n)
708	return &id
709}
710
711func TestTagsTransformation(t *testing.T) {
712	keyValue := "value"
713	doubleValue := 123.456
714	uintValue := int64(123)
715	statusMessage := "this is a problem"
716	instrLibName := "instrumentation-library"
717	instrLibVersion := "semver:1.0.0"
718
719	tests := []struct {
720		name string
721		data *tracesdk.SpanSnapshot
722		want map[string]string
723	}{
724		{
725			name: "attributes",
726			data: &tracesdk.SpanSnapshot{
727				Attributes: []attribute.KeyValue{
728					attribute.String("key", keyValue),
729					attribute.Float64("double", doubleValue),
730					attribute.Int64("uint", uintValue),
731					attribute.Bool("ok", true),
732				},
733			},
734			want: map[string]string{
735				"double": fmt.Sprint(doubleValue),
736				"key":    keyValue,
737				"ok":     "true",
738				"uint":   strconv.FormatInt(uintValue, 10),
739			},
740		},
741		{
742			name: "no attributes",
743			data: &tracesdk.SpanSnapshot{},
744			want: nil,
745		},
746		{
747			name: "omit-noerror",
748			data: &tracesdk.SpanSnapshot{
749				Attributes: []attribute.KeyValue{
750					attribute.Bool("error", false),
751				},
752			},
753			want: nil,
754		},
755		{
756			name: "statusCode",
757			data: &tracesdk.SpanSnapshot{
758				Attributes: []attribute.KeyValue{
759					attribute.String("key", keyValue),
760					attribute.Bool("error", true),
761				},
762				StatusCode:    codes.Error,
763				StatusMessage: statusMessage,
764			},
765			want: map[string]string{
766				"error":            statusMessage,
767				"key":              keyValue,
768				"otel.status_code": codes.Error.String(),
769			},
770		},
771		{
772			name: "instrLib-empty",
773			data: &tracesdk.SpanSnapshot{
774				InstrumentationLibrary: instrumentation.Library{},
775			},
776			want: nil,
777		},
778		{
779			name: "instrLib-noversion",
780			data: &tracesdk.SpanSnapshot{
781				Attributes: []attribute.KeyValue{},
782				InstrumentationLibrary: instrumentation.Library{
783					Name: instrLibName,
784				},
785			},
786			want: map[string]string{
787				"otel.library.name": instrLibName,
788			},
789		},
790		{
791			name: "instrLib-with-version",
792			data: &tracesdk.SpanSnapshot{
793				Attributes: []attribute.KeyValue{},
794				InstrumentationLibrary: instrumentation.Library{
795					Name:    instrLibName,
796					Version: instrLibVersion,
797				},
798			},
799			want: map[string]string{
800				"otel.library.name":    instrLibName,
801				"otel.library.version": instrLibVersion,
802			},
803		},
804	}
805	for _, tt := range tests {
806		t.Run(tt.name, func(t *testing.T) {
807			got := toZipkinTags(tt.data)
808			if diff := cmp.Diff(got, tt.want); diff != "" {
809				t.Errorf("Diff%v", diff)
810			}
811		})
812	}
813}
814
815func TestRemoteEndpointTransformation(t *testing.T) {
816	tests := []struct {
817		name string
818		data *tracesdk.SpanSnapshot
819		want *zkmodel.Endpoint
820	}{
821		{
822			name: "nil-not-applicable",
823			data: &tracesdk.SpanSnapshot{
824				SpanKind:   trace.SpanKindClient,
825				Attributes: []attribute.KeyValue{},
826			},
827			want: nil,
828		},
829		{
830			name: "nil-not-found",
831			data: &tracesdk.SpanSnapshot{
832				SpanKind: trace.SpanKindConsumer,
833				Attributes: []attribute.KeyValue{
834					attribute.String("attr", "test"),
835				},
836			},
837			want: nil,
838		},
839		{
840			name: "peer-service-rank",
841			data: &tracesdk.SpanSnapshot{
842				SpanKind: trace.SpanKindProducer,
843				Attributes: []attribute.KeyValue{
844					semconv.PeerServiceKey.String("peer-service-test"),
845					semconv.NetPeerNameKey.String("peer-name-test"),
846					semconv.HTTPHostKey.String("http-host-test"),
847				},
848			},
849			want: &zkmodel.Endpoint{
850				ServiceName: "peer-service-test",
851			},
852		},
853		{
854			name: "http-host-rank",
855			data: &tracesdk.SpanSnapshot{
856				SpanKind: trace.SpanKindProducer,
857				Attributes: []attribute.KeyValue{
858					semconv.HTTPHostKey.String("http-host-test"),
859					semconv.DBNameKey.String("db-name-test"),
860				},
861			},
862			want: &zkmodel.Endpoint{
863				ServiceName: "http-host-test",
864			},
865		},
866		{
867			name: "db-name-rank",
868			data: &tracesdk.SpanSnapshot{
869				SpanKind: trace.SpanKindProducer,
870				Attributes: []attribute.KeyValue{
871					attribute.String("foo", "bar"),
872					semconv.DBNameKey.String("db-name-test"),
873				},
874			},
875			want: &zkmodel.Endpoint{
876				ServiceName: "db-name-test",
877			},
878		},
879		{
880			name: "peer-hostname-rank",
881			data: &tracesdk.SpanSnapshot{
882				SpanKind: trace.SpanKindProducer,
883				Attributes: []attribute.KeyValue{
884					keyPeerHostname.String("peer-hostname-test"),
885					keyPeerAddress.String("peer-address-test"),
886					semconv.HTTPHostKey.String("http-host-test"),
887					semconv.DBNameKey.String("http-host-test"),
888				},
889			},
890			want: &zkmodel.Endpoint{
891				ServiceName: "peer-hostname-test",
892			},
893		},
894		{
895			name: "peer-address-rank",
896			data: &tracesdk.SpanSnapshot{
897				SpanKind: trace.SpanKindProducer,
898				Attributes: []attribute.KeyValue{
899					keyPeerAddress.String("peer-address-test"),
900					semconv.HTTPHostKey.String("http-host-test"),
901					semconv.DBNameKey.String("http-host-test"),
902				},
903			},
904			want: &zkmodel.Endpoint{
905				ServiceName: "peer-address-test",
906			},
907		},
908		{
909			name: "net-peer-invalid-ip",
910			data: &tracesdk.SpanSnapshot{
911				SpanKind: trace.SpanKindProducer,
912				Attributes: []attribute.KeyValue{
913					semconv.NetPeerIPKey.String("INVALID"),
914				},
915			},
916			want: nil,
917		},
918		{
919			name: "net-peer-ipv6-no-port",
920			data: &tracesdk.SpanSnapshot{
921				SpanKind: trace.SpanKindProducer,
922				Attributes: []attribute.KeyValue{
923					semconv.NetPeerIPKey.String("0:0:1:5ee:bad:c0de:0:0"),
924				},
925			},
926			want: &zkmodel.Endpoint{
927				IPv6: net.ParseIP("0:0:1:5ee:bad:c0de:0:0"),
928			},
929		},
930		{
931			name: "net-peer-ipv4-port",
932			data: &tracesdk.SpanSnapshot{
933				SpanKind: trace.SpanKindProducer,
934				Attributes: []attribute.KeyValue{
935					semconv.NetPeerIPKey.String("1.2.3.4"),
936					semconv.NetPeerPortKey.Int(9876),
937				},
938			},
939			want: &zkmodel.Endpoint{
940				IPv4: net.ParseIP("1.2.3.4"),
941				Port: 9876,
942			},
943		},
944	}
945	for _, tt := range tests {
946		t.Run(tt.name, func(t *testing.T) {
947			got := toZipkinRemoteEndpoint(tt.data)
948			if diff := cmp.Diff(got, tt.want); diff != "" {
949				t.Errorf("Diff%v", diff)
950			}
951		})
952	}
953}
954
955func TestServiceName(t *testing.T) {
956	attrs := []attribute.KeyValue{}
957	assert.Equal(t, defaultServiceName, getServiceName(attrs))
958
959	attrs = append(attrs, attribute.String("test_key", "test_value"))
960	assert.Equal(t, defaultServiceName, getServiceName(attrs))
961
962	attrs = append(attrs, semconv.ServiceNameKey.String("my_service"))
963	assert.Equal(t, "my_service", getServiceName(attrs))
964}
965