diff --git a/plugins/module_utils/network/vyos/argspec/ospfv2/ospfv2.py b/plugins/module_utils/network/vyos/argspec/ospfv2/ospfv2.py index 275aaf3..b4cdadf 100644 --- a/plugins/module_utils/network/vyos/argspec/ospfv2/ospfv2.py +++ b/plugins/module_utils/network/vyos/argspec/ospfv2/ospfv2.py @@ -1,269 +1,273 @@ # # -*- 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) +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + ############################################# # WARNING # ############################################# # # This file is auto generated by the resource # module builder playbook. # # Do not edit this file manually. # # Changes to this file will be over written # by the resource module builder. # # Changes should be made in the model used to # generate this file or in the resource module # builder template. # ############################################# """ The arg spec for the vyos_ospfv2 module """ class Ospfv2Args(object): # pylint: disable=R0903 """The arg spec for the vyos_ospfv2 module """ def __init__(self, **kwargs): pass argument_spec = { "config": { "options": { "auto_cost": { "options": {"reference_bandwidth": {"type": "int"}}, "type": "dict", }, "default_information": { "options": { "originate": { "options": { "always": {"type": "bool"}, "metric": {"type": "int"}, "metric_type": {"type": "int"}, "route_map": {"type": "str"}, }, "type": "dict", } }, "type": "dict", }, "default_metric": {"type": "int"}, "distance": { "options": { "global": {"type": "int"}, "ospf": { "options": { "external": {"type": "int"}, "inter_area": {"type": "int"}, "intra_area": {"type": "int"}, }, "type": "dict", }, }, "type": "dict", }, "log_adjacency_changes": { "choices": ["detail"], "type": "str", }, "max_metric": { "options": { "router_lsa": { "options": { "administrative": {"type": "bool"}, "on_shutdown": {"type": "int"}, "on_startup": {"type": "int"}, }, "type": "dict", } }, "type": "dict", }, "mpls_te": { "options": { "enabled": {"type": "bool"}, "router_address": {"type": "str"}, }, "type": "dict", }, "neighbor": { "elements": "dict", "options": { "neighbor_id": {"type": "str"}, "poll_interval": {"type": "int"}, "priority": {"type": "int"}, }, "type": "list", }, "areas": { "elements": "dict", "options": { "area_id": {"type": "str"}, "area_type": { "options": { "normal": {"type": "bool"}, "nssa": { "options": { "default_cost": {"type": "int"}, "no_summary": {"type": "bool"}, "set": {"type": "bool"}, "translate": { "choices": [ "always", "candidate", "never", ], "type": "str", }, }, "type": "dict", }, "stub": { "options": { "default_cost": {"type": "int"}, "no_summary": {"type": "bool"}, "set": {"type": "bool"}, }, "type": "dict", }, }, "type": "dict", }, "authentication": { "choices": ["plaintext-password", "md5"], "type": "str", }, "network": { "elements": "dict", "options": { "address": {"required": True, "type": "str"} }, "type": "list", }, "range": { "elements": "dict", "options": { "address": {"type": "str"}, "cost": {"type": "int"}, "not_advertise": {"type": "bool"}, "substitute": {"type": "str"}, }, "type": "list", }, "shortcut": { "choices": ["default", "disable", "enable"], "type": "str", }, "virtual_link": { "elements": "dict", "options": { "address": {"type": "str"}, "authentication": { "options": { "md5": { "elements": "dict", "options": { "key_id": {"type": "int"}, "md5_key": {"type": "str"}, }, "type": "list", }, "plaintext_password": {"type": "str"}, }, "type": "dict", }, "dead_interval": {"type": "int"}, "hello_interval": {"type": "int"}, "retransmit_interval": {"type": "int"}, "transmit_delay": {"type": "int"}, }, "type": "list", }, }, "type": "list", }, "parameters": { "options": { "abr_type": { "choices": [ "cisco", "ibm", "shortcut", "standard", ], "type": "str", }, "opaque_lsa": {"type": "bool"}, "rfc1583_compatibility": {"type": "bool"}, "router_id": {"type": "str"}, }, "type": "dict", }, "passive_interface": {"type": "list"}, "passive_interface_exclude": {"type": "list"}, "redistribute": { "elements": "dict", "options": { "metric": {"type": "int"}, "metric_type": {"type": "int"}, "route_map": {"type": "str"}, "route_type": { "choices": [ "bgp", "connected", "kernel", "rip", "static", ], "type": "str", }, }, "type": "list", }, "route_map": {"type": "list"}, "timers": { "options": { "refresh": { "options": {"timers": {"type": "int"}}, "type": "dict", }, "throttle": { "options": { "spf": { "options": { "delay": {"type": "int"}, "initial_holdtime": {"type": "int"}, "max_holdtime": {"type": "int"}, }, "type": "dict", } }, "type": "dict", }, }, "type": "dict", }, }, "type": "dict", }, "running_config": {"type": "str"}, "state": { "choices": [ "merged", "replaced", "deleted", "parsed", "gathered", "rendered", ], "default": "merged", "type": "str", }, } # pylint: disable=C0301 diff --git a/plugins/module_utils/network/vyos/argspec/ospfv3/ospfv3.py b/plugins/module_utils/network/vyos/argspec/ospfv3/ospfv3.py index 66aaa8c..25f979c 100644 --- a/plugins/module_utils/network/vyos/argspec/ospfv3/ospfv3.py +++ b/plugins/module_utils/network/vyos/argspec/ospfv3/ospfv3.py @@ -1,94 +1,98 @@ # # -*- 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) +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + ############################################# # 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_ospfv3 module """ class Ospfv3Args(object): # pylint: disable=R0903 """The arg spec for the vyos_ospfv3 module """ def __init__(self, **kwargs): pass argument_spec = { "config": { "options": { "areas": { "elements": "dict", "options": { "area_id": {"type": "str"}, "export_list": {"type": "str"}, "import_list": {"type": "str"}, "range": { "elements": "dict", "options": { "address": {"type": "str"}, "advertise": {"type": "bool"}, "not_advertise": {"type": "bool"}, }, "type": "list", }, }, "type": "list", }, "parameters": { "options": {"router_id": {"type": "str"}}, "type": "dict", }, "redistribute": { "elements": "dict", "options": { "route_map": {"type": "str"}, "route_type": { "choices": [ "bgp", "connected", "kernel", "ripng", "static", ], "type": "str", }, }, "type": "list", }, }, "type": "dict", }, "running_config": {"type": "str"}, "state": { "choices": [ "merged", "replaced", "deleted", "parsed", "gathered", "rendered", ], "default": "merged", "type": "str", }, } # pylint: disable=C0301 diff --git a/plugins/module_utils/network/vyos/facts/l3_interfaces/l3_interfaces.py b/plugins/module_utils/network/vyos/facts/l3_interfaces/l3_interfaces.py index 3b99d34..eac8467 100644 --- a/plugins/module_utils/network/vyos/facts/l3_interfaces/l3_interfaces.py +++ b/plugins/module_utils/network/vyos/facts/l3_interfaces/l3_interfaces.py @@ -1,148 +1,143 @@ # # -*- 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 l3_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, - unicode_literals, -) +from __future__ import absolute_import, division, print_function __metaclass__ = type import re from copy import deepcopy from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import ( utils, ) from ansible.module_utils.six import iteritems -from ansible_collections.ansible.netcommon.plugins.module_utils.compat import ( - ipaddress, +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.utils import ( + get_ip_address_version, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.argspec.l3_interfaces.l3_interfaces import ( L3_interfacesArgs, ) class L3_interfacesFacts(object): """ The vyos l3_interfaces fact class """ def __init__(self, module, subspec="config", options="options"): self._module = module self.argument_spec = L3_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 l3_interfaces :param connection: the device connection :param ansible_facts: Facts dictionary :param data: previously collected conf :rtype: dictionary :returns: facts """ if not data: data = connection.get_config() # operate on a collection of resource x objs = [] interface_names = re.findall( r"set interfaces (?:ethernet|bonding|vti|vxlan) (?:\'*)(\S+)(?:\'*)", data, re.M, ) if interface_names: for interface in set(interface_names): intf_regex = r" %s .+$" % interface cfg = re.findall(intf_regex, data, re.M) obj = self.render_config(cfg) obj["name"] = interface.strip("'") if obj: objs.append(obj) ansible_facts["ansible_network_resources"].pop("l3_interfaces", None) facts = {} if objs: facts["l3_interfaces"] = [] params = utils.validate_config( self.argument_spec, {"config": objs} ) for cfg in params["config"]: facts["l3_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 """ vif_conf = "\n".join(filter(lambda x: ("vif" in x), conf)) eth_conf = "\n".join(filter(lambda x: ("vif" not in x), conf)) config = self.parse_attribs(eth_conf) config["vifs"] = self.parse_vifs(vif_conf) return utils.remove_empties(config) def parse_vifs(self, conf): vif_names = re.findall(r"vif (\d+)", conf, re.M) vifs_list = None if vif_names: vifs_list = [] for vif in set(vif_names): vif_regex = r" %s .+$" % vif cfg = "\n".join(re.findall(vif_regex, conf, re.M)) obj = self.parse_attribs(cfg) obj["vlan_id"] = vif if obj: vifs_list.append(obj) return vifs_list def parse_attribs(self, conf): config = {} ipaddrs = re.findall(r"address (\S+)", conf, re.M) config["ipv4"] = [] config["ipv6"] = [] for item in ipaddrs: item = item.strip("'") if item == "dhcp": config["ipv4"].append({"address": item}) elif item == "dhcpv6": config["ipv6"].append({"address": item}) else: - ip_version = ipaddress.ip_address(item.split("/")[0]).version + ip_version = get_ip_address_version(item.split("/")[0]) if ip_version == 4: config["ipv4"].append({"address": item}) else: config["ipv6"].append({"address": item}) for key, value in iteritems(config): if value == []: config[key] = None return utils.remove_empties(config) diff --git a/plugins/modules/vyos_command.py b/plugins/modules/vyos_command.py index 58e98c9..2871683 100644 --- a/plugins/modules/vyos_command.py +++ b/plugins/modules/vyos_command.py @@ -1,219 +1,219 @@ #!/usr/bin/python # # This file is part of Ansible # # Ansible is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # Ansible is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with Ansible. If not, see . # DOCUMENTATION = """ module: vyos_command author: Nathaniel Case (@Qalthos) short_description: Run one or more commands on VyOS devices description: - The command module allows running one or more commands on remote devices running VyOS. This module can also be introspected to validate key parameters before returning successfully. If the conditional statements are not met in the wait period, the task fails. - Certain C(show) commands in VyOS produce many lines of output and use a custom pager that can cause this module to hang. If the value of the environment variable C(ANSIBLE_VYOS_TERMINAL_LENGTH) is not set, the default number of 10000 is used. version_added: 1.0.0 extends_documentation_fragment: - vyos.vyos.vyos options: commands: description: - The ordered set of commands to execute on the remote device running VyOS. The output from the command execution is returned to the playbook. If the I(wait_for) argument is provided, the module is not returned until the condition is satisfied or the number of retries has been exceeded. required: true wait_for: description: - Specifies what to evaluate from the output of the command and what conditionals to apply. This argument will cause the task to wait for a particular conditional to be true before moving forward. If the conditional is not true by the configured I(retries), the task fails. See examples. aliases: - waitfor match: description: - The I(match) argument is used in conjunction with the I(wait_for) argument to specify the match policy. Valid values are C(all) or C(any). If the value is set to C(all) then all conditionals in the wait_for must be satisfied. If the value is set to C(any) then only one of the values must be satisfied. default: all choices: - any - all retries: description: - Specifies the number of retries a command should be tried before it is considered failed. The command is run on the target device every retry and evaluated against the I(wait_for) conditionals. default: 10 interval: description: - Configures the interval in seconds to wait between I(retries) of the command. If the command does not pass the specified conditions, the interval indicates how long to wait before trying the command again. default: 1 notes: - Tested against VyOS 1.1.8 (helium). - Running C(show system boot-messages all) will cause the module to hang since VyOS is using a custom pager setting to display the output of that command. - If a command sent to the device requires answering a prompt, it is possible to pass a dict containing I(command), I(answer) and I(prompt). See examples. - This module works with connection C(network_cli). See L(the VyOS OS Platform Options,../network/user_guide/platform_vyos.html). """ EXAMPLES = """ - name: show configuration on ethernet devices eth0 and eth1 vyos.vyos.vyos_command: commands: - show interfaces ethernet {{ item }} with_items: - eth0 - eth1 - name: run multiple commands and check if version output contains specific version string vyos.vyos.vyos_command: commands: - show version - show hardware cpu wait_for: - result[0] contains 'VyOS 1.1.7' - name: run command that requires answering a prompt vyos.vyos.vyos_command: commands: - command: rollback 1 prompt: Proceed with reboot? [confirm][y] answer: y """ RETURN = """ stdout: description: The set of responses from the commands returned: always apart from low level errors (such as action plugin) type: list sample: ['...', '...'] stdout_lines: description: The value of stdout split into a list returned: always type: list sample: [['...', '...'], ['...'], ['...']] failed_conditions: description: The list of conditionals that have failed returned: failed type: list sample: ['...', '...'] warnings: description: The list of warnings (if any) generated by module based on arguments returned: always type: list sample: ['...', '...'] """ import time from ansible.module_utils._text import to_text from ansible.module_utils.basic import AnsibleModule from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.parsing import ( Conditional, ) from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ( transform_commands, to_lines, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.vyos import ( run_commands, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.vyos import ( vyos_argument_spec, ) def parse_commands(module, warnings): commands = transform_commands(module) if module.check_mode: for item in list(commands): if not item["command"].startswith("show"): warnings.append( "Only show commands are supported when using check mode, not " "executing %s" % item["command"] ) commands.remove(item) return commands def main(): spec = dict( commands=dict(type="list", required=True), wait_for=dict(type="list", aliases=["waitfor"]), match=dict(default="all", choices=["all", "any"]), retries=dict(default=10, type="int"), interval=dict(default=1, type="int"), ) spec.update(vyos_argument_spec) module = AnsibleModule(argument_spec=spec, supports_check_mode=True) warnings = list() result = {"changed": False, "warnings": warnings} commands = parse_commands(module, warnings) wait_for = module.params["wait_for"] or list() try: conditionals = [Conditional(c) for c in wait_for] except AttributeError as exc: module.fail_json(msg=to_text(exc)) retries = module.params["retries"] interval = module.params["interval"] match = module.params["match"] for _ in range(retries): responses = run_commands(module, commands) for item in list(conditionals): if item(responses): if match == "any": conditionals = list() break conditionals.remove(item) if not conditionals: break time.sleep(interval) if conditionals: failed_conditions = [item.raw for item in conditionals] msg = "One or more conditional statements have not been satisfied" module.fail_json(msg=msg, failed_conditions=failed_conditions) result.update( - {"stdout": responses, "stdout_lines": list(to_lines(responses)),} + {"stdout": responses, "stdout_lines": list(to_lines(responses))} ) module.exit_json(**result) if __name__ == "__main__": main() diff --git a/plugins/modules/vyos_interface.py b/plugins/modules/vyos_interface.py index 11a1d49..fe4fce3 100644 --- a/plugins/modules/vyos_interface.py +++ b/plugins/modules/vyos_interface.py @@ -1,468 +1,471 @@ #!/usr/bin/python # -*- coding: utf-8 -*- # (c) 2017, Ansible by Red Hat, inc # # This file is part of Ansible by Red Hat # # Ansible is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # Ansible is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with Ansible. If not, see . # +from __future__ import absolute_import, division, print_function + +__metaclass__ = type DOCUMENTATION = """ module: vyos_interface author: Ganesh Nalawade (@ganeshrn) short_description: (deprecated) Manage Interface on VyOS network devices description: - This module provides declarative management of Interfaces on VyOS network devices. version_added: 1.0.0 deprecated: removed_in: '2.13' alternative: vyos_interfaces why: Updated modules released with more functionality. notes: - Tested against VYOS 1.1.7 options: name: description: - Name of the Interface. required: true description: description: - Description of Interface. enabled: description: - Interface link status. type: bool speed: description: - Interface link speed. mtu: description: - Maximum size of transmit packet. duplex: description: - Interface link status. default: auto choices: - full - half - auto delay: description: - Time in seconds to wait before checking for the operational state on remote device. This wait is applicable for operational state argument which are I(state) with values C(up)/C(down) and I(neighbors). default: 10 neighbors: description: - Check the operational state of given interface C(name) for LLDP neighbor. - The following suboptions are available. suboptions: host: description: - LLDP neighbor host for given interface C(name). port: description: - LLDP neighbor port to which given interface C(name) is connected. aggregate: description: List of Interfaces definitions. state: description: - State of the Interface configuration, C(up) means present and operationally up and C(down) means present and operationally C(down) default: present choices: - present - absent - up - down extends_documentation_fragment: - vyos.vyos.vyos """ EXAMPLES = """ - name: configure interface vyos.vyos.vyos_interface: name: eth0 description: test-interface - name: remove interface vyos.vyos.vyos_interface: name: eth0 state: absent - name: make interface down vyos.vyos.vyos_interface: name: eth0 enabled: false - name: make interface up vyos.vyos.vyos_interface: name: eth0 enabled: true - name: Configure interface speed, mtu, duplex vyos.vyos.vyos_interface: name: eth5 state: present speed: 100 mtu: 256 duplex: full - name: Set interface using aggregate vyos.vyos.vyos_interface: aggregate: - {name: eth1, description: test-interface-1, speed: 100, duplex: half, mtu: 512} - {name: eth2, description: test-interface-2, speed: 1000, duplex: full, mtu: 256} - name: Disable interface on aggregate net_interface: aggregate: - name: eth1 - name: eth2 enabled: false - name: Delete interface using aggregate net_interface: aggregate: - name: eth1 - name: eth2 state: absent - name: Check lldp neighbors intent arguments vyos.vyos.vyos_interface: name: eth0 neighbors: - port: eth0 host: netdev - name: Config + intent vyos.vyos.vyos_interface: name: eth1 enabled: false state: down """ RETURN = """ commands: description: The list of configuration mode commands to send to the device returned: always, except for the platforms that use Netconf transport to manage the device. type: list sample: - set interfaces ethernet eth0 description "test-interface" - set interfaces ethernet eth0 speed 100 - set interfaces ethernet eth0 mtu 256 - set interfaces ethernet eth0 duplex full """ import re from copy import deepcopy from time import sleep from ansible.module_utils._text import to_text from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.connection import exec_command from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ( conditional, remove_default_spec, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.vyos import ( load_config, get_config, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.vyos import ( vyos_argument_spec, ) def search_obj_in_list(name, lst): for o in lst: if o["name"] == name: return o return None def map_obj_to_commands(updates): commands = list() want, have = updates params = ("speed", "description", "duplex", "mtu") for w in want: name = w["name"] disable = w["disable"] state = w["state"] obj_in_have = search_obj_in_list(name, have) set_interface = "set interfaces ethernet " + name delete_interface = "delete interfaces ethernet " + name if state == "absent" and obj_in_have: commands.append(delete_interface) elif state in ("present", "up", "down"): if obj_in_have: for item in params: value = w.get(item) if value and value != obj_in_have.get(item): if item == "description": value = "'" + str(value) + "'" commands.append( set_interface + " " + item + " " + str(value) ) if disable and not obj_in_have.get("disable", False): commands.append(set_interface + " disable") elif not disable and obj_in_have.get("disable", False): commands.append(delete_interface + " disable") else: commands.append(set_interface) for item in params: value = w.get(item) if value: if item == "description": value = "'" + str(value) + "'" commands.append( set_interface + " " + item + " " + str(value) ) if disable: commands.append(set_interface + " disable") return commands def map_config_to_obj(module): data = get_config(module, flags=["| grep interface"]) obj = [] for line in data.split("\n"): if line.startswith("set interfaces ethernet"): match = re.search(r"set interfaces ethernet (\S+)", line, re.M) name = match.group(1) if name: interface = {} for item in obj: if item["name"] == name: interface = item break if not interface: interface = {"name": name} obj.append(interface) match = re.search(r"%s (\S+)" % name, line, re.M) if match: param = match.group(1) if param == "description": match = re.search(r"description (.+)", line, re.M) description = match.group(1).strip("'") interface["description"] = description elif param == "speed": match = re.search(r"speed (\S+)", line, re.M) speed = match.group(1).strip("'") interface["speed"] = speed elif param == "mtu": match = re.search(r"mtu (\S+)", line, re.M) mtu = match.group(1).strip("'") interface["mtu"] = int(mtu) elif param == "duplex": match = re.search(r"duplex (\S+)", line, re.M) duplex = match.group(1).strip("'") interface["duplex"] = duplex elif param.strip("'") == "disable": interface["disable"] = True return obj def map_params_to_obj(module): obj = [] aggregate = module.params.get("aggregate") if aggregate: for item in aggregate: for key in item: if item.get(key) is None: item[key] = module.params[key] d = item.copy() if d["enabled"]: d["disable"] = False else: d["disable"] = True obj.append(d) else: params = { "name": module.params["name"], "description": module.params["description"], "speed": module.params["speed"], "mtu": module.params["mtu"], "duplex": module.params["duplex"], "delay": module.params["delay"], "state": module.params["state"], "neighbors": module.params["neighbors"], } if module.params["enabled"]: params.update({"disable": False}) else: params.update({"disable": True}) obj.append(params) return obj def check_declarative_intent_params(module, want, result): failed_conditions = [] have_neighbors = None for w in want: want_state = w.get("state") want_neighbors = w.get("neighbors") if want_state not in ("up", "down") and not want_neighbors: continue if result["changed"]: sleep(w["delay"]) command = "show interfaces ethernet %s" % w["name"] rc, out, err = exec_command(module, command) if rc != 0: module.fail_json( msg=to_text(err, errors="surrogate_then_replace"), command=command, rc=rc, ) if want_state in ("up", "down"): match = re.search(r"%s (\w+)" % "state", out, re.M) have_state = None if match: have_state = match.group(1) if have_state is None or not conditional( want_state, have_state.strip().lower() ): failed_conditions.append("state " + "eq(%s)" % want_state) if want_neighbors: have_host = [] have_port = [] if have_neighbors is None: rc, have_neighbors, err = exec_command( module, "show lldp neighbors detail" ) if rc != 0: module.fail_json( msg=to_text(err, errors="surrogate_then_replace"), command=command, rc=rc, ) if have_neighbors: lines = have_neighbors.strip().split("Interface: ") for line in lines: field = line.split("\n") if field[0].split(",")[0].strip() == w["name"]: for item in field: if item.strip().startswith("SysName:"): have_host.append(item.split(":")[1].strip()) if item.strip().startswith("PortDescr:"): have_port.append(item.split(":")[1].strip()) for item in want_neighbors: host = item.get("host") port = item.get("port") if host and host not in have_host: failed_conditions.append("host " + host) if port and port not in have_port: failed_conditions.append("port " + port) return failed_conditions def main(): """ main entry point for module execution """ neighbors_spec = dict(host=dict(), port=dict()) element_spec = dict( name=dict(), description=dict(), speed=dict(), mtu=dict(type="int"), duplex=dict(choices=["full", "half", "auto"]), enabled=dict(default=True, type="bool"), neighbors=dict(type="list", elements="dict", options=neighbors_spec), delay=dict(default=10, type="int"), state=dict( default="present", choices=["present", "absent", "up", "down"] ), ) aggregate_spec = deepcopy(element_spec) aggregate_spec["name"] = dict(required=True) # remove default in aggregate spec, to handle common arguments remove_default_spec(aggregate_spec) argument_spec = dict( aggregate=dict(type="list", elements="dict", options=aggregate_spec), ) argument_spec.update(element_spec) argument_spec.update(vyos_argument_spec) required_one_of = [["name", "aggregate"]] mutually_exclusive = [["name", "aggregate"]] required_together = [["speed", "duplex"]] module = AnsibleModule( argument_spec=argument_spec, required_one_of=required_one_of, mutually_exclusive=mutually_exclusive, required_together=required_together, supports_check_mode=True, ) warnings = list() result = {"changed": False} if warnings: result["warnings"] = warnings want = map_params_to_obj(module) have = map_config_to_obj(module) commands = map_obj_to_commands((want, have)) result["commands"] = commands if commands: commit = not module.check_mode diff = load_config(module, commands, commit=commit) if diff: if module._diff: result["diff"] = {"prepared": diff} result["changed"] = True failed_conditions = check_declarative_intent_params(module, want, result) if failed_conditions: msg = "One or more conditional statements have not been satisfied" module.fail_json(msg=msg, failed_conditions=failed_conditions) module.exit_json(**result) if __name__ == "__main__": main() diff --git a/plugins/modules/vyos_l3_interface.py b/plugins/modules/vyos_l3_interface.py index 3e43f7c..2feb824 100644 --- a/plugins/modules/vyos_l3_interface.py +++ b/plugins/modules/vyos_l3_interface.py @@ -1,326 +1,329 @@ #!/usr/bin/python # -*- coding: utf-8 -*- # (c) 2017, Ansible by Red Hat, inc # # This file is part of Ansible by Red Hat # # Ansible is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # Ansible is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with Ansible. If not, see . # +from __future__ import absolute_import, division, print_function + +__metaclass__ = type DOCUMENTATION = """ module: vyos_l3_interface author: Ricardo Carrillo Cruz (@rcarrillocruz) short_description: (deprecated) Manage L3 interfaces on VyOS network devices description: - This module provides declarative management of L3 interfaces on VyOS network devices. version_added: 1.0.0 deprecated: removed_in: '2.13' alternative: vyos_l3_interfaces why: Updated modules released with more functionality. notes: - Tested against VYOS 1.1.7 options: name: description: - Name of the L3 interface. ipv4: description: - IPv4 of the L3 interface. ipv6: description: - IPv6 of the L3 interface. aggregate: description: List of L3 interfaces definitions state: description: - State of the L3 interface configuration. default: present choices: - present - absent extends_documentation_fragment: - vyos.vyos.vyos """ EXAMPLES = """ - name: Set eth0 IPv4 address vyos.vyos.vyos_l3_interface: name: eth0 ipv4: 192.168.0.1/24 - name: Remove eth0 IPv4 address vyos.vyos.vyos_l3_interface: name: eth0 state: absent - name: Set IP addresses on aggregate vyos.vyos.vyos_l3_interface: aggregate: - {name: eth1, ipv4: 192.168.2.10/24} - {name: eth2, ipv4: 192.168.3.10/24, ipv6: fd5d:12c9:2201:1::1/64} - name: Remove IP addresses on aggregate vyos.vyos.vyos_l3_interface: aggregate: - {name: eth1, ipv4: 192.168.2.10/24} - {name: eth2, ipv4: 192.168.3.10/24, ipv6: fd5d:12c9:2201:1::1/64} state: absent """ RETURN = """ commands: description: The list of configuration mode commands to send to the device returned: always, except for the platforms that use Netconf transport to manage the device. type: list sample: - set interfaces ethernet eth0 address '192.168.0.1/24' """ import socket import re from copy import deepcopy from ansible.module_utils.basic import AnsibleModule from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ( is_masklen, validate_ip_address, ) from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ( remove_default_spec, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.vyos import ( load_config, run_commands, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.vyos import ( vyos_argument_spec, ) def is_ipv4(value): if value: address = value.split("/") if is_masklen(address[1]) and validate_ip_address(address[0]): return True return False def is_ipv6(value): if value: address = value.split("/") if 0 <= int(address[1]) <= 128: try: socket.inet_pton(socket.AF_INET6, address[0]) except socket.error: return False return True return False def search_obj_in_list(name, lst): for o in lst: if o["name"] == name: return o return None def map_obj_to_commands(updates, module): commands = list() want, have = updates for w in want: name = w["name"] ipv4 = w["ipv4"] ipv6 = w["ipv6"] state = w["state"] obj_in_have = search_obj_in_list(name, have) if state == "absent" and obj_in_have: if ( not ipv4 and not ipv6 and (obj_in_have["ipv4"] or obj_in_have["ipv6"]) ): if name == "lo": commands.append("delete interfaces loopback lo address") else: commands.append( "delete interfaces ethernet " + name + " address" ) else: if ipv4 and ipv4 in obj_in_have["ipv4"]: if name == "lo": commands.append( "delete interfaces loopback lo address " + ipv4 ) else: commands.append( "delete interfaces ethernet " + name + " address " + ipv4 ) if ipv6 and ipv6 in obj_in_have["ipv6"]: if name == "lo": commands.append( "delete interfaces loopback lo address " + ipv6 ) else: commands.append( "delete interfaces ethernet " + name + " address " + ipv6 ) elif state == "present" and obj_in_have: if ipv4 and ipv4 not in obj_in_have["ipv4"]: if name == "lo": commands.append( "set interfaces loopback lo address " + ipv4 ) else: commands.append( "set interfaces ethernet " + name + " address " + ipv4 ) if ipv6 and ipv6 not in obj_in_have["ipv6"]: if name == "lo": commands.append( "set interfaces loopback lo address " + ipv6 ) else: commands.append( "set interfaces ethernet " + name + " address " + ipv6 ) return commands def map_config_to_obj(module): obj = [] output = run_commands(module, ["show interfaces"]) lines = re.split(r"\n[e|l]", output[0])[1:] if len(lines) > 0: for line in lines: splitted_line = line.split() if len(splitted_line) > 0: ipv4 = [] ipv6 = [] if splitted_line[0].lower().startswith("th"): name = "e" + splitted_line[0].lower() elif splitted_line[0].lower().startswith("o"): name = "l" + splitted_line[0].lower() for i in splitted_line[1:]: if ("." in i or ":" in i) and "/" in i: value = i.split(r"\n")[0] if is_ipv4(value): ipv4.append(value) elif is_ipv6(value): ipv6.append(value) obj.append({"name": name, "ipv4": ipv4, "ipv6": ipv6}) return obj def map_params_to_obj(module): obj = [] aggregate = module.params.get("aggregate") if aggregate: for item in aggregate: for key in item: if item.get(key) is None: item[key] = module.params[key] obj.append(item.copy()) else: obj.append( { "name": module.params["name"], "ipv4": module.params["ipv4"], "ipv6": module.params["ipv6"], "state": module.params["state"], } ) return obj def main(): """ main entry point for module execution """ element_spec = dict( name=dict(), ipv4=dict(), ipv6=dict(), state=dict(default="present", choices=["present", "absent"]), ) aggregate_spec = deepcopy(element_spec) aggregate_spec["name"] = dict(required=True) # remove default in aggregate spec, to handle common arguments remove_default_spec(aggregate_spec) argument_spec = dict( aggregate=dict(type="list", elements="dict", options=aggregate_spec), ) argument_spec.update(element_spec) argument_spec.update(vyos_argument_spec) required_one_of = [["name", "aggregate"]] mutually_exclusive = [["name", "aggregate"]] module = AnsibleModule( argument_spec=argument_spec, required_one_of=required_one_of, mutually_exclusive=mutually_exclusive, supports_check_mode=True, ) warnings = list() result = {"changed": False} if warnings: result["warnings"] = warnings want = map_params_to_obj(module) have = map_config_to_obj(module) commands = map_obj_to_commands((want, have), module) result["commands"] = commands if commands: commit = not module.check_mode load_config(module, commands, commit=commit) result["changed"] = True module.exit_json(**result) if __name__ == "__main__": main() diff --git a/plugins/modules/vyos_linkagg.py b/plugins/modules/vyos_linkagg.py index 0d939b2..a68197b 100644 --- a/plugins/modules/vyos_linkagg.py +++ b/plugins/modules/vyos_linkagg.py @@ -1,324 +1,327 @@ #!/usr/bin/python # -*- coding: utf-8 -*- # (c) 2017, Ansible by Red Hat, inc # # This file is part of Ansible by Red Hat # # Ansible is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # Ansible is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with Ansible. If not, see . # +from __future__ import absolute_import, division, print_function + +__metaclass__ = type DOCUMENTATION = """ module: vyos_linkagg author: Ricardo Carrillo Cruz (@rcarrillocruz) short_description: (deprecated) Manage link aggregation groups on VyOS network devices description: - This module provides declarative management of link aggregation groups on VyOS network devices. version_added: 1.0.0 deprecated: removed_in: '2.13' alternative: vyos_lag_interfaces why: Updated modules released with more functionality. notes: - Tested against VYOS 1.1.7 options: name: description: - Name of the link aggregation group. required: true type: str mode: description: - Mode of the link aggregation group. choices: - 802.3ad - active-backup - broadcast - round-robin - transmit-load-balance - adaptive-load-balance - xor-hash - on type: str members: description: - List of members of the link aggregation group. type: list aggregate: description: List of link aggregation definitions. type: list state: description: - State of the link aggregation group. default: present choices: - present - absent - up - down type: str extends_documentation_fragment: - vyos.vyos.vyos """ EXAMPLES = """ - name: configure link aggregation group vyos.vyos.vyos_linkagg: name: bond0 members: - eth0 - eth1 - name: remove configuration vyos.vyos.vyos_linkagg: name: bond0 state: absent - name: Create aggregate of linkagg definitions vyos.vyos.vyos_linkagg: aggregate: - {name: bond0, members: [eth1]} - {name: bond1, members: [eth2]} - name: Remove aggregate of linkagg definitions vyos.vyos.vyos_linkagg: aggregate: - name: bond0 - name: bond1 state: absent """ RETURN = """ commands: description: The list of configuration mode commands to send to the device returned: always, except for the platforms that use Netconf transport to manage the device. type: list sample: - set interfaces bonding bond0 - set interfaces ethernet eth0 bond-group 'bond0' - set interfaces ethernet eth1 bond-group 'bond0' """ from copy import deepcopy from ansible.module_utils.basic import AnsibleModule from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ( remove_default_spec, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.vyos import ( load_config, run_commands, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.vyos import ( vyos_argument_spec, ) def search_obj_in_list(name, lst): for o in lst: if o["name"] == name: return o return None def map_obj_to_commands(updates, module): commands = list() want, have = updates for w in want: name = w["name"] members = w.get("members") or [] mode = w["mode"] if mode == "on": mode = "802.3ad" state = w["state"] obj_in_have = search_obj_in_list(name, have) if state == "absent": if obj_in_have: for m in obj_in_have["members"]: commands.append( "delete interfaces ethernet " + m + " bond-group" ) commands.append("delete interfaces bonding " + name) else: if not obj_in_have: commands.append( "set interfaces bonding " + name + " mode " + mode ) for m in members: commands.append( "set interfaces ethernet " + m + " bond-group " + name ) if state == "down": commands.append( "set interfaces bonding " + name + " disable" ) else: if mode != obj_in_have["mode"]: commands.append( "set interfaces bonding " + name + " mode " + mode ) missing_members = list( set(members) - set(obj_in_have["members"]) ) for m in missing_members: commands.append( "set interfaces ethernet " + m + " bond-group " + name ) if state == "down" and obj_in_have["state"] == "up": commands.append( "set interfaces bonding " + name + " disable" ) elif state == "up" and obj_in_have["state"] == "down": commands.append( "delete interfaces bonding " + name + " disable" ) return commands def map_config_to_obj(module): obj = [] output = run_commands(module, ["show interfaces bonding slaves"]) lines = output[0].splitlines() if len(lines) > 1: for line in lines[1:]: splitted_line = line.split() name = splitted_line[0] mode = splitted_line[1] state = splitted_line[2] if len(splitted_line) > 4: members = splitted_line[4:] else: members = [] obj.append( { "name": name, "mode": mode, "members": members, "state": state, } ) return obj def map_params_to_obj(module): obj = [] aggregate = module.params.get("aggregate") if aggregate: for item in aggregate: for key in item: if item.get(key) is None: item[key] = module.params[key] obj.append(item.copy()) else: obj.append( { "name": module.params["name"], "mode": module.params["mode"], "members": module.params["members"], "state": module.params["state"], } ) return obj def main(): """ main entry point for module execution """ element_spec = dict( name=dict(), mode=dict( choices=[ "802.3ad", "active-backup", "broadcast", "round-robin", "transmit-load-balance", "adaptive-load-balance", "xor-hash", "on", ], default="802.3ad", ), members=dict(type="list"), state=dict( default="present", choices=["present", "absent", "up", "down"] ), ) aggregate_spec = deepcopy(element_spec) aggregate_spec["name"] = dict(required=True) # remove default in aggregate spec, to handle common arguments remove_default_spec(aggregate_spec) argument_spec = dict( aggregate=dict(type="list", elements="dict", options=aggregate_spec), ) argument_spec.update(element_spec) argument_spec.update(vyos_argument_spec) required_one_of = [["name", "aggregate"]] mutually_exclusive = [["name", "aggregate"]] module = AnsibleModule( argument_spec=argument_spec, required_one_of=required_one_of, mutually_exclusive=mutually_exclusive, supports_check_mode=True, ) warnings = list() result = {"changed": False} if warnings: result["warnings"] = warnings want = map_params_to_obj(module) have = map_config_to_obj(module) commands = map_obj_to_commands((want, have), module) result["commands"] = commands if commands: commit = not module.check_mode load_config(module, commands, commit=commit) result["changed"] = True module.exit_json(**result) if __name__ == "__main__": main() diff --git a/plugins/modules/vyos_lldp_interface.py b/plugins/modules/vyos_lldp_interface.py index 80d4dbf..90e123d 100644 --- a/plugins/modules/vyos_lldp_interface.py +++ b/plugins/modules/vyos_lldp_interface.py @@ -1,261 +1,264 @@ #!/usr/bin/python # -*- coding: utf-8 -*- # (c) 2017, Ansible by Red Hat, inc # # This file is part of Ansible by Red Hat # # Ansible is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # Ansible is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with Ansible. If not, see . # +from __future__ import absolute_import, division, print_function + +__metaclass__ = type DOCUMENTATION = """ module: vyos_lldp_interface author: Ricardo Carrillo Cruz (@rcarrillocruz) short_description: (deprecated) Manage LLDP interfaces configuration on VyOS network devices description: - This module provides declarative management of LLDP interfaces configuration on VyOS network devices. version_added: 1.0.0 deprecated: removed_in: '2.13' alternative: vyos_lldp_interfaces why: Updated modules released with more functionality. notes: - Tested against VYOS 1.1.7 options: name: description: - Name of the interface LLDP should be configured on. type: str aggregate: description: List of interfaces LLDP should be configured on. type: list state: description: - State of the LLDP configuration. default: present choices: - present - absent - enabled - disabled type: str extends_documentation_fragment: - vyos.vyos.vyos """ EXAMPLES = """ - name: Enable LLDP on eth1 net_lldp_interface: state: present - name: Enable LLDP on specific interfaces net_lldp_interface: interfaces: - eth1 - eth2 state: present - name: Disable LLDP globally net_lldp_interface: state: disabled - name: Create aggregate of LLDP interface configurations vyos.vyos.vyos_lldp_interface: aggregate: - name: eth1 - name: eth2 state: present - name: Delete aggregate of LLDP interface configurations vyos.vyos.vyos_lldp_interface: aggregate: - name: eth1 - name: eth2 state: absent """ RETURN = """ commands: description: The list of configuration mode commands to send to the device returned: always, except for the platforms that use Netconf transport to manage the device. type: list sample: - set service lldp eth1 - set service lldp eth2 disable """ from copy import deepcopy from ansible.module_utils.basic import AnsibleModule from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ( remove_default_spec, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.vyos import ( get_config, load_config, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.vyos import ( vyos_argument_spec, ) def search_obj_in_list(name, lst): for o in lst: if o["name"] == name: return o return None def map_obj_to_commands(updates, module): commands = list() want, have = updates for w in want: name = w["name"] state = w["state"] obj_in_have = search_obj_in_list(name, have) if state == "absent" and obj_in_have: commands.append("delete service lldp interface " + name) elif state in ("present", "enabled"): if not obj_in_have: commands.append("set service lldp interface " + name) elif ( obj_in_have and obj_in_have["state"] == "disabled" and state == "enabled" ): commands.append( "delete service lldp interface " + name + " disable" ) elif state == "disabled": if not obj_in_have: commands.append("set service lldp interface " + name) commands.append( "set service lldp interface " + name + " disable" ) elif obj_in_have and obj_in_have["state"] != "disabled": commands.append( "set service lldp interface " + name + " disable" ) return commands def map_config_to_obj(module): obj = [] config = get_config(module).splitlines() output = [c for c in config if c.startswith("set service lldp interface")] for i in output: splitted_line = i.split() if len(splitted_line) > 5: new_obj = {"name": splitted_line[4]} if splitted_line[5] == "'disable'": new_obj["state"] = "disabled" else: new_obj = {"name": splitted_line[4][1:-1]} new_obj["state"] = "present" obj.append(new_obj) return obj def map_params_to_obj(module): obj = [] aggregate = module.params.get("aggregate") if aggregate: for item in aggregate: for key in item: if item.get(key) is None: item[key] = module.params[key] obj.append(item.copy()) else: obj.append( {"name": module.params["name"], "state": module.params["state"]} ) return obj def main(): """ main entry point for module execution """ element_spec = dict( name=dict(), state=dict( default="present", choices=["present", "absent", "enabled", "disabled"], ), ) aggregate_spec = deepcopy(element_spec) aggregate_spec["name"] = dict(required=True) # remove default in aggregate spec, to handle common arguments remove_default_spec(aggregate_spec) argument_spec = dict( aggregate=dict(type="list", elements="dict", options=aggregate_spec), ) argument_spec.update(element_spec) argument_spec.update(vyos_argument_spec) required_one_of = [["name", "aggregate"]] mutually_exclusive = [["name", "aggregate"]] module = AnsibleModule( argument_spec=argument_spec, required_one_of=required_one_of, mutually_exclusive=mutually_exclusive, supports_check_mode=True, ) warnings = list() result = {"changed": False} if warnings: result["warnings"] = warnings want = map_params_to_obj(module) have = map_config_to_obj(module) commands = map_obj_to_commands((want, have), module) result["commands"] = commands if commands: commit = not module.check_mode load_config(module, commands, commit=commit) result["changed"] = True module.exit_json(**result) if __name__ == "__main__": main() diff --git a/plugins/modules/vyos_static_route.py b/plugins/modules/vyos_static_route.py index 7859467..4724d5f 100644 --- a/plugins/modules/vyos_static_route.py +++ b/plugins/modules/vyos_static_route.py @@ -1,299 +1,302 @@ #!/usr/bin/python # -*- coding: utf-8 -*- # (c) 2017, Ansible by Red Hat, inc # # This file is part of Ansible by Red Hat # # Ansible is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # Ansible is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with Ansible. If not, see . # +from __future__ import absolute_import, division, print_function + +__metaclass__ = type DOCUMENTATION = """ module: vyos_static_route author: Trishna Guha (@trishnaguha) short_description: (deprecated) Manage static IP routes on Vyatta VyOS network devices description: - This module provides declarative management of static IP routes on Vyatta VyOS network devices. version_added: 1.0.0 deprecated: removed_in: '2.13' alternative: vyos_static_routes why: Updated modules released with more functionality. 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). options: prefix: description: - Network prefix of the static route. C(mask) param should be ignored if C(prefix) is provided with C(mask) value C(prefix/mask). type: str mask: description: - Network prefix mask of the static route. type: str next_hop: description: - Next hop IP of the static route. type: str admin_distance: description: - Admin distance of the static route. type: int aggregate: description: List of static route definitions type: list state: description: - State of the static route configuration. default: present choices: - present - absent type: str extends_documentation_fragment: - vyos.vyos.vyos """ EXAMPLES = """ - name: configure static route vyos.vyos.vyos_static_route: prefix: 192.168.2.0 mask: 24 next_hop: 10.0.0.1 - name: configure static route prefix/mask vyos.vyos.vyos_static_route: prefix: 192.168.2.0/16 next_hop: 10.0.0.1 - name: remove configuration vyos.vyos.vyos_static_route: prefix: 192.168.2.0 mask: 16 next_hop: 10.0.0.1 state: absent - name: configure aggregates of static routes vyos.vyos.vyos_static_route: aggregate: - {prefix: 192.168.2.0, mask: 24, next_hop: 10.0.0.1} - {prefix: 192.168.3.0, mask: 16, next_hop: 10.0.2.1} - {prefix: 192.168.3.0/16, next_hop: 10.0.2.1} - name: Remove static route collections vyos.vyos.vyos_static_route: aggregate: - {prefix: 172.24.1.0/24, next_hop: 192.168.42.64} - {prefix: 172.24.3.0/24, next_hop: 192.168.42.64} state: absent """ RETURN = """ commands: description: The list of configuration mode commands to send to the device returned: always type: list sample: - set protocols static route 192.168.2.0/16 next-hop 10.0.0.1 """ import re from copy import deepcopy from ansible.module_utils.basic import AnsibleModule from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ( remove_default_spec, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.vyos import ( get_config, load_config, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.vyos import ( vyos_argument_spec, ) def spec_to_commands(updates, module): commands = list() want, have = updates for w in want: prefix = w["prefix"] mask = w["mask"] next_hop = w["next_hop"] admin_distance = w["admin_distance"] state = w["state"] del w["state"] if state == "absent" and w in have: commands.append( "delete protocols static route %s/%s" % (prefix, mask) ) elif state == "present" and w not in have: cmd = "set protocols static route %s/%s next-hop %s" % ( prefix, mask, next_hop, ) if admin_distance != "None": cmd += " distance %s" % (admin_distance) commands.append(cmd) return commands def config_to_dict(module): data = get_config(module) obj = [] for line in data.split("\n"): if line.startswith("set protocols static route"): match = re.search(r"static route (\S+)", line, re.M) prefix = match.group(1).split("/")[0] mask = match.group(1).split("/")[1] if "next-hop" in line: match_hop = re.search(r"next-hop (\S+)", line, re.M) next_hop = match_hop.group(1).strip("'") match_distance = re.search(r"distance (\S+)", line, re.M) if match_distance is not None: admin_distance = match_distance.group(1)[1:-1] else: admin_distance = None if admin_distance is not None: obj.append( { "prefix": prefix, "mask": mask, "next_hop": next_hop, "admin_distance": admin_distance, } ) else: obj.append( { "prefix": prefix, "mask": mask, "next_hop": next_hop, "admin_distance": "None", } ) return obj def map_params_to_obj(module, required_together=None): obj = [] aggregate = module.params.get("aggregate") if aggregate: for item in aggregate: for key in item: if item.get(key) is None: item[key] = module.params[key] module._check_required_together(required_together, item) d = item.copy() if "/" in d["prefix"]: d["mask"] = d["prefix"].split("/")[1] d["prefix"] = d["prefix"].split("/")[0] if "admin_distance" in d: d["admin_distance"] = str(d["admin_distance"]) obj.append(d) else: prefix = module.params["prefix"].strip() if "/" in prefix: mask = prefix.split("/")[1] prefix = prefix.split("/")[0] else: mask = module.params["mask"].strip() next_hop = module.params["next_hop"].strip() admin_distance = str(module.params["admin_distance"]) state = module.params["state"] obj.append( { "prefix": prefix, "mask": mask, "next_hop": next_hop, "admin_distance": admin_distance, "state": state, } ) return obj def main(): """ main entry point for module execution """ element_spec = dict( prefix=dict(type="str"), mask=dict(type="str"), next_hop=dict(type="str"), admin_distance=dict(type="int"), state=dict(default="present", choices=["present", "absent"]), ) aggregate_spec = deepcopy(element_spec) aggregate_spec["prefix"] = dict(required=True) # remove default in aggregate spec, to handle common arguments remove_default_spec(aggregate_spec) argument_spec = dict( aggregate=dict(type="list", elements="dict", options=aggregate_spec), ) argument_spec.update(element_spec) argument_spec.update(vyos_argument_spec) required_one_of = [["aggregate", "prefix"]] required_together = [["prefix", "next_hop"]] mutually_exclusive = [["aggregate", "prefix"]] module = AnsibleModule( argument_spec=argument_spec, required_one_of=required_one_of, required_together=required_together, mutually_exclusive=mutually_exclusive, supports_check_mode=True, ) warnings = list() result = {"changed": False} if warnings: result["warnings"] = warnings want = map_params_to_obj(module, required_together=required_together) have = config_to_dict(module) commands = spec_to_commands((want, have), module) result["commands"] = commands if commands: commit = not module.check_mode load_config(module, commands, commit=commit) result["changed"] = True module.exit_json(**result) if __name__ == "__main__": main() diff --git a/tests/sanity/ignore-2.10.txt b/tests/sanity/ignore-2.10.txt deleted file mode 100644 index a62f497..0000000 --- a/tests/sanity/ignore-2.10.txt +++ /dev/null @@ -1,83 +0,0 @@ -plugins/module_utils/network/vyos/vyos.py future-import-boilerplate -plugins/module_utils/network/vyos/vyos.py metaclass-boilerplate -plugins/modules/vyos_banner.py future-import-boilerplate -plugins/modules/vyos_banner.py metaclass-boilerplate -plugins/modules/vyos_banner.py validate-modules:doc-default-does-not-match-spec -plugins/modules/vyos_banner.py validate-modules:doc-missing-type -plugins/modules/vyos_banner.py validate-modules:doc-required-mismatch -plugins/modules/vyos_command.py future-import-boilerplate -plugins/modules/vyos_command.py metaclass-boilerplate -plugins/modules/vyos_command.py pylint:blacklisted-name -plugins/modules/vyos_command.py validate-modules:doc-default-does-not-match-spec -plugins/modules/vyos_command.py validate-modules:doc-missing-type -plugins/modules/vyos_command.py validate-modules:doc-required-mismatch -plugins/modules/vyos_command.py validate-modules:parameter-list-no-elements -plugins/modules/vyos_command.py validate-modules:parameter-type-not-in-doc -plugins/modules/vyos_config.py future-import-boilerplate -plugins/modules/vyos_config.py metaclass-boilerplate -plugins/modules/vyos_config.py validate-modules:doc-default-does-not-match-spec -plugins/modules/vyos_config.py validate-modules:doc-missing-type -plugins/modules/vyos_config.py validate-modules:doc-required-mismatch -plugins/modules/vyos_config.py validate-modules:parameter-list-no-elements -plugins/modules/vyos_config.py validate-modules:parameter-type-not-in-doc -plugins/modules/vyos_facts.py future-import-boilerplate -plugins/modules/vyos_facts.py metaclass-boilerplate -plugins/modules/vyos_facts.py validate-modules:doc-default-does-not-match-spec -plugins/modules/vyos_facts.py validate-modules:doc-required-mismatch -plugins/modules/vyos_facts.py validate-modules:parameter-list-no-elements -plugins/modules/vyos_facts.py validate-modules:parameter-type-not-in-doc -plugins/modules/vyos_interfaces.py validate-modules:doc-elements-mismatch -plugins/modules/vyos_lag_interfaces.py validate-modules:doc-elements-mismatch -plugins/modules/vyos_lag_interfaces.py validate-modules:parameter-list-no-elements -plugins/modules/vyos_lldp_global.py validate-modules:parameter-list-no-elements -plugins/modules/vyos_lldp_interfaces.py validate-modules:doc-elements-mismatch -plugins/modules/vyos_lldp_interfaces.py validate-modules:doc-required-mismatch -plugins/modules/vyos_logging.py future-import-boilerplate -plugins/modules/vyos_logging.py metaclass-boilerplate -plugins/modules/vyos_logging.py validate-modules:doc-choices-do-not-match-spec -plugins/modules/vyos_logging.py validate-modules:doc-default-does-not-match-spec -plugins/modules/vyos_logging.py validate-modules:doc-elements-mismatch -plugins/modules/vyos_logging.py validate-modules:doc-missing-type -plugins/modules/vyos_logging.py validate-modules:doc-required-mismatch -plugins/modules/vyos_logging.py validate-modules:missing-suboption-docs -plugins/modules/vyos_logging.py validate-modules:parameter-type-not-in-doc -plugins/modules/vyos_logging.py validate-modules:undocumented-parameter -plugins/modules/vyos_ping.py validate-modules:doc-default-does-not-match-spec -plugins/modules/vyos_ping.py validate-modules:doc-required-mismatch -plugins/modules/vyos_ping.py validate-modules:parameter-type-not-in-doc -plugins/modules/vyos_system.py future-import-boilerplate -plugins/modules/vyos_system.py metaclass-boilerplate -plugins/modules/vyos_system.py validate-modules:doc-default-does-not-match-spec -plugins/modules/vyos_system.py validate-modules:doc-required-mismatch -plugins/modules/vyos_system.py validate-modules:parameter-list-no-elements -plugins/modules/vyos_system.py validate-modules:parameter-type-not-in-doc -plugins/modules/vyos_user.py future-import-boilerplate -plugins/modules/vyos_user.py metaclass-boilerplate -plugins/modules/vyos_user.py validate-modules:doc-choices-do-not-match-spec -plugins/modules/vyos_user.py validate-modules:doc-default-does-not-match-spec -plugins/modules/vyos_user.py validate-modules:doc-elements-mismatch -plugins/modules/vyos_user.py validate-modules:doc-missing-type -plugins/modules/vyos_user.py validate-modules:doc-required-mismatch -plugins/modules/vyos_user.py validate-modules:missing-suboption-docs -plugins/modules/vyos_user.py validate-modules:parameter-type-not-in-doc -plugins/modules/vyos_user.py validate-modules:undocumented-parameter -plugins/modules/vyos_vlan.py validate-modules:doc-choices-do-not-match-spec -plugins/modules/vyos_vlan.py validate-modules:doc-default-does-not-match-spec -plugins/modules/vyos_vlan.py validate-modules:doc-elements-mismatch -plugins/modules/vyos_vlan.py validate-modules:doc-missing-type -plugins/modules/vyos_vlan.py validate-modules:doc-required-mismatch -plugins/modules/vyos_vlan.py validate-modules:missing-suboption-docs -plugins/modules/vyos_vlan.py validate-modules:parameter-list-no-elements -plugins/modules/vyos_vlan.py validate-modules:parameter-type-not-in-doc -plugins/modules/vyos_vlan.py validate-modules:undocumented-parameter -plugins/action/vyos.py action-plugin-docs # base class for deprecated network platform modules using `connection: local` -plugins/doc_fragments/vyos.py future-import-boilerplate -plugins/doc_fragments/vyos.py metaclass-boilerplate -tests/unit/mock/path.py future-import-boilerplate -tests/unit/mock/path.py metaclass-boilerplate -tests/unit/mock/yaml_helper.py future-import-boilerplate -tests/unit/mock/yaml_helper.py metaclass-boilerplate -tests/unit/modules/conftest.py future-import-boilerplate -tests/unit/modules/conftest.py metaclass-boilerplate -tests/unit/modules/utils.py future-import-boilerplate -tests/unit/modules/utils.py metaclass-boilerplate \ No newline at end of file diff --git a/tests/sanity/ignore-2.9.txt b/tests/sanity/ignore-2.9.txt index a62f497..68c5fc2 100644 --- a/tests/sanity/ignore-2.9.txt +++ b/tests/sanity/ignore-2.9.txt @@ -1,83 +1,105 @@ -plugins/module_utils/network/vyos/vyos.py future-import-boilerplate -plugins/module_utils/network/vyos/vyos.py metaclass-boilerplate +plugins/action/vyos.py action-plugin-docs # base class for deprecated network platform modules using `connection: local` +plugins/doc_fragments/vyos.py future-import-boilerplate +plugins/doc_fragments/vyos.py metaclass-boilerplate plugins/modules/vyos_banner.py future-import-boilerplate plugins/modules/vyos_banner.py metaclass-boilerplate plugins/modules/vyos_banner.py validate-modules:doc-default-does-not-match-spec plugins/modules/vyos_banner.py validate-modules:doc-missing-type -plugins/modules/vyos_banner.py validate-modules:doc-required-mismatch plugins/modules/vyos_command.py future-import-boilerplate plugins/modules/vyos_command.py metaclass-boilerplate plugins/modules/vyos_command.py pylint:blacklisted-name plugins/modules/vyos_command.py validate-modules:doc-default-does-not-match-spec plugins/modules/vyos_command.py validate-modules:doc-missing-type -plugins/modules/vyos_command.py validate-modules:doc-required-mismatch -plugins/modules/vyos_command.py validate-modules:parameter-list-no-elements plugins/modules/vyos_command.py validate-modules:parameter-type-not-in-doc plugins/modules/vyos_config.py future-import-boilerplate plugins/modules/vyos_config.py metaclass-boilerplate plugins/modules/vyos_config.py validate-modules:doc-default-does-not-match-spec plugins/modules/vyos_config.py validate-modules:doc-missing-type -plugins/modules/vyos_config.py validate-modules:doc-required-mismatch -plugins/modules/vyos_config.py validate-modules:parameter-list-no-elements plugins/modules/vyos_config.py validate-modules:parameter-type-not-in-doc plugins/modules/vyos_facts.py future-import-boilerplate plugins/modules/vyos_facts.py metaclass-boilerplate plugins/modules/vyos_facts.py validate-modules:doc-default-does-not-match-spec -plugins/modules/vyos_facts.py validate-modules:doc-required-mismatch -plugins/modules/vyos_facts.py validate-modules:parameter-list-no-elements plugins/modules/vyos_facts.py validate-modules:parameter-type-not-in-doc -plugins/modules/vyos_interfaces.py validate-modules:doc-elements-mismatch -plugins/modules/vyos_lag_interfaces.py validate-modules:doc-elements-mismatch -plugins/modules/vyos_lag_interfaces.py validate-modules:parameter-list-no-elements -plugins/modules/vyos_lldp_global.py validate-modules:parameter-list-no-elements -plugins/modules/vyos_lldp_interfaces.py validate-modules:doc-elements-mismatch -plugins/modules/vyos_lldp_interfaces.py validate-modules:doc-required-mismatch +plugins/modules/vyos_firewall_global.py validate-modules:doc-choices-do-not-match-spec +plugins/modules/vyos_interface.py validate-modules:deprecation-mismatch +plugins/modules/vyos_interface.py validate-modules:doc-choices-do-not-match-spec +plugins/modules/vyos_interface.py validate-modules:doc-default-does-not-match-spec +plugins/modules/vyos_interface.py validate-modules:doc-missing-type +plugins/modules/vyos_interface.py validate-modules:invalid-documentation +plugins/modules/vyos_interface.py validate-modules:missing-suboption-docs +plugins/modules/vyos_interface.py validate-modules:parameter-type-not-in-doc +plugins/modules/vyos_interface.py validate-modules:undocumented-parameter +plugins/modules/vyos_l3_interface.py validate-modules:deprecation-mismatch +plugins/modules/vyos_l3_interface.py validate-modules:doc-choices-do-not-match-spec +plugins/modules/vyos_l3_interface.py validate-modules:doc-default-does-not-match-spec +plugins/modules/vyos_l3_interface.py validate-modules:doc-missing-type +plugins/modules/vyos_l3_interface.py validate-modules:invalid-documentation +plugins/modules/vyos_l3_interface.py validate-modules:missing-suboption-docs +plugins/modules/vyos_l3_interface.py validate-modules:parameter-type-not-in-doc +plugins/modules/vyos_l3_interface.py validate-modules:undocumented-parameter +plugins/modules/vyos_linkagg.py validate-modules:deprecation-mismatch +plugins/modules/vyos_linkagg.py validate-modules:doc-choices-do-not-match-spec +plugins/modules/vyos_linkagg.py validate-modules:doc-default-does-not-match-spec +plugins/modules/vyos_linkagg.py validate-modules:doc-missing-type +plugins/modules/vyos_linkagg.py validate-modules:invalid-documentation +plugins/modules/vyos_linkagg.py validate-modules:missing-suboption-docs +plugins/modules/vyos_linkagg.py validate-modules:parameter-type-not-in-doc +plugins/modules/vyos_linkagg.py validate-modules:undocumented-parameter +plugins/modules/vyos_lldp_interface.py validate-modules:deprecation-mismatch +plugins/modules/vyos_lldp_interface.py validate-modules:doc-choices-do-not-match-spec +plugins/modules/vyos_lldp_interface.py validate-modules:doc-default-does-not-match-spec +plugins/modules/vyos_lldp_interface.py validate-modules:doc-missing-type +plugins/modules/vyos_lldp_interface.py validate-modules:invalid-documentation +plugins/modules/vyos_lldp_interface.py validate-modules:missing-suboption-docs +plugins/modules/vyos_lldp_interface.py validate-modules:undocumented-parameter +plugins/modules/vyos_lldp.py validate-modules:deprecation-mismatch +plugins/modules/vyos_lldp.py validate-modules:doc-default-does-not-match-spec +plugins/modules/vyos_lldp.py validate-modules:invalid-documentation plugins/modules/vyos_logging.py future-import-boilerplate plugins/modules/vyos_logging.py metaclass-boilerplate plugins/modules/vyos_logging.py validate-modules:doc-choices-do-not-match-spec plugins/modules/vyos_logging.py validate-modules:doc-default-does-not-match-spec -plugins/modules/vyos_logging.py validate-modules:doc-elements-mismatch plugins/modules/vyos_logging.py validate-modules:doc-missing-type -plugins/modules/vyos_logging.py validate-modules:doc-required-mismatch plugins/modules/vyos_logging.py validate-modules:missing-suboption-docs plugins/modules/vyos_logging.py validate-modules:parameter-type-not-in-doc plugins/modules/vyos_logging.py validate-modules:undocumented-parameter +plugins/modules/vyos_ospfv2.py validate-modules:invalid-documentation +plugins/modules/vyos_ospfv3.py validate-modules:invalid-documentation plugins/modules/vyos_ping.py validate-modules:doc-default-does-not-match-spec -plugins/modules/vyos_ping.py validate-modules:doc-required-mismatch plugins/modules/vyos_ping.py validate-modules:parameter-type-not-in-doc +plugins/modules/vyos_static_route.py validate-modules:deprecation-mismatch +plugins/modules/vyos_static_route.py validate-modules:doc-choices-do-not-match-spec +plugins/modules/vyos_static_route.py validate-modules:doc-default-does-not-match-spec +plugins/modules/vyos_static_route.py validate-modules:doc-missing-type +plugins/modules/vyos_static_route.py validate-modules:invalid-documentation +plugins/modules/vyos_static_route.py validate-modules:missing-suboption-docs +plugins/modules/vyos_static_route.py validate-modules:parameter-type-not-in-doc +plugins/modules/vyos_static_route.py validate-modules:undocumented-parameter plugins/modules/vyos_system.py future-import-boilerplate plugins/modules/vyos_system.py metaclass-boilerplate plugins/modules/vyos_system.py validate-modules:doc-default-does-not-match-spec -plugins/modules/vyos_system.py validate-modules:doc-required-mismatch -plugins/modules/vyos_system.py validate-modules:parameter-list-no-elements plugins/modules/vyos_system.py validate-modules:parameter-type-not-in-doc plugins/modules/vyos_user.py future-import-boilerplate plugins/modules/vyos_user.py metaclass-boilerplate plugins/modules/vyos_user.py validate-modules:doc-choices-do-not-match-spec plugins/modules/vyos_user.py validate-modules:doc-default-does-not-match-spec -plugins/modules/vyos_user.py validate-modules:doc-elements-mismatch plugins/modules/vyos_user.py validate-modules:doc-missing-type -plugins/modules/vyos_user.py validate-modules:doc-required-mismatch plugins/modules/vyos_user.py validate-modules:missing-suboption-docs plugins/modules/vyos_user.py validate-modules:parameter-type-not-in-doc plugins/modules/vyos_user.py validate-modules:undocumented-parameter plugins/modules/vyos_vlan.py validate-modules:doc-choices-do-not-match-spec plugins/modules/vyos_vlan.py validate-modules:doc-default-does-not-match-spec -plugins/modules/vyos_vlan.py validate-modules:doc-elements-mismatch plugins/modules/vyos_vlan.py validate-modules:doc-missing-type -plugins/modules/vyos_vlan.py validate-modules:doc-required-mismatch plugins/modules/vyos_vlan.py validate-modules:missing-suboption-docs -plugins/modules/vyos_vlan.py validate-modules:parameter-list-no-elements plugins/modules/vyos_vlan.py validate-modules:parameter-type-not-in-doc plugins/modules/vyos_vlan.py validate-modules:undocumented-parameter -plugins/action/vyos.py action-plugin-docs # base class for deprecated network platform modules using `connection: local` -plugins/doc_fragments/vyos.py future-import-boilerplate -plugins/doc_fragments/vyos.py metaclass-boilerplate +plugins/module_utils/network/vyos/vyos.py future-import-boilerplate +plugins/module_utils/network/vyos/vyos.py metaclass-boilerplate tests/unit/mock/path.py future-import-boilerplate tests/unit/mock/path.py metaclass-boilerplate tests/unit/mock/yaml_helper.py future-import-boilerplate tests/unit/mock/yaml_helper.py metaclass-boilerplate tests/unit/modules/conftest.py future-import-boilerplate tests/unit/modules/conftest.py metaclass-boilerplate tests/unit/modules/utils.py future-import-boilerplate -tests/unit/modules/utils.py metaclass-boilerplate \ No newline at end of file +tests/unit/modules/utils.py metaclass-boilerplate diff --git a/tests/unit/modules/network/vyos/test_vyos_firewall_interfaces.py b/tests/unit/modules/network/vyos/test_vyos_firewall_interfaces.py index 92d2a4f..2a9a81c 100644 --- a/tests/unit/modules/network/vyos/test_vyos_firewall_interfaces.py +++ b/tests/unit/modules/network/vyos/test_vyos_firewall_interfaces.py @@ -1,430 +1,430 @@ # (c) 2016 Red Hat Inc. # # This file is part of Ansible # # Ansible is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # Ansible is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with Ansible. If not, see . # Make coding more python3-ish from __future__ import absolute_import, division, print_function __metaclass__ = type from ansible_collections.vyos.vyos.tests.unit.compat.mock import patch from ansible_collections.vyos.vyos.plugins.modules import ( vyos_firewall_interfaces, ) from ansible_collections.vyos.vyos.tests.unit.modules.utils import ( set_module_args, ) from .vyos_module import TestVyosModule, load_fixture class TestVyosFirewallInterfacesModule(TestVyosModule): module = vyos_firewall_interfaces def setUp(self): super(TestVyosFirewallInterfacesModule, self).setUp() self.mock_get_config = patch( "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.network.Config.get_config" ) self.get_config = self.mock_get_config.start() self.mock_load_config = patch( "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.network.Config.load_config" ) self.load_config = self.mock_load_config.start() self.mock_get_resource_connection_config = patch( "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base.get_resource_connection" ) self.get_resource_connection_config = ( self.mock_get_resource_connection_config.start() ) self.mock_get_resource_connection_facts = patch( "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.facts.facts.get_resource_connection" ) self.get_resource_connection_facts = ( self.mock_get_resource_connection_facts.start() ) self.mock_execute_show_command = patch( "ansible_collections.vyos.vyos.plugins.module_utils.network.vyos." "facts.firewall_interfaces.firewall_interfaces.Firewall_interfacesFacts.get_device_data" ) self.execute_show_command = self.mock_execute_show_command.start() def tearDown(self): super(TestVyosFirewallInterfacesModule, self).tearDown() self.mock_get_resource_connection_config.stop() self.mock_get_resource_connection_facts.stop() self.mock_get_config.stop() self.mock_load_config.stop() self.mock_execute_show_command.stop() def load_fixtures(self, commands=None): def load_from_file(*args, **kwargs): return load_fixture("vyos_firewall_interfaces_config.cfg") self.execute_show_command.side_effect = load_from_file def test_vyos_firewall_rule_set_01_merged(self): set_module_args( dict( config=[ dict( name="eth1", access_rules=[ dict( afi="ipv4", rules=[ dict(name="INBOUND", direction="in"), dict(name="OUTBOUND", direction="out"), dict(name="LOCAL", direction="local"), ], ), dict( afi="ipv6", rules=[ dict(name="V6-LOCAL", direction="local") ], ), ], ), dict( name="eth3", access_rules=[ dict( afi="ipv4", rules=[ dict(name="INBOUND", direction="in"), dict(name="OUTBOUND", direction="out"), dict(name="LOCAL", direction="local"), ], ), dict( afi="ipv6", rules=[ dict(name="V6-LOCAL", direction="local") ], ), ], ), ], state="merged", ) ) commands = [ "set interfaces ethernet eth1 firewall in name 'INBOUND'", "set interfaces ethernet eth1 firewall out name 'OUTBOUND'", "set interfaces ethernet eth1 firewall local name 'LOCAL'", "set interfaces ethernet eth1 firewall local ipv6-name 'V6-LOCAL'", "set interfaces ethernet eth3 firewall in name 'INBOUND'", "set interfaces ethernet eth3 firewall out name 'OUTBOUND'", "set interfaces ethernet eth3 firewall local name 'LOCAL'", "set interfaces ethernet eth3 firewall local ipv6-name 'V6-LOCAL'", ] self.execute_module(changed=True, commands=commands) def test_vyos_firewall_rule_set_02_merged_idem(self): set_module_args( dict( config=[ dict( name="eth0", access_rules=[ dict( afi="ipv4", rules=[ dict(name="INBOUND", direction="in"), dict(name="OUTBOUND", direction="out"), dict(name="LOCAL", direction="local"), ], ), dict( afi="ipv6", rules=[ dict(name="V6-LOCAL", direction="local") ], ), ], ), dict( name="eth2", access_rules=[ dict( afi="ipv4", rules=[ dict(name="INBOUND", direction="in"), dict(name="OUTBOUND", direction="out"), dict(name="LOCAL", direction="local"), ], ), dict( afi="ipv6", rules=[ dict(name="V6-LOCAL", direction="local") ], ), ], ), ], state="merged", ) ) self.execute_module(changed=False, commands=[]) def test_vyos_firewall_rule_set_01_deleted_per_afi(self): set_module_args( dict( config=[ dict( name="eth0", access_rules=[dict(afi="ipv4"), dict(afi="ipv6")], ) ], state="deleted", ) ) commands = [ "delete interfaces ethernet eth0 firewall in name", "delete interfaces ethernet eth0 firewall local name", "delete interfaces ethernet eth0 firewall out name", "delete interfaces ethernet eth0 firewall local ipv6-name", ] self.execute_module(changed=True, commands=commands) def test_vyos_firewall_rule_set_03_deleted_per_interface(self): set_module_args( dict( config=[dict(name="eth0"), dict(name="eth2")], state="deleted" ) ) commands = [ "delete interfaces ethernet eth0 firewall", "delete interfaces ethernet eth2 firewall", ] self.execute_module(changed=True, commands=commands) def test_vyos_firewall_rule_set_03_deleted_all(self): set_module_args(dict(config=[], state="deleted")) commands = [ "delete interfaces ethernet eth0 firewall", "delete interfaces ethernet eth2 firewall", ] self.execute_module(changed=True, commands=commands) def test_vyos_firewall_rule_set_03_deleted(self): set_module_args( dict( config=[dict(name="eth0"), dict(name="eth2")], state="deleted" ) ) commands = [ "delete interfaces ethernet eth0 firewall", "delete interfaces ethernet eth2 firewall", ] self.execute_module(changed=True, commands=commands) def test_vyos_firewall_rule_set_04_deleted_interface_idem(self): set_module_args( dict( config=[dict(name="eth1"), dict(name="eth3")], state="deleted" ) ) self.execute_module(changed=False, commands=[]) def test_vyos_firewall_rule_set_02_replaced_idem(self): set_module_args( dict( config=[ dict( name="eth0", access_rules=[ dict( afi="ipv4", rules=[ dict(name="INBOUND", direction="in"), dict(name="OUTBOUND", direction="out"), dict(name="LOCAL", direction="local"), ], ), dict( afi="ipv6", rules=[ dict(name="V6-LOCAL", direction="local") ], ), ], ), dict( name="eth2", access_rules=[ dict( afi="ipv4", rules=[ dict(name="INBOUND", direction="in"), dict(name="OUTBOUND", direction="out"), dict(name="LOCAL", direction="local"), ], ), dict( afi="ipv6", rules=[ dict(name="V6-LOCAL", direction="local") ], ), ], ), ], state="replaced", ) ) self.execute_module(changed=False, commands=[]) def test_vyos_firewall_rule_set_01_replaced(self): set_module_args( dict( config=[ dict( name="eth0", access_rules=[ dict( afi="ipv4", - rules=[dict(name="INBOUND", direction="in"),], + rules=[dict(name="INBOUND", direction="in")], ), dict( afi="ipv6", rules=[ dict(name="V6-LOCAL", direction="local") ], ), ], ), dict( name="eth2", access_rules=[ dict( afi="ipv4", rules=[dict(name="LOCAL", direction="local")], ), dict( afi="ipv6", rules=[ dict(name="V6-LOCAL", direction="local") ], ), ], ), dict( name="eth3", access_rules=[ dict( afi="ipv4", rules=[dict(name="LOCAL", direction="local")], ), dict( afi="ipv6", rules=[ dict(name="V6-LOCAL", direction="local") ], ), ], ), ], state="replaced", ) ) commands = [ "delete interfaces ethernet eth0 firewall out name", "delete interfaces ethernet eth0 firewall local name", "delete interfaces ethernet eth2 firewall in name", "delete interfaces ethernet eth2 firewall out name", "set interfaces ethernet eth3 firewall local name 'LOCAL'", "set interfaces ethernet eth3 firewall local ipv6-name 'V6-LOCAL'", ] self.execute_module(changed=True, commands=commands) def test_vyos_firewall_rule_set_01_overridden(self): set_module_args( dict( config=[ dict( name="eth1", access_rules=[ dict( afi="ipv4", rules=[dict(name="INBOUND", direction="in")], ) ], ) ], state="overridden", ) ) commands = [ "delete interfaces ethernet eth0 firewall", "delete interfaces ethernet eth2 firewall", "set interfaces ethernet eth1 firewall in name 'INBOUND'", ] self.execute_module(changed=True, commands=commands) def test_vyos_firewall_rule_set_02_overridden_idem(self): set_module_args( dict( config=[ dict( name="eth0", access_rules=[ dict( afi="ipv4", rules=[ dict(name="INBOUND", direction="in"), dict(name="OUTBOUND", direction="out"), dict(name="LOCAL", direction="local"), ], ), dict( afi="ipv6", rules=[ dict(name="V6-LOCAL", direction="local") ], ), ], ), dict( name="eth2", access_rules=[ dict( afi="ipv4", rules=[ dict(name="INBOUND", direction="in"), dict(name="OUTBOUND", direction="out"), dict(name="LOCAL", direction="local"), ], ), dict( afi="ipv6", rules=[ dict(name="V6-LOCAL", direction="local") ], ), ], ), ], state="overridden", ) ) self.execute_module(changed=False, commands=[]) diff --git a/tests/unit/modules/network/vyos/test_vyos_firewall_rules.py b/tests/unit/modules/network/vyos/test_vyos_firewall_rules.py index 86fcc65..8d59e19 100644 --- a/tests/unit/modules/network/vyos/test_vyos_firewall_rules.py +++ b/tests/unit/modules/network/vyos/test_vyos_firewall_rules.py @@ -1,1039 +1,1037 @@ # (c) 2016 Red Hat Inc. # # This file is part of Ansible # # Ansible is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # Ansible is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with Ansible. If not, see . # Make coding more python3-ish from __future__ import absolute_import, division, print_function __metaclass__ = type from ansible_collections.vyos.vyos.tests.unit.compat.mock import patch from ansible_collections.vyos.vyos.plugins.modules import vyos_firewall_rules from ansible_collections.vyos.vyos.tests.unit.modules.utils import ( set_module_args, ) from .vyos_module import TestVyosModule, load_fixture class TestVyosFirewallRulesModule(TestVyosModule): module = vyos_firewall_rules def setUp(self): super(TestVyosFirewallRulesModule, self).setUp() self.mock_get_config = patch( "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.network.Config.get_config" ) self.get_config = self.mock_get_config.start() self.mock_load_config = patch( "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.network.Config.load_config" ) self.load_config = self.mock_load_config.start() self.mock_get_resource_connection_config = patch( "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base.get_resource_connection" ) self.get_resource_connection_config = ( self.mock_get_resource_connection_config.start() ) self.mock_get_resource_connection_facts = patch( "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.facts.facts.get_resource_connection" ) self.get_resource_connection_facts = ( self.mock_get_resource_connection_facts.start() ) self.mock_execute_show_command = patch( "ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.static_routes.static_routes.Static_routesFacts.get_device_data" ) self.mock_execute_show_command = patch( "ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.firewall_rules.firewall_rules.Firewall_rulesFacts.get_device_data" ) self.execute_show_command = self.mock_execute_show_command.start() def tearDown(self): super(TestVyosFirewallRulesModule, self).tearDown() self.mock_get_resource_connection_config.stop() self.mock_get_resource_connection_facts.stop() self.mock_get_config.stop() self.mock_load_config.stop() self.mock_execute_show_command.stop() def load_fixtures(self, commands=None): def load_from_file(*args, **kwargs): return load_fixture("vyos_firewall_rules_config.cfg") self.execute_show_command.side_effect = load_from_file def test_vyos_firewall_rule_set_01_merged(self): set_module_args( dict( config=[ dict( afi="ipv6", rule_sets=[ dict( name="V6-INBOUND", description="This is IPv6 INBOUND rule set", default_action="reject", enable_default_log=True, rules=[], ), dict( name="V6-OUTBOUND", description="This is IPv6 OUTBOUND rule set", default_action="accept", enable_default_log=False, rules=[], ), ], ), dict( afi="ipv4", rule_sets=[ dict( name="V4-INBOUND", description="This is IPv4 INBOUND rule set", default_action="reject", enable_default_log=True, rules=[], ), dict( name="V4-OUTBOUND", description="This is IPv4 OUTBOUND rule set", default_action="accept", enable_default_log=False, rules=[], ), ], ), ], state="merged", ) ) commands = [ "set firewall ipv6-name V6-INBOUND default-action 'reject'", "set firewall ipv6-name V6-INBOUND description 'This is IPv6 INBOUND rule set'", "set firewall ipv6-name V6-INBOUND enable-default-log", "set firewall ipv6-name V6-OUTBOUND default-action 'accept'", "set firewall ipv6-name V6-OUTBOUND description 'This is IPv6 OUTBOUND rule set'", "set firewall name V4-INBOUND default-action 'reject'", "set firewall name V4-INBOUND description 'This is IPv4 INBOUND rule set'", "set firewall name V4-INBOUND enable-default-log", "set firewall name V4-OUTBOUND default-action 'accept'", "set firewall name V4-OUTBOUND description 'This is IPv4 OUTBOUND rule set'", ] self.execute_module(changed=True, commands=commands) def test_vyos_firewall_rule_set_02_merged(self): set_module_args( dict( config=[ dict( afi="ipv6", rule_sets=[ dict( name="V6-INBOUND", description="This is IPv6 INBOUND rule set", default_action="reject", enable_default_log=True, rules=[], ), dict( name="V6-OUTBOUND", description="This is IPv6 OUTBOUND rule set", default_action="accept", enable_default_log=False, rules=[], ), ], ), dict( afi="ipv4", rule_sets=[ dict( name="V4-INBOUND", description="This is IPv4 INBOUND rule set", default_action="reject", enable_default_log=True, rules=[], ), dict( name="V4-OUTBOUND", description="This is IPv4 OUTBOUND rule set", default_action="accept", enable_default_log=False, rules=[], ), ], ), ], state="merged", ) ) commands = [ "set firewall ipv6-name V6-INBOUND default-action 'reject'", "set firewall ipv6-name V6-INBOUND description 'This is IPv6 INBOUND rule set'", "set firewall ipv6-name V6-INBOUND enable-default-log", "set firewall ipv6-name V6-OUTBOUND default-action 'accept'", "set firewall ipv6-name V6-OUTBOUND description 'This is IPv6 OUTBOUND rule set'", "set firewall name V4-INBOUND default-action 'reject'", "set firewall name V4-INBOUND description 'This is IPv4 INBOUND rule set'", "set firewall name V4-INBOUND enable-default-log", "set firewall name V4-OUTBOUND default-action 'accept'", "set firewall name V4-OUTBOUND description 'This is IPv4 OUTBOUND rule set'", ] self.execute_module(changed=True, commands=commands) def test_vyos_firewall_v4_rule_sets_rule_merged_01(self): set_module_args( dict( config=[ dict( afi="ipv4", rule_sets=[ dict( name="INBOUND", description="This is IPv4 INBOUND rule set", default_action="accept", enable_default_log=True, rules=[ dict( number="101", action="accept", description="Rule 101 is configured by Ansible", ipsec="match-ipsec", protocol="icmp", fragment="match-frag", disabled=True, ) ], ), ], ) ], state="merged", ) ) commands = [ "set firewall name INBOUND default-action 'accept'", "set firewall name INBOUND description 'This is IPv4 INBOUND rule set'", "set firewall name INBOUND enable-default-log", "set firewall name INBOUND rule 101 protocol 'icmp'", "set firewall name INBOUND rule 101 description 'Rule 101 is configured by Ansible'", "set firewall name INBOUND rule 101 fragment 'match-frag'", "set firewall name INBOUND rule 101", "set firewall name INBOUND rule 101 disabled", "set firewall name INBOUND rule 101 action 'accept'", "set firewall name INBOUND rule 101 ipsec 'match-ipsec'", ] self.execute_module(changed=True, commands=commands) def test_vyos_firewall_v4_rule_sets_rule_merged_02(self): set_module_args( dict( config=[ dict( afi="ipv4", rule_sets=[ dict( name="INBOUND", rules=[ dict( number="101", protocol="tcp", source=dict( address="192.0.2.0", mac_address="38:00:25:19:76:0c", port=2127, ), destination=dict( address="192.0.1.0", port=2124 ), limit=dict( burst=10, rate=dict( number=20, unit="second" ), ), recent=dict(count=10, time=20), state=dict( established=True, related=True, invalid=True, new=True, ), ) ], ), ], ) ], state="merged", ) ) commands = [ "set firewall name INBOUND rule 101 protocol 'tcp'", "set firewall name INBOUND rule 101 destination address 192.0.1.0", "set firewall name INBOUND rule 101 destination port 2124", "set firewall name INBOUND rule 101", "set firewall name INBOUND rule 101 source address 192.0.2.0", "set firewall name INBOUND rule 101 source mac-address 38:00:25:19:76:0c", "set firewall name INBOUND rule 101 source port 2127", "set firewall name INBOUND rule 101 state new enable", "set firewall name INBOUND rule 101 state invalid enable", "set firewall name INBOUND rule 101 state related enable", "set firewall name INBOUND rule 101 state established enable", "set firewall name INBOUND rule 101 limit burst 10", "set firewall name INBOUND rule 101 limit rate 20/second", "set firewall name INBOUND rule 101 recent count 10", "set firewall name INBOUND rule 101 recent time 20", ] self.execute_module(changed=True, commands=commands) def test_vyos_firewall_v4_rule_sets_rule_merged_03(self): set_module_args( dict( config=[ dict( afi="ipv4", rule_sets=[ dict( name="INBOUND", rules=[ dict( number="101", destination=dict( group=dict( address_group="OUT-ADDR-GROUP", network_group="OUT-NET-GROUP", port_group="OUT-PORT-GROUP", ) ), source=dict( group=dict( address_group="IN-ADDR-GROUP", network_group="IN-NET-GROUP", port_group="IN-PORT-GROUP", ) ), ) ], ), ], ) ], state="merged", ) ) commands = [ "set firewall name INBOUND rule 101 source group address-group IN-ADDR-GROUP", "set firewall name INBOUND rule 101 source group network-group IN-NET-GROUP", "set firewall name INBOUND rule 101 source group port-group IN-PORT-GROUP", "set firewall name INBOUND rule 101 destination group address-group OUT-ADDR-GROUP", "set firewall name INBOUND rule 101 destination group network-group OUT-NET-GROUP", "set firewall name INBOUND rule 101 destination group port-group OUT-PORT-GROUP", "set firewall name INBOUND rule 101", ] self.execute_module(changed=True, commands=commands) def test_vyos_firewall_v4_rule_sets_rule_merged_04(self): set_module_args( dict( config=[ dict( afi="ipv4", rule_sets=[ dict( name="INBOUND", rules=[ dict( number="101", time=dict( monthdays="2", startdate="2020-01-24", starttime="13:20:00", stopdate="2020-01-28", stoptime="13:30:00", weekdays="!Sat,Sun", utc=True, ), tcp=dict(flags="ALL"), ) ], ), ], ) ], state="merged", ) ) commands = [ "set firewall name INBOUND rule 101", "set firewall name INBOUND rule 101 tcp flags ALL", "set firewall name INBOUND rule 101 time utc", "set firewall name INBOUND rule 101 time monthdays 2", "set firewall name INBOUND rule 101 time startdate 2020-01-24", "set firewall name INBOUND rule 101 time stopdate 2020-01-28", "set firewall name INBOUND rule 101 time weekdays !Sat,Sun", "set firewall name INBOUND rule 101 time stoptime 13:30:00", "set firewall name INBOUND rule 101 time starttime 13:20:00", ] self.execute_module(changed=True, commands=commands) def test_vyos_firewall_v6_rule_sets_rule_merged_01(self): set_module_args( dict( config=[ dict( afi="ipv6", rule_sets=[ dict( name="INBOUND", description="This is IPv6 INBOUND rule set", default_action="accept", enable_default_log=True, rules=[ dict( number="101", action="accept", description="Rule 101 is configured by Ansible", ipsec="match-ipsec", protocol="icmp", disabled=True, ) ], ), ], ) ], state="merged", ) ) commands = [ "set firewall ipv6-name INBOUND default-action 'accept'", "set firewall ipv6-name INBOUND description 'This is IPv6 INBOUND rule set'", "set firewall ipv6-name INBOUND enable-default-log", "set firewall ipv6-name INBOUND rule 101 protocol 'icmp'", "set firewall ipv6-name INBOUND rule 101 description 'Rule 101 is configured by Ansible'", "set firewall ipv6-name INBOUND rule 101", "set firewall ipv6-name INBOUND rule 101 disabled", "set firewall ipv6-name INBOUND rule 101 action 'accept'", "set firewall ipv6-name INBOUND rule 101 ipsec 'match-ipsec'", ] self.execute_module(changed=True, commands=commands) def test_vyos_firewall_v6_rule_sets_rule_merged_02(self): set_module_args( dict( config=[ dict( afi="ipv6", rule_sets=[ dict( name="INBOUND", rules=[ dict( number="101", protocol="tcp", source=dict( address="2001:db8::12", mac_address="38:00:25:19:76:0c", port=2127, ), destination=dict( address="2001:db8::11", port=2124 ), limit=dict( burst=10, rate=dict( number=20, unit="second" ), ), recent=dict(count=10, time=20), state=dict( established=True, related=True, invalid=True, new=True, ), ) ], ), ], ) ], state="merged", ) ) commands = [ "set firewall ipv6-name INBOUND rule 101 protocol 'tcp'", "set firewall ipv6-name INBOUND rule 101 destination address 2001:db8::11", "set firewall ipv6-name INBOUND rule 101 destination port 2124", "set firewall ipv6-name INBOUND rule 101", "set firewall ipv6-name INBOUND rule 101 source address 2001:db8::12", "set firewall ipv6-name INBOUND rule 101 source mac-address 38:00:25:19:76:0c", "set firewall ipv6-name INBOUND rule 101 source port 2127", "set firewall ipv6-name INBOUND rule 101 state new enable", "set firewall ipv6-name INBOUND rule 101 state invalid enable", "set firewall ipv6-name INBOUND rule 101 state related enable", "set firewall ipv6-name INBOUND rule 101 state established enable", "set firewall ipv6-name INBOUND rule 101 limit burst 10", "set firewall ipv6-name INBOUND rule 101 recent count 10", "set firewall ipv6-name INBOUND rule 101 recent time 20", "set firewall ipv6-name INBOUND rule 101 limit rate 20/second", ] self.execute_module(changed=True, commands=commands) def test_vyos_firewall_v6_rule_sets_rule_merged_03(self): set_module_args( dict( config=[ dict( afi="ipv6", rule_sets=[ dict( name="INBOUND", rules=[ dict( number="101", destination=dict( group=dict( address_group="OUT-ADDR-GROUP", network_group="OUT-NET-GROUP", port_group="OUT-PORT-GROUP", ) ), source=dict( group=dict( address_group="IN-ADDR-GROUP", network_group="IN-NET-GROUP", port_group="IN-PORT-GROUP", ) ), ) ], ), ], ) ], state="merged", ) ) commands = [ "set firewall ipv6-name INBOUND rule 101 source group address-group IN-ADDR-GROUP", "set firewall ipv6-name INBOUND rule 101 source group network-group IN-NET-GROUP", "set firewall ipv6-name INBOUND rule 101 source group port-group IN-PORT-GROUP", "set firewall ipv6-name INBOUND rule 101 destination group address-group OUT-ADDR-GROUP", "set firewall ipv6-name INBOUND rule 101 destination group network-group OUT-NET-GROUP", "set firewall ipv6-name INBOUND rule 101 destination group port-group OUT-PORT-GROUP", "set firewall ipv6-name INBOUND rule 101", ] self.execute_module(changed=True, commands=commands) def test_vyos_firewall_v6_rule_sets_rule_merged_04(self): set_module_args( dict( config=[ dict( afi="ipv6", rule_sets=[ dict( name="INBOUND", rules=[ dict( number="101", time=dict( monthdays="2", startdate="2020-01-24", starttime="13:20:00", stopdate="2020-01-28", stoptime="13:30:00", weekdays="!Sat,Sun", utc=True, ), tcp=dict(flags="ALL"), ) ], ), ], ) ], state="merged", ) ) commands = [ "set firewall ipv6-name INBOUND rule 101", "set firewall ipv6-name INBOUND rule 101 tcp flags ALL", "set firewall ipv6-name INBOUND rule 101 time utc", "set firewall ipv6-name INBOUND rule 101 time monthdays 2", "set firewall ipv6-name INBOUND rule 101 time startdate 2020-01-24", "set firewall ipv6-name INBOUND rule 101 time stopdate 2020-01-28", "set firewall ipv6-name INBOUND rule 101 time weekdays !Sat,Sun", "set firewall ipv6-name INBOUND rule 101 time stoptime 13:30:00", "set firewall ipv6-name INBOUND rule 101 time starttime 13:20:00", ] self.execute_module(changed=True, commands=commands) def test_vyos_firewall_v6_rule_sets_rule_merged_icmp_01(self): set_module_args( dict( config=[ dict( afi="ipv6", rule_sets=[ dict( name="INBOUND", rules=[ dict( number="101", protocol="icmp", icmp=dict( type_name="port-unreachable" ), ) ], ), ], ) ], state="merged", ) ) commands = [ "set firewall ipv6-name INBOUND rule 101 icmpv6 type port-unreachable", "set firewall ipv6-name INBOUND rule 101 protocol 'icmp'", "set firewall ipv6-name INBOUND rule 101", ] self.execute_module(changed=True, commands=commands) def test_vyos_firewall_v4_rule_sets_rule_merged_icmp_01(self): set_module_args( dict( config=[ dict( afi="ipv4", rule_sets=[ dict( name="INBOUND", rules=[ dict( number="101", protocol="icmp", icmp=dict(type=1, code=1), ) ], ), ], ) ], state="merged", ) ) commands = [ "set firewall name INBOUND rule 101 icmp type 1", "set firewall name INBOUND rule 101 icmp code 1", "set firewall name INBOUND rule 101 protocol 'icmp'", "set firewall name INBOUND rule 101", ] self.execute_module(changed=True, commands=commands) def test_vyos_firewall_v4_rule_sets_rule_merged_icmp_02(self): set_module_args( dict( config=[ dict( afi="ipv4", rule_sets=[ dict( name="INBOUND", rules=[ dict( number="101", protocol="icmp", icmp=dict(type_name="echo-request"), ) ], ), ], ) ], state="merged", ) ) commands = [ "set firewall name INBOUND rule 101 icmp type-name echo-request", "set firewall name INBOUND rule 101 protocol 'icmp'", "set firewall name INBOUND rule 101", ] self.execute_module(changed=True, commands=commands) def test_vyos_firewall_v4_rule_sets_del_01(self): set_module_args( dict( - config=[ - dict(afi="ipv4", rule_sets=[dict(name="V4-INGRESS"),]) - ], + config=[dict(afi="ipv4", rule_sets=[dict(name="V4-INGRESS")])], state="deleted", ) ) commands = ["delete firewall name V4-INGRESS"] self.execute_module(changed=True, commands=commands) def test_vyos_firewall_v4v6_rule_sets_del_02(self): set_module_args( dict( config=[ - dict(afi="ipv4", rule_sets=[dict(name="V4-INGRESS"),]), - dict(afi="ipv6", rule_sets=[dict(name="V6-INGRESS"),]), + dict(afi="ipv4", rule_sets=[dict(name="V4-INGRESS")]), + dict(afi="ipv6", rule_sets=[dict(name="V6-INGRESS")]), ], state="deleted", ) ) commands = [ "delete firewall name V4-INGRESS", "delete firewall ipv6-name V6-INGRESS", ] self.execute_module(changed=True, commands=commands) def test_vyos_firewall_v4v6_rule_sets_del_03(self): set_module_args(dict(config=[], state="deleted")) commands = ["delete firewall name", "delete firewall ipv6-name"] self.execute_module(changed=True, commands=commands) def test_vyos_firewall_v4v6_rule_sets_del_04(self): set_module_args( dict( config=[ - dict(afi="ipv4", rule_sets=[dict(name="V4-ING"),]), - dict(afi="ipv6", rule_sets=[dict(name="V6-ING"),]), + dict(afi="ipv4", rule_sets=[dict(name="V4-ING")]), + dict(afi="ipv6", rule_sets=[dict(name="V6-ING")]), ], state="deleted", ) ) self.execute_module(changed=False, commands=[]) def test_vyos_firewall_v4v6_rule_sets_rule_rep_01(self): set_module_args( dict( config=[ dict( afi="ipv4", rule_sets=[ dict( name="V4-INGRESS", description="This is IPv4 INGRESS rule set", default_action="accept", enable_default_log=True, rules=[ dict( number="101", action="reject", description="Rule 101 is configured by Ansible RM", ipsec="match-ipsec", protocol="tcp", fragment="match-frag", disabled=False, ), dict( number="102", action="accept", description="Rule 102 is configured by Ansible RM", protocol="icmp", disabled=True, ), ], ), ], ), dict( afi="ipv6", rule_sets=[ dict( name="V6-INGRESS", default_action="accept", description="This rule-set is configured by Ansible RM", ), dict( name="V6-EGRESS", default_action="reject", description="This rule-set is configured by Ansible RM", ), ], ), ], state="replaced", ) ) commands = [ "delete firewall name V4-INGRESS rule 101 disabled", "delete firewall name V4-EGRESS default-action", "set firewall name V4-INGRESS description 'This is IPv4 INGRESS rule set'", "set firewall name V4-INGRESS rule 101 protocol 'tcp'", "set firewall name V4-INGRESS rule 101 description 'Rule 101 is configured by Ansible RM'", "set firewall name V4-INGRESS rule 101 action 'reject'", "set firewall name V4-INGRESS rule 102 disabled", "set firewall name V4-INGRESS rule 102 action 'accept'", "set firewall name V4-INGRESS rule 102 protocol 'icmp'", "set firewall name V4-INGRESS rule 102 description 'Rule 102 is configured by Ansible RM'", "set firewall name V4-INGRESS rule 102", "set firewall ipv6-name V6-INGRESS description 'This rule-set is configured by Ansible RM'", "set firewall ipv6-name V6-EGRESS description 'This rule-set is configured by Ansible RM'", ] self.execute_module(changed=True, commands=commands) def test_vyos_firewall_v4v6_rule_sets_rule_rep_02(self): set_module_args( dict( config=[ dict( afi="ipv4", rule_sets=[ dict( name="V4-INGRESS", description="This is IPv4 V4-INGRESS rule set", default_action="accept", enable_default_log=False, rules=[ dict( number="101", action="accept", description="Rule 101 is configured by Ansible", ipsec="match-ipsec", protocol="icmp", fragment="match-frag", disabled=True, ), ], ), ], ), dict( afi="ipv6", rule_sets=[ dict(name="V6-INGRESS", default_action="accept",), dict(name="V6-EGRESS", default_action="reject",), ], ), ], state="replaced", ) ) commands = [ "delete firewall name V4-INGRESS enable-default-log", "delete firewall name V4-EGRESS default-action", ] self.execute_module(changed=True, commands=commands) def test_vyos_firewall_v4v6_rule_sets_rule_rep_idem_01(self): set_module_args( dict( config=[ dict( afi="ipv4", rule_sets=[ dict( name="V4-INGRESS", description="This is IPv4 V4-INGRESS rule set", default_action="accept", enable_default_log=True, rules=[ dict( number="101", action="accept", description="Rule 101 is configured by Ansible", ipsec="match-ipsec", protocol="icmp", fragment="match-frag", disabled=True, ) ], ), dict(name="V4-EGRESS", default_action="reject",), ], ), dict( afi="ipv6", rule_sets=[ dict(name="V6-INGRESS", default_action="accept",), dict(name="V6-EGRESS", default_action="reject",), ], ), ], state="replaced", ) ) self.execute_module(changed=False, commands=[]) def test_vyos_firewall_v4v6_rule_sets_rule_mer_idem_01(self): set_module_args( dict( config=[ dict( afi="ipv4", rule_sets=[ dict( name="V4-INGRESS", description="This is IPv4 V4-INGRESS rule set", default_action="accept", enable_default_log=True, rules=[ dict( number="101", action="accept", description="Rule 101 is configured by Ansible", ipsec="match-ipsec", protocol="icmp", fragment="match-frag", disabled=True, ) ], ), dict(name="V4-EGRESS", default_action="reject",), ], ), dict( afi="ipv6", rule_sets=[ dict(name="V6-INGRESS", default_action="accept",), dict(name="V6-EGRESS", default_action="reject",), ], ), ], state="merged", ) ) self.execute_module(changed=False, commands=[]) def test_vyos_firewall_v4v6_rule_sets_rule_ovr_01(self): set_module_args( dict( config=[ dict( afi="ipv4", rule_sets=[ dict( name="V4-IN", description="This is IPv4 INGRESS rule set", default_action="accept", enable_default_log=True, rules=[ dict( number="1", action="reject", description="Rule 1 is configured by Ansible RM", ipsec="match-ipsec", protocol="tcp", fragment="match-frag", disabled=False, ), dict( number="2", action="accept", description="Rule 102 is configured by Ansible RM", protocol="icmp", disabled=True, ), ], ), ], ), dict( afi="ipv6", rule_sets=[ dict( name="V6-IN", default_action="accept", description="This rule-set is configured by Ansible RM", ), dict( name="V6-EG", default_action="reject", description="This rule-set is configured by Ansible RM", ), ], ), ], state="overridden", ) ) commands = [ "delete firewall ipv6-name V6-INGRESS", "delete firewall ipv6-name V6-EGRESS", "delete firewall name V4-INGRESS", "delete firewall name V4-EGRESS", "set firewall name V4-IN default-action 'accept'", "set firewall name V4-IN description 'This is IPv4 INGRESS rule set'", "set firewall name V4-IN enable-default-log", "set firewall name V4-IN rule 1 protocol 'tcp'", "set firewall name V4-IN rule 1 description 'Rule 1 is configured by Ansible RM'", "set firewall name V4-IN rule 1 fragment 'match-frag'", "set firewall name V4-IN rule 1", "set firewall name V4-IN rule 1 action 'reject'", "set firewall name V4-IN rule 1 ipsec 'match-ipsec'", "set firewall name V4-IN rule 2 disabled", "set firewall name V4-IN rule 2 action 'accept'", "set firewall name V4-IN rule 2 protocol 'icmp'", "set firewall name V4-IN rule 2 description 'Rule 102 is configured by Ansible RM'", "set firewall name V4-IN rule 2", "set firewall ipv6-name V6-IN default-action 'accept'", "set firewall ipv6-name V6-IN description 'This rule-set is configured by Ansible RM'", "set firewall ipv6-name V6-EG default-action 'reject'", "set firewall ipv6-name V6-EG description 'This rule-set is configured by Ansible RM'", ] self.execute_module(changed=True, commands=commands) def test_vyos_firewall_v4v6_rule_sets_rule_ovr_idem_01(self): set_module_args( dict( config=[ dict( afi="ipv4", rule_sets=[ dict( name="V4-INGRESS", description="This is IPv4 V4-INGRESS rule set", default_action="accept", enable_default_log=True, rules=[ dict( number="101", action="accept", description="Rule 101 is configured by Ansible", ipsec="match-ipsec", protocol="icmp", fragment="match-frag", disabled=True, ) ], ), dict(name="V4-EGRESS", default_action="reject",), ], ), dict( afi="ipv6", rule_sets=[ dict(name="V6-INGRESS", default_action="accept",), dict(name="V6-EGRESS", default_action="reject",), ], ), ], state="overridden", ) ) self.execute_module(changed=False, commands=[]) diff --git a/tests/unit/modules/network/vyos/test_vyos_ospfv2.py b/tests/unit/modules/network/vyos/test_vyos_ospfv2.py index b825066..3d558b8 100644 --- a/tests/unit/modules/network/vyos/test_vyos_ospfv2.py +++ b/tests/unit/modules/network/vyos/test_vyos_ospfv2.py @@ -1,435 +1,435 @@ # (c) 2016 Red Hat Inc. # # This file is part of Ansible # # Ansible is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # Ansible is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with Ansible. If not, see . # Make coding more python3-ish from __future__ import absolute_import, division, print_function __metaclass__ = type from ansible_collections.vyos.vyos.tests.unit.compat.mock import patch from ansible_collections.vyos.vyos.plugins.modules import vyos_ospfv2 from ansible_collections.vyos.vyos.tests.unit.modules.utils import ( set_module_args, ) from .vyos_module import TestVyosModule, load_fixture class TestVyosFirewallRulesModule(TestVyosModule): module = vyos_ospfv2 def setUp(self): super(TestVyosFirewallRulesModule, self).setUp() self.mock_get_config = patch( "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.network.Config.get_config" ) self.get_config = self.mock_get_config.start() self.mock_load_config = patch( "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.network.Config.load_config" ) self.load_config = self.mock_load_config.start() self.mock_get_resource_connection_config = patch( "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base.get_resource_connection" ) self.get_resource_connection_config = ( self.mock_get_resource_connection_config.start() ) self.mock_get_resource_connection_facts = patch( "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.facts.facts.get_resource_connection" ) self.get_resource_connection_facts = ( self.mock_get_resource_connection_facts.start() ) self.mock_execute_show_command = patch( "ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.ospfv2.ospfv2.Ospfv2Facts.get_device_data" ) self.execute_show_command = self.mock_execute_show_command.start() def tearDown(self): super(TestVyosFirewallRulesModule, self).tearDown() self.mock_get_resource_connection_config.stop() self.mock_get_resource_connection_facts.stop() self.mock_get_config.stop() self.mock_load_config.stop() self.mock_execute_show_command.stop() def load_fixtures(self, commands=None, transport="cli", filename=None): if filename is None: filename = "vyos_ospfv2_config.cfg" def load_from_file(*args, **kwargs): output = load_fixture(filename) return output self.execute_show_command.side_effect = load_from_file def test_vyos_ospfv2_merged_new_config(self): set_module_args( dict( config=dict( log_adjacency_changes="detail", mpls_te=dict(enabled=True, router_address="192.0.11.11"), auto_cost=dict(reference_bandwidth=2), areas=[ dict( area_id="2", area_type=dict(normal=True), authentication="plaintext-password", shortcut="enable", ), dict( area_id="4", area_type=dict(stub=dict(default_cost=10)), - network=[dict(address="192.0.2.0/24"),], + network=[dict(address="192.0.2.0/24")], range=[ dict(address="192.0.3.0/24", cost=10), dict(address="192.0.4.0/24", cost=12), ], ), ], ), state="merged", ) ) commands = [ "set protocols ospf mpls-te enable", "set protocols ospf mpls-te router-address '192.0.11.11'", "set protocols ospf auto-cost reference-bandwidth '2'", "set protocols ospf log-adjacency-changes 'detail'", "set protocols ospf area '2'", "set protocols ospf area 2 authentication plaintext-password", "set protocols ospf area 2 shortcut enable", "set protocols ospf area 2 area-type normal", "set protocols ospf area 4 range 192.0.3.0/24 cost 10", "set protocols ospf area 4 range 192.0.3.0/24", "set protocols ospf area 4 range 192.0.4.0/24 cost 12", "set protocols ospf area 4 range 192.0.4.0/24", "set protocols ospf area 4 area-type stub default-cost 10", "set protocols ospf area '4'", "set protocols ospf area 4 network 192.0.2.0/24", ] self.execute_module(changed=True, commands=commands) def test_vyos_ospfv2_merged_idem(self): set_module_args( dict( config=dict( areas=[ dict( area_id="12", area_type=dict(normal=True), authentication="plaintext-password", shortcut="enable", ), dict( area_id="14", area_type=dict(stub=dict(default_cost=20)), - network=[dict(address="192.0.12.0/24"),], + network=[dict(address="192.0.12.0/24")], range=[ dict(address="192.0.13.0/24", cost=10), dict(address="192.0.14.0/24", cost=12), ], ), ], ), state="merged", ) ) self.execute_module(changed=False, commands=[]) def test_vyos_ospfv2_merged_update_existing(self): set_module_args( dict( config=dict( areas=[ dict( area_id="12", area_type=dict(normal=True), authentication="plaintext-password", shortcut="enable", ), dict( area_id="14", area_type=dict(stub=dict(set=False)), network=[ dict(address="192.0.12.0/24"), dict(address="192.0.22.0/24"), ], range=[ dict(address="192.0.13.0/24", cost=10), dict(address="192.0.14.0/24", cost=12), ], ), ], ), state="merged", ) ) commands = [ "delete protocols ospf area 14 area-type stub", "set protocols ospf area 14 network 192.0.22.0/24", ] self.execute_module(changed=True, commands=commands) def test_vyos_ospfv2_replaced(self): set_module_args( dict( config=dict( log_adjacency_changes="detail", mpls_te=dict(enabled=True, router_address="192.0.11.11"), auto_cost=dict(reference_bandwidth=2), areas=[ dict( area_id="12", area_type=dict(normal=True), authentication="plaintext-password", shortcut="enable", ), dict( area_id="15", area_type=dict(stub=dict(default_cost=10)), - network=[dict(address="192.0.12.0/24"),], + network=[dict(address="192.0.12.0/24")], range=[ dict(address="192.0.13.0/24", cost=10), dict(address="192.0.14.0/24", cost=12), dict(address="192.0.15.0/24", cost=14), ], ), ], ), state="replaced", ) ) commands = [ "set protocols ospf mpls-te enable", "set protocols ospf mpls-te router-address '192.0.11.11'", "set protocols ospf auto-cost reference-bandwidth '2'", "set protocols ospf log-adjacency-changes 'detail'", "delete protocols ospf area 14", "set protocols ospf area 15 range 192.0.13.0/24 cost 10", "set protocols ospf area 15 range 192.0.13.0/24", "set protocols ospf area 15 range 192.0.14.0/24 cost 12", "set protocols ospf area 15 range 192.0.14.0/24", "set protocols ospf area 15 range 192.0.15.0/24 cost 14", "set protocols ospf area 15 range 192.0.15.0/24", "set protocols ospf area 15 area-type stub default-cost 10", "set protocols ospf area '15'", "set protocols ospf area 15 network 192.0.12.0/24", ] self.execute_module(changed=True, commands=commands) def test_vyos_ospfv2_replaced_idem(self): set_module_args( dict( config=dict( areas=[ dict( area_id="12", area_type=dict(normal=True), authentication="plaintext-password", shortcut="enable", ), dict( area_id="14", area_type=dict(stub=dict(default_cost=20)), - network=[dict(address="192.0.12.0/24"),], + network=[dict(address="192.0.12.0/24")], range=[ dict(address="192.0.13.0/24", cost=10), dict(address="192.0.14.0/24", cost=12), ], ), ], ), state="replaced", ) ) self.execute_module(changed=False, commands=[]) def test_vyos_ospfv2_deleted_no_config(self): set_module_args(dict(config=None, state="deleted")) commands = ["delete protocols ospf"] self.execute_module(changed=True, commands=commands) def test_vyos_ospfv2_gathered(self): set_module_args(dict(state="gathered")) result = self.execute_module( changed=False, filename="vyos_ospfv2_config.cfg" ) gather_dict = { "areas": [ { "area_id": "2", "area_type": {"normal": True}, "authentication": "plaintext-password", "shortcut": "enable", }, { "area_id": "14", "area_type": {"stub": {"default_cost": 20, "set": True}}, "network": [{"address": "192.0.12.0/24"}], "range": [ {"address": "192.0.13.0/24", "cost": 10}, {"address": "192.0.14.0/24", "cost": 12}, ], }, ], } self.assertEqual(sorted(gather_dict), sorted(result["gathered"])) def test_vyos_ospfv2_parsed(self): parsed_str = """set protocols ospf area 2 area-type 'normal' set protocols ospf area 2 authentication 'plaintext-password' set protocols ospf area 2 shortcut 'enable' set protocols ospf area 3 area-type 'nssa' set protocols ospf area 4 area-type stub default-cost '20' set protocols ospf area 4 network '192.0.2.0/24' set protocols ospf area 4 range 192.0.3.0/24 cost '10' set protocols ospf area 4 range 192.0.4.0/24 cost '12' set protocols ospf default-information originate 'always' set protocols ospf default-information originate metric '10' set protocols ospf default-information originate metric-type '2' set protocols ospf auto-cost reference-bandwidth '2' set protocols ospf default-information originate route-map 'ingress' set protocols ospf log-adjacency-changes 'detail' set protocols ospf max-metric router-lsa 'administrative' set protocols ospf max-metric router-lsa on-shutdown '10' set protocols ospf max-metric router-lsa on-startup '10' set protocols ospf mpls-te 'enable' set protocols ospf mpls-te router-address '192.0.11.11' set protocols ospf neighbor 192.0.11.12 poll-interval '10' set protocols ospf neighbor 192.0.11.12 priority '2' set protocols ospf parameters abr-type 'cisco' set protocols ospf parameters 'opaque-lsa' set protocols ospf parameters 'rfc1583-compatibility' set protocols ospf parameters router-id '192.0.1.1' set protocols ospf passive-interface 'eth1' set protocols ospf passive-interface 'eth2' set protocols ospf redistribute bgp metric '10' set protocols ospf redistribute bgp metric-type '2'""" set_module_args(dict(running_config=parsed_str, state="parsed")) result = self.execute_module(changed=False) parsed_list = { "areas": [ { "area_id": "2", "area_type": {"normal": True}, "authentication": "plaintext-password", "shortcut": "enable", }, {"area_id": "3", "area_type": {"nssa": {"set": True}}}, { "area_id": "4", "area_type": {"stub": {"default_cost": 20, "set": True}}, "network": [{"address": "192.0.2.0/24"}], "range": [ {"address": "192.0.3.0/24", "cost": 10}, {"address": "192.0.4.0/24", "cost": 12}, ], }, ], "auto_cost": {"reference_bandwidth": 2}, "default_information": { "originate": { "always": True, "metric": 10, "metric_type": 2, "route_map": "ingress", } }, "log_adjacency_changes": "detail", "max_metric": { "router_lsa": { "administrative": True, "on_shutdown": 10, "on_startup": 10, } }, "mpls_te": {"enabled": True, "router_address": "192.0.11.11"}, "neighbor": [ { "neighbor_id": "192.0.11.12", "poll_interval": 10, "priority": 2, } ], "parameters": { "abr_type": "cisco", "opaque_lsa": True, "rfc1583_compatibility": True, "router_id": "192.0.1.1", }, "passive_interface": ["eth2", "eth1"], "redistribute": [ {"metric": 10, "metric_type": 2, "route_type": "bgp"} ], } self.assertEqual(sorted(parsed_list), sorted(result["parsed"])) def test_vyos_ospfv2_rendered(self): set_module_args( dict( config=dict( log_adjacency_changes="detail", mpls_te=dict(enabled=True, router_address="192.0.11.11"), auto_cost=dict(reference_bandwidth=2), areas=[ dict( area_id="2", area_type=dict(normal=True), authentication="plaintext-password", shortcut="enable", ), dict( area_id="4", area_type=dict(stub=dict(default_cost=10)), - network=[dict(address="192.0.2.0/24"),], + network=[dict(address="192.0.2.0/24")], range=[ dict(address="192.0.3.0/24", cost=10), dict(address="192.0.4.0/24", cost=12), ], ), ], ), state="rendered", ) ) commands = [ "set protocols ospf mpls-te enable", "set protocols ospf mpls-te router-address '192.0.11.11'", "set protocols ospf auto-cost reference-bandwidth '2'", "set protocols ospf log-adjacency-changes 'detail'", "set protocols ospf area '2'", "set protocols ospf area 2 authentication plaintext-password", "set protocols ospf area 2 shortcut enable", "set protocols ospf area 2 area-type normal", "set protocols ospf area 4 range 192.0.3.0/24 cost 10", "set protocols ospf area 4 range 192.0.3.0/24", "set protocols ospf area 4 range 192.0.4.0/24 cost 12", "set protocols ospf area 4 range 192.0.4.0/24", "set protocols ospf area 4 area-type stub default-cost 10", "set protocols ospf area '4'", "set protocols ospf area 4 network 192.0.2.0/24", ] result = self.execute_module(changed=False) self.assertEqual( sorted(result["rendered"]), sorted(commands), result["rendered"] ) diff --git a/tests/unit/modules/network/vyos/test_vyos_ospfv3.py b/tests/unit/modules/network/vyos/test_vyos_ospfv3.py index 1d9cb3a..9c016da 100644 --- a/tests/unit/modules/network/vyos/test_vyos_ospfv3.py +++ b/tests/unit/modules/network/vyos/test_vyos_ospfv3.py @@ -1,348 +1,348 @@ # (c) 2016 Red Hat Inc. # # This file is part of Ansible # # Ansible is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # Ansible is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with Ansible. If not, see . # Make coding more python3-ish from __future__ import absolute_import, division, print_function __metaclass__ = type from ansible_collections.vyos.vyos.tests.unit.compat.mock import patch from ansible_collections.vyos.vyos.plugins.modules import vyos_ospfv3 from ansible_collections.vyos.vyos.tests.unit.modules.utils import ( set_module_args, ) from .vyos_module import TestVyosModule, load_fixture class TestVyosFirewallRulesModule(TestVyosModule): module = vyos_ospfv3 def setUp(self): super(TestVyosFirewallRulesModule, self).setUp() self.mock_get_config = patch( "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.network.Config.get_config" ) self.get_config = self.mock_get_config.start() self.mock_load_config = patch( "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.network.Config.load_config" ) self.load_config = self.mock_load_config.start() self.mock_get_resource_connection_config = patch( "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base.get_resource_connection" ) self.get_resource_connection_config = ( self.mock_get_resource_connection_config.start() ) self.mock_get_resource_connection_facts = patch( "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.facts.facts.get_resource_connection" ) self.get_resource_connection_facts = ( self.mock_get_resource_connection_facts.start() ) self.mock_execute_show_command = patch( "ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.ospfv3.ospfv3.Ospfv3Facts.get_device_data" ) self.execute_show_command = self.mock_execute_show_command.start() def tearDown(self): super(TestVyosFirewallRulesModule, self).tearDown() self.mock_get_resource_connection_config.stop() self.mock_get_resource_connection_facts.stop() self.mock_get_config.stop() self.mock_load_config.stop() self.mock_execute_show_command.stop() def load_fixtures(self, commands=None, transport="cli", filename=None): if filename is None: filename = "vyos_ospfv3_config.cfg" def load_from_file(*args, **kwargs): output = load_fixture(filename) return output self.execute_show_command.side_effect = load_from_file def test_vyos_ospfv3_merged_new_config(self): set_module_args( dict( config=dict( redistribute=[dict(route_type="bgp")], parameters=dict(router_id="192.0.2.10"), areas=[ dict( area_id="2", export_list="export1", import_list="import1", range=[ dict(address="2001:db10::/32"), dict(address="2001:db20::/32"), dict(address="2001:db30::/32"), ], ), dict( area_id="3", - range=[dict(address="2001:db40::/32"),], + range=[dict(address="2001:db40::/32")], ), ], ), state="merged", ) ) commands = [ "set protocols ospfv3 redistribute bgp", "set protocols ospfv3 parameters router-id '192.0.2.10'", "set protocols ospfv3 area 2 range 2001:db10::/32", "set protocols ospfv3 area 2 range 2001:db20::/32", "set protocols ospfv3 area 2 range 2001:db30::/32", "set protocols ospfv3 area '2'", "set protocols ospfv3 area 2 export-list export1", "set protocols ospfv3 area 2 import-list import1", "set protocols ospfv3 area '3'", "set protocols ospfv3 area 3 range 2001:db40::/32", ] self.execute_module(changed=True, commands=commands) def test_vyos_ospfv3_merged_idem(self): set_module_args( dict( config=dict( areas=[ dict( area_id="12", export_list="export1", import_list="import1", range=[ dict(address="2001:db11::/32"), dict(address="2001:db22::/32"), dict(address="2001:db33::/32"), ], ), dict( area_id="13", - range=[dict(address="2001:db44::/32"),], + range=[dict(address="2001:db44::/32")], ), ], ), state="merged", ) ) self.execute_module(changed=False, commands=[]) def test_vyos_ospfv3_merged_update_existing(self): set_module_args( dict( config=dict( redistribute=[dict(route_type="bgp")], parameters=dict(router_id="192.0.2.10"), areas=[ dict( area_id="12", export_list="export1", import_list="import1", range=[ dict(address="2001:db11::/32"), dict(address="2001:db22::/32"), dict(address="2001:db33::/32"), ], ), dict( area_id="13", range=[ dict(address="2001:db44::/32"), dict(address="2001:db55::/32"), ], ), ], ), state="merged", ) ) commands = [ "set protocols ospfv3 redistribute bgp", "set protocols ospfv3 parameters router-id '192.0.2.10'", "set protocols ospfv3 area 13 range 2001:db55::/32", ] self.execute_module(changed=True, commands=commands) def test_vyos_ospfv3_replaced(self): set_module_args( dict( config=dict( redistribute=[dict(route_type="bgp")], parameters=dict(router_id="192.0.2.10"), areas=[ dict( area_id="12", export_list="export1", import_list="import1", range=[ dict(address="2001:db10::/32"), dict(address="2001:db22::/32"), dict(address="2001:db33::/32"), ], ), dict( area_id="14", - range=[dict(address="2001:db40::/32"),], + range=[dict(address="2001:db40::/32")], ), ], ), state="replaced", ) ) commands = [ "set protocols ospfv3 redistribute bgp", "set protocols ospfv3 parameters router-id '192.0.2.10'", "delete protocols ospfv3 area 12 range 2001:db11::/32", "set protocols ospfv3 area 12 range 2001:db10::/32", "delete protocols ospfv3 area 13", "set protocols ospfv3 area '14'", "set protocols ospfv3 area 14 range 2001:db40::/32", ] self.execute_module(changed=True, commands=commands) def test_vyos_ospfv3_replaced_idem(self): set_module_args( dict( config=dict( areas=[ dict( area_id="12", export_list="export1", import_list="import1", range=[ dict(address="2001:db11::/32"), dict(address="2001:db22::/32"), dict(address="2001:db33::/32"), ], ), dict( area_id="13", - range=[dict(address="2001:db44::/32"),], + range=[dict(address="2001:db44::/32")], ), ], ), state="replaced", ) ) self.execute_module(changed=False, commands=[]) def test_vyos_ospfv3_deleted_no_config(self): set_module_args(dict(config=None, state="deleted")) commands = ["delete protocols ospfv3"] self.execute_module(changed=True, commands=commands) def test_vyos_ospfv3_gathered(self): set_module_args(dict(state="gathered")) result = self.execute_module( changed=False, filename="vyos_ospfv3_config.cfg" ) gather_dict = { "areas": [ { "area_id": "12", "export_list": "export1", "import_list": "import1", "range": [ {"address": "2001:db11::/32"}, {"address": "2001:db22::/32"}, {"address": "2001:db33::/32"}, ], }, {"area_id": "13", "range": [{"address": "2001:db44::/32"}]}, ], } self.assertEqual(sorted(gather_dict), sorted(result["gathered"])) def test_vyos_ospfv3_parsed(self): parsed_str = """set protocols ospfv3 area 2 export-list 'export1' set protocols ospfv3 area 2 import-list 'import1' set protocols ospfv3 area 2 range '2001:db10::/32' set protocols ospfv3 area 2 range '2001:db20::/32' set protocols ospfv3 area 2 range '2001:db30::/32' set protocols ospfv3 area 3 range '2001:db40::/32' set protocols ospfv3 parameters router-id '192.0.2.10' set protocols ospfv3 redistribute 'bgp'""" set_module_args(dict(running_config=parsed_str, state="parsed")) result = self.execute_module(changed=False) parsed_dict = { "areas": [ { "area_id": "2", "export_list": "export1", "import_list": "import1", "range": [ {"address": "2001:db10::/32"}, {"address": "2001:db20::/32"}, {"address": "2001:db30::/32"}, ], }, {"area_id": "3", "range": [{"address": "2001:db40::/32"}]}, ], "parameters": {"router_id": "192.0.2.10"}, "redistribute": [{"route_type": "bgp"}], } self.assertEqual(sorted(parsed_dict), sorted(result["parsed"])) def test_vyos_ospfv3_rendered(self): set_module_args( dict( config=dict( redistribute=[dict(route_type="bgp")], parameters=dict(router_id="192.0.2.10"), areas=[ dict( area_id="2", export_list="export1", import_list="import1", range=[ dict(address="2001:db10::/32"), dict(address="2001:db20::/32"), dict(address="2001:db30::/32"), ], ), dict( area_id="3", - range=[dict(address="2001:db40::/32"),], + range=[dict(address="2001:db40::/32")], ), ], ), state="rendered", ) ) commands = [ "set protocols ospfv3 redistribute bgp", "set protocols ospfv3 parameters router-id '192.0.2.10'", "set protocols ospfv3 area 2 range 2001:db10::/32", "set protocols ospfv3 area 2 range 2001:db20::/32", "set protocols ospfv3 area 2 range 2001:db30::/32", "set protocols ospfv3 area '2'", "set protocols ospfv3 area 2 export-list export1", "set protocols ospfv3 area 2 import-list import1", "set protocols ospfv3 area '3'", "set protocols ospfv3 area 3 range 2001:db40::/32", ] result = self.execute_module(changed=False) self.assertEqual( sorted(result["rendered"]), sorted(commands), result["rendered"] ) diff --git a/tox.ini b/tox.ini index 3a6d05a..4aaa22a 100644 --- a/tox.ini +++ b/tox.ini @@ -1,32 +1,32 @@ [tox] minversion = 1.4.2 envlist = linters skipsdist = True [testenv] deps = -r{toxinidir}/requirements.txt -r{toxinidir}/test-requirements.txt [testenv:black] install_command = pip install {opts} {packages} commands = black -v -l79 {toxinidir} [testenv:linters] install_command = pip install {opts} {packages} commands = black -v -l79 --check {toxinidir} flake8 {posargs} yamllint -s . [testenv:venv] commands = {posargs} [flake8] # E123, E125 skipped as they are invalid PEP-8. show-source = True -ignore = E123,E125,E231,E402,W503 +ignore = E123,E125,E402,W503 max-line-length = 160 builtins = _ exclude = .git,.tox,tests/unit/compat/