diff --git a/scripts/phase-00-maas-carve.sh b/scripts/phase-00-maas-carve.sh deleted file mode 100644 index 368c525..0000000 --- a/scripts/phase-00-maas-carve.sh +++ /dev/null @@ -1,97 +0,0 @@ -#!/usr/bin/env bash -# scripts/phase-00-maas-carve.sh -# -# Phase-00 MAAS VIP/FIP address carve (rebuild foundation; KI-P3-001). Idempotently -# reserves the front-loaded API-VIP /26 on provider + metal-admin and the FIP pool on -# provider, so MAAS auto-static can never land a host/container primary on a configured -# VIP. Encapsulates the do-doc carve AS A SCRIPT (D-056): idempotent + self-gating; the -# human gates by invoking it (and separately for the destructive stale-delete). -# -# Two correctness fixes vs the do-doc block: -# DOCFIX-047: subnets resolved BY CIDR (lib-net PATTERN-1), never hardcoded subnet=1/2. -# DOCFIX-048: VIP reserve defaults to .2-.100 (the as-built width), not the do-doc's -# stale .2-.63. Idempotency is anchored on the START ip, so a pre-existing .2-.63 OR -# .2-.100 reserve is left untouched (never an overlap-create). -# -# The stale metal .224-.254 reservation (old scheme) is DETECTED + REPORTED; it is -# deleted only with DELETE_STALE=1 (destructive, gated) and only on an exact start/end match. -# -# Tunables via env: PROVIDER_CIDR METAL_CIDR PROVIDER_VIP_END METAL_VIP_END FIP_START FIP_END DELETE_STALE -# Requires: jumphost; jq; the 'admin' MAAS profile (never 'maas list' -- DOCFIX-016). -# Usage: scripts/phase-00-maas-carve.sh (report + create-if-absent) -# DELETE_STALE=1 scripts/phase-00-maas-carve.sh (also remove the stale .224-.254) -# Exit: 0 carve present/created | 1 error | 2 precondition -# ASCII + LF. - -set -euo pipefail -shopt -s inherit_errexit 2>/dev/null || true -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -# shellcheck source=scripts/lib-net.sh -. "$SCRIPT_DIR/lib-net.sh" - -PROVIDER_CIDR="${PROVIDER_CIDR:-10.12.4.0/22}" -METAL_CIDR="${METAL_CIDR:-10.12.8.0/22}" -PROVIDER_VIP_START="10.12.4.2"; PROVIDER_VIP_END="${PROVIDER_VIP_END:-10.12.4.100}" # DOCFIX-048 (do-doc said .63) -METAL_VIP_START="10.12.8.2"; METAL_VIP_END="${METAL_VIP_END:-10.12.8.100}" -FIP_START="${FIP_START:-10.12.5.0}"; FIP_END="${FIP_END:-10.12.7.254}" -STALE_METAL_START="10.12.8.224"; STALE_METAL_END="10.12.8.254" - -need_jq || exit 2 -command -v maas >/dev/null 2>&1 || { echo "FAIL: maas client not found" >&2; exit 2; } -SUBNETS="$(maas admin subnets read 2>/dev/null || true)" -printf '%s' "$SUBNETS" | jq -e 'type=="array"' >/dev/null 2>&1 \ - || { echo "FAIL: 'maas admin subnets read' not JSON (profile 'admin' logged in?)" >&2; exit 2; } -RANGES="$(maas admin ipranges read 2>/dev/null || true)" -printf '%s' "$RANGES" | jq -e 'type=="array"' >/dev/null 2>&1 \ - || { echo "FAIL: 'maas admin ipranges read' not JSON" >&2; exit 2; } - -FATAL=0 -sid_by_cidr() { printf '%s' "$SUBNETS" | jq -r --arg c "$1" '.[] | select(.cidr==$c) | .id' | head -1; } - -ensure_reserved() { # cidr start end comment - local cidr="$1" start="$2" end="$3" comment="$4" sid existing - sid="$(sid_by_cidr "$cidr")" - [ -n "$sid" ] || { echo "FAIL: no MAAS subnet for cidr $cidr"; FATAL=$((FATAL+1)); return; } - existing="$(printf '%s' "$RANGES" | jq -r --arg s "$sid" --arg a "$start" \ - '.[] | select(.type=="reserved" and (.subnet.id|tostring)==$s and .start_ip==$a) | "\(.start_ip)-\(.end_ip)"' | head -1)" - if [ -n "$existing" ]; then - echo "[SKIP] $cidr: reserved range starting $start already present ($existing)" - else - echo "[..] $cidr: reserving $start-$end (subnet id=$sid)" - maas admin ipranges create type=reserved subnet="$sid" start_ip="$start" end_ip="$end" comment="$comment" >/dev/null - echo "[OK] reserved $start-$end on $cidr" - fi -} - -echo "=== MAAS carve (CIDR-resolved; idempotent) ===" -ensure_reserved "$PROVIDER_CIDR" "$PROVIDER_VIP_START" "$PROVIDER_VIP_END" "OpenStack public API HA VIPs (front-loaded /26)" -ensure_reserved "$METAL_CIDR" "$METAL_VIP_START" "$METAL_VIP_END" "OpenStack internal/admin API HA VIPs (front-loaded /26)" -ensure_reserved "$PROVIDER_CIDR" "$FIP_START" "$FIP_END" "OpenStack Neutron external FIP pool (D-003)" - -echo "=== stale metal $STALE_METAL_START-$STALE_METAL_END (old scheme) ===" -MSID="$(sid_by_cidr "$METAL_CIDR")" -STALE_ID="" -[ -n "$MSID" ] && STALE_ID="$(printf '%s' "$RANGES" | jq -r --arg s "$MSID" --arg a "$STALE_METAL_START" --arg b "$STALE_METAL_END" \ - '.[] | select(.type=="reserved" and (.subnet.id|tostring)==$s and .start_ip==$a and .end_ip==$b) | .id' | head -1)" -if [ -z "$STALE_ID" ]; then - echo "[OK] stale range absent -- nothing to delete" -elif [ "${DELETE_STALE:-0}" = "1" ]; then - echo "[..] deleting stale reserved range id=$STALE_ID ($STALE_METAL_START-$STALE_METAL_END)" - maas admin iprange delete "$STALE_ID" >/dev/null - echo "[OK] stale range id=$STALE_ID deleted" -else - echo "[REPORT] stale range present: id=$STALE_ID $STALE_METAL_START-$STALE_METAL_END" - echo " re-run with DELETE_STALE=1 to remove it (destructive; gated separately)" -fi - -echo "=== final: reserved ranges on provider + metal ===" -RANGES="$(maas admin ipranges read 2>/dev/null || true)" # re-read for post-state -for c in "$PROVIDER_CIDR" "$METAL_CIDR"; do - sid="$(sid_by_cidr "$c")" - [ -n "$sid" ] || { echo " $c: subnet not found"; continue; } - echo " $c (subnet id=$sid):" - printf '%s' "$RANGES" | jq -r --arg s "$sid" '.[] | select(.type=="reserved" and (.subnet.id|tostring)==$s) | " \(.start_ip)-\(.end_ip) [\(.comment // "")]"' -done - -[ "$FATAL" -eq 0 ] || { echo "Summary: ERRORS ($FATAL)"; exit 1; } -echo "Summary: carve complete (idempotent)." diff --git a/tests/phase-00-carve/fakebin/maas b/tests/phase-00-carve/fakebin/maas deleted file mode 100644 index 057f8e8..0000000 --- a/tests/phase-00-carve/fakebin/maas +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env bash -# fake maas: admin {subnets read | ipranges read | ipranges create | iprange delete} -prof="${1:-}"; obj="${2:-}"; act="${3:-}" -if [ "$prof" = admin ] && [ "$obj" = subnets ] && [ "$act" = read ]; then cat "${FIX_SUBNETS_FILE:?}"; exit 0; fi -if [ "$prof" = admin ] && [ "$obj" = ipranges ] && [ "$act" = read ]; then cat "${FIX_RANGES_FILE:?}"; exit 0; fi -if [ "$prof" = admin ] && [ "$obj" = ipranges ] && [ "$act" = create ]; then echo "{}"; exit 0; fi -if [ "$prof" = admin ] && [ "$obj" = iprange ] && [ "$act" = delete ]; then exit 0; fi -exit 0 diff --git a/tests/phase-00-carve/run-tests.sh b/tests/phase-00-carve/run-tests.sh deleted file mode 100644 index b450603..0000000 --- a/tests/phase-00-carve/run-tests.sh +++ /dev/null @@ -1,61 +0,0 @@ -#!/usr/bin/env bash -# tests/phase-00-carve/run-tests.sh -- offline regression for phase-00-maas-carve.sh. -set -euo pipefail -IFS=$'\n\t' -HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -SCRIPTS="$(cd "$HERE/../../scripts" && pwd)" -TARGET="$SCRIPTS/phase-00-maas-carve.sh" -BIN="$HERE/fakebin" -command -v jq >/dev/null 2>&1 || { echo "FAIL: jq required" >&2; exit 1; } -[ -f "$TARGET" ] || { echo "FAIL: target missing" >&2; exit 1; } -chmod +x "$BIN"/* 2>/dev/null || true -WORK="$(mktemp -d)"; trap 'rm -rf "$WORK"' EXIT - -# --- fixtures --- -SUB_BOTH="$WORK/sub_both.json"; printf '[{"id":1,"cidr":"10.12.4.0/22"},{"id":2,"cidr":"10.12.8.0/22"}]\n' > "$SUB_BOTH" -SUB_NOPROV="$WORK/sub_noprov.json"; printf '[{"id":2,"cidr":"10.12.8.0/22"}]\n' > "$SUB_NOPROV" -R_EMPTY="$WORK/r_empty.json"; printf '[]\n' > "$R_EMPTY" -R_ALL="$WORK/r_all.json"; cat > "$R_ALL" <<'JSON' -[ {"id":1,"type":"reserved","start_ip":"10.12.4.2","end_ip":"10.12.4.100","subnet":{"id":1},"comment":"prov vip"}, - {"id":2,"type":"reserved","start_ip":"10.12.8.2","end_ip":"10.12.8.100","subnet":{"id":2},"comment":"metal vip"}, - {"id":3,"type":"reserved","start_ip":"10.12.5.0","end_ip":"10.12.7.254","subnet":{"id":1},"comment":"fip pool"} ] -JSON -R_DRIFT="$WORK/r_drift.json"; cat > "$R_DRIFT" <<'JSON' -[ {"id":1,"type":"reserved","start_ip":"10.12.4.2","end_ip":"10.12.4.63","subnet":{"id":1},"comment":"prov vip (do-doc width)"}, - {"id":2,"type":"reserved","start_ip":"10.12.8.2","end_ip":"10.12.8.100","subnet":{"id":2},"comment":"metal vip"}, - {"id":3,"type":"reserved","start_ip":"10.12.5.0","end_ip":"10.12.7.254","subnet":{"id":1},"comment":"fip pool"} ] -JSON -R_STALE="$WORK/r_stale.json"; cat > "$R_STALE" <<'JSON' -[ {"id":1,"type":"reserved","start_ip":"10.12.4.2","end_ip":"10.12.4.100","subnet":{"id":1},"comment":"prov vip"}, - {"id":2,"type":"reserved","start_ip":"10.12.8.2","end_ip":"10.12.8.100","subnet":{"id":2},"comment":"metal vip"}, - {"id":3,"type":"reserved","start_ip":"10.12.5.0","end_ip":"10.12.7.254","subnet":{"id":1},"comment":"fip pool"}, - {"id":9,"type":"reserved","start_ip":"10.12.8.224","end_ip":"10.12.8.254","subnet":{"id":2},"comment":"STALE old scheme"} ] -JSON - -rc_all=0 -run() { - local want="$1" re="$2" label="$3" subs="$4" ranges="$5"; shift 5 - local rc - set +e - PATH="$BIN:$PATH" FIX_SUBNETS_FILE="$subs" FIX_RANGES_FILE="$ranges" \ - env "$@" bash "$TARGET" >"$WORK/out" 2>&1 - rc=$?; set -e - if [ "$rc" -eq "$want" ] && grep -qE "$re" "$WORK/out"; then - printf ' [OK] %-40s exit %s\n' "$label" "$rc" - else - printf ' [XX] %-40s exit %s (want %s; /%s/)\n' "$label" "$rc" "$want" "$re" - sed 's/^/ /' "$WORK/out"; rc_all=1 - fi -} -echo "=== phase-00-maas-carve.sh (fake maas + real jq) ===" -run 0 'reserved 10.12.4.2-10.12.4.100 on 10.12.4.0/22' "fresh: create provider VIP" "$SUB_BOTH" "$R_EMPTY" -run 0 'reserved 10.12.5.0-10.12.7.254 on 10.12.4.0/22' "fresh: create FIP pool" "$SUB_BOTH" "$R_EMPTY" -run 0 'stale range absent' "fresh: stale absent" "$SUB_BOTH" "$R_EMPTY" -run 0 'starting 10.12.4.2 already present .10.12.4.2-10.12.4.100.' "idempotent: skip all" "$SUB_BOTH" "$R_ALL" -run 0 'starting 10.12.4.2 already present .10.12.4.2-10.12.4.63.' "width drift .2-.63 tolerated" "$SUB_BOTH" "$R_DRIFT" -run 0 'REPORT. stale range present: id=9' "stale present: report-only" "$SUB_BOTH" "$R_STALE" -run 0 'stale range id=9 deleted' "stale present: DELETE_STALE=1" "$SUB_BOTH" "$R_STALE" DELETE_STALE=1 -run 1 'no MAAS subnet for cidr 10.12.4.0/22' "provider subnet missing -> fail" "$SUB_NOPROV" "$R_EMPTY" -echo -[ "$rc_all" -eq 0 ] && echo "ALL PASS" || echo "SOME FAILED" -exit "$rc_all"