From 46cd6bebda1d67b46afb27ce4a9440cc1c30d02f Mon Sep 17 00:00:00 2001 From: Ruchi Pakhle Date: Thu, 2 May 2024 17:26:51 +0530 Subject: [PATCH] updated argspec --- .../ios/argspec/vrf_global/vrf_global.py | 69 ++-- .../ios/config/vrf_global/vrf_global.py | 24 +- .../ios/facts/vrf_global/vrf_global.py | 34 +- .../network/ios/rm_templates/vrf_global.py | 96 +++-- plugins/modules/ios_vrf_global.py | 120 +++--- .../network/ios/test_ios_vrf_global.py | 367 ++++++++++++++++++ 6 files changed, 566 insertions(+), 144 deletions(-) create mode 100644 tests/unit/modules/network/ios/test_ios_vrf_global.py diff --git a/plugins/module_utils/network/ios/argspec/vrf_global/vrf_global.py b/plugins/module_utils/network/ios/argspec/vrf_global/vrf_global.py index 3766d323c..86ba695ea 100644 --- a/plugins/module_utils/network/ios/argspec/vrf_global/vrf_global.py +++ b/plugins/module_utils/network/ios/argspec/vrf_global/vrf_global.py @@ -34,40 +34,51 @@ class Vrf_globalArgs(object): # pylint: disable=R0903 argument_spec = { "config": { - "type": "list", - "elements": "dict", + "type": "dict", "options": { - "name": {"type": "str", "required": True}, - "description": {"type": "str"}, - "ipv4": { - "type": "dict", + "vrfs": { + "type": "list", + "elements": "dict", "options": { - "multicast": { + "name": {"type": "str", "required": True}, + "description": {"type": "str"}, + "ipv4": { "type": "dict", - "options": {"multitopology": {"type": "bool"}}, - } - }, - }, - "ipv6": { - "type": "dict", - "options": { - "multicast": { + "options": { + "multicast": { + "type": "dict", + "options": {"multitopology": {"type": "bool"}}, + } + }, + }, + "ipv6": { "type": "dict", - "options": {"multitopology": {"type": "bool"}}, - } - }, - }, - "rd": {"type": "str"}, - "route_target": { - "type": "dict", - "options": { - "export": {"type": "str"}, - "import_config": {"type": "str"}, - "both": {"type": "str"}, + "options": { + "multicast": { + "type": "dict", + "options": {"multitopology": {"type": "bool"}}, + } + }, + }, + "rd": {"type": "str"}, + "route_target": { + "type": "dict", + "options": { + "export": {"type": "str"}, + "import_config": {"type": "str"}, + "both": {"type": "str"}, + }, + }, + "vnet": { + "type": "dict", + "options": {"tag": {"type": "int"}}, + }, + "vpn": { + "type": "dict", + "options": {"id": {"type": "str"}}, + }, }, - }, - "vnet": {"type": "dict", "options": {"tag": {"type": "int"}}}, - "vpn": {"type": "dict", "options": {"id": {"type": "str"}}}, + } }, }, "running_config": {"type": "str"}, diff --git a/plugins/module_utils/network/ios/config/vrf_global/vrf_global.py b/plugins/module_utils/network/ios/config/vrf_global/vrf_global.py index c30422abf..e8fa4ec62 100644 --- a/plugins/module_utils/network/ios/config/vrf_global/vrf_global.py +++ b/plugins/module_utils/network/ios/config/vrf_global/vrf_global.py @@ -17,9 +17,7 @@ necessary to bring the current configuration to its desired end-state is created. """ - -from copy import deepcopy - +import q from ansible.module_utils.six import iteritems from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.resource_module import ( ResourceModule, @@ -75,21 +73,24 @@ def generate_commands(self): """Generate configuration commands to send based on want, have and desired state. """ + wantd = {entry["name"]: entry for entry in self.want} + q(wantd) haved = {entry["name"]: entry for entry in self.have} - - # if state is merged, merge want onto have and then compare + q(haved) + # if state is merged, merge want onto have if self.state == "merged": wantd = dict_merge(haved, wantd) - # if state is deleted, empty out wantd and set haved to wantd + # if state is deleted, limit the have to anything in want & set want to nothing if self.state == "deleted": haved = {k: v for k, v in iteritems(haved) if k in wantd or not wantd} wantd = {} - if self.state == "purged": - haved = {k: v for k, v in iteritems(haved) if k in wantd or not wantd} - wantd = {} + # if self.state in ["overridden", "deleted"]: + # for k, have in iteritems(haved): + # if k not in wantd: + # self.addcmd(have, "name", True) self._compare(want=wantd, have=haved) @@ -99,6 +100,7 @@ def _compare(self, want, have): the `want` and `have` data with the `parsers` defined for the Vrf_global network resource. """ + self._compare_vrf(want=want, have=have) def _compare_vrf(self, want, have): @@ -113,8 +115,8 @@ def _compare_vrf(self, want, have): self.compare(parsers=self.parsers, want=entry, have=vrf_have) - # for deleted and overridden state - if self.state != "replaced": + # for purged state + if self.state == "purged": begin = len(self.commands) for name, entry in iteritems(have): self.commands.insert(begin, "no vrf definition {0}".format(name)) diff --git a/plugins/module_utils/network/ios/facts/vrf_global/vrf_global.py b/plugins/module_utils/network/ios/facts/vrf_global/vrf_global.py index a95640ab6..107cf8309 100644 --- a/plugins/module_utils/network/ios/facts/vrf_global/vrf_global.py +++ b/plugins/module_utils/network/ios/facts/vrf_global/vrf_global.py @@ -38,6 +38,18 @@ def get_config(self, connection): return connection.get("show running-config vrf") + def dict_to_list(self, vrf_data): + """Convert a dictionary to a list of dictionaries""" + + facts_output = {"vrfs": []} + + for vrf_entry in vrf_data.get("vrfs", []): + if "vrfs" in vrf_entry: + vrf_entry["vrfs"] = list(vrf_entry["vrfs"].values()) + facts_output["vrfs"].append(vrf_entry) + + return facts_output + def populate_facts(self, connection, ansible_facts, data=None): """ Populate the facts for Vrf_global network resource @@ -48,23 +60,29 @@ def populate_facts(self, connection, ansible_facts, data=None): :rtype: dictionary :returns: facts """ + # import epdb; epdb.serve() facts = {} - objs = [] if not data: data = self.get_config(connection) # parse native config using the Vrf_global template vrf_global_parser = Vrf_globalTemplate(lines=data.splitlines(), module=self._module) - objs = list(vrf_global_parser.parse().values()) + objs = vrf_global_parser.parse() - ansible_facts['ansible_network_resources'].pop('vrf_global', None) - - params = utils.remove_empties( - vrf_global_parser.validate_config(self.argument_spec, {"config": objs}, redact=True) + # Convert the dictionary to a list of dictionaries + objs["vrfs"] = ( + objs["vrfs"].values() if "vrfs" in objs else [] ) - facts['vrf_global'] = params.get("config", {}) - ansible_facts['ansible_network_resources'].update(facts) + facts_output = self.dict_to_list(objs) + + ansible_facts['ansible_network_resources'].pop('vrf_global', None) + + if objs["vrfs"]: + params = vrf_global_parser.validate_config(self.argument_spec, {"config": facts_output}, redact=True) + params = utils.remove_empties(params) + facts['vrf_global'] = params["config"] + ansible_facts['ansible_network_resources'].update(facts) return ansible_facts diff --git a/plugins/module_utils/network/ios/rm_templates/vrf_global.py b/plugins/module_utils/network/ios/rm_templates/vrf_global.py index 208ee8982..61c5d970d 100644 --- a/plugins/module_utils/network/ios/rm_templates/vrf_global.py +++ b/plugins/module_utils/network/ios/rm_templates/vrf_global.py @@ -40,8 +40,10 @@ def __init__(self, lines=None, module=None): ), "setval": "vrf definition {{ name }}", "result": { - '{{ name }}': { - 'name': '{{ name }}', + "vrfs": { + '{{ name }}': { + 'name': '{{ name }}', + }, }, }, "shared": True, @@ -55,9 +57,11 @@ def __init__(self, lines=None, module=None): ), "setval": "description {{ description }}", "result": { - '{{ name }}': { - 'name': '{{ name }}', - 'description': '{{ description }}', + "vrfs": { + '{{ name }}': { + 'name': '{{ name }}', + 'description': '{{ description }}', + }, }, }, }, @@ -70,11 +74,13 @@ def __init__(self, lines=None, module=None): ), "setval": "ipv4 multicast multitopology", "result": { - '{{ name }}': { - 'name': '{{ name }}', - 'ipv4': { - 'multicast': { - 'multitopology': "{{ true if multitopology is defined }}", + "vrfs": { + '{{ name }}': { + 'name': '{{ name }}', + 'ipv4': { + 'multicast': { + 'multitopology': "{{ true if multitopology is defined }}", + }, }, }, }, @@ -89,11 +95,13 @@ def __init__(self, lines=None, module=None): ), "setval": "ipv6 multicast multitopology", "result": { - '{{ name }}': { - 'name': '{{ name }}', - 'ipv6': { - 'multicast': { - 'multitopology': "{{ true if multitopology is defined }}", + "vrfs": { + '{{ name }}': { + 'name': '{{ name }}', + 'ipv6': { + 'multicast': { + 'multitopology': "{{ true if multitopology is defined }}", + }, }, }, }, @@ -108,9 +116,11 @@ def __init__(self, lines=None, module=None): ), "setval": "rd {{ rd }}", "result": { - '{{ name }}': { - 'name': '{{ name }}', - "rd": "{{ rd }}", + "vrfs": { + '{{ name }}': { + 'name': '{{ name }}', + "rd": "{{ rd }}", + }, }, }, }, @@ -123,10 +133,12 @@ def __init__(self, lines=None, module=None): ), "setval": "route-target export {{ route_target.export }}", "result": { - '{{ name }}': { - 'name': '{{ name }}', - "route_target": { - "export": "{{ route_target_export }}", + "vrfs": { + '{{ name }}': { + 'name': '{{ name }}', + "route_target": { + "export": "{{ route_target_export }}", + }, }, }, }, @@ -140,10 +152,12 @@ def __init__(self, lines=None, module=None): ), "setval": "route-target import {{ route_target.import_config}}", "result": { - '{{ name }}': { - 'name': '{{ name }}', - "route_target": { - "import_config": "{{ route_target_import_config }}", + "vrfs": { + '{{ name }}': { + 'name': '{{ name }}', + "route_target": { + "import_config": "{{ route_target_import_config }}", + }, }, }, }, @@ -157,10 +171,12 @@ def __init__(self, lines=None, module=None): ), "setval": "route-target both {{ route_target.both }}", "result": { - '{{ name }}': { - 'name': '{{ name }}', - "route_target": { - "both": "{{ route_target_both }}", + "vrfs": { + '{{ name }}': { + 'name': '{{ name }}', + "route_target": { + "both": "{{ route_target_both }}", + }, }, }, }, @@ -174,10 +190,12 @@ def __init__(self, lines=None, module=None): ), "setval": "vnet tag {{ vnet.tag }}", "result": { - '{{ name }}': { - 'name': '{{ name }}', - "vnet": { - "tag": "{{ vnet_tag }}", + "vrfs": { + '{{ name }}': { + 'name': '{{ name }}', + "vnet": { + "tag": "{{ vnet_tag }}", + }, }, }, }, @@ -191,10 +209,12 @@ def __init__(self, lines=None, module=None): ), "setval": "vpn id {{ vpn.id }}", "result": { - '{{ name }}': { - 'name': '{{ name }}', - "vpn": { - "id": "{{ vpn_id }}", + "vrfs": { + '{{ name }}': { + 'name': '{{ name }}', + "vpn": { + "id": "{{ vpn_id }}", + }, }, }, }, diff --git a/plugins/modules/ios_vrf_global.py b/plugins/modules/ios_vrf_global.py index 0334c2182..8af1e9c90 100644 --- a/plugins/modules/ios_vrf_global.py +++ b/plugins/modules/ios_vrf_global.py @@ -16,77 +16,81 @@ module: ios_vrf_global short_description: Resource module to configure VRF definitions. description: This module provides declarative management of VRF definitions on Cisco IOS. -version_added: 7.0.0 +version_added: 8.0.0 author: Ruchi Pakhle (@Ruchip16) notes: - - Tested against Cisco IOSXE version 17.3 on CML. + - Tested against Cisco IOS-XE version 17.3 on CML. - This module works with connection C(network_cli). See U(https://docs.ansible.com/ansible/latest/network/user_guide/platform_ios.html) options: config: - description: A list of device configurations for VRF. - type: list - elements: dict + description: A dictionary containing device configurations for VRF, including a list of VRF definitions. + type: dict suboptions: - name: - description: Name of the VRF. - type: str - required: true - description: - description: VRF specific description - type: str - ipv4: - description: VRF IPv4 configuration - type: dict + vrfs: + description: List of VRF definitions. + type: list + elements: dict suboptions: - multicast: - description: IP Multicast configuration + name: + description: Name of the VRF. + type: str + required: true + description: + description: VRF specific description + type: str + ipv4: + description: VRF IPv4 configuration type: dict suboptions: - multitopology: - description: Enable Multicast-Specific RPF Topology - type: bool - ipv6: - description: VRF IPv6 configuration - type: dict - suboptions: - multicast: - description: IP Multicast configuration + multicast: + description: IP Multicast configuration + type: dict + suboptions: + multitopology: + description: Enable Multicast-Specific RPF Topology + type: bool + ipv6: + description: VRF IPv6 configuration type: dict suboptions: - multitopology: - description: Enable Multicast-Specific RPF Topology - type: bool - rd: - description: Specify Route Distinguisher (RD). - type: str - route_target: - description: Specify Target VPN Extended Communities. - type: dict - suboptions: - export: - description: Export Target-VPN community. - type: str - import_config: - description: Export Target-VPN community. - type: str - both: - description: Both export and import Target-VPN community - type: str - vnet: - description: Virtual NETworking configuration. - type: dict - suboptions: - tag: - description: Identifier used to tag packets associated with this VNET - type: int - vpn: - description: Configure VPN ID for the VRF as specified in RFC 2685 - type: dict - suboptions: - id: - description: Configure VPN ID in RFC 2685 format + multicast: + description: IP Multicast configuration + type: dict + suboptions: + multitopology: + description: Enable Multicast-Specific RPF Topology + type: bool + rd: + description: Specify Route Distinguisher (RD). type: str + route_target: + description: Specify Target VPN Extended Communities. + type: dict + suboptions: + export: + description: Export Target-VPN community. + type: str + import_config: + description: Export Target-VPN community. + type: str + both: + description: Both export and import Target-VPN community + type: str + vnet: + description: Virtual NETworking configuration. + type: dict + suboptions: + tag: + description: Identifier used to tag packets associated with this VNET + type: int + vpn: + description: Configure VPN ID for the VRF as specified in RFC 2685 + type: dict + suboptions: + id: + description: Configure VPN ID in RFC 2685 format + type: str running_config: description: - This option is used only with state I(parsed). diff --git a/tests/unit/modules/network/ios/test_ios_vrf_global.py b/tests/unit/modules/network/ios/test_ios_vrf_global.py new file mode 100644 index 000000000..db73354fe --- /dev/null +++ b/tests/unit/modules/network/ios/test_ios_vrf_global.py @@ -0,0 +1,367 @@ +# (c) 2024, Ansible by Red Hat, inc +# 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 +from textwrap import dedent +from unittest.mock import patch + +from ansible_collections.cisco.ios.plugins.modules import ios_vrf_global +from ansible_collections.cisco.ios.tests.unit.modules.utils import AnsibleFailJson, set_module_args + +from .ios_module import TestIosModule + + +class TestIosVrfGlobalModule(TestIosModule): + """Test the ios_vrf_global module.""" + module = ios_vrf_global + + def setUp(self): + """Set up for ios_vrf_global module tests.""" + super(TestIosVrfGlobalModule, self).setUp() + + self.mock_get_resource_connection_facts = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.resource_module_base." + "get_resource_connection", + ) + self.get_resource_connection_facts = self.mock_get_resource_connection_facts.start() + + self.mock_execute_show_command = patch( + "ansible_collections.cisco.ios.plugins.module_utils.network.ios.facts.vrf_global.vrf_global." + "Vrf_globalFacts.get_config", + ) + self.execute_show_command = self.mock_execute_show_command.start() + + def tearDown(self): + super(TestIosVrfGlobalModule, self).tearDown() + self.mock_get_resource_connection_facts.stop() + self.mock_execute_show_command.stop() + + def test_ios_vrf_global_merged(self): + self.execute_show_command.return_value = dedent( + """\ + vrf definition test + description This is test VRF + ipv4 multicast multitopology + ipv6 multicast multitopology + rd 10.2.3.4:300 + route-target export 23.1.3.4:400 + route-target import 123.3.4.5:700 + vnet tag 34 + vpn id 3:4 + """, + ) + + set_module_args( + dict( + config=[ + dict( + name="VRF2", + description="This is a test VRF for merged state", + ipv4=dict(multicast=dict(multitopology=True)), + ipv6=dict(multicast=dict(multitopology=True)), + rd="2:3", + route_target=dict(export="23.1.3.4:400", import_config="10.1.3.4:400"), + vnet=dict(tag=200), + vpn=dict(id="2:45"), + ), + ], + state="merged", + ), + ) + commands = [ + "vrf definition VRF2", + "description This is a test VRF for merged state", + "ipv4 multicast multitopology", + "ipv6 multicast multitopology", + "rd 2:3", + "route-target export 23.1.3.4:400", + "route-target import 10.1.3.4:400", + "vnet tag 200", + "vpn id 2:45", + ] + result = self.execute_module(changed=True) + self.assertEqual(sorted(result["commands"]), sorted(commands)) + + def test_ios_vrf_global_merged_idempotent(self): + self.execute_show_command.return_value = dedent( + """\ + vrf definition VRF2 + description This is a test VRF for merged state + ipv4 multicast multitopology + ipv6 multicast multitopology + rd 2:3 + route-target export 23.1.3.4:400 + route-target import 10.1.3.4:400 + vnet tag 200 + vpn id 2:45 + """, + ) + set_module_args( + dict( + config=[ + dict( + name="VRF2", + description="This is a test VRF for merged state", + ipv4=dict(multicast=dict(multitopology=True)), + ipv6=dict(multicast=dict(multitopology=True)), + rd="2:3", + route_target=dict(export="23.1.3.4:400", import_config="10.1.3.4:400"), + vnet=dict(tag=200), + vpn=dict(id="2:45"), + ), + ], + state="merged", + ), + ) + self.execute_module(changed=False, commands=[]) + + def test_ios_vrf_global_overridden(self): + self.execute_show_command.return_value = dedent( + """\ + vrf definition VRF2 + description This is a test VRF for merged state + ipv4 multicast multitopology + ipv6 multicast multitopology + rd 2:3 + route-target export 23.1.3.4:400 + route-target import 10.1.3.4:400 + vnet tag 200 + vpn id 2:45 + """, + ) + set_module_args( + dict( + config=[ + dict( + name="VRF6", + description="VRF6 description", + ipv4=dict(multicast=dict(multitopology=True)), + ipv6=dict(multicast=dict(multitopology=True)), + rd="6:7", + route_target=dict(export="3.1.3.4:400", import_config="1.12.3.4:200"), + vnet=dict(tag=500), + vpn=dict(id="4:5"), + ), + ], + state="overridden", + ), + ) + commands = [ + "vrf definition VRF6", + "description VRF6 description", + "ipv4 multicast multitopology", + "ipv6 multicast multitopology", + "rd 6:7", + "route-target export 3.1.3.4:400", + "route-target import 1.12.3.4:200", + "vnet tag 500", + "vpn id 4:5", + "no vrf definition VRF2", + ] + result = self.execute_module(changed=True) + self.assertEqual(sorted(result["commands"]), sorted(commands)) + + def test_ios_vrf_global_replaced(self): + self.execute_show_command.return_value = dedent( + """\ + vrf definition VRF6 + description VRF6 description + ipv4 multicast multitopology + ipv6 multicast multitopology + rd 6:7 + route-target export 3.1.3.4:400 + route-target import 1.12.3.4:200 + vnet tag 500 + vpn id 4:5 + """, + ) + set_module_args( + dict( + config=[ + dict( + name="VRF7", + description="VRF7 description", + ipv4=dict(multicast=dict(multitopology=True)), + ipv6=dict(multicast=dict(multitopology=True)), + rd="7:8", + route_target=dict(export="23.1.3.4:500", import_config="12.1.3.4:400"), + vnet=dict(tag=300), + vpn=dict(id="2:45"), + ), + ], + state="replaced", + ), + ) + commands = [ + "vrf definition VRF7", + "description VRF7 description", + "ipv4 multicast multitopology", + "ipv6 multicast multitopology", + "rd 7:8", + "route-target export 23.1.3.4:500", + "route-target import 12.1.3.4:400", + "vnet tag 300", + "vpn id 2:45", + ] + result = self.execute_module(changed=True) + self.assertEqual(sorted(result["commands"]), sorted(commands)) + + def test_ios_vrf_global_replaced_idempotent(self): + self.execute_show_command.return_value = dedent( + """\ + vrf definition VRF7 + description VRF7 description + ipv4 multicast multitopology + ipv6 multicast multitopology + rd 7:8 + route-target export 23.1.3.4:500 + route-target import 12.1.3.4:400 + vnet tag 300 + vpn id 2:45 + """, + ) + set_module_args( + dict( + config=[ + dict( + name="VRF7", + description="VRF7 description", + ipv4=dict(multicast=dict(multitopology=True)), + ipv6=dict(multicast=dict(multitopology=True)), + rd="7:8", + route_target=dict(export="23.1.3.4:500", import_config="12.1.3.4:400"), + vnet=dict(tag=300), + vpn=dict(id="2:45"), + ), + ], + state="replaced", + ), + ) + self.execute_module(changed=False, commands=[]) + + # def test_ios_vrf_global_deleted(self): + # self.execute_show_command.return_value = dedent( + # """\ + # router bgp 65000 + # no bgp default ipv4-unicast + # bgp nopeerup-delay post-boot 10 + # bgp bestpath compare-routerid + # bgp advertise-best-external + # timers bgp 100 200 150 + # redistribute connected metric 10 + # neighbor 192.0.2.1 remote-as 100 + # neighbor 192.0.2.1 route-map test-route out + # address-family ipv4 + # neighbor 192.0.2.28 activate + # neighbor 172.31.35.140 activate + # """, + # ) + # set_module_args(dict(config=dict(as_number=65000), state="deleted")) + # commands = [ + # "router bgp 65000", + # "bgp default ipv4-unicast", + # "no timers bgp 100 200 150", + # "no bgp advertise-best-external", + # "no bgp bestpath compare-routerid", + # "no bgp nopeerup-delay post-boot 10", + # "no neighbor 192.0.2.1", + # "no redistribute connected", + # ] + # result = self.execute_module(changed=True) + # self.assertEqual(sorted(result["commands"]), sorted(commands)) + + # def test_ios_vrf_global_deleted_empty(self): + # self.execute_show_command.return_value = dedent( + # """\ + # """, + # ) + # set_module_args(dict(config=dict(as_number=65000), state="deleted")) + # result = self.execute_module(changed=False) + # self.assertEqual(result["commands"], []) + + def test_ios_vrf_global_purged(self): + self.execute_show_command.return_value = dedent( + """\ + vrf definition VRF7 + description VRF7 description + ipv4 multicast multitopology + ipv6 multicast multitopology + rd 7:8 + route-target export 23.1.3.4:500 + route-target import 12.1.3.4:400 + vnet tag 300 + vpn id 2:45 + """, + ) + set_module_args(dict(state="purged")) + commands = ["no vrf definition VRF7"] + self.execute_module(changed=True, commands=commands) + + def test_deprecated_attributes_rendered(self): + set_module_args( + dict( + config=[ + dict( + name="VRF2", + description="This is a test VRF for rendered state", + ipv4=dict(multicast=dict(multitopology=True)), + ipv6=dict(multicast=dict(multitopology=True)), + rd="2:3", + route_target=dict(export="23.1.3.4:400", import_config="10.1.3.4:400"), + vnet=dict(tag=200), + vpn=dict(id="2:45"), + ), + ], + state="rendered", + ), + ) + commands = [ + "vrf definition VRF2", + "description This is a test VRF for rendered state", + "ipv4 multicast multitopology", + "ipv6 multicast multitopology", + "rd 2:3", + "route-target export 23.1.3.4:400", + "route-target import 10.1.3.4:400", + "vnet tag 200", + "vpn id 2:45", + ] + result = self.execute_module(changed=False) + self.assertEqual(sorted(result["rendered"]), sorted(commands)) + + def test_ios_vrf_global_parsed(self): + set_module_args( + dict( + running_config=dedent( + """\ + vrf definition test + vnet tag 34 + description This is test VRF + ipv4 multicast multitopology + ipv6 multicast multitopology + rd 10.2.3.4:300 + vpn id 3:4 + route-target export 23.1.3.4:400 + route-target import 123.3.4.5:700 + """, + ), + state="parsed", + ), + ) + result = self.execute_module(changed=False) + parsed_list = [ + { + "name": "test", + "description": "This is test VRF", + "ipv4": {"multicast": {"multitopology": True}}, + "ipv6": {"multicast": {"multitopology": True}}, + "rd": "10.2.3.4:300", + "route_target": {"export": "23.1.3.4:400", "import_config": "123.3.4.5:700"}, + "vnet": {"tag": 34}, + "vpn": {"id": "3:4"}, + } + ] + self.assertEqual(parsed_list, result["parsed"])