summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorNilashish Chakraborty <nilashishchakraborty8@gmail.com>2020-02-27 14:31:00 +0530
committerGitHub <noreply@github.com>2020-02-27 14:31:00 +0530
commit3405ee1c0142b98258f4dcfe548049dbe39c61e3 (patch)
tree9218199b0b36c02efb2ecd54beffca4436a13e0a /lib
parent9afe87139a0916a42335d56c55ed3f3523e4168e (diff)
downloadansible-3405ee1c0142b98258f4dcfe548049dbe39c61e3.tar.gz
Add iosxr_static_routes RM (#65181)
Signed-off-by: NilashishC <nilashishchakraborty8@gmail.com>
Diffstat (limited to 'lib')
-rw-r--r--lib/ansible/module_utils/network/iosxr/argspec/static_routes/__init__.py0
-rw-r--r--lib/ansible/module_utils/network/iosxr/argspec/static_routes/static_routes.py121
-rw-r--r--lib/ansible/module_utils/network/iosxr/config/static_routes/__init__.py0
-rw-r--r--lib/ansible/module_utils/network/iosxr/config/static_routes/static_routes.py560
-rw-r--r--lib/ansible/module_utils/network/iosxr/facts/facts.py4
-rw-r--r--lib/ansible/module_utils/network/iosxr/facts/static_routes/__init__.py0
-rw-r--r--lib/ansible/module_utils/network/iosxr/facts/static_routes/static_routes.py176
-rw-r--r--lib/ansible/modules/network/iosxr/iosxr_facts.py2
-rw-r--r--lib/ansible/modules/network/iosxr/iosxr_static_routes.py966
9 files changed, 1827 insertions, 2 deletions
diff --git a/lib/ansible/module_utils/network/iosxr/argspec/static_routes/__init__.py b/lib/ansible/module_utils/network/iosxr/argspec/static_routes/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/lib/ansible/module_utils/network/iosxr/argspec/static_routes/__init__.py
diff --git a/lib/ansible/module_utils/network/iosxr/argspec/static_routes/static_routes.py b/lib/ansible/module_utils/network/iosxr/argspec/static_routes/static_routes.py
new file mode 100644
index 0000000000..030515b877
--- /dev/null
+++ b/lib/ansible/module_utils/network/iosxr/argspec/static_routes/static_routes.py
@@ -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
diff --git a/lib/ansible/module_utils/network/iosxr/config/static_routes/__init__.py b/lib/ansible/module_utils/network/iosxr/config/static_routes/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/lib/ansible/module_utils/network/iosxr/config/static_routes/__init__.py
diff --git a/lib/ansible/module_utils/network/iosxr/config/static_routes/static_routes.py b/lib/ansible/module_utils/network/iosxr/config/static_routes/static_routes.py
new file mode 100644
index 0000000000..1c843b9166
--- /dev/null
+++ b/lib/ansible/module_utils/network/iosxr/config/static_routes/static_routes.py
@@ -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
diff --git a/lib/ansible/module_utils/network/iosxr/facts/facts.py b/lib/ansible/module_utils/network/iosxr/facts/facts.py
index 312682f1ad..109a813a59 100644
--- a/lib/ansible/module_utils/network/iosxr/facts/facts.py
+++ b/lib/ansible/module_utils/network/iosxr/facts/facts.py
@@ -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
)
diff --git a/lib/ansible/module_utils/network/iosxr/facts/static_routes/__init__.py b/lib/ansible/module_utils/network/iosxr/facts/static_routes/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/lib/ansible/module_utils/network/iosxr/facts/static_routes/__init__.py
diff --git a/lib/ansible/module_utils/network/iosxr/facts/static_routes/static_routes.py b/lib/ansible/module_utils/network/iosxr/facts/static_routes/static_routes.py
new file mode 100644
index 0000000000..3485cc7813
--- /dev/null
+++ b/lib/ansible/module_utils/network/iosxr/facts/static_routes/static_routes.py
@@ -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
diff --git a/lib/ansible/modules/network/iosxr/iosxr_facts.py b/lib/ansible/modules/network/iosxr/iosxr_facts.py
index 244cb61781..797d58dcd0 100644
--- a/lib/ansible/modules/network/iosxr/iosxr_facts.py
+++ b/lib/ansible/modules/network/iosxr/iosxr_facts.py
@@ -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"
"""
diff --git a/lib/ansible/modules/network/iosxr/iosxr_static_routes.py b/lib/ansible/modules/network/iosxr/iosxr_static_routes.py
new file mode 100644
index 0000000000..4f75513fd5
--- /dev/null
+++ b/lib/ansible/modules/network/iosxr/iosxr_static_routes.py
@@ -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()