Resource module for IOS static routes (#64632)

* ios_static_routes
pull/49000/merge
Sumit Jaiswal 5 years ago committed by GitHub
parent 6629b9feef
commit 4c0eb4396b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -0,0 +1,85 @@
#
# -*- coding: utf-8 -*-
# Copyright 2019 Red Hat
# GNU General Public License v3.0+
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
#############################################
# WARNING #
#############################################
#
# This file is auto generated by the resource
# module builder playbook.
#
# Do not edit this file manually.
#
# Changes to this file will be over written
# by the resource module builder.
#
# Changes should be made in the model used to
# generate this file or in the resource module
# builder template.
#
#############################################
"""
The arg spec for the ios_static_routes module
"""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
class Static_RoutesArgs(object):
"""The arg spec for the ios_static_routes module
"""
def __init__(self, **kwargs):
pass
argument_spec = {
'config': {
'elements': 'dict',
'options': {
'vrf': {'type': 'str'},
'address_families': {
'elements': 'dict',
'type': 'list',
'options': {
'afi': {'required': True, 'choices': ['ipv4', 'ipv6'], 'type': 'str'},
'routes': {
'elements': 'dict',
'type': 'list',
'options': {
'dest': {'required': True, 'type': 'str'},
'topology': {'type': 'str'},
'next_hops': {
'elements': 'dict',
'type': 'list',
'options': {
'forward_router_address': {'type': 'str'},
'interface': {'type': 'str'},
'dhcp': {'type': 'bool'},
'distance_metric': {'type': 'int'},
'global': {'type': 'bool'},
'name': {'type': 'str'},
'multicast': {'type': 'bool'},
'permanent': {'type': 'bool'},
'tag': {'type': 'int'},
'track': {'type': 'int'}
}
}
}
}
}
}
},
'type': 'list'
},
'running_config': {'type': 'str'},
'state': {
'choices': ['merged', 'replaced', 'overridden', 'deleted', 'gathered', 'rendered', 'parsed'],
'default': 'merged',
'type': 'str'
}
}

@ -0,0 +1,532 @@
#
# -*- coding: utf-8 -*-
# Copyright 2019 Red Hat
# GNU General Public License v3.0+
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
"""
The ios_static_routes class
It is in this file where the current configuration (as dict)
is compared to the provided configuration (as dict) and the command set
necessary to bring the current configuration to it's desired end-state is
created
"""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
import copy
from ansible.module_utils.six import iteritems
from ansible.module_utils.network.common.cfg.base import ConfigBase
from ansible.module_utils.network.common.utils import to_list
from ansible.module_utils.network.ios.facts.facts import Facts
from ansible.module_utils.network.ios.utils.utils import new_dict_to_set, validate_n_expand_ipv4, filter_dict_having_none_value
class Static_Routes(ConfigBase):
"""
The ios_static_routes class
"""
gather_subset = [
'!all',
'!min',
]
gather_network_resources = [
'static_routes',
]
def __init__(self, module):
super(Static_Routes, self).__init__(module)
def get_static_routes_facts(self, data=None):
""" Get the 'facts' (the current configuration)
:rtype: A dictionary
:returns: The current configuration as a dictionary
"""
facts, _warnings = Facts(self._module).get_facts(self.gather_subset, self.gather_network_resources, data=data)
static_routes_facts = facts['ansible_network_resources'].get('static_routes')
if not static_routes_facts:
return []
return static_routes_facts
def execute_module(self):
""" Execute the module
:rtype: A dictionary
:returns: The result from module execution
"""
result = {'changed': False}
commands = list()
warnings = list()
if self.state in self.ACTION_STATES:
existing_static_routes_facts = self.get_static_routes_facts()
else:
existing_static_routes_facts = []
if self.state in self.ACTION_STATES or self.state == 'rendered':
commands.extend(self.set_config(existing_static_routes_facts))
if commands and self.state in self.ACTION_STATES:
if not self._module.check_mode:
self._connection.edit_config(commands)
result['changed'] = True
if self.state in self.ACTION_STATES:
result['commands'] = commands
if self.state in self.ACTION_STATES or self.state == 'gathered':
changed_static_routes_facts = self.get_static_routes_facts()
elif self.state == 'rendered':
result['rendered'] = commands
elif self.state == 'parsed':
running_config = self._module.params['running_config']
if not running_config:
self._module.fail_json(
msg="value of running_config parameter must not be empty for state parsed"
)
result['parsed'] = self.get_static_routes_facts(data=running_config)
else:
changed_static_routes_facts = []
if self.state in self.ACTION_STATES:
result['before'] = existing_static_routes_facts
if result['changed']:
result['after'] = changed_static_routes_facts
elif self.state == 'gathered':
result['gathered'] = changed_static_routes_facts
result['warnings'] = warnings
return result
def set_config(self, existing_static_routes_facts):
""" Collect the configuration from the args passed to the module,
collect the current configuration (as a dict from facts)
:rtype: A list
:returns: the commands necessary to migrate the current configuration
to the desired configuration
"""
want = self._module.params['config']
have = existing_static_routes_facts
resp = self.set_state(want, have)
return to_list(resp)
def set_state(self, want, have):
""" Select the appropriate function based on the state provided
:param want: the desired configuration as a dictionary
:param have: the current configuration as a dictionary
:rtype: A list
:returns: the commands necessary to migrate the current configuration
to the desired configuration
"""
state = self._module.params['state']
if state in ('overridden', 'merged', 'replaced', 'rendered') and not want:
self._module.fail_json(msg='value of config parameter must not be empty for state {0}'.format(state))
commands = []
if state == 'overridden':
commands = self._state_overridden(want, have)
elif state == 'deleted':
commands = self._state_deleted(want, have)
elif state == 'merged' or state == 'rendered':
commands = self._state_merged(want, have)
elif state == 'replaced':
commands = self._state_replaced(want, have)
return commands
def _state_replaced(self, want, have):
""" The command generator when state is replaced
:rtype: A list
:returns: the commands necessary to migrate the current configuration
to the desired configuration
"""
commands = []
# Drill each iteration of want n have and then based on dest and afi tyoe comparison take config call
for w in want:
for addr_want in w.get('address_families'):
for route_want in addr_want.get('routes'):
check = False
for h in have:
if h.get('address_families'):
for addr_have in h.get('address_families'):
for route_have in addr_have.get('routes'):
if route_want.get('dest') == route_have.get('dest')\
and addr_want['afi'] == addr_have['afi']:
check = True
have_set = set()
new_hops = []
for each in route_want.get('next_hops'):
want_set = set()
new_dict_to_set(each, [], want_set, 0)
new_hops.append(want_set)
new_dict_to_set(addr_have, [], have_set, 0)
# Check if the have dict next_hops value is diff from want dict next_hops
have_dict = filter_dict_having_none_value(route_want.get('next_hops')[0],
route_have.get('next_hops')[0])
# update the have_dict with forward_router_address
have_dict.update({'forward_router_address': route_have.get('next_hops')[0].
get('forward_router_address')})
# updating the have_dict with next_hops val that's not None
new_have_dict = {}
for k, v in have_dict.items():
if v is not None:
new_have_dict.update({k: v})
# Set the new config from the user provided want config
cmd = self._set_config(w, h, addr_want, route_want, route_have, new_hops, have_set)
if cmd:
# since inplace update isn't allowed for static routes, preconfigured
# static routes needs to be deleted before the new want static routes changes
# are applied
clear_route_have = copy.deepcopy(route_have)
# inplace update is allowed in case of ipv6 static routes, so not deleting it
# before applying the want changes
if ':' not in route_want.get('dest'):
commands.extend(self._clear_config({}, h, {}, addr_have,
{}, clear_route_have))
commands.extend(cmd)
if check:
break
if check:
break
if not check:
# For configuring any non-existing want config
new_hops = []
for each in route_want.get('next_hops'):
want_set = set()
new_dict_to_set(each, [], want_set, 0)
new_hops.append(want_set)
commands.extend(self._set_config(w, {}, addr_want, route_want, {}, new_hops, set()))
commands = [each for each in commands if 'no' in each] + \
[each for each in commands if 'no' not in each]
return commands
def _state_overridden(self, want, have):
""" The command generator when state is overridden
:rtype: A list
:returns: the commands necessary to migrate the current configuration
to the desired configuration
"""
commands = []
# Creating a copy of want, so that want dict is intact even after delete operation
# performed during override want n have comparison
temp_want = copy.deepcopy(want)
# Drill each iteration of want n have and then based on dest and afi tyoe comparison take config call
for h in have:
if h.get('address_families'):
for addr_have in h.get('address_families'):
for route_have in addr_have.get('routes'):
check = False
for w in temp_want:
for addr_want in w.get('address_families'):
count = 0
for route_want in addr_want.get('routes'):
if route_want.get('dest') == route_have.get('dest') \
and addr_want['afi'] == addr_have['afi']:
check = True
have_set = set()
new_hops = []
for each in route_want.get('next_hops'):
want_set = set()
new_dict_to_set(each, [], want_set, 0)
new_hops.append(want_set)
new_dict_to_set(addr_have, [], have_set, 0)
commands.extend(self._clear_config(w, h, addr_want, addr_have,
route_want, route_have))
commands.extend(self._set_config(w, h, addr_want,
route_want, route_have, new_hops, have_set))
del addr_want.get('routes')[count]
count += 1
if check:
break
if check:
break
if not check:
commands.extend(self._clear_config({}, h, {}, addr_have, {}, route_have))
# For configuring any non-existing want config
for w in temp_want:
for addr_want in w.get('address_families'):
for route_want in addr_want.get('routes'):
new_hops = []
for each in route_want.get('next_hops'):
want_set = set()
new_dict_to_set(each, [], want_set, 0)
new_hops.append(want_set)
commands.extend(self._set_config(w, {}, addr_want, route_want, {}, new_hops, set()))
# Arranging the cmds suct that all delete cmds are fired before all set cmds
commands = [each for each in sorted(commands) if 'no' in each] + \
[each for each in sorted(commands) if 'no' not in each]
return commands
def _state_merged(self, want, have):
""" The command generator when state is merged
:rtype: A list
:returns: the commands necessary to merge the provided into
the current configuration
"""
commands = []
# Drill each iteration of want n have and then based on dest and afi tyoe comparison take config call
for w in want:
for addr_want in w.get('address_families'):
for route_want in addr_want.get('routes'):
check = False
for h in have:
if h.get('address_families'):
for addr_have in h.get('address_families'):
for route_have in addr_have.get('routes'):
if route_want.get('dest') == route_have.get('dest')\
and addr_want['afi'] == addr_have['afi']:
check = True
have_set = set()
new_hops = []
for each in route_want.get('next_hops'):
want_set = set()
new_dict_to_set(each, [], want_set, 0)
new_hops.append(want_set)
new_dict_to_set(addr_have, [], have_set, 0)
commands.extend(self._set_config(w, h, addr_want,
route_want, route_have, new_hops, have_set))
if check:
break
if check:
break
if not check:
# For configuring any non-existing want config
new_hops = []
for each in route_want.get('next_hops'):
want_set = set()
new_dict_to_set(each, [], want_set, 0)
new_hops.append(want_set)
commands.extend(self._set_config(w, {}, addr_want, route_want, {}, new_hops, set()))
return commands
def _state_deleted(self, want, have):
""" The command generator when state is deleted
:rtype: A list
:returns: the commands necessary to remove the current configuration
of the provided objects
"""
commands = []
if want:
# Drill each iteration of want n have and then based on dest and afi type comparison fire delete config call
for w in want:
if w.get('address_families'):
for addr_want in w.get('address_families'):
for route_want in addr_want.get('routes'):
check = False
for h in have:
if h.get('address_families'):
for addr_have in h.get('address_families'):
for route_have in addr_have.get('routes'):
if route_want.get('dest') == route_have.get('dest') \
and addr_want['afi'] == addr_have['afi']:
check = True
if route_want.get('next_hops'):
commands.extend(self._clear_config({}, w, {}, addr_want, {}, route_want))
else:
commands.extend(self._clear_config({}, h, {}, addr_have, {}, route_have))
if check:
break
if check:
break
else:
for h in have:
for addr_have in h.get('address_families'):
for route_have in addr_have.get('routes'):
if w.get('vrf') == h.get('vrf'):
commands.extend(self._clear_config({}, h, {}, addr_have, {}, route_have))
else:
# Drill each iteration of have and then based on dest and afi type comparison fire delete config call
for h in have:
for addr_have in h.get('address_families'):
for route_have in addr_have.get('routes'):
commands.extend(self._clear_config({}, h, {}, addr_have, {}, route_have))
return commands
def prepare_config_commands(self, config_dict, cmd):
"""
function to parse the input dict and form the prepare the config commands
:rtype: A str
:returns: The command necessary to configure the static routes
"""
dhcp = config_dict.get('dhcp')
distance_metric = config_dict.get('distance_metric')
forward_router_address = config_dict.get('forward_router_address')
global_route_config = config_dict.get('global')
interface = config_dict.get('interface')
multicast = config_dict.get('multicast')
name = config_dict.get('name')
permanent = config_dict.get('permanent')
tag = config_dict.get('tag')
track = config_dict.get('track')
dest = config_dict.get('dest')
temp_dest = dest.split('/')
if temp_dest and ':' not in dest:
dest = validate_n_expand_ipv4(self._module, {'address': dest})
cmd = cmd + dest
if interface:
cmd = cmd + ' {0}'.format(interface)
if forward_router_address:
cmd = cmd + ' {0}'.format(forward_router_address)
if dhcp:
cmd = cmd + ' DHCP'
if distance_metric:
cmd = cmd + ' {0}'.format(distance_metric)
if global_route_config:
cmd = cmd + ' global'
if multicast:
cmd = cmd + ' multicast'
if name:
cmd = cmd + ' name {0}'.format(name)
if permanent:
cmd = cmd + ' permanent'
elif track:
cmd = cmd + ' track {0}'.format(track)
if tag:
cmd = cmd + ' tag {0}'.format(tag)
return cmd
def _set_config(self, want, have, addr_want, route_want, route_have, hops, have_set):
"""
Set the interface config based on the want and have config
:rtype: A list
:returns: The commands necessary to configure the static routes
"""
commands = []
cmd = None
vrf_diff = False
topology_diff = False
want_vrf = want.get('vrf')
have_vrf = have.get('vrf')
if want_vrf != have_vrf:
vrf_diff = True
want_topology = want.get('topology')
have_topology = have.get('topology')
if want_topology != have_topology:
topology_diff = True
have_dest = route_have.get('dest')
if have_dest:
have_set.add(tuple(iteritems({'dest': have_dest})))
# configure set cmd for each hops under the same destination
for each in hops:
diff = each - have_set
if vrf_diff:
each.add(tuple(iteritems({'vrf': want_vrf})))
if topology_diff:
each.add(tuple(iteritems({'topology': want_topology})))
if diff or vrf_diff or topology_diff:
if want_vrf and not vrf_diff:
each.add(tuple(iteritems({'vrf': want_vrf})))
if want_topology and not vrf_diff:
each.add(tuple(iteritems({'topology': want_topology})))
each.add(tuple(iteritems({'afi': addr_want.get('afi')})))
each.add(tuple(iteritems({'dest': route_want.get('dest')})))
temp_want = {}
for each_want in each:
temp_want.update(dict(each_want))
if temp_want.get('afi') == 'ipv4':
cmd = 'ip route '
vrf = temp_want.get('vrf')
if vrf:
cmd = cmd + 'vrf {0} '.format(vrf)
cmd = self.prepare_config_commands(temp_want, cmd)
elif temp_want.get('afi') == 'ipv6':
cmd = 'ipv6 route '
cmd = self.prepare_config_commands(temp_want, cmd)
commands.append(cmd)
return commands
def _clear_config(self, want, have, addr_want, addr_have, route_want, route_have):
"""
Delete the interface config based on the want and have config
:rtype: A list
:returns: The commands necessary to configure the static routes
"""
commands = []
cmd = None
vrf_diff = False
topology_diff = False
want_vrf = want.get('vrf')
have_vrf = have.get('vrf')
if want_vrf != have_vrf:
vrf_diff = True
want_topology = want.get('topology')
have_topology = have.get('topology')
if want_topology != have_topology:
topology_diff = True
want_set = set()
new_dict_to_set(addr_want, [], want_set, 0)
have_hops = []
for each in route_have.get('next_hops'):
temp_have_set = set()
new_dict_to_set(each, [], temp_have_set, 0)
have_hops.append(temp_have_set)
# configure delete cmd for each hops under the same destination
for each in have_hops:
diff = each - want_set
if vrf_diff:
each.add(tuple(iteritems({'vrf': have_vrf})))
if topology_diff:
each.add(tuple(iteritems({'topology': want_topology})))
if diff or vrf_diff or topology_diff:
if want_vrf and not vrf_diff:
each.add(tuple(iteritems({'vrf': want_vrf})))
if want_topology and not vrf_diff:
each.add(tuple(iteritems({'topology': want_topology})))
if addr_want:
each.add(tuple(iteritems({'afi': addr_want.get('afi')})))
else:
each.add(tuple(iteritems({'afi': addr_have.get('afi')})))
if route_want:
each.add(tuple(iteritems({'dest': route_want.get('dest')})))
else:
each.add(tuple(iteritems({'dest': route_have.get('dest')})))
temp_want = {}
for each_want in each:
temp_want.update(dict(each_want))
if temp_want.get('afi') == 'ipv4':
cmd = 'no ip route '
vrf = temp_want.get('vrf')
if vrf:
cmd = cmd + 'vrf {0} '.format(vrf)
cmd = self.prepare_config_commands(temp_want, cmd)
elif temp_want.get('afi') == 'ipv6':
cmd = 'no ipv6 route '
cmd = self.prepare_config_commands(temp_want, cmd)
commands.append(cmd)
return commands

@ -24,6 +24,7 @@ from ansible.module_utils.network.ios.facts.lldp_global.lldp_global import Lldp_
from ansible.module_utils.network.ios.facts.lldp_interfaces.lldp_interfaces import Lldp_InterfacesFacts from ansible.module_utils.network.ios.facts.lldp_interfaces.lldp_interfaces import Lldp_InterfacesFacts
from ansible.module_utils.network.ios.facts.l3_interfaces.l3_interfaces import L3_InterfacesFacts from ansible.module_utils.network.ios.facts.l3_interfaces.l3_interfaces import L3_InterfacesFacts
from ansible.module_utils.network.ios.facts.acl_interfaces.acl_interfaces import Acl_InterfacesFacts from ansible.module_utils.network.ios.facts.acl_interfaces.acl_interfaces import Acl_InterfacesFacts
from ansible.module_utils.network.ios.facts.static_routes.static_routes import Static_RoutesFacts
from ansible.module_utils.network.ios.facts.legacy.base import Default, Hardware, Interfaces, Config from ansible.module_utils.network.ios.facts.legacy.base import Default, Hardware, Interfaces, Config
@ -45,6 +46,7 @@ FACT_RESOURCE_SUBSETS = dict(
lldp_interfaces=Lldp_InterfacesFacts, lldp_interfaces=Lldp_InterfacesFacts,
l3_interfaces=L3_InterfacesFacts, l3_interfaces=L3_InterfacesFacts,
acl_interfaces=Acl_InterfacesFacts, acl_interfaces=Acl_InterfacesFacts,
static_routes=Static_RoutesFacts,
) )

