Add iosxr_static_routes RM (#65181)

Signed-off-by: NilashishC <nilashishchakraborty8@gmail.com>
pull/67815/head
Nilashish Chakraborty 5 years ago committed by GitHub
parent 9afe87139a
commit 3405ee1c01
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -0,0 +1,121 @@
#
# -*- 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 iosxr_static_routes module
"""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
class Static_routesArgs(object): # pylint: disable=R0903
"""The arg spec for the iosxr_static_routes module
"""
def __init__(self, **kwargs):
pass
argument_spec = {
'config': {
'elements': 'dict',
'options': {
'vrf': {
'type': 'str'
},
'address_families': {
'elements': 'dict',
'options': {
'afi': {
'choices': ['ipv4', 'ipv6'],
'required': True,
'type': 'str'
},
'safi': {
'choices': ['unicast', 'multicast'],
'required': True,
'type': 'str'
},
'routes': {
'elements': 'dict',
'options': {
'dest': {
'required': True,
'type': 'str'
},
'next_hops': {
'elements': 'dict',
'options': {
'admin_distance': {
'type': 'int'
},
'description': {
'type': 'str'
},
'dest_vrf': {
'type': 'str'
},
'forward_router_address': {
'type': 'str'
},
'interface': {
'type': 'str'
},
'metric': {
'type': 'int'
},
'tag': {
'type': 'int'
},
'track': {
'type': 'str'
},
'tunnel_id': {
'type': 'int'
},
'vrflabel': {
'type': 'int'
}
},
'type': 'list'
}
},
'type': 'list'
},
},
'type': 'list'
},
},
'type': 'list'
},
'running_config': {
'type': 'str'
},
'state': {
'choices': [
'merged', 'replaced', 'overridden', 'deleted', 'gathered', 'rendered', 'parsed'
],
'default': 'merged',
'type': 'str'
}
} # pylint: disable=C0301

@ -0,0 +1,560 @@
#
# -*- 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 iosxr_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
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.iosxr.facts.facts import Facts
from ansible.module_utils.six import iteritems
from ansible.module_utils.network.common.utils import (
search_obj_in_list,
remove_empties,
dict_diff,
dict_merge,
)
class Static_routes(ConfigBase):
"""
The iosxr_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}
warnings = list()
commands = 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)
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"]
commands = []
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
)
)
if state == "overridden":
commands.extend(self._state_overridden(want, have))
elif state == "deleted":
if not want:
if len(have) >= 1:
return "no router static"
else:
for w_item in want:
obj_in_have = self._find_vrf(w_item, have)
if obj_in_have:
commands.extend(
self._state_deleted(remove_empties(w_item), obj_in_have)
)
else:
for w_item in want:
obj_in_have = self._find_vrf(w_item, have)
if state == "merged" or self.state == "rendered":
commands.extend(
self._state_merged(remove_empties(w_item), obj_in_have)
)
elif state == "replaced":
commands.extend(
self._state_replaced(remove_empties(w_item), obj_in_have)
)
if commands:
commands.insert(0, "router static")
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 = []
for want_afi in want.get("address_families", []):
have_afi = (
self.find_af_context(want_afi, have.get("address_families", [])) or {}
)
update_commands = []
for want_route in want_afi.get("routes", []):
have_route = (
search_obj_in_list(
want_route["dest"], have_afi.get("routes", []), key="dest"
)
or {}
)
rotated_have_next_hops = self.rotate_next_hops(
have_route.get("next_hops", {})
)
rotated_want_next_hops = self.rotate_next_hops(
want_route.get("next_hops", {})
)
for key in rotated_have_next_hops.keys():
if key not in rotated_want_next_hops:
cmd = "no {0}".format(want_route["dest"])
for item in key:
if "." in item or ":" in item or "/" in item:
cmd += " {0}".format(item)
else:
cmd += " vrf {0}".format(item)
update_commands.append(cmd)
for key, value in iteritems(rotated_want_next_hops):
if key in rotated_have_next_hops:
existing = True
have_exit_point_attribs = rotated_have_next_hops[key]
else:
existing = False
have_exit_point_attribs = {}
updates = dict_diff(have_exit_point_attribs, value)
if updates or not existing:
update_commands.append(
self._compute_commands(
dest=want_route["dest"], next_hop=key, updates=updates
)
)
if update_commands:
update_commands.insert(
0,
"address-family {0} {1}".format(want_afi["afi"], want_afi["safi"]),
)
commands.extend(update_commands)
if "vrf" in want and update_commands:
commands.insert(0, "vrf {0}".format(want["vrf"]))
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 = []
# Iterate through all the entries, i.e., VRFs and Global entry in have
# and fully remove the ones that are not present in want and then call
# replaced
for h_item in have:
w_item = self._find_vrf(h_item, want)
# Delete all the top-level keys (VRFs/Global Route Entry) that are
# not specified in want.
if not w_item:
if "vrf" in h_item:
commands.append("no vrf {0}".format(h_item["vrf"]))
else:
for have_afi in h_item.get("address_families", []):
commands.append(
"no address-family {0} {1}".format(
have_afi["afi"], have_afi["safi"]
)
)
# For VRFs/Global Entry present in want, we also need to delete extraneous routes
# from them. We cannot reuse `_state_replaced` for this purpose since its scope is
# limited to replacing a single `dest`.
else:
del_cmds = []
for have_afi in h_item.get("address_families", []):
want_afi = (
self.find_af_context(
have_afi, w_item.get("address_families", [])
)
or {}
)
update_commands = []
for h_route in have_afi.get("routes", []):
w_route = (
search_obj_in_list(
h_route["dest"], want_afi.get("routes", []), key="dest"
)
or {}
)
if not w_route:
update_commands.append("no {0}".format(h_route["dest"]))
if update_commands:
update_commands.insert(
0,
"address-family {0} {1}".format(
want_afi["afi"], want_afi["safi"]
),
)
del_cmds.extend(update_commands)
if "vrf" in want and update_commands:
del_cmds.insert(0, "vrf {0}".format(want["vrf"]))
commands.extend(del_cmds)
# We finally call `_state_replaced` to replace exiting `dest` entries
# or add new ones as specified in want.
for w_item in want:
h_item = self._find_vrf(w_item, have)
commands.extend(self._state_replaced(remove_empties(w_item), h_item))
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 = []
for want_afi in want.get("address_families", []):
have_afi = (
self.find_af_context(want_afi, have.get("address_families", [])) or {}
)
update_commands = []
for want_route in want_afi.get("routes", []):
have_route = (
search_obj_in_list(
want_route["dest"], have_afi.get("routes", []), key="dest"
)
or {}
)
# convert the next_hops list of dictionaries to dictionary of
# dictionaries with (`dest_vrf`, `forward_router_address`, `interface`) tuple
# being the key for each dictionary.
# a combination of these 3 attributes uniquely identifies a route entry.
# in case `dest_vrf` is not specified, `forward_router_address` and `interface`
# become the unique identifier
rotated_have_next_hops = self.rotate_next_hops(
have_route.get("next_hops", {})
)
rotated_want_next_hops = self.rotate_next_hops(
want_route.get("next_hops", {})
)
# for every dict in the want next_hops dictionaries, if the key
# is present in `rotated_have_next_hops`, we set `existing` to True,
# which means the the given want exit point exists and we run dict_diff
# on `value` which is basically all the other attributes of the exit point
# if the key is not present, it means that this is a new exit point
for key, value in iteritems(rotated_want_next_hops):
if key in rotated_have_next_hops:
existing = True
have_exit_point_attribs = rotated_have_next_hops[key]
else:
existing = False
have_exit_point_attribs = {}
updates = dict_diff(have_exit_point_attribs, value)
if updates or not existing:
update_commands.append(
self._compute_commands(
dest=want_route["dest"],
next_hop=key,
# dict_merge() is necessary to make sure that we
# don't end up overridding the entry and also to
# allow incremental updates
updates=dict_merge(
rotated_have_next_hops.get(key, {}), updates
),
)
)
if update_commands:
update_commands.insert(
0,
"address-family {0} {1}".format(want_afi["afi"], want_afi["safi"]),
)
commands.extend(update_commands)
if "vrf" in want and update_commands:
commands.insert(0, "vrf {0}".format(want["vrf"]))
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 "address_families" not in want:
return ["no vrf {0}".format(want["vrf"])]
else:
for want_afi in want.get("address_families", []):
update_commands = []
have_afi = (
self.find_af_context(want_afi, have.get("address_families", []))
or {}
)
if have_afi:
if "routes" not in want_afi:
commands.append(
"no address-family {0} {1}".format(
have_afi["afi"], have_afi["safi"]
)
)
else:
for want_route in want_afi.get("routes", []):
have_route = (
search_obj_in_list(
want_route["dest"],
have_afi.get("routes", []),
key="dest",
)
or {}
)
if have_route:
if "next_hops" not in want_route:
update_commands.append(
"no {0}".format(want_route["dest"])
)
else:
rotated_have_next_hops = self.rotate_next_hops(
have_route.get("next_hops", {})
)
rotated_want_next_hops = self.rotate_next_hops(
want_route.get("next_hops", {})
)
for key in rotated_want_next_hops.keys():
if key in rotated_have_next_hops:
cmd = "no {0}".format(want_route["dest"])
for item in key:
if (
"." in item
or ":" in item
or "/" in item
):
cmd += " {0}".format(item)
else:
cmd += " vrf {0}".format(item)
update_commands.append(cmd)
if update_commands:
update_commands.insert(
0,
"address-family {0} {1}".format(
want_afi["afi"], want_afi["safi"]
),
)
commands.extend(update_commands)
if "vrf" in want and commands:
commands.insert(0, "vrf {0}".format(want["vrf"]))
return commands
def _find_vrf(self, item, entries):
""" This method iterates through the items
in `entries` and returns the object that
matches `item`.
:rtype: A dict
:returns: the obj in `entries` that matches `item`
"""
obj = {}
afi = item.get("vrf")
if afi:
obj = search_obj_in_list(afi, entries, key="vrf") or {}
else:
for x in entries:
if "vrf" not in remove_empties(x):
obj = x
break
return obj
def find_af_context(self, want_af_context, have_address_families):
""" This method iterates through the have AFs
and returns the one that matches the want AF
:rtype: A dict
:returns: the corresponding AF in have AFs
that matches the want AF
"""
for have_af in have_address_families:
if (
have_af["afi"] == want_af_context["afi"]
and have_af["safi"] == want_af_context["safi"]
):
return have_af
def rotate_next_hops(self, next_hops):
""" This method iterates through the list of
next hops for a given destination network
and converts it to a dictionary of dictionaries.
Each dictionary has a primary key indicated by the
tuple of `dest_vrf`, `forward_router_address` and
`interface` and the value of this key is a dictionary
that contains all the other attributes of the next hop.
:rtype: A dict
:returns: A next_hops list in a dictionary of dictionaries format
"""
next_hops_dict = {}
for entry in next_hops:
entry = entry.copy()
key_list = []
for x in ["dest_vrf", "forward_router_address", "interface"]:
if entry.get(x):
key_list.append(entry.pop(x))
key = tuple(key_list)
next_hops_dict[key] = entry
return next_hops_dict
def _compute_commands(self, dest, next_hop, updates=None):
""" This method computes a static route entry command
from the specified `dest`, `next_hop` and `updates`
:rtype: A str
:returns: A platform specific static routes command
"""
if not updates:
updates = {}
command = dest
for x in next_hop:
if "." in x or ":" in x or "/" in x:
command += " {0}".format(x)
else:
command += " vrf {0}".format(x)
for key in sorted(updates):
if key == "admin_distance":
command += " {0}".format(updates[key])
else:
command += " {0} {1}".format(key, updates[key])
return command

