Skip to content

Pair-scoped vPC interface identity: support same vPC id on two pairs - #411

Merged
allenrobel merged 12 commits into
developfrom
nd_interface_vpc_multi_pair_identity
Aug 28, 2026
Merged

Pair-scoped vPC interface identity: support same vPC id on two pairs#411
allenrobel merged 12 commits into
developfrom
nd_interface_vpc_multi_pair_identity

Conversation

@allenrobel

@allenrobel allenrobel commented Jul 21, 2026

Copy link
Copy Markdown
Collaborator

Merge Ordering — #422 merged 2026-08-28; rebased and lab-verified

#422 merged into develop on 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 two
branches 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:

After #422 merges: rebase this branch and re-run the vPC replaced/overridden integration scenarios (including
the new multi_pair.yaml) against the live lab before merging, to confirm idempotency holds with the reverse pass
active on the rebased code.

Related Issue(s)

Fixes #356

Added NaC-CapSet1 since Issue #356 also contains this label.

Proposed Changes

  • Restore the composite (switch_ip, interface_name) identifier on both vPC interface models
    (accessVpcHost, trunkVpcHost), matching every other interface module. Two vPC pairs in one fabric
    can now legally own the same vPC id (e.g. vpc100 on two pairs) in a single task config — previously
    this collided at parse time ("Item with identifier vpc100 already exists").
  • Move the vpc-interface-dual-peer-duplicate workaround from the model identity into
    VpcInterfaceBaseOrchestrator.query_all: dedup is now keyed on interfaceName + the unordered
    {switchId, peerSwitchId} pair set, so one pair's two peer echoes collapse while same-name interfaces
    on different pairs stay distinct (fixes the state: overridden silent-collapse gap).
  • Add a preflight() guard that fails fast when one config lists the same interface_name under both
    peers 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.
  • Document the pair-scoped identity and the same-pair-duplicate rejection in both modules' notes:.
  • Integration: new multi_pair.yaml scenario in both vPC targets (same name + id created on both lab
    pairs in one task, idempotency, fail-fast guard, overridden reconciliation, cleanup), plus a
    backward-compatible nd_test_manage_vpc_pairs gate (default true) so test runs can treat hand-built
    lab vPC pairs as substrate instead of creating/unpairing them. The multi-pair scenario deliberately
    runs after deleted.yaml: its state: overridden step is fabric-wide, so running it earlier would
    delete the vpc201/vpc101 artifacts the deleted-state tests assert against.

Design/spec: docs/superpowers/specs/2026-07-20-vpc-multi-pair-identity-design.md. Lab premise directly
confirmed on ND 4.2.1: the vpcId resource pool is devicePair-scoped, and creating the same-name/same-id
vPC interface intent on two pairs succeeds (findings recorded in the team bug-tracker vault note
vpc-interface-dual-peer-duplicate).