@ -0,0 +1,225 @@
#
# -*- coding: utf-8 -*-
# Copyright 2019 Red Hat
# GNU General Public License v3.0+
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
"""
The ios_static_routes fact class
It is in this file the configuration is collected from the device
for a given resource, parsed, and the facts tree is populated
based on the configuration.
"""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
from copy import deepcopy
from ansible.module_utils.network.common import utils
from ansible.module_utils.network.ios.utils.utils import netmask_to_cidr
from ansible.module_utils.network.ios.argspec.static_routes.static_routes import Static_RoutesArgs
class Static_RoutesFacts(object):
""" The ios_static_routes fact class
"""
def __init__(self, module, subspec='config', options='options'):
self._module = module
self.argument_spec = Static_RoutesArgs.argument_spec
spec = deepcopy(self.argument_spec)
if subspec:
if options:
facts_argument_spec = spec[subspec][options]
else:
facts_argument_spec = spec[subspec]
else:
facts_argument_spec = spec
self.generated_spec = utils.generate_dict(facts_argument_spec)
def get_static_routes_data(self, connection):
return connection.get('sh running-config | include ip route|ipv6 route')
def populate_facts(self, connection, ansible_facts, data=None):
""" Populate the facts for static_routes
:param connection: the device connection
:param ansible_facts: Facts dictionary
:param data: previously collected conf
:rtype: dictionary
:returns: facts
"""
objs = []
if not data:
data = self.get_static_routes_data(connection)
# operate on a collection of resource x
config = data.split('\n')
same_dest = self.populate_destination(config)
for key in same_dest.keys():
if key:
obj = self.render_config(self.generated_spec, key, same_dest[key])
if obj:
objs.append(obj)
facts = {}
# append all static routes address_family with NO VRF together
no_vrf_address_family = {
'address_families': [each.get('address_families')[0] for each in objs if each.get('vrf') is None]
}
temp_objs = [each for each in objs if each.get('vrf') is not None]
temp_objs.append(no_vrf_address_family)
objs = temp_objs
if objs:
facts['static_routes'] = []
params = utils.validate_config(self.argument_spec, {'config': objs})
for cfg in params['config']:
facts['static_routes'].append(utils.remove_empties(cfg))
ansible_facts['ansible_network_resources'].update(facts)
return ansible_facts
def update_netmask_to_cidr(self, filter, pos, del_pos):
netmask = filter.split(' ')
dest = netmask[pos] + '/' + netmask_to_cidr(netmask[del_pos])
netmask[pos] = dest
del netmask[del_pos]
filter_vrf = ' '
return filter_vrf.join(netmask), dest
def populate_destination(self, config):
same_dest = {}
ip_str = ''
for i in sorted(config):
if i:
if '::' in i and 'vrf' in i:
ip_str = 'ipv6 route vrf'
elif '::' in i and 'vrf' not in i:
ip_str = 'ipv6 route'
elif '.' in i and 'vrf' in i:
ip_str = 'ip route vrf'
elif '.' in i and 'vrf' not in i:
ip_str = 'ip route'
if 'vrf' in i:
filter_vrf = utils.parse_conf_arg(i, ip_str)
if '/' not in filter_vrf and '::' not in filter_vrf:
filter_vrf, dest_vrf = self.update_netmask_to_cidr(filter_vrf, 1, 2)
dest_vrf = dest_vrf + '_vrf'
else:
dest_vrf = filter_vrf.split(' ')[1]
if dest_vrf not in same_dest.keys():
same_dest[dest_vrf] = []
same_dest[dest_vrf].append('vrf ' + filter_vrf)
elif 'vrf' not in same_dest[dest_vrf][0]:
same_dest[dest_vrf] = []
same_dest[dest_vrf].append('vrf ' + filter_vrf)
else:
same_dest[dest_vrf].append(('vrf ' + filter_vrf))
else:
filter = utils.parse_conf_arg(i, ip_str)
if '/' not in filter and '::' not in filter:
filter, dest = self.update_netmask_to_cidr(filter, 0, 1)
else:
dest = filter.split(' ')[0]
if dest not in same_dest.keys():
same_dest[dest] = []
same_dest[dest].append(filter)
elif 'vrf' in same_dest[dest][0]:
same_dest[dest] = []
same_dest[dest].append(filter)
else:
same_dest[dest].append(filter)
return same_dest
def render_config(self, spec, conf, conf_val):
"""
Render config as dictionary structure and delete keys
from spec for null values
:param spec: The facts tree, generated from the argspec
:param conf: The configuration
:rtype: dictionary
:returns: The generated config
"""
config = deepcopy(spec)
config['address_families'] = []
route_dict = dict()
final_route = dict()
afi = dict()
final_route['routes'] = []
next_hops = []
hops = {}
vrf = ''
address_family = dict()
for each in conf_val:
route = each.split(' ')
if 'vrf' in conf_val[0]:
vrf = route[route.index('vrf') + 1]
route_dict['dest'] = conf.split('_')[0]
else:
route_dict['dest'] = conf
if 'vrf' in conf_val[0]:
hops = {}
if '::' in conf:
hops['forward_router_address'] = route[3]
afi['afi'] = 'ipv6'
elif '.' in conf:
hops['forward_router_address'] = route[3]
afi['afi'] = "ipv4"
else:
hops['interface'] = conf
else:
if '::' in conf:
hops['forward_router_address'] = route[1]
afi['afi'] = 'ipv6'
elif '.' in conf:
hops['forward_router_address'] = route[1]
afi['afi'] = "ipv4"
else:
hops['interface'] = route[1]
try:
temp_list = each.split(' ')
if 'tag' in temp_list:
del temp_list[temp_list.index('tag') + 1]
if 'track' in temp_list:
del temp_list[temp_list.index('track') + 1]
# find distance metric
dist_metrics = int(
[i for i in temp_list if '.' not in i and ':' not in i and ord(i[0]) > 48 and ord(i[0]) < 57][0]
)
except IndexError:
dist_metrics = None
if dist_metrics:
hops['distance_metric'] = dist_metrics
if 'name' in route:
hops['name'] = route[route.index('name') + 1]
if 'multicast' in route:
hops['multicast'] = True
if 'dhcp' in route:
hops['dhcp'] = True
if 'global' in route:
hops['global'] = True
if 'permanent' in route:
hops['permanent'] = True
if 'tag' in route:
hops['tag'] = route[route.index('tag') + 1]
if 'track' in route:
hops['track'] = route[route.index('track') + 1]
next_hops.append(hops)
hops = {}
route_dict['next_hops'] = next_hops
if route_dict:
final_route['routes'].append(route_dict)
address_family.update(afi)
address_family.update(final_route)
config['address_families'].append(address_family)
if vrf:
config['vrf'] = vrf
return utils.remove_empties(config)

