-
Notifications
You must be signed in to change notification settings - Fork 28
nd_fabric_prepare_update - ND 4.2 software staging #291
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
11d976a
Add nd_fabric_prepare_update module for ND 4.2 software staging
allenrobel 0d8cce3
Reuse fetched summary in nd_fabric_prepare_update startup
allenrobel 9b3dbea
Bump version_added to 2.0.0 for next ND collection release
allenrobel 8cb994b
Fix prepare_update Results surfacing and switch-less wait hang
allenrobel d2acf4b
Harden prepare_update wait loop and reuse final poll summary
allenrobel 27d677f
Add from __future__ import annotations to satisfy sanity pylint
allenrobel 148fede
Address PR #291 review comments for nd_fabric_prepare_update
allenrobel c8b0b21
Docs: clarify wait=false return, check-mode wording, required update_…
allenrobel 2f89da6
Cap wait_for_completion poll sleeps at the remaining deadline
allenrobel d9a71d2
Default summary-model list fields to empty lists instead of None
allenrobel 4530942
Type per-switch image statuses as ImageStatus alias; document free-fo…
allenrobel File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
107 changes: 107 additions & 0 deletions
107
plugins/module_utils/endpoints/v1/manage/software_update_plan_summary.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,107 @@ | ||
| # Copyright: (c) 2026, Allen Robel (@allenrobel) <arobel@cisco.com> | ||
|
|
||
| # GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) | ||
| """ | ||
| ND Manage Fabric Software Management software update plan summary endpoint model. | ||
|
|
||
| ## Endpoints | ||
|
|
||
| - `EpFabricSoftwareUpdatePlanSummary` - Software update plan summary for a fabric | ||
| (GET /api/v1/manage/fabrics/{fabric_name}/softwareUpdatePlan/summary) | ||
| """ | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| from typing import Literal | ||
| from urllib.parse import quote | ||
|
|
||
| from ansible_collections.cisco.nd.plugins.module_utils.common.pydantic_compat import Field | ||
| from ansible_collections.cisco.nd.plugins.module_utils.endpoints.base import NDEndpointBaseModel | ||
| from ansible_collections.cisco.nd.plugins.module_utils.endpoints.mixins import FabricNameMixin | ||
| from ansible_collections.cisco.nd.plugins.module_utils.endpoints.query_params import EndpointQueryParams | ||
| from ansible_collections.cisco.nd.plugins.module_utils.endpoints.v1.manage.base_path import BasePath | ||
| from ansible_collections.cisco.nd.plugins.module_utils.enums import HttpVerbEnum | ||
|
|
||
|
|
||
| class SoftwareUpdatePlanSummaryEndpointParams(EndpointQueryParams): | ||
| """ | ||
| # Summary | ||
|
|
||
| Endpoint-specific query parameters for the software update plan summary endpoint. | ||
|
|
||
| `update_group_name` is rendered as the API's `updateGroupName` query parameter (snake_case -> | ||
| camelCase is automatic via `EndpointQueryParams`). When set, Nexus Dashboard scopes the summary | ||
| to that single update group instead of returning the fabric-wide plan. | ||
|
|
||
| ## Raises | ||
|
|
||
| None | ||
| """ | ||
|
|
||
| update_group_name: str | None = Field(default=None, min_length=1, description="Scope the summary to a single update group") | ||
|
|
||
|
|
||
| class EpFabricSoftwareUpdatePlanSummary(FabricNameMixin, NDEndpointBaseModel): | ||
| """ | ||
| # Summary | ||
|
|
||
| Software update plan summary for a fabric. | ||
|
|
||
| Returns the fabric-wide software update plan: every update group with its per-switch stage / | ||
| validate / install status. Used to drive the `nd_fabric_prepare_update` pre-flight role check | ||
| and to poll for staging completion. | ||
|
|
||
| Set `endpoint_params.update_group_name` to scope the summary to a single update group via the | ||
| API's optional `updateGroupName` query parameter; leave it unset for the fabric-wide summary. | ||
|
|
||
| - Path: `/api/v1/manage/fabrics/{fabric_name}/softwareUpdatePlan/summary` | ||
| - Verb: GET | ||
|
|
||
| ## Raises | ||
|
|
||
| ### ValueError | ||
|
|
||
| - Via `path` property if `fabric_name` is not set. | ||
| """ | ||
|
|
||
| class_name: Literal["EpFabricSoftwareUpdatePlanSummary"] = Field( | ||
| default="EpFabricSoftwareUpdatePlanSummary", frozen=True, description="Class name for backward compatibility" | ||
| ) | ||
| endpoint_params: SoftwareUpdatePlanSummaryEndpointParams = Field( | ||
| default_factory=SoftwareUpdatePlanSummaryEndpointParams, description="Endpoint-specific query parameters" | ||
| ) | ||
|
|
||
| @property | ||
| def path(self) -> str: | ||
| """ | ||
| # Summary | ||
|
|
||
| Build the software update plan summary endpoint path. `fabric_name` is percent-encoded with `safe=""`. When | ||
| `endpoint_params.update_group_name` is set, the `updateGroupName` query string is appended to scope the summary. | ||
|
|
||
| ## Raises | ||
|
|
||
| ### ValueError | ||
|
|
||
| - If `fabric_name` is not set before accessing `path`. | ||
| """ | ||
| if self.fabric_name is None: | ||
| raise ValueError(f"{type(self).__name__}.path: fabric_name must be set before accessing path.") | ||
| base_path = BasePath.path("fabrics", quote(self.fabric_name, safe=""), "softwareUpdatePlan", "summary") | ||
| query_string = self.endpoint_params.to_query_string() | ||
| if query_string: | ||
| return f"{base_path}?{query_string}" | ||
| return base_path | ||
|
|
||
| @property | ||
| def verb(self) -> HttpVerbEnum: | ||
| """ | ||
| # Summary | ||
|
|
||
| Return `HttpVerbEnum.GET`. | ||
|
|
||
| ## Raises | ||
|
|
||
| None | ||
| """ | ||
| return HttpVerbEnum.GET |
Empty file.
115 changes: 115 additions & 0 deletions
115
plugins/module_utils/models/fabric_prepare_update/software_update_plan_summary.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,115 @@ | ||
| # Copyright: (c) 2026, Allen Robel (@allenrobel) <arobel@cisco.com> | ||
|
|
||
| # GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) | ||
| """ | ||
| Pydantic models for parsing the ND `softwareUpdatePlan/summary` response. | ||
|
|
||
| The summary endpoint returns the fabric-wide software update plan: every update group with its | ||
| per-switch stage / validate / install status. `nd_fabric_prepare_update` uses these models for the | ||
| pre-flight switch-role check and to poll for staging completion. The models are response-only - | ||
| they parse the wire shape and are never serialized back to ND. | ||
| """ | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| from typing import Literal, TypeAlias | ||
|
|
||
| from ansible_collections.cisco.nd.plugins.module_utils.common.pydantic_compat import Field | ||
| from ansible_collections.cisco.nd.plugins.module_utils.models.nested import NDNestedModel | ||
|
|
||
| # Known per-switch stage / validate statuses on ND 4.2.1. The `str` fallback in `ImageStatus` lets | ||
| # an unrecognized status from a newer ND release parse rather than raising during a poll; type | ||
| # checkers collapse the union to `str`, so the Literal is documentation of the known vocabulary. | ||
| KnownImageStatus: TypeAlias = Literal["none", "inProgress", "success", "failed", "skipped"] | ||
| ImageStatus: TypeAlias = KnownImageStatus | str | ||
|
|
||
|
|
||
| class UpdateGroupWarningModel(NDNestedModel): | ||
| """ | ||
| # Summary | ||
|
|
||
| A single advisory warning ND attaches to an update group in the software update plan summary | ||
| (for example, that upgrading the selected switches would impact every switch of a role). | ||
|
|
||
| ## Raises | ||
|
|
||
| None | ||
| """ | ||
|
|
||
| message: str | None = Field(default=None, alias="message") | ||
| switch_name: str | None = Field(default=None, alias="switchName") | ||
|
|
||
|
|
||
| class SwitchStageStatusModel(NDNestedModel): | ||
| """ | ||
| # Summary | ||
|
|
||
| Per-switch stage / validate status within an update group, as reported by the | ||
| `softwareUpdatePlan/summary` endpoint. | ||
|
|
||
| `image_staged_status` and `image_validated_status` are typed `ImageStatus` | ||
| (`KnownImageStatus | str`): the `Literal` half documents the known vocabulary while the `str` | ||
| fallback lets an unrecognized status from a newer ND release parse rather than raising during | ||
| a poll. | ||
|
|
||
| ## Raises | ||
|
|
||
| None | ||
| """ | ||
|
|
||
| switch_id: str | None = Field(default=None, alias="switchId") | ||
| switch_name: str | None = Field(default=None, alias="switchName") | ||
| switch_role: str | None = Field(default=None, alias="switchRole") | ||
| switch_management_ip: str | None = Field(default=None, alias="switchManagementIP") | ||
| switch_version: str | None = Field(default=None, alias="switchVersion") | ||
| selected_version: str | None = Field(default=None, alias="selectedVersion") | ||
| image_staged_status: ImageStatus | None = Field(default=None, alias="imageStagedStatus") | ||
| image_validated_status: ImageStatus | None = Field(default=None, alias="imageValidatedStatus") | ||
| switch_stage_validate_percentage: int | None = Field(default=None, alias="switchStageValidatePercentage") | ||
|
|
||
|
|
||
| class UpdateGroupStatusModel(NDNestedModel): | ||
| """ | ||
| # Summary | ||
|
|
||
| A single update group's status within the software update plan summary, including its member | ||
| switches and any advisory warnings ND raises for the group. | ||
|
|
||
| The `warnings` and `update_group_switches` lists default to empty: a key absent from the | ||
| response body parses the same as an explicitly empty list, since every consumer treats the two | ||
| identically. | ||
|
|
||
| `update_group_status` is kept as a free-form string (not a `Literal`) for the same reason as | ||
| the per-switch statuses in `SwitchStageStatusModel`: an unrecognized status from a newer ND | ||
| release must parse rather than raise during a poll. Its vocabulary is not lab-confirmed, so no | ||
| known-values alias is provided. | ||
|
|
||
| ## Raises | ||
|
|
||
| None | ||
| """ | ||
|
|
||
| update_group_name: str | None = Field(default=None, alias="updateGroupName") | ||
| update_group_status: str | None = Field(default=None, alias="updateGroupStatus") | ||
| update_type: str | None = Field(default=None, alias="updateType") | ||
| stage_validate_percentage: int | None = Field(default=None, alias="stageValidatePercentage") | ||
| switch_count: int | None = Field(default=None, alias="switchCount") | ||
| warnings: list[UpdateGroupWarningModel] = Field(default_factory=list, alias="warnings") | ||
| update_group_switches: list[SwitchStageStatusModel] = Field(default_factory=list, alias="updateGroupSwitches") | ||
|
|
||
|
|
||
| class SoftwareUpdatePlanSummaryModel(NDNestedModel): | ||
| """ | ||
| # Summary | ||
|
|
||
| Top-level parse of the `softwareUpdatePlan/summary` response. Only the `updateGroups` list is | ||
| modeled; the `softwareUpdateSummary` and `tableHeaders` blocks are ignored (`extra="ignore"`). | ||
| `update_groups` defaults to empty, so a response missing the key parses the same as an | ||
| explicitly empty plan. | ||
|
|
||
| ## Raises | ||
|
|
||
| None | ||
| """ | ||
|
|
||
| update_groups: list[UpdateGroupStatusModel] = Field(default_factory=list, alias="updateGroups") | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.