Repository navigation
feat: Add datadog-mock-intake crate and serve trace-agent tests with it - #174
lucaspimentel wants to merge 32 commits into
Conversation
datadog-mock-intake crate and serve trace-agent tests with it
|
@DataDog review |
There was a problem hiding this comment.
Two of the most critical issues are in datadog-mock-intake: accepted connection tasks are not cancelled on MockIntake drop, allowing traffic to leak across tests; and typed payload captures can record requests out of arrival order due to async decoding. Additionally, the no-stats integration test asserts against decoded stats_payloads() rather than raw captures, meaning a malformed stats POST would silently pass the assertion.
🤖 Bits Code Review · Commit a074193 · @DataDog review to ask questions
|
@DataDog review |
There was a problem hiding this comment.
datadog-mock-intake crate and serve trace-agent tests with itdatadog-mock-intake crate and serve trace-agent tests with it
|
@DataDog review |
There was a problem hiding this comment.
|
@DataDog review |
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
A disconnected request can permanently wedge ordered capture, and connection errors and shutdown-test races need handling.
Review effort: Balanced
Findings: 1
Open (3)
What changed in this PR
Adds a reusable Hyper-based mock APM intake and migrates trace-agent integration tests to its typed payload capture APIs.
Changes:
- Supports trace, stats, and DSM intake endpoints with decoding, limits, diagnostics, and failure injection.
- Replaces the trace-agent’s local mock server.
- Updates workspace dependencies and lockfile.
| File | Description |
|---|---|
crates/datadog-mock-intake/src/lib.rs |
Implements the mock intake and tests. |
crates/datadog-mock-intake/Cargo.toml |
Defines crate dependencies. |
crates/datadog-trace-agent/tests/integration_test.rs |
Migrates integration tests. |
crates/datadog-trace-agent/tests/common/mod.rs |
Removes the old mock module. |
crates/datadog-trace-agent/tests/common/mock_server.rs |
Deletes the local mock server. |
crates/datadog-trace-agent/tests/common/helpers.rs |
Removes redundant decoding logic. |
crates/datadog-trace-agent/Cargo.toml |
Adds the mock intake dev dependency. |
Cargo.lock |
Records dependency resolution changes. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 463cde2ee8
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@DataDog review |
There was a problem hiding this comment.
|
@DataDog review |
In-process mock Datadog intake that accepts the APM endpoints serverless components flush to (/api/v0.2/stats, /api/v0.2/traces, /api/v0.1/pipeline_stats), decodes msgpack/protobuf payloads on arrival, and exposes typed query methods plus raw request capture for test assertions. Ported from the bottlecap fake intake and served with hyper; preserves the 2 MiB wire-body limit, gzip/zstd decoding, stats failure injection, request summaries, and JSON dumps.
Replace the test-local MockServer with the shared datadog-mock-intake crate. Payload assertions now use the intake's decoded capture instead of re-decoding captured bytes, positive assertions wait on decoded payload arrival, and the DSM forwarding test sends a valid msgpack pipeline-stats payload so it verifies both unchanged byte forwarding and intake acceptance.
…-intake Guards decompress() against decompression-bomb-style memory blow-up by capping gzip/zstd output at 64 MiB. Also skips building the diagnostic JSON dump when no dump_dir is configured (the default), avoiding a wasted allocation on every request, and marks DSM pipeline-stats payload fields #[serde(default)] so a payload missing an optional field still decodes. 🤖
Unknown Content-Encoding values were silently decoded as identity, masking mismatches with the real intake, which rejects unsupported encodings. Rejected attempts are still raw-captured but do not consume the stats failure-injection budget; explicit identity encoding is still accepted.
Request ids were assigned inside the blocking handlers, so under concurrency dump and summary ids could interleave out of arrival order relative to the captured requests.
Summaries reported a fixed payload count of 1 per pipeline-stats request; report the number of decoded points like the other endpoints.
The dump serializer mirrors pb::AgentPayload fields by hand; an exhaustive struct literal in the test forces it to be updated whenever the protobuf struct gains a field, instead of silently dropping it from dumps.
Indexing decoded payload collections panicked with bare out-of-bounds errors; use first() with expects that name the failed assertion.
Drop the inline datadog_mock_intake qualifications in favor of the top-level import used for the other helper types.
Captured requests were appended after the blocking handler completed, so under concurrent clients a later-arriving request that decompressed or decoded faster could be recorded before an earlier one, breaking the arrival-order contract of the query APIs. Completed requests are now buffered by request id and flushed as a contiguous prefix in one critical section.
A request canceled by a client disconnect could leave its request id unrecorded, holding back every later capture from the query APIs. Captures are now recorded inside the blocking handler task, which always runs to completion, with handler panics recorded as rejected requests.
Connection failures such as malformed HTTP or requests cut off mid-message were silently discarded, making a test timeout look like a missing payload. They are now logged to stderr.
The keep-alive shutdown test required writes after Drop to succeed, but the server may already have closed the socket. Broken pipe and reset errors are now accepted as evidence of shutdown; the capture-count assertion is unchanged.
Request ids were assigned after the body was fully read, so a slow upload could be ordered after a later request. Ids are now assigned when the request head arrives. Requests that end without a capture (oversized or unreadable body, client disconnect) release their id so they cannot hold back later captures.
Drop the HandledRequest request_id field, which always echoed the handler's request_id parameter, and use the parameter directly at summary and dump call sites. Replace the three then_some/flatten accepted-payload constructs with Option::filter, and remove the always-false inject_failure parameter from decompress_failure_status. No behavior change.
Replace hand-written Display impl with thiserror, reuse the existing content-encoding helper, inline the one-use request-id helper, drop a redundant state clone, and only compute payload counts when summaries are enabled. Extract a shared helper for the duplicated marker-stats assertion in the trace-agent tests.
Captures were only exposed once every earlier request finished, so a 2xx did not guarantee the capture was visible, and a stalled request hid all later captures indefinitely. Captures are now visible as soon as the intake responds, still ordered by arrival.
test_internal_span_kind_does_not_produce_stats and test_peer_tags_in_flushed_stats read the intake capture immediately after shutdown; wait for it like the other tests to avoid timing-dependent failures.
The trace-agent verify helpers waited for decoded payloads, so a payload the intake rejected surfaced as a 60s timeout claiming no request arrived. Wait for the raw request, then assert it was accepted.
A Content-Encoding value that was not valid UTF-8 was treated as absent, so the body was silently decoded as identity. It is now rejected with 415 like any other unsupported encoding.
The dual-transport test byte-searched raw request bodies for the service names and checked only after the first trace POST arrived. It now waits until spans from both services appear in the decoded trace payloads.
d54fc76 to
d107012
Compare


