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/changelogs/fragments/T7012_ospfv2_integration_tests.yaml b/changelogs/fragments/T7012_ospfv2_integration_tests.yaml new file mode 100644 index 00000000..b1382697 --- /dev/null +++ b/changelogs/fragments/T7012_ospfv2_integration_tests.yaml @@ -0,0 +1,6 @@ +--- +trivial: + - vyos_ospfv2 - fix intergration test suite. + - vyos_ospfv2 - fix unit tests. +bugfixes: + - vyos_ospfv2 - passive-interface processing for 1.3- and 1.4+ diff --git a/changelogs/fragments/T7284-delete_firewall_description.yml b/changelogs/fragments/T7284-delete_firewall_description.yml new file mode 100644 index 00000000..fe2b1882 --- /dev/null +++ b/changelogs/fragments/T7284-delete_firewall_description.yml @@ -0,0 +1,2 @@ +bugfixes: + - vyos_firewall_rules - Allow deleting of firewall description. diff --git a/plugins/module_utils/network/vyos/config/firewall_global/firewall_global.py b/plugins/module_utils/network/vyos/config/firewall_global/firewall_global.py index 34dc0ed6..e2a25e32 100644 --- a/plugins/module_utils/network/vyos/config/firewall_global/firewall_global.py +++ b/plugins/module_utils/network/vyos/config/firewall_global/firewall_global.py @@ -1,770 +1,767 @@ # # -*- 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_firewall_global 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.six import iteritems from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base import ( ConfigBase, ) from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ( remove_empties, to_list, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.facts import Facts from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.utils import ( list_diff_want_only, + in_target_not_none, ) 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 Firewall_global(ConfigBase): """ The vyos_firewall_global class """ gather_subset = ["!all", "!min"] gather_network_resources = ["firewall_global"] def __init__(self, module): super(Firewall_global, self).__init__(module) def get_firewall_global_facts(self, data=None): """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, data=data, ) firewall_global_facts = facts["ansible_network_resources"].get("firewall_global") if not firewall_global_facts: return [] return firewall_global_facts def execute_module(self): """Execute the module :rtype: A dictionary :returns: The result from module execution """ result = {"changed": False} warnings = list() commands = list() if self.state in self.ACTION_STATES: existing_firewall_global_facts = self.get_firewall_global_facts() else: existing_firewall_global_facts = [] if self.state in self.ACTION_STATES or self.state == "rendered": commands.extend(self.set_config(existing_firewall_global_facts)) if commands and self.state in self.ACTION_STATES: if not self._module.check_mode: self._connection.edit_config(commands) result["changed"] = True if self.state in self.ACTION_STATES: result["commands"] = commands if self.state in self.ACTION_STATES or self.state == "gathered": changed_firewall_global_facts = self.get_firewall_global_facts() elif self.state == "rendered": result["rendered"] = commands elif self.state == "parsed": running_config = self._module.params["running_config"] if not running_config: self._module.fail_json( msg="value of running_config parameter must not be empty for state parsed", ) result["parsed"] = self.get_firewall_global_facts(data=running_config) else: changed_firewall_global_facts = [] if self.state in self.ACTION_STATES: result["before"] = existing_firewall_global_facts if result["changed"]: result["after"] = changed_firewall_global_facts elif self.state == "gathered": result["gathered"] = changed_firewall_global_facts result["warnings"] = warnings return result def set_config(self, existing_firewall_global_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_firewall_global_facts resp = self.set_state(want, have) return to_list(resp) def set_state(self, w, h): """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 = [] if self.state in ("merged", "replaced", "rendered") and not w: self._module.fail_json( msg="value of config parameter must not be empty for state {0}".format(self.state), ) if self.state == "deleted": commands.extend(self._state_deleted(want=None, have=h)) elif w: if self.state == "merged" or self.state == "rendered": commands.extend(self._state_merged(w, h)) elif self.state == "replaced": commands.extend(self._state_replaced(w, h)) return commands def _state_replaced(self, w, h): """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 h: commands.extend(self._state_deleted(h, w)) commands.extend(self._state_merged(w, h)) 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 = [] commands.extend(self._add_global_attr(want, have)) 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 = [] b_set = ( "config_trap", "validation", "log_martians", "syn_cookies", "twa_hazards_protection", ) if want: for key, val in iteritems(want): if val and key in b_set and not have: commands.append(self._form_attr_cmd(attr=key, opr=False)) elif val and key in b_set and have and key in have and have[key] != val: commands.append(self._form_attr_cmd(attr=key, opr=False)) else: commands.extend(self._render_attr_config(want, have, key)) elif not want and have: commands.append(self._compute_command(opr=False)) elif have: for key, val in iteritems(have): if val and key in b_set: commands.append(self._form_attr_cmd(attr=key, opr=False)) else: commands.extend(self._render_attr_config(want, have, key)) return commands def _render_attr_config(self, w, h, key, opr=False): """ This function invoke the function to extend commands based on the key. :param w: the desired configuration. :param h: the current configuration. :param key: attribute name :param opr: operation :return: list of commands """ commands = [] if key == "ping": commands.extend(self._render_ping(key, w, h, opr=opr)) elif key == "group": commands.extend(self._render_group(key, w, h, opr=opr)) elif key == "state_policy": commands.extend(self._render_state_policy(key, w, h, opr=opr)) elif key == "route_redirects": commands.extend(self._render_route_redirects(key, w, h, opr=opr)) return commands def _add_global_attr(self, w, h, opr=True): """ This function forms the set/delete commands based on the 'opr' type for firewall_global attributes. :param w: the desired config. :param h: the target config. :param opr: True/False. :return: generated commands list. """ commands = [] w_fg = deepcopy(remove_empties(w)) l_set = ( "config_trap", "validation", "log_martians", "syn_cookies", "twa_hazards_protection", ) if w_fg: for key, val in iteritems(w_fg): if opr and key in l_set and not (h and self._is_w_same(w_fg, h, key)): commands.append( self._form_attr_cmd(attr=key, val=self._bool_to_str(val), opr=opr), ) elif not opr: if key and self._is_del(l_set, h): commands.append( self._form_attr_cmd(attr=key, key=self._bool_to_str(val), opr=opr), ) continue if ( key in l_set and not self._in_target(h, key) and not self._is_del(l_set, h) ): commands.append( self._form_attr_cmd(attr=key, val=self._bool_to_str(val), opr=opr), ) else: commands.extend(self._render_attr_config(w_fg, h, key, opr)) return commands def _render_ping(self, attr, w, h, opr): """ This function forms the commands for 'ping' attributes based on the 'opr'. :param attr: attribute name. :param w: the desired configuration. :param h: the target config. :param opr: True/False. :return: generated list of commands. """ commands = [] h_ping = {} l_set = ("all", "broadcast") if h: h_ping = h.get(attr) or {} if self._is_root_del(w[attr], h_ping, attr): for item, value in iteritems(h[attr]): if not opr and item in l_set: commands.append(self._form_attr_cmd(attr=item, opr=opr)) elif w[attr]: if h and attr in h.keys(): h_ping = h.get(attr) or {} for item, value in iteritems(w[attr]): if ( opr and item in l_set and not (h_ping and self._is_w_same(w[attr], h_ping, item)) ): commands.append( self._form_attr_cmd(attr=item, val=self._bool_to_str(value), opr=opr), ) elif ( not opr and item in l_set and not (h_ping and self._is_w_same(w[attr], h_ping, item)) ): commands.append(self._form_attr_cmd(attr=item, opr=opr)) return commands def _render_group(self, attr, w, h, opr): """ This function forms the commands for 'group' attribute based on the 'opr'. :param attr: attribute name. :param w: base config. :param h: target config. :param opr: True/False. :return: generated list of commands. """ commands = [] h_grp = {} if not opr and self._is_root_del(h, w, attr): commands.append(self._form_attr_cmd(attr=attr, opr=opr)) else: if h: h_grp = h.get("group") or {} if w: commands.extend(self._render_grp_mem("port_group", w["group"], h_grp, opr)) commands.extend(self._render_grp_mem("address_group", w["group"], h_grp, opr)) commands.extend(self._render_grp_mem("network_group", w["group"], h_grp, opr)) return commands def _render_grp_mem(self, attr, w, h, opr): """ This function forms the commands for group list/members attributes based on the 'opr'. :param attr: attribute name. :param w: the desired config. :param h: the target config. :param opr: True/False. :return: generated list of commands. """ commands = [] h_grp = [] w_grp = [] l_set = ("name", "description") if w: w_grp = w.get(attr) or [] if h: h_grp = h.get(attr) or [] if w_grp: for want in w_grp: h = self.search_attrib_in_have(h_grp, want, "name") if "afi" in want and want["afi"] == "ipv6": cmd = self._compute_command(key="group", attr="ipv6-" + attr, opr=opr) else: cmd = self._compute_command(key="group", attr=attr, opr=opr) for key, val in iteritems(want): if val: if opr and key in l_set and not (h and self._is_w_same(want, h, key)): if key == "name": commands.append(cmd + " " + str(val)) else: commands.append( cmd + " " + want["name"] + " " + key + " '" + str(want[key]) + "'", ) elif not opr and key in l_set: if key == "name" and self._is_grp_del(h, want, key): commands.append(cmd + " " + want["name"]) continue - if not (h and self._in_target(h, key)) and not self._is_grp_del( - h, - want, - key, - ): + if not (h and in_target_not_none(h, key)) and not self._is_grp_del(h, want, "name"): commands.append(cmd + " " + want["name"] + " " + key) elif key == "members": commands.extend( self._render_ports_addrs( key, want, h, opr, cmd, want["name"], attr, ), ) return commands def _render_ports_addrs(self, attr, w, h, opr, cmd, name, type): """ This function forms the commands for port/address/network group members based on the 'opr'. :param attr: attribute name. :param w: the desired config. :param h: the target config. :param cmd: commands to be prepend. :param name: name of group. :param type: group type. :return: generated list of commands. """ commands = [] have = [] if w: want = w.get(attr) or [] if h: have = h.get(attr) or [] if want: if opr: members = list_diff_want_only(want, have) for member in members: commands.append( cmd + " " + name + " " + self._grp_type(type) + " " + member[self._get_mem_type(type)], ) elif not opr and have: members = list_diff_want_only(want, have) for member in members: commands.append( cmd + " " + name + " " + self._grp_type(type) + " " + member[self._get_mem_type(type)], ) return commands def _get_mem_type(self, group): """ This function returns the member type based on the type of group. """ return "port" if group == "port_group" else "address" def _render_state_policy(self, attr, w, h, opr): """ This function forms the commands for 'state-policy' attributes based on the 'opr'. :param attr: attribute name. :param w: the desired config. :param h: the target config. :param opr: True/False. :return: generated list of commands. """ commands = [] have = [] if LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4"): l_set = ("log", "action", "connection_type", "log_level") else: l_set = ("log", "action", "connection_type") if not opr and self._is_root_del(h, w, attr): commands.append(self._form_attr_cmd(attr=attr, opr=opr)) else: w_sp = deepcopy(remove_empties(w)) want = w_sp.get(attr) or [] if h: have = h.get(attr) or [] if want: for w in want: h = self.search_attrib_in_have(have, w, "connection_type") for key, val in iteritems(w): if val and key != "connection_type": if opr and key in l_set and not (h and self._is_w_same(w, h, key)): if key == "log" and LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4"): commands.append( self._form_attr_cmd( key=attr + " " + w["connection_type"], attr=key, opr=opr, ), ) else: commands.append( self._form_attr_cmd( key=attr + " " + w["connection_type"], attr=key, val=self._bool_to_str(val), opr=opr, ), ) elif not opr and key in l_set: if not h: commands.append( self._form_attr_cmd( attr=attr + " " + w["connection_type"], opr=opr, ), ) break # delete the whole thing and move on if (not self._in_target(h, key) or h[key] is None) and (self._in_target(w, key) and w[key]): # delete if not being replaced and value currently exists commands.append( self._form_attr_cmd( attr=attr + " " + w["connection_type"] + " " + key, val=self._bool_to_str(val), opr=opr, ), ) return commands def _render_route_redirects(self, attr, w, h, opr): """ This function forms the commands for 'route_redirects' attributes based on the 'opr'. :param attr: attribute name. :param w: the desired config. :param h: the target config. :param opr: True/False. :return: generated list of commands. """ commands = [] have = [] l_set = ("afi", "ip_src_route") if w: want = w.get(attr) or [] if h: have = h.get(attr) or [] if want: for w in want: h = self.search_attrib_in_have(have, w, "afi") if 'afi' in w: afi = w['afi'] else: if h and 'afi' in h: afi = h['afi'] else: afi = None afi = None for key, val in iteritems(w): if val and key != "afi": if opr and key in l_set and not (h and self._is_w_same(w, h, key)): commands.append( self._form_attr_cmd( attr=key, val=self._bool_to_str(val), opr=opr, type=afi ), ) elif not opr and key in l_set: if self._is_del(l_set, h): commands.append( self._form_attr_cmd( attr=key, val=self._bool_to_str(val), opr=opr, type=afi ), ) continue if not (h and self._in_target(h, key)) and not self._is_del(l_set, h): commands.append( self._form_attr_cmd( attr=key, val=self._bool_to_str(val), opr=opr, type=afi ), ) elif key == "icmp_redirects": commands.extend(self._render_icmp_redirects(key, w, h, opr)) return commands def _render_icmp_redirects(self, attr, w, h, opr): """ This function forms the commands for 'icmp_redirects' attributes based on the 'opr'. :param attr: attribute name. :param w: the desired config. :param h: the target config. :param opr: True/False. :return: generated list of commands. """ commands = [] h_red = {} l_set = ("send", "receive") if w and 'afi' in w: afi = w['afi'] else: if h and 'afi' in h: afi = h['afi'] else: afi = None if w[attr]: if h and attr in h.keys(): h_red = h.get(attr) or {} for item, value in iteritems(w[attr]): if opr and item in l_set and not (h_red and self._is_w_same(w[attr], h_red, item)): commands.append( self._form_attr_cmd(attr=item, val=self._bool_to_str(value), opr=opr, type=afi) ) elif ( not opr and item in l_set and not (h_red and self._is_w_same(w[attr], h_red, item)) ): commands.append(self._form_attr_cmd(attr=item, opr=opr, type=afi)) return commands def search_attrib_in_have(self, have, want, attr): """ This function returns the attribute if it is present in target config. :param have: the target config. :param want: the desired config. :param attr: attribute name . :return: attribute/None """ if have: for h in have: if h[attr] == want[attr]: return h return None def _form_attr_cmd(self, key=None, attr=None, val=None, opr=True, type=None): """ This function forms the command for leaf attribute. :param key: parent key. :param attr: attribute name :param value: value :param opr: True/False. :param type: AF type of attribute. :return: generated command. """ command = self._compute_command(key=key, attr=self._map_attrib(attr, type=type), val=val, opr=opr) return command def _compute_command(self, key=None, attr=None, val=None, remove=False, opr=True): """ This function construct the add/delete command based on passed attributes. :param key: parent key. :param attr: attribute name :param value: value :param remove: True/False. :param opr: True/False. :return: generated command. """ if remove or not opr: cmd = "delete firewall " else: cmd = "set firewall " if attr and key != "group" and LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4"): cmd += "global-options " if key: cmd += key.replace("_", "-") + " " if attr: cmd += attr.replace("_", "-") if val and opr: if key == "state_policy" and LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4"): cmd += "" else: cmd += " '" + str(val) + "'" return cmd.strip() def _bool_to_str(self, 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 _grp_type(self, val): """ This function returns the group member type based on value argument. :param val: value. :return: member type. """ return ( "address" if val == "address_group" else "network" if val == "network_group" else "port" ) def _is_w_same(self, 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(self, 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 def _is_grp_del(self, w, h, key): """ This function checks whether group needed to be deleted based on desired and target configs. :param w: the desired config. :param h: the target config. :param key: group name. :return: True/False. """ return True if h and key in h and (not w or key not in w or not w[key]) else False def _is_root_del(self, w, h, key): """ This function checks whether a root attribute which can have further child attributes needed to be deleted. :param w: the desired config. :param h: the target config. :param key: attribute name. :return: True/False. """ return True if h and key in h and (not w or key not in w or not w[key]) else False def _is_del(self, b_set, h, key="number"): """ This function checks whether attribute needs to be deleted when operation is false and attribute present in present target config. :param b_set: attribute set. :param h: target config. :param key: number. :return: True/False. """ return key in b_set and not self._in_target(h, key) def _map_attrib(self, attrib, type=None): """ - This function construct the regex string. - replace the underscore with hyphen. :param attrib: attribute :return: regex string """ regex = attrib.replace("_", "-") if attrib == "send": if type == "ipv6": regex = "ipv6-send-redirects" else: regex = "send-redirects" elif attrib == "ip_src_route": if type == "ipv6": regex = "ipv6-src-route" elif attrib == "receive": if type == "ipv6": regex = "ipv6-receive-redirects" else: regex = "receive-redirects" elif attrib == "disabled": regex = "disable" elif attrib == "all": regex = "all-ping" elif attrib == "broadcast": regex = "broadcast-ping" elif attrib == "validation": regex = "source-validation" return regex diff --git a/plugins/module_utils/network/vyos/config/ospfv2/ospfv2.py b/plugins/module_utils/network/vyos/config/ospfv2/ospfv2.py index a9c1de1b..ababc6f1 100644 --- a/plugins/module_utils/network/vyos/config/ospfv2/ospfv2.py +++ b/plugins/module_utils/network/vyos/config/ospfv2/ospfv2.py @@ -1,811 +1,820 @@ # # -*- 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_ospfv2 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.six import iteritems from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base import ( ConfigBase, ) from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ( remove_empties, to_list, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.facts import Facts from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.utils import ( _bool_to_str, _in_target, _is_w_same, list_diff_want_only, ) +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 Ospfv2(ConfigBase): """ The vyos_ospfv2 class """ gather_subset = ["!all", "!min"] gather_network_resources = ["ospfv2"] def __init__(self, module): super(Ospfv2, self).__init__(module) def get_ospfv2_facts(self, data=None): """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, data=data, ) ospfv2_facts = facts["ansible_network_resources"].get("ospfv2", {}) return ospfv2_facts def execute_module(self): """Execute the module :rtype: A dictionary :returns: The result from module execution """ result = {"changed": False} warnings = list() commands = list() if self.state in self.ACTION_STATES: existing_ospfv2_facts = self.get_ospfv2_facts() else: existing_ospfv2_facts = {} if self.state in self.ACTION_STATES or self.state == "rendered": commands.extend(self.set_config(existing_ospfv2_facts)) if commands and self.state in self.ACTION_STATES: if not self._module.check_mode: self._connection.edit_config(commands) result["changed"] = True if self.state in self.ACTION_STATES: result["commands"] = commands if self.state in self.ACTION_STATES or self.state == "gathered": changed_ospfv2_facts = self.get_ospfv2_facts() elif self.state == "rendered": result["rendered"] = commands elif self.state == "parsed": running_config = self._module.params["running_config"] if not running_config: self._module.fail_json( msg="value of running_config parameter must not be empty for state parsed", ) result["parsed"] = self.get_ospfv2_facts(data=running_config) else: changed_ospfv2_facts = {} if self.state in self.ACTION_STATES: result["before"] = existing_ospfv2_facts if result["changed"]: result["after"] = changed_ospfv2_facts elif self.state == "gathered": result["gathered"] = changed_ospfv2_facts result["warnings"] = warnings return result def set_config(self, existing_ospfv2_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_ospfv2_facts - resp = self.set_state(want, have) + resp = self.set_state(remove_empties(want), remove_empties(have)) return to_list(resp) def set_state(self, w, h): """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 = [] if self.state in ("merged", "replaced", "overridden", "rendered") and not w: self._module.fail_json( msg="value of config parameter must not be empty for state {0}".format(self.state), ) if self.state == "deleted": commands.extend(self._state_deleted(h)) elif self.state in ("merged", "rendered"): commands.extend(self._state_merged(w, h)) elif self.state == "replaced": commands.extend(self._state_replaced(w, h)) return commands def search_obj_in_have(self, have, w_name, key): """ This function returns the rule-set/rule if it is present in target config. :param have: target config. :param w_name: rule-set name. :param type: rule_sets/rule/r_list. :return: rule-set/rule. """ if have: for item in have: if item[key] == w_name[key]: return item return None 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._render_ospf_param(have, want, opr=False)) commands.extend(self._render_ospf_param(want, 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 = [] commands.extend(self._render_ospf_param(want, have)) return commands def _state_deleted(self, 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 = [] if have: commands.append("delete protocols ospf") return commands def _render_ospf_param(self, want, have, opr=True): """ This function forms the set/delete commands for ospf leaf attributes and triggers the process for other child attributes. for firewall_global attributes. :param w: the desired config. :param h: the target config. :param opr: True/False. :return: generated commands list. """ commands = [] w = deepcopy(remove_empties(want)) leaf = ("default_metric", "log_adjacency_changes") if w: for key, val in iteritems(w): if opr and key in leaf and not _is_w_same(w, have, key): commands.append(self._form_attr_cmd(attr=key, val=_bool_to_str(val), opr=opr)) elif not opr and key in leaf and not _in_target(have, key): commands.append(self._form_attr_cmd(attr=key, val=_bool_to_str(val), opr=opr)) else: commands.extend(self._render_child_param(w, have, key, opr)) return commands def _render_child_param(self, w, h, key, opr=True): """ This function invoke the function to extend commands based on the key. :param w: the desired configuration. :param h: the current configuration. :param key: attribute name. :param opr: operation. :return: list of commands. """ commands = [] if key in ("neighbor", "redistribute"): commands.extend(self._render_list_dict_param(key, w, h, opr=opr)) elif key in ("default_information", "max_metric"): commands.extend(self._render_nested_dict_param(key, w, h, opr=opr)) elif key in ("mpls_te", "auto_cost", "parameters", "auto_cost"): commands.extend(self._render_dict_param(key, w, h, opr=opr)) elif key in ( "route_map", "passive_interface", "passive_interface_exclude", ): commands.extend(self._render_list_param(key, w, h, opr=opr)) elif key == "areas": commands.extend(self._render_areas(key, w, h, opr=opr)) elif key == "timers": commands.extend(self._render_timers(key, w, h, opr=opr)) elif key == "distance": commands.extend(self._render_distance(key, w, h, opr=opr)) return commands def _render_dict_param(self, attr, want, have, opr=True): """ This function generate the commands for dictionary elements. :param attr: attribute name. :param w: the desired configuration. :param h: the target config. :param opr: True/False. :return: generated list of commands. """ - commands = [] h = {} if have: h = have.get(attr) or {} if not opr and not h: commands.append(self._form_attr_cmd(attr=attr, opr=opr)) elif want[attr]: leaf_dict = { "auto_cost": "reference_bandwidth", "mpls_te": ("enabled", "router_address"), "parameters": ( "router_id", "abr_type", "opaque_lsa", "rfc1583_compatibility", ), } leaf = leaf_dict[attr] for item, value in iteritems(want[attr]): if opr and item in leaf and not _is_w_same(want[attr], h, item): if item == "enabled": item = "enable" if item in ( "opaque_lsa", "enable", "rfc1583_compatibility", ): commands.append(self._form_attr_cmd(key=attr, attr=item, opr=opr)) else: commands.append( self._form_attr_cmd(key=attr, attr=item, val=value, opr=opr), ) elif not opr and item in leaf and not _in_target(h, item): if item == "enabled": commands.append(self._form_attr_cmd(key=attr, attr="enable", opr=opr)) else: commands.append(self._form_attr_cmd(key=attr, attr=item, opr=opr)) return commands def _render_list_param(self, attr, want, have, cmd=None, opr=True): """ This function forms the commands for passed target list attributes'. :param attr: attribute name. :param w: the desired config. :param h: the target config. :param cmd: commands to be prepend. :param opr: True/False. :return: generated list of commands. """ - commands = [] h = [] if want: w = want.get(attr) or [] if have: h = have.get(attr) or [] if not cmd: cmd = self._compute_command(opr=opr) if w: if opr: members = list_diff_want_only(w, h) for member in members: command = cmd + attr.replace("_", "-") + " " if attr == "network": command += member["address"] + elif attr == "passive_interface" and member != "default" and LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4"): + command = command.replace("passive-interface", "interface") + member + " passive" + elif attr == "passive_interface_exclude" and LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4"): + command = command.replace("passive-interface-exclude", "interface") + member + " passive disable" else: command += member commands.append(command) elif not opr: if h: for member in w: if attr == "network": if not self.search_obj_in_have(h, member, "address"): commands.append( cmd + attr.replace("_", "-") + " " + member["address"], ) elif member not in h: - commands.append(cmd + attr.replace("_", "-") + " " + member) + if attr == "passive_interface" and member != "default" and LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4"): + commands.append(cmd + "interface" + " " + member + " passive") + elif attr == "passive_interface_exclude" and LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4"): + command = command.replace("passive-interface-exclude", "interface") + member + " passive disable" + else: + commands.append(cmd + attr.replace("_", "-") + " " + member) else: commands.append(cmd + " " + attr.replace("_", "-")) return commands def _render_vlink(self, attr, want, have, cmd=None, opr=True): """ This function forms the set/delete commands based on the 'opr' type for attributes with in desired list of dictionary. :param attr: attribute name. :param w: the desired config. :param h: the target config. :param cmd: commands to be prepend. :param opr: True/False. :return: generated commands list. """ commands = [] h = [] name = {"virtual_link": "address"} leaf_dict = { "virtual_link": ( "address", "dead_interval", "transmit_delay", "hello_interval", "retransmit_interval", ), } leaf = leaf_dict[attr] w = want.get(attr) or [] if have: h = have.get(attr) or [] if not opr and not h: commands.append(cmd + attr.replace("_", "-")) elif w: for w_item in w: for key, val in iteritems(w_item): if not cmd: cmd = self._compute_command(opr=opr) h_item = self.search_obj_in_have(h, w_item, name[attr]) if opr and key in leaf and not _is_w_same(w_item, h_item, key): if key in "address": commands.append(cmd + attr.replace("_", "-") + " " + str(val)) else: commands.append( cmd + attr.replace("_", "-") + " " + w_item[name[attr]] + " " + key.replace("_", "-") + " " + str(val), ) elif not opr and key in leaf and not _in_target(h_item, key): if key in "address": commands.append(cmd + attr.replace("_", "-") + " " + str(val)) else: commands.append( cmd + attr.replace("_", "-") + " " + w_item[name[attr]] + " " + key, ) elif key == "authentication": commands.extend( self._render_vlink_auth( attr, key, w_item, h_item, w_item["address"], cmd, opr, ), ) return commands def _render_vlink_auth(self, attr, key, want, have, address, cmd=None, opr=True): """ This function forms the set/delete commands based on the 'opr' type for attributes with in desired list of dictionary. :param attr: attribute name. :param w: the desired config. :param h: the target config. :param cmd: commands to be prepend. :param opr: True/False. :return: generated commands list. """ commands = [] h = [] w = want.get(key) or {} if have: h = have.get(key) or {} cmd += attr.replace("_", "-") + " " + address + " " + key + " " commands.extend(self._render_list_dict_param("md5", w, h, cmd, opr)) return commands def _render_list_dict_param(self, attr, want, have, cmd=None, opr=True): """ This function forms the set/delete commands based on the 'opr' type for attributes with in desired list of dictionary. :param attr: attribute name. :param w: the desired config. :param h: the target config. :param cmd: commands to be prepend. :param opr: True/False. :return: generated commands list. """ commands = [] h = [] name = { "redistribute": "route_type", "neighbor": "neighbor_id", "range": "address", "md5": "key_id", "vlink": "address", } leaf_dict = { "md5": "md5_key", "redistribute": ( "metric", "route_map", "route_type", "metric_type", ), "neighbor": ("priority", "poll_interval", "neighbor_id"), "range": ("cost", "address", "substitute", "not_advertise"), "vlink": ( "address", "dead_interval", "transmit_delay", "hello_interval", "retransmit_interval", ), } leaf = leaf_dict[attr] w = want.get(attr) or [] if have: h = have.get(attr) or [] if not opr and not h: commands.append(self._compute_command(attr=attr, opr=opr)) elif w: for w_item in w: for key, val in iteritems(w_item): if not cmd: cmd = self._compute_command(opr=opr) h_item = self.search_obj_in_have(h, w_item, name[attr]) if opr and key in leaf and not _is_w_same(w_item, h_item, key): if key in ( "route_type", "neighbor_id", "address", "key_id", ): commands.append(cmd + attr + " " + str(val)) elif key == "cost": commands.append( cmd + attr + " " + w_item[name[attr]] + " " + key + " " + str(val), ) elif key == "not_advertise": commands.append( cmd + attr + " " + w_item[name[attr]] + " " + key.replace("_", "-"), ) elif key == "md5_key": commands.append( cmd + attr + " " + "key-id" + " " + str(w_item[name[attr]]) + " " + key.replace("_", "-") + " " + w_item[key], ) else: commands.append( cmd + attr + " " + w_item[name[attr]] + " " + key.replace("_", "-") + " " + str(val), ) elif not opr and key in leaf and not _in_target(h_item, key): if key in ( "route_type", "neighbor_id", "address", "key_id", ): commands.append(cmd + attr + " " + str(val)) else: commands.append(cmd + attr + " " + w_item[name[attr]] + " " + key) return commands def _render_nested_dict_param(self, attr, want, have, opr=True): """ This function forms the set/delete commands based on the 'opr' type for attributes with in desired nested dicts. :param attr: attribute name. :param w: the desired config. :param h: the target config. :param cmd: commands to be prepend. :param opr: True/False. :return: generated commands list. """ commands = [] attr_dict = { "default_information": "originate", "max_metric": "router_lsa", } leaf_dict = { "default_information": ( "always", "metric", "metric_type", "route_map", ), "max_metric": ("administrative", "on_startup", "on_shutdown"), } h = {} w = want.get(attr) or {} if have: h = have.get(attr) or {} if not opr and not h: commands.append(self._form_attr_cmd(attr=attr, opr=opr)) elif w: key = attr_dict[attr] w_attrib = want[attr].get(key) or {} cmd = self._compute_command(opr=opr) h_attrib = {} if w_attrib: leaf = leaf_dict[attr] if h and key in h.keys(): h_attrib = h.get(key) or {} for item, val in iteritems(w[key]): if opr and item in leaf and not _is_w_same(w[key], h_attrib, item): if item in ("administrative", "always") and val: commands.append( cmd + attr.replace("_", "-") + " " + key.replace("_", "-") + " " + item.replace("_", "-"), ) elif item not in ("administrative", "always"): commands.append( cmd + attr.replace("_", "-") + " " + key.replace("_", "-") + " " + item.replace("_", "-") + " " + str(val), ) elif not opr and item in leaf and not _in_target(h_attrib, item): commands.append(cmd + attr + " " + item) return commands def _render_areas(self, attr, want, have, opr=True): """ This function forms the set/delete commands based on the 'opr' type for ospf area attributes. :param attr: attribute name. :param w: the desired config. :param h: the target config. :param opr: True/False. :return: generated commands list. """ commands = [] h_lst = {} w_lst = want.get(attr) or [] l_set = ("area_id", "shortcut", "authentication") if have: h_lst = have.get(attr) or [] if not opr and not h_lst: commands.append(self._form_attr_cmd(attr="area", opr=opr)) elif w_lst: for w_area in w_lst: cmd = ( self._compute_command( key="area", attr=_bool_to_str(w_area["area_id"]), opr=opr, ) + " " ) h_area = self.search_obj_in_have(h_lst, w_area, "area_id") if not opr and not h_area: commands.append( self._form_attr_cmd(key="area", attr=w_area["area_id"], opr=opr), ) else: for key, val in iteritems(w_area): if opr and key in l_set and not _is_w_same(w_area, h_area, key): if key == "area_id": commands.append( self._form_attr_cmd( attr="area", val=_bool_to_str(val), opr=opr, ), ) else: commands.append( cmd + key + " " + _bool_to_str(val).replace("_", "-"), ) elif not opr and key in l_set: if key == "area_id" and not _in_target(h_area, key): commands.append(cmd) continue if key != "area_id" and not _in_target(h_area, key): commands.append(cmd + val + " " + key) elif key == "area_type": commands.extend(self._render_area_type(w_area, h_area, key, cmd, opr)) elif key == "network": commands.extend(self._render_list_param(key, w_area, h_area, cmd, opr)) elif key == "range": commands.extend( self._render_list_dict_param(key, w_area, h_area, cmd, opr), ) elif key == "virtual_link": commands.extend(self._render_vlink(key, w_area, h_area, cmd, opr)) return commands def _render_area_type(self, want, have, attr, cmd, opr=True): """ This function forms the set/delete commands based on the 'opr' type for area_types attributes. :param attr: attribute name. :param w: the desired config. :param h: the target config. :param cmd: command to prepend. :param opr: True/False. :return: generated commands list. """ commands = [] h_type = {} w_type = want.get(attr) or [] if have: h_type = have.get(attr) or {} if not opr and not h_type: commands.append(cmd + attr.replace("_", "-")) elif w_type: key = "normal" if opr and key in w_type.keys() and not _is_w_same(w_type, h_type, key): if not w_type[key] and h_type and h_type[key]: commands.append( cmd.replace("set", "delete") + attr.replace("_", "-") + " " + key, ) elif w_type[key]: commands.append(cmd + attr.replace("_", "-") + " " + key) elif not opr and key in w_type.keys() and not (h_type and key in h_type.keys()): commands.append(cmd + want["area"] + " " + attr.replace("_", "-")) a_type = { "nssa": ("set", "default_cost", "no_summary", "translate"), "stub": ("set", "default_cost", "no_summary"), } for key in a_type: w_area = want[attr].get(key) or {} h_area = {} if w_area: if h_type and key in h_type.keys(): h_area = h_type.get(key) or {} for item, val in iteritems(w_type[key]): if ( opr and item in a_type[key] and not _is_w_same(w_type[key], h_area, item) ): if item == "set" and val: commands.append(cmd + attr.replace("_", "-") + " " + key) elif not val and h_area and h_area[item]: commands.append( cmd.replace("set", "delete") + attr.replace("_", "-") + " " + key, ) elif item != "set": commands.append( cmd + attr.replace("_", "-") + " " + key + " " + item.replace("_", "-") + " " + str(val), ) elif not opr and item in a_type[key] and not (h_type and key in h_type): if item == "set": commands.append(cmd + attr.replace("_", "-") + " " + key) else: commands.append( cmd + want["area"] + " " + attr.replace("_", "-") + " " + key + " " + item.replace("_", "-"), ) return commands def _form_attr_cmd(self, key=None, attr=None, val=None, opr=True): """ This function forms the command for leaf attribute. :param key: parent key. :param attr: attribute name :param value: value :param opr: True/False. :return: generated command. """ return self._compute_command(key, attr=self._map_attrib(attr), val=val, opr=opr) def _compute_command(self, key=None, attr=None, val=None, remove=False, opr=True): """ This function construct the add/delete command based on passed attributes. :param key: parent key. :param attr: attribute name :param value: value :param opr: True/False. :return: generated command. """ if remove or not opr: cmd = "delete protocols ospf " else: cmd = "set protocols ospf " if key: cmd += key.replace("_", "-") + " " if attr: cmd += attr.replace("_", "-") if val: cmd += " '" + str(val) + "'" return cmd def _map_attrib(self, attrib): """ - This function construct the regex string. - replace the underscore with hyphen. :param attrib: attribute :return: regex string """ return "disable" if attrib == "disabled" else attrib.replace("_", "-") 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/facts/ospfv2/ospfv2.py b/plugins/module_utils/network/vyos/facts/ospfv2/ospfv2.py index bdc7c9f8..d07bf13a 100644 --- a/plugins/module_utils/network/vyos/facts/ospfv2/ospfv2.py +++ b/plugins/module_utils/network/vyos/facts/ospfv2/ospfv2.py @@ -1,482 +1,509 @@ # # -*- 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 ospfv2 fact class It is in this file the configuration is collected from the device for a given resource, parsed, and the facts tree is populated based on the configuration. """ from __future__ import absolute_import, division, print_function __metaclass__ = type from copy import deepcopy from re import M, findall, search from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.argspec.ospfv2.ospfv2 import ( Ospfv2Args, ) +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 class Ospfv2Facts(object): """The vyos ospfv2 fact class""" def __init__( self, module, subspec="config", options="options", ): self._module = module self.argument_spec = Ospfv2Args.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 get_device_data(self, connection): return connection.get_config() def populate_facts(self, connection, ansible_facts, data=None): """Populate the facts for ospfv2 :param connection: the device connection :param ansible_facts: Facts dictionary :param data: previously collected conf :rtype: dictionary :returns: facts """ if not data: data = self.get_device_data(connection) # typically data is populated from the current device configuration # data = connection.get('show running-config | section ^interface') # using mock data instead objs = {} ospfv2 = findall(r"^set protocols ospf (.+)", data, M) if ospfv2: objs = self.render_config(ospfv2) facts = {} params = utils.validate_config(self.argument_spec, {"config": objs}) facts["ospfv2"] = utils.remove_empties(params["config"]) ansible_facts["ansible_network_resources"].update(facts) return ansible_facts def render_config(self, conf): """ Render config as dictionary structure :param conf: The configuration :returns: The generated config """ conf = "\n".join(filter(lambda x: x, conf)) a_lst = ["default_metric", "log_adjacency_changes"] config = self.parse_attr(conf, a_lst) if not config: config = {} config["timers"] = self.parse_timers(conf) config["auto_cost"] = self.parse_auto_cost(conf) config["distance"] = self.parse_distance(conf) config["max_metric"] = self.parse_max_metric(conf) config["default_information"] = self.parse_def_info(conf) config["route_map"] = self.parse_leaf_list(conf, "route-map") config["mpls_te"] = self.parse_attrib(conf, "mpls_te", "mpls-te") config["areas"] = self.parse_attrib_list(conf, "area", "area_id") config["parameters"] = self.parse_attrib(conf, "parameters", "parameters") config["neighbor"] = self.parse_attrib_list(conf, "neighbor", "neighbor_id") - config["passive_interface"] = self.parse_leaf_list(conf, "passive-interface") + config["passive_interface"] = self.parse_passive(conf, "passive-interface") config["redistribute"] = self.parse_attrib_list(conf, "redistribute", "route_type") - config["passive_interface_exclude"] = self.parse_leaf_list( + config["passive_interface_exclude"] = self.parse_passive( conf, "passive-interface-exclude", ) return config def parse_timers(self, conf): """ This function triggers the parsing of 'timers' attributes :param conf: configuration :return: generated config dictionary """ cfg_dict = {} cfg_dict["refresh"] = self.parse_refresh(conf, "refresh") cfg_dict["throttle"] = self.parse_throttle(conf, "spf") return cfg_dict def parse_throttle(self, conf, attrib=None): """ This function triggers the parsing of 'throttle' attributes :param conf: configuration :param attrib: 'spf' :return: generated config dictionary """ cfg_dict = {} cfg_dict[attrib] = self.parse_attrib(conf, attrib, match=attrib) return cfg_dict def parse_refresh(self, conf, attrib=None): """ This function triggers the parsing of 'refresh' attributes :param conf: configuration :param attrib: 'refresh' :return: generated config dictionary """ cfg_dict = self.parse_attr(conf, ["timers"], match=attrib) return cfg_dict def parse_leaf_list(self, conf, attrib): """ This function forms the regex to fetch the listed attributes from the configuration data :param conf: configuration data :param attrib: attribute name :return: generated rule list configuration """ lst = [] items = findall(r"^" + attrib + " (?:'*)(\\S+)(?:'*)", conf, M) if items: for i in set(items): lst.append(i.strip("'")) lst.sort() return lst + def parse_passive(self, conf, attrib): + """ + This function forms the regex to fetch the listed attributes + from the configuration data + :param conf: configuration data + :param attrib: attribute name + :return: generated rule list configuration + """ + lst = [] + items = [] + if LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4"): + if attrib == "passive-interface-exclude": + items = findall("^interface (?:'*)(\\S+)(?:'*) passive disable$", conf, M) + else: + items = findall("^interface (?:'*)(\\S+)(?:'*) passive$", conf, M) + + items += findall(r"^" + attrib + " (?:'*)(\\S+)(?:'*)", conf, M) + if items: + for i in set(items): + lst.append(i.strip("'")) + lst.sort() + return lst + def parse_distance(self, conf, attrib=None): """ This function triggers the parsing of 'distance' attributes :param conf: configuration :param attrib: attribute name :return: generated config dictionary """ cfg_dict = self.parse_attr(conf, ["global"], match=attrib) cfg_dict["ospf"] = self.parse_ospf(conf, "ospf") return cfg_dict def parse_ospf(self, conf, attrib=None): """ This function triggers the parsing of 'distance ospf' attributes :param conf: configuration :param attrib: 'ospf' :return: generated config dictionary """ cfg_dict = self.parse_attrib(conf, "ospf", match=attrib) return cfg_dict def parse_max_metric(self, conf): """ This function triggers the parsing of 'max_metric' attributes :param conf: configuration :return: generated config dictionary """ cfg_dict = {} cfg_dict["router_lsa"] = self.parse_attrib(conf, "router_lsa", match="router-lsa") return cfg_dict def parse_auto_cost(self, conf, attrib=None): """ This function triggers the parsing of 'auto_cost' attributes :param conf: configuration :param attrib: attribute name :return: generated config dictionary """ cfg_dict = self.parse_attr(conf, ["reference_bandwidth"], match=attrib) return cfg_dict def parse_def_info(self, conf): """ This function triggers the parsing of 'default_information' attributes :param conf: configuration :return: generated config dictionary """ cfg_dict = {} cfg_dict["originate"] = self.parse_attrib(conf, "originate", "originate") return cfg_dict def parse_area(self, conf, area_id): """ This function triggers the parsing of 'area' attributes. :param conf: configuration data :param area_id: area identity :return: generated rule configuration dictionary. """ rule = self.parse_attrib(conf, "area_id", match=area_id) r_sub = { "area_type": self.parse_area_type(conf, "area-type"), "network": self.parse_network(conf), "range": self.parse_attrib_list(conf, "range", "address"), "virtual_link": self.parse_attrib_list(conf, "virtual-link", "address"), } rule.update(r_sub) return rule def parse_key(self, conf, key_id): """ This function triggers the parsing of 'area' attributes. :param conf: configuration data :param area_id: area identity :return: generated rule configuration dictionary. """ rule = self.parse_attrib(conf, "key_id", match=key_id) return rule def parse_area_type(self, conf, attrib=None): """ This function triggers the parsing of 'area_type' attributes :param conf: configuration :param attrib: 'area-type' :return: generated config dictionary """ cfg_dict = self.parse_attr(conf, ["normal"], match=attrib) cfg_dict["nssa"] = self.parse_attrib(conf, "nssa", match="nssa") cfg_dict["stub"] = self.parse_attrib(conf, "stub", match="stub") return cfg_dict def parse_network(self, conf): """ This function forms the regex to fetch the 'network' :param conf: configuration data :return: generated rule list configuration """ a_lst = [] applications = findall(r"network (.+)", conf, M) if applications: app_lst = [] for r in set(applications): obj = {"address": r.strip("'")} app_lst.append(obj) a_lst = sorted(app_lst, key=lambda i: i["address"]) return a_lst def parse_vlink(self, conf): """ This function triggers the parsing of 'virtual_link' attributes :param conf: configuration data :return: generated rule configuration dictionary """ rule = self.parse_attrib(conf, "vlink") r_sub = {"authentication": self.parse_authentication(conf, "authentication")} rule.update(r_sub) return rule def parse_authentication(self, conf, attrib=None): """ This function triggers the parsing of 'authentication' attributes. :param conf: configuration :param attrib: 'authentication' :return: generated config dictionary """ cfg_dict = self.parse_attr(conf, ["plaintext_password"], match=attrib) cfg_dict["md5"] = self.parse_attrib_list(conf, "key-id", "key_id") return cfg_dict def parse_attrib_list(self, conf, attrib, param): """ This function forms the regex to fetch the listed attributes from config :param conf: configuration data :param attrib: attribute name :param param: parameter data :return: generated rule list configuration """ r_lst = [] if attrib == "area": items = findall( r"^" + attrib.replace("_", "-") + " (?:'*)(\\S+)(?:'*)", conf, M, ) elif attrib == "key-id": items = findall( r"^.*" + attrib.replace("_", "-") + " (?:'*)(\\S+)(?:'*)", conf, M, ) else: items = findall(r"" + attrib + " (?:'*)(\\S+)(?:'*)", conf, M) if items: a_lst = [] for item in set(items): i_regex = r" %s .+$" % item cfg = "\n".join(findall(i_regex, conf, M)) if attrib == "area": obj = self.parse_area(cfg, item) elif attrib == "virtual-link": obj = self.parse_vlink(cfg) elif attrib == "key-id": obj = self.parse_key(cfg, item) else: obj = self.parse_attrib(cfg, attrib) obj[param] = item.strip("'") if obj: a_lst.append(obj) r_lst = sorted(a_lst, key=lambda i: i[param]) return r_lst def parse_attrib(self, conf, param, match=None): """ This function triggers the parsing of 'ospf' attributes :param conf: configuration data :return: generated configuration dictionary """ param_lst = { "key_id": ["md5_key"], "mpls_te": ["enabled", "router_address"], "area_id": ["shortcut", "authentication"], "neighbor": ["priority", "poll_interval"], "stub": ["set", "default_cost", "no_summary"], "range": ["cost", "substitute", "not_advertise"], "ospf": ["external", "inter_area", "intra_area"], "spf": ["delay", "max_holdtime", "initial_holdtime"], "redistribute": ["metric", "metric_type", "route_map"], "nssa": ["set", "translate", "default_cost", "no_summary"], "config_routes": ["default_metric", "log_adjacency_changes"], "originate": ["always", "metric", "metric_type", "route_map"], "router_lsa": ["administrative", "on_shutdown", "on_startup"], "parameters": [ "abr_type", "opaque_lsa", "router_id", "rfc1583_compatibility", ], "vlink": [ "dead_interval", "hello_interval", "transmit_delay", "retransmit_interval", ], } cfg_dict = self.parse_attr(conf, param_lst[param], match) return cfg_dict def parse_attr(self, conf, attr_list, match=None): """ This function peforms the following: - Form the regex to fetch the required attribute config. - Type cast the output in desired format. :param conf: configuration. :param attr_list: list of attributes. :param match: parent node/attribute name. :return: generated config dictionary. """ - config = {} for attrib in attr_list: regex = self.map_regex(attrib) if match: regex = match.replace("_", "-") + " " + regex + if conf: if self.is_bool(attrib): out = conf.find(attrib.replace("_", "-")) dis = conf.find(attrib.replace("_", "-") + " 'disable'") if match: if attrib == "set" and conf.find(match) >= 1: config[attrib] = True - en = conf.find(match + " 'enable'") + en = conf.find(match + " enable") != -1 if out >= 1: if dis >= 1: config[attrib] = False else: config[attrib] = True - elif match and en >= 1: + elif match and en: config[attrib] = True else: out = search(r"^.*" + regex + " (.+)", conf, M) if out: val = out.group(1).strip("'") if self.is_num(attrib): val = int(val) config[attrib] = val return config def map_regex(self, attrib): """ - This function construct the regex string. - replace the underscore with hyphen. :param attrib: attribute :return: regex string """ return ( "disable" if attrib == "disabled" else ( "enable" if attrib == "enabled" else ("area" if attrib == "area_id" else attrib.replace("_", "-")) ) ) def is_bool(self, attrib): """ This function looks for the attribute in predefined bool type set. :param attrib: attribute. :return: True/False """ bool_set = ( "set", "always", "normal", "enabled", "opaque_lsa", "not_advertise", "administrative", "rfc1583_compatibility", ) return True if attrib in bool_set else False def is_num(self, attrib): """ This function looks for the attribute in predefined integer type set. :param attrib: attribute. :return: True/false. """ num_set = ( "ospf", "delay", "metric", "inter_area", "intra_area", "on_startup", "metric_type", "on_shutdown", "max_holdtime", "poll_interval", "default_metric", "initial_holdtime", "key_id", ) return True if attrib in num_set else False 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..4c371962 100644 --- a/plugins/module_utils/network/vyos/utils/utils.py +++ b/plugins/module_utils/network/vyos/utils/utils.py @@ -1,266 +1,288 @@ # -*- 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 + + +def in_target_not_none(h, key): + """ + This function checks whether the target exist,key present in target config, and the value is not None. + :param h: target config. + :param key: attribute name. + :return: True/False. + """ + return True if h and key in h and h[key] is not None 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/integration/targets/vyos_ospfv2/tasks/main.yaml b/tests/integration/targets/vyos_ospfv2/tasks/main.yaml index 9a3359ed..53afd6c2 100644 --- a/tests/integration/targets/vyos_ospfv2/tasks/main.yaml +++ b/tests/integration/targets/vyos_ospfv2/tasks/main.yaml @@ -1,11 +1,19 @@ --- +- name: Run preflight setup + ansible.builtin.import_tasks: pre_tasks.yaml + failed_when: false + - name: Run CLI tests ansible.builtin.include_tasks: cli.yaml tags: - network_cli - name: Run redirection CLI tests ansible.builtin.include_tasks: redirection.yaml when: ansible_version.full is version('2.10.0', '>=') tags: - network_cli + +- name: Run post-test cleanup tasks + ansible.builtin.import_tasks: post_tasks.yaml + failed_when: false diff --git a/tests/integration/targets/vyos_ospf_interfaces/tasks/post_tasks.yml b/tests/integration/targets/vyos_ospfv2/tasks/post_tasks.yaml similarity index 56% copy from tests/integration/targets/vyos_ospf_interfaces/tasks/post_tasks.yml copy to tests/integration/targets/vyos_ospfv2/tasks/post_tasks.yaml index 0883ef48..e172db2d 100644 --- a/tests/integration/targets/vyos_ospf_interfaces/tasks/post_tasks.yml +++ b/tests/integration/targets/vyos_ospfv2/tasks/post_tasks.yaml @@ -1,7 +1,7 @@ --- -- name: Demolish environment +- name: Remove pre-requisite configuration vyos.vyos.vyos_config: lines: |- - delete interfaces bonding bond2 + delete policy route-map ingress vars: ansible_connection: ansible.netcommon.network_cli diff --git a/tests/integration/targets/vyos_ospf_interfaces/tasks/post_tasks.yml b/tests/integration/targets/vyos_ospfv2/tasks/pre_tasks.yaml similarity index 58% copy from tests/integration/targets/vyos_ospf_interfaces/tasks/post_tasks.yml copy to tests/integration/targets/vyos_ospfv2/tasks/pre_tasks.yaml index 0883ef48..bdca8772 100644 --- a/tests/integration/targets/vyos_ospf_interfaces/tasks/post_tasks.yml +++ b/tests/integration/targets/vyos_ospfv2/tasks/pre_tasks.yaml @@ -1,7 +1,7 @@ --- -- name: Demolish environment +- name: Add pre-requisite configuration vyos.vyos.vyos_config: lines: |- - delete interfaces bonding bond2 + set policy route-map ingress vars: ansible_connection: ansible.netcommon.network_cli diff --git a/tests/integration/targets/vyos_ospfv2/tests/cli/_get_version.yaml b/tests/integration/targets/vyos_ospfv2/tests/cli/_get_version.yaml new file mode 100644 index 00000000..50b0ec61 --- /dev/null +++ b/tests/integration/targets/vyos_ospfv2/tests/cli/_get_version.yaml @@ -0,0 +1,30 @@ +- name: make sure to get facts + vyos.vyos.vyos_facts: + vars: + ansible_connection: ansible.netcommon.network_cli + register: vyos_facts + when: vyos_version is not defined + +- name: debug vyos_facts + debug: + var: vyos_facts + +- name: pull version from facts + set_fact: + vyos_version: "{{ vyos_facts.ansible_facts.ansible_net_version.split('-')[0].split(' ')[-1] }}" + when: vyos_version is not defined + +- name: fix '.0' versions + set_fact: + vyos_version: "{{ vyos_version }}.0" + when: vyos_version.count('.') == 1 + +- name: include correct vars + include_vars: pre-v1_4.yaml + when: vyos_version is version('1.4.0', '<', version_type='semver') + +- name: include correct vars + include_vars: v1_4.yaml + when: vyos_version is version('1.4.0', '>=', version_type='semver') +# - name: include common vars +# include_vars: main.yaml diff --git a/tests/integration/targets/vyos_ospfv2/tests/cli/_parsed_config.cfg b/tests/integration/targets/vyos_ospfv2/tests/cli/_parsed_config_1_3.cfg similarity index 94% copy from tests/integration/targets/vyos_ospfv2/tests/cli/_parsed_config.cfg copy to tests/integration/targets/vyos_ospfv2/tests/cli/_parsed_config_1_3.cfg index 9cc720b4..0d8100d8 100644 --- a/tests/integration/targets/vyos_ospfv2/tests/cli/_parsed_config.cfg +++ b/tests/integration/targets/vyos_ospfv2/tests/cli/_parsed_config_1_3.cfg @@ -1,29 +1,29 @@ set protocols ospf area 2 area-type 'normal' set protocols ospf area 2 authentication 'plaintext-password' -set protocols ospf area 2 shortcut 'enable' +set protocols ospf area 2 shortcut enable set protocols ospf area 3 area-type 'nssa' set protocols ospf area 4 area-type stub default-cost '20' set protocols ospf area 4 network '192.0.2.0/24' set protocols ospf area 4 range 192.0.3.0/24 cost '10' set protocols ospf area 4 range 192.0.4.0/24 cost '12' set protocols ospf auto-cost reference-bandwidth '2' set protocols ospf default-information originate 'always' set protocols ospf default-information originate metric '10' set protocols ospf default-information originate metric-type '2' set protocols ospf default-information originate route-map 'ingress' set protocols ospf log-adjacency-changes 'detail' set protocols ospf max-metric router-lsa 'administrative' set protocols ospf max-metric router-lsa on-shutdown '10' set protocols ospf max-metric router-lsa on-startup '10' -set protocols ospf mpls-te 'enable' +set protocols ospf mpls-te enable set protocols ospf mpls-te router-address '192.0.11.11' set protocols ospf neighbor 192.0.11.12 poll-interval '10' set protocols ospf neighbor 192.0.11.12 priority '2' set protocols ospf parameters abr-type 'cisco' set protocols ospf parameters 'opaque-lsa' set protocols ospf parameters 'rfc1583-compatibility' set protocols ospf parameters router-id '192.0.1.1' set protocols ospf passive-interface 'eth1' set protocols ospf passive-interface 'eth2' set protocols ospf redistribute bgp metric '10' set protocols ospf redistribute bgp metric-type '2' diff --git a/tests/integration/targets/vyos_ospfv2/tests/cli/_parsed_config.cfg b/tests/integration/targets/vyos_ospfv2/tests/cli/_parsed_config_1_4.cfg similarity index 89% rename from tests/integration/targets/vyos_ospfv2/tests/cli/_parsed_config.cfg rename to tests/integration/targets/vyos_ospfv2/tests/cli/_parsed_config_1_4.cfg index 9cc720b4..8e24ab69 100644 --- a/tests/integration/targets/vyos_ospfv2/tests/cli/_parsed_config.cfg +++ b/tests/integration/targets/vyos_ospfv2/tests/cli/_parsed_config_1_4.cfg @@ -1,29 +1,29 @@ set protocols ospf area 2 area-type 'normal' set protocols ospf area 2 authentication 'plaintext-password' -set protocols ospf area 2 shortcut 'enable' +set protocols ospf area 2 shortcut enable set protocols ospf area 3 area-type 'nssa' set protocols ospf area 4 area-type stub default-cost '20' set protocols ospf area 4 network '192.0.2.0/24' set protocols ospf area 4 range 192.0.3.0/24 cost '10' set protocols ospf area 4 range 192.0.4.0/24 cost '12' set protocols ospf auto-cost reference-bandwidth '2' set protocols ospf default-information originate 'always' set protocols ospf default-information originate metric '10' set protocols ospf default-information originate metric-type '2' set protocols ospf default-information originate route-map 'ingress' set protocols ospf log-adjacency-changes 'detail' set protocols ospf max-metric router-lsa 'administrative' set protocols ospf max-metric router-lsa on-shutdown '10' set protocols ospf max-metric router-lsa on-startup '10' -set protocols ospf mpls-te 'enable' +set protocols ospf mpls-te enable set protocols ospf mpls-te router-address '192.0.11.11' set protocols ospf neighbor 192.0.11.12 poll-interval '10' set protocols ospf neighbor 192.0.11.12 priority '2' set protocols ospf parameters abr-type 'cisco' set protocols ospf parameters 'opaque-lsa' set protocols ospf parameters 'rfc1583-compatibility' set protocols ospf parameters router-id '192.0.1.1' -set protocols ospf passive-interface 'eth1' -set protocols ospf passive-interface 'eth2' +set protocols ospf interface 'eth1' passive +set protocols ospf interface 'eth2' passive set protocols ospf redistribute bgp metric '10' set protocols ospf redistribute bgp metric-type '2' diff --git a/tests/integration/targets/vyos_ospfv2/tests/cli/_populate.yaml b/tests/integration/targets/vyos_ospfv2/tests/cli/_populate.yaml index 872d3302..748dca70 100644 --- a/tests/integration/targets/vyos_ospfv2/tests/cli/_populate.yaml +++ b/tests/integration/targets/vyos_ospfv2/tests/cli/_populate.yaml @@ -1,37 +1,11 @@ --- - ansible.builtin.include_tasks: _remove_config.yaml +- name: ensure facts + include_tasks: _get_version.yaml + - name: Setup + vyos.vyos.vyos_config: + lines: "{{ populate_commands }}" vars: - lines: >- - "set protocols ospf mpls-te 'enable' - \n set protocols ospf mpls-te router-address '192.0.11.11' - \n set protocols ospf redistribute bgp metric-type '2' - \n set protocols ospf redistribute bgp metric '10' - \n set protocols ospf default-information originate metric-type '2' - \n set protocols ospf default-information originate 'always' - \n set protocols ospf default-information originate metric '10' - \n set protocols ospf default-information originate route-map 'ingress' - \n set protocols ospf auto-cost reference-bandwidth '2' - \n set protocols ospf parameters router-id '192.0.1.1' - \n set protocols ospf parameters 'opaque-lsa' - \n set protocols ospf parameters abr-type 'cisco' - \n set protocols ospf parameters 'rfc1583-compatibility' - \n set protocols ospf passive-interface 'eth1' - \n set protocols ospf passive-interface 'eth2' - \n set protocols ospf max-metric router-lsa on-shutdown '10' - \n set protocols ospf max-metric router-lsa 'administrative' - \n set protocols ospf max-metric router-lsa on-startup '10' - \n set protocols ospf log-adjacency-changes 'detail' - \n set protocols ospf neighbor 192.0.11.12 priority '2' - \n set protocols ospf neighbor 192.0.11.12 poll-interval '10' - \n set protocols ospf area 2 authentication 'plaintext-password' - \n set protocols ospf area 2 shortcut 'enable' - \n set protocols ospf area 2 area-type 'normal' - \n set protocols ospf area 3 area-type 'nssa' - \n set protocols ospf area 4 range 192.0.3.0/24 cost '10' - \n set protocols ospf area 4 range 192.0.4.0/24 cost '12' - \n set protocols ospf area 4 area-type stub default-cost '20' - \n set protocols ospf area 4 network '192.0.2.0/24'" - ansible.netcommon.cli_config: - config: "{{ lines }}" + ansible_connection: ansible.netcommon.network_cli diff --git a/tests/integration/targets/vyos_ospfv2/tests/cli/parsed.yaml b/tests/integration/targets/vyos_ospfv2/tests/cli/parsed.yaml index ad5b005d..4b6e0c5c 100644 --- a/tests/integration/targets/vyos_ospfv2/tests/cli/parsed.yaml +++ b/tests/integration/targets/vyos_ospfv2/tests/cli/parsed.yaml @@ -1,14 +1,20 @@ --- - debug: msg: START vyos_ospfv2 parsed integration tests on connection={{ ansible_connection }} +- name: ensure facts + include_tasks: _get_version.yaml + - name: Parse externally provided ospfv2 config to agnostic model register: result vyos.vyos.vyos_ospfv2: - running_config: "{{ lookup('file', '_parsed_config.cfg') }}" + running_config: "{{ lookup('file', parsed_config_file) }}" state: parsed +- debug: + msg: "{{ parsed['after'] }}" + - name: Assert that config was correctly parsed assert: that: - "{{ parsed['after'] == result['parsed'] }}" diff --git a/tests/integration/targets/vyos_ospfv2/vars/main.yaml b/tests/integration/targets/vyos_ospfv2/vars/main.yaml index 70d25fc7..1f1b9ba8 100644 --- a/tests/integration/targets/vyos_ospfv2/vars/main.yaml +++ b/tests/integration/targets/vyos_ospfv2/vars/main.yaml @@ -1,485 +1,408 @@ --- merged: before: {} - commands: - - set protocols ospf mpls-te enable - - set protocols ospf mpls-te router-address '192.0.11.11' - - set protocols ospf redistribute bgp - - set protocols ospf redistribute bgp metric-type 2 - - set protocols ospf redistribute bgp metric 10 - - set protocols ospf default-information originate metric-type 2 - - set protocols ospf default-information originate always - - set protocols ospf default-information originate metric 10 - - set protocols ospf default-information originate route-map ingress - - set protocols ospf auto-cost reference-bandwidth '2' - - set protocols ospf parameters router-id '192.0.1.1' - - set protocols ospf parameters opaque-lsa - - set protocols ospf parameters abr-type 'cisco' - - set protocols ospf parameters rfc1583-compatibility - - set protocols ospf passive-interface eth1 - - set protocols ospf passive-interface eth2 - - set protocols ospf max-metric router-lsa on-shutdown 10 - - set protocols ospf max-metric router-lsa administrative - - set protocols ospf max-metric router-lsa on-startup 10 - - set protocols ospf log-adjacency-changes 'detail' - - set protocols ospf neighbor 192.0.11.12 priority 2 - - set protocols ospf neighbor 192.0.11.12 poll-interval 10 - - set protocols ospf neighbor 192.0.11.12 - - set protocols ospf area '2' - - set protocols ospf area 2 authentication plaintext-password - - set protocols ospf area 2 shortcut enable - - set protocols ospf area 2 area-type normal - - set protocols ospf area '3' - - set protocols ospf area 3 area-type nssa - - set protocols ospf area 4 range 192.0.3.0/24 cost 10 - - set protocols ospf area 4 range 192.0.3.0/24 - - set protocols ospf area 4 range 192.0.4.0/24 cost 12 - - set protocols ospf area 4 range 192.0.4.0/24 - - set protocols ospf area 4 area-type stub default-cost 20 - - set protocols ospf area '4' - - set protocols ospf area 4 network 192.0.2.0/24 + commands: "{{ merged_commands }}" after: areas: - area_id: "2" area_type: normal: true authentication: plaintext-password shortcut: enable - area_id: "3" area_type: nssa: set: true - area_id: "4" area_type: stub: default_cost: 20 set: true network: - address: 192.0.2.0/24 range: - address: 192.0.3.0/24 cost: 10 - address: 192.0.4.0/24 cost: 12 auto_cost: reference_bandwidth: 2 default_information: originate: always: true metric: 10 metric_type: 2 route_map: ingress log_adjacency_changes: detail max_metric: router_lsa: administrative: true on_shutdown: 10 on_startup: 10 mpls_te: enabled: true router_address: 192.0.11.11 neighbor: - neighbor_id: 192.0.11.12 poll_interval: 10 priority: 2 parameters: abr_type: cisco opaque_lsa: true rfc1583_compatibility: true router_id: 192.0.1.1 passive_interface: - eth1 - eth2 redistribute: - metric: 10 metric_type: 2 route_type: bgp + merged_update: before: areas: - area_id: "2" area_type: normal: true authentication: plaintext-password shortcut: enable - area_id: "3" area_type: nssa: set: true - area_id: "4" area_type: stub: default_cost: 20 set: true network: - address: 192.0.2.0/24 range: - address: 192.0.3.0/24 cost: 10 - address: 192.0.4.0/24 cost: 12 auto_cost: reference_bandwidth: 2 default_information: originate: always: true metric: 10 metric_type: 2 route_map: ingress log_adjacency_changes: detail max_metric: router_lsa: administrative: true on_shutdown: 10 on_startup: 10 mpls_te: enabled: true router_address: 192.0.11.11 neighbor: - neighbor_id: 192.0.11.12 poll_interval: 10 priority: 2 parameters: abr_type: cisco opaque_lsa: true rfc1583_compatibility: true router_id: 192.0.1.1 passive_interface: - eth1 - eth2 redistribute: - metric: 10 metric_type: 2 route_type: bgp after: areas: - area_id: "2" area_type: normal: true authentication: plaintext-password shortcut: enable - area_id: "3" area_type: nssa: set: true - area_id: "4" network: - address: 192.0.2.0/24 - address: 192.0.22.0/24 - address: 192.0.32.0/24 range: - address: 192.0.3.0/24 cost: 10 - address: 192.0.4.0/24 cost: 12 auto_cost: reference_bandwidth: 2 default_information: originate: always: true metric: 10 metric_type: 2 route_map: ingress log_adjacency_changes: detail max_metric: router_lsa: administrative: true on_shutdown: 10 on_startup: 10 mpls_te: enabled: true router_address: 192.0.11.11 neighbor: - neighbor_id: 192.0.11.12 poll_interval: 10 priority: 2 parameters: abr_type: cisco opaque_lsa: true rfc1583_compatibility: true router_id: 192.0.1.1 passive_interface: - eth1 - eth2 redistribute: - metric: 10 metric_type: 2 route_type: bgp commands: - delete protocols ospf area 4 area-type stub - set protocols ospf area 4 network 192.0.22.0/24 - set protocols ospf area 4 network 192.0.32.0/24 + populate: areas: - area_id: "2" area_type: normal: true authentication: plaintext-password shortcut: enable - area_id: "3" area_type: nssa: set: true - area_id: "4" area_type: stub: default_cost: 20 set: true network: - address: 192.0.2.0/24 range: - address: 192.0.3.0/24 cost: 10 - address: 192.0.4.0/24 cost: 12 auto_cost: reference_bandwidth: 2 default_information: originate: always: true metric: 10 metric_type: 2 route_map: ingress log_adjacency_changes: detail max_metric: router_lsa: administrative: true on_shutdown: 10 on_startup: 10 mpls_te: enabled: true router_address: 192.0.11.11 neighbor: - neighbor_id: 192.0.11.12 poll_interval: 10 priority: 2 parameters: abr_type: cisco opaque_lsa: true rfc1583_compatibility: true router_id: 192.0.1.1 passive_interface: - eth1 - eth2 redistribute: - metric: 10 metric_type: 2 route_type: bgp + replaced: - commands: - - delete protocols ospf passive-interface eth2 - - delete protocols ospf area 3 - - delete protocols ospf area 4 range 192.0.3.0/24 cost - - delete protocols ospf area 4 range 192.0.3.0/24 - - delete protocols ospf area 4 range 192.0.4.0/24 cost - - delete protocols ospf area 4 range 192.0.4.0/24 - - set protocols ospf mpls-te router-address '192.0.22.22' - - set protocols ospf area 4 range 1.1.2.0/24 cost 10 - - set protocols ospf area 4 range 1.1.2.0/24 - - set protocols ospf area 4 network 192.0.12.0/24 - - set protocols ospf area 4 network 192.0.22.0/24 - - set protocols ospf area 4 network 192.0.32.0/24 + commands: "{{ replaced_commands }}" after: areas: - area_id: "2" area_type: normal: true authentication: plaintext-password shortcut: enable - area_id: "4" area_type: stub: default_cost: 20 set: true network: - address: 192.0.12.0/24 - address: 192.0.2.0/24 - address: 192.0.22.0/24 - address: 192.0.32.0/24 range: - address: 1.1.2.0/24 cost: 10 auto_cost: reference_bandwidth: 2 default_information: originate: always: true metric: 10 metric_type: 2 route_map: ingress log_adjacency_changes: detail max_metric: router_lsa: administrative: true on_shutdown: 10 on_startup: 10 mpls_te: enabled: true router_address: 192.0.22.22 neighbor: - neighbor_id: 192.0.11.12 poll_interval: 10 priority: 2 parameters: abr_type: cisco opaque_lsa: true rfc1583_compatibility: true router_id: 192.0.1.1 passive_interface: - eth1 redistribute: - metric: 10 metric_type: 2 route_type: bgp + rendered: - commands: - - set protocols ospf mpls-te enable - - set protocols ospf mpls-te router-address '192.0.11.11' - - set protocols ospf redistribute bgp - - set protocols ospf redistribute bgp metric-type 2 - - set protocols ospf redistribute bgp metric 10 - - set protocols ospf default-information originate metric-type 2 - - set protocols ospf default-information originate always - - set protocols ospf default-information originate metric 10 - - set protocols ospf default-information originate route-map ingress - - set protocols ospf auto-cost reference-bandwidth '2' - - set protocols ospf parameters router-id '192.0.1.1' - - set protocols ospf parameters opaque-lsa - - set protocols ospf parameters abr-type 'cisco' - - set protocols ospf parameters rfc1583-compatibility - - set protocols ospf passive-interface eth1 - - set protocols ospf passive-interface eth2 - - set protocols ospf max-metric router-lsa on-shutdown 10 - - set protocols ospf max-metric router-lsa administrative - - set protocols ospf max-metric router-lsa on-startup 10 - - set protocols ospf log-adjacency-changes 'detail' - - set protocols ospf neighbor 192.0.11.12 priority 2 - - set protocols ospf neighbor 192.0.11.12 poll-interval 10 - - set protocols ospf neighbor 192.0.11.12 - - set protocols ospf area '2' - - set protocols ospf area 2 authentication plaintext-password - - set protocols ospf area 2 shortcut enable - - set protocols ospf area 2 area-type normal - - set protocols ospf area '3' - - set protocols ospf area 3 area-type nssa - - set protocols ospf area 4 range 192.0.3.0/24 cost 10 - - set protocols ospf area 4 range 192.0.3.0/24 - - set protocols ospf area 4 range 192.0.4.0/24 cost 12 - - set protocols ospf area 4 range 192.0.4.0/24 - - set protocols ospf area 4 area-type stub default-cost 20 - - set protocols ospf area '4' - - set protocols ospf area 4 network 192.0.2.0/24 + commands: "{{ rendered_commands }}" + parsed: after: areas: - area_id: "2" area_type: normal: true authentication: plaintext-password shortcut: enable - area_id: "3" area_type: nssa: set: true - area_id: "4" area_type: stub: default_cost: 20 set: true network: - address: 192.0.2.0/24 range: - address: 192.0.3.0/24 cost: 10 - address: 192.0.4.0/24 cost: 12 auto_cost: reference_bandwidth: 2 default_information: originate: always: true metric: 10 metric_type: 2 route_map: ingress log_adjacency_changes: detail max_metric: router_lsa: administrative: true on_shutdown: 10 on_startup: 10 mpls_te: enabled: true router_address: 192.0.11.11 neighbor: - neighbor_id: 192.0.11.12 poll_interval: 10 priority: 2 parameters: abr_type: cisco opaque_lsa: true rfc1583_compatibility: true router_id: 192.0.1.1 passive_interface: - eth1 - eth2 redistribute: - metric: 10 metric_type: 2 route_type: bgp + deleted: commands: - delete protocols ospf after: {} + round_trip: after: areas: - area_id: "2" area_type: normal: true authentication: plaintext-password shortcut: enable - area_id: "4" area_type: stub: default_cost: 20 set: true network: - address: 192.0.12.0/24 - address: 192.0.2.0/24 - address: 192.0.22.0/24 - address: 192.0.32.0/24 range: - address: 1.1.2.0/24 cost: 10 auto_cost: reference_bandwidth: 2 default_information: originate: always: true metric: 10 metric_type: 2 route_map: ingress log_adjacency_changes: detail max_metric: router_lsa: administrative: true on_shutdown: 10 on_startup: 10 mpls_te: enabled: true router_address: 192.0.22.22 neighbor: - neighbor_id: 192.0.11.12 poll_interval: 10 priority: 2 parameters: abr_type: cisco opaque_lsa: true rfc1583_compatibility: true router_id: 192.0.1.1 passive_interface: - eth1 redistribute: - metric: 10 metric_type: 2 route_type: bgp diff --git a/tests/integration/targets/vyos_ospfv2/vars/pre-v1_4.yaml b/tests/integration/targets/vyos_ospfv2/vars/pre-v1_4.yaml new file mode 100644 index 00000000..a5e8a725 --- /dev/null +++ b/tests/integration/targets/vyos_ospfv2/vars/pre-v1_4.yaml @@ -0,0 +1,123 @@ +--- +merged_commands: + - set protocols ospf mpls-te enable + - set protocols ospf mpls-te router-address '192.0.11.11' + - set protocols ospf redistribute bgp + - set protocols ospf redistribute bgp metric-type 2 + - set protocols ospf redistribute bgp metric 10 + - set protocols ospf default-information originate metric-type 2 + - set protocols ospf default-information originate always + - set protocols ospf default-information originate metric 10 + - set protocols ospf default-information originate route-map ingress + - set protocols ospf auto-cost reference-bandwidth '2' + - set protocols ospf parameters router-id '192.0.1.1' + - set protocols ospf parameters opaque-lsa + - set protocols ospf parameters abr-type 'cisco' + - set protocols ospf parameters rfc1583-compatibility + - set protocols ospf passive-interface eth1 + - set protocols ospf passive-interface eth2 + - set protocols ospf max-metric router-lsa on-shutdown 10 + - set protocols ospf max-metric router-lsa administrative + - set protocols ospf max-metric router-lsa on-startup 10 + - set protocols ospf log-adjacency-changes 'detail' + - set protocols ospf neighbor 192.0.11.12 priority 2 + - set protocols ospf neighbor 192.0.11.12 poll-interval 10 + - set protocols ospf neighbor 192.0.11.12 + - set protocols ospf area '2' + - set protocols ospf area 2 authentication plaintext-password + - set protocols ospf area 2 shortcut enable + - set protocols ospf area 2 area-type normal + - set protocols ospf area '3' + - set protocols ospf area 3 area-type nssa + - set protocols ospf area 4 range 192.0.3.0/24 cost 10 + - set protocols ospf area 4 range 192.0.3.0/24 + - set protocols ospf area 4 range 192.0.4.0/24 cost 12 + - set protocols ospf area 4 range 192.0.4.0/24 + - set protocols ospf area 4 area-type stub default-cost 20 + - set protocols ospf area '4' + - set protocols ospf area 4 network 192.0.2.0/24 + +populate_commands: + - set protocols ospf mpls-te 'enable' + - set protocols ospf mpls-te router-address '192.0.11.11' + - set protocols ospf redistribute bgp metric-type '2' + - set protocols ospf redistribute bgp metric '10' + - set protocols ospf default-information originate metric-type '2' + - set protocols ospf default-information originate 'always' + - set protocols ospf default-information originate metric '10' + - set protocols ospf default-information originate route-map 'ingress' + - set protocols ospf auto-cost reference-bandwidth '2' + - set protocols ospf parameters router-id '192.0.1.1' + - set protocols ospf parameters 'opaque-lsa' + - set protocols ospf parameters abr-type 'cisco' + - set protocols ospf parameters 'rfc1583-compatibility' + - set protocols ospf passive-interface 'eth1' + - set protocols ospf passive-interface 'eth2' + - set protocols ospf max-metric router-lsa on-shutdown '10' + - set protocols ospf max-metric router-lsa 'administrative' + - set protocols ospf max-metric router-lsa on-startup '10' + - set protocols ospf log-adjacency-changes 'detail' + - set protocols ospf neighbor 192.0.11.12 priority '2' + - set protocols ospf neighbor 192.0.11.12 poll-interval '10' + - set protocols ospf area 2 authentication 'plaintext-password' + - set protocols ospf area 2 shortcut 'enable' + - set protocols ospf area 2 area-type 'normal' + - set protocols ospf area 3 area-type 'nssa' + - set protocols ospf area 4 range 192.0.3.0/24 cost '10' + - set protocols ospf area 4 range 192.0.4.0/24 cost '12' + - set protocols ospf area 4 area-type stub default-cost '20' + - set protocols ospf area 4 network '192.0.2.0/24' + +replaced_commands: + - delete protocols ospf passive-interface eth2 + - delete protocols ospf area 3 + - delete protocols ospf area 4 range 192.0.3.0/24 cost + - delete protocols ospf area 4 range 192.0.3.0/24 + - delete protocols ospf area 4 range 192.0.4.0/24 cost + - delete protocols ospf area 4 range 192.0.4.0/24 + - set protocols ospf mpls-te router-address '192.0.22.22' + - set protocols ospf area 4 range 1.1.2.0/24 cost 10 + - set protocols ospf area 4 range 1.1.2.0/24 + - set protocols ospf area 4 network 192.0.12.0/24 + - set protocols ospf area 4 network 192.0.22.0/24 + - set protocols ospf area 4 network 192.0.32.0/24 + +rendered_commands: + - set protocols ospf mpls-te enable + - set protocols ospf mpls-te router-address '192.0.11.11' + - set protocols ospf redistribute bgp + - set protocols ospf redistribute bgp metric-type 2 + - set protocols ospf redistribute bgp metric 10 + - set protocols ospf default-information originate metric-type 2 + - set protocols ospf default-information originate always + - set protocols ospf default-information originate metric 10 + - set protocols ospf default-information originate route-map ingress + - set protocols ospf auto-cost reference-bandwidth '2' + - set protocols ospf parameters router-id '192.0.1.1' + - set protocols ospf parameters opaque-lsa + - set protocols ospf parameters abr-type 'cisco' + - set protocols ospf parameters rfc1583-compatibility + - set protocols ospf passive-interface eth1 + - set protocols ospf passive-interface eth2 + - set protocols ospf max-metric router-lsa on-shutdown 10 + - set protocols ospf max-metric router-lsa administrative + - set protocols ospf max-metric router-lsa on-startup 10 + - set protocols ospf log-adjacency-changes 'detail' + - set protocols ospf neighbor 192.0.11.12 priority 2 + - set protocols ospf neighbor 192.0.11.12 poll-interval 10 + - set protocols ospf neighbor 192.0.11.12 + - set protocols ospf area '2' + - set protocols ospf area 2 authentication plaintext-password + - set protocols ospf area 2 shortcut enable + - set protocols ospf area 2 area-type normal + - set protocols ospf area '3' + - set protocols ospf area 3 area-type nssa + - set protocols ospf area 4 range 192.0.3.0/24 cost 10 + - set protocols ospf area 4 range 192.0.3.0/24 + - set protocols ospf area 4 range 192.0.4.0/24 cost 12 + - set protocols ospf area 4 range 192.0.4.0/24 + - set protocols ospf area 4 area-type stub default-cost 20 + - set protocols ospf area '4' + - set protocols ospf area 4 network 192.0.2.0/24 + +parsed_config_file: "_parsed_config_1_3.cfg" diff --git a/tests/integration/targets/vyos_ospfv2/vars/v1_4.yaml b/tests/integration/targets/vyos_ospfv2/vars/v1_4.yaml new file mode 100644 index 00000000..4b7d0ab1 --- /dev/null +++ b/tests/integration/targets/vyos_ospfv2/vars/v1_4.yaml @@ -0,0 +1,123 @@ +--- +merged_commands: + - set protocols ospf mpls-te enable + - set protocols ospf mpls-te router-address '192.0.11.11' + - set protocols ospf redistribute bgp + - set protocols ospf redistribute bgp metric-type 2 + - set protocols ospf redistribute bgp metric 10 + - set protocols ospf default-information originate metric-type 2 + - set protocols ospf default-information originate always + - set protocols ospf default-information originate metric 10 + - set protocols ospf default-information originate route-map ingress + - set protocols ospf auto-cost reference-bandwidth '2' + - set protocols ospf parameters router-id '192.0.1.1' + - set protocols ospf parameters opaque-lsa + - set protocols ospf parameters abr-type 'cisco' + - set protocols ospf parameters rfc1583-compatibility + - set protocols ospf interface eth1 passive + - set protocols ospf interface eth2 passive + - set protocols ospf max-metric router-lsa on-shutdown 10 + - set protocols ospf max-metric router-lsa administrative + - set protocols ospf max-metric router-lsa on-startup 10 + - set protocols ospf log-adjacency-changes 'detail' + - set protocols ospf neighbor 192.0.11.12 priority 2 + - set protocols ospf neighbor 192.0.11.12 poll-interval 10 + - set protocols ospf neighbor 192.0.11.12 + - set protocols ospf area '2' + - set protocols ospf area 2 authentication plaintext-password + - set protocols ospf area 2 shortcut enable + - set protocols ospf area 2 area-type normal + - set protocols ospf area '3' + - set protocols ospf area 3 area-type nssa + - set protocols ospf area 4 range 192.0.3.0/24 cost 10 + - set protocols ospf area 4 range 192.0.3.0/24 + - set protocols ospf area 4 range 192.0.4.0/24 cost 12 + - set protocols ospf area 4 range 192.0.4.0/24 + - set protocols ospf area 4 area-type stub default-cost 20 + - set protocols ospf area '4' + - set protocols ospf area 4 network 192.0.2.0/24 + +populate_commands: + - set protocols ospf mpls-te 'enable' + - set protocols ospf mpls-te router-address '192.0.11.11' + - set protocols ospf redistribute bgp metric-type '2' + - set protocols ospf redistribute bgp metric '10' + - set protocols ospf default-information originate metric-type '2' + - set protocols ospf default-information originate 'always' + - set protocols ospf default-information originate metric '10' + - set protocols ospf default-information originate route-map 'ingress' + - set protocols ospf auto-cost reference-bandwidth '2' + - set protocols ospf parameters router-id '192.0.1.1' + - set protocols ospf parameters 'opaque-lsa' + - set protocols ospf parameters abr-type 'cisco' + - set protocols ospf parameters 'rfc1583-compatibility' + - set protocols ospf interface 'eth1' passive + - set protocols ospf interface 'eth2' passive + - set protocols ospf max-metric router-lsa on-shutdown '10' + - set protocols ospf max-metric router-lsa 'administrative' + - set protocols ospf max-metric router-lsa on-startup '10' + - set protocols ospf log-adjacency-changes 'detail' + - set protocols ospf neighbor 192.0.11.12 priority '2' + - set protocols ospf neighbor 192.0.11.12 poll-interval '10' + - set protocols ospf area 2 authentication 'plaintext-password' + - set protocols ospf area 2 shortcut 'enable' + - set protocols ospf area 2 area-type 'normal' + - set protocols ospf area 3 area-type 'nssa' + - set protocols ospf area 4 range 192.0.3.0/24 cost '10' + - set protocols ospf area 4 range 192.0.4.0/24 cost '12' + - set protocols ospf area 4 area-type stub default-cost '20' + - set protocols ospf area 4 network '192.0.2.0/24' + +replaced_commands: + - delete protocols ospf interface eth2 passive + - delete protocols ospf area 3 + - delete protocols ospf area 4 range 192.0.3.0/24 cost + - delete protocols ospf area 4 range 192.0.3.0/24 + - delete protocols ospf area 4 range 192.0.4.0/24 cost + - delete protocols ospf area 4 range 192.0.4.0/24 + - set protocols ospf mpls-te router-address '192.0.22.22' + - set protocols ospf area 4 range 1.1.2.0/24 cost 10 + - set protocols ospf area 4 range 1.1.2.0/24 + - set protocols ospf area 4 network 192.0.12.0/24 + - set protocols ospf area 4 network 192.0.22.0/24 + - set protocols ospf area 4 network 192.0.32.0/24 + +rendered_commands: + - set protocols ospf mpls-te enable + - set protocols ospf mpls-te router-address '192.0.11.11' + - set protocols ospf redistribute bgp + - set protocols ospf redistribute bgp metric-type 2 + - set protocols ospf redistribute bgp metric 10 + - set protocols ospf default-information originate metric-type 2 + - set protocols ospf default-information originate always + - set protocols ospf default-information originate metric 10 + - set protocols ospf default-information originate route-map ingress + - set protocols ospf auto-cost reference-bandwidth '2' + - set protocols ospf parameters router-id '192.0.1.1' + - set protocols ospf parameters opaque-lsa + - set protocols ospf parameters abr-type 'cisco' + - set protocols ospf parameters rfc1583-compatibility + - set protocols ospf interface eth1 passive + - set protocols ospf interface eth2 passive + - set protocols ospf max-metric router-lsa on-shutdown 10 + - set protocols ospf max-metric router-lsa administrative + - set protocols ospf max-metric router-lsa on-startup 10 + - set protocols ospf log-adjacency-changes 'detail' + - set protocols ospf neighbor 192.0.11.12 priority 2 + - set protocols ospf neighbor 192.0.11.12 poll-interval 10 + - set protocols ospf neighbor 192.0.11.12 + - set protocols ospf area '2' + - set protocols ospf area 2 authentication plaintext-password + - set protocols ospf area 2 shortcut enable + - set protocols ospf area 2 area-type normal + - set protocols ospf area '3' + - set protocols ospf area 3 area-type nssa + - set protocols ospf area 4 range 192.0.3.0/24 cost 10 + - set protocols ospf area 4 range 192.0.3.0/24 + - set protocols ospf area 4 range 192.0.4.0/24 cost 12 + - set protocols ospf area 4 range 192.0.4.0/24 + - set protocols ospf area 4 area-type stub default-cost 20 + - set protocols ospf area '4' + - set protocols ospf area 4 network 192.0.2.0/24 + +parsed_config_file: "_parsed_config_1_4.cfg" diff --git a/tests/unit/modules/network/vyos/test_vyos_firewall_global.py b/tests/unit/modules/network/vyos/test_vyos_firewall_global.py index 2ecd0621..481cc1dd 100644 --- a/tests/unit/modules/network/vyos/test_vyos_firewall_global.py +++ b/tests/unit/modules/network/vyos/test_vyos_firewall_global.py @@ -1,454 +1,455 @@ # (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_firewall_global from ansible_collections.vyos.vyos.tests.unit.modules.utils import set_module_args from .vyos_module import TestVyosModule, load_fixture class TestVyosFirewallGlobalModule(TestVyosModule): module = vyos_firewall_global def setUp(self): super(TestVyosFirewallGlobalModule, self).setUp() self.mock_get_config = patch( "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.network.Config.get_config", ) self.get_config = self.mock_get_config.start() self.mock_load_config = patch( "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.network.Config.load_config", ) self.load_config = self.mock_load_config.start() self.mock_get_resource_connection_config = patch( "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base.get_resource_connection", ) self.get_resource_connection_config = self.mock_get_resource_connection_config.start() self.mock_get_resource_connection_facts = patch( "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.facts.facts.get_resource_connection", ) self.get_resource_connection_facts = self.mock_get_resource_connection_facts.start() self.mock_execute_show_command = patch( "ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.firewall_global.firewall_global.Firewall_globalFacts.get_device_data", ) self.mock_get_os_version = patch( "ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.config.firewall_global.firewall_global.get_os_version", ) self.get_os_version = self.mock_get_os_version.start() self.get_os_version.return_value = "1.3" self.execute_show_command = self.mock_execute_show_command.start() self.maxDiff = None def tearDown(self): super(TestVyosFirewallGlobalModule, self).tearDown() self.mock_get_resource_connection_config.stop() self.mock_get_resource_connection_facts.stop() self.mock_get_config.stop() self.mock_load_config.stop() self.mock_execute_show_command.stop() self.mock_get_os_version.stop() def load_fixtures(self, commands=None, filename=None): def load_from_file(*args, **kwargs): return load_fixture("vyos_firewall_global_config.cfg") self.execute_show_command.side_effect = load_from_file def test_vyos_firewall_global_set_01_merged(self): set_module_args( dict( config=dict( validation="strict", config_trap=True, log_martians=True, syn_cookies=True, twa_hazards_protection=True, ping=dict(all=True, broadcast=True), state_policy=[ dict( connection_type="established", action="accept", log=True, log_level="emerg", ), dict(connection_type="invalid", action="reject"), ], route_redirects=[ dict( afi="ipv4", ip_src_route=True, icmp_redirects=dict(send=True, receive=False), ), dict( afi="ipv6", ip_src_route=True, icmp_redirects=dict(receive=False), ), ], group=dict( address_group=[ dict( afi="ipv4", name="MGMT-HOSTS", description="This group has the Management hosts address lists", members=[ dict(address="192.0.1.1"), dict(address="192.0.1.3"), dict(address="192.0.1.5"), ], ), dict( afi="ipv6", name="GOOGLE-DNS-v6", members=[ dict(address="2001:4860:4860::8888"), dict(address="2001:4860:4860::8844"), ], ), ], network_group=[ dict( afi="ipv4", name="MGMT", description="This group has the Management network addresses", members=[dict(address="192.0.1.0/24")], ), dict( afi="ipv6", name="DOCUMENTATION-v6", description="IPv6 Addresses reserved for documentation per RFC 3849", members=[ dict(address="2001:0DB8::/32"), dict(address="3FFF:FFFF::/32"), ], ), ], port_group=[ dict( name="TELNET", description="This group has the telnet ports", members=[dict(port="23")], ), ], ), ), state="merged", ), ) commands = [ "set firewall group address-group MGMT-HOSTS address 192.0.1.1", "set firewall group address-group MGMT-HOSTS address 192.0.1.3", "set firewall group address-group MGMT-HOSTS address 192.0.1.5", "set firewall group address-group MGMT-HOSTS description 'This group has the Management hosts address lists'", "set firewall group address-group MGMT-HOSTS", "set firewall group ipv6-address-group GOOGLE-DNS-v6 address 2001:4860:4860::8888", "set firewall group ipv6-address-group GOOGLE-DNS-v6 address 2001:4860:4860::8844", "set firewall group ipv6-address-group GOOGLE-DNS-v6", "set firewall group network-group MGMT network 192.0.1.0/24", "set firewall group network-group MGMT description 'This group has the Management network addresses'", "set firewall group network-group MGMT", "set firewall group ipv6-network-group DOCUMENTATION-v6 network 2001:0DB8::/32", "set firewall group ipv6-network-group DOCUMENTATION-v6 network 3FFF:FFFF::/32", "set firewall group ipv6-network-group DOCUMENTATION-v6 description 'IPv6 Addresses reserved for documentation per RFC 3849'", "set firewall group ipv6-network-group DOCUMENTATION-v6", "set firewall group port-group TELNET port 23", "set firewall group port-group TELNET description 'This group has the telnet ports'", "set firewall group port-group TELNET", "set firewall ip-src-route 'enable'", "set firewall receive-redirects 'disable'", "set firewall config-trap 'enable'", "set firewall ipv6-receive-redirects 'disable'", "set firewall state-policy established action 'accept'", "set firewall state-policy established log 'enable'", "set firewall state-policy invalid action 'reject'", "set firewall broadcast-ping 'enable'", "set firewall all-ping 'enable'", "set firewall log-martians 'enable'", "set firewall twa-hazards-protection 'enable'", "set firewall syn-cookies 'enable'", "set firewall source-validation 'strict'", ] self.execute_module(changed=True, commands=commands) def test_vyos_firewall_global_set_01_merged_idem(self): set_module_args( dict( config=dict( group=dict( address_group=[ dict( afi="ipv4", name="RND-HOSTS", description="This group has the Management hosts address lists", members=[ dict(address="192.0.2.1"), dict(address="192.0.2.3"), dict(address="192.0.2.5"), ], ), dict( afi="ipv6", name="LOCAL-v6", description="This group has the hosts address lists of this machine", members=[ dict(address="::1"), dict(address="fdec:2503:89d6:59b3::1"), ], ), ], network_group=[ dict( afi="ipv4", name="RND", description="This group has the Management network addresses", members=[dict(address="192.0.2.0/24")], ), dict( afi="ipv6", name="UNIQUE-LOCAL-v6", description="This group encompasses the ULA address space in IPv6", members=[dict(address="fc00::/7")], ), ], port_group=[ dict( name="SSH", description="This group has the ssh ports", members=[dict(port="22")], ), ], ), ), state="merged", ), ) self.execute_module(changed=False, commands=[]) def test_vyos_firewall_global_set_01_replaced(self): set_module_args( dict( config=dict( group=dict( address_group=[ dict( afi="ipv4", name="RND-HOSTS", description="This group has the Management hosts address lists", members=[ dict(address="192.0.2.1"), dict(address="192.0.2.7"), dict(address="192.0.2.9"), ], ), dict( afi="ipv6", name="LOCAL-v6", description="This group has the hosts address lists of this machine", members=[ dict(address="::1"), dict(address="fdec:2503:89d6:59b3::2"), ], ), ], network_group=[ dict( afi="ipv4", name="RND", - description="This group has the Management network addresses", + # Deleted the description here. members=[dict(address="192.0.2.0/24")], ), dict( afi="ipv6", name="UNIQUE-LOCAL-v6", description="This group encompasses the ULA address space in IPv6", members=[dict(address="fc00::/7")], ), ], port_group=[ dict( name="SSH", description="This group has the ssh ports", members=[dict(port="2222")], ), ], ), ), state="replaced", ), ) commands = [ "delete firewall ipv6-src-route", "delete firewall send-redirects", "delete firewall group address-group RND-HOSTS address 192.0.2.3", "delete firewall group address-group RND-HOSTS address 192.0.2.5", "set firewall group address-group RND-HOSTS address 192.0.2.7", "set firewall group address-group RND-HOSTS address 192.0.2.9", + "delete firewall group network-group RND description", "delete firewall group ipv6-address-group LOCAL-v6 address fdec:2503:89d6:59b3::1", "set firewall group ipv6-address-group LOCAL-v6 address fdec:2503:89d6:59b3::2", "delete firewall group port-group SSH port 22", "set firewall group port-group SSH port 2222", ] self.execute_module(changed=True, commands=commands) def test_vyos_firewall_global_set_02_replaced(self): set_module_args( dict( config=dict( state_policy=[ dict(connection_type="invalid", action="reject"), dict(connection_type="related", action="drop"), ], group=dict( address_group=[ dict( afi="ipv4", name="RND-HOSTS", description="This group has the Management hosts address lists", members=[ dict(address="192.0.2.1"), dict(address="192.0.2.7"), dict(address="192.0.2.9"), ], ), dict( afi="ipv6", name="LOCAL-v6", description="This group has the hosts address lists of this machine", members=[ dict(address="::1"), dict(address="fdec:2503:89d6:59b3::2"), ], ), ], network_group=[ dict( afi="ipv4", name="RND", description="This group has the Management network addresses", members=[dict(address="192.0.2.0/24")], ), dict( afi="ipv6", name="UNIQUE-LOCAL-v6", description="This group encompasses the ULA address space in IPv6", members=[dict(address="fc00::/7")], ), ], port_group=[ dict( name="SSH", description="This group has the ssh ports", members=[dict(port="2222")], ), ], ), ), state="replaced", ), ) commands = [ "delete firewall group address-group RND-HOSTS address 192.0.2.3", "delete firewall group address-group RND-HOSTS address 192.0.2.5", "delete firewall ipv6-src-route", "delete firewall send-redirects", "set firewall state-policy related action 'drop'", "set firewall state-policy invalid action 'reject'", "set firewall group address-group RND-HOSTS address 192.0.2.7", "set firewall group address-group RND-HOSTS address 192.0.2.9", "delete firewall group ipv6-address-group LOCAL-v6 address fdec:2503:89d6:59b3::1", "set firewall group ipv6-address-group LOCAL-v6 address fdec:2503:89d6:59b3::2", "delete firewall group port-group SSH port 22", "set firewall group port-group SSH port 2222", ] self.execute_module(changed=True, commands=commands) def test_vyos_firewall_global_set_01_replaced_idem(self): set_module_args( dict( config=dict( route_redirects=[ dict(ip_src_route=True, afi="ipv6"), dict(icmp_redirects=dict(send=True), afi="ipv4"), ], group=dict( address_group=[ dict( afi="ipv4", name="RND-HOSTS", description="This group has the Management hosts address lists", members=[ dict(address="192.0.2.1"), dict(address="192.0.2.3"), dict(address="192.0.2.5"), ], ), dict( afi="ipv6", name="LOCAL-v6", description="This group has the hosts address lists of this machine", members=[ dict(address="::1"), dict(address="fdec:2503:89d6:59b3::1"), ], ), ], network_group=[ dict( afi="ipv4", name="RND", description="This group has the Management network addresses", members=[dict(address="192.0.2.0/24")], ), dict( afi="ipv6", name="UNIQUE-LOCAL-v6", description="This group encompasses the ULA address space in IPv6", members=[dict(address="fc00::/7")], ), ], port_group=[ dict( name="SSH", description="This group has the ssh ports", members=[dict(port="22")], ), ], ), ), state="replaced", ), ) self.execute_module(changed=False, commands=[]) def test_vyos_firewall_global_set_01_deleted(self): set_module_args(dict(config=dict(), state="deleted")) commands = ["delete firewall"] self.execute_module(changed=True, commands=commands) diff --git a/tests/unit/modules/network/vyos/test_vyos_firewall_global14.py b/tests/unit/modules/network/vyos/test_vyos_firewall_global14.py index f4ae4add..aae4aa83 100644 --- a/tests/unit/modules/network/vyos/test_vyos_firewall_global14.py +++ b/tests/unit/modules/network/vyos/test_vyos_firewall_global14.py @@ -1,466 +1,467 @@ # (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_firewall_global from ansible_collections.vyos.vyos.tests.unit.modules.utils import set_module_args from .vyos_module import TestVyosModule, load_fixture class TestVyosFirewallRulesModule14(TestVyosModule): module = vyos_firewall_global def setUp(self): super(TestVyosFirewallRulesModule14, self).setUp() self.mock_get_config = patch( "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.network.Config.get_config", ) self.get_config = self.mock_get_config.start() self.mock_load_config = patch( "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.network.Config.load_config", ) self.load_config = self.mock_load_config.start() self.mock_get_resource_connection_config = patch( "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base.get_resource_connection", ) self.get_resource_connection_config = self.mock_get_resource_connection_config.start() self.mock_get_resource_connection_facts = patch( "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.facts.facts.get_resource_connection", ) self.get_resource_connection_facts = self.mock_get_resource_connection_facts.start() self.mock_execute_show_command = patch( "ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.firewall_global.firewall_global.Firewall_globalFacts.get_device_data", ) self.mock_get_os_version = patch( "ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.config.firewall_global.firewall_global.get_os_version", ) self.get_os_version = self.mock_get_os_version.start() self.get_os_version.return_value = "1.4" self.execute_show_command = self.mock_execute_show_command.start() self.maxDiff = None def tearDown(self): super(TestVyosFirewallRulesModule14, self).tearDown() self.mock_get_resource_connection_config.stop() self.mock_get_resource_connection_facts.stop() self.mock_get_config.stop() self.mock_load_config.stop() self.mock_execute_show_command.stop() self.mock_get_os_version.stop() def load_fixtures(self, commands=None, filename=None): def load_from_file(*args, **kwargs): return load_fixture("vyos_firewall_global_config_v14.cfg") self.execute_show_command.side_effect = load_from_file def test_vyos_firewall_global_set_01_merged(self): set_module_args( dict( config=dict( validation="strict", config_trap=True, log_martians=True, syn_cookies=True, twa_hazards_protection=True, ping=dict(all=True, broadcast=True), state_policy=[ dict( connection_type="established", action="accept", log=True, log_level="emerg", ), dict(connection_type="invalid", action="reject"), ], route_redirects=[ dict( afi="ipv4", ip_src_route=True, icmp_redirects=dict(send=True, receive=False), ), dict( afi="ipv6", ip_src_route=True, icmp_redirects=dict(receive=False), ), ], group=dict( address_group=[ dict( afi="ipv4", name="MGMT-HOSTS", description="This group has the Management hosts address lists", members=[ dict(address="192.0.1.1"), dict(address="192.0.1.3"), dict(address="192.0.1.5"), ], ), dict( afi="ipv6", name="GOOGLE-DNS-v6", members=[ dict(address="2001:4860:4860::8888"), dict(address="2001:4860:4860::8844"), ], ), ], network_group=[ dict( afi="ipv4", name="MGMT", description="This group has the Management network addresses", members=[dict(address="192.0.1.0/24")], ), dict( afi="ipv6", name="DOCUMENTATION-v6", description="IPv6 Addresses reserved for documentation per RFC 3849", members=[ dict(address="2001:0DB8::/32"), dict(address="3FFF:FFFF::/32"), ], ), ], port_group=[ dict( name="TELNET", description="This group has the telnet ports", members=[dict(port="23")], ), ], ), ), state="merged", ), ) commands = [ "set firewall group address-group MGMT-HOSTS address 192.0.1.1", "set firewall group address-group MGMT-HOSTS address 192.0.1.3", "set firewall group address-group MGMT-HOSTS address 192.0.1.5", "set firewall group address-group MGMT-HOSTS description 'This group has the Management hosts address lists'", "set firewall group address-group MGMT-HOSTS", "set firewall group ipv6-address-group GOOGLE-DNS-v6 address 2001:4860:4860::8888", "set firewall group ipv6-address-group GOOGLE-DNS-v6 address 2001:4860:4860::8844", "set firewall group ipv6-address-group GOOGLE-DNS-v6", "set firewall group network-group MGMT network 192.0.1.0/24", "set firewall group network-group MGMT description 'This group has the Management network addresses'", "set firewall group network-group MGMT", "set firewall group ipv6-network-group DOCUMENTATION-v6 network 2001:0DB8::/32", "set firewall group ipv6-network-group DOCUMENTATION-v6 network 3FFF:FFFF::/32", "set firewall group ipv6-network-group DOCUMENTATION-v6 description 'IPv6 Addresses reserved for documentation per RFC 3849'", "set firewall group ipv6-network-group DOCUMENTATION-v6", "set firewall group port-group TELNET port 23", "set firewall group port-group TELNET description 'This group has the telnet ports'", "set firewall group port-group TELNET", "set firewall global-options ip-src-route 'enable'", "set firewall global-options receive-redirects 'disable'", "set firewall global-options config-trap 'enable'", "set firewall global-options ipv6-receive-redirects 'disable'", "set firewall global-options state-policy established action 'accept'", "set firewall global-options state-policy established log", "set firewall global-options state-policy established log-level 'emerg'", "set firewall global-options state-policy invalid action 'reject'", "set firewall global-options broadcast-ping 'enable'", "set firewall global-options log-martians 'enable'", "set firewall global-options twa-hazards-protection 'enable'", "set firewall global-options syn-cookies 'enable'", "set firewall global-options source-validation 'strict'", ] self.execute_module(changed=True, commands=commands) def test_vyos_firewall_global_set_01_merged_idem(self): set_module_args( dict( config=dict( group=dict( address_group=[ dict( afi="ipv4", name="RND-HOSTS", description="This group has the Management hosts address lists", members=[ dict(address="192.0.2.1"), dict(address="192.0.2.3"), dict(address="192.0.2.5"), ], ), dict( afi="ipv6", name="LOCAL-v6", description="This group has the hosts address lists of this machine", members=[ dict(address="::1"), dict(address="fdec:2503:89d6:59b3::1"), ], ), ], network_group=[ dict( afi="ipv4", name="RND", description="This group has the Management network addresses", members=[dict(address="192.0.2.0/24")], ), dict( afi="ipv6", name="UNIQUE-LOCAL-v6", description="This group encompasses the ULA address space in IPv6", members=[dict(address="fc00::/7")], ), ], port_group=[ dict( name="SSH", description="This group has the ssh ports", members=[dict(port="22")], ), ], ), ), state="merged", ), ) self.execute_module(changed=False, commands=[]) def test_vyos_firewall_global_set_01_replaced(self): set_module_args( dict( config=dict( state_policy=[ dict(connection_type="invalid", action="reject"), ], group=dict( address_group=[ dict( afi="ipv4", name="RND-HOSTS", description="This group has the Management hosts address lists", members=[ dict(address="192.0.2.1"), dict(address="192.0.2.7"), dict(address="192.0.2.9"), ], ), dict( afi="ipv6", name="LOCAL-v6", description="This group has the hosts address lists of this machine", members=[ dict(address="::1"), dict(address="fdec:2503:89d6:59b3::2"), ], ), ], network_group=[ dict( afi="ipv4", name="RND", - description="This group has the Management network addresses", + # Deleted the description here. members=[dict(address="192.0.2.0/24")], ), dict( afi="ipv6", name="UNIQUE-LOCAL-v6", description="This group encompasses the ULA address space in IPv6", members=[dict(address="fc00::/7")], ), ], port_group=[ dict( name="SSH", description="This group has the ssh ports", members=[dict(port="2222")], ), ], ), ), state="replaced", ), ) commands = [ "delete firewall group address-group RND-HOSTS address 192.0.2.3", "delete firewall group address-group RND-HOSTS address 192.0.2.5", "delete firewall global-options all-ping", "delete firewall global-options state-policy related", "delete firewall global-options ipv6-src-route", "delete firewall global-options send-redirects", "set firewall global-options state-policy invalid action 'reject'", "set firewall group address-group RND-HOSTS address 192.0.2.7", "set firewall group address-group RND-HOSTS address 192.0.2.9", + "delete firewall group network-group RND description", "delete firewall group ipv6-address-group LOCAL-v6 address fdec:2503:89d6:59b3::1", "set firewall group ipv6-address-group LOCAL-v6 address fdec:2503:89d6:59b3::2", "delete firewall group port-group SSH port 22", "set firewall group port-group SSH port 2222", ] self.execute_module(changed=True, commands=commands) def test_vyos_firewall_global_set_01_replaced_idem(self): set_module_args( dict( config=dict( ping=dict(all=True), route_redirects=[ dict(ip_src_route=True, afi="ipv6"), dict(icmp_redirects=dict(send=True), afi="ipv4"), ], state_policy=[ dict(connection_type="related", action="accept", log_level="alert"), ], group=dict( address_group=[ dict( afi="ipv4", name="RND-HOSTS", description="This group has the Management hosts address lists", members=[ dict(address="192.0.2.1"), dict(address="192.0.2.3"), dict(address="192.0.2.5"), ], ), dict( afi="ipv6", name="LOCAL-v6", description="This group has the hosts address lists of this machine", members=[ dict(address="::1"), dict(address="fdec:2503:89d6:59b3::1"), ], ), ], network_group=[ dict( afi="ipv4", name="RND", description="This group has the Management network addresses", members=[dict(address="192.0.2.0/24")], ), dict( afi="ipv6", name="UNIQUE-LOCAL-v6", description="This group encompasses the ULA address space in IPv6", members=[dict(address="fc00::/7")], ), ], port_group=[ dict( name="SSH", description="This group has the ssh ports", members=[dict(port="22")], ), ], ), ), state="replaced", ), ) self.execute_module(changed=False, commands=[]) def test_vyos_firewall_global_set_02_replaced(self): set_module_args( dict( config=dict( state_policy=[ dict(connection_type="invalid", action="reject"), dict(connection_type="related", action="drop"), ], group=dict( address_group=[ dict( afi="ipv4", name="RND-HOSTS", description="This group has the Management hosts address lists", members=[ dict(address="192.0.2.1"), dict(address="192.0.2.7"), dict(address="192.0.2.9"), ], ), dict( afi="ipv6", name="LOCAL-v6", description="This group has the hosts address lists of this machine", members=[ dict(address="::1"), dict(address="fdec:2503:89d6:59b3::2"), ], ), ], network_group=[ dict( afi="ipv4", name="RND", description="This group has the Management network addresses", members=[dict(address="192.0.2.0/24")], ), dict( afi="ipv6", name="UNIQUE-LOCAL-v6", description="This group encompasses the ULA address space in IPv6", members=[dict(address="fc00::/7")], ), ], port_group=[ dict( name="SSH", description="This group has the ssh ports", members=[dict(port="2222")], ), ], ), ), state="replaced", ), ) commands = [ "delete firewall group address-group RND-HOSTS address 192.0.2.3", "delete firewall group address-group RND-HOSTS address 192.0.2.5", "delete firewall global-options all-ping", "delete firewall global-options ipv6-src-route", "delete firewall global-options send-redirects", "set firewall global-options state-policy related action 'drop'", "delete firewall global-options state-policy related log-level", "set firewall global-options state-policy invalid action 'reject'", "set firewall group address-group RND-HOSTS address 192.0.2.7", "set firewall group address-group RND-HOSTS address 192.0.2.9", "delete firewall group ipv6-address-group LOCAL-v6 address fdec:2503:89d6:59b3::1", "set firewall group ipv6-address-group LOCAL-v6 address fdec:2503:89d6:59b3::2", "delete firewall group port-group SSH port 22", "set firewall group port-group SSH port 2222", ] self.execute_module(changed=True, commands=commands) def test_vyos_firewall_global_set_01_deleted(self): set_module_args(dict(config=dict(), state="deleted")) commands = ["delete firewall"] self.execute_module(changed=True, commands=commands) 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) diff --git a/tests/unit/modules/network/vyos/test_vyos_ospfv2.py b/tests/unit/modules/network/vyos/test_vyos_ospfv2.py index ec4018e1..2620b1cd 100644 --- a/tests/unit/modules/network/vyos/test_vyos_ospfv2.py +++ b/tests/unit/modules/network/vyos/test_vyos_ospfv2.py @@ -1,425 +1,437 @@ # (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_ospfv2 from ansible_collections.vyos.vyos.tests.unit.modules.utils import set_module_args from .vyos_module import TestVyosModule, load_fixture class TestVyosOspfv2Module(TestVyosModule): module = vyos_ospfv2 def setUp(self): super(TestVyosOspfv2Module, self).setUp() self.mock_get_config = patch( "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.network.Config.get_config", ) self.get_config = self.mock_get_config.start() self.mock_load_config = patch( "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.network.Config.load_config", ) self.load_config = self.mock_load_config.start() self.mock_get_resource_connection_config = patch( "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base.get_resource_connection", ) self.get_resource_connection_config = self.mock_get_resource_connection_config.start() self.mock_get_resource_connection_facts = patch( "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.facts.facts.get_resource_connection", ) self.get_resource_connection_facts = self.mock_get_resource_connection_facts.start() self.mock_execute_show_command = patch( "ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.ospfv2.ospfv2.Ospfv2Facts.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.ospfv2.ospfv2.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.ospfv2.ospfv2.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(TestVyosOspfv2Module, self).tearDown() self.mock_get_resource_connection_config.stop() self.mock_get_resource_connection_facts.stop() self.mock_get_config.stop() self.mock_load_config.stop() self.mock_execute_show_command.stop() def load_fixtures(self, commands=None, filename=None): if filename is None: filename = "vyos_ospfv2_config.cfg" def load_from_file(*args, **kwargs): output = load_fixture(filename) return output self.execute_show_command.side_effect = load_from_file def test_vyos_ospfv2_merged_new_config(self): set_module_args( dict( config=dict( log_adjacency_changes="detail", mpls_te=dict(enabled=True, router_address="192.0.11.11"), auto_cost=dict(reference_bandwidth=2), areas=[ dict( area_id="2", area_type=dict(normal=True), authentication="plaintext-password", shortcut="enable", ), dict( area_id="4", area_type=dict(stub=dict(default_cost=10)), network=[dict(address="192.0.2.0/24")], range=[ dict(address="192.0.3.0/24", cost=10), dict(address="192.0.4.0/24", cost=12), ], ), ], ), state="merged", ), ) commands = [ "set protocols ospf mpls-te enable", "set protocols ospf mpls-te router-address '192.0.11.11'", "set protocols ospf auto-cost reference-bandwidth '2'", "set protocols ospf log-adjacency-changes 'detail'", "set protocols ospf area '2'", "set protocols ospf area 2 authentication plaintext-password", "set protocols ospf area 2 shortcut enable", "set protocols ospf area 2 area-type normal", "set protocols ospf area 4 range 192.0.3.0/24 cost 10", "set protocols ospf area 4 range 192.0.3.0/24", "set protocols ospf area 4 range 192.0.4.0/24 cost 12", "set protocols ospf area 4 range 192.0.4.0/24", "set protocols ospf area 4 area-type stub default-cost 10", "set protocols ospf area '4'", "set protocols ospf area 4 network 192.0.2.0/24", ] self.execute_module(changed=True, commands=commands) def test_vyos_ospfv2_merged_idem(self): set_module_args( dict( config=dict( areas=[ dict( area_id="12", area_type=dict(normal=True), authentication="plaintext-password", shortcut="enable", ), dict( area_id="14", area_type=dict(stub=dict(default_cost=20)), network=[dict(address="192.0.12.0/24")], range=[ dict(address="192.0.13.0/24", cost=10), dict(address="192.0.14.0/24", cost=12), ], ), ], ), state="merged", ), ) self.execute_module(changed=False, commands=[]) def test_vyos_ospfv2_merged_update_existing(self): set_module_args( dict( config=dict( areas=[ dict( area_id="12", area_type=dict(normal=True), authentication="plaintext-password", shortcut="enable", ), dict( area_id="14", area_type=dict(stub=dict(set=False)), network=[ dict(address="192.0.12.0/24"), dict(address="192.0.22.0/24"), ], range=[ dict(address="192.0.13.0/24", cost=10), dict(address="192.0.14.0/24", cost=12), ], ), ], ), state="merged", ), ) commands = [ "delete protocols ospf area 14 area-type stub", "set protocols ospf area 14 network 192.0.22.0/24", ] self.execute_module(changed=True, commands=commands) def test_vyos_ospfv2_replaced(self): set_module_args( dict( config=dict( log_adjacency_changes="detail", mpls_te=dict(enabled=True, router_address="192.0.11.11"), auto_cost=dict(reference_bandwidth=2), areas=[ dict( area_id="12", area_type=dict(normal=True), authentication="plaintext-password", shortcut="enable", ), dict( area_id="15", area_type=dict(stub=dict(default_cost=10)), network=[dict(address="192.0.12.0/24")], range=[ dict(address="192.0.13.0/24", cost=10), dict(address="192.0.14.0/24", cost=12), dict(address="192.0.15.0/24", cost=14), ], ), ], ), state="replaced", ), ) commands = [ "set protocols ospf mpls-te enable", "set protocols ospf mpls-te router-address '192.0.11.11'", "set protocols ospf auto-cost reference-bandwidth '2'", "set protocols ospf log-adjacency-changes 'detail'", "delete protocols ospf area 14", "set protocols ospf area 15 range 192.0.13.0/24 cost 10", "set protocols ospf area 15 range 192.0.13.0/24", "set protocols ospf area 15 range 192.0.14.0/24 cost 12", "set protocols ospf area 15 range 192.0.14.0/24", "set protocols ospf area 15 range 192.0.15.0/24 cost 14", "set protocols ospf area 15 range 192.0.15.0/24", "set protocols ospf area 15 area-type stub default-cost 10", "set protocols ospf area '15'", "set protocols ospf area 15 network 192.0.12.0/24", ] self.execute_module(changed=True, commands=commands) def test_vyos_ospfv2_replaced_idem(self): set_module_args( dict( config=dict( areas=[ dict( area_id="12", area_type=dict(normal=True), authentication="plaintext-password", shortcut="enable", ), dict( area_id="14", area_type=dict(stub=dict(default_cost=20)), network=[dict(address="192.0.12.0/24")], range=[ dict(address="192.0.13.0/24", cost=10), dict(address="192.0.14.0/24", cost=12), ], ), ], ), state="replaced", ), ) self.execute_module(changed=False, commands=[]) def test_vyos_ospfv2_deleted_no_config(self): set_module_args(dict(config=None, state="deleted")) commands = ["delete protocols ospf"] self.execute_module(changed=True, commands=commands) def test_vyos_ospfv2_gathered(self): set_module_args(dict(state="gathered")) result = self.execute_module(changed=False, filename="vyos_ospfv2_config.cfg") gather_dict = { "areas": [ { "area_id": "2", "area_type": {"normal": True}, "authentication": "plaintext-password", "shortcut": "enable", }, { "area_id": "14", "area_type": {"stub": {"default_cost": 20, "set": True}}, "network": [{"address": "192.0.12.0/24"}], "range": [ {"address": "192.0.13.0/24", "cost": 10}, {"address": "192.0.14.0/24", "cost": 12}, ], }, ], } self.assertEqual(sorted(gather_dict), sorted(result["gathered"])) def test_vyos_ospfv2_parsed(self): parsed_str = """set protocols ospf area 2 area-type 'normal' set protocols ospf area 2 authentication 'plaintext-password' set protocols ospf area 2 shortcut 'enable' set protocols ospf area 3 area-type 'nssa' set protocols ospf area 4 area-type stub default-cost '20' set protocols ospf area 4 network '192.0.2.0/24' set protocols ospf area 4 range 192.0.3.0/24 cost '10' set protocols ospf area 4 range 192.0.4.0/24 cost '12' set protocols ospf default-information originate 'always' set protocols ospf default-information originate metric '10' set protocols ospf default-information originate metric-type '2' set protocols ospf auto-cost reference-bandwidth '2' set protocols ospf default-information originate route-map 'ingress' set protocols ospf log-adjacency-changes 'detail' set protocols ospf max-metric router-lsa 'administrative' set protocols ospf max-metric router-lsa on-shutdown '10' set protocols ospf max-metric router-lsa on-startup '10' set protocols ospf mpls-te 'enable' set protocols ospf mpls-te router-address '192.0.11.11' set protocols ospf neighbor 192.0.11.12 poll-interval '10' set protocols ospf neighbor 192.0.11.12 priority '2' set protocols ospf parameters abr-type 'cisco' set protocols ospf parameters 'opaque-lsa' set protocols ospf parameters 'rfc1583-compatibility' set protocols ospf parameters router-id '192.0.1.1' set protocols ospf passive-interface 'eth1' set protocols ospf passive-interface 'eth2' set protocols ospf redistribute bgp metric '10' set protocols ospf redistribute bgp metric-type '2'""" set_module_args(dict(running_config=parsed_str, state="parsed")) result = self.execute_module(changed=False) parsed_list = { "areas": [ { "area_id": "2", "area_type": {"normal": True}, "authentication": "plaintext-password", "shortcut": "enable", }, {"area_id": "3", "area_type": {"nssa": {"set": True}}}, { "area_id": "4", "area_type": {"stub": {"default_cost": 20, "set": True}}, "network": [{"address": "192.0.2.0/24"}], "range": [ {"address": "192.0.3.0/24", "cost": 10}, {"address": "192.0.4.0/24", "cost": 12}, ], }, ], "auto_cost": {"reference_bandwidth": 2}, "default_information": { "originate": { "always": True, "metric": 10, "metric_type": 2, "route_map": "ingress", }, }, "log_adjacency_changes": "detail", "max_metric": { "router_lsa": { "administrative": True, "on_shutdown": 10, "on_startup": 10, }, }, "mpls_te": {"enabled": True, "router_address": "192.0.11.11"}, "neighbor": [ { "neighbor_id": "192.0.11.12", "poll_interval": 10, "priority": 2, }, ], "parameters": { "abr_type": "cisco", "opaque_lsa": True, "rfc1583_compatibility": True, "router_id": "192.0.1.1", }, "passive_interface": ["eth2", "eth1"], "redistribute": [{"metric": 10, "metric_type": 2, "route_type": "bgp"}], } self.assertEqual(sorted(parsed_list), sorted(result["parsed"])) def test_vyos_ospfv2_rendered(self): set_module_args( dict( config=dict( log_adjacency_changes="detail", mpls_te=dict(enabled=True, router_address="192.0.11.11"), auto_cost=dict(reference_bandwidth=2), areas=[ dict( area_id="2", area_type=dict(normal=True), authentication="plaintext-password", shortcut="enable", ), dict( area_id="4", area_type=dict(stub=dict(default_cost=10)), network=[dict(address="192.0.2.0/24")], range=[ dict(address="192.0.3.0/24", cost=10), dict(address="192.0.4.0/24", cost=12), ], ), ], ), state="rendered", ), ) commands = [ "set protocols ospf mpls-te enable", "set protocols ospf mpls-te router-address '192.0.11.11'", "set protocols ospf auto-cost reference-bandwidth '2'", "set protocols ospf log-adjacency-changes 'detail'", "set protocols ospf area '2'", "set protocols ospf area 2 authentication plaintext-password", "set protocols ospf area 2 shortcut enable", "set protocols ospf area 2 area-type normal", "set protocols ospf area 4 range 192.0.3.0/24 cost 10", "set protocols ospf area 4 range 192.0.3.0/24", "set protocols ospf area 4 range 192.0.4.0/24 cost 12", "set protocols ospf area 4 range 192.0.4.0/24", "set protocols ospf area 4 area-type stub default-cost 10", "set protocols ospf area '4'", "set protocols ospf area 4 network 192.0.2.0/24", ] result = self.execute_module(changed=False) self.assertEqual(sorted(result["rendered"]), sorted(commands), result["rendered"]) diff --git a/tests/unit/modules/network/vyos/test_vyos_ospfv2.py b/tests/unit/modules/network/vyos/test_vyos_ospfv2v14.py similarity index 95% copy from tests/unit/modules/network/vyos/test_vyos_ospfv2.py copy to tests/unit/modules/network/vyos/test_vyos_ospfv2v14.py index ec4018e1..5c77cb88 100644 --- a/tests/unit/modules/network/vyos/test_vyos_ospfv2.py +++ b/tests/unit/modules/network/vyos/test_vyos_ospfv2v14.py @@ -1,425 +1,437 @@ # (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_ospfv2 from ansible_collections.vyos.vyos.tests.unit.modules.utils import set_module_args from .vyos_module import TestVyosModule, load_fixture -class TestVyosOspfv2Module(TestVyosModule): +class TestVyosOspfv2Module14(TestVyosModule): module = vyos_ospfv2 def setUp(self): - super(TestVyosOspfv2Module, self).setUp() + super(TestVyosOspfv2Module14, self).setUp() self.mock_get_config = patch( "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.network.Config.get_config", ) self.get_config = self.mock_get_config.start() self.mock_load_config = patch( "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.network.Config.load_config", ) self.load_config = self.mock_load_config.start() self.mock_get_resource_connection_config = patch( "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base.get_resource_connection", ) self.get_resource_connection_config = self.mock_get_resource_connection_config.start() self.mock_get_resource_connection_facts = patch( "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.facts.facts.get_resource_connection", ) self.get_resource_connection_facts = self.mock_get_resource_connection_facts.start() self.mock_execute_show_command = patch( "ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.ospfv2.ospfv2.Ospfv2Facts.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.ospfv2.ospfv2.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.ospfv2.ospfv2.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(TestVyosOspfv2Module, self).tearDown() + super(TestVyosOspfv2Module14, self).tearDown() self.mock_get_resource_connection_config.stop() self.mock_get_resource_connection_facts.stop() self.mock_get_config.stop() self.mock_load_config.stop() self.mock_execute_show_command.stop() def load_fixtures(self, commands=None, filename=None): if filename is None: filename = "vyos_ospfv2_config.cfg" def load_from_file(*args, **kwargs): output = load_fixture(filename) return output self.execute_show_command.side_effect = load_from_file def test_vyos_ospfv2_merged_new_config(self): set_module_args( dict( config=dict( log_adjacency_changes="detail", mpls_te=dict(enabled=True, router_address="192.0.11.11"), auto_cost=dict(reference_bandwidth=2), areas=[ dict( area_id="2", area_type=dict(normal=True), authentication="plaintext-password", shortcut="enable", ), dict( area_id="4", area_type=dict(stub=dict(default_cost=10)), network=[dict(address="192.0.2.0/24")], range=[ dict(address="192.0.3.0/24", cost=10), dict(address="192.0.4.0/24", cost=12), ], ), ], ), state="merged", ), ) commands = [ "set protocols ospf mpls-te enable", "set protocols ospf mpls-te router-address '192.0.11.11'", "set protocols ospf auto-cost reference-bandwidth '2'", "set protocols ospf log-adjacency-changes 'detail'", "set protocols ospf area '2'", "set protocols ospf area 2 authentication plaintext-password", "set protocols ospf area 2 shortcut enable", "set protocols ospf area 2 area-type normal", "set protocols ospf area 4 range 192.0.3.0/24 cost 10", "set protocols ospf area 4 range 192.0.3.0/24", "set protocols ospf area 4 range 192.0.4.0/24 cost 12", "set protocols ospf area 4 range 192.0.4.0/24", "set protocols ospf area 4 area-type stub default-cost 10", "set protocols ospf area '4'", "set protocols ospf area 4 network 192.0.2.0/24", ] self.execute_module(changed=True, commands=commands) def test_vyos_ospfv2_merged_idem(self): set_module_args( dict( config=dict( areas=[ dict( area_id="12", area_type=dict(normal=True), authentication="plaintext-password", shortcut="enable", ), dict( area_id="14", area_type=dict(stub=dict(default_cost=20)), network=[dict(address="192.0.12.0/24")], range=[ dict(address="192.0.13.0/24", cost=10), dict(address="192.0.14.0/24", cost=12), ], ), ], ), state="merged", ), ) self.execute_module(changed=False, commands=[]) def test_vyos_ospfv2_merged_update_existing(self): set_module_args( dict( config=dict( areas=[ dict( area_id="12", area_type=dict(normal=True), authentication="plaintext-password", shortcut="enable", ), dict( area_id="14", area_type=dict(stub=dict(set=False)), network=[ dict(address="192.0.12.0/24"), dict(address="192.0.22.0/24"), ], range=[ dict(address="192.0.13.0/24", cost=10), dict(address="192.0.14.0/24", cost=12), ], ), ], ), state="merged", ), ) commands = [ "delete protocols ospf area 14 area-type stub", "set protocols ospf area 14 network 192.0.22.0/24", ] self.execute_module(changed=True, commands=commands) def test_vyos_ospfv2_replaced(self): set_module_args( dict( config=dict( log_adjacency_changes="detail", mpls_te=dict(enabled=True, router_address="192.0.11.11"), auto_cost=dict(reference_bandwidth=2), areas=[ dict( area_id="12", area_type=dict(normal=True), authentication="plaintext-password", shortcut="enable", ), dict( area_id="15", area_type=dict(stub=dict(default_cost=10)), network=[dict(address="192.0.12.0/24")], range=[ dict(address="192.0.13.0/24", cost=10), dict(address="192.0.14.0/24", cost=12), dict(address="192.0.15.0/24", cost=14), ], ), ], ), state="replaced", ), ) commands = [ "set protocols ospf mpls-te enable", "set protocols ospf mpls-te router-address '192.0.11.11'", "set protocols ospf auto-cost reference-bandwidth '2'", "set protocols ospf log-adjacency-changes 'detail'", "delete protocols ospf area 14", "set protocols ospf area 15 range 192.0.13.0/24 cost 10", "set protocols ospf area 15 range 192.0.13.0/24", "set protocols ospf area 15 range 192.0.14.0/24 cost 12", "set protocols ospf area 15 range 192.0.14.0/24", "set protocols ospf area 15 range 192.0.15.0/24 cost 14", "set protocols ospf area 15 range 192.0.15.0/24", "set protocols ospf area 15 area-type stub default-cost 10", "set protocols ospf area '15'", "set protocols ospf area 15 network 192.0.12.0/24", ] self.execute_module(changed=True, commands=commands) def test_vyos_ospfv2_replaced_idem(self): set_module_args( dict( config=dict( areas=[ dict( area_id="12", area_type=dict(normal=True), authentication="plaintext-password", shortcut="enable", ), dict( area_id="14", area_type=dict(stub=dict(default_cost=20)), network=[dict(address="192.0.12.0/24")], range=[ dict(address="192.0.13.0/24", cost=10), dict(address="192.0.14.0/24", cost=12), ], ), ], ), state="replaced", ), ) self.execute_module(changed=False, commands=[]) def test_vyos_ospfv2_deleted_no_config(self): set_module_args(dict(config=None, state="deleted")) commands = ["delete protocols ospf"] self.execute_module(changed=True, commands=commands) def test_vyos_ospfv2_gathered(self): set_module_args(dict(state="gathered")) result = self.execute_module(changed=False, filename="vyos_ospfv2_config.cfg") gather_dict = { "areas": [ { "area_id": "2", "area_type": {"normal": True}, "authentication": "plaintext-password", "shortcut": "enable", }, { "area_id": "14", "area_type": {"stub": {"default_cost": 20, "set": True}}, "network": [{"address": "192.0.12.0/24"}], "range": [ {"address": "192.0.13.0/24", "cost": 10}, {"address": "192.0.14.0/24", "cost": 12}, ], }, ], } self.assertEqual(sorted(gather_dict), sorted(result["gathered"])) def test_vyos_ospfv2_parsed(self): parsed_str = """set protocols ospf area 2 area-type 'normal' set protocols ospf area 2 authentication 'plaintext-password' set protocols ospf area 2 shortcut 'enable' set protocols ospf area 3 area-type 'nssa' set protocols ospf area 4 area-type stub default-cost '20' set protocols ospf area 4 network '192.0.2.0/24' set protocols ospf area 4 range 192.0.3.0/24 cost '10' set protocols ospf area 4 range 192.0.4.0/24 cost '12' set protocols ospf default-information originate 'always' set protocols ospf default-information originate metric '10' set protocols ospf default-information originate metric-type '2' set protocols ospf auto-cost reference-bandwidth '2' set protocols ospf default-information originate route-map 'ingress' set protocols ospf log-adjacency-changes 'detail' set protocols ospf max-metric router-lsa 'administrative' set protocols ospf max-metric router-lsa on-shutdown '10' set protocols ospf max-metric router-lsa on-startup '10' set protocols ospf mpls-te 'enable' set protocols ospf mpls-te router-address '192.0.11.11' set protocols ospf neighbor 192.0.11.12 poll-interval '10' set protocols ospf neighbor 192.0.11.12 priority '2' set protocols ospf parameters abr-type 'cisco' set protocols ospf parameters 'opaque-lsa' set protocols ospf parameters 'rfc1583-compatibility' set protocols ospf parameters router-id '192.0.1.1' -set protocols ospf passive-interface 'eth1' -set protocols ospf passive-interface 'eth2' +set protocols ospf interface 'eth1' passive +set protocols ospf interface 'eth2' passive set protocols ospf redistribute bgp metric '10' set protocols ospf redistribute bgp metric-type '2'""" set_module_args(dict(running_config=parsed_str, state="parsed")) result = self.execute_module(changed=False) parsed_list = { "areas": [ { "area_id": "2", "area_type": {"normal": True}, "authentication": "plaintext-password", "shortcut": "enable", }, {"area_id": "3", "area_type": {"nssa": {"set": True}}}, { "area_id": "4", "area_type": {"stub": {"default_cost": 20, "set": True}}, "network": [{"address": "192.0.2.0/24"}], "range": [ {"address": "192.0.3.0/24", "cost": 10}, {"address": "192.0.4.0/24", "cost": 12}, ], }, ], "auto_cost": {"reference_bandwidth": 2}, "default_information": { "originate": { "always": True, "metric": 10, "metric_type": 2, "route_map": "ingress", }, }, "log_adjacency_changes": "detail", "max_metric": { "router_lsa": { "administrative": True, "on_shutdown": 10, "on_startup": 10, }, }, "mpls_te": {"enabled": True, "router_address": "192.0.11.11"}, "neighbor": [ { "neighbor_id": "192.0.11.12", "poll_interval": 10, "priority": 2, }, ], "parameters": { "abr_type": "cisco", "opaque_lsa": True, "rfc1583_compatibility": True, "router_id": "192.0.1.1", }, "passive_interface": ["eth2", "eth1"], "redistribute": [{"metric": 10, "metric_type": 2, "route_type": "bgp"}], } self.assertEqual(sorted(parsed_list), sorted(result["parsed"])) def test_vyos_ospfv2_rendered(self): set_module_args( dict( config=dict( log_adjacency_changes="detail", mpls_te=dict(enabled=True, router_address="192.0.11.11"), auto_cost=dict(reference_bandwidth=2), areas=[ dict( area_id="2", area_type=dict(normal=True), authentication="plaintext-password", shortcut="enable", ), dict( area_id="4", area_type=dict(stub=dict(default_cost=10)), network=[dict(address="192.0.2.0/24")], range=[ dict(address="192.0.3.0/24", cost=10), dict(address="192.0.4.0/24", cost=12), ], ), ], ), state="rendered", ), ) commands = [ "set protocols ospf mpls-te enable", "set protocols ospf mpls-te router-address '192.0.11.11'", "set protocols ospf auto-cost reference-bandwidth '2'", "set protocols ospf log-adjacency-changes 'detail'", "set protocols ospf area '2'", "set protocols ospf area 2 authentication plaintext-password", "set protocols ospf area 2 shortcut enable", "set protocols ospf area 2 area-type normal", "set protocols ospf area 4 range 192.0.3.0/24 cost 10", "set protocols ospf area 4 range 192.0.3.0/24", "set protocols ospf area 4 range 192.0.4.0/24 cost 12", "set protocols ospf area 4 range 192.0.4.0/24", "set protocols ospf area 4 area-type stub default-cost 10", "set protocols ospf area '4'", "set protocols ospf area 4 network 192.0.2.0/24", ] result = self.execute_module(changed=False) self.assertEqual(sorted(result["rendered"]), sorted(commands), result["rendered"])