Newer
Older
openstack-caracal-ipv4 / tests / phase-00-teardown-d061 / run-tests.sh
@JANeumatrix JANeumatrix 8 hours ago 6 KB Patches
#!/usr/bin/env bash
# tests/phase-00-teardown-d061/run-tests.sh -- offline harness for the D-061
# teardown PAIR (phase-00-teardown-release.sh / phase-00-teardown-destroy.sh).
# These are the most destructive scripts in the repo and shipped WITHOUT tests
# (their --no-prompt flag exists for "tested automation only" -- this is it).
# Replaces the retired tests/phase-00-teardown/ (its target script is git rm'd).
#
# Stateful fakebin: fake `maas` serves machines-<phase>.json selected by a
# state file; fake `juju remove-machine`/`destroy-model` advance the phase, so
# survival/decompose verification reads post-mutation state. Fake `sleep` makes
# the 30s settle instant. Mutates nothing outside $TMP.
# Exit: 0 all pass | 1 any case failed.  ASCII + LF.
set -uo pipefail
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO="$(cd "$HERE/../.." && pwd)"
TMP="$(mktemp -d)"; trap 'rm -rf "$TMP"' EXIT
PASS=0; FAIL=0
BIN="$TMP/bin"; mkdir -p "$BIN"

cat > "$BIN/maas" <<'FB'
#!/usr/bin/env bash
# fake maas: `maas admin machines read` serves machines-$(phase).json;
# `maas admin machine delete <sid>` logs the delete.
case "$*" in
  "admin machines read") cat "$FIXDIR/machines-$(cat "$FIXDIR/phase").json" ;;
  "admin machine delete"*) echo "$4" >> "$FIXDIR/deleted.log" ;;
  *) echo "fake-maas unmatched: $*" >&2; exit 1 ;;
esac
FB
cat > "$BIN/juju" <<'FB'
#!/usr/bin/env bash
case "$*" in
  "models --format=json") printf '{"models":[{"name":"admin/openstack"}]}' ;;
  "machines -m openstack --format=json") cat "$FIXDIR/juju-machines.json" ;;
  "remove-machine "*)
    echo "$*" >> "$FIXDIR/remove.log"
    # advance state so the survival re-read sees post-remove reality
    echo "postremove" > "$FIXDIR/phase" ;;
  "destroy-model "*)
    echo "$*" >> "$FIXDIR/destroy.log"
    echo "postdestroy" > "$FIXDIR/phase" ;;
  *) echo "fake-juju unmatched: $*" >&2; exit 1 ;;
esac
FB
printf '#!/usr/bin/env bash\nexit 0\n' > "$BIN/sleep"
chmod +x "$BIN"/maas "$BIN"/juju "$BIN"/sleep

MJ_FULL='[
 {"hostname":"juju","system_id":"sub001","status_name":"Deployed"},
 {"hostname":"lxd","system_id":"sub002","status_name":"Deployed"},
 {"hostname":"tailscale","system_id":"sub003","status_name":"Deployed"},
 {"hostname":"openstack0","system_id":"abc100","status_name":"Deployed"},
 {"hostname":"openstack1","system_id":"abc101","status_name":"Deployed"},
 {"hostname":"openstack2","system_id":"abc102","status_name":"Deployed"},
 {"hostname":"openstack3","system_id":"abc103","status_name":"Deployed"},
 {"hostname":"capi-mgmt","system_id":"orp001","status_name":"Ready"}]'
MJ_SUBSTRATE_ONLY='[
 {"hostname":"juju","system_id":"sub001","status_name":"Deployed"},
 {"hostname":"lxd","system_id":"sub002","status_name":"Deployed"},
 {"hostname":"tailscale","system_id":"sub003","status_name":"Deployed"}]'
JM='{"machines":{"0":{"instance-id":"openstack0"},"1":{"instance-id":"openstack1"},"2":{"instance-id":"openstack2"},"3":{"instance-id":"openstack3"}}}'

mkfix() { # mkfix <name> <initial-json> <postremove-json> <postdestroy-json>
  local d="$TMP/$1"; mkdir -p "$d"
  printf '%s' "$2" > "$d/machines-initial.json"
  printf '%s' "$3" > "$d/machines-postremove.json"
  printf '%s' "$4" > "$d/machines-postdestroy.json"
  printf '%s' "$JM" > "$d/juju-machines.json"
  echo "initial" > "$d/phase"; : > "$d/remove.log"; : > "$d/deleted.log"; : > "$d/destroy.log"
  echo "$d"
}
run() { # run <script> <want_rc> <regex> <label> <fixdir> [args...]
  local sc="$1" want="$2" rx="$3" label="$4" fd="$5"; shift 5
  local out rc
  out=$(env FIXDIR="$fd" PATH="$BIN:$PATH" bash "$REPO/scripts/$sc" "$@" 2>&1); rc=$?
  if [ "$rc" = "$want" ] && printf '%s' "$out" | grep -qE "$rx"; then
    echo "  PASS  $label"; PASS=$((PASS+1))
  else
    echo "  FAIL  $label (rc=$rc want=$want)"; printf '%s\n' "$out" | tail -6 | sed 's/^/        /'; FAIL=$((FAIL+1))
  fi
}

