Pair-scoped vPC interface identity: support same vPC id on two pairs - #411
Merged
Conversation
allenrobel
requested review from
akinross,
anvitha-jain,
gmicol,
lhercot,
mikewiebe,
mtarking,
sajagana,
samiib and
shrsr
as code owners
July 21, 2026 01:36
There was a problem hiding this comment.
Pull request overview
Updates vPC interface handling to support the NX-OS/ND reality that the same vPC ID (e.g. vpc100) can exist on multiple vPC pairs within the same fabric, by restoring composite identity at the model layer and moving “dual-peer echo” handling into orchestrator-side dedup and validation.
Changes:
- Restore composite identifier (
switch_ip,interface_name) foraccessVpcHostandtrunkVpcHostmodels, aligning with other interface modules. - Rework
VpcInterfaceBaseOrchestrator.query_all()dedup to key on(interfaceName, {switchId, peerSwitchId})and add apreflight()guard for same-pair duplicates. - Add/extend unit + integration coverage, plus design/spec documentation for issue #356.
Reviewed changes
Copilot reviewed 19 out of 19 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
plugins/module_utils/orchestrators/vpc_interface_base.py |
Adds pair-keyed dedup in query_all() and a preflight guard against same-pair duplicate config. |
plugins/module_utils/models/interfaces/vpc_access_interface.py |
Switches vPC access interface model identity back to composite (switch_ip, interface_name). |
plugins/module_utils/models/interfaces/vpc_trunk_host_interface.py |
Switches vPC trunk-host interface model identity back to composite (switch_ip, interface_name). |
plugins/modules/nd_interface_vpc_access.py |
Documents the pair-scoped identity expectations and same-pair duplicate rejection behavior. |
plugins/modules/nd_interface_vpc_trunk_host.py |
Documents the pair-scoped identity expectations and same-pair duplicate rejection behavior. |
tests/unit/module_utils/orchestrators/test_vpc_interface_base.py |
Adds unit tests covering multi-pair dedup, missing peerSwitchId fallback, and preflight guard behavior. |
tests/unit/module_utils/models/test_vpc_access_interface.py |
Updates identifier behavior test for composite identity. |
tests/unit/module_utils/models/test_vpc_trunk_host_interface.py |
Updates identifier behavior test for composite identity. |
tests/unit/module_utils/fixtures/fixture_data/test_vpc_interface_base.json |
Updates fixtures with peerSwitchId and adds new multi-pair + preflight fixtures. |
tests/integration/targets/nd_interface_vpc_access/vars/main.yaml |
Adds second-pair variables and multi-pair scenario inputs; introduces nd_test_manage_vpc_pairs gate. |
tests/integration/targets/nd_interface_vpc_access/tasks/setup.yaml |
Gates vPC pair creation/teardown steps behind test_manage_vpc_pairs. |
tests/integration/targets/nd_interface_vpc_access/tasks/main.yaml |
Wires in the new multi-pair integration scenario. |
tests/integration/targets/nd_interface_vpc_access/tasks/multi_pair.yaml |
New integration scenario validating same-name-on-two-pairs behavior + guard + overridden reconciliation. |
tests/integration/targets/nd_interface_vpc_trunk_host/vars/main.yaml |
Adds second-pair variables and multi-pair scenario inputs; introduces nd_test_manage_vpc_pairs gate. |
tests/integration/targets/nd_interface_vpc_trunk_host/tasks/setup.yaml |
Gates vPC pair creation/teardown steps behind test_manage_vpc_pairs. |
tests/integration/targets/nd_interface_vpc_trunk_host/tasks/main.yaml |
Wires in the new multi-pair integration scenario. |
tests/integration/targets/nd_interface_vpc_trunk_host/tasks/multi_pair.yaml |
New integration scenario validating same-name-on-two-pairs behavior + guard + overridden reconciliation. |
docs/superpowers/specs/2026-07-20-vpc-multi-pair-identity-design.md |
Design/spec writeup for the chosen approach and lab findings. |
docs/superpowers/plans/2026-07-20-vpc-multi-pair-identity.md |
Detailed implementation plan and verification checklist for the change set. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
allenrobel
force-pushed
the
nd_interface_vpc_multi_pair_identity
branch
2 times, most recently
from
July 27, 2026 23:18
81d54a4 to
3ddba46
Compare
mikewiebe
reviewed
Jul 30, 2026
mikewiebe
reviewed
Jul 30, 2026
allenrobel
force-pushed
the
nd_interface_vpc_multi_pair_identity
branch
from
July 30, 2026 01:19
3ddba46 to
f2c18b0
Compare
allenrobel
force-pushed
the
nd_interface_vpc_multi_pair_identity
branch
from
August 10, 2026 20:12
f2c18b0 to
71a37e3
Compare
allenrobel
force-pushed
the
nd_interface_vpc_multi_pair_identity
branch
2 times, most recently
from
August 13, 2026 19:25
5725fc6 to
465a681
Compare
mikewiebe
reviewed
Aug 21, 2026
mikewiebe
reviewed
Aug 21, 2026
allenrobel
force-pushed
the
nd_interface_vpc_multi_pair_identity
branch
from
August 22, 2026 23:01
465a681 to
722d352
Compare
allenrobel
added a commit
that referenced
this pull request
Aug 24, 2026
…C overridden dedup (#356) Two correctness fixes in VpcInterfaceBaseOrchestrator.query_all's per-peer dedup, both from the PR #411 review (mikewiebe, 2026-08-21): 1. Missing peerSwitchId no longer falls back to a per-switch singleton key. The OpenAPI schema does not mark peerSwitchId required, and with a singleton fallback one missing echo is enough for the two peer copies to key on {A} vs {A, B}, both survive dedup, and state: overridden deletes the pair-wide interface through the "unconfigured" peer. _pair_key now resolves the peer from the authoritative vpcPair endpoint (_resolve_peer_switch_id, cached per switch) and query_all fails closed if the pair cannot be established. 2. The configured-peer preference map is keyed on the canonical (lowercase) interface name, matching the models' normalize_interface_name and ND's echo, so a mixed-case config (VPC501) still matches its echo. Previously the raw key never matched, the dedup fell back to the lower-serial peer, and overridden planned a spurious CREATE + DELETE (check mode included). Tests: base orchestrator (00970 mixed case; 01010/01020/01030 one/both echoes missing peerSwitchId with the request sequence pinned; 01040 fail-closed on vpcPair 404) plus NDStateMachine-level access and trunk-host tests (00440/00445 mixed case idempotent in normal and check mode; 00450/00455 one/both peers missing, no delete queued). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F7fNh2q6RyHbb37T67hkN6
allenrobel
added a commit
that referenced
this pull request
Aug 24, 2026
…C overridden dedup (#356) Two correctness fixes in VpcInterfaceBaseOrchestrator.query_all's per-peer dedup, both from the PR #411 review (mikewiebe, 2026-08-21): 1. Missing peerSwitchId no longer falls back to a per-switch singleton key. The OpenAPI schema does not mark peerSwitchId required, and with a singleton fallback one missing echo is enough for the two peer copies to key on {A} vs {A, B}, both survive dedup, and state: overridden deletes the pair-wide interface through the "unconfigured" peer. _pair_key now resolves the peer from the authoritative vpcPair endpoint (_resolve_peer_switch_id, cached per switch) and query_all fails closed if the pair cannot be established. 2. The configured-peer preference map is keyed on the canonical (lowercase) interface name, matching the models' normalize_interface_name and ND's echo, so a mixed-case config (VPC501) still matches its echo. Previously the raw key never matched, the dedup fell back to the lower-serial peer, and overridden planned a spurious CREATE + DELETE (check mode included). Tests: base orchestrator (00970 mixed case; 01010/01020/01030 one/both echoes missing peerSwitchId with the request sequence pinned; 01040 fail-closed on vpcPair 404) plus NDStateMachine-level access and trunk-host tests (00440/00445 mixed case idempotent in normal and check mode; 00450/00455 one/both peers missing, no delete queued). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F7fNh2q6RyHbb37T67hkN6
allenrobel
force-pushed
the
nd_interface_vpc_multi_pair_identity
branch
2 times, most recently
from
August 25, 2026 03:12
5226a9b to
6346290
Compare
allenrobel
added a commit
that referenced
this pull request
Aug 25, 2026
…C overridden dedup (#356) Two correctness fixes in VpcInterfaceBaseOrchestrator.query_all's per-peer dedup, both from the PR #411 review (mikewiebe, 2026-08-21): 1. Missing peerSwitchId no longer falls back to a per-switch singleton key. The OpenAPI schema does not mark peerSwitchId required, and with a singleton fallback one missing echo is enough for the two peer copies to key on {A} vs {A, B}, both survive dedup, and state: overridden deletes the pair-wide interface through the "unconfigured" peer. _pair_key now resolves the peer from the authoritative vpcPair endpoint (_resolve_peer_switch_id, cached per switch) and query_all fails closed if the pair cannot be established. 2. The configured-peer preference map is keyed on the canonical (lowercase) interface name, matching the models' normalize_interface_name and ND's echo, so a mixed-case config (VPC501) still matches its echo. Previously the raw key never matched, the dedup fell back to the lower-serial peer, and overridden planned a spurious CREATE + DELETE (check mode included). Tests: base orchestrator (00970 mixed case; 01010/01020/01030 one/both echoes missing peerSwitchId with the request sequence pinned; 01040 fail-closed on vpcPair 404) plus NDStateMachine-level access and trunk-host tests (00440/00445 mixed case idempotent in normal and check mode; 00450/00455 one/both peers missing, no delete queued). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F7fNh2q6RyHbb37T67hkN6
akinross
approved these changes
Aug 26, 2026
allenrobel
added a commit
that referenced
this pull request
Aug 27, 2026
…C overridden dedup (#356) Two correctness fixes in VpcInterfaceBaseOrchestrator.query_all's per-peer dedup, both from the PR #411 review (mikewiebe, 2026-08-21): 1. Missing peerSwitchId no longer falls back to a per-switch singleton key. The OpenAPI schema does not mark peerSwitchId required, and with a singleton fallback one missing echo is enough for the two peer copies to key on {A} vs {A, B}, both survive dedup, and state: overridden deletes the pair-wide interface through the "unconfigured" peer. _pair_key now resolves the peer from the authoritative vpcPair endpoint (_resolve_peer_switch_id, cached per switch) and query_all fails closed if the pair cannot be established. 2. The configured-peer preference map is keyed on the canonical (lowercase) interface name, matching the models' normalize_interface_name and ND's echo, so a mixed-case config (VPC501) still matches its echo. Previously the raw key never matched, the dedup fell back to the lower-serial peer, and overridden planned a spurious CREATE + DELETE (check mode included). Tests: base orchestrator (00970 mixed case; 01010/01020/01030 one/both echoes missing peerSwitchId with the request sequence pinned; 01040 fail-closed on vpcPair 404) plus NDStateMachine-level access and trunk-host tests (00440/00445 mixed case idempotent in normal and check mode; 00450/00455 one/both peers missing, no delete queued). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F7fNh2q6RyHbb37T67hkN6
allenrobel
force-pushed
the
nd_interface_vpc_multi_pair_identity
branch
from
August 27, 2026 20:38
6346290 to
e645f1b
Compare
…st model (#356) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BHGFDqqU37PRqNHhKw1FCQ
…t model (#356) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BHGFDqqU37PRqNHhKw1FCQ
) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BHGFDqqU37PRqNHhKw1FCQ
…#356) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BHGFDqqU37PRqNHhKw1FCQ
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BHGFDqqU37PRqNHhKw1FCQ
…st (#356) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BHGFDqqU37PRqNHhKw1FCQ
…tifacts (#356) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BHGFDqqU37PRqNHhKw1FCQ
…rs (#356) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BHGFDqqU37PRqNHhKw1FCQ
… docstring (#356) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BHGFDqqU37PRqNHhKw1FCQ
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BHGFDqqU37PRqNHhKw1FCQ
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BHGFDqqU37PRqNHhKw1FCQ
…C overridden dedup (#356) Two correctness fixes in VpcInterfaceBaseOrchestrator.query_all's per-peer dedup, both from the PR #411 review (mikewiebe, 2026-08-21): 1. Missing peerSwitchId no longer falls back to a per-switch singleton key. The OpenAPI schema does not mark peerSwitchId required, and with a singleton fallback one missing echo is enough for the two peer copies to key on {A} vs {A, B}, both survive dedup, and state: overridden deletes the pair-wide interface through the "unconfigured" peer. _pair_key now resolves the peer from the authoritative vpcPair endpoint (_resolve_peer_switch_id, cached per switch) and query_all fails closed if the pair cannot be established. 2. The configured-peer preference map is keyed on the canonical (lowercase) interface name, matching the models' normalize_interface_name and ND's echo, so a mixed-case config (VPC501) still matches its echo. Previously the raw key never matched, the dedup fell back to the lower-serial peer, and overridden planned a spurious CREATE + DELETE (check mode included). Tests: base orchestrator (00970 mixed case; 01010/01020/01030 one/both echoes missing peerSwitchId with the request sequence pinned; 01040 fail-closed on vpcPair 404) plus NDStateMachine-level access and trunk-host tests (00440/00445 mixed case idempotent in normal and check mode; 00450/00455 one/both peers missing, no delete queued). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F7fNh2q6RyHbb37T67hkN6
allenrobel
force-pushed
the
nd_interface_vpc_multi_pair_identity
branch
from
August 28, 2026 17:00
e645f1b to
6a5a1dc
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Merge Ordering — #422 merged 2026-08-28; rebased and lab-verified
#422 merged into
developon 2026-08-28 and this branch is rebased on it (clean replay, no conflicts). The replaced/overridden re-run it called for is done — see the 2026-08-28 entry under Test Notes. Original reasoning kept for the record:Prefer merging #422 (replaced/overridden field-removal detection) before this PR.Not a textual concern — the twobranches touch different regions of the vPC model files (identifier block here vs. policy-model defaults tables there)
and auto-merge cleanly. The coupling is semantic:
orchestrator-injected
peerSwitchIdinside the policy block, which the proposed config can never express, so thereverse pass reports a removal on every run (permanent
changed=true+ re-PUT). The fix lands in Detect field removals in replaced/overridden states (one-way subset diff) #422 and will touchthe same vPC model files this PR modifies.
query_alldedup — exactly the machinery that produces thebefore[]set Detect field removals in replaced/overridden states (one-way subset diff) #422'sreverse pass classifies against.
After #422 merges: rebase this branch and re-run the vPC
replaced/overriddenintegration scenarios (includingthe new
multi_pair.yaml) against the live lab before merging, to confirm idempotency holds with the reverse passactive on the rebased code.
Related Issue(s)
Fixes #356
Added NaC-CapSet1 since Issue #356 also contains this label.
Proposed Changes
switch_ip,interface_name) identifier on both vPC interface models(
accessVpcHost,trunkVpcHost), matching every other interface module. Two vPC pairs in one fabriccan now legally own the same vPC id (e.g.
vpc100on two pairs) in a single taskconfig— previouslythis collided at parse time ("Item with identifier
vpc100already exists").vpc-interface-dual-peer-duplicateworkaround from the model identity intoVpcInterfaceBaseOrchestrator.query_all: dedup is now keyed oninterfaceName+ the unordered{switchId, peerSwitchId}pair set, so one pair's two peer echoes collapse while same-name interfaceson different pairs stay distinct (fixes the
state: overriddensilent-collapse gap).preflight()guard that fails fast when one config lists the sameinterface_nameunder bothpeers of the same vPC pair (two items targeting one ND resource). Pair resolution only runs for
duplicated names, so runs with unique names issue zero extra requests.
notes:.multi_pair.yamlscenario in both vPC targets (same name + id created on both labpairs in one task, idempotency, fail-fast guard,
overriddenreconciliation, cleanup), plus abackward-compatible
nd_test_manage_vpc_pairsgate (defaulttrue) so test runs can treat hand-builtlab vPC pairs as substrate instead of creating/unpairing them. The multi-pair scenario deliberately
runs after
deleted.yaml: itsstate: overriddenstep is fabric-wide, so running it earlier woulddelete the
vpc201/vpc101artifacts the deleted-state tests assert against.Design/spec:
docs/superpowers/specs/2026-07-20-vpc-multi-pair-identity-design.md. Lab premise directlyconfirmed on ND 4.2.1: the
vpcIdresource pool isdevicePair-scoped, and creating the same-name/same-idvPC interface intent on two pairs succeeds (findings recorded in the team bug-tracker vault note
vpc-interface-dual-peer-duplicate).Test Notes
develop@bb985d74; full unit suite 4146 passed; CI green. Live integration against the rebuilt ND 4.2.1.10 lab (SITE1, pair S1_LE1/S1_LE2,nd_test_manage_vpc_pairs=false): thesetup→merged→replaced→overridden→deletedscenarios of bothnd_interface_vpc_trunk_hostandnd_interface_vpc_accessare green (eachok=30 changed=8 failed=0), and everyREPLACED IDEMPOTENT/OVERRIDDEN IDEMPOTENTre-apply reports no change — confirming replaced/overridden idempotency holds with Detect field removals in replaced/overridden states (one-way subset diff) #422's reverse pass (peerSwitchIdexclusion) active on this branch's pair-scoped identity and dedup. Then, after rebuilding the second lab pair (S1_LE3/S1_LE4, domain 2, physical peer link — the rebuilt lab had left them unpaired),multi_pair.yamlre-run green on both targets (eachok=20 changed=4 failed=0): same-name/same-id create on both pairs, idempotent re-apply, the same-pair fail-fast guard, cross-pairoverriddenreconciliation plus its idempotent re-apply, and cleanup. Post-run raw-API check: no vPC interfaces left on any of the four leafs, both pairs intact.ndpytest tests/unit/(3184 passed), including new multi-pair dedup,missing-
peerSwitchIdfallback, and preflight-guard tests (TDD; fixtures modeled on captured live wiredata from both lab pairs).
ndtest— all checks relevant to the changed files pass; 2 pre-existing failures on develop(
action-plugin-docs,pylintonmanage_fabric_base.py) are in files untouched by this branch.physical peer link):
ansible-test network-integrationfornd_interface_vpc_trunk_hostandnd_interface_vpc_access, run withnd_test_manage_vpc_pairs=false. Both targets green end-to-end(each
ok=49 changed=12 failed=0), including the new multi-pair blocks; post-run raw-API checkconfirmed no leftover
vpcIdpool entries and both lab pairs intact.Cisco Nexus Dashboard Version
4.2.1
Related ND API Resource Category
Checklist
🤖 Generated with Claude Code
https://claude.ai/code/session_01BHGFDqqU37PRqNHhKw1FCQ