Skip to content

nd_fabric_prepare_update - ND 4.2 software staging - #291

Open
allenrobel wants to merge 7 commits into
developfrom
nd_fabric_prepare_update
Open

nd_fabric_prepare_update - ND 4.2 software staging#291
allenrobel wants to merge 7 commits into
developfrom
nd_fabric_prepare_update

Conversation

@allenrobel

@allenrobel allenrobel commented May 21, 2026

Copy link
Copy Markdown
Collaborator

Related Issue(s)

N/A — new module.

Proposed Changes

  • New module nd_fabric_prepare_update — the Fabric Software Management "Prepare" step on Nexus Dashboard 4.2, i.e. the softwareUpdatePlan/actions/stage action: stages and validates the configured image on the switches of one or more update groups.
  • One-shot action module: pre-flight check rejects mixed-role update groups (ND will not prepare them), idempotent against already-staged groups, and with wait: true polls to completion.
  • FabricPrepareUpdateOrchestrator — drives the stage action and a poll loop that tolerates transient transport errors during the long wait.
  • New endpoint models EpFabricSoftwareUpdatePlanStage and EpFabricSoftwareUpdatePlanSummary; Pydantic models for the softwareUpdatePlan/summary response.
  • 33 unit tests (orchestrator + models) and an integration test target.

Test Notes

  • Unit: python -m pytest tests/unit/module_utils/models/test_fabric_prepare_update.py tests/unit/module_utils/orchestrators/test_fabric_prepare_update.py — 33 pass.
  • Sanity: ansible-test sanity --docker passes for all new/changed files.
  • Integration: ansible-test integration nd_fabric_prepare_update — green against a live ND 4.2.1 lab (fabric SITE1), covering mixed-role pre-flight rejection, check mode, a real stage + wait, and idempotency.

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

@allenrobel
allenrobel force-pushed the nd_fabric_update_group branch from 3c80477 to 67167cd Compare May 21, 2026 19:41
@allenrobel
allenrobel force-pushed the nd_fabric_prepare_update branch from fc757df to 49a49b9 Compare May 21, 2026 19:41
@allenrobel allenrobel changed the title Add nd_fabric_prepare_update module for ND 4.2 software staging nd_fabric_prepare_update - ND 4.2 software staging May 27, 2026
@allenrobel
allenrobel force-pushed the nd_fabric_update_group branch from 67167cd to fbf128d Compare May 28, 2026 17:34
@allenrobel
allenrobel force-pushed the nd_fabric_prepare_update branch 2 times, most recently from 542a13f to 5f144b3 Compare May 28, 2026 18:16
@allenrobel
allenrobel force-pushed the nd_fabric_update_group branch from ae32c0e to a9575ad Compare June 1, 2026 19:29
@allenrobel
allenrobel force-pushed the nd_fabric_prepare_update branch 5 times, most recently from b06785f to f5a5e6d Compare June 4, 2026 18:18
@allenrobel
allenrobel force-pushed the nd_fabric_update_group branch from afbd34b to 8bb515f Compare June 4, 2026 20:42
@allenrobel
allenrobel force-pushed the nd_fabric_prepare_update branch from f5a5e6d to 2ed1e9d Compare June 4, 2026 20:42
Base automatically changed from nd_fabric_update_group to develop June 8, 2026 23:45
@allenrobel
allenrobel force-pushed the nd_fabric_prepare_update branch from 2ed1e9d to 7167190 Compare June 9, 2026 00:04
Copilot AI review requested due to automatic review settings June 13, 2026 03:28
@allenrobel
allenrobel force-pushed the nd_fabric_prepare_update branch from 7167190 to 12e2301 Compare June 13, 2026 03:28

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

Adds a new one-shot Ansible module to drive Nexus Dashboard 4.2 “Prepare” (stage + validate) for one or more fabric software update groups, including a dedicated orchestrator, endpoint models, response parsing models, and test coverage.

Changes:

  • Introduces nd_fabric_prepare_update module with preflight mixed-role rejection, idempotency detection, and optional wait/poll-to-completion behavior.
  • Adds FabricPrepareUpdateOrchestrator plus new Manage endpoint models for softwareUpdatePlan/summary and softwareUpdatePlan/actions/stage.
  • Adds unit tests (orchestrator + models), fixtures, and an integration test target.