Test Notes

  • 2026-08-28 (post-Detect field removals in replaced/overridden states (one-way subset diff) #422 rebase): rebased onto 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): the setupmergedreplacedoverriddendeleted scenarios of both nd_interface_vpc_trunk_host and nd_interface_vpc_access are green (each ok=30 changed=8 failed=0), and every REPLACED IDEMPOTENT / OVERRIDDEN IDEMPOTENT re-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 (peerSwitchId exclusion) 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.yaml re-run green on both targets (each ok=20 changed=4 failed=0): same-name/same-id create on both pairs, idempotent re-apply, the same-pair fail-fast guard, cross-pair overridden reconciliation 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.
  • Unit: full suite green via ndpytest tests/unit/ (3184 passed), including new multi-pair dedup,
    missing-peerSwitchId fallback, and preflight-guard tests (TDD; fixtures modeled on captured live wire
    data from both lab pairs).
  • Sanity: ndtest — all checks relevant to the changed files pass; 2 pre-existing failures on develop
    (action-plugin-docs, pylint on manage_fabric_base.py) are in files untouched by this branch.
  • Live integration (ND 4.2.1, SITE1, two vPC pairs — S1_LE1/S1_LE2 domain 1, S1_LE3/S1_LE4 domain 2, both
    physical peer link): ansible-test network-integration for nd_interface_vpc_trunk_host and
    nd_interface_vpc_access, run with nd_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 check
    confirmed no leftover vpcId pool entries and both lab pairs intact.
  • validate-modules pass for both modules.

Cisco Nexus Dashboard Version

4.2.1

Related ND API Resource Category

  • analyze
  • infra
  • manage
  • onemanage
  • other

Checklist

  • Latest commit is rebased from develop with merge conflicts resolved
  • New or updates to documentation has been made accordingly
  • Assigned the proper reviewers

🤖 Generated with Claude Code

https://claude.ai/code/session_01BHGFDqqU37PRqNHhKw1FCQ

Copilot AI review requested due to automatic review settings July 21, 2026 01:36
@allenrobel allenrobel self-assigned this Jul 21, 2026
@allenrobel allenrobel added 2.0.0 Release 2.0.0 ready for review Submitter is requesting a PR review nac-0.0.1 NaC ND release 0.0.1 labels Jul 21, 2026

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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) for accessVpcHost and trunkVpcHost models, aligning with other interface modules.
  • Rework VpcInterfaceBaseOrchestrator.query_all() dedup to key on (interfaceName, {switchId, peerSwitchId}) and add a preflight() 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.

Comment thread plugins/modules/nd_interface_vpc_trunk_host.py Outdated
Comment thread plugins/modules/nd_interface_vpc_access.py Outdated
@allenrobel allenrobel removed the ready for review Submitter is requesting a PR review label Jul 21, 2026
@allenrobel
allenrobel requested a review from Copilot July 21, 2026 01:53
@allenrobel allenrobel added the ready for review Submitter is requesting a PR review label Jul 21, 2026

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 19 out of 19 changed files in this pull request and generated no new comments.

Comment thread docs/superpowers/plans/2026-07-20-vpc-multi-pair-identity.md Outdated
Comment thread docs/superpowers/specs/2026-07-20-vpc-multi-pair-identity-design.md Outdated
@allenrobel
allenrobel force-pushed the nd_interface_vpc_multi_pair_identity branch from 3ddba46 to f2c18b0 Compare July 30, 2026 01:19
@allenrobel
allenrobel force-pushed the nd_interface_vpc_multi_pair_identity branch from f2c18b0 to 71a37e3 Compare August 10, 2026 20:12
@allenrobel
allenrobel force-pushed the nd_interface_vpc_multi_pair_identity branch 2 times, most recently from 5725fc6 to 465a681 Compare August 13, 2026 19:25
Comment thread plugins/module_utils/orchestrators/vpc_interface_base.py Outdated
Comment thread plugins/module_utils/orchestrators/vpc_interface_base.py Outdated
@allenrobel
allenrobel force-pushed the nd_interface_vpc_multi_pair_identity branch from 465a681 to 722d352 Compare August 22, 2026 23:01
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
allenrobel requested a review from mikewiebe August 24, 2026 19:23

@mikewiebe mikewiebe left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved.

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
allenrobel force-pushed the nd_interface_vpc_multi_pair_identity branch 2 times, most recently from 5226a9b to 6346290 Compare August 25, 2026 03:12
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
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
allenrobel force-pushed the nd_interface_vpc_multi_pair_identity branch from 6346290 to e645f1b Compare August 27, 2026 20:38
allenrobel and others added 12 commits August 28, 2026 06:59
… docstring (#356)

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
allenrobel force-pushed the nd_interface_vpc_multi_pair_identity branch from e645f1b to 6a5a1dc Compare August 28, 2026 17:00
@allenrobel
allenrobel merged commit 5927ac3 into develop Aug 28, 2026
59 checks passed
@allenrobel
allenrobel deleted the nd_interface_vpc_multi_pair_identity branch August 28, 2026 17:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

2.0.0 Release 2.0.0 nac-0.0.1 NaC ND release 0.0.1 ready for review Submitter is requesting a PR review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

vPC name-only identifier breaks when two pairs reuse the same vPC id in one fabric

5 participants