#!/usr/bin/env bash
# scripts/provider-vip-standup.sh [--apply]
#
# D-057: stand up the provider-vip MAAS plane so the carve + bundle can resolve it.
# Creates, idempotently and in this order (per MAAS semantics: a space attaches to a
# VLAN, and a subnet inherits the space via its VLAN):
# 1. space provider-vip
# 2. VLAN vid=104 on the PROVIDER fabric (the fabric that owns 10.12.4.0/22),
# mtu mirrored from the metal-internal VLAN
# 3. assign that VLAN -> space provider-vip
# 4. subnet 10.12.8.0/22 on that VLAN; gateway_ip + managed + dns set after
# 5. reserved VIP band 10.12.8.2-.100 (VIPs .50-.60 live in it)
#
# Default is DRY-RUN: resolves every id live by NAME/CIDR (PATTERN-1, no hardcoded
# MAAS ids) and prints each mutation it WOULD run, changing nothing. Pass --apply to
# execute. Idempotent: anything already present is SKIPped; re-runnable.
#
# NOTE: VIDs are PER-FABRIC in MAAS, so VID 104 existing on some OTHER fabric (e.g.
# nothing here, but in general) is irrelevant -- only the provider fabric is checked.
#
# MAAS-only by design (portable to Roosevelt -- no virbr1/host assumptions). The
# jumphost L3 gateway (virbr1.104 = 10.12.8.1) and the virbr1 vlan_filtering gate
# are SEPARATE host steps, not part of this script.
#
# Exit: 0 ok | 1 fatal
#
# CLI forms verified against Canonical MAAS docs (how-to-manage-networks):
# vlans create $FABRIC name= vid= mtu= ; vlan update $FABRIC $VID space= ;
# subnets create cidr= vlan= ; subnet update $CIDR key=value ;
# spaces create name= ; ipranges create type=reserved subnet= start_ip= end_ip=
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"
MAAS_PROFILE="${MAAS_PROFILE:-admin}"
FATAL=0
fail() { echo "FAIL: $*" >&2; FATAL=$((FATAL+1)); }
note() { echo "NOTE: $*"; }
hdr() { echo; echo "=== $* ==="; }
MODE="dryrun"; [ "${1:-}" = "--apply" ] && MODE="apply"
need_jq || exit 1
# ---- plane definition (constants; lib-net carries the shared CIDR/VID) ------
PROVIDER_CIDR="10.12.4.0/22" # fabric anchor: VID 104 lives on THIS fabric
PVIP_CIDR="$PROVIDER_VIP_CIDR" # 10.12.8.0/22
PVIP_VID="$PROVIDER_VIP_VID" # 104
PVIP_SPACE="provider-vip"
PVIP_GATEWAY="10.12.8.1" # D-057 routed plane; set "" to omit (default-route watch-item)
PVIP_RANGE_LO="10.12.8.2"
PVIP_RANGE_HI="10.12.8.100"
# ---- live resolvers (read-only; re-queried each call -- the plane mutates) --
maas_q() { maas "$MAAS_PROFILE" "$@"; }
prov_fabric() { maas_q subnets read | jq -r --arg c "$PROVIDER_CIDR" '.[]|select(.cidr==$c)|.vlan.fabric_id' | head -1; }
subid_of() { maas_q subnets read | jq -r --arg c "$1" '.[]|select(.cidr==$c)|.id' | head -1; }
subvid_of() { maas_q subnets read | jq -r --arg c "$1" '.[]|select(.cidr==$c)|.vlan.vid' | head -1; }
sub_field() { maas_q subnets read | jq -r --arg c "$1" --arg f "$2" '.[]|select(.cidr==$c)|(.[$f] // "")' | head -1; }
space_id() { maas_q spaces read | jq -r --arg n "$PVIP_SPACE" '.[]|select(.name==$n)|.id' | head -1; }
metal_mtu() { maas_q subnets read | jq -r --arg c "$METAL_INTERNAL_CIDR" '.[]|select(.cidr==$c)|.vlan.mtu' | head -1; }
metal_dns() { maas_q subnets read | jq -r --arg c "$METAL_INTERNAL_CIDR" '.[]|select(.cidr==$c)|(.dns_servers // []|join(","))' | head -1; }
# parent_mtu: VID 104 is a child of enp1s0 (the PROVIDER untagged plane), so its MTU must
# track the provider fabric -- NOT metal-internal (a child of a different fabric). A VLAN
# MTU exceeding its parent's would break the interface.
parent_mtu() { maas_q subnets read | jq -r --arg c "$PROVIDER_CIDR" '.[]|select(.cidr==$c)|.vlan.mtu' | head -1; }
# vlan obj id of a vid on a fabric (empty if absent)
vlanobj_on_fab() { maas_q vlans read "$1" | jq -r --arg v "$2" '.[]|select((.vid|tostring)==$v)|.id' | head -1; }
# current space NAME of a vid on a fabric
vlanspace_on_fab(){ maas_q vlans read "$1" | jq -r --arg v "$2" '.[]|select((.vid|tostring)==$v)|(.space // "")' | head -1; }
# ---- mutation emitter (runs in apply; prints WOULD in dryrun) ---------------
emit() {
local desc="$1"; shift
if [ "$MODE" = "apply" ]; then
echo " DO: $desc"
local out
if ! out="$(maas "$MAAS_PROFILE" "$@" 2>&1)"; then
fail "$desc"
echo " MAAS said: $(printf '%s' "$out" | grep -viE '^(Success|Machine-readable)' | head -3 | tr '\n' ' ')" >&2
return 1
fi
else
echo " WOULD: $desc"
echo " maas $MAAS_PROFILE $*"
fi
}
hdr "provider-vip stand-up mode=$MODE ($PVIP_SPACE / $PVIP_CIDR / VID $PVIP_VID)"
# ---- 0) resolve + guard the provider fabric --------------------------------
PROV_FAB="$(prov_fabric)"
[ -n "$PROV_FAB" ] || { fail "provider fabric not found (no MAAS subnet $PROVIDER_CIDR)"; exit 1; }
note "provider fabric_id = $PROV_FAB (VID $PVIP_VID will be created here)"
# guard: if the target subnet already exists, it MUST be on VID 104 (else misconfigured)
if [ -n "$(subid_of "$PVIP_CIDR")" ]; then
GOTVID="$(subvid_of "$PVIP_CIDR")"
[ "$GOTVID" = "$PVIP_VID" ] || { fail "subnet $PVIP_CIDR exists on VID '$GOTVID', expected $PVIP_VID -- refusing to proceed"; exit 1; }
fi
# ---- 1) space --------------------------------------------------------------
hdr "space $PVIP_SPACE"
if [ -z "$(space_id)" ]; then
emit "create space $PVIP_SPACE" spaces create name="$PVIP_SPACE"
else note "space $PVIP_SPACE exists -- SKIP create"; fi
# ---- 2) VLAN VID 104 on the provider fabric --------------------------------
hdr "VLAN VID $PVIP_VID on fabric $PROV_FAB"
if [ -z "$(vlanobj_on_fab "$PROV_FAB" "$PVIP_VID")" ]; then
MTU="$(parent_mtu)"; { [ -n "$MTU" ] && [ "$MTU" != "null" ]; } || MTU="1500"
emit "create VLAN vid=$PVIP_VID name=$PVIP_SPACE mtu=$MTU on fabric $PROV_FAB" \
vlans create "$PROV_FAB" name="$PVIP_SPACE" vid="$PVIP_VID" mtu="$MTU"
else note "VID $PVIP_VID already on fabric $PROV_FAB -- SKIP create"; fi
# ---- 3) assign VLAN -> space (idempotent) ----------------------------------
hdr "assign VID $PVIP_VID -> space $PVIP_SPACE"
CURSPACE="$(vlanspace_on_fab "$PROV_FAB" "$PVIP_VID")"
if [ "$CURSPACE" != "$PVIP_SPACE" ]; then
SID_SPACE="$(space_id)"; [ -n "$SID_SPACE" ] || SID_SPACE="<provider-vip-space-id>"
emit "assign fabric $PROV_FAB vid $PVIP_VID -> space $PVIP_SPACE (id $SID_SPACE)" \
vlan update "$PROV_FAB" "$PVIP_VID" space="$SID_SPACE"
else note "VID $PVIP_VID already in space $PVIP_SPACE -- SKIP"; fi
# ---- 4) subnet + gateway/managed/dns ---------------------------------------
hdr "subnet $PVIP_CIDR on VID $PVIP_VID"
if [ -z "$(subid_of "$PVIP_CIDR")" ]; then
VOBJ="$(vlanobj_on_fab "$PROV_FAB" "$PVIP_VID")"; [ -n "$VOBJ" ] || VOBJ="<vid-$PVIP_VID-vlan-obj-id>"
emit "create subnet $PVIP_CIDR vlan=$VOBJ" subnets create cidr="$PVIP_CIDR" vlan="$VOBJ"
else note "subnet $PVIP_CIDR exists -- SKIP create"; fi
# gateway (routed plane); only if set and not already correct
if [ -n "$PVIP_GATEWAY" ]; then
CURGW="$(sub_field "$PVIP_CIDR" gateway_ip)"
if [ "$CURGW" != "$PVIP_GATEWAY" ]; then
emit "subnet $PVIP_CIDR -> gateway_ip=$PVIP_GATEWAY" subnet update "$PVIP_CIDR" gateway_ip="$PVIP_GATEWAY"
else note "gateway_ip already $PVIP_GATEWAY -- SKIP"; fi
fi
# managed
if [ "$(sub_field "$PVIP_CIDR" managed)" != "true" ]; then
emit "subnet $PVIP_CIDR -> managed=true" subnet update "$PVIP_CIDR" managed=true
else note "subnet $PVIP_CIDR already managed -- SKIP"; fi
# dns mirrored from metal-internal (only if metal has dns and ours differs)
DNS="$(metal_dns)"
if [ -n "$DNS" ] && [ "$DNS" != "null" ]; then
CURDNS="$(maas_q subnets read | jq -r --arg c "$PVIP_CIDR" '.[]|select(.cidr==$c)|(.dns_servers // []|join(","))' | head -1)"
if [ "$CURDNS" != "$DNS" ]; then
emit "subnet $PVIP_CIDR -> dns_servers=$DNS (mirrors metal-internal)" subnet update "$PVIP_CIDR" dns_servers="$DNS"
else note "dns_servers already $DNS -- SKIP"; fi
else note "metal-internal has no dns_servers -- leaving $PVIP_CIDR dns unset"; fi
# ---- 5) reserved VIP band --------------------------------------------------
hdr "reserved VIP band $PVIP_RANGE_LO-$PVIP_RANGE_HI"
if maas_q ipranges read | jq -e --arg lo "$PVIP_RANGE_LO" '.[]|select(.start_ip==$lo)' >/dev/null 2>&1; then
note "reserved range starting $PVIP_RANGE_LO exists -- SKIP"
else
SUB="$(subid_of "$PVIP_CIDR")"; [ -n "$SUB" ] || SUB="<subnet-$PVIP_CIDR-id>"
emit "create reserved range $PVIP_RANGE_LO-$PVIP_RANGE_HI on subnet $SUB" \
ipranges create type=reserved subnet="$SUB" start_ip="$PVIP_RANGE_LO" end_ip="$PVIP_RANGE_HI" \
comment="provider-vip API VIP band (D-057)"
fi
# ---- result ----------------------------------------------------------------
hdr "resulting provider-vip subnet (live)"
maas_q subnets read | jq -r --arg c "$PVIP_CIDR" '.[]|select(.cidr==$c)
|{cidr, vid:.vlan.vid, fabric:.vlan.fabric, space, managed, gateway_ip, dns_servers}' \
|| note "(dry-run: plane not created yet -- the WOULD lines above are the plan)"
[ "$FATAL" = 0 ] || { echo; echo "completed with $FATAL failure(s)"; exit 1; }
echo; echo "OK ($MODE)"