#!/usr/bin/env python3
"""
Behavioral tests for netbox/dc-rack-mgmt-import.py -- drives the REAL main()/preflight
against an in-memory fake NB (tests/dc-rack-mgmt-import/fake_netbox.py) injected at the
get_nb() seam. No live NetBox. Invoked by run-tests.sh alongside the static greps.
The properties pinned here map to this repo's documented IPAM bug classes + the D-124
scheme (Scheme A -- transit-numbered mesh):
* HALF-WRITE (roles-aggregates-import.py): a bad rack IP (the .1 gateway) must abort
with ZERO creates, never "wrote the transit prefix then died on the rack IP".
* ROLE/SCOPE binding: the transit prefix carries the `transit` role id and is
dcim.site-scoped to vr1-dc0; the rack IP carries NO ipam role (NetBox ip-address
`role` is a choice field) -- identity is in dns_name/description.
* BAND arithmetic: rack IP within metal-admin 10.12.8.0/22, D-120 static band .2-.49,
NOT the .1 gateway, NOT .0.
* TRANSIT shape: /30 or /31, host-bits-clear, subnet_of the transit container.
* ALREADY-PRESENT = SKIP (idempotent), distinct from missing-infra = die.
* DRY BY DEFAULT: no --commit writes NOTHING.
* UPSTREAM-WRITE guard: --commit to a non-sandbox host refuses without the flag.
* NO INVENTED LITERAL: missing --transit-cidr / --rack-ip fails loud.
"""
from __future__ import annotations
import contextlib
import faulthandler
import importlib.util
import inspect
import io
import os
import sys
faulthandler.dump_traceback_later(30, exit=True)
HERE = os.path.dirname(os.path.abspath(__file__))
REPO_ROOT = os.path.dirname(os.path.dirname(HERE))
TARGET_PATH = os.path.join(REPO_ROOT, "netbox", "dc-rack-mgmt-import.py")
sys.path.insert(0, HERE)
import fake_netbox # noqa: E402
P = 0
F = 0
def ok(label):
global P
P += 1
print(f"PASS: {label}")
def no(label, detail=""):
global F
F += 1
print(f"FAIL: {label}" + (f" ({detail})" if detail else ""))
def check(cond, label, detail=""):
ok(label) if cond else no(label, detail)
def load_target():
spec = importlib.util.spec_from_file_location("dc_rack_mgmt_import", TARGET_PATH)
mod = importlib.util.module_from_spec(spec)
spec.loader.exec_module(mod)
return mod
T = load_target()
@contextlib.contextmanager
def captured_stdout():
buf = io.StringIO()
with contextlib.redirect_stdout(buf):
yield buf
# The operator-supplied, NetBox-assigned example values under test. Chosen INSIDE the
# scheme: transit /30 subnet_of the dedicated transit supernet 172.31.0.0/24; rack IP in the .2-.49 static band.
TRANSIT = "172.31.0.0/30"
TRANSIT31 = "172.31.0.0/31"
RACK = "10.12.8.5"
RACK_ADDR = "10.12.8.5/22" # how the tool stores it (host + metal-admin /22 mask)
def run_main(argv, fake):
T.get_nb = lambda base=None, token=None: fake
old = sys.argv
sys.argv = ["dc-rack-mgmt-import.py"] + argv
try:
return T.main()
finally:
sys.argv = old
def run_dies(label, argv, fake):
# swallow stdout AND stderr -- die() prints "FAIL: ..." to stderr by design, and
# for an EXPECTED death that is not a test failure, just noise on the gauntlet.
try:
with contextlib.redirect_stderr(io.StringIO()), captured_stdout():
run_main(argv, fake)
no(label, "did not raise SystemExit")
except SystemExit:
ok(label)
# Full-precondition fixture builders (transit role + transit container + vr1-dc0 site).
ROLE = {"slug": "transit", "name": "Transit", "id": 7}
CONTAINER = {"prefix": "172.31.0.0/24", "id": 10}
SITE_DC0 = {"slug": "vr1-dc0", "name": "VR1 DC0", "id": 20}
def full_fake(prefixes_extra=(), ip_extra=()):
return fake_netbox.FakeNB(
roles=[ROLE],
prefixes=[CONTAINER, *prefixes_extra],
sites=[SITE_DC0],
ip_addresses=list(ip_extra),
)
def base_args(extra=()):
return ["--transit-cidr", TRANSIT, "--rack-ip", RACK, *extra]
os.environ["NETBOX_URL"] = "http://10.10.1.10:8000" # a known sandbox (guard passes)
os.environ["NETBOX_TOKEN"] = "fake-token"
# -----------------------------------------------------------------------------
# 1. DRY RUN is the default and writes NOTHING.
# -----------------------------------------------------------------------------
fk = full_fake()
with captured_stdout() as out:
rc = run_main(base_args(), fk)
check(rc == 0, "dry run rc==0", str(rc))
check(out.getvalue().count("would CREATE") == 2, "dry run PLANS both objects",
str(out.getvalue().count("would CREATE")))
check(out.getvalue().count("CREATED ") == 0, "dry run CREATES nothing (no CREATED line)")
check("DRY RUN" in out.getvalue(), "dry run says so, loudly")
check(len(fk.creates) == 0, "dry run left the NetBox untouched (0 writes)", str(len(fk.creates)))
# -----------------------------------------------------------------------------
# 2. --commit writes exactly the transit prefix + the rack IP, correctly bound.
# -----------------------------------------------------------------------------
fk = full_fake()
with captured_stdout():
rc = run_main(base_args(["--commit"]), fk)
check(rc == 0, "commit rc==0", str(rc))
check(len(fk.creates) == 2, "commit created exactly 2 objects", str(len(fk.creates)))
paths = [p for p, _ in fk.creates]
check(paths.count("ipam/prefixes") == 1, "commit wrote exactly one ipam/prefixes")
check(paths.count("ipam/ip-addresses") == 1, "commit wrote exactly one ipam/ip-addresses")
check("ipam/roles" not in paths and "dcim/sites" not in paths,
"commit created NO role / site (those are preconditions)")
pfx = next(pl for pp, pl in fk.creates if pp == "ipam/prefixes")
ipa = next(pl for pp, pl in fk.creates if pp == "ipam/ip-addresses")
check(pfx.get("prefix") == TRANSIT, "transit prefix is the supplied CIDR", str(pfx.get("prefix")))
check(pfx.get("role") == 7, "transit prefix carries the transit role id (7)", str(pfx.get("role")))
check(pfx.get("scope_type") == "dcim.site", "transit prefix is dcim.site-scoped",
str(pfx.get("scope_type")))
check(pfx.get("scope_id") == 20, "transit prefix binds vr1-dc0's site id (20)", str(pfx.get("scope_id")))
check(pfx.get("status") == "active", "transit prefix is status active")
check(ipa.get("address") == RACK_ADDR, "rack IP stored with the /22 metal-admin mask",
str(ipa.get("address")))
check("role" not in ipa, "rack IP carries NO ipam role (NetBox ip role is a choice field)")
check(ipa.get("dns_name") == "vvr1-dc0", "rack IP dns_name identifies the rack controller vvr1-dc0",
str(ipa.get("dns_name")))
check("rack controller" in ipa.get("description", "").lower(),
"rack IP description notes it is the vr1-dc0 MAAS rack controller")
check(ipa.get("status") == "active", "rack IP is status active")
# -----------------------------------------------------------------------------
# 3. A /31 transit is also accepted (point-to-point).
# -----------------------------------------------------------------------------
fk = full_fake()
with captured_stdout():
rc = run_main(["--transit-cidr", TRANSIT31, "--rack-ip", RACK, "--commit"], fk)
check(rc == 0 and len(fk.creates) == 2, "a /31 transit is accepted and writes both objects",
str((rc, len(fk.creates))))
# -----------------------------------------------------------------------------
# 4. Already-present = idempotent SKIP (both present -> 0 creates, exit clean).
# -----------------------------------------------------------------------------
fk = full_fake(prefixes_extra=[{"prefix": TRANSIT, "id": 30}],
ip_extra=[{"address": RACK_ADDR, "id": 31}])
with captured_stdout() as out:
rc = run_main(base_args(["--commit"]), fk)
check(rc == 0, "both-present idempotent run rc==0")
check(out.getvalue().count("EXISTS") == 2, "both-present run reports both as EXISTS")
check(len(fk.creates) == 0, "both-present run creates nothing new")
# -----------------------------------------------------------------------------
# 5. Partial-present: transit already there, rack IP absent -> creates ONLY the rack IP.
# -----------------------------------------------------------------------------
fk = full_fake(prefixes_extra=[{"prefix": TRANSIT, "id": 30}])
with captured_stdout():
rc = run_main(base_args(["--commit"]), fk)
check(rc == 0 and len(fk.creates) == 1 and fk.creates[0][0] == "ipam/ip-addresses",
"transit-present run creates ONLY the missing rack IP",
str([p for p, _ in fk.creates]))
# 5b. Partial-present the other way: rack IP present, transit absent -> creates ONLY transit.
fk = full_fake(ip_extra=[{"address": RACK_ADDR, "id": 31}])
with captured_stdout():
rc = run_main(base_args(["--commit"]), fk)
check(rc == 0 and len(fk.creates) == 1 and fk.creates[0][0] == "ipam/prefixes",
"rack-IP-present run creates ONLY the missing transit prefix",
str([p for p, _ in fk.creates]))
# -----------------------------------------------------------------------------
# 6. HALF-WRITE GUARD -- a bad rack IP (the .1 gateway) aborts with ZERO creates,
# even though the transit prefix itself is perfectly valid. The transit must NOT
# be written before the rack-IP check fails.
# -----------------------------------------------------------------------------
fk = full_fake()
run_dies("half-write: rack IP == .1 gateway is REJECTED before any write",
["--transit-cidr", TRANSIT, "--rack-ip", "10.12.8.1", "--commit"], fk)
check(len(fk.creates) == 0, "half-write: the rejected run wrote NOTHING (transit not created)")
# -----------------------------------------------------------------------------
# 7. Missing infra = die (each precondition), each writing nothing.
# -----------------------------------------------------------------------------
fk = fake_netbox.FakeNB(roles=[], prefixes=[CONTAINER], sites=[SITE_DC0])
run_dies("missing transit role is REJECTED", base_args(["--commit"]), fk)
check(len(fk.creates) == 0, "missing-role run wrote nothing")
fk = fake_netbox.FakeNB(roles=[ROLE], prefixes=[], sites=[SITE_DC0])
run_dies("missing dedicated transit supernet 172.31.0.0/24 is REJECTED", base_args(["--commit"]), fk)
check(len(fk.creates) == 0, "missing-container run wrote nothing")
fk = fake_netbox.FakeNB(roles=[ROLE], prefixes=[CONTAINER], sites=[])
run_dies("missing vr1-dc0 site is REJECTED", base_args(["--commit"]), fk)
check(len(fk.creates) == 0, "missing-site run wrote nothing")
# -----------------------------------------------------------------------------
# 8. Transit CIDR shape / placement rejects.
# -----------------------------------------------------------------------------
run_dies("a transit OUTSIDE the transit container is REJECTED",
["--transit-cidr", "192.168.0.0/30", "--rack-ip", RACK, "--commit"], full_fake())
run_dies("a /29 transit is REJECTED (not point-to-point)",
["--transit-cidr", "172.31.0.0/29", "--rack-ip", RACK, "--commit"], full_fake())
run_dies("a /32 transit is REJECTED",
["--transit-cidr", "172.31.0.1/32", "--rack-ip", RACK, "--commit"], full_fake())
run_dies("a host-bits-set transit (172.31.0.1/30) is REJECTED",
["--transit-cidr", "172.31.0.1/30", "--rack-ip", RACK, "--commit"], full_fake())
run_dies("a non-CIDR transit is REJECTED",
["--transit-cidr", "not-a-cidr", "--rack-ip", RACK, "--commit"], full_fake())
# each of the above must have written nothing -- prove it once with a fresh fake
_fk = full_fake()
run_dies("bad transit writes nothing (proof)",
["--transit-cidr", "172.31.0.0/29", "--rack-ip", RACK, "--commit"], _fk)
check(len(_fk.creates) == 0, "the rejected bad-transit run wrote NOTHING")
# -----------------------------------------------------------------------------
# 9. Rack-IP band rejects.
# -----------------------------------------------------------------------------
run_dies("rack IP OUTSIDE metal-admin 10.12.8.0/22 is REJECTED",
["--transit-cidr", TRANSIT, "--rack-ip", "10.13.0.5", "--commit"], full_fake())
run_dies("rack IP == .1 gateway is REJECTED",
["--transit-cidr", TRANSIT, "--rack-ip", "10.12.8.1", "--commit"], full_fake())
run_dies("rack IP == .0 network address is REJECTED",
["--transit-cidr", TRANSIT, "--rack-ip", "10.12.8.0", "--commit"], full_fake())
run_dies("rack IP in-/22-but-above-static-band (.50) is REJECTED",
["--transit-cidr", TRANSIT, "--rack-ip", "10.12.8.50", "--commit"], full_fake())
run_dies("rack IP in-/22-but-in-a-higher-/24 (10.12.9.5) is REJECTED",
["--transit-cidr", TRANSIT, "--rack-ip", "10.12.9.5", "--commit"], full_fake())
run_dies("rack IP with a non-/22 mask is REJECTED",
["--transit-cidr", TRANSIT, "--rack-ip", "10.12.8.5/24", "--commit"], full_fake())
# accepted: the boundaries of the static band (.2 low, .49 high) both write.
for edge_ip in ("10.12.8.2", "10.12.8.49"):
fk = full_fake()
with captured_stdout():
rc = run_main(["--transit-cidr", TRANSIT, "--rack-ip", edge_ip, "--commit"], fk)
check(rc == 0 and len(fk.creates) == 2, f"rack IP band edge {edge_ip} is ACCEPTED",
str((rc, len(fk.creates))))
# -----------------------------------------------------------------------------
# 10. NO INVENTED LITERAL -- missing input fails loud (does not guess a value).
# -----------------------------------------------------------------------------
run_dies("missing --transit-cidr fails loud", ["--rack-ip", RACK], full_fake())
run_dies("missing --rack-ip fails loud", ["--transit-cidr", TRANSIT], full_fake())
# env fallback works (args-or-env): both via env, no flags.
os.environ["TRANSIT_CIDR"] = TRANSIT
os.environ["RACK_IP"] = RACK
fk = full_fake()
with captured_stdout():
rc = run_main(["--commit"], fk)
check(rc == 0 and len(fk.creates) == 2, "env TRANSIT_CIDR/RACK_IP are honored (args-or-env)",
str((rc, len(fk.creates))))
del os.environ["TRANSIT_CIDR"]
del os.environ["RACK_IP"]
# -----------------------------------------------------------------------------
# 11. Missing NETBOX_URL/TOKEN fails loud (does not guess a target).
# -----------------------------------------------------------------------------
_u = os.environ.pop("NETBOX_URL", None)
run_dies("missing NETBOX_URL fails loud", base_args(), full_fake())
os.environ["NETBOX_URL"] = _u
# -----------------------------------------------------------------------------
# 12. UPSTREAM-WRITE guard: --commit to a non-sandbox host refuses without the flag,
# proceeds with it. Sandbox needs no flag (covered above).
# -----------------------------------------------------------------------------
os.environ["NETBOX_URL"] = "https://netbox.baldurkeep.com" # the production apex
fk = full_fake()
run_dies("upstream --commit WITHOUT --yes-write-upstream is REFUSED", base_args(["--commit"]), fk)
check(len(fk.creates) == 0, "the refused upstream --commit wrote NOTHING")
fk = full_fake()
with captured_stdout():
rc = run_main(base_args(["--commit", "--yes-write-upstream"]), fk)
check(rc == 0 and len(fk.creates) == 2,
"upstream --commit --yes-write-upstream IS allowed and writes both objects",
str((rc, len(fk.creates))))
# a DRY upstream run needs no flag and writes nothing (the guard is a --commit gate).
fk = full_fake()
with captured_stdout():
rc = run_main(base_args(), fk)
check(rc == 0 and len(fk.creates) == 0, "a DRY upstream run is fine and writes nothing")
os.environ["NETBOX_URL"] = "http://10.10.1.10:8000"
# -----------------------------------------------------------------------------
# 13. Structural pins (a changed constant a behavioral test alone would miss).
# -----------------------------------------------------------------------------
check(T.ROLE_SLUG == "transit", "ROLE_SLUG is 'transit'")
check(T.CONTAINER == "172.31.0.0/24", "CONTAINER is 172.31.0.0/24 (dedicated transit supernet)")
check(T.SITE_SLUG == "vr1-dc0", "SITE_SLUG is vr1-dc0 (transit site scope)")
check(T.METAL_ADMIN == "10.12.8.0/22", "METAL_ADMIN is 10.12.8.0/22")
check(T.STATIC_BAND_LOW == 2 and T.STATIC_BAND_HIGH == 49,
"static band is .2-.49 (D-120)")
check(T.STATUS == "active", "STATUS is active")
check(T.RACK_DNS == "vvr1-dc0", "RACK_DNS is vvr1-dc0")
check(T.SANDBOX_HOSTS == {"localhost", "127.0.0.1", "10.10.1.10"},
"SANDBOX_HOSTS matches the sibling importers")
check("get_nb" in dir(T) and callable(T.get_nb), "get_nb() injection seam exists")
_src = inspect.getsource(T)
check('User-Agent"' in _src and "curl/8.5.0" in _src, "UA-aware (WAF-safe User-Agent set)")
print()
if F == 0:
print(f"test_logic: ALL PASS ({P} checks)")
sys.exit(0)
print(f"test_logic: {F} FAIL of {P + F}")
sys.exit(1)