diff options
author | omnom62 <75066712+omnom62@users.noreply.github.com> | 2025-05-16 07:55:33 +1000 |
---|---|---|
committer | GitHub <noreply@github.com> | 2025-05-16 07:55:33 +1000 |
commit | cd2f41d34ff22ea7719348ec18a30f1c499b72c8 (patch) | |
tree | 0d1adfa900eea95c71f0058c8d929a528ed1850a | |
parent | 098f31c6fe7a6310594656efc0af5a2739ede82b (diff) | |
download | vyos.vyos-cd2f41d34ff22ea7719348ec18a30f1c499b72c8.tar.gz vyos.vyos-cd2f41d34ff22ea7719348ec18a30f1c499b72c8.zip |
T6883 route-map extras support (#402)
* t6883 as_prepend_path fix
* chaneglog
* draft for rm community handling
* rm updates
* unit and integration tests for route maps
* rm unit tests fixed
* 1.4+ rm integration tests draft
* rm set_community parser
* large-community 1.4+
* unit tests fix for rm
* extcommunity config
* pre-commit
* cleanup
* RM bandwidth clauses
* fixing parsers index for 1.3 RM
* updated dic
* fix unit tests
* unit tests for RM
* RM unit tests
* dict literal for reserved keywords in unit tests
* set_src ipv6 replaced unit test
* unit test case for RM call
* RM action type unit tests
* rm match protocol code and unit test
* doc update
* set_table RM
* set_table RM unit test
* unit tests refactor
* copilot recommendations
12 files changed, 2564 insertions, 13 deletions
diff --git a/changelogs/fragments/T6821_route_map_options.yml b/changelogs/fragments/T6821_route_map_options.yml new file mode 100644 index 00000000..6d1e974b --- /dev/null +++ b/changelogs/fragments/T6821_route_map_options.yml @@ -0,0 +1,3 @@ +--- +minor_changes: + - vyos_route_maps - add support for as-path-prepend policy option diff --git a/docs/vyos.vyos.vyos_route_maps_module.rst b/docs/vyos.vyos.vyos_route_maps_module.rst index 4e0c5322..f6a74d46 100644 --- a/docs/vyos.vyos.vyos_route_maps_module.rst +++ b/docs/vyos.vyos.vyos_route_maps_module.rst @@ -667,6 +667,38 @@ Parameters <td class="elbow-placeholder"></td> <td colspan="3"> <div class="ansibleOptionAnchor" id="parameter-"></div> + <b>protocol</b> + <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a> + <div style="font-size: small"> + <span style="color: purple">string</span> + </div> + </td> + <td> + <ul style="margin: 0; padding: 0"><b>Choices:</b> + <li>babel</li> + <li>bgp</li> + <li>connected</li> + <li>isis</li> + <li>kernel</li> + <li>ospf</li> + <li>ospfv3</li> + <li>rip</li> + <li>ripng</li> + <li>static</li> + <li>table</li> + <li>vnc</li> + </ul> + </td> + <td> + <div>Source protocol to match.</div> + </td> + </tr> + <tr> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td colspan="3"> + <div class="ansibleOptionAnchor" id="parameter-"></div> <b>rpki</b> <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a> <div style="font-size: small"> @@ -1015,6 +1047,46 @@ Parameters <td class="elbow-placeholder"></td> <td colspan="3"> <div class="ansibleOptionAnchor" id="parameter-"></div> + <b>extcommunity_bandwidth</b> + <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a> + <div style="font-size: small"> + <span style="color: purple">string</span> + </div> + </td> + <td> + </td> + <td> + <div>Set Bandwidth of Origin value. 1-25600|cumulative|num-multipaths VPN extended community</div> + </td> + </tr> + <tr> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td colspan="3"> + <div class="ansibleOptionAnchor" id="parameter-"></div> + <b>extcommunity_bandwidth_non_transitive</b> + <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a> + <div style="font-size: small"> + <span style="color: purple">boolean</span> + </div> + </td> + <td> + <ul style="margin: 0; padding: 0"><b>Choices:</b> + <li>no</li> + <li>yes</li> + </ul> + </td> + <td> + <div>Set the bandwidth extended community encoded as non-transitive True/False VPN extended community</div> + </td> + </tr> + <tr> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td colspan="3"> + <div class="ansibleOptionAnchor" id="parameter-"></div> <b>extcommunity_rt</b> <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a> <div style="font-size: small"> @@ -1265,6 +1337,24 @@ Parameters <td class="elbow-placeholder"></td> <td colspan="3"> <div class="ansibleOptionAnchor" id="parameter-"></div> + <b>table</b> + <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a> + <div style="font-size: small"> + <span style="color: purple">string</span> + </div> + </td> + <td> + </td> + <td> + <div>Set prefixes to table. Example <1-200></div> + </td> + </tr> + <tr> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td class="elbow-placeholder"></td> + <td colspan="3"> + <div class="ansibleOptionAnchor" id="parameter-"></div> <b>tag</b> <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a> <div style="font-size: small"> diff --git a/plugins/module_utils/network/vyos/argspec/route_maps/route_maps.py b/plugins/module_utils/network/vyos/argspec/route_maps/route_maps.py index 196db0c7..5b6d404d 100644 --- a/plugins/module_utils/network/vyos/argspec/route_maps/route_maps.py +++ b/plugins/module_utils/network/vyos/argspec/route_maps/route_maps.py @@ -74,6 +74,8 @@ class Route_mapsArgs(object): # pylint: disable=R0903 }, "extcommunity_rt": {"type": "str"}, "extcommunity_soo": {"type": "str"}, + "extcommunity_bandwidth": {"type": "str"}, + "extcommunity_bandwidth_non_transitive": {"type": "bool"}, "ip_next_hop": {"type": "str"}, "ipv6_next_hop": { "type": "dict", @@ -100,6 +102,7 @@ class Route_mapsArgs(object): # pylint: disable=R0903 "src": {"type": "str"}, "tag": {"type": "str"}, "weight": {"type": "str"}, + "table": {"type": "str"}, }, }, "match": { @@ -178,6 +181,23 @@ class Route_mapsArgs(object): # pylint: disable=R0903 "next_hop": {"type": "str"}, }, }, + "protocol": { + "type": "str", + "choices": [ + "babel", + "bgp", + "connected", + "isis", + "kernel", + "ospf", + "ospfv3", + "rip", + "ripng", + "static", + "table", + "vnc", + ], + }, "large_community_large_community_list": { "type": "str", }, diff --git a/plugins/module_utils/network/vyos/config/route_maps/route_maps.py b/plugins/module_utils/network/vyos/config/route_maps/route_maps.py index 9b6c3e9d..9692a253 100644 --- a/plugins/module_utils/network/vyos/config/route_maps/route_maps.py +++ b/plugins/module_utils/network/vyos/config/route_maps/route_maps.py @@ -31,6 +31,13 @@ from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.facts from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.rm_templates.route_maps import ( Route_mapsTemplate, ) +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.rm_templates.route_maps_14 import ( + Route_mapsTemplate14, +) +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 Route_maps(ResourceModule): @@ -59,6 +66,8 @@ class Route_maps(ResourceModule): "set_bgp_extcommunity_rt", "set_extcommunity_rt", "set_extcommunity_soo", + "set_extcommunity_bandwidth", + "set_extcommunity_bandwidth_non_transitive", "set_ip_next_hop", "set_ipv6_next_hop", "set_large_community", @@ -70,6 +79,7 @@ class Route_maps(ResourceModule): "set_src", "set_tag", "set_weight", + "set_table", "set_comm_list", "set_comm_list_delete", "set_community", @@ -89,15 +99,34 @@ class Route_maps(ResourceModule): "on_match_next", "match_ipv6_address", "match_ipv6_nexthop", + "match_protocol", "match_rpki", ] + def _validate_template(self): + version = get_os_version(self._module) + if LooseVersion(version) >= LooseVersion("1.4"): + self._tmplt = Route_mapsTemplate14() + else: + self._tmplt = Route_mapsTemplate() + + 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() diff --git a/plugins/module_utils/network/vyos/facts/route_maps/route_maps.py b/plugins/module_utils/network/vyos/facts/route_maps/route_maps.py index 2ad54e63..cfae26e3 100644 --- a/plugins/module_utils/network/vyos/facts/route_maps/route_maps.py +++ b/plugins/module_utils/network/vyos/facts/route_maps/route_maps.py @@ -25,6 +25,13 @@ from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.argspec.rou from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.rm_templates.route_maps import ( Route_mapsTemplate, ) +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.rm_templates.route_maps_14 import ( + Route_mapsTemplate14, +) +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 Route_mapsFacts(object): @@ -59,11 +66,18 @@ class Route_mapsFacts(object): """ facts = {} objs = [] + + if LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4"): + route_maps_class = Route_mapsTemplate14 + else: + route_maps_class = Route_mapsTemplate + if not data: data = self.get_config(connection) # parse native config using the Route_maps template - route_maps_parser = Route_mapsTemplate(lines=data.splitlines()) + route_maps_parser = route_maps_class(lines=data.splitlines()) + if route_maps_parser.parse().get("route_maps"): objs = list(route_maps_parser.parse().get("route_maps").values()) for item in objs: diff --git a/plugins/module_utils/network/vyos/rm_templates/route_maps.py b/plugins/module_utils/network/vyos/rm_templates/route_maps.py index 8f218a6b..c6b88f7b 100644 --- a/plugins/module_utils/network/vyos/rm_templates/route_maps.py +++ b/plugins/module_utils/network/vyos/rm_templates/route_maps.py @@ -310,12 +310,12 @@ class Route_mapsTemplate(NetworkTemplate): "name": "set_as_path_prepend", "getval": re.compile( r""" - ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\sset\sas-path-prepend\s(?P<as>\S+) - *$""", + ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\sset\sas-path-prepend\s(?P<as>.*) + $""", re.VERBOSE, ), "compval": "set.as_path_prepend", - "setval": "policy route-map {{route_map}} rule {{sequence}} set as-path-prepend {{set.as_path_prepend}}", + "setval": "policy route-map {{route_map}} rule {{sequence}} set as-path-prepend '{{set.as_path_prepend}}'", "result": { "route_maps": { "{{ route_map }}": { @@ -337,10 +337,11 @@ class Route_mapsTemplate(NetworkTemplate): "name": "set_atomic_aggregate", "getval": re.compile( r""" - ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\sset\satomic-aggregate(?P<as>) + ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\sset\s(?P<as>atomic-aggregate) *$""", re.VERBOSE, ), + "compval": "set.atomic_aggregate", "setval": "policy route-map {{route_map}} rule {{sequence}} set atomic-aggregate", "result": { "route_maps": { @@ -391,13 +392,13 @@ class Route_mapsTemplate(NetworkTemplate): "name": "set_comm_list", "getval": re.compile( r""" - ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\sset\scomm-list\scomm-list\s(?P<comm_list>\S+) + ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\smatch\scommunity\scommunity-list\s(?P<comm_list>\S+) *$""", re.VERBOSE, ), - "compval": "set.comm_list.comm_list", + "compval": "match.community.community_list", "setval": "policy route-map {{route_map}} rule {{sequence}} " - "set comm-list comm-list {{set.comm_list.comm_list}}", + "match community community-list {{set.comm_list.comm_list}}", "result": { "route_maps": { "{{ route_map }}": { @@ -406,8 +407,8 @@ class Route_mapsTemplate(NetworkTemplate): "{{sequence}}": { "sequence": "{{sequence}}", - "set": { - "comm_list": {"comm_list": "{{comm_list}}"}, + "match": { + "community": {"community_list": "{{comm_list}}"}, }, }, }, @@ -500,6 +501,62 @@ class Route_mapsTemplate(NetworkTemplate): }, }, { + "name": "set_extcommunity_bandwidth", + "getval": re.compile( + r""" + ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\sset\sextcommunity\sbandwidth\s(?P<extcommunity_bw>\S+) + *$""", + re.VERBOSE, + ), + "compval": "set.extcommunity_bandwidth", + "setval": "policy route-map {{route_map}} rule {{sequence}} " + "set extcommunity bandwidth {{set.extcommunity_bandwidth}}", + "result": { + "route_maps": { + "{{ route_map }}": { + "route_map": '{{ route_map }}', + "entries": { + "{{sequence}}": + { + "sequence": "{{sequence}}", + "set": { + "extcommunity_bandwidth": "{{extcommunity_bw}}", + }, + }, + }, + }, + }, + }, + }, + { + "name": "set_extcommunity_bandwidth_non_transitive", + "getval": re.compile( + r""" + ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\sset\sextcommunity\s(?P<extcommunity_bw_nt>bandwidth-non-transitive) + *$""", + re.VERBOSE, + ), + "compval": "set.extcommunity_bandwidth_non_transitive", + "setval": "policy route-map {{route_map}} rule {{sequence}} " + "set extcommunity bandwidth-non-transitive", + "result": { + "route_maps": { + "{{ route_map }}": { + "route_map": '{{ route_map }}', + "entries": { + "{{sequence}}": + { + "sequence": "{{sequence}}", + "set": { + "extcommunity_bandwidth_non_transitive": "{{True if extcommunity_bw_nt is defined}}", + }, + }, + }, + }, + }, + }, + }, + { "name": "set_ip_next_hop", "getval": re.compile( r""" @@ -813,6 +870,34 @@ class Route_mapsTemplate(NetworkTemplate): }, }, { + "name": "set_table", + "getval": re.compile( + r""" + ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\sset\stable\s(?P<table>\S+) + *$""", + re.VERBOSE, + ), + "compval": "set.weight", + "setval": "policy route-map {{route_map}} rule {{sequence}} " + "set table {{set.table}}", + "result": { + "route_maps": { + "{{ route_map }}": { + "route_map": '{{ route_map }}', + "entries": { + "{{sequence}}": + { + "sequence": "{{sequence}}", + "set": { + "table": "{{table}}", + }, + }, + }, + }, + }, + }, + }, + { "name": "set_community", "getval": re.compile( r""" @@ -1261,6 +1346,33 @@ class Route_mapsTemplate(NetworkTemplate): }, }, { + "name": "match_protocol", + "getval": re.compile( + r""" + ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\smatch\sprotocol\s(?P<value>\S+) + *$""", + re.VERBOSE, + ), + "compval": "match.protocol", + "setval": "policy route-map {{route_map}} rule {{sequence}} " + "match protocol {{match.protocol}}", + "result": { + "route_maps": { + "{{ route_map }}": { + "route_map": '{{ route_map }}', + "entries": { + "{{sequence}}": { + "sequence": "{{sequence}}", + "match": { + "protocol": "{{value}}", + }, + }, + }, + }, + }, + }, + }, + { "name": "match_rpki", "getval": re.compile( r""" diff --git a/plugins/module_utils/network/vyos/rm_templates/route_maps_14.py b/plugins/module_utils/network/vyos/rm_templates/route_maps_14.py new file mode 100644 index 00000000..6564280d --- /dev/null +++ b/plugins/module_utils/network/vyos/rm_templates/route_maps_14.py @@ -0,0 +1,1405 @@ +# -*- 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 Route_maps 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_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.network_template import ( + NetworkTemplate, +) + + +class Route_mapsTemplate14(NetworkTemplate): + def __init__(self, lines=None): + prefix = {"set": "set", "remove": "delete"} + super(Route_mapsTemplate14, self).__init__(lines=lines, tmplt=self, prefix=prefix) + + # fmt: off + PARSERS = [ + { + "name": "route_map", + "getval": re.compile( + r""" + ^set\spolicy\sroute-map\s(?P<route_map>\S+) + *$""", + re.VERBOSE, + ), + "compval": "route_map", + "setval": "policy route-map {{route_map}}", + "result": { + "route_maps": { + "{{ route_map }}": { + "route_map": '{{ route_map }}', + }, + }, + }, + }, + { + "name": "sequence", + "getval": re.compile( + r""" + ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+) + *$""", + re.VERBOSE, + ), + "compval": "sequence", + "setval": "policy route-map {{route_map}} rule {{sequence}}", + "result": { + "route_maps": { + "{{ route_map }}": { + "route_map": '{{ route_map }}', + "entries": { + "{{sequence}}": + { + "sequence": "{{sequence}}", + }, + }, + }, + }, + }, + }, + { + "name": "call", + "getval": re.compile( + r""" + ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\scall\s(?P<call>\S+) + *$""", + re.VERBOSE, + ), + "setval": "policy route-map {{route_map}} rule {{sequence}} call {{call}}", + "result": { + "route_maps": { + "{{ route_map }}": { + "route_map": '{{ route_map }}', + "entries": { + "{{sequence}}": + { + "sequence": "{{sequence}}", + "call": "{{call}}", + }, + }, + }, + }, + }, + }, + { + "name": "description", + "getval": re.compile( + r""" + ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\sdescription\s(?P<description>\S+) + *$""", + re.VERBOSE, + ), + "setval": "policy route-map {{route_map}} rule {{sequence}} description {{description}}", + "result": { + "route_maps": { + "{{ route_map }}": { + "route_map": '{{ route_map }}', + "entries": { + "{{sequence}}": + { + "sequence": "{{sequence}}", + "description": "{{description}}", + }, + }, + }, + }, + }, + }, + { + "name": "action", + "getval": re.compile( + r""" + ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\saction\s(?P<action>\S+) + *$""", + re.VERBOSE, + ), + "setval": "policy route-map {{route_map}} rule {{sequence}} action {{action}}", + "result": { + "route_maps": { + "{{ route_map }}": { + "route_map": '{{ route_map }}', + "entries": { + "{{sequence}}": + { + "sequence": "{{sequence}}", + "action": "{{action}}", + }, + }, + }, + }, + }, + }, + { + "name": "continue_sequence", + "getval": re.compile( + r""" + ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\scontinue\s(?P<continue>\S+) + *$""", + re.VERBOSE, + ), + "setval": "policy route-map {{route_map}} rule {{sequence}} continue {{continue_sequence}}", + "result": { + "route_maps": { + "{{ route_map }}": { + "route_map": '{{ route_map }}', + "entries": { + "{{sequence}}": + { + "sequence": "{{sequence}}", + "continue_sequence": "{{continue}}", + }, + }, + }, + }, + }, + }, + { + "name": "on_match_next", + "getval": re.compile( + r""" + ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\son-match\s(?P<next>next) + *$""", + re.VERBOSE, + ), + "compval": "on_match.next", + "setval": "policy route-map {{route_map}} rule {{sequence}} on-match next", + "result": { + "route_maps": { + "{{ route_map }}": { + "route_map": '{{ route_map }}', + "entries": { + "{{sequence}}": + { + "sequence": "{{sequence}}", + "on_match": { + "next": "{{True if next is defined}}", + }, + }, + }, + }, + }, + }, + }, + { + "name": "on_match_goto", + "getval": re.compile( + r""" + ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\son-match\sgoto\s(?P<goto>\S+) + *$""", + re.VERBOSE, + ), + "compval": "on_match.goto", + "setval": "policy route-map {{route_map}} rule {{sequence}} on-match goto {{on_match.goto}}", + "result": { + "route_maps": { + "{{ route_map }}": { + "route_map": '{{ route_map }}', + "entries": { + "{{sequence}}": + { + "sequence": "{{sequence}}", + "on_match": { + "goto": "{{goto}}", + }, + }, + }, + }, + }, + }, + }, + { + "name": "set_aggregator_ip", + "getval": re.compile( + r""" + ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\sset\saggregator\sip\s(?P<ip>\S+) + *$""", + re.VERBOSE, + ), + "compval": "set.aggregator.ip", + "setval": "policy route-map {{route_map}} rule {{sequence}} set aggregator ip {{set.aggregator.ip}}", + "result": { + "route_maps": { + "{{ route_map }}": { + "route_map": '{{ route_map }}', + "entries": { + "{{sequence}}": + { + "sequence": "{{sequence}}", + "set": { + "aggregator": { + "ip": "{{ip}}", + }, + }, + }, + }, + }, + }, + }, + }, + { + "name": "set_aggregator_as", + "getval": re.compile( + r""" + ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\sset\saggregator\sas\s(?P<as>\S+) + *$""", + re.VERBOSE, + ), + "compval": "set.aggregator.as", + "setval": "policy route-map {{route_map}} rule {{sequence}} set aggregator as {{set.aggregator.as}}", + "result": { + "route_maps": { + "{{ route_map }}": { + "route_map": '{{ route_map }}', + "entries": { + "{{sequence}}": + { + "sequence": "{{sequence}}", + "set": { + "aggregator": { + "as": "{{as}}", + }, + }, + }, + }, + }, + }, + }, + }, + { + "name": "set_as_path_exclude", + "getval": re.compile( + r""" + ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\sset\sas-path\sexclude\s(?P<as>\S+) + *$""", + re.VERBOSE, + ), + "compval": "set.as_path_exclude", + "setval": "policy route-map {{route_map}} rule {{sequence}} set as-path exclude {{set.as_path_exclude}}", + "result": { + "route_maps": { + "{{ route_map }}": { + "route_map": '{{ route_map }}', + "entries": { + "{{sequence}}": + { + "sequence": "{{sequence}}", + "set": { + "as_path_exclude": "{{as}}", + }, + }, + }, + }, + }, + }, + }, + { + "name": "set_as_path_prepend", + "getval": re.compile( + r""" + ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\sset\sas-path\sprepend\s(?P<as>.*) + $""", + re.VERBOSE, + ), + "compval": "set.as_path_prepend", + "setval": "policy route-map {{route_map}} rule {{sequence}} set as-path prepend '{{set.as_path_prepend}}'", + "result": { + "route_maps": { + "{{ route_map }}": { + "route_map": '{{ route_map }}', + "entries": { + "{{sequence}}": + { + "sequence": "{{sequence}}", + "set": { + "as_path_prepend": "{{as}}", + }, + }, + }, + }, + }, + }, + }, + { + "name": "set_atomic_aggregate", + "getval": re.compile( + r""" + ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\sset\s(?P<as>atomic-aggregate) + *$""", + re.VERBOSE, + ), + "compval": "set.atomic_aggregate", + "setval": "policy route-map {{route_map}} rule {{sequence}} set atomic-aggregate", + "result": { + "route_maps": { + "{{ route_map }}": { + "route_map": '{{ route_map }}', + "entries": { + "{{sequence}}": + { + "sequence": "{{sequence}}", + "set": { + "atomic_aggregate": "{{True if as is defined}}", + }, + }, + }, + }, + }, + }, + }, + { + "name": "set_bgp_extcommunity_rt", + "getval": re.compile( + r""" + ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\sset\sbgp-extcommunity-rt\s(?P<bgp>\S+) + *$""", + re.VERBOSE, + ), + "compval": "set.bgp_extcommunity_rt", + "setval": "policy route-map {{route_map}} rule {{sequence}} " + "set bgp-extcommunity-rt {{set.bgp_extcommunity_rt}}", + "result": { + "route_maps": { + "{{ route_map }}": { + "route_map": '{{ route_map }}', + "entries": { + "{{sequence}}": + { + "sequence": "{{sequence}}", + "set": { + "bgp_extcommunity_rt": "{{bgp}}", + }, + }, + }, + }, + }, + }, + }, + { + "name": "set_comm_list", + "getval": re.compile( + r""" + ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\smatch\scommunity\scommunity-list\s(?P<comm_list>\S+) + *$""", + re.VERBOSE, + ), + "compval": "match.community.community_list", + "setval": "policy route-map {{route_map}} rule {{sequence}} " + "match community community-list {{set.comm_list.comm_list}}", + "result": { + "route_maps": { + "{{ route_map }}": { + "route_map": '{{ route_map }}', + "entries": { + "{{sequence}}": + { + "sequence": "{{sequence}}", + "match": { + "community": {"community_list": "{{comm_list}}"}, + }, + }, + }, + }, + }, + }, + }, + { + "name": "set_comm_list_delete", + "getval": re.compile( + r""" + ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\sset\scomm-list\sdelete(?P<delete>\S+) + *$""", + re.VERBOSE, + ), + "compval": "set.comm_list.comm_list", + "setval": "policy route-map {{route_map}} rule {{sequence}} " + "set comm-list delete", + "result": { + "route_maps": { + "{{ route_map }}": { + "route_map": '{{ route_map }}', + "entries": { + "{{sequence}}": + { + "sequence": "{{sequence}}", + "set": { + "comm_list": {"delete": "{{True if delete is defined}}"}, + }, + }, + }, + }, + }, + }, + }, + { + "name": "set_extcommunity_rt", + "getval": re.compile( + r""" + ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\sset\sextcommunity\srt\s(?P<extcommunity_rt>\S+) + *$""", + re.VERBOSE, + ), + "compval": "set.extcommunity_rt", + "setval": "policy route-map {{route_map}} rule {{sequence}} " + "set extcommunity rt {{set.extcommunity_rt}}", + "result": { + "route_maps": { + "{{ route_map }}": { + "route_map": '{{ route_map }}', + "entries": { + "{{sequence}}": + { + "sequence": "{{sequence}}", + "set": { + "extcommunity_rt": "{{extcommunity_rt}}", + }, + }, + }, + }, + }, + }, + }, + { + "name": "set_extcommunity_soo", + "getval": re.compile( + r""" + ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\sset\sextcommunity\ssoo\s(?P<extcommunity_soo>\S+) + *$""", + re.VERBOSE, + ), + "compval": "set.extcommunity_soo", + "setval": "policy route-map {{route_map}} rule {{sequence}} " + "set extcommunity soo {{set.extcommunity_soo}}", + "result": { + "route_maps": { + "{{ route_map }}": { + "route_map": '{{ route_map }}', + "entries": { + "{{sequence}}": + { + "sequence": "{{sequence}}", + "set": { + "extcommunity_soo": "{{extcommunity_soo}}", + }, + }, + }, + }, + }, + }, + }, + { + "name": "set_extcommunity_bandwidth", + "getval": re.compile( + r""" + ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\sset\sextcommunity\sbandwidth\s(?P<extcommunity_bw>\S+) + *$""", + re.VERBOSE, + ), + "compval": "set.extcommunity_bandwidth", + "setval": "policy route-map {{route_map}} rule {{sequence}} " + "set extcommunity bandwidth {{set.extcommunity_bandwidth}}", + "result": { + "route_maps": { + "{{ route_map }}": { + "route_map": '{{ route_map }}', + "entries": { + "{{sequence}}": + { + "sequence": "{{sequence}}", + "set": { + "extcommunity_bandwidth": "{{extcommunity_bw}}", + }, + }, + }, + }, + }, + }, + }, + { + "name": "set_extcommunity_bandwidth_non_transitive", + "getval": re.compile( + r""" + ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\sset\sextcommunity\s(?P<extcommunity_bw_nt>bandwidth-non-transitive) + *$""", + re.VERBOSE, + ), + "compval": "set.extcommunity_bandwidth_non_transitive", + "setval": "policy route-map {{route_map}} rule {{sequence}} " + "set extcommunity bandwidth-non-transitive", + "result": { + "route_maps": { + "{{ route_map }}": { + "route_map": '{{ route_map }}', + "entries": { + "{{sequence}}": + { + "sequence": "{{sequence}}", + "set": { + "extcommunity_bandwidth_non_transitive": "{{True if extcommunity_bw_nt is defined}}", + }, + }, + }, + }, + }, + }, + }, + { + "name": "set_ip_next_hop", + "getval": re.compile( + r""" + ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\sset\sip-next-hop\s(?P<ip_next_hop>\S+) + *$""", + re.VERBOSE, + ), + "compval": "set.ip_next_hop", + "setval": "policy route-map {{route_map}} rule {{sequence}} " + "set ip-next-hop {{set.ip_next_hop}}", + "result": { + "route_maps": { + "{{ route_map }}": { + "route_map": '{{ route_map }}', + "entries": { + "{{sequence}}": + { + "sequence": "{{sequence}}", + "set": { + "ip_next_hop": "{{ip_next_hop}}", + }, + }, + }, + }, + }, + }, + }, + { + "name": "set_ipv6_next_hop", + "getval": re.compile( + r""" + ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\sset\sipv6-next-hop + \s(?P<type>global|local) + \s(?P<value>\S+) + *$""", + re.VERBOSE, + ), + "compval": "set.ipv6_next_hop", + "setval": "policy route-map {{route_map}} rule {{sequence}} " + "set ipv6-next-hop {{set.ipv6_next_hop.ip_type}} {{set.ipv6_next_hop.value}}", + "result": { + "route_maps": { + "{{ route_map }}": { + "route_map": '{{ route_map }}', + "entries": { + "{{sequence}}": + { + "sequence": "{{sequence}}", + "set": { + "ipv6_next_hop": { + "ip_type": "{{type}}", + "value": "{{value}}", + }, + }, + }, + }, + }, + }, + }, + }, + { + "name": "set_large_community", + "getval": re.compile( + r""" + ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\sset\slarge-community\s(?P<op>none|replace\s(?P<large_community>\S+)) + $""", + re.VERBOSE, + ), + "compval": "set.large_community", + "setval": "policy route-map {{route_map}} rule {{sequence}} " + "set large-community {{set.large_community if set.large_community == 'none' else 'replace ' + set.large_community}}", + "result": { + "route_maps": { + "{{ route_map }}": { + "route_map": '{{ route_map }}', + "entries": { + "{{sequence}}": + { + "sequence": "{{sequence}}", + "set": { + "large_community": "{{op if op == 'none' else large_community}}", + }, + }, + }, + }, + }, + }, + }, + { + "name": "set_local_preference", + "getval": re.compile( + r""" + ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\sset\slocal-preference\s(?P<local_preference>\S+) + *$""", + re.VERBOSE, + ), + "compval": "set.local_preference", + "setval": "policy route-map {{route_map}} rule {{sequence}} " + "set local-preference {{set.local_preference}}", + "result": { + "route_maps": { + "{{ route_map }}": { + "route_map": '{{ route_map }}', + "entries": { + "{{sequence}}": + { + "sequence": "{{sequence}}", + "set": { + "local_preference": "{{local_preference}}", + }, + }, + }, + }, + }, + }, + }, + { + "name": "set_metric", + "getval": re.compile( + r""" + ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\sset\smetric\s(?P<metric>\S+) + *$""", + re.VERBOSE, + ), + "compval": "set.metric", + "setval": "policy route-map {{route_map}} rule {{sequence}} " + "set metric {{set.metric}}", + "result": { + "route_maps": { + "{{ route_map }}": { + "route_map": '{{ route_map }}', + "entries": { + "{{sequence}}": + { + "sequence": "{{sequence}}", + "set": { + "metric": "{{metric}}", + }, + }, + }, + }, + }, + }, + }, + { + "name": "set_metric_type", + "getval": re.compile( + r""" + ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\sset\smetric-type\s(?P<metric_type>\S+) + *$""", + re.VERBOSE, + ), + "compval": "set.metric_type", + "setval": "policy route-map {{route_map}} rule {{sequence}} " + "set metric-type {{set.metric_type}}", + "result": { + "route_maps": { + "{{ route_map }}": { + "route_map": '{{ route_map }}', + "entries": { + "{{sequence}}": + { + "sequence": "{{sequence}}", + "set": { + "metric_type": "{{metric_type}}", + }, + }, + }, + }, + }, + }, + }, + { + "name": "set_origin", + "getval": re.compile( + r""" + ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\sset\sorigin\s(?P<origin>\S+) + *$""", + re.VERBOSE, + ), + "compval": "set.origin", + "setval": "policy route-map {{route_map}} rule {{sequence}} " + "set origin {{set.origin}}", + "result": { + "route_maps": { + "{{ route_map }}": { + "route_map": '{{ route_map }}', + "entries": { + "{{sequence}}": + { + "sequence": "{{sequence}}", + "set": { + "origin": "{{origin}}", + }, + }, + }, + }, + }, + }, + }, + { + "name": "set_originator_id", + "getval": re.compile( + r""" + ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\sset\soriginator-id\s(?P<originator_id>\S+) + *$""", + re.VERBOSE, + ), + "compval": "set.originator_id", + "setval": "policy route-map {{route_map}} rule {{sequence}} " + "set originator-id {{set.originator_id}}", + "result": { + "route_maps": { + "{{ route_map }}": { + "route_map": '{{ route_map }}', + "entries": { + "{{sequence}}": + { + "sequence": "{{sequence}}", + "set": { + "originator_id": "{{originator_id}}", + }, + }, + }, + }, + }, + }, + }, + { + "name": "set_src", + "getval": re.compile( + r""" + ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\sset\ssrc\s(?P<src>\S+) + *$""", + re.VERBOSE, + ), + "compval": "set.src", + "setval": "policy route-map {{route_map}} rule {{sequence}} " + "set src {{set.src}}", + "result": { + "route_maps": { + "{{ route_map }}": { + "route_map": '{{ route_map }}', + "entries": { + "{{sequence}}": + { + "sequence": "{{sequence}}", + "set": { + "src": "{{src}}", + }, + }, + }, + }, + }, + }, + }, + { + "name": "set_tag", + "getval": re.compile( + r""" + ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\sset\stag\s(?P<tag>\S+) + *$""", + re.VERBOSE, + ), + "compval": "set.tag", + "setval": "policy route-map {{route_map}} rule {{sequence}} " + "set tag {{set.tag}}", + "result": { + "route_maps": { + "{{ route_map }}": { + "route_map": '{{ route_map }}', + "entries": { + "{{sequence}}": + { + "sequence": "{{sequence}}", + "set": { + "tag": "{{tag}}", + }, + }, + }, + }, + }, + }, + }, + { + "name": "set_weight", + "getval": re.compile( + r""" + ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\sset\sweight\s(?P<weight>\S+) + *$""", + re.VERBOSE, + ), + "compval": "set.weight", + "setval": "policy route-map {{route_map}} rule {{sequence}} " + "set weight {{set.weight}}", + "result": { + "route_maps": { + "{{ route_map }}": { + "route_map": '{{ route_map }}', + "entries": { + "{{sequence}}": + { + "sequence": "{{sequence}}", + "set": { + "weight": "{{weight}}", + }, + }, + }, + }, + }, + }, + }, + { + "name": "set_table", + "getval": re.compile( + r""" + ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\sset\stable\s(?P<table>\S+) + *$""", + re.VERBOSE, + ), + "compval": "set.weight", + "setval": "policy route-map {{route_map}} rule {{sequence}} " + "set table {{set.table}}", + "result": { + "route_maps": { + "{{ route_map }}": { + "route_map": '{{ route_map }}', + "entries": { + "{{sequence}}": + { + "sequence": "{{sequence}}", + "set": { + "table": "{{table}}", + }, + }, + }, + }, + }, + }, + }, + { + "name": "set_community", + "getval": re.compile( + r""" + ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\sset\scommunity\s(?P<op>none|replace\s(?P<value>\S+)) + $""", + re.VERBOSE, + ), + "compval": "set.community.value", + "setval": "policy route-map {{route_map}} rule {{sequence}} " + "set community {{set.community.value if set.community.value == 'none' else 'replace ' + set.community.value}}", + "result": { + "route_maps": { + "{{ route_map }}": { + "route_map": '{{ route_map }}', + "entries": { + "{{sequence}}": + { + "sequence": "{{sequence}}", + "set": { + "community": { + "value": "{{op if op == 'none' else value}}", + }, + }, + }, + }, + }, + }, + }, + }, + { + "name": "match_as_path", + "getval": re.compile( + r""" + ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\smatch\sas-path\s(?P<as_path>\S+) + *$""", + re.VERBOSE, + ), + "compval": "match.as_path", + "setval": "policy route-map {{route_map}} rule {{sequence}} " + "match as-path {{match.as_path}}", + "result": { + "route_maps": { + "{{ route_map }}": { + "route_map": '{{ route_map }}', + "entries": { + "{{sequence}}": + { + "sequence": "{{sequence}}", + "match": { + "as_path": "{{as_path}}", + }, + }, + }, + }, + }, + }, + }, + { + "name": "match_community_community_list", + "getval": re.compile( + r""" + ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\smatch\scommunity\scommunity-list\s(?P<community_list>\S+) + *$""", + re.VERBOSE, + ), + "compval": "match.community.community_list", + "setval": "policy route-map {{route_map}} rule {{sequence}} " + "match community community-list {{match.community.community_list}}", + "result": { + "route_maps": { + "{{ route_map }}": { + "route_map": '{{ route_map }}', + "entries": { + "{{sequence}}": + { + "sequence": "{{sequence}}", + "match": { + "community": {"community_list": "{{community_list}}"}, + }, + }, + }, + }, + }, + }, + }, + { + "name": "match_community_exact_match", + "getval": re.compile( + r""" + ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\smatch\scommunity\sexact-match(?P<exact_match>) + *$""", + re.VERBOSE, + ), + "compval": "match.community.exact_match", + "setval": "policy route-map {{route_map}} rule {{sequence}} " + "match community exact-match", + "result": { + "route_maps": { + "{{ route_map }}": { + "route_map": '{{ route_map }}', + "entries": { + "{{sequence}}": + { + "sequence": "{{sequence}}", + "match": { + "community": {"exact_match": "{{True if exact_match is defined}}"}, + }, + }, + }, + }, + }, + }, + }, + { + "name": "match_extcommunity", + "getval": re.compile( + r""" + ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\smatch\sextcommunity\s(?P<extcommunity>\S+) + *$""", + re.VERBOSE, + ), + "compval": "match.extcommunity", + "setval": "policy route-map {{route_map}} rule {{sequence}} " + "match extcommunity {{match.extcommunity}}", + "result": { + "route_maps": { + "{{ route_map }}": { + "route_map": '{{ route_map }}', + "entries": { + "{{sequence}}": + { + "sequence": "{{sequence}}", + "match": { + "extcommunity": "{{extcommunity}}", + }, + }, + }, + }, + }, + }, + }, + { + "name": "match_interface", + "getval": re.compile( + r""" + ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\smatch\sinterface\s(?P<interface>\S+) + *$""", + re.VERBOSE, + ), + "compval": "match.interface", + "setval": "policy route-map {{route_map}} rule {{sequence}} " + "match interface {{match.interface}}", + "result": { + "route_maps": { + "{{ route_map }}": { + "route_map": '{{ route_map }}', + "entries": { + "{{sequence}}": + { + "sequence": "{{sequence}}", + "match": { + "interface": "{{interface}}", + }, + }, + }, + }, + }, + }, + }, + { + "name": "match_large_community_large_community_list", + "getval": re.compile( + r""" + ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\smatch\slarge-community\slarge-community-list\s(?P<lc>\S+) + *$""", + re.VERBOSE, + ), + "compval": "match.large_community_large_community_list", + "setval": "policy route-map {{route_map}} rule {{sequence}} " + "match large-community large-community-list {{match.large_community_large_community_list}}", + "result": { + "route_maps": { + "{{ route_map }}": { + "route_map": '{{ route_map }}', + "entries": { + "{{sequence}}": + { + "sequence": "{{sequence}}", + "match": { + "large_community_large_community_list": "{{lc}}", + }, + }, + }, + }, + }, + }, + }, + { + "name": "match_metric", + "getval": re.compile( + r""" + ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\smatch\smetric\s(?P<metric>\S+) + *$""", + re.VERBOSE, + ), + "compval": "match.metric", + "setval": "policy route-map {{route_map}} rule {{sequence}} " + "match metric {{match.metric}}", + "result": { + "route_maps": { + "{{ route_map }}": { + "route_map": '{{ route_map }}', + "entries": { + "{{sequence}}": + { + "sequence": "{{sequence}}", + "match": { + "metric": "{{metric}}", + }, + }, + }, + }, + }, + }, + }, + { + "name": "match_origin", + "getval": re.compile( + r""" + ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\smatch\sorigin\s(?P<origin>\S+) + *$""", + re.VERBOSE, + ), + "compval": "match.origin", + "setval": "policy route-map {{route_map}} rule {{sequence}} " + "match origin {{match.origin}}", + "result": { + "route_maps": { + "{{ route_map }}": { + "route_map": '{{ route_map }}', + "entries": { + "{{sequence}}": + { + "sequence": "{{sequence}}", + "match": { + "origin": "{{origin}}", + }, + }, + }, + }, + }, + }, + }, + { + "name": "match_peer", + "getval": re.compile( + r""" + ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\smatch\speer\s(?P<peer>\S+) + *$""", + re.VERBOSE, + ), + "compval": "match.peer", + "setval": "policy route-map {{route_map}} rule {{sequence}} " + "match peer {{match.peer}}", + "result": { + "route_maps": { + "{{ route_map }}": { + "route_map": '{{ route_map }}', + "entries": { + "{{sequence}}": + { + "sequence": "{{sequence}}", + "match": { + "peer": "{{peer}}", + }, + }, + }, + }, + }, + }, + }, + { + "name": "match_ip_address", + "getval": re.compile( + r""" + ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\smatch\sip\saddress + \s(?P<list_type>access-list|prefix-list) + \s(?P<value>\S+) + *$""", + re.VERBOSE, + ), + "compval": "match.ip.address", + "setval": "policy route-map {{route_map}} rule {{sequence}} " + "match ip address {{match.ip.address.list_type}} {{match.ip.address.value}}", + "result": { + "route_maps": { + "{{ route_map }}": { + "route_map": '{{ route_map }}', + "entries": { + "{{sequence}}": { + "sequence": "{{sequence}}", + "match": { + "ip": { + "address": { + "list_type": "{{list_type}}", + "value": "{{value}}", + }, + }, + }, + }, + }, + }, + }, + }, + }, + { + "name": "match_ip_next_hop", + "getval": re.compile( + r""" + ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\smatch\sip\snexthop + \s(?P<list_type>access-list|prefix-list) + \s(?P<value>\S+) + *$""", + re.VERBOSE, + ), + "compval": "match.ip.next_hop", + "setval": "policy route-map {{route_map}} rule {{sequence}} " + "match ip nexthop {{match.ip.next_hop.list_type}} {{match.ip.next_hop.value}}", + "result": { + "route_maps": { + "{{ route_map }}": { + "route_map": '{{ route_map }}', + "entries": { + "{{sequence}}": { + "sequence": "{{sequence}}", + "match": { + "ip": { + "next_hop": { + "list_type": "{{list_type}}", + "value": "{{value}}", + }, + }, + }, + }, + }, + }, + }, + }, + }, + { + "name": "match_ip_route_source", + "getval": re.compile( + r""" + ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\smatch\sip\sroute-source + \s(?P<list_type>access-list|prefix-list) + \s(?P<value>\S+) + *$""", + re.VERBOSE, + ), + "compval": "match.ip.route_source", + "setval": "policy route-map {{route_map}} rule {{sequence}} " + "match ip route-source {{match.ip.route_source.list_type}} {{match.ip.route_source.value}}", + "result": { + "route_maps": { + "{{ route_map }}": { + "route_map": '{{ route_map }}', + "entries": { + "{{sequence}}": { + "sequence": "{{sequence}}", + "match": { + "ip": { + "route_source": { + "list_type": "{{list_type}}", + "value": "{{value}}", + }, + }, + }, + }, + }, + }, + }, + }, + }, + { + "name": "match_ipv6_address", + "getval": re.compile( + r""" + ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\smatch\sipv6\saddress + \s(?P<list_type>access-list|prefix-list) + \s(?P<value>\S+) + *$""", + re.VERBOSE, + ), + "compval": "match.ipv6.address", + "setval": "policy route-map {{route_map}} rule {{sequence}} " + "match ipv6 address {{match.ipv6.address.list_type}} {{match.ipv6.address.value}}", + "result": { + "route_maps": { + "{{ route_map }}": { + "route_map": '{{ route_map }}', + "entries": { + "{{sequence}}": { + "sequence": "{{sequence}}", + "match": { + "ipv6": { + "address": { + "list_type": "{{list_type}}", + "value": "{{value}}", + }, + }, + }, + }, + }, + }, + }, + }, + }, + { + "name": "match_ipv6_nexthop", + "getval": re.compile( + r""" + ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\smatch\sipv6\snexthop + \s(?P<value>\S+) + *$""", + re.VERBOSE, + ), + "compval": "match.ipv6.next_hop", + "setval": "policy route-map {{route_map}} rule {{sequence}} " + "match ipv6 nexthop {{match.ipv6.next_hop}}", + "result": { + "route_maps": { + "{{ route_map }}": { + "route_map": '{{ route_map }}', + "entries": { + "{{sequence}}": { + "sequence": "{{sequence}}", + "match": { + "ipv6": { + "next_hop": "{{value}}", + }, + }, + }, + }, + }, + }, + }, + }, + { + "name": "match_protocol", + "getval": re.compile( + r""" + ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\smatch\sprotocol\s(?P<value>\S+) + *$""", + re.VERBOSE, + ), + "compval": "match.protocol", + "setval": "policy route-map {{route_map}} rule {{sequence}} " + "match protocol {{match.protocol}}", + "result": { + "route_maps": { + "{{ route_map }}": { + "route_map": '{{ route_map }}', + "entries": { + "{{sequence}}": { + "sequence": "{{sequence}}", + "match": { + "protocol": "{{value}}", + }, + }, + }, + }, + }, + }, + }, + { + "name": "match_rpki", + "getval": re.compile( + r""" + ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\smatch\srpki + \s(?P<value>\S+) + *$""", + re.VERBOSE, + ), + "compval": "match.rpki", + "setval": "policy route-map {{route_map}} rule {{sequence}} " + "match rpki {{match.rpki}}", + "result": { + "route_maps": { + "{{ route_map }}": { + "route_map": '{{ route_map }}', + "entries": { + "{{sequence}}": { + "sequence": "{{sequence}}", + "match": { + "rpki": "{{value}}", + }, + }, + }, + }, + }, + }, + }, + + ] + # fmt: on diff --git a/plugins/modules/vyos_route_maps.py b/plugins/modules/vyos_route_maps.py index 67d327a6..455bd3e6 100644 --- a/plugins/modules/vyos_route_maps.py +++ b/plugins/modules/vyos_route_maps.py @@ -103,6 +103,12 @@ options: extcommunity_soo: type: str description: Set Site of Origin value. ASN:nn_or_IP_address:nn VPN extended community + extcommunity_bandwidth: + type: str + description: Set Bandwidth of Origin value. 1-25600|cumulative|num-multipaths VPN extended community + extcommunity_bandwidth_non_transitive: + type: bool + description: Set the bandwidth extended community encoded as non-transitive True/False VPN extended community ip_next_hop: type: str description: IP address. @@ -146,6 +152,9 @@ options: weight: type: str description: Border Gateway Protocol (BGP) weight attribute. Example <0-4294967295> + table: + type: str + description: Set prefixes to table. Example <1-200> match: description: Route parameters to match. type: dict @@ -226,6 +235,10 @@ options: type: str description: RPKI validation value. choices: [ "notfound", "invalid", "valid" ] + protocol: + type: str + description: Source protocol to match. + choices: [ "babel","bgp","connected","isis","kernel","ospf","ospfv3","rip","ripng","static","table","vnc" ] on_match: type: dict description: Exit policy on matches. diff --git a/tests/integration/targets/vyos_route_maps/tests/cli/replaced.yaml b/tests/integration/targets/vyos_route_maps/tests/cli/replaced.yaml index d7d05ec1..dce0cba9 100644 --- a/tests/integration/targets/vyos_route_maps/tests/cli/replaced.yaml +++ b/tests/integration/targets/vyos_route_maps/tests/cli/replaced.yaml @@ -31,7 +31,6 @@ - assert: that: - - result.commands|length == 7 - result.changed == true - result.commands|symmetric_difference(replaced.commands) == [] - result.after|symmetric_difference(ansible_facts['network_resources']['route_maps']) == [] diff --git a/tests/unit/modules/network/vyos/fixtures/vyos_route_maps_config_v14.cfg b/tests/unit/modules/network/vyos/fixtures/vyos_route_maps_config_v14.cfg new file mode 100644 index 00000000..1e84e5eb --- /dev/null +++ b/tests/unit/modules/network/vyos/fixtures/vyos_route_maps_config_v14.cfg @@ -0,0 +1,18 @@ +set policy route-map test3 rule 1 action 'permit' +set policy route-map test3 rule 1 match interface 'eth2' +set policy route-map test3 rule 1 match ipv6 nexthop 'fdda:5cc1:23:4::1f' +set policy route-map test3 rule 1 match metric '1' +set policy route-map test3 rule 1 match peer '1.1.1.2' +set policy route-map test3 rule 1 match rpki 'invalid' +set policy route-map test3 rule 1 set bgp-extcommunity-rt '22:11' +set policy route-map test3 rule 1 set community replace 'internet' +set policy route-map test3 rule 1 set ipv6-next-hop global 'fdda:5cc1:23:4::1f' +set policy route-map test3 rule 1 set ip-next-hop '10.20.10.20' +set policy route-map test3 rule 1 set local-preference '4' +set policy route-map test3 rule 1 set metric '5' +set policy route-map test3 rule 1 set metric-type 'type-1' +set policy route-map test3 rule 1 set origin 'egp' +set policy route-map test3 rule 1 set originator-id '10.0.2.3' +set policy route-map test3 rule 1 set src '10.0.2.15' +set policy route-map test3 rule 1 set tag '5' +set policy route-map test3 rule 1 set weight '4' diff --git a/tests/unit/modules/network/vyos/test_vyos_route_maps.py b/tests/unit/modules/network/vyos/test_vyos_route_maps.py index ce13dcf2..db83ea40 100644 --- a/tests/unit/modules/network/vyos/test_vyos_route_maps.py +++ b/tests/unit/modules/network/vyos/test_vyos_route_maps.py @@ -50,6 +50,18 @@ class TestVyosRouteMapsModule(TestVyosModule): ) 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.route_maps.route_maps.get_os_version", + ) + self.test_version = "1.2" + self.get_os_version = self.mock_get_os_version.start() + self.get_os_version.return_value = self.test_version + self.mock_facts_get_os_version = patch( + "ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.route_maps.route_maps.get_os_version", + ) + self.get_facts_os_version = self.mock_facts_get_os_version.start() + self.get_facts_os_version.return_value = self.test_version + self.maxDiff = None def tearDown(self): super(TestVyosRouteMapsModule, self).tearDown() @@ -176,21 +188,25 @@ class TestVyosRouteMapsModule(TestVyosModule): self.execute_module(changed=True, commands=commands) - def test_route_maps_replaced(self): + def test_route_maps_extras_merged(self): set_module_args( dict( config=[ dict( - route_map="test3", + route_map="test2", entries=[ dict( sequence=1, action="permit", + call="2", + continue_sequence=2, match=dict( rpki="invalid", + interface="eth2", metric=1, peer="1.1.1.3", ipv6=dict(next_hop="fdda:5cc1:23:4::1f"), + community=dict(community_list="235"), ), set=dict( ipv6_next_hop=dict( @@ -198,9 +214,17 @@ class TestVyosRouteMapsModule(TestVyosModule): value="fdda:5cc1:23:4::1f", ), community=dict(value="internet"), + extcommunity_rt="22:11", + extcommunity_soo="220:110", + extcommunity_bandwidth="100", + extcommunity_bandwidth_non_transitive=True, + atomic_aggregate=True, + aggregator={"ip": "10.20.11.22", "as": "245"}, bgp_extcommunity_rt="22:11", ip_next_hop="10.20.10.22", large_community="10:20:21", + as_path_prepend="100 200 350", + as_path_exclude="150", local_preference=4, metric=5, metric_type="type-2", @@ -209,6 +233,85 @@ class TestVyosRouteMapsModule(TestVyosModule): src="10.0.2.15", tag=4, weight=4, + table=7, + ), + ), + ], + ), + ], + state="merged", + ), + ) + commands = [ + "set policy route-map test2 rule 1 action permit", + "set policy route-map test2 rule 1 call 2", + "set policy route-map test2 rule 1 set bgp-extcommunity-rt 22:11", + "set policy route-map test2 rule 1 set ip-next-hop 10.20.10.22", + "set policy route-map test2 rule 1 set ipv6-next-hop global fdda:5cc1:23:4::1f", + "set policy route-map test2 rule 1 set large-community 10:20:21", + "set policy route-map test2 rule 1 set as-path-prepend '100 200 350'", + "set policy route-map test2 rule 1 set as-path-exclude 150", + "set policy route-map test2 rule 1 set local-preference 4", + "set policy route-map test2 rule 1 set metric 5", + "set policy route-map test2 rule 1 set metric-type type-2", + "set policy route-map test2 rule 1 set origin egp", + "set policy route-map test2 rule 1 set originator-id 10.0.2.2", + "set policy route-map test2 rule 1 set src 10.0.2.15", + "set policy route-map test2 rule 1 set tag 4", + "set policy route-map test2 rule 1 set weight 4", + "set policy route-map test2 rule 1 set table 7", + "set policy route-map test2 rule 1 set community internet", + "set policy route-map test2 rule 1 set extcommunity-rt 22:11", + "set policy route-map test2 rule 1 set extcommunity-soo 220:110", + "set policy route-map test2 rule 1 set extcommunity bandwidth 100", + "set policy route-map test2 rule 1 set extcommunity bandwidth-non-transitive", + "set policy route-map test2 rule 1 set atomic-aggregate", + "set policy route-map test2 rule 1 set aggregator as 245", + "set policy route-map test2 rule 1 set aggregator ip 10.20.11.22", + "set policy route-map test2 rule 1 match interface eth2", + "set policy route-map test2 rule 1 match metric 1", + "set policy route-map test2 rule 1 match peer 1.1.1.3", + "set policy route-map test2 rule 1 match ipv6 nexthop fdda:5cc1:23:4::1f", + "set policy route-map test2 rule 1 match rpki invalid", + "set policy route-map test2 rule 1 match community community-list 235", + "set policy route-map test2 rule 1 continue 2", + ] + + self.execute_module(changed=True, commands=commands) + + def test_route_maps_replaced(self): + set_module_args( + dict( + config=[ + dict( + route_map="test3", + entries=[ + dict( + sequence=1, + action="permit", + match=dict( + rpki="invalid", + metric=1, + peer="1.1.1.3", + ipv6=dict(next_hop="fdda:5cc1:23:4::1f"), + ), + set=dict( + ipv6_next_hop=dict( + ip_type="global", + value="fdda:5cc1:23:4::1f", + ), + community=dict(value="100:100"), + bgp_extcommunity_rt="22:11", + ip_next_hop="10.20.10.22", + large_community="10:20:21", + local_preference=4, + metric=5, + metric_type="type-2", + origin="egp", + originator_id="10.0.2.2", + src="fdda:5cc1:23:4::12", + tag=4, + weight=4, ), ), ], @@ -220,10 +323,12 @@ class TestVyosRouteMapsModule(TestVyosModule): commands = [ "delete policy route-map test3 rule 1 match interface eth2", "set policy route-map test3 rule 1 set ip-next-hop 10.20.10.22", + "set policy route-map test3 rule 1 set community 100:100", "set policy route-map test3 rule 1 set large-community 10:20:21", "set policy route-map test3 rule 1 set metric-type type-2", "set policy route-map test3 rule 1 set originator-id 10.0.2.2", "set policy route-map test3 rule 1 set tag 4", + "set policy route-map test3 rule 1 set src fdda:5cc1:23:4::12", "set policy route-map test3 rule 1 match peer 1.1.1.3", ] self.execute_module(changed=True, commands=commands) @@ -328,6 +433,32 @@ class TestVyosRouteMapsModule(TestVyosModule): ] self.execute_module(changed=True, commands=commands) + def test_route_maps__deny_overridden(self): + set_module_args( + dict( + config=[ + dict( + route_map="test2", + entries=[ + dict( + sequence=1, + action="deny", + match=dict(rpki="invalid", peer="1.1.1.5"), + ), + ], + ), + ], + state="overridden", + ), + ) + commands = [ + "delete policy route-map test3", + "set policy route-map test2 rule 1 action deny", + "set policy route-map test2 rule 1 match peer 1.1.1.5", + "set policy route-map test2 rule 1 match rpki invalid", + ] + self.execute_module(changed=True, commands=commands) + def test_vyos_route_maps_overridden_idempotent(self): set_module_args( dict( diff --git a/tests/unit/modules/network/vyos/test_vyos_route_maps14.py b/tests/unit/modules/network/vyos/test_vyos_route_maps14.py new file mode 100644 index 00000000..7bc5b74e --- /dev/null +++ b/tests/unit/modules/network/vyos/test_vyos_route_maps14.py @@ -0,0 +1,717 @@ +# (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 <http://www.gnu.org/licenses/>. + +# 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_route_maps +from ansible_collections.vyos.vyos.tests.unit.modules.utils import set_module_args + +from .vyos_module import TestVyosModule, load_fixture + + +class TestVyosRouteMapsModule(TestVyosModule): + module = vyos_route_maps + + def setUp(self): + super(TestVyosRouteMapsModule, 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.route_maps.route_maps.Route_mapsFacts.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.route_maps.route_maps.get_os_version", + ) + self.test_version = "1.4" + self.get_os_version = self.mock_get_os_version.start() + self.get_os_version.return_value = self.test_version + self.mock_facts_get_os_version = patch( + "ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.route_maps.route_maps.get_os_version", + ) + self.get_facts_os_version = self.mock_facts_get_os_version.start() + self.get_facts_os_version.return_value = self.test_version + self.maxDiff = None + + def tearDown(self): + super(TestVyosRouteMapsModule, self).tearDown() + self.mock_get_resource_connection_config.stop() + self.mock_get_resource_connection_facts.stop() + self.mock_execute_show_command.stop() + + def load_fixtures(self, commands=None, filename=None): + if filename is None: + filename = "vyos_route_maps_config_v14.cfg" + + def load_from_file(*args, **kwargs): + output = load_fixture(filename) + return output + + self.execute_show_command.side_effect = load_from_file + + def test_vyos_route_maps_merged_idempotent(self): + set_module_args( + dict( + config=[ + dict( + route_map="test3", + entries=[ + dict( + sequence=1, + action="permit", + match=dict( + rpki="invalid", + interface="eth2", + metric=1, + peer="1.1.1.2", + ipv6=dict(next_hop="fdda:5cc1:23:4::1f"), + ), + set=dict( + ipv6_next_hop=dict( + ip_type="global", + value="fdda:5cc1:23:4::1f", + ), + community=dict(value="internet"), + bgp_extcommunity_rt="22:11", + ip_next_hop="10.20.10.20", + local_preference=4, + metric=5, + metric_type="type-1", + origin="egp", + originator_id="10.0.2.3", + src="10.0.2.15", + tag=5, + weight=4, + ), + ), + ], + ), + ], + state="merged", + ), + ) + self.execute_module(changed=False, commands=[]) + + def test_route_maps_merged(self): + set_module_args( + dict( + config=[ + dict( + route_map="test2", + entries=[ + dict( + sequence=1, + action="permit", + match=dict( + rpki="invalid", + interface="eth2", + metric=1, + peer="1.1.1.3", + ipv6=dict(next_hop="fdda:5cc1:23:4::1f"), + ), + set=dict( + ipv6_next_hop=dict( + ip_type="global", + value="fdda:5cc1:23:4::1f", + ), + community=dict(value="internet"), + bgp_extcommunity_rt="22:11", + ip_next_hop="10.20.10.22", + large_community="10:20:21", + local_preference=4, + metric=5, + metric_type="type-2", + origin="egp", + originator_id="10.0.2.2", + src="10.0.2.15", + tag=4, + weight=4, + ), + ), + ], + ), + ], + state="merged", + ), + ) + commands = [ + "set policy route-map test2 rule 1 action permit", + "set policy route-map test2 rule 1 set bgp-extcommunity-rt 22:11", + "set policy route-map test2 rule 1 set ip-next-hop 10.20.10.22", + "set policy route-map test2 rule 1 set ipv6-next-hop global fdda:5cc1:23:4::1f", + "set policy route-map test2 rule 1 set large-community replace 10:20:21", + "set policy route-map test2 rule 1 set local-preference 4", + "set policy route-map test2 rule 1 set metric 5", + "set policy route-map test2 rule 1 set metric-type type-2", + "set policy route-map test2 rule 1 set origin egp", + "set policy route-map test2 rule 1 set originator-id 10.0.2.2", + "set policy route-map test2 rule 1 set src 10.0.2.15", + "set policy route-map test2 rule 1 set tag 4", + "set policy route-map test2 rule 1 set weight 4", + "set policy route-map test2 rule 1 set community replace internet", + "set policy route-map test2 rule 1 match interface eth2", + "set policy route-map test2 rule 1 match metric 1", + "set policy route-map test2 rule 1 match peer 1.1.1.3", + "set policy route-map test2 rule 1 match ipv6 nexthop fdda:5cc1:23:4::1f", + "set policy route-map test2 rule 1 match rpki invalid", + ] + + self.execute_module(changed=True, commands=commands) + + def test_route_maps_extras_merged(self): + set_module_args( + dict( + config=[ + dict( + route_map="test2", + entries=[ + dict( + sequence=1, + action="permit", + call="2", + continue_sequence=2, + match=dict( + rpki="invalid", + interface="eth2", + metric=1, + peer="1.1.1.3", + ipv6=dict(next_hop="fdda:5cc1:23:4::1f"), + community=dict(community_list="235"), + protocol="bgp", + ), + set=dict( + ipv6_next_hop=dict( + ip_type="global", + value="fdda:5cc1:23:4::1f", + ), + community=dict(value="internet"), + extcommunity_rt="22:11", + extcommunity_soo="220:110", + extcommunity_bandwidth="100", + extcommunity_bandwidth_non_transitive=True, + atomic_aggregate=True, + aggregator={"ip": "10.20.11.22", "as": "245"}, + bgp_extcommunity_rt="22:11", + ip_next_hop="10.20.10.22", + large_community="10:20:21", + as_path_prepend="100 200 350", + as_path_exclude="150", + local_preference=4, + metric=5, + metric_type="type-2", + origin="egp", + originator_id="10.0.2.2", + src="10.0.2.15", + tag=4, + weight=4, + table=7, + ), + ), + ], + ), + ], + state="merged", + ), + ) + commands = [ + "set policy route-map test2 rule 1 action permit", + "set policy route-map test2 rule 1 call 2", + "set policy route-map test2 rule 1 set bgp-extcommunity-rt 22:11", + "set policy route-map test2 rule 1 set ip-next-hop 10.20.10.22", + "set policy route-map test2 rule 1 set ipv6-next-hop global fdda:5cc1:23:4::1f", + "set policy route-map test2 rule 1 set large-community replace 10:20:21", + "set policy route-map test2 rule 1 set as-path prepend '100 200 350'", + "set policy route-map test2 rule 1 set as-path exclude 150", + "set policy route-map test2 rule 1 set local-preference 4", + "set policy route-map test2 rule 1 set metric 5", + "set policy route-map test2 rule 1 set metric-type type-2", + "set policy route-map test2 rule 1 set origin egp", + "set policy route-map test2 rule 1 set originator-id 10.0.2.2", + "set policy route-map test2 rule 1 set src 10.0.2.15", + "set policy route-map test2 rule 1 set tag 4", + "set policy route-map test2 rule 1 set weight 4", + "set policy route-map test2 rule 1 set table 7", + "set policy route-map test2 rule 1 set community replace internet", + "set policy route-map test2 rule 1 set extcommunity rt 22:11", + "set policy route-map test2 rule 1 set extcommunity soo 220:110", + "set policy route-map test2 rule 1 set extcommunity bandwidth 100", + "set policy route-map test2 rule 1 set extcommunity bandwidth-non-transitive", + "set policy route-map test2 rule 1 set atomic-aggregate", + "set policy route-map test2 rule 1 set aggregator as 245", + "set policy route-map test2 rule 1 set aggregator ip 10.20.11.22", + "set policy route-map test2 rule 1 match interface eth2", + "set policy route-map test2 rule 1 match metric 1", + "set policy route-map test2 rule 1 match peer 1.1.1.3", + "set policy route-map test2 rule 1 match ipv6 nexthop fdda:5cc1:23:4::1f", + "set policy route-map test2 rule 1 match rpki invalid", + "set policy route-map test2 rule 1 match protocol bgp", + "set policy route-map test2 rule 1 match community community-list 235", + "set policy route-map test2 rule 1 continue 2", + ] + + self.execute_module(changed=True, commands=commands) + + def test_route_maps_replaced(self): + set_module_args( + dict( + config=[ + dict( + route_map="test3", + entries=[ + dict( + sequence=1, + action="permit", + match=dict( + rpki="invalid", + metric=1, + peer="1.1.1.3", + ipv6=dict(next_hop="fdda:5cc1:23:4::1f"), + ), + set=dict( + ipv6_next_hop=dict( + ip_type="global", + value="fdda:5cc1:23:4::1f", + ), + community=dict(value="100:100"), + bgp_extcommunity_rt="22:11", + ip_next_hop="10.20.10.22", + large_community="10:20:21", + local_preference=4, + metric=5, + metric_type="type-2", + origin="egp", + originator_id="10.0.2.2", + src="fdda:5cc1:23:4::12", + tag=4, + weight=4, + ), + ), + ], + ), + ], + state="replaced", + ), + ) + commands = [ + "delete policy route-map test3 rule 1 match interface eth2", + "set policy route-map test3 rule 1 set ip-next-hop 10.20.10.22", + "set policy route-map test3 rule 1 set community replace 100:100", + "set policy route-map test3 rule 1 set large-community replace 10:20:21", + "set policy route-map test3 rule 1 set metric-type type-2", + "set policy route-map test3 rule 1 set originator-id 10.0.2.2", + "set policy route-map test3 rule 1 set tag 4", + "set policy route-map test3 rule 1 set src fdda:5cc1:23:4::12", + "set policy route-map test3 rule 1 match peer 1.1.1.3", + ] + self.execute_module(changed=True, commands=commands) + + def test_vyos_route_maps_replaced_idempotent(self): + set_module_args( + dict( + config=[ + dict( + route_map="test3", + entries=[ + dict( + sequence=1, + action="permit", + match=dict( + rpki="invalid", + interface="eth2", + metric=1, + peer="1.1.1.2", + ipv6=dict(next_hop="fdda:5cc1:23:4::1f"), + ), + set=dict( + ipv6_next_hop=dict( + ip_type="global", + value="fdda:5cc1:23:4::1f", + ), + community=dict(value="internet"), + bgp_extcommunity_rt="22:11", + ip_next_hop="10.20.10.20", + local_preference=4, + metric=5, + metric_type="type-1", + origin="egp", + originator_id="10.0.2.3", + src="10.0.2.15", + tag=5, + weight=4, + ), + ), + ], + ), + ], + state="replaced", + ), + ) + self.execute_module(changed=False, commands=[]) + + def test_route_maps_overridden(self): + set_module_args( + dict( + config=[ + dict( + route_map="test2", + entries=[ + dict( + sequence=1, + action="permit", + match=dict(rpki="invalid", peer="1.1.1.3"), + set=dict( + ipv6_next_hop=dict( + ip_type="global", + value="fdda:5cc1:23:4::1f", + ), + community=dict(value="internet"), + bgp_extcommunity_rt="22:11", + ip_next_hop="10.20.10.22", + large_community="10:20:21", + local_preference=4, + metric=5, + metric_type="type-2", + origin="egp", + originator_id="10.0.2.2", + src="10.0.2.15", + tag=4, + weight=4, + ), + ), + ], + ), + ], + state="overridden", + ), + ) + commands = [ + "delete policy route-map test3", + "set policy route-map test2 rule 1 action permit", + "set policy route-map test2 rule 1 set bgp-extcommunity-rt 22:11", + "set policy route-map test2 rule 1 set ip-next-hop 10.20.10.22", + "set policy route-map test2 rule 1 set ipv6-next-hop global fdda:5cc1:23:4::1f", + "set policy route-map test2 rule 1 set large-community replace 10:20:21", + "set policy route-map test2 rule 1 set local-preference 4", + "set policy route-map test2 rule 1 set metric 5", + "set policy route-map test2 rule 1 set metric-type type-2", + "set policy route-map test2 rule 1 set origin egp", + "set policy route-map test2 rule 1 set originator-id 10.0.2.2", + "set policy route-map test2 rule 1 set src 10.0.2.15", + "set policy route-map test2 rule 1 set tag 4", + "set policy route-map test2 rule 1 set weight 4", + "set policy route-map test2 rule 1 set community replace internet", + "set policy route-map test2 rule 1 match peer 1.1.1.3", + "set policy route-map test2 rule 1 match rpki invalid", + ] + self.execute_module(changed=True, commands=commands) + + def test_route_maps__deny_overridden(self): + set_module_args( + dict( + config=[ + dict( + route_map="test2", + entries=[ + dict( + sequence=1, + action="deny", + match=dict(rpki="invalid", peer="1.1.1.5"), + ), + ], + ), + ], + state="overridden", + ), + ) + commands = [ + "delete policy route-map test3", + "set policy route-map test2 rule 1 action deny", + "set policy route-map test2 rule 1 match peer 1.1.1.5", + "set policy route-map test2 rule 1 match rpki invalid", + ] + self.execute_module(changed=True, commands=commands) + + def test_vyos_route_maps_overridden_idempotent(self): + set_module_args( + dict( + config=[ + dict( + route_map="test3", + entries=[ + dict( + sequence=1, + action="permit", + match=dict( + rpki="invalid", + interface="eth2", + metric=1, + peer="1.1.1.2", + ipv6=dict(next_hop="fdda:5cc1:23:4::1f"), + ), + set=dict( + ipv6_next_hop=dict( + ip_type="global", + value="fdda:5cc1:23:4::1f", + ), + community=dict(value="internet"), + bgp_extcommunity_rt="22:11", + ip_next_hop="10.20.10.20", + local_preference=4, + metric=5, + metric_type="type-1", + origin="egp", + originator_id="10.0.2.3", + src="10.0.2.15", + tag=5, + weight=4, + ), + ), + ], + ), + ], + state="overridden", + ), + ) + self.execute_module(changed=False, commands=[]) + + def test_vyos_route_maps_rendered(self): + set_module_args( + dict( + config=[ + dict( + route_map="test3", + entries=[ + dict( + sequence=1, + action="permit", + match=dict( + rpki="invalid", + interface="eth2", + metric=1, + peer="1.1.1.2", + ipv6=dict(next_hop="fdda:5cc1:23:4::1f"), + ), + set=dict( + ipv6_next_hop=dict( + ip_type="global", + value="fdda:5cc1:23:4::1f", + ), + community=dict(value="internet"), + bgp_extcommunity_rt="22:11", + ip_next_hop="10.20.10.20", + local_preference=4, + metric=5, + metric_type="type-1", + origin="egp", + originator_id="10.0.2.3", + src="10.0.2.15", + tag=5, + weight=4, + ), + ), + ], + ), + dict( + route_map="test1", + entries=[ + dict( + sequence=1, + action="permit", + description="test", + on_match=dict(next=True), + ), + dict( + sequence=2, + action="permit", + on_match=dict(goto=4), + ), + ], + ), + ], + state="rendered", + ), + ) + rendered_cmds = [ + "set policy route-map test3 rule 1 action permit", + "set policy route-map test3 rule 1 set bgp-extcommunity-rt 22:11", + "set policy route-map test3 rule 1 set ip-next-hop 10.20.10.20", + "set policy route-map test3 rule 1 set ipv6-next-hop global fdda:5cc1:23:4::1f", + "set policy route-map test3 rule 1 set local-preference 4", + "set policy route-map test3 rule 1 set metric 5", + "set policy route-map test3 rule 1 set metric-type type-1", + "set policy route-map test3 rule 1 set origin egp", + "set policy route-map test3 rule 1 set originator-id 10.0.2.3", + "set policy route-map test3 rule 1 set src 10.0.2.15", + "set policy route-map test3 rule 1 set tag 5", + "set policy route-map test3 rule 1 set weight 4", + "set policy route-map test3 rule 1 set community replace internet", + "set policy route-map test3 rule 1 match interface eth2", + "set policy route-map test3 rule 1 match metric 1", + "set policy route-map test3 rule 1 match peer 1.1.1.2", + "set policy route-map test3 rule 1 match ipv6 nexthop fdda:5cc1:23:4::1f", + "set policy route-map test3 rule 1 match rpki invalid", + "set policy route-map test1 rule 1 description test", + "set policy route-map test1 rule 1 action permit", + "set policy route-map test1 rule 1 on-match next", + "set policy route-map test1 rule 2 action permit", + "set policy route-map test1 rule 2 on-match goto 4", + ] + result = self.execute_module(changed=False) + self.assertEqual( + sorted(result["rendered"]), + sorted(rendered_cmds), + result["rendered"], + ) + + def test_yos_route_maps_parsed(self): + parsed_str = ( + "set policy route-map test3 rule 1 action 'permit'" + "\nset policy route-map test3 rule 1 match interface 'eth2'\nset policy route-map test3 rule 1 match ipv6 nexthop" + " 'fdda:5cc1:23:4::1f'\nset policy route-map test3 rule 1 match metric '1'\nset policy route-map test3 rule 1 match peer " + "'1.1.1.2'\nset policy route-map test3 rule 1 match rpki 'invalid'\nset policy route-map test3 rule 1 set bgp-extcommunity-rt " + "'22:11'\nset policy route-map test3 rule 1 set community replace 'internet'\nset policy route-map test3 rule 1 set ipv6-next-hop global" + " 'fdda:5cc1:23:4::1f'\nset policy route-map test3 rule 1 set ip-next-hop '10.20.10.20'\nset policy route-map " + "test3 rule 1 set local-preference '4'\nset policy route-map test3 rule 1 set metric '5'\nset policy route-map test3 " + "rule 1 set metric-type 'type-1'\nset policy route-map test3 rule 1 set origin 'egp'\nset policy route-map test3 rule 1 set originator-id " + "'10.0.2.3'\nset policy route-map test3 rule 1 set src '10.0.2.15'" + "\nset policy route-map test3 rule 1 set tag '5'\nset policy route-map test3 rule 1 set weight '4'" + ) + set_module_args(dict(running_config=parsed_str, state="parsed")) + result = self.execute_module(changed=False) + parsed_list = [ + { + "entries": [ + { + "action": "permit", + "match": { + "interface": "eth2", + "ipv6": {"next_hop": "fdda:5cc1:23:4::1f"}, + "metric": 1, + "peer": "1.1.1.2", + "rpki": "invalid", + }, + "sequence": 1, + "set": { + "bgp_extcommunity_rt": "22:11", + "community": {"value": "internet"}, + "ip_next_hop": "10.20.10.20", + "ipv6_next_hop": { + "ip_type": "global", + "value": "fdda:5cc1:23:4::1f", + }, + "local_preference": "4", + "metric": "5", + "metric_type": "type-1", + "origin": "egp", + "originator_id": "10.0.2.3", + "src": "10.0.2.15", + "tag": "5", + "weight": "4", + }, + }, + ], + "route_map": "test3", + }, + ] + self.assertEqual(parsed_list, result["parsed"]) + + def test_vyos_route_maps_gathered(self): + set_module_args(dict(state="gathered")) + result = self.execute_module(changed=False) + gathered_list = [ + { + "entries": [ + { + "action": "permit", + "match": { + "interface": "eth2", + "ipv6": {"next_hop": "fdda:5cc1:23:4::1f"}, + "metric": 1, + "peer": "1.1.1.2", + "rpki": "invalid", + }, + "sequence": 1, + "set": { + "bgp_extcommunity_rt": "22:11", + "community": {"value": "internet"}, + "ip_next_hop": "10.20.10.20", + "ipv6_next_hop": { + "ip_type": "global", + "value": "fdda:5cc1:23:4::1f", + }, + "local_preference": "4", + "metric": "5", + "metric_type": "type-1", + "origin": "egp", + "originator_id": "10.0.2.3", + "src": "10.0.2.15", + "tag": "5", + "weight": "4", + }, + }, + ], + "route_map": "test3", + }, + ] + self.assertEqual(gathered_list, result["gathered"]) + + def test_vyos_route_maps_deleted(self): + set_module_args( + dict( + config=[ + dict( + route_map="test3", + entries=[ + dict( + sequence=1, + action="permit", + match=dict( + rpki="invalid", + interface="eth2", + ), + set=dict( + origin="egp", + originator_id="10.0.2.3", + src="10.0.2.15", + tag=5, + weight=4, + ), + ), + ], + ), + ], + state="deleted", + ), + ) + commands = ["delete policy route-map test3"] + self.execute_module(changed=True, commands=commands) |