#!/usr/bin/env bash
# tests/dc-dc-dr-drill/run-tests.sh -- offline harness for
# scripts/dc-dc-dr-drill.sh. Only tests THIS SCRIPT's own logic: argument
# parsing / guard clauses, the --confirmed-down gate on failover --apply,
# the failback --apply typed-confirmation gate, and -- the single most
# important assertion in this suite -- that the failback PLAN's printed
# step order is demote-recovering, then demote-CURRENT-PRIMARY-before-
# promote-recovering, verified by grepping the actual LINE ORDER of the
# dry-run output (not just that all four lines are present). Does NOT and
# CANNOT validate real rbd-mirror promote/demote behavior -- no live
# cluster exists this session (2026-07-10, prep-only).
# Exit: 0 all pass | 1 any case failed. ASCII + LF.
set -uo pipefail
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SCRIPT="$HERE/../../scripts/dc-dc-dr-drill.sh"
PASS=0; FAIL=0
run() { # run <want_rc> <regex> <label> -- args...
local want="$1" rx="$2" label="$3"; shift 3
local out rc
out="$(bash "$SCRIPT" "$@" 2>&1)"; rc=$?
if [[ "$rc" == "$want" ]] && grep -qE "$rx" <<<"$out"; then
echo " PASS $label"; PASS=$((PASS+1))
else
echo " FAIL $label (rc=$rc want=$want)"; echo "$out" | sed 's/^/ /'; FAIL=$((FAIL+1))
fi
}
run 1 'a subcommand is required' "T1 no subcommand FAILS (rc 1)"
run 1 'unknown subcommand' "T2 bogus subcommand FAILS (rc 1)" bogus
run 0 'usage:' "T3 --help exits 0 with usage" --help
# --- failover arg parsing ---
run 1 '\-\-dc is required' "T4 failover missing --dc FAILS" failover
run 1 '\-\-dc must be dc1 or dc2' "T5 failover bad --dc token FAILS" failover --dc bogus --unit ceph-mon/0 --pool glance
run 1 '\-\-unit is required' "T6 failover missing --unit FAILS" failover --dc dc1
run 1 'never assume .glance.' "T7 failover missing --pool FAILS, cites no-assume-glance" failover --dc dc1 --unit ceph-mon/0
# --- failback arg parsing ---
run 1 '\-\-pool is required' "T8 failback missing --pool FAILS" failback
run 1 '\-\-recovering-dc is required' "T9 failback missing --recovering-dc FAILS" failback --pool glance
run 1 '\-\-primary-dc is required' "T10 failback missing --primary-dc FAILS" failback --pool glance --recovering-dc dc1
run 1 'must differ' "T11 failback recovering-dc == primary-dc FAILS" failback --pool glance --recovering-dc dc1 --primary-dc dc1 --recovering-unit u/0 --primary-unit u/1
run 1 '\-\-recovering-unit is required' "T12 failback missing --recovering-unit FAILS" failback --pool glance --recovering-dc dc1 --primary-dc dc2
run 1 '\-\-primary-unit is required' "T13 failback missing --primary-unit FAILS" failback --pool glance --recovering-dc dc1 --primary-dc dc2 --recovering-unit u/0
# --- failover: --confirmed-down gates --apply, but not the dry-run plan print ---
FO_DRY="$(bash "$SCRIPT" failover --dc dc1 --unit ceph-mon/0 --pool glance 2>&1)"; FO_DRY_RC=$?
[[ "$FO_DRY_RC" == 0 ]] && grep -q 'promote --force glance' <<<"$FO_DRY" && grep -q 'confirmed-down is required' <<<"$FO_DRY" \
&& { echo " PASS T14 failover dry-run (no --confirmed-down) still prints the plan, rc 0"; PASS=$((PASS+1)); } || { echo " FAIL T14"; echo "$FO_DRY" | sed 's/^/ /'; FAIL=$((FAIL+1)); }
run 1 'confirmed-down is required' "T15 failover --apply WITHOUT --confirmed-down is BLOCKED (rc 1)" failover --dc dc1 --unit ceph-mon/0 --pool glance --apply
FO_REREG="$(bash "$SCRIPT" failover --dc dc1 --unit ceph-mon/0 --pool glance --confirmed-down --reregister-image img1 2>&1)"
grep -q 'NEVER EXECUTED BY THIS SCRIPT' <<<"$FO_REREG" && grep -q 'img1' <<<"$FO_REREG" \
&& { echo " PASS T16 failover re-registration step is printed but flagged manual (never auto-executed)"; PASS=$((PASS+1)); } || { echo " FAIL T16"; echo "$FO_REREG" | sed 's/^/ /'; FAIL=$((FAIL+1)); }
# --- failback: the safety-critical step ORDER, verified by LINE NUMBER ---
FB_OUT="$(bash "$SCRIPT" failback --pool glance --recovering-dc dc1 --recovering-unit rec/0 --primary-dc dc2 --primary-unit pri/0 2>&1)"; FB_RC=$?
[[ "$FB_RC" == 0 ]] && { echo " PASS T17 failback dry-run (dc1 recovering, dc2 primary) exits 0"; PASS=$((PASS+1)); } || { echo " FAIL T17"; echo "$FB_OUT" | sed 's/^/ /'; FAIL=$((FAIL+1)); }
L_112=$(grep -n '11.2 (demote recovering' <<<"$FB_OUT" | head -1 | cut -d: -f1)
L_114A=$(grep -n '11.4a (demote CURRENT PRIMARY' <<<"$FB_OUT" | head -1 | cut -d: -f1)
L_114B=$(grep -n '11.4b (promote RECOVERING' <<<"$FB_OUT" | head -1 | cut -d: -f1)
if [[ -n "$L_112" && -n "$L_114A" && -n "$L_114B" && "$L_112" -lt "$L_114A" && "$L_114A" -lt "$L_114B" ]]; then
echo " PASS T18 failback PLAN line order is 11.2 < 11.4a(demote primary) < 11.4b(promote recovering)"; PASS=$((PASS+1))
else
echo " FAIL T18 failback PLAN line order wrong (11.2=$L_112 11.4a=$L_114A 11.4b=$L_114B)"; echo "$FB_OUT" | sed 's/^/ /'; FAIL=$((FAIL+1))
fi
# the demote command must appear on the primary line BEFORE the promote command on the recovering line
grep -A0 '11.4a' <<<"$FB_OUT" | grep -q 'demote glance' \
&& grep -A0 '11.4b' <<<"$FB_OUT" | grep -q 'promote glance' \
&& { echo " PASS T19 11.4a is a demote, 11.4b is a promote (not swapped)"; PASS=$((PASS+1)); } || { echo " FAIL T19"; echo "$FB_OUT" | sed 's/^/ /'; FAIL=$((FAIL+1)); }
FB_SKIP="$(bash "$SCRIPT" failback --pool glance --recovering-dc dc1 --recovering-unit rec/0 --primary-dc dc2 --primary-unit pri/0 --skip-11-4 2>&1)"
grep -q '11.4 SKIPPED' <<<"$FB_SKIP" && ! grep -q '11.4a' <<<"$FB_SKIP" \
&& { echo " PASS T20 --skip-11-4 stops the plan after 11.2/11.3"; PASS=$((PASS+1)); } || { echo " FAIL T20"; echo "$FB_SKIP" | sed 's/^/ /'; FAIL=$((FAIL+1)); }
# --- $DC gate: informational in dry-run (dc2 involved), blocking before --apply ---
grep -q 'gate: FAILED' <<<"$FB_OUT" && grep -q 'gate: OK (dc1)' <<<"$FB_OUT" \
&& { echo " PASS T21 failback dry-run reports dc1 OK / dc2 FAILED, still prints full plan"; PASS=$((PASS+1)); } || { echo " FAIL T21"; echo "$FB_OUT" | sed 's/^/ /'; FAIL=$((FAIL+1)); }
run 3 '\$DC gate refused' "T22 failback --apply is BLOCKED by the dc2 gate (rc 3)" failback --pool glance --recovering-dc dc1 --recovering-unit rec/0 --primary-dc dc2 --primary-unit pri/0 --apply --no-prompt
# --- --apply never runs without --apply: no juju invocation attempted in dry-run ---
if command -v juju >/dev/null 2>&1; then
echo " SKIP T23 juju-missing case (juju IS present in this environment -- can't exercise the missing-tool guard here)"
else
run 2 'juju required on PATH' "T23 failover --apply (dc1 gate OK) but juju absent FAILS (rc 2)" failover --dc dc1 --unit ceph-mon/0 --pool glance --confirmed-down --apply
fi
echo; echo "RESULT: PASS=$PASS FAIL=$FAIL"
[[ "$FAIL" -eq 0 ]] && { echo "ALL PASS"; exit 0; } || exit 1