@ -25,6 +25,7 @@ from ansible.module_utils.network.iosxr.facts.l2_interfaces.l2_interfaces import
from ansible.module_utils.network.iosxr.facts.l3_interfaces.l3_interfaces import L3_InterfacesFacts
from ansible.module_utils.network.iosxr.facts.acl_interfaces.acl_interfaces import Acl_interfacesFacts
from ansible.module_utils.network.iosxr.facts.acls.acls import AclsFacts
from ansible.module_utils.network.iosxr.facts.static_routes.static_routes import Static_routesFacts
FACT_LEGACY_SUBSETS = dict(
@ -43,7 +44,8 @@ FACT_RESOURCE_SUBSETS = dict(
lag_interfaces=Lag_interfacesFacts,
l3_interfaces=L3_InterfacesFacts,
acl_interfaces=Acl_interfacesFacts,
acls=AclsFacts
acls=AclsFacts,
static_routes=Static_routesFacts
)

@ -0,0 +1,176 @@
#
# -*- 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 iosxr 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
import re
from copy import deepcopy
from ansible.module_utils.network.common import utils
from ansible.module_utils.network.iosxr.argspec.static_routes.static_routes import Static_routesArgs
class Static_routesFacts(object):
""" The iosxr 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_device_data(self, connection):
return connection.get_config(flags="router static")
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
"""
if not data:
data = self.get_device_data(connection)
objs = []
if "No such configuration" not in data:
for entry in re.compile(r"(\s) vrf").split(data):
obj = self.render_config(self.generated_spec, entry)
if obj:
objs.append(obj)
ansible_facts["ansible_network_resources"].pop("static_routes", None)
facts = {}
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 render_config(self, spec, conf):
"""
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)
entry_list = conf.split(" address-family")
config["address_families"] = []
if "router static" not in entry_list[0]:
config["vrf"] = entry_list[0].replace("!", "").strip()
for item in entry_list[1:]:
routes = []
address_family = {"routes": []}
address_family["afi"], address_family["safi"] = self.parse_af(item)
destinations = re.findall(r"((?:\S+)/(?:\d+)) (?:.*)", item, re.M)
for dest in set(destinations):
route = {"next_hops": []}
route["dest"] = dest
regex = r"%s .+$" % dest
cfg = re.findall(regex, item, re.M)
for route_entry in cfg:
exit_point = {}
exit_point["forward_router_address"] = self.parse_faddr(route_entry)
exit_point["interface"] = self.parse_intf(route_entry)
exit_point["admin_distance"] = self.parse_admin_distance(route_entry)
for x in [
"tag",
"tunnel-id",
"metric",
"description",
"track",
"vrflabel",
"dest_vrf",
]:
exit_point[x.replace("-", "_")] = self.parse_attrib(
route_entry, x.replace("dest_vrf", "vrf")
)
route["next_hops"].append(exit_point)
routes.append(route)
address_family["routes"] = sorted(routes, key=lambda i: i["dest"])
config["address_families"].append(address_family)
return utils.remove_empties(config)
def parse_af(self, item):
match = re.search(r"(?:\s*)(\w+)(?:\s*)(\w+)", item, re.M)
if match:
return match.group(1), match.group(2)
def parse_faddr(self, item):
for x in item.split(" "):
if (":" in x or "." in x) and "/" not in x:
return x
def parse_intf(self, item):
match = re.search(r" ((\w+)((?:\d)/(?:\d)/(?:\d)/(?:\d+)))", item)
if match:
return match.group(1)
def parse_attrib(self, item, attrib):
match = re.search(r" %s (\S+)" % attrib, item)
if match:
val = match.group(1).strip("'")
if attrib in ["tunnel-id", "vrflabel", "tag", "metric"]:
val = int(val)
return val
def parse_admin_distance(self, item):
split_item = item.split(" ")
for item in [
"vrf",
"metric",
"tunnel-id",
"vrflabel",
"track",
"tag",
"description",
]:
try:
del split_item[split_item.index(item) + 1]
del split_item[split_item.index(item)]
except ValueError:
continue
try:
return [
i for i in split_item if "." not in i and ":" not in i and ord(i[0]) > 48 and ord(i[0]) < 57
][0]
except IndexError:
return None

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

@ -0,0 +1,966 @@
#!/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 iosxr_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: iosxr_static_routes
version_added: "2.10"
short_description: Manage static routes on devices running Cisco IOS-XR.
description:
- This module manages static routes on devices running Cisco IOS-XR.
author: Nilashish Chakraborty (@NilashishC)
options:
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
B(show running-config router static).
type: str
config:
description: A dictionary of static route options.
type: list
elements: dict
suboptions:
vrf:
description:
- The VRF to which the static route(s) belong.
type: str
address_families:
description: A dictionary specifying the address family to which the static route(s) belong.
type: list
elements: dict
suboptions:
afi:
description:
- Specifies the top level address family indicator.
type: str
choices: ['ipv4', 'ipv6']
required: True
safi:
description:
- Specifies the subsequent address family indicator.
type: str
choices: ['unicast', 'multicast']
required: True
routes:
description: A dictionary that specifies the static route configurations.
elements: dict
type: list
suboptions:
dest:
description:
- An IPv4 or IPv6 address in CIDR notation that specifies the destination network for the static route.
type: str
required: True
next_hops:
description:
- Next hops to the specified destination.
type: list
elements: dict
suboptions:
forward_router_address:
description:
- The IP address of the next hop that can be used to reach the destination network.
type: str
interface:
description:
- The interface to use to reach the destination.
type: str
dest_vrf:
description:
- The destination VRF.
type: str
admin_distance:
description:
- The administrative distance for this static route.
- Refer to vendor documentation for valid values.
type: int
metric:
description:
- Specifes the metric for this static route.
- Refer to vendor documentation for valid values.
type: int
description:
description:
- Specifies the description for this static route.
type: str
vrflabel:
description:
- Specifies the VRF label for this static route.
- Refer to vendor documentation for valid values.
type: int
tag:
description:
- Specifies a numeric tag for this static route.
- Refer to vendor documentation for valid values.
type: int
track:
description:
- Specifies the object to be tracked.
- This enables object tracking for static routes.
type: str
tunnel_id:
description:
- Specifies a tunnel id for the route.
- Refer to vendor documentation for valid values.
type: int
state:
description:
- The state the configuration should be left in.
type: str
choices:
- merged
- replaced
- overridden
- deleted
- gathered
- rendered
- parsed
default: merged
"""
EXAMPLES = """
# Using merged
# Before state
# -------------
# RP/0/RP0/CPU0:ios#show running-config router static
# Sat Feb 22 07:46:30.089 UTC
# % No such configuration item(s)
#
- name: Merge the provided configuration with the exisiting running configuration
iosxr_static_routes: &merged
config:
- address_families:
- afi: ipv4
safi: unicast
routes:
- dest: 192.0.2.16/28
next_hops:
- forward_router_address: 192.0.2.10
interface: FastEthernet0/0/0/1
description: "LAB"
metric: 120
tag: 10
- interface: FastEthernet0/0/0/5
track: ip_sla_1
- dest: 192.0.2.32/28
next_hops:
- forward_router_address: 192.0.2.11
admin_distance: 100
- afi: ipv6
safi: unicast
routes:
- dest: 2001:db8:1000::/36
next_hops:
- interface: FastEthernet0/0/0/7
description: "DC"
- interface: FastEthernet0/0/0/8
forward_router_address: 2001:db8:2000:2::1
- vrf: DEV_SITE
address_families:
- afi: ipv4
safi: unicast
routes:
- dest: 192.0.2.48/28
next_hops:
- forward_router_address: 192.0.2.12
description: "DEV"
dest_vrf: test_1
- dest: 192.0.2.80/28
next_hops:
- interface: FastEthernet0/0/0/2
forward_router_address: 192.0.2.14
dest_vrf: test_1
track: ip_sla_2
vrflabel: 124
state: merged
# After state
# -------------
# RP/0/RP0/CPU0:ios#show running-config router static
# Sat Feb 22 07:49:11.754 UTC
# router static
# address-family ipv4 unicast
# 192.0.2.16/28 FastEthernet0/0/0/1 192.0.2.10 tag 10 description LAB metric 120
# 192.0.2.16/28 FastEthernet0/0/0/5 track ip_sla_1
# 192.0.2.32/28 192.0.2.11 100
# !
# address-family ipv6 unicast
# 2001:db8:1000::/36 FastEthernet0/0/0/7 description DC
# 2001:db8:1000::/36 FastEthernet0/0/0/8 2001:db8:2000:2::1
# !
# vrf DEV_SITE
# address-family ipv4 unicast
# 192.0.2.48/28 vrf test_1 192.0.2.12 description DEV
# 192.0.2.80/28 vrf test_1 FastEthernet0/0/0/2 192.0.2.14 vrflabel 124 track ip_sla_2
# !
# !
# !
# Using merged to update existing static routes
# Before state
# -------------
# RP/0/RP0/CPU0:ios#show running-config router static
# Sat Feb 22 07:49:11.754 UTC
# router static
# address-family ipv4 unicast
# 192.0.2.16/28 FastEthernet0/0/0/1 192.0.2.10 tag 10 description LAB metric 120
# 192.0.2.16/28 FastEthernet0/0/0/5 track ip_sla_1
# 192.0.2.32/28 192.0.2.11 100
# !
# address-family ipv6 unicast
# 2001:db8:1000::/36 FastEthernet0/0/0/7 description DC
# 2001:db8:1000::/36 FastEthernet0/0/0/8 2001:db8:2000:2::1
# !
# vrf DEV_SITE
# address-family ipv4 unicast
# 192.0.2.48/28 vrf test_1 192.0.2.12 description DEV
# 192.0.2.80/28 vrf test_1 FastEthernet0/0/0/2 192.0.2.14 vrflabel 124 track ip_sla_2
# !
# !
# !
- name: Update existing static routes configuration using merged
iosxr_static_routes: &merged_update
config:
- vrf: DEV_SITE
address_families:
- afi: ipv4
safi: unicast
routes:
- dest: 192.0.2.48/28
next_hops:
- forward_router_address: 192.0.2.12
vrflabel: 2301
dest_vrf: test_1
- dest: 192.0.2.80/28
next_hops:
- interface: FastEthernet0/0/0/2
forward_router_address: 192.0.2.14
dest_vrf: test_1
description: "rt_test_1"
state: merged
# After state
# -------------
# RP/0/RP0/CPU0:ios#show running-config router static
# Sat Feb 22 07:49:11.754 UTC
# router static
# address-family ipv4 unicast
# 192.0.2.16/28 FastEthernet0/0/0/1 192.0.2.10 tag 10 description LAB metric 120
# 192.0.2.16/28 FastEthernet0/0/0/5 track ip_sla_1
# 192.0.2.32/28 192.0.2.11 100
# !
# address-family ipv6 unicast
# 2001:db8:1000::/36 FastEthernet0/0/0/7 description DC
# 2001:db8:1000::/36 FastEthernet0/0/0/8 2001:db8:2000:2::1
# !
# vrf DEV_SITE
# address-family ipv4 unicast
# 192.0.2.48/28 vrf test_1 192.0.2.12 description DEV vrflabel 2301
# 192.0.2.80/28 vrf test_1 192.0.2.14 FastEthernet0/0/0/2 description rt_test_1 track ip_sla_2 vrflabel 124
# !
# !
# !
# Using replaced to replace all next hop entries for a single destination network
# Before state
# --------------
# RP/0/RP0/CPU0:ios#sh running-config router static
# Sat Feb 22 07:59:08.669 UTC
# router static
# address-family ipv4 unicast
# 192.0.2.16/28 FastEthernet0/0/0/1 192.0.2.10 tag 10 description LAB metric 120
# 192.0.2.16/28 FastEthernet0/0/0/5 track ip_sla_1
# 192.0.2.32/28 192.0.2.11 100
# !
# address-family ipv6 unicast
# 2001:db8:1000::/36 FastEthernet0/0/0/7 description DC
# 2001:db8:1000::/36 FastEthernet0/0/0/8 2001:db8:2000:2::1
# !
# vrf DEV_SITE
# address-family ipv4 unicast
# 192.0.2.48/28 vrf test_1 192.0.2.12 description DEV
# 192.0.2.48/28 GigabitEthernet0/0/0/1 192.0.3.24 vrflabel 2302
# 192.0.2.80/28 vrf test_1 FastEthernet0/0/0/2 192.0.2.14 vrflabel 124 track ip_sla_2
# !
# !
# !
- name: Replace device configurations of static routes with provided configurations
iosxr_static_routes: &replaced
config:
- vrf: DEV_SITE
address_families:
- afi: ipv4
safi: unicast
routes:
- dest: 192.0.2.48/28
next_hops:
- forward_router_address: 192.0.2.15
interface: FastEthernet0/0/0/3
description: "DEV_NEW"
dest_vrf: dev_test_2
state: replaced
# After state
# ------------
# RP/0/RP0/CPU0:ios#sh running-config router static
# Sat Feb 22 08:04:07.085 UTC
# router static
# address-family ipv4 unicast
# 192.0.2.16/28 FastEthernet0/0/0/1 192.0.2.10 tag 10 description LAB metric 120
# 192.0.2.16/28 FastEthernet0/0/0/5 track ip_sla_1
# 192.0.2.32/28 192.0.2.11 100
# !
# address-family ipv6 unicast
# 2001:db8:1000::/36 FastEthernet0/0/0/7 description DC
# 2001:db8:1000::/36 FastEthernet0/0/0/8 2001:db8:2000:2::1
# !
# vrf DEV_SITE
# address-family ipv4 unicast
# 192.0.2.48/28 vrf dev_test_2 FastEthernet0/0/0/3 192.0.2.15 description DEV_NEW
# 192.0.2.80/28 vrf test_1 FastEthernet0/0/0/2 192.0.2.14 vrflabel 124 track ip_sla_2
# !
# !
# !
# Using overridden to override all static route entries on the device
# Before state
# -------------
# RP/0/RP0/CPU0:ios#sh running-config router static
# Sat Feb 22 07:59:08.669 UTC
# router static
# address-family ipv4 unicast
# 192.0.2.16/28 FastEthernet0/0/0/1 192.0.2.10 tag 10 description LAB metric 120
# 192.0.2.16/28 FastEthernet0/0/0/5 track ip_sla_1
# 192.0.2.32/28 192.0.2.11 100
# !
# address-family ipv6 unicast
# 2001:db8:1000::/36 FastEthernet0/0/0/7 description DC
# 2001:db8:1000::/36 FastEthernet0/0/0/8 2001:db8:2000:2::1
# !
# vrf DEV_SITE
# address-family ipv4 unicast
# 192.0.2.48/28 vrf test_1 192.0.2.12 description DEV
# 192.0.2.48/28 GigabitEthernet0/0/0/1 192.0.3.24 vrflabel 2302
# 192.0.2.80/28 vrf test_1 FastEthernet0/0/0/2 192.0.2.14 vrflabel 124 track ip_sla_2
# !
# !
# !
- name: Overridde all static routes configuration with provided configuration
iosxr_static_routes: &overridden
config:
- vrf: DEV_NEW
address_families:
- afi: ipv4
safi: unicast
routes:
- dest: 192.0.2.48/28
next_hops:
- forward_router_address: 192.0.2.15
interface: FastEthernet0/0/0/3
description: "DEV1"
- afi: ipv6
safi: unicast
routes:
- dest: 2001:db8:3000::/36
next_hops:
- interface: FastEthernet0/0/0/4
forward_router_address: 2001:db8:2000:2::2
description: "PROD1"
track: ip_sla_1
state: overridden
# After state
# -------------
# RP/0/RP0/CPU0:ios#sh running-config router static
# Sat Feb 22 08:07:41.516 UTC
# router static
# vrf DEV_NEW
# address-family ipv4 unicast
# 192.0.2.48/28 FastEthernet0/0/0/3 192.0.2.15 description DEV1
# !
# address-family ipv6 unicast
# 2001:db8:3000::/36 FastEthernet0/0/0/4 2001:db8:2000:2::2 description PROD1 track ip_sla_1
# !
# !
# !
# Using deleted to delete a single next hop for a destination network
# Before state
# -------------
# RP/0/RP0/CPU0:ios#sh running-config router static
# Sat Feb 22 07:59:08.669 UTC
# router static
# address-family ipv4 unicast
# 192.0.2.16/28 FastEthernet0/0/0/1 192.0.2.10 tag 10 description LAB metric 120
# 192.0.2.16/28 FastEthernet0/0/0/5 track ip_sla_1
# 192.0.2.32/28 192.0.2.11 100
# !
# address-family ipv6 unicast
# 2001:db8:1000::/36 FastEthernet0/0/0/7 description DC
# 2001:db8:1000::/36 FastEthernet0/0/0/8 2001:db8:2000:2::1
# !
# vrf DEV_SITE
# address-family ipv4 unicast
# 192.0.2.48/28 vrf test_1 192.0.2.12 description DEV
# 192.0.2.48/28 GigabitEthernet0/0/0/1 192.0.3.24 vrflabel 2302
# 192.0.2.80/28 vrf test_1 FastEthernet0/0/0/2 192.0.2.14 vrflabel 124 track ip_sla_2
# !
# !
# !
- name: Delete a single next_hop from a destination network
iosxr_static_routes:
config:
- address_families:
- afi: ipv4
safi: unicast
routes:
- dest: 192.0.2.16/28
next_hops:
- forward_router_address: 192.0.2.10
interface: FastEthernet0/0/0/1
state: deleted
# After state
# -------------
# RP/0/RP0/CPU0:ios#sh running-config router static
# Sat Feb 22 07:59:08.669 UTC
# router static
# address-family ipv4 unicast
# 192.0.2.16/28 FastEthernet0/0/0/5 track ip_sla_1
# 192.0.2.32/28 192.0.2.11 100
# !
# address-family ipv6 unicast
# 2001:db8:1000::/36 FastEthernet0/0/0/7 description DC
# 2001:db8:1000::/36 FastEthernet0/0/0/8 2001:db8:2000:2::1
# !
# vrf DEV_SITE
# address-family ipv4 unicast
# 192.0.2.48/28 vrf test_1 192.0.2.12 description DEV
# 192.0.2.48/28 GigabitEthernet0/0/0/1 192.0.3.24 vrflabel 2302
# 192.0.2.80/28 vrf test_1 FastEthernet0/0/0/2 192.0.2.14 vrflabel 124 track ip_sla_2
# !
# !
# !
# Using deleted to delete a destination network entry
# Before state
# -------------
# RP/0/RP0/CPU0:ios#sh running-config router static
# Sat Feb 22 07:59:08.669 UTC
# router static
# address-family ipv4 unicast
# 192.0.2.16/28 FastEthernet0/0/0/1 192.0.2.10 tag 10 description LAB metric 120
# 192.0.2.16/28 FastEthernet0/0/0/5 track ip_sla_1
# 192.0.2.32/28 192.0.2.11 100
# !
# address-family ipv6 unicast
# 2001:db8:1000::/36 FastEthernet0/0/0/7 description DC
# 2001:db8:1000::/36 FastEthernet0/0/0/8 2001:db8:2000:2::1
# !
# vrf DEV_SITE
# address-family ipv4 unicast
# 192.0.2.48/28 vrf test_1 192.0.2.12 description DEV
# 192.0.2.48/28 GigabitEthernet0/0/0/1 192.0.3.24 vrflabel 2302
# 192.0.2.80/28 vrf test_1 FastEthernet0/0/0/2 192.0.2.14 vrflabel 124 track ip_sla_2
# !
# !
# !
- name: Delete a destination network entry
iosxr_static_routes:
config:
- vrf: DEV_SITE
address_families:
- afi: ipv4
safi: unicast
routes:
- dest: 192.0.2.48/28
state: deleted
# After state
# -------------
# RP/0/RP0/CPU0:ios#sh running-config router static
# Sat Feb 22 07:59:08.669 UTC
# router static
# address-family ipv4 unicast
# 192.0.2.16/28 FastEthernet0/0/0/5 track ip_sla_1
# 192.0.2.32/28 192.0.2.11 100
# !
# address-family ipv6 unicast
# 2001:db8:1000::/36 FastEthernet0/0/0/7 description DC
# 2001:db8:1000::/36 FastEthernet0/0/0/8 2001:db8:2000:2::1
# !
# vrf DEV_SITE
# address-family ipv4 unicast
# 192.0.2.80/28 vrf test_1 FastEthernet0/0/0/2 192.0.2.14 vrflabel 124 track ip_sla_2
# !
# !
# !
# Using deleted to delete all destination network entries under a single AFI
# Before state
# -------------
# RP/0/RP0/CPU0:ios#sh running-config router static
# Sat Feb 22 07:59:08.669 UTC
# router static
# address-family ipv4 unicast
# 192.0.2.16/28 FastEthernet0/0/0/1 192.0.2.10 tag 10 description LAB metric 120
# 192.0.2.16/28 FastEthernet0/0/0/5 track ip_sla_1
# 192.0.2.32/28 192.0.2.11 100
# !
# address-family ipv6 unicast
# 2001:db8:1000::/36 FastEthernet0/0/0/7 description DC
# 2001:db8:1000::/36 FastEthernet0/0/0/8 2001:db8:2000:2::1
# !
# vrf DEV_SITE
# address-family ipv4 unicast
# 192.0.2.48/28 vrf test_1 192.0.2.12 description DEV
# 192.0.2.48/28 GigabitEthernet0/0/0/1 192.0.3.24 vrflabel 2302
# 192.0.2.80/28 vrf test_1 FastEthernet0/0/0/2 192.0.2.14 vrflabel 124 track ip_sla_2
# !
# !
# !
- name: Delete all destination network entries under a single AFI
iosxr_static_routes:
config:
- vrf: DEV_SITE
address_families:
- afi: ipv4
safi: unicast
state: deleted
# After state
# ------------
# RP/0/RP0/CPU0:ios#sh running-config router static
# Sat Feb 22 08:16:41.464 UTC
# router static
# address-family ipv4 unicast
# 192.0.2.16/28 FastEthernet0/0/0/1 192.0.2.10 tag 10 description LAB metric 120
# 192.0.2.16/28 FastEthernet0/0/0/5 track ip_sla_1
# 192.0.2.32/28 192.0.2.11 100
# !
# address-family ipv6 unicast
# 2001:db8:1000::/36 FastEthernet0/0/0/7 description DC
# 2001:db8:1000::/36 FastEthernet0/0/0/8 2001:db8:2000:2::1
# !
# vrf DEV_SITE
# !
# !
# Using deleted to remove all static route entries from the device
# Before state
# -------------
# RP/0/RP0/CPU0:ios#sh running-config router static
# Sat Feb 22 07:59:08.669 UTC
# router static
# address-family ipv4 unicast
# 192.0.2.16/28 FastEthernet0/0/0/1 192.0.2.10 tag 10 description LAB metric 120
# 192.0.2.16/28 FastEthernet0/0/0/5 track ip_sla_1
# 192.0.2.32/28 192.0.2.11 100
# !
# address-family ipv6 unicast
# 2001:db8:1000::/36 FastEthernet0/0/0/7 description DC
# 2001:db8:1000::/36 FastEthernet0/0/0/8 2001:db8:2000:2::1
# !
# vrf DEV_SITE
# address-family ipv4 unicast
# 192.0.2.48/28 vrf test_1 192.0.2.12 description DEV
# 192.0.2.48/28 GigabitEthernet0/0/0/1 192.0.3.24 vrflabel 2302
# 192.0.2.80/28 vrf test_1 FastEthernet0/0/0/2 192.0.2.14 vrflabel 124 track ip_sla_2
# !
# !
# !
- name: Delete static routes configuration
iosxr_static_routes: &deleted
state: deleted
# After state
# ------------
# RP/0/RP0/CPU0:ios#sh running-config router static
# Sat Feb 22 08:50:43.038 UTC
# % No such configuration item(s)
# Using gathered to gather static route facts from the device
- name: Gather static routes facts from the device using iosxr_static_routes module
iosxr_static_routes:
state: gathered
# Task output (redacted)
# -----------------------
# "gathered": [
# {
# "address_families": [
# {
# "afi": "ipv4",
# "routes": [
# {
# "dest": "192.0.2.16/28",
# "next_hops": [
# {
# "description": "LAB",
# "forward_router_address": "192.0.2.10",
# "interface": "FastEthernet0/0/0/1",
# "metric": 120,
# "tag": 10
# },
# {
# "interface": "FastEthernet0/0/0/5",
# "track": "ip_sla_1"
# }
# ]
# },
# {
# "dest": "192.0.2.32/28",
# "next_hops": [
# {
# "admin_distance": 100,
# "forward_router_address": "192.0.2.11"
# }
# ]
# }
# ],
# "safi": "unicast"
# },
# {
# "afi": "ipv6",
# "routes": [
# {
# "dest": "2001:db8:1000::/36",
# "next_hops": [
# {
# "description": "DC",
# "interface": "FastEthernet0/0/0/7"
# },
# {
# "forward_router_address": "2001:db8:2000:2::1",
# "interface": "FastEthernet0/0/0/8"
# }
# ]
# }
# ],
# "safi": "unicast"
# }
# ]
# },
# {
# "address_families": [
# {
# "afi": "ipv4",
# "routes": [
# {
# "dest": "192.0.2.48/28",
# "next_hops": [
# {
# "description": "DEV",
# "dest_vrf": "test_1",
# "forward_router_address": "192.0.2.12"
# },
# {
# "forward_router_address": "192.0.3.24",
# "interface": "GigabitEthernet0/0/0/1",
# "vrflabel": 2302
# }
# ]
# },
# {
# "dest": "192.0.2.80/28",
# "next_hops": [
# {
# "dest_vrf": "test_1",
# "forward_router_address": "192.0.2.14",
# "interface": "FastEthernet0/0/0/2",
# "track": "ip_sla_2",
# "vrflabel": 124
# }
# ]
# }
# ],
# "safi": "unicast"
# }
# ],
# "vrf": "DEV_SITE"
# }
# ]
# Using rendered
- name: Render platform specific commands (without connecting to the device)
iosxr_static_routes:
config:
- vrf: DEV_SITE
address_families:
- afi: ipv4
safi: unicast
routes:
- dest: 192.0.2.48/28
next_hops:
- forward_router_address: 192.0.2.12
description: "DEV"
dest_vrf: test_1
- dest: 192.0.2.80/28
next_hops:
- interface: FastEthernet0/0/0/2
forward_router_address: 192.0.2.14
dest_vrf: test_1
track: ip_sla_2
vrflabel: 124
# Task Output (redacted)
# -----------------------
# "rendered": [
# "router static"s,
# "vrf DEV_SITE",
# "address-family ipv4 unicast",
# "192.0.2.48/28 vrf test_1 192.0.2.12 description DEV",
# "192.0.2.80/28 vrf test_1 192.0.2.14 FastEthernet0/0/0/2 track ip_sla_2 vrflabel 124"
# Using parsed
# parsed.cfg
# ------------
# Fri Nov 29 21:10:41.896 UTC
# router static
# address-family ipv4 unicast
# 192.0.2.16/28 FastEthernet0/0/0/1 192.0.2.10 tag 10 description LAB metric 120
# 192.0.2.16/28 FastEthernet0/0/0/5 track ip_sla_1
# 192.0.2.32/28 192.0.2.11 100
# !
# address-family ipv6 unicast
# 2001:db8:1000::/36 FastEthernet0/0/0/7 description DC
# 2001:db8:1000::/36 FastEthernet0/0/0/8 2001:db8:2000:2::1
# !
# vrf DEV_SITE
# address-family ipv4 unicast
# 192.0.2.48/28 vrf test_1 192.0.2.12 description DEV
# 192.0.2.80/28 vrf test_1 FastEthernet0/0/0/2 192.0.2.14 vrflabel 124 track ip_sla_2
# !
# !
# !
- name: Use parsed state to convert externally supplied device specific static routes commands to structured format
iosxr_static_routes:
running_config: "{{ lookup('file', '../../fixtures/parsed.cfg') }}"
state: parsed
# Task output (redacted)
# -----------------------
# "parsed": [
# {
# "address_families": [
# {
# "afi": "ipv4",
# "routes": [
# {
# "dest": "192.0.2.16/28",
# "next_hops": [
# {
# "description": "LAB",
# "forward_router_address": "192.0.2.10",
# "interface": "FastEthernet0/0/0/1",
# "metric": 120,
# "tag": 10
# },
# {
# "interface": "FastEthernet0/0/0/5",
# "track": "ip_sla_1"
# }
# ]
# },
# {
# "dest": "192.0.2.32/28",
# "next_hops": [
# {
# "admin_distance": 100,
# "forward_router_address": "192.0.2.11"
# }
# ]
# }
# ],
# "safi": "unicast"
# },
# {
# "afi": "ipv6",
# "routes": [
# {
# "dest": "2001:db8:1000::/36",
# "next_hops": [
# {
# "description": "DC",
# "interface": "FastEthernet0/0/0/7"
# },
# {
# "forward_router_address": "2001:db8:2000:2::1",
# "interface": "FastEthernet0/0/0/8"
# }
# ]
# }
# ],
# "safi": "unicast"
# }
# ]
# },
# {
# "address_families": [
# {
# "afi": "ipv4",
# "routes": [
# {
# "dest": "192.0.2.48/28",
# "next_hops": [
# {
# "description": "DEV",
# "dest_vrf": "test_1",
# "forward_router_address": "192.0.2.12"
# }
# ]
# },
# {
# "dest": "192.0.2.80/28",
# "next_hops": [
# {
# "dest_vrf": "test_1",
# "forward_router_address": "192.0.2.14",
# "interface": "FastEthernet0/0/0/2",
# "track": "ip_sla_2",
# "vrflabel": 124
# }
# ]
# }
# ],
# "safi": "unicast"
# }
# ],
# "vrf": "DEV_SITE"
# }
# ]
# }
"""
RETURN = """
before:
description: The configuration prior to the model invocation.
returned: always
type: list
sample: >
The configuration returned will always be in the same format
of the parameters above.
after:
description: The resulting configuration model invocation.
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:
- router static
- vrf dev_site
- address-family ipv4 unicast
- 192.0.2.48/28 192.0.2.12 FastEthernet0/0/0/1 track ip_sla_10 description dev1
- address-family ipv6 unicast
- no 2001:db8:1000::/36
- 2001:db8:3000::/36 2001:db8:2000:2::2 FastEthernet0/0/0/4 track ip_sla_11 description prod1
"""
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.network.iosxr.argspec.static_routes.static_routes import Static_routesArgs
from ansible.module_utils.network.iosxr.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", "parsed", ("running_config",)),
]
mutually_exclusive = [("config", "running_config")]
module = AnsibleModule(
argument_spec=Static_routesArgs.argument_spec,
required_if=required_if,
mutually_exclusive=mutually_exclusive,
supports_check_mode=True,
)
result = Static_routes(module).execute_module()
module.exit_json(**result)
if __name__ == "__main__":
main()

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

