diff options
Diffstat (limited to 'plugins/module_utils/network')
11 files changed, 687 insertions, 10 deletions
| diff --git a/plugins/module_utils/network/vyos/argspec/facts/facts.py b/plugins/module_utils/network/vyos/argspec/facts/facts.py index fc9d438e..624d2fdb 100644 --- a/plugins/module_utils/network/vyos/argspec/facts/facts.py +++ b/plugins/module_utils/network/vyos/argspec/facts/facts.py @@ -4,8 +4,6 @@  """  The arg spec for the vyos facts module.  """ - -  from __future__ import absolute_import, division, print_function  __metaclass__ = type @@ -24,6 +22,8 @@ class FactsArgs(object):  # pylint: disable=R0903          "!interfaces",          "l3_interfaces",          "!l3_interfaces", +        "lag_interfaces", +        "!lag_interfaces",      ]      argument_spec = { diff --git a/plugins/module_utils/network/vyos/argspec/lag_interfaces/__init__.py b/plugins/module_utils/network/vyos/argspec/lag_interfaces/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/plugins/module_utils/network/vyos/argspec/lag_interfaces/__init__.py diff --git a/plugins/module_utils/network/vyos/argspec/lag_interfaces/lag_interfaces.py b/plugins/module_utils/network/vyos/argspec/lag_interfaces/lag_interfaces.py new file mode 100644 index 00000000..97c5d5a2 --- /dev/null +++ b/plugins/module_utils/network/vyos/argspec/lag_interfaces/lag_interfaces.py @@ -0,0 +1,80 @@ +# 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_lag_interfaces module +""" +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + + +class Lag_interfacesArgs(object):  # pylint: disable=R0903 +    """The arg spec for the vyos_lag_interfaces module +    """ + +    def __init__(self, **kwargs): +        pass + +    argument_spec = { +        "config": { +            "elements": "dict", +            "options": { +                "arp_monitor": { +                    "options": { +                        "interval": {"type": "int"}, +                        "target": {"type": "list"}, +                    }, +                    "type": "dict", +                }, +                "hash_policy": { +                    "choices": ["layer2", "layer2+3", "layer3+4"], +                    "type": "str", +                }, +                "members": { +                    "elements": "dict", +                    "options": {"member": {"type": "str"}}, +                    "type": "list", +                }, +                "mode": { +                    "choices": [ +                        "802.3ad", +                        "active-backup", +                        "broadcast", +                        "round-robin", +                        "transmit-load-balance", +                        "adaptive-load-balance", +                        "xor-hash", +                    ], +                    "type": "str", +                }, +                "name": {"required": True, "type": "str"}, +                "primary": {"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/interfaces/interfaces.py b/plugins/module_utils/network/vyos/config/interfaces/interfaces.py index adf61d78..e5724f52 100644 --- a/plugins/module_utils/network/vyos/config/interfaces/interfaces.py +++ b/plugins/module_utils/network/vyos/config/interfaces/interfaces.py @@ -25,7 +25,6 @@ from ansible.module_utils.six import iteritems  from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.facts import (      Facts,  ) -  from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.utils import (      search_obj_in_list,      get_interface_type, diff --git a/plugins/module_utils/network/vyos/config/l3_interfaces/l3_interfaces.py b/plugins/module_utils/network/vyos/config/l3_interfaces/l3_interfaces.py index a69db052..2bd04b69 100644 --- a/plugins/module_utils/network/vyos/config/l3_interfaces/l3_interfaces.py +++ b/plugins/module_utils/network/vyos/config/l3_interfaces/l3_interfaces.py @@ -23,7 +23,6 @@ from ansible.module_utils.six import iteritems  from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.facts import (      Facts,  ) -  from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.utils import (      search_obj_in_list,      get_interface_type, diff --git a/plugins/module_utils/network/vyos/config/lag_interfaces/__init__.py b/plugins/module_utils/network/vyos/config/lag_interfaces/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/plugins/module_utils/network/vyos/config/lag_interfaces/__init__.py diff --git a/plugins/module_utils/network/vyos/config/lag_interfaces/lag_interfaces.py b/plugins/module_utils/network/vyos/config/lag_interfaces/lag_interfaces.py new file mode 100644 index 00000000..44a8a62e --- /dev/null +++ b/plugins/module_utils/network/vyos/config/lag_interfaces/lag_interfaces.py @@ -0,0 +1,410 @@ +# Copyright 2019 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +""" +The vyos_lag_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_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.utils import ( +    search_obj_in_list, +    get_lst_diff_for_dicts, +    list_diff_want_only, +    list_diff_have_only, +) + + +class Lag_interfaces(ConfigBase): +    """ +    The vyos_lag_interfaces class +    """ + +    gather_subset = ["!all", "!min"] + +    gather_network_resources = ["lag_interfaces"] + +    params = [ +        "arp_monitor", +        "hash_policy", +        "members", +        "mode", +        "name", +        "primary", +    ] + +    def __init__(self, module): +        super(Lag_interfaces, self).__init__(module) + +    def get_lag_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 +        ) +        lag_interfaces_facts = facts["ansible_network_resources"].get( +            "lag_interfaces" +        ) +        if not lag_interfaces_facts: +            return [] +        return lag_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_lag_interfaces_facts = self.get_lag_interfaces_facts() +        commands.extend(self.set_config(existing_lag_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_lag_interfaces_facts = self.get_lag_interfaces_facts() + +        result["before"] = existing_lag_interfaces_facts +        if result["changed"]: +            result["after"] = changed_lag_interfaces_facts + +        result["warnings"] = warnings +        return result + +    def set_config(self, existing_lag_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_lag_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, have)) +        elif state == "deleted": +            if want: +                for want_item in want: +                    name = want_item["name"] +                    obj_in_have = search_obj_in_list(name, have) +                    commands.extend(self._state_deleted(obj_in_have)) +            else: +                for have_item in have: +                    commands.extend(self._state_deleted(have_item)) +        else: +            for want_item in want: +                name = want_item["name"] +                obj_in_have = search_obj_in_list(name, have) +                if state == "merged": +                    commands.extend(self._state_merged(want_item, obj_in_have)) +                elif state == "replaced": +                    commands.extend( +                        self._state_replaced(want_item, obj_in_have) +                    ) +        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._render_del_commands(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: +            lag_name = have_item["name"] +            obj_in_want = search_obj_in_list(lag_name, want) +            if not obj_in_want: +                commands.extend(self._purge_attribs(have_item)) + +        for want_item in want: +            name = want_item["name"] +            obj_in_have = search_obj_in_list(name, have) +            commands.extend(self._state_replaced(want_item, obj_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, 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.extend(self._purge_attribs(have)) +        return commands + +    def _render_updates(self, want, have): +        commands = [] + +        temp_have_members = have.pop("members", None) +        temp_want_members = want.pop("members", None) + +        updates = dict_diff(have, want) + +        if temp_have_members: +            have["members"] = temp_have_members +        if temp_want_members: +            want["members"] = temp_want_members + +        commands.extend(self._add_bond_members(want, have)) + +        if updates: +            for key, value in iteritems(updates): +                if value: +                    if key == "arp_monitor": +                        commands.extend( +                            self._add_arp_monitor(updates, key, want, have) +                        ) +                    else: +                        commands.append( +                            self._compute_command( +                                have["name"], key, str(value) +                            ) +                        ) +        return commands + +    def _render_set_commands(self, want): +        commands = [] +        have = [] + +        params = Lag_interfaces.params + +        for attrib in params: +            value = want[attrib] +            if value: +                if attrib == "arp_monitor": +                    commands.extend( +                        self._add_arp_monitor(want, attrib, want, have) +                    ) +                elif attrib == "members": +                    commands.extend(self._add_bond_members(want, have)) +                elif attrib != "name": +                    commands.append( +                        self._compute_command( +                            want["name"], attrib, value=str(value) +                        ) +                    ) +        return commands + +    def _purge_attribs(self, have): +        commands = [] +        for item in Lag_interfaces.params: +            if have.get(item): +                if item == "members": +                    commands.extend(self._delete_bond_members(have)) +                elif item != "name": +                    commands.append( +                        self._compute_command( +                            have["name"], attrib=item, remove=True +                        ) +                    ) +        return commands + +    def _render_del_commands(self, want, have): +        commands = [] + +        params = Lag_interfaces.params +        for attrib in params: +            if attrib == "members": +                commands.extend(self._update_bond_members(attrib, want, have)) +            elif attrib == "arp_monitor": +                commands.extend(self._update_arp_monitor(attrib, want, have)) +            elif have.get(attrib) and not want.get(attrib): +                commands.append( +                    self._compute_command(have["name"], attrib, remove=True) +                ) +        return commands + +    def _add_bond_members(self, want, have): +        commands = [] +        diff_members = get_lst_diff_for_dicts(want, have, "members") +        if diff_members: +            for key in diff_members: +                commands.append( +                    self._compute_command( +                        key["member"], +                        "bond-group", +                        want["name"], +                        type="ethernet", +                    ) +                ) +        return commands + +    def _add_arp_monitor(self, updates, key, want, have): +        commands = [] +        arp_monitor = updates.get(key) or {} +        diff_targets = self._get_arp_monitor_target_diff( +            want, have, key, "target" +        ) + +        if "interval" in arp_monitor: +            commands.append( +                self._compute_command( +                    key, "interval", str(arp_monitor["interval"]) +                ) +            ) +        if diff_targets: +            for target in diff_targets: +                commands.append(self._compute_commands(key, "target", target)) +        return commands + +    def _delete_bond_members(self, have): +        commands = [] +        for member in have["members"]: +            commands.append( +                self._compute_command( +                    member["member"], +                    "bond-group", +                    have["name"], +                    remove=True, +                    type="ethernet", +                ) +            ) +        return commands + +    def _update_arp_monitor(self, key, want, have): +        commands = [] +        want_arp_target = [] +        have_arp_target = [] +        want_arp_monitor = want.get(key) or {} +        have_arp_monitor = have.get(key) or {} +        del_cmd = "delete interface bonding " + have["name"] + +        if want_arp_monitor and "target" in want_arp_monitor: +            want_arp_target = want_arp_monitor["target"] + +        if have_arp_monitor and "target" in have_arp_monitor: +            have_arp_target = have_arp_monitor["target"] + +        if "interval" in have_arp_monitor and not want_arp_monitor: +            commands.append(del_cmd + " " + key + " interval") +        if "target" in have_arp_monitor: +            target_diff = list_diff_have_only(want_arp_target, have_arp_target) +            if target_diff: +                for target in target_diff: +                    commands.append(del_cmd + " " + key + " target " + target) + +        return commands + +    def _update_bond_members(self, key, want, have): +        commands = [] +        want_members = want.get(key) or [] +        have_members = have.get(key) or [] + +        members_diff = list_diff_have_only(want_members, have_members) +        if members_diff: +            for member in members_diff: +                commands.append( +                    self._compute_command( +                        member[key], +                        "bond-group", +                        have["name"], +                        False, +                        "ethernet", +                    ) +                ) +        return commands + +    def _get_arp_monitor_target_diff( +        self, want_list, have_list, dict_name, lst +    ): +        want_arp_target = [] +        have_arp_target = [] + +        want_arp_monitor = want_list.get(dict_name) or {} +        if want_arp_monitor and lst in want_arp_monitor: +            want_arp_target = want_arp_monitor[lst] + +        if not have_list: +            diff = want_arp_target +        else: +            have_arp_monitor = have_list.get(dict_name) or {} +            if have_arp_monitor and lst in have_arp_monitor: +                have_arp_target = have_arp_monitor[lst] + +            diff = list_diff_want_only(want_arp_target, have_arp_target) +        return diff + +    def _compute_command( +        self, key, attrib, value=None, remove=False, type="bonding" +    ): +        if remove: +            cmd = "delete interfaces " + type +        else: +            cmd = "set interfaces " + type +        cmd += " " + key +        if attrib == "arp_monitor": +            attrib = "arp-monitor" +        elif attrib == "hash_policy": +            attrib = "hash-policy" +        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 eae9489f..9c389c91 100644 --- a/plugins/module_utils/network/vyos/facts/facts.py +++ b/plugins/module_utils/network/vyos/facts/facts.py @@ -15,16 +15,16 @@ __metaclass__ = type  from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.argspec.facts.facts import (      FactsArgs,  ) -  from ansible.module_utils.network.common.facts.facts import FactsBase  from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.interfaces.interfaces import (      InterfacesFacts,  ) -  from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.l3_interfaces.l3_interfaces import (      L3_interfacesFacts,  ) - +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.lag_interfaces.lag_interfaces import ( +    Lag_interfacesFacts, +)  from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.legacy.base import (      Default,      Neighbors, @@ -34,7 +34,9 @@ from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.legac  FACT_LEGACY_SUBSETS = dict(default=Default, neighbors=Neighbors, config=Config)  FACT_RESOURCE_SUBSETS = dict( -    interfaces=InterfacesFacts, l3_interfaces=L3_interfacesFacts +    interfaces=InterfacesFacts, +    l3_interfaces=L3_interfacesFacts, +    lag_interfaces=Lag_interfacesFacts,  ) diff --git a/plugins/module_utils/network/vyos/facts/lag_interfaces/__init__.py b/plugins/module_utils/network/vyos/facts/lag_interfaces/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/plugins/module_utils/network/vyos/facts/lag_interfaces/__init__.py diff --git a/plugins/module_utils/network/vyos/facts/lag_interfaces/lag_interfaces.py b/plugins/module_utils/network/vyos/facts/lag_interfaces/lag_interfaces.py new file mode 100644 index 00000000..6ae780f4 --- /dev/null +++ b/plugins/module_utils/network/vyos/facts/lag_interfaces/lag_interfaces.py @@ -0,0 +1,150 @@ +# +# -*- 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 lag_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.lag_interfaces.lag_interfaces import ( +    Lag_interfacesArgs, +) + + +class Lag_interfacesFacts(object): +    """ The vyos lag_interfaces fact class +    """ + +    def __init__(self, module, subspec="config", options="options"): +        self._module = module +        self.argument_spec = Lag_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 lag_interfaces +        :param module: the module instance +        :param connection: the device connection +        :param data: previously collected conf +        :rtype: dictionary +        :returns: facts +        """ +        if not data: +            data = connection.get_config() + +        objs = [] +        lag_names = findall(r"^set interfaces bonding (\S+)", data, M) +        if lag_names: +            for lag in set(lag_names): +                lag_regex = r" %s .+$" % lag +                cfg = findall(lag_regex, data, M) +                obj = self.render_config(cfg) + +                output = connection.run_commands( +                    ["show interfaces bonding " + lag + " slaves"] +                ) +                lines = output[0].splitlines() +                members = [] +                member = {} +                if len(lines) > 1: +                    for line in lines[2:]: +                        splitted_line = line.split() + +                        if len(splitted_line) > 1: +                            member["member"] = splitted_line[0] +                            members.append(member) +                        else: +                            members = [] +                        member = {} +                obj["name"] = lag.strip("'") +                if members: +                    obj["members"] = members + +                if obj: +                    objs.append(obj) + +        facts = {} +        if objs: +            facts["lag_interfaces"] = [] +            params = utils.validate_config( +                self.argument_spec, {"config": objs} +            ) +            for cfg in params["config"]: +                facts["lag_interfaces"].append(utils.remove_empties(cfg)) + +        ansible_facts["ansible_network_resources"].update(facts) +        return ansible_facts + +    def render_config(self, conf): +        """ +        Render config as dictionary structure 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 +        """ +        arp_monitor_conf = "\n".join( +            filter(lambda x: ("arp-monitor" in x), conf) +        ) +        hash_policy_conf = "\n".join( +            filter(lambda x: ("hash-policy" in x), conf) +        ) +        lag_conf = "\n".join(filter(lambda x: ("bond" in x), conf)) +        config = self.parse_attribs(["mode", "primary"], lag_conf) +        config["arp_monitor"] = self.parse_arp_monitor(arp_monitor_conf) +        config["hash_policy"] = self.parse_hash_policy(hash_policy_conf) + +        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: +                config[item] = value.strip("'") +            else: +                config[item] = None +        return utils.remove_empties(config) + +    def parse_arp_monitor(self, conf): +        arp_monitor = None +        if conf: +            arp_monitor = {} +            target_list = [] +            interval = search(r"^.*arp-monitor interval (.+)", conf, M) +            targets = findall(r"^.*arp-monitor target '(.+)'", conf, M) +            if targets: +                for target in targets: +                    target_list.append(target) +                arp_monitor["target"] = target_list +            if interval: +                value = interval.group(1).strip("'") +                arp_monitor["interval"] = int(value) +        return arp_monitor + +    def parse_hash_policy(self, conf): +        hash_policy = None +        if conf: +            hash_policy = search(r"^.*hash-policy (.+)", conf, M) +            hash_policy = hash_policy.group(1).strip("'") +        return hash_policy diff --git a/plugins/module_utils/network/vyos/utils/utils.py b/plugins/module_utils/network/vyos/utils/utils.py index 5fd0da24..d6c11bdc 100644 --- a/plugins/module_utils/network/vyos/utils/utils.py +++ b/plugins/module_utils/network/vyos/utils/utils.py @@ -4,8 +4,6 @@  # (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)  # utils - -  from __future__ import absolute_import, division, print_function  __metaclass__ = type @@ -67,3 +65,42 @@ def diff_list_of_dicts(want, have):          diff.append(dict((x, y) for x, y in element))      return diff + + +def list_diff_have_only(want_list, have_list): +    if have_list and not want_list: +        diff = have_list +    elif not have_list: +        diff = None +    else: +        diff = [ +            i +            for i in have_list + want_list +            if i in have_list and i not in want_list +        ] +    return diff + + +def list_diff_want_only(want_list, have_list): +    if have_list and not want_list: +        diff = None +    elif not have_list: +        diff = want_list +    else: +        diff = [ +            i +            for i in have_list + want_list +            if i in want_list and i not in have_list +        ] +    return diff + + +def get_lst_diff_for_dicts(want, have, lst): +    if not have: +        diff = want.get(lst) or [] + +    else: +        want_elements = want.get(lst) or {} +        have_elements = have.get(lst) or {} +        diff = list_diff_want_only(want_elements, have_elements) +    return diff | 
