diff --git a/plugins/module_utils/network/vyos/argspec/ospfv2/__init__.py b/plugins/module_utils/network/vyos/argspec/ospfv2/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/plugins/module_utils/network/vyos/argspec/ospfv2/ospfv2.py b/plugins/module_utils/network/vyos/argspec/ospfv2/ospfv2.py new file mode 100644 index 0000000..275aaf3 --- /dev/null +++ b/plugins/module_utils/network/vyos/argspec/ospfv2/ospfv2.py @@ -0,0 +1,269 @@ +# +# -*- coding: utf-8 -*- +# Copyright 2019 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +############################################# +# WARNING # +############################################# +# +# This file is auto generated by the resource +# module builder playbook. +# +# Do not edit this file manually. +# +# Changes to this file will be over written +# by the resource module builder. +# +# Changes should be made in the model used to +# generate this file or in the resource module +# builder template. +# +############################################# +""" +The arg spec for the vyos_ospfv2 module +""" + + +class Ospfv2Args(object): # pylint: disable=R0903 + """The arg spec for the vyos_ospfv2 module + """ + + def __init__(self, **kwargs): + pass + + argument_spec = { + "config": { + "options": { + "auto_cost": { + "options": {"reference_bandwidth": {"type": "int"}}, + "type": "dict", + }, + "default_information": { + "options": { + "originate": { + "options": { + "always": {"type": "bool"}, + "metric": {"type": "int"}, + "metric_type": {"type": "int"}, + "route_map": {"type": "str"}, + }, + "type": "dict", + } + }, + "type": "dict", + }, + "default_metric": {"type": "int"}, + "distance": { + "options": { + "global": {"type": "int"}, + "ospf": { + "options": { + "external": {"type": "int"}, + "inter_area": {"type": "int"}, + "intra_area": {"type": "int"}, + }, + "type": "dict", + }, + }, + "type": "dict", + }, + "log_adjacency_changes": { + "choices": ["detail"], + "type": "str", + }, + "max_metric": { + "options": { + "router_lsa": { + "options": { + "administrative": {"type": "bool"}, + "on_shutdown": {"type": "int"}, + "on_startup": {"type": "int"}, + }, + "type": "dict", + } + }, + "type": "dict", + }, + "mpls_te": { + "options": { + "enabled": {"type": "bool"}, + "router_address": {"type": "str"}, + }, + "type": "dict", + }, + "neighbor": { + "elements": "dict", + "options": { + "neighbor_id": {"type": "str"}, + "poll_interval": {"type": "int"}, + "priority": {"type": "int"}, + }, + "type": "list", + }, + "areas": { + "elements": "dict", + "options": { + "area_id": {"type": "str"}, + "area_type": { + "options": { + "normal": {"type": "bool"}, + "nssa": { + "options": { + "default_cost": {"type": "int"}, + "no_summary": {"type": "bool"}, + "set": {"type": "bool"}, + "translate": { + "choices": [ + "always", + "candidate", + "never", + ], + "type": "str", + }, + }, + "type": "dict", + }, + "stub": { + "options": { + "default_cost": {"type": "int"}, + "no_summary": {"type": "bool"}, + "set": {"type": "bool"}, + }, + "type": "dict", + }, + }, + "type": "dict", + }, + "authentication": { + "choices": ["plaintext-password", "md5"], + "type": "str", + }, + "network": { + "elements": "dict", + "options": { + "address": {"required": True, "type": "str"} + }, + "type": "list", + }, + "range": { + "elements": "dict", + "options": { + "address": {"type": "str"}, + "cost": {"type": "int"}, + "not_advertise": {"type": "bool"}, + "substitute": {"type": "str"}, + }, + "type": "list", + }, + "shortcut": { + "choices": ["default", "disable", "enable"], + "type": "str", + }, + "virtual_link": { + "elements": "dict", + "options": { + "address": {"type": "str"}, + "authentication": { + "options": { + "md5": { + "elements": "dict", + "options": { + "key_id": {"type": "int"}, + "md5_key": {"type": "str"}, + }, + "type": "list", + }, + "plaintext_password": {"type": "str"}, + }, + "type": "dict", + }, + "dead_interval": {"type": "int"}, + "hello_interval": {"type": "int"}, + "retransmit_interval": {"type": "int"}, + "transmit_delay": {"type": "int"}, + }, + "type": "list", + }, + }, + "type": "list", + }, + "parameters": { + "options": { + "abr_type": { + "choices": [ + "cisco", + "ibm", + "shortcut", + "standard", + ], + "type": "str", + }, + "opaque_lsa": {"type": "bool"}, + "rfc1583_compatibility": {"type": "bool"}, + "router_id": {"type": "str"}, + }, + "type": "dict", + }, + "passive_interface": {"type": "list"}, + "passive_interface_exclude": {"type": "list"}, + "redistribute": { + "elements": "dict", + "options": { + "metric": {"type": "int"}, + "metric_type": {"type": "int"}, + "route_map": {"type": "str"}, + "route_type": { + "choices": [ + "bgp", + "connected", + "kernel", + "rip", + "static", + ], + "type": "str", + }, + }, + "type": "list", + }, + "route_map": {"type": "list"}, + "timers": { + "options": { + "refresh": { + "options": {"timers": {"type": "int"}}, + "type": "dict", + }, + "throttle": { + "options": { + "spf": { + "options": { + "delay": {"type": "int"}, + "initial_holdtime": {"type": "int"}, + "max_holdtime": {"type": "int"}, + }, + "type": "dict", + } + }, + "type": "dict", + }, + }, + "type": "dict", + }, + }, + "type": "dict", + }, + "running_config": {"type": "str"}, + "state": { + "choices": [ + "merged", + "replaced", + "deleted", + "parsed", + "gathered", + "rendered", + ], + "default": "merged", + "type": "str", + }, + } # pylint: disable=C0301 diff --git a/plugins/module_utils/network/vyos/config/ospfv2/__init__.py b/plugins/module_utils/network/vyos/config/ospfv2/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/plugins/module_utils/network/vyos/config/ospfv2/ospfv2.py b/plugins/module_utils/network/vyos/config/ospfv2/ospfv2.py new file mode 100644 index 0000000..fd25c17 --- /dev/null +++ b/plugins/module_utils/network/vyos/config/ospfv2/ospfv2.py @@ -0,0 +1,949 @@ +# +# -*- 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_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base import ( + ConfigBase, +) +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ( + to_list, + remove_empties, +) +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.facts import ( + Facts, +) +from ansible.module_utils.six import iteritems + +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.utils import ( + list_diff_want_only, + _in_target, + _is_w_same, + _bool_to_str, +) + + +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) + 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"] + 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 + ) + 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 + elif 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/facts.py b/plugins/module_utils/network/vyos/facts/facts.py index 3c87be6..4c7b340 100644 --- a/plugins/module_utils/network/vyos/facts/facts.py +++ b/plugins/module_utils/network/vyos/facts/facts.py @@ -1,96 +1,100 @@ # Copyright 2019 Red Hat # GNU General Public License v3.0+ # (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) """ The facts class for vyos this file validates each subset of facts and selectively calls the appropriate facts gathering function """ from __future__ import absolute_import, division, print_function __metaclass__ = type from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.facts.facts import ( FactsBase, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.interfaces.interfaces import ( InterfacesFacts, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.l3_interfaces.l3_interfaces import ( L3_interfacesFacts, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.lag_interfaces.lag_interfaces import ( Lag_interfacesFacts, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.lldp_global.lldp_global import ( Lldp_globalFacts, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.lldp_interfaces.lldp_interfaces import ( Lldp_interfacesFacts, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.firewall_rules.firewall_rules import ( Firewall_rulesFacts, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.static_routes.static_routes import ( Static_routesFacts, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.firewall_global.firewall_global import ( Firewall_globalFacts, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.firewall_interfaces.firewall_interfaces import ( Firewall_interfacesFacts, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.ospfv3.ospfv3 import ( Ospfv3Facts, ) +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.ospfv2.ospfv2 import ( + Ospfv2Facts, +) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.legacy.base import ( Default, Neighbors, Config, ) FACT_LEGACY_SUBSETS = dict(default=Default, neighbors=Neighbors, config=Config) FACT_RESOURCE_SUBSETS = dict( interfaces=InterfacesFacts, l3_interfaces=L3_interfacesFacts, lag_interfaces=Lag_interfacesFacts, lldp_global=Lldp_globalFacts, lldp_interfaces=Lldp_interfacesFacts, static_routes=Static_routesFacts, firewall_rules=Firewall_rulesFacts, firewall_global=Firewall_globalFacts, firewall_interfaces=Firewall_interfacesFacts, ospfv3=Ospfv3Facts, + ospfv2=Ospfv2Facts, ) class Facts(FactsBase): """ The fact class for vyos """ VALID_LEGACY_GATHER_SUBSETS = frozenset(FACT_LEGACY_SUBSETS.keys()) VALID_RESOURCE_SUBSETS = frozenset(FACT_RESOURCE_SUBSETS.keys()) def __init__(self, module): super(Facts, self).__init__(module) def get_facts( self, legacy_facts_type=None, resource_facts_type=None, data=None ): """ Collect the facts for vyos :param legacy_facts_type: List of legacy facts types :param resource_facts_type: List of resource fact types :param data: previously collected conf :rtype: dict :return: the facts gathered """ if self.VALID_RESOURCE_SUBSETS: self.get_network_resources_facts( FACT_RESOURCE_SUBSETS, resource_facts_type, data ) if self.VALID_LEGACY_GATHER_SUBSETS: self.get_network_legacy_facts( FACT_LEGACY_SUBSETS, legacy_facts_type ) return self.ansible_facts, self._warnings diff --git a/plugins/module_utils/network/vyos/facts/ospfv2/__init__.py b/plugins/module_utils/network/vyos/facts/ospfv2/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/plugins/module_utils/network/vyos/facts/ospfv2/ospfv2.py b/plugins/module_utils/network/vyos/facts/ospfv2/ospfv2.py new file mode 100644 index 0000000..d62fa9a --- /dev/null +++ b/plugins/module_utils/network/vyos/facts/ospfv2/ospfv2.py @@ -0,0 +1,499 @@ +# +# -*- 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 re import findall, search, M +from copy import deepcopy +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, +) + + +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["redistribute"] = self.parse_attrib_list( + conf, "redistribute", "route_type" + ) + config["passive_interface_exclude"] = self.parse_leaf_list( + 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_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'") + if out >= 1: + if dis >= 1: + config[attrib] = False + else: + config[attrib] = True + elif match and en >= 1: + 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/utils/utils.py b/plugins/module_utils/network/vyos/utils/utils.py index 96feddd..7e0f3cc 100644 --- a/plugins/module_utils/network/vyos/utils/utils.py +++ b/plugins/module_utils/network/vyos/utils/utils.py @@ -1,269 +1,269 @@ # -*- 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.six import iteritems from ansible_collections.ansible.netcommon.plugins.module_utils.compat import ( ipaddress, ) 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" 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: """ try: address = unicode(address) except NameError: address = str(address) version = ipaddress.ip_address(address.split("/")[0]).version return version def get_route_type(address): """ This function returns the route type based on IP address :param address: :return: """ version = get_ip_address_version(address) if version == 6: return "route6" elif version == 4: return "route" def _bool_to_str(val): """ This function converts the bool value into string. :param val: bool value. :return: enable/disable. """ return ( "enable" if str(val) == "True" else "disable" if str(val) == "False" else val ) def _is_w_same(w, h, key): """ This function checks whether the key value is same in desired and target config dictionary. :param w: base config. :param h: target config. :param key:attribute name. :return: True/False. """ return True if h and key in h and h[key] == w[key] else False def _in_target(h, key): """ This function checks whether the target exist and key present in target config. :param h: target config. :param key: attribute name. :return: True/False. """ return True if h and key in h else False diff --git a/plugins/modules/vyos_facts.py b/plugins/modules/vyos_facts.py index 5849519..72b191c 100644 --- a/plugins/modules/vyos_facts.py +++ b/plugins/modules/vyos_facts.py @@ -1,175 +1,175 @@ #!/usr/bin/python # -*- coding: utf-8 -*- # Copyright 2019 Red Hat # GNU General Public License v3.0+ # (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) """ The module file for vyos_facts """ ANSIBLE_METADATA = { "metadata_version": "1.1", "status": [u"preview"], "supported_by": "network", } DOCUMENTATION = """module: vyos_facts short_description: Get facts about vyos devices. description: - Collects facts from network devices running the vyos operating system. This module places the facts gathered in the fact tree keyed by the respective resource name. The facts module will always collect a base set of facts from the device and can enable or disable collection of additional facts. author: - Nathaniel Case (@qalthos) - Nilashish Chakraborty (@Nilashishc) - Rohit Thakur (@rohitthakur2590) extends_documentation_fragment: - vyos.vyos.vyos notes: - Tested against VyOS 1.1.8 (helium). - This module works with connection C(network_cli). See L(the VyOS OS Platform Options,../network/user_guide/platform_vyos.html). options: gather_subset: description: - When supplied, this argument will restrict the facts collected to a given subset. Possible values for this argument include all, default, config, and neighbors. Can specify a list of values to include a larger subset. Values can also be used with an initial C(M(!)) to specify that a specific subset should not be collected. required: false default: '!config' gather_network_resources: description: - When supplied, this argument will restrict the facts collected to a given subset. Possible values for this argument include all and the resources like interfaces. Can specify a list of values to include a larger subset. Values can also be used with an initial C(M(!)) to specify that a specific subset should not be collected. Valid subsets are 'all', 'interfaces', 'l3_interfaces', 'lag_interfaces', 'lldp_global', 'lldp_interfaces', 'static_routes', 'firewall_rules', 'firewall_global', - 'firewall_interfaces', 'ospfv3'. + 'firewall_interfaces', 'ospfv3', 'ospfv2'. required: false """ EXAMPLES = """ # Gather all facts - vyos_facts: gather_subset: all gather_network_resources: all # collect only the config and default facts - vyos_facts: gather_subset: config # collect everything exception the config - vyos_facts: gather_subset: "!config" # Collect only the interfaces facts - vyos_facts: gather_subset: - '!all' - '!min' gather_network_resources: - interfaces # Do not collect interfaces facts - vyos_facts: gather_network_resources: - "!interfaces" # Collect interfaces and minimal default facts - vyos_facts: gather_subset: min gather_network_resources: interfaces """ RETURN = """ ansible_net_config: description: The running-config from the device returned: when config is configured type: str ansible_net_commits: description: The set of available configuration revisions returned: when present type: list ansible_net_hostname: description: The configured system hostname returned: always type: str ansible_net_model: description: The device model string returned: always type: str ansible_net_serialnum: description: The serial number of the device returned: always type: str ansible_net_version: description: The version of the software running returned: always type: str ansible_net_neighbors: description: The set of LLDP neighbors returned: when interface is configured type: list ansible_net_gather_subset: description: The list of subsets gathered by the module returned: always type: list ansible_net_api: description: The name of the transport returned: always type: str ansible_net_python_version: description: The Python version Ansible controller is using returned: always type: str ansible_net_gather_network_resources: description: The list of fact resource subsets collected from the device returned: always type: list """ from ansible.module_utils.basic import AnsibleModule from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.argspec.facts.facts import ( FactsArgs, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.facts import ( Facts, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.vyos import ( vyos_argument_spec, ) def main(): """ Main entry point for module execution :returns: ansible_facts """ argument_spec = FactsArgs.argument_spec argument_spec.update(vyos_argument_spec) module = AnsibleModule( argument_spec=argument_spec, supports_check_mode=True ) warnings = [] if module.params["gather_subset"] == "!config": warnings.append( "default value for `gather_subset` will be changed to `min` from `!config` v2.11 onwards" ) result = Facts(module).get_facts() ansible_facts, additional_warnings = result warnings.extend(additional_warnings) module.exit_json(ansible_facts=ansible_facts, warnings=warnings) if __name__ == "__main__": main() diff --git a/plugins/modules/vyos_ospfv2.py b/plugins/modules/vyos_ospfv2.py new file mode 100644 index 0000000..9c40d9c --- /dev/null +++ b/plugins/modules/vyos_ospfv2.py @@ -0,0 +1,1814 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright 2019 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +############################################# +# WARNING # +############################################# +# +# This file is auto generated by the resource +# module builder playbook. +# +# Do not edit this file manually. +# +# Changes to this file will be over written +# by the resource module builder. +# +# Changes should be made in the model used to +# generate this file or in the resource module +# builder template. +# +############################################# + +""" +The module file for vyos_ospfv2 +""" + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "supported_by": "Ansible"} + +DOCUMENTATION = """ +--- +module: vyos_ospfv2 +version_added: 2.10 +short_description: OSPFV2 resource module +description: This resource module configures and manages attributes of OSPFv2 routes on VyOS network devices. +version_added: "1.0.0" +notes: + - Tested against VyOS 1.1.8 (helium). + - This module works with connection C(network_cli). See L(the VyOS OS Platform Options,../network/user_guide/platform_vyos.html). +author: + - Rohit Thakur (@rohitthakur2590) +options: + config: + description: A provided OSPFv2 route configuration. + type: dict + suboptions: + areas: + description: OSPFv2 area. + type: list + elements: dict + suboptions: + area_id: + description: OSPFv2 area identity. + type: str + area_type: + description: Area type. + type: dict + suboptions: + normal: + description: Normal OSPFv2 area. + type: bool + nssa: + description: NSSA OSPFv2 area. + type: dict + suboptions: + set: + description: Enabling NSSA. + type: bool + default_cost: + description: Summary-default cost of NSSA area. + type: int + no_summary: + description: Do not inject inter-area routes into stub. + type: bool + translate: + description: NSSA-ABR. + type: str + choices: ['always', 'candidate', 'never'] + stub: + description: Stub OSPFv2 area. + type: dict + suboptions: + set: + description: Enabling stub. + type: bool + default_cost: + description: Summary-default cost of stub area. + type: int + no_summary: + description: Do not inject inter-area routes into stub. + type: bool + authentication: + description: OSPFv2 area authentication type. + type: str + choices: ['plaintext-password', 'md5'] + network: + description: OSPFv2 network. + type: list + elements: dict + suboptions: + address: + required: True + description: OSPFv2 IPv4 network address. + type: str + range: + description: Summarize routes matching prefix (border routers only). + type: list + elements: dict + suboptions: + address: + description: border router IPv4 address. + type: str + cost: + description: Metric for this range. + type: int + not_advertise: + description: Don't advertise this range. + type: bool + substitute: + description: Announce area range (IPv4 address) as another prefix. + type: str + shortcut: + description: Area's shortcut mode. + type: str + choices: ['default', 'disable', 'enable'] + virtual_link: + description: Virtual link address. + type: list + elements: dict + suboptions: + address: + description: virtual link address. + type: str + authentication: + description: OSPFv2 area authentication type. + type: dict + suboptions: + md5: + description: MD5 key id based authentication. + type: list + elements: dict + suboptions: + key_id: + description: MD5 key id. + type: int + md5_key: + description: MD5 key. + type: str + plaintext_password: + description: Plain text password. + type: str + dead_interval: + description: Interval after which a neighbor is declared dead. + type: int + hello_interval: + description: Interval between hello packets. + type: int + retransmit_interval: + description: Interval between retransmitting lost link state advertisements. + type: int + transmit_delay: + description: Link state transmit delay. + type: int + log_adjacency_changes: + description: Log changes in adjacency state. + type: str + choices: ['detail'] + max_metric: + description: OSPFv2 maximum/infinite-distance metric. + type: dict + suboptions: + router_lsa: + description: Advertise own Router-LSA with infinite distance (stub router). + type: dict + suboptions: + administrative: + description: Administratively apply, for an indefinite period. + type: bool + on_shutdown: + description: Time to advertise self as stub-router. + type: int + on_startup: + description: Time to advertise self as stub-router + type: int + auto_cost: + description: Calculate OSPFv2 interface cost according to bandwidth. + type: dict + suboptions: + reference_bandwidth: + description: Reference bandwidth cost in Mbits/sec. + type: int + default_information: + description: Control distribution of default information. + type: dict + suboptions: + originate: + description: Distribute a default route. + type: dict + suboptions: + always: + description: Always advertise default route. + type: bool + metric: + description: OSPFv2 default metric. + type: int + metric_type: + description: OSPFv2 Metric types for default routes. + type: int + route_map: + description: Route map references. + type: str + default_metric: + description: Metric of redistributed routes + type: int + distance: + description: Administrative distance. + type: dict + suboptions: + global: + description: Global OSPFv2 administrative distance. + type: int + ospf: + description: OSPFv2 administrative distance. + type: dict + suboptions: + external: + description: Distance for external routes. + type: int + inter_area: + description: Distance for inter-area routes. + type: int + intra_area: + description: Distance for intra-area routes. + type: int + mpls_te: + description: MultiProtocol Label Switching-Traffic Engineering (MPLS-TE) parameters. + type: dict + suboptions: + enabled: + description: Enable MPLS-TE functionality. + type: bool + router_address: + description: Stable IP address of the advertising router. + type: str + + neighbor: + description: Neighbor IP address. + type: list + elements: dict + suboptions: + neighbor_id: + description: Identity (number/IP address) of neighbor. + type: str + poll_interval: + description: Seconds between dead neighbor polling interval. + type: int + priority: + description: Neighbor priority. + type: int + parameters: + descriptions: OSPFv2 specific parameters. + type: dict + suboptions: + abr_type: + description: OSPFv2 ABR Type. + type: str + choices: ['cisco', 'ibm', 'shortcut', 'standard'] + opaque_lsa: + description: Enable the Opaque-LSA capability (rfc2370). + type: bool + rfc1583_compatibility: + description: Enable rfc1583 criteria for handling AS external routes. + type: bool + router_id: + description: Override the default router identifier. + type: str + passive_interface: + description: Suppress routing updates on an interface. + type: list + passive_interface_exclude: + description: Interface to exclude when using passive-interface default. + type: list + redistribute: + description: Redistribute information from another routing protocol. + type: list + elements: dict + suboptions: + route_type: + description: Route type to redistribute. + type: str + choices: ['bgp', 'connected', 'kernel', 'rip', 'static'] + metric: + description: Metric for redistribution routes. + type: int + metric_type: + description: OSPFv2 Metric types. + type: int + route_map: + description: Route map references. + type: str + route_map: + description: Filter routes installed in local route map. + type: list + timers: + description: Adjust routing timers. + type: dict + suboptions: + refresh: + description: Adjust refresh parameters. + type: dict + suboptions: + timers: + description: refresh timer. + type: int + throttle: + description: Throttling adaptive timers. + type: dict + suboptions: + spf: + description: OSPFv2 SPF timers. + type: dict + suboptions: + delay: + description: Delay (msec) from first change received till SPF calculation. + type: int + initial_holdtime: + description: Initial hold time(msec) between consecutive SPF calculations. + type: int + max_holdtime: + description: maximum hold time (sec). + type: int + running_config: + description: + - This option is used only with state I(parsed). + - The value of this option should be the output received from the VyOS device by executing + the command B(show configuration commands | grep ospf). + - The state I(parsed) reads the configuration from C(running_config) option and transforms + it into Ansible structured data as per the resource module's argspec and the value is then + returned in the I(parsed) key within the result. + type: str + state: + description: + - The state the configuration should be left in. + type: str + choices: + - merged + - replaced + - deleted + - parsed + - gathered + - rendered + default: merged +""" +EXAMPLES = """ +# Using merged +# +# Before state: +# ------------- +# +# vyos@vyos# run show configuration commands | grep ospf +# +# +- name: Merge the provided configuration with the existing running configuration + vyos.vyos.vyos_ospfv2: + config: + log_adjacency_changes: 'detail' + max_metric: + router_lsa: + administrative: true + on_shutdown: 10 + on_startup: 10 + default_information: + originate: + always: true + metric: 10 + metric_type: 2 + route_map: 'ingress' + mpls_te: + enabled: true + router_address: '192.0.11.11' + auto_cost: + reference_bandwidth: 2 + neighbor: + - neighbor_id: '192.0.11.12' + poll_interval: 10 + priority: 2 + redistribute: + - route_type: 'bgp' + metric: 10 + metric_type: 2 + passive_interface: + - 'eth1' + - 'eth2' + parameters: + router_id: '192.0.1.1' + opaque_lsa: true + rfc1583_compatibility: true + abr_type: 'cisco' + 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 + network: + - address: '192.0.2.0/24' + range: + - address: '192.0.3.0/24' + cost: 10 + - address: '192.0.4.0/24' + cost: 12 + state: merged +# +# +# ------------------------- +# Module Execution Result +# ------------------------- +# +# 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" +# ] +# +# "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": [ +# "eth2", +# "eth1" +# ], +# "redistribute": [ +# { +# "metric": 10, +# "metric_type": 2, +# "route_type": "bgp" +# } +# ] +# } +# +# After state: +# ------------- +# +# vyos@192# run show configuration commands | grep ospf +# 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 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 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' + + +# Using merged +# +# Before state: +# ------------- +# +# vyos@vyos# run show configuration commands | grep ospf +# +# +- name: Merge the provided configuration to update existing running configuration + vyos.vyos.vyos_ospfv2: + config: + areas: + - area_id: '2' + area_type: + normal: true + authentication: "plaintext-password" + shortcut: 'enable' + - area_id: '3' + area_type: + nssa: + set: false + - area_id: '4' + area_type: + stub: + default_cost: 20 + network: + - address: '192.0.2.0/24' + - address: '192.0.22.0/24' + - address: '192.0.32.0/24' + state: merged +# +# +# ------------------------- +# Module Execution Result +# ------------------------- +# +# "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": [ +# "eth2", +# "eth1" +# ], +# "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" +# ] +# +# "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": [ +# "eth2", +# "eth1" +# ], +# "redistribute": [ +# { +# "metric": 10, +# "metric_type": 2, +# "route_type": "bgp" +# } +# ] +# } +# +# After state: +# ------------- +# +# vyos@192# run show configuration commands | grep ospf +# 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 network '192.0.2.0/24' +# set protocols ospf area 4 network '192.0.22.0/24' +# set protocols ospf area 4 network '192.0.32.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 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' + + +# Using replaced +# +# Before state: +# ------------- +# +# vyos@192# run show configuration commands | grep ospf +# 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 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 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' +# +- name: Replace ospfv2 routes attributes configuration. + vyos.vyos.vyos_ospfv2: + config: + log_adjacency_changes: 'detail' + max_metric: + router_lsa: + administrative: true + on_shutdown: 10 + on_startup: 10 + default_information: + originate: + always: true + metric: 10 + metric_type: 2 + route_map: 'ingress' + mpls_te: + enabled: true + router_address: '192.0.22.22' + auto_cost: + reference_bandwidth: 2 + neighbor: + - neighbor_id: '192.0.11.12' + poll_interval: 10 + priority: 2 + redistribute: + - route_type: 'bgp' + metric: 10 + metric_type: 2 + passive_interface: + - 'eth1' + parameters: + router_id: '192.0.1.1' + opaque_lsa: true + rfc1583_compatibility: true + abr_type: 'cisco' + areas: + - area_id: '2' + area_type: + normal: true + authentication: "plaintext-password" + shortcut: 'enable' + - area_id: '4' + area_type: + stub: + default_cost: 20 + network: + - address: '192.0.2.0/24' + - address: '192.0.12.0/24' + - address: '192.0.22.0/24' + - address: '192.0.32.0/24' + range: + - address: '192.0.42.0/24' + cost: 10 + state: replaced +# +# +# ------------------------- +# Module Execution Result +# ------------------------- +# +# "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": [ +# "eth2", +# "eth1" +# ], +# "redistribute": [ +# { +# "metric": 10, +# "metric_type": 2, +# "route_type": "bgp" +# } +# ] +# } +# +# "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 192.0.42.0/24 cost 10", +# "set protocols ospf area 4 range 192.0.42.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" +# ] +# +# "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": "192.0.42.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" +# } +# ] +# } +# +# After state: +# ------------- +# +# vyos@192# run show configuration commands | grep ospf +# 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 4 area-type stub default-cost '20' +# set protocols ospf area 4 network '192.0.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' +# set protocols ospf area 4 range 192.0.42.0/24 cost '10' +# 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 router-address '192.0.22.22' +# 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 redistribute bgp metric '10' +# set protocols ospf redistribute bgp metric-type '2' + + +# Using rendered +# +# +- name: Render the commands for provided configuration + vyos.vyos.vyos_ospfv2: + config: + log_adjacency_changes: 'detail' + max_metric: + router_lsa: + administrative: true + on_shutdown: 10 + on_startup: 10 + default_information: + originate: + always: true + metric: 10 + metric_type: 2 + route_map: 'ingress' + mpls_te: + enabled: true + router_address: '192.0.11.11' + auto_cost: + reference_bandwidth: 2 + neighbor: + - neighbor_id: '192.0.11.12' + poll_interval: 10 + priority: 2 + redistribute: + - route_type: 'bgp' + metric: 10 + metric_type: 2 + passive_interface: + - 'eth1' + - 'eth2' + parameters: + router_id: '192.0.1.1' + opaque_lsa: true + rfc1583_compatibility: true + abr_type: 'cisco' + 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 + network: + - address: '192.0.2.0/24' + range: + - address: '192.0.3.0/24' + cost: 10 + - address: '192.0.4.0/24' + cost: 12 + state: rendered +# +# +# ------------------------- +# Module Execution Result +# ------------------------- +# +# +# "rendered": [ +# [ +# "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" +# ] + + +# Using parsed +# +# +- name: Parse the commands for provided structured configuration + vyos.vyos.vyos_ospfv2: + running_config: + "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 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 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'" + state: parsed +# +# +# ------------------------- +# Module Execution Result +# ------------------------- +# +# +# "parsed": { +# "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" +# } +# ] +# } +# } + + +# Using gathered +# +# Before state: +# ------------- +# +# vyos@192# run show configuration commands | grep ospf +# 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 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 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' +# +- name: Gather ospfv2 routes config with provided configurations + vyos.vyos.vyos_ospfv2: + config: + state: gathered +# +# +# ------------------------- +# Module Execution Result +# ------------------------- +# +# "gathered": { +# "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" +# } +# ] +# } +# +# After state: +# ------------- +# +# vyos@192# run show configuration commands | grep ospf +# 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 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 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' + + +# Using deleted +# +# Before state +# ------------- +# +# vyos@192# run show configuration commands | grep ospf +# 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 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 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' +# +- name: Delete attributes of ospfv2 routes. + vyos.vyos.vyos_ospfv2: + config: + state: deleted +# +# +# ------------------------ +# Module Execution Results +# ------------------------ +# +# "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": [ +# "eth2", +# "eth1" +# ], +# "redistribute": [ +# { +# "metric": 10, +# "metric_type": 2, +# "route_type": "bgp" +# } +# ] +# } +# "commands": [ +# "delete protocols ospf" +# ] +# +# "after": {} +# After state +# ------------ +# vyos@192# run show configuration commands | grep ospf +# + +""" +RETURN = """ +before: + description: The configuration prior to the model invocation. + returned: always + type: dict + sample: > + The configuration returned will always be in the same format + of the parameters above. +after: + description: The resulting configuration model invocation. + returned: when changed + type: dict + sample: > + The configuration returned will always be in the same format + of the parameters above. +commands: + description: The set of commands pushed to the remote device. + returned: always + type: list + sample: ['set protocols ospf parameters router-id 192.0.1.1', + 'set protocols ospf passive-interface 'eth1'] +""" + + +from ansible.module_utils.basic import AnsibleModule +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.config.ospfv2.ospfv2 import ( + Ospfv2, +) + + +def main(): + """ + Main entry point for module execution + + :returns: the result form module invocation + """ + required_if = [ + ("state", "merged", ("config",)), + ("state", "replaced", ("config",)), + ("state", "rendered", ("config",)), + ("state", "parsed", ("running_config",)), + ] + mutually_exclusive = [("config", "running_config")] + module = AnsibleModule( + argument_spec=Ospfv2Args.argument_spec, + required_if=required_if, + supports_check_mode=True, + mutually_exclusive=mutually_exclusive, + ) + + result = Ospfv2(module).execute_module() + module.exit_json(**result) + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/vyos_vlan.py b/plugins/modules/vyos_vlan.py index a0aafb5..04f5856 100644 --- a/plugins/modules/vyos_vlan.py +++ b/plugins/modules/vyos_vlan.py @@ -1,367 +1,367 @@ #!/usr/bin/python # -*- coding: utf-8 -*- # Copyright: (c) 2017, Ansible by Red Hat, inc # 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 ANSIBLE_METADATA = { "metadata_version": "1.1", "status": ["preview"], "supported_by": "network", } DOCUMENTATION = """module: vyos_vlan author: Trishna Guha (@trishnaguha) short_description: Manage VLANs on VyOS network devices description: - This module provides declarative management of VLANs on VyOS network devices. notes: - Tested against VyOS 1.1.8 (helium). - This module works with connection C(network_cli). See L(the VyOS OS Platform Options,../network/user_guide/platform_vyos.html). options: name: description: - Name of the VLAN. address: description: - Configure Virtual interface address. vlan_id: description: - ID of the VLAN. Range 0-4094. required: true interfaces: description: - List of interfaces that should be associated to the VLAN. required: true associated_interfaces: description: - This is a intent option and checks the operational state of the for given vlan C(name) for associated interfaces. If the value in the C(associated_interfaces) does not match with the operational state of vlan on device it will result in failure. delay: description: - Delay the play should wait to check for declarative intent params values. default: 10 aggregate: description: List of VLANs definitions. purge: description: - Purge VLANs not defined in the I(aggregate) parameter. default: false type: bool state: description: - State of the VLAN configuration. default: present choices: - present - absent extends_documentation_fragment: - vyos.vyos.vyos """ EXAMPLES = """ - name: Create vlan vyos_vlan: vlan_id: 100 name: vlan-100 interfaces: eth1 state: present - name: Add interfaces to VLAN vyos_vlan: vlan_id: 100 interfaces: - eth1 - eth2 - name: Configure virtual interface address vyos_vlan: vlan_id: 100 interfaces: eth1 address: 172.26.100.37/24 - name: vlan interface config + intent vyos_vlan: vlan_id: 100 interfaces: eth0 associated_interfaces: - eth0 - name: vlan intent check vyos_vlan: vlan_id: 100 associated_interfaces: - eth3 - eth4 - name: Delete vlan vyos_vlan: vlan_id: 100 interfaces: eth1 state: absent """ RETURN = """ commands: description: The list of configuration mode commands to send to the device returned: always type: list sample: - set interfaces ethernet eth1 vif 100 description VLAN 100 - set interfaces ethernet eth1 vif 100 address 172.26.100.37/24 - delete interfaces ethernet eth1 vif 100 """ import re import time from copy import deepcopy from ansible.module_utils.basic import AnsibleModule from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ( remove_default_spec, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.vyos import ( load_config, run_commands, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.vyos import ( vyos_argument_spec, ) def search_obj_in_list(vlan_id, lst): obj = list() for o in lst: if o["vlan_id"] == vlan_id: obj.append(o) return obj def map_obj_to_commands(updates, module): commands = list() want, have = updates purge = module.params["purge"] for w in want: vlan_id = w["vlan_id"] name = w["name"] address = w["address"] state = w["state"] obj_in_have = search_obj_in_list(vlan_id, have) if state == "absent": if obj_in_have: for obj in obj_in_have: for i in obj["interfaces"]: commands.append( "delete interfaces ethernet {0} vif {1}".format( i, vlan_id ) ) elif state == "present": if not obj_in_have: if w["interfaces"] and w["vlan_id"]: for i in w["interfaces"]: cmd = "set interfaces ethernet {0} vif {1}".format( i, vlan_id ) if w["name"]: commands.append( cmd + " description {0}".format(name) ) elif w["address"]: commands.append( cmd + " address {0}".format(address) ) else: commands.append(cmd) if purge: for h in have: obj_in_want = search_obj_in_list(h["vlan_id"], want) if not obj_in_want: for i in h["interfaces"]: commands.append( "delete interfaces ethernet {0} vif {1}".format( i, h["vlan_id"] ) ) return commands def map_params_to_obj(module): obj = [] aggregate = module.params.get("aggregate") if aggregate: for item in aggregate: for key in item: if item.get(key) is None: item[key] = module.params[key] d = item.copy() if not d["vlan_id"]: module.fail_json(msg="vlan_id is required") d["vlan_id"] = str(d["vlan_id"]) module._check_required_one_of(module.required_one_of, item) obj.append(d) else: obj.append( { "vlan_id": str(module.params["vlan_id"]), "name": module.params["name"], "address": module.params["address"], "state": module.params["state"], "interfaces": module.params["interfaces"], "associated_interfaces": module.params[ "associated_interfaces" ], } ) return obj def map_config_to_obj(module): objs = [] output = run_commands(module, "show interfaces") lines = output[0].strip().splitlines()[3:] - for l in lines: - splitted_line = re.split(r"\s{2,}", l.strip()) + for line in lines: + splitted_line = re.split(r"\s{2,}", line.strip()) obj = {} eth = splitted_line[0].strip("'") if eth.startswith("eth"): obj["interfaces"] = [] if "." in eth: interface = eth.split(".")[0] obj["interfaces"].append(interface) obj["vlan_id"] = eth.split(".")[-1] else: obj["interfaces"].append(eth) obj["vlan_id"] = None if splitted_line[1].strip("'") != "-": obj["address"] = splitted_line[1].strip("'") if len(splitted_line) > 3: obj["name"] = splitted_line[3].strip("'") obj["state"] = "present" objs.append(obj) return objs def check_declarative_intent_params(want, module, result): have = None obj_interface = list() is_delay = False for w in want: if w.get("associated_interfaces") is None: continue if result["changed"] and not is_delay: time.sleep(module.params["delay"]) is_delay = True if have is None: have = map_config_to_obj(module) obj_in_have = search_obj_in_list(w["vlan_id"], have) if obj_in_have: for obj in obj_in_have: obj_interface.extend(obj["interfaces"]) for w in want: if w.get("associated_interfaces") is None: continue for i in w["associated_interfaces"]: if (set(obj_interface) - set(w["associated_interfaces"])) != set( [] ): module.fail_json( msg="Interface {0} not configured on vlan {1}".format( i, w["vlan_id"] ) ) def main(): """ main entry point for module execution """ element_spec = dict( vlan_id=dict(type="int"), name=dict(), address=dict(), interfaces=dict(type="list"), associated_interfaces=dict(type="list"), delay=dict(default=10, type="int"), state=dict(default="present", choices=["present", "absent"]), ) aggregate_spec = deepcopy(element_spec) # remove default in aggregate spec, to handle common arguments remove_default_spec(aggregate_spec) argument_spec = dict( aggregate=dict(type="list", elements="dict", options=aggregate_spec), purge=dict(default=False, type="bool"), ) argument_spec.update(element_spec) argument_spec.update(vyos_argument_spec) required_one_of = [ ["vlan_id", "aggregate"], ["aggregate", "interfaces", "associated_interfaces"], ] mutually_exclusive = [["vlan_id", "aggregate"]] module = AnsibleModule( argument_spec=argument_spec, supports_check_mode=True, required_one_of=required_one_of, mutually_exclusive=mutually_exclusive, ) warnings = list() result = {"changed": False} if warnings: result["warnings"] = warnings want = map_params_to_obj(module) have = map_config_to_obj(module) commands = map_obj_to_commands((want, have), module) result["commands"] = commands if commands: commit = not module.check_mode load_config(module, commands, commit=commit) result["changed"] = True check_declarative_intent_params(want, module, result) module.exit_json(**result) if __name__ == "__main__": main() diff --git a/tests/integration/targets/vyos_ospfv2/defaults/main.yaml b/tests/integration/targets/vyos_ospfv2/defaults/main.yaml new file mode 100644 index 0000000..852a6be --- /dev/null +++ b/tests/integration/targets/vyos_ospfv2/defaults/main.yaml @@ -0,0 +1,3 @@ +--- +testcase: '[^_].*' +test_items: [] diff --git a/tests/integration/targets/vyos_ospfv2/meta/main.yaml b/tests/integration/targets/vyos_ospfv2/meta/main.yaml new file mode 100644 index 0000000..7413320 --- /dev/null +++ b/tests/integration/targets/vyos_ospfv2/meta/main.yaml @@ -0,0 +1,3 @@ +--- +dependencies: + - prepare_vyos_tests diff --git a/tests/integration/targets/vyos_ospfv2/tasks/cli.yaml b/tests/integration/targets/vyos_ospfv2/tasks/cli.yaml new file mode 100644 index 0000000..93eb2fe --- /dev/null +++ b/tests/integration/targets/vyos_ospfv2/tasks/cli.yaml @@ -0,0 +1,19 @@ +--- +- name: Collect all cli test cases + find: + paths: '{{ role_path }}/tests/cli' + patterns: '{{ testcase }}.yaml' + use_regex: true + register: test_cases + delegate_to: localhost + +- name: Set test_items + set_fact: test_items="{{ test_cases.files | map(attribute='path') | list }}" + +- name: Run test case (connection=ansible.netcommon.network_cli) + include: '{{ test_case_to_run }}' + vars: + ansible_connection: ansible.netcommon.network_cli + with_items: '{{ test_items }}' + loop_control: + loop_var: test_case_to_run diff --git a/tests/integration/targets/vyos_ospfv2/tasks/main.yaml b/tests/integration/targets/vyos_ospfv2/tasks/main.yaml new file mode 100644 index 0000000..a3db933 --- /dev/null +++ b/tests/integration/targets/vyos_ospfv2/tasks/main.yaml @@ -0,0 +1,4 @@ +--- +- include: cli.yaml + tags: + - cli diff --git a/tests/integration/targets/vyos_ospfv2/tests/cli/_parsed_config.cfg b/tests/integration/targets/vyos_ospfv2/tests/cli/_parsed_config.cfg new file mode 100644 index 0000000..9cc720b --- /dev/null +++ b/tests/integration/targets/vyos_ospfv2/tests/cli/_parsed_config.cfg @@ -0,0 +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 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 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/_populate.yaml b/tests/integration/targets/vyos_ospfv2/tests/cli/_populate.yaml new file mode 100644 index 0000000..9f358d5 --- /dev/null +++ b/tests/integration/targets/vyos_ospfv2/tests/cli/_populate.yaml @@ -0,0 +1,35 @@ +--- +- name: Setup + 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 }}' diff --git a/tests/integration/targets/vyos_ospfv2/tests/cli/_remove_config.yaml b/tests/integration/targets/vyos_ospfv2/tests/cli/_remove_config.yaml new file mode 100644 index 0000000..7360870 --- /dev/null +++ b/tests/integration/targets/vyos_ospfv2/tests/cli/_remove_config.yaml @@ -0,0 +1,6 @@ +--- +- name: Remove Config + vars: + lines: "delete protocols ospf\n" + ansible.netcommon.cli_config: + config: '{{ lines }}' diff --git a/tests/integration/targets/vyos_ospfv2/tests/cli/deleted.yaml b/tests/integration/targets/vyos_ospfv2/tests/cli/deleted.yaml new file mode 100644 index 0000000..a61f5a7 --- /dev/null +++ b/tests/integration/targets/vyos_ospfv2/tests/cli/deleted.yaml @@ -0,0 +1,48 @@ +--- +- debug: + msg: Start vyos_ospfv2 deleted integration tests ansible_connection={{ + ansible_connection }} + +- include_tasks: _populate.yaml + +- block: + + - name: Delete attributes of ospfv2. + register: result + vyos.vyos.vyos_ospfv2: &id001 + config: + state: deleted + + - name: Assert that the before dicts were correctly generated + assert: + that: + - "{{ populate == result['before'] }}" + + - name: Assert that the correct set of commands were generated + assert: + that: + - "{{ deleted['commands'] | symmetric_difference(result['commands']) |length\ + \ == 0 }}" + + - name: Assert that the after dicts were correctly generated + assert: + that: + - "{{ deleted['after'] == result['after'] }}" + + - name: Delete attributes of given interfaces (IDEMPOTENT) + register: result + vyos.vyos.vyos_ospfv2: *id001 + + - name: Assert that the previous task was idempotent + assert: + that: + - result.changed == false + - result.commands|length == 0 + + - name: Assert that the before dicts were correctly generated + assert: + that: + - "{{ deleted['after'] == result['before'] }}" + always: + + - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_ospfv2/tests/cli/empty_config.yaml b/tests/integration/targets/vyos_ospfv2/tests/cli/empty_config.yaml new file mode 100644 index 0000000..4566bf4 --- /dev/null +++ b/tests/integration/targets/vyos_ospfv2/tests/cli/empty_config.yaml @@ -0,0 +1,49 @@ +--- +- debug: + msg: START vyos_ospfv2 empty_config integration tests on connection={{ + ansible_connection }} + +- name: Merged with empty config should give appropriate error message + register: result + ignore_errors: true + vyos.vyos.vyos_ospfv2: + config: + state: merged + +- assert: + that: + - result.msg == 'value of config parameter must not be empty for state merged' + +- name: Replaced with empty config should give appropriate error message + register: result + ignore_errors: true + vyos.vyos.vyos_ospfv2: + config: + state: replaced + +- assert: + that: + - result.msg == 'value of config parameter must not be empty for state replaced' + +- name: Parsed with empty running_config should give appropriate error message + register: result + ignore_errors: true + vyos.vyos.vyos_ospfv2: + running_config: + state: parsed + +- assert: + that: + - result.msg == 'value of running_config parameter must not be empty for state + parsed' + +- name: Rendered with empty config should give appropriate error message + register: result + ignore_errors: true + vyos.vyos.vyos_ospfv2: + config: + state: rendered + +- assert: + that: + - result.msg == 'value of config parameter must not be empty for state rendered' diff --git a/tests/integration/targets/vyos_ospfv2/tests/cli/gathered.yaml b/tests/integration/targets/vyos_ospfv2/tests/cli/gathered.yaml new file mode 100644 index 0000000..bc5e1e2 --- /dev/null +++ b/tests/integration/targets/vyos_ospfv2/tests/cli/gathered.yaml @@ -0,0 +1,24 @@ +--- +- debug: + msg: START vyos_ospfv2 gathered integration tests on connection={{ + ansible_connection }} + +- include_tasks: _remove_config.yaml + +- include_tasks: _populate.yaml + +- block: + + - name: Gather the provided configuration with the exisiting running configuration + register: result + vyos.vyos.vyos_ospfv2: + config: + state: gathered + + - name: Assert that gathered dicts was correctly generated + assert: + that: + - "{{ populate == result['gathered'] }}" + always: + + - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_ospfv2/tests/cli/merged.yaml b/tests/integration/targets/vyos_ospfv2/tests/cli/merged.yaml new file mode 100644 index 0000000..6a58bb5 --- /dev/null +++ b/tests/integration/targets/vyos_ospfv2/tests/cli/merged.yaml @@ -0,0 +1,101 @@ +--- +- debug: + msg: START vyos_ospfv2 merged integration tests on connection={{ ansible_connection + }} + +- include_tasks: _remove_config.yaml + +- block: + + - name: Merge the provided configuration with the exisiting running configuration + register: result + vyos.vyos.vyos_ospfv2: &id001 + config: + log_adjacency_changes: 'detail' + max_metric: + router_lsa: + administrative: true + on_shutdown: 10 + on_startup: 10 + default_information: + originate: + always: true + metric: 10 + metric_type: 2 + route_map: 'ingress' + mpls_te: + enabled: true + router_address: '192.0.11.11' + auto_cost: + reference_bandwidth: 2 + neighbor: + - neighbor_id: '192.0.11.12' + poll_interval: 10 + priority: 2 + redistribute: + - route_type: 'bgp' + metric: 10 + metric_type: 2 + passive_interface: + - 'eth1' + - 'eth2' + parameters: + router_id: '192.0.1.1' + opaque_lsa: true + rfc1583_compatibility: true + abr_type: 'cisco' + 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 + network: + - address: '192.0.2.0/24' + range: + - address: '192.0.3.0/24' + cost: 10 + - address: '192.0.4.0/24' + cost: 12 + state: merged + + - name: Assert that before dicts were correctly generated + assert: + that: "{{ merged['before'] == result['before'] }}" + + - name: Assert that correct set of commands were generated + assert: + that: + - "{{ merged['commands'] | symmetric_difference(result['commands']) |length\ + \ == 0 }}" + + - name: Assert that after dicts was correctly generated + assert: + that: + - "{{ merged['after'] == result['after'] }}" + + - name: Merge the provided configuration with the existing running configuration + (IDEMPOTENT) + register: result + vyos.vyos.vyos_ospfv2: *id001 + + - name: Assert that the previous task was idempotent + assert: + that: + - result['changed'] == false + + - name: Assert that before dicts were correctly generated + assert: + that: + - "{{ merged['after'] == result['before'] }}" + always: + + - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_ospfv2/tests/cli/merged_update.yaml b/tests/integration/targets/vyos_ospfv2/tests/cli/merged_update.yaml new file mode 100644 index 0000000..9b6823c --- /dev/null +++ b/tests/integration/targets/vyos_ospfv2/tests/cli/merged_update.yaml @@ -0,0 +1,70 @@ +--- +- debug: + msg: START vyos_ospfv2 merged integration tests on connection={{ ansible_connection + }} + +- include_tasks: _remove_config.yaml + +- include_tasks: _populate.yaml + +- block: + + - name: Merge the provided configuration with the exisiting running configuration + register: result + vyos.vyos.vyos_ospfv2: &id001 + config: + log_adjacency_changes: 'detail' + max_metric: + router_lsa: + administrative: true + on_shutdown: 10 + on_startup: 10 + passive_interface: + - 'eth1' + areas: + - area_id: '3' + area_type: + nssa: + set: true + - area_id: '4' + area_type: + stub: + set: false + network: + - address: '192.0.2.0/24' + - address: '192.0.22.0/24' + - address: '192.0.32.0/24' + state: merged + + - name: Assert that before dicts were correctly generated + assert: + that: "{{ merged_update['before'] == result['before'] }}" + + - name: Assert that correct set of commands were generated + assert: + that: + - "{{ merged_update['commands'] | symmetric_difference(result['commands']) |length\ + \ == 0 }}" + + - name: Assert that after dicts was correctly generated + assert: + that: + - "{{ merged_update['after'] == result['after'] }}" + + - name: Merge the provided configuration with the existing running configuration + (IDEMPOTENT) + register: result + vyos.vyos.vyos_ospfv2: *id001 + + - name: Assert that the previous task was idempotent + assert: + that: + - result['changed'] == false + + - name: Assert that before dicts were correctly generated + assert: + that: + - "{{ merged_update['after'] == result['before'] }}" + always: + + - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_ospfv2/tests/cli/parsed.yaml b/tests/integration/targets/vyos_ospfv2/tests/cli/parsed.yaml new file mode 100644 index 0000000..cfa29f9 --- /dev/null +++ b/tests/integration/targets/vyos_ospfv2/tests/cli/parsed.yaml @@ -0,0 +1,15 @@ +--- +- debug: + msg: START vyos_ospfv2 parsed integration tests on connection={{ ansible_connection + }} + +- name: Parse externally provided ospfv2 config to agnostic model + register: result + vyos.vyos.vyos_ospfv2: + running_config: "{{ lookup('file', '_parsed_config.cfg') }}" + state: parsed + +- name: Assert that config was correctly parsed + assert: + that: + - "{{ parsed['after'] == result['parsed'] }}" diff --git a/tests/integration/targets/vyos_ospfv2/tests/cli/rendered.yaml b/tests/integration/targets/vyos_ospfv2/tests/cli/rendered.yaml new file mode 100644 index 0000000..8a805a6 --- /dev/null +++ b/tests/integration/targets/vyos_ospfv2/tests/cli/rendered.yaml @@ -0,0 +1,76 @@ +--- +- debug: + msg: START vyos_ospfv2 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_ospfv2: + config: + log_adjacency_changes: 'detail' + max_metric: + router_lsa: + administrative: true + on_shutdown: 10 + on_startup: 10 + default_information: + originate: + always: true + metric: 10 + metric_type: 2 + route_map: 'ingress' + mpls_te: + enabled: true + router_address: '192.0.11.11' + auto_cost: + reference_bandwidth: 2 + neighbor: + - neighbor_id: '192.0.11.12' + poll_interval: 10 + priority: 2 + redistribute: + - route_type: 'bgp' + metric: 10 + metric_type: 2 + passive_interface: + - 'eth1' + - 'eth2' + parameters: + router_id: '192.0.1.1' + opaque_lsa: true + rfc1583_compatibility: true + abr_type: 'cisco' + 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 + network: + - address: '192.0.2.0/24' + range: + - address: '192.0.3.0/24' + cost: 10 + - address: '192.0.4.0/24' + cost: 12 + state: rendered + + - name: Assert that correct set of commands were generated + assert: + that: + - "{{ rendered['commands'] | symmetric_difference(result['rendered'])\ + \ |length == 0 }}" +- debug: + msg: END vyos_ospfv2 rendered integration tests on connection={{ ansible_connection }} diff --git a/tests/integration/targets/vyos_ospfv2/tests/cli/replaced.yaml b/tests/integration/targets/vyos_ospfv2/tests/cli/replaced.yaml new file mode 100644 index 0000000..07606f9 --- /dev/null +++ b/tests/integration/targets/vyos_ospfv2/tests/cli/replaced.yaml @@ -0,0 +1,100 @@ +--- +- debug: + msg: START vyos_ospfv2 replaced integration tests on connection={{ + ansible_connection }} + +- include_tasks: _remove_config.yaml + +- include_tasks: _populate.yaml + +- block: + + - name: Replace device configurations of listed ospfv2 routes with provided configurations + register: result + vyos.vyos.vyos_ospfv2: &id001 + config: + log_adjacency_changes: 'detail' + max_metric: + router_lsa: + administrative: true + on_shutdown: 10 + on_startup: 10 + default_information: + originate: + always: true + metric: 10 + metric_type: 2 + route_map: 'ingress' + mpls_te: + enabled: true + router_address: '192.0.22.22' + auto_cost: + reference_bandwidth: 2 + neighbor: + - neighbor_id: '192.0.11.12' + poll_interval: 10 + priority: 2 + redistribute: + - route_type: 'bgp' + metric: 10 + metric_type: 2 + passive_interface: + - 'eth1' + parameters: + router_id: '192.0.1.1' + opaque_lsa: true + rfc1583_compatibility: true + abr_type: 'cisco' + areas: + - area_id: '2' + area_type: + normal: true + authentication: "plaintext-password" + shortcut: 'enable' + - area_id: '4' + area_type: + stub: + default_cost: 20 + network: + - address: '192.0.2.0/24' + - address: '192.0.12.0/24' + - address: '192.0.22.0/24' + - address: '192.0.32.0/24' + range: + - address: '1.1.2.0/24' + cost: 10 + state: replaced + + - name: Assert that correct set of commands were generated + assert: + that: + - "{{ replaced['commands'] | symmetric_difference(result['commands'])\ + \ |length == 0 }}" + + - name: Assert that before dicts are correctly generated + assert: + that: + - "{{ populate == result['before'] }}" + + - name: Assert that after dict is correctly generated + assert: + that: + - "{{ replaced['after'] == result['after'] }}" + + - name: Replace device configurations of listed ospfv2 routes with provided configurarions + (IDEMPOTENT) + register: result + vyos.vyos.vyos_ospfv2: *id001 + + - name: Assert that task was idempotent + assert: + that: + - result['changed'] == false + + - name: Assert that before dict is correctly generated + assert: + that: + - "{{ replaced['after'] == result['before'] }}" + always: + + - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_ospfv2/tests/cli/rtt.yaml b/tests/integration/targets/vyos_ospfv2/tests/cli/rtt.yaml new file mode 100644 index 0000000..7efc2a7 --- /dev/null +++ b/tests/integration/targets/vyos_ospfv2/tests/cli/rtt.yaml @@ -0,0 +1,149 @@ +--- +- debug: + msg: START vyos_ospfv2 round trip integration tests on connection={{ + ansible_connection }} + +- include_tasks: _remove_config.yaml + +- block: + + - name: Apply the provided configuration (base config) + register: base_config + vyos.vyos.vyos_ospfv2: + config: + log_adjacency_changes: 'detail' + max_metric: + router_lsa: + administrative: true + on_shutdown: 10 + on_startup: 10 + default_information: + originate: + always: true + metric: 10 + metric_type: 2 + route_map: 'ingress' + mpls_te: + enabled: true + router_address: '192.0.11.11' + auto_cost: + reference_bandwidth: 2 + neighbor: + - neighbor_id: '192.0.11.12' + poll_interval: 10 + priority: 2 + redistribute: + - route_type: 'bgp' + metric: 10 + metric_type: 2 + passive_interface: + - 'eth1' + - 'eth2' + parameters: + router_id: '192.0.1.1' + opaque_lsa: true + rfc1583_compatibility: true + abr_type: 'cisco' + 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 + network: + - address: '192.0.2.0/24' + range: + - address: '192.0.3.0/24' + cost: 10 + - address: '192.0.4.0/24' + cost: 12 + state: merged + + - name: Gather ospfv2 facts + vyos.vyos.vyos_facts: + gather_subset: + - default + gather_network_resources: + - ospfv2 + + - name: Apply the provided configuration (config to be reverted) + register: result + vyos.vyos.vyos_ospfv2: + config: + 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' + state: replaced + + - name: Assert that changes were applied + assert: + that: "{{ round_trip['after'] == result['after'] }}" + + - name: Revert back to base config using facts round trip + register: revert + vyos.vyos.vyos_ospfv2: + config: "{{ ansible_facts['network_resources']['ospfv2'] }}" + state: replaced + + - name: Assert that config was reverted + assert: + that: "{{ base_config['after'] == revert['after']}}" + always: + + - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_ospfv2/vars/main.yaml b/tests/integration/targets/vyos_ospfv2/vars/main.yaml new file mode 100644 index 0000000..e55da20 --- /dev/null +++ b/tests/integration/targets/vyos_ospfv2/vars/main.yaml @@ -0,0 +1,485 @@ +--- +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 + 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 + 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 +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/unit/modules/network/vyos/fixtures/vyos_ospfv2_config.cfg b/tests/unit/modules/network/vyos/fixtures/vyos_ospfv2_config.cfg new file mode 100644 index 0000000..297671b --- /dev/null +++ b/tests/unit/modules/network/vyos/fixtures/vyos_ospfv2_config.cfg @@ -0,0 +1,9 @@ +set protocols ospf area 12 area-type normal +set protocols ospf area 12 authentication plaintext-password +set protocols ospf area 12 shortcut enable +set protocols ospf area 14 range 192.0.13.0/24 cost 10 +set protocols ospf area 14 range 192.0.13.0/24 +set protocols ospf area 14 range 192.0.14.0/24 cost 12 +set protocols ospf area 14 range 192.0.14.0/24 +set protocols ospf area 14 area-type stub default-cost 20 +set protocols ospf area 14 network 192.0.12.0/24 diff --git a/tests/unit/modules/network/vyos/test_vyos_ospfv2.py b/tests/unit/modules/network/vyos/test_vyos_ospfv2.py new file mode 100644 index 0000000..b825066 --- /dev/null +++ b/tests/unit/modules/network/vyos/test_vyos_ospfv2.py @@ -0,0 +1,435 @@ +# (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 ansible_collections.vyos.vyos.tests.unit.compat.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 TestVyosFirewallRulesModule(TestVyosModule): + + module = vyos_ospfv2 + + def setUp(self): + super(TestVyosFirewallRulesModule, 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() + + def tearDown(self): + super(TestVyosFirewallRulesModule, 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, transport="cli", 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"] + )