Skip to content

Enhancing Fabric Type Support for Switches Module - #405

Open
AKDRG wants to merge 9 commits into
CiscoDevNet:developfrom
AKDRG:nd_switches_enhancement
Open

Enhancing Fabric Type Support for Switches Module#405
AKDRG wants to merge 9 commits into
CiscoDevNet:developfrom
AKDRG:nd_switches_enhancement

Conversation

@AKDRG

@AKDRG AKDRG commented Jul 16, 2026

Copy link
Copy Markdown
Collaborator

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

  • Added fabric capability validation before switch discovery/add workflows.
  • Enforced fabric-specific support for:
  1. switch role
  2. preserve_config
  3. platform_type
  • Exposed platform_type in the playbook argspec with supported choices:
  1. nx-os
  2. ios-xe
  3. ios-xr
  4. other
  • Registered finalize operations in module output:
  1. config_save
  2. deploy_switches
  3. deploy_config
  • Preserved full controller response metadata for finalize API calls where available.
  • Added/updated unit tests for fabric validation, platform input handling, and finalize output visibility.

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

  • analyze
  • infa
  • 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

Comment thread plugins/module_utils/manage_switches/fabric_switch_capabilities.py
@allenrobel

Copy link
Copy Markdown
Collaborator

@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 PlatformTypeEnum to plugins/module_utils/enums.py for FabricContext.get_platform_type(), which reads additionalData.platformType off GET /fabrics/{fabric}/switches. I did that without noticing your PlatformType in models/manage_switches/enums.py already covers the same ground — same values, except yours also has SONIC, which mine is missing (a real gap: my lookup would silently return None for a SONiC switch). Two enums one token apart in the name is a trap for the next reader, so I'd rather converge than ship the duplicate.

Proposal. Promote PlatformType from models/manage_switches/enums.py up to module_utils/enums.py, and have FabricContext use it. Per CLAUDE.md, module_utils/enums.py is for "Enums commonly used by most module utilities" — with both manage_switches and fabric_context needing it, that's what it's become. The reverse (a top-level util importing from models/manage_switches/) would be a layering inversion.

This shouldn't cost you anything. Done as a move + re-export — leaving PlatformType re-exported from models/manage_switches/enums.py (via __all__, to keep pylint quiet) — every existing import site keeps working untouched, nothing in this PR changes, and you don't need to rebase. module_utils/enums.py imports only from enum import Enum, so there's no cycle, and the re-export lives in a named module rather than __init__.py, so it's clear of the empty-init sanity rule. Two of your changed files (nd_switch_resources.py, config_models.py) are PlatformType consumers, which is exactly why I'd rather not touch import sites while you're in flight.

The question I actually want your read on. Your enums look deliberately endpoint-scoped — PlatformType documented as AddSwitches (POST switches), ShallowDiscoveryPlatformType split out because shallowDiscovery excludes apic. My use is a third context: the GET switches response. Is that response set known to match the AddSwitches request set, or would you rather it stay a separate enum on principle? If they can legitimately diverge, sharing one is the wrong call and I'll keep a distinct read-side enum — properly named and documented, with SONIC added. You know this API surface better than I do.

One detail either way: normalize() defaults None -> NX_OS, which suits the write path but not mine, where "switch reports no platformType" has to stay None. So I'd use the bare enum rather than normalize() regardless.

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.

allenrobel added a commit that referenced this pull request Jul 16, 2026
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 added a commit that referenced this pull request Jul 19, 2026
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

Copy link
Copy Markdown
Collaborator

@AKDRG thanks for moving PlatformType to plugins/module_utils/enums.py 👍

I'll wait for #405 to merge to leverage PlatformType at its new home.

@allenrobel allenrobel 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.

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

Comment thread plugins/module_utils/utils.py Outdated
Comment thread plugins/module_utils/manage_switches/fabric_capabilities.py Outdated
Comment thread plugins/module_utils/manage_switches/nd_switch_resources.py
Comment thread plugins/module_utils/models/manage_switches/config_models.py
@AKDRG AKDRG added the ready for review Submitter is requesting a PR review label Jul 22, 2026
allenrobel added a commit that referenced this pull request Jul 22, 2026
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
allenrobel previously approved these changes Jul 23, 2026

@allenrobel allenrobel 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.

LGTM after comments were addressed.

@AKDRG
AKDRG requested a review from akinross July 23, 2026 14:47
@sivakasi-cisco sivakasi-cisco added the nac01 NaC ND release 0.0.1 label Jul 23, 2026
akinross
akinross previously approved these changes Jul 24, 2026
@sivakasi-cisco

Copy link
Copy Markdown
Collaborator

#438

allenrobel added a commit that referenced this pull request Jul 27, 2026
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
Comment thread plugins/module_utils/manage_switches/nd_switch_resources.py
if self.state not in ("merged", "replaced", "overridden"):
self.nd.module.fail_json(msg=f"Unsupported state: {self.state}")

# --- Validate & classify ------------------------------------------------

@mikewiebe mikewiebe Jul 28, 2026

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.

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.py lines 3042-3059 runs the new capability validation before diff classification and idempotency.

  • config_models.py lines 323-327 defaults omitted preserve_config to false.

  • fabric_switch_capabilities.py lines 243-247 rejects that value unconditionally for External fabrics.

  • nd_switch_resources.py lines 2748-2756 also hard-codes preserve_config: false in 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 before compute_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.


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.

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?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

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

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.

What is other?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Other is used for Switches from other vendors such as Arista.

Comment thread plugins/modules/nd_manage_switches.py Outdated
Comment thread plugins/modules/nd_manage_switches.py Outdated
Comment thread plugins/modules/nd_manage_switches.py Outdated
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,

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.

I do not think this statement is true. Lets discuss in standup if needed.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

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.

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.

This is implicit true for external fabrics and not user configurable and in general defaults to true.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Defaults derived and validated depending on fabric type.

Comment thread plugins/modules/nd_manage_switches.py Outdated
@@ -117,11 +120,24 @@
- edge_router
- core_router
- tor
- tier2_leaf

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.

We shouldn't expose yet imho.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

nac01 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.

6 participants