diff options
Diffstat (limited to 'plugins/module_utils')
9 files changed, 722 insertions, 1 deletions
diff --git a/plugins/module_utils/network/vyos/argspec/facts/facts.py b/plugins/module_utils/network/vyos/argspec/facts/facts.py index 31b1aa9a..1dd15105 100644 --- a/plugins/module_utils/network/vyos/argspec/facts/facts.py +++ b/plugins/module_utils/network/vyos/argspec/facts/facts.py @@ -26,6 +26,8 @@ class FactsArgs(object):  # pylint: disable=R0903          "!lag_interfaces",          "lldp_global",          "!lldp_global", +        "lldp_interfaces", +        "!lldp_interfaces",      ]      argument_spec = { diff --git a/plugins/module_utils/network/vyos/argspec/lldp_interfaces/__init__.py b/plugins/module_utils/network/vyos/argspec/lldp_interfaces/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/plugins/module_utils/network/vyos/argspec/lldp_interfaces/__init__.py diff --git a/plugins/module_utils/network/vyos/argspec/lldp_interfaces/lldp_interfaces.py b/plugins/module_utils/network/vyos/argspec/lldp_interfaces/lldp_interfaces.py new file mode 100644 index 00000000..2976fc09 --- /dev/null +++ b/plugins/module_utils/network/vyos/argspec/lldp_interfaces/lldp_interfaces.py @@ -0,0 +1,89 @@ +# +# -*- 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_lldp_interfaces module +""" + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + + +class Lldp_interfacesArgs(object):  # pylint: disable=R0903 +    """The arg spec for the vyos_lldp_interfaces module +    """ + +    def __init__(self, **kwargs): +        pass + +    argument_spec = { +        "config": { +            "elements": "dict", +            "options": { +                "enable": {"default": True, "type": "bool"}, +                "location": { +                    "options": { +                        "civic_based": { +                            "options": { +                                "ca_info": { +                                    "elements": "dict", +                                    "options": { +                                        "ca_type": {"type": "int"}, +                                        "ca_value": {"type": "str"}, +                                    }, +                                    "type": "list", +                                }, +                                "country_code": { +                                    "required": True, +                                    "type": "str", +                                }, +                            }, +                            "type": "dict", +                        }, +                        "coordinate_based": { +                            "options": { +                                "altitude": {"type": "int"}, +                                "datum": { +                                    "choices": ["WGS84", "NAD83", "MLLW"], +                                    "type": "str", +                                }, +                                "latitude": {"required": True, "type": "str"}, +                                "longitude": {"required": True, "type": "str"}, +                            }, +                            "type": "dict", +                        }, +                        "elin": {"type": "str"}, +                    }, +                    "type": "dict", +                }, +                "name": {"required": True, "type": "str"}, +            }, +            "type": "list", +        }, +        "state": { +            "choices": ["merged", "replaced", "overridden", "deleted"], +            "default": "merged", +            "type": "str", +        }, +    }  # pylint: disable=C0301 diff --git a/plugins/module_utils/network/vyos/config/lldp_interfaces/__init__.py b/plugins/module_utils/network/vyos/config/lldp_interfaces/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/plugins/module_utils/network/vyos/config/lldp_interfaces/__init__.py diff --git a/plugins/module_utils/network/vyos/config/lldp_interfaces/lldp_interfaces.py b/plugins/module_utils/network/vyos/config/lldp_interfaces/lldp_interfaces.py new file mode 100644 index 00000000..a22c27e5 --- /dev/null +++ b/plugins/module_utils/network/vyos/config/lldp_interfaces/lldp_interfaces.py @@ -0,0 +1,422 @@ +# +# -*- 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_lldp_interfaces 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 ansible.module_utils.network.common.cfg.base import ConfigBase +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.facts import ( +    Facts, +) +from ansible.module_utils.network.common.utils import to_list, dict_diff +from ansible.module_utils.six import iteritems +from ansible.module_utils.network.vyos.utils.utils import ( +    search_obj_in_list, +    search_dict_tv_in_list, +    key_value_in_dict, +    is_dict_element_present, +) + + +class Lldp_interfaces(ConfigBase): +    """ +    The vyos_lldp_interfaces class +    """ + +    gather_subset = ["!all", "!min"] + +    gather_network_resources = ["lldp_interfaces"] + +    params = ["enable", "location", "name"] + +    def __init__(self, module): +        super(Lldp_interfaces, self).__init__(module) + +    def get_lldp_interfaces_facts(self): +        """ 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 +        ) +        lldp_interfaces_facts = facts["ansible_network_resources"].get( +            "lldp_interfaces" +        ) +        if not lldp_interfaces_facts: +            return [] +        return lldp_interfaces_facts + +    def execute_module(self): +        """ Execute the module + +        :rtype: A dictionary +        :returns: The result from module execution +        """ +        result = {"changed": False} +        commands = list() +        warnings = list() +        existing_lldp_interfaces_facts = self.get_lldp_interfaces_facts() +        commands.extend(self.set_config(existing_lldp_interfaces_facts)) +        if commands: +            if self._module.check_mode: +                resp = self._connection.edit_config(commands, commit=False) +            else: +                resp = self._connection.edit_config(commands) +            result["changed"] = True + +        result["commands"] = commands + +        if self._module._diff: +            result["diff"] = resp["diff"] if result["changed"] else None + +        changed_lldp_interfaces_facts = self.get_lldp_interfaces_facts() +        result["before"] = existing_lldp_interfaces_facts +        if result["changed"]: +            result["after"] = changed_lldp_interfaces_facts + +        result["warnings"] = warnings +        return result + +    def set_config(self, existing_lldp_interfaces_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_lldp_interfaces_facts +        resp = self.set_state(want, have) +        return to_list(resp) + +    def set_state(self, want, have): +        """ 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 = [] +        state = self._module.params["state"] +        if state == "overridden": +            commands.extend(self._state_overridden(want=want, have=have)) +        elif state == "deleted": +            if want: +                for item in want: +                    name = item["name"] +                    have_item = search_obj_in_list(name, have) +                    commands.extend( +                        self._state_deleted(want=None, have=have_item) +                    ) +            else: +                for have_item in have: +                    commands.extend( +                        self._state_deleted(want=None, have=have_item) +                    ) +        else: +            for want_item in want: +                name = want_item["name"] +                have_item = search_obj_in_list(name, have) +                if state == "merged": +                    commands.extend( +                        self._state_merged(want=want_item, have=have_item) +                    ) +                else: +                    commands.extend( +                        self._state_replaced(want=want_item, have=have_item) +                    ) +        return commands + +    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._state_deleted(want, have)) +        commands.extend(self._state_merged(want, have)) +        return commands + +    def _state_overridden(self, want, have): +        """ The command generator when state is overridden + +        :rtype: A list +        :returns: the commands necessary to migrate the current configuration +                  to the desired configuration +        """ +        commands = [] +        for have_item in have: +            lldp_name = have_item["name"] +            lldp_in_want = search_obj_in_list(lldp_name, want) +            if not lldp_in_want: +                commands.append( +                    self._compute_command(have_item["name"], remove=True) +                ) + +        for want_item in want: +            name = want_item["name"] +            lldp_in_have = search_obj_in_list(name, have) +            commands.extend(self._state_replaced(want_item, lldp_in_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 = [] +        if have: +            commands.extend(self._render_updates(want, have)) +        else: +            commands.extend(self._render_set_commands(want)) +        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: +            params = Lldp_interfaces.params +            for attrib in params: +                if attrib == "location": +                    commands.extend( +                        self._update_location(have["name"], want, have) +                    ) + +        elif have: +            commands.append(self._compute_command(have["name"], remove=True)) +        return commands + +    def _render_updates(self, want, have): +        commands = [] +        lldp_name = have["name"] +        commands.extend(self._configure_status(lldp_name, want, have)) +        commands.extend(self._add_location(lldp_name, want, have)) + +        return commands + +    def _render_set_commands(self, want): +        commands = [] +        have = {} +        lldp_name = want["name"] +        params = Lldp_interfaces.params + +        commands.extend(self._add_location(lldp_name, want, have)) +        for attrib in params: +            value = want[attrib] +            if value: +                if attrib == "location": +                    commands.extend(self._add_location(lldp_name, want, have)) +                elif attrib == "enable": +                    if not value: +                        commands.append( +                            self._compute_command(lldp_name, value="disable") +                        ) +                else: +                    commands.append(self._compute_command(lldp_name)) + +        return commands + +    def _configure_status(self, name, want_item, have_item): +        commands = [] +        if is_dict_element_present(have_item, "enable"): +            temp_have_item = False +        else: +            temp_have_item = True +        if want_item["enable"] != temp_have_item: +            if want_item["enable"]: +                commands.append( +                    self._compute_command(name, value="disable", remove=True) +                ) +            else: +                commands.append(self._compute_command(name, value="disable")) +        return commands + +    def _add_location(self, name, want_item, have_item): +        commands = [] +        have_dict = {} +        have_ca = {} +        set_cmd = name + " location " +        want_location_type = want_item.get("location") or {} +        have_location_type = have_item.get("location") or {} + +        if want_location_type["coordinate_based"]: +            want_dict = want_location_type.get("coordinate_based") or {} +            if is_dict_element_present(have_location_type, "coordinate_based"): +                have_dict = have_location_type.get("coordinate_based") or {} +            location_type = "coordinate-based" +            updates = dict_diff(have_dict, want_dict) +            for key, value in iteritems(updates): +                if value: +                    commands.append( +                        self._compute_command( +                            set_cmd + location_type, key, str(value) +                        ) +                    ) + +        elif want_location_type["civic_based"]: +            location_type = "civic-based" +            want_dict = want_location_type.get("civic_based") or {} +            want_ca = want_dict.get("ca_info") or [] +            if is_dict_element_present(have_location_type, "civic_based"): +                have_dict = have_location_type.get("civic_based") or {} +                have_ca = have_dict.get("ca_info") or [] +                if want_dict["country_code"] != have_dict["country_code"]: +                    commands.append( +                        self._compute_command( +                            set_cmd + location_type, +                            "country-code", +                            str(want_dict["country_code"]), +                        ) +                    ) +            else: +                commands.append( +                    self._compute_command( +                        set_cmd + location_type, +                        "country-code", +                        str(want_dict["country_code"]), +                    ) +                ) +            commands.extend(self._add_civic_address(name, want_ca, have_ca)) + +        elif want_location_type["elin"]: +            location_type = "elin" +            if is_dict_element_present(have_location_type, "elin"): +                if want_location_type.get("elin") != have_location_type.get( +                    "elin" +                ): +                    commands.append( +                        self._compute_command( +                            set_cmd + location_type, +                            value=str(want_location_type["elin"]), +                        ) +                    ) +            else: +                commands.append( +                    self._compute_command( +                        set_cmd + location_type, +                        value=str(want_location_type["elin"]), +                    ) +                ) +        return commands + +    def _update_location(self, name, want_item, have_item): +        commands = [] +        del_cmd = name + " location" +        want_location_type = want_item.get("location") or {} +        have_location_type = have_item.get("location") or {} + +        if want_location_type["coordinate_based"]: +            want_dict = want_location_type.get("coordinate_based") or {} +            if is_dict_element_present(have_location_type, "coordinate_based"): +                have_dict = have_location_type.get("coordinate_based") or {} +                location_type = "coordinate-based" +                for key, value in iteritems(have_dict): +                    only_in_have = key_value_in_dict(key, value, want_dict) +                    if not only_in_have: +                        commands.append( +                            self._compute_command( +                                del_cmd + location_type, key, str(value), True +                            ) +                        ) +            else: +                commands.append(self._compute_command(del_cmd, remove=True)) + +        elif want_location_type["civic_based"]: +            want_dict = want_location_type.get("civic_based") or {} +            want_ca = want_dict.get("ca_info") or [] +            if is_dict_element_present(have_location_type, "civic_based"): +                have_dict = have_location_type.get("civic_based") or {} +                have_ca = have_dict.get("ca_info") +                commands.extend( +                    self._update_civic_address(name, want_ca, have_ca) +                ) +            else: +                commands.append(self._compute_command(del_cmd, remove=True)) + +        else: +            if is_dict_element_present(have_location_type, "elin"): +                if want_location_type.get("elin") != have_location_type.get( +                    "elin" +                ): +                    commands.append( +                        self._compute_command(del_cmd, remove=True) +                    ) +            else: +                commands.append(self._compute_command(del_cmd, remove=True)) +        return commands + +    def _add_civic_address(self, name, want, have): +        commands = [] +        for item in want: +            ca_type = item["ca_type"] +            ca_value = item["ca_value"] +            obj_in_have = search_dict_tv_in_list( +                ca_type, ca_value, have, "ca_type", "ca_value" +            ) +            if not obj_in_have: +                commands.append( +                    self._compute_command( +                        key=name + " location civic-based ca-type", +                        attrib=str(ca_type) + " ca-value", +                        value=ca_value, +                    ) +                ) +        return commands + +    def _update_civic_address(self, name, want, have): +        commands = [] +        for item in have: +            ca_type = item["ca_type"] +            ca_value = item["ca_value"] +            in_want = search_dict_tv_in_list( +                ca_type, ca_value, want, "ca_type", "ca_value" +            ) +            if not in_want: +                commands.append( +                    self._compute_command( +                        name, +                        "location civic-based ca-type", +                        str(ca_type), +                        remove=True, +                    ) +                ) +        return commands + +    def _compute_command(self, key, attrib=None, value=None, remove=False): +        if remove: +            cmd = "delete service lldp interface " +        else: +            cmd = "set service lldp interface " +        cmd += key +        if attrib: +            cmd += " " + attrib +        if value: +            cmd += " '" + value + "'" +        return cmd diff --git a/plugins/module_utils/network/vyos/facts/facts.py b/plugins/module_utils/network/vyos/facts/facts.py index fb05f2ac..d4aec706 100644 --- a/plugins/module_utils/network/vyos/facts/facts.py +++ b/plugins/module_utils/network/vyos/facts/facts.py @@ -25,6 +25,9 @@ from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.lag_i  from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.lldp_global.lldp_global import (      Lldp_globalFacts,  ) +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.lldp_interfaces.lldp_interfaces import ( +    Lldp_interfacesFacts, +)  from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.legacy.base import (      Default,      Neighbors, @@ -38,6 +41,7 @@ FACT_RESOURCE_SUBSETS = dict(      l3_interfaces=L3_interfacesFacts,      lag_interfaces=Lag_interfacesFacts,      lldp_global=Lldp_globalFacts, +    lldp_interfaces=Lldp_interfacesFacts,  ) @@ -55,7 +59,6 @@ class Facts(FactsBase):          self, legacy_facts_type=None, resource_facts_type=None, data=None      ):          """ Collect the facts for vyos -          :param legacy_facts_type: List of legacy facts types          :param resource_facts_type: List of resource fact types          :param data: previously collected conf diff --git a/plugins/module_utils/network/vyos/facts/lldp_interfaces/__init__.py b/plugins/module_utils/network/vyos/facts/lldp_interfaces/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/plugins/module_utils/network/vyos/facts/lldp_interfaces/__init__.py diff --git a/plugins/module_utils/network/vyos/facts/lldp_interfaces/lldp_interfaces.py b/plugins/module_utils/network/vyos/facts/lldp_interfaces/lldp_interfaces.py new file mode 100644 index 00000000..72629ed1 --- /dev/null +++ b/plugins/module_utils/network/vyos/facts/lldp_interfaces/lldp_interfaces.py @@ -0,0 +1,153 @@ +# +# -*- 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 lldp_interfaces fact class +It is in this file the configuration is collected from the device +for a given resource, parsed, and the facts tree is populated +based on the configuration. +""" + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + + +from re import findall, search, M +from copy import deepcopy + +from ansible.module_utils.network.common import utils +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.argspec.lldp_interfaces.lldp_interfaces import ( +    Lldp_interfacesArgs, +) + + +class Lldp_interfacesFacts(object): +    """ The vyos lldp_interfaces fact class +    """ + +    def __init__(self, module, subspec="config", options="options"): +        self._module = module +        self.argument_spec = Lldp_interfacesArgs.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 populate_facts(self, connection, ansible_facts, data=None): +        """ Populate the facts for lldp_interfaces +        :param connection: the device connection +        :param ansible_facts: Facts dictionary +        :param data: previously collected conf +        :rtype: dictionary +        :returns: facts +        """ +        if not data: +            data = connection.get_config() + +        objs = [] +        lldp_names = findall(r"^set service lldp interface (\S+)", data, M) +        if lldp_names: +            for lldp in set(lldp_names): +                lldp_regex = r" %s .+$" % lldp +                cfg = findall(lldp_regex, data, M) +                obj = self.render_config(cfg) +                obj["name"] = lldp.strip("'") +                if obj: +                    objs.append(obj) +        facts = {} +        if objs: +            facts["lldp_interfaces"] = objs +            ansible_facts["ansible_network_resources"].update(facts) + +        ansible_facts["ansible_network_resources"].update(facts) +        return ansible_facts + +    def render_config(self, conf): +        """ +        Render config as dictionary structure and delete keys +          from spec for null values + +        :param spec: The facts tree, generated from the argspec +        :param conf: The configuration +        :rtype: dictionary +        :returns: The generated config +        """ +        config = {} +        location = {} + +        civic_conf = "\n".join(filter(lambda x: ("civic-based" in x), conf)) +        elin_conf = "\n".join(filter(lambda x: ("elin" in x), conf)) +        coordinate_conf = "\n".join( +            filter(lambda x: ("coordinate-based" in x), conf) +        ) +        disable = "\n".join(filter(lambda x: ("disable" in x), conf)) + +        coordinate_based_conf = self.parse_attribs( +            ["altitude", "datum", "longitude", "latitude"], coordinate_conf +        ) +        elin_based_conf = self.parse_lldp_elin_based(elin_conf) +        civic_based_conf = self.parse_lldp_civic_based(civic_conf) +        if disable: +            config["enable"] = False +        if coordinate_conf: +            location["coordinate_based"] = coordinate_based_conf +            config["location"] = location +        elif civic_based_conf: +            location["civic_based"] = civic_based_conf +            config["location"] = location +        elif elin_conf: +            location["elin"] = elin_based_conf +            config["location"] = location + +        return utils.remove_empties(config) + +    def parse_attribs(self, attribs, conf): +        config = {} +        for item in attribs: +            value = utils.parse_conf_arg(conf, item) +            if value: +                value = value.strip("'") +                if item == "altitude": +                    value = int(value) +                config[item] = value +            else: +                config[item] = None +        return utils.remove_empties(config) + +    def parse_lldp_civic_based(self, conf): +        civic_based = None +        if conf: +            civic_info_list = [] +            civic_add_list = findall(r"^.*civic-based ca-type (.+)", conf, M) +            if civic_add_list: +                for civic_add in civic_add_list: +                    ca = civic_add.split(" ") +                    c_add = {} +                    c_add["ca_type"] = int(ca[0].strip("'")) +                    c_add["ca_value"] = ca[2].strip("'") +                    civic_info_list.append(c_add) + +                country_code = search( +                    r"^.*civic-based country-code (.+)", conf, M +                ) +                civic_based = {} +                civic_based["ca_info"] = civic_info_list +                civic_based["country_code"] = country_code.group(1).strip("'") +        return civic_based + +    def parse_lldp_elin_based(self, conf): +        elin_based = None +        if conf: +            e_num = search(r"^.* elin (.+)", conf, M) +            elin_based = e_num.group(1).strip("'") + +        return elin_based diff --git a/plugins/module_utils/network/vyos/utils/utils.py b/plugins/module_utils/network/vyos/utils/utils.py index 1968cccd..6504bcd9 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 +from ansible.module_utils.six import iteritems  def search_obj_in_list(name, lst, key="name"): @@ -126,3 +127,54 @@ def list_diff_want_only(want_list, have_list):              if i in want_list and i not in have_list          ]      return diff + + +def search_dict_tv_in_list(d_val1, d_val2, lst, key1, key2): +    """ +    This function return the dict object if it exist in list. +    :param d_val1: +    :param d_val2: +    :param lst: +    :param key1: +    :param key2: +    :return: +    """ +    obj = next( +        ( +            item +            for item in lst +            if item[key1] == d_val1 and item[key2] == d_val2 +        ), +        None, +    ) +    if obj: +        return obj +    else: +        return None + + +def key_value_in_dict(have_key, have_value, want_dict): +    """ +    This function checks whether the key and values exist in dict +    :param have_key: +    :param have_value: +    :param want_dict: +    :return: +    """ +    for key, value in iteritems(want_dict): +        if key == have_key and value == have_value: +            return True +    return False + + +def is_dict_element_present(dict, key): +    """ +    This function checks whether the key is present in dict. +    :param dict: +    :param key: +    :return: +    """ +    for item in dict: +        if item == key: +            return True +    return False  | 
