Summary
nd_manage_vpc_pair (added in #197) cannot create a vPC pair with a physical peer-link. The intent PUT .../vpcPair succeeds (HTTP 204), but the deploy step — fabric-level POST .../actions/configSave — returns HTTP 500 and the pair never forms:
[<sn>/<host>] - Unexpected error generating vPC configuration. Last Status [Peer Link Interface]
Switch [<host>/<sn>]: Member port Ethernet1/2 is a member of VPC_PAIR, remove Ethernet1/2 from VPC_PAIR first.
A virtual peer-link (use_virtual_peer_link: true) works fine through the same module. The failure is specific to physical peer-links (i.e. configs that set switch_member_interfaces / peer_switch_member_interfaces / switch_po_id under vpc_pair_details).
Root cause
It's the deploy mechanism, not the intent. The config-generation succeeds via the switch-scoped deploy and fails via the fabric-scoped config-save — same intent, same switches, only the endpoint differs (lab-verified via the raw ND API, independent of any module):
| Path |
Endpoint(s) |
Physical peer-link |
| Switch-level |
POST .../switchActions/deploy {"switchIds":[le1,le2]} |
✅ works — switchActions/preview shows the generated CLI; deploy returns "Deployed Successfully"; pair forms (vpcConfigured: true on both, roles elected) |
| Fabric-level |
POST .../actions/configSave then .../actions/deploy |
❌ HTTP 500 "Member port … is a member of VPC_PAIR" |
nd_manage_vpc_pair deploys via the fabric-level actions/configSave + actions/deploy path (plugins/module_utils/manage_vpc_pair/deploy.py → EpFabricConfigSavePost / EpFabricDeployPost). The peer-link member interface is a clean trunkHost when unpaired, so this is ND's config generator self-conflicting on the peer-link member through the fabric path — not a pre-existing claim on the interface.
(There is no switch-level config-save endpoint; switchActions/deploy itself generates+pushes. So the fabric configSave is the only path nd_manage_vpc_pair currently uses, and it's the broken one for physical peer-links.)
Why it wasn't caught
The nd_manage_vpc_pair integration suite (tests/integration/targets/nd_vpc_pair/) only exercises use_virtual_peer_link: true/false and never sets switch_member_interfaces / peer_switch_member_interfaces / switch_po_id — so a physical peer-link with member ports (the failing config) was never run.
Secondary: even when configSave 500s, deploy.py::_is_non_fatal_config_save_error does not catch it, because it matches against error.msg, which is the top-level "Internal Server Error", not the nested "Unexpected error generating vPC configuration …" message. (And even if it matched, the config still wouldn't generate.)
Steps to reproduce
- cisco.nd.nd_manage_vpc_pair:
fabric_name: SITE1
state: merged
config:
- peer1_switch_id: "<le1 mgmt ip or serial>"
peer2_switch_id: "<le2 mgmt ip or serial>"
use_virtual_peer_link: false
vpc_pair_details:
type: default
domain_id: 1
keep_alive_vrf: management
switch_keep_alive_local_ip: "<le1 mgmt ip>"
peer_switch_keep_alive_local_ip: "<le2 mgmt ip>"
switch_po_id: 1
peer_switch_po_id: 1
switch_member_interfaces: ["Ethernet1/2"]
peer_switch_member_interfaces: ["Ethernet1/2"]
Result: task fails at config save with the HTTP 500 above. Switching to use_virtual_peer_link: true (no member interfaces) succeeds.
Expected behavior
Creating a vPC pair with a physical peer-link should generate and deploy the peer-link config, as it does via switchActions/deploy and as the former nd_vpc_pair module did.
Recommended fix
Deploy vPC pairs via the switch-scoped POST .../switchActions/deploy {"switchIds":[le1, le2]} instead of the fabric-level actions/configSave + actions/deploy. This is exactly what the (now-closed) nd_vpc_pair implementation did — see PR #278 / the nd_vpc_pair branch (plugins/module_utils/orchestrators/vpc_pair.py, which queues both peer serials and flushes via EpManageFabricSwitchActionsDeployPost). It fixes physical peer-link and continues to work for virtual.
Environment
Impact on dependent work
Blocks physical-peer-link vPC pair creation. The nd_interface_vpc_access / nd_interface_vpc_trunk_host modules (PRs #279/#280, on base #334) work around it by using a virtual peer-link substrate in their integration tests, with a TODO to revert to physical once this is fixed.
Summary
nd_manage_vpc_pair(added in #197) cannot create a vPC pair with a physical peer-link. The intentPUT .../vpcPairsucceeds (HTTP 204), but the deploy step — fabric-levelPOST .../actions/configSave— returns HTTP 500 and the pair never forms:A virtual peer-link (
use_virtual_peer_link: true) works fine through the same module. The failure is specific to physical peer-links (i.e. configs that setswitch_member_interfaces/peer_switch_member_interfaces/switch_po_idundervpc_pair_details).Root cause
It's the deploy mechanism, not the intent. The config-generation succeeds via the switch-scoped deploy and fails via the fabric-scoped config-save — same intent, same switches, only the endpoint differs (lab-verified via the raw ND API, independent of any module):
POST .../switchActions/deploy {"switchIds":[le1,le2]}switchActions/previewshows the generated CLI;deployreturns "Deployed Successfully"; pair forms (vpcConfigured: trueon both, roles elected)POST .../actions/configSavethen.../actions/deploynd_manage_vpc_pairdeploys via the fabric-levelactions/configSave+actions/deploypath (plugins/module_utils/manage_vpc_pair/deploy.py→EpFabricConfigSavePost/EpFabricDeployPost). The peer-link member interface is a cleantrunkHostwhen unpaired, so this is ND's config generator self-conflicting on the peer-link member through the fabric path — not a pre-existing claim on the interface.(There is no switch-level config-save endpoint;
switchActions/deployitself generates+pushes. So the fabricconfigSaveis the only pathnd_manage_vpc_paircurrently uses, and it's the broken one for physical peer-links.)Why it wasn't caught
The
nd_manage_vpc_pairintegration suite (tests/integration/targets/nd_vpc_pair/) only exercisesuse_virtual_peer_link: true/falseand never setsswitch_member_interfaces/peer_switch_member_interfaces/switch_po_id— so a physical peer-link with member ports (the failing config) was never run.Secondary: even when
configSave500s,deploy.py::_is_non_fatal_config_save_errordoes not catch it, because it matches againsterror.msg, which is the top-level"Internal Server Error", not the nested"Unexpected error generating vPC configuration …"message. (And even if it matched, the config still wouldn't generate.)Steps to reproduce
Result: task fails at config save with the HTTP 500 above. Switching to
use_virtual_peer_link: true(no member interfaces) succeeds.Expected behavior
Creating a vPC pair with a physical peer-link should generate and deploy the peer-link config, as it does via
switchActions/deployand as the formernd_vpc_pairmodule did.Recommended fix
Deploy vPC pairs via the switch-scoped
POST .../switchActions/deploy {"switchIds":[le1, le2]}instead of the fabric-levelactions/configSave+actions/deploy. This is exactly what the (now-closed)nd_vpc_pairimplementation did — see PR #278 / thend_vpc_pairbranch (plugins/module_utils/orchestrators/vpc_pair.py, which queues both peer serials and flushes viaEpManageFabricSwitchActionsDeployPost). It fixes physical peer-link and continues to work for virtual.Environment
nd_manage_vpc_pair(VPC Pair 4.x Implementation #197)Impact on dependent work
Blocks physical-peer-link vPC pair creation. The
nd_interface_vpc_access/nd_interface_vpc_trunk_hostmodules (PRs #279/#280, on base #334) work around it by using a virtual peer-link substrate in their integration tests, with a TODO to revert to physical once this is fixed.