Reviewed changes

Copilot reviewed 14 out of 15 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
tests/unit/module_utils/orchestrators/test_fabric_prepare_update.py Unit tests covering orchestrator preflight, stage call, idempotency snapshot logic, and polling behavior.
tests/unit/module_utils/models/test_fabric_prepare_update.py Unit tests validating Pydantic parsing/aliasing for the summary response models.
tests/unit/module_utils/fixtures/fixture_data/test_fabric_prepare_update.json Fixture responses for summary/stage workflows and polling scenarios.
tests/integration/targets/nd_fabric_prepare_update/vars/main.yaml Integration test variables for real ND 4.2.1 fabric + switches + image name.
tests/integration/targets/nd_fabric_prepare_update/tasks/setup.yaml Creates prerequisite update groups (single-role and mixed-role) for integration tests.
tests/integration/targets/nd_fabric_prepare_update/tasks/prepare.yaml Executes prepare module scenarios (mixed-role rejection, check mode, real stage+wait, idempotency).
tests/integration/targets/nd_fabric_prepare_update/tasks/main.yaml Integration test harness with module defaults/timeouts and setup/cleanup blocks.
tests/integration/targets/nd_fabric_prepare_update/tasks/cleanup.yaml Removes created update groups after test run.
tests/integration/targets/nd_fabric_prepare_update/meta/main.yml Declares integration target metadata (no dependencies).
plugins/modules/nd_fabric_prepare_update.py New module implementing prepare/stage workflow and documentation/examples.
plugins/module_utils/orchestrators/fabric_prepare_update.py Orchestrator implementing summary fetch, preflight checks, stage action, and poll loop with transient-failure tolerance.
plugins/module_utils/models/fabric_prepare_update/software_update_plan_summary.py Pydantic models for softwareUpdatePlan/summary response parsing.
plugins/module_utils/models/fabric_prepare_update/init.py Package init for the new model namespace.
plugins/module_utils/endpoints/v1/manage/software_update_plan_summary.py New endpoint model for GET summary.
plugins/module_utils/endpoints/v1/manage/software_update_plan_actions.py Adds endpoint model for POST stage action.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread plugins/module_utils/orchestrators/fabric_prepare_update.py
Comment thread plugins/modules/nd_fabric_prepare_update.py
Comment thread plugins/modules/nd_fabric_prepare_update.py Outdated
@allenrobel
allenrobel force-pushed the nd_fabric_prepare_update branch from 12e2301 to ce02880 Compare June 16, 2026 19:05
allenrobel added a commit that referenced this pull request Jul 19, 2026
Resolve reviewer feedback on the fabric prepare-update (stage) module:

- Clamp wait_interval to >= 0 in wait_for_completion so a negative interval
  no-ops instead of raising ValueError from time.sleep (Copilot).
- Fix the multi-group EXAMPLES wait_interval (60 -> 20) so it no longer
  contradicts the "keep below persistent_command_timeout (30s)" guidance
  (Copilot).
- Enforce a non-empty update_groups via a _validate_update_groups guard in
  the module wrapper; required=True alone accepts an empty list (akinross).
- snapshot_fully_prepared now returns True for an empty/switch-less snapshot
  (vacuously prepared, changed=False), consistent with wait_for_completion
  treating a switch-less group as already satisfied (akinross).
- Scope summary polls with the API's updateGroupName query parameter for the
  common single-group prepare, avoiding a fabric-wide GET every wait_interval;
  multi-group prepares keep the single fabric-wide fetch and filter in memory
  to avoid N+1 requests (mikewiebe). Models the param on
  EpFabricSoftwareUpdatePlanSummary via EndpointQueryParams.
- Add module-wrapper unit tests (tests/unit/modules/test_nd_fabric_prepare_update.py)
  covering the update_groups guard and the _run_prepare decision surface, plus
  endpoint tests for the new scoped summary path (mikewiebe).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@allenrobel
