Skip to content

nd_interface_loopback: policy_type discriminated union - #403

Open
allenrobel wants to merge 40 commits into
developfrom
nd_interface_loopback_policy_type_union
Open

nd_interface_loopback: policy_type discriminated union#403
allenrobel wants to merge 40 commits into
developfrom
nd_interface_loopback_policy_type_union

Conversation

@allenrobel

@allenrobel allenrobel commented Jul 15, 2026

Copy link
Copy Markdown
Collaborator

Formerly stacked on #387 (nd_argument_specs_relocate). #387 has merged, this PR is retargeted to develop and rebased on it, and remote CI now runs (and passes) on this branch.

⚠️ Merge Ordering — hold until #422 merges

Do not merge this PR before #422 (replaced/overridden field-removal detection). Reasoning:

Rebase checklist for this branch after #422 lands:

  • Move the "routeMapTag": "12345" entry into NexusLoopbackPolicyModel.reverse_diff_defaults; keep adminState at the level where it lives post-restructure.
  • Add reverse_diff_defaults tables for the eight new policy models (ipfmLoopback, mplsLoopback, and the six IOS-XE templates), each sourced from its policyType's OpenAPI template schema; lab-verify echoes where possible. Any policy model without a table silently loses replaced/overridden removal detection.
  • Verify the read-tolerant policy models do not retain unknown server-injected keys — any retained extra key would be counted as a removal by the reverse pass and break replaced/overridden idempotency (the 2026-07-18 XE probe found no ND-injected keys in XE policy reads, but re-check on the rebased code).

Related Issue(s)

Relates to #401 (develop ip=IPv4CIDR handling). The bare-IPv4 normalization on this branch overlaps with open PR #402 — whichever lands second will rebase and drop the duplicate change. No issue is closed by this PR.

The design/plan docs that accompanied the POC phase have been removed from the branch and archived outside the repo.

Proposed Changes

Consolidates the loopback interface behind discriminated unions so a single module handles every ND loopback policy template, across both switch OSes.

policy_type discriminated union (NX-OS):

  • Add LoopbackPolicyTypeEnum and a LoopbackPolicyBase; make policy_type required and strict on the write path.
  • Add IpfmLoopbackPolicyModel, MplsLoopbackPolicyModel, and SecondaryIpModel; wire the discriminated union on policy_type.
  • Extend the loopback argspec with policy_type and the per-template union fields.
  • query_all manages loopback, ipfmLoopback, and mplsLoopback via a set filter.
  • Read path tolerates ND-injected policy keys (extra="forbid" was aborting reads); the write path stays strict.
  • Normalize loopback ip to bare IPv4 (ND 4.2.1 rejects CIDR on the wire).
  • Docs for policy_type and the per-template fields.

Required network_os_type discriminator:

IOS-XE loopback surface (choices: [nx-os, ios-xe]):

  • config_data.network_os is an outer discriminated union (NexusLoopbackNetworkOSModel | XeLoopbackNetworkOSModel), each OS branch carrying its own policy_type union: NX-OS keeps loopback/ipfmLoopback/mplsLoopback; IOS-XE adds iosXeLoopback, iosXeLoopbackShutNoshut, iosXeUnderlayLoopback, iosXeInternalLoopback, csrLoopback, csr1kvLoopback (all six real XE templates; userDefined stays excluded on both OSes). Cross-OS mismatches (nx-os + iosXeLoopback) fail structurally in Pydantic — no hand-written cross-check.
  • LoopbackPolicyStrictBase split: extra="forbid" + read-tolerant validator + admin_state shared by all nine branches; NX common fields stay on LoopbackPolicyBase.
  • Six IOS-XE branch models with exact per-template fields (schema-verified); new flat options secondary_ip, enable_pim.
  • query_all filter is the union of the NX and XE policy-type enums (XeLoopbackPolicyTypeEnum).
  • Lab probe (2026-07-18, IOS-XE device in fabric ISN) proved the OpenAPI READ schema's csrIntLoopback never appears on the wire — ND echoes the create-side csrLoopback — so the model uses the single wire-verified name (drift recorded in the team bug-tracker vault: csr-loopback-read-schema-name-drift); the same probe found no ND-injected keys in XE policy reads.
  • DOCUMENTATION/EXAMPLES cover all four states for ios-xe plus a csrLoopback example.
  • XE integration tasks gated on nd_test_xe_switch_ip (skipped unless supplied), including a fabric-equality-guarded mixed NX+XE overridden variant.

