diff options
-rw-r--r-- | plugins/module_utils/network/vyos/argspec/ospfv2/__init__.py | 0 | ||||
-rw-r--r-- | plugins/module_utils/network/vyos/argspec/ospfv2/ospfv2.py | 359 | ||||
-rw-r--r-- | plugins/module_utils/network/vyos/config/ospfv2/__init__.py | 0 | ||||
-rw-r--r-- | plugins/module_utils/network/vyos/config/ospfv2/ospfv2.py | 570 | ||||
-rw-r--r-- | plugins/module_utils/network/vyos/facts/facts.py | 3 | ||||
-rw-r--r-- | plugins/module_utils/network/vyos/facts/ospfv2/__init__.py | 0 | ||||
-rw-r--r-- | plugins/module_utils/network/vyos/facts/ospfv2/ospfv2.py | 395 | ||||
-rw-r--r-- | plugins/module_utils/network/vyos/utils/utils.py | 9 | ||||
-rw-r--r-- | plugins/modules/vyos_facts.py | 2 | ||||
-rw-r--r-- | plugins/modules/vyos_ospfv2.py | 1369 |
10 files changed, 2699 insertions, 8 deletions
diff --git a/plugins/module_utils/network/vyos/argspec/ospfv2/__init__.py b/plugins/module_utils/network/vyos/argspec/ospfv2/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/plugins/module_utils/network/vyos/argspec/ospfv2/__init__.py diff --git a/plugins/module_utils/network/vyos/argspec/ospfv2/ospfv2.py b/plugins/module_utils/network/vyos/argspec/ospfv2/ospfv2.py new file mode 100644 index 0000000..1b11d3c --- /dev/null +++ b/plugins/module_utils/network/vyos/argspec/ospfv2/ospfv2.py @@ -0,0 +1,359 @@ +# +# -*- coding: utf-8 -*- +# Copyright 2019 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +############################################# +# WARNING # +############################################# +# +# This file is auto generated by the resource +# module builder playbook. +# +# Do not edit this file manually. +# +# Changes to this file will be over written +# by the resource module builder. +# +# Changes should be made in the model used to +# generate this file or in the resource module +# builder template. +# +############################################# +""" +The arg spec for the vyos_ospfv2 module +""" + + +class Ospfv2Args(object): # pylint: disable=R0903 + """The arg spec for the vyos_ospfv2 module + """ + def __init__(self, **kwargs): + pass + + argument_spec = { + 'config': { + 'elements': 'dict', + 'options': { + 'auto_cost': { + 'options': { + 'reference_bandwidth': { + 'type': 'int' + } + }, + 'type': 'dict' + }, + 'default_information': { + 'options': { + 'originate': { + 'options': { + 'always': { + 'type': 'bool' + }, + 'metric': { + 'type': 'int' + }, + 'metric_type': { + 'type': 'int' + }, + 'route_map': { + 'type': 'str' + } + }, + 'type': 'dict' + } + }, + 'type': 'dict' + }, + 'default_metric': { + 'type': 'int' + }, + 'distance': { + 'options': { + 'global': { + 'type': 'int' + }, + 'ospf': { + 'options': { + 'external': { + 'type': 'int' + }, + 'inter_area': { + 'type': 'int' + }, + 'intra_area': { + 'type': 'int' + } + }, + 'type': 'dict' + } + }, + 'type': 'dict' + }, + 'log_adjacency_changes': { + 'choices': ['detail'], + 'type': 'str' + }, + 'max_metric': { + 'options': { + 'router_lsa': { + 'options': { + 'administrative': { + 'type': 'bool' + }, + 'on_shutdown': { + 'type': 'int' + }, + 'on_startup': { + 'type': 'int' + } + }, + 'type': 'dict' + } + }, + 'type': 'dict' + }, + 'mpls_te': { + 'options': { + 'enabled': { + 'type': 'bool' + }, + 'router_address': { + 'type': 'str' + } + }, + 'type': 'dict' + }, + 'neighbor': { + 'elements': 'dict', + 'options': { + 'neighbor_id': { + 'type': 'str' + }, + 'poll_interval': { + 'type': 'int' + }, + 'priority': { + 'type': 'int' + } + }, + 'type': 'list' + }, + 'ospf_area': { + 'elements': 'dict', + 'options': { + 'area': { + 'type': 'str' + }, + 'area_type': { + 'options': { + 'normal': { + 'type': 'bool' + }, + 'nssa': { + 'options': { + 'default_cost': { + 'type': 'int' + }, + 'no_summary': { + 'type': 'bool' + }, + 'translate': { + 'choices': + ['always', 'candidate', 'never'], + 'type': + 'str' + } + }, + 'type': 'dict' + }, + 'stub': { + 'options': { + 'default_cost': { + 'type': 'int' + }, + 'no_summary': { + 'type': 'bool' + } + }, + 'type': 'dict' + } + }, + 'type': 'dict' + }, + 'authentication': { + 'choices': ['plaintext-password', 'md5'], + 'type': 'str' + }, + 'network': { + 'elements': 'dict', + 'options': { + 'address': { + 'required': True, + 'type': 'str' + } + }, + 'type': 'list' + }, + 'range': { + 'elements': 'dict', + 'options': { + 'address': { + 'type': 'str' + }, + 'cost': { + 'type': 'int' + }, + 'not_advertise': { + 'type': 'bool' + }, + 'substitute': { + 'type': 'str' + } + }, + 'type': 'list' + }, + 'shortcut': { + 'choices': ['default', 'disable', 'enable'], + 'type': 'str' + }, + 'virtual_link': { + 'elements': 'dict', + 'options': { + 'address': { + 'type': 'str' + }, + 'authentication': { + 'options': { + 'md5': { + 'options': { + 'key_id': { + 'type': 'int' + }, + 'md5_key': { + 'type': 'str' + } + }, + 'type': 'dict' + }, + 'plaintext_password': { + 'type': 'str' + } + }, + 'type': 'dict' + }, + 'dead_interval': { + 'type': 'int' + }, + 'hello_interval': { + 'type': 'int' + }, + 'retransmit_interval': { + 'type': 'int' + }, + 'transmit_delay': { + 'type': 'int' + } + }, + 'type': 'list' + } + }, + 'type': 'list' + }, + 'parameters': { + 'options': { + 'abr_type': { + 'choices': + ['cisco', 'ibm', 'shortcut', 'standard'], + 'type': 'str' + }, + 'opaque_lsa': { + 'type': 'bool' + }, + 'rfc1583_compatibility': { + 'type': 'bool' + }, + 'router_id': { + 'type': 'str' + } + }, + 'type': 'dict' + }, + 'passive_interface': { + 'type': 'list' + }, + 'passive_interface_exclude': { + 'type': 'list' + }, + 'redistribute': { + 'elements': 'dict', + 'options': { + 'metric': { + 'type': 'int' + }, + 'metric_type': { + 'type': 'int' + }, + 'route_map': { + 'type': 'str' + }, + 'route_type': { + 'choices': + ['bgp', 'connected', 'kernel', 'rip', 'static'], + 'type': + 'str' + } + }, + 'type': 'list' + }, + 'route_map': { + 'type': 'list' + }, + 'timers': { + 'options': { + 'refresh': { + 'options': { + 'timers': { + 'type': 'int' + } + }, + 'type': 'dict' + }, + 'throttle': { + 'options': { + 'spf': { + 'options': { + 'delay': { + 'type': 'int' + }, + 'initial_holdtime': { + 'type': 'int' + }, + 'max_holdtime': { + 'type': 'int' + } + }, + 'type': 'dict' + } + }, + 'type': 'dict' + } + }, + 'type': 'dict' + } + }, + 'type': '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/ospfv2/__init__.py b/plugins/module_utils/network/vyos/config/ospfv2/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/plugins/module_utils/network/vyos/config/ospfv2/__init__.py diff --git a/plugins/module_utils/network/vyos/config/ospfv2/ospfv2.py b/plugins/module_utils/network/vyos/config/ospfv2/ospfv2.py new file mode 100644 index 0000000..0109ca1 --- /dev/null +++ b/plugins/module_utils/network/vyos/config/ospfv2/ospfv2.py @@ -0,0 +1,570 @@ +# +# -*- coding: utf-8 -*- +# Copyright 2019 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +""" +The vyos_ospfv2 class +It is in this file where the current configuration (as dict) +is compared to the provided configuration (as dict) and the command set +necessary to bring the current configuration to it's desired end-state is +created +""" +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +from copy import deepcopy +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base import ( + ConfigBase, +) +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ( + to_list, + remove_empties, +) +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.facts import Facts +from ansible.module_utils.six import iteritems + +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.utils import ( + list_diff_want_only, _in_target, _is_w_same, _bool_to_str +) + +class Ospfv2(ConfigBase): + """ + The vyos_ospfv2 class + """ + + gather_subset = [ + '!all', + '!min', + ] + + gather_network_resources = [ + 'ospfv2', + ] + + def __init__(self, module): + super(Ospfv2, self).__init__(module) + + def get_ospfv2_facts(self, data=None): + """ Get the 'facts' (the current configuration) + + :rtype: A dictionary + :returns: The current configuration as a dictionary + """ + facts, _warnings = Facts(self._module).get_facts(self.gather_subset, self.gather_network_resources, data=data) + ospfv2_facts = facts['ansible_network_resources'].get('ospfv2') + if not ospfv2_facts: + return [] + return ospfv2_facts + + def execute_module(self): + """ Execute the module + + :rtype: A dictionary + :returns: The result from module execution + """ + result = {'changed': False} + warnings = list() + commands = list() + + if self.state in self.ACTION_STATES: + existing_ospfv2_facts = self.get_ospfv2_facts() + else: + existing_ospfv2_facts = [] + + if self.state in self.ACTION_STATES or self.state == "rendered": + commands.extend(self.set_config(existing_ospfv2_facts)) + + if commands and self.state in self.ACTION_STATES: + if not self._module.check_mode: + self._connection.edit_config(commands) + result["changed"] = True + + if self.state in self.ACTION_STATES: + result["commands"] = commands + + if self.state in self.ACTION_STATES or self.state == "gathered": + changed_ospfv2_facts = self.get_ospfv2_facts() + elif self.state == "rendered": + result["rendered"] = commands + elif self.state == "parsed": + running_config = self._module.params["running_config"] + if not running_config: + self._module.fail_json( + msg="value of running_config parameter must not be empty for state parsed" + ) + result["parsed"] = self.get_ospfv2_facts( + data=running_config + ) + else: + changed_ospfv2_facts = [] + + if self.state in self.ACTION_STATES: + result["before"] = existing_ospfv2_facts + if result["changed"]: + result["after"] = changed_ospfv2_facts + elif self.state == "gathered": + result["gathered"] = changed_ospfv2_facts + + result["warnings"] = warnings + return result + + def set_config(self, existing_ospfv2_facts): + """ Collect the configuration from the args passed to the module, + collect the current configuration (as a dict from facts) + + :rtype: A list + :returns: the commands necessary to migrate the current configuration + to the desired configuration + """ + want = self._module.params['config'] + have = existing_ospfv2_facts + resp = self.set_state(want, have) + return to_list(resp) + + def set_state(self, w, h): + """ Select the appropriate function based on the state provided + + :param want: the desired configuration as a dictionary + :param have: the current configuration as a dictionary + :rtype: A list + :returns: the commands necessary to migrate the current configuration + to the desired configuration + """ + commands = [] + if self.state in ("merged", "replaced", "overridden", "rendered") and not w: + self._module.fail_json( + msg="value of config parameter must not be empty for state {0}".format( + self.state + ) + ) + if self.state == "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 ospf') + return commands + + def _render_ospf_param(self, want, have, opr=True): + """ + This function forms the set/delete commands for ospf leaf attributes + and triggers the process for other child attributes. + for firewall_global attributes. + :param w: the desired config. + :param h: the target config. + :param opr: True/False. + :return: generated commands list. + """ + commands = [] + w = deepcopy(remove_empties(want)) + leaf = ( + "default_metric", + "log_adjacency_changes" + ) + if w: + for key, val in iteritems(w): + if opr and key in leaf and not _is_w_same(w, have, key): + commands.append(self._form_attr_cmd(attr=key, val=_bool_to_str(val), opr=opr)) + elif not opr and key in leaf and not _in_target(have, key): + commands.append(self._form_attr_cmd(attr=key, val=_bool_to_str(val), opr=opr)) + else: + commands.extend(self._render_child_param(w, have, key, opr)) + return commands + + def _render_child_param(self, w, h, key, opr=True): + """ + This function invoke the function to extend commands + based on the key. + :param w: the desired configuration. + :param h: the current configuration. + :param key: attribute name. + :param opr: operation. + :return: list of commands. + """ + commands = [] + if key in ("neighbor", "redistribute"): + commands.extend(self._render_list_dict_param(key, w, h, opr=opr)) + elif key in ("default_information", 'max_metric'): + commands.extend(self._render_nested_dict_param(key, w, h, opr=opr)) + elif key in ("mpls_te", "auto_cost", "parameters", "auto_cost"): + commands.extend(self._render_dict_param(key, w, h, opr=opr)) + elif key in ("route_map", "passive_interface", "passive_interface_exclude"): + commands.extend(self._render_list_param(key, w, h, opr=opr)) + elif key == "ospf_area": + commands.extend(self._render_ospf_area(key, w, h, opr=opr)) + elif key == "timers": + commands.extend(self._render_timers(key, w, h, opr=opr, remove=remove)) + elif key == "distance": + commands.extend(self._render_distance(key, w, h, opr=opr, remove=remove)) + return commands + + def _render_dict_param(self, attr, want, have, opr=True): + """ + This function generate the commands for dictionary elements. + :param attr: attribute name. + :param w: the desired configuration. + :param h: the target config. + :param opr: True/False. + :return: generated list of commands. + """ + commands = [] + h = {} + if have: + h = have.get(attr) or {} + if not opr and not h: + commands.append(self._form_attr_cmd(attr=attr, opr=opr)) + elif want[attr]: + leaf_dict = {'auto_cost': 'reference_bandwidth', + 'mpls_te': ('enabled', 'router_address'), + 'parameters': ("router_id", "abr_type", "opaque_lsa", "rfc1583_compatibility")} + leaf = leaf_dict[attr] + for item, value in iteritems(want[attr]): + if opr and item in leaf and not _is_w_same(want[attr], h, item): + if item == "enabled": + item = 'enable' + if item in ("opaque_lsa", "enable", "rfc1583_compatibility"): + commands.append(self._form_attr_cmd(key=attr, attr=item, opr=opr)) + else: + commands.append(self._form_attr_cmd(key=attr, attr=item, val=value, opr=opr)) + elif not opr and item in leaf and not _in_target(h, item): + if item == "enabled": + commands.append(self._form_attr_cmd(key=attr, attr='enable', opr=opr)) + else: + commands.append(self._form_attr_cmd(key=attr, attr=item, opr=opr)) + return commands + + def _render_list_param(self, attr, want, have, cmd=None, opr=True): + """ + This function forms the commands for passed target list attributes'. + :param attr: attribute name. + :param w: the desired config. + :param h: the target config. + :param cmd: commands to be prepend. + :param opr: True/False. + :return: generated list of commands. + """ + commands = [] + h = [] + if want: + w = want.get(attr) or [] + if have: + h = have.get(attr) or [] + if not cmd: + cmd = self._compute_command(opr=opr) + if w: + if opr: + members = list_diff_want_only(w, h) + for member in members: + command = cmd + attr.replace("_","-") + " " + if attr == 'network': + command += member['address'] + else: + command += member + commands.append(command) + elif not opr: + if h: + for member in w: + if attr == 'network': + if not self.search_obj_in_have(h, member, 'address'): + commands.append(cmd + attr.replace("_","-") + ' ' + member['address']) + elif member not in h: + commands.append(cmd + attr.replace("_","-") + ' ' + member) + else: + commands.append(cmd + " " + attr.replace("_","-")) + return commands + + def _render_list_dict_param(self, attr, want, have, cmd=None, opr=True): + """ + This function forms the set/delete commands based on the 'opr' type + for attributes with in desired list of dictionary. + :param attr: attribute name. + :param w: the desired config. + :param h: the target config. + :param cmd: commands to be prepend. + :param opr: True/False. + :return: generated commands list. + """ + commands = [] + h = [] + name = {'redistribute': 'route_type', + 'neighbor': 'neighbor_id', + 'range': 'address', + 'vlink': 'address'} + leaf_dict = {'redistribute': ("metric", "route_map", "route_type", "metric_type"), + 'neighbor': ("priority", "poll_interval", "neighbor_id"), + 'range': ("cost", "address", "substitute", "not_advertise"), + 'vlink': ("address", "dead_interval", "transmit_delay", "hello_interval", "retransmit_interval") + } + leaf = leaf_dict[attr] + w = want.get(attr) or [] + if have: + h = have.get(attr) or [] + if not opr and not h: + commands.append(self._compute_command(attr=attr, opr=opr)) + elif w: + for w_item in w: + for key, val in iteritems(w_item): + if not cmd: + cmd = self._compute_command(opr=opr) + h_item = self.search_obj_in_have(h, w_item, name[attr]) + if opr and key in leaf and not _is_w_same(w_item, h_item, key): + if key == 'cost': + commands.append(cmd + attr + ' ' + w_item[name[attr]] + ' ' + key + ' ' + str(val)) + elif key == 'not_advertise': + commands.append(cmd + attr + ' ' + w_item[name[attr]] + ' ' + key.replace("_","-")) + elif key in ('route_type', 'neighbor_id', 'address'): + commands.append(cmd + attr + ' ' + str(val)) + elif key == 'authentication': + commands.append(self._render_vlink(key, w_item, h_item, cmd, opr)) + else: + commands.append(cmd + attr + ' ' + w_item[name[attr]] + ' ' + key.replace("_", "-") + ' ' + str(val)) + elif not opr and key in leaf and not _in_target(h_item, key): + if key in ('route_type', 'neighbor_id', 'address'): + commands.append(cmd + attr + ' ' + str(val)) + else: + commands.append(cmd + (attr + ' ' + w_item[name[attr]] + ' ' + key)) + return commands + + def _render_nested_dict_param(self, attr, want, have, opr=True): + """ + This function forms the set/delete commands based on the 'opr' type + for attributes with in desired nested dicts. + :param attr: attribute name. + :param w: the desired config. + :param h: the target config. + :param cmd: commands to be prepend. + :param opr: True/False. + :return: generated commands list. + """ + commands = [] + attr_dict = {'default_information': 'originate', + 'max_metric': 'router_lsa', + } + leaf_dict = {'default_information': ("always", "metric", "metric_type", "route_map"), + 'max_metric': ("always", "metric", "metric_type", "route_map"), + } + h = {} + w = want.get(attr) or {} + if have: + h = have.get(attr) or {} + if not opr and not h: + commands.append(self._form_attr_cmd(attr=attr, opr=opr)) + elif w: + key = attr_dict[attr] + w_attrib = want[attr].get(key) or {} + cmd = self._compute_command(opr=opr) + h_attrib = {} + if w_attrib: + leaf = leaf_dict[attr] + if h and key in h.keys(): + h_attrib = h.get(key) or {} + for item, val in iteritems(w[key]): + if opr and item in leaf and not _is_w_same(w[key], h_attrib, item): + if item in ('administrative', 'always'): + commands.append(cmd + (attr.replace("_","-") + " " + key.replace("_","-") + " " + item.replace("_","-"))) + else: + commands.append(cmd + (attr.replace("_","-") + " " + key.replace("_","-") + " " + item.replace("_","-") + " " + str(val))) + + elif not opr and item in leaf and not _in_target(h_attrib, item): + commands.append(cmd + (attr + " " + item)) + return commands + + def _render_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", "shortcut", "authentication") + if have: + h_lst = have.get(attr) or [] + if not opr and not h_lst: + commands.append(self._form_attr_cmd(attr='area', opr=opr)) + elif w_lst: + for w_area in w_lst: + cmd = self._compute_command(key='area', attr=_bool_to_str(w_area['area']), 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 == 'area_type': + commands.extend(self._render_area_type(w_area, h_area, key, cmd, opr)) + elif key == 'network': + commands.extend(self._render_list_param(key, w_area, h_area, cmd, opr)) + elif key == 'range': + commands.extend(self._render_list_dict_param(key, w_area, h_area, cmd, opr)) + elif key == 'virtual_link': + commands.extend(self._render_vlink(key, w_area, h_area, cmd, opr)) + return commands + + def _render_area_type(self, want, have, attr, cmd, opr=True): + """ + This function forms the set/delete commands based on the 'opr' type + for area_types attributes. + :param attr: attribute name. + :param w: the desired config. + :param h: the target config. + :param cmd: command to prepend. + :param opr: True/False. + :return: generated commands list. + """ + commands = [] + h_type = {} + w_type = want.get(attr) or [] + if have: + h_type = have.get(attr) or {} + if not opr and not h_type: + commands.append(cmd + attr.replace("_","-")) + elif w_type: + key = "normal" + if opr and key in w_type.keys() and not _is_w_same(w_type, h_type, key): + commands.append(cmd + attr.replace("_","-") + ' ' + key) + elif not opr and key in w_type.keys() and not (h_type and key in h_type.keys()): + commands.append(cmd + want['area'] + ' ' + attr.replace("_","-")) + + a_type = {'nssa': ("default_cost", "no_summary", "translate"), + 'stub': ("default_cost", "no_summary")} + for key in a_type: + w_area = want[attr].get(key) or {} + h_area = {} + if w_area: + if h_type and key in h_type.keys(): + h_area = h_type.get(key) or {} + for item, val in iteritems(w_type[key]): + if opr and item in a_type[key] and not _is_w_same(w_type[key], h_area, item): + commands.append(cmd + (attr.replace("_","-") + " " + key + " " + item.replace("_","-") + " " + str(val))) + elif not opr and item in a_type[key] and not (h_type and key in h_type): + commands.append(cmd + (want['area'] + ' ' + attr.replace("_","-") + " " + key + " " + item.replace("_","-"))) + return commands + + def _form_attr_cmd(self, key=None, attr=None, val=None, opr=True): + """ + This function forms the command for leaf attribute. + :param key: parent key. + :param attr: attribute name + :param value: value + :param opr: True/False. + :return: generated command. + """ + return self._compute_command(key, attr=self._map_attrib(attr), val=val, opr=opr) + + def _compute_command(self, key=None, attr=None, val=None, remove=False, opr=True): + """ + This function construct the add/delete command based on passed attributes. + :param key: parent key. + :param attr: attribute name + :param value: value + :param opr: True/False. + :return: generated command. + """ + if remove or not opr: + cmd = "delete protocols ospf " + else: + cmd = "set protocols ospf " + if key: + cmd += key.replace("_", "-") + " " + if attr: + cmd += attr.replace("_", "-") + if val 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 3c87be6..17c7aa3 100644 --- a/plugins/module_utils/network/vyos/facts/facts.py +++ b/plugins/module_utils/network/vyos/facts/facts.py @@ -42,6 +42,8 @@ from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.firew ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.ospfv3.ospfv3 import ( Ospfv3Facts, +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.ospfv2.ospfv2 import ( + Ospfv2Facts, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.legacy.base import ( Default, @@ -62,6 +64,7 @@ FACT_RESOURCE_SUBSETS = dict( firewall_global=Firewall_globalFacts, firewall_interfaces=Firewall_interfacesFacts, ospfv3=Ospfv3Facts, + ospfv2=Ospfv2Facts, ) diff --git a/plugins/module_utils/network/vyos/facts/ospfv2/__init__.py b/plugins/module_utils/network/vyos/facts/ospfv2/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/plugins/module_utils/network/vyos/facts/ospfv2/__init__.py diff --git a/plugins/module_utils/network/vyos/facts/ospfv2/ospfv2.py b/plugins/module_utils/network/vyos/facts/ospfv2/ospfv2.py new file mode 100644 index 0000000..3457fac --- /dev/null +++ b/plugins/module_utils/network/vyos/facts/ospfv2/ospfv2.py @@ -0,0 +1,395 @@ +# +# -*- coding: utf-8 -*- +# Copyright 2019 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +""" +The vyos ospfv2 fact class +It is in this file the configuration is collected from the device +for a given resource, parsed, and the facts tree is populated +based on the configuration. +""" +from re import findall, search, M +from copy import deepcopy +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import ( + utils, +) +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.argspec.ospfv2.ospfv2 import Ospfv2Args + + +class Ospfv2Facts(object): + """ The vyos ospfv2 fact class + """ + + def __init__(self, module, subspec='config', options='options'): + self._module = module + self.argument_spec = Ospfv2Args.argument_spec + spec = deepcopy(self.argument_spec) + if subspec: + if options: + facts_argument_spec = spec[subspec][options] + else: + facts_argument_spec = spec[subspec] + else: + facts_argument_spec = spec + + self.generated_spec = utils.generate_dict(facts_argument_spec) + + def get_device_data(self, connection): + return connection.get_config() + + def populate_facts(self, connection, ansible_facts, data=None): + """ Populate the facts for ospfv2 + :param connection: the device connection + :param ansible_facts: Facts dictionary + :param data: previously collected conf + :rtype: dictionary + :returns: facts + """ + if not data: + data = self.get_device_data(connection) + # typically data is populated from the current device configuration + # data = connection.get('show running-config | section ^interface') + # using mock data instead + objs = [] + ospfv2 = findall(r"^set protocols ospf (.+)", data, M) + if ospfv2: + config = self.render_config(ospfv2) + if config: + objs.append(config) + ansible_facts["ansible_network_resources"].pop("ospfv2", None) + facts = {} + if objs: + facts["ospfv2"] = [] + params = utils.validate_config(self.argument_spec, {"config": objs}) + for cfg in params["config"]: + facts["ospfv2"].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)) + a_lst = ["default_metric", "log_adjacency_changes"] + config = self.parse_attr(conf, a_lst) + + if not config: + config = {} + config["timers"] = self.parse_timers(conf) + config["auto_cost"] = self.parse_auto_cost(conf) + config["distance"] = self.parse_distance(conf) + config["max_metric"] = self.parse_max_metric(conf) + config["mpls_te"] = self.parse_attrib(conf, "mpls_te", "mpls-te") + config["default_information"] = self.parse_def_info(conf) + config["parameters"] = self.parse_attrib(conf, "parameters", "parameters") + config["route_map"] = self.parse_leaf_list(conf, "route-map") + config["ospf_area"] = self.parse_attrib_list(conf, "area", "area") + config["neighbor"] = self.parse_attrib_list(conf, "neighbor", "neighbor_id") + config["passive_interface"] = self.parse_leaf_list(conf, "passive-interface") + config["redistribute"] = self.parse_attrib_list(conf, "redistribute", "route_type") + config["passive_interface_exclude"] = self.parse_leaf_list(conf, "passive-interface-exclude") + return config + + def parse_timers(self, conf, attrib=None): + """ + This function triggers the parsing of 'timers' attributes + :param conf: configuration + :param attrib: attribute name + :return: generated config dictionary + """ + cfg_dict = {} + cfg_dict["refresh"] = self.parse_refresh(conf, "refresh") + cfg_dict["throttle"] = self.parse_throttle(conf, "spf") + return cfg_dict + + def parse_throttle(self, conf, attrib=None): + """ + This function triggers the parsing of 'throttle' attributes + :param conf: configuration + :param attrib: 'spf' + :return: generated config dictionary + """ + cfg_dict = {} + cfg_dict[attrib] = self.parse_attrib(conf, attrib, match=attrib) + return cfg_dict + + def parse_refresh(self, conf, attrib=None): + """ + This function triggers the parsing of 'refresh' attributes + :param conf: configuration + :param attrib: 'refresh' + :return: generated config dictionary + """ + cfg_dict = self.parse_attr(conf, ["timers"], match=attrib) + return cfg_dict + + def parse_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) + elif attrib == 'virtual-link': + obj = self.parse_vlink(cfg) + 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_leaf_list(self, conf, attrib): + """ + This function forms the regex to fetch the listed attributes + from the configuration data + :param conf: configuration data + :param attrib: attribute name + :return: generated rule list configuration + """ + lst = [] + items = findall(r"^" + attrib + " (?:\'*)(\S+)(?:\'*)", conf, M) + if items: + for i in set(items): + lst.append(i.strip("'")) + return lst + + def parse_distance(self, conf, attrib=None): + """ + This function triggers the parsing of 'distance' attributes + :param conf: configuration + :param attrib: attribute name + :return: generated config dictionary + """ + cfg_dict = self.parse_attr(conf, ["global"], match=attrib) + cfg_dict["ospf"] = self.parse_ospf(conf, "ospf") + return cfg_dict + + def parse_ospf(self, conf, attrib=None): + """ + This function triggers the parsing of 'distance ospf' attributes + :param conf: configuration + :param attrib: 'ospf' + :return: generated config dictionary + """ + cfg_dict = self.parse_attrib(conf, 'ospf', match=attrib) + return cfg_dict + + def parse_max_metric(self, conf, attrib=None): + """ + This function triggers the parsing of 'max_metric' attributes + :param conf: configuration + :param attrib: attribute name + :return: generated config dictionary + """ + cfg_dict = {} + cfg_dict["router_lsa"] = self.parse_attrib(conf, "router_lsa", match="router-lsa") + return cfg_dict + + def parse_auto_cost(self, conf, attrib=None): + """ + This function triggers the parsing of 'auto_cost' attributes + :param conf: configuration + :param attrib: attribute name + :return: generated config dictionary + """ + cfg_dict = self.parse_attr(conf, ["reference_bandwidth"], match=attrib) + return cfg_dict + + def parse_def_info(self, conf, attrib=None): + """ + This function triggers the parsing of 'default_information' attributes + :param conf: configuration + :param attrib: attribute name + :return: generated config dictionary + """ + cfg_dict = {} + cfg_dict["originate"] = self.parse_attrib(conf, "originate", "originate") + return cfg_dict + + def parse_area(self, conf, area_id): + """ + This function triggers the parsing of 'area' attributes. + :param conf: configuration data + :param area_id: area identity + :return: generated rule configuration dictionary. + """ + rule = self.parse_attrib(conf, "area", match=area_id) + r_sub = { + "area_type": self.parse_area_type(conf, "area-type"), + "network": self.parse_network(conf), + "range": self.parse_attrib_list(conf, "range", "address"), + "virtual_link": self.parse_attrib_list(conf, "virtual-link", "address") + } + rule.update(r_sub) + return rule + + def parse_area_type(self, conf, attrib=None): + """ + This function triggers the parsing of 'area_type' attributes + :param conf: configuration + :param attrib: 'area-type' + :return: generated config dictionary + """ + cfg_dict = self.parse_attr(conf, ["normal"], match=attrib) + cfg_dict["nssa"] = self.parse_attrib(conf, "nssa") + cfg_dict["stub"] = self.parse_attrib(conf, "stub") + return cfg_dict + + def parse_network(self, conf): + """ + This function forms the regex to fetch the 'network' + :param conf: configuration data + :return: generated rule list configuration + """ + a_lst = [] + applications = findall(r"network (.+)", conf, M) + if applications: + app_lst = [] + for r in set(applications): + obj = {"address": r.strip("'")} + app_lst.append(obj) + a_lst = sorted(app_lst, key=lambda i: i["address"]) + return a_lst + + def parse_vlink(self, conf): + """ + This function triggers the parsing of 'vitual_link' attributes + :param conf: configuration data + :return: generated rule configuration dictionary + """ + rule = self.parse_attrib(conf, 'vlink') + r_sub = {"authentication": self.parse_authentication(conf, "authentication")} + rule.update(r_sub) + return rule + + def parse_authentication(self, conf, attrib=None): + """ + This function triggers the parsing of 'authentication' attributes. + :param conf: configuration + :param attrib: 'authentication' + :return: generated config dictionary + """ + cfg_dict = self.parse_attr(conf, ["plaintext_password"], match=attrib) + cfg_dict["md5"] = self.parse_md5(conf, "md5") + return cfg_dict + + def parse_md5(self, conf, attrib=None): + """ + This function triggers the parsing of 'md5' attributes + :param conf: configuration + :param attrib: 'md5' + :return: generated config dictionary + """ + cfg_dict = self.parse_attr(conf, ["key_id"], match=attrib) + 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 = { + 'stub': ["default_cost", "no_summary"], + 'area': ["shortcut", "authentication"], + 'mpls_te': ["enabled", "router_address"], + 'neighbor': ["priority", "poll_interval"], + 'ospf': ["external", "inter_area", "intra_area"], + 'nssa': ["translate", "default_cost", "no_summary"], + 'redistribute': ["metric", "metric_type", "route_map"], + 'spf': ["delay", "max_holdtime", "initial_holdtime"], + 'range': ["cost", "substitute", "not_advertise"], + 'originate': ["always", "metric", "metric_type", "route_map"], + 'router_lsa': ["administrative", "on_shutdown", "on_startup"], + 'config_routes': ["default_metric", "log_adjacency_changes"], + 'parameters': ["abr_type", "opaque_lsa", "router_id", "rfc1583_compatibility"], + 'vlink': ["dead_interval", "hello_interval", "transmit_delay", "retransmit_interval"] + } + cfg_dict = self.parse_attr(conf, param_lst[param], match) + return cfg_dict + + def parse_attr(self, conf, attr_list, match=None): + """ + This function peforms the following: + - Form the regex to fetch the required attribute config. + - Type cast the output in desired format. + :param conf: configuration. + :param attr_list: list of attributes. + :param match: parent node/attribute name. + :return: generated config dictionary. + """ + config = {} + for attrib in attr_list: + regex = self.map_regex(attrib) + if match: + regex = match.replace("_", "-") + " " + regex + if conf: + if self.is_bool(attrib): + out = conf.find(attrib.replace("_", "-")) + dis = conf.find(attrib.replace("_", "-") + " 'disable'") + if match: + 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 = ("always", "normal", "enabled", "opaque_lsa", "not_advertise", "administrative", "rfc1583_compatibility") + return True if attrib in bool_set else False + + def is_num(self, attrib): + """ + This function looks for the attribute in predefined integer type set. + :param attrib: attribute. + :return: True/false. + """ + num_set = ("ospf", "delay", "metric", "inter_area", "intra_area", "on_startup", "metric_type", "on_shutdown", + "max_holdtime", "default_metric", "initial_holdtime") + return True if attrib in num_set else False diff --git a/plugins/module_utils/network/vyos/utils/utils.py b/plugins/module_utils/network/vyos/utils/utils.py index 96feddd..c7dc575 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, @@ -238,13 +239,7 @@ def _bool_to_str(val): :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): diff --git a/plugins/modules/vyos_facts.py b/plugins/modules/vyos_facts.py index 5849519..72b191c 100644 --- a/plugins/modules/vyos_facts.py +++ b/plugins/modules/vyos_facts.py @@ -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', 'ospfv3'. + 'firewall_interfaces', 'ospfv3', 'ospfv2'. required: false """ diff --git a/plugins/modules/vyos_ospfv2.py b/plugins/modules/vyos_ospfv2.py new file mode 100644 index 0000000..73528eb --- /dev/null +++ b/plugins/modules/vyos_ospfv2.py @@ -0,0 +1,1369 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright 2019 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +############################################# +# WARNING # +############################################# +# +# This file is auto generated by the resource +# module builder playbook. +# +# Do not edit this file manually. +# +# Changes to this file will be over written +# by the resource module builder. +# +# Changes should be made in the model used to +# generate this file or in the resource module +# builder template. +# +############################################# + +""" +The module file for vyos_ospfv2 +""" + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +ANSIBLE_METADATA = { + 'metadata_version': '1.1', + 'status': ['preview'], + 'supported_by': 'network' +} + +DOCUMENTATION = """ +--- +module: vyos_ospfv2 +version_added: 2.10 +short_description: Manages attributes of OSPF IPv4 routes on VyOS network devices. +description: This module manages attributes of OSPF IPv4 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: Configured to discard packets. + type: str + area_type: + description: Area type. + type: dict + suboptions: + normal: + description: Normal OSPF area. + type: bool + nssa: + description: Nssa OSPF area. + type: dict + suboptions: + default_cost: + description: Summary-default cost of nssa area. + type: int + no_summary: + description: Do not inject inter-area routes into stub. + type: bool + translate: + description: Nssa-abr. + type: str + choices: ['always', 'candidate', 'never'] + stub: + description: Stub OSPF area. + type: dict + suboptions: + default_cost: + description: Summary-default cost of stub area. + type: int + no_summary: + description: Do not inject inter-area routes into stub. + type: bool + authentication: + description: OSPF area authentication type. + type: str + choices: ['plaintext-password', 'md5'] + network: + description: OSPF network. + type: list + elements: dict + suboptions: + address: + required: True + description: OSPF IPv4 network address. + type: str + range: + description: Summarize routes matching prefix (border routers only). + type: list + elements: dict + suboptions: + address: + description: border router IPv4 address. + type: str + cost: + description: Metric for this range. + type: int + not_advertise: + description: Don't advertise this range. + type: bool + substitute: + description: Announce area range (IPv4 address) as another prefix. + type: str + shortcut: + description: Area's shortcut mode. + type: str + choices: ['default', 'disable', 'enable'] + virtual_link: + description: Virtual link address. + type: list + elements: dict + suboptions: + address: + description: virtual link address. + type: str + authentication: + description: OSPF area authentication type. + type: dict + suboptions: + md5: + description: MD5 key id based authentication. + type: dict + suboptions: + key_id: + description: MD5 key id. + type: int + md5_key: + description: MD5 key. + type: str + plaintext_password: + description: Plain text password. + type: str + dead_interval: + description: Interval after which a neighbor is declared dead. + type: int + hello_interval: + description: Interval between hello packets. + type: int + retransmit_interval: + description: Interval between retransmitting lost link state advertisements. + type: int + transmit_delay: + description: Link state transmit delay. + type: int + log_adjacency_changes: + description: Log changes in adjacency state. + type: str + choices: ['detail'] + max_metric: + description: OSPF maximum/infinite-distance metric. + type: dict + suboptions: + router_lsa: + description: Advertise own Router-LSA with infinite distance (stub router). + type: dict + suboptions: + administrative: + description: Administratively apply, for an indefinite period. + type: bool + on_shutdown: + description: Time to advertise self as stub-router. + type: int + on_startup: + description: Time to advertise self as stub-router + type: int + auto_cost: + description: Calculate OSPF interface cost according to bandwidth. + type: dict + suboptions: + reference_bandwidth: + description: Reference bandwidth cost in Mbits/sec. + type: int + default_information: + description: Control distribution of default information. + type: dict + suboptions: + originate: + description: Distribute a default route. + type: dict + suboptions: + always: + description: Always advertise default route. + type: bool + metric: + description: OSPF default metric. + type: int + metric_type: + description: OSPF Metric types for default routes. + type: int + route_map: + description: Route map references. + type: str + default_metric: + description: Metric of redistributed routes + type: int + distance: + description: Administrative distance. + type: dict + suboptions: + global: + description: Global OSPF administrative distance. + type: int + ospf: + description: OSPF administrative distance. + type: dict + suboptions: + external: + description: Distance for external routes. + type: int + inter_area: + description: Distance for inter-area routes. + type: int + intra_area: + description: Distance for intra-area routes. + type: int + mpls_te: + description: MultiProtocol Label Switching-Traffic Engineering (MPLS-TE) parameters. + type: dict + suboptions: + enabled: + description: Enable MPLS-TE functionality. + type: bool + router_address: + description: Stable IP address of the advertising router. + type: str + + neighbor: + description: Neighbor IP address. + type: list + elements: dict + suboptions: + neighbor_id: + description: Identity (number/IP address) of neighbor. + type: str + poll_interval: + description: Seconds between dead neighbor polling interval. + type: int + priority: + description: Neighbor priority. + type: int + parameters: + descriptions: OSPF specific parameters. + type: dict + suboptions: + abr_type: + description: OSPF ABR Type. + type: str + choices: ['cisco', 'ibm', 'shortcut', 'standard'] + opaque_lsa: + description: Enable the Opaque-LSA capability (rfc2370). + type: bool + rfc1583_compatibility: + description: Enable rfc1583 criteria for handling AS external routes. + type: bool + router_id: + description: Override the default router identifier. + type: str + passive_interface: + description: Suppress routing updates on an interface. + type: list + passive_interface_exclude: + description: Interface to exclude when using passive-interface default. + type: list + redistribute: + description: Redistribute information from another routing protocol. + type: list + elements: dict + suboptions: + route_type: + description: Route type to redistribute. + type: str + choices: ['bgp', 'connected', 'kernel', 'rip', 'static'] + metric: + description: Metric for redistribution routes. + type: int + metric_type: + description: OSPF Metric types. + type: int + route_map: + description: Route map references. + type: str + + route_map: + description: Filter routes installed in local route map. + type: list + timers: + description: Adjust routing timers. + type: dict + suboptions: + refresh: + description: Adjust refresh parameters. + type: dict + suboptions: + timers: + description: refresh timer. + type: int + throttle: + description: Throttling adaptive timers. + type: dict + suboptions: + spf: + description: OSPF SPF timers. + type: dict + suboptions: + delay: + description: Delay (msec) from first change received till SPF calculation. + type: int + initial_holdtime: + description: Initial hold time(msec) between consecutive SPF calculations. + type: int + max_holdtime: + description: maximum hold time (sec). + type: int + state: + description: + - The state the configuration should be left in. + type: str + choices: + - merged + - replaced + - deleted + - parsed + - gathered + - rendered + default: merged +""" +EXAMPLES = """ +# Using merged +# +# Before state: +# ------------- +# +# vyos@192# run show configuration commands | grep ospf +# +- name: Merge the provided configuration with the existing running configuration + vyos_ospf_routes: + config: + - afi: 'ipv4' + ospf_area: + - area: 0 + network: 192.168.0.0/24 + default_information: + originate: + always: true + metric: 2 + metric_type: 10 + log_adjacency_changes: "details" + parameters: + router_id: 10.1.1.1 + redistribute: + - route_type: 'connected' + metric_type: 2 + route_map: 'CONNECT' + - afi: 'ipv6' + ospf_area: + - area: 0.0.0.0 + range: 2001:db8:1::/64 + parameters: + router-id 192.168.1.1 + redistribute: + - route_type: 'connected' + state: merged +# +# +# ------------------------- +# Module Execution Result +# ------------------------- +# +# before": [] +# +# "commands": [ +# "set interfaces ethernet eth1 firewall in name 'INBOUND'", +# "set protocols ospf area 0 network 192.168.0.0/24", +# "set protocols ospf default-information originate always", +# "set protocols ospf default-information originate metric 10", +# "set protocols ospf default-information originate metric-type 2", +# "set protocols ospf log-adjacency-changes", +# "set protocols ospf parameters router-id 10.1.1.1", +# "set protocols ospf redistribute connected metric-type 2", +# "set protocols ospf redistribute connected route-map CONNECT", +# "set protocols ospfv3 area 0.0.0.0 range 2001:db8:1::/64, +# "set protocols ospfv3 parameters router-id 192.168.1.1, +# "set protocols ospfv3 redistribute connected +# ] +# +# "after": [ +# { +# { +# "afi": "ipv4", +# "ospf_area":[ +# { +# "area": "0", +# "network": "192.168.0.0/24" +# } +# ], +# "default_information": +# { +# "originate": +# { +# always: true, +# metric: 2, +# metric_type: 10 +# } +# }, +# "log_adjacency_changes": "details" +# "parameters": +# { +# "router_id": "10.1.1.1" +# }, +# "redistribute":[ +# { +# "route_type": "connetced", +# "metric_type": 2 +# "route_map": "CONNECT" +# } +# ] +# }, +# { +# "afi": "ipv6", +# "ospf_area":[ +# { +# "area": "0.0.0.0", +# } +# ], +# "range": "2001:db8:1::/64", +# "parameters": +# { +# "router_id": "192.168.1.1" +# }, +# "redistribute": +# [ +# { +# "route_type": "connetced", +# } +# ] +# } +# ] +# +# After state: +# ------------- +# +# vyos@vyos:~$ show configuration commands| grep firewall +# set protocols ospf area 0 network 192.168.0.0/24 +# set protocols ospf default-information originate always +# set protocols ospf default-information originate metric 10 +# set protocols ospf default-information originate metric-type 2 +# set protocols ospf log-adjacency-changes details +# set protocols ospf parameters router-id 10.1.1.1 +# set protocols ospf redistribute connected metric-type 2 +# set protocols ospf redistribute connected route-map CONNECT +# set protocols ospfv3 area 0.0.0.0 range 2001:db8:1::/64 +# set protocols ospfv3 parameters router-id 192.168.1.1 +# set protocols ospfv3 redistribute connected + + +# Using replaced +# +# Before state: +# ------------- +# +# vyos@192# run show configuration commands | grep ospf +# set protocols ospf area 0 network 192.168.0.0/24 +# set protocols ospf default-information originate always +# set protocols ospf default-information originate metric 10 +# set protocols ospf default-information originate metric-type 2 +# set protocols ospf log-adjacency-changes details +# set protocols ospf parameters router-id 10.1.1.1 +# set protocols ospf redistribute connected metric-type 2 +# set protocols ospf redistribute connected route-map CONNECT +# set protocols ospfv3 area 0.0.0.0 range 2001:db8:1::/64 +# set protocols ospfv3 parameters router-id 192.168.1.1 +# set protocols ospfv3 redistribute connected +# +- name: Replace the provided configuration with the existing running configuration + vyos_ospf_routes: + config: + - afi: 'ipv4' + ospf_area: + - area: 0 + network: 192.168.0.0/24 + area_type: + normal: True + default_information: + originate: + always: true + metric: 2 + metric_type: 10 + log_adjacency_changes: "details" + parameters: + router_id: 10.1.1.1 + redistribute: + - route_type: 'static' + metric_type: 2 + route_map: 'STATIC' + - afi: 'ipv6' + ospf_area: + - area: 0.0.0.0 + range: 2001:db8:1::/64 + parameters: + router-id 192.168.1.1 + redistribute: + - route_type: 'connected' + state: replaced +# +# +# ------------------------- +# Module Execution Result +# ------------------------- +# +# before": [ +# { +# { +# "afi": "ipv4", +# "ospf_area":[ +# { +# "area": "0", +# "network": "192.168.0.0/24" +# } +# ], +# "default_information": +# { +# "originate": +# { +# always: true, +# metric: 2, +# metric_type: 10 +# } +# }, +# "log_adjacency_changes": "details" +# "parameters": +# { +# "router_id": "10.1.1.1" +# }, +# "redistribute":[ +# { +# "route_type": "connetced", +# "metric_type": 2 +# "route_map": "CONNECT" +# } +# ] +# }, +# { +# "afi": "ipv6", +# "ospf_area":[ +# { +# "area": "0.0.0.0", +# } +# ], +# "range": "2001:db8:1::/64", +# "parameters": +# { +# "router_id": "192.168.1.1" +# }, +# "redistribute": +# [ +# { +# "route_type": "connetced", +# } +# ] +# } +# ] +# +# "commands": [ +# "delete protocols ospf redistribute connected", +# "set protocols ospf area 0 area_type normal", +# "set protocols ospf redistribute static metric-type 2", +# "set protocols ospf redistribute static route-map CONNECT" +# ] +# +# "after": [ +# { +# { +# "afi": "ipv4", +# "ospf_area":[ +# { +# "area": "0", +# "area_type": +# { +# normal: true +# } +# "network": "192.168.0.0/24" +# } +# ], +# "default_information": +# { +# "originate": +# { +# always: true, +# metric: 2, +# metric_type: 10 +# } +# }, +# "log_adjacency_changes": "details" +# "parameters": +# { +# "router_id": "10.1.1.1" +# }, +# "redistribute":[ +# { +# "route_type": "static", +# "metric_type": 2 +# "route_map": "STATIC" +# } +# ] +# }, +# { +# "afi": "ipv6", +# "ospf_area":[ +# { +# "area": "0.0.0.0", +# } +# ], +# "range": "2001:db8:1::/64", +# "parameters": +# { +# "router_id": "192.168.1.1" +# }, +# "redistribute": +# [ +# { +# "route_type": "connected", +# } +# ] +# } +# ] +# +# After state: +# ------------- +# +# vyos@vyos:~$ show configuration commands| grep firewall +# set protocols ospf area 0 network 192.168.0.0/24 +# set protocols ospf default-information originate always +# set protocols ospf default-information originate metric 10 +# set protocols ospf default-information originate metric-type 2 +# set protocols ospf log-adjacency-changes details +# set protocols ospf parameters router-id 10.1.1.1 +# set protocols ospf redistribute static metric-type 2 +# set protocols ospf redistribute static route-map CONNECT +# set protocols ospfv3 area 0.0.0.0 range 2001:db8:2::/64 +# set protocols ospfv3 parameters router-id 192.168.2.1 +# set protocols ospfv3 redistribute connected + + +# Using replaced +# +# Before state: +# ------------- +# +# vyos@192# run show configuration commands | grep ospf +# set protocols ospf area 0 network 192.168.0.0/24 +# set protocols ospf default-information originate always +# set protocols ospf default-information originate metric 10 +# set protocols ospf default-information originate metric-type 2 +# set protocols ospf log-adjacency-changes details +# set protocols ospf parameters router-id 10.1.1.1 +# set protocols ospf redistribute connected metric-type 2 +# set protocols ospf redistribute connected route-map CONNECT +# set protocols ospfv3 area 0.0.0.0 range 2001:db8:1::/64 +# set protocols ospfv3 parameters router-id 192.168.1.1 +# set protocols ospfv3 redistribute connected +# +- name: Replace the provided configuration with the existing running configuration + vyos_ospf_routes: + config: + - afi: 'ipv4' + ospf_area: + - area: 0 + network: 192.168.0.0/24 + area_type: + normal: True + default_information: + originate: + always: true + metric: 2 + metric_type: 10 + log_adjacency_changes: "details" + parameters: + router_id: 10.1.1.1 + redistribute: + - route_type: 'static' + metric_type: 2 + route_map: 'STATIC' + - afi: 'ipv6' + ospf_area: + - area: 0.0.0.0 + range: 2001:db8:1::/64 + parameters: + router-id 192.168.1.1 + redistribute: + - route_type: 'connected' + state: replaced +# +# +# ------------------------- +# Module Execution Result +# ------------------------- +# +# before": [ +# { +# { +# "afi": "ipv4", +# "ospf_area":[ +# { +# "area": "0", +# "network": "192.168.0.0/24" +# } +# ], +# "default_information": +# { +# "originate": +# { +# always: true, +# metric: 2, +# metric_type: 10 +# } +# }, +# "log_adjacency_changes": "details" +# "parameters": +# { +# "router_id": "10.1.1.1" +# }, +# "redistribute":[ +# { +# "route_type": "connetced", +# "metric_type": 2 +# "route_map": "CONNECT" +# } +# ] +# }, +# { +# "afi": "ipv6", +# "ospf_area":[ +# { +# "area": "0.0.0.0", +# } +# ], +# "range": "2001:db8:1::/64", +# "parameters": +# { +# "router_id": "192.168.1.1" +# }, +# "redistribute": +# [ +# { +# "route_type": "connetced", +# } +# ] +# } +# ] +# +# "commands": [ +# "delete protocols ospf redistribute connected", +# "set protocols ospf area 0 area_type normal", +# "set protocols ospf redistribute static metric-type 2", +# "set protocols ospf redistribute static route-map CONNECT" +# ] +# +# "after": [ +# { +# { +# "afi": "ipv4", +# "ospf_area":[ +# { +# "area": "0", +# "area_type": +# { +# normal: true +# } +# "network": "192.168.0.0/24" +# } +# ], +# "default_information": +# { +# "originate": +# { +# always: true, +# metric: 2, +# metric_type: 10 +# } +# }, +# "log_adjacency_changes": "details" +# "parameters": +# { +# "router_id": "10.1.1.1" +# }, +# "redistribute":[ +# { +# "route_type": "static", +# "metric_type": 2 +# "route_map": "STATIC" +# } +# ] +# }, +# { +# "afi": "ipv6", +# "ospf_area":[ +# { +# "area": "0.0.0.0", +# } +# ], +# "range": "2001:db8:1::/64", +# "parameters": +# { +# "router_id": "192.168.1.1" +# }, +# "redistribute": +# [ +# { +# "route_type": "connected", +# } +# ] +# } +# ] +# +# After state: +# ------------- +# +# vyos@vyos:~$ show configuration commands| grep firewall +# set protocols ospf area 0 network 192.168.0.0/24 +# set protocols ospf default-information originate always +# set protocols ospf default-information originate metric 10 +# set protocols ospf default-information originate metric-type 2 +# set protocols ospf log-adjacency-changes details +# set protocols ospf parameters router-id 10.1.1.1 +# set protocols ospf redistribute static metric-type 2 +# set protocols ospf redistribute static route-map CONNECT +# set protocols ospfv3 area 0.0.0.0 range 2001:db8:2::/64 +# set protocols ospfv3 parameters router-id 192.168.2.1 +# set protocols ospfv3 redistribute connected + + +# Using replaced +# +# Before state: +# ------------- +# +# vyos@192# run show configuration commands | grep ospf +# set protocols ospf area 0 network 192.168.0.0/24 +# set protocols ospf default-information originate always +# set protocols ospf default-information originate metric 10 +# set protocols ospf default-information originate metric-type 2 +# set protocols ospf log-adjacency-changes details +# set protocols ospf parameters router-id 10.1.1.1 +# set protocols ospf redistribute connected metric-type 2 +# set protocols ospf redistribute connected route-map CONNECT +# set protocols ospfv3 area 0.0.0.0 range 2001:db8:1::/64 +# set protocols ospfv3 parameters router-id 192.168.1.1 +# set protocols ospfv3 redistribute connected +# +- name: Replace the provided configuration with the existing running configuration + vyos_ospf_routes: + config: + - afi: 'ipv4' + ospf_area: + - area: 0 + network: 192.168.0.0/24 + area_type: + normal: True + default_information: + originate: + always: true + metric: 2 + metric_type: 10 + log_adjacency_changes: "details" + parameters: + router_id: 10.1.1.1 + redistribute: + - route_type: 'static' + metric_type: 2 + route_map: 'STATIC' + - afi: 'ipv6' + ospf_area: + - area: 0.0.0.0 + range: 2001:db8:1::/64 + parameters: + router-id 192.168.1.1 + redistribute: + - route_type: 'connected' + state: replaced +# +# +# ------------------------- +# Module Execution Result +# ------------------------- +# +# before": [ +# { +# { +# "afi": "ipv4", +# "ospf_area":[ +# { +# "area": "0", +# "network": "192.168.0.0/24" +# } +# ], +# "default_information": +# { +# "originate": +# { +# always: true, +# metric: 2, +# metric_type: 10 +# } +# }, +# "log_adjacency_changes": "details" +# "parameters": +# { +# "router_id": "10.1.1.1" +# }, +# "redistribute":[ +# { +# "route_type": "connetced", +# "metric_type": 2 +# "route_map": "CONNECT" +# } +# ] +# }, +# { +# "afi": "ipv6", +# "ospf_area":[ +# { +# "area": "0.0.0.0", +# } +# ], +# "range": "2001:db8:1::/64", +# "parameters": +# { +# "router_id": "192.168.1.1" +# }, +# "redistribute": +# [ +# { +# "route_type": "connetced", +# } +# ] +# } +# ] +# +# "commands": [ +# "delete protocols ospf redistribute connected", +# "set protocols ospf area 0 area_type normal", +# "set protocols ospf redistribute static metric-type 2", +# "set protocols ospf redistribute static route-map CONNECT" +# ] +# +# "after": [ +# { +# { +# "afi": "ipv4", +# "ospf_area":[ +# { +# "area": "0", +# "area_type": +# { +# normal: true +# } +# "network": "192.168.0.0/24" +# } +# ], +# "default_information": +# { +# "originate": +# { +# always: true, +# metric: 2, +# metric_type: 10 +# } +# }, +# "log_adjacency_changes": "details" +# "parameters": +# { +# "router_id": "10.1.1.1" +# }, +# "redistribute":[ +# { +# "route_type": "static", +# "metric_type": 2 +# "route_map": "STATIC" +# } +# ] +# }, +# { +# "afi": "ipv6", +# "ospf_area":[ +# { +# "area": "0.0.0.0", +# } +# ], +# "range": "2001:db8:1::/64", +# "parameters": +# { +# "router_id": "192.168.1.1" +# }, +# "redistribute": +# [ +# { +# "route_type": "connected", +# } +# ] +# } +# ] +# +# After state: +# ------------- +# +# vyos@vyos:~$ show configuration commands| grep firewall +# set protocols ospf area 0 network 192.168.0.0/24 +# set protocols ospf default-information originate always +# set protocols ospf default-information originate metric 10 +# set protocols ospf default-information originate metric-type 2 +# set protocols ospf log-adjacency-changes details +# set protocols ospf parameters router-id 10.1.1.1 +# set protocols ospf redistribute static metric-type 2 +# set protocols ospf redistribute static route-map CONNECT +# set protocols ospfv3 area 0.0.0.0 range 2001:db8:2::/64 +# set protocols ospfv3 parameters router-id 192.168.2.1 +# set protocols ospfv3 redistribute connected + + +# Using replaced +# +# Before state: +# ------------- +# +# vyos@192# run show configuration commands | grep ospf +# set protocols ospf area 0 network 192.168.0.0/24 +# set protocols ospf default-information originate always +# set protocols ospf default-information originate metric 10 +# set protocols ospf default-information originate metric-type 2 +# set protocols ospf log-adjacency-changes details +# set protocols ospf parameters router-id 10.1.1.1 +# set protocols ospf redistribute connected metric-type 2 +# set protocols ospf redistribute connected route-map CONNECT +# set protocols ospfv3 area 0.0.0.0 range 2001:db8:1::/64 +# set protocols ospfv3 parameters router-id 192.168.1.1 +# set protocols ospfv3 redistribute connected +# +- name: Replace the provided configuration with the existing running configuration + vyos_ospf_routes: + config: + - afi: 'ipv4' + ospf_area: + - area: 0 + network: 192.168.0.0/24 + area_type: + normal: True + default_information: + originate: + always: true + metric: 2 + metric_type: 10 + log_adjacency_changes: "details" + parameters: + router_id: 10.1.1.1 + redistribute: + - route_type: 'static' + metric_type: 2 + route_map: 'STATIC' + - afi: 'ipv6' + ospf_area: + - area: 0.0.0.0 + range: 2001:db8:1::/64 + parameters: + router-id 192.168.1.1 + redistribute: + - route_type: 'connected' + state: replaced +# +# +# ------------------------- +# Module Execution Result +# ------------------------- +# +# before": [ +# { +# { +# "afi": "ipv4", +# "ospf_area":[ +# { +# "area": "0", +# "network": "192.168.0.0/24" +# } +# ], +# "default_information": +# { +# "originate": +# { +# always: true, +# metric: 2, +# metric_type: 10 +# } +# }, +# "log_adjacency_changes": "details" +# "parameters": +# { +# "router_id": "10.1.1.1" +# }, +# "redistribute":[ +# { +# "route_type": "connetced", +# "metric_type": 2 +# "route_map": "CONNECT" +# } +# ] +# }, +# { +# "afi": "ipv6", +# "ospf_area":[ +# { +# "area": "0.0.0.0", +# } +# ], +# "range": "2001:db8:1::/64", +# "parameters": +# { +# "router_id": "192.168.1.1" +# }, +# "redistribute": +# [ +# { +# "route_type": "connetced", +# } +# ] +# } +# ] +# +# "commands": [ +# "delete protocols ospf redistribute connected", +# "set protocols ospf area 0 area_type normal", +# "set protocols ospf redistribute static metric-type 2", +# "set protocols ospf redistribute static route-map CONNECT" +# ] +# +# "after": [ +# { +# { +# "afi": "ipv4", +# "ospf_area":[ +# { +# "area": "0", +# "area_type": +# { +# normal: true +# } +# "network": "192.168.0.0/24" +# } +# ], +# "default_information": +# { +# "originate": +# { +# always: true, +# metric: 2, +# metric_type: 10 +# } +# }, +# "log_adjacency_changes": "details" +# "parameters": +# { +# "router_id": "10.1.1.1" +# }, +# "redistribute":[ +# { +# "route_type": "static", +# "metric_type": 2 +# "route_map": "STATIC" +# } +# ] +# }, +# { +# "afi": "ipv6", +# "ospf_area":[ +# { +# "area": "0.0.0.0", +# } +# ], +# "range": "2001:db8:1::/64", +# "parameters": +# { +# "router_id": "192.168.1.1" +# }, +# "redistribute": +# [ +# { +# "route_type": "connected", +# } +# ] +# } +# ] +# +# After state: +# ------------- +# +# vyos@vyos:~$ show configuration commands| grep firewall +# set protocols ospf area 0 network 192.168.0.0/24 +# set protocols ospf default-information originate always +# set protocols ospf default-information originate metric 10 +# set protocols ospf default-information originate metric-type 2 +# set protocols ospf log-adjacency-changes details +# set protocols ospf parameters router-id 10.1.1.1 +# set protocols ospf redistribute static metric-type 2 +# set protocols ospf redistribute static route-map CONNECT +# set protocols ospfv3 area 0.0.0.0 range 2001:db8:2::/64 +# set protocols ospfv3 parameters router-id 192.168.2.1 +# set protocols ospfv3 redistribute connected + + +# Using deleted +# +# Before state: +# ------------- +# +# vyos@192# run show configuration commands | grep ospf +# set protocols ospf area 0 network 192.168.0.0/24 +# set protocols ospf default-information originate always +# set protocols ospf default-information originate metric 10 +# set protocols ospf default-information originate metric-type 2 +# set protocols ospf log-adjacency-changes details +# set protocols ospf parameters router-id 10.1.1.1 +# set protocols ospf redistribute connected metric-type 2 +# set protocols ospf redistribute connected route-map CONNECT +# set protocols ospfv3 area 0.0.0.0 range 2001:db8:1::/64 +# set protocols ospfv3 parameters router-id 192.168.1.1 +# set protocols ospfv3 redistribute connected +# +- name: Delete all the configuration + vyos_ospf_routes: + config: + state: deleted +# +# +# ------------------------- +# Module Execution Result +# ------------------------- +# +# before": [ +# { +# { +# "afi": "ipv4", +# "ospf_area":[ +# { +# "area": "0", +# "network": "192.168.0.0/24" +# } +# ], +# "default_information": +# { +# "originate": +# { +# always: true, +# metric: 2, +# metric_type: 10 +# } +# }, +# "log_adjacency_changes": "details" +# "parameters": +# { +# "router_id": "10.1.1.1" +# }, +# "redistribute":[ +# { +# "route_type": "connetced", +# "metric_type": 2 +# "route_map": "CONNECT" +# } +# ] +# }, +# { +# "afi": "ipv6", +# "ospf_area":[ +# { +# "area": "0.0.0.0", +# } +# ], +# "range": "2001:db8:1::/64", +# "parameters": +# { +# "router_id": "192.168.1.1" +# }, +# "redistribute": +# [ +# { +# "route_type": "connetced", +# } +# ] +# } +# ] +# +# "commands": [ +# "delete protocols ospf", +# "delete protocols ospfv3", +# ] +# +# "after": [] +# +# After state: +# ------------- +# +# vyos@vyos:~$ show configuration commands| grep firewall + + +""" +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.ospfv2.ospfv2 import Ospfv2Args +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.config.ospfv2.ospfv2 import Ospfv2 + + +def main(): + """ + Main entry point for module execution + + :returns: the result form module invocation + """ + required_if = [ + ("state", "merged", ("config",)), + ("state", "replaced", ("config",)), + ("state", "parsed", ("running_config",)), + ] + mutually_exclusive = [("config", "running_config")] + module = AnsibleModule( + argument_spec=Ospfv2Args.argument_spec, + required_if=required_if, + supports_check_mode=True, + mutually_exclusive=mutually_exclusive, + ) + + result = Ospfv2(module).execute_module() + module.exit_json(**result) + + +if __name__ == '__main__': + main() |