What does this PR do?
Adds a shared
datadog-mock-intakecrate: an in-process mock Datadog APM intake served with hyper that accepts the APM endpoints serverless components flush to, decodes msgpack/protobuf payloads on arrival, and exposes typed query methods plus raw request capture for test assertions.POST /api/v0.2/traces- APM traces and spansPOST /api/v0.2/stats- APM trace statsPOST /api/v0.1/pipeline_stats- DSMRewires the
trace-agentintegration tests onto the crate, replacing the test-localMockServer.Motivation
The mock intake was originally built in
datadog-lambda-extension(#1385) as a bottlecap test fixture and then promoted to a feature-gated library module plus a standalonemock-intakebinary. It has already proven useful there:MOCK_INTAKE_FAIL_STATS_FIRST_N) is what lets those tests cover tracer retry behavior against 500 responses, including the guarantee that exactly the first N attempts are rejected.The APM/DSM integration tests in
datadog-lambda-extension(bottlecap) anddatadog-trace-agenteach carried their own intake fixture. Extracting the bottlecap mock intake into this shared crate lets both test suites use one implementation. A follow-up PR indatadog-lambda-extension(#1392) consumes this crate (stacked on this PR's revision), deletes the bottlecap-local copy, and supersedes #1385.Additional Notes
413before decode), 404/405 routing, stats failure injection, request summaries, and JSON dumps.pipeline_stats_url()accessor, and raw request capture (CapturedRequest,requests_for_path()). Typed query methods return only accepted, successfully decoded payloads; raw capture records completed POST attempts including rejected ones.MockIntakeis dropped.MockServeranswered/api/v0.2/traceswith200 {"ok":true}; the mock intake answers every accepted payload with202and an empty body.libdd-trace-utilstreats any non-4xx/5xx response as success, so trace flushing is unaffected.LICENSE-3rdparty.csv;dd-rust-license-tool checkpasses.Describe how to test/QA your changes
cargo test -p datadog-mock-intake: includes ports of the original bottlecap intake's unit tests plus new transport regression tests (body limit, 404/405, raw-capture semantics, zstd decoding).cargo test --workspace: passes.cargo clippy --workspace --all-targets: clean.dd-rust-license-tool check: passes.