@ -28,6 +28,28 @@ def add_command_to_config_list(interface, cmd, commands):
commands.append(cmd) commands.append(cmd)
def new_dict_to_set(input_dict, temp_list, test_set, count):
test_dict = dict()
if isinstance(input_dict, dict):
input_dict_len = len(input_dict)
for k, v in sorted(iteritems(input_dict)):
count += 1
if isinstance(v, list):
temp_list.append(k)
for each in v:
if isinstance(each, dict):
if [True for i in each.values() if type(i) == list]:
new_dict_to_set(each, temp_list, test_set, count)
else:
new_dict_to_set(each, temp_list, test_set, 0)
else:
if v is not None:
test_dict.update({k: v})
if tuple(iteritems(test_dict)) not in test_set and count == input_dict_len:
test_set.add(tuple(iteritems(test_dict)))
count = 0
def dict_to_set(sample_dict): def dict_to_set(sample_dict):
# Generate a set with passed dictionary for comparison # Generate a set with passed dictionary for comparison
test_dict = dict() test_dict = dict()
@ -162,6 +184,31 @@ def validate_n_expand_ipv4(module, want):
return ip_addr_want return ip_addr_want
def netmask_to_cidr(netmask):
bit_range = [128, 64, 32, 16, 8, 4, 2, 1]
count = 0
cidr = 0
netmask_list = netmask.split('.')
netmask_calc = [i for i in netmask_list if int(i) != 255 and int(i) != 0]
if netmask_calc:
netmask_calc_index = netmask_list.index(netmask_calc[0])
elif sum(list(map(int, netmask_list))) == 0:
return '32'
else:
return '24'
for each in bit_range:
if cidr == int(netmask.split('.')[2]):
if netmask_calc_index == 1:
return str(8 + count)
elif netmask_calc_index == 2:
return str(8 * 2 + count)
elif netmask_calc_index == 3:
return str(8 * 3 + count)
break
cidr += each
count += 1
def normalize_interface(name): def normalize_interface(name):
"""Return the normalized interface name """Return the normalized interface name
""" """

@ -58,7 +58,7 @@ options:
a specific subset should not be collected. a specific subset should not be collected.
Valid subsets are 'all', 'interfaces', 'l2_interfaces', 'vlans', Valid subsets are 'all', 'interfaces', 'l2_interfaces', 'vlans',
'lag_interfaces', 'lacp', 'lacp_interfaces', 'lldp_global', 'lag_interfaces', 'lacp', 'lacp_interfaces', 'lldp_global',
'lldp_interfaces', 'l3_interfaces', 'acl_interfaces'. 'lldp_interfaces', 'l3_interfaces', 'acl_interfaces', 'static_routes'.
version_added: "2.9" version_added: "2.9"
""" """

