From d96559db55c0de5722b48c619c51c31b10810922 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Fri, 12 Apr 2019 16:29:41 +0530 Subject: [PATCH 01/49] iosxr interface initial commit Signed-off-by: Sumit Jaiswal --- library/iosxr_facts.py | 88 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 library/iosxr_facts.py diff --git a/library/iosxr_facts.py b/library/iosxr_facts.py new file mode 100644 index 0000000..1002551 --- /dev/null +++ b/library/iosxr_facts.py @@ -0,0 +1,88 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright 2019 +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +""" +The module file for iosxr_facts +""" + +from __future__ import absolute_import, division, print_function +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.connection import Connection +from ansible.module_utils.iosxr.facts.facts import Facts + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'status': [u'preview'], + 'supported_by': [u'Ansible Network']} + + +DOCUMENTATION = """ +--- +module: iosxr_facts +version_added: 2.9 +short_description: Collect facts from remote devices running Cisco IOSXR +description: + - Collects a base set of device facts from a remote device that + is running IOSXR. 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: [u'Sumit Jaiswal (@justjais)'] +notes: + - Tested against IOS-XR Version 6.1.3 on VIRL +options: + gather_subset: + description: + - When supplied, this argument restricts the facts collected + to a given subset. + - Possible values for this argument include + C(all), C(hardware), C(config), and C(interfaces). + - Specify a list of values to include a larger subset. + - Use a value with an initial C(!) to collect all facts except that subset. + required: false + default: '!config' +""" + +EXAMPLES = """ +# Gather all facts +- iosxr_facts: + gather_subset: all + +# Collect only the interfaces and default facts +- iosxr_facts: + gather_subset: + - config + +# Do not collect interfaces facts +- iosxr_facts: + gather_subset: + - "!hardware" +""" + +RETURN = """ +See the respective resource module parameters for the tree. +""" + + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.connection import Connection +from ansible.module_utils.iosxr.facts.facts import Facts + + +def main(): + """ + Main entry point for module execution + + :returns: ansible_facts + """ + module = AnsibleModule(argument_spec=Facts.argument_spec, + supports_check_mode=True) + warnings = list() + + connection = Connection(module._socket_path) #pylint: disable=W0212 + gather_subset = module.params['gather_subset'] + ansible_facts = Facts().get_facts(module, connection, gather_subset) + module.exit_json(ansible_facts=ansible_facts, warnings=warnings) + +if __name__ == '__main__': + main() From 6d2410b770557cb52598526df720d9eaf0064591 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Fri, 12 Apr 2019 16:29:53 +0530 Subject: [PATCH 02/49] iosxr interface initial commit Signed-off-by: Sumit Jaiswal --- library/iosxr_interfaces.py | 180 ++++++++++++++++++++++++++++++++++++ 1 file changed, 180 insertions(+) create mode 100644 library/iosxr_interfaces.py diff --git a/library/iosxr_interfaces.py b/library/iosxr_interfaces.py new file mode 100644 index 0000000..59542e4 --- /dev/null +++ b/library/iosxr_interfaces.py @@ -0,0 +1,180 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright 2019 Red Hat Inc. +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +############################################## +################# WARNING #################### +############################################## +### +### This file is auto generated by the resource +### module builder playbook. +### +### Do not edit this file manually. +### +### Changes to this file will be over written +### by the resource module builder. +### +### Changes should be made in the model used to +### generate this file or in the resource module +### builder template. +### +############################################## +############################################## +############################################## + +""" +The module file for iosxr_interfaces +""" + +from __future__ import absolute_import, division, print_function +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.iosxr.config.interfaces.interfaces import Interfaces + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'status': [u'preview'], + 'supported_by': [u'Ansible Network']} + + +DOCUMENTATION = """ +--- +module: iosxr_interfaces +version_added: 2.9 +short_description: Manage Interface on Cisco IOSXR network devices. +description: Manage Interface on Cisco IOSXR network devices. +author: [u'Sumit Jaiswal (@justjais)'] +options: + config: + description: The provided configuration + suboptions: + name: + description: + - Name of interface, i.e. GigabitEthernet0/2, loopback999. + type: str + required: true + description: + description: + - Description of Interface. + type: str + enabled: + description: + - Interface link status. + type: bool + default: true + speed: + description: + - Interface link speed. Applicable for ethernet interface only. + type: str + mtu: + description: + - Maximum size of transmit packet. Must be an number between 64 and 9600. + Applicable for Ethernet interface only. + type: str + duplex: + description: + - Interface link status. Applicable for ethernet interface only. + type: str + default: auto + choices: ['full', 'half'] + state: + choices: + - merged + - replaced + - overridden + - deleted + default: merged + description: + - The state the configuration should be left in + type: str +""" + +EXAMPLES = """ +--- + +# Using merged + +- name: Configure Ethernet interfaces + iosxr_interfaces: + config: + - name: GigabitEthernet0/0/0/2 + description: 'Configured by Ansible' + enabled: True + - name: GigabitEthernet0/0/0/3 + description: 'Configured by Ansible Network' + enabled: False + duplex: full + operation: merged + +# Using replaced + +- name: Configure following interfaces and replace their existing config + iosxr_interfaces: + config: + - name: GigabitEthernet0/0/0/2 + description: Configured by Ansible + enabled: True + mtu: 2000 + - name: GigabitEthernet0/0/0/3 + description: 'Configured by Ansible Network' + enabled: False + duplex: auto + operation: replaced + +# Using overridden + +- name: Override interfaces + iosxr_interfaces: + config: + - name: GigabitEthernet0/0/0/2 + description: 'Configured by Ansible' + enabled: True + duplex: auto + - name: GigabitEthernet0/0/0/3 + description: 'Configured by Ansible Network' + enabled: False + speed: 1000 + operation: overridden + +# Using deleted + +- name: Delete IOSXR interfaces as in given arguments + iosxr_interfaces: + config: + - name: GigabitEthernet0/0/0/2 + - name: GigabitEthernet0/0/0/3 + operation: deleted + +""" + +RETURN = """ +before: + description: The configuration prior to the model invocation + returned: always + sample: The configuration returned will alwys be in the same format of the paramters above. +after: + description: The resulting configuration model invocation + returned: when changed + sample: The configuration returned will alwys be in the same format of the paramters above. +commands: + description: The set of commands pushed to the remote device + returned: always + type: list + sample: ['command 1', 'command 2', 'command 3'] +""" + + +def main(): + """ + Main entry point for module execution + + :returns: the result form module invocation + """ + module = AnsibleModule(argument_spec=Interfaces.argument_spec, + supports_check_mode=True) + + result = Interfaces(module).execute_module() + module.exit_json(**result) + +if __name__ == '__main__': + main() + From a4db4d92b797217d4c29bc87939ae8f2d87c7f3d Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Fri, 12 Apr 2019 16:30:04 +0530 Subject: [PATCH 03/49] iosxr interface initial commit Signed-off-by: Sumit Jaiswal --- module_utils/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 module_utils/__init__.py diff --git a/module_utils/__init__.py b/module_utils/__init__.py new file mode 100644 index 0000000..e69de29 From e68c4b5b8adae7469bfdbe92df1e9b7b0965bce4 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Fri, 12 Apr 2019 16:30:14 +0530 Subject: [PATCH 04/49] iosxr interface initial commit Signed-off-by: Sumit Jaiswal --- module_utils/iosxr/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 module_utils/iosxr/__init__.py diff --git a/module_utils/iosxr/__init__.py b/module_utils/iosxr/__init__.py new file mode 100644 index 0000000..e69de29 From ea6e5c8d26824ea397c55531e1245c97cc10b74f Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Fri, 12 Apr 2019 16:30:25 +0530 Subject: [PATCH 05/49] iosxr interface initial commit Signed-off-by: Sumit Jaiswal --- module_utils/iosxr/argspec/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 module_utils/iosxr/argspec/__init__.py diff --git a/module_utils/iosxr/argspec/__init__.py b/module_utils/iosxr/argspec/__init__.py new file mode 100644 index 0000000..e69de29 From 24ba78a594d87943b6b5bb85daabc89649141dc3 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Fri, 12 Apr 2019 16:30:36 +0530 Subject: [PATCH 06/49] iosxr interface initial commit Signed-off-by: Sumit Jaiswal --- module_utils/iosxr/argspec/facts/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 module_utils/iosxr/argspec/facts/__init__.py diff --git a/module_utils/iosxr/argspec/facts/__init__.py b/module_utils/iosxr/argspec/facts/__init__.py new file mode 100644 index 0000000..e69de29 From 425cae378a74beb8b17bf8a17c997379528e6053 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Fri, 12 Apr 2019 16:30:47 +0530 Subject: [PATCH 07/49] iosxr interface initial commit Signed-off-by: Sumit Jaiswal --- module_utils/iosxr/argspec/facts/facts.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 module_utils/iosxr/argspec/facts/facts.py diff --git a/module_utils/iosxr/argspec/facts/facts.py b/module_utils/iosxr/argspec/facts/facts.py new file mode 100644 index 0000000..15a7e04 --- /dev/null +++ b/module_utils/iosxr/argspec/facts/facts.py @@ -0,0 +1,23 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright 2019 +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +""" +The arg spec for the myos facts module. +""" + +class FactsArgs(object): #pylint: disable=R0903 + """ The arg spec for the myos facts module + """ + + def __init__(self, **kwargs): + pass + + choices = [ + 'all', + 'net_configuration_interfaces', + ] + + argument_spec = { + 'gather_subset': dict(default=['all'], choices=choices, type='list') + } From 12a3a80a1177353fcdf54a291a96cedc108a1732 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Fri, 12 Apr 2019 16:31:01 +0530 Subject: [PATCH 08/49] iosxr interface initial commit Signed-off-by: Sumit Jaiswal --- module_utils/iosxr/argspec/interfaces/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 module_utils/iosxr/argspec/interfaces/__init__.py diff --git a/module_utils/iosxr/argspec/interfaces/__init__.py b/module_utils/iosxr/argspec/interfaces/__init__.py new file mode 100644 index 0000000..e69de29 From 96a3c1876ce952f819ae4c24e5adbf8b3b11d469 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Fri, 12 Apr 2019 16:31:13 +0530 Subject: [PATCH 09/49] iosxr interface initial commit Signed-off-by: Sumit Jaiswal --- .../iosxr/argspec/interfaces/interfaces.py | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 module_utils/iosxr/argspec/interfaces/interfaces.py diff --git a/module_utils/iosxr/argspec/interfaces/interfaces.py b/module_utils/iosxr/argspec/interfaces/interfaces.py new file mode 100644 index 0000000..23958f7 --- /dev/null +++ b/module_utils/iosxr/argspec/interfaces/interfaces.py @@ -0,0 +1,48 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright 2019 +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +############################################# +################# WARNING #################### +############################################## +### +### This file is auto generated by the resource +### module builder playbook. +### +### Do not edit this file manually. +### +### Changes to this file will be over written +### by the resource module builder. +### +### Changes should be made in the model used to +### generate this file or in the resource module +### builder template. +### +############################################## +############################################## +############################################## +""" +The arg spec for the myos_interfaces module +""" + +class InterfacesArgs(object): #pylint: disable=R0903 + """The arg spec for the myos_interfaces module + """ + + def __init__(self, **kwargs): + pass + + config_spec = { + 'name': dict(type='str', required=True), + 'description': dict(), + 'enabled': dict(default=True, type=bool), + 'speed': dict(), + 'mtu': dict(), + 'duplex': dict(choices=['full', 'half']), + } + + argument_spec = { + 'state': dict(default='merged', choices=['merged', 'replaced', 'overriden', 'deleted']), + 'config': dict(type='list', elements='dict', options=config_spec) + } \ No newline at end of file From 7a3b505189adc905157b4790e8afb6744e4eae31 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Fri, 12 Apr 2019 16:31:22 +0530 Subject: [PATCH 10/49] iosxr interface initial commit Signed-off-by: Sumit Jaiswal --- module_utils/iosxr/config/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 module_utils/iosxr/config/__init__.py diff --git a/module_utils/iosxr/config/__init__.py b/module_utils/iosxr/config/__init__.py new file mode 100644 index 0000000..e69de29 From ed8f97b254b240c8d853d8060abdae1a004cdd71 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Fri, 12 Apr 2019 16:31:33 +0530 Subject: [PATCH 11/49] iosxr interface initial commit Signed-off-by: Sumit Jaiswal --- module_utils/iosxr/config/base.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 module_utils/iosxr/config/base.py diff --git a/module_utils/iosxr/config/base.py b/module_utils/iosxr/config/base.py new file mode 100644 index 0000000..72fc5c7 --- /dev/null +++ b/module_utils/iosxr/config/base.py @@ -0,0 +1,24 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright 2019 +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +""" +The base class for all myos resource modules +""" + +from ansible.module_utils.connection import Connection + +class ConfigBase(object): #pylint: disable=R0205,R0903 + """ The base class for all myos resource modules + """ + _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) #pylint: disable=W0212 + return self._connection From ec6c9fdb63737b4898463813b10cf5a60856e0ae Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Fri, 12 Apr 2019 16:31:43 +0530 Subject: [PATCH 12/49] iosxr interface initial commit Signed-off-by: Sumit Jaiswal --- module_utils/iosxr/config/interfaces/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 module_utils/iosxr/config/interfaces/__init__.py diff --git a/module_utils/iosxr/config/interfaces/__init__.py b/module_utils/iosxr/config/interfaces/__init__.py new file mode 100644 index 0000000..e69de29 From af7bbd254ddad012ce5051ed6c84b289e044a653 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Fri, 12 Apr 2019 16:31:53 +0530 Subject: [PATCH 13/49] iosxr interface initial commit Signed-off-by: Sumit Jaiswal --- .../iosxr/config/interfaces/interfaces.py | 149 ++++++++++++++++++ 1 file changed, 149 insertions(+) create mode 100644 module_utils/iosxr/config/interfaces/interfaces.py diff --git a/module_utils/iosxr/config/interfaces/interfaces.py b/module_utils/iosxr/config/interfaces/interfaces.py new file mode 100644 index 0000000..ff621c0 --- /dev/null +++ b/module_utils/iosxr/config/interfaces/interfaces.py @@ -0,0 +1,149 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright 2019 +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +""" +The myos_interfaces class +It is in this file where the current configuration (as dict) +is compared to the provided configuration (as dict) and the command set +necessary to bring the current configuration to it's desired end-state is +created +""" + +from ansible.module_utils.six import iteritems +from ansible.module_utils.network.common.utils import to_list + +from ansible.module_utils.iosxr.argspec.interfaces.interfaces import InterfaceArgs +from ansible.module_utils.iosxr.config.base import ConfigBase +from ansible.module_utils.iosxr.facts.facts import Facts +from ansible.module_utils.iosxr.utils.utils import get_interface_type, normalize_interface, search_obj_in_list + + +class Interfaces(ConfigBase, InterfacesArgs): + """ + The iosxr_interfaces class + """ + + gather_subset = [ + 'net_configuration_interfaces', + ] + + def get_interfaces_facts(self): + """ Get the 'facts' (the current configuration) + + :rtype: A dictionary + :returns: The current configuration as a dictionary + """ + facts = Facts().get_facts(self._module, self._connection, self.gather_subset) + interfaces_facts = facts['net_configuration'].get('interfaces') + if not interfaces_facts: + return [] + return interfaces_facts + + def execute_module(self): + """ Execute the module + + :rtype: A dictionary + :returns: The result from module execution + """ + result = {'changed': False} + commands = list() + warnings = list() + + existing_interfaces_facts = self.get_interfaces_facts() + commands.extend(self.set_config(existing_interfaces_facts)) + if commands: + if not self._module.check_mode: + self._connection.edit_config(commands) + result['changed'] = True + result['commands'] = commands + + changed_interfaces_facts = self.get_interfaces_facts() + + result['before'] = existing_interfaces_facts + if result['changed']: + result['after'] = changed_interfaces_facts + + result['warnings'] = warnings + return result + + def set_config(self, existing_interfaces_facts): + """ Collect the configuration from the args passed to the module, + collect the current configuration (as a dict from facts) + + :rtype: A list + :returns: the commands necessary to migrate the current configuration + to the desired configuration + """ + want = self._module.params['config'] + have = existing_interfaces_facts + resp = self.set_state(want, have) + return to_list(resp) + + def set_state(self, want, have): + """ Select the appropriate function based on the state provided + + :param want: the desired configuration as a dictionary + :param have: the current configuration as a dictionary + :rtype: A list + :returns: the commands necessary to migrate the current configuration + to the desired configuration + """ + state = self._module.params['state'] + if state == 'overridden': + kwargs = {} + commands = self._state_overridden(**kwargs) + elif state == 'deleted': + kwargs = {} + commands = self._state_deleted(**kwargs) + elif state == 'merged': + kwargs = {} + commands = self._state_merged(**kwargs) + elif state == 'replaced': + kwargs = {} + commands = self._state_replaced(**kwargs) + return commands + + @staticmethod + def _state_replaced(**kwargs): + """ The command generator when state is replaced + + :rtype: A list + :returns: the commands necessary to migrate the current configuration + to the desired configuration + """ + commands = [] + return commands + + @staticmethod + def _state_overridden(**kwargs): + """ The command generator when state is overridden + + :rtype: A list + :returns: the commands necessary to migrate the current configuration + to the desired configuration + """ + commands = [] + return commands + + @staticmethod + def _state_merged(**kwargs): + """ The command generator when state is merged + + :rtype: A list + :returns: the commands necessary to merge the provided into + the current configuration + """ + commands = [] + return commands + + @staticmethod + def _state_deleted(**kwargs): + """ The command generator when state is deleted + + :rtype: A list + :returns: the commands necessary to remove the current configuration + of the provided objects + """ + commands = [] + return commands From a9174811e49362209cbf375674c3a5da4e021e92 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Fri, 12 Apr 2019 16:32:02 +0530 Subject: [PATCH 14/49] iosxr interface initial commit Signed-off-by: Sumit Jaiswal --- module_utils/iosxr/facts/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 module_utils/iosxr/facts/__init__.py diff --git a/module_utils/iosxr/facts/__init__.py b/module_utils/iosxr/facts/__init__.py new file mode 100644 index 0000000..e69de29 From bf7eb48c803e0ea4cf3ae0f26363462d2896bd53 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Fri, 12 Apr 2019 16:32:13 +0530 Subject: [PATCH 15/49] iosxr interface initial commit Signed-off-by: Sumit Jaiswal --- module_utils/iosxr/facts/base.py | 110 +++++++++++++++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 module_utils/iosxr/facts/base.py diff --git a/module_utils/iosxr/facts/base.py b/module_utils/iosxr/facts/base.py new file mode 100644 index 0000000..ab1c8d2 --- /dev/null +++ b/module_utils/iosxr/facts/base.py @@ -0,0 +1,110 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright 2019 +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +""" +The myos facts base class +this contains methods common to all facts subsets +""" + +import re +from copy import deepcopy +from ansible.module_utils.six import iteritems + + +class FactsBase(object): #pylint: disable=R0205 + """ + The myos facts base class + """ + generated_spec = {} + ansible_facts = {'net_configuration': {}} + + def __init__(self, argspec, subspec=None, options=None): + spec = deepcopy(argspec) + if subspec: + if options: + facts_argument_spec = spec[subspec][options] + else: + facts_argument_spec = spec[subspec] + else: + facts_argument_spec = spec + + self.generated_spec = self.generate_dict(facts_argument_spec) + + @staticmethod + def generate_dict(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 + + for key, val in iteritems(spec): + if 'default' in val: + dct = {key: val['default']} + else: + dct = {key: None} + obj.update(dct) + + return obj + + @staticmethod + def parse_conf_arg(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: + result = match.group(1).strip() + else: + result = None + return result + + @staticmethod + def parse_conf_cmd_arg(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 + if res2 is not None: + match = re.search(r'\n\s+no %s(\n|$)' % cmd, cfg) + if match: + return res2 + return None + + @staticmethod + def generate_final_config(cfg_dict): + """ + Generate final config dictionary + + :param cfg_dict: A dictionary parsed in the facts system + :rtype: A dictionary + :returns: A dictionary by eliminating keys that have null values + """ + final_cfg = {} + if not cfg_dict: + return final_cfg + + for key, val in iteritems(cfg_dict): + if val is not None: + final_cfg.update({key:val}) + return final_cfg From e2ed6711252d89b27ef274a525ed8df8cd665443 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Fri, 12 Apr 2019 16:32:23 +0530 Subject: [PATCH 16/49] iosxr interface initial commit Signed-off-by: Sumit Jaiswal --- module_utils/iosxr/facts/facts.py | 72 +++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 module_utils/iosxr/facts/facts.py diff --git a/module_utils/iosxr/facts/facts.py b/module_utils/iosxr/facts/facts.py new file mode 100644 index 0000000..3a26a8f --- /dev/null +++ b/module_utils/iosxr/facts/facts.py @@ -0,0 +1,72 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright 2019 Red Hat Inc. +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +""" +The facts class for iosxr +this file validates each subset of facts and selectively +calls the appropriate facts gathering function +""" + +from ansible.module_utils.iosxr.argspec.facts.facts import FactsArgs +from ansible.module_utils.iosxr.facts.base import FactsBase +from ansible.module_utils.iosxr.argspec.interfaces.interfaces import InterfacesArgs +from ansible.module_utils.iosxr.facts.interfaces.interfaces import InterfacesFacts + +class Facts(FactsArgs, FactsBase): #pylint: disable=R0903 + """ The fact class for iosxr + """ + + def get_facts(self, module, connection, gather_subset=['all']): + """ Collect the facts for myos + + :param module: The module instance + :param connection: The device connection + :param gather_subset: The facts subset to collect + :rtype: dict + :returns: the facts gathered + """ + 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() + if not gather_subset: + gather_subset = ['all'] + + for subset in gather_subset: + if subset == 'all': + runable_subsets.update(valid_subsets) + continue + if subset.startswith('!'): + subset = subset[1:] + if subset == 'all': + exclude_subsets.update(valid_subsets) + continue + exclude = True + else: + exclude = False + + if subset not in 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(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 self.ansible_facts + + @staticmethod + def _get_net_configuration_interfaces(module, connection): + return InterfacesFacts(InterfacesArgs.argument_spec, 'config', 'options').populate_facts(module, connection) From d99b20674bb6b160037b7017ba0805ab43447323 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Fri, 12 Apr 2019 16:32:33 +0530 Subject: [PATCH 17/49] iosxr interface initial commit Signed-off-by: Sumit Jaiswal --- module_utils/iosxr/facts/interfaces/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 module_utils/iosxr/facts/interfaces/__init__.py diff --git a/module_utils/iosxr/facts/interfaces/__init__.py b/module_utils/iosxr/facts/interfaces/__init__.py new file mode 100644 index 0000000..e69de29 From b0cb913795cfec68538599c2b543a2526027d9a7 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Fri, 12 Apr 2019 16:32:46 +0530 Subject: [PATCH 18/49] iosxr interface initial commit Signed-off-by: Sumit Jaiswal --- .../iosxr/facts/interfaces/interfaces.py | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 module_utils/iosxr/facts/interfaces/interfaces.py diff --git a/module_utils/iosxr/facts/interfaces/interfaces.py b/module_utils/iosxr/facts/interfaces/interfaces.py new file mode 100644 index 0000000..b35debb --- /dev/null +++ b/module_utils/iosxr/facts/interfaces/interfaces.py @@ -0,0 +1,79 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright 2019 Red Hat Inc. +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +""" +The iosxr interfaces fact class +It is in this file the configuration is collected from the device +for a given resource, parsed, and the facts tree is populated +based on the configuration. +""" + +import re +from copy import deepcopy + +from ansible.module_utils.iosxr.facts.base import FactsBase +from ansible.module_utils.iosxr.utils.utils import get_interface_type, normalize_interface +import q + +class InterfacesFacts(FactsBase): + """ The iosxr interfaces fact class + """ + + def populate_facts(self, module, connection, data=None): + """ Populate the facts for interfaces + + :param module: the module instance + :param connection: the device connection + :param data: previously collected conf + :rtype: dictionary + :returns: facts + """ + if module: #just for linting purposes + pass + if connection: #just for linting purposes + pass + objs = [] + if not data: + data = connection.get('show running-config interface') + + # operate on a collection of resource x + config = data.split('interface ') + q(config) + for conf in config: + if conf: + obj = self.render_config(self.generated_spec, conf) + if obj: + objs.append(obj) + facts = {} + if objs: + facts['interfaces'] = objs + self.ansible_facts['net_configuration'].update(facts) + return self.ansible_facts + + def render_config(self, spec, conf): + """ + Render config as dictionary structure and delete keys from spec for null values + + :param spec: The facts tree, generated from the argspec + :param conf: The configuration + :rtype: dictionary + :returns: The generated config + """ + + config = deepcopy(spec) + match = re.search(r'^(\S+)', conf) + intf = match.group(1) + + if get_interface_type(intf) == 'unknown': + return {} + # populate the facts from the configuration + 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') + enabled = self.parse_conf_cmd_arg(conf, 'shutdown', False) + config['enabled'] = enabled if enabled is not None else config['enabled'] + + return self.generate_final_config(config) From 1e5f45018bb2baa1eb0216ac3e45c4f421a310e4 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Fri, 12 Apr 2019 16:32:56 +0530 Subject: [PATCH 19/49] iosxr interface initial commit Signed-off-by: Sumit Jaiswal --- module_utils/iosxr/utils/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 module_utils/iosxr/utils/__init__.py diff --git a/module_utils/iosxr/utils/__init__.py b/module_utils/iosxr/utils/__init__.py new file mode 100644 index 0000000..e69de29 From 9853ab41774ea3c4c74bdadc22ae936d625c568b Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Fri, 12 Apr 2019 16:33:19 +0530 Subject: [PATCH 20/49] iosxr interface initial commit Signed-off-by: Sumit Jaiswal --- module_utils/iosxr/utils/utils.py | 92 +++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 module_utils/iosxr/utils/utils.py diff --git a/module_utils/iosxr/utils/utils.py b/module_utils/iosxr/utils/utils.py new file mode 100644 index 0000000..7d4e7ac --- /dev/null +++ b/module_utils/iosxr/utils/utils.py @@ -0,0 +1,92 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright 2019 Red Hat Inc. +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +# utils + +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('gi'): + if_type = 'GigabitEthernet' + elif name.lower().startswith('te'): + if_type = 'TenGigabitEthernet' + elif name.lower().startswith('fa'): + if_type = 'FastEthernet' + elif name.lower().startswith('fo'): + if_type = 'FortyGigabitEthernet' + elif 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' + elif name.lower().startswith('twe'): + if_type = 'TwentyFiveGigE' + elif name.lower().startswith('hu'): + if_type = 'HundredGigE' + 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('GI'): + return 'GigabitEthernet' + elif interface.upper().startswith('TE'): + return 'TenGigabitEthernet' + elif interface.upper().startswith('FA'): + return 'FastEthernet' + elif interface.upper().startswith('FO'): + return 'FortyGigabitEthernet' + elif interface.upper().startswith('ET'): + return 'Ethernet' + elif interface.upper().startswith('VL'): + return 'Vlan' + elif interface.upper().startswith('LO'): + return 'loopback' + elif interface.upper().startswith('PO'): + return 'port-channel' + elif interface.upper().startswith('NV'): + return 'nve' + elif interface.upper().startswith('TWE'): + return 'TwentyFiveGigE' + elif interface.upper().startswith('HU'): + return 'HundredGigE' + else: + return 'unknown' \ No newline at end of file From fc3243db7124f9bc3e0d3a625ae820c8a3f019d2 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Fri, 12 Apr 2019 16:33:31 +0530 Subject: [PATCH 21/49] iosxr interface initial commit Signed-off-by: Sumit Jaiswal --- module_utils/network/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 module_utils/network/__init__.py diff --git a/module_utils/network/__init__.py b/module_utils/network/__init__.py new file mode 100644 index 0000000..e69de29 From a714390047edeb6f12d5820c87713589bbb4de00 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Fri, 12 Apr 2019 16:33:42 +0530 Subject: [PATCH 22/49] iosxr interface initial commit Signed-off-by: Sumit Jaiswal --- module_utils/network/argspec/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 module_utils/network/argspec/__init__.py diff --git a/module_utils/network/argspec/__init__.py b/module_utils/network/argspec/__init__.py new file mode 100644 index 0000000..e69de29 From 3b6952b05669ba83ffc49a09ce20a5294dd37e7c Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Fri, 12 Apr 2019 16:33:53 +0530 Subject: [PATCH 23/49] iosxr interface initial commit Signed-off-by: Sumit Jaiswal --- module_utils/network/argspec/base.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 module_utils/network/argspec/base.py diff --git a/module_utils/network/argspec/base.py b/module_utils/network/argspec/base.py new file mode 100644 index 0000000..b7a599d --- /dev/null +++ b/module_utils/network/argspec/base.py @@ -0,0 +1,12 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright 2019 Red Hat Inc. +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +""" The argspec base class +""" + +class ArgspecBase(object): + """ The argspec base class + """ + def __init__(self, **kwargs): + pass From 51c8acfa9ac9a88d6333a76c25e3ef25701186f1 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Fri, 12 Apr 2019 16:34:08 +0530 Subject: [PATCH 24/49] iosxr interface initial commit Signed-off-by: Sumit Jaiswal --- library/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 library/__init__.py diff --git a/library/__init__.py b/library/__init__.py new file mode 100644 index 0000000..e69de29 From 399670e0a4aedd29371337e1baba60832b588ffa Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Mon, 15 Apr 2019 21:29:11 +0530 Subject: [PATCH 25/49] fix iosxr operations Signed-off-by: Sumit Jaiswal --- module_utils/iosxr/argspec/interfaces/interfaces.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module_utils/iosxr/argspec/interfaces/interfaces.py b/module_utils/iosxr/argspec/interfaces/interfaces.py index 23958f7..313228d 100644 --- a/module_utils/iosxr/argspec/interfaces/interfaces.py +++ b/module_utils/iosxr/argspec/interfaces/interfaces.py @@ -43,6 +43,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) } \ No newline at end of file From 30b10815c85bb75522d6a374e0ed22c1288d09c7 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Mon, 15 Apr 2019 21:29:21 +0530 Subject: [PATCH 26/49] fix iosxr operations Signed-off-by: Sumit Jaiswal --- .../iosxr/config/interfaces/interfaces.py | 155 ++++++++++++++++-- 1 file changed, 144 insertions(+), 11 deletions(-) diff --git a/module_utils/iosxr/config/interfaces/interfaces.py b/module_utils/iosxr/config/interfaces/interfaces.py index ff621c0..86097a3 100644 --- a/module_utils/iosxr/config/interfaces/interfaces.py +++ b/module_utils/iosxr/config/interfaces/interfaces.py @@ -13,7 +13,7 @@ from ansible.module_utils.six import iteritems from ansible.module_utils.network.common.utils import to_list -from ansible.module_utils.iosxr.argspec.interfaces.interfaces import InterfaceArgs +from ansible.module_utils.iosxr.argspec.interfaces.interfaces import InterfacesArgs from ansible.module_utils.iosxr.config.base import ConfigBase from ansible.module_utils.iosxr.facts.facts import Facts from ansible.module_utils.iosxr.utils.utils import get_interface_type, normalize_interface, search_obj_in_list @@ -27,6 +27,7 @@ class Interfaces(ConfigBase, InterfacesArgs): gather_subset = [ 'net_configuration_interfaces', ] + params = ('description', 'mtu', 'speed', 'duplex') def get_interfaces_facts(self): """ Get the 'facts' (the current configuration) @@ -76,8 +77,15 @@ def set_config(self, existing_interfaces_facts): to the desired configuration """ want = self._module.params['config'] + for w in want: + if len(w['name'].split(' ')) > 1 and w['name'].split(' ')[0] == "preconfigure": + name = "preconfigure " + normalize_interface(w['name'].split(' ')[1]) + w.update({'name': name}) + else: + w.update({'name': normalize_interface(w['name'])}) have = existing_interfaces_facts resp = self.set_state(want, have) + return to_list(resp) def set_state(self, want, have): @@ -89,19 +97,29 @@ def set_state(self, want, have): :returns: the commands necessary to migrate the current configuration to the desired configuration """ + commands = [] state = self._module.params['state'] + if state == 'overridden': - kwargs = {} + kwargs = {'want': want, 'have': have} commands = self._state_overridden(**kwargs) - elif state == 'deleted': - kwargs = {} - commands = self._state_deleted(**kwargs) - elif state == 'merged': - kwargs = {} - commands = self._state_merged(**kwargs) - elif state == 'replaced': - kwargs = {} - commands = self._state_replaced(**kwargs) + else: + for w in want: + # get the interface type + name = w['name'] + if len(name.split(' ')) > 1 and name.split(' ')[0] == "preconfigure": + interface_type = get_interface_type(name.split(' ')[1]) + else: + interface_type = get_interface_type(name) + obj_in_have = search_obj_in_list(name, have) + kwargs = {'want': w, 'have': obj_in_have, 'type': interface_type} + if state == 'deleted': + commands.extend(Interfaces._state_deleted(**kwargs)) + elif state == 'merged': + commands.extend(Interfaces._state_merged(**kwargs)) + elif state == 'replaced': + commands.extend(Interfaces._state_replaced(**kwargs)) + return commands @staticmethod @@ -113,6 +131,24 @@ def _state_replaced(**kwargs): to the desired configuration """ commands = [] + want = kwargs['want'] + obj_in_have = kwargs['have'] + interface_type = kwargs['type'] + + if want['name']: + interface = 'interface ' + want['name'] + + if obj_in_have: + if interface_type.lower() == 'loopback': + commands.extend(Interfaces._state_merged(want, obj_in_have)) + elif interface_type.lower() == 'gigabitethernet' or interface_type.lower() == "preconfigure": + for item in Interfaces.params: + value = obj_in_have.get(item) + if value and want[item] != value and value != 'auto': + Interfaces._remove_command_from_interface(interface, item, commands) + kwargs = {'want': want, 'have': obj_in_have} + commands.extend(Interfaces._state_merged(**kwargs)) + return commands @staticmethod @@ -124,6 +160,43 @@ def _state_overridden(**kwargs): to the desired configuration """ commands = [] + want = kwargs['want'] + obj_in_have = kwargs['have'] + + for have in obj_in_have: + + name = have['name'] + obj_in_want = search_obj_in_list(name, want) + if not obj_in_want: + interface_type = get_interface_type(name) + if interface_type.lower() == 'loopback': + interface = 'interface ' + name + Interfaces._remove_command_from_interface(interface, 'description', commands) + elif interface_type.lower() == 'gigabitethernet' or interface_type.lower() == "preconfigure": + default = True + if have['enabled'] is True: + for k, v in iteritems(have): + if k in Interfaces.params: + if have[k] is not None: + default = False + break + else: + default = False + + if default is False: + if name not in commands: + commands.append('interface {0}'.format(name)) + commands.append('no description') + commands.append('no mtu') + commands.append('no speed') + commands.append('no duplex') + + for w in want: + name = w['name'] + obj_in_have = search_obj_in_list(name, obj_in_have) + kwargs = {'want': w, 'have': have} + commands.extend(Interfaces._state_merged(**kwargs)) + return commands @staticmethod @@ -135,6 +208,32 @@ def _state_merged(**kwargs): the current configuration """ commands = [] + want = kwargs['want'] + obj_in_have = kwargs['have'] + name = want['name'] + enabled = want.get('enabled') + + if name: + interface = 'interface ' + name + if not obj_in_have: + commands.append(interface) + commands.append('no shutdown') if enabled else commands.append('shutdown') + + for item in Interfaces.params: + candidate = want.get(item) + if candidate: + commands.append(item + ' ' + str(candidate)) + else: + if enabled is True and enabled != obj_in_have.get('enabled'): + Interfaces._add_command_to_interface(interface, 'no shutdown', commands) + elif enabled is False and enabled != obj_in_have.get('enabled'): + Interfaces._add_command_to_interface(interface, 'shutdown', commands) + for item in Interfaces.params: + candidate = want.get(item) + if candidate and candidate != obj_in_have.get(item): + cmd = item + ' ' + str(candidate) + Interfaces._add_command_to_interface(interface, cmd, commands) + return commands @staticmethod @@ -146,4 +245,38 @@ def _state_deleted(**kwargs): of the provided objects """ commands = [] + want = kwargs['want'] + obj_in_have = kwargs['have'] + interface_type = kwargs['type'] + if not obj_in_have or interface_type == 'unknown': + return commands + + interface = 'interface ' + want['name'] + if 'description' in obj_in_have: + Interfaces._remove_command_from_interface(interface, 'description', commands) + if 'enabled' in obj_in_have and obj_in_have['enabled'] is False: + # if enable is False set enable as True which is the default behavior + Interfaces._remove_command_from_interface(interface, 'shutdown', commands) + + if interface_type.lower() == 'gigabitethernet': + if 'speed' in obj_in_have and obj_in_have['speed'] != 'auto': + Interfaces._remove_command_from_interface(interface, 'speed', commands) + if 'duplex' in obj_in_have and obj_in_have['duplex'] != 'auto': + Interfaces._remove_command_from_interface(interface, 'duplex', commands) + if 'mtu' in obj_in_have: + Interfaces._remove_command_from_interface(interface, 'mtu', commands) + return commands + + @staticmethod + def _remove_command_from_interface(interface, cmd, commands): + if interface not in commands: + commands.insert(0, interface) + commands.append('no %s' % cmd) + return commands + + @staticmethod + def _add_command_to_interface(interface, cmd, commands): + if interface not in commands: + commands.insert(0, interface) + commands.append(cmd) From aedfe0e368115c2617f25c975aa305a144a093c8 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Mon, 15 Apr 2019 21:29:31 +0530 Subject: [PATCH 27/49] fix iosxr operations Signed-off-by: Sumit Jaiswal --- module_utils/iosxr/facts/interfaces/interfaces.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/module_utils/iosxr/facts/interfaces/interfaces.py b/module_utils/iosxr/facts/interfaces/interfaces.py index b35debb..371a7f5 100644 --- a/module_utils/iosxr/facts/interfaces/interfaces.py +++ b/module_utils/iosxr/facts/interfaces/interfaces.py @@ -14,7 +14,7 @@ from ansible.module_utils.iosxr.facts.base import FactsBase from ansible.module_utils.iosxr.utils.utils import get_interface_type, normalize_interface -import q + class InterfacesFacts(FactsBase): """ The iosxr interfaces fact class @@ -39,7 +39,6 @@ def populate_facts(self, module, connection, data=None): # operate on a collection of resource x config = data.split('interface ') - q(config) for conf in config: if conf: obj = self.render_config(self.generated_spec, conf) @@ -63,8 +62,9 @@ def render_config(self, spec, conf): config = deepcopy(spec) match = re.search(r'^(\S+)', conf) + if match.group(1).lower() == "preconfigure": + match = re.search(r'^(\S+ \S+)', conf) intf = match.group(1) - if get_interface_type(intf) == 'unknown': return {} # populate the facts from the configuration From 0290c24090e6725177980b612f96d46d66607407 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Mon, 15 Apr 2019 21:29:41 +0530 Subject: [PATCH 28/49] fix iosxr operations Signed-off-by: Sumit Jaiswal --- module_utils/iosxr/utils/utils.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/module_utils/iosxr/utils/utils.py b/module_utils/iosxr/utils/utils.py index 7d4e7ac..d50b239 100644 --- a/module_utils/iosxr/utils/utils.py +++ b/module_utils/iosxr/utils/utils.py @@ -26,12 +26,10 @@ def _get_number(name): if name.lower().startswith('gi'): if_type = 'GigabitEthernet' - elif name.lower().startswith('te'): - if_type = 'TenGigabitEthernet' elif name.lower().startswith('fa'): if_type = 'FastEthernet' elif name.lower().startswith('fo'): - if_type = 'FortyGigabitEthernet' + if_type = 'FortyGigE' elif name.lower().startswith('et'): if_type = 'Ethernet' elif name.lower().startswith('vl'): @@ -68,12 +66,10 @@ def get_interface_type(interface): if interface.upper().startswith('GI'): return 'GigabitEthernet' - elif interface.upper().startswith('TE'): - return 'TenGigabitEthernet' elif interface.upper().startswith('FA'): return 'FastEthernet' elif interface.upper().startswith('FO'): - return 'FortyGigabitEthernet' + return 'FortyGigE' elif interface.upper().startswith('ET'): return 'Ethernet' elif interface.upper().startswith('VL'): @@ -88,5 +84,7 @@ def get_interface_type(interface): return 'TwentyFiveGigE' elif interface.upper().startswith('HU'): return 'HundredGigE' + elif interface.upper().startswith('PRE'): + return 'preconfigure' else: return 'unknown' \ No newline at end of file From a0abbfd0eccd3b097abc4337299abfe7d6cf3c09 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Tue, 16 Apr 2019 16:32:18 +0530 Subject: [PATCH 29/49] operation overridden fix Signed-off-by: Sumit Jaiswal --- .../iosxr/config/interfaces/interfaces.py | 47 ++++++++++++------- 1 file changed, 31 insertions(+), 16 deletions(-) diff --git a/module_utils/iosxr/config/interfaces/interfaces.py b/module_utils/iosxr/config/interfaces/interfaces.py index 86097a3..37578ca 100644 --- a/module_utils/iosxr/config/interfaces/interfaces.py +++ b/module_utils/iosxr/config/interfaces/interfaces.py @@ -162,16 +162,16 @@ def _state_overridden(**kwargs): commands = [] want = kwargs['want'] obj_in_have = kwargs['have'] + interface_want = 'interface ' + want[0]['name'] for have in obj_in_have: - name = have['name'] obj_in_want = search_obj_in_list(name, want) if not obj_in_want: interface_type = get_interface_type(name) if interface_type.lower() == 'loopback': - interface = 'interface ' + name - Interfaces._remove_command_from_interface(interface, 'description', commands) + commands.append('interface ' + name) + commands.append('no description') elif interface_type.lower() == 'gigabitethernet' or interface_type.lower() == "preconfigure": default = True if have['enabled'] is True: @@ -182,20 +182,35 @@ def _state_overridden(**kwargs): break else: default = False - if default is False: - if name not in commands: - commands.append('interface {0}'.format(name)) - commands.append('no description') - commands.append('no mtu') - commands.append('no speed') - commands.append('no duplex') - - for w in want: - name = w['name'] - obj_in_have = search_obj_in_list(name, obj_in_have) - kwargs = {'want': w, 'have': have} - commands.extend(Interfaces._state_merged(**kwargs)) + # Delete the configurable params by interface module + interface = 'interface ' + name + for each in Interfaces.params: + if interface not in commands: + commands.append(interface) + commands.append('no {0}'.format(each)) + elif name == want[0]['name']: + changed = False + # Delete the wanted interface to be replaced with provided values + for k, v in iteritems(have): + if obj_in_want[k] != have[k] and have[k] != "auto": + if interface_want not in commands: + changed = True + commands.append(interface_want) + commands.append('no {0}'.format(k)) + if not changed: + break + + if interface_want in commands: + # if there's change in interface_want then extend the commands + for w in want: + name = w['name'] + have = search_obj_in_list(name, obj_in_have) + kwargs = {'want': w, 'have': have} + commands.extend(Interfaces._state_merged(**kwargs)) + else: + # if there's no change in inteface_want then maintain idempotency + commands = [] return commands From f6252a600586892795854cd38d397be8f91b6afc Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Tue, 16 Apr 2019 16:32:36 +0530 Subject: [PATCH 30/49] pep8 complain fix Signed-off-by: Sumit Jaiswal --- module_utils/iosxr/utils/utils.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/module_utils/iosxr/utils/utils.py b/module_utils/iosxr/utils/utils.py index d50b239..ea5b253 100644 --- a/module_utils/iosxr/utils/utils.py +++ b/module_utils/iosxr/utils/utils.py @@ -5,12 +5,14 @@ # utils + 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 """ @@ -60,6 +62,7 @@ def _get_number(name): return proper_interface + def get_interface_type(interface): """Gets the type of interface """ From 9053cd585e939df998d8062b16f06053ee1932c9 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Mon, 22 Apr 2019 11:52:12 +0530 Subject: [PATCH 31/49] adding before n after config Signed-off-by: Sumit Jaiswal --- library/iosxr_interfaces.py | 176 ++++++++++++++++++++++++++++++++++++ 1 file changed, 176 insertions(+) diff --git a/library/iosxr_interfaces.py b/library/iosxr_interfaces.py index 59542e4..5996e62 100644 --- a/library/iosxr_interfaces.py +++ b/library/iosxr_interfaces.py @@ -93,6 +93,26 @@ # Using merged +# Before state: +# ------------- +# +# viosxr#show running-config interface +# interface GigabitEthernet0/0/0/1 +# shutdown +# ! +# interface GigabitEthernet0/0/0/2 +# vrf custB +# ipv4 address 178.18.169.23 255.255.255.0 +# dot1q native vlan 30 +# ! +# interface GigabitEthernet0/0/0/3 +# description Replaced by Ansible Team +# mtu 2000 +# vrf custB +# ipv4 address 10.10.0.2 255.255.255.0 +# dot1q native vlan 1021 +# ! + - name: Configure Ethernet interfaces iosxr_interfaces: config: @@ -105,8 +125,51 @@ duplex: full operation: merged +# After state: +# ------------ +# +# viosxr#show running-config interface +# interface GigabitEthernet0/0/0/1 +# shutdown +# ! +# interface GigabitEthernet0/0/0/2 +# description Configured and Merged by Ansible Network +# vrf custB +# ipv4 address 178.18.169.23 255.255.255.0 +# dot1q native vlan 30 +# ! +# interface GigabitEthernet0/0/0/3 +# description Configured and Merged by Ansible Network +# mtu 2600 +# vrf custB +# ipv4 address 10.10.0.2 255.255.255.0 +# duplex full +# shutdown +# dot1q native vlan 1021 +# ! + # Using replaced +# Before state: +# ------------- +# +# viosxr#show running-config interface +# interface GigabitEthernet0/0/0/1 +# description Configured by Ansible +# shutdown +# ! +# interface GigabitEthernet0/0/0/2 +# description Test +# vrf custB +# ipv4 address 178.18.169.23 255.255.255.0 +# dot1q native vlan 30 +# ! +# interface GigabitEthernet0/0/0/3 +# vrf custB +# ipv4 address 10.10.0.2 255.255.255.0 +# dot1q native vlan 1021 +# ! + - name: Configure following interfaces and replace their existing config iosxr_interfaces: config: @@ -120,8 +183,55 @@ duplex: auto operation: replaced +# After state: +# ------------- +# +# viosxr#show running-config interface +# interface GigabitEthernet0/0/0/1 +# description Configured by Ansible +# shutdown +# ! +# interface GigabitEthernet0/0/0/2 +# description Configured and Replaced by Ansible +# mtu 2000 +# vrf custB +# ipv4 address 178.18.169.23 255.255.255.0 +# dot1q native vlan 30 +# ! +# interface GigabitEthernet0/0/0/3 +# description Configured and Replaced by Ansible Network +# vrf custB +# ipv4 address 10.10.0.2 255.255.255.0 +# duplex half +# shutdown +# dot1q native vlan 1021 +# ! + # Using overridden +# Before state: +# ------------- +# +# viosxr#show running-config interface +# interface GigabitEthernet0/0/0/1 +# shutdown +# ! +# interface GigabitEthernet0/0/0/2 +# description Configured by Ansible +# vrf custB +# ipv4 address 178.18.169.23 255.255.255.0 +# dot1q native vlan 30 +# ! +# interface GigabitEthernet0/0/0/3 +# description Configured by Ansible +# mtu 2600 +# vrf custB +# ipv4 address 10.10.0.2 255.255.255.0 +# duplex full +# shutdown +# dot1q native vlan 1021 +# ! + - name: Override interfaces iosxr_interfaces: config: @@ -135,8 +245,56 @@ speed: 1000 operation: overridden +# After state: +# ------------- +# +# viosxr#show running-config interface +# interface GigabitEthernet0/0/0/1 +# shutdown +# ! +# interface GigabitEthernet0/0/0/2 +# description Configured and Overridden by Ansible Network +# vrf custB +# ipv4 address 178.18.169.23 255.255.255.0 +# speed 1000 +# dot1q native vlan 30 +# ! +# interface GigabitEthernet0/0/0/3 +# description Configured and Overridden by Ansible Network +# mtu 2000 +# vrf custB +# ipv4 address 10.10.0.2 255.255.255.0 +# duplex full +# shutdown +# dot1q native vlan 1021 +# ! + # Using deleted +# Before state: +# ------------- +# +# viosxr#show running-config interface +# interface GigabitEthernet0/0/0/1 +# shutdown +# ! +# interface GigabitEthernet0/0/0/2 +# description Configured and Overridden by Ansible Network +# vrf custB +# ipv4 address 178.18.169.23 255.255.255.0 +# speed 1000 +# dot1q native vlan 30 +# ! +# interface GigabitEthernet0/0/0/3 +# description Configured and Overridden by Ansible Network +# mtu 2000 +# vrf custB +# ipv4 address 10.10.0.2 255.255.255.0 +# duplex full +# shutdown +# dot1q native vlan 1021 +# ! + - name: Delete IOSXR interfaces as in given arguments iosxr_interfaces: config: @@ -144,6 +302,24 @@ - name: GigabitEthernet0/0/0/3 operation: deleted +# After state: +# ------------- +# +# viosxr#show running-config interface +# interface GigabitEthernet0/0/0/1 +# shutdown +# ! +# interface GigabitEthernet0/0/0/2 +# vrf custB +# ipv4 address 178.18.169.23 255.255.255.0 +# dot1q native vlan 30 +# ! +# interface GigabitEthernet0/0/0/3 +# vrf custB +# ipv4 address 10.10.0.2 255.255.255.0 +# dot1q native vlan 1021 +# ! + """ RETURN = """ From 02a2aed67436ab31f53c1007869bf02784eaaadc Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Tue, 28 May 2019 15:21:21 +0530 Subject: [PATCH 32/49] adehere to new facts Signed-off-by: Sumit Jaiswal --- library/iosxr_facts.py | 85 +++++++++++++++++++++++++++--------------- 1 file changed, 55 insertions(+), 30 deletions(-) diff --git a/library/iosxr_facts.py b/library/iosxr_facts.py index 1002551..c5ac399 100644 --- a/library/iosxr_facts.py +++ b/library/iosxr_facts.py @@ -1,62 +1,79 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -# Copyright 2019 +# Copyright 2019 Red Hat Inc. # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) """ The module file for iosxr_facts """ from __future__ import absolute_import, division, print_function -from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils.connection import Connection -from ansible.module_utils.iosxr.facts.facts import Facts ANSIBLE_METADATA = {'metadata_version': '1.1', - 'status': [u'preview'], - 'supported_by': [u'Ansible Network']} + 'status': ['preview'], + 'supported_by': 'network'} +NETWORK_OS = "iosxr" +RESOURCE = "facts" +COPYRIGHT = "Copyright 2019 Red Hat" DOCUMENTATION = """ --- module: iosxr_facts version_added: 2.9 -short_description: Collect facts from remote devices running Cisco IOSXR +short_description: Get facts about Cisco IOSXR devices. description: - - Collects a base set of device facts from a remote device that - is running IOSXR. 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. + - Collects facts from network devices running the iosxr operating + system. This module places the facts gathered in the fact tree keyed by the + respective resource name. The facts module will always collect a + base set of facts from the device and can enable or disable + collection of additional facts. author: [u'Sumit Jaiswal (@justjais)'] notes: - - Tested against IOS-XR Version 6.1.3 on VIRL + - Tested against IOSXRv Version 6.1.3 on VIRL options: gather_subset: description: - - When supplied, this argument restricts the facts collected - to a given subset. - - Possible values for this argument include - C(all), C(hardware), C(config), and C(interfaces). - - Specify a list of values to include a larger subset. - - Use a value with an initial C(!) to collect all facts except that subset. + - When supplied, this argument will restrict the facts collected + 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' + default: 'all' + 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 - iosxr_facts: gather_subset: all - -# Collect only the interfaces and default facts + gather_network_resources: all +# Collect only the iosxr facts - iosxr_facts: gather_subset: - - config - -# Do not collect interfaces facts + - !all + - !min + gather_network_resources: + - iosxr +# Do not collect iosxr facts - iosxr_facts: - gather_subset: - - "!hardware" + gather_network_resources: + - "!iosxr" +# Collect iosxr and minimal default facts +- iosxr_facts: + gather_subset: min + gather_network_resources: iosxr """ RETURN = """ @@ -77,11 +94,19 @@ def main(): """ module = AnsibleModule(argument_spec=Facts.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) #pylint: disable=W0212 + 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'] + result = Facts().get_facts(module, connection, gather_subset, gather_network_resources) + + try: + ansible_facts, warning = result + warnings.extend(warning) + except (TypeError, KeyError): + ansible_facts = result + module.exit_json(ansible_facts=ansible_facts, warnings=warnings) if __name__ == '__main__': From 9ac8ec7945dad14cf2b0ec6c63db7d01197f0824 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Tue, 28 May 2019 15:21:21 +0530 Subject: [PATCH 33/49] adehere to new facts Signed-off-by: Sumit Jaiswal --- module_utils/iosxr/argspec/facts/facts.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/module_utils/iosxr/argspec/facts/facts.py b/module_utils/iosxr/argspec/facts/facts.py index 15a7e04..60bbb33 100644 --- a/module_utils/iosxr/argspec/facts/facts.py +++ b/module_utils/iosxr/argspec/facts/facts.py @@ -1,13 +1,13 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -# Copyright 2019 +# Copyright 2019 Red Hat Inc. # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) """ -The arg spec for the myos facts module. +The arg spec for the iosxr facts module. """ class FactsArgs(object): #pylint: disable=R0903 - """ The arg spec for the myos facts module + """ The arg spec for the iosxr facts module """ def __init__(self, **kwargs): @@ -15,9 +15,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=['all'], choices=choices, type='list'), + 'gather_network_resources': dict(default=['all'], choices=choices, type='list'), } From 78e40d352553d392eb4098ad8c733337d72460d1 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Tue, 28 May 2019 15:21:21 +0530 Subject: [PATCH 34/49] adehere to new facts Signed-off-by: Sumit Jaiswal --- module_utils/iosxr/argspec/interfaces/interfaces.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/module_utils/iosxr/argspec/interfaces/interfaces.py b/module_utils/iosxr/argspec/interfaces/interfaces.py index 313228d..023a2a5 100644 --- a/module_utils/iosxr/argspec/interfaces/interfaces.py +++ b/module_utils/iosxr/argspec/interfaces/interfaces.py @@ -1,6 +1,6 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -# Copyright 2019 +# Copyright 2019 Red Hat Inc. # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) ############################################# @@ -23,11 +23,11 @@ ############################################## ############################################## """ -The arg spec for the myos_interfaces module +The arg spec for the iosxr_interfaces module """ class InterfacesArgs(object): #pylint: disable=R0903 - """The arg spec for the myos_interfaces module + """The arg spec for the iosxr_interfaces module """ def __init__(self, **kwargs): @@ -37,8 +37,8 @@ def __init__(self, **kwargs): 'name': dict(type='str', required=True), 'description': dict(), 'enabled': dict(default=True, type=bool), - 'speed': dict(), - 'mtu': dict(), + 'speed': dict(type='str'), + 'mtu': dict(type='str'), 'duplex': dict(choices=['full', 'half']), } From 6772347126e8e1266084786c04554475be63be2f Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Tue, 28 May 2019 15:21:21 +0530 Subject: [PATCH 35/49] adehere to new facts Signed-off-by: Sumit Jaiswal --- module_utils/iosxr/config/base.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/module_utils/iosxr/config/base.py b/module_utils/iosxr/config/base.py index 72fc5c7..7ef97ea 100644 --- a/module_utils/iosxr/config/base.py +++ b/module_utils/iosxr/config/base.py @@ -1,15 +1,15 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -# Copyright 2019 +# Copyright 2019 Red Hat Inc. # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) """ -The base class for all myos resource modules +The base class for all iosxr resource modules """ from ansible.module_utils.connection import Connection class ConfigBase(object): #pylint: disable=R0205,R0903 - """ The base class for all myos resource modules + """ The base class for all iosxr resource modules """ _connection = None From ceb37386b11ab81d0cf9db911368df0e01ff7ff7 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Tue, 28 May 2019 15:21:21 +0530 Subject: [PATCH 36/49] adehere to new facts Signed-off-by: Sumit Jaiswal --- .../iosxr/config/interfaces/interfaces.py | 266 +++++++++--------- 1 file changed, 131 insertions(+), 135 deletions(-) diff --git a/module_utils/iosxr/config/interfaces/interfaces.py b/module_utils/iosxr/config/interfaces/interfaces.py index 37578ca..3467a3d 100644 --- a/module_utils/iosxr/config/interfaces/interfaces.py +++ b/module_utils/iosxr/config/interfaces/interfaces.py @@ -1,9 +1,9 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -# Copyright 2019 +# Copyright 2019 Red Hat Inc. # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) """ -The myos_interfaces class +The iosxr_interfaces class It is in this file where the current configuration (as dict) is compared to the provided configuration (as dict) and the command set necessary to bring the current configuration to it's desired end-state is @@ -25,8 +25,14 @@ class Interfaces(ConfigBase, InterfacesArgs): """ gather_subset = [ - 'net_configuration_interfaces', + '!all', + '!min', ] + + gather_network_resources = [ + 'interfaces', + ] + params = ('description', 'mtu', 'speed', 'duplex') def get_interfaces_facts(self): @@ -35,8 +41,9 @@ def get_interfaces_facts(self): :rtype: A dictionary :returns: The current configuration as a dictionary """ - facts = Facts().get_facts(self._module, self._connection, self.gather_subset) - interfaces_facts = facts['net_configuration'].get('interfaces') + result = Facts().get_facts(self._module, self._connection, self.gather_subset, self.gather_network_resources) + facts = result + interfaces_facts = facts['ansible_network_resources'].get('interfaces') if not interfaces_facts: return [] return interfaces_facts @@ -77,12 +84,6 @@ def set_config(self, existing_interfaces_facts): to the desired configuration """ want = self._module.params['config'] - for w in want: - if len(w['name'].split(' ')) > 1 and w['name'].split(' ')[0] == "preconfigure": - name = "preconfigure " + normalize_interface(w['name'].split(' ')[1]) - w.update({'name': name}) - else: - w.update({'name': normalize_interface(w['name'])}) have = existing_interfaces_facts resp = self.set_state(want, have) @@ -98,27 +99,20 @@ def set_state(self, want, have): to the desired configuration """ commands = [] - state = self._module.params['state'] + state = self._module.params['state'] if state == 'overridden': kwargs = {'want': want, 'have': have} commands = self._state_overridden(**kwargs) - else: - for w in want: - # get the interface type - name = w['name'] - if len(name.split(' ')) > 1 and name.split(' ')[0] == "preconfigure": - interface_type = get_interface_type(name.split(' ')[1]) - else: - interface_type = get_interface_type(name) - obj_in_have = search_obj_in_list(name, have) - kwargs = {'want': w, 'have': obj_in_have, 'type': interface_type} - if state == 'deleted': - commands.extend(Interfaces._state_deleted(**kwargs)) - elif state == 'merged': - commands.extend(Interfaces._state_merged(**kwargs)) - elif state == 'replaced': - commands.extend(Interfaces._state_replaced(**kwargs)) + elif state == 'deleted': + kwargs = {'want': want, 'have': have} + commands = self._state_deleted(**kwargs) + elif state == 'merged': + kwargs = {'want': want, 'have': have} + commands = self._state_merged(**kwargs) + elif state == 'replaced': + kwargs = {'want': want, 'have': have} + commands = self._state_replaced(**kwargs) return commands @@ -132,22 +126,20 @@ def _state_replaced(**kwargs): """ commands = [] want = kwargs['want'] - obj_in_have = kwargs['have'] - interface_type = kwargs['type'] - - if want['name']: - interface = 'interface ' + want['name'] - - if obj_in_have: - if interface_type.lower() == 'loopback': - commands.extend(Interfaces._state_merged(want, obj_in_have)) - elif interface_type.lower() == 'gigabitethernet' or interface_type.lower() == "preconfigure": - for item in Interfaces.params: - value = obj_in_have.get(item) - if value and want[item] != value and value != 'auto': - Interfaces._remove_command_from_interface(interface, item, commands) - kwargs = {'want': want, 'have': obj_in_have} - commands.extend(Interfaces._state_merged(**kwargs)) + have = kwargs['have'] + + for interface in want: + for each in have: + if each['name'] == interface['name']: + break + elif interface['name'] in each['name']: + break + else: + continue + kwargs = {'want': interface, 'have': each, } + commands.extend(Interfaces.clear_interface(**kwargs)) + kwargs = {'want': interface, 'have': each, 'commands': commands} + commands.extend(Interfaces.set_interface(**kwargs)) return commands @@ -161,56 +153,25 @@ def _state_overridden(**kwargs): """ commands = [] want = kwargs['want'] - obj_in_have = kwargs['have'] - interface_want = 'interface ' + want[0]['name'] - - for have in obj_in_have: - name = have['name'] - obj_in_want = search_obj_in_list(name, want) - if not obj_in_want: - interface_type = get_interface_type(name) - if interface_type.lower() == 'loopback': - commands.append('interface ' + name) - commands.append('no description') - elif interface_type.lower() == 'gigabitethernet' or interface_type.lower() == "preconfigure": - default = True - if have['enabled'] is True: - for k, v in iteritems(have): - if k in Interfaces.params: - if have[k] is not None: - default = False - break - else: - default = False - if default is False: - # Delete the configurable params by interface module - interface = 'interface ' + name - for each in Interfaces.params: - if interface not in commands: - commands.append(interface) - commands.append('no {0}'.format(each)) - elif name == want[0]['name']: - changed = False - # Delete the wanted interface to be replaced with provided values - for k, v in iteritems(have): - if obj_in_want[k] != have[k] and have[k] != "auto": - if interface_want not in commands: - changed = True - commands.append(interface_want) - commands.append('no {0}'.format(k)) - if not changed: - break + have = kwargs['have'] - if interface_want in commands: - # if there's change in interface_want then extend the commands - for w in want: - name = w['name'] - have = search_obj_in_list(name, obj_in_have) - kwargs = {'want': w, 'have': have} - commands.extend(Interfaces._state_merged(**kwargs)) - else: - # if there's no change in inteface_want then maintain idempotency - commands = [] + for each in have: + for interface in want: + if each['name'] == interface['name']: + break + elif interface['name'] in each['name']: + break + else: + # We didn't find a matching desired state, which means we can + # pretend we recieved an empty desired state. + interface = dict(name=each['name']) + kwargs = {'want': interface, 'have': each} + commands.extend(Interfaces.clear_interface(**kwargs)) + continue + kwargs = {'want': interface, 'have': each} + commands.extend(Interfaces.clear_interface(**kwargs)) + kwargs = {'want': interface, 'have': each, 'commands': commands} + commands.extend(Interfaces.set_interface(**kwargs)) return commands @@ -224,30 +185,18 @@ def _state_merged(**kwargs): """ commands = [] want = kwargs['want'] - obj_in_have = kwargs['have'] - name = want['name'] - enabled = want.get('enabled') - - if name: - interface = 'interface ' + name - if not obj_in_have: - commands.append(interface) - commands.append('no shutdown') if enabled else commands.append('shutdown') - - for item in Interfaces.params: - candidate = want.get(item) - if candidate: - commands.append(item + ' ' + str(candidate)) - else: - if enabled is True and enabled != obj_in_have.get('enabled'): - Interfaces._add_command_to_interface(interface, 'no shutdown', commands) - elif enabled is False and enabled != obj_in_have.get('enabled'): - Interfaces._add_command_to_interface(interface, 'shutdown', commands) - for item in Interfaces.params: - candidate = want.get(item) - if candidate and candidate != obj_in_have.get(item): - cmd = item + ' ' + str(candidate) - Interfaces._add_command_to_interface(interface, cmd, commands) + have = kwargs['have'] + + for interface in want: + for each in have: + if each['name'] == interface['name']: + break + elif interface['name'] in each['name']: + break + else: + continue + kwargs = {'want': interface, 'have': each} + commands.extend(Interfaces.set_interface(**kwargs)) return commands @@ -261,25 +210,19 @@ def _state_deleted(**kwargs): """ commands = [] want = kwargs['want'] - obj_in_have = kwargs['have'] - interface_type = kwargs['type'] - if not obj_in_have or interface_type == 'unknown': - return commands + have = kwargs['have'] - interface = 'interface ' + want['name'] - if 'description' in obj_in_have: - Interfaces._remove_command_from_interface(interface, 'description', commands) - if 'enabled' in obj_in_have and obj_in_have['enabled'] is False: - # if enable is False set enable as True which is the default behavior - Interfaces._remove_command_from_interface(interface, 'shutdown', commands) - - if interface_type.lower() == 'gigabitethernet': - if 'speed' in obj_in_have and obj_in_have['speed'] != 'auto': - Interfaces._remove_command_from_interface(interface, 'speed', commands) - if 'duplex' in obj_in_have and obj_in_have['duplex'] != 'auto': - Interfaces._remove_command_from_interface(interface, 'duplex', commands) - if 'mtu' in obj_in_have: - Interfaces._remove_command_from_interface(interface, 'mtu', commands) + for interface in want: + for each in have: + if each['name'] == interface['name']: + break + elif interface['name'] in each['name']: + break + else: + continue + interface = dict(name=interface['name']) + kwargs = {'want': interface, 'have': each} + commands.extend(Interfaces.clear_interface(**kwargs)) return commands @@ -295,3 +238,56 @@ def _add_command_to_interface(interface, cmd, commands): if interface not in commands: commands.insert(0, interface) commands.append(cmd) + + @staticmethod + def set_interface(**kwargs): + # Set the interface config based on the want and have config + commands = [] + want = kwargs['want'] + have = kwargs['have'] + clear_cmds = [] + + if kwargs.get('commands'): + clear_cmds = kwargs['commands'] + + interface = 'interface ' + want['name'] + + if want.get('enabled') and want.get('enabled') != have.get('enabled'): + Interfaces._add_command_to_interface(interface, 'no shutdown', commands) + elif not want.get('enabled') and want.get('enabled') != have.get('enabled'): + Interfaces._add_command_to_interface(interface, 'shutdown', commands) + for item in Interfaces.params: + cmd = 'no ' + item + candidate = want.get(item) + if candidate and (candidate != have.get(item) or cmd in clear_cmds): + cmd = item + ' ' + str(candidate) + Interfaces._add_command_to_interface(interface, cmd, commands) + + return commands + + @staticmethod + def clear_interface(**kwargs): + # Delete the interface config based on the want and have config + commands = [] + want = kwargs['want'] + have = kwargs['have'] + interface_type = get_interface_type(want['name']) + interface = 'interface ' + want['name'] + + if have.get('description') and want.get('description') != have.get('description'): + Interfaces._remove_command_from_interface(interface, 'description', commands) + if not have.get('enabled') and want.get('enabled') != have.get('enabled'): + # if enable is False set enable as True which is the default behavior + Interfaces._remove_command_from_interface(interface, 'shutdown', commands) + + if interface_type.lower() == 'gigabitethernet': + if have.get('speed') and have.get('speed') != 'auto' and want.get('speed') != have.get('speed'): + Interfaces._remove_command_from_interface(interface, 'speed', commands) + if have.get('duplex') and have.get('duplex') != 'auto' and want.get('duplex') != have.get('duplex'): + Interfaces._remove_command_from_interface(interface, 'duplex', commands) + if have.get('mtu') and want.get('mtu') != have.get('mtu'): + Interfaces._remove_command_from_interface(interface, 'mtu', commands) + + return commands + + From 501bc48dbd290718e6bb2b5faeaf2b76eda3332d Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Tue, 28 May 2019 15:21:21 +0530 Subject: [PATCH 37/49] adehere to new facts Signed-off-by: Sumit Jaiswal --- module_utils/iosxr/facts/base.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/module_utils/iosxr/facts/base.py b/module_utils/iosxr/facts/base.py index ab1c8d2..a0a7808 100644 --- a/module_utils/iosxr/facts/base.py +++ b/module_utils/iosxr/facts/base.py @@ -1,9 +1,9 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -# Copyright 2019 +# Copyright 2019 Red Hat Inc. # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) """ -The myos facts base class +The iosxr facts base class this contains methods common to all facts subsets """ @@ -14,10 +14,10 @@ class FactsBase(object): #pylint: disable=R0205 """ - The myos facts base class + The iosxr facts base class """ generated_spec = {} - ansible_facts = {'net_configuration': {}} + ansible_facts = {'ansible_network_resources': {}} def __init__(self, argspec, subspec=None, options=None): spec = deepcopy(argspec) From cf42cab88088be77f19d514cc9ca12c3867d6395 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Tue, 28 May 2019 15:21:21 +0530 Subject: [PATCH 38/49] adehere to new facts Signed-off-by: Sumit Jaiswal --- module_utils/iosxr/facts/facts.py | 88 +++++++++++++++++++++++-------- 1 file changed, 66 insertions(+), 22 deletions(-) diff --git a/module_utils/iosxr/facts/facts.py b/module_utils/iosxr/facts/facts.py index 3a26a8f..6634571 100644 --- a/module_utils/iosxr/facts/facts.py +++ b/module_utils/iosxr/facts/facts.py @@ -8,41 +8,41 @@ calls the appropriate facts gathering function """ +from ansible.module_utils.six import iteritems + from ansible.module_utils.iosxr.argspec.facts.facts import FactsArgs from ansible.module_utils.iosxr.facts.base import FactsBase from ansible.module_utils.iosxr.argspec.interfaces.interfaces import InterfacesArgs from ansible.module_utils.iosxr.facts.interfaces.interfaces import InterfacesFacts + +FACT_SUBSETS = {} + class Facts(FactsArgs, FactsBase): #pylint: disable=R0903 """ The fact class for iosxr """ - def get_facts(self, module, connection, gather_subset=['all']): - """ Collect the facts for myos - - :param module: The module instance - :param connection: The device connection - :param gather_subset: The facts subset to collect - :rtype: dict - :returns: the facts gathered - """ - 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() - if not gather_subset: - gather_subset = ['all'] + 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: @@ -58,15 +58,59 @@ 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']): + """ Collect the facts for iosxr + :param module: The module instance + :param connection: The device connection + :param gather_subset: The facts subset to collect + :param gather_network_resources: The resource subset to collect + :rtype: dict + :returns: the facts gathered + """ + warnings = [] + 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) + warnings.extend(inst.warnings) + + for key, value in iteritems(facts): + key = 'ansible_net_%s' % key + self.ansible_facts[key] = value + + if warnings: + return self.ansible_facts, warnings + else: + return self.ansible_facts - return self.ansible_facts @staticmethod - def _get_net_configuration_interfaces(module, connection): + def _get_interfaces(module, connection): return InterfacesFacts(InterfacesArgs.argument_spec, 'config', 'options').populate_facts(module, connection) From ba3770811ae420da07e31c69663ecbd8cd11f37a Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Tue, 28 May 2019 15:21:21 +0530 Subject: [PATCH 39/49] adehere to new facts Signed-off-by: Sumit Jaiswal --- module_utils/iosxr/facts/interfaces/interfaces.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module_utils/iosxr/facts/interfaces/interfaces.py b/module_utils/iosxr/facts/interfaces/interfaces.py index 371a7f5..4e69ebe 100644 --- a/module_utils/iosxr/facts/interfaces/interfaces.py +++ b/module_utils/iosxr/facts/interfaces/interfaces.py @@ -47,7 +47,7 @@ def populate_facts(self, module, connection, data=None): facts = {} if objs: facts['interfaces'] = objs - self.ansible_facts['net_configuration'].update(facts) + self.ansible_facts['ansible_network_resources'].update(facts) return self.ansible_facts def render_config(self, spec, conf): From d6266524029ac3b414c7817fee2323109b0a530b Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Tue, 28 May 2019 15:22:19 +0530 Subject: [PATCH 40/49] remove space Signed-off-by: Sumit Jaiswal --- module_utils/iosxr/config/interfaces/interfaces.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/module_utils/iosxr/config/interfaces/interfaces.py b/module_utils/iosxr/config/interfaces/interfaces.py index 3467a3d..a9e94de 100644 --- a/module_utils/iosxr/config/interfaces/interfaces.py +++ b/module_utils/iosxr/config/interfaces/interfaces.py @@ -289,5 +289,3 @@ def clear_interface(**kwargs): Interfaces._remove_command_from_interface(interface, 'mtu', commands) return commands - - From 1f45188d7491507cf22a5f4d13a6f34a611587ff Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Tue, 28 May 2019 15:29:42 +0530 Subject: [PATCH 41/49] review comment fix Signed-off-by: Sumit Jaiswal --- library/iosxr_interfaces.py | 120 +++++++++++++++++++++--------------- 1 file changed, 69 insertions(+), 51 deletions(-) diff --git a/library/iosxr_interfaces.py b/library/iosxr_interfaces.py index 5996e62..4012c12 100644 --- a/library/iosxr_interfaces.py +++ b/library/iosxr_interfaces.py @@ -28,64 +28,78 @@ """ from __future__ import absolute_import, division, print_function -from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils.iosxr.config.interfaces.interfaces import Interfaces +__metaclass__ = type + +GENERATOR_VERSION = '1.0' ANSIBLE_METADATA = {'metadata_version': '1.1', - 'status': [u'preview'], - 'supported_by': [u'Ansible Network']} + 'status': ['preview'], + 'supported_by': 'network'} +NETWORK_OS = "iosxr" +RESOURCE = "interfaces" +COPYRIGHT = "Copyright 2019 Red Hat" DOCUMENTATION = """ ---- -module: iosxr_interfaces -version_added: 2.9 -short_description: Manage Interface on Cisco IOSXR network devices. -description: Manage Interface on Cisco IOSXR network devices. -author: [u'Sumit Jaiswal (@justjais)'] -options: - config: - description: The provided configuration - suboptions: - name: + module: iosxr_interfaces + version_added: 2.9 + short_description: Manage interface attributes on Cisco IOS-XR network devices + description: This module manages the interface attributes on Cisco IOS-XR network devices. + author: Sumit Jaiswal (@justjais) + options: + config: + description: A dictionary of interface options + type: list + elements: dict + suboptions: + name: + description: + - Full name of the interface to configure in C(type + path) format. e.g. C(GigabitEthernet0/0/0/0) + type: str + required: True description: - - Name of interface, i.e. GigabitEthernet0/2, loopback999. - type: str - required: true + description: + - Interface description. + type: str + enable: + default: True + description: + - Administrative state of the interface. + - Set the value to C(True) to administratively enable the interface or C(False) to disable it. + type: bool + active: + default: active + description: + - Whether the interface is C(active) or C(preconfigured). Preconfiguration allows you to configure modular + services cards before they are inserted into the router. When the cards are inserted, they are instantly + configured. Active cards are the ones already inserted. + type: str + choices: ['active', 'preconfigure'] + speed: + description: + - Configure the speed for an interface. Default is auto-negotiation when not configured. + type: str + choices: ['10', '100', '1000'] + mtu: + description: + - Sets the MTU value for the interface. Range is between 64 and 65535. Applicable for Ethernet interfaces only. + type: str + duplex: + default: auto + description: + - Configures the interface duplex mode. Default is auto-negotiation when not configured. + type: str + choices: ['full', 'half'] + state: + choices: + - merged + - replaced + - overridden + - deleted + default: merged description: - description: - - Description of Interface. - type: str - enabled: - description: - - Interface link status. - type: bool - default: true - speed: - description: - - Interface link speed. Applicable for ethernet interface only. - type: str - mtu: - description: - - Maximum size of transmit packet. Must be an number between 64 and 9600. - Applicable for Ethernet interface only. - type: str - duplex: - description: - - Interface link status. Applicable for ethernet interface only. - type: str - default: auto - choices: ['full', 'half'] - state: - choices: - - merged - - replaced - - overridden - - deleted - default: merged - description: - - The state the configuration should be left in - type: str + - The state the configuration should be left in + type: str """ EXAMPLES = """ @@ -339,6 +353,10 @@ """ +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.iosxr.config.interfaces.interfaces import Interfaces + + def main(): """ Main entry point for module execution From 6292255834ea734b1d1bfa67e213545f74c56e59 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Thu, 30 May 2019 16:50:44 +0530 Subject: [PATCH 42/49] iosxr interface tests Signed-off-by: Sumit Jaiswal --- tests/modules/interfaces/deleted.yaml | 34 +++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 tests/modules/interfaces/deleted.yaml diff --git a/tests/modules/interfaces/deleted.yaml b/tests/modules/interfaces/deleted.yaml new file mode 100644 index 0000000..e49d2ca --- /dev/null +++ b/tests/modules/interfaces/deleted.yaml @@ -0,0 +1,34 @@ +--- +- debug: + msg: "START iosxr_interfaces Deleted integration tests on connection={{ ansible_connection }}" + +- include_tasks: reset_config.yaml + +- name: Delete attributes of given interfaces (Note: This won't delete the interface itself) + iosxr_interfaces: + config: + - name: GigabitEthernet0/0/0/1 + - name: GigabitEthernet0/0/0/2 + state: deleted + +- iosxr_facts: + gather_subset: net_configuration_interfaces + +- set_fact: + expected_output: + - name: GigabitEthernet0/0/0/1 + duplex: auto + enable: True + - name: GigabitEthernet0/0/0/2 + duplex: auto + enable: True + - name: GigabitEthernet0/0/0/3 + description: 'Configured by Ansible-Network' + duplex: auto + enable: True + +- assert: + that: + - "ansible_facts.net_configuration.interfaces == expected_output" + +- include_tasks: reset_config.yaml From 3a10b45720ae34e89121b42ab5a6c954f22713c9 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Thu, 30 May 2019 16:50:44 +0530 Subject: [PATCH 43/49] iosxr interface tests Signed-off-by: Sumit Jaiswal --- tests/modules/interfaces/main.yaml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 tests/modules/interfaces/main.yaml diff --git a/tests/modules/interfaces/main.yaml b/tests/modules/interfaces/main.yaml new file mode 100644 index 0000000..496dec3 --- /dev/null +++ b/tests/modules/interfaces/main.yaml @@ -0,0 +1,15 @@ +--- +- hosts: iosxr + gather_subset: + - net_configuration_interfaces + + tasks: + - import_role: + name: ./network + + - include_tasks: "{{ item }}.yaml" + loop: + - merged + - replaced + - overridden + - delete From 38f8396ac1eaf619ac2f00b308bd40ee437b02cf Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Thu, 30 May 2019 16:50:44 +0530 Subject: [PATCH 44/49] iosxr interface tests Signed-off-by: Sumit Jaiswal --- tests/modules/interfaces/merged.yaml | 44 ++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 tests/modules/interfaces/merged.yaml diff --git a/tests/modules/interfaces/merged.yaml b/tests/modules/interfaces/merged.yaml new file mode 100644 index 0000000..ed8bee7 --- /dev/null +++ b/tests/modules/interfaces/merged.yaml @@ -0,0 +1,44 @@ +--- +- debug: + msg: "START iosxr_interfaces Merged integration tests on connection={{ ansible_connection }}" + +- include_tasks: reset_config.yaml + +- name: Merge provided configuration with device configuration + iosxr_interfaces: + config: + - name: GigabitEthernet0/0/0/1 + enable: True + - name: GigabitEthernet0/0/0/2 + description: 'Configured by Ansible-Network' + speed: 10 + duplex: half + enable: False + state: merged + +- iosxr_facts: + gather_subset: net_configuration_interfaces + +- set_fact: + expected_output: + - name: GigabitEthernet0/0/0/1 + description: Interface 1 + speed: 100 + duplex: auto + enable: True + - name: GigabitEthernet0/0/0/2 + description: 'Configured by Ansible-Network' + speed: 10 + duplex: half + enable: False + mtu: 1000 + - name: GigabitEthernet0/0/0/3 + description: 'Configured by Ansible-Network' + duplex: auto + enable: True + +- assert: + that: + - "ansible_facts.net_configuration.interfaces == expected_output" + +- include_tasks: reset_config.yaml From 329ead6f8ea5a921ec2395c0af06b529f2b656e3 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Thu, 30 May 2019 16:50:44 +0530 Subject: [PATCH 45/49] iosxr interface tests Signed-off-by: Sumit Jaiswal --- tests/modules/interfaces/overridden.yaml | 37 ++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 tests/modules/interfaces/overridden.yaml diff --git a/tests/modules/interfaces/overridden.yaml b/tests/modules/interfaces/overridden.yaml new file mode 100644 index 0000000..e8e2b76 --- /dev/null +++ b/tests/modules/interfaces/overridden.yaml @@ -0,0 +1,37 @@ +--- +- debug: + msg: "START iosxr_interfaces Overridden integration tests on connection={{ ansible_connection }}" + +- include_tasks: reset_config.yaml + +- name: Override device configuration of all interfaces with provided configuration + iosxr_interfaces: + config: + - name: GigabitEthernet0/0/0/1 + enable: True + - name: GigabitEthernet0/0/0/2 + description: 'Configured by Ansible-Network' + enable: False + state: overridden + +- iosxr_facts: + gather_subset: net_configuration_interfaces + +- set_fact: + expected_output: + - name: GigabitEthernet0/0/0/2 + duplex: auto + enable: True + - name: GigabitEthernet0/0/0/2 + description: 'Configured by Ansible-Network' + duplex: auto + enable: False + - name: GigabitEthernet0/0/0/3 + duplex: auto + enable: True + +- assert: + that: + - "ansible_facts.net_configuration.interfaces == expected_output" + +- include_tasks: reset_config.yaml From 83843d5b6797787dd76bc879121d147b05c9bfe1 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Thu, 30 May 2019 16:50:44 +0530 Subject: [PATCH 46/49] iosxr interface tests Signed-off-by: Sumit Jaiswal --- tests/modules/interfaces/replaced.yaml | 38 ++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 tests/modules/interfaces/replaced.yaml diff --git a/tests/modules/interfaces/replaced.yaml b/tests/modules/interfaces/replaced.yaml new file mode 100644 index 0000000..8a5e74d --- /dev/null +++ b/tests/modules/interfaces/replaced.yaml @@ -0,0 +1,38 @@ +--- +- debug: + msg: "START iosxr_interfaces Replaced integration tests on connection={{ ansible_connection }}" + +- include_tasks: reset_config.yaml + +- name: Replaces device configuration of listed interfaces with provided configuration + iosxr_interfaces: + config: + - name: GigabitEthernet0/0/0/1 + enable: True + - name: GigabitEthernet0/0/0/2 + description: 'Configured by Ansible-Network' + enable: False + state: replaced + +- iosxr_facts: + gather_subset: net_configuration_interfaces + +- set_fact: + expected_output: + - name: GigabitEthernet0/0/0/1 + duplex: auto + enable: True + - name: GigabitEthernet0/0/0/2 + description: 'Configured by Ansible-Network' + duplex: auto + enable: False + - name: GigabitEthernet0/0/0/3 + description: 'Configured by Ansible-Network' + duplex: auto + enable: True + +- assert: + that: + - "ansible_facts.net_configuration.interfaces == expected_output" + +- include_tasks: reset_config.yaml From 20d32d34e594385b653e5bdb1efb2b76ace4fbc5 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Thu, 30 May 2019 16:50:44 +0530 Subject: [PATCH 47/49] iosxr interface tests Signed-off-by: Sumit Jaiswal --- tests/modules/interfaces/reset_config.yaml | 40 ++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 tests/modules/interfaces/reset_config.yaml diff --git a/tests/modules/interfaces/reset_config.yaml b/tests/modules/interfaces/reset_config.yaml new file mode 100644 index 0000000..d91e0a0 --- /dev/null +++ b/tests/modules/interfaces/reset_config.yaml @@ -0,0 +1,40 @@ +--- +- name: Reset initial config + cli_config: + config: | + interface GigabitEthernet0/0/0/1 + description Ansible-Network configured + shutdown + duplex auto + speed 100 + interface GigabitEthernet0/0/0/2 + duplex auto + speed auto + mtu 1000 + interface GigabitEthernet0/0/0/3 + description Ansible-Network configured + duplex auto + speed auto + +- iosxr_facts: + gather_subset: net_configuration_interfaces + +- set_fact: + expected_output: + - name: GigabitEthernet0/0/0/1 + description: 'Ansible-Network configured' + speed: 100 + duplex: auto + enable: False + - name: GigabitEthernet0/0/0/2 + duplex: auto + enable: True + mtu: 1000 + - name: GigabitEthernet0/0/0/3 + description: 'Configured by Ansible-Network' + duplex: auto + enable: True + +- assert: + that: + - "ansible_facts.net_configuration.interfaces == expected_output" \ No newline at end of file From f4cd3e28817b757862bf762e1efb314eda03237b Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Thu, 30 May 2019 16:52:13 +0530 Subject: [PATCH 48/49] iosxr interface tests Signed-off-by: Sumit Jaiswal --- tests/modules/interfaces/main.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/modules/interfaces/main.yaml b/tests/modules/interfaces/main.yaml index 496dec3..506c655 100644 --- a/tests/modules/interfaces/main.yaml +++ b/tests/modules/interfaces/main.yaml @@ -5,7 +5,7 @@ tasks: - import_role: - name: ./network + name: ~/collections/network - include_tasks: "{{ item }}.yaml" loop: From b062363ff4920da6cef6bfb7f810f315789f0d5f Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Fri, 2 Aug 2019 00:06:08 +0530 Subject: [PATCH 49/49] adhered to new changes --- library/iosxr_facts.py | 166 +++++++++++++---- library/iosxr_interfaces.py | 72 ++++---- module_utils/iosxr/argspec/facts/facts.py | 24 --- .../iosxr/argspec/interfaces/interfaces.py | 48 ----- module_utils/iosxr/config/base.py | 24 --- module_utils/iosxr/facts/base.py | 110 ----------- module_utils/iosxr/facts/facts.py | 116 ------------ .../iosxr/facts/interfaces/interfaces.py | 79 -------- module_utils/network/argspec/__init__.py | 0 module_utils/network/argspec/base.py | 12 -- module_utils/{ => network}/iosxr/__init__.py | 0 .../{ => network}/iosxr/argspec/__init__.py | 0 .../iosxr/argspec/facts/__init__.py | 0 .../network/iosxr/argspec/facts/facts.py | 29 +++ .../iosxr/argspec/interfaces/__init__.py | 0 .../iosxr/argspec/interfaces/interfaces.py | 47 +++++ .../{ => network}/iosxr/config/__init__.py | 0 .../iosxr/config/interfaces/__init__.py | 0 .../iosxr/config/interfaces/interfaces.py | 173 ++++++++---------- .../{ => network}/iosxr/facts/__init__.py | 0 module_utils/network/iosxr/facts/facts.py | 59 ++++++ .../iosxr/facts/interfaces/__init__.py | 0 .../iosxr/facts/interfaces/interfaces.py | 102 +++++++++++ .../{ => network}/iosxr/utils/__init__.py | 0 .../{ => network}/iosxr/utils/utils.py | 55 ++++++ 25 files changed, 533 insertions(+), 583 deletions(-) delete mode 100644 module_utils/iosxr/argspec/facts/facts.py delete mode 100644 module_utils/iosxr/argspec/interfaces/interfaces.py delete mode 100644 module_utils/iosxr/config/base.py delete mode 100644 module_utils/iosxr/facts/base.py delete mode 100644 module_utils/iosxr/facts/facts.py delete mode 100644 module_utils/iosxr/facts/interfaces/interfaces.py delete mode 100644 module_utils/network/argspec/__init__.py delete mode 100644 module_utils/network/argspec/base.py rename module_utils/{ => network}/iosxr/__init__.py (100%) rename module_utils/{ => network}/iosxr/argspec/__init__.py (100%) rename module_utils/{ => network}/iosxr/argspec/facts/__init__.py (100%) create mode 100644 module_utils/network/iosxr/argspec/facts/facts.py rename module_utils/{ => network}/iosxr/argspec/interfaces/__init__.py (100%) create mode 100644 module_utils/network/iosxr/argspec/interfaces/interfaces.py rename module_utils/{ => network}/iosxr/config/__init__.py (100%) rename module_utils/{ => network}/iosxr/config/interfaces/__init__.py (100%) rename module_utils/{ => network}/iosxr/config/interfaces/interfaces.py (61%) rename module_utils/{ => network}/iosxr/facts/__init__.py (100%) create mode 100644 module_utils/network/iosxr/facts/facts.py rename module_utils/{ => network}/iosxr/facts/interfaces/__init__.py (100%) create mode 100644 module_utils/network/iosxr/facts/interfaces/interfaces.py rename module_utils/{ => network}/iosxr/utils/__init__.py (100%) rename module_utils/{ => network}/iosxr/utils/utils.py (62%) diff --git a/library/iosxr_facts.py b/library/iosxr_facts.py index c5ac399..190e7cb 100644 --- a/library/iosxr_facts.py +++ b/library/iosxr_facts.py @@ -1,56 +1,57 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -# Copyright 2019 Red Hat Inc. -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# Copyright 2019 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) """ The module file for iosxr_facts """ from __future__ import absolute_import, division, print_function +__metaclass__ = type + ANSIBLE_METADATA = {'metadata_version': '1.1', - 'status': ['preview'], + 'status': [u'preview'], 'supported_by': 'network'} -NETWORK_OS = "iosxr" -RESOURCE = "facts" -COPYRIGHT = "Copyright 2019 Red Hat" DOCUMENTATION = """ --- module: iosxr_facts -version_added: 2.9 -short_description: Get facts about Cisco IOSXR devices. +version_added: 2.2 +short_description: Get facts about iosxr devices. +extends_documentation_fragment: iosxr description: - Collects facts from network devices running the iosxr operating system. This module places the facts gathered in the fact tree keyed by the respective resource name. The facts module will always collect a base set of facts from the device and can enable or disable collection of additional facts. -author: [u'Sumit Jaiswal (@justjais)'] -notes: - - Tested against IOSXRv Version 6.1.3 on VIRL +author: + - Ricardo Carrillo Cruz (@rcarrillocruz) + - Nilashish Chakraborty (@Nilashishc) options: gather_subset: description: - When supplied, this argument will restrict the facts collected - 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 + to a given subset. Possible values for this argument include + all, hardware, config, 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: 'all' - version_added: "2.2" + default: '!config' 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. + all and the resources like interfaces, lacp 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 + choices: ['all', 'lacp'] version_added: "2.9" """ @@ -59,31 +60,117 @@ - iosxr_facts: gather_subset: all gather_network_resources: all -# Collect only the iosxr facts + +# Collect only the config and default facts +- iosxr_facts: + gather_subset: + - config + +# Do not collect hardware facts - iosxr_facts: gather_subset: - - !all - - !min + - "!hardware" + +# Collect only the lag_interfaces facts +- iosxr_facts: + gather_subset: + - "!all" + - "!min" gather_network_resources: - - iosxr -# Do not collect iosxr facts + - lacp + +# Do not collect lag_interfaces facts - iosxr_facts: gather_network_resources: - - "!iosxr" -# Collect iosxr and minimal default facts + - "!lacp" + +# Collect lag_interfaces and minimal default facts - iosxr_facts: gather_subset: min - gather_network_resources: iosxr + gather_network_resources: lacp """ RETURN = """ -See the respective resource module parameters for the tree. -""" +ansible_net_gather_subset: + description: The list of fact subsets collected from the device + returned: always + type: list + +# default +ansible_net_version: + description: The operating system version running on the remote device + returned: always + type: str +ansible_net_hostname: + description: The configured hostname of the device + returned: always + type: str +ansible_net_image: + description: The image file the device is running + returned: always + type: str +ansible_net_api: + description: The name of the transport + returned: always + type: str +ansible_net_python_version: + description: The Python version Ansible controller is using + returned: always + type: str +ansible_net_model: + description: The model name returned from the device + returned: always + type: str + +# hardware +ansible_net_filesystems: + description: All file system names available on the device + returned: when hardware is configured + type: list +ansible_net_memfree_mb: + description: The available free memory on the remote device in Mb + returned: when hardware is configured + type: int +ansible_net_memtotal_mb: + description: The total memory on the remote device in Mb + returned: when hardware is configured + type: int + +# config +ansible_net_config: + description: The current active config from the device + returned: when config is configured + type: str +# interfaces +ansible_net_all_ipv4_addresses: + description: All IPv4 addresses configured on the device + returned: when interfaces is configured + type: list +ansible_net_all_ipv6_addresses: + description: All IPv6 addresses configured on the device + returned: when interfaces is configured + type: list +ansible_net_interfaces: + description: A hash of all interfaces running on the system + returned: when interfaces is configured + type: dict +ansible_net_neighbors: + description: The list of LLDP neighbors from the remote device + returned: when interfaces is configured + type: dict + +# network resources +ansible_net_gather_network_resources: + description: The list of fact resource subsets collected from the device + returned: always + type: list +""" from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils.connection import Connection -from ansible.module_utils.iosxr.facts.facts import Facts +from ansible.module_utils.network.iosxr.iosxr import iosxr_argument_spec +from ansible.module_utils.network.iosxr.argspec.facts.facts import FactsArgs +from ansible.module_utils.network.iosxr.facts.facts import Facts def main(): @@ -92,22 +179,21 @@ def main(): :returns: ansible_facts """ - module = AnsibleModule(argument_spec=Facts.argument_spec, + spec = FactsArgs.argument_spec + spec.update(iosxr_argument_spec) + + module = AnsibleModule(argument_spec=spec, supports_check_mode=True) - warnings = ['default value for `gather_subset` will be changed to `min` from `!config` v2.11 onwards'] + 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'] - result = Facts().get_facts(module, connection, gather_subset, gather_network_resources) + result = Facts(module).get_facts() - try: - ansible_facts, warning = result - warnings.extend(warning) - except (TypeError, KeyError): - ansible_facts = result + ansible_facts, additional_warnings = result + warnings.extend(additional_warnings) module.exit_json(ansible_facts=ansible_facts, warnings=warnings) + if __name__ == '__main__': main() diff --git a/library/iosxr_interfaces.py b/library/iosxr_interfaces.py index 4012c12..284d242 100644 --- a/library/iosxr_interfaces.py +++ b/library/iosxr_interfaces.py @@ -1,30 +1,29 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -# Copyright 2019 Red Hat Inc. -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# Copyright 2019 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) -############################################## -################# WARNING #################### -############################################## -### -### This file is auto generated by the resource -### module builder playbook. -### -### Do not edit this file manually. -### -### Changes to this file will be over written -### by the resource module builder. -### -### Changes should be made in the model used to -### generate this file or in the resource module -### builder template. -### -############################################## -############################################## -############################################## +############################################# +# WARNING # +############################################# +# +# This file is auto generated by the resource +# module builder playbook. +# +# Do not edit this file manually. +# +# Changes to this file will be over written +# by the resource module builder. +# +# Changes should be made in the model used to +# generate this file or in the resource module +# builder template. +# +############################################# """ -The module file for iosxr_interfaces +The module file for ios_interfaces """ from __future__ import absolute_import, division, print_function @@ -61,7 +60,7 @@ description: - Interface description. type: str - enable: + enabled: default: True description: - Administrative state of the interface. @@ -85,7 +84,6 @@ - Sets the MTU value for the interface. Range is between 64 and 65535. Applicable for Ethernet interfaces only. type: str duplex: - default: auto description: - Configures the interface duplex mode. Default is auto-negotiation when not configured. type: str @@ -104,7 +102,6 @@ EXAMPLES = """ --- - # Using merged # Before state: @@ -165,7 +162,7 @@ # Using replaced # Before state: -# ------------- +# ------------ # # viosxr#show running-config interface # interface GigabitEthernet0/0/0/1 @@ -198,7 +195,7 @@ operation: replaced # After state: -# ------------- +# ------------ # # viosxr#show running-config interface # interface GigabitEthernet0/0/0/1 @@ -224,7 +221,7 @@ # Using overridden # Before state: -# ------------- +# ------------ # # viosxr#show running-config interface # interface GigabitEthernet0/0/0/1 @@ -260,7 +257,7 @@ operation: overridden # After state: -# ------------- +# ------------ # # viosxr#show running-config interface # interface GigabitEthernet0/0/0/1 @@ -286,7 +283,7 @@ # Using deleted # Before state: -# ------------- +# ------------ # # viosxr#show running-config interface # interface GigabitEthernet0/0/0/1 @@ -317,7 +314,7 @@ operation: deleted # After state: -# ------------- +# ------------ # # viosxr#show running-config interface # interface GigabitEthernet0/0/0/1 @@ -333,42 +330,43 @@ # ipv4 address 10.10.0.2 255.255.255.0 # dot1q native vlan 1021 # ! - """ RETURN = """ before: description: The configuration prior to the model invocation returned: always + type: list sample: The configuration returned will alwys be in the same format of the paramters above. after: description: The resulting configuration model invocation returned: when changed + type: list sample: The configuration returned will alwys be in the same format of the paramters above. commands: description: The set of commands pushed to the remote device returned: always type: list - sample: ['command 1', 'command 2', 'command 3'] + sample: ['interface GigabitEthernet0/0/0/2', 'description: Configured by Ansible', 'shutdown'] """ - from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils.iosxr.config.interfaces.interfaces import Interfaces +from ansible.module_utils.network.iosxr.argspec.interfaces.interfaces import InterfacesArgs +from ansible.module_utils.network.iosxr.config.interfaces.interfaces import Interfaces def main(): """ Main entry point for module execution - :returns: the result form module invocation """ - module = AnsibleModule(argument_spec=Interfaces.argument_spec, + module = AnsibleModule(argument_spec=InterfacesArgs.argument_spec, supports_check_mode=True) result = Interfaces(module).execute_module() module.exit_json(**result) + if __name__ == '__main__': main() diff --git a/module_utils/iosxr/argspec/facts/facts.py b/module_utils/iosxr/argspec/facts/facts.py deleted file mode 100644 index 60bbb33..0000000 --- a/module_utils/iosxr/argspec/facts/facts.py +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- -# Copyright 2019 Red Hat Inc. -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) -""" -The arg spec for the iosxr facts module. -""" - -class FactsArgs(object): #pylint: disable=R0903 - """ The arg spec for the iosxr facts module - """ - - def __init__(self, **kwargs): - pass - - choices = [ - 'all', - 'interfaces', - ] - - argument_spec = { - 'gather_subset': dict(default=['all'], choices=choices, type='list'), - 'gather_network_resources': dict(default=['all'], choices=choices, type='list'), - } diff --git a/module_utils/iosxr/argspec/interfaces/interfaces.py b/module_utils/iosxr/argspec/interfaces/interfaces.py deleted file mode 100644 index 023a2a5..0000000 --- a/module_utils/iosxr/argspec/interfaces/interfaces.py +++ /dev/null @@ -1,48 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- -# Copyright 2019 Red Hat Inc. -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) - -############################################# -################# WARNING #################### -############################################## -### -### This file is auto generated by the resource -### module builder playbook. -### -### Do not edit this file manually. -### -### Changes to this file will be over written -### by the resource module builder. -### -### Changes should be made in the model used to -### generate this file or in the resource module -### builder template. -### -############################################## -############################################## -############################################## -""" -The arg spec for the iosxr_interfaces module -""" - -class InterfacesArgs(object): #pylint: disable=R0903 - """The arg spec for the iosxr_interfaces module - """ - - def __init__(self, **kwargs): - pass - - config_spec = { - 'name': dict(type='str', required=True), - 'description': dict(), - 'enabled': dict(default=True, type=bool), - 'speed': dict(type='str'), - 'mtu': dict(type='str'), - 'duplex': dict(choices=['full', 'half']), - } - - argument_spec = { - 'state': dict(default='merged', choices=['merged', 'replaced', 'overridden', 'deleted']), - 'config': dict(type='list', elements='dict', options=config_spec) - } \ No newline at end of file diff --git a/module_utils/iosxr/config/base.py b/module_utils/iosxr/config/base.py deleted file mode 100644 index 7ef97ea..0000000 --- a/module_utils/iosxr/config/base.py +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- -# Copyright 2019 Red Hat Inc. -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) -""" -The base class for all iosxr resource modules -""" - -from ansible.module_utils.connection import Connection - -class ConfigBase(object): #pylint: disable=R0205,R0903 - """ The base class for all iosxr resource modules - """ - _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) #pylint: disable=W0212 - return self._connection diff --git a/module_utils/iosxr/facts/base.py b/module_utils/iosxr/facts/base.py deleted file mode 100644 index a0a7808..0000000 --- a/module_utils/iosxr/facts/base.py +++ /dev/null @@ -1,110 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- -# Copyright 2019 Red Hat Inc. -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) -""" -The iosxr facts base class -this contains methods common to all facts subsets -""" - -import re -from copy import deepcopy -from ansible.module_utils.six import iteritems - - -class FactsBase(object): #pylint: disable=R0205 - """ - The iosxr facts base class - """ - generated_spec = {} - ansible_facts = {'ansible_network_resources': {}} - - def __init__(self, argspec, subspec=None, options=None): - spec = deepcopy(argspec) - if subspec: - if options: - facts_argument_spec = spec[subspec][options] - else: - facts_argument_spec = spec[subspec] - else: - facts_argument_spec = spec - - self.generated_spec = self.generate_dict(facts_argument_spec) - - @staticmethod - def generate_dict(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 - - for key, val in iteritems(spec): - if 'default' in val: - dct = {key: val['default']} - else: - dct = {key: None} - obj.update(dct) - - return obj - - @staticmethod - def parse_conf_arg(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: - result = match.group(1).strip() - else: - result = None - return result - - @staticmethod - def parse_conf_cmd_arg(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 - if res2 is not None: - match = re.search(r'\n\s+no %s(\n|$)' % cmd, cfg) - if match: - return res2 - return None - - @staticmethod - def generate_final_config(cfg_dict): - """ - Generate final config dictionary - - :param cfg_dict: A dictionary parsed in the facts system - :rtype: A dictionary - :returns: A dictionary by eliminating keys that have null values - """ - final_cfg = {} - if not cfg_dict: - return final_cfg - - for key, val in iteritems(cfg_dict): - if val is not None: - final_cfg.update({key:val}) - return final_cfg diff --git a/module_utils/iosxr/facts/facts.py b/module_utils/iosxr/facts/facts.py deleted file mode 100644 index 6634571..0000000 --- a/module_utils/iosxr/facts/facts.py +++ /dev/null @@ -1,116 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- -# Copyright 2019 Red Hat Inc. -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) -""" -The facts class for iosxr -this file validates each subset of facts and selectively -calls the appropriate facts gathering function -""" - -from ansible.module_utils.six import iteritems - -from ansible.module_utils.iosxr.argspec.facts.facts import FactsArgs -from ansible.module_utils.iosxr.facts.base import FactsBase -from ansible.module_utils.iosxr.argspec.interfaces.interfaces import InterfacesArgs -from ansible.module_utils.iosxr.facts.interfaces.interfaces import InterfacesFacts - - -FACT_SUBSETS = {} - -class Facts(FactsArgs, FactsBase): #pylint: disable=R0903 - """ The fact class for iosxr - """ - - 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 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 - minimal_gather_subset) - continue - exclude = True - else: - exclude = False - - if subset not in 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(valid_subsets) - runable_subsets.difference_update(exclude_subsets) - - return runable_subsets - - def get_facts(self, module, connection, gather_subset=['!config'], gather_network_resources=['all']): - """ Collect the facts for iosxr - :param module: The module instance - :param connection: The device connection - :param gather_subset: The facts subset to collect - :param gather_network_resources: The resource subset to collect - :rtype: dict - :returns: the facts gathered - """ - warnings = [] - 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) - warnings.extend(inst.warnings) - - for key, value in iteritems(facts): - key = 'ansible_net_%s' % key - self.ansible_facts[key] = value - - if warnings: - return self.ansible_facts, warnings - else: - return self.ansible_facts - - - @staticmethod - def _get_interfaces(module, connection): - return InterfacesFacts(InterfacesArgs.argument_spec, 'config', 'options').populate_facts(module, connection) diff --git a/module_utils/iosxr/facts/interfaces/interfaces.py b/module_utils/iosxr/facts/interfaces/interfaces.py deleted file mode 100644 index 4e69ebe..0000000 --- a/module_utils/iosxr/facts/interfaces/interfaces.py +++ /dev/null @@ -1,79 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- -# Copyright 2019 Red Hat Inc. -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) -""" -The iosxr interfaces fact class -It is in this file the configuration is collected from the device -for a given resource, parsed, and the facts tree is populated -based on the configuration. -""" - -import re -from copy import deepcopy - -from ansible.module_utils.iosxr.facts.base import FactsBase -from ansible.module_utils.iosxr.utils.utils import get_interface_type, normalize_interface - - -class InterfacesFacts(FactsBase): - """ The iosxr interfaces fact class - """ - - def populate_facts(self, module, connection, data=None): - """ Populate the facts for interfaces - - :param module: the module instance - :param connection: the device connection - :param data: previously collected conf - :rtype: dictionary - :returns: facts - """ - if module: #just for linting purposes - pass - if connection: #just for linting purposes - pass - objs = [] - if not data: - data = connection.get('show running-config interface') - - # operate on a collection of resource x - config = 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['interfaces'] = objs - self.ansible_facts['ansible_network_resources'].update(facts) - return self.ansible_facts - - def render_config(self, spec, conf): - """ - Render config as dictionary structure and delete keys from spec for null values - - :param spec: The facts tree, generated from the argspec - :param conf: The configuration - :rtype: dictionary - :returns: The generated config - """ - - config = deepcopy(spec) - match = re.search(r'^(\S+)', conf) - if match.group(1).lower() == "preconfigure": - match = re.search(r'^(\S+ \S+)', conf) - intf = match.group(1) - if get_interface_type(intf) == 'unknown': - return {} - # populate the facts from the configuration - 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') - enabled = self.parse_conf_cmd_arg(conf, 'shutdown', False) - config['enabled'] = enabled if enabled is not None else config['enabled'] - - return self.generate_final_config(config) 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 b7a599d..0000000 --- a/module_utils/network/argspec/base.py +++ /dev/null @@ -1,12 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- -# Copyright 2019 Red Hat Inc. -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) -""" The argspec base class -""" - -class ArgspecBase(object): - """ The argspec base class - """ - def __init__(self, **kwargs): - pass diff --git a/module_utils/iosxr/__init__.py b/module_utils/network/iosxr/__init__.py similarity index 100% rename from module_utils/iosxr/__init__.py rename to module_utils/network/iosxr/__init__.py diff --git a/module_utils/iosxr/argspec/__init__.py b/module_utils/network/iosxr/argspec/__init__.py similarity index 100% rename from module_utils/iosxr/argspec/__init__.py rename to module_utils/network/iosxr/argspec/__init__.py diff --git a/module_utils/iosxr/argspec/facts/__init__.py b/module_utils/network/iosxr/argspec/facts/__init__.py similarity index 100% rename from module_utils/iosxr/argspec/facts/__init__.py rename to module_utils/network/iosxr/argspec/facts/__init__.py diff --git a/module_utils/network/iosxr/argspec/facts/facts.py b/module_utils/network/iosxr/argspec/facts/facts.py new file mode 100644 index 0000000..4689f3a --- /dev/null +++ b/module_utils/network/iosxr/argspec/facts/facts.py @@ -0,0 +1,29 @@ +# +# -*- coding: utf-8 -*- +# Copyright 2019 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +""" +The arg spec for the iosxr facts module. +""" + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +class FactsArgs(object): # pylint: disable=R0903 + """ The arg spec for the iosxr facts module + """ + + def __init__(self, **kwargs): + pass + + choices = [ + 'all', + 'interfaces' + ] + + argument_spec = { + 'gather_subset': dict(default=['!config'], type='list'), + 'gather_network_resources': dict(choices=choices, type='list'), + } diff --git a/module_utils/iosxr/argspec/interfaces/__init__.py b/module_utils/network/iosxr/argspec/interfaces/__init__.py similarity index 100% rename from module_utils/iosxr/argspec/interfaces/__init__.py rename to module_utils/network/iosxr/argspec/interfaces/__init__.py diff --git a/module_utils/network/iosxr/argspec/interfaces/interfaces.py b/module_utils/network/iosxr/argspec/interfaces/interfaces.py new file mode 100644 index 0000000..11e0646 --- /dev/null +++ b/module_utils/network/iosxr/argspec/interfaces/interfaces.py @@ -0,0 +1,47 @@ +# +# -*- coding: utf-8 -*- +# Copyright 2019 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +############################################# +# WARNING # +############################################# +# +# This file is auto generated by the resource +# module builder playbook. +# +# Do not edit this file manually. +# +# Changes to this file will be over written +# by the resource module builder. +# +# Changes should be made in the model used to +# generate this file or in the resource module +# builder template. +# +############################################# +""" +The arg spec for the ios_interfaces module +""" + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +class InterfacesArgs(object): + + def __init__(self, **kwargs): + pass + + argument_spec = {'config': {'elements': 'dict', + 'options': {'name': {'type': 'str', 'required': True}, + 'description': {'type': 'str'}, + 'enabled': {'default': True, 'type': 'bool'}, + 'speed': {'type': 'int', 'choices': [10, 100, 1000]}, + 'mtu': {'type': 'int'}, + 'duplex': {'type': 'str', 'choices': ['full', 'half']}}, + 'type': 'list'}, + 'state': {'choices': ['merged', 'replaced', 'overridden', 'deleted'], + 'default': 'merged', + 'type': 'str'}} diff --git a/module_utils/iosxr/config/__init__.py b/module_utils/network/iosxr/config/__init__.py similarity index 100% rename from module_utils/iosxr/config/__init__.py rename to module_utils/network/iosxr/config/__init__.py diff --git a/module_utils/iosxr/config/interfaces/__init__.py b/module_utils/network/iosxr/config/interfaces/__init__.py similarity index 100% rename from module_utils/iosxr/config/interfaces/__init__.py rename to module_utils/network/iosxr/config/interfaces/__init__.py diff --git a/module_utils/iosxr/config/interfaces/interfaces.py b/module_utils/network/iosxr/config/interfaces/interfaces.py similarity index 61% rename from module_utils/iosxr/config/interfaces/interfaces.py rename to module_utils/network/iosxr/config/interfaces/interfaces.py index a9e94de..208c1e8 100644 --- a/module_utils/iosxr/config/interfaces/interfaces.py +++ b/module_utils/network/iosxr/config/interfaces/interfaces.py @@ -1,4 +1,3 @@ -#!/usr/bin/python # -*- coding: utf-8 -*- # Copyright 2019 Red Hat Inc. # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) @@ -10,16 +9,19 @@ created """ -from ansible.module_utils.six import iteritems -from ansible.module_utils.network.common.utils import to_list +from __future__ import absolute_import, division, print_function +__metaclass__ = type -from ansible.module_utils.iosxr.argspec.interfaces.interfaces import InterfacesArgs -from ansible.module_utils.iosxr.config.base import ConfigBase -from ansible.module_utils.iosxr.facts.facts import Facts -from ansible.module_utils.iosxr.utils.utils import get_interface_type, normalize_interface, search_obj_in_list +from ansible.module_utils.network.common.cfg.base import ConfigBase +from ansible.module_utils.network.common.utils import to_list +from ansible.module_utils.network.iosxr.facts.facts import Facts +from ansible.module_utils.network.iosxr.utils.utils import get_interface_type, dict_diff +from ansible.module_utils.network.iosxr.utils.utils import remove_command_from_interface, add_command_to_interface +from ansible.module_utils.network.iosxr.utils.utils import filter_dict_having_none_value, remove_duplicate_interface +import q -class Interfaces(ConfigBase, InterfacesArgs): +class Interfaces(ConfigBase): """ The iosxr_interfaces class """ @@ -35,14 +37,15 @@ class Interfaces(ConfigBase, InterfacesArgs): params = ('description', 'mtu', 'speed', 'duplex') + def __init__(self, module): + super(Interfaces, self).__init__(module) + def get_interfaces_facts(self): """ Get the 'facts' (the current configuration) - :rtype: A dictionary :returns: The current configuration as a dictionary """ - result = Facts().get_facts(self._module, self._connection, self.gather_subset, self.gather_network_resources) - facts = result + facts, _warnings = Facts(self._module).get_facts(self.gather_subset, self.gather_network_resources) interfaces_facts = facts['ansible_network_resources'].get('interfaces') if not interfaces_facts: return [] @@ -50,7 +53,6 @@ def get_interfaces_facts(self): def execute_module(self): """ Execute the module - :rtype: A dictionary :returns: The result from module execution """ @@ -78,7 +80,6 @@ def execute_module(self): def set_config(self, existing_interfaces_facts): """ Collect the configuration from the args passed to the module, collect the current configuration (as a dict from facts) - :rtype: A list :returns: the commands necessary to migrate the current configuration to the desired configuration @@ -91,7 +92,6 @@ def set_config(self, existing_interfaces_facts): def set_state(self, want, have): """ Select the appropriate function based on the state provided - :param want: the desired configuration as a dictionary :param have: the current configuration as a dictionary :rtype: A list @@ -99,34 +99,27 @@ def set_state(self, want, have): to the desired configuration """ commands = [] - state = self._module.params['state'] + if state == 'overridden': - kwargs = {'want': want, 'have': have} - commands = self._state_overridden(**kwargs) + commands = self._state_overridden(want, have) elif state == 'deleted': - kwargs = {'want': want, 'have': have} - commands = self._state_deleted(**kwargs) + commands = self._state_deleted(want, have) elif state == 'merged': - kwargs = {'want': want, 'have': have} - commands = self._state_merged(**kwargs) + commands = self._state_merged(want, have) elif state == 'replaced': - kwargs = {'want': want, 'have': have} - commands = self._state_replaced(**kwargs) + commands = self._state_replaced(want, have) return commands @staticmethod - def _state_replaced(**kwargs): + def _state_replaced(want, have): """ The command generator when state is replaced - :rtype: A list :returns: the commands necessary to migrate the current configuration to the desired configuration """ commands = [] - want = kwargs['want'] - have = kwargs['have'] for interface in want: for each in have: @@ -136,24 +129,24 @@ def _state_replaced(**kwargs): break else: continue - kwargs = {'want': interface, 'have': each, } - commands.extend(Interfaces.clear_interface(**kwargs)) + have_dict = filter_dict_having_none_value(interface, each) + kwargs = {'want': {}, 'have': have_dict} + commands.extend(Interfaces._clear_config(**kwargs)) kwargs = {'want': interface, 'have': each, 'commands': commands} - commands.extend(Interfaces.set_interface(**kwargs)) + commands.extend(Interfaces._set_config(**kwargs)) + # Remove the duplicate interface call + commands = remove_duplicate_interface(commands) return commands @staticmethod - def _state_overridden(**kwargs): + def _state_overridden(want, have): """ The command generator when state is overridden - :rtype: A list :returns: the commands necessary to migrate the current configuration to the desired configuration """ commands = [] - want = kwargs['want'] - have = kwargs['have'] for each in have: for interface in want: @@ -166,26 +159,26 @@ def _state_overridden(**kwargs): # pretend we recieved an empty desired state. interface = dict(name=each['name']) kwargs = {'want': interface, 'have': each} - commands.extend(Interfaces.clear_interface(**kwargs)) + commands.extend(Interfaces._clear_config(**kwargs)) continue - kwargs = {'want': interface, 'have': each} - commands.extend(Interfaces.clear_interface(**kwargs)) + have_dict = filter_dict_having_none_value(interface, each) + kwargs = {'want': {}, 'have': have_dict} + commands.extend(Interfaces._clear_config(**kwargs)) kwargs = {'want': interface, 'have': each, 'commands': commands} - commands.extend(Interfaces.set_interface(**kwargs)) + commands.extend(Interfaces._set_config(**kwargs)) + # Remove the duplicate interface call + commands = remove_duplicate_interface(commands) return commands @staticmethod - def _state_merged(**kwargs): + def _state_merged(want, have): """ The command generator when state is merged - :rtype: A list :returns: the commands necessary to merge the provided into the current configuration """ commands = [] - want = kwargs['want'] - have = kwargs['have'] for interface in want: for each in have: @@ -196,96 +189,90 @@ def _state_merged(**kwargs): else: continue kwargs = {'want': interface, 'have': each} - commands.extend(Interfaces.set_interface(**kwargs)) + commands.extend(Interfaces._set_config(**kwargs)) return commands @staticmethod - def _state_deleted(**kwargs): + def _state_deleted(want, have): """ The command generator when state is deleted - :rtype: A list :returns: the commands necessary to remove the current configuration of the provided objects """ commands = [] - want = kwargs['want'] - have = kwargs['have'] - for interface in want: + if want: + for interface in want: + for each in have: + if each['name'] == interface['name']: + break + elif interface['name'] in each['name']: + break + else: + continue + interface = dict(name=interface['name']) + kwargs = {'want': interface, 'have': each} + commands.extend(Interfaces._clear_config(**kwargs)) + else: for each in have: - if each['name'] == interface['name']: - break - elif interface['name'] in each['name']: - break - else: - continue - interface = dict(name=interface['name']) - kwargs = {'want': interface, 'have': each} - commands.extend(Interfaces.clear_interface(**kwargs)) + kwargs = {'want': {}, 'have': each} + commands.extend(Interfaces._clear_config(**kwargs)) return commands @staticmethod - def _remove_command_from_interface(interface, cmd, commands): - if interface not in commands: - commands.insert(0, interface) - commands.append('no %s' % cmd) - return commands - - @staticmethod - def _add_command_to_interface(interface, cmd, commands): - if interface not in commands: - commands.insert(0, interface) - commands.append(cmd) - - @staticmethod - def set_interface(**kwargs): + def _set_config(**kwargs): # Set the interface config based on the want and have config commands = [] want = kwargs['want'] have = kwargs['have'] - clear_cmds = [] - - if kwargs.get('commands'): - clear_cmds = kwargs['commands'] interface = 'interface ' + want['name'] - if want.get('enabled') and want.get('enabled') != have.get('enabled'): - Interfaces._add_command_to_interface(interface, 'no shutdown', commands) - elif not want.get('enabled') and want.get('enabled') != have.get('enabled'): - Interfaces._add_command_to_interface(interface, 'shutdown', commands) - for item in Interfaces.params: - cmd = 'no ' + item - candidate = want.get(item) - if candidate and (candidate != have.get(item) or cmd in clear_cmds): - cmd = item + ' ' + str(candidate) - Interfaces._add_command_to_interface(interface, cmd, commands) + # Get the diff b/w want and have + want_dict = dict_diff(want) + have_dict = dict_diff(have) + diff = want_dict - have_dict + q(want_dict, have_dict, diff) + if diff: + diff = dict(diff) + for item in Interfaces.params: + if diff.get(item): + cmd = item + ' ' + str(want.get(item)) + add_command_to_interface(interface, cmd, commands) + if diff.get('enabled'): + add_command_to_interface(interface, 'no shutdown', commands) + elif diff.get('enabled') is False: + add_command_to_interface(interface, 'shutdown', commands) return commands @staticmethod - def clear_interface(**kwargs): + def _clear_config(**kwargs): # Delete the interface config based on the want and have config commands = [] want = kwargs['want'] have = kwargs['have'] - interface_type = get_interface_type(want['name']) - interface = 'interface ' + want['name'] + if want.get('name'): + interface_type = get_interface_type(want['name']) + interface = 'interface ' + want['name'] + else: + interface_type = get_interface_type(have['name']) + interface = 'interface ' + have['name'] if have.get('description') and want.get('description') != have.get('description'): - Interfaces._remove_command_from_interface(interface, 'description', commands) + remove_command_from_interface(interface, 'description', commands) if not have.get('enabled') and want.get('enabled') != have.get('enabled'): # if enable is False set enable as True which is the default behavior - Interfaces._remove_command_from_interface(interface, 'shutdown', commands) + remove_command_from_interface(interface, 'shutdown', commands) if interface_type.lower() == 'gigabitethernet': if have.get('speed') and have.get('speed') != 'auto' and want.get('speed') != have.get('speed'): - Interfaces._remove_command_from_interface(interface, 'speed', commands) + remove_command_from_interface(interface, 'speed', commands) if have.get('duplex') and have.get('duplex') != 'auto' and want.get('duplex') != have.get('duplex'): - Interfaces._remove_command_from_interface(interface, 'duplex', commands) + remove_command_from_interface(interface, 'duplex', commands) if have.get('mtu') and want.get('mtu') != have.get('mtu'): - Interfaces._remove_command_from_interface(interface, 'mtu', commands) + remove_command_from_interface(interface, 'mtu', commands) return commands diff --git a/module_utils/iosxr/facts/__init__.py b/module_utils/network/iosxr/facts/__init__.py similarity index 100% rename from module_utils/iosxr/facts/__init__.py rename to module_utils/network/iosxr/facts/__init__.py diff --git a/module_utils/network/iosxr/facts/facts.py b/module_utils/network/iosxr/facts/facts.py new file mode 100644 index 0000000..8222fca --- /dev/null +++ b/module_utils/network/iosxr/facts/facts.py @@ -0,0 +1,59 @@ +# +# -*- coding: utf-8 -*- +# Copyright 2019 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +""" +The facts class for iosxr +this file validates each subset of facts and selectively +calls the appropriate facts gathering function +""" + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +from ansible.module_utils.network.iosxr.argspec.facts.facts import FactsArgs +from ansible.module_utils.network.common.facts.facts import FactsBase +from ansible.module_utils.network.iosxr.facts.interfaces.interfaces import InterfacesFacts +from ansible.module_utils.network.iosxr.facts.legacy.base import Default, Hardware, Interfaces, Config + + +FACT_LEGACY_SUBSETS = dict( + default=Default, + hardware=Hardware, + interfaces=Interfaces, + config=Config, +) +FACT_RESOURCE_SUBSETS = dict( + interfaces=InterfacesFacts, +) + + +class Facts(FactsBase): + """ The fact class for iosxr + """ + + VALID_LEGACY_GATHER_SUBSETS = frozenset(FACT_LEGACY_SUBSETS.keys()) + VALID_RESOURCE_SUBSETS = frozenset(FACT_RESOURCE_SUBSETS.keys()) + + def __init__(self, module): + super(Facts, self).__init__(module) + + def get_facts(self, legacy_facts_type=None, resource_facts_type=None, data=None): + """ Collect the facts for iosxr + + :param legacy_facts_type: List of legacy facts types + :param resource_facts_type: List of resource fact types + :param data: previously collected conf + :rtype: dict + :return: the facts gathered + """ + netres_choices = FactsArgs.argument_spec['gather_network_resources'].get('choices', []) + if self.VALID_RESOURCE_SUBSETS: + self.get_network_resources_facts(netres_choices, FACT_RESOURCE_SUBSETS, resource_facts_type, data) + + if self.VALID_LEGACY_GATHER_SUBSETS: + self.get_network_legacy_facts(FACT_LEGACY_SUBSETS, legacy_facts_type) + + return self.ansible_facts, self._warnings diff --git a/module_utils/iosxr/facts/interfaces/__init__.py b/module_utils/network/iosxr/facts/interfaces/__init__.py similarity index 100% rename from module_utils/iosxr/facts/interfaces/__init__.py rename to module_utils/network/iosxr/facts/interfaces/__init__.py diff --git a/module_utils/network/iosxr/facts/interfaces/interfaces.py b/module_utils/network/iosxr/facts/interfaces/interfaces.py new file mode 100644 index 0000000..346e91d --- /dev/null +++ b/module_utils/network/iosxr/facts/interfaces/interfaces.py @@ -0,0 +1,102 @@ +# +# -*- coding: utf-8 -*- +# Copyright 2019 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +""" +The ios interfaces fact class +It is in this file the configuration is collected from the device +for a given resource, parsed, and the facts tree is populated +based on the configuration. +""" + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +from copy import deepcopy +import re +from ansible.module_utils.network.common import utils +from ansible.module_utils.network.iosxr.utils.utils import get_interface_type, normalize_interface +from ansible.module_utils.network.iosxr.argspec.interfaces.interfaces import InterfacesArgs + + +class InterfacesFacts(object): + """ The iosxr interfaces fact class + """ + + def __init__(self, module, subspec='config', options='options'): + self._module = module + self.argument_spec = InterfacesArgs.argument_spec + spec = deepcopy(self.argument_spec) + if subspec: + if options: + facts_argument_spec = spec[subspec][options] + else: + facts_argument_spec = spec[subspec] + else: + facts_argument_spec = spec + + self.generated_spec = utils.generate_dict(facts_argument_spec) + + def populate_facts(self, connection, ansible_facts, data=None): + """ Populate the facts for interfaces + :param module: the module instance + :param connection: the device connection + :param data: previously collected conf + :rtype: dictionary + :returns: facts + """ + if connection: + pass + + objs = [] + if not data: + data = connection.get('show running-config interface') + + # operate on a collection of resource x + config = 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['interfaces'] = [] + params = utils.validate_config(self.argument_spec, {'config': objs}) + for cfg in params['config']: + facts['interfaces'].append(utils.remove_empties(cfg)) + + ansible_facts['ansible_network_resources'].update(facts) + return ansible_facts + + def render_config(self, spec, conf): + """ + Render config as dictionary structure and delete keys from spec for null values + :param spec: The facts tree, generated from the argspec + :param conf: The configuration + :rtype: dictionary + :returns: The generated config + """ + + config = deepcopy(spec) + match = re.search(r'^(\S+)', conf) + if match.group(1).lower() == "preconfigure": + match = re.search(r'^(\S+ \S+)', conf) + intf = match.group(1) + if get_interface_type(intf) == 'unknown': + return {} + # populate the facts from the configuration + config['name'] = normalize_interface(intf) + config['description'] = utils.parse_conf_arg(conf, 'description') + if utils.parse_conf_arg(conf, 'speed'): + config['speed'] = int(utils.parse_conf_arg(conf, 'speed')) + if utils.parse_conf_arg(conf, 'mtu'): + config['mtu'] = int(utils.parse_conf_arg(conf, 'mtu')) + config['duplex'] = utils.parse_conf_arg(conf, 'duplex') + enabled = utils.parse_conf_cmd_arg(conf, 'shutdown', False) + config['enabled'] = enabled if enabled is not None else True + + return utils.remove_empties(config) diff --git a/module_utils/iosxr/utils/__init__.py b/module_utils/network/iosxr/utils/__init__.py similarity index 100% rename from module_utils/iosxr/utils/__init__.py rename to module_utils/network/iosxr/utils/__init__.py diff --git a/module_utils/iosxr/utils/utils.py b/module_utils/network/iosxr/utils/utils.py similarity index 62% rename from module_utils/iosxr/utils/utils.py rename to module_utils/network/iosxr/utils/utils.py index ea5b253..5ad3452 100644 --- a/module_utils/iosxr/utils/utils.py +++ b/module_utils/network/iosxr/utils/utils.py @@ -5,6 +5,61 @@ # utils +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +from ansible.module_utils.six import iteritems + + +def remove_command_from_interface(interface, cmd, commands): + # To delete the passed config + if interface not in commands: + commands.insert(0, interface) + commands.append('no %s' % cmd) + return commands + + +def add_command_to_interface(interface, cmd, commands): + # To set the passed config + if interface not in commands: + commands.insert(0, interface) + commands.append(cmd) + + +def dict_diff(sample_dict): + # Generate a set with passed dictionary for comparison + test_dict = {} + for k, v in iteritems(sample_dict): + if v is not None: + test_dict.update({k: v}) + return_set = set(tuple(test_dict.items())) + return return_set + + +def filter_dict_having_none_value(want, have): + # Generate dict with have dict value which is None in want dict + test_dict = dict() + test_dict['name'] = want.get('name') + for k, v in iteritems(want): + if v is None: + val = have.get(k) + test_dict.update({k: val}) + return test_dict + + +def remove_duplicate_interface(commands): + # Remove duplicate interface from commands + set_cmd = [] + for each in commands: + if 'interface' in each: + interface = each + if interface not in set_cmd: + set_cmd.append(each) + else: + set_cmd.append(each) + + return set_cmd + def search_obj_in_list(name, lst): for o in lst: