Skip to content

Commit 2fcdd8d

Browse files
committed
Antithesis intake
This commit introduces an intake API for antithesis tests. It's a new implementation compared to the pre-existing intake in the project as this intake has different concerns. However, my hope is to eventually merge the two together once this one is done baking, or supplant the other depending on where we get to.
1 parent eb2197b commit 2fcdd8d

22 files changed

Lines changed: 959 additions & 103 deletions

Cargo.lock

Lines changed: 20 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ members = [
3333
"lib/saluki-tls",
3434
"lib/stringtheory",
3535
"test/antithesis/harness",
36+
"test/antithesis/intake",
3637
]
3738
resolver = "2"
3839

test/antithesis/deploy/Dockerfile

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
#
55
# Build context is the repository root. Three named targets:
66
# - adp : agent-data-plane built WITH Antithesis coverage instrumentation + SDK (the SUT)
7-
# - intake : datadog-intake mock Datadog intake (dependency)
7+
# - intake : antithesis-intake mock Datadog intake + W-property assertions (dependency)
88
# - workload : DogStatsD driver + test templates + setup-complete (the client)
99
#
1010
# ADP is built native x86_64-unknown-linux-gnu (glibc), so no musl cross-compile headers are needed.
@@ -67,7 +67,7 @@ RUN --mount=type=cache,target=/adp/target,id=antithesis-adp-target \
6767
echo "Instrumentation symbols present."
6868

6969
# ---------------------------------------------------------------------------
70-
# Build the correctness tools (datadog-intake) and the test-command binaries, uninstrumented.
70+
# Build the antithesis-intake mock and the test-command binaries, uninstrumented.
7171
# These are supporting harness components, not the SUT, so they need no coverage instrumentation.
7272
# ---------------------------------------------------------------------------
7373
FROM build-base AS tools-builder
@@ -77,14 +77,13 @@ RUN --mount=type=cache,target=/tools/target,id=antithesis-tools-target \
7777
--mount=type=cache,target=/root/.cargo/registry,id=cargo-registry \
7878
--mount=type=cache,target=/root/.cargo/git,id=cargo-git \
7979
cargo build --release \
80-
--bin datadog-intake \
80+
--bin antithesis-intake \
8181
--bin parallel_driver_send_dogstatsd --bin parallel_driver_sketchburst \
82-
--bin finally_verify_delivery --bin eventually_adp_alive \
82+
--bin eventually_adp_alive \
8383
--bin first_sample_config && \
84-
cp /tools/target/release/datadog-intake /usr/local/bin/datadog-intake && \
84+
cp /tools/target/release/antithesis-intake /usr/local/bin/antithesis-intake && \
8585
cp /tools/target/release/parallel_driver_send_dogstatsd /usr/local/bin/parallel_driver_send_dogstatsd && \
8686
cp /tools/target/release/parallel_driver_sketchburst /usr/local/bin/parallel_driver_sketchburst && \
87-
cp /tools/target/release/finally_verify_delivery /usr/local/bin/finally_verify_delivery && \
8887
cp /tools/target/release/eventually_adp_alive /usr/local/bin/eventually_adp_alive && \
8988
cp /tools/target/release/first_sample_config /usr/local/bin/first_sample_config
9089

@@ -118,12 +117,15 @@ ENTRYPOINT ["/entrypoint.sh"]
118117
CMD ["run"]
119118

120119
# ---------------------------------------------------------------------------
121-
# Runtime: datadog-intake (mock Datadog intake dependency).
120+
# Runtime: antithesis-intake (mock Datadog intake + W-property assertions).
122121
# ---------------------------------------------------------------------------
123122
FROM ${APP_IMAGE} AS intake
124123
ENV NO_COLOR=1
125-
COPY --from=tools-builder /usr/local/bin/datadog-intake /usr/local/bin/datadog-intake
126-
ENTRYPOINT ["/usr/local/bin/datadog-intake"]
124+
# W17 resolves each series' host resource against this hostname. Keep it in sync
125+
# with `hostname:` in adp/datadog.yaml and the DD_HOSTNAME default in the harness.
126+
ENV DD_HOSTNAME=antithesis-adp
127+
COPY --from=tools-builder /usr/local/bin/antithesis-intake /usr/local/bin/antithesis-intake
128+
ENTRYPOINT ["/usr/local/bin/antithesis-intake"]
127129

