diff --git a/docs/docsite/rst/porting_guides/porting_guide_2.9.rst b/docs/docsite/rst/porting_guides/porting_guide_2.9.rst index 8490ac51a55..5e3ef2efe2f 100644 --- a/docs/docsite/rst/porting_guides/porting_guide_2.9.rst +++ b/docs/docsite/rst/porting_guides/porting_guide_2.9.rst @@ -64,6 +64,8 @@ The following modules will be removed in Ansible 2.13. Please update update your * nxos_linkagg use :ref:`nxos_lag_interfaces ` instead. +* vyos_interface use :ref:`vyos_interfaces ` instead. + Noteworthy module changes ------------------------- diff --git a/lib/ansible/module_utils/network/vyos/argspec/__init__.py b/lib/ansible/module_utils/network/vyos/argspec/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/lib/ansible/module_utils/network/vyos/argspec/facts/__init__.py b/lib/ansible/module_utils/network/vyos/argspec/facts/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/lib/ansible/module_utils/network/vyos/argspec/facts/facts.py b/lib/ansible/module_utils/network/vyos/argspec/facts/facts.py new file mode 100644 index 00000000000..0ad41a094c4 --- /dev/null +++ b/lib/ansible/module_utils/network/vyos/argspec/facts/facts.py @@ -0,0 +1,29 @@ +# 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 vyos facts module. +""" + + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +class FactsArgs(object): # pylint: disable=R0903 + """ The arg spec for the vyos facts module + """ + + def __init__(self, **kwargs): + pass + + choices = [ + 'all', + 'interfaces', + '!interfaces' + ] + + 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/vyos/argspec/interfaces/__init__.py b/lib/ansible/module_utils/network/vyos/argspec/interfaces/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/lib/ansible/module_utils/network/vyos/argspec/interfaces/interfaces.py b/lib/ansible/module_utils/network/vyos/argspec/interfaces/interfaces.py new file mode 100644 index 00000000000..d6ab4465581 --- /dev/null +++ b/lib/ansible/module_utils/network/vyos/argspec/interfaces/interfaces.py @@ -0,0 +1,67 @@ +# 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 vyos_interfaces module +""" + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +class InterfacesArgs(object): # pylint: disable=R0903 + """The arg spec for the vyos_interfaces module + """ + + def __init__(self, **kwargs): + pass + + argument_spec = \ + { + 'config': { + 'elements': 'dict', + 'options': { + 'description': {'type': 'str'}, + 'duplex': {'choices': ['full', 'half', 'auto']}, + 'enabled': {'default': True, 'type': 'bool'}, + 'mtu': {'type': 'int'}, + 'name': {'required': True, 'type': 'str'}, + 'speed': {'choices': ['auto', '10', '100', '1000', '2500', + '10000'], + 'type': 'str'}, + 'vifs': { + 'elements': 'dict', + 'options': { + 'vlan_id': {'type': 'int'}, + 'description': {'type': 'str'}, + 'enabled': {'default': True, 'type': 'bool'}, + 'mtu': {'type': 'int'} + }, + 'type': 'list' + }, + }, + 'type': 'list' + }, + 'state': {'choices': ['merged', 'replaced', + 'overridden', 'deleted'], + 'default': 'merged', + 'type': 'str'} + } # pylint: disable=C0301 diff --git a/lib/ansible/module_utils/network/vyos/config/__init__.py b/lib/ansible/module_utils/network/vyos/config/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/lib/ansible/module_utils/network/vyos/config/interfaces/__init__.py b/lib/ansible/module_utils/network/vyos/config/interfaces/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/lib/ansible/module_utils/network/vyos/config/interfaces/interfaces.py b/lib/ansible/module_utils/network/vyos/config/interfaces/interfaces.py new file mode 100644 index 00000000000..791a3194a78 --- /dev/null +++ b/lib/ansible/module_utils/network/vyos/config/interfaces/interfaces.py @@ -0,0 +1,280 @@ +# Copyright 2019 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +""" +The vyos_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 __future__ import absolute_import, division, print_function +__metaclass__ = type + + +from copy import deepcopy +from ansible.module_utils.network.common.cfg.base import ConfigBase +from ansible.module_utils.network.common.utils import to_list, dict_diff, remove_empties +from ansible.module_utils.six import iteritems +from ansible.module_utils.network.vyos.facts.facts import Facts +from ansible.module_utils.network. \ + vyos.utils.utils import search_obj_in_list, get_interface_type, dict_delete + + +class Interfaces(ConfigBase): + """ + The vyos_interfaces class + """ + + gather_subset = [ + '!all', + '!min', + ] + + gather_network_resources = [ + 'interfaces' + ] + + 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 + """ + 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 [] + 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 self._module.check_mode: + resp = self._connection.edit_config(commands, commit=False) + else: + resp = self._connection.edit_config(commands) + result['changed'] = True + + result['commands'] = commands + + if self._module._diff: + result['diff'] = resp['diff'] if result['changed'] else None + + 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 + """ + commands = [] + state = self._module.params['state'] + if state == 'overridden': + commands.extend(self._state_overridden(want=want, have=have)) + + elif state == 'deleted': + if not want: + for intf in have: + commands.extend( + self._state_deleted( + {'name': intf['name']}, + intf + ) + ) + else: + for item in want: + obj_in_have = search_obj_in_list(item['name'], have) + commands.extend( + self._state_deleted( + item, obj_in_have + ) + ) + else: + for item in want: + name = item['name'] + obj_in_have = search_obj_in_list(name, have) + + if not obj_in_have: + obj_in_have = {'name': item['name']} + + elif state == 'merged': + commands.extend( + self._state_merged( + item, obj_in_have + ) + ) + + elif state == 'replaced': + commands.extend( + self._state_replaced( + item, obj_in_have + ) + ) + + return commands + + def _state_replaced(self, 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 = [] + if have: + commands.extend(self._state_deleted(want, have)) + + commands.extend(self._state_merged(want, have)) + + return commands + + def _state_overridden(self, want, have): + """ The command generator when state is overridden + + :rtype: A list + :returns: the commands necessary to migrate the current configuration + to the desired configuration + """ + commands = [] + + for intf in have: + intf_in_want = search_obj_in_list(intf['name'], want) + if not intf_in_want: + commands.extend(self._state_deleted({'name': intf['name']}, intf)) + + for intf in want: + intf_in_have = search_obj_in_list(intf['name'], have) + commands.extend(self._state_replaced(intf, intf_in_have)) + + return commands + + def _state_merged(self, 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_copy = deepcopy(remove_empties(want)) + have_copy = deepcopy(have) + + want_vifs = want_copy.pop('vifs', []) + have_vifs = have_copy.pop('vifs', []) + + updates = dict_diff(have_copy, want_copy) + + if updates: + for key, value in iteritems(updates): + commands.append(self._compute_commands(key=key, value=value, interface=want_copy['name'])) + + if want_vifs: + for want_vif in want_vifs: + have_vif = search_obj_in_list(want_vif['vlan_id'], have_vifs, key='vlan_id') + if not have_vif: + have_vif = {'vlan_id': want_vif['vlan_id'], 'enabled': True} + + vif_updates = dict_diff(have_vif, want_vif) + if vif_updates: + for key, value in iteritems(vif_updates): + commands.append(self._compute_commands(key=key, value=value, interface=want_copy['name'], vif=want_vif['vlan_id'])) + + return commands + + def _state_deleted(self, 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_copy = deepcopy(remove_empties(want)) + have_copy = deepcopy(have) + + want_vifs = want_copy.pop('vifs', []) + have_vifs = have_copy.pop('vifs', []) + + for key in dict_delete(have_copy, want_copy).keys(): + if key == 'enabled': + continue + commands.append(self._compute_commands(key=key, interface=want_copy['name'], remove=True)) + if have_copy['enabled'] is False: + commands.append(self._compute_commands(key='enabled', value=True, interface=want_copy['name'])) + + if have_vifs: + for have_vif in have_vifs: + want_vif = search_obj_in_list(have_vif['vlan_id'], want_vifs, key='vlan_id') + if not want_vif: + want_vif = {'vlan_id': have_vif['vlan_id'], 'enabled': True} + + for key in dict_delete(have_vif, want_vif).keys(): + if key == 'enabled': + continue + commands.append(self._compute_commands(key=key, interface=want_copy['name'], vif=want_vif['vlan_id'], remove=True)) + if have_vif['enabled'] is False: + commands.append(self._compute_commands(key='enabled', value=True, interface=want_copy['name'], vif=want_vif['vlan_id'])) + + return commands + + def _compute_commands(self, interface, key, vif=None, value=None, remove=False): + intf_context = 'interfaces {0} {1}'.format(get_interface_type(interface), interface) + set_cmd = 'set {0}'.format(intf_context) + del_cmd = 'delete {0}'.format(intf_context) + + if vif: + set_cmd = set_cmd + (' vif {0}'.format(vif)) + del_cmd = del_cmd + (' vif {0}'.format(vif)) + + if key == 'enabled': + if not value: + command = "{0} disable".format(set_cmd) + else: + command = "{0} disable".format(del_cmd) + else: + if not remove: + command = "{0} {1} '{2}'".format(set_cmd, key, value) + else: + command = "{0} {1}".format(del_cmd, key) + + return command diff --git a/lib/ansible/module_utils/network/vyos/facts/__init__.py b/lib/ansible/module_utils/network/vyos/facts/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/lib/ansible/module_utils/network/vyos/facts/facts.py b/lib/ansible/module_utils/network/vyos/facts/facts.py new file mode 100644 index 00000000000..080b5cca90b --- /dev/null +++ b/lib/ansible/module_utils/network/vyos/facts/facts.py @@ -0,0 +1,59 @@ +# 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 vyos +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.vyos.argspec.facts.facts import FactsArgs +from ansible.module_utils.network.common.facts.facts import FactsBase +from ansible.module_utils.network.vyos.facts.interfaces.interfaces import InterfacesFacts +from ansible.module_utils.network.vyos.facts.legacy.base import Default, Neighbors, Config +from ansible.module_utils. \ + network.vyos.vyos import run_commands, get_capabilities + + +FACT_LEGACY_SUBSETS = dict( + default=Default, + neighbors=Neighbors, + config=Config +) +FACT_RESOURCE_SUBSETS = dict( + interfaces=InterfacesFacts, +) + + +class Facts(FactsBase): + """ The fact class for vyos + """ + + 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 vyos + + :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/vyos/facts/interfaces/__init__.py b/lib/ansible/module_utils/network/vyos/facts/interfaces/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/lib/ansible/module_utils/network/vyos/facts/interfaces/interfaces.py b/lib/ansible/module_utils/network/vyos/facts/interfaces/interfaces.py new file mode 100644 index 00000000000..325319e7bfa --- /dev/null +++ b/lib/ansible/module_utils/network/vyos/facts/interfaces/interfaces.py @@ -0,0 +1,123 @@ +# +# -*- 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 vyos 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 re import findall, M +from copy import deepcopy +from ansible.module_utils.network.common import utils +from ansible.module_utils.network.vyos.argspec.interfaces.interfaces import InterfacesArgs + + +class InterfacesFacts(object): + """ The vyos 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 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=['| grep interfaces']) + + objs = [] + interface_names = findall(r'^set interfaces (?:ethernet|bonding|vti|loopback|vxlan) (?:\'*)(\S+)(?:\'*)', + data, M) + if interface_names: + for interface in set(interface_names): + intf_regex = r' %s .+$' % interface.strip("'") + cfg = findall(intf_regex, data, M) + obj = self.render_config(cfg) + obj['name'] = interface.strip("'") + 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, 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 + """ + vif_conf = '\n'.join(filter(lambda x: ('vif' in x), conf)) + eth_conf = '\n'.join(filter(lambda x: ('vif' not in x), conf)) + config = self.parse_attribs( + ['description', 'speed', 'mtu', 'duplex'], eth_conf) + config['vifs'] = self.parse_vifs(vif_conf) + + return utils.remove_empties(config) + + def parse_vifs(self, conf): + vif_names = findall(r'vif (?:\'*)(\d+)(?:\'*)', conf, M) + vifs_list = None + + if vif_names: + vifs_list = [] + for vif in set(vif_names): + vif_regex = r' %s .+$' % vif + cfg = '\n'.join(findall(vif_regex, conf, M)) + obj = self.parse_attribs(['description', 'mtu'], cfg) + obj['vlan_id'] = int(vif) + if obj: + vifs_list.append(obj) + vifs_list = sorted(vifs_list, key=lambda i: i['vlan_id']) + + return vifs_list + + def parse_attribs(self, attribs, conf): + config = {} + for item in attribs: + value = utils.parse_conf_arg(conf, item) + if value and item == 'mtu': + config[item] = int(value.strip("'")) + elif value: + config[item] = value.strip("'") + else: + config[item] = None + if 'disable' in conf: + config['enabled'] = False + else: + config['enabled'] = True + + return utils.remove_empties(config) diff --git a/lib/ansible/module_utils/network/vyos/facts/legacy/__init__.py b/lib/ansible/module_utils/network/vyos/facts/legacy/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/lib/ansible/module_utils/network/vyos/facts/legacy/base.py b/lib/ansible/module_utils/network/vyos/facts/legacy/base.py new file mode 100644 index 00000000000..cdf0cceb5b6 --- /dev/null +++ b/lib/ansible/module_utils/network/vyos/facts/legacy/base.py @@ -0,0 +1,161 @@ +# -*- 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 VyOS 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 + + +import platform +import re +from ansible.module_utils. \ + network.vyos.vyos import run_commands, get_capabilities + + +class LegacyFactsBase(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)) + + +class Default(LegacyFactsBase): + + COMMANDS = [ + 'show version', + ] + + def populate(self): + super(Default, self).populate() + data = self.responses[0] + self.facts['serialnum'] = self.parse_serialnum(data) + self.facts.update(self.platform_facts()) + + def parse_serialnum(self, data): + match = re.search(r'HW S/N:\s+(\S+)', data) + if match: + return match.group(1) + + 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 Config(LegacyFactsBase): + + COMMANDS = [ + 'show configuration commands', + 'show system commit', + ] + + def populate(self): + super(Config, self).populate() + + self.facts['config'] = self.responses + + commits = self.responses[1] + entries = list() + entry = None + + for line in commits.split('\n'): + match = re.match(r'(\d+)\s+(.+)by(.+)via(.+)', line) + if match: + if entry: + entries.append(entry) + + entry = dict(revision=match.group(1), + datetime=match.group(2), + by=str(match.group(3)).strip(), + via=str(match.group(4)).strip(), + comment=None) + else: + entry['comment'] = line.strip() + + self.facts['commits'] = entries + + +class Neighbors(LegacyFactsBase): + + COMMANDS = [ + 'show lldp neighbors', + 'show lldp neighbors detail', + ] + + def populate(self): + super(Neighbors, self).populate() + + all_neighbors = self.responses[0] + if 'LLDP not configured' not in all_neighbors: + neighbors = self.parse( + self.responses[1] + ) + self.facts['neighbors'] = self.parse_neighbors(neighbors) + + def parse(self, data): + parsed = list() + values = None + for line in data.split('\n'): + if not line: + continue + elif line[0] == ' ': + values += '\n%s' % line + elif line.startswith('Interface'): + if values: + parsed.append(values) + values = line + if values: + parsed.append(values) + return parsed + + def parse_neighbors(self, data): + facts = dict() + for item in data: + interface = self.parse_interface(item) + host = self.parse_host(item) + port = self.parse_port(item) + if interface not in facts: + facts[interface] = list() + facts[interface].append(dict(host=host, port=port)) + return facts + + def parse_interface(self, data): + match = re.search(r'^Interface:\s+(\S+),', data) + return match.group(1) + + def parse_host(self, data): + match = re.search(r'SysName:\s+(.+)$', data, re.M) + if match: + return match.group(1) + + def parse_port(self, data): + match = re.search(r'PortDescr:\s+(.+)$', data, re.M) + if match: + return match.group(1) diff --git a/lib/ansible/module_utils/network/vyos/utils/__init__.py b/lib/ansible/module_utils/network/vyos/utils/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/lib/ansible/module_utils/network/vyos/utils/utils.py b/lib/ansible/module_utils/network/vyos/utils/utils.py new file mode 100644 index 00000000000..dbfe4227a07 --- /dev/null +++ b/lib/ansible/module_utils/network/vyos/utils/utils.py @@ -0,0 +1,55 @@ +# -*- coding: utf-8 -*- +# Copyright 2019 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +# utils + + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +def search_obj_in_list(name, lst, key='name'): + for item in lst: + if item[key] == name: + return item + return None + + +def get_interface_type(interface): + """Gets the type of interface + """ + if interface.startswith('eth'): + return 'ethernet' + elif interface.startswith('bond'): + return 'bonding' + elif interface.startswith('vti'): + return 'vti' + elif interface.startswith('lo'): + return 'loopback' + + +def dict_delete(base, comparable): + """ + This function generates a dict containing key, value pairs for keys + that are present in the `base` dict but not present in the `comparable` + dict. + + :param base: dict object to base the diff on + :param comparable: dict object to compare against base + :returns: new dict object with key, value pairs that needs to be deleted. + + """ + to_delete = dict() + + for key in base: + if isinstance(base[key], dict): + sub_diff = dict_delete(base[key], comparable.get(key, {})) + if sub_diff: + to_delete[key] = sub_diff + else: + if key not in comparable: + to_delete[key] = base[key] + + return to_delete diff --git a/lib/ansible/modules/network/vyos/vyos_interface.py b/lib/ansible/modules/network/vyos/_vyos_interface.py similarity index 98% rename from lib/ansible/modules/network/vyos/vyos_interface.py rename to lib/ansible/modules/network/vyos/_vyos_interface.py index 4b97ea55df1..f6652b1fd31 100644 --- a/lib/ansible/modules/network/vyos/vyos_interface.py +++ b/lib/ansible/modules/network/vyos/_vyos_interface.py @@ -20,7 +20,7 @@ # ANSIBLE_METADATA = {'metadata_version': '1.1', - 'status': ['preview'], + 'status': ['deprecated'], 'supported_by': 'network'} @@ -33,6 +33,10 @@ short_description: Manage Interface on VyOS network devices description: - This module provides declarative management of Interfaces on VyOS network devices. +deprecated: + removed_in: '2.13' + alternative: vyos_interfaces + why: Updated modules released with more functionality. notes: - Tested against VYOS 1.1.7 options: diff --git a/lib/ansible/modules/network/vyos/vyos_facts.py b/lib/ansible/modules/network/vyos/vyos_facts.py index c4a69745677..ef2682bec35 100644 --- a/lib/ansible/modules/network/vyos/vyos_facts.py +++ b/lib/ansible/modules/network/vyos/vyos_facts.py @@ -1,65 +1,90 @@ #!/usr/bin/python -# -# This file is part of Ansible -# -# Ansible is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Ansible is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Ansible. If not, see . -# +# -*- 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 vyos_facts +""" + + ANSIBLE_METADATA = {'metadata_version': '1.1', - 'status': ['preview'], + 'status': [u'preview'], 'supported_by': 'network'} DOCUMENTATION = """ --- module: vyos_facts -version_added: "2.2" -author: "Nathaniel Case (@Qalthos)" -short_description: Collect facts from remote devices running VyOS +version_added: 2.2 +short_description: Get facts about vyos devices. description: - - Collects a base set of device facts from a remote device that - is running VyOS. This module prepends all of the - base network fact keys with U(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 vyos 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: + - Nathaniel Case (@qalthos) + - Nilashish Chakraborty (@Nilashishc) extends_documentation_fragment: vyos notes: - - Tested against VYOS 1.1.7 + - Tested against VyOS 1.1.8 options: gather_subset: description: - When supplied, this argument will restrict the facts collected to a given subset. Possible values for this argument include - all, default, config, and neighbors. Can specify a list of - values to include a larger subset. Values can also be used + all, default, config, and neighbors. 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" + 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. + 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" + choices: ['all', 'interfaces', '!interfaces'] """ EXAMPLES = """ -- name: collect all facts from the device - vyos_facts: +# Gather all facts +- vyos_facts: gather_subset: all + gather_network_resources: all -- name: collect only the config and default facts - vyos_facts: +# collect only the config and default facts +- vyos_facts: gather_subset: config -- name: collect everything exception the config - vyos_facts: +# collect everything exception the config +- vyos_facts: gather_subset: "!config" + +# Collect only the interfaces facts +- vyos_facts: + gather_subset: + - '!all' + - '!min' + gather_network_resources: + - interfaces + +# Do not collect interfaces facts +- vyos_facts: + gather_network_resources: + - "!interfaces" + +# Collect interfaces and minimal default facts +- vyos_facts: + gather_subset: min + gather_network_resources: interfaces """ RETURN = """ @@ -103,228 +128,38 @@ ansible_net_python_version: description: The Python version Ansible controller is using returned: always type: str +ansible_net_gather_network_resources: + description: The list of fact resource subsets collected from the device + returned: always + type: list """ -import platform -import re - from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils.six import iteritems -from ansible.module_utils.network.vyos.vyos import run_commands, get_capabilities +from ansible.module_utils.network.vyos.argspec.facts.facts import FactsArgs +from ansible.module_utils.network.vyos.facts.facts import Facts from ansible.module_utils.network.vyos.vyos import vyos_argument_spec -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)) - - -class Default(FactsBase): - - COMMANDS = [ - 'show version', - ] - - def populate(self): - super(Default, self).populate() - data = self.responses[0] - self.facts['serialnum'] = self.parse_serialnum(data) - self.facts.update(self.platform_facts()) - - def parse_serialnum(self, data): - match = re.search(r'HW S/N:\s+(\S+)', data) - if match: - return match.group(1) - - 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 Config(FactsBase): - - COMMANDS = [ - 'show configuration commands', - 'show system commit', - ] - - def populate(self): - super(Config, self).populate() - - self.facts['config'] = self.responses - - commits = self.responses[1] - entries = list() - entry = None - - for line in commits.split('\n'): - match = re.match(r'(\d+)\s+(.+)by(.+)via(.+)', line) - if match: - if entry: - entries.append(entry) - - entry = dict(revision=match.group(1), - datetime=match.group(2), - by=str(match.group(3)).strip(), - via=str(match.group(4)).strip(), - comment=None) - else: - entry['comment'] = line.strip() - - self.facts['commits'] = entries - - -class Neighbors(FactsBase): - - COMMANDS = [ - 'show lldp neighbors', - 'show lldp neighbors detail', - ] - - def populate(self): - super(Neighbors, self).populate() - - all_neighbors = self.responses[0] - if 'LLDP not configured' not in all_neighbors: - neighbors = self.parse( - self.responses[1] - ) - self.facts['neighbors'] = self.parse_neighbors(neighbors) - - def parse(self, data): - parsed = list() - values = None - for line in data.split('\n'): - if not line: - continue - elif line[0] == ' ': - values += '\n%s' % line - elif line.startswith('Interface'): - if values: - parsed.append(values) - values = line - if values: - parsed.append(values) - return parsed - - def parse_neighbors(self, data): - facts = dict() - for item in data: - interface = self.parse_interface(item) - host = self.parse_host(item) - port = self.parse_port(item) - if interface not in facts: - facts[interface] = list() - facts[interface].append(dict(host=host, port=port)) - return facts - - def parse_interface(self, data): - match = re.search(r'^Interface:\s+(\S+),', data) - return match.group(1) - - def parse_host(self, data): - match = re.search(r'SysName:\s+(.+)$', data, re.M) - if match: - return match.group(1) - - def parse_port(self, data): - match = re.search(r'PortDescr:\s+(.+)$', data, re.M) - if match: - return match.group(1) - - -FACT_SUBSETS = dict( - default=Default, - neighbors=Neighbors, - config=Config -) - -VALID_SUBSETS = frozenset(FACT_SUBSETS.keys()) - - def main(): - argument_spec = dict( - gather_subset=dict(default=['!config'], type='list') - ) + """ + Main entry point for module execution + + :returns: ansible_facts + """ + argument_spec = FactsArgs.argument_spec argument_spec.update(vyos_argument_spec) module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) - 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='Subset must be one of [%s], got %s' % - (', '.join(VALID_SUBSETS), 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)) + warnings = ['default value for `gather_subset` ' + 'will be changed to `min` from `!config` v2.11 onwards'] - 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/vyos/vyos_interfaces.py b/lib/ansible/modules/network/vyos/vyos_interfaces.py new file mode 100644 index 00000000000..c8733fdaf98 --- /dev/null +++ b/lib/ansible/modules/network/vyos/vyos_interfaces.py @@ -0,0 +1,876 @@ +#!/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 vyos_interfaces +""" + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +ANSIBLE_METADATA = { + 'metadata_version': '1.1', + 'status': ['preview'], + 'supported_by': 'network' +} + +DOCUMENTATION = """ +--- +module: vyos_interfaces +version_added: 2.9 +short_description: Manages interface attributes of VyOS network devices. +description: + - This module manages the interface attributes on VyOS network devices. + - This module supports managing base attributes of Ethernet, Bonding, + VXLAN, Loopback and Virtual Tunnel Interfaces. +author: Nilashish Chakraborty (@nilashishc) +options: + config: + description: The provided interfaces configuration. + type: list + suboptions: + name: + description: + - Full name of the interface, e.g. eth0, eth1, bond0, vti1, vxlan2. + type: str + required: True + description: + description: + - Interface description. + type: str + duplex: + description: + - Interface duplex mode. + - Applicable for Ethernet interfaces only. + choices: ['full', 'half', 'auto'] + type: str + enabled: + 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 + mtu: + description: + - MTU for a specific interface. Refer to vendor documentation for valid values. + - Applicable for Ethernet, Bonding, VXLAN and Virtual Tunnel interfaces. + type: int + speed: + description: + - Interface link speed. + - Applicable for Ethernet interfaces only. + type: str + choices: ['auto', '10', '100', '1000', '2500', '10000'] + vifs: + description: + - Virtual sub-interfaces related configuration. + - 802.1Q VLAN interfaces are represented as virtual sub-interfaces in VyOS. + type: list + suboptions: + vlan_id: + description: + - Identifier for the virtual sub-interface. + type: int + description: + description: + - Virtual sub-interface description. + type: str + enabled: + description: + - Administrative state of the virtual sub-interface. + - Set the value to C(true) to administratively enable + the interface or C(false) to disable it. + type: bool + default: True + mtu: + description: + - MTU for the virtual sub-interface. + - Refer to vendor documentation for valid values. + type: int + state: + description: + - The state the configuration should be left in. + type: str + choices: + - merged + - replaced + - overridden + - deleted + default: merged +""" +EXAMPLES = """ +# Using merged +# +# ------------- +# Before state: +# ------------- +# +# vyos@vyos:~$ show configuration commands | grep interfaces +# set interfaces ethernet eth0 address 'dhcp' +# set interfaces ethernet eth0 address 'dhcpv6' +# set interfaces ethernet eth0 duplex 'auto' +# set interfaces ethernet eth0 hw-id '08:00:27:30:f0:22' +# set interfaces ethernet eth0 smp-affinity 'auto' +# set interfaces ethernet eth0 speed 'auto' +# set interfaces ethernet eth1 hw-id '08:00:27:ea:0f:b9' +# set interfaces ethernet eth1 smp-affinity 'auto' +# set interfaces ethernet eth2 hw-id '08:00:27:c2:98:23' +# set interfaces ethernet eth2 smp-affinity 'auto' +# set interfaces ethernet eth3 hw-id '08:00:27:43:70:8c' +# set interfaces loopback lo + +- name: Merge provided configuration with device configuration + vyos_interfaces: + config: + - name: eth2 + description: 'Configured by Ansible' + enabled: True + vifs: + - vlan_id: 200 + description: "VIF 200 - ETH2" + + - name: eth3 + description: 'Configured by Ansible' + mtu: 1500 + + - name: bond1 + description: 'Bond - 1' + mtu: 1200 + + - name: vti2 + description: 'VTI - 2' + enabled: false + state: merged +# +# +# ------------------------- +# Module Execution Result +# ------------------------- +# +# "before": [ +# { +# "enabled": true, +# "name": "lo" +# }, +# { +# "enabled": true, +# "name": "eth3" +# }, +# { +# "enabled": true, +# "name": "eth2" +# }, +# { +# "enabled": true, +# "name": "eth1" +# }, +# { +# "duplex": "auto", +# "enabled": true, +# "name": "eth0", +# "speed": "auto" +# } +# ] +# +# "commands": [ +# "set interfaces ethernet eth2 description 'Configured by Ansible'", +# "set interfaces ethernet eth2 vif 200", +# "set interfaces ethernet eth2 vif 200 description 'VIF 200 - ETH2'", +# "set interfaces ethernet eth3 description 'Configured by Ansible'", +# "set interfaces ethernet eth3 mtu '1500'", +# "set interfaces bonding bond1", +# "set interfaces bonding bond1 description 'Bond - 1'", +# "set interfaces bonding bond1 mtu '1200'", +# "set interfaces vti vti2", +# "set interfaces vti vti2 description 'VTI - 2'", +# "set interfaces vti vti2 disable" +# ] +# +# "after": [ +# { +# "description": "Bond - 1", +# "enabled": true, +# "mtu": 1200, +# "name": "bond1" +# }, +# { +# "enabled": true, +# "name": "lo" +# }, +# { +# "description": "VTI - 2", +# "enabled": false, +# "name": "vti2" +# }, +# { +# "description": "Configured by Ansible", +# "enabled": true, +# "mtu": 1500, +# "name": "eth3" +# }, +# { +# "description": "Configured by Ansible", +# "enabled": true, +# "name": "eth2", +# "vifs": [ +# { +# "description": "VIF 200 - ETH2", +# "enabled": true, +# "vlan_id": "200" +# } +# ] +# }, +# { +# "enabled": true, +# "name": "eth1" +# }, +# { +# "duplex": "auto", +# "enabled": true, +# "name": "eth0", +# "speed": "auto" +# } +# ] +# +# +# ------------- +# After state: +# ------------- +# +# vyos@vyos:~$ show configuration commands | grep interfaces +# set interfaces bonding bond1 description 'Bond - 1' +# set interfaces bonding bond1 mtu '1200' +# set interfaces ethernet eth0 address 'dhcp' +# set interfaces ethernet eth0 address 'dhcpv6' +# set interfaces ethernet eth0 duplex 'auto' +# set interfaces ethernet eth0 hw-id '08:00:27:30:f0:22' +# set interfaces ethernet eth0 smp-affinity 'auto' +# set interfaces ethernet eth0 speed 'auto' +# set interfaces ethernet eth1 hw-id '08:00:27:ea:0f:b9' +# set interfaces ethernet eth1 smp-affinity 'auto' +# set interfaces ethernet eth2 description 'Configured by Ansible' +# set interfaces ethernet eth2 hw-id '08:00:27:c2:98:23' +# set interfaces ethernet eth2 smp-affinity 'auto' +# set interfaces ethernet eth2 vif 200 description 'VIF 200 - ETH2' +# set interfaces ethernet eth3 description 'Configured by Ansible' +# set interfaces ethernet eth3 hw-id '08:00:27:43:70:8c' +# set interfaces ethernet eth3 mtu '1500' +# set interfaces loopback lo +# set interfaces vti vti2 description 'VTI - 2' +# set interfaces vti vti2 disable +# + + +# Using replaced +# +# ------------- +# Before state: +# ------------- +# +# vyos:~$ show configuration commands | grep eth +# set interfaces bonding bond1 description 'Bond - 1' +# set interfaces bonding bond1 mtu '1400' +# set interfaces ethernet eth0 address 'dhcp' +# set interfaces ethernet eth0 description 'Management Interface for the Appliance' +# set interfaces ethernet eth0 duplex 'auto' +# set interfaces ethernet eth0 hw-id '08:00:27:f3:6c:b5' +# set interfaces ethernet eth0 smp_affinity 'auto' +# set interfaces ethernet eth0 speed 'auto' +# set interfaces ethernet eth1 description 'Configured by Ansible Eng Team' +# set interfaces ethernet eth1 duplex 'full' +# set interfaces ethernet eth1 hw-id '08:00:27:ad:ef:65' +# set interfaces ethernet eth1 smp_affinity 'auto' +# set interfaces ethernet eth1 speed '100' +# set interfaces ethernet eth2 description 'Configured by Ansible' +# set interfaces ethernet eth2 duplex 'full' +# set interfaces ethernet eth2 hw-id '08:00:27:ab:4e:79' +# set interfaces ethernet eth2 mtu '500' +# set interfaces ethernet eth2 smp_affinity 'auto' +# set interfaces ethernet eth2 speed '100' +# set interfaces ethernet eth2 vif 200 description 'Configured by Ansible' +# set interfaces ethernet eth3 description 'Configured by Ansible' +# set interfaces ethernet eth3 duplex 'full' +# set interfaces ethernet eth3 hw-id '08:00:27:17:3c:85' +# set interfaces ethernet eth3 mtu '1500' +# set interfaces ethernet eth3 smp_affinity 'auto' +# set interfaces ethernet eth3 speed '100' +# set interfaces loopback lo +# +# +- name: Replace device configurations of listed interfaces with provided configurations + vyos_interfaces: + config: + - name: eth2 + description: "Replaced by Ansible" + + - name: eth3 + description: "Replaced by Ansible" + + - name: eth1 + description: "Replaced by Ansible" + state: replaced +# +# +# ----------------------- +# Module Execution Result +# ----------------------- +# +# "before": [ +# { +# "description": "Bond - 1", +# "enabled": true, +# "mtu": 1400, +# "name": "bond1" +# }, +# { +# "enabled": true, +# "name": "lo" +# }, +# { +# "description": "Configured by Ansible", +# "duplex": "full", +# "enabled": true, +# "mtu": 1500, +# "name": "eth3", +# "speed": "100" +# }, +# { +# "description": "Configured by Ansible", +# "duplex": "full", +# "enabled": true, +# "mtu": 500, +# "name": "eth2", +# "speed": "100", +# "vifs": [ +# { +# "description": "VIF 200 - ETH2", +# "enabled": true, +# "vlan_id": "200" +# } +# ] +# }, +# { +# "description": "Configured by Ansible Eng Team", +# "duplex": "full", +# "enabled": true, +# "name": "eth1", +# "speed": "100" +# }, +# { +# "description": "Management Interface for the Appliance", +# "duplex": "auto", +# "enabled": true, +# "name": "eth0", +# "speed": "auto" +# } +# ] +# +# "commands": [ +# "delete interfaces ethernet eth2 speed", +# "delete interfaces ethernet eth2 duplex", +# "delete interfaces ethernet eth2 mtu", +# "delete interfaces ethernet eth2 vif 200 description", +# "set interfaces ethernet eth2 description 'Replaced by Ansible'", +# "delete interfaces ethernet eth3 speed", +# "delete interfaces ethernet eth3 duplex", +# "delete interfaces ethernet eth3 mtu", +# "set interfaces ethernet eth3 description 'Replaced by Ansible'", +# "delete interfaces ethernet eth1 speed", +# "delete interfaces ethernet eth1 duplex", +# "set interfaces ethernet eth1 description 'Replaced by Ansible'" +# ] +# +# "after": [ +# { +# "description": "Bond - 1", +# "enabled": true, +# "mtu": 1400, +# "name": "bond1" +# }, +# { +# "enabled": true, +# "name": "lo" +# }, +# { +# "description": "Replaced by Ansible", +# "enabled": true, +# "name": "eth3" +# }, +# { +# "description": "Replaced by Ansible", +# "enabled": true, +# "name": "eth2", +# "vifs": [ +# { +# "enabled": true, +# "vlan_id": "200" +# } +# ] +# }, +# { +# "description": "Replaced by Ansible", +# "enabled": true, +# "name": "eth1" +# }, +# { +# "description": "Management Interface for the Appliance", +# "duplex": "auto", +# "enabled": true, +# "name": "eth0", +# "speed": "auto" +# } +# ] +# +# +# ------------- +# After state: +# ------------- +# +# vyos@vyos:~$ show configuration commands | grep interfaces +# set interfaces bonding bond1 description 'Bond - 1' +# set interfaces bonding bond1 mtu '1400' +# set interfaces ethernet eth0 address 'dhcp' +# set interfaces ethernet eth0 address 'dhcpv6' +# set interfaces ethernet eth0 description 'Management Interface for the Appliance' +# set interfaces ethernet eth0 duplex 'auto' +# set interfaces ethernet eth0 hw-id '08:00:27:30:f0:22' +# set interfaces ethernet eth0 smp-affinity 'auto' +# set interfaces ethernet eth0 speed 'auto' +# set interfaces ethernet eth1 description 'Replaced by Ansible' +# set interfaces ethernet eth1 hw-id '08:00:27:ea:0f:b9' +# set interfaces ethernet eth1 smp-affinity 'auto' +# set interfaces ethernet eth2 description 'Replaced by Ansible' +# set interfaces ethernet eth2 hw-id '08:00:27:c2:98:23' +# set interfaces ethernet eth2 smp-affinity 'auto' +# set interfaces ethernet eth2 vif 200 +# set interfaces ethernet eth3 description 'Replaced by Ansible' +# set interfaces ethernet eth3 hw-id '08:00:27:43:70:8c' +# set interfaces loopback lo +# +# +# Using overridden +# +# +# -------------- +# Before state +# -------------- +# +# vyos@vyos:~$ show configuration commands | grep interfaces +# set interfaces ethernet eth0 address 'dhcp' +# set interfaces ethernet eth0 address 'dhcpv6' +# set interfaces ethernet eth0 description 'Ethernet Interface - 0' +# set interfaces ethernet eth0 duplex 'auto' +# set interfaces ethernet eth0 hw-id '08:00:27:30:f0:22' +# set interfaces ethernet eth0 mtu '1200' +# set interfaces ethernet eth0 smp-affinity 'auto' +# set interfaces ethernet eth0 speed 'auto' +# set interfaces ethernet eth1 description 'Configured by Ansible Eng Team' +# set interfaces ethernet eth1 hw-id '08:00:27:ea:0f:b9' +# set interfaces ethernet eth1 mtu '100' +# set interfaces ethernet eth1 smp-affinity 'auto' +# set interfaces ethernet eth1 vif 100 description 'VIF 100 - ETH1' +# set interfaces ethernet eth1 vif 100 disable +# set interfaces ethernet eth2 description 'Configured by Ansible Team (Admin Down)' +# set interfaces ethernet eth2 disable +# set interfaces ethernet eth2 hw-id '08:00:27:c2:98:23' +# set interfaces ethernet eth2 mtu '600' +# set interfaces ethernet eth2 smp-affinity 'auto' +# set interfaces ethernet eth3 description 'Configured by Ansible Network' +# set interfaces ethernet eth3 hw-id '08:00:27:43:70:8c' +# set interfaces loopback lo +# set interfaces vti vti1 description 'Virtual Tunnel Interface - 1' +# set interfaces vti vti1 mtu '68' +# +# +- name: Overrides all device configuration with provided configuration + vyos_interfaces: + config: + - name: eth0 + description: Outbound Interface For The Appliance + speed: auto + duplex: auto + + - name: eth2 + speed: auto + duplex: auto + + - name: eth3 + mtu: 1200 + state: overridden +# +# +# ------------------------ +# Module Execution Result +# ------------------------ +# +# "before": [ +# { +# "enabled": true, +# "name": "lo" +# }, +# { +# "description": "Virtual Tunnel Interface - 1", +# "enabled": true, +# "mtu": 68, +# "name": "vti1" +# }, +# { +# "description": "Configured by Ansible Network", +# "enabled": true, +# "name": "eth3" +# }, +# { +# "description": "Configured by Ansible Team (Admin Down)", +# "enabled": false, +# "mtu": 600, +# "name": "eth2" +# }, +# { +# "description": "Configured by Ansible Eng Team", +# "enabled": true, +# "mtu": 100, +# "name": "eth1", +# "vifs": [ +# { +# "description": "VIF 100 - ETH1", +# "enabled": false, +# "vlan_id": "100" +# } +# ] +# }, +# { +# "description": "Ethernet Interface - 0", +# "duplex": "auto", +# "enabled": true, +# "mtu": 1200, +# "name": "eth0", +# "speed": "auto" +# } +# ] +# +# "commands": [ +# "delete interfaces vti vti1 description", +# "delete interfaces vti vti1 mtu", +# "delete interfaces ethernet eth1 description", +# "delete interfaces ethernet eth1 mtu", +# "delete interfaces ethernet eth1 vif 100 description", +# "delete interfaces ethernet eth1 vif 100 disable", +# "delete interfaces ethernet eth0 mtu", +# "set interfaces ethernet eth0 description 'Outbound Interface For The Appliance'", +# "delete interfaces ethernet eth2 description", +# "delete interfaces ethernet eth2 mtu", +# "set interfaces ethernet eth2 duplex 'auto'", +# "delete interfaces ethernet eth2 disable", +# "set interfaces ethernet eth2 speed 'auto'", +# "delete interfaces ethernet eth3 description", +# "set interfaces ethernet eth3 mtu '1200'" +# ], +# +# "after": [ +# { +# "enabled": true, +# "name": "lo" +# }, +# { +# "enabled": true, +# "name": "vti1" +# }, +# { +# "enabled": true, +# "mtu": 1200, +# "name": "eth3" +# }, +# { +# "duplex": "auto", +# "enabled": true, +# "name": "eth2", +# "speed": "auto" +# }, +# { +# "enabled": true, +# "name": "eth1", +# "vifs": [ +# { +# "enabled": true, +# "vlan_id": "100" +# } +# ] +# }, +# { +# "description": "Outbound Interface For The Appliance", +# "duplex": "auto", +# "enabled": true, +# "name": "eth0", +# "speed": "auto" +# } +# ] +# +# +# ------------ +# After state +# ------------ +# +# vyos@vyos:~$ show configuration commands | grep interfaces +# set interfaces ethernet eth0 address 'dhcp' +# set interfaces ethernet eth0 address 'dhcpv6' +# set interfaces ethernet eth0 description 'Outbound Interface For The Appliance' +# set interfaces ethernet eth0 duplex 'auto' +# set interfaces ethernet eth0 hw-id '08:00:27:30:f0:22' +# set interfaces ethernet eth0 smp-affinity 'auto' +# set interfaces ethernet eth0 speed 'auto' +# set interfaces ethernet eth1 hw-id '08:00:27:ea:0f:b9' +# set interfaces ethernet eth1 smp-affinity 'auto' +# set interfaces ethernet eth1 vif 100 +# set interfaces ethernet eth2 duplex 'auto' +# set interfaces ethernet eth2 hw-id '08:00:27:c2:98:23' +# set interfaces ethernet eth2 smp-affinity 'auto' +# set interfaces ethernet eth2 speed 'auto' +# set interfaces ethernet eth3 hw-id '08:00:27:43:70:8c' +# set interfaces ethernet eth3 mtu '1200' +# set interfaces loopback lo +# set interfaces vti vti1 +# +# +# Using deleted +# +# +# ------------- +# Before state +# ------------- +# +# vyos@vyos:~$ show configuration commands | grep interfaces +# set interfaces bonding bond0 mtu '1300' +# set interfaces bonding bond1 description 'LAG - 1' +# set interfaces ethernet eth0 address 'dhcp' +# set interfaces ethernet eth0 address 'dhcpv6' +# set interfaces ethernet eth0 description 'Outbound Interface for this appliance' +# set interfaces ethernet eth0 duplex 'auto' +# set interfaces ethernet eth0 hw-id '08:00:27:30:f0:22' +# set interfaces ethernet eth0 smp-affinity 'auto' +# set interfaces ethernet eth0 speed 'auto' +# set interfaces ethernet eth1 description 'Configured by Ansible Network' +# set interfaces ethernet eth1 duplex 'full' +# set interfaces ethernet eth1 hw-id '08:00:27:ea:0f:b9' +# set interfaces ethernet eth1 smp-affinity 'auto' +# set interfaces ethernet eth1 speed '100' +# set interfaces ethernet eth2 description 'Configured by Ansible' +# set interfaces ethernet eth2 disable +# set interfaces ethernet eth2 duplex 'full' +# set interfaces ethernet eth2 hw-id '08:00:27:c2:98:23' +# set interfaces ethernet eth2 mtu '600' +# set interfaces ethernet eth2 smp-affinity 'auto' +# set interfaces ethernet eth2 speed '100' +# set interfaces ethernet eth3 description 'Configured by Ansible Network' +# set interfaces ethernet eth3 duplex 'full' +# set interfaces ethernet eth3 hw-id '08:00:27:43:70:8c' +# set interfaces ethernet eth3 speed '100' +# set interfaces loopback lo +# +# +- name: Delete attributes of given interfaces (Note - This won't delete the interfaces themselves) + vyos_interfaces: + config: + - name: bond1 + + - name: eth1 + + - name: eth2 + + - name: eth3 + state: deleted +# +# +# ------------------------ +# Module Execution Results +# ------------------------ +# +# "before": [ +# { +# "enabled": true, +# "mtu": 1300, +# "name": "bond0" +# }, +# { +# "description": "LAG - 1", +# "enabled": true, +# "name": "bond1" +# }, +# { +# "enabled": true, +# "name": "lo" +# }, +# { +# "description": "Configured by Ansible Network", +# "duplex": "full", +# "enabled": true, +# "name": "eth3", +# "speed": "100" +# }, +# { +# "description": "Configured by Ansible", +# "duplex": "full", +# "enabled": false, +# "mtu": 600, +# "name": "eth2", +# "speed": "100" +# }, +# { +# "description": "Configured by Ansible Network", +# "duplex": "full", +# "enabled": true, +# "name": "eth1", +# "speed": "100" +# }, +# { +# "description": "Outbound Interface for this appliance", +# "duplex": "auto", +# "enabled": true, +# "name": "eth0", +# "speed": "auto" +# } +# ] +# +# "commands": [ +# "delete interfaces bonding bond1 description", +# "delete interfaces ethernet eth1 speed", +# "delete interfaces ethernet eth1 duplex", +# "delete interfaces ethernet eth1 description", +# "delete interfaces ethernet eth2 speed", +# "delete interfaces ethernet eth2 disable", +# "delete interfaces ethernet eth2 duplex", +# "delete interfaces ethernet eth2 disable", +# "delete interfaces ethernet eth2 description", +# "delete interfaces ethernet eth2 disable", +# "delete interfaces ethernet eth2 mtu", +# "delete interfaces ethernet eth2 disable", +# "delete interfaces ethernet eth3 speed", +# "delete interfaces ethernet eth3 duplex", +# "delete interfaces ethernet eth3 description" +# ] +# +# "after": [ +# { +# "enabled": true, +# "mtu": 1300, +# "name": "bond0" +# }, +# { +# "enabled": true, +# "name": "bond1" +# }, +# { +# "enabled": true, +# "name": "lo" +# }, +# { +# "enabled": true, +# "name": "eth3" +# }, +# { +# "enabled": true, +# "name": "eth2" +# }, +# { +# "enabled": true, +# "name": "eth1" +# }, +# { +# "description": "Outbound Interface for this appliance", +# "duplex": "auto", +# "enabled": true, +# "name": "eth0", +# "speed": "auto" +# } +# ] +# +# +# ------------ +# After state +# ------------ +# +# vyos@vyos:~$ show configuration commands | grep interfaces +# set interfaces bonding bond0 mtu '1300' +# set interfaces bonding bond1 +# set interfaces ethernet eth0 address 'dhcp' +# set interfaces ethernet eth0 address 'dhcpv6' +# set interfaces ethernet eth0 description 'Outbound Interface for this appliance' +# set interfaces ethernet eth0 duplex 'auto' +# set interfaces ethernet eth0 hw-id '08:00:27:30:f0:22' +# set interfaces ethernet eth0 smp-affinity 'auto' +# set interfaces ethernet eth0 speed 'auto' +# set interfaces ethernet eth1 hw-id '08:00:27:ea:0f:b9' +# set interfaces ethernet eth1 smp-affinity 'auto' +# set interfaces ethernet eth2 hw-id '08:00:27:c2:98:23' +# set interfaces ethernet eth2 smp-affinity 'auto' +# set interfaces ethernet eth3 hw-id '08:00:27:43:70:8c' +# set interfaces loopback lo +# +# +""" +RETURN = """ +before: + description: The configuration prior to the model invocation. + returned: always + sample: > + The configuration returned will always be in the same format + of the parameters above. + type: list +after: + description: The resulting configuration model invocation. + returned: when changed + sample: > + The configuration returned will always be in the same format + of the parameters above. + type: list +commands: + description: The set of commands pushed to the remote device. + returned: always + type: list + sample: + - 'set interfaces ethernet eth1 mtu 1200' + - 'set interfaces ethernet eth2 vif 100 description VIF 100' +""" + + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.network.vyos.argspec.interfaces.interfaces import InterfacesArgs +from ansible.module_utils.network.vyos.config.interfaces.interfaces import Interfaces + + +def main(): + """ + Main entry point for module execution + + :returns: the result form module invocation + """ + 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/test/integration/targets/prepare_vyos_tests/tasks/main.yaml b/test/integration/targets/prepare_vyos_tests/tasks/main.yaml new file mode 100644 index 00000000000..9f2fbf8d63c --- /dev/null +++ b/test/integration/targets/prepare_vyos_tests/tasks/main.yaml @@ -0,0 +1,9 @@ +--- +- name: Ensure required interfaces are present in running-config + cli_config: + config: "{{ lines }}" + vars: + lines: | + set interfaces ethernet eth1 + set interfaces ethernet eth2 + set interfaces loopback lo diff --git a/test/integration/targets/vyos_interfaces/defaults/main.yaml b/test/integration/targets/vyos_interfaces/defaults/main.yaml new file mode 100644 index 00000000000..164afead284 --- /dev/null +++ b/test/integration/targets/vyos_interfaces/defaults/main.yaml @@ -0,0 +1,3 @@ +--- +testcase: "[^_].*" +test_items: [] diff --git a/test/integration/targets/vyos_interfaces/meta/main.yaml b/test/integration/targets/vyos_interfaces/meta/main.yaml new file mode 100644 index 00000000000..e380a13e01a --- /dev/null +++ b/test/integration/targets/vyos_interfaces/meta/main.yaml @@ -0,0 +1,2 @@ +dependencies: + - prepare_vyos_tests \ No newline at end of file diff --git a/test/integration/targets/vyos_interfaces/tasks/cli.yaml b/test/integration/targets/vyos_interfaces/tasks/cli.yaml new file mode 100644 index 00000000000..655e51ee63d --- /dev/null +++ b/test/integration/targets/vyos_interfaces/tasks/cli.yaml @@ -0,0 +1,19 @@ +--- +- 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 }}" + +- 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/vyos_interfaces/tasks/main.yaml b/test/integration/targets/vyos_interfaces/tasks/main.yaml new file mode 100644 index 00000000000..415c99d8b12 --- /dev/null +++ b/test/integration/targets/vyos_interfaces/tasks/main.yaml @@ -0,0 +1,2 @@ +--- +- { include: cli.yaml, tags: ['cli'] } diff --git a/test/integration/targets/vyos_interfaces/tests/cli/_populate.yaml b/test/integration/targets/vyos_interfaces/tests/cli/_populate.yaml new file mode 100644 index 00000000000..b798bae015e --- /dev/null +++ b/test/integration/targets/vyos_interfaces/tests/cli/_populate.yaml @@ -0,0 +1,17 @@ +--- +- name: Setup + cli_config: + config: "{{ lines }}" + vars: + lines: | + set interfaces ethernet "{{ intf }}" description 'Configured by Ansible' + set interfaces ethernet "{{ intf }}" speed 'auto' + set interfaces ethernet "{{ intf }}" duplex 'auto' + set interfaces ethernet "{{ intf }}" mtu '1500' + set interfaces ethernet "{{ intf }}" vif 200 + set interfaces ethernet "{{ intf }}" vif 200 description 'VIF - 200' + loop: + - eth1 + - eth2 + loop_control: + loop_var: intf \ No newline at end of file diff --git a/test/integration/targets/vyos_interfaces/tests/cli/_remove_config.yaml b/test/integration/targets/vyos_interfaces/tests/cli/_remove_config.yaml new file mode 100644 index 00000000000..6d559c15122 --- /dev/null +++ b/test/integration/targets/vyos_interfaces/tests/cli/_remove_config.yaml @@ -0,0 +1,17 @@ +--- +- name: Remove Config + cli_config: + config: "{{ lines }}" + vars: + lines: | + delete interfaces ethernet "{{ intf }}" description + delete interfaces ethernet "{{ intf }}" speed + delete interfaces ethernet "{{ intf }}" duplex + delete interfaces ethernet "{{ intf }}" mtu + delete interfaces ethernet "{{ intf }}" disable + delete interfaces ethernet "{{ intf }}" vif + loop: + - eth1 + - eth2 + loop_control: + loop_var: intf \ No newline at end of file diff --git a/test/integration/targets/vyos_interfaces/tests/cli/deleted.yaml b/test/integration/targets/vyos_interfaces/tests/cli/deleted.yaml new file mode 100644 index 00000000000..5b08ea95f85 --- /dev/null +++ b/test/integration/targets/vyos_interfaces/tests/cli/deleted.yaml @@ -0,0 +1,46 @@ +--- +- debug: + msg: "Start vyos_interfaces deleted integration tests ansible_connection={{ ansible_connection }}" + +- include_tasks: _populate.yaml + +- block: + - name: Delete attributes of given interfaces + vyos_interfaces: &deleted + config: + - name: eth1 + - name: eth2 + state: deleted + register: result + + - name: Assert that the before dicts were correctly generated + assert: + that: + - "{{ populate | symmetric_difference(result['before']) |length == 0 }}" + + - 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'] | symmetric_difference(result['after']) |length == 0 }}" + + - name: Delete attributes of given interfaces (IDEMPOTENT) + vyos_interfaces: *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'] | symmetric_difference(result['before']) |length == 0 }}" + + always: + - include_tasks: _remove_config.yaml diff --git a/test/integration/targets/vyos_interfaces/tests/cli/merged.yaml b/test/integration/targets/vyos_interfaces/tests/cli/merged.yaml new file mode 100644 index 00000000000..62371913da9 --- /dev/null +++ b/test/integration/targets/vyos_interfaces/tests/cli/merged.yaml @@ -0,0 +1,59 @@ +--- +- debug: + msg: "START vyos_interfaces merged integration tests on connection={{ ansible_connection }}" + +- include_tasks: _remove_config.yaml + +- block: + - name: Merge the provided configuration with the exisiting running configuration + vyos_interfaces: &merged + config: + - name: eth1 + description: "Configured by Ansible - Interface 1" + mtu: 1500 + speed: auto + duplex: auto + vifs: + - vlan_id: 100 + description: "Eth1 - VIF 100" + mtu: 400 + + - vlan_id: 101 + description: "Eth1 - VIF 101" + + - name: eth2 + description: "Configured by Ansible - Interface 2 (ADMIN DOWN)" + mtu: 600 + enabled: false + state: merged + register: result + + - name: Assert that before dicts were correctly generated + assert: + that: "{{ merged['before'] | symmetric_difference(result['before']) |length == 0 }}" + + - 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'] | symmetric_difference(result['after']) |length == 0 }}" + + - name: Merge the provided configuration with the existing running configuration (IDEMPOTENT) + vyos_interfaces: *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'] | symmetric_difference(result['before']) |length == 0 }}" + always: + - include_tasks: _remove_config.yaml diff --git a/test/integration/targets/vyos_interfaces/tests/cli/overridden.yaml b/test/integration/targets/vyos_interfaces/tests/cli/overridden.yaml new file mode 100644 index 00000000000..43040c1e671 --- /dev/null +++ b/test/integration/targets/vyos_interfaces/tests/cli/overridden.yaml @@ -0,0 +1,52 @@ +--- +- debug: + msg: "START vyos_interfaces overridden integration tests on connection={{ ansible_connection }}" + +- include_tasks: _remove_config.yaml + +- include_tasks: _populate.yaml + +- block: + - name: Overrides all device configuration with provided configuration + vyos_interfaces: &overridden + config: + - name: eth0 + speed: "auto" + duplex: "auto" + - name: eth2 + description: "Overridden by Ansible" + mtu: 1200 + state: overridden + register: result + + - name: Assert that before dicts were correctly generated + assert: + that: + - "{{ populate | symmetric_difference(result['before']) |length == 0 }}" + + - name: Assert that correct commands were generated + assert: + that: + - "{{ overridden['commands'] | symmetric_difference(result['commands']) |length == 0 }}" + + - name: Assert that after dicts were correctly generated + assert: + that: + - "{{ overridden['after'] | symmetric_difference(result['after']) |length == 0 }}" + + - name: Overrides all device configuration with provided configurations (IDEMPOTENT) + vyos_interfaces: *overridden + 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: + - "{{ overridden['after'] | symmetric_difference(result['before']) |length == 0 }}" + + always: + - include_tasks: _remove_config.yaml diff --git a/test/integration/targets/vyos_interfaces/tests/cli/replaced.yaml b/test/integration/targets/vyos_interfaces/tests/cli/replaced.yaml new file mode 100644 index 00000000000..6acc5830a6b --- /dev/null +++ b/test/integration/targets/vyos_interfaces/tests/cli/replaced.yaml @@ -0,0 +1,55 @@ +--- +- debug: + msg: "START vyos_interfaces 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 + vyos_interfaces: &replaced + config: + - name: eth1 + description: "Replaced by Ansible" + vifs: + - vlan_id: 100 + description: "VIF 100 - Replaced by Ansible" + + - name: eth2 + mtu: 1400 + description: "Replaced by Ansible" + 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 | symmetric_difference(result['before']) |length == 0 }}" + + - name: Assert that after dict is correctly generated + assert: + that: + - "{{ replaced['after'] | symmetric_difference(result['after']) |length == 0 }}" + + - name: Replace device configurations of listed interfaces with provided configurarions (IDEMPOTENT) + vyos_interfaces: *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'] | symmetric_difference(result['before']) |length == 0 }}" + + always: + - include_tasks: _remove_config.yaml \ No newline at end of file diff --git a/test/integration/targets/vyos_interfaces/tests/cli/rtt.yaml b/test/integration/targets/vyos_interfaces/tests/cli/rtt.yaml new file mode 100644 index 00000000000..c246fa35916 --- /dev/null +++ b/test/integration/targets/vyos_interfaces/tests/cli/rtt.yaml @@ -0,0 +1,78 @@ +--- +- debug: + msg: "START vyos_interfaces round trip integration tests on connection={{ ansible_connection }}" + +- include_tasks: _remove_config.yaml + +- block: + - name: Apply the provided configuration (base config) + vyos_interfaces: + config: + - name: eth0 + enabled: true + duplex: "auto" + speed: "auto" + + - name: eth1 + description: "Interface - 1" + mtu: 1500 + vifs: + - vlan_id: 100 + description: "Eth1 - VIF 100" + mtu: 200 + + - vlan_id: 101 + enabled: false + + - name: eth2 + description: "Interface - 2" + enabled: true + mtu: 900 + state: merged + register: base_config + + - name: Gather interfaces facts + vyos_facts: + gather_subset: + - default + gather_network_resources: + - interfaces + + - name: Apply the provided configuration (config to be reverted) + vyos_interfaces: + config: + - name: eth1 + description: "Interface 1 - Description (WILL BE REVERTED)" + mtu: 1200 + vifs: + - vlan_id: 100 + description: "Eth1 - VIF 100 (WILL BE REVERTED)" + mtu: 400 + + - vlan_id: 101 + description: "Eth1 - VIF 101 (WILL BE REMOVED)" + enabled: true + + - name: eth2 + description: "Interface 2 (ADMIN DOWN) (WILL BE REVERTED)" + mtu: 600 + enabled: false + state: merged + register: result + + - name: Assert that changes were applied + assert: + that: "{{ round_trip['after'] | symmetric_difference(result['after']) |length == 0 }}" + + - name: Revert back to base config using facts round trip + vyos_interfaces: + config: "{{ ansible_facts['network_resources']['interfaces'] }}" + state: replaced + register: revert + + - name: Assert that config was reverted + assert: + that: "{{ base_config['after'] | symmetric_difference(revert['after']) |length == 0 }}" + + always: + - include_tasks: _remove_config.yaml diff --git a/test/integration/targets/vyos_interfaces/vars/main.yaml b/test/integration/targets/vyos_interfaces/vars/main.yaml new file mode 100644 index 00000000000..e3bd8188d77 --- /dev/null +++ b/test/integration/targets/vyos_interfaces/vars/main.yaml @@ -0,0 +1,230 @@ +--- +merged: + before: + - name: "eth0" + enabled: true + speed: "auto" + duplex: "auto" + + - name: "eth1" + enabled: true + + - name: "eth2" + enabled: true + + - name: "lo" + enabled: true + + commands: + - "set interfaces ethernet eth1 description 'Configured by Ansible - Interface 1'" + - "set interfaces ethernet eth1 mtu '1500'" + - "set interfaces ethernet eth1 duplex 'auto'" + - "set interfaces ethernet eth1 speed 'auto'" + - "set interfaces ethernet eth1 vif 100 description 'Eth1 - VIF 100'" + - "set interfaces ethernet eth1 vif 100 mtu '400'" + - "set interfaces ethernet eth1 vif 101 description 'Eth1 - VIF 101'" + - "set interfaces ethernet eth2 description 'Configured by Ansible - Interface 2 (ADMIN DOWN)'" + - "set interfaces ethernet eth2 mtu '600'" + - "set interfaces ethernet eth2 disable" + + after: + - name: "lo" + enabled: true + + - name: "eth0" + enabled: true + duplex: "auto" + speed: "auto" + + - name: "eth1" + description: "Configured by Ansible - Interface 1" + mtu: 1500 + speed: "auto" + duplex: "auto" + enabled: true + vifs: + - vlan_id: 100 + description: "Eth1 - VIF 100" + mtu: 400 + enabled: true + + - vlan_id: 101 + description: "Eth1 - VIF 101" + enabled: true + + - name: "eth2" + description: "Configured by Ansible - Interface 2 (ADMIN DOWN)" + mtu: 600 + enabled: false + +populate: + - name: "eth1" + enabled: true + speed: "auto" + duplex: "auto" + description: "Configured by Ansible" + mtu: 1500 + vifs: + - vlan_id: 200 + enabled: true + description: "VIF - 200" + + - name: "eth2" + enabled: true + speed: "auto" + duplex: "auto" + description: "Configured by Ansible" + mtu: 1500 + vifs: + - vlan_id: 200 + enabled: true + description: "VIF - 200" + + - name: "eth0" + enabled: true + duplex: "auto" + speed: "auto" + + - name: "lo" + enabled: true + +replaced: + commands: + - "delete interfaces ethernet eth1 mtu" + - "delete interfaces ethernet eth1 speed" + - "delete interfaces ethernet eth1 duplex" + - "delete interfaces ethernet eth1 vif 200 description" + - "set interfaces ethernet eth1 description 'Replaced by Ansible'" + - "set interfaces ethernet eth1 vif 100 description 'VIF 100 - Replaced by Ansible'" + - "delete interfaces ethernet eth2 speed" + - "delete interfaces ethernet eth2 duplex" + - "delete interfaces ethernet eth2 vif 200 description" + - "set interfaces ethernet eth2 description 'Replaced by Ansible'" + - "set interfaces ethernet eth2 mtu '1400'" + + after: + - name: "lo" + enabled: true + + - name: "eth1" + description: "Replaced by Ansible" + enabled: true + vifs: + - vlan_id: 100 + enabled: true + description: "VIF 100 - Replaced by Ansible" + + - vlan_id: 200 + enabled: true + + - name: "eth2" + mtu: 1400 + description: "Replaced by Ansible" + enabled: true + vifs: + - vlan_id: 200 + enabled: true + + - name: "eth0" + enabled: true + duplex: "auto" + speed: "auto" + +overridden: + commands: + - "delete interfaces ethernet eth1 description" + - "delete interfaces ethernet eth1 speed" + - "delete interfaces ethernet eth1 duplex" + - "delete interfaces ethernet eth1 mtu" + - "delete interfaces ethernet eth1 vif 200 description" + - "delete interfaces ethernet eth2 speed" + - "delete interfaces ethernet eth2 duplex" + - "delete interfaces ethernet eth2 vif 200 description" + - "set interfaces ethernet eth2 description 'Overridden by Ansible'" + - "set interfaces ethernet eth2 mtu '1200'" + + after: + - name: "lo" + enabled: true + + - name: "eth0" + enabled: true + speed: "auto" + duplex: "auto" + + - name: "eth1" + enabled: true + vifs: + - vlan_id: 200 + enabled: true + + - name: "eth2" + enabled: true + description: "Overridden by Ansible" + mtu: 1200 + vifs: + - vlan_id: 200 + enabled: true + +deleted: + commands: + - "delete interfaces ethernet eth1 description" + - "delete interfaces ethernet eth1 speed" + - "delete interfaces ethernet eth1 duplex" + - "delete interfaces ethernet eth1 mtu" + - "delete interfaces ethernet eth1 vif 200 description" + - "delete interfaces ethernet eth2 description" + - "delete interfaces ethernet eth2 speed" + - "delete interfaces ethernet eth2 duplex" + - "delete interfaces ethernet eth2 mtu" + - "delete interfaces ethernet eth2 vif 200 description" + + after: + - name: "lo" + enabled: true + + - name: "eth0" + enabled: true + speed: "auto" + duplex: "auto" + + - name: "eth1" + enabled: true + vifs: + - vlan_id: 200 + enabled: true + + - name: "eth2" + enabled: true + vifs: + - vlan_id: 200 + enabled: true + +round_trip: + after: + - name: "lo" + enabled: true + + - name: "eth0" + enabled: true + speed: "auto" + duplex: "auto" + + - name: "eth1" + description: "Interface 1 - Description (WILL BE REVERTED)" + enabled: true + mtu: 1200 + vifs: + - vlan_id: 100 + description: "Eth1 - VIF 100 (WILL BE REVERTED)" + mtu: 400 + enabled: true + + - vlan_id: 101 + description: "Eth1 - VIF 101 (WILL BE REMOVED)" + enabled: true + + - name: "eth2" + description: "Interface 2 (ADMIN DOWN) (WILL BE REVERTED)" + mtu: 600 + enabled: false diff --git a/test/sanity/ignore.txt b/test/sanity/ignore.txt index e5b08324cb5..58dfa8fb16b 100644 --- a/test/sanity/ignore.txt +++ b/test/sanity/ignore.txt @@ -5767,14 +5767,14 @@ lib/ansible/modules/network/vyos/vyos_facts.py future-import-boilerplate lib/ansible/modules/network/vyos/vyos_facts.py metaclass-boilerplate lib/ansible/modules/network/vyos/vyos_facts.py validate-modules:E324 lib/ansible/modules/network/vyos/vyos_facts.py validate-modules:E337 -lib/ansible/modules/network/vyos/vyos_interface.py future-import-boilerplate -lib/ansible/modules/network/vyos/vyos_interface.py metaclass-boilerplate -lib/ansible/modules/network/vyos/vyos_interface.py validate-modules:E322 -lib/ansible/modules/network/vyos/vyos_interface.py validate-modules:E324 -lib/ansible/modules/network/vyos/vyos_interface.py validate-modules:E326 -lib/ansible/modules/network/vyos/vyos_interface.py validate-modules:E337 -lib/ansible/modules/network/vyos/vyos_interface.py validate-modules:E338 -lib/ansible/modules/network/vyos/vyos_interface.py validate-modules:E340 +lib/ansible/modules/network/vyos/_vyos_interface.py future-import-boilerplate +lib/ansible/modules/network/vyos/_vyos_interface.py metaclass-boilerplate +lib/ansible/modules/network/vyos/_vyos_interface.py validate-modules:E322 +lib/ansible/modules/network/vyos/_vyos_interface.py validate-modules:E324 +lib/ansible/modules/network/vyos/_vyos_interface.py validate-modules:E326 +lib/ansible/modules/network/vyos/_vyos_interface.py validate-modules:E337 +lib/ansible/modules/network/vyos/_vyos_interface.py validate-modules:E338 +lib/ansible/modules/network/vyos/_vyos_interface.py validate-modules:E340 lib/ansible/modules/network/vyos/vyos_l3_interface.py future-import-boilerplate lib/ansible/modules/network/vyos/vyos_l3_interface.py metaclass-boilerplate lib/ansible/modules/network/vyos/vyos_l3_interface.py validate-modules:E322 diff --git a/test/units/modules/network/vyos/test_vyos_facts.py b/test/units/modules/network/vyos/test_vyos_facts.py index abc68ad8557..91ad81a80e8 100644 --- a/test/units/modules/network/vyos/test_vyos_facts.py +++ b/test/units/modules/network/vyos/test_vyos_facts.py @@ -33,10 +33,13 @@ class TestVyosFactsModule(TestVyosModule): def setUp(self): super(TestVyosFactsModule, self).setUp() - self.mock_run_commands = patch('ansible.modules.network.vyos.vyos_facts.run_commands') + self.mock_run_commands = patch('ansible.module_utils.network.vyos.facts.legacy.base.run_commands') self.run_commands = self.mock_run_commands.start() - self.mock_get_capabilities = patch('ansible.modules.network.vyos.vyos_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.vyos.facts.legacy.base.get_capabilities') self.get_capabilities = self.mock_get_capabilities.start() self.get_capabilities.return_value = { 'device_info': { @@ -52,6 +55,7 @@ class TestVyosFactsModule(TestVyosModule): super(TestVyosFactsModule, self).tearDown() self.mock_run_commands.stop() self.mock_get_capabilities.stop() + self.mock_get_resource_connection.stop() def load_fixtures(self, commands=None): def load_from_file(*args, **kwargs): @@ -74,7 +78,7 @@ class TestVyosFactsModule(TestVyosModule): set_module_args(dict(gather_subset='default')) result = self.execute_module() facts = result.get('ansible_facts') - self.assertEqual(len(facts), 8) + self.assertEqual(len(facts), 10) self.assertEqual(facts['ansible_net_hostname'].strip(), 'vyos01') self.assertEqual(facts['ansible_net_version'], 'VyOS 1.1.7') @@ -82,7 +86,7 @@ class TestVyosFactsModule(TestVyosModule): set_module_args(dict(gather_subset='!all')) result = self.execute_module() facts = result.get('ansible_facts') - self.assertEqual(len(facts), 8) + self.assertEqual(len(facts), 10) self.assertEqual(facts['ansible_net_hostname'].strip(), 'vyos01') self.assertEqual(facts['ansible_net_version'], 'VyOS 1.1.7') @@ -90,7 +94,7 @@ class TestVyosFactsModule(TestVyosModule): set_module_args(dict(gather_subset=['!neighbors', '!config'])) result = self.execute_module() facts = result.get('ansible_facts') - self.assertEqual(len(facts), 8) + self.assertEqual(len(facts), 10) self.assertEqual(facts['ansible_net_hostname'].strip(), 'vyos01') self.assertEqual(facts['ansible_net_version'], 'VyOS 1.1.7')