allenrobel force-pushed the nd_fabric_prepare_update branch from 639eb93 to c9bbb83 Compare July 19, 2026 18:17
@sivakasi-cisco sivakasi-cisco added the nac01 NaC ND release 0.0.1 label Jul 21, 2026
allenrobel added a commit that referenced this pull request Jul 22, 2026
Resolve reviewer feedback on the fabric prepare-update (stage) module:

- Clamp wait_interval to >= 0 in wait_for_completion so a negative interval
  no-ops instead of raising ValueError from time.sleep (Copilot).
- Fix the multi-group EXAMPLES wait_interval (60 -> 20) so it no longer
  contradicts the "keep below persistent_command_timeout (30s)" guidance
  (Copilot).
- Enforce a non-empty update_groups via a _validate_update_groups guard in
  the module wrapper; required=True alone accepts an empty list (akinross).
- snapshot_fully_prepared now returns True for an empty/switch-less snapshot
  (vacuously prepared, changed=False), consistent with wait_for_completion
  treating a switch-less group as already satisfied (akinross).
- Scope summary polls with the API's updateGroupName query parameter for the
  common single-group prepare, avoiding a fabric-wide GET every wait_interval;
  multi-group prepares keep the single fabric-wide fetch and filter in memory
  to avoid N+1 requests (mikewiebe). Models the param on
  EpFabricSoftwareUpdatePlanSummary via EndpointQueryParams.
- Add module-wrapper unit tests (tests/unit/modules/test_nd_fabric_prepare_update.py)
  covering the update_groups guard and the _run_prepare decision surface, plus
  endpoint tests for the new scoped summary path (mikewiebe).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@allenrobel
allenrobel force-pushed the nd_fabric_prepare_update branch from c9bbb83 to c2fe075 Compare July 22, 2026 16:12
allenrobel added a commit that referenced this pull request Jul 27, 2026
Resolve reviewer feedback on the fabric prepare-update (stage) module:

- Clamp wait_interval to >= 0 in wait_for_completion so a negative interval
  no-ops instead of raising ValueError from time.sleep (Copilot).
- Fix the multi-group EXAMPLES wait_interval (60 -> 20) so it no longer
  contradicts the "keep below persistent_command_timeout (30s)" guidance
  (Copilot).
- Enforce a non-empty update_groups via a _validate_update_groups guard in
  the module wrapper; required=True alone accepts an empty list (akinross).
- snapshot_fully_prepared now returns True for an empty/switch-less snapshot
  (vacuously prepared, changed=False), consistent with wait_for_completion
  treating a switch-less group as already satisfied (akinross).
- Scope summary polls with the API's updateGroupName query parameter for the
  common single-group prepare, avoiding a fabric-wide GET every wait_interval;
  multi-group prepares keep the single fabric-wide fetch and filter in memory
  to avoid N+1 requests (mikewiebe). Models the param on
  EpFabricSoftwareUpdatePlanSummary via EndpointQueryParams.
- Add module-wrapper unit tests (tests/unit/modules/test_nd_fabric_prepare_update.py)
  covering the update_groups guard and the _run_prepare decision surface, plus
  endpoint tests for the new scoped summary path (mikewiebe).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@allenrobel
allenrobel force-pushed the nd_fabric_prepare_update branch from c2fe075 to 5cb4cb4 Compare July 27, 2026 23:17
@mikewiebe mikewiebe removed the nac01 NaC ND release 0.0.1 label Aug 10, 2026
allenrobel added a commit that referenced this pull request Aug 10, 2026
Resolve reviewer feedback on the fabric prepare-update (stage) module:

- Clamp wait_interval to >= 0 in wait_for_completion so a negative interval
  no-ops instead of raising ValueError from time.sleep (Copilot).
- Fix the multi-group EXAMPLES wait_interval (60 -> 20) so it no longer
  contradicts the "keep below persistent_command_timeout (30s)" guidance
  (Copilot).
- Enforce a non-empty update_groups via a _validate_update_groups guard in
  the module wrapper; required=True alone accepts an empty list (akinross).
- snapshot_fully_prepared now returns True for an empty/switch-less snapshot
  (vacuously prepared, changed=False), consistent with wait_for_completion
  treating a switch-less group as already satisfied (akinross).
