diff --git a/plugins/module_utils/network/vyos/argspec/vrrp/vrrp.py b/plugins/module_utils/network/vyos/argspec/vrrp/vrrp.py index 1d673dfa..8a730d1d 100644 --- a/plugins/module_utils/network/vyos/argspec/vrrp/vrrp.py +++ b/plugins/module_utils/network/vyos/argspec/vrrp/vrrp.py @@ -1,191 +1,191 @@ # -*- coding: utf-8 -*- # Copyright 2024 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 # cli_rm_builder. # # Manually editing this file is not advised. # # To update the argspec make the desired changes # in the module docstring and re-run # cli_rm_builder. # ############################################# """ The arg spec for the vyos_vrrp module """ class VrrpArgs(object): # pylint: disable=R0903 """The arg spec for the vyos_vrrp module""" argument_spec = { "config": { "type": "dict", "required": False, "options": { - "disable": {"type": "bool"}, + "disable": {"type": "bool", "default": False}, "virtual_servers": { "type": "list", "elements": "dict", "options": { "alias": {"type": "str", "required": True}, "address": {"type": "str"}, "algorithm": {"type": "str"}, "delay_loop": {"type": "int"}, "forward_method": {"type": "str", "choices": ["direct", "nat"]}, "fwmark": {"type": "str"}, "persistence_timeout": {"type": "str"}, "port": {"type": "int"}, "protocol": {"type": "str", "choices": ["tcp", "udp"]}, "real_servers": { "type": "list", "elements": "dict", "options": { "address": {"type": "str", "required": True}, "port": {"type": "int"}, "health_check_script": {"type": "str"}, }, }, }, }, "vrrp": { "type": "dict", "options": { "global_parameters": { "type": "dict", "options": { "garp": { "type": "dict", "options": { "interval": {"type": "int"}, "master_delay": {"type": "int"}, "master_refresh": {"type": "int"}, "master_refresh_repeat": {"type": "int"}, "master_repeat": {"type": "int"}, }, }, "startup_delay": {"type": "int"}, "version": {"type": "str"}, }, }, "groups": { "type": "list", "elements": "dict", "options": { "name": {"type": "str", "required": True}, "address": {"type": "str"}, "advertise_interval": {"type": "int"}, "authentication": { "type": "dict", "options": { "password": {"type": "str", "no_log": True}, "type": {"type": "str"}, }, }, "description": {"type": "str"}, "disable": {"type": "bool"}, "excluded_address": {"type": "str"}, "garp": { "type": "dict", "options": { "interval": {"type": "int"}, "master_delay": {"type": "int"}, "master_refresh": {"type": "int"}, "master_refresh_repeat": {"type": "int"}, "master_repeat": {"type": "int"}, }, }, "health_check": { "type": "dict", "options": { "failure_count": {"type": "int"}, "interval": {"type": "int"}, "ping": {"type": "str"}, "script": {"type": "str"}, }, }, "hello_source_address": {"type": "str"}, "interface": {"type": "str"}, "no_preempt": {"type": "bool"}, "peer_address": {"type": "str"}, "preempt_delay": {"type": "int"}, "priority": {"type": "int"}, "rfc3768_compatibility": {"type": "bool"}, "track": { "type": "dict", "options": { "exclude_vrrp_interface": {"type": "bool"}, "interface": {"type": "str"}, }, }, "transition_script": { "type": "dict", "options": { "backup": {"type": "str"}, "fault": {"type": "str"}, "master": {"type": "str"}, "stop": {"type": "str"}, }, }, "vrid": {"type": "int", "required": True}, }, }, "snmp": {"type": "bool"}, "sync_groups": { "type": "list", "elements": "dict", "options": { "name": {"type": "str", "required": True}, "health_check": { "type": "dict", "options": { "failure_count": {"type": "int"}, "interval": {"type": "int"}, "ping": {"type": "str"}, "script": {"type": "str"}, }, }, "member": {"type": "list", "elements": "str"}, "transition_script": { "type": "dict", "options": { "backup": {"type": "str"}, "fault": {"type": "str"}, "master": {"type": "str"}, "stop": {"type": "str"}, }, }, }, }, }, }, }, }, "state": { "type": "str", "choices": [ "deleted", "merged", "purged", "replaced", "gathered", "rendered", "parsed", ], "default": "merged", }, "running_config": {"type": "str"}, } # pylint: disable=C0301 diff --git a/plugins/module_utils/network/vyos/config/vrrp/vrrp.py b/plugins/module_utils/network/vyos/config/vrrp/vrrp.py index 5f51e276..b17aced3 100644 --- a/plugins/module_utils/network/vyos/config/vrrp/vrrp.py +++ b/plugins/module_utils/network/vyos/config/vrrp/vrrp.py @@ -1,404 +1,245 @@ # # -*- 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 ansible.module_utils.six import iteritems +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.ansible.netcommon.plugins.module_utils.network.common.utils import ( -# dict_merge, -# ) +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 = [] + self.parsers = [ + "disable", + ] def _validate_template(self): - self._tmplt = VrrpTemplate() + 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 """ - self._validate_template() 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) - # if ( - # self.want.get("as_number") == self.have.get("as_number") - # or not self.have - # or LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4") - # ): - # if self.want: - # wantd = {self.want["as_number"]: self.want} - # if self.have: - # haved = {self.have["as_number"]: self.have} - # else: - # self._module.fail_json(msg="Only one bgp instance is allowed per device") - # - # # turn all lists of dicts into dicts prior to merge - # for entry in wantd, haved: - # self._vrrp_list_to_dict(entry) - # - # # if state is merged, merge want onto have and then compare - # if self.state == "merged": - # wantd = dict_merge(haved, wantd) - # - # # if state is deleted, empty out wantd and set haved to wantd - # if self.state == "purged": - # h_del = {} - # for k, v in iteritems(haved): - # if k in wantd or not wantd: - # h_del.update({k: v}) - # for num, entry in iteritems(h_del): - # self.commands.append(self._tmplt.render({"as_number": num}, "router", True)) - # wantd = {} - # + # self._module.fail_json(msg="WanT: " + str(self.want) + "========== H: " + str(self.have)) + + # if state is merged, merge want onto have and then compare + if self.state in ["merged", "replaced"]: + # wantd = dict_merge(wantd, haved) + # wantd = haved | combine(wantd, recursive=True) + 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": - # self._compare(want={}, have=self.have) - # wantd = {} - # - # for k, want in iteritems(wantd): - # self._compare(want=want, have=haved.pop(k, {})) + # 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] # - - def _compare(self, want, have): - """Leverages the base class `compare()` method and - populates the list of commands to be run by comparing - the `want` and `have` data with the `parsers` defined - for the vrrp network resource. - """ - # if LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4"): - # self._compare_asn(want, have) + # 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") # - parsers = ["maximum_paths", "timers"] - self._compare_neighbor(want, have) - self._compare_bgp_params(want, have) - for name, entry in iteritems(want): - if name != "as_number": - self.compare( - parsers=parsers, - want={"as_number": want["as_number"], name: entry}, - have={ - "as_number": want["as_number"], - name: have.pop(name, {}), - }, - ) - for name, entry in iteritems(have): - if name != "as_number": - self.compare( - parsers=parsers, - want={}, - have={"as_number": have["as_number"], name: entry}, - ) - # Do the negation first - command_set = [] - for cmd in self.commands: - if cmd not in command_set: - if "delete" in cmd: - command_set.insert(0, cmd) - else: - command_set.append(cmd) - self.commands = command_set - - def _compare_neighbor(self, want, have): - parsers = [ - "neighbor.advertisement_interval", - "neighbor.allowas_in", - "neighbor.as_override", - "neighbor.attribute_unchanged.as_path", - "neighbor.attribute_unchanged.med", - "neighbor.attribute_unchanged.next_hop", - "neighbor.capability_dynamic", - "neighbor.capability_orf", - "neighbor.default_originate", - "neighbor.description", - "neighbor.disable_capability_negotiation", - "neighbor.disable_connected_check", - "neighbor.disable_send_community", - "neighbor.distribute_list", - "neighbor.ebgp_multihop", - "neighbor.filter_list", - "neighbor.local_as", - "neighbor.maximum_prefix", - "neighbor.nexthop_self", - "neighbor.override_capability", - "neighbor.passive", - "neighbor.password", - "neighbor.peer_group_name", - "neighbor.port", - "neighbor.prefix_list", - "neighbor.remote_as", - "neighbor.remove_private_as", - "neighbor.route_map", - "neighbor.route_reflector_client", - "neighbor.route_server_client", - "neighbor.shutdown", - "neighbor.soft_reconfiguration", - "neighbor.strict_capability_match", - "neighbor.unsuppress_map", - "neighbor.update_source", - "neighbor.weight", - "neighbor.ttl_security", - "neighbor.timers", - ] - - wneigh = want.pop("neighbor", {}) - hneigh = have.pop("neighbor", {}) - self._compare_neigh_lists(wneigh, hneigh) - - for name, entry in iteritems(wneigh): - for k, v in entry.items(): - if k == "address": - continue - if hneigh.get(name): - h = {"address": name, k: hneigh[name].pop(k, {})} - else: - h = {} - self.compare( - parsers=parsers, - want={ - "as_number": want["as_number"], - "neighbor": {"address": name, k: v}, - }, - have={"as_number": want["as_number"], "neighbor": h}, - ) - for name, entry in iteritems(hneigh): - if name not in wneigh.keys(): - if self._check_af(name): - msg = "Use the _bgp_address_family module to delete the address_family under neighbor {0}, before replacing/deleting the neighbor.".format( - name, - ) - self._module.fail_json(msg=msg) - else: - self.commands.append( - "delete protocols bgp" + self._asn_mod + " neighbor " + name, - ) - continue - for k, v in entry.items(): - self.compare( - parsers=parsers, - want={}, - have={ - "as_number": have["as_number"], - "neighbor": {"address": name, k: v}, - }, - ) - - def _compare_bgp_params(self, want, have): - parsers = [ - "bgp_params.always_compare_med", - "bgp_params.bestpath.as_path", - "bgp_params.bestpath.compare_routerid", - "bgp_params.bestpath.med", - "bgp_params.cluster_id", - "bgp_params.confederation", - "bgp_params.dampening_half_life", - "bgp_params.dampening_max_suppress_time", - "bgp_params.dampening_re_use", - "bgp_params.dampening_start_suppress_time", - "bgp_params.default", - "bgp_params.deterministic_med", - "bgp_params.disbale_network_import_check", - "bgp_params.enforce_first_as", - "bgp_params.graceful_restart", - "bgp_params.log_neighbor_changes", - "bgp_params.no_client_to_client_reflection", - "bgp_params.no_fast_external_failover", - "bgp_params.routerid", - "bgp_params.scan_time", - ] - - wbgp = want.pop("bgp_params", {}) - hbgp = have.pop("bgp_params", {}) - for name, entry in iteritems(wbgp): - if name == "confederation": - if entry != hbgp.pop(name, {}): - self.addcmd( - { - "as_number": want["as_number"], - "bgp_params": {name: entry}, - }, - "bgp_params.confederation", - False, - ) - elif name == "distance": - if entry != hbgp.pop(name, {}): - distance_parsers = [ - "bgp_params.distance.global", - "bgp_params.distance.prefix", - ] - for distance_type in entry: - self.compare( - parsers=distance_parsers, - want={ - "as_number": want["as_number"], - "bgp_params": {name: distance_type}, - }, - have={ - "as_number": want["as_number"], - "bgp_params": {name: hbgp.pop(name, {})}, - }, - ) - else: - self.compare( - parsers=parsers, - want={ - "as_number": want["as_number"], - "bgp_params": {name: entry}, - }, - have={ - "as_number": want["as_number"], - "bgp_params": {name: hbgp.pop(name, {})}, - }, - ) - if not wbgp and hbgp: - self.commands.append("delete protocols bgp" + self._asn_mod + " parameters") - hbgp = {} - for name, entry in iteritems(hbgp): - if name == "confederation": - self.commands.append( - "delete protocols bgp" + self._asn_mod + " parameters confederation", - ) - elif name == "distance": - distance_parsers = [ - "bgp_params.distance.global", - "bgp_params.distance.prefix", - ] - self.compare( - parsers=distance_parsers, - want={}, - have={ - "as_number": have["as_number"], - "bgp_params": {name: entry[0]}, - }, - ) - else: - self.compare( - parsers=parsers, - want={}, - have={ - "as_number": have["as_number"], - "bgp_params": {name: entry}, - }, - ) - - def _compare_neigh_lists(self, want, have): - for attrib in [ - "distribute_list", - "filter_list", - "prefix_list", - "route_map", - ]: - wdict = want.pop(attrib, {}) - hdict = have.pop(attrib, {}) - for key, entry in iteritems(wdict): - if entry != hdict.pop(key, {}): - self.addcmd(entry, "neighbor.{0}".format(attrib), False) - # remove remaining items in have for replaced - for entry in hdict.values(): - self.addcmd(entry, "neighbor.{0}".format(attrib), True) - - def _vrrp_list_to_dict(self, entry): - for name, proc in iteritems(entry): - if "neighbor" in proc: - neigh_dict = {} - for entry in proc.get("neighbor", []): - neigh_dict.update({entry["address"]: entry}) - proc["neighbor"] = neigh_dict - - if "network" in proc: - network_dict = {} - for entry in proc.get("network", []): - network_dict.update({entry["address"]: entry}) - proc["network"] = network_dict - - if "aggregate_address" in proc: - agg_dict = {} - for entry in proc.get("aggregate_address", []): - agg_dict.update({entry["prefix"]: entry}) - proc["aggregate_address"] = agg_dict - - if "redistribute" in proc: - redis_dict = {} - for entry in proc.get("redistribute", []): - redis_dict.update({entry["protocol"]: entry}) - proc["redistribute"] = redis_dict - - def _compare_asn(self, want, have): - if want.get("as_number") and not have.get("as_number"): - self.commands.append( - "set protocols bgp " + "system-as" + " " + str(want.get("as_number")), + for k, want in wantd.items(): + # self._module.fail_json(msg=want) + # 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, {})}, ) - - def _check_af(self, neighbor): - af_present = False - if self._connection: - config_lines = self._get_config(self._connection).splitlines() - for line in config_lines: - if neighbor in line: - if "address-family" in line: - af_present = True - return af_present - - def _get_config(self, connection): - return connection.get( - 'show configuration commands | match "set protocols bgp .*neighbor"', - ) + self._module.fail_json(msg=self.commands) + + # def _compare_vsrvs(self, want, have): + # """Compare virtual servers of VRRP.py""" + # parsers = [ + # "virtual_servers", + # "virtual_servers.real_servers", + # ] + # # self._module.fail_json(msg="want: " + str(want) + "**** have: " + str(have)) + # + # # for entry in want: + # # h = {} + # # wname = entry.get("name") + # # # h = next((vrrp for vrrp in have if vrrp["name"] == wname), {}) + # # h = { + # # k: v + # # for vrrp in have + # # if vrrp.get("name") == wname + # # for k, v in vrrp.items() + # # if k != "address_family" + # # } + # self.compare(parsers=parsers, want=entry, have=h) + # + # if "address_family" in entry: + # wafi = {"name": wname, "address_family": entry.get("address_family", [])} + # # hdict = next((item for item in have if item["name"] == wname), None) + # hdict = next((d for d in have if d.get("name") == wname), None) + # + # hafi = { + # "name": (hdict or {"name": wname})["name"], + # "address_family": hdict.get("address_family", []) if hdict else [], + # } + + # self._module.fail_json(msg="wafi: " + str(wafi) + "**** hafi: " + str(hafi)) + + # def _compare_vrrp(self, want, have): + # """Compare the instances of VRRP""" + # vrrp_parsers = [ + # "vrrp.sync_group.member", + # "vrrp.sync_group.health_check", + # "vrrp.sync_group.transition_script", + # "vrrp.global_parametrs_garp", + # "vrrp.global_parameters", + # "vrrp.group", + # "vrrp.group.aunthentication", + # "vrrp.group.garp", + # "vrrp.group.transition_script", + # "vrrp.group.health_check", + # "vrrp.group.track", + # "vrrp.group.transition_script", + # ] + # # self._module.fail_json(msg="wAfi: " + str(want) + "**** hAfi: " + str(have)) + # + # wafi = self.afi_to_list(want) + # hafi = self.afi_to_list(have) + # + # lookup = {(d["name"], d["afi"]): d for d in hafi} + # pairs = [(d1, lookup.get((d1["name"], d1["afi"]), {})) for d1 in wafi] + # + # for wafd, hafd in pairs: + # # self._module.fail_json(msg="wAfd: " + str(wafd) + "**** hAfd: " + str(hafd)) + # if "route_maps" in wafd: + # self._compare_route_maps(wafd, hafd) + # self.compare(parsers=afi_parsers, want=wafd, have=hafd) + # # self.compare(parsers=afi_parsers, want=wafi, have=hafi) + # + # def afi_to_list(self, data): + # """Convert address family dict to list""" + # + # return [ + # {"name": data["name"], **{**af, "afi": "ip" if af["afi"] == "ipv4" else af["afi"]}} + # for af in data["address_family"] + # ] + # + # def _compare_route_maps(self, wafd, hafd): + # want_rms = wafd.get("route_maps", []) + # have_rms = hafd.get("route_maps", []) + # + # for want in want_rms: + # match = next( + # ( + # h + # for h in have_rms + # if h["rm_name"] == want["rm_name"] and h["protocol"] == want["protocol"] + # ), + # {}, + # ) + # base = {"name": wafd["name"], "afi": wafd["afi"]} + # + # self.compare( + # parsers="route_maps", + # want={**base, "route_maps": want}, + # have={**base, "route_maps": match}, + # ) diff --git a/plugins/module_utils/network/vyos/facts/vrrp/vrrp.py b/plugins/module_utils/network/vyos/facts/vrrp/vrrp.py index 54ee0d5a..acf8ce41 100644 --- a/plugins/module_utils/network/vyos/facts/vrrp/vrrp.py +++ b/plugins/module_utils/network/vyos/facts/vrrp/vrrp.py @@ -1,146 +1,149 @@ # -*- 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 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. """ import re from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.argspec.vrrp.vrrp import ( VrrpArgs, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.rm_templates.vrrp import ( VrrpTemplate, ) class VrrpFacts(object): """The vyos vrrp facts class""" def __init__(self, module, subspec="config", options="options"): self._module = module self.argument_spec = VrrpArgs.argument_spec def get_device_data(self, connection): return connection.get('show configuration commands | match "set high-availability"') def get_config_set(self, data, connection): """To classify the configurations beased on vrrp""" config_dict = {} for config_line in data.splitlines(): vrrp_grp = re.search(r"set high-availability vrrp group (\S+).*", config_line) vrrp_snmp = re.search(r"set high-availability vrrp snmp", config_line) vrrp_gp = re.search( r"set high-availability vrrp global-parameters (\S+).*", config_line, ) vrrp_sg = re.search(r"set high-availability vrrp sync-group (\S+).*", config_line) vrrp_vsrv = re.search(r"set high-availability virtual-server (\S+).*", config_line) vrrp_disable = re.search(r"set high-availability disable", config_line) if vrrp_disable: config_dict["disable"] = config_dict.get("disable", "") + config_line + "\n" if vrrp_gp: config_dict["global_parameters"] = ( config_dict.get(vrrp_gp.group(1), "") + config_line + "\n" ) if vrrp_vsrv: config_dict[vrrp_vsrv.group(1)] = ( config_dict.get(vrrp_vsrv.group(1), "") + config_line + "\n" ) if vrrp_sg: config_dict[vrrp_sg.group(1)] = ( config_dict.get(vrrp_sg.group(1), "") + config_line + "\n" ) if vrrp_grp: config_dict[vrrp_grp.group(1)] = ( config_dict.get(vrrp_grp.group(1), "") + config_line + "\n" ) return list(config_dict.values()) def deep_merge(self, dest, src): for key, value in src.items(): if key in dest and isinstance(dest[key], dict) and isinstance(value, dict): self.deep_merge(dest[key], value) else: dest[key] = value return dest def populate_facts(self, connection, ansible_facts, data=None): """Populate the facts for vrrp network resource :param connection: the device connection :param ansible_facts: Facts dictionary :param data: previously collected conf :rtype: dictionary :returns: facts """ facts = {} objs = {} config_lines = [] if not data: data = self.get_device_data(connection) - vrrp_facts = {"virtual_servers": {}, "vrrp": {}} resources = self.get_config_set(data, connection) + vrrp_facts = {"disable": False, "virtual_servers": {}, "vrrp": {}} for resource in data.splitlines(): vrrp_parser = VrrpTemplate( lines=resource.split("\n"), module=self._module, ) objs = vrrp_parser.parse() if "disable" in objs: vrrp_facts["disable"] = objs["disable"] for section in ("virtual_servers", "vrrp"): if section in objs: for name, data in objs[section].items(): existing = vrrp_facts[section].get(name, {}) vrrp_facts[section][name] = self.deep_merge(existing, data) ansible_facts["ansible_network_resources"].pop("vrrp", None) - # self._module.fail_json(msg=str(vrrp_facts)) + # vrrp_facts = self.normalize_config(vrrp_facts) - # self._module.fail_json(msg=str(vrrp_facts)) + # + validate_parser = VrrpTemplate(lines=[], module=self._module) params = utils.remove_empties( - vrrp_parser.validate_config(self.argument_spec, {"config": vrrp_facts}, redact=True), + validate_parser.validate_config( + self.argument_spec, + {"config": vrrp_facts}, + redact=True, + ), ) facts["vrrp"] = params.get("config", []) ansible_facts["ansible_network_resources"].update(facts) - self._module.fail_json(msg=ansible_facts) - return ansible_facts def normalize_config(self, config): if not config: return config # Normalize virtual_servers if isinstance(config.get("virtual_servers"), dict): config["virtual_servers"] = list(config["virtual_servers"].values()) # Normalize vrrp vrrp = config.get("vrrp", {}) if isinstance(vrrp.get("groups"), dict): vrrp["groups"] = list(vrrp["groups"].values()) if isinstance(vrrp.get("sync_groups"), dict): vrrp["sync_groups"] = list(vrrp["sync_groups"].values()) # Normalize real_server inside each virtual_server for vs in config.get("virtual_servers", []): if isinstance(vs.get("real_servers"), dict): vs["real_servers"] = list(vs["real_servers"].values()) return config diff --git a/plugins/module_utils/network/vyos/rm_templates/vrrp.py b/plugins/module_utils/network/vyos/rm_templates/vrrp.py index a398e131..5beb1996 100644 --- a/plugins/module_utils/network/vyos/rm_templates/vrrp.py +++ b/plugins/module_utils/network/vyos/rm_templates/vrrp.py @@ -1,679 +1,679 @@ # -*- 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-server"] 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_vsrvs_rsrv(config_data): config_data = config_data["virtual-server"]["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 = "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_ts(config_data): config_data = config_data["sync-group"]["transition-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_gp(config_data): config_data = config_data["vrrp"]["global-parameters"] 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_gp_garp(config_data): config_data = config_data["vrrp"]["global-parameters"]["garp"] 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(config_data): config_data = config_data["vrrp"]["group"] 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_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"]["group"]["garp"] 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_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, "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": "sync_group.member", + "name": "vrrp.sync_group.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}}", "compval": "sync_groups.member", "result": { "vrrp": { "sync_groups": { "{{ sgname }}": { "name": "{{ sgname }}", "member": "{{ member }}", }, }, }, }, }, { - "name": "sync_group.health_check", + "name": "vrrp.sync_group.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