Commit Graph
14 Commits
Author SHA1 Message Date
dtzp555-maxandClaude Opus 4.8 c2959ab2cb feat(rest-aci): faithful created/modified/unchanged write-status (F8a) + idempotent delete-of-missing (F1) (v0.25.0)
The APIC POST handler unconditionally stamped status:"created" on every
write with no comparison to stored state. cisco.aci.aci_rest derives its
"changed" result from a recursive deep-scan of the response for any status
in {created,modified,deleted}, so every raw aci_rest-driven write reported
changed=true forever — breaking pass2 idempotency across the L3Out topology
(l3extLNodeP/l3extLIfP/l3extRsNodeL3OutAtt/l3extRsPathL3OutAtt/bgpPeerP/
bfdIfP), static-path, and PBR-redirect blocks.

F8a computes a real per-MO status at the write choke point
(writes._upsert_recursive) by diffing ONLY the caller's raw posted attrs
(excluding dn/status, before the create-time _CLASS_DEFAULTS overlay)
against the pre-existing stored MO. apply() builds imdata from only the
changed MOs — empty imdata (+ totalCount "0") when nothing changed, which is
the shape that makes aci_rest's changed()-scan return false. post_mo reads
?rsp-subtree and threads it through (modified/absent/full -> changed list;
no -> empty). Higher-level cisco.aci modules were already idempotent
(client-side get-diff) and are unaffected. The flat changed-MO response is
changed()-scan faithful; real APIC's nested rsp-subtree wire shape is a
documented deferred enhancement (no real-gear capture to reproduce it).

F1 fold-in: deleting an already-absent DN now reports unchanged (store pop
is a silent no-op) instead of deleted, matching real-APIC idempotent delete.

Verification: full pytest 1143 passed (1131 baseline + 12 new F8a tests,
zero regressions); independent opus review traced the posted-keys-only /
pre-defaults diff invariant + dict-aliasing safety to ground truth
(APPROVE); live E2E gate on the restarted sim — create_tenant SF-TN1 pass2
changed 6->0 (idempotent), pass1 still builds (changed=17), F10 bad-mtu 400
+ good-mtu 200 intact.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017zdUTqU9fvCCvF3uVGZsu1
2026-07-10 22:52:43 +10:00
dtzp555-max 694454d9fc docs: scrub private repo name, self-contain design references, document F12 coverage bias 2026-07-10 19:55:16 +10:00
dtzp555-maxandClaude Fable 5 c5f8d0f940 feat(ndo): enforce service-graph device-existence + uniform site redirect (v0.24.0)
Closes F4 + F12: the sim NDO silently accepted two writes that real NDO 4.x
rejects server-side (the two documented walls behind the two-phase deploy):

- F4: binding a site-local service graph whose serviceNodes[].device.dn does
  not exist on the target APIC -> now 400 "Service graph device <dev> does
  not exist in tenant <t> in Fabric <site>" (cross-plane check against the
  per-site APIC MITStore; single-process sandbox shares live store objects).
- F12: partial per-fabric serviceGraphRelationship redirect coverage -> now
  400 "must have uniform redirect policy configured on all fabrics";
  validated on POST-request state (deepcopy->apply->validate->commit) so a
  single atomic all-fabric PATCH passes, matching real NDO. Coverage-based
  per (template, contract) — sibling templates with same-named contracts
  cannot cross-reject.

Non-service-graph PATCHes keep the in-place fast path. Fail-safe
default-allow; all-or-nothing on rejection. +33 tests (1131 passed).
Live gate: MS-TN2 two-phase chain 12/12 green (no false-reject); phase2-only
400s with golden message + zero residue; single-fabric PATCH 400s while the
atomic both-fabric control returns 200.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017zdUTqU9fvCCvF3uVGZsu1
2026-07-10 18:01:14 +10:00
dtzp555-maxandClaude Opus 4.8 fe3d13221a feat(validation): server-side MO value validation — real-APIC 400 on bad values (v0.23.0)
Closes F10: the sim silently stored out-of-range/malformed scalar values
(e.g. vlan-9999, IP 999.888.777.x) that real APIC/NDO reject server-side.

New aci_sim/rest_aci/validators.py: table-driven RULES {(class,prop) -> validator}
(range/enum/IP·MAC-format/vlan-encap). Enforced in writes.py::_validate_planned
(APIC) and ndo/patch.py::apply_json_patch (NDO) before any store mutation.
Fail-safe default-allow: only registered (class,prop) validated; unknown pass.
Bad values now -> APIC Error 103 Malformed MO body. ~45 rules / ~18 classes.

Constraints sourced from vendored cisco.aci arg-specs + standard ACI ranges,
cross-checked against the real-machine var corpus for zero false-rejects
(regression: full create_tenant MS-TN1 + SF-TN1 chains stay green). +176 tests
(1098 passed). Referential integrity split out as F11 (needs real-APIC study).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017zdUTqU9fvCCvF3uVGZsu1
2026-07-09 09:59:34 +10:00
dtzp555-maxandClaude Fable 5 ffaf9964c2 feat(ndo): DELETE endpoints for schemas and tenants (v0.22.0)
F5 fidelity gap: cisco.mso mso_schema/mso_tenant state=absent send
DELETE /mso/api/v1/schemas/{id} and DELETE /mso/api/v1/tenants/{id};
both were unrouted (405), blocking E2E baseline cleanup.

- DELETE schemas/{id}: removes detail + summary (seeded or extra) +
  policy-states; 404 on unknown id. No cascade into the APIC deploy
  mirror — real NDO orphans deployed objects on schema delete.
- DELETE tenants/{id} (+ bare /api/v1 alias, matching the GET pair):
  400 while any schema template tenantId references the tenant,
  200 once dereferenced, 404 on unknown id.