@ -0,0 +1,710 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright 2019 Red Hat
# GNU General Public License v3.0+
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
#############################################
# WARNING #
#############################################
#
# This file is auto generated by the resource
# module builder playbook.
#
# Do not edit this file manually.
#
# Changes to this file will be over written
# by the resource module builder.
#
# Changes should be made in the model used to
# generate this file or in the resource module
# builder template.
#
#############################################
"""
The module file for ios_static_routes
"""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'network'}
DOCUMENTATION = """
---
module: ios_static_routes
version_added: "2.10"
short_description: Configure and manage static routes on IOS devices.
description: This module configures and manages the static routes on IOS platforms.
author: Sumit Jaiswal (@justjais)
notes:
- Tested against Cisco IOSv Version 15.2 on VIRL
- This module works with connection C(network_cli).
See L(IOS Platform Options,../network/user_guide/platform_ios.html).
options:
config:
description: A dictionary of static route options
type: list
elements: dict
suboptions:
vrf:
description:
- IP VPN Routing/Forwarding instance name.
- NOTE, In case of IPV4/IPV6 VRF routing table should pre-exist before
configuring.
- NOTE, if the vrf information is not provided then the routes shall be
configured under global vrf.
type: str
address_families:
elements: dict
description:
- Address family to use for the static routes
type: list
suboptions:
afi:
description:
- Top level address family indicator.
required: true
type: str
choices:
- ipv4
- ipv6
routes:
description: Configuring static route
type: list
elements: dict
suboptions:
dest:
description: Destination prefix with its subnet mask
type: str
required: true
topology:
description:
- Configure static route for a Topology Routing/Forwarding instance
- NOTE, VRF and Topology can be used together only with Multicast and
Topology should pre-exist before it can be used
type: str
next_hops:
description:
- next hop address or interface
type: list
elements: dict
suboptions:
forward_router_address:
description: Forwarding router's address
type: str
interface:
description: Interface for directly connected static routes
type: str
dhcp:
description: Default gateway obtained from DHCP
type: bool
distance_metric:
description: Distance metric for this route
type: int
global:
description: Next hop address is global
type: bool
name:
description: Specify name of the next hop
type: str
multicast:
description: multicast route
type: bool
permanent:
description: permanent route
type: bool
tag:
description:
- Set tag for this route
- Refer to vendor documentation for valid values.
type: int
track:
description:
- Install route depending on tracked item with tracked object number.
- Tracking does not support multicast
- Refer to vendor documentation for valid values.
type: int
running_config:
description:
- The module, by default, will connect to the remote device and
retrieve the current running-config to use as a base for comparing
against the contents of source. There are times when it is not
desirable to have the task get the current running-config for
every task in a playbook. The I(running_config) argument allows the
implementer to pass in the configuration to use as the base
config for comparison. This value of this option should be the
output received from device by executing command
C(show configuration commands | grep 'static route')
type: str
state:
description:
- The state the configuration should be left in
- The states I(rendered), I(gathered) and I(parsed) does not perform any change on the
device.
- The state I(rendered) will transform the configuration in C(config) option to platform
specific CLI commands which will be returned in the I(rendered) key within the result.
For state I(rendered) active connection to remote host is not required.
- The state I(gathered) will fetch the running configuration from device and transform
it into structured data in the format as per the resource module argspec and the
value is returned in the I(gathered) key within the result.
- The state I(parsed) reads the configuration from C(running_config) option and transforms
it into JSON format as per the resource module parameters and the value is returned in
the I(parsed) key within the result. The value of C(running_config) option should be the
same format as the output of command I(show running-config | include ip route|ipv6 route)
executed on device. For state I(parsed) active connection to remote host is not required.
type: str
choices:
- merged
- replaced
- overridden
- deleted
- gathered
- rendered
- parsed
default: merged
"""
EXAMPLES = """
---
# Using merged
# Before state:
# -------------
#
# vios#show running-config | include ip route|ipv6 route
- name: Merge provided configuration with device configuration
ios_static_routes:
config:
- vrf: blue
address_families:
- afi: ipv4
routes:
- dest: 192.0.2.0/24
next_hops:
- forward_router_address: 192.0.2.1
name: merged_blue
tag: 50
track: 150
- address_families:
- afi: ipv4
routes:
- dest: 198.51.100.0/24
next_hops:
- forward_router_address: 198.51.101.1
name: merged_route_1
distance_metric: 110
tag: 40
multicast: True
- forward_router_address: 198.51.101.2
name: merged_route_2
distance_metric: 30
- forward_router_address: 198.51.101.3
name: merged_route_3
- afi: ipv6
routes:
- dest: 2001:DB8:0:3::/64
next_hops:
- forward_router_address: 2001:DB8:0:3::2
name: merged_v6
tag: 105
state: merged
# Commands fired:
# ---------------
# ip route vrf blue 192.0.2.0 255.255.255.0 10.0.0.8 name merged_blue track 150 tag 50
# ip route 198.51.100.0 255.255.255.0 198.51.101.1 110 multicast name merged_route_1 tag 40
# ip route 198.51.100.0 255.255.255.0 198.51.101.2 30 name merged_route_2
# ip route 198.51.100.0 255.255.255.0 198.51.101.3 name merged_route_3
# ipv6 route 2001:DB8:0:3::/64 2001:DB8:0:3::2 name merged_v6 tag 105
# After state:
# ------------
#
# vios#show running-config | include ip route|ipv6 route
# ip route vrf blue 192.0.2.0 255.255.255.0 192.0.2.1 tag 50 name merged_blue track 150
# ip route 198.51.100.0 255.255.255.0 198.51.101.3 name merged_route_3
# ip route 198.51.100.0 255.255.255.0 198.51.101.2 30 name merged_route_2
# ip route 198.51.100.0 255.255.255.0 198.51.101.1 110 tag 40 name merged_route_1 multicast
# ipv6 route 2001:DB8:0:3::/64 2001:DB8:0:3::2 tag 105 name merged_v6
# Using replaced
# Before state:
# -------------
#
# vios#show running-config | include ip route|ipv6 route
# ip route vrf ansible_temp_vrf 192.0.2.0 255.255.255.0 192.0.2.1 name test_vrf track 150 tag 50
# ip route 198.51.100.0 255.255.255.0 198.51.101.1 110 multicast name route_1 tag 40
# ip route 198.51.100.0 255.255.255.0 198.51.101.2 30 name route_2
# ip route 198.51.100.0 255.255.255.0 198.51.101.3 name route_3
# ipv6 route 2001:DB8:0:3::/64 2001:DB8:0:3::2 name test_v6 tag 105
- name: Replace provided configuration with device configuration
ios_static_routes:
config:
- address_families:
- afi: ipv4
routes:
- dest: 198.51.100.0/24
next_hops:
- forward_router_address: 198.51.101.1
name: replaced_route
distance_metric: 175
tag: 70
multicast: True
state: replaced
# Commands fired:
# ---------------
# no ip route 198.51.100.0 255.255.255.0 198.51.101.1 110 multicast name route_1 tag 40
# no ip route 198.51.100.0 255.255.255.0 198.51.101.2 30 name route_2
# no ip route 198.51.100.0 255.255.255.0 198.51.101.3 name route_3
# ip route 198.51.100.0 255.255.255.0 198.51.101.1 175 name replaced_route track 150 tag 70
# After state:
# ------------
#
# vios#show running-config | include ip route|ipv6 route
# ip route vrf ansible_temp_vrf 192.0.2.0 255.255.255.0 192.0.2.1 name test_vrf track 150 tag 50
# ip route 198.51.100.0 255.255.255.0 198.51.101.1 175 name replaced_route track 150 tag 70
# ipv6 route 2001:DB8:0:3::/64 2001:DB8:0:3::2 tag 105 name test_v6
# Using overridden
# Before state:
# -------------
#
# vios#show running-config | include ip route|ipv6 route
# ip route vrf ansible_temp_vrf 192.0.2.0 255.255.255.0 192.0.2.1 name test_vrf track 150 tag 50
# ip route 198.51.100.0 255.255.255.0 198.51.101.1 110 multicast name route_1 tag 40
# ip route 198.51.100.0 255.255.255.0 198.51.101.2 30 name route_2
# ip route 198.51.100.0 255.255.255.0 198.51.101.3 name route_3
# ipv6 route 2001:DB8:0:3::/64 2001:DB8:0:3::2 name test_v6 tag 105
- name: Override provided configuration with device configuration
ios_static_routes:
config:
- vrf: blue
address_families:
- afi: ipv4
routes:
- dest: 192.0.2.0/24
next_hops:
- forward_router_address: 192.0.2.1
name: override_vrf
tag: 50
track: 150
state: overridden
# Commands fired:
# ---------------
# no ip route 198.51.100.0 255.255.255.0 198.51.101.1 110 multicast name route_1 tag 40
# no ip route 198.51.100.0 255.255.255.0 198.51.101.2 30 name route_2
# no ip route 198.51.100.0 255.255.255.0 198.51.101.3 name route_3
# no ip route vrf ansible_temp_vrf 192.0.2.0 255.255.255.0 198.51.101.8 name test_vrf track 150 tag 50
# no ipv6 route FD5D:12C9:2201:1::/64 FD5D:12C9:2202::2 name test_v6 tag 105
# ip route vrf blue 192.0.2.0 255.255.255.0 198.51.101.4 name override_vrf track 150 tag 50
# After state:
# ------------
#
# vios#show running-config | include ip route|ipv6 route
# ip route vrf blue 192.0.2.0 255.255.255.0 192.0.2.1 tag 50 name override_vrf track 150
# Using Deleted
# Example 1:
# ----------
# To delete the exact static routes, with all the static routes explicitly mentioned in want
# Before state:
# -------------
#
# vios#show running-config | include ip route|ipv6 route
# ip route vrf ansible_temp_vrf 192.0.2.0 255.255.255.0 192.0.2.1 name test_vrf track 150 tag 50
# ip route 198.51.100.0 255.255.255.0 198.51.101.1 110 multicast name route_1 tag 40
# ip route 198.51.100.0 255.255.255.0 198.51.101.2 30 name route_2
# ip route 198.51.100.0 255.255.255.0 198.51.101.3 name route_3
# ipv6 route 2001:DB8:0:3::/64 2001:DB8:0:3::2 name test_v6 tag 105
- name: Delete provided configuration from the device configuration
ios_static_routes:
config:
- vrf: ansible_temp_vrf
address_families:
- afi: ipv4
routes:
- dest: 192.0.2.0/24
next_hops:
- forward_router_address: 192.0.2.1
name: test_vrf
tag: 50
track: 150
- address_families:
- afi: ipv4
routes:
- dest: 198.51.100.0/24
next_hops:
- forward_router_address: 198.51.101.1
name: route_1
distance_metric: 110
tag: 40
multicast: True
- forward_router_address: 198.51.101.2
name: route_2
distance_metric: 30
- forward_router_address: 198.51.101.3
name: route_3
- afi: ipv6
routes:
- dest: 2001:DB8:0:3::/64
next_hops:
- forward_router_address: 2001:DB8:0:3::2
name: test_v6
tag: 105
state: deleted
# Commands fired:
# ---------------
# no ip route vrf ansible_temp_vrf 192.0.2.0 255.255.255.0 198.51.101.8 name test_vrf track 150 tag 50
# no ip route 198.51.100.0 255.255.255.0 198.51.101.1 110 multicast name route_1 tag 40
# no ip route 198.51.100.0 255.255.255.0 198.51.101.2 30 name route_2
# no ip route 198.51.100.0 255.255.255.0 198.51.101.3 name route_3
# no ipv6 route FD5D:12C9:2201:1::/64 FD5D:12C9:2202::2 name test_v6 tag 105
# After state:
# ------------
#
# vios#show running-config | include ip route|ipv6 route
# Example 2:
# ----------
# To delete the destination specific static routes
# Before state:
# -------------
#
# vios#show running-config | include ip route|ipv6 route
# ip route vrf ansible_temp_vrf 192.0.2.0 255.255.255.0 192.0.2.1 name test_vrf track 150 tag 50
# ip route 198.51.100.0 255.255.255.0 198.51.101.1 110 multicast name route_1 tag 40
# ip route 198.51.100.0 255.255.255.0 198.51.101.2 30 name route_2
# ip route 198.51.100.0 255.255.255.0 198.51.101.3 name route_3
# ipv6 route 2001:DB8:0:3::/64 2001:DB8:0:3::2 name test_v6 tag 105
- name: Delete provided configuration from the device configuration
ios_static_routes:
config:
- address_families:
- afi: ipv4
routes:
- dest: 198.51.100.0/24
state: deleted
# Commands fired:
# ---------------
# no ip route 198.51.100.0 255.255.255.0 198.51.101.3 name route_3
# no ip route 198.51.100.0 255.255.255.0 198.51.101.2 30 name route_2
# no ip route 198.51.100.0 255.255.255.0 198.51.101.1 110 tag 40 name route_1 multicast
# After state:
# ------------
#
# vios#show running-config | include ip route|ipv6 route
# ip route vrf ansible_temp_vrf 192.0.2.0 255.255.255.0 192.0.2.1 tag 50 name test_vrf track 150
# ipv6 route 2001:DB8:0:3::/64 2001:DB8:0:3::2 tag 105 name test_v6
# Example 3:
# ----------
# To delete the vrf specific static routes
# Before state:
# -------------
#
# vios#show running-config | include ip route|ipv6 route
# ip route vrf ansible_temp_vrf 192.0.2.0 255.255.255.0 192.0.2.1 name test_vrf track 150 tag 50
# ip route 198.51.100.0 255.255.255.0 198.51.101.1 110 multicast name route_1 tag 40
# ip route 198.51.100.0 255.255.255.0 198.51.101.2 30 name route_2
# ip route 198.51.100.0 255.255.255.0 198.51.101.3 name route_3
# ipv6 route 2001:DB8:0:3::/64 2001:DB8:0:3::2 name test_v6 tag 105
- name: Delete provided configuration from the device configuration
ios_static_routes:
config:
- vrf: ansible_temp_vrf
state: deleted
# Commands fired:
# ---------------
# no ip route vrf ansible_temp_vrf 192.0.2.0 255.255.255.0 192.0.2.1 name test_vrf track 150 tag 50
# After state:
# ------------
#
# vios#show running-config | include ip route|ipv6 route
# ip route 198.51.100.0 255.255.255.0 198.51.101.3 name route_3
# ip route 198.51.100.0 255.255.255.0 198.51.101.2 30 name route_2
# ip route 198.51.100.0 255.255.255.0 198.51.101.1 110 tag 40 name route_1 multicast
# ipv6 route 2001:DB8:0:3::/64 2001:DB8:0:3::2 tag 105 name test_v6
# Using Deleted without any config passed
#"(NOTE: This will delete all of configured resource module attributes from each configured interface)"
# Before state:
# -------------
#
# vios#show running-config | include ip route|ipv6 route
# ip route vrf ansible_temp_vrf 192.0.2.0 255.255.255.0 192.0.2.1 name test_vrf track 150 tag 50
# ip route 198.51.100.0 255.255.255.0 198.51.101.1 110 multicast name route_1 tag 40
# ip route 198.51.100.0 255.255.255.0 198.51.101.2 30 name route_2
# ip route 198.51.100.0 255.255.255.0 198.51.101.3 name route_3
# ipv6 route 2001:DB8:0:3::/64 2001:DB8:0:3::2 name test_v6 tag 105
- name: Delete ALL configured IOS static routes
ios_static_routes:
state: deleted
# Commands fired:
# ---------------
# no ip route vrf ansible_temp_vrf 192.0.2.0 255.255.255.0 192.0.2.1 tag 50 name test_vrf track 150
# no ip route 198.51.100.0 255.255.255.0 198.51.101.3 name route_3
# no ip route 198.51.100.0 255.255.255.0 198.51.101.2 30 name route_2
# no ip route 198.51.100.0 255.255.255.0 198.51.101.1 110 tag 40 name route_1 multicast
# no ipv6 route 2001:DB8:0:3::/64 2001:DB8:0:3::2 tag 105 name test_v6
# After state:
# -------------
#
# vios#show running-config | include ip route|ipv6 route
#
# Using gathered
# Before state:
# -------------
#
# vios#show running-config | include ip route|ipv6 route
# ip route vrf ansible_temp_vrf 192.0.2.0 255.255.255.0 192.0.2.1 name test_vrf track 150 tag 50
# ip route 198.51.100.0 255.255.255.0 198.51.101.1 110 multicast name route_1 tag 40
# ip route 198.51.100.0 255.255.255.0 198.51.101.2 30 name route_2
# ip route 198.51.100.0 255.255.255.0 198.51.101.3 name route_3
# ipv6 route 2001:DB8:0:3::/64 2001:DB8:0:3::2 name test_v6 tag 105
- name: Gather listed static routes with provided configurations
ios_static_routes:
config:
state: gathered
# Module Execution Result:
# ------------------------
#
# "gathered": [
# {
# "address_families": [
# {
# "afi": "ipv4",
# "routes": [
# {
# "dest": "192.0.2.0/24",
# "next_hops": [
# {
# "forward_router_address": "192.0.2.1",
# "name": "test_vrf",
# "tag": 50,
# "track": 150
# }
# ]
# }
# ]
# }
# ],
# "vrf": "ansible_temp_vrf"
# },
# {
# "address_families": [
# {
# "afi": "ipv6",
# "routes": [
# {
# "dest": "2001:DB8:0:3::/64",
# "next_hops": [
# {
# "forward_router_address": "2001:DB8:0:3::2",
# "name": "test_v6",
# "tag": 105
# }
# ]
# }
# ]
# },
# {
# "afi": "ipv4",
# "routes": [
# {
# "dest": "198.51.100.0/24",
# "next_hops": [
# {
# "distance_metric": 110,
# "forward_router_address": "198.51.101.1",
# "multicast": true,
# "name": "route_1",
# "tag": 40
# },
# {
# "distance_metric": 30,
# "forward_router_address": "198.51.101.2",
# "name": "route_2"
# },
# {
# "forward_router_address": "198.51.101.3",
# "name": "route_3"
# }
# ]
# }
# ]
# }
# ]
# }
# ]
# After state:
# ------------
#
# vios#show running-config | include ip route|ipv6 route
# ip route vrf ansible_temp_vrf 192.0.2.0 255.255.255.0 192.0.2.1 name test_vrf track 150 tag 50
# ip route 198.51.100.0 255.255.255.0 198.51.101.1 110 multicast name route_1 tag 40
# ip route 198.51.100.0 255.255.255.0 198.51.101.2 30 name route_2
# ip route 198.51.100.0 255.255.255.0 198.51.101.3 name route_3
# ipv6 route 2001:DB8:0:3::/64 2001:DB8:0:3::2 name test_v6 tag 105
# Using rendered
- name: Render the commands for provided configuration
ios_static_routes:
config:
- vrf: ansible_temp_vrf
address_families:
- afi: ipv4
routes:
- dest: 192.0.2.0/24
next_hops:
- forward_router_address: 192.0.2.1
name: test_vrf
tag: 50
track: 150
- address_families:
- afi: ipv4
routes:
- dest: 198.51.100.0/24
next_hops:
- forward_router_address: 198.51.101.1
name: route_1
distance_metric: 110
tag: 40
multicast: True
- forward_router_address: 198.51.101.2
name: route_2
distance_metric: 30
- forward_router_address: 198.51.101.3
name: route_3
- afi: ipv6
routes:
- dest: 2001:DB8:0:3::/64
next_hops:
- forward_router_address: 2001:DB8:0:3::2
name: test_v6
tag: 105
state: rendered
# Module Execution Result:
# ------------------------
#
# "rendered": [
# "ip route vrf ansible_temp_vrf 192.0.2.0 255.255.255.0 192.0.2.1 name test_vrf track 150 tag 50",
# "ip route 198.51.100.0 255.255.255.0 198.51.101.1 110 multicast name route_1 tag 40",
# "ip route 198.51.100.0 255.255.255.0 198.51.101.2 30 name route_2",
# "ip route 198.51.100.0 255.255.255.0 198.51.101.3 name route_3",
# "ipv6 route 2001:DB8:0:3::/64 2001:DB8:0:3::2 name test_v6 tag 105"
# ]
"""
RETURN = """
before:
description: The configuration as structured data prior to module invocation.
returned: always
type: list
sample: The configuration returned will always be in the same format of the parameters above.
after:
description: The configuration as structured data after module completion.
returned: when changed
type: list
sample: The configuration returned will always be in the same format of the parameters above.
commands:
description: The set of commands pushed to the remote device
returned: always
type: list
sample: ['ip route vrf test 172.31.10.0 255.255.255.0 10.10.10.2 name new_test multicast']
rendered:
description: The set of CLI commands generated from the value in C(config) option
returned: When C(state) is I(rendered)
type: list
sample: ['interface Ethernet1/1', 'mtu 1800']
gathered:
description:
- The configuration as structured data transformed for the running configuration
fetched from remote host
returned: When C(state) is I(gathered)
type: list
sample: >
The configuration returned will always be in the same format
of the parameters above.
parsed:
description:
- The configuration as structured data transformed for the value of
C(running_config) option
returned: When C(state) is I(parsed)
type: list
sample: >
The configuration returned will always be in the same format
of the parameters above.
"""
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.network.ios.argspec.static_routes.static_routes import Static_RoutesArgs
from ansible.module_utils.network.ios.config.static_routes.static_routes import Static_Routes
def main():
"""
Main entry point for module execution
:returns: the result form module invocation
"""
required_if = [('state', 'merged', ('config',)),
('state', 'replaced', ('config',)),
('state', 'overridden', ('config',)),
('state', 'rendered', ('config',)),
('state', 'parsed', ('running_config',))]
mutually_exclusive = [('config', 'running_config')]
module = AnsibleModule(argument_spec=Static_RoutesArgs.argument_spec,
required_if=required_if,
supports_check_mode=True,
mutually_exclusive=mutually_exclusive)
result = Static_Routes(module).execute_module()
module.exit_json(**result)
if __name__ == '__main__':
main()

@ -0,0 +1,3 @@
---
testcase: "[^_].*"
test_items: []

@ -0,0 +1,20 @@
---
- name: Collect all cli test cases
find:
paths: "{{ role_path }}/tests/cli"
patterns: "{{ testcase }}.yaml"
use_regex: true
register: test_cases
delegate_to: localhost
- name: Set test_items
set_fact: test_items="{{ test_cases.files | map(attribute='path') | list }}"
delegate_to: localhost
- name: Run test case (connection=network_cli)
include: "{{ test_case_to_run }}"
vars:
ansible_connection: network_cli
with_items: "{{ test_items }}"
loop_control:
loop_var: test_case_to_run

@ -0,0 +1,2 @@
---
- { include: cli.yaml, tags: ['cli'] }

@ -0,0 +1,13 @@
---
- name: Intitial Setup Config
cli_config:
config: "{{ lines }}"
vars:
lines: |
vrf definition ansible_temp_vrf
address-family ipv4
ip route vrf ansible_temp_vrf 192.0.2.0 255.255.255.0 192.0.2.1 tag 50 name test_vrf track 150
ip route 198.51.100.0 255.255.255.0 198.51.101.3 name route_3
ip route 198.51.100.0 255.255.255.0 198.51.101.2 30 name route_2
ip route 198.51.100.0 255.255.255.0 198.51.101.1 110 tag 40 name route_1 multicast
ipv6 route 2001:DB8:0:3::/64 2001:DB8:0:3::2 tag 105 name test_v6

