Newer
Older
openstack-caracal-dc-dc / scripts / lib-net.sh
# scripts/lib-net.sh
#
# Single source of truth for v1 (VR0 / Baldurkeep) pinned network values.
# SOURCED by discovery / verify scripts -- not executed directly. ASCII + LF.
# Authoritative per D-052 / D-053 (matches bundle.yaml at committed HEAD).
# Contains constants + tiny read-only helpers ONLY. No mutations, no state changes.
#
# CIDR is the stable key throughout: MAAS subnet IDs drift across cutovers
# (the D-052 cutover moved metal-internal to id=10, not the old id=6), so every
# lookup resolves BY CIDR, never by a hardcoded subnet ID. (PATTERN-1.)

# shellcheck shell=bash
# shellcheck disable=SC2034  # constants consumed by sourcing scripts

# Guard: refuse to run directly (it is a library).
if [ "${BASH_SOURCE[0]:-}" = "${0}" ]; then
  echo "lib-net.sh is a sourced library; do not run it directly." >&2
  exit 2
fi

# --- The six MAAS spaces / planes (D-052 / D-053). ---
PLANE_CIDRS=( "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" )
declare -A PLANE_NAME=(
  ["10.12.4.0/22"]="provider-public"
  ["10.12.8.0/22"]="metal-admin"
  ["10.12.12.0/22"]="metal-internal"
  ["10.12.16.0/22"]="data-tenant"
  ["10.12.32.0/22"]="storage"
  ["10.12.36.0/22"]="replication"
)
SPACES6=( provider-public metal-admin metal-internal data-tenant storage replication )

# Names that MUST be gone after the D-052 / D-053 cutover (deploy fails or mis-binds if any reappear).
STALE_SPACES=( provider metal data fabric-data lbaas provider-vip )

# Gateways: only provider-public and metal-admin route; the other four are gw=none.
declare -A PLANE_GW=( ["10.12.4.0/22"]="10.12.4.1" ["10.12.8.0/22"]="10.12.8.1" )

# The four non-API, non-PXE planes whose host NICs MAAS must have provisioned.
DATA_PLANE_CIDRS=( "10.12.12.0/22" "10.12.16.0/22" "10.12.32.0/22" "10.12.36.0/22" )

# metal-internal is a TAGGED VLAN bridged on the metal fabric; host links land on br-internal.
METAL_INTERNAL_CIDR="10.12.12.0/22"
METAL_INTERNAL_VID="103"
METAL_INTERNAL_IFACE="br-internal"

# Host identity (hostnames, octets, boot MACs, system_id resolution) now lives in
# scripts/lib-hosts.sh, keyed by HOSTNAME -- system_ids are re-minted on every
# (re-)enrollment, so the old SYSID-keyed maps here were a landmine and were retired
# (DOCFIX-040). Source lib-hosts.sh for HOSTS / HOST_OCTET / host_sysid().

# Triple HA VIPs (D-020 + D-052): each API charm carries provider/admin/internal columns,
# matching last octet, in the .50-.60 band. 11 clustered API charms.
VIP_PREFIX_PROVIDER="10.12.4"   # provider-public leg (public API VIPs share the FIP plane; Pattern A)
VIP_PREFIX_ADMIN="10.12.8"
VIP_PREFIX_INTERNAL="10.12.12"
VIP_OCTET_MIN=50
VIP_OCTET_MAX=60
VIP_COUNT_EXPECT=11

# D-003 provider FIP allocation pool: a RESERVED iprange on the provider-ext subnet
# so neutron owns it without colliding with a MAAS auto-static primary (KI-P3-001).
# Single source of truth for phase-04-network-{create,verify} (create stays
# env-overridable via FIP_START/FIP_END; verify pins to these).
FIP_POOL_START="10.12.5.0"
FIP_POOL_END="10.12.7.254"

# Keystone PUBLIC endpoint host (provider VIP, D-020/D-052 -- a .50-.60 provider VIP).
# Single source of truth for the KEYSTONE_VIP / KEYSTONE_HOSTPORT defaults across the
# tenant-* family + phase-03/phase-06 scripts (each stays env-overridable). Roosevelt:
# change here once, not in 6 scripts.
KEYSTONE_VIP_DEFAULT="10.12.4.50"

# --- $DC selector convention (2026-07-09, tooling gap register #1) ---
#
# Backward compatible by construction: sourcing this file with no further
# action continues to populate PLANE_CIDRS / PLANE_NAME / PLANE_GW / etc.
# exactly as above (VR0/DC0's real, measured values) -- every existing
# script that sources lib-net.sh keeps working completely unchanged.
#
# DC-aware scripts call `lib_net_select_dc "$DC"` ONCE, immediately after
# sourcing this file, before using any of the flat variables. This mirrors
# the $REPO convention (DOCFIX-139/141): one explicit, measured selector,
# never an inferred default.
#
# SELECTORS ARE REGION-QUALIFIED (D-119, ADOPTED 2026-07-14). The selector
# string IS the NetBox apex site slug -- vr0-dc0 / vr1-dc0 / vr1-dc1 -- so the
# shell, the OpenTofu modules and the IPAM authority all speak ONE namespace.
#
# WHY, because a bare `dcN` looks harmless: before D-119, `dc0` meant VR0's
# LIVE testcloud HERE, but VR1's FIRST DC in the NetBox importer. One string,
# two clouds, one of them in production. A bare dcN is therefore REJECTED --
# accepting it "for compatibility" would preserve exactly the ambiguity D-119
# exists to delete.
lib_net_select_dc() {
  local dc="${1:?usage: lib_net_select_dc <vr0-dc0|vr1-dc0|vr1-dc1>}"
  case "$dc" in
    vr0-dc0)
      : # VR0's DC0 -- the LIVE testcloud. The literals sourced above ARE its
        # real, measured values. Explicit no-op, not a guess.
      ;;
    vr1-dc0)
      : # VR1's FIRST DC. Same values as vr0-dc0, but for a DIFFERENT REASON:
        # D-101 rules that VR1's first DC INHERITS VR0 DC0's v4 layout UNCHANGED.
        # This is a SEPARATE case arm ON PURPOSE. The day D-101 stops holding,
        # this arm diverges and vr0-dc0's must not. Folding the two arms together
        # would encode the coincidence and lose the reason -- and the reason is
        # the thing that changes (VR1 DC0 gains its own v6 per the family matrix).
      ;;
    vr1-dc1)
      echo "FAIL: vr1-dc1 (VR1's SECOND DC) has no assigned network literals yet (D-101 NetBox-literals open item, tooling gap register #3) -- do not select it until NetBox assigns real CIDRs for it" >&2
      return 1
      ;;
    dc0|dc1|dc2)
      echo "FAIL: bare '$dc' is RETIRED (D-119). It was AMBIGUOUS ACROSS REGIONS: 'dc0' meant VR0's live DC0 here, but VR1's FIRST DC in the NetBox importer. Use the region-qualified selector: vr0-dc0 | vr1-dc0 | vr1-dc1" >&2
      return 1
      ;;
    *)
      echo "FAIL: unknown DC '$dc' (expected vr0-dc0, vr1-dc0, or vr1-dc1)" >&2
      return 1
      ;;
  esac
}

# --- tiny read-only helpers ---

# need_jq: jq is required (present on the jumphost). Returns non-zero if absent.
need_jq() {
  command -v jq >/dev/null 2>&1 || { echo "FAIL: jq not found on PATH (jumphost should have it)" >&2; return 1; }
}

# fourth_octet <ip>: echo the last dotted octet of an IPv4 address.
fourth_octet() { local ip="$1"; echo "${ip##*.}"; }