mirror of
https://github.com/dtzp555-max/aci-sim.git
synced 2026-07-22 21:45:10 +00:00
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
71 lines
2.1 KiB
TOML
71 lines
2.1 KiB
TOML
[project]
|
|
name = "aci-sim"
|
|
version = "0.25.0"
|
|
description = "Faithful REST simulator of a 2-site Cisco ACI fabric (per-site APIC + ND/NDO)"
|
|
readme = "README.md"
|
|
license = "PolyForm-Noncommercial-1.0.0"
|
|
authors = [
|
|
{ name = "aci-sim contributors" },
|
|
]
|
|
requires-python = ">=3.11"
|
|
classifiers = [
|
|
# No license classifier: the SPDX `license` field above supersedes
|
|
# license classifiers (PEP 639)
|
|
# and modern setuptools rejects having both.
|
|
"Development Status :: 4 - Beta",
|
|
"Intended Audience :: Developers",
|
|
"Intended Audience :: System Administrators",
|
|
"Operating System :: OS Independent",
|
|
"Programming Language :: Python :: 3",
|
|
"Programming Language :: Python :: 3.11",
|
|
"Programming Language :: Python :: 3.12",
|
|
"Programming Language :: Python :: 3.13",
|
|
"Programming Language :: Python :: 3.14",
|
|
"Topic :: System :: Networking",
|
|
"Topic :: Software Development :: Testing",
|
|
"Typing :: Typed",
|
|
]
|
|
# Runtime dependencies are declared statically here (NOT `dynamic`) so that a
|
|
# plain `pip install aci-sim` / `pip install .` pulls them in. requirements.txt
|
|
# is kept as a dev convenience (it also lists the test-only extras below).
|
|
dependencies = [
|
|
"fastapi>=0.110",
|
|
"uvicorn[standard]>=0.29",
|
|
"pydantic>=2.6",
|
|
"pyyaml>=6.0",
|
|
]
|
|
|
|
[project.optional-dependencies]
|
|
# Test/dev-only — not needed to run the simulator. `pip install -e '.[dev]'`.
|
|
dev = [
|
|
"httpx>=0.27",
|
|
"pytest>=8.0",
|
|
"pytest-asyncio>=0.23",
|
|
]
|
|
|
|
[project.urls]
|
|
Homepage = "https://github.com/dtzp555-max/aci-sim"
|
|
Repository = "https://github.com/dtzp555-max/aci-sim"
|
|
Changelog = "https://github.com/dtzp555-max/aci-sim/blob/main/CHANGELOG.md"
|
|
|
|
[project.scripts]
|
|
aci-sim = "aci_sim.cli:main"
|
|
|
|
[tool.setuptools.packages.find]
|
|
# Auto-discover ALL subpackages (aci_sim.topology/build/rest_aci/ndo/
|
|
# mit/runtime/query/control/...) — a bare `packages = ["aci_sim"]` would
|
|
# ship only the top-level module and omit every subpackage.
|
|
include = ["aci_sim*"]
|
|
|
|
[tool.pytest.ini_options]
|
|
asyncio_mode = "auto"
|
|
testpaths = ["tests"]
|
|
|
|
[tool.ruff]
|
|
line-length = 100
|
|
target-version = "py311"
|
|
|
|
[tool.ruff.lint]
|
|
select = ["E", "F", "I", "UP", "B"]
|
|
ignore = ["E501"]
|