openstack-caracal-dc-dc / netbox /
..
README.md DOCFIX-152: NetBox multi-DC/dual-stack import pipeline (gap #3 mechanism) 12 hours ago
dc-dc-prefixes-import.py DOCFIX-160: adversarial review pass finds and fixes 3 real issues 7 hours ago
ipv4-prefixes-import.py Patches 7 days ago
ipv6-mark-reserved.py Patches 7 days ago
README.md

NetBox imports -- v1 (IPv4-only) and VR1 DC-DC (multi-DC, dual-stack) scope

This directory contains the NetBox import scripts for both the original v1 (IPv4-only, single-site) deployment and the VR1 DC-DC (two-DC, dual-stack) buildout. Each script is idempotent; re-running is safe.

VR1 DC-DC scripts (2026-07-09, closes tooling gap register #3)

dc-dc-prefixes-import.py

Extends the v1 single-site IPv4-only pattern to VR1's two-DC, dual-stack model (D-101, docs/design-decisions.md). Per DC (--dc dc1 or --dc dc2): creates the six-plane IPv4 structure plus the D-101 family-matrix IPv6 legs (ULA for metal-admin/metal-internal/data-tenant/storage/replication, GUA for provider-public).

Nothing is invented. D-101 names three literals as NetBox-assigned and explicitly "NOT hardcoded in this decision": the org ULA /48, the per-DC GUA carve out of the real ARIN 2602:f3e2::/32 block, and DC2's v4 supernet. All three are REQUIRED environment variables with no defaults -- ORG_ULA_48, DC_GUA_PREFIX, and (for --dc dc2 only) DC2_V4_SUPERNET. The script validates format/containment and FAILS LOUD if any is missing or malformed; it never silently substitutes DC1's values for DC2 or invents a /48. DC1's v4 planes ARE hardcoded here, deliberately -- D-101 rules DC1 inherits DC0's layout unchanged, so those six CIDRs are the ADOPTED decision text itself, not an inferred value.

The exact per-plane subdivision of the ULA /48 and the GUA carve (which /64 within the /56/whatever goes to which plane) is this script's OWN PROPOSED scheme -- documented in the script's module docstring -- and has NOT itself been operator-ratified as a D-NNN sub-decision. Review it before trusting what this script would write to a real NetBox.

NETBOX_URL=https://netbox.baldurkeep.com NETBOX_TOKEN=<token> \
    ORG_ULA_48=fd00:1234:5678::/48 DC_GUA_PREFIX=2602:f3e2:1000::/40 \
    python3 dc-dc-prefixes-import.py --dc dc1

NETBOX_URL=... NETBOX_TOKEN=... \
    ORG_ULA_48=fd00:1234:5678::/48 DC_GUA_PREFIX=2602:f3e2:2000::/40 \
    DC2_V4_SUPERNET=10.13.0.0/19 \
    python3 dc-dc-prefixes-import.py --dc dc2

Role slugs used here are NEW (provider-public/metal-admin/ metal-internal/data-tenant/storage/replication, matching the CURRENT six-plane model in scripts/lib-net.sh) -- NOT the same slugs as ipv4-prefixes-import.py below, which predates the D-052/D-053 six-plane cutover. This staleness in the v1 script is flagged, not fixed, here (out of scope -- that script is a v1/DC0 artifact this delivery doesn't touch). These roles must exist in NetBox before running (create out of band; this script does not create IPAM roles, same assumption the v1 scripts make).

Site collision note: DC1 may create prefixes numerically identical to the existing vr0-dc0 site's entries (D-101's inherit-unchanged ruling). NetBox permits duplicate global prefixes (soft warning, not a hard block), but whether to retire the vr0-dc0 entries once DC1 supersedes that rehearsal environment, or keep both for history, is an operator IPAM-hygiene call this script does not make for you (it prints a note, does not decide).

UNVALIDATED: no live NetBox / pynetbox-reachable instance was available this session (operator's workstation, not the vopenstack-jesse jumphost) to run this end-to-end against a real server. Tested via tests/dc-dc-prefixes-import/ -- an in-memory fake NetBox client exercising the full main() path for both DCs, idempotency, and every fail-loud case (37/37 PASS) -- but a real NetBox run has not confirmed the actual API shape/behavior.

Run order: after ipv4-prefixes-import.py/ipv6-mark-reserved.py are no longer relevant to VR1 (they are v1/DC0-scoped), and before Stage 2/3 of docs/dc-dc-deployment-workflow.md (which cite this pipeline's literals).

v1 (IPv4-only, single-site) scripts

ipv4-prefixes-import.py

Adds the IPv4 prefixes required for v1:

  • Metal /22 (10.12.8.0/22)
  • Provider /22 (10.12.4.0/22) with Provider IP Ranges (FIP pool + API VIPs)
  • LBaaS Mgmt /22 (10.12.32.0/22)
  • IPv4 tenant pool /16 (default 10.20.0.0/16; configurable via TENANT_POOL_CIDR env var)

Run order: first.

NETBOX_URL=https://netbox.baldurkeep.com NETBOX_TOKEN=<token> \
    python3 ipv4-prefixes-import.py

ipv6-mark-reserved.py

Marks existing IPv6 prefixes scoped to VR0 DC0 as reservation status per D-015 (v1/v2 fork). The IPv6 entries from earlier session work are NOT deleted -- they're preserved to document v2 design intent without implying they are active during v1.

Run order: second (after IPv4 prefixes are in place).

NETBOX_URL=https://netbox.baldurkeep.com NETBOX_TOKEN=<token> \
    python3 ipv6-mark-reserved.py

Use --dry-run to preview without changes. Use --revert only when v2 work begins (sets IPv6 prefixes back to active).

Deferred to v2

VLAN imports (vlans-import.py -- removed)

Per the v1/v2 fork session Q2, additional VR0 DC0-VLANs group entries (VIDs 50, 200, 220, 221, 222, 230, 260, 270) are not imported in v1.

Rationale: MAAS currently uses untagged-per-fabric on the existing network layout. Modeling additional VLAN entries in NetBox without corresponding network-side VLAN tagging would be misleading documentation. The VID 240 (OS-Provider) entry imported during prior session work is sufficient for v1 since it pairs with the Provider /22 prefix.

When v2 work begins, the VLAN import script will be re-introduced under the v2 design with VLAN tagging actually in play.

IPv6 active prefixes

The IPv6 prefix entries previously imported (Provider /60 + sub-/64s, Metal /60, Data /60, Storage /60, Replication /60, LBaaS Mgmt /60, OOB /60) are reserved in NetBox via ipv6-mark-reserved.py. They become active again under the planned /60 -> /64 refactor and full re-import when v2 work begins.

NetBox version

These scripts assume NetBox 4.x:

  • Prefix scope: scope_type="dcim.site" + scope_id=<id> (not legacy site= field)
  • Prefix read: p.scope (not p.site)
  • Status field: "reserved" / "active" as lowercase choice slugs

If your NetBox is older, the scripts will need adjustment.

Sequence for v1 deploy gate

  1. python3 ipv4-prefixes-import.py -- succeeds; verification block clean
  2. python3 ipv6-mark-reserved.py -- succeeds; all IPv6 prefixes now reserved
  3. Bundle drafting can proceed; values traced to NetBox are now authoritative