[stable-2.9] 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
(cherry picked from commit 7917d4def7)

Co-authored-by: Nathaniel Case <ncase@redhat.com>
This commit is contained in:
Nathaniel Case 2019-09-13 09:35:50 -04:00 committed by Toshio Kuratomi
parent f082d23eec
commit 9d6282e633
23 changed files with 350 additions and 265 deletions

View file

@ -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.

View file

@ -1,6 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright 2019 Red Hat # 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 The eos_interfaces class
It is in this file where the current configuration (as dict) It is in this file where the current configuration (as dict)
@ -12,10 +13,10 @@ created
from __future__ import (absolute_import, division, print_function) from __future__ import (absolute_import, division, print_function)
__metaclass__ = type __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.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.facts.facts import Facts
from ansible.module_utils.network.eos.utils.utils import normalize_interface
class Interfaces(ConfigBase): class Interfaces(ConfigBase):
@ -93,148 +94,143 @@ class Interfaces(ConfigBase):
:returns: the commands necessary to migrate the current configuration :returns: the commands necessary to migrate the current configuration
to the desired configuration to the desired configuration
""" """
state = self._module.params['state']
want = param_list_to_dict(want) want = param_list_to_dict(want)
have = param_list_to_dict(have) have = param_list_to_dict(have)
state = self._module.params['state']
if state == 'overridden': if state == 'overridden':
commands = state_overridden(want, have) commands = self._state_overridden(want, have)
elif state == 'deleted': elif state == 'deleted':
commands = state_deleted(want, have) commands = self._state_deleted(want, have)
elif state == 'merged': elif state == 'merged':
commands = state_merged(want, have) commands = self._state_merged(want, have)
elif state == 'replaced': 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 return commands
def state_replaced(want, have): def generate_commands(interface, to_set, to_remove):
""" 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):
commands = [] commands = []
for interface, interface_commands in command_dict.items(): for key, value in to_set.items():
commands.append('interface {0}'.format(interface)) if value is None:
commands.extend(interface_commands) 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 return commands

View file

@ -1,6 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright 2019 Red Hat # 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 The eos_l2_interfaces class
It is in this file where the current configuration (as dict) It is in this file where the current configuration (as dict)
@ -12,10 +13,10 @@ created
from __future__ import (absolute_import, division, print_function) from __future__ import (absolute_import, division, print_function)
__metaclass__ = type __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.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.facts.facts import Facts
from ansible.module_utils.network.eos.utils.utils import normalize_interface
class L2_interfaces(ConfigBase): class L2_interfaces(ConfigBase):
@ -94,6 +95,8 @@ class L2_interfaces(ConfigBase):
to the desired configuration to the desired configuration
""" """
state = self._module.params['state'] state = self._module.params['state']
want = param_list_to_dict(want)
have = param_list_to_dict(have)
if state == 'overridden': if state == 'overridden':
commands = self._state_overridden(want, have) commands = self._state_overridden(want, have)
elif state == 'deleted': elif state == 'deleted':
@ -113,15 +116,20 @@ class L2_interfaces(ConfigBase):
to the desired configuration to the desired configuration
""" """
commands = [] commands = []
for interface in want: for key, desired in want.items():
for extant in have: interface_name = normalize_interface(key)
if extant['name'] == interface['name']: if interface_name in have:
break extant = have[interface_name]
else: 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 return commands
@staticmethod @staticmethod
@ -133,19 +141,19 @@ class L2_interfaces(ConfigBase):
to the desired configuration to the desired configuration
""" """
commands = [] commands = []
for extant in have: for key, extant in have.items():
for interface in want: if key in want:
if extant['name'] == interface['name']: desired = want[key]
break
else: else:
# We didn't find a matching desired state, which means we can desired = dict()
# pretend we recieved an empty desired state.
interface = dict(name=extant['name']) intf_commands = set_interface(desired, extant)
commands.extend(clear_interface(interface, extant)) intf_commands.extend(clear_interface(desired, extant))
continue
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 return commands
@staticmethod @staticmethod
@ -157,14 +165,18 @@ class L2_interfaces(ConfigBase):
the current configuration the current configuration
""" """
commands = [] commands = []
for interface in want: for key, desired in want.items():
for extant in have: interface_name = normalize_interface(key)
if extant['name'] == interface['name']: if interface_name in have:
break extant = have[interface_name]
else: 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 return commands
@ -177,29 +189,31 @@ class L2_interfaces(ConfigBase):
of the provided objects of the provided objects
""" """
commands = [] commands = []
for interface in want: for key in want:
for extant in have: desired = dict()
if extant['name'] == interface['name']: if key in have:
break extant = have[key]
else: else:
continue continue
# Use an empty configuration, just in case intf_commands = clear_interface(desired, extant)
interface = dict(name=interface['name'])
commands.extend(clear_interface(interface, extant)) if intf_commands:
commands.append("interface {0}".format(key))
commands.extend(intf_commands)
return commands return commands
def set_interface(want, have): def set_interface(want, have):
commands = [] commands = []
wants_access = want["access"] wants_access = want.get("access")
if wants_access: if wants_access:
access_vlan = wants_access.get("vlan") access_vlan = wants_access.get("vlan")
if access_vlan and access_vlan != have.get("access", {}).get("vlan"): if access_vlan and access_vlan != have.get("access", {}).get("vlan"):
commands.append("switchport access vlan {0}".format(access_vlan)) commands.append("switchport access vlan {0}".format(access_vlan))
wants_trunk = want["trunk"] wants_trunk = want.get("trunk")
if wants_trunk: if wants_trunk:
has_trunk = have.get("trunk", {}) has_trunk = have.get("trunk", {})
native_vlan = wants_trunk.get("native_vlan") native_vlan = wants_trunk.get("native_vlan")
@ -210,9 +224,6 @@ def set_interface(want, have):
if allowed_vlans: if allowed_vlans:
allowed_vlans = ','.join(allowed_vlans) allowed_vlans = ','.join(allowed_vlans)
commands.append("switchport trunk allowed vlan {0}".format(allowed_vlans)) commands.append("switchport trunk allowed vlan {0}".format(allowed_vlans))
if commands:
commands.insert(0, "interface {0}".format(want['name']))
return commands return commands
@ -227,7 +238,4 @@ def clear_interface(want, have):
commands.append("no switchport trunk allowed vlan") commands.append("no switchport trunk allowed vlan")
if "native_vlan" in has_trunk and "native_vlan" not in wants_trunk: if "native_vlan" in has_trunk and "native_vlan" not in wants_trunk:
commands.append("no switchport trunk native vlan") commands.append("no switchport trunk native vlan")
if commands:
commands.insert(0, "interface {0}".format(want["name"]))
return commands return commands

View file

@ -13,10 +13,10 @@ created
from __future__ import (absolute_import, division, print_function) from __future__ import (absolute_import, division, print_function)
__metaclass__ = type __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.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.facts.facts import Facts
from ansible.module_utils.network.eos.utils.utils import normalize_interface
class L3_interfaces(ConfigBase): class L3_interfaces(ConfigBase):
@ -95,6 +95,8 @@ class L3_interfaces(ConfigBase):
to the desired configuration to the desired configuration
""" """
state = self._module.params['state'] state = self._module.params['state']
want = param_list_to_dict(want)
have = param_list_to_dict(have)
if state == 'overridden': if state == 'overridden':
commands = self._state_overridden(want, have) commands = self._state_overridden(want, have)
elif state == 'deleted': elif state == 'deleted':
@ -114,18 +116,18 @@ class L3_interfaces(ConfigBase):
to the desired configuration to the desired configuration
""" """
commands = [] commands = []
for interface in want: for key, desired in want.items():
for extant in have: interface_name = normalize_interface(key)
if interface["name"] == extant["name"]: if interface_name in have:
break extant = have[interface_name]
else: else:
extant = dict(name=interface["name"]) extant = dict()
intf_commands = set_interface(interface, extant) intf_commands = set_interface(desired, extant)
intf_commands.extend(clear_interface(interface, extant)) intf_commands.extend(clear_interface(desired, extant))
if intf_commands: if intf_commands:
commands.append("interface {0}".format(interface["name"])) commands.append("interface {0}".format(interface_name))
commands.extend(intf_commands) commands.extend(intf_commands)
return commands return commands
@ -139,22 +141,21 @@ class L3_interfaces(ConfigBase):
to the desired configuration to the desired configuration
""" """
commands = [] commands = []
for extant in have: for key, extant in have.items():
for interface in want: if key in want:
if extant["name"] == interface["name"]: desired = want[key]
break
else: else:
interface = dict(name=extant["name"]) desired = dict()
if interface.get("ipv4"): if desired.get("ipv4"):
for ipv4 in interface["ipv4"]: for ipv4 in desired["ipv4"]:
if ipv4["secondary"] is None: if ipv4["secondary"] is None:
del ipv4["secondary"] del ipv4["secondary"]
intf_commands = set_interface(interface, extant) intf_commands = set_interface(desired, extant)
intf_commands.extend(clear_interface(interface, extant)) intf_commands.extend(clear_interface(desired, extant))
if intf_commands: if intf_commands:
commands.append("interface {0}".format(interface["name"])) commands.append("interface {0}".format(key))
commands.extend(intf_commands) commands.extend(intf_commands)
return commands return commands
@ -168,17 +169,17 @@ class L3_interfaces(ConfigBase):
the current configuration the current configuration
""" """
commands = [] commands = []
for interface in want: for key, desired in want.items():
for extant in have: interface_name = normalize_interface(key)
if extant["name"] == interface["name"]: if interface_name in have:
break extant = have[interface_name]
else: else:
extant = dict(name=interface["name"]) extant = dict()
intf_commands = set_interface(interface, extant) intf_commands = set_interface(desired, extant)
if intf_commands: if intf_commands:
commands.append("interface {0}".format(interface["name"])) commands.append("interface {0}".format(interface_name))
commands.extend(intf_commands) commands.extend(intf_commands)
return commands return commands
@ -192,19 +193,17 @@ class L3_interfaces(ConfigBase):
of the provided objects of the provided objects
""" """
commands = [] commands = []
for interface in want: for key in want:
for extant in have: desired = dict()
if extant["name"] == interface["name"]: if key in have:
break extant = have[key]
else: else:
continue continue
# Clearing all args, send empty dictionary intf_commands = clear_interface(desired, extant)
interface = dict(name=interface["name"])
intf_commands = clear_interface(interface, extant)
if intf_commands: if intf_commands:
commands.append("interface {0}".format(interface["name"])) commands.append("interface {0}".format(key))
commands.extend(intf_commands) commands.extend(intf_commands)
return commands return commands
@ -244,7 +243,7 @@ def clear_interface(want, have):
if not want_ipv4: if not want_ipv4:
commands.append("no ip address") commands.append("no ip address")
else: else:
for address in (have_ipv4 - want_ipv4): for address in have_ipv4 - want_ipv4:
address = dict(address) address = dict(address)
if "secondary" not in address: if "secondary" not in address:
address["secondary"] = False address["secondary"] = False

View file

@ -1,4 +1,3 @@
#
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright 2019 Red Hat # Copyright 2019 Red Hat
# GNU General Public License v3.0+ # 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.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.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.facts.facts import Facts
from ansible.module_utils.network.eos.utils.utils import normalize_interface
class Lacp_interfaces(ConfigBase): class Lacp_interfaces(ConfigBase):
@ -33,9 +33,6 @@ class Lacp_interfaces(ConfigBase):
'lacp_interfaces', 'lacp_interfaces',
] ]
def __init__(self, module):
super(Lacp_interfaces, self).__init__(module)
def get_lacp_interfaces_facts(self): def get_lacp_interfaces_facts(self):
""" Get the 'facts' (the current configuration) """ Get the 'facts' (the current configuration)
@ -120,8 +117,9 @@ class Lacp_interfaces(ConfigBase):
""" """
commands = [] commands = []
for key, desired in want.items(): for key, desired in want.items():
if key in have: interface_name = normalize_interface(key)
extant = have[key] if interface_name in have:
extant = have[interface_name]
else: else:
extant = dict() extant = dict()
@ -164,8 +162,9 @@ class Lacp_interfaces(ConfigBase):
""" """
commands = [] commands = []
for key, desired in want.items(): for key, desired in want.items():
if key in have: interface_name = normalize_interface(key)
extant = have[key] if interface_name in have:
extant = have[interface_name]
else: else:
extant = dict() extant = dict()
@ -184,12 +183,12 @@ class Lacp_interfaces(ConfigBase):
of the provided objects of the provided objects
""" """
commands = [] commands = []
for key in want.keys(): for key in want:
desired = dict() desired = dict()
if key in have: if key in have:
extant = have[key] extant = have[key]
else: else:
extant = dict() continue
del_config = dict_diff(desired, extant) del_config = dict_diff(desired, extant)

View file

@ -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.common.cfg.base import ConfigBase
from ansible.module_utils.network.eos.facts.facts import Facts 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): class Lag_interfaces(ConfigBase):
@ -114,11 +115,12 @@ class Lag_interfaces(ConfigBase):
""" """
commands = [] commands = []
for interface in want: for interface in want:
interface_name = normalize_interface(interface["name"])
for extant in have: for extant in have:
if extant["name"] == interface["name"]: if extant["name"] == interface_name:
break break
else: else:
extant = dict(name=interface["name"]) extant = dict(name=interface_name)
commands.extend(set_config(interface, extant)) commands.extend(set_config(interface, extant))
commands.extend(remove_config(interface, extant)) commands.extend(remove_config(interface, extant))
@ -135,18 +137,19 @@ class Lag_interfaces(ConfigBase):
commands = [] commands = []
for extant in have: for extant in have:
for interface in want: for interface in want:
if interface["name"] == extant["name"]: if normalize_interface(interface["name"]) == extant["name"]:
break break
else: else:
interface = dict(name=extant["name"]) interface = dict(name=extant["name"])
commands.extend(remove_config(interface, extant)) commands.extend(remove_config(interface, extant))
for interface in want: for interface in want:
interface_name = normalize_interface(interface["name"])
for extant in have: for extant in have:
if extant["name"] == interface["name"]: if extant["name"] == interface_name:
break break
else: else:
extant = dict(name=interface["name"]) extant = dict(name=interface_name)
commands.extend(set_config(interface, extant)) commands.extend(set_config(interface, extant))
return commands return commands
@ -160,11 +163,12 @@ class Lag_interfaces(ConfigBase):
""" """
commands = [] commands = []
for interface in want: for interface in want:
interface_name = normalize_interface(interface["name"])
for extant in have: for extant in have:
if extant["name"] == interface["name"]: if extant["name"] == interface_name:
break break
else: else:
extant = dict(name=interface["name"]) extant = dict(name=interface_name)
commands.extend(set_config(interface, extant)) commands.extend(set_config(interface, extant))
@ -179,14 +183,15 @@ class Lag_interfaces(ConfigBase):
""" """
commands = [] commands = []
for interface in want: for interface in want:
interface_name = normalize_interface(interface["name"])
for extant in have: for extant in have:
if extant["name"] == interface["name"]: if extant["name"] == interface_name:
break break
else: else:
continue extant = dict(name=interface_name)
# Clearing all args, send empty dictionary # Clearing all args, send empty dictionary
interface = dict(name=interface["name"]) interface = dict(name=interface_name)
commands.extend(remove_config(interface, extant)) commands.extend(remove_config(interface, extant))
return commands return commands

View file

@ -1,4 +1,3 @@
#
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright 2019 Red Hat # Copyright 2019 Red Hat
# GNU General Public License v3.0+ # 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.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.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.facts.facts import Facts
from ansible.module_utils.network.eos.utils.utils import normalize_interface
class Lldp_interfaces(ConfigBase): class Lldp_interfaces(ConfigBase):
@ -117,15 +117,16 @@ class Lldp_interfaces(ConfigBase):
""" """
commands = [] commands = []
for key, desired in want.items(): for key, desired in want.items():
if key in have: interface_name = normalize_interface(key)
extant = have[key] if interface_name in have:
extant = have[interface_name]
else: else:
extant = dict(name=key) extant = dict(name=interface_name)
add_config = dict_diff(extant, desired) add_config = dict_diff(extant, desired)
del_config = dict_diff(desired, extant) 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 return commands
@ -161,14 +162,15 @@ class Lldp_interfaces(ConfigBase):
""" """
commands = [] commands = []
for key, desired in want.items(): for key, desired in want.items():
if key in have: interface_name = normalize_interface(key)
extant = have[key] if interface_name in have:
extant = have[interface_name]
else: else:
extant = dict(name=key) extant = dict(name=interface_name)
add_config = dict_diff(extant, desired) add_config = dict_diff(extant, desired)
commands.extend(generate_commands(key, add_config, {})) commands.extend(generate_commands(interface_name, add_config, {}))
return commands return commands
@ -182,15 +184,16 @@ class Lldp_interfaces(ConfigBase):
""" """
commands = [] commands = []
for key in want.keys(): for key in want.keys():
desired = dict(name=key) interface_name = normalize_interface(key)
if key in have: desired = dict(name=interface_name)
extant = have[key] if interface_name in have:
extant = have[interface_name]
else: else:
extant = dict(name=key) continue
del_config = dict_diff(desired, extant) del_config = dict_diff(desired, extant)
commands.extend(generate_commands(key, {}, del_config)) commands.extend(generate_commands(interface_name, {}, del_config))
return commands return commands

View file

@ -1,6 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright 2019 Red Hat # 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 The eos interfaces fact class
It is in this file the configuration is collected from the device It is in this file the configuration is collected from the device
@ -40,6 +41,7 @@ class InterfacesFacts(object):
""" Populate the facts for interfaces """ Populate the facts for interfaces
:param connection: the device connection :param connection: the device connection
:param ansible_facts: Facts dictionary
:param data: previously collected configuration :param data: previously collected configuration
:rtype: dictionary :rtype: dictionary
:returns: facts :returns: facts
@ -55,9 +57,8 @@ class InterfacesFacts(object):
obj = self.render_config(self.generated_spec, conf) obj = self.render_config(self.generated_spec, conf)
if obj: if obj:
objs.append(obj) objs.append(obj)
facts = {} facts = {'interfaces': []}
if objs: if objs:
facts['interfaces'] = []
params = utils.validate_config(self.argument_spec, {'config': objs}) params = utils.validate_config(self.argument_spec, {'config': objs})
for cfg in params['config']: for cfg in params['config']:
facts['interfaces'].append(utils.remove_empties(cfg)) facts['interfaces'].append(utils.remove_empties(cfg))

View file

@ -1,6 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright 2019 Red Hat # 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 The eos l2_interfaces fact class
It is in this file the configuration is collected from the device 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): def populate_facts(self, connection, ansible_facts, data=None):
""" Populate the facts for l2_interfaces """ Populate the facts for l2_interfaces
:param module: the module instance
:param connection: the device connection :param connection: the device connection
:param data: previously collected conf :param ansible_facts: Facts dictionary
:param data: previously collected configuration
:rtype: dictionary :rtype: dictionary
:returns: facts :returns: facts
""" """

View file

@ -40,11 +40,11 @@ class L3_interfacesFacts(object):
def populate_facts(self, connection, ansible_facts, data=None): def populate_facts(self, connection, ansible_facts, data=None):
""" Populate the facts for l3_interfaces """ Populate the facts for l3_interfaces
:param connection: the device connection :param connection: the device connection
:param ansible_facts: Facts dictionary
:param data: previously collected configuration :param data: previously collected configuration
:rtype: dictionary :rtype: dictionary
:returns: facts :returns: facts
""" """
if not data: if not data:
data = connection.get('show running-config | section ^interface') data = connection.get('show running-config | section ^interface')

View file

@ -41,7 +41,7 @@ class LacpFacts(object):
""" Populate the facts for lacp """ Populate the facts for lacp
:param connection: the device connection :param connection: the device connection
:param ansible_facts: Facts dictionary :param ansible_facts: Facts dictionary
:param data: previously collected conf :param data: previously collected configuration
:rtype: dictionary :rtype: dictionary
:returns: facts :returns: facts
""" """

View file

@ -1,4 +1,3 @@
#
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright 2019 Red Hat # Copyright 2019 Red Hat
# GNU General Public License v3.0+ # GNU General Public License v3.0+
@ -42,7 +41,7 @@ class Lacp_interfacesFacts(object):
""" Populate the facts for lacp_interfaces """ Populate the facts for lacp_interfaces
:param connection: the device connection :param connection: the device connection
:param ansible_facts: Facts dictionary :param ansible_facts: Facts dictionary
:param data: previously collected conf :param data: previously collected configuration
:rtype: dictionary :rtype: dictionary
:returns: facts :returns: facts
""" """

View file

@ -40,6 +40,7 @@ class Lag_interfacesFacts(object):
def populate_facts(self, connection, ansible_facts, data=None): def populate_facts(self, connection, ansible_facts, data=None):
""" Populate the facts for lag_interfaces """ Populate the facts for lag_interfaces
:param connection: the device connection :param connection: the device connection
:param ansible_facts: Facts dictionary
:param data: previously collected configuration :param data: previously collected configuration
:rtype: dictionary :rtype: dictionary
:returns: facts :returns: facts
@ -69,7 +70,7 @@ class Lag_interfacesFacts(object):
else: else:
objs[group_name] = obj objs[group_name] = obj
objs = list(objs.values()) objs = list(objs.values())
facts = {} facts = {'lag_interfaces': []}
if objs: if objs:
params = utils.validate_config(self.argument_spec, {'config': objs}) params = utils.validate_config(self.argument_spec, {'config': objs})
facts['lag_interfaces'] = [utils.remove_empties(cfg) for cfg in params['config']] facts['lag_interfaces'] = [utils.remove_empties(cfg) for cfg in params['config']]

View file

@ -1,4 +1,3 @@
#
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright 2019 Red Hat # Copyright 2019 Red Hat
# GNU General Public License v3.0+ # GNU General Public License v3.0+
@ -42,7 +41,7 @@ class Lldp_interfacesFacts(object):
""" Populate the facts for lldp_interfaces """ Populate the facts for lldp_interfaces
:param connection: the device connection :param connection: the device connection
:param ansible_facts: Facts dictionary :param ansible_facts: Facts dictionary
:param data: previously collected conf :param data: previously collected configuration
:rtype: dictionary :rtype: dictionary
:returns: facts :returns: facts
""" """

View file

@ -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

View file

@ -1,7 +1,8 @@
#!/usr/bin/python #!/usr/bin/python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright 2019 Red Hat # 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 # # WARNING #
@ -41,6 +42,10 @@ version_added: 2.9
short_description: Manages interface attributes of Arista EOS interfaces short_description: Manages interface attributes of Arista EOS interfaces
description: ['This module manages the interface attributes of Arista EOS interfaces.'] description: ['This module manages the interface attributes of Arista EOS interfaces.']
author: ['Nathaniel Case (@qalthos)'] 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: options:
config: config:
description: The provided configuration description: The provided configuration

View file

@ -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.' description: 'This module provides declarative management of Layer 3 interfaces on Arista EOS devices.'
author: Nathaniel Case (@qalthos) author: Nathaniel Case (@qalthos)
notes: notes:
- 'Tested against vEOS v4.20.x' - Tested against Arista EOS 4.20.10M
- This module works with connection C(network_cli). See the - This module works with connection C(network_cli). See the
L(EOS Platform Options,../network/user_guide/platform_eos.html). L(EOS Platform Options,../network/user_guide/platform_eos.html).
options: options:

View file

@ -43,6 +43,10 @@ short_description: Manage Link Aggregation Control Protocol (LACP) attributes of
description: description:
- This module manages Link Aggregation Control Protocol (LACP) attributes of interfaces on Arista EOS devices. - This module manages Link Aggregation Control Protocol (LACP) attributes of interfaces on Arista EOS devices.
author: Nathaniel Case (@Qalthos) 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: options:
config: config:
description: A dictionary of LACP interfaces options. description: A dictionary of LACP interfaces options.

View file

@ -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. description: This module manages attributes of link aggregation groups on Arista EOS devices.
author: Nathaniel Case (@Qalthos) author: Nathaniel Case (@Qalthos)
notes: notes:
- 'Tested against vEOS v4.20.x' - Tested against Arista EOS 4.20.10M
- This module works with connection C(network_cli). See the - This module works with connection C(network_cli). See the
L(EOS Platform Options,../network/user_guide/platform_eos.html). L(EOS Platform Options,../network/user_guide/platform_eos.html).
options: options:

View file

@ -43,6 +43,10 @@ short_description: Manage Global Link Layer Discovery Protocol (LLDP) settings o
description: description:
- This module manages Global Link Layer Discovery Protocol (LLDP) settings on Arista EOS devices. - This module manages Global Link Layer Discovery Protocol (LLDP) settings on Arista EOS devices.
author: Nathaniel Case (@Qalthos) 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: options:
config: config:
description: The provided global LLDP configuration. description: The provided global LLDP configuration.

View file

@ -43,6 +43,10 @@ short_description: Manage Link Layer Discovery Protocol (LLDP) attributes of int
description: description:
- This module manages Link Layer Discovery Protocol (LLDP) attributes of interfaces on Arista EOS devices. - This module manages Link Layer Discovery Protocol (LLDP) attributes of interfaces on Arista EOS devices.
author: Nathaniel Case (@Qalthos) 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: options:
config: config:
description: A dictionary of LLDP interfaces options. description: A dictionary of LLDP interfaces options.

View file

@ -26,4 +26,4 @@
- assert: - assert:
that: that:
- "'lag_interfaces' not in ansible_facts.network_resources" - "ansible_facts.network_resources.lag_interfaces == []"