- Scope summary polls with the API's updateGroupName query parameter for the
  common single-group prepare, avoiding a fabric-wide GET every wait_interval;
  multi-group prepares keep the single fabric-wide fetch and filter in memory
  to avoid N+1 requests (mikewiebe). Models the param on
  EpFabricSoftwareUpdatePlanSummary via EndpointQueryParams.
- Add module-wrapper unit tests (tests/unit/modules/test_nd_fabric_prepare_update.py)
  covering the update_groups guard and the _run_prepare decision surface, plus
  endpoint tests for the new scoped summary path (mikewiebe).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@allenrobel
allenrobel force-pushed the nd_fabric_prepare_update branch from 5cb4cb4 to 1f82ed3 Compare August 10, 2026 20:11
allenrobel added a commit that referenced this pull request Aug 11, 2026
Resolve reviewer feedback on the fabric prepare-update (stage) module:

- Clamp wait_interval to >= 0 in wait_for_completion so a negative interval
  no-ops instead of raising ValueError from time.sleep (Copilot).
- Fix the multi-group EXAMPLES wait_interval (60 -> 20) so it no longer
  contradicts the "keep below persistent_command_timeout (30s)" guidance
  (Copilot).
- Enforce a non-empty update_groups via a _validate_update_groups guard in
  the module wrapper; required=True alone accepts an empty list (akinross).
- snapshot_fully_prepared now returns True for an empty/switch-less snapshot
  (vacuously prepared, changed=False), consistent with wait_for_completion
  treating a switch-less group as already satisfied (akinross).
- Scope summary polls with the API's updateGroupName query parameter for the
  common single-group prepare, avoiding a fabric-wide GET every wait_interval;
  multi-group prepares keep the single fabric-wide fetch and filter in memory
  to avoid N+1 requests (mikewiebe). Models the param on
  EpFabricSoftwareUpdatePlanSummary via EndpointQueryParams.
- Add module-wrapper unit tests (tests/unit/modules/test_nd_fabric_prepare_update.py)
  covering the update_groups guard and the _run_prepare decision surface, plus
  endpoint tests for the new scoped summary path (mikewiebe).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@allenrobel
allenrobel force-pushed the nd_fabric_prepare_update branch from 1f82ed3 to d50c7d3 Compare August 11, 2026 01:24
allenrobel added a commit that referenced this pull request Aug 11, 2026
Resolve reviewer feedback on the fabric prepare-update (stage) module:

- Clamp wait_interval to >= 0 in wait_for_completion so a negative interval
  no-ops instead of raising ValueError from time.sleep (Copilot).
- Fix the multi-group EXAMPLES wait_interval (60 -> 20) so it no longer
  contradicts the "keep below persistent_command_timeout (30s)" guidance
  (Copilot).
- Enforce a non-empty update_groups via a _validate_update_groups guard in
  the module wrapper; required=True alone accepts an empty list (akinross).
- snapshot_fully_prepared now returns True for an empty/switch-less snapshot
  (vacuously prepared, changed=False), consistent with wait_for_completion
  treating a switch-less group as already satisfied (akinross).
- Scope summary polls with the API's updateGroupName query parameter for the
  common single-group prepare, avoiding a fabric-wide GET every wait_interval;
  multi-group prepares keep the single fabric-wide fetch and filter in memory
  to avoid N+1 requests (mikewiebe). Models the param on
  EpFabricSoftwareUpdatePlanSummary via EndpointQueryParams.
- Add module-wrapper unit tests (tests/unit/modules/test_nd_fabric_prepare_update.py)
  covering the update_groups guard and the _run_prepare decision surface, plus
  endpoint tests for the new scoped summary path (mikewiebe).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@allenrobel
allenrobel force-pushed the nd_fabric_prepare_update branch from d50c7d3 to e532084 Compare August 11, 2026 20:54
allenrobel added a commit that referenced this pull request Aug 11, 2026
Resolve reviewer feedback on the fabric prepare-update (stage) module:

- Clamp wait_interval to >= 0 in wait_for_completion so a negative interval
  no-ops instead of raising ValueError from time.sleep (Copilot).
- Fix the multi-group EXAMPLES wait_interval (60 -> 20) so it no longer
  contradicts the "keep below persistent_command_timeout (30s)" guidance
  (Copilot).
