mirror of
https://github.com/dtzp555-max/aci-sim.git
synced 2026-07-19 09:46:33 +00:00
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>
318 lines
16 KiB
Markdown
318 lines
16 KiB
Markdown
# 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.
|
|
|
|
```bash
|
|
# 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:
|
|
|
|
```bash
|
|
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:
|
|
|
|
```yaml
|
|
auth:
|
|
username: admin
|
|
password: cisco
|
|
# ndo_username / ndo_password only if you gave NDO a separate account
|
|
```
|
|
|
|
- **APIC plane ENFORCES it** — `aaaLogin` returns 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 explicit `SIM_USERNAME`/`SIM_PASSWORD` in the
|
|
environment wins (atomic — set either and you own both) -> else the topology `auth:` section ->
|
|
else the built-in `admin`/`cisco` default. `aci-sim run` prints 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.
|
|
|
|
```bash
|
|
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).
|
|
|
|
```bash
|
|
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):**
|
|
```yaml
|
|
# 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`/MSO `ansible_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.
|
|
|
|
```bash
|
|
# 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 same
|
|
`SIM_STATE_DIR` never collide); NDO's save writes `{name}.ndo.json`.
|
|
- A missing `load` name 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:**
|
|
|
|
```bash
|
|
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:**
|
|
|
|
```bash
|
|
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`.*
|