Lab-surfaced bug fixes:

  • create_bulk now groups one POST per (switch, policyType) — ND rejects mixed-policyType bulk creates (207 with a failed item, nothing created) — and both create paths validate 207 results[] per item instead of trusting the HTTP code (stopgap until Detect Multi-status per-item failures in NdV1Strategy (#295) #398's NdV1Strategy 207 support; both carry TODO(4.2.1) markers referencing vault notes: bulk-interface-create-rejects-mixed-policy-types and multi-status-207-status-field-inconsistent).
  • Integration loopback IDs renumbered 1xx→2xx: the lab's new multisite topology auto-provisions loopback100 (ND's default multisite loopback ID) on border gateways, colliding with the old reserved range.

Test Notes

  • Integration: nd_interface_loopback integration tests are green against these changes (merged/replaced/overridden/query, including the added policy_type: loopback tasks).
  • Unit: all unit tests green — models (test_loopback_interface.py), orchestrator, and nd_argument_specs suites.
  • 2026-07-16 (network_os_type commits): integration tests re-run green against a live ND 4.2.1 lab with the now-required network_os_type: nx-os in every network_os block; full unit suite (3195 tests) green; validate-modules sanity clean on nd_interface_loopback.py.
  • 2026-07-18 (IOS-XE commits): full unit suite (3208 tests) green; validate-modules clean; black/isort/pylint/mypy clean on changed files; ansible-lint clean on the integration target. Live-lab raw-REST probe verified XE create/read/delete behavior on a real IOS-XE device (intent-only). XE integration run against the live lab: all tests pass (all four states + idempotency on the IOS-XE device, iosXeLoopback and csrLoopback), which also verifies the csr single-name decision on the full deploy path — the deployed read echoes csrLoopback, not the read-schema's csrIntLoopback. The first run surfaced two real module bugs (mixed-policyType bulk-create rejection masked by unchecked 207s) — fixed with TDD unit coverage, then the re-run went green. (No CI on stacked PRs — local verification per team convention.)

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

@allenrobel allenrobel self-assigned this Jul 15, 2026
@allenrobel
allenrobel force-pushed the nd_argument_specs_relocate branch from 67358f0 to 2afa387 Compare July 16, 2026 00:46
@allenrobel allenrobel changed the title [POC] nd_interface_loopback: policy_type discriminated union nd_interface_loopback: policy_type discriminated union Jul 19, 2026
@allenrobel
allenrobel marked this pull request as ready for review July 19, 2026 17:47
@allenrobel allenrobel added the ready for review Submitter is requesting a PR review label Jul 19, 2026
Base automatically changed from nd_argument_specs_relocate to develop July 19, 2026 18:04
@allenrobel
allenrobel force-pushed the nd_interface_loopback_policy_type_union branch from e4da480 to 78cb3f2 Compare July 19, 2026 18:17
Comment thread plugins/module_utils/models/interfaces/loopback_interface.py
Comment thread plugins/module_utils/orchestrators/loopback_interface.py
Comment thread plugins/module_utils/orchestrators/loopback_interface.py Outdated
Comment thread plugins/module_utils/orchestrators/loopback_interface.py Outdated
Comment thread plugins/module_utils/orchestrators/loopback_interface.py Outdated
Comment thread plugins/module_utils/orchestrators/loopback_interface.py
Comment thread plugins/module_utils/orchestrators/loopback_interface.py
Comment thread plugins/module_utils/orchestrators/loopback_interface.py
allenrobel added a commit that referenced this pull request Jul 21, 2026
…lasses (#403)

Review feedback from @akinross:

- Rename LoopbackPolicyBase/LoopbackPolicyModel to NexusLoopbackPolicyBase/
  NexusLoopbackPolicyModel so NX-OS-specific classes carry an OS prefix like
  the Xe* classes do (Nexus chosen to match NexusLoopbackNetworkOSModel).
  Ipfm/Mpls/Csr* classes keep their names - they already mirror their wire
  policyType discriminator, which is OS-unambiguous.
- Expose policy_type as a property on LoopbackInterfaceModel; drop the
  orchestrator's getattr-chain _extract_policy_type static method.
- Extract bulk-create group construction into
  _group_by_switch_and_policy_type (the mixed-policy-type TODO marker moves
  with it).
- Replace the group tuples with frozen slotted dataclasses
  _BulkCreateGroupKey/_BulkCreateItem for readability.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BHGFDqqU37PRqNHhKw1FCQ
Copilot AI review requested due to automatic review settings July 21, 2026 16:07

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

This PR expands nd_interface_loopback from a single NX-OS loopback template into a discriminated-union driven implementation that supports multiple NX-OS loopback policy templates and a full IOS-XE loopback surface, while hardening create/bulk-create behavior against ND’s per-item 207 Multi-Status failure reporting and policyType-mixing limitations.

Changes:

  • Introduces required discriminators network_os_type (outer union) and policy_type (policy union) and adds per-template models/argspec/docs to support NX-OS + IOS-XE loopback templates in one module.
  • Normalizes loopback IPv4 ip input to a bare host address for ND 4.2.1 wire compatibility (accepts CIDR input, sends bare IPv4).
  • Updates the orchestrator to (a) validate per-item DATA.results[] statuses for create/create_bulk and (b) group bulk creates by (switch_id, policy_type).

Reviewed changes

Copilot reviewed 16 out of 16 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
tests/unit/module_utils/orchestrators/test_loopback_interface.py Updates orchestrator unit tests for new model types, adds coverage for per-item 207 failures and bulk-create grouping by policy type, and expands query_all filtering expectations.
tests/unit/module_utils/models/test_loopback_interface.py Adds extensive unit coverage for the new discriminated unions (NX + XE) and for IPv4 host normalization + strict/read-tolerant policy validation.
tests/unit/module_utils/fixtures/fixture_data/test_loopback_interface.json Extends fixtures to cover new query_all filtering cases and the new 207-results failure shapes used in create/create_bulk tests.
tests/integration/targets/nd_interface_loopback/vars/main.yaml Updates reserved loopback ID ranges, makes network_os_type/policy_type explicit, and adds IOS-XE test variables gated by nd_test_xe_switch_ip.
tests/integration/targets/nd_interface_loopback/tasks/main.yaml Conditionally includes IOS-XE integration tasks when nd_test_xe_switch_ip is provided.
tests/integration/targets/nd_interface_loopback/tasks/setup.yaml Renumbers reserved loopback IDs used for integration setup cleanup.
tests/integration/targets/nd_interface_loopback/tasks/merged.yaml Updates merged-state integration coverage to include required discriminators and new ID ranges.
tests/integration/targets/nd_interface_loopback/tasks/replaced.yaml Updates replaced-state tasks for required discriminators and renumbered IDs.
tests/integration/targets/nd_interface_loopback/tasks/overridden.yaml Updates overridden-state tasks for required discriminators, renumbered IDs, and system-loopback filtering assumptions.
tests/integration/targets/nd_interface_loopback/tasks/deleted.yaml Updates deleted-state tasks for renumbered IDs and the revised vars structure.
tests/integration/targets/nd_interface_loopback/tasks/xe.yaml Adds IOS-XE integration coverage across all states plus idempotency, including mixed NX+XE overridden coverage when fabrics match.
plugins/modules/nd_interface_loopback.py Updates module docs/examples to expose and describe the new discriminators and per-template policy fields across NX-OS and IOS-XE.
plugins/module_utils/orchestrators/loopback_interface.py Hardens create/create_bulk against per-item failures and changes bulk-create grouping to (switch_id, policy_type); expands query_all filtering to NX + XE managed policy types.
plugins/module_utils/models/types.py Adds IPv4Host type to normalize IPv4 to bare host form while accepting CIDR input.
plugins/module_utils/models/interfaces/loopback_interface.py Reworks loopback models into nested discriminated unions (network OS + policy type), adds strict/read-tolerant behavior for policy objects, and exposes required discriminators via argspec.
plugins/module_utils/models/interfaces/enums.py Adds managed loopback policy-type enums for NX-OS and IOS-XE used by orchestrator filtering and documentation.

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

Comment thread plugins/modules/nd_interface_loopback.py
@allenrobel
allenrobel requested a review from akinross July 21, 2026 16:21
allenrobel added a commit that referenced this pull request Jul 22, 2026
Copilot review: the ip option description did not mention that CIDR
input (e.g. 10.1.1.1/32) is accepted but normalized to the bare address
on the wire and in returned state (IPv4Host, models/types.py).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WqAuV2pWYJTno2bfdcNZCm
akinross
akinross previously approved these changes Jul 22, 2026
allenrobel added a commit that referenced this pull request Jul 22, 2026
…lasses (#403)

Review feedback from @akinross:

- Rename LoopbackPolicyBase/LoopbackPolicyModel to NexusLoopbackPolicyBase/
  NexusLoopbackPolicyModel so NX-OS-specific classes carry an OS prefix like
  the Xe* classes do (Nexus chosen to match NexusLoopbackNetworkOSModel).
  Ipfm/Mpls/Csr* classes keep their names - they already mirror their wire
  policyType discriminator, which is OS-unambiguous.
- Expose policy_type as a property on LoopbackInterfaceModel; drop the
  orchestrator's getattr-chain _extract_policy_type static method.
- Extract bulk-create group construction into
  _group_by_switch_and_policy_type (the mixed-policy-type TODO marker moves
  with it).
- Replace the group tuples with frozen slotted dataclasses
  _BulkCreateGroupKey/_BulkCreateItem for readability.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BHGFDqqU37PRqNHhKw1FCQ
allenrobel added a commit that referenced this pull request Jul 22, 2026
Copilot review: the ip option description did not mention that CIDR
input (e.g. 10.1.1.1/32) is accepted but normalized to the bare address
on the wire and in returned state (IPv4Host, models/types.py).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WqAuV2pWYJTno2bfdcNZCm
@allenrobel
allenrobel force-pushed the nd_interface_loopback_policy_type_union branch from dd5cfc3 to 137bc8d Compare July 22, 2026 16:12
allenrobel and others added 22 commits July 27, 2026 13:02
Per team decision, the platform/OS selector is a mandatory argspec
parameter for now so interface parameters validate before any
controller call; a FabricContext.get_platform_type() fallback comes
later when the parameter becomes optional. Mirrors the ND schema,
where networkOSType is a required create-side discriminator.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014aJ3Y2TJEAqJZBeUKdUnGa
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ypeEnum

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…-18)

ND 4.2.1 echoes csrLoopback on reads despite the OpenAPI read schema
listing csrIntLoopback; drop the defensive dual-name tolerance. Drift
recorded in the bug-tracker vault (csr-loopback-read-schema-name-drift).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… cleanups

- 00770c: nest networkOSType inside networkOS; policy objects mirror the
  2026-07-18 lab GET key set; honest TEST_NOTES
- XeLoopbackNetworkOSModel: restore not-frozen rationale comment
- 00770: assert result length, not just the policy-type set
- argspec: secondary_ip/enable_pim placed to match DOCUMENTATION order
- integration: XE gate rejects empty nd_test_xe_switch_ip
- csr1kv extra_config description aligned with siblings
- query_all: hoist invariant managed_policy_types out of the switch loop

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…back100)

The lab's new multisite topology (S1_BG1 <-> WAN1/ISN) auto-provisions
loopback100 (policyType multisiteLoopback, ND's default multisite loopback
ID) on the border gateway. query_all rightly filters system-provisioned
types, so the module cannot see the name squatter and the create 500s
("loopback100 is already in use"). Move the reserved test range clear of
ND defaults: IDs 200-209 (+299 never-exists probe), IPs 10.100.20x.x.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ND rejects mixed-policyType bulk creates (207 with a failed item and
nothing created) and the module trusted the 207, reporting changed while
creating nothing - surfaced by the XE integration idempotency test.
Group one POST per (switch, policyType) and fail on any results[] item
whose status is not exactly 'success'. Scoped to the loopback
orchestrator; PR #398's NdV1Strategy 207 support supersedes the check.
Vault: bulk-interface-create-rejects-mixed-policy-types,
multi-status-207-status-field-inconsistent.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The in-repo design and plan docs were POC-phase working artifacts; per the
PR description's graduation note they are removed before opening for team
review. Archived locally at ~/pr/403 for historical reference.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…lasses (#403)

Review feedback from @akinross:

- Rename LoopbackPolicyBase/LoopbackPolicyModel to NexusLoopbackPolicyBase/
  NexusLoopbackPolicyModel so NX-OS-specific classes carry an OS prefix like
  the Xe* classes do (Nexus chosen to match NexusLoopbackNetworkOSModel).
  Ipfm/Mpls/Csr* classes keep their names - they already mirror their wire
  policyType discriminator, which is OS-unambiguous.
- Expose policy_type as a property on LoopbackInterfaceModel; drop the
  orchestrator's getattr-chain _extract_policy_type static method.
- Extract bulk-create group construction into
  _group_by_switch_and_policy_type (the mixed-policy-type TODO marker moves
  with it).
- Replace the group tuples with frozen slotted dataclasses
  _BulkCreateGroupKey/_BulkCreateItem for readability.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BHGFDqqU37PRqNHhKw1FCQ
Copilot review: the ip option description did not mention that CIDR
input (e.g. 10.1.1.1/32) is accepted but normalized to the bare address
on the wire and in returned state (IPv4Host, models/types.py).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WqAuV2pWYJTno2bfdcNZCm
…#403)

With validate_assignment=True, every setattr (e.g. inside
NDBaseModel.merge()) re-runs the mode="before" model validator over the
model's full __dict__. LoopbackPolicyStrictBase.strip_none_valued_keys
dropped ALL None-valued keys - declared fields included - so the rebuilt
__dict__ lost every unset field and the next getattr mid-merge raised
AttributeError. Merging a newly-set field onto an existing model whose
corresponding field was None (e.g. adding a description to a loopback
created without one) crashed.

Declared keys now survive the strip even when None (a declared None
never trips extra="forbid"); only undeclared None keys are dropped, and
read mode still strips undeclared keys regardless of value.

Found live by the nd_interface_ethernet_routed integration run
(2026-07-27), which copies this base's pattern; loopback lab flows never
merged a new field onto an existing-None field, so the bug was latent
here. Regression test test_loopback_interface_00740 pins the merge.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

mode: Literal["managed"] = Field(default="managed", alias="mode", frozen=True)
network_os: LoopbackNetworkOSModel = Field(alias="networkOS")
network_os: NexusLoopbackNetworkOSModel | XeLoopbackNetworkOSModel = Field(alias="networkOS", discriminator="network_os_type")

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: merged cannot transition between loopback policy types

Issue

The new discriminated union represents different loopback policy types with
different concrete Pydantic classes, but NDBaseModel.merge() rejects a nested
model when its concrete type differs from the existing model.

Evidence

  • loopback_interface.py line 367
    adds the outer discriminated union; its nested policy field similarly
    selects a different concrete model for each policy_type.

  • base.py lines 269-293
    recursively merges direct nested models and raises TypeError when their
    concrete classes differ.

  • An exact-head state: merged reproduction for an existing loopback policy
    and proposed mplsLoopback policy failed before any API request with:

    TypeError: Cannot merge MplsLoopbackPolicyModel into NexusLoopbackPolicyModel.
    

Practical example

The same loopback interface can validly move between these policy templates:

State network_os_type policy_type Concrete policy model
Existing controller state nx-os loopback NexusLoopbackPolicyModel
Desired merged state nx-os mplsLoopback MplsLoopbackPolicyModel

NDStateMachine identifies the same interface and invokes
existing.merge(proposed). The merge descends into policy, sees the two
different classes, and raises instead of replacing the discriminated-union
branch with the requested policy.

Existing PR overlap

No matching existing PR comment found. The 19 current inline comments discuss
model naming, bulk grouping, and IPv4 normalization, but not merged-state
behavior when a discriminator changes.

Existing open issue overlap

No matching open issue found. Issue
#386 tracks
policy-aware merging for nested lists and dictionaries, not replacement of a
direct discriminated-union model when its type changes. Issue
#452 tracks the broader
loopback policy expansion but does not identify this concrete transition
failure.

Impact

A valid state: merged policy transition fails locally before Nexus Dashboard
receives a request. Users cannot reconcile an existing loopback to another
supported policy type through the normal merged workflow, despite both policy
types being accepted by the module contract.

Suggested fix

When a direct nested field is a discriminated union and its discriminator
changes, replace that branch with the proposed model instead of recursively
merging incompatible classes. Preserve recursive merge behavior for matching
types, and add end-to-end merged-state tests for policy-type and network-OS
transitions in both check mode and normal execution.


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.

@allenrobel not sure if we should support a transition like this so I am fine if you intentially reject this kind of transition. Let me know what you think

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.

Confirmed — reproduced exactly as described; the merge recursion hits the two concrete policy classes and fails before any API request.

Decision: intentionally reject the transition, per your suggestion. A field-by-field merge across two policy templates is undefined anyway — the branches have disjoint field sets — so "Reject policy_type transitions in merged state with an actionable error (#403 review)" replaces the internal TypeError: Cannot merge MplsLoopbackPolicyModel into NexusLoopbackPolicyModel with:

Cannot change policy_type from 'loopback' to 'mplsLoopback' with state: merged. Use state: replaced (or delete and re-create the resource) to change policy_type.

The guard keys off the union's discriminator, so it also covers the networkOSType level and any future discriminated unions in other interface models; non-union type mismatches keep the existing TypeError.

The transition itself remains available via state: replaced/overridden — those paths never call merge(); the proposed model goes to ND wholesale as a PUT. Lab-verified (intent-only): a PUT changing an existing loopback's policy from loopback to ipfmLoopback returned 204 and the GET echoes the new policy. Caveat: the lab currently runs ND 4.3.1.145 (dev build), not the 4.2.1 this PR targets. Same probe surfaced two 4.3.1-specific behaviors unrelated to this thread — mplsLoopback PUT requires a populated MPLS_LOOPBACK_IP_POOL even with an explicit ip, and the create-side gateway now enforces the discriminator enum (schema-rejecting mplsLoopback create, which 4.2.1 accepts) — tracking those separately.

…or (#403 review)

NDBaseModel.merge() raised a bare TypeError naming internal Pydantic classes
when a proposed nested model's concrete type differed from the existing one.
For discriminated-union fields (policy_type, network_os_type) this is a user
input scenario, not an internal error: merging across two union branches is
undefined because the branches have disjoint field sets.

Add a discriminator-aware pre-check in the merge recursion loop: when the
field is a discriminated union and the discriminator value would change,
raise ValueError with a message naming both values and pointing at
state: replaced. Non-union type mismatches keep the existing TypeError.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

NaC_01 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+] Extend nd_interface_loopback for NX-OS and IOS-XE policy types

5 participants