@ -0,0 +1,18 @@
Fri Nov 29 21:10:41.896 UTC
router static
address-family ipv4 unicast
192.0.2.16/28 FastEthernet0/0/0/1 192.0.2.10 tag 10 description LAB metric 120
192.0.2.16/28 FastEthernet0/0/0/5 track ip_sla_1
192.0.2.32/28 192.0.2.11 100
!
address-family ipv6 unicast
2001:db8:1000::/36 FastEthernet0/0/0/7 description DC
2001:db8:1000::/36 FastEthernet0/0/0/8 2001:db8:2000:2::1
!
vrf DEV_SITE
address-family ipv4 unicast
192.0.2.48/28 vrf test_1 192.0.2.12 description DEV
192.0.2.80/28 vrf test_1 FastEthernet0/0/0/2 192.0.2.14 vrflabel 124 track ip_sla_2
!
!
!

@ -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,60 @@
---
- name: Setup
iosxr_static_routes:
config:
- address_families:
- afi: ipv4
safi: unicast
routes:
- dest: 192.0.2.16/28
next_hops:
- forward_router_address: 192.0.2.10
interface: FastEthernet0/0/0/1
description: "LAB"
metric: 120
tag: 10
- interface: FastEthernet0/0/0/5
track: ip_sla_1
- dest: 192.0.2.32/28
next_hops:
- forward_router_address: 192.0.2.11
admin_distance: 100
- afi: ipv6
safi: unicast
routes:
- dest: 2001:db8:1000::/36
next_hops:
- interface: FastEthernet0/0/0/7
description: "DC"
- interface: FastEthernet0/0/0/8
forward_router_address: 2001:db8:2000:2::1
- vrf: DEV_SITE
address_families:
- afi: ipv4
safi: unicast
routes:
- dest: 192.0.2.48/28
next_hops:
- forward_router_address: 192.0.2.12
description: "DEV"
dest_vrf: test_1
- forward_router_address: 192.0.3.24
interface: GigabitEthernet0/0/0/1
vrflabel: 2302
- dest: 192.0.2.80/28
next_hops:
- interface: FastEthernet0/0/0/2
forward_router_address: 192.0.2.14
dest_vrf: test_1
track: ip_sla_2
vrflabel: 124
state: merged

