nd_interface_ethernet_trunk_host#265
Open
allenrobel wants to merge 20 commits into
Open
Conversation
9620a8f to
74f70ae
Compare
03b45b0 to
94e8389
Compare
74f70ae to
c05c9b4
Compare
94e8389 to
c80fa2f
Compare
7f93144 to
b556f6d
Compare
a6e4008 to
64cebc1
Compare
b556f6d to
e0d0b54
Compare
0d3c857 to
0bc5c99
Compare
8989c2c to
63772b4
Compare
0bc5c99 to
6f98652
Compare
63772b4 to
4ef3a29
Compare
6f98652 to
bcff933
Compare
bba7fdc to
a92738c
Compare
eca8c3d to
a02ccf5
Compare
4058f65 to
be6663b
Compare
724ba60 to
567f385
Compare
be6663b to
80f9619
Compare
567f385 to
61e8706
Compare
80f9619 to
16e8ffa
Compare
61e8706 to
b115f74
Compare
16e8ffa to
ba57b6c
Compare
b115f74 to
3961059
Compare
ba57b6c to
c388ad2
Compare
3961059 to
915bdc3
Compare
Adds a new Ansible module for CRUD operations on host-facing ethernet trunk interfaces in Nexus Dashboard 4.2. Follows the same composite- identifier, bulk-CRUD, and deploy-lifecycle pattern as the access module, filtering on policyType=trunkHost. - TrunkHostPolicyTypeEnum added to shared interface enums. - EthernetTrunkHostInterfaceModel defines trunk-specific policy fields (allowed_vlans with regex validator, native_vlan, vlan_mapping, vlan_mapping_entries) and reuses shared enums for the rest. - EthernetTrunkHostInterfaceOrchestrator inherits the shared ethernet base; only _managed_policy_types() is type-specific. - Integration test target covers merged/replaced/overridden/deleted. Unit tests deferred to match nd_interface_ethernet_access. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Resolves issues surfaced by the full trunk-host integration run:
- `EthernetTrunkHostVlanMappingEntryModel.customer_vlan_id` is now
`List[str]`, matching the ND API which expects `customerVlanId` as an
array of VLAN tokens rather than a single string.
- `EthernetTrunkHostPolicyModel.validate_allowed_vlans` now coerces
integer input to string before regex validation, so ND responses that
return a single-VLAN `allowedVlans` as a bare integer (e.g. `999`) no
longer raise a Pydantic `ValueError`.
- `InterfaceDefaultPolicyModel` now includes `description: ""` and
`nativeVlan: 1`, so `interfaceActions/normalize` fully resets user-set
descriptions and native VLANs on deleted / overridden interfaces
instead of leaving them behind.
- `EthernetTrunkHostInterfaceOrchestrator.query_all` filters out
interfaces matching the unconfigured `int_trunk_host` default
signature. Normalizing a trunkHost interface produces another
trunkHost interface, so without this filter `state: overridden` could
not be idempotent (every fabric-default interface appeared in `before`
and was re-queued for normalization on every run).
- Integration tests scope `selectattr('interface_name', ...)` lookups
by `switch_ip` so assertions work on multi-switch fabrics, and the
VLAN-mapping block is gated on a new `supports_vlan_mapping` extra-var
because Nexus 9000v virtual switches reject
`switchport vlan mapping ... dot1q-tunnel ...`.
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. Add `from __future__ import annotations` to the model file so sanity pylint tolerates PEP 604 union syntax under older py-version settings. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Cover the two branch-new source files with 142 tests and ~99% line coverage: - `tests/unit/module_utils/models/test_ethernet_trunk_host_interface.py` exercises every nested Pydantic model (VlanMappingEntry, Policy, NetworkOS, ConfigData, Interface) with field validation, range and enum constraints, validators, serializers, 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_trunk_host_interface.py` wires a real `RestSend` with the file-based `Sender` from `tests/unit/module_utils/sender_file.py` and `ResponseHandler` to verify `_managed_policy_types`, the `_is_unconfigured_default` truth table, and `query_all` filtering (policy-type + unconfigured-default) across multiple switches. Fixture JSON added under `tests/unit/module_utils/fixtures/fixture_data/`. Parametrize collapses all value-table tests (range checks, enum choice checks, truth table, input/output tables) 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. Updated the header comment to document the variable and correct the supports_vlan_mapping example to use inventory.networking instead of the unsupported --extra-vars flag.
Replaces the inline `description: str | None` with the shared `AsciiDescription` Annotated type. 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 in both the happy-path (00400) and all-default (00410) tests to keep the response generator aligned. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The OpenAPI intTrunkHostTemplate 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. Update DOCUMENTATION block to type: float to match. 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_trunk_host module targets trunkHost 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 TrunkHostPolicyTypeEnum.TRUNK_HOST as the model default; field still serialized into payloads as policyType - Strip policy_type from integration test inputs - Update unit tests (122/122 pass) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Replace shape-only regex with Annotated types (AllowedVlans, CustomerVlanIdList) backed by a shared per-token validator that enforces VLAN ids in 1..4094 and rejects reversed ranges. Mirrors the fix applied to nd_interface_port_channel_trunk_host. Mark both types with a TODO to consolidate into models/types.py once sibling branches merge. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
VlanId (1..4094) recurs as Field(ge=1, le=4094) on multiple fields across the interface family. Defer the cleanup to the same post-merge PR that introduces AllowedVlans / CustomerVlanIdList in models/types.py. 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 (the Ethernet1/48 cleanups after the vlan_mapping and no-deploy tests 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.
…l tests validate_for_mutation reads freeze status from cached fabric_summary (see fabric_context.py:166), so query_all no longer issues a separate GET to /deploymentFreeze. Applies to both the happy-path 00400 test (which was failing) and the all-defaults 00410 test (which passed by luck because it expects an empty result). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…spec Same change as ethernet_access (5ecd435): drop interface_type, mode, and network_os_type from get_argument_spec() and DOCUMENTATION; narrow each in the Pydantic model to Literal[<value>] (or keep the existing Enum for policy_type) and add Field(frozen=True) so the values are immutable post-construction. Updates the argspec assertion test and the network_os_type test to verify the Literal lock instead of accepting "ios-xe". Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
EthernetBaseOrchestrator.query_all now only iterates switches whose
switch_ip appears in rest_send.params["config"] (unless state is
overridden). The trunk_host orchestrator tests still built RestSend
with only check_mode/fabric_name, so _switches_to_query() returned {}
and test_00400 hit assert len(result) == 2 with 0.
_build_rest_send / _build_orchestrator now accept a params dict that
is merged into the RestSend params, mirroring the access-orchestrator
test helper. test_00400 supplies state=merged plus both switch IPs.
test_00410 had been passing for the wrong reason — the switch was
never queried, so the empty result said nothing about the filter.
It now supplies state=merged plus the fixture switch_ip so the per-
switch GET fires, and spies on _is_unconfigured_default to assert the
filter was actually invoked on both interfaces returned by the switch.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Carry over nine fixes that landed on the ethernet_access parent but were
not yet applied to the cloned trunk_host module. Each gap was identified
in a side-by-side audit of the two branches.
Module (plugins/modules/nd_interface_ethernet_trunk_host.py):
- Add validate_interface_names, validate_within_item_duplicates, and
validate_across_item_duplicates so null/empty entries and
case-insensitive duplicates raise friendly ValueErrors instead of
crashing downstream on name.lower() or silently expanding into
identical orchestrator items.
- Make expand_config null-safe (`group.get("interface_names") or []`)
so an explicit YAML `interface_names: ~` no longer crashes with
TypeError.
- Update expand_config docstring to list the ValueError conditions.
- Wrap the expand_config call site in try/except ValueError so
configuration errors surface via module.fail_json instead of an
unhandled traceback.
- Add a broad `except Exception` handler in main() to format
unexpected exceptions consistently with the NDStateMachineError path.
Model (plugins/module_utils/models/interfaces/ethernet_trunk_host_interface.py):
- Rewrite normalize_interface_name to Title-case the full leading
alphabetic prefix via _INTERFACE_NAME_PREFIX_RE so mixed-case input
(etHernet1/1, ETHERNET1/1) canonicalizes to Ethernet1/1 instead of
EtHernet1/1, restoring idempotency on subsequent reads.
- Default EthernetTrunkHostConfigDataModel.network_os via
default_factory=EthernetTrunkHostNetworkOSModel so an empty
config_data builds without a `field required` ValidationError.
- Add a wrap-mode model_serializer that strips the hardcoded
policy_type ("trunkHost") from to_config() output while leaving
to_payload() and to_diff_dict() untouched, keeping playbook
before/after diffs free of frozen scaffolding fields.
Tests (tests/unit/module_utils/models/test_ethernet_trunk_host_interface.py):
- Drop policy_type from SAMPLE_ANSIBLE_CONFIG so round-trip
assertions match the new to_config output.
- Invert test 00470 to verify the network_os default_factory
behaviour instead of the prior `field required` expectation.
- Update test 00710 to assert policy_type is omitted from to_config
output and add 00711 to assert to_payload still emits the
wire-form policyType.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The base orchestrator's new selective delete dispatcher routes ethernet interfaces with `bandwidth`, `debounceLinkupTimer`, or `inheritBandwidth` set to a per-interface PUT-as-replace reset path. For that path to ever fire on a trunkHost interface, the existing `EthernetTrunkHostInterfaceOrchestrator._is_unconfigured_default` filter has to recognise that an interface with one of those Class C fields set is NOT unconfigured — otherwise it falls out of `existing` and `_manage_delete_state` never queues it for delete. Extends the predicate to additionally reject any interface whose policy carries a non-null value for any field in `InterfaceDefaultConfig.UNRESETTABLE_FIELDS`. Parametrized truth-table test 00200 gains three new ids (`class_c_bandwidth`, `class_c_debounce_linkup`, `class_c_inherit_bandwidth`) pinning the extended behaviour. Integration scenario in `tests/integration/targets/nd_interface_ethernet_trunk_host/tasks/deleted.yaml` seeds bandwidth=1500000 on Ethernet1/48 via `state: merged`, runs `state: deleted`, and asserts the field clears. Lab-verified end-to-end on ND 4.2.1 (SITE1 / S1_LE3 Ethernet1/41 — see the playbook in the audit work). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Related Issue(s)
Stacks on top of #222 (access module). Replaces #264, which was auto-closed when its source branch was renamed.
Proposed Changes
Adds the
nd_interface_ethernet_trunk_hostmodule for managing Cisco Nexus Dashboard 4.2 ethernet interfaces with thetrunkHostpolicy type. Parallelsnd_interface_ethernet_access: composite(switch_ip, interface_name)identifier, bulk create / bulk normalize-as-delete, per-interface deploy queueing, and the full merged / replaced / overridden / deleted state lifecycle.EthernetTrunkHostInterfaceModelwith trunk-specific fields —allowed_vlans(regex-validated, acceptsnone,all, or comma-separated IDs/ranges),native_vlan,vlan_mapping, and nestedvlan_mapping_entrieswithcustomer_vlan_idasList[str]to match the ND API.TrunkHostPolicyTypeEnumadded to shared interface enums.EthernetTrunkHostInterfaceOrchestratorfiltersquery_allto exclude interfaces matching the unconfiguredint_trunk_hostdefault signature, sostate: overriddenis idempotent on the second run. Normalizing a trunkHost interface produces another trunkHost interface, so without this filter every fabric-default interface would appear inbeforeand be re-queued for normalization on every run.InterfaceDefaultPolicyModelgainsdescription: ""andnativeVlan: 1sointerfaceActions/normalizeactually clears user-set descriptions and native VLANs on deleted / overridden interfaces instead of leaving them behind. This affects all ethernet modules that delete via normalize, which is the desired behavior.allowed_vlansvalidator coerces bare-integer ND responses (e.g. a single VLAN returned as999) to string before regex validation.setup_logging()inmain(). Module-levelnd.nd_interface_ethernet_trunk_hostlogger emits debug records atexpand_config,manage_statebegin/end, and onNDStateMachineError(vialog.exceptionso tracebacks reach the log file regardless ofoutput_level). Logging activates only whenND_LOGGING_CONFIGpoints at alogging.config.dictConfigJSON file; otherwise the calls are no-ops.selectattr('interface_name', ...)lookups are scoped byswitch_ipso assertions hold on multi-switch fabrics. The VLAN-mapping test block is gated on asupports_vlan_mappingvariable because Nexus 9000v virtual switches rejectswitchport vlan mapping ... dot1q-tunnel ...at the NX-OS layer. The test target'stasks/main.yamlwraps itsinclude_tasksin a block that forwardsnd_logging_config(set ininventory.networking [nd:vars]) into the module subprocess asND_LOGGING_CONFIG, sinceansible-teststrips the controller's shell environment.Test Notes
ansible-test network-integration nd_interface_ethernet_trunk_host -v.supports_vlan_mapping=trueto the[nd:vars]section oftests/integration/inventory.networking. (ansible-testdoes not accept--extra-varson the command line.)nd_logging_config=/absolute/path/to/logging_config.jsonto[nd:vars]intests/integration/inventory.networking. An example config conforming tologging.config.dictConfigis documented inplugins/module_utils/common/log.py.tests/unit/module_utils/models/test_ethernet_trunk_host_interface.pyexercises every nested Pydantic model (VlanMappingEntry,Policy,NetworkOS,ConfigData,Interface) with field validation, range and enum constraints, validators, serializers, round-tripto_payload/to_config/from_response/from_config, composite identifier, diff, merge, and argument_spec shape.tests/unit/module_utils/orchestrators/test_ethernet_trunk_host_interface.pywires a realRestSendwith the file-basedSenderfromtests/unit/module_utils/sender_file.pyandResponseHandlerto verify_managed_policy_types, the_is_unconfigured_defaulttruth table, andquery_allfiltering (policy-type + unconfigured-default) across multiple switches.ids=labels so pytest output still names each case.python -m pytest tests/unit/module_utils/models/test_ethernet_trunk_host_interface.py tests/unit/module_utils/orchestrators/test_ethernet_trunk_host_interface.pyCisco Nexus Dashboard Version
4.2
Related ND API Resource Category
Checklist