diff --git a/bottlecap/Cargo.lock b/bottlecap/Cargo.lock index 25837d6a1..c360692fe 100644 --- a/bottlecap/Cargo.lock +++ b/bottlecap/Cargo.lock @@ -507,12 +507,12 @@ dependencies = [ "itertools 0.14.0", "lazy_static", "libdd-capabilities", - "libdd-common 4.0.0", + "libdd-common 4.2.0", "libdd-trace-normalization 2.0.0", "libdd-trace-obfuscation", - "libdd-trace-protobuf 3.0.1", - "libdd-trace-stats 2.0.0", - "libdd-trace-utils 3.0.1", + "libdd-trace-protobuf 3.0.2", + "libdd-trace-stats 4.0.0", + "libdd-trace-utils 6.0.1", "libddwaf", "log", "mime", @@ -805,7 +805,7 @@ dependencies = [ "libdd-common 2.0.1", "libdd-data-pipeline", "libdd-telemetry", - "libdd-tinybytes 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libdd-tinybytes 1.1.0", "libdd-trace-utils 2.0.2", "lru", "opentelemetry", @@ -1868,8 +1868,8 @@ checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" [[package]] name = "libdd-capabilities" -version = "1.0.0" -source = "git+https://github.com/DataDog/libdatadog?rev=db05e1f8408a76075efb37ecec544d2e74217e57#db05e1f8408a76075efb37ecec544d2e74217e57" +version = "2.0.0" +source = "git+https://github.com/DataDog/libdatadog?rev=48da0d82cb32b43d4cdece35b794c9bcbc275a03#48da0d82cb32b43d4cdece35b794c9bcbc275a03" dependencies = [ "anyhow", "bytes", @@ -1879,14 +1879,15 @@ dependencies = [ [[package]] name = "libdd-capabilities-impl" -version = "1.0.0" -source = "git+https://github.com/DataDog/libdatadog?rev=db05e1f8408a76075efb37ecec544d2e74217e57#db05e1f8408a76075efb37ecec544d2e74217e57" +version = "2.0.0" +source = "git+https://github.com/DataDog/libdatadog?rev=48da0d82cb32b43d4cdece35b794c9bcbc275a03#48da0d82cb32b43d4cdece35b794c9bcbc275a03" dependencies = [ "bytes", "http 1.4.0", "http-body-util", "libdd-capabilities", - "libdd-common 4.0.0", + "libdd-common 4.2.0", + "tokio", ] [[package]] @@ -1922,8 +1923,8 @@ dependencies = [ [[package]] name = "libdd-common" -version = "4.0.0" -source = "git+https://github.com/DataDog/libdatadog?rev=db05e1f8408a76075efb37ecec544d2e74217e57#db05e1f8408a76075efb37ecec544d2e74217e57" +version = "4.2.0" +source = "git+https://github.com/DataDog/libdatadog?rev=48da0d82cb32b43d4cdece35b794c9bcbc275a03#48da0d82cb32b43d4cdece35b794c9bcbc275a03" dependencies = [ "anyhow", "bytes", @@ -1970,7 +1971,7 @@ dependencies = [ "libdd-ddsketch 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "libdd-dogstatsd-client", "libdd-telemetry", - "libdd-tinybytes 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libdd-tinybytes 1.1.0", "libdd-trace-protobuf 2.0.0", "libdd-trace-stats 1.0.3", "libdd-trace-utils 2.0.2", @@ -1996,7 +1997,7 @@ dependencies = [ [[package]] name = "libdd-ddsketch" version = "1.0.1" -source = "git+https://github.com/DataDog/libdatadog?rev=db05e1f8408a76075efb37ecec544d2e74217e57#db05e1f8408a76075efb37ecec544d2e74217e57" +source = "git+https://github.com/DataDog/libdatadog?rev=48da0d82cb32b43d4cdece35b794c9bcbc275a03#48da0d82cb32b43d4cdece35b794c9bcbc275a03" dependencies = [ "prost 0.14.3", ] @@ -2017,16 +2018,19 @@ dependencies = [ [[package]] name = "libdd-shared-runtime" -version = "0.1.0" -source = "git+https://github.com/DataDog/libdatadog?rev=db05e1f8408a76075efb37ecec544d2e74217e57#db05e1f8408a76075efb37ecec544d2e74217e57" +version = "1.0.0" +source = "git+https://github.com/DataDog/libdatadog?rev=48da0d82cb32b43d4cdece35b794c9bcbc275a03#48da0d82cb32b43d4cdece35b794c9bcbc275a03" dependencies = [ "async-trait", "futures", + "futures-util", "libdd-capabilities", - "libdd-common 4.0.0", + "libdd-capabilities-impl", + "libdd-common 4.2.0", "tokio", "tokio-util", "tracing", + "wasm-bindgen-futures", ] [[package]] @@ -2065,8 +2069,8 @@ dependencies = [ [[package]] name = "libdd-tinybytes" -version = "1.1.0" -source = "git+https://github.com/DataDog/libdatadog?rev=db05e1f8408a76075efb37ecec544d2e74217e57#db05e1f8408a76075efb37ecec544d2e74217e57" +version = "1.1.1" +source = "git+https://github.com/DataDog/libdatadog?rev=48da0d82cb32b43d4cdece35b794c9bcbc275a03#48da0d82cb32b43d4cdece35b794c9bcbc275a03" dependencies = [ "serde", ] @@ -2084,25 +2088,24 @@ dependencies = [ [[package]] name = "libdd-trace-normalization" version = "2.0.0" -source = "git+https://github.com/DataDog/libdatadog?rev=db05e1f8408a76075efb37ecec544d2e74217e57#db05e1f8408a76075efb37ecec544d2e74217e57" +source = "git+https://github.com/DataDog/libdatadog?rev=48da0d82cb32b43d4cdece35b794c9bcbc275a03#48da0d82cb32b43d4cdece35b794c9bcbc275a03" dependencies = [ "anyhow", - "libdd-trace-protobuf 3.0.1", + "libdd-trace-protobuf 3.0.2", ] [[package]] name = "libdd-trace-obfuscation" -version = "2.0.0" -source = "git+https://github.com/DataDog/libdatadog?rev=db05e1f8408a76075efb37ecec544d2e74217e57#db05e1f8408a76075efb37ecec544d2e74217e57" +version = "3.1.0" +source = "git+https://github.com/DataDog/libdatadog?rev=48da0d82cb32b43d4cdece35b794c9bcbc275a03#48da0d82cb32b43d4cdece35b794c9bcbc275a03" dependencies = [ "anyhow", "fluent-uri", - "libdd-common 4.0.0", - "libdd-trace-protobuf 3.0.1", - "libdd-trace-utils 3.0.1", + "libdd-common 4.2.0", + "libdd-trace-protobuf 3.0.2", + "libdd-trace-utils 6.0.1", "log", "percent-encoding", - "regex", "serde", "serde_json", ] @@ -2120,8 +2123,8 @@ dependencies = [ [[package]] name = "libdd-trace-protobuf" -version = "3.0.1" -source = "git+https://github.com/DataDog/libdatadog?rev=db05e1f8408a76075efb37ecec544d2e74217e57#db05e1f8408a76075efb37ecec544d2e74217e57" +version = "3.0.2" +source = "git+https://github.com/DataDog/libdatadog?rev=48da0d82cb32b43d4cdece35b794c9bcbc275a03#48da0d82cb32b43d4cdece35b794c9bcbc275a03" dependencies = [ "prost 0.14.3", "serde", @@ -2142,20 +2145,22 @@ dependencies = [ [[package]] name = "libdd-trace-stats" -version = "2.0.0" -source = "git+https://github.com/DataDog/libdatadog?rev=db05e1f8408a76075efb37ecec544d2e74217e57#db05e1f8408a76075efb37ecec544d2e74217e57" +version = "4.0.0" +source = "git+https://github.com/DataDog/libdatadog?rev=48da0d82cb32b43d4cdece35b794c9bcbc275a03#48da0d82cb32b43d4cdece35b794c9bcbc275a03" dependencies = [ "anyhow", + "arc-swap", "async-trait", "hashbrown 0.15.5", "http 1.4.0", "libdd-capabilities", "libdd-capabilities-impl", - "libdd-common 4.0.0", - "libdd-ddsketch 1.0.1 (git+https://github.com/DataDog/libdatadog?rev=db05e1f8408a76075efb37ecec544d2e74217e57)", + "libdd-common 4.2.0", + "libdd-ddsketch 1.0.1 (git+https://github.com/DataDog/libdatadog?rev=48da0d82cb32b43d4cdece35b794c9bcbc275a03)", "libdd-shared-runtime", - "libdd-trace-protobuf 3.0.1", - "libdd-trace-utils 3.0.1", + "libdd-trace-obfuscation", + "libdd-trace-protobuf 3.0.2", + "libdd-trace-utils 6.0.1", "rmp-serde", "serde", "tokio", @@ -2177,7 +2182,7 @@ dependencies = [ "http-body-util", "indexmap", "libdd-common 2.0.1", - "libdd-tinybytes 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libdd-tinybytes 1.1.0", "libdd-trace-normalization 1.0.2", "libdd-trace-protobuf 2.0.0", "prost 0.14.3", @@ -2193,8 +2198,8 @@ dependencies = [ [[package]] name = "libdd-trace-utils" -version = "3.0.1" -source = "git+https://github.com/DataDog/libdatadog?rev=db05e1f8408a76075efb37ecec544d2e74217e57#db05e1f8408a76075efb37ecec544d2e74217e57" +version = "6.0.1" +source = "git+https://github.com/DataDog/libdatadog?rev=48da0d82cb32b43d4cdece35b794c9bcbc275a03#48da0d82cb32b43d4cdece35b794c9bcbc275a03" dependencies = [ "anyhow", "base64 0.22.1", @@ -2208,10 +2213,10 @@ dependencies = [ "indexmap", "libdd-capabilities", "libdd-capabilities-impl", - "libdd-common 4.0.0", - "libdd-tinybytes 1.1.0 (git+https://github.com/DataDog/libdatadog?rev=db05e1f8408a76075efb37ecec544d2e74217e57)", + "libdd-common 4.2.0", + "libdd-tinybytes 1.1.1", "libdd-trace-normalization 2.0.0", - "libdd-trace-protobuf 3.0.1", + "libdd-trace-protobuf 3.0.2", "prost 0.14.3", "rand 0.8.6", "rmp", diff --git a/bottlecap/Cargo.toml b/bottlecap/Cargo.toml index 0563a8199..446bab161 100644 --- a/bottlecap/Cargo.toml +++ b/bottlecap/Cargo.toml @@ -74,13 +74,13 @@ indexmap = {version = "2.11.0", default-features = false} # be found in the clippy.toml file adjacent to this Cargo.toml. datadog-protos = { version = "0.1.0", default-features = false, git = "https://github.com/DataDog/saluki/", rev = "f863626dbfe3c59bb390985fa6530b0621c2a0a2"} ddsketch-agent = { version = "0.1.0", default-features = false, git = "https://github.com/DataDog/saluki/", rev = "f863626dbfe3c59bb390985fa6530b0621c2a0a2"} -libdd-capabilities = { git = "https://github.com/DataDog/libdatadog", rev = "db05e1f8408a76075efb37ecec544d2e74217e57" } -libdd-common = { git = "https://github.com/DataDog/libdatadog", rev = "db05e1f8408a76075efb37ecec544d2e74217e57", default-features = false } -libdd-trace-protobuf = { git = "https://github.com/DataDog/libdatadog", rev = "db05e1f8408a76075efb37ecec544d2e74217e57" } -libdd-trace-utils = { git = "https://github.com/DataDog/libdatadog", rev = "db05e1f8408a76075efb37ecec544d2e74217e57", default-features = false, features = ["mini_agent"] } -libdd-trace-normalization = { git = "https://github.com/DataDog/libdatadog", rev = "db05e1f8408a76075efb37ecec544d2e74217e57" } -libdd-trace-obfuscation = { git = "https://github.com/DataDog/libdatadog", rev = "db05e1f8408a76075efb37ecec544d2e74217e57", default-features = false } -libdd-trace-stats = { git = "https://github.com/DataDog/libdatadog", rev = "db05e1f8408a76075efb37ecec544d2e74217e57", default-features = false } +libdd-capabilities = { git = "https://github.com/DataDog/libdatadog", rev = "48da0d82cb32b43d4cdece35b794c9bcbc275a03" } +libdd-common = { git = "https://github.com/DataDog/libdatadog", rev = "48da0d82cb32b43d4cdece35b794c9bcbc275a03", default-features = false } +libdd-trace-protobuf = { git = "https://github.com/DataDog/libdatadog", rev = "48da0d82cb32b43d4cdece35b794c9bcbc275a03" } +libdd-trace-utils = { git = "https://github.com/DataDog/libdatadog", rev = "48da0d82cb32b43d4cdece35b794c9bcbc275a03", default-features = false, features = ["mini_agent"] } +libdd-trace-normalization = { git = "https://github.com/DataDog/libdatadog", rev = "48da0d82cb32b43d4cdece35b794c9bcbc275a03" } +libdd-trace-obfuscation = { git = "https://github.com/DataDog/libdatadog", rev = "48da0d82cb32b43d4cdece35b794c9bcbc275a03", default-features = false } +libdd-trace-stats = { git = "https://github.com/DataDog/libdatadog", rev = "48da0d82cb32b43d4cdece35b794c9bcbc275a03", default-features = false } datadog-opentelemetry = { git = "https://github.com/DataDog/dd-trace-rs", rev = "f51cefc4ad24bec81b38fb2f36b1ed93f21ae913", default-features = false } dogstatsd = { git = "https://github.com/DataDog/serverless-components", rev = "5b68f50f49c9defbfed4d25bd621e2a86405a972", default-features = false } datadog-fips = { git = "https://github.com/DataDog/serverless-components", rev = "5b68f50f49c9defbfed4d25bd621e2a86405a972", default-features = false } diff --git a/bottlecap/src/traces/http_client.rs b/bottlecap/src/traces/http_client.rs index ce6ea5769..4b3c2a4b5 100644 --- a/bottlecap/src/traces/http_client.rs +++ b/bottlecap/src/traces/http_client.rs @@ -11,7 +11,8 @@ use hyper_http_proxy; use hyper_rustls::HttpsConnectorBuilder; use libdd_capabilities::{ MaybeSend, - http::{HttpClientTrait, HttpError}, + http::{HttpClientCapability, HttpError}, + sleep::SleepCapability, }; use libdd_common::http_common::{self, Body, GenericHttpClient}; use rustls::RootCertStore; @@ -21,6 +22,7 @@ use std::fs::File; use std::future::Future; use std::io::BufReader; use std::sync::{Arc, LazyLock}; +use std::time::Duration; use tracing::debug; type InnerClient = @@ -29,8 +31,8 @@ type InnerClient = /// HTTP client used by trace and stats flushers. /// /// Wraps a hyper client preconfigured with optional proxy and TLS settings, and -/// implements [`HttpClientTrait`] so it can be passed to `libdd_trace_utils` -/// senders such as `SendData::send`. +/// implements [`HttpClientCapability`] and [`SleepCapability`] so it can be +/// passed to `libdd_trace_utils` senders such as `SendData::send`. #[derive(Clone, Debug)] pub struct HttpClient { inner: InnerClient, @@ -42,10 +44,10 @@ impl HttpClient { } } -impl HttpClientTrait for HttpClient { +impl HttpClientCapability for HttpClient { #[allow(clippy::expect_used)] fn new_client() -> Self { - // Required by `HttpClientTrait` but never invoked on bottlecap's code + // Required by `HttpClientCapability` but never invoked on bottlecap's code // paths — production builds the client via // `create_client(proxy, tls_cert, skip_ssl)`. Routing this fallback // through the same constructor keeps the failure mode and error @@ -76,6 +78,20 @@ impl HttpClientTrait for HttpClient { } } +impl SleepCapability for HttpClient { + fn new() -> Self { + // Required by `SleepCapability` but never invoked on bottlecap's code + // paths — production builds the client via + // `create_client(proxy, tls_cert, skip_ssl)`. Mirror `new_client()` so + // the failure mode stays consistent with the rest of the module. + ::new_client() + } + + fn sleep(&self, duration: Duration) -> impl Future + MaybeSend { + tokio::time::sleep(duration) + } +} + /// Initialize the crypto provider needed for setting custom root certificates. fn ensure_crypto_provider_initialized() { static INIT_CRYPTO_PROVIDER: LazyLock<()> = LazyLock::new(|| { diff --git a/bottlecap/src/traces/stats_flusher.rs b/bottlecap/src/traces/stats_flusher.rs index 207112881..5b94d96df 100644 --- a/bottlecap/src/traces/stats_flusher.rs +++ b/bottlecap/src/traces/stats_flusher.rs @@ -13,7 +13,7 @@ use crate::traces::http_client::HttpClient; use crate::traces::stats_aggregator::StatsAggregator; use bytes::Bytes; use dogstatsd::api_key::ApiKeyFactory; -use libdd_capabilities::http::HttpClientTrait; +use libdd_capabilities::http::HttpClientCapability; use libdd_common::Endpoint; use libdd_trace_protobuf::pb; use libdd_trace_utils::stats_utils; diff --git a/bottlecap/src/traces/trace_agent.rs b/bottlecap/src/traces/trace_agent.rs index 9e0175efa..0a6e2279c 100644 --- a/bottlecap/src/traces/trace_agent.rs +++ b/bottlecap/src/traces/trace_agent.rs @@ -774,3 +774,99 @@ fn success_response(message: &str) -> Response { debug!("{}", message); (StatusCode::OK, json!({"rate_by_service": {}}).to_string()).into_response() } + +#[cfg(test)] +mod tests { + //! Behavioral coverage for the `libdatadog` bump (`db05e1f` -> `48da0d8`), which pulls in + //! [`DataDog/libdatadog#2071`](https://github.com/DataDog/libdatadog/pull/2071). That PR makes + //! `From<&HeaderMap> for TracerHeaderTags` parse the `datadog-client-computed-stats` and + //! `datadog-client-computed-top-level` headers with the Go trace-agent's `isHeaderTrue` rule: + //! empty -> false; the false-like values `strconv.ParseBool` recognizes + //! (`0`, `f`, `F`, `FALSE`, `False`, `false`) -> false; every other non-empty value + //! (including unparseable ones like `"yes"`) -> true. These tests pin that parsing at the + //! `(&headers).into()` boundary `handle_traces` uses (see `trace_agent.rs:506`); on the + //! pre-bump rev they would fail (falsey strings used to resolve to `true`, and presence alone + //! set `top_level`), so they give the dep bump its missing behavioral coverage. + //! + //! The `client_computed_stats` bool pinned here is what APMSVLS-487 + //! (`lpimentel/respect-client-computed-stats`) consumes. + + use axum::http::{HeaderMap, HeaderName, HeaderValue}; + use libdd_trace_utils::trace_utils::TracerHeaderTags; + + /// Build a `HeaderMap` with `name: value` (or no header when `value` is `None`), convert it the + /// same way `handle_traces` does, and return `(client_computed_stats, client_computed_top_level)`. + fn parse(name: &str, value: Option<&str>) -> (bool, bool) { + let mut headers = HeaderMap::new(); + if let Some(v) = value { + headers.insert( + HeaderName::from_bytes(name.as_bytes()).expect("valid header name"), + HeaderValue::from_str(v).expect("valid header value"), + ); + } + let tags: TracerHeaderTags<'_> = (&headers).into(); + (tags.client_computed_stats, tags.client_computed_top_level) + } + + fn parse_stats(value: Option<&str>) -> bool { + parse("datadog-client-computed-stats", value).0 + } + + fn parse_top_level(value: Option<&str>) -> bool { + parse("datadog-client-computed-top-level", value).1 + } + + #[test] + fn truthy_values_trigger_the_fix_on_every_runtime() { + // Cross-runtime header values: ".NET/Java/PHP/Python" -> "true", "JS/Ruby/C++" -> "yes", + // "Go" -> "t", plus a canonical "1". All non-empty/non-falsey -> the fix must trigger. + for value in ["true", "yes", "t", "1"] { + assert!( + parse_stats(Some(value)), + "expected client_computed_stats == true for {value:?}" + ); + } + } + + #[test] + fn absent_or_empty_does_not_trigger_the_fix() { + assert!(!parse_stats(None), "absent header must be false"); + assert!(!parse_stats(Some("")), "empty-present header must be false"); + } + + #[test] + fn falsey_strings_parse_as_false_go_agent_aligned() { + // Post-bump (libdatadog#2071): `client_computed_stats` follows the Go trace-agent's + // `isHeaderTrue`/`ParseBool` rule, so the false-like literals resolve to `false`. + // (Pre-bump rev db05e1f parsed these as `!value.is_empty()` -> `true`; this test would + // have failed there, which is exactly the behavior change the bump introduces.) + for value in ["false", "0", "f", "F", "FALSE", "False"] { + assert!( + !parse_stats(Some(value)), + "expected client_computed_stats == false for {value:?} (libdatadog#2071)" + ); + } + } + + #[test] + fn top_level_no_longer_set_by_presence_alone() { + // libdatadog#2071 also fixes `client_computed_top_level`, which previously was `true` + // whenever the header was merely *present* (even empty or falsey). Now it follows the + // same Go-agent rule as `stats`. + assert!(!parse_top_level(None), "absent must be false"); + assert!( + !parse_top_level(Some("")), + "empty-present must be false (was true pre-bump)" + ); + assert!( + !parse_top_level(Some("false")), + "\"false\" must be false (was true pre-bump)" + ); + for value in ["true", "yes", "t", "1"] { + assert!( + parse_top_level(Some(value)), + "expected client_computed_top_level == true for {value:?}" + ); + } + } +}