@ -0,0 +1,8 @@
---
- name: Remove Static Routes
cli_config:
config: "{{ lines }}"
vars:
lines: |
no router static
ignore_errors: yes

@ -0,0 +1,124 @@
---
- debug:
msg: "Start iosxr_static_routes deleted integration tests ansible_connection={{ ansible_connection }}"
- include_tasks: _remove_config.yaml
- include_tasks: _populate_config.yaml
- block:
- name: Delete a single next_hop from a destination network
iosxr_static_routes: &deleted_1
config:
- address_families:
- afi: ipv4
safi: unicast
routes:
- dest: 192.0.2.16/28
next_hops:
- forward_router_address: 192.0.2.10
interface: FastEthernet0/0/0/1
state: deleted
register: result
- assert:
that:
- '"router static" in result.commands'
- '"address-family ipv4 unicast" in result.commands'
- '"no 192.0.2.16/28 192.0.2.10 FastEthernet0/0/0/1" in result.commands'
- 'result.commands|length == 3'
- name: Delete a single next_hop from a destination network (IDEMPOTENT)
iosxr_static_routes: *deleted_1
register: result
- assert: &unchanged
that:
- "result.changed == false"
- "result.commands|length == 0"
- name: Delete a destination network entry
iosxr_static_routes: &deleted_2
config:
- vrf: DEV_SITE
address_families:
- afi: ipv4
safi: unicast
routes:
- dest: 192.0.2.48/28
state: deleted
register: result
- assert:
that:
- '"router static" in result.commands'
- '"vrf DEV_SITE" in result.commands'
- '"address-family ipv4 unicast" in result.commands'
- '"no 192.0.2.48/28" in result.commands'
- "result.commands|length == 4"
- name: Delete a destination network entry (IDEMPOTENT)
iosxr_static_routes: *deleted_2
register: result
- assert: *unchanged
- name: Delete all destination network entries under a single AFI
iosxr_static_routes: &deleted_3
config:
- vrf: DEV_SITE
address_families:
- afi: ipv4
safi: unicast
state: deleted
register: result
- assert:
that:
- '"router static" in result.commands'
- '"vrf DEV_SITE" in result.commands'
- '"no address-family ipv4 unicast" in result.commands'
- "result.commands|length == 3"
- name: Delete all destination network entries under a single AFI (IDEMPOTENT)
iosxr_static_routes: *deleted_3
register: result
- assert: *unchanged
- include_tasks: _populate_config.yaml
- name: Delete static routes configuration
iosxr_static_routes: &deleted
state: deleted
register: result
- name: Assert that the before dicts were correctly generated
assert:
that:
- "{{ replaced['before'] | symmetric_difference(result['before']) |length == 0 }}"
- name: Assert that the correct set of commands were generated
assert:
that:
- "{{ deleted['commands'] | symmetric_difference(result['commands']) |length == 0 }}"
- name: Assert that the after dicts were correctly generated
assert:
that:
- "{{ deleted['after'] | symmetric_difference(result['after']) |length == 0 }}"
- name: Delete all static routes (IDEMPOTENT)
iosxr_static_routes: *deleted
register: result
- name: Assert that the previous task was idempotent
assert: *unchanged
- name: Assert that the before dicts were correctly generated
assert:
that:
- "{{ deleted['after'] | symmetric_difference(result['before']) |length == 0 }}"
always:
- include_tasks: _remove_config.yaml

