Skip to content

[Tracking] Standalone interface-module defects found during nd_interfaces_workflow testing #533

Description

@mikewiebe

Purpose

This issue tracks problems found in individual cisco.nd interface modules while testing cisco.nd.nd_interfaces_workflow.

The aggregator is the discovery context only. Each finding documents behavior attributable to a standalone interface module or its shared implementation.

Finding conventions

  • Every finding receives a permanent IFACE-### identifier.
  • Every finding is added to the index.
  • Every finding uses visible headings and HTML start/end delimiters.
  • Finding identifiers will not be renumbered.
  • If a finding is moved into a separate GitHub issue, that issue will be linked from the index while the summary remains here.

Initial test environment

  • Ansible Core: 2.16.3
  • Python: 3.12.13
  • cisco.nd: 1.5.0 development checkout
  • Tested branch: interface_aggregator
  • Tested revision: cdcb8de1488e30a635a93c1cfd9444fed9b14da0
  • Nexus Dashboard: 4.2.1 (build_version: 4.2.1.10)
  • NDFC: 12.5.0.475
  • NX-OS version: Not relevant to IFACE-001 because the failure occurs during module planning before any controller mutation or deployment request.

Future findings should document any differing environment details inside their respective finding block.

Finding index

ID Module Summary Status
IFACE-001 cisco.nd.nd_interface_ethernet_access state: deleted silently ignores a named physical Ethernet interface whose current policy is dot1qTunnelHost Open

===== BEGIN FINDING IFACE-001 =====

IFACE-001: nd_interface_ethernet_access silently ignores a delete when the current policy is not accessHost

Module

cisco.nd.nd_interface_ethernet_access

Summary

When cisco.nd.nd_interface_ethernet_access is called with state: deleted for an explicitly named physical Ethernet interface whose current policyType is dot1qTunnelHost, the interface is silently excluded from deletion planning.

The task succeeds with:

changed: false

The requested interface is absent from both before and after, and no interface normalization is planned.

This behavior was discovered through interface-aggregator testing and then reproduced directly with cisco.nd.nd_interface_ethernet_access in check mode. The problem originates in the standalone module's shared Ethernet orchestration path.

Preconditions

  • The target is a physical Ethernet interface.
  • The target is explicitly identified by switch_ip and interface_names.
  • The target is not a port-channel member.
  • The interface is not already at fabric-default configuration.
  • Its current controller state includes:
interfaceType: ethernet
configData:
  mode: dot1qTunnel
  networkOS:
    policy:
      policyType: dot1qTunnelHost

Ansible playbook

The addresses and fabric name below are sanitized.

---
- name: Reproduce cross-policy Ethernet deletion
  hosts: nd_controller
  gather_facts: false

  tasks:
    - name: Reset an explicitly named physical Ethernet interface
      cisco.nd.nd_interface_ethernet_access:
        fabric_name: test_fabric
        config:
          - switch_ip: 192.0.2.10
            interface_names:
              - Ethernet1/40
        config_actions:
          deploy: false
        state: deleted
      check_mode: true
      register: delete_result

    - name: Display the result
      ansible.builtin.debug:
        var: delete_result

Steps to reproduce

  1. Configure a physical Ethernet interface with policyType: dot1qTunnelHost through Nexus Dashboard.
  2. Confirm that the interface is not a port-channel member.
  3. Run the playbook above.
  4. Inspect changed, before, and after.
  5. Observe that the explicitly requested interface is absent from both state collections and no normalization is planned.

Expected behavior

Because the user explicitly named an eligible physical Ethernet interface with state: deleted, the module should select it regardless of its current Ethernet policy family.

In check mode, the module should report a prospective change:

changed: true

During normal execution, it should use the existing normalize/reset implementation to return the interface to the applicable fabric-default physical-interface configuration, currently represented by the trunkHost normalization template.

Existing safeguards must remain enforced, including the protection against normalizing port-channel members. A subsequent identical deletion against an interface already at fabric default must be idempotent.

If cross-policy normalization is intentionally outside the module contract, the module should instead return a clear policy-mismatch error. A successful silent no-op should not occur.

Actual behavior

The direct standalone check-mode invocation completes successfully with:

changed: false

before:
  # Contains unrelated accessHost interfaces only.
  # The explicitly requested Ethernet1/40 is absent.

after:
  # Identical to before.
  # Ethernet1/40 remains absent.

The requested interface is treated as though it does not exist even though it is present in the controller's physical Ethernet inventory.

Suspected cause

EthernetAccessInterfaceOrchestrator._managed_policy_types() returns only:

accessHost

EthernetBaseOrchestrator.query_all() then filters the physical Ethernet inventory using that managed-policy set before deletion planning.

Relevant paths:

plugins/module_utils/orchestrators/ethernet_access_interface.py
    EthernetAccessInterfaceOrchestrator._managed_policy_types()

plugins/module_utils/orchestrators/ethernet_base.py
    EthernetBaseOrchestrator.query_all()

plugins/module_utils/nd_state_machine.py
    NDStateMachine._manage_delete_state()

The standalone delete logic only selects proposed identifiers found in the filtered current-state collection. Because dot1qTunnelHost was removed by query_all(), the explicitly requested interface never reaches delete_bulk() or remove_pending().

The existing delete execution path already supports normalization once an interface reaches it:

plugins/module_utils/orchestrators/ethernet_base.py
    EthernetBaseOrchestrator.delete_bulk()
    EthernetBaseOrchestrator.remove_pending()

Suggested implementation scope

Deletion discovery should retain explicitly named, eligible, non-default physical Ethernet interfaces independently of their current policy family.

The existing policy-family filtering should remain unchanged for:

  • state: merged
  • state: replaced
  • state: overridden

A foreign-policy response cannot simply be deserialized unchanged as EthernetAccessInterfaceModel, because that model fixes mode to access and policyType to accessHost. Identity-only deletion candidates can be used while retaining access to the cached raw interface record for:

  • Port-channel membership protection
  • Detection of fields requiring the PUT reset path
  • Normalization payload construction

Acceptance criteria

  • An explicitly named, eligible physical Ethernet interface using dot1qTunnelHost is not silently filtered from state: deleted.
  • Check mode reports changed: true when normalization is required.
  • Normal execution resets the interface to fabric-default configuration.
  • Existing port-channel-member protection remains enforced.
  • A subsequent identical deletion is idempotent.
  • Policy-family filtering remains unchanged for create and update states.
  • Standalone-module regression coverage is added.
  • Aggregate-workflow regression coverage verifies the inherited behavior.

Recommended regression cases:

  1. A named dot1qTunnelHost interface becomes a deletion candidate.
  2. The candidate produces changed: true in check mode.
  3. Normal execution resets the interface.
  4. A second deletion is idempotent.
  5. An interface already at fabric default remains unchanged.
  6. A port-channel member remains protected.
  7. merged, replaced, and overridden remain scoped to accessHost.

===== END FINDING IFACE-001 =====


Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions