#!/usr/bin/env bash
# tests/dc-node-v6-carve/run-tests.sh -- offline harness for scripts/dc-node-v6-carve.py.
# Stubs `maas` with a fakebin returning controlled JSON. Never touches live MAAS.
# 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/^/ /'; }
# mkbin <dir> <v6:full|none|novlan> <nodev6:none|correct|wrong> <tag:ok|missing>
mkbin() {
local d="$1"; mkdir -p "$d/fakebin"
cat > "$d/fakebin/maas" <<FB
#!/usr/bin/env bash
if [ "\$2" = "subnets" ]; then python3 "$d/subnets.py"; exit 0; fi
if [ "\$2" = "machines" ]; then python3 "$d/machines.py"; exit 0; fi
if [ "\$2" = "interface" ]; then echo '{}'; exit 0; fi
exit 1
FB
chmod +x "$d/fakebin/maas"
cat > "$d/subnets.py" <<PY
import json
mode = "$2"
s = [{"cidr": "10.12.8.0/22", "id": 6, "vlan": {"id": 5005}},
{"cidr": "10.12.4.0/22", "id": 7, "vlan": {"id": 5189}}]
if mode == "full":
s += [{"cidr": "fd50:840e:74e2:220::/64", "id": 21, "vlan": {"id": 5005}},
{"cidr": "2602:f3e2:f02:10::/64", "id": 23, "vlan": {"id": 5189}}]
elif mode == "novlan":
s += [{"cidr": "fd50:840e:74e2:220::/64", "id": 21, "vlan": {"id": 5005}}]
print(json.dumps(s))
PY
cat > "$d/machines.py" <<PY
import json
nodev6, tag = "$3", "$4"
links_admin = [{"mode": "static", "ip_address": "10.12.8.121",
"subnet": {"cidr": "10.12.8.0/22", "vlan": {"id": 5005}}}]
links_brex = [{"mode": "static", "ip_address": "10.12.4.121",
"subnet": {"cidr": "10.12.4.0/22", "vlan": {"id": 5189}}}]
if nodev6 == "correct":
links_admin.append({"mode": "static", "ip_address": "fd50:840e:74e2:220::121",
"subnet": {"cidr": "fd50:840e:74e2:220::/64", "vlan": {"id": 5005}}})
links_brex.append({"mode": "static", "ip_address": "2602:f3e2:f02:10::121",
"subnet": {"cidr": "2602:f3e2:f02:10::/64", "vlan": {"id": 5189}}})
elif nodev6 == "wrong":
links_admin.append({"mode": "static", "ip_address": "fd50:840e:74e2:220::999",
"subnet": {"cidr": "fd50:840e:74e2:220::/64", "vlan": {"id": 5005}}})
m = {"hostname": "n1", "system_id": "aaa", "status_name": "Ready",
"tag_names": (["virtual", "openstack-vr1-dc0"] if tag == "ok" else ["virtual"]),
"interface_set": [
{"name": "enp1s0", "id": 216, "links": links_admin},
{"name": "enp2s0", "id": 223, "links": []},
{"name": "br-ex", "id": 404, "links": links_brex}]}
print(json.dumps([m]))
PY
}
run() { # run <want_rc> <regex> <label> <dir> <action> [args...]
local want="$1" re="$2" label="$3" d="$4"; shift 4
local out rc
out="$(cd "$REPO" && PATH="$d/fakebin:$PATH" python3 scripts/dc-node-v6-carve.py "$@" 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
}
echo "== dc-node-v6-carve =="
D="$TMP/none"; mkbin "$D" full none ok
run 1 'planned v6 links : 2' "T1 uncarved node plans one v6 link per v4-carrying iface" "$D" check vr1-dc0
# THE OCTET MIRROR IS TEXTUAL. .121 -> ::121, never the hex 0x121 or hex(121)=0x79.
run 1 'fd50:840e:74e2:220::121' "T2 mirror is TEXTUAL: .121 -> ::121" "$D" check vr1-dc0
out="$(cd "$REPO" && PATH="$D/fakebin:$PATH" python3 scripts/dc-node-v6-carve.py check vr1-dc0 2>&1)"
if ! grep -qE '::(79|121121)' <<<"$out"; then ok "T3 no hex conversion of the octet"
else bad "T3 octet was hex-converted" "$out"; fi
# The RAW provider NIC (D-100) has no v4 link and must be SKIPPED, not errored --
# and br-ex, which carries provider-public instead, must be INCLUDED.
if ! grep -q 'enp2s0' <<<"$out" && grep -q 'br-ex' <<<"$out"; then
ok "T4 raw provider NIC skipped (no v4 link); br-ex included (has one)"
else bad "T4 provider-NIC/br-ex handling wrong" "$out"; fi
D="$TMP/done"; mkbin "$D" full correct ok
run 0 'PASS: dc-node-v6-carve' "T5 fully-carved fixture PASSES -- the gate can go green" "$D" check vr1-dc0
D="$TMP/wrong"; mkbin "$D" full wrong ok
run 1 'REFUSING to add a second address' \
"T6 an interface with a DIFFERENT v6 refuses rather than adding a second" "$D" check vr1-dc0
D="$TMP/novlan"; mkbin "$D" novlan none ok
run 1 'NO v6 subnet on that vlan' \
"T7 a vlan with no v6 subnet is a NAMED error, not a silent skip" "$D" check vr1-dc0
# A tag that matches nothing must REFUSE, never read as "0 planned, all clean".
D="$TMP/notag"; mkbin "$D" full none missing
run 2 'Refusing to fall back to a heuristic' \
"T8 no tagged machines REFUSES (exit 2) rather than reporting a clean zero" "$D" check vr1-dc0
# apply without --commit must write nothing and say so.
D="$TMP/dry"; mkbin "$D" full none ok
run 0 'DRY RUN -- nothing was written' "T9 apply is DRY by default" "$D" apply vr1-dc0
echo
echo "RESULT: PASS=$PASS FAIL=$FAIL"
[ "$FAIL" -eq 0 ] || exit 1