mirror of
https://github.com/dtzp555-max/aci-sim.git
synced 2026-07-19 09:46:33 +00:00
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
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
694454d9fc
commit
c2959ab2cb
@@ -6,6 +6,35 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html)
|
||||
(pre-1.0: minor bumps may include breaking changes to the sim's behavior).
|
||||
|
||||
## [0.25.0] - 2026-07-10
|
||||
|
||||
### Changed
|
||||
- **APIC write-face changed-status fidelity (F8a)** — a POST now reports the
|
||||
real per-MO status (`created` / `modified` / *unchanged*) instead of
|
||||
unconditionally stamping `created`. `aci_sim/rest_aci/writes.py` diffs each
|
||||
planned MO's **raw posted attributes** (excluding `dn`/`status`, before the
|
||||
create-time `_CLASS_DEFAULTS` overlay) against the pre-existing stored MO at
|
||||
the write choke point, and `apply()` builds `imdata` from **only the changed
|
||||
MOs** — returning empty `imdata` (+ `totalCount "0"`) when nothing changed.
|
||||
`post_mo` reads `?rsp-subtree` and threads it through (`modified`/absent/`full`
|
||||
→ changed-MO list; `no` → empty). This restores real-APIC idempotency for
|
||||
every raw `aci_rest`-driven write: an unchanged re-push now yields
|
||||
`changed=false` (previously `changed=true` forever), fixing pass2 idempotency
|
||||
across the L3Out topology (`l3extLNodeP`/`l3extLIfP`/`l3extRsNodeL3OutAtt`/
|
||||
`l3extRsPathL3OutAtt`/`bgpPeerP`/`bfdIfP`), static-path, and PBR-redirect
|
||||
blocks. 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 available to reproduce it accurately).
|
||||
- **Idempotent delete-of-missing (F1)** — POSTing `status:"deleted"` to an
|
||||
already-absent DN now reports *unchanged* (empty `imdata`) instead of
|
||||
`deleted`, matching real APIC: `store.upsert` pops a missing DN as a silent
|
||||
no-op, so nothing changed. A delete that actually removes an existing MO still
|
||||
reports `deleted`.
|
||||
+12 tests (re-push idempotency, defaults-overlay guard, partial re-push,
|
||||
parent-unchanged/child-changed, status-pollution re-push, delete-of-missing,
|
||||
`rsp-subtree=no`).
|
||||
|
||||
## [0.24.0] - 2026-07-10
|
||||
|
||||
### Added
|
||||
|
||||
Reference in New Issue
Block a user