@ -0,0 +1,11 @@
---
- name: Populate Config
cli_config:
config: "{{ lines }}"
vars:
lines: |
ip route vrf ansible_temp_vrf 192.0.2.0 255.255.255.0 192.0.2.1 tag 50 name test_vrf track 150
ip route 198.51.100.0 255.255.255.0 198.51.101.3 name route_3
ip route 198.51.100.0 255.255.255.0 198.51.101.2 30 name route_2
ip route 198.51.100.0 255.255.255.0 198.51.101.1 110 tag 40 name route_1 multicast
ipv6 route 2001:DB8:0:3::/64 2001:DB8:0:3::2 tag 105 name test_v6

@ -0,0 +1,12 @@
---
- name: Remove Config
cli_config:
config: "{{ lines }}"
vars:
lines: |
no ip route vrf ansible_temp_vrf 192.0.2.0 255.255.255.0 192.0.2.1 tag 50 name test_vrf track 150
no ip route 198.51.100.0 255.255.255.0 198.51.101.3 name route_3
no ip route 198.51.100.0 255.255.255.0 198.51.101.2 30 name route_2
no ip route 198.51.100.0 255.255.255.0 198.51.101.1 110 tag 40 name route_1 multicast
no ipv6 route 2001:DB8:0:3::/64 2001:DB8:0:3::2 tag 105 name test_v6
no vrf definition ansible_temp_vrf

@ -0,0 +1,66 @@
---
- debug:
msg: "Start Deleted integration state for ios_static_routes ansible_connection={{ ansible_connection }}"
- include_tasks: _remove_config.yaml
- include_tasks: _intial_setup_config.yaml
- block:
- name: Delete attributes of provided configured interfaces
ios_static_routes: &deleted
config:
- vrf: ansible_temp_vrf
address_families:
- afi: ipv4
routes:
- dest: 192.0.2.0/24
next_hops:
- forward_router_address: 192.0.2.1
name: test_vrf
tag: 50
track: 150
- address_families:
- afi: ipv4
routes:
- dest: 198.51.100.0/24
next_hops:
- forward_router_address: 198.51.101.1
name: route_1
distance_metric: 110
tag: 40
multicast: True
- forward_router_address: 198.51.101.2
name: route_2
distance_metric: 30
- forward_router_address: 198.51.101.3
name: route_3
- afi: ipv6
routes:
- dest: 2001:DB8:0:3::/64
next_hops:
- forward_router_address: 2001:DB8:0:3::2
name: test_v6
tag: 105
state: deleted
register: result
- assert:
that:
- "result.commands|length == 5"
- "result.changed == true"
- "result.commands|symmetric_difference(deleted.commands) == []"
- name: Delete attributes of all configured interfaces (IDEMPOTENT)
ios_static_routes: *deleted
register: result
- name: Assert that the previous task was idempotent
assert:
that:
- "result.commands|length == 0"
- "result.changed == false"
always:
- include_tasks: _populate_config.yaml
- include_tasks: _remove_config.yaml

@ -0,0 +1,58 @@
---
- debug:
msg: "START ios_static_routes empty_config.yaml integration tests on connection={{ ansible_connection }}"
- name: Merged with empty config should give appropriate error message
ios_static_routes:
config:
state: merged
register: result
ignore_errors: True
- assert:
that:
- result.msg == 'value of config parameter must not be empty for state merged'
- name: Replaced with empty config should give appropriate error message
ios_static_routes:
config:
state: replaced
register: result
ignore_errors: True
- assert:
that:
- result.msg == 'value of config parameter must not be empty for state replaced'
- name: Overridden with empty config should give appropriate error message
ios_static_routes:
config:
state: overridden
register: result
ignore_errors: True
- assert:
that:
- result.msg == 'value of config parameter must not be empty for state overridden'
- name: Rendered with empty config should give appropriate error message
ios_static_routes:
config:
state: rendered
register: result
ignore_errors: True
- assert:
that:
- result.msg == 'value of config parameter must not be empty for state rendered'
- name: Parsed with empty config should give appropriate error message
ios_static_routes:
running_config:
state: parsed
register: result
ignore_errors: True
- assert:
that:
- result.msg == 'value of running_config parameter must not be empty for state parsed'

@ -0,0 +1,22 @@
---
- debug:
msg: "START ios_static_routes gathered integration tests on connection={{ ansible_connection }}"
- include_tasks: _remove_config.yaml
- include_tasks: _intial_setup_config.yaml
- block:
- name: Gather the provided configuration with the exisiting running configuration
ios_static_routes: &gathered
config:
state: gathered
register: result
- name: Assert that gathered dicts was correctly generated
assert:
that:
- "result['changed'] == false"
always:
- include_tasks: _remove_config.yaml

@ -0,0 +1,66 @@
---
- debug:
msg: "START Merged ios_static_routes state for integration tests on connection={{ ansible_connection }}"
- include_tasks: _remove_config.yaml
- include_tasks: _intial_setup_config.yaml
- block:
- name: Merge provided configuration with device configuration
ios_static_routes: &merged
config:
- vrf: ansible_temp_vrf
address_families:
- afi: ipv4
routes:
- dest: 192.0.2.0/24
next_hops:
- forward_router_address: 192.0.2.1
name: merged_vrf
tag: 50
track: 150
- address_families:
- afi: ipv4
routes:
- dest: 198.51.100.0/24
next_hops:
- forward_router_address: 198.51.101.1
name: merged_route_1
distance_metric: 110
tag: 40
multicast: True
- forward_router_address: 198.51.101.2
name: merged_route_2
distance_metric: 30
- forward_router_address: 198.51.101.3
name: merged_route_3
- afi: ipv6
routes:
- dest: 2001:DB8:0:3::/64
next_hops:
- forward_router_address: 2001:DB8:0:3::2
name: merged_v6
tag: 105
state: merged
register: result
- assert:
that:
- "result.commands|length == 5"
- "result.changed == true"
- "result.commands|symmetric_difference(merged.commands) == []"
- name: Merge provided configuration with device configuration (IDEMPOTENT)
ios_static_routes: *merged
register: result
- name: Assert that the previous task was idempotent
assert:
that:
- "result.commands|length == 0"
- "result['changed'] == false"
always:
- include_tasks: _populate_config.yaml
- include_tasks: _remove_config.yaml

@ -0,0 +1,58 @@
---
- debug:
msg: "START Overridden ios_static_routes state for integration tests on connection={{ ansible_connection }}"
- include_tasks: _remove_config.yaml
- include_tasks: _intial_setup_config.yaml
- block:
- name: Override device configuration of all interfaces with provided configuration
ios_static_routes: &overridden
config:
- vrf: ansible_temp_vrf
address_families:
- afi: ipv4
routes:
- dest: 192.0.2.0/24
next_hops:
- forward_router_address: 192.0.2.1
name: override_vrf
tag: 50
track: 150
- address_families:
- afi: ipv4
routes:
- dest: 198.51.100.0/24
next_hops:
- forward_router_address: 198.51.101.3
name: override_route
- afi: ipv6
routes:
- dest: 2001:DB8:0:3::/64
next_hops:
- forward_router_address: 2001:DB8:0:3::2
name: override_v6
tag: 175
state: overridden
register: result
- assert:
that:
- "result.commands|length == 8"
- "result.changed == true"
- "result.commands|symmetric_difference(overridden.commands) == []"
- name: Override device configuration of all interfaces with provided configuration (IDEMPOTENT)
ios_static_routes: *overridden
register: result
- name: Assert that task was idempotent
assert:
that:
- "result.commands|length == 0"
- "result['changed'] == false"
always:
- include_tasks: _populate_config.yaml
- include_tasks: _remove_config.yaml

