Enhancing Fabric Type Support for Switches Module - #405
Conversation
|
@AKDRG — a heads-up and a question, not a request to change anything here. This PR is doing the right thing; the collision is on my side. Situation. In #404 I added a Proposal. Promote This shouldn't cost you anything. Done as a move + re-export — leaving The question I actually want your read on. Your enums look deliberately endpoint-scoped — One detail either way: Happy to do the move in #404 if you're good with it, or leave it to you if you'd rather own that file. |
get_platform_type() resolves via PlatformTypeEnum(raw) inside a try/except ValueError, falling through to None for any value the enum does not know. With SONIC absent, a SONiC switch silently reported "no platform type" rather than its actual platform -- the exact failure the enum exists to prevent. Found while checking PlatformTypeEnum against develop's pre-existing PlatformType (plugins/module_utils/models/manage_switches/enums.py), which has carried SONIC all along. Whether the two enums converge is an open question being discussed with @AKDRG on #405; this gap is a defect either way, so fix it now rather than leave it pending that outcome. If PlatformTypeEnum is later dropped in favor of a promoted PlatformType, this member goes with it. Test: extends 00230 with a fourth switch reporting platformType "sonic", asserting it resolves to PlatformTypeEnum.SONIC. Verified to fail against the unfixed enum (AttributeError: type object 'PlatformTypeEnum' has no attribute 'SONIC'). module_utils suite green: 3036 passed. black/isort/pylint/mypy clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014aJ3Y2TJEAqJZBeUKdUnGa
get_platform_type() resolves via PlatformTypeEnum(raw) inside a try/except ValueError, falling through to None for any value the enum does not know. With SONIC absent, a SONiC switch silently reported "no platform type" rather than its actual platform -- the exact failure the enum exists to prevent. Found while checking PlatformTypeEnum against develop's pre-existing PlatformType (plugins/module_utils/models/manage_switches/enums.py), which has carried SONIC all along. Whether the two enums converge is an open question being discussed with @AKDRG on #405; this gap is a defect either way, so fix it now rather than leave it pending that outcome. If PlatformTypeEnum is later dropped in favor of a promoted PlatformType, this member goes with it. Test: extends 00230 with a fourth switch reporting platformType "sonic", asserting it resolves to PlatformTypeEnum.SONIC. Verified to fail against the unfixed enum (AttributeError: type object 'PlatformTypeEnum' has no attribute 'SONIC'). module_utils suite green: 3036 passed. black/isort/pylint/mypy clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014aJ3Y2TJEAqJZBeUKdUnGa
allenrobel
left a comment
There was a problem hiding this comment.
Code review
Four findings, each as an inline comment: endpoint-import coupling in shared utils.py, role: None rejected by the new fabric-capability validation, a now-dead all_preserve_config flag, and an undocumented ValueError in normalize_platform_type.
🤖 Generated with Claude Code
get_platform_type() resolves via PlatformTypeEnum(raw) inside a try/except ValueError, falling through to None for any value the enum does not know. With SONIC absent, a SONiC switch silently reported "no platform type" rather than its actual platform -- the exact failure the enum exists to prevent. Found while checking PlatformTypeEnum against develop's pre-existing PlatformType (plugins/module_utils/models/manage_switches/enums.py), which has carried SONIC all along. Whether the two enums converge is an open question being discussed with @AKDRG on #405; this gap is a defect either way, so fix it now rather than leave it pending that outcome. If PlatformTypeEnum is later dropped in favor of a promoted PlatformType, this member goes with it. Test: extends 00230 with a fourth switch reporting platformType "sonic", asserting it resolves to PlatformTypeEnum.SONIC. Verified to fail against the unfixed enum (AttributeError: type object 'PlatformTypeEnum' has no attribute 'SONIC'). module_utils suite green: 3036 passed. black/isort/pylint/mypy clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014aJ3Y2TJEAqJZBeUKdUnGa
allenrobel
left a comment
There was a problem hiding this comment.
LGTM after comments were addressed.
get_platform_type() resolves via PlatformTypeEnum(raw) inside a try/except ValueError, falling through to None for any value the enum does not know. With SONIC absent, a SONiC switch silently reported "no platform type" rather than its actual platform -- the exact failure the enum exists to prevent. Found while checking PlatformTypeEnum against develop's pre-existing PlatformType (plugins/module_utils/models/manage_switches/enums.py), which has carried SONIC all along. Whether the two enums converge is an open question being discussed with @AKDRG on #405; this gap is a defect either way, so fix it now rather than leave it pending that outcome. If PlatformTypeEnum is later dropped in favor of a promoted PlatformType, this member goes with it. Test: extends 00230 with a fourth switch reporting platformType "sonic", asserting it resolves to PlatformTypeEnum.SONIC. Verified to fail against the unfixed enum (AttributeError: type object 'PlatformTypeEnum' has no attribute 'SONIC'). module_utils suite green: 3036 passed. black/isort/pylint/mypy clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014aJ3Y2TJEAqJZBeUKdUnGa
| if self.state not in ("merged", "replaced", "overridden"): | ||
| self.nd.module.fail_json(msg=f"Unsupported state: {self.state}") | ||
|
|
||
| # --- Validate & classify ------------------------------------------------ |
There was a problem hiding this comment.
High: Onboarding-only preserve_config validation rejects existing no-op switches
Issue
Capability validation runs before compute_changes(), so it validates preserve_config for every desired switch, including switches already managed and ultimately classified as idempotent. Because the model injects preserve_config: false, an existing External switch fails unless every reconciliation playbook repeats the historical onboarding choice.
Evidence
-
nd_switch_resources.pylines 3042-3059 runs the new capability validation before diff classification and idempotency. -
config_models.pylines 323-327 defaults omittedpreserve_configto false. -
fabric_switch_capabilities.pylines 243-247 rejects that value unconditionally for External fabrics. -
nd_switch_resources.pylines 2748-2756 also hard-codespreserve_config: falsein gathered inventory. -
An exact-head probe classified an existing External switch as a complete no-op, with every work bucket empty, but the pre-classification capability check still raised:
preserve_config 'false' is not supported for External ... Supported preserve_config values: true. -
For example, an External switch is initially onboarded with
preserve_config: true. On a later idempotency run, the playbook describes the same managed switch but omits this onboarding-only option:- cisco.nd.nd_manage_switches: state: merged config: - ip_address: 192.0.2.20 username: admin password: password
The model inserts
preserve_config: false, and capability validation rejects it beforecompute_changes()can establish that the operation is a no-op:preserve_config 'false' is not supported for External. Supported preserve_config values: true.Had change computation run first, every work bucket would have been empty:
switches_to_add: [] switches_to_update: [] switches_to_remove: []Gathered External configurations have the same problem because the serializer explicitly emits
preserve_config: false.
Existing PR overlap
No matching existing PR comment found. Earlier comments discuss a dead preserve-config wait flag but do not cover pre-diff validation of an onboarding-only setting.
Existing open issue overlap
No matching open issue found. Focused searches for External switch and preserve_config idempotency did not identify an owner.
Impact
Existing External switch playbooks that were idempotent before this PR can now fail before diffing, discovery, or writes. Gathered External configuration is inherently unreplayable because it carries the rejected default.
Suggested fix
Classify the plan before enforcing onboarding-only fields and validate preserve_config only for switches that will actually be discovered, added, or re-added. Preserve input explicitness if omission must differ from false. Add a regression test for an existing External no-op switch with omitted preserve_config.
There was a problem hiding this comment.
Can you clarify why External switches require preserve_config: true? Is this an ND controller requirement or an intended module policy?
preserve_config appears to control initial onboarding behavior. Requiring it to remain true during every subsequent reconciliation means an omitted value—and the module’s own gathered value of false—causes an already-managed no-op switch to fail.
If this is a controller requirement, could we document the rationale and limit its validation to discovery, add, and re-add operations? Otherwise, should omission be accepted for switches that are already managed?
There was a problem hiding this comment.
External Fabric has fixed preserve config "true". Omission has been now handled through the default validator which will fix it to true.
There was a problem hiding this comment.
Following are 3 more issues introduced after the last set of changes
1. High: Omitted platform defaults still break an existing Campus IOS-XE no-op
Issue
@AKDRG while verifying this update, found the following regession/behavior. Are you planning to handle this as part of #488?
Capability validation applies the NX-OS add default before determining whether an existing switch needs onboarding work. An idempotent Campus IOS-XE leaf therefore fails when a normal reconciliation playbook omits the newly introduced platform_type.
This is a regression introduced by PR #405. Before this PR, platform_type was not part of the module's public switch configuration, and current develop treats the same input as an unchanged no-op.
Evidence
-
nd_switch_resources.pylines 3051-3067 validates capabilities beforecompute_changes()can classify the existing switch as idempotent. -
fabric_switch_capabilities.pylines 283-299 changes an omitted platform to NX-OS and validates the role against that assumed platform. -
config_models.pylines 283-294 excludesplatform_typefrom normal-switch diffing, confirming it is not a drift field for an existing no-op. -
Practical example: assume
Campus_AKalready contains this switch:Management IP: 192.0.2.10 Platform: ios-xe Role: leafA pre-PR reconciliation playbook can legitimately omit the newly introduced platform field:
- name: Reconcile the existing Campus IOS-XE leaf cisco.nd.nd_manage_switches: fabric: Campus_AK state: merged config: - seed_ip: 192.0.2.10 username: admin password: "{{ switch_password }}" role: leaf # platform_type omitted
Expected:
changed: false
Actual result under PR Enhancing Fabric Type Support for Switches Module #405:
role 'leaf' is not supported for platform_type 'nx-os' in Campus VXLAN -
The exact-head diff engine correctly classified this input as:
idempotent=1 to_add=0 to_update=0The actual module never reaches that result because capability validation runs first.
-
Current
developat3026bddc147861e07aa89b26be1277f884c6def4treats the same input as a no-op. The regression exists at both the previously reviewed PR head7fbfa7cand current headd0e213c.
Existing PR overlap
This is a follow-up to the existing pre-diff validation thread. Omitted preserve_config derivation was addressed, but this confirms that onboarding-only defaults are still applied to existing idempotent switches.
Existing open issue overlap
Related to #488, which says NX-OS should be the default only for add operations. It does not defer this PR-specific existing-switch regression.
Impact
A valid Campus IOS-XE leaf that previously returned changed: false now fails before discovery, diffing, or writes. Adding platform_type: ios-xe avoids the failure, but requiring newly introduced onboarding metadata in every existing-switch reconciliation is the compatibility regression.
Suggested fix
Classify entries against live inventory before applying onboarding defaults. For an existing switch, resolve an omitted platform from its inventory record; apply the NX-OS default only to actual add or re-add operations. Add a manage_state() regression test proving this Campus IOS-XE input performs no writes.
There was a problem hiding this comment.
2. High: The derived iBGP preserve default lets POAP and swap finish before reboot
Issue
preserve_config no longer has one static default. When omitted, PR #405 derives true for Data Center VXLAN iBGP and false for eBGP and Campus. The iBGP value is applied to every operation, including POAP and swap.
Those operations are consequently placed in the preserve-config wait set, which can accept the first pre-reboot normal response instead of requiring the documented normal -> unreachable -> ok reboot transition.
Evidence
fabric_switch_capabilities.pylines 113-118 sets the omitted iBGP value totrue.fabric_switch_capabilities.pylines 283-300 applies it without considering whether the operation is normal onboarding, POAP, or swap.- The corresponding omitted defaults are
falsefor eBGP and Campus. Changing every fabric default tofalsewould route this omitted-value example through the reboot wait, but it would also change normal iBGP brownfield onboarding and would not protect an explicitpreserve_config: truePOAP/swap input. nd_switch_resources.pylines 1546-1562 places every NX-OS entry withpreserve_config: trueinnxos_preserve.utils.pylines 792-807 explains that POAP devices must be observed through the reboot transition, but lines 863-866 accept a preserve-config device as soon assystemMode == normal.
For example:
- name: Bootstrap an iBGP leaf
cisco.nd.nd_manage_switches:
fabric: FABRIC_IBGP
state: merged
config:
- seed_ip: 192.0.2.20
username: admin
password: "{{ switch_password }}"
role: leaf
poap:
serial_number: FDO12345ABC
hostname: leaf1
# preserve_config omittedPR #405 derives:
preserve_config=True
nxos_reload=[]
nxos_preserve=['FDO12345ABC']
Immediately after import, ND can still return the pre-reboot state:
systemMode=normal
discoveryStatus=ok
The preserve-config branch accepts that first response:
wait_returned=True
calls=['fetch']
Expected behavior is to keep waiting until the reboot transition has been observed:
normal -> unreachable -> ok
Existing PR overlap
Related to the resolved wait-set discussion and the active preserve-config thread. Neither discussion tests the derived iBGP default through POAP or swap readiness.
Existing open issue overlap
Related to #488, which owns fabric-derived preserve defaults. It does not cover operation-specific POAP/swap reboot behavior.
Impact
The module can proceed with credentials, save, deploy, and report success while the switch has not completed—or even begun—its expected reboot. Subsequent finalization may fail or act against a switch that is not ready.
Suggested fix
Make readiness operation-aware instead of changing every fabric default. Normal onboarding can retain its fabric-derived value, while NX-OS POAP and swap imports should always use the reload-observation policy regardless of preserve_config.
If preserve mode is not meaningful for those operations, reject or ignore it explicitly. Add omitted-value and explicit-true regression tests that require an observed unreachable -> ok transition before finalization.
There was a problem hiding this comment.
3. High: No switches can be added to External and Inter-Fabric Connectivity fabrics
Issue
PR #405’s new fabric exposure gate excludes the fabric type shown in ND as External and Inter-Fabric Connectivity (externalConnectivity).
Consequently, cisco.nd.nd_manage_switches rejects every switch addition to this fabric type before discovery or any write. This affects NX-OS, IOS-XE, IOS-XR, and other platforms.
This is a regression: current develop allows the request to proceed to the controller, while PR #405 rejects it locally.
Practical example
- name: Add an NX-OS switch to External and Inter-Fabric Connectivity
cisco.nd.nd_manage_switches:
fabric: EXTERNAL_A
state: merged
config:
- seed_ip: 192.0.2.30
username: admin
password: "{{ switch_password }}"
platform_type: nx-os
role: leafPR #405 fails with the equivalent of:
Fabric type 'externalConnectivity' is not supported.
Changing platform_type does not help because the fabric type—not the switch platform—is rejected.
Impact
No switches can be added to External and Inter-Fabric Connectivity fabrics through cisco.nd.nd_manage_switches. Read-only gathering still works, and legacy cisco.dcnm.dcnm_inventory or direct API workflows are unaffected.
Suggested fix
Add normalized externalConnectivity to the supported-fabric gate and test onboarding with all four supported platform types. This is also explicitly required by open issue #488.
get_platform_type() resolves via PlatformTypeEnum(raw) inside a try/except ValueError, falling through to None for any value the enum does not know. With SONIC absent, a SONiC switch silently reported "no platform type" rather than its actual platform -- the exact failure the enum exists to prevent. Found while checking PlatformTypeEnum against develop's pre-existing PlatformType (plugins/module_utils/models/manage_switches/enums.py), which has carried SONIC all along. Whether the two enums converge is an open question being discussed with @AKDRG on #405; this gap is a defect either way, so fix it now rather than leave it pending that outcome. If PlatformTypeEnum is later dropped in favor of a promoted PlatformType, this member goes with it. Test: extends 00230 with a fourth switch reporting platformType "sonic", asserting it resolves to PlatformTypeEnum.SONIC. Verified to fail against the unfixed enum (AttributeError: type object 'PlatformTypeEnum' has no attribute 'SONIC'). module_utils suite green: 3036 passed. black/isort/pylint/mypy clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014aJ3Y2TJEAqJZBeUKdUnGa
get_platform_type() resolves via PlatformTypeEnum(raw) inside a try/except ValueError, falling through to None for any value the enum does not know. With SONIC absent, a SONiC switch silently reported "no platform type" rather than its actual platform -- the exact failure the enum exists to prevent. Found while checking PlatformTypeEnum against develop's pre-existing PlatformType (plugins/module_utils/models/manage_switches/enums.py), which has carried SONIC all along. Whether the two enums converge is an open question being discussed with @AKDRG on #405; this gap is a defect either way, so fix it now rather than leave it pending that outcome. If PlatformTypeEnum is later dropped in favor of a promoted PlatformType, this member goes with it. Test: extends 00230 with a fourth switch reporting platformType "sonic", asserting it resolves to PlatformTypeEnum.SONIC. Verified to fail against the unfixed enum (AttributeError: type object 'PlatformTypeEnum' has no attribute 'SONIC'). module_utils suite green: 3036 passed. black/isort/pylint/mypy clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014aJ3Y2TJEAqJZBeUKdUnGa
get_platform_type() resolves via PlatformTypeEnum(raw) inside a try/except ValueError, falling through to None for any value the enum does not know. With SONIC absent, a SONiC switch silently reported "no platform type" rather than its actual platform -- the exact failure the enum exists to prevent. Found while checking PlatformTypeEnum against develop's pre-existing PlatformType (plugins/module_utils/models/manage_switches/enums.py), which has carried SONIC all along. Whether the two enums converge is an open question being discussed with @AKDRG on #405; this gap is a defect either way, so fix it now rather than leave it pending that outcome. If PlatformTypeEnum is later dropped in favor of a promoted PlatformType, this member goes with it. Test: extends 00230 with a fourth switch reporting platformType "sonic", asserting it resolves to PlatformTypeEnum.SONIC. Verified to fail against the unfixed enum (AttributeError: type object 'PlatformTypeEnum' has no attribute 'SONIC'). module_utils suite green: 3036 passed. black/isort/pylint/mypy clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014aJ3Y2TJEAqJZBeUKdUnGa
get_platform_type() resolves via PlatformTypeEnum(raw) inside a try/except ValueError, falling through to None for any value the enum does not know. With SONIC absent, a SONiC switch silently reported "no platform type" rather than its actual platform -- the exact failure the enum exists to prevent. Found while checking PlatformTypeEnum against develop's pre-existing PlatformType (plugins/module_utils/models/manage_switches/enums.py), which has carried SONIC all along. Whether the two enums converge is an open question being discussed with @AKDRG on #405; this gap is a defect either way, so fix it now rather than leave it pending that outcome. If PlatformTypeEnum is later dropped in favor of a promoted PlatformType, this member goes with it. Test: extends 00230 with a fourth switch reporting platformType "sonic", asserting it resolves to PlatformTypeEnum.SONIC. Verified to fail against the unfixed enum (AttributeError: type object 'PlatformTypeEnum' has no attribute 'SONIC'). module_utils suite green: 3036 passed. black/isort/pylint/mypy clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014aJ3Y2TJEAqJZBeUKdUnGa
allenrobel
left a comment
There was a problem hiding this comment.
Code review
Incremental review of the three commits since my last review (Fix Preserve Config, Platform Type Validation + Support Docs, Remove support for IPFM/Tier2, Refine switch preserve config capabilities). One finding, posted inline — a follow-up to the earlier (resolved) IPFM thread.
🤖 Generated with Claude Code
| family="IPFM", | ||
| fabric_types=frozenset({"ipfm", "ipfmenhanced"}), | ||
| platform_types=frozenset({PlatformType.NX_OS}), | ||
| roles=IPFM_ROLES, | ||
| preserve_config_values=frozenset({False}), | ||
| default_preserve_config=False, |
There was a problem hiding this comment.
Follow-up to the earlier IPFM thread ("We shouldn't expose any support for IPFM yet" / "Removed."): the removal in Remove support for IPFM/Tier2 covers the tier2_leaf argspec choice, the DOCUMENTATION notes, and the unit test, but this capability entry still makes IPFM fully functional. Nothing gates the fabric parameter, so a task targeting an ipfm/ipfmenhanced fabric with role: leaf or spine still validates and onboards end-to-end. IPFM_ROLES (line 93) also still contains SwitchRole.TIER2_LEAF, so the enforcement layer continues to accept the role that was removed from the argspec. If IPFM should not be exposed yet, this entry (and IPFM_ROLES) needs to be removed or gated as well; if the entry stays, the DOCUMENTATION notes should re-document IPFM's constraints, including the newly added default_preserve_config=False derivation.
There was a problem hiding this comment.
Gated the onboarding of switches for supported fabrics through SUPPORTED_SWITCH_ONBOARDING_FABRIC_TYPES. Documentation has been modified to announce the support of only DC VXLAN and Campus.
There was a problem hiding this comment.
High: Unsupported fabrics still reach destructive removal paths
Issue
The new supported-fabric gate runs after the early deleted and empty-overridden branches. Those states can therefore remove selected switches—or the entire inventory—from IPFM or any other fabric the module declares unsupported.
Evidence
nd_switch_resources.pylines 3033-3045 returns directly into deletion for both states.nd_switch_resources.pylines 3050-3052 invokes capability validation only after those returns.fabric_switch_capabilities.pylines 184-195 permits only iBGP, eBGP, and Campus onboarding.nd_manage_switches.pylines 289-293 says validation occurs before write operations.
For example, the module correctly rejects a nonempty write configuration against IPFM:
- cisco.nd.nd_manage_switches:
fabric: IPFM_A
state: merged
config:
- seed_ip: 192.0.2.10
username: admin
password: "{{ switch_password }}"Fabric type 'ipfm' is not supported.
However, changing only the state to deleted bypasses that gate:
- name: Remove one switch from the unsupported IPFM fabric
cisco.nd.nd_manage_switches:
fabric: IPFM_A
state: deleted
config:
- seed_ip: 192.0.2.10The exact-head probe called the bulk-remove path with that switch's serial number.
More seriously, an empty overridden configuration also bypasses validation:
- name: Remove every switch from the unsupported IPFM fabric
cisco.nd.nd_manage_switches:
fabric: IPFM_A
state: overridden
config: []That path calls _handle_deleted_state(None), which treats the whole existing inventory as the deletion target.
Existing PR overlap
This expands the active IPFM fail-closed thread. The latest change blocks onboarding, but destructive states remain reachable because they return before the new validation call.
Existing open issue overlap
Tracked by #488, which requires fabric validation before write operations. #477 is also related because it requires explicit validation of switch-removal safety.
Impact
A module advertised as unsupported for IPFM can still remove a selected IPFM switch. Empty overridden can deboard every switch in the fabric, making the fail-closed boundary inconsistent specifically on destructive operations.
Suggested fix
Resolve and validate the fabric support boundary before every mutating state, including deleted and empty overridden.
Keep onboarding-field validation separate so deletion does not require credentials, role, platform, or preserve settings. If deboarding from unsupported fabrics is intentionally allowed, document and test that narrower contract explicitly instead of bypassing the gate implicitly.
get_platform_type() resolves via PlatformTypeEnum(raw) inside a try/except ValueError, falling through to None for any value the enum does not know. With SONIC absent, a SONiC switch silently reported "no platform type" rather than its actual platform -- the exact failure the enum exists to prevent. Found while checking PlatformTypeEnum against develop's pre-existing PlatformType (plugins/module_utils/models/manage_switches/enums.py), which has carried SONIC all along. Whether the two enums converge is an open question being discussed with @AKDRG on #405; this gap is a defect either way, so fix it now rather than leave it pending that outcome. If PlatformTypeEnum is later dropped in favor of a promoted PlatformType, this member goes with it. Test: extends 00230 with a fourth switch reporting platformType "sonic", asserting it resolves to PlatformTypeEnum.SONIC. Verified to fail against the unfixed enum (AttributeError: type object 'PlatformTypeEnum' has no attribute 'SONIC'). module_utils suite green: 3036 passed. black/isort/pylint/mypy clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014aJ3Y2TJEAqJZBeUKdUnGa
Proposed Changes
This PR improves nd_manage_switches support for fabric-specific switch onboarding by adding fabric capability validation, exposing supported switch platform types, and improving lifecycle output visibility for config-save and deploy operations.
Changes
Test Notes
Validated against live controller
pytest tests/unit/module_utils/test_nd_switch_resources.py -q
44 passed
Cisco Nexus Dashboard Version
4.2(1)
Related ND API Resource Category
Checklist