diff --git a/changelogs/fragments/T6831_ospf_vif.yml b/changelogs/fragments/T6831_ospf_vif.yml new file mode 100644 index 00000000..97a4c7c8 --- /dev/null +++ b/changelogs/fragments/T6831_ospf_vif.yml @@ -0,0 +1,3 @@ +--- +minor_changes: + - vyos_ospf_interfaces - add support for VyOS 1.3- virtual interfaces diff --git a/plugins/module_utils/network/vyos/facts/ospf_interfaces/ospf_interfaces.py b/plugins/module_utils/network/vyos/facts/ospf_interfaces/ospf_interfaces.py index 852e1da7..2160fc71 100644 --- a/plugins/module_utils/network/vyos/facts/ospf_interfaces/ospf_interfaces.py +++ b/plugins/module_utils/network/vyos/facts/ospf_interfaces/ospf_interfaces.py @@ -1,133 +1,135 @@ # -*- coding: utf-8 -*- # Copyright 2020 Red Hat # GNU General Public License v3.0+ # (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) from __future__ import absolute_import, division, print_function __metaclass__ = type """ The vyos ospf_interfaces fact class It is in this file the configuration is collected from the device for a given resource, parsed, and the facts tree is populated based on the configuration. """ import re from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.argspec.ospf_interfaces.ospf_interfaces import ( Ospf_interfacesArgs, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.rm_templates.ospf_interfaces import ( - Ospf_interfacesTemplate + Ospf_interfacesTemplate, ) - from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.rm_templates.ospf_interfaces_14 import ( - Ospf_interfacesTemplate14 + Ospf_interfacesTemplate14, +) +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.version import ( + LooseVersion, ) - from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.vyos import get_os_version -from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.version import LooseVersion - class Ospf_interfacesFacts(object): """The vyos ospf_interfaces facts class""" def __init__(self, module, subspec="config", options="options"): self._module = module self.argument_spec = Ospf_interfacesArgs.argument_spec def get_device_data(self, connection): if LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4"): # use set protocols ospf in order to get both ospf and ospfv3 return connection.get("show configuration commands | match 'set protocols ospf'") return connection.get('show configuration commands | match "set interfaces"') def get_config_set_1_4(self, data): """To classify the configurations beased on interface""" config_dict = {} for config_line in data.splitlines(): ospf_int = re.search(r"set protocols (?:ospf|ospfv3) interface (\S+).*", config_line) if ospf_int: - config_dict[ospf_int.group(1)] = config_dict.get(ospf_int.group(1), "") + config_line + "\n" + config_dict[ospf_int.group(1)] = ( + config_dict.get(ospf_int.group(1), "") + config_line + "\n" + ) return list(config_dict.values()) def get_config_set_1_2(self, data): """To classify the configurations beased on interface""" interface_list = [] config_set = [] int_string = "" for config_line in data.splitlines(): - ospf_int = re.search(r"set interfaces \S+ (\S+) .*", config_line) + ospf_int_raw = re.findall(r"^set interfaces \S+ (\S+)", config_line, re.M) + ospf_int_vif = re.findall(r"^set interfaces \S+ (\S+) vif (\d+)", config_line, re.M) + + ospf_int = ospf_int_raw + ospf_int_vif if ospf_int: - if ospf_int.group(1) not in interface_list: + if ospf_int not in interface_list: if int_string: config_set.append(int_string) - interface_list.append(ospf_int.group(1)) + interface_list.append(ospf_int) int_string = "" int_string = int_string + config_line + "\n" if int_string: config_set.append(int_string) return config_set def get_config_set(self, data, connection): """To classify the configurations beased on interface""" if LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4"): return self.get_config_set_1_4(data) return self.get_config_set_1_2(data) def populate_facts(self, connection, ansible_facts, data=None): """Populate the facts for Ospf_interfaces network resource :param connection: the device connection :param ansible_facts: Facts dictionary :param data: previously collected conf :rtype: dictionary :returns: facts """ facts = {} objs = [] if LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4"): ospf_interface_class = Ospf_interfacesTemplate14 else: ospf_interface_class = Ospf_interfacesTemplate ospf_interfaces_parser = ospf_interface_class(lines=[], module=self._module) if not data: data = self.get_device_data(connection) # parse native config using the Ospf_interfaces template ospf_interfaces_facts = [] resources = self.get_config_set(data, connection) for resource in resources: ospf_interfaces_parser = ospf_interface_class( lines=resource.split("\n"), module=self._module, ) objs = ospf_interfaces_parser.parse() for key, sortv in [("address_family", "afi")]: if key in objs and objs[key]: objs[key] = list(objs[key].values()) ospf_interfaces_facts.append(objs) - ansible_facts["ansible_network_resources"].pop("ospf_interfaces", None) facts = {"ospf_interfaces": []} params = utils.remove_empties( ospf_interfaces_parser.validate_config( self.argument_spec, {"config": ospf_interfaces_facts}, redact=True, - ) + ), ) if params.get("config"): for cfg in params["config"]: facts["ospf_interfaces"].append(utils.remove_empties(cfg)) ansible_facts["ansible_network_resources"].update(facts) - return ansible_facts diff --git a/plugins/module_utils/network/vyos/rm_templates/ospf_interfaces.py b/plugins/module_utils/network/vyos/rm_templates/ospf_interfaces.py index 0d7eaf84..134effca 100644 --- a/plugins/module_utils/network/vyos/rm_templates/ospf_interfaces.py +++ b/plugins/module_utils/network/vyos/rm_templates/ospf_interfaces.py @@ -1,733 +1,776 @@ # -*- coding: utf-8 -*- # Copyright 2020 Red Hat # GNU General Public License v3.0+ # (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) from __future__ import absolute_import, division, print_function __metaclass__ = type """ The Ospf_interfaces parser templates file. This contains a list of parser definitions and associated functions that facilitates both facts gathering and native command generation for the given network resource. """ import re from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.network_template import ( NetworkTemplate, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.utils import ( get_interface_type, + get_interface_with_vif, ) def _get_parameters(data): if data["afi"] == "ipv6": val = ["ospfv3", "ipv6"] else: val = ["ospf", "ip"] return val def _tmplt_ospf_int_delete(config_data): int_type = get_interface_type(config_data["name"]) + name = get_interface_with_vif(config_data["name"]) params = _get_parameters(config_data["address_family"]) - command = ( - "interfaces " + int_type + " {name} ".format(**config_data) + params[1] + " " + params[0] - ) + command = "interfaces " + int_type + " " + name + " " + params[1] + " " + params[0] return command def _tmplt_ospf_int_cost(config_data): int_type = get_interface_type(config_data["name"]) + name = get_interface_with_vif(config_data["name"]) params = _get_parameters(config_data["address_family"]) command = ( "interfaces " + int_type - + " {name} ".format(**config_data) + + " " + + name + + " " + params[1] + " " + params[0] + " cost {cost}".format(**config_data["address_family"]) ) return command def _tmplt_ospf_int_auth_password(config_data): int_type = get_interface_type(config_data["name"]) + name = get_interface_with_vif(config_data["name"]) params = _get_parameters(config_data["address_family"]) command = ( "interfaces " + int_type - + " {name} ".format(**config_data) + + " " + + name + + " " + params[1] + " " + params[0] + " authentication plaintext-password {plaintext_password}".format( **config_data["address_family"]["authentication"], ) ) return command def _tmplt_ospf_int_auth_md5(config_data): int_type = get_interface_type(config_data["name"]) + name = get_interface_with_vif(config_data["name"]) params = _get_parameters(config_data["address_family"]) command = ( "interfaces " + int_type - + " {name} ".format(**config_data) + + " " + + name + + " " + params[1] + " " + params[0] + " authentication md5 key-id {key_id} ".format( **config_data["address_family"]["authentication"]["md5_key"], ) + "md5-key {key}".format(**config_data["address_family"]["authentication"]["md5_key"]) ) return command def _tmplt_ospf_int_auth_md5_delete(config_data): int_type = get_interface_type(config_data["name"]) + name = get_interface_with_vif(config_data["name"]) params = _get_parameters(config_data["address_family"]) command = ( "interfaces " + int_type - + " {name} ".format(**config_data) + + " " + + name + + " " + params[1] + " " + params[0] + " authentication" ) return command def _tmplt_ospf_int_bw(config_data): int_type = get_interface_type(config_data["name"]) + name = get_interface_with_vif(config_data["name"]) params = _get_parameters(config_data["address_family"]) command = ( "interfaces " + int_type - + " {name} ".format(**config_data) + + " " + + name + + " " + params[1] + " " + params[0] + " bandwidth {bandwidth}".format(**config_data["address_family"]) ) return command def _tmplt_ospf_int_hello_interval(config_data): int_type = get_interface_type(config_data["name"]) + name = get_interface_with_vif(config_data["name"]) params = _get_parameters(config_data["address_family"]) command = ( "interfaces " + int_type - + " {name} ".format(**config_data) + + " " + + name + + " " + params[1] + " " + params[0] + " hello-interval {hello_interval}".format(**config_data["address_family"]) ) return command def _tmplt_ospf_int_dead_interval(config_data): int_type = get_interface_type(config_data["name"]) + name = get_interface_with_vif(config_data["name"]) params = _get_parameters(config_data["address_family"]) command = ( "interfaces " + int_type - + " {name} ".format(**config_data) + + " " + + name + + " " + params[1] + " " + params[0] + " dead-interval {dead_interval}".format(**config_data["address_family"]) ) return command def _tmplt_ospf_int_mtu_ignore(config_data): int_type = get_interface_type(config_data["name"]) + name = get_interface_with_vif(config_data["name"]) params = _get_parameters(config_data["address_family"]) command = ( - "interfaces " - + int_type - + " {name} ".format(**config_data) - + params[1] - + " " - + params[0] - + " mtu-ignore" + "interfaces " + int_type + " " + name + " " + params[1] + " " + params[0] + " mtu-ignore" ) return command def _tmplt_ospf_int_network(config_data): int_type = get_interface_type(config_data["name"]) + name = get_interface_with_vif(config_data["name"]) params = _get_parameters(config_data["address_family"]) command = ( "interfaces " + int_type - + " {name} ".format(**config_data) + + " " + + name + + " " + params[1] + " " + params[0] + " network {network}".format(**config_data["address_family"]) ) return command def _tmplt_ospf_int_priority(config_data): int_type = get_interface_type(config_data["name"]) + name = get_interface_with_vif(config_data["name"]) params = _get_parameters(config_data["address_family"]) command = ( "interfaces " + int_type - + " {name} ".format(**config_data) + + " " + + name + + " " + params[1] + " " + params[0] + " priority {priority}".format(**config_data["address_family"]) ) return command def _tmplt_ospf_int_retransmit_interval(config_data): int_type = get_interface_type(config_data["name"]) + name = get_interface_with_vif(config_data["name"]) params = _get_parameters(config_data["address_family"]) command = ( "interfaces " + int_type - + " {name} ".format(**config_data) + + " " + + name + + " " + params[1] + " " + params[0] + " retransmit-interval {retransmit_interval}".format(**config_data["address_family"]) ) return command def _tmplt_ospf_int_transmit_delay(config_data): int_type = get_interface_type(config_data["name"]) + name = get_interface_with_vif(config_data["name"]) params = _get_parameters(config_data["address_family"]) command = ( "interfaces " + int_type - + " {name} ".format(**config_data) + + " " + + name + + " " + params[1] + " " + params[0] + " transmit-delay {transmit_delay}".format(**config_data["address_family"]) ) return command def _tmplt_ospf_int_ifmtu(config_data): int_type = get_interface_type(config_data["name"]) + name = get_interface_with_vif(config_data["name"]) params = _get_parameters(config_data["address_family"]) command = ( "interfaces " + int_type - + " {name} ".format(**config_data) + + " " + + name + + " " + params[1] + " " + params[0] + " ifmtu {ifmtu}".format(**config_data["address_family"]) ) return command def _tmplt_ospf_int_instance(config_data): int_type = get_interface_type(config_data["name"]) + name = get_interface_with_vif(config_data["name"]) params = _get_parameters(config_data["address_family"]) command = ( "interfaces " + int_type - + " {name} ".format(**config_data) + + " " + + name + + " " + params[1] + " " + params[0] + " instance-id {instance}".format(**config_data["address_family"]) ) return command def _tmplt_ospf_int_passive(config_data): int_type = get_interface_type(config_data["name"]) + name = get_interface_with_vif(config_data["name"]) params = _get_parameters(config_data["address_family"]) - command = ( - "interfaces " - + int_type - + " {name} ".format(**config_data) - + params[1] - + " " - + params[0] - + " passive" - ) + command = "interfaces " + int_type + " " + name + " " + params[1] + " " + params[0] + " passive" return command class Ospf_interfacesTemplate(NetworkTemplate): def __init__(self, lines=None, module=None): prefix = {"set": "set", "remove": "delete"} super(Ospf_interfacesTemplate, self).__init__( lines=lines, tmplt=self, prefix=prefix, module=module, ) # fmt: off PARSERS = [ { "name": "ip_ospf", "getval": re.compile( r""" ^set \s+interfaces \s+(?P\S+) \s+(?P\S+) + (?:\s+vif\s+(?P\d+))? \s+(?Pip|ipv6) \s+(?Pospf|ospfv3) *$""", re.VERBOSE, ), "remval": _tmplt_ospf_int_delete, "compval": "address_family", "result": { - "name": "{{ name }}", + "name": "{{ name + '.' + vif if vif is defined else name }}", "address_family": { "{{ afi }}": { "afi": '{{ "ipv4" if afi == "ip" else "ipv6" }}', }, }, }, }, { "name": "authentication_password", "getval": re.compile( r""" ^set \s+interfaces \s+(?P\S+) \s+(?P\S+) + (?:\s+vif\s+(?P\d+))? \s+(?Pip|ipv6) \s+(?Pospf|ospfv3) \s+authentication \s+plaintext-password \s+(?P\S+) *$""", re.VERBOSE, ), "setval": _tmplt_ospf_int_auth_password, "compval": "address_family.authentication", "result": { - "name": "{{ name }}", + "name": "{{ name + '.' + vif if vif is defined else name }}", "address_family": { "{{ afi }}": { "afi": '{{ "ipv4" if afi == "ip" else "ipv6" }}', "authentication": { "plaintext_password": "{{ text }}", }, }, }, }, }, { "name": "authentication_md5", "getval": re.compile( r""" ^set \s+interfaces \s+(?P\S+) \s+(?P\S+) + (?:\s+vif\s+(?P\d+))? \s+(?Pip|ipv6) \s+(?Pospf|ospfv3) \s+authentication \s+md5 \s+key-id \s+(?P\d+) \s+md5-key \s+(?P\S+) *$""", re.VERBOSE, ), "setval": _tmplt_ospf_int_auth_md5, "remval": _tmplt_ospf_int_auth_md5_delete, "compval": "address_family.authentication", "result": { - "name": "{{ name }}", + "name": "{{ name + '.' + vif if vif is defined else name }}", "address_family": { "{{ afi }}": { "afi": '{{ "ipv4" if afi == "ip" else "ipv6" }}', "authentication": { "md5_key": { "key_id": "{{ id }}", "key": "{{ text }}", }, }, }, }, }, }, { "name": "bandwidth", "getval": re.compile( r""" ^set \s+interfaces \s+(?P\S+) \s+(?P\S+) + (?:\s+vif\s+(?P\d+))? \s+(?Pip|ipv6) \s+(?Pospf|ospfv3) \s+bandwidth \s+(?P\'\d+\') *$""", re.VERBOSE, ), "setval": _tmplt_ospf_int_bw, "compval": "address_family.bandwidth", "result": { - "name": "{{ name }}", + "name": "{{ name + '.' + vif if vif is defined else name }}", "address_family": { "{{ afi }}": { "afi": '{{ "ipv4" if afi == "ip" else "ipv6" }}', "bandwidth": "{{ bw }}", }, }, }, }, { "name": "cost", "getval": re.compile( r""" ^set \s+interfaces \s+(?P\S+) \s+(?P\S+) + (?:\s+vif\s+(?P\d+))? \s+(?Pip|ipv6) \s+(?Pospf|ospfv3) \s+cost \s+(?P\'\d+\') *$""", re.VERBOSE, ), "setval": _tmplt_ospf_int_cost, "compval": "address_family.cost", "result": { - "name": "{{ name }}", + "name": "{{ name + '.' + vif if vif is defined else name }}", "address_family": { "{{ afi }}": { "afi": '{{ "ipv4" if afi == "ip" else "ipv6" }}', "cost": "{{ val }}", }, }, }, }, { "name": "hello_interval", "getval": re.compile( r""" ^set \s+interfaces \s+(?P\S+) \s+(?P\S+) + (?:\s+vif\s+(?P\d+))? \s+(?Pip|ipv6) \s+(?Pospf|ospfv3) \s+hello-interval \s+(?P\'\d+\') *$""", re.VERBOSE, ), "setval": _tmplt_ospf_int_hello_interval, "compval": "address_family.hello_interval", "result": { - "name": "{{ name }}", + "name": "{{ name + '.' + vif if vif is defined else name }}", "address_family": { "{{ afi }}": { "afi": '{{ "ipv4" if afi == "ip" else "ipv6" }}', "hello_interval": "{{ val }}", }, }, }, }, { "name": "dead_interval", "getval": re.compile( r""" ^set \s+interfaces \s+(?P\S+) \s+(?P\S+) + (?:\s+vif\s+(?P\d+))? \s+(?Pip|ipv6) \s+(?Pospf|ospfv3) \s+dead-interval \s+(?P\'\d+\') *$""", re.VERBOSE, ), "setval": _tmplt_ospf_int_dead_interval, "compval": "address_family.dead_interval", "result": { - "name": "{{ name }}", + "name": "{{ name + '.' + vif if vif is defined else name }}", "address_family": { "{{ afi }}": { "afi": '{{ "ipv4" if afi == "ip" else "ipv6" }}', "dead_interval": "{{ val }}", }, }, }, }, { "name": "mtu_ignore", "getval": re.compile( r""" ^set \s+interfaces \s+(?P\S+) \s+(?P\S+) + (?:\s+vif\s+(?P\d+))? \s+(?Pip|ipv6) \s+(?Pospf|ospfv3) \s+(?Pmtu-ignore) *$""", re.VERBOSE, ), "setval": _tmplt_ospf_int_mtu_ignore, "compval": "address_family.mtu_ignore", "result": { - "name": "{{ name }}", + "name": "{{ name + '.' + vif if vif is defined else name }}", "address_family": { "{{ afi }}": { "afi": '{{ "ipv4" if afi == "ip" else "ipv6" }}', "mtu_ignore": "{{ True if mtu is defined }}", }, }, }, }, { "name": "network", "getval": re.compile( r""" ^set \s+interfaces \s+(?P\S+) \s+(?P\S+) + (?:\s+vif\s+(?P\d+))? \s+(?Pip|ipv6) \s+(?Pospf|ospfv3) \s+network \s+(?P\S+) *$""", re.VERBOSE, ), "setval": _tmplt_ospf_int_network, "compval": "address_family.network", "result": { - "name": "{{ name }}", + "name": "{{ name + '.' + vif if vif is defined else name }}", "address_family": { "{{ afi }}": { "afi": '{{ "ipv4" if afi == "ip" else "ipv6" }}', "network": "{{ val }}", }, }, }, }, { "name": "priority", "getval": re.compile( r""" ^set \s+interfaces \s+(?P\S+) \s+(?P\S+) + (?:\s+vif\s+(?P\d+))? \s+(?Pip|ipv6) \s+(?Pospf|ospfv3) \s+priority \s+(?P\'\d+\') *$""", re.VERBOSE, ), "setval": _tmplt_ospf_int_priority, "compval": "address_family.priority", "result": { - "name": "{{ name }}", + "name": "{{ name + '.' + vif if vif is defined else name }}", "address_family": { "{{ afi }}": { "afi": '{{ "ipv4" if afi == "ip" else "ipv6" }}', "priority": "{{ val }}", }, }, }, }, { "name": "retransmit_interval", "getval": re.compile( r""" ^set \s+interfaces \s+(?P\S+) \s+(?P\S+) + (?:\s+vif\s+(?P\d+))? \s+(?Pip|ipv6) \s+(?Pospf|ospfv3) \s+retransmit-interval \s+(?P\'\d+\') *$""", re.VERBOSE, ), "setval": _tmplt_ospf_int_retransmit_interval, "compval": "address_family.retransmit_interval", "result": { - "name": "{{ name }}", + "name": "{{ name + '.' + vif if vif is defined else name }}", "address_family": { "{{ afi }}": { "afi": '{{ "ipv4" if afi == "ip" else "ipv6" }}', "retransmit_interval": "{{ val }}", }, }, }, }, { "name": "transmit_delay", "getval": re.compile( r""" ^set \s+interfaces \s+(?P\S+) \s+(?P\S+) + (?:\s+vif\s+(?P\d+))? \s+(?Pip|ipv6) \s+(?Pospf|ospfv3) \s+transmit-delay \s+(?P\'\d+\') *$""", re.VERBOSE, ), "setval": _tmplt_ospf_int_transmit_delay, "compval": "address_family.transmit_delay", "result": { - "name": "{{ name }}", + "name": "{{ name + '.' + vif if vif is defined else name }}", "address_family": { "{{ afi }}": { "afi": '{{ "ipv4" if afi == "ip" else "ipv6" }}', "transmit_delay": "{{ val }}", }, }, }, }, { "name": "ifmtu", "getval": re.compile( r""" ^set \s+interfaces \s+(?P\S+) \s+(?P\S+) + (?:\s+vif\s+(?P\d+))? \s+(?Pip|ipv6) \s+(?Pospf|ospfv3) \s+ifmtu \s+(?P\'\d+\') *$""", re.VERBOSE, ), "setval": _tmplt_ospf_int_ifmtu, "compval": "address_family.ifmtu", "result": { - "name": "{{ name }}", + "name": "{{ name + '.' + vif if vif is defined else name }}", "address_family": { "{{ afi }}": { "afi": '{{ "ipv4" if afi == "ip" else "ipv6" }}', "ifmtu": "{{ val }}", }, }, }, }, { "name": "instance", "getval": re.compile( r""" ^set \s+interfaces \s+(?P\S+) \s+(?P\S+) + (?:\s+vif\s+(?P\d+))? \s+(?Pip|ipv6) \s+(?Pospf|ospfv3) \s+instance-id \s+(?P\'\d+\') *$""", re.VERBOSE, ), "setval": _tmplt_ospf_int_instance, "compval": "address_family.instance", "result": { - "name": "{{ name }}", + "name": "{{ name + '.' + vif if vif is defined else name }}", "address_family": { "{{ afi }}": { "afi": '{{ "ipv4" if afi == "ip" else "ipv6" }}', "instance": "{{ val }}", }, }, }, }, { "name": "passive", "getval": re.compile( r""" ^set \s+interfaces \s+(?P\S+) \s+(?P\S+) + (?:\s+vif\s+(?P\d+))? \s+(?Pip|ipv6) \s+(?Pospf|ospfv3) \s+(?Ppassive) *$""", re.VERBOSE, ), "setval": _tmplt_ospf_int_passive, "compval": "address_family.passive", "result": { - "name": "{{ name }}", + "name": "{{ name + '.' + vif if vif is defined else name }}", "address_family": { "{{ afi }}": { "afi": '{{ "ipv4" if afi == "ip" else "ipv6" }}', "passive": "{{ True if pass is defined }}", }, }, }, }, { "name": "interface_name", "getval": re.compile( r""" ^set \s+interfaces \s+(?P\S+) \s+(?P\S+) + (?:\s+vif\s+(?P\d+))? .*$""", re.VERBOSE, ), "setval": "set interface {{ type }} {{ name }}", "result": { - "name": "{{ name }}", + "name": "{{ name + '.' + vif if vif is defined else name }}", }, }, ] # fmt: on diff --git a/plugins/module_utils/network/vyos/utils/utils.py b/plugins/module_utils/network/vyos/utils/utils.py index 8722251e..a6b03c80 100644 --- a/plugins/module_utils/network/vyos/utils/utils.py +++ b/plugins/module_utils/network/vyos/utils/utils.py @@ -1,266 +1,278 @@ # -*- 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 from ansible.module_utils.basic import missing_required_lib from ansible.module_utils.six import iteritems try: import ipaddress HAS_IPADDRESS = True except ImportError: HAS_IPADDRESS = False def search_obj_in_list(name, lst, key="name"): if lst: 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" elif interface.startswith("vtun"): return "openvpn" elif interface.startswith("wg"): return "wireguard" elif interface.startswith("tun"): return "tunnel" elif interface.startswith("br"): return "bridge" elif interface.startswith("dum"): return "dummy" +def get_interface_with_vif(interface): + """Gets virtual interface if any or return as is""" + vlan = None + interface_real = interface + if "." in interface: + interface_real, vlan = interface.split(".") + + if vlan is not None: + interface_real = interface_real + " vif " + vlan + return interface_real + + 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 def diff_list_of_dicts(want, have): diff = [] set_w = set(tuple(d.items()) for d in want) set_h = set(tuple(d.items()) for d in have) difference = set_w.difference(set_h) for element in difference: diff.append(dict((x, y) for x, y in element)) return diff def get_lst_diff_for_dicts(want, have, lst): """ This function generates a list containing values that are only in want and not in list in have dict :param want: dict object to want :param have: dict object to have :param lst: list the diff on :return: new list object with values which are only in want. """ if not have: diff = want.get(lst) or [] else: want_elements = want.get(lst) or {} have_elements = have.get(lst) or {} diff = list_diff_want_only(want_elements, have_elements) return diff def get_lst_same_for_dicts(want, have, lst): """ This function generates a list containing values that are common for list in want and list in have dict :param want: dict object to want :param have: dict object to have :param lst: list the comparison on :return: new list object with values which are common in want and have. """ diff = None if want and have: want_list = want.get(lst) or {} have_list = have.get(lst) or {} diff = [i for i in want_list and have_list if i in have_list and i in want_list] return diff def list_diff_have_only(want_list, have_list): """ This function generated the list containing values that are only in have list. :param want_list: :param have_list: :return: new list with values which are only in have list """ if have_list and not want_list: diff = have_list elif not have_list: diff = None else: diff = [i for i in have_list + want_list if i in have_list and i not in want_list] return diff def list_diff_want_only(want_list, have_list): """ This function generated the list containing values that are only in want list. :param want_list: :param have_list: :return: new list with values which are only in want list """ if have_list and not want_list: diff = None elif not have_list: diff = want_list else: diff = [i for i in have_list + want_list if i in want_list and i not in have_list] return diff def search_dict_tv_in_list(d_val1, d_val2, lst, key1, key2): """ This function return the dict object if it exist in list. :param d_val1: :param d_val2: :param lst: :param key1: :param key2: :return: """ obj = next( (item for item in lst if item[key1] == d_val1 and item[key2] == d_val2), None, ) if obj: return obj else: return None def key_value_in_dict(have_key, have_value, want_dict): """ This function checks whether the key and values exist in dict :param have_key: :param have_value: :param want_dict: :return: """ for key, value in iteritems(want_dict): if key == have_key and value == have_value: return True return False def is_dict_element_present(dict, key): """ This function checks whether the key is present in dict. :param dict: :param key: :return: """ for item in dict: if item == key: return True return False def get_ip_address_version(address): """ This function returns the version of IP address :param address: IP address :return: """ if not HAS_IPADDRESS: raise Exception(missing_required_lib("ipaddress")) try: address = unicode(address) except NameError: address = str(address) version = ipaddress.ip_address(address.split("/")[0]).version return version def get_route_type(address): """ This function returns the route type based on IP address :param address: :return: """ version = get_ip_address_version(address) if version == 6: return "route6" elif version == 4: return "route" def _bool_to_str(val): """ This function converts the bool value into string. :param val: bool value. :return: enable/disable. """ return "enable" if str(val) == "True" else "disable" if str(val) == "False" else val def _is_w_same(w, h, key): """ This function checks whether the key value is same in desired and target config dictionary. :param w: base config. :param h: target config. :param key:attribute name. :return: True/False. """ return True if h and key in h and h[key] == w[key] else False def _in_target(h, key): """ This function checks whether the target exist and key present in target config. :param h: target config. :param key: attribute name. :return: True/False. """ return True if h and key in h else False diff --git a/tests/integration/targets/vyos_ospf_interfaces/tasks/post_tasks.yml b/tests/integration/targets/vyos_ospf_interfaces/tasks/post_tasks.yml index 0883ef48..f6b009a8 100644 --- a/tests/integration/targets/vyos_ospf_interfaces/tasks/post_tasks.yml +++ b/tests/integration/targets/vyos_ospf_interfaces/tasks/post_tasks.yml @@ -1,7 +1,9 @@ --- - name: Demolish environment vyos.vyos.vyos_config: lines: |- delete interfaces bonding bond2 + delete interfaces ethernet eth2 vif 3 + delete interfaces ethernet eth2 vif 18 vars: ansible_connection: ansible.netcommon.network_cli diff --git a/tests/integration/targets/vyos_ospf_interfaces/tasks/pre_tasks.yml b/tests/integration/targets/vyos_ospf_interfaces/tasks/pre_tasks.yml index af74ff7a..fdb4981c 100644 --- a/tests/integration/targets/vyos_ospf_interfaces/tasks/pre_tasks.yml +++ b/tests/integration/targets/vyos_ospf_interfaces/tasks/pre_tasks.yml @@ -1,7 +1,9 @@ --- - name: Setup environment vyos.vyos.vyos_config: lines: |- set interfaces bonding bond2 + set interfaces ethernet eth2 vif 3 + set interfaces ethernet eth2 vif 18 vars: ansible_connection: ansible.netcommon.network_cli diff --git a/tests/integration/targets/vyos_ospf_interfaces/tests/cli/deleted.yaml b/tests/integration/targets/vyos_ospf_interfaces/tests/cli/deleted.yaml index 59fe52ac..bd70d071 100644 --- a/tests/integration/targets/vyos_ospf_interfaces/tests/cli/deleted.yaml +++ b/tests/integration/targets/vyos_ospf_interfaces/tests/cli/deleted.yaml @@ -1,35 +1,34 @@ --- - debug: msg: START vyos_ospf_interfaces deleted integration tests on connection={{ ansible_connection }} - include_tasks: _populate.yaml - block: - name: Delete the provided configuration register: result vyos.vyos.vyos_ospf_interfaces: &id001 config: - name: bond2 state: deleted - vyos.vyos.vyos_facts: gather_network_resources: ospf_interfaces - assert: that: - - result.commands|length == 2 - result.changed == true - result.commands|symmetric_difference(deleted.commands) == [] - result.after|symmetric_difference(ansible_facts['network_resources']['ospf_interfaces']) == [] - name: Delete the existing configuration with the provided running configuration (IDEMPOTENT) register: result vyos.vyos.vyos_ospf_interfaces: *id001 - name: Assert that the previous task was idempotent assert: that: - result['changed'] == false always: - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_ospf_interfaces/tests/cli/merged.yaml b/tests/integration/targets/vyos_ospf_interfaces/tests/cli/merged.yaml index 7b091dd7..ddff03c9 100644 --- a/tests/integration/targets/vyos_ospf_interfaces/tests/cli/merged.yaml +++ b/tests/integration/targets/vyos_ospf_interfaces/tests/cli/merged.yaml @@ -1,48 +1,52 @@ --- - debug: msg: START vyos_ospf_interfaces merged integration tests on connection={{ ansible_connection }} - include_tasks: _remove_config.yaml - block: - name: Merge the provided configuration with the existing running configuration register: result vyos.vyos.vyos_ospf_interfaces: &id001 config: - name: eth0 address_family: - afi: ipv4 cost: 50 priority: 26 - afi: ipv6 mtu_ignore: true instance: 33 + - name: eth2.3 + address_family: + - afi: ipv4 + cost: 60 + priority: 40 - name: bond2 address_family: - afi: ipv4 transmit_delay: 45 - afi: ipv6 passive: true state: merged - vyos.vyos.vyos_facts: gather_network_resources: ospf_interfaces - assert: that: - - result.commands|length == 6 - result.changed == true - result.commands|symmetric_difference(merged.commands) == [] - result.after|symmetric_difference(ansible_facts['network_resources']['ospf_interfaces']) == [] - name: Merge the provided configuration with the existing running configuration (IDEMPOTENT) register: result vyos.vyos.vyos_ospf_interfaces: *id001 - name: Assert that the previous task was idempotent assert: that: - result['changed'] == false always: - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_ospf_interfaces/tests/cli/overridden.yaml b/tests/integration/targets/vyos_ospf_interfaces/tests/cli/overridden.yaml index 7e728069..252336fc 100644 --- a/tests/integration/targets/vyos_ospf_interfaces/tests/cli/overridden.yaml +++ b/tests/integration/targets/vyos_ospf_interfaces/tests/cli/overridden.yaml @@ -1,42 +1,41 @@ --- - debug: msg: START vyos_ospf_interfaces overridden integration tests on connection={{ ansible_connection }} - include_tasks: _populate.yaml - block: - name: Override the existing configuration with the provided running configuration register: result vyos.vyos.vyos_ospf_interfaces: &id001 config: - name: eth0 address_family: - afi: ipv4 transmit_delay: 50 priority: 26 network: point-to-point - afi: ipv6 dead_interval: 39 state: overridden - vyos.vyos.vyos_facts: gather_network_resources: ospf_interfaces - assert: that: - - result.commands|length == 8 - result.changed == true - result.commands|symmetric_difference(overridden.commands) == [] - result.after|symmetric_difference(ansible_facts['network_resources']['ospf_interfaces']) == [] - name: Override the existing configuration with the provided running configuration (IDEMPOTENT) register: result vyos.vyos.vyos_ospf_interfaces: *id001 - name: Assert that the previous task was idempotent assert: that: - result['changed'] == false always: - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_ospf_interfaces/tests/cli/rendered.yaml b/tests/integration/targets/vyos_ospf_interfaces/tests/cli/rendered.yaml index 4cb5f4f9..5a07c95d 100644 --- a/tests/integration/targets/vyos_ospf_interfaces/tests/cli/rendered.yaml +++ b/tests/integration/targets/vyos_ospf_interfaces/tests/cli/rendered.yaml @@ -1,31 +1,36 @@ --- - debug: msg: START vyos_ospf_interfaces rendered integration tests on connection={{ ansible_connection }} - include_tasks: _remove_config.yaml - block: - name: Structure provided configuration into device specific commands register: result vyos.vyos.vyos_ospf_interfaces: config: - name: eth0 address_family: - afi: ipv4 cost: 50 priority: 26 - afi: ipv6 mtu_ignore: true instance: 33 + - name: eth2.3 + address_family: + - afi: ipv4 + cost: 60 + priority: 40 - name: bond2 address_family: - afi: ipv4 transmit_delay: 45 - afi: ipv6 passive: true state: rendered - assert: that: - result.changed == false - result.rendered|symmetric_difference(merged.commands) == [] diff --git a/tests/integration/targets/vyos_ospf_interfaces/tests/cli/replaced.yaml b/tests/integration/targets/vyos_ospf_interfaces/tests/cli/replaced.yaml index 2bb8a02f..7a5b9fa8 100644 --- a/tests/integration/targets/vyos_ospf_interfaces/tests/cli/replaced.yaml +++ b/tests/integration/targets/vyos_ospf_interfaces/tests/cli/replaced.yaml @@ -1,53 +1,52 @@ --- - debug: msg: START vyos_ospf_interfaces replaced integration tests on connection={{ ansible_connection }} - include_tasks: _populate.yaml - block: - name: Replace the existing configuration with the provided running configuration register: result vyos.vyos.vyos_ospf_interfaces: &id001 config: - name: eth0 address_family: - afi: ipv4 transmit_delay: 50 priority: 26 network: point-to-point - afi: ipv6 dead_interval: 39 - name: bond2 address_family: - afi: ipv4 transmit_delay: 45 bandwidth: 70 authentication: md5_key: key_id: 10 key: "1111111111232345" - afi: ipv6 passive: true state: replaced - vyos.vyos.vyos_facts: gather_network_resources: ospf_interfaces - assert: that: - - result.commands|length == 8 - result.changed == true - result.commands|symmetric_difference(replaced.commands) == [] - result.after|symmetric_difference(ansible_facts['network_resources']['ospf_interfaces']) == [] - name: Replace the existing configuration with the provided running configuration (IDEMPOTENT) register: result vyos.vyos.vyos_ospf_interfaces: *id001 - name: Assert that the previous task was idempotent assert: that: - result['changed'] == false always: - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_ospf_interfaces/tests/cli/rtt.yaml b/tests/integration/targets/vyos_ospf_interfaces/tests/cli/rtt.yaml index c74248e0..e2464457 100644 --- a/tests/integration/targets/vyos_ospf_interfaces/tests/cli/rtt.yaml +++ b/tests/integration/targets/vyos_ospf_interfaces/tests/cli/rtt.yaml @@ -1,64 +1,68 @@ --- - debug: msg: START vyos_ospf_interfaces rtt integration tests on connection={{ ansible_connection }} - include_tasks: _populate.yaml - include_tasks: _remove_config.yaml - block: - name: Merge the provided configuration with the existing running configuration register: baseconfig vyos.vyos.vyos_ospf_interfaces: config: - name: eth0 address_family: - afi: ipv4 cost: 50 priority: 26 - afi: ipv6 mtu_ignore: true instance: 33 + - name: eth2.3 + address_family: + - afi: ipv4 + cost: 60 + priority: 40 - name: bond2 address_family: - afi: ipv4 transmit_delay: 45 - afi: ipv6 passive: true state: merged - vyos.vyos.vyos_facts: gather_network_resources: ospf_interfaces - assert: that: - - baseconfig.commands|length == 6 - baseconfig.changed == true - baseconfig.commands|symmetric_difference(merged.commands) == [] - baseconfig.after|symmetric_difference(ansible_facts['network_resources']['ospf_interfaces']) == [] - name: Apply the provided configuration (config to be reverted) register: result vyos.vyos.vyos_ospf_interfaces: config: - name: eth0 address_family: - afi: ipv4 transmit_delay: 50 priority: 26 network: point-to-point - afi: ipv6 dead_interval: 39 - name: Revert back to base config using facts round trip register: revert vyos.vyos.vyos_ospf_interfaces: config: "{{ ansible_facts['network_resources']['ospf_interfaces'] }}" state: overridden - name: Assert that config was reverted assert: that: baseconfig.after == revert.after always: - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_ospf_interfaces/vars/pre-v1_4.yaml b/tests/integration/targets/vyos_ospf_interfaces/vars/pre-v1_4.yaml index a9e03421..7f84e92e 100644 --- a/tests/integration/targets/vyos_ospf_interfaces/vars/pre-v1_4.yaml +++ b/tests/integration/targets/vyos_ospf_interfaces/vars/pre-v1_4.yaml @@ -1,53 +1,58 @@ --- merged_commands: - set interfaces ethernet eth0 ip ospf cost 50 - set interfaces ethernet eth0 ip ospf priority 26 - set interfaces ethernet eth0 ipv6 ospfv3 mtu-ignore - set interfaces ethernet eth0 ipv6 ospfv3 instance-id 33 + - set interfaces ethernet eth2 vif 3 ip ospf cost 60 + - set interfaces ethernet eth2 vif 3 ip ospf priority 40 - set interfaces bonding bond2 ip ospf transmit-delay 45 - set interfaces bonding bond2 ipv6 ospfv3 passive populate_commands: - set interfaces ethernet eth0 ip ospf cost 50 - set interfaces ethernet eth0 ip ospf priority 26 - set interfaces ethernet eth0 ipv6 ospfv3 mtu-ignore - set interfaces ethernet eth0 ipv6 ospfv3 instance-id 33 + - set interfaces ethernet eth2 vif 18 ip ospf cost 80 - set interfaces bonding bond2 ip ospf transmit-delay 45 - set interfaces bonding bond2 ipv6 ospfv3 passive remove_commands: - delete interfaces ethernet eth0 ip ospf - delete interfaces ethernet eth0 ipv6 ospfv3 - delete interfaces ethernet eth1 ip ospf - delete interfaces ethernet eth1 ipv6 ospfv3 + - delete interfaces ethernet eth2 vif 3 ip ospf - delete interfaces bonding bond1 ip ospf - delete interfaces bonding bond1 ipv6 ospfv3 - delete interfaces bonding bond2 ip ospf - delete interfaces bonding bond2 ipv6 ospfv3 - delete interfaces bonding bond2 parsed_config_file: "_parsed_config_1_3.cfg" replaced_commands: - set interfaces ethernet eth0 ip ospf transmit-delay 50 - set interfaces ethernet eth0 ip ospf network point-to-point - set interfaces ethernet eth0 ipv6 ospfv3 dead-interval 39 - delete interfaces ethernet eth0 ip ospf cost 50 - delete interfaces ethernet eth0 ipv6 ospfv3 instance-id 33 - delete interfaces ethernet eth0 ipv6 ospfv3 mtu-ignore - set interfaces bonding bond2 ip ospf bandwidth 70 - set interfaces bonding bond2 ip ospf authentication md5 key-id 10 md5-key ******** overridden_commands: - delete interfaces bonding bond2 ip ospf - delete interfaces bonding bond2 ipv6 ospfv3 + - delete interfaces ethernet eth2 vif 18 ip ospf - set interfaces ethernet eth0 ip ospf transmit-delay 50 - set interfaces ethernet eth0 ip ospf network point-to-point - set interfaces ethernet eth0 ipv6 ospfv3 dead-interval 39 - delete interfaces ethernet eth0 ip ospf cost 50 - delete interfaces ethernet eth0 ipv6 ospfv3 instance-id 33 - delete interfaces ethernet eth0 ipv6 ospfv3 mtu-ignore deleted_commands: - delete interfaces bonding bond2 ip ospf - delete interfaces bonding bond2 ipv6 ospfv3 diff --git a/tests/integration/targets/vyos_ospf_interfaces/vars/v1_4.yaml b/tests/integration/targets/vyos_ospf_interfaces/vars/v1_4.yaml index 15b7f5a7..3864f33a 100644 --- a/tests/integration/targets/vyos_ospf_interfaces/vars/v1_4.yaml +++ b/tests/integration/targets/vyos_ospf_interfaces/vars/v1_4.yaml @@ -1,48 +1,53 @@ --- merged_commands: - set protocols ospf interface eth0 cost 50 - set protocols ospf interface eth0 priority 26 - set protocols ospfv3 interface eth0 mtu-ignore - set protocols ospfv3 interface eth0 instance-id 33 + - set protocols ospf interface eth2.3 cost 60 + - set protocols ospf interface eth2.3 priority 40 - set protocols ospfv3 interface bond2 passive - set protocols ospf interface bond2 transmit-delay 45 populate_commands: - set protocols ospf interface eth0 cost 50 - set protocols ospf interface eth0 priority 26 - set protocols ospfv3 interface eth0 mtu-ignore - set protocols ospfv3 interface eth0 instance-id 33 + - set protocols ospf interface eth2.18 cost 80 - set protocols ospfv3 interface bond2 passive - set protocols ospf interface bond2 transmit-delay 45 remove_commands: - delete protocols ospf interface eth0 + - delete protocols ospf interface eth2.3 - delete protocols ospf interface bond2 - delete protocols ospfv3 interface bond2 - delete protocols ospfv3 interface eth0 parsed_config_file: "_parsed_config_1_4.cfg" replaced_commands: - set protocols ospf interface eth0 transmit-delay 50 - set protocols ospf interface eth0 network point-to-point - set protocols ospfv3 interface eth0 dead-interval 39 - delete protocols ospf interface eth0 cost 50 - delete protocols ospfv3 interface eth0 instance-id 33 - delete protocols ospfv3 interface eth0 mtu-ignore - set protocols ospf interface bond2 bandwidth 70 - set protocols ospf interface bond2 authentication md5 key-id 10 md5-key ******** overridden_commands: - delete protocols ospf interface bond2 - delete protocols ospfv3 interface bond2 + - delete protocols ospf interface eth2.18 - set protocols ospf interface eth0 transmit-delay 50 - set protocols ospf interface eth0 network point-to-point - set protocols ospfv3 interface eth0 dead-interval 39 - delete protocols ospf interface eth0 cost 50 - delete protocols ospfv3 interface eth0 instance-id 33 - delete protocols ospfv3 interface eth0 mtu-ignore deleted_commands: - delete protocols ospf interface bond2 - delete protocols ospfv3 interface bond2 diff --git a/tests/unit/modules/network/vyos/test_vyos_ospf_interfaces.py b/tests/unit/modules/network/vyos/test_vyos_ospf_interfaces.py index c7d69d0d..b0a0f0ff 100644 --- a/tests/unit/modules/network/vyos/test_vyos_ospf_interfaces.py +++ b/tests/unit/modules/network/vyos/test_vyos_ospf_interfaces.py @@ -1,459 +1,487 @@ # (c) 2016 Red Hat Inc. # # 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 . # Make coding more python3-ish from __future__ import absolute_import, division, print_function __metaclass__ = type from unittest.mock import patch from ansible_collections.vyos.vyos.plugins.modules import vyos_ospf_interfaces from ansible_collections.vyos.vyos.tests.unit.modules.utils import set_module_args from .vyos_module import TestVyosModule, load_fixture class TestVyosOspfInterfacesModule(TestVyosModule): module = vyos_ospf_interfaces def setUp(self): super(TestVyosOspfInterfacesModule, self).setUp() self.mock_get_resource_connection_config = patch( "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.resource_module_base.get_resource_connection", ) self.get_resource_connection_config = self.mock_get_resource_connection_config.start() self.mock_execute_show_command = patch( "ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.ospf_interfaces.ospf_interfaces.Ospf_interfacesFacts.get_device_data", ) self.execute_show_command = self.mock_execute_show_command.start() self.mock_get_os_version = patch( - "ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.config.ospf_interfaces.ospf_interfaces.get_os_version" + "ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.config.ospf_interfaces.ospf_interfaces.get_os_version", ) self.test_version = "1.2" self.get_os_version = self.mock_get_os_version.start() self.get_os_version.return_value = self.test_version self.mock_facts_get_os_version = patch( - "ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.ospf_interfaces.ospf_interfaces.get_os_version" + "ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.ospf_interfaces.ospf_interfaces.get_os_version", ) self.get_facts_os_version = self.mock_facts_get_os_version.start() self.get_facts_os_version.return_value = self.test_version self.maxDiff = None def tearDown(self): super(TestVyosOspfInterfacesModule, self).tearDown() self.mock_get_resource_connection_config.stop() self.mock_execute_show_command.stop() self.mock_get_os_version.stop() self.mock_facts_get_os_version.stop() def load_fixtures(self, commands=None, filename=None): if filename is None: filename = "vyos_ospf_interfaces_config.cfg" def load_from_file(*args, **kwargs): output = load_fixture(filename) return output self.execute_show_command.side_effect = load_from_file def sort_address_family(self, entry_list): for entry in entry_list: if entry.get("address_family"): entry["address_family"].sort(key=lambda i: i.get("afi")) def test_vyos_ospf_interfaces_merged_new_config(self): set_module_args( dict( config=[ dict( name="eth0", address_family=[ dict( afi="ipv4", cost=100, authentication=dict(plaintext_password="abcdefg!"), priority=55, ), dict(afi="ipv6", mtu_ignore=True, instance=20), ], ), dict( name="bond2", address_family=[ dict( afi="ipv4", transmit_delay=9, ), dict(afi="ipv6", passive=True), ], ), ], state="merged", ), ) commands = [ "set interfaces bonding bond2 ip ospf transmit-delay 9", "set interfaces bonding bond2 ipv6 ospfv3 passive", "set interfaces ethernet eth0 ip ospf cost 100", "set interfaces ethernet eth0 ip ospf priority 55", "set interfaces ethernet eth0 ip ospf authentication plaintext-password abcdefg!", "set interfaces ethernet eth0 ipv6 ospfv3 instance-id 20", ] self.execute_module(changed=True, commands=commands) + def test_vyos_ospf_interfaces_merged_vif_config(self): + set_module_args( + dict( + config=[ + dict( + name="eth0.3", + address_family=[ + dict( + afi="ipv4", + cost=100, + authentication=dict(plaintext_password="abcdefg!"), + priority=55, + ), + dict(afi="ipv6", mtu_ignore=True), + ], + ), + ], + state="merged", + ), + ) + commands = [ + "set interfaces ethernet eth0 vif 3 ip ospf cost 100", + "set interfaces ethernet eth0 vif 3 ip ospf priority 55", + "set interfaces ethernet eth0 vif 3 ip ospf authentication plaintext-password abcdefg!", + "set interfaces ethernet eth0 vif 3 ipv6 ospfv3 mtu-ignore", + ] + self.execute_module(changed=True, commands=commands) + def test_vyos_ospf_interfaces_merged_idempotent(self): set_module_args( dict( config=[ dict( name="eth0", address_family=[ dict(afi="ipv6", mtu_ignore=True, instance=33), ], ), dict( name="eth1", address_family=[ dict( afi="ipv4", cost=100, ), dict(afi="ipv6", ifmtu=33), ], ), ], ), ) self.execute_module(changed=False, commands=[]) def test_vyos_ospf_interfaces_existing_config_merged(self): set_module_args( dict( config=[ dict( name="eth0", address_family=[ dict(afi="ipv6", cost=500), ], ), dict( name="eth1", address_family=[ dict( afi="ipv4", priority=100, ), dict(afi="ipv6", ifmtu=25), ], ), ], ), ) commands = [ "set interfaces ethernet eth0 ipv6 ospfv3 cost 500", "set interfaces ethernet eth1 ip ospf priority 100", "set interfaces ethernet eth1 ipv6 ospfv3 ifmtu 25", ] self.execute_module(changed=True, commands=commands) def test_vyos_ospf_interfaces_replaced(self): set_module_args( dict( config=[ dict( name="eth0", address_family=[ dict( afi="ipv4", cost=100, authentication=dict(plaintext_password="abcdefg!"), priority=55, ), ], ), dict( name="bond2", address_family=[ dict( afi="ipv4", transmit_delay=9, ), dict(afi="ipv6", passive=True), ], ), ], state="replaced", ), ) commands = [ "set interfaces bonding bond2 ip ospf transmit-delay 9", "set interfaces bonding bond2 ipv6 ospfv3 passive", "set interfaces ethernet eth0 ip ospf cost 100", "set interfaces ethernet eth0 ip ospf priority 55", "set interfaces ethernet eth0 ip ospf authentication plaintext-password abcdefg!", "delete interfaces ethernet eth0 ipv6 ospfv3 instance-id 33", "delete interfaces ethernet eth0 ipv6 ospfv3 mtu-ignore", ] self.execute_module(changed=True, commands=commands) def test_vyos_ospf_interfaces_replaced_idempotent(self): set_module_args( dict( config=[ dict( name="eth0", address_family=[ dict(afi="ipv6", mtu_ignore=True, instance=33), ], ), dict( name="eth1", address_family=[ dict( afi="ipv4", cost=100, ), dict(afi="ipv6", ifmtu=33), ], ), ], state="replaced", ), ) self.execute_module(changed=False, commands=[]) def test_vyos_ospf_interfaces_overridden(self): set_module_args( dict( config=[ dict( name="eth0", address_family=[ dict( afi="ipv4", cost=100, authentication=dict(plaintext_password="abcdefg!"), priority=55, ), ], ), dict( name="bond2", address_family=[ dict( afi="ipv4", transmit_delay=9, ), dict(afi="ipv6", passive=True), ], ), ], state="overridden", ), ) commands = [ "set interfaces bonding bond2 ip ospf transmit-delay 9", "set interfaces bonding bond2 ipv6 ospfv3 passive", "set interfaces ethernet eth0 ip ospf cost 100", "set interfaces ethernet eth0 ip ospf priority 55", "set interfaces ethernet eth0 ip ospf authentication plaintext-password abcdefg!", "delete interfaces ethernet eth1 ip ospf", "delete interfaces ethernet eth1 ipv6 ospfv3", "delete interfaces ethernet eth0 ipv6 ospfv3 mtu-ignore", "delete interfaces ethernet eth0 ipv6 ospfv3 instance-id 33", ] self.execute_module(changed=True, commands=commands) def test_vyos_ospf_interfaces_overridden_idempotent(self): set_module_args( dict( config=[ dict( name="eth0", address_family=[ dict(afi="ipv6", mtu_ignore=True, instance=33), ], ), dict( name="eth1", address_family=[ dict( afi="ipv4", cost=100, ), dict(afi="ipv6", ifmtu=33), ], ), ], state="overridden", ), ) self.execute_module(changed=False, commands=[]) def test_vyos_ospf_interfaces_deleted(self): set_module_args( dict( config=[ dict( name="eth0", ), ], state="deleted", ), ) commands = ["delete interfaces ethernet eth0 ipv6 ospfv3"] self.execute_module(changed=True, commands=commands) def test_vyos_ospf_interfaces_notpresent_deleted(self): set_module_args( dict( config=[ dict( name="eth3", ), ], state="deleted", ), ) self.execute_module(changed=False, commands=[]) def test_vyos_ospf_interfaces_rendered(self): set_module_args( dict( config=[ dict( name="eth0", address_family=[ dict( afi="ipv4", cost=100, authentication=dict(plaintext_password="abcdefg!"), priority=55, ), dict(afi="ipv6", mtu_ignore=True, instance=20), ], ), dict( name="bond2", address_family=[ dict( afi="ipv4", transmit_delay=9, ), dict(afi="ipv6", passive=True), ], ), ], state="rendered", ), ) commands = [ "set interfaces ethernet eth0 ip ospf cost 100", "set interfaces ethernet eth0 ip ospf authentication plaintext-password abcdefg!", "set interfaces ethernet eth0 ip ospf priority 55", "set interfaces ethernet eth0 ipv6 ospfv3 mtu-ignore", "set interfaces ethernet eth0 ipv6 ospfv3 instance-id 20", "set interfaces bonding bond2 ip ospf transmit-delay 9", "set interfaces bonding bond2 ipv6 ospfv3 passive", ] result = self.execute_module(changed=False) self.assertEqual(sorted(result["rendered"]), sorted(commands), result["rendered"]) def test_vyos_ospf_interfaces_parsed(self): commands = [ "set interfaces bonding bond2 ip ospf authentication md5 key-id 10 md5-key '1111111111232345'", "set interfaces bonding bond2 ip ospf bandwidth '70'", "set interfaces bonding bond2 ip ospf transmit-delay '45'", "set interfaces bonding bond2 ipv6 ospfv3 'passive'", "set interfaces ethernet eth0 ip ospf cost '50'", "set interfaces ethernet eth0 ip ospf priority '26'", "set interfaces ethernet eth0 ipv6 ospfv3 instance-id '33'", "set interfaces ethernet eth0 ipv6 ospfv3 'mtu-ignore'", "set interfaces ethernet eth1 ip ospf network 'point-to-point'", "set interfaces ethernet eth1 ip ospf priority '26'", "set interfaces ethernet eth1 ip ospf transmit-delay '50'", "set interfaces ethernet eth1 ipv6 ospfv3 dead-interval '39'", ] parsed_str = "\n".join(commands) set_module_args(dict(running_config=parsed_str, state="parsed")) result = self.execute_module(changed=False) parsed_list = [ { "address_family": [ { "afi": "ipv4", "authentication": { "md5_key": { "key": "1111111111232345", "key_id": 10, }, }, "bandwidth": 70, "transmit_delay": 45, }, {"afi": "ipv6", "passive": True}, ], "name": "bond2", }, { "address_family": [ {"afi": "ipv4", "cost": 50, "priority": 26}, {"afi": "ipv6", "instance": "33", "mtu_ignore": True}, ], "name": "eth0", }, { "address_family": [ { "afi": "ipv4", "network": "point-to-point", "priority": 26, "transmit_delay": 50, }, {"afi": "ipv6", "dead_interval": 39}, ], "name": "eth1", }, ] result_list = self.sort_address_family(result["parsed"]) given_list = self.sort_address_family(parsed_list) self.assertEqual(result_list, given_list) def test_vyos_ospf_interfaces_gathered(self): set_module_args(dict(state="gathered")) result = self.execute_module(changed=False, filename="vyos_ospf_interfaces_config.cfg") gathered_list = [ { "address_family": [{"afi": "ipv6", "instance": "33", "mtu_ignore": True}], "name": "eth0", }, { "address_family": [ {"afi": "ipv4", "cost": 100}, {"afi": "ipv6", "ifmtu": 33}, ], "name": "eth1", }, ] result_list = self.sort_address_family(result["gathered"]) given_list = self.sort_address_family(gathered_list) self.assertEqual(result_list, given_list) diff --git a/tests/unit/modules/network/vyos/test_vyos_ospf_interfaces14.py b/tests/unit/modules/network/vyos/test_vyos_ospf_interfaces14.py index ef27860a..d3f8bc38 100644 --- a/tests/unit/modules/network/vyos/test_vyos_ospf_interfaces14.py +++ b/tests/unit/modules/network/vyos/test_vyos_ospf_interfaces14.py @@ -1,511 +1,540 @@ # Spawned from test_vyos_ospf_interfaces (c) 2016 Red Hat Inc. # # 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 . # Make coding more python3-ish from __future__ import absolute_import, division, print_function + __metaclass__ = type from unittest.mock import patch from ansible_collections.vyos.vyos.plugins.modules import vyos_ospf_interfaces from ansible_collections.vyos.vyos.tests.unit.modules.utils import set_module_args from .vyos_module import TestVyosModule, load_fixture class TestVyosOspfInterfacesModule14(TestVyosModule): module = vyos_ospf_interfaces def setUp(self): super(TestVyosOspfInterfacesModule14, self).setUp() self.mock_get_resource_connection_config = patch( - "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.resource_module_base.get_resource_connection" + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.resource_module_base.get_resource_connection", ) self.get_resource_connection_config = self.mock_get_resource_connection_config.start() self.mock_execute_show_command = patch( - "ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.ospf_interfaces.ospf_interfaces.Ospf_interfacesFacts.get_device_data" + "ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.ospf_interfaces.ospf_interfaces.Ospf_interfacesFacts.get_device_data", ) self.execute_show_command = self.mock_execute_show_command.start() self.mock_get_os_version = patch( - "ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.config.ospf_interfaces.ospf_interfaces.get_os_version" + "ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.config.ospf_interfaces.ospf_interfaces.get_os_version", ) self.test_version = "1.4" self.get_os_version = self.mock_get_os_version.start() self.get_os_version.return_value = self.test_version self.mock_facts_get_os_version = patch( - "ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.ospf_interfaces.ospf_interfaces.get_os_version" + "ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.ospf_interfaces.ospf_interfaces.get_os_version", ) self.get_facts_os_version = self.mock_facts_get_os_version.start() self.get_facts_os_version.return_value = self.test_version self.maxDiff = None def tearDown(self): super(TestVyosOspfInterfacesModule14, self).tearDown() self.mock_get_resource_connection_config.stop() self.mock_execute_show_command.stop() self.mock_get_os_version.stop() def load_fixtures(self, commands=None, filename=None): if filename is None: filename = "vyos_ospf_interfaces_config_14.cfg" def load_from_file(*args, **kwargs): output = load_fixture(filename) return output self.execute_show_command.side_effect = load_from_file def sort_address_family(self, entry_list): for entry in entry_list: if entry.get("address_family"): entry["address_family"].sort(key=lambda i: i.get("afi")) def test_vyos_ospf_interfaces_merged_new_config(self): set_module_args( dict( config=[ dict( name="eth0", address_family=[ dict( afi="ipv4", cost=100, authentication=dict(plaintext_password="abcdefg!"), priority=55, ), dict(afi="ipv6", mtu_ignore=True, instance=20), ], ), dict( name="bond2", address_family=[ dict( afi="ipv4", transmit_delay=9, ), dict(afi="ipv6", passive=True), ], ), ], state="merged", - ) + ), ) commands = [ "set protocols ospf interface bond2 transmit-delay 9", "set protocols ospfv3 interface bond2 passive", "set protocols ospf interface eth0 cost 100", "set protocols ospf interface eth0 priority 55", "set protocols ospf interface eth0 authentication plaintext-password abcdefg!", "set protocols ospfv3 interface eth0 instance-id 20", ] self.execute_module(changed=True, commands=commands) + def test_vyos_ospf_interfaces_merged_vif_config(self): + set_module_args( + dict( + config=[ + dict( + name="eth0.3", + address_family=[ + dict( + afi="ipv4", + cost=100, + authentication=dict(plaintext_password="abcdefg!"), + priority=55, + ), + dict(afi="ipv6", mtu_ignore=True), + ], + ), + ], + state="merged", + ), + ) + commands = [ + "set protocols ospf interface eth0.3 cost 100", + "set protocols ospf interface eth0.3 priority 55", + "set protocols ospf interface eth0.3 authentication plaintext-password abcdefg!", + "set protocols ospfv3 interface eth0.3 mtu-ignore", + ] + self.execute_module(changed=True, commands=commands) + def test_vyos_ospf_interfaces_merged_idempotent(self): set_module_args( dict( config=[ dict( name="eth0", address_family=[ dict(afi="ipv6", mtu_ignore=True, instance=33), ], ), dict( name="eth1", address_family=[ dict( afi="ipv4", cost=100, ), dict(afi="ipv6", ifmtu=33), ], ), ], - ) + ), ) self.execute_module(changed=False, commands=[]) def test_vyos_ospf_interfaces_existing_config_merged(self): set_module_args( dict( config=[ dict( name="eth0", address_family=[ dict(afi="ipv6", cost=500), ], ), dict( name="eth1", address_family=[ dict( afi="ipv4", priority=100, ), dict(afi="ipv6", ifmtu=25), ], ), ], - ) + ), ) commands = [ "set protocols ospfv3 interface eth0 cost 500", "set protocols ospf interface eth1 priority 100", "set protocols ospfv3 interface eth1 ifmtu 25", ] self.execute_module(changed=True, commands=commands) def test_vyos_ospf_interfaces_replaced(self): set_module_args( dict( config=[ dict( name="eth0", address_family=[ dict( afi="ipv4", cost=100, authentication=dict(plaintext_password="abcdefg!"), priority=55, ), ], ), dict( name="bond2", address_family=[ dict( afi="ipv4", transmit_delay=9, ), dict(afi="ipv6", passive=True), ], ), ], state="replaced", - ) + ), ) commands = [ "set protocols ospf interface bond2 transmit-delay 9", "set protocols ospfv3 interface bond2 passive", "set protocols ospf interface eth0 cost 100", "set protocols ospf interface eth0 priority 55", "set protocols ospf interface eth0 authentication plaintext-password abcdefg!", "delete protocols ospfv3 interface eth0 instance-id 33", "delete protocols ospfv3 interface eth0 mtu-ignore", ] self.execute_module(changed=True, commands=commands) def test_vyos_ospf_passive_interfaces_replaced(self): set_module_args( dict( config=[ dict( name="eth0", address_family=[ dict( afi="ipv4", passive=True, ), ], ), dict( name="eth1", address_family=[ dict( afi="ipv4", passive=True, ), dict( afi="ipv6", passive=True, ), ], ), dict( name="bond2", address_family=[ dict( afi="ipv4", passive=True, ), dict(afi="ipv6", passive=True), ], ), ], state="replaced", - ) + ), ) commands = [ "delete protocols ospf interface eth1 cost 100", "delete protocols ospfv3 interface eth0 instance-id 33", "delete protocols ospfv3 interface eth0 mtu-ignore", "delete protocols ospfv3 interface eth1 ifmtu 33", "set protocols ospf interface bond2 passive", "set protocols ospfv3 interface bond2 passive", "set protocols ospf interface eth0 passive", "set protocols ospf interface eth1 passive", "set protocols ospfv3 interface eth1 passive", ] self.maxDiff = None self.execute_module(changed=True, commands=commands) def test_vyos_ospf_interfaces_replaced_idempotent(self): set_module_args( dict( config=[ dict( name="eth0", address_family=[ dict(afi="ipv6", mtu_ignore=True, instance=33), ], ), dict( name="eth1", address_family=[ dict( afi="ipv4", cost=100, ), dict(afi="ipv6", ifmtu=33), ], ), ], state="replaced", - ) + ), ) self.execute_module(changed=False, commands=[]) def test_vyos_ospf_interfaces_overridden(self): set_module_args( dict( config=[ dict( name="eth0", address_family=[ dict( afi="ipv4", cost=100, authentication=dict(plaintext_password="abcdefg!"), priority=55, ), ], ), dict( name="bond2", address_family=[ dict( afi="ipv4", transmit_delay=9, ), dict(afi="ipv6", passive=True), ], ), ], state="overridden", - ) + ), ) commands = [ "set protocols ospf interface bond2 transmit-delay 9", "set protocols ospfv3 interface bond2 passive", "set protocols ospf interface eth0 cost 100", "set protocols ospf interface eth0 priority 55", "set protocols ospf interface eth0 authentication plaintext-password abcdefg!", "delete protocols ospf interface eth1", "delete protocols ospfv3 interface eth1", "delete protocols ospfv3 interface eth0 mtu-ignore", "delete protocols ospfv3 interface eth0 instance-id 33", ] self.execute_module(changed=True, commands=commands) def test_vyos_ospf_interfaces_overridden_idempotent(self): set_module_args( dict( config=[ dict( name="eth0", address_family=[ dict(afi="ipv6", mtu_ignore=True, instance=33), ], ), dict( name="eth1", address_family=[ dict( afi="ipv4", cost=100, ), dict(afi="ipv6", ifmtu=33), ], ), ], state="overridden", - ) + ), ) self.execute_module(changed=False, commands=[]) def test_vyos_ospf_interfaces_deleted(self): set_module_args( dict( config=[ dict( name="eth0", ), ], state="deleted", - ) + ), ) commands = ["delete protocols ospfv3 interface eth0"] self.execute_module(changed=True, commands=commands) def test_vyos_ospf_interfaces_notpresent_deleted(self): set_module_args( dict( config=[ dict( name="eth3", ), ], state="deleted", - ) + ), ) self.execute_module(changed=False, commands=[]) def test_vyos_ospf_interfaces_rendered(self): set_module_args( dict( config=[ dict( name="eth0", address_family=[ dict( afi="ipv4", cost=100, authentication=dict(plaintext_password="abcdefg!"), priority=55, ), dict(afi="ipv6", mtu_ignore=True, instance=20), ], ), dict( name="bond2", address_family=[ dict( afi="ipv4", transmit_delay=9, ), dict(afi="ipv6", passive=True), ], ), ], state="rendered", - ) + ), ) commands = [ "set protocols ospf interface eth0 cost 100", "set protocols ospf interface eth0 authentication plaintext-password abcdefg!", "set protocols ospf interface eth0 priority 55", "set protocols ospfv3 interface eth0 mtu-ignore", "set protocols ospfv3 interface eth0 instance-id 20", "set protocols ospf interface bond2 transmit-delay 9", "set protocols ospfv3 interface bond2 passive", ] result = self.execute_module(changed=False) self.assertEqual(sorted(result["rendered"]), sorted(commands), result["rendered"]) def test_vyos_ospf_interfaces_parsed(self): commands = [ "set protocols ospf interface bond2 authentication md5 key-id 10 md5-key '1111111111232345'", "set protocols ospf interface bond2 bandwidth '70'", "set protocols ospf interface bond2 transmit-delay '45'", "set protocols ospfv3 interface bond2 'passive'", "set protocols ospf interface eth0 cost '50'", "set protocols ospf interface eth0 priority '26'", "set protocols ospfv3 interface eth0 instance-id '33'", "set protocols ospfv3 interface eth0 'mtu-ignore'", "set protocols ospf interface eth1 network 'point-to-point'", "set protocols ospf interface eth1 priority '26'", "set protocols ospf interface eth1 transmit-delay '50'", "set protocols ospfv3 interface eth1 dead-interval '39'", ] parsed_str = "\n".join(commands) set_module_args(dict(running_config=parsed_str, state="parsed")) result = self.execute_module(changed=False) parsed_list = [ { "address_family": [ { "afi": "ipv4", "authentication": { "md5_key": { "key": "1111111111232345", "key_id": 10, - } + }, }, "bandwidth": 70, "transmit_delay": 45, }, {"afi": "ipv6", "passive": True}, ], "name": "bond2", }, { "address_family": [ {"afi": "ipv4", "cost": 50, "priority": 26}, {"afi": "ipv6", "instance": "33", "mtu_ignore": True}, ], "name": "eth0", }, { "address_family": [ { "afi": "ipv4", "network": "point-to-point", "priority": 26, "transmit_delay": 50, }, {"afi": "ipv6", "dead_interval": 39}, ], "name": "eth1", }, ] result_list = self.sort_address_family(result["parsed"]) given_list = self.sort_address_family(parsed_list) self.assertEqual(result_list, given_list) def test_vyos_ospf_interfaces_gathered(self): set_module_args(dict(state="gathered")) result = self.execute_module(changed=False, filename="vyos_ospf_interfaces_config.cfg") gathered_list = [ { "address_family": [{"afi": "ipv6", "instance": "33", "mtu_ignore": True}], "name": "eth0", }, { "address_family": [ {"afi": "ipv4", "cost": 100}, {"afi": "ipv6", "ifmtu": 33}, ], "name": "eth1", }, ] result_list = self.sort_address_family(result["gathered"]) given_list = self.sort_address_family(gathered_list) self.assertEqual(result_list, given_list)