diff --git a/lib/ansible/modules/network/ios/ios_vrf.py b/lib/ansible/modules/network/ios/ios_vrf.py
index 70fee2f7453..9d01ff4de02 100644
--- a/lib/ansible/modules/network/ios/ios_vrf.py
+++ b/lib/ansible/modules/network/ios/ios_vrf.py
@@ -15,7 +15,6 @@
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see .
#
-
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'network'}
@@ -103,6 +102,30 @@ options:
description:
- Adds an import list of extended route target communities to the VRF.
version_added: "2.5"
+ route_both_ipv4:
+ description:
+ - Adds an export and import list of extended route target communities in address-family configuration submode to the VRF.
+ version_added: "2.7"
+ route_export_ipv4:
+ description:
+ - Adds an export list of extended route target communities in address-family configuration submode to the VRF.
+ version_added: "2.7"
+ route_import_ipv4:
+ description:
+ - Adds an import list of extended route target communities in address-family configuration submode to the VRF.
+ version_added: "2.7"
+ route_both_ipv6:
+ description:
+ - Adds an export and import list of extended route target communities in address-family configuration submode to the VRF.
+ version_added: "2.7"
+ route_export_ipv6:
+ description:
+ - Adds an export list of extended route target communities in address-family configuration submode to the VRF.
+ version_added: "2.7"
+ route_import_ipv6:
+ description:
+ - Adds an import list of extended route target communities in address-family configuration submode to the VRF.
+ version_added: "2.7"
"""
EXAMPLES = """
@@ -134,6 +157,22 @@ EXAMPLES = """
- 1:100
- 3:100
+- name: Creates a list of import RTs in address-family configuration submode for the VRF with the same parameters
+ ios_vrf:
+ name: test_import_ipv4
+ rd: 1:100
+ route_import_ipv4:
+ - 1:100
+ - 3:100
+
+- name: Creates a list of import RTs in address-family configuration submode for the VRF with the same parameters
+ ios_vrf:
+ name: test_import_ipv6
+ rd: 1:100
+ route_import_ipv6:
+ - 1:100
+ - 3:100
+
- name: Creates a list of export RTs for the VRF with the same parameters
ios_vrf:
name: test_export
@@ -142,6 +181,22 @@ EXAMPLES = """
- 1:100
- 3:100
+- name: Creates a list of export RTs in address-family configuration submode for the VRF with the same parameters
+ ios_vrf:
+ name: test_export_ipv4
+ rd: 1:100
+ route_export_ipv4:
+ - 1:100
+ - 3:100
+
+- name: Creates a list of export RTs in address-family configuration submode for the VRF with the same parameters
+ ios_vrf:
+ name: test_export_ipv6
+ rd: 1:100
+ route_export_ipv6:
+ - 1:100
+ - 3:100
+
- name: Creates a list of import and export route targets for the VRF with the same parameters
ios_vrf:
name: test_both
@@ -149,6 +204,23 @@ EXAMPLES = """
route_both:
- 1:100
- 3:100
+
+- name: Creates a list of import and export route targets in address-family configuration submode for the VRF with the same parameters
+ ios_vrf:
+ name: test_both_ipv4
+ rd: 1:100
+ route_both_ipv4:
+ - 1:100
+ - 3:100
+
+- name: Creates a list of import and export route targets in address-family configuration submode for the VRF with the same parameters
+ ios_vrf:
+ name: test_both_ipv6
+ rd: 1:100
+ route_both_ipv6:
+ - 1:100
+ - 3:100
+
"""
RETURN = """
@@ -220,12 +292,13 @@ def add_command_to_vrf(name, cmd, commands):
def map_obj_to_commands(updates, module):
commands = list()
- state = module.params['state'] # FIXME NOT USED
for update in updates:
want, have = update
def needs_update(want, have, x):
+ if isinstance(want.get(x), list) and isinstance(have.get(x), list):
+ return want.get(x) and (want.get(x) != have.get(x)) and not all(elem in have.get(x) for elem in want.get(x))
return want.get(x) and (want.get(x) != have.get(x))
if want['state'] == 'absent':
@@ -257,9 +330,40 @@ def map_obj_to_commands(updates, module):
cmd = 'route-target export %s' % route
add_command_to_vrf(want['name'], cmd, commands)
- if needs_update(want, have, 'route_both'):
- for route in want['route_both']:
- cmd = 'route-target both %s' % route
+ if needs_update(want, have, 'route_import_ipv4'):
+ cmd = 'address-family ipv4'
+ add_command_to_vrf(want['name'], cmd, commands)
+ for route in want['route_import_ipv4']:
+ cmd = 'route-target import %s' % route
+ add_command_to_vrf(want['name'], cmd, commands)
+ cmd = 'exit-address-family'
+ add_command_to_vrf(want['name'], cmd, commands)
+
+ if needs_update(want, have, 'route_export_ipv4'):
+ cmd = 'address-family ipv4'
+ add_command_to_vrf(want['name'], cmd, commands)
+ for route in want['route_export_ipv4']:
+ cmd = 'route-target export %s' % route
+ add_command_to_vrf(want['name'], cmd, commands)
+ cmd = 'exit-address-family'
+ add_command_to_vrf(want['name'], cmd, commands)
+
+ if needs_update(want, have, 'route_import_ipv6'):
+ cmd = 'address-family ipv6'
+ add_command_to_vrf(want['name'], cmd, commands)
+ for route in want['route_import_ipv6']:
+ cmd = 'route-target import %s' % route
+ add_command_to_vrf(want['name'], cmd, commands)
+ cmd = 'exit-address-family'
+ add_command_to_vrf(want['name'], cmd, commands)
+
+ if needs_update(want, have, 'route_export_ipv6'):
+ cmd = 'address-family ipv6'
+ add_command_to_vrf(want['name'], cmd, commands)
+ for route in want['route_export_ipv6']:
+ cmd = 'route-target export %s' % route
+ add_command_to_vrf(want['name'], cmd, commands)
+ cmd = 'exit-address-family'
add_command_to_vrf(want['name'], cmd, commands)
if want['interfaces'] is not None:
@@ -324,15 +428,77 @@ def parse_export(configobj, name):
return matches
-def parse_both(configobj, name):
+def parse_both(configobj, name, address_family='global'):
+ rd_pattern = re.compile('(?P.+:.+)')
matches = list()
- export_match = parse_export(configobj, name)
- import_match = parse_import(configobj, name)
- matches.extend(export_match)
- matches.extend(import_match)
+ export_match = None
+ import_match = None
+ if address_family == "global":
+ export_match = parse_export(configobj, name)
+ import_match = parse_import(configobj, name)
+ elif address_family == "ipv4":
+ export_match = parse_export_ipv4(configobj, name)
+ import_match = parse_import_ipv4(configobj, name)
+ elif address_family == "ipv6":
+ export_match = parse_export_ipv6(configobj, name)
+ import_match = parse_import_ipv6(configobj, name)
+ if import_match and export_match:
+ for ex in export_match:
+ exrd = rd_pattern.search(ex)
+ exrd = exrd.groupdict().get('rd')
+ for im in import_match:
+ imrd = rd_pattern.search(im)
+ imrd = imrd.groupdict().get('rd')
+ if exrd == imrd:
+ matches.extend([exrd]) if exrd not in matches else None
+ matches.extend([imrd]) if imrd not in matches else None
return matches
+def parse_import_ipv4(configobj, name):
+ cfg = configobj['vrf definition %s' % name]
+ try:
+ subcfg = cfg['address-family ipv4']
+ subcfg = '\n'.join(subcfg.children)
+ matches = re.findall(r'route-target\s+import\s+(.+)', subcfg, re.M)
+ return matches
+ except KeyError:
+ pass
+
+
+def parse_export_ipv4(configobj, name):
+ cfg = configobj['vrf definition %s' % name]
+ try:
+ subcfg = cfg['address-family ipv4']
+ subcfg = '\n'.join(subcfg.children)
+ matches = re.findall(r'route-target\s+export\s+(.+)', subcfg, re.M)
+ return matches
+ except KeyError:
+ pass
+
+
+def parse_import_ipv6(configobj, name):
+ cfg = configobj['vrf definition %s' % name]
+ try:
+ subcfg = cfg['address-family ipv6']
+ subcfg = '\n'.join(subcfg.children)
+ matches = re.findall(r'route-target\s+import\s+(.+)', subcfg, re.M)
+ return matches
+ except KeyError:
+ pass
+
+
+def parse_export_ipv6(configobj, name):
+ cfg = configobj['vrf definition %s' % name]
+ try:
+ subcfg = cfg['address-family ipv6']
+ subcfg = '\n'.join(subcfg.children)
+ matches = re.findall(r'route-target\s+export\s+(.+)', subcfg, re.M)
+ return matches
+ except KeyError:
+ pass
+
+
def map_config_to_obj(module):
config = get_config(module)
configobj = NetworkConfig(indent=1, contents=config)
@@ -341,7 +507,6 @@ def map_config_to_obj(module):
return list()
instances = list()
-
for item in set(match):
obj = {
'name': item,
@@ -351,7 +516,13 @@ def map_config_to_obj(module):
'interfaces': parse_interfaces(configobj, item),
'route_import': parse_import(configobj, item),
'route_export': parse_export(configobj, item),
- 'route_both': parse_both(configobj, item)
+ 'route_both': parse_both(configobj, item),
+ 'route_import_ipv4': parse_import_ipv4(configobj, item),
+ 'route_export_ipv4': parse_export_ipv4(configobj, item),
+ 'route_both_ipv4': parse_both(configobj, item, address_family='ipv4'),
+ 'route_import_ipv6': parse_import_ipv6(configobj, item),
+ 'route_export_ipv6': parse_export_ipv6(configobj, item),
+ 'route_both_ipv6': parse_both(configobj, item, address_family='ipv6'),
}
instances.append(obj)
return instances
@@ -396,7 +567,6 @@ def map_params_to_obj(module):
collection.append(item)
objects = list()
-
for item in collection:
get_value = partial(get_param_value, item=item, module=module)
item['description'] = get_value('description')
@@ -406,6 +576,21 @@ def map_params_to_obj(module):
item['route_import'] = get_value('route_import')
item['route_export'] = get_value('route_export')
item['route_both'] = get_value('route_both')
+ item['route_import_ipv4'] = get_value('route_import_ipv4')
+ item['route_export_ipv4'] = get_value('route_export_ipv4')
+ item['route_both_ipv4'] = get_value('route_both_ipv4')
+ item['route_import_ipv6'] = get_value('route_import_ipv6')
+ item['route_export_ipv6'] = get_value('route_export_ipv6')
+ item['route_both_ipv6'] = get_value('route_both_ipv6')
+ both_addresses_family = ["", "_ipv6", "_ipv4"]
+ for address_family in both_addresses_family:
+ if item["route_both%s" % address_family]:
+ if not item["route_export%s" % address_family]:
+ item["route_export%s" % address_family] = list()
+ if not item["route_import%s" % address_family]:
+ item["route_import%s" % address_family] = list()
+ item["route_export%s" % address_family].extend(get_value("route_both%s" % address_family))
+ item["route_import%s" % address_family].extend(get_value("route_both%s" % address_family))
item['associated_interfaces'] = get_value('associated_interfaces')
objects.append(item)
@@ -421,13 +606,16 @@ def update_objects(want, have):
else:
for key, value in iteritems(entry):
if value:
- if isinstance(value, list):
- if sorted(value) != sorted(item[key]):
+ try:
+ if isinstance(value, list):
+ if sorted(value) != sorted(item[key]):
+ if (entry, item) not in updates:
+ updates.append((entry, item))
+ elif value != item[key]:
if (entry, item) not in updates:
updates.append((entry, item))
- elif value != item[key]:
- if (entry, item) not in updates:
- updates.append((entry, item))
+ except TypeError:
+ pass
return updates
@@ -469,6 +657,13 @@ def main():
route_export=dict(type='list'),
route_import=dict(type='list'),
route_both=dict(type='list'),
+ route_export_ipv4=dict(type='list'),
+ route_import_ipv4=dict(type='list'),
+ route_both_ipv4=dict(type='list'),
+ route_export_ipv6=dict(type='list'),
+ route_import_ipv6=dict(type='list'),
+ route_both_ipv6=dict(type='list'),
+
interfaces=dict(type='list'),
associated_interfaces=dict(type='list'),
@@ -480,7 +675,7 @@ def main():
argument_spec.update(ios_argument_spec)
- mutually_exclusive = [('name', 'vrfs'), ('route_import', 'route_both'), ('route_export', 'route_both')]
+ mutually_exclusive = [('name', 'vrfs')]
module = AnsibleModule(argument_spec=argument_spec,
mutually_exclusive=mutually_exclusive,
supports_check_mode=True)
@@ -493,7 +688,6 @@ def main():
want = map_params_to_obj(module)
have = map_config_to_obj(module)
-
commands = map_obj_to_commands(update_objects(want, have), module)
if module.params['purge']:
diff --git a/test/units/modules/network/ios/fixtures/ios_vrf_config.cfg b/test/units/modules/network/ios/fixtures/ios_vrf_config.cfg
index e09c2d12115..0a2d35f8fb8 100644
--- a/test/units/modules/network/ios/fixtures/ios_vrf_config.cfg
+++ b/test/units/modules/network/ios/fixtures/ios_vrf_config.cfg
@@ -8,6 +8,70 @@ vrf definition test_2
!
vrf definition test_3
!
+vrf definition test_17
+ rd 2:100
+ !
+ address-family ipv4
+ exit-address-family
+ !
+ address-family ipv6
+ route-target export 168.0.0.15:100
+ route-target export 4:100
+ route-target export 2:100
+ route-target export 168.0.0.13:100
+ route-target import 168.0.0.14:100
+ route-target import 2:100
+ route-target import 168.0.0.13:100
+ exit-address-family
+!
+vrf definition test_18
+ rd 168.0.0.9:100
+ !
+ address-family ipv4
+ route-target export 168.0.0.10:100
+ route-target export 168.0.0.9:100
+ route-target export 3:100
+ route-target import 168.0.0.9:100
+ route-target import 3:100
+ route-target import 168.0.0.10:600
+ exit-address-family
+ !
+ address-family ipv6
+ exit-address-family
+!
+vrf definition test_19
+ rd 10:700
+ route-target export 2:102
+ route-target export 2:103
+ route-target export 2:100
+ route-target export 2:101
+ route-target import 2:104
+ route-target import 2:105
+ route-target import 2:100
+ route-target import 2:101
+ !
+ address-family ipv4
+ route-target export 2:102
+ route-target export 2:103
+ route-target export 2:100
+ route-target export 2:101
+ route-target import 2:104
+ route-target import 2:105
+ route-target import 2:100
+ route-target import 2:101
+ exit-address-family
+ !
+ address-family ipv6
+ route-target export 2:102
+ route-target export 2:103
+ route-target export 2:100
+ route-target export 2:101
+ route-target import 2:104
+ route-target import 2:105
+ route-target import 2:100
+ route-target import 2:101
+ exit-address-family
+!
interface Ethernet1
ip address 1.2.3.4/5
!
diff --git a/test/units/modules/network/ios/test_ios_vrf.py b/test/units/modules/network/ios/test_ios_vrf.py
index 87e01a89961..1565a650746 100644
--- a/test/units/modules/network/ios/test_ios_vrf.py
+++ b/test/units/modules/network/ios/test_ios_vrf.py
@@ -18,6 +18,7 @@
# Make coding more python3-ish
from __future__ import (absolute_import, division, print_function)
+
__metaclass__ = type
from ansible.compat.tests.mock import patch
@@ -83,12 +84,14 @@ class TestIosVrfModule(TestIosModule):
def test_ios_vrf_purge_all(self):
set_module_args(dict(purge=True))
- commands = ['no vrf definition test_1', 'no vrf definition test_2', 'no vrf definition test_3']
+ commands = ['no vrf definition test_1', 'no vrf definition test_2', 'no vrf definition test_3', 'no vrf definition test_17',
+ 'no vrf definition test_18', 'no vrf definition test_19']
self.execute_module(changed=True, commands=commands)
def test_ios_vrf_purge_all_but_one(self):
set_module_args(dict(name='test_1', purge=True))
- commands = ['no vrf definition test_2', 'no vrf definition test_3']
+ commands = ['no vrf definition test_2', 'no vrf definition test_3', 'no vrf definition test_17', 'no vrf definition test_18',
+ 'no vrf definition test_19']
self.execute_module(changed=True, commands=commands)
def test_ios_vrfs_no_purge(self):
@@ -101,7 +104,7 @@ class TestIosVrfModule(TestIosModule):
vrfs = [{'name': 'test_1'}, {'name': 'test_4'}]
set_module_args(dict(vrfs=vrfs, purge=True))
commands = ['vrf definition test_4', 'address-family ipv4', 'exit', 'address-family ipv6', 'exit', 'no vrf definition test_2',
- 'no vrf definition test_3']
+ 'no vrf definition test_3', 'no vrf definition test_17', 'no vrf definition test_18', 'no vrf definition test_19']
self.execute_module(changed=True, commands=commands)
def test_ios_vrfs_global_arg(self):
@@ -126,8 +129,8 @@ class TestIosVrfModule(TestIosModule):
def test_ios_vrf_route_both(self):
set_module_args(dict(name='test_5', rd='2:100', route_both=['2:100', '3:100']))
- commands = ['vrf definition test_5', 'address-family ipv4', 'exit', 'address-family ipv6', 'exit', 'rd 2:100', 'route-target both 2:100',
- 'route-target both 3:100']
+ commands = ['vrf definition test_5', 'address-family ipv4', 'exit', 'address-family ipv6', 'exit', 'rd 2:100', 'route-target import 2:100',
+ 'route-target import 3:100', 'route-target export 2:100', 'route-target export 3:100']
self.execute_module(changed=True, commands=commands, sort=False)
def test_ios_vrf_route_import(self):
@@ -142,6 +145,68 @@ class TestIosVrfModule(TestIosModule):
'route-target export 4:100']
self.execute_module(changed=True, commands=commands, sort=False)
- def test_ios_vrf_route_both_exclusive(self):
+ def test_ios_vrf_route_both_mixed(self):
set_module_args(dict(name='test_8', rd='5:100', route_both=['3:100', '4:100'], route_export=['3:100', '4:100']))
- self.execute_module(failed=True)
+ self.execute_module(changed=True)
+
+ def test_ios_vrf_route_both_ipv4(self):
+ set_module_args(dict(name='test_9', rd='168.0.0.9:100', route_both_ipv4=['168.0.0.9:100', '3:100']))
+ commands = ['vrf definition test_9', 'address-family ipv4', 'exit', 'address-family ipv6', 'exit', 'rd 168.0.0.9:100', 'address-family ipv4',
+ 'route-target import 168.0.0.9:100', 'route-target import 3:100', 'exit-address-family', 'address-family ipv4',
+ 'route-target export 168.0.0.9:100', 'route-target export 3:100', 'exit-address-family']
+ self.execute_module(changed=True, commands=commands, sort=False)
+
+ def test_ios_vrf_route_import_ipv4(self):
+ set_module_args(dict(name='test_10', rd='168.0.0.10:100', route_import_ipv4=['168.0.0.10:100', '3:100']))
+ commands = ['vrf definition test_10', 'address-family ipv4', 'exit', 'address-family ipv6', 'exit', 'rd 168.0.0.10:100', 'address-family ipv4',
+ 'route-target import 168.0.0.10:100', 'route-target import 3:100', 'exit-address-family']
+ self.execute_module(changed=True, commands=commands, sort=False)
+
+ def test_ios_vrf_route_export_ipv4(self):
+ set_module_args(dict(name='test_11', rd='168.0.0.11:100', route_export_ipv4=['168.0.0.11:100', '3:100']))
+ commands = ['vrf definition test_11', 'address-family ipv4', 'exit', 'address-family ipv6', 'exit', 'rd 168.0.0.11:100', 'address-family ipv4',
+ 'route-target export 168.0.0.11:100', 'route-target export 3:100', 'exit-address-family']
+ self.execute_module(changed=True, commands=commands, sort=False)
+
+ def test_ios_vrf_route_both_ipv4_mixed(self):
+ set_module_args(dict(name='test_12', rd='168.0.0.12:100', route_both_ipv4=['168.0.0.12:100', '3:100'], route_export_ipv4=['168.0.0.15:100', '6:100']))
+ self.execute_module(changed=True)
+
+ def test_ios_vrf_route_both_ipv6(self):
+ set_module_args(dict(name='test_13', rd='2:100', route_both_ipv6=['2:100', '168.0.0.13:100']))
+ commands = ['vrf definition test_13', 'address-family ipv4', 'exit', 'address-family ipv6', 'exit', 'rd 2:100', 'address-family ipv6',
+ 'route-target import 2:100', 'route-target import 168.0.0.13:100', 'exit-address-family', 'address-family ipv6',
+ 'route-target export 2:100', 'route-target export 168.0.0.13:100', 'exit-address-family']
+ self.execute_module(changed=True, commands=commands, sort=False)
+
+ def test_ios_vrf_route_import_ipv6(self):
+ set_module_args(dict(name='test_14', rd='3:100', route_import_ipv6=['3:100', '168.0.0.14:100']))
+ commands = ['vrf definition test_14', 'address-family ipv4', 'exit', 'address-family ipv6', 'exit', 'rd 3:100', 'address-family ipv6',
+ 'route-target import 3:100', 'route-target import 168.0.0.14:100', 'exit-address-family']
+ self.execute_module(changed=True, commands=commands, sort=False)
+
+ def test_ios_vrf_route_export_ipv6(self):
+ set_module_args(dict(name='test_15', rd='4:100', route_export_ipv6=['168.0.0.15:100', '4:100']))
+ commands = ['vrf definition test_15', 'address-family ipv4', 'exit', 'address-family ipv6', 'exit', 'rd 4:100', 'address-family ipv6',
+ 'route-target export 168.0.0.15:100', 'route-target export 4:100', 'exit-address-family']
+ self.execute_module(changed=True, commands=commands, sort=False)
+
+ def test_ios_vrf_route_both_ipv6_mixed(self):
+ set_module_args(dict(name='test_16', rd='5:100', route_both_ipv6=['168.0.0.9:100', '4:100'], route_export_ipv6=['168.0.0.12:100', '6:100']))
+ self.execute_module(changed=True)
+
+ def test_ios_vrf_route_both_ipv6_mixed_idempotent(self):
+ set_module_args(dict(name='test_17', rd='2:100', route_import_ipv6=['168.0.0.14:100'], route_both_ipv6=['2:100', '168.0.0.13:100'],
+ route_export_ipv6=['168.0.0.15:100', '4:100']))
+ self.execute_module(changed=False, commands=[], sort=False)
+
+ def test_ios_vrf_route_both_ipv4_mixed_idempotent(self):
+ set_module_args(dict(name='test_18', rd='168.0.0.9:100', route_import_ipv4=['168.0.0.10:600'], route_export_ipv4=['168.0.0.10:100'],
+ route_both_ipv4=['168.0.0.9:100', '3:100']))
+ self.execute_module(changed=False, commands=[], sort=False)
+
+ def test_ios_vrf_all_route_both_idempotent(self):
+ set_module_args(dict(name='test_19', rd='10:700', route_both=['2:100', '2:101'], route_export=['2:102', '2:103'], route_import=['2:104', '2:105'],
+ route_both_ipv4=['2:100', '2:101'], route_export_ipv4=['2:102', '2:103'], route_import_ipv4=['2:104', '2:105'],
+ route_both_ipv6=['2:100', '2:101'], route_export_ipv6=['2:102', '2:103'], route_import_ipv6=['2:104', '2:105']))
+ self.execute_module(changed=False, commands=[], sort=False)