diff options
Diffstat (limited to 'plugins/module_utils')
8 files changed, 1722 insertions, 1 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 00000000..e69de29b --- /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 00000000..275aaf36 --- /dev/null +++ b/plugins/module_utils/network/vyos/argspec/ospfv2/ospfv2.py @@ -0,0 +1,269 @@ +# +# -*- coding: utf-8 -*- +# Copyright 2019 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +############################################# +#                WARNING                    # +############################################# +# +# This file is auto generated by the resource +#   module builder playbook. +# +# Do not edit this file manually. +# +# Changes to this file will be over written +#   by the resource module builder. +# +# Changes should be made in the model used to +#   generate this file or in the resource module +#   builder template. +# +############################################# +""" +The arg spec for the vyos_ospfv2 module +""" + + +class Ospfv2Args(object):  # pylint: disable=R0903 +    """The arg spec for the vyos_ospfv2 module +    """ + +    def __init__(self, **kwargs): +        pass + +    argument_spec = { +        "config": { +            "options": { +                "auto_cost": { +                    "options": {"reference_bandwidth": {"type": "int"}}, +                    "type": "dict", +                }, +                "default_information": { +                    "options": { +                        "originate": { +                            "options": { +                                "always": {"type": "bool"}, +                                "metric": {"type": "int"}, +                                "metric_type": {"type": "int"}, +                                "route_map": {"type": "str"}, +                            }, +                            "type": "dict", +                        } +                    }, +                    "type": "dict", +                }, +                "default_metric": {"type": "int"}, +                "distance": { +                    "options": { +                        "global": {"type": "int"}, +                        "ospf": { +                            "options": { +                                "external": {"type": "int"}, +                                "inter_area": {"type": "int"}, +                                "intra_area": {"type": "int"}, +                            }, +                            "type": "dict", +                        }, +                    }, +                    "type": "dict", +                }, +                "log_adjacency_changes": { +                    "choices": ["detail"], +                    "type": "str", +                }, +                "max_metric": { +                    "options": { +                        "router_lsa": { +                            "options": { +                                "administrative": {"type": "bool"}, +                                "on_shutdown": {"type": "int"}, +                                "on_startup": {"type": "int"}, +                            }, +                            "type": "dict", +                        } +                    }, +                    "type": "dict", +                }, +                "mpls_te": { +                    "options": { +                        "enabled": {"type": "bool"}, +                        "router_address": {"type": "str"}, +                    }, +                    "type": "dict", +                }, +                "neighbor": { +                    "elements": "dict", +                    "options": { +                        "neighbor_id": {"type": "str"}, +                        "poll_interval": {"type": "int"}, +                        "priority": {"type": "int"}, +                    }, +                    "type": "list", +                }, +                "areas": { +                    "elements": "dict", +                    "options": { +                        "area_id": {"type": "str"}, +                        "area_type": { +                            "options": { +                                "normal": {"type": "bool"}, +                                "nssa": { +                                    "options": { +                                        "default_cost": {"type": "int"}, +                                        "no_summary": {"type": "bool"}, +                                        "set": {"type": "bool"}, +                                        "translate": { +                                            "choices": [ +                                                "always", +                                                "candidate", +                                                "never", +                                            ], +                                            "type": "str", +                                        }, +                                    }, +                                    "type": "dict", +                                }, +                                "stub": { +                                    "options": { +                                        "default_cost": {"type": "int"}, +                                        "no_summary": {"type": "bool"}, +                                        "set": {"type": "bool"}, +                                    }, +                                    "type": "dict", +                                }, +                            }, +                            "type": "dict", +                        }, +                        "authentication": { +                            "choices": ["plaintext-password", "md5"], +                            "type": "str", +                        }, +                        "network": { +                            "elements": "dict", +                            "options": { +                                "address": {"required": True, "type": "str"} +                            }, +                            "type": "list", +                        }, +                        "range": { +                            "elements": "dict", +                            "options": { +                                "address": {"type": "str"}, +                                "cost": {"type": "int"}, +                                "not_advertise": {"type": "bool"}, +                                "substitute": {"type": "str"}, +                            }, +                            "type": "list", +                        }, +                        "shortcut": { +                            "choices": ["default", "disable", "enable"], +                            "type": "str", +                        }, +                        "virtual_link": { +                            "elements": "dict", +                            "options": { +                                "address": {"type": "str"}, +                                "authentication": { +                                    "options": { +                                        "md5": { +                                            "elements": "dict", +                                            "options": { +                                                "key_id": {"type": "int"}, +                                                "md5_key": {"type": "str"}, +                                            }, +                                            "type": "list", +                                        }, +                                        "plaintext_password": {"type": "str"}, +                                    }, +                                    "type": "dict", +                                }, +                                "dead_interval": {"type": "int"}, +                                "hello_interval": {"type": "int"}, +                                "retransmit_interval": {"type": "int"}, +                                "transmit_delay": {"type": "int"}, +                            }, +                            "type": "list", +                        }, +                    }, +                    "type": "list", +                }, +                "parameters": { +                    "options": { +                        "abr_type": { +                            "choices": [ +                                "cisco", +                                "ibm", +                                "shortcut", +                                "standard", +                            ], +                            "type": "str", +                        }, +                        "opaque_lsa": {"type": "bool"}, +                        "rfc1583_compatibility": {"type": "bool"}, +                        "router_id": {"type": "str"}, +                    }, +                    "type": "dict", +                }, +                "passive_interface": {"type": "list"}, +                "passive_interface_exclude": {"type": "list"}, +                "redistribute": { +                    "elements": "dict", +                    "options": { +                        "metric": {"type": "int"}, +                        "metric_type": {"type": "int"}, +                        "route_map": {"type": "str"}, +                        "route_type": { +                            "choices": [ +                                "bgp", +                                "connected", +                                "kernel", +                                "rip", +                                "static", +                            ], +                            "type": "str", +                        }, +                    }, +                    "type": "list", +                }, +                "route_map": {"type": "list"}, +                "timers": { +                    "options": { +                        "refresh": { +                            "options": {"timers": {"type": "int"}}, +                            "type": "dict", +                        }, +                        "throttle": { +                            "options": { +                                "spf": { +                                    "options": { +                                        "delay": {"type": "int"}, +                                        "initial_holdtime": {"type": "int"}, +                                        "max_holdtime": {"type": "int"}, +                                    }, +                                    "type": "dict", +                                } +                            }, +                            "type": "dict", +                        }, +                    }, +                    "type": "dict", +                }, +            }, +            "type": "dict", +        }, +        "running_config": {"type": "str"}, +        "state": { +            "choices": [ +                "merged", +                "replaced", +                "deleted", +                "parsed", +                "gathered", +                "rendered", +            ], +            "default": "merged", +            "type": "str", +        }, +    }  # pylint: disable=C0301 diff --git a/plugins/module_utils/network/vyos/config/ospfv2/__init__.py b/plugins/module_utils/network/vyos/config/ospfv2/__init__.py new file mode 100644 index 00000000..e69de29b --- /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 00000000..fd25c178 --- /dev/null +++ b/plugins/module_utils/network/vyos/config/ospfv2/ospfv2.py @@ -0,0 +1,949 @@ +# +# -*- coding: utf-8 -*- +# Copyright 2019 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +""" +The vyos_ospfv2 class +It is in this file where the current configuration (as dict) +is compared to the provided configuration (as dict) and the command set +necessary to bring the current configuration to it's desired end-state is +created +""" +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +from copy import deepcopy +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base import ( +    ConfigBase, +) +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ( +    to_list, +    remove_empties, +) +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.facts import ( +    Facts, +) +from ansible.module_utils.six import iteritems + +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.utils import ( +    list_diff_want_only, +    _in_target, +    _is_w_same, +    _bool_to_str, +) + + +class Ospfv2(ConfigBase): + +    """ +    The vyos_ospfv2 class +    """ + +    gather_subset = ["!all", "!min"] + +    gather_network_resources = ["ospfv2"] + +    def __init__(self, module): +        super(Ospfv2, self).__init__(module) + +    def get_ospfv2_facts(self, data=None): +        """ Get the 'facts' (the current configuration) + +        :rtype: A dictionary +        :returns: The current configuration as a dictionary +        """ + +        (facts, _warnings) = Facts(self._module).get_facts( +            self.gather_subset, self.gather_network_resources, data=data +        ) +        ospfv2_facts = facts["ansible_network_resources"].get("ospfv2", {}) +        return ospfv2_facts + +    def execute_module(self): +        """ Execute the module + +        :rtype: A dictionary +        :returns: The result from module execution +        """ + +        result = {"changed": False} +        warnings = list() +        commands = list() + +        if self.state in self.ACTION_STATES: +            existing_ospfv2_facts = self.get_ospfv2_facts() +        else: +            existing_ospfv2_facts = {} + +        if self.state in self.ACTION_STATES or self.state == "rendered": +            commands.extend(self.set_config(existing_ospfv2_facts)) + +        if commands and self.state in self.ACTION_STATES: +            if not self._module.check_mode: +                self._connection.edit_config(commands) +            result["changed"] = True + +        if self.state in self.ACTION_STATES: +            result["commands"] = commands + +        if self.state in self.ACTION_STATES or self.state == "gathered": +            changed_ospfv2_facts = self.get_ospfv2_facts() +        elif self.state == "rendered": +            result["rendered"] = commands +        elif self.state == "parsed": +            running_config = self._module.params["running_config"] +            if not running_config: +                self._module.fail_json( +                    msg="value of running_config parameter must not be empty for state parsed" +                ) +            result["parsed"] = self.get_ospfv2_facts(data=running_config) +        else: +            changed_ospfv2_facts = {} + +        if self.state in self.ACTION_STATES: +            result["before"] = existing_ospfv2_facts +            if result["changed"]: +                result["after"] = changed_ospfv2_facts +        elif self.state == "gathered": +            result["gathered"] = changed_ospfv2_facts + +        result["warnings"] = warnings +        return result + +    def set_config(self, existing_ospfv2_facts): +        """ Collect the configuration from the args passed to the module, +            collect the current configuration (as a dict from facts) + +        :rtype: A list +        :returns: the commands necessary to migrate the current configuration +                  to the desired configuration +        """ + +        want = self._module.params["config"] +        have = existing_ospfv2_facts +        resp = self.set_state(want, have) +        return to_list(resp) + +    def set_state(self, w, h): +        """ Select the appropriate function based on the state provided + +        :param want: the desired configuration as a dictionary +        :param have: the current configuration as a dictionary +        :rtype: A list +        :returns: the commands necessary to migrate the current configuration +                  to the desired configuration +        """ + +        commands = [] +        if ( +            self.state in ("merged", "replaced", "overridden", "rendered") +            and not w +        ): +            self._module.fail_json( +                msg="value of config parameter must not be empty for state {0}".format( +                    self.state +                ) +            ) + +        if self.state == "deleted": +            commands.extend(self._state_deleted(h)) +        elif self.state in ("merged", "rendered"): +            commands.extend(self._state_merged(w, h)) +        elif self.state == "replaced": +            commands.extend(self._state_replaced(w, h)) +        return commands + +    def search_obj_in_have(self, have, w_name, key): +        """ +        This function  returns the rule-set/rule if it is present in target config. +        :param have: target config. +        :param w_name: rule-set name. +        :param type: rule_sets/rule/r_list. +        :return: rule-set/rule. +        """ + +        if have: +            for item in have: +                if item[key] == w_name[key]: +                    return item +        return None + +    def _state_replaced(self, want, have): +        """ The command generator when state is replaced + +        :rtype: A list +        :returns: the commands necessary to migrate the current configuration +                  to the desired configuration +        """ + +        commands = [] +        if have: +            commands.extend(self._render_ospf_param(have, want, opr=False)) +        commands.extend(self._render_ospf_param(want, have)) +        return commands + +    def _state_merged(self, want, have): +        """ The command generator when state is merged + +        :rtype: A list +        :returns: the commands necessary to merge the provided into +                  the current configuration +        """ + +        commands = [] +        commands.extend(self._render_ospf_param(want, have)) +        return commands + +    def _state_deleted(self, have): +        """ The command generator when state is deleted + +        :rtype: A list +        :returns: the commands necessary to remove the current configuration +                  of the provided objects +        """ + +        commands = [] +        if have: +            commands.append("delete protocols ospf") +        return commands + +    def _render_ospf_param(self, want, have, opr=True): +        """ +        This function forms the set/delete commands for ospf leaf attributes +        and triggers the process for other child attributes. +        for firewall_global attributes. +        :param w: the desired config. +        :param h: the target config. +        :param opr: True/False. +        :return: generated commands list. +        """ + +        commands = [] +        w = deepcopy(remove_empties(want)) +        leaf = ("default_metric", "log_adjacency_changes") +        if w: +            for (key, val) in iteritems(w): +                if opr and key in leaf and not _is_w_same(w, have, key): +                    commands.append( +                        self._form_attr_cmd( +                            attr=key, val=_bool_to_str(val), opr=opr +                        ) +                    ) +                elif not opr and key in leaf and not _in_target(have, key): +                    commands.append( +                        self._form_attr_cmd( +                            attr=key, val=_bool_to_str(val), opr=opr +                        ) +                    ) +                else: +                    commands.extend( +                        self._render_child_param(w, have, key, opr) +                    ) +        return commands + +    def _render_child_param(self, w, h, key, opr=True): +        """ +        This function invoke the function to extend commands +        based on the key. +        :param w: the desired configuration. +        :param h: the current configuration. +        :param key: attribute name. +        :param opr: operation. +        :return: list of commands. +        """ + +        commands = [] +        if key in ("neighbor", "redistribute"): +            commands.extend(self._render_list_dict_param(key, w, h, opr=opr)) +        elif key in ("default_information", "max_metric"): +            commands.extend(self._render_nested_dict_param(key, w, h, opr=opr)) +        elif key in ("mpls_te", "auto_cost", "parameters", "auto_cost"): +            commands.extend(self._render_dict_param(key, w, h, opr=opr)) +        elif key in ( +            "route_map", +            "passive_interface", +            "passive_interface_exclude", +        ): +            commands.extend(self._render_list_param(key, w, h, opr=opr)) +        elif key == "areas": +            commands.extend(self._render_areas(key, w, h, opr=opr)) +        elif key == "timers": +            commands.extend(self._render_timers(key, w, h, opr=opr)) +        elif key == "distance": +            commands.extend(self._render_distance(key, w, h, opr=opr)) +        return commands + +    def _render_dict_param(self, attr, want, have, opr=True): +        """ +        This function generate the commands for dictionary elements. +        :param attr: attribute name. +        :param w: the desired configuration. +        :param h: the target config. +        :param opr: True/False. +        :return: generated list of commands. +        """ + +        commands = [] +        h = {} +        if have: +            h = have.get(attr) or {} +        if not opr and not h: +            commands.append(self._form_attr_cmd(attr=attr, opr=opr)) +        elif want[attr]: +            leaf_dict = { +                "auto_cost": "reference_bandwidth", +                "mpls_te": ("enabled", "router_address"), +                "parameters": ( +                    "router_id", +                    "abr_type", +                    "opaque_lsa", +                    "rfc1583_compatibility", +                ), +            } +            leaf = leaf_dict[attr] +            for (item, value) in iteritems(want[attr]): +                if ( +                    opr +                    and item in leaf +                    and not _is_w_same(want[attr], h, item) +                ): +                    if item == "enabled": +                        item = "enable" +                    if item in ( +                        "opaque_lsa", +                        "enable", +                        "rfc1583_compatibility", +                    ): +                        commands.append( +                            self._form_attr_cmd(key=attr, attr=item, opr=opr) +                        ) +                    else: +                        commands.append( +                            self._form_attr_cmd( +                                key=attr, attr=item, val=value, opr=opr +                            ) +                        ) +                elif not opr and item in leaf and not _in_target(h, item): +                    if item == "enabled": +                        commands.append( +                            self._form_attr_cmd( +                                key=attr, attr="enable", opr=opr +                            ) +                        ) +                    else: +                        commands.append( +                            self._form_attr_cmd(key=attr, attr=item, opr=opr) +                        ) +        return commands + +    def _render_list_param(self, attr, want, have, cmd=None, opr=True): +        """ +        This function forms the commands for passed target list attributes'. +        :param attr: attribute name. +        :param w: the desired config. +        :param h: the target config. +        :param cmd: commands to be prepend. +        :param opr: True/False. +        :return: generated list of commands. +        """ + +        commands = [] +        h = [] +        if want: +            w = want.get(attr) or [] +        if have: +            h = have.get(attr) or [] +        if not cmd: +            cmd = self._compute_command(opr=opr) +        if w: +            if opr: +                members = list_diff_want_only(w, h) +                for member in members: +                    command = cmd + attr.replace("_", "-") + " " +                    if attr == "network": +                        command += member["address"] +                    else: +                        command += member +                    commands.append(command) +            elif not opr: +                if h: +                    for member in w: +                        if attr == "network": +                            if not self.search_obj_in_have( +                                h, member, "address" +                            ): +                                commands.append( +                                    cmd +                                    + attr.replace("_", "-") +                                    + " " +                                    + member["address"] +                                ) +                        elif member not in h: +                            commands.append( +                                cmd + attr.replace("_", "-") + " " + member +                            ) +                else: +                    commands.append(cmd + " " + attr.replace("_", "-")) +        return commands + +    def _render_vlink(self, attr, want, have, cmd=None, opr=True): +        """ +        This function forms the set/delete commands based on the 'opr' type +        for attributes with in desired list of dictionary. +        :param attr: attribute name. +        :param w: the desired config. +        :param h: the target config. +        :param cmd: commands to be prepend. +        :param opr: True/False. +        :return: generated commands list. +        """ + +        commands = [] +        h = [] +        name = {"virtual_link": "address"} +        leaf_dict = { +            "virtual_link": ( +                "address", +                "dead_interval", +                "transmit_delay", +                "hello_interval", +                "retransmit_interval", +            ) +        } +        leaf = leaf_dict[attr] +        w = want.get(attr) or [] +        if have: +            h = have.get(attr) or [] +        if not opr and not h: +            commands.append(cmd + attr.replace("_", "-")) +        elif w: +            for w_item in w: +                for (key, val) in iteritems(w_item): +                    if not cmd: +                        cmd = self._compute_command(opr=opr) +                    h_item = self.search_obj_in_have(h, w_item, name[attr]) +                    if ( +                        opr +                        and key in leaf +                        and not _is_w_same(w_item, h_item, key) +                    ): +                        if key in "address": +                            commands.append( +                                cmd + attr.replace("_", "-") + " " + str(val) +                            ) +                        else: +                            commands.append( +                                cmd +                                + attr.replace("_", "-") +                                + " " +                                + w_item[name[attr]] +                                + " " +                                + key.replace("_", "-") +                                + " " +                                + str(val) +                            ) +                    elif ( +                        not opr and key in leaf and not _in_target(h_item, key) +                    ): +                        if key in "address": +                            commands.append( +                                cmd + attr.replace("_", "-") + " " + str(val) +                            ) +                        else: +                            commands.append( +                                cmd +                                + attr.replace("_", "-") +                                + " " +                                + w_item[name[attr]] +                                + " " +                                + key +                            ) +                    elif key == "authentication": +                        commands.extend( +                            self._render_vlink_auth( +                                attr, +                                key, +                                w_item, +                                h_item, +                                w_item["address"], +                                cmd, +                                opr, +                            ) +                        ) +        return commands + +    def _render_vlink_auth( +        self, attr, key, want, have, address, cmd=None, opr=True +    ): +        """ +        This function forms the set/delete commands based on the 'opr' type +        for attributes with in desired list of dictionary. +        :param attr: attribute name. +        :param w: the desired config. +        :param h: the target config. +        :param cmd: commands to be prepend. +        :param opr: True/False. +        :return: generated commands list. +        """ + +        commands = [] +        h = [] + +        w = want.get(key) or {} +        if have: +            h = have.get(key) or {} +        cmd += attr.replace("_", "-") + " " + address + " " + key + " " +        commands.extend(self._render_list_dict_param("md5", w, h, cmd, opr)) +        return commands + +    def _render_list_dict_param(self, attr, want, have, cmd=None, opr=True): +        """ +        This function forms the set/delete commands based on the 'opr' type +        for attributes with in desired list of dictionary. +        :param attr: attribute name. +        :param w: the desired config. +        :param h: the target config. +        :param cmd: commands to be prepend. +        :param opr: True/False. +        :return: generated commands list. +        """ + +        commands = [] +        h = [] +        name = { +            "redistribute": "route_type", +            "neighbor": "neighbor_id", +            "range": "address", +            "md5": "key_id", +            "vlink": "address", +        } +        leaf_dict = { +            "md5": "md5_key", +            "redistribute": ( +                "metric", +                "route_map", +                "route_type", +                "metric_type", +            ), +            "neighbor": ("priority", "poll_interval", "neighbor_id"), +            "range": ("cost", "address", "substitute", "not_advertise"), +            "vlink": ( +                "address", +                "dead_interval", +                "transmit_delay", +                "hello_interval", +                "retransmit_interval", +            ), +        } +        leaf = leaf_dict[attr] +        w = want.get(attr) or [] +        if have: +            h = have.get(attr) or [] +        if not opr and not h: +            commands.append(self._compute_command(attr=attr, opr=opr)) +        elif w: +            for w_item in w: +                for (key, val) in iteritems(w_item): +                    if not cmd: +                        cmd = self._compute_command(opr=opr) +                    h_item = self.search_obj_in_have(h, w_item, name[attr]) +                    if ( +                        opr +                        and key in leaf +                        and not _is_w_same(w_item, h_item, key) +                    ): +                        if key in ( +                            "route_type", +                            "neighbor_id", +                            "address", +                            "key_id", +                        ): +                            commands.append(cmd + attr + " " + str(val)) +                        elif key == "cost": +                            commands.append( +                                cmd +                                + attr +                                + " " +                                + w_item[name[attr]] +                                + " " +                                + key +                                + " " +                                + str(val) +                            ) +                        elif key == "not_advertise": +                            commands.append( +                                cmd +                                + attr +                                + " " +                                + w_item[name[attr]] +                                + " " +                                + key.replace("_", "-") +                            ) +                        elif key == "md5_key": +                            commands.append( +                                cmd +                                + attr +                                + " " +                                + "key-id" +                                + " " +                                + str(w_item[name[attr]]) +                                + " " +                                + key.replace("_", "-") +                                + " " +                                + w_item[key] +                            ) +                        else: +                            commands.append( +                                cmd +                                + attr +                                + " " +                                + w_item[name[attr]] +                                + " " +                                + key.replace("_", "-") +                                + " " +                                + str(val) +                            ) +                    elif ( +                        not opr and key in leaf and not _in_target(h_item, key) +                    ): +                        if key in ( +                            "route_type", +                            "neighbor_id", +                            "address", +                            "key_id", +                        ): +                            commands.append(cmd + attr + " " + str(val)) +                        else: +                            commands.append( +                                cmd +                                + attr +                                + " " +                                + w_item[name[attr]] +                                + " " +                                + key +                            ) +        return commands + +    def _render_nested_dict_param(self, attr, want, have, opr=True): +        """ +        This function forms the set/delete commands based on the 'opr' type +        for attributes with in desired nested dicts. +        :param attr: attribute name. +        :param w: the desired config. +        :param h: the target config. +        :param cmd: commands to be prepend. +        :param opr: True/False. +        :return: generated commands list. +        """ + +        commands = [] +        attr_dict = { +            "default_information": "originate", +            "max_metric": "router_lsa", +        } +        leaf_dict = { +            "default_information": ( +                "always", +                "metric", +                "metric_type", +                "route_map", +            ), +            "max_metric": ("administrative", "on_startup", "on_shutdown"), +        } +        h = {} +        w = want.get(attr) or {} +        if have: +            h = have.get(attr) or {} +        if not opr and not h: +            commands.append(self._form_attr_cmd(attr=attr, opr=opr)) +        elif w: +            key = attr_dict[attr] +            w_attrib = want[attr].get(key) or {} +            cmd = self._compute_command(opr=opr) +            h_attrib = {} +            if w_attrib: +                leaf = leaf_dict[attr] +                if h and key in h.keys(): +                    h_attrib = h.get(key) or {} +                for (item, val) in iteritems(w[key]): +                    if ( +                        opr +                        and item in leaf +                        and not _is_w_same(w[key], h_attrib, item) +                    ): +                        if item in ("administrative", "always") and val: +                            commands.append( +                                cmd +                                + attr.replace("_", "-") +                                + " " +                                + key.replace("_", "-") +                                + " " +                                + item.replace("_", "-") +                            ) +                        elif item not in ("administrative", "always"): +                            commands.append( +                                cmd +                                + attr.replace("_", "-") +                                + " " +                                + key.replace("_", "-") +                                + " " +                                + item.replace("_", "-") +                                + " " +                                + str(val) +                            ) +                    elif ( +                        not opr +                        and item in leaf +                        and not _in_target(h_attrib, item) +                    ): + +                        commands.append(cmd + attr + " " + item) +        return commands + +    def _render_areas(self, attr, want, have, opr=True): +        """ +        This function forms the set/delete commands based on the 'opr' type +        for ospf area attributes. +        :param attr: attribute name. +        :param w: the desired config. +        :param h: the target config. +        :param opr: True/False. +        :return: generated commands list. +        """ + +        commands = [] +        h_lst = {} +        w_lst = want.get(attr) or [] +        l_set = ("area_id", "shortcut", "authentication") +        if have: +            h_lst = have.get(attr) or [] +        if not opr and not h_lst: +            commands.append(self._form_attr_cmd(attr="area", opr=opr)) +        elif w_lst: +            for w_area in w_lst: +                cmd = ( +                    self._compute_command( +                        key="area", +                        attr=_bool_to_str(w_area["area_id"]), +                        opr=opr, +                    ) +                    + " " +                ) +                h_area = self.search_obj_in_have(h_lst, w_area, "area_id") +                if not opr and not h_area: +                    commands.append( +                        self._form_attr_cmd( +                            key="area", attr=w_area["area_id"], opr=opr +                        ) +                    ) +                else: +                    for (key, val) in iteritems(w_area): +                        if ( +                            opr +                            and key in l_set +                            and not _is_w_same(w_area, h_area, key) +                        ): +                            if key == "area_id": +                                commands.append( +                                    self._form_attr_cmd( +                                        attr="area", +                                        val=_bool_to_str(val), +                                        opr=opr, +                                    ) +                                ) +                            else: +                                commands.append( +                                    cmd +                                    + key +                                    + " " +                                    + _bool_to_str(val).replace("_", "-") +                                ) +                        elif not opr and key in l_set: +                            if key == "area_id" and not _in_target( +                                h_area, key +                            ): +                                commands.append(cmd) +                                continue +                            elif key != "area_id" and not _in_target( +                                h_area, key +                            ): +                                commands.append(cmd + val + " " + key) +                        elif key == "area_type": +                            commands.extend( +                                self._render_area_type( +                                    w_area, h_area, key, cmd, opr +                                ) +                            ) +                        elif key == "network": +                            commands.extend( +                                self._render_list_param( +                                    key, w_area, h_area, cmd, opr +                                ) +                            ) +                        elif key == "range": +                            commands.extend( +                                self._render_list_dict_param( +                                    key, w_area, h_area, cmd, opr +                                ) +                            ) +                        elif key == "virtual_link": +                            commands.extend( +                                self._render_vlink( +                                    key, w_area, h_area, cmd, opr +                                ) +                            ) +        return commands + +    def _render_area_type(self, want, have, attr, cmd, opr=True): +        """ +        This function forms the set/delete commands based on the 'opr' type +        for area_types attributes. +        :param attr: attribute name. +        :param w: the desired config. +        :param h: the target config. +        :param cmd: command to prepend. +        :param opr: True/False. +        :return: generated commands list. +        """ + +        commands = [] +        h_type = {} +        w_type = want.get(attr) or [] +        if have: +            h_type = have.get(attr) or {} +        if not opr and not h_type: +            commands.append(cmd + attr.replace("_", "-")) +        elif w_type: +            key = "normal" +            if ( +                opr +                and key in w_type.keys() +                and not _is_w_same(w_type, h_type, key) +            ): +                if not w_type[key] and h_type and h_type[key]: +                    commands.append( +                        cmd.replace("set", "delete") +                        + attr.replace("_", "-") +                        + " " +                        + key +                    ) +                elif w_type[key]: +                    commands.append(cmd + attr.replace("_", "-") + " " + key) +            elif ( +                not opr +                and key in w_type.keys() +                and not (h_type and key in h_type.keys()) +            ): +                commands.append( +                    cmd + want["area"] + " " + attr.replace("_", "-") +                ) + +            a_type = { +                "nssa": ("set", "default_cost", "no_summary", "translate"), +                "stub": ("set", "default_cost", "no_summary"), +            } +            for key in a_type: +                w_area = want[attr].get(key) or {} +                h_area = {} +                if w_area: +                    if h_type and key in h_type.keys(): +                        h_area = h_type.get(key) or {} +                    for (item, val) in iteritems(w_type[key]): +                        if ( +                            opr +                            and item in a_type[key] +                            and not _is_w_same(w_type[key], h_area, item) +                        ): +                            if item == "set" and val: +                                commands.append( +                                    cmd + attr.replace("_", "-") + " " + key +                                ) +                            elif not val and h_area and h_area[item]: +                                commands.append( +                                    cmd.replace("set", "delete") +                                    + attr.replace("_", "-") +                                    + " " +                                    + key +                                ) +                            elif item != "set": +                                commands.append( +                                    cmd +                                    + attr.replace("_", "-") +                                    + " " +                                    + key +                                    + " " +                                    + item.replace("_", "-") +                                    + " " +                                    + str(val) +                                ) +                        elif ( +                            not opr +                            and item in a_type[key] +                            and not (h_type and key in h_type) +                        ): +                            if item == "set": +                                commands.append( +                                    cmd + attr.replace("_", "-") + " " + key +                                ) +                            else: +                                commands.append( +                                    cmd +                                    + want["area"] +                                    + " " +                                    + attr.replace("_", "-") +                                    + " " +                                    + key +                                    + " " +                                    + item.replace("_", "-") +                                ) +        return commands + +    def _form_attr_cmd(self, key=None, attr=None, val=None, opr=True): +        """ +        This function forms the command for leaf attribute. +        :param key: parent key. +        :param attr: attribute name +        :param value: value +        :param opr: True/False. +        :return: generated command. +        """ + +        return self._compute_command( +            key, attr=self._map_attrib(attr), val=val, opr=opr +        ) + +    def _compute_command( +        self, key=None, attr=None, val=None, remove=False, opr=True +    ): +        """ +        This function construct the add/delete command based on passed attributes. +        :param key: parent key. +        :param attr: attribute name +        :param value: value +        :param opr: True/False. +        :return: generated command. +        """ + +        if remove or not opr: +            cmd = "delete protocols ospf " +        else: +            cmd = "set protocols ospf " +        if key: +            cmd += key.replace("_", "-") + " " +        if attr: +            cmd += attr.replace("_", "-") +        if val: +            cmd += " '" + str(val) + "'" +        return cmd + +    def _map_attrib(self, attrib): +        """ +        - This function construct the regex string. +        - replace the underscore with hyphen. +        :param attrib: attribute +        :return: regex string +        """ + +        return "disable" if attrib == "disabled" else attrib.replace("_", "-") diff --git a/plugins/module_utils/network/vyos/facts/facts.py b/plugins/module_utils/network/vyos/facts/facts.py index 3c87be6b..4c7b340d 100644 --- a/plugins/module_utils/network/vyos/facts/facts.py +++ b/plugins/module_utils/network/vyos/facts/facts.py @@ -43,6 +43,9 @@ 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,      Neighbors, @@ -62,6 +65,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 00000000..e69de29b --- /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 00000000..d62fa9ab --- /dev/null +++ b/plugins/module_utils/network/vyos/facts/ospfv2/ospfv2.py @@ -0,0 +1,499 @@ +# +# -*- coding: utf-8 -*- +# Copyright 2019 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +""" +The vyos ospfv2 fact class +It is in this file the configuration is collected from the device +for a given resource, parsed, and the facts tree is populated +based on the configuration. +""" +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +from re import findall, search, M +from copy import deepcopy +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import ( +    utils, +) + +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.argspec.ospfv2.ospfv2 import ( +    Ospfv2Args, +) + + +class Ospfv2Facts(object): + +    """ The vyos ospfv2 fact class +    """ + +    def __init__( +        self, module, subspec="config", options="options", +    ): + +        self._module = module +        self.argument_spec = Ospfv2Args.argument_spec +        spec = deepcopy(self.argument_spec) +        if subspec: +            if options: +                facts_argument_spec = spec[subspec][options] +            else: +                facts_argument_spec = spec[subspec] +        else: +            facts_argument_spec = spec + +        self.generated_spec = utils.generate_dict(facts_argument_spec) + +    def get_device_data(self, connection): +        return connection.get_config() + +    def populate_facts(self, connection, ansible_facts, data=None): +        """ Populate the facts for ospfv2 +        :param connection: the device connection +        :param ansible_facts: Facts dictionary +        :param data: previously collected conf +        :rtype: dictionary +        :returns: facts +        """ + +        if not data: +            data = self.get_device_data(connection) + +            # typically data is populated from the current device configuration +            # data = connection.get('show running-config | section ^interface') +            # using mock data instead + +        objs = {} +        ospfv2 = findall(r"^set protocols ospf (.+)", data, M) +        if ospfv2: +            objs = self.render_config(ospfv2) +        facts = {} +        params = utils.validate_config(self.argument_spec, {"config": objs}) +        facts["ospfv2"] = utils.remove_empties(params["config"]) +        ansible_facts["ansible_network_resources"].update(facts) +        return ansible_facts + +    def render_config(self, conf): +        """ +        Render config as dictionary structure + +        :param conf: The configuration +        :returns: The generated config +        """ + +        conf = "\n".join(filter(lambda x: x, conf)) +        a_lst = ["default_metric", "log_adjacency_changes"] +        config = self.parse_attr(conf, a_lst) + +        if not config: +            config = {} +        config["timers"] = self.parse_timers(conf) +        config["auto_cost"] = self.parse_auto_cost(conf) +        config["distance"] = self.parse_distance(conf) +        config["max_metric"] = self.parse_max_metric(conf) +        config["default_information"] = self.parse_def_info(conf) +        config["route_map"] = self.parse_leaf_list(conf, "route-map") +        config["mpls_te"] = self.parse_attrib(conf, "mpls_te", "mpls-te") +        config["areas"] = self.parse_attrib_list(conf, "area", "area_id") +        config["parameters"] = self.parse_attrib( +            conf, "parameters", "parameters" +        ) +        config["neighbor"] = self.parse_attrib_list( +            conf, "neighbor", "neighbor_id" +        ) +        config["passive_interface"] = self.parse_leaf_list( +            conf, "passive-interface" +        ) +        config["redistribute"] = self.parse_attrib_list( +            conf, "redistribute", "route_type" +        ) +        config["passive_interface_exclude"] = self.parse_leaf_list( +            conf, "passive-interface-exclude" +        ) +        return config + +    def parse_timers(self, conf): +        """ +        This function triggers the parsing of 'timers' attributes +        :param conf: configuration +        :return: generated config dictionary +        """ + +        cfg_dict = {} +        cfg_dict["refresh"] = self.parse_refresh(conf, "refresh") +        cfg_dict["throttle"] = self.parse_throttle(conf, "spf") +        return cfg_dict + +    def parse_throttle(self, conf, attrib=None): +        """ +        This function triggers the parsing of 'throttle' attributes +        :param conf: configuration +        :param attrib: 'spf' +        :return: generated config dictionary +        """ + +        cfg_dict = {} +        cfg_dict[attrib] = self.parse_attrib(conf, attrib, match=attrib) +        return cfg_dict + +    def parse_refresh(self, conf, attrib=None): +        """ +        This function triggers the parsing of 'refresh' attributes +        :param conf: configuration +        :param attrib: 'refresh' +        :return: generated config dictionary +        """ + +        cfg_dict = self.parse_attr(conf, ["timers"], match=attrib) +        return cfg_dict + +    def parse_leaf_list(self, conf, attrib): +        """ +        This function forms the regex to fetch the listed attributes +        from the configuration data +        :param conf: configuration data +        :param attrib: attribute name +        :return: generated rule list configuration +        """ + +        lst = [] +        items = findall(r"^" + attrib + " (?:'*)(\\S+)(?:'*)", conf, M) +        if items: +            for i in set(items): +                lst.append(i.strip("'")) +                lst.sort() +        return lst + +    def parse_distance(self, conf, attrib=None): +        """ +        This function triggers the parsing of 'distance' attributes +        :param conf: configuration +        :param attrib: attribute name +        :return: generated config dictionary +        """ + +        cfg_dict = self.parse_attr(conf, ["global"], match=attrib) +        cfg_dict["ospf"] = self.parse_ospf(conf, "ospf") +        return cfg_dict + +    def parse_ospf(self, conf, attrib=None): +        """ +        This function triggers the parsing of 'distance ospf' attributes +        :param conf: configuration +        :param attrib: 'ospf' +        :return: generated config dictionary +        """ + +        cfg_dict = self.parse_attrib(conf, "ospf", match=attrib) +        return cfg_dict + +    def parse_max_metric(self, conf): +        """ +        This function triggers the parsing of 'max_metric' attributes +        :param conf: configuration +        :return: generated config dictionary +        """ + +        cfg_dict = {} +        cfg_dict["router_lsa"] = self.parse_attrib( +            conf, "router_lsa", match="router-lsa" +        ) +        return cfg_dict + +    def parse_auto_cost(self, conf, attrib=None): +        """ +        This function triggers the parsing of 'auto_cost' attributes +        :param conf: configuration +        :param attrib: attribute name +        :return: generated config dictionary +        """ + +        cfg_dict = self.parse_attr(conf, ["reference_bandwidth"], match=attrib) +        return cfg_dict + +    def parse_def_info(self, conf): +        """ +        This function triggers the parsing of 'default_information' attributes +        :param conf: configuration +        :return: generated config dictionary +        """ + +        cfg_dict = {} +        cfg_dict["originate"] = self.parse_attrib( +            conf, "originate", "originate" +        ) +        return cfg_dict + +    def parse_area(self, conf, area_id): +        """ +        This function triggers the parsing of 'area' attributes. +        :param conf: configuration data +        :param area_id: area identity +        :return: generated rule configuration dictionary. +        """ + +        rule = self.parse_attrib(conf, "area_id", match=area_id) +        r_sub = { +            "area_type": self.parse_area_type(conf, "area-type"), +            "network": self.parse_network(conf), +            "range": self.parse_attrib_list(conf, "range", "address"), +            "virtual_link": self.parse_attrib_list( +                conf, "virtual-link", "address" +            ), +        } +        rule.update(r_sub) +        return rule + +    def parse_key(self, conf, key_id): +        """ +        This function triggers the parsing of 'area' attributes. +        :param conf: configuration data +        :param area_id: area identity +        :return: generated rule configuration dictionary. +        """ + +        rule = self.parse_attrib(conf, "key_id", match=key_id) +        return rule + +    def parse_area_type(self, conf, attrib=None): +        """ +        This function triggers the parsing of 'area_type' attributes +        :param conf: configuration +        :param attrib: 'area-type' +        :return: generated config dictionary +        """ + +        cfg_dict = self.parse_attr(conf, ["normal"], match=attrib) +        cfg_dict["nssa"] = self.parse_attrib(conf, "nssa", match="nssa") +        cfg_dict["stub"] = self.parse_attrib(conf, "stub", match="stub") +        return cfg_dict + +    def parse_network(self, conf): +        """ +        This function forms the regex to fetch the 'network' +        :param conf: configuration data +        :return: generated rule list configuration +        """ + +        a_lst = [] +        applications = findall(r"network (.+)", conf, M) +        if applications: +            app_lst = [] +            for r in set(applications): +                obj = {"address": r.strip("'")} +                app_lst.append(obj) +            a_lst = sorted(app_lst, key=lambda i: i["address"]) +        return a_lst + +    def parse_vlink(self, conf): +        """ +        This function triggers the parsing of 'virtual_link' attributes +        :param conf: configuration data +        :return: generated rule configuration dictionary +        """ + +        rule = self.parse_attrib(conf, "vlink") +        r_sub = { +            "authentication": self.parse_authentication(conf, "authentication") +        } +        rule.update(r_sub) +        return rule + +    def parse_authentication(self, conf, attrib=None): +        """ +        This function triggers the parsing of 'authentication' attributes. +        :param conf: configuration +        :param attrib: 'authentication' +        :return: generated config dictionary +        """ + +        cfg_dict = self.parse_attr(conf, ["plaintext_password"], match=attrib) +        cfg_dict["md5"] = self.parse_attrib_list(conf, "key-id", "key_id") +        return cfg_dict + +    def parse_attrib_list(self, conf, attrib, param): +        """ +        This function forms the regex to fetch the listed attributes +        from config +        :param conf: configuration data +        :param attrib: attribute name +        :param param: parameter data +        :return: generated rule list configuration +        """ + +        r_lst = [] +        if attrib == "area": +            items = findall( +                r"^" + attrib.replace("_", "-") + " (?:'*)(\\S+)(?:'*)", +                conf, +                M, +            ) +        elif attrib == "key-id": +            items = findall( +                r"^.*" + attrib.replace("_", "-") + " (?:'*)(\\S+)(?:'*)", +                conf, +                M, +            ) +        else: +            items = findall(r"" + attrib + " (?:'*)(\\S+)(?:'*)", conf, M) +        if items: +            a_lst = [] +            for item in set(items): +                i_regex = r" %s .+$" % item +                cfg = "\n".join(findall(i_regex, conf, M)) +                if attrib == "area": +                    obj = self.parse_area(cfg, item) +                elif attrib == "virtual-link": +                    obj = self.parse_vlink(cfg) +                elif attrib == "key-id": +                    obj = self.parse_key(cfg, item) +                else: +                    obj = self.parse_attrib(cfg, attrib) +                obj[param] = item.strip("'") +                if obj: +                    a_lst.append(obj) +            r_lst = sorted(a_lst, key=lambda i: i[param]) +        return r_lst + +    def parse_attrib(self, conf, param, match=None): +        """ +        This function triggers the parsing of 'ospf' attributes +        :param conf: configuration data +        :return: generated configuration dictionary +        """ + +        param_lst = { +            "key_id": ["md5_key"], +            "mpls_te": ["enabled", "router_address"], +            "area_id": ["shortcut", "authentication"], +            "neighbor": ["priority", "poll_interval"], +            "stub": ["set", "default_cost", "no_summary"], +            "range": ["cost", "substitute", "not_advertise"], +            "ospf": ["external", "inter_area", "intra_area"], +            "spf": ["delay", "max_holdtime", "initial_holdtime"], +            "redistribute": ["metric", "metric_type", "route_map"], +            "nssa": ["set", "translate", "default_cost", "no_summary"], +            "config_routes": ["default_metric", "log_adjacency_changes"], +            "originate": ["always", "metric", "metric_type", "route_map"], +            "router_lsa": ["administrative", "on_shutdown", "on_startup"], +            "parameters": [ +                "abr_type", +                "opaque_lsa", +                "router_id", +                "rfc1583_compatibility", +            ], +            "vlink": [ +                "dead_interval", +                "hello_interval", +                "transmit_delay", +                "retransmit_interval", +            ], +        } +        cfg_dict = self.parse_attr(conf, param_lst[param], match) +        return cfg_dict + +    def parse_attr(self, conf, attr_list, match=None): +        """ +        This function peforms the following: +        - Form the regex to fetch the required attribute config. +        - Type cast the output in desired format. +        :param conf: configuration. +        :param attr_list: list of attributes. +        :param match: parent node/attribute name. +        :return: generated config dictionary. +        """ + +        config = {} +        for attrib in attr_list: +            regex = self.map_regex(attrib) + +            if match: +                regex = match.replace("_", "-") + " " + regex +            if conf: +                if self.is_bool(attrib): +                    out = conf.find(attrib.replace("_", "-")) +                    dis = conf.find(attrib.replace("_", "-") + " 'disable'") +                    if match: +                        if attrib == "set" and conf.find(match) >= 1: +                            config[attrib] = True +                        en = conf.find(match + " 'enable'") +                    if out >= 1: +                        if dis >= 1: +                            config[attrib] = False +                        else: +                            config[attrib] = True +                    elif match and en >= 1: +                        config[attrib] = True +                else: +                    out = search(r"^.*" + regex + " (.+)", conf, M) +                    if out: +                        val = out.group(1).strip("'") +                        if self.is_num(attrib): +                            val = int(val) +                        config[attrib] = val +        return config + +    def map_regex(self, attrib): +        """ +        - This function construct the regex string. +        - replace the underscore with hyphen. +        :param attrib: attribute +        :return: regex string +        """ + +        return ( +            "disable" +            if attrib == "disabled" +            else ( +                "enable" +                if attrib == "enabled" +                else ( +                    "area" if attrib == "area_id" else attrib.replace("_", "-") +                ) +            ) +        ) + +    def is_bool(self, attrib): +        """ +        This function looks for the attribute in predefined bool type set. +        :param attrib: attribute. +        :return: True/False +        """ + +        bool_set = ( +            "set", +            "always", +            "normal", +            "enabled", +            "opaque_lsa", +            "not_advertise", +            "administrative", +            "rfc1583_compatibility", +        ) +        return True if attrib in bool_set else False + +    def is_num(self, attrib): +        """ +        This function looks for the attribute in predefined integer type set. +        :param attrib: attribute. +        :return: True/false. +        """ + +        num_set = ( +            "ospf", +            "delay", +            "metric", +            "inter_area", +            "intra_area", +            "on_startup", +            "metric_type", +            "on_shutdown", +            "max_holdtime", +            "poll_interval", +            "default_metric", +            "initial_holdtime", +            "key_id", +        ) +        return True if attrib in num_set else False diff --git a/plugins/module_utils/network/vyos/utils/utils.py b/plugins/module_utils/network/vyos/utils/utils.py index 96feddd9..7e0f3cc3 100644 --- a/plugins/module_utils/network/vyos/utils/utils.py +++ b/plugins/module_utils/network/vyos/utils/utils.py @@ -113,7 +113,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:  | 
