#!/usr/bin/env bash
# tests/dc-plane-ipam/run-tests.sh -- offline harness for scripts/dc-plane-ipam.sh.
#
# Stubs the `maas` client with a fakebin returning controlled JSON, and points the
# tool at fixture apex records. Mutates nothing outside $TMP; never touches live MAAS.
#
# THE POINT OF T6: everything this gate asserts is currently ABSENT live, so every
# real run FAILS. A gate only ever observed failing is as untrustworthy as one only
# ever observed passing -- T6 proves it CAN go green on a fully-provisioned fixture,
# so a future green is meaningful rather than merely unobserved.
# 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
ok() { PASS=$((PASS+1)); printf ' PASS %s\n' "$1"; }
bad() { FAIL=$((FAIL+1)); printf ' FAIL %s\n' "$1"; shift; [ $# -gt 0 ] && printf '%s\n' "$*" | sed 's/^/ /'; }
# ---- fixture apex record: the six dc0 plane /64s + the provider VIP /64 --------
mkapex() { # mkapex <file> [omit-role]
python3 - "$1" "${2:-}" <<'PY'
import json, sys
out, omit = [], sys.argv[2]
planes = [("metal-admin","fd50:840e:74e2:220::/64"),("metal-internal","fd50:840e:74e2:221::/64"),
("data-tenant","fd50:840e:74e2:230::/64"),("storage","fd50:840e:74e2:240::/64"),
("replication","fd50:840e:74e2:250::/64"),("provider-public","2602:f3e2:f02:10::/64")]
for role, pre in planes:
if role == omit: continue
out.append({"prefix":pre,"scope":{"name":"vr1-dc0"},"role":{"slug":role},
"description":"VR1 DC0 %s (ULA /64 active)" % role})
out.append({"prefix":"2602:f3e2:f02:11::/64","scope":{"name":"vr1-dc0"},
"role":{"slug":"provider-public"},"description":"VR1 DC0 provider-public (GUA VIP /64)"})
out.append({"prefix":"fd50:840e:74e2:220::/60","scope":{"name":"vr1-dc0"},
"role":{"slug":"metal-admin"},"description":"parent /60 -- must be IGNORED"})
json.dump({"ipam/prefixes": out}, open(sys.argv[1], "w"))
PY
}
# ---- fakebin maas -------------------------------------------------------------
# <dir> <v6:full|none> <bands:full|none|wrongtype> [subnets_rc] [ranges_rc]
mkmaas() {
local d="$1" v6="$2" bands="$3" src="${4:-0}" rrc="${5:-0}"
mkdir -p "$d/fakebin"
cat > "$d/fakebin/maas" <<FB
#!/usr/bin/env bash
if [ "\$2" = "subnets" ]; then
[ "$src" -ne 0 ] && exit $src
python3 - "$v6" <<'PY'
import json,sys
v6=sys.argv[1]
s=[{"cidr":c,"id":i,"vlan":{"id":5000+i,"fabric":"f-%d"%i}} for i,c in enumerate(
["10.12.4.0/22","10.12.8.0/22","10.12.12.0/22","10.12.16.0/22","10.12.32.0/22","10.12.36.0/22"])]
if v6=="full":
for i,c in enumerate(["fd50:840e:74e2:220::/64","fd50:840e:74e2:221::/64","fd50:840e:74e2:230::/64",
"fd50:840e:74e2:240::/64","fd50:840e:74e2:250::/64","2602:f3e2:f02:10::/64"]):
s.append({"cidr":c,"id":100+i,"vlan":{"id":5000+i,"fabric":"f-%d"%i}})
print(json.dumps(s))
PY
exit 0
fi
if [ "\$2" = "ipranges" ]; then
[ "$rrc" -ne 0 ] && exit $rrc
python3 - "$bands" <<'PY'
import json,sys
b=sys.argv[1]; r=[]
if b in ("full","wrongtype"):
t = "dynamic" if b=="wrongtype" else "reserved"
for third in (4,8,12,16,32,36):
r.append({"type":t,"start_ip":"10.12.%d.4"%third,"end_ip":"10.12.%d.49"%third})
r.append({"type":t,"start_ip":"10.12.%d.50"%third,"end_ip":"10.12.%d.99"%third})
print(json.dumps(r))
PY
exit 0
fi
exit 1
FB
chmod +x "$d/fakebin/maas"
}
run() { # run <want_rc> <regex> <label> <dir> [apex]
local want="$1" re="$2" label="$3" d="$4"
local apex="${5:-$d/apex.json}" # split: bash expands ALL words of a `local`
# before assigning any, so $d is unset above
local out rc
out="$(cd "$REPO" && PATH="$d/fakebin:$PATH" APEX_RECORD="$apex" \
bash scripts/dc-plane-ipam.sh check vr1-dc0 2>&1)"; rc=$?
if [ "$rc" = "$want" ] && grep -qE "$re" <<<"$out"; then ok "$label"
else bad "$label (rc=$rc want=$want)" "$(printf '%s' "$out" | tail -4)"; fi
}
echo "== dc-plane-ipam =="
# ---- argument / selector guards ----
for a in "check dc0:2:RETIRED" "check dc1:2:RETIRED" "bogus vr1-dc0:2:usage" "check:2:usage"; do
args="${a%%:*}"; rest="${a#*:}"; want="${rest%%:*}"; re="${rest##*:}"
out="$(cd "$REPO" && bash scripts/dc-plane-ipam.sh $args 2>&1)"; rc=$?
if [ "$rc" = "$want" ] && grep -qiE "$re" <<<"$out"; then ok "T-guard '$args' -> rc$want ($re)"
else bad "T-guard '$args' (rc=$rc want=$want)" "$out"; fi
done
# ---- T1 the fully-absent baseline: the live state today ----
D="$TMP/absent"; mkdir -p "$D"; mkapex "$D/apex.json"; mkmaas "$D" none none
run 1 'v6 metal-admin .* ABSENT' "T1 no v6 and no bands -> FAIL (the measured live baseline)" "$D"
# ---- T2/T3 "could not look" must never read as clean ----
D="$TMP/subrc"; mkdir -p "$D"; mkapex "$D/apex.json"; mkmaas "$D" full full 4 0
run 2 'REFUSE' "T2 'maas subnets read' failing REFUSES (exit 2), never reports clean" "$D"
D="$TMP/rngrc"; mkdir -p "$D"; mkapex "$D/apex.json"; mkmaas "$D" full full 0 4
run 2 'REFUSE' "T3 'maas ipranges read' failing REFUSES (exit 2)" "$D"
# ---- T4 an unreadable apex REFUSES rather than asserting over zero planes ----
D="$TMP/noapex"; mkdir -p "$D"; mkmaas "$D" full full
run 2 'REFUSE' "T4 unreadable apex record REFUSES -- never 'no v6 planes, all clear'" "$D" "$D/nope.json"
# ---- T5 bands present but the WRONG TYPE: unrecognised state must not pass ----
D="$TMP/wrongtype"; mkdir -p "$D"; mkapex "$D/apex.json"; mkmaas "$D" full wrongtype
run 1 "type='dynamic'" "T5 band exists as 'dynamic' not 'reserved' -> FAIL, not a pass" "$D"
# ---- T6 THE CRITICAL ONE: a fully-provisioned fixture must PASS ----
D="$TMP/full"; mkdir -p "$D"; mkapex "$D/apex.json"; mkmaas "$D" full full
run 0 'PASS: dc-plane-ipam' "T6 fully-provisioned fixture PASSES -- the gate can go green" "$D"
# ---- T7 one missing v6 plane is caught (not masked by the other five) ----
D="$TMP/one"; mkdir -p "$D"; mkapex "$D/apex.json" storage; mkmaas "$D" full full
run 0 'PASS: dc-plane-ipam' "T7a apex omitting a plane does not invent a failure" "$D"
D="$TMP/one2"; mkdir -p "$D"; mkapex "$D/apex.json"; mkmaas "$D" none full
run 1 'v6 storage .* ABSENT' "T7b a v6 plane absent from MAAS is named individually" "$D"
# ---- T8 the parent /60 must be IGNORED, not treated as a plane ----
D="$TMP/p60"; mkdir -p "$D"; mkapex "$D/apex.json"; mkmaas "$D" full full
out="$(cd "$REPO" && PATH="$D/fakebin:$PATH" APEX_RECORD="$D/apex.json" \
bash scripts/dc-plane-ipam.sh check vr1-dc0 2>&1)"
if ! grep -q '220::/60' <<<"$out"; then ok "T8 parent /60 blocks are ignored, not asserted as planes"
else bad "T8 a /60 parent block was treated as a plane" "$out"; fi
# ---- T9 the provider VIP /64 is reported but NOT asserted ----
if grep -q 'provider VIP block' <<<"$out" && ! grep -qE '\[FAIL\].*f02:11::/64' <<<"$out"; then
ok "T9 provider VIP /64 is reported for visibility, deliberately not asserted"
else bad "T9 provider VIP /64 handling wrong" "$out"; fi
# ============ carve-v6 / reserve (dry by default) ============================
runact() { # runact <action> <want_rc> <regex> <label> <dir> [extra-args...]
local act="$1" want="$2" re="$3" label="$4" d="$5"; shift 5
local out rc
out="$(cd "$REPO" && PATH="$d/fakebin:$PATH" APEX_RECORD="$d/apex.json" \
bash scripts/dc-plane-ipam.sh "$act" vr1-dc0 "$@" 2>&1)"; rc=$?
if [ "$rc" = "$want" ] && grep -qE "$re" <<<"$out"; then ok "$label"
else bad "$label (rc=$rc want=$want)" "$(printf '%s' "$out" | tail -5)"; fi
}
# --- carve-v6 ---
D="$TMP/carve"; mkdir -p "$D"; mkapex "$D/apex.json"; mkmaas "$D" none none
runact carve-v6 0 'planned=6 applied=0' "T10 carve-v6 dry run plans 6 and writes NOTHING" "$D"
runact carve-v6 0 'DRY RUN -- nothing was written' "T11 carve-v6 says plainly that it is a dry run" "$D"
runact carve-v6 0 'provider VIP block, deliberately not a MAAS subnet' \
"T12 carve-v6 SKIPS the provider VIP /64 rather than creating it" "$D"
D="$TMP/carve2"; mkdir -p "$D"; mkapex "$D/apex.json"; mkmaas "$D" full none
runact carve-v6 0 'planned=0 applied=0 skipped=7' "T13 carve-v6 is IDEMPOTENT -- all present, nothing planned" "$D"
# --- reserve ---
D="$TMP/res"; mkdir -p "$D"; mkapex "$D/apex.json"; mkmaas "$D" none none
runact reserve 0 'reserve 10.12.4.50-10.12.4.99' "T14 reserve plans the D-134 VIP band" "$D"
runact reserve 0 'reserve 10.12.4.4-10.12.4.49' "T15 reserve plans the D-134 utility band" "$D"
runact reserve 0 'not in MAAS yet -- run carve-v6 first' \
"T16 v6 bands SKIP until carve-v6 has run (forced sequencing, not a silent pass)" "$D"
D="$TMP/res2"; mkdir -p "$D"; mkapex "$D/apex.json"; mkmaas "$D" none full
runact reserve 0 'already reserved' "T17 reserve is IDEMPOTENT -- existing reserved bands are skipped" "$D"
D="$TMP/res3"; mkdir -p "$D"; mkapex "$D/apex.json"; mkmaas "$D" none wrongtype
runact reserve 1 "REFUSING to rewrite an existing range" \
"T18 a same-bounds range of the WRONG type REFUSES rather than being overwritten" "$D"
# --- T19: the v6 band mirror is TEXTUAL, not numeric ---
# Caught pre-ship: printf '%%x' 50 yields 32, which would have silently created
# ::32-::63 -- a plausible-looking band that is NOT the one ruled. The ruling
# mirrors the DIGITS: v4 .50 -> ::50.
D="$TMP/v6band"; mkdir -p "$D"; mkapex "$D/apex.json"; mkmaas "$D" full none
out="$(cd "$REPO" && PATH="$D/fakebin:$PATH" APEX_RECORD="$D/apex.json" \
bash scripts/dc-plane-ipam.sh reserve vr1-dc0 2>&1)"
if grep -q 'fd50:840e:74e2:220::50-fd50:840e:74e2:220::99' <<<"$out" \
&& ! grep -q '::32-' <<<"$out"; then
ok "T19 v6 VIP band is ::50-::99 (textual octet mirror), NOT the hex ::32-::63"
else bad "T19 v6 band mirror wrong" "$(grep -E '::' <<<"$out" | head -4)"; fi
# --- T20: dc1 must REFUSE to invent the FIP pool ---
# lib-net deliberately unsets FIP_POOL_* for dc1 ("UNSET so any use fails loud").
# Mirroring dc0's shape would be an inferred value -- hard rule 2.
mkapex1() { python3 - "$1" <<'PY2'
import json,sys
out=[{"prefix":"fd50:840e:74e2:320::/64","scope":{"name":"vr1-dc1"},
"role":{"slug":"metal-admin"},"description":"VR1 DC1 metal-admin (ULA /64 active)"}]
json.dump({"ipam/prefixes":out},open(sys.argv[1],"w"))
PY2
}
D="$TMP/dc1fip"; mkdir -p "$D"; mkapex1 "$D/apex.json"; mkmaas "$D" none none
out="$(cd "$REPO" && PATH="$D/fakebin:$PATH" APEX_RECORD="$D/apex.json" \
bash scripts/dc-plane-ipam.sh reserve vr1-dc1 2>&1)"; rc=$?
if [ "$rc" = 1 ] && grep -q 'Refusing to infer it from dc0' <<<"$out"; then
ok "T20 dc1 REFUSES to invent the unruled FIP pool (hard rule 2), exit 1"
else bad "T20 dc1 FIP refusal wrong (rc=$rc)" "$(printf '%s' "$out" | tail -4)"; fi
echo
echo "RESULT: PASS=$PASS FAIL=$FAIL"
[ "$FAIL" -eq 0 ] || exit 1