- tests/test_ndo_delete.py: 8 tests incl. no-cascade mirror check.

Full suite: 922 passed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017zdUTqU9fvCCvF3uVGZsu1
2026-07-08 10:57:31 +10:00
dtzp555-maxandClaude Fable 5 867c05c9c5 feat: APIC-style write validation, query-target=children, contract shadow mirroring (v0.21.0)
Three fidelity gaps surfaced by one broken TN2 var file (missing firewall
block), all fixed:
- writes: reject malformed fvSubnet/l3extSubnet/vnsRedirectDest ip values
  with an APIC-style 400 before any store mutation (deletes exempt)
- query engine: support query-target=children (direct children, flat
  imdata, root excluded) — was silently returning empty
- deploy mirror: materialize vzFilter/vzEntry, vzBrCP/vzSubj (+ filter and
  service-graph subject bindings) and vnsAbsGraph shadows per target site;
  undeploy removes them

E2E-verified through the real pipeline (aci-py hidden push): a TN2 var
missing the fw block now fails with 'Invalid value "." for property ip of
vnsRedirectDest ... -> 400' and nothing lands; a complete var set builds
both MS tenants with contract shadows + sgt-FW graph binding on both sites.
Suite: 914 passed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 18:11:59 +10:00
dtzp555-maxandClaude Fable 5 bcf26826b1 fix(graph): border leaves share the leaf row (no bogus third tier); regen README diagram (v0.20.3)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 13:58:44 +10:00
dtzp555-maxandClaude Fable 5 dc43b3550d fix(graph): legend min-width on narrow diagrams; regenerate README topology image (v0.20.2)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 13:31:45 +10:00
dtzp555-maxandClaude Fable 5 e52ac1981d feat: /31 point-to-point spine-ISN underlay + graph physical-links table (v0.20.0)
Spine-to-ISN links now use per-link /31 point-to-point sub-interface pairs
(Cisco Multi-Site design guide) instead of a shared /24 with a .254 IPN
router; ospfIf/ospfAdjEp carry the pair addresses (ISN CSW LLDP mgmt IP
unchanged - device-level address). aci-sim graph follows the autoACI topology
view logic: solid ISN uplinks, link-type legend entries, and a physical-links
table (fabric links + ISN uplinks with /31 local/remote IPs) under the
diagram. Tests updated + strengthened; suite: 908 passed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 12:18:23 +10:00
dtzp555-max c926702a29 fix(isn): flip sub-interface naming to real-gear conventions (v0.19.1)
Owner review of the v0.19.0 demo + Cisco docs/real spine CLI verification:
ACI spines name the VLAN-4 sub-if after the PORT (eth1/49.49, cf.
Eth1/29.29-style in show ip ospf neighbors vrf overlay-1); the ISN Nexus
names its dot1q-4 sub-if after the VLAN (Ethernet1/1.4). v0.19.0 had the
two conventions swapped. 908 tests green.
2026-07-07 08:55:16 +10:00
dtzp555-max f6c95a0105 feat(isn): VLAN-4 routed sub-interface + addr on spine ISN ospfIf; ISN-CSW LLDP neighbor (v0.19.0)
Real ACI multi-site spines carry the OSPF address toward the ISN on a
routed sub-interface, not the bare port: ospfIf id/dn are now
eth1/{49+si}.4 with addr=172.16.{site}.{si+1}/24 (same /24 as the
existing .254 peer); ospfAdjEp nests under the sub-if. Each spine ISN
uplink physical port also gets an ISN-CSW lldpAdjEp/cdpAdjEp
(sysName ISN-CSW{site}, portIdV Ethernet1/{si+1}, model N9K-C9364C,
locally-administered 02:1B:0D MAC hashed from site.id). Single-site
fabrics build none of this. Closes the autoACI Fabric (physical) gap
where spine uplink rows showed blank Local IP / Neighbor Port.
908 tests green.
2026-07-07 08:17:23 +10:00
dtzp555-maxandClaude Fable 5 6ecd89d550 feat: materialize L3Out eBGP operational sessions for runtime-pushed tenants (v0.18.0)
A tenant L3Out POSTed live via /api/mo (cisco.aci / aci-py) only landed the
bgpPeerP config MO; the operational bgpPeer/bgpPeerEntry/bgpPeerAfEntry that
build/l3out.py emits for topology-defined L3Outs never existed for it, so
per-tenant L3Out BGP session views stayed empty. writes.py now reacts to
runtime bgpPeerP writes: dom-{tenant}:{vrf} (VRF from l3extRsEctx, ASN from
the nested bgpAsP, node from the path DN), operSt=established. Skips when no
VRF is bound or on status=deleted. 3 new regression tests; suite: 903 passed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 21:44:00 +10:00
taodengandClaude Fable 5 1d29a2f289 feat: aci-sim query helper + onboarding fixes (v0.17.0)
Clean-room README test found a newcomer hits failures in the first 5 min —
all doc/script, not sim bugs. Fixes:
- scripts/run.sh: use ./.venv/bin/python (was bare `python` → ModuleNotFoundError:
  uvicorn unless the venv was activated; now matches sandbox-up.sh).
- NEW `aci-sim query CLASS [--dn/--host/--json]`: stdlib aaaLogin→cookie→query
  helper so newcomers don't hand-roll auth cookies (the old README smoke test
  `curl class/fabricNode.json` had no auth → returned an APIC error envelope).
- README: 60-second quickstart + embedded topology screenshot (docs/images/
  topology.png), port-mode leads with `aci-sim run`, smoke test uses `aci-sim
  query`, install notes git+https is package-only.

900 tests pass (+12 for aci-sim query).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 22:51:55 +10:00
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