Skip to content

nd_interface_ethernet_access module#222

Open
allenrobel wants to merge 21 commits intond_interface_loopbackfrom
nd_interface_ethernet_access
Open

nd_interface_ethernet_access module#222
allenrobel wants to merge 21 commits intond_interface_loopbackfrom
nd_interface_ethernet_access

Conversation

@allenrobel
Copy link
Copy Markdown
Collaborator

@allenrobel allenrobel commented Apr 8, 2026

Summary

  • Add nd_interface_ethernet_access Ansible module for managing ethernet accessHost interfaces on Nexus Dashboard
  • Fan-out playbook shape: interface_names list with shared config_data, expanded into flat items before the state machine
  • Shared EthernetBaseOrchestrator base class for all future ethernet interface modules (trunk, routed, breakout, freeform)
  • Port-channel membership enforcement with whitelisted modifiable fields (description, admin_state, extra_config)
  • Bulk normalize-based delete via interfaceActions/normalize with the int_trunk_host template defaults
  • InterfaceDefaultConfig model providing the full int_trunk_host normalize payload
  • EpManageInterfacesDelete and EpManageInterfacesNormalize endpoint classes
  • File-based logging integrated via setup_logging() in main(). Module-level nd.nd_interface_ethernet_access logger emits debug records at expand_config, manage_state begin/end, and on NDStateMachineError (via log.exception so tracebacks reach the log file regardless of output_level). Logging activates only when ND_LOGGING_CONFIG points at a logging.config.dictConfig JSON file; otherwise the calls are no-ops.
  • Integration tests for all four states: merged, replaced, overridden, deleted. The test target's tasks/main.yaml wraps its include_tasks in a block that forwards nd_logging_config (set in inventory.networking [nd:vars]) into the module subprocess as ND_LOGGING_CONFIG, since ansible-test strips the controller's shell environment.

Delete mechanism for physical ethernet interfaces

/api/v1/manage/fabrics/{fabricName}/interfaceActions/normalize is used with the full int_trunk_host config template defaults (mode: "trunk", policyType: "trunkHost", etc). This resets the interface to fabric defaults and changes its policyType to trunkHost, which naturally removes it from the accessHost query_all() filter, enabling idempotent state: overridden.

Merge order

