4.6 KiB
Fix Docker E2E Tests and Enable Local Reproduction
Status: Complete
Summary
Both CI E2E tests (single-node and multi-node) are failing after recent multi-server changes. This improvement fixes the root causes and adds a local runner script so developers can reproduce E2E tests without pushing to CI.
Context / Problem Statement
Single-Node E2E Failure
Symptom: DescribeInstanceTypes returns empty — logs show "nats: no responders available for request".
Root cause: Race condition. The E2E script waits for the gateway HTTP endpoint (curl -s https://localhost:9999), but this only confirms the gateway process is listening — not that the daemon has connected to NATS and subscribed to topics.
Daemon startup path: connectNATS() -> ClusterManager() -> initJetStream() -> create services -> restoreInstances() -> subscribeAll(). If the gateway accepts requests before the daemon finishes subscribeAll(), NATS publishes find no subscribers.
Note: DescribeRegions and DescribeAvailabilityZones succeed because they're handled locally in the gateway (gateway/ec2.go) without NATS. DescribeInstanceTypes is the first action that requires daemon NATS subscriptions.
Multi-Node E2E Failure
Symptom: Formation server waits 10 minutes for nodes to join, then times out (1/3 nodes joined).
Root cause: init_leader_node() calls hive admin init without explicit --nodes flag. The default is --nodes 3 (admin.go:156). Combined with --bind 10.11.12.1 (not 0.0.0.0), this triggers the multi-node formation path:
isMultiNode := nodes >= 2 && bindIP != "0.0.0.0"
The formation server starts and blocks at WaitForCompletion(formationTimeout) waiting for 2 more nodes. But the E2E script is sequential — join_follower_node 2 and 3 run AFTER init_leader_node returns, creating a deadlock.
Additionally, hive admin join also blocks — it polls /formation/status until complete: true (all nodes joined). So even if init were backgrounded, running join_2 then join_3 sequentially would deadlock because join_2 blocks waiting for node_3.
Timing of CA cert and config files
The runAdminInit flow (admin.go):
- Cert generation (
admin.GenerateCertificatesIfNeeded) — happens BEFORE multi-node check - Multi-node path starts formation server, blocks at
WaitForCompletion - Config generation happens AFTER all nodes join
This means:
~/node1/config/ca.pem— available as soon as formation server starts (generated beforehand)~/node1/config/hive.tomland other configs — only available after ALL nodes join
Proposed Changes
1. Single-node fix: daemon readiness check
Added wait_for_daemon_ready() helper in multinode-helpers.sh that polls describe-instance-types until it returns a non-empty result. Called after gateway health check in both E2E scripts.
2. Multi-node fix: concurrent formation orchestration
Restructured init_leader_node() to background the init process and poll for formation server readiness. Restructured Phase 2 of run-multinode-e2e.sh to:
- Background init (starts formation server, generates certs first)
- Trust CA cert (exists before formation completes)
- Background BOTH joins concurrently (they poll
/formation/status) - Wait for all three processes to finish
- Start services after formation completes (configs now exist)
This mirrors production where init and joins run on separate machines simultaneously.
3. Local E2E runner script
Created scripts/run-e2e-local.sh that mirrors the CI workflow (e2e.yml) for local Docker E2E testing. Supports running single-node, multi-node, or both suites.
Files to Modify
| File | Change |
|---|---|
tests/e2e/lib/multinode-helpers.sh |
Added wait_for_daemon_ready(), restructured init_leader_node() to background init and poll formation health |
tests/e2e/run-e2e.sh |
Added wait_for_daemon_ready call after gateway wait |
tests/e2e/run-multinode-e2e.sh |
Restructured Phase 2 for concurrent formation, added wait_for_daemon_ready |
scripts/run-e2e-local.sh |
New local Docker E2E runner script |
.claude/CLAUDE.md |
Added git history guidance to Development Process section |
Testing
- Multi-node fix: Run
./scripts/run-e2e-local.sh multi— formation server starts, all 3 joins complete concurrently, configs generated, services start, tests pass - Single-node fix: Run
./scripts/run-e2e-local.sh single— daemon readiness check passes,describe-instance-typesreturns instance types - Full local E2E: Run
./scripts/run-e2e-local.sh— both suites pass - CI validation: Push and verify GitHub Actions E2E workflow passes