128130
# ---------------------------------------------------------------------------
129131
# Runtime: workload client (DogStatsD driver + test templates).
@@ -141,7 +143,6 @@ COPY test/antithesis/deploy/workload/test/ /opt/antithesis/test/
141143
COPY --from=tools-builder --chmod=755 /usr/local/bin/first_sample_config /opt/antithesis/test/v1/main/first_sample_config
142144
COPY --from=tools-builder --chmod=755 /usr/local/bin/parallel_driver_send_dogstatsd /opt/antithesis/test/v1/main/parallel_driver_send_dogstatsd
143145
COPY --from=tools-builder --chmod=755 /usr/local/bin/parallel_driver_sketchburst /opt/antithesis/test/v1/main/parallel_driver_sketchburst
144-
COPY --from=tools-builder --chmod=755 /usr/local/bin/finally_verify_delivery /opt/antithesis/test/v1/main/finally_verify_delivery
145146
COPY --from=tools-builder --chmod=755 /usr/local/bin/eventually_adp_alive /opt/antithesis/test/v1/main/eventually_adp_alive
146147
COPY --chmod=755 test/antithesis/deploy/workload/entrypoint.sh /entrypoint.sh
147148
ENTRYPOINT ["/entrypoint.sh"]

test/antithesis/deploy/docker-compose.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ services:
1414
environment:
1515
NO_COLOR: "1"
1616
healthcheck:
17-
# datadog-intake serves HTTP on :2049. /dev/tcp avoids needing curl in the image.
17+
# antithesis-intake serves HTTP on :2049. /dev/tcp avoids needing curl in the image.
1818
test: ["CMD-SHELL", "bash -c 'exec 3<>/dev/tcp/localhost/2049'"]
1919
interval: 2s
2020
timeout: 2s

test/antithesis/harness/src/bin/finally_verify_delivery.rs

Lines changed: 0 additions & 92 deletions
This file was deleted.

test/antithesis/intake/Cargo.toml

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
[package]
2+
name = "antithesis-intake"
3+
version = "0.1.0"
4+
edition = { workspace = true }
5+
license = { workspace = true }
6+
repository = { workspace = true }
7+
publish = false
8+
9+
[[bin]]
10+
name = "antithesis-intake"
11+
path = "src/bin/intake.rs"
12+
13+
[lints]
14+
workspace = true
15+
16+
[dependencies]
17+
antithesis_sdk = { workspace = true, features = ["full"] }
18+
anyhow = { workspace = true, features = ["std"] }
19+
axum = { workspace = true, features = ["http1", "json", "tokio", "tracing"] }
20+
clap = { workspace = true, features = ["derive", "env", "error-context", "help", "std", "usage"] }
21+
datadog-protos = { workspace = true }
22+
headers = "0.4"
23+
mime = "0.3"
24+
protobuf = { workspace = true }
25+
serde_json = { workspace = true }
26+
tokio = { workspace = true, features = [
27+
"macros",
28+
"net",
29+
"rt",
30+
"rt-multi-thread",
31+
"signal",
32+
] }
33+
tower = { workspace = true }
34+
tower-http = { workspace = true, features = [
35+
"decompression-deflate",
36+
"decompression-gzip",
37+
"decompression-zstd",
38+
] }
39+
tracing = { workspace = true }
40+
tracing-subscriber = { workspace = true, features = [
41+
"ansi",
42+
"env-filter",
43+
"fmt",
44+
"local-time",
45+
"registry",
46+
"std",
47+
"tracing-log",
48+
] }

