From d4ff3d16e65b03c8b8c2808b1b78d77e2e0289fe Mon Sep 17 00:00:00 2001 From: Chris Portman Date: Tue, 19 Aug 2025 09:07:32 +1000 Subject: [PATCH 1/2] Issue #974 Interface Enable State Idempotency v10.2.0 removed the use of `module_utils.network.nxos.nxos.default_intf_enabled()` to determine the targets default state of various interface types. This change re-instates that behaviour, to resolve idempotency issues. Tests are adjusted to suit. --- .../nxos/config/interfaces/interfaces.py | 42 ++++++++++++------- .../network/nxos/rm_templates/interfaces.py | 2 +- .../network/nxos/test_nxos_interfaces.py | 18 ++++---- 3 files changed, 37 insertions(+), 25 deletions(-) diff --git a/plugins/module_utils/network/nxos/config/interfaces/interfaces.py b/plugins/module_utils/network/nxos/config/interfaces/interfaces.py index dc2b21cea..d364c57ee 100644 --- a/plugins/module_utils/network/nxos/config/interfaces/interfaces.py +++ b/plugins/module_utils/network/nxos/config/interfaces/interfaces.py @@ -37,6 +37,9 @@ from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.utils.utils import ( normalize_interface, ) +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import ( + default_intf_enabled, +) class Interfaces(ResourceModule): @@ -138,22 +141,6 @@ def _compare(self, want, have): begin = len(self.commands) self.compare(parsers=self.parsers, want=want, have=have) - # Handle the 'enabled' state separately - want_enabled = want.get("enabled") - have_enabled = have.get("enabled") - if want_enabled is not None: - if want_enabled != have_enabled: - if want_enabled is True: - self.addcmd(want, "enabled", True) - else: - self.addcmd(want, "enabled", False) - elif not want and self.state == "overridden": - if have_enabled is not None: - self.addcmd(have, "enabled", False) - elif not want and self.state == "deleted": - if have_enabled: - self.addcmd(have, "enabled", False) - # Handle the 'mode' state separately want_mode = want.get("mode") have_mode = have.get("mode", self.defaults.get("default_mode")) @@ -172,6 +159,26 @@ def _compare(self, want, have): no_cmd = True if self.defaults.get("default_mode") == "layer3" else False self.addcmd(have, "mode", no_cmd) + # Handle the 'enabled' state separately + want_enabled = want.get("enabled") + have_enabled = have.get("enabled") + + if have_enabled is None and have.get("name", None) is not None: + have_enabled = default_intf_enabled(have["name"], self.defaults, have_mode) + + if want_enabled is not None: + if want_enabled != have_enabled: + if want_enabled is True: + self.addcmd(want, "enabled", True) + else: + self.addcmd(want, "enabled", False) + elif not want and self.state == "overridden": + if have_enabled is not None: + self.addcmd(have, "enabled", False) + elif not want and self.state == "deleted": + if have_enabled: + self.addcmd(have, "enabled", False) + if len(self.commands) != begin: self.commands.insert(begin, self._tmplt.render(want or have, "name", False)) @@ -217,4 +224,7 @@ def get_interface_defaults(self): if default_enabled: interface_defs["L2_enabled"] = True if "no " in default_enabled.groups() else False + # Layer 3 interfaces default to shutdown (disabled) + interface_defs["L3_enabled"] = False + return interface_defs diff --git a/plugins/module_utils/network/nxos/rm_templates/interfaces.py b/plugins/module_utils/network/nxos/rm_templates/interfaces.py index e780b0f5e..0cacd9582 100644 --- a/plugins/module_utils/network/nxos/rm_templates/interfaces.py +++ b/plugins/module_utils/network/nxos/rm_templates/interfaces.py @@ -68,7 +68,7 @@ def __init__(self, lines=None, module=None): "setval": "shutdown", "result": { '{{ name }}': { - 'enabled': "{{ False if shutdown is defined and negate is not defined else True }}", + 'enabled': "{{ True if negate is defined and shutdown is defined else False if shutdown is defined else None }}", }, }, }, diff --git a/tests/unit/modules/network/nxos/test_nxos_interfaces.py b/tests/unit/modules/network/nxos/test_nxos_interfaces.py index 09f8f665f..a1b0855d4 100644 --- a/tests/unit/modules/network/nxos/test_nxos_interfaces.py +++ b/tests/unit/modules/network/nxos/test_nxos_interfaces.py @@ -451,7 +451,6 @@ def test_3(self): ) merged = [ "interface Ethernet1/1", - "no shutdown", "no switchport", "interface Ethernet1/2", "shutdown", @@ -459,8 +458,6 @@ def test_3(self): "shutdown", "interface loopback2", "no shutdown", - "interface loopback3", - "no shutdown", "interface port-channel3", "no shutdown", "interface loopback4", @@ -474,6 +471,11 @@ def test_3(self): self.assertEqual(sorted(result["commands"]), sorted(merged)) # Test with an older image version which has different defaults + self.exec_get_defaults.return_value = { + "default_mode": "layer2", + "L2_enabled": False, + } + merged_legacy = [ "interface Ethernet1/1", "no shutdown", @@ -484,8 +486,6 @@ def test_3(self): "shutdown", "interface loopback2", "no shutdown", - "interface loopback3", - "no shutdown", "interface port-channel3", "no shutdown", "interface loopback4", @@ -493,13 +493,17 @@ def test_3(self): "interface port-channel4", "no shutdown", ] - result = self.execute_module(changed=True, device="legacy") + result = self.execute_module(changed=True) self.assertEqual(sorted(result["commands"]), sorted(merged_legacy)) deleted = [ "interface Ethernet1/2", "switchport", "shutdown", + "interface loopback1", + "shutdown", + "interface loopback3", + "shutdown", ] playbook["state"] = "deleted" set_module_args(playbook) @@ -516,8 +520,6 @@ def test_3(self): "shutdown", "interface loopback2", "no shutdown", - "interface loopback3", - "no shutdown", "interface port-channel3", "no shutdown", "interface loopback4", From d2d40c1037ca1fed888afe38fd5e11cfedbfa6e1 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 18 Aug 2025 23:26:16 +0000 Subject: [PATCH 2/2] chore: auto fixes from pre-commit.com hooks --- .../network/nxos/config/interfaces/interfaces.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/module_utils/network/nxos/config/interfaces/interfaces.py b/plugins/module_utils/network/nxos/config/interfaces/interfaces.py index d364c57ee..1c396d7b6 100644 --- a/plugins/module_utils/network/nxos/config/interfaces/interfaces.py +++ b/plugins/module_utils/network/nxos/config/interfaces/interfaces.py @@ -31,15 +31,15 @@ from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.facts import ( Facts, ) +from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import ( + default_intf_enabled, +) from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.rm_templates.interfaces import ( InterfacesTemplate, ) from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.utils.utils import ( normalize_interface, ) -from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import ( - default_intf_enabled, -) class Interfaces(ResourceModule):