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.
| - nx-os | ||
| - ios-xe | ||
| - ios-xr | ||
| - other |
There was a problem hiding this comment.
Other is used for Switches from other vendors such as Arista.
| AI Routed, and IPFM.' | ||
| - 'NX-OS and IOS-XE fabrics: Campus VXLAN.' | ||
| - 'NX-OS, IOS-XE, IOS-XR, and other platform fabrics: External.' | ||
| - C(preserve_config) must be C(false) for Routed, Campus VXLAN, AI VXLAN, |
There was a problem hiding this comment.
I do not think this statement is true. Lets discuss in standup if needed.
There was a problem hiding this comment.
Preserve config is disabled(false) for these fabrics.
| - 'NX-OS, IOS-XE, IOS-XR, and other platform fabrics: External.' | ||
| - C(preserve_config) must be C(false) for Routed, Campus VXLAN, AI VXLAN, | ||
| AI Routed, and IPFM fabrics. | ||
| - C(preserve_config) must be C(true) for External fabrics. |
There was a problem hiding this comment.
This is implicit true for external fabrics and not user configurable and in general defaults to true.
There was a problem hiding this comment.
Defaults derived and validated depending on fabric type.
| @@ -117,11 +120,24 @@ | |||
| - edge_router | |||
| - core_router | |||
| - tor | |||
| - tier2_leaf | |||
There was a problem hiding this comment.
We shouldn't expose yet imho.
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