diff --git a/plugins/module_utils/network/vyos/config/vrf/vrf.py b/plugins/module_utils/network/vyos/config/vrf/vrf.py index a281b9f9..01ae1804 100644 --- a/plugins/module_utils/network/vyos/config/vrf/vrf.py +++ b/plugins/module_utils/network/vyos/config/vrf/vrf.py @@ -1,331 +1,332 @@ # # -*- 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_vrf 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.module_utils.six import iteritems 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.config.bgp_global.bgp_global import ( Bgp_global, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.config.ospfv2.ospfv2 import ( Ospfv2, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.config.ospfv3.ospfv3 import ( Ospfv3, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.config.static_routes.static_routes import ( Static_routes, ) 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.vrf import ( VrfTemplate, ) 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.plugins.filter.core import combine class Vrf(ResourceModule): """ The vyos_vrf config class """ def __init__(self, module): super(Vrf, self).__init__( empty_fact_val={}, facts_module=Facts(module), module=module, resource="vrf", tmplt=VrfTemplate(), ) self.parsers = [ "bind_to_all", ] def _validate_template(self): version = get_os_version(self._module) if LooseVersion(version) >= LooseVersion("1.4"): self._tmplt = VrfTemplate() else: self._module.fail_json(msg="VRF 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) # 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": w = deepcopy(wantd) if w == {} and haved != {}: self.commands = ["delete vrf"] return for k, want in iteritems(w): 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 vrf name {}".format(wname)) else: self.commands.append("delete vrf {}".format(k.replace("_", "-"))) del wantd[k] if self.state == "overridden": w = deepcopy(wantd) h = deepcopy(haved) for k, want in iteritems(w): if k in haved and 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 vrf name {}".format(wname)) self.commands.append("commit") for k, want in iteritems(wantd): if isinstance(want, list): self._compare_instances(want=want, have=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_instances(self, want, have): """Compare the instances of the VRF""" parsers = [ "table_id", "vni", "description", "disable_vrf", ] # self._module.fail_json(msg="want: " + str(want) + "**** have: " + str(have)) for entry in want: h = {} wname = entry.get("name") # h = next((vrf for vrf in have if vrf["name"] == wname), {}) h = { k: v for vrf in have if vrf.get("name") == wname for k, v in vrf.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)) self._compare_addr_family(wafi, hafi) if "protocols" in entry: for protocol_name in entry["protocols"]: # protocol_module = self._load_protocol_module(protocol_name) # protocol_commands = [] w_p_dict = entry["protocols"][protocol_name] h_p_dict = next( ( v.get("protocols", {}).get(protocol_name) for v in have if v.get("name") == wname ), {}, ) if protocol_name == "bgp": bgp_module = Bgp_global(self._module) bgp_module._validate_template() bgp_module.want = w_p_dict bgp_module.have = h_p_dict bgp_module.generate_commands() protocol_commands = bgp_module.commands elif protocol_name == "ospf": ospfv2_module = Ospfv2(self._module) ospfv2_module._module.params["config"] = w_p_dict ospfv2_module.state = self.state protocol_commands = ospfv2_module.set_config(h_p_dict) elif protocol_name == "ospfv3": ospfv3_module = Ospfv3(self._module) ospfv3_module._module.params["config"] = w_p_dict ospfv3_module.state = self.state protocol_commands = ospfv3_module.set_config(h_p_dict) elif protocol_name == "static": static_routes_module = Static_routes(self._module) static_routes_module._module.params["config"] = w_p_dict static_routes_module.state = self.state - # protocol_commands = static_routes_module.set_config(h_p_dict) - self._module.fail_json(msg=str(h_p_dict)) - # self._module.fail_json(msg= + protocol_commands = static_routes_module.set_config(h_p_dict) + # self._module.fail_json(msg=str(w_p_dict)) + # self._module.fail_json(msg="here!") else: self._module.fail_json( msg="The protocol {} is not supported".format(protocol_name), ) self.commands.extend( [ cmd.replace("protocols", "vrf name " + wname + " protocols", 1) for cmd in protocol_commands ], ) def _compare_addr_family(self, want, have): """Compare the address families of the VRF""" afi_parsers = [ # "address_family", "disable_forwarding", "disable_nht", ] # 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}, ) - def _load_protocol_module(self, protocol_name): - if protocol_name == "bgp": - from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.config.bgp_global.bgp_global import ( - Bgp_global, - ) - - return Bgp_global(self._module) - elif protocol_name == "ospf": - from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.config.ospfv2.ospfv2 import ( - Ospfv2, - ) - - return Ospfv2(self._module) - elif protocol_name == "ospfv3": - from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.config.ospfv3.ospfv3 import ( - Ospfv3, - ) - - return Ospfv3(self._module) - elif protocol_name == "static": - from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.config.static_routes.static_routes import ( - Static_routes, - ) - - return Static_routes(self._module) - else: - self._module.fail_json(msg="The protocol is not supported") + # def _load_protocol_module(self, protocol_name): + # if protocol_name == "bgp": + # from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.config.bgp_global.bgp_global import ( + # Bgp_global, + # ) + + # return Bgp_global(self._module) + # elif protocol_name == "ospf": + # from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.config.ospfv2.ospfv2 import ( + # Ospfv2, + # ) + + # return Ospfv2(self._module) + # elif protocol_name == "ospfv3": + # from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.config.ospfv3.ospfv3 import ( + # Ospfv3, + # ) + + # return Ospfv3(self._module) + # elif protocol_name == "static": + # self._module.fail_json(msg="here!") + # from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.config.static_routes.static_routes import ( + # Static_routes, + # ) + + # return Static_routes(self._module) + # else: + # self._module.fail_json(msg="The protocol is not supported") diff --git a/plugins/module_utils/network/vyos/facts/vrf/vrf.py b/plugins/module_utils/network/vyos/facts/vrf/vrf.py index 64ac011e..07045a9b 100644 --- a/plugins/module_utils/network/vyos/facts/vrf/vrf.py +++ b/plugins/module_utils/network/vyos/facts/vrf/vrf.py @@ -1,211 +1,272 @@ # -*- 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 vrf 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.vrf.vrf import VrfArgs +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.bgp_global.bgp_global import ( + Bgp_globalFacts, +) +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.ospfv2.ospfv2 import ( + Ospfv2Facts, +) +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.ospfv3.ospfv3 import ( + Ospfv3Facts, +) +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.static_routes.static_routes import ( + Static_routesFacts, +) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.rm_templates.vrf import ( VrfTemplate, ) class VrfFacts(object): """The vyos vrf facts class""" def __init__(self, module, subspec="config", options="options"): self._module = module self.argument_spec = VrfArgs.argument_spec def get_config(self, connection): return connection.get("show configuration commands | match 'set vrf'") def get_config_set(self, data, connection): """To classify the configurations beased on vrf""" config_dict = {} for config_line in data.splitlines(): vrf_inst = re.search(r"set vrf name (\S+).*", config_line) vrf_bta = re.search(r"set vrf bind-to-all", config_line) if vrf_bta: config_dict["bind_to_all"] = config_dict.get("bind_to_all", "") + config_line + "\n" if vrf_inst: config_dict[vrf_inst.group(1)] = ( config_dict.get(vrf_inst.group(1), "") + config_line + "\n" ) return list(config_dict.values()) def populate_facts(self, connection, ansible_facts, data=None): """Populate the facts for Vrf network resource :param connection: the device connection :param ansible_facts: Facts dictionary :param data: previously collected conf :rtype: dictionary :returns: facts """ facts = {} objs = [] if not data: data = self.get_config(connection) vrf_facts = {} instances = [] vrf_parser = VrfTemplate(lines=[], module=self._module) resources = self.get_config_set(data, connection) for resource in resources: vrf_parser = VrfTemplate( lines=resource.split("\n"), module=self._module, ) objs = vrf_parser.parse() if objs and "protocols" in resource: protocol_lines = [] for line in resource.strip().split("\n"): if "protocols" in line: idx = line.index("protocols") protocol_lines.append("set " + line[idx:]) objs["protocols"] = self._parse_protocols("\n".join(protocol_lines)) if objs: if "bind_to_all" in objs: vrf_facts.update(objs) if "name" in objs: # for key, afiv in [("address_family", "afi")]: # if key in objs and objs[key]: # # self._module.fail_json(msg=str(objs[key])) # objs[key] = list(objs[key].values()) instances.append(self._normalise_instance(objs)) if instances: vrf_facts.update({"instances": instances}) ansible_facts["ansible_network_resources"].pop("vrf_facts", None) facts = {"vrf": []} params = utils.remove_empties( vrf_parser.validate_config( self.argument_spec, {"config": vrf_facts}, redact=True, ), ) if not resources: params["config"].pop("bind_to_all", None) if params.get("config"): facts["vrf"] = params["config"] ansible_facts["ansible_network_resources"].update(facts) # self._module.fail_json(msg=ansible_facts) return ansible_facts def _normalise_instance(self, instance): n_inst = instance.copy() af_map = {} for af in instance.get("address_family", []): afi = af.get("afi") if not afi: continue if afi not in af_map: af_map[afi] = {"afi": afi} for k, v in af.items(): if k == "afi": continue elif k == "route_maps": if "route_maps" not in af_map[afi]: af_map[afi]["route_maps"] = [] af_map[afi]["route_maps"].extend(v) else: af_map[afi][k] = v for afi_data in af_map.values(): if "route_maps" in afi_data: seen = [] deduped = [] for item in afi_data["route_maps"]: if item not in seen: seen.append(item) deduped.append(item) afi_data["route_maps"] = deduped n_inst["address_family"] = list(af_map.values()) return n_inst def _parse_protocols(self, protocols): """Parse protocols and return a dictionary""" protocol_chunks = {} parsed_protocols = {} for line in protocols.split("\n"): parts = line.split() if len(parts) > 2 and parts[0] == "set" and parts[1] == "protocols": protocol = parts[2] protocol_chunks.setdefault(protocol, []).append(line) protocol_strings = {proto: "\n".join(lines) for proto, lines in protocol_chunks.items()} for protocol_name, protocol_string in protocol_strings.items(): - protocol_module = self._load_protocol_module(protocol_name) - protocol_dict = protocol_module.populate_facts( - connection=self._module._connection, - ansible_facts={"ansible_network_resources": {}}, - data=protocol_string, - ) - parsed_protocols[protocol_name] = list( - protocol_dict.get("ansible_network_resources").values(), - )[0] + # protocol_module = self._load_protocol_module(protocol_name) + protocol_dict = {} + # protocol_dict = protocol_module.populate_facts( + # connection=self._module._connection, + # ansible_facts={"ansible_network_resources": {}}, + # data=protocol_string, + # ) + # parsed_protocols[protocol_name] = list( + # protocol_dict.get("ansible_network_resources").values(), + # )[0] + + if protocol_name == "bgp": + bgp_module = Bgp_globalFacts(self._module) + protocol_dict = bgp_module.populate_facts( + connection=self._module._connection, + ansible_facts={"ansible_network_resources": {}}, + data=protocol_string, + ) + parsed_protocols[protocol_name] = list( + protocol_dict.get("ansible_network_resources").values(), + )[0] + + elif protocol_name == "ospf": + ospf_module = Ospfv2Facts(self._module) + protocol_dict = ospf_module.populate_facts( + connection=self._module._connection, + ansible_facts={"ansible_network_resources": {}}, + data=protocol_string, + ) + parsed_protocols[protocol_name] = list( + protocol_dict.get("ansible_network_resources").values(), + )[0] + + elif protocol_name == "ospfv3": + ospfv3_module = Ospfv3Facts(self._module) + protocol_dict = ospfv3_module.populate_facts( + connection=self._module._connection, + ansible_facts={"ansible_network_resources": {}}, + data=protocol_string, + ) + parsed_protocols[protocol_name] = list( + protocol_dict.get("ansible_network_resources").values(), + )[0] + + elif protocol_name == "static": + static_routes_module = Static_routesFacts(self._module) + # self._module.fail_json(msg=protocol_string) + protocol_dict = static_routes_module.populate_facts( + connection=self._module._connection, + ansible_facts={"ansible_network_resources": {}}, + data=protocol_string, + ) + parsed_protocols[protocol_name] = list( + protocol_dict.get("ansible_network_resources").values(), + )[0] + # parsed_protocols[protocol_name] = [] + else: + self._module.fail_json(msg="The protocol is not supported" + protocol_name) # self._module.fail_json(msg=parsed_protocols) return parsed_protocols - def _load_protocol_module(self, protocol_name): - if protocol_name == "bgp": - from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.bgp_global.bgp_global import ( - Bgp_globalFacts, - ) - - return Bgp_globalFacts(self._module) - elif protocol_name == "ospf": - from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.ospfv2.ospfv2 import ( - Ospfv2Facts, - ) - - return Ospfv2Facts(self._module) - elif protocol_name == "ospfv3": - from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.ospfv3.ospfv3 import ( - Ospfv3Facts, - ) - - return Ospfv3Facts(self._module) - elif protocol_name == "static": - from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.static_routes.static_routes import ( - Static_routesFacts, - ) - - return Static_routesFacts(self._module) - else: - self._module.fail_json(msg="The protocol is not supported") + # def _load_protocol_module(self, protocol_name): + # if protocol_name == "bgp": + # from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.bgp_global.bgp_global import ( + # Bgp_globalFacts, + # ) + + # return Bgp_globalFacts(self._module) + # elif protocol_name == "ospf": + # from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.ospfv2.ospfv2 import ( + # Ospfv2Facts, + # ) + + # return Ospfv2Facts(self._module) + # elif protocol_name == "ospfv3": + # from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.ospfv3.ospfv3 import ( + # Ospfv3Facts, + # ) + + # return Ospfv3Facts(self._module) + # elif protocol_name == "static": + # from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.static_routes.static_routes import ( + # Static_routesFacts, + # ) + + # return Static_routesFacts(self._module) + # else: + # self._module.fail_json(msg="The protocol is not supported")