system-tests
system-tests copied to clipboard
apm_tracing_e2e: Add tracecontext smoke test
Description
This PR adds a new e2e test that passes in a traceparent header and asserts that its trace context is used by the instrumented application.
Motivation
Implementing tracecontext is a feature that our tracers must support end-to-end, so this provides an assurance that it does.
Reviewer checklist
- [ ] If this PR modifies anything else than strictly the default scenario, then add the
run-all-scenarioslabel (more info). - [ ] CI is green
- [ ] If not, failing jobs are not related to this change (and you are 100% sure about this statement)
Workflow
- ⚠️⚠️ Create your PR as draft
- Follow the style guidelines of this project (See how to easily lint the code)
- Work on you PR until the CI passes (if something not related to your task is failing, you can ignore it)
- Mark it as ready for review
Once your PR is reviewed, you can merge it! :heart:
When testing against the Python library, I was able to get this to work locally. I'm not sure if there's a better, more reliable way to test this. While this PR is high priority, I don't think this is a blocker for GA
I am going to leave here the progress I had, I opted in for a separate endpoint for the better clarity.
import json
from tests.apm_tracing_e2e.test_single_span import _get_spans_submitted, _assert_msg
from utils import weblog, scenarios, interfaces
from utils.parametric.spec.tracecontext import get_traceparent
@scenarios.apm_tracing_e2e_tracecontext
class Test_Tracecontext_Span:
"""This is a smoke test that exercises the W3C context propagation.
'/make_tracecontext_call' request flow:
- extracts context from the propagated headers,
- creates a span with extracted context,
- injects new span context into a map and returns it as text upon success
As a summary, successful request produces a span with injected context and
returns a map with newly generated W3C headers.
"""
def setup_tracecontext_span(self):
self.req = weblog.get(
"/make_tracecontext_call",
{
"shouldIndex": 1,
"name": "inherited_child",
"tracestate": "dd=s:2;o:system-tests;t.usr.id:baz64~~,othervendor=t61rcWkgMzE",
"traceparent": "00-00000000000000001111111111111111-2222222222222222-01",
})
def test_tracecontext_span(self):
# Assert the weblog server span was sent by the agent.
spans = _get_spans_submitted(self.req)
# we are expecting two spans, one of which is generated by weblog
assert 2 == len(spans), _assert_msg(2, len(spans), "Agent did not submit the spans we want!")
# Assert received span has properties from the passed headers
span = _get_span(spans, "inherited_child")
# 00000000000000001111111111111111 in integer form
assert span.get("traceID") == '1229782938247303441'
# 2222222222222222 in integer form
assert span.get("parentID") == '2459565876494606882'
data = json.loads(self.req.text)
# Assert only W3C headers were injected (no Datadog headers)
assert "traceparent" in data
assert "tracestate" in data
assert "x-datadog-parent-id" not in data
assert "x-datadog-sampling-priority" not in data
assert "x-datadog-tags" not in data
assert "x-datadog-trace-id" not in data
# Assert injected headers have
# - the same trace id, as passed through traceparent header before
# - different parent id, that is equal to the spanId of the received span
# - trace flags == 01,
traceparent = get_traceparent(data)
assert traceparent.trace_id == '00000000000000001111111111111111'
assert int(traceparent.parent_id, 16) == int(span.get('spanID'))
assert traceparent.trace_flags == '01'
# Assert all spans in the distributed trace were received from the backend
spans = interfaces.backend.assert_request_spans_exist(self.req, query_filter="")
assert 1 == len(spans), _assert_msg(1, len(spans))
def _get_span(spans, span_name):
for s in spans:
if s["name"] == span_name:
return s
return {}
mux.HandleFunc("/make_tracecontext_call", func(w http.ResponseWriter, r *http.Request) {
tracestate := r.URL.Query().Get("tracestate")
traceparent := r.URL.Query().Get("traceparent")
spanName := r.URL.Query().Get("name")
tags := []ddtracer.StartSpanOption{}
// We need to propagate the user agent header to retain the mapping between the system-tests/weblog request id
// and the traces/spans that will be generated below, so that we can reference to them in our tests.
// See https://github.com/DataDog/system-tests/blob/2d6ae4d5bf87d55855afd36abf36ee710e7d8b3c/utils/interfaces/_core.py#L156
userAgent := r.UserAgent()
tags = append(tags, ddtracer.Tag("http.useragent", userAgent))
if r.URL.Query().Get("shouldIndex") == "1" {
tags = append(tags,
ddtracer.Tag("_dd.filter.kept", 1),
ddtracer.Tag("_dd.filter.id", "system_tests_e2e"),
)
}
ctx, err := ddtracer.Extract(ddtracer.TextMapCarrier{
"tracestate": tracestate,
"traceparent": traceparent,
})
if err != nil {
log.Fatalln(err)
w.WriteHeader(500)
}
span := ddtracer.StartSpan(spanName, append(tags, ddtracer.ChildOf(ctx))...)
headers := ddtracer.TextMapCarrier{}
err = ddtracer.Inject(span.Context(), headers)
if err != nil {
log.Fatalln(err)
w.WriteHeader(500)
}
span.Finish()
ddtracer.Flush()
b, _ := json.Marshal(headers)
w.Write(b)
})