#!/usr/bin/env bash
# scripts/maas-node-power.sh [--commit] <power-address> [<hostname-prefix>]
#
#   e.g. bash scripts/maas-node-power.sh qemu+ssh://jessea123@172.31.0.2/system vr1-dc0
#        bash scripts/maas-node-power.sh --commit qemu+ssh://jessea123@172.31.0.2/system vr1-dc0
#
# Gives MAAS POWER CONTROL over VMs it has already ENLISTED, by setting
# `power_type=virsh` + power_address/power_id per machine. Domains are matched to
# MAAS machines BY MAC ADDRESS -- never by name, because MAAS assigns its own
# random hostnames at enlistment (a node is "ace-oyster", not "vr1-dc0-control-01").
#
# WHY PER-MACHINE AND NOT A VM HOST (pod). Operator-ruled 2026-07-20 after both
# alternatives were measured on the live vr1-dc0 substrate:
#   - A MAAS virsh POD is INCOMPATIBLE with this repo's node VMs. MAAS inventories
#     pod storage with `virsh domblkinfo <domain> vda`, which fails on disks
#     declared as pool+volume references -- the shape modules/node-vm produces:
#         internal error: missing storage backend for 'volume' storage
#     REPRODUCED LOCALLY on the rack with an ACTIVE, autostart pool, so it is
#     libvirtd, not a snap/SSH artifact. Supporting pods would mean converting
#     node-vm to file-path disks and re-applying every node domain.
#   - The pod is UNNECESSARY anyway: its D-103 role was DISCOVERY, and PXE
#     enlistment already discovered these machines. Only POWER was missing.
#   - Per-machine power is also the ROOSEVELT SHAPE: bare-metal nodes each carry
#     their own IPMI/BMC parameters. The pod was a VR1-only convenience, so this
#     is the smaller Roosevelt delta as well as the smaller change.
# See D-103/D-123 amendments and the 2026-07-20 session changelog.
#
# CREDENTIAL NOTE: MAAS dials the power address from whichever controller it
# chooses -- MEASURED to be the REGION, not the rack that owns the hardware. The
# SSH key must therefore exist in the REGION's MAAS snap, not only the rack's.
#
# DRY BY DEFAULT -- without --commit it prints the intended mapping and changes
# nothing. Run where a `maas admin` CLI profile exists (the Office1 region).
#
# Exit: 0 ok (or dry run) | 1 usage/precondition | 2 a machine update failed.
set -uo pipefail

COMMIT=0
if [ "${1:-}" = "--commit" ]; then COMMIT=1; shift; fi

POWER_ADDRESS="${1:-}"
PREFIX="${2:-vr1-dc0}"

usage() {
  echo "usage: maas-node-power.sh [--commit] <power-address> [<domain-name-prefix>]" >&2
  echo "  e.g. maas-node-power.sh --commit qemu+ssh://jessea123@172.31.0.2/system vr1-dc0" >&2
  echo "  default: DRY RUN -- prints the MAC-matched mapping, changes nothing." >&2
  exit 1
}
[ -n "$POWER_ADDRESS" ] || usage
case "$POWER_ADDRESS" in
  qemu*://*|qemu:///*) : ;;
  *) echo "FAIL: '$POWER_ADDRESS' does not look like a libvirt URI" >&2; exit 1 ;;
esac

command -v maas >/dev/null 2>&1 || { echo "FAIL: no maas CLI on PATH" >&2; exit 1; }
command -v virsh >/dev/null 2>&1 || { echo "FAIL: no virsh on PATH" >&2; exit 1; }
: "${MAAS_PROFILE:=admin}"
: "${VIRSH_URI:=$POWER_ADDRESS}"

# --- domain MAC -> domain name, from libvirt (the hardware's own view) ---
DOMMAP="$(mktemp)"; trap 'rm -f "$DOMMAP"' EXIT
for d in $(virsh -c "$VIRSH_URI" list --all --name 2>/dev/null | grep "^${PREFIX}-" || true); do
  # every NIC: a machine may PXE from any of them, so index them all
  virsh -c "$VIRSH_URI" domiflist "$d" 2>/dev/null \
    | awk -v dom="$d" 'NF>=5 && $5 ~ /:/ {print tolower($5)" "dom}'
done > "$DOMMAP"
[ -s "$DOMMAP" ] || { echo "FAIL: no domains matching '${PREFIX}-*' at $VIRSH_URI" >&2; exit 1; }
echo "== domains found: $(cut -d' ' -f2 "$DOMMAP" | sort -u | wc -l) (${PREFIX}-*) =="

# --- MAAS machines -> their MACs; match, then set power ---
MACHINES="$(maas "$MAAS_PROFILE" machines read 2>/dev/null)" \
  || { echo "FAIL: 'maas $MAAS_PROFILE machines read' failed -- is the CLI logged in?" >&2; exit 1; }

MAPPING="$(printf '%s' "$MACHINES" | python3 -c '
import json,sys
machines=json.load(sys.stdin)
dommap={}
for line in open(sys.argv[1]):
    mac,dom=line.split()
    dommap[mac]=dom
for m in machines:
    for i in m.get("interface_set",[]):
        mac=(i.get("mac_address") or "").lower()
        if mac in dommap:
            print(m["system_id"], m["hostname"], dommap[mac], m.get("power_type") or "-")
            break
' "$DOMMAP")"

[ -n "$MAPPING" ] || { echo "FAIL: no MAAS machine MAC matched any ${PREFIX}-* domain" >&2; exit 1; }

printf '%-10s %-22s %-24s %s\n' SYSTEM_ID MAAS_HOSTNAME DOMAIN CURRENT_POWER
printf '%s\n' "$MAPPING" | while read -r sid host dom cur; do
  printf '%-10s %-22s %-24s %s\n' "$sid" "$host" "$dom" "$cur"
done

if [ "$COMMIT" != "1" ]; then
  echo
  echo "DRY RUN -- nothing written. Re-run with --commit to set power on the machines above."
  exit 0
fi

echo
echo "== setting power_type=virsh =="
rc=0
printf '%s\n' "$MAPPING" | while read -r sid host dom cur; do
  if maas "$MAAS_PROFILE" machine update "$sid" \
        power_type=virsh \
        "power_parameters_power_address=$POWER_ADDRESS" \
        "power_parameters_power_id=$dom" >/dev/null 2>&1; then
    # GROUND TRUTH: MAAS must be able to actually TALK to the power driver.
    # A saved parameter is not power control; querying the state proves it.
    st="$(maas "$MAAS_PROFILE" machine query-power-state "$sid" 2>/dev/null \
          | python3 -c 'import json,sys; print(json.load(sys.stdin).get("state","?"))' 2>/dev/null)"
    case "$st" in
      on|off) echo "  [ok] $host -> $dom (power state: $st)" ;;
      *)      echo "  [--] $host -> $dom SET but power query returned '${st:-error}'"; rc=2 ;;
    esac
  else
    echo "  [--] $host -> $dom UPDATE FAILED"; rc=2
  fi
done
exit $rc