@ -0,0 +1,54 @@
---
- debug:
msg: "START ios_static_routes rendered integration tests on connection={{ ansible_connection }}"
- include_tasks: _remove_config.yaml
- include_tasks: _intial_setup_config.yaml
- block:
- name: Rendered the provided configuration with the exisiting running configuration
ios_static_routes: &rendered
config:
- vrf: ansible_temp_vrf
address_families:
- afi: ipv4
routes:
- dest: 192.0.2.0/24
next_hops:
- forward_router_address: 192.0.2.1
name: test_vrf
tag: 50
track: 150
- address_families:
- afi: ipv4
routes:
- dest: 198.51.100.0/24
next_hops:
- forward_router_address: 198.51.101.1
name: route_1
distance_metric: 110
tag: 40
multicast: True
- forward_router_address: 198.51.101.2
name: route_2
distance_metric: 30
- forward_router_address: 198.51.101.3
name: route_3
- afi: ipv6
routes:
- dest: 2001:DB8:0:3::/64
next_hops:
- forward_router_address: 2001:DB8:0:3::2
name: test_v6
tag: 105
state: rendered
register: result
- assert:
that:
- "result.changed == false"
- "result.rendered|symmetric_difference(rendered.commands) == []"
always:
- include_tasks: _remove_config.yaml

@ -0,0 +1,61 @@
---
- debug:
msg: "START Replaced ios_static_routes state for integration tests on connection={{ ansible_connection }}"
- include_tasks: _remove_config.yaml
- include_tasks: _intial_setup_config.yaml
- block:
- name: Replaces device configuration of listed interfaces with provided configuration
ios_static_routes: &replaced
config:
- vrf: ansible_temp_vrf
address_families:
- afi: ipv4
routes:
- dest: 192.0.2.0/24
next_hops:
- forward_router_address: 192.0.2.1
name: replaced_vrf
tag: 75
track: 155
- address_families:
- afi: ipv4
routes:
- dest: 198.51.100.0/24
next_hops:
- forward_router_address: 198.51.101.1
name: replaced_route
distance_metric: 175
tag: 70
multicast: True
- afi: ipv6
routes:
- dest: 2001:DB8:0:3::/64
next_hops:
- forward_router_address: 2001:DB8:0:3::2
name: replaced_v6
tag: 110
state: replaced
register: result
- assert:
that:
- "result.commands|length == 7"
- "result.changed == true"
- "result.commands|symmetric_difference(replaced.commands) == []"
- name: Replaces device configuration of listed interfaces with provided configuration (IDEMPOTENT)
ios_static_routes: *replaced
register: result
- name: Assert that task was idempotent
assert:
that:
- "result.commands|length == 0"
- "result['changed'] == false"
always:
- include_tasks: _populate_config.yaml
- include_tasks: _remove_config.yaml

@ -0,0 +1,78 @@
---
- debug:
msg: "START ios_static_routes round trip integration tests on connection={{ ansible_connection }}"
- include_tasks: _remove_config.yaml
- block:
- name: Apply the provided configuration (base config)
ios_static_routes:
config:
- vrf: ansible_temp_vrf
address_families:
- afi: ipv4
routes:
- dest: 192.0.2.0/24
next_hops:
- forward_router_address: 192.0.2.1
name: rtt_vrf
tag: 50
track: 150
- address_families:
- afi: ipv4
routes:
- dest: 198.51.100.0/24
next_hops:
- forward_router_address: 198.51.101.1
name: rtt_route_1
distance_metric: 110
tag: 40
multicast: True
state: merged
register: base_config
- name: Gather static routes facts
ios_facts:
gather_subset:
- "!all"
- "!min"
gather_network_resources:
- static_routes
- name: Apply the configuration which need to be reverted
ios_static_routes:
config:
- vrf: ansible_temp_vrf
address_families:
- afi: ipv4
routes:
- dest: 192.0.2.0/24
next_hops:
- forward_router_address: 192.0.2.12
name: new_rtt_vrf
tag: 10
track: 150
state: overridden
register: result
- assert:
that:
- "result.commands|length == 2"
- "result.changed == true"
- "result.commands|symmetric_difference(rtt.override_commands) == []"
- name: Revert back to base config using facts round trip
ios_static_routes:
config: "{{ ansible_facts['network_resources']['static_routes'] }}"
state: overridden
register: revert
- assert:
that:
- "revert.commands|length == 1"
- "revert.changed == true"
- "revert.commands|symmetric_difference(rtt.override_revert_commands) == []"
always:
- include_tasks: _populate_config.yaml
- include_tasks: _remove_config.yaml

@ -0,0 +1,88 @@
---
deleted:
commands:
- "no ip route vrf ansible_temp_vrf 192.0.2.0 255.255.255.0 192.0.2.1 name test_vrf track 150 tag 50"
- "no ip route 198.51.100.0 255.255.255.0 198.51.101.1 110 multicast name route_1 tag 40"
- "no ip route 198.51.100.0 255.255.255.0 198.51.101.2 30 name route_2"
- "no ip route 198.51.100.0 255.255.255.0 198.51.101.3 name route_3"
- "no ipv6 route 2001:DB8:0:3::/64 2001:DB8:0:3::2 name test_v6 tag 105"
merged:
commands:
- "ip route vrf ansible_temp_vrf 192.0.2.0 255.255.255.0 192.0.2.1 name merged_vrf track 150 tag 50"
- "ip route 198.51.100.0 255.255.255.0 198.51.101.1 110 multicast name merged_route_1 tag 40"
- "ip route 198.51.100.0 255.255.255.0 198.51.101.2 30 name merged_route_2"
- "ip route 198.51.100.0 255.255.255.0 198.51.101.3 name merged_route_3"
- "ipv6 route 2001:DB8:0:3::/64 2001:DB8:0:3::2 name merged_v6 tag 105"
replaced:
commands:
- "no ip route vrf ansible_temp_vrf 192.0.2.0 255.255.255.0 192.0.2.1 name test_vrf track 150 tag 50"
- "no ip route 198.51.100.0 255.255.255.0 198.51.101.1 110 multicast name route_1 tag 40"
- "no ip route 198.51.100.0 255.255.255.0 198.51.101.2 30 name route_2"
- "no ip route 198.51.100.0 255.255.255.0 198.51.101.3 name route_3"
- "ip route vrf ansible_temp_vrf 192.0.2.0 255.255.255.0 192.0.2.1 name replaced_vrf track 155 tag 75"
- "ip route 198.51.100.0 255.255.255.0 198.51.101.1 175 multicast name replaced_route tag 70"
- "ipv6 route 2001:DB8:0:3::/64 2001:DB8:0:3::2 name replaced_v6 tag 110"
overridden:
commands:
- "no ip route 198.51.100.0 255.255.255.0 198.51.101.1 110 multicast name route_1 tag 40"
- "no ip route 198.51.100.0 255.255.255.0 198.51.101.2 30 name route_2"
- "no ip route 198.51.100.0 255.255.255.0 198.51.101.3 name route_3"
- "no ip route vrf ansible_temp_vrf 192.0.2.0 255.255.255.0 192.0.2.1 name test_vrf track 150 tag 50"
- "no ipv6 route 2001:DB8:0:3::/64 2001:DB8:0:3::2 name test_v6 tag 105"
- "ip route 198.51.100.0 255.255.255.0 198.51.101.3 name override_route"
- "ip route vrf ansible_temp_vrf 192.0.2.0 255.255.255.0 192.0.2.1 name override_vrf track 150 tag 50"
- "ipv6 route 2001:DB8:0:3::/64 2001:DB8:0:3::2 name override_v6 tag 175"
rendered:
commands:
- "ip route vrf ansible_temp_vrf 192.0.2.0 255.255.255.0 192.0.2.1 name test_vrf track 150 tag 50"
- "ip route 198.51.100.0 255.255.255.0 198.51.101.1 110 multicast name route_1 tag 40"
- "ip route 198.51.100.0 255.255.255.0 198.51.101.2 30 name route_2"
- "ip route 198.51.100.0 255.255.255.0 198.51.101.3 name route_3"
- "ipv6 route 2001:DB8:0:3::/64 2001:DB8:0:3::2 name test_v6 tag 105"
gathered:
config:
- address_families:
- afi: ipv4
routes:
- dest: 192.0.2.0/24
next_hops:
- forward_router_address: 192.0.2.1
name: test_vrf
tag: 50
track: 150
vrf: ansible_temp_vrf
- address_families:
- afi: ipv6
routes:
- dest: 2001:DB8:0:3::/64
next_hops:
- forward_router_address: 2001:DB8:0:3::2
name: test_v6
tag: 105
- afi: ipv4
routes:
- dest: 198.51.100.0/24
next_hops:
- distance_metric: 110
forward_router_address: 198.51.101.1
multicast: true
name: route_1
tag: 40
- distance_metric: 30
forward_router_address: 198.51.101.2
name: route_2
- forward_router_address: 198.51.101.3
name: route_3
rtt:
override_commands:
- "no ip route 198.51.100.0 255.255.255.0 198.51.101.1 110 multicast name rtt_route_1 tag 40"
- "ip route vrf ansible_temp_vrf 192.0.2.0 255.255.255.0 192.0.2.12 name new_rtt_vrf track 150 tag 10"
override_revert_commands:
- "ip route 198.51.100.0 255.255.255.0 198.51.101.1 110 multicast name rtt_route_1 tag 40"