- Enforce a non-empty update_groups via a _validate_update_groups guard in
  the module wrapper; required=True alone accepts an empty list (akinross).
- snapshot_fully_prepared now returns True for an empty/switch-less snapshot
  (vacuously prepared, changed=False), consistent with wait_for_completion
  treating a switch-less group as already satisfied (akinross).
- Scope summary polls with the API's updateGroupName query parameter for the
  common single-group prepare, avoiding a fabric-wide GET every wait_interval;
  multi-group prepares keep the single fabric-wide fetch and filter in memory
  to avoid N+1 requests (mikewiebe). Models the param on
  EpFabricSoftwareUpdatePlanSummary via EndpointQueryParams.
- Add module-wrapper unit tests (tests/unit/modules/test_nd_fabric_prepare_update.py)
  covering the update_groups guard and the _run_prepare decision surface, plus
  endpoint tests for the new scoped summary path (mikewiebe).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@allenrobel
allenrobel force-pushed the nd_fabric_prepare_update branch from e532084 to 55e4611 Compare August 11, 2026 23:36
allenrobel added a commit that referenced this pull request Aug 13, 2026
Resolve reviewer feedback on the fabric prepare-update (stage) module:

- Clamp wait_interval to >= 0 in wait_for_completion so a negative interval
  no-ops instead of raising ValueError from time.sleep (Copilot).
- Fix the multi-group EXAMPLES wait_interval (60 -> 20) so it no longer
  contradicts the "keep below persistent_command_timeout (30s)" guidance
  (Copilot).
- Enforce a non-empty update_groups via a _validate_update_groups guard in
  the module wrapper; required=True alone accepts an empty list (akinross).
- snapshot_fully_prepared now returns True for an empty/switch-less snapshot
  (vacuously prepared, changed=False), consistent with wait_for_completion
  treating a switch-less group as already satisfied (akinross).
- Scope summary polls with the API's updateGroupName query parameter for the
  common single-group prepare, avoiding a fabric-wide GET every wait_interval;
  multi-group prepares keep the single fabric-wide fetch and filter in memory
  to avoid N+1 requests (mikewiebe). Models the param on
  EpFabricSoftwareUpdatePlanSummary via EndpointQueryParams.
- Add module-wrapper unit tests (tests/unit/modules/test_nd_fabric_prepare_update.py)
  covering the update_groups guard and the _run_prepare decision surface, plus
  endpoint tests for the new scoped summary path (mikewiebe).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@allenrobel