test/antithesis/intake/README.md

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# A property asserting intake for Datadog Agent-like artifacts
2+
3+
This intake asserts structural and aggregation properties on payloads from
4+
Datadog Agent-like programs and is intended to mimic the constraints set by
5+
Datadog Intake API. This implementation is forked from the other intake in this
6+
project and may be later merged back up, although the goals to make aggregation
7+
assertions here is invasive.
8+
9+
### How This Project Works
10+
11+
This document is the 'specification' for an abstract DogStatsD Agent. We assert
12+
that for any given input stream ADP emits to the intake data that is correctly
13+
shaped and, in a future update, that the aggregation model of ADP is accurate to
14+
the reference implementation of Datadog Agent DogStatsD.
15+
16+
# Properties
17+
18+
## Payloads
19+
20+
The Agent emits outputs to Datadog intake endpoints as payloads. The current
21+
specification covers `/api/v2/series` only. The Agent also emits to
22+
`/api/v3/series`, which a future revision will add.
23+
24+
In this section we define properties that hold for `/api/v2/series` payloads
25+
irrespective of load generation profile. Precisely, a 'payload' is an HTTP
26+
envelope wrap around the compressed bytes of a
27+
[`MetricPayload`](https://github.com/DataDog/agent-payload/blob/0a5f9ebbbe9c2a1f1e671467511f6189d3a3b443/proto/metrics/agent_payload.proto#L30-L72).
28+
29+
Some properties reference rig-controlled parameters. `MaxTags(orgID)` and
30+
`MaxResources(orgID)` are per-org caps with defaults 100 and 500 respectively.
31+
`hostname` is the value the rig passes to the Agent via the `DD_HOSTNAME`
32+
environment variable. The Agent's hostname provider chain resolves this first,
33+
short-circuiting cloud-metadata and OS fallbacks.
34+
35+
| Number | Category | Name | Description |
36+
|--------|---------------|------------------------|----------------------------------------------------------------|
37+
| Pyld01 | Envelope | Content-Type | `Content-Type` in `{application/x-protobuf, application/json}` |
38+
| Pyld02 | Envelope | Content-Encoding | `Content-Encoding` in `{deflate, gzip, zstd, identity}` |
39+
| Pyld03 | Envelope | API Key | `DD-Api-Key` header present iff non-empty key configured |
40+
| Pyld05 | Bytes | Compressed Size | body < 500 KiB compressed |
41+
| Pyld06 | Bytes | Uncompressed Size | body <= 5 MiB uncompressed |
42+
| Pyld07 | MetricPayload | Decode | body decodes as v2 `MetricPayload` via `rust-protobuf`. |
43+
| Pyld08 | MetricPayload | Point Count | total points <= configured `serializer_max_series_points_per_payload` |
44+
| Pyld09 | MetricSeries | Metric Non-Empty | `MetricSeries.metric` is non-empty |
45+
| Pyld10 | MetricSeries | Metric Length | `len(metric) <= 350` bytes |
46+
| Pyld11 | MetricSeries | Metric Alphabetic | `metric` contains at least one ASCII alphabetic char |
47+
| Pyld12 | MetricSeries | Type Enum | `type` in `{COUNT, RATE, GAUGE}` |
48+
| Pyld13 | MetricSeries | Tag Count | `len(tags) <= MaxTags(orgID)` |
49+
| Pyld14 | MetricSeries | Tag Prefix Reserved | no tag starts with `device:` or `dd.internal.resource:` |
50+
| Pyld15 | MetricSeries | Per-Series Point Count | `len(points) <=` configured `serializer_max_series_points_per_payload` |
51+
| Pyld16 | MetricSeries | Origin Populated | `origin.{product, category, service}` enum-valid |
52+
| Pyld17 | Resource | Host Resource Resolved | intake's `Host()` scan resolves a `(type="host", name=hostname)` resource |
53+
| Pyld18 | Resource | Resource Count | `len(resources) <= MaxResources(orgID)` |
54+
| Pyld19 | Resource | Host Name Length | host `name <= 255` bytes |
55+
| Pyld20 | MetricPoint | Value Not-NaN | `value` is not NaN |
56+
| Pyld21 | MetricPoint | Timestamp Future Bound | `timestamp <= intake_now + 600s` |
57+
| Pyld22 | Bytes | Content-Length | `Content-Length` absent or value equals body byte count |
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
//! Binary entry point for the mock Datadog intake.
2+
3+
#![deny(warnings)]
4+
5+
use std::sync::Arc;
6+
7+
use antithesis_intake::http::build_router;
8+
use antithesis_sdk::prelude::*;
9+
use anyhow::{Context, Result};
10+
use clap::Parser;
11+
use tokio::{net::TcpListener, sync::mpsc};
12+
use tracing::{error, info};
13+
use tracing_subscriber::{filter::LevelFilter, EnvFilter};
14+
15+
/// Mock Datadog intake configuration, settable via environment variables
16+
#[derive(Parser)]
17+
#[command(name = "antithesis-intake")]
18+
struct Config {
19+
/// Address the HTTP intake binds and serves on.
20+
#[arg(long = "listen-addr", env = "LISTEN_ADDR", default_value = "0.0.0.0:2049")]
21+
listen_addr: String,
22+
23+
/// Agent hostname, resolved by Pyld17 (keep in sync with `hostname` in `datadog.yaml`)
24+
#[arg(long = "hostname", env = "DD_HOSTNAME", default_value = "antithesis-adp")]
25+
hostname: String,
26+
}
27+
28+
#[tokio::main]
29+
async fn main() {
30+
tracing_subscriber::fmt()
31+
.compact()
32+
.with_env_filter(
33+
EnvFilter::builder()
34+
.with_default_directive(LevelFilter::INFO.into())
35+
.from_env_lossy(),
36+
)
37+
.with_ansi(true)
38+
.with_target(true)
39+
.init();
40+
41+
antithesis_init();
42+
43+
if let Err(e) = serve(Config::parse()).await {
44+
error!("{:?}", e);
45+
std::process::exit(1);
46+
}
47+
info!("antithesis-intake stopped.");
48+
}
49+
50+
/// Build the intake app, bind the listener, and serve until a shutdown signal.
51+
async fn serve(config: Config) -> Result<()> {
52+
info!(hostname = %config.hostname, "Pyld17 resolves each series host against this hostname.");
53+
54+
let (shutdown_tx, mut shutdown_rx) = mpsc::channel(1);
55+
spawn_signal_handlers(shutdown_tx).context("Failed to configure signal handlers.")?;
56+
57+
let listener = TcpListener::bind(&config.listen_addr)
58+
.await
59+
.context("Failed to bind HTTP intake listener.")?;
60+
info!("antithesis-intake started: listening on {}.", config.listen_addr);
61+
62+
axum::serve(listener, build_router(Arc::from(config.hostname.as_str())))
63+
.with_graceful_shutdown(async move { shutdown_rx.recv().await.unwrap_or(()) })
64+
.await
65+
.map_err(Into::into)
66+
}
67+
68+
/// Spawn a task that signals shutdown on SIGINT or SIGTERM. Unix-only: this
69+
/// intake runs only in the Linux harness container.
70+
fn spawn_signal_handlers(shutdown_tx: mpsc::Sender<()>) -> Result<()> {
71+
use tokio::{
72+
select,
73+
signal::unix::{signal, SignalKind},
74+
};
75+
76+
let mut sigint_handler = signal(SignalKind::interrupt()).context("Failed to set up SIGINT handler.")?;
77+
let mut sigterm_handler = signal(SignalKind::terminate()).context("Failed to set up SIGTERM handler.")?;
78+
79+
tokio::spawn(async move {
80+
select! {
81+
_ = sigint_handler.recv() => info!("Received SIGINT, shutting down..."),
82+
_ = sigterm_handler.recv() => info!("Received SIGTERM, shutting down..."),
83+
}
84+
85+
if let Err(e) = shutdown_tx.send(()).await {
86+
error!("Failed to send shutdown signal: {:?}", e);
87+
}
88+
});
89+
90+
Ok(())
91+
}

0 commit comments

Comments
 (0)