Added tests for Haystack propagation support
MODIFY
dd-trace-ot/src/main/java/datadog/opentracing/propagation/HaystackHttpCodec.java
dd-trace-ot/src/main/java/datadog/opentracing/propagation/HaystackHttpCodec.java
package datadog.opentracing.propagation;
+import static datadog.opentracing.propagation.HttpCodec.ZERO;
+import static datadog.opentracing.propagation.HttpCodec.validateUInt64BitsID;
+
import datadog.opentracing.DDSpanContext;
import datadog.trace.api.sampling.PrioritySampling;
import io.opentracing.SpanContext;
import io.opentracing.propagation.TextMap;
-import lombok.extern.slf4j.Slf4j;
-
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
-
-import static datadog.opentracing.propagation.HttpCodec.ZERO;
-import static datadog.opentracing.propagation.HttpCodec.validateUInt64BitsID;
+import lombok.extern.slf4j.Slf4j;
/**
+ * A codec designed for HTTP transport via headers using Haystack headers.
+ *
* @author Alex Antonov
*/
@Slf4j
@@ -46,6 +47,7 @@ public class HaystackHttpCodec {
public static class Extractor implements HttpCodec.Extractor {
private final Map<String, String> taggedHeaders;
+ /** Creates Header Extractor using Haystack propagation. */
public Extractor(final Map<String, String> taggedHeaders) {
this.taggedHeaders = new HashMap<>();
for (final Map.Entry<String, String> mapping : taggedHeaders.entrySet()) {
@@ -75,11 +77,11 @@ public class HaystackHttpCodec {
traceId = validateUInt64BitsID(value, 10);
} else if (SPAN_ID_KEY.equalsIgnoreCase(key)) {
spanId = validateUInt64BitsID(value, 10);
- } else if (key.startsWith(OT_BAGGAGE_PREFIX)) {
+ } else if (key.startsWith(OT_BAGGAGE_PREFIX.toLowerCase())) {
if (baggage.isEmpty()) {
baggage = new HashMap<>();
}
- baggage.put(key.replace(OT_BAGGAGE_PREFIX, ""), HttpCodec.decode(value));
+ baggage.put(key.replace(OT_BAGGAGE_PREFIX.toLowerCase(), ""), HttpCodec.decode(value));
}
if (taggedHeaders.containsKey(key)) {

ADD
dd-trace-ot/src/test/groovy/datadog/opentracing/propagation/HaystackHttpExtractorTest.groovy
+package datadog.opentracing.propagation
+
+import datadog.trace.api.sampling.PrioritySampling
+import io.opentracing.SpanContext
+import io.opentracing.propagation.TextMapExtractAdapter
+import spock.lang.Specification
+
+import static datadog.opentracing.propagation.HaystackHttpCodec.OT_BAGGAGE_PREFIX
+import static datadog.opentracing.propagation.HaystackHttpCodec.SPAN_ID_KEY
+import static datadog.opentracing.propagation.HaystackHttpCodec.TRACE_ID_KEY
+import static datadog.opentracing.propagation.HttpCodec.UINT64_MAX
+
+class HaystackHttpExtractorTest extends Specification {
+
+ HttpCodec.Extractor extractor = new HaystackHttpCodec.Extractor(["SOME_HEADER": "some-tag"])
+
+ def "extract http headers"() {
+ setup:
+ def headers = [
+ (TRACE_ID_KEY.toUpperCase()) : traceId,
+ (SPAN_ID_KEY.toUpperCase()) : spanId,
+ (OT_BAGGAGE_PREFIX.toUpperCase() + "k1"): "v1",
+ (OT_BAGGAGE_PREFIX.toUpperCase() + "k2"): "v2",
+ SOME_HEADER : "my-interesting-info",
+ ]
+
+ when:
+ final ExtractedContext context = extractor.extract(new TextMapExtractAdapter(headers))
+
+ then:
+ context.traceId == traceId
+ context.spanId == spanId
+ context.baggage == ["k1": "v1", "k2": "v2"]
+ context.tags == ["some-tag": "my-interesting-info"]
+ context.samplingPriority == samplingPriority
+ context.origin == origin
+
+ where:
+ traceId | spanId | samplingPriority | origin
+ "1" | "2" | PrioritySampling.SAMPLER_KEEP | null
+ "2" | "3" | PrioritySampling.SAMPLER_KEEP | null
+ UINT64_MAX.toString() | UINT64_MAX.minus(1).toString() | PrioritySampling.SAMPLER_KEEP | null
+ UINT64_MAX.minus(1).toString() | UINT64_MAX.toString() | PrioritySampling.SAMPLER_KEEP | null
+ }
+
+ def "extract header tags with no propagation"() {
+ when:
+ TagContext context = extractor.extract(new TextMapExtractAdapter(headers))
+
+ then:
+ !(context instanceof ExtractedContext)
+ context.getTags() == ["some-tag": "my-interesting-info"]
+
+
+ where:
+ headers | _
+ [SOME_HEADER: "my-interesting-info"] | _
+ }
+
+ def "extract empty headers returns null"() {
+ expect:
+ extractor.extract(new TextMapExtractAdapter(["ignored-header": "ignored-value"])) == null
+ }
+
+ def "extract http headers with invalid non-numeric ID"() {
+ setup:
+ def headers = [
+ (TRACE_ID_KEY.toUpperCase()) : "traceId",
+ (SPAN_ID_KEY.toUpperCase()) : "spanId",
+ (OT_BAGGAGE_PREFIX.toUpperCase() + "k1"): "v1",
+ (OT_BAGGAGE_PREFIX.toUpperCase() + "k2"): "v2",
+ SOME_HEADER : "my-interesting-info",
+ ]
+
+ when:
+ SpanContext context = extractor.extract(new TextMapExtractAdapter(headers))
+
+ then:
+ context == null
+ }
+
+ def "extract http headers with out of range trace ID"() {
+ setup:
+ String outOfRangeTraceId = UINT64_MAX.add(BigInteger.ONE).toString()
+ def headers = [
+ (TRACE_ID_KEY.toUpperCase()) : outOfRangeTraceId,
+ (SPAN_ID_KEY.toUpperCase()) : "0",
+ (OT_BAGGAGE_PREFIX.toUpperCase() + "k1"): "v1",
+ (OT_BAGGAGE_PREFIX.toUpperCase() + "k2"): "v2",
+ SOME_HEADER : "my-interesting-info",
+ ]
+
+ when:
+ SpanContext context = extractor.extract(new TextMapExtractAdapter(headers))
+
+ then:
+ context == null
+ }
+
+ def "extract http headers with out of range span ID"() {
+ setup:
+ def headers = [
+ (TRACE_ID_KEY.toUpperCase()) : "0",
+ (SPAN_ID_KEY.toUpperCase()) : "-1",
+ (OT_BAGGAGE_PREFIX.toUpperCase() + "k1"): "v1",
+ (OT_BAGGAGE_PREFIX.toUpperCase() + "k2"): "v2",
+ SOME_HEADER : "my-interesting-info",
+ ]
+
+ when:
+ SpanContext context = extractor.extract(new TextMapExtractAdapter(headers))
+
+ then:
+ context == null
+ }
+
+ def "more ID range validation"() {
+ setup:
+ def headers = [
+ (TRACE_ID_KEY.toUpperCase()): traceId,
+ (SPAN_ID_KEY.toUpperCase()) : spanId,
+ ]
+
+ when:
+ final ExtractedContext context = extractor.extract(new TextMapExtractAdapter(headers))
+
+ then:
+ if (expectedTraceId) {
+ assert context.traceId == expectedTraceId
+ assert context.spanId == expectedSpanId
+ } else {
+ assert context == null
+ }
+
+ where:
+ gtTraceId | gSpanId | expectedTraceId | expectedSpanId
+ "-1" | "1" | null | "0"
+ "1" | "-1" | null | "0"
+ "0" | "1" | null | "0"
+ "1" | "0" | "1" | "0"
+ "$UINT64_MAX" | "1" | "$UINT64_MAX" | "1"
+ "${UINT64_MAX.plus(1)}" | "1" | null | "1"
+ "1" | "$UINT64_MAX" | "1" | "$UINT64_MAX"
+ "1" | "${UINT64_MAX.plus(1)}" | null | "0"
+
+ traceId = gtTraceId.toString()
+ spanId = gSpanId.toString()
+ }
+}

ADD
dd-trace-ot/src/test/groovy/datadog/opentracing/propagation/HaystackHttpInjectorTest.groovy
+package datadog.opentracing.propagation
+
+import datadog.opentracing.DDSpanContext
+import datadog.opentracing.DDTracer
+import datadog.opentracing.PendingTrace
+import datadog.trace.api.sampling.PrioritySampling
+import datadog.trace.common.writer.ListWriter
+import io.opentracing.propagation.TextMapInjectAdapter
+import spock.lang.Specification
+
+import static datadog.opentracing.propagation.HaystackHttpCodec.OT_BAGGAGE_PREFIX
+import static datadog.opentracing.propagation.HaystackHttpCodec.SPAN_ID_KEY
+import static datadog.opentracing.propagation.HaystackHttpCodec.TRACE_ID_KEY
+import static datadog.opentracing.propagation.HttpCodec.UINT64_MAX
+
+class HaystackHttpInjectorTest extends Specification {
+
+ HttpCodec.Injector injector = new HaystackHttpCodec.Injector()
+
+ def "inject http headers"() {
+ setup:
+ def writer = new ListWriter()
+ def tracer = new DDTracer(writer)
+ final DDSpanContext mockedContext =
+ new DDSpanContext(
+ traceId,
+ spanId,
+ "0",
+ "fakeService",
+ "fakeOperation",
+ "fakeResource",
+ samplingPriority,
+ origin,
+ new HashMap<String, String>() {
+ {
+ put("k1", "v1")
+ put("k2", "v2")
+ }
+ },
+ false,
+ "fakeType",
+ null,
+ new PendingTrace(tracer, "1", [:]),
+ tracer)
+
+ final Map<String, String> carrier = Mock()
+
+ when:
+ injector.inject(mockedContext, new TextMapInjectAdapter(carrier))
+
+ then:
+ 1 * carrier.put(TRACE_ID_KEY, traceId)
+ 1 * carrier.put(SPAN_ID_KEY, spanId)
+ 1 * carrier.put(OT_BAGGAGE_PREFIX + "k1", "v1")
+ 1 * carrier.put(OT_BAGGAGE_PREFIX + "k2", "v2")
+
+
+
+ where:
+ traceId | spanId | samplingPriority | origin
+ "1" | "2" | PrioritySampling.SAMPLER_KEEP | null
+ "1" | "2" | PrioritySampling.SAMPLER_KEEP | null
+ UINT64_MAX.toString() | UINT64_MAX.minus(1).toString() | PrioritySampling.SAMPLER_KEEP | null
+ UINT64_MAX.minus(1).toString() | UINT64_MAX.toString() | PrioritySampling.SAMPLER_KEEP | null
+ }
+}

Added tests for Haystack propagation support