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
+50
View File
@@ -0,0 +1,50 @@
"""MO (Managed Object) — building block of the ACI Managed Information Tree."""
from __future__ import annotations
from typing import Any
class MO:
"""A single node in the ACI MIT.
Attributes
----------
class_name : str e.g. ``"fvTenant"``
attrs : dict[str, str] must include ``"dn"``
children : list[MO] direct children in this MO's subtree
"""
def __init__(self, class_name: str, **attrs: str) -> None:
self.class_name = class_name
self.attrs: dict[str, str] = dict(attrs)
self.children: list[MO] = []
@property
def dn(self) -> str:
return self.attrs.get("dn", "")
def add_child(self, mo: MO) -> None:
"""Append *mo* to this MO's children list."""
self.children.append(mo)
def flat(self) -> list[MO]:
"""Return self + all descendants in depth-first order."""
result: list[MO] = [self]
for child in self.children:
result.extend(child.flat())
return result
def to_imdata(self) -> dict[str, Any]:
"""Produce the APIC wire shape for this MO.
``{"class_name": {"attributes": {...}, "children": [...]}}``
The ``children`` key is omitted when the MO has no children.
"""
body: dict[str, Any] = {"attributes": dict(self.attrs)}
if self.children:
body["children"] = [c.to_imdata() for c in self.children]
return {self.class_name: body}
def __repr__(self) -> str:
return f"MO({self.class_name!r}, dn={self.dn!r})"