Skip to content

feat(e2e): two-container split topology under Compose (dc-ros + vector) - #458

Merged
Minipada merged 3 commits into
jazzyfrom
feature/445-two-container-split-ros-stack-and-shippe
Aug 31, 2026
Merged

feat(e2e): two-container split topology under Compose (dc-ros + vector)#458
Minipada merged 3 commits into
jazzyfrom
feature/445-two-container-split-ros-stack-and-shippe

Conversation

@Minipada

Copy link
Copy Markdown
Owner

Summary

Scenario 2 of #440's split-deployment epic: dc-ros and vector run as separate Compose-managed containers on one shared network instead of one process tree.

  • tools/e2e/compose.split.yaml — a standalone, runnable Compose deployment: vector runs unmodified from the upstream timberio/vector:0.57.0-debian image (matching vector_vendor's pinned version), waits for dc-ros's atomically-rendered config on a shared volume, then runs with --watch-config so a later render reloads without a restart. dc-ros runs in unmanaged-shipper mode (Bridge: unmanaged-shipper mode and atomic config write #444). No depends_on anywhere between the two — dc_bringup's existing readiness gate is what lets dc-ros recover on its own if the Shipper starts late. The Shipper's buffer and the Bridge's upload state stay on separate volumes (Split shipper.data_dir into separate Shipper and Uploader directories #441).
  • Discovered while wiring this up: dc_bridge's Forwarder/readiness prober parse vector_forward_host with inet_pton() (a literal IPv4 parse, not a resolver call), so vector needs a fixed compose-network IP rather than its DNS name. Worked around at the deployment level (pinned subnet + static ipv4_address) — no dc_bridge code change.
  • tools/e2e/scripts/run_split.sh folds the topology into the zero-loss E2E harness: starts dc-ros well before vector to prove no orchestrator-level ordering is required, then runs the same steady-state/outage/restart/drain sequence as run.sh, reusing verify_zero_loss.py's Postgres/ledger/upload-intent-queue checks.
  • verify_zero_loss.py's --passthrough-file/--mcap-summary-file/--raw-file flags become optional (were required=True) since this scenario's own params file carries no passthrough/MCAP/raw config — out of scope for a container-boundary proof. Every existing caller keeps passing all three unchanged, so run.sh/run_degraded.sh are unaffected.

Not wired into ci.yaml, matching every other narrow E2E scenario (retention/incident/degraded/limits axes).

Closes #445

Test plan

  • prek run --all-files --skip build-doc passes (ruff, shellcheck, clang-format, REUSE, YAML/JSON/TOML syntax, etc.)
  • bash -n + shellcheck clean on run_split.sh
  • compose.split.yaml / e2e_split_params.yaml parse as valid YAML
  • Verified empirically with podman compose: postgres/rustfs come up and are reachable via compose service-name DNS; vector gets its pinned static IP; the vector service genuinely blocks until the shared config file appears, then starts and picks up a config-file change via --watch-config with no restart (confirmed in the container's own logs)
  • Confirmed docker.io/timberio/vector:0.57.0-debian is a real, pullable tag matching vector_vendor's pinned version
  • Full run_split.sh execution against a built dc-e2e image (a full colcon build of the workspace was outside this session's time budget; the harness reuses run.sh's own build path unchanged, and the parts specific to this issue — compose mechanics, the wait/watch entrypoint, DNS vs. static-IP addressing — were validated directly against real containers as above)

Scenario 2 of #440's split-deployment epic: dc-ros and vector as separate
Compose-managed containers on one shared network, instead of one process
tree. vector runs unmodified from the upstream image (docker.io/timberio/
vector:0.57.0-debian, matching vector_vendor's pinned version), waits for
dc-ros's atomically-rendered config to appear on a shared volume, then
runs with --watch-config so a later render reloads without a restart.
dc-ros runs in unmanaged-shipper mode (#444) with no depends_on on
vector anywhere in compose.split.yaml — dc_bringup's existing readiness
gate is what lets it recover on its own if the Shipper starts late. The
Shipper's buffer and the Bridge's upload state stay on separate volumes,
matching #441.

Discovered while wiring this up: dc_bridge's Forwarder and readiness
prober parse vector_forward_host with inet_pton() (a literal IPv4 parse,
not a resolver call), so the vector service needs a fixed compose-network
IP rather than its DNS name — worked around at the deployment level (a
pinned subnet + static ipv4_address), no dc_bridge change needed.

tools/e2e/scripts/run_split.sh folds the scenario into the zero-loss E2E
harness: starts dc-ros well before vector to prove no orchestrator-level
ordering is required, then runs the same steady-state/outage/restart/
drain sequence as run.sh, reusing verify_zero_loss.py's Postgres/ledger/
upload-intent-queue checks. Its own params file carries no passthrough/
MCAP/raw config (out of scope for this container-boundary proof), so
verify_zero_loss.py's --passthrough-file/--mcap-summary-file/--raw-file
flags become optional — every existing caller keeps passing all three
unchanged.

Not wired into ci.yaml, same as every other narrow E2E scenario.

Closes #445

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GAFgrfo5YzvvnUW4i6eTVu
Signed-off-by: David Bensoussan <d.bensoussan@proton.me>
@codecov

codecov Bot commented Aug 31, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 87.50000% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 71.03%. Comparing base (393b4f0) to head (1d45658).

Files with missing lines Patch % Lines
dc_bridge/src/forwarder.cpp 33.34% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##            jazzy     #458      +/-   ##
==========================================
+ Coverage   71.00%   71.03%   +0.03%     
==========================================
  Files         120      121       +1     
  Lines        7462     7471       +9     
==========================================
+ Hits         5298     5306       +8     
- Misses       2164     2165       +1     
Flag Coverage Δ
cpp-jazzy 71.03% <87.50%> (+0.03%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

forwarder.cpp and readiness.cpp parsed the Shipper's host with
inet_pton() — a literal IPv4 parse, not a resolver call — so a hostname
there (e.g. a Compose service name in the split-deployment topology,
#445) failed startup immediately with "invalid host address". Add
dc_bridge_core's resolve_ipv4() (net_resolve.hpp/.cpp), backed by
getaddrinfo(), and use it in both places; a literal IP still resolves
with no network round trip, so every existing IP-based deployment is
unaffected.

Update compose.split.yaml/e2e_split_params.yaml to point
vector_forward_host at the plain "vector" service name now that it
resolves correctly, dropping the fixed-subnet/static-IP workaround from
the previous commit.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GAFgrfo5YzvvnUW4i6eTVu
Signed-off-by: David Bensoussan <d.bensoussan@proton.me>
@Minipada

Copy link
Copy Markdown
Owner Author

Follow-up commit: fixed the root cause instead of working around it. `dc_bridge`'s Forwarder and readiness prober now resolve `vector_forward_host` via `getaddrinfo()` (new `dc_bridge/src/net_resolve.cpp`) instead of the old literal-IPv4-only `inet_pton()` parse. `compose.split.yaml`/`e2e_split_params.yaml` now use the plain `vector` service name — the fixed-subnet/static-IP workaround from the first commit is gone.

Added unit coverage: NetResolve.* (misc_test.cpp) for the resolver itself, Readiness.ProbeAcceptsAHostnameNotOnlyALiteralIp, and Forwarder.ConnectsUsingAHostnameNotOnlyALiteralIp (a real hostname connect against a mock ingest peer). Existing IP-literal configs are unaffected — getaddrinfo() returns a literal address as-is, no network round trip.

Shorten the compose.split.yaml/e2e_split_params.yaml/run_split.sh
headers and a few C++ comments down to one or two lines each, per
CLAUDE.md's comment guidance. No behavior change.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GAFgrfo5YzvvnUW4i6eTVu
Signed-off-by: David Bensoussan <d.bensoussan@proton.me>
@Minipada
Minipada merged commit afcda63 into jazzy Aug 31, 2026
9 checks passed
@github-actions
github-actions Bot deleted the feature/445-two-container-split-ros-stack-and-shippe branch August 31, 2026 13:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant