Skip to content

Commit 90f327d

Browse files
committed
Vary datadog.yaml in test/antithesis, assert aliveness
This PR introduces variation in the datadog.yaml we use under test in the antithesis rig. The goal here is to explore variation in buffer sizes etc and also startup panics on truly weird configs. ADP aliveness on bootup via the way it is rigged into the compose cluster and we assert a 'sometimes' check to forwarding in datadog/io.rs. This later 'sometimes' acts as a checkpoint for antithesis, allowing it to figure that ADP has reached a nominally functional state and can be explored from that point. The antithesis setup checkpoint is done before datadog.yaml is sampled. Notable things: * first_sample_config runs after setup-checkpoint and before ADP boots, is responsible for creating datadog.yaml and other configs in the future * eventually_adp_alive is a weak check and we may drop it in the future as our coverage improves, but it doesn't hurt anything now * I introduced a harness::rand to encode antithesis-friendly sampling of large domains, this will expand over time * Skill `antithesis-research` has updated its 'scratchbook' but this is a mechanical domain for now, will later convert it to a human-hybrid material
1 parent ba3b71f commit 90f327d

22 files changed

Lines changed: 904 additions & 115 deletions

Cargo.lock

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

bin/agent-data-plane/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ workspace = true
1111
[features]
1212
default = []
1313
fips = ["saluki-app/tls-fips", "saluki-components/fips"]
14-
antithesis = ["dep:antithesis_sdk", "antithesis_sdk/full", "dep:antithesis-instrumentation"]
14+
antithesis = ["dep:antithesis_sdk", "antithesis_sdk/full", "dep:antithesis-instrumentation", "saluki-components/antithesis"]
1515

1616
[dependencies]
1717
antithesis-instrumentation = { workspace = true, optional = true }

lib/saluki-components/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,10 @@ workspace = true
1212
default = []
1313
config-test-support = []
1414
fips = ["saluki-io/fips"]
15+
antithesis = ["dep:antithesis_sdk", "antithesis_sdk/full"]
1516

1617
[dependencies]
18+
antithesis_sdk = { workspace = true, optional = true }
1719
arc-swap = { workspace = true }
1820
async-trait = { workspace = true }
1921
axum = { workspace = true }

