Skip to content

ND Manage VRFs & Networks: Add Internal Staged Workflow - #509

Open
AKDRG wants to merge 10 commits into
CiscoDevNet:developfrom
AKDRG:vrf_network_overridden_no_delete_internal
Open

ND Manage VRFs & Networks: Add Internal Staged Workflow#509
AKDRG wants to merge 10 commits into
CiscoDevNet:developfrom
AKDRG:vrf_network_overridden_no_delete_internal

Conversation

@AKDRG

@AKDRG AKDRG commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Related Issue(s)

Related to:

Proposed Changes

Add a private/internal _staged workflow state for nd_manage_vrfs and nd_manage_networks.

The new workflow supports staged configuration removal where omitted resources should have attachments detached, but the omitted VRFs or Networks themselves should not be deleted.

Key behavior:

  • _staged follows overridden attachment scope.
  • Current VRF/Network attachments are queried across the full current resource set.
  • Attachments omitted from desired config are detached.
  • Generic CRUD execution is mapped to replaced, preventing overridden-style delete requests.
  • Deployment is always suppressed for _staged.
  • Delete deploy payloads, pending deploy fallback, and delete-readiness wait flows are not invoked.
  • The implementation stays localized to the VRF/Network state machines and attachment managers.

Test Notes

Commands run:

PYTHONPATH=/Users/achengam/Documents/Ansible_Dev/NDNetwork_empty_interfaces
/Users/achengam/.pyenv/versions/ndfclab/bin/python
-m pytest tests/unit/module_utils/orchestrators/test_networks.py -k staged -q

Result:

2 passed, 80 deselected

PYTHONPATH=/Users/achengam/Documents/Ansible_Dev/NDNetwork_empty_interfaces
/Users/achengam/.pyenv/versions/ndfclab/bin/python
-m pytest tests/unit/module_utils/orchestrators/test_vrf_workflow_coordinator.py -k staged -q

Result:

2 passed, 54 deselected

black -l 159 --check
plugins/module_utils/orchestrators/network_attachment_manager.py
plugins/module_utils/orchestrators/network_state_machine.py
plugins/module_utils/orchestrators/vrf_attachment_manager.py
plugins/module_utils/orchestrators/vrf_state_machine.py
plugins/modules/nd_manage_networks.py
plugins/modules/nd_manage_vrfs.py
tests/unit/module_utils/orchestrators/test_networks.py
tests/unit/module_utils/orchestrators/test_vrf_workflow_coordinator.py

Result:

8 files would be left unchanged.

Cisco Nexus Dashboard Version

Workflow-layer change. No new API endpoint schema dependency introduced.

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

@AKDRG
AKDRG force-pushed the vrf_network_overridden_no_delete_internal branch from 661a7f6 to 47d5826 Compare August 14, 2026 10:32
@mtarking mtarking added 2.0.0 Release 2.0.0 nac-0.0.1 NaC ND release 0.0.1 labels Aug 14, 2026
@AKDRG AKDRG self-assigned this Aug 14, 2026
@AKDRG AKDRG added the ready for review Submitter is requesting a PR review label Aug 14, 2026
Comment thread plugins/modules/nd_manage_vrfs.py Outdated
- V(deleted) removes specified VRFs (or all if config is empty).
- V(gathered) returns current VRF state (the only state allowed on child
fabrics when targeted directly).
- V(staged) is an internal/private workflow state. It follows

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.

Why is staged called “internal/private” while also being included in the module’s public choices?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Since "state" comes under the argspec, the choices for the state needs documentation else get's flagged by validate-module sanity.

@akinross akinross Aug 25, 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.

did you consider exposing this "feature" through an undocumented environment variable that can be specified in at task level? this way the documentation would remain clean

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.

@AKDRG just remove the usage of internal/private as last conversation on this was to fully document it as a state.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

My bad, was thinking we needed to document it as an internal state. Amended the wordings now.

Comment thread plugins/modules/nd_manage_vrfs.py Outdated
- V(deleted) removes specified VRFs (or all if config is empty).
- V(gathered) returns current VRF state (the only state allowed on child
fabrics when targeted directly).
- V(staged) is an internal/private workflow state. It follows

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 add example for this state with description what it is actually doing?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Since it's an private state and not intended for customer use cases, I guess we need not document examples.

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.

@AKDRG customers can use it since we'll support it as a state. Original intention was undocumented state, but it's documented and in arg spec now.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Got it, fixed!

Comment thread plugins/modules/nd_manage_vrfs.py Outdated
- V(deleted) removes specified VRFs (or all if config is empty).
- V(gathered) returns current VRF state (the only state allowed on child
fabrics when targeted directly).
- V(staged) is an internal/private workflow state. It follows

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 am a bit confused on what staged is supposed to be doing, could you clarify my overall understanding which might be wrong.

Should state: staged use full overridden attachment semantics while running definition CRUD as replaced and suppressing deployment, so that:

  • Removing an attachment from a VRF that remains in config submits an undeployed attach:false; the current code appears to skip this attachment removal.
  • Omitting an existing VRF entirely submits undeployed detach requests for its current attachments while retaining the VRF definition; the current code appears to handle this case as expected.
  • Adding an attachment to an existing VRF submits an undeployed attach:true; the current code appears to skip this attachment addition.
  • Creating a new VRF with attachments creates the VRF definition and submits undeployed attach:true requests; the current code appears to create the definition but skip its attachments.

