diff --git a/plugins/module_utils/network/vyos/config/vrrp/vrrp.py b/plugins/module_utils/network/vyos/config/vrrp/vrrp.py index d6b4f793..c4af82ea 100644 --- a/plugins/module_utils/network/vyos/config/vrrp/vrrp.py +++ b/plugins/module_utils/network/vyos/config/vrrp/vrrp.py @@ -1,263 +1,281 @@ # # -*- 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 # from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ( # dict_merge, # ) 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"]: self.generate_commands() 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) 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._module.fail_json(msg="Init - want: " + str(wantd) + " (((()))) have: " + str(haved)) + # self._module.fail_json(msg="Normalise - want: " + str(wantd) + " (((()))) have: " + str(haved)) # if state is merged, merge want onto have and then compare if self.state in ["merged"]: wantd = combine(haved, wantd, recursive=True) # self._module.fail_json(msg="Want: " + str(wantd) + "**** H: " + str(haved)) # if state is deleted, delete and empty out wantd # if self.state == "deleted": # w = deepcopy(wantd) # if w == {} and haved != {}: # self.commands = ["delete vrrp"] # return # for k, want in w.items(): # if not (k in haved and haved[k]): # del wantd[k] # else: # if isinstance(want, list): # for entry in want: # wname = entry.get("name") # haved["instances"] = [ # i for i in haved.get("instances", []) if i.get("name") != wname # ] # self.commands.append("delete vrrp name {}".format(wname)) # else: # self.commands.append("delete vrrp {}".format(k.replace("_", "-"))) # del wantd[k] # # if self.state == "overridden": # w = deepcopy(wantd) # h = deepcopy(haved) # for k, want in w.items(): # if k in haved anzd haved[k] != want: # if isinstance(want, list): # for entry in want: # wname = entry.get("name") # hdict = next( # (inst for inst in haved["instances"] if inst["name"] == wname), # None, # ) # if entry != hdict: # # self._module.fail_json(msg="Want: " + str(entry) + "**** H: " + str(hdict)) # haved["instances"] = [ # i for i in haved.get("instances", []) if i.get("name") != wname # ] # self.commands.append("delete vrrp name {}".format(wname)) # self.commands.append("commit") # for k, want in wantd.items(): if k == "vrrp": self._compare_vrrp(want, haved.get(k, {})) if k == "virtual_servers": # self._module.fail_json(msg="VSERVERS: " + str(want) + " ---- " + str(haved.get(k, {}))) self._compare_vsrvs(want, haved.get(k, {})) # if isinstance(want, list): # self._module.fail_json(msg=str(want) + " +++ " + str(haved.pop(k, {}))) self.compare( parsers=self.parsers, want={k: want}, have={k: haved.pop(k, {})}, ) self._module.fail_json(msg=self.commands) def _compare_vsrvs(self, want, have): """Compare virtual servers of VRRP.py""" vs_parsers = [ "virtual_servers", # "virtual_servers.real_servers", ] # self._module.fail_json(msg="want: " + str(want) + "**** have: " + str(have)) self.compare( parsers=vs_parsers, want={"virtual_servers": want}, have={"virtual_servers": have}, ) def _compare_vrrp(self, want, have): """Compare the instances of VRRP""" vrrp_parsers = [ "vrrp.snmp", # "vrrp.sync_groups.member", # "vrrp.sync_groups.health_check", # "vrrp.sync_groups.transition_script", "vrrp.global_parameters.garp", "vrrp.global_parameters", "vrrp.groups.garp", - # "vrrp.groups", + "vrrp.groups", # "vrrp.group.aunthentication", # "vrrp.group.transition_script", # "vrrp.groups.health_check", # "vrrp.group.track", # "vrrp.group.transition_script", ] # self._module.fail_json(msg="Conf: " + str(want) + " <*****************> " + str(have)) - want = {"vrrp": {"groups": {"garp": {"interval": "1"}, "name": "g1"}}} + want = { + "vrrp": { + "groups": { + "name": "g2", + "interface": "eth1", + "address": "2.2.2.2", + "disable": False, + "no_preempt": False, + "vrid": 11, + "garp": { + "interval": 21, + "master_delay": 6, + "master_refresh": 51, + "master_refresh_repeat": 101, + "master_repeat": 4, + }, + }, + }, + } have = {"vrrp": {"groups": {}}} self.compare(parsers=vrrp_parsers, want=want, have=have) # self.compare(parsers=vrrp_parsers, want={"vrrp": want}, have={"vrrp": have}) def _vrrp_groups_list_to_dict(self, data): vrrp = data.get("vrrp", {}) groups = vrrp.get("groups") # Nothing to do if not groups: return data # Already dict-based if isinstance(groups, dict): return data # Must be list → convert it 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 # Unexpected shape → leave as-is return data def _virtual_servers_list_to_dict(self, data): vss = data.get("virtual_servers") if not vss: return data # Already normalized dict → return untouched if isinstance(vss, dict): return data # List → convert if isinstance(vss, list): new_vss = {} for item in vss: # Skip non-dict items if not isinstance(item, dict): continue alias = item.get("alias") if not alias: continue new_vss[alias] = item data["virtual_servers"] = new_vss return data # Anything else → leave unchanged return data diff --git a/plugins/module_utils/network/vyos/rm_templates/vrrp.py b/plugins/module_utils/network/vyos/rm_templates/vrrp.py index fce4e332..483a470e 100644 --- a/plugins/module_utils/network/vyos/rm_templates/vrrp.py +++ b/plugins/module_utils/network/vyos/rm_templates/vrrp.py @@ -1,767 +1,754 @@ # -*- 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 Bgp_global parser templates file. This contains a list of parser definitions and associated functions that facilitates both facts gathering and native command generation for the given network resource. """ import re # from ansible.module_utils.six import iteritems from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.network_template import ( NetworkTemplate, ) # def _tmplt_vsrvs(config_data): # config_data = config_data["virtual_servers"] # command = [] # cmd = "high-availability virtual-server".format(**config_data) # command = [str(cmd)] # # if "mode" in config_data: # # mode_cmd = cmd + " mode {mode}".format(**config_data) # # command.append(mode_cmd) # # if "seclevel" in config_data: # # sec_cmd = cmd + " seclevel {seclevel}".format(**config_data) # # command.append(sec_cmd) # # if "view" in config_data: # # view_cmd = cmd + " view {view}".format(**config_data) # # command.append(view_cmd) # return command def _tmplt_vsrvs(config_data): vs = config_data["virtual_servers"] command = [] for alias, item in vs.items(): cmd = f"set high-availability virtual-server {alias}" for key, value in item.items(): if key == "alias" or isinstance(value, list) or value is None: continue command.append(f"{cmd} {key.replace('_', '-')} {value}") return command def _tmplt_vsrvs_rsrv(config_data): config_data = config_data["virtual_servers"]["real_servers"] command = [] # cmd = "service snmp v3 group {group}".format(**config_data) # if "mode" in config_data: # mode_cmd = cmd + " mode {mode}".format(**config_data) # command.append(mode_cmd) # if "seclevel" in config_data: # sec_cmd = cmd + " seclevel {seclevel}".format(**config_data) # command.append(sec_cmd) # if "view" in config_data: # view_cmd = cmd + " view {view}".format(**config_data) # command.append(view_cmd) return command def _tmplt_sgroup_hc(config_data): config_data = config_data["sync-group"]["health-check"] command = [] cmd = "high-availability vrrp sync-group health-check {health_check}".format(**config_data) if "failure_count" in config_data: failure_count_cmd = cmd + " failure-count {failure_count}".format(**config_data) command.append(failure_count_cmd) if "interval" in config_data: interval_cmd = cmd + " interval {interval}".format(**config_data) command.append(interval_cmd) if "ping" in config_data: ping_cmd = cmd + " ping {ping}".format(**config_data) command.append(ping_cmd) if "script" in config_data: script_cmd = cmd + " script {script}".format(**config_data) command.append(script_cmd) return command def _tmplt_sgroup_ts(config_data): config_data = config_data["sync-group"]["transition-script"] command = [] cmd = "high-availability vrrp sync-group transition-script {transition_script}".format( **config_data, ) if "backup" in config_data: backup_cmd = cmd + " backup {backup}".format(**config_data) command.append(backup_cmd) if "fault" in config_data: fault_cmd = cmd + " fault {fault}".format(**config_data) command.append(fault_cmd) if "master" in config_data: master_cmd = cmd + " master {master}".format(**config_data) command.append(master_cmd) if "stop" in config_data: stop_cmd = cmd + " stop {stop}".format(**config_data) command.append(stop_cmd) return command def _tmplt_vrrp_gp(config_data): config_data = config_data["vrrp"]["global_parameters"] command = [] cmd = "high-availability vrrp global-parameters" if "version" in config_data: version_cmd = cmd + " version {version}".format(**config_data) command.append(version_cmd) if "startup_delay" in config_data: startup_delay_cmd = cmd + " startup-delay {startup_delay}".format(**config_data) command.append(startup_delay_cmd) return command def _tmplt_vrrp_gp_garp(config_data): config_data = config_data["vrrp"]["global_parameters"]["garp"] command = [] cmd = "high-availability vrrp global-parameters garp" if "interval" in config_data: interval_cmd = cmd + " interval {interval}".format(**config_data) command.append(interval_cmd) if "master_delay" in config_data: master_delay_cmd = cmd + " master-delay {master_delay}".format(**config_data) command.append(master_delay_cmd) if "master_refresh" in config_data: master_refresh_cmd = cmd + " master-refresh {master_refresh}".format(**config_data) command.append(master_refresh_cmd) if "master_refresh_repeat" in config_data: master_refresh_repeat_cmd = cmd + " master-refresh-repeat {master_refresh_repeat}".format( **config_data, ) command.append(master_refresh_repeat_cmd) if "master_repeat" in config_data: master_repeat_cmd = cmd + " master-repeat {master_repeat}".format(**config_data) command.append(master_repeat_cmd) return command def _tmplt_vrrp_group(config_data): - groups = config_data["vrrp"]["groups"] + config_data = config_data["vrrp"]["groups"] command = [] + cmd = "high-availability vrrp group {name}".format(**config_data) - for name, item in groups.items(): + for key, value in config_data.items(): - # base command - cmd = f"high-availability vrrp group {name}" + if key == "name" or isinstance(value, dict) or value is None: + continue - for key, value in item.items(): + if isinstance(value, bool) and value is not None: + command.append(f"{cmd} {key.replace('_', '-')}") - if key == "name" or isinstance(value, dict) or value is None: - continue + command.append(f"{cmd} {key.replace('_', '-')} {value}") + return command - if isinstance(value, bool) and value is not None: - command.append(f"{cmd} {key.replace('_', '-')}") - - command.append(f"{cmd} {key.replace('_', '-')} {value}") +def _tmplt_vrrp_group_garp(config_data): + config_data = config_data["vrrp"]["groups"] + command = [] + cmd = "high-availability vrrp group {name}".format(**config_data) + config_data = config_data["garp"] + for key, value in config_data.items(): + if value is not None: + command.append(cmd + " garp " + f"{key.replace('_', '-')} {value}") return command def _tmplt_vrrp_group_track(config_data): config_data = config_data["vrrp"]["group"]["track"] command = [] # cmd = "service snmp v3 group {group}".format(**config_data) # if "mode" in config_data: # mode_cmd = cmd + " mode {mode}".format(**config_data) # command.append(mode_cmd) # if "seclevel" in config_data: # sec_cmd = cmd + " seclevel {seclevel}".format(**config_data) # command.append(sec_cmd) # if "view" in config_data: # view_cmd = cmd + " view {view}".format(**config_data) # command.append(view_cmd) return command def _tmplt_vrrp_group_hc(config_data): config_data = config_data["vrrp"]["group"]["health-check"] command = [] # cmd = "service snmp v3 group {group}".format(**config_data) # if "mode" in config_data: # mode_cmd = cmd + " mode {mode}".format(**config_data) # command.append(mode_cmd) # if "seclevel" in config_data: # sec_cmd = cmd + " seclevel {seclevel}".format(**config_data) # command.append(sec_cmd) # if "view" in config_data: # view_cmd = cmd + " view {view}".format(**config_data) # command.append(view_cmd) return command def _tmplt_vrrp_group_ts(config_data): config_data = config_data["vrrp"]["group"]["transcription-script"] command = [] # cmd = "service snmp v3 group {group}".format(**config_data) # if "mode" in config_data: # mode_cmd = cmd + " mode {mode}".format(**config_data) # command.append(mode_cmd) # if "seclevel" in config_data: # sec_cmd = cmd + " seclevel {seclevel}".format(**config_data) # command.append(sec_cmd) # if "view" in config_data: # view_cmd = cmd + " view {view}".format(**config_data) # command.append(view_cmd) return command -def _tmplt_vrrp_group_garp(config_data): - config_data = config_data["vrrp"]["groups"] - command = [str(config_data)] - # cmd = "service snmp v3 group {name}".format(**config_data) - # if "mode" in config_data: - # mode_cmd = cmd + " mode {mode}".format(**config_data) - # command.append(mode_cmd) - # if "seclevel" in config_data: - # sec_cmd = cmd + " seclevel {seclevel}".format(**config_data) - # command.append(sec_cmd) - # if "view" in config_data: - # view_cmd = cmd + " view {view}".format(**config_data) - # command.append(view_cmd) - return command - # return [str(cmd)] - - def _tmplt_vrrp_group_auth(config_data): config_data = config_data["vrrp"]["group"]["authentication"] command = [] # cmd = "service snmp v3 group {group}".format(**config_data) # if "mode" in config_data: # mode_cmd = cmd + " mode {mode}".format(**config_data) # command.append(mode_cmd) # if "seclevel" in config_data: # sec_cmd = cmd + " seclevel {seclevel}".format(**config_data) # command.append(sec_cmd) # if "view" in config_data: # view_cmd = cmd + " view {view}".format(**config_data) # command.append(view_cmd) return command class VrrpTemplate(NetworkTemplate): def __init__(self, lines=None, module=None): prefix = {"set": "set", "remove": "delete"} super(VrrpTemplate, self).__init__( lines=lines, tmplt=self, prefix=prefix, module=module, ) # fmt: off PARSERS = [ { "name": "disable", "getval": re.compile( r""" ^set \shigh-availability \s(?Pdisable) $""", re.VERBOSE, ), "setval": "high-availability disable", "result": { "disable": "{{ True if disable is defined }}", }, }, { "name": "virtual_servers", "getval": re.compile( r""" ^set\shigh-availability\svirtual-server \s+(?P\S+) (?:\s+address\s+(?P
\S+))? (?:\s+algorithm\s+(?P\S+))? (?:\s+delay-loop\s+(?P\S+))? (?:\s+forward-method\s+(?P\S+))? (?:\s+fwmark\s+(?P\S+))? (?:\s+persistence-timeout\s+(?P\S+))? (?:\s+port\s+(?P\S+))? (?:\s+protocol\s+(?P\S+))? $ """, re.VERBOSE, ), "setval": _tmplt_vsrvs, # "setval": "virtual-server", "result": { "virtual_servers": { "{{ alias }}": { "alias": "{{ alias }}", "address": "{{ address if address is defined else None }}", "algorithm": "{{ algorithm if algorithm is defined else None }}", "delay_loop": "{{ delay_loop if delay_loop is defined else None }}", "forward_method": "{{ forward_method if forward_method is defined else None }}", "fwmark": "{{ fwmark if fwmark is defined else None }}", "persistence_timeout": "{{ persistence_timeout if persistence_timeout is defined else None }}", "port": "{{ port if port is defined else None }}", "protocol": "{{ protocol if protocol is defined else None }}", }, }, }, }, { "name": "virtual_servers.real_servers", "getval": re.compile( r""" ^set\shigh-availability\svirtual-server \s+(?P\S+) \sreal-server \s+(?P
\S+) (?:\s+port\s+(?P\S+))? (?:\s+health-check\sscript\s+(?P\S+))? (?:\s+connection-timeout\s+(?P\S+))? $ """, re.VERBOSE, ), "setval": _tmplt_vsrvs_rsrv, # "compval": "global_parameters.garp.master_refersh_repeat", "result": { "virtual_servers": { "{{ alias }}": { "alias": "{{ alias }}", "real_servers": { "{{ address }}": { "address": "{{ address }}", "port": "{{ port if port is defined else None }}", "health_check_script": "{{ hcscript if hcscript is defined else None }}", "connection_timeout": "{{ cont if cont is defined else None }}", }, }, }, }, }, }, # { # "name": "vrrp.sync_groups.member", # "getval": re.compile( # r""" # ^set\shigh-availability\svrrp\ssync-group # \s+(?P\S+) # \smember # \s+(?P\S+) # $ # """, # re.VERBOSE, # ), # "setval": "set high-availability vrrp sync-group {{sgname}} member {{member}}", # "result": { # "vrrp": { # "sync_groups": { # "{{ sgname }}": { # "name": "{{ sgname }}", # "member": [ # "{{ member }}" # ], # }, # }, # }, # }, # }, # { # "name": "vrrp.sync_groups.member", # "getval": re.compile( # r""" # ^set\shigh-availability\svrrp\ssync-group # \s+(?P\S+) # \smember # \s+(?P\S+) # $ # """, # re.VERBOSE, # ), # "setval": "set high-availability vrrp sync-group {{sgname}} member {{member}}", # "result": { # "vrrp": { # "sync_groups": [ # { # "name": "{{ sgname }}", # "member": ["{{ member }}"], # }, # ], # }, # }, # }, { "name": "vrrp.sync_groups.health_check", "getval": re.compile( r""" ^set\shigh-availability\svrrp\ssync-group \s+(?P\S+) \shealth-check (?:\s+failure-count\s+(?P\S+)) ?(?:\s+interval\s+(?P\S+)) ?(?:\s+ping\s+(?P\S+)) ?(?:\s+script\s+(?P