nd_interface_subinterface_managed: add the IOS-XE iosXeSubinterface and iosXeSubinterfaceShutNoshut policy types - #572
allenrobel wants to merge 1 commit into
Conversation
a16b888 to
1f8a004
Compare
9863d64 to
1fdb6fc
Compare
1f8a004 to
bd7c07c
Compare
1fdb6fc to
f02099c
Compare
…nd iosXeSubinterfaceShutNoshut policy types Extends nd_interface_subinterface_managed with the IOS-XE branch on the discriminated-union pattern of the ethernet (#558), port-channel (#570) and SVI (#571) modules (issue #541): - config_data.network_os is a union on network_os_type, injected as nx-os when omitted so existing playbooks are unchanged. The NX-OS SubinterfaceManagedPolicyModel moves onto InterfacePolicyStrictBase, so cross-platform fields fail before any controller call. - The IOS-XE branch is a policy_type union of XeSubinterfacePolicyModel (iosXeSubinterface, injected when omitted; VLAN tag, L3 addressing, VRF, with the ios_xe_int_subintf ranges: vlanId 1-4094, ipv6Prefix 64-127, description 1-200) and XeSubinterfaceShutNoshutPolicyModel (admin state only). Both templates are identical on ND 4.2.1 and 4.3.1; the ND-internal iosXeInternalSubinterface and userDefined are left out. - interface_name accepts the Catalyst parent families: an abbreviated or lowercase prefix that matches exactly one canonical name (Ethernet, Port-channel, GigabitEthernet, TenGigabitEthernet, TwentyFiveGigE, ...) is expanded to it, anything else passes through verbatim instead of being rejected. ND removes an IOS-XE subinterface from the switch only under its canonical spelling (TODO(4.2.1) xe-subinterface-remove-leaves-switch-interface), so the expansion is what keeps state deleted / overridden working. - create_bulk adopts bulk_create_groups (one POST per switch and policy type, #409): ND rejects an interfaces[] array that mixes the two IOS-XE policy types. query_all manages the three policy types and tolerates the policy-less records a Catalyst switch list carries. - The NX-OS branch now always emits the int_subif template default mtu 9216 on the wire (payload_defaults): ND 4.3.1 refuses a create that omits it where 4.2.1 defaulted it (TODO(4.3.1) ethernet-create-required-fields-431, the #564 class). - policy_type is visible in before/after output for both branches; the argspec gains network_os_type and policy_type. - Module docs gain per-option applicability lines, the IOS-XE VRF guidance (omit vrf_interface for the global table; the Catalyst has no VRF named default) and the controller-required ip / vlan_id on an iosXeSubinterface create, plus IOS-XE examples. - Unit tests for both branches, the union injection, cross-branch rejection, the XE ranges, the parent-name expansion, the mtu default, the grouped bulk create and the Catalyst switch-list shapes; xe.yaml integration scenario gated on nd_test_xe_switch_ip that converts the parent port to an IOS-XE routed host, waits for discovery, runs every state and resets the parent afterwards. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018LdsxVhpcrri7nbHXQxCcR
bd7c07c to
8d5c788
Compare
f02099c to
d0f3491
Compare
| deploys for all created subinterfaces for later bulk execution via `deploy_pending`. | ||
| Create multiple managed L3 subinterfaces in bulk. Groups subinterfaces by `(switch, policyType)` through the shared | ||
| `bulk_create_groups` (issue #409) and sends one POST per group with all of its subinterfaces in the `interfaces` array: ND | ||
| rejects an array that mixes policy types, which a Catalyst carrying both `iosXeSubinterface` and `iosXeSubinterfaceShutNoshut` |
There was a problem hiding this comment.
Medium: Deploy successful subinterfaces from a mixed HTTP 207 response
Issue
When one bulk-create request contains both successful and failed subinterfaces, _request() raises as soon as it detects the failed item.
Deployment entries are queued only after _request() returns successfully. Therefore, subinterfaces ND accepted in the same response are never queued for deployment.
Evidence
subinterface_managed_interface.py:154-183performs the bulk request before queueing deployments.- A mixed HTTP 207 causes
_request()to raise before the queueing loop runs. - Existing tests do not cover success and failure inside the same response group.
Existing PR overlap
No matching comment or review thread exists on #572.
The same response-handling pattern was previously identified in #570 and #571, but #572 has its own orchestrator implementation and requires its own fix or a shared correction.
Existing open issue overlap
- [ND 4.x] Extend nd_interface_subinterface_managed for NX-OS and IOS-XE policy types #541 owns the managed-subinterface acceptance criteria.
- Consolidate per-item 207 Multi-Status detection: retire remaining bespoke orchestrator detectors onto NdV1Strategy #397 is related to HTTP 207 response classification.
Impact
ND can store a successfully accepted subinterface while the switch never receives it. A retry may find matching ND intent and report no change, leaving ND and the switch inconsistent.
Practical example
A task creates two subinterfaces with deployment enabled:
cisco.nd.nd_interface_subinterface_managed:
fabric_name: CAMPUS1
config:
- switch_ip: 10.122.84.182
interface_name: GigabitEthernet1/0/1.100
config_data:
network_os:
network_os_type: ios-xe
policy:
policy_type: iosXeSubinterface
vlan_id: 100
ip: 10.100.0.1
prefix: 24
- switch_ip: 10.122.84.182
interface_name: GigabitEthernet1/0/1.200
config_data:
network_os:
network_os_type: ios-xe
policy:
policy_type: iosXeSubinterface
vlan_id: 200
ip: 10.200.0.1
prefix: 24
config_actions:
deploy: true
state: mergedND returns:
{
"results": [
{
"name": "GigabitEthernet1/0/1.100",
"status": "success"
},
{
"name": "GigabitEthernet1/0/1.200",
"status": "failed",
"message": "VLAN is already in use"
}
]
}Current behavior:
.100 intent: accepted by ND
.200 intent: rejected
task: fails
.100 deployment: never queued
switch: .100 remains absent
Expected behavior:
.100 intent: accepted and queued for deployment
.200 intent: reported as failed
task: still fails honestly
failure finalizer: deploys accepted .100
Suggested fix
When _request() raises for a fresh HTTP 207 response:
- Inspect only that response per-item results.
- Correlate exact successful names with the requested models.
- Queue deployment for those accepted items.
- Re-raise the original failure.
- Allow the existing failure finalizer to deploy the accepted subset.
Add a test with one success and one failure inside the same (switch, policyType) group.
ND version applicability
The defect applies to the per-item HTTP 207 behavior used by both ND 4.2.1 and ND 4.3.1; it is not a version-specific schema difference.
| - name: "XE DISCOVERY WAIT: Wait until ND has discovered .100, .101 and .102 on the Catalyst" | ||
| cisco.nd.nd_rest: | ||
| path: "/api/v1/manage/fabrics/{{ test_xe_fabric_name }}/switches/{{ xe_switch_id }}/interfaces" | ||
| method: get |
There was a problem hiding this comment.
Medium: Reject subinterface removal until ND discovery is complete
Issue
A newly created IOS-XE subinterface may temporarily have a missing or unknown operData.operationalStatus.
The module currently allows removal during this discovery window. ND can delete its intent without generating the corresponding Catalyst-side removal, leaving the subinterface configured on the switch.
Evidence
subinterface_managed_interface.py:136-152queues individual removals without checking discovery status.subinterface_managed_interface.py:185-199does the same for bulk removal.- This integration scenario polls for discovery before cleanup, but module users do not receive that protection.
Existing PR overlap
No matching comment or review thread exists on #572. Equivalent findings exist for the port-channel and SVI modules, but this PR has a separate subinterface removal implementation.
Existing open issue overlap
- [ND 4.x] Extend nd_interface_subinterface_managed for NX-OS and IOS-XE policy types #541 owns the managed-subinterface lifecycle requirements.
- Track state: gathered support for NDStateMachine/orchestrator-backed modules #340 tracks future gathered-state support that users could use for explicit polling.
Impact
The Ansible task can report that the subinterface was removed while the physical switch still contains it.
Practical example
Create and deploy the subinterface:
cisco.nd.nd_interface_subinterface_managed:
fabric_name: CAMPUS1
config:
- switch_ip: 10.122.84.182
interface_name: GigabitEthernet1/0/1.100
config_data:
network_os:
network_os_type: ios-xe
policy:
policy_type: iosXeSubinterface
vlan_id: 100
ip: 10.100.0.1
prefix: 24
config_actions:
deploy: true
state: mergedImmediately remove it:
cisco.nd.nd_interface_subinterface_managed:
fabric_name: CAMPUS1
config:
- switch_ip: 10.122.84.182
interface_name: GigabitEthernet1/0/1.100
config_actions:
deploy: true
state: deletedAt that moment ND reports:
{
"interfaceName": "GigabitEthernet1/0/1.100",
"operData": {
"operationalStatus": "unknown"
}
}Current result:
ND intent: removed
Catalyst configuration:
interface GigabitEthernet1/0/1.100
encapsulation dot1Q 100
ip address 10.100.0.1 255.255.255.0
Expected result:
Cannot remove GigabitEthernet1/0/1.100 because ND has not finished
discovering it (operationalStatus=unknown). Retry when the status is
up or down. No changes were made.
Suggested fix
Reuse the raw interface inventory already fetched and cached by the orchestrator. Do not add another GET.
Before queueing any removal:
- Allow
operationalStatus: up. - Allow
operationalStatus: down. - Reject missing,
unknown, or unrecognized values. - Validate all requested removals before queueing any of them.
- Apply the same validation in check mode.
- Do not poll, sleep, or retry inside the module.
Users who want waiting behavior should eventually use a separate gathered-state task with Ansible until and retries.
ND version applicability
This discovery transition affects the IOS-XE lifecycle on both ND 4.2.1 and ND 4.3.1; it is not an OpenAPI schema difference.
| - 802.1Q VLAN tag for the subinterface. | ||
| - Valid range is 2-4094. | ||
| - Valid range is 2-4094 for C(subinterface) and 1-4094 for C(iosXeSubinterface). | ||
| - Required by the controller when creating an C(iosXeSubinterface). |
There was a problem hiding this comment.
Medium: Enforce IOS-XE create requirements before mutation
Issue
The module documentation says a new full iosXeSubinterface requires vlan_id and ip, but the model declares both fields as optional.
When the subinterface does not already exist, incomplete configuration passes model and check-mode validation and reaches the controller. This validation must be create-specific so sparse updates to an existing subinterface remain valid.
Evidence
- The public option documentation here says
vlan_idandipare required by the controller on create. subinterface_managed_interface.py:268-277makesvlan_id,ip, andprefixoptional.- The singular and bulk create paths send payloads without enforcing those requirements.
- The shared create preflight verifies that a policy exists, but not whether the full IOS-XE policy has its create-required fields.
Existing PR overlap
No matching comment or review thread exists on #572.
Existing open issue overlap
Issue #541 owns the IOS-XE managed-subinterface acceptance criteria.
Impact
An incomplete playbook can pass local validation and check mode but fail only after an actual controller request.
Practical example
Assume this subinterface does not exist:
cisco.nd.nd_interface_subinterface_managed:
fabric_name: CAMPUS1
config:
- switch_ip: 10.122.84.182
interface_name: GigabitEthernet1/0/1.100
config_data:
network_os:
network_os_type: ios-xe
policy:
policy_type: iosXeSubinterface
admin_state: true
config_actions:
deploy: false
state: mergedCurrent local payload:
{
"interfaceName": "GigabitEthernet1/0/1.100",
"configData": {
"networkOS": {
"networkOSType": "ios-xe",
"policy": {
"policyType": "iosXeSubinterface",
"adminState": true
}
}
}
}Missing create data:
vlanId
ip
prefix
Current behavior:
model validation: passes
check mode: can report the create as viable
normal mode: sends the incomplete request to ND
controller: rejects the create
Expected behavior:
Cannot create IOS-XE subinterface GigabitEthernet1/0/1.100:
vlan_id, ip, and prefix are required for a new iosXeSubinterface.
No changes were made.
This existing-interface update must remain valid:
policy:
policy_type: iosXeSubinterface
admin_state: falseThe separate admin-only template must also remain valid without addressing fields:
policy:
policy_type: iosXeSubinterfaceShutNoshut
admin_state: falseSuggested fix
Use the state machine already-computed create subset and existing inventory snapshot; do not add another GET.
For an absent interface using the full iosXeSubinterface policy:
- Require
vlan_id. - Require
ip. - Require its paired
prefix. - Aggregate all incomplete create items into one error.
- Apply the same validation in normal and check mode.
Do not apply the requirement to sparse updates of an existing subinterface, state: deleted, or the iosXeSubinterfaceShutNoshut admin-only policy.
Add tests covering incomplete creates, valid full creates, sparse updates, admin-only policies, bulk input, and check-mode parity.
ND version applicability
Neither ND 4.2.1 nor ND 4.3.1 marks these fields as required in the published schema. This requirement comes from the public module documentation and verified controller create behavior, so it should be treated as a live cross-version create requirement rather than an OpenAPI requirement.
| vrf_interface: str | None = Field( | ||
| default=None, alias="vrfInterface", min_length=1, max_length=32, description="Interface VRF name; use `default` for default VRF" | ||
| ) | ||
| ip: str | None = Field(default=None, alias="ip", description="IPv4 address of the subinterface") |
There was a problem hiding this comment.
Medium: Validate IOS-XE IPv4 and IPv6 address syntax locally
Issue
The IOS-XE managed-subinterface model treats ip and ipv6 as unrestricted strings.
Its pair validators only check that an address and prefix appear together. They do not check whether the address is actually valid.
Evidence
subinterface_managed_interface.py:274-277defines both address fields as strings.- Supplying a numeric prefix satisfies the pair validator even when the address is malformed.
- Both ND 4.2.1 and ND 4.3.1 schemas identify these fields as IPv4 and IPv6 values.
Existing PR overlap
No matching comment or review thread exists on #572.
Existing open issue overlap
Issue #541 owns strict validation for the new IOS-XE managed-subinterface policy.
Impact
Invalid addresses pass model validation and check mode. Normal execution discovers the error only after making a controller request.
Practical example
This configuration includes every create-required field, but both addresses are invalid:
cisco.nd.nd_interface_subinterface_managed:
fabric_name: CAMPUS1
config:
- switch_ip: 10.122.84.182
interface_name: GigabitEthernet1/0/1.100
config_data:
network_os:
network_os_type: ios-xe
policy:
policy_type: iosXeSubinterface
vlan_id: 100
ip: 999.999.999.999
prefix: 24
ipv6: not-an-ipv6
ipv6_prefix: 64
config_actions:
deploy: false
state: mergedCurrent behavior:
ip and prefix are both present: passes pair validation
ipv6 and ipv6_prefix are both present: passes pair validation
check mode: can report the configuration as viable
normal mode: sends malformed addresses to ND
controller: rejects the request
Expected behavior:
Invalid IPv4 address for ip: 999.999.999.999
Invalid IPv6 address for ipv6: not-an-ipv6
No changes were made.
A correct configuration should continue to pass:
policy:
policy_type: iosXeSubinterface
vlan_id: 100
ip: 10.100.0.1
prefix: 24
ipv6: 2001:db8:100::1
ipv6_prefix: 64Suggested fix
Use the collection shared strict address types for proposed configuration:
IPv4Hostor the appropriate strict IPv4 host type forip.IPv6Hostor the appropriate strict IPv6 host type foripv6.
Retain the existing address/prefix pair validators.
Add tests proving that malformed IPv4 and IPv6 values fail locally, valid addresses serialize correctly, normal and check mode return the same validation error, controller-response parsing remains tolerant of legitimate echoed values, and no controller GET or mutation is added for this validation.
ND version applicability
Both ND 4.2.1 and ND 4.3.1 specify the corresponding IPv4 and IPv6 formats, so this finding applies equally to both supported versions.
Related Issue(s)
Closes #541
Stacked on #571 (
feat_540_svi_iosxe); refs #409 (grouped bulk create), #564 (4.3.1 create-required template defaults).Proposed Changes
Extends
nd_interface_subinterface_managedwith the IOS-XE branch on the discriminated-union pattern of #558 / #570 / #571.Applicability (ND 4.2.1 and 4.3.1 OpenAPI, identical enum and template fields on both):
iosXeSubinterfaceiosXeSubinterfaceShutNoshutiosXeInternalSubinterfacesubinterfaceInternal)userDefinedconfig_data.network_osis a union onnetwork_os_type(injected asnx-oswhen omitted, existing playbooks unchanged); the NX-OS policy moves ontoInterfacePolicyStrictBase, so cross-platform fields (mtu, routing tag, PIM, Netflow on IOS-XE) fail before any controller call.policy_typeunion ofXeSubinterfacePolicyModel(injected asiosXeSubinterface) andXeSubinterfaceShutNoshutPolicyModel, with the IOS-XE template ranges.interface_nameaccepts the Catalyst parent families: an abbreviated / lowercase prefix that matches exactly one canonical name (Ethernet,Port-channel,GigabitEthernet,TenGigabitEthernet,TwentyFiveGigE, ...) is expanded, anything else passes through verbatim. ND removes an IOS-XE subinterface from the switch only under its canonical spelling, so the expansion is what keepsdeleted/overriddenworking:TODO(4.2.1) xe-subinterface-remove-leaves-switch-interface(vault note written; same class as the ND leaves the IOS-XE port-channel interface on the switch after a port-channel is removed (4.2.1 and 4.3.1) #569 port-channel finding).create_bulkuses the sharedbulk_create_groups(one POST per switch and policy type);query_allmanages the three policy types and tolerates the policy-less records a Catalyst switch list carries.payload_defaults = {"mtu": 9216}because ND 4.3.1 refuses asubinterfacecreate that omitsmtuwhere 4.2.1 defaulted it (TODO(4.3.1) ethernet-create-required-fields-431, the nd_interface_{ethernet_trunk_host,ethernet_routed,ethernet_access (XE),vpc_trunk_host}: ND 4.3.1 rejects create/update bodies that omit fields 4.2.1 defaulted (allowedVlans, mtu) #564 class; vault note extended). Found by the 4.3.1 regression run of the existing NX-OS scenarios.policy_typeis visible in before/after output for both branches; argspec gainsnetwork_os_typeandpolicy_type.vrf_interfacefor the global table; the Catalyst has no VRF nameddefault), the controller-requiredip/vlan_idon aniosXeSubinterfacecreate, IOS-XE examples for merged and deleted.Four questions (no new bulk stage or queue): the only bulk change is the create grouping; a mixed 207 is handled per item by the existing
create_bulk/_requestpath exactly as in #571; the finalizer, except-path reader and remove-side keys are untouched.Test Notes
tests/unit/4886 passed (24 new tests: enums, union dispatch, cross-branch rejection, XE ranges, parent-name expansion, mtu default, grouped bulk create keys, Catalyst list shapes).validate-modules,yamllintpass;ansible-lintpasses on the new scenario; black / isort clean; pylint / mypy report only the pre-existing patterns the sibling orchestrators carry.nd_interface_subinterface_managedfull target (NX-OS scenarios on SITE1 S1_TOR1 / S3_TOR1 withEthernet1/63routedHost andPort-channel10l3Po parents, plus the newxe.yamlon CAMPUS1 C1_LE1 / C3_LE1 Catalyst 9000v, parentGigabitEthernet1/0/2):ok=82 changed=22 failed=0 ignored=2(the two ignored results are the cross-platform negative test)ok=82 changed=22 failed=0 ignored=2xe.yamlconverts the parent to an IOS-XE routed host and waits for discovery (ND checks the parent's DISCOVERED mode, not its intent), runs merged / replaced / overridden / deleted with both policy types, verifies the Catalyst running config is clean afterdeletedwith abbreviated (lowercase) names, and resets the parent to the fabric-default trunk host.ip+vlanIdare required on aniosXeSubinterfacecreate;vrfInterface: defaultfails at deploy on the Catalyst; the only echoed default isadminState: true; mixed policy types in oneinterfaces[]are rejected; a lowercase remove orphans the switch interface (canonical name required).Cisco Nexus Dashboard Version
4.2.1 (also verified on 4.3.1)
Related ND API Resource Category
Checklist
🤖 Generated with Claude Code
https://claude.ai/code/session_018LdsxVhpcrri7nbHXQxCcR