This PR must be merged AFTER nd_interface_loopback (PR #208). It is based on that branch and depends on the state machine, orchestrator base classes, fabric context, and endpoint infrastructure introduced there.

Merge & rebase note

This PR is part of a stack with a follow-on branch (nd_interface_ethernet_trunk). Because the team uses squash merges, after this PR is merged, @allenrobel will rebase the remaining stack branch onto develop before the next PR can be opened/merged. Please ping him once this is squashed.

Unit tests

Unit tests now cover the two branch-new source files with 105 tests and 100% real line coverage:

  • tests/unit/module_utils/models/test_ethernet_access_interface.py exercises every nested Pydantic model (Policy, NetworkOS, ConfigData, Interface) with field validation, range and enum constraints, normalize_policy_type, serialize_policy_type, round-trip to_payload / to_config / from_response / from_config, composite identifier, diff, merge, and argument_spec shape.
  • tests/unit/module_utils/orchestrators/test_ethernet_access_interface.py wires a real RestSend with the file-based Sender from tests/unit/module_utils/sender_file.py and ResponseHandler to verify model_class, supports_bulk_* inheritance, and _managed_policy_types returning {"accessHost"}. A query_all smoke test exercises the inherited base-class logic end-to-end across two switches, confirming non-accessHost interfaces are filtered out and switchIp is injected on the kept interfaces.

Parametrize collapses all value-table tests (range checks, enum choice checks, serializer modes, normalizer inputs) into single functions with descriptive ids= labels so pytest output still names each case.

Test plan

  • Integration tests pass for state: merged (create, fan-out, idempotency, update, deploy:false)
  • Integration tests pass for state: replaced (full replace, fan-out, idempotency)
  • Integration tests pass for state: overridden (reduce, idempotency, swap, fan-out, non-accessHost filtering)
  • Integration tests pass for state: deleted (single, fan-out, idempotency, non-existent)
  • Unit tests pass: python -m pytest tests/unit/module_utils/models/test_ethernet_access_interface.py tests/unit/module_utils/orchestrators/test_ethernet_access_interface.py (105 tests, 100% line coverage of the two new files)
  • To enable file-based logging during an integration run, add nd_logging_config=/absolute/path/to/logging_config.json to [nd:vars] in tests/integration/inventory.networking. An example config conforming to logging.config.dictConfig is documented in plugins/module_utils/common/log.py.

🤖 Generated with Claude Code

@allenrobel allenrobel marked this pull request as draft April 8, 2026 00:07
@allenrobel allenrobel changed the title Add nd_interface_ethernet_access module nd_interface_ethernet_access module Apr 8, 2026
@allenrobel allenrobel force-pushed the nd_interface_ethernet_access branch 2 times, most recently from 2f28392 to f7efdf8 Compare April 9, 2026 17:43
@allenrobel allenrobel force-pushed the nd_interface_loopback branch from 2d2895d to 4abc3c5 Compare April 9, 2026 18:00
@allenrobel allenrobel force-pushed the nd_interface_ethernet_access branch from f7efdf8 to 9912dd3 Compare April 9, 2026 18:01
@allenrobel allenrobel force-pushed the nd_interface_loopback branch from 4b2453f to f5ee201 Compare April 10, 2026 21:33
@allenrobel allenrobel force-pushed the nd_interface_ethernet_access branch 4 times, most recently from cab6652 to 7cb3d5d Compare April 15, 2026 20:50
@allenrobel allenrobel marked this pull request as ready for review April 15, 2026 21:16
@allenrobel allenrobel force-pushed the nd_interface_ethernet_access branch from 8380710 to 3940b27 Compare April 16, 2026 17:42
@allenrobel allenrobel force-pushed the nd_interface_loopback branch from abc59bc to 78a5554 Compare April 21, 2026 22:27
@allenrobel allenrobel force-pushed the nd_interface_ethernet_access branch from 3940b27 to 7aa132e Compare April 21, 2026 22:28
@allenrobel allenrobel mentioned this pull request Apr 21, 2026
4 tasks
@allenrobel allenrobel force-pushed the nd_interface_ethernet_access branch from 7aa132e to 9620a8f Compare April 22, 2026 01:54
@allenrobel allenrobel force-pushed the nd_interface_ethernet_access branch 2 times, most recently from 74f70ae to c05c9b4 Compare April 23, 2026 01:40
@allenrobel allenrobel force-pushed the nd_interface_ethernet_access branch 7 times, most recently from 313593f to b810abf Compare May 5, 2026 20:23
@allenrobel allenrobel force-pushed the nd_interface_ethernet_access branch from b810abf to ca0da57 Compare May 5, 2026 20:37
allenrobel and others added 21 commits May 5, 2026 15:41
Add Ansible module for managing ethernet accessHost interfaces on
Nexus Dashboard, with fan-out playbook shape, port-channel membership
enforcement, and bulk normalize-based delete for physical interfaces.

New files:
- Module: nd_interface_ethernet_access with expand_config() fan-out
- Model: EthernetAccessInterfaceModel with 19 accessHost policy fields
- Model: InterfaceDefaultConfig for int_trunk_host normalize payload
- Orchestrator: EthernetBaseOrchestrator (shared ethernet CRUD logic)
- Orchestrator: EthernetAccessInterfaceOrchestrator (thin subclass)
- Endpoints: EpManageInterfacesDelete, EpManageInterfacesNormalize
- Integration tests: merged, replaced, overridden, deleted states

Physical ethernet interfaces cannot be deleted via interfaceActions/remove
(silently does nothing) or DELETE (returns 500). The delete mechanism uses
interfaceActions/normalize with the int_trunk_host template defaults,
which resets interfaces to policyType: trunkHost, removing them from the
accessHost query filter and enabling idempotent state: overridden.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ntries

The DELETE endpoint does not work for physical ethernet interfaces (returns
HTTP 500). Updated docstring to reflect this and point to EpManageInterfacesNormalize.
Added missing EpManageInterfacesDelete and EpManageInterfacesNormalize to the
module-level endpoint list.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Removed mention of “reset” and “default interface” CLI.  While this endpoint can serve as the equivalent to “default interface” this is only so if the payload is constructured to do so.
Update docstring to clarify that EpManageInterfacesNormalize does not reset physical interfaces without an appropriate payload.
…elds

Add plugins/module_utils/models/interfaces/enums.py with reusable enums
derived from the int_access_host config template. Replace unconstrained
Optional[str]/Optional[int] fields with enum types, Field(ge=/le=) for
integer ranges, and Field(max_length=) for strings. Add 16 missing policy
fields from the template (storm control, fec, debounce, etc.). Replace
NDConstantMapping with AccessHostPolicyTypeEnum for policy_type handling.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Leverage NDBaseOrchestrator bulk infrastructure from PR #223 to reduce
API calls for all ethernet interface types. create_bulk groups interfaces
by switch and sends one POST per switch. delete_bulk queues all interfaces
for deferred normalization and deployment via remove_pending/deploy_pending.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…estrator

Reuse shared interface lifecycle methods (deploy, remove, fabric context,
switch resolution) from NDBaseInterfaceOrchestrator instead of duplicating
them. Add isinstance type narrowing in both interface modules, using
explicit raise AssertionError to satisfy ansible-test sanity no-assert check.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Match base class Optional type for create_bulk_endpoint and
delete_bulk_endpoint. Add pyright ignore for the guarded callsites
where the @requires_bulk_support decorator ensures non-None at runtime.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
ModelType is NDBaseModel which lacks interface-specific fields (switch_ip,
interface_name, config_data). Concrete subclasses always bind ModelType to
a model that provides these fields, so the accesses are safe at runtime.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Same migration as fabric_context/base_interface/loopback_interface: replace
self.sender.request() with self._request() inherited from NDBaseOrchestrator.

Six call sites in ethernet_base.py:
- _normalize_interfaces (bulk normalize via interfaceActions/normalize)
- create / update / create_bulk (per-switch POST/PUT)
- query_one (single interface GET)
- query_all (was self.sender.query_obj; now uses _request with
  not_found_ok=True to preserve 404-as-empty semantics)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Migrate to builtin generics and union syntax in files introduced on this
branch: Optional[X] -> X | None, List[X] -> list[X], Dict -> dict,
Set -> set, Type -> type. Drop the now-unused imports from typing.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Cover the two branch-new source files with 105 tests and 100% real
line coverage:

- `tests/unit/module_utils/models/test_ethernet_access_interface.py`
  exercises every nested Pydantic model (Policy, NetworkOS, ConfigData,
  Interface) with field validation, range and enum constraints,
  `normalize_policy_type`, `serialize_policy_type`, round-trip
  to_payload / to_config / from_response / from_config, composite
  identifier, diff, merge, and argument_spec shape.

- `tests/unit/module_utils/orchestrators/test_ethernet_access_interface.py`
  wires a real `RestSend` with the file-based `Sender` from
  `tests/unit/module_utils/sender_file.py` and `ResponseHandler` to
  verify `model_class`, `supports_bulk_*` inheritance, and
  `_managed_policy_types` returning `{"accessHost"}`. A `query_all`
  smoke test exercises the inherited base-class logic end-to-end
  across two switches, confirming non-accessHost interfaces are
  filtered out and `switchIp` is injected on the kept interfaces.

Parametrize collapses all value-table tests (range checks, enum choice
checks, serializer modes, normalizer inputs) into single functions
with descriptive `ids=` labels so pytest output still names each case.

Adds an empty `tests/unit/module_utils/orchestrators/__init__.py` to
satisfy ansible-test's `empty-init` sanity check.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Wire setup_logging() into the module's main() and emit debug records at
expand_config, manage_state begin/end, and on NDStateMachineError.
Logging activates only when ND_LOGGING_CONFIG points at a logging.config
JSON file; otherwise the calls are no-ops.

ansible-test strips the controller's shell environment, so the
integration test's main.yaml now wraps its include_tasks in a block that
forwards nd_logging_config (set in inventory.networking [nd:vars]) into
the module subprocess as ND_LOGGING_CONFIG. Added a header comment
documenting the variable.
Replaces the inline `description: str | None` declaration with the
shared `AsciiDescription` Annotated type introduced in
nd_interface_loopback. Same `max_length=254` constraint via Field(...).

Adds parametrized tests for em-dash, smart quotes, emoji, and latin-1
rejection alongside ASCII passthrough.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
`FabricContext.validate_for_mutation` now also fetches
`/deploymentFreeze` after the fabric summary, so the orchestrator's
`query_all` consumes one extra response from the queue. Insert a
`deploymentFreeze: false` fixture between the summary and switch list
yields to keep the response generator aligned.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The OpenAPI intAccessHostTemplate declares stormControlBroadcastLevel,
stormControlMulticastLevel, and stormControlUnicastLevel as
number/float, but the model and argument spec had them as str. Switch
to float | None with ge=0.0/le=100.0 so the model matches the API
contract and the argument spec validates percentage input correctly.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Per-policy interface modules (one module = one policyType) must not
expose policy_type as a user-facing argspec option. The
nd_interface_ethernet_access module targets accessHost only, so
policy_type was dead surface area.

- Remove policy_type from argspec and DOCUMENTATION
- Drop normalize_policy_type validator and serialize_policy_type
  field serializer
- Hardcode AccessHostPolicyTypeEnum.ACCESS_HOST as the model
  default; field still serialized into payloads as policyType
- Strip policy_type from integration test inputs
- Update unit tests (96/96 pass)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Replace verbatim-repeated module argument blocks (check / normal / idempotent
runs of the same call) with YAML anchors. Removes a class of typo where one
of the duplicated blocks could drift from its siblings.

Anchors are defined at first use within each section to keep them local to
the reader.
Move the top-of-file SETUP block from merged.yaml into a dedicated
tasks/setup.yaml and call it from main.yaml before the state-test blocks.
Includes the post-cleanup DEBUG re-query probe alongside the cleanup since
both are file-level observability rather than test-specific.

Intra-test setup that's coupled to specific tests stays inline (e.g. the
Ethernet1/48 cleanup after the no-deploy test in merged.yaml). The final
CLEANUP at the bottom of deleted.yaml stays inline as well — it's post-test
teardown for the last state file, not file-level pre-test prep.
@allenrobel allenrobel force-pushed the nd_interface_ethernet_access branch from af37a04 to a63bc2a Compare May 6, 2026 01:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant