diff --git a/plugins/module_utils/network/vyos/config/vrrp/vrrp.old b/plugins/module_utils/network/vyos/config/vrrp/vrrp.old new file mode 100644 index 00000000..f6311ad7 --- /dev/null +++ b/plugins/module_utils/network/vyos/config/vrrp/vrrp.old @@ -0,0 +1,630 @@ +# +# -*- coding: utf-8 -*- +# Copyright 2021 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 + +""" +The vyos_vrrp config file. +It is in this file where the current configuration (as dict) +is compared to the provided configuration (as dict) and the command set +necessary to bring the current configuration to its desired end-state is +created. +""" + +from copy import deepcopy + +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.resource_module import ( + ResourceModule, +) + +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.facts import Facts +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.rm_templates.vrrp import ( + VrrpTemplate, +) +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.utils import combine +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.version import ( + LooseVersion, +) +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.vyos import get_os_version + + +class Vrrp(ResourceModule): + """ + The vyos_vrrp config class + """ + + def __init__(self, module): + super(Vrrp, self).__init__( + empty_fact_val={}, + facts_module=Facts(module), + module=module, + resource="vrrp", + tmplt=VrrpTemplate(), + ) + self.parsers = [ + "disable", + ] + + def _validate_template(self): + version = get_os_version(self._module) + if LooseVersion(version) >= LooseVersion("1.4"): + self._tmplt = VrrpTemplate() + else: + self._module.fail_json(msg="VRRP is not supported in this version of VyOS") + + def parse(self): + """override parse to check template""" + self._validate_template() + return super().parse() + + def get_parser(self, name): + """get_parsers""" + self._validate_template() + return super().get_parser(name) + + def execute_module(self): + """Execute the module + + :rtype: A dictionary + :returns: The result from module execution + """ + + if self.state not in ["parsed", "gathered", "purged"]: + self.generate_commands() + self.run_commands() + + if self.state == "purged": + self.commands = ["delete high-availability"] + self.run_commands() + + return self.result + + def generate_commands(self): + """Generate configuration commands to send based on + want, have and desired state. + """ + wantd = {} + haved = {} + wantd = deepcopy(self.want) + haved = deepcopy(self.have) + # self._module.fail_json(msg="Generate commands - want: " + str(self.want) + " (((()))) have: " + str(haved)) + for entry in wantd, haved: + # self._module.fail_json(msg="Before normalize_vrrp_groups - entry: " + str(entry)) + self._vrrp_groups_list_to_dict(entry) + self._virtual_servers_list_to_dict(entry) + self._vrrp_sync_groups_list_to_dict(entry) + self._normalize_lists(entry) + # self._module.fail_json(msg="Normalise - want: " + str(wantd) + " (((()))) have: " + str(haved)) + + keys = set(wantd) | set(haved) + + for k in keys: + want = wantd.get(k, {}) + have = haved.get(k, {}) + + if k == "vrrp": + if self.state in ["merged"]: + want = combine(have, want, recursive=True) + self._compare_vrrp(want, have) + if k == "virtual_servers": + if self.state in ["merged"]: + want = combine(have, want, recursive=True) + self._compare_vsrvs(want, have) + if self.state == "deleted" and k == "disable": + want = False + self.compare( + parsers=self.parsers, + want={k: want}, + have={k: have}, + ) + + # for k, want in wantd.items(): + # if k == "vrrp": + # if self.state in ["merged"]: + # want = combine(haved.get(k, {}), want, recursive=True) + # self._compare_vrrp(want, haved.get(k, {})) + # if k == "virtual_servers": + # if self.state in ["merged"]: + # want = combine(haved.get(k, {}), want, recursive=True) + # self._compare_vsrvs(want, haved.get(k, {})) + # self.compare( + # parsers=self.parsers, + # want={k: want}, + # have={k: haved.pop(k, {})}, + # ) + self.commands = list(dict.fromkeys(self.commands)) + # self._module.fail_json(msg=self.commands) + + def _compare_vsrvs(self, want, have): + """Compare virtual servers of VRRP""" + vs_parsers = [ + # "virtual_servers", + "virtual_servers.address", + "virtual_servers.algorithm", + "virtual_servers.delay_loop", + "virtual_servers.forward_method", + "virtual_servers.persistence_timeout", + "virtual_servers.fwmark", + "virtual_servers.port", + "virtual_servers.protocol", + "virtual_servers.real_server.port", + "virtual_servers.real_server.health_check_script", + "virtual_servers.real_server.connection_timeout", + ] + pairs = [] + hlist = self._extract_named_leafs(have) + wlist = self._extract_named_leafs(want) + # self._module.fail_json(msg="Want: " + str(wlist) + "&&&&&&&&&&&&&&&&&&&& have: " + str(hlist)) + + if self.state in ["replaced", "deleted", "overridden"]: + for hdict in hlist: + wdict = self._find_matching_by_path(hdict, wlist) + if self.state == "deleted" and wdict: + wdict = {} + else: + continue + pairs.append((wdict, hdict)) + self.compare( + parsers=vs_parsers, + want={"virtual_servers": wdict}, + have={"virtual_servers": hdict}, + ) + # self._module.fail_json(msg=pairs) + if self.state in ["merged", "replaced"]: + for wdict in wlist: + hdict = self._find_matching_by_path(wdict, hlist) + pairs.append((wdict, hdict)) + self.compare( + parsers=vs_parsers, + want={"virtual_servers": wdict}, + have={"virtual_servers": hdict}, + ) + # self._module.fail_json(msg=pairs) + + def _compare_vrrp(self, want, have): + """Compare the instances of VRRP""" + vrrp_parsers = [ + "vrrp.snmp", + "vrrp.global_parameters", + "vrrp.global_parameters.garp", + "vrrp.groups", + "vrrp.groups.disable", + "vrrp.groups.no_preempt", + "vrrp.groups.rfc3768_compatibility", + "vrrp.groups.excluded_address", + "vrrp.groups.garp", + "vrrp.groups.authentication", + "vrrp.groups.transition_script", + "vrrp.groups.health_check", + "vrrp.groups.track.interface", + "vrrp.groups.track.exclude_vrrp_interface", + "vrrp.sync_groups.member", + "vrrp.sync_groups.transition_script", + "vrrp.sync_groups.health_check", + ] + + pairs = [] + + # self._module.fail_json(msg="Compare VRRP - want: " + str(want) + " (((()))) have: " + str(have)) + + if have.get("snmp") == "enabled" and want.get("snmp") != "enabled": + self.commands.append("delete high-availability vrrp snmp") + + hlist = self._extract_leaf_items(have) + wlist = self._extract_leaf_items(want) + + # self._module.fail_json(msg="leaf VRRP - want: " + str(wlist) + " (((()))) have: " + str(hlist)) + + if self.state in ["replaced", "deleted", "overridden"]: + for hdict in hlist: + wdict = self._find_matching_by_path(hdict, wlist) + if self.state == "deleted" and wdict: + wdict = {} + else: + continue + pairs.append((wdict, hdict)) + self.compare(parsers=vrrp_parsers, want={"vrrp": wdict}, have={"vrrp": hdict}) + # self._module.fail_json(msg=pairs) + + if self.state in ["merged", "replaced"]: + for wdict in wlist: + hdict = self._find_matching_by_path(wdict, hlist) + pairs.append((wdict, hdict)) + self.compare(parsers=vrrp_parsers, want={"vrrp": wdict}, have={"vrrp": hdict}) + + + # self._module.fail_json(msg=pairs) + + def _vrrp_groups_list_to_dict(self, data): + + vrrp = data.get("vrrp", {}) + groups = vrrp.get("groups") + + if not groups: + return data + if isinstance(groups, dict): + return data + if isinstance(groups, list): + new_groups = {} + for item in groups: + name = item.get("name") + if not name: + continue + new_groups[name] = item + + data["vrrp"]["groups"] = new_groups + return data + return data + + def _vrrp_sync_groups_list_to_dict(self, data): + vrrp = data.get("vrrp", {}) + groups = vrrp.get("sync_groups") + + if not groups: + return data + if isinstance(groups, dict): + return data + if isinstance(groups, list): + new_groups = {} + for item in groups: + name = item.get("name") + if not name: + continue + new_groups[name] = item + + data["vrrp"]["sync_groups"] = new_groups + return data + return data + + def _virtual_servers_list_to_dict(self, data): + + vss = data.get("virtual_servers") + if not vss: + return data + + if isinstance(vss, dict): + for vs in vss.items(): + rs = vs.get("real_server") + if isinstance(rs, list): + vs["real_server"] = { + item["address"]: item + for item in rs + if isinstance(item, dict) and item.get("address") + } + return data + + if isinstance(vss, list): + new_vss = {} + for vs in vss: + if not isinstance(vs, dict): + continue + name = vs.get("name") + if not name: + continue + rs = vs.get("real_server") + if isinstance(rs, list): + vs["real_server"] = { + item["address"]: item + for item in rs + if isinstance(item, dict) and item.get("address") + } + + new_vss[name] = vs + + data["virtual_servers"] = new_vss + return data + + return data + + def _extract_leaf_items(self, data, path=None, parent_name=None): + path = path or [] + results = [] + + if isinstance(data, dict): + current_name = data.get("name", parent_name) + + for k, v in data.items(): + if k == "name" or (k == "snmp" and v == "disabled"): + continue + results.extend(self._extract_leaf_items(v, path + [k], current_name)) + return results + + leaf_key = path[-1] + top_key = path[0] + + if top_key in ["groups", "sync_groups"]: + subkeys = path[2:] + else: + subkeys = path[1:] + + nested = {leaf_key: data} + + for p in reversed(subkeys[:-1]): + nested = {p: nested} + if parent_name: + out = {top_key: {"name": parent_name}} + out[top_key].update(nested) + else: + out = {top_key: nested} + + results.append(out) + return results + + # def _extract_named_leafs(self, data, parent_name=None, prefix_key=None): + # results = [] + + # if prefix_key == "real_server" and isinstance(data, dict): + # for server_name, server_data in data.items(): + # if isinstance(server_data, dict): + # results.append( + # { + # "name": parent_name, + # "real_server": server_data, + # }, + # ) + # return results + + # if isinstance(data, dict): + # current_name = data.get("name", parent_name) + + # for k, v in data.items(): + # if k == "name": + # continue + # leaves = self._extract_named_leafs(v, current_name, k) + # results.extend(leaves) + # return results + # return [ + # { + # "name": parent_name, + # prefix_key: data, + # }, + # ] + + def _lookup_by_path(self, want_item, have_list): + """ + Find matching object in have_list by structural path + name (if present). + Ignore values. Return {} if not found. + """ + + def extract_signature(d): + sig = [] + + while isinstance(d, dict) and d: + k = next(iter(d)) + sig.append(k) + d = d[k] + + if isinstance(d, dict) and "name" in d: + sig.append(("name", d["name"])) + return tuple(sig) + + want_sig = extract_signature(want_item) + + for obj in have_list: + if extract_signature(obj) == want_sig: + return obj + + return {} + + def _find_matching_by_path(self, want_item, have_list): + """ + Match extracted leaf dicts from _extract_named_leafs(). + Supports both container-style and flat-style leaves. + """ + + # def build_sig(item): + # if not isinstance(item, dict) or not item: + # return () + + # if len(item) == 1: + # top = next(iter(item)) + # node = item[top] + # sig = [top] + + # if isinstance(node, dict): + # if "name" in node: + # sig.append(("name", node["name"])) + + # for k, v in node.items(): + # if k == "name": + # continue + # sig.append(k) + + # if isinstance(v, dict): + # if k == "real_server" and "address" in v: + # sig.append(("address", v["address"])) + # else: + # sig.append(next(iter(v))) + # break + + # return tuple(sig) + + # sig = [] + # if "name" in item: + # sig.append(("name", item["name"])) + + # for k, v in item.items(): + # if k != "name": + # sig.append(k) + # if isinstance(v, dict) and k == "real_server" and "address" in v: + # sig.append(("address", v["address"])) + # break + + # return tuple(sig) + + def build_sig(item): + if not isinstance(item, dict) or not item: + return () + + sig = [] + + if "name" in item: + sig.append(("name", item["name"])) + + for k, v in item.items(): + if k == "name": + continue + + sig.append(k) + + # existing real_server logic (UNCHANGED) + if isinstance(v, dict) and k == "real_server" and "address" in v: + sig.append(("address", v["address"])) + for leaf_key in v: + if leaf_key != "address": + sig.append(("leaf", leaf_key)) + break + + # 🔧 FIX: walk down generic nested dicts until leaf + elif isinstance(v, dict): + cur = v + while isinstance(cur, dict) and cur: + leaf_key = next(iter(cur)) + sig.append(leaf_key) + cur = cur[leaf_key] + + break + + return tuple(sig) + + + + # def build_sig(item): + # if not isinstance(item, dict) or not item: + # return () + + # sig = [] + + # if "name" in item: + # sig.append(("name", item["name"])) + + # for k, v in item.items(): + # if k == "name": + # continue + + # sig.append(k) + + # if isinstance(v, dict) and k == "real_server" and "address" in v: + # sig.append(("address", v["address"])) + + # # ✅ include leaf identity + # for leaf_key in v: + # if leaf_key != "address": + # sig.append(("leaf", leaf_key)) + # break + # break + + # return tuple(sig) + + sig_want = build_sig(want_item) + + for obj in have_list: + if build_sig(obj) == sig_want: + return obj + + return {} + + def _normalize_lists(self, node): + """ + Recursively normalize all lists inside a dict or list. + All lists are sorted to ensure consistent ordering for comparison. + """ + if isinstance(node, dict): + for k, v in node.items(): + if isinstance(v, list): + # sort the list if it contains only scalars + if all(not isinstance(i, (dict, list)) for i in v): + node[k] = sorted(v) + else: + # recurse into each item if the list contains dicts + for item in v: + self._normalize_lists(item) + elif isinstance(v, dict): + self._normalize_lists(v) + elif isinstance(node, list): + for item in node: + self._normalize_lists(item) + + def project_structure(self, have, want): + """ + Project the structure of `have` onto `want`. + + - Existing values in `want` are preserved + - Missing paths are created with empty values + """ + if not isinstance(have, dict): + return want + + if want is None or not isinstance(want, dict): + want = {} + + for key, have_val in have.items(): + if key not in want: + want[key] = self.empty_like(have_val) + else: + want[key] = self.project_structure(have_val, want[key]) + + return want + + def empty_like(self, value): + if isinstance(value, dict): + return {} + if isinstance(value, list): + return [] + return None + + def _extract_named_leafs(self, data, parent_name=None, prefix_key=None): + results = [] + + if prefix_key == "real_server" and isinstance(data, dict): + for d, server_data in data.items(): + if not isinstance(server_data, dict): + continue + + address = server_data.get("address") + if not address: + continue + + for k, v in server_data.items(): + if k == "address": + continue + + results.append( + { + "name": parent_name, + "real_server": { + "address": address, + k: v, + }, + }, + ) + return results + + # Generic dict handling + if isinstance(data, dict): + current_name = data.get("name", parent_name) + + for k, v in data.items(): + if k == "name": + continue + + results.extend( + self._extract_named_leafs(v, current_name, k), + ) + + return results + + # Primitive leaf + return [ + { + "name": parent_name, + prefix_key: data, + }, + ] diff --git a/tests/integration/targets/vyos_vrrp/defaults/main.yaml b/tests/integration/targets/vyos_vrrp/defaults/main.yaml new file mode 100644 index 00000000..164afead --- /dev/null +++ b/tests/integration/targets/vyos_vrrp/defaults/main.yaml @@ -0,0 +1,3 @@ +--- +testcase: "[^_].*" +test_items: [] diff --git a/tests/integration/targets/vyos_vrrp/tasks/cli.yaml b/tests/integration/targets/vyos_vrrp/tasks/cli.yaml new file mode 100644 index 00000000..daccf720 --- /dev/null +++ b/tests/integration/targets/vyos_vrrp/tasks/cli.yaml @@ -0,0 +1,20 @@ +--- +- name: Collect all cli test cases + ansible.builtin.find: + paths: "{{ role_path }}/tests/cli" + patterns: "{{ testcase }}.yaml" + use_regex: true + register: test_cases + delegate_to: localhost + +- name: Set test_items + ansible.builtin.set_fact: + test_items: "{{ test_cases.files | map(attribute='path') | list }}" + +- name: Run test case (connection=ansible.netcommon.network_cli) + ansible.builtin.include_tasks: "{{ test_case_to_run }}" + vars: + ansible_connection: ansible.netcommon.network_cli + with_items: "{{ test_items }}" + loop_control: + loop_var: test_case_to_run diff --git a/tests/integration/targets/vyos_vrrp/tasks/main.yaml b/tests/integration/targets/vyos_vrrp/tasks/main.yaml new file mode 100644 index 00000000..e6378581 --- /dev/null +++ b/tests/integration/targets/vyos_vrrp/tasks/main.yaml @@ -0,0 +1,5 @@ +--- +- name: Run CLI tests + ansible.builtin.include_tasks: cli.yaml + tags: + - network_cli diff --git a/tests/integration/targets/vyos_vrrp/tests/cli/_add_dummy_scripts.yaml b/tests/integration/targets/vyos_vrrp/tests/cli/_add_dummy_scripts.yaml new file mode 100644 index 00000000..63264db4 --- /dev/null +++ b/tests/integration/targets/vyos_vrrp/tests/cli/_add_dummy_scripts.yaml @@ -0,0 +1,6 @@ +--- +- name: Create dummy scripts for VRRP tests + vyos.vyos.vyos_command: + commands: touch /var/tmp/script.sh && chmod +x /var/tmp/script.sh + vars: + ansible_connection: ansible.netcommon.network_cli diff --git a/tests/integration/targets/vyos_vrrp/tests/cli/_parsed.cfg b/tests/integration/targets/vyos_vrrp/tests/cli/_parsed.cfg new file mode 100644 index 00000000..ddafa153 --- /dev/null +++ b/tests/integration/targets/vyos_vrrp/tests/cli/_parsed.cfg @@ -0,0 +1,66 @@ +set high-availability virtual-server s1 address 10.10.10.1 +set high-availability virtual-server s1 algorithm round-robin +set high-availability virtual-server s1 delay-loop 60 +set high-availability virtual-server s1 forward-method direct +set high-availability virtual-server s1 fwmark 10 +set high-availability virtual-server s1 persistence-timeout 30 +set high-availability virtual-server s1 protocol tcp +set high-availability virtual-server s1 real-server 10.10.50.1 health-check script /var/tmp/script.sh +set high-availability virtual-server s1 real-server 10.10.50.1 port 443 +set high-availability virtual-server s2 address 10.10.10.2 +set high-availability virtual-server s2 port 81 +set high-availability virtual-server s2 real-server real1 connection-timeout 5 +set high-availability virtual-server s2 real-server real1 port 8081 +set high-availability virtual-server s2 real-server real2 port 8080 +set high-availability vrrp global-parameters startup-delay 31 +set high-availability vrrp global-parameters version 3 +set high-availability vrrp global-parameters garp interval 30 +set high-availability vrrp global-parameters garp master-delay 10 +set high-availability vrrp global-parameters garp master-refresh 100 +set high-availability vrrp global-parameters garp master-refresh-repeat 200 +set high-availability vrrp global-parameters garp master-repeat 5 +set high-availability vrrp group g1 address 1.1.1.1 +set high-availability vrrp group g1 advertise-interval 10 +set high-availability vrrp group g1 description 'Group 1' +set high-availability vrrp group g1 disable +set high-availability vrrp group g1 interface eth2 +set high-availability vrrp group g1 no-preempt +set high-availability vrrp group g1 peer-address 192.168.1.3 +set high-availability vrrp group g1 priority 100 +set high-availability vrrp group g1 rfc3768-compatibility +set high-availability vrrp group g1 vrid 20 +set high-availability vrrp group g1 authentication password testpass +set high-availability vrrp group g1 authentication type plaintext-password +set high-availability vrrp group g1 excluded-address 192.168.1.7 interface eth3 +set high-availability vrrp group g1 excluded-address 192.168.1.8 +set high-availability vrrp group g1 garp interval 20 +set high-availability vrrp group g1 garp master-delay 5 +set high-availability vrrp group g1 garp master-refresh 50 +set high-availability vrrp group g1 garp master-refresh-repeat 100 +set high-availability vrrp group g1 garp master-repeat 3 +set high-availability vrrp group g1 track exclude-vrrp-interface +set high-availability vrrp group g1 track interface eth0 +set high-availability vrrp group g1 track interface eth1 +set high-availability vrrp group g1 transition-script backup /var/tmp/script.sh +set high-availability vrrp group g1 transition-script fault /var/tmp/script.sh +set high-availability vrrp group g1 transition-script master /var/tmp/script.sh +set high-availability vrrp group g1 transition-script stop /var/tmp/script.sh +set high-availability vrrp group g2 address 2.2.2.2 +set high-availability vrrp group g2 description 'Group 2' +set high-availability vrrp group g2 interface eth1 +set high-availability vrrp group g2 vrid 11 +set high-availability vrrp group g2 health-check failure-count 5 +set high-availability vrrp group g2 health-check interval 15 +set high-availability vrrp group g2 health-check ping 192.168.1.100 +set high-availability vrrp group g2 health-check script /var/tmp/script.sh +set high-availability vrrp group g2 hello-source-address 2.2.2.2 +set high-availability vrrp sync-group sg1 health-check failure-count 3 +set high-availability vrrp sync-group sg1 health-check interval 10 +set high-availability vrrp sync-group sg1 health-check ping 192.168.2.100 +set high-availability vrrp sync-group sg1 health-check script /var/tmp/script.sh +set high-availability vrrp sync-group sg1 member g1 +set high-availability vrrp sync-group sg1 member g2 +set high-availability vrrp sync-group sg1 transition-script backup /var/tmp/script.sh +set high-availability vrrp sync-group sg1 transition-script fault /var/tmp/script.sh +set high-availability vrrp sync-group sg1 transition-script master /var/tmp/script.sh +set high-availability vrrp sync-group sg1 transition-script stop /var/tmp/script.sh diff --git a/tests/integration/targets/vyos_vrrp/tests/cli/_populate_config.yaml b/tests/integration/targets/vyos_vrrp/tests/cli/_populate_config.yaml new file mode 100644 index 00000000..0bd5d904 --- /dev/null +++ b/tests/integration/targets/vyos_vrrp/tests/cli/_populate_config.yaml @@ -0,0 +1,10 @@ +--- +- ansible.builtin.include_tasks: _remove_config.yaml + +- ansible.builtin.include_tasks: _add_dummy_scripts.yaml + +- name: Setup initial VRRP configuration + vyos.vyos.vyos_config: + lines: "{{ populate_config}}" + vars: + ansible_connection: ansible.netcommon.network_cli diff --git a/tests/integration/targets/vyos_vrrp/tests/cli/_remove_config.yaml b/tests/integration/targets/vyos_vrrp/tests/cli/_remove_config.yaml new file mode 100644 index 00000000..5f367d77 --- /dev/null +++ b/tests/integration/targets/vyos_vrrp/tests/cli/_remove_config.yaml @@ -0,0 +1,15 @@ +--- +- name: Remove pre-existing vrrp config + vyos.vyos.vyos_vrrp: + config: + state: purged + ignore_errors: true + vars: + ansible_connection: ansible.netcommon.network_cli + +- name: Remove dummy scripts for VRRP tests + vyos.vyos.vyos_command: + commands: + - rm -f /var/tmp/script.sh + vars: + ansible_connection: ansible.netcommon.network_cli diff --git a/tests/integration/targets/vyos_vrrp/tests/cli/deleted.yaml b/tests/integration/targets/vyos_vrrp/tests/cli/deleted.yaml new file mode 100644 index 00000000..3077bd8f --- /dev/null +++ b/tests/integration/targets/vyos_vrrp/tests/cli/deleted.yaml @@ -0,0 +1,37 @@ +--- +- debug: + msg: START vyos_ntp_global deleted integration tests on connection={{ansible_connection }} + +- include_tasks: _remove_config.yaml + +- include_tasks: _populate_config.yaml + +- block: + - name: Delete the provided configuration + register: result + vyos.vyos.vyos_ntp_global: &id001 + config: + state: deleted + + - name: Assert that before dicts were correctly generated + assert: + that: + - result.changed == true + - result.commands == deleted.commands + + - name: Assert that the after dicts were correctly generated + assert: + that: + - result.after == {} + + - name: Delete the existing configuration with the provided running configuration (IDEMPOTENT) + register: result + vyos.vyos.vyos_ntp_global: *id001 + + - name: Assert that the previous task was idempotent + assert: + that: + - result['changed'] == false + + always: + - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_vrrp/tests/cli/empty_config.yaml b/tests/integration/targets/vyos_vrrp/tests/cli/empty_config.yaml new file mode 100644 index 00000000..94d44707 --- /dev/null +++ b/tests/integration/targets/vyos_vrrp/tests/cli/empty_config.yaml @@ -0,0 +1,58 @@ +--- +- debug: + msg: START vyos_vrrp empty_config integration tests on connection={{ ansible_connection }} + +- name: Merged with empty config should give appropriate error message + register: result + ignore_errors: true + vyos.vyos.vyos_vrrp: + config: + state: merged + +- assert: + that: + - result.msg == 'value of config parameter must not be empty for state merged' + +- name: Replaced with empty config should give appropriate error message + register: result + ignore_errors: true + vyos.vyos.vyos_vrrp: + config: + state: replaced + +- assert: + that: + - result.msg == 'value of config parameter must not be empty for state replaced' + +- name: Overridden with empty config should give appropriate error message + register: result + ignore_errors: true + vyos.vyos.vyos_vrrp: + config: + state: overridden + +- assert: + that: + - result.msg == 'value of config parameter must not be empty for state overridden' + +- name: Parsed with empty running_config should give appropriate error message + register: result + ignore_errors: true + vyos.vyos.vyos_vrrp: + running_config: + state: parsed + +- assert: + that: + - result.msg == 'value of running_config parameter must not be empty for state parsed' + +- name: Rendered with empty config should give appropriate error message + register: result + ignore_errors: true + vyos.vyos.vyos_vrrp: + config: + state: rendered + +- assert: + that: + - result.msg == 'value of config parameter must not be empty for state rendered' diff --git a/tests/integration/targets/vyos_vrrp/tests/cli/gathered.yaml b/tests/integration/targets/vyos_vrrp/tests/cli/gathered.yaml new file mode 100644 index 00000000..eb060bef --- /dev/null +++ b/tests/integration/targets/vyos_vrrp/tests/cli/gathered.yaml @@ -0,0 +1,25 @@ +--- +- debug: + msg: START vyos_vrrp gathered integration tests on connection={{ ansible_connection }} + +- include_tasks: _remove_config.yaml + +- include_tasks: _populate_config.yaml + +- block: + - name: Gather config from the device in structured format. + register: result + vyos.vyos.vyos_vrrp: + state: gathered + + - vyos.vyos.vyos_facts: + gather_network_resources: vrrp + + - name: Assert that facts are correctly generated + assert: + that: + - result.changed == false + - result.gathered|symmetric_difference(ansible_facts['network_resources']['vrrp']) == [] + + always: + - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_vrrp/tests/cli/merged.yaml b/tests/integration/targets/vyos_vrrp/tests/cli/merged.yaml new file mode 100644 index 00000000..c62a5005 --- /dev/null +++ b/tests/integration/targets/vyos_vrrp/tests/cli/merged.yaml @@ -0,0 +1,42 @@ +--- +- debug: + msg: START vyos_ntp_global merged integration tests on connection={{ ansible_connection }} + +- include_tasks: _remove_config.yaml + +- block: + - name: Merge the provided configuration with the existing running configuration + register: result + vyos.vyos.vyos_ntp_global: &id001 + config: "{{ merged.config }}" + state: merged + + - vyos.vyos.vyos_facts: + gather_network_resources: ntp_global + + - assert: + that: + - result.commands|length == 7 + - result.changed == true + - result.commands|symmetric_difference(merged.commands) == [] + - result.after == ansible_facts['network_resources']['ntp_global'] + - result.after == merged.after + + - name: Assert that before dicts were correctly generated + assert: + that: + - result.before == {} + + - name: Merge the provided configuration with the existing running configuration (IDEMPOTENT) + register: result + vyos.vyos.vyos_ntp_global: *id001 + + - name: Assert that the previous task was idempotent + assert: + that: + - result['changed'] == false + - result['commands'] == [] + - result['before'] == ansible_facts['network_resources']['ntp_global'] + + always: + - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_vrrp/tests/cli/overridden.yaml b/tests/integration/targets/vyos_vrrp/tests/cli/overridden.yaml new file mode 100644 index 00000000..0911869b --- /dev/null +++ b/tests/integration/targets/vyos_vrrp/tests/cli/overridden.yaml @@ -0,0 +1,60 @@ +--- +- debug: + msg: START vyos_ntp_global overridden integration tests on connection={{ ansible_connection }} + +- include_tasks: _remove_config.yaml + +- include_tasks: _populate_config.yaml + +- block: + - name: Override the existing configuration with the provided running configuration + register: result + vyos.vyos.vyos_ntp_global: &id001 + config: + servers: + - server: server1.example.com + options: + - pool + - prefer + + - server: server2.example.com + options: + - noselect + - prefer + + - server: server-add.example.com + options: + - prefer + state: overridden + + - vyos.vyos.vyos_facts: + gather_network_resources: ntp_global + + - name: debug result.after and overridden.after + debug: + msg: "{{ result.after }} != {{ overridden.after }}" + when: result.after != overridden.after + + - name: debug ansible_facts['network_resources']['ntp_global'] + debug: + msg: "{{ ansible_facts['network_resources']['ntp_global'] }}" + when: result.after != ansible_facts['network_resources']['ntp_global'] + + - name: Verify that the configuration was correctly overridden + assert: + that: + - result.changed == true + - result.after == ansible_facts['network_resources']['ntp_global'] + - result.after == overridden.after + + - name: Override the existing configuration with the provided running configuration (IDEMPOTENT) + register: result + vyos.vyos.vyos_ntp_global: *id001 + + - name: Assert that the previous task was idempotent + assert: + that: + - result['changed'] == false + + always: + - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_vrrp/tests/cli/parsed.yaml b/tests/integration/targets/vyos_vrrp/tests/cli/parsed.yaml new file mode 100644 index 00000000..9dc6e1ab --- /dev/null +++ b/tests/integration/targets/vyos_vrrp/tests/cli/parsed.yaml @@ -0,0 +1,15 @@ +--- +- debug: + msg: START vyos_vrrp parsed integration tests on connection={{ ansible_connection }} + +- name: Provide the running configuration for parsing (config to be parsed) + register: result + vyos.vyos.vyos_vrrp: + running_config: "{{ lookup('file', '_parsed.cfg') }}" + state: parsed + +- name: Assert that config was correctly parsed + assert: + that: + - result.changed == false + - result.parsed == merged.before diff --git a/tests/integration/targets/vyos_vrrp/tests/cli/rendered.yaml b/tests/integration/targets/vyos_vrrp/tests/cli/rendered.yaml new file mode 100644 index 00000000..e5de57e3 --- /dev/null +++ b/tests/integration/targets/vyos_vrrp/tests/cli/rendered.yaml @@ -0,0 +1,17 @@ +--- +- debug: + msg: START vyos_ntp_global rendered integration tests on connection={{ ansible_connection }} + +- include_tasks: _remove_config.yaml + +- block: + - name: Structure provided configuration into device specific commands + register: result + vyos.vyos.vyos_ntp_global: + config: "{{ merged.config }}" + state: rendered + + - assert: + that: + - result.changed == false + - result.rendered|symmetric_difference(merged.commands) == [] diff --git a/tests/integration/targets/vyos_vrrp/tests/cli/replaced.yaml b/tests/integration/targets/vyos_vrrp/tests/cli/replaced.yaml new file mode 100644 index 00000000..a78d7f55 --- /dev/null +++ b/tests/integration/targets/vyos_vrrp/tests/cli/replaced.yaml @@ -0,0 +1,39 @@ +--- +- debug: + msg: START vyos_ntp_global replaced integration tests on connection={{ ansible_connection }} + +- include_tasks: _remove_config.yaml + +- include_tasks: _populate_config.yaml + +- block: + - name: Replace the provided configuration with the existing running configuration + register: result + vyos.vyos.vyos_ntp_global: &id001 + config: + servers: + - server: server-new.example.com + options: + - prefer + state: replaced + + - vyos.vyos.vyos_facts: + gather_network_resources: ntp_global + + - assert: + that: + - result.changed == true + - result.after == ansible_facts['network_resources']['ntp_global'] + - result.after == replaced.after + + - name: Replace the provided configuration with the existing running configuration (IDEMPOTENT) + register: result + vyos.vyos.vyos_ntp_global: *id001 + + - name: Assert that the previous task was idempotent + assert: + that: + - result['changed'] == false + + always: + - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_vrrp/tests/cli/rtt.yaml b/tests/integration/targets/vyos_vrrp/tests/cli/rtt.yaml new file mode 100644 index 00000000..d089aea0 --- /dev/null +++ b/tests/integration/targets/vyos_vrrp/tests/cli/rtt.yaml @@ -0,0 +1,48 @@ +--- +- debug: + msg: START vyos_vrrp rtt integration tests on connection={{ ansible_connection }} + +- include_tasks: _remove_config.yaml + +- block: + - name: RTT Merge the provided configuration with the existing running configuration + register: baseconfig + vyos.vyos.vyos_vrrp: + config: "{{ rtt.config }}" + state: merged + + - vyos.vyos.vyos_facts: + gather_network_resources: vrrp + + - assert: + that: + - baseconfig.changed == true + - baseconfig.commands|symmetric_difference(rtt.commands) == [] + - baseconfig.after|symmetric_difference(ansible_facts['network_resources']['vrrp']) == [] + + - name: RTT Merge the existing configuration with the provided running configuration + register: result + vyos.vyos.vyos_vrrp: + config: + servers: + - server: server1 + options: + - pool + - prefer + + - server: server2 + options: + - noselect + + - name: RTT Revert back to base config using facts round trip + register: revert + vyos.vyos.vyos_vrrp: + config: "{{ ansible_facts['network_resources']['vrrp'] }}" + state: overridden + + - name: RTT Assert that config was reverted + assert: + that: baseconfig.after == revert.after + + always: + - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_vrrp/vars/main.yaml b/tests/integration/targets/vyos_vrrp/vars/main.yaml new file mode 100644 index 00000000..755b7f56 --- /dev/null +++ b/tests/integration/targets/vyos_vrrp/vars/main.yaml @@ -0,0 +1,168 @@ +--- +merged: + before: + disable: false + virtual_servers: + - name: s1 + address: 10.10.10.1 + algorithm: round-robin + delay_loop: 60 + forward_method: direct + fwmark: 10 + persistence_timeout: 30 + protocol: tcp + real_server: + - address: 10.10.50.1 + health_check_script: /var/tmp/script.sh + port: 443 + - name: s2 + address: 10.10.10.2 + port: 81 + real_server: + - address: real1 + connection_timeout: 5 + port: 8081 + - address: real2 + port: 8080 + vrrp: + snmp: disabled + global_parameters: + startup_delay: 31 + version: "3" + garp: + interval: 30 + master_delay: 10 + master_refresh: 100 + master_refresh_repeat: 200 + master_repeat: 5 + groups: + - name: "g1" + description: "Group 1" + interface: "eth2" + address: "1.1.1.1" + advertise_interval: 10 + peer_address: 192.168.1.3 + priority: 100 + disable: true + no_preempt: true + rfc3768_compatibility: true + vrid: 20 + excluded_address: + - "192.168.1.7 interface eth3" + - "192.168.1.8" + garp: + interval: 20 + master_delay: 5 + master_refresh: 50 + master_refresh_repeat: 100 + master_repeat: 3 + authentication: + type: "plaintext-password" + password: "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER" + transition_script: + master: "/var/tmp/script.sh" + backup: "/var/tmp/script.sh" + fault: "/var/tmp/script.sh" + stop: "/var/tmp/script.sh" + track: + exclude_vrrp_interface: true + interface: + - eth0 + - eth1 + - name: "g2" + description: "Group 2" + interface: "eth1" + address: "2.2.2.2" + disable: false + no_preempt: false + rfc3768_compatibility: false + vrid: 11 + health_check: + failure_count: 5 + interval: 15 + ping: "192.168.1.100" + script: "/var/tmp/script.sh" + hello_source_address: "2.2.2.2" + sync_groups: + - name: "sg1" + member: + - "g1" + - "g2" + transition_script: + master: "/var/tmp/script.sh" + backup: "/var/tmp/script.sh" + fault: "/var/tmp/script.sh" + stop: "/var/tmp/script.sh" + health_check: + failure_count: 3 + interval: 10 + ping: "192.168.2.100" + script: "/var/tmp/script.sh" + +populate_config: | + set high-availability virtual-server s1 address 10.10.10.1 + set high-availability virtual-server s1 algorithm round-robin + set high-availability virtual-server s1 delay-loop 60 + set high-availability virtual-server s1 forward-method direct + set high-availability virtual-server s1 fwmark 10 + set high-availability virtual-server s1 persistence-timeout 30 + set high-availability virtual-server s1 protocol tcp + set high-availability virtual-server s1 real-server 10.10.50.1 health-check script /var/tmp/script.sh + set high-availability virtual-server s1 real-server 10.10.50.1 port 443 + set high-availability virtual-server s2 address 10.10.10.2 + set high-availability virtual-server s2 port 81 + set high-availability virtual-server s2 real-server real1 connection-timeout 5 + set high-availability virtual-server s2 real-server real1 port 8081 + set high-availability virtual-server s2 real-server real2 port 8080 + set high-availability vrrp global-parameters startup-delay 31 + set high-availability vrrp global-parameters version 3 + set high-availability vrrp global-parameters garp interval 30 + set high-availability vrrp global-parameters garp master-delay 10 + set high-availability vrrp global-parameters garp master-refresh 100 + set high-availability vrrp global-parameters garp master-refresh-repeat 200 + set high-availability vrrp global-parameters garp master-repeat 5 + set high-availability vrrp group g1 address 1.1.1.1 + set high-availability vrrp group g1 advertise-interval 10 + set high-availability vrrp group g1 description 'Group 1' + set high-availability vrrp group g1 disable + set high-availability vrrp group g1 interface eth2 + set high-availability vrrp group g1 no-preempt + set high-availability vrrp group g1 peer-address 192.168.1.3 + set high-availability vrrp group g1 priority 100 + set high-availability vrrp group g1 rfc3768-compatibility + set high-availability vrrp group g1 vrid 20 + set high-availability vrrp group g1 authentication password testpass + set high-availability vrrp group g1 authentication type plaintext-password + set high-availability vrrp group g1 excluded-address 192.168.1.7 interface eth3 + set high-availability vrrp group g1 excluded-address 192.168.1.8 + set high-availability vrrp group g1 garp interval 20 + set high-availability vrrp group g1 garp master-delay 5 + set high-availability vrrp group g1 garp master-refresh 50 + set high-availability vrrp group g1 garp master-refresh-repeat 100 + set high-availability vrrp group g1 garp master-repeat 3 + set high-availability vrrp group g1 track exclude-vrrp-interface + set high-availability vrrp group g1 track interface eth0 + set high-availability vrrp group g1 track interface eth1 + set high-availability vrrp group g1 transition-script backup /var/tmp/script.sh + set high-availability vrrp group g1 transition-script fault /var/tmp/script.sh + set high-availability vrrp group g1 transition-script master /var/tmp/script.sh + set high-availability vrrp group g1 transition-script stop /var/tmp/script.sh + set high-availability vrrp group g2 address 2.2.2.2 + set high-availability vrrp group g2 description 'Group 2' + set high-availability vrrp group g2 interface eth1 + set high-availability vrrp group g2 vrid 11 + set high-availability vrrp group g2 health-check failure-count 5 + set high-availability vrrp group g2 health-check interval 15 + set high-availability vrrp group g2 health-check ping 192.168.1.100 + set high-availability vrrp group g2 health-check script /var/tmp/script.sh + set high-availability vrrp group g2 hello-source-address 2.2.2.2 + set high-availability vrrp sync-group sg1 health-check failure-count 3 + set high-availability vrrp sync-group sg1 health-check interval 10 + set high-availability vrrp sync-group sg1 health-check ping 192.168.2.100 + set high-availability vrrp sync-group sg1 health-check script /var/tmp/script.sh + set high-availability vrrp sync-group sg1 member g1 + set high-availability vrrp sync-group sg1 member g2 + set high-availability vrrp sync-group sg1 transition-script backup /var/tmp/script.sh + set high-availability vrrp sync-group sg1 transition-script fault /var/tmp/script.sh + set high-availability vrrp sync-group sg1 transition-script master /var/tmp/script.sh + set high-availability vrrp sync-group sg1 transition-script stop /var/tmp/script.sh diff --git a/tests/unit/modules/network/vyos/fixtures/vyos_vrrp_config.cfg b/tests/unit/modules/network/vyos/fixtures/vyos_vrrp_config.cfg new file mode 100644 index 00000000..2874af59 --- /dev/null +++ b/tests/unit/modules/network/vyos/fixtures/vyos_vrrp_config.cfg @@ -0,0 +1,61 @@ +set high-availability disable +set high-availability virtual-server s1 address 10.10.10.1 +set high-availability virtual-server s1 algorithm round-robin +set high-availability virtual-server s1 delay-loop 60 +set high-availability virtual-server s1 forward-method direct +set high-availability virtual-server s1 fwmark 10 +set high-availability virtual-server s1 persistence-timeout 30 +set high-availability virtual-server s1 protocol tcp +set high-availability virtual-server s1 real-server 10.10.50.1 health-check script '/var/tmp/script.sh' +set high-availability virtual-server s1 real-server 10.10.50.1 port 443 +set high-availability virtual-server s2 address 10.10.10.2 +set high-availability virtual-server s2 port 81 +set high-availability virtual-server s2 real-server real1 connection-timeout 5 +set high-availability virtual-server s2 real-server real1 port 8081 +set high-availability virtual-server s2 real-server real2 port 8080 +# set high-availability vrrp global-parameters garp interval 30 +# set high-availability vrrp global-parameters garp master-delay 10 +# set high-availability vrrp global-parameters garp master-refresh 100 +# set high-availability vrrp global-parameters garp master-refresh-repeat 200 +# set high-availability vrrp global-parameters garp master-repeat 5 +# set high-availability vrrp global-parameters startup-delay 30 +# set high-availability vrrp global-parameters version 3 +# set high-availability vrrp group g1 address '1.1.1.1' +# set high-availability vrrp group g1 advertise-interval 10 +# set high-availability vrrp group g1 authentication password 'testpass' +# set high-availability vrrp group g1 authentication type 'plaintext-password' +# set high-availability vrrp group g1 description 'Group 1' +# set high-availability vrrp group g1 disable +# set high-availability vrrp group g1 excluded-address '192.168.1.7' interface 'eth3' +# set high-availability vrrp group g1 excluded-address '192.168.1.8' +# set high-availability vrrp group g1 garp interval 20 +# set high-availability vrrp group g1 garp master-delay 5 +# set high-availability vrrp group g1 garp master-refresh 50 +# set high-availability vrrp group g1 garp master-refresh-repeat 100 +# set high-availability vrrp group g1 garp master-repeat 3 +# set high-availability vrrp group g1 health-check failure-count 3 +# set high-availability vrrp group g1 health-check interval 10 +# set high-availability vrrp group g1 health-check ping '192.168.1.1' +# set high-availability vrrp group g1 health-check script 'script.sh' +# set high-availability vrrp group g1 hello-source-address '192.168.1.2' +# set high-availability vrrp group g1 interface 'eth2' +# set high-availability vrrp group g1 no-preempt +# set high-availability vrrp group g1 peer-address '192.168.1.3' +# set high-availability vrrp group g1 priority 100 +# set high-availability vrrp group g1 rfc3768-compatibility +# set high-availability vrrp group g1 track exclude-vrrp-interface +# set high-availability vrrp group g1 track interface 'eth1' +# set high-availability vrrp group g1 transition-script backup '/var/tmp/script.sh' +# set high-availability vrrp group g1 transition-script fault '/var/tmp/script.sh' +# set high-availability vrrp group g1 transition-script master '/var/tmp/script.sh' +# set high-availability vrrp group g1 transition-script stop '/var/tmp/script.sh' +# set high-availability vrrp group g1 vrid 20 +# set high-availability vrrp sync-group sg1 health-check failure-count 3 +# set high-availability vrrp sync-group sg1 health-check interval 10 +# set high-availability vrrp sync-group sg1 health-check ping '192.168.1.1' +# set high-availability vrrp sync-group sg1 health-check script '/var/tmp/script.sh' +# set high-availability vrrp sync-group sg1 member 'g1' +# set high-availability vrrp sync-group sg1 transition-script backup '/var/tmp/script.sh' +# set high-availability vrrp sync-group sg1 transition-script fault '/var/tmp/script.sh' +# set high-availability vrrp sync-group sg1 transition-script master '/var/tmp/script.sh' +# set high-availability vrrp sync-group sg1 transition-script stop '/var/tmp/script.sh' diff --git a/tests/unit/modules/network/vyos/test_vyos_vrrp.py b/tests/unit/modules/network/vyos/test_vyos_vrrp.py new file mode 100644 index 00000000..8a748da7 --- /dev/null +++ b/tests/unit/modules/network/vyos/test_vyos_vrrp.py @@ -0,0 +1,484 @@ +# (c) 2021 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 unittest.mock import patch + +from ansible_collections.vyos.vyos.plugins.modules import vyos_vrrp +from ansible_collections.vyos.vyos.tests.unit.modules.utils import set_module_args + +from .vyos_module import TestVyosModule, load_fixture + + +class TestVyosVrrpModule(TestVyosModule): + module = vyos_vrrp + + def setUp(self): + super(TestVyosVrrpModule, self).setUp() + + self.mock_get_resource_connection_config = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.resource_module_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.vrrp.vrrp.VrrpFacts.get_config", + ) + + self.execute_show_command = self.mock_execute_show_command.start() + + self.mock_get_os_version = patch( + "ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.config.vrrp.vrrp.get_os_version", + ) + self.get_os_version = self.mock_get_os_version.start() + self.get_os_version.return_value = "1.5" + + def tearDown(self): + super(TestVyosVrrpModule, self).tearDown() + self.mock_get_resource_connection_config.stop() + self.mock_get_resource_connection_facts.stop() + self.mock_execute_show_command.stop() + self.mock_get_os_version.stop() + + def load_fixtures(self, commands=None, filename=None): + if filename is None: + filename = "vyos_vrrp_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_vrrp_merged_idempotent(self): + set_module_args( + dict( + config=dict( + disable=True, + virtual_servers=[ + dict( + address="10.10.10.1", + algorithm="round-robin", + delay_loop=60, + forward_method="direct", + fwmark=10, + name="s1", + persistence_timeout="30", + protocol="tcp", + real_server=[ + dict( + address="10.10.50.1", + port=443, + health_check_script="/var/tmp/script.sh", + ), + ], + ), + dict( + address="10.10.10.2", + name="s2", + port=81, + real_server=[ + dict( + address="real1", + port=8081, + connection_timeout=5, + ), + dict( + address="real2", + port=8080, + ), + ], + ), + ], + # vrrp=dict( + # global_parameters=dict( + # garp=dict( + # master_refresh=100, + # ), + # version="3", + # ), + # groups=[ + # dict( + # authentication=dict( + # password="test123", + # type="plaintext-password", + # ), + # description="Group 1", + # disable=False, + # excluded_address=[ + # "192.168.1.8", + # "192.168.1.7 interface eth3 ", + # ], + # garp=dict( + # interval=20, + # master_delay=5, + # master_refresh_repeat=100, + # master_repeat=3, + # ), + # health_check=dict( + # failure_count=3, + # interval=10, + # ), + # hello_source_address="192.168.1.2", + # interface="eth2", + # name="g1", + # no_preempt=True, + # peer_address="192.168.1.3", + # priority=100, + # rfc3768_compatibility=False, + # track=dict( + # exclude_vrrp_interface=True, + # interface=["eth1"], + # ), + # transition_script=dict( + # backup="/var/tmp/script.sh", + # master="/var/tmp/script.sh", + # stop="/var/tmp/script.sh", + # ), + # vrid=20, + # ) + # ], + # snmp="disabled", + # sync_groups=[ + # dict( + # health_check=dict( + # failure_count=3, + # interval=10, + # ), + # member=["192.168.1.100"], + # name="sg1", + # transition_script=dict( + # backup="script.sh", + # master="script.sh", + # stop="script.sh", + # ), + # ) + # ], + # ), + ), + state="merged", + ), + ) + + # config=dict( + # allow_clients=["10.1.1.0/24", "10.1.2.0/24"], + # listen_addresses=["10.2.3.1", "10.4.3.1"], + # servers=[ + # dict(server="server1"), + # dict(server="server3", options=["noselect", "dynamic"]), + # dict(server="time1.vyos.net"), + # dict(server="time2.vyos.net"), + # dict(server="time3.vyos.net"), + # ], + # ), + # state="merged", + # ), + # ) + self.execute_module(changed=False, commands=[]) + + # def test_vrrp_merged(self): + # set_module_args( + # dict( + # config=dict( + # allow_clients=["10.2.2.0/24", "10.3.3.0/24"], + # listen_addresses=["10.3.4.1", "10.4.5.1"], + # servers=[ + # dict(server="server4", options=["dynamic", "preempt"]), + # dict( + # server="server5", + # options=[ + # "noselect", + # "dynamic", + # "preempt", + # "prefer", + # ], + # ), + # ], + # ), + # state="merged", + # ), + # ) + + # commands = [ + # "set system ntp allow-clients address 10.2.2.0/24", + # "set system ntp allow-clients address 10.3.3.0/24", + # "set system ntp listen-address 10.3.4.1", + # "set system ntp listen-address 10.4.5.1", + # "set system ntp server server4 dynamic", + # "set system ntp server server4 preempt", + # "set system ntp server server5 dynamic", + # "set system ntp server server5 noselect", + # "set system ntp server server5 preempt", + # "set system ntp server server5 prefer", + # ] + + # self.execute_module(changed=True, commands=commands) + + # def test_ntp_replaced(self): + # set_module_args( + # dict( + # config=dict( + # allow_clients=["10.3.4.0/24", "10.4.5.0/24"], + # listen_addresses=["10.3.3.1", "10.4.4.1"], + # servers=[ + # dict(server="server4", options=["noselect", "prefer"]), + # dict( + # server="server6", + # options=[ + # "noselect", + # "dynamic", + # "prefer", + # "preempt", + # ], + # ), + # dict(server="time1.vyos.net"), + # dict(server="time2.vyos.net"), + # dict(server="time3.vyos.net"), + # ], + # ), + # state="replaced", + # ), + # ) + # commands = [ + # "delete system ntp allow-clients address 10.1.1.0/24", + # "delete system ntp allow-clients address 10.1.2.0/24", + # "delete system ntp listen-address 10.2.3.1", + # "delete system ntp listen-address 10.4.3.1", + # "delete system ntp server server1", + # "delete system ntp server server3", + # "set system ntp allow-clients address 10.3.4.0/24", + # "set system ntp allow-clients address 10.4.5.0/24", + # "set system ntp listen-address 10.3.3.1", + # "set system ntp listen-address 10.4.4.1", + # "set system ntp server server4 noselect", + # "set system ntp server server4 prefer", + # "set system ntp server server6 noselect", + # "set system ntp server server6 dynamic", + # "set system ntp server server6 prefer", + # "set system ntp server server6 preempt", + # ] + # self.execute_module(changed=True, commands=commands) + + # def test_ntp_replaced_idempotent(self): + # set_module_args( + # dict( + # config=dict( + # allow_clients=["10.1.1.0/24", "10.1.2.0/24"], + # listen_addresses=["10.2.3.1", "10.4.3.1"], + # servers=[ + # dict(server="server1"), + # dict(server="server3", options=["noselect", "dynamic"]), + # dict(server="time1.vyos.net"), + # dict(server="time2.vyos.net"), + # dict(server="time3.vyos.net"), + # ], + # ), + # state="replaced", + # ), + # ) + # self.execute_module(changed=False, commands=[]) + + # def test_ntp_overridden(self): + # set_module_args( + # dict( + # config=dict( + # allow_clients=["10.9.9.0/24"], + # listen_addresses=["10.9.9.1"], + # servers=[ + # dict(server="server9"), + # dict(server="server6", options=["noselect", "dynamic"]), + # dict(server="time1.vyos.net"), + # dict(server="time2.vyos.net"), + # dict(server="time3.vyos.net"), + # ], + # ), + # state="overridden", + # ), + # ) + # commands = [ + # "delete system ntp allow-clients address 10.1.1.0/24", + # "delete system ntp allow-clients address 10.1.2.0/24", + # "delete system ntp listen-address 10.2.3.1", + # "delete system ntp listen-address 10.4.3.1", + # "delete system ntp server server1", + # "delete system ntp server server3", + # "set system ntp allow-clients address 10.9.9.0/24", + # "set system ntp listen-address 10.9.9.1", + # "set system ntp server server9", + # "set system ntp server server6 noselect", + # "set system ntp server server6 dynamic", + # ] + # self.execute_module(changed=True, commands=commands) + + # def test_ntp_overridden_idempotent(self): + # set_module_args( + # dict( + # config=dict( + # allow_clients=["10.1.1.0/24", "10.1.2.0/24"], + # listen_addresses=["10.2.3.1", "10.4.3.1"], + # servers=[ + # dict(server="server1"), + # dict(server="server3", options=["noselect", "dynamic"]), + # dict(server="time1.vyos.net"), + # dict(server="time2.vyos.net"), + # dict(server="time3.vyos.net"), + # ], + # ), + # state="overridden", + # ), + # ) + # self.execute_module(changed=False, commands=[]) + + # def test_vrrp_rendered(self): + # set_module_args( + # dict( + # config=dict( + # allow_clients=["10.7.7.0/24", "10.8.8.0/24"], + # listen_addresses=["10.7.9.1"], + # servers=[ + # dict(server="server79"), + # dict(server="server46", options=["noselect", "dynamic"]), + # dict(server="time1.vyos.net"), + # dict(server="time2.vyos.net"), + # dict(server="time3.vyos.net"), + # ], + # ), + # state="rendered", + # ), + # ) + # rendered_commands = [ + # "set system ntp allow-clients address 10.7.7.0/24", + # "set system ntp allow-clients address 10.8.8.0/24", + # "set system ntp listen-address 10.7.9.1", + # "set system ntp server server79", + # "set system ntp server server46 noselect", + # "set system ntp server server46 dynamic", + # "set system ntp server time1.vyos.net", + # "set system ntp server time2.vyos.net", + # "set system ntp server time3.vyos.net", + # ] + # result = self.execute_module(changed=False) + # self.assertEqual( + # sorted(result["rendered"]), + # sorted(rendered_commands), + # result["rendered"], + # ) + + # def test_ntp_parsed(self): + # commands = ( + # "set system ntp allow-clients address 10.7.7.0/24", + # "set system ntp allow-clients address 10.6.7.0/24", + # "set system ntp listen-address 10.7.9.1", + # "set system ntp listen-address 10.7.7.1", + # "set system ntp server check", + # "set system ntp server server46 noselect", + # "set system ntp server server46 prefer", + # "set system ntp server time1.vyos.net", + # "set system ntp server time2.vyos.net", + # "set system ntp server time3.vyos.net", + # ) + # parsed_str = "\n".join(commands) + # set_module_args(dict(running_config=parsed_str, state="parsed")) + # result = self.execute_module(changed=False) + # parsed_list = { + # "allow_clients": ["10.6.7.0/24", "10.7.7.0/24"], + # "listen_addresses": ["10.7.7.1", "10.7.9.1"], + # "servers": [ + # {"server": "check"}, + # {"server": "server46", "options": ["noselect", "prefer"]}, + # {"server": "time1.vyos.net"}, + # {"server": "time2.vyos.net"}, + # {"server": "time3.vyos.net"}, + # ], + # } + # self.assertEqual(parsed_list, result["parsed"]) + + # def test_ntp_gathered(self): + # set_module_args(dict(state="gathered")) + # result = self.execute_module(changed=False) + # gathered_list = { + # "allow_clients": ["10.1.1.0/24", "10.1.2.0/24"], + # "listen_addresses": ["10.2.3.1", "10.4.3.1"], + # "servers": [ + # {"server": "server1"}, + # {"server": "server3", "options": ["dynamic", "noselect"]}, + # {"server": "time1.vyos.net"}, + # {"server": "time2.vyos.net"}, + # {"server": "time3.vyos.net"}, + # ], + # } + + # self.assertEqual(gathered_list, result["gathered"]) + + # def test_ntp_deleted(self): + # # Delete the subsections that we include (listen_addresses and servers) + # set_module_args( + # dict( + # config=dict( + # allow_clients=["10.1.1.0/24"], + # listen_addresses=["10.2.3.1"], + # servers=[ + # dict(server="server1"), + # dict(server="server3", options=["noselect"]), + # dict(server="time1.vyos.net"), + # dict(server="time2.vyos.net"), + # dict(server="time3.vyos.net"), + # ], + # ), + # state="deleted", + # ), + # ) + # commands = [ + # "delete system ntp allow-clients", # 10.1.1.0/24", + # "delete system ntp listen-address", # 10.2.3.1", + # "delete system ntp server server1", + # "delete system ntp server server3", + # "delete system ntp server time1.vyos.net", + # "delete system ntp server time2.vyos.net", + # "delete system ntp server time3.vyos.net", + # "delete system ntp", + # ] + # self.execute_module(changed=True, commands=commands) + + # def test_ntp__all_deleted(self): + # set_module_args( + # dict( + # config=dict(), + # state="deleted", + # ), + # ) + # commands = [ + # "delete system ntp allow-clients", + # "delete system ntp listen-address", + # "delete system ntp server server1", + # "delete system ntp server server3", + # "delete system ntp server time1.vyos.net", + # "delete system ntp server time2.vyos.net", + # "delete system ntp server time3.vyos.net", + # "delete system ntp", + # ] + # self.execute_module(changed=True, commands=commands)