Skip to content

Commit 44969b7

Browse files
authored
chore(tracing): Add tracing propagation tests (#4287)
This adds tests for the tracing propagation service layer. This will help a future PR that modifies this to use the upstream tracing propagation. Signed-off-by: Scott Fleener <[email protected]>
1 parent 9329994 commit 44969b7

File tree

4 files changed

+186
-1
lines changed

4 files changed

+186
-1
lines changed

Cargo.lock

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2581,10 +2581,14 @@ dependencies = [
25812581
"hex",
25822582
"http",
25832583
"linkerd-error",
2584+
"linkerd-http-box",
25842585
"linkerd-stack",
2586+
"linkerd-tracing",
25852587
"rand 0.9.2",
25862588
"thiserror",
2589+
"tokio",
25872590
"tower",
2591+
"tower-test",
25882592
"tracing",
25892593
]
25902594

linkerd/trace-context/Cargo.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,9 @@ rand = "0.9"
1818
thiserror = "2"
1919
tower = { workspace = true, default-features = false, features = ["util"] }
2020
tracing = { workspace = true }
21+
22+
[dev-dependencies]
23+
linkerd-http-box = { path = "../http/box" }
24+
linkerd-tracing = { path = "../tracing" }
25+
tokio = { version = "1", features = ["test-util"] }
26+
tower-test = { workspace = true }

linkerd/trace-context/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use thiserror::Error;
1616

1717
const SPAN_ID_LEN: usize = 8;
1818

19-
#[derive(Debug, Default)]
19+
#[derive(Debug, Default, Eq, PartialEq)]
2020
pub struct Id(Vec<u8>);
2121

2222
#[derive(Debug, Error)]

linkerd/trace-context/src/service.rs

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,3 +212,178 @@ where
212212
Either::Left(self.inner.call(req))
213213
}
214214
}
215+
216+
#[cfg(test)]
217+
mod tests {
218+
use super::*;
219+
use crate::Id;
220+
use bytes::Bytes;
221+
use http::HeaderMap;
222+
use linkerd_error::Error;
223+
use linkerd_http_box::BoxBody;
224+
use std::collections::BTreeMap;
225+
use tokio::sync::mpsc;
226+
use tower::{Layer, Service, ServiceExt};
227+
228+
const W3C_TRACEPARENT_HEADER: &str = "traceparent";
229+
const B3_TRACE_ID_HEADER: &str = "x-b3-traceid";
230+
const B3_SPAN_ID_HEADER: &str = "x-b3-spanid";
231+
const B3_SAMPLED_HEADER: &str = "x-b3-sampled";
232+
233+
#[tokio::test(flavor = "current_thread")]
234+
async fn w3c_propagation() {
235+
let _trace = linkerd_tracing::test::trace_init();
236+
237+
let (req_headers, exported_span) = send_mock_request(
238+
http::Request::builder()
239+
.header(
240+
W3C_TRACEPARENT_HEADER,
241+
"00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01",
242+
)
243+
.body(BoxBody::empty())
244+
.expect("request"),
245+
)
246+
.await;
247+
248+
assert!(req_headers.get(W3C_TRACEPARENT_HEADER).is_some());
249+
assert!(req_headers.get(B3_TRACE_ID_HEADER).is_none());
250+
assert!(req_headers.get(B3_SPAN_ID_HEADER).is_none());
251+
assert!(req_headers.get(B3_SAMPLED_HEADER).is_none());
252+
253+
assert_eq!(
254+
exported_span.trace_id,
255+
Id::from(Bytes::from(
256+
hex::decode("4bf92f3577b34da6a3ce929d0e0e4736").expect("decode")
257+
)),
258+
);
259+
assert_eq!(
260+
exported_span.parent_id,
261+
Id::from(Bytes::from(
262+
hex::decode("00f067aa0ba902b7").expect("decode")
263+
)),
264+
);
265+
assert_ne!(
266+
exported_span.span_id,
267+
Id::from(Bytes::from(
268+
hex::decode("00f067aa0ba902b7").expect("decode")
269+
)),
270+
);
271+
}
272+
273+
#[tokio::test(flavor = "current_thread")]
274+
async fn b3_propagation() {
275+
let _trace = linkerd_tracing::test::trace_init();
276+
277+
let (req_headers, exported_span) = send_mock_request(
278+
http::Request::builder()
279+
.header(B3_TRACE_ID_HEADER, "4bf92f3577b34da6a3ce929d0e0e4736")
280+
.header(B3_SPAN_ID_HEADER, "00f067aa0ba902b7")
281+
.header(B3_SAMPLED_HEADER, "1")
282+
.body(BoxBody::empty())
283+
.expect("request"),
284+
)
285+
.await;
286+
287+
assert!(req_headers.get(W3C_TRACEPARENT_HEADER).is_none());
288+
assert!(req_headers.get(B3_TRACE_ID_HEADER).is_some());
289+
assert!(req_headers.get(B3_SPAN_ID_HEADER).is_some());
290+
assert!(req_headers.get(B3_SAMPLED_HEADER).is_some());
291+
292+
assert_eq!(
293+
exported_span.trace_id,
294+
Id::from(Bytes::from(
295+
hex::decode("4bf92f3577b34da6a3ce929d0e0e4736").expect("decode")
296+
)),
297+
);
298+
assert_eq!(
299+
exported_span.parent_id,
300+
Id::from(Bytes::from(
301+
hex::decode("00f067aa0ba902b7").expect("decode")
302+
)),
303+
);
304+
assert_ne!(
305+
exported_span.span_id,
306+
Id::from(Bytes::from(
307+
hex::decode("00f067aa0ba902b7").expect("decode")
308+
)),
309+
);
310+
}
311+
312+
#[tokio::test(flavor = "current_thread")]
313+
async fn trace_labels() {
314+
let _trace = linkerd_tracing::test::trace_init();
315+
316+
let (_, exported_span) = send_mock_request(
317+
http::Request::builder()
318+
.uri("http://example.com:80/foo?bar=baz")
319+
.header(
320+
W3C_TRACEPARENT_HEADER,
321+
"00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01",
322+
)
323+
.header("user-agent", "tokio-test")
324+
.header("content-length", "0")
325+
.header("content-type", "text/plain")
326+
.header("l5d-orig-proto", "HTTP/1.1")
327+
.body(BoxBody::empty())
328+
.expect("request"),
329+
)
330+
.await;
331+
332+
let labels = exported_span.labels.into_iter().collect::<BTreeMap<_, _>>();
333+
assert_eq!(
334+
labels,
335+
BTreeMap::from_iter([
336+
("http.request.header.content-length", "0".to_string()),
337+
("http.request.header.content-type", "text/plain".to_string()),
338+
("http.request.header.l5d-orig-proto", "HTTP/1.1".to_string()),
339+
("http.request.method", "GET".to_string()),
340+
("http.response.status_code", "200".to_string()),
341+
("network.transport", "tcp".to_string()),
342+
("url.full", "http://example.com:80/foo?bar=baz".to_string()),
343+
("url.path", "/foo".to_string()),
344+
("url.query", "bar=baz".to_string()),
345+
("url.scheme", "http".to_string()),
346+
("user_agent.original", "tokio-test".to_string())
347+
])
348+
)
349+
}
350+
351+
async fn send_mock_request(req: http::Request<BoxBody>) -> (HeaderMap, Span) {
352+
let (span_tx, mut span_rx) = mpsc::channel(1);
353+
354+
let (inner, mut handle) =
355+
tower_test::mock::pair::<http::Request<BoxBody>, http::Response<BoxBody>>();
356+
let mut stack = TraceContext::<TestSink, _>::layer(TestSink(span_tx)).layer(inner);
357+
handle.allow(1);
358+
359+
let stack = stack.ready().await.expect("ready");
360+
361+
let (_, req_headers): (http::Response<BoxBody>, _) = tokio::join! {
362+
stack.call(req).map(|res| res.expect("must not fail")),
363+
handle.next_request().map(|req| {
364+
let (req, tx) = req.expect("request");
365+
tx.send_response(http::Response::default());
366+
req.headers().clone()
367+
}),
368+
};
369+
370+
(
371+
req_headers,
372+
span_rx.try_recv().expect("must have exported span"),
373+
)
374+
}
375+
376+
#[derive(Clone)]
377+
struct TestSink(mpsc::Sender<Span>);
378+
379+
impl SpanSink for TestSink {
380+
fn is_enabled(&self) -> bool {
381+
true
382+
}
383+
384+
fn try_send(&mut self, span: Span) -> Result<(), Error> {
385+
self.0.try_send(span)?;
386+
Ok(())
387+
}
388+
}
389+
}

0 commit comments

Comments
 (0)