@ -0,0 +1,47 @@
---
- debug:
msg: "START iosxr_static_routes empty_config integration tests on connection={{ ansible_connection }}"
- name: Merged with empty config should give appropriate error message
iosxr_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
iosxr_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
iosxr_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: Parsed with empty running_config should give appropriate error message
iosxr_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,20 @@
---
- debug:
msg: "START iosxr_static_routes gathered integration tests on connection={{ ansible_connection }}"
- include_tasks: _remove_config.yaml
- include_tasks: _populate_config.yaml
- block:
- name: Gather static routes facts from the device using iosxr_static_routes module
iosxr_static_routes:
state: gathered
register: result
- assert:
that: "{{ replaced['before'] | symmetric_difference(result['gathered']) |length == 0 }}"
always:
- include_tasks: _remove_config.yaml

@ -0,0 +1,141 @@
---
- debug:
msg: "START iosxr_static_routes merged integration tests on connection={{ ansible_connection }}"
- include_tasks: _remove_config.yaml
- block:
- name: Merge the provided configuration with the exisiting running configuration
iosxr_static_routes: &merged
config:
- address_families:
- afi: ipv4
safi: unicast
routes:
- dest: 192.0.2.16/28
next_hops:
- forward_router_address: 192.0.2.10
interface: FastEthernet0/0/0/1
description: "LAB"
metric: 120
tag: 10
- interface: FastEthernet0/0/0/5
track: ip_sla_1
- dest: 192.0.2.32/28
next_hops:
- forward_router_address: 192.0.2.11
admin_distance: 100
- afi: ipv6
safi: unicast
routes:
- dest: 2001:db8:1000::/36
next_hops:
- interface: FastEthernet0/0/0/7
description: "DC"
- interface: FastEthernet0/0/0/8
forward_router_address: 2001:db8:2000:2::1
- vrf: DEV_SITE
address_families:
- afi: ipv4
safi: unicast
routes:
- dest: 192.0.2.48/28
next_hops:
- forward_router_address: 192.0.2.12
description: "DEV"
dest_vrf: test_1
- dest: 192.0.2.80/28
next_hops:
- interface: FastEthernet0/0/0/2
forward_router_address: 192.0.2.14
dest_vrf: test_1
track: ip_sla_2
vrflabel: 124
state: merged
register: result
- name: Assert that before dicts were correctly generated
assert:
that: "{{ merged['before'] | symmetric_difference(result['before']) |length == 0 }}"
- name: Assert that correct set of commands were generated
assert:
that:
- "{{ merged['commands'] | symmetric_difference(result['commands']) |length == 0 }}"
- set_fact:
diff: "{{ merged['after'] | symmetric_difference(result['after']) }}"
- name: Assert that after dicts was correctly generated
assert:
that:
- "{{ merged['after'] | symmetric_difference(result['after']) |length == 0 }}"
- name: Merge the provided configuration with the existing running configuration (IDEMPOTENT)
iosxr_static_routes: *merged
register: result
- name: Assert that the previous task was idempotent
assert:
that:
- "result['changed'] == false"
- "result.commands|length == 0"
- name: Assert that before dicts were correctly generated
assert:
that:
- "{{ merged['after'] | symmetric_difference(result['before']) |length == 0 }}"
- name: Update existing configuration using merged
iosxr_static_routes: &merged_update
config:
- vrf: DEV_SITE
address_families:
- afi: ipv4
safi: unicast
routes:
- dest: 192.0.2.48/28
next_hops:
- forward_router_address: 192.0.2.12
vrflabel: 2301
dest_vrf: test_1
- dest: 192.0.2.80/28
next_hops:
- interface: FastEthernet0/0/0/2
forward_router_address: 192.0.2.14
dest_vrf: test_1
description: "rt_test_1"
register: result
- name: Assert that before dicts were correctly generated
assert:
that: "{{ merged['after'] | symmetric_difference(result['before']) |length == 0 }}"
- name: Assert that correct set of commands were generated
assert:
that:
- "{{ merged['update_commands'] | symmetric_difference(result['commands']) |length == 0 }}"
- name: Assert that after dicts were correctly generated
assert:
that: "{{ merged['update_after'] | symmetric_difference(result['after']) |length == 0 }}"
- name: Update existing static_routes configuration using merged (IDEMPOTENT)
iosxr_static_routes: *merged_update
register: result
- name: Assert that the previous task was idempotent
assert:
that:
- "result['changed'] == false"
- "result.commands|length == 0"
always:
- include_tasks: _remove_config.yaml