lib/saluki-components/src/common/datadog/io.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -546,6 +546,19 @@ async fn process_http_response(
546546
if status.is_success() {
547547
debug!(endpoint_url, %status, "Request completed.");
548548

549+
// Reaching a successful intake response means the whole pipeline
550+
// ran. This is a useful signal for process health but also
551+
// acts as a checkpoint anchor for Antithesis replay: at this point
552+
// there is a nominally functional system.
553+
//
554+
// No-op outside the `antithesis` feature build.
555+
#[cfg(feature = "antithesis")]
556+
antithesis_sdk::assert_sometimes!(
557+
true,
558+
"ADP forwarded a payload to the intake",
559+
&serde_json::json!({ "domain": domain })
560+
);
561+
549562
telemetry.track_successful_transaction(&metadata, domain);
550563
} else {
551564
telemetry.track_permanently_failed_transaction(&metadata, Some(status), domain);

test/antithesis/deploy/Dockerfile

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,14 @@ RUN --mount=type=bind,source=rust-toolchain.toml,target=/tmp/rust-toolchain.toml
3232
# ---------------------------------------------------------------------------
3333
# Build the instrumented Agent Data Plane.
3434
#
35-
# Coverage instrumentation uses the modern Antithesis Rust flow (post-2026-05-22): the
36-
# `antithesis-instrumentation` crate (referenced once in main.rs behind the `antithesis` feature)
37-
# provides the runtime shim, and these RUSTFLAGS enable LLVM sancov coverage. `--build-id` is
38-
# required for symbolization; the release profile sets `debug = true`, so the binary keeps DWARF
39-
# for /symbols. LTO is disabled to keep sancov instrumentation predictable.
35+
# Coverage instrumentation uses the modern Antithesis Rust flow
36+
# (post-2026-05-22): the `antithesis-instrumentation` crate (referenced once in
37+
# main.rs behind the `antithesis` feature) provides the runtime shim, and these
38+
# RUSTFLAGS enable LLVM sancov coverage. `--build-id` is required for
39+
# symbolization; the release profile sets `debug = true`, so the binary keeps
40+
# DWARF for /symbols. LTO is disabled to keep sancov instrumentation
41+
# predictable. `panic = "abort"` (antithesis build only) turns any ADP panic
42+
# into SIGABRT, caught as a hard crash.
4043
# ---------------------------------------------------------------------------
4144
FROM build-base AS adp-builder
4245
ENV APP_FULL_NAME="Agent Data Plane" \
@@ -55,6 +58,7 @@ RUN --mount=type=cache,target=/adp/target,id=antithesis-adp-target \
5558
--mount=type=cache,target=/root/.cargo/git,id=cargo-git \
5659
cargo build --release --package agent-data-plane --features antithesis \
5760
--target x86_64-unknown-linux-gnu \
61+
--config 'profile.release.panic="abort"' \
5862
--config 'target.x86_64-unknown-linux-gnu.rustflags=["--cfg","tokio_unstable","-Ccodegen-units=1","-Cpasses=sancov-module","-Cllvm-args=-sanitizer-coverage-level=3","-Cllvm-args=-sanitizer-coverage-trace-pc-guard","-Clink-args=-Wl,--build-id"]' && \
5963
cp /adp/target/x86_64-unknown-linux-gnu/release/agent-data-plane /usr/local/bin/agent-data-plane && \
6064
echo "Validating Antithesis instrumentation symbols..." && \
@@ -74,11 +78,14 @@ RUN --mount=type=cache,target=/tools/target,id=antithesis-tools-target \
7478
--mount=type=cache,target=/root/.cargo/git,id=cargo-git \
7579
cargo build --release \
7680
--bin datadog-intake --bin millstone \
77-
--bin parallel_driver_send_dogstatsd --bin finally_verify_delivery && \
81+
--bin parallel_driver_send_dogstatsd --bin finally_verify_delivery --bin eventually_adp_alive \
82+
--bin first_sample_config && \
7883
cp /tools/target/release/datadog-intake /usr/local/bin/datadog-intake && \
7984
cp /tools/target/release/millstone /usr/local/bin/millstone && \
8085
cp /tools/target/release/parallel_driver_send_dogstatsd /usr/local/bin/parallel_driver_send_dogstatsd && \
81-
cp /tools/target/release/finally_verify_delivery /usr/local/bin/finally_verify_delivery
86+
cp /tools/target/release/finally_verify_delivery /usr/local/bin/finally_verify_delivery && \
87+
cp /tools/target/release/eventually_adp_alive /usr/local/bin/eventually_adp_alive && \
88+
cp /tools/target/release/first_sample_config /usr/local/bin/first_sample_config
8289

8390
# ---------------------------------------------------------------------------
8491
# Runtime: Agent Data Plane (SUT).
@@ -92,8 +99,12 @@ RUN apt-get update && \
9299
COPY --from=adp-builder /usr/local/bin/agent-data-plane /usr/local/bin/agent-data-plane
93100
# Expose DWARF/build-id symbols to Antithesis for symbolization (one-hop symlink to the unstripped binary).
94101
RUN mkdir -p /symbols && ln -s /usr/local/bin/agent-data-plane /symbols/agent-data-plane
95-
# main.rs requires the bootstrap config file to exist at the default path; ship a minimal standalone config.
102+
# main.rs requires the bootstrap config file to exist at the default path; ship a minimal standalone
103+
# config as a fallback. The boot wrapper overwrites it with the per-replay config written by the
104+
# `first_sample_config` workload command onto the shared `agent-config` volume.
96105
COPY test/antithesis/deploy/adp/datadog.yaml /etc/datadog-agent/datadog.yaml
106+
# Boot wrapper: waits for the drawn config sentinel, copies the config into place, then execs ADP.
107+
COPY --chmod=755 test/antithesis/deploy/adp/entrypoint.sh /entrypoint.sh
97108
# ADP's control-plane secure API requires an IPC TLS cert (a single PEM holding both certificate and
98109
# private key) that the Core Agent normally generates. In standalone mode there is no Core Agent, so
99110
# generate a self-signed cert+key. An empty auth_token satisfies the IPC auth config at startup.
@@ -103,7 +114,7 @@ RUN openssl req -x509 -newkey rsa:2048 -nodes -days 3650 \
103114
cat /tmp/ipc_cert.pem /tmp/ipc_key.pem > /etc/datadog-agent/ipc_cert.pem && \
104115
rm -f /tmp/ipc_cert.pem /tmp/ipc_key.pem && \
105116
touch /etc/datadog-agent/auth_token
106-
ENTRYPOINT ["/usr/local/bin/agent-data-plane"]
117+
ENTRYPOINT ["/entrypoint.sh"]
107118
CMD ["run"]
108119

109120
# ---------------------------------------------------------------------------
@@ -128,7 +139,9 @@ COPY --from=tools-builder /usr/local/bin/millstone /usr/local/bin/millstone
128139
COPY --chmod=755 test/antithesis/deploy/workload/setup-complete.sh /opt/antithesis/setup-complete.sh
129140
COPY test/antithesis/deploy/workload/test/ /opt/antithesis/test/
130141
# Inject the compiled test-command binaries into the "main" test template.
142+
COPY --from=tools-builder --chmod=755 /usr/local/bin/first_sample_config /opt/antithesis/test/v1/main/first_sample_config
131143
COPY --from=tools-builder --chmod=755 /usr/local/bin/parallel_driver_send_dogstatsd /opt/antithesis/test/v1/main/parallel_driver_send_dogstatsd
132144
COPY --from=tools-builder --chmod=755 /usr/local/bin/finally_verify_delivery /opt/antithesis/test/v1/main/finally_verify_delivery
145+
COPY --from=tools-builder --chmod=755 /usr/local/bin/eventually_adp_alive /opt/antithesis/test/v1/main/eventually_adp_alive
133146
COPY --chmod=755 test/antithesis/deploy/workload/entrypoint.sh /entrypoint.sh
134147
ENTRYPOINT ["/entrypoint.sh"]
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
# Agent Data Plane boot wrapper.
5+
#
6+
# first_sample_config writes this timeline's datadog.yaml + a `ready` sentinel to
7+
# the shared volume; we block on it, copy the config, then `exec` one stable ADP.
8+
# We block indefinitely rather than timing out and exiting non-zero, which would
9+
# be read as an ADP crash. The startup log below makes the wait visible in triage,
10+
# so a missing release shows as "waiting…" with no boot rather than a silent hang.
11+
12+
CONFIG_DIR="${AGENT_CONFIG_DIR:-/agent-config}"
13+
14+
echo "adp: waiting for ${CONFIG_DIR}/ready (released by first_sample_config)" >&2
15+
while [ ! -f "${CONFIG_DIR}/ready" ]; do
16+
sleep 1
17+
done
18+
19+
cp "${CONFIG_DIR}/datadog.yaml" /etc/datadog-agent/datadog.yaml
20+
21+
exec /usr/local/bin/agent-data-plane "$@"

test/antithesis/deploy/docker-compose.yaml

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -40,15 +40,11 @@ services:
4040
DD_DATA_PLANE_DOGSTATSD_ENABLED: "true"
4141
volumes:
4242
- dogstatsd-socket:/var/run/datadog
43+
# first_sample_config (workload) writes this timeline's datadog.yaml + ready sentinel here.
44+
- agent-config:/agent-config:ro
4345
depends_on:
4446
intake:
4547
condition: service_healthy
46-
healthcheck:
47-
# ADP's unprivileged API listens on TCP :5100 once the internal supervisor is up.
48-
test: ["CMD-SHELL", "bash -c 'exec 3<>/dev/tcp/localhost/5100'"]
49-
interval: 2s
50-
timeout: 2s
51-
retries: 60
5248

5349
workload:
5450
container_name: workload
@@ -62,19 +58,15 @@ services:
6258
image: workload:latest
6359
environment:
6460
NO_COLOR: "1"
65-
ADP_HOST: "adp"
66-
ADP_API_PORT: "5100"
6761
DSD_SOCKET: "/var/run/datadog/dsd.socket"
6862
INTAKE_ADDR: "intake:2049"
69-
INTAKE_HOST: "intake"
70-
INTAKE_PORT: "2049"
7163
volumes:
7264
- dogstatsd-socket:/var/run/datadog
65+
- agent-config:/agent-config
7366
depends_on:
74-
adp:
75-
condition: service_healthy
7667
intake:
7768
condition: service_healthy
7869

7970
volumes:
8071
dogstatsd-socket:
72+
agent-config:

test/antithesis/deploy/workload/entrypoint.sh

Lines changed: 3 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -3,51 +3,9 @@ set -euo pipefail
33

44
# Workload client entrypoint.
55
#
6-
# By the time this runs, docker-compose has gated startup on the `adp` and `intake` services being
7-
# healthy (depends_on: condition: service_healthy). We re-confirm reachability defensively, emit the
8-
# Antithesis `setup_complete` signal, then idle so Antithesis can run test commands from the test
9-
# template at /opt/antithesis/test/v1/.
6+
# Gated on intake-healthy (compose `depends_on`). Emit `setup_complete`, then
7+
# idle so Antithesis runs the test commands.
108

11-
ADP_HOST="${ADP_HOST:-adp}"
12-
ADP_API_PORT="${ADP_API_PORT:-5100}"
13-
DSD_SOCKET="${DSD_SOCKET:-/var/run/datadog/dsd.socket}"
14-
INTAKE_HOST="${INTAKE_HOST:-intake}"
15-
INTAKE_PORT="${INTAKE_PORT:-2049}"
16-
17-
wait_for_tcp() {
18-
local host="$1" port="$2" name="$3" tries=60
19-
echo "Waiting for ${name} (${host}:${port})..."
20-
while (( tries-- > 0 )); do
21-
if (exec 3<>"/dev/tcp/${host}/${port}") 2>/dev/null; then
22-
echo "${name} is reachable."
23-
return 0
24-
fi
25-
sleep 1
26-
done
27-
echo "Timed out waiting for ${name} (${host}:${port})." >&2
28-
return 1
29-
}
30-
31-
wait_for_socket() {
32-
local path="$1" name="$2" tries=60
33-
echo "Waiting for ${name} (${path})..."
34-
while (( tries-- > 0 )); do
35-
if [[ -S "${path}" ]]; then
36-
echo "${name} is reachable."
37-
return 0
38-
fi
39-
sleep 1
40-
done
41-
echo "Timed out waiting for ${name} (${path})." >&2
42-
return 1
43-
}
44-
45-
wait_for_tcp "${ADP_HOST}" "${ADP_API_PORT}" "agent-data-plane API"
46-
wait_for_socket "${DSD_SOCKET}" "agent-data-plane DogStatsD socket"
47-
wait_for_tcp "${INTAKE_HOST}" "${INTAKE_PORT}" "datadog-intake"
48-
49-
echo "System is ready. Emitting setup_complete."
509
/opt/antithesis/setup-complete.sh
51-
52-
echo "Workload client idle; awaiting Antithesis test commands."
10+
echo "setup_complete emitted; workload idle, awaiting Antithesis test commands."
5311
exec tail -f /dev/null

test/antithesis/harness/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,12 @@ clap = { workspace = true, features = [
1616
"std",
1717
"usage",
1818
] }
19+
num-traits = { workspace = true }
1920
rand = { workspace = true }
21+
rand_distr = { workspace = true }
22+
serde = { workspace = true }
2023
serde_json = { workspace = true }
24+
serde_yaml = { workspace = true }
2125

2226
[lints.clippy]
2327
all = "deny"
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
//! Antithesis `eventually_` liveness check: ADP booted and became reachable
2+
//! within a bounded window.
3+
//!
4+
//! `eventually_` commands run in a fault-quiet period, so a node-fault induced
5+
//! kill of ADP does not trip this check but a self-inflicted process exit
6+
//! does. This triggers on ADP's own bugs, rather than antithesis fault
7+
//! injection.
8+
//!
9+
//! We check two signals. First that ADP is reachable on :5100 and second that
10+
//! it created a `DogStatsD` listener socket.
11+
12+
use std::net::{TcpStream, ToSocketAddrs};
13+
use std::os::unix::fs::FileTypeExt;
14+
use std::path::PathBuf;
15+
use std::thread::sleep;
16+
use std::time::Duration;
17+
18+
use antithesis_sdk::prelude::*;
19+
use clap::{builder::NonEmptyStringValueParser, Parser};
20+
use serde_json::json;
21+
22+
#[derive(Debug, Parser)]
23+
#[command(name = "eventually_adp_alive")]
24+
struct Config {
25+
#[arg(
26+
long = "adp-api-addr",
27+
env = "ADP_API_ADDR",
28+
default_value = "adp:5100",
29+
value_parser = NonEmptyStringValueParser::new()
30+
)]
31+
adp_api_addr: String,
32+
#[arg(
33+
long = "dsd-socket",
34+
env = "DSD_SOCKET",
35+
default_value = "/var/run/datadog/dsd.socket"
36+
)]
37+
dsd_socket: PathBuf,
38+
}
39+
40+
fn main() -> anyhow::Result<()> {
41+
antithesis_init();
42+
let config = Config::try_parse()?;
43+
44+
let mut api_reachable = false;
45+
let mut socket_present = false;
46+
// Check that the adp-api is reachable and the DogStatsD socket exists for
47+
// about 60 seconds. A 1s connect timeout keeps the poll cadence bounded
48+
// even when the API host is unresponsive.
49+
for _ in 0..60 {
50+
api_reachable = config
51+
.adp_api_addr
52+
.to_socket_addrs()
53+
.ok()
54+
.and_then(|mut addrs| addrs.next())
55+
.is_some_and(|addr| TcpStream::connect_timeout(&addr, Duration::from_secs(1)).is_ok());
56+
socket_present = config.dsd_socket.metadata().is_ok_and(|m| m.file_type().is_socket());
57+
if api_reachable && socket_present {
58+
break;
59+
}
60+
sleep(Duration::from_secs(1));
61+
}
62+
63+
assert_always!(
64+
api_reachable && socket_present,
65+
"ADP booted: API reachable and DogStatsD socket present",
66+
&json!({
67+
"adp_api_addr": config.adp_api_addr,
68+
"dsd_socket": config.dsd_socket.display().to_string(),
69+
"api_reachable": api_reachable,
70+
"socket_present": socket_present,
71+
})
72+
);
73+
74+
Ok(())
75+
}

0 commit comments

Comments
 (0)