Repository navigation
[APMSVLS-499] test infra: use shared datadog-mock-intake crate and add standalone mock-intake binary
#1392
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
lucaspimentel
wants to merge
19
commits into
main
Choose a base branch
from
lpimentel/mock-intake-consume
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
[APMSVLS-499] test infra: use shared datadog-mock-intake crate and add standalone mock-intake binary
#1392
Changes from all commits
Commits
Show all changes
19 commits
Select commit
Hold shift + click to select a range
4ac1097
Use shared datadog-mock-intake crate and add standalone fake-intake b…
lucaspimentel 05694a7
Rename the fake-intake binary, feature, and env vars to mock-intake
lucaspimentel c7d34bd
Clean up mock-intake env parsing and dev-deps
lucaspimentel 7a66922
Point mock-intake smoke docs at test-mode PR
lucaspimentel 657cd30
Use pipeline_stats_url() in mock-intake startup output
lucaspimentel ed521cf
Accept non-UTF-8 paths for MOCK_INTAKE_DUMP_DIR
lucaspimentel 5bf9e7d
Lint all targets in every GitLab clippy run
lucaspimentel 553d1a1
Rename fake intake references in tests to mock intake
lucaspimentel 20b9742
Remove orphaned empty tests/common module
lucaspimentel b0a3eb7
Move mock-intake detail docs to binary README
lucaspimentel ca798c4
Pin datadog-mock-intake to the merged serverless-components rev
lucaspimentel 10d15ef
Simplify mock intake configuration and fixtures
lucaspimentel 190509d
Disable captures in standalone mock intake
lucaspimentel 51cff75
Propagate mock intake signal errors
lucaspimentel 0f1c679
Build smoke test binaries from one checkout
lucaspimentel 076ef97
Enable JSON dumps in the smoke procedure
lucaspimentel d763a38
Simplify mock intake setup and documentation
lucaspimentel d5f6607
Deduplicate mock-intake env var docs
lucaspimentel 99c5fb9
Run mock-intake binary tests in CI
lucaspimentel File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,66 @@ | ||
| # mock-intake | ||
|
|
||
| A standalone mock Datadog APM intake for local APM debugging: a small HTTP server that accepts the same APM endpoints the extension flushes to, decodes the payloads, and reports them locally. | ||
|
|
||
| Built behind the `mock-intake` feature, which is never part of `default` or `fips` production builds: | ||
|
|
||
| ```bash | ||
| cd bottlecap | ||
| cargo build --bin mock-intake --features mock-intake | ||
| ``` | ||
|
|
||
| The binary wraps the shared `datadog-mock-intake` crate from the `serverless-components` repository. The standalone binary disables in-memory captures; request summaries and JSON dumps remain available. The same crate backs the `apm_integration_test` and `dsm_integration_test` integration tests, which retain captures by default and run in every test build via a dev-dependency. | ||
|
|
||
| ## Configuration | ||
|
|
||
| | Variable | Default | Meaning | | ||
| |---|---|---| | ||
| | `MOCK_INTAKE_PORT` | `8127` | Loopback listener port; `0` picks a free port (the bound address is printed at startup) | | ||
| | `MOCK_INTAKE_FAIL_STATS_FIRST_N` | `0` | Return HTTP 500 for the first N stats request attempts | | ||
| | `MOCK_INTAKE_DUMP_DIR` | unset | Write one JSON envelope per successfully decoded request into this directory | | ||
|
|
||
| Malformed configuration values cause a clear error and a nonzero exit. Bind failures and dump-directory creation failures are also reported instead of being ignored. | ||
|
|
||
| ## Endpoints and response codes | ||
|
|
||
| - `POST /api/v0.2/stats`: MessagePack `StatsPayload` (gzip, zstd, and identity accepted): `202 Accepted` when decoded (unless failure injection rejects the attempt) | ||
| - `POST /api/v0.2/traces`: protobuf `AgentPayload` (gzip, zstd, and identity accepted): `202 Accepted` when decoded | ||
| - `POST /api/v0.1/pipeline_stats`: MessagePack DSM pipeline stats: `202 Accepted` when decoded | ||
| - Malformed payloads return `400 Bad Request` (unless inside the stats failure-injection window, which returns `500`) | ||
| - Request bodies above the 2 MiB wire-body limit return `413 Payload Too Large` without being decoded | ||
|
|
||
| ## Failure injection semantics | ||
|
|
||
| With `MOCK_INTAKE_FAIL_STATS_FIRST_N=N`, the first N stats *attempts* return 500 and their payloads are not captured by the intake. Attempts are counted per server instance, atomically, so concurrent requests cannot exceed the limit. Rejected attempts are still decoded, summarized, and dumped, so a rejected attempt and its successful retry are each visible with their own status. Trace and DSM requests do not consume the rejection budget. | ||
|
|
||
| ## Request summaries and stats grouping | ||
|
|
||
| Each handled request produces one summary line on stderr (prefix `mock-intake:`) with the request identity, endpoint, content encoding (`identity` when absent), response status, and decoded payload count. For stats requests, hits are grouped by the full aggregation key: client service/env/version plus origin context (hostname, container, tags, git/image metadata, process tags, aggregation mode) and grouped dimensions (service, name, resource, type, DB type, HTTP status, gRPC status, synthetics, span kind, trace-root flag, HTTP method/endpoint, service source, peer tags, deprecated span-derived primary tags, and additional metric tags). Measurements (hits, errors, duration, sketches) and delivery metadata are excluded from the key, so matching dimensions combine across time buckets. Tag lists are sorted in the summary without modifying captured payloads. | ||
|
|
||
| ## JSON dumps | ||
|
|
||
| With `MOCK_INTAKE_DUMP_DIR` set, each successfully decoded request attempt (including rejected stats attempts) writes one JSON file containing the request identity, endpoint, encoding, response status, and decoded payload. Trace payloads use explicit field-by-field JSON serialization rather than a derived `Serialize` implementation. DSM dumps contain only the fields the crate decodes. Filenames are collision-resistant and never overwrite earlier dumps. | ||
|
|
||
| ## Smoke procedure | ||
|
|
||
| Point the test-mode trace processor at a local mock intake. Build both binaries from this checkout, starting at the repository root: | ||
|
|
||
| ```bash | ||
| cd bottlecap | ||
| cargo build --bin bottlecap-test-mode --bin mock-intake --features test-mode,mock-intake | ||
| dump_dir=$(mktemp -d) | ||
| printf 'JSON dumps: %s\n' "$dump_dir" | ||
| MOCK_INTAKE_DUMP_DIR="$dump_dir" ./target/debug/mock-intake | ||
| ``` | ||
|
|
||
| In another shell, start at the same checkout's repository root: | ||
|
|
||
| ```bash | ||
| cd bottlecap | ||
| DD_APM_DD_URL=http://127.0.0.1:8127 \ | ||
| DD_LAMBDA_EXTENSION_COMPUTE_STATS=true \ | ||
| DD_SERVICE=mock-intake-smoke DD_ENV=local DD_VERSION=smoke \ | ||
| ./target/debug/bottlecap-test-mode | ||
| ``` | ||
|
|
||
| Unset `DD_SERVERLESS_FLUSH_STRATEGY` so flushing is manual, and make sure no ambient proxy intercepts localhost. Once `GET http://127.0.0.1:8126/info` succeeds, POST a MessagePack trace payload to `/v0.4/traces` (with `X-Datadog-Trace-Count`), then `POST /flush`. The `mock-intake:` summary lines should show the trace request and a stats request whose grouped hits match your spans, and the JSON dumps should contain both payloads. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,115 @@ | ||
| // Copyright 2023-Present Datadog, Inc. https://www.datadoghq.com/ | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| //! Standalone mock Datadog APM intake for local debugging. | ||
| //! | ||
| //! Runs the shared `datadog-mock-intake` crate with request summaries enabled | ||
| //! and accepts the same APM endpoints the Lambda extension flushes to. Point a | ||
| //! tracer or the `bottlecap-test-mode` trace processor at it and inspect | ||
| //! decoded payloads and JSON dumps locally. See `bottlecap/README.md` for usage. | ||
| //! | ||
| //! Environment variables are documented in `README.md` next to this file. | ||
|
|
||
| #![deny(clippy::all)] | ||
| #![deny(clippy::pedantic)] | ||
| #![deny(clippy::unwrap_used)] | ||
| #![deny(unused_extern_crates)] | ||
| #![deny(unused_allocation)] | ||
| #![deny(unused_assignments)] | ||
| #![deny(unused_comparisons)] | ||
| #![deny(unreachable_pub)] | ||
| #![deny(missing_copy_implementations)] | ||
| #![deny(missing_debug_implementations)] | ||
|
|
||
| use std::path::PathBuf; | ||
|
|
||
| use anyhow::{Context, bail}; | ||
| use datadog_mock_intake::{MockIntake, MockIntakeOptions}; | ||
|
|
||
| #[tokio::main] | ||
| async fn main() -> anyhow::Result<()> { | ||
| let options = parse_options()?; | ||
| let intake = MockIntake::start_with_options(options) | ||
| .await | ||
| .context("mock-intake: failed to start")?; | ||
|
|
||
| println!("mock-intake: listening on {}", intake.base_url()); | ||
| println!("mock-intake: stats endpoint POST {}", intake.stats_url()); | ||
| println!("mock-intake: traces endpoint POST {}", intake.traces_url()); | ||
| println!( | ||
| "mock-intake: DSM endpoint POST {}", | ||
| intake.pipeline_stats_url() | ||
| ); | ||
| println!("mock-intake: waiting for SIGINT or SIGTERM to shut down"); | ||
|
|
||
| wait_for_shutdown() | ||
| .await | ||
| .context("mock-intake: failed to wait for shutdown signal")?; | ||
| drop(intake); | ||
| println!("mock-intake: shut down"); | ||
| Ok(()) | ||
| } | ||
|
|
||
| /// Parse an environment variable via `FromStr`, falling back to `default` when | ||
| /// unset. `expected` describes the accepted format for the error message. | ||
| fn parse_env_or<T: std::str::FromStr>(name: &str, default: T, expected: &str) -> anyhow::Result<T> { | ||
| match std::env::var(name) { | ||
| Ok(raw) => raw.parse::<T>().map_err(|_| { | ||
| anyhow::anyhow!("mock-intake: invalid {name} value '{raw}', expected {expected}") | ||
| }), | ||
| Err(std::env::VarError::NotPresent) => Ok(default), | ||
| Err(std::env::VarError::NotUnicode(raw)) => bail!( | ||
| "mock-intake: {name} is not valid Unicode: {}", | ||
| raw.display() | ||
| ), | ||
| } | ||
| } | ||
|
|
||
| fn parse_options() -> anyhow::Result<MockIntakeOptions> { | ||
| let port = parse_env_or( | ||
| "MOCK_INTAKE_PORT", | ||
| 8127u16, | ||
| "a port number between 0 and 65535", | ||
| )?; | ||
| let fail_stats_first_n = parse_env_or( | ||
| "MOCK_INTAKE_FAIL_STATS_FIRST_N", | ||
| 0usize, | ||
| "a non-negative integer", | ||
| )?; | ||
|
|
||
| Ok(MockIntakeOptions { | ||
| port, | ||
| request_summaries: true, | ||
| fail_stats_first_n, | ||
| dump_dir: std::env::var_os("MOCK_INTAKE_DUMP_DIR").map(PathBuf::from), | ||
| disable_captures: true, | ||
| }) | ||
| } | ||
|
|
||
| async fn wait_for_shutdown() -> anyhow::Result<()> { | ||
| use tokio::signal::unix::{SignalKind, signal}; | ||
|
|
||
| let mut sigterm = signal(SignalKind::terminate()) | ||
| .context("mock-intake: failed to register SIGTERM handler")?; | ||
| tokio::select! { | ||
| result = tokio::signal::ctrl_c() => result?, | ||
| _ = sigterm.recv() => {} | ||
| } | ||
| Ok(()) | ||
| } | ||
|
|
||
| #[cfg(test)] | ||
| #[cfg_attr(coverage_nightly, coverage(off))] | ||
| mod tests { | ||
| use super::parse_options; | ||
|
|
||
| #[test] | ||
| fn standalone_disables_captures() { | ||
| figment::Jail::expect_with(|jail| { | ||
| jail.clear_env(); | ||
| let options = parse_options().expect("default options should parse"); | ||
| assert!(options.disable_captures); | ||
| Ok(()) | ||
| }); | ||
| } | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.