@ -0,0 +1,66 @@
---
- debug:
msg: "START iosxr_static_routes overridden integration tests on connection={{ ansible_connection }}"
- include_tasks: _remove_config.yaml
- include_tasks: _populate_config.yaml
- block:
- name: Overridde all static routes configuration with provided configuration
iosxr_static_routes: &overridden
config:
- vrf: DEV_NEW
address_families:
- afi: ipv4
safi: unicast
routes:
- dest: 192.0.2.48/28
next_hops:
- forward_router_address: 192.0.2.15
interface: FastEthernet0/0/0/3
description: "DEV1"
- afi: ipv6
safi: unicast
routes:
- dest: 2001:db8:3000::/36
next_hops:
- interface: FastEthernet0/0/0/4
forward_router_address: 2001:db8:2000:2::2
description: "PROD1"
track: ip_sla_1
state: overridden
register: result
- name: Assert that correct set of commands were generated
assert:
that:
- "{{ overridden['commands'] | symmetric_difference(result['commands']) |length == 0 }}"
- name: Assert that before dicts are correctly generated
assert:
that:
- "{{ replaced['before'] | symmetric_difference(result['before']) |length == 0 }}"
- name: Assert that after dict is correctly generated
assert:
that:
- "{{ overridden['after'] | symmetric_difference(result['after']) |length == 0 }}"
- name: Overridde all static routes configuration with given configuration (IDEMPOTENT)
iosxr_static_routes: *overridden
register: result
- name: Assert that task was idempotent
assert:
that:
- "result['changed'] == false"
- "result.commands|length == 0"
- name: Assert that before dict is correctly generated
assert:
that:
- "{{ overridden['after'] | symmetric_difference(result['before']) |length == 0 }}"
always:
- include_tasks: _remove_config.yaml

@ -0,0 +1,15 @@
---
- debug:
msg: "START iosxr_static_routes parsed integration tests on connection={{ ansible_connection }}"
- block:
- name: Use parsed state to convert externally supplied device specific static routes commands to structured format
iosxr_static_routes:
running_config: "{{ lookup('file', '../../fixtures/parsed.cfg') }}"
state: parsed
register: result
- assert:
that: "{{ merged['after'] | symmetric_difference(result['parsed']) |length==0 }}"

@ -0,0 +1,76 @@
---
- debug:
msg: "START iosxr_static_routes rendered integration tests on connection={{ ansible_connection }}"
- include_tasks: _remove_config.yaml
- block:
- name: Use rendered state to convert task input to device specific commands
iosxr_static_routes:
config:
- vrf: DEV_SITE
address_families:
- afi: ipv4
safi: unicast
routes:
- dest: 192.0.2.48/28
next_hops:
- forward_router_address: 192.0.2.12
description: "DEV"
dest_vrf: test_1
- dest: 192.0.2.80/28
next_hops:
- interface: FastEthernet0/0/0/2
forward_router_address: 192.0.2.14
dest_vrf: test_1
track: ip_sla_2
vrflabel: 124
- address_families:
- afi: ipv4
safi: unicast
routes:
- dest: 192.0.2.16/28
next_hops:
- forward_router_address: 192.0.2.10
interface: FastEthernet0/0/0/1
description: "LAB"
metric: 120
tag: 10
- interface: FastEthernet0/0/0/5
track: ip_sla_1
- dest: 192.0.2.32/28
next_hops:
- forward_router_address: 192.0.2.11
admin_distance: 100
- afi: ipv6
safi: unicast
routes:
- dest: 2001:db8:1000::/36
next_hops:
- interface: FastEthernet0/0/0/7
description: "DC"
- interface: FastEthernet0/0/0/8
forward_router_address: 2001:db8:2000:2::1
state: rendered
register: result
- assert:
that: "{{ merged['commands'] | symmetric_difference(result['rendered']) |length==0 }}"
- name: Gather static routes facts from the device and assert that its empty
iosxr_static_routes:
state: gathered
register: result
- name: Make sure that rendered task actually did not make any changes to the device
assert:
that: "{{ result['gathered'] == [] }}"
always:
- include_tasks: _remove_config.yaml