allenrobel force-pushed the nd_fabric_prepare_update branch from 55e4611 to d38a62e Compare August 13, 2026 19:25
"""

update_group_name: str | None = Field(default=None, alias="updateGroupName")
update_group_status: str | None = Field(default=None, alias="updateGroupStatus")

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.

does the same free-form comment about string apply to update_group_status?

Comment on lines +42 to +44
`image_staged_status` and `image_validated_status` are kept as free-form strings (not a
`Literal`) so an unrecognized status from a newer ND release parses rather than raising during
a poll. Known values: `none`, `inProgress`, `success`, `failed`, `skipped`.

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.

Did you consider a Literal and TypeAlias combination for this case, in a way that known values for the status are Literals and the states can contain that TypeAlias or a string?

from typing import Literal, TypeAlias

KnownImageStatus: TypeAlias = Literal[
    "none", "inProgress", "success", "failed", "skipped",
]
ImageStatus: TypeAlias = KnownImageStatus | str

At runtime that is effectively str, but it makes the known vocabulary visible to readers and can generate a schema with both the enum branch and the general-string fallback.

try:
summary = self.get_summary(update_group_name=scope)
groups = self._resolve_groups(summary, update_group_names)
switches = [sw for group in groups for sw in (group.update_group_switches or [])]

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 assume we need the or [] because group.update_group_switches can be None according to update_group_switches: list[SwitchStageStatusModel] | None = Field(default=None, alias="updateGroupSwitches").

I am confused on when the case would be None, is this when the field is missing from response (since the model is only used for response as documented)? Should we simplify the model to not write None and default to an empty list? Another option would be to error when updateGroupSwitches is not part of the response, assuming that would lead to None?

f"Polling staging status for update group(s) {update_group_names} in fabric "
f"'{self.fabric_name}' failed {consecutive_failures} times in a row: {e}"
) from e
time.sleep(interval)

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.

When the remaining timeout is shorter than wait_interval, the loop still sleeps for the full interval before checking the deadline again. For example, wait_timeout=65 and wait_interval=30 can time out around 90 seconds. I think we should wait for a maximum of the remaining time until deadline, so in the example wait 30, 30 and 5 instead of 30, 30, 30.

same applies to the timeout below

summary = self.get_summary(update_group_name=scope)
groups = self._resolve_groups(summary, update_group_names)
switches = [sw for group in groups for sw in (group.update_group_switches or [])]
except Exception as e: # pylint: disable=broad-except

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.

Do we want to retry pydantic validation errors that potentially could be raised in this try/except block? I assume the chance we hit those are small, but they should not be retried in my opinion.

description:
- Whether to wait for staging to complete on every switch before returning.
- When V(true), the module polls Nexus Dashboard until every switch has staged and validated, or until O(wait_timeout) is reached.
- When V(false), the module returns as soon as Nexus Dashboard accepts the stage action; staging continues on Nexus Dashboard.

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 think we need to reword this because the module still performs the status snapshot for after in results, so it does not return immediately.

- The name of the fabric that contains the update groups to prepare.
type: str
required: true
update_groups:

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.

should we document the validation rule that at least on entry is required?

options:
fabric_name:
description:
- The name of the fabric that contains the update groups to prepare.

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.

Comment on lines +166 to +174
sender = Sender()
sender.ansible_module = module
rest_send_params = dict(module.params)
rest_send_params["check_mode"] = module.check_mode
rest_send = RestSend(rest_send_params)
rest_send.sender = sender
rest_send.response_handler = ResponseHandler()

orchestrator = FabricPrepareUpdateOrchestrator(rest_send=rest_send, results=Results())

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.

not wrong but more stylistic inconsistency question as to why some parts are assigned to variable first but FabricPrepareUpdateOrchestrator is constructed directly with the objects and functions.

Suggested change
sender = Sender()
sender.ansible_module = module
rest_send_params = dict(module.params)
rest_send_params["check_mode"] = module.check_mode
rest_send = RestSend(rest_send_params)
rest_send.sender = sender
rest_send.response_handler = ResponseHandler()
orchestrator = FabricPrepareUpdateOrchestrator(rest_send=rest_send, results=Results())
rest_send = RestSend({**module.params, "check_mode": module.check_mode})
rest_send.sender = Sender(ansible_module=module)
rest_send.response_handler = ResponseHandler()
orchestrator = FabricPrepareUpdateOrchestrator(rest_send=rest_send, results=Results())

- The target image, whether the upgrade is disruptive or non-disruptive, maintenance mode, and the report checks
are properties of the update group itself, not of this module. Configure them with M(cisco.nd.nd_fabric_update_group)
before preparing. This module only selects which update groups to prepare.
- In check mode, when staging is required, the module reports a change but does not stage, because the stage

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 would slight reword the description in check mode

Suggested change
- In check mode, when staging is required, the module reports a change but does not stage, because the stage
- In check mode, if staging is required, the module reports changed=true but does not invoke the stage action because it cannot be previewed. Consequently, the returned after status is unchanged from before.

allenrobel and others added 7 commits August 28, 2026 07:42
Implements the Fabric Software Management "Prepare" step on Nexus
Dashboard 4.2 - the softwareUpdatePlan/actions/stage action - which
stages and validates the configured image on the switches of one or
more update groups.

- New module nd_fabric_prepare_update: a one-shot action module that
  pre-flight checks for mixed-role update groups, is idempotent against
  already-staged groups, and (with wait) polls to completion.
- FabricPrepareUpdateOrchestrator: drives the stage action and a poll
  loop that tolerates transient transport errors during the long wait.
- EpFabricSoftwareUpdatePlanStage and EpFabricSoftwareUpdatePlanSummary
  endpoint models; softwareUpdatePlan/summary response models.
- 33 unit tests (orchestrator + models) and an integration test target.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
preflight_role_check and status_snapshot each fetched
softwareUpdatePlan/summary independently, so a prepare run made two
identical GETs back-to-back at startup. Both now accept an optional
pre-fetched summary; _run_prepare fetches it once and passes it to both.

Startup now costs one summary GET instead of two (check mode included),
and the role check and `before` snapshot observe the same point-in-time
summary. Adds test_fabric_prepare_update_00220 asserting a single GET
across the three startup calls.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two review fixes for nd_fabric_prepare_update:

- Surface the orchestrator's Results in module output. _register_api_call
  was recording every REST call, but the module returned output.format(...)
  so none of it reached the user. main() now extracts module._verbosity and
  calls format_with_verbosity(verbosity, results, ...), matching the
  nd_local_user / nd_manage_fabric_* pattern; -vv shows the stage write
  summary and -vvv shows full controller detail.

- wait_for_completion no longer polls a switch-less update group until
  wait_timeout. The success guard "if switches and all(...)" could never
  satisfy a group that resolved with zero member switches, causing a
  ~30-minute hang and a misleading timeout. Added "if not switches: return".

Adds test_fabric_prepare_update_00550 (+fixture) covering the switch-less
wait case.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Three lower-priority review fixes for nd_fabric_prepare_update
(wait_for_completion); the duplication finding is tracked in #336.

- Honor wait_timeout ahead of the retry budget. The transient-failure
  branch checked the consecutive-failure count before the deadline, so a
  small wait_timeout could be overshot by up to (_MAX+1)*interval and
  report "failed N times in a row" instead of a timeout. The deadline
  check now runs first.

- Retry an unresolved poll. _resolve_groups ran outside the try, so a
  summary that briefly fails to resolve the requested groups (e.g. a
  partial body during a controller hiccup, or a 404 ResponseHandler maps
  to an empty body) aborted the whole prepare. It now runs inside the try
  and is absorbed by the same consecutive-failure budget.

- Reuse the final poll summary for the `after` snapshot. wait_for_completion
  now returns the last SoftwareUpdatePlanSummaryModel, and the module passes
  it to status_snapshot, saving one GET per waited run.

Adds tests 00560 (timeout-over-retry ordering) and 00570 (unresolved-poll
retry), and asserts the wait return contract in 00500.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The _run_prepare return annotation tuple[Results | None, dict] is evaluated
at runtime because the module lacked from __future__ import annotations.
ansible-test sanity's pinned pylint/astroid flags an evaluated `Class | None`
union as unsupported-binary-operation (independent of the runtime Python,
which supports PEP 604). Making annotations lazy (PEP 563) silences it.

Placed before ANSIBLE_METADATA as the first statement, matching nd_log.py and
nd_manage_switches.py. Verified with:
ansible-test sanity --test pylint --docker default <module> (exit 0).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Resolve reviewer feedback on the fabric prepare-update (stage) module:

- Clamp wait_interval to >= 0 in wait_for_completion so a negative interval
  no-ops instead of raising ValueError from time.sleep (Copilot).
- Fix the multi-group EXAMPLES wait_interval (60 -> 20) so it no longer
  contradicts the "keep below persistent_command_timeout (30s)" guidance
  (Copilot).
- Enforce a non-empty update_groups via a _validate_update_groups guard in
  the module wrapper; required=True alone accepts an empty list (akinross).
- snapshot_fully_prepared now returns True for an empty/switch-less snapshot
  (vacuously prepared, changed=False), consistent with wait_for_completion
  treating a switch-less group as already satisfied (akinross).
- Scope summary polls with the API's updateGroupName query parameter for the
  common single-group prepare, avoiding a fabric-wide GET every wait_interval;
  multi-group prepares keep the single fabric-wide fetch and filter in memory
  to avoid N+1 requests (mikewiebe). Models the param on
  EpFabricSoftwareUpdatePlanSummary via EndpointQueryParams.
- Add module-wrapper unit tests (tests/unit/modules/test_nd_fabric_prepare_update.py)
  covering the update_groups guard and the _run_prepare decision surface, plus
  endpoint tests for the new scoped summary path (mikewiebe).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready for review Submitter is requesting a PR review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[ND 4.2+] Add Fabric Software Prepare/Stage Module (nd_fabric_prepare_update)

6 participants