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>
This commit is contained in:
2026-07-05 20:06:36 +10:00
co-authored by Claude Fable 5
commit 7e9a175ce6
117 changed files with 31769 additions and 0 deletions
+113
View File
@@ -0,0 +1,113 @@
"""build/health_faults.py — healthInst, faultInst, coopPol/coopInst, configExportP.
healthInst DN (read by topology.py line 160-163):
topology/pod-{pod}/node-{id}/sys/health
→ connector.query_dn(dn) → item.get("healthInst", {}).get("attributes", {})
faultInst DNs must:
1. Start with topology/pod-{pod}/node-{id}/ (for node-scoped queries)
2. Contain /node-{id}/ (for topology.py line 196 grouping regex)
Required attrs: severity, code, lc, type, subject, descr, created, lastTransition
"""
from __future__ import annotations
from aci_sim.mit.mo import MO
from aci_sim.mit.store import MITStore
from aci_sim.topology.schema import Site, Topology
_CREATED_TS = "2026-01-01T00:00:00.000+00:00"
def build(topo: Topology, site: Site, store: MITStore) -> None:
"""Emit health/fault/coop/config MOs for *site*."""
pod = site.pod
faults_cfg = topo.faults
hd = faults_cfg.health_defaults
# ---- Per-node healthInst --------------------------------------------------
for node in site.all_nodes():
store.add(MO(
"healthInst",
dn=f"topology/pod-{pod}/node-{node.id}/sys/health",
cur=str(hd.node),
min=str(hd.node),
max="100",
prev=str(hd.node),
))
# ---- Per-controller healthInst --------------------------------------------
# Real APIC exposes controller (role=controller, node IDs 1..N) health at
# the same .../sys/health DN shape as switch nodes; fabric.py already
# builds a topSystem at topology/pod-{pod}/node-{cid}/sys for each
# controller, so this just adds the matching healthInst there too.
for cid in range(1, site.controllers + 1):
store.add(MO(
"healthInst",
dn=f"topology/pod-{pod}/node-{cid}/sys/health",
cur=str(hd.node),
min=str(hd.node),
max="100",
prev=str(hd.node),
))
# ---- Per-tenant healthInst (for tenants deployed in this site) -----------
for tenant in topo.tenants:
if site.name not in tenant.sites:
continue
store.add(MO(
"healthInst",
dn=f"uni/tn-{tenant.name}/health",
cur=str(hd.tenant),
min=str(hd.tenant),
max="100",
prev=str(hd.tenant),
))
# ---- Seeded faultInst (node-local DNs for node-scoped query support) -----
# Map node_id → pod for this site
site_node_pods: dict[int, int] = {n.id: pod for n in site.all_nodes()}
for seed in faults_cfg.seed:
node_pod = site_node_pods.get(seed.node)
if node_pod is None:
continue # this fault belongs to a different site's node
# seed.code already carries the full ACI fault code (e.g. "F0532") —
# do NOT prepend another "F" (that produced invalid "FF0532").
fault_dn = (
f"topology/pod-{node_pod}/node-{seed.node}"
f"/local/svc-policyelem-id-0/uni/fault-{seed.code}"
)
store.add(MO(
"faultInst",
dn=fault_dn,
severity=seed.severity,
code=seed.code,
descr=seed.descr,
created=_CREATED_TS,
lastTransition=_CREATED_TS,
lc="raised",
type="operational",
subject="node",
))
# ---- coopPol + coopInst --------------------------------------------------
store.add(MO("coopPol", dn="uni/fabric/pol-coop", name="coop"))
for node in site.all_nodes():
store.add(MO(
"coopInst",
dn=f"topology/pod-{pod}/node-{node.id}/local/svc-coop-id-0/uni/epp/fv-[coop]/node-{node.id}",
operSt="formed",
))
# ---- configExportP + configJob (backup/export stubs) ---------------------
store.add(MO(
"configExportP",
dn="uni/fabric/configexp-defaultOneTime",
name="defaultOneTime",
))
store.add(MO(
"configJob",
dn="uni/fabric/configexp-defaultOneTime/job-defaultOneTime",
operSt="success",
executeTime=_CREATED_TS,
))