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 index 97c5d5a..6cfdabf 100644 --- a/plugins/module_utils/network/vyos/argspec/lag_interfaces/lag_interfaces.py +++ b/plugins/module_utils/network/vyos/argspec/lag_interfaces/lag_interfaces.py @@ -1,80 +1,88 @@ # 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", }, + "running_config": {"type": "str"}, "state": { - "choices": ["merged", "replaced", "overridden", "deleted"], + "choices": [ + "merged", + "replaced", + "overridden", + "deleted", + "rendered", + "gathered", + "parsed", + ], "default": "merged", "type": "str", }, } # pylint: disable=C0301 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 index 2a9efd9..452670f 100644 --- a/plugins/module_utils/network/vyos/config/lag_interfaces/lag_interfaces.py +++ b/plugins/module_utils/network/vyos/config/lag_interfaces/lag_interfaces.py @@ -1,446 +1,468 @@ # 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_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base import ( ConfigBase, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.facts import ( Facts, ) from ansible_collections.ansible.netcommon.plugins.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): + def get_lag_interfaces_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 + self.gather_subset, self.gather_network_resources, data=data ) 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 + commands = list() + + if self.state in self.ACTION_STATES: + existing_lag_interfaces_facts = self.get_lag_interfaces_facts() + else: + existing_lag_interfaces_facts = [] - result["commands"] = commands + if self.state in self.ACTION_STATES or self.state == "rendered": + commands.extend(self.set_config(existing_lag_interfaces_facts)) - if self._module._diff: - result["diff"] = resp["diff"] if result["changed"] else None + if commands and self.state in self.ACTION_STATES: + if not self._module.check_mode: + self._connection.edit_config(commands) + result["changed"] = True - changed_lag_interfaces_facts = self.get_lag_interfaces_facts() + if self.state in self.ACTION_STATES: + result["commands"] = commands + + if self.state in self.ACTION_STATES or self.state == "gathered": + changed_lag_interfaces_facts = self.get_lag_interfaces_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_lag_interfaces_facts( + data=running_config + ) + else: + changed_lag_interfaces_facts = [] - result["before"] = existing_lag_interfaces_facts - if result["changed"]: - result["after"] = changed_lag_interfaces_facts + if self.state in self.ACTION_STATES: + result["before"] = existing_lag_interfaces_facts + if result["changed"]: + result["after"] = changed_lag_interfaces_facts + elif self.state == "gathered": + result["gathered"] = 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 in ("merged", "replaced", "overridden") and not want: + if ( + self.state in ("merged", "replaced", "overridden", "rendered") + and not want + ): self._module.fail_json( msg="value of config parameter must not be empty for state {0}".format( - state + self.state ) ) - if state == "overridden": + if self.state == "overridden": commands.extend(self._state_overridden(want, have)) - elif state == "deleted": + elif self.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": + if self.state in ("merged", "rendered"): commands.extend(self._state_merged(want_item, obj_in_have)) - elif state == "replaced": + elif self.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=want["name"] + " arp-monitor", attrib="interval", value=str(arp_monitor["interval"]), ) ) if diff_targets: for target in diff_targets: commands.append( self._compute_command( key=want["name"] + " arp-monitor", attrib="target", value=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 {} 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( self._compute_command( key=have["name"] + " arp-monitor", attrib="interval", remove=True, ) ) 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( self._compute_command( key=have["name"] + " arp-monitor", attrib="target", value=target, remove=True, ) ) 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["member"], "bond-group", have["name"], True, "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/lag_interfaces/lag_interfaces.py b/plugins/module_utils/network/vyos/facts/lag_interfaces/lag_interfaces.py index 9201e5c..9056294 100644 --- a/plugins/module_utils/network/vyos/facts/lag_interfaces/lag_interfaces.py +++ b/plugins/module_utils/network/vyos/facts/lag_interfaces/lag_interfaces.py @@ -1,152 +1,144 @@ # # -*- 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_collections.ansible.netcommon.plugins.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 = {} + + group_regex = r".*eth.* '%s'" % lag + g_cfg = findall(group_regex, data, M) + for item in g_cfg: + output = search("^set interfaces ethernet (\\S+)", item, M) + if output: + member["member"] = output.group(1).strip("'") + members.append(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/modules/vyos_lag_interfaces.py b/plugins/modules/vyos_lag_interfaces.py index 84f3d01..a034d40 100644 --- a/plugins/modules/vyos_lag_interfaces.py +++ b/plugins/modules/vyos_lag_interfaces.py @@ -1,567 +1,780 @@ #!/usr/bin/python # -*- coding: utf-8 -*- # Copyright 2019 Red Hat # GNU General Public License v3.0+ # (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) ############################################# # WARNING # ############################################# # # This file is auto generated by the resource # module builder playbook. # # Do not edit this file manually. # # Changes to this file will be over written # by the resource module builder. # # Changes should be made in the model used to # generate this file or in the resource module # builder template. # ############################################# """ The module file for vyos_lag_interfaces """ from __future__ import absolute_import, division, print_function __metaclass__ = type ANSIBLE_METADATA = { "metadata_version": "1.1", "status": ["preview"], "supported_by": "network", } DOCUMENTATION = """module: vyos_lag_interfaces -short_description: Manages attributes of link aggregation groups on VyOS network devices. +short_description: LAG interfaces resource module description: This module manages attributes of link aggregation groups on VyOS network devices. notes: - Tested against VyOS 1.1.8 (helium). - This module works with connection C(network_cli). See L(the VyOS OS Platform Options,../network/user_guide/platform_vyos.html). author: Rohit Thakur (@rohitthakur2590) options: config: description: A list of link aggregation group configurations. type: list suboptions: name: description: - Name of the link aggregation group (LAG) or bond. type: str required: true mode: description: - LAG or bond mode. type: str choices: - 802.3ad - active-backup - broadcast - round-robin - transmit-load-balance - adaptive-load-balance - xor-hash members: description: - List of member interfaces for the LAG (bond). type: list suboptions: member: description: - Name of the member interface. type: str primary: description: - Primary device interfaces for the LAG (bond). type: str hash_policy: description: - LAG or bonding transmit hash policy. type: str choices: - layer2 - layer2+3 - layer3+4 arp_monitor: description: - ARP Link monitoring parameters. type: dict suboptions: interval: description: - ARP link monitoring frequency in milliseconds. type: int target: description: - IP address to use for ARP monitoring. type: list + running_config: + description: + - This option is used only with state I(parsed). + - The value of this option should be the output received from the VyOS device by executing + the command B(show configuration commands | grep bond). + - The state I(parsed) reads the configuration from C(running_config) option and transforms + it into Ansible structured data as per the resource module's argspec and the value is then + returned in the I(parsed) key within the result. + type: str state: description: - The state of the configuration after module completion. type: str choices: - merged - replaced - overridden - deleted + - parsed + - gathered + - rendered default: merged """ EXAMPLES = """ # Using merged # # Before state: # ------------- # # vyos@vyos:~$ show configuration commands | grep bond # set interfaces bonding bond2 # set interfaces bonding bond3 # - name: Merge provided configuration with device configuration - vyos_lag_interfaces: + vyos.vyos.vyos_lag_interfaces: config: - name: bond2 mode: active-backup members: - member: eth2 - member: eth1 hash_policy: layer2 primary: eth2 - name: 'bond3' mode: 'active-backup' hash_policy: 'layer2+3' members: - member: eth3 primary: 'eth3' state: merged # # # ------------------------- # Module Execution Result # ------------------------- # # "before": [ # { # "name": "bond2" # }, # { # "name": "bond3" # } # ], # # "commands": [ # "set interfaces bonding bond2 hash-policy 'layer2'", # "set interfaces bonding bond2 mode 'active-backup'", # "set interfaces ethernet eth2 bond-group bond2", # "set interfaces ethernet eth1 bond-group bond2", # "set interfaces bonding bond2 primary 'eth2'", # "set interfaces bonding bond3 hash-policy 'layer2+3'", # "set interfaces bonding bond3 mode 'active-backup'", # "set interfaces ethernet eth3 bond-group bond3", # "set interfaces bonding bond3 primary 'eth3'" # ] # # "after": [ # { # "hash_policy": "layer2", # "members": [ # { # "member": "eth1" # }, # { # "member": "eth2" # } # ], # "mode": "active-backup", # "name": "bond2", # "primary": "eth2" # }, # { # "hash_policy": "layer2+3", # "members": [ # { # "member": "eth3" # } # ], # "mode": "active-backup", # "name": "bond3", # "primary": "eth3" # } # ] # # After state: # ------------- # # vyos@vyos:~$ show configuration commands | grep bond # set interfaces bonding bond2 hash-policy 'layer2' # set interfaces bonding bond2 mode 'active-backup' # set interfaces bonding bond2 primary 'eth2' # set interfaces bonding bond3 hash-policy 'layer2+3' # set interfaces bonding bond3 mode 'active-backup' # set interfaces bonding bond3 primary 'eth3' # set interfaces ethernet eth1 bond-group 'bond2' # set interfaces ethernet eth2 bond-group 'bond2' # set interfaces ethernet eth3 bond-group 'bond3' # Using replaced # # Before state: # ------------- # # vyos@vyos:~$ show configuration commands | grep bond # set interfaces bonding bond2 hash-policy 'layer2' # set interfaces bonding bond2 mode 'active-backup' # set interfaces bonding bond2 primary 'eth2' # set interfaces bonding bond3 hash-policy 'layer2+3' # set interfaces bonding bond3 mode 'active-backup' # set interfaces bonding bond3 primary 'eth3' # set interfaces ethernet eth1 bond-group 'bond2' # set interfaces ethernet eth2 bond-group 'bond2' # set interfaces ethernet eth3 bond-group 'bond3' # - name: Replace device configurations of listed LAGs with provided configurations - vyos_lag_interfaces: + vyos.vyos.vyos_lag_interfaces: config: - name: bond3 mode: '802.3ad' hash_policy: 'layer2' members: - member: eth3 state: replaced # # # ------------------------- # Module Execution Result # ------------------------- # # "before": [ # { # "hash_policy": "layer2", # "members": [ # { # "member": "eth1" # }, # { # "member": "eth2" # } # ], # "mode": "active-backup", # "name": "bond2", # "primary": "eth2" # }, # { # "hash_policy": "layer2+3", # "members": [ # { # "member": "eth3" # } # ], # "mode": "active-backup", # "name": "bond3", # "primary": "eth3" # } # ], # # "commands": [ # "delete interfaces bonding bond3 primary", # "set interfaces bonding bond3 hash-policy 'layer2'", # "set interfaces bonding bond3 mode '802.3ad'" # ], # # "after": [ # { # "hash_policy": "layer2", # "members": [ # { # "member": "eth1" # }, # { # "member": "eth2" # } # ], # "mode": "active-backup", # "name": "bond2", # "primary": "eth2" # }, # { # "hash_policy": "layer2", # "members": [ # { # "member": "eth3" # } # ], # "mode": "802.3ad", # "name": "bond3" # } # ], # # After state: # ------------- # # vyos@vyos:~$ show configuration commands | grep bond # set interfaces bonding bond2 hash-policy 'layer2' # set interfaces bonding bond2 mode 'active-backup' # set interfaces bonding bond2 primary 'eth2' # set interfaces bonding bond3 hash-policy 'layer2' # set interfaces bonding bond3 mode '802.3ad' # set interfaces ethernet eth1 bond-group 'bond2' # set interfaces ethernet eth2 bond-group 'bond2' # set interfaces ethernet eth3 bond-group 'bond3' # Using overridden # # Before state # -------------- # # vyos@vyos:~$ show configuration commands | grep bond # set interfaces bonding bond2 hash-policy 'layer2' # set interfaces bonding bond2 mode 'active-backup' # set interfaces bonding bond2 primary 'eth2' # set interfaces bonding bond3 hash-policy 'layer2' # set interfaces bonding bond3 mode '802.3ad' # set interfaces ethernet eth1 bond-group 'bond2' # set interfaces ethernet eth2 bond-group 'bond2' # set interfaces ethernet eth3 bond-group 'bond3' # - name: Overrides all device configuration with provided configuration - vyos_lag_interfaces: + vyos.vyos.vyos_lag_interfaces: config: - name: bond3 mode: active-backup members: - member: eth1 - member: eth2 - member: eth3 primary: eth3 hash_policy: layer2 state: overridden # # # ------------------------- # Module Execution Result # ------------------------- # # "before": [ # { # "hash_policy": "layer2", # "members": [ # { # "member": "eth1" # }, # { # "member": "eth2" # } # ], # "mode": "active-backup", # "name": "bond2", # "primary": "eth2" # }, # { # "hash_policy": "layer2", # "members": [ # { # "member": "eth3" # } # ], # "mode": "802.3ad", # "name": "bond3" # } # ], # # "commands": [ # "delete interfaces bonding bond2 hash-policy", # "delete interfaces ethernet eth1 bond-group bond2", # "delete interfaces ethernet eth2 bond-group bond2", # "delete interfaces bonding bond2 mode", # "delete interfaces bonding bond2 primary", # "set interfaces bonding bond3 mode 'active-backup'", # "set interfaces ethernet eth1 bond-group bond3", # "set interfaces ethernet eth2 bond-group bond3", # "set interfaces bonding bond3 primary 'eth3'" # ], # # "after": [ # { # "name": "bond2" # }, # { # "hash_policy": "layer2", # "members": [ # { # "member": "eth1" # }, # { # "member": "eth2" # }, # { # "member": "eth3" # } # ], # "mode": "active-backup", # "name": "bond3", # "primary": "eth3" # } # ], # # # After state # ------------ # # vyos@vyos:~$ show configuration commands | grep bond # set interfaces bonding bond2 # set interfaces bonding bond3 hash-policy 'layer2' # set interfaces bonding bond3 mode 'active-backup' # set interfaces bonding bond3 primary 'eth3' # set interfaces ethernet eth1 bond-group 'bond3' # set interfaces ethernet eth2 bond-group 'bond3' # set interfaces ethernet eth3 bond-group 'bond3' # Using deleted # # Before state # ------------- # # vyos@vyos:~$ show configuration commands | grep bond # set interfaces bonding bond2 hash-policy 'layer2' # set interfaces bonding bond2 mode 'active-backup' # set interfaces bonding bond2 primary 'eth2' # set interfaces bonding bond3 hash-policy 'layer2+3' # set interfaces bonding bond3 mode 'active-backup' # set interfaces bonding bond3 primary 'eth3' # set interfaces ethernet eth1 bond-group 'bond2' # set interfaces ethernet eth2 bond-group 'bond2' # set interfaces ethernet eth3 bond-group 'bond3' # - name: Delete LAG attributes of given interfaces (Note This won't delete the interface itself) - vyos_lag_interfaces: + vyos.vyos.vyos_lag_interfaces: config: - name: bond2 - name: bond3 state: deleted # # # ------------------------ # Module Execution Results # ------------------------ # # "before": [ # { # "hash_policy": "layer2", # "members": [ # { # "member": "eth1" # }, # { # "member": "eth2" # } # ], # "mode": "active-backup", # "name": "bond2", # "primary": "eth2" # }, # { # "hash_policy": "layer2+3", # "members": [ # { # "member": "eth3" # } # ], # "mode": "active-backup", # "name": "bond3", # "primary": "eth3" # } # ], # "commands": [ # "delete interfaces bonding bond2 hash-policy", # "delete interfaces ethernet eth1 bond-group bond2", # "delete interfaces ethernet eth2 bond-group bond2", # "delete interfaces bonding bond2 mode", # "delete interfaces bonding bond2 primary", # "delete interfaces bonding bond3 hash-policy", # "delete interfaces ethernet eth3 bond-group bond3", # "delete interfaces bonding bond3 mode", # "delete interfaces bonding bond3 primary" # ], # # "after": [ # { # "name": "bond2" # }, # { # "name": "bond3" # } # ], # # After state # ------------ # vyos@vyos:~$ show configuration commands | grep bond # set interfaces bonding bond2 # set interfaces bonding bond3 +# Using gathered +# +# Before state: +# ------------- +# +# vyos@192# run show configuration commands | grep bond +# set interfaces bonding bond0 hash-policy 'layer2' +# set interfaces bonding bond0 mode 'active-backup' +# set interfaces bonding bond0 primary 'eth1' +# set interfaces bonding bond1 hash-policy 'layer2+3' +# set interfaces bonding bond1 mode 'active-backup' +# set interfaces bonding bond1 primary 'eth2' +# set interfaces ethernet eth1 bond-group 'bond0' +# set interfaces ethernet eth2 bond-group 'bond1' +# +- name: Gather listed lag interfaces with provided configurations + vyos.vyos.vyos_lag_interfaces: + config: + state: gathered +# +# +# ------------------------- +# Module Execution Result +# ------------------------- +# +# "gathered": [ +# { +# "afi": "ipv6", +# "rule_sets": [ +# { +# "default_action": "accept", +# "description": "This is ipv6 specific rule-set", +# "name": "UPLINK", +# "rules": [ +# { +# "action": "accept", +# "description": "Fwipv6-Rule 1 is configured by Ansible", +# "ipsec": "match-ipsec", +# "number": 1 +# }, +# { +# "action": "accept", +# "description": "Fwipv6-Rule 2 is configured by Ansible", +# "ipsec": "match-ipsec", +# "number": 2 +# } +# ] +# } +# ] +# }, +# { +# "afi": "ipv4", +# "rule_sets": [ +# { +# "default_action": "accept", +# "description": "IPv4 INBOUND rule set", +# "name": "INBOUND", +# "rules": [ +# { +# "action": "accept", +# "description": "Rule 101 is configured by Ansible", +# "ipsec": "match-ipsec", +# "number": 101 +# }, +# { +# "action": "reject", +# "description": "Rule 102 is configured by Ansible", +# "ipsec": "match-ipsec", +# "number": 102 +# }, +# { +# "action": "accept", +# "description": "Rule 103 is configured by Ansible", +# "destination": { +# "group": { +# "address_group": "inbound" +# } +# }, +# "number": 103, +# "source": { +# "address": "192.0.2.0" +# }, +# "state": { +# "established": true, +# "invalid": false, +# "new": false, +# "related": true +# } +# } +# ] +# } +# ] +# } +# ] +# +# +# After state: +# ------------- +# +# vyos@192# run show configuration commands | grep bond +# set interfaces bonding bond0 hash-policy 'layer2' +# set interfaces bonding bond0 mode 'active-backup' +# set interfaces bonding bond0 primary 'eth1' +# set interfaces bonding bond1 hash-policy 'layer2+3' +# set interfaces bonding bond1 mode 'active-backup' +# set interfaces bonding bond1 primary 'eth2' +# set interfaces ethernet eth1 bond-group 'bond0' +# set interfaces ethernet eth2 bond-group 'bond1' + + +# Using rendered +# +# +- name: Render the commands for provided configuration + vyos.vyos.vyos_lag_interfaces: + config: + - name: bond0 + hash_policy: layer2 + members: + - member: eth1 + mode: active-backup + primary: eth1 + - name: bond1 + hash_policy: layer2+3 + members: + - member: eth2 + mode: active-backup + primary: eth2 + state: rendered +# +# +# ------------------------- +# Module Execution Result +# ------------------------- +# +# +# "rendered": [ +# "set interfaces bonding bond0 hash-policy 'layer2'", +# "set interfaces ethernet eth1 bond-group 'bond0'", +# "set interfaces bonding bond0 mode 'active-backup'", +# "set interfaces bonding bond0 primary 'eth1'", +# "set interfaces bonding bond1 hash-policy 'layer2+3'", +# "set interfaces ethernet eth2 bond-group 'bond1'", +# "set interfaces bonding bond1 mode 'active-backup'", +# "set interfaces bonding bond1 primary 'eth2'" +# ] + + +# Using parsed +# +# +- name: Parsed the commands for provided configuration + vyos.vyos.vyos_l3_interfaces: + running_config: + "set interfaces bonding bond0 hash-policy 'layer2' + set interfaces bonding bond0 mode 'active-backup' + set interfaces bonding bond0 primary 'eth1' + set interfaces bonding bond1 hash-policy 'layer2+3' + set interfaces bonding bond1 mode 'active-backup' + set interfaces bonding bond1 primary 'eth2' + set interfaces ethernet eth1 bond-group 'bond0' + set interfaces ethernet eth2 bond-group 'bond1'" + state: parsed +# +# +# ------------------------- +# Module Execution Result +# ------------------------- +# +# +# "parsed": [ +# { +# "hash_policy": "layer2", +# "members": [ +# { +# "member": "eth1" +# } +# ], +# "mode": "active-backup", +# "name": "bond0", +# "primary": "eth1" +# }, +# { +# "hash_policy": "layer2+3", +# "members": [ +# { +# "member": "eth2" +# } +# ], +# "mode": "active-backup", +# "name": "bond1", +# "primary": "eth2" +# } +# ] + + """ RETURN = """ before: description: The configuration as structured data prior to module invocation. returned: always type: list sample: > The configuration returned will always be in the same format of the parameters above. after: description: The configuration as structured data after module completion. returned: when changed type: list sample: > The configuration returned will always be in the same format of the parameters above. commands: description: The set of commands pushed to the remote device. returned: always type: list sample: - 'set interfaces bonding bond2' - 'set interfaces bonding bond2 hash-policy layer2' """ from ansible.module_utils.basic import AnsibleModule from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.argspec.lag_interfaces.lag_interfaces import ( Lag_interfacesArgs, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.config.lag_interfaces.lag_interfaces import ( Lag_interfaces, ) def main(): """ Main entry point for module execution :returns: the result form module invocation """ required_if = [ ("state", "merged", ("config",)), ("state", "replaced", ("config",)), + ("state", "rendered", ("config",)), ("state", "overridden", ("config",)), + ("state", "parsed", ("running_config",)), ] + mutually_exclusive = [("config", "running_config")] + module = AnsibleModule( argument_spec=Lag_interfacesArgs.argument_spec, required_if=required_if, supports_check_mode=True, + mutually_exclusive=mutually_exclusive, ) result = Lag_interfaces(module).execute_module() module.exit_json(**result) if __name__ == "__main__": main() diff --git a/tests/integration/targets/vyos_lag_interfaces/tests/cli/_parsed_config.cfg b/tests/integration/targets/vyos_lag_interfaces/tests/cli/_parsed_config.cfg new file mode 100644 index 0000000..ea3bfce --- /dev/null +++ b/tests/integration/targets/vyos_lag_interfaces/tests/cli/_parsed_config.cfg @@ -0,0 +1,8 @@ +set interfaces bonding bond0 hash-policy 'layer2' +set interfaces bonding bond0 mode 'active-backup' +set interfaces bonding bond0 primary 'eth1' +set interfaces bonding bond1 hash-policy 'layer2+3' +set interfaces bonding bond1 mode 'active-backup' +set interfaces bonding bond1 primary 'eth2' +set interfaces ethernet eth1 bond-group 'bond0' +set interfaces ethernet eth2 bond-group 'bond1' \ No newline at end of file diff --git a/tests/integration/targets/vyos_lag_interfaces/tests/cli/empty_config.yaml b/tests/integration/targets/vyos_lag_interfaces/tests/cli/empty_config.yaml index 3894fb5..6e89eae 100644 --- a/tests/integration/targets/vyos_lag_interfaces/tests/cli/empty_config.yaml +++ b/tests/integration/targets/vyos_lag_interfaces/tests/cli/empty_config.yaml @@ -1,37 +1,60 @@ --- - debug: msg: START vyos_lag_interfaces empty_config integration tests on connection={{ ansible_connection }} - name: Merged with empty config should give appropriate error message register: result ignore_errors: true vyos.vyos.vyos_lag_interfaces: config: state: merged - assert: that: - result.msg == 'value of config parameter must not be empty for state merged' - name: Replaced with empty config should give appropriate error message register: result ignore_errors: true vyos.vyos.vyos_lag_interfaces: config: state: replaced - assert: that: - result.msg == 'value of config parameter must not be empty for state replaced' - name: Overridden with empty config should give appropriate error message register: result ignore_errors: true vyos.vyos.vyos_lag_interfaces: config: state: overridden - assert: that: - result.msg == 'value of config parameter must not be empty for state overridden' + +- name: Parsed with empty running_config should give appropriate error message + register: result + ignore_errors: true + vyos.vyos.vyos_lag_interfaces: + running_config: + state: parsed + +- assert: + that: + - result.msg == 'value of running_config parameter must not be empty for state + parsed' + +- name: Rendered with empty config should give appropriate error message + register: result + ignore_errors: true + vyos.vyos.vyos_lag_interfaces: + config: + state: rendered + +- assert: + that: + - result.msg == 'value of config parameter must not be empty for state rendered' diff --git a/tests/integration/targets/vyos_lag_interfaces/tests/cli/gathered.yaml b/tests/integration/targets/vyos_lag_interfaces/tests/cli/gathered.yaml new file mode 100644 index 0000000..c600ec1 --- /dev/null +++ b/tests/integration/targets/vyos_lag_interfaces/tests/cli/gathered.yaml @@ -0,0 +1,34 @@ +--- +- debug: + msg: START vyos_lag_interfaces gathered integration tests on connection={{ ansible_connection + }} + +- include_tasks: _remove_config.yaml + +- include_tasks: _populate.yaml + +- block: + + - name: Merge the provided configuration with the exisiting running configuration + register: result + vyos.vyos.vyos_lag_interfaces: &id001 + config: + state: gathered + + - name: Assert that gathered dicts was correctly generated + assert: + that: + - "{{ populate | symmetric_difference(result['gathered']) |length == 0\ + \ }}" + + - name: Gather the existing running configuration (IDEMPOTENT) + register: result + vyos.vyos.vyos_lag_interfaces: *id001 + + - name: Assert that the previous task was idempotent + assert: + that: + - result['changed'] == false + always: + + - include_tasks: _remove_config.yaml \ No newline at end of file diff --git a/tests/integration/targets/vyos_lag_interfaces/tests/cli/parsed.yaml b/tests/integration/targets/vyos_lag_interfaces/tests/cli/parsed.yaml new file mode 100644 index 0000000..64e7780 --- /dev/null +++ b/tests/integration/targets/vyos_lag_interfaces/tests/cli/parsed.yaml @@ -0,0 +1,41 @@ +--- +- debug: + msg: START vyos_lag_interfaces parsed integration tests on connection={{ ansible_connection + }} + +- include_tasks: _remove_config.yaml + +- include_tasks: _populate.yaml + +- block: + + - name: Gather lag_interfaces facts + register: lag_interfaces_facts + vyos.vyos.vyos_facts: + gather_subset: + - default + gather_network_resources: + - lag_interfaces + + - name: Provide the running configuration for parsing (config to be parsed) + register: result + vyos.vyos.vyos_lag_interfaces: &id001 + running_config: "{{ lookup('file', '_parsed_config.cfg') }}" + state: parsed + + - name: Assert that correct parsing done + assert: + that: "{{ ansible_facts['network_resources']['lag_interfaces'] | symmetric_difference(result['parsed'])\ + \ |length == 0 }}" + + - name: Gather the existing running configuration (IDEMPOTENT) + register: result + vyos.vyos.vyos_lag_interfaces: *id001 + + - name: Assert that the previous task was idempotent + assert: + that: + - result['changed'] == false + always: + + - include_tasks: _remove_config.yaml \ No newline at end of file diff --git a/tests/integration/targets/vyos_lag_interfaces/tests/cli/rendered.yaml b/tests/integration/targets/vyos_lag_interfaces/tests/cli/rendered.yaml new file mode 100644 index 0000000..9892a3f --- /dev/null +++ b/tests/integration/targets/vyos_lag_interfaces/tests/cli/rendered.yaml @@ -0,0 +1,46 @@ +--- +- debug: + msg: START vyos_lag_interfaces rendered integration tests on connection={{ ansible_connection + }} + +- include_tasks: _remove_config.yaml + +- include_tasks: _populate.yaml + +- block: + + - name: Structure provided configuration into device specific commands + register: result + vyos.vyos.vyos_lag_interfaces: &id001 + config: + - name: bond0 + hash_policy: layer2 + members: + - member: eth1 + mode: active-backup + primary: eth1 + - name: bond1 + hash_policy: layer2+3 + members: + - member: eth2 + mode: active-backup + primary: eth2 + state: rendered + + - name: Assert that correct set of commands were generated + assert: + that: + - "{{ rendered['commands'] | symmetric_difference(result['rendered'])\ + \ |length == 0 }}" + + - name: Structure provided configuration into device specific commands (IDEMPOTENT) + register: result + vyos.vyos.vyos_lag_interfaces: *id001 + + - name: Assert that the previous task was idempotent + assert: + that: + - result['changed'] == false + always: + + - include_tasks: _remove_config.yaml \ No newline at end of file diff --git a/tests/integration/targets/vyos_lag_interfaces/vars/main.yaml b/tests/integration/targets/vyos_lag_interfaces/vars/main.yaml index 5783693..9784fb9 100644 --- a/tests/integration/targets/vyos_lag_interfaces/vars/main.yaml +++ b/tests/integration/targets/vyos_lag_interfaces/vars/main.yaml @@ -1,99 +1,109 @@ --- merged: before: - name: bond0 - name: bond1 commands: - set interfaces bonding bond0 hash-policy 'layer2' - set interfaces bonding bond0 mode 'active-backup' - set interfaces ethernet eth1 bond-group 'bond0' - set interfaces bonding bond0 primary 'eth1' - set interfaces bonding bond1 hash-policy 'layer2+3' - set interfaces bonding bond1 mode 'active-backup' - set interfaces ethernet eth2 bond-group 'bond1' - set interfaces bonding bond1 primary 'eth2' after: - name: bond0 hash_policy: layer2 members: - member: eth1 mode: active-backup primary: eth1 - name: bond1 hash_policy: layer2+3 members: - member: eth2 mode: active-backup primary: eth2 populate: - name: bond0 hash_policy: layer2 members: - member: eth1 mode: active-backup primary: eth1 - name: bond1 hash_policy: layer2+3 members: - member: eth2 mode: active-backup primary: eth2 replaced: commands: - delete interfaces bonding bond1 primary - set interfaces bonding bond1 hash-policy 'layer2' - set interfaces bonding bond1 mode '802.3ad' after: - name: bond0 hash_policy: layer2 members: - member: eth1 mode: active-backup primary: eth1 - name: bond1 hash_policy: layer2 members: - member: eth2 mode: 802.3ad overridden: commands: - delete interfaces bonding bond0 hash-policy - delete interfaces ethernet eth1 bond-group 'bond0' - delete interfaces bonding bond0 mode - delete interfaces bonding bond0 primary - set interfaces bonding bond1 hash-policy 'layer2' after: - name: bond0 - name: bond1 hash_policy: layer2 members: - member: eth2 mode: active-backup primary: eth2 deleted: commands: - delete interfaces bonding bond0 hash-policy - delete interfaces ethernet eth1 bond-group 'bond0' - delete interfaces bonding bond0 mode - delete interfaces bonding bond0 primary - delete interfaces bonding bond1 hash-policy - delete interfaces ethernet eth2 bond-group 'bond1' - delete interfaces bonding bond1 mode - delete interfaces bonding bond1 primary after: - name: bond0 - name: bond1 +rendered: + commands: + - set interfaces bonding bond0 hash-policy 'layer2' + - set interfaces ethernet eth1 bond-group 'bond0' + - set interfaces bonding bond0 mode 'active-backup' + - set interfaces bonding bond0 primary 'eth1' + - set interfaces bonding bond1 hash-policy 'layer2+3' + - set interfaces ethernet eth2 bond-group 'bond1' + - set interfaces bonding bond1 mode 'active-backup' + - set interfaces bonding bond1 primary 'eth2' round_trip: after: - name: bond0 hash_policy: layer2+3 members: - member: eth1 mode: 802.3ad primary: eth1 - name: bond1 hash_policy: layer2 members: - member: eth2 mode: xor-hash primary: eth2