@ -0,0 +1,2 @@
ip route vrf ansible_vrf 192.0.2.0 255.255.255.0 192.0.2.1 name test_vrf track 175 tag 50
ip route 198.51.100.0 255.255.255.0 198.51.101.1 110 multicast name route_1 tag 60

@ -0,0 +1,357 @@
#
# (c) 2019, 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 units.compat.mock import patch
from ansible.modules.network.ios import ios_static_routes
from units.modules.utils import set_module_args
from .ios_module import TestIosModule, load_fixture
class TestIosStaticRoutesModule(TestIosModule):
module = ios_static_routes
def setUp(self):
super(TestIosStaticRoutesModule, self).setUp()
self.mock_get_config = patch('ansible.module_utils.network.common.network.Config.get_config')
self.get_config = self.mock_get_config.start()
self.mock_load_config = patch('ansible.module_utils.network.common.network.Config.load_config')
self.load_config = self.mock_load_config.start()
self.mock_get_resource_connection_config = patch('ansible.module_utils.network.common.cfg.base.'
'get_resource_connection')
self.get_resource_connection_config = self.mock_get_resource_connection_config.start()
self.mock_get_resource_connection_facts = patch('ansible.module_utils.network.common.facts.facts.'
'get_resource_connection')
self.get_resource_connection_facts = self.mock_get_resource_connection_facts.start()
self.mock_edit_config = patch('ansible.module_utils.network.ios.providers.providers.CliProvider.edit_config')
self.edit_config = self.mock_edit_config.start()
self.mock_execute_show_command = patch('ansible.module_utils.network.ios.facts.static_routes.static_routes.'
'Static_RoutesFacts.get_static_routes_data')
self.execute_show_command = self.mock_execute_show_command.start()
def tearDown(self):
super(TestIosStaticRoutesModule, self).tearDown()
self.mock_get_resource_connection_config.stop()
self.mock_get_resource_connection_facts.stop()
self.mock_edit_config.stop()
self.mock_get_config.stop()
self.mock_load_config.stop()
self.mock_execute_show_command.stop()
def load_fixtures(self, commands=None, transport='cli'):
def load_from_file(*args, **kwargs):
return load_fixture('ios_static_routes_config.cfg')
self.execute_show_command.side_effect = load_from_file
def test_ios_static_routes_merged(self):
set_module_args(dict(
config=[dict(
vrf="ansible_vrf",
address_families=[dict(
afi="ipv4",
routes=[dict(
dest="192.0.2.0 255.255.255.0",
next_hops=[dict(
forward_router_address="192.0.2.1",
name="test_vrf",
tag=50,
track=150
)],
)],
)],
), dict(
address_families=[dict(
afi="ipv4",
routes=[dict(
dest="198.51.100.0 255.255.255.0",
next_hops=[dict(
forward_router_address="198.51.101.1",
name="route_1",
distance_metric=110,
tag=40,
multicast=True
)],
)],
)],
)], state="merged"
))
result = self.execute_module(changed=True)
commands = ['ip route vrf ansible_vrf 192.0.2.0 255.255.255.0 192.0.2.1 name test_vrf track 150 tag 50',
'ip route 198.51.100.0 255.255.255.0 198.51.101.1 110 multicast name route_1 tag 40'
]
self.assertEqual(result['commands'], commands)
def test_ios_static_routes_merged_idempotent(self):
set_module_args(dict(
config=[dict(
vrf="ansible_vrf",
address_families=[dict(
afi="ipv4",
routes=[dict(
dest="192.0.2.0/24",
next_hops=[dict(
forward_router_address="192.0.2.1",
name="test_vrf",
tag=50,
track=175
)],
)],
)],
), dict(
address_families=[dict(
afi="ipv4",
routes=[dict(
dest="198.51.100.0/24",
next_hops=[dict(
forward_router_address="198.51.101.1",
name="route_1",
distance_metric=110,
tag=60,
multicast=True
)],
)],
)],
)], state="merged"
))
self.execute_module(changed=False, commands=[], sort=True)
def test_ios_static_routes_replaced(self):
set_module_args(dict(
config=[dict(
vrf="ansible_vrf",
address_families=[dict(
afi="ipv4",
routes=[dict(
dest="192.0.2.0 255.255.255.0",
next_hops=[dict(
forward_router_address="192.0.2.1",
name="replaced_vrf",
tag=10,
track=170
)],
)],
)],
), dict(
address_families=[dict(
afi="ipv4",
routes=[dict(
dest="198.51.100.0 255.255.255.0",
next_hops=[dict(
forward_router_address="198.51.101.1",
name="replaced_route_1",
distance_metric=110,
tag=60,
multicast=True
)],
)],
)],
)], state="replaced"
))
result = self.execute_module(changed=True)
commands = ['ip route vrf ansible_vrf 192.0.2.0 255.255.255.0 192.0.2.1 name replaced_vrf track 170 tag 10',
'ip route 198.51.100.0 255.255.255.0 198.51.101.1 110 multicast name replaced_route_1 tag 60'
]
self.assertEqual(result['commands'], commands)
def test_ios_static_routes_replaced_idempotent(self):
set_module_args(dict(
config=[dict(
vrf="ansible_vrf",
address_families=[dict(
afi="ipv4",
routes=[dict(
dest="192.0.2.0/24",
next_hops=[dict(
forward_router_address="192.0.2.1",
name="test_vrf",
tag=50,
track=175
)],
)],
)],
), dict(
address_families=[dict(
afi="ipv4",
routes=[dict(
dest="198.51.100.0/24",
next_hops=[dict(
forward_router_address="198.51.101.1",
name="route_1",
distance_metric=110,
tag=60,
multicast=True
)],
)],
)],
)], state="replaced"
))
self.execute_module(changed=False, commands=[], sort=True)
def test_ios_static_routes_overridden(self):
set_module_args(dict(
config=[dict(
address_families=[dict(
afi="ipv4",
routes=[dict(
dest="198.51.100.0 255.255.255.0",
next_hops=[dict(
forward_router_address="198.51.101.1",
name="override_route_1",
distance_metric=150,
tag=50,
multicast=True
)],
)],
)],
)], state="overridden"
))
result = self.execute_module(changed=True)
commands = [
'no ip route 198.51.100.0 255.255.255.0 198.51.101.1 110 multicast name route_1 tag 60',
'no ip route vrf ansible_vrf 192.0.2.0 255.255.255.0 192.0.2.1 name test_vrf track 175 tag 50',
'ip route 198.51.100.0 255.255.255.0 198.51.101.1 150 multicast name override_route_1 tag 50'
]
self.assertEqual(result['commands'], commands)
def test_ios_static_routes_overridden_idempotent(self):
set_module_args(dict(
config=[dict(
vrf="ansible_vrf",
address_families=[dict(
afi="ipv4",
routes=[dict(
dest="192.0.2.0/24",
next_hops=[dict(
forward_router_address="192.0.2.1",
name="test_vrf",
tag=50,
track=175
)],
)],
)],
), dict(
address_families=[dict(
afi="ipv4",
routes=[dict(
dest="198.51.100.0/24",
next_hops=[dict(
forward_router_address="198.51.101.1",
name="route_1",
distance_metric=110,
tag=60,
multicast=True
)],
)],
)],
)], state="overridden"
))
self.execute_module(changed=False, commands=[], sort=True)
def test_ios_delete_static_route_config(self):
set_module_args(dict(
config=[dict(
vrf="ansible_vrf",
address_families=[dict(
afi="ipv4",
routes=[dict(
dest="192.0.2.0/24",
next_hops=[dict(
forward_router_address="192.0.2.1",
name="test_vrf",
tag=50,
track=175
)],
)],
)],
), dict(
address_families=[dict(
afi="ipv4",
routes=[dict(
dest="198.51.100.0/24",
next_hops=[dict(
forward_router_address="198.51.101.1",
name="route_1",
distance_metric=110,
tag=60,
multicast=True
)],
)],
)],
)], state="deleted"
))
result = self.execute_module(changed=True)
commands = [
'no ip route vrf ansible_vrf 192.0.2.0 255.255.255.0 192.0.2.1 name test_vrf track 175 tag 50',
'no ip route 198.51.100.0 255.255.255.0 198.51.101.1 110 multicast name route_1 tag 60'
]
self.assertEqual(result['commands'], commands)
def test_ios_delete_static_route_dest_based(self):
set_module_args(dict(
config=[dict(
address_families=[dict(
afi="ipv4",
routes=[dict(
dest="198.51.100.0/24"
)],
)],
)], state="deleted"
))
result = self.execute_module(changed=True)
commands = [
'no ip route 198.51.100.0 255.255.255.0 198.51.101.1 110 multicast name route_1 tag 60'
]
self.assertEqual(result['commands'], commands)
def test_ios_delete_static_route_vrf_based(self):
set_module_args(dict(
config=[dict(
vrf="ansible_vrf",
address_families=[dict(
afi="ipv4",
routes=[dict(
dest="192.0.2.0/24"
)],
)],
)], state="deleted"
))
result = self.execute_module(changed=True)
commands = [
'no ip route vrf ansible_vrf 192.0.2.0 255.255.255.0 192.0.2.1 name test_vrf track 175 tag 50'
]
self.assertEqual(result['commands'], commands)
def test_static_route_rendered(self):
set_module_args(dict(
config=[dict(
vrf="ansible_vrf",
address_families=[dict(
afi="ipv4",
routes=[dict(
dest="192.0.2.0/24",
next_hops=[dict(
forward_router_address="192.0.2.1",
name="test_vrf",
tag=50,
track=175
)],
)],
)],
)], state="rendered"
))
commands = [
'ip route vrf ansible_vrf 192.0.2.0 255.255.255.0 192.0.2.1 name test_vrf track 175 tag 50'
]
result = self.execute_module(changed=False)
self.assertEqual(sorted(result['rendered']), commands)
Loading…
Cancel
Save