diff options
author | Bradley A. Thornton <bthornto@thethorntons.net> | 2019-08-16 11:45:39 -0700 |
---|---|---|
committer | Bradley A. Thornton <bthornto@thethorntons.net> | 2019-08-16 11:45:39 -0700 |
commit | 6a2aa3b8adaf9dd220916e42ec65904820d64092 (patch) | |
tree | 391360212f87a5d815f91e05205f38f7c733a10a /plugins/module_utils | |
parent | 83a80f9f9df748b10eaa36dd3aab4592aaf40b28 (diff) | |
download | vyos.vyos-6a2aa3b8adaf9dd220916e42ec65904820d64092.tar.gz vyos.vyos-6a2aa3b8adaf9dd220916e42ec65904820d64092.zip |
based on ansible/ansible e7a8e4805349aec2d8a9538f544ad9e29b2e6318
Diffstat (limited to 'plugins/module_utils')
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 |