@ -0,0 +1,58 @@
---
- debug:
msg: "START iosxr_static_routes replaced integration tests on connection={{ ansible_connection }}"
- include_tasks: _remove_config.yaml
- include_tasks: _populate_config.yaml
- block:
- name: Replace device configurations of static routes with provided configurations
iosxr_static_routes: &replaced
config:
- vrf: DEV_SITE
address_families:
- afi: ipv4
safi: unicast
routes:
- dest: 192.0.2.48/28
next_hops:
- forward_router_address: 192.0.2.15
interface: FastEthernet0/0/0/3
description: "DEV_NEW"
dest_vrf: dev_test_2
state: replaced
register: result
- name: Assert that correct set of commands were generated
assert:
that:
- "{{ replaced['commands'] | symmetric_difference(result['commands']) |length == 0 }}"
- name: Assert that before dicts are correctly generated
assert:
that:
- "{{ replaced['before'] | symmetric_difference(result['before']) |length == 0 }}"
- name: Assert that after dict is correctly generated
assert:
that:
- "{{ replaced['after'] | symmetric_difference(result['after']) |length == 0 }}"
- name: Replace device configurations of listed vrfs/global entry with provided configuration (IDEMPOTENT)
iosxr_static_routes: *replaced
register: result
- name: Assert that task was idempotent
assert:
that:
- "result['changed'] == false"
- "result.commands|length == 0"
- name: Assert that before dict is correctly generated
assert:
that:
- "{{ replaced['after'] | symmetric_difference(result['before']) |length == 0 }}"
always:
- include_tasks: _remove_config.yaml

@ -0,0 +1,73 @@
---
- debug:
msg: "START iosxr_static_routes round trip integration tests on connection={{ ansible_connection }}"
- block:
- include_tasks: _remove_config.yaml
- name: Apply the provided configuration (base config)
iosxr_static_routes:
config:
- address_families:
- afi: ipv4
safi: unicast
routes:
- dest: 192.0.2.48/28
next_hops:
- forward_router_address: 192.0.2.15
admin_distance: 105
track: ip_sla_2
- vrf: DEV_SITE
address_families:
- afi: ipv6
safi: unicast
routes:
- dest: 2001:db8:3000::/36
next_hops:
- forward_router_address: 2001:db8:2000:2::2
interface: FastEthernet0/0/0/11
description: PROD1
state: merged
register: base_config
- name: Gather interfaces facts
iosxr_facts:
gather_subset:
- "!all"
- "!min"
gather_network_resources:
- static_routes
- name: Apply the provided configuration (config to be reverted)
iosxr_static_routes:
config:
- vrf: TEST_SITE
address_families:
- afi: ipv4
safi: unicast
routes:
- dest: 192.0.2.80/28
next_hops:
- forward_router_address: 192.0.2.12
interface: FastEthernet0/0/0/3
description: "DEV_MOVED"
dest_vrf: dev_moved
state: overridden
register: result
- name: Assert that changes were applied
assert:
that: "{{ round_trip['after'] | symmetric_difference(result['after']) |length == 0 }}"
- name: Revert back to base config using facts round trip
iosxr_static_routes:
config: "{{ ansible_facts['network_resources']['static_routes'] }}"
state: overridden
register: revert
- name: Assert that config was reverted
assert:
that: "{{ base_config['after'] | symmetric_difference(revert['after']) |length == 0 }}"
always:
- include_tasks: _remove_config.yaml

@ -0,0 +1,281 @@
---
merged:
before: []
commands:
- router static
- address-family ipv4 unicast
- 192.0.2.16/28 192.0.2.10 FastEthernet0/0/0/1 description LAB metric 120 tag 10
- 192.0.2.16/28 FastEthernet0/0/0/5 track ip_sla_1
- 192.0.2.32/28 192.0.2.11 100
- address-family ipv6 unicast
- 2001:db8:1000::/36 FastEthernet0/0/0/7 description DC
- 2001:db8:1000::/36 2001:db8:2000:2::1 FastEthernet0/0/0/8
- vrf DEV_SITE
- address-family ipv4 unicast
- 192.0.2.48/28 vrf test_1 192.0.2.12 description DEV
- 192.0.2.80/28 vrf test_1 192.0.2.14 FastEthernet0/0/0/2 track ip_sla_2 vrflabel 124
update_commands:
- router static
- vrf DEV_SITE
- address-family ipv4 unicast
- 192.0.2.48/28 vrf test_1 192.0.2.12 description DEV vrflabel 2301
- 192.0.2.80/28 vrf test_1 192.0.2.14 FastEthernet0/0/0/2 description rt_test_1 track ip_sla_2 vrflabel 124
after:
- address_families:
- afi: ipv4
routes:
- dest: 192.0.2.16/28
next_hops:
- description: LAB
forward_router_address: 192.0.2.10
interface: FastEthernet0/0/0/1
metric: 120
tag: 10
- interface: FastEthernet0/0/0/5
track: ip_sla_1
- dest: 192.0.2.32/28
next_hops:
- admin_distance: 100
forward_router_address: 192.0.2.11
safi: unicast
- afi: ipv6
routes:
- dest: 2001:db8:1000::/36
next_hops:
- description: DC
interface: FastEthernet0/0/0/7
- forward_router_address: 2001:db8:2000:2::1
interface: FastEthernet0/0/0/8
safi: unicast
- address_families:
- afi: ipv4
routes:
- dest: 192.0.2.48/28
next_hops:
- description: DEV
dest_vrf: test_1
forward_router_address: 192.0.2.12
- dest: 192.0.2.80/28
next_hops:
- dest_vrf: test_1
forward_router_address: 192.0.2.14
interface: FastEthernet0/0/0/2
track: ip_sla_2
vrflabel: 124
safi: unicast
vrf: DEV_SITE
update_after:
- address_families:
- afi: ipv4
routes:
- dest: 192.0.2.16/28
next_hops:
- description: LAB
forward_router_address: 192.0.2.10
interface: FastEthernet0/0/0/1
metric: 120
tag: 10
- interface: FastEthernet0/0/0/5
track: ip_sla_1
- dest: 192.0.2.32/28
next_hops:
- admin_distance: 100
forward_router_address: 192.0.2.11
safi: unicast
- afi: ipv6
routes:
- dest: 2001:db8:1000::/36
next_hops:
- description: DC
interface: FastEthernet0/0/0/7
- forward_router_address: 2001:db8:2000:2::1
interface: FastEthernet0/0/0/8
safi: unicast
- address_families:
- afi: ipv4
routes:
- dest: 192.0.2.48/28
next_hops:
- description: DEV
dest_vrf: test_1
forward_router_address: 192.0.2.12
vrflabel: 2301
- dest: 192.0.2.80/28
next_hops:
- dest_vrf: test_1
forward_router_address: 192.0.2.14
interface: FastEthernet0/0/0/2
track: ip_sla_2
vrflabel: 124
description: rt_test_1
safi: unicast
vrf: DEV_SITE
replaced:
before:
- address_families:
- afi: ipv4
routes:
- dest: 192.0.2.16/28
next_hops:
- description: LAB
forward_router_address: 192.0.2.10
interface: FastEthernet0/0/0/1
metric: 120
tag: 10
- interface: FastEthernet0/0/0/5
track: ip_sla_1
- dest: 192.0.2.32/28
next_hops:
- admin_distance: 100
forward_router_address: 192.0.2.11
safi: unicast
- afi: ipv6
routes:
- dest: 2001:db8:1000::/36
next_hops:
- description: DC
interface: FastEthernet0/0/0/7
- forward_router_address: 2001:db8:2000:2::1
interface: FastEthernet0/0/0/8
safi: unicast
- address_families:
- afi: ipv4
routes:
- dest: 192.0.2.48/28
next_hops:
- description: DEV
dest_vrf: test_1
forward_router_address: 192.0.2.12
- forward_router_address: 192.0.3.24
interface: GigabitEthernet0/0/0/1
vrflabel: 2302
- dest: 192.0.2.80/28
next_hops:
- dest_vrf: test_1
forward_router_address: 192.0.2.14
interface: FastEthernet0/0/0/2
track: ip_sla_2
vrflabel: 124
safi: unicast
vrf: DEV_SITE
commands:
- router static
- vrf DEV_SITE
- address-family ipv4 unicast
- no 192.0.2.48/28 192.0.3.24 GigabitEthernet0/0/0/1
- no 192.0.2.48/28 vrf test_1 192.0.2.12
- 192.0.2.48/28 vrf dev_test_2 192.0.2.15 FastEthernet0/0/0/3 description DEV_NEW
after:
- address_families:
- afi: ipv4
routes:
- dest: 192.0.2.16/28
next_hops:
- description: LAB
forward_router_address: 192.0.2.10
interface: FastEthernet0/0/0/1
metric: 120
tag: 10
- interface: FastEthernet0/0/0/5
track: ip_sla_1
- dest: 192.0.2.32/28
next_hops:
- admin_distance: 100
forward_router_address: 192.0.2.11
safi: unicast
- afi: ipv6
routes:
- dest: 2001:db8:1000::/36
next_hops:
- description: DC
interface: FastEthernet0/0/0/7
- forward_router_address: 2001:db8:2000:2::1
interface: FastEthernet0/0/0/8
safi: unicast
- address_families:
- afi: ipv4
routes:
- dest: 192.0.2.48/28
next_hops:
- forward_router_address: 192.0.2.15
interface: FastEthernet0/0/0/3
description: "DEV_NEW"
dest_vrf: dev_test_2
- dest: 192.0.2.80/28
next_hops:
- dest_vrf: test_1
forward_router_address: 192.0.2.14
interface: FastEthernet0/0/0/2
track: ip_sla_2
vrflabel: 124
safi: unicast
vrf: DEV_SITE
overridden:
commands:
- router static
- no vrf DEV_SITE
- no address-family ipv4 unicast
- no address-family ipv6 unicast
- vrf DEV_NEW
- address-family ipv4 unicast
- 192.0.2.48/28 192.0.2.15 FastEthernet0/0/0/3 description DEV1
- address-family ipv6 unicast
- 2001:db8:3000::/36 2001:db8:2000:2::2 FastEthernet0/0/0/4 description PROD1 track ip_sla_1
after:
- vrf: DEV_NEW
address_families:
- afi: ipv4
safi: unicast
routes:
- dest: 192.0.2.48/28
next_hops:
- forward_router_address: 192.0.2.15
interface: FastEthernet0/0/0/3
description: "DEV1"
- afi: ipv6
safi: unicast
routes:
- dest: 2001:db8:3000::/36
next_hops:
- interface: FastEthernet0/0/0/4
forward_router_address: 2001:db8:2000:2::2
description: "PROD1"
track: ip_sla_1
deleted:
commands:
- no router static
after: []
round_trip:
after:
- vrf: TEST_SITE
address_families:
- afi: ipv4
safi: unicast
routes:
- dest: 192.0.2.80/28
next_hops:
- forward_router_address: 192.0.2.12
interface: FastEthernet0/0/0/3
description: "DEV_MOVED"
dest_vrf: dev_moved

