Skip to content

Commit

Permalink
enable otel span link system tests for golang (#2489)
Browse files Browse the repository at this point in the history
* enable span link system tests for golang & seperate span link attribute validations into its own test
  • Loading branch information
khanayan123 authored May 28, 2024
1 parent 309bf6d commit 86e0e8f
Show file tree
Hide file tree
Showing 2 changed files with 171 additions and 66 deletions.
66 changes: 50 additions & 16 deletions tests/parametric/test_otel_span_methods.py
Original file line number Diff line number Diff line change
Expand Up @@ -404,8 +404,8 @@ def test_otel_set_attributes_separately(self, test_agent, test_library):

@missing_feature(context.library == "dotnet", reason="Not implemented")
@missing_feature(context.library < "[email protected]", reason="Implemented in 1.26.0")
@missing_feature(context.library == "golang", reason="Not implemented")
@missing_feature(context.library < "[email protected]", reason="Implemented in 3.48.0, 4.27.0, and 5.3.0")
@missing_feature(context.library < "[email protected]", reason="Implemented in 1.61.0")
@missing_feature(context.library == "ruby", reason="Not implemented")
@missing_feature(context.library == "php", reason="Not implemented")
def test_otel_span_started_with_link_from_another_span(self, test_agent, test_library):
Expand Down Expand Up @@ -440,15 +440,11 @@ def test_otel_span_started_with_link_from_another_span(self, test_agent, test_li
assert link.get("trace_id") == root.get("trace_id")
root_tid = root["meta"].get("_dd.p.tid") or "0" if "meta" in root else "0"
assert (link.get("trace_id_high") or 0) == int(root_tid, 16)
assert link["attributes"].get("foo") == "bar"
assert link["attributes"].get("array.0") == "a"
assert link["attributes"].get("array.1") == "b"
assert link["attributes"].get("array.2") == "c"

@missing_feature(context.library == "dotnet", reason="Not implemented")
@missing_feature(context.library < "[email protected]", reason="Implemented in 1.26.0")
@missing_feature(context.library == "golang", reason="Not implemented")
@missing_feature(context.library < "[email protected]", reason="Implemented in 3.48.0, 4.27.0, and 5.3.0")
@missing_feature(context.library < "[email protected]", reason="Implemented in 1.61.0")
@missing_feature(context.library < "[email protected]", reason="Not implemented")
@missing_feature(context.library == "php", reason="Not implemented")
def test_otel_span_started_with_link_from_datadog_headers(self, test_agent, test_library):
Expand Down Expand Up @@ -494,12 +490,11 @@ def test_otel_span_started_with_link_from_datadog_headers(self, test_agent, test
assert link.get("flags") == 1 | TRACECONTEXT_FLAGS_SET

assert len(link.get("attributes")) == 1
assert link["attributes"].get("foo") == "bar"

@missing_feature(context.library == "dotnet", reason="Not implemented")
@missing_feature(context.library < "[email protected]", reason="Implemented in 1.28.0")
@missing_feature(context.library == "golang", reason="Not implemented")
@missing_feature(context.library < "[email protected]", reason="Implemented in 3.48.0, 4.27.0, and 5.3.0")
@missing_feature(context.library < "[email protected]", reason="Implemented in 1.61.0")
@missing_feature(context.library < "[email protected]", reason="Not implemented")
@missing_feature(context.library == "php", reason="Not implemented")
def test_otel_span_started_with_link_from_w3c_headers(self, test_agent, test_library):
Expand Down Expand Up @@ -541,15 +536,59 @@ def test_otel_span_started_with_link_from_w3c_headers(self, test_agent, test_lib
assert "s:2" in tracestateDD
assert "t.dm:-4" in tracestateDD

assert link.get("flags") == 1 | TRACECONTEXT_FLAGS_SET
assert link.get("attributes") is None
assert link.get("flags") == 1 | TRACECONTEXT_FLAGS_SET or TRACECONTEXT_FLAGS_SET
assert link.get("attributes") is None or len(link.get("attributes")) == 0

@missing_feature(context.library == "dotnet", reason="Not implemented")
@missing_feature(context.library < "[email protected]", reason="Implemented in 1.26.0")
@missing_feature(context.library == "golang", reason="Not implemented")
@missing_feature(context.library < "[email protected]", reason="Implemented in 3.48.0, 4.27.0, and 5.3.0")
@missing_feature(context.library < "[email protected]", reason="Not implemented")
@missing_feature(context.library == "php", reason="Not implemented")
def test_otel_span_link_attribute_handling(self, test_agent, test_library):
"""Test that span links implementations correctly handle attributes according to spec.
"""
with test_library:
with test_library.otel_start_span(
"root",
links=[
Link(
http_headers=[
["x-datadog-trace-id", "1234567890"],
["x-datadog-parent-id", "9876543210"],
["x-datadog-sampling-priority", "2"],
["x-datadog-origin", "synthetics"],
["x-datadog-tags", "_dd.p.dm=-4,_dd.p.tid=0000000000000010"],
],
attributes={"foo": "bar", "array": ["a", "b", "c"], "bools": [True, False], "nested": [1, 2]},
)
],
) as span:
span.end_span()

span = get_span(test_agent)
span_links = retrieve_span_links(span)
assert span_links is not None
assert len(span_links) == 1

link = span_links[0]

assert len(link.get("attributes")) == 8
assert link["attributes"].get("foo") == "bar"
assert link["attributes"].get("bools.0") == "true"
assert link["attributes"].get("bools.1") == "false"
assert link["attributes"].get("nested.0") == "1"
assert link["attributes"].get("nested.1") == "2"
assert link["attributes"].get("array.0") == "a"
assert link["attributes"].get("array.1") == "b"
assert link["attributes"].get("array.2") == "c"

@missing_feature(context.library == "dotnet", reason="Not implemented")
@missing_feature(context.library < "[email protected]", reason="Implemented in 1.26.0")
@missing_feature(context.library < "[email protected]", reason="Implemented in 1.61.0")
@missing_feature(context.library == "nodejs", reason="Not implemented")
@missing_feature(context.library < "[email protected]", reason="Not implemented")
@missing_feature(context.library == "php", reason="Not implemented")
def test_otel_span_started_with_link_from_other_spans(self, test_agent, test_library):
"""Test adding a span link from a span to another span.
"""
Expand Down Expand Up @@ -587,19 +626,14 @@ def test_otel_span_started_with_link_from_other_spans(self, test_agent, test_lib
assert link.get("span_id") == root.get("span_id")
assert link.get("trace_id") == root.get("trace_id")
assert link.get("trace_id_high") == int(root_tid, 16)
assert link.get("attributes") is None
assert link.get("attributes") is None or len(link.get("attributes")) == 0
# Tracestate is not required, but if it is present, it must contain the linked span's tracestate
assert link.get("tracestate") is None or "dd=" in link.get("tracestate")

link = span_links[1]
assert link.get("span_id") == first.get("span_id")
assert link.get("trace_id") == first.get("trace_id")
assert link.get("trace_id_high") == int(root_tid, 16)
assert len(link.get("attributes")) == 4
assert link["attributes"].get("bools.0") == "true"
assert link["attributes"].get("bools.1") == "false"
assert link["attributes"].get("nested.0") == "1"
assert link["attributes"].get("nested.1") == "2"
assert link.get("tracestate") is None or "dd=" in link.get("tracestate")

@missing_feature(context.library < "[email protected]", reason="Implemented in 1.24.1")
Expand Down
171 changes: 121 additions & 50 deletions utils/build/docker/golang/parametric/otel.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package main

import (
"context"
"encoding/binary"
"fmt"
"strconv"
"strings"
Expand All @@ -10,10 +11,80 @@ import (
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
otel_trace "go.opentelemetry.io/otel/trace"
"gopkg.in/DataDog/dd-trace-go.v1/ddtrace"
ddotel "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/opentelemetry"
"gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer"
)

func ConvertKeyValsToAttributes(keyVals map[string]*ListVal) map[string][]attribute.KeyValue {
attributes := make([]attribute.KeyValue, 0, len(keyVals))
attributesStringified := make([]attribute.KeyValue, 0, len(keyVals))
for k, lv := range keyVals {
n := len(lv.GetVal())
if n == 0 {
continue
}
// all values are represented as slices
first := lv.GetVal()[0]
switch first.Val.(type) {
case *AttrVal_StringVal:
inp := make([]string, n)
for i, v := range lv.GetVal() {
inp[i] = v.GetStringVal()
}
attributesStringified = append(attributesStringified, attribute.String(k, "["+strings.Join(inp, ", ")+"]"))
if len(inp) > 1 {
attributes = append(attributes, attribute.StringSlice(k, inp))
} else {
attributes = append(attributes, attribute.String(k, inp[0]))
}
case *AttrVal_BoolVal:
inp := make([]bool, n)
stringifiedInp := make([]string, n)
for i, v := range lv.GetVal() {
inp[i] = v.GetBoolVal()
stringifiedInp[i] = strconv.FormatBool(v.GetBoolVal())
}
attributesStringified = append(attributesStringified, attribute.String(k, "["+strings.Join(stringifiedInp, ", ")+"]"))
if len(inp) > 1 {
attributes = append(attributes, attribute.BoolSlice(k, inp))
} else {
attributes = append(attributes, attribute.Bool(k, inp[0]))
}
case *AttrVal_DoubleVal:
inp := make([]float64, n)
stringifiedInp := make([]string, n)
for i, v := range lv.GetVal() {
inp[i] = v.GetDoubleVal()
stringifiedInp[i] = strconv.FormatFloat(v.GetDoubleVal(), 'f', -1, 64)
}
attributesStringified = append(attributesStringified, attribute.String(k, "["+strings.Join(stringifiedInp, ", ")+"]"))
if len(inp) > 1 {
attributes = append(attributes, attribute.Float64Slice(k, inp))
} else {
attributes = append(attributes, attribute.Float64(k, inp[0]))
}
case *AttrVal_IntegerVal:
inp := make([]int64, n)
stringifiedInp := make([]string, n)
for i, v := range lv.GetVal() {
inp[i] = v.GetIntegerVal()
stringifiedInp[i] = strconv.FormatInt(v.GetIntegerVal(), 10)
}
attributesStringified = append(attributesStringified, attribute.String(k, "["+strings.Join(stringifiedInp, ", ")+"]"))
if len(inp) > 1 {
attributes = append(attributes, attribute.Int64Slice(k, inp))
} else {
attributes = append(attributes, attribute.Int64(k, inp[0]))
}
}
}
return map[string][]attribute.KeyValue{
"0": attributes,
"1": attributesStringified,
}
}

func (s *apmClientServer) OtelStartSpan(ctx context.Context, args *OtelStartSpanArgs) (*OtelStartSpanReturn, error) {
if s.tracer == nil {
s.tracer = s.tp.Tracer("")
Expand All @@ -35,56 +106,7 @@ func (s *apmClientServer) OtelStartSpan(ctx context.Context, args *OtelStartSpan
otelOpts = append(otelOpts, otel_trace.WithTimestamp(tm))
}
if args.GetAttributes() != nil {
for k, lv := range args.GetAttributes().KeyVals {
n := len(lv.GetVal())
if n == 0 {
continue
}
// all values are represented as slices
first := lv.GetVal()[0]
switch first.Val.(type) {
case *AttrVal_StringVal:
inp := make([]string, n)
for i, v := range lv.GetVal() {
inp[i] = v.GetStringVal()
}
if len(inp) > 1 {
otelOpts = append(otelOpts, otel_trace.WithAttributes(attribute.StringSlice(k, inp)))
} else {
otelOpts = append(otelOpts, otel_trace.WithAttributes(attribute.String(k, inp[0])))
}
case *AttrVal_BoolVal:
inp := make([]bool, n)
for i, v := range lv.GetVal() {
inp[i] = v.GetBoolVal()
}
if len(inp) > 1 {
otelOpts = append(otelOpts, otel_trace.WithAttributes(attribute.BoolSlice(k, inp)))
} else {
otelOpts = append(otelOpts, otel_trace.WithAttributes(attribute.Bool(k, inp[0])))
}
case *AttrVal_DoubleVal:
inp := make([]float64, n)
for i, v := range lv.GetVal() {
inp[i] = v.GetDoubleVal()
}
if len(inp) > 1 {
otelOpts = append(otelOpts, otel_trace.WithAttributes(attribute.Float64Slice(k, inp)))
} else {
otelOpts = append(otelOpts, otel_trace.WithAttributes(attribute.Float64(k, inp[0])))
}
case *AttrVal_IntegerVal:
inp := make([]int64, n)
for i, v := range lv.GetVal() {
inp[i] = v.GetIntegerVal()
}
if len(inp) > 1 {
otelOpts = append(otelOpts, otel_trace.WithAttributes(attribute.Int64Slice(k, inp)))
} else {
otelOpts = append(otelOpts, otel_trace.WithAttributes(attribute.Int64(k, inp[0])))
}
}
}
otelOpts = append(otelOpts, otel_trace.WithAttributes(ConvertKeyValsToAttributes(args.GetAttributes().KeyVals)["0"]...))
}
if args.GetHttpHeaders() != nil && len(args.HttpHeaders.HttpHeaders) != 0 {
headers := map[string]string{}
Expand All @@ -102,18 +124,67 @@ func (s *apmClientServer) OtelStartSpan(ctx context.Context, args *OtelStartSpan
ddOpts = append(ddOpts, tracer.ChildOf(sctx))
}
}

if links := args.GetSpanLinks(); links != nil {
for _, link := range links {
switch from := link.From.(type) {
case *SpanLink_ParentId:
if _, ok := s.otelSpans[from.ParentId]; ok {
otelOpts = append(otelOpts, otel_trace.WithLinks(otel_trace.Link{SpanContext: s.otelSpans[from.ParentId].span.SpanContext(), Attributes: ConvertKeyValsToAttributes(link.GetAttributes().KeyVals)["1"]}))
}
case *SpanLink_HttpHeaders:
headers := map[string]string{}
for _, headerTuple := range from.HttpHeaders.HttpHeaders {
k := headerTuple.GetKey()
v := headerTuple.GetValue()
if k != "" && v != "" {
headers[k] = v
}
}
extractedContext, _ := tracer.NewPropagator(nil).Extract(tracer.TextMapCarrier(headers))
state, _ := otel_trace.ParseTraceState(headers["tracestate"])

var traceID otel_trace.TraceID
var spanID otel_trace.SpanID
if w3cCtx, ok := extractedContext.(ddtrace.SpanContextW3C); ok {
traceID = w3cCtx.TraceID128Bytes()
} else {
fmt.Printf("Non-W3C context found in span, unable to get full 128 bit trace id")
uint64ToByte(extractedContext.TraceID(), traceID[:])
}
uint64ToByte(extractedContext.SpanID(), spanID[:])
config := otel_trace.SpanContextConfig{
TraceID: traceID,
SpanID: spanID,
TraceState: state,
}
var newCtx = otel_trace.NewSpanContext(config)
otelOpts = append(otelOpts, otel_trace.WithLinks(otel_trace.Link{
SpanContext: newCtx,
Attributes: ConvertKeyValsToAttributes(link.GetAttributes().KeyVals)["1"],
}))
}

}
}

ctx, span := s.tracer.Start(ddotel.ContextWithStartOptions(pCtx, ddOpts...), args.Name, otelOpts...)
hexSpanId := hex2int(span.SpanContext().SpanID().String())
s.otelSpans[hexSpanId] = spanContext{
span: span,
ctx: ctx,
}

return &OtelStartSpanReturn{
SpanId: hexSpanId,
TraceId: hex2int(span.SpanContext().TraceID().String()),
}, nil
}

func uint64ToByte(n uint64, b []byte) {
binary.BigEndian.PutUint64(b, n)
}

func (s *apmClientServer) OtelEndSpan(ctx context.Context, args *OtelEndSpanArgs) (*OtelEndSpanReturn, error) {
sctx, ok := s.otelSpans[args.Id]
if !ok {
Expand Down

0 comments on commit 86e0e8f

Please sign in to comment.