From cb12d4ada848173749af997def02e55081f1db26 Mon Sep 17 00:00:00 2001 From: Trishna Guha Date: Mon, 11 Mar 2019 16:12:37 +0530 Subject: [PATCH 01/21] nxos_interfaces resource module Signed-off-by: Trishna Guha --- library/nxos/nxos_interfaces.py | 70 ++++++ module_utils/argspec/__init__.py | 0 module_utils/argspec/base.py | 24 ++ module_utils/argspec/nxos/__init__.py | 0 .../argspec/nxos/interfaces/__init__.py | 0 .../argspec/nxos/interfaces/interfaces.py | 21 ++ module_utils/nxos/config/__init__.py | 0 module_utils/nxos/config/base.py | 69 ++++++ .../nxos/config/interfaces/__init__.py | 0 .../nxos/config/interfaces/interfaces.py | 208 ++++++++++++++++++ module_utils/nxos/facts/__init__.py | 0 module_utils/nxos/facts/base.py | 37 ++++ .../nxos/facts/interfaces/__init__.py | 0 .../nxos/facts/interfaces/interfaces.py | 62 ++++++ 14 files changed, 491 insertions(+) create mode 100644 library/nxos/nxos_interfaces.py create mode 100644 module_utils/argspec/__init__.py create mode 100644 module_utils/argspec/base.py create mode 100644 module_utils/argspec/nxos/__init__.py create mode 100644 module_utils/argspec/nxos/interfaces/__init__.py create mode 100644 module_utils/argspec/nxos/interfaces/interfaces.py create mode 100644 module_utils/nxos/config/__init__.py create mode 100644 module_utils/nxos/config/base.py create mode 100644 module_utils/nxos/config/interfaces/__init__.py create mode 100644 module_utils/nxos/config/interfaces/interfaces.py create mode 100644 module_utils/nxos/facts/__init__.py create mode 100644 module_utils/nxos/facts/base.py create mode 100644 module_utils/nxos/facts/interfaces/__init__.py create mode 100644 module_utils/nxos/facts/interfaces/interfaces.py diff --git a/library/nxos/nxos_interfaces.py b/library/nxos/nxos_interfaces.py new file mode 100644 index 0000000..a832312 --- /dev/null +++ b/library/nxos/nxos_interfaces.py @@ -0,0 +1,70 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2018, Red Hat, Inc. +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'status': ['preview'], + 'supported_by': 'network'} + + +DOCUMENTATION = """ +--- +module: interfaces +version_added: "2.8" +short_description: Manages physical attributes of interfaces on Cisco NX-OS devices. +description: + - Manages physical attributes of interfaces of NX-OS switches. +author: + - Trishna Guha (@trishnaguha) +options: {} +""" + +EXAMPLES = """ +- name: Configure interfaces + nxos_interfaces: + config: + - name: Ethernet1/1 + description: 'Configured by Ansible' + enable: True + - name: Ethernet1/2 + description: 'Configured by Ansible' + enable: False + operation: merge + +""" + +RETURN = """ +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.network.nxos.nxos import load_config +from ansible.module_utils.nxos.config.interfaces.interfaces import Interface + + +def main(): + """ main entry point for module execution + """ + module = AnsibleModule(argument_spec=Interface.argument_spec, + supports_check_mode=True) + result = {'changed': False} + commands = list() + + intf = Interface(**module.params) + commands.extend(intf.set_config(module)) + if commands: + if not module.check_mode: + load_config(module, commands) + result['changed'] = True + + result['commands'] = commands + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/module_utils/argspec/__init__.py b/module_utils/argspec/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/module_utils/argspec/base.py b/module_utils/argspec/base.py new file mode 100644 index 0000000..a618475 --- /dev/null +++ b/module_utils/argspec/base.py @@ -0,0 +1,24 @@ +from ansible.module_utils.six import iteritems + + +class ArgspecBase(object): + + argument_spec = {} + + def __init__(self, **kwargs): + self.values = {} + + for key, value in iteritems(kwargs): + if key in self.argument_spec: + setattr(self, key, value) + + def __getattr__(self, key): + if key in self.argument_spec: + return self.values.get(key) + + def __setattr__(self, key, value): + if key in self.argument_spec: + if value is not None: + self.values[key] = value + else: + super(ArgspecBase, self).__setattr__(key, value) diff --git a/module_utils/argspec/nxos/__init__.py b/module_utils/argspec/nxos/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/module_utils/argspec/nxos/interfaces/__init__.py b/module_utils/argspec/nxos/interfaces/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/module_utils/argspec/nxos/interfaces/interfaces.py b/module_utils/argspec/nxos/interfaces/interfaces.py new file mode 100644 index 0000000..c694740 --- /dev/null +++ b/module_utils/argspec/nxos/interfaces/interfaces.py @@ -0,0 +1,21 @@ +from ansible.module_utils.argspec.base import ArgspecBase + +class InterfaceArgs(ArgspecBase): + + config_spec = { + 'name': dict(type='str', required=True), + 'description': dict(), + 'enable': dict(default=True, type=bool), + 'speed': dict(), + 'mode': dict(choices=['layer2', 'layer3']), + 'mtu': dict(), + 'duplex': dict(choices=['full', 'half', 'auto']), + 'mode': dict(choices=['layer2', 'layer3']), + 'ip_forward': dict(choices=['enable', 'disable']), + 'fabric_forwarding_anycast_gateway': dict(type='bool'), + } + + argument_spec = { + 'operation': dict(default='merge', choices=['merge', 'replace', 'override', 'delete']), + 'config': dict(type='list', elements='dict', options=config_spec) + } diff --git a/module_utils/nxos/config/__init__.py b/module_utils/nxos/config/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/module_utils/nxos/config/base.py b/module_utils/nxos/config/base.py new file mode 100644 index 0000000..3248104 --- /dev/null +++ b/module_utils/nxos/config/base.py @@ -0,0 +1,69 @@ +from ansible.module_utils.argspec.base import ArgspecBase + + +class ConfigBase(ArgspecBase): + + def search_obj_in_list(self, name, lst): + for o in lst: + if o['name'] == name: + return o + return None + + def normalize_interface(self, name): + """Return the normalized interface name + """ + if not name: + return + + def _get_number(name): + digits = '' + for char in name: + if char.isdigit() or char in '/.': + digits += char + return digits + + if name.lower().startswith('et'): + if_type = 'Ethernet' + elif name.lower().startswith('vl'): + if_type = 'Vlan' + elif name.lower().startswith('lo'): + if_type = 'loopback' + elif name.lower().startswith('po'): + if_type = 'port-channel' + elif name.lower().startswith('nv'): + if_type = 'nve' + else: + if_type = None + + number_list = name.split(' ') + if len(number_list) == 2: + number = number_list[-1].strip() + else: + number = _get_number(name) + + if if_type: + proper_interface = if_type + number + else: + proper_interface = name + + return proper_interface + + def get_interface_type(self, interface): + """Gets the type of interface + """ + if interface.upper().startswith('ET'): + return 'ethernet' + elif interface.upper().startswith('VL'): + return 'svi' + elif interface.upper().startswith('LO'): + return 'loopback' + elif interface.upper().startswith('MG'): + return 'management' + elif interface.upper().startswith('MA'): + return 'management' + elif interface.upper().startswith('PO'): + return 'portchannel' + elif interface.upper().startswith('NV'): + return 'nve' + else: + return 'unknown' diff --git a/module_utils/nxos/config/interfaces/__init__.py b/module_utils/nxos/config/interfaces/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/module_utils/nxos/config/interfaces/interfaces.py b/module_utils/nxos/config/interfaces/interfaces.py new file mode 100644 index 0000000..b02f759 --- /dev/null +++ b/module_utils/nxos/config/interfaces/interfaces.py @@ -0,0 +1,208 @@ +import re + +from ansible.module_utils.facts import ansible_facts +from ansible.module_utils.network.common.utils import to_list +from ansible.module_utils.argspec.nxos.interfaces.interfaces import InterfaceArgs +from ansible.module_utils.nxos.config.base import ConfigBase +from ansible.module_utils.six import iteritems + + +class Interface(ConfigBase): + + self.argument_spec = InterfaceArgs.argument_spec + + def set_config(self, module): + want = self._config_map_params_to_obj(module) + interfaces_facts = ansible_facts(module, ['interfaces']) + interfaces = interfaces_facts['network_interfaces'] + have = [] + if interfaces: + have = interfaces['config'] + resp = self.set_operation(want, have) + return to_list(resp) + + def _config_map_params_to_obj(self, module): + objs = [] + collection = module.params['config'] + for config in collection: + obj = { + 'name': self.normalize_interface(config['name']), + 'description': config['description'], + 'enable': config['enable'], + 'speed': config['speed'], + 'mtu': config['mtu'], + 'duplex': config['duplex'], + 'mode': config['mode'], + 'ip_forward': config['ip_forward'], + 'fabric_forwarding_anycast_gateway': config['fabric_forwarding_anycast_gateway'], + } + objs.append(obj) + + return objs + + def set_operation(self, want, have): + commands = list() + + operation = self.operation + if operation == 'override': + commands.extend(self._operation_override(want, have)) + else: + for w in want: + name = w['name'] + interface_type = self.get_interface_type(name) + obj_in_have = self.search_obj_in_list(name, have) + if operation == 'delete' and obj_in_have: + commands.append('no interface {0}'.format(w['name'])) + + if operation == 'merge': + commands.extend(self._operation_merge(w, obj_in_have, interface_type)) + + if operation == 'replace': + commands.extend(self._operation_replace(w, obj_in_have, interface_type)) + + return commands + + def _operation_replace(self, w, obj_in_have, interface_type): + commands = list() + + if interface_type in ('loopback', 'portchannel', 'svi'): + commands.append('no interface {0}'. format(w['name'])) + commands.extend(self._operation_merge(w, obj_in_have, interface_type)) + else: + commands.append('default interface {0}'.format(w['name'])) + commands.extend(self._operation_merge(w, obj_in_have, interface_type)) + + return commands + + def _operation_override(self, want, have): + """ + purge interfaces + """ + commands = list() + + for h in have: + name = h['name'] + obj_in_want = self.search_obj_in_list(name, want) + if not obj_in_want: + interface_type = self.get_interface_type(name) + + # Remove logical interfaces + if interface_type in ('loopback', 'portchannel', 'svi'): + commands.append('no interface {0}'.format(name)) + elif interface_type == 'ethernet': + default = True + if h['enable'] is True: + keys = ('description', 'mode', 'mtu', 'speed', 'duplex', 'ip_forward','fabric_forwarding_anycast_gateway') + for k, v in iteritems(h): + if k in keys: + if h[k] is not None: + default = False + break + else: + default = False + + if default is False: + # Put physical interface back into default state + commands.append('default interface {0}'.format(name)) + + for w in want: + name = w['name'] + interface_type = self.get_interface_type(name) + obj_in_have = self.search_obj_in_list(name, have) + commands.extend(self._operation_merge( w, obj_in_have, interface_type)) + + return commands + + def _operation_merge(self, w, obj_in_have, interface_type): + commands = list() + + args = ('speed', 'description', 'duplex', 'mtu') + name = w['name'] + mode = w['mode'] + ip_forward = w['ip_forward'] + fabric_forwarding_anycast_gateway = w['fabric_forwarding_anycast_gateway'] + enable = w['enable'] + + if name: + interface = 'interface ' + name + + if not obj_in_have: + commands.append(interface) + if interface_type in ('ethernet', 'portchannel'): + if mode == 'layer2': + commands.append('switchport') + elif mode == 'layer3': + commands.append('no switchport') + + if enable is True: + commands.append('no shutdown') + elif enable is False: + commands.append('shutdown') + + if ip_forward == 'enable': + commands.append('ip forward') + elif ip_forward == 'disable': + commands.append('no ip forward') + + if fabric_forwarding_anycast_gateway is True: + commands.append('fabric forwarding mode anycast-gateway') + elif fabric_forwarding_anycast_gateway is False: + commands.append('no fabric forwarding mode anycast-gateway') + + for item in args: + candidate = w.get(item) + if candidate: + commands.append(item + ' ' + str(candidate)) + + else: + if interface_type in ('ethernet', 'portchannel'): + if mode == 'layer2' and mode != obj_in_have.get('mode'): + self.add_command_to_interface(interface, 'switchport', commands) + elif mode == 'layer3' and mode != obj_in_have.get('mode'): + self.add_command_to_interface(interface, 'no switchport', commands) + + if enable is True and enable != obj_in_have.get('enable'): + self.add_command_to_interface(interface, 'no shutdown', commands) + elif enable is False and enable != obj_in_have.get('enable'): + self.add_command_to_interface(interface, 'shutdown', commands) + + if ip_forward == 'enable' and ip_forward != obj_in_have.get('ip_forward'): + self.add_command_to_interface(interface, 'ip forward', commands) + elif ip_forward == 'disable' and ip_forward != obj_in_have.get('ip forward'): + self.add_command_to_interface(interface, 'no ip forward', commands) + + if (fabric_forwarding_anycast_gateway is True and obj_in_have.get('fabric_forwarding_anycast_gateway') is False): + self.add_command_to_interface(interface, 'fabric forwarding mode anycast-gateway', commands) + + elif (fabric_forwarding_anycast_gateway is False and obj_in_have.get('fabric_forwarding_anycast_gateway') is True): + self.add_command_to_interface(interface, 'no fabric forwarding mode anycast-gateway', commands) + + for item in args: + candidate = w.get(item) + if candidate and candidate != obj_in_have.get(item): + cmd = item + ' ' + str(candidate) + self.add_command_to_interface(interface, cmd, commands) + + # if the mode changes from L2 to L3, the admin state + # seems to change after the API call, so adding a second API + # call to ensure it's in the desired state. + if name and interface_type == 'ethernet': + if mode and mode != obj_in_have.get('mode'): + enable = w.get('enable') or obj_in_have.get('enable') + if enable is True: + commands.append(self._get_admin_state(enable)) + + return commands + + def _get_admin_state(self, enable): + command = '' + if enable is True: + command = 'no shutdown' + elif enable is False: + command = 'shutdown' + return command + + def add_command_to_interface(self, interface, cmd, commands): + if interface not in commands: + commands.append(interface) + commands.append(cmd) diff --git a/module_utils/nxos/facts/__init__.py b/module_utils/nxos/facts/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/module_utils/nxos/facts/base.py b/module_utils/nxos/facts/base.py new file mode 100644 index 0000000..429e204 --- /dev/null +++ b/module_utils/nxos/facts/base.py @@ -0,0 +1,37 @@ +from copy import deepcopy + +from ansible.module_utils.six import iteritems + + +class FactsBase(object): + + generated_spec = {} + data = None + + def __init__(self, data, argspec, subspec=None, options=None): + self.data = data + + spec = deepcopy(argspec) + if subspec: + if options: + facts_argument_spec = spec[subspec][options] + else: + facts_argument_spec = spec[subspec] + else: + facts_argument_spec = spec + + generate_spec = self.generate_dict(facts_argument_spec) + + def generate_dict(self, spec): + obj = {} + if not spec: + return obj + + for k, v in iteritems(spec): + if 'default' in v: + d = {k: v['default']} + else: + d = {k: None} + obj.update(d) + + return obj diff --git a/module_utils/nxos/facts/interfaces/__init__.py b/module_utils/nxos/facts/interfaces/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/module_utils/nxos/facts/interfaces/interfaces.py b/module_utils/nxos/facts/interfaces/interfaces.py new file mode 100644 index 0000000..cf24cd4 --- /dev/null +++ b/module_utils/nxos/facts/interfaces/interfaces.py @@ -0,0 +1,62 @@ +import re + +from ansible.module_utils.network.nxos.nxos import get_interface_type +from ansible.module_utils.nxos.facts.base import FactsBase + + +class NxosInterfacesFacts(FactsBase): + + def populate_facts(self): + objs = [] + if self.data is None: + return {} + + config = self.data.split('interface ') + for conf in config: + if conf: + obj = self.render_config(self.generated_spec, conf) + if obj: + objs.append(obj) + + facts = {} + if objs: + facts = {'config': objs} + return facts + + + def render_config(self, generated_spec, conf): + match = re.search(r'^(\S+)\n', conf) + intf = match.group(1) + if get_interface_type(intf) != 'unknown': + name = intf + else: + return {} + config = self._config_map_conf_to_obj(conf, name, spec=generated_spec) + return config + + def _config_map_conf_to_obj(conf, name, spec): + spec['name'] = name + sp + + + + + + def parse_conf_arg(self, cfg, arg): + match = re.search(r'%s (.+)(\n|$)' % arg, cfg, re.M) + if match: + return match.group(1).strip() + return None + + def parse_conf_cmd_arg(self, cfg, cmd, res1, res2=None, default=None): + match = re.search(r'\n\s+%s(\n|$)' % cmd, cfg) + if match: + return res1 + else: + if res2 is not None: + match = re.search(r'\n\s+no %s(\n|$)' % cmd, cfg) + if match: + return res2 + if default is not None: + return default + return None From 4c8d98e3442d00ce4586ac88aa224933a8475df0 Mon Sep 17 00:00:00 2001 From: Trishna Guha Date: Tue, 12 Mar 2019 21:09:41 +0530 Subject: [PATCH 02/21] facts generation Signed-off-by: Trishna Guha --- library/{nxos => }/nxos_interfaces.py | 0 module_utils/__init__.py | 0 module_utils/nxos/__init__.py | 0 module_utils/nxos/config/base.py | 59 ------------------ .../nxos/config/interfaces/interfaces.py | 13 ++-- module_utils/nxos/facts/base.py | 42 +++++++++++++ .../nxos/facts/interfaces/interfaces.py | 62 ++++++++----------- 7 files changed, 73 insertions(+), 103 deletions(-) rename library/{nxos => }/nxos_interfaces.py (100%) create mode 100644 module_utils/__init__.py create mode 100644 module_utils/nxos/__init__.py diff --git a/library/nxos/nxos_interfaces.py b/library/nxos_interfaces.py similarity index 100% rename from library/nxos/nxos_interfaces.py rename to library/nxos_interfaces.py diff --git a/module_utils/__init__.py b/module_utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/module_utils/nxos/__init__.py b/module_utils/nxos/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/module_utils/nxos/config/base.py b/module_utils/nxos/config/base.py index 3248104..d1faafe 100644 --- a/module_utils/nxos/config/base.py +++ b/module_utils/nxos/config/base.py @@ -8,62 +8,3 @@ def search_obj_in_list(self, name, lst): if o['name'] == name: return o return None - - def normalize_interface(self, name): - """Return the normalized interface name - """ - if not name: - return - - def _get_number(name): - digits = '' - for char in name: - if char.isdigit() or char in '/.': - digits += char - return digits - - if name.lower().startswith('et'): - if_type = 'Ethernet' - elif name.lower().startswith('vl'): - if_type = 'Vlan' - elif name.lower().startswith('lo'): - if_type = 'loopback' - elif name.lower().startswith('po'): - if_type = 'port-channel' - elif name.lower().startswith('nv'): - if_type = 'nve' - else: - if_type = None - - number_list = name.split(' ') - if len(number_list) == 2: - number = number_list[-1].strip() - else: - number = _get_number(name) - - if if_type: - proper_interface = if_type + number - else: - proper_interface = name - - return proper_interface - - def get_interface_type(self, interface): - """Gets the type of interface - """ - if interface.upper().startswith('ET'): - return 'ethernet' - elif interface.upper().startswith('VL'): - return 'svi' - elif interface.upper().startswith('LO'): - return 'loopback' - elif interface.upper().startswith('MG'): - return 'management' - elif interface.upper().startswith('MA'): - return 'management' - elif interface.upper().startswith('PO'): - return 'portchannel' - elif interface.upper().startswith('NV'): - return 'nve' - else: - return 'unknown' diff --git a/module_utils/nxos/config/interfaces/interfaces.py b/module_utils/nxos/config/interfaces/interfaces.py index b02f759..499fa7a 100644 --- a/module_utils/nxos/config/interfaces/interfaces.py +++ b/module_utils/nxos/config/interfaces/interfaces.py @@ -1,9 +1,8 @@ -import re - -from ansible.module_utils.facts import ansible_facts from ansible.module_utils.network.common.utils import to_list from ansible.module_utils.argspec.nxos.interfaces.interfaces import InterfaceArgs +from ansible.module_utils.network.nxos.nxos import get_config, get_interface_type, normalize_interface from ansible.module_utils.nxos.config.base import ConfigBase +from ansible.module_utils.nxos.facts.interfaces.interfaces import NxosInterfacesFacts from ansible.module_utils.six import iteritems @@ -13,11 +12,9 @@ class Interface(ConfigBase): def set_config(self, module): want = self._config_map_params_to_obj(module) - interfaces_facts = ansible_facts(module, ['interfaces']) - interfaces = interfaces_facts['network_interfaces'] - have = [] - if interfaces: - have = interfaces['config'] + data = get_config(module, ['| section ^interface']) + facts = NxosInterfacesFacts(data, self.argument_spec, 'config', 'options').populate_facts() + have = facts['ansible_net_configuration'].get('interfaces') resp = self.set_operation(want, have) return to_list(resp) diff --git a/module_utils/nxos/facts/base.py b/module_utils/nxos/facts/base.py index 429e204..17ee910 100644 --- a/module_utils/nxos/facts/base.py +++ b/module_utils/nxos/facts/base.py @@ -1,3 +1,4 @@ +import re from copy import deepcopy from ansible.module_utils.six import iteritems @@ -6,6 +7,7 @@ class FactsBase(object): generated_spec = {} + ansible_facts = {'ansible_net_configuration': {}} data = None def __init__(self, data, argspec, subspec=None, options=None): @@ -23,6 +25,13 @@ def __init__(self, data, argspec, subspec=None, options=None): generate_spec = self.generate_dict(facts_argument_spec) def generate_dict(self, spec): + """ + Generate dictionary which is in sync with argspec + + :param spec: A dictionary which the argspec of module + :rtype: A dictionary + :returns: A dictionary in sync with argspec with default value + """ obj = {} if not spec: return obj @@ -35,3 +44,36 @@ def generate_dict(self, spec): obj.update(d) return obj + + def parse_conf_arg(self, cfg, arg): + """ + Parse config based on argument + + :param cfg: A text string which is a line of configuration. + :param arg: A text string which is to be matched. + :rtype: A text string + :returns: A text string if match is found + """ + match = re.search(r'%s (.+)(\n|$)' % arg, cfg, re.M) + if match: + return match.group(1).strip() + + def parse_conf_cmd_arg(self, cfg, cmd, res1, res2=None): + """ + Parse config based on command + + :param cfg: A text string which is a line of configuration. + :param cmd: A text string which is the command to be matched + :param res1: A text string to be returned if the command is present + :param res2: A text string to be returned if the negate command is present + :rtype: A text string + :returns: A text string if match is found + """ + match = re.search(r'\n\s+%s(\n|$)' % cmd, cfg) + if match: + return res1 + else: + if res2 is not None: + match = re.search(r'\n\s+no %s(\n|$)' % cmd, cfg) + if match: + return res2 diff --git a/module_utils/nxos/facts/interfaces/interfaces.py b/module_utils/nxos/facts/interfaces/interfaces.py index cf24cd4..0d28c9f 100644 --- a/module_utils/nxos/facts/interfaces/interfaces.py +++ b/module_utils/nxos/facts/interfaces/interfaces.py @@ -1,12 +1,15 @@ -import re +from copy import deepcopy -from ansible.module_utils.network.nxos.nxos import get_interface_type +from ansible.module_utils.network.nxos.nxos import get_interface_type, normalize_interface from ansible.module_utils.nxos.facts.base import FactsBase class NxosInterfacesFacts(FactsBase): def populate_facts(self): + """ + Populate nxos interfaces facts + """ objs = [] if self.data is None: return {} @@ -20,43 +23,30 @@ def populate_facts(self): facts = {} if objs: - facts = {'config': objs} - return facts + facts['interfaces'] = obj + self.ansible_facts['ansible_net_configuration'].update(facts) + return self.ansible_facts + def render_config(self, spec, conf): + """ + Render config in dictionary structure and delete keys from spec for null values + """ + config = deepcopy(spec) - def render_config(self, generated_spec, conf): match = re.search(r'^(\S+)\n', conf) intf = match.group(1) - if get_interface_type(intf) != 'unknown': - name = intf - else: + name = get_interface_type(intf) + if name == 'unknown': return {} - config = self._config_map_conf_to_obj(conf, name, spec=generated_spec) - return config + config['name'] = normalize_interface(name) + + config['description'] = if self.parse_conf_arg(conf, 'description') else del config['description'] + config['speed'] = if self.parse_conf_arg(conf, 'speed') else del config['speed'] + config['mtu'] = if self.parse_conf_arg(conf, 'mtu') else del config['mtu'] + config['duplex'] = if self.parse_conf_arg(conf, 'duplex') else del config['duplex'] + cofig['mode'] = if self.parse_conf_cmd_arg(conf, 'switchport', 'layer2', res2='layer3') else del config['mode'] + config['enable'] = if self.parse_conf_cmd_arg(conf, 'shutdown', False) + config['fabric_forwarding_anycast_gateway'] = if self.parse_conf_cmd_arg(conf, 'fabric forwarding mode anycast-gateway', True, res2=False) else del config['fabric_forwarding_anycast_gateway'] + config['ip_forward'] = if self.parse_conf_cmd_arg(conf, 'ip forward', 'enable', res2='disable') else del config['ip_forward'] - def _config_map_conf_to_obj(conf, name, spec): - spec['name'] = name - sp - - - - - - def parse_conf_arg(self, cfg, arg): - match = re.search(r'%s (.+)(\n|$)' % arg, cfg, re.M) - if match: - return match.group(1).strip() - return None - - def parse_conf_cmd_arg(self, cfg, cmd, res1, res2=None, default=None): - match = re.search(r'\n\s+%s(\n|$)' % cmd, cfg) - if match: - return res1 - else: - if res2 is not None: - match = re.search(r'\n\s+no %s(\n|$)' % cmd, cfg) - if match: - return res2 - if default is not None: - return default - return None + return config From b04a7ab2bd12b80c733fd5f2b4738e7de02ed797 Mon Sep 17 00:00:00 2001 From: Trishna Guha Date: Wed, 13 Mar 2019 15:14:42 +0530 Subject: [PATCH 03/21] fix argument_spec instantiation Signed-off-by: Trishna Guha --- .../nxos/config/interfaces/interfaces.py | 10 +++--- module_utils/nxos/facts/base.py | 18 +++++++++++ .../nxos/facts/interfaces/interfaces.py | 32 +++++++++---------- 3 files changed, 39 insertions(+), 21 deletions(-) diff --git a/module_utils/nxos/config/interfaces/interfaces.py b/module_utils/nxos/config/interfaces/interfaces.py index 499fa7a..9b83483 100644 --- a/module_utils/nxos/config/interfaces/interfaces.py +++ b/module_utils/nxos/config/interfaces/interfaces.py @@ -8,7 +8,7 @@ class Interface(ConfigBase): - self.argument_spec = InterfaceArgs.argument_spec + argument_spec = InterfaceArgs.argument_spec def set_config(self, module): want = self._config_map_params_to_obj(module) @@ -23,7 +23,7 @@ def _config_map_params_to_obj(self, module): collection = module.params['config'] for config in collection: obj = { - 'name': self.normalize_interface(config['name']), + 'name': normalize_interface(config['name']), 'description': config['description'], 'enable': config['enable'], 'speed': config['speed'], @@ -46,7 +46,7 @@ def set_operation(self, want, have): else: for w in want: name = w['name'] - interface_type = self.get_interface_type(name) + interface_type = get_interface_type(name) obj_in_have = self.search_obj_in_list(name, have) if operation == 'delete' and obj_in_have: commands.append('no interface {0}'.format(w['name'])) @@ -81,7 +81,7 @@ def _operation_override(self, want, have): name = h['name'] obj_in_want = self.search_obj_in_list(name, want) if not obj_in_want: - interface_type = self.get_interface_type(name) + interface_type = get_interface_type(name) # Remove logical interfaces if interface_type in ('loopback', 'portchannel', 'svi'): @@ -104,7 +104,7 @@ def _operation_override(self, want, have): for w in want: name = w['name'] - interface_type = self.get_interface_type(name) + interface_type = get_interface_type(name) obj_in_have = self.search_obj_in_list(name, have) commands.extend(self._operation_merge( w, obj_in_have, interface_type)) diff --git a/module_utils/nxos/facts/base.py b/module_utils/nxos/facts/base.py index 17ee910..1ad6d39 100644 --- a/module_utils/nxos/facts/base.py +++ b/module_utils/nxos/facts/base.py @@ -77,3 +77,21 @@ def parse_conf_cmd_arg(self, cfg, cmd, res1, res2=None): match = re.search(r'\n\s+no %s(\n|$)' % cmd, cfg) if match: return res2 + + def generate_final_config(self, cfg_dict): + """ + Generate final config dictionary + + :param cfg_dict: A dictionary parsed in the facts system + :rtype: A dictionary + :returns: A dictionary by elimating keys that have null values + """ + final_cfg = {} + if not cfg_dict: + return final_cfg + + for k, v in iteritems(cfg_dict): + if v is not None: + final_cfg.update({k:v}) + + return final_cfg diff --git a/module_utils/nxos/facts/interfaces/interfaces.py b/module_utils/nxos/facts/interfaces/interfaces.py index 0d28c9f..caa3f32 100644 --- a/module_utils/nxos/facts/interfaces/interfaces.py +++ b/module_utils/nxos/facts/interfaces/interfaces.py @@ -1,3 +1,5 @@ +import re + from copy import deepcopy from ansible.module_utils.network.nxos.nxos import get_interface_type, normalize_interface @@ -13,7 +15,6 @@ def populate_facts(self): objs = [] if self.data is None: return {} - config = self.data.split('interface ') for conf in config: if conf: @@ -23,7 +24,7 @@ def populate_facts(self): facts = {} if objs: - facts['interfaces'] = obj + facts['interfaces'] = objs self.ansible_facts['ansible_net_configuration'].update(facts) return self.ansible_facts @@ -35,18 +36,17 @@ def render_config(self, spec, conf): match = re.search(r'^(\S+)\n', conf) intf = match.group(1) - name = get_interface_type(intf) - if name == 'unknown': + if get_interface_type(intf) == 'unknown': return {} - config['name'] = normalize_interface(name) - - config['description'] = if self.parse_conf_arg(conf, 'description') else del config['description'] - config['speed'] = if self.parse_conf_arg(conf, 'speed') else del config['speed'] - config['mtu'] = if self.parse_conf_arg(conf, 'mtu') else del config['mtu'] - config['duplex'] = if self.parse_conf_arg(conf, 'duplex') else del config['duplex'] - cofig['mode'] = if self.parse_conf_cmd_arg(conf, 'switchport', 'layer2', res2='layer3') else del config['mode'] - config['enable'] = if self.parse_conf_cmd_arg(conf, 'shutdown', False) - config['fabric_forwarding_anycast_gateway'] = if self.parse_conf_cmd_arg(conf, 'fabric forwarding mode anycast-gateway', True, res2=False) else del config['fabric_forwarding_anycast_gateway'] - config['ip_forward'] = if self.parse_conf_cmd_arg(conf, 'ip forward', 'enable', res2='disable') else del config['ip_forward'] - - return config + config['name'] = normalize_interface(intf) + + config['description'] = self.parse_conf_arg(conf, 'description') + config['speed'] = self.parse_conf_arg(conf, 'speed') + config['mtu'] = self.parse_conf_arg(conf, 'mtu') + config['duplex'] = self.parse_conf_arg(conf, 'duplex') + config['mode'] = self.parse_conf_cmd_arg(conf, 'switchport', 'layer2', res2='layer3') + config['enable'] = self.parse_conf_cmd_arg(conf, 'shutdown', False) + config['fabric_forwarding_anycast_gateway'] = self.parse_conf_cmd_arg(conf, 'fabric forwarding mode anycast-gateway', True, res2=False) + config['ip_forward'] = self.parse_conf_cmd_arg(conf, 'ip forward', 'enable', res2='disable') + + return self.generate_final_config(config) From 77c8699051588eadd3dc9e889de0b809c42d1258 Mon Sep 17 00:00:00 2001 From: Trishna Guha Date: Thu, 14 Mar 2019 21:31:44 +0530 Subject: [PATCH 04/21] change operation keyword to state Signed-off-by: Trishna Guha --- .../argspec/nxos/interfaces/interfaces.py | 2 +- .../nxos/config/interfaces/interfaces.py | 32 +++++++++---------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/module_utils/argspec/nxos/interfaces/interfaces.py b/module_utils/argspec/nxos/interfaces/interfaces.py index c694740..a97550e 100644 --- a/module_utils/argspec/nxos/interfaces/interfaces.py +++ b/module_utils/argspec/nxos/interfaces/interfaces.py @@ -16,6 +16,6 @@ class InterfaceArgs(ArgspecBase): } argument_spec = { - 'operation': dict(default='merge', choices=['merge', 'replace', 'override', 'delete']), + 'state': dict(default='merged', choices=['merged', 'replaced', 'overriden', 'deleted']), 'config': dict(type='list', elements='dict', options=config_spec) } diff --git a/module_utils/nxos/config/interfaces/interfaces.py b/module_utils/nxos/config/interfaces/interfaces.py index 9b83483..5333960 100644 --- a/module_utils/nxos/config/interfaces/interfaces.py +++ b/module_utils/nxos/config/interfaces/interfaces.py @@ -15,7 +15,7 @@ def set_config(self, module): data = get_config(module, ['| section ^interface']) facts = NxosInterfacesFacts(data, self.argument_spec, 'config', 'options').populate_facts() have = facts['ansible_net_configuration'].get('interfaces') - resp = self.set_operation(want, have) + resp = self.set_state(want, have) return to_list(resp) def _config_map_params_to_obj(self, module): @@ -37,41 +37,41 @@ def _config_map_params_to_obj(self, module): return objs - def set_operation(self, want, have): + def set_state(self, want, have): commands = list() - operation = self.operation - if operation == 'override': - commands.extend(self._operation_override(want, have)) + state = self.state + if state == 'overriden': + commands.extend(self._state_overriden(want, have)) else: for w in want: name = w['name'] interface_type = get_interface_type(name) obj_in_have = self.search_obj_in_list(name, have) - if operation == 'delete' and obj_in_have: + if state == 'deleted' and obj_in_have: commands.append('no interface {0}'.format(w['name'])) - if operation == 'merge': - commands.extend(self._operation_merge(w, obj_in_have, interface_type)) + if state == 'merged': + commands.extend(self._state_merged(w, obj_in_have, interface_type)) - if operation == 'replace': - commands.extend(self._operation_replace(w, obj_in_have, interface_type)) + if state == 'replaced': + commands.extend(self._state_replaced(w, obj_in_have, interface_type)) return commands - def _operation_replace(self, w, obj_in_have, interface_type): + def _state_replaced(self, w, obj_in_have, interface_type): commands = list() if interface_type in ('loopback', 'portchannel', 'svi'): commands.append('no interface {0}'. format(w['name'])) - commands.extend(self._operation_merge(w, obj_in_have, interface_type)) + commands.extend(self._state_merged(w, obj_in_have, interface_type)) else: commands.append('default interface {0}'.format(w['name'])) - commands.extend(self._operation_merge(w, obj_in_have, interface_type)) + commands.extend(self._state_merged(w, obj_in_have, interface_type)) return commands - def _operation_override(self, want, have): + def _state_overriden(self, want, have): """ purge interfaces """ @@ -106,11 +106,11 @@ def _operation_override(self, want, have): name = w['name'] interface_type = get_interface_type(name) obj_in_have = self.search_obj_in_list(name, have) - commands.extend(self._operation_merge( w, obj_in_have, interface_type)) + commands.extend(self._state_merged( w, obj_in_have, interface_type)) return commands - def _operation_merge(self, w, obj_in_have, interface_type): + def _state_merged(self, w, obj_in_have, interface_type): commands = list() args = ('speed', 'description', 'duplex', 'mtu') From e1ede04979cabdc240e6576777903dc9f960eff9 Mon Sep 17 00:00:00 2001 From: Trishna Guha Date: Mon, 18 Mar 2019 20:26:42 +0530 Subject: [PATCH 05/21] Add facts module Signed-off-by: Trishna Guha --- library/nxos_facts.py | 85 +++++++++++++++++++ module_utils/argspec/nxos/facts/__init__.py | 0 module_utils/argspec/nxos/facts/facts.py | 7 ++ .../nxos/config/interfaces/interfaces.py | 4 +- module_utils/nxos/facts/base.py | 8 +- module_utils/nxos/facts/facts.py | 55 ++++++++++++ .../nxos/facts/interfaces/interfaces.py | 2 +- 7 files changed, 154 insertions(+), 7 deletions(-) create mode 100644 library/nxos_facts.py create mode 100644 module_utils/argspec/nxos/facts/__init__.py create mode 100644 module_utils/argspec/nxos/facts/facts.py create mode 100644 module_utils/nxos/facts/facts.py diff --git a/library/nxos_facts.py b/library/nxos_facts.py new file mode 100644 index 0000000..5fb5f43 --- /dev/null +++ b/library/nxos_facts.py @@ -0,0 +1,85 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2019, Red Hat, Inc. +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'status': ['preview'], + 'supported_by': 'network'} + + +DOCUMENTATION = """ +--- +module: nxos_facts +extends_documentation_fragment: nxos +version_added: "2.9" +short_description: Gets facts about NX-OS switches +description: + - Collects facts from Cisco Nexus devices running the NX-OS operating + system. Fact collection is supported over both Cli and Nxapi + transports. This module prepends all of the base network fact keys + with C(ansible_net_). The facts module will always collect a + base set of facts from the device and can enable or disable + collection of additional facts. +author: + - Trishna Guha (@trishnaguha) +notes: + - Tested against NXOSv 7.3.(0)D1(1) on VIRL +options: + gather_subset: + description: + - When supplied, this argument will restrict the facts collected + to a given subset. Possible values for this argument include + all, hardware, config, legacy, and interfaces. Can specify a + list of values to include a larger subset. Values can also be used + with an initial C(M(!)) to specify that a specific subset should + not be collected. + required: false + default: '!config' + version_added: "2.2" +""" + +EXAMPLES = """ +# Gather all facts +- nxos_facts: + gather_subset: all + +# Collect only the config and default facts +- nxos_facts: + gather_subset: + - config + +# Do not collect hardware facts +- nxos_facts: + gather_subset: + - "!hardware" +""" + +RETURN = """ +ansible_net_gather_subset: + description: The list of fact subsets collected from the device + returned: always + type: list +""" + + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.nxos.facts.facts import Facts + + +def main(): + module = AnsibleModule(argument_spec=Facts.argument_spec, supports_check_mode=True) + warnings = list() + + facts = Facts(**module.params) + ansible_facts = facts.get_facts(module) + + module.exit_json(ansible_facts=ansible_facts, warnings=warnings) + + +if __name__ == '__main__': + main() diff --git a/module_utils/argspec/nxos/facts/__init__.py b/module_utils/argspec/nxos/facts/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/module_utils/argspec/nxos/facts/facts.py b/module_utils/argspec/nxos/facts/facts.py new file mode 100644 index 0000000..84f383c --- /dev/null +++ b/module_utils/argspec/nxos/facts/facts.py @@ -0,0 +1,7 @@ +from ansible.module_utils.argspec.base import ArgspecBase + +class FactsArgs(ArgspecBase): + + argument_spec = { + 'gather_subset': dict(default=['all'], type='list') + } diff --git a/module_utils/nxos/config/interfaces/interfaces.py b/module_utils/nxos/config/interfaces/interfaces.py index 5333960..bb32609 100644 --- a/module_utils/nxos/config/interfaces/interfaces.py +++ b/module_utils/nxos/config/interfaces/interfaces.py @@ -13,8 +13,8 @@ class Interface(ConfigBase): def set_config(self, module): want = self._config_map_params_to_obj(module) data = get_config(module, ['| section ^interface']) - facts = NxosInterfacesFacts(data, self.argument_spec, 'config', 'options').populate_facts() - have = facts['ansible_net_configuration'].get('interfaces') + facts = NxosInterfacesFacts(self.argument_spec, data, 'config', 'options').populate_facts() + have = facts['net_configuration'].get('interfaces') resp = self.set_state(want, have) return to_list(resp) diff --git a/module_utils/nxos/facts/base.py b/module_utils/nxos/facts/base.py index 1ad6d39..c202edc 100644 --- a/module_utils/nxos/facts/base.py +++ b/module_utils/nxos/facts/base.py @@ -7,11 +7,11 @@ class FactsBase(object): generated_spec = {} - ansible_facts = {'ansible_net_configuration': {}} - data = None + ansible_facts = {'net_configuration': {}} - def __init__(self, data, argspec, subspec=None, options=None): - self.data = data + def __init__(self, argspec, data=None, subspec=None, options=None): + if data: + self.data = data spec = deepcopy(argspec) if subspec: diff --git a/module_utils/nxos/facts/facts.py b/module_utils/nxos/facts/facts.py new file mode 100644 index 0000000..04aa0ba --- /dev/null +++ b/module_utils/nxos/facts/facts.py @@ -0,0 +1,55 @@ +from ansible.module_utils.argspec.nxos.facts.facts import FactsArgs +from ansible.module_utils.argspec.nxos.interfaces.interfaces import InterfaceArgs +from ansible.module_utils.argspec.base import ArgspecBase +from ansible.module_utils.network.nxos.nxos import get_config +from ansible.module_utils.nxos.facts.base import FactsBase +from ansible.module_utils.nxos.facts.interfaces.interfaces import NxosInterfacesFacts + + +class Facts(ArgspecBase, FactsBase): + + argument_spec = FactsArgs.argument_spec + VALID_SUBSETS = [ + 'ansible_net_configuration_interfaces', + ] + + def get_facts(self, module): + gather_subset = module.params['gather_subset'] + runable_subsets = set() + exclude_subsets = set() + + for subset in gather_subset: + if subset == 'all': + runable_subsets.update(self.VALID_SUBSETS) + continue + if subset.startswith('!'): + subset = subset[1:] + if subset == 'all': + exclude_subsets.update(self.VALID_SUBSETS) + continue + exclude = True + else: + exclude = False + + if subset not in self.VALID_SUBSETS: + module.fail_json(msg='Bad subset') + + if exclude: + exclude_subsets.add(subset) + else: + runable_subsets.add(subset) + + if not runable_subsets: + runable_subsets.update(self.VALID_SUBSETS) + + runable_subsets.difference_update(exclude_subsets) + self.ansible_facts['gather_subset'] = list(runable_subsets) + + for attr in runable_subsets: + getattr(self, '_get_%s' % attr, {})(module) + + return self.ansible_facts + + def _get_ansible_net_configuration_interfaces(self, module): + data = get_config(module, ['| section ^interface']) + return NxosInterfacesFacts(InterfaceArgs.argument_spec, data, 'config', 'options').populate_facts() diff --git a/module_utils/nxos/facts/interfaces/interfaces.py b/module_utils/nxos/facts/interfaces/interfaces.py index caa3f32..5e7dd2e 100644 --- a/module_utils/nxos/facts/interfaces/interfaces.py +++ b/module_utils/nxos/facts/interfaces/interfaces.py @@ -25,7 +25,7 @@ def populate_facts(self): facts = {} if objs: facts['interfaces'] = objs - self.ansible_facts['ansible_net_configuration'].update(facts) + self.ansible_facts['net_configuration'].update(facts) return self.ansible_facts def render_config(self, spec, conf): From b42fbe92cdc5e57a9ffb0d3a2c1bfb2018c0ed46 Mon Sep 17 00:00:00 2001 From: Trishna Guha Date: Tue, 19 Mar 2019 10:39:58 +0530 Subject: [PATCH 06/21] cosmetic changes for APIs Signed-off-by: Trishna Guha --- library/nxos_facts.py | 9 +++++---- library/nxos_interfaces.py | 1 + module_utils/nxos/config/base.py | 10 ---------- .../nxos/config/interfaces/interfaces.py | 18 +++++++----------- module_utils/nxos/facts/base.py | 5 +---- module_utils/nxos/facts/facts.py | 14 +++++--------- .../nxos/facts/interfaces/interfaces.py | 11 ++++++----- 7 files changed, 25 insertions(+), 43 deletions(-) delete mode 100644 module_utils/nxos/config/base.py diff --git a/library/nxos_facts.py b/library/nxos_facts.py index 5fb5f43..01bfa38 100644 --- a/library/nxos_facts.py +++ b/library/nxos_facts.py @@ -68,15 +68,16 @@ from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils.nxos.facts.facts import Facts +from ansible.module_utils.nxos.facts.facts import NxosFacts def main(): - module = AnsibleModule(argument_spec=Facts.argument_spec, supports_check_mode=True) + module = AnsibleModule(argument_spec=NxosFacts.argument_spec, supports_check_mode=True) warnings = list() - facts = Facts(**module.params) - ansible_facts = facts.get_facts(module) + gather_subset = module.params['gather_subset'] + facts = NxosFacts(**module.params) + ansible_facts = facts.get_facts(module, gather_subset) module.exit_json(ansible_facts=ansible_facts, warnings=warnings) diff --git a/library/nxos_interfaces.py b/library/nxos_interfaces.py index a832312..d7d7225 100644 --- a/library/nxos_interfaces.py +++ b/library/nxos_interfaces.py @@ -52,6 +52,7 @@ def main(): """ module = AnsibleModule(argument_spec=Interface.argument_spec, supports_check_mode=True) + result = {'changed': False} commands = list() diff --git a/module_utils/nxos/config/base.py b/module_utils/nxos/config/base.py deleted file mode 100644 index d1faafe..0000000 --- a/module_utils/nxos/config/base.py +++ /dev/null @@ -1,10 +0,0 @@ -from ansible.module_utils.argspec.base import ArgspecBase - - -class ConfigBase(ArgspecBase): - - def search_obj_in_list(self, name, lst): - for o in lst: - if o['name'] == name: - return o - return None diff --git a/module_utils/nxos/config/interfaces/interfaces.py b/module_utils/nxos/config/interfaces/interfaces.py index bb32609..5e9d75d 100644 --- a/module_utils/nxos/config/interfaces/interfaces.py +++ b/module_utils/nxos/config/interfaces/interfaces.py @@ -1,19 +1,15 @@ from ansible.module_utils.network.common.utils import to_list from ansible.module_utils.argspec.nxos.interfaces.interfaces import InterfaceArgs -from ansible.module_utils.network.nxos.nxos import get_config, get_interface_type, normalize_interface -from ansible.module_utils.nxos.config.base import ConfigBase -from ansible.module_utils.nxos.facts.interfaces.interfaces import NxosInterfacesFacts +from ansible.module_utils.nxos.facts.facts import NxosFacts +from ansible.module_utils.nxos.utils.utils import get_interface_type, normalize_interface, search_obj_in_list from ansible.module_utils.six import iteritems -class Interface(ConfigBase): - - argument_spec = InterfaceArgs.argument_spec +class Interface(InterfaceArgs): def set_config(self, module): want = self._config_map_params_to_obj(module) - data = get_config(module, ['| section ^interface']) - facts = NxosInterfacesFacts(self.argument_spec, data, 'config', 'options').populate_facts() + facts = NxosFacts().get_facts(module, gather_subset=['net_configuration_interfaces']) have = facts['net_configuration'].get('interfaces') resp = self.set_state(want, have) return to_list(resp) @@ -47,7 +43,7 @@ def set_state(self, want, have): for w in want: name = w['name'] interface_type = get_interface_type(name) - obj_in_have = self.search_obj_in_list(name, have) + obj_in_have = search_obj_in_list(name, have) if state == 'deleted' and obj_in_have: commands.append('no interface {0}'.format(w['name'])) @@ -79,7 +75,7 @@ def _state_overriden(self, want, have): for h in have: name = h['name'] - obj_in_want = self.search_obj_in_list(name, want) + obj_in_want = search_obj_in_list(name, want) if not obj_in_want: interface_type = get_interface_type(name) @@ -105,7 +101,7 @@ def _state_overriden(self, want, have): for w in want: name = w['name'] interface_type = get_interface_type(name) - obj_in_have = self.search_obj_in_list(name, have) + obj_in_have = search_obj_in_list(name, have) commands.extend(self._state_merged( w, obj_in_have, interface_type)) return commands diff --git a/module_utils/nxos/facts/base.py b/module_utils/nxos/facts/base.py index c202edc..069a0c6 100644 --- a/module_utils/nxos/facts/base.py +++ b/module_utils/nxos/facts/base.py @@ -9,10 +9,7 @@ class FactsBase(object): generated_spec = {} ansible_facts = {'net_configuration': {}} - def __init__(self, argspec, data=None, subspec=None, options=None): - if data: - self.data = data - + def __init__(self, argspec, subspec=None, options=None): spec = deepcopy(argspec) if subspec: if options: diff --git a/module_utils/nxos/facts/facts.py b/module_utils/nxos/facts/facts.py index 04aa0ba..161cef0 100644 --- a/module_utils/nxos/facts/facts.py +++ b/module_utils/nxos/facts/facts.py @@ -1,20 +1,17 @@ from ansible.module_utils.argspec.nxos.facts.facts import FactsArgs from ansible.module_utils.argspec.nxos.interfaces.interfaces import InterfaceArgs -from ansible.module_utils.argspec.base import ArgspecBase from ansible.module_utils.network.nxos.nxos import get_config from ansible.module_utils.nxos.facts.base import FactsBase from ansible.module_utils.nxos.facts.interfaces.interfaces import NxosInterfacesFacts -class Facts(ArgspecBase, FactsBase): +class NxosFacts(FactsArgs, FactsBase): - argument_spec = FactsArgs.argument_spec VALID_SUBSETS = [ - 'ansible_net_configuration_interfaces', + 'net_configuration_interfaces', ] - def get_facts(self, module): - gather_subset = module.params['gather_subset'] + def get_facts(self, module, gather_subset=['all']): runable_subsets = set() exclude_subsets = set() @@ -50,6 +47,5 @@ def get_facts(self, module): return self.ansible_facts - def _get_ansible_net_configuration_interfaces(self, module): - data = get_config(module, ['| section ^interface']) - return NxosInterfacesFacts(InterfaceArgs.argument_spec, data, 'config', 'options').populate_facts() + def _get_net_configuration_interfaces(self, module): + return NxosInterfacesFacts(InterfaceArgs.argument_spec, 'config', 'options').populate_facts(module) diff --git a/module_utils/nxos/facts/interfaces/interfaces.py b/module_utils/nxos/facts/interfaces/interfaces.py index 5e7dd2e..0bacf11 100644 --- a/module_utils/nxos/facts/interfaces/interfaces.py +++ b/module_utils/nxos/facts/interfaces/interfaces.py @@ -2,20 +2,21 @@ from copy import deepcopy -from ansible.module_utils.network.nxos.nxos import get_interface_type, normalize_interface +from ansible.module_utils.network.nxos.nxos import get_config, get_interface_type, normalize_interface from ansible.module_utils.nxos.facts.base import FactsBase class NxosInterfacesFacts(FactsBase): - def populate_facts(self): + def populate_facts(self, module, data=None): """ Populate nxos interfaces facts """ objs = [] - if self.data is None: - return {} - config = self.data.split('interface ') + if not data: + data = get_config(module, ['| section ^interface']) + + config = data.split('interface ') for conf in config: if conf: obj = self.render_config(self.generated_spec, conf) From abc483972feaca8b40fba6c5c4b64d157141ab8c Mon Sep 17 00:00:00 2001 From: Trishna Guha Date: Tue, 19 Mar 2019 11:48:30 +0530 Subject: [PATCH 07/21] Add utils Signed-off-by: Trishna Guha --- module_utils/nxos/utils/__init__.py | 0 module_utils/nxos/utils/utils.py | 64 +++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+) create mode 100644 module_utils/nxos/utils/__init__.py create mode 100644 module_utils/nxos/utils/utils.py diff --git a/module_utils/nxos/utils/__init__.py b/module_utils/nxos/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/module_utils/nxos/utils/utils.py b/module_utils/nxos/utils/utils.py new file mode 100644 index 0000000..492665a --- /dev/null +++ b/module_utils/nxos/utils/utils.py @@ -0,0 +1,64 @@ +def search_obj_in_list(name, lst): + for o in lst: + if o['name'] == name: + return o + return None + +def normalize_interface(name): + """Return the normalized interface name + """ + if not name: + return + + def _get_number(name): + digits = '' + for char in name: + if char.isdigit() or char in '/.': + digits += char + return digits + + if name.lower().startswith('et'): + if_type = 'Ethernet' + elif name.lower().startswith('vl'): + if_type = 'Vlan' + elif name.lower().startswith('lo'): + if_type = 'loopback' + elif name.lower().startswith('po'): + if_type = 'port-channel' + elif name.lower().startswith('nv'): + if_type = 'nve' + else: + if_type = None + + number_list = name.split(' ') + if len(number_list) == 2: + number = number_list[-1].strip() + else: + number = _get_number(name) + + if if_type: + proper_interface = if_type + number + else: + proper_interface = name + + return proper_interface + +def get_interface_type(interface): + """Gets the type of interface + """ + if interface.upper().startswith('ET'): + return 'ethernet' + elif interface.upper().startswith('VL'): + return 'svi' + elif interface.upper().startswith('LO'): + return 'loopback' + elif interface.upper().startswith('MG'): + return 'management' + elif interface.upper().startswith('MA'): + return 'management' + elif interface.upper().startswith('PO'): + return 'portchannel' + elif interface.upper().startswith('NV'): + return 'nve' + else: + return 'unknown' From ef011c617b7a82b57ce515dc685d3a77a2242f3b Mon Sep 17 00:00:00 2001 From: Trishna Guha Date: Tue, 19 Mar 2019 17:51:54 +0530 Subject: [PATCH 08/21] execute_module API Signed-off-by: Trishna Guha --- library/nxos_facts.py | 4 ++- library/nxos_interfaces.py | 14 ++------ .../nxos/config/interfaces/interfaces.py | 35 +++++++++++++++++-- module_utils/nxos/facts/base.py | 3 +- module_utils/nxos/facts/facts.py | 9 +++-- .../nxos/facts/interfaces/interfaces.py | 10 +++--- 6 files changed, 50 insertions(+), 25 deletions(-) diff --git a/library/nxos_facts.py b/library/nxos_facts.py index 01bfa38..7689065 100644 --- a/library/nxos_facts.py +++ b/library/nxos_facts.py @@ -68,6 +68,7 @@ from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.connection import Connection from ansible.module_utils.nxos.facts.facts import NxosFacts @@ -75,9 +76,10 @@ def main(): module = AnsibleModule(argument_spec=NxosFacts.argument_spec, supports_check_mode=True) warnings = list() + connection = Connection(module._socket_path) gather_subset = module.params['gather_subset'] facts = NxosFacts(**module.params) - ansible_facts = facts.get_facts(module, gather_subset) + ansible_facts = facts.get_facts(module, connection, gather_subset) module.exit_json(ansible_facts=ansible_facts, warnings=warnings) diff --git a/library/nxos_interfaces.py b/library/nxos_interfaces.py index d7d7225..329d796 100644 --- a/library/nxos_interfaces.py +++ b/library/nxos_interfaces.py @@ -43,7 +43,7 @@ """ from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils.network.nxos.nxos import load_config +from ansible.module_utils.connection import Connection from ansible.module_utils.nxos.config.interfaces.interfaces import Interface @@ -53,17 +53,9 @@ def main(): module = AnsibleModule(argument_spec=Interface.argument_spec, supports_check_mode=True) - result = {'changed': False} - commands = list() - + connection = Connection(module._socket_path) intf = Interface(**module.params) - commands.extend(intf.set_config(module)) - if commands: - if not module.check_mode: - load_config(module, commands) - result['changed'] = True - - result['commands'] = commands + result = intf.execute_module(module, connection) module.exit_json(**result) diff --git a/module_utils/nxos/config/interfaces/interfaces.py b/module_utils/nxos/config/interfaces/interfaces.py index 5e9d75d..388b043 100644 --- a/module_utils/nxos/config/interfaces/interfaces.py +++ b/module_utils/nxos/config/interfaces/interfaces.py @@ -1,15 +1,44 @@ +from ansible.module_utils.six import iteritems + from ansible.module_utils.network.common.utils import to_list + from ansible.module_utils.argspec.nxos.interfaces.interfaces import InterfaceArgs from ansible.module_utils.nxos.facts.facts import NxosFacts from ansible.module_utils.nxos.utils.utils import get_interface_type, normalize_interface, search_obj_in_list -from ansible.module_utils.six import iteritems class Interface(InterfaceArgs): - def set_config(self, module): + gather_subset = [ + 'net_configuration_interfaces', + ] + + def execute_module(self, module, connection): + result = {'changed': False} + commands = list() + warnings = list() + + commands.extend(self.set_config(module, connection)) + if commands: + if not module.check_mode: + connection.edit_config(commands) + result['changed'] = True + result['commands'] = commands + + facts = NxosFacts().get_facts(module, connection, self.gather_subset) + interfaces_facts = facts['net_configuration'].get('interfaces') + + if result['changed'] == False: + result['before'] = interfaces_facts + elif result['changed'] == True: + result['after'] = interfaces_facts + + result['warnings'] = warnings + return result + + def set_config(self, module, connection): want = self._config_map_params_to_obj(module) - facts = NxosFacts().get_facts(module, gather_subset=['net_configuration_interfaces']) + facts = NxosFacts().get_facts(module, connection, self.gather_subset) have = facts['net_configuration'].get('interfaces') resp = self.set_state(want, have) return to_list(resp) diff --git a/module_utils/nxos/facts/base.py b/module_utils/nxos/facts/base.py index 069a0c6..a700b6f 100644 --- a/module_utils/nxos/facts/base.py +++ b/module_utils/nxos/facts/base.py @@ -1,4 +1,5 @@ import re + from copy import deepcopy from ansible.module_utils.six import iteritems @@ -19,7 +20,7 @@ def __init__(self, argspec, subspec=None, options=None): else: facts_argument_spec = spec - generate_spec = self.generate_dict(facts_argument_spec) + self.generated_spec = self.generate_dict(facts_argument_spec) def generate_dict(self, spec): """ diff --git a/module_utils/nxos/facts/facts.py b/module_utils/nxos/facts/facts.py index 161cef0..28c5587 100644 --- a/module_utils/nxos/facts/facts.py +++ b/module_utils/nxos/facts/facts.py @@ -1,6 +1,5 @@ from ansible.module_utils.argspec.nxos.facts.facts import FactsArgs from ansible.module_utils.argspec.nxos.interfaces.interfaces import InterfaceArgs -from ansible.module_utils.network.nxos.nxos import get_config from ansible.module_utils.nxos.facts.base import FactsBase from ansible.module_utils.nxos.facts.interfaces.interfaces import NxosInterfacesFacts @@ -11,7 +10,7 @@ class NxosFacts(FactsArgs, FactsBase): 'net_configuration_interfaces', ] - def get_facts(self, module, gather_subset=['all']): + def get_facts(self, module, connection, gather_subset=['all']): runable_subsets = set() exclude_subsets = set() @@ -43,9 +42,9 @@ def get_facts(self, module, gather_subset=['all']): self.ansible_facts['gather_subset'] = list(runable_subsets) for attr in runable_subsets: - getattr(self, '_get_%s' % attr, {})(module) + getattr(self, '_get_%s' % attr, {})(module, connection) return self.ansible_facts - def _get_net_configuration_interfaces(self, module): - return NxosInterfacesFacts(InterfaceArgs.argument_spec, 'config', 'options').populate_facts(module) + def _get_net_configuration_interfaces(self, module, connection): + return NxosInterfacesFacts(InterfaceArgs.argument_spec, 'config', 'options').populate_facts(module, connection) diff --git a/module_utils/nxos/facts/interfaces/interfaces.py b/module_utils/nxos/facts/interfaces/interfaces.py index 0bacf11..33073bb 100644 --- a/module_utils/nxos/facts/interfaces/interfaces.py +++ b/module_utils/nxos/facts/interfaces/interfaces.py @@ -2,19 +2,20 @@ from copy import deepcopy -from ansible.module_utils.network.nxos.nxos import get_config, get_interface_type, normalize_interface +from ansible.module_utils.nxos.utils.utils import get_interface_type, normalize_interface from ansible.module_utils.nxos.facts.base import FactsBase class NxosInterfacesFacts(FactsBase): - def populate_facts(self, module, data=None): + def populate_facts(self, module, connection, data=None): """ Populate nxos interfaces facts """ objs = [] + if not data: - data = get_config(module, ['| section ^interface']) + data = connection.get('show running-config | section ^interface') config = data.split('interface ') for conf in config: @@ -46,7 +47,8 @@ def render_config(self, spec, conf): config['mtu'] = self.parse_conf_arg(conf, 'mtu') config['duplex'] = self.parse_conf_arg(conf, 'duplex') config['mode'] = self.parse_conf_cmd_arg(conf, 'switchport', 'layer2', res2='layer3') - config['enable'] = self.parse_conf_cmd_arg(conf, 'shutdown', False) + enable = self.parse_conf_cmd_arg(conf, 'shutdown', False) + config['enable'] = enable if enable is not None else config['enable'] config['fabric_forwarding_anycast_gateway'] = self.parse_conf_cmd_arg(conf, 'fabric forwarding mode anycast-gateway', True, res2=False) config['ip_forward'] = self.parse_conf_cmd_arg(conf, 'ip forward', 'enable', res2='disable') From 2832b3cb690346a1d0d32d071e673c570f95f637 Mon Sep 17 00:00:00 2001 From: Trishna Guha Date: Wed, 20 Mar 2019 20:50:08 +0530 Subject: [PATCH 09/21] update ArgspecBase Signed-off-by: Trishna Guha --- module_utils/argspec/base.py | 22 +--------- .../nxos/config/interfaces/interfaces.py | 43 +++++++++++-------- 2 files changed, 25 insertions(+), 40 deletions(-) diff --git a/module_utils/argspec/base.py b/module_utils/argspec/base.py index a618475..650d49f 100644 --- a/module_utils/argspec/base.py +++ b/module_utils/argspec/base.py @@ -1,24 +1,4 @@ -from ansible.module_utils.six import iteritems - - class ArgspecBase(object): - argument_spec = {} - def __init__(self, **kwargs): - self.values = {} - - for key, value in iteritems(kwargs): - if key in self.argument_spec: - setattr(self, key, value) - - def __getattr__(self, key): - if key in self.argument_spec: - return self.values.get(key) - - def __setattr__(self, key, value): - if key in self.argument_spec: - if value is not None: - self.values[key] = value - else: - super(ArgspecBase, self).__setattr__(key, value) + pass diff --git a/module_utils/nxos/config/interfaces/interfaces.py b/module_utils/nxos/config/interfaces/interfaces.py index 388b043..d49cb95 100644 --- a/module_utils/nxos/config/interfaces/interfaces.py +++ b/module_utils/nxos/config/interfaces/interfaces.py @@ -13,6 +13,13 @@ class Interface(InterfaceArgs): 'net_configuration_interfaces', ] + def get_interface_facts(self, module, connection): + facts = NxosFacts().get_facts(module, connection, self.gather_subset) + interface_facts = facts['net_configuration'].get('interfaces') + if not interface_facts: + return [] + return interface_facts + def execute_module(self, module, connection): result = {'changed': False} commands = list() @@ -25,22 +32,20 @@ def execute_module(self, module, connection): result['changed'] = True result['commands'] = commands - facts = NxosFacts().get_facts(module, connection, self.gather_subset) - interfaces_facts = facts['net_configuration'].get('interfaces') + interface_facts = self.get_interface_facts(module, connection) if result['changed'] == False: - result['before'] = interfaces_facts + result['before'] = interface_facts elif result['changed'] == True: - result['after'] = interfaces_facts + result['after'] = interface_facts result['warnings'] = warnings return result def set_config(self, module, connection): want = self._config_map_params_to_obj(module) - facts = NxosFacts().get_facts(module, connection, self.gather_subset) - have = facts['net_configuration'].get('interfaces') - resp = self.set_state(want, have) + have = self.get_interface_facts(module, connection) + resp = self.set_state(module, want, have) return to_list(resp) def _config_map_params_to_obj(self, module): @@ -62,10 +67,10 @@ def _config_map_params_to_obj(self, module): return objs - def set_state(self, want, have): + def set_state(self, module, want, have): commands = list() - state = self.state + state = module.params['state'] if state == 'overriden': commands.extend(self._state_overriden(want, have)) else: @@ -179,31 +184,31 @@ def _state_merged(self, w, obj_in_have, interface_type): else: if interface_type in ('ethernet', 'portchannel'): if mode == 'layer2' and mode != obj_in_have.get('mode'): - self.add_command_to_interface(interface, 'switchport', commands) + self._add_command_to_interface(interface, 'switchport', commands) elif mode == 'layer3' and mode != obj_in_have.get('mode'): - self.add_command_to_interface(interface, 'no switchport', commands) + self._add_command_to_interface(interface, 'no switchport', commands) if enable is True and enable != obj_in_have.get('enable'): - self.add_command_to_interface(interface, 'no shutdown', commands) + self._add_command_to_interface(interface, 'no shutdown', commands) elif enable is False and enable != obj_in_have.get('enable'): - self.add_command_to_interface(interface, 'shutdown', commands) + self._add_command_to_interface(interface, 'shutdown', commands) if ip_forward == 'enable' and ip_forward != obj_in_have.get('ip_forward'): - self.add_command_to_interface(interface, 'ip forward', commands) + self._add_command_to_interface(interface, 'ip forward', commands) elif ip_forward == 'disable' and ip_forward != obj_in_have.get('ip forward'): - self.add_command_to_interface(interface, 'no ip forward', commands) + self._add_command_to_interface(interface, 'no ip forward', commands) if (fabric_forwarding_anycast_gateway is True and obj_in_have.get('fabric_forwarding_anycast_gateway') is False): - self.add_command_to_interface(interface, 'fabric forwarding mode anycast-gateway', commands) + self._add_command_to_interface(interface, 'fabric forwarding mode anycast-gateway', commands) elif (fabric_forwarding_anycast_gateway is False and obj_in_have.get('fabric_forwarding_anycast_gateway') is True): - self.add_command_to_interface(interface, 'no fabric forwarding mode anycast-gateway', commands) + self._add_command_to_interface(interface, 'no fabric forwarding mode anycast-gateway', commands) for item in args: candidate = w.get(item) if candidate and candidate != obj_in_have.get(item): cmd = item + ' ' + str(candidate) - self.add_command_to_interface(interface, cmd, commands) + self._add_command_to_interface(interface, cmd, commands) # if the mode changes from L2 to L3, the admin state # seems to change after the API call, so adding a second API @@ -224,7 +229,7 @@ def _get_admin_state(self, enable): command = 'shutdown' return command - def add_command_to_interface(self, interface, cmd, commands): + def _add_command_to_interface(self, interface, cmd, commands): if interface not in commands: commands.append(interface) commands.append(cmd) From 3819ecacd4bd09b364caf7bdcb672f846e8fea01 Mon Sep 17 00:00:00 2001 From: Trishna Guha Date: Thu, 21 Mar 2019 10:22:29 +0530 Subject: [PATCH 10/21] ConfigBase class Signed-off-by: Trishna Guha --- library/nxos_facts.py | 6 ++-- library/nxos_interfaces.py | 5 +-- module_utils/nxos/config/base.py | 16 +++++++++ .../nxos/config/interfaces/interfaces.py | 34 +++++++++---------- .../nxos/facts/interfaces/interfaces.py | 2 +- 5 files changed, 38 insertions(+), 25 deletions(-) create mode 100644 module_utils/nxos/config/base.py diff --git a/library/nxos_facts.py b/library/nxos_facts.py index 7689065..f4871ec 100644 --- a/library/nxos_facts.py +++ b/library/nxos_facts.py @@ -73,13 +73,13 @@ def main(): - module = AnsibleModule(argument_spec=NxosFacts.argument_spec, supports_check_mode=True) + module = AnsibleModule(argument_spec=NxosFacts.argument_spec, + supports_check_mode=True) warnings = list() connection = Connection(module._socket_path) gather_subset = module.params['gather_subset'] - facts = NxosFacts(**module.params) - ansible_facts = facts.get_facts(module, connection, gather_subset) + ansible_facts = NxosFacts().get_facts(module, connection, gather_subset) module.exit_json(ansible_facts=ansible_facts, warnings=warnings) diff --git a/library/nxos_interfaces.py b/library/nxos_interfaces.py index 329d796..a636aa6 100644 --- a/library/nxos_interfaces.py +++ b/library/nxos_interfaces.py @@ -43,7 +43,6 @@ """ from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils.connection import Connection from ansible.module_utils.nxos.config.interfaces.interfaces import Interface @@ -53,9 +52,7 @@ def main(): module = AnsibleModule(argument_spec=Interface.argument_spec, supports_check_mode=True) - connection = Connection(module._socket_path) - intf = Interface(**module.params) - result = intf.execute_module(module, connection) + result = Interface(module).execute_module() module.exit_json(**result) diff --git a/module_utils/nxos/config/base.py b/module_utils/nxos/config/base.py new file mode 100644 index 0000000..191921f --- /dev/null +++ b/module_utils/nxos/config/base.py @@ -0,0 +1,16 @@ +from ansible.module_utils.connection import Connection + + +class ConfigBase(object): + + _connection = None + + def __init__(self, module): + self._module = module + self._connection = self._get_connection() + + def _get_connection(self): + if self._connection: + return self._connection + self._connection = Connection(self._module._socket_path) + return self._connection diff --git a/module_utils/nxos/config/interfaces/interfaces.py b/module_utils/nxos/config/interfaces/interfaces.py index d49cb95..3bafe83 100644 --- a/module_utils/nxos/config/interfaces/interfaces.py +++ b/module_utils/nxos/config/interfaces/interfaces.py @@ -1,38 +1,38 @@ from ansible.module_utils.six import iteritems - from ansible.module_utils.network.common.utils import to_list from ansible.module_utils.argspec.nxos.interfaces.interfaces import InterfaceArgs +from ansible.module_utils.nxos.config.base import ConfigBase from ansible.module_utils.nxos.facts.facts import NxosFacts from ansible.module_utils.nxos.utils.utils import get_interface_type, normalize_interface, search_obj_in_list -class Interface(InterfaceArgs): +class Interface(ConfigBase, InterfaceArgs): gather_subset = [ 'net_configuration_interfaces', ] - def get_interface_facts(self, module, connection): - facts = NxosFacts().get_facts(module, connection, self.gather_subset) + def get_interface_facts(self): + facts = NxosFacts().get_facts(self._module, self._connection, self.gather_subset) interface_facts = facts['net_configuration'].get('interfaces') if not interface_facts: return [] return interface_facts - def execute_module(self, module, connection): + def execute_module(self): result = {'changed': False} commands = list() warnings = list() - commands.extend(self.set_config(module, connection)) + commands.extend(self.set_config()) if commands: - if not module.check_mode: - connection.edit_config(commands) + if not self._module.check_mode: + self._connection.edit_config(commands) result['changed'] = True result['commands'] = commands - interface_facts = self.get_interface_facts(module, connection) + interface_facts = self.get_interface_facts() if result['changed'] == False: result['before'] = interface_facts @@ -42,15 +42,15 @@ def execute_module(self, module, connection): result['warnings'] = warnings return result - def set_config(self, module, connection): - want = self._config_map_params_to_obj(module) - have = self.get_interface_facts(module, connection) - resp = self.set_state(module, want, have) + def set_config(self): + want = self._config_map_params_to_obj() + have = self.get_interface_facts() + resp = self.set_state(want, have) return to_list(resp) - def _config_map_params_to_obj(self, module): + def _config_map_params_to_obj(self): objs = [] - collection = module.params['config'] + collection = self._module.params['config'] for config in collection: obj = { 'name': normalize_interface(config['name']), @@ -67,10 +67,10 @@ def _config_map_params_to_obj(self, module): return objs - def set_state(self, module, want, have): + def set_state(self, want, have): commands = list() - state = module.params['state'] + state = self._module.params['state'] if state == 'overriden': commands.extend(self._state_overriden(want, have)) else: diff --git a/module_utils/nxos/facts/interfaces/interfaces.py b/module_utils/nxos/facts/interfaces/interfaces.py index 33073bb..19b46f8 100644 --- a/module_utils/nxos/facts/interfaces/interfaces.py +++ b/module_utils/nxos/facts/interfaces/interfaces.py @@ -2,8 +2,8 @@ from copy import deepcopy -from ansible.module_utils.nxos.utils.utils import get_interface_type, normalize_interface from ansible.module_utils.nxos.facts.base import FactsBase +from ansible.module_utils.nxos.utils.utils import get_interface_type, normalize_interface class NxosInterfacesFacts(FactsBase): From a5510551053b8bb7d25fd7d9fba5b460517981f2 Mon Sep 17 00:00:00 2001 From: Trishna Guha Date: Thu, 21 Mar 2019 13:12:13 +0530 Subject: [PATCH 11/21] fix regex and state_deleted Signed-off-by: Trishna Guha --- .../nxos/config/interfaces/interfaces.py | 50 +++++++++++++++++-- .../nxos/facts/interfaces/interfaces.py | 2 +- 2 files changed, 47 insertions(+), 5 deletions(-) diff --git a/module_utils/nxos/config/interfaces/interfaces.py b/module_utils/nxos/config/interfaces/interfaces.py index 3bafe83..2d6560d 100644 --- a/module_utils/nxos/config/interfaces/interfaces.py +++ b/module_utils/nxos/config/interfaces/interfaces.py @@ -78,8 +78,9 @@ def set_state(self, want, have): name = w['name'] interface_type = get_interface_type(name) obj_in_have = search_obj_in_list(name, have) - if state == 'deleted' and obj_in_have: - commands.append('no interface {0}'.format(w['name'])) + + if state == 'deleted': + commands.extend(self._state_deleted(w, obj_in_have, interface_type)) if state == 'merged': commands.extend(self._state_merged(w, obj_in_have, interface_type)) @@ -119,7 +120,7 @@ def _state_overriden(self, want, have): elif interface_type == 'ethernet': default = True if h['enable'] is True: - keys = ('description', 'mode', 'mtu', 'speed', 'duplex', 'ip_forward','fabric_forwarding_anycast_gateway') + keys = ('description', 'mode', 'mtu', 'speed', 'duplex', 'ip_forward', 'fabric_forwarding_anycast_gateway') for k, v in iteritems(h): if k in keys: if h[k] is not None: @@ -221,6 +222,47 @@ def _state_merged(self, w, obj_in_have, interface_type): return commands + def _state_deleted(self, w, obj_in_have, interface_type): + commands = [] + if not obj_in_have or interface_type == 'unknown': + return commands + + interface = 'interface ' + w['name'] + + if 'description' in obj_in_have: + self._remove_command_from_interface(interface, 'description', commands) + if 'enable' in obj_in_have and obj_in_have['enable'] is False: + # if enable is False set enable as True which is the default behavior + self._remove_command_from_interface(interface, 'shutdown', commands) + + if interface_type == 'ethernet': + if 'mode' in obj_in_have and obj_in_have['mode'] != 'layer2': + # if mode is not layer2 set mode as layer2 which is the default behavior + self._remove_command_from_interface(interface, 'switchport', commands) + + if 'speed' in obj_in_have: + self._remove_command_from_interface(interface, 'speed', commands) + if 'duplex' in obj_in_have: + self._remote_command_from_interface(interface, 'duplex', commands) + + if interface_type in ('ethernet', 'portchannel', 'svi'): + if 'mtu' in obj_in_have: + self._remove_command_from_interface(interface, 'mtu', commands) + + if interface_type in ('ethernet', 'svi'): + if 'ip_forward' in obj_in_have: + self._remove_command_from_interface(interface, 'ip forward', commands) + if 'fabric_forwarding_anycast_gateway' in obj_in_have: + self._remove_command_from_interface(interface, 'fabric forwarding anycast gateway', commands) + + return commands + + def _remove_command_from_interface(self, interface, cmd, commands): + if interface not in commands: + commands.insert(0, interface) + commands.append('no %s' % cmd) + return commands + def _get_admin_state(self, enable): command = '' if enable is True: @@ -231,5 +273,5 @@ def _get_admin_state(self, enable): def _add_command_to_interface(self, interface, cmd, commands): if interface not in commands: - commands.append(interface) + commands.insert(0, interface) commands.append(cmd) diff --git a/module_utils/nxos/facts/interfaces/interfaces.py b/module_utils/nxos/facts/interfaces/interfaces.py index 19b46f8..f069aa0 100644 --- a/module_utils/nxos/facts/interfaces/interfaces.py +++ b/module_utils/nxos/facts/interfaces/interfaces.py @@ -36,7 +36,7 @@ def render_config(self, spec, conf): """ config = deepcopy(spec) - match = re.search(r'^(\S+)\n', conf) + match = re.search(r'^(\S+)', conf) intf = match.group(1) if get_interface_type(intf) == 'unknown': return {} From 9d76427649e467cc5efff682a2ef7cad212172b4 Mon Sep 17 00:00:00 2001 From: Trishna Guha Date: Fri, 22 Mar 2019 10:19:15 +0530 Subject: [PATCH 12/21] move argspec to ansible_network_os dir Signed-off-by: Trishna Guha --- module_utils/{argspec => network}/__init__.py | 0 module_utils/{argspec/nxos => network/argspec}/__init__.py | 0 module_utils/{ => network}/argspec/base.py | 0 module_utils/{argspec/nxos/facts => nxos/argspec}/__init__.py | 0 .../nxos/interfaces => nxos/argspec/facts}/__init__.py | 0 module_utils/{argspec/nxos => nxos/argspec}/facts/facts.py | 2 +- module_utils/nxos/argspec/interfaces/__init__.py | 0 .../{argspec/nxos => nxos/argspec}/interfaces/interfaces.py | 3 +-- module_utils/nxos/config/interfaces/interfaces.py | 2 +- module_utils/nxos/facts/facts.py | 4 ++-- 10 files changed, 5 insertions(+), 6 deletions(-) rename module_utils/{argspec => network}/__init__.py (100%) rename module_utils/{argspec/nxos => network/argspec}/__init__.py (100%) rename module_utils/{ => network}/argspec/base.py (100%) rename module_utils/{argspec/nxos/facts => nxos/argspec}/__init__.py (100%) rename module_utils/{argspec/nxos/interfaces => nxos/argspec/facts}/__init__.py (100%) rename module_utils/{argspec/nxos => nxos/argspec}/facts/facts.py (64%) create mode 100644 module_utils/nxos/argspec/interfaces/__init__.py rename module_utils/{argspec/nxos => nxos/argspec}/interfaces/interfaces.py (85%) diff --git a/module_utils/argspec/__init__.py b/module_utils/network/__init__.py similarity index 100% rename from module_utils/argspec/__init__.py rename to module_utils/network/__init__.py diff --git a/module_utils/argspec/nxos/__init__.py b/module_utils/network/argspec/__init__.py similarity index 100% rename from module_utils/argspec/nxos/__init__.py rename to module_utils/network/argspec/__init__.py diff --git a/module_utils/argspec/base.py b/module_utils/network/argspec/base.py similarity index 100% rename from module_utils/argspec/base.py rename to module_utils/network/argspec/base.py diff --git a/module_utils/argspec/nxos/facts/__init__.py b/module_utils/nxos/argspec/__init__.py similarity index 100% rename from module_utils/argspec/nxos/facts/__init__.py rename to module_utils/nxos/argspec/__init__.py diff --git a/module_utils/argspec/nxos/interfaces/__init__.py b/module_utils/nxos/argspec/facts/__init__.py similarity index 100% rename from module_utils/argspec/nxos/interfaces/__init__.py rename to module_utils/nxos/argspec/facts/__init__.py diff --git a/module_utils/argspec/nxos/facts/facts.py b/module_utils/nxos/argspec/facts/facts.py similarity index 64% rename from module_utils/argspec/nxos/facts/facts.py rename to module_utils/nxos/argspec/facts/facts.py index 84f383c..503502b 100644 --- a/module_utils/argspec/nxos/facts/facts.py +++ b/module_utils/nxos/argspec/facts/facts.py @@ -1,4 +1,4 @@ -from ansible.module_utils.argspec.base import ArgspecBase +from ansible.module_utils.network.argspec.base import ArgspecBase class FactsArgs(ArgspecBase): diff --git a/module_utils/nxos/argspec/interfaces/__init__.py b/module_utils/nxos/argspec/interfaces/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/module_utils/argspec/nxos/interfaces/interfaces.py b/module_utils/nxos/argspec/interfaces/interfaces.py similarity index 85% rename from module_utils/argspec/nxos/interfaces/interfaces.py rename to module_utils/nxos/argspec/interfaces/interfaces.py index a97550e..9eaded4 100644 --- a/module_utils/argspec/nxos/interfaces/interfaces.py +++ b/module_utils/nxos/argspec/interfaces/interfaces.py @@ -1,4 +1,4 @@ -from ansible.module_utils.argspec.base import ArgspecBase +from ansible.module_utils.network.argspec.base import ArgspecBase class InterfaceArgs(ArgspecBase): @@ -10,7 +10,6 @@ class InterfaceArgs(ArgspecBase): 'mode': dict(choices=['layer2', 'layer3']), 'mtu': dict(), 'duplex': dict(choices=['full', 'half', 'auto']), - 'mode': dict(choices=['layer2', 'layer3']), 'ip_forward': dict(choices=['enable', 'disable']), 'fabric_forwarding_anycast_gateway': dict(type='bool'), } diff --git a/module_utils/nxos/config/interfaces/interfaces.py b/module_utils/nxos/config/interfaces/interfaces.py index 2d6560d..dcf0a2c 100644 --- a/module_utils/nxos/config/interfaces/interfaces.py +++ b/module_utils/nxos/config/interfaces/interfaces.py @@ -1,7 +1,7 @@ from ansible.module_utils.six import iteritems from ansible.module_utils.network.common.utils import to_list -from ansible.module_utils.argspec.nxos.interfaces.interfaces import InterfaceArgs +from ansible.module_utils.nxos.argspec.interfaces.interfaces import InterfaceArgs from ansible.module_utils.nxos.config.base import ConfigBase from ansible.module_utils.nxos.facts.facts import NxosFacts from ansible.module_utils.nxos.utils.utils import get_interface_type, normalize_interface, search_obj_in_list diff --git a/module_utils/nxos/facts/facts.py b/module_utils/nxos/facts/facts.py index 28c5587..21e447d 100644 --- a/module_utils/nxos/facts/facts.py +++ b/module_utils/nxos/facts/facts.py @@ -1,5 +1,5 @@ -from ansible.module_utils.argspec.nxos.facts.facts import FactsArgs -from ansible.module_utils.argspec.nxos.interfaces.interfaces import InterfaceArgs +from ansible.module_utils.nxos.argspec.facts.facts import FactsArgs +from ansible.module_utils.nxos.argspec.interfaces.interfaces import InterfaceArgs from ansible.module_utils.nxos.facts.base import FactsBase from ansible.module_utils.nxos.facts.interfaces.interfaces import NxosInterfacesFacts From 49241c603d4c82fbcb4cc332817d392316524cfe Mon Sep 17 00:00:00 2001 From: Trishna Guha Date: Fri, 22 Mar 2019 10:27:30 +0530 Subject: [PATCH 13/21] standardize nomenclature Signed-off-by: Trishna Guha --- library/nxos_facts.py | 6 +++--- library/nxos_interfaces.py | 6 +++--- module_utils/nxos/config/interfaces/interfaces.py | 6 +++--- module_utils/nxos/facts/facts.py | 6 +++--- module_utils/nxos/facts/interfaces/interfaces.py | 2 +- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/library/nxos_facts.py b/library/nxos_facts.py index f4871ec..19da266 100644 --- a/library/nxos_facts.py +++ b/library/nxos_facts.py @@ -69,17 +69,17 @@ from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.connection import Connection -from ansible.module_utils.nxos.facts.facts import NxosFacts +from ansible.module_utils.nxos.facts.facts import Facts def main(): - module = AnsibleModule(argument_spec=NxosFacts.argument_spec, + module = AnsibleModule(argument_spec=Facts.argument_spec, supports_check_mode=True) warnings = list() connection = Connection(module._socket_path) gather_subset = module.params['gather_subset'] - ansible_facts = NxosFacts().get_facts(module, connection, gather_subset) + ansible_facts = Facts().get_facts(module, connection, gather_subset) module.exit_json(ansible_facts=ansible_facts, warnings=warnings) diff --git a/library/nxos_interfaces.py b/library/nxos_interfaces.py index a636aa6..2715ba3 100644 --- a/library/nxos_interfaces.py +++ b/library/nxos_interfaces.py @@ -43,16 +43,16 @@ """ from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils.nxos.config.interfaces.interfaces import Interface +from ansible.module_utils.nxos.config.interfaces.interfaces import Interfaces def main(): """ main entry point for module execution """ - module = AnsibleModule(argument_spec=Interface.argument_spec, + module = AnsibleModule(argument_spec=Interfaces.argument_spec, supports_check_mode=True) - result = Interface(module).execute_module() + result = Interfaces(module).execute_module() module.exit_json(**result) diff --git a/module_utils/nxos/config/interfaces/interfaces.py b/module_utils/nxos/config/interfaces/interfaces.py index dcf0a2c..65b6dca 100644 --- a/module_utils/nxos/config/interfaces/interfaces.py +++ b/module_utils/nxos/config/interfaces/interfaces.py @@ -3,18 +3,18 @@ from ansible.module_utils.nxos.argspec.interfaces.interfaces import InterfaceArgs from ansible.module_utils.nxos.config.base import ConfigBase -from ansible.module_utils.nxos.facts.facts import NxosFacts +from ansible.module_utils.nxos.facts.facts import Facts from ansible.module_utils.nxos.utils.utils import get_interface_type, normalize_interface, search_obj_in_list -class Interface(ConfigBase, InterfaceArgs): +class Interfaces(ConfigBase, InterfaceArgs): gather_subset = [ 'net_configuration_interfaces', ] def get_interface_facts(self): - facts = NxosFacts().get_facts(self._module, self._connection, self.gather_subset) + facts = Facts().get_facts(self._module, self._connection, self.gather_subset) interface_facts = facts['net_configuration'].get('interfaces') if not interface_facts: return [] diff --git a/module_utils/nxos/facts/facts.py b/module_utils/nxos/facts/facts.py index 21e447d..3adc3cd 100644 --- a/module_utils/nxos/facts/facts.py +++ b/module_utils/nxos/facts/facts.py @@ -1,10 +1,10 @@ from ansible.module_utils.nxos.argspec.facts.facts import FactsArgs from ansible.module_utils.nxos.argspec.interfaces.interfaces import InterfaceArgs from ansible.module_utils.nxos.facts.base import FactsBase -from ansible.module_utils.nxos.facts.interfaces.interfaces import NxosInterfacesFacts +from ansible.module_utils.nxos.facts.interfaces.interfaces import InterfacesFacts -class NxosFacts(FactsArgs, FactsBase): +class Facts(FactsArgs, FactsBase): VALID_SUBSETS = [ 'net_configuration_interfaces', @@ -47,4 +47,4 @@ def get_facts(self, module, connection, gather_subset=['all']): return self.ansible_facts def _get_net_configuration_interfaces(self, module, connection): - return NxosInterfacesFacts(InterfaceArgs.argument_spec, 'config', 'options').populate_facts(module, connection) + return InterfacesFacts(InterfaceArgs.argument_spec, 'config', 'options').populate_facts(module, connection) diff --git a/module_utils/nxos/facts/interfaces/interfaces.py b/module_utils/nxos/facts/interfaces/interfaces.py index f069aa0..b73fe84 100644 --- a/module_utils/nxos/facts/interfaces/interfaces.py +++ b/module_utils/nxos/facts/interfaces/interfaces.py @@ -6,7 +6,7 @@ from ansible.module_utils.nxos.utils.utils import get_interface_type, normalize_interface -class NxosInterfacesFacts(FactsBase): +class InterfacesFacts(FactsBase): def populate_facts(self, module, connection, data=None): """ From 2e4577124cac3ac017282b294ad913a65f4fc4e0 Mon Sep 17 00:00:00 2001 From: Trishna Guha Date: Fri, 22 Mar 2019 12:40:21 +0530 Subject: [PATCH 14/21] set module params as want instead of creating key-value pairs again Signed-off-by: Trishna Guha --- .../nxos/config/interfaces/interfaces.py | 24 ++++--------------- 1 file changed, 4 insertions(+), 20 deletions(-) diff --git a/module_utils/nxos/config/interfaces/interfaces.py b/module_utils/nxos/config/interfaces/interfaces.py index 65b6dca..9001626 100644 --- a/module_utils/nxos/config/interfaces/interfaces.py +++ b/module_utils/nxos/config/interfaces/interfaces.py @@ -43,30 +43,14 @@ def execute_module(self): return result def set_config(self): - want = self._config_map_params_to_obj() + want = self._module.params['config'] + for w in want: + if 'name' in w: + w.update({'name': normalize_interface(w['name'])}) have = self.get_interface_facts() resp = self.set_state(want, have) return to_list(resp) - def _config_map_params_to_obj(self): - objs = [] - collection = self._module.params['config'] - for config in collection: - obj = { - 'name': normalize_interface(config['name']), - 'description': config['description'], - 'enable': config['enable'], - 'speed': config['speed'], - 'mtu': config['mtu'], - 'duplex': config['duplex'], - 'mode': config['mode'], - 'ip_forward': config['ip_forward'], - 'fabric_forwarding_anycast_gateway': config['fabric_forwarding_anycast_gateway'], - } - objs.append(obj) - - return objs - def set_state(self, want, have): commands = list() From cfc870c5a0412326a24a0d87729e9268c027dc47 Mon Sep 17 00:00:00 2001 From: Trishna Guha Date: Wed, 27 Mar 2019 12:31:13 +0530 Subject: [PATCH 15/21] Address review comment Signed-off-by: Trishna Guha --- module_utils/nxos/argspec/facts/facts.py | 7 ++++++- module_utils/nxos/facts/facts.py | 16 ++++++++-------- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/module_utils/nxos/argspec/facts/facts.py b/module_utils/nxos/argspec/facts/facts.py index 503502b..796f67c 100644 --- a/module_utils/nxos/argspec/facts/facts.py +++ b/module_utils/nxos/argspec/facts/facts.py @@ -2,6 +2,11 @@ class FactsArgs(ArgspecBase): + choices = [ + 'all', + 'net_configuration_interfaces', + ] + argument_spec = { - 'gather_subset': dict(default=['all'], type='list') + 'gather_subset': dict(default=['all'], choices=choices, type='list') } diff --git a/module_utils/nxos/facts/facts.py b/module_utils/nxos/facts/facts.py index 3adc3cd..61699bd 100644 --- a/module_utils/nxos/facts/facts.py +++ b/module_utils/nxos/facts/facts.py @@ -6,28 +6,28 @@ class Facts(FactsArgs, FactsBase): - VALID_SUBSETS = [ - 'net_configuration_interfaces', - ] - def get_facts(self, module, connection, gather_subset=['all']): + valid_subsets = self.argument_spec['gather_subset'].get('choices', []) + if valid_subsets and 'all' in valid_subsets: + valid_subsets.remove('all') + runable_subsets = set() exclude_subsets = set() for subset in gather_subset: if subset == 'all': - runable_subsets.update(self.VALID_SUBSETS) + runable_subsets.update(valid_subsets) continue if subset.startswith('!'): subset = subset[1:] if subset == 'all': - exclude_subsets.update(self.VALID_SUBSETS) + exclude_subsets.update(valid_subsets) continue exclude = True else: exclude = False - if subset not in self.VALID_SUBSETS: + if subset not in valid_subsets: module.fail_json(msg='Bad subset') if exclude: @@ -36,7 +36,7 @@ def get_facts(self, module, connection, gather_subset=['all']): runable_subsets.add(subset) if not runable_subsets: - runable_subsets.update(self.VALID_SUBSETS) + runable_subsets.update(valid_subsets) runable_subsets.difference_update(exclude_subsets) self.ansible_facts['gather_subset'] = list(runable_subsets) From 2a5f6e8cf27597a009ef1d4f433640612124d277 Mon Sep 17 00:00:00 2001 From: Trishna Guha Date: Thu, 28 Mar 2019 20:28:21 +0530 Subject: [PATCH 16/21] remove ArgspecBase as it is not in use Signed-off-by: Trishna Guha --- module_utils/network/__init__.py | 0 module_utils/network/argspec/__init__.py | 0 module_utils/network/argspec/base.py | 4 ---- module_utils/nxos/argspec/facts/facts.py | 5 +++-- module_utils/nxos/argspec/interfaces/interfaces.py | 5 +++-- 5 files changed, 6 insertions(+), 8 deletions(-) delete mode 100644 module_utils/network/__init__.py delete mode 100644 module_utils/network/argspec/__init__.py delete mode 100644 module_utils/network/argspec/base.py diff --git a/module_utils/network/__init__.py b/module_utils/network/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/module_utils/network/argspec/__init__.py b/module_utils/network/argspec/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/module_utils/network/argspec/base.py b/module_utils/network/argspec/base.py deleted file mode 100644 index 650d49f..0000000 --- a/module_utils/network/argspec/base.py +++ /dev/null @@ -1,4 +0,0 @@ -class ArgspecBase(object): - - def __init__(self, **kwargs): - pass diff --git a/module_utils/nxos/argspec/facts/facts.py b/module_utils/nxos/argspec/facts/facts.py index 796f67c..7d112e9 100644 --- a/module_utils/nxos/argspec/facts/facts.py +++ b/module_utils/nxos/argspec/facts/facts.py @@ -1,6 +1,7 @@ -from ansible.module_utils.network.argspec.base import ArgspecBase +class FactsArgs(object): -class FactsArgs(ArgspecBase): + def __init__(self, **kwargs): + pass choices = [ 'all', diff --git a/module_utils/nxos/argspec/interfaces/interfaces.py b/module_utils/nxos/argspec/interfaces/interfaces.py index 9eaded4..30d54fb 100644 --- a/module_utils/nxos/argspec/interfaces/interfaces.py +++ b/module_utils/nxos/argspec/interfaces/interfaces.py @@ -1,6 +1,7 @@ -from ansible.module_utils.network.argspec.base import ArgspecBase +class InterfaceArgs(object): -class InterfaceArgs(ArgspecBase): + def __init__(self, **kwargs): + pass config_spec = { 'name': dict(type='str', required=True), From 91fa629e74f3d334409e11548b553c01e80f82e7 Mon Sep 17 00:00:00 2001 From: Trishna Guha Date: Thu, 4 Apr 2019 15:51:24 +0530 Subject: [PATCH 17/21] use get Signed-off-by: Trishna Guha --- module_utils/nxos/config/interfaces/interfaces.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/module_utils/nxos/config/interfaces/interfaces.py b/module_utils/nxos/config/interfaces/interfaces.py index 9001626..84d79e6 100644 --- a/module_utils/nxos/config/interfaces/interfaces.py +++ b/module_utils/nxos/config/interfaces/interfaces.py @@ -45,8 +45,7 @@ def execute_module(self): def set_config(self): want = self._module.params['config'] for w in want: - if 'name' in w: - w.update({'name': normalize_interface(w['name'])}) + w.update({'name': normalize_interface(w['name'])}) have = self.get_interface_facts() resp = self.set_state(want, have) return to_list(resp) @@ -129,11 +128,10 @@ def _state_merged(self, w, obj_in_have, interface_type): commands = list() args = ('speed', 'description', 'duplex', 'mtu') - name = w['name'] - mode = w['mode'] - ip_forward = w['ip_forward'] - fabric_forwarding_anycast_gateway = w['fabric_forwarding_anycast_gateway'] - enable = w['enable'] + mode = w.get('mode') + ip_forward = w.get('ip_forward') + fabric_forwarding_anycast_gateway = w.get('fabric_forwarding_anycast_gateway') + enable = w.get('enable') if name: interface = 'interface ' + name From afafdcc094696bf27ca77c2e9b75fc3076747f66 Mon Sep 17 00:00:00 2001 From: Trishna Guha Date: Mon, 8 Apr 2019 12:26:17 +0530 Subject: [PATCH 18/21] fix facts generation for before key in result Signed-off-by: Trishna Guha --- .../nxos/config/interfaces/interfaces.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/module_utils/nxos/config/interfaces/interfaces.py b/module_utils/nxos/config/interfaces/interfaces.py index 84d79e6..bf0b1db 100644 --- a/module_utils/nxos/config/interfaces/interfaces.py +++ b/module_utils/nxos/config/interfaces/interfaces.py @@ -25,28 +25,28 @@ def execute_module(self): commands = list() warnings = list() - commands.extend(self.set_config()) + existing_interface_facts = self.get_interface_facts() + commands.extend(self.set_config(existing_interface_facts)) if commands: if not self._module.check_mode: self._connection.edit_config(commands) result['changed'] = True result['commands'] = commands - interface_facts = self.get_interface_facts() + changed_interface_facts = self.get_interface_facts() - if result['changed'] == False: - result['before'] = interface_facts - elif result['changed'] == True: - result['after'] = interface_facts + result['before'] = existing_interface_facts + if result['changed'] == True: + result['after'] = changed_interface_facts result['warnings'] = warnings return result - def set_config(self): + def set_config(self, existing_interface_facts): want = self._module.params['config'] for w in want: w.update({'name': normalize_interface(w['name'])}) - have = self.get_interface_facts() + have = existing_interface_facts resp = self.set_state(want, have) return to_list(resp) @@ -128,6 +128,7 @@ def _state_merged(self, w, obj_in_have, interface_type): commands = list() args = ('speed', 'description', 'duplex', 'mtu') + name = w['name'] mode = w.get('mode') ip_forward = w.get('ip_forward') fabric_forwarding_anycast_gateway = w.get('fabric_forwarding_anycast_gateway') From 522aa07d51efdbea6e15fab8f8a44b465554eb5b Mon Sep 17 00:00:00 2001 From: Trishna Guha Date: Mon, 29 Apr 2019 15:17:03 +0530 Subject: [PATCH 19/21] gather_network_resources attribute and facts update Signed-off-by: Trishna Guha --- library/nxos_facts.py | 8 +- library/nxos_interfaces.py | 5 +- module_utils/nxos/argspec/facts/facts.py | 5 +- .../nxos/config/interfaces/interfaces.py | 20 +- module_utils/nxos/facts/base.py | 2 +- module_utils/nxos/facts/facts.py | 825 +++++++++++++++++- .../nxos/facts/interfaces/interfaces.py | 3 +- 7 files changed, 841 insertions(+), 27 deletions(-) diff --git a/library/nxos_facts.py b/library/nxos_facts.py index 19da266..ef80495 100644 --- a/library/nxos_facts.py +++ b/library/nxos_facts.py @@ -69,17 +69,21 @@ from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.connection import Connection +from ansible.module_utils.network.nxos.nxos import nxos_argument_spec from ansible.module_utils.nxos.facts.facts import Facts def main(): - module = AnsibleModule(argument_spec=Facts.argument_spec, + argument_spec = Facts.argument_spec + argument_spec.update(nxos_argument_spec) + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) warnings = list() connection = Connection(module._socket_path) gather_subset = module.params['gather_subset'] - ansible_facts = Facts().get_facts(module, connection, gather_subset) + gather_network_resources = module.params['gather_network_resources'] + ansible_facts = Facts().get_facts(module, connection, gather_subset, gather_network_resources) module.exit_json(ansible_facts=ansible_facts, warnings=warnings) diff --git a/library/nxos_interfaces.py b/library/nxos_interfaces.py index 2715ba3..78fa7bd 100644 --- a/library/nxos_interfaces.py +++ b/library/nxos_interfaces.py @@ -43,13 +43,16 @@ """ from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.network.nxos.nxos import nxos_argument_spec from ansible.module_utils.nxos.config.interfaces.interfaces import Interfaces def main(): """ main entry point for module execution """ - module = AnsibleModule(argument_spec=Interfaces.argument_spec, + argument_spec = Interfaces.argument_spec + argument_spec.update(nxos_argument_spec) + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) result = Interfaces(module).execute_module() diff --git a/module_utils/nxos/argspec/facts/facts.py b/module_utils/nxos/argspec/facts/facts.py index 7d112e9..4ed903f 100644 --- a/module_utils/nxos/argspec/facts/facts.py +++ b/module_utils/nxos/argspec/facts/facts.py @@ -5,9 +5,10 @@ def __init__(self, **kwargs): choices = [ 'all', - 'net_configuration_interfaces', + 'interfaces', ] argument_spec = { - 'gather_subset': dict(default=['all'], choices=choices, type='list') + 'gather_subset': dict(default=['!config'], type='list'), + 'gather_network_resources': dict(default=['all'], choices=choices, type='list'), } diff --git a/module_utils/nxos/config/interfaces/interfaces.py b/module_utils/nxos/config/interfaces/interfaces.py index bf0b1db..24b23d5 100644 --- a/module_utils/nxos/config/interfaces/interfaces.py +++ b/module_utils/nxos/config/interfaces/interfaces.py @@ -10,12 +10,17 @@ class Interfaces(ConfigBase, InterfaceArgs): gather_subset = [ - 'net_configuration_interfaces', + '!all', + '!min', + ] + + gather_network_resources = [ + 'interfaces', ] def get_interface_facts(self): - facts = Facts().get_facts(self._module, self._connection, self.gather_subset) - interface_facts = facts['net_configuration'].get('interfaces') + facts = Facts().get_facts(self._module, self._connection, self.gather_subset, self.gather_network_resources) + interface_facts = facts['network_resources'].get('interfaces') if not interface_facts: return [] return interface_facts @@ -30,15 +35,12 @@ def execute_module(self): if commands: if not self._module.check_mode: self._connection.edit_config(commands) + changed_interface_facts = self.get_interface_facts() + result['after'] = changed_interface_facts result['changed'] = True - result['commands'] = commands - - changed_interface_facts = self.get_interface_facts() + result['commands'] = commands result['before'] = existing_interface_facts - if result['changed'] == True: - result['after'] = changed_interface_facts - result['warnings'] = warnings return result diff --git a/module_utils/nxos/facts/base.py b/module_utils/nxos/facts/base.py index a700b6f..878df32 100644 --- a/module_utils/nxos/facts/base.py +++ b/module_utils/nxos/facts/base.py @@ -8,7 +8,7 @@ class FactsBase(object): generated_spec = {} - ansible_facts = {'net_configuration': {}} + ansible_facts = {'network_resources': {}} def __init__(self, argspec, subspec=None, options=None): spec = deepcopy(argspec) diff --git a/module_utils/nxos/facts/facts.py b/module_utils/nxos/facts/facts.py index 61699bd..0db6107 100644 --- a/module_utils/nxos/facts/facts.py +++ b/module_utils/nxos/facts/facts.py @@ -1,27 +1,794 @@ +import platform +import re + +from ansible.module_utils.network.nxos.nxos import run_commands, get_config +from ansible.module_utils.network.nxos.nxos import get_capabilities, get_interface_type +from ansible.module_utils.network.nxos.nxos import normalize_interface +from ansible.module_utils.connection import ConnectionError +from ansible.module_utils.six import string_types, iteritems + +from ansible.module_utils.network.common.utils import to_list from ansible.module_utils.nxos.argspec.facts.facts import FactsArgs from ansible.module_utils.nxos.argspec.interfaces.interfaces import InterfaceArgs from ansible.module_utils.nxos.facts.base import FactsBase from ansible.module_utils.nxos.facts.interfaces.interfaces import InterfacesFacts +g_config = None + +class LegacyFacts(object): + + def __init__(self, module): + self.module = module + self.warnings = list() + self.facts = dict() + self.capabilities = get_capabilities(self.module) + + def populate(self): + pass + + def run(self, command, output='text'): + command_string = command + command = { + 'command': command, + 'output': output + } + resp = run_commands(self.module, [command], check_rc='retry_json') + try: + return resp[0] + except IndexError: + self.warnings.append('command %s failed, facts for this command will not be populated' % command_string) + return None + + def get_config(self): + global g_config + if not g_config: + g_config = get_config(self.module) + return g_config + + def transform_dict(self, data, keymap): + transform = dict() + for key, fact in keymap: + if key in data: + transform[fact] = data[key] + return transform + + def transform_iterable(self, iterable, keymap): + for item in iterable: + yield self.transform_dict(item, keymap) + +class Default(LegacyFacts): + + def populate(self): + data = None + data = self.run('show version') + + if data: + self.facts['serialnum'] = self.parse_serialnum(data) + + data = self.run('show license host-id') + if data: + self.facts['license_hostid'] = self.parse_license_hostid(data) + + self.facts.update(self.platform_facts()) + + def parse_serialnum(self, data): + match = re.search(r'Processor Board ID\s*(\S+)', data, re.M) + if match: + return match.group(1) + + def parse_license_hostid(self, data): + match = re.search(r'License hostid: VDH=(.+)$', data, re.M) + if match: + return match.group(1) + + def platform_facts(self): + platform_facts = {} + + resp = self.capabilities + device_info = resp['device_info'] + + platform_facts['system'] = device_info['network_os'] + + for item in ('model', 'image', 'version', 'platform', 'hostname'): + val = device_info.get('network_os_%s' % item) + if val: + platform_facts[item] = val + + platform_facts['api'] = resp['network_api'] + platform_facts['python_version'] = platform.python_version() + + return platform_facts + + def parse_license_hostid(self, data): + match = re.search(r'License hostid: VDH=(.+)$', data, re.M) + if match: + return match.group(1) + +class Config(LegacyFacts): + + def populate(self): + super(Config, self).populate() + self.facts['config'] = self.get_config() + + +class Features(LegacyFacts): + + def populate(self): + super(Features, self).populate() + data = self.get_config() + + if data: + features = [] + for line in data.splitlines(): + if line.startswith('feature'): + features.append(line.replace('feature', '').strip()) + + self.facts['features_enabled'] = features + + +class Hardware(LegacyFacts): + + def populate(self): + data = self.run('dir') + if data: + self.facts['filesystems'] = self.parse_filesystems(data) + + data = None + data = self.run('show system resources', output='json') + + if data: + if isinstance(data, dict): + self.facts['memtotal_mb'] = int(data['memory_usage_total']) / 1024 + self.facts['memfree_mb'] = int(data['memory_usage_free']) / 1024 + else: + self.facts['memtotal_mb'] = self.parse_memtotal_mb(data) + self.facts['memfree_mb'] = self.parse_memfree_mb(data) + + def parse_filesystems(self, data): + return re.findall(r'^Usage for (\S+)//', data, re.M) + + def parse_memtotal_mb(self, data): + match = re.search(r'(\S+)K(\s+|)total', data, re.M) + if match: + memtotal = match.group(1) + return int(memtotal) / 1024 + + def parse_memfree_mb(self, data): + match = re.search(r'(\S+)K(\s+|)free', data, re.M) + if match: + memfree = match.group(1) + return int(memfree) / 1024 + + +class Interfaces(LegacyFacts): + + INTERFACE_MAP = frozenset([ + ('state', 'state'), + ('desc', 'description'), + ('eth_bw', 'bandwidth'), + ('eth_duplex', 'duplex'), + ('eth_speed', 'speed'), + ('eth_mode', 'mode'), + ('eth_hw_addr', 'macaddress'), + ('eth_mtu', 'mtu'), + ('eth_hw_desc', 'type') + ]) + + INTERFACE_SVI_MAP = frozenset([ + ('svi_line_proto', 'state'), + ('svi_bw', 'bandwidth'), + ('svi_mac', 'macaddress'), + ('svi_mtu', 'mtu'), + ('type', 'type') + ]) + + INTERFACE_IPV4_MAP = frozenset([ + ('eth_ip_addr', 'address'), + ('eth_ip_mask', 'masklen') + ]) + + INTERFACE_SVI_IPV4_MAP = frozenset([ + ('svi_ip_addr', 'address'), + ('svi_ip_mask', 'masklen') + ]) + + INTERFACE_IPV6_MAP = frozenset([ + ('addr', 'address'), + ('prefix', 'subnet') + ]) + + def ipv6_structure_op_supported(self): + data = self.capabilities + if data: + nxos_os_version = data['device_info']['network_os_version'] + unsupported_versions = ['I2', 'F1', 'A8'] + for ver in unsupported_versions: + if ver in nxos_os_version: + return False + return True + + def populate(self): + self.facts['all_ipv4_addresses'] = list() + self.facts['all_ipv6_addresses'] = list() + self.facts['neighbors'] = {} + data = None + + data = self.run('show interface', output='json') + + if data: + if isinstance(data, dict): + self.facts['interfaces'] = self.populate_structured_interfaces(data) + else: + interfaces = self.parse_interfaces(data) + self.facts['interfaces'] = self.populate_interfaces(interfaces) + + if self.ipv6_structure_op_supported(): + data = self.run('show ipv6 interface', output='json') + else: + data = None + if data: + if isinstance(data, dict): + self.populate_structured_ipv6_interfaces(data) + else: + interfaces = self.parse_interfaces(data) + self.populate_ipv6_interfaces(interfaces) + + data = self.run('show lldp neighbors', output='json') + if data: + if isinstance(data, dict): + self.facts['neighbors'].update(self.populate_structured_neighbors_lldp(data)) + else: + self.facts['neighbors'].update(self.populate_neighbors(data)) + + data = self.run('show cdp neighbors detail', output='json') + if data: + if isinstance(data, dict): + self.facts['neighbors'].update(self.populate_structured_neighbors_cdp(data)) + else: + self.facts['neighbors'].update(self.populate_neighbors_cdp(data)) + + self.facts['neighbors'].pop(None, None) # Remove null key + + def populate_structured_interfaces(self, data): + interfaces = dict() + for item in data['TABLE_interface']['ROW_interface']: + name = item['interface'] + + intf = dict() + if 'type' in item: + intf.update(self.transform_dict(item, self.INTERFACE_SVI_MAP)) + else: + intf.update(self.transform_dict(item, self.INTERFACE_MAP)) + + if 'eth_ip_addr' in item: + intf['ipv4'] = self.transform_dict(item, self.INTERFACE_IPV4_MAP) + self.facts['all_ipv4_addresses'].append(item['eth_ip_addr']) + + if 'svi_ip_addr' in item: + intf['ipv4'] = self.transform_dict(item, self.INTERFACE_SVI_IPV4_MAP) + self.facts['all_ipv4_addresses'].append(item['svi_ip_addr']) + + interfaces[name] = intf + + return interfaces + + def populate_structured_ipv6_interfaces(self, data): + try: + data = data['TABLE_intf'] + if data: + if isinstance(data, dict): + data = [data] + for item in data: + name = item['ROW_intf']['intf-name'] + intf = self.facts['interfaces'][name] + intf['ipv6'] = self.transform_dict(item, self.INTERFACE_IPV6_MAP) + try: + addr = item['ROW_intf']['addr'] + except KeyError: + addr = item['ROW_intf']['TABLE_addr']['ROW_addr']['addr'] + self.facts['all_ipv6_addresses'].append(addr) + else: + return "" + except TypeError: + return "" + + def populate_structured_neighbors_lldp(self, data): + objects = dict() + data = data['TABLE_nbor']['ROW_nbor'] + + if isinstance(data, dict): + data = [data] + + for item in data: + local_intf = normalize_interface(item['l_port_id']) + objects[local_intf] = list() + nbor = dict() + nbor['port'] = item['port_id'] + nbor['host'] = nbor['sysname'] = item['chassis_id'] + objects[local_intf].append(nbor) + + return objects + + def populate_structured_neighbors_cdp(self, data): + objects = dict() + data = data['TABLE_cdp_neighbor_detail_info']['ROW_cdp_neighbor_detail_info'] + + if isinstance(data, dict): + data = [data] + + for item in data: + local_intf = item['intf_id'] + objects[local_intf] = list() + nbor = dict() + nbor['port'] = item['port_id'] + nbor['host'] = nbor['sysname'] = item['device_id'] + objects[local_intf].append(nbor) + + return objects + + def parse_interfaces(self, data): + parsed = dict() + key = '' + for line in data.split('\n'): + if len(line) == 0: + continue + elif line.startswith('admin') or line[0] == ' ': + parsed[key] += '\n%s' % line + else: + match = re.match(r'^(\S+)', line) + if match: + key = match.group(1) + if not key.startswith('admin') or not key.startswith('IPv6 Interface'): + parsed[key] = line + return parsed + + def populate_interfaces(self, interfaces): + facts = dict() + for key, value in iteritems(interfaces): + intf = dict() + if get_interface_type(key) == 'svi': + intf['state'] = self.parse_state(key, value, intf_type='svi') + intf['macaddress'] = self.parse_macaddress(value, intf_type='svi') + intf['mtu'] = self.parse_mtu(value, intf_type='svi') + intf['bandwidth'] = self.parse_bandwidth(value, intf_type='svi') + intf['type'] = self.parse_type(value, intf_type='svi') + if 'Internet Address' in value: + intf['ipv4'] = self.parse_ipv4_address(value, intf_type='svi') + facts[key] = intf + else: + intf['state'] = self.parse_state(key, value) + intf['description'] = self.parse_description(value) + intf['macaddress'] = self.parse_macaddress(value) + intf['mode'] = self.parse_mode(value) + intf['mtu'] = self.parse_mtu(value) + intf['bandwidth'] = self.parse_bandwidth(value) + intf['duplex'] = self.parse_duplex(value) + intf['speed'] = self.parse_speed(value) + intf['type'] = self.parse_type(value) + if 'Internet Address' in value: + intf['ipv4'] = self.parse_ipv4_address(value) + facts[key] = intf + + return facts + + def parse_state(self, key, value, intf_type='ethernet'): + match = None + if intf_type == 'svi': + match = re.search(r'line protocol is\s*(\S+)', value, re.M) + else: + match = re.search(r'%s is\s*(\S+)' % key, value, re.M) + + if match: + return match.group(1) + + def parse_macaddress(self, value, intf_type='ethernet'): + match = None + if intf_type == 'svi': + match = re.search(r'address is\s*(\S+)', value, re.M) + else: + match = re.search(r'address:\s*(\S+)', value, re.M) + + if match: + return match.group(1) + + def parse_mtu(self, value, intf_type='ethernet'): + match = re.search(r'MTU\s*(\S+)', value, re.M) + if match: + return match.group(1) + + def parse_bandwidth(self, value, intf_type='ethernet'): + match = re.search(r'BW\s*(\S+)', value, re.M) + if match: + return match.group(1) + + def parse_type(self, value, intf_type='ethernet'): + match = None + if intf_type == 'svi': + match = re.search(r'Hardware is\s*(\S+)', value, re.M) + else: + match = re.search(r'Hardware:\s*(.+),', value, re.M) + + if match: + return match.group(1) + + def parse_description(self, value, intf_type='ethernet'): + match = re.search(r'Description: (.+)$', value, re.M) + if match: + return match.group(1) + + def parse_mode(self, value, intf_type='ethernet'): + match = re.search(r'Port mode is (\S+)', value, re.M) + if match: + return match.group(1) + + def parse_duplex(self, value, intf_type='ethernet'): + match = re.search(r'(\S+)-duplex', value, re.M) + if match: + return match.group(1) + + def parse_speed(self, value, intf_type='ethernet'): + match = re.search(r'duplex, (.+)$', value, re.M) + if match: + return match.group(1) + + def parse_ipv4_address(self, value, intf_type='ethernet'): + ipv4 = {} + match = re.search(r'Internet Address is (.+)$', value, re.M) + if match: + address = match.group(1) + addr = address.split('/')[0] + ipv4['address'] = address.split('/')[0] + ipv4['masklen'] = address.split('/')[1] + self.facts['all_ipv4_addresses'].append(addr) + return ipv4 + + def populate_neighbors(self, data): + objects = dict() + # if there are no neighbors the show command returns + # ERROR: No neighbour information + if data.startswith('ERROR'): + return dict() + + regex = re.compile(r'(\S+)\s+(\S+)\s+\d+\s+\w+\s+(\S+)') + + for item in data.split('\n')[4:-1]: + match = regex.match(item) + if match: + nbor = dict() + nbor['host'] = nbor['sysname'] = match.group(1) + nbor['port'] = match.group(3) + local_intf = normalize_interface(match.group(2)) + if local_intf not in objects: + objects[local_intf] = [] + objects[local_intf].append(nbor) + + return objects + + def populate_neighbors_cdp(self, data): + facts = dict() + + for item in data.split('----------------------------------------'): + if item == '': + continue + local_intf = self.parse_lldp_intf(item) + if local_intf not in facts: + facts[local_intf] = list() + + fact = dict() + fact['port'] = self.parse_lldp_port(item) + fact['sysname'] = self.parse_lldp_sysname(item) + facts[local_intf].append(fact) + + return facts + + def parse_lldp_intf(self, data): + match = re.search(r'Interface:\s*(\S+)', data, re.M) + if match: + return match.group(1).strip(',') + + def parse_lldp_port(self, data): + match = re.search(r'Port ID \(outgoing port\):\s*(\S+)', data, re.M) + if match: + return match.group(1) + + def parse_lldp_sysname(self, data): + match = re.search(r'Device ID:(.+)$', data, re.M) + if match: + return match.group(1) + + def populate_ipv6_interfaces(self, interfaces): + facts = dict() + for key, value in iteritems(interfaces): + intf = dict() + intf['ipv6'] = self.parse_ipv6_address(value) + facts[key] = intf + + def parse_ipv6_address(self, value): + ipv6 = {} + match_addr = re.search(r'IPv6 address:\s*(\S+)', value, re.M) + if match_addr: + addr = match_addr.group(1) + ipv6['address'] = addr + self.facts['all_ipv6_addresses'].append(addr) + match_subnet = re.search(r'IPv6 subnet:\s*(\S+)', value, re.M) + if match_subnet: + ipv6['subnet'] = match_subnet.group(1) + + return ipv6 + + +class Legacy(LegacyFacts): + # facts from nxos_facts 2.1 + + VERSION_MAP = frozenset([ + ('host_name', '_hostname'), + ('kickstart_ver_str', '_os'), + ('chassis_id', '_platform') + ]) + + MODULE_MAP = frozenset([ + ('model', 'model'), + ('modtype', 'type'), + ('ports', 'ports'), + ('status', 'status') + ]) + + FAN_MAP = frozenset([ + ('fanname', 'name'), + ('fanmodel', 'model'), + ('fanhwver', 'hw_ver'), + ('fandir', 'direction'), + ('fanstatus', 'status') + ]) + + POWERSUP_MAP = frozenset([ + ('psmodel', 'model'), + ('psnum', 'number'), + ('ps_status', 'status'), + ('ps_status_3k', 'status'), + ('actual_out', 'actual_output'), + ('actual_in', 'actual_in'), + ('total_capa', 'total_capacity'), + ('input_type', 'input_type'), + ('watts', 'watts'), + ('amps', 'amps') + ]) + + def populate(self): + data = None + + data = self.run('show version', output='json') + if data: + if isinstance(data, dict): + self.facts.update(self.transform_dict(data, self.VERSION_MAP)) + else: + self.facts['_hostname'] = self.parse_hostname(data) + self.facts['_os'] = self.parse_os(data) + self.facts['_platform'] = self.parse_platform(data) + + data = self.run('show interface', output='json') + if data: + if isinstance(data, dict): + self.facts['_interfaces_list'] = self.parse_structured_interfaces(data) + else: + self.facts['_interfaces_list'] = self.parse_interfaces(data) + + data = self.run('show vlan brief', output='json') + if data: + if isinstance(data, dict): + self.facts['_vlan_list'] = self.parse_structured_vlans(data) + else: + self.facts['_vlan_list'] = self.parse_vlans(data) + + data = self.run('show module', output='json') + if data: + if isinstance(data, dict): + self.facts['_module'] = self.parse_structured_module(data) + else: + self.facts['_module'] = self.parse_module(data) + + data = self.run('show environment fan', output='json') + if data: + if isinstance(data, dict): + self.facts['_fan_info'] = self.parse_structured_fan_info(data) + else: + self.facts['_fan_info'] = self.parse_fan_info(data) + + data = self.run('show environment power', output='json') + if data: + if isinstance(data, dict): + self.facts['_power_supply_info'] = self.parse_structured_power_supply_info(data) + else: + self.facts['_power_supply_info'] = self.parse_power_supply_info(data) + + def parse_structured_interfaces(self, data): + objects = list() + for item in data['TABLE_interface']['ROW_interface']: + objects.append(item['interface']) + return objects + + def parse_structured_vlans(self, data): + objects = list() + data = data['TABLE_vlanbriefxbrief']['ROW_vlanbriefxbrief'] + if isinstance(data, dict): + objects.append(data['vlanshowbr-vlanid-utf']) + elif isinstance(data, list): + for item in data: + objects.append(item['vlanshowbr-vlanid-utf']) + return objects + + def parse_structured_module(self, data): + data = data['TABLE_modinfo']['ROW_modinfo'] + if isinstance(data, dict): + data = [data] + objects = list(self.transform_iterable(data, self.MODULE_MAP)) + return objects + + def parse_structured_fan_info(self, data): + objects = list() + if data.get('fandetails'): + data = data['fandetails']['TABLE_faninfo']['ROW_faninfo'] + elif data.get('fandetails_3k'): + data = data['fandetails_3k']['TABLE_faninfo']['ROW_faninfo'] + else: + return objects + objects = list(self.transform_iterable(data, self.FAN_MAP)) + return objects + + def parse_structured_power_supply_info(self, data): + if data.get('powersup').get('TABLE_psinfo_n3k'): + fact = data['powersup']['TABLE_psinfo_n3k']['ROW_psinfo_n3k'] + else: + if isinstance(data['powersup']['TABLE_psinfo'], list): + fact = [] + for i in data['powersup']['TABLE_psinfo']: + fact.append(i['ROW_psinfo']) + else: + fact = data['powersup']['TABLE_psinfo']['ROW_psinfo'] + + objects = list(self.transform_iterable(fact, self.POWERSUP_MAP)) + return objects + + def parse_hostname(self, data): + match = re.search(r'\s+Device name:\s+(\S+)', data, re.M) + if match: + return match.group(1) + + def parse_os(self, data): + match = re.search(r'\s+system:\s+version\s*(\S+)', data, re.M) + if match: + return match.group(1) + else: + match = re.search(r'\s+kickstart:\s+version\s*(\S+)', data, re.M) + if match: + return match.group(1) + + def parse_platform(self, data): + match = re.search(r'Hardware\n\s+cisco\s+(\S+\s+\S+)', data, re.M) + if match: + return match.group(1) + + def parse_interfaces(self, data): + objects = list() + for line in data.split('\n'): + if len(line) == 0: + continue + elif line.startswith('admin') or line[0] == ' ': + continue + else: + match = re.match(r'^(\S+)', line) + if match: + intf = match.group(1) + if get_interface_type(intf) != 'unknown': + objects.append(intf) + return objects + + def parse_vlans(self, data): + objects = list() + for line in data.splitlines(): + if line == '': + continue + if line[0].isdigit(): + vlan = line.split()[0] + objects.append(vlan) + return objects + + def parse_module(self, data): + objects = list() + for line in data.splitlines(): + if line == '': + break + if line[0].isdigit(): + obj = {} + match_port = re.search(r'\d\s*(\d*)', line, re.M) + if match_port: + obj['ports'] = match_port.group(1) + + match = re.search(r'\d\s*\d*\s*(.+)$', line, re.M) + if match: + l = match.group(1).split(' ') + items = list() + for item in l: + if item == '': + continue + items.append(item.strip()) + + if items: + obj['type'] = items[0] + obj['model'] = items[1] + obj['status'] = items[2] + + objects.append(obj) + return objects + + def parse_fan_info(self, data): + objects = list() + + for l in data.splitlines(): + if '-----------------' in l or 'Status' in l: + continue + line = l.split() + if len(line) > 1: + obj = {} + obj['name'] = line[0] + obj['model'] = line[1] + obj['hw_ver'] = line[-2] + obj['status'] = line[-1] + objects.append(obj) + return objects + + def parse_power_supply_info(self, data): + objects = list() + + for l in data.splitlines(): + if l == '': + break + if l[0].isdigit(): + obj = {} + line = l.split() + obj['model'] = line[1] + obj['number'] = line[0] + obj['status'] = line[-1] + + objects.append(obj) + return objects + + + +FACT_SUBSETS = dict( + default=Default, + legacy=Legacy, + hardware=Hardware, + interfaces=Interfaces, + config=Config, + features=Features +) + + class Facts(FactsArgs, FactsBase): - def get_facts(self, module, connection, gather_subset=['all']): - valid_subsets = self.argument_spec['gather_subset'].get('choices', []) - if valid_subsets and 'all' in valid_subsets: - valid_subsets.remove('all') + VALID_GATHER_SUBSETS = frozenset(FACT_SUBSETS.keys()) + def generate_runable_subsets(self, module, subsets, valid_subsets): runable_subsets = set() exclude_subsets = set() + minimal_gather_subset = frozenset(['default']) - for subset in gather_subset: + for subset in subsets: if subset == 'all': runable_subsets.update(valid_subsets) continue + if subset == 'min' and minimal_gather_subset: + runable_subsets.update(minimal_gather_subset) + continue if subset.startswith('!'): subset = subset[1:] + if subset == 'min': + exclude_subsets.update(minimal_gather_subset) + continue if subset == 'all': - exclude_subsets.update(valid_subsets) + exclude_subsets.update(valid_subsets - minimal_gather_subset) continue exclude = True else: @@ -37,14 +804,50 @@ def get_facts(self, module, connection, gather_subset=['all']): if not runable_subsets: runable_subsets.update(valid_subsets) - runable_subsets.difference_update(exclude_subsets) - self.ansible_facts['gather_subset'] = list(runable_subsets) - for attr in runable_subsets: - getattr(self, '_get_%s' % attr, {})(module, connection) + return runable_subsets + + + def get_facts(self, module, connection, gather_subset=['!config'], gather_network_resources=['all']): + self.ansible_facts['gather_network_resources'] = list() + self.ansible_facts['gather_subset'] = list() + + valid_network_resources_subsets = self.argument_spec['gather_network_resources'].get('choices', []) + if valid_network_resources_subsets and 'all' in valid_network_resources_subsets: + valid_network_resources_subsets.remove('all') + + if valid_network_resources_subsets: + resources_runable_subsets = self.generate_runable_subsets(module, gather_network_resources, valid_network_resources_subsets) + if resources_runable_subsets: + self.ansible_facts['gather_network_resources'] = list(resources_runable_subsets) + for attr in resources_runable_subsets: + getattr(self, '_get_%s' % attr, {})(module, connection) + + if self.VALID_GATHER_SUBSETS: + runable_subsets = self.generate_runable_subsets(module, gather_subset, self.VALID_GATHER_SUBSETS) + + if runable_subsets: + facts = dict() + self.ansible_facts['gather_subset'] = list(runable_subsets) + + instances = list() + for key in runable_subsets: + instances.append(FACT_SUBSETS[key](module)) + + for inst in instances: + inst.populate() + facts.update(inst.facts) + + for key, value in iteritems(facts): + # this is to maintain capability with nxos_facts 2.1 + if key.startswith('_'): + self.ansible_facts[key[1:]] = value + else: + key = 'ansible_net_%s' % key + self.ansible_facts[key] = value return self.ansible_facts - def _get_net_configuration_interfaces(self, module, connection): + def _get_interfaces(self, module, connection): return InterfacesFacts(InterfaceArgs.argument_spec, 'config', 'options').populate_facts(module, connection) diff --git a/module_utils/nxos/facts/interfaces/interfaces.py b/module_utils/nxos/facts/interfaces/interfaces.py index b73fe84..1856743 100644 --- a/module_utils/nxos/facts/interfaces/interfaces.py +++ b/module_utils/nxos/facts/interfaces/interfaces.py @@ -27,7 +27,8 @@ def populate_facts(self, module, connection, data=None): facts = {} if objs: facts['interfaces'] = objs - self.ansible_facts['net_configuration'].update(facts) + + self.ansible_facts['network_resources'].update(facts) return self.ansible_facts def render_config(self, spec, conf): From 4c0357c047e6ba9059b5839a356ad30a2a5dc02e Mon Sep 17 00:00:00 2001 From: Trishna Guha Date: Thu, 2 May 2019 19:19:37 +0530 Subject: [PATCH 20/21] replace fix Signed-off-by: Trishna Guha --- .../nxos/config/interfaces/interfaces.py | 71 +++++++++++++------ 1 file changed, 50 insertions(+), 21 deletions(-) diff --git a/module_utils/nxos/config/interfaces/interfaces.py b/module_utils/nxos/config/interfaces/interfaces.py index 24b23d5..fdc9104 100644 --- a/module_utils/nxos/config/interfaces/interfaces.py +++ b/module_utils/nxos/config/interfaces/interfaces.py @@ -78,12 +78,39 @@ def set_state(self, want, have): def _state_replaced(self, w, obj_in_have, interface_type): commands = list() - if interface_type in ('loopback', 'portchannel', 'svi'): - commands.append('no interface {0}'. format(w['name'])) - commands.extend(self._state_merged(w, obj_in_have, interface_type)) - else: - commands.append('default interface {0}'.format(w['name'])) - commands.extend(self._state_merged(w, obj_in_have, interface_type)) + merged_commands = self._state_merged(w, obj_in_have, interface_type) + commands.extend(self._replace_config(w, obj_in_have, interface_type)) + if commands: + commands.insert(0, 'interface ' + w['name']) + cmds = set(commands).intersection(set(merged_commands)) + for cmd in cmds: + merged_commands.remove(cmd) + + commands.extend(merged_commands) + return commands + + def _replace_config(self, w, obj_in_have, interface_type): + commands = [] + for key, value in w.items(): + if key == 'enable' and obj_in_have.get('enable') == False: + commands.append('no shutdown') + if key == 'description' and ('description' in obj_in_have and value != obj_in_have['description']): + commands.append('no description') + if interface_type == 'ethernet': + if key == 'mode' and obj_in_have.get('mode'): + commands.append('switchport') + if key == 'speed' and obj_in_have.get('speed'): + commands.append('no speed') + if key == 'duplex' and obj_in_have.get('duplex'): + commands.append('no duplex') + if interface_type in ('ethernet', 'portchannel', 'svi'): + if key == 'mtu' and obj_in_have.get('mtu'): + commands.append('no mtu') + if interface_type in ('ethernet', 'svi'): + if key == 'ip_forward': + commands.append('no ip forward') + if key == 'fabric_forwarding_anycast_gateway': + commands.append('no fabric forwarding anycast gateway') return commands @@ -212,42 +239,44 @@ def _state_deleted(self, w, obj_in_have, interface_type): if not obj_in_have or interface_type == 'unknown': return commands - interface = 'interface ' + w['name'] + commands.append('interface ' + w['name']) + commands.extend(self._default_attributes(self, interface_type, obj_in_have)) + return commands + + def _remove_command_from_interface(self, cmd): + commands = 'no %s' % cmd + return commands + def _default_attributes(self, interface_type, obj_in_have): + commands = [] if 'description' in obj_in_have: - self._remove_command_from_interface(interface, 'description', commands) + commands.append(self._remove_command_from_interface('description')) if 'enable' in obj_in_have and obj_in_have['enable'] is False: # if enable is False set enable as True which is the default behavior - self._remove_command_from_interface(interface, 'shutdown', commands) + commands.append(self._remove_command_from_interface('shutdown')) if interface_type == 'ethernet': if 'mode' in obj_in_have and obj_in_have['mode'] != 'layer2': # if mode is not layer2 set mode as layer2 which is the default behavior - self._remove_command_from_interface(interface, 'switchport', commands) + commands.append(self._remove_command_from_interface('switchport')) if 'speed' in obj_in_have: - self._remove_command_from_interface(interface, 'speed', commands) + commands.append(self._remove_command_from_interface('speed')) if 'duplex' in obj_in_have: - self._remote_command_from_interface(interface, 'duplex', commands) + commands.append(self._remove_command_from_interface('duplex')) if interface_type in ('ethernet', 'portchannel', 'svi'): if 'mtu' in obj_in_have: - self._remove_command_from_interface(interface, 'mtu', commands) + commands.append(self._remove_command_from_interface('mtu')) if interface_type in ('ethernet', 'svi'): if 'ip_forward' in obj_in_have: - self._remove_command_from_interface(interface, 'ip forward', commands) + commands.append(self._remove_command_from_interface('ip forward')) if 'fabric_forwarding_anycast_gateway' in obj_in_have: - self._remove_command_from_interface(interface, 'fabric forwarding anycast gateway', commands) + commands.append(self._remove_command_from_interface('fabric forwarding anycast gateway')) return commands - def _remove_command_from_interface(self, interface, cmd, commands): - if interface not in commands: - commands.insert(0, interface) - commands.append('no %s' % cmd) - return commands - def _get_admin_state(self, enable): command = '' if enable is True: From f96e58857861998c1be3f2e5d834a4847d291072 Mon Sep 17 00:00:00 2001 From: Trishna Guha Date: Mon, 20 May 2019 20:41:58 +0530 Subject: [PATCH 21/21] update with facts builder Signed-off-by: Trishna Guha --- library/nxos_facts.py | 46 +++++++++++---- .../nxos/argspec/interfaces/interfaces.py | 2 +- .../nxos/config/interfaces/interfaces.py | 58 +++++++++---------- module_utils/nxos/facts/base.py | 2 +- module_utils/nxos/facts/facts.py | 15 +++-- .../nxos/facts/interfaces/interfaces.py | 6 +- 6 files changed, 74 insertions(+), 55 deletions(-) diff --git a/library/nxos_facts.py b/library/nxos_facts.py index ef80495..a76a047 100644 --- a/library/nxos_facts.py +++ b/library/nxos_facts.py @@ -33,30 +33,49 @@ gather_subset: description: - When supplied, this argument will restrict the facts collected - to a given subset. Possible values for this argument include - all, hardware, config, legacy, and interfaces. Can specify a - list of values to include a larger subset. Values can also be used + to a given subset. Possible values for this argument include + all, min, hardware, config, legacy, and interfaces. Can specify a + list of values to include a larger subset.Values can also be used with an initial C(M(!)) to specify that a specific subset should not be collected. required: false default: '!config' version_added: "2.2" + gather_network_resources: + description: + - When supplied, this argument will restrict the facts collected + to a given subset. Possible values for this argument include + all and the resources like interfaces, vlans etc. + Can specify a list of values to include a larger subset. Values + can also be used with an initial C(M(!)) to specify that a + specific subset should not be collected. + required: false + version_added: "2.9" """ EXAMPLES = """ # Gather all facts - nxos_facts: gather_subset: all + gather_network_resources: all -# Collect only the config and default facts +# Collect only the interfaces facts - nxos_facts: gather_subset: - - config + - !all + - !min + gather_network_resources: + - interfaces -# Do not collect hardware facts +# Do not collect interfaces facts - nxos_facts: - gather_subset: - - "!hardware" + gather_network_resources: + - "!interfaces" + +# Collect interfaces and default facts +- nxos_facts: + gather_subset: min + gather_network_resources: interfaces """ RETURN = """ @@ -64,6 +83,10 @@ description: The list of fact subsets collected from the device returned: always type: list +ansible_gather_network_resources: + description: The list of fact resource subsets collected from the device + returned: always + type: list """ @@ -78,12 +101,15 @@ def main(): argument_spec.update(nxos_argument_spec) module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) - warnings = list() + warnings = ['default value for `gather_subset` will be changed to `min` from `!config` v2.11 onwards'] connection = Connection(module._socket_path) gather_subset = module.params['gather_subset'] gather_network_resources = module.params['gather_network_resources'] - ansible_facts = Facts().get_facts(module, connection, gather_subset, gather_network_resources) + result = Facts().get_facts(module, connection, gather_subset, gather_network_resources) + + ansible_facts, additional_warnings = result + warnings.extend(additional_warnings) module.exit_json(ansible_facts=ansible_facts, warnings=warnings) diff --git a/module_utils/nxos/argspec/interfaces/interfaces.py b/module_utils/nxos/argspec/interfaces/interfaces.py index 30d54fb..636ee33 100644 --- a/module_utils/nxos/argspec/interfaces/interfaces.py +++ b/module_utils/nxos/argspec/interfaces/interfaces.py @@ -16,6 +16,6 @@ def __init__(self, **kwargs): } argument_spec = { - 'state': dict(default='merged', choices=['merged', 'replaced', 'overriden', 'deleted']), + 'state': dict(default='merged', choices=['merged', 'replaced', 'overridden', 'deleted']), 'config': dict(type='list', elements='dict', options=config_spec) } diff --git a/module_utils/nxos/config/interfaces/interfaces.py b/module_utils/nxos/config/interfaces/interfaces.py index fdc9104..8a449bc 100644 --- a/module_utils/nxos/config/interfaces/interfaces.py +++ b/module_utils/nxos/config/interfaces/interfaces.py @@ -19,8 +19,8 @@ class Interfaces(ConfigBase, InterfaceArgs): ] def get_interface_facts(self): - facts = Facts().get_facts(self._module, self._connection, self.gather_subset, self.gather_network_resources) - interface_facts = facts['network_resources'].get('interfaces') + facts, warnings = Facts().get_facts(self._module, self._connection, self.gather_subset, self.gather_network_resources) + interface_facts = facts['ansible_network_resources'].get('interfaces') if not interface_facts: return [] return interface_facts @@ -56,8 +56,8 @@ def set_state(self, want, have): commands = list() state = self._module.params['state'] - if state == 'overriden': - commands.extend(self._state_overriden(want, have)) + if state == 'overridden': + commands.extend(self._state_overridden(want, have)) else: for w in want: name = w['name'] @@ -91,6 +91,9 @@ def _state_replaced(self, w, obj_in_have, interface_type): def _replace_config(self, w, obj_in_have, interface_type): commands = [] + if not w: + return commands + for key, value in w.items(): if key == 'enable' and obj_in_have.get('enable') == False: commands.append('no shutdown') @@ -107,14 +110,14 @@ def _replace_config(self, w, obj_in_have, interface_type): if key == 'mtu' and obj_in_have.get('mtu'): commands.append('no mtu') if interface_type in ('ethernet', 'svi'): - if key == 'ip_forward': + if key == 'ip_forward' and value != obj_in_have.get('ip_forward'): commands.append('no ip forward') - if key == 'fabric_forwarding_anycast_gateway': + if key == 'fabric_forwarding_anycast_gateway' and value != obj_in_have.get('fabric_forwarding_anycast_gateway'): commands.append('no fabric forwarding anycast gateway') return commands - def _state_overriden(self, want, have): + def _state_overridden(self, want, have): """ purge interfaces """ @@ -123,33 +126,23 @@ def _state_overriden(self, want, have): for h in have: name = h['name'] obj_in_want = search_obj_in_list(name, want) - if not obj_in_want: - interface_type = get_interface_type(name) - - # Remove logical interfaces - if interface_type in ('loopback', 'portchannel', 'svi'): - commands.append('no interface {0}'.format(name)) - elif interface_type == 'ethernet': - default = True - if h['enable'] is True: - keys = ('description', 'mode', 'mtu', 'speed', 'duplex', 'ip_forward', 'fabric_forwarding_anycast_gateway') - for k, v in iteritems(h): - if k in keys: - if h[k] is not None: - default = False - break - else: - default = False - - if default is False: - # Put physical interface back into default state - commands.append('default interface {0}'.format(name)) + interface_type = get_interface_type(name) + if obj_in_want: + replaced_command = self._replace_config(obj_in_want, h, interface_type) + if replaced_command: + replaced_command.insert(0, 'interface ' + name) + commands.extend(replaced_command) + else: + replaced_command = self._default_attributes(interface_type, h) + if replaced_command: + replaced_command.insert(0, 'interface ' + name) + commands.extend(replaced_command) for w in want: name = w['name'] interface_type = get_interface_type(name) obj_in_have = search_obj_in_list(name, have) - commands.extend(self._state_merged( w, obj_in_have, interface_type)) + commands.extend(self._state_merged(w, obj_in_have, interface_type)) return commands @@ -231,7 +224,6 @@ def _state_merged(self, w, obj_in_have, interface_type): enable = w.get('enable') or obj_in_have.get('enable') if enable is True: commands.append(self._get_admin_state(enable)) - return commands def _state_deleted(self, w, obj_in_have, interface_type): @@ -239,8 +231,10 @@ def _state_deleted(self, w, obj_in_have, interface_type): if not obj_in_have or interface_type == 'unknown': return commands - commands.append('interface ' + w['name']) - commands.extend(self._default_attributes(self, interface_type, obj_in_have)) + commands.extend(self._default_attributes(interface_type, obj_in_have)) + if commands: + interface = 'interface ' + w['name'] + commands.insert(0, interface) return commands def _remove_command_from_interface(self, cmd): diff --git a/module_utils/nxos/facts/base.py b/module_utils/nxos/facts/base.py index 878df32..1574da2 100644 --- a/module_utils/nxos/facts/base.py +++ b/module_utils/nxos/facts/base.py @@ -8,7 +8,7 @@ class FactsBase(object): generated_spec = {} - ansible_facts = {'network_resources': {}} + ansible_facts = {'ansible_network_resources': {}} def __init__(self, argspec, subspec=None, options=None): spec = deepcopy(argspec) diff --git a/module_utils/nxos/facts/facts.py b/module_utils/nxos/facts/facts.py index 0db6107..a5b7de6 100644 --- a/module_utils/nxos/facts/facts.py +++ b/module_utils/nxos/facts/facts.py @@ -1,13 +1,9 @@ import platform import re -from ansible.module_utils.network.nxos.nxos import run_commands, get_config -from ansible.module_utils.network.nxos.nxos import get_capabilities, get_interface_type -from ansible.module_utils.network.nxos.nxos import normalize_interface -from ansible.module_utils.connection import ConnectionError -from ansible.module_utils.six import string_types, iteritems - -from ansible.module_utils.network.common.utils import to_list +from ansible.module_utils.network.nxos.nxos import run_commands, get_config, get_capabilities +from ansible.module_utils.nxos.utils.utils import get_interface_type, normalize_interface +from ansible.module_utils.six import iteritems from ansible.module_utils.nxos.argspec.facts.facts import FactsArgs from ansible.module_utils.nxos.argspec.interfaces.interfaces import InterfaceArgs from ansible.module_utils.nxos.facts.base import FactsBase @@ -810,6 +806,8 @@ def generate_runable_subsets(self, module, subsets, valid_subsets): def get_facts(self, module, connection, gather_subset=['!config'], gather_network_resources=['all']): + warnings = list() + self.ansible_facts['gather_network_resources'] = list() self.ansible_facts['gather_subset'] = list() @@ -838,6 +836,7 @@ def get_facts(self, module, connection, gather_subset=['!config'], gather_networ for inst in instances: inst.populate() facts.update(inst.facts) + warnings.extend(inst.warnings) for key, value in iteritems(facts): # this is to maintain capability with nxos_facts 2.1 @@ -847,7 +846,7 @@ def get_facts(self, module, connection, gather_subset=['!config'], gather_networ key = 'ansible_net_%s' % key self.ansible_facts[key] = value - return self.ansible_facts + return self.ansible_facts, warnings def _get_interfaces(self, module, connection): return InterfacesFacts(InterfaceArgs.argument_spec, 'config', 'options').populate_facts(module, connection) diff --git a/module_utils/nxos/facts/interfaces/interfaces.py b/module_utils/nxos/facts/interfaces/interfaces.py index 1856743..8f8911a 100644 --- a/module_utils/nxos/facts/interfaces/interfaces.py +++ b/module_utils/nxos/facts/interfaces/interfaces.py @@ -28,7 +28,7 @@ def populate_facts(self, module, connection, data=None): if objs: facts['interfaces'] = objs - self.ansible_facts['network_resources'].update(facts) + self.ansible_facts['ansible_network_resources'].update(facts) return self.ansible_facts def render_config(self, spec, conf): @@ -50,7 +50,7 @@ def render_config(self, spec, conf): config['mode'] = self.parse_conf_cmd_arg(conf, 'switchport', 'layer2', res2='layer3') enable = self.parse_conf_cmd_arg(conf, 'shutdown', False) config['enable'] = enable if enable is not None else config['enable'] - config['fabric_forwarding_anycast_gateway'] = self.parse_conf_cmd_arg(conf, 'fabric forwarding mode anycast-gateway', True, res2=False) - config['ip_forward'] = self.parse_conf_cmd_arg(conf, 'ip forward', 'enable', res2='disable') + config['fabric_forwarding_anycast_gateway'] = self.parse_conf_arg(conf, 'fabric forwarding mode anycast-gateway') + config['ip_forward'] = self.parse_conf_arg(conf, 'ip forward') return self.generate_final_config(config)