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>
16 KiB
aci-sim — Operation Manual / 操作手册
A stateful REST simulator of a Cisco ACI/NDO management plane (per-site APIC + Nexus
Dashboard/NDO). It answers moquery/class/DN queries computed from an in-memory MIT, applies
POST/DELETE mutations atomically, and models aaaLogin + NDO orchestration — enough fidelity
to test Ansible playbooks, aci-py, autoACI, or any tool that speaks the APIC/NDO REST API, with
no hardware, no data plane, fully deterministic and resettable.
一个有状态的 Cisco ACI/NDO 管理平面 REST 模拟器(每站点一个 APIC + Nexus Dashboard/NDO)。 它从内存中的 MIT 计算
moquery/class/DN 查询的响应、原子地应用POST/DELETE变更、模拟aaaLogin与 NDO 编排 —— 保真度足以测试 Ansible playbook、aci-py、autoACI,或任何讲 APIC/NDO REST API 的工具,无需硬件、无数据平面、完全确定且可重置。
1. Overview / 概述
- What it does: serves the APIC REST plane (GET class/mo queries, POST/DELETE mutations,
aaaLogin) and the NDO/ND REST plane (schema/template CRUD + deploy). Both are backed by one
in-memory Management Information Tree (MIT) built from
topology.yaml. - What it does NOT do: no data plane, no real NX-OS/APIC software stack, no live telemetry. It models the object model + REST behavior, not packet forwarding.
能做什么:提供 APIC REST 平面(GET class/mo 查询、POST/DELETE 变更、aaaLogin)和 NDO/ND REST 平面(schema/template 增删改 + deploy)。两者都由一棵从
topology.yaml构建的内存 MIT 支撑。 不做什么:没有数据平面、没有真实 NX-OS/APIC 软件栈、没有实时遥测。它模拟对象模型 + REST 行为,不做报文转发。
Primary use cases / 主要场景: Ansible playbook testing · CI gate before real hardware · SDK/provider testing · AI-agent sandbox · contract/regression tests · teaching/demo.
2. Installation / 安装
Requires Python >= 3.11.
# from a checkout (or pip install "aci-sim @ git+<repo-url>")
python3 -m venv .venv
.venv/bin/pip install -e . # runtime deps: fastapi, uvicorn[standard], pydantic, pyyaml
.venv/bin/pip install -e '.[dev]' # + test deps: pytest, pytest-asyncio, httpx
aci-sim --help
需要 Python >= 3.11。
pip install -e .安装运行依赖(fastapi/uvicorn/pydantic/pyyaml);.[dev]额外装测试依赖。装完aci-sim --help应列出子命令,无 traceback。
3. Topology & the init wizard / 拓扑与 init 向导
Everything is driven by topology.yaml (Pydantic-validated). Author it three ways:
aci-sim init # interactive wizard (APIC setup-dialog style) — RECOMMENDED
aci-sim new --sites 2 # non-interactive scaffold with flags
# or hand-edit topology.yaml
aci-sim validate topology.yaml # validate before running (CI gate)
aci-sim show topology.yaml # print the derived inventory (IDs/IPs/ports)
The aci-sim init wizard walks: Step 0 Admin account -> Step 1 Deployment type (single
fabric / multi-site) -> Step 2 per-site (name/id/asn/pod/controllers/IPs) -> Step 3 multi-site
(NDO IP + ISN). Press ENTER to accept the [bracketed] default.
一切由
topology.yaml驱动(Pydantic 校验)。三种方式创建:aci-sim init(交互式向导, 仿 APIC setup dialog,推荐)、aci-sim new(带 flag 非交互脚手架)、手改。运行前用aci-sim validate校验。向导顺序:Step 0 管理员账号 -> 1 部署类型 -> 2 每站点参数 -> 3 多站点 (NDO IP + ISN)。回车接受[方括号]里的默认值。
4. Admin account / 管理员账号
The wizard's Step 0 sets the fabric admin credentials, written to a topology.yaml auth:
section:
auth:
username: admin
password: cisco
# ndo_username / ndo_password only if you gave NDO a separate account
- APIC plane ENFORCES it —
aaaLoginreturns 401 on a username/password mismatch. - NDO plane is lenient by design (accepts any credential, for cisco.mso/aci-py compat); the same account drives it by default.
- Credential precedence at
aci-sim run: an explicitSIM_USERNAME/SIM_PASSWORDin the environment wins (atomic — set either and you own both) -> else the topologyauth:section -> else the built-inadmin/ciscodefault.aci-sim runprints which source is live (never the password).
向导 Step 0 设置管理员凭据,写进
topology.yaml的auth:段。APIC 平面强制校验(密码错 返回 401);NDO 平面故意宽松(接受任意凭据,为兼容 cisco.mso/aci-py),默认用同一账号。aci-sim run的凭据优先级:环境里显式的SIM_USERNAME/SIM_PASSWORD最高(原子:设了任一就 两个都归你)-> 其次 topology 的auth:-> 再次内建默认admin/cisco。run 会打印当前凭据来源 (不打印密码)。
5. Running — two modes / 运行的两种模式
5a. Port mode (default) / 端口模式(默认)
Binds 127.0.0.1 on distinct ports. Simplest; good for local dev + CI.
aci-sim run topology.yaml
# APIC LAB1 -> https://127.0.0.1:8443 APIC LAB2 -> :8444 NDO -> :8445
5b. Sandbox mode (real per-device IPs on :443) / sandbox 模式(真实 per-device IP,:443)
Each APIC/NDO gets its own loopback-alias IP on :443 (like real gear). Needed for tools that
assume one IP per controller (e.g. autoACI multi-site discovery). Requires root (adds lo
aliases + binds :443).
sudo bash scripts/sandbox-up.sh # adds lo aliases from topology mgmt IPs, binds :443, detaches
# APIC LAB1 -> https://10.192.0.11:443 APIC LAB2 -> https://10.192.128.11:443
# NDO -> https://10.192.0.10:443
sudo bash scripts/sandbox-down.sh # stop + remove aliases
The sandbox IPs come from topology.yaml's fabric.ndo_mgmt_ip + each site.mgmt_ip. On Linux
they are lo aliases -> reachable only on that host (run clients on the same box). PID in
/tmp/aci-sim-sandbox.pid, log in /tmp/aci-sim-sandbox.log.
端口模式(默认):绑
127.0.0.1不同端口,最简单,适合本地开发/CI。 sandbox 模式:每个 APIC/NDO 拿到自己的 loopback 别名 IP、都在 :443(像真机),适合假设"每个 控制器一个 IP"的工具(如 autoACI 多站点发现)。需 root(加lo别名 + 绑 :443)。sandbox IP 来自topology.yaml的ndo_mgmt_ip和各site.mgmt_ip;Linux 上是lo别名,只在本机可达(客户端 要在同一台跑)。PID/日志见/tmp/aci-sim-sandbox.{pid,log}。
6. Connecting clients / 连接客户端
Credentials = your admin account (default admin/cisco). TLS is self-signed -> disable cert
validation.
Ansible — cisco.aci (single fabric) / cisco.mso (multi-site):
# inventory: point apic_host / MSO ansible_host at the sim's IP:port
apic_host: "10.192.0.11:443" # sandbox, or 127.0.0.1:8443 in port mode
apic_username: admin
apic_password: cisco
apic_validate_certs: no
aci-py (Python pusher): point --apic at the APIC IP; for multi-site pass an NDO connector
(Ndo(host, username, password)) so tenants become NDO-managed (visible in autoACI).
autoACI: log in to the NDO IP (10.192.0.10) -> Discover -> Connect All Sites. It reads
NDO, so multi-site tenants (created via cisco.mso or aci-py's NDO path) appear there.
凭据 = 你的管理员账号(默认
admin/cisco),TLS 自签 -> 关掉证书校验。Ansible 用 cisco.aci (单站点)/cisco.mso(多站点),inventory 里把apic_host/MSOansible_host指向 sim 的 IP:端口。 aci-py 把--apic指向 APIC;多站点要传 NDO 连接器,tenant 才会被 NDO 管理(autoACI 才看得到)。 autoACI 登录 NDO IP(10.192.0.10)-> Discover -> Connect All Sites。
Multi-site invariant / 多站点不变量: a tenant is "multi-site" only if it exists in NDO. An APIC-only push (e.g. aci-py without an NDO connector) creates an APIC-local tenant that autoACI's NDO view will NOT show. 名字含多站点语义的 tenant 必须出现在 NDO 上。
7. Querying the MIT / 查询 MIT
Speaks the plain APIC REST API — works with curl, httpx, cisco.aci, or a real APIC.
# login -> cookie
curl -sk -c j.txt -X POST https://<apic>/api/aaaLogin.json \
-d '{"aaaUser":{"attributes":{"name":"admin","pwd":"cisco"}}}'
# class query (== moquery -c fvTenant)
curl -sk -b j.txt https://<apic>/api/class/fvTenant.json
# mo query + subtree
curl -sk -b j.txt "https://<apic>/api/mo/uni/tn-MS-TN1.json?query-target=subtree&target-subtree-class=fvBD"
# LLDP / CDP neighbors
curl -sk -b j.txt https://<apic>/api/class/lldpAdjEp.json
aci-sim lldp topology.yaml [--site N --node N --cdp --json] prints a show lldp neighbors-style
table without hand-writing URLs. Query subscriptions + websocket push-on-change are supported
(?subscription=yes + /socket<token>).
讲标准 APIC REST API,
curl/httpx/cisco.aci/真机 APIC 都能用。class 查询等价moquery -c; mo 查询支持query-target=subtree。aci-sim lldp直接打印邻居表。支持查询订阅 + websocket 变更推送(?subscription=yes)。
8. CLI reference / CLI 参考
| Command | Purpose / 用途 |
|---|---|
aci-sim init |
interactive wizard -> topology.yaml / 交互向导 |
aci-sim new --sites N ... |
non-interactive scaffold / 非交互脚手架 |
aci-sim validate FILE |
validate topology (CI gate) / 校验拓扑 |
aci-sim show FILE [--json] |
print derived inventory / 打印推导出的清单 |
aci-sim lldp FILE [--site --node --cdp --json] |
LLDP/CDP neighbor table / 邻居表 |
aci-sim graph FILE -o out.svg|.html |
render topology diagram / 渲染拓扑图 |
aci-sim run FILE |
run the supervisor (port mode) / 起 supervisor(端口模式) |
scripts/sandbox-up.sh / sandbox-down.sh |
sandbox mode (real IPs :443) / sandbox 模式 |
scripts/sim-state.sh {save|restore} NAME |
save/restore whole-fabric state (§10) / 保存恢复整个 fabric 状态(见 §10) |
9. /_sim control API / 控制 API
For AI-agent sandboxes and test isolation: snapshot / restore / reset the MIT, and seed faults — so each test starts from a known state.
面向 AI agent 沙箱和测试隔离:对 MIT 做 snapshot / restore / reset、注入 fault —— 让每个测试从 已知状态开始。
10. State persistence / 状态保存与恢复
In sandbox/port mode the sim's state is in-memory only — restarting the process wipes every tenant/schema/template on every plane. To survive a restart, save state to disk first and restore it after.
Per-plane endpoints (mirrors the /_sim control API in §9):
| Plane | Save | Load |
|---|---|---|
| APIC (each site) | POST /_sim/save/{name} |
POST /_sim/load/{name} |
| NDO | POST /_sim/save/{name} |
POST /_sim/load/{name} |
- APIC's save writes
{name}.{site.id}.apic.json(keyed by site id, so multiple sites in the sameSIM_STATE_DIRnever collide); NDO's save writes{name}.ndo.json. - A missing
loadname returns 404 — an APIC-envelope error (imdata[0].error) on the APIC plane, a plain{"detail": ...}on the NDO plane (matching each plane's existing error shape). - Files are plain JSON under
SIM_STATE_DIR(default~/.aci-sim/state) — human-readable, diffable, safe to check into a fixtures directory for a known-good baseline.
Wrapper script — save/restore the WHOLE fabric (all APIC sites + NDO) in one command:
scripts/sim-state.sh save mybaseline # snapshot every plane
scripts/sim-state.sh restore mybaseline # restore every plane (maps to /_sim/load)
It derives each plane's address the same way scripts/sandbox-up.sh does (straight from
topology.yaml's fabric.ndo_mgmt_ip / each site.mgmt_ip, sandbox mode's real per-device
:443 IPs), and falls back to port mode (127.0.0.1:8443/:8444/:8445) if those IPs aren't
reachable — so it works unmodified in either running mode.
Restart workflow:
scripts/sim-state.sh save mybaseline
sudo bash scripts/sandbox-down.sh && sudo bash scripts/sandbox-up.sh # or Ctrl-C + aci-sim run
scripts/sim-state.sh restore mybaseline
SIM_STATE_DIR — override the base directory for all saved state (default
~/.aci-sim/state; created automatically). Tests set this to an isolated tmp_path so
nothing is ever written under the real home directory.
sandbox/端口模式下 sim 状态只在内存里——重启进程会清空所有平面上的 tenant/schema/template。 要跨重启保留状态,重启前先保存、重启后再恢复。
各平面端点(与 §9 的
/_sim控制 API 呼应):APIC(每个站点)和 NDO 都提供POST /_sim/save/{name}/POST /_sim/load/{name}。APIC 的保存文件名为{name}.{site.id}.apic.json(按 site id 区分,同一个SIM_STATE_DIR里多个站点不会冲突); NDO 的保存文件名为{name}.ndo.json。load遇到不存在的 name 返回 404 ——APIC 平面是 APIC 信封错误(imdata[0].error),NDO 平面是普通{"detail": ...}(各自匹配平面已有的 错误格式)。文件是SIM_STATE_DIR(默认~/.aci-sim/state)下的纯 JSON——可读、可 diff,也可以存进 fixtures 目录当已知良好基线用。一键保存/恢复整个 fabric(所有 APIC 站点 + NDO):
scripts/sim-state.sh save <name>/scripts/sim-state.sh restore <name>(restore对应/_sim/load)。地址推导方式与scripts/sandbox-up.sh一致(从topology.yaml读ndo_mgmt_ip/各site.mgmt_ip),这些 IP 不可达时自动退回端口模式(127.0.0.1:8443/:8444/:8445),两种运行模式下都能直接用。重启工作流:先
sim-state.sh save→ 重启 sim(sandbox-down.sh && sandbox-up.sh,或 Ctrl-C 后aci-sim run)→ 再sim-state.sh restore。
SIM_STATE_DIR:覆盖所有保存状态的根目录(默认~/.aci-sim/state,自动创建)。 测试会把它设成隔离的tmp_path,绝不会写到真实 home 目录下。
11. Multi-site vs single-fabric / 多站点 vs 单站点
- Single-fabric: one APIC. Tenants pushed directly via cisco.aci — APIC-local, no NDO.
- Multi-site: NDO + per-site APICs. Tenants are created as NDO schemas/templates and deployed to sites; only then do they appear on the site APICs (and in autoACI).
- multi-pod ~= single-fabric for Ansible tenant testing — the IPN lives on external Nexus (NX-OS),
not cisco.aci; the only per-pod nuance is the pod id in static binding paths, covered by
site.pod.
单站点:一个 APIC,tenant 经 cisco.aci 直推,APIC 本地,无 NDO。多站点:NDO + 各站点 APIC,tenant 建成 NDO schema/template 再 deploy 到站点,之后才落到站点 APIC(和 autoACI)。对 Ansible tenant 测试,multi-pod ~= single-fabric —— IPN 在外部 Nexus 上(NX-OS),不经 cisco.aci。
12. Troubleshooting / 排错
| Symptom / 现象 | Cause & fix / 原因与解决 |
|---|---|
aaaLogin 401 |
password != the enforced account; check auth: / SIM_USERNAME+SIM_PASSWORD / the run banner. |
sandbox: Needs root |
sudo bash scripts/sandbox-up.sh (needs lo alias + :443 bind). |
| sandbox: nothing on :443 after start | old listener still holds the socket; the script waits for it to free — check /tmp/aci-sim-sandbox.log. |
| deep-root subtree query returns 0 | query a DN that has a materialized MO (e.g. .../sys, not an un-materialized container). |
| client can't reach sandbox IP from another host | sandbox IPs are lo aliases -> same-host only; use port mode + the host LAN IP for remote clients, or run the client on the sim host. |
pip install CLI crashes on import |
ensure v0.13.1+ (earlier packaging didn't declare deps / omitted subpackages). |
Generated as part of the sim playbook-E2E effort. For the REST contract details see
docs/CONTRACT.md; for design rationale see docs/DESIGN.md; for the changelog see CHANGELOG.md.