From 7917d4def75378bd8f90a615c6e2bab1ea58f0cd Mon Sep 17 00:00:00 2001 From: Nathaniel Case Date: Fri, 13 Sep 2019 09:35:50 -0400 Subject: [PATCH] Standardize eos resource modules (#61736) * Fix eos_l3_interfaces case sensitivity * Unify EOS module notes * Add normalize_interfaces to eos_l2_interfaces * Pull normalize_interface into eos_interfaces * Add normalize_interface to lag_interfaces * Add normalize_interface to lldp_interfaces * Add normalize_interface to lacp_interfaces * more module cleanup * Add changelog --- .../61736-eos-normalize-interface.yaml | 3 + .../eos/config/interfaces/interfaces.py | 268 +++++++++--------- .../eos/config/l2_interfaces/l2_interfaces.py | 92 +++--- .../eos/config/l3_interfaces/l3_interfaces.py | 71 +++-- .../config/lacp_interfaces/lacp_interfaces.py | 19 +- .../config/lag_interfaces/lag_interfaces.py | 25 +- .../config/lldp_interfaces/lldp_interfaces.py | 31 +- .../eos/facts/interfaces/interfaces.py | 7 +- .../eos/facts/l2_interfaces/l2_interfaces.py | 7 +- .../eos/facts/l3_interfaces/l3_interfaces.py | 2 +- .../network/eos/facts/lacp/lacp.py | 2 +- .../facts/lacp_interfaces/lacp_interfaces.py | 3 +- .../facts/lag_interfaces/lag_interfaces.py | 3 +- .../facts/lldp_interfaces/lldp_interfaces.py | 3 +- .../network/eos/utils/__init__.py | 0 .../module_utils/network/eos/utils/utils.py | 54 ++++ .../modules/network/eos/eos_interfaces.py | 7 +- .../modules/network/eos/eos_l3_interfaces.py | 2 +- .../network/eos/eos_lacp_interfaces.py | 4 + .../modules/network/eos/eos_lag_interfaces.py | 2 +- .../modules/network/eos/eos_lldp_global.py | 4 + .../network/eos/eos_lldp_interfaces.py | 4 + .../eos_lag_interfaces/tests/cli/deleted.yaml | 2 +- 23 files changed, 350 insertions(+), 265 deletions(-) create mode 100644 changelogs/fragments/61736-eos-normalize-interface.yaml create mode 100644 lib/ansible/module_utils/network/eos/utils/__init__.py create mode 100644 lib/ansible/module_utils/network/eos/utils/utils.py diff --git a/changelogs/fragments/61736-eos-normalize-interface.yaml b/changelogs/fragments/61736-eos-normalize-interface.yaml new file mode 100644 index 0000000000..4009808cca --- /dev/null +++ b/changelogs/fragments/61736-eos-normalize-interface.yaml @@ -0,0 +1,3 @@ +bugfixes: +- Remove case sensitivity on interface names from eos_interfaces, eos_l2_interfaces, eos_l3_interfaces, + eos_lacp_interfaces, eos_lag_interfaces, and eos_lldp_interfaces. diff --git a/lib/ansible/module_utils/network/eos/config/interfaces/interfaces.py b/lib/ansible/module_utils/network/eos/config/interfaces/interfaces.py index cf3fba7e09..7687ae296f 100644 --- a/lib/ansible/module_utils/network/eos/config/interfaces/interfaces.py +++ b/lib/ansible/module_utils/network/eos/config/interfaces/interfaces.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- # Copyright 2019 Red Hat -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) """ The eos_interfaces class It is in this file where the current configuration (as dict) @@ -12,10 +13,10 @@ created from __future__ import (absolute_import, division, print_function) __metaclass__ = type -from ansible.module_utils.network.common.utils import to_list, param_list_to_dict - from ansible.module_utils.network.common.cfg.base import ConfigBase +from ansible.module_utils.network.common.utils import to_list, dict_diff, param_list_to_dict from ansible.module_utils.network.eos.facts.facts import Facts +from ansible.module_utils.network.eos.utils.utils import normalize_interface class Interfaces(ConfigBase): @@ -93,148 +94,143 @@ class Interfaces(ConfigBase): :returns: the commands necessary to migrate the current configuration to the desired configuration """ + state = self._module.params['state'] want = param_list_to_dict(want) have = param_list_to_dict(have) - state = self._module.params['state'] if state == 'overridden': - commands = state_overridden(want, have) + commands = self._state_overridden(want, have) elif state == 'deleted': - commands = state_deleted(want, have) + commands = self._state_deleted(want, have) elif state == 'merged': - commands = state_merged(want, have) + commands = self._state_merged(want, have) elif state == 'replaced': - commands = state_replaced(want, have) + commands = self._state_replaced(want, have) + return commands + + @staticmethod + def _state_replaced(want, have): + """ The command generator when state is replaced + + :rtype: A list + :returns: the commands necessary to migrate the current configuration + to the desired configuration + """ + commands = [] + for key, desired in want.items(): + interface_name = normalize_interface(key) + if interface_name in have: + extant = have[interface_name] + else: + extant = dict() + + add_config = dict_diff(extant, desired) + del_config = dict_diff(desired, extant) + + commands.extend(generate_commands(key, add_config, del_config)) + + return commands + + @staticmethod + def _state_overridden(want, have): + """ The command generator when state is overridden + + :rtype: A list + :returns: the commands necessary to migrate the current configuration + to the desired configuration + """ + commands = [] + for key, extant in have.items(): + if key in want: + desired = want[key] + else: + desired = dict() + + add_config = dict_diff(extant, desired) + del_config = dict_diff(desired, extant) + + commands.extend(generate_commands(key, add_config, del_config)) + + return commands + + @staticmethod + def _state_merged(want, have): + """ The command generator when state is merged + + :rtype: A list + :returns: the commands necessary to merge the provided into + the current configuration + """ + commands = [] + for key, desired in want.items(): + interface_name = normalize_interface(key) + if interface_name in have: + extant = have[interface_name] + else: + extant = dict() + + add_config = dict_diff(extant, desired) + + commands.extend(generate_commands(key, add_config, {})) + + return commands + + @staticmethod + def _state_deleted(want, have): + """ The command generator when state is deleted + + :rtype: A list + :returns: the commands necessary to remove the current configuration + of the provided objects + """ + commands = [] + for key in want: + desired = dict() + if key in have: + extant = have[key] + else: + continue + + del_config = dict_diff(desired, extant) + + commands.extend(generate_commands(key, {}, del_config)) + return commands -def state_replaced(want, have): - """ The command generator when state is replaced - - :rtype: A list - :returns: the commands necessary to migrate the current configuration - to the desired configuration - """ - commands = _compute_commands(want, have, replace=True, remove=True) - - replace = commands['replace'] - remove = commands['remove'] - - commands_by_interface = replace - for interface, commands in remove.items(): - commands_by_interface[interface] = replace.get(interface, []) + commands - - return _flatten_commands(commands_by_interface) - - -def state_overridden(want, have): - """ The command generator when state is overridden - - :rtype: A list - :returns: the commands necessary to migrate the current configuration - to the desired configuration - """ - # Add empty desired state for unspecified interfaces - for key in have: - if key not in want: - want[key] = {} - - # Otherwise it's the same as replaced - return state_replaced(want, have) - - -def state_merged(want, have): - """ The command generator when state is merged - - :rtype: A list - :returns: the commands necessary to merge the provided into - the current configuration - """ - commands = _compute_commands(want, have, replace=True) - return _flatten_commands(commands['replace']) - - -def state_deleted(want, have): - """ The command generator when state is deleted - - :rtype: A list - :returns: the commands necessary to remove the current configuration - of the provided objects - """ - commands = _compute_commands(want, have, remove=True) - return _flatten_commands(commands['remove']) - - -def _compute_commands(want, have, replace=False, remove=False): - replace_params = {} - remove_params = {} - for name, config in want.items(): - extant = have.get(name, {}) - - if remove: - remove_params[name] = dict(set(extant.items()).difference(config.items())) - if replace: - replace_params[name] = dict(set(config.items()).difference(extant.items())) - if remove: - # We won't need to also clear the configuration if we've - # already set it to something - for param in replace_params[name]: - remove_params[name].pop(param, None) - - returns = {} - if replace: - returns['replace'] = _replace_config(replace_params) - if remove: - returns['remove'] = _remove_config(remove_params) - - return returns - - -def _remove_config(params): - """ - Generates commands to reset config to defaults based on keys provided. - """ - commands = {} - for interface, config in params.items(): - interface_commands = [] - for param in config: - if param == 'enabled': - interface_commands.append('no shutdown') - elif param in ('description', 'mtu'): - interface_commands.append('no {0}'.format(param)) - elif param == 'speed': - interface_commands.append('speed auto') - if interface_commands: - commands[interface] = interface_commands - - return commands - - -def _replace_config(params): - """ - Generates commands to replace config to new values based on provided dictionary. - """ - commands = {} - for interface, config in params.items(): - interface_commands = [] - for param, state in config.items(): - if param == 'description': - interface_commands.append('description "{0}"'.format(state)) - elif param == 'enabled': - interface_commands.append('{0}shutdown'.format('no ' if state else '')) - elif param == 'mtu': - interface_commands.append('mtu {0}'.format(state)) - if 'speed' in config: - interface_commands.append('speed {0}{1}'.format(config['speed'], config['duplex'])) - if interface_commands: - commands[interface] = interface_commands - - return commands - - -def _flatten_commands(command_dict): +def generate_commands(interface, to_set, to_remove): commands = [] - for interface, interface_commands in command_dict.items(): - commands.append('interface {0}'.format(interface)) - commands.extend(interface_commands) + for key, value in to_set.items(): + if value is None: + continue + + if key == "enabled": + commands.append('{0}shutdown'.format('no ' if value else '')) + elif key == "speed": + if value == "auto": + commands.append("{0} {1}".format(key, value)) + else: + commands.append('speed {0}{1}'.format(value, to_set['duplex'])) + elif key == "duplex": + # duplex is handled with speed + continue + else: + commands.append("{0} {1}".format(key, value)) + + # Don't try to also remove the same key, if present in to_remove + to_remove.pop(key, None) + + for key in to_remove.keys(): + if key == "enabled": + commands.append('no shutdown') + elif key == "speed": + commands.append("speed auto") + elif key == "duplex": + # duplex is handled with speed + continue + else: + commands.append("no {0}".format(key)) + + if commands: + commands.insert(0, "interface {0}".format(interface)) return commands diff --git a/lib/ansible/module_utils/network/eos/config/l2_interfaces/l2_interfaces.py b/lib/ansible/module_utils/network/eos/config/l2_interfaces/l2_interfaces.py index 519453d610..71d7bf20e6 100644 --- a/lib/ansible/module_utils/network/eos/config/l2_interfaces/l2_interfaces.py +++ b/lib/ansible/module_utils/network/eos/config/l2_interfaces/l2_interfaces.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- # Copyright 2019 Red Hat -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) """ The eos_l2_interfaces class It is in this file where the current configuration (as dict) @@ -12,10 +13,10 @@ created from __future__ import (absolute_import, division, print_function) __metaclass__ = type -from ansible.module_utils.network.common.utils import to_list - from ansible.module_utils.network.common.cfg.base import ConfigBase +from ansible.module_utils.network.common.utils import to_list, param_list_to_dict from ansible.module_utils.network.eos.facts.facts import Facts +from ansible.module_utils.network.eos.utils.utils import normalize_interface class L2_interfaces(ConfigBase): @@ -94,6 +95,8 @@ class L2_interfaces(ConfigBase): to the desired configuration """ state = self._module.params['state'] + want = param_list_to_dict(want) + have = param_list_to_dict(have) if state == 'overridden': commands = self._state_overridden(want, have) elif state == 'deleted': @@ -113,15 +116,20 @@ class L2_interfaces(ConfigBase): to the desired configuration """ commands = [] - for interface in want: - for extant in have: - if extant['name'] == interface['name']: - break + for key, desired in want.items(): + interface_name = normalize_interface(key) + if interface_name in have: + extant = have[interface_name] else: - continue + extant = dict() + + intf_commands = set_interface(desired, extant) + intf_commands.extend(clear_interface(desired, extant)) + + if intf_commands: + commands.append("interface {0}".format(interface_name)) + commands.extend(intf_commands) - commands.extend(clear_interface(interface, extant)) - commands.extend(set_interface(interface, extant)) return commands @staticmethod @@ -133,19 +141,19 @@ class L2_interfaces(ConfigBase): to the desired configuration """ commands = [] - for extant in have: - for interface in want: - if extant['name'] == interface['name']: - break + for key, extant in have.items(): + if key in want: + desired = want[key] else: - # We didn't find a matching desired state, which means we can - # pretend we recieved an empty desired state. - interface = dict(name=extant['name']) - commands.extend(clear_interface(interface, extant)) - continue + desired = dict() + + intf_commands = set_interface(desired, extant) + intf_commands.extend(clear_interface(desired, extant)) + + if intf_commands: + commands.append("interface {0}".format(key)) + commands.extend(intf_commands) - commands.extend(clear_interface(interface, extant)) - commands.extend(set_interface(interface, extant)) return commands @staticmethod @@ -157,14 +165,18 @@ class L2_interfaces(ConfigBase): the current configuration """ commands = [] - for interface in want: - for extant in have: - if extant['name'] == interface['name']: - break + for key, desired in want.items(): + interface_name = normalize_interface(key) + if interface_name in have: + extant = have[interface_name] else: - continue + extant = dict() - commands.extend(set_interface(interface, extant)) + intf_commands = set_interface(desired, extant) + + if intf_commands: + commands.append("interface {0}".format(interface_name)) + commands.extend(intf_commands) return commands @@ -177,29 +189,31 @@ class L2_interfaces(ConfigBase): of the provided objects """ commands = [] - for interface in want: - for extant in have: - if extant['name'] == interface['name']: - break + for key in want: + desired = dict() + if key in have: + extant = have[key] else: continue - # Use an empty configuration, just in case - interface = dict(name=interface['name']) - commands.extend(clear_interface(interface, extant)) + intf_commands = clear_interface(desired, extant) + + if intf_commands: + commands.append("interface {0}".format(key)) + commands.extend(intf_commands) return commands def set_interface(want, have): commands = [] - wants_access = want["access"] + wants_access = want.get("access") if wants_access: access_vlan = wants_access.get("vlan") if access_vlan and access_vlan != have.get("access", {}).get("vlan"): commands.append("switchport access vlan {0}".format(access_vlan)) - wants_trunk = want["trunk"] + wants_trunk = want.get("trunk") if wants_trunk: has_trunk = have.get("trunk", {}) native_vlan = wants_trunk.get("native_vlan") @@ -210,9 +224,6 @@ def set_interface(want, have): if allowed_vlans: allowed_vlans = ','.join(allowed_vlans) commands.append("switchport trunk allowed vlan {0}".format(allowed_vlans)) - - if commands: - commands.insert(0, "interface {0}".format(want['name'])) return commands @@ -227,7 +238,4 @@ def clear_interface(want, have): commands.append("no switchport trunk allowed vlan") if "native_vlan" in has_trunk and "native_vlan" not in wants_trunk: commands.append("no switchport trunk native vlan") - - if commands: - commands.insert(0, "interface {0}".format(want["name"])) return commands diff --git a/lib/ansible/module_utils/network/eos/config/l3_interfaces/l3_interfaces.py b/lib/ansible/module_utils/network/eos/config/l3_interfaces/l3_interfaces.py index e60d7232e4..08af1996fc 100644 --- a/lib/ansible/module_utils/network/eos/config/l3_interfaces/l3_interfaces.py +++ b/lib/ansible/module_utils/network/eos/config/l3_interfaces/l3_interfaces.py @@ -13,10 +13,10 @@ created from __future__ import (absolute_import, division, print_function) __metaclass__ = type -from ansible.module_utils.network.common.utils import to_list - from ansible.module_utils.network.common.cfg.base import ConfigBase +from ansible.module_utils.network.common.utils import to_list, param_list_to_dict from ansible.module_utils.network.eos.facts.facts import Facts +from ansible.module_utils.network.eos.utils.utils import normalize_interface class L3_interfaces(ConfigBase): @@ -95,6 +95,8 @@ class L3_interfaces(ConfigBase): to the desired configuration """ state = self._module.params['state'] + want = param_list_to_dict(want) + have = param_list_to_dict(have) if state == 'overridden': commands = self._state_overridden(want, have) elif state == 'deleted': @@ -114,18 +116,18 @@ class L3_interfaces(ConfigBase): to the desired configuration """ commands = [] - for interface in want: - for extant in have: - if interface["name"] == extant["name"]: - break + for key, desired in want.items(): + interface_name = normalize_interface(key) + if interface_name in have: + extant = have[interface_name] else: - extant = dict(name=interface["name"]) + extant = dict() - intf_commands = set_interface(interface, extant) - intf_commands.extend(clear_interface(interface, extant)) + intf_commands = set_interface(desired, extant) + intf_commands.extend(clear_interface(desired, extant)) if intf_commands: - commands.append("interface {0}".format(interface["name"])) + commands.append("interface {0}".format(interface_name)) commands.extend(intf_commands) return commands @@ -139,22 +141,21 @@ class L3_interfaces(ConfigBase): to the desired configuration """ commands = [] - for extant in have: - for interface in want: - if extant["name"] == interface["name"]: - break + for key, extant in have.items(): + if key in want: + desired = want[key] else: - interface = dict(name=extant["name"]) - if interface.get("ipv4"): - for ipv4 in interface["ipv4"]: + desired = dict() + if desired.get("ipv4"): + for ipv4 in desired["ipv4"]: if ipv4["secondary"] is None: del ipv4["secondary"] - intf_commands = set_interface(interface, extant) - intf_commands.extend(clear_interface(interface, extant)) + intf_commands = set_interface(desired, extant) + intf_commands.extend(clear_interface(desired, extant)) if intf_commands: - commands.append("interface {0}".format(interface["name"])) + commands.append("interface {0}".format(key)) commands.extend(intf_commands) return commands @@ -168,17 +169,17 @@ class L3_interfaces(ConfigBase): the current configuration """ commands = [] - for interface in want: - for extant in have: - if extant["name"] == interface["name"]: - break + for key, desired in want.items(): + interface_name = normalize_interface(key) + if interface_name in have: + extant = have[interface_name] else: - extant = dict(name=interface["name"]) + extant = dict() - intf_commands = set_interface(interface, extant) + intf_commands = set_interface(desired, extant) if intf_commands: - commands.append("interface {0}".format(interface["name"])) + commands.append("interface {0}".format(interface_name)) commands.extend(intf_commands) return commands @@ -192,19 +193,17 @@ class L3_interfaces(ConfigBase): of the provided objects """ commands = [] - for interface in want: - for extant in have: - if extant["name"] == interface["name"]: - break + for key in want: + desired = dict() + if key in have: + extant = have[key] else: continue - # Clearing all args, send empty dictionary - interface = dict(name=interface["name"]) - intf_commands = clear_interface(interface, extant) + intf_commands = clear_interface(desired, extant) if intf_commands: - commands.append("interface {0}".format(interface["name"])) + commands.append("interface {0}".format(key)) commands.extend(intf_commands) return commands @@ -244,7 +243,7 @@ def clear_interface(want, have): if not want_ipv4: commands.append("no ip address") else: - for address in (have_ipv4 - want_ipv4): + for address in have_ipv4 - want_ipv4: address = dict(address) if "secondary" not in address: address["secondary"] = False diff --git a/lib/ansible/module_utils/network/eos/config/lacp_interfaces/lacp_interfaces.py b/lib/ansible/module_utils/network/eos/config/lacp_interfaces/lacp_interfaces.py index 8b057756e8..6af98a51cc 100644 --- a/lib/ansible/module_utils/network/eos/config/lacp_interfaces/lacp_interfaces.py +++ b/lib/ansible/module_utils/network/eos/config/lacp_interfaces/lacp_interfaces.py @@ -1,4 +1,3 @@ -# # -*- coding: utf-8 -*- # Copyright 2019 Red Hat # GNU General Public License v3.0+ @@ -17,6 +16,7 @@ __metaclass__ = type from ansible.module_utils.network.common.cfg.base import ConfigBase from ansible.module_utils.network.common.utils import to_list, dict_diff, param_list_to_dict from ansible.module_utils.network.eos.facts.facts import Facts +from ansible.module_utils.network.eos.utils.utils import normalize_interface class Lacp_interfaces(ConfigBase): @@ -33,9 +33,6 @@ class Lacp_interfaces(ConfigBase): 'lacp_interfaces', ] - def __init__(self, module): - super(Lacp_interfaces, self).__init__(module) - def get_lacp_interfaces_facts(self): """ Get the 'facts' (the current configuration) @@ -120,8 +117,9 @@ class Lacp_interfaces(ConfigBase): """ commands = [] for key, desired in want.items(): - if key in have: - extant = have[key] + interface_name = normalize_interface(key) + if interface_name in have: + extant = have[interface_name] else: extant = dict() @@ -164,8 +162,9 @@ class Lacp_interfaces(ConfigBase): """ commands = [] for key, desired in want.items(): - if key in have: - extant = have[key] + interface_name = normalize_interface(key) + if interface_name in have: + extant = have[interface_name] else: extant = dict() @@ -184,12 +183,12 @@ class Lacp_interfaces(ConfigBase): of the provided objects """ commands = [] - for key in want.keys(): + for key in want: desired = dict() if key in have: extant = have[key] else: - extant = dict() + continue del_config = dict_diff(desired, extant) diff --git a/lib/ansible/module_utils/network/eos/config/lag_interfaces/lag_interfaces.py b/lib/ansible/module_utils/network/eos/config/lag_interfaces/lag_interfaces.py index ec9cbf98a9..f9affa404a 100644 --- a/lib/ansible/module_utils/network/eos/config/lag_interfaces/lag_interfaces.py +++ b/lib/ansible/module_utils/network/eos/config/lag_interfaces/lag_interfaces.py @@ -17,6 +17,7 @@ from ansible.module_utils.network.common.utils import to_list, dict_diff from ansible.module_utils.network.common.cfg.base import ConfigBase from ansible.module_utils.network.eos.facts.facts import Facts +from ansible.module_utils.network.eos.utils.utils import normalize_interface class Lag_interfaces(ConfigBase): @@ -114,11 +115,12 @@ class Lag_interfaces(ConfigBase): """ commands = [] for interface in want: + interface_name = normalize_interface(interface["name"]) for extant in have: - if extant["name"] == interface["name"]: + if extant["name"] == interface_name: break else: - extant = dict(name=interface["name"]) + extant = dict(name=interface_name) commands.extend(set_config(interface, extant)) commands.extend(remove_config(interface, extant)) @@ -135,18 +137,19 @@ class Lag_interfaces(ConfigBase): commands = [] for extant in have: for interface in want: - if interface["name"] == extant["name"]: + if normalize_interface(interface["name"]) == extant["name"]: break else: interface = dict(name=extant["name"]) commands.extend(remove_config(interface, extant)) for interface in want: + interface_name = normalize_interface(interface["name"]) for extant in have: - if extant["name"] == interface["name"]: + if extant["name"] == interface_name: break else: - extant = dict(name=interface["name"]) + extant = dict(name=interface_name) commands.extend(set_config(interface, extant)) return commands @@ -160,11 +163,12 @@ class Lag_interfaces(ConfigBase): """ commands = [] for interface in want: + interface_name = normalize_interface(interface["name"]) for extant in have: - if extant["name"] == interface["name"]: + if extant["name"] == interface_name: break else: - extant = dict(name=interface["name"]) + extant = dict(name=interface_name) commands.extend(set_config(interface, extant)) @@ -179,14 +183,15 @@ class Lag_interfaces(ConfigBase): """ commands = [] for interface in want: + interface_name = normalize_interface(interface["name"]) for extant in have: - if extant["name"] == interface["name"]: + if extant["name"] == interface_name: break else: - continue + extant = dict(name=interface_name) # Clearing all args, send empty dictionary - interface = dict(name=interface["name"]) + interface = dict(name=interface_name) commands.extend(remove_config(interface, extant)) return commands diff --git a/lib/ansible/module_utils/network/eos/config/lldp_interfaces/lldp_interfaces.py b/lib/ansible/module_utils/network/eos/config/lldp_interfaces/lldp_interfaces.py index cfa56b554e..93dcd8db9d 100644 --- a/lib/ansible/module_utils/network/eos/config/lldp_interfaces/lldp_interfaces.py +++ b/lib/ansible/module_utils/network/eos/config/lldp_interfaces/lldp_interfaces.py @@ -1,4 +1,3 @@ -# # -*- coding: utf-8 -*- # Copyright 2019 Red Hat # GNU General Public License v3.0+ @@ -17,6 +16,7 @@ __metaclass__ = type from ansible.module_utils.network.common.cfg.base import ConfigBase from ansible.module_utils.network.common.utils import to_list, dict_diff, param_list_to_dict from ansible.module_utils.network.eos.facts.facts import Facts +from ansible.module_utils.network.eos.utils.utils import normalize_interface class Lldp_interfaces(ConfigBase): @@ -117,15 +117,16 @@ class Lldp_interfaces(ConfigBase): """ commands = [] for key, desired in want.items(): - if key in have: - extant = have[key] + interface_name = normalize_interface(key) + if interface_name in have: + extant = have[interface_name] else: - extant = dict(name=key) + extant = dict(name=interface_name) add_config = dict_diff(extant, desired) del_config = dict_diff(desired, extant) - commands.extend(generate_commands(key, add_config, del_config)) + commands.extend(generate_commands(interface_name, add_config, del_config)) return commands @@ -161,14 +162,15 @@ class Lldp_interfaces(ConfigBase): """ commands = [] for key, desired in want.items(): - if key in have: - extant = have[key] + interface_name = normalize_interface(key) + if interface_name in have: + extant = have[interface_name] else: - extant = dict(name=key) + extant = dict(name=interface_name) add_config = dict_diff(extant, desired) - commands.extend(generate_commands(key, add_config, {})) + commands.extend(generate_commands(interface_name, add_config, {})) return commands @@ -182,15 +184,16 @@ class Lldp_interfaces(ConfigBase): """ commands = [] for key in want.keys(): - desired = dict(name=key) - if key in have: - extant = have[key] + interface_name = normalize_interface(key) + desired = dict(name=interface_name) + if interface_name in have: + extant = have[interface_name] else: - extant = dict(name=key) + continue del_config = dict_diff(desired, extant) - commands.extend(generate_commands(key, {}, del_config)) + commands.extend(generate_commands(interface_name, {}, del_config)) return commands diff --git a/lib/ansible/module_utils/network/eos/facts/interfaces/interfaces.py b/lib/ansible/module_utils/network/eos/facts/interfaces/interfaces.py index 5cfe2a2891..5b23e06533 100644 --- a/lib/ansible/module_utils/network/eos/facts/interfaces/interfaces.py +++ b/lib/ansible/module_utils/network/eos/facts/interfaces/interfaces.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- # Copyright 2019 Red Hat -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) """ The eos interfaces fact class It is in this file the configuration is collected from the device @@ -40,6 +41,7 @@ class InterfacesFacts(object): """ Populate the facts for interfaces :param connection: the device connection + :param ansible_facts: Facts dictionary :param data: previously collected configuration :rtype: dictionary :returns: facts @@ -55,9 +57,8 @@ class InterfacesFacts(object): obj = self.render_config(self.generated_spec, conf) if obj: objs.append(obj) - facts = {} + facts = {'interfaces': []} if objs: - facts['interfaces'] = [] params = utils.validate_config(self.argument_spec, {'config': objs}) for cfg in params['config']: facts['interfaces'].append(utils.remove_empties(cfg)) diff --git a/lib/ansible/module_utils/network/eos/facts/l2_interfaces/l2_interfaces.py b/lib/ansible/module_utils/network/eos/facts/l2_interfaces/l2_interfaces.py index 37d76ed9fc..d2684a84cb 100644 --- a/lib/ansible/module_utils/network/eos/facts/l2_interfaces/l2_interfaces.py +++ b/lib/ansible/module_utils/network/eos/facts/l2_interfaces/l2_interfaces.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- # Copyright 2019 Red Hat -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) """ The eos l2_interfaces fact class It is in this file the configuration is collected from the device @@ -39,9 +40,9 @@ class L2_interfacesFacts(object): def populate_facts(self, connection, ansible_facts, data=None): """ Populate the facts for l2_interfaces - :param module: the module instance :param connection: the device connection - :param data: previously collected conf + :param ansible_facts: Facts dictionary + :param data: previously collected configuration :rtype: dictionary :returns: facts """ diff --git a/lib/ansible/module_utils/network/eos/facts/l3_interfaces/l3_interfaces.py b/lib/ansible/module_utils/network/eos/facts/l3_interfaces/l3_interfaces.py index f90a7edc20..67041729df 100644 --- a/lib/ansible/module_utils/network/eos/facts/l3_interfaces/l3_interfaces.py +++ b/lib/ansible/module_utils/network/eos/facts/l3_interfaces/l3_interfaces.py @@ -40,11 +40,11 @@ class L3_interfacesFacts(object): def populate_facts(self, connection, ansible_facts, data=None): """ Populate the facts for l3_interfaces :param connection: the device connection + :param ansible_facts: Facts dictionary :param data: previously collected configuration :rtype: dictionary :returns: facts """ - if not data: data = connection.get('show running-config | section ^interface') diff --git a/lib/ansible/module_utils/network/eos/facts/lacp/lacp.py b/lib/ansible/module_utils/network/eos/facts/lacp/lacp.py index 1d17c44e88..720254787d 100644 --- a/lib/ansible/module_utils/network/eos/facts/lacp/lacp.py +++ b/lib/ansible/module_utils/network/eos/facts/lacp/lacp.py @@ -41,7 +41,7 @@ class LacpFacts(object): """ Populate the facts for lacp :param connection: the device connection :param ansible_facts: Facts dictionary - :param data: previously collected conf + :param data: previously collected configuration :rtype: dictionary :returns: facts """ diff --git a/lib/ansible/module_utils/network/eos/facts/lacp_interfaces/lacp_interfaces.py b/lib/ansible/module_utils/network/eos/facts/lacp_interfaces/lacp_interfaces.py index 45efdf35f2..7c1076c5ab 100644 --- a/lib/ansible/module_utils/network/eos/facts/lacp_interfaces/lacp_interfaces.py +++ b/lib/ansible/module_utils/network/eos/facts/lacp_interfaces/lacp_interfaces.py @@ -1,4 +1,3 @@ -# # -*- coding: utf-8 -*- # Copyright 2019 Red Hat # GNU General Public License v3.0+ @@ -42,7 +41,7 @@ class Lacp_interfacesFacts(object): """ Populate the facts for lacp_interfaces :param connection: the device connection :param ansible_facts: Facts dictionary - :param data: previously collected conf + :param data: previously collected configuration :rtype: dictionary :returns: facts """ diff --git a/lib/ansible/module_utils/network/eos/facts/lag_interfaces/lag_interfaces.py b/lib/ansible/module_utils/network/eos/facts/lag_interfaces/lag_interfaces.py index 788fbbf845..d93152a01b 100644 --- a/lib/ansible/module_utils/network/eos/facts/lag_interfaces/lag_interfaces.py +++ b/lib/ansible/module_utils/network/eos/facts/lag_interfaces/lag_interfaces.py @@ -40,6 +40,7 @@ class Lag_interfacesFacts(object): def populate_facts(self, connection, ansible_facts, data=None): """ Populate the facts for lag_interfaces :param connection: the device connection + :param ansible_facts: Facts dictionary :param data: previously collected configuration :rtype: dictionary :returns: facts @@ -69,7 +70,7 @@ class Lag_interfacesFacts(object): else: objs[group_name] = obj objs = list(objs.values()) - facts = {} + facts = {'lag_interfaces': []} if objs: params = utils.validate_config(self.argument_spec, {'config': objs}) facts['lag_interfaces'] = [utils.remove_empties(cfg) for cfg in params['config']] diff --git a/lib/ansible/module_utils/network/eos/facts/lldp_interfaces/lldp_interfaces.py b/lib/ansible/module_utils/network/eos/facts/lldp_interfaces/lldp_interfaces.py index 72d35438a8..8fa02697f6 100644 --- a/lib/ansible/module_utils/network/eos/facts/lldp_interfaces/lldp_interfaces.py +++ b/lib/ansible/module_utils/network/eos/facts/lldp_interfaces/lldp_interfaces.py @@ -1,4 +1,3 @@ -# # -*- coding: utf-8 -*- # Copyright 2019 Red Hat # GNU General Public License v3.0+ @@ -42,7 +41,7 @@ class Lldp_interfacesFacts(object): """ Populate the facts for lldp_interfaces :param connection: the device connection :param ansible_facts: Facts dictionary - :param data: previously collected conf + :param data: previously collected configuration :rtype: dictionary :returns: facts """ diff --git a/lib/ansible/module_utils/network/eos/utils/__init__.py b/lib/ansible/module_utils/network/eos/utils/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lib/ansible/module_utils/network/eos/utils/utils.py b/lib/ansible/module_utils/network/eos/utils/utils.py new file mode 100644 index 0000000000..beb3a6fd2e --- /dev/null +++ b/lib/ansible/module_utils/network/eos/utils/utils.py @@ -0,0 +1,54 @@ +# -*- coding: utf-8 -*- +# Copyright 2019 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +# utils + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +def get_interface_number(name): + digits = '' + for char in name: + if char.isdigit() or char in '/.': + digits += char + return digits + + +def normalize_interface(name): + """Return the normalized interface name + """ + if not name: + return None + + if name.lower().startswith('et'): + if_type = 'Ethernet' + elif name.lower().startswith('lo'): + if_type = 'Loopback' + elif name.lower().startswith('ma'): + if_type = 'Management' + elif name.lower().startswith('po'): + if_type = 'Port-Channel' + elif name.lower().startswith('tu'): + if_type = 'Tunnel' + elif name.lower().startswith('vl'): + if_type = 'Vlan' + elif name.lower().startswith('vx'): + if_type = 'Vxlan' + else: + if_type = None + + number_list = name.split(' ') + if len(number_list) == 2: + number = number_list[-1].strip() + else: + number = get_interface_number(name) + + if if_type: + proper_interface = if_type + number + else: + proper_interface = name + + return proper_interface diff --git a/lib/ansible/modules/network/eos/eos_interfaces.py b/lib/ansible/modules/network/eos/eos_interfaces.py index eae8014060..9e421264ec 100644 --- a/lib/ansible/modules/network/eos/eos_interfaces.py +++ b/lib/ansible/modules/network/eos/eos_interfaces.py @@ -1,7 +1,8 @@ #!/usr/bin/python # -*- coding: utf-8 -*- # Copyright 2019 Red Hat -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) ############################################## # WARNING # @@ -41,6 +42,10 @@ version_added: 2.9 short_description: Manages interface attributes of Arista EOS interfaces description: ['This module manages the interface attributes of Arista EOS interfaces.'] author: ['Nathaniel Case (@qalthos)'] +notes: +- Tested against Arista EOS 4.20.10M +- This module works with connection C(network_cli). See the + L(EOS Platform Options,../network/user_guide/platform_eos.html). options: config: description: The provided configuration diff --git a/lib/ansible/modules/network/eos/eos_l3_interfaces.py b/lib/ansible/modules/network/eos/eos_l3_interfaces.py index cf1511450e..38f8077c0e 100644 --- a/lib/ansible/modules/network/eos/eos_l3_interfaces.py +++ b/lib/ansible/modules/network/eos/eos_l3_interfaces.py @@ -43,7 +43,7 @@ short_description: 'Manages L3 interface attributes of Arista EOS devices.' description: 'This module provides declarative management of Layer 3 interfaces on Arista EOS devices.' author: Nathaniel Case (@qalthos) notes: -- 'Tested against vEOS v4.20.x' +- Tested against Arista EOS 4.20.10M - This module works with connection C(network_cli). See the L(EOS Platform Options,../network/user_guide/platform_eos.html). options: diff --git a/lib/ansible/modules/network/eos/eos_lacp_interfaces.py b/lib/ansible/modules/network/eos/eos_lacp_interfaces.py index 0dc120c67b..246c3ed5a2 100644 --- a/lib/ansible/modules/network/eos/eos_lacp_interfaces.py +++ b/lib/ansible/modules/network/eos/eos_lacp_interfaces.py @@ -43,6 +43,10 @@ short_description: Manage Link Aggregation Control Protocol (LACP) attributes of description: - This module manages Link Aggregation Control Protocol (LACP) attributes of interfaces on Arista EOS devices. author: Nathaniel Case (@Qalthos) +notes: +- Tested against Arista EOS 4.20.10M +- This module works with connection C(network_cli). See the + L(EOS Platform Options,../network/user_guide/platform_eos.html). options: config: description: A dictionary of LACP interfaces options. diff --git a/lib/ansible/modules/network/eos/eos_lag_interfaces.py b/lib/ansible/modules/network/eos/eos_lag_interfaces.py index e07363428c..a82102e29b 100644 --- a/lib/ansible/modules/network/eos/eos_lag_interfaces.py +++ b/lib/ansible/modules/network/eos/eos_lag_interfaces.py @@ -44,7 +44,7 @@ short_description: Manages link aggregation groups on Arista EOS devices description: This module manages attributes of link aggregation groups on Arista EOS devices. author: Nathaniel Case (@Qalthos) notes: -- 'Tested against vEOS v4.20.x' +- Tested against Arista EOS 4.20.10M - This module works with connection C(network_cli). See the L(EOS Platform Options,../network/user_guide/platform_eos.html). options: diff --git a/lib/ansible/modules/network/eos/eos_lldp_global.py b/lib/ansible/modules/network/eos/eos_lldp_global.py index dac48647b5..e9799a0cd6 100644 --- a/lib/ansible/modules/network/eos/eos_lldp_global.py +++ b/lib/ansible/modules/network/eos/eos_lldp_global.py @@ -43,6 +43,10 @@ short_description: Manage Global Link Layer Discovery Protocol (LLDP) settings o description: - This module manages Global Link Layer Discovery Protocol (LLDP) settings on Arista EOS devices. author: Nathaniel Case (@Qalthos) +notes: +- Tested against Arista EOS 4.20.10M +- This module works with connection C(network_cli). See the + L(EOS Platform Options,../network/user_guide/platform_eos.html). options: config: description: The provided global LLDP configuration. diff --git a/lib/ansible/modules/network/eos/eos_lldp_interfaces.py b/lib/ansible/modules/network/eos/eos_lldp_interfaces.py index 640e519364..09e3178849 100644 --- a/lib/ansible/modules/network/eos/eos_lldp_interfaces.py +++ b/lib/ansible/modules/network/eos/eos_lldp_interfaces.py @@ -43,6 +43,10 @@ short_description: Manage Link Layer Discovery Protocol (LLDP) attributes of int description: - This module manages Link Layer Discovery Protocol (LLDP) attributes of interfaces on Arista EOS devices. author: Nathaniel Case (@Qalthos) +notes: +- Tested against Arista EOS 4.20.10M +- This module works with connection C(network_cli). See the + L(EOS Platform Options,../network/user_guide/platform_eos.html). options: config: description: A dictionary of LLDP interfaces options. diff --git a/test/integration/targets/eos_lag_interfaces/tests/cli/deleted.yaml b/test/integration/targets/eos_lag_interfaces/tests/cli/deleted.yaml index 4069fa0191..dbf63d64bf 100644 --- a/test/integration/targets/eos_lag_interfaces/tests/cli/deleted.yaml +++ b/test/integration/targets/eos_lag_interfaces/tests/cli/deleted.yaml @@ -26,4 +26,4 @@ - assert: that: - - "'lag_interfaces' not in ansible_facts.network_resources" + - "ansible_facts.network_resources.lag_interfaces == []"