From e31d8fce791afe62d13f60056dd3f6aaa4a6f859 Mon Sep 17 00:00:00 2001 From: Rohit Thakur Date: Thu, 2 Apr 2020 17:12:08 +0530 Subject: vyos_ospfv3 resource module Signed-off-by: Rohit Thakur --- .../network/vyos/argspec/ospfv3/__init__.py | 0 .../network/vyos/argspec/ospfv3/ospfv3.py | 106 +++++ .../network/vyos/config/ospfv3/__init__.py | 0 .../network/vyos/config/ospfv3/ospfv3.py | 446 +++++++++++++++++++++ plugins/module_utils/network/vyos/facts/facts.py | 4 + .../network/vyos/facts/ospfv3/__init__.py | 0 .../network/vyos/facts/ospfv3/ospfv3.py | 199 +++++++++ plugins/module_utils/network/vyos/utils/utils.py | 31 +- plugins/modules/vyos_facts.py | 2 +- plugins/modules/vyos_ospfv3.py | 181 +++++++++ 10 files changed, 967 insertions(+), 2 deletions(-) create mode 100644 plugins/module_utils/network/vyos/argspec/ospfv3/__init__.py create mode 100644 plugins/module_utils/network/vyos/argspec/ospfv3/ospfv3.py create mode 100644 plugins/module_utils/network/vyos/config/ospfv3/__init__.py create mode 100644 plugins/module_utils/network/vyos/config/ospfv3/ospfv3.py create mode 100644 plugins/module_utils/network/vyos/facts/ospfv3/__init__.py create mode 100644 plugins/module_utils/network/vyos/facts/ospfv3/ospfv3.py create mode 100644 plugins/modules/vyos_ospfv3.py diff --git a/plugins/module_utils/network/vyos/argspec/ospfv3/__init__.py b/plugins/module_utils/network/vyos/argspec/ospfv3/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/plugins/module_utils/network/vyos/argspec/ospfv3/ospfv3.py b/plugins/module_utils/network/vyos/argspec/ospfv3/ospfv3.py new file mode 100644 index 0000000..b0166d8 --- /dev/null +++ b/plugins/module_utils/network/vyos/argspec/ospfv3/ospfv3.py @@ -0,0 +1,106 @@ +# +# -*- 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_ospfv3 module +""" + + +class Ospfv3Args(object): # pylint: disable=R0903 + """The arg spec for the vyos_ospfv3 module + """ + def __init__(self, **kwargs): + pass + + argument_spec = { + 'config': { + 'elements': 'dict', + 'options': { + 'ospf_area': { + 'elements': 'dict', + 'options': { + 'area': { + 'type': 'str' + }, + 'export_list': { + 'type': 'str' + }, + 'import_list': { + 'type': 'str' + }, + 'range': { + 'elements': 'dict', + 'options': { + 'address': { + 'type': 'str' + }, + 'advertise': { + 'type': 'bool' + }, + 'not_advertise': { + 'type': 'bool' + } + }, + 'type': 'list' + } + }, + 'type': 'list' + }, + 'parameters': { + 'options': { + 'router_id': { + 'type': 'str' + } + }, + 'type': 'dict' + }, + 'redistribute': { + 'elements': 'dict', + 'options': { + 'route_map': { + 'type': 'str' + }, + 'route_type': { + 'choices': + ['bgp', 'connected', 'kernel', 'ripng', 'static'], + 'type': + 'str' + } + }, + 'type': 'list' + } + }, + 'type': 'list' + }, + "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/ospfv3/__init__.py b/plugins/module_utils/network/vyos/config/ospfv3/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/plugins/module_utils/network/vyos/config/ospfv3/ospfv3.py b/plugins/module_utils/network/vyos/config/ospfv3/ospfv3.py new file mode 100644 index 0000000..fa869da --- /dev/null +++ b/plugins/module_utils/network/vyos/config/ospfv3/ospfv3.py @@ -0,0 +1,446 @@ +# +# -*- 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_ospfv3 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 Ospfv3(ConfigBase): + """ + The vyos_ospfv3 class + """ + + gather_subset = [ + '!all', + '!min', + ] + + gather_network_resources = [ + 'ospfv3', + ] + + def __init__(self, module): + super(Ospfv3, self).__init__(module) + + def get_ospfv3_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) + ospfv3_facts = facts['ansible_network_resources'].get('ospfv3') + if not ospfv3_facts: + return [] + return ospfv3_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_ospfv3_facts = self.get_ospfv3_facts() + else: + existing_ospfv3_facts = [] + + if self.state in self.ACTION_STATES or self.state == "rendered": + commands.extend(self.set_config(existing_ospfv3_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_ospfv3_facts = self.get_ospfv3_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_ospfv3_facts( + data=running_config + ) + else: + changed_ospfv3_facts = [] + + if self.state in self.ACTION_STATES: + result["before"] = existing_ospfv3_facts + if result["changed"]: + result["after"] = changed_ospfv3_facts + elif self.state == "gathered": + result["gathered"] = changed_ospfv3_facts + + result["warnings"] = warnings + return result + + def set_config(self, existing_ospfv3_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_ospfv3_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 == "overridden": + commands.extend(self._state_overridden(w, h)) + elif self.state == "deleted": + commands.extend(self._state_deleted(w, h)) + elif w: + if self.state == "merged" or self.state == "rendered": + for w_item in w: + commands.extend(self._state_merged(w_item, h)) + elif self.state == "replaced": + for w_item in w: + commands.extend(self._state_replaced(w_item, 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 = [] + h_item = {} + if have: + h_item = have[0] + commands.extend(self._render_ospf_param(h_item, want, opr=False)) + commands.extend(self._render_ospf_param(want, h_item)) + 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 = [] + h_item = {} + if have: + h_item = have[0] + commands.extend(self._render_ospf_param(want, h_item)) + return commands + + def _state_deleted(self, want, have): + """ The command generator when state is deleted + + :rtype: A list + :returns: the commands necessary to remove the current configuration + of the provided objects + """ + commands = [] + if want: + for w in want: + if have: + h = have[0] + if h: + for key, val in iteritems(w): + if key in h: + if key == 'ospf_area': + key = 'area' + commands.append(self._compute_command(attr=key, opr=False)) + elif have and have[0]: + commands.append('delete protocols ospfv3') + 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)) + if w: + for key, val in iteritems(w): + 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 == 'ospf_area': + commands.extend(self._render_ospf_area(key, w, h, opr=opr)) + elif key == 'parameters': + commands.extend(self._render_dict_param(key, w, h, opr=opr)) + elif key == 'redistribute': + commands.extend(self._render_list_dict_param(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 = {'parameters': "router_id"} + 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): + 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): + 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_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', + 'range': 'address', + } + leaf_dict = {'redistribute': ("route_map", "route_type"), + 'range': ("address", "advertise", "not_advertise") + } + 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', 'address'): + commands.append(cmd + attr + ' ' + str(val)) + elif key in leaf_dict['range']: + commands.append(cmd + attr + ' ' + w_item[name[attr]] + ' ' + key.replace("_","-")) + 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', 'address'): + commands.append(cmd + attr + ' ' + str(val)) + else: + commands.append(cmd + (attr + ' ' + w_item[name[attr]] + ' ' + key)) + return commands + + def _render_ospf_area(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") + 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']), opr=opr) + ' ' + h_area = self.search_obj_in_have(h_lst, w_area, 'area') + if not opr and not h_area: + commands.append(self._form_attr_cmd(key='area', attr=w_area['area'], 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': + commands.append(self._form_attr_cmd(attr=key, 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' and not _in_target(h_area, key): + commands.append(cmd) + continue + elif key != 'area' and not _in_target(h_area, key): + commands.append(cmd + val + ' ' + key) + elif key == 'range': + commands.extend(self._render_list_dict_param(key, w_area, h_area, cmd, opr)) + 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 ospfv3 " + else: + cmd = "set protocols ospfv3 " + if key: + cmd += key.replace("_", "-") + " " + if attr: + cmd += attr.replace("_", "-") + if val and opr: + 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 ff3d098..6ead7fe 100644 --- a/plugins/module_utils/network/vyos/facts/facts.py +++ b/plugins/module_utils/network/vyos/facts/facts.py @@ -40,6 +40,9 @@ from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.firew 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.legacy.base import ( Default, Neighbors, @@ -58,6 +61,7 @@ FACT_RESOURCE_SUBSETS = dict( firewall_rules=Firewall_rulesFacts, firewall_global=Firewall_globalFacts, firewall_interfaces=Firewall_interfacesFacts, + ospfv3=Ospfv3Facts ) diff --git a/plugins/module_utils/network/vyos/facts/ospfv3/__init__.py b/plugins/module_utils/network/vyos/facts/ospfv3/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/plugins/module_utils/network/vyos/facts/ospfv3/ospfv3.py b/plugins/module_utils/network/vyos/facts/ospfv3/ospfv3.py new file mode 100644 index 0000000..c980795 --- /dev/null +++ b/plugins/module_utils/network/vyos/facts/ospfv3/ospfv3.py @@ -0,0 +1,199 @@ +# +# -*- 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 ospfv3 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 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.ospfv3.ospfv3 import Ospfv3Args + + +class Ospfv3Facts(object): + """ The vyos ospfv3 fact class + """ + + def __init__(self, module, subspec='config', options='options'): + self._module = module + self.argument_spec = Ospfv3Args.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 ospfv3 + :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 = [] + ospfv3 = findall(r"^set protocols ospfv3 (.+)", data, M) + if ospfv3: + config = self.render_config(ospfv3) + if config: + objs.append(config) + ansible_facts["ansible_network_resources"].pop("ospfv3", None) + facts = {} + if objs: + facts["ospfv3"] = [] + params = utils.validate_config(self.argument_spec, {"config": objs}) + for cfg in params["config"]: + facts["ospfv3"].append(utils.remove_empties(cfg)) + ansible_facts["ansible_network_resources"].update(facts) + return ansible_facts + + def render_config(self, conf): + """ + Render config as dictionary structure + + :param conf: The configuration + :returns: The generated config + """ + conf = "\n".join(filter(lambda x: x, conf)) + config = {} + config["parameters"] = self.parse_attrib(conf, "parameters", "parameters") + config["ospf_area"] = self.parse_attrib_list(conf, "area", "area") + config["redistribute"] = self.parse_attrib_list(conf, "redistribute", "route_type") + return config + + 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 + " (?:\'*)(\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) + 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_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. + """ + cfg_dict = {"range": self.parse_attrib_list(conf, "range", "address")} + return cfg_dict + + 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 = { + 'redistribute': ["route_map"], + 'range': ["advertise", "not_advertise"], + 'parameters': ["router_id"] + } + 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: + 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 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 = ("enabled", "advertise", "not_advertise") + 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") + 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 4635234..2d5b74b 100644 --- a/plugins/module_utils/network/vyos/utils/utils.py +++ b/plugins/module_utils/network/vyos/utils/utils.py @@ -7,6 +7,7 @@ from __future__ import absolute_import, division, print_function __metaclass__ = type +import q from ansible.module_utils.six import iteritems from ansible_collections.ansible.netcommon.plugins.module_utils.compat import ( ipaddress, @@ -113,7 +114,7 @@ def get_lst_same_for_dicts(want, have, lst): 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: @@ -230,3 +231,31 @@ def get_route_type(address): 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 4a64066..5849519 100644 --- a/plugins/modules/vyos_facts.py +++ b/plugins/modules/vyos_facts.py @@ -48,7 +48,7 @@ options: 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'. + 'firewall_interfaces', 'ospfv3'. required: false """ diff --git a/plugins/modules/vyos_ospfv3.py b/plugins/modules/vyos_ospfv3.py new file mode 100644 index 0000000..5b4b846 --- /dev/null +++ b/plugins/modules/vyos_ospfv3.py @@ -0,0 +1,181 @@ +#!/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_ospfv3 +""" + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +ANSIBLE_METADATA = { + 'metadata_version': '1.1', + 'status': ['preview'], + 'supported_by': 'network' +} + +DOCUMENTATION = """ +--- +module: vyos_ospfv3 +version_added: 2.10 +short_description: Manages attributes of OSPF IPv6 routes on VyOS network devices. +description: This module manages attributes of OSPF IPv6 routes on VyOS network devices. +author: Rohit Thakur (@rohitthakur2590) +options: + config: + description: A provided OSPF route configuration. + type: list + elements: dict + suboptions: + ospf_area: + description: OSPF area. + type: list + elements: dict + suboptions: + area: + description: Area name/identity. + type: str + export_list: + description: Name of export-list. + type: str + import_list: + description: Name of import-list. + type: str + range: + description: Summarize routes matching prefix (border routers only). + type: list + elements: dict + suboptions: + address: + description: border router IPv4 address. + type: str + advertise: + description: Advertise this range. + type: bool + not_advertise: + description: Don't advertise this range. + type: bool + parameters: + descriptions: OSPFv3 specific parameters. + type: dict + suboptions: + router_id: + description: Override the default router identifier. + type: str + 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', 'ripng', 'static'] + route_map: + description: Route map references. + type: str + state: + description: + - The state the configuration should be left in. + type: str + choices: + - merged + - replaced + - deleted + - parsed + - gathered + - rendered + default: merged +""" +EXAMPLES = """ + + + + + + + + + + + + + + + + + + +""" +RETURN = """ +before: + description: The configuration prior to the model invocation. + returned: always + sample: > + The configuration returned will always be in the same format + of the parameters above. +after: + description: The resulting configuration model invocation. + returned: when changed + sample: > + The configuration returned will always be in the same format + of the parameters above. +commands: + description: The set of commands pushed to the remote device. + returned: always + type: list + sample: ['command 1', 'command 2', 'command 3'] +""" + + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.argspec.ospfv3.ospfv3 import Ospfv3Args +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.config.ospfv3.ospfv3 import Ospfv3 + + +def main(): + """ + Main entry point for module execution + + :returns: the result form module invocation + """ + required_if = [ + ("state", "merged", ("config",)), + ("state", "replaced", ("config",)), + ("state", "parsed", ("running_config",)), + ] + mutually_exclusive = [("config", "running_config")] + module = AnsibleModule( + argument_spec=Ospfv3Args.argument_spec, + required_if=required_if, + supports_check_mode=True, + mutually_exclusive=mutually_exclusive, + ) + + result = Ospfv3(module).execute_module() + module.exit_json(**result) + + +if __name__ == '__main__': + main() -- cgit v1.2.3 From 2f68e4bfd9b908e6dc36ce7ce04bfdc29dfb6264 Mon Sep 17 00:00:00 2001 From: Rohit Thakur Date: Wed, 22 Apr 2020 20:23:39 +0530 Subject: final code changes Signed-off-by: Rohit Thakur --- .../module_utils/network/vyos/facts/ospfv3/ospfv3.py | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/plugins/module_utils/network/vyos/facts/ospfv3/ospfv3.py b/plugins/module_utils/network/vyos/facts/ospfv3/ospfv3.py index c980795..9bb7c0a 100644 --- a/plugins/module_utils/network/vyos/facts/ospfv3/ospfv3.py +++ b/plugins/module_utils/network/vyos/facts/ospfv3/ospfv3.py @@ -51,20 +51,14 @@ class Ospfv3Facts(object): # typically data is populated from the current device configuration # data = connection.get('show running-config | section ^interface') # using mock data instead - objs = [] + objs = {} ospfv3 = findall(r"^set protocols ospfv3 (.+)", data, M) if ospfv3: - config = self.render_config(ospfv3) - if config: - objs.append(config) - ansible_facts["ansible_network_resources"].pop("ospfv3", None) + objs = self.render_config(ospfv3) facts = {} - if objs: - facts["ospfv3"] = [] - params = utils.validate_config(self.argument_spec, {"config": objs}) - for cfg in params["config"]: - facts["ospfv3"].append(utils.remove_empties(cfg)) - ansible_facts["ansible_network_resources"].update(facts) + params = utils.validate_config(self.argument_spec, {'config': objs}) + facts['ospfv3'] = utils.remove_empties(params['config']) + ansible_facts['ansible_network_resources'].update(facts) return ansible_facts def render_config(self, conf): @@ -92,9 +86,9 @@ class Ospfv3Facts(object): """ r_lst = [] if attrib == "area": - items = findall(r"^" + attrib + " (?:\'*)(\S+)(?:\'*)", conf, M) + items = findall(r"^" + attrib + " (?:\'*)(\\S+)(?:\'*)", conf, M) else: - items = findall(r"" + attrib + " (?:\'*)(\S+)(?:\'*)", conf, M) + items = findall(r"" + attrib + " (?:\'*)(\\S+)(?:\'*)", conf, M) if items: a_lst = [] for item in set(items): -- cgit v1.2.3 From 6726b46caabe79f27485e5b33f7f1e324ded6dd1 Mon Sep 17 00:00:00 2001 From: Rohit Thakur Date: Fri, 24 Apr 2020 13:52:56 +0530 Subject: ospfv3 work going on Signed-off-by: Rohit Thakur --- .../network/vyos/argspec/ospfv3/ospfv3.py | 10 +- .../network/vyos/config/ospfv3/ospfv3.py | 132 ++++++++------------- .../network/vyos/facts/ospfv3/ospfv3.py | 10 +- plugins/modules/vyos_ospfv3.py | 21 ++-- 4 files changed, 75 insertions(+), 98 deletions(-) diff --git a/plugins/module_utils/network/vyos/argspec/ospfv3/ospfv3.py b/plugins/module_utils/network/vyos/argspec/ospfv3/ospfv3.py index b0166d8..60e208c 100644 --- a/plugins/module_utils/network/vyos/argspec/ospfv3/ospfv3.py +++ b/plugins/module_utils/network/vyos/argspec/ospfv3/ospfv3.py @@ -34,12 +34,11 @@ class Ospfv3Args(object): # pylint: disable=R0903 argument_spec = { 'config': { - 'elements': 'dict', 'options': { - 'ospf_area': { + 'areas': { 'elements': 'dict', 'options': { - 'area': { + 'area_id': { 'type': 'str' }, 'export_list': { @@ -90,7 +89,10 @@ class Ospfv3Args(object): # pylint: disable=R0903 'type': 'list' } }, - 'type': 'list' + 'type': 'dict' + }, + 'running_config': { + 'type': 'str' }, "running_config": {"type": "str"}, 'state': { diff --git a/plugins/module_utils/network/vyos/config/ospfv3/ospfv3.py b/plugins/module_utils/network/vyos/config/ospfv3/ospfv3.py index fa869da..3875763 100644 --- a/plugins/module_utils/network/vyos/config/ospfv3/ospfv3.py +++ b/plugins/module_utils/network/vyos/config/ospfv3/ospfv3.py @@ -11,9 +11,10 @@ necessary to bring the current configuration to it's desired end-state is created """ from __future__ import absolute_import, division, print_function - __metaclass__ = type +import q + from copy import deepcopy from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base import ( ConfigBase, @@ -21,6 +22,7 @@ from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.c from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ( to_list, remove_empties, + search_obj_in_list ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.facts import Facts from ansible.module_utils.six import iteritems @@ -143,15 +145,13 @@ class Ospfv3(ConfigBase): commands.extend(self._state_overridden(w, h)) elif self.state == "deleted": commands.extend(self._state_deleted(w, h)) - elif w: - if self.state == "merged" or self.state == "rendered": - for w_item in w: - commands.extend(self._state_merged(w_item, h)) - elif self.state == "replaced": - for w_item in w: - commands.extend(self._state_replaced(w_item, 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. @@ -166,6 +166,15 @@ class Ospfv3(ConfigBase): return item return None + def search_obj_in_list(name, lst, key="name"): + if not lst: + return None + else: + for item in lst: + if item.get(key) == name: + return item + ''' + def _state_replaced(self, want, have): """ The command generator when state is replaced @@ -174,11 +183,9 @@ class Ospfv3(ConfigBase): to the desired configuration """ commands = [] - h_item = {} if have: - h_item = have[0] - commands.extend(self._render_ospf_param(h_item, want, opr=False)) - commands.extend(self._render_ospf_param(want, h_item)) + 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): @@ -189,10 +196,7 @@ class Ospfv3(ConfigBase): the current configuration """ commands = [] - h_item = {} - if have: - h_item = have[0] - commands.extend(self._render_ospf_param(want, h_item)) + commands.extend(self._render_ospf_param(want, have)) return commands def _state_deleted(self, want, have): @@ -204,16 +208,14 @@ class Ospfv3(ConfigBase): """ commands = [] if want: - for w in want: + if have: if have: - h = have[0] - if h: - for key, val in iteritems(w): - if key in h: - if key == 'ospf_area': - key = 'area' - commands.append(self._compute_command(attr=key, opr=False)) - elif have and have[0]: + for key, val in iteritems(want): + if key in have: + if key == 'areas': + key = 'area' + commands.append(self._compute_command(attr=key, opr=False)) + elif have: commands.append('delete protocols ospfv3') return commands @@ -245,8 +247,8 @@ class Ospfv3(ConfigBase): :return: list of commands. """ commands = [] - if key == 'ospf_area': - commands.extend(self._render_ospf_area(key, w, h, opr=opr)) + if key == 'areas': + commands.extend(self._render_areas(key, w, h, opr=opr)) elif key == 'parameters': commands.extend(self._render_dict_param(key, w, h, opr=opr)) elif key == 'redistribute': @@ -278,46 +280,6 @@ class Ospfv3(ConfigBase): 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_list_dict_param(self, attr, want, have, cmd=None, opr=True): """ This function forms the set/delete commands based on the 'opr' type @@ -346,24 +308,30 @@ class Ospfv3(ConfigBase): elif w: for w_item in w: for key, val in iteritems(w_item): + q(key) + q(val) + q(name[attr]) + q(w_item) + q(attr) if not cmd: cmd = self._compute_command(opr=opr) - h_item = self.search_obj_in_have(h, w_item, name[attr]) + h_item = search_obj_in_list(w_item[name[attr]], h, name[attr]) if opr and key in leaf and not _is_w_same(w_item, h_item, key): - if key in ('route_type', 'address'): + if key == 'route_type' or (key == 'address' and not ('advertise', 'not-advertise') not in w_item): commands.append(cmd + attr + ' ' + str(val)) - elif key in leaf_dict['range']: - commands.append(cmd + attr + ' ' + w_item[name[attr]] + ' ' + key.replace("_","-")) - else: + elif key in leaf_dict['range'] and key != 'address': + commands.append(cmd + attr + ' ' + w_item[name[attr]] + ' ' + key.replace("_", "-")) + elif key == 'route_map': 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', 'address'): commands.append(cmd + attr + ' ' + str(val)) else: commands.append(cmd + (attr + ' ' + w_item[name[attr]] + ' ' + key)) + q(commands) return commands - def _render_ospf_area(self, attr, want, have, opr=True): + 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. @@ -376,29 +344,29 @@ class Ospfv3(ConfigBase): commands = [] h_lst = {} w_lst = want.get(attr) or [] - l_set = ("area") + l_set = ("area_id, export_list, import_list") 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']), opr=opr) + ' ' - h_area = self.search_obj_in_have(h_lst, w_area, 'area') + cmd = self._compute_command(key='area', attr=_bool_to_str(w_area['area_id']), opr=opr) + ' ' + h_area = search_obj_in_list(w_area['area_id'], h_lst, 'area_id') if not opr and not h_area: - commands.append(self._form_attr_cmd(key='area', attr=w_area['area'], opr=opr)) + 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': - commands.append(self._form_attr_cmd(attr=key, val=_bool_to_str(val), opr=opr)) + 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("_","-")) + commands.append(cmd + key.replace("_", "-") + ' ' + _bool_to_str(val).replace("_", "-")) elif not opr and key in l_set: - if key == 'area' and not _in_target(h_area, key): + if key == 'area_id' and not _in_target(h_area, key): commands.append(cmd) continue - elif key != 'area' and not _in_target(h_area, key): + elif key != 'area_id' and not _in_target(h_area, key): commands.append(cmd + val + ' ' + key) elif key == 'range': commands.extend(self._render_list_dict_param(key, w_area, h_area, cmd, opr)) diff --git a/plugins/module_utils/network/vyos/facts/ospfv3/ospfv3.py b/plugins/module_utils/network/vyos/facts/ospfv3/ospfv3.py index 9bb7c0a..a26f9e7 100644 --- a/plugins/module_utils/network/vyos/facts/ospfv3/ospfv3.py +++ b/plugins/module_utils/network/vyos/facts/ospfv3/ospfv3.py @@ -71,7 +71,7 @@ class Ospfv3Facts(object): conf = "\n".join(filter(lambda x: x, conf)) config = {} config["parameters"] = self.parse_attrib(conf, "parameters", "parameters") - config["ospf_area"] = self.parse_attrib_list(conf, "area", "area") + config["areas"] = self.parse_attrib_list(conf, "area", "area_id") config["redistribute"] = self.parse_attrib_list(conf, "redistribute", "route_type") return config @@ -111,8 +111,11 @@ class Ospfv3Facts(object): :param area_id: area identity :return: generated rule configuration dictionary. """ - cfg_dict = {"range": self.parse_attrib_list(conf, "range", "address")} - return cfg_dict + + rule = self.parse_attrib(conf, 'area_id', match=area_id) + r_sub = {'range': self.parse_attrib_list(conf, 'range', 'address')} + rule.update(r_sub) + return rule def parse_attrib(self, conf, param, match=None): """ @@ -121,6 +124,7 @@ class Ospfv3Facts(object): :return: generated configuration dictionary """ param_lst = { + 'area_id': ['export_list', 'import_list'], 'redistribute': ["route_map"], 'range': ["advertise", "not_advertise"], 'parameters': ["router_id"] diff --git a/plugins/modules/vyos_ospfv3.py b/plugins/modules/vyos_ospfv3.py index 5b4b846..e620fe1 100644 --- a/plugins/modules/vyos_ospfv3.py +++ b/plugins/modules/vyos_ospfv3.py @@ -39,22 +39,21 @@ DOCUMENTATION = """ --- module: vyos_ospfv3 version_added: 2.10 -short_description: Manages attributes of OSPF IPv6 routes on VyOS network devices. -description: This module manages attributes of OSPF IPv6 routes on VyOS network devices. +short_description: OSPFv3 resource module. +description: This resource module configures and manages attributes of OSPFv3 routes on VyOS network devices. author: Rohit Thakur (@rohitthakur2590) options: config: - description: A provided OSPF route configuration. - type: list - elements: dict + description: A provided OSPFv3 route configuration. + type: dict suboptions: - ospf_area: - description: OSPF area. + areas: + description: OSPFv3 area. type: list elements: dict suboptions: - area: - description: Area name/identity. + area_id: + description: OSPFv3 Area name/identity. type: str export_list: description: Name of export-list. @@ -95,6 +94,10 @@ options: route_map: description: Route map references. type: str + running_config: + description: + - The configuration to be parsed. + type: str state: description: - The state the configuration should be left in. -- cgit v1.2.3 From e584c5ce7db0adbdcced3427411bbd82c6dc1b68 Mon Sep 17 00:00:00 2001 From: Rohit Thakur Date: Fri, 8 May 2020 18:53:03 +0530 Subject: ospfv3 test cases updated Signed-off-by: Rohit Thakur --- .../network/vyos/argspec/ospfv3/ospfv3.py | 110 ++--- .../network/vyos/config/ospfv3/ospfv3.py | 263 ++++++----- plugins/module_utils/network/vyos/facts/facts.py | 4 +- .../network/vyos/facts/ospfv3/ospfv3.py | 54 ++- plugins/module_utils/network/vyos/utils/utils.py | 11 +- plugins/modules/vyos_ospfv3.py | 516 ++++++++++++++++++++- .../targets/vyos_ospfv3/defaults/main.yaml | 3 + .../integration/targets/vyos_ospfv3/meta/main.yaml | 3 + .../integration/targets/vyos_ospfv3/tasks/cli.yaml | 19 + .../targets/vyos_ospfv3/tasks/main.yaml | 4 + .../vyos_ospfv3/tests/cli/_parsed_config.cfg | 8 + .../targets/vyos_ospfv3/tests/cli/_populate.yaml | 13 + .../vyos_ospfv3/tests/cli/_remove_config.yaml | 6 + .../targets/vyos_ospfv3/tests/cli/deleted.yaml | 48 ++ .../vyos_ospfv3/tests/cli/empty_config.yaml | 49 ++ .../targets/vyos_ospfv3/tests/cli/gathered.yaml | 25 + .../targets/vyos_ospfv3/tests/cli/merged.yaml | 62 +++ .../vyos_ospfv3/tests/cli/merged_update.yaml | 61 +++ .../targets/vyos_ospfv3/tests/cli/parsed.yaml | 15 + .../targets/vyos_ospfv3/tests/cli/rendered.yaml | 38 ++ .../targets/vyos_ospfv3/tests/cli/replaced.yaml | 66 +++ .../targets/vyos_ospfv3/tests/cli/rtt.yaml | 75 +++ .../integration/targets/vyos_ospfv3/vars/main.yaml | 140 ++++++ .../network/vyos/fixtures/vyos_ospfv3_config.cfg | 6 + .../unit/modules/network/vyos/test_vyos_ospfv3.py | 348 ++++++++++++++ tests/unit/modules/network/vyos/vyos_module.py | 1 + 26 files changed, 1743 insertions(+), 205 deletions(-) create mode 100644 tests/integration/targets/vyos_ospfv3/defaults/main.yaml create mode 100644 tests/integration/targets/vyos_ospfv3/meta/main.yaml create mode 100644 tests/integration/targets/vyos_ospfv3/tasks/cli.yaml create mode 100644 tests/integration/targets/vyos_ospfv3/tasks/main.yaml create mode 100644 tests/integration/targets/vyos_ospfv3/tests/cli/_parsed_config.cfg create mode 100644 tests/integration/targets/vyos_ospfv3/tests/cli/_populate.yaml create mode 100644 tests/integration/targets/vyos_ospfv3/tests/cli/_remove_config.yaml create mode 100644 tests/integration/targets/vyos_ospfv3/tests/cli/deleted.yaml create mode 100644 tests/integration/targets/vyos_ospfv3/tests/cli/empty_config.yaml create mode 100644 tests/integration/targets/vyos_ospfv3/tests/cli/gathered.yaml create mode 100644 tests/integration/targets/vyos_ospfv3/tests/cli/merged.yaml create mode 100644 tests/integration/targets/vyos_ospfv3/tests/cli/merged_update.yaml create mode 100644 tests/integration/targets/vyos_ospfv3/tests/cli/parsed.yaml create mode 100644 tests/integration/targets/vyos_ospfv3/tests/cli/rendered.yaml create mode 100644 tests/integration/targets/vyos_ospfv3/tests/cli/replaced.yaml create mode 100644 tests/integration/targets/vyos_ospfv3/tests/cli/rtt.yaml create mode 100644 tests/integration/targets/vyos_ospfv3/vars/main.yaml create mode 100644 tests/unit/modules/network/vyos/fixtures/vyos_ospfv3_config.cfg create mode 100644 tests/unit/modules/network/vyos/test_vyos_ospfv3.py diff --git a/plugins/module_utils/network/vyos/argspec/ospfv3/ospfv3.py b/plugins/module_utils/network/vyos/argspec/ospfv3/ospfv3.py index 60e208c..66aaa8c 100644 --- a/plugins/module_utils/network/vyos/argspec/ospfv3/ospfv3.py +++ b/plugins/module_utils/network/vyos/argspec/ospfv3/ospfv3.py @@ -29,80 +29,66 @@ The arg spec for the vyos_ospfv3 module class Ospfv3Args(object): # pylint: disable=R0903 """The arg spec for the vyos_ospfv3 module """ + def __init__(self, **kwargs): pass argument_spec = { - 'config': { - 'options': { - 'areas': { - 'elements': 'dict', - 'options': { - 'area_id': { - 'type': 'str' - }, - 'export_list': { - 'type': 'str' - }, - 'import_list': { - 'type': 'str' - }, - 'range': { - 'elements': 'dict', - 'options': { - 'address': { - 'type': 'str' - }, - 'advertise': { - 'type': 'bool' - }, - 'not_advertise': { - 'type': 'bool' - } + "config": { + "options": { + "areas": { + "elements": "dict", + "options": { + "area_id": {"type": "str"}, + "export_list": {"type": "str"}, + "import_list": {"type": "str"}, + "range": { + "elements": "dict", + "options": { + "address": {"type": "str"}, + "advertise": {"type": "bool"}, + "not_advertise": {"type": "bool"}, }, - 'type': 'list' - } + "type": "list", + }, }, - 'type': 'list' + "type": "list", }, - 'parameters': { - 'options': { - 'router_id': { - 'type': 'str' - } - }, - 'type': 'dict' + "parameters": { + "options": {"router_id": {"type": "str"}}, + "type": "dict", }, - 'redistribute': { - 'elements': 'dict', - 'options': { - 'route_map': { - 'type': 'str' + "redistribute": { + "elements": "dict", + "options": { + "route_map": {"type": "str"}, + "route_type": { + "choices": [ + "bgp", + "connected", + "kernel", + "ripng", + "static", + ], + "type": "str", }, - 'route_type': { - 'choices': - ['bgp', 'connected', 'kernel', 'ripng', 'static'], - 'type': - 'str' - } }, - 'type': 'list' - } + "type": "list", + }, }, - 'type': 'dict' - }, - 'running_config': { - 'type': 'str' + "type": "dict", }, "running_config": {"type": "str"}, - 'state': { - 'choices': [ - 'merged', 'replaced', 'deleted', 'parsed', 'gathered', - 'rendered' + "state": { + "choices": [ + "merged", + "replaced", + "deleted", + "parsed", + "gathered", + "rendered", ], - 'default': - 'merged', - 'type': - 'str' - } + "default": "merged", + "type": "str", + }, } # pylint: disable=C0301 diff --git a/plugins/module_utils/network/vyos/config/ospfv3/ospfv3.py b/plugins/module_utils/network/vyos/config/ospfv3/ospfv3.py index 3875763..de972bc 100644 --- a/plugins/module_utils/network/vyos/config/ospfv3/ospfv3.py +++ b/plugins/module_utils/network/vyos/config/ospfv3/ospfv3.py @@ -11,9 +11,8 @@ necessary to bring the current configuration to it's desired end-state is created """ from __future__ import absolute_import, division, print_function -__metaclass__ = type -import q +__metaclass__ = type from copy import deepcopy from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base import ( @@ -22,27 +21,33 @@ from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.c from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ( to_list, remove_empties, - search_obj_in_list + search_obj_in_list, +) +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.facts import ( + Facts, ) -from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.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 + list_diff_want_only, + _in_target, + _is_w_same, + _bool_to_str, ) + class Ospfv3(ConfigBase): """ The vyos_ospfv3 class """ gather_subset = [ - '!all', - '!min', + "!all", + "!min", ] gather_network_resources = [ - 'ospfv3', + "ospfv3", ] def __init__(self, module): @@ -54,10 +59,10 @@ class Ospfv3(ConfigBase): :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) - ospfv3_facts = facts['ansible_network_resources'].get('ospfv3') - if not ospfv3_facts: - return [] + facts, _warnings = Facts(self._module).get_facts( + self.gather_subset, self.gather_network_resources, data=data + ) + ospfv3_facts = facts["ansible_network_resources"].get("ospfv3", {}) return ospfv3_facts def execute_module(self): @@ -66,14 +71,14 @@ class Ospfv3(ConfigBase): :rtype: A dictionary :returns: The result from module execution """ - result = {'changed': False} + result = {"changed": False} warnings = list() commands = list() if self.state in self.ACTION_STATES: existing_ospfv3_facts = self.get_ospfv3_facts() else: - existing_ospfv3_facts = [] + existing_ospfv3_facts = {} if self.state in self.ACTION_STATES or self.state == "rendered": commands.extend(self.set_config(existing_ospfv3_facts)) @@ -96,11 +101,9 @@ class Ospfv3(ConfigBase): self._module.fail_json( msg="value of running_config parameter must not be empty for state parsed" ) - result["parsed"] = self.get_ospfv3_facts( - data=running_config - ) + result["parsed"] = self.get_ospfv3_facts(data=running_config) else: - changed_ospfv3_facts = [] + changed_ospfv3_facts = {} if self.state in self.ACTION_STATES: result["before"] = existing_ospfv3_facts @@ -120,7 +123,7 @@ class Ospfv3(ConfigBase): :returns: the commands necessary to migrate the current configuration to the desired configuration """ - want = self._module.params['config'] + want = self._module.params["config"] have = existing_ospfv3_facts resp = self.set_state(want, have) return to_list(resp) @@ -135,15 +138,16 @@ class Ospfv3(ConfigBase): to the desired configuration """ commands = [] - if self.state in ("merged", "replaced", "overridden", "rendered") and not w: + 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 == "overridden": - commands.extend(self._state_overridden(w, h)) - elif self.state == "deleted": + if self.state == "deleted": commands.extend(self._state_deleted(w, h)) elif self.state in ("merged", "rendered"): commands.extend(self._state_merged(w, h)) @@ -151,30 +155,6 @@ class Ospfv3(ConfigBase): 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 search_obj_in_list(name, lst, key="name"): - if not lst: - return None - else: - for item in lst: - if item.get(key) == name: - return item - ''' - def _state_replaced(self, want, have): """ The command generator when state is replaced @@ -207,16 +187,8 @@ class Ospfv3(ConfigBase): of the provided objects """ commands = [] - if want: - if have: - if have: - for key, val in iteritems(want): - if key in have: - if key == 'areas': - key = 'area' - commands.append(self._compute_command(attr=key, opr=False)) - elif have: - commands.append('delete protocols ospfv3') + if have: + commands.append("delete protocols ospfv3") return commands def _render_ospf_param(self, want, have, opr=True): @@ -233,7 +205,7 @@ class Ospfv3(ConfigBase): w = deepcopy(remove_empties(want)) if w: for key, val in iteritems(w): - commands.extend(self._render_child_param(w, have, key, opr)) + commands.extend(self._render_child_param(w, have, key, opr)) return commands def _render_child_param(self, w, h, key, opr=True): @@ -247,11 +219,11 @@ class Ospfv3(ConfigBase): :return: list of commands. """ commands = [] - if key == 'areas': + if key == "areas": commands.extend(self._render_areas(key, w, h, opr=opr)) - elif key == 'parameters': + elif key == "parameters": commands.extend(self._render_dict_param(key, w, h, opr=opr)) - elif key == 'redistribute': + elif key == "redistribute": commands.extend(self._render_list_dict_param(key, w, h, opr=opr)) return commands @@ -271,13 +243,23 @@ class Ospfv3(ConfigBase): if not opr and not h: commands.append(self._form_attr_cmd(attr=attr, opr=opr)) elif want[attr]: - leaf_dict = {'parameters': "router_id"} + leaf_dict = {"parameters": "router_id"} 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): - commands.append(self._form_attr_cmd(key=attr, attr=item, val=value, opr=opr)) + if ( + opr + and item in leaf + and not _is_w_same(want[attr], h, item) + ): + 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): - commands.append(self._form_attr_cmd(key=attr, attr=item, opr=opr)) + commands.append( + self._form_attr_cmd(key=attr, attr=item, opr=opr) + ) return commands def _render_list_dict_param(self, attr, want, have, cmd=None, opr=True): @@ -293,12 +275,14 @@ class Ospfv3(ConfigBase): """ commands = [] h = [] - name = {'redistribute': 'route_type', - 'range': 'address', - } - leaf_dict = {'redistribute': ("route_map", "route_type"), - 'range': ("address", "advertise", "not_advertise") - } + name = { + "redistribute": "route_type", + "range": "address", + } + leaf_dict = { + "redistribute": ("route_map", "route_type"), + "range": ("address", "advertise", "not_advertise"), + } leaf = leaf_dict[attr] w = want.get(attr) or [] if have: @@ -308,27 +292,54 @@ class Ospfv3(ConfigBase): elif w: for w_item in w: for key, val in iteritems(w_item): - q(key) - q(val) - q(name[attr]) - q(w_item) - q(attr) if not cmd: cmd = self._compute_command(opr=opr) - h_item = search_obj_in_list(w_item[name[attr]], h, name[attr]) - if opr and key in leaf and not _is_w_same(w_item, h_item, key): - if key == 'route_type' or (key == 'address' and not ('advertise', 'not-advertise') not in w_item): - commands.append(cmd + attr + ' ' + str(val)) - elif key in leaf_dict['range'] and key != 'address': - commands.append(cmd + attr + ' ' + w_item[name[attr]] + ' ' + key.replace("_", "-")) - elif key == 'route_map': - 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', 'address'): - commands.append(cmd + attr + ' ' + str(val)) + h_item = search_obj_in_list( + w_item[name[attr]], h, name[attr] + ) + if ( + opr + and key in leaf + and not _is_w_same(w_item, h_item, key) + ): + if key == "route_type" or ( + key == "address" + and "advertise" not in w_item + and "not-advertise" not in w_item + ): + if not val: + cmd = cmd.replace("set", "delete") + commands.append(cmd + attr + " " + str(val)) + elif key in leaf_dict["range"] and key != "address": + commands.append( + cmd + + attr + + " " + + w_item[name[attr]] + + " " + + key.replace("_", "-") + ) + elif key == "route_map": + 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", "address"): + commands.append(cmd + attr + " " + str(val)) else: - commands.append(cmd + (attr + ' ' + w_item[name[attr]] + ' ' + key)) - q(commands) + commands.append( + cmd + + (attr + " " + w_item[name[attr]] + " " + key) + ) return commands def _render_areas(self, attr, want, have, opr=True): @@ -344,32 +355,68 @@ class Ospfv3(ConfigBase): commands = [] h_lst = {} w_lst = want.get(attr) or [] - l_set = ("area_id, export_list, import_list") + l_set = ("area_id", "export_list", "import_list") 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)) + 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 = search_obj_in_list(w_area['area_id'], h_lst, 'area_id') + cmd = ( + self._compute_command( + key="area", + attr=_bool_to_str(w_area["area_id"]), + opr=opr, + ) + + " " + ) + h_area = search_obj_in_list( + w_area["area_id"], h_lst, "area_id" + ) if not opr and not h_area: - commands.append(self._form_attr_cmd(key='area', attr=w_area['area_id'], opr=opr)) + 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)) + 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.replace("_", "-") + ' ' + _bool_to_str(val).replace("_", "-")) + commands.append( + cmd + + key.replace("_", "-") + + " " + + _bool_to_str(val).replace("_", "-") + ) elif not opr and key in l_set: - if key == 'area_id' and not _in_target(h_area, key): + 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 == 'range': - commands.extend(self._render_list_dict_param(key, w_area, h_area, cmd, opr)) + elif key != "area_id" and not _in_target( + h_area, key + ): + commands.append(cmd + val + " " + key) + elif key == "range": + commands.extend( + self._render_list_dict_param( + key, w_area, h_area, cmd, opr + ) + ) return commands def _form_attr_cmd(self, key=None, attr=None, val=None, opr=True): @@ -381,9 +428,13 @@ class Ospfv3(ConfigBase): :param opr: True/False. :return: generated command. """ - return self._compute_command(key, attr=self._map_attrib(attr), val=val, opr=opr) + 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): + 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. @@ -411,4 +462,4 @@ class Ospfv3(ConfigBase): :param attrib: attribute :return: regex string """ - return 'disable' if attrib == 'disabled' else attrib.replace("_","-") + 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 6ead7fe..3c87be6 100644 --- a/plugins/module_utils/network/vyos/facts/facts.py +++ b/plugins/module_utils/network/vyos/facts/facts.py @@ -41,7 +41,7 @@ from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.firew Firewall_interfacesFacts, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.ospfv3.ospfv3 import ( - Ospfv3Facts, + Ospfv3Facts, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.legacy.base import ( Default, @@ -61,7 +61,7 @@ FACT_RESOURCE_SUBSETS = dict( firewall_rules=Firewall_rulesFacts, firewall_global=Firewall_globalFacts, firewall_interfaces=Firewall_interfacesFacts, - ospfv3=Ospfv3Facts + ospfv3=Ospfv3Facts, ) diff --git a/plugins/module_utils/network/vyos/facts/ospfv3/ospfv3.py b/plugins/module_utils/network/vyos/facts/ospfv3/ospfv3.py index a26f9e7..457a963 100644 --- a/plugins/module_utils/network/vyos/facts/ospfv3/ospfv3.py +++ b/plugins/module_utils/network/vyos/facts/ospfv3/ospfv3.py @@ -9,19 +9,25 @@ 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.ospfv3.ospfv3 import Ospfv3Args +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.argspec.ospfv3.ospfv3 import ( + Ospfv3Args, +) class Ospfv3Facts(object): """ The vyos ospfv3 fact class """ - def __init__(self, module, subspec='config', options='options'): + def __init__(self, module, subspec="config", options="options"): self._module = module self.argument_spec = Ospfv3Args.argument_spec spec = deepcopy(self.argument_spec) @@ -56,9 +62,9 @@ class Ospfv3Facts(object): if ospfv3: objs = self.render_config(ospfv3) facts = {} - params = utils.validate_config(self.argument_spec, {'config': objs}) - facts['ospfv3'] = utils.remove_empties(params['config']) - ansible_facts['ansible_network_resources'].update(facts) + params = utils.validate_config(self.argument_spec, {"config": objs}) + facts["ospfv3"] = utils.remove_empties(params["config"]) + ansible_facts["ansible_network_resources"].update(facts) return ansible_facts def render_config(self, conf): @@ -70,9 +76,13 @@ class Ospfv3Facts(object): """ conf = "\n".join(filter(lambda x: x, conf)) config = {} - config["parameters"] = self.parse_attrib(conf, "parameters", "parameters") + config["parameters"] = self.parse_attrib( + conf, "parameters", "parameters" + ) config["areas"] = self.parse_attrib_list(conf, "area", "area_id") - config["redistribute"] = self.parse_attrib_list(conf, "redistribute", "route_type") + config["redistribute"] = self.parse_attrib_list( + conf, "redistribute", "route_type" + ) return config def parse_attrib_list(self, conf, attrib, param): @@ -86,15 +96,15 @@ class Ospfv3Facts(object): """ r_lst = [] if attrib == "area": - items = findall(r"^" + attrib + " (?:\'*)(\\S+)(?:\'*)", conf, M) + items = findall(r"^" + attrib + " (?:'*)(\\S+)(?:'*)", conf, M) else: - items = findall(r"" + attrib + " (?:\'*)(\\S+)(?:\'*)", conf, M) + 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': + if attrib == "area": obj = self.parse_area(cfg, item) else: obj = self.parse_attrib(cfg, attrib) @@ -112,8 +122,8 @@ class Ospfv3Facts(object): :return: generated rule configuration dictionary. """ - rule = self.parse_attrib(conf, 'area_id', match=area_id) - r_sub = {'range': self.parse_attrib_list(conf, 'range', 'address')} + rule = self.parse_attrib(conf, "area_id", match=area_id) + r_sub = {"range": self.parse_attrib_list(conf, "range", "address")} rule.update(r_sub) return rule @@ -124,11 +134,11 @@ class Ospfv3Facts(object): :return: generated configuration dictionary """ param_lst = { - 'area_id': ['export_list', 'import_list'], - 'redistribute': ["route_map"], - 'range': ["advertise", "not_advertise"], - 'parameters': ["router_id"] - } + "area_id": ["export_list", "import_list"], + "redistribute": ["route_map"], + "range": ["advertise", "not_advertise"], + "parameters": ["router_id"], + } cfg_dict = self.parse_attr(conf, param_lst[param], match) return cfg_dict @@ -176,7 +186,13 @@ class Ospfv3Facts(object): :param attrib: attribute :return: regex string """ - return 'disable' if attrib == "disabled" else 'enable' if attrib == "enabled" else attrib.replace("_","-") + return ( + "disable" + if attrib == "disabled" + else "enable" + if attrib == "enabled" + else attrib.replace("_", "-") + ) def is_bool(self, attrib): """ @@ -193,5 +209,5 @@ class Ospfv3Facts(object): :param attrib: attribute. :return: True/false. """ - num_set = ("ospf") + num_set = "ospf" 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 2d5b74b..f2986aa 100644 --- a/plugins/module_utils/network/vyos/utils/utils.py +++ b/plugins/module_utils/network/vyos/utils/utils.py @@ -232,13 +232,21 @@ def get_route_type(address): 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 + return ( + "enable" + if str(val) == "True" + else "disable" + if str(val) == "False" + else val + ) + def _is_w_same(w, h, key): """ @@ -251,6 +259,7 @@ def _is_w_same(w, h, key): """ 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. diff --git a/plugins/modules/vyos_ospfv3.py b/plugins/modules/vyos_ospfv3.py index e620fe1..ae93500 100644 --- a/plugins/modules/vyos_ospfv3.py +++ b/plugins/modules/vyos_ospfv3.py @@ -27,21 +27,23 @@ The module file for vyos_ospfv3 """ from __future__ import absolute_import, division, print_function + __metaclass__ = type -ANSIBLE_METADATA = { - 'metadata_version': '1.1', - 'status': ['preview'], - 'supported_by': 'network' -} +ANSIBLE_METADATA = {"metadata_version": "1.1", "supported_by": "Ansible"} DOCUMENTATION = """ --- module: vyos_ospfv3 version_added: 2.10 -short_description: OSPFv3 resource module. +short_description: OSPFV3 resource module. description: This resource module configures and manages attributes of OSPFv3 routes on VyOS network devices. -author: Rohit Thakur (@rohitthakur2590) +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 OSPFv3 route configuration. @@ -96,7 +98,12 @@ options: type: str running_config: description: - - The configuration to be parsed. + - 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 ospfv3). + - 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: @@ -112,22 +119,493 @@ options: default: merged """ EXAMPLES = """ +# Using merged +# +# Before state: +# ------------- +# +# vyos@vyos# run show configuration commands | grep ospfv3 +# +# +- name: Merge the provided configuration with the exisiting running configuration + vyos.vyos.vyos_ospfv3: + config: + redistribute: + - route_type: 'bgp' + parameters: + router_id: '192.0.2.10' + areas: + - area_id: '2' + export_list: 'export1' + import_list: 'import1' + range: + - address: '2001:db10::/32' + - address: '2001:db20::/32' + - address: '2001:db30::/32' + - area_id: '3' + range: + - address: '2001:db40::/32' + state: merged +# +# +# ------------------------- +# Module Execution Result +# ------------------------- +# +# before": {} +# +# "commands": [ +# "set protocols ospfv3 redistribute bgp", +# "set protocols ospfv3 parameters router-id '192.0.2.10'", +# "set protocols ospfv3 area 2 range 2001:db10::/32", +# "set protocols ospfv3 area 2 range 2001:db20::/32", +# "set protocols ospfv3 area 2 range 2001:db30::/32", +# "set protocols ospfv3 area '2'", +# "set protocols ospfv3 area 2 export-list export1", +# "set protocols ospfv3 area 2 import-list import1", +# "set protocols ospfv3 area '3'", +# "set protocols ospfv3 area 3 range 2001:db40::/32" +# ] +# +# "after": { +# "areas": [ +# { +# "area_id": "2", +# "export_list": "export1", +# "import_list": "import1", +# "range": [ +# { +# "address": "2001:db10::/32" +# }, +# { +# "address": "2001:db20::/32" +# }, +# { +# "address": "2001:db30::/32" +# } +# ] +# }, +# { +# "area_id": "3", +# "range": [ +# { +# "address": "2001:db40::/32" +# } +# ] +# } +# ], +# "parameters": { +# "router_id": "192.0.2.10" +# }, +# "redistribute": [ +# { +# "route_type": "bgp" +# } +# ] +# } +# +# After state: +# ------------- +# +# vyos@192# run show configuration commands | grep ospfv3 +# set protocols ospfv3 area 2 export-list 'export1' +# set protocols ospfv3 area 2 import-list 'import1' +# set protocols ospfv3 area 2 range '2001:db10::/32' +# set protocols ospfv3 area 2 range '2001:db20::/32' +# set protocols ospfv3 area 2 range '2001:db30::/32' +# set protocols ospfv3 area 3 range '2001:db40::/32' +# set protocols ospfv3 parameters router-id '192.0.2.10' +# set protocols ospfv3 redistribute 'bgp' +# Using replaced +# +# Before state: +# ------------- +# +# vyos@192# run show configuration commands | grep ospfv3 +# set protocols ospfv3 area 2 export-list 'export1' +# set protocols ospfv3 area 2 import-list 'import1' +# set protocols ospfv3 area 2 range '2001:db10::/32' +# set protocols ospfv3 area 2 range '2001:db20::/32' +# set protocols ospfv3 area 2 range '2001:db30::/32' +# set protocols ospfv3 area 3 range '2001:db40::/32' +# set protocols ospfv3 parameters router-id '192.0.2.10' +# set protocols ospfv3 redistribute 'bgp' +# +- name: Replace ospfv3 routes attributes configuration. + vyos.vyos.vyos_ospfv3: + config: + redistribute: + - route_type: 'bgp' + parameters: + router_id: '192.0.2.10' + areas: + - area_id: '2' + export_list: 'export1' + import_list: 'import1' + range: + - address: '2001:db10::/32' + - address: '2001:db30::/32' + - address: '2001:db50::/32' + - area_id: '4' + range: + - address: '2001:db60::/32' + state: replaced +# +# +# ------------------------- +# Module Execution Result +# ------------------------- +# +# "before": { +# "areas": [ +# { +# "area_id": "2", +# "export_list": "export1", +# "import_list": "import1", +# "range": [ +# { +# "address": "2001:db10::/32" +# }, +# { +# "address": "2001:db20::/32" +# }, +# { +# "address": "2001:db30::/32" +# } +# ] +# }, +# { +# "area_id": "3", +# "range": [ +# { +# "address": "2001:db40::/32" +# } +# ] +# } +# ], +# "parameters": { +# "router_id": "192.0.2.10" +# }, +# "redistribute": [ +# { +# "route_type": "bgp" +# } +# ] +# } +# +# "commands": [ +# "delete protocols ospfv3 area 2 range 2001:db20::/32", +# "delete protocols ospfv3 area 3", +# "set protocols ospfv3 area 2 range 2001:db50::/32", +# "set protocols ospfv3 area '4'", +# "set protocols ospfv3 area 4 range 2001:db60::/32" +# ] +# +# "after": { +# "areas": [ +# { +# "area_id": "2", +# "export_list": "export1", +# "import_list": "import1", +# "range": [ +# { +# "address": "2001:db10::/32" +# }, +# { +# "address": "2001:db30::/32" +# }, +# { +# "address": "2001:db50::/32" +# } +# ] +# }, +# { +# "area_id": "4", +# "range": [ +# { +# "address": "2001:db60::/32" +# } +# ] +# } +# ], +# "parameters": { +# "router_id": "192.0.2.10" +# }, +# "redistribute": [ +# { +# "route_type": "bgp" +# } +# ] +# } +# +# After state: +# ------------- +# +# vyos@192# run show configuration commands | grep ospfv3 +# set protocols ospfv3 area 2 export-list 'export1' +# set protocols ospfv3 area 2 import-list 'import1' +# set protocols ospfv3 area 2 range '2001:db10::/32' +# set protocols ospfv3 area 2 range '2001:db30::/32' +# set protocols ospfv3 area 2 range '2001:db50::/32' +# set protocols ospfv3 area 4 range '2001:db60::/32' +# set protocols ospfv3 parameters router-id '192.0.2.10' +# set protocols ospfv3 redistribute 'bgp' +# Using rendered +# +# +- name: Render the commands for provided configuration + vyos.vyos.vyos_ospfv3: + config: + redistribute: + - route_type: 'bgp' + parameters: + router_id: '192.0.2.10' + areas: + - area_id: '2' + export_list: 'export1' + import_list: 'import1' + range: + - address: '2001:db10::/32' + - address: '2001:db20::/32' + - address: '2001:db30::/32' + - area_id: '3' + range: + - address: '2001:db40::/32' + state: rendered +# +# +# ------------------------- +# Module Execution Result +# ------------------------- +# +# +# "rendered": [ +# [ +# "set protocols ospfv3 redistribute bgp", +# "set protocols ospfv3 parameters router-id '192.0.2.10'", +# "set protocols ospfv3 area 2 range 2001:db10::/32", +# "set protocols ospfv3 area 2 range 2001:db20::/32", +# "set protocols ospfv3 area 2 range 2001:db30::/32", +# "set protocols ospfv3 area '2'", +# "set protocols ospfv3 area 2 export-list export1", +# "set protocols ospfv3 area 2 import-list import1", +# "set protocols ospfv3 area '3'", +# "set protocols ospfv3 area 3 range 2001:db40::/32" +# ] +# Using parsed +# +# +- name: Parse the commands to provide structured configuration. + vyos.vyos.vyos_ospfv3: + running_config: + "set protocols ospfv3 area 2 export-list 'export1' +set protocols ospfv3 area 2 import-list 'import1' +set protocols ospfv3 area 2 range '2001:db10::/32' +set protocols ospfv3 area 2 range '2001:db20::/32' +set protocols ospfv3 area 2 range '2001:db30::/32' +set protocols ospfv3 area 3 range '2001:db40::/32' +set protocols ospfv3 parameters router-id '192.0.2.10' +set protocols ospfv3 redistribute 'bgp'" + state: parsed +# +# +# ------------------------- +# Module Execution Result +# ------------------------- +# +# +# "parsed": { +# "areas": [ +# { +# "area_id": "2", +# "export_list": "export1", +# "import_list": "import1", +# "range": [ +# { +# "address": "2001:db10::/32" +# }, +# { +# "address": "2001:db20::/32" +# }, +# { +# "address": "2001:db30::/32" +# } +# ] +# }, +# { +# "area_id": "3", +# "range": [ +# { +# "address": "2001:db40::/32" +# } +# ] +# } +# ], +# "parameters": { +# "router_id": "192.0.2.10" +# }, +# "redistribute": [ +# { +# "route_type": "bgp" +# } +# ] +# } +# Using gathered +# +# Before state: +# ------------- +# +# vyos@192# run show configuration commands | grep ospfv3 +# set protocols ospfv3 area 2 export-list 'export1' +# set protocols ospfv3 area 2 import-list 'import1' +# set protocols ospfv3 area 2 range '2001:db10::/32' +# set protocols ospfv3 area 2 range '2001:db20::/32' +# set protocols ospfv3 area 2 range '2001:db30::/32' +# set protocols ospfv3 area 3 range '2001:db40::/32' +# set protocols ospfv3 parameters router-id '192.0.2.10' +# set protocols ospfv3 redistribute 'bgp' +# +- name: Gather ospfv3 routes config with provided configurations + vyos.vyos.vyos_ospfv3: + config: + state: gathered +# +# +# ------------------------- +# Module Execution Result +# ------------------------- +# +# "gathered": { +# "areas": [ +# { +# "area_id": "2", +# "export_list": "export1", +# "import_list": "import1", +# "range": [ +# { +# "address": "2001:db10::/32" +# }, +# { +# "address": "2001:db20::/32" +# }, +# { +# "address": "2001:db30::/32" +# } +# ] +# }, +# { +# "area_id": "3", +# "range": [ +# { +# "address": "2001:db40::/32" +# } +# ] +# } +# ], +# "parameters": { +# "router_id": "192.0.2.10" +# }, +# "redistribute": [ +# { +# "route_type": "bgp" +# } +# ] +# } +# +# After state: +# ------------- +# +# vyos@192# run show configuration commands | grep ospfv3 +# set protocols ospfv3 area 2 export-list 'export1' +# set protocols ospfv3 area 2 import-list 'import1' +# set protocols ospfv3 area 2 range '2001:db10::/32' +# set protocols ospfv3 area 2 range '2001:db20::/32' +# set protocols ospfv3 area 2 range '2001:db30::/32' +# set protocols ospfv3 area 3 range '2001:db40::/32' +# set protocols ospfv3 parameters router-id '192.0.2.10' +# set protocols ospfv3 redistribute 'bgp' - - - +# Using deleted +# +# Before state +# ------------- +# +# vyos@192# run show configuration commands | grep ospfv3 +# set protocols ospfv3 area 2 export-list 'export1' +# set protocols ospfv3 area 2 import-list 'import1' +# set protocols ospfv3 area 2 range '2001:db10::/32' +# set protocols ospfv3 area 2 range '2001:db20::/32' +# set protocols ospfv3 area 2 range '2001:db30::/32' +# set protocols ospfv3 area 3 range '2001:db40::/32' +# set protocols ospfv3 parameters router-id '192.0.2.10' +# set protocols ospfv3 redistribute 'bgp' +# +- name: Delete attributes of ospfv3 routes. + vyos.vyos.vyos_ospfv3: + config: + state: deleted +# +# +# ------------------------ +# Module Execution Results +# ------------------------ +# +# "before": { +# "areas": [ +# { +# "area_id": "2", +# "export_list": "export1", +# "import_list": "import1", +# "range": [ +# { +# "address": "2001:db10::/32" +# }, +# { +# "address": "2001:db20::/32" +# }, +# { +# "address": "2001:db30::/32" +# } +# ] +# }, +# { +# "area_id": "3", +# "range": [ +# { +# "address": "2001:db40::/32" +# } +# ] +# } +# ], +# "parameters": { +# "router_id": "192.0.2.10" +# }, +# "redistribute": [ +# { +# "route_type": "bgp" +# } +# ] +# } +# "commands": [ +# "delete protocols ospfv3" +# ] +# +# "after": {} +# After state +# ------------ +# vyos@192# run show configuration commands | grep ospfv3 """ @@ -135,12 +613,14 @@ 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. @@ -148,13 +628,18 @@ commands: description: The set of commands pushed to the remote device. returned: always type: list - sample: ['command 1', 'command 2', 'command 3'] + sample: ['set protocols ospf parameters router-id 192.0.1.1', + 'set protocols ospfv3 area 2 range '2001:db10::/32'] """ from ansible.module_utils.basic import AnsibleModule -from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.argspec.ospfv3.ospfv3 import Ospfv3Args -from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.config.ospfv3.ospfv3 import Ospfv3 +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.argspec.ospfv3.ospfv3 import ( + Ospfv3Args, +) +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.config.ospfv3.ospfv3 import ( + Ospfv3, +) def main(): @@ -166,6 +651,7 @@ def main(): required_if = [ ("state", "merged", ("config",)), ("state", "replaced", ("config",)), + ("state", "rendered", ("config",)), ("state", "parsed", ("running_config",)), ] mutually_exclusive = [("config", "running_config")] @@ -180,5 +666,5 @@ def main(): module.exit_json(**result) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/tests/integration/targets/vyos_ospfv3/defaults/main.yaml b/tests/integration/targets/vyos_ospfv3/defaults/main.yaml new file mode 100644 index 0000000..852a6be --- /dev/null +++ b/tests/integration/targets/vyos_ospfv3/defaults/main.yaml @@ -0,0 +1,3 @@ +--- +testcase: '[^_].*' +test_items: [] diff --git a/tests/integration/targets/vyos_ospfv3/meta/main.yaml b/tests/integration/targets/vyos_ospfv3/meta/main.yaml new file mode 100644 index 0000000..7413320 --- /dev/null +++ b/tests/integration/targets/vyos_ospfv3/meta/main.yaml @@ -0,0 +1,3 @@ +--- +dependencies: + - prepare_vyos_tests diff --git a/tests/integration/targets/vyos_ospfv3/tasks/cli.yaml b/tests/integration/targets/vyos_ospfv3/tasks/cli.yaml new file mode 100644 index 0000000..93eb2fe --- /dev/null +++ b/tests/integration/targets/vyos_ospfv3/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_ospfv3/tasks/main.yaml b/tests/integration/targets/vyos_ospfv3/tasks/main.yaml new file mode 100644 index 0000000..a3db933 --- /dev/null +++ b/tests/integration/targets/vyos_ospfv3/tasks/main.yaml @@ -0,0 +1,4 @@ +--- +- include: cli.yaml + tags: + - cli diff --git a/tests/integration/targets/vyos_ospfv3/tests/cli/_parsed_config.cfg b/tests/integration/targets/vyos_ospfv3/tests/cli/_parsed_config.cfg new file mode 100644 index 0000000..5e012d5 --- /dev/null +++ b/tests/integration/targets/vyos_ospfv3/tests/cli/_parsed_config.cfg @@ -0,0 +1,8 @@ +set protocols ospfv3 area 2 export-list 'export1' +set protocols ospfv3 area 2 import-list 'import1' +set protocols ospfv3 area 2 range '2001:db10::/32' +set protocols ospfv3 area 2 range '2001:db20::/32' +set protocols ospfv3 area 2 range '2001:db30::/32' +set protocols ospfv3 area 3 range '2001:db40::/32' +set protocols ospfv3 parameters router-id '192.0.2.10' +set protocols ospfv3 redistribute 'bgp' diff --git a/tests/integration/targets/vyos_ospfv3/tests/cli/_populate.yaml b/tests/integration/targets/vyos_ospfv3/tests/cli/_populate.yaml new file mode 100644 index 0000000..fb66d0a --- /dev/null +++ b/tests/integration/targets/vyos_ospfv3/tests/cli/_populate.yaml @@ -0,0 +1,13 @@ +--- +- name: Setup + vars: + lines: "set protocols ospfv3 area 2 export-list 'export1' \n + set protocols ospfv3 area 2 import-list 'import1' \n + set protocols ospfv3 area 2 range '2001:db10::/32' \n + set protocols ospfv3 area 2 range '2001:db20::/32' \n + set protocols ospfv3 area 2 range '2001:db30::/32' \n + set protocols ospfv3 area 3 range '2001:db40::/32' \n + set protocols ospfv3 parameters router-id '192.0.2.10' \n + set protocols ospfv3 redistribute 'bgp'" + ansible.netcommon.cli_config: + config: '{{ lines }}' diff --git a/tests/integration/targets/vyos_ospfv3/tests/cli/_remove_config.yaml b/tests/integration/targets/vyos_ospfv3/tests/cli/_remove_config.yaml new file mode 100644 index 0000000..2a47505 --- /dev/null +++ b/tests/integration/targets/vyos_ospfv3/tests/cli/_remove_config.yaml @@ -0,0 +1,6 @@ +--- +- name: Remove Config + vars: + lines: "delete protocols ospfv3\n" + ansible.netcommon.cli_config: + config: '{{ lines }}' diff --git a/tests/integration/targets/vyos_ospfv3/tests/cli/deleted.yaml b/tests/integration/targets/vyos_ospfv3/tests/cli/deleted.yaml new file mode 100644 index 0000000..d400ff1 --- /dev/null +++ b/tests/integration/targets/vyos_ospfv3/tests/cli/deleted.yaml @@ -0,0 +1,48 @@ +--- +- debug: + msg: Start vyos_ospfv3 deleted integration tests ansible_connection={{ + ansible_connection }} + +- include_tasks: _populate.yaml + +- block: + + - name: Delete attributes of firewall. + register: result + vyos.vyos.vyos_ospfv3: &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_ospfv3: *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_ospfv3/tests/cli/empty_config.yaml b/tests/integration/targets/vyos_ospfv3/tests/cli/empty_config.yaml new file mode 100644 index 0000000..fec61ab --- /dev/null +++ b/tests/integration/targets/vyos_ospfv3/tests/cli/empty_config.yaml @@ -0,0 +1,49 @@ +--- +- debug: + msg: START vyos_ospfv3 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_ospfv3: + 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_ospfv3: + 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_ospfv3: + 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_ospfv3: + 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_ospfv3/tests/cli/gathered.yaml b/tests/integration/targets/vyos_ospfv3/tests/cli/gathered.yaml new file mode 100644 index 0000000..6645b99 --- /dev/null +++ b/tests/integration/targets/vyos_ospfv3/tests/cli/gathered.yaml @@ -0,0 +1,25 @@ +--- +- debug: + msg: START vyos_ospfv3 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_ospfv3: + 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_ospfv3/tests/cli/merged.yaml b/tests/integration/targets/vyos_ospfv3/tests/cli/merged.yaml new file mode 100644 index 0000000..9309500 --- /dev/null +++ b/tests/integration/targets/vyos_ospfv3/tests/cli/merged.yaml @@ -0,0 +1,62 @@ +--- +- debug: + msg: START vyos_ospfv3 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_ospfv3: &id001 + config: + areas: + - area_id: '2' + export_list: 'export1' + import_list: 'import1' + range: + - address: '2001:db10::/32' + - address: '2001:db20::/32' + - address: '2001:db30::/32' + - area_id: '3' + range: + - address: '2001:db40::/32' + parameters: + router_id: '192.0.2.10' + redistribute: + - route_type: 'bgp' + 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_ospfv3: *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_ospfv3/tests/cli/merged_update.yaml b/tests/integration/targets/vyos_ospfv3/tests/cli/merged_update.yaml new file mode 100644 index 0000000..a65cfaf --- /dev/null +++ b/tests/integration/targets/vyos_ospfv3/tests/cli/merged_update.yaml @@ -0,0 +1,61 @@ +--- +- debug: + msg: START vyos_ospfv3 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_ospfv3: &id001 + config: + areas: + - area_id: '2' + range: + - address: '2001:db10::/32' + - area_id: '3' + range: + - address: '2001:db40::/32' + - address: '2001:db70::/32' + parameters: + router_id: '192.0.2.10' + redistribute: + - route_type: 'bgp' + state: merged + + - name: Assert that before dicts were correctly generated + assert: + that: "{{ populate == 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_ospfv3: *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 \ No newline at end of file diff --git a/tests/integration/targets/vyos_ospfv3/tests/cli/parsed.yaml b/tests/integration/targets/vyos_ospfv3/tests/cli/parsed.yaml new file mode 100644 index 0000000..6287083 --- /dev/null +++ b/tests/integration/targets/vyos_ospfv3/tests/cli/parsed.yaml @@ -0,0 +1,15 @@ +--- +- debug: + msg: START vyos_ospfv3 parsed integration tests on connection={{ ansible_connection + }} + +- name: Parse externally provided ospfv3 config to agnostic model + register: result + vyos.vyos.vyos_ospfv3: + 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_ospfv3/tests/cli/rendered.yaml b/tests/integration/targets/vyos_ospfv3/tests/cli/rendered.yaml new file mode 100644 index 0000000..3f714ce --- /dev/null +++ b/tests/integration/targets/vyos_ospfv3/tests/cli/rendered.yaml @@ -0,0 +1,38 @@ +--- +- debug: + msg: START vyos_ospfv3 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_ospfv3: + config: + redistribute: + - route_type: 'bgp' + parameters: + router_id: '192.0.2.10' + areas: + - area_id: '2' + export_list: 'export1' + import_list: 'import1' + range: + - address: '2001:db10::/32' + - address: '2001:db20::/32' + - address: '2001:db30::/32' + - area_id: '3' + range: + - address: '2001:db40::/32' + 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_ospfv3 rendered integration tests on connection={{ ansible_connection }} diff --git a/tests/integration/targets/vyos_ospfv3/tests/cli/replaced.yaml b/tests/integration/targets/vyos_ospfv3/tests/cli/replaced.yaml new file mode 100644 index 0000000..74d25db --- /dev/null +++ b/tests/integration/targets/vyos_ospfv3/tests/cli/replaced.yaml @@ -0,0 +1,66 @@ +--- +- debug: + msg: START vyos_ospfv3 replaced integration tests on connection={{ + ansible_connection }} + +- include_tasks: _remove_config.yaml + +- include_tasks: _populate.yaml + +- block: + + - name: Replace device configurations of listed ospfv3 routes with provided configurations + register: result + vyos.vyos.vyos_ospfv3: &id001 + config: + redistribute: + - route_type: 'bgp' + parameters: + router_id: '192.0.2.10' + areas: + - area_id: '2' + export_list: 'export1' + import_list: 'import1' + + range: + - address: '2001:db10::/32' + - address: '2001:db30::/32' + - address: '2001:db50::/32' + - area_id: '4' + range: + - address: '2001:db60::/32' + 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 ospfv3 routes with provided configurarions + (IDEMPOTENT) + register: result + vyos.vyos.vyos_ospfv3: *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_ospfv3/tests/cli/rtt.yaml b/tests/integration/targets/vyos_ospfv3/tests/cli/rtt.yaml new file mode 100644 index 0000000..d817554 --- /dev/null +++ b/tests/integration/targets/vyos_ospfv3/tests/cli/rtt.yaml @@ -0,0 +1,75 @@ +--- +- debug: + msg: START vyos_ospfv3 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_ospfv3: + config: + areas: + - area_id: '2' + export_list: 'export1' + import_list: 'import1' + range: + - address: '2001:db10::/32' + - address: '2001:db20::/32' + - address: '2001:db30::/32' + - area_id: '3' + range: + - address: '2001:db40::/32' + parameters: + router_id: '192.0.2.10' + redistribute: + - route_type: 'bgp' + state: merged + + - name: Gather ospfv3 facts + vyos.vyos.vyos_facts: + gather_subset: + - default + gather_network_resources: + - ospfv3 + + - name: Apply the provided configuration (config to be reverted) + register: result + vyos.vyos.vyos_ospfv3: + config: + redistribute: + - route_type: 'bgp' + parameters: + router_id: '192.0.2.10' + areas: + - area_id: '2' + export_list: 'export1' + import_list: 'import1' + + range: + - address: '2001:db10::/32' + - address: '2001:db30::/32' + - address: '2001:db50::/32' + - area_id: '4' + range: + - address: '2001:db60::/32' + 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_ospfv3: + config: "{{ ansible_facts['network_resources']['ospfv3'] }}" + 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_ospfv3/vars/main.yaml b/tests/integration/targets/vyos_ospfv3/vars/main.yaml new file mode 100644 index 0000000..d384e2a --- /dev/null +++ b/tests/integration/targets/vyos_ospfv3/vars/main.yaml @@ -0,0 +1,140 @@ +--- +merged: + before: {} + commands: + - set protocols ospfv3 redistribute bgp + - set protocols ospfv3 parameters router-id '192.0.2.10' + - set protocols ospfv3 area 2 range 2001:db10::/32 + - set protocols ospfv3 area 2 range 2001:db20::/32 + - set protocols ospfv3 area 2 range 2001:db30::/32 + - set protocols ospfv3 area '2' + - set protocols ospfv3 area 2 export-list export1 + - set protocols ospfv3 area 2 import-list import1 + - set protocols ospfv3 area '3' + - set protocols ospfv3 area 3 range 2001:db40::/32 + after: + areas: + - area_id: '2' + export_list: 'export1' + import_list: 'import1' + range: + - address: '2001:db10::/32' + - address: '2001:db20::/32' + - address: '2001:db30::/32' + - area_id: '3' + range: + - address: '2001:db40::/32' + parameters: + router_id: '192.0.2.10' + redistribute: + - route_type: 'bgp' + +merged_update: + commands: + - set protocols ospfv3 area 3 range 2001:db70::/32 + after: + areas: + - area_id: '2' + export_list: 'export1' + import_list: 'import1' + range: + - address: '2001:db10::/32' + - address: '2001:db20::/32' + - address: '2001:db30::/32' + - area_id: '3' + range: + - address: '2001:db40::/32' + - address: '2001:db70::/32' + parameters: + router_id: '192.0.2.10' + redistribute: + - route_type: 'bgp' +populate: + areas: + - area_id: '2' + export_list: 'export1' + import_list: 'import1' + range: + - address: '2001:db10::/32' + - address: '2001:db20::/32' + - address: '2001:db30::/32' + - area_id: '3' + range: + - address: '2001:db40::/32' + parameters: + router_id: '192.0.2.10' + redistribute: + - route_type: 'bgp' +replaced: + commands: + - delete protocols ospfv3 area 2 range 2001:db20::/32 + - delete protocols ospfv3 area 3 + - set protocols ospfv3 area 2 range 2001:db50::/32 + - set protocols ospfv3 area '4' + - set protocols ospfv3 area 4 range 2001:db60::/32 + after: + areas: + - area_id: '2' + export_list: 'export1' + import_list: 'import1' + range: + - address: '2001:db10::/32' + - address: '2001:db30::/32' + - address: '2001:db50::/32' + - area_id: '4' + range: + - address: '2001:db60::/32' + parameters: + router_id: '192.0.2.10' + redistribute: + - route_type: 'bgp' +rendered: + commands: + - set protocols ospfv3 redistribute bgp + - set protocols ospfv3 parameters router-id '192.0.2.10' + - set protocols ospfv3 area 2 range 2001:db10::/32 + - set protocols ospfv3 area 2 range 2001:db20::/32 + - set protocols ospfv3 area 2 range 2001:db30::/32 + - set protocols ospfv3 area '2' + - set protocols ospfv3 area 2 export-list export1 + - set protocols ospfv3 area 2 import-list import1 + - set protocols ospfv3 area '3' + - set protocols ospfv3 area 3 range 2001:db40::/32 +parsed: + after: + areas: + - area_id: '2' + export_list: 'export1' + import_list: 'import1' + range: + - address: '2001:db10::/32' + - address: '2001:db20::/32' + - address: '2001:db30::/32' + - area_id: '3' + range: + - address: '2001:db40::/32' + parameters: + router_id: '192.0.2.10' + redistribute: + - route_type: 'bgp' +deleted: + commands: + - 'delete protocols ospfv3' + after: {} +round_trip: + after: + areas: + - area_id: '2' + export_list: 'export1' + import_list: 'import1' + range: + - address: '2001:db10::/32' + - address: '2001:db30::/32' + - address: '2001:db50::/32' + - area_id: '4' + range: + - address: '2001:db60::/32' + parameters: + router_id: '192.0.2.10' + redistribute: + - route_type: 'bgp' diff --git a/tests/unit/modules/network/vyos/fixtures/vyos_ospfv3_config.cfg b/tests/unit/modules/network/vyos/fixtures/vyos_ospfv3_config.cfg new file mode 100644 index 0000000..060b9b3 --- /dev/null +++ b/tests/unit/modules/network/vyos/fixtures/vyos_ospfv3_config.cfg @@ -0,0 +1,6 @@ +set protocols ospfv3 area 12 export-list 'export1' +set protocols ospfv3 area 12 import-list 'import1' +set protocols ospfv3 area 12 range '2001:db11::/32' +set protocols ospfv3 area 12 range '2001:db22::/32' +set protocols ospfv3 area 12 range '2001:db33::/32' +set protocols ospfv3 area 13 range '2001:db44::/32' \ No newline at end of file diff --git a/tests/unit/modules/network/vyos/test_vyos_ospfv3.py b/tests/unit/modules/network/vyos/test_vyos_ospfv3.py new file mode 100644 index 0000000..1d9cb3a --- /dev/null +++ b/tests/unit/modules/network/vyos/test_vyos_ospfv3.py @@ -0,0 +1,348 @@ +# (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_ospfv3 +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_ospfv3 + + 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.ospfv3.ospfv3.Ospfv3Facts.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_ospfv3_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_ospfv3_merged_new_config(self): + set_module_args( + dict( + config=dict( + redistribute=[dict(route_type="bgp")], + parameters=dict(router_id="192.0.2.10"), + areas=[ + dict( + area_id="2", + export_list="export1", + import_list="import1", + range=[ + dict(address="2001:db10::/32"), + dict(address="2001:db20::/32"), + dict(address="2001:db30::/32"), + ], + ), + dict( + area_id="3", + range=[dict(address="2001:db40::/32"),], + ), + ], + ), + state="merged", + ) + ) + commands = [ + "set protocols ospfv3 redistribute bgp", + "set protocols ospfv3 parameters router-id '192.0.2.10'", + "set protocols ospfv3 area 2 range 2001:db10::/32", + "set protocols ospfv3 area 2 range 2001:db20::/32", + "set protocols ospfv3 area 2 range 2001:db30::/32", + "set protocols ospfv3 area '2'", + "set protocols ospfv3 area 2 export-list export1", + "set protocols ospfv3 area 2 import-list import1", + "set protocols ospfv3 area '3'", + "set protocols ospfv3 area 3 range 2001:db40::/32", + ] + self.execute_module(changed=True, commands=commands) + + def test_vyos_ospfv3_merged_idem(self): + set_module_args( + dict( + config=dict( + areas=[ + dict( + area_id="12", + export_list="export1", + import_list="import1", + range=[ + dict(address="2001:db11::/32"), + dict(address="2001:db22::/32"), + dict(address="2001:db33::/32"), + ], + ), + dict( + area_id="13", + range=[dict(address="2001:db44::/32"),], + ), + ], + ), + state="merged", + ) + ) + self.execute_module(changed=False, commands=[]) + + def test_vyos_ospfv3_merged_update_existing(self): + set_module_args( + dict( + config=dict( + redistribute=[dict(route_type="bgp")], + parameters=dict(router_id="192.0.2.10"), + areas=[ + dict( + area_id="12", + export_list="export1", + import_list="import1", + range=[ + dict(address="2001:db11::/32"), + dict(address="2001:db22::/32"), + dict(address="2001:db33::/32"), + ], + ), + dict( + area_id="13", + range=[ + dict(address="2001:db44::/32"), + dict(address="2001:db55::/32"), + ], + ), + ], + ), + state="merged", + ) + ) + commands = [ + "set protocols ospfv3 redistribute bgp", + "set protocols ospfv3 parameters router-id '192.0.2.10'", + "set protocols ospfv3 area 13 range 2001:db55::/32", + ] + self.execute_module(changed=True, commands=commands) + + def test_vyos_ospfv3_replaced(self): + set_module_args( + dict( + config=dict( + redistribute=[dict(route_type="bgp")], + parameters=dict(router_id="192.0.2.10"), + areas=[ + dict( + area_id="12", + export_list="export1", + import_list="import1", + range=[ + dict(address="2001:db10::/32"), + dict(address="2001:db22::/32"), + dict(address="2001:db33::/32"), + ], + ), + dict( + area_id="14", + range=[dict(address="2001:db40::/32"),], + ), + ], + ), + state="replaced", + ) + ) + commands = [ + "set protocols ospfv3 redistribute bgp", + "set protocols ospfv3 parameters router-id '192.0.2.10'", + "delete protocols ospfv3 area 12 range 2001:db11::/32", + "set protocols ospfv3 area 12 range 2001:db10::/32", + "delete protocols ospfv3 area 13", + "set protocols ospfv3 area '14'", + "set protocols ospfv3 area 14 range 2001:db40::/32", + ] + self.execute_module(changed=True, commands=commands) + + def test_vyos_ospfv3_replaced_idem(self): + set_module_args( + dict( + config=dict( + areas=[ + dict( + area_id="12", + export_list="export1", + import_list="import1", + range=[ + dict(address="2001:db11::/32"), + dict(address="2001:db22::/32"), + dict(address="2001:db33::/32"), + ], + ), + dict( + area_id="13", + range=[dict(address="2001:db44::/32"),], + ), + ], + ), + state="replaced", + ) + ) + self.execute_module(changed=False, commands=[]) + + def test_vyos_ospfv3_deleted_no_config(self): + set_module_args(dict(config=None, state="deleted")) + commands = ["delete protocols ospfv3"] + self.execute_module(changed=True, commands=commands) + + def test_vyos_ospfv3_gathered(self): + set_module_args(dict(state="gathered")) + result = self.execute_module( + changed=False, filename="vyos_ospfv3_config.cfg" + ) + gather_dict = { + "areas": [ + { + "area_id": "12", + "export_list": "export1", + "import_list": "import1", + "range": [ + {"address": "2001:db11::/32"}, + {"address": "2001:db22::/32"}, + {"address": "2001:db33::/32"}, + ], + }, + {"area_id": "13", "range": [{"address": "2001:db44::/32"}]}, + ], + } + self.assertEqual(sorted(gather_dict), sorted(result["gathered"])) + + def test_vyos_ospfv3_parsed(self): + parsed_str = """set protocols ospfv3 area 2 export-list 'export1' +set protocols ospfv3 area 2 import-list 'import1' +set protocols ospfv3 area 2 range '2001:db10::/32' +set protocols ospfv3 area 2 range '2001:db20::/32' +set protocols ospfv3 area 2 range '2001:db30::/32' +set protocols ospfv3 area 3 range '2001:db40::/32' +set protocols ospfv3 parameters router-id '192.0.2.10' +set protocols ospfv3 redistribute 'bgp'""" + set_module_args(dict(running_config=parsed_str, state="parsed")) + result = self.execute_module(changed=False) + parsed_dict = { + "areas": [ + { + "area_id": "2", + "export_list": "export1", + "import_list": "import1", + "range": [ + {"address": "2001:db10::/32"}, + {"address": "2001:db20::/32"}, + {"address": "2001:db30::/32"}, + ], + }, + {"area_id": "3", "range": [{"address": "2001:db40::/32"}]}, + ], + "parameters": {"router_id": "192.0.2.10"}, + "redistribute": [{"route_type": "bgp"}], + } + self.assertEqual(sorted(parsed_dict), sorted(result["parsed"])) + + def test_vyos_ospfv3_rendered(self): + set_module_args( + dict( + config=dict( + redistribute=[dict(route_type="bgp")], + parameters=dict(router_id="192.0.2.10"), + areas=[ + dict( + area_id="2", + export_list="export1", + import_list="import1", + range=[ + dict(address="2001:db10::/32"), + dict(address="2001:db20::/32"), + dict(address="2001:db30::/32"), + ], + ), + dict( + area_id="3", + range=[dict(address="2001:db40::/32"),], + ), + ], + ), + state="rendered", + ) + ) + commands = [ + "set protocols ospfv3 redistribute bgp", + "set protocols ospfv3 parameters router-id '192.0.2.10'", + "set protocols ospfv3 area 2 range 2001:db10::/32", + "set protocols ospfv3 area 2 range 2001:db20::/32", + "set protocols ospfv3 area 2 range 2001:db30::/32", + "set protocols ospfv3 area '2'", + "set protocols ospfv3 area 2 export-list export1", + "set protocols ospfv3 area 2 import-list import1", + "set protocols ospfv3 area '3'", + "set protocols ospfv3 area 3 range 2001:db40::/32", + ] + result = self.execute_module(changed=False) + self.assertEqual( + sorted(result["rendered"]), sorted(commands), result["rendered"] + ) diff --git a/tests/unit/modules/network/vyos/vyos_module.py b/tests/unit/modules/network/vyos/vyos_module.py index fb15c71..49d4652 100644 --- a/tests/unit/modules/network/vyos/vyos_module.py +++ b/tests/unit/modules/network/vyos/vyos_module.py @@ -60,6 +60,7 @@ class TestVyosModule(ModuleTestCase): commands=None, sort=True, defaults=False, + filename=None, ): self.load_fixtures(commands) -- cgit v1.2.3 From 44a2e8c885b1d050f9a36fc095403c1568cc1cfd Mon Sep 17 00:00:00 2001 From: Rohit Thakur Date: Mon, 11 May 2020 13:48:29 +0530 Subject: comments incorporated Signed-off-by: Rohit Thakur --- .../network/vyos/config/ospfv3/ospfv3.py | 1 - plugins/module_utils/network/vyos/utils/utils.py | 1 - plugins/modules/vyos_ospfv3.py | 141 ++++++++++----------- .../targets/vyos_ospfv3/tests/cli/deleted.yaml | 4 +- .../vyos_ospfv3/tests/cli/merged_update.yaml | 2 +- .../integration/targets/vyos_ospfv3/vars/main.yaml | 64 +++++----- 6 files changed, 105 insertions(+), 108 deletions(-) diff --git a/plugins/module_utils/network/vyos/config/ospfv3/ospfv3.py b/plugins/module_utils/network/vyos/config/ospfv3/ospfv3.py index de972bc..acda380 100644 --- a/plugins/module_utils/network/vyos/config/ospfv3/ospfv3.py +++ b/plugins/module_utils/network/vyos/config/ospfv3/ospfv3.py @@ -29,7 +29,6 @@ from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.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, diff --git a/plugins/module_utils/network/vyos/utils/utils.py b/plugins/module_utils/network/vyos/utils/utils.py index f2986aa..96feddd 100644 --- a/plugins/module_utils/network/vyos/utils/utils.py +++ b/plugins/module_utils/network/vyos/utils/utils.py @@ -7,7 +7,6 @@ from __future__ import absolute_import, division, print_function __metaclass__ = type -import q from ansible.module_utils.six import iteritems from ansible_collections.ansible.netcommon.plugins.module_utils.compat import ( ipaddress, diff --git a/plugins/modules/vyos_ospfv3.py b/plugins/modules/vyos_ospfv3.py index ae93500..e2d3ff7 100644 --- a/plugins/modules/vyos_ospfv3.py +++ b/plugins/modules/vyos_ospfv3.py @@ -80,11 +80,11 @@ options: parameters: descriptions: OSPFv3 specific parameters. type: dict - suboptions: - router_id: + suboptions: + router_id: description: Override the default router identifier. type: str - redistribute: + redistribute: description: Redistribute information from another routing protocol. type: list elements: dict @@ -92,10 +92,10 @@ options: route_type: description: Route type to redistribute. type: str - choices: ['bgp', 'connected', 'kernel', 'ripng', 'static'] + choices: ['bgp', 'connected', 'kernel', 'ripng', 'static'] route_map: description: Route map references. - type: str + type: str running_config: description: - This option is used only with state I(parsed). @@ -128,25 +128,24 @@ EXAMPLES = """ # # - name: Merge the provided configuration with the exisiting running configuration - vyos.vyos.vyos_ospfv3: - config: - redistribute: - - route_type: 'bgp' - parameters: - router_id: '192.0.2.10' - areas: - - area_id: '2' - export_list: 'export1' - import_list: 'import1' - - range: - - address: '2001:db10::/32' - - address: '2001:db20::/32' - - address: '2001:db30::/32' - - area_id: '3' - range: - - address: '2001:db40::/32' - state: merged + vyos.vyos.vyos_ospfv3: + config: + redistribute: + - route_type: 'bgp' + parameters: + router_id: '192.0.2.10' + areas: + - area_id: '2' + export_list: 'export1' + import_list: 'import1' + range: + - address: '2001:db10::/32' + - address: '2001:db20::/32' + - address: '2001:db30::/32' + - area_id: '3' + range: + - address: '2001:db40::/32' + state: merged # # # ------------------------- @@ -235,25 +234,24 @@ EXAMPLES = """ # set protocols ospfv3 redistribute 'bgp' # - name: Replace ospfv3 routes attributes configuration. - vyos.vyos.vyos_ospfv3: - config: - redistribute: - - route_type: 'bgp' - parameters: - router_id: '192.0.2.10' - areas: - - area_id: '2' - export_list: 'export1' - import_list: 'import1' - - range: - - address: '2001:db10::/32' - - address: '2001:db30::/32' - - address: '2001:db50::/32' - - area_id: '4' - range: - - address: '2001:db60::/32' - state: replaced + vyos.vyos.vyos_ospfv3: + config: + redistribute: + - route_type: 'bgp' + parameters: + router_id: '192.0.2.10' + areas: + - area_id: '2' + export_list: 'export1' + import_list: 'import1' + range: + - address: '2001:db10::/32' + - address: '2001:db30::/32' + - address: '2001:db50::/32' + - area_id: '4' + range: + - address: '2001:db60::/32' + state: replaced # # # ------------------------- @@ -360,25 +358,24 @@ EXAMPLES = """ # # - name: Render the commands for provided configuration - vyos.vyos.vyos_ospfv3: - config: - redistribute: - - route_type: 'bgp' - parameters: - router_id: '192.0.2.10' - areas: - - area_id: '2' - export_list: 'export1' - import_list: 'import1' - - range: - - address: '2001:db10::/32' - - address: '2001:db20::/32' - - address: '2001:db30::/32' - - area_id: '3' - range: - - address: '2001:db40::/32' - state: rendered + vyos.vyos.vyos_ospfv3: + config: + redistribute: + - route_type: 'bgp' + parameters: + router_id: '192.0.2.10' + areas: + - area_id: '2' + export_list: 'export1' + import_list: 'import1' + range: + - address: '2001:db10::/32' + - address: '2001:db20::/32' + - address: '2001:db30::/32' + - area_id: '3' + range: + - address: '2001:db40::/32' + state: rendered # # # ------------------------- @@ -405,9 +402,9 @@ EXAMPLES = """ # # - name: Parse the commands to provide structured configuration. - vyos.vyos.vyos_ospfv3: - running_config: - "set protocols ospfv3 area 2 export-list 'export1' + vyos.vyos.vyos_ospfv3: + running_config: + "set protocols ospfv3 area 2 export-list 'export1' set protocols ospfv3 area 2 import-list 'import1' set protocols ospfv3 area 2 range '2001:db10::/32' set protocols ospfv3 area 2 range '2001:db20::/32' @@ -415,7 +412,7 @@ set protocols ospfv3 area 2 range '2001:db30::/32' set protocols ospfv3 area 3 range '2001:db40::/32' set protocols ospfv3 parameters router-id '192.0.2.10' set protocols ospfv3 redistribute 'bgp'" - state: parsed + state: parsed # # # ------------------------- @@ -477,9 +474,9 @@ set protocols ospfv3 redistribute 'bgp'" # set protocols ospfv3 redistribute 'bgp' # - name: Gather ospfv3 routes config with provided configurations - vyos.vyos.vyos_ospfv3: - config: - state: gathered + vyos.vyos.vyos_ospfv3: + config: + state: gathered # # # ------------------------- @@ -553,9 +550,9 @@ set protocols ospfv3 redistribute 'bgp'" # set protocols ospfv3 redistribute 'bgp' # - name: Delete attributes of ospfv3 routes. - vyos.vyos.vyos_ospfv3: - config: - state: deleted + vyos.vyos.vyos_ospfv3: + config: + state: deleted # # # ------------------------ diff --git a/tests/integration/targets/vyos_ospfv3/tests/cli/deleted.yaml b/tests/integration/targets/vyos_ospfv3/tests/cli/deleted.yaml index d400ff1..55bec18 100644 --- a/tests/integration/targets/vyos_ospfv3/tests/cli/deleted.yaml +++ b/tests/integration/targets/vyos_ospfv3/tests/cli/deleted.yaml @@ -7,7 +7,7 @@ - block: - - name: Delete attributes of firewall. + - name: Delete ospfv3 routes register: result vyos.vyos.vyos_ospfv3: &id001 config: @@ -29,7 +29,7 @@ that: - "{{ deleted['after'] == result['after'] }}" - - name: Delete attributes of given interfaces (IDEMPOTENT) + - name: Delete ospfv3 routes (IDEMPOTENT) register: result vyos.vyos.vyos_ospfv3: *id001 diff --git a/tests/integration/targets/vyos_ospfv3/tests/cli/merged_update.yaml b/tests/integration/targets/vyos_ospfv3/tests/cli/merged_update.yaml index a65cfaf..0d506b3 100644 --- a/tests/integration/targets/vyos_ospfv3/tests/cli/merged_update.yaml +++ b/tests/integration/targets/vyos_ospfv3/tests/cli/merged_update.yaml @@ -58,4 +58,4 @@ - "{{ merged_update['after'] == result['before'] }}" always: - - include_tasks: _remove_config.yaml \ No newline at end of file + - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_ospfv3/vars/main.yaml b/tests/integration/targets/vyos_ospfv3/vars/main.yaml index d384e2a..6ded89a 100644 --- a/tests/integration/targets/vyos_ospfv3/vars/main.yaml +++ b/tests/integration/targets/vyos_ospfv3/vars/main.yaml @@ -13,42 +13,44 @@ merged: - set protocols ospfv3 area '3' - set protocols ospfv3 area 3 range 2001:db40::/32 after: - areas: - - area_id: '2' - export_list: 'export1' - import_list: 'import1' - range: - - address: '2001:db10::/32' - - address: '2001:db20::/32' - - address: '2001:db30::/32' - - area_id: '3' - range: - - address: '2001:db40::/32' - parameters: - router_id: '192.0.2.10' - redistribute: - - route_type: 'bgp' + areas: + - area_id: '2' + export_list: 'export1' + import_list: 'import1' + range: + - address: '2001:db10::/32' + - address: '2001:db20::/32' + - address: '2001:db30::/32' + - area_id: '3' + range: + - address: '2001:db40::/32' + parameters: + router_id: '192.0.2.10' + redistribute: + - route_type: 'bgp' + merged_update: commands: - set protocols ospfv3 area 3 range 2001:db70::/32 after: - areas: - - area_id: '2' - export_list: 'export1' - import_list: 'import1' - range: - - address: '2001:db10::/32' - - address: '2001:db20::/32' - - address: '2001:db30::/32' - - area_id: '3' - range: - - address: '2001:db40::/32' - - address: '2001:db70::/32' - parameters: - router_id: '192.0.2.10' - redistribute: - - route_type: 'bgp' + areas: + - area_id: '2' + export_list: 'export1' + import_list: 'import1' + range: + - address: '2001:db10::/32' + - address: '2001:db20::/32' + - address: '2001:db30::/32' + - area_id: '3' + range: + - address: '2001:db40::/32' + - address: '2001:db70::/32' + parameters: + router_id: '192.0.2.10' + redistribute: + - route_type: 'bgp' + populate: areas: - area_id: '2' -- cgit v1.2.3