Skip to content

Commit a827246

Browse files
VijitSingh97claude
andauthored
test(tier3): fake monerod + failover scenarios in the mini-stack (#616)
Wires a fake monerod (control endpoint to toggle down/busy/synced) into docker-compose.fake.yml and sets LOCAL_MONERO_HOST=fake-monerod so the dashboard actually probes it (else monerod is treated remote and never checked). Adds CI-runnable scenarios: monerod down→reject→readmit (#31/#564), busy/mid-reorg reject, double-outage both-must-recover, and Tari-optional-keeps-mining (#562, its own compose cycle since it's a boot-time flag). Fixes the stale header comment (said monerod, tested Tari). Closes #562 Closes #564 Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 46db88a commit a827246

3 files changed

Lines changed: 105 additions & 26 deletions

File tree

docs/testing-strategy.md

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -64,10 +64,11 @@ The deploy-time axes — each changes a real runtime path. Full table and assert
6464
| Situation | Trigger | Tier |
6565
|---|---|---|
6666
| monerod down → **reject workers** (stop `xmrig-proxy`) | unreachable ≥ `NODE_DOWN_AFTER_SEC` | 1 ✅ · 3 ▶ · 4 ▶ |
67-
| Tari down + required → reject; Tari down + non-blocking → **ignore** | `tari_down ∧ TARI_REQUIRED?` | 1 ✅ |
67+
| monerod busy / mid-reorg (HTTP 200, `status≠OK`) → **reject workers** | RPC answers but distrusted | 1 ✅ · 3 ▶ |
68+
| Tari down + required → reject; Tari down + non-blocking → **ignore** | `tari_down ∧ TARI_REQUIRED?` | 1 ✅ · 3 ▶ |
6869
| Recovery hysteresis — readmit only after stable `NODE_RECOVERY_AFTER_SEC` | reachable again | 1 ✅ |
6970
| Transient blip / never-reachable → **no** false reject | debounce / `ever_up` | 1 ✅ |
70-
| Double outage; readmit only when **both** healthy | both down → both up | 1 ✅ (added) |
71+
| Double outage; readmit only when **both** healthy | both down → both up | 1 ✅ (added) · 3 ▶ |
7172
| #35 latch × #31 failover coexist after release | down post-release | 1 ✅ (added) · 3 ▶ |
7273
| Stop/start fails → retry next cycle (idempotent) | docker error | 1 ✅ |
7374

@@ -259,16 +260,22 @@ tier 3/4:
259260
root-owned file under the dashboard data dir and asserts the pool-flip `apply` (which runs
260261
`ensure_directories``ensure_owner`) chowns it to uid 1000 — the #255 "scan contents, not just
261262
the dir" regression. Runs at the release gate only (needs root to create a foreign-uid inode).
262-
- **Real-container monerod failover in PR CI.** The primary-node reject/readmit cycle only runs on
263-
the manual tier-4 box (`--fault-injection`); the mini-stack (tier 3) breaks Tari, not monerod.
264-
- **Non-blocking-Tari "ignore" path with real containers.** Unit-tested only; the mini-stack proves
265-
Tari-down-while-required (reject) but never Tari-down-while-optional (keep mining). This is the
266-
path that silently kills yield if it regresses to a reject.
267-
- **monerod busy / mid-reorg failover.** The contract test proves the client reads a busy node as
268-
unreachable; no mini-stack or fault-injection scenario asserts the dashboard actually rejects
269-
workers on a busy-but-alive node (a real reorg state, distinct from a clean stop).
270-
- **Double outage, both-must-recover.** Unit-tested (monerod ∧ Tari down → readmit only when both
271-
healthy); never driven with real containers, so the recovery ordering is unproven end-to-end.
263+
- **Real-container monerod failover in PR CI.** ✅ Now tier-3 scenarios 6/7 in the mini-stack: the
264+
compose env had a fake `monerod` container wired at the network level (`MONERO_RPC_URL`) but the
265+
dashboard's `LOCAL_MONERO_HOST` default didn't match it, so `MONERO_NODE_HOST != LOCAL_MONERO_HOST`
266+
put the dashboard on the "remote" code path, which never probes reachability — a monerod outage
267+
was a silent no-op end-to-end. Setting `LOCAL_MONERO_HOST` to the fake's hostname fixed the wiring;
268+
scenarios 6/7 down/readmit the real `itest-xmrig-proxy` container against it. The tier-4
269+
`--fault-injection` box run still covers the real binary/real kernel leg.
270+
- **Non-blocking-Tari "ignore" path with real containers.** ✅ Mini-stack scenario 11: recreates the
271+
stack with `dashboard.tari_required=false` (baked in at container boot, so it needs its own
272+
compose cycle) and asserts `itest-xmrig-proxy` stays running through a Tari outage — the path that
273+
silently kills yield if it regresses to a reject.
274+
- **monerod busy / mid-reorg failover.** ✅ Mini-stack scenario 8: the fake's `busy` mode (HTTP 200,
275+
`status≠OK`) drives the same reject/readmit cycle as a clean outage.
276+
- **Double outage, both-must-recover.** ✅ Mini-stack scenario 9: both monerod and Tari down →
277+
rejected; recovering only Tari leaves it rejected; recovering monerod too readmits — the recovery
278+
ordering proven end-to-end, not just at the unit level.
272279
- **Partial-start / stop-failure idempotency.** The control loop's "container fails to start/stop →
273280
retry next cycle" is unit-only; no tier-3/4 scenario injects a docker start/stop error.
274281
- **`pithead doctor` on a real box.** ✅ The `--check` phase now runs `doctor` and asserts exit 0

tests/integration/mini-stack/docker-compose.fake.yml

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,12 @@ services:
4444
MONERO_NODE_USERNAME: ""
4545
MONERO_NODE_PASSWORD: ""
4646
MONERO_NODE_HOST: "fake-monerod"
47+
# Must equal MONERO_NODE_HOST so the dashboard takes the "local" RPC-driven path
48+
# (mining_dashboard.collector.logs.get_monero_sync_status) instead of the "remote" path,
49+
# which never probes reachability and makes monerod-down/#31/#564 a silent no-op — the
50+
# fake-monerod container below was wired at the network level but never actually read for
51+
# node-health without this.
52+
LOCAL_MONERO_HOST: "fake-monerod"
4753
MONERO_PRUNE: "true"
4854
TARI_GRPC_ADDRESS: "fake-tari:18142"
4955
DOCKER_PROXY_URL: "tcp://docker-proxy:2375"
@@ -52,7 +58,9 @@ services:
5258
# deployment's p2pool/xmrig-proxy on the same host.
5359
SYNC_GATE_CONTAINERS: "itest-p2pool,itest-xmrig-proxy"
5460
REJECT_WORKERS_CONTAINER: "itest-xmrig-proxy"
55-
TARI_REQUIRED: "true"
61+
# Overridable (scenario 11, #562) since it's baked in at container boot — a live toggle
62+
# isn't possible, so that scenario recreates the stack with TARI_REQUIRED=false.
63+
TARI_REQUIRED: "${TARI_REQUIRED:-true}"
5664
XVB_ENABLED: "false"
5765
XVB_POOL_URL: ""
5866
XVB_DONOR_ID: ""

tests/integration/mini-stack/run-mini-stack.sh

Lines changed: 77 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,19 @@
66
# test-mini-stack`.
77
#
88
# Scenarios:
9-
# 1. boot syncing → dashboard HOLDS itest-p2pool + itest-xmrig-proxy (#35)
10-
# 2. both chains synced → dashboard RELEASES them
11-
# 3. monerod down → dashboard REJECTS workers (stops itest-xmrig-proxy) (#31)
12-
# 4. monerod back → dashboard READMITS workers
13-
# +. healthchecks (#79) → REAL loop fires a liveness heartbeat to the fake-hc receiver
14-
# (pure dead-man's switch; node-health alerting is Telegram #121)
9+
# 1. boot syncing → dashboard HOLDS itest-p2pool + itest-xmrig-proxy (#35)
10+
# 2. monerod synced, Tari not → still HELD (the gate needs both required chains)
11+
# 3. both chains synced → dashboard RELEASES them
12+
# +. healthchecks (#79) → REAL loop fires a liveness heartbeat to the fake-hc receiver
13+
# (pure dead-man's switch; node-health alerting is Telegram #121)
14+
# 4. Tari down (required) → dashboard REJECTS workers (stops itest-xmrig-proxy) (#31)
15+
# 5. Tari back → dashboard READMITS workers
16+
# 6. monerod down → dashboard REJECTS workers (#31/#564)
17+
# 7. monerod back → dashboard READMITS workers (#564)
18+
# 8. monerod busy/mid-reorg → dashboard REJECTS, then READMITS on recovery
19+
# 9. monerod + Tari both down → REJECTS; recovering only one does NOT readmit; both does
20+
# 10. dashboard restart → the one-way sync latch survives (#35 persistence)
21+
# 11. Tari OPTIONAL, Tari down → dashboard keeps mining, workers stay accepted (#562)
1522
#
1623
set -uo pipefail
1724

@@ -191,10 +198,6 @@ log "scenario 3b: the dashboard fires a real healthchecks heartbeat"
191198
wait_hc "healthchecks: real loop fired a liveness heartbeat" '^/ph$' 40
192199

193200
# 4. Tari down while required → reject workers (stop the proxy); itest-p2pool keeps running. (#31)
194-
# NOTE: monerod-down failover is deliberately NOT simulated here — the dashboard's monerod
195-
# down-path falls back to log-scraping a real `monerod` container, which this fake stack has
196-
# no equivalent of. That path is covered on real hardware by the tier-4 --fault-injection
197-
# phase. Tari has no such fallback, so its reject/readmit exercises the failover cleanly.
198201
log "scenario 4: rejects workers when required Tari is down"
199202
set_tari down
200203
assert_state "rejected on Tari outage: itest-xmrig-proxy stopped" itest-xmrig-proxy exited 90
@@ -209,9 +212,46 @@ log "scenario 5: readmits workers when Tari recovers"
209212
set_tari synced
210213
assert_state "readmitted after Tari recovery: itest-xmrig-proxy running" itest-xmrig-proxy running 90
211214

212-
# 6. Dashboard restart after release → the one-way latch is persisted, so the miner is NOT
213-
# re-held: both containers stay running across the restart. (#35 persistence)
214-
log "scenario 6: a dashboard restart does not re-hold a released miner"
215+
# 6. monerod down → reject workers (stop the proxy); itest-p2pool keeps running. (#31/#564)
216+
# Needs LOCAL_MONERO_HOST == MONERO_NODE_HOST in the compose env — otherwise the dashboard
217+
# treats monerod as "remote" and never probes it for reachability at all (see that env var's
218+
# comment in docker-compose.fake.yml).
219+
log "scenario 6: rejects workers when monerod is down"
220+
set_monerod down
221+
assert_state "rejected on monerod outage: itest-xmrig-proxy stopped" itest-xmrig-proxy exited 90
222+
if [ "$(cstate itest-p2pool)" = "running" ]; then
223+
c_ok "monerod-outage rejection leaves itest-p2pool running (only the proxy fails over)"
224+
else
225+
c_bad "monerod-outage rejection leaves itest-p2pool running" "itest-p2pool is '$(cstate itest-p2pool)'"
226+
fi
227+
228+
# 7. monerod recovers → readmit (after the recovery-hysteresis window). (#564)
229+
log "scenario 7: readmits workers when monerod recovers"
230+
set_monerod synced
231+
assert_state "readmitted after monerod recovery: itest-xmrig-proxy running" itest-xmrig-proxy running 90
232+
233+
# 8. monerod busy (HTTP 200 but status != OK, e.g. mid-reorg) — the client must distrust the
234+
# heights and treat the node as unreachable, not synced, the same as a clean outage.
235+
log "scenario 8: rejects workers when monerod reports busy/mid-reorg"
236+
set_monerod busy
237+
assert_state "rejected on monerod busy: itest-xmrig-proxy stopped" itest-xmrig-proxy exited 90
238+
set_monerod synced
239+
assert_state "readmitted after monerod busy clears: itest-xmrig-proxy running" itest-xmrig-proxy running 90
240+
241+
# 9. Double outage — monerod AND Tari both down → rejected; recovering only Tari must NOT
242+
# readmit (monerod is still down); recovering monerod too readmits both.
243+
log "scenario 9: double outage — readmits only once BOTH nodes recover"
244+
set_monerod down
245+
set_tari down
246+
assert_state "rejected on double outage: itest-xmrig-proxy stopped" itest-xmrig-proxy exited 90
247+
set_tari synced
248+
assert_stays "still rejected with only Tari recovered" itest-xmrig-proxy exited 8
249+
set_monerod synced
250+
assert_state "readmitted once both nodes recovered: itest-xmrig-proxy running" itest-xmrig-proxy running 90
251+
252+
# 10. Dashboard restart after release → the one-way latch is persisted, so the miner is NOT
253+
# re-held: both containers stay running across the restart. (#35 persistence)
254+
log "scenario 10: a dashboard restart does not re-hold a released miner"
215255
compose restart dashboard >/dev/null 2>&1
216256
for _ in $(seq 1 30); do
217257
compose exec -T dashboard python3 -c \
@@ -221,6 +261,30 @@ done
221261
assert_stays "itest-p2pool stays up across restart" itest-p2pool running 6
222262
assert_stays "itest-xmrig-proxy stays up across restart" itest-xmrig-proxy running 6
223263

264+
# 11. Tari OPTIONAL (dashboard.tari_required=false) → a Tari outage must NOT reject workers;
265+
# Monero mining keeps going. (#562 — the silent-yield-loss path: unit-tested at
266+
# test_data_service.py::test_tari_down_ignored_when_non_blocking; this drives the same
267+
# decision through real containers.) TARI_REQUIRED is baked into the dashboard container at
268+
# boot, so this needs its own compose cycle rather than a live toggle.
269+
log "scenario 11: Tari-optional — a Tari outage does not reject workers (#562)"
270+
compose down -v --remove-orphans >/dev/null 2>&1 || true
271+
TARI_REQUIRED=false compose up -d >/dev/null 2>&1
272+
api_up=0
273+
for _ in $(seq 1 30); do
274+
if compose exec -T dashboard python3 -c \
275+
"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/api/state', timeout=3)" >/dev/null 2>&1; then
276+
api_up=1
277+
break
278+
fi
279+
sleep 2
280+
done
281+
[ "$api_up" = 1 ] && c_ok "Tari-optional stack: dashboard API is up" || c_bad "Tari-optional stack: dashboard API is up" "no /api/state after ~60s"
282+
# Tari is non-blocking, so monerod alone gates the sync-hold; release it to reach steady state.
283+
set_monerod synced
284+
assert_state "Tari-optional: released itest-xmrig-proxy running" itest-xmrig-proxy running 90
285+
set_tari down
286+
assert_stays "Tari-optional: itest-xmrig-proxy keeps mining through a Tari outage" itest-xmrig-proxy running 8
287+
224288
echo ""
225289
log "mini-stack: $PASS passed, $FAIL failed"
226290
[ "$FAIL" -eq 0 ]

0 commit comments

Comments
 (0)