R=phase-00-teardown-release.sh
D=phase-00-teardown-destroy.sh

# --- release path ---
F=$(mkfix r-dry "$MJ_FULL" "$MJ_FULL" "$MJ_FULL")
run "$R" 0 'OK \(dryrun\)' "R1 release dry-run: plan only, nothing changed" "$F"
[ -s "$F/remove.log" ] && { echo "  FAIL  R1b dry-run must not remove"; FAIL=$((FAIL+1)); }

F=$(mkfix r-canary "$MJ_FULL" "$MJ_FULL" "$MJ_FULL")
run "$R" 0 'CANARY OK: openstack0 .* SURVIVED' "R2 canary: remove openstack0 only, survival verified, STOP" "$F" --apply --canary --no-prompt
CNT=$(wc -l < "$F/remove.log"); [ "$CNT" = 1 ] || { echo "  FAIL  R2b canary must remove exactly 1 (got $CNT)"; FAIL=$((FAIL+1)); }
[ -s "$F/destroy.log" ] && { echo "  FAIL  R2c canary must NOT destroy the model"; FAIL=$((FAIL+1)); }

# decompose detection: post-remove state DROPS openstack0 -> FAIL LOUD, no destroy
F=$(mkfix r-decomp "$MJ_FULL" "$(printf '%s' "$MJ_FULL" | jq 'map(select(.hostname!="openstack0"))')" "$MJ_FULL")
run "$R" 1 'DECOMPOSED .* did NOT hold' "R3 canary decompose-detection FAILS LOUD" "$F" --apply --canary --no-prompt
[ -s "$F/destroy.log" ] && { echo "  FAIL  R3b decompose must block destroy-model"; FAIL=$((FAIL+1)); }

F=$(mkfix r-full "$MJ_FULL" "$MJ_FULL" "$MJ_FULL")
run "$R" 0 'hosts are KEPT' "R4 full release: 4 removes + destroy + orphan delete, hosts kept" "$F" --apply --no-prompt
CNT=$(wc -l < "$F/remove.log"); [ "$CNT" = 4 ] || { echo "  FAIL  R4b want 4 removes (got $CNT)"; FAIL=$((FAIL+1)); }
grep -q -- '--release-storage' "$F/destroy.log" || { echo "  FAIL  R4c destroy must use --release-storage"; FAIL=$((FAIL+1)); }
grep -qx 'orp001' "$F/deleted.log" || { echo "  FAIL  R4d orphan capi-mgmt not deleted"; FAIL=$((FAIL+1)); }

# substrate collision: a host hostname resolving to a PROTECTED sid must abort pre-mutation
F=$(mkfix r-prot "$(printf '%s' "$MJ_FULL" | jq '(.[] | select(.hostname=="openstack0") | .system_id) = "sub001"')" "$MJ_FULL" "$MJ_FULL")
run "$R" 1 'PROTECTED sid .* ABORT' "R5 substrate-sid collision aborts with nothing changed" "$F" --apply --no-prompt
[ -s "$F/remove.log" ] && { echo "  FAIL  R5b abort must precede any remove"; FAIL=$((FAIL+1)); }

# --- destroy path ---
F=$(mkfix d-dry "$MJ_FULL" "$MJ_FULL" "$MJ_FULL")
run "$D" 0 'OK \(dryrun\)' "D1 destroy dry-run: plan only" "$F"

F=$(mkfix d-full "$MJ_FULL" "$MJ_FULL" "$MJ_SUBSTRATE_ONLY")
run "$D" 0 'OK \(apply\)' "D2 full destroy: hosts decomposed, substrate intact, orphan deleted" "$F" --apply --no-prompt
grep -q -- '--destroy-storage' "$F/destroy.log" || { echo "  FAIL  D2b destroy must use --destroy-storage"; FAIL=$((FAIL+1)); }
grep -qx 'orp001' "$F/deleted.log" || { echo "  FAIL  D2c orphan not deleted"; FAIL=$((FAIL+1)); }

F=$(mkfix d-prot "$(printf '%s' "$MJ_FULL" | jq '(.[] | select(.hostname=="capi-mgmt") | .system_id) = "sub002"')" "$MJ_FULL" "$MJ_FULL")
run "$D" 1 'PROTECTED sid .* ABORT' "D3 orphan-sid collision with substrate aborts" "$F" --apply --no-prompt
[ -s "$F/destroy.log" ] && { echo "  FAIL  D3b abort must precede destroy"; FAIL=$((FAIL+1)); }

echo; echo "RESULT: PASS=$PASS FAIL=$FAIL"
[ "$FAIL" -eq 0 ] && { echo "ALL PASS"; exit 0; } || exit 1