It appears to me we that we do not test all of these scenarios but could have missed this.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Hey Akini,
Thanks a lot for your feedback and testing. You had identified a critical bug here. I have fixed the gaps that were left out due to my commit miss and we are fully covered now.

  1. Removing an attachment from a VRF that remains in config submits an undeployed attach:false; the current code appears to skip this attachment removal.

Not skipped, attachment is removed with attach:false. Not un-deployed -> Staged.

  1. Adding an attachment to an existing VRF submits an undeployed attach:true; the current code appears to skip this attachment addition.

Not skipped, attachment is posted with attach:true. Not deployed -> Staged.

  1. Creating a new VRF with attachments creates the VRF definition and submits undeployed attach:true requests; the current code appears to create the definition but skip its attachments.

Not skipped, attachment is posted with attach:true. Not deployed -> Staged.

Added the integration tests too!

Thanks,
Akshay

@AKDRG
AKDRG force-pushed the vrf_network_overridden_no_delete_internal branch from 636838b to 0bcb7b5 Compare August 26, 2026 18:05

@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, all on the staged workflow plumbing in vrf_state_machine.py (the first is a functional bug; the network-side twin of each is noted inline where it applies).

🤖 Generated with Claude Code

omitted_vrf_names,
attachment_details=omitted_attachment_details,
)
if module_args.get("state") == "staged":

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 staged short-circuit comes after the unconditional self.coordinator._ensure_vrfs_have_no_networks(...) call at the top of _prepare_overridden_deletions (line 378). That guard exists to "Fail before VRF deletion when networks still reference the VRFs" (vrf_dependency_checker.py::ensure_no_networks) and calls fail_json("Cannot delete VRF(s) because network(s) still reference them ...").

Since staged never deletes ("omitted VRFs are detached but not removed"), a staged task that omits a VRF still carrying networks hard-fails with a deletion error instead of performing the legitimate detach-only reconciliation. Suggest skipping the dependency check when module_args.get("state") == "staged", i.e. hoisting this guard above the _ensure_vrfs_have_no_networks call.

Note the new unit test (test_vrf_staged_detaches_omitted_vrfs_without_running_overridden_crud_delete) stubs _ensure_vrfs_have_no_networks as a call logger, so this path is not exercised by the added tests. The network twin (_ensure_networks_have_no_networks) is currently a no-op placeholder, so only the VRF side fails today — but the same reordering there would keep the two symmetric if that checker is ever implemented.

Comment on lines +288 to +311
@staticmethod
def _crud_module_args(module_args: dict) -> dict:
"""Return module args for the generic CRUD state machine."""
if module_args.get("state") != "staged":
return module_args
crud_args = dict(module_args)
crud_args["state"] = "replaced"
return crud_args

@staticmethod
def _query_module_args(module_args: dict) -> dict:
"""Return module args for the current-state query phase."""
if module_args.get("state") != "staged":
return module_args
query_args = dict(module_args)
query_args["state"] = "overridden"
return query_args

@staticmethod
def _prepare_crud_state(sm: Any, requested_state: str) -> None:
"""Switch staged workflows to replacement CRUD after query."""
if requested_state != "staged":
return
sm.state = "replaced"

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.

These three helpers (_crud_module_args, _query_module_args, _prepare_crud_state) are byte-for-byte identical to the copies added in network_state_machine.py (lines 293-316), and the two classes share no base. This is the same two-independent-copies-may-drift situation raised on PR #505, which was resolved there by extracting the shared attachment_vpc_peer_expander.py. Consider the same treatment here — a small shared helper module or mixin — so a future change to the staged state mapping lands in one place instead of two.

desired_vrf_names = self.coordinator._configured_vrf_names(config)

sm, original_config, original_state = self.coordinator._new_state_machine(module_args, strategy)
sm, original_config, original_state = self.coordinator._new_state_machine(self._query_module_args(module_args), strategy)

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.

Because _query_module_args remaps state to "overridden" before _new_state_machine constructs the NDStateMachine, results.state (documented in rest/results.py as "The Ansible state for the current task") is captured as "overridden" for the whole staged run, and every API call's metadata["state"] in the -vvv api_metadata output reports overridden rather than staged. Metadata-only (requests, diff, and changed are unaffected), but it will mislead anyone debugging a staged task from verbose output. Worth restoring the user-requested state on sm.results after construction (e.g. in _prepare_crud_state). Same applies to the network twin.

sm, original_config, original_state = self.coordinator._new_state_machine(module_args, strategy)
sm, original_config, original_state = self.coordinator._new_state_machine(self._query_module_args(module_args), strategy)
try:
self._prepare_crud_state(sm, state)

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.

Minor/latent: NDStateMachine.__init__ threads context={"state": self.state} into pydantic validation "so models can apply state-aware validation", so sm.proposed here was validated under "overridden"; _prepare_crud_state then flips sm.state to "replaced" without re-validating. No current VRF/Network model reads the validation context, so this has no effect today — flagging it so a future state-aware validator doesn't silently validate staged runs under the wrong state. A comment noting the assumption would be enough.

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

Labels

2.0.0 Release 2.0.0 nac-0.0.1 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.

4 participants