@ -0,0 +1,18 @@
Fri Nov 29 21:10:41.896 UTC
router static
address-family ipv4 unicast
192.0.2.16/28 FastEthernet0/0/0/1 192.0.2.10 tag 10 description LAB metric 120
192.0.2.16/28 FastEthernet0/0/0/5 track ip_sla_1
192.0.2.32/28 192.0.2.11 100
!
address-family ipv6 unicast
2001:db8:1000::/36 FastEthernet0/0/0/7 description DC
2001:db8:1000::/36 FastEthernet0/0/0/8 2001:db8:2000:2::1
!
vrf DEV_SITE
address-family ipv4 unicast
192.0.2.48/28 vrf test_1 192.0.2.12 description DEV
192.0.2.80/28 vrf test_1 FastEthernet0/0/0/2 192.0.2.14 vrflabel 124 track ip_sla_2
!
!
!

@ -0,0 +1,395 @@
#
# (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.iosxr import iosxr_static_routes
from units.modules.utils import set_module_args
from .iosxr_module import TestIosxrModule, load_fixture
class TestIosxrStaticRoutesModule(TestIosxrModule):
module = iosxr_static_routes
def setUp(self):
super(TestIosxrStaticRoutesModule, 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_execute_show_command = patch(
"ansible.module_utils.network.iosxr.facts.static_routes.static_routes.Static_routesFacts.get_device_data"
)
self.execute_show_command = self.mock_execute_show_command.start()
def tearDown(self):
super(TestIosxrStaticRoutesModule, self).tearDown()
self.mock_get_resource_connection_config.stop()
self.mock_get_resource_connection_facts.stop()
self.mock_get_config.stop()
self.mock_load_config.stop()
self.mock_execute_show_command.stop()
def load_fixtures(self, commands=None):
def load_from_file(*args, **kwargs):
return load_fixture("iosxr_static_routes_config.cfg")
self.execute_show_command.side_effect = load_from_file
def test_iosxr_static_routes_merged(self):
set_module_args(
dict(
config=[
dict(
vrf="dev_site",
address_families=[
dict(
afi="ipv6",
safi="unicast",
routes=[
dict(
dest="1200:10::/64",
next_hops=[
dict(
interface="GigabitEthernet0/0/0/1",
admin_distance=55,
)
],
)
],
)
],
)
],
state="merged",
)
)
commands = [
"router static",
"vrf dev_site",
"address-family ipv6 unicast",
"1200:10::/64 GigabitEthernet0/0/0/1 55",
]
self.execute_module(changed=True, commands=commands)
def test_iosxr_static_routes_merged_idempotent(self):
set_module_args(
dict(
config=[
dict(
vrf="DEV_SITE",
address_families=[
dict(
afi="ipv4",
safi="unicast",
routes=[
dict(
dest="192.0.2.48/28",
next_hops=[
dict(
interface="192.0.2.12",
description="DEV",
dest_vrf="test_1",
)
],
)
],
)
],
)
],
state="merged",
)
)
self.execute_module(changed=False, commands=[])
def test_iosxr_static_routes_default(self):
set_module_args(
dict(
config=[
dict(
address_families=[
dict(
afi="ipv4",
safi="unicast",
routes=[
dict(
dest="192.168.1.0/24",
next_hops=[
dict(
interface="GigabitEthernet0/0/0/2",
track="ip_sla_2",
vrflabel=1200,
)
],
)
],
)
]
)
]
)
)
commands = [
"router static",
"address-family ipv4 unicast",
"192.168.1.0/24 GigabitEthernet0/0/0/2 track ip_sla_2 vrflabel 1200",
]
self.execute_module(changed=True, commands=commands)
def test_iosxr_static_routes_default_idempotent(self):
set_module_args(
dict(
config=[
dict(
address_families=[
dict(
afi="ipv4",
safi="unicast",
routes=[
dict(
dest="192.0.2.32/28",
next_hops=[
dict(
forward_router_address="192.0.2.11",
admin_distance=100,
)
],
)
],
)
]
)
]
)
)
self.execute_module(changed=False, commands=[])
def test_iosxr_static_routes_replaced(self):
set_module_args(
dict(
config=[
dict(
address_families=[
dict(
afi="ipv4",
safi="unicast",
routes=[
dict(
dest="192.0.2.16/28",
next_hops=[
dict(
forward_router_address="192.0.2.11",
admin_distance=100,
)
],
)
],
)
]
)
],
state="replaced",
)
)
commands = [
"router static",
"address-family ipv4 unicast",
"no 192.0.2.16/28 192.0.2.10 FastEthernet0/0/0/1",
"no 192.0.2.16/28 FastEthernet0/0/0/5",
"192.0.2.16/28 192.0.2.11 100",
]
self.execute_module(changed=True, commands=commands)
def test_iosxr_static_routes_replaced_idempotent(self):
set_module_args(
dict(
config=[
dict(
address_families=[
dict(
afi="ipv4",
safi="unicast",
routes=[
dict(
dest="192.0.2.16/28",
next_hops=[
dict(
interface="FastEthernet0/0/0/5",
track="ip_sla_1",
),
dict(
interface="FastEthernet0/0/0/1",
forward_router_address="192.0.2.10",
tag=10,
description="LAB",
metric=120,
),
],
)
],
)
]
)
],
state="replaced",
)
)
self.execute_module(changed=False, commands=[])
def test_iosxr_static_routes_overridden(self):
set_module_args(
dict(
config=[
dict(
vrf="DEV_SITE_NEW",
address_families=[
dict(
afi="ipv4",
safi="unicast",
routes=[
dict(
dest="192.0.4.16/28",
next_hops=[
dict(
interface="FastEthernet0/0/0/5",
track="ip_sla_1",
),
dict(
interface="FastEthernet0/0/0/1",
forward_router_address="192.0.2.10",
tag=10,
description="LAB",
metric=120,
),
],
)
],
)
],
)
],
state="overridden",
)
)
commands = [
"router static",
"no address-family ipv4 unicast",
"no address-family ipv6 unicast",
"no vrf DEV_SITE",
"vrf DEV_SITE_NEW",
"address-family ipv4 unicast",
"192.0.4.16/28 192.0.2.10 FastEthernet0/0/0/1 description LAB metric 120 tag 10",
"192.0.4.16/28 FastEthernet0/0/0/5 track ip_sla_1",
]
self.execute_module(changed=True, commands=commands)
def test_iosxr_static_routes_deleted_next_hop(self):
set_module_args(
dict(
config=[
dict(
address_families=[
dict(
afi="ipv4",
safi="unicast",
routes=[
dict(
dest="192.0.2.16/28",
next_hops=[
dict(interface="FastEthernet0/0/0/5")
],
)
],
)
]
)
],
state="deleted",
)
)
commands = [
"router static",
"address-family ipv4 unicast",
"no 192.0.2.16/28 FastEthernet0/0/0/5",
]
self.execute_module(changed=True, commands=commands)
def test_iosxr_static_routes_deleted_dest(self):
set_module_args(
dict(
config=[
dict(
address_families=[
dict(
afi="ipv4",
safi="unicast",
routes=[dict(dest="192.0.2.16/28")],
)
]
)
],
state="deleted",
)
)
commands = ["router static", "address-family ipv4 unicast", "no 192.0.2.16/28"]
self.execute_module(changed=True, commands=commands)
def test_iosxr_static_routes_deleted_afi(self):
set_module_args(
dict(
config=[dict(address_families=[dict(afi="ipv4", safi="unicast")])],
state="deleted",
)
)
commands = [
"router static",
"no address-family ipv4 unicast",
]
self.execute_module(changed=True, commands=commands)
def test_iosxr_static_routes_deleted_vrf(self):
set_module_args(dict(config=[dict(vrf="DEV_SITE")], state="deleted"))
commands = [
"router static",
"no vrf DEV_SITE",
]
self.execute_module(changed=True, commands=commands)
def test_iosxr_static_routes_deleted_all(self):
set_module_args(dict(state="deleted"))
commands = [
"no router static",
]
self.execute_module(changed=True, commands=commands)
Loading…
Cancel
Save