From f2b0bfd4aaa3c71be4c0fb8f566451936aeca629 Mon Sep 17 00:00:00 2001 From: Nilashish Chakraborty Date: Wed, 24 Jul 2019 22:55:42 +0530 Subject: [PATCH] Add iosxr_lacp resource module (#59281) Signed-off-by: NilashishC --- .../network/iosxr/argspec/__init__.py | 0 .../network/iosxr/argspec/facts/__init__.py | 0 .../network/iosxr/argspec/facts/facts.py | 29 ++ .../network/iosxr/argspec/lacp/__init__.py | 0 .../network/iosxr/argspec/lacp/lacp.py | 62 +++ .../network/iosxr/config/__init__.py | 0 .../network/iosxr/config/lacp/__init__.py | 0 .../network/iosxr/config/lacp/lacp.py | 169 ++++++++ .../network/iosxr/facts/__init__.py | 0 .../module_utils/network/iosxr/facts/facts.py | 60 +++ .../network/iosxr/facts/lacp/__init__.py | 0 .../network/iosxr/facts/lacp/lacp.py | 82 ++++ .../network/iosxr/facts/legacy/__init__.py | 0 .../network/iosxr/facts/legacy/base.py | 259 ++++++++++++ .../modules/network/iosxr/iosxr_facts.py | 389 ++++-------------- .../modules/network/iosxr/iosxr_lacp.py | 283 +++++++++++++ .../iosxr_facts/tests/cli/invalid_subset.yaml | 2 +- .../targets/iosxr_lacp/defaults/main.yaml | 3 + .../targets/iosxr_lacp/tasks/cli.yaml | 20 + .../targets/iosxr_lacp/tasks/main.yaml | 2 + .../iosxr_lacp/tests/cli/_populate.yaml | 9 + .../iosxr_lacp/tests/cli/_remove_config.yaml | 8 + .../targets/iosxr_lacp/tests/cli/deleted.yaml | 45 ++ .../targets/iosxr_lacp/tests/cli/merged.yaml | 45 ++ .../iosxr_lacp/tests/cli/replaced.yaml | 48 +++ .../targets/iosxr_lacp/tests/cli/rtt.yaml | 49 +++ .../targets/iosxr_lacp/vars/main.yaml | 40 ++ .../modules/network/iosxr/test_iosxr_facts.py | 8 +- 28 files changed, 1289 insertions(+), 323 deletions(-) create mode 100644 lib/ansible/module_utils/network/iosxr/argspec/__init__.py create mode 100644 lib/ansible/module_utils/network/iosxr/argspec/facts/__init__.py create mode 100644 lib/ansible/module_utils/network/iosxr/argspec/facts/facts.py create mode 100644 lib/ansible/module_utils/network/iosxr/argspec/lacp/__init__.py create mode 100644 lib/ansible/module_utils/network/iosxr/argspec/lacp/lacp.py create mode 100644 lib/ansible/module_utils/network/iosxr/config/__init__.py create mode 100644 lib/ansible/module_utils/network/iosxr/config/lacp/__init__.py create mode 100644 lib/ansible/module_utils/network/iosxr/config/lacp/lacp.py create mode 100644 lib/ansible/module_utils/network/iosxr/facts/__init__.py create mode 100644 lib/ansible/module_utils/network/iosxr/facts/facts.py create mode 100644 lib/ansible/module_utils/network/iosxr/facts/lacp/__init__.py create mode 100644 lib/ansible/module_utils/network/iosxr/facts/lacp/lacp.py create mode 100644 lib/ansible/module_utils/network/iosxr/facts/legacy/__init__.py create mode 100644 lib/ansible/module_utils/network/iosxr/facts/legacy/base.py create mode 100644 lib/ansible/modules/network/iosxr/iosxr_lacp.py create mode 100644 test/integration/targets/iosxr_lacp/defaults/main.yaml create mode 100644 test/integration/targets/iosxr_lacp/tasks/cli.yaml create mode 100644 test/integration/targets/iosxr_lacp/tasks/main.yaml create mode 100644 test/integration/targets/iosxr_lacp/tests/cli/_populate.yaml create mode 100644 test/integration/targets/iosxr_lacp/tests/cli/_remove_config.yaml create mode 100644 test/integration/targets/iosxr_lacp/tests/cli/deleted.yaml create mode 100644 test/integration/targets/iosxr_lacp/tests/cli/merged.yaml create mode 100644 test/integration/targets/iosxr_lacp/tests/cli/replaced.yaml create mode 100644 test/integration/targets/iosxr_lacp/tests/cli/rtt.yaml create mode 100644 test/integration/targets/iosxr_lacp/vars/main.yaml diff --git a/lib/ansible/module_utils/network/iosxr/argspec/__init__.py b/lib/ansible/module_utils/network/iosxr/argspec/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/lib/ansible/module_utils/network/iosxr/argspec/facts/__init__.py b/lib/ansible/module_utils/network/iosxr/argspec/facts/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/lib/ansible/module_utils/network/iosxr/argspec/facts/facts.py b/lib/ansible/module_utils/network/iosxr/argspec/facts/facts.py new file mode 100644 index 00000000000..600360db357 --- /dev/null +++ b/lib/ansible/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', + 'lacp' + ] + + argument_spec = { + 'gather_subset': dict(default=['!config'], type='list'), + 'gather_network_resources': dict(choices=choices, type='list'), + } diff --git a/lib/ansible/module_utils/network/iosxr/argspec/lacp/__init__.py b/lib/ansible/module_utils/network/iosxr/argspec/lacp/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/lib/ansible/module_utils/network/iosxr/argspec/lacp/lacp.py b/lib/ansible/module_utils/network/iosxr/argspec/lacp/lacp.py new file mode 100644 index 00000000000..4eb731f93ae --- /dev/null +++ b/lib/ansible/module_utils/network/iosxr/argspec/lacp/lacp.py @@ -0,0 +1,62 @@ +# +# -*- 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 iosxr_lacp module +""" + + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +class LacpArgs(object): # pylint: disable=R0903 + """The arg spec for the iosxr_lacp module + """ + + def __init__(self, **kwargs): + pass + + argument_spec = { + 'config': { + 'options': { + 'system': { + 'options': { + 'mac': { + 'type': 'str' + }, + 'priority': { + 'type': 'int' + } + }, + 'type': 'dict' + } + }, + 'type': 'dict' + }, + 'state': { + 'choices': ['merged', 'replaced', 'deleted'], + 'default': 'merged', + 'type': 'str' + } + } # pylint: disable=C0301 diff --git a/lib/ansible/module_utils/network/iosxr/config/__init__.py b/lib/ansible/module_utils/network/iosxr/config/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/lib/ansible/module_utils/network/iosxr/config/lacp/__init__.py b/lib/ansible/module_utils/network/iosxr/config/lacp/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/lib/ansible/module_utils/network/iosxr/config/lacp/lacp.py b/lib/ansible/module_utils/network/iosxr/config/lacp/lacp.py new file mode 100644 index 00000000000..bba7c4da785 --- /dev/null +++ b/lib/ansible/module_utils/network/iosxr/config/lacp/lacp.py @@ -0,0 +1,169 @@ +# +# -*- 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 iosxr_lacp 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 __future__ import absolute_import, division, print_function +__metaclass__ = type + + +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.common.utils import dict_diff +from ansible.module_utils.six import iteritems +from ansible.module_utils.network.common import utils + + +class Lacp(ConfigBase): + """ + The iosxr_lacp class + """ + + gather_subset = [ + '!all', + '!min', + ] + + gather_network_resources = [ + 'lacp', + ] + + def __init__(self, module): + super(Lacp, self).__init__(module) + + def get_lacp_facts(self): + """ Get the 'facts' (the current configuration) + + :rtype: A dictionary + :returns: The current configuration as a dictionary + """ + facts, _warnings = Facts(self._module).get_facts(self.gather_subset, self.gather_network_resources) + lacp_facts = facts['ansible_network_resources'].get('lacp') + if not lacp_facts: + return {} + return lacp_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_lacp_facts = self.get_lacp_facts() + commands.extend(self.set_config(existing_lacp_facts)) + if commands: + if not self._module.check_mode: + self._connection.edit_config(commands) + result['changed'] = True + result['commands'] = commands + + changed_lacp_facts = self.get_lacp_facts() + + result['before'] = existing_lacp_facts + if result['changed']: + result['after'] = changed_lacp_facts + + result['warnings'] = warnings + return result + + def set_config(self, existing_lacp_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.get('config') + if not want: + want = {} + have = existing_lacp_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 == 'deleted': + commands = self._state_deleted(want, have) + elif state == 'merged': + commands = self._state_merged(want, have) + elif state == 'replaced': + commands = self._state_replaced(want, have) + + return commands + + @staticmethod + def _state_replaced(want, have): + """ The command generator when state is replaced + + :rtype: A list + :returns: the commands necessary to migrate the current configuration + to the desired configuration + """ + commands = [] + + commands.extend( + Lacp._state_deleted(want, have) + ) + + commands.extend( + Lacp._state_merged(want, have) + ) + + return commands + + @staticmethod + def _state_merged(want, have): + """ The command generator when state is merged + + :rtype: A list + :returns: the commands necessary to merge the provided into + the current configuration + """ + commands = [] + + updates = dict_diff(have, want) + if updates: + for key, value in iteritems(updates['system']): + if value: + commands.append('lacp system {0} {1}'.format(key, value)) + + return commands + + @staticmethod + def _state_deleted(want, have): + """ The command generator when state is deleted + + :rtype: A list + :returns: the commands necessary to remove the current configuration + of the provided objects + """ + commands = [] + + for x in [k for k in have.get('system', {}) if k not in utils.remove_empties(want.get('system', {}))]: + commands.append('no lacp system {0}'.format(x)) + + return commands diff --git a/lib/ansible/module_utils/network/iosxr/facts/__init__.py b/lib/ansible/module_utils/network/iosxr/facts/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/lib/ansible/module_utils/network/iosxr/facts/facts.py b/lib/ansible/module_utils/network/iosxr/facts/facts.py new file mode 100644 index 00000000000..fad4f617fb8 --- /dev/null +++ b/lib/ansible/module_utils/network/iosxr/facts/facts.py @@ -0,0 +1,60 @@ +# +# -*- 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.lacp.lacp import LacpFacts +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( + lacp=LacpFacts, +) + + +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/lib/ansible/module_utils/network/iosxr/facts/lacp/__init__.py b/lib/ansible/module_utils/network/iosxr/facts/lacp/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/lib/ansible/module_utils/network/iosxr/facts/lacp/lacp.py b/lib/ansible/module_utils/network/iosxr/facts/lacp/lacp.py new file mode 100644 index 00000000000..2d877680180 --- /dev/null +++ b/lib/ansible/module_utils/network/iosxr/facts/lacp/lacp.py @@ -0,0 +1,82 @@ +# +# -*- 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 iosxr lacp 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 +from ansible.module_utils.network.common import utils +from ansible.module_utils.network.iosxr.argspec.lacp.lacp import LacpArgs + + +class LacpFacts(object): + """ The iosxr lacp fact class + """ + + def __init__(self, module, subspec='config', options='options'): + self._module = module + self.argument_spec = LacpArgs.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 lacp + :param connection: the device connection + :param ansible_facts: Facts dictionary + :param data: previously collected conf + :rtype: dictionary + :returns: facts + """ + if not data: + data = connection.get_config(flags='lacp') + + obj = {} + if data: + lacp_obj = self.render_config(self.generated_spec, data) + if lacp_obj: + obj = lacp_obj + + ansible_facts['ansible_network_resources'].pop('lacp', None) + facts = {} + if obj: + params = utils.validate_config(self.argument_spec, {'config': obj}) + facts['lacp'] = utils.remove_empties(params['config']) + + 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) + + system_priority = utils.parse_conf_arg(conf, 'priority') + config['system']['priority'] = int(system_priority) if system_priority else system_priority + config['system']['mac'] = utils.parse_conf_arg(conf, 'mac') + + return config diff --git a/lib/ansible/module_utils/network/iosxr/facts/legacy/__init__.py b/lib/ansible/module_utils/network/iosxr/facts/legacy/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/lib/ansible/module_utils/network/iosxr/facts/legacy/base.py b/lib/ansible/module_utils/network/iosxr/facts/legacy/base.py new file mode 100644 index 00000000000..cbcee0bb507 --- /dev/null +++ b/lib/ansible/module_utils/network/iosxr/facts/legacy/base.py @@ -0,0 +1,259 @@ +# -*- 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 iosxr legacy 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 + + +import platform +import re + +from ansible.module_utils.network.iosxr.iosxr import run_commands, get_capabilities +from ansible.module_utils.six import iteritems +from ansible.module_utils.six.moves import zip + + +class FactsBase(object): + + COMMANDS = frozenset() + + def __init__(self, module): + self.module = module + self.facts = dict() + self.warnings = list() + self.responses = None + + def populate(self): + self.responses = run_commands(self.module, list(self.COMMANDS), check_rc=False) + + +class Default(FactsBase): + + def populate(self): + self.facts.update(self.platform_facts()) + + def platform_facts(self): + platform_facts = {} + + resp = get_capabilities(self.module) + device_info = resp['device_info'] + + platform_facts['system'] = device_info['network_os'] + + for item in ('model', 'image', 'version', 'platform', 'hostname'): + val = device_info.get('network_os_%s' % item) + if val: + platform_facts[item] = val + + platform_facts['api'] = resp['network_api'] + platform_facts['python_version'] = platform.python_version() + + return platform_facts + + +class Hardware(FactsBase): + + COMMANDS = [ + 'dir /all', + 'show memory summary' + ] + + def populate(self): + super(Hardware, self).populate() + data = self.responses[0] + self.facts['filesystems'] = self.parse_filesystems(data) + + data = self.responses[1] + match = re.search(r'Physical Memory: (\d+)M total \((\d+)', data) + if match: + self.facts['memtotal_mb'] = match.group(1) + self.facts['memfree_mb'] = match.group(2) + + def parse_filesystems(self, data): + return re.findall(r'^Directory of (\S+)', data, re.M) + + +class Config(FactsBase): + + COMMANDS = [ + 'show running-config' + ] + + def populate(self): + super(Config, self).populate() + self.facts['config'] = self.responses[0] + + +class Interfaces(FactsBase): + + COMMANDS = [ + 'show interfaces', + 'show ipv6 interface', + 'show lldp', + 'show lldp neighbors detail' + ] + + def populate(self): + super(Interfaces, self).populate() + self.facts['all_ipv4_addresses'] = list() + self.facts['all_ipv6_addresses'] = list() + + interfaces = self.parse_interfaces(self.responses[0]) + self.facts['interfaces'] = self.populate_interfaces(interfaces) + + data = self.responses[1] + if len(data) > 0: + data = self.parse_interfaces(data) + self.populate_ipv6_interfaces(data) + + if 'LLDP is not enabled' not in self.responses[2]: + neighbors = self.responses[3] + self.facts['neighbors'] = self.parse_neighbors(neighbors) + + def populate_interfaces(self, interfaces): + facts = dict() + for key, value in iteritems(interfaces): + intf = dict() + intf['description'] = self.parse_description(value) + intf['macaddress'] = self.parse_macaddress(value) + + ipv4 = self.parse_ipv4(value) + intf['ipv4'] = self.parse_ipv4(value) + if ipv4: + self.add_ip_address(ipv4['address'], 'ipv4') + + intf['mtu'] = self.parse_mtu(value) + intf['bandwidth'] = self.parse_bandwidth(value) + intf['duplex'] = self.parse_duplex(value) + intf['lineprotocol'] = self.parse_lineprotocol(value) + intf['operstatus'] = self.parse_operstatus(value) + intf['type'] = self.parse_type(value) + + facts[key] = intf + return facts + + def populate_ipv6_interfaces(self, data): + for key, value in iteritems(data): + if key in ['No', 'RPF'] or key.startswith('IP'): + continue + self.facts['interfaces'][key]['ipv6'] = list() + addresses = re.findall(r'\s+(.+), subnet', value, re.M) + subnets = re.findall(r', subnet is (.+)$', value, re.M) + for addr, subnet in zip(addresses, subnets): + ipv6 = dict(address=addr.strip(), subnet=subnet.strip()) + self.add_ip_address(addr.strip(), 'ipv6') + self.facts['interfaces'][key]['ipv6'].append(ipv6) + + def add_ip_address(self, address, family): + if family == 'ipv4': + self.facts['all_ipv4_addresses'].append(address) + else: + self.facts['all_ipv6_addresses'].append(address) + + def parse_neighbors(self, neighbors): + facts = dict() + nbors = neighbors.split('------------------------------------------------') + for entry in nbors[1:]: + if entry == '': + continue + intf = self.parse_lldp_intf(entry) + if intf not in facts: + facts[intf] = list() + fact = dict() + fact['host'] = self.parse_lldp_host(entry) + fact['remote_description'] = self.parse_lldp_remote_desc(entry) + fact['port'] = self.parse_lldp_port(entry) + facts[intf].append(fact) + return facts + + def parse_interfaces(self, data): + parsed = dict() + key = '' + for line in data.split('\n'): + if len(line) == 0: + continue + elif line[0] == ' ': + parsed[key] += '\n%s' % line + else: + match = re.match(r'^(\S+)', line) + if match: + key = match.group(1) + parsed[key] = line + return parsed + + def parse_description(self, data): + match = re.search(r'Description: (.+)$', data, re.M) + if match: + return match.group(1) + + def parse_macaddress(self, data): + match = re.search(r'address is (\S+)', data) + if match: + return match.group(1) + + def parse_ipv4(self, data): + match = re.search(r'Internet address is (\S+)/(\d+)', data) + if match: + addr = match.group(1) + masklen = int(match.group(2)) + return dict(address=addr, masklen=masklen) + + def parse_mtu(self, data): + match = re.search(r'MTU (\d+)', data) + if match: + return int(match.group(1)) + + def parse_bandwidth(self, data): + match = re.search(r'BW (\d+)', data) + if match: + return int(match.group(1)) + + def parse_duplex(self, data): + match = re.search(r'(\w+)(?: D|-d)uplex', data, re.M) + if match: + return match.group(1) + + def parse_type(self, data): + match = re.search(r'Hardware is (.+),', data, re.M) + if match: + return match.group(1) + + def parse_lineprotocol(self, data): + match = re.search(r'line protocol is (.+)\s+?$', data, re.M) + if match: + return match.group(1) + + def parse_operstatus(self, data): + match = re.search(r'^(?:.+) is (.+),', data, re.M) + if match: + return match.group(1) + + def parse_lldp_intf(self, data): + match = re.search(r'^Local Interface: (.+)$', data, re.M) + if match: + return match.group(1) + + def parse_lldp_remote_desc(self, data): + match = re.search(r'Port Description: (.+)$', data, re.M) + if match: + return match.group(1) + + def parse_lldp_host(self, data): + match = re.search(r'System Name: (.+)$', data, re.M) + if match: + return match.group(1) + + def parse_lldp_port(self, data): + match = re.search(r'Port id: (.+)$', data, re.M) + if match: + return match.group(1) diff --git a/lib/ansible/modules/network/iosxr/iosxr_facts.py b/lib/ansible/modules/network/iosxr/iosxr_facts.py index c5edd2ba70e..190e7cb1fd0 100644 --- a/lib/ansible/modules/network/iosxr/iosxr_facts.py +++ b/lib/ansible/modules/network/iosxr/iosxr_facts.py @@ -1,33 +1,36 @@ #!/usr/bin/python -# -# Copyright: Ansible Project -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# -*- 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 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'} DOCUMENTATION = """ --- module: iosxr_facts -version_added: "2.2" -author: "Ricardo Carrillo Cruz (@rcarrillocruz)" -short_description: Collect facts from remote devices running IOS XR -description: - - Collects a base set of device facts from a remote device that - is running IOS XR. 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. +version_added: 2.2 +short_description: Get facts about iosxr devices. extends_documentation_fragment: iosxr -notes: - - Tested against IOS XRv 6.1.2 - - This module does not support netconf connection +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: + - Ricardo Carrillo Cruz (@rcarrillocruz) + - Nilashish Chakraborty (@Nilashishc) options: gather_subset: description: @@ -39,12 +42,24 @@ options: not be collected. required: false 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, 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" """ EXAMPLES = """ -# Collect all facts from the device +# Gather all facts - iosxr_facts: gather_subset: all + gather_network_resources: all # Collect only the config and default facts - iosxr_facts: @@ -55,6 +70,24 @@ EXAMPLES = """ - iosxr_facts: gather_subset: - "!hardware" + +# Collect only the lag_interfaces facts +- iosxr_facts: + gather_subset: + - "!all" + - "!min" + gather_network_resources: + - lacp + +# Do not collect lag_interfaces facts +- iosxr_facts: + gather_network_resources: + - "!lacp" + +# Collect lag_interfaces and minimal default facts +- iosxr_facts: + gather_subset: min + gather_network_resources: lacp """ RETURN = """ @@ -126,322 +159,38 @@ ansible_net_neighbors: description: The list of LLDP neighbors from the remote device returned: when interfaces is configured type: dict -""" -import platform -import re +# 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.network.iosxr.iosxr import iosxr_argument_spec, run_commands, get_capabilities -from ansible.module_utils.six import iteritems -from ansible.module_utils.six.moves import zip - - -class FactsBase(object): - - COMMANDS = frozenset() - - def __init__(self, module): - self.module = module - self.facts = dict() - self.responses = None - - def populate(self): - self.responses = run_commands(self.module, list(self.COMMANDS), check_rc=False) - - -class Default(FactsBase): - - def populate(self): - self.facts.update(self.platform_facts()) - - def platform_facts(self): - platform_facts = {} - - resp = get_capabilities(self.module) - device_info = resp['device_info'] - - platform_facts['system'] = device_info['network_os'] - - for item in ('model', 'image', 'version', 'platform', 'hostname'): - val = device_info.get('network_os_%s' % item) - if val: - platform_facts[item] = val - - platform_facts['api'] = resp['network_api'] - platform_facts['python_version'] = platform.python_version() - - return platform_facts - - -class Hardware(FactsBase): - - COMMANDS = [ - 'dir /all', - 'show memory summary' - ] - - def populate(self): - super(Hardware, self).populate() - data = self.responses[0] - self.facts['filesystems'] = self.parse_filesystems(data) - - data = self.responses[1] - match = re.search(r'Physical Memory: (\d+)M total \((\d+)', data) - if match: - self.facts['memtotal_mb'] = match.group(1) - self.facts['memfree_mb'] = match.group(2) - - def parse_filesystems(self, data): - return re.findall(r'^Directory of (\S+)', data, re.M) - - -class Config(FactsBase): - - COMMANDS = [ - 'show running-config' - ] - - def populate(self): - super(Config, self).populate() - self.facts['config'] = self.responses[0] - - -class Interfaces(FactsBase): - - COMMANDS = [ - 'show interfaces', - 'show ipv6 interface', - 'show lldp', - 'show lldp neighbors detail' - ] - - def populate(self): - super(Interfaces, self).populate() - self.facts['all_ipv4_addresses'] = list() - self.facts['all_ipv6_addresses'] = list() - - interfaces = self.parse_interfaces(self.responses[0]) - self.facts['interfaces'] = self.populate_interfaces(interfaces) - - data = self.responses[1] - if len(data) > 0: - data = self.parse_interfaces(data) - self.populate_ipv6_interfaces(data) - - if 'LLDP is not enabled' not in self.responses[2]: - neighbors = self.responses[3] - self.facts['neighbors'] = self.parse_neighbors(neighbors) - - def populate_interfaces(self, interfaces): - facts = dict() - for key, value in iteritems(interfaces): - intf = dict() - intf['description'] = self.parse_description(value) - intf['macaddress'] = self.parse_macaddress(value) - - ipv4 = self.parse_ipv4(value) - intf['ipv4'] = self.parse_ipv4(value) - if ipv4: - self.add_ip_address(ipv4['address'], 'ipv4') - - intf['mtu'] = self.parse_mtu(value) - intf['bandwidth'] = self.parse_bandwidth(value) - intf['duplex'] = self.parse_duplex(value) - intf['lineprotocol'] = self.parse_lineprotocol(value) - intf['operstatus'] = self.parse_operstatus(value) - intf['type'] = self.parse_type(value) - - facts[key] = intf - return facts - - def populate_ipv6_interfaces(self, data): - for key, value in iteritems(data): - if key in ['No', 'RPF'] or key.startswith('IP'): - continue - self.facts['interfaces'][key]['ipv6'] = list() - addresses = re.findall(r'\s+(.+), subnet', value, re.M) - subnets = re.findall(r', subnet is (.+)$', value, re.M) - for addr, subnet in zip(addresses, subnets): - ipv6 = dict(address=addr.strip(), subnet=subnet.strip()) - self.add_ip_address(addr.strip(), 'ipv6') - self.facts['interfaces'][key]['ipv6'].append(ipv6) - - def add_ip_address(self, address, family): - if family == 'ipv4': - self.facts['all_ipv4_addresses'].append(address) - else: - self.facts['all_ipv6_addresses'].append(address) - - def parse_neighbors(self, neighbors): - facts = dict() - nbors = neighbors.split('------------------------------------------------') - for entry in nbors[1:]: - if entry == '': - continue - intf = self.parse_lldp_intf(entry) - if intf not in facts: - facts[intf] = list() - fact = dict() - fact['host'] = self.parse_lldp_host(entry) - fact['remote_description'] = self.parse_lldp_remote_desc(entry) - fact['port'] = self.parse_lldp_port(entry) - facts[intf].append(fact) - return facts - - def parse_interfaces(self, data): - parsed = dict() - key = '' - for line in data.split('\n'): - if len(line) == 0: - continue - elif line[0] == ' ': - parsed[key] += '\n%s' % line - else: - match = re.match(r'^(\S+)', line) - if match: - key = match.group(1) - parsed[key] = line - return parsed - - def parse_description(self, data): - match = re.search(r'Description: (.+)$', data, re.M) - if match: - return match.group(1) - - def parse_macaddress(self, data): - match = re.search(r'address is (\S+)', data) - if match: - return match.group(1) - - def parse_ipv4(self, data): - match = re.search(r'Internet address is (\S+)/(\d+)', data) - if match: - addr = match.group(1) - masklen = int(match.group(2)) - return dict(address=addr, masklen=masklen) - - def parse_mtu(self, data): - match = re.search(r'MTU (\d+)', data) - if match: - return int(match.group(1)) - - def parse_bandwidth(self, data): - match = re.search(r'BW (\d+)', data) - if match: - return int(match.group(1)) - - def parse_duplex(self, data): - match = re.search(r'(\w+)(?: D|-d)uplex', data, re.M) - if match: - return match.group(1) - - def parse_type(self, data): - match = re.search(r'Hardware is (.+),', data, re.M) - if match: - return match.group(1) - - def parse_lineprotocol(self, data): - match = re.search(r'line protocol is (.+)\s+?$', data, re.M) - if match: - return match.group(1) - - def parse_operstatus(self, data): - match = re.search(r'^(?:.+) is (.+),', data, re.M) - if match: - return match.group(1) - - def parse_lldp_intf(self, data): - match = re.search(r'^Local Interface: (.+)$', data, re.M) - if match: - return match.group(1) - - def parse_lldp_remote_desc(self, data): - match = re.search(r'Port Description: (.+)$', data, re.M) - if match: - return match.group(1) - - def parse_lldp_host(self, data): - match = re.search(r'System Name: (.+)$', data, re.M) - if match: - return match.group(1) - - def parse_lldp_port(self, data): - match = re.search(r'Port id: (.+)$', data, re.M) - if match: - return match.group(1) - - -FACT_SUBSETS = dict( - default=Default, - hardware=Hardware, - interfaces=Interfaces, - config=Config, -) - -VALID_SUBSETS = frozenset(FACT_SUBSETS.keys()) +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(): - spec = dict( - gather_subset=dict(default=['!config'], type='list') - ) + """ + Main entry point for module execution + :returns: ansible_facts + """ + 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 = list() - - gather_subset = module.params['gather_subset'] - - runable_subsets = set() - exclude_subsets = set() - - 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) - runable_subsets.add('default') - - facts = dict() - 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) + result = Facts(module).get_facts() - ansible_facts = dict() - for key, value in iteritems(facts): - key = 'ansible_net_%s' % key - ansible_facts[key] = value + ansible_facts, additional_warnings = result + warnings.extend(additional_warnings) module.exit_json(ansible_facts=ansible_facts, warnings=warnings) diff --git a/lib/ansible/modules/network/iosxr/iosxr_lacp.py b/lib/ansible/modules/network/iosxr/iosxr_lacp.py new file mode 100644 index 00000000000..a15aab98b9f --- /dev/null +++ b/lib/ansible/modules/network/iosxr/iosxr_lacp.py @@ -0,0 +1,283 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright 2019 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +############################################# +# 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_lacp +""" + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +ANSIBLE_METADATA = { + 'metadata_version': '1.1', + 'status': ['preview'], + 'supported_by': 'network' +} + +DOCUMENTATION = """ +--- +module: iosxr_lacp +version_added: 2.9 +short_description: Manage Global Link Aggregation Control Protocol (LACP) on IOS-XR devices. +description: + - This module manages Global Link Aggregation Control Protocol (LACP) on IOS-XR devices. +author: Nilashish Chakraborty (@nilashishc) +options: + config: + description: The provided configurations. + type: dict + suboptions: + system: + description: This option sets the default system parameters for LACP bundles. + type: dict + suboptions: + priority: + description: + - The system priority to use in LACP negotiations. + - Lower value is higher priority. + - Refer to vendor documentation for valid values. + type: int + mac: + description: + - The system ID to use in LACP negotiations. + type: str + state: + description: + - The state the configuration should be left in. + type: str + choices: + - merged + - replaced + - deleted + default: merged +""" +EXAMPLES = """ +# Using merged +# +# +# ------------ +# Before state +# ------------ +# +# +# RP/0/0/CPU0:iosxr01#show running-config lacp +# Tue Jul 16 17:46:08.147 UTC +# % No such configuration item(s) +# +# + +- name: Merge provided configuration with device configuration + iosxr_lacp: + config: + system: + priority: 10 + mac: 00c1.4c00.bd15 + state: merged + +# +# +# ----------------------- +# Module Execution Result +# ----------------------- +# +# "before": {} +# +# +# "commands": [ +# "lacp system priority 10", +# "lacp system mac 00c1.4c00.bd15" +# ] +# +# +# "after": { +# "system": { +# "mac": "00c1.4c00.bd15", +# "priority": 10 +# } +# } +# +# ----------- +# After state +# ----------- +# +# +# RP/0/0/CPU0:iosxr01#sh run lacp +# Tue Jul 16 17:51:29.365 UTC +# lacp system mac 00c1.4c00.bd15 +# lacp system priority 10 +# +# + +# Using replaced +# +# +# ------------- +# Before state +# ------------- +# +# +# RP/0/0/CPU0:iosxr01#sh run lacp +# Tue Jul 16 17:53:59.904 UTC +# lacp system mac 00c1.4c00.bd15 +# lacp system priority 10 +# + +- name: Replace device global lacp configuration with the given configuration + iosxr_lacp: + config: + system: + priority: 11 + state: replaced +# +# +# ----------------------- +# Module Execution Result +# ----------------------- +# "before": { +# "system": { +# "mac": "00c1.4c00.bd15", +# "priority": 10 +# } +# } +# +# +# "commands": [ +# "no lacp system mac", +# "lacp system priority 11" +# ] +# +# +# "after": { +# "system": { +# "priority": 11 +# } +# } +# +# ----------- +# After state +# ----------- +# +# +# RP/0/0/CPU0:iosxr01#sh run lacp +# Tue Jul 16 18:02:40.379 UTC +# lacp system priority 11 +# +# + +# Using deleted +# +# +# ------------ +# Before state +# ------------ +# +# +# RP/0/0/CPU0:iosxr01#sh run lacp +# Tue Jul 16 18:37:09.727 UTC +# lacp system mac 00c1.4c00.bd15 +# lacp system priority 11 +# +# + +- name: Delete global LACP configurations from the device + iosxr_lacp: + state: deleted + +# +# +# ----------------------- +# Module Execution Result +# ----------------------- +# "before": { +# "system": { +# "mac": "00c1.4c00.bd15", +# "priority": 11 +# } +# } +# +# +# "commands": [ +# "no lacp system mac", +# "no lacp system priority" +# ] +# +# +# "after": {} +# +# ------------ +# After state +# ------------ +# +# +# RP/0/0/CPU0:iosxr01#sh run lacp +# Tue Jul 16 18:39:44.116 UTC +# % No such configuration item(s) +# +# + + +""" +RETURN = """ +before: + description: The configuration prior to the model invocation. + returned: always + type: list + sample: > + The configuration returned will always be in the same format + of the parameters above. +after: + description: The resulting configuration model invocation. + returned: when changed + type: list + sample: > + The configuration returned will always be in the same format + of the parameters above. +commands: + description: The set of commands pushed to the remote device. + returned: always + type: list + sample: ['lacp system priority 10', 'lacp system mac 00c1.4c00.bd15'] +""" + + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.network.iosxr.argspec.lacp.lacp import LacpArgs +from ansible.module_utils.network.iosxr.config.lacp.lacp import Lacp + + +def main(): + """ + Main entry point for module execution + + :returns: the result form module invocation + """ + module = AnsibleModule(argument_spec=LacpArgs.argument_spec, + supports_check_mode=True) + + result = Lacp(module).execute_module() + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/test/integration/targets/iosxr_facts/tests/cli/invalid_subset.yaml b/test/integration/targets/iosxr_facts/tests/cli/invalid_subset.yaml index 5064a86a687..188337ae441 100644 --- a/test/integration/targets/iosxr_facts/tests/cli/invalid_subset.yaml +++ b/test/integration/targets/iosxr_facts/tests/cli/invalid_subset.yaml @@ -18,7 +18,7 @@ # It's a failure - "result.failed == true" # Sensible Failure message - - "result.msg == 'Bad subset'" + - "result.msg == 'Subset must be one of [config, default, hardware, interfaces], got foobar'" ############### # FIXME Future diff --git a/test/integration/targets/iosxr_lacp/defaults/main.yaml b/test/integration/targets/iosxr_lacp/defaults/main.yaml new file mode 100644 index 00000000000..164afead284 --- /dev/null +++ b/test/integration/targets/iosxr_lacp/defaults/main.yaml @@ -0,0 +1,3 @@ +--- +testcase: "[^_].*" +test_items: [] diff --git a/test/integration/targets/iosxr_lacp/tasks/cli.yaml b/test/integration/targets/iosxr_lacp/tasks/cli.yaml new file mode 100644 index 00000000000..337e34133b0 --- /dev/null +++ b/test/integration/targets/iosxr_lacp/tasks/cli.yaml @@ -0,0 +1,20 @@ +--- +- name: Collect all cli test cases + find: + paths: "{{ role_path }}/tests/cli" + patterns: "{{ testcase }}.yaml" + use_regex: true + register: test_cases + delegate_to: localhost + +- name: Set test_items + set_fact: test_items="{{ test_cases.files | map(attribute='path') | list }}" + delegate_to: localhost + +- name: Run test case (connection=network_cli) + include: "{{ test_case_to_run }}" + vars: + ansible_connection: network_cli + with_items: "{{ test_items }}" + loop_control: + loop_var: test_case_to_run diff --git a/test/integration/targets/iosxr_lacp/tasks/main.yaml b/test/integration/targets/iosxr_lacp/tasks/main.yaml new file mode 100644 index 00000000000..415c99d8b12 --- /dev/null +++ b/test/integration/targets/iosxr_lacp/tasks/main.yaml @@ -0,0 +1,2 @@ +--- +- { include: cli.yaml, tags: ['cli'] } diff --git a/test/integration/targets/iosxr_lacp/tests/cli/_populate.yaml b/test/integration/targets/iosxr_lacp/tests/cli/_populate.yaml new file mode 100644 index 00000000000..5f34cde033f --- /dev/null +++ b/test/integration/targets/iosxr_lacp/tests/cli/_populate.yaml @@ -0,0 +1,9 @@ +--- +- name: Setup + cli_config: + config: "{{ lines }}" + vars: + lines: | + lacp system priority 12 + lacp system mac 00c1.4c00.bd16 + diff --git a/test/integration/targets/iosxr_lacp/tests/cli/_remove_config.yaml b/test/integration/targets/iosxr_lacp/tests/cli/_remove_config.yaml new file mode 100644 index 00000000000..fcdfb194b9d --- /dev/null +++ b/test/integration/targets/iosxr_lacp/tests/cli/_remove_config.yaml @@ -0,0 +1,8 @@ +--- +- name: Remove Config + cli_config: + config: "{{ lines }}" + vars: + lines: | + no lacp system priority + no lacp system mac diff --git a/test/integration/targets/iosxr_lacp/tests/cli/deleted.yaml b/test/integration/targets/iosxr_lacp/tests/cli/deleted.yaml new file mode 100644 index 00000000000..2d8e537d11f --- /dev/null +++ b/test/integration/targets/iosxr_lacp/tests/cli/deleted.yaml @@ -0,0 +1,45 @@ +--- +- debug: + msg: "Start iosxr_lacp deleted integration tests ansible_connection={{ ansible_connection }}" + +- include_tasks: _remove_config.yaml + +- include_tasks: _populate.yaml + +- block: + - name: Delete attributes of given interfaces + iosxr_lacp: &deleted + state: deleted + register: result + + - name: Assert that the before dicts were correctly generated + assert: + that: + - "{{ populate == result['before'] }}" + + - name: Assert that the correct set of commands were generated + assert: + that: + - "{{ deleted['commands'] | symmetric_difference(result['commands']) |length == 0 }}" + + - name: Assert that the after dicts were correctly generated + assert: + that: + - "{{ deleted['after'] == result['after'] }}" + + - name: Delete attributes of given interfaces (IDEMPOTENT) + iosxr_lacp: *deleted + register: result + + - name: Assert that the previous task was idempotent + assert: + that: + - "result.changed == false" + + - name: Assert that the before dicts were correctly generated + assert: + that: + - "{{ deleted['after'] == result['before'] }}" + + always: + - include_tasks: _remove_config.yaml diff --git a/test/integration/targets/iosxr_lacp/tests/cli/merged.yaml b/test/integration/targets/iosxr_lacp/tests/cli/merged.yaml new file mode 100644 index 00000000000..2fbab882ef1 --- /dev/null +++ b/test/integration/targets/iosxr_lacp/tests/cli/merged.yaml @@ -0,0 +1,45 @@ +--- +- debug: + msg: "START iosxr_lacp merged integration tests on connection={{ ansible_connection }}" + +- include_tasks: _remove_config.yaml + +- block: + - name: Merge the provided configuration with the exisiting running configuration + iosxr_lacp: &merged + config: + system: + priority: 11 + mac: 00c1.4c00.bd15 + state: merged + register: result + + - name: Assert that before dicts were correctly generated + assert: + that: "{{ merged['before'] == result['before'] }}" + + - name: Assert that correct set of commands were generated + assert: + that: + - "{{ merged['commands'] | symmetric_difference(result['commands']) |length == 0 }}" + + - name: Assert that after dicts was correctly generated + assert: + that: + - "{{ merged['after'] == result['after'] }}" + + - name: Merge the provided configuration with the existing running configuration (IDEMPOTENT) + iosxr_lacp: *merged + register: result + + - name: Assert that the previous task was idempotent + assert: + that: + - "result['changed'] == false" + + - name: Assert that before dicts were correctly generated + assert: + that: + - "{{ merged['after'] == result['before']}}" + always: + - include_tasks: _remove_config.yaml diff --git a/test/integration/targets/iosxr_lacp/tests/cli/replaced.yaml b/test/integration/targets/iosxr_lacp/tests/cli/replaced.yaml new file mode 100644 index 00000000000..9f147b8b1fe --- /dev/null +++ b/test/integration/targets/iosxr_lacp/tests/cli/replaced.yaml @@ -0,0 +1,48 @@ +--- +- debug: + msg: "START iosxr_lacp replaced integration tests on connection={{ ansible_connection }}" + +- include_tasks: _remove_config.yaml + +- include_tasks: _populate.yaml + +- block: + - name: Replace device configurations of listed interfaces with provided configurations + iosxr_lacp: &replaced + config: + system: + priority: 11 + state: replaced + register: result + + - name: Assert that correct set of commands were generated + assert: + that: + - "{{ replaced['commands'] | symmetric_difference(result['commands']) |length == 0 }}" + + - name: Assert that before dicts are correctly generated + assert: + that: + - "{{ populate == result['before'] }}" + + - name: Assert that after dict is correctly generated + assert: + that: + - "{{ replaced['after'] == result['after'] }}" + + - name: Replace device configurations of listed interfaces with provided configurarions (IDEMPOTENT) + iosxr_lacp: *replaced + register: result + + - name: Assert that task was idempotent + assert: + that: + - "result['changed'] == false" + + - name: Assert that before dict is correctly generated + assert: + that: + - "{{ replaced['after'] == result['before'] }}" + + always: + - include_tasks: _remove_config.yaml diff --git a/test/integration/targets/iosxr_lacp/tests/cli/rtt.yaml b/test/integration/targets/iosxr_lacp/tests/cli/rtt.yaml new file mode 100644 index 00000000000..aa449ab6e66 --- /dev/null +++ b/test/integration/targets/iosxr_lacp/tests/cli/rtt.yaml @@ -0,0 +1,49 @@ +--- +- debug: + msg: "START isoxr_lacp round trip integration tests on connection={{ ansible_connection }}" + +- block: + - include_tasks: _remove_config.yaml + + - name: Apply the provided configuration (base config) + iosxr_lacp: + config: + system: + priority: 15 + mac: 00c1.4c00.bd16 + state: merged + register: base_config + + - name: Gather interfaces facts + iosxr_facts: + gather_subset: + - "!all" + - "!min" + gather_network_resources: + - lacp + + - name: Apply the provided configuration (config to be reverted) + iosxr_lacp: + config: + system: + priority: 10 + mac: 00c1.4c00.bd10 + state: merged + register: result + + - name: Assert that changes were applied + assert: + that: "{{ round_trip['after'] == result['after'] }}" + + - name: Revert back to base config using facts round trip + iosxr_lacp: + config: "{{ ansible_facts['network_resources']['lacp'] }}" + state: replaced + register: revert + + - name: Assert that config was reverted + assert: + that: "{{ base_config['after'] == revert['after'] }}" + + always: + - include_tasks: _remove_config.yaml diff --git a/test/integration/targets/iosxr_lacp/vars/main.yaml b/test/integration/targets/iosxr_lacp/vars/main.yaml new file mode 100644 index 00000000000..6c8e835ab43 --- /dev/null +++ b/test/integration/targets/iosxr_lacp/vars/main.yaml @@ -0,0 +1,40 @@ +--- +merged: + before: {} + + commands: + - "lacp system priority 11" + - "lacp system mac 00c1.4c00.bd15" + + after: + system: + priority: 11 + mac: "00c1.4c00.bd15" + +populate: + system: + priority: 12 + mac: "00c1.4c00.bd16" + +replaced: + commands: + - "no lacp system mac" + - "lacp system priority 11" + + after: + system: + priority: 11 + +deleted: + commands: + - "no lacp system priority" + - "no lacp system mac" + + after: {} + +round_trip: + after: + system: + priority: 10 + mac: "00c1.4c00.bd10" + diff --git a/test/units/modules/network/iosxr/test_iosxr_facts.py b/test/units/modules/network/iosxr/test_iosxr_facts.py index 09be247f35b..58646c30fe8 100644 --- a/test/units/modules/network/iosxr/test_iosxr_facts.py +++ b/test/units/modules/network/iosxr/test_iosxr_facts.py @@ -35,10 +35,13 @@ class TestIosxrFacts(TestIosxrModule): super(TestIosxrFacts, self).setUp() self.mock_run_commands = patch( - 'ansible.modules.network.iosxr.iosxr_facts.run_commands') + 'ansible.module_utils.network.iosxr.facts.legacy.base.run_commands') self.run_commands = self.mock_run_commands.start() - self.mock_get_capabilities = patch('ansible.modules.network.iosxr.iosxr_facts.get_capabilities') + self.mock_get_resource_connection = patch('ansible.module_utils.network.common.facts.facts.get_resource_connection') + self.get_resource_connection = self.mock_get_resource_connection.start() + + self.mock_get_capabilities = patch('ansible.module_utils.network.iosxr.facts.legacy.base.get_capabilities') self.get_capabilities = self.mock_get_capabilities.start() self.get_capabilities.return_value = { 'device_info': { @@ -55,6 +58,7 @@ class TestIosxrFacts(TestIosxrModule): self.mock_run_commands.stop() self.mock_get_capabilities.stop() + self.mock_get_resource_connection.stop() def load_fixtures(self, commands=None):