Files
taodengandClaude Fable 5 7e9a175ce6 Initial public release — aci-sim v0.16.0
aci-sim is a stateful REST simulator of a 2-site Cisco ACI fabric
(per-site APIC + ND/NDO management planes) — for testing ACI automation
(Ansible cisco.aci / cisco.mso, aci-py, custom REST clients) and CI gates
without real hardware.

Highlights: APIC + NDO REST surface served from one in-memory MIT built
from a declarative topology.yaml; port mode + sandbox (real per-device IPs
on :443) run modes; LLDP/CDP neighbor visibility; NDO->APIC deploy mirror
(multi-site templates materialize onto the target sites' APIC stores);
real-APIC fvBD default attributes; file-backed state save/restore; an
`aci-sim` CLI (validate/show/graph/run/new/init/lldp); and an 888-test suite.

History squashed for the public release.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 20:06:36 +10:00

94 lines
3.3 KiB
Python

"""Sandbox E2E — replicate autoACI's exact NDO-discovery → Connect-All flow."""
import sys
from urllib.parse import urlparse
import httpx
BASE = "http://127.0.0.1:8000"
NDO_IP = "10.192.0.10"
c = httpx.Client(base_url=BASE, timeout=90.0)
results = []
def rec(n, ok, d=""):
results.append((n, ok, d))
print(f" {'PASS' if ok else 'FAIL'} {n} {d}")
def csrf():
return c.cookies.get("csrf_token", "")
def post(p, b, site=None):
h = {"X-CSRF-Token": csrf(), "Content-Type": "application/json"}
if site:
h["X-Active-Site"] = site
return c.post(p, json=b, headers=h)
def get(p, site=None):
h = {"X-Active-Site": site} if site else {}
return c.get(p, headers=h)
c.get("/api/health")
# 1. NDO login — the only thing the user types
r = post("/api/ndo/login", {"host": NDO_IP, "username": "admin", "password": "cisco", "verify_ssl": False})
rec(f"NDO login ({NDO_IP})", r.status_code == 200 and r.json().get("success"), f"HTTP {r.status_code}")
# 2. Discover sites — replicate the frontend's host extraction (new URL(url).hostname)
r = get("/api/ndo/sites")
sites = r.json().get("sites", [])
discovered = []
for s in sites:
urls = s.get("urls") or s.get("apicUrls") or []
host = ""
if urls:
try:
host = urlparse(urls[0]).hostname # == JS new URL(urls[0]).hostname
except Exception:
host = urls[0]
discovered.append({"name": s.get("name"), "apicHost": host})
rec("NDO discovered sites", len(discovered) >= 2, str([(d["name"], d["apicHost"]) for d in discovered]))
# 3. Connect All Sites — log in to each discovered APIC by IP, NO port
for d in discovered:
r = post("/api/auth/login", {"host": d["apicHost"], "username": "admin", "password": "cisco", "verify_ssl": False})
j = r.json()
rec(f"Connect {d['name']} ({d['apicHost']})", r.status_code == 200 and j.get("success"),
f"fabric={j.get('fabric_name')!r} ver={j.get('apic_version')!r}")
site_a = discovered[0]["apicHost"]
# 4. Topology (aggregates all connectors → both sites + ISN)
r = get("/api/topology", site=site_a)
topo = r.json() if r.status_code == 200 else {}
nodes, links, isn = topo.get("nodes", []), topo.get("links", []), topo.get("isnConnections", [])
rec("Topology", len(nodes) > 0,
f"{len(nodes)} nodes, {len(links)} links, {len(isn)} ISN links, roles={sorted({n.get('role') for n in nodes})}")
# 5. plugins
for name, params, mode in [
("fabric_nodes", {}, "rows"), ("fabric_bgp_evpn", {}, "rows"),
("adjacency_health", {}, "ran"), ("l3out_detail", {"tenant_name": "MS-TN1"}, "rows"),
("endpoint_search", {}, "rows"),
]:
r = post(f"/api/query/prebuilt/{name}", {"plugin_name": name, "params": params}, site=site_a)
qr = r.json() if r.status_code == 200 else {}
rows = qr.get("rows", [])
ok = (r.status_code == 200) if mode == "ran" else len(rows) > 0
rec(f"plugin {name}", ok, f"{len(rows)} rows · {str(qr.get('summary', ''))[:50]}")
# 6. NDO views
r = get("/api/ndo/sites")
rec("NDO /sites view", r.status_code == 200, str([s.get("name") for s in r.json().get("sites", [])]))
passed = sum(1 for _, ok, _ in results if ok)
print("=" * 60)
print(f" SANDBOX E2E: {passed}/{len(results)} passed")
fails = [n for n, ok, _ in results if not ok]
if fails:
print(" FAILED:", ", ".join(fails))
print("=" * 60)
sys.exit(0 if not fails else 1)