summaryrefslogtreecommitdiffstats
path: root/ansible_collections/cisco/nxos/plugins/modules
diff options
context:
space:
mode:
Diffstat (limited to 'ansible_collections/cisco/nxos/plugins/modules')
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/__init__.py0
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_aaa_server.py336
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_aaa_server_host.py370
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_acl_interfaces.py440
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_acls.py915
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_banner.py224
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_bfd_global.py333
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_bfd_interfaces.py302
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_bgp.py761
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_bgp_address_family.py1031
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_bgp_af.py877
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_bgp_global.py1694
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_bgp_neighbor.py569
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_bgp_neighbor_address_family.py1155
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_bgp_neighbor_af.py781
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_command.py231
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_config.py579
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_devicealias.py550
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_evpn_global.py103
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_evpn_vni.py299
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_facts.py268
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_feature.py308
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_file_copy.py497
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_gir.py342
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_gir_profile_management.py214
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_hostname.py229
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_hsrp.py506
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_hsrp_interfaces.py264
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_igmp.py162
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_igmp_interface.py650
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_igmp_snooping.py319
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_install_os.py594
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_interfaces.py455
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_l2_interfaces.py408
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_l3_interfaces.py411
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_lacp.py276
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_lacp_interfaces.py381
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_lag_interfaces.py368
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_lldp_global.py345
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_lldp_interfaces.py263
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_logging.py940
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_logging_global.py735
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_ntp.py446
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_ntp_auth.py336
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_ntp_global.py736
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_ntp_options.py173
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_nxapi.py424
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_ospf_interfaces.py1455
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_ospfv2.py1984
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_ospfv3.py1702
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_overlay_global.py194
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_pim.py216
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_pim_interface.py604
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_pim_rp_address.py248
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_ping.py255
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_prefix_lists.py842
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_reboot.py89
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_rollback.py130
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_route_maps.py1648
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_rpm.py396
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_snapshot.py413
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_snmp_community.py254
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_snmp_contact.py151
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_snmp_host.py513
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_snmp_location.py156
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_snmp_server.py1480
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_snmp_traps.py319
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_snmp_user.py412
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_static_routes.py477
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_system.py399
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_telemetry.py339
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_udld.py254
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_udld_interface.py301
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_user.py473
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_vlans.py439
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_vpc.py470
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_vpc_interface.py342
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_vrf.py616
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_vrf_af.py274
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_vrf_interface.py267
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_vrrp.py432
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_vsan.py354
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_vtp_domain.py215
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_vtp_password.py277
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_vtp_version.py210
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_vxlan_vtep.py458
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_vxlan_vtep_vni.py452
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_zone_zoneset.py888
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/storage/__init__.py0
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/storage/nxos_devicealias.py550
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/storage/nxos_vsan.py354
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/storage/nxos_zone_zoneset.py888
92 files changed, 45790 insertions, 0 deletions
diff --git a/ansible_collections/cisco/nxos/plugins/modules/__init__.py b/ansible_collections/cisco/nxos/plugins/modules/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/__init__.py
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_aaa_server.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_aaa_server.py
new file mode 100644
index 00000000..9103f931
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_aaa_server.py
@@ -0,0 +1,336 @@
+#!/usr/bin/python
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_aaa_server
+extends_documentation_fragment:
+- cisco.nxos.nxos
+short_description: Manages AAA server global configuration.
+description:
+- Manages AAA server global configuration
+version_added: 1.0.0
+author:
+- Jason Edelman (@jedelman8)
+notes:
+- Tested against NXOSv 7.3.(0)D1(1) on VIRL
+- Limited Support for Cisco MDS
+- The server_type parameter is always required.
+- If encrypt_type is not supplied, the global AAA server key will be stored as encrypted
+ (type 7).
+- Changes to the global AAA server key with encrypt_type=0 are not idempotent.
+- state=default will set the supplied parameters to their default values. The parameters
+ that you want to default must also be set to default. If global_key=default, the
+ global key will be removed.
+options:
+ server_type:
+ description:
+ - The server type is either radius or tacacs.
+ required: true
+ choices:
+ - radius
+ - tacacs
+ type: str
+ global_key:
+ description:
+ - Global AAA shared secret or keyword 'default'.
+ type: str
+ encrypt_type:
+ description:
+ - The state of encryption applied to the entered global key. O clear text, 7 encrypted.
+ Type-6 encryption is not supported.
+ choices:
+ - '0'
+ - '7'
+ type: str
+ deadtime:
+ description:
+ - Duration for which a non-reachable AAA server is skipped, in minutes or keyword
+ 'default. Range is 1-1440. Device default is 0.
+ type: str
+ server_timeout:
+ description:
+ - Global AAA server timeout period, in seconds or keyword 'default. Range is 1-60.
+ Device default is 5.
+ type: str
+ directed_request:
+ description:
+ - Enables direct authentication requests to AAA server or keyword 'default' Device
+ default is disabled.
+ choices:
+ - enabled
+ - disabled
+ - default
+ type: str
+ state:
+ description:
+ - Manage the state of the resource.
+ default: present
+ choices:
+ - present
+ - default
+ type: str
+"""
+
+EXAMPLES = """
+# Radius Server Basic settings
+- name: Radius Server Basic settings
+ cisco.nxos.nxos_aaa_server:
+ server_type: radius
+ server_timeout: 9
+ deadtime: 20
+ directed_request: enabled
+
+# Tacacs Server Basic settings
+- name: Tacacs Server Basic settings
+ cisco.nxos.nxos_aaa_server:
+ server_type: tacacs
+ server_timeout: 8
+ deadtime: 19
+ directed_request: disabled
+
+# Setting Global Key
+- name: AAA Server Global Key
+ cisco.nxos.nxos_aaa_server:
+ server_type: radius
+ global_key: test_key
+"""
+
+RETURN = """
+commands:
+ description: command sent to the device
+ returned: always
+ type: list
+ sample: ["radius-server deadtime 22", "radius-server timeout 11",
+ "radius-server directed-request"]
+"""
+import re
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ load_config,
+ run_commands,
+)
+
+
+PARAM_TO_DEFAULT_KEYMAP = {
+ "server_timeout": "5",
+ "deadtime": "0",
+ "directed_request": "disabled",
+}
+
+
+def execute_show_command(command, module):
+ command = {"command": command, "output": "text"}
+
+ return run_commands(module, command)
+
+
+def flatten_list(command_lists):
+ flat_command_list = []
+ for command in command_lists:
+ if isinstance(command, list):
+ flat_command_list.extend(command)
+ else:
+ flat_command_list.append(command)
+ return flat_command_list
+
+
+def get_aaa_server_info(server_type, module):
+ aaa_server_info = {}
+ server_command = "show {0}-server".format(server_type)
+ request_command = "show {0}-server directed-request".format(server_type)
+ global_key_command = "show run | sec {0}".format(server_type)
+ aaa_regex = r".*{0}-server\skey\s\d\s+(?P<key>\S+).*".format(server_type)
+
+ server_body = execute_show_command(server_command, module)[0]
+
+ split_server = server_body.splitlines()
+
+ for line in split_server:
+ if line.startswith("timeout"):
+ aaa_server_info["server_timeout"] = line.split(":")[1]
+
+ elif line.startswith("deadtime"):
+ aaa_server_info["deadtime"] = line.split(":")[1]
+
+ request_body = execute_show_command(request_command, module)[0]
+
+ if bool(request_body):
+ aaa_server_info["directed_request"] = request_body.replace("\n", "")
+ else:
+ aaa_server_info["directed_request"] = "disabled"
+
+ key_body = execute_show_command(global_key_command, module)[0]
+
+ try:
+ match_global_key = re.match(aaa_regex, key_body, re.DOTALL)
+ group_key = match_global_key.groupdict()
+ aaa_server_info["global_key"] = group_key["key"].replace('"', "")
+ except (AttributeError, TypeError):
+ aaa_server_info["global_key"] = None
+
+ return aaa_server_info
+
+
+def config_aaa_server(params, server_type):
+ cmds = []
+
+ deadtime = params.get("deadtime")
+ server_timeout = params.get("server_timeout")
+ directed_request = params.get("directed_request")
+ encrypt_type = params.get("encrypt_type", "7")
+ global_key = params.get("global_key")
+
+ if deadtime is not None:
+ cmds.append("{0}-server deadtime {1}".format(server_type, deadtime))
+
+ if server_timeout is not None:
+ cmds.append("{0}-server timeout {1}".format(server_type, server_timeout))
+
+ if directed_request is not None:
+ if directed_request == "enabled":
+ cmds.append("{0}-server directed-request".format(server_type))
+ elif directed_request == "disabled":
+ cmds.append("no {0}-server directed-request".format(server_type))
+
+ if global_key is not None:
+ cmds.append("{0}-server key {1} {2}".format(server_type, encrypt_type, global_key))
+
+ return cmds
+
+
+def default_aaa_server(existing, params, server_type):
+ cmds = []
+
+ deadtime = params.get("deadtime")
+ server_timeout = params.get("server_timeout")
+ directed_request = params.get("directed_request")
+ global_key = params.get("global_key")
+ existing_key = existing.get("global_key")
+
+ if deadtime is not None and existing.get("deadtime") != PARAM_TO_DEFAULT_KEYMAP["deadtime"]:
+ cmds.append("no {0}-server deadtime 1".format(server_type))
+
+ if (
+ server_timeout is not None
+ and existing.get("server_timeout") != PARAM_TO_DEFAULT_KEYMAP["server_timeout"]
+ ):
+ cmds.append("no {0}-server timeout 1".format(server_type))
+
+ if (
+ directed_request is not None
+ and existing.get("directed_request") != PARAM_TO_DEFAULT_KEYMAP["directed_request"]
+ ):
+ cmds.append("no {0}-server directed-request".format(server_type))
+
+ if global_key is not None and existing_key is not None:
+ cmds.append("no {0}-server key 7 {1}".format(server_type, existing_key))
+
+ return cmds
+
+
+def main():
+ argument_spec = dict(
+ server_type=dict(type="str", choices=["radius", "tacacs"], required=True),
+ global_key=dict(type="str", no_log=True),
+ encrypt_type=dict(type="str", choices=["0", "7"]),
+ deadtime=dict(type="str"),
+ server_timeout=dict(type="str"),
+ directed_request=dict(type="str", choices=["enabled", "disabled", "default"]),
+ state=dict(choices=["default", "present"], default="present"),
+ )
+
+ module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)
+
+ warnings = list()
+ results = {"changed": False, "commands": [], "warnings": warnings}
+
+ server_type = module.params["server_type"]
+ global_key = module.params["global_key"]
+ encrypt_type = module.params["encrypt_type"]
+ deadtime = module.params["deadtime"]
+ server_timeout = module.params["server_timeout"]
+ directed_request = module.params["directed_request"]
+ state = module.params["state"]
+
+ if encrypt_type and not global_key:
+ module.fail_json(msg="encrypt_type must be used with global_key.")
+
+ args = dict(
+ server_type=server_type,
+ global_key=global_key,
+ encrypt_type=encrypt_type,
+ deadtime=deadtime,
+ server_timeout=server_timeout,
+ directed_request=directed_request,
+ )
+
+ proposed = dict((k, v) for k, v in args.items() if v is not None)
+
+ existing = get_aaa_server_info(server_type, module)
+
+ commands = []
+ if state == "present":
+ if deadtime:
+ try:
+ if int(deadtime) < 0 or int(deadtime) > 1440:
+ raise ValueError
+ except ValueError:
+ module.fail_json(msg="deadtime must be an integer between 0 and 1440")
+
+ if server_timeout:
+ try:
+ if int(server_timeout) < 1 or int(server_timeout) > 60:
+ raise ValueError
+ except ValueError:
+ module.fail_json(msg="server_timeout must be an integer between 1 and 60")
+
+ delta = dict(set(proposed.items()).difference(existing.items()))
+ if delta:
+ command = config_aaa_server(delta, server_type)
+ if command:
+ commands.append(command)
+
+ elif state == "default":
+ for key, value in proposed.items():
+ if key != "server_type" and value != "default":
+ module.fail_json(msg='Parameters must be set to "default"' "when state=default")
+ command = default_aaa_server(existing, proposed, server_type)
+ if command:
+ commands.append(command)
+
+ cmds = flatten_list(commands)
+ if cmds:
+ results["changed"] = True
+ if not module.check_mode:
+ load_config(module, cmds)
+ if "configure" in cmds:
+ cmds.pop(0)
+ results["commands"] = cmds
+
+ module.exit_json(**results)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_aaa_server_host.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_aaa_server_host.py
new file mode 100644
index 00000000..d2f84f76
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_aaa_server_host.py
@@ -0,0 +1,370 @@
+#!/usr/bin/python
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_aaa_server_host
+extends_documentation_fragment:
+- cisco.nxos.nxos
+short_description: Manages AAA server host-specific configuration.
+description:
+- Manages AAA server host-specific configuration.
+version_added: 1.0.0
+author: Jason Edelman (@jedelman8)
+notes:
+- Tested against NXOSv 7.3.(0)D1(1) on VIRL
+- Limited Support for Cisco MDS
+- Changes to the host key (shared secret) are not idempotent for type 0.
+- If C(state=absent) removes the whole host configuration.
+options:
+ server_type:
+ description:
+ - The server type is either radius or tacacs.
+ required: true
+ choices:
+ - radius
+ - tacacs
+ type: str
+ address:
+ description:
+ - Address or name of the radius or tacacs host.
+ required: true
+ type: str
+ key:
+ description:
+ - Shared secret for the specified host or keyword 'default'.
+ type: str
+ encrypt_type:
+ description:
+ - The state of encryption applied to the entered key. O for clear text, 7 for
+ encrypted. Type-6 encryption is not supported.
+ choices:
+ - '0'
+ - '7'
+ type: str
+ host_timeout:
+ description:
+ - Timeout period for specified host, in seconds or keyword 'default. Range is
+ 1-60.
+ type: str
+ auth_port:
+ description:
+ - Alternate UDP port for RADIUS authentication or keyword 'default'.
+ type: str
+ acct_port:
+ description:
+ - Alternate UDP port for RADIUS accounting or keyword 'default'.
+ type: str
+ tacacs_port:
+ description:
+ - Alternate TCP port TACACS Server or keyword 'default'.
+ type: str
+ state:
+ description:
+ - Manage the state of the resource.
+ default: present
+ choices:
+ - present
+ - absent
+ type: str
+"""
+EXAMPLES = """
+# Radius Server Host Basic settings
+- name: Radius Server Host Basic settings
+ cisco.nxos.nxos_aaa_server_host:
+ state: present
+ server_type: radius
+ address: 1.2.3.4
+ acct_port: 2084
+ host_timeout: 10
+
+# Radius Server Host Key Configuration
+- name: Radius Server Host Key Configuration
+ cisco.nxos.nxos_aaa_server_host:
+ state: present
+ server_type: radius
+ address: 1.2.3.4
+ key: hello
+ encrypt_type: 7
+
+# TACACS Server Host Configuration
+- name: Tacacs Server Host Configuration
+ cisco.nxos.nxos_aaa_server_host:
+ state: present
+ server_type: tacacs
+ tacacs_port: 89
+ host_timeout: 10
+ address: 5.6.7.8
+
+"""
+
+RETURN = """
+proposed:
+ description: k/v pairs of parameters passed into module
+ returned: always
+ type: dict
+ sample: {"address": "1.2.3.4", "auth_port": "2084",
+ "host_timeout": "10", "server_type": "radius"}
+existing:
+ description:
+ - k/v pairs of existing configuration
+ returned: always
+ type: dict
+ sample: {}
+end_state:
+ description: k/v pairs of configuration after module execution
+ returned: always
+ type: dict
+ sample: {"address": "1.2.3.4", "auth_port": "2084",
+ "host_timeout": "10", "server_type": "radius"}
+updates:
+ description: command sent to the device
+ returned: always
+ type: list
+ sample: ["radius-server host 1.2.3.4 auth-port 2084 timeout 10"]
+changed:
+ description: check to see if a change was made on the device
+ returned: always
+ type: bool
+ sample: true
+"""
+import re
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ get_capabilities,
+ load_config,
+ run_commands,
+)
+
+
+def execute_show_command(command, module):
+ device_info = get_capabilities(module)
+ network_api = device_info.get("network_api", "nxapi")
+
+ if network_api == "cliconf":
+ cmds = [command]
+ body = run_commands(module, cmds)
+ elif network_api == "nxapi":
+ cmds = {"command": command, "output": "text"}
+ body = run_commands(module, cmds)
+
+ return body
+
+
+def flatten_list(command_lists):
+ flat_command_list = []
+ for command in command_lists:
+ if isinstance(command, list):
+ flat_command_list.extend(command)
+ else:
+ flat_command_list.append(command)
+ return flat_command_list
+
+
+def get_aaa_host_info(module, server_type, address):
+ aaa_host_info = {}
+ command = "show run | inc {0}-server.host.{1}".format(server_type, address)
+
+ body = execute_show_command(command, module)[0]
+ if body:
+ try:
+ if "radius" in body:
+ pattern = (
+ r"\S+ host \S+(?:\s+key 7\s+(\S+))?(?:\s+auth-port (\d+))?"
+ r"(?:\s+acct-port (\d+))?(?:\s+authentication)?"
+ r"(?:\s+accounting)?(?:\s+timeout (\d+))?"
+ )
+ match = re.search(pattern, body)
+ aaa_host_info["key"] = match.group(1)
+ if aaa_host_info["key"]:
+ aaa_host_info["key"] = aaa_host_info["key"].replace('"', "")
+ aaa_host_info["encrypt_type"] = "7"
+ aaa_host_info["auth_port"] = match.group(2)
+ aaa_host_info["acct_port"] = match.group(3)
+ aaa_host_info["host_timeout"] = match.group(4)
+ elif "tacacs" in body:
+ pattern = (
+ r"\S+ host \S+(?:\s+key 7\s+(\S+))?(?:\s+port (\d+))?(?:\s+timeout (\d+))?"
+ )
+ match = re.search(pattern, body)
+ aaa_host_info["key"] = match.group(1)
+ if aaa_host_info["key"]:
+ aaa_host_info["key"] = aaa_host_info["key"].replace('"', "")
+ aaa_host_info["encrypt_type"] = "7"
+ aaa_host_info["tacacs_port"] = match.group(2)
+ aaa_host_info["host_timeout"] = match.group(3)
+
+ aaa_host_info["server_type"] = server_type
+ aaa_host_info["address"] = address
+ except TypeError:
+ return {}
+ else:
+ return {}
+
+ return aaa_host_info
+
+
+def config_aaa_host(server_type, address, params, existing):
+ cmds = []
+ cmd_str = "{0}-server host {1}".format(server_type, address)
+ cmd_no_str = "no " + cmd_str
+
+ key = params.get("key")
+ enc_type = params.get("encrypt_type", "")
+
+ defval = False
+ nondef = False
+
+ if key:
+ if key != "default":
+ cmds.append(cmd_str + " key {0} {1}".format(enc_type, key))
+ else:
+ cmds.append(cmd_no_str + " key 7 {0}".format(existing.get("key")))
+
+ locdict = {
+ "auth_port": "auth-port",
+ "acct_port": "acct-port",
+ "tacacs_port": "port",
+ "host_timeout": "timeout",
+ }
+
+ # platform CLI needs the keywords in the following order
+ for key in ["auth_port", "acct_port", "tacacs_port", "host_timeout"]:
+ item = params.get(key)
+ if item:
+ if item != "default":
+ cmd_str += " {0} {1}".format(locdict.get(key), item)
+ nondef = True
+ else:
+ cmd_no_str += " {0} 1".format(locdict.get(key))
+ defval = True
+ if defval:
+ cmds.append(cmd_no_str)
+ if nondef or not existing:
+ cmds.append(cmd_str)
+
+ return cmds
+
+
+def main():
+ argument_spec = dict(
+ server_type=dict(choices=["radius", "tacacs"], required=True),
+ address=dict(type="str", required=True),
+ key=dict(type="str", no_log=False),
+ encrypt_type=dict(type="str", choices=["0", "7"]),
+ host_timeout=dict(type="str"),
+ auth_port=dict(type="str"),
+ acct_port=dict(type="str"),
+ tacacs_port=dict(type="str"),
+ state=dict(choices=["absent", "present"], default="present"),
+ )
+
+ module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)
+
+ warnings = list()
+
+ server_type = module.params["server_type"]
+ address = module.params["address"]
+ key = module.params["key"]
+ encrypt_type = module.params["encrypt_type"]
+ host_timeout = module.params["host_timeout"]
+ auth_port = module.params["auth_port"]
+ acct_port = module.params["acct_port"]
+ tacacs_port = module.params["tacacs_port"]
+ state = module.params["state"]
+
+ args = dict(
+ server_type=server_type,
+ address=address,
+ key=key,
+ encrypt_type=encrypt_type,
+ host_timeout=host_timeout,
+ auth_port=auth_port,
+ acct_port=acct_port,
+ tacacs_port=tacacs_port,
+ )
+
+ proposed = dict((k, v) for k, v in args.items() if v is not None)
+ changed = False
+
+ if encrypt_type and not key:
+ module.fail_json(msg="encrypt_type must be used with key")
+
+ if tacacs_port and server_type != "tacacs":
+ module.fail_json(msg="tacacs_port can only be used with server_type=tacacs")
+
+ if (auth_port or acct_port) and server_type != "radius":
+ module.fail_json(msg="auth_port and acct_port can only be used" "when server_type=radius")
+
+ existing = get_aaa_host_info(module, server_type, address)
+ end_state = existing
+
+ commands = []
+ delta = {}
+ if state == "present":
+ if not existing:
+ delta = proposed
+ else:
+ for key, value in proposed.items():
+ if key == "encrypt_type":
+ delta[key] = value
+ if value != existing.get(key):
+ if value != "default" or existing.get(key):
+ delta[key] = value
+
+ command = config_aaa_host(server_type, address, delta, existing)
+ if command:
+ commands.append(command)
+
+ elif state == "absent":
+ intersect = dict(set(proposed.items()).intersection(existing.items()))
+ if intersect.get("address") and intersect.get("server_type"):
+ command = "no {0}-server host {1}".format(
+ intersect.get("server_type"),
+ intersect.get("address"),
+ )
+ commands.append(command)
+
+ cmds = flatten_list(commands)
+ if cmds:
+ if module.check_mode:
+ module.exit_json(changed=True, commands=cmds)
+ else:
+ changed = True
+ load_config(module, cmds)
+ end_state = get_aaa_host_info(module, server_type, address)
+
+ results = {}
+ results["proposed"] = proposed
+ results["existing"] = existing
+ results["updates"] = cmds
+ results["changed"] = changed
+ results["warnings"] = warnings
+ results["end_state"] = end_state
+
+ module.exit_json(**results)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_acl_interfaces.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_acl_interfaces.py
new file mode 100644
index 00000000..e61746ff
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_acl_interfaces.py
@@ -0,0 +1,440 @@
+#!/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 nxos_acl_interfaces
+"""
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_acl_interfaces
+short_description: ACL interfaces resource module
+description: Add and remove Access Control Lists on interfaces in NX-OS platform
+version_added: 1.0.0
+author: Adharsh Srivats Rangarajan (@adharshsrivatsr)
+notes:
+- Tested against NX-OS 7.3.(0)D1(1) on VIRL
+- Unsupported for Cisco MDS
+options:
+ running_config:
+ description:
+ - This option is used only with state I(parsed).
+ - The value of this option should be the output received from the NX-OS device
+ by executing the command B(show running-config | section '^interface').
+ - The state I(parsed) reads the configuration from C(running_config) option and
+ transforms it into Ansible structured data as per the resource module's argspec
+ and the value is then returned in the I(parsed) key within the result.
+ type: str
+ config:
+ description: A list of interfaces to be configured with ACLs
+ type: list
+ elements: dict
+ suboptions:
+ name:
+ description: Name of the interface
+ type: str
+ required: true
+ access_groups:
+ description: List of address family indicators with ACLs to be configured
+ on the interface
+ type: list
+ elements: dict
+ suboptions:
+ afi:
+ description: Address Family Indicator of the ACLs to be configured
+ type: str
+ required: true
+ choices:
+ - ipv4
+ - ipv6
+ acls:
+ description: List of Access Control Lists for the interface
+ type: list
+ elements: dict
+ suboptions:
+ name:
+ description: Name of the ACL to be added/removed
+ type: str
+ required: true
+ direction:
+ description: Direction to be applied for the ACL
+ type: str
+ required: true
+ choices:
+ - in
+ - out
+ port:
+ description: Use ACL as port policy.
+ type: bool
+ state:
+ description: The state the configuration should be left in
+ type: str
+ choices:
+ - deleted
+ - gathered
+ - merged
+ - overridden
+ - rendered
+ - replaced
+ - parsed
+ default: merged
+
+"""
+EXAMPLES = """
+# Using merged
+
+# Before state:
+# ------------
+#
+
+- name: Merge ACL interfaces configuration
+ cisco.nxos.nxos_acl_interfaces:
+ config:
+ - name: Ethernet1/2
+ access_groups:
+ - afi: ipv6
+ acls:
+ - name: ACL1v6
+ direction: in
+
+ - name: Eth1/5
+ access_groups:
+ - afi: ipv4
+ acls:
+ - name: PortACL
+ direction: in
+ port: true
+
+ - name: ACL1v4
+ direction: out
+
+ - afi: ipv6
+ acls:
+ - name: ACL1v6
+ direction: in
+ state: merged
+
+# After state:
+# ------------
+# interface Ethernet1/2
+# ipv6 traffic-filter ACL1v6 in
+# interface Ethernet1/5
+# ip port access-group PortACL in
+# ip access-group ACL1v4 out
+# ipv6 traffic-filter ACL1v6 in
+
+# Using replaced
+
+# Before state:
+# ------------
+# interface Ethernet1/2
+# ipv6 traffic-filter ACL1v6 in
+# interface Ethernet1/5
+# ip port access-group PortACL in
+# ip access-group ACL1v4 out
+# ipv6 traffic-filter ACL1v6 in
+
+- name: Replace interface configuration with given configuration
+ cisco.nxos.nxos_acl_interfaces:
+ config:
+ - name: Eth1/5
+ access_groups:
+ - afi: ipv4
+ acls:
+ - name: NewACLv4
+ direction: out
+
+ - name: Ethernet1/3
+ access_groups:
+ - afi: ipv6
+ acls:
+ - name: NewACLv6
+ direction: in
+ port: true
+ state: replaced
+
+# After state:
+# ------------
+# interface Ethernet1/2
+# ipv6 traffic-filter ACL1v6 in
+# interface Ethernet1/3
+# ipv6 port traffic-filter NewACLv6 in
+# interface Ethernet1/5
+# ip access-group NewACLv4 out
+
+# Using overridden
+
+# Before state:
+# ------------
+# interface Ethernet1/2
+# ipv6 traffic-filter ACL1v6 in
+# interface Ethernet1/5
+# ip port access-group PortACL in
+# ip access-group ACL1v4 out
+# ipv6 traffic-filter ACL1v6 in
+
+- name: Override interface configuration with given configuration
+ cisco.nxos.nxos_acl_interfaces:
+ config:
+ - name: Ethernet1/3
+ access_groups:
+ - afi: ipv4
+ acls:
+ - name: ACL1v4
+ direction: out
+
+ - name: PortACL
+ port: true
+ direction: in
+ - afi: ipv6
+ acls:
+ - name: NewACLv6
+ direction: in
+ port: true
+ state: overridden
+
+# After state:
+# ------------
+# interface Ethernet1/3
+# ip access-group ACL1v4 out
+# ip port access-group PortACL in
+# ipv6 port traffic-filter NewACLv6 in
+
+# Using deleted to remove ACL config from specified interfaces
+
+# Before state:
+# -------------
+# interface Ethernet1/1
+# ip access-group ACL2v4 in
+# interface Ethernet1/2
+# ipv6 traffic-filter ACL1v6 in
+# interface Ethernet1/5
+# ip port access-group PortACL in
+# ip access-group ACL1v4 out
+# ipv6 traffic-filter ACL1v6 in
+
+- name: Delete ACL configuration on interfaces
+ cisco.nxos.nxos_acl_interfaces:
+ config:
+ - name: Ethernet1/5
+ - name: Ethernet1/2
+ state: deleted
+
+# After state:
+# -------------
+# interface Ethernet1/1
+# ip access-group ACL2v4 in
+# interface Ethernet1/2
+# interface Ethernet1/5
+
+# Using deleted to remove ACL config from all interfaces
+
+# Before state:
+# -------------
+# interface Ethernet1/1
+# ip access-group ACL2v4 in
+# interface Ethernet1/2
+# ipv6 traffic-filter ACL1v6 in
+# interface Ethernet1/5
+# ip port access-group PortACL in
+# ip access-group ACL1v4 out
+# ipv6 traffic-filter ACL1v6 in
+
+- name: Delete ACL configuration from all interfaces
+ cisco.nxos.nxos_acl_interfaces:
+ state: deleted
+
+# After state:
+# -------------
+# interface Ethernet1/1
+# interface Ethernet1/2
+# interface Ethernet1/5
+
+# Using parsed
+
+- name: Parse given configuration into structured format
+ cisco.nxos.nxos_acl_interfaces:
+ running_config: |
+ interface Ethernet1/2
+ ipv6 traffic-filter ACL1v6 in
+ interface Ethernet1/5
+ ipv6 traffic-filter ACL1v6 in
+ ip access-group ACL1v4 out
+ ip port access-group PortACL in
+ state: parsed
+
+# returns
+# parsed:
+# - name: Ethernet1/2
+# access_groups:
+# - afi: ipv6
+# acls:
+# - name: ACL1v6
+# direction: in
+# - name: Ethernet1/5
+# access_groups:
+# - afi: ipv4
+# acls:
+# - name: PortACL
+# direction: in
+# port: True
+# - name: ACL1v4
+# direction: out
+# - afi: ipv6
+# acls:
+# - name: ACL1v6
+# direction: in
+
+
+# Using gathered:
+
+# Before state:
+# ------------
+# interface Ethernet1/2
+# ipv6 traffic-filter ACL1v6 in
+# interface Ethernet1/5
+# ipv6 traffic-filter ACL1v6 in
+# ip access-group ACL1v4 out
+# ip port access-group PortACL in
+
+- name: Gather existing configuration from device
+ cisco.nxos.nxos_acl_interfaces:
+ config:
+ state: gathered
+
+# returns
+# gathered:
+# - name: Ethernet1/2
+# access_groups:
+# - afi: ipv6
+# acls:
+# - name: ACL1v6
+# direction: in
+# - name: Ethernet1/5
+# access_groups:
+# - afi: ipv4
+# acls:
+# - name: PortACL
+# direction: in
+# port: True
+# - name: ACL1v4
+# direction: out
+# - afi: ipv6
+# acls:
+# - name: ACL1v6
+# direction: in
+
+
+# Using rendered
+
+- name: Render required configuration to be pushed to the device
+ cisco.nxos.nxos_acl_interfaces:
+ config:
+ - name: Ethernet1/2
+ access_groups:
+ - afi: ipv6
+ acls:
+ - name: ACL1v6
+ direction: in
+
+ - name: Ethernet1/5
+ access_groups:
+ - afi: ipv4
+ acls:
+ - name: PortACL
+ direction: in
+ port: true
+ - name: ACL1v4
+ direction: out
+ - afi: ipv6
+ acls:
+ - name: ACL1v6
+ direction: in
+ state: rendered
+
+# returns
+# rendered:
+# interface Ethernet1/2
+# ipv6 traffic-filter ACL1v6 in
+# interface Ethernet1/5
+# ipv6 traffic-filter ACL1v6 in
+# ip access-group ACL1v4 out
+# ip port access-group PortACL in
+
+"""
+RETURN = """
+before:
+ description: The configuration prior to the model invocation.
+ returned: always
+ type: dict
+ 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: dict
+ 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: ['interface Ethernet1/2', 'ipv6 traffic-filter ACL1v6 out', 'ip port access-group PortACL in']
+"""
+
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.acl_interfaces.acl_interfaces import (
+ Acl_interfacesArgs,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.config.acl_interfaces.acl_interfaces import (
+ Acl_interfaces,
+)
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ module = AnsibleModule(
+ argument_spec=Acl_interfacesArgs.argument_spec,
+ supports_check_mode=True,
+ )
+
+ result = Acl_interfaces(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_acls.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_acls.py
new file mode 100644
index 00000000..51a66504
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_acls.py
@@ -0,0 +1,915 @@
+#!/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 nxos_acls
+"""
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_acls
+short_description: ACLs resource module
+description: Manage named IP ACLs on the Cisco NX-OS platform
+version_added: 1.0.0
+author: Adharsh Srivats Rangarajan (@adharshsrivatsr)
+notes:
+- Tested against NX-OS 7.3.(0)D1(1) on VIRL
+- Unsupported for Cisco MDS
+- As NX-OS allows configuring a rule again with different sequence numbers, the user
+ is expected to provide sequence numbers for the access control entries to preserve
+ idempotency. If no sequence number is given, the rule will be added as a new rule
+ by the device.
+options:
+ running_config:
+ description:
+ - This option is used only with state I(parsed).
+ - The value of this option should be the output received from the NX-OS device
+ by executing the command B(show running-config | section 'ip(v6)* access-list).
+ - The state I(parsed) reads the configuration from C(running_config) option and
+ transforms it into Ansible structured data as per the resource module's argspec
+ and the value is then returned in the I(parsed) key within the result.
+ type: str
+ config:
+ description: A dictionary of ACL options.
+ type: list
+ elements: dict
+ suboptions:
+ afi:
+ description: The Address Family Indicator (AFI) for the ACL.
+ type: str
+ required: true
+ choices:
+ - ipv4
+ - ipv6
+ acls:
+ description: A list of the ACLs.
+ type: list
+ elements: dict
+ suboptions:
+ name:
+ description: Name of the ACL.
+ type: str
+ required: true
+ aces:
+ description: The entries within the ACL.
+ type: list
+ elements: dict
+ suboptions:
+ grant:
+ description: Action to be applied on the rule.
+ type: str
+ choices:
+ - permit
+ - deny
+ destination:
+ description: Specify the packet destination.
+ type: dict
+ suboptions:
+ address:
+ description: Destination network address.
+ type: str
+ any:
+ description: Any destination address.
+ type: bool
+ host:
+ description: Host IP address.
+ type: str
+ port_protocol:
+ description: Specify the destination port or protocol (only for
+ TCP and UDP).
+ type: dict
+ suboptions:
+ eq:
+ description: Match only packets on a given port number.
+ type: str
+ gt:
+ description: Match only packets with a greater port number.
+ type: str
+ lt:
+ description: Match only packets with a lower port number.
+ type: str
+ neq:
+ description: Match only packets not on a given port number.
+ type: str
+ range:
+ description: Match only packets in the range of port numbers.
+ type: dict
+ suboptions:
+ start:
+ description: Specify the start of the port range.
+ type: str
+ end:
+ description: Specify the end of the port range.
+ type: str
+ prefix:
+ description: Destination network prefix. Only for prefixes of
+ value less than 31 for ipv4 and 127 for ipv6. Prefixes of 32
+ (ipv4) and 128 (ipv6) should be given in the 'host' key.
+ type: str
+ wildcard_bits:
+ description: Destination wildcard bits.
+ type: str
+ dscp:
+ description: Match packets with given DSCP value.
+ type: str
+ fragments:
+ description: Check non-initial fragments.
+ type: bool
+ remark:
+ description: Access list entry comment.
+ type: str
+ sequence:
+ description: Sequence number.
+ type: int
+ source:
+ description: Specify the packet source.
+ type: dict
+ suboptions:
+ address:
+ description: Source network address.
+ type: str
+ any:
+ description: Any source address.
+ type: bool
+ host:
+ description: Host IP address.
+ type: str
+ port_protocol:
+ description: Specify the destination port or protocol (only for
+ TCP and UDP).
+ type: dict
+ suboptions:
+ eq:
+ description: Match only packets on a given port number.
+ type: str
+ gt:
+ description: Match only packets with a greater port number.
+ type: str
+ lt:
+ description: Match only packets with a lower port number.
+ type: str
+ neq:
+ description: Match only packets not on a given port number.
+ type: str
+ range:
+ description: Match only packets in the range of port numbers.
+ type: dict
+ suboptions:
+ start:
+ description: Specify the start of the port range.
+ type: str
+ end:
+ description: Specify the end of the port range.
+ type: str
+ prefix:
+ description: Source network prefix. Only for prefixes of mask
+ value less than 31 for ipv4 and 127 for ipv6. Prefixes of mask
+ 32 (ipv4) and 128 (ipv6) should be given in the 'host' key.
+ type: str
+ wildcard_bits:
+ description: Source wildcard bits.
+ type: str
+ log:
+ description: Log matches against this entry.
+ type: bool
+ precedence:
+ description: Match packets with given precedence value.
+ type: str
+ protocol:
+ description: Specify the protocol.
+ type: str
+ protocol_options:
+ description: All possible suboptions for the protocol chosen.
+ type: dict
+ suboptions:
+ icmp:
+ description: ICMP protocol options.
+ type: dict
+ suboptions:
+ administratively_prohibited:
+ description: Administratively prohibited
+ type: bool
+ alternate_address:
+ description: Alternate address
+ type: bool
+ conversion_error:
+ description: Datagram conversion
+ type: bool
+ dod_host_prohibited:
+ description: Host prohibited
+ type: bool
+ dod_net_prohibited:
+ description: Net prohibited
+ type: bool
+ echo:
+ description: Echo (ping)
+ type: bool
+ echo_reply:
+ description: Echo reply
+ type: bool
+ echo_request:
+ description: Echo request (ping)
+ type: bool
+ general_parameter_problem:
+ description: Parameter problem
+ type: bool
+ host_isolated:
+ description: Host isolated
+ type: bool
+ host_precedence_unreachable:
+ description: Host unreachable for precedence
+ type: bool
+ host_redirect:
+ description: Host redirect
+ type: bool
+ host_tos_redirect:
+ description: Host redirect for TOS
+ type: bool
+ host_tos_unreachable:
+ description: Host unreachable for TOS
+ type: bool
+ host_unknown:
+ description: Host unknown
+ type: bool
+ host_unreachable:
+ description: Host unreachable
+ type: bool
+ information_reply:
+ description: Information replies
+ type: bool
+ information_request:
+ description: Information requests
+ type: bool
+ mask_reply:
+ description: Mask replies
+ type: bool
+ mask_request:
+ description: Mask requests
+ type: bool
+ message_code:
+ description: ICMP message code
+ type: int
+ message_type:
+ description: ICMP message type
+ type: int
+ mobile_redirect:
+ description: Mobile host redirect
+ type: bool
+ net_redirect:
+ description: Network redirect
+ type: bool
+ net_tos_redirect:
+ description: Net redirect for TOS
+ type: bool
+ net_tos_unreachable:
+ description: Network unreachable for TOS
+ type: bool
+ net_unreachable:
+ description: Net unreachable
+ type: bool
+ network_unknown:
+ description: Network unknown
+ type: bool
+ no_room_for_option:
+ description: Parameter required but no room
+ type: bool
+ option_missing:
+ description: Parameter required but not present
+ type: bool
+ packet_too_big:
+ description: Fragmentation needed and DF set
+ type: bool
+ parameter_problem:
+ description: All parameter problems
+ type: bool
+ port_unreachable:
+ description: Port unreachable
+ type: bool
+ precedence_unreachable:
+ description: Precedence cutoff
+ type: bool
+ protocol_unreachable:
+ description: Protocol unreachable
+ type: bool
+ reassembly_timeout:
+ description: Reassembly timeout
+ type: bool
+ redirect:
+ description: All redirects
+ type: bool
+ router_advertisement:
+ description: Router discovery advertisements
+ type: bool
+ router_solicitation:
+ description: Router discovery solicitations
+ type: bool
+ source_quench:
+ description: Source quenches
+ type: bool
+ source_route_failed:
+ description: Source route failed
+ type: bool
+ time_exceeded:
+ description: All time exceeded.
+ type: bool
+ timestamp_reply:
+ description: Timestamp replies
+ type: bool
+ timestamp_request:
+ description: Timestamp requests
+ type: bool
+ traceroute:
+ description: Traceroute
+ type: bool
+ ttl_exceeded:
+ description: TTL exceeded
+ type: bool
+ unreachable:
+ description: All unreachables
+ type: bool
+ icmpv6:
+ description: ICMPv6 protocol options.
+ type: dict
+ suboptions:
+ beyond_scope:
+ description: Destination beyond scope.
+ type: bool
+ destination_unreachable:
+ description: Destination address is unreachable.
+ type: bool
+ echo_reply:
+ description: Echo reply.
+ type: bool
+ echo_request:
+ description: Echo request (ping).
+ type: bool
+ fragments:
+ description: Check non-initial fragments.
+ type: bool
+ header:
+ description: Parameter header problem.
+ type: bool
+ hop_limit:
+ description: Hop limit exceeded in transit.
+ type: bool
+ mld_query:
+ description: Multicast Listener Discovery Query.
+ type: bool
+ mld_reduction:
+ description: Multicast Listener Discovery Reduction.
+ type: bool
+ mld_report:
+ description: Multicast Listener Discovery Report.
+ type: bool
+ mldv2:
+ description: Multicast Listener Discovery Protocol.
+ type: bool
+ nd_na:
+ description: Neighbor discovery neighbor advertisements.
+ type: bool
+ nd_ns:
+ description: Neighbor discovery neighbor solicitations.
+ type: bool
+ next_header:
+ description: Parameter next header problems.
+ type: bool
+ no_admin:
+ description: Administration prohibited destination.
+ type: bool
+ no_route:
+ description: No route to destination.
+ type: bool
+ packet_too_big:
+ description: Packet too big.
+ type: bool
+ parameter_option:
+ description: Parameter option problems.
+ type: bool
+ parameter_problem:
+ description: All parameter problems.
+ type: bool
+ port_unreachable:
+ description: Port unreachable.
+ type: bool
+ reassembly_timeout:
+ description: Reassembly timeout.
+ type: bool
+ renum_command:
+ description: Router renumbering command.
+ type: bool
+ renum_result:
+ description: Router renumbering result.
+ type: bool
+ renum_seq_number:
+ description: Router renumbering sequence number reset.
+ type: bool
+ router_advertisement:
+ description: Neighbor discovery router advertisements.
+ type: bool
+ router_renumbering:
+ description: All router renumbering.
+ type: bool
+ router_solicitation:
+ description: Neighbor discovery router solicitations.
+ type: bool
+ time_exceeded:
+ description: All time exceeded.
+ type: bool
+ unreachable:
+ description: All unreachable.
+ type: bool
+ telemetry_path:
+ description: IPT enabled.
+ type: bool
+ telemetry_queue:
+ description: Flow of interest for BDC/HDC.
+ type: bool
+ tcp:
+ description: TCP flags.
+ type: dict
+ suboptions:
+ ack:
+ description: Match on the ACK bit
+ type: bool
+ established:
+ description: Match established connections
+ type: bool
+ fin:
+ description: Match on the FIN bit
+ type: bool
+ psh:
+ description: Match on the PSH bit
+ type: bool
+ rst:
+ description: Match on the RST bit
+ type: bool
+ syn:
+ description: Match on the SYN bit
+ type: bool
+ urg:
+ description: Match on the URG bit
+ type: bool
+ igmp:
+ description: IGMP protocol options.
+ type: dict
+ suboptions:
+ dvmrp:
+ description: Distance Vector Multicast Routing Protocol
+ type: bool
+ host_query:
+ description: Host Query
+ type: bool
+ host_report:
+ description: Host Report
+ type: bool
+ state:
+ description:
+ - The state the configuration should be left in
+ type: str
+ choices:
+ - deleted
+ - gathered
+ - merged
+ - overridden
+ - rendered
+ - replaced
+ - parsed
+ default: merged
+
+"""
+EXAMPLES = """
+# Using merged
+
+# Before state:
+# -------------
+#
+
+- name: Merge new ACLs configuration
+ cisco.nxos.nxos_acls:
+ config:
+ - afi: ipv4
+ acls:
+ - name: ACL1v4
+ aces:
+ - grant: deny
+ destination:
+ address: 192.0.2.64
+ wildcard_bits: 0.0.0.255
+ source:
+ any: true
+ port_protocol:
+ lt: 55
+ protocol: tcp
+ protocol_options:
+ tcp:
+ ack: true
+ fin: true
+ sequence: 50
+
+ - afi: ipv6
+ acls:
+ - name: ACL1v6
+ aces:
+ - grant: permit
+ sequence: 10
+ source:
+ any: true
+ destination:
+ prefix: 2001:db8:12::/32
+ protocol: sctp
+ state: merged
+
+# After state:
+# ------------
+#
+# ip access-list ACL1v4
+# 50 deny tcp any lt 55 192.0.2.64 0.0.0.255 ack fin
+# ipv6 access-list ACL1v6
+# 10 permit sctp any any
+
+# Using replaced
+
+# Before state:
+# ----------------
+#
+# ip access-list ACL1v4
+# 10 permit ip any any
+# 20 deny udp any any
+# ip access-list ACL2v4
+# 10 permit ahp 192.0.2.0 0.0.0.255 any
+# ip access-list ACL1v6
+# 10 permit sctp any any
+# 20 remark IPv6 ACL
+# ip access-list ACL2v6
+# 10 deny ipv6 any 2001:db8:3000::/36
+# 20 permit tcp 2001:db8:2000:2::2/128 2001:db8:2000:ab::2/128
+
+- name: Replace existing ACL configuration with provided configuration
+ cisco.nxos.nxos_acls:
+ config:
+ - afi: ipv4
+ - afi: ipv6
+ acls:
+ - name: ACL1v6
+ aces:
+ - sequence: 20
+ grant: permit
+ source:
+ any: true
+ destination:
+ any: true
+ protocol: pip
+
+ - remark: Replaced ACE
+
+ - name: ACL2v6
+ state: replaced
+
+# After state:
+# ---------------
+#
+# ipv6 access-list ACL1v6
+# 20 permit pip any any
+# 30 remark Replaced ACE
+# ipv6 access-list ACL2v6
+
+# Using overridden
+
+# Before state:
+# ----------------
+#
+# ip access-list ACL1v4
+# 10 permit ip any any
+# 20 deny udp any any
+# ip access-list ACL2v4
+# 10 permit ahp 192.0.2.0 0.0.0.255 any
+# ip access-list ACL1v6
+# 10 permit sctp any any
+# 20 remark IPv6 ACL
+# ip access-list ACL2v6
+# 10 deny ipv6 any 2001:db8:3000::/36
+# 20 permit tcp 2001:db8:2000:2::2/128 2001:db8:2000:ab::2/128
+
+- name: Override existing configuration with provided configuration
+ cisco.nxos.nxos_acls:
+ config:
+ - afi: ipv4
+ acls:
+ - name: NewACL
+ aces:
+ - grant: deny
+ source:
+ address: 192.0.2.0
+ wildcard_bits: 0.0.255.255
+ destination:
+ any: true
+ protocol: eigrp
+ - remark: Example for overridden state
+ state: overridden
+
+# After state:
+# ------------
+#
+# ip access-list NewACL
+# 10 deny eigrp 192.0.2.0 0.0.255.255 any
+# 20 remark Example for overridden state
+
+# Using deleted:
+#
+# Before state:
+# -------------
+#
+# ip access-list ACL1v4
+# 10 permit ip any any
+# 20 deny udp any any
+# ip access-list ACL2v4
+# 10 permit ahp 192.0.2.0 0.0.0.255 any
+# ip access-list ACL1v6
+# 10 permit sctp any any
+# 20 remark IPv6 ACL
+# ip access-list ACL2v6
+# 10 deny ipv6 any 2001:db8:3000::/36
+# 20 permit tcp 2001:db8:2000:2::2/128 2001:db8:2000:ab::2/128
+
+- name: Delete all ACLs
+ cisco.nxos.nxos_acls:
+ config:
+ state: deleted
+
+# After state:
+# -----------
+#
+
+
+# Before state:
+# -------------
+#
+# ip access-list ACL1v4
+# 10 permit ip any any
+# 20 deny udp any any
+# ip access-list ACL2v4
+# 10 permit ahp 192.0.2.0 0.0.0.255 any
+# ip access-list ACL1v6
+# 10 permit sctp any any
+# 20 remark IPv6 ACL
+# ip access-list ACL2v6
+# 10 deny ipv6 any 2001:db8:3000::/36
+# 20 permit tcp 2001:db8:2000:2::2/128 2001:db8:2000:ab::2/128
+
+- name: Delete all ACLs in given AFI
+ cisco.nxos.nxos_acls:
+ config:
+ - afi: ipv4
+ state: deleted
+
+# After state:
+# ------------
+#
+# ip access-list ACL1v6
+# 10 permit sctp any any
+# 20 remark IPv6 ACL
+# ip access-list ACL2v6
+# 10 deny ipv6 any 2001:db8:3000::/36
+# 20 permit tcp 2001:db8:2000:2::2/128 2001:db8:2000:ab::2/128
+
+
+
+# Before state:
+# -------------
+#
+# ip access-list ACL1v4
+# 10 permit ip any any
+# 20 deny udp any any
+# ip access-list ACL2v4
+# 10 permit ahp 192.0.2.0 0.0.0.255 any
+# ipv6 access-list ACL1v6
+# 10 permit sctp any any
+# 20 remark IPv6 ACL
+# ipv6 access-list ACL2v6
+# 10 deny ipv6 any 2001:db8:3000::/36
+# 20 permit tcp 2001:db8:2000:2::2/128 2001:db8:2000:ab::2/128
+
+- name: Delete specific ACLs
+ cisco.nxos.nxos_acls:
+ config:
+ - afi: ipv4
+ acls:
+ - name: ACL1v4
+ - name: ACL2v4
+ - afi: ipv6
+ acls:
+ - name: ACL1v6
+ state: deleted
+
+# After state:
+# ------------
+# ipv6 access-list ACL2v6
+# 10 deny ipv6 any 2001:db8:3000::/36
+# 20 permit tcp 2001:db8:2000:2::2/128 2001:db8:2000:ab::2/128
+
+# Using parsed
+
+- name: Parse given config to structured data
+ cisco.nxos.nxos_acls:
+ running_config: |
+ ip access-list ACL1v4
+ 50 deny tcp any lt 55 192.0.2.64 0.0.0.255 ack fin
+ ipv6 access-list ACL1v6
+ 10 permit sctp any any
+ state: parsed
+
+# returns:
+# parsed:
+# - afi: ipv4
+# acls:
+# - name: ACL1v4
+# aces:
+# - grant: deny
+# destination:
+# address: 192.0.2.64
+# wildcard_bits: 0.0.0.255
+# source:
+# any: true
+# port_protocol:
+# lt: 55
+# protocol: tcp
+# protocol_options:
+# tcp:
+# ack: true
+# fin: true
+# sequence: 50
+#
+# - afi: ipv6
+# acls:
+# - name: ACL1v6
+# aces:
+# - grant: permit
+# sequence: 10
+# source:
+# any: true
+# destination:
+# prefix: 2001:db8:12::/32
+# protocol: sctp
+
+
+# Using gathered:
+
+# Before state:
+# ------------
+#
+# ip access-list ACL1v4
+# 50 deny tcp any lt 55 192.0.2.64 0.0.0.255 ack fin
+# ipv6 access-list ACL1v6
+# 10 permit sctp any any
+
+- name: Gather existing configuration
+ cisco.nxos.nxos_acls:
+ state: gathered
+
+# returns:
+# gathered:
+# - afi: ipv4
+# acls:
+# - name: ACL1v4
+# aces:
+# - grant: deny
+# destination:
+# address: 192.0.2.64
+# wildcard_bits: 0.0.0.255
+# source:
+# any: true
+# port_protocol:
+# lt: 55
+# protocol: tcp
+# protocol_options:
+# tcp:
+# ack: true
+# fin: true
+# sequence: 50
+
+# - afi: ipv6
+# acls:
+# - name: ACL1v6
+# aces:
+# - grant: permit
+# sequence: 10
+# source:
+# any: true
+# destination:
+# prefix: 2001:db8:12::/32
+# protocol: sctp
+
+
+# Using rendered
+
+- name: Render required configuration to be pushed to the device
+ cisco.nxos.nxos_acls:
+ config:
+ - afi: ipv4
+ acls:
+ - name: ACL1v4
+ aces:
+ - grant: deny
+ destination:
+ address: 192.0.2.64
+ wildcard_bits: 0.0.0.255
+ source:
+ any: true
+ port_protocol:
+ lt: 55
+ protocol: tcp
+ protocol_options:
+ tcp:
+ ack: true
+ fin: true
+ sequence: 50
+
+ - afi: ipv6
+ acls:
+ - name: ACL1v6
+ aces:
+ - grant: permit
+ sequence: 10
+ source:
+ any: true
+ destination:
+ prefix: 2001:db8:12::/32
+ protocol: sctp
+ state: rendered
+
+# returns:
+# rendered:
+# ip access-list ACL1v4
+# 50 deny tcp any lt 55 192.0.2.64 0.0.0.255 ack fin
+# ipv6 access-list ACL1v6
+# 10 permit sctp any any
+"""
+RETURN = """
+before:
+ description: The configuration prior to the model invocation.
+ returned: always
+ type: dict
+ 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: dict
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+commands:
+ description: The set of commands pushed to the remote device.
+ returned: always
+ type: list
+ sample: ['ip access-list ACL1v4', '10 permit ip any any precedence critical log', '20 deny tcp any lt smtp host 192.0.2.64 ack fin']
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.acls.acls import (
+ AclsArgs,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.config.acls.acls import Acls
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ module = AnsibleModule(argument_spec=AclsArgs.argument_spec, supports_check_mode=True)
+
+ result = Acls(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_banner.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_banner.py
new file mode 100644
index 00000000..f946dc0a
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_banner.py
@@ -0,0 +1,224 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+# (c) 2017, Ansible by Red Hat, inc
+#
+# This file is part of Ansible by Red Hat
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+
+
+DOCUMENTATION = """
+module: nxos_banner
+author: Trishna Guha (@trishnaguha)
+short_description: Manage multiline banners on Cisco NXOS devices
+description:
+- This will configure both exec and motd banners on remote devices running Cisco NXOS.
+ It allows playbooks to add or remove banner text from the active running configuration.
+notes:
+- Since responses from the device are always read with surrounding whitespaces stripped,
+ tasks that configure banners with preceeding or trailing whitespaces will not be idempotent.
+- Limited Support for Cisco MDS
+version_added: 1.0.0
+options:
+ banner:
+ description:
+ - Specifies which banner that should be configured on the remote device.
+ required: true
+ choices:
+ - exec
+ - motd
+ type: str
+ text:
+ description:
+ - The banner text that should be present in the remote device running configuration.
+ This argument accepts a multiline string, with no empty lines. Requires I(state=present).
+ type: str
+ state:
+ description:
+ - Specifies whether or not the configuration is present in the current devices
+ active running configuration.
+ default: present
+ choices:
+ - present
+ - absent
+ type: str
+extends_documentation_fragment:
+- cisco.nxos.nxos
+"""
+
+EXAMPLES = """
+- name: configure the exec banner
+ cisco.nxos.nxos_banner:
+ banner: exec
+ text: |
+ this is my exec banner
+ that contains a multiline
+ string
+ state: present
+- name: remove the motd banner
+ cisco.nxos.nxos_banner:
+ banner: motd
+ state: absent
+- name: Configure banner from file
+ cisco.nxos.nxos_banner:
+ banner: motd
+ text: "{{ lookup('file', './config_partial/raw_banner.cfg') }}"
+ state: present
+"""
+
+RETURN = """
+commands:
+ description: The list of configuration mode commands to send to the device
+ returned: always
+ type: list
+ sample:
+ - banner exec
+ - this is my exec banner
+ - that contains a multiline
+ - string
+"""
+
+import re
+
+from ansible.module_utils._text import to_text
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ load_config,
+ run_commands,
+)
+
+
+def execute_show_command(module, command):
+ format = "text"
+ cmds = [{"command": command, "output": format}]
+ output = run_commands(module, cmds)
+ return output
+
+
+def map_obj_to_commands(want, have, module):
+ commands = list()
+ state = module.params["state"]
+ platform_regex = "Nexus.*Switch"
+
+ if state == "absent":
+ if have.get("text") and not (
+ (have.get("text") == "User Access Verification")
+ or re.match(platform_regex, have.get("text"))
+ ):
+ commands.append("no banner %s" % module.params["banner"])
+
+ elif state == "present" and want.get("text") != have.get("text"):
+ banner_cmd = "banner %s @\n%s\n@" % (
+ module.params["banner"],
+ want["text"],
+ )
+ commands.append(banner_cmd)
+
+ return commands
+
+
+def map_config_to_obj(module):
+ command = "show banner %s" % module.params["banner"]
+ output = execute_show_command(module, command)[0]
+
+ if "Invalid command" in output:
+ module.fail_json(
+ msg="banner: %s may not be supported on this platform. Possible values are : exec | motd"
+ % module.params["banner"],
+ )
+
+ if isinstance(output, dict):
+ output = list(output.values())
+ if output != []:
+ output = output[0]
+ else:
+ output = ""
+ if isinstance(output, dict):
+ output = list(output.values())
+ if output != []:
+ output = output[0]
+ else:
+ output = ""
+ else:
+ output = output.rstrip()
+
+ obj = {"banner": module.params["banner"], "state": "absent"}
+ if output:
+ obj["text"] = output
+ obj["state"] = "present"
+ return obj
+
+
+def map_params_to_obj(module):
+ text = module.params["text"]
+ return {
+ "banner": module.params["banner"],
+ "text": to_text(text) if text else None,
+ "state": module.params["state"],
+ }
+
+
+def main():
+ """main entry point for module execution"""
+ argument_spec = dict(
+ banner=dict(required=True, choices=["exec", "motd"]),
+ text=dict(),
+ state=dict(default="present", choices=["present", "absent"]),
+ )
+
+ required_if = [("state", "present", ("text",))]
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ required_if=required_if,
+ supports_check_mode=True,
+ )
+
+ warnings = list()
+
+ result = {"changed": False}
+ if warnings:
+ result["warnings"] = warnings
+ want = map_params_to_obj(module)
+ have = map_config_to_obj(module)
+ commands = map_obj_to_commands(want, have, module)
+ result["commands"] = commands
+
+ if commands:
+ if not module.check_mode:
+ msgs = load_config(module, commands, True)
+ if msgs:
+ for item in msgs:
+ if item:
+ if isinstance(item, dict):
+ err_str = item["clierror"]
+ else:
+ err_str = item
+ if "more than 40 lines" in err_str or "buffer overflowed" in err_str:
+ load_config(module, commands)
+
+ result["changed"] = True
+
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_bfd_global.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_bfd_global.py
new file mode 100644
index 00000000..6ae1a88d
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_bfd_global.py
@@ -0,0 +1,333 @@
+#!/usr/bin/python
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_bfd_global
+extends_documentation_fragment:
+- cisco.nxos.nxos
+short_description: Bidirectional Forwarding Detection (BFD) global-level configuration
+description:
+- Manages Bidirectional Forwarding Detection (BFD) global-level configuration.
+version_added: 1.0.0
+author:
+- Chris Van Heuveln (@chrisvanheuveln)
+notes:
+- Tested against NXOSv 9.2(2)
+- Unsupported for Cisco MDS
+- BFD global will automatically enable 'feature bfd' if it is disabled.
+- BFD global does not have a 'state' parameter. All of the BFD commands are unique
+ and are defined if 'feature bfd' is enabled.
+options:
+ echo_interface:
+ description:
+ - Loopback interface used for echo frames.
+ - Valid values are loopback interface name or 'deleted'.
+ - Not supported on N5K/N6K
+ required: false
+ type: str
+ echo_rx_interval:
+ description:
+ - BFD Echo receive interval in milliseconds.
+ required: false
+ type: int
+ interval:
+ description:
+ - BFD interval timer values.
+ - Value must be a dict defining values for keys (tx, min_rx, and multiplier)
+ required: false
+ type: dict
+ slow_timer:
+ description:
+ - BFD slow rate timer in milliseconds.
+ required: false
+ type: int
+ startup_timer:
+ description:
+ - BFD delayed startup timer in seconds.
+ - Not supported on N5K/N6K/N7K
+ required: false
+ type: int
+ ipv4_echo_rx_interval:
+ description:
+ - BFD IPv4 session echo receive interval in milliseconds.
+ required: false
+ type: int
+ ipv4_interval:
+ description:
+ - BFD IPv4 interval timer values.
+ - Value must be a dict defining values for keys (tx, min_rx, and multiplier).
+ required: false
+ type: dict
+ ipv4_slow_timer:
+ description:
+ - BFD IPv4 slow rate timer in milliseconds.
+ required: false
+ type: int
+ ipv6_echo_rx_interval:
+ description:
+ - BFD IPv6 session echo receive interval in milliseconds.
+ required: false
+ type: int
+ ipv6_interval:
+ description:
+ - BFD IPv6 interval timer values.
+ - Value must be a dict defining values for keys (tx, min_rx, and multiplier).
+ required: false
+ type: dict
+ ipv6_slow_timer:
+ description:
+ - BFD IPv6 slow rate timer in milliseconds.
+ required: false
+ type: int
+ fabricpath_interval:
+ description:
+ - BFD fabricpath interval timer values.
+ - Value must be a dict defining values for keys (tx, min_rx, and multiplier).
+ required: false
+ type: dict
+ fabricpath_slow_timer:
+ description:
+ - BFD fabricpath slow rate timer in milliseconds.
+ required: false
+ type: int
+ fabricpath_vlan:
+ description:
+ - BFD fabricpath control vlan.
+ required: false
+ type: int
+"""
+EXAMPLES = """
+- cisco.nxos.nxos_bfd_global:
+ echo_interface: Ethernet1/2
+ echo_rx_interval: 50
+ interval:
+ tx: 50
+ min_rx: 50
+ multiplier: 4
+"""
+
+RETURN = """
+cmds:
+ description: commands sent to the device
+ returned: always
+ type: list
+ sample: ["bfd echo-interface loopback1", "bfd slow-timer 2000"]
+"""
+
+
+import re
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ NxosCmdRef,
+ load_config,
+)
+
+
+BFD_CMD_REF = """
+# The cmd_ref is a yaml formatted list of module commands.
+# A leading underscore denotes a non-command variable; e.g. _template.
+# BFD does not have convenient json data so this cmd_ref uses raw cli configs.
+---
+_template: # _template holds common settings for all commands
+ # Enable feature bfd if disabled
+ feature: bfd
+ # Common get syntax for BFD commands
+ get_command: show run bfd all | incl '^(no )*bfd'
+
+echo_interface:
+ kind: str
+ getval: (no )*bfd echo-interface *(\\S+)*$
+ setval: 'bfd echo-interface {0}'
+ default: ~
+
+echo_rx_interval:
+ _exclude: ['N5K', 'N6K']
+ kind: int
+ getval: bfd echo-rx-interval (\\d+)$
+ setval: bfd echo-rx-interval {0}
+ default: 50
+ N3K:
+ default: 250
+
+interval:
+ kind: dict
+ getval: bfd interval (?P<tx>\\d+) min_rx (?P<min_rx>\\d+) multiplier (?P<multiplier>\\d+)
+ setval: bfd interval {tx} min_rx {min_rx} multiplier {multiplier}
+ default: &def_interval
+ tx: 50
+ min_rx: 50
+ multiplier: 3
+ N3K:
+ default: &n3k_def_interval
+ tx: 250
+ min_rx: 250
+ multiplier: 3
+
+slow_timer:
+ kind: int
+ getval: bfd slow-timer (\\d+)$
+ setval: bfd slow-timer {0}
+ default: 2000
+
+startup_timer:
+ _exclude: ['N5K', 'N6K', 'N7K']
+ kind: int
+ getval: bfd startup-timer (\\d+)$
+ setval: bfd startup-timer {0}
+ default: 5
+
+# IPv4/IPv6 specific commands
+ipv4_echo_rx_interval:
+ _exclude: ['N5K', 'N6K']
+ kind: int
+ getval: bfd ipv4 echo-rx-interval (\\d+)$
+ setval: bfd ipv4 echo-rx-interval {0}
+ default: 50
+ N3K:
+ default: 250
+
+ipv4_interval:
+ _exclude: ['N5K', 'N6K']
+ kind: dict
+ getval: bfd ipv4 interval (?P<tx>\\d+) min_rx (?P<min_rx>\\d+) multiplier (?P<multiplier>\\d+)
+ setval: bfd ipv4 interval {tx} min_rx {min_rx} multiplier {multiplier}
+ default: *def_interval
+ N3K:
+ default: *n3k_def_interval
+
+ipv4_slow_timer:
+ _exclude: ['N5K', 'N6K']
+ kind: int
+ getval: bfd ipv4 slow-timer (\\d+)$
+ setval: bfd ipv4 slow-timer {0}
+ default: 2000
+
+ipv6_echo_rx_interval:
+ _exclude: ['N35', 'N5K', 'N6K']
+ kind: int
+ getval: bfd ipv6 echo-rx-interval (\\d+)$
+ setval: bfd ipv6 echo-rx-interval {0}
+ default: 50
+ N3K:
+ default: 250
+
+ipv6_interval:
+ _exclude: ['N35', 'N5K', 'N6K']
+ kind: dict
+ getval: bfd ipv6 interval (?P<tx>\\d+) min_rx (?P<min_rx>\\d+) multiplier (?P<multiplier>\\d+)
+ setval: bfd ipv6 interval {tx} min_rx {min_rx} multiplier {multiplier}
+ default: *def_interval
+ N3K:
+ default: *n3k_def_interval
+
+ipv6_slow_timer:
+ _exclude: ['N35', 'N5K', 'N6K']
+ kind: int
+ getval: bfd ipv6 slow-timer (\\d+)$
+ setval: bfd ipv6 slow-timer {0}
+ default: 2000
+
+# Fabricpath Commands
+fabricpath_interval:
+ _exclude: ['N35', 'N3K', 'N9K']
+ kind: dict
+ getval: bfd fabricpath interval (?P<tx>\\d+) min_rx (?P<min_rx>\\d+) multiplier (?P<multiplier>\\d+)
+ setval: bfd fabricpath interval {tx} min_rx {min_rx} multiplier {multiplier}
+ default: *def_interval
+
+fabricpath_slow_timer:
+ _exclude: ['N35', 'N3K', 'N9K']
+ kind: int
+ getval: bfd fabricpath slow-timer (\\d+)$
+ setval: bfd fabricpath slow-timer {0}
+ default: 2000
+
+fabricpath_vlan:
+ _exclude: ['N35', 'N3K', 'N9K']
+ kind: int
+ getval: bfd fabricpath vlan (\\d+)$
+ setval: bfd fabricpath vlan {0}
+ default: 1
+"""
+
+
+def reorder_cmds(cmds):
+ """
+ There is a bug in some image versions where bfd echo-interface and
+ bfd echo-rx-interval need to be applied last for them to nvgen properly.
+ """
+ regex1 = re.compile(r"^bfd echo-interface")
+ regex2 = re.compile(r"^bfd echo-rx-interval")
+ filtered_cmds = [i for i in cmds if not regex1.match(i)]
+ filtered_cmds = [i for i in filtered_cmds if not regex2.match(i)]
+ echo_int_cmd = [i for i in cmds if regex1.match(i)]
+ echo_rx_cmd = [i for i in cmds if regex2.match(i)]
+ filtered_cmds.extend(echo_int_cmd)
+ filtered_cmds.extend(echo_rx_cmd)
+
+ return filtered_cmds
+
+
+def main():
+ argument_spec = dict(
+ echo_interface=dict(required=False, type="str"),
+ echo_rx_interval=dict(required=False, type="int"),
+ interval=dict(required=False, type="dict"),
+ slow_timer=dict(required=False, type="int"),
+ startup_timer=dict(required=False, type="int"),
+ ipv4_echo_rx_interval=dict(required=False, type="int"),
+ ipv4_interval=dict(required=False, type="dict"),
+ ipv4_slow_timer=dict(required=False, type="int"),
+ ipv6_echo_rx_interval=dict(required=False, type="int"),
+ ipv6_interval=dict(required=False, type="dict"),
+ ipv6_slow_timer=dict(required=False, type="int"),
+ fabricpath_interval=dict(required=False, type="dict"),
+ fabricpath_slow_timer=dict(required=False, type="int"),
+ fabricpath_vlan=dict(required=False, type="int"),
+ )
+ module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)
+ warnings = list()
+
+ cmd_ref = NxosCmdRef(module, BFD_CMD_REF)
+ cmd_ref.get_existing()
+ cmd_ref.get_playvals()
+ cmds = reorder_cmds(cmd_ref.get_proposed())
+
+ result = {
+ "changed": False,
+ "commands": cmds,
+ "warnings": warnings,
+ "check_mode": module.check_mode,
+ }
+ if cmds:
+ result["changed"] = True
+ if not module.check_mode:
+ load_config(module, cmds)
+
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_bfd_interfaces.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_bfd_interfaces.py
new file mode 100644
index 00000000..1790f8e0
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_bfd_interfaces.py
@@ -0,0 +1,302 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# Copyright 2019 Cisco and/or its affiliates.
+# 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 nxos_bfd_interfaces
+"""
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_bfd_interfaces
+short_description: BFD interfaces resource module
+description: Manages attributes of Bidirectional Forwarding Detection (BFD) on the
+ interface.
+version_added: 1.0.0
+author: Chris Van Heuveln (@chrisvanheuveln)
+notes:
+- Tested against NX-OS 7.0(3)I5(1).
+- Unsupported for Cisco MDS
+- Feature bfd should be enabled for this module.
+options:
+ running_config:
+ description:
+ - This option is used only with state I(parsed).
+ - The value of this option should be the output received from the NX-OS device
+ by executing the command B(show running-config | section '^interface|^feature
+ bfd').
+ - The state I(parsed) reads the configuration from C(running_config) option and
+ transforms it into Ansible structured data as per the resource module's argspec
+ and the value is then returned in the I(parsed) key within the result.
+ type: str
+ config:
+ description: The provided configuration
+ type: list
+ elements: dict
+ suboptions:
+ name:
+ type: str
+ description: The name of the interface.
+ bfd:
+ type: str
+ description:
+ - Enable/Disable Bidirectional Forwarding Detection (BFD) on the interface.
+ choices:
+ - enable
+ - disable
+ echo:
+ type: str
+ description:
+ - Enable/Disable BFD Echo functionality on the interface.
+ choices:
+ - enable
+ - disable
+ state:
+ description:
+ - The state of the configuration after module completion
+ type: str
+ choices:
+ - merged
+ - replaced
+ - overridden
+ - deleted
+ - gathered
+ - rendered
+ - parsed
+ default: merged
+
+"""
+EXAMPLES = """
+# Using deleted
+
+- name: Configure interfaces
+ cisco.nxos.nxos_bfd_interfaces:
+ state: deleted
+
+
+# Using merged
+
+- name: Configure interfaces
+ cisco.nxos.nxos_bfd_interfaces:
+ config:
+ - name: Ethernet1/1
+ bfd: enable
+ echo: enable
+ - name: Ethernet1/2
+ bfd: disable
+ echo: disable
+ state: merged
+
+
+# Using overridden
+
+- name: Configure interfaces
+ cisco.nxos.nxos_bfd_interfaces:
+ config:
+ - name: Ethernet1/1
+ bfd: enable
+ echo: enable
+ - name: Ethernet1/2
+ bfd: disable
+ echo: disable
+ state: overridden
+
+
+# Using replaced
+
+- name: Configure interfaces
+ cisco.nxos.nxos_bfd_interfaces:
+ config:
+ - name: Ethernet1/1
+ bfd: enable
+ echo: enable
+ - name: Ethernet1/2
+ bfd: disable
+ echo: disable
+ state: replaced
+
+# Using rendered
+
+- name: Use rendered state to convert task input to device specific commands
+ cisco.nxos.nxos_bfd_interfaces:
+ config:
+ - name: Ethernet1/800
+ bfd: enable
+ echo: enable
+ - name: Ethernet1/801
+ bfd: disable
+ echo: disable
+ state: rendered
+
+# Task Output (redacted)
+# -----------------------
+
+# rendered:
+# - "interface Ethernet1/800"
+# - "bfd"
+# - "bfd echo"
+# - "interface Ethernet1/801"
+# - "no bfd"
+# - "no bfd echo"
+
+# Using parsed
+
+# parsed.cfg
+# ------------
+
+# feature bfd
+# interface Ethernet1/800
+# no switchport
+# no bfd
+# no bfd echo
+# interface Ethernet1/801
+# no switchport
+# no bfd
+# interface Ethernet1/802
+# no switchport
+# no bfd echo
+# interface mgmt0
+# ip address dhcp
+# vrf member management
+
+- name: Use parsed state to convert externally supplied config to structured format
+ cisco.nxos.nxos_bfd_interfaces:
+ running_config: "{{ lookup('file', 'parsed.cfg') }}"
+ state: parsed
+
+# Task output (redacted)
+# -----------------------
+
+# parsed:
+# - bfd: disable
+# echo: disable
+# name: Ethernet1/800
+# - bfd: disable
+# echo: enable
+# name: Ethernet1/801
+# - bfd: enable
+# echo: disable
+# name: Ethernet1/802
+# - bfd: enable
+# echo: enable
+# name: mgmt0
+
+# Using gathered
+
+# Existing device config state
+# -------------------------------
+
+# feature bfd
+# interface Ethernet1/1
+# no switchport
+# no bfd
+# interface Ethernet1/2
+# no switchport
+# no bfd echo
+# interface mgmt0
+# ip address dhcp
+# vrf member management
+
+- name: Gather bfd_interfaces facts from the device using nxos_bfd_interfaces
+ cisco.nxos.nxos_bfd_interfaces:
+ state: gathered
+
+# Task output (redacted)
+# -----------------------
+# gathered:
+# - name: Ethernet1/1
+# bfd: disable
+# echo: enable
+# - name: Ethernet1/3
+# echo: disable
+# bfd: enable
+# - name: mgmt0
+# bfd: enable
+# echo: enable
+"""
+RETURN = """
+before:
+ description: The configuration as structured data prior to module invocation.
+ returned: always
+ type: list
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+after:
+ description: The configuration as structured data after module completion.
+ returned: when changed
+ type: list
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+commands:
+ description: The set of commands pushed to the remote device.
+ returned: always
+ type: list
+ sample: ['interface Ethernet1/1', 'no bfd', 'no bfd echo']
+"""
+
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.bfd_interfaces.bfd_interfaces import (
+ Bfd_interfacesArgs,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.config.bfd_interfaces.bfd_interfaces import (
+ Bfd_interfaces,
+)
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ required_if = [
+ ("state", "merged", ("config",)),
+ ("state", "replaced", ("config",)),
+ ("state", "overridden", ("config",)),
+ ("state", "rendered", ("config",)),
+ ("state", "parsed", ("running_config",)),
+ ]
+ mutually_exclusive = [("config", "running_config")]
+
+ module = AnsibleModule(
+ argument_spec=Bfd_interfacesArgs.argument_spec,
+ required_if=required_if,
+ mutually_exclusive=mutually_exclusive,
+ supports_check_mode=True,
+ )
+
+ result = Bfd_interfaces(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_bgp.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_bgp.py
new file mode 100644
index 00000000..2c56f412
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_bgp.py
@@ -0,0 +1,761 @@
+#!/usr/bin/python
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_bgp
+extends_documentation_fragment:
+- cisco.nxos.nxos
+short_description: (deprecated, removed after 2023-01-27) Manages BGP configuration.
+description:
+- Manages BGP configurations on NX-OS switches.
+version_added: 1.0.0
+author:
+- Jason Edelman (@jedelman8)
+- Gabriele Gerbino (@GGabriele)
+deprecated:
+ alternative: nxos_bgp_global
+ why: Updated module released with more functionality.
+ removed_at_date: '2023-01-27'
+notes:
+- Tested against NXOSv 7.3.(0)D1(1) on VIRL
+- Unsupported for Cisco MDS
+- C(state=absent) removes the whole BGP ASN configuration when C(vrf=default) or the
+ whole VRF instance within the BGP process when using a different VRF.
+- Default when supported restores params default value.
+- Configuring global params is only permitted if C(vrf=default).
+options:
+ asn:
+ description:
+ - BGP autonomous system number. Valid values are String, Integer in ASPLAIN or
+ ASDOT notation.
+ required: true
+ type: str
+ vrf:
+ description:
+ - Name of the VRF. The name 'default' is a valid VRF representing the global BGP.
+ default: 'default'
+ type: str
+ bestpath_always_compare_med:
+ description:
+ - Enable/Disable MED comparison on paths from different autonomous systems.
+ type: bool
+ bestpath_aspath_multipath_relax:
+ description:
+ - Enable/Disable load sharing across the providers with different (but equal-length)
+ AS paths.
+ type: bool
+ bestpath_compare_routerid:
+ description:
+ - Enable/Disable comparison of router IDs for identical eBGP paths.
+ type: bool
+ bestpath_compare_neighborid:
+ description:
+ - Enable/Disable neighborid. Use this when more paths available than max path
+ config.
+ type: bool
+ bestpath_cost_community_ignore:
+ description:
+ - Enable/Disable Ignores the cost community for BGP best-path calculations.
+ type: bool
+ bestpath_med_confed:
+ description:
+ - Enable/Disable enforcement of bestpath to do a MED comparison only between paths
+ originated within a confederation.
+ type: bool
+ bestpath_med_missing_as_worst:
+ description:
+ - Enable/Disable assigns the value of infinity to received routes that do not
+ carry the MED attribute, making these routes the least desirable.
+ type: bool
+ bestpath_med_non_deterministic:
+ description:
+ - Enable/Disable deterministic selection of the best MED pat from among the paths
+ from the same autonomous system.
+ type: bool
+ cluster_id:
+ description:
+ - Route Reflector Cluster-ID.
+ type: str
+ confederation_id:
+ description:
+ - Routing domain confederation AS.
+ type: str
+ confederation_peers:
+ description:
+ - AS confederation parameters.
+ type: list
+ elements: str
+ disable_policy_batching:
+ description:
+ - Enable/Disable the batching evaluation of prefix advertisement to all peers.
+ type: bool
+ disable_policy_batching_ipv4_prefix_list:
+ description:
+ - Enable/Disable the batching evaluation of prefix advertisements to all peers
+ with prefix list.
+ type: str
+ disable_policy_batching_ipv6_prefix_list:
+ description:
+ - Enable/Disable the batching evaluation of prefix advertisements to all peers
+ with prefix list.
+ type: str
+ enforce_first_as:
+ description:
+ - Enable/Disable enforces the neighbor autonomous system to be the first AS number
+ listed in the AS path attribute for eBGP. On NX-OS, this property is only supported
+ in the global BGP context.
+ type: bool
+ event_history_cli:
+ description:
+ - Enable/Disable cli event history buffer.
+ choices:
+ - size_small
+ - size_medium
+ - size_large
+ - size_disable
+ - default
+ - 'true'
+ - 'false'
+ type: str
+ event_history_detail:
+ description:
+ - Enable/Disable detail event history buffer.
+ choices:
+ - size_small
+ - size_medium
+ - size_large
+ - size_disable
+ - default
+ - 'true'
+ - 'false'
+ type: str
+ event_history_events:
+ description:
+ - Enable/Disable event history buffer.
+ choices:
+ - size_small
+ - size_medium
+ - size_large
+ - size_disable
+ - default
+ - 'true'
+ - 'false'
+ type: str
+ event_history_periodic:
+ description:
+ - Enable/Disable periodic event history buffer.
+ choices:
+ - size_small
+ - size_medium
+ - size_large
+ - size_disable
+ - default
+ - 'true'
+ - 'false'
+ type: str
+ fast_external_fallover:
+ description:
+ - Enable/Disable immediately reset the session if the link to a directly connected
+ BGP peer goes down. Only supported in the global BGP context.
+ type: bool
+ flush_routes:
+ description:
+ - Enable/Disable flush routes in RIB upon controlled restart. On NX-OS, this property
+ is only supported in the global BGP context.
+ type: bool
+ graceful_restart:
+ description:
+ - Enable/Disable graceful restart.
+ type: bool
+ graceful_restart_helper:
+ description:
+ - Enable/Disable graceful restart helper mode.
+ type: bool
+ graceful_restart_timers_restart:
+ description:
+ - Set maximum time for a restart sent to the BGP peer.
+ type: str
+ graceful_restart_timers_stalepath_time:
+ description:
+ - Set maximum time that BGP keeps the stale routes from the restarting BGP peer.
+ type: str
+ isolate:
+ description:
+ - Enable/Disable isolate this router from BGP perspective.
+ type: bool
+ local_as:
+ description:
+ - Local AS number to be used within a VRF instance.
+ type: str
+ log_neighbor_changes:
+ description:
+ - Enable/Disable message logging for neighbor up/down event.
+ type: bool
+ maxas_limit:
+ description:
+ - Specify Maximum number of AS numbers allowed in the AS-path attribute. Valid
+ values are between 1 and 512.
+ type: str
+ neighbor_down_fib_accelerate:
+ description:
+ - Enable/Disable handle BGP neighbor down event, due to various reasons.
+ type: bool
+ reconnect_interval:
+ description:
+ - The BGP reconnection interval for dropped sessions. Valid values are between
+ 1 and 60.
+ type: str
+ router_id:
+ description:
+ - Router Identifier (ID) of the BGP router VRF instance.
+ type: str
+ shutdown:
+ description:
+ - Administratively shutdown the BGP protocol.
+ type: bool
+ suppress_fib_pending:
+ description:
+ - Enable/Disable advertise only routes programmed in hardware to peers.
+ type: bool
+ timer_bestpath_limit:
+ description:
+ - Specify timeout for the first best path after a restart, in seconds.
+ type: str
+ timer_bgp_hold:
+ description:
+ - Set BGP hold timer.
+ type: str
+ timer_bgp_keepalive:
+ description:
+ - Set BGP keepalive timer.
+ type: str
+ state:
+ description:
+ - Determines whether the config should be present or not on the device.
+ default: present
+ choices:
+ - present
+ - absent
+ type: str
+"""
+
+
+EXAMPLES = """
+- name: Configure a simple ASN
+ cisco.nxos.nxos_bgp:
+ asn: 65535
+ vrf: test
+ router_id: 192.0.2.1
+ state: present
+"""
+
+RETURN = """
+commands:
+ description: commands sent to the device
+ returned: always
+ type: list
+ sample: ["router bgp 65535", "vrf test", "router-id 192.0.2.1"]
+"""
+
+import re
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import (
+ CustomNetworkConfig,
+)
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ get_config,
+ load_config,
+)
+
+
+BOOL_PARAMS = [
+ "bestpath_always_compare_med",
+ "bestpath_aspath_multipath_relax",
+ "bestpath_compare_neighborid",
+ "bestpath_compare_routerid",
+ "bestpath_cost_community_ignore",
+ "bestpath_med_confed",
+ "bestpath_med_missing_as_worst",
+ "bestpath_med_non_deterministic",
+ "disable_policy_batching",
+ "enforce_first_as",
+ "fast_external_fallover",
+ "flush_routes",
+ "graceful_restart",
+ "graceful_restart_helper",
+ "isolate",
+ "log_neighbor_changes",
+ "neighbor_down_fib_accelerate",
+ "shutdown",
+ "suppress_fib_pending",
+]
+GLOBAL_PARAMS = [
+ "disable_policy_batching",
+ "disable_policy_batching_ipv4_prefix_list",
+ "disable_policy_batching_ipv6_prefix_list",
+ "enforce_first_as",
+ "event_history_cli",
+ "event_history_detail",
+ "event_history_events",
+ "event_history_periodic",
+ "fast_external_fallover",
+ "flush_routes",
+ "isolate",
+ "suppress_fib_pending",
+ "shutdown",
+]
+PARAM_TO_DEFAULT_KEYMAP = {
+ "timer_bgp_keepalive": "60",
+ "timer_bgp_hold": "180",
+ "timer_bestpath_limit": "300",
+ "graceful_restart": True,
+ "graceful_restart_timers_restart": "120",
+ "graceful_restart_timers_stalepath_time": "300",
+ "reconnect_interval": "60",
+ "suppress_fib_pending": True,
+ "fast_external_fallover": True,
+ "enforce_first_as": True,
+ "event_history_cli": True,
+ "event_history_detail": False,
+ "event_history_events": True,
+ "event_history_periodic": True,
+ "maxas_limit": "",
+ "router_id": "",
+ "cluster_id": "",
+ "disable_policy_batching_ipv4_prefix_list": "",
+ "disable_policy_batching_ipv6_prefix_list": "",
+ "local_as": "",
+ "confederation_id": "",
+}
+PARAM_TO_COMMAND_KEYMAP = {
+ "asn": "router bgp",
+ "bestpath_always_compare_med": "bestpath always-compare-med",
+ "bestpath_aspath_multipath_relax": "bestpath as-path multipath-relax",
+ "bestpath_compare_neighborid": "bestpath compare-neighborid",
+ "bestpath_compare_routerid": "bestpath compare-routerid",
+ "bestpath_cost_community_ignore": "bestpath cost-community ignore",
+ "bestpath_med_confed": "bestpath med confed",
+ "bestpath_med_missing_as_worst": "bestpath med missing-as-worst",
+ "bestpath_med_non_deterministic": "bestpath med non-deterministic",
+ "cluster_id": "cluster-id",
+ "confederation_id": "confederation identifier",
+ "confederation_peers": "confederation peers",
+ "disable_policy_batching": "disable-policy-batching",
+ "disable_policy_batching_ipv4_prefix_list": "disable-policy-batching ipv4 prefix-list",
+ "disable_policy_batching_ipv6_prefix_list": "disable-policy-batching ipv6 prefix-list",
+ "enforce_first_as": "enforce-first-as",
+ "event_history_cli": "event-history cli",
+ "event_history_detail": "event-history detail",
+ "event_history_events": "event-history events",
+ "event_history_periodic": "event-history periodic",
+ "fast_external_fallover": "fast-external-fallover",
+ "flush_routes": "flush-routes",
+ "graceful_restart": "graceful-restart",
+ "graceful_restart_helper": "graceful-restart-helper",
+ "graceful_restart_timers_restart": "graceful-restart restart-time",
+ "graceful_restart_timers_stalepath_time": "graceful-restart stalepath-time",
+ "isolate": "isolate",
+ "local_as": "local-as",
+ "log_neighbor_changes": "log-neighbor-changes",
+ "maxas_limit": "maxas-limit",
+ "neighbor_down_fib_accelerate": "neighbor-down fib-accelerate",
+ "reconnect_interval": "reconnect-interval",
+ "router_id": "router-id",
+ "shutdown": "shutdown",
+ "suppress_fib_pending": "suppress-fib-pending",
+ "timer_bestpath_limit": "timers bestpath-limit",
+ "timer_bgp_hold": "timers bgp",
+ "timer_bgp_keepalive": "timers bgp",
+ "vrf": "vrf",
+}
+
+
+def get_value(arg, config):
+ command = PARAM_TO_COMMAND_KEYMAP.get(arg)
+
+ if command.split()[0] == "event-history":
+ has_size = re.search(r"^\s+{0} size\s(?P<value>.*)$".format(command), config, re.M)
+
+ if command == "event-history detail":
+ value = False
+ else:
+ value = "size_small"
+
+ if has_size:
+ value = "size_%s" % has_size.group("value")
+
+ elif arg in ["enforce_first_as", "fast_external_fallover"]:
+ no_command_re = re.compile(r"no\s+{0}\s*".format(command), re.M)
+ value = True
+
+ if no_command_re.search(config):
+ value = False
+
+ elif arg in BOOL_PARAMS:
+ has_command = re.search(r"^\s+{0}\s*$".format(command), config, re.M)
+ value = False
+
+ if has_command:
+ value = True
+ else:
+ command_val_re = re.compile(r"(?:{0}\s)(?P<value>.*)".format(command), re.M)
+ value = ""
+
+ has_command = command_val_re.search(config)
+ if has_command:
+ found_value = has_command.group("value")
+
+ if arg == "confederation_peers":
+ value = found_value.split()
+ elif arg == "timer_bgp_keepalive":
+ value = found_value.split()[0]
+ elif arg == "timer_bgp_hold":
+ split_values = found_value.split()
+ if len(split_values) == 2:
+ value = split_values[1]
+ elif found_value:
+ value = found_value
+
+ return value
+
+
+def get_existing(module, args, warnings):
+ existing = {}
+ netcfg = CustomNetworkConfig(indent=2, contents=get_config(module, flags=["bgp all"]))
+
+ asn_re = re.compile(r".*router\sbgp\s(?P<existing_asn>\d+(\.\d+)?).*", re.S)
+ asn_match = asn_re.match(str(netcfg))
+
+ if asn_match:
+ existing_asn = asn_match.group("existing_asn")
+ bgp_parent = "router bgp {0}".format(existing_asn)
+
+ if module.params["vrf"] != "default":
+ parents = [bgp_parent, "vrf {0}".format(module.params["vrf"])]
+ else:
+ parents = [bgp_parent]
+
+ config = netcfg.get_section(parents)
+ if config:
+ for arg in args:
+ if arg != "asn" and (module.params["vrf"] == "default" or arg not in GLOBAL_PARAMS):
+ existing[arg] = get_value(arg, config)
+
+ existing["asn"] = existing_asn
+ if module.params["vrf"] == "default":
+ existing["vrf"] = "default"
+
+ if not existing and module.params["vrf"] != "default" and module.params["state"] == "present":
+ msg = "VRF {0} doesn't exist.".format(module.params["vrf"])
+ warnings.append(msg)
+
+ return existing
+
+
+def apply_key_map(key_map, table):
+ new_dict = {}
+ for key in table:
+ new_key = key_map.get(key)
+ if new_key:
+ new_dict[new_key] = table.get(key)
+
+ return new_dict
+
+
+def state_present(module, existing, proposed, candidate):
+ commands = list()
+ proposed_commands = apply_key_map(PARAM_TO_COMMAND_KEYMAP, proposed)
+ existing_commands = apply_key_map(PARAM_TO_COMMAND_KEYMAP, existing)
+
+ for key, value in proposed_commands.items():
+ if value is True:
+ commands.append(key)
+ elif value is False:
+ commands.append("no {0}".format(key))
+ elif value == "default":
+ default_value = PARAM_TO_DEFAULT_KEYMAP.get(key)
+ existing_value = existing_commands.get(key)
+
+ if default_value:
+ commands.append("{0} {1}".format(key, default_value))
+ elif existing_value:
+ if key == "confederation peers":
+ existing_value = " ".join(existing_value)
+ commands.append("no {0} {1}".format(key, existing_value))
+ elif not value:
+ existing_value = existing_commands.get(key)
+ if existing_value:
+ commands.append("no {0} {1}".format(key, existing_value))
+ elif key == "confederation peers":
+ commands.append("{0} {1}".format(key, value))
+ elif key.startswith("timers bgp"):
+ command = "timers bgp {0} {1}".format(
+ proposed["timer_bgp_keepalive"],
+ proposed["timer_bgp_hold"],
+ )
+ if command not in commands:
+ commands.append(command)
+ else:
+ if value.startswith("size"):
+ value = value.replace("_", " ")
+ command = "{0} {1}".format(key, value)
+ commands.append(command)
+
+ parents = []
+ if commands:
+ commands = fix_commands(commands)
+ parents = ["router bgp {0}".format(module.params["asn"])]
+ if module.params["vrf"] != "default":
+ parents.append("vrf {0}".format(module.params["vrf"]))
+ elif proposed:
+ if module.params["vrf"] != "default":
+ commands.append("vrf {0}".format(module.params["vrf"]))
+ parents = ["router bgp {0}".format(module.params["asn"])]
+ else:
+ commands.append("router bgp {0}".format(module.params["asn"]))
+
+ candidate.add(commands, parents=parents)
+
+
+def state_absent(module, existing, candidate):
+ commands = []
+ parents = []
+ if module.params["vrf"] == "default":
+ commands.append("no router bgp {0}".format(module.params["asn"]))
+ elif existing.get("vrf") == module.params["vrf"]:
+ commands.append("no vrf {0}".format(module.params["vrf"]))
+ parents = ["router bgp {0}".format(module.params["asn"])]
+
+ candidate.add(commands, parents=parents)
+
+
+def fix_commands(commands):
+ local_as_command = ""
+ confederation_id_command = ""
+ confederation_peers_command = ""
+
+ for command in commands:
+ if "local-as" in command:
+ local_as_command = command
+ elif "confederation identifier" in command:
+ confederation_id_command = command
+ elif "confederation peers" in command:
+ confederation_peers_command = command
+
+ if local_as_command and confederation_id_command:
+ if "no" in confederation_id_command:
+ commands.pop(commands.index(local_as_command))
+ commands.pop(commands.index(confederation_id_command))
+ commands.append(confederation_id_command)
+ commands.append(local_as_command)
+ else:
+ commands.pop(commands.index(local_as_command))
+ commands.pop(commands.index(confederation_id_command))
+ commands.append(local_as_command)
+ commands.append(confederation_id_command)
+
+ if confederation_peers_command and confederation_id_command:
+ if local_as_command:
+ if "no" in local_as_command:
+ commands.pop(commands.index(local_as_command))
+ commands.pop(commands.index(confederation_id_command))
+ commands.pop(commands.index(confederation_peers_command))
+ commands.append(confederation_id_command)
+ commands.append(confederation_peers_command)
+ commands.append(local_as_command)
+ else:
+ commands.pop(commands.index(local_as_command))
+ commands.pop(commands.index(confederation_id_command))
+ commands.pop(commands.index(confederation_peers_command))
+ commands.append(local_as_command)
+ commands.append(confederation_id_command)
+ commands.append(confederation_peers_command)
+ else:
+ commands.pop(commands.index(confederation_peers_command))
+ commands.pop(commands.index(confederation_id_command))
+ commands.append(confederation_id_command)
+ commands.append(confederation_peers_command)
+
+ return commands
+
+
+def main():
+ argument_spec = dict(
+ asn=dict(required=True, type="str"),
+ vrf=dict(required=False, type="str", default="default"),
+ bestpath_always_compare_med=dict(required=False, type="bool"),
+ bestpath_aspath_multipath_relax=dict(required=False, type="bool"),
+ bestpath_compare_neighborid=dict(required=False, type="bool"),
+ bestpath_compare_routerid=dict(required=False, type="bool"),
+ bestpath_cost_community_ignore=dict(required=False, type="bool"),
+ bestpath_med_confed=dict(required=False, type="bool"),
+ bestpath_med_missing_as_worst=dict(required=False, type="bool"),
+ bestpath_med_non_deterministic=dict(required=False, type="bool"),
+ cluster_id=dict(required=False, type="str"),
+ confederation_id=dict(required=False, type="str"),
+ confederation_peers=dict(required=False, type="list", elements="str"),
+ disable_policy_batching=dict(required=False, type="bool"),
+ disable_policy_batching_ipv4_prefix_list=dict(required=False, type="str"),
+ disable_policy_batching_ipv6_prefix_list=dict(required=False, type="str"),
+ enforce_first_as=dict(required=False, type="bool"),
+ event_history_cli=dict(
+ required=False,
+ choices=[
+ "true",
+ "false",
+ "default",
+ "size_small",
+ "size_medium",
+ "size_large",
+ "size_disable",
+ ],
+ ),
+ event_history_detail=dict(
+ required=False,
+ choices=[
+ "true",
+ "false",
+ "default",
+ "size_small",
+ "size_medium",
+ "size_large",
+ "size_disable",
+ ],
+ ),
+ event_history_events=dict(
+ required=False,
+ choices=[
+ "true",
+ "false",
+ "default",
+ "size_small",
+ "size_medium",
+ "size_large",
+ "size_disable",
+ ],
+ ),
+ event_history_periodic=dict(
+ required=False,
+ choices=[
+ "true",
+ "false",
+ "default",
+ "size_small",
+ "size_medium",
+ "size_large",
+ "size_disable",
+ ],
+ ),
+ fast_external_fallover=dict(required=False, type="bool"),
+ flush_routes=dict(required=False, type="bool"),
+ graceful_restart=dict(required=False, type="bool"),
+ graceful_restart_helper=dict(required=False, type="bool"),
+ graceful_restart_timers_restart=dict(required=False, type="str"),
+ graceful_restart_timers_stalepath_time=dict(required=False, type="str"),
+ isolate=dict(required=False, type="bool"),
+ local_as=dict(required=False, type="str"),
+ log_neighbor_changes=dict(required=False, type="bool"),
+ maxas_limit=dict(required=False, type="str"),
+ neighbor_down_fib_accelerate=dict(required=False, type="bool"),
+ reconnect_interval=dict(required=False, type="str"),
+ router_id=dict(required=False, type="str"),
+ shutdown=dict(required=False, type="bool"),
+ suppress_fib_pending=dict(required=False, type="bool"),
+ timer_bestpath_limit=dict(required=False, type="str"),
+ timer_bgp_hold=dict(required=False, type="str"),
+ timer_bgp_keepalive=dict(required=False, type="str"),
+ state=dict(choices=["present", "absent"], default="present", required=False),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ required_together=[["timer_bgp_hold", "timer_bgp_keepalive"]],
+ supports_check_mode=True,
+ )
+
+ warnings = list()
+ result = dict(changed=False, warnings=warnings)
+
+ state = module.params["state"]
+
+ if module.params["vrf"] != "default":
+ for param in GLOBAL_PARAMS:
+ if module.params[param]:
+ module.fail_json(
+ msg='Global params can be modified only under "default" VRF.',
+ vrf=module.params["vrf"],
+ global_param=param,
+ )
+
+ args = PARAM_TO_COMMAND_KEYMAP.keys()
+ existing = get_existing(module, args, warnings)
+
+ if existing.get("asn") and state == "present":
+ if existing.get("asn") != module.params["asn"]:
+ module.fail_json(
+ msg="Another BGP ASN already exists.",
+ proposed_asn=module.params["asn"],
+ existing_asn=existing.get("asn"),
+ )
+
+ proposed_args = dict((k, v) for k, v in module.params.items() if v is not None and k in args)
+ proposed = {}
+ for key, value in proposed_args.items():
+ if key not in ["asn", "vrf"]:
+ if str(value).lower() == "default":
+ value = PARAM_TO_DEFAULT_KEYMAP.get(key, "default")
+ if key == "confederation_peers":
+ if value[0] == "default":
+ if existing.get(key):
+ proposed[key] = "default"
+ else:
+ v = set([int(i) for i in value])
+ ex = set([int(i) for i in existing.get(key)])
+ if v != ex:
+ proposed[key] = " ".join(str(s) for s in v)
+ else:
+ if existing.get(key) != value:
+ proposed[key] = value
+
+ candidate = CustomNetworkConfig(indent=3)
+ if state == "present":
+ state_present(module, existing, proposed, candidate)
+ elif existing.get("asn") == module.params["asn"]:
+ state_absent(module, existing, candidate)
+
+ if candidate:
+ candidate = candidate.items_text()
+ if not module.check_mode:
+ load_config(module, candidate)
+ result["changed"] = True
+ result["commands"] = candidate
+ else:
+ result["commands"] = []
+
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_bgp_address_family.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_bgp_address_family.py
new file mode 100644
index 00000000..c838d25f
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_bgp_address_family.py
@@ -0,0 +1,1031 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# Copyright 2021 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+"""
+The module file for nxos_bgp_address_family
+"""
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+DOCUMENTATION = """
+module: nxos_bgp_address_family
+short_description: BGP Address Family resource module.
+description:
+- This module manages BGP Address Family configuration on devices running Cisco NX-OS.
+version_added: 2.0.0
+notes:
+- Tested against NX-OS 9.3.6.
+- Unsupported for Cisco MDS
+- For managing BGP neighbor address family configurations please use
+ the M(cisco.nxos.nxos_bgp_neighbor_address_family) module.
+- This module works with connection C(network_cli) and C(httpapi).
+author: Nilashish Chakraborty (@NilashishC)
+options:
+ running_config:
+ description:
+ - This option is used only with state I(parsed).
+ - The value of this option should be the output received from the NX-OS device
+ by executing the command B(show running-config | section '^router bgp').
+ - The state I(parsed) reads the configuration from C(running_config) option and
+ transforms it into Ansible structured data as per the resource module's argspec
+ and the value is then returned in the I(parsed) key within the result.
+ type: str
+ config:
+ description: A list of BGP process configuration.
+ type: dict
+ suboptions:
+ as_number:
+ description: Autonomous System Number of the router.
+ type: str
+ address_family:
+ description: Address Family related configurations.
+ type: list
+ elements: dict
+ suboptions:
+ afi:
+ description: Address Family indicator.
+ type: str
+ choices: ["ipv4", "ipv6", "link-state", "vpnv4", "vpnv6", "l2vpn"]
+ required: True
+ safi:
+ description: Sub Address Family indicator.
+ type: str
+ choices: ["unicast", "multicast", "mvpn", "evpn"]
+ additional_paths:
+ description: Additional paths configuration.
+ type: dict
+ suboptions:
+ install_backup:
+ description: Install backup path.
+ type: bool
+ receive:
+ description: Additional paths Receive capability.
+ type: bool
+ selection:
+ description: Additional paths selection
+ type: dict
+ suboptions:
+ route_map:
+ description: Route-map for additional paths selection
+ type: str
+ send:
+ description: Additional paths Send capability
+ type: bool
+ advertise_pip:
+ description: Advertise physical ip for type-5 route.
+ type: bool
+ advertise_l2vpn_evpn:
+ description: Enable advertising EVPN routes.
+ type: bool
+ advertise_system_mac:
+ description: Advertise extra EVPN RT-2 with system MAC.
+ type: bool
+ allow_vni_in_ethertag:
+ description: Allow VNI in Ethernet Tag field in EVPN route.
+ type: bool
+ aggregate_address:
+ description: Configure BGP aggregate prefixes
+ type: list
+ elements: dict
+ suboptions:
+ prefix:
+ description: Aggregate prefix.
+ type: str
+ advertise_map:
+ description: Select attribute information from specific routes.
+ type: str
+ as_set:
+ description: Generate AS-SET information.
+ type: bool
+ attribute_map:
+ description: Set attribute information of aggregate.
+ type: str
+ summary_only:
+ description: Do not advertise more specifics.
+ type: bool
+ suppress_map:
+ description: Conditionally filter more specific routes.
+ type: str
+ client_to_client:
+ description: Configure client-to-client route reflection.
+ type: dict
+ suboptions:
+ no_reflection:
+ description: Reflection of routes permitted.
+ type: bool
+ dampen_igp_metric:
+ description: Dampen IGP metric-related changes.
+ type: int
+ dampening:
+ description: Configure route flap dampening.
+ type: dict
+ suboptions:
+ set:
+ description: Set route flap dampening.
+ type: bool
+ decay_half_life:
+ description: Decay half life.
+ type: int
+ start_reuse_route:
+ description: Value to start reusing a route.
+ type: int
+ start_suppress_route:
+ description: Value to start suppressing a route.
+ type: int
+ max_suppress_time:
+ description: Maximum suppress time for stable route.
+ type: int
+ route_map:
+ description: Apply route-map to specify dampening criteria.
+ type: str
+ default_information:
+ description: Control distribution of default information.
+ type: dict
+ suboptions:
+ originate:
+ description: Distribute a default route.
+ type: bool
+ default_metric:
+ description: Set metric of redistributed routes.
+ type: int
+ distance:
+ description: Configure administrative distance.
+ type: dict
+ suboptions:
+ ebgp_routes:
+ description: Distance for EBGP routes.
+ type: int
+ ibgp_routes:
+ description: Distance for IBGP routes.
+ type: int
+ local_routes:
+ description: Distance for local routes.
+ type: int
+ export_gateway_ip:
+ description: Export Gateway IP to Type-5 EVPN routes for VRF
+ type: bool
+ inject_map:
+ description: Routemap which specifies prefixes to inject.
+ type: list
+ elements: dict
+ suboptions:
+ route_map:
+ description: Route-map name.
+ type: str
+ exist_map:
+ description: Routemap which specifies exist condition.
+ type: str
+ copy_attributes:
+ description: Copy attributes from aggregate.
+ type: bool
+ maximum_paths:
+ description: Forward packets over multipath paths.
+ type: dict
+ suboptions:
+ parallel_paths:
+ description: Number of parallel paths.
+ type: int
+ ibgp:
+ description: Configure multipath for IBGP paths.
+ type: dict
+ suboptions:
+ parallel_paths:
+ description: Number of parallel paths.
+ type: int
+ eibgp:
+ description: Configure multipath for both EBGP and IBGP paths.
+ type: dict
+ suboptions:
+ parallel_paths:
+ description: Number of parallel paths.
+ type: int
+ local:
+ description: Configure multipath for local paths.
+ type: dict
+ suboptions:
+ parallel_paths:
+ description: Number of parallel paths.
+ type: int
+ mixed:
+ description: Configure multipath for local and remote paths.
+ type: dict
+ suboptions:
+ parallel_paths:
+ description: Number of parallel paths.
+ type: int
+ networks:
+ description: Configure an IP prefix to advertise.
+ type: list
+ elements: dict
+ suboptions:
+ prefix:
+ description: IP prefix in CIDR format.
+ type: str
+ route_map:
+ description: Route-map name.
+ type: str
+ nexthop:
+ description: Nexthop tracking.
+ type: dict
+ suboptions:
+ route_map:
+ description: Route-map name.
+ type: str
+ trigger_delay:
+ description: Set the delay to trigger nexthop tracking.
+ type: dict
+ suboptions:
+ critical_delay:
+ description:
+ - Nexthop changes affecting reachability.
+ - Delay value (miliseconds).
+ type: int
+ non_critical_delay:
+ description:
+ - Other nexthop changes.
+ - Delay value (miliseconds).
+ type: int
+ redistribute:
+ description: Configure redistribution.
+ type: list
+ elements: dict
+ suboptions:
+ protocol:
+ description:
+ - The name of the protocol.
+ type: str
+ choices: ["am", "direct", "eigrp", "isis", "lisp", "ospf", "ospfv3", "rip", "static", "hmm"]
+ required: true
+ id:
+ description:
+ - The identifier for the protocol specified.
+ type: str
+ route_map:
+ description:
+ - The route map policy to constrain redistribution.
+ type: str
+ required: true
+ retain:
+ description: Retain the routes based on Target VPN Extended Communities.
+ type: dict
+ suboptions:
+ route_target:
+ description: Specify Target VPN Extended Communities
+ type: dict
+ suboptions:
+ retain_all:
+ description: All the routes regardless of Target-VPN community
+ type: bool
+ route_map:
+ description: Apply route-map to filter routes.
+ type: str
+ suppress_inactive:
+ description: Advertise only active routes to peers.
+ type: bool
+ table_map:
+ description:
+ - Policy for filtering/modifying OSPF routes before sending them to RIB.
+ type: dict
+ suboptions:
+ name:
+ description:
+ - The Route Map name.
+ type: str
+ required: true
+ filter:
+ description:
+ - Block the OSPF routes from being sent to RIB.
+ type: bool
+ timers:
+ description: Configure bgp related timers.
+ type: dict
+ suboptions:
+ bestpath_defer:
+ description: Configure bestpath defer timer value for batch prefix processing.
+ type: dict
+ suboptions:
+ defer_time:
+ description: Bestpath defer time (mseconds).
+ type: int
+ maximum_defer_time:
+ description: Maximum bestpath defer time (mseconds).
+ type: int
+ wait_igp_convergence:
+ description: Delay initial bestpath until redistributed IGPs have converged.
+ type: bool
+ vrf:
+ description: Virtual Router Context.
+ type: str
+ state:
+ description:
+ - The state the configuration should be left in.
+ - State I(deleted) only removes BGP attributes that this modules
+ manages and does not negate the BGP process completely.
+ - Refer to examples for more details.
+ type: str
+ choices:
+ - merged
+ - replaced
+ - overridden
+ - deleted
+ - parsed
+ - gathered
+ - rendered
+ default: merged
+"""
+EXAMPLES = """
+# Using merged
+
+# Before state:
+# -------------
+# Nexus9000v# show running-config | section "^router bgp"
+# Nexus9000v#
+
+- name: Merge the provided configuration with the existing running configuration
+ cisco.nxos.nxos_bgp_address_family:
+ config:
+ as_number: 65536
+ address_family:
+ - afi: ipv4
+ safi: multicast
+ networks:
+ - prefix: 192.0.2.32/27
+ - prefix: 192.0.2.64/27
+ route_map: rmap1
+ nexthop:
+ route_map: rmap2
+ trigger_delay:
+ critical_delay: 120
+ non_critical_delay: 180
+ - afi: ipv4
+ safi: unicast
+ vrf: site-1
+ default_information:
+ originate: True
+ aggregate_address:
+ - prefix: 203.0.113.0/24
+ as_set: True
+ summary_only: True
+ - afi: ipv6
+ safi: multicast
+ vrf: site-1
+ redistribute:
+ - protocol: ospfv3
+ id: 100
+ route_map: rmap-ospf-1
+ - protocol: eigrp
+ id: 101
+ route_map: rmap-eigrp-1
+
+# Task output
+# -------------
+# before: {}
+#
+# commands:
+# - router bgp 65536
+# - address-family ipv4 multicast
+# - nexthop route-map rmap2
+# - nexthop trigger-delay critical 120 non-critical 180
+# - network 192.0.2.32/27
+# - network 192.0.2.64/27 route-map rmap1
+# - vrf site-1
+# - address-family ipv4 unicast
+# - default-information originate
+# - aggregate-address 203.0.113.0/24 as-set summary-only
+# - address-family ipv6 multicast
+# - redistribute ospfv3 100 route-map rmap-ospf-1
+# - redistribute eigrp 101 route-map rmap-eigrp-1
+#
+# after:
+# as_number: "65536"
+# address_family:
+# - afi: ipv4
+# safi: multicast
+# networks:
+# - prefix: 192.0.2.32/27
+# - prefix: 192.0.2.64/27
+# route_map: rmap1
+# nexthop:
+# route_map: rmap2
+# trigger_delay:
+# critical_delay: 120
+# non_critical_delay: 180
+# - afi: ipv4
+# safi: unicast
+# vrf: site-1
+# default_information:
+# originate: True
+# aggregate_address:
+# - prefix: 203.0.113.0/24
+# as_set: True
+# summary_only: True
+# - afi: ipv6
+# safi: multicast
+# vrf: site-1
+# redistribute:
+# - id: "100"
+# protocol: ospfv3
+# route_map: rmap-ospf-1
+# - id: "101"
+# protocol: eigrp
+# route_map: rmap-eigrp-1
+
+# After state:
+# -------------
+# Nexus9000v# show running-config | section "^router bgp"
+# router bgp 65536
+# address-family ipv4 multicast
+# nexthop route-map rmap2
+# nexthop trigger-delay critical 120 non-critical 180
+# network 192.0.2.32/27
+# network 192.0.2.64/27 route-map rmap1
+# vrf site-1
+# address-family ipv4 unicast
+# default-information originate
+# aggregate-address 203.0.113.0/24 as-set summary-only
+# address-family ipv6 multicast
+# redistribute ospfv3 100 route-map rmap-ospf-1
+# redistribute eigrp 101 route-map rmap-eigrp-1
+#
+
+# Using replaced
+
+# Before state:
+# -------------
+# Nexus9000v# show running-config | section "^router bgp"
+# router bgp 65536
+# address-family ipv4 multicast
+# nexthop route-map rmap2
+# nexthop trigger-delay critical 120 non-critical 180
+# network 192.0.2.32/27
+# network 192.0.2.64/27 route-map rmap1
+# vrf site-1
+# address-family ipv4 unicast
+# default-information originate
+# aggregate-address 203.0.113.0/24 as-set summary-only
+# address-family ipv6 multicast
+# redistribute ospfv3 100 route-map rmap-ospf-1
+# redistribute eigrp 101 route-map rmap-eigrp-1
+
+- name: Replace configuration of specified AFs
+ cisco.nxos.nxos_bgp_address_family:
+ config:
+ as_number: 65536
+ address_family:
+ - afi: ipv4
+ safi: multicast
+ networks:
+ - prefix: 192.0.2.64/27
+ route_map: rmap1
+ nexthop:
+ route_map: rmap2
+ trigger_delay:
+ critical_delay: 120
+ non_critical_delay: 180
+ aggregate_address:
+ - prefix: 203.0.113.0/24
+ as_set: True
+ summary_only: True
+ - afi: ipv4
+ safi: unicast
+ vrf: site-1
+ state: replaced
+
+# Task output
+# -------------
+# before:
+# as_number: "65536"
+# address_family:
+# - afi: ipv4
+# safi: multicast
+# networks:
+# - prefix: 192.0.2.32/27
+# - prefix: 192.0.2.64/27
+# route_map: rmap1
+# nexthop:
+# route_map: rmap2
+# trigger_delay:
+# critical_delay: 120
+# non_critical_delay: 180
+# - afi: ipv4
+# safi: unicast
+# vrf: site-1
+# default_information:
+# originate: True
+# aggregate_address:
+# - prefix: 203.0.113.0/24
+# as_set: True
+# summary_only: True
+# - afi: ipv6
+# safi: multicast
+# vrf: site-1
+# redistribute:
+# - id: "100"
+# protocol: ospfv3
+# route_map: rmap-ospf-1
+# - id: "101"
+# protocol: eigrp
+# route_map: rmap-eigrp-1
+#
+# commands:
+# - router bgp 65536
+# - address-family ipv4 multicast
+# - no network 192.0.2.32/27
+# - aggregate-address 203.0.113.0/24 as-set summary-only
+# - vrf site-1
+# - address-family ipv4 unicast
+# - no default-information originate
+# - no aggregate-address 203.0.113.0/24 as-set summary-only
+#
+# after:
+# as_number: "65536"
+# address_family:
+# - afi: ipv4
+# safi: multicast
+# networks:
+# - prefix: 192.0.2.64/27
+# route_map: rmap1
+# nexthop:
+# route_map: rmap2
+# trigger_delay:
+# critical_delay: 120
+# non_critical_delay: 180
+# aggregate_address:
+# - prefix: 203.0.113.0/24
+# as_set: True
+# summary_only: True
+#
+# - afi: ipv4
+# safi: unicast
+# vrf: site-1
+#
+# - afi: ipv6
+# safi: multicast
+# vrf: site-1
+# redistribute:
+# - protocol: ospfv3
+# id: "100"
+# route_map: rmap-ospf-1
+# - protocol: eigrp
+# id: "101"
+# route_map: rmap-eigrp-1
+
+# After state:
+# -------------
+# Nexus9000v# show running-config | section "^router bgp"
+# router bgp 65536
+# address-family ipv4 multicast
+# nexthop route-map rmap2
+# nexthop trigger-delay critical 120 non-critical 180
+# network 192.0.2.64/27 route-map rmap1
+# aggregate-address 203.0.113.0/24 as-set summary-only
+# vrf site-1
+# address-family ipv4 unicast
+# address-family ipv6 multicast
+# redistribute ospfv3 100 route-map rmap-ospf-1
+# redistribute eigrp 101 route-map rmap-eigrp-1
+
+# Using overridden
+
+# Before state:
+# -------------
+# Nexus9000v# show running-config | section "^router bgp"
+# router bgp 65536
+# address-family ipv4 multicast
+# nexthop route-map rmap2
+# nexthop trigger-delay critical 120 non-critical 180
+# network 192.0.2.32/27
+# network 192.0.2.64/27 route-map rmap1
+# vrf site-1
+# address-family ipv4 unicast
+# default-information originate
+# aggregate-address 203.0.113.0/24 as-set summary-only
+# address-family ipv6 multicast
+# redistribute ospfv3 100 route-map rmap-ospf-1
+# redistribute eigrp 101 route-map rmap-eigrp-1
+
+- name: Override all BGP AF configuration with provided configuration
+ cisco.nxos.nxos_bgp_address_family: &overridden
+ config:
+ as_number: 65536
+ address_family:
+ - afi: ipv4
+ safi: multicast
+ networks:
+ - prefix: 192.0.2.64/27
+ route_map: rmap1
+ aggregate_address:
+ - prefix: 203.0.113.0/24
+ as_set: True
+ summary_only: True
+ - afi: ipv4
+ safi: unicast
+ vrf: site-1
+ state: overridden
+
+# Task output
+# -------------
+# before:
+# as_number: "65536"
+# address_family:
+# - afi: ipv4
+# safi: multicast
+# networks:
+# - prefix: 192.0.2.32/27
+# - prefix: 192.0.2.64/27
+# route_map: rmap1
+# nexthop:
+# route_map: rmap2
+# trigger_delay:
+# critical_delay: 120
+# non_critical_delay: 180
+# - afi: ipv4
+# safi: unicast
+# vrf: site-1
+# default_information:
+# originate: True
+# aggregate_address:
+# - prefix: 203.0.113.0/24
+# as_set: True
+# summary_only: True
+# - afi: ipv6
+# safi: multicast
+# vrf: site-1
+# redistribute:
+# - id: "100"
+# protocol: ospfv3
+# route_map: rmap-ospf-1
+# - id: "101"
+# protocol: eigrp
+# route_map: rmap-eigrp-1
+#
+# commands:
+# - router bgp 65536
+# - vrf site-1
+# - no address-family ipv6 multicast
+# - exit
+# - address-family ipv4 multicast
+# - no nexthop route-map rmap2
+# - no nexthop trigger-delay critical 120 non-critical 180
+# - aggregate-address 203.0.113.0/24 as-set summary-only
+# - no network 192.0.2.32/27
+# - vrf site-1
+# - address-family ipv4 unicast
+# - no default-information originate
+# - no aggregate-address 203.0.113.0/24 as-set summary-only
+#
+# after:
+# as_number: "65536"
+# address_family:
+# - afi: ipv4
+# safi: multicast
+# networks:
+# - prefix: 192.0.2.64/27
+# route_map: rmap1
+# aggregate_address:
+# - prefix: 203.0.113.0/24
+# as_set: True
+# summary_only: True
+# - afi: ipv4
+# safi: unicast
+# vrf: site-1
+
+#
+# After state:
+# -------------
+# Nexus9000v# show running-config | section "^router bgp"
+# router bgp 65536
+# address-family ipv4 multicast
+# network 192.0.2.64/27 route-map rmap1
+# aggregate-address 203.0.113.0/24 as-set summary-only
+# vrf site-1
+# address-family ipv4 unicast
+#
+
+# Using deleted to remove specified AFs
+
+# Before state:
+# -------------
+# Nexus9000v# show running-config | section "^router bgp"
+# router bgp 65536
+# address-family ipv4 multicast
+# nexthop route-map rmap2
+# nexthop trigger-delay critical 120 non-critical 180
+# network 192.0.2.32/27
+# network 192.0.2.64/27 route-map rmap1
+# vrf site-1
+# address-family ipv4 unicast
+# default-information originate
+# aggregate-address 203.0.113.0/24 as-set summary-only
+# address-family ipv6 multicast
+# redistribute ospfv3 100 route-map rmap-ospf-1
+# redistribute eigrp 101 route-map rmap-eigrp-1
+
+- name: Delete specified BGP AFs
+ cisco.nxos.nxos_bgp_address_family:
+ config:
+ as_number: 65536
+ address_family:
+ - afi: ipv4
+ safi: multicast
+ - vrf: site-1
+ afi: ipv6
+ safi: multicast
+ state: deleted
+
+# Task output
+# -------------
+# before:
+# as_number: "65536"
+# address_family:
+# - afi: ipv4
+# safi: multicast
+# networks:
+# - prefix: 192.0.2.32/27
+# - prefix: 192.0.2.64/27
+# route_map: rmap1
+# nexthop:
+# route_map: rmap2
+# trigger_delay:
+# critical_delay: 120
+# non_critical_delay: 180
+# - afi: ipv4
+# safi: unicast
+# vrf: site-1
+# default_information:
+# originate: True
+# aggregate_address:
+# - prefix: 203.0.113.0/24
+# as_set: True
+# summary_only: True
+# - afi: ipv6
+# safi: multicast
+# vrf: site-1
+# redistribute:
+# - id: "100"
+# protocol: ospfv3
+# route_map: rmap-ospf-1
+# - id: "101"
+# protocol: eigrp
+# route_map: rmap-eigrp-1
+#
+# commands:
+# - router bgp 65563
+# - no address-family ipv4 multicast
+# - vrf site-1
+# - no address-family ipv6 multicast
+#
+# after:
+# as_number: "65536"
+# address_family:
+# - afi: ipv4
+# safi: unicast
+# vrf: site-1
+# default_information:
+# originate: True
+# aggregate_address:
+# - prefix: 203.0.113.0/24
+# as_set: True
+# summary_only: True
+
+# After state:
+# -------------
+# Nexus9000v# show running-config | section "^router bgp"
+# router bgp 65536
+# vrf site-1
+# address-family ipv4 unicast
+# default-information originate
+# aggregate-address 203.0.113.0/24 as-set summary-only
+
+# Using deleted to remove all BGP AFs
+
+# Before state:
+# -------------
+# Nexus9000v# show running-config | section "^router bgp"
+# router bgp 65536
+# address-family ipv4 multicast
+# nexthop route-map rmap2
+# nexthop trigger-delay critical 120 non-critical 180
+# network 192.0.2.32/27
+# network 192.0.2.64/27 route-map rmap1
+# vrf site-1
+# address-family ipv4 unicast
+# default-information originate
+# aggregate-address 203.0.113.0/24 as-set summary-only
+# address-family ipv6 multicast
+# redistribute ospfv3 100 route-map rmap-ospf-1
+# redistribute eigrp 101 route-map rmap-eigrp-1
+
+- name: Delete all BGP AFs
+ cisco.nxos.nxos_bgp_address_family:
+ state: deleted
+
+# Task output
+# -------------
+# before:
+# as_number: "65536"
+# address_family:
+# - afi: ipv4
+# safi: multicast
+# networks:
+# - prefix: 192.0.2.32/27
+# - prefix: 192.0.2.64/27
+# route_map: rmap1
+# nexthop:
+# route_map: rmap2
+# trigger_delay:
+# critical_delay: 120
+# non_critical_delay: 180
+# - afi: ipv4
+# safi: unicast
+# vrf: site-1
+# default_information:
+# originate: True
+# aggregate_address:
+# - prefix: 203.0.113.0/24
+# as_set: True
+# summary_only: True
+# - afi: ipv6
+# safi: multicast
+# vrf: site-1
+# redistribute:
+# - id: "100"
+# protocol: ospfv3
+# route_map: rmap-ospf-1
+# - id: "101"
+# protocol: eigrp
+# route_map: rmap-eigrp-1
+#
+# commands:
+# - router bgp 65563
+# - no address-family ipv4 multicast
+# - vrf site-1
+# - no address-family ipv4 unicast
+# - no address-family ipv6 multicast
+#
+# after:
+# as_number: "65536"
+
+# After state:
+# -------------
+# Nexus9000v# show running-config | section "^router bgp"
+# router bgp 65536
+# Nexus9000v#
+
+# Using rendered
+
+- name: Render platform specific configuration lines with state rendered (without connecting to the device)
+ cisco.nxos.nxos_bgp_address_family:
+ config:
+ as_number: 65536
+ address_family:
+ - afi: ipv4
+ safi: multicast
+ networks:
+ - prefix: 192.0.2.32/27
+ - prefix: 192.0.2.64/27
+ route_map: rmap1
+ nexthop:
+ route_map: rmap2
+ trigger_delay:
+ critical_delay: 120
+ non_critical_delay: 180
+ - afi: ipv4
+ safi: unicast
+ vrf: site-1
+ default_information:
+ originate: True
+ aggregate_address:
+ - prefix: 203.0.113.0/24
+ as_set: True
+ summary_only: True
+ - afi: ipv6
+ safi: multicast
+ vrf: site-1
+ redistribute:
+ - protocol: ospfv3
+ id: 100
+ route_map: rmap-ospf-1
+ - protocol: eigrp
+ id: 101
+ route_map: rmap-eigrp-1
+ state: rendered
+
+# Task Output (redacted)
+# -----------------------
+# rendered:
+# - router bgp 65536
+# - address-family ipv4 multicast
+# - nexthop route-map rmap2
+# - nexthop trigger-delay critical 120 non-critical 180
+# - network 192.0.2.32/27
+# - network 192.0.2.64/27 route-map rmap1
+# - vrf site-1
+# - address-family ipv4 unicast
+# - default-information originate
+# - aggregate-address 203.0.113.0/24 as-set summary-only
+# - address-family ipv6 multicast
+# - redistribute ospfv3 100 route-map rmap-ospf-1
+# - redistribute eigrp 101 route-map rmap-eigrp-1
+
+# Using parsed
+
+# parsed.cfg
+# ------------
+# router bgp 65536
+# address-family ipv4 multicast
+# nexthop route-map rmap2
+# nexthop trigger-delay critical 120 non-critical 180
+# network 192.0.2.32/27
+# network 192.0.2.64/27 route-map rmap1
+# vrf site-1
+# address-family ipv4 unicast
+# default-information originate
+# aggregate-address 203.0.113.0/24 as-set summary-only
+# address-family ipv6 multicast
+# redistribute ospfv3 100 route-map rmap-ospf-1
+# redistribute eigrp 101 route-map rmap-eigrp-1
+
+- name: Parse externally provided BGP AF config
+ cisco.nxos.nxos_bgp_address_family:
+ running_config: "{{ lookup('file', 'parsed.cfg') }}"
+ state: parsed
+
+# Task output (redacted)
+# -----------------------
+# parsed:
+# as_number: "65536"
+# address_family:
+# - afi: ipv4
+# safi: multicast
+# networks:
+# - prefix: 192.0.2.32/27
+# - prefix: 192.0.2.64/27
+# route_map: rmap1
+# nexthop:
+# route_map: rmap2
+# trigger_delay:
+# critical_delay: 120
+# non_critical_delay: 180
+# - afi: ipv4
+# safi: unicast
+# vrf: site-1
+# default_information:
+# originate: True
+# aggregate_address:
+# - prefix: 203.0.113.0/24
+# as_set: True
+# summary_only: True
+# - afi: ipv6
+# safi: multicast
+# vrf: site-1
+# redistribute:
+# - id: "100"
+# protocol: ospfv3
+# route_map: rmap-ospf-1
+# - id: "101"
+# protocol: eigrp
+# route_map: rmap-eigrp-1
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.bgp_address_family.bgp_address_family import (
+ Bgp_address_familyArgs,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.config.bgp_address_family.bgp_address_family import (
+ Bgp_address_family,
+)
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ module = AnsibleModule(
+ argument_spec=Bgp_address_familyArgs.argument_spec,
+ mutually_exclusive=[["config", "running_config"]],
+ required_if=[
+ ["state", "merged", ["config"]],
+ ["state", "replaced", ["config"]],
+ ["state", "overridden", ["config"]],
+ ["state", "rendered", ["config"]],
+ ["state", "parsed", ["running_config"]],
+ ],
+ supports_check_mode=True,
+ )
+
+ result = Bgp_address_family(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_bgp_af.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_bgp_af.py
new file mode 100644
index 00000000..290f241d
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_bgp_af.py
@@ -0,0 +1,877 @@
+#!/usr/bin/python
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_bgp_af
+extends_documentation_fragment:
+- cisco.nxos.nxos
+short_description: (deprecated, removed after 2023-02-24) Manages BGP Address-family configuration.
+description:
+- Manages BGP Address-family configurations on NX-OS switches.
+version_added: 1.0.0
+author: Gabriele Gerbino (@GGabriele)
+deprecated:
+ alternative: nxos_bgp_address_family
+ why: Updated module released with more functionality.
+ removed_at_date: '2023-02-24'
+notes:
+- Tested against NXOSv 7.3.(0)D1(1) on VIRL
+- Unsupported for Cisco MDS
+- C(state=absent) removes the whole BGP ASN configuration
+- Default, where supported, restores params default value.
+options:
+ asn:
+ description:
+ - BGP autonomous system number. Valid values are String, Integer in ASPLAIN or
+ ASDOT notation.
+ required: true
+ type: str
+ vrf:
+ description:
+ - Name of the VRF. The name 'default' is a valid VRF representing the global bgp.
+ default: 'default'
+ type: str
+ afi:
+ description:
+ - Address Family Identifier.
+ required: true
+ choices:
+ - ipv4
+ - ipv6
+ - vpnv4
+ - vpnv6
+ - l2vpn
+ type: str
+ safi:
+ description:
+ - Sub Address Family Identifier.
+ required: true
+ choices:
+ - unicast
+ - multicast
+ - evpn
+ type: str
+ additional_paths_install:
+ description:
+ - Install a backup path into the forwarding table and provide prefix independent
+ convergence (PIC) in case of a PE-CE link failure.
+ type: bool
+ additional_paths_receive:
+ description:
+ - Enables the receive capability of additional paths for all of the neighbors
+ under this address family for which the capability has not been disabled.
+ type: bool
+ additional_paths_selection:
+ description:
+ - Configures the capability of selecting additional paths for a prefix. Valid
+ values are a string defining the name of the route-map.
+ type: str
+ additional_paths_send:
+ description:
+ - Enables the send capability of additional paths for all of the neighbors under
+ this address family for which the capability has not been disabled.
+ type: bool
+ advertise_l2vpn_evpn:
+ description:
+ - Advertise evpn routes.
+ type: bool
+ client_to_client:
+ description:
+ - Configure client-to-client route reflection.
+ type: bool
+ dampen_igp_metric:
+ description:
+ - Specify dampen value for IGP metric-related changes, in seconds. Valid values
+ are integer and keyword 'default'.
+ type: str
+ dampening_state:
+ description:
+ - Enable/disable route-flap dampening.
+ type: bool
+ dampening_half_time:
+ description:
+ - Specify decay half-life in minutes for route-flap dampening. Valid values are
+ integer and keyword 'default'.
+ type: str
+ dampening_max_suppress_time:
+ description:
+ - Specify max suppress time for route-flap dampening stable route. Valid values
+ are integer and keyword 'default'.
+ type: str
+ dampening_reuse_time:
+ description:
+ - Specify route reuse time for route-flap dampening. Valid values are integer
+ and keyword 'default'.
+ type: str
+ dampening_routemap:
+ description:
+ - Specify route-map for route-flap dampening. Valid values are a string defining
+ the name of the route-map.
+ type: str
+ dampening_suppress_time:
+ description:
+ - Specify route suppress time for route-flap dampening. Valid values are integer
+ and keyword 'default'.
+ type: str
+ default_information_originate:
+ description:
+ - Default information originate.
+ type: bool
+ default_metric:
+ description:
+ - Sets default metrics for routes redistributed into BGP. Valid values are Integer
+ or keyword 'default'
+ type: str
+ distance_ebgp:
+ description:
+ - Sets the administrative distance for eBGP routes. Valid values are Integer or
+ keyword 'default'.
+ type: str
+ distance_ibgp:
+ description:
+ - Sets the administrative distance for iBGP routes. Valid values are Integer or
+ keyword 'default'.
+ type: str
+ distance_local:
+ description:
+ - Sets the administrative distance for local BGP routes. Valid values are Integer
+ or keyword 'default'.
+ type: str
+ inject_map:
+ description:
+ - An array of route-map names which will specify prefixes to inject. Each array
+ entry must first specify the inject-map name, secondly an exist-map name, and
+ optionally the copy-attributes keyword which indicates that attributes should
+ be copied from the aggregate. For example [['lax_inject_map', 'lax_exist_map'],
+ ['nyc_inject_map', 'nyc_exist_map', 'copy-attributes'], ['fsd_inject_map', 'fsd_exist_map']].
+ type: list
+ elements: list
+ maximum_paths:
+ description:
+ - Configures the maximum number of equal-cost paths for load sharing. Valid value
+ is an integer in the range 1-64.
+ type: str
+ maximum_paths_ibgp:
+ description:
+ - Configures the maximum number of ibgp equal-cost paths for load sharing. Valid
+ value is an integer in the range 1-64.
+ type: str
+ networks:
+ description:
+ - Networks to configure. Valid value is a list of network prefixes to advertise.
+ The list must be in the form of an array. Each entry in the array must include
+ a prefix address and an optional route-map. For example [['10.0.0.0/16', 'routemap_LA'],
+ ['192.168.1.1', 'Chicago'], ['192.168.2.0/24'], ['192.168.3.0/24', 'routemap_NYC']].
+ type: list
+ elements: list
+ next_hop_route_map:
+ description:
+ - Configure a route-map for valid nexthops. Valid values are a string defining
+ the name of the route-map.
+ type: str
+ redistribute:
+ description:
+ - A list of redistribute directives. Multiple redistribute entries are allowed.
+ The list must be in the form of a nested array. the first entry of each array
+ defines the source-protocol to redistribute from; the second entry defines a
+ route-map name. A route-map is highly advised but may be optional on some platforms,
+ in which case it may be omitted from the array list. For example [['direct',
+ 'rm_direct'], ['lisp', 'rm_lisp']].
+ type: list
+ elements: list
+ suppress_inactive:
+ description:
+ - Advertises only active routes to peers.
+ type: bool
+ table_map:
+ description:
+ - Apply table-map to filter routes downloaded into URIB. Valid values are a string.
+ type: str
+ table_map_filter:
+ description:
+ - Filters routes rejected by the route-map and does not download them to the RIB.
+ type: bool
+ state:
+ description:
+ - Determines whether the config should be present or not on the device.
+ default: present
+ choices:
+ - present
+ - absent
+ type: str
+ retain_route_target:
+ description:
+ - Retains all of the routes or the routes which are part of configured route-map.
+ Valid values are route-map names or keyword C(all) or keyword C(default). C(all)
+ retains all the routes regardless of Target-VPN community. C(default) will disable
+ the retain route target option. If you are using route-map name please ensure
+ that the name is not same as C(all) and C(default).
+ type: str
+ version_added: 1.1.0
+"""
+EXAMPLES = """
+# configure a simple address-family
+- cisco.nxos.nxos_bgp_af:
+ asn: 65535
+ vrf: TESTING
+ afi: ipv4
+ safi: unicast
+ advertise_l2vpn_evpn: true
+ state: present
+ retain_route_target: all
+"""
+
+RETURN = """
+commands:
+ description: commands sent to the device
+ returned: always
+ type: list
+ sample: ["router bgp 65535", "vrf TESTING",
+ "address-family ipv4 unicast", "advertise l2vpn evpn",
+ "retain route-target all"]
+"""
+
+import re
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import (
+ CustomNetworkConfig,
+)
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ get_config,
+ load_config,
+)
+
+
+BOOL_PARAMS = [
+ "additional_paths_install",
+ "additional_paths_receive",
+ "additional_paths_send",
+ "advertise_l2vpn_evpn",
+ "dampening_state",
+ "default_information_originate",
+ "suppress_inactive",
+]
+PARAM_TO_DEFAULT_KEYMAP = {
+ "maximum_paths": "1",
+ "maximum_paths_ibgp": "1",
+ "client_to_client": True,
+ "distance_ebgp": "20",
+ "distance_ibgp": "200",
+ "distance_local": "220",
+ "dampen_igp_metric": "600",
+}
+PARAM_TO_COMMAND_KEYMAP = {
+ "asn": "router bgp",
+ "afi": "address-family",
+ "safi": "address-family",
+ "additional_paths_install": "additional-paths install backup",
+ "additional_paths_receive": "additional-paths receive",
+ "additional_paths_selection": "additional-paths selection route-map",
+ "additional_paths_send": "additional-paths send",
+ "advertise_l2vpn_evpn": "advertise l2vpn evpn",
+ "client_to_client": "client-to-client reflection",
+ "dampen_igp_metric": "dampen-igp-metric",
+ "dampening_state": "dampening",
+ "dampening_half_time": "dampening",
+ "dampening_max_suppress_time": "dampening",
+ "dampening_reuse_time": "dampening",
+ "dampening_routemap": "dampening route-map",
+ "dampening_suppress_time": "dampening",
+ "default_information_originate": "default-information originate",
+ "default_metric": "default-metric",
+ "distance_ebgp": "distance",
+ "distance_ibgp": "distance",
+ "distance_local": "distance",
+ "inject_map": "inject-map",
+ "maximum_paths": "maximum-paths",
+ "maximum_paths_ibgp": "maximum-paths ibgp",
+ "networks": "network",
+ "redistribute": "redistribute",
+ "next_hop_route_map": "nexthop route-map",
+ "suppress_inactive": "suppress-inactive",
+ "table_map": "table-map",
+ "table_map_filter": "table-map-filter",
+ "vrf": "vrf",
+ "retain_route_target": "retain route-target",
+}
+DAMPENING_PARAMS = [
+ "dampening_half_time",
+ "dampening_suppress_time",
+ "dampening_reuse_time",
+ "dampening_max_suppress_time",
+]
+
+
+def get_value(arg, config, module):
+ command = PARAM_TO_COMMAND_KEYMAP[arg]
+ command_val_re = re.compile(r"(?:{0}\s)(?P<value>.*)$".format(command), re.M)
+ has_command_val = command_val_re.search(config)
+
+ if arg in ["networks", "redistribute", "inject_map"]:
+ value = []
+ for ele in command_val_re.findall(config):
+ tl = ele.split()
+ if "exist-map" in tl:
+ tl.remove("exist-map")
+ elif "route-map" in tl:
+ tl.remove("route-map")
+ value.append(tl)
+
+ elif command == "distance":
+ distance_re = r".*distance\s(?P<d_ebgp>\w+)\s(?P<d_ibgp>\w+)\s(?P<d_local>\w+)"
+ match_distance = re.match(distance_re, config, re.DOTALL)
+
+ value = ""
+ if match_distance:
+ distance_group = match_distance.groupdict()
+
+ if arg == "distance_ebgp":
+ value = distance_group["d_ebgp"]
+ elif arg == "distance_ibgp":
+ value = distance_group["d_ibgp"]
+ elif arg == "distance_local":
+ value = distance_group["d_local"]
+
+ elif command.split()[0] == "dampening":
+ value = ""
+ if arg == "dampen_igp_metric" or arg == "dampening_routemap":
+ if command in config:
+ value = has_command_val.group("value")
+ else:
+ dampening_re = r".*dampening\s(?P<half>\w+)\s(?P<reuse>\w+)\s(?P<suppress>\w+)\s(?P<max_suppress>\w+)"
+ match_dampening = re.match(dampening_re, config, re.DOTALL)
+ if match_dampening:
+ dampening_group = match_dampening.groupdict()
+
+ if arg == "dampening_half_time":
+ value = dampening_group["half"]
+ elif arg == "dampening_reuse_time":
+ value = dampening_group["reuse"]
+ elif arg == "dampening_suppress_time":
+ value = dampening_group["suppress"]
+ elif arg == "dampening_max_suppress_time":
+ value = dampening_group["max_suppress"]
+ else:
+ if arg == "dampening_state":
+ value = True if "dampening" in config else False
+ elif arg == "table_map_filter":
+ tmf_regex = re.compile(r"\s+table-map.*filter$", re.M)
+ value = False
+ if tmf_regex.search(config):
+ value = True
+
+ elif arg == "table_map":
+ tm_regex = re.compile(r"(?:table-map\s)(?P<value>\S+)(\sfilter)?$", re.M)
+ has_tablemap = tm_regex.search(config)
+ value = ""
+ if has_tablemap:
+ value = has_tablemap.group("value")
+
+ elif arg == "client_to_client":
+ no_command_re = re.compile(r"^\s+no\s{0}\s*$".format(command), re.M)
+ value = True
+
+ if no_command_re.search(config):
+ value = False
+
+ elif arg == "retain_route_target":
+ value = ""
+ if command in config:
+ route_target = has_command_val.group("value")
+ if route_target:
+ if route_target == "all":
+ value = "all"
+ elif "route-map" in route_target:
+ value = route_target.replace("route-map ", "")
+
+ elif arg in BOOL_PARAMS:
+ command_re = re.compile(r"^\s+{0}\s*$".format(command), re.M)
+ value = False
+
+ if command_re.search(config):
+ value = True
+
+ else:
+ value = ""
+
+ if has_command_val:
+ value = has_command_val.group("value")
+
+ return value
+
+
+def get_existing(module, args, warnings):
+ existing = {}
+ netcfg = CustomNetworkConfig(indent=2, contents=get_config(module))
+
+ asn_regex = re.compile(r".*router\sbgp\s(?P<existing_asn>\d+(\.\d+)?).*", re.DOTALL)
+ match_asn = asn_regex.match(str(netcfg))
+
+ if match_asn:
+ existing_asn = match_asn.group("existing_asn")
+ parents = ["router bgp {0}".format(existing_asn)]
+ if module.params["vrf"] != "default":
+ parents.append("vrf {0}".format(module.params["vrf"]))
+
+ parents.append("address-family {0} {1}".format(module.params["afi"], module.params["safi"]))
+ config = netcfg.get_section(parents)
+
+ if config:
+ for arg in args:
+ if arg not in ["asn", "afi", "safi", "vrf"]:
+ gv = get_value(arg, config, module)
+ if gv:
+ existing[arg] = gv
+ else:
+ if arg != "client_to_client" and arg in PARAM_TO_DEFAULT_KEYMAP.keys():
+ existing[arg] = PARAM_TO_DEFAULT_KEYMAP.get(arg)
+ else:
+ existing[arg] = gv
+
+ existing["asn"] = existing_asn
+ existing["afi"] = module.params["afi"]
+ existing["safi"] = module.params["safi"]
+ existing["vrf"] = module.params["vrf"]
+ else:
+ warnings.append(
+ "The BGP process {0} didn't exist but the task just created it.".format(
+ module.params["asn"],
+ ),
+ )
+
+ return existing
+
+
+def apply_key_map(key_map, table):
+ new_dict = {}
+ for key, value in table.items():
+ new_key = key_map.get(key)
+ if new_key:
+ new_dict[new_key] = value
+
+ return new_dict
+
+
+def fix_proposed(module, proposed, existing):
+ commands = list()
+ command = ""
+ fixed_proposed = {}
+ for key, value in proposed.items():
+ if key in DAMPENING_PARAMS:
+ if value != "default":
+ command = "dampening {0} {1} {2} {3}".format(
+ proposed.get("dampening_half_time"),
+ proposed.get("dampening_reuse_time"),
+ proposed.get("dampening_suppress_time"),
+ proposed.get("dampening_max_suppress_time"),
+ )
+ else:
+ if existing.get(key):
+ command = "no dampening {0} {1} {2} {3}".format(
+ existing["dampening_half_time"],
+ existing["dampening_reuse_time"],
+ existing["dampening_suppress_time"],
+ existing["dampening_max_suppress_time"],
+ )
+ if "default" in command:
+ command = ""
+ elif key.startswith("distance"):
+ command = "distance {0} {1} {2}".format(
+ proposed.get("distance_ebgp"),
+ proposed.get("distance_ibgp"),
+ proposed.get("distance_local"),
+ )
+ else:
+ fixed_proposed[key] = value
+
+ if command:
+ if command not in commands:
+ commands.append(command)
+
+ return fixed_proposed, commands
+
+
+def default_existing(existing_value, key, value):
+ commands = []
+ if key == "network":
+ for network in existing_value:
+ if len(network) == 2:
+ commands.append("no network {0} route-map {1}".format(network[0], network[1]))
+ elif len(network) == 1:
+ commands.append("no network {0}".format(network[0]))
+
+ elif key == "inject-map":
+ for maps in existing_value:
+ if len(maps) == 2:
+ commands.append("no inject-map {0} exist-map {1}".format(maps[0], maps[1]))
+ elif len(maps) == 3:
+ commands.append(
+ "no inject-map {0} exist-map {1} " "copy-attributes".format(maps[0], maps[1]),
+ )
+
+ elif key == "redistribute":
+ for maps in existing_value:
+ commands.append("no redistribute {0} route-map {1}".format(maps[0], maps[1]))
+
+ elif key == "retain route-target":
+ if existing_value == "all":
+ commands.append("no {0} {1}".format(key, existing_value))
+ elif existing_value != "default":
+ commands.append("no {0} route-map {1}".format(key, existing_value))
+
+ else:
+ commands.append("no {0} {1}".format(key, existing_value))
+ return commands
+
+
+def get_network_command(existing, key, value):
+ commands = []
+ existing_networks = existing.get("networks", [])
+ for inet in value:
+ if not isinstance(inet, list):
+ inet = [inet]
+ if inet not in existing_networks:
+ if len(inet) == 1:
+ command = "{0} {1}".format(key, inet[0])
+ elif len(inet) == 2:
+ command = "{0} {1} route-map {2}".format(key, inet[0], inet[1])
+ if command:
+ commands.append(command)
+ for enet in existing_networks:
+ if enet not in value:
+ if len(enet) == 1:
+ command = "no {0} {1}".format(key, enet[0])
+ elif len(enet) == 2:
+ command = "no {0} {1} route-map {2}".format(key, enet[0], enet[1])
+ if command:
+ commands.append(command)
+ return commands
+
+
+def get_inject_map_command(existing, key, value):
+ commands = []
+ existing_maps = existing.get("inject_map", [])
+ for maps in value:
+ if not isinstance(maps, list):
+ maps = [maps]
+ if maps not in existing_maps:
+ if len(maps) == 2:
+ command = "inject-map {0} exist-map {1}".format(maps[0], maps[1])
+ elif len(maps) == 3:
+ command = "inject-map {0} exist-map {1} " "copy-attributes".format(maps[0], maps[1])
+ if command:
+ commands.append(command)
+ for emaps in existing_maps:
+ if emaps not in value:
+ if len(emaps) == 2:
+ command = "no inject-map {0} exist-map {1}".format(emaps[0], emaps[1])
+ elif len(emaps) == 3:
+ command = "no inject-map {0} exist-map {1} " "copy-attributes".format(
+ emaps[0],
+ emaps[1],
+ )
+ if command:
+ commands.append(command)
+ return commands
+
+
+def get_redistribute_command(existing, key, value):
+ commands = []
+ existing_rules = existing.get("redistribute", [])
+ for rule in value:
+ if not isinstance(rule, list):
+ rule = [rule]
+ if rule not in existing_rules:
+ command = "redistribute {0} route-map {1}".format(rule[0], rule[1])
+ commands.append(command)
+ for erule in existing_rules:
+ if erule not in value:
+ command = "no redistribute {0} route-map {1}".format(erule[0], erule[1])
+ commands.append(command)
+ return commands
+
+
+def get_table_map_command(module, existing, key, value):
+ commands = []
+ if key == "table-map":
+ if value != "default":
+ command = "{0} {1}".format(key, module.params["table_map"])
+ if (
+ module.params["table_map_filter"] is not None
+ and module.params["table_map_filter"] != "default"
+ ):
+ command += " filter"
+ commands.append(command)
+ else:
+ if existing.get("table_map"):
+ command = "no {0} {1}".format(key, existing.get("table_map"))
+ commands.append(command)
+ return commands
+
+
+def get_retain_route_target_command(existing, key, value):
+ commands = []
+ if key == "retain route-target":
+ if value != "default":
+ if value == "all":
+ command = "{0} {1}".format(key, value)
+ else:
+ command = "{0} route-map {1}".format(key, value)
+ else:
+ existing_value = existing.get("retain_route_target")
+ if existing_value == "all":
+ command = "no {0} {1}".format(key, existing_value)
+ else:
+ command = "no {0} route-map {1}".format(key, existing_value)
+ commands.append(command)
+ return commands
+
+
+def get_default_table_map_filter(existing):
+ commands = []
+ existing_table_map_filter = existing.get("table_map_filter")
+ if existing_table_map_filter:
+ existing_table_map = existing.get("table_map")
+ if existing_table_map:
+ command = "table-map {0}".format(existing_table_map)
+ commands.append(command)
+ return commands
+
+
+def state_present(module, existing, proposed, candidate):
+ fixed_proposed, commands = fix_proposed(module, proposed, existing)
+ proposed_commands = apply_key_map(PARAM_TO_COMMAND_KEYMAP, fixed_proposed)
+ existing_commands = apply_key_map(PARAM_TO_COMMAND_KEYMAP, existing)
+ for key, value in proposed_commands.items():
+ if key == "address-family":
+ addr_family_command = "address-family {0} {1}".format(
+ module.params["afi"],
+ module.params["safi"],
+ )
+ if addr_family_command not in commands:
+ commands.append(addr_family_command)
+
+ elif key.startswith("table-map"):
+ table_map_commands = get_table_map_command(module, existing, key, value)
+ if table_map_commands:
+ commands.extend(table_map_commands)
+
+ elif value is True:
+ commands.append(key)
+
+ elif value is False:
+ commands.append("no {0}".format(key))
+
+ elif value == "default":
+ if key in PARAM_TO_DEFAULT_KEYMAP:
+ commands.append("{0} {1}".format(key, PARAM_TO_DEFAULT_KEYMAP[key]))
+
+ elif existing_commands.get(key):
+ if key == "table-map-filter":
+ default_tmf_command = get_default_table_map_filter(existing)
+
+ if default_tmf_command:
+ commands.extend(default_tmf_command)
+ else:
+ existing_value = existing_commands.get(key)
+ default_command = default_existing(existing_value, key, value)
+ if default_command:
+ commands.extend(default_command)
+ else:
+ if key == "network":
+ network_commands = get_network_command(existing, key, value)
+ if network_commands:
+ commands.extend(network_commands)
+
+ elif key == "inject-map":
+ inject_map_commands = get_inject_map_command(existing, key, value)
+ if inject_map_commands:
+ commands.extend(inject_map_commands)
+
+ elif key == "redistribute":
+ redistribute_commands = get_redistribute_command(existing, key, value)
+ if redistribute_commands:
+ commands.extend(redistribute_commands)
+
+ elif key == "retain route-target":
+ retain_route_target_commands = get_retain_route_target_command(existing, key, value)
+ if retain_route_target_commands:
+ commands.extend(retain_route_target_commands)
+
+ else:
+ command = "{0} {1}".format(key, value)
+ commands.append(command)
+
+ if commands:
+ parents = ["router bgp {0}".format(module.params["asn"])]
+ if module.params["vrf"] != "default":
+ parents.append("vrf {0}".format(module.params["vrf"]))
+
+ addr_family_command = "address-family {0} {1}".format(
+ module.params["afi"],
+ module.params["safi"],
+ )
+ parents.append(addr_family_command)
+ if addr_family_command in commands:
+ commands.remove(addr_family_command)
+ candidate.add(commands, parents=parents)
+
+
+def state_absent(module, candidate):
+ commands = []
+ parents = ["router bgp {0}".format(module.params["asn"])]
+ if module.params["vrf"] != "default":
+ parents.append("vrf {0}".format(module.params["vrf"]))
+
+ commands.append("no address-family {0} {1}".format(module.params["afi"], module.params["safi"]))
+ candidate.add(commands, parents=parents)
+
+
+def main():
+ argument_spec = dict(
+ asn=dict(required=True, type="str"),
+ vrf=dict(required=False, type="str", default="default"),
+ safi=dict(required=True, type="str", choices=["unicast", "multicast", "evpn"]),
+ afi=dict(
+ required=True,
+ type="str",
+ choices=["ipv4", "ipv6", "vpnv4", "vpnv6", "l2vpn"],
+ ),
+ additional_paths_install=dict(required=False, type="bool"),
+ additional_paths_receive=dict(required=False, type="bool"),
+ additional_paths_selection=dict(required=False, type="str"),
+ additional_paths_send=dict(required=False, type="bool"),
+ advertise_l2vpn_evpn=dict(required=False, type="bool"),
+ client_to_client=dict(required=False, type="bool"),
+ dampen_igp_metric=dict(required=False, type="str"),
+ dampening_state=dict(required=False, type="bool"),
+ dampening_half_time=dict(required=False, type="str"),
+ dampening_max_suppress_time=dict(required=False, type="str"),
+ dampening_reuse_time=dict(required=False, type="str"),
+ dampening_routemap=dict(required=False, type="str"),
+ dampening_suppress_time=dict(required=False, type="str"),
+ default_information_originate=dict(required=False, type="bool"),
+ default_metric=dict(required=False, type="str"),
+ distance_ebgp=dict(required=False, type="str"),
+ distance_ibgp=dict(required=False, type="str"),
+ distance_local=dict(required=False, type="str"),
+ inject_map=dict(required=False, type="list", elements="list"),
+ maximum_paths=dict(required=False, type="str"),
+ maximum_paths_ibgp=dict(required=False, type="str"),
+ networks=dict(required=False, type="list", elements="list"),
+ next_hop_route_map=dict(required=False, type="str"),
+ redistribute=dict(required=False, type="list", elements="list"),
+ suppress_inactive=dict(required=False, type="bool"),
+ table_map=dict(required=False, type="str"),
+ table_map_filter=dict(required=False, type="bool"),
+ state=dict(choices=["present", "absent"], default="present", required=False),
+ retain_route_target=dict(required=False, type="str"),
+ )
+
+ mutually_exclusive = [
+ ("dampening_state", "dampening_routemap"),
+ ("dampening_state", "dampening_half_time"),
+ ("dampening_state", "dampening_suppress_time"),
+ ("dampening_state", "dampening_reuse_time"),
+ ("dampening_state", "dampening_max_suppress_time"),
+ ("dampening_routemap", "dampening_half_time"),
+ ("dampening_routemap", "dampening_suppress_time"),
+ ("dampening_routemap", "dampening_reuse_time"),
+ ("dampening_routemap", "dampening_max_suppress_time"),
+ ]
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ mutually_exclusive=mutually_exclusive,
+ required_together=[
+ DAMPENING_PARAMS,
+ ["distance_ibgp", "distance_ebgp", "distance_local"],
+ ],
+ supports_check_mode=True,
+ )
+
+ warnings = list()
+ result = dict(changed=False, warnings=warnings)
+
+ state = module.params["state"]
+
+ if module.params["advertise_l2vpn_evpn"]:
+ if module.params["vrf"] == "default":
+ module.fail_json(
+ msg="It is not possible to advertise L2VPN "
+ "EVPN in the default VRF. Please specify "
+ "another one.",
+ vrf=module.params["vrf"],
+ )
+
+ if module.params["table_map_filter"] and not module.params["table_map"]:
+ module.fail_json(msg="table_map param is needed when using" " table_map_filter filter.")
+
+ args = PARAM_TO_COMMAND_KEYMAP.keys()
+ existing = get_existing(module, args, warnings)
+
+ if existing.get("asn") and state == "present":
+ if existing.get("asn") != module.params["asn"]:
+ module.fail_json(
+ msg="Another BGP ASN already exists.",
+ proposed_asn=module.params["asn"],
+ existing_asn=existing.get("asn"),
+ )
+
+ proposed_args = dict((k, v) for k, v in module.params.items() if v is not None and k in args)
+
+ for arg in ["networks", "inject_map", "redistribute"]:
+ if proposed_args.get(arg):
+ if proposed_args[arg][0] == "default":
+ proposed_args[arg] = "default"
+
+ proposed = {}
+ for key, value in proposed_args.items():
+ if key not in ["asn", "vrf"]:
+ if str(value).lower() == "default":
+ value = PARAM_TO_DEFAULT_KEYMAP.get(key, "default")
+ if existing.get(key) != value:
+ proposed[key] = value
+
+ candidate = CustomNetworkConfig(indent=3)
+ if state == "present":
+ state_present(module, existing, proposed, candidate)
+ elif state == "absent" and existing:
+ state_absent(module, candidate)
+
+ if candidate:
+ candidate = candidate.items_text()
+ if not module.check_mode:
+ load_config(module, candidate)
+ result["changed"] = True
+ result["commands"] = candidate
+ else:
+ result["commands"] = []
+
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_bgp_global.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_bgp_global.py
new file mode 100644
index 00000000..b3072d66
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_bgp_global.py
@@ -0,0 +1,1694 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# Copyright 2021 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+"""
+The module file for nxos_bgp_global
+"""
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+DOCUMENTATION = """
+module: nxos_bgp_global
+short_description: BGP Global resource module.
+description:
+- This module manages global BGP configuration on devices running Cisco NX-OS.
+version_added: 1.4.0
+notes:
+- Tested against NX-OS 9.3.6.
+- Unsupported for Cisco MDS
+- This module works with connection C(network_cli) and C(httpapi).
+author: Nilashish Chakraborty (@NilashishC)
+options:
+ running_config:
+ description:
+ - This option is used only with state I(parsed).
+ - The value of this option should be the output received from the NX-OS device
+ by executing the command B(show running-config | section '^router bgp').
+ - The state I(parsed) reads the configuration from C(running_config) option and
+ transforms it into Ansible structured data as per the resource module's argspec
+ and the value is then returned in the I(parsed) key within the result.
+ type: str
+ config:
+ description: A list of BGP process configuration.
+ type: dict
+ suboptions:
+ as_number:
+ description: Autonomous System Number of the router.
+ type: str
+ affinity_group:
+ description: Configure an affinity group.
+ type: dict
+ suboptions:
+ group_id:
+ description: Affinity Group ID.
+ type: int
+ bestpath: &bestpath
+ description: Define the default bestpath selection algorithm.
+ type: dict
+ suboptions:
+ always_compare_med:
+ description: Compare MED on paths from different AS.
+ type: bool
+ as_path:
+ description: AS-Path.
+ type: dict
+ suboptions:
+ ignore:
+ description: Ignore AS-Path during bestpath selection.
+ type: bool
+ multipath_relax:
+ description: Relax AS-Path restriction when choosing multipaths.
+ type: bool
+ compare_neighborid:
+ description: When more paths are available than max path config, use neighborid as tie-breaker.
+ type: bool
+ compare_routerid:
+ description: Compare router-id for identical EBGP paths.
+ type: bool
+ cost_community_ignore:
+ description: Ignore cost communities in bestpath selection.
+ type: bool
+ igp_metric_ignore:
+ description: Ignore IGP metric for next-hop during bestpath selection.
+ type: bool
+ med:
+ description: MED
+ type: dict
+ suboptions:
+ confed:
+ description: Compare MED only from paths originated from within a confederation.
+ type: bool
+ missing_as_worst:
+ description: Treat missing MED as highest MED.
+ type: bool
+ non_deterministic:
+ description: Not always pick the best-MED path among paths from same AS.
+ type: bool
+ cluster_id: &cluster_id
+ description: Configure Route Reflector Cluster-ID.
+ type: str
+ confederation: &confederation
+ description: AS confederation parameters.
+ type: dict
+ suboptions:
+ identifier:
+ description: Set routing domain confederation AS.
+ type: str
+ peers:
+ description: Peer ASs in BGP confederation.
+ type: list
+ elements: str
+ disable_policy_batching:
+ description: Disable batching evaluation of outbound policy for a peer.
+ type: dict
+ suboptions:
+ set:
+ description: Set policy batching.
+ type: bool
+ ipv4:
+ description: IPv4 address-family settings.
+ type: dict
+ suboptions:
+ prefix_list:
+ description: Name of prefix-list to apply.
+ type: str
+ ipv6:
+ description: IPv6 address-family settings.
+ type: dict
+ suboptions:
+ prefix_list:
+ description: Name of prefix-list to apply.
+ type: str
+ nexthop:
+ description: Batching based on nexthop.
+ type: bool
+ dynamic_med_interval:
+ description: Sets the interval for dampening of med changes.
+ type: int
+ enforce_first_as:
+ description: Enforce neighbor AS is the first AS in AS-PATH attribute (EBGP).
+ type: bool
+ enhanced_error:
+ description: Enable BGP Enhanced error handling.
+ type: bool
+ fabric_soo:
+ description: Fabric site of origin.
+ type: str
+ fast_external_fallover:
+ description: Immediately reset the session if the link to a directly connected BGP peer goes down.
+ type: bool
+ flush_routes:
+ description: Flush routes in RIB upon controlled restart.
+ type: bool
+ graceful_restart: &graceful_restart
+ description: Configure Graceful Restart functionality.
+ type: dict
+ suboptions:
+ set:
+ description: Enable graceful-restart.
+ type: bool
+ restart_time:
+ description: Maximum time for restart advertised to peers.
+ type: int
+ stalepath_time:
+ description: Maximum time to keep a restarting peer's stale routes.
+ type: int
+ helper:
+ description: Configure Graceful Restart Helper mode functionality.
+ type: bool
+ graceful_shutdown:
+ description: Graceful-shutdown for BGP protocol.
+ type: dict
+ suboptions:
+ activate:
+ description: Send graceful-shutdown community on all routes.
+ type: dict
+ suboptions:
+ set:
+ description: Activiate graceful-shutdown.
+ type: bool
+ route_map:
+ description: Apply route-map to modify attributes for outbound.
+ type: str
+ aware:
+ description: Lower preference of routes carrying graceful-shutdown community.
+ type: bool
+ isolate:
+ description: Isolate this router from BGP perspective.
+ type: dict
+ suboptions:
+ set:
+ description: Withdraw remote BGP routes to isolate this router.
+ type: bool
+ include_local:
+ description: Withdraw both local and remote BGP routes.
+ type: bool
+ log_neighbor_changes: &log_nbr
+ description: Log a message for neighbor up/down event.
+ type: bool
+ maxas_limit: &maxas_limit
+ description: Allow AS-PATH attribute from EBGP neighbor imposing a limit on number of ASes.
+ type: int
+ neighbors: &nbr
+ description: Configure BGP neighbors.
+ type: list
+ elements: dict
+ suboptions:
+ neighbor_address:
+ description: IP address/Prefix of the neighbor or interface.
+ type: str
+ required: True
+ bfd:
+ description: Bidirectional Fast Detection for the neighbor.
+ type: dict
+ suboptions:
+ set:
+ description: Set BFD for this neighbor.
+ type: bool
+ singlehop:
+ description: Single-hop session.
+ type: bool
+ multihop:
+ description: Multihop session.
+ type: dict
+ suboptions:
+ set:
+ description: Set BFD multihop.
+ type: bool
+ interval:
+ description: Configure BFD session interval parameters.
+ type: dict
+ suboptions:
+ tx_interval:
+ description: TX interval in milliseconds.
+ type: int
+ min_rx_interval:
+ description: Minimum RX interval.
+ type: int
+ multiplier:
+ description: Detect Multiplier.
+ type: int
+ neighbor_affinity_group:
+ description: Configure an affinity group.
+ type: dict
+ suboptions:
+ group_id:
+ description: Affinity Group ID.
+ type: int
+ bmp_activate_server:
+ description: Specify server ID for activating BMP monitoring for the peer.
+ type: int
+ capability:
+ description: Capability.
+ type: dict
+ suboptions:
+ suppress_4_byte_as:
+ description: Suppress 4-byte AS Capability.
+ type: bool
+ description:
+ description: Neighbor specific descripion.
+ type: str
+ disable_connected_check:
+ description: Disable check for directly connected peer.
+ type: bool
+ dont_capability_negotiate:
+ description: Don't negotiate capability with this neighbor.
+ type: bool
+ dscp:
+ description: Set dscp value for tcp transport.
+ type: str
+ dynamic_capability:
+ description: Dynamic Capability
+ type: bool
+ ebgp_multihop:
+ description: Specify multihop TTL for remote peer.
+ type: int
+ graceful_shutdown:
+ description: Graceful-shutdown for this neighbor.
+ type: dict
+ suboptions:
+ activate:
+ description: Send graceful-shutdown community.
+ type: dict
+ suboptions:
+ set:
+ description: Set activate.
+ type: bool
+ route_map:
+ description: Apply route-map to modify attributes for outbound.
+ type: str
+ inherit:
+ description: Inherit a template.
+ type: dict
+ suboptions:
+ peer:
+ description: Peer template to inherit.
+ type: str
+ peer_session:
+ description: Peer-session template to inherit.
+ type: str
+ local_as:
+ description: Specify the local-as number for the eBGP neighbor.
+ type: str
+ log_neighbor_changes:
+ description: Log message for neighbor up/down event.
+ type: dict
+ suboptions:
+ set:
+ description:
+ - Set log-neighbor-changes.
+ type: bool
+ disable:
+ description:
+ - Disable logging of neighbor up/down event.
+ type: bool
+ low_memory:
+ description: Behaviour in low memory situations.
+ type: dict
+ suboptions:
+ exempt:
+ description: Do not shutdown this peer when under memory pressure.
+ type: bool
+ password:
+ description: Configure a password for neighbor.
+ type: dict
+ suboptions:
+ encryption:
+ description:
+ - 0 specifies an UNENCRYPTED neighbor password.
+ - 3 specifies an 3DES ENCRYPTED neighbor password will follow.
+ - 7 specifies a Cisco type 7 ENCRYPTED neighbor password will follow.
+ type: int
+ key:
+ description: Authentication password.
+ type: str
+ path_attribute:
+ description: BGP path attribute optional filtering.
+ type: list
+ elements: dict
+ suboptions:
+ action:
+ description: Action.
+ type: str
+ choices: ["discard", "treat-as-withdraw"]
+ type:
+ description: Path attribute type
+ type: int
+ range:
+ description: Path attribute range.
+ type: dict
+ suboptions:
+ start:
+ description: Path attribute range start value.
+ type: int
+ end:
+ description: Path attribute range end value.
+ type: int
+ peer_type:
+ description: Neighbor facing
+ type: str
+ choices: ["fabric-border-leaf", "fabric-external"]
+ remote_as:
+ description: Specify Autonomous System Number of the neighbor.
+ type: str
+ remove_private_as:
+ description: Remove private AS number from outbound updates.
+ type: dict
+ suboptions:
+ set:
+ description: Remove private AS.
+ type: bool
+ replace_as:
+ description: Replace.
+ type: bool
+ all:
+ description: All.
+ type: bool
+ shutdown:
+ description: Administratively shutdown this neighbor.
+ type: bool
+ timers:
+ description: Configure keepalive and hold timers.
+ type: dict
+ suboptions:
+ keepalive:
+ description: Keepalive interval (seconds).
+ type: int
+ holdtime:
+ description: Holdtime (seconds).
+ type: int
+ transport:
+ description: BGP transport connection.
+ type: dict
+ suboptions:
+ connection_mode:
+ description: Specify type of connection.
+ type: dict
+ suboptions:
+ passive:
+ description: Allow passive connection setup only.
+ type: bool
+ ttl_security:
+ description: Enable TTL Security Mechanism.
+ type: dict
+ suboptions:
+ hops:
+ description: Specify hop count for remote peer.
+ type: int
+ update_source:
+ description: Specify source of BGP session and updates.
+ type: str
+ neighbor_down: &nbr_down
+ description: Handle BGP neighbor down event, due to various reasons.
+ type: dict
+ suboptions:
+ fib_accelerate:
+ description: Accelerate the hardware updates for IP/IPv6 adjacencies for neighbor.
+ type: bool
+ nexthop:
+ description: Nexthop resolution options.
+ type: dict
+ suboptions:
+ suppress_default_resolution:
+ description: Prohibit use of default route for nexthop address resolution.
+ type: bool
+ rd:
+ description: Secondary Route Distinguisher for vxlan multisite border gateway.
+ type: dict
+ suboptions:
+ dual:
+ description: Generate Secondary RD for all VRFs and L2VNIs.
+ type: bool
+ id:
+ description: Specify 2 byte value for ID.
+ type: int
+ reconnect_interval: &reconn_intv
+ description: Configure connection reconnect interval.
+ type: int
+ router_id: &rtr_id
+ description: Specify the IP address to use as router-id.
+ type: str
+ shutdown: &shtdwn
+ description: Administratively shutdown BGP protocol.
+ type: bool
+ suppress_fib_pending: &suppr
+ description: Advertise only routes that are programmed in hardware to peers.
+ type: bool
+ timers: &timers
+ description: Configure bgp related timers.
+ type: dict
+ suboptions:
+ bestpath_limit:
+ description: Configure timeout for first bestpath after restart.
+ type: dict
+ suboptions:
+ timeout:
+ description: Bestpath timeout (seconds).
+ type: int
+ always:
+ description: Configure update-delay-always option.
+ type: bool
+ bgp:
+ description: Configure different bgp keepalive and holdtimes.
+ type: dict
+ suboptions:
+ keepalive:
+ description: Keepalive interval (seconds).
+ type: int
+ holdtime:
+ description: Holdtime (seconds).
+ type: int
+ prefix_peer_timeout:
+ description: Prefix Peer timeout (seconds).
+ type: int
+ prefix_peer_wait:
+ description: Configure wait timer for a prefix peer.
+ type: int
+ vrfs:
+ description: Virtual Router Context configurations.
+ type: list
+ elements: dict
+ suboptions:
+ vrf:
+ description: VRF name.
+ type: str
+ allocate_index:
+ description: Configure allocate-index.
+ type: int
+ bestpath: *bestpath
+ cluster_id: *cluster_id
+ confederation: *confederation
+ graceful_restart: *graceful_restart
+ local_as:
+ description: Specify the local-as for this vrf.
+ type: str
+ log_neighbor_changes: *log_nbr
+ maxas_limit: *maxas_limit
+ neighbors: *nbr
+ neighbor_down: *nbr_down
+ reconnect_interval: *reconn_intv
+ router_id: *rtr_id
+ timers: *timers
+ state:
+ description:
+ - The state the configuration should be left in.
+ - State I(purged) removes all the BGP configurations from the
+ target device. Use caution with this state.
+ - State I(deleted) only removes BGP attributes that this modules
+ manages and does not negate the BGP process completely. Thereby, preserving
+ address-family related configurations under BGP context.
+ - Running states I(deleted) and I(replaced) will result in an error if there
+ are address-family configuration lines present under a neighbor,
+ or a vrf context that is to be removed. Please use the
+ M(cisco.nxos.nxos_bgp_af) or M(cisco.nxos.nxos_bgp_neighbor_af)
+ modules for prior cleanup.
+ - States I(merged) and I(replaced) will result in a failure if BGP is already configured
+ with a different ASN than what is provided in the task. In such cases, please use
+ state I(purged) to remove the existing BGP process and proceed further.
+ - Refer to examples for more details.
+ type: str
+ choices:
+ - merged
+ - replaced
+ - deleted
+ - purged
+ - parsed
+ - gathered
+ - rendered
+ default: merged
+"""
+EXAMPLES = """
+# Using merged
+
+# Before state:
+# -------------
+# Nexus9000v# show running-config | section "^router bgp"
+# Nexus9000v#
+
+- name: Merge the provided configuration with the existing running configuration
+ cisco.nxos.nxos_bgp_global:
+ config:
+ as_number: 65563
+ router_id: 192.168.1.1
+ bestpath:
+ as_path:
+ multipath_relax: True
+ compare_neighborid: True
+ cost_community_ignore: True
+ confederation:
+ identifier: 42
+ peers:
+ - 65020
+ - 65030
+ - 65040
+ log_neighbor_changes: True
+ maxas_limit: 20
+ neighbors:
+ - neighbor_address: 192.168.1.100
+ neighbor_affinity_group:
+ group_id: 160
+ bmp_activate_server: 1
+ remote_as: 65563
+ description: NBR-1
+ low_memory:
+ exempt: True
+ - neighbor_address: 192.168.1.101
+ remote_as: 65563
+ password:
+ encryption: 7
+ key: 12090404011C03162E
+ neighbor_down:
+ fib_accelerate: True
+ vrfs:
+ - vrf: site-1
+ allocate_index: 5000
+ local_as: 200
+ log_neighbor_changes: True
+ neighbors:
+ - neighbor_address: 198.51.100.1
+ description: site-1-nbr-1
+ password:
+ encryption: 3
+ key: 13D4D3549493D2877B1DC116EE27A6BE
+ remote_as: 65562
+ - neighbor_address: 198.51.100.2
+ remote_as: 65562
+ description: site-1-nbr-2
+ - vrf: site-2
+ local_as: 300
+ log_neighbor_changes: True
+ neighbors:
+ - neighbor_address: 203.0.113.2
+ description: site-2-nbr-1
+ password:
+ encryption: 3
+ key: AF92F4C16A0A0EC5BDF56CF58BC030F6
+ remote_as: 65568
+ neighbor_down:
+ fib_accelerate: True
+
+# Task output
+# -------------
+# before: {}
+#
+# commands:
+# - router bgp 65563
+# - bestpath as-path multipath-relax
+# - bestpath compare-neighborid
+# - bestpath cost-community ignore
+# - confederation identifier 42
+# - log-neighbor-changes
+# - maxas-limit 20
+# - neighbor-down fib-accelerate
+# - router-id 192.168.1.1
+# - confederation peers 65020 65030 65040
+# - neighbor 192.168.1.100
+# - remote-as 65563
+# - affinity-group 160
+# - bmp-activate-server 1
+# - description NBR-1
+# - low-memory exempt
+# - neighbor 192.168.1.101
+# - remote-as 65563
+# - password 7 12090404011C03162E
+# - vrf site-1
+# - allocate-index 5000
+# - local-as 200
+# - log-neighbor-changes
+# - neighbor 198.51.100.1
+# - remote-as 65562
+# - description site-1-nbr-1
+# - password 3 13D4D3549493D2877B1DC116EE27A6BE
+# - neighbor 198.51.100.2
+# - remote-as 65562
+# - description site-1-nbr-2
+# - vrf site-2
+# - local-as 300
+# - log-neighbor-changes
+# - neighbor-down fib-accelerate
+# - neighbor 203.0.113.2
+# - remote-as 65568
+# - description site-2-nbr-1
+# - password 3 AF92F4C16A0A0EC5BDF56CF58BC030F6
+#
+# after:
+# as_number: '65563'
+# bestpath:
+# as_path:
+# multipath_relax: true
+# compare_neighborid: true
+# cost_community_ignore: true
+# confederation:
+# identifier: '42'
+# peers:
+# - '65020'
+# - '65030'
+# - '65040'
+# log_neighbor_changes: true
+# maxas_limit: 20
+# neighbor_down:
+# fib_accelerate: true
+# neighbors:
+# - bmp_activate_server: 1
+# description: NBR-1
+# low_memory:
+# exempt: true
+# neighbor_address: 192.168.1.100
+# neighbor_affinity_group:
+# group_id: 160
+# remote_as: '65563'
+# - neighbor_address: 192.168.1.101
+# password:
+# encryption: 7
+# key: 12090404011C03162E
+# remote_as: '65563'
+# router_id: 192.168.1.1
+# vrfs:
+# - allocate_index: 5000
+# local_as: '200'
+# log_neighbor_changes: true
+# neighbors:
+# - description: site-1-nbr-1
+# neighbor_address: 198.51.100.1
+# password:
+# encryption: 3
+# key: 13D4D3549493D2877B1DC116EE27A6BE
+# remote_as: '65562'
+# - description: site-1-nbr-2
+# neighbor_address: 198.51.100.2
+# remote_as: '65562'
+# vrf: site-1
+# - local_as: '300'
+# log_neighbor_changes: true
+# neighbor_down:
+# fib_accelerate: true
+# neighbors:
+# - description: site-2-nbr-1
+# neighbor_address: 203.0.113.2
+# password:
+# encryption: 3
+# key: AF92F4C16A0A0EC5BDF56CF58BC030F6
+# remote_as: '65568'
+# vrf: site-2
+
+
+# After state:
+# -------------
+# Nexus9000v# show running-config | section "^router bgp"
+# router bgp 65563
+# router-id 192.168.1.1
+# confederation identifier 42
+# confederation peers 65020 65030 65040
+# bestpath as-path multipath-relax
+# bestpath cost-community ignore
+# bestpath compare-neighborid
+# neighbor-down fib-accelerate
+# maxas-limit 20
+# log-neighbor-changes
+# neighbor 192.168.1.100
+# low-memory exempt
+# bmp-activate-server 1
+# remote-as 65563
+# description NBR-1
+# affinity-group 160
+# neighbor 192.168.1.101
+# remote-as 65563
+# password 7 12090404011C03162E
+# vrf site-1
+# local-as 200
+# log-neighbor-changes
+# allocate-index 5000
+# neighbor 198.51.100.1
+# remote-as 65562
+# description site-1-nbr-1
+# password 3 13D4D3549493D2877B1DC116EE27A6BE
+# neighbor 198.51.100.2
+# remote-as 65562
+# description site-1-nbr-2
+# vrf site-2
+# local-as 300
+# neighbor-down fib-accelerate
+# log-neighbor-changes
+# neighbor 203.0.113.2
+# remote-as 65568
+# description site-2-nbr-1
+# password 3 AF92F4C16A0A0EC5BDF56CF58BC030F6
+
+# Using replaced
+
+# Before state:
+# -------------
+# Nexus9000v# show running-config | section "^router bgp"
+# router bgp 65563
+# router-id 192.168.1.1
+# confederation identifier 42
+# confederation peers 65020 65030 65040
+# bestpath as-path multipath-relax
+# bestpath cost-community ignore
+# bestpath compare-neighborid
+# neighbor-down fib-accelerate
+# maxas-limit 20
+# log-neighbor-changes
+# neighbor 192.168.1.100
+# low-memory exempt
+# bmp-activate-server 1
+# remote-as 65563
+# description NBR-1
+# affinity-group 160
+# neighbor 192.168.1.101
+# remote-as 65563
+# password 7 12090404011C03162E
+# vrf site-1
+# local-as 200
+# log-neighbor-changes
+# allocate-index 5000
+# neighbor 198.51.100.1
+# remote-as 65562
+# description site-1-nbr-1
+# password 3 13D4D3549493D2877B1DC116EE27A6BE
+# neighbor 198.51.100.2
+# remote-as 65562
+# description site-1-nbr-2
+# vrf site-2
+# local-as 300
+# neighbor-down fib-accelerate
+# log-neighbor-changes
+# neighbor 203.0.113.2
+# remote-as 65568
+# description site-2-nbr-1
+# password 3 AF92F4C16A0A0EC5BDF56CF58BC030F6
+
+- name: Replace BGP configuration with provided configuration
+ cisco.nxos.nxos_bgp_global:
+ config:
+ as_number: 65563
+ router_id: 192.168.1.1
+ bestpath:
+ compare_neighborid: True
+ cost_community_ignore: True
+ confederation:
+ identifier: 42
+ peers:
+ - 65020
+ - 65030
+ - 65050
+ maxas_limit: 40
+ neighbors:
+ - neighbor_address: 192.168.1.100
+ neighbor_affinity_group:
+ group_id: 160
+ bmp_activate_server: 1
+ remote_as: 65563
+ description: NBR-1
+ low_memory:
+ exempt: True
+ neighbor_down:
+ fib_accelerate: True
+ vrfs:
+ - vrf: site-2
+ local_as: 300
+ log_neighbor_changes: True
+ neighbors:
+ - neighbor_address: 203.0.113.2
+ password:
+ encryption: 7
+ key: 12090404011C03162E
+ neighbor_down:
+ fib_accelerate: True
+ state: replaced
+
+# Task output
+# -------------
+# before:
+# as_number: '65563'
+# bestpath:
+# as_path:
+# multipath_relax: true
+# compare_neighborid: true
+# cost_community_ignore: true
+# confederation:
+# identifier: '42'
+# peers:
+# - '65020'
+# - '65030'
+# - '65040'
+# log_neighbor_changes: true
+# maxas_limit: 20
+# neighbor_down:
+# fib_accelerate: true
+# neighbors:
+# - bmp_activate_server: 1
+# description: NBR-1
+# low_memory:
+# exempt: true
+# neighbor_address: 192.168.1.100
+# neighbor_affinity_group:
+# group_id: 160
+# remote_as: '65563'
+# - neighbor_address: 192.168.1.101
+# password:
+# encryption: 7
+# key: 12090404011C03162E
+# remote_as: '65563'
+# router_id: 192.168.1.1
+# vrfs:
+# - allocate_index: 5000
+# local_as: '200'
+# log_neighbor_changes: true
+# neighbors:
+# - description: site-1-nbr-1
+# neighbor_address: 198.51.100.1
+# password:
+# encryption: 3
+# key: 13D4D3549493D2877B1DC116EE27A6BE
+# remote_as: '65562'
+# - description: site-1-nbr-2
+# neighbor_address: 198.51.100.2
+# remote_as: '65562'
+# vrf: site-1
+# - local_as: '300'
+# log_neighbor_changes: true
+# neighbor_down:
+# fib_accelerate: true
+# neighbors:
+# - description: site-2-nbr-1
+# neighbor_address: 203.0.113.2
+# password:
+# encryption: 3
+# key: AF92F4C16A0A0EC5BDF56CF58BC030F6
+# remote_as: '65568'
+# vrf: site-2
+#
+# commands:
+# - router bgp 65563
+# - no bestpath as-path multipath-relax
+# - no log-neighbor-changes
+# - maxas-limit 40
+# - no confederation peers 65020 65030 65040
+# - confederation peers 65020 65030 65050
+# - no neighbor 192.168.1.101
+# - vrf site-2
+# - neighbor 203.0.113.2
+# - no remote-as 65568
+# - no description site-2-nbr-1
+# - password 7 12090404011C03162E
+# - no vrf site-1
+
+# after:
+# as_number: '65563'
+# bestpath:
+# compare_neighborid: true
+# cost_community_ignore: true
+# confederation:
+# identifier: '42'
+# peers:
+# - '65020'
+# - '65030'
+# - '65050'
+# maxas_limit: 40
+# neighbor_down:
+# fib_accelerate: true
+# neighbors:
+# - bmp_activate_server: 1
+# description: NBR-1
+# low_memory:
+# exempt: true
+# neighbor_address: 192.168.1.100
+# neighbor_affinity_group:
+# group_id: 160
+# remote_as: '65563'
+# router_id: 192.168.1.1
+# vrfs:
+# - local_as: '300'
+# log_neighbor_changes: true
+# neighbor_down:
+# fib_accelerate: true
+# neighbors:
+# - neighbor_address: 203.0.113.2
+# password:
+# encryption: 7
+# key: 12090404011C03162E
+# vrf: site-2
+#
+# After state:
+# -------------
+# Nexus9000v# show running-config | section "^router bgp"
+# router bgp 65563
+# router-id 192.168.1.1
+# confederation identifier 42
+# confederation peers 65020 65030 65050
+# bestpath cost-community ignore
+# bestpath compare-neighborid
+# neighbor-down fib-accelerate
+# maxas-limit 40
+# neighbor 192.168.1.100
+# low-memory exempt
+# bmp-activate-server 1
+# remote-as 65563
+# description NBR-1
+# affinity-group 160
+# vrf site-2
+# local-as 300
+# neighbor-down fib-accelerate
+# log-neighbor-changes
+# neighbor 203.0.113.2
+# password 7 12090404011C03162E
+
+# Using deleted
+
+# Before state:
+# -------------
+# Nexus9000v# show running-config | section "^router bgp"
+# router bgp 65563
+# router-id 192.168.1.1
+# confederation identifier 42
+# confederation peers 65020 65030 65040
+# bestpath as-path multipath-relax
+# bestpath cost-community ignore
+# bestpath compare-neighborid
+# neighbor-down fib-accelerate
+# maxas-limit 20
+# log-neighbor-changes
+# address-family ipv4 unicast
+# default-metric 400
+# suppress-inactive
+# default-information originate
+# address-family ipv6 multicast
+# wait-igp-convergence
+# redistribute eigrp eigrp-1 route-map site-1-rmap
+# neighbor 192.168.1.100
+# low-memory exempt
+# bmp-activate-server 1
+# remote-as 65563
+# description NBR-1
+# affinity-group 160
+# neighbor 192.168.1.101
+# remote-as 65563
+# password 7 12090404011C03162E
+# vrf site-1
+# local-as 200
+# log-neighbor-changes
+# allocate-index 5000
+# address-family ipv4 multicast
+# maximum-paths 40
+# dampen-igp-metric 1200
+# neighbor 198.51.100.1
+# remote-as 65562
+# description site-1-nbr-1
+# password 3 13D4D3549493D2877B1DC116EE27A6BE
+# neighbor 198.51.100.2
+# remote-as 65562
+# description site-1-nbr-2
+# vrf site-2
+# local-as 300
+# neighbor-down fib-accelerate
+# log-neighbor-changes
+# neighbor 203.0.113.2
+# remote-as 65568
+# description site-1-nbr-1
+# password 3 AF92F4C16A0A0EC5BDF56CF58BC030F6
+
+- name: Delete BGP configurations handled by this module
+ cisco.nxos.nxos_bgp_global:
+ state: deleted
+
+# Task output
+# -------------
+
+# before:
+# as_number: '65563'
+# bestpath:
+# as_path:
+# multipath_relax: true
+# compare_neighborid: true
+# cost_community_ignore: true
+# confederation:
+# identifier: '42'
+# peers:
+# - '65020'
+# - '65030'
+# - '65040'
+# log_neighbor_changes: true
+# maxas_limit: 20
+# neighbor_down:
+# fib_accelerate: true
+# neighbors:
+# - bmp_activate_server: 1
+# description: NBR-1
+# low_memory:
+# exempt: true
+# neighbor_address: 192.168.1.100
+# neighbor_affinity_group:
+# group_id: 160
+# remote_as: '65563'
+# - neighbor_address: 192.168.1.101
+# password:
+# encryption: 7
+# key: 12090404011C03162E
+# remote_as: '65563'
+# router_id: 192.168.1.1
+# vrfs:
+# - allocate_index: 5000
+# local_as: '200'
+# log_neighbor_changes: true
+# neighbors:
+# - description: site-1-nbr-1
+# neighbor_address: 198.51.100.1
+# password:
+# encryption: 3
+# key: 13D4D3549493D2877B1DC116EE27A6BE
+# remote_as: '65562'
+# - description: site-1-nbr-2
+# neighbor_address: 198.51.100.2
+# remote_as: '65562'
+# vrf: site-1
+# - local_as: '300'
+# log_neighbor_changes: true
+# neighbor_down:
+# fib_accelerate: true
+# neighbors:
+# - description: site-1-nbr-1
+# neighbor_address: 203.0.113.2
+# password:
+# encryption: 3
+# key: AF92F4C16A0A0EC5BDF56CF58BC030F6
+# remote_as: '65568'
+# vrf: site-2
+#
+# commands:
+# - router bgp 65563
+# - no bestpath as-path multipath-relax
+# - no bestpath compare-neighborid
+# - no bestpath cost-community ignore
+# - no confederation identifier 42
+# - no log-neighbor-changes
+# - no maxas-limit 20
+# - no neighbor-down fib-accelerate
+# - no router-id 192.168.1.1
+# - no confederation peers 65020 65030 65040
+# - no neighbor 192.168.1.100
+# - no neighbor 192.168.1.101
+# - no vrf site-1
+# - no vrf site-2
+#
+# after:
+# as_number: '65563'
+#
+# After state:
+# -------------
+# Nexus9000v# show running-config | section "^router bgp"
+# router bgp 65563
+# address-family ipv4 unicast
+# default-metric 400
+# suppress-inactive
+# default-information originate
+# address-family ipv6 multicast
+# wait-igp-convergence
+# redistribute eigrp eigrp-1 route-map site-1-rmap
+#
+
+# Using purged
+
+# Before state:
+# -------------
+# Nexus9000v# show running-config | section "^router bgp"
+# router bgp 65563
+# router-id 192.168.1.1
+# confederation identifier 42
+# confederation peers 65020 65030 65040
+# bestpath as-path multipath-relax
+# bestpath cost-community ignore
+# bestpath compare-neighborid
+# neighbor-down fib-accelerate
+# maxas-limit 20
+# log-neighbor-changes
+# address-family ipv4 unicast
+# default-metric 400
+# suppress-inactive
+# default-information originate
+# address-family ipv6 multicast
+# wait-igp-convergence
+# redistribute eigrp eigrp-1 route-map site-1-rmap
+# neighbor 192.168.1.100
+# low-memory exempt
+# bmp-activate-server 1
+# remote-as 65563
+# description NBR-1
+# affinity-group 160
+# neighbor 192.168.1.101
+# remote-as 65563
+# password 7 12090404011C03162E
+# vrf site-1
+# local-as 200
+# log-neighbor-changes
+# allocate-index 5000
+# address-family ipv4 multicast
+# maximum-paths 40
+# dampen-igp-metric 1200
+# neighbor 198.51.100.1
+# remote-as 65562
+# description site-1-nbr-1
+# password 3 13D4D3549493D2877B1DC116EE27A6BE
+# neighbor 198.51.100.2
+# remote-as 65562
+# description site-1-nbr-2
+# vrf site-2
+# local-as 300
+# neighbor-down fib-accelerate
+# log-neighbor-changes
+# neighbor 203.0.113.2
+# remote-as 65568
+# description site-1-nbr-1
+# password 3 AF92F4C16A0A0EC5BDF56CF58BC030F6
+
+- name: Purge all BGP configurations from the device
+ cisco.nxos.nxos_bgp_global:
+ state: purged
+
+# Task output
+# -------------
+
+# before:
+# as_number: '65563'
+# bestpath:
+# as_path:
+# multipath_relax: true
+# compare_neighborid: true
+# cost_community_ignore: true
+# confederation:
+# identifier: '42'
+# peers:
+# - '65020'
+# - '65030'
+# - '65040'
+# log_neighbor_changes: true
+# maxas_limit: 20
+# neighbor_down:
+# fib_accelerate: true
+# neighbors:
+# - bmp_activate_server: 1
+# description: NBR-1
+# low_memory:
+# exempt: true
+# neighbor_address: 192.168.1.100
+# neighbor_affinity_group:
+# group_id: 160
+# remote_as: '65563'
+# - neighbor_address: 192.168.1.101
+# password:
+# encryption: 7
+# key: 12090404011C03162E
+# remote_as: '65563'
+# router_id: 192.168.1.1
+# vrfs:
+# - allocate_index: 5000
+# local_as: '200'
+# log_neighbor_changes: true
+# neighbors:
+# - description: site-1-nbr-1
+# neighbor_address: 198.51.100.1
+# password:
+# encryption: 3
+# key: 13D4D3549493D2877B1DC116EE27A6BE
+# remote_as: '65562'
+# - description: site-1-nbr-2
+# neighbor_address: 198.51.100.2
+# remote_as: '65562'
+# vrf: site-1
+# - local_as: '300'
+# log_neighbor_changes: true
+# neighbor_down:
+# fib_accelerate: true
+# neighbors:
+# - description: site-1-nbr-1
+# neighbor_address: 203.0.113.2
+# password:
+# encryption: 3
+# key: AF92F4C16A0A0EC5BDF56CF58BC030F6
+# remote_as: '65568'
+# vrf: site-2
+#
+# commands:
+# - no router bgp 65563
+#
+# after: {}
+#
+# After state:
+# -------------
+# Nexus9000v# show running-config | section "^router bgp"
+# Nexus9000v#
+
+# Using rendered
+
+- name: Render platform specific configuration lines (without connecting to the device)
+ cisco.nxos.nxos_bgp_global:
+ config:
+ as_number: 65563
+ router_id: 192.168.1.1
+ bestpath:
+ as_path:
+ multipath_relax: True
+ compare_neighborid: True
+ cost_community_ignore: True
+ confederation:
+ identifier: 42
+ peers:
+ - 65020
+ - 65030
+ - 65040
+ log_neighbor_changes: True
+ maxas_limit: 20
+ neighbors:
+ - neighbor_address: 192.168.1.100
+ neighbor_affinity_group:
+ group_id: 160
+ bmp_activate_server: 1
+ remote_as: 65563
+ description: NBR-1
+ low_memory:
+ exempt: True
+ - neighbor_address: 192.168.1.101
+ remote_as: 65563
+ password:
+ encryption: 7
+ key: 12090404011C03162E
+ neighbor_down:
+ fib_accelerate: True
+ vrfs:
+ - vrf: site-1
+ allocate_index: 5000
+ local_as: 200
+ log_neighbor_changes: True
+ neighbors:
+ - neighbor_address: 198.51.100.1
+ description: site-1-nbr-1
+ password:
+ encryption: 3
+ key: 13D4D3549493D2877B1DC116EE27A6BE
+ remote_as: 65562
+ - neighbor_address: 198.51.100.2
+ remote_as: 65562
+ description: site-1-nbr-2
+ - vrf: site-2
+ local_as: 300
+ log_neighbor_changes: True
+ neighbors:
+ - neighbor_address: 203.0.113.2
+ description: site-1-nbr-1
+ password:
+ encryption: 3
+ key: AF92F4C16A0A0EC5BDF56CF58BC030F6
+ remote_as: 65568
+ neighbor_down:
+ fib_accelerate: True
+
+# Task Output (redacted)
+# -----------------------
+# rendered:
+# - router bgp 65563
+# - bestpath as-path multipath-relax
+# - bestpath compare-neighborid
+# - bestpath cost-community ignore
+# - confederation identifier 42
+# - log-neighbor-changes
+# - maxas-limit 20
+# - neighbor-down fib-accelerate
+# - router-id 192.168.1.1
+# - confederation peers 65020 65030 65040
+# - neighbor 192.168.1.100
+# - remote-as 65563
+# - affinity-group 160
+# - bmp-activate-server 1
+# - description NBR-1
+# - low-memory exempt
+# - neighbor 192.168.1.101
+# - remote-as 65563
+# - password 7 12090404011C03162E
+# - vrf site-1
+# - allocate-index 5000
+# - local-as 200
+# - log-neighbor-changes
+# - neighbor 198.51.100.1
+# - remote-as 65562
+# - description site-1-nbr-1
+# - password 3 13D4D3549493D2877B1DC116EE27A6BE
+# - neighbor 198.51.100.2
+# - remote-as 65562
+# - description site-1-nbr-2
+# - vrf site-2
+# - local-as 300
+# - log-neighbor-changes
+# - neighbor-down fib-accelerate
+# - neighbor 203.0.113.2
+# - remote-as 65568
+# - description site-1-nbr-1
+# - password 3 AF92F4C16A0A0EC5BDF56CF58BC030F6
+
+# Using parsed
+
+# parsed.cfg
+# ------------
+# router bgp 65563
+# router-id 192.168.1.1
+# confederation identifier 42
+# confederation peers 65020 65030 65040
+# bestpath as-path multipath-relax
+# bestpath cost-community ignore
+# bestpath compare-neighborid
+# neighbor-down fib-accelerate
+# maxas-limit 20
+# log-neighbor-changes
+# neighbor 192.168.1.100
+# low-memory exempt
+# bmp-activate-server 1
+# remote-as 65563
+# description NBR-1
+# affinity-group 160
+# neighbor 192.168.1.101
+# remote-as 65563
+# password 7 12090404011C03162E
+# vrf site-1
+# local-as 200
+# log-neighbor-changes
+# allocate-index 5000
+# neighbor 198.51.100.1
+# remote-as 65562
+# description site-1-nbr-1
+# password 3 13D4D3549493D2877B1DC116EE27A6BE
+# neighbor 198.51.100.2
+# remote-as 65562
+# description site-1-nbr-2
+# vrf site-2
+# local-as 300
+# neighbor-down fib-accelerate
+# log-neighbor-changes
+# neighbor 203.0.113.2
+# remote-as 65568
+# description site-1-nbr-1
+# password 3 AF92F4C16A0A0EC5BDF56CF58BC030F6
+
+- name: Parse externally provided BGP config
+ cisco.nxos.nxos_bgp_global:
+ running_config: "{{ lookup('file', 'parsed.cfg') }}"
+ state: parsed
+
+# Task output (redacted)
+# -----------------------
+# parsed:
+# as_number: '65563'
+# bestpath:
+# as_path:
+# multipath_relax: true
+# compare_neighborid: true
+# cost_community_ignore: true
+# confederation:
+# identifier: '42'
+# peers:
+# - '65020'
+# - '65030'
+# - '65040'
+# log_neighbor_changes: true
+# maxas_limit: 20
+# neighbor_down:
+# fib_accelerate: true
+# neighbors:
+# - bmp_activate_server: 1
+# description: NBR-1
+# low_memory:
+# exempt: true
+# neighbor_address: 192.168.1.100
+# neighbor_affinity_group:
+# group_id: 160
+# remote_as: '65563'
+# - neighbor_address: 192.168.1.101
+# password:
+# encryption: 7
+# key: 12090404011C03162E
+# remote_as: '65563'
+# router_id: 192.168.1.1
+# vrfs:
+# - allocate_index: 5000
+# local_as: '200'
+# log_neighbor_changes: true
+# neighbors:
+# - description: site-1-nbr-1
+# neighbor_address: 198.51.100.1
+# password:
+# encryption: 3
+# key: 13D4D3549493D2877B1DC116EE27A6BE
+# remote_as: '65562'
+# - description: site-1-nbr-2
+# neighbor_address: 198.51.100.2
+# remote_as: '65562'
+# vrf: site-1
+# - local_as: '300'
+# log_neighbor_changes: true
+# neighbor_down:
+# fib_accelerate: true
+# neighbors:
+# - description: site-1-nbr-1
+# neighbor_address: 203.0.113.2
+# password:
+# encryption: 3
+# key: AF92F4C16A0A0EC5BDF56CF58BC030F6
+# remote_as: '65568'
+# vrf: site-2
+
+# Using gathered
+
+# existing config
+#
+# Nexus9000v# show running-config | section "^router bgp"
+# router bgp 65563
+# router-id 192.168.1.1
+# confederation identifier 42
+# confederation peers 65020 65030 65050
+# bestpath cost-community ignore
+# bestpath compare-neighborid
+# neighbor-down fib-accelerate
+# maxas-limit 40
+# neighbor 192.168.1.100
+# low-memory exempt
+# bmp-activate-server 1
+# remote-as 65563
+# description NBR-1
+# affinity-group 160
+# vrf site-1
+# vrf site-2
+# local-as 300
+# neighbor-down fib-accelerate
+# log-neighbor-changes
+# neighbor 203.0.113.2
+# password 7 12090404011C03162E
+
+- name: Gather BGP facts using gathered
+ cisco.nxos.nxos_bgp_global:
+ state: gathered
+
+# Task output (redacted)
+# -----------------------
+# gathered:
+# as_number: '65563'
+# bestpath:
+# compare_neighborid: true
+# cost_community_ignore: true
+# confederation:
+# identifier: '42'
+# peers:
+# - '65020'
+# - '65030'
+# - '65050'
+# maxas_limit: 40
+# neighbor_down:
+# fib_accelerate: true
+# neighbors:
+# - bmp_activate_server: 1
+# description: NBR-1
+# low_memory:
+# exempt: true
+# neighbor_address: 192.168.1.100
+# neighbor_affinity_group:
+# group_id: 160
+# remote_as: '65563'
+# router_id: 192.168.1.1
+# vrfs:
+# - vrf: site-1
+# - local_as: '300'
+# log_neighbor_changes: true
+# neighbor_down:
+# fib_accelerate: true
+# neighbors:
+# - neighbor_address: 203.0.113.2
+# password:
+# encryption: 7
+# key: 12090404011C03162E
+# vrf: site-2
+
+# Remove a neighbor having AF configurations with state replaced (will fail)
+
+# Before state:
+# -------------
+# Nexus9000v# show running-config | section "^router bgp"
+# router bgp 65536
+# log-neighbor-changes
+# maxas-limit 20
+# router-id 198.51.100.2
+# neighbor 203.0.113.2
+# address-family ipv4 unicast
+# next-hop-self
+# remote-as 65538
+# affinity-group 160
+# description NBR-1
+# low-memory exempt
+# neighbor 192.0.2.1
+# remote-as 65537
+# password 7 12090404011C03162E
+
+- name: Remove a neighbor having AF configurations (should fail)
+ cisco.nxos.nxos_bgp_global:
+ config:
+ as_number: 65536
+ router_id: 198.51.100.2
+ maxas_limit: 20
+ log_neighbor_changes: True
+ neighbors:
+ - neighbor_address: 192.0.2.1
+ remote_as: 65537
+ password:
+ encryption: 7
+ key: 12090404011C03162E
+ state: replaced
+
+# Task output (redacted)
+# -----------------------
+# fatal: [Nexus9000v]: FAILED! => changed=false
+# msg: Neighbor 203.0.113.2 has address-family configurations.
+# Please use the nxos_bgp_neighbor_af module to remove those first.
+
+# Remove a VRF having AF configurations with state replaced (will fail)
+
+# Before state:
+# -------------
+# Nexus9000v# show running-config | section "^router bgp"
+# router bgp 65536
+# log-neighbor-changes
+# maxas-limit 20
+# router-id 198.51.100.2
+# neighbor 192.0.2.1
+# remote-as 65537
+# password 7 12090404011C03162E
+# vrf site-1
+# address-family ipv4 unicast
+# default-information originate
+# neighbor 203.0.113.2
+# remote-as 65538
+# affinity-group 160
+# description NBR-1
+# low-memory exempt
+# vrf site-2
+# neighbor-down fib-accelerate
+
+- name: Remove a VRF having AF configurations (should fail)
+ cisco.nxos.nxos_bgp_global:
+ config:
+ as_number: 65536
+ router_id: 198.51.100.2
+ maxas_limit: 20
+ log_neighbor_changes: True
+ neighbors:
+ - neighbor_address: 192.0.2.1
+ remote_as: 65537
+ password:
+ encryption: 7
+ key: 12090404011C03162E
+ vrfs:
+ - vrf: site-2
+ neighbor_down:
+ fib_accelerate: True
+ state: replaced
+
+# Task output (redacted)
+# -----------------------
+# fatal: [Nexus9000v]: FAILED! => changed=false
+# msg: VRF site-1 has address-family configurations.
+# Please use the nxos_bgp_af module to remove those first.
+"""
+
+RETURN = """
+before:
+ description: The configuration prior to the model invocation.
+ returned: always
+ type: dict
+ 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: dict
+ 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 bgp 65563
+ - maxas-limit 20
+ - router-id 192.168.1.1
+ - confederation peers 65020 65030 65040
+ - neighbor 192.168.1.100
+ - remote-as 65563
+ - affinity-group 160
+ - bmp-activate-server 1
+ - description NBR-1
+ - low-memory exempt
+ - vrf site-1
+ - log-neighbor-changes
+ - neighbor 198.51.100.1
+ - remote-as 65562
+ - description site-1-nbr-1
+ - password 3 13D4D3549493D2877B1DC116EE27A6BE
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.bgp_global.bgp_global import (
+ Bgp_globalArgs,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.config.bgp_global.bgp_global import (
+ Bgp_global,
+)
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ module = AnsibleModule(
+ argument_spec=Bgp_globalArgs.argument_spec,
+ mutually_exclusive=[["config", "running_config"]],
+ required_if=[
+ ["state", "merged", ["config"]],
+ ["state", "replaced", ["config"]],
+ ["state", "rendered", ["config"]],
+ ["state", "parsed", ["running_config"]],
+ ],
+ supports_check_mode=True,
+ )
+
+ result = Bgp_global(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_bgp_neighbor.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_bgp_neighbor.py
new file mode 100644
index 00000000..2c5283ca
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_bgp_neighbor.py
@@ -0,0 +1,569 @@
+#!/usr/bin/python
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_bgp_neighbor
+extends_documentation_fragment:
+- cisco.nxos.nxos
+short_description: (deprecated, removed after 2023-01-27) Manages BGP neighbors configurations.
+description:
+- Manages BGP neighbors configurations on NX-OS switches.
+version_added: 1.0.0
+author: Gabriele Gerbino (@GGabriele)
+deprecated:
+ alternative: nxos_bgp_global
+ why: Updated module released with more functionality.
+ removed_at_date: '2023-01-27'
+notes:
+- Tested against NXOSv 7.3.(0)D1(1) on VIRL
+- Unsupported for Cisco MDS
+- C(state=absent) removes the whole BGP neighbor configuration.
+- Default, where supported, restores params default value.
+options:
+ asn:
+ description:
+ - BGP autonomous system number. Valid values are string, Integer in ASPLAIN or
+ ASDOT notation.
+ required: true
+ type: str
+ vrf:
+ description:
+ - Name of the VRF. The name 'default' is a valid VRF representing the global bgp.
+ default: default
+ type: str
+ neighbor:
+ description:
+ - Neighbor Identifier. Valid values are string. Neighbors may use IPv4 or IPv6
+ notation, with or without prefix length.
+ required: true
+ type: str
+ description:
+ description:
+ - Description of the neighbor.
+ type: str
+ bfd:
+ description:
+ - Enables/Disables BFD for a given neighbor.
+ - "Dependency: ''feature bfd''"
+ type: str
+ choices:
+ - enable
+ - disable
+ connected_check:
+ description:
+ - Configure whether or not to check for directly connected peer.
+ type: bool
+ capability_negotiation:
+ description:
+ - Configure whether or not to negotiate capability with this neighbor.
+ type: bool
+ dynamic_capability:
+ description:
+ - Configure whether or not to enable dynamic capability.
+ type: bool
+ ebgp_multihop:
+ description:
+ - Specify multihop TTL for a remote peer. Valid values are integers between 2
+ and 255, or keyword 'default' to disable this property.
+ type: str
+ local_as:
+ description:
+ - Specify the local-as number for the eBGP neighbor. Valid values are String or
+ Integer in ASPLAIN or ASDOT notation, or 'default', which means not to configure
+ it.
+ type: str
+ log_neighbor_changes:
+ description:
+ - Specify whether or not to enable log messages for neighbor up/down event.
+ choices:
+ - enable
+ - disable
+ - inherit
+ type: str
+ low_memory_exempt:
+ description:
+ - Specify whether or not to shut down this neighbor under memory pressure.
+ type: bool
+ maximum_peers:
+ description:
+ - Specify Maximum number of peers for this neighbor prefix Valid values are between
+ 1 and 1000, or 'default', which does not impose the limit. Note that this parameter
+ is accepted only on neighbors with address/prefix.
+ type: str
+ pwd:
+ description:
+ - Specify the password for neighbor. Valid value is string.
+ type: str
+ pwd_type:
+ description:
+ - Specify the encryption type the password will use. Valid values are '3des' or
+ 'cisco_type_7' encryption or keyword 'default'.
+ choices:
+ - 3des
+ - cisco_type_7
+ - default
+ type: str
+ remote_as:
+ description:
+ - Specify Autonomous System Number of the neighbor. Valid values are String or
+ Integer in ASPLAIN or ASDOT notation, or 'default', which means not to configure
+ it.
+ type: str
+ remove_private_as:
+ description:
+ - Specify the config to remove private AS number from outbound updates. Valid
+ values are 'enable' to enable this config, 'disable' to disable this config,
+ 'all' to remove all private AS number, or 'replace-as', to replace the private
+ AS number.
+ choices:
+ - enable
+ - disable
+ - all
+ - replace-as
+ type: str
+ shutdown:
+ description:
+ - Configure to administratively shutdown this neighbor.
+ type: bool
+ suppress_4_byte_as:
+ description:
+ - Configure to suppress 4-byte AS Capability.
+ type: bool
+ timers_keepalive:
+ description:
+ - Specify keepalive timer value. Valid values are integers between 0 and 3600
+ in terms of seconds, or 'default', which is 60.
+ type: str
+ timers_holdtime:
+ description:
+ - Specify holdtime timer value. Valid values are integers between 0 and 3600 in
+ terms of seconds, or 'default', which is 180.
+ type: str
+ transport_passive_only:
+ description:
+ - Specify whether or not to only allow passive connection setup. Valid values
+ are 'true', 'false', and 'default', which defaults to 'false'. This property
+ can only be configured when the neighbor is in 'ip' address format without prefix
+ length.
+ type: bool
+ update_source:
+ description:
+ - Specify source interface of BGP session and updates.
+ type: str
+ state:
+ description:
+ - Determines whether the config should be present or not on the device.
+ default: present
+ choices:
+ - present
+ - absent
+ type: str
+ peer_type:
+ description:
+ - Specify the peer type for BGP session.
+ choices:
+ - fabric_border_leaf
+ - fabric_external
+ - disable
+ type: str
+ version_added: 1.1.0
+"""
+EXAMPLES = """
+# create a new neighbor
+- cisco.nxos.nxos_bgp_neighbor:
+ asn: 65535
+ neighbor: 192.0.2.3
+ local_as: 20
+ remote_as: 30
+ bfd: enable
+ description: just a description
+ update_source: Ethernet1/3
+ state: present
+ peer_type: fabric_external
+"""
+
+RETURN = """
+commands:
+ description: commands sent to the device
+ returned: always
+ type: list
+ sample: ["router bgp 65535", "neighbor 192.0.2.3",
+ "remote-as 30", "update-source Ethernet1/3",
+ "description just a description", "local-as 20", "peer-type fabric-external"]
+"""
+
+import re
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import (
+ CustomNetworkConfig,
+)
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ get_config,
+ load_config,
+)
+
+
+BOOL_PARAMS = [
+ "capability_negotiation",
+ "shutdown",
+ "connected_check",
+ "dynamic_capability",
+ "low_memory_exempt",
+ "suppress_4_byte_as",
+ "transport_passive_only",
+]
+PARAM_TO_COMMAND_KEYMAP = {
+ "asn": "router bgp",
+ "bfd": "bfd",
+ "capability_negotiation": "dont-capability-negotiate",
+ "connected_check": "disable-connected-check",
+ "description": "description",
+ "dynamic_capability": "dynamic-capability",
+ "ebgp_multihop": "ebgp-multihop",
+ "local_as": "local-as",
+ "log_neighbor_changes": "log-neighbor-changes",
+ "low_memory_exempt": "low-memory exempt",
+ "maximum_peers": "maximum-peers",
+ "neighbor": "neighbor",
+ "pwd": "password",
+ "pwd_type": "password",
+ "remote_as": "remote-as",
+ "remove_private_as": "remove-private-as",
+ "shutdown": "shutdown",
+ "suppress_4_byte_as": "capability suppress 4-byte-as",
+ "timers_keepalive": "timers",
+ "timers_holdtime": "timers",
+ "transport_passive_only": "transport connection-mode passive",
+ "update_source": "update-source",
+ "vrf": "vrf",
+ "peer_type": "peer-type",
+}
+PARAM_TO_DEFAULT_KEYMAP = {
+ "bfd": "disable",
+ "shutdown": False,
+ "dynamic_capability": True,
+ "timers_keepalive": 60,
+ "timers_holdtime": 180,
+ "peer_type": "disable",
+}
+
+
+def get_value(arg, config):
+ command = PARAM_TO_COMMAND_KEYMAP[arg]
+ has_command = re.search(r"^\s+{0}$".format(command), config, re.M)
+ has_command_val = re.search(r"(?:\s+{0}\s*)(?P<value>.*)$".format(command), config, re.M)
+
+ if arg == "dynamic_capability":
+ has_no_command = re.search(r"\s+no\s{0}\s*$".format(command), config, re.M)
+ value = True
+ if has_no_command:
+ value = False
+ elif arg in BOOL_PARAMS:
+ value = False
+ if has_command:
+ value = True
+ elif arg == "log_neighbor_changes":
+ value = ""
+ if has_command:
+ value = "enable"
+ elif has_command_val:
+ value = "disable"
+
+ elif arg == "remove_private_as":
+ value = "disable"
+ if has_command:
+ value = "enable"
+ elif has_command_val:
+ value = has_command_val.group("value")
+ elif arg == "bfd":
+ value = "enable" if has_command else "disable"
+
+ elif arg == "peer_type":
+ value = "disable"
+ if has_command_val:
+ value = has_command_val.group("value").replace("-", "_")
+ else:
+ value = ""
+
+ if has_command_val:
+ value = has_command_val.group("value")
+
+ if command in ["timers", "password"]:
+ split_value = value.split()
+ value = ""
+
+ if arg in ["timers_keepalive", "pwd_type"]:
+ value = split_value[0]
+ elif arg in ["timers_holdtime", "pwd"] and len(split_value) == 2:
+ value = split_value[1]
+
+ return value
+
+
+def get_existing(module, args, warnings):
+ existing = {}
+ netcfg = CustomNetworkConfig(indent=2, contents=get_config(module))
+
+ asn_regex = re.compile(r".*router\sbgp\s(?P<existing_asn>\d+(\.\d+)?).*", re.S)
+ match_asn = asn_regex.match(str(netcfg))
+
+ if match_asn:
+ existing_asn = match_asn.group("existing_asn")
+ parents = ["router bgp {0}".format(existing_asn)]
+
+ if module.params["vrf"] != "default":
+ parents.append("vrf {0}".format(module.params["vrf"]))
+
+ parents.append("neighbor {0}".format(module.params["neighbor"]))
+ config = netcfg.get_section(parents)
+ if config:
+ for arg in args:
+ if arg not in ["asn", "vrf", "neighbor"]:
+ existing[arg] = get_value(arg, config)
+
+ existing["asn"] = existing_asn
+ existing["neighbor"] = module.params["neighbor"]
+ existing["vrf"] = module.params["vrf"]
+ else:
+ warnings.append("The BGP process didn't exist but the task just created it.")
+ return existing
+
+
+def apply_key_map(key_map, table):
+ new_dict = {}
+ for key in table:
+ new_key = key_map.get(key)
+ if new_key:
+ new_dict[new_key] = table.get(key)
+
+ return new_dict
+
+
+def state_present(module, existing, proposed, candidate):
+ commands = list()
+ proposed_commands = apply_key_map(PARAM_TO_COMMAND_KEYMAP, proposed)
+ existing_commands = apply_key_map(PARAM_TO_COMMAND_KEYMAP, existing)
+
+ for key, value in proposed_commands.items():
+ if value is True:
+ commands.append(key)
+ elif value is False:
+ commands.append("no {0}".format(key))
+ elif value == "default":
+ if existing_commands.get(key):
+ if key == "password":
+ commands.append("no password")
+ else:
+ existing_value = existing_commands.get(key)
+ commands.append("no {0} {1}".format(key, existing_value))
+ else:
+ if key == "log-neighbor-changes":
+ if value == "enable":
+ commands.append("{0}".format(key))
+ elif value == "disable":
+ commands.append("{0} {1}".format(key, value))
+ elif value == "inherit":
+ if existing_commands.get(key):
+ commands.append("no {0}".format(key))
+ elif key == "password":
+ pwd_type = module.params["pwd_type"]
+ if pwd_type == "3des":
+ pwd_type = 3
+ else:
+ pwd_type = 7
+ command = "{0} {1} {2}".format(key, pwd_type, value)
+ if command not in commands:
+ commands.append(command)
+ elif key == "remove-private-as":
+ if value == "enable":
+ command = "{0}".format(key)
+ commands.append(command)
+ elif value == "disable":
+ if existing_commands.get(key) != "disable":
+ command = "no {0}".format(key)
+ commands.append(command)
+ else:
+ command = "{0} {1}".format(key, value)
+ commands.append(command)
+ elif key == "timers":
+ if proposed["timers_keepalive"] != PARAM_TO_DEFAULT_KEYMAP.get(
+ "timers_keepalive",
+ ) or proposed["timers_holdtime"] != PARAM_TO_DEFAULT_KEYMAP.get("timers_holdtime"):
+ command = "timers {0} {1}".format(
+ proposed["timers_keepalive"],
+ proposed["timers_holdtime"],
+ )
+ if command not in commands:
+ commands.append(command)
+ elif key == "bfd":
+ no_cmd = "no " if value == "disable" else ""
+ commands.append(no_cmd + key)
+ elif key == "peer-type":
+ if value == "disable":
+ if existing_commands.get(key) != "disable":
+ command = "no {0}".format(key)
+ commands.append(command)
+ elif value == "fabric_external":
+ ptype = "fabric-external"
+ command = "{0} {1}".format(key, ptype)
+ commands.append(command)
+ elif value == "fabric_border_leaf":
+ ptype = "fabric-border-leaf"
+ command = "{0} {1}".format(key, ptype)
+ commands.append(command)
+ else:
+ command = "{0} {1}".format(key, value)
+ commands.append(command)
+
+ if commands:
+ parents = ["router bgp {0}".format(module.params["asn"])]
+ if module.params["vrf"] != "default":
+ parents.append("vrf {0}".format(module.params["vrf"]))
+
+ parents.append("neighbor {0}".format(module.params["neighbor"]))
+
+ # make sure that local-as is the last command in the list.
+ local_as_command = "local-as {0}".format(module.params["local_as"])
+ if local_as_command in commands:
+ commands.remove(local_as_command)
+ commands.append(local_as_command)
+ candidate.add(commands, parents=parents)
+
+
+def state_absent(module, existing, proposed, candidate):
+ commands = []
+ parents = ["router bgp {0}".format(module.params["asn"])]
+ if module.params["vrf"] != "default":
+ parents.append("vrf {0}".format(module.params["vrf"]))
+
+ commands.append("no neighbor {0}".format(module.params["neighbor"]))
+ candidate.add(commands, parents=parents)
+
+
+def main():
+ argument_spec = dict(
+ asn=dict(required=True, type="str"),
+ vrf=dict(required=False, type="str", default="default"),
+ neighbor=dict(required=True, type="str"),
+ description=dict(required=False, type="str"),
+ bfd=dict(required=False, type="str", choices=["enable", "disable"]),
+ capability_negotiation=dict(required=False, type="bool"),
+ connected_check=dict(required=False, type="bool"),
+ dynamic_capability=dict(required=False, type="bool"),
+ ebgp_multihop=dict(required=False, type="str"),
+ local_as=dict(required=False, type="str"),
+ log_neighbor_changes=dict(
+ required=False,
+ type="str",
+ choices=["enable", "disable", "inherit"],
+ ),
+ low_memory_exempt=dict(required=False, type="bool"),
+ maximum_peers=dict(required=False, type="str"),
+ pwd=dict(required=False, type="str"),
+ pwd_type=dict(
+ required=False,
+ type="str",
+ choices=["3des", "cisco_type_7", "default"],
+ ),
+ remote_as=dict(required=False, type="str"),
+ remove_private_as=dict(
+ required=False,
+ type="str",
+ choices=["enable", "disable", "all", "replace-as"],
+ ),
+ shutdown=dict(required=False, type="bool"),
+ suppress_4_byte_as=dict(required=False, type="bool"),
+ timers_keepalive=dict(required=False, type="str"),
+ timers_holdtime=dict(required=False, type="str"),
+ transport_passive_only=dict(required=False, type="bool"),
+ update_source=dict(required=False, type="str"),
+ state=dict(choices=["present", "absent"], default="present", required=False),
+ peer_type=dict(
+ required=False,
+ type="str",
+ choices=["disable", "fabric_border_leaf", "fabric_external"],
+ ),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ required_together=[
+ ["timers_holdtime", "timers_keepalive"],
+ ["pwd", "pwd_type"],
+ ],
+ supports_check_mode=True,
+ )
+
+ warnings = list()
+ result = dict(changed=False, warnings=warnings)
+
+ state = module.params["state"]
+
+ if module.params["pwd_type"] == "default":
+ module.params["pwd_type"] = "0"
+
+ args = PARAM_TO_COMMAND_KEYMAP.keys()
+ existing = get_existing(module, args, warnings)
+
+ if existing.get("asn") and state == "present":
+ if existing["asn"] != module.params["asn"]:
+ module.fail_json(
+ msg="Another BGP ASN already exists.",
+ proposed_asn=module.params["asn"],
+ existing_asn=existing.get("asn"),
+ )
+
+ proposed_args = dict((k, v) for k, v in module.params.items() if v is not None and k in args)
+ proposed = {}
+ for key, value in proposed_args.items():
+ if key not in ["asn", "vrf", "neighbor", "pwd_type"]:
+ if str(value).lower() == "default":
+ value = PARAM_TO_DEFAULT_KEYMAP.get(key, "default")
+ if key == "bfd":
+ if existing.get("bfd", "disable") != value:
+ proposed[key] = value
+ elif existing.get(key) != value:
+ proposed[key] = value
+
+ candidate = CustomNetworkConfig(indent=3)
+ if state == "present":
+ state_present(module, existing, proposed, candidate)
+ elif state == "absent" and existing:
+ state_absent(module, existing, proposed, candidate)
+
+ if candidate:
+ candidate = candidate.items_text()
+ if not module.check_mode:
+ load_config(module, candidate)
+ result["changed"] = True
+ result["commands"] = candidate
+ else:
+ result["commands"] = []
+
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_bgp_neighbor_address_family.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_bgp_neighbor_address_family.py
new file mode 100644
index 00000000..967f8ee4
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_bgp_neighbor_address_family.py
@@ -0,0 +1,1155 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# Copyright 2021 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+"""
+The module file for nxos_bgp_neighbor_address_family
+"""
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+DOCUMENTATION = """
+module: nxos_bgp_neighbor_address_family
+short_description: BGP Neighbor Address Family resource module.
+description:
+- This module manages BGP Neighbor Address Family configuration on devices running Cisco NX-OS.
+version_added: 2.0.0
+notes:
+- Tested against NX-OS 9.3.6.
+- Unsupported for Cisco MDS
+- For managing BGP address family configurations please use
+ the M(cisco.nxos.nxos_bgp_address_family) module.
+- This module works with connection C(network_cli) and C(httpapi).
+author: Nilashish Chakraborty (@NilashishC)
+options:
+ running_config:
+ description:
+ - This option is used only with state I(parsed).
+ - The value of this option should be the output received from the NX-OS device
+ by executing the command B(show running-config | section '^router bgp').
+ - The state I(parsed) reads the configuration from C(running_config) option and
+ transforms it into Ansible structured data as per the resource module's argspec
+ and the value is then returned in the I(parsed) key within the result.
+ type: str
+ config:
+ description: BGP Neighbor AF configuration.
+ type: dict
+ suboptions:
+ as_number:
+ description: Autonomous System Number of the router.
+ type: str
+ neighbors: &nbr
+ description: A list of BGP Neighbor AF configuration.
+ type: list
+ elements: dict
+ suboptions:
+ neighbor_address:
+ description: IP/IPv6 address of the neighbor.
+ type: str
+ required: True
+ address_family:
+ description: BGP Neighbor Address Family related configurations.
+ type: list
+ elements: dict
+ suboptions:
+ afi:
+ description: Address Family indicator.
+ type: str
+ choices: ["ipv4", "ipv6", "link-state", "vpnv4", "vpnv6", "l2vpn"]
+ required: True
+ safi:
+ description: Sub Address Family indicator.
+ type: str
+ choices: ["unicast", "multicast", "mvpn", "evpn"]
+ advertise_map:
+ description: Specify route-map for conditional advertisement.
+ type: dict
+ suboptions:
+ route_map:
+ description: Route-map name.
+ type: str
+ required: True
+ exist_map:
+ description: Condition route-map to advertise only when prefix in condition exists.
+ type: str
+ non_exist_map:
+ description: Condition route-map to advertise only when prefix in condition does not exist.
+ type: str
+ advertisement_interval:
+ description: Minimum interval between sending BGP routing updates.
+ type: int
+ allowas_in:
+ description: Accept as-path with my AS present in it.
+ type: dict
+ suboptions:
+ set:
+ description: Activate allowas-in property.
+ type: bool
+ max_occurences:
+ description: Number of occurrences of AS number, default is 3.
+ type: int
+ as_override:
+ description: Override matching AS-number while sending update.
+ type: bool
+ capability:
+ description: Advertise capability to the peer.
+ type: dict
+ suboptions:
+ additional_paths:
+ description: Additional paths capability.
+ type: dict
+ suboptions:
+ receive:
+ description: Additional paths Receive capability.
+ type: str
+ choices: ["enable", "disable"]
+ send:
+ description: Additional paths Send capability.
+ type: str
+ choices: ["enable", "disable"]
+ default_originate:
+ description: Originate a default toward this peer.
+ type: dict
+ suboptions:
+ set:
+ description: Set default-originate attribute.
+ type: bool
+ route_map:
+ description: Route-map to specify criteria for originating default.
+ type: str
+ disable_peer_as_check:
+ description: Disable checking of peer AS-number while advertising.
+ type: bool
+ filter_list:
+ description: Name of filter-list.
+ type: dict
+ suboptions:
+ inbound:
+ description: Apply policy to incoming routes.
+ type: str
+ outbound:
+ description: Apply policy to outgoing routes.
+ type: str
+ inherit:
+ description: Inherit a template.
+ type: dict
+ suboptions:
+ template:
+ description: Template name.
+ type: str
+ sequence:
+ description: Sequence number.
+ type: int
+ maximum_prefix:
+ description: Maximum number of prefixes from this neighbor.
+ type: dict
+ suboptions:
+ max_prefix_limit:
+ description: Maximum prefix limit.
+ type: int
+ generate_warning_threshold:
+ description: Threshold percentage at which to generate a warning.
+ type: int
+ restart_interval:
+ description: Restart bgp connection after limit is exceeded.
+ type: int
+ warning_only:
+ description: Only give a warning message when limit is exceeded.
+ type: bool
+ next_hop_self:
+ description: Set our address as nexthop (non-reflected).
+ type: dict
+ suboptions:
+ set:
+ description: Set next-hop-self attribute.
+ type: bool
+ all_routes:
+ description: Set our address as nexthop for all routes.
+ type: bool
+ next_hop_third_party:
+ description: Compute a third-party nexthop if possible.
+ type: bool
+ prefix_list:
+ description: Apply prefix-list.
+ type: dict
+ suboptions:
+ inbound:
+ description: Apply policy to incoming routes.
+ type: str
+ outbound:
+ description: Apply policy to outgoing routes.
+ type: str
+ rewrite_evpn_rt_asn:
+ description: Auto generate RTs for EBGP neighbor.
+ type: bool
+ route_map:
+ description: Apply route-map to neighbor.
+ type: dict
+ suboptions:
+ inbound:
+ description: Apply policy to incoming routes.
+ type: str
+ outbound:
+ description: Apply policy to outgoing routes.
+ type: str
+ route_reflector_client:
+ description: Configure a neighbor as Route reflector client.
+ type: bool
+ send_community:
+ description: Send Community attribute to this neighbor.
+ type: dict
+ suboptions:
+ set:
+ description: Set send-community attribute.
+ type: bool
+ extended:
+ description: Send Extended Community attribute.
+ type: bool
+ standard:
+ description: Send Standard Community attribute.
+ type: bool
+ both:
+ description: Send Standard and Extended Community attributes.
+ type: bool
+ soft_reconfiguration_inbound:
+ description: Soft reconfiguration.
+ type: dict
+ suboptions:
+ set:
+ description: Set soft-reconfiguration inbound attribute.
+ type: bool
+ always:
+ description: Always perform inbound soft reconfiguration.
+ type: bool
+ soo:
+ description: Specify Site-of-origin extcommunity.
+ type: str
+ suppress_inactive:
+ description: Advertise only active routes to peer.
+ type: bool
+ unsuppress_map:
+ description: Route-map to selectively unsuppress suppressed routes.
+ type: str
+ weight:
+ description: Set default weight for routes from this neighbor.
+ type: int
+ vrfs:
+ description: Virtual Router Context.
+ type: list
+ elements: dict
+ suboptions:
+ vrf:
+ description: VRF name.
+ type: str
+ neighbors: *nbr
+ state:
+ description:
+ - The state the configuration should be left in.
+ - State I(deleted) only removes BGP attributes that this modules
+ manages and does not negate the BGP process completely.
+ - Refer to examples for more details.
+ type: str
+ choices:
+ - merged
+ - replaced
+ - overridden
+ - deleted
+ - parsed
+ - gathered
+ - rendered
+ default: merged
+"""
+EXAMPLES = """
+# Using merged
+
+# Before state:
+# -------------
+# Nexus9000v# show running-config | section "^router bgp"
+# Nexus9000v#
+
+- name: Merge the provided configuration with the existing running configuration
+ cisco.nxos.nxos_bgp_neighbor_address_family: &id001
+ config:
+ as_number: 65536
+ neighbors:
+ - neighbor_address: 192.0.2.32
+ address_family:
+ - afi: ipv4
+ safi: unicast
+ maximum_prefix:
+ max_prefix_limit: 20
+ generate_warning_threshold: 75
+ weight: 100
+ prefix_list:
+ inbound: rmap1
+ outbound: rmap2
+ - afi: ipv6
+ safi: unicast
+ - neighbor_address: 192.0.2.33
+ address_family:
+ - afi: ipv4
+ safi: multicast
+ inherit:
+ template: BasePolicy
+ sequence: 200
+ vrfs:
+ - vrf: site-1
+ neighbors:
+ - neighbor_address: 203.0.113.1
+ address_family:
+ - afi: ipv4
+ safi: unicast
+ suppress_inactive: True
+ next_hop_self:
+ set: True
+ - neighbor_address: 203.0.113.2
+ address_family:
+ - afi: ipv6
+ safi: unicast
+ - afi: ipv4
+ safi: multicast
+ send_community:
+ set: True
+
+# Task output
+# -------------
+# before: {}
+#
+# commands:
+# - router bgp 65536
+# - neighbor 192.0.2.32
+# - address-family ipv4 unicast
+# - maximum-prefix 20 75
+# - weight 100
+# - prefix-list rmap1 in
+# - prefix-list rmap2 out
+# - address-family ipv6 unicast
+# - neighbor 192.0.2.33
+# - address-family ipv4 multicast
+# - inherit peer-policy BasePolicy 200
+# - vrf site-1
+# - neighbor 203.0.113.1
+# - address-family ipv4 unicast
+# - suppress-inactive
+# - next-hop-self
+# - neighbor 203.0.113.2
+# - address-family ipv6 unicast
+# - address-family ipv4 multicast
+# - send-community
+#
+# after:
+# as_number: "65536"
+# neighbors:
+# - neighbor_address: 192.0.2.32
+# address_family:
+# - afi: ipv4
+# safi: unicast
+# maximum_prefix:
+# max_prefix_limit: 20
+# generate_warning_threshold: 75
+# weight: 100
+# prefix_list:
+# inbound: rmap1
+# outbound: rmap2
+# - afi: ipv6
+# safi: unicast
+# - neighbor_address: 192.0.2.33
+# address_family:
+# - afi: ipv4
+# safi: multicast
+# inherit:
+# template: BasePolicy
+# sequence: 200
+# vrfs:
+# - vrf: site-1
+# neighbors:
+# - neighbor_address: 203.0.113.1
+# address_family:
+# - afi: ipv4
+# safi: unicast
+# suppress_inactive: true
+# next_hop_self:
+# set: true
+# - neighbor_address: 203.0.113.2
+# address_family:
+# - afi: ipv4
+# safi: multicast
+# send_community:
+# set: True
+# - afi: ipv6
+# safi: unicast
+
+# After state:
+# -------------
+# Nexus9000v# show running-config | section "^router bgp"
+# router bgp 65536
+# neighbor 192.0.2.32
+# address-family ipv4 unicast
+# maximum-prefix 20 75
+# weight 100
+# prefix-list rmap1 in
+# prefix-list rmap2 out
+# address-family ipv6 unicast
+# neighbor 192.0.2.33
+# address-family ipv4 multicast
+# inherit peer-policy BasePolicy 200
+# vrf site-1
+# neighbor 203.0.113.1
+# address-family ipv4 unicast
+# suppress-inactive
+# next-hop-self
+# neighbor 203.0.113.2
+# address-family ipv4 multicast
+# send-community
+# address-family ipv6 unicast
+#
+
+# Using replaced
+
+# Before state:
+# -------------
+# Nexus9000v# show running-config | section "^router bgp"
+# router bgp 65536
+# neighbor 192.0.2.32
+# address-family ipv4 unicast
+# maximum-prefix 20 75
+# weight 100
+# prefix-list rmap1 in
+# prefix-list rmap2 out
+# address-family ipv6 unicast
+# neighbor 192.0.2.33
+# address-family ipv4 multicast
+# inherit peer-policy BasePolicy 200
+# vrf site-1
+# neighbor 203.0.113.1
+# address-family ipv4 unicast
+# suppress-inactive
+# next-hop-self
+# neighbor 203.0.113.2
+# address-family ipv4 multicast
+# send-community
+# address-family ipv6 unicast
+#
+
+- name: Replace specified neighbor AFs with given configuration
+ cisco.nxos.nxos_bgp_neighbor_address_family: &replaced
+ config:
+ as_number: 65536
+ neighbors:
+ - neighbor_address: 192.0.2.32
+ address_family:
+ - afi: ipv4
+ safi: unicast
+ weight: 110
+ - afi: ipv6
+ safi: unicast
+ - neighbor_address: 192.0.2.33
+ address_family:
+ - afi: ipv4
+ safi: multicast
+ inherit:
+ template: BasePolicy
+ sequence: 200
+ vrfs:
+ - vrf: site-1
+ neighbors:
+ - neighbor_address: 203.0.113.1
+ address_family:
+ - afi: ipv4
+ safi: unicast
+ - neighbor_address: 203.0.113.2
+ address_family:
+ - afi: ipv6
+ safi: unicast
+ - afi: ipv4
+ safi: multicast
+ send_community:
+ set: True
+ state: replaced
+
+# Task output
+# -------------
+# before:
+# as_number: "65536"
+# neighbors:
+# - neighbor_address: 192.0.2.32
+# address_family:
+# - afi: ipv4
+# safi: unicast
+# maximum_prefix:
+# max_prefix_limit: 20
+# generate_warning_threshold: 75
+# weight: 100
+# prefix_list:
+# inbound: rmap1
+# outbound: rmap2
+# - afi: ipv6
+# safi: unicast
+# - neighbor_address: 192.0.2.33
+# address_family:
+# - afi: ipv4
+# safi: multicast
+# inherit:
+# template: BasePolicy
+# sequence: 200
+# vrfs:
+# - vrf: site-1
+# neighbors:
+# - neighbor_address: 203.0.113.1
+# address_family:
+# - afi: ipv4
+# safi: unicast
+# suppress_inactive: true
+# next_hop_self:
+# set: true
+# - neighbor_address: 203.0.113.2
+# address_family:
+# - afi: ipv4
+# safi: multicast
+# send_community:
+# set: True
+# - afi: ipv6
+# safi: unicast
+#
+# commands:
+# - router bgp 65536
+# - neighbor 192.0.2.32
+# - address-family ipv4 unicast
+# - no maximum-prefix 20 75
+# - weight 110
+# - no prefix-list rmap1 in
+# - no prefix-list rmap2 out
+# - vrf site-1
+# - neighbor 203.0.113.1
+# - address-family ipv4 unicast
+# - no suppress-inactive
+# - no next-hop-self
+#
+# after:
+# as_number: "65536"
+# neighbors:
+# - neighbor_address: 192.0.2.32
+# address_family:
+# - afi: ipv4
+# safi: unicast
+# weight: 110
+# - afi: ipv6
+# safi: unicast
+# - neighbor_address: 192.0.2.33
+# address_family:
+# - afi: ipv4
+# safi: multicast
+# inherit:
+# template: BasePolicy
+# sequence: 200
+# vrfs:
+# - vrf: site-1
+# neighbors:
+# - neighbor_address: 203.0.113.1
+# address_family:
+# - afi: ipv4
+# safi: unicast
+# - neighbor_address: 203.0.113.2
+# address_family:
+# - afi: ipv4
+# safi: multicast
+# send_community:
+# set: True
+# - afi: ipv6
+# safi: unicast
+
+# After state:
+# -------------
+# Nexus9000v# show running-config | section "^router bgp"
+# router bgp 65536
+# neighbor 192.0.2.32
+# address-family ipv4 unicast
+# weight 110
+# address-family ipv6 unicast
+# neighbor 192.0.2.33
+# address-family ipv4 multicast
+# inherit peer-policy BasePolicy 200
+# vrf site-1
+# neighbor 203.0.113.1
+# address-family ipv4 unicast
+# neighbor 203.0.113.2
+# address-family ipv4 multicast
+# send-community
+# address-family ipv6 unicast
+#
+
+# Using overridden
+
+# Before state:
+# -------------
+# Nexus9000v# show running-config | section "^router bgp"
+# router bgp 65536
+# neighbor 192.0.2.32
+# address-family ipv4 unicast
+# maximum-prefix 20 75
+# weight 100
+# prefix-list rmap1 in
+# prefix-list rmap2 out
+# address-family ipv6 unicast
+# neighbor 192.0.2.33
+# address-family ipv4 multicast
+# inherit peer-policy BasePolicy 200
+# vrf site-1
+# neighbor 203.0.113.1
+# address-family ipv4 unicast
+# suppress-inactive
+# next-hop-self
+# neighbor 203.0.113.2
+# address-family ipv4 multicast
+# send-community
+# address-family ipv6 unicast
+#
+
+- name: Override all BGP AF configuration with provided configuration
+ cisco.nxos.nxos_bgp_neighbor_address_family:
+ config:
+ as_number: 65536
+ neighbors:
+ - neighbor_address: 192.0.2.32
+ address_family:
+ - afi: ipv4
+ safi: unicast
+ vrfs:
+ - vrf: site-1
+ neighbors:
+ - neighbor_address: 203.0.113.1
+ address_family:
+ - afi: ipv4
+ safi: unicast
+ suppress_inactive: True
+ next_hop_self:
+ set: True
+ state: overridden
+
+# Task output
+# -------------
+# before:
+# as_number: "65536"
+# neighbors:
+# - neighbor_address: 192.0.2.32
+# address_family:
+# - afi: ipv4
+# safi: unicast
+# maximum_prefix:
+# max_prefix_limit: 20
+# generate_warning_threshold: 75
+# weight: 100
+# prefix_list:
+# inbound: rmap1
+# outbound: rmap2
+# - afi: ipv6
+# safi: unicast
+# - neighbor_address: 192.0.2.33
+# address_family:
+# - afi: ipv4
+# safi: multicast
+# inherit:
+# template: BasePolicy
+# sequence: 200
+# vrfs:
+# - vrf: site-1
+# neighbors:
+# - neighbor_address: 203.0.113.1
+# address_family:
+# - afi: ipv4
+# safi: unicast
+# suppress_inactive: true
+# next_hop_self:
+# set: true
+# - neighbor_address: 203.0.113.2
+# address_family:
+# - afi: ipv4
+# safi: multicast
+# send_community:
+# set: True
+# - afi: ipv6
+# safi: unicast
+#
+# commands:
+# - router bgp 65536
+# - neighbor 192.0.2.32
+# - address-family ipv4 unicast
+# - no maximum-prefix 20 75
+# - no weight 100
+# - no prefix-list rmap1 in
+# - no prefix-list rmap2 out
+# - no address-family ipv6 unicast
+# - neighbor 192.0.2.33
+# - no address-family ipv4 multicast
+# - vrf site-1
+# - neighbor 203.0.113.2
+# - no address-family ipv4 multicast
+# - no address-family ipv6 unicast
+#
+# after:
+# as_number: "65536"
+# neighbors:
+# - neighbor_address: 192.0.2.32
+# address_family:
+# - afi: ipv4
+# safi: unicast
+# vrfs:
+# - vrf: site-1
+# neighbors:
+# - neighbor_address: 203.0.113.1
+# address_family:
+# - afi: ipv4
+# safi: unicast
+# suppress_inactive: True
+# next_hop_self:
+# set: True
+
+# After state:
+# -------------
+# Nexus9000v# show running-config | section "^router bgp"
+# router bgp 65536
+# neighbor 192.0.2.32
+# address-family ipv4 unicast
+# vrf site-1
+# neighbor 203.0.113.1
+# address-family ipv4 unicast
+# suppress-inactive
+# next-hop-self
+
+# Using deleted to remove specified neighbor AFs
+
+# Before state:
+# -------------
+# Nexus9000v# show running-config | section "^router bgp"
+# router bgp 65536
+# neighbor 192.0.2.32
+# address-family ipv4 unicast
+# maximum-prefix 20 75
+# weight 100
+# prefix-list rmap1 in
+# prefix-list rmap2 out
+# address-family ipv6 unicast
+# neighbor 192.0.2.33
+# address-family ipv4 multicast
+# inherit peer-policy BasePolicy 200
+# vrf site-1
+# neighbor 203.0.113.1
+# address-family ipv4 unicast
+# suppress-inactive
+# next-hop-self
+# neighbor 203.0.113.2
+# address-family ipv4 multicast
+# send-community
+# address-family ipv6 unicast
+#
+
+- name: Delete BGP configs handled by this module
+ cisco.nxos.nxos_bgp_neighbor_address_family:
+ config:
+ as_number: 65536
+ neighbors:
+ - neighbor_address: 192.0.2.32
+ address_family:
+ - afi: ipv4
+ safi: unicast
+ vrfs:
+ - vrf: site-1
+ neighbors:
+ - neighbor_address: 203.0.113.2
+ address_family:
+ - afi: ipv6
+ safi: unicast
+ state: deleted
+
+# Task output
+# -------------
+# before:
+# as_number: "65536"
+# neighbors:
+# - neighbor_address: 192.0.2.32
+# address_family:
+# - afi: ipv4
+# safi: unicast
+# maximum_prefix:
+# max_prefix_limit: 20
+# generate_warning_threshold: 75
+# weight: 100
+# prefix_list:
+# inbound: rmap1
+# outbound: rmap2
+# - afi: ipv6
+# safi: unicast
+# - neighbor_address: 192.0.2.33
+# address_family:
+# - afi: ipv4
+# safi: multicast
+# inherit:
+# template: BasePolicy
+# sequence: 200
+# vrfs:
+# - vrf: site-1
+# neighbors:
+# - neighbor_address: 203.0.113.1
+# address_family:
+# - afi: ipv4
+# safi: unicast
+# suppress_inactive: true
+# next_hop_self:
+# set: true
+# - neighbor_address: 203.0.113.2
+# address_family:
+# - afi: ipv4
+# safi: multicast
+# send_community:
+# set: True
+# - afi: ipv6
+# safi: unicast
+#
+# commands:
+# - router bgp 65536
+# - neighbor 192.0.2.32
+# - no address-family ipv4 unicast
+# - vrf site-1
+# - neighbor 203.0.113.2
+# - no address-family ipv6 unicast
+#
+# after:
+# as_number: "65536"
+# neighbors:
+# - neighbor_address: 192.0.2.32
+# address_family:
+# - afi: ipv6
+# safi: unicast
+# - neighbor_address: 192.0.2.33
+# address_family:
+# - afi: ipv4
+# safi: multicast
+# inherit:
+# template: BasePolicy
+# sequence: 200
+# vrfs:
+# - vrf: site-1
+# neighbors:
+# - neighbor_address: 203.0.113.1
+# address_family:
+# - afi: ipv4
+# safi: unicast
+# suppress_inactive: true
+# next_hop_self:
+# set: true
+# - neighbor_address: 203.0.113.2
+# address_family:
+# - afi: ipv4
+# safi: multicast
+# send_community:
+# set: True
+#
+# After state:
+# -------------
+# Nexus9000v# show running-config | section "^router bgp"
+# router bgp 65536
+# neighbor 192.0.2.32
+# address-family ipv6 unicast
+# neighbor 192.0.2.33
+# address-family ipv4 multicast
+# inherit peer-policy BasePolicy 200
+# vrf site-1
+# neighbor 203.0.113.1
+# address-family ipv4 unicast
+# suppress-inactive
+# next-hop-self
+# neighbor 203.0.113.2
+# address-family ipv4 multicast
+# send-community
+#
+
+# Using deleted to remove all neighbor AFs
+
+# Before state:
+# -------------
+# Nexus9000v# show running-config | section "^router bgp"
+# router bgp 65536
+# neighbor 192.0.2.32
+# address-family ipv4 unicast
+# maximum-prefix 20 75
+# weight 100
+# prefix-list rmap1 in
+# prefix-list rmap2 out
+# address-family ipv6 unicast
+# neighbor 192.0.2.33
+# address-family ipv4 multicast
+# inherit peer-policy BasePolicy 200
+# vrf site-1
+# neighbor 203.0.113.1
+# address-family ipv4 unicast
+# suppress-inactive
+# next-hop-self
+# neighbor 203.0.113.2
+# address-family ipv4 multicast
+# send-community
+# address-family ipv6 unicast
+#
+
+- name: Delete all BGP neighbor AF configs handled by this module
+ cisco.nxos.nxos_bgp_neighbor_address_family:
+ state: deleted
+
+# Task output
+# -------------
+# before:
+# as_number: "65536"
+# neighbors:
+# - neighbor_address: 192.0.2.32
+# address_family:
+# - afi: ipv4
+# safi: unicast
+# maximum_prefix:
+# max_prefix_limit: 20
+# generate_warning_threshold: 75
+# weight: 100
+# prefix_list:
+# inbound: rmap1
+# outbound: rmap2
+# - afi: ipv6
+# safi: unicast
+# - neighbor_address: 192.0.2.33
+# address_family:
+# - afi: ipv4
+# safi: multicast
+# inherit:
+# template: BasePolicy
+# sequence: 200
+# vrfs:
+# - vrf: site-1
+# neighbors:
+# - neighbor_address: 203.0.113.1
+# address_family:
+# - afi: ipv4
+# safi: unicast
+# suppress_inactive: true
+# next_hop_self:
+# set: true
+# - neighbor_address: 203.0.113.2
+# address_family:
+# - afi: ipv4
+# safi: multicast
+# send_community:
+# set: True
+# - afi: ipv6
+# safi: unicast
+#
+# commands:
+# - router bgp 65536
+# - neighbor 192.0.2.32
+# - no address-family ipv4 unicast
+# - no address-family ipv6 unicast
+# - neighbor 192.0.2.33
+# - no address-family ipv4 multicast
+# - vrf site-1
+# - neighbor 203.0.113.1
+# - no address-family ipv4 unicast
+# - neighbor 203.0.113.2
+# - no address-family ipv6 unicast
+# - no address-family ipv4 multicast
+#
+# after:
+# as_number: "65536"
+#
+# After state:
+# -------------
+# Nexus9000v# show running-config | section "^router bgp"
+# router bgp 65536
+# neighbor 192.0.2.32
+# neighbor 192.0.2.33
+# vrf site-1
+# neighbor 203.0.113.1
+# neighbor 203.0.113.2
+#
+
+# Using rendered
+
+- name: Render platform specific configuration lines with state rendered (without connecting to the device)
+ cisco.nxos.nxos_bgp_neighbor_address_family:
+ config:
+ as_number: 65536
+ neighbors:
+ - neighbor_address: 192.0.2.32
+ address_family:
+ - afi: ipv4
+ safi: unicast
+ maximum_prefix:
+ max_prefix_limit: 20
+ generate_warning_threshold: 75
+ weight: 100
+ prefix_list:
+ inbound: rmap1
+ outbound: rmap2
+ - afi: ipv6
+ safi: unicast
+ - neighbor_address: 192.0.2.33
+ address_family:
+ - afi: ipv4
+ safi: multicast
+ inherit:
+ template: BasePolicy
+ sequence: 200
+ vrfs:
+ - vrf: site-1
+ neighbors:
+ - neighbor_address: 203.0.113.1
+ address_family:
+ - afi: ipv4
+ safi: unicast
+ suppress_inactive: True
+ next_hop_self:
+ set: True
+ - neighbor_address: 203.0.113.2
+ address_family:
+ - afi: ipv6
+ safi: unicast
+ - afi: ipv4
+ safi: multicast
+ send_community:
+ set: True
+ state: rendered
+
+# Task Output (redacted)
+# -----------------------
+# rendered:
+# - router bgp 65536
+# - neighbor 192.0.2.32
+# - address-family ipv4 unicast
+# - maximum-prefix 20 75
+# - weight 100
+# - prefix-list rmap1 in
+# - prefix-list rmap2 out
+# - address-family ipv6 unicast
+# - neighbor 192.0.2.33
+# - address-family ipv4 multicast
+# - inherit peer-policy BasePolicy 200
+# - vrf site-1
+# - neighbor 203.0.113.1
+# - address-family ipv4 unicast
+# - suppress-inactive
+# - next-hop-self
+# - neighbor 203.0.113.2
+# - address-family ipv6 unicast
+# - address-family ipv4 multicast
+# - send-community
+
+# Using parsed
+
+# parsed.cfg
+# ------------
+# router bgp 65536
+# neighbor 192.0.2.32
+# address-family ipv4 unicast
+# maximum-prefix 20 75
+# weight 100
+# prefix-list rmap1 in
+# prefix-list rmap2 out
+# address-family ipv6 unicast
+# neighbor 192.0.2.33
+# address-family ipv4 multicast
+# inherit peer-policy BasePolicy 200
+# vrf site-1
+# neighbor 203.0.113.1
+# address-family ipv4 unicast
+# suppress-inactive
+# next-hop-self
+# neighbor 203.0.113.2
+# address-family ipv4 multicast
+# send-community
+# address-family ipv6 unicast
+
+- name: Parse externally provided BGP neighbor AF config
+ register: result
+ cisco.nxos.nxos_bgp_neighbor_address_family:
+ running_config: "{{ lookup('file', 'parsed.cfg') }}"
+ state: parsed
+
+# Task output (redacted)
+# -----------------------
+# parsed:
+# as_number: "65536"
+# neighbors:
+# - neighbor_address: 192.0.2.32
+# address_family:
+# - afi: ipv4
+# safi: unicast
+# maximum_prefix:
+# max_prefix_limit: 20
+# generate_warning_threshold: 75
+# weight: 100
+# prefix_list:
+# inbound: rmap1
+# outbound: rmap2
+# - afi: ipv6
+# safi: unicast
+# - neighbor_address: 192.0.2.33
+# address_family:
+# - afi: ipv4
+# safi: multicast
+# inherit:
+# template: BasePolicy
+# sequence: 200
+# vrfs:
+# - vrf: site-1
+# neighbors:
+# - neighbor_address: 203.0.113.1
+# address_family:
+# - afi: ipv4
+# safi: unicast
+# suppress_inactive: true
+# next_hop_self:
+# set: true
+# - neighbor_address: 203.0.113.2
+# address_family:
+# - afi: ipv4
+# safi: multicast
+# send_community:
+# set: True
+# - afi: ipv6
+# safi: unicast
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.bgp_neighbor_address_family.bgp_neighbor_address_family import (
+ Bgp_neighbor_address_familyArgs,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.config.bgp_neighbor_address_family.bgp_neighbor_address_family import (
+ Bgp_neighbor_address_family,
+)
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ module = AnsibleModule(
+ argument_spec=Bgp_neighbor_address_familyArgs.argument_spec,
+ mutually_exclusive=[["config", "running_config"]],
+ required_if=[
+ ["state", "merged", ["config"]],
+ ["state", "replaced", ["config"]],
+ ["state", "overridden", ["config"]],
+ ["state", "rendered", ["config"]],
+ ["state", "parsed", ["running_config"]],
+ ],
+ supports_check_mode=True,
+ )
+
+ result = Bgp_neighbor_address_family(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_bgp_neighbor_af.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_bgp_neighbor_af.py
new file mode 100644
index 00000000..cc5624ee
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_bgp_neighbor_af.py
@@ -0,0 +1,781 @@
+#!/usr/bin/python
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_bgp_neighbor_af
+extends_documentation_fragment:
+- cisco.nxos.nxos
+short_description: (deprecated, removed after 2023-02-24) Manages BGP address-family's neighbors configuration.
+description:
+- Manages BGP address-family's neighbors configurations on NX-OS switches.
+version_added: 1.0.0
+author: Gabriele Gerbino (@GGabriele)
+deprecated:
+ alternative: nxos_bgp_neighbor_address_family
+ why: Updated module released with more functionality.
+ removed_at_date: '2023-02-24'
+notes:
+- Tested against NXOSv 7.3.(0)D1(1) on VIRL
+- Unsupported for Cisco MDS
+- C(state=absent) removes the whole BGP address-family's neighbor configuration.
+- Default, when supported, removes properties
+- In order to default maximum-prefix configuration, only C(max_prefix_limit=default)
+ is needed.
+options:
+ asn:
+ description:
+ - BGP autonomous system number. Valid values are String, Integer in ASPLAIN or
+ ASDOT notation.
+ required: true
+ type: str
+ vrf:
+ description:
+ - Name of the VRF. The name 'default' is a valid VRF representing the global bgp.
+ default: default
+ type: str
+ neighbor:
+ description:
+ - Neighbor Identifier. Valid values are string. Neighbors may use IPv4 or IPv6
+ notation, with or without prefix length.
+ required: true
+ type: str
+ afi:
+ description:
+ - Address Family Identifier.
+ required: true
+ choices:
+ - ipv4
+ - ipv6
+ - vpnv4
+ - vpnv6
+ - l2vpn
+ type: str
+ safi:
+ description:
+ - Sub Address Family Identifier.
+ required: true
+ choices:
+ - unicast
+ - multicast
+ - evpn
+ type: str
+ additional_paths_receive:
+ description:
+ - Valid values are enable for basic command enablement; disable for disabling
+ the command at the neighbor af level (it adds the disable keyword to the basic
+ command); and inherit to remove the command at this level (the command value
+ is inherited from a higher BGP layer).
+ choices:
+ - enable
+ - disable
+ - inherit
+ type: str
+ additional_paths_send:
+ description:
+ - Valid values are enable for basic command enablement; disable for disabling
+ the command at the neighbor af level (it adds the disable keyword to the basic
+ command); and inherit to remove the command at this level (the command value
+ is inherited from a higher BGP layer).
+ choices:
+ - enable
+ - disable
+ - inherit
+ type: str
+ advertise_map_exist:
+ description:
+ - Conditional route advertisement. This property requires two route maps, an advertise-map
+ and an exist-map. Valid values are an array specifying both the advertise-map
+ name and the exist-map name, or simply 'default' e.g. ['my_advertise_map', 'my_exist_map'].
+ This command is mutually exclusive with the advertise_map_non_exist property.
+ type: list
+ elements: str
+ advertise_map_non_exist:
+ description:
+ - Conditional route advertisement. This property requires two route maps, an advertise-map
+ and an exist-map. Valid values are an array specifying both the advertise-map
+ name and the non-exist-map name, or simply 'default' e.g. ['my_advertise_map',
+ 'my_non_exist_map']. This command is mutually exclusive with the advertise_map_exist
+ property.
+ type: list
+ elements: str
+ allowas_in:
+ description:
+ - Activate allowas-in property
+ type: bool
+ allowas_in_max:
+ description:
+ - Max-occurrences value for allowas_in. Valid values are an integer value or 'default'.
+ This is mutually exclusive with allowas_in.
+ type: str
+ as_override:
+ description:
+ - Activate the as-override feature.
+ type: bool
+ default_originate:
+ description:
+ - Activate the default-originate feature.
+ type: bool
+ default_originate_route_map:
+ description:
+ - Route-map for the default_originate property. Valid values are a string defining
+ a route-map name, or 'default'. This is mutually exclusive with default_originate.
+ type: str
+ disable_peer_as_check:
+ description:
+ - Disable checking of peer AS-number while advertising
+ type: bool
+ filter_list_in:
+ description:
+ - Valid values are a string defining a filter-list name, or 'default'.
+ type: str
+ filter_list_out:
+ description:
+ - Valid values are a string defining a filter-list name, or 'default'.
+ type: str
+ max_prefix_limit:
+ description:
+ - maximum-prefix limit value. Valid values are an integer value or 'default'.
+ type: str
+ max_prefix_interval:
+ description:
+ - Optional restart interval. Valid values are an integer. Requires max_prefix_limit.
+ May not be combined with max_prefix_warning.
+ type: str
+ max_prefix_threshold:
+ description:
+ - Optional threshold percentage at which to generate a warning. Valid values are
+ an integer value. Requires max_prefix_limit.
+ type: str
+ max_prefix_warning:
+ description:
+ - Optional warning-only keyword. Requires max_prefix_limit. May not be combined
+ with max_prefix_interval.
+ type: bool
+ next_hop_self:
+ description:
+ - Activate the next-hop-self feature.
+ type: bool
+ next_hop_third_party:
+ description:
+ - Activate the next-hop-third-party feature.
+ type: bool
+ prefix_list_in:
+ description:
+ - Valid values are a string defining a prefix-list name, or 'default'.
+ type: str
+ prefix_list_out:
+ description:
+ - Valid values are a string defining a prefix-list name, or 'default'.
+ type: str
+ route_map_in:
+ description:
+ - Valid values are a string defining a route-map name, or 'default'.
+ type: str
+ route_map_out:
+ description:
+ - Valid values are a string defining a route-map name, or 'default'.
+ type: str
+ route_reflector_client:
+ description:
+ - Router reflector client.
+ type: bool
+ send_community:
+ description:
+ - send-community attribute.
+ choices:
+ - none
+ - both
+ - extended
+ - standard
+ - default
+ type: str
+ soft_reconfiguration_in:
+ description:
+ - Valid values are 'enable' for basic command enablement; 'always' to add the
+ always keyword to the basic command; and 'inherit' to remove the command at
+ this level (the command value is inherited from a higher BGP layer).
+ choices:
+ - enable
+ - always
+ - inherit
+ type: str
+ soo:
+ description:
+ - Site-of-origin. Valid values are a string defining a VPN extcommunity or 'default'.
+ type: str
+ suppress_inactive:
+ description:
+ - suppress-inactive feature.
+ type: bool
+ unsuppress_map:
+ description:
+ - unsuppress-map. Valid values are a string defining a route-map name or 'default'.
+ type: str
+ weight:
+ description:
+ - Weight value. Valid values are an integer value or 'default'.
+ type: str
+ state:
+ description:
+ - Determines whether the config should be present or not on the device.
+ default: present
+ choices:
+ - present
+ - absent
+ type: str
+ rewrite_evpn_rt_asn:
+ description:
+ - Auto generate route targets for EBGP neighbor.
+ type: bool
+ version_added: 1.1.0
+"""
+EXAMPLES = """
+- name: configure RR client
+ cisco.nxos.nxos_bgp_neighbor_af:
+ asn: 65535
+ neighbor: 192.0.2.3
+ afi: ipv4
+ safi: unicast
+ route_reflector_client: true
+ state: present
+ rewrite_evpn_rt_asn: true
+"""
+
+RETURN = """
+commands:
+ description: commands sent to the device
+ returned: always
+ type: list
+ sample: ["router bgp 65535", "neighbor 192.0.2.3",
+ "address-family ipv4 unicast", "route-reflector-client",
+ "rewrite-evpn-rt-asn"]
+"""
+
+import re
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import (
+ CustomNetworkConfig,
+)
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ get_config,
+ load_config,
+)
+
+
+BOOL_PARAMS = [
+ "allowas_in",
+ "as_override",
+ "default_originate",
+ "disable_peer_as_check",
+ "next_hop_self",
+ "next_hop_third_party",
+ "route_reflector_client",
+ "suppress_inactive",
+ "rewrite_evpn_rt_asn",
+]
+PARAM_TO_COMMAND_KEYMAP = {
+ "afi": "address-family",
+ "asn": "router bgp",
+ "neighbor": "neighbor",
+ "additional_paths_receive": "capability additional-paths receive",
+ "additional_paths_send": "capability additional-paths send",
+ "advertise_map_exist": "advertise-map exist-map",
+ "advertise_map_non_exist": "advertise-map non-exist-map",
+ "allowas_in": "allowas-in",
+ "allowas_in_max": "allowas-in",
+ "as_override": "as-override",
+ "default_originate": "default-originate",
+ "default_originate_route_map": "default-originate route-map",
+ "disable_peer_as_check": "disable-peer-as-check",
+ "filter_list_in": "filter-list in",
+ "filter_list_out": "filter-list out",
+ "max_prefix_limit": "maximum-prefix",
+ "max_prefix_interval": "maximum-prefix interval",
+ "max_prefix_threshold": "maximum-prefix threshold",
+ "max_prefix_warning": "maximum-prefix warning",
+ "next_hop_self": "next-hop-self",
+ "next_hop_third_party": "next-hop-third-party",
+ "prefix_list_in": "prefix-list in",
+ "prefix_list_out": "prefix-list out",
+ "route_map_in": "route-map in",
+ "route_map_out": "route-map out",
+ "route_reflector_client": "route-reflector-client",
+ "safi": "address-family",
+ "send_community": "send-community",
+ "soft_reconfiguration_in": "soft-reconfiguration inbound",
+ "soo": "soo",
+ "suppress_inactive": "suppress-inactive",
+ "unsuppress_map": "unsuppress-map",
+ "weight": "weight",
+ "vrf": "vrf",
+ "rewrite_evpn_rt_asn": "rewrite-evpn-rt-asn",
+}
+
+
+def get_value(arg, config, module):
+ custom = [
+ "additional_paths_send",
+ "additional_paths_receive",
+ "max_prefix_limit",
+ "max_prefix_interval",
+ "max_prefix_threshold",
+ "max_prefix_warning",
+ "send_community",
+ "soft_reconfiguration_in",
+ ]
+ command = PARAM_TO_COMMAND_KEYMAP[arg]
+ has_command = re.search(r"^\s+{0}\s*".format(command), config, re.M)
+ has_command_val = re.search(r"(?:{0}\s)(?P<value>.*)$".format(command), config, re.M)
+ value = ""
+
+ if arg in custom:
+ value = get_custom_value(arg, config, module)
+
+ elif arg == "next_hop_third_party":
+ has_no_command = re.search(r"^\s+no\s+{0}\s*$".format(command), config, re.M)
+ value = False
+ if not has_no_command:
+ value = True
+
+ elif arg in BOOL_PARAMS:
+ value = False
+ if has_command:
+ value = True
+
+ elif command.startswith("advertise-map"):
+ value = []
+ has_adv_map = re.search(
+ r"{0}\s(?P<value1>.*)\s{1}\s(?P<value2>.*)$".format(*command.split()),
+ config,
+ re.M,
+ )
+ if has_adv_map:
+ value = list(has_adv_map.groups())
+
+ elif command.split()[0] in ["filter-list", "prefix-list", "route-map"]:
+ has_cmd_direction_val = re.search(
+ r"{0}\s(?P<value>.*)\s{1}$".format(*command.split()),
+ config,
+ re.M,
+ )
+ if has_cmd_direction_val:
+ value = has_cmd_direction_val.group("value")
+
+ elif has_command_val:
+ value = has_command_val.group("value")
+
+ return value
+
+
+def get_custom_value(arg, config, module):
+ command = PARAM_TO_COMMAND_KEYMAP.get(arg)
+ splitted_config = config.splitlines()
+ value = ""
+
+ if arg.startswith("additional_paths"):
+ value = "inherit"
+ for line in splitted_config:
+ if command in line:
+ if "disable" in line:
+ value = "disable"
+ else:
+ value = "enable"
+ elif arg.startswith("max_prefix"):
+ for line in splitted_config:
+ if "maximum-prefix" in line:
+ splitted_line = line.split()
+ if arg == "max_prefix_limit":
+ value = splitted_line[1]
+ elif arg == "max_prefix_interval" and "restart" in line:
+ value = splitted_line[-1]
+ elif arg == "max_prefix_threshold" and len(splitted_line) > 2:
+ try:
+ int(splitted_line[2])
+ value = splitted_line[2]
+ except ValueError:
+ value = ""
+ elif arg == "max_prefix_warning":
+ value = "warning-only" in line
+ elif arg == "soft_reconfiguration_in":
+ value = "inherit"
+ for line in splitted_config:
+ if command in line:
+ if "always" in line:
+ value = "always"
+ else:
+ value = "enable"
+
+ elif arg == "send_community":
+ value = "none"
+ for line in splitted_config:
+ if command in line:
+ if "extended" in line:
+ if value == "standard":
+ value = "both"
+ else:
+ value = "extended"
+ elif "both" in line:
+ value = "both"
+ else:
+ value = "standard"
+
+ return value
+
+
+def get_existing(module, args, warnings):
+ existing = {}
+ netcfg = CustomNetworkConfig(indent=2, contents=get_config(module))
+
+ asn_regex = re.compile(r".*router\sbgp\s(?P<existing_asn>\d+(\.\d+)?).*", re.S)
+ match_asn = asn_regex.match(str(netcfg))
+
+ if match_asn:
+ existing_asn = match_asn.group("existing_asn")
+ parents = ["router bgp {0}".format(existing_asn)]
+
+ if module.params["vrf"] != "default":
+ parents.append("vrf {0}".format(module.params["vrf"]))
+
+ parents.append("neighbor {0}".format(module.params["neighbor"]))
+ parents.append("address-family {0} {1}".format(module.params["afi"], module.params["safi"]))
+ config = netcfg.get_section(parents)
+
+ if config:
+ for arg in args:
+ if arg not in ["asn", "vrf", "neighbor", "afi", "safi"]:
+ existing[arg] = get_value(arg, config, module)
+
+ existing["asn"] = existing_asn
+ existing["neighbor"] = module.params["neighbor"]
+ existing["vrf"] = module.params["vrf"]
+ existing["afi"] = module.params["afi"]
+ existing["safi"] = module.params["safi"]
+ else:
+ warnings.append("The BGP process didn't exist but the task just created it.")
+
+ return existing
+
+
+def apply_key_map(key_map, table):
+ new_dict = {}
+ for key in table:
+ new_key = key_map.get(key)
+ if new_key:
+ new_dict[new_key] = table.get(key)
+
+ return new_dict
+
+
+def get_default_command(key, value, existing_commands):
+ command = ""
+ if existing_commands.get(key):
+ existing_value = existing_commands.get(key)
+ if value == "inherit":
+ if existing_value != "inherit":
+ command = "no {0}".format(key)
+ else:
+ if key == "advertise-map exist-map":
+ command = "no advertise-map {0} exist-map {1}".format(
+ existing_value[0],
+ existing_value[1],
+ )
+ elif key == "advertise-map non-exist-map":
+ command = "no advertise-map {0} non-exist-map {1}".format(
+ existing_value[0],
+ existing_value[1],
+ )
+ elif key == "filter-list in":
+ command = "no filter-list {0} in".format(existing_value)
+ elif key == "filter-list out":
+ command = "no filter-list {0} out".format(existing_value)
+ elif key == "prefix-list in":
+ command = "no prefix-list {0} in".format(existing_value)
+ elif key == "prefix-list out":
+ command = "no prefix-list {0} out".format(existing_value)
+ elif key == "route-map in":
+ command = "no route-map {0} in".format(existing_value)
+ elif key == "route-map out":
+ command = "no route-map {0} out".format(existing_value)
+ elif key.startswith("maximum-prefix"):
+ command = "no maximum-prefix"
+ elif key == "allowas-in max":
+ command = ["no allowas-in {0}".format(existing_value)]
+ command.append("allowas-in")
+ else:
+ command = "no {0} {1}".format(key, existing_value)
+ else:
+ if key.replace(" ", "_").replace("-", "_") in BOOL_PARAMS:
+ command = "no {0}".format(key)
+ return command
+
+
+def fix_proposed(module, existing, proposed):
+ allowas_in = proposed.get("allowas_in")
+ allowas_in_max = proposed.get("allowas_in_max")
+
+ if allowas_in_max and not allowas_in:
+ proposed.pop("allowas_in_max")
+ elif allowas_in and allowas_in_max:
+ proposed.pop("allowas_in")
+
+ if existing.get("send_community") == "none" and proposed.get("send_community") == "default":
+ proposed.pop("send_community")
+ return proposed
+
+
+def state_present(module, existing, proposed, candidate):
+ commands = list()
+ proposed = fix_proposed(module, existing, proposed)
+
+ proposed_commands = apply_key_map(PARAM_TO_COMMAND_KEYMAP, proposed)
+ existing_commands = apply_key_map(PARAM_TO_COMMAND_KEYMAP, existing)
+ for key, value in proposed_commands.items():
+ if value in ["inherit", "default"]:
+ command = get_default_command(key, value, existing_commands)
+
+ if isinstance(command, str):
+ if command and command not in commands:
+ commands.append(command)
+ elif isinstance(command, list):
+ for cmd in command:
+ if cmd not in commands:
+ commands.append(cmd)
+
+ elif key.startswith("maximum-prefix"):
+ if module.params["max_prefix_limit"] != "default":
+ command = "maximum-prefix {0}".format(module.params["max_prefix_limit"])
+ if module.params["max_prefix_threshold"]:
+ command += " {0}".format(module.params["max_prefix_threshold"])
+ if module.params["max_prefix_interval"]:
+ command += " restart {0}".format(module.params["max_prefix_interval"])
+ elif module.params["max_prefix_warning"]:
+ command += " warning-only"
+ commands.append(command)
+
+ elif value is True:
+ commands.append(key)
+ elif value is False:
+ commands.append("no {0}".format(key))
+ elif key == "address-family":
+ commands.append(
+ "address-family {0} {1}".format(module.params["afi"], module.params["safi"]),
+ )
+ elif key.startswith("capability additional-paths"):
+ command = key
+ if value == "disable":
+ command += " disable"
+ commands.append(command)
+ elif key.startswith("advertise-map"):
+ direction = key.split()[1]
+ commands.append("advertise-map {1} {0} {2}".format(direction, *value))
+ elif key.split()[0] in ["filter-list", "prefix-list", "route-map"]:
+ commands.append("{1} {0} {2}".format(value, *key.split()))
+
+ elif key == "soft-reconfiguration inbound":
+ command = ""
+ if value == "enable":
+ command = key
+ elif value == "always":
+ command = "{0} {1}".format(key, value)
+ commands.append(command)
+ elif key == "send-community":
+ command = key
+ if value in ["standard", "extended"]:
+ commands.append("no " + key + " both")
+ command += " {0}".format(value)
+ commands.append(command)
+ else:
+ command = "{0} {1}".format(key, value)
+ commands.append(command)
+
+ if commands:
+ parents = ["router bgp {0}".format(module.params["asn"])]
+ if module.params["vrf"] != "default":
+ parents.append("vrf {0}".format(module.params["vrf"]))
+
+ parents.append("neighbor {0}".format(module.params["neighbor"]))
+
+ af_command = "address-family {0} {1}".format(module.params["afi"], module.params["safi"])
+ parents.append(af_command)
+ if af_command in commands:
+ commands.remove(af_command)
+ candidate.add(commands, parents=parents)
+
+
+def state_absent(module, existing, candidate):
+ commands = []
+ parents = ["router bgp {0}".format(module.params["asn"])]
+ if module.params["vrf"] != "default":
+ parents.append("vrf {0}".format(module.params["vrf"]))
+
+ parents.append("neighbor {0}".format(module.params["neighbor"]))
+ commands.append("no address-family {0} {1}".format(module.params["afi"], module.params["safi"]))
+ candidate.add(commands, parents=parents)
+
+
+def main():
+ argument_spec = dict(
+ asn=dict(required=True, type="str"),
+ vrf=dict(required=False, type="str", default="default"),
+ neighbor=dict(required=True, type="str"),
+ afi=dict(
+ required=True,
+ type="str",
+ choices=["ipv4", "ipv6", "vpnv4", "vpnv6", "l2vpn"],
+ ),
+ safi=dict(required=True, type="str", choices=["unicast", "multicast", "evpn"]),
+ additional_paths_receive=dict(
+ required=False,
+ type="str",
+ choices=["enable", "disable", "inherit"],
+ ),
+ additional_paths_send=dict(
+ required=False,
+ type="str",
+ choices=["enable", "disable", "inherit"],
+ ),
+ advertise_map_exist=dict(required=False, type="list", elements="str"),
+ advertise_map_non_exist=dict(required=False, type="list", elements="str"),
+ allowas_in=dict(required=False, type="bool"),
+ allowas_in_max=dict(required=False, type="str"),
+ as_override=dict(required=False, type="bool"),
+ default_originate=dict(required=False, type="bool"),
+ default_originate_route_map=dict(required=False, type="str"),
+ disable_peer_as_check=dict(required=False, type="bool"),
+ filter_list_in=dict(required=False, type="str"),
+ filter_list_out=dict(required=False, type="str"),
+ max_prefix_limit=dict(required=False, type="str"),
+ max_prefix_interval=dict(required=False, type="str"),
+ max_prefix_threshold=dict(required=False, type="str"),
+ max_prefix_warning=dict(required=False, type="bool"),
+ next_hop_self=dict(required=False, type="bool"),
+ next_hop_third_party=dict(required=False, type="bool"),
+ prefix_list_in=dict(required=False, type="str"),
+ prefix_list_out=dict(required=False, type="str"),
+ route_map_in=dict(required=False, type="str"),
+ route_map_out=dict(required=False, type="str"),
+ route_reflector_client=dict(required=False, type="bool"),
+ send_community=dict(
+ required=False,
+ choices=["none", "both", "extended", "standard", "default"],
+ ),
+ soft_reconfiguration_in=dict(
+ required=False,
+ type="str",
+ choices=["enable", "always", "inherit"],
+ ),
+ soo=dict(required=False, type="str"),
+ suppress_inactive=dict(required=False, type="bool"),
+ unsuppress_map=dict(required=False, type="str"),
+ weight=dict(required=False, type="str"),
+ state=dict(choices=["present", "absent"], default="present", required=False),
+ rewrite_evpn_rt_asn=dict(required=False, type="bool"),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ mutually_exclusive=[
+ ["advertise_map_exist", "advertise_map_non_exist"],
+ ["max_prefix_interval", "max_prefix_warning"],
+ ["default_originate", "default_originate_route_map"],
+ ["allowas_in", "allowas_in_max"],
+ ],
+ supports_check_mode=True,
+ )
+
+ warnings = list()
+ result = dict(changed=False, warnings=warnings)
+
+ state = module.params["state"]
+ for key in [
+ "max_prefix_interval",
+ "max_prefix_warning",
+ "max_prefix_threshold",
+ ]:
+ if module.params[key] and not module.params["max_prefix_limit"]:
+ module.fail_json(msg="max_prefix_limit is required when using %s" % key)
+ if module.params["vrf"] == "default" and module.params["soo"]:
+ module.fail_json(msg="SOO is only allowed in non-default VRF")
+
+ args = PARAM_TO_COMMAND_KEYMAP.keys()
+ existing = get_existing(module, args, warnings)
+
+ if existing.get("asn") and state == "present":
+ if existing.get("asn") != module.params["asn"]:
+ module.fail_json(
+ msg="Another BGP ASN already exists.",
+ proposed_asn=module.params["asn"],
+ existing_asn=existing.get("asn"),
+ )
+
+ for param in ["advertise_map_exist", "advertise_map_non_exist"]:
+ if module.params[param] == ["default"]:
+ module.params[param] = "default"
+
+ proposed_args = dict((k, v) for k, v in module.params.items() if v is not None and k in args)
+
+ proposed = {}
+ for key, value in proposed_args.items():
+ if key not in ["asn", "vrf", "neighbor"]:
+ if not isinstance(value, list):
+ if str(value).lower() == "true":
+ value = True
+ elif str(value).lower() == "false":
+ value = False
+ elif str(value).lower() == "default":
+ if key in BOOL_PARAMS:
+ value = False
+ else:
+ value = "default"
+ elif key == "send_community" and str(value).lower() == "none":
+ value = "default"
+ if existing.get(key) != value:
+ proposed[key] = value
+
+ candidate = CustomNetworkConfig(indent=3)
+ if state == "present":
+ state_present(module, existing, proposed, candidate)
+ elif state == "absent" and existing:
+ state_absent(module, existing, candidate)
+
+ if candidate:
+ candidate = candidate.items_text()
+ if not module.check_mode:
+ responses = load_config(module, candidate)
+ if responses:
+ for resp in responses:
+ if resp:
+ if resp.endswith("is valid only for EBGP peers"):
+ module.fail_json(msg=resp)
+ result["changed"] = True
+ result["commands"] = candidate
+ else:
+ result["commands"] = []
+
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_command.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_command.py
new file mode 100644
index 00000000..7febbf8a
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_command.py
@@ -0,0 +1,231 @@
+#!/usr/bin/python
+# Copyright: Ansible Project
+# 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
+
+
+DOCUMENTATION = """
+module: nxos_command
+extends_documentation_fragment:
+- cisco.nxos.nxos
+author: Peter Sprygada (@privateip)
+notes:
+- Limited Support for Cisco MDS
+short_description: Run arbitrary command on Cisco NXOS devices
+description:
+- Sends an arbitrary command to an NXOS node and returns the results read from the
+ device. This module includes an argument that will cause the module to wait for
+ a specific condition before returning or timing out if the condition is not met.
+version_added: 1.0.0
+options:
+ commands:
+ description:
+ - The commands to send to the remote NXOS device. The resulting output from the
+ command is returned. If the I(wait_for) argument is provided, the module is
+ not returned until the condition is satisfied or the number of retires as expired.
+ - The I(commands) argument also accepts an alternative form that allows for complex
+ values that specify the command to run and the output format to return. This
+ can be done on a command by command basis. The complex argument supports the
+ keywords C(command) and C(output) where C(command) is the command to run and
+ C(output) is one of 'text' or 'json'.
+ - If a command sent to the device requires answering a prompt, it is possible to pass
+ a dict containing command, answer and prompt. Common answers are 'y' or "\\r"
+ (carriage return, must be double quotes). See examples.
+ required: true
+ type: list
+ elements: raw
+ wait_for:
+ description:
+ - Specifies what to evaluate from the output of the command and what conditionals
+ to apply. This argument will cause the task to wait for a particular conditional
+ to be true before moving forward. If the conditional is not true by the configured
+ retries, the task fails. See examples.
+ aliases:
+ - waitfor
+ type: list
+ elements: str
+ match:
+ description:
+ - The I(match) argument is used in conjunction with the I(wait_for) argument to
+ specify the match policy. Valid values are C(all) or C(any). If the value
+ is set to C(all) then all conditionals in the I(wait_for) must be satisfied. If
+ the value is set to C(any) then only one of the values must be satisfied.
+ default: all
+ choices: ['any', 'all']
+ type: str
+ retries:
+ description:
+ - Specifies the number of retries a command should by tried before it is considered
+ failed. The command is run on the target device every retry and evaluated against
+ the I(wait_for) conditionals.
+ - The commands are run once when I(retries) is set to C(0).
+ default: 9
+ type: int
+ interval:
+ description:
+ - Configures the interval in seconds to wait between retries of the command. If
+ the command does not pass the specified conditional, the interval indicates
+ how to long to wait before trying the command again.
+ default: 1
+ type: int
+"""
+
+EXAMPLES = """
+- name: run show version on remote devices
+ cisco.nxos.nxos_command:
+ commands: show version
+
+- name: run show version and check to see if output contains Cisco
+ cisco.nxos.nxos_command:
+ commands: show version
+ wait_for: result[0] contains Cisco
+
+- name: run multiple commands on remote nodes
+ cisco.nxos.nxos_command:
+ commands:
+ - show version
+ - show interfaces
+
+- name: run multiple commands and evaluate the output
+ cisco.nxos.nxos_command:
+ commands:
+ - show version
+ - show interfaces
+ wait_for:
+ - result[0] contains Cisco
+ - result[1] contains loopback0
+
+- name: run commands and specify the output format
+ cisco.nxos.nxos_command:
+ commands:
+ - command: show version
+ output: json
+
+- name: run commands that require answering a prompt
+ cisco.nxos.nxos_command:
+ commands:
+ - configure terminal
+ - command: no feature npv
+ prompt: Do you want to continue
+ answer: y
+
+"""
+
+RETURN = """
+stdout:
+ description: The set of responses from the commands
+ returned: always apart from low level errors (such as action plugin)
+ type: list
+ sample: ['...', '...']
+stdout_lines:
+ description: The value of stdout split into a list
+ returned: always apart from low level errors (such as action plugin)
+ type: list
+ sample: [['...', '...'], ['...'], ['...']]
+failed_conditions:
+ description: The list of conditionals that have failed
+ returned: failed
+ type: list
+ sample: ['...', '...']
+"""
+import time
+
+from ansible.module_utils._text import to_text
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.parsing import (
+ Conditional,
+ FailedConditionalError,
+)
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import (
+ to_lines,
+ transform_commands,
+)
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import run_commands
+
+
+def parse_commands(module, warnings):
+ commands = transform_commands(module)
+
+ if module.check_mode:
+ for item in list(commands):
+ if not item["command"].startswith("show"):
+ warnings.append(
+ "Only show commands are supported when using check mode, not "
+ "executing %s" % item["command"],
+ )
+ commands.remove(item)
+
+ return commands
+
+
+def to_cli(obj):
+ cmd = obj["command"]
+ if obj.get("output") == "json":
+ cmd += " | json"
+ return cmd
+
+
+def main():
+ """entry point for module execution"""
+ argument_spec = dict(
+ # { command: <str>, output: <str>, prompt: <str>, response: <str> }
+ commands=dict(type="list", required=True, elements="raw"),
+ wait_for=dict(type="list", aliases=["waitfor"], elements="str"),
+ match=dict(default="all", choices=["any", "all"]),
+ retries=dict(default=9, type="int"),
+ interval=dict(default=1, type="int"),
+ )
+
+ module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)
+
+ warnings = list()
+ result = {"changed": False, "warnings": warnings}
+ commands = parse_commands(module, warnings)
+ wait_for = module.params["wait_for"] or list()
+ conditionals = []
+
+ try:
+ conditionals = [Conditional(c) for c in wait_for]
+ except AttributeError as exc:
+ module.fail_json(msg=to_text(exc))
+
+ retries = module.params["retries"]
+ interval = module.params["interval"]
+ match = module.params["match"]
+
+ while retries >= 0:
+ responses = run_commands(module, commands)
+
+ for item in list(conditionals):
+ try:
+ if item(responses):
+ if match == "any":
+ conditionals = list()
+ break
+ conditionals.remove(item)
+ except FailedConditionalError as exc:
+ module.fail_json(msg=to_text(exc))
+
+ if not conditionals:
+ break
+
+ time.sleep(interval)
+ retries -= 1
+
+ if conditionals:
+ failed_conditions = [item.raw for item in conditionals]
+ msg = "One or more conditional statements have not been satisfied"
+ module.fail_json(msg=msg, failed_conditions=failed_conditions)
+
+ result.update({"stdout": responses, "stdout_lines": list(to_lines(responses))})
+
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_config.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_config.py
new file mode 100644
index 00000000..45789124
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_config.py
@@ -0,0 +1,579 @@
+#!/usr/bin/python
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_config
+extends_documentation_fragment:
+- cisco.nxos.nxos
+author: Peter Sprygada (@privateip)
+short_description: Manage Cisco NXOS configuration sections
+description:
+- Cisco NXOS configurations use a simple block indent file syntax for segmenting configuration
+ into sections. This module provides an implementation for working with NXOS configuration
+ sections in a deterministic way. This module works with either CLI or NXAPI transports.
+version_added: 1.0.0
+options:
+ lines:
+ description:
+ - The ordered set of commands that should be configured in the section. The commands
+ must be the exact same commands as found in the device running-config to ensure idempotency
+ and correct diff. Be sure to note the configuration command syntax as some commands are
+ automatically modified by the device config parser.
+ type: list
+ aliases:
+ - commands
+ elements: str
+ parents:
+ description:
+ - The ordered set of parents that uniquely identify the section or hierarchy the
+ commands should be checked against. If the parents argument is omitted, the
+ commands are checked against the set of top level or global commands.
+ type: list
+ elements: str
+ src:
+ description:
+ - The I(src) argument provides a path to the configuration file to load into the
+ remote system. The path can either be a full system path to the configuration
+ file if the value starts with / or relative to the root of the implemented role
+ or playbook. This argument is mutually exclusive with the I(lines) and I(parents)
+ arguments. The configuration lines in the source file should be similar to how it
+ will appear if present in the running-configuration of the device including indentation
+ to ensure idempotency and correct diff.
+ type: path
+ replace_src:
+ description:
+ - The I(replace_src) argument provides path to the configuration file to load
+ into the remote system. This argument is used to replace the entire config with
+ a flat-file. This is used with argument I(replace) with value I(config). This
+ is mutually exclusive with the I(lines) and I(src) arguments. This argument
+ will only work for NX-OS versions that support `config replace`. Use I(nxos_file_copy)
+ module to copy the flat file to remote device and then use the path with this argument.
+ The configuration lines in the file should be similar to how it
+ will appear if present in the running-configuration of the device including the indentation
+ to ensure idempotency and correct diff.
+ type: str
+ before:
+ description:
+ - The ordered set of commands to push on to the command stack if a change needs
+ to be made. This allows the playbook designer the opportunity to perform configuration
+ commands prior to pushing any changes without affecting how the set of commands
+ are matched against the system.
+ type: list
+ elements: str
+ after:
+ description:
+ - The ordered set of commands to append to the end of the command stack if a change
+ needs to be made. Just like with I(before) this allows the playbook designer
+ to append a set of commands to be executed after the command set.
+ type: list
+ elements: str
+ match:
+ description:
+ - Instructs the module on the way to perform the matching of the set of commands
+ against the current device config. If match is set to I(line), commands are
+ matched line by line. If match is set to I(strict), command lines are matched
+ with respect to position. If match is set to I(exact), command lines must be
+ an equal match. Finally, if match is set to I(none), the module will not attempt
+ to compare the source configuration with the running configuration on the remote
+ device.
+ default: line
+ choices:
+ - line
+ - strict
+ - exact
+ - none
+ type: str
+ replace:
+ description:
+ - Instructs the module on the way to perform the configuration on the device. If
+ the replace argument is set to I(line) then the modified lines are pushed to
+ the device in configuration mode. If the replace argument is set to I(block)
+ then the entire command block is pushed to the device in configuration mode
+ if any line is not correct. replace I(config) will only work for NX-OS versions
+ that support `config replace`.
+ default: line
+ choices:
+ - line
+ - block
+ - config
+ type: str
+ backup:
+ description:
+ - This argument will cause the module to create a full backup of the current C(running-config)
+ from the remote device before any changes are made. If the C(backup_options)
+ value is not given, the backup file is written to the C(backup) folder in the
+ playbook root directory or role root directory, if playbook is part of an ansible
+ role. If the directory does not exist, it is created.
+ type: bool
+ default: no
+ 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.
+ The configuration lines for this option should be similar to how it will appear if present
+ in the running-configuration of the device including the indentation to ensure idempotency
+ and correct diff.
+ aliases:
+ - config
+ type: str
+ defaults:
+ description:
+ - The I(defaults) argument will influence how the running-config is collected
+ from the device. When the value is set to true, the command used to collect
+ the running-config is append with the all keyword. When the value is set to
+ false, the command is issued without the all keyword
+ type: bool
+ default: no
+ save_when:
+ description:
+ - When changes are made to the device running-configuration, the changes are not
+ copied to non-volatile storage by default. Using this argument will change
+ that before. If the argument is set to I(always), then the running-config will
+ always be copied to the startup-config and the I(modified) flag will always
+ be set to True. If the argument is set to I(modified), then the running-config
+ will only be copied to the startup-config if it has changed since the last save
+ to startup-config. If the argument is set to I(never), the running-config will
+ never be copied to the startup-config. If the argument is set to I(changed),
+ then the running-config will only be copied to the startup-config if the task
+ has made a change. I(changed) was added in Ansible 2.6.
+ default: never
+ choices:
+ - always
+ - never
+ - modified
+ - changed
+ type: str
+ diff_against:
+ description:
+ - When using the C(ansible-playbook --diff) command line argument the module can
+ generate diffs against different sources.
+ - When this option is configure as I(startup), the module will return the diff
+ of the running-config against the startup-config.
+ - When this option is configured as I(intended), the module will return the diff
+ of the running-config against the configuration provided in the C(intended_config)
+ argument.
+ - When this option is configured as I(running), the module will return the before
+ and after diff of the running-config with respect to any changes made to the
+ device configuration.
+ choices:
+ - startup
+ - intended
+ - running
+ type: str
+ diff_ignore_lines:
+ description:
+ - Use this argument to specify one or more lines that should be ignored during
+ the diff. This is used for lines in the configuration that are automatically
+ updated by the system. This argument takes a list of regular expressions or
+ exact line matches.
+ type: list
+ elements: str
+ intended_config:
+ description:
+ - The C(intended_config) provides the master configuration that the node should
+ conform to and is used to check the final running-config against. This argument
+ will not modify any settings on the remote device and is strictly used to check
+ the compliance of the current device's configuration against. When specifying
+ this argument, the task should also modify the C(diff_against) value and set
+ it to I(intended). The configuration lines for this value should be similar to how it
+ will appear if present in the running-configuration of the device including the indentation
+ to ensure correct diff.
+ type: str
+ backup_options:
+ description:
+ - This is a dict object containing configurable options related to backup file
+ path. The value of this option is read only when C(backup) is set to I(True),
+ if C(backup) is set to I(false) this option will be silently ignored.
+ suboptions:
+ filename:
+ description:
+ - The filename to be used to store the backup configuration. If the filename
+ is not given it will be generated based on the hostname, current time and
+ date in format defined by <hostname>_config.<current-date>@<current-time>
+ type: str
+ dir_path:
+ description:
+ - This option provides the path ending with directory name in which the backup
+ configuration file will be stored. If the directory does not exist it will
+ be created and the filename is either the value of C(filename) or default
+ filename as described in C(filename) options description. If the path value
+ is not given in that case a I(backup) directory will be created in the current
+ working directory and backup configuration will be copied in C(filename)
+ within I(backup) directory.
+ type: path
+ type: dict
+notes:
+- Unsupported for Cisco MDS
+- Abbreviated commands are NOT idempotent, see
+ U(https://docs.ansible.com/ansible/latest/network/user_guide/faq.html#why-do-the-config-modules-always-return-changed-true-with-abbreviated-commands).
+- To ensure idempotency and correct diff the configuration lines in the relevant module options should be similar to how they
+ appear if present in the running configuration on device including the indentation.
+"""
+
+EXAMPLES = """
+- name: configure top level configuration and save it
+ cisco.nxos.nxos_config:
+ lines: hostname {{ inventory_hostname }}
+ save_when: modified
+
+- name: diff the running-config against a provided config
+ cisco.nxos.nxos_config:
+ diff_against: intended
+ intended_config: "{{ lookup('file', 'master.cfg') }}"
+
+- cisco.nxos.nxos_config:
+ lines:
+ - 10 permit ip 192.0.2.1/32 any log
+ - 20 permit ip 192.0.2.2/32 any log
+ - 30 permit ip 192.0.2.3/32 any log
+ - 40 permit ip 192.0.2.4/32 any log
+ - 50 permit ip 192.0.2.5/32 any log
+ parents: ip access-list test
+ before: no ip access-list test
+ match: exact
+
+- cisco.nxos.nxos_config:
+ lines:
+ - 10 permit ip 192.0.2.1/32 any log
+ - 20 permit ip 192.0.2.2/32 any log
+ - 30 permit ip 192.0.2.3/32 any log
+ - 40 permit ip 192.0.2.4/32 any log
+ parents: ip access-list test
+ before: no ip access-list test
+ replace: block
+
+- name: replace config with flat file
+ cisco.nxos.nxos_config:
+ replace_src: config.txt
+ replace: config
+
+- name: for idempotency, use full-form commands
+ cisco.nxos.nxos_config:
+ lines:
+ # - shut
+ - shutdown
+ # parents: int eth1/1
+ parents: interface Ethernet1/1
+
+- name: configurable backup path
+ cisco.nxos.nxos_config:
+ backup: yes
+ backup_options:
+ filename: backup.cfg
+ dir_path: /home/user
+"""
+
+RETURN = """
+commands:
+ description: The set of commands that will be pushed to the remote device
+ returned: always
+ type: list
+ sample: ['hostname foo', 'vlan 1', 'name default']
+updates:
+ description: The set of commands that will be pushed to the remote device
+ returned: always
+ type: list
+ sample: ['hostname foo', 'vlan 1', 'name default']
+backup_path:
+ description: The full path to the backup file
+ returned: when backup is yes
+ type: str
+ sample: /playbooks/ansible/backup/nxos_config.2016-07-16@22:28:34
+filename:
+ description: The name of the backup file
+ returned: when backup is yes and filename is not specified in backup options
+ type: str
+ sample: nxos_config.2016-07-16@22:28:34
+shortname:
+ description: The full path to the backup file excluding the timestamp
+ returned: when backup is yes and filename is not specified in backup options
+ type: str
+ sample: /playbooks/ansible/backup/nxos_config
+date:
+ description: The date extracted from the backup file name
+ returned: when backup is yes
+ type: str
+ sample: "2016-07-16"
+time:
+ description: The time extracted from the backup file name
+ returned: when backup is yes
+ type: str
+ sample: "22:28:34"
+"""
+from ansible.module_utils._text import to_text
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils.connection import ConnectionError
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import (
+ NetworkConfig,
+ dumps,
+)
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import to_list
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ get_config,
+ get_connection,
+ load_config,
+ run_commands,
+)
+
+
+def get_running_config(module, config=None, flags=None):
+ contents = module.params["running_config"]
+ if not contents:
+ if config:
+ contents = config
+ else:
+ contents = get_config(module, flags=flags)
+ return contents
+
+
+def get_candidate(module):
+ candidate = ""
+ if module.params["src"]:
+ if module.params["replace"] != "config":
+ candidate = module.params["src"]
+ if module.params["replace"] == "config":
+ candidate = "config replace {0}".format(module.params["replace_src"])
+ elif module.params["lines"]:
+ candidate_obj = NetworkConfig(indent=2)
+ parents = module.params["parents"] or list()
+ candidate_obj.add(module.params["lines"], parents=parents)
+ candidate = dumps(candidate_obj, "raw")
+ return candidate
+
+
+def execute_show_commands(module, commands, output="text"):
+ cmds = []
+ for command in to_list(commands):
+ cmd = {"command": command, "output": output}
+ cmds.append(cmd)
+ body = run_commands(module, cmds)
+ return body
+
+
+def save_config(module, result):
+ result["changed"] = True
+ if not module.check_mode:
+ cmd = {
+ "command": "copy running-config startup-config",
+ "output": "text",
+ }
+ run_commands(module, [cmd])
+ else:
+ module.warn(
+ "Skipping command `copy running-config startup-config` "
+ "due to check_mode. Configuration not copied to "
+ "non-volatile storage",
+ )
+
+
+def main():
+ """main entry point for module execution"""
+ backup_spec = dict(filename=dict(), dir_path=dict(type="path"))
+ argument_spec = dict(
+ src=dict(type="path"),
+ replace_src=dict(),
+ lines=dict(aliases=["commands"], type="list", elements="str"),
+ parents=dict(type="list", elements="str"),
+ before=dict(type="list", elements="str"),
+ after=dict(type="list", elements="str"),
+ match=dict(default="line", choices=["line", "strict", "exact", "none"]),
+ replace=dict(default="line", choices=["line", "block", "config"]),
+ running_config=dict(aliases=["config"]),
+ intended_config=dict(),
+ defaults=dict(type="bool", default=False),
+ backup=dict(type="bool", default=False),
+ backup_options=dict(type="dict", options=backup_spec),
+ save_when=dict(choices=["always", "never", "modified", "changed"], default="never"),
+ diff_against=dict(choices=["running", "startup", "intended"]),
+ diff_ignore_lines=dict(type="list", elements="str"),
+ )
+
+ mutually_exclusive = [("lines", "src", "replace_src"), ("parents", "src")]
+
+ required_if = [
+ ("match", "strict", ["lines"]),
+ ("match", "exact", ["lines"]),
+ ("replace", "block", ["lines"]),
+ ("replace", "config", ["replace_src"]),
+ ("diff_against", "intended", ["intended_config"]),
+ ]
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ mutually_exclusive=mutually_exclusive,
+ required_if=required_if,
+ supports_check_mode=True,
+ )
+
+ warnings = list()
+
+ result = {"changed": False, "warnings": warnings}
+
+ config = None
+
+ diff_ignore_lines = module.params["diff_ignore_lines"]
+ path = module.params["parents"]
+ connection = get_connection(module)
+ contents = None
+ flags = ["all"] if module.params["defaults"] else []
+ replace_src = module.params["replace_src"]
+ if replace_src:
+ if module.params["replace"] != "config":
+ module.fail_json(msg="replace: config is required with replace_src")
+
+ if module.params["backup"] or (module._diff and module.params["diff_against"] == "running"):
+ contents = get_config(module, flags=flags)
+ config = NetworkConfig(indent=2, contents=contents)
+ if module.params["backup"]:
+ result["__backup__"] = contents
+
+ if any((module.params["src"], module.params["lines"], replace_src)):
+ match = module.params["match"]
+ replace = module.params["replace"]
+
+ commit = not module.check_mode
+ candidate = get_candidate(module)
+ running = get_running_config(module, contents, flags=flags)
+ if replace_src:
+ commands = candidate.split("\n")
+ result["commands"] = result["updates"] = commands
+ if commit:
+ load_config(module, commands, replace=replace_src)
+
+ result["changed"] = True
+ else:
+ try:
+ response = connection.get_diff(
+ candidate=candidate,
+ running=running,
+ diff_match=match,
+ diff_ignore_lines=diff_ignore_lines,
+ path=path,
+ diff_replace=replace,
+ )
+ except ConnectionError as exc:
+ module.fail_json(msg=to_text(exc, errors="surrogate_then_replace"))
+
+ config_diff = response["config_diff"]
+ if config_diff:
+ commands = config_diff.split("\n")
+
+ if module.params["before"]:
+ commands[:0] = module.params["before"]
+
+ if module.params["after"]:
+ commands.extend(module.params["after"])
+
+ result["commands"] = commands
+ result["updates"] = commands
+
+ if commit:
+ load_config(module, commands, replace=replace_src)
+
+ result["changed"] = True
+
+ running_config = module.params["running_config"]
+ startup_config = None
+
+ if module.params["save_when"] == "always":
+ save_config(module, result)
+ elif module.params["save_when"] == "modified":
+ output = execute_show_commands(module, ["show running-config", "show startup-config"])
+
+ running_config = NetworkConfig(indent=2, contents=output[0], ignore_lines=diff_ignore_lines)
+ startup_config = NetworkConfig(indent=2, contents=output[1], ignore_lines=diff_ignore_lines)
+
+ if running_config.sha1 != startup_config.sha1:
+ save_config(module, result)
+ elif module.params["save_when"] == "changed" and result["changed"]:
+ save_config(module, result)
+
+ if module._diff:
+ if not running_config:
+ output = execute_show_commands(module, "show running-config")
+ contents = output[0]
+ else:
+ contents = running_config
+
+ # recreate the object in order to process diff_ignore_lines
+ running_config = NetworkConfig(indent=2, contents=contents, ignore_lines=diff_ignore_lines)
+
+ if module.params["diff_against"] == "running":
+ if module.check_mode:
+ module.warn("unable to perform diff against running-config due to check mode")
+ contents = None
+ else:
+ contents = config.config_text
+
+ elif module.params["diff_against"] == "startup":
+ if not startup_config:
+ output = execute_show_commands(module, "show startup-config")
+ contents = output[0]
+ else:
+ contents = startup_config.config_text
+
+ elif module.params["diff_against"] == "intended":
+ contents = module.params["intended_config"]
+
+ if contents is not None:
+ base_config = NetworkConfig(indent=2, contents=contents, ignore_lines=diff_ignore_lines)
+
+ if running_config.sha1 != base_config.sha1:
+ before = ""
+ after = ""
+ if module.params["diff_against"] == "intended":
+ before = running_config
+ after = base_config
+ elif module.params["diff_against"] in ("startup", "running"):
+ before = base_config
+ after = running_config
+
+ result.update(
+ {
+ "changed": True,
+ "diff": {"before": str(before), "after": str(after)},
+ },
+ )
+
+ if result.get("changed") and any((module.params["src"], module.params["lines"])):
+ msg = (
+ "To ensure idempotency and correct diff the input configuration lines should be"
+ " similar to how they appear if present in"
+ " the running configuration on device"
+ )
+ if module.params["src"]:
+ msg += " including the indentation"
+ if "warnings" in result:
+ result["warnings"].append(msg)
+ else:
+ result["warnings"] = msg
+
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_devicealias.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_devicealias.py
new file mode 100644
index 00000000..71d4ebb6
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_devicealias.py
@@ -0,0 +1,550 @@
+#!/usr/bin/python
+# Copyright: Ansible Project
+# 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
+
+
+DOCUMENTATION = """
+module: nxos_devicealias
+short_description: Configuration of device alias for Cisco NXOS MDS Switches.
+description:
+- Configuration of device alias for Cisco MDS NXOS.
+version_added: 1.0.0
+author:
+- Suhas Bharadwaj (@srbharadwaj) (subharad@cisco.com)
+notes:
+- Tested against Cisco MDS NX-OS 8.4(1)
+options:
+ distribute:
+ description:
+ - Enable/Disable device-alias distribution
+ type: bool
+ mode:
+ description:
+ - Mode of devices-alias, basic or enhanced
+ choices:
+ - basic
+ - enhanced
+ type: str
+ da:
+ description:
+ - List of device-alias to be added or removed
+ type: list
+ elements: dict
+ suboptions:
+ name:
+ description:
+ - Name of the device-alias to be added or removed
+ required: true
+ type: str
+ pwwn:
+ description:
+ - pwwn to which the name needs to be associated with
+ type: str
+ remove:
+ description:
+ - Removes the device-alias if set to True
+ type: bool
+ default: false
+ rename:
+ description:
+ - List of device-alias to be renamed
+ type: list
+ elements: dict
+ suboptions:
+ old_name:
+ description:
+ - Old name of the device-alias that needs to be renamed
+ required: true
+ type: str
+ new_name:
+ description:
+ - New name of the device-alias
+ required: true
+ type: str
+"""
+
+EXAMPLES = """
+- name: Test that device alias module works
+ cisco.nxos.nxos_devicealias:
+ da:
+ - name: test1_add
+ pwwn: 56:2:22:11:22:88:11:67
+ - name: test2_add
+ pwwn: 65:22:22:11:22:22:11:d
+ - name: dev1
+ remove: true
+ - name: dev2
+ remove: true
+ distribute: true
+ mode: enhanced
+ rename:
+ - new_name: bcd
+ old_name: abc
+ - new_name: bcd1
+ old_name: abc1
+
+
+"""
+
+RETURN = """
+commands:
+ description: commands sent to the device
+ returned: always
+ type: list
+ sample:
+ - terminal dont-ask
+ - device-alias database
+ - device-alias name somename pwwn 10:00:00:00:89:a1:01:03
+ - device-alias name somename1 pwwn 10:00:00:00:89:a1:02:03
+ - device-alias commit
+ - no terminal dont-ask
+"""
+
+import string
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ load_config,
+ run_commands,
+)
+
+
+__metaclass__ = type
+
+VALID_DA_CHARS = ("-", "_", "$", "^")
+
+
+class showDeviceAliasStatus(object):
+ """docstring for showDeviceAliasStatus"""
+
+ def __init__(self, module):
+ self.module = module
+ self.distribute = ""
+ self.mode = ""
+ self.locked = False
+ self.update()
+
+ def execute_show_cmd(self, cmd):
+ output = execute_show_command(cmd, self.module)[0]
+ return output
+
+ def update(self):
+ command = "show device-alias status"
+ output = self.execute_show_cmd(command).split("\n")
+ for o in output:
+ if "Fabric Distribution" in o:
+ self.distribute = o.split(":")[1].strip().lower()
+ if "Mode" in o:
+ self.mode = o.split("Mode:")[1].strip().lower()
+ if "Locked" in o:
+ self.locked = True
+
+ def isLocked(self):
+ return self.locked
+
+ def getDistribute(self):
+ return self.distribute
+
+ def getMode(self):
+ return self.mode
+
+
+class showDeviceAliasDatabase(object):
+ """docstring for showDeviceAliasDatabase"""
+
+ def __init__(self, module):
+ self.module = module
+ self.da_dict = {}
+ self.update()
+
+ def execute_show_cmd(self, cmd):
+ output = execute_show_command(cmd, self.module)[0]
+ return output
+
+ def update(self):
+ command = "show device-alias database"
+ # output = execute_show_command(command, self.module)[0].split("\n")
+ output = self.execute_show_cmd(command)
+ self.da_list = output.split("\n")
+ for eachline in self.da_list:
+ if "device-alias" in eachline:
+ sv = eachline.strip().split()
+ self.da_dict[sv[2]] = sv[4]
+
+ def isNameInDaDatabase(self, name):
+ return name in self.da_dict.keys()
+
+ def isPwwnInDaDatabase(self, pwwn):
+ newpwwn = ":".join(["0" + str(ep) if len(ep) == 1 else ep for ep in pwwn.split(":")])
+ return newpwwn in self.da_dict.values()
+
+ def isNamePwwnPresentInDatabase(self, name, pwwn):
+ newpwwn = ":".join(["0" + str(ep) if len(ep) == 1 else ep for ep in pwwn.split(":")])
+ if name in self.da_dict.keys():
+ if newpwwn == self.da_dict[name]:
+ return True
+ return False
+
+ def getPwwnByName(self, name):
+ if name in self.da_dict.keys():
+ return self.da_dict[name]
+ else:
+ return None
+
+ def getNameByPwwn(self, pwwn):
+ newpwwn = ":".join(["0" + str(ep) if len(ep) == 1 else ep for ep in pwwn.split(":")])
+ for n, p in self.da_dict.items():
+ if p == newpwwn:
+ return n
+ return None
+
+
+def isPwwnValid(pwwn):
+ pwwnsplit = pwwn.split(":")
+ if len(pwwnsplit) != 8:
+ return False
+ for eachpwwnsplit in pwwnsplit:
+ if len(eachpwwnsplit) > 2 or len(eachpwwnsplit) < 1:
+ return False
+ if not all(c in string.hexdigits for c in eachpwwnsplit):
+ return False
+ return True
+
+
+def isNameValid(name):
+ if not name[0].isalpha():
+ # Illegal first character. Name must start with a letter
+ return False
+ if len(name) > 64:
+ return False
+ for character in name:
+ if not character.isalnum() and character not in VALID_DA_CHARS:
+ return False
+ return True
+
+
+def execute_show_command(command, module, command_type="cli_show"):
+ output = "text"
+ commands = [{"command": command, "output": output}]
+ out = run_commands(module, commands)
+ return out
+
+
+def flatten_list(command_lists):
+ flat_command_list = []
+ for command in command_lists:
+ if isinstance(command, list):
+ flat_command_list.extend(command)
+ else:
+ flat_command_list.append(command)
+ return flat_command_list
+
+
+def main():
+ element_spec = dict(
+ name=dict(required=True, type="str"),
+ pwwn=dict(type="str"),
+ remove=dict(type="bool", default=False),
+ )
+
+ element_spec_rename = dict(
+ old_name=dict(required=True, type="str"),
+ new_name=dict(required=True, type="str"),
+ )
+
+ argument_spec = dict(
+ distribute=dict(type="bool"),
+ mode=dict(type="str", choices=["enhanced", "basic"]),
+ da=dict(type="list", elements="dict", options=element_spec),
+ rename=dict(type="list", elements="dict", options=element_spec_rename),
+ )
+
+ module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)
+
+ warnings = list()
+ messages = list()
+ commands_to_execute = list()
+ result = {"changed": False}
+
+ distribute = module.params["distribute"]
+ mode = module.params["mode"]
+ da = module.params["da"]
+ rename = module.params["rename"]
+
+ # Step 0.0: Validate syntax of name and pwwn
+ # Also validate syntax of rename arguments
+ if da is not None:
+ for eachdict in da:
+ name = eachdict["name"]
+ pwwn = eachdict["pwwn"]
+ remove = eachdict["remove"]
+ if pwwn is not None:
+ pwwn = pwwn.lower()
+ if not remove:
+ if pwwn is None:
+ module.fail_json(
+ msg="This device alias name "
+ + str(name)
+ + " which needs to be added, does not have pwwn specified. Please specify a valid pwwn",
+ )
+ if not isNameValid(name):
+ module.fail_json(
+ msg="This pwwn name is invalid : "
+ + str(name)
+ + ". Note that name cannot be more than 64 alphanumeric chars, "
+ + "it must start with a letter, and can only contain these characters: "
+ + ", ".join(["'{0}'".format(c) for c in VALID_DA_CHARS]),
+ )
+ if not isPwwnValid(pwwn):
+ module.fail_json(
+ msg="This pwwn is invalid : "
+ + str(pwwn)
+ + ". Please check that its a valid pwwn",
+ )
+ if rename is not None:
+ for eachdict in rename:
+ oldname = eachdict["old_name"]
+ newname = eachdict["new_name"]
+ if not isNameValid(oldname):
+ module.fail_json(
+ msg="This pwwn name is invalid : "
+ + str(oldname)
+ + ". Note that name cannot be more than 64 alphanumeric chars, "
+ + "it must start with a letter, and can only contain these characters: "
+ + ", ".join(["'{0}'".format(c) for c in VALID_DA_CHARS]),
+ )
+ if not isNameValid(newname):
+ module.fail_json(
+ msg="This pwwn name is invalid : "
+ + str(newname)
+ + ". Note that name cannot be more than 64 alphanumeric chars, "
+ + "it must start with a letter, and can only contain these characters: "
+ + ", ".join(["'{0}'".format(c) for c in VALID_DA_CHARS]),
+ )
+
+ # Step 0.1: Check DA status
+ shDAStausObj = showDeviceAliasStatus(module)
+ d = shDAStausObj.getDistribute()
+ m = shDAStausObj.getMode()
+ if shDAStausObj.isLocked():
+ module.fail_json(msg="device-alias has acquired lock on the switch. Hence cannot procced.")
+
+ # Step 1: Process distribute
+ commands = []
+ if distribute is not None:
+ if distribute:
+ # playbook has distribute as True(enabled)
+ if d == "disabled":
+ # but switch distribute is disabled(false), so set it to
+ # true(enabled)
+ commands.append("device-alias distribute")
+ messages.append("device-alias distribute changed from disabled to enabled")
+ else:
+ messages.append(
+ "device-alias distribute remains unchanged. current distribution mode is enabled",
+ )
+ else:
+ # playbook has distribute as False(disabled)
+ if d == "enabled":
+ # but switch distribute is enabled(true), so set it to
+ # false(disabled)
+ commands.append("no device-alias distribute")
+ messages.append("device-alias distribute changed from enabled to disabled")
+ else:
+ messages.append(
+ "device-alias distribute remains unchanged. current distribution mode is disabled",
+ )
+
+ cmds = flatten_list(commands)
+ if cmds:
+ commands_to_execute = commands_to_execute + cmds
+ if module.check_mode:
+ # Check mode implemented at the da_add/da_remove stage
+ pass
+ else:
+ result["changed"] = True
+ load_config(module, cmds)
+
+ # Step 2: Process mode
+ commands = []
+ if mode is not None:
+ if mode == "basic":
+ # playbook has mode as basic
+ if m == "enhanced":
+ # but switch mode is enhanced, so set it to basic
+ commands.append("no device-alias mode enhanced")
+ messages.append("device-alias mode changed from enhanced to basic")
+ else:
+ messages.append("device-alias mode remains unchanged. current mode is basic")
+
+ else:
+ # playbook has mode as enhanced
+ if m == "basic":
+ # but switch mode is basic, so set it to enhanced
+ commands.append("device-alias mode enhanced")
+ messages.append("device-alias mode changed from basic to enhanced")
+ else:
+ messages.append("device-alias mode remains unchanged. current mode is enhanced")
+
+ if commands:
+ if distribute:
+ commands.append("device-alias commit")
+ commands = ["terminal dont-ask"] + commands + ["no terminal dont-ask"]
+ else:
+ if distribute is None and d == "enabled":
+ commands.append("device-alias commit")
+ commands = ["terminal dont-ask"] + commands + ["no terminal dont-ask"]
+
+ cmds = flatten_list(commands)
+
+ if cmds:
+ commands_to_execute = commands_to_execute + cmds
+ if module.check_mode:
+ # Check mode implemented at the end
+ pass
+ else:
+ result["changed"] = True
+ load_config(module, cmds)
+
+ # Step 3: Process da
+ commands = []
+ shDADatabaseObj = showDeviceAliasDatabase(module)
+ if da is not None:
+ da_remove_list = []
+ da_add_list = []
+ for eachdict in da:
+ name = eachdict["name"]
+ pwwn = eachdict["pwwn"]
+ remove = eachdict["remove"]
+ if pwwn is not None:
+ pwwn = pwwn.lower()
+ if remove:
+ if shDADatabaseObj.isNameInDaDatabase(name):
+ commands.append("no device-alias name " + name)
+ da_remove_list.append(name)
+ else:
+ messages.append(
+ name
+ + " - This device alias name is not in switch device-alias database, hence cannot be removed.",
+ )
+ else:
+ if shDADatabaseObj.isNamePwwnPresentInDatabase(name, pwwn):
+ messages.append(
+ name
+ + " : "
+ + pwwn
+ + " - This device alias name,pwwn is already in switch device-alias database, hence nothing to configure",
+ )
+ else:
+ if shDADatabaseObj.isNameInDaDatabase(name):
+ module.fail_json(
+ msg=name
+ + " - This device alias name is already present in switch device-alias database but assigned to another pwwn ("
+ + shDADatabaseObj.getPwwnByName(name)
+ + ") hence cannot be added",
+ )
+
+ elif shDADatabaseObj.isPwwnInDaDatabase(pwwn):
+ module.fail_json(
+ msg=pwwn
+ + " - This device alias pwwn is already present in switch device-alias database but assigned to another name ("
+ + shDADatabaseObj.getNameByPwwn(pwwn)
+ + ") hence cannot be added",
+ )
+
+ else:
+ commands.append("device-alias name " + name + " pwwn " + pwwn)
+ da_add_list.append(name)
+
+ if len(da_add_list) != 0 or len(da_remove_list) != 0:
+ commands = ["device-alias database"] + commands
+ if distribute:
+ commands.append("device-alias commit")
+ commands = ["terminal dont-ask"] + commands + ["no terminal dont-ask"]
+ else:
+ if distribute is None and d == "enabled":
+ commands.append("device-alias commit")
+ commands = ["terminal dont-ask"] + commands + ["no terminal dont-ask"]
+
+ cmds = flatten_list(commands)
+ if cmds:
+ commands_to_execute = commands_to_execute + cmds
+ if module.check_mode:
+ # Check mode implemented at the end
+ pass
+ else:
+ result["changed"] = True
+ load_config(module, cmds)
+ if len(da_remove_list) != 0:
+ messages.append(
+ "the required device-alias were removed. " + ",".join(da_remove_list),
+ )
+ if len(da_add_list) != 0:
+ messages.append(
+ "the required device-alias were added. " + ",".join(da_add_list),
+ )
+
+ # Step 5: Process rename
+ commands = []
+ if rename is not None:
+ for eachdict in rename:
+ oldname = eachdict["old_name"]
+ newname = eachdict["new_name"]
+ if shDADatabaseObj.isNameInDaDatabase(newname):
+ module.fail_json(
+ changed=False,
+ commands=cmds,
+ msg=newname
+ + " - this name is already present in the device-alias database, hence we cannot rename "
+ + oldname
+ + " with this one",
+ )
+ if shDADatabaseObj.isNameInDaDatabase(oldname):
+ commands.append("device-alias rename " + oldname + " " + newname)
+ else:
+ module.fail_json(
+ changed=False,
+ commands=cmds,
+ msg=oldname
+ + " - this name is not present in the device-alias database, hence we cannot rename.",
+ )
+
+ if len(commands) != 0:
+ commands = ["device-alias database"] + commands
+ if distribute:
+ commands.append("device-alias commit")
+ commands = ["terminal dont-ask"] + commands + ["no terminal dont-ask"]
+ else:
+ if distribute is None and d == "enabled":
+ commands.append("device-alias commit")
+ commands = ["terminal dont-ask"] + commands + ["no terminal dont-ask"]
+ cmds = flatten_list(commands)
+ if cmds:
+ commands_to_execute = commands_to_execute + cmds
+ if module.check_mode:
+ # Check mode implemented at the end
+ pass
+ else:
+ result["changed"] = True
+ load_config(module, cmds)
+
+ # Step END: check for 'check' mode
+ if module.check_mode:
+ module.exit_json(
+ changed=False,
+ commands=commands_to_execute,
+ msg="Check Mode: No cmds issued to the hosts",
+ )
+
+ result["messages"] = messages
+ result["commands"] = commands_to_execute
+ result["warnings"] = warnings
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_evpn_global.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_evpn_global.py
new file mode 100644
index 00000000..83ef7839
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_evpn_global.py
@@ -0,0 +1,103 @@
+#!/usr/bin/python
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_evpn_global
+extends_documentation_fragment:
+- cisco.nxos.nxos
+short_description: Handles the EVPN control plane for VXLAN.
+description:
+- Handles the EVPN control plane for VXLAN.
+version_added: 1.0.0
+author: Gabriele Gerbino (@GGabriele)
+notes:
+- This module is not supported on Nexus 3000 series of switches.
+- Unsupported for Cisco MDS
+options:
+ nv_overlay_evpn:
+ description:
+ - EVPN control plane.
+ required: true
+ type: bool
+"""
+
+EXAMPLES = """
+- cisco.nxos.nxos_evpn_global:
+ nv_overlay_evpn: true
+"""
+
+RETURN = """
+commands:
+ description: The set of commands to be sent to the remote device
+ returned: always
+ type: list
+ sample: ['nv overlay evpn']
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ get_capabilities,
+ get_config,
+ load_config,
+)
+
+
+def main():
+ argument_spec = dict(nv_overlay_evpn=dict(required=True, type="bool"))
+
+ module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)
+
+ result = {"changed": False}
+
+ warnings = list()
+ if warnings:
+ result["warnings"] = warnings
+
+ config = get_config(module)
+ commands = list()
+
+ info = get_capabilities(module).get("device_info", {})
+ os_platform = info.get("network_os_platform", "")
+
+ if "3K" in os_platform:
+ module.fail_json(msg="This module is not supported on Nexus 3000 series")
+
+ if module.params["nv_overlay_evpn"] is True:
+ if "nv overlay evpn" not in config:
+ commands.append("nv overlay evpn")
+ elif "nv overlay evpn" in config:
+ commands.append("no nv overlay evpn")
+
+ if commands:
+ if not module.check_mode:
+ load_config(module, commands)
+ result["changed"] = True
+
+ result["commands"] = commands
+
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_evpn_vni.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_evpn_vni.py
new file mode 100644
index 00000000..d4490bb7
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_evpn_vni.py
@@ -0,0 +1,299 @@
+#!/usr/bin/python
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_evpn_vni
+extends_documentation_fragment:
+- cisco.nxos.nxos
+short_description: Manages Cisco EVPN VXLAN Network Identifier (VNI).
+description:
+- Manages Cisco Ethernet Virtual Private Network (EVPN) VXLAN Network Identifier (VNI)
+ configurations of a Nexus device.
+version_added: 1.0.0
+author: Gabriele Gerbino (@GGabriele)
+notes:
+- Tested against NXOSv 7.3.(0)D1(1) on VIRL
+- Unsupported for Cisco MDS
+- default, where supported, restores params default value.
+- RD override is not permitted. You should set it to the default values first and
+ then reconfigure it.
+- C(route_target_both), C(route_target_import) and C(route_target_export valid) values
+ are a list of extended communities, (i.e. ['1.2.3.4:5', '33:55']) or the keywords
+ 'auto' or 'default'.
+- The C(route_target_both) property is discouraged due to the inconsistent behavior
+ of the property across Nexus platforms and image versions. For this reason it is
+ recommended to use explicit C(route_target_export) and C(route_target_import) properties
+ instead of C(route_target_both).
+- RD valid values are a string in one of the route-distinguisher formats, the keyword
+ 'auto', or the keyword 'default'.
+options:
+ vni:
+ description:
+ - The EVPN VXLAN Network Identifier.
+ required: true
+ type: str
+ route_distinguisher:
+ description:
+ - The VPN Route Distinguisher (RD). The RD is combined with the IPv4 or IPv6 prefix
+ learned by the PE router to create a globally unique address.
+ type: str
+ route_target_both:
+ description:
+ - Enables/Disables route-target settings for both import and export target communities
+ using a single property.
+ type: list
+ elements: str
+ route_target_import:
+ description:
+ - Sets the route-target 'import' extended communities.
+ type: list
+ elements: str
+ route_target_export:
+ description:
+ - Sets the route-target 'export' extended communities.
+ type: list
+ elements: str
+ state:
+ description:
+ - Determines whether the config should be present or not on the device.
+ default: present
+ choices:
+ - present
+ - absent
+ type: str
+"""
+
+EXAMPLES = """
+- name: vni configuration
+ cisco.nxos.nxos_evpn_vni:
+ vni: 6000
+ route_distinguisher: 60:10
+ route_target_import:
+ - 5000:10
+ - 4100:100
+ route_target_export: auto
+ route_target_both: default
+"""
+
+RETURN = """
+commands:
+ description: commands sent to the device
+ returned: always
+ type: list
+ sample: ["evpn", "vni 6000 l2", "route-target import 5001:10"]
+"""
+
+import re
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import (
+ CustomNetworkConfig,
+)
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ get_config,
+ load_config,
+)
+
+
+PARAM_TO_COMMAND_KEYMAP = {
+ "vni": "vni",
+ "route_distinguisher": "rd",
+ "route_target_both": "route-target both",
+ "route_target_import": "route-target import",
+ "route_target_export": "route-target export",
+}
+
+
+def get_value(arg, config, module):
+ command = PARAM_TO_COMMAND_KEYMAP.get(arg)
+ command_re = re.compile(r"(?:{0}\s)(?P<value>.*)$".format(command), re.M)
+ value = ""
+ if command in config:
+ value = command_re.search(config).group("value")
+ return value
+
+
+def get_route_target_value(arg, config, module):
+ splitted_config = config.splitlines()
+ value_list = []
+ command = PARAM_TO_COMMAND_KEYMAP.get(arg)
+ command_re = re.compile(r"(?:{0}\s)(?P<value>.*)$".format(command), re.M)
+
+ for line in splitted_config:
+ value = ""
+ if command in line.strip():
+ value = command_re.search(line).group("value")
+ value_list.append(value)
+ return value_list
+
+
+def get_existing(module, args):
+ existing = {}
+ netcfg = CustomNetworkConfig(indent=2, contents=get_config(module))
+ parents = ["evpn", "vni {0} l2".format(module.params["vni"])]
+ config = netcfg.get_section(parents)
+
+ if config:
+ for arg in args:
+ if arg != "vni":
+ if arg == "route_distinguisher":
+ existing[arg] = get_value(arg, config, module)
+ else:
+ existing[arg] = get_route_target_value(arg, config, module)
+
+ existing_fix = dict((k, v) for k, v in existing.items() if v)
+ if not existing_fix:
+ existing = existing_fix
+
+ existing["vni"] = module.params["vni"]
+
+ return existing
+
+
+def apply_key_map(key_map, table):
+ new_dict = {}
+ for key in table:
+ new_key = key_map.get(key)
+ if new_key:
+ new_dict[new_key] = table.get(key)
+ return new_dict
+
+
+def fix_proposed(proposed_commands):
+ new_proposed = {}
+ for key, value in proposed_commands.items():
+ if key == "route-target both":
+ new_proposed["route-target export"] = value
+ new_proposed["route-target import"] = value
+ else:
+ new_proposed[key] = value
+ return new_proposed
+
+
+def state_present(module, existing, proposed):
+ commands = list()
+ parents = list()
+ proposed_commands = apply_key_map(PARAM_TO_COMMAND_KEYMAP, proposed)
+ existing_commands = apply_key_map(PARAM_TO_COMMAND_KEYMAP, existing)
+
+ if proposed_commands.get("route-target both"):
+ proposed_commands = fix_proposed(proposed_commands)
+
+ for key, value in proposed_commands.items():
+ if key.startswith("route-target"):
+ if value == ["default"]:
+ existing_value = existing_commands.get(key)
+
+ if existing_value:
+ for target in existing_value:
+ commands.append("no {0} {1}".format(key, target))
+ elif not isinstance(value, list):
+ value = [value]
+
+ for target in value:
+ if target == "default":
+ continue
+ if existing:
+ if target not in existing.get(key.replace("-", "_").replace(" ", "_")):
+ commands.append("{0} {1}".format(key, target))
+ else:
+ commands.append("{0} {1}".format(key, target))
+
+ if existing.get(key.replace("-", "_").replace(" ", "_")):
+ for exi in existing.get(key.replace("-", "_").replace(" ", "_")):
+ if exi not in value:
+ commands.append("no {0} {1}".format(key, exi))
+
+ elif value == "default":
+ existing_value = existing_commands.get(key)
+ if existing_value:
+ commands.append("no {0} {1}".format(key, existing_value))
+ else:
+ command = "{0} {1}".format(key, value)
+ commands.append(command)
+
+ if commands:
+ parents = ["evpn", "vni {0} l2".format(module.params["vni"])]
+
+ return commands, parents
+
+
+def state_absent(module, existing, proposed):
+ commands = ["no vni {0} l2".format(module.params["vni"])]
+ parents = ["evpn"]
+ return commands, parents
+
+
+def main():
+ argument_spec = dict(
+ vni=dict(required=True, type="str"),
+ route_distinguisher=dict(required=False, type="str"),
+ route_target_both=dict(required=False, type="list", elements="str"),
+ route_target_import=dict(required=False, type="list", elements="str"),
+ route_target_export=dict(required=False, type="list", elements="str"),
+ state=dict(choices=["present", "absent"], default="present", required=False),
+ )
+
+ module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)
+
+ warnings = list()
+ results = dict(changed=False, warnings=warnings)
+
+ state = module.params["state"]
+ args = PARAM_TO_COMMAND_KEYMAP.keys()
+ existing = get_existing(module, args)
+ proposed_args = dict((k, v) for k, v in module.params.items() if v is not None and k in args)
+ commands = []
+ parents = []
+
+ proposed = {}
+ for key, value in proposed_args.items():
+ if key != "vni":
+ if value == "true":
+ value = True
+ elif value == "false":
+ value = False
+ if existing.get(key) != value:
+ proposed[key] = value
+
+ if state == "present":
+ commands, parents = state_present(module, existing, proposed)
+ elif state == "absent" and existing:
+ commands, parents = state_absent(module, existing, proposed)
+
+ if commands:
+ candidate = CustomNetworkConfig(indent=3)
+ candidate.add(commands, parents=parents)
+ candidate = candidate.items_text()
+ if not module.check_mode:
+ load_config(module, candidate)
+ results["changed"] = True
+ results["commands"] = candidate
+ else:
+ results["commands"] = []
+ module.exit_json(**results)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_facts.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_facts.py
new file mode 100644
index 00000000..24e0dad2
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_facts.py
@@ -0,0 +1,268 @@
+#!/usr/bin/python
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_facts
+extends_documentation_fragment:
+- cisco.nxos.nxos
+short_description: Gets facts about NX-OS switches
+description:
+- Collects facts from Cisco Nexus devices running the NX-OS operating system. Fact
+ collection is supported over both C(network_cli) and C(httpapi). This module prepends
+ all of the base network fact keys with C(ansible_net_<fact>). The facts module
+ will always collect a base set of facts from the device and can enable or disable
+ collection of additional facts.
+version_added: 1.0.0
+author:
+- Jason Edelman (@jedelman8)
+- Gabriele Gerbino (@GGabriele)
+notes:
+- Tested against NXOSv 7.3.(0)D1(1) on VIRL
+- Unsupported for Cisco MDS
+options:
+ gather_subset:
+ description:
+ - When supplied, this argument will gather operational facts only for the given subset. Possible
+ values for this argument include C(all), C(hardware), C(config), C(legacy), C(interfaces), and C(min). Can
+ specify a list of values to include a larger subset. Values can also be used
+ with an initial C(!) to specify that a specific subset should not be collected.
+ required: false
+ default: 'min'
+ type: list
+ elements: str
+ gather_network_resources:
+ description:
+ - When supplied, this argument will gather configuration facts only for the given subset.
+ Can specify a list of values to include a larger subset. Values can
+ also be used with an initial C(!) to specify that a specific subset should
+ not be collected.
+ - Valid subsets are C(all), C(bfd_interfaces), C(lag_interfaces),
+ C(telemetry), C(vlans), C(lacp), C(lacp_interfaces), C(interfaces), C(l3_interfaces),
+ C(l2_interfaces), C(lldp_global), C(acls), C(acl_interfaces), C(ospfv2), C(ospfv3), C(ospf_interfaces),
+ C(bgp_global), C(bgp_address_family), C(route_maps), C(prefix_lists), C(logging_global), C(ntp_global),
+ C(snmp_server), C(hostname).
+ required: false
+ type: list
+ elements: str
+ available_network_resources:
+ description: When set to C(true) a list of network resources for which resource modules are available will be provided.
+ type: bool
+ default: false
+"""
+
+EXAMPLES = """
+- name: Gather all legacy facts
+ cisco.nxos.nxos_facts:
+ gather_subset: all
+- name: Gather only the config and default facts
+ cisco.nxos.nxos_facts:
+ gather_subset:
+ - config
+- name: Do not gather hardware facts
+ cisco.nxos.nxos_facts:
+ gather_subset:
+ - '!hardware'
+- name: Gather legacy and resource facts
+ cisco.nxos.nxos_facts:
+ gather_subset: all
+ gather_network_resources: all
+- name: Gather only the interfaces resource facts and no legacy facts
+ cisco.nxos.nxos_facts:
+ gather_subset:
+ - '!all'
+ - '!min'
+ gather_network_resources:
+ - interfaces
+- name: Gather interfaces resource and minimal legacy facts
+ cisco.nxos.nxos_facts:
+ gather_subset: min
+ gather_network_resources: interfaces
+"""
+
+RETURN = """
+ansible_net_gather_subset:
+ description: The list of fact subsets collected from the device
+ returned: always
+ type: list
+ansible_net_gather_network_resources:
+ description: The list of fact for network resource subsets collected from the device
+ returned: when the resource is configured
+ type: list
+# default
+ansible_net_model:
+ description: The model name returned from the device
+ returned: always
+ type: str
+ansible_net_serialnum:
+ description: The serial number of the remote device
+ returned: always
+ type: str
+ansible_net_version:
+ description: The operating system version running on the remote device
+ returned: always
+ type: str
+ansible_net_hostname:
+ description: The configured hostname of the device
+ returned: always
+ type: str
+ansible_net_image:
+ description: The image file the device is running
+ returned: always
+ type: str
+ansible_net_api:
+ description: The name of the transport
+ returned: always
+ type: str
+ansible_net_license_hostid:
+ description: The License host id of the device
+ returned: always
+ type: str
+ansible_net_python_version:
+ description: The Python version Ansible controller is using
+ returned: always
+ type: str
+# hardware
+ansible_net_filesystems:
+ description: All file system names available on the device
+ returned: when hardware is configured
+ type: list
+ansible_net_memfree_mb:
+ description: The available free memory on the remote device in Mb
+ returned: when hardware is configured
+ type: int
+ansible_net_memtotal_mb:
+ description: The total memory on the remote device in Mb
+ returned: when hardware is configured
+ type: int
+# config
+ansible_net_config:
+ description: The current active config from the device
+ returned: when config is configured
+ type: str
+# interfaces
+ansible_net_all_ipv4_addresses:
+ description: All IPv4 addresses configured on the device
+ returned: when interfaces is configured
+ type: list
+ansible_net_all_ipv6_addresses:
+ description: All IPv6 addresses configured on the device
+ returned: when interfaces is configured
+ type: list
+ansible_net_interfaces:
+ description: A hash of all interfaces running on the system
+ returned: when interfaces is configured
+ type: dict
+ansible_net_neighbors:
+ description:
+ - The list of LLDP and CDP neighbors from the device. If both,
+ CDP and LLDP neighbor data is present on one port, CDP is preferred.
+ returned: when interfaces is configured
+ type: dict
+# legacy (pre Ansible 2.2)
+fan_info:
+ description: A hash of facts about fans in the remote device
+ returned: when legacy is configured
+ type: dict
+hostname:
+ description: The configured hostname of the remote device
+ returned: when legacy is configured
+ type: dict
+interfaces_list:
+ description: The list of interface names on the remote device
+ returned: when legacy is configured
+ type: dict
+kickstart:
+ description: The software version used to boot the system
+ returned: when legacy is configured
+ type: str
+module:
+ description: A hash of facts about the modules in a remote device
+ returned: when legacy is configured
+ type: dict
+platform:
+ description: The hardware platform reported by the remote device
+ returned: when legacy is configured
+ type: str
+power_supply_info:
+ description: A hash of facts about the power supplies in the remote device
+ returned: when legacy is configured
+ type: str
+vlan_list:
+ description: The list of VLAN IDs configured on the remote device
+ returned: when legacy is configured
+ type: list
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils.connection import Connection
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.facts.facts import (
+ FactsArgs,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.facts import Facts
+
+
+def get_chassis_type(connection):
+ """Return facts resource subsets based on
+ chassis model.
+ """
+ target_type = "nexus"
+
+ device_info = connection.get_device_info()
+ model = device_info.get("network_os_model", "")
+ platform = device_info.get("network_os_platform", "")
+
+ if platform.startswith("DS-") and "MDS" in model:
+ target_type = "mds"
+
+ return target_type
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: ansible_facts
+ """
+ argument_spec = FactsArgs.argument_spec
+
+ module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)
+ connection = Connection(module._socket_path)
+ facts = Facts(module, chassis_type=get_chassis_type(connection))
+
+ warnings = []
+
+ ansible_facts = {}
+ if module.params.get("available_network_resources"):
+ ansible_facts["available_network_resources"] = sorted(facts.get_resource_subsets().keys())
+
+ result = facts.get_facts()
+ additional_facts, additional_warnings = result
+ ansible_facts.update(additional_facts)
+ warnings.extend(additional_warnings)
+
+ module.exit_json(ansible_facts=ansible_facts, warnings=warnings)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_feature.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_feature.py
new file mode 100644
index 00000000..c9229c4d
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_feature.py
@@ -0,0 +1,308 @@
+#!/usr/bin/python
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+DOCUMENTATION = """
+module: nxos_feature
+extends_documentation_fragment:
+- cisco.nxos.nxos
+short_description: Manage features in NX-OS switches.
+notes:
+- Tested against Cisco MDS NX-OS 9.2(2)
+description:
+- Offers ability to enable and disable features in NX-OS.
+version_added: 1.0.0
+author:
+- Jason Edelman (@jedelman8)
+- Gabriele Gerbino (@GGabriele)
+- Suhas Bharadwaj (@srbharadwaj)
+options:
+ feature:
+ description:
+ - Name of feature.
+ required: true
+ type: str
+ state:
+ description:
+ - Desired state of the feature.
+ required: false
+ default: enabled
+ choices:
+ - enabled
+ - disabled
+ type: str
+"""
+
+EXAMPLES = """
+- name: Ensure lacp is enabled
+ cisco.nxos.nxos_feature:
+ feature: lacp
+ state: enabled
+
+- name: Ensure ospf is disabled
+ cisco.nxos.nxos_feature:
+ feature: ospf
+ state: disabled
+
+- name: Ensure vpc is enabled
+ cisco.nxos.nxos_feature:
+ feature: vpc
+ state: enabled
+"""
+
+RETURN = """
+commands:
+ description: The set of commands to be sent to the remote device
+ returned: always
+ type: list
+ sample: ['nv overlay evpn']
+"""
+
+import re
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils.connection import ConnectionError
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ get_capabilities,
+ get_config,
+ load_config,
+ run_commands,
+)
+
+
+def get_available_features(feature, module):
+ available_features = {}
+ feature_regex = r"(?P<feature>\S+)\s+\d+\s+(?P<state>.*)"
+ command = {"command": "show feature", "output": "text"}
+
+ try:
+ body = run_commands(module, [command])[0]
+ split_body = body.splitlines()
+ except (KeyError, IndexError):
+ return {}
+
+ for line in split_body:
+ try:
+ match_feature = re.match(feature_regex, line, re.DOTALL)
+ feature_group = match_feature.groupdict()
+ feature = feature_group["feature"]
+ state = feature_group["state"]
+ except AttributeError:
+ feature = ""
+ state = ""
+
+ if feature and state:
+ if "enabled" in state:
+ state = "enabled"
+
+ if feature not in available_features:
+ available_features[feature] = state
+ else:
+ if available_features[feature] == "disabled" and state == "enabled":
+ available_features[feature] = state
+
+ # certain configurable features do not
+ # show up in the output of "show feature"
+ # but appear in running-config when set
+ run_cfg = get_config(module, flags=["| include ^feature"])
+ for item in re.findall(r"feature\s(.*)", run_cfg):
+ if item not in available_features:
+ available_features[item] = "enabled"
+
+ if "fabric forwarding" not in available_features:
+ available_features["fabric forwarding"] = "disabled"
+
+ return available_features
+
+
+def get_commands(proposed, existing, state, module):
+ feature = validate_feature(module, mode="config")
+ commands = []
+ feature_check = proposed == existing
+ if not feature_check:
+ if state == "enabled":
+ command = "feature {0}".format(feature)
+ commands.append(command)
+ elif state == "disabled":
+ command = "no feature {0}".format(feature)
+ commands.append(command)
+ return commands
+
+
+def get_mds_mapping_features():
+ feature_to_be_mapped = {
+ "show": {
+ "fcrxbbcredit": "extended_credit",
+ "port-track": "port_track",
+ "scp-server": "scpServer",
+ "sftp-server": "sftpServer",
+ "ssh": "sshServer",
+ "tacacs+": "tacacs",
+ "telnet": "telnetServer",
+ },
+ "config": {
+ "extended_credit": "fcrxbbcredit",
+ "port_track": "port-track",
+ "scpServer": "scp-server",
+ "sftpServer": "sftp-server",
+ "sshServer": "ssh",
+ "tacacs": "tacacs+",
+ "telnetServer": "telnet",
+ },
+ }
+ return feature_to_be_mapped
+
+
+def validate_feature(module, mode="show"):
+ """Some features may need to be mapped due to inconsistency
+ between how they appear from "show feature" output and
+ how they are configured"""
+
+ feature = module.params["feature"]
+
+ try:
+ info = get_capabilities(module)
+ device_info = info.get("device_info", {})
+ os_version = device_info.get("network_os_version", "")
+ os_platform = device_info.get("network_os_platform", "")
+ except ConnectionError:
+ os_version = ""
+ os_platform = ""
+
+ if "8.1" in os_version:
+ feature_to_be_mapped = {
+ "show": {
+ "nv overlay": "nve",
+ "vn-segment-vlan-based": "vnseg_vlan",
+ "hsrp": "hsrp_engine",
+ "fabric multicast": "fabric_mcast",
+ "scp-server": "scpServer",
+ "sftp-server": "sftpServer",
+ "sla responder": "sla_responder",
+ "sla sender": "sla_sender",
+ "ssh": "sshServer",
+ "tacacs+": "tacacs",
+ "telnet": "telnetServer",
+ "ethernet-link-oam": "elo",
+ },
+ "config": {
+ "nve": "nv overlay",
+ "vnseg_vlan": "vn-segment-vlan-based",
+ "hsrp_engine": "hsrp",
+ "fabric_mcast": "fabric multicast",
+ "scpServer": "scp-server",
+ "sftpServer": "sftp-server",
+ "sla_sender": "sla sender",
+ "sla_responder": "sla responder",
+ "sshServer": "ssh",
+ "tacacs": "tacacs+",
+ "telnetServer": "telnet",
+ "elo": "ethernet-link-oam",
+ },
+ }
+ else:
+ feature_to_be_mapped = {
+ "show": {
+ "nv overlay": "nve",
+ "vn-segment-vlan-based": "vnseg_vlan",
+ "hsrp": "hsrp_engine",
+ "fabric multicast": "fabric_mcast",
+ "scp-server": "scpServer",
+ "sftp-server": "sftpServer",
+ "sla responder": "sla_responder",
+ "sla sender": "sla_sender",
+ "ssh": "sshServer",
+ "tacacs+": "tacacs",
+ "telnet": "telnetServer",
+ "ethernet-link-oam": "elo",
+ "port-security": "eth_port_sec",
+ },
+ "config": {
+ "nve": "nv overlay",
+ "vnseg_vlan": "vn-segment-vlan-based",
+ "hsrp_engine": "hsrp",
+ "fabric_mcast": "fabric multicast",
+ "scpServer": "scp-server",
+ "sftpServer": "sftp-server",
+ "sla_sender": "sla sender",
+ "sla_responder": "sla responder",
+ "sshServer": "ssh",
+ "tacacs": "tacacs+",
+ "telnetServer": "telnet",
+ "elo": "ethernet-link-oam",
+ "eth_port_sec": "port-security",
+ },
+ }
+
+ if os_platform.startswith("DS-"):
+ feature_to_be_mapped = get_mds_mapping_features()
+
+ if feature in feature_to_be_mapped[mode]:
+ feature = feature_to_be_mapped[mode][feature]
+
+ return feature
+
+
+def main():
+ argument_spec = dict(
+ feature=dict(type="str", required=True),
+ state=dict(choices=["enabled", "disabled"], default="enabled"),
+ )
+
+ module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)
+
+ warnings = list()
+ results = dict(changed=False, warnings=warnings)
+
+ feature = validate_feature(module)
+ state = module.params["state"].lower()
+
+ available_features = get_available_features(feature, module)
+ if feature not in available_features:
+ module.fail_json(
+ msg="Invalid feature name.",
+ features_currently_supported=available_features,
+ invalid_feature=feature,
+ )
+ else:
+ existstate = available_features[feature]
+
+ existing = dict(state=existstate)
+ proposed = dict(state=state)
+ results["changed"] = False
+
+ cmds = get_commands(proposed, existing, state, module)
+
+ if cmds:
+ # On N35 A8 images, some features return a yes/no prompt
+ # on enablement or disablement. Bypass using terminal dont-ask
+ cmds.insert(0, "terminal dont-ask")
+ if not module.check_mode:
+ load_config(module, cmds)
+ results["changed"] = True
+
+ results["commands"] = cmds
+ module.exit_json(**results)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_file_copy.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_file_copy.py
new file mode 100644
index 00000000..4847a446
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_file_copy.py
@@ -0,0 +1,497 @@
+#!/usr/bin/python
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+DOCUMENTATION = """
+module: nxos_file_copy
+extends_documentation_fragment:
+- cisco.nxos.nxos
+short_description: Copy a file to a remote NXOS device.
+description:
+- This module supports two different workflows for copying a file to flash (or bootflash)
+ on NXOS devices. Files can either be (1) pushed from the Ansible controller to
+ the device or (2) pulled from a remote SCP file server to the device. File copies
+ are initiated from the NXOS device to the remote SCP server. This module only supports
+ the use of connection C(network_cli) or C(Cli) transport with connection C(local).
+version_added: 1.0.0
+author:
+- Jason Edelman (@jedelman8)
+- Gabriele Gerbino (@GGabriele)
+- Rewritten as a plugin by (@mikewiebe)
+notes:
+- Tested against NXOS 7.0(3)I2(5), 7.0(3)I4(6), 7.0(3)I5(3), 7.0(3)I6(1), 7.0(3)I7(3),
+ 6.0(2)A8(8), 7.0(3)F3(4), 7.3(0)D1(1), 8.3(0), 9.2, 9.3
+- Limited Support for Cisco MDS
+- When pushing files (file_pull is False) to the NXOS device, feature scp-server must
+ be enabled.
+- When pulling files (file_pull is True) to the NXOS device, feature scp-server is
+ not required.
+- When pulling files (file_pull is True) to the NXOS device, no transfer will take
+ place if the file is already present.
+- Check mode will tell you if the file would be copied.
+requirements:
+- paramiko or libssh (required when file_pull is False)
+- scp (required when file_pull is False)
+options:
+ local_file:
+ description:
+ - When (file_pull is False) this is the path to the local file on the Ansible
+ controller. The local directory must exist.
+ - When (file_pull is True) this is the target file name on the NXOS device.
+ type: path
+ remote_file:
+ description:
+ - When (file_pull is False) this is the remote file path on the NXOS device. If
+ omitted, the name of the local file will be used. The remote directory must
+ exist.
+ - When (file_pull is True) this is the full path to the file on the remote SCP
+ server to be copied to the NXOS device.
+ type: path
+ file_system:
+ description:
+ - The remote file system on the nxos device. If omitted, devices that support
+ a I(file_system) parameter will use their default values.
+ default: 'bootflash:'
+ type: str
+ connect_ssh_port:
+ description:
+ - B(Deprecated)
+ - This option has been deprecated and will be removed in a release after 2024-06-01.
+ - To maintain backwards compatibility, this option will continue to override the value of I(ansible_port) until removed.
+ - HORIZONTALLINE
+ - SSH server port used for file transfer.
+ - Only used when I(file_pull) is C(True).
+ default: 22
+ type: int
+ file_pull:
+ description:
+ - When (False) file is copied from the Ansible controller to the NXOS device.
+ - When (True) file is copied from a remote SCP server to the NXOS device. In this
+ mode, the file copy is initiated from the NXOS device.
+ - If the file is already present on the device it will be overwritten and therefore
+ the operation is NOT idempotent.
+ type: bool
+ default: false
+ file_pull_protocol:
+ description:
+ - When file_pull is True, this can be used to define the transfer protocol for
+ copying file from remote to the NXOS device.
+ - When (file_pull is False), this is not used.
+ default: 'scp'
+ choices:
+ - scp
+ - sftp
+ - ftp
+ - http
+ - https
+ - tftp
+ type: str
+ file_pull_compact:
+ description:
+ - When file_pull is True, this is used to compact nxos image files. This option
+ can only be used with nxos image files.
+ - When (file_pull is False), this is not used.
+ type: bool
+ default: false
+ file_pull_kstack:
+ description:
+ - When file_pull is True, this can be used to speed up file copies when the nxos
+ running image supports the use-kstack option.
+ - When (file_pull is False), this is not used.
+ type: bool
+ default: false
+ local_file_directory:
+ description:
+ - When (file_pull is True) file is copied from a remote SCP server to the NXOS
+ device, and written to this directory on the NXOS device. If the directory does
+ not exist, it will be created under the file_system. This is an optional parameter.
+ - When (file_pull is False), this is not used.
+ type: path
+ file_pull_timeout:
+ description:
+ - B(Deprecated)
+ - This option has been deprecated and will be removed in a release after 2024-06-01.
+ - To maintain backwards compatibility, this option will continue to override the value of I(ansible_command_timeout) until removed.
+ - HORIZONTALLINE
+ - Use this parameter to set timeout in seconds, when transferring large files
+ or when the network is slow.
+ - When (file_pull is False), this is not used.
+ default: 300
+ type: int
+ remote_scp_server:
+ description:
+ - The remote scp server address when file_pull is True. This is required if file_pull
+ is True.
+ - When (file_pull is False), this is not used.
+ type: str
+ remote_scp_server_user:
+ description:
+ - The remote scp server username when file_pull is True. This is required if file_pull
+ is True.
+ - When (file_pull is False), this is not used.
+ type: str
+ remote_scp_server_password:
+ description:
+ - The remote scp server password when file_pull is True. This is required if file_pull
+ is True.
+ - When (file_pull is False), this is not used.
+ type: str
+ vrf:
+ description:
+ - The VRF used to pull the file. Useful when no vrf management is defined.
+ - This option is not applicable for MDS switches.
+ default: management
+ type: str
+"""
+
+EXAMPLES = """
+# File copy from ansible controller to nxos device
+- name: copy from server to device
+ cisco.nxos.nxos_file_copy:
+ local_file: ./test_file.txt
+ remote_file: test_file.txt
+
+# Initiate file copy from the nxos device to transfer file from an SCP server back to the nxos device
+- name: initiate file copy from device
+ cisco.nxos.nxos_file_copy:
+ file_pull: true
+ local_file: xyz
+ local_file_directory: dir1/dir2/dir3
+ remote_file: /mydir/abc
+ remote_scp_server: 192.168.0.1
+ remote_scp_server_user: myUser
+ remote_scp_server_password: myPassword
+ vrf: management
+
+# Initiate file copy from the nxos device to transfer file from a ftp server back to the nxos device.
+# remote_scp_server_user and remote_scp_server_password are used to login to the FTP server.
+- name: initiate file copy from device
+ cisco.nxos.nxos_file_copy:
+ file_pull: true
+ file_pull_protocol: ftp
+ local_file: xyz
+ remote_file: /mydir/abc
+ remote_scp_server: 192.168.0.1
+ remote_scp_server_user: myUser
+ remote_scp_server_password: myPassword
+ vrf: management
+"""
+
+RETURN = """
+transfer_status:
+ description: Whether a file was transferred to the nxos device.
+ returned: success
+ type: str
+ sample: 'Sent'
+local_file:
+ description: The path of the local file.
+ returned: success
+ type: str
+ sample: '/path/to/local/file'
+remote_file:
+ description: The path of the remote file.
+ returned: success
+ type: str
+ sample: '/path/to/remote/file'
+remote_scp_server:
+ description: The name of the scp server when file_pull is True.
+ returned: success
+ type: str
+ sample: 'fileserver.example.com'
+changed:
+ description: Indicates whether or not the file was copied.
+ returned: success
+ type: bool
+ sample: true
+"""
+
+import hashlib
+import os
+import re
+
+from ansible.module_utils._text import to_bytes, to_text
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.network import (
+ get_resource_connection,
+)
+
+
+class FileCopy:
+ def __init__(self, module):
+ self._module = module
+ self._connection = get_resource_connection(self._module)
+ device_info = self._connection.get_device_info()
+ self._model = device_info.get("network_os_model", "")
+ self._platform = device_info.get("network_os_platform", "")
+
+
+class FilePush(FileCopy):
+ def __init__(self, module):
+ super(FilePush, self).__init__(module)
+ self.result = {}
+
+ def md5sum_check(self, dst, file_system):
+ command = "show file {0}{1} md5sum".format(file_system, dst)
+ remote_filehash = self._connection.run_commands(command)[0]
+ remote_filehash = to_bytes(remote_filehash, errors="surrogate_or_strict")
+
+ local_file = self._module.params["local_file"]
+ try:
+ with open(local_file, "rb") as f:
+ filecontent = f.read()
+ except (OSError, IOError) as exc:
+ self._module.fail_json("Error reading the file: {0}".format(to_text(exc)))
+
+ filecontent = to_bytes(filecontent, errors="surrogate_or_strict")
+ local_filehash = hashlib.md5(filecontent).hexdigest()
+
+ decoded_rhash = remote_filehash.decode("UTF-8")
+
+ if local_filehash == decoded_rhash:
+ return True
+ else:
+ return False
+
+ def remote_file_exists(self, remote_file, file_system):
+ command = "dir {0}/{1}".format(file_system, remote_file)
+ body = self._connection.run_commands(command)[0]
+
+ if "No such file" in body:
+ return False
+ else:
+ return self.md5sum_check(remote_file, file_system)
+
+ def get_flash_size(self, file_system):
+ command = "dir {0}".format(file_system)
+ body = self._connection.run_commands(command)[0]
+
+ match = re.search(r"(\d+) bytes free", body)
+ if match:
+ bytes_free = match.group(1)
+ return int(bytes_free)
+
+ match = re.search(r"No such file or directory", body)
+ if match:
+ self._module.fail_json("Invalid nxos filesystem {0}".format(file_system))
+ else:
+ self._module.fail_json("Unable to determine size of filesystem {0}".format(file_system))
+
+ def enough_space(self, file, file_system):
+ flash_size = self.get_flash_size(file_system)
+ file_size = os.path.getsize(file)
+ if file_size > flash_size:
+ return False
+
+ return True
+
+ def transfer_file_to_device(self, remote_file):
+ local_file = self._module.params["local_file"]
+ file_system = self._module.params["file_system"]
+
+ if not self.enough_space(local_file, file_system):
+ self._module.fail_json("Could not transfer file. Not enough space on device.")
+
+ # frp = full_remote_path, flp = full_local_path
+ frp = remote_file
+ if not file_system.startswith("bootflash:"):
+ frp = "{0}{1}".format(file_system, remote_file)
+ flp = os.path.join(os.path.abspath(local_file))
+
+ try:
+ self._connection.copy_file(
+ source=flp,
+ destination=frp,
+ proto="scp",
+ timeout=self._connection.get_option("persistent_command_timeout"),
+ )
+ self.result["transfer_status"] = "Sent: File copied to remote device."
+ except Exception as exc:
+ self.result["failed"] = True
+ self.result["msg"] = "Exception received : %s" % exc
+
+ def run(self):
+ local_file = self._module.params["local_file"]
+ remote_file = self._module.params["remote_file"] or os.path.basename(local_file)
+ file_system = self._module.params["file_system"]
+
+ if not os.path.isfile(local_file):
+ self._module.fail_json("Local file {0} not found".format(local_file))
+
+ remote_file = remote_file or os.path.basename(local_file)
+ remote_exists = self.remote_file_exists(remote_file, file_system)
+
+ if not remote_exists:
+ self.result["changed"] = True
+ file_exists = False
+ else:
+ self.result["transfer_status"] = "No Transfer: File already copied to remote device."
+ file_exists = True
+
+ if not self._module.check_mode and not file_exists:
+ self.transfer_file_to_device(remote_file)
+
+ self.result["local_file"] = local_file
+ if remote_file is None:
+ remote_file = os.path.basename(local_file)
+ self.result["remote_file"] = remote_file
+ self.result["file_system"] = file_system
+
+ return self.result
+
+
+class FilePull(FileCopy):
+ def __init__(self, module):
+ super(FilePull, self).__init__(module)
+ self.result = {}
+
+ def mkdir(self, directory):
+ local_dir_root = "/"
+ dir_array = directory.split("/")
+ for each in dir_array:
+ if each:
+ mkdir_cmd = "mkdir " + local_dir_root + each
+ self._connection.run_commands(mkdir_cmd)
+ local_dir_root += each + "/"
+ return local_dir_root
+
+ def copy_file_from_remote(self, local, local_file_directory, file_system):
+ # Build copy command components that will be used to initiate copy from the nxos device.
+ cmdroot = "copy " + self._module.params["file_pull_protocol"] + "://"
+ ruser = self._module.params["remote_scp_server_user"] + "@"
+ rserver = self._module.params["remote_scp_server"]
+ rserverpassword = self._module.params["remote_scp_server_password"]
+ rfile = self._module.params["remote_file"] + " "
+ if not rfile.startswith("/"):
+ rfile = "/" + rfile
+
+ if not self._platform.startswith("DS-") and "MDS" not in self._model:
+ vrf = " vrf " + self._module.params["vrf"]
+ else:
+ vrf = ""
+ if self._module.params["file_pull_compact"]:
+ compact = " compact "
+ else:
+ compact = ""
+ if self._module.params["file_pull_kstack"]:
+ kstack = " use-kstack "
+ else:
+ kstack = ""
+
+ # Create local file directory under NX-OS filesystem if
+ # local_file_directory playbook parameter is set.
+ local_dir_root = "/"
+ if local_file_directory:
+ local_dir_root = self.mkdir(local_file_directory)
+
+ copy_cmd = (
+ cmdroot
+ + ruser
+ + rserver
+ + rfile
+ + file_system
+ + local_dir_root
+ + local
+ + compact
+ + vrf
+ + kstack
+ )
+
+ self.result["copy_cmd"] = copy_cmd
+ pulled = self._connection.pull_file(command=copy_cmd, remotepassword=rserverpassword)
+ if pulled:
+ self.result[
+ "transfer_status"
+ ] = "Received: File copied/pulled to nxos device from remote scp server."
+ else:
+ self.result["failed"] = True
+
+ def run(self):
+ self.result["failed"] = False
+ remote_file = self._module.params["remote_file"]
+ local_file = self._module.params["local_file"] or remote_file.split("/")[-1]
+ file_system = self._module.params["file_system"]
+ # Note: This is the local file directory on the remote nxos device.
+ local_file_dir = self._module.params["local_file_directory"]
+
+ if not self._module.check_mode:
+ self.copy_file_from_remote(local_file, local_file_dir, file_system)
+
+ self.result["remote_file"] = remote_file
+ if local_file_dir:
+ dir = local_file_dir
+ else:
+ dir = ""
+ self.result["local_file"] = file_system + dir + "/" + local_file
+ self.result["remote_scp_server"] = self._module.params["remote_scp_server"]
+ self.result["file_system"] = self._module.params["file_system"]
+
+ if not self.result["failed"]:
+ self.result["changed"] = True
+
+ return self.result
+
+
+def main():
+ argument_spec = dict(
+ vrf=dict(type="str", default="management"),
+ connect_ssh_port=dict(type="int", default=22),
+ file_system=dict(type="str", default="bootflash:"),
+ file_pull=dict(type="bool", default=False),
+ file_pull_timeout=dict(type="int", default=300),
+ file_pull_protocol=dict(
+ type="str",
+ default="scp",
+ choices=["scp", "sftp", "http", "https", "tftp", "ftp"],
+ ),
+ file_pull_compact=dict(type="bool", default=False),
+ file_pull_kstack=dict(type="bool", default=False),
+ local_file=dict(type="path"),
+ local_file_directory=dict(type="path"),
+ remote_file=dict(type="path"),
+ remote_scp_server=dict(type="str"),
+ remote_scp_server_user=dict(type="str"),
+ remote_scp_server_password=dict(no_log=True),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ required_if=[("file_pull", True, ("remote_file", "remote_scp_server"))],
+ required_together=[("remote_scp_server", "remote_scp_server_user")],
+ supports_check_mode=True,
+ )
+
+ file_pull = module.params["file_pull"]
+
+ warnings = list()
+
+ if file_pull:
+ result = FilePull(module).run()
+ else:
+ result = FilePush(module).run()
+
+ result["warnings"] = warnings
+
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_gir.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_gir.py
new file mode 100644
index 00000000..c473ff5a
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_gir.py
@@ -0,0 +1,342 @@
+#!/usr/bin/python
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_gir
+extends_documentation_fragment:
+- cisco.nxos.nxos
+short_description: Trigger a graceful removal or insertion (GIR) of the switch.
+description:
+- Trigger a graceful removal or insertion (GIR) of the switch.
+- GIR processing may take more than 2 minutes. Timeout settings are automatically
+ extended to 200s when user timeout settings are insufficient.
+version_added: 1.0.0
+author:
+- Gabriele Gerbino (@GGabriele)
+notes:
+- Tested against NXOSv 7.3.(0)D1(1) on VIRL
+- Unsupported for Cisco MDS
+- C(state) has effect only in combination with C(system_mode_maintenance_timeout)
+ or C(system_mode_maintenance_on_reload_reset_reason).
+- Using C(system_mode_maintenance) and C(system_mode_maintenance_dont_generate_profile)
+ would make the module fail, but the system mode will be triggered anyway.
+options:
+ system_mode_maintenance:
+ description:
+ - When C(system_mode_maintenance=true) it puts all enabled protocols in maintenance
+ mode (using the isolate command). When C(system_mode_maintenance=false) it puts
+ all enabled protocols in normal mode (using the no isolate command).
+ type: bool
+ system_mode_maintenance_dont_generate_profile:
+ description:
+ - When C(system_mode_maintenance_dont_generate_profile=true) it prevents the dynamic
+ searching of enabled protocols and executes commands configured in a maintenance-mode
+ profile. Use this option if you want the system to use a maintenance-mode profile
+ that you have created. When C(system_mode_maintenance_dont_generate_profile=false)
+ it prevents the dynamic searching of enabled protocols and executes commands
+ configured in a normal-mode profile. Use this option if you want the system
+ to use a normal-mode profile that you have created.
+ type: bool
+ system_mode_maintenance_timeout:
+ description:
+ - Keeps the switch in maintenance mode for a specified number of minutes. Range
+ is 5-65535.
+ type: str
+ system_mode_maintenance_shutdown:
+ description:
+ - Shuts down all protocols, vPC domains, and interfaces except the management
+ interface (using the shutdown command). This option is disruptive while C(system_mode_maintenance)
+ (which uses the isolate command) is not.
+ type: bool
+ system_mode_maintenance_on_reload_reset_reason:
+ description:
+ - Boots the switch into maintenance mode automatically in the event of a specified
+ system crash. Note that not all reset reasons are applicable for all platforms.
+ Also if reset reason is set to match_any, it is not idempotent as it turns on
+ all reset reasons. If reset reason is match_any and state is absent, it turns
+ off all the reset reasons.
+ choices:
+ - hw_error
+ - svc_failure
+ - kern_failure
+ - wdog_timeout
+ - fatal_error
+ - lc_failure
+ - match_any
+ - manual_reload
+ - any_other
+ - maintenance
+ type: str
+ state:
+ description:
+ - Specify desired state of the resource.
+ default: present
+ choices:
+ - present
+ - absent
+ type: str
+"""
+
+EXAMPLES = """
+# Trigger system maintenance mode
+- cisco.nxos.nxos_gir:
+ system_mode_maintenance: true
+ host: '{{ inventory_hostname }}'
+ username: '{{ un }}'
+ password: '{{ pwd }}'
+# Trigger system normal mode
+- cisco.nxos.nxos_gir:
+ system_mode_maintenance: false
+ host: '{{ inventory_hostname }}'
+ username: '{{ un }}'
+ password: '{{ pwd }}'
+# Configure on-reload reset-reason for maintenance mode
+- cisco.nxos.nxos_gir:
+ system_mode_maintenance_on_reload_reset_reason: manual_reload
+ state: present
+ host: '{{ inventory_hostname }}'
+ username: '{{ un }}'
+ password: '{{ pwd }}'
+# Add on-reload reset-reason for maintenance mode
+- cisco.nxos.nxos_gir:
+ system_mode_maintenance_on_reload_reset_reason: hw_error
+ state: present
+ host: '{{ inventory_hostname }}'
+ username: '{{ un }}'
+ password: '{{ pwd }}'
+# Remove on-reload reset-reason for maintenance mode
+- cisco.nxos.nxos_gir:
+ system_mode_maintenance_on_reload_reset_reason: manual_reload
+ state: absent
+ host: '{{ inventory_hostname }}'
+ username: '{{ un }}'
+ password: '{{ pwd }}'
+# Set timeout for maintenance mode
+- cisco.nxos.nxos_gir:
+ system_mode_maintenance_timeout: 30
+ state: present
+ host: '{{ inventory_hostname }}'
+ username: '{{ un }}'
+ password: '{{ pwd }}'
+# Remove timeout for maintenance mode
+- cisco.nxos.nxos_gir:
+ system_mode_maintenance_timeout: 30
+ state: absent
+ host: '{{ inventory_hostname }}'
+ username: '{{ un }}'
+ password: '{{ pwd }}'
+"""
+
+RETURN = """
+final_system_mode:
+ description: describe the last system mode
+ returned: verbose mode
+ type: str
+ sample: normal
+updates:
+ description: commands sent to the device
+ returned: verbose mode
+ type: list
+ sample: ["terminal dont-ask", "system mode maintenance timeout 10"]
+changed:
+ description: check to see if a change was made on the device
+ returned: always
+ type: bool
+ sample: true
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ load_config,
+ run_commands,
+)
+
+
+def get_system_mode(module):
+ command = {"command": "show system mode", "output": "text"}
+ body = run_commands(module, [command])[0]
+ if body and "normal" in body.lower():
+ mode = "normal"
+ else:
+ mode = "maintenance"
+ return mode
+
+
+def get_maintenance_timeout(module):
+ command = {"command": "show maintenance timeout", "output": "text"}
+ body = run_commands(module, [command])[0]
+ timeout = body.split()[4]
+ return timeout
+
+
+def get_reset_reasons(module):
+ command = {
+ "command": "show maintenance on-reload reset-reasons",
+ "output": "text",
+ }
+ body = run_commands(module, [command])[0]
+ return body
+
+
+def get_commands(module, state, mode):
+ commands = list()
+ if module.params["system_mode_maintenance"] is True and mode == "normal":
+ commands.append("system mode maintenance")
+ elif module.params["system_mode_maintenance"] is False and mode == "maintenance":
+ commands.append("no system mode maintenance")
+
+ elif (
+ module.params["system_mode_maintenance_dont_generate_profile"] is True and mode == "normal"
+ ):
+ commands.append("system mode maintenance dont-generate-profile")
+ elif (
+ module.params["system_mode_maintenance_dont_generate_profile"] is False
+ and mode == "maintenance"
+ ):
+ commands.append("no system mode maintenance dont-generate-profile")
+
+ elif module.params["system_mode_maintenance_timeout"]:
+ timeout = get_maintenance_timeout(module)
+ if state == "present" and timeout != module.params["system_mode_maintenance_timeout"]:
+ commands.append(
+ "system mode maintenance timeout {0}".format(
+ module.params["system_mode_maintenance_timeout"],
+ ),
+ )
+ elif state == "absent" and timeout == module.params["system_mode_maintenance_timeout"]:
+ commands.append(
+ "no system mode maintenance timeout {0}".format(
+ module.params["system_mode_maintenance_timeout"],
+ ),
+ )
+
+ elif module.params["system_mode_maintenance_shutdown"] and mode == "normal":
+ commands.append("system mode maintenance shutdown")
+ elif module.params["system_mode_maintenance_shutdown"] is False and mode == "maintenance":
+ commands.append("no system mode maintenance")
+
+ elif module.params["system_mode_maintenance_on_reload_reset_reason"]:
+ reset_reasons = get_reset_reasons(module)
+ if (
+ state == "present"
+ and module.params["system_mode_maintenance_on_reload_reset_reason"].lower()
+ not in reset_reasons.lower()
+ ):
+ commands.append(
+ "system mode maintenance on-reload "
+ "reset-reason {0}".format(
+ module.params["system_mode_maintenance_on_reload_reset_reason"],
+ ),
+ )
+ elif (
+ state == "absent"
+ and module.params["system_mode_maintenance_on_reload_reset_reason"].lower()
+ in reset_reasons.lower()
+ ):
+ commands.append(
+ "no system mode maintenance on-reload "
+ "reset-reason {0}".format(
+ module.params["system_mode_maintenance_on_reload_reset_reason"],
+ ),
+ )
+
+ if commands:
+ commands.insert(0, "terminal dont-ask")
+ return commands
+
+
+def main():
+ argument_spec = dict(
+ system_mode_maintenance=dict(required=False, type="bool"),
+ system_mode_maintenance_dont_generate_profile=dict(required=False, type="bool"),
+ system_mode_maintenance_timeout=dict(required=False, type="str"),
+ system_mode_maintenance_shutdown=dict(required=False, type="bool"),
+ system_mode_maintenance_on_reload_reset_reason=dict(
+ required=False,
+ choices=[
+ "hw_error",
+ "svc_failure",
+ "kern_failure",
+ "wdog_timeout",
+ "fatal_error",
+ "lc_failure",
+ "match_any",
+ "manual_reload",
+ "any_other",
+ "maintenance",
+ ],
+ ),
+ state=dict(choices=["absent", "present"], default="present", required=False),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ mutually_exclusive=[
+ [
+ "system_mode_maintenance",
+ "system_mode_maintenance_dont_generate_profile",
+ "system_mode_maintenance_timeout",
+ "system_mode_maintenance_shutdown",
+ "system_mode_maintenance_on_reload_reset_reason",
+ ],
+ ],
+ required_one_of=[
+ [
+ "system_mode_maintenance",
+ "system_mode_maintenance_dont_generate_profile",
+ "system_mode_maintenance_timeout",
+ "system_mode_maintenance_shutdown",
+ "system_mode_maintenance_on_reload_reset_reason",
+ ],
+ ],
+ supports_check_mode=True,
+ )
+
+ warnings = list()
+
+ state = module.params["state"]
+ mode = get_system_mode(module)
+ commands = get_commands(module, state, mode)
+ changed = False
+ if commands:
+ if module.check_mode:
+ module.exit_json(changed=True, commands=commands)
+ else:
+ load_config(module, commands)
+ changed = True
+
+ result = {}
+ result["changed"] = changed
+ if module._verbosity > 0:
+ final_system_mode = get_system_mode(module)
+ result["final_system_mode"] = final_system_mode
+ result["updates"] = commands
+
+ result["warnings"] = warnings
+
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_gir_profile_management.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_gir_profile_management.py
new file mode 100644
index 00000000..84cfc145
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_gir_profile_management.py
@@ -0,0 +1,214 @@
+#!/usr/bin/python
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_gir_profile_management
+extends_documentation_fragment:
+- cisco.nxos.nxos
+short_description: Create a maintenance-mode or normal-mode profile for GIR.
+description:
+- Manage a maintenance-mode or normal-mode profile with configuration commands that
+ can be applied during graceful removal or graceful insertion.
+version_added: 1.0.0
+author:
+- Gabriele Gerbino (@GGabriele)
+notes:
+- Tested against NXOSv 7.3.(0)D1(1) on VIRL
+- Unsupported for Cisco MDS
+- C(state=absent) removes the whole profile.
+options:
+ commands:
+ description:
+ - List of commands to be included into the profile.
+ type: list
+ elements: str
+ mode:
+ description:
+ - Configure the profile as Maintenance or Normal mode.
+ required: true
+ choices:
+ - maintenance
+ - normal
+ type: str
+ state:
+ description:
+ - Specify desired state of the resource.
+ default: present
+ choices:
+ - present
+ - absent
+ type: str
+"""
+
+EXAMPLES = """
+# Create a maintenance-mode profile
+- cisco.nxos.nxos_gir_profile_management:
+ mode: maintenance
+ commands:
+ - router eigrp 11
+ - isolate
+
+# Remove the maintenance-mode profile
+- cisco.nxos.nxos_gir_profile_management:
+ mode: maintenance
+ state: absent
+"""
+
+RETURN = """
+proposed:
+ description: list of commands passed into module.
+ returned: verbose mode
+ type: list
+ sample: ["router eigrp 11", "isolate"]
+existing:
+ description: list of existing profile commands.
+ returned: verbose mode
+ type: list
+ sample: ["router bgp 65535","isolate","router eigrp 10","isolate",
+ "diagnostic bootup level complete"]
+end_state:
+ description: list of profile entries after module execution.
+ returned: verbose mode
+ type: list
+ sample: ["router bgp 65535","isolate","router eigrp 10","isolate",
+ "diagnostic bootup level complete","router eigrp 11", "isolate"]
+updates:
+ description: commands sent to the device
+ returned: always
+ type: list
+ sample: ["configure maintenance profile maintenance-mode",
+ "router eigrp 11","isolate"]
+changed:
+ description: check to see if a change was made on the device
+ returned: always
+ type: bool
+ sample: true
+"""
+
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import (
+ CustomNetworkConfig,
+)
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ get_config,
+ load_config,
+)
+
+
+def get_existing(module):
+ existing = []
+ netcfg = CustomNetworkConfig(indent=2, contents=get_config(module))
+
+ if module.params["mode"] == "maintenance":
+ parents = ["configure maintenance profile maintenance-mode"]
+ else:
+ parents = ["configure maintenance profile normal-mode"]
+
+ config = netcfg.get_section(parents)
+ if config:
+ existing = config.splitlines()
+ existing = [cmd.strip() for cmd in existing]
+ existing.pop(0)
+
+ return existing
+
+
+def state_present(module, existing, commands):
+ cmds = list()
+ if existing == commands:
+ # Idempotent case
+ return cmds
+ cmds.extend(commands)
+ if module.params["mode"] == "maintenance":
+ cmds.insert(0, "configure maintenance profile maintenance-mode")
+ else:
+ cmds.insert(0, "configure maintenance profile normal-mode")
+
+ return cmds
+
+
+def state_absent(module, existing, commands):
+ if module.params["mode"] == "maintenance":
+ cmds = ["no configure maintenance profile maintenance-mode"]
+ else:
+ cmds = ["no configure maintenance profile normal-mode"]
+ return cmds
+
+
+def invoke(name, *args, **kwargs):
+ func = globals().get(name)
+ if func:
+ return func(*args, **kwargs)
+
+
+def main():
+ argument_spec = dict(
+ commands=dict(required=False, type="list", elements="str"),
+ mode=dict(required=True, choices=["maintenance", "normal"]),
+ state=dict(choices=["absent", "present"], default="present"),
+ )
+
+ module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)
+
+ warnings = list()
+
+ state = module.params["state"]
+ commands = module.params["commands"] or []
+
+ if state == "absent" and commands:
+ module.fail_json(msg="when state is absent, no command can be used.")
+
+ existing = invoke("get_existing", module)
+ end_state = existing
+ changed = False
+
+ result = {}
+ cmds = []
+ if state == "present" or (state == "absent" and existing):
+ cmds = invoke("state_%s" % state, module, existing, commands)
+
+ if module.check_mode:
+ module.exit_json(changed=True, commands=cmds)
+ else:
+ if cmds:
+ load_config(module, cmds)
+ changed = True
+ end_state = invoke("get_existing", module)
+
+ result["changed"] = changed
+ if module._verbosity > 0:
+ end_state = invoke("get_existing", module)
+ result["end_state"] = end_state
+ result["existing"] = existing
+ result["proposed"] = commands
+ result["updates"] = cmds
+
+ result["warnings"] = warnings
+
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_hostname.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_hostname.py
new file mode 100644
index 00000000..42e45677
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_hostname.py
@@ -0,0 +1,229 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# Copyright 2022 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+"""
+The module file for nxos_hostname
+"""
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+DOCUMENTATION = """
+module: nxos_hostname
+short_description: Hostname resource module.
+description:
+- This module manages hostname configuration on devices running Cisco NX-OS.
+version_added: 2.9.0
+notes:
+- Tested against NX-OS 9.3.6.
+- This module works with connection C(network_cli) and C(httpapi).
+author: Nilashish Chakraborty (@NilashishC)
+options:
+ running_config:
+ description:
+ - This option is used only with state I(parsed).
+ - The value of this option should be the output received from the NX-OS device
+ by executing the command B(show running-config | section hostname).
+ - The state I(parsed) reads the configuration from C(running_config) option and
+ transforms it into Ansible structured data as per the resource module's argspec
+ and the value is then returned in the I(parsed) key within the result.
+ type: str
+ config:
+ description: A dictionary of hostname configuration.
+ type: dict
+ suboptions:
+ hostname:
+ description: Hostname of the device.
+ type: str
+ state:
+ description:
+ - The state the configuration should be left in.
+ - The states I(merged), I(replaced) and I(overridden) have identical
+ behaviour for this module.
+ - Refer to examples for more details.
+ type: str
+ choices:
+ - merged
+ - replaced
+ - overridden
+ - deleted
+ - parsed
+ - gathered
+ - rendered
+ default: merged
+"""
+
+EXAMPLES = """
+# Using merged (replaced, overridden has the same behaviour)
+
+# Before state:
+# -------------
+# nxos-9k-rdo# show running-config | section ^hostname
+# nxos-9k-rdo#
+
+- name: Merge the provided configuration with the existing running configuration
+ cisco.nxos.nxos_hostname:
+ config:
+ hostname: NXOSv-9k
+
+# Task output
+# -------------
+# before: {}
+#
+# commands:
+# - hostname NXOSv-9k
+#
+# after:
+# hostname: NXOSv-9k
+
+# After state:
+# ------------
+# nxos-9k-rdo# show running-config | section ^hostname
+# hostname NXOSv-9k
+#
+
+# Using deleted
+
+# Before state:
+# ------------
+# nxos-9k-rdo# show running-config | section ^hostname
+# hostname NXOSv-9k
+
+- name: Delete hostname from running-config
+ cisco.nxos.nxos_hostname:
+ state: deleted
+
+# Task output
+# -------------
+# before:
+# hostname: NXOSv-9k
+#
+# commands:
+# - no hostname NXOSv-9k
+#
+# after: {}
+
+# Using gathered
+
+- name: Gather hostname facts using gathered
+ cisco.nxos.nxos_hostname:
+ state: gathered
+
+# Task output (redacted)
+# -----------------------
+# gathered:
+# hostname: NXOSv-9k
+
+# Using rendered
+
+- name: Render platform specific configuration lines (without connecting to the device)
+ cisco.nxos.nxos_hostname:
+ config:
+ hostname: NXOSv-9k
+
+# Task Output (redacted)
+# -----------------------
+# rendered:
+# - hostname NXOSv-9k
+
+# Using parsed
+
+# parsed.cfg
+# ------------
+# hostname NXOSv-9k
+
+- name: Parse externally provided hostname config
+ cisco.nxos.nxos_hostname:
+ running_config: "{{ lookup('file', 'parsed.cfg') }}"
+ state: parsed
+
+# Task output (redacted)
+# -----------------------
+# parsed:
+# hostname: NXOSv-9k
+
+"""
+
+RETURN = """
+before:
+ description: The configuration prior to the module execution.
+ returned: when I(state) is C(merged), C(replaced), C(overridden), C(deleted) or C(purged)
+ type: dict
+ sample: >
+ This output will always be in the same format as the
+ module argspec.
+after:
+ description: The resulting configuration after module execution.
+ returned: when changed
+ type: dict
+ sample: >
+ This output will always be in the same format as the
+ module argspec.
+commands:
+ description: The set of commands pushed to the remote device.
+ returned: when I(state) is C(merged), C(replaced), C(overridden), C(deleted) or C(purged)
+ type: list
+ sample:
+ - hostname switch01
+rendered:
+ description: The provided configuration in the task rendered in device-native format (offline).
+ returned: when I(state) is C(rendered)
+ type: list
+ sample:
+ - hostname switch01
+gathered:
+ description: Facts about the network resource gathered from the remote device as structured data.
+ returned: when I(state) is C(gathered)
+ type: list
+ sample: >
+ This output will always be in the same format as the
+ module argspec.
+parsed:
+ description: The device native config provided in I(running_config) option parsed into structured data as per module argspec.
+ returned: when I(state) is C(parsed)
+ type: list
+ sample: >
+ This output will always be in the same format as the
+ module argspec.
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.hostname.hostname import (
+ HostnameArgs,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.config.hostname.hostname import (
+ Hostname,
+)
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ module = AnsibleModule(
+ argument_spec=HostnameArgs.argument_spec,
+ mutually_exclusive=[["config", "running_config"]],
+ required_if=[
+ ["state", "merged", ["config"]],
+ ["state", "replaced", ["config"]],
+ ["state", "overridden", ["config"]],
+ ["state", "rendered", ["config"]],
+ ["state", "parsed", ["running_config"]],
+ ],
+ supports_check_mode=True,
+ )
+
+ result = Hostname(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_hsrp.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_hsrp.py
new file mode 100644
index 00000000..ee59b7dd
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_hsrp.py
@@ -0,0 +1,506 @@
+#!/usr/bin/python
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+
+DOCUMENTATION = """
+module: nxos_hsrp
+extends_documentation_fragment:
+- cisco.nxos.nxos
+short_description: Manages HSRP configuration on NX-OS switches.
+description:
+- Manages HSRP configuration on NX-OS switches.
+version_added: 1.0.0
+author:
+- Jason Edelman (@jedelman8)
+- Gabriele Gerbino (@GGabriele)
+notes:
+- Tested against NXOSv 7.3.(0)D1(1) on VIRL
+- Unsupported for Cisco MDS
+- HSRP feature needs to be enabled first on the system.
+- SVIs must exist before using this module.
+- Interface must be a L3 port before using this module.
+- HSRP cannot be configured on loopback interfaces.
+- MD5 authentication is only possible with HSRPv2 while it is ignored if HSRPv1 is
+ used instead, while it will not raise any error. Here we allow MD5 authentication
+ only with HSRPv2 in order to enforce better practice.
+options:
+ group:
+ description:
+ - HSRP group number.
+ required: true
+ type: str
+ interface:
+ description:
+ - Full name of interface that is being managed for HSRP.
+ required: true
+ type: str
+ version:
+ description:
+ - HSRP version.
+ default: '1'
+ choices:
+ - '1'
+ - '2'
+ type: str
+ priority:
+ description:
+ - HSRP priority or keyword 'default'.
+ type: str
+ preempt:
+ description:
+ - Enable/Disable preempt.
+ choices:
+ - enabled
+ - disabled
+ type: str
+ vip:
+ description:
+ - HSRP virtual IP address or keyword 'default'
+ type: str
+ auth_string:
+ description:
+ - Authentication string. If this needs to be hidden(for md5 type), the string
+ should be 7 followed by the key string. Otherwise, it can be 0 followed by key
+ string or just key string (for backward compatibility). For text type, this
+ should be just be a key string. if this is 'default', authentication is removed.
+ type: str
+ auth_type:
+ description:
+ - Authentication type.
+ choices:
+ - text
+ - md5
+ type: str
+ state:
+ description:
+ - Specify desired state of the resource.
+ choices:
+ - present
+ - absent
+ default: present
+ type: str
+"""
+
+EXAMPLES = """
+- name: Ensure HSRP is configured with following params on a SVI
+ cisco.nxos.nxos_hsrp:
+ group: 10
+ vip: 10.1.1.1
+ priority: 150
+ interface: vlan10
+ preempt: enabled
+
+- name: Ensure HSRP is configured with following params on a SVI with clear text authentication
+ cisco.nxos.nxos_hsrp:
+ group: 10
+ vip: 10.1.1.1
+ priority: 150
+ interface: vlan10
+ preempt: enabled
+ auth_type: text
+ auth_string: CISCO
+
+- name: Ensure HSRP is configured with md5 authentication and clear authentication
+ string
+ cisco.nxos.nxos_hsrp:
+ group: 10
+ vip: 10.1.1.1
+ priority: 150
+ interface: vlan10
+ preempt: enabled
+ auth_type: md5
+ auth_string: 0 1234
+
+- name: Ensure HSRP is configured with md5 authentication and hidden authentication
+ string
+ cisco.nxos.nxos_hsrp:
+ group: 10
+ vip: 10.1.1.1
+ priority: 150
+ interface: vlan10
+ preempt: enabled
+ auth_type: md5
+ auth_string: 7 1234
+
+- name: Remove HSRP config for given interface, group, and VIP
+ cisco.nxos.nxos_hsrp:
+ group: 10
+ interface: vlan10
+ vip: 10.1.1.1
+ state: absent
+"""
+
+RETURN = r"""
+commands:
+ description: commands sent to the device
+ returned: always
+ type: list
+ sample: ["interface vlan10", "hsrp version 2", "hsrp 30", "ip 10.30.1.1"]
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ get_capabilities,
+ get_interface_type,
+ load_config,
+ run_commands,
+)
+
+
+PARAM_TO_DEFAULT_KEYMAP = {
+ "vip": None,
+ "priority": "100",
+ "auth_type": "text",
+ "auth_string": "cisco",
+}
+
+
+def apply_key_map(key_map, table):
+ new_dict = {}
+ for key in table:
+ new_key = key_map.get(key)
+ if new_key:
+ value = table.get(key)
+ if value:
+ new_dict[new_key] = str(value)
+ else:
+ new_dict[new_key] = value
+ return new_dict
+
+
+def get_interface_mode(interface, intf_type, module):
+ command = "show interface {0} | json".format(interface)
+ interface = {}
+ mode = "unknown"
+ try:
+ body = run_commands(module, [command])[0]
+ except IndexError:
+ return None
+
+ if intf_type in ["ethernet", "portchannel"]:
+ interface_table = body["TABLE_interface"]["ROW_interface"]
+ mode = str(interface_table.get("eth_mode", "layer3"))
+ if mode == "access" or mode == "trunk":
+ mode = "layer2"
+ elif intf_type == "svi":
+ mode = "layer3"
+ return mode
+
+
+def get_hsrp_group(group, interface, module):
+ command = "show hsrp group {0} all | json".format(group)
+ hsrp = {}
+
+ hsrp_key = {
+ "sh_if_index": "interface",
+ "sh_group_num": "group",
+ "sh_group_version": "version",
+ "sh_cfg_prio": "priority",
+ "sh_preempt": "preempt",
+ "sh_vip": "vip",
+ "sh_authentication_type": "auth_type",
+ "sh_keystring_attr": "auth_enc",
+ "sh_authentication_data": "auth_string",
+ }
+
+ try:
+ body = run_commands(module, [command])[0]
+ hsrp_table = body["TABLE_grp_detail"]["ROW_grp_detail"]
+ if "sh_keystring_attr" not in hsrp_table:
+ del hsrp_key["sh_keystring_attr"]
+ if "unknown enum:" in str(hsrp_table):
+ hsrp_table = get_hsrp_group_unknown_enum(module, command, hsrp_table)
+ except (AttributeError, IndexError, TypeError, KeyError):
+ return {}
+
+ if isinstance(hsrp_table, dict):
+ hsrp_table = [hsrp_table]
+
+ for hsrp_group in hsrp_table:
+ parsed_hsrp = apply_key_map(hsrp_key, hsrp_group)
+
+ parsed_hsrp["interface"] = parsed_hsrp["interface"].lower()
+
+ if parsed_hsrp["version"] == "v1":
+ parsed_hsrp["version"] = "1"
+ elif parsed_hsrp["version"] == "v2":
+ parsed_hsrp["version"] = "2"
+
+ if parsed_hsrp["auth_type"] == "md5":
+ if parsed_hsrp["auth_enc"] == "hidden":
+ parsed_hsrp["auth_enc"] = "7"
+ else:
+ parsed_hsrp["auth_enc"] = "0"
+
+ if parsed_hsrp["interface"] == interface:
+ return parsed_hsrp
+
+ return hsrp
+
+
+def get_hsrp_group_unknown_enum(module, command, hsrp_table):
+ """Some older NXOS images fail to set the attr values when using structured output and
+ instead set the values to <unknown enum>. This fallback method is a workaround that
+ uses an unstructured (text) request to query the device a second time.
+ 'sh_preempt' is currently the only attr affected. Add checks for other attrs as needed.
+ """
+ if "unknown enum:" in hsrp_table["sh_preempt"]:
+ cmd = {"output": "text", "command": command.split("|")[0]}
+ out = run_commands(module, cmd)[0]
+ hsrp_table["sh_preempt"] = "enabled" if ("may preempt" in out) else "disabled"
+ return hsrp_table
+
+
+def get_commands_remove_hsrp(group, interface):
+ commands = ["interface {0}".format(interface), "no hsrp {0}".format(group)]
+ return commands
+
+
+def get_commands_config_hsrp(delta, interface, args, existing):
+ commands = []
+
+ config_args = {
+ "group": "hsrp {group}",
+ "priority": "{priority}",
+ "preempt": "{preempt}",
+ "vip": "{vip}",
+ }
+
+ preempt = delta.get("preempt", None)
+ group = delta.get("group", None)
+ vip = delta.get("vip", None)
+ priority = delta.get("priority", None)
+
+ if preempt:
+ if preempt == "enabled":
+ delta["preempt"] = "preempt"
+ elif preempt == "disabled":
+ delta["preempt"] = "no preempt"
+
+ if priority:
+ if priority == "default":
+ if existing and existing.get("priority") != PARAM_TO_DEFAULT_KEYMAP.get("priority"):
+ delta["priority"] = "no priority"
+ else:
+ del delta["priority"]
+ else:
+ delta["priority"] = "priority {0}".format(delta["priority"])
+
+ if vip:
+ if vip == "default":
+ if existing and existing.get("vip") != PARAM_TO_DEFAULT_KEYMAP.get("vip"):
+ delta["vip"] = "no ip"
+ else:
+ del delta["vip"]
+ else:
+ delta["vip"] = "ip {0}".format(delta["vip"])
+
+ for key in delta:
+ command = config_args.get(key, "DNE").format(**delta)
+ if command and command != "DNE":
+ if key == "group":
+ commands.insert(0, command)
+ else:
+ commands.append(command)
+ command = None
+
+ auth_type = delta.get("auth_type", None)
+ auth_string = delta.get("auth_string", None)
+ auth_enc = delta.get("auth_enc", None)
+ if auth_type or auth_string:
+ if not auth_type:
+ auth_type = args["auth_type"]
+ elif not auth_string:
+ auth_string = args["auth_string"]
+ if auth_string != "default":
+ if auth_type == "md5":
+ command = "authentication md5 key-string {0} {1}".format(auth_enc, auth_string)
+ commands.append(command)
+ elif auth_type == "text":
+ command = "authentication text {0}".format(auth_string)
+ commands.append(command)
+ else:
+ if existing and existing.get("auth_string") != PARAM_TO_DEFAULT_KEYMAP.get(
+ "auth_string",
+ ):
+ commands.append("no authentication")
+
+ if commands and not group:
+ commands.insert(0, "hsrp {0}".format(args["group"]))
+
+ version = delta.get("version", None)
+ if version:
+ if version == "2":
+ command = "hsrp version 2"
+ elif version == "1":
+ command = "hsrp version 1"
+ commands.insert(0, command)
+ commands.insert(0, "interface {0}".format(interface))
+
+ if commands:
+ if not commands[0].startswith("interface"):
+ commands.insert(0, "interface {0}".format(interface))
+
+ return commands
+
+
+def is_default(interface, module):
+ command = "show run interface {0}".format(interface)
+
+ try:
+ body = run_commands(module, [command], check_rc=False)[0]
+ if "invalid" in body.lower():
+ return "DNE"
+ else:
+ raw_list = body.split("\n")
+ if raw_list[-1].startswith("interface"):
+ return True
+ else:
+ return False
+ except KeyError:
+ return "DNE"
+
+
+def validate_config(body, vip, module):
+ new_body = "".join(body)
+ if "invalid ip address" in new_body.lower():
+ module.fail_json(msg="Invalid VIP. Possible duplicate IP address.", vip=vip)
+
+
+def main():
+ argument_spec = dict(
+ group=dict(required=True, type="str"),
+ interface=dict(required=True),
+ version=dict(choices=["1", "2"], default="1", required=False),
+ priority=dict(type="str", required=False),
+ preempt=dict(type="str", choices=["disabled", "enabled"], required=False),
+ vip=dict(type="str", required=False),
+ auth_type=dict(choices=["text", "md5"], required=False),
+ auth_string=dict(type="str", required=False),
+ state=dict(choices=["absent", "present"], required=False, default="present"),
+ )
+
+ module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)
+
+ warnings = list()
+ results = dict(changed=False, warnings=warnings)
+
+ interface = module.params["interface"].lower()
+ group = module.params["group"]
+ version = module.params["version"]
+ state = module.params["state"]
+ priority = module.params["priority"]
+ preempt = module.params["preempt"]
+ vip = module.params["vip"]
+ auth_type = module.params["auth_type"]
+ auth_full_string = module.params["auth_string"]
+ auth_enc = "0"
+ auth_string = None
+ if auth_full_string:
+ kstr = auth_full_string.split()
+ if len(kstr) == 2:
+ auth_enc = kstr[0]
+ auth_string = kstr[1]
+ elif len(kstr) == 1:
+ auth_string = kstr[0]
+ else:
+ module.fail_json(msg="Invalid auth_string")
+ if auth_enc != "0" and auth_enc != "7":
+ module.fail_json(msg="Invalid auth_string, only 0 or 7 allowed")
+
+ device_info = get_capabilities(module)
+ network_api = device_info.get("network_api", "nxapi")
+
+ intf_type = get_interface_type(interface)
+ if intf_type != "ethernet" and network_api == "cliconf":
+ if is_default(interface, module) == "DNE":
+ module.fail_json(
+ msg="That interface does not exist yet. Create " "it first.",
+ interface=interface,
+ )
+ if intf_type == "loopback":
+ module.fail_json(
+ msg="Loopback interfaces don't support HSRP.",
+ interface=interface,
+ )
+
+ mode = get_interface_mode(interface, intf_type, module)
+ if mode == "layer2":
+ module.fail_json(
+ msg="That interface is a layer2 port.\nMake it " "a layer 3 port first.",
+ interface=interface,
+ )
+
+ if auth_type or auth_string:
+ if not (auth_type and auth_string):
+ module.fail_json(
+ msg="When using auth parameters, you need BOTH " "auth_type AND auth_string.",
+ )
+
+ args = dict(
+ group=group,
+ version=version,
+ priority=priority,
+ preempt=preempt,
+ vip=vip,
+ auth_type=auth_type,
+ auth_string=auth_string,
+ auth_enc=auth_enc,
+ )
+
+ proposed = dict((k, v) for k, v in args.items() if v is not None)
+
+ existing = get_hsrp_group(group, interface, module)
+
+ # This will enforce better practice with md5 and hsrp version.
+ if proposed.get("auth_type", None) == "md5":
+ if proposed["version"] == "1":
+ module.fail_json(msg="It's recommended to use HSRP v2 " "when auth_type=md5")
+
+ elif not proposed.get("auth_type", None) and existing:
+ if (proposed["version"] == "1" and existing["auth_type"] == "md5") and state == "present":
+ module.fail_json(
+ msg="Existing auth_type is md5. It's recommended " "to use HSRP v2 when using md5",
+ )
+
+ commands = []
+ if state == "present":
+ delta = dict(set(proposed.items()).difference(existing.items()))
+ if delta:
+ command = get_commands_config_hsrp(delta, interface, args, existing)
+ commands.extend(command)
+
+ elif state == "absent":
+ if existing:
+ command = get_commands_remove_hsrp(group, interface)
+ commands.extend(command)
+
+ if commands:
+ if module.check_mode:
+ module.exit_json(**results)
+ else:
+ load_config(module, commands)
+
+ # validate IP
+ if network_api == "cliconf" and state == "present":
+ commands.insert(0, "config t")
+ body = run_commands(module, commands)
+ validate_config(body, vip, module)
+
+ results["changed"] = True
+
+ if "configure" in commands:
+ commands.pop(0)
+
+ results["commands"] = commands
+ module.exit_json(**results)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_hsrp_interfaces.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_hsrp_interfaces.py
new file mode 100644
index 00000000..e5ac6737
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_hsrp_interfaces.py
@@ -0,0 +1,264 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# Copyright 2019 Cisco and/or its affiliates.
+# 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 nxos_hsrp_interfaces
+"""
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_hsrp_interfaces
+short_description: HSRP interfaces resource module
+description: Manages Hot Standby Router Protocol (HSRP) interface attributes.
+version_added: 1.0.0
+author: Chris Van Heuveln (@chrisvanheuveln)
+notes:
+- Tested against NX-OS 7.0(3)I5(1).
+- Feature bfd should be enabled for this module.
+- Unsupported for Cisco MDS
+options:
+ running_config:
+ description:
+ - This option is used only with state I(parsed).
+ - The value of this option should be the output received from the NX-OS device
+ by executing the command B(show running-config | section '^interface').
+ - The state I(parsed) reads the configuration from C(running_config) option and
+ transforms it into Ansible structured data as per the resource module's argspec
+ and the value is then returned in the I(parsed) key within the result.
+ type: str
+ config:
+ description: The provided configuration
+ type: list
+ elements: dict
+ suboptions:
+ name:
+ type: str
+ description: The name of the interface.
+ bfd:
+ type: str
+ description:
+ - Enable/Disable HSRP Bidirectional Forwarding Detection (BFD) on the interface.
+ choices:
+ - enable
+ - disable
+ state:
+ description:
+ - The state the configuration should be left in
+ type: str
+ choices:
+ - merged
+ - replaced
+ - overridden
+ - deleted
+ - gathered
+ - rendered
+ - parsed
+ default: merged
+
+"""
+EXAMPLES = """
+# Using deleted
+
+- name: Configure hsrp attributes on interfaces
+ cisco.nxos.nxos_hsrp_interfaces:
+ config:
+ - name: Ethernet1/1
+ - name: Ethernet1/2
+ operation: deleted
+
+
+# Using merged
+
+- name: Configure hsrp attributes on interfaces
+ cisco.nxos.nxos_hsrp_interfaces:
+ config:
+ - name: Ethernet1/1
+ bfd: enable
+ - name: Ethernet1/2
+ bfd: disable
+ operation: merged
+
+
+# Using overridden
+
+- name: Configure hsrp attributes on interfaces
+ cisco.nxos.nxos_hsrp_interfaces:
+ config:
+ - name: Ethernet1/1
+ bfd: enable
+ - name: Ethernet1/2
+ bfd: disable
+ operation: overridden
+
+
+# Using replaced
+
+- name: Configure hsrp attributes on interfaces
+ cisco.nxos.nxos_hsrp_interfaces:
+ config:
+ - name: Ethernet1/1
+ bfd: enable
+ - name: Ethernet1/2
+ bfd: disable
+ operation: replaced
+
+# Using rendered
+
+- name: Use rendered state to convert task input to device specific commands
+ cisco.nxos.nxos_hsrp_interfaces:
+ config:
+ - name: Ethernet1/800
+ bfd: enable
+ - name: Ethernet1/801
+ bfd: enable
+ state: rendered
+
+# Task Output (redacted)
+# -----------------------
+
+# rendered:
+# - "interface Ethernet1/800"
+# - "hsrp bfd"
+# - "interface Ethernet1/801"
+# - "hsrp bfd"
+
+# Using parsed
+
+# parsed.cfg
+# ------------
+# interface Ethernet1/800
+# no switchport
+# hsrp bfd
+# interface Ethernet1/801
+# no switchport
+# hsrp bfd
+
+- name: Use parsed state to convert externally supplied config to structured format
+ cisco.nxos.nxos_hsrp_interfaces:
+ running_config: "{{ lookup('file', 'parsed.cfg') }}"
+ state: parsed
+
+# Task output (redacted)
+# -----------------------
+
+# parsed:
+# - name: Ethernet1/800
+# bfd: enable
+# - name: Ethernet1/801
+# bfd: enable
+
+# Using gathered
+
+# Existing device config state
+# -------------------------------
+
+# interface Ethernet1/1
+# no switchport
+# hsrp bfd
+# interface Ethernet1/2
+# no switchport
+# hsrp bfd
+# interface Ethernet1/3
+# no switchport
+
+- name: Gather hsrp_interfaces facts from the device using nxos_hsrp_interfaces
+ cisco.nxos.nxos_hsrp_interfaces:
+ state: gathered
+
+# Task output (redacted)
+# -----------------------
+
+# gathered:
+# - name: Ethernet1/1
+# bfd: enable
+# - name: Ethernet1/2
+# bfd: enable
+
+"""
+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: ['interface Ethernet1/1', 'hsrp bfd']
+"""
+
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.hsrp_interfaces.hsrp_interfaces import (
+ Hsrp_interfacesArgs,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.config.hsrp_interfaces.hsrp_interfaces import (
+ Hsrp_interfaces,
+)
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ required_if = [
+ ("state", "merged", ("config",)),
+ ("state", "replaced", ("config",)),
+ ("state", "overridden", ("config",)),
+ ("state", "rendered", ("config",)),
+ ("state", "parsed", ("running_config",)),
+ ]
+ mutually_exclusive = [("config", "running_config")]
+
+ module = AnsibleModule(
+ argument_spec=Hsrp_interfacesArgs.argument_spec,
+ required_if=required_if,
+ mutually_exclusive=mutually_exclusive,
+ supports_check_mode=True,
+ )
+
+ result = Hsrp_interfaces(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_igmp.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_igmp.py
new file mode 100644
index 00000000..93770616
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_igmp.py
@@ -0,0 +1,162 @@
+#!/usr/bin/python
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_igmp
+extends_documentation_fragment:
+- cisco.nxos.nxos
+short_description: Manages IGMP global configuration.
+description:
+- Manages IGMP global configuration configuration settings.
+version_added: 1.0.0
+author:
+- Jason Edelman (@jedelman8)
+- Gabriele Gerbino (@GGabriele)
+notes:
+- Tested against NXOSv 7.3.(0)D1(1) on VIRL
+- Unsupported for Cisco MDS
+- When C(state=default), all supported params will be reset to a default state.
+- If restart is set to true with other params set, the restart will happen last, i.e.
+ after the configuration takes place.
+options:
+ flush_routes:
+ description:
+ - Removes routes when the IGMP process is restarted. By default, routes are not
+ flushed.
+ type: bool
+ enforce_rtr_alert:
+ description:
+ - Enables or disables the enforce router alert option check for IGMPv2 and IGMPv3
+ packets.
+ type: bool
+ restart:
+ description:
+ - Restarts the igmp process (using an exec config command).
+ type: bool
+ default: False
+ state:
+ description:
+ - Manages desired state of the resource.
+ default: present
+ choices:
+ - present
+ - default
+ type: str
+"""
+EXAMPLES = """
+- name: Default igmp global params (all params except restart)
+ cisco.nxos.nxos_igmp:
+ state: default
+
+- name: Ensure the following igmp global config exists on the device
+ cisco.nxos.nxos_igmp:
+ flush_routes: true
+ enforce_rtr_alert: true
+
+- name: Restart the igmp process
+ cisco.nxos.nxos_igmp:
+ restart: true
+"""
+
+RETURN = """
+updates:
+ description: commands sent to the device
+ returned: always
+ type: list
+ sample: ["ip igmp flush-routes"]
+"""
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ load_config,
+ run_commands,
+)
+
+
+def get_current(module):
+ output = run_commands(module, {"command": "show running-config", "output": "text"})
+ return {
+ "flush_routes": "ip igmp flush-routes" in output[0],
+ "enforce_rtr_alert": "ip igmp enforce-router-alert" in output[0],
+ }
+
+
+def get_desired(module):
+ return {
+ "flush_routes": module.params["flush_routes"],
+ "enforce_rtr_alert": module.params["enforce_rtr_alert"],
+ }
+
+
+def main():
+ argument_spec = dict(
+ flush_routes=dict(type="bool"),
+ enforce_rtr_alert=dict(type="bool"),
+ restart=dict(type="bool", default=False),
+ state=dict(choices=["present", "default"], default="present"),
+ )
+
+ module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)
+
+ warnings = list()
+
+ current = get_current(module)
+ desired = get_desired(module)
+
+ state = module.params["state"]
+
+ commands = list()
+
+ if state == "default":
+ if current["flush_routes"]:
+ commands.append("no ip igmp flush-routes")
+ if current["enforce_rtr_alert"]:
+ commands.append("no ip igmp enforce-router-alert")
+
+ elif state == "present":
+ ldict = {
+ "flush_routes": "flush-routes",
+ "enforce_rtr_alert": "enforce-router-alert",
+ }
+ for arg in ["flush_routes", "enforce_rtr_alert"]:
+ if desired[arg] and not current[arg]:
+ commands.append("ip igmp {0}".format(ldict.get(arg)))
+ elif current[arg] and not desired[arg]:
+ commands.append("no ip igmp {0}".format(ldict.get(arg)))
+
+ result = {"changed": False, "updates": commands, "warnings": warnings}
+
+ if commands:
+ if not module.check_mode:
+ load_config(module, commands)
+ result["changed"] = True
+
+ if module.params["restart"]:
+ cmd = {"command": "restart igmp", "output": "text"}
+ run_commands(module, cmd)
+
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_igmp_interface.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_igmp_interface.py
new file mode 100644
index 00000000..105dac5e
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_igmp_interface.py
@@ -0,0 +1,650 @@
+#!/usr/bin/python
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_igmp_interface
+extends_documentation_fragment:
+- cisco.nxos.nxos
+short_description: Manages IGMP interface configuration.
+description:
+- Manages IGMP interface configuration settings.
+version_added: 1.0.0
+author:
+- Jason Edelman (@jedelman8)
+- Gabriele Gerbino (@GGabriele)
+notes:
+- Tested against NXOSv 7.3.(0)D1(1) on VIRL
+- Unsupported for Cisco MDS
+- When C(state=default), supported params will be reset to a default state. These
+ include C(version), C(startup_query_interval), C(startup_query_count), C(robustness),
+ C(querier_timeout), C(query_mrt), C(query_interval), C(last_member_qrt), C(last_member_query_count),
+ C(group_timeout), C(report_llg), and C(immediate_leave).
+- When C(state=absent), all configs for C(oif_ps), and C(oif_routemap) will be removed.
+- PIM must be enabled to use this module.
+- This module is for Layer 3 interfaces.
+- Route-map check not performed (same as CLI) check when configuring route-map with
+ 'static-oif'
+- If restart is set to true with other params set, the restart will happen last, i.e.
+ after the configuration takes place. However, 'restart' itself is not idempotent
+ as it is an action and not configuration.
+options:
+ interface:
+ description:
+ - The full interface name for IGMP configuration. e.g. I(Ethernet1/2).
+ required: true
+ type: str
+ version:
+ description:
+ - IGMP version. It can be 2 or 3 or keyword 'default'.
+ choices:
+ - '2'
+ - '3'
+ - default
+ type: str
+ startup_query_interval:
+ description:
+ - Query interval used when the IGMP process starts up. The range is from 1 to
+ 18000 or keyword 'default'. The default is 31.
+ type: str
+ startup_query_count:
+ description:
+ - Query count used when the IGMP process starts up. The range is from 1 to 10
+ or keyword 'default'. The default is 2.
+ type: str
+ robustness:
+ description:
+ - Sets the robustness variable. Values can range from 1 to 7 or keyword 'default'.
+ The default is 2.
+ type: str
+ querier_timeout:
+ description:
+ - Sets the querier timeout that the software uses when deciding to take over as
+ the querier. Values can range from 1 to 65535 seconds or keyword 'default'.
+ The default is 255 seconds.
+ type: str
+ query_mrt:
+ description:
+ - Sets the response time advertised in IGMP queries. Values can range from 1 to
+ 25 seconds or keyword 'default'. The default is 10 seconds.
+ type: str
+ query_interval:
+ description:
+ - Sets the frequency at which the software sends IGMP host query messages. Values
+ can range from 1 to 18000 seconds or keyword 'default'. The default is 125 seconds.
+ type: str
+ last_member_qrt:
+ description:
+ - Sets the query interval waited after sending membership reports before the software
+ deletes the group state. Values can range from 1 to 25 seconds or keyword 'default'.
+ The default is 1 second.
+ type: str
+ last_member_query_count:
+ description:
+ - Sets the number of times that the software sends an IGMP query in response to
+ a host leave message. Values can range from 1 to 5 or keyword 'default'. The
+ default is 2.
+ type: str
+ group_timeout:
+ description:
+ - Sets the group membership timeout for IGMPv2. Values can range from 3 to 65,535
+ seconds or keyword 'default'. The default is 260 seconds.
+ type: str
+ report_llg:
+ description:
+ - Configures report-link-local-groups. Enables sending reports for groups in 224.0.0.0/24.
+ Reports are always sent for nonlink local groups. By default, reports are not
+ sent for link local groups.
+ type: bool
+ immediate_leave:
+ description:
+ - Enables the device to remove the group entry from the multicast routing table
+ immediately upon receiving a leave message for the group. Use this command to
+ minimize the leave latency of IGMPv2 group memberships on a given IGMP interface
+ because the device does not send group-specific queries. The default is disabled.
+ type: bool
+ oif_routemap:
+ description:
+ - Configure a routemap for static outgoing interface (OIF) or keyword 'default'.
+ type: str
+ oif_ps:
+ description:
+ - Configure prefixes and sources for static outgoing interface (OIF). This is
+ a list of dict where each dict has source and prefix defined or just prefix
+ if source is not needed. The specified values will be configured on the device
+ and if any previous prefix/sources exist, they will be removed. Keyword 'default'
+ is also accepted which removes all existing prefix/sources.
+ type: raw
+ restart:
+ description:
+ - Restart IGMP. This is NOT idempotent as this is action only.
+ type: bool
+ default: false
+ state:
+ description:
+ - Manages desired state of the resource.
+ default: present
+ choices:
+ - present
+ - absent
+ - default
+ type: str
+"""
+EXAMPLES = """
+- cisco.nxos.nxos_igmp_interface:
+ interface: ethernet1/32
+ startup_query_interval: 30
+ oif_ps:
+ - {prefix: 238.2.2.6}
+ - {source: 192.168.0.1, prefix: 238.2.2.5}
+ state: present
+"""
+RETURN = """
+proposed:
+ description: k/v pairs of parameters passed into module
+ returned: always
+ type: dict
+ sample: {"startup_query_count": "30",
+ "oif_ps": [{'prefix': '238.2.2.6'}, {'source': '192.168.0.1', 'prefix': '238.2.2.5'}]}
+existing:
+ description: k/v pairs of existing igmp_interface configuration
+ returned: always
+ type: dict
+ sample: {"startup_query_count": "2", "oif_ps": []}
+end_state:
+ description: k/v pairs of igmp interface configuration after module execution
+ returned: always
+ type: dict
+ sample: {"startup_query_count": "30",
+ "oif_ps": [{'prefix': '238.2.2.6'}, {'source': '192.168.0.1', 'prefix': '238.2.2.5'}]}
+updates:
+ description: commands sent to the device
+ returned: always
+ type: list
+ sample: ["interface Ethernet1/32", "ip igmp startup-query-count 30",
+ "ip igmp static-oif 238.2.2.6", "ip igmp static-oif 238.2.2.5 source 192.168.0.1"]
+changed:
+ description: check to see if a change was made on the device
+ returned: always
+ type: bool
+ sample: true
+"""
+
+import re
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ get_interface_type,
+ load_config,
+ run_commands,
+)
+
+
+def execute_show_command(command, module, command_type="cli_show"):
+ if command_type == "cli_show_ascii":
+ cmds = [{"command": command, "output": "text"}]
+ else:
+ cmds = [{"command": command, "output": "json"}]
+
+ return run_commands(module, cmds)
+
+
+def get_interface_mode(interface, intf_type, module):
+ command = "show interface {0}".format(interface)
+ interface = {}
+ mode = "unknown"
+
+ if intf_type in ["ethernet", "portchannel"]:
+ body = execute_show_command(command, module)[0]
+ interface_table = body["TABLE_interface"]["ROW_interface"]
+ mode = str(interface_table.get("eth_mode", "layer3"))
+ if mode == "access" or mode == "trunk":
+ mode = "layer2"
+ elif intf_type == "loopback" or intf_type == "svi":
+ mode = "layer3"
+ return mode
+
+
+def apply_key_map(key_map, table):
+ new_dict = {}
+ for key, value in table.items():
+ new_key = key_map.get(key)
+ if new_key:
+ value = table.get(key)
+ if value:
+ new_dict[new_key] = value
+ else:
+ new_dict[new_key] = value
+ return new_dict
+
+
+def flatten_list(command_lists):
+ flat_command_list = []
+ for command in command_lists:
+ if isinstance(command, list):
+ flat_command_list.extend(command)
+ else:
+ flat_command_list.append(command)
+ return flat_command_list
+
+
+def get_igmp_interface(module, interface):
+ command = "show ip igmp interface {0}".format(interface)
+ igmp = {}
+
+ key_map = {
+ "IGMPVersion": "version",
+ "ConfiguredStartupQueryInterval": "startup_query_interval",
+ "StartupQueryCount": "startup_query_count",
+ "RobustnessVariable": "robustness",
+ "ConfiguredQuerierTimeout": "querier_timeout",
+ "ConfiguredMaxResponseTime": "query_mrt",
+ "ConfiguredQueryInterval": "query_interval",
+ "LastMemberMTR": "last_member_qrt",
+ "LastMemberQueryCount": "last_member_query_count",
+ "ConfiguredGroupTimeout": "group_timeout",
+ }
+
+ body = execute_show_command(command, module)[0]
+
+ if body:
+ if "not running" in body:
+ return igmp
+ resource = body["TABLE_vrf"]["ROW_vrf"]["TABLE_if"]["ROW_if"]
+ igmp = apply_key_map(key_map, resource)
+ report_llg = str(resource["ReportingForLinkLocal"]).lower()
+ if report_llg == "true":
+ igmp["report_llg"] = True
+ elif report_llg == "false":
+ igmp["report_llg"] = False
+
+ immediate_leave = str(resource["ImmediateLeave"]).lower() # returns en or dis
+ if re.search(r"^en|^true|^enabled", immediate_leave):
+ igmp["immediate_leave"] = True
+ elif re.search(r"^dis|^false|^disabled", immediate_leave):
+ igmp["immediate_leave"] = False
+
+ # the next block of code is used to retrieve anything with:
+ # ip igmp static-oif *** i.e.. could be route-map ROUTEMAP
+ # or PREFIX source <ip>, etc.
+ command = "show run interface {0} | inc oif".format(interface)
+
+ body = execute_show_command(command, module, command_type="cli_show_ascii")[0]
+
+ staticoif = []
+ if body:
+ split_body = body.split("\n")
+ route_map_regex = r".*ip igmp static-oif route-map\s+(?P<route_map>\S+).*"
+ prefix_source_regex = (
+ r".*ip igmp static-oif\s+(?P<prefix>"
+ r"((\d+.){3}\d+))(\ssource\s"
+ r"(?P<source>\S+))?.*"
+ )
+
+ for line in split_body:
+ temp = {}
+ try:
+ match_route_map = re.match(route_map_regex, line, re.DOTALL)
+ route_map = match_route_map.groupdict()["route_map"]
+ except AttributeError:
+ route_map = ""
+
+ try:
+ match_prefix_source = re.match(prefix_source_regex, line, re.DOTALL)
+ prefix_source_group = match_prefix_source.groupdict()
+ prefix = prefix_source_group["prefix"]
+ source = prefix_source_group["source"]
+ except AttributeError:
+ prefix = ""
+ source = ""
+
+ if route_map:
+ temp["route_map"] = route_map
+ if prefix:
+ temp["prefix"] = prefix
+ if source:
+ temp["source"] = source
+ if temp:
+ staticoif.append(temp)
+
+ igmp["oif_routemap"] = None
+ igmp["oif_prefix_source"] = []
+
+ if staticoif:
+ if len(staticoif) == 1 and staticoif[0].get("route_map"):
+ igmp["oif_routemap"] = staticoif[0]["route_map"]
+ else:
+ igmp["oif_prefix_source"] = staticoif
+
+ return igmp
+
+
+def config_igmp_interface(delta, existing, existing_oif_prefix_source):
+ CMDS = {
+ "version": "ip igmp version {0}",
+ "startup_query_interval": "ip igmp startup-query-interval {0}",
+ "startup_query_count": "ip igmp startup-query-count {0}",
+ "robustness": "ip igmp robustness-variable {0}",
+ "querier_timeout": "ip igmp querier-timeout {0}",
+ "query_mrt": "ip igmp query-max-response-time {0}",
+ "query_interval": "ip igmp query-interval {0}",
+ "last_member_qrt": "ip igmp last-member-query-response-time {0}",
+ "last_member_query_count": "ip igmp last-member-query-count {0}",
+ "group_timeout": "ip igmp group-timeout {0}",
+ "report_llg": "ip igmp report-link-local-groups",
+ "immediate_leave": "ip igmp immediate-leave",
+ "oif_prefix_source": "ip igmp static-oif {0} source {1} ",
+ "oif_routemap": "ip igmp static-oif route-map {0}",
+ "oif_prefix": "ip igmp static-oif {0}",
+ }
+
+ commands = []
+ command = None
+ def_vals = get_igmp_interface_defaults()
+
+ for key, value in delta.items():
+ if key == "oif_ps" and value != "default":
+ for each in value:
+ if each in existing_oif_prefix_source:
+ existing_oif_prefix_source.remove(each)
+ else:
+ # add new prefix/sources
+ pf = each["prefix"]
+ src = ""
+ if "source" in each.keys():
+ src = each["source"]
+ if src:
+ commands.append(CMDS.get("oif_prefix_source").format(pf, src))
+ else:
+ commands.append(CMDS.get("oif_prefix").format(pf))
+ if existing_oif_prefix_source:
+ for each in existing_oif_prefix_source:
+ # remove stale prefix/sources
+ pf = each["prefix"]
+ src = ""
+ if "source" in each.keys():
+ src = each["source"]
+ if src:
+ commands.append("no " + CMDS.get("oif_prefix_source").format(pf, src))
+ else:
+ commands.append("no " + CMDS.get("oif_prefix").format(pf))
+ elif key == "oif_routemap":
+ if value == "default":
+ if existing.get(key):
+ command = "no " + CMDS.get(key).format(existing.get(key))
+ else:
+ command = CMDS.get(key).format(value)
+ elif value:
+ if value == "default":
+ if def_vals.get(key) != existing.get(key):
+ command = CMDS.get(key).format(def_vals.get(key))
+ else:
+ command = CMDS.get(key).format(value)
+ elif not value:
+ command = "no {0}".format(CMDS.get(key).format(value))
+
+ if command:
+ if command not in commands:
+ commands.append(command)
+ command = None
+
+ return commands
+
+
+def get_igmp_interface_defaults():
+ version = "2"
+ startup_query_interval = "31"
+ startup_query_count = "2"
+ robustness = "2"
+ querier_timeout = "255"
+ query_mrt = "10"
+ query_interval = "125"
+ last_member_qrt = "1"
+ last_member_query_count = "2"
+ group_timeout = "260"
+ report_llg = False
+ immediate_leave = False
+
+ args = dict(
+ version=version,
+ startup_query_interval=startup_query_interval,
+ startup_query_count=startup_query_count,
+ robustness=robustness,
+ querier_timeout=querier_timeout,
+ query_mrt=query_mrt,
+ query_interval=query_interval,
+ last_member_qrt=last_member_qrt,
+ last_member_query_count=last_member_query_count,
+ group_timeout=group_timeout,
+ report_llg=report_llg,
+ immediate_leave=immediate_leave,
+ )
+
+ default = dict((param, value) for (param, value) in args.items() if value is not None)
+
+ return default
+
+
+def config_default_igmp_interface(existing, delta):
+ commands = []
+ proposed = get_igmp_interface_defaults()
+ delta = dict(set(proposed.items()).difference(existing.items()))
+ if delta:
+ command = config_igmp_interface(delta, existing, existing_oif_prefix_source=None)
+
+ if command:
+ for each in command:
+ commands.append(each)
+
+ return commands
+
+
+def config_remove_oif(existing, existing_oif_prefix_source):
+ commands = []
+ command = None
+ if existing.get("oif_routemap"):
+ commands.append("no ip igmp static-oif route-map {0}".format(existing.get("oif_routemap")))
+ elif existing_oif_prefix_source:
+ for each in existing_oif_prefix_source:
+ if each.get("prefix") and each.get("source"):
+ command = "no ip igmp static-oif {0} source {1} ".format(
+ each.get("prefix"),
+ each.get("source"),
+ )
+ elif each.get("prefix"):
+ command = "no ip igmp static-oif {0}".format(each.get("prefix"))
+ if command:
+ commands.append(command)
+ command = None
+
+ return commands
+
+
+def main():
+ argument_spec = dict(
+ interface=dict(required=True, type="str"),
+ version=dict(required=False, type="str", choices=["2", "3", "default"]),
+ startup_query_interval=dict(required=False, type="str"),
+ startup_query_count=dict(required=False, type="str"),
+ robustness=dict(required=False, type="str"),
+ querier_timeout=dict(required=False, type="str"),
+ query_mrt=dict(required=False, type="str"),
+ query_interval=dict(required=False, type="str"),
+ last_member_qrt=dict(required=False, type="str"),
+ last_member_query_count=dict(required=False, type="str"),
+ group_timeout=dict(required=False, type="str"),
+ report_llg=dict(type="bool"),
+ immediate_leave=dict(type="bool"),
+ oif_routemap=dict(required=False, type="str"),
+ oif_ps=dict(required=False, type="raw"),
+ restart=dict(type="bool", default=False),
+ state=dict(choices=["present", "absent", "default"], default="present"),
+ )
+
+ mutually_exclusive = [("oif_ps", "oif_routemap")]
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ mutually_exclusive=mutually_exclusive,
+ supports_check_mode=True,
+ )
+
+ warnings = list()
+
+ state = module.params["state"]
+ interface = module.params["interface"]
+ oif_routemap = module.params["oif_routemap"]
+ oif_ps = module.params["oif_ps"]
+
+ intf_type = get_interface_type(interface)
+ if get_interface_mode(interface, intf_type, module) == "layer2":
+ module.fail_json(msg="this module only works on Layer 3 interfaces")
+
+ existing = get_igmp_interface(module, interface)
+ existing_copy = existing.copy()
+ end_state = existing_copy
+
+ if not existing.get("version"):
+ module.fail_json(msg="pim needs to be enabled on the interface")
+
+ existing_oif_prefix_source = existing.get("oif_prefix_source")
+ # not json serializable
+ existing.pop("oif_prefix_source")
+
+ if oif_routemap and existing_oif_prefix_source:
+ module.fail_json(
+ msg="Delete static-oif configurations on this "
+ "interface if you want to use a routemap",
+ )
+
+ if oif_ps and existing.get("oif_routemap"):
+ module.fail_json(
+ msg="Delete static-oif route-map configuration "
+ "on this interface if you want to config "
+ "static entries",
+ )
+
+ args = [
+ "version",
+ "startup_query_interval",
+ "startup_query_count",
+ "robustness",
+ "querier_timeout",
+ "query_mrt",
+ "query_interval",
+ "last_member_qrt",
+ "last_member_query_count",
+ "group_timeout",
+ "report_llg",
+ "immediate_leave",
+ "oif_routemap",
+ ]
+
+ changed = False
+ commands = []
+ proposed = dict((k, v) for k, v in module.params.items() if v is not None and k in args)
+
+ CANNOT_ABSENT = [
+ "version",
+ "startup_query_interval",
+ "startup_query_count",
+ "robustness",
+ "querier_timeout",
+ "query_mrt",
+ "query_interval",
+ "last_member_qrt",
+ "last_member_query_count",
+ "group_timeout",
+ "report_llg",
+ "immediate_leave",
+ ]
+
+ if state == "absent":
+ for each in CANNOT_ABSENT:
+ if each in proposed:
+ module.fail_json(
+ msg="only params: " "oif_ps, oif_routemap can be used when " "state=absent",
+ )
+
+ # delta check for all params except oif_ps
+ delta = dict(set(proposed.items()).difference(existing.items()))
+
+ if oif_ps:
+ if oif_ps == "default":
+ delta["oif_ps"] = []
+ else:
+ delta["oif_ps"] = oif_ps
+
+ if state == "present":
+ if delta:
+ command = config_igmp_interface(delta, existing, existing_oif_prefix_source)
+ if command:
+ commands.append(command)
+
+ elif state == "default":
+ command = config_default_igmp_interface(existing, delta)
+ if command:
+ commands.append(command)
+ elif state == "absent":
+ command = None
+ if existing.get("oif_routemap") or existing_oif_prefix_source:
+ command = config_remove_oif(existing, existing_oif_prefix_source)
+
+ if command:
+ commands.append(command)
+
+ command = config_default_igmp_interface(existing, delta)
+ if command:
+ commands.append(command)
+
+ cmds = []
+ results = {}
+ if commands:
+ commands.insert(0, ["interface {0}".format(interface)])
+ cmds = flatten_list(commands)
+
+ if module.check_mode:
+ module.exit_json(changed=True, commands=cmds)
+ else:
+ load_config(module, cmds)
+ changed = True
+ end_state = get_igmp_interface(module, interface)
+ if "configure" in cmds:
+ cmds.pop(0)
+
+ if module.params["restart"]:
+ cmd = {"command": "restart igmp", "output": "text"}
+ run_commands(module, cmd)
+
+ results["proposed"] = proposed
+ results["existing"] = existing_copy
+ results["updates"] = cmds
+ results["changed"] = changed
+ results["warnings"] = warnings
+ results["end_state"] = end_state
+
+ module.exit_json(**results)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_igmp_snooping.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_igmp_snooping.py
new file mode 100644
index 00000000..6d14ba17
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_igmp_snooping.py
@@ -0,0 +1,319 @@
+#!/usr/bin/python
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_igmp_snooping
+extends_documentation_fragment:
+- cisco.nxos.nxos
+short_description: Manages IGMP snooping global configuration.
+description:
+- Manages IGMP snooping global configuration.
+version_added: 1.0.0
+author:
+- Jason Edelman (@jedelman8)
+- Gabriele Gerbino (@GGabriele)
+notes:
+- Tested against NXOSv 7.3.(0)D1(1) on VIRL
+- Unsupported for Cisco MDS
+- When C(state=default), params will be reset to a default state.
+- C(group_timeout) also accepts I(never) as an input.
+options:
+ snooping:
+ description:
+ - Enables/disables IGMP snooping on the switch.
+ type: bool
+ group_timeout:
+ description:
+ - Group membership timeout value for all VLANs on the device. Accepted values
+ are integer in range 1-10080, I(never) and I(default).
+ type: str
+ link_local_grp_supp:
+ description:
+ - Global link-local groups suppression.
+ type: bool
+ report_supp:
+ description:
+ - Global IGMPv1/IGMPv2 Report Suppression.
+ type: bool
+ v3_report_supp:
+ description:
+ - Global IGMPv3 Report Suppression and Proxy Reporting.
+ type: bool
+ state:
+ description:
+ - Manage the state of the resource.
+ default: present
+ choices:
+ - present
+ - default
+ type: str
+"""
+
+EXAMPLES = """
+# ensure igmp snooping params supported in this module are in there default state
+- cisco.nxos.nxos_igmp_snooping:
+ state: default
+
+# ensure following igmp snooping params are in the desired state
+- cisco.nxos.nxos_igmp_snooping:
+ group_timeout: never
+ snooping: true
+ link_local_grp_supp: false
+ optimize_mcast_flood: false
+ report_supp: true
+ v3_report_supp: true
+"""
+
+RETURN = """
+commands:
+ description: command sent to the device
+ returned: always
+ type: list
+ sample: ["ip igmp snooping link-local-groups-suppression",
+ "ip igmp snooping group-timeout 50",
+ "no ip igmp snooping report-suppression",
+ "no ip igmp snooping v3-report-suppression",
+ "no ip igmp snooping"]
+"""
+
+
+import re
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ load_config,
+ run_commands,
+)
+
+
+def execute_show_command(command, module, output="text"):
+ command = {"command": command, "output": output}
+
+ return run_commands(module, [command])
+
+
+def flatten_list(command_lists):
+ flat_command_list = []
+ for command in command_lists:
+ if isinstance(command, list):
+ flat_command_list.extend(command)
+ else:
+ flat_command_list.append(command)
+ return flat_command_list
+
+
+def get_group_timeout(config):
+ match = re.search(r" Group timeout configured: (\S+)", config, re.M)
+ if match:
+ value = match.group(1)
+ else:
+ value = ""
+ return value
+
+
+def get_igmp_snooping(module):
+ command = "show ip igmp snooping"
+ existing = {}
+
+ try:
+ body = execute_show_command(command, module, output="json")[0]
+ except IndexError:
+ body = []
+
+ if body:
+ snooping = str(body.get("enabled")).lower()
+ if "none" in snooping:
+ snooping = str(body.get("GlobalSnoopEnabled")).lower()
+ if snooping == "true" or snooping == "enabled" or snooping == "yes":
+ existing["snooping"] = True
+ else:
+ existing["snooping"] = False
+
+ report_supp = str(body.get("grepsup")).lower()
+ if "none" in report_supp:
+ report_supp = str(body.get("GlobalReportSupression")).lower()
+ if report_supp == "true" or report_supp == "enabled":
+ existing["report_supp"] = True
+ else:
+ existing["report_supp"] = False
+
+ link_local_grp_supp = str(body.get("glinklocalgrpsup")).lower()
+ if "none" in link_local_grp_supp:
+ link_local_grp_supp = str(body.get("GlobalLinkLocalGroupSupression")).lower()
+ if link_local_grp_supp == "true" or link_local_grp_supp == "enabled":
+ existing["link_local_grp_supp"] = True
+ else:
+ existing["link_local_grp_supp"] = False
+
+ v3_report_supp = str(body.get("gv3repsup")).lower()
+ if "none" in v3_report_supp:
+ v3_report_supp = str(body.get("GlobalV3ReportSupression")).lower()
+ if v3_report_supp == "true" or v3_report_supp == "enabled":
+ existing["v3_report_supp"] = True
+ else:
+ existing["v3_report_supp"] = False
+
+ command = "show ip igmp snooping"
+ body = execute_show_command(command, module)[0]
+ if body:
+ existing["group_timeout"] = get_group_timeout(body)
+
+ return existing
+
+
+def config_igmp_snooping(delta, existing, default=False):
+ CMDS = {
+ "snooping": "ip igmp snooping",
+ "group_timeout": "ip igmp snooping group-timeout {}",
+ "link_local_grp_supp": "ip igmp snooping link-local-groups-suppression",
+ "v3_report_supp": "ip igmp snooping v3-report-suppression",
+ "report_supp": "ip igmp snooping report-suppression",
+ }
+
+ commands = []
+ command = None
+ gt_command = None
+ for key, value in delta.items():
+ if value:
+ if default and key == "group_timeout":
+ if existing.get(key):
+ gt_command = "no " + CMDS.get(key).format(existing.get(key))
+ elif value == "default" and key == "group_timeout":
+ if existing.get(key):
+ command = "no " + CMDS.get(key).format(existing.get(key))
+ else:
+ command = CMDS.get(key).format(value)
+ else:
+ command = "no " + CMDS.get(key).format(value)
+
+ if command:
+ commands.append(command)
+ command = None
+
+ if gt_command:
+ # ensure that group-timeout command is configured last
+ commands.append(gt_command)
+ return commands
+
+
+def get_igmp_snooping_defaults():
+ group_timeout = "dummy"
+ report_supp = True
+ link_local_grp_supp = True
+ v3_report_supp = False
+ snooping = True
+
+ args = dict(
+ snooping=snooping,
+ link_local_grp_supp=link_local_grp_supp,
+ report_supp=report_supp,
+ v3_report_supp=v3_report_supp,
+ group_timeout=group_timeout,
+ )
+
+ default = dict((param, value) for (param, value) in args.items() if value is not None)
+
+ return default
+
+
+def igmp_snooping_gt_dependency(command, existing, module):
+ # group-timeout will fail if igmp snooping is disabled
+ gt = [i for i in command if i.startswith("ip igmp snooping group-timeout")]
+ if gt:
+ if "no ip igmp snooping" in command or (
+ existing["snooping"] is False and "ip igmp snooping" not in command
+ ):
+ msg = "group-timeout cannot be enabled or changed when ip igmp snooping is disabled"
+ module.fail_json(msg=msg)
+ else:
+ # ensure that group-timeout command is configured last
+ command.remove(gt[0])
+ command.append(gt[0])
+
+
+def main():
+ argument_spec = dict(
+ snooping=dict(required=False, type="bool"),
+ group_timeout=dict(required=False, type="str"),
+ link_local_grp_supp=dict(required=False, type="bool"),
+ report_supp=dict(required=False, type="bool"),
+ v3_report_supp=dict(required=False, type="bool"),
+ state=dict(choices=["present", "default"], default="present"),
+ )
+
+ module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)
+
+ warnings = list()
+ results = {"changed": False, "commands": [], "warnings": warnings}
+
+ snooping = module.params["snooping"]
+ link_local_grp_supp = module.params["link_local_grp_supp"]
+ report_supp = module.params["report_supp"]
+ v3_report_supp = module.params["v3_report_supp"]
+ group_timeout = module.params["group_timeout"]
+ state = module.params["state"]
+
+ args = dict(
+ snooping=snooping,
+ link_local_grp_supp=link_local_grp_supp,
+ report_supp=report_supp,
+ v3_report_supp=v3_report_supp,
+ group_timeout=group_timeout,
+ )
+
+ proposed = dict((param, value) for (param, value) in args.items() if value is not None)
+
+ existing = get_igmp_snooping(module)
+
+ commands = []
+ if state == "present":
+ delta = dict(set(proposed.items()).difference(existing.items()))
+ if delta:
+ command = config_igmp_snooping(delta, existing)
+ if command:
+ if group_timeout:
+ igmp_snooping_gt_dependency(command, existing, module)
+ commands.append(command)
+ elif state == "default":
+ proposed = get_igmp_snooping_defaults()
+ delta = dict(set(proposed.items()).difference(existing.items()))
+ if delta:
+ command = config_igmp_snooping(delta, existing, default=True)
+ if command:
+ commands.append(command)
+
+ cmds = flatten_list(commands)
+ if cmds:
+ results["changed"] = True
+ if not module.check_mode:
+ load_config(module, cmds)
+ if "configure" in cmds:
+ cmds.pop(0)
+ results["commands"] = cmds
+
+ module.exit_json(**results)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_install_os.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_install_os.py
new file mode 100644
index 00000000..2327ab54
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_install_os.py
@@ -0,0 +1,594 @@
+#!/usr/bin/python
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_install_os
+extends_documentation_fragment:
+- cisco.nxos.nxos
+short_description: Set boot options like boot, kickstart image and issu.
+description:
+- Install an operating system by setting the boot options like boot image and kickstart
+ image and optionally select to install using ISSU (In Server Software Upgrade).
+version_added: 1.0.0
+notes:
+- Tested against the following platforms and images - N9k 7.0(3)I4(6), 7.0(3)I5(3),
+ 7.0(3)I6(1), 7.0(3)I7(1), 7.0(3)F2(2), 7.0(3)F3(2) - N3k 6.0(2)A8(6), 6.0(2)A8(8),
+ 7.0(3)I6(1), 7.0(3)I7(1) - N7k 7.3(0)D1(1), 8.0(1), 8.1(1), 8.2(1)
+- Tested against Cisco MDS NX-OS 9.2(1)
+- This module requires both the ANSIBLE_PERSISTENT_CONNECT_TIMEOUT and ANSIBLE_PERSISTENT_COMMAND_TIMEOUT
+ timers to be set to 600 seconds or higher. The module will exit if the timers are
+ not set properly.
+- When using connection local, ANSIBLE_PERSISTENT_CONNECT_TIMEOUT and ANSIBLE_PERSISTENT_COMMAND_TIMEOUT
+ can only be set using ENV variables or the ansible.cfg file.
+- Do not include full file paths, just the name of the file(s) stored on the top level
+ flash directory.
+- This module attempts to install the software immediately, which may trigger a reboot.
+- In check mode, the module will indicate if an upgrade is needed and whether or not
+ the upgrade is disruptive or non-disruptive(ISSU).
+author:
+- Jason Edelman (@jedelman8)
+- Gabriele Gerbibo (@GGabriele)
+options:
+ system_image_file:
+ description:
+ - Name of the system (or combined) image file on flash.
+ required: true
+ type: str
+ kickstart_image_file:
+ description:
+ - Name of the kickstart image file on flash. (Not required on all Nexus platforms)
+ type: str
+ issu:
+ description:
+ - Upgrade using In Service Software Upgrade (ISSU). (Supported on N5k, N7k, N9k
+ platforms)
+ - Selecting 'required' or 'yes' means that upgrades will only proceed if the switch
+ is capable of ISSU.
+ - Selecting 'desired' means that upgrades will use ISSU if possible but will fall
+ back to disruptive upgrade if needed.
+ - Selecting 'no' means do not use ISSU. Forced disruptive.
+ choices:
+ - "required"
+ - "desired"
+ - "yes"
+ - "no"
+ default: "no"
+ type: str
+"""
+
+EXAMPLES = """
+- name: Install OS on N9k
+ check_mode: no
+ cisco.nxos.nxos_install_os:
+ system_image_file: nxos.7.0.3.I6.1.bin
+ issu: desired
+
+- name: Wait for device to come back up with new image
+ wait_for:
+ port: 22
+ state: started
+ timeout: 500
+ delay: 60
+ host: '{{ inventory_hostname }}'
+
+- name: Check installed OS for newly installed version
+ nxos_command:
+ commands: [show version | json]
+ register: output
+- assert:
+ that:
+ - output['stdout'][0]['kickstart_ver_str'] == '7.0(3)I6(1)'
+"""
+
+RETURN = """
+install_state:
+ description: Boot and install information.
+ returned: always
+ type: dict
+ sample: {
+ "install_state": [
+ "Compatibility check is done:",
+ "Module bootable Impact Install-type Reason",
+ "------ -------- -------------- ------------ ------",
+ " 1 yes non-disruptive reset ",
+ "Images will be upgraded according to following table:",
+ "Module Image Running-Version(pri:alt) New-Version Upg-Required",
+ "------ ---------- ---------------------------------------- -------------------- ------------",
+ " 1 nxos 7.0(3)I6(1) 7.0(3)I7(1) yes",
+ " 1 bios v4.4.0(07/12/2017) v4.4.0(07/12/2017) no"
+ ],
+ }
+"""
+
+
+import re
+
+from time import sleep
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ load_config,
+ run_commands,
+)
+
+
+# Output options are 'text' or 'json'
+def execute_show_command(module, command, output="text"):
+ cmds = [{"command": command, "output": output}]
+
+ return run_commands(module, cmds)
+
+
+def get_platform(module):
+ """Determine platform type"""
+ data = execute_show_command(module, "show inventory", "json")
+ pid = data[0]["TABLE_inv"]["ROW_inv"][0]["productid"]
+
+ if re.search(r"N3K", pid):
+ type = "N3K"
+ elif re.search(r"N5K", pid):
+ type = "N5K"
+ elif re.search(r"N6K", pid):
+ type = "N6K"
+ elif re.search(r"N7K", pid):
+ type = "N7K"
+ elif re.search(r"N9K", pid):
+ type = "N9K"
+ else:
+ type = "unknown"
+
+ return type
+
+
+def parse_show_install(data):
+ """Helper method to parse the output of the 'show install all impact' or
+ 'install all' commands.
+
+ Sample Output:
+
+ Installer will perform impact only check. Please wait.
+
+ Verifying image bootflash:/nxos.7.0.3.F2.2.bin for boot variable "nxos".
+ [####################] 100% -- SUCCESS
+
+ Verifying image type.
+ [####################] 100% -- SUCCESS
+
+ Preparing "bios" version info using image bootflash:/nxos.7.0.3.F2.2.bin.
+ [####################] 100% -- SUCCESS
+
+ Preparing "nxos" version info using image bootflash:/nxos.7.0.3.F2.2.bin.
+ [####################] 100% -- SUCCESS
+
+ Performing module support checks.
+ [####################] 100% -- SUCCESS
+
+ Notifying services about system upgrade.
+ [####################] 100% -- SUCCESS
+
+
+
+ Compatibility check is done:
+ Module bootable Impact Install-type Reason
+ ------ -------- -------------- ------------ ------
+ 8 yes disruptive reset Incompatible image for ISSU
+ 21 yes disruptive reset Incompatible image for ISSU
+
+
+ Images will be upgraded according to following table:
+ Module Image Running-Version(pri:alt) New-Version Upg-Required
+ ------ ---------- ---------------------------------------- ------------
+ 8 lcn9k 7.0(3)F3(2) 7.0(3)F2(2) yes
+ 8 bios v01.17 v01.17 no
+ 21 lcn9k 7.0(3)F3(2) 7.0(3)F2(2) yes
+ 21 bios v01.70 v01.70 no
+ """
+ if len(data) > 0:
+ data = massage_install_data(data)
+ ud = {"raw": data}
+ ud["processed"] = []
+ ud["disruptive"] = False
+ ud["upgrade_needed"] = False
+ ud["error"] = False
+ ud["invalid_command"] = False
+ ud["install_in_progress"] = False
+ ud["server_error"] = False
+ ud["upgrade_succeeded"] = False
+ ud["use_impact_data"] = False
+
+ # Check for server errors
+ if isinstance(data, int):
+ if data == -1:
+ ud["server_error"] = True
+ elif data >= 500:
+ ud["server_error"] = True
+ elif data == -32603:
+ ud["server_error"] = True
+ elif data == 1:
+ ud["server_error"] = True
+ return ud
+ else:
+ ud["list_data"] = data.split("\n")
+
+ for x in ud["list_data"]:
+ # Check for errors and exit if found.
+ if re.search(r"Pre-upgrade check failed", x):
+ ud["error"] = True
+ break
+ if re.search(r"[I|i]nvalid command", x):
+ ud["invalid_command"] = True
+ ud["error"] = True
+ break
+ if re.search(r"No install all data found", x):
+ ud["error"] = True
+ break
+
+ # Check for potentially transient conditions
+ if re.search(r"Another install procedure may\s*be in progress", x):
+ ud["install_in_progress"] = True
+ break
+ if re.search(r"Backend processing error", x):
+ ud["server_error"] = True
+ break
+ if re.search(r"timed out", x):
+ ud["server_error"] = True
+ break
+ if re.search(r"^(-1|5\d\d)$", x):
+ ud["server_error"] = True
+ break
+
+ # Check for messages indicating a successful upgrade.
+ if re.search(r"Finishing the upgrade", x):
+ ud["upgrade_succeeded"] = True
+ break
+ if re.search(r"Install has been successful", x):
+ ud["upgrade_succeeded"] = True
+ break
+ if re.search(r"Switching over onto standby", x):
+ ud["upgrade_succeeded"] = True
+ break
+
+ # We get these messages when the upgrade is non-disruptive and
+ # we loose connection with the switchover but far enough along that
+ # we can be confident the upgrade succeeded.
+ if re.search(r"timeout .*trying to send command: install", x):
+ ud["upgrade_succeeded"] = True
+ ud["use_impact_data"] = True
+ break
+ if re.search(r"[C|c]onnection failure: timed out", x):
+ ud["upgrade_succeeded"] = True
+ ud["use_impact_data"] = True
+ break
+
+ # Begin normal parsing.
+ if re.search(r"----|Module|Images will|Compatibility", x):
+ ud["processed"].append(x)
+ continue
+ # Check to see if upgrade will be disruptive or non-disruptive and
+ # build dictionary of individual modules and their status.
+ # Sample Line:
+ #
+ # Module bootable Impact Install-type Reason
+ # ------ -------- ---------- ------------ ------
+ # 8 yes disruptive reset Incompatible image
+ rd = r"(\d+)\s+(\S+)\s+(disruptive|non-disruptive)\s+(\S+)"
+ mo = re.search(rd, x)
+ if mo:
+ ud["processed"].append(x)
+ key = "m%s" % mo.group(1)
+ field = "disruptive"
+ if mo.group(3) == "non-disruptive":
+ ud[key] = {field: False}
+ else:
+ ud[field] = True
+ ud[key] = {field: True}
+ field = "bootable"
+ if mo.group(2) == "yes":
+ ud[key].update({field: True})
+ else:
+ ud[key].update({field: False})
+ continue
+
+ # Check to see if switch needs an upgrade and build a dictionary
+ # of individual modules and their individual upgrade status.
+ # Sample Line:
+ #
+ # Module Image Running-Version(pri:alt) New-Version Upg-Required
+ # ------ ----- ---------------------------------------- ------------
+ # 8 lcn9k 7.0(3)F3(2) 7.0(3)F2(2) yes
+ mo = re.search(r"(\d+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(yes|no)", x)
+ if mo:
+ ud["processed"].append(x)
+ key = "m%s_%s" % (mo.group(1), mo.group(2))
+ field = "upgrade_needed"
+ if mo.group(5) == "yes":
+ ud[field] = True
+ ud[key] = {field: True}
+ else:
+ ud[key] = {field: False}
+ continue
+
+ return ud
+
+
+def massage_install_data(data):
+ # Transport cli returns a list containing one result item.
+ # Transport nxapi returns a list containing two items. The second item
+ # contains the data we are interested in.
+ default_error_msg = "No install all data found"
+ if len(data) == 1:
+ result_data = data[0]
+ elif len(data) == 2:
+ result_data = data[1]
+ else:
+ result_data = default_error_msg
+
+ # Further processing may be needed for result_data
+ if len(data) == 2 and isinstance(data[1], dict):
+ if "clierror" in data[1].keys():
+ result_data = data[1]["clierror"]
+ elif "code" in data[1].keys() and data[1]["code"] == "500":
+ # We encountered a backend processing error for nxapi
+ result_data = data[1]["msg"]
+ else:
+ result_data = default_error_msg
+ return result_data
+
+
+def build_install_cmd_set(issu, image, kick, type, force=True):
+ commands = ["terminal dont-ask"]
+
+ # Different NX-OS platforms behave differently for
+ # disruptive and non-disruptive upgrade paths.
+ #
+ # 1) Combined kickstart/system image:
+ # * Use option 'non-disruptive' for issu.
+ # * Omit option 'non-disruptive' for disruptive upgrades.
+ # 2) Separate kickstart + system images.
+ # * Omit hidden 'force' option for issu.
+ # * Use hidden 'force' option for disruptive upgrades.
+ # * Note: Not supported on all platforms
+ if re.search(r"required|desired|yes", issu):
+ if kick is None:
+ issu_cmd = "non-disruptive"
+ else:
+ issu_cmd = ""
+ else:
+ if kick is None:
+ issu_cmd = ""
+ else:
+ issu_cmd = "force" if force else ""
+
+ if type == "impact":
+ rootcmd = "show install all impact"
+ # The force option is not available for the impact command.
+ if kick:
+ issu_cmd = ""
+ else:
+ rootcmd = "install all"
+ if kick is None:
+ commands.append("%s nxos %s %s" % (rootcmd, image, issu_cmd))
+ else:
+ commands.append("%s %s system %s kickstart %s" % (rootcmd, issu_cmd, image, kick))
+
+ return commands
+
+
+def parse_show_version(data):
+ version_data = {"raw": data[0].split("\n")}
+ version_data["version"] = ""
+ version_data["error"] = False
+ for x in version_data["raw"]:
+ mo = re.search(r"(kickstart|system|NXOS):\s+version\s+(\S+)", x)
+ if mo:
+ version_data["version"] = mo.group(2)
+ continue
+
+ if version_data["version"] == "":
+ version_data["error"] = True
+
+ return version_data
+
+
+def check_mode_legacy(module, issu, image, kick=None):
+ """Some platforms/images/transports don't support the 'install all impact'
+ command so we need to use a different method."""
+ current = execute_show_command(module, "show version", "json")[0]
+ # Call parse_show_data on empty string to create the default upgrade
+ # data structure dictionary
+ data = parse_show_install("")
+ upgrade_msg = "No upgrade required"
+
+ # Process System Image
+ data["error"] = False
+ tsver = "show version image bootflash:%s" % image
+ data["upgrade_cmd"] = [tsver]
+ target_image = parse_show_version(execute_show_command(module, tsver))
+ if target_image["error"]:
+ data["error"] = True
+ data["raw"] = target_image["raw"]
+ if current["kickstart_ver_str"] != target_image["version"] and not data["error"]:
+ data["upgrade_needed"] = True
+ data["disruptive"] = True
+ upgrade_msg = "Switch upgraded: system: %s" % tsver
+
+ # Process Kickstart Image
+ if kick is not None and not data["error"]:
+ tkver = "show version image bootflash:%s" % kick
+ data["upgrade_cmd"].append(tsver)
+ target_kick = parse_show_version(execute_show_command(module, tkver))
+ if target_kick["error"]:
+ data["error"] = True
+ data["raw"] = target_kick["raw"]
+ if current["kickstart_ver_str"] != target_kick["version"] and not data["error"]:
+ data["upgrade_needed"] = True
+ data["disruptive"] = True
+ upgrade_msg = upgrade_msg + " kickstart: %s" % tkver
+
+ data["list_data"] = data["raw"]
+ data["processed"] = upgrade_msg
+ return data
+
+
+def check_mode_nextgen(module, issu, image, kick=None):
+ """Use the 'install all impact' command for check_mode"""
+ opts = {"ignore_timeout": True}
+ commands = build_install_cmd_set(issu, image, kick, "impact")
+ data = parse_show_install(load_config(module, commands, True, opts))
+ # If an error is encountered when issu is 'desired' then try again
+ # but set issu to 'no'
+ if data["error"] and issu == "desired":
+ issu = "no"
+ commands = build_install_cmd_set(issu, image, kick, "impact")
+ # The system may be busy from the previous call to check_mode so loop
+ # until it's done.
+ data = check_install_in_progress(module, commands, opts)
+ if data["server_error"]:
+ data["error"] = True
+ data["upgrade_cmd"] = commands
+ return data
+
+
+def check_install_in_progress(module, commands, opts):
+ for attempt in range(20):
+ data = parse_show_install(load_config(module, commands, True, opts))
+ if data["install_in_progress"]:
+ sleep(1)
+ continue
+ break
+ return data
+
+
+def check_mode(module, issu, image, kick=None):
+ """Check switch upgrade impact using 'show install all impact' command"""
+ data = check_mode_nextgen(module, issu, image, kick)
+ if data["server_error"]:
+ # We encountered an unrecoverable error in the attempt to get upgrade
+ # impact data from the 'show install all impact' command.
+ # Fallback to legacy method.
+ data = check_mode_legacy(module, issu, image, kick)
+ if data["invalid_command"]:
+ # If we are upgrading from a device running a separate kickstart and
+ # system image the impact command will fail.
+ # Fallback to legacy method.
+ data = check_mode_legacy(module, issu, image, kick)
+ return data
+
+
+def do_install_all(module, issu, image, kick=None):
+ """Perform the switch upgrade using the 'install all' command"""
+ impact_data = check_mode(module, issu, image, kick)
+ if module.check_mode:
+ # Check mode set in the playbook so just return the impact data.
+ msg = "*** SWITCH WAS NOT UPGRADED: IMPACT DATA ONLY ***"
+ impact_data["processed"].append(msg)
+ return impact_data
+ if impact_data["error"]:
+ # Check mode discovered an error so return with this info.
+ return impact_data
+ elif not impact_data["upgrade_needed"]:
+ # The switch is already upgraded. Nothing more to do.
+ return impact_data
+ else:
+ # If we get here, check_mode returned no errors and the switch
+ # needs to be upgraded.
+ if impact_data["disruptive"]:
+ # Check mode indicated that ISSU is not possible so issue the
+ # upgrade command without the non-disruptive flag unless the
+ # playbook specified issu: yes/required.
+ if issu == "yes":
+ msg = "ISSU/ISSD requested but impact data indicates ISSU/ISSD is not possible"
+ module.fail_json(msg=msg, raw_data=impact_data["list_data"])
+ else:
+ issu = "no"
+
+ commands = build_install_cmd_set(issu, image, kick, "install")
+ opts = {"ignore_timeout": True}
+ # The system may be busy from the call to check_mode so loop until
+ # it's done.
+ upgrade = check_install_in_progress(module, commands, opts)
+ if upgrade["invalid_command"] and "force" in commands[1]:
+ # Not all platforms support the 'force' keyword. Check for this
+ # condition and re-try without the 'force' keyword if needed.
+ commands = build_install_cmd_set(issu, image, kick, "install", False)
+ upgrade = check_install_in_progress(module, commands, opts)
+ upgrade["upgrade_cmd"] = commands
+
+ # Special case: If we encounter a server error at this stage
+ # it means the command was sent and the upgrade was started but
+ # we will need to use the impact data instead of the current install
+ # data.
+ if upgrade["server_error"]:
+ upgrade["upgrade_succeeded"] = True
+ upgrade["use_impact_data"] = True
+
+ if upgrade["use_impact_data"]:
+ if upgrade["upgrade_succeeded"]:
+ upgrade = impact_data
+ upgrade["upgrade_succeeded"] = True
+ else:
+ upgrade = impact_data
+ upgrade["upgrade_succeeded"] = False
+
+ if not upgrade["upgrade_succeeded"]:
+ upgrade["error"] = True
+ return upgrade
+
+
+def main():
+ argument_spec = dict(
+ system_image_file=dict(required=True),
+ kickstart_image_file=dict(required=False),
+ issu=dict(choices=["required", "desired", "no", "yes"], default="no"),
+ )
+
+ module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)
+
+ warnings = list()
+
+ # Get system_image_file(sif), kickstart_image_file(kif) and
+ # issu settings from module params.
+ sif = module.params["system_image_file"]
+ kif = module.params["kickstart_image_file"]
+ issu = module.params["issu"]
+
+ if re.search(r"(yes|required)", issu):
+ issu = "yes"
+
+ if kif == "null" or kif == "":
+ kif = None
+
+ install_result = do_install_all(module, issu, sif, kick=kif)
+ if install_result["error"]:
+ cmd = install_result["upgrade_cmd"]
+ msg = "Failed to upgrade device using command: %s" % cmd
+ module.fail_json(msg=msg, raw_data=install_result["list_data"])
+
+ state = install_result["processed"]
+ changed = install_result["upgrade_needed"]
+ module.exit_json(changed=changed, install_state=state, warnings=warnings)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_interfaces.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_interfaces.py
new file mode 100644
index 00000000..f03f746b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_interfaces.py
@@ -0,0 +1,455 @@
+#!/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 nxos_interfaces
+"""
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_interfaces
+short_description: Interfaces resource module
+description: This module manages the interface attributes of NX-OS interfaces.
+version_added: 1.0.0
+author: Trishna Guha (@trishnaguha)
+notes:
+- Tested against NXOS 7.3.(0)D1(1) on VIRL
+- Unsupported for Cisco MDS
+options:
+ running_config:
+ description:
+ - This option is used only with state I(parsed).
+ - The value of this option should be the output received from the NX-OS device
+ by executing the command B(show running-config | section ^interface)
+ - The state I(parsed) reads the configuration from C(running_config) option and
+ transforms it into Ansible structured data as per the resource module's argspec
+ and the value is then returned in the I(parsed) key within the result.
+ type: str
+ config:
+ description: A dictionary of interface options
+ type: list
+ elements: dict
+ suboptions:
+ name:
+ description:
+ - Full name of interface, e.g. Ethernet1/1, port-channel10.
+ type: str
+ required: true
+ description:
+ description:
+ - Interface description.
+ type: str
+ enabled:
+ description:
+ - Administrative state of the interface. Set the value to C(true) to administratively
+ enable the interface or C(false) to disable it
+ type: bool
+ speed:
+ description:
+ - Interface link speed. Applicable for Ethernet interfaces only.
+ type: str
+ mode:
+ description:
+ - Manage Layer2 or Layer3 state of the interface. Applicable for Ethernet
+ and port channel interfaces only.
+ choices:
+ - layer2
+ - layer3
+ type: str
+ mtu:
+ description:
+ - MTU for a specific interface. Must be an even number between 576 and 9216.
+ Applicable for Ethernet interfaces only.
+ type: str
+ duplex:
+ description:
+ - Interface link status. Applicable for Ethernet interfaces only.
+ type: str
+ choices:
+ - full
+ - half
+ - auto
+ ip_forward:
+ description:
+ - Enable or disable IP forward feature on SVIs. Set the value to C(true) to
+ enable or C(false) to disable.
+ type: bool
+ fabric_forwarding_anycast_gateway:
+ description:
+ - Associate SVI with anycast gateway under VLAN configuration mode. Applicable
+ for SVI interfaces only.
+ type: bool
+ state:
+ description:
+ - The state of the configuration after module completion
+ - The state I(rendered) considers the system default mode for interfaces to be
+ "Layer 3" and the system default state for interfaces to be shutdown.
+ - The state I(purged) negates virtual interfaces that are specified in task
+ from running-config.
+ type: str
+ choices:
+ - merged
+ - replaced
+ - overridden
+ - deleted
+ - gathered
+ - rendered
+ - parsed
+ - purged
+ default: merged
+
+"""
+EXAMPLES = """
+# Using merged
+
+# Before state:
+# -------------
+#
+# interface Ethernet1/1
+# description testing
+# mtu 1800
+
+- name: Merge provided configuration with device configuration
+ cisco.nxos.nxos_interfaces:
+ config:
+ - name: Ethernet1/1
+ description: Configured by Ansible
+ enabled: true
+ - name: Ethernet1/2
+ description: Configured by Ansible Network
+ enabled: false
+ state: merged
+
+# After state:
+# ------------
+#
+# interface Ethernet1/1
+# description Configured by Ansible
+# no shutdown
+# mtu 1800
+# interface Ethernet2
+# description Configured by Ansible Network
+# shutdown
+
+
+# Using replaced
+
+# Before state:
+# -------------
+#
+# interface Ethernet1/1
+# description Interface 1/1
+# interface Ethernet1/2
+
+- name: Replaces device configuration of listed interfaces with provided configuration
+ cisco.nxos.nxos_interfaces:
+ config:
+ - name: Ethernet1/1
+ description: Configured by Ansible
+ enabled: true
+ mtu: 2000
+ - name: Ethernet1/2
+ description: Configured by Ansible Network
+ enabled: false
+ mode: layer2
+ state: replaced
+
+# After state:
+# ------------
+#
+# interface Ethernet1/1
+# description Configured by Ansible
+# no shutdown
+# mtu 1500
+# interface Ethernet2/2
+# description Configured by Ansible Network
+# shutdown
+# switchport
+
+
+# Using overridden
+
+# Before state:
+# -------------
+#
+# interface Ethernet1/1
+# description Interface Ethernet1/1
+# interface Ethernet1/2
+# interface mgmt0
+# description Management interface
+# ip address dhcp
+
+- name: Override device configuration of all interfaces with provided configuration
+ cisco.nxos.nxos_interfaces:
+ config:
+ - name: Ethernet1/1
+ enabled: true
+ - name: Ethernet1/2
+ description: Configured by Ansible Network
+ enabled: false
+ state: overridden
+
+# After state:
+# ------------
+#
+# interface Ethernet1/1
+# interface Ethernet1/2
+# description Configured by Ansible Network
+# shutdown
+# interface mgmt0
+# ip address dhcp
+
+
+# Using deleted
+
+# Before state:
+# -------------
+#
+# interface Ethernet1/1
+# description Interface Ethernet1/1
+# interface Ethernet1/2
+# interface mgmt0
+# description Management interface
+# ip address dhcp
+
+- name: Delete or return interface parameters to default settings
+ cisco.nxos.nxos_interfaces:
+ config:
+ - name: Ethernet1/1
+ state: deleted
+
+# After state:
+# ------------
+#
+# interface Ethernet1/1
+# interface Ethernet1/2
+# interface mgmt0
+# description Management interface
+# ip address dhcp
+
+# Using rendered
+
+- name: Use rendered state to convert task input to device specific commands
+ cisco.nxos.nxos_interfaces:
+ config:
+ - name: Ethernet1/1
+ description: outbound-intf
+ mode: layer3
+ speed: 100
+ - name: Ethernet1/2
+ mode: layer2
+ enabled: true
+ duplex: full
+ state: rendered
+
+# Task Output (redacted)
+# -----------------------
+
+# rendered:
+# - "interface Ethernet1/1"
+# - "description outbound-intf"
+# - "speed 100"
+# - "interface Ethernet1/2"
+# - "switchport"
+# - "duplex full"
+# - "no shutdown"
+
+# Using parsed
+
+# parsed.cfg
+# ------------
+# interface Ethernet1/800
+# description test-1
+# speed 1000
+# shutdown
+# no switchport
+# duplex half
+# interface Ethernet1/801
+# description test-2
+# switchport
+# no shutdown
+# mtu 1800
+
+- name: Use parsed state to convert externally supplied config to structured format
+ cisco.nxos.nxos_interfaces:
+ running_config: "{{ lookup('file', 'parsed.cfg') }}"
+ state: parsed
+
+# Task output (redacted)
+# -----------------------
+# parsed:
+# - description: "test-1"
+# duplex: "half"
+# enabled: false
+# mode: "layer3"
+# name: "Ethernet1/800"
+# speed: "1000"
+#
+# - description: "test-2"
+# enabled: true
+# mode: "layer2"
+# mtu: "1800"
+# name: "Ethernet1/801"
+
+# Using gathered
+
+# Existing device config state
+# -----------------------------
+# interface Ethernet1/1
+# description outbound-intf
+# switchport
+# no shutdown
+# interface Ethernet1/2
+# description intf-l3
+# speed 1000
+# interface Ethernet1/3
+# interface Ethernet1/4
+# interface Ethernet1/5
+
+- name: Gather interfaces facts from the device using nxos_interfaces
+ cisco.nxos.nxos_interfaces:
+ state: gathered
+
+# Task output (redacted)
+# -----------------------
+# - name: Ethernet1/1
+# description: outbound-intf
+# mode: layer2
+# enabled: True
+# - name: Ethernet1/2
+# description: intf-l3
+# speed: "1000"
+
+# Using purged
+
+# Existing device config state
+# -----------------------------
+# interface Vlan1
+# interface Vlan42
+# mtu 1800
+# interface port-channel10
+# interface port-channel11
+# interface Ethernet1/1
+# interface Ethernet1/2
+# interface Ethernet1/2.100
+# description sub-intf
+
+- name: Purge virtual interfaces from running-config
+ cisco.nxos.nxos_interfaces:
+ config:
+ - name: Vlan42
+ - name: port-channel10
+ - name: Ethernet1/2.100
+ state: purged
+
+# Task output
+# ------------
+# before:
+# - name: Vlan1
+# - mtu: '1800'
+# name: Vlan42
+# - name: port-channel10
+# - name: port-channel11
+# - name: Ethernet1/1
+# - name: Ethernet1/2
+# - description: sub-intf
+# name: Ethernet1/2.100
+#
+# commands:
+# - no interface port-channel10
+# - no interface Ethernet1/2.100
+# - no interface Vlan42
+#
+# after:
+# - name: Vlan1
+# - name: port-channel11
+# - name: Ethernet1/1
+# - name: Ethernet1/2
+"""
+RETURN = """
+before:
+ description: The configuration as structured data prior to module invocation.
+ returned: always
+ type: list
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+after:
+ description: The configuration as structured data after module completion.
+ returned: when changed
+ type: list
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+commands:
+ description: The set of commands pushed to the remote device.
+ returned: always
+ type: list
+ sample: ['interface Ethernet1/1', 'mtu 1800']
+"""
+
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.interfaces.interfaces import (
+ InterfacesArgs,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.config.interfaces.interfaces import (
+ Interfaces,
+)
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ required_if = [
+ ("state", "merged", ("config",)),
+ ("state", "replaced", ("config",)),
+ ("state", "overridden", ("config",)),
+ ("state", "rendered", ("config",)),
+ ("state", "parsed", ("running_config",)),
+ ]
+ mutually_exclusive = [("config", "running_config")]
+
+ module = AnsibleModule(
+ argument_spec=InterfacesArgs.argument_spec,
+ required_if=required_if,
+ mutually_exclusive=mutually_exclusive,
+ supports_check_mode=True,
+ )
+
+ result = Interfaces(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_l2_interfaces.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_l2_interfaces.py
new file mode 100644
index 00000000..3e73c432
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_l2_interfaces.py
@@ -0,0 +1,408 @@
+#!/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 nxos_l2_interfaces
+"""
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_l2_interfaces
+short_description: L2 interfaces resource module
+description: This module manages Layer-2 interfaces attributes of NX-OS Interfaces.
+version_added: 1.0.0
+author: Trishna Guha (@trishnaguha)
+notes:
+- Tested against NXOS 7.3.(0)D1(1) on VIRL
+- Unsupported for Cisco MDS
+options:
+ running_config:
+ description:
+ - This option is used only with state I(parsed).
+ - The value of this option should be the output received from the NX-OS device
+ by executing the command B(show running-config | section ^interface).
+ - The state I(parsed) reads the configuration from C(running_config) option and
+ transforms it into Ansible structured data as per the resource module's argspec
+ and the value is then returned in the I(parsed) key within the result.
+ type: str
+ config:
+ description: A dictionary of Layer-2 interface options
+ type: list
+ elements: dict
+ suboptions:
+ name:
+ description:
+ - Full name of interface, i.e. Ethernet1/1.
+ type: str
+ required: true
+ access:
+ description:
+ - Switchport mode access command to configure the interface as a Layer-2 access.
+ type: dict
+ suboptions:
+ vlan:
+ description:
+ - Configure given VLAN in access port. It's used as the access VLAN ID.
+ type: int
+ trunk:
+ description:
+ - Switchport mode trunk command to configure the interface as a Layer-2 trunk.
+ type: dict
+ suboptions:
+ native_vlan:
+ description:
+ - Native VLAN to be configured in trunk port. It is used as the trunk
+ native VLAN ID.
+ type: int
+ allowed_vlans:
+ description:
+ - List of allowed VLANs in a given trunk port. These are the only VLANs
+ that will be configured on the trunk.
+ type: str
+ mode:
+ description:
+ - Mode in which interface needs to be configured.
+ - Access mode is not shown in interface facts, so idempotency will not be
+ maintained for switchport mode access and every time the output will come
+ as changed=True.
+ type: str
+ choices:
+ - dot1q-tunnel
+ - access
+ - trunk
+ - fex-fabric
+ - fabricpath
+ state:
+ description:
+ - The state of the configuration after module completion.
+ type: str
+ choices:
+ - merged
+ - replaced
+ - overridden
+ - deleted
+ - gathered
+ - rendered
+ - parsed
+ default: merged
+
+"""
+EXAMPLES = """
+# Using merged
+
+# Before state:
+# -------------
+#
+# interface Ethernet1/1
+# switchport access vlan 20
+# interface Ethernet1/2
+# switchport trunk native vlan 20
+# interface mgmt0
+# ip address dhcp
+# ipv6 address auto-config
+
+- name: Merge provided configuration with device configuration.
+ cisco.nxos.nxos_l2_interfaces:
+ config:
+ - name: Ethernet1/1
+ trunk:
+ native_vlan: 10
+ allowed_vlans: 2,4,15
+ - name: Ethernet1/2
+ access:
+ vlan: 30
+ state: merged
+
+# After state:
+# ------------
+#
+# interface Ethernet1/1
+# switchport trunk native vlan 10
+# switchport trunk allowed vlans 2,4,15
+# interface Ethernet1/2
+# switchport access vlan 30
+# interface mgmt0
+# ip address dhcp
+# ipv6 address auto-config
+
+
+# Using replaced
+
+# Before state:
+# -------------
+#
+# interface Ethernet1/1
+# switchport access vlan 20
+# interface Ethernet1/2
+# switchport trunk native vlan 20
+# interface mgmt0
+# ip address dhcp
+# ipv6 address auto-config
+
+- name: Replace device configuration of specified L2 interfaces with provided configuration.
+ cisco.nxos.nxos_l2_interfaces:
+ config:
+ - name: Ethernet1/1
+ trunk:
+ native_vlan: 20
+ allowed_vlans: 5-10, 15
+ state: replaced
+
+# After state:
+# ------------
+#
+# interface Ethernet1/1
+# switchport trunk native vlan 20
+# switchport trunk allowed vlan 5-10,15
+# interface Ethernet1/2
+# switchport trunk native vlan 20
+# switchport mode trunk
+# interface mgmt0
+# ip address dhcp
+# ipv6 address auto-config
+
+
+# Using overridden
+
+# Before state:
+# -------------
+#
+# interface Ethernet1/1
+# switchport access vlan 20
+# interface Ethernet1/2
+# switchport trunk native vlan 20
+# interface mgmt0
+# ip address dhcp
+# ipv6 address auto-config
+
+- name: Override device configuration of all L2 interfaces on device with provided
+ configuration.
+ cisco.nxos.nxos_l2_interfaces:
+ config:
+ - name: Ethernet1/2
+ access:
+ vlan: 30
+ state: overridden
+
+# After state:
+# ------------
+#
+# interface Ethernet1/1
+# interface Ethernet1/2
+# switchport access vlan 30
+# interface mgmt0
+# ip address dhcp
+# ipv6 address auto-config
+
+
+# Using deleted
+
+# Before state:
+# -------------
+#
+# interface Ethernet1/1
+# switchport access vlan 20
+# interface Ethernet1/2
+# switchport trunk native vlan 20
+# interface mgmt0
+# ip address dhcp
+# ipv6 address auto-config
+
+- name: Delete L2 attributes of given interfaces (Note This won't delete the interface
+ itself).
+ cisco.nxos.nxos_l2_interfaces:
+ config:
+ - name: Ethernet1/1
+ - name: Ethernet1/2
+ state: deleted
+
+# After state:
+# ------------
+#
+# interface Ethernet1/1
+# interface Ethernet1/2
+# interface mgmt0
+# ip address dhcp
+# ipv6 address auto-config
+
+# Using rendered
+
+- name: Render platform specific configuration lines (without connecting to the device)
+ cisco.nxos.nxos_l2_interfaces:
+ config:
+ - name: Ethernet1/1
+ trunk:
+ native_vlan: 10
+ allowed_vlans: 2,4,15
+ - name: Ethernet1/2
+ access:
+ vlan: 30
+ - name: Ethernet1/3
+ trunk:
+ native_vlan: 20
+ allowed_vlans: 5-10, 15
+ state: rendered
+
+# Task Output (redacted)
+# -----------------------
+
+# rendered:
+# - "interface Ethernet1/1"
+# - "switchport trunk allowed vlan 2,4,15"
+# - "switchport trunk native vlan 10"
+# - "interface Ethernet1/2"
+# - "switchport access vlan 30"
+# - "interface Ethernet1/3"
+# - "switchport trunk allowed vlan 5,6,7,8,9,10,15"
+# - "switchport trunk native vlan 20"
+
+# Using parsed
+
+# parsed.cfg
+# ------------
+# interface Ethernet1/800
+# switchport access vlan 18
+# switchport trunk allowed vlan 210
+# interface Ethernet1/801
+# switchport trunk allowed vlan 2,4,15
+
+- name: Use parsed state to convert externally supplied config to structured format
+ cisco.nxos.nxos_l2_interfaces:
+ running_config: "{{ lookup('file', 'parsed.cfg') }}"
+ state: parsed
+
+# Task output (redacted)
+# -----------------------
+# parsed:
+# - name: Ethernet1/800
+# access:
+# vlan: 18
+# trunk:
+# allowed_vlans: "210"
+# - name: Ethernet1/801
+# trunk:
+# allowed_vlans: "2,4,15"
+
+# Using gathered
+
+# Existing device config state
+# -------------------------------
+# Nexus9kvI5# sh running-config | section ^interface
+# interface Ethernet1/1
+# switchport access vlan 6
+# switchport trunk allowed vlan 200
+# interface Ethernet1/2
+# switchport trunk native vlan 10
+
+- name: Gather l2_interfaces facts from the device using nxos_l2_interfaces
+ cisco.nxos.nxos_l2_interfaces:
+ state: gathered
+
+# Task output (redacted)
+# -----------------------
+# gathered:
+# - name: "Ethernet1/1"
+# access:
+# vlan: 6
+# trunk:
+# allowed_vlans: "200"
+#
+# - name: "Ethernet1/2"
+# trunk:
+# native_vlan: 10
+"""
+RETURN = """
+before:
+ description: The configuration as structured data prior to module invocation.
+ returned: always
+ type: list
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+after:
+ description: The configuration as structured data after module completion.
+ returned: when changed
+ type: list
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+commands:
+ description: The set of commands pushed to the remote device.
+ returned: always
+ type: list
+ sample:
+ - "interface Ethernet1/1"
+ - "switchport trunk allowed vlan 2,4,15"
+ - "switchport trunk native vlan 10"
+ - "interface Ethernet1/2"
+ - "switchport access vlan 30"
+ - "interface Ethernet1/3"
+ - "switchport trunk allowed vlan 5,6,7,8,9,10,15"
+ - "switchport trunk native vlan 20"
+"""
+
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.l2_interfaces.l2_interfaces import (
+ L2_interfacesArgs,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.config.l2_interfaces.l2_interfaces import (
+ L2_interfaces,
+)
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ required_if = [
+ ("state", "merged", ("config",)),
+ ("state", "replaced", ("config",)),
+ ("state", "overridden", ("config",)),
+ ("state", "rendered", ("config",)),
+ ("state", "parsed", ("running_config",)),
+ ]
+ mutually_exclusive = [("config", "running_config")]
+
+ module = AnsibleModule(
+ argument_spec=L2_interfacesArgs.argument_spec,
+ required_if=required_if,
+ mutually_exclusive=mutually_exclusive,
+ supports_check_mode=True,
+ )
+
+ result = L2_interfaces(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_l3_interfaces.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_l3_interfaces.py
new file mode 100644
index 00000000..7b5cb7a6
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_l3_interfaces.py
@@ -0,0 +1,411 @@
+#!/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 nxos_l3_interfaces
+"""
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_l3_interfaces
+short_description: L3 interfaces resource module
+description: This module manages Layer-3 interfaces attributes of NX-OS Interfaces.
+version_added: 1.0.0
+author: Trishna Guha (@trishnaguha)
+notes:
+- Tested against NXOS 7.3.(0)D1(1) on VIRL
+- Unsupported for Cisco MDS
+options:
+ running_config:
+ description:
+ - This option is used only with state I(parsed).
+ - The value of this option should be the output received from the NX-OS device
+ by executing the command B(show running-config | section '^interface').
+ - The state I(parsed) reads the configuration from C(running_config) option and
+ transforms it into Ansible structured data as per the resource module's argspec
+ and the value is then returned in the I(parsed) key within the result.
+ type: str
+ config:
+ description: A dictionary of Layer-3 interface options
+ type: list
+ elements: dict
+ suboptions:
+ name:
+ description:
+ - Full name of L3 interface, i.e. Ethernet1/1.
+ type: str
+ required: true
+ dot1q:
+ description:
+ - Configures IEEE 802.1Q VLAN encapsulation on a subinterface.
+ type: int
+ ipv4:
+ description:
+ - IPv4 address and attributes of the L3 interface.
+ type: list
+ elements: dict
+ suboptions:
+ address:
+ description:
+ - IPV4 address of the L3 interface.
+ type: str
+ tag:
+ description:
+ - URIB route tag value for local/direct routes.
+ type: int
+ secondary:
+ description:
+ - A boolean attribute to manage addition of secondary IP address.
+ type: bool
+ ipv6:
+ description:
+ - IPv6 address and attributes of the L3 interface.
+ type: list
+ elements: dict
+ suboptions:
+ address:
+ description:
+ - IPV6 address of the L3 interface.
+ type: str
+ tag:
+ description:
+ - URIB route tag value for local/direct routes.
+ type: int
+ redirects:
+ description:
+ - Enables/disables ipv4 redirects.
+ type: bool
+ ipv6_redirects:
+ description:
+ - Enables/disables ipv6 redirects.
+ type: bool
+ unreachables:
+ description:
+ - Enables/disables ip redirects
+ type: bool
+ evpn_multisite_tracking:
+ description:
+ - VxLAN evpn multisite Interface tracking. Supported only on selected model.
+ type: str
+ version_added: 1.1.0
+ choices:
+ - fabric-tracking
+ - dci-tracking
+ state:
+ description:
+ - The state of the configuration after module completion.
+ - The state I(overridden) would override the IP address configuration
+ of all interfaces on the device with the provided configuration in
+ the task. Use caution with this state as you may loose access to the
+ device.
+ type: str
+ choices:
+ - merged
+ - replaced
+ - overridden
+ - deleted
+ - gathered
+ - rendered
+ - parsed
+ default: merged
+
+"""
+EXAMPLES = """
+# Using merged
+
+# Before state:
+# -------------
+#
+# interface Ethernet1/6
+
+- name: Merge provided configuration with device configuration.
+ cisco.nxos.nxos_l3_interfaces:
+ config:
+ - name: Ethernet1/6
+ ipv4:
+ - address: 192.168.1.1/24
+ tag: 5
+ - address: 10.1.1.1/24
+ secondary: true
+ tag: 10
+ ipv6:
+ - address: fd5d:12c9:2201:2::1/64
+ tag: 6
+ - name: Ethernet1/7.42
+ dot1q: 42
+ redirects: false
+ unreachables: false
+ state: merged
+
+# After state:
+# ------------
+#
+# interface Ethernet1/6
+# ip address 192.168.22.1/24 tag 5
+# ip address 10.1.1.1/24 secondary tag 10
+# interface Ethernet1/6
+# ipv6 address fd5d:12c9:2201:2::1/64 tag 6
+# interface Ethernet1/7.42
+# encapsulation dot1q 42
+# no ip redirects
+# no ip unreachables
+
+
+# Using replaced
+
+# Before state:
+# -------------
+#
+# interface Ethernet1/6
+# ip address 192.168.22.1/24
+# ipv6 address "fd5d:12c9:2201:1::1/64"
+
+- name: Replace device configuration of specified L3 interfaces with provided configuration.
+ cisco.nxos.nxos_l3_interfaces:
+ config:
+ - name: Ethernet1/6
+ ipv4:
+ - address: 192.168.22.3/24
+ state: replaced
+
+# After state:
+# ------------
+#
+# interface Ethernet1/6
+# ip address 192.168.22.3/24
+
+
+# Using overridden
+
+# Before state:
+# -------------
+#
+# interface Ethernet1/2
+# ip address 192.168.22.1/24
+# interface Ethernet1/6
+# ipv6 address "fd5d:12c9:2201:1::1/64"
+
+- name: Override device configuration of all L3 interfaces on device with provided
+ configuration.
+ cisco.nxos.nxos_l3_interfaces:
+ config:
+ - name: Ethernet1/2
+ ipv4: 192.168.22.3/4
+ state: overridden
+
+# After state:
+# ------------
+#
+# interface Ethernet1/2
+# ipv4 address 192.168.22.3/24
+# interface Ethernet1/6
+
+
+# Using deleted
+
+# Before state:
+# -------------
+#
+# interface Ethernet1/6
+# ip address 192.168.22.1/24
+# interface Ethernet1/2
+# ipv6 address "fd5d:12c9:2201:1::1/64"
+
+- name: Delete L3 attributes of given interfaces (This won't delete the interface
+ itself).
+ cisco.nxos.nxos_l3_interfaces:
+ config:
+ - name: Ethernet1/6
+ - name: Ethernet1/2
+ state: deleted
+
+# After state:
+# ------------
+#
+# interface Ethernet1/6
+# interface Ethernet1/2
+
+# Using rendered
+
+- name: Use rendered state to convert task input to device specific commands
+ cisco.nxos.nxos_l3_interfaces:
+ config:
+ - name: Ethernet1/800
+ ipv4:
+ - address: 192.168.1.100/24
+ tag: 5
+ - address: 10.1.1.1/24
+ secondary: true
+ tag: 10
+ - name: Ethernet1/800
+ ipv6:
+ - address: fd5d:12c9:2201:2::1/64
+ tag: 6
+ state: rendered
+
+# Task Output (redacted)
+# -----------------------
+
+# rendered:
+# - "interface Ethernet1/800"
+# - "ip address 192.168.1.100/24 tag 5"
+# - "ip address 10.1.1.1/24 secondary tag 10"
+# - "interface Ethernet1/800"
+# - "ipv6 address fd5d:12c9:2201:2::1/64 tag 6"
+
+# Using parsed
+
+# parsed.cfg
+# ------------
+# interface Ethernet1/800
+# ip address 192.168.1.100/24 tag 5
+# ip address 10.1.1.1/24 secondary tag 10
+# no ip redirects
+# interface Ethernet1/801
+# ipv6 address fd5d:12c9:2201:2::1/64 tag 6
+# ip unreachables
+# interface mgmt0
+# ip address dhcp
+# vrf member management
+
+- name: Use parsed state to convert externally supplied config to structured format
+ cisco.nxos.nxos_l3_interfaces:
+ running_config: "{{ lookup('file', 'parsed.cfg') }}"
+ state: parsed
+
+# Task output (redacted)
+# -----------------------
+
+# parsed:
+# - name: Ethernet1/800
+# ipv4:
+# - address: 192.168.1.100/24
+# tag: 5
+# - address: 10.1.1.1/24
+# secondary: True
+# tag: 10
+# redirects: False
+# - name: Ethernet1/801
+# ipv6:
+# - address: fd5d:12c9:2201:2::1/64
+# tag: 6
+# unreachables: True
+
+# Using gathered
+
+# Existing device config state
+# -------------------------------
+# interface Ethernet1/1
+# ip address 192.0.2.100/24
+# interface Ethernet1/2
+# no ip redirects
+# ip address 203.0.113.10/24
+# ip unreachables
+# ipv6 address 2001:db8::1/32
+
+- name: Gather l3_interfaces facts from the device using nxos_l3_interfaces
+ cisco.nxos.nxos_l3_interfaces:
+ state: gathered
+
+# Task output (redacted)
+# -----------------------
+
+# gathered:
+# - name: Ethernet1/1
+# ipv4:
+# - address: 192.0.2.100/24
+# - name: Ethernet1/2
+# ipv4:
+# - address: 203.0.113.10/24
+# ipv6:
+# - address: 2001:db8::1/32
+# redirects: False
+# unreachables: True
+"""
+RETURN = """
+before:
+ description: The configuration as structured data prior to module invocation.
+ returned: always
+ type: list
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+after:
+ description: The configuration as structured data after module completion.
+ returned: when changed
+ type: list
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+commands:
+ description: The set of commands pushed to the remote device.
+ returned: always
+ type: list
+ sample: ['interface Ethernet1/2', 'ip address 192.168.0.1/2']
+"""
+
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.l3_interfaces.l3_interfaces import (
+ L3_interfacesArgs,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.config.l3_interfaces.l3_interfaces import (
+ L3_interfaces,
+)
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ required_if = [
+ ("state", "merged", ("config",)),
+ ("state", "replaced", ("config",)),
+ ("state", "overridden", ("config",)),
+ ("state", "rendered", ("config",)),
+ ("state", "parsed", ("running_config",)),
+ ]
+ mutually_exclusive = [("config", "running_config")]
+
+ module = AnsibleModule(
+ argument_spec=L3_interfacesArgs.argument_spec,
+ required_if=required_if,
+ mutually_exclusive=mutually_exclusive,
+ supports_check_mode=True,
+ )
+
+ result = L3_interfaces(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_lacp.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_lacp.py
new file mode 100644
index 00000000..23349113
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_lacp.py
@@ -0,0 +1,276 @@
+#!/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 nxos_lacp
+"""
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_lacp
+short_description: LACP resource module
+description: This module manages Global Link Aggregation Control Protocol (LACP) on
+ NX-OS devices.
+version_added: 1.0.0
+author: Trishna Guha (@trishnaguha)
+notes:
+- Tested against NXOS 7.3.(0)D1(1) on VIRL.
+- Unsupported for Cisco MDS
+- Feature lacp should be enabled for this module.
+options:
+ running_config:
+ description:
+ - This option is used only with state I(parsed).
+ - The value of this option should be the output received from the NX-OS device
+ by executing the command B(show running-config | include lacp).
+ - The state I(parsed) reads the configuration from C(running_config) option and
+ transforms it into Ansible structured data as per the resource module's argspec
+ and the value is then returned in the I(parsed) key within the result.
+ type: str
+ config:
+ description: LACP global options.
+ type: dict
+ suboptions:
+ system:
+ description:
+ - LACP system options
+ type: dict
+ suboptions:
+ priority:
+ description:
+ - The system priority to use in LACP negotiations.
+ type: int
+ mac:
+ description:
+ - MAC address to be used for the LACP Protocol exchanges
+ type: dict
+ suboptions:
+ address:
+ description:
+ - MAC-address (FORMAT :xxxx.xxxx.xxxx).
+ type: str
+ role:
+ description:
+ - The role for the Switch.
+ type: str
+ choices:
+ - primary
+ - secondary
+ state:
+ description:
+ - The state of the configuration after module completion.
+ type: str
+ choices:
+ - merged
+ - replaced
+ - deleted
+ - gathered
+ - rendered
+ - parsed
+ default: merged
+
+"""
+EXAMPLES = """
+# Using merged
+
+# Before state:
+# -------------
+#
+
+- name: Merge provided configuration with device configuration.
+ cisco.nxos.nxos_lacp:
+ config:
+ system:
+ priority: 10
+ mac:
+ address: 00c1.4c00.bd15
+ state: merged
+
+# After state:
+# ------------
+#
+# lacp system-priority 10
+# lacp system-mac 00c1.4c00.bd15
+
+
+# Using replaced
+
+# Before state:
+# -------------
+#
+# lacp system-priority 10
+
+- name: Replace device global lacp configuration with the given configuration.
+ cisco.nxos.nxos_lacp:
+ config:
+ system:
+ mac:
+ address: 00c1.4c00.bd15
+ state: replaced
+
+# After state:
+# ------------
+#
+# lacp system-mac 00c1.4c00.bd15
+
+
+# Using deleted
+
+# Before state:
+# -------------
+#
+# lacp system-priority 10
+
+- name: Delete global LACP configurations.
+ cisco.nxos.nxos_lacp:
+ state: deleted
+
+# After state:
+# ------------
+#
+
+# Using rendered
+
+- name: Render platform specific configuration lines (without connecting to the device)
+ cisco.nxos.nxos_lacp:
+ config:
+ system:
+ priority: 10
+ mac:
+ address: 00c1.4c00.bd15
+ role: secondary
+ state: rendered
+
+# Task Output (redacted)
+# -----------------------
+
+# rendered:
+# - "lacp system-priority 10"
+# - "lacp system-mac 00c1.4c00.bd15 role secondary"
+
+# Using parsed
+
+# parsed.cfg
+# ------------
+# lacp system-priority 10
+# lacp system-mac 00c1.4c00.bd15 role secondary
+
+- name: Use parsed state to convert externally supplied config to structured format
+ cisco.nxos.nxos_lacp:
+ running_config: "{{ lookup('file', 'parsed.cfg') }}"
+ state: parsed
+
+# Task output (redacted)
+# -----------------------
+# parsed:
+# system:
+# priority: 10
+# mac:
+# address: 00c1.4c00.bd15
+# role: secondary
+
+# Using gathered
+
+# Existing device config state
+# -------------------------------
+# Nexus9000v# show running-config | include lacp
+# lacp system-priority 11
+# lacp system-mac 00c1.4c00.bd15 role primary
+
+- name: Gather lacp facts from the device using nxos_lacp
+ cisco.nxos.nxos_lacp:
+ state: gathered
+
+# Task output (redacted)
+# -----------------------
+# gathered:
+# system:
+# priority: 11
+# mac:
+# address: 00c1.4c00.bd15
+# role: primary
+"""
+RETURN = """
+before:
+ description: The configuration as structured data prior to module invocation.
+ returned: always
+ type: dict
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+after:
+ description: The configuration as structured data after module completion.
+ returned: when changed
+ type: dict
+ 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: ['lacp system-priority 15', 'lacp system-mac 00c1.4c00.bd15 role primary']
+"""
+
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.lacp.lacp import (
+ LacpArgs,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.config.lacp.lacp import Lacp
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ required_if = [
+ ("state", "merged", ("config",)),
+ ("state", "replaced", ("config",)),
+ ("state", "rendered", ("config",)),
+ ("state", "parsed", ("running_config",)),
+ ]
+ mutually_exclusive = [("config", "running_config")]
+
+ module = AnsibleModule(
+ argument_spec=LacpArgs.argument_spec,
+ required_if=required_if,
+ mutually_exclusive=mutually_exclusive,
+ supports_check_mode=True,
+ )
+
+ result = Lacp(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_lacp_interfaces.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_lacp_interfaces.py
new file mode 100644
index 00000000..98e5a633
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_lacp_interfaces.py
@@ -0,0 +1,381 @@
+#!/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 nxos_lacp_interfaces
+"""
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_lacp_interfaces
+short_description: LACP interfaces resource module
+description: This module manages Link Aggregation Control Protocol (LACP) attributes
+ of NX-OS Interfaces.
+version_added: 1.0.0
+author: Trishna Guha (@trishnaguha)
+notes:
+- Tested against NXOS 7.3.(0)D1(1) on VIRL
+- Unsupported for Cisco MDS
+options:
+ running_config:
+ description:
+ - This option is used only with state I(parsed).
+ - The value of this option should be the output received from the NX-OS device
+ by executing the command B(show running-config | section ^interface).
+ - The state I(parsed) reads the configuration from C(running_config) option and
+ transforms it into Ansible structured data as per the resource module's argspec
+ and the value is then returned in the I(parsed) key within the result.
+ type: str
+ config:
+ description: A dictionary of LACP interfaces options.
+ type: list
+ elements: dict
+ suboptions:
+ name:
+ description:
+ - Name of the interface.
+ required: true
+ type: str
+ port_priority:
+ description:
+ - LACP port priority for the interface. Range 1-65535. Applicable only for
+ Ethernet.
+ type: int
+ rate:
+ description:
+ - Rate at which PDUs are sent by LACP. Applicable only for Ethernet. At fast
+ rate LACP is transmitted once every 1 second. At normal rate LACP is transmitted
+ every 30 seconds after the link is bundled.
+ type: str
+ choices:
+ - fast
+ - normal
+ links:
+ description:
+ - This dict contains configurable options related to max and min port-channel
+ links. Applicable only for Port-channel.
+ type: dict
+ suboptions:
+ max:
+ description:
+ - Port-channel max bundle.
+ type: int
+ min:
+ description:
+ - Port-channel min links.
+ type: int
+ mode:
+ description:
+ - LACP mode. Applicable only for Port-channel.
+ type: str
+ choices:
+ - delay
+ suspend_individual:
+ description:
+ - port-channel lacp state. Disabling this will cause lacp to put the port
+ to individual state and not suspend the port in case it does not get LACP
+ BPDU from the peer ports in the port-channel.
+ type: bool
+ convergence:
+ description:
+ - This dict contains configurable options related to convergence. Applicable
+ only for Port-channel.
+ type: dict
+ suboptions:
+ graceful:
+ description:
+ - port-channel lacp graceful convergence. Disable this only with lacp
+ ports connected to Non-Nexus peer. Disabling this with Nexus peer can
+ lead to port suspension.
+ type: bool
+ vpc:
+ description:
+ - Enable lacp convergence for vPC port channels.
+ type: bool
+ state:
+ description:
+ - The state of the configuration after module completion.
+ type: str
+ choices:
+ - merged
+ - replaced
+ - overridden
+ - deleted
+ - gathered
+ - rendered
+ - parsed
+ default: merged
+
+"""
+EXAMPLES = """
+# Using merged
+
+# Before state:
+# -------------
+#
+
+- name: Merge provided configuration with device configuration.
+ cisco.nxos.nxos_lacp_interfaces:
+ config:
+ - name: Ethernet1/3
+ port_priority: 5
+ rate: fast
+ state: merged
+
+# After state:
+# ------------
+#
+# interface Ethernet1/3
+# lacp port-priority 5
+# lacp rate fast
+
+
+# Using replaced
+
+# Before state:
+# -------------
+#
+# interface Ethernet1/3
+# lacp port-priority 5
+# interface port-channel11
+# lacp mode delay
+
+- name: Replace device lacp interfaces configuration with the given configuration.
+ cisco.nxos.nxos_lacp_interfaces:
+ config:
+ - name: port-channel11
+ links:
+ min: 4
+ state: replaced
+
+# After state:
+# ------------
+#
+# interface Ethernet1/3
+# lacp port-priority 5
+# interface port-channel11
+# lacp min-links 4
+
+
+# Using overridden
+
+# Before state:
+# -------------
+#
+# interface Ethernet1/3
+# lacp port-priority 5
+# interface port-channel11
+# lacp mode delay
+
+- name: Override device configuration of all LACP interfaces attributes of given interfaces
+ on device with provided configuration.
+ cisco.nxos.nxos_lacp_interfaces:
+ config:
+ - name: port-channel11
+ links:
+ min: 4
+ state: overridden
+
+# After state:
+# ------------
+#
+# interface port-channel11
+# lacp min-links 4
+
+
+# Using deleted
+
+# Before state:
+# -------------
+#
+# interface Ethernet1/3
+# lacp port-priority 5
+# interface port-channel11
+# lacp mode delay
+
+- name: Delete LACP interfaces configurations.
+ cisco.nxos.nxos_lacp_interfaces:
+ state: deleted
+
+# After state:
+# ------------
+#
+
+# Using rendered
+
+- name: Use rendered state to convert task input to device specific commands
+ cisco.nxos.nxos_lacp_interfaces:
+ config:
+ - name: Ethernet1/800
+ rate: fast
+ - name: Ethernet1/801
+ rate: fast
+ port_priority: 32
+ - name: port-channel10
+ links:
+ max: 15
+ min: 2
+ convergence:
+ graceful: true
+ state: rendered
+
+# Task Output (redacted)
+# -----------------------
+
+# rendered:
+# - "interface Ethernet1/800"
+# - "lacp rate fast"
+# - "interface Ethernet1/801"
+# - "lacp port-priority 32"
+# - "lacp rate fast"
+# - "interface port-channel10"
+# - "lacp min-links 2"
+# - "lacp max-bundle 15"
+# - "lacp graceful-convergence"
+
+# Using parsed
+
+# parsed.cfg
+# ------------
+
+# interface port-channel10
+# lacp min-links 10
+# lacp max-bundle 15
+# interface Ethernet1/800
+# lacp port-priority 100
+# lacp rate fast
+
+- name: Use parsed state to convert externally supplied config to structured format
+ cisco.nxos.nxos_lacp_interfaces:
+ running_config: "{{ lookup('file', 'parsed.cfg') }}"
+ state: parsed
+
+# Task output (redacted)
+# -----------------------
+
+# parsed:
+# - name: port-channel10
+# links:
+# max: 15
+# min: 10
+# - name: Ethernet1/800
+# port_priority: 100
+# rate: fast
+
+# Using gathered
+
+# Existing device config state
+# -------------------------------
+# interface Ethernet1/1
+# lacp port-priority 5
+# lacp rate fast
+# interface port-channel10
+# lacp mode delay
+# interface port-channel11
+# lacp max-bundle 10
+# lacp min-links 5
+
+- name: Gather lacp_interfaces facts from the device using nxos_lacp_interfaces
+ cisco.nxos.nxos_lacp_interfaces:
+ state: gathered
+
+# Task output (redacted)
+# -----------------------
+# gathered:
+# - name: Ethernet1/1
+# port_priority: 5
+# rate: fast
+# - name: port-channel10
+# mode: delay
+# - name: port-channel11
+# links:
+# max: 10
+# min: 5
+"""
+RETURN = """
+before:
+ description: The configuration as structured data prior to module invocation.
+ returned: always
+ type: list
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+after:
+ description: The configuration as structured data after module completion.
+ returned: when changed
+ type: list
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+commands:
+ description: The set of commands pushed to the remote device.
+ returned: always
+ type: list
+ sample: ['interface port-channel10', 'lacp min-links 5', 'lacp mode delay']
+"""
+
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.lacp_interfaces.lacp_interfaces import (
+ Lacp_interfacesArgs,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.config.lacp_interfaces.lacp_interfaces import (
+ Lacp_interfaces,
+)
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ required_if = [
+ ("state", "merged", ("config",)),
+ ("state", "replaced", ("config",)),
+ ("state", "overridden", ("config",)),
+ ("state", "rendered", ("config",)),
+ ("state", "parsed", ("running_config",)),
+ ]
+ mutually_exclusive = [("config", "running_config")]
+
+ module = AnsibleModule(
+ argument_spec=Lacp_interfacesArgs.argument_spec,
+ required_if=required_if,
+ mutually_exclusive=mutually_exclusive,
+ supports_check_mode=True,
+ )
+
+ result = Lacp_interfaces(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_lag_interfaces.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_lag_interfaces.py
new file mode 100644
index 00000000..ce06462e
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_lag_interfaces.py
@@ -0,0 +1,368 @@
+#!/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 nxos_lag_interfaces
+"""
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_lag_interfaces
+short_description: LAG interfaces resource module
+description: This module manages attributes of link aggregation groups of NX-OS Interfaces.
+version_added: 1.0.0
+author:
+- Trishna Guha (@trishnaguha)
+- Nilashish Chakraborty (@NilashishC)
+options:
+ running_config:
+ description:
+ - This option is used only with state I(parsed).
+ - The value of this option should be the output received from the NX-OS device
+ by executing the command B(show running-config | section ^interface).
+ - The state I(parsed) reads the configuration from C(running_config) option and
+ transforms it into Ansible structured data as per the resource module's argspec
+ and the value is then returned in the I(parsed) key within the result.
+ type: str
+ config:
+ description: A list of link aggregation group configurations.
+ type: list
+ elements: dict
+ suboptions:
+ name:
+ description:
+ - Name of the link aggregation group (LAG).
+ type: str
+ required: true
+ members:
+ description:
+ - The list of interfaces that are part of the group.
+ type: list
+ elements: dict
+ suboptions:
+ member:
+ description:
+ - The interface name.
+ type: str
+ mode:
+ description:
+ - Link aggregation group (LAG).
+ type: str
+ choices:
+ - 'active'
+ - 'on'
+ - 'passive'
+ force:
+ description:
+ - When true it forces link aggregation group members to match what is
+ declared in the members param. This can be used to remove members.
+ type: bool
+ state:
+ description:
+ - The state of the configuration after module completion.
+ type: str
+ choices:
+ - merged
+ - replaced
+ - overridden
+ - deleted
+ - gathered
+ - rendered
+ - parsed
+ default: merged
+notes:
+- Tested against NXOS 7.3.(0)D1(1) on VIRL.
+- Unsupported for Cisco MDS
+- This module works with connection C(network_cli).
+
+"""
+EXAMPLES = """
+# Using merged
+
+# Before state:
+# -------------
+#
+# interface Ethernet1/4
+
+- name: Merge provided configuration with device configuration.
+ cisco.nxos.nxos_lag_interfaces:
+ config:
+ - name: port-channel99
+ members:
+ - member: Ethernet1/4
+ state: merged
+
+# After state:
+# ------------
+#
+# interface Ethernet1/4
+# channel-group 99
+
+
+# Using replaced
+
+# Before state:
+# -------------
+#
+# interface Ethernet1/4
+# channel-group 99 mode active
+
+- name: Replace device configuration of specified LAG attributes of given interfaces
+ with provided configuration.
+ cisco.nxos.nxos_lag_interfaces:
+ config:
+ - name: port-channel10
+ members:
+ - member: Ethernet1/4
+ state: replaced
+
+# After state:
+# ------------
+#
+# interface Ethernet1/4
+# channel-group 10
+
+
+# Using overridden
+
+# Before state:
+# -------------
+#
+# interface Ethernet1/4
+# channel-group 10
+# interface Ethernet1/2
+# channel-group 99 mode passive
+
+- name: Override device configuration of all LAG attributes of given interfaces on
+ device with provided configuration.
+ cisco.nxos.nxos_lag_interfaces:
+ config:
+ - name: port-channel20
+ members:
+ - member: Ethernet1/6
+ force: true
+ state: overridden
+
+# After state:
+# ------------
+# interface Ethernet1/2
+# interface Ethernet1/4
+# interface Ethernet1/6
+# channel-group 20 force
+
+
+# Using deleted
+
+# Before state:
+# -------------
+#
+# interface Ethernet1/4
+# channel-group 99 mode active
+
+- name: Delete LAG attributes of given interface (This won't delete the port-channel
+ itself).
+ cisco.nxos.nxos_lag_interfaces:
+ config:
+ - port-channel: port-channel99
+ state: deleted
+
+- name: Delete LAG attributes of all the interfaces
+ cisco.nxos.nxos_lag_interfaces:
+ state: deleted
+
+# After state:
+# ------------
+#
+# interface Ethernet1/4
+# no channel-group 99
+
+# Using rendered
+
+- name: Use rendered state to convert task input to device specific commands
+ cisco.nxos.nxos_lag_interfaces:
+ config:
+ - name: port-channel10
+ members:
+ - member: Ethernet1/800
+ mode: active
+ - member: Ethernet1/801
+ - name: port-channel11
+ members:
+ - member: Ethernet1/802
+ mode: passive
+ state: rendered
+
+# Task Output (redacted)
+# -----------------------
+
+# rendered:
+# - "interface Ethernet1/800"
+# - "channel-group 10 mode active"
+# - "interface Ethernet1/801"
+# - "channel-group 10"
+# - "interface Ethernet1/802"
+# - "channel-group 11 mode passive"
+
+# Using parsed
+
+# parsed.cfg
+# ------------
+
+# interface port-channel10
+# interface port-channel11
+# interface port-channel12
+# interface Ethernet1/800
+# channel-group 10 mode active
+# interface Ethernet1/801
+# channel-group 10 mode active
+# interface Ethernet1/802
+# channel-group 11 mode passive
+# interface Ethernet1/803
+# channel-group 11 mode passive
+
+- name: Use parsed state to convert externally supplied config to structured format
+ cisco.nxos.nxos_lag_interfaces:
+ running_config: "{{ lookup('file', 'parsed.cfg') }}"
+ state: parsed
+
+# Task output (redacted)
+# -----------------------
+
+# parsed:
+# - members:
+# - member: Ethernet1/800
+# mode: active
+# - member: Ethernet1/801
+# mode: active
+# name: port-channel10
+#
+# - members:
+# - member: Ethernet1/802
+# mode: passive
+# - member: Ethernet1/803
+# mode: passive
+# name: port-channel11
+#
+# - name: port-channel12
+
+# Using gathered
+
+# Existing device config state
+# -------------------------------
+# interface port-channel10
+# interface port-channel11
+# interface Ethernet1/1
+# channel-group 10 mode active
+# interface Ethernet1/2
+# channel-group 11 mode passive
+#
+
+- name: Gather lag_interfaces facts from the device using nxos_lag_interfaces
+ cisco.nxos.nxos_lag_interfaces:
+ state: gathered
+
+# Task output (redacted)
+# -----------------------
+# gathered:
+# - name: port-channel10
+# members:
+# - member: Ethernet1/1
+# mode: active
+# - name: port-channel11
+# members:
+# - member: Ethernet1/2
+# mode: passive
+"""
+RETURN = """
+before:
+ description: The configuration as structured data prior to module invocation.
+ returned: always
+ type: list
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+after:
+ description: The configuration as structured data after module completion.
+ returned: when changed
+ type: list
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+commands:
+ description: The set of commands pushed to the remote device.
+ returned: always
+ type: list
+ sample:
+ - "interface Ethernet1/800"
+ - "channel-group 10 mode active"
+ - "interface Ethernet1/801"
+ - "channel-group 10"
+ - "interface Ethernet1/802"
+ - "channel-group 11 mode passive"
+"""
+
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.lag_interfaces.lag_interfaces import (
+ Lag_interfacesArgs,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.config.lag_interfaces.lag_interfaces import (
+ Lag_interfaces,
+)
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ required_if = [
+ ("state", "merged", ("config",)),
+ ("state", "replaced", ("config",)),
+ ("state", "overridden", ("config",)),
+ ("state", "rendered", ("config",)),
+ ("state", "parsed", ("running_config",)),
+ ]
+ mutually_exclusive = [("config", "running_config")]
+
+ module = AnsibleModule(
+ argument_spec=Lag_interfacesArgs.argument_spec,
+ required_if=required_if,
+ mutually_exclusive=mutually_exclusive,
+ supports_check_mode=True,
+ )
+
+ result = Lag_interfaces(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_lldp_global.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_lldp_global.py
new file mode 100644
index 00000000..3bd3c553
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_lldp_global.py
@@ -0,0 +1,345 @@
+#!/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 nxos_lldp_global
+"""
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_lldp_global
+short_description: LLDP resource module
+description: This module configures and manages the Link Layer Discovery Protocol(LLDP)
+ attributes on NX-OS platforms.
+version_added: 1.0.0
+author: Adharsh Srivats Rangarajan (@adharshsrivatsr)
+notes:
+- Tested against NxOS 7.3.(0)D1(1) on VIRL
+- Unsupported for Cisco MDS
+- The LLDP feature needs to be enabled before using this module
+options:
+ running_config:
+ description:
+ - This option is used only with state I(parsed).
+ - The value of this option should be the output received from the NX-OS device
+ by executing the command B(show running-config | include lldp).
+ - The state I(parsed) reads the configuration from C(running_config) option and
+ transforms it into Ansible structured data as per the resource module's argspec
+ and the value is then returned in the I(parsed) key within the result.
+ type: str
+ config:
+ description:
+ - A list of link layer discovery configurations
+ type: dict
+ suboptions:
+ holdtime:
+ description:
+ - Amount of time the receiving device should hold the information (in seconds)
+ type: int
+ port_id:
+ description:
+ - This attribute defines if the interface names should be advertised in the
+ long(0) or short(1) form.
+ type: int
+ choices:
+ - 0
+ - 1
+ reinit:
+ description:
+ - Amount of time to delay the initialization of LLDP on any interface (in
+ seconds)
+ type: int
+ timer:
+ description:
+ - Frequency at which LLDP updates need to be transmitted (in seconds)
+ type: int
+ tlv_select:
+ description:
+ - This attribute can be used to specify the TLVs that need to be sent and
+ received in the LLDP packets. By default, all TLVs are advertised
+ type: dict
+ suboptions:
+ dcbxp:
+ description:
+ - Used to specify the Data Center Bridging Exchange Protocol TLV
+ type: bool
+ management_address:
+ description:
+ - Used to specify the management address in TLV messages
+ type: dict
+ suboptions:
+ v4:
+ description: Management address with TLV v4
+ type: bool
+ v6:
+ description: Management address with TLV v6
+ type: bool
+ port:
+ description:
+ - Used to manage port based attributes in TLV messages
+ type: dict
+ suboptions:
+ description:
+ description:
+ - Used to specify the port description TLV
+ type: bool
+ vlan:
+ description:
+ - Used to specify the port VLAN ID TLV
+ type: bool
+ power_management:
+ description:
+ - Used to specify IEEE 802.3 DTE Power via MDI TLV
+ type: bool
+ system:
+ description:
+ - Used to manage system based attributes in TLV messages
+ type: dict
+ suboptions:
+ capabilities:
+ description:
+ - Used to specify the system capabilities TLV
+ type: bool
+ description:
+ description:
+ - Used to specify the system description TLV
+ type: bool
+ name:
+ description:
+ - Used to specify the system name TLV
+ type: bool
+ state:
+ description:
+ - The state of the configuration after module completion
+ type: str
+ choices:
+ - merged
+ - replaced
+ - deleted
+ - gathered
+ - rendered
+ - parsed
+ default: merged
+
+"""
+EXAMPLES = """
+# Using merged
+# Before state:
+# -------------
+#
+# user(config)# show running-config | include lldp
+# feature lldp
+
+- name: Merge provided configuration with device configuration
+ cisco.nxos.nxos_lldp_global:
+ config:
+ timer: 35
+ holdtime: 100
+ state: merged
+
+# After state:
+# ------------
+#
+# user(config)# show running-config | include lldp
+# feature lldp
+# lldp timer 35
+# lldp holdtime 100
+
+
+# Using replaced
+# Before state:
+# -------------
+#
+# user(config)# show running-config | include lldp
+# feature lldp
+# lldp holdtime 100
+# lldp reinit 5
+# lldp timer 35
+
+- name: Replace device configuration of specific LLDP attributes with provided configuration
+ cisco.nxos.nxos_lldp_global:
+ config:
+ timer: 40
+ tlv_select:
+ system:
+ description: true
+ name: false
+ management_address:
+ v4: true
+ state: replaced
+
+# After state:
+# ------------
+#
+# user(config)# show running-config | include lldp
+# feature lldp
+# lldp timer 40
+# no lldp tlv-select system-name
+
+
+# Using deleted
+# Before state:
+# -------------
+#
+# user(config)# show running-config | include lldp
+# feature lldp
+# lldp holdtime 5
+# lldp reinit 3
+
+- name: Delete LLDP configuration (this will by default remove all lldp configuration)
+ cisco.nxos.nxos_lldp_global:
+ state: deleted
+
+# After state:
+# ------------
+#
+# user(config)# show running-config | include lldp
+# feature lldp
+
+# Using rendered
+
+- name: Use rendered state to convert task input to device specific commands
+ cisco.nxos.nxos_lldp_global:
+ config:
+ holdtime: 130
+ port_id: 1
+ reinit: 5
+ tlv_select:
+ dcbxp: yes
+ power_management: yes
+ state: rendered
+
+# Task Output (redacted)
+# -----------------------
+
+# rendered:
+# - "lldp tlv-select dcbxp"
+# - "lldp tlv-select power-management"
+# - "lldp portid-subtype 1"
+# - "lldp reinit 5"
+# - "lldp holdtime 130"
+
+# Using parsed
+
+# parsed.cfg
+# ------------
+# lldp holdtime 131
+# lldp reinit 7
+# no lldp tlv-select system-name
+# no lldp tlv-select system-description
+
+# Task output (redacted)
+# -----------------------
+
+# parsed:
+# holdtime: 131
+# reinit: 7
+# tlv_select:
+# system:
+# description: false
+# name: false
+
+# Using gathered
+
+# Existing device config state
+# -------------------------------
+# feature lldp
+# lldp holdtime 129
+# lldp reinit 5
+# lldp timer 35
+# no lldp tlv-select system-name
+
+# Task output (redacted)
+# -----------------------
+
+# gathered:
+# reinit: 5
+# timer: 35
+# tlv_select:
+# system:
+# name: False
+# holdtime: 129
+"""
+RETURN = """
+before:
+ description: The configuration as structured data prior to module invocation.
+ returned: always
+ type: dict
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+after:
+ description: The configuration as structured data after module completion.
+ returned: when changed
+ type: dict
+ 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: ['lldp holdtime 125', 'lldp reinit 4', 'no lldp tlv-select system-name']
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.lldp_global.lldp_global import (
+ Lldp_globalArgs,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.config.lldp_global.lldp_global import (
+ Lldp_global,
+)
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ required_if = [
+ ("state", "merged", ("config",)),
+ ("state", "replaced", ("config",)),
+ ("state", "rendered", ("config",)),
+ ("state", "parsed", ("running_config",)),
+ ]
+ mutually_exclusive = [("config", "running_config")]
+
+ module = AnsibleModule(
+ argument_spec=Lldp_globalArgs.argument_spec,
+ required_if=required_if,
+ mutually_exclusive=mutually_exclusive,
+ supports_check_mode=True,
+ )
+
+ result = Lldp_global(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_lldp_interfaces.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_lldp_interfaces.py
new file mode 100644
index 00000000..cf49a9ac
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_lldp_interfaces.py
@@ -0,0 +1,263 @@
+#!/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 nxos_lldp_interfaces
+"""
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_lldp_interfaces
+short_description: LLDP interfaces resource module
+description: This module manages interfaces' configuration for Link Layer Discovery
+ Protocol (LLDP) on NX-OS platforms.
+version_added: 1.0.0
+author: Adharsh Srivats Rangarajan (@adharshsrivatsr)
+notes:
+- Tested against NXOS 7.3.(0)D1(1) on VIRL
+- Unsupported for Cisco MDS
+- The LLDP feature needs to be enabled before using this module
+options:
+ running_config:
+ description:
+ - This option is used only with state I(parsed).
+ - The value of this option should be the output received from the NX-OS device
+ by executing the command B(show running-config | section ^interface).
+ - The state I(parsed) reads the configuration from C(running_config) option and
+ transforms it into Ansible structured data as per the resource module's argspec
+ and the value is then returned in the I(parsed) key within the result.
+ type: str
+ config:
+ description:
+ - A list of link layer discovery configurations for interfaces.
+ type: list
+ elements: dict
+ suboptions:
+ name:
+ description:
+ - Name of the interface
+ required: true
+ type: str
+ receive:
+ description:
+ - Used to enable or disable the reception of LLDP packets on that interface.
+ By default, this is enabled after LLDP is enabled globally.
+ type: bool
+ transmit:
+ description:
+ - Used to enable or disable the transmission of LLDP packets on that interface.
+ By default, this is enabled after LLDP is enabled globally.
+ type: bool
+ tlv_set:
+ description:
+ - Used to configure TLV parameters on the interface
+ type: dict
+ suboptions:
+ management_address:
+ description:
+ - Used to mention the IPv4 or IPv6 management address for the interface
+ type: str
+ vlan:
+ description:
+ - Used to mention the VLAN for the interface
+ 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:
+# -------------
+#
+
+- name: Merge provided configuration with device configuration
+ cisco.nxos.nxos_lldp_interfaces:
+ config:
+ - name: Ethernet1/4
+ receive: false
+ transmit: true
+ tlv_set:
+ management_address: 192.168.122.64
+ vlan: 12
+ state: merged
+
+# After state:
+# -------------
+#
+# interface Ethernet1/4
+# no lldp receive
+# lldp tlv-set management-address 192.168.122.64
+# lldp tlv-set vlan 12
+
+
+# Using replaced
+
+# Before state:
+# ------------
+#
+# interface Ethernet1/4
+# no lldp receive
+# lldp tlv-set management-address 192.168.122.64
+# interface Ethernet1/5
+# no lldp transmit
+# lldp tlv-set vlan 10
+
+- name: Replace LLDP configuration on interfaces with given configuration
+ cisco.nxos.nxos_lldp_interfaces:
+ config:
+ - name: Ethernet1/4
+ transmit: no
+ tlv_set:
+ vlan: 2
+ state: replaced
+
+
+# After state:
+# -----------
+#
+# interface Ethernet1/4
+# no lldp transmit
+# lldp tlv_set vlan 2
+# interface Ethernet1/5
+# no lldp transmit
+# lldp tlv-set vlan 10
+
+
+# Using overridden
+
+# Before state:
+# ------------
+#
+# interface Ethernet1/4
+# no lldp receive
+# lldp tlv-set management-address 192.168.122.64
+# interface Ethernet1/5
+# no lldp transmit
+# lldp tlv-set vlan 10
+
+- name: Override LLDP configuration on all interfaces with given configuration
+ cisco.nxos.nxos_lldp_interfaces:
+ config:
+ - name: Ethernet1/7
+ receive: no
+ tlv_set:
+ vlan: 12
+ state: overridden
+
+
+# After state:
+# -----------
+#
+# interface Ethernet1/7
+# no lldp receive
+# lldp tlv_set vlan 12
+
+
+# Using deleted
+
+# Before state:
+# ------------
+#
+# interface Ethernet1/4
+# lldp tlv-set management vlan 24
+# no lldp transmit
+# interface mgmt0
+# no lldp receive
+
+- name: Delete LLDP interfaces configuration
+ cisco.nxos.nxos_lldp_interfaces:
+ state: deleted
+
+# After state:
+# ------------
+#
+
+
+"""
+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: ['interface Ethernet1/2', 'lldp receive', 'lldp tlv-set vlan 12']
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.lldp_interfaces.lldp_interfaces import (
+ Lldp_interfacesArgs,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.config.lldp_interfaces.lldp_interfaces import (
+ Lldp_interfaces,
+)
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ module = AnsibleModule(
+ argument_spec=Lldp_interfacesArgs.argument_spec,
+ supports_check_mode=True,
+ )
+
+ result = Lldp_interfaces(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_logging.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_logging.py
new file mode 100644
index 00000000..7782eb32
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_logging.py
@@ -0,0 +1,940 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+# Copyright: (c) 2017, Ansible by Red Hat, inc
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+
+DOCUMENTATION = """
+module: nxos_logging
+author: Trishna Guha (@trishnaguha)
+short_description: Manage logging on network devices
+notes:
+- Limited Support for Cisco MDS
+description:
+- This module provides declarative management of logging on Cisco NX-OS devices.
+version_added: 1.0.0
+deprecated:
+ alternative: nxos_logging_global
+ why: Updated module released with more functionality.
+ removed_at_date: '2023-08-01'
+options:
+ dest:
+ description:
+ - Destination of the logs.
+ choices:
+ - console
+ - logfile
+ - module
+ - monitor
+ - server
+ type: str
+ remote_server:
+ description:
+ - Hostname or IP Address for remote logging (when dest is 'server').
+ type: str
+ use_vrf:
+ description:
+ - VRF to be used while configuring remote logging (when dest is 'server').
+ type: str
+ interface:
+ description:
+ - Interface to be used while configuring source-interface for logging (e.g., 'Ethernet1/2',
+ 'mgmt0')
+ type: str
+ name:
+ description:
+ - If value of C(dest) is I(logfile) it indicates file-name.
+ type: str
+ facility:
+ description:
+ - Facility name for logging.
+ type: str
+ dest_level:
+ description:
+ - Set logging severity levels.
+ aliases:
+ - level
+ type: int
+ facility_level:
+ description:
+ - Set logging severity levels for facility based log messages.
+ type: int
+ aggregate:
+ description: List of logging definitions.
+ type: list
+ elements: dict
+ state:
+ description:
+ - State of the logging configuration.
+ default: present
+ choices:
+ - present
+ - absent
+ type: str
+ event:
+ description:
+ - Link/trunk enable/default interface configuration logging
+ choices:
+ - link-enable
+ - link-default
+ - trunk-enable
+ - trunk-default
+ type: str
+ interface_message:
+ description:
+ - Add interface description to interface syslogs. Does not work with version 6.0
+ images using nxapi as a transport.
+ choices:
+ - add-interface-description
+ type: str
+ file_size:
+ description:
+ - Set logfile size
+ type: int
+ facility_link_status:
+ description:
+ - Set logging facility ethpm link status. Not idempotent with version 6.0 images.
+ choices:
+ - link-down-notif
+ - link-down-error
+ - link-up-notif
+ - link-up-error
+ type: str
+ timestamp:
+ description:
+ - Set logging timestamp format
+ choices:
+ - microseconds
+ - milliseconds
+ - seconds
+ type: str
+ purge:
+ description:
+ - Remove any switch logging configuration that does not match what has been configured
+ Not supported for ansible_connection local. All nxos_logging tasks must use
+ the same ansible_connection type.
+ type: bool
+ default: false
+extends_documentation_fragment:
+- cisco.nxos.nxos
+"""
+
+EXAMPLES = """
+- name: configure console logging with level
+ cisco.nxos.nxos_logging:
+ dest: console
+ level: 2
+ state: present
+- name: remove console logging configuration
+ cisco.nxos.nxos_logging:
+ dest: console
+ level: 2
+ state: absent
+- name: configure file logging with level
+ cisco.nxos.nxos_logging:
+ dest: logfile
+ name: testfile
+ dest_level: 3
+ state: present
+- name: Configure logging logfile with size
+ cisco.nxos.nxos_logging:
+ dest: logfile
+ name: testfile
+ dest_level: 3
+ file_size: 16384
+- name: configure facility level logging
+ cisco.nxos.nxos_logging:
+ facility: daemon
+ facility_level: 0
+ state: present
+- name: remove facility level logging
+ cisco.nxos.nxos_logging:
+ facility: daemon
+ facility_level: 0
+ state: absent
+- name: Configure Remote Logging
+ cisco.nxos.nxos_logging:
+ dest: server
+ remote_server: test-syslogserver.com
+ facility: auth
+ facility_level: 1
+ use_vrf: management
+ state: present
+- name: Configure Source Interface for Logging
+ cisco.nxos.nxos_logging:
+ interface: mgmt0
+ state: present
+- name: Purge nxos_logging configuration not managed by this playbook
+ cisco.nxos.nxos_logging:
+ purge: true
+- name: Configure logging timestamp
+ cisco.nxos.nxos_logging:
+ timestamp: milliseconds
+ state: present
+- name: Configure logging facility ethpm link status
+ cisco.nxos.nxos_logging:
+ facility: ethpm
+ facility_link_status: link-up-notif
+ state: present
+- name: Configure logging message ethernet description
+ cisco.nxos.nxos_logging:
+ interface_message: add-interface-description
+ state: present
+- name: Configure logging event link enable
+ cisco.nxos.nxos_logging:
+ event: link-enable
+ state: present
+- name: Configure logging using aggregate
+ cisco.nxos.nxos_logging:
+ aggregate:
+ - {dest: console, dest_level: 2}
+ - {dest: logfile, dest_level: 2, name: testfile}
+ - {facility: daemon, facility_level: 0}
+ state: present
+"""
+
+RETURN = """
+commands:
+ description: The list of configuration mode commands to send to the device
+ returned: always
+ type: list
+ sample:
+ - logging console 2
+ - logging logfile testfile 3
+ - logging level daemon 0
+"""
+
+import copy
+import re
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ get_config,
+ load_config,
+ normalize_interface,
+ read_module_context,
+ run_commands,
+ save_module_context,
+)
+
+
+STATIC_CLI = {
+ "link-enable": "logging event link-status enable",
+ "link-default": "logging event link-status default",
+ "trunk-enable": "logging event trunk-status enable",
+ "trunk-default": "logging event trunk-status default",
+ "microseconds": "logging timestamp microseconds",
+ "milliseconds": "logging timestamp milliseconds",
+ "seconds": "logging timestamp seconds",
+ "link-up-error": "link-up error",
+ "link-up-notif": "link-up notif",
+ "link-down-error": "link-down error",
+ "link-down-notif": "link-down notif",
+ "add-interface-description": "logging message interface type ethernet description",
+}
+
+DEFAULT_LOGGING_LEVEL = {
+ 0: [],
+ 1: [],
+ 2: ["pktmgr"],
+ 3: ["adjmgr", "arp", "icmpv6", "l2rib", "netstack"],
+ 4: [],
+ 5: ["mrib", "m6rib"],
+ 6: [],
+ 7: [],
+}
+
+DEST_GROUP = ["console", "logfile", "module", "monitor", "server"]
+
+
+def map_obj_to_commands(module, updates):
+ commands = list()
+ want, have = updates
+
+ for w in want:
+ state = w["state"]
+ del w["state"]
+
+ if state == "absent" and w in have:
+ if w["facility"] is not None:
+ if (
+ not w["dest"]
+ and not w["facility_link_status"]
+ and w["facility"] not in DEFAULT_LOGGING_LEVEL[int(w["facility_level"])]
+ ):
+ commands.append(
+ "no logging level {0} {1}".format(w["facility"], w["facility_level"]),
+ )
+
+ if w["facility_link_status"] and w["facility"] in ("ethpm"):
+ commands.append(
+ "no logging level {0} {1}".format(
+ w["facility"],
+ STATIC_CLI[w["facility_link_status"]],
+ ),
+ )
+
+ if w["name"] is not None:
+ commands.append("no logging logfile")
+
+ if w["dest"] in ("console", "module", "monitor"):
+ commands.append("no logging {0}".format(w["dest"]))
+
+ if w["dest"] == "server":
+ commands.append("no logging server {0}".format(w["remote_server"]))
+
+ if w["interface"]:
+ commands.append("no logging source-interface")
+
+ if w["event"] and w["event"] in STATIC_CLI:
+ commands.append("no " + STATIC_CLI[w["event"]])
+
+ if w["message"] and w["message"] in STATIC_CLI:
+ commands.append("no " + STATIC_CLI[w["message"]])
+
+ if w["timestamp"] and w["timestamp"] in STATIC_CLI:
+ commands.append("no " + STATIC_CLI[w["timestamp"]])
+
+ if state == "present" and w not in have:
+ if w["facility"] is None:
+ if w["dest"]:
+ if w["dest"] not in ("logfile", "server"):
+ commands.append("logging {0} {1}".format(w["dest"], w["dest_level"]))
+
+ elif w["dest"] == "logfile":
+ if w["file_size"]:
+ commands.append(
+ "logging logfile {0} {1} size {2}".format(
+ w["name"],
+ w["dest_level"],
+ w["file_size"],
+ ),
+ )
+ else:
+ commands.append(
+ "logging logfile {0} {1}".format(w["name"], w["dest_level"]),
+ )
+
+ elif w["dest"] == "server":
+ if w["facility_level"]:
+ if w["use_vrf"]:
+ commands.append(
+ "logging server {0} {1} use-vrf {2}".format(
+ w["remote_server"],
+ w["facility_level"],
+ w["use_vrf"],
+ ),
+ )
+ else:
+ commands.append(
+ "logging server {0} {1}".format(
+ w["remote_server"],
+ w["facility_level"],
+ ),
+ )
+
+ else:
+ if w["use_vrf"]:
+ commands.append(
+ "logging server {0} use-vrf {1}".format(
+ w["remote_server"],
+ w["use_vrf"],
+ ),
+ )
+ else:
+ commands.append("logging server {0}".format(w["remote_server"]))
+
+ if w["facility"]:
+ if w["dest"] == "server":
+ if w["facility_level"]:
+ if w["use_vrf"]:
+ commands.append(
+ "logging server {0} {1} facility {2} use-vrf {3}".format(
+ w["remote_server"],
+ w["facility_level"],
+ w["facility"],
+ w["use_vrf"],
+ ),
+ )
+ else:
+ commands.append(
+ "logging server {0} {1} facility {2}".format(
+ w["remote_server"],
+ w["facility_level"],
+ w["facility"],
+ ),
+ )
+ else:
+ if w["use_vrf"]:
+ commands.append(
+ "logging server {0} facility {1} use-vrf {2}".format(
+ w["remote_server"],
+ w["facility"],
+ w["use_vrf"],
+ ),
+ )
+ else:
+ commands.append(
+ "logging server {0} facility {1}".format(
+ w["remote_server"],
+ w["facility"],
+ ),
+ )
+ else:
+ if w["facility_link_status"]:
+ commands.append(
+ "logging level {0} {1}".format(
+ w["facility"],
+ STATIC_CLI[w["facility_link_status"]],
+ ),
+ )
+ else:
+ if not match_facility_default(module, w["facility"], w["facility_level"]):
+ commands.append(
+ "logging level {0} {1}".format(w["facility"], w["facility_level"]),
+ )
+
+ if w["interface"]:
+ commands.append(
+ "logging source-interface {0} {1}".format(*split_interface(w["interface"])),
+ )
+
+ if w["event"] and w["event"] in STATIC_CLI:
+ commands.append(STATIC_CLI[w["event"]])
+
+ if w["message"] and w["message"] in STATIC_CLI:
+ commands.append(STATIC_CLI[w["message"]])
+
+ if w["timestamp"] and w["timestamp"] in STATIC_CLI:
+ commands.append(STATIC_CLI[w["timestamp"]])
+
+ return commands
+
+
+def match_facility_default(module, facility, want_level):
+ """Check wanted facility to see if it matches current device default"""
+
+ matches_default = False
+ # Sample output from show logging level command
+ # Facility Default Severity Current Session Severity
+ # -------- ---------------- ------------------------
+ # bfd 5 5
+ #
+ # 0(emergencies) 1(alerts) 2(critical)
+ # 3(errors) 4(warnings) 5(notifications)
+ # 6(information) 7(debugging)
+
+ regexl = r"\S+\s+(\d+)\s+(\d+)"
+ cmd = {
+ "command": "show logging level {0}".format(facility),
+ "output": "text",
+ }
+ facility_data = run_commands(module, cmd)
+ for line in facility_data[0].split("\n"):
+ mo = re.search(regexl, line)
+ if mo and int(mo.group(1)) == int(want_level) and int(mo.group(2)) == int(want_level):
+ matches_default = True
+
+ return matches_default
+
+
+def split_interface(interface):
+ match = re.search(r"(\D+)(\S*)", interface, re.M)
+ if match:
+ return match.group(1), match.group(2)
+
+
+def parse_facility_link_status(line, facility, status):
+ facility_link_status = None
+
+ if facility is not None:
+ match = re.search(r"logging level {0} {1} (\S+)".format(facility, status), line, re.M)
+ if match:
+ facility_link_status = status + "-" + match.group(1)
+
+ return facility_link_status
+
+
+def parse_event_status(line, event):
+ status = None
+
+ match = re.search(r"logging event {0} (\S+)".format(event + "-status"), line, re.M)
+ if match:
+ state = match.group(1)
+ if state:
+ status = state
+
+ return status
+
+
+def parse_event(line):
+ event = None
+
+ match = re.search(r"logging event (\S+)", line, re.M)
+ if match:
+ state = match.group(1)
+ if state == "link-status":
+ event = "link"
+ elif state == "trunk-status":
+ event = "trunk"
+
+ return event
+
+
+def parse_message(line):
+ message = None
+
+ match = re.search(r"logging message interface type ethernet description", line, re.M)
+ if match:
+ message = "add-interface-description"
+
+ return message
+
+
+def parse_file_size(line, name, level):
+ file_size = None
+
+ match = re.search(r"logging logfile {0} {1} size (\S+)".format(name, level), line, re.M)
+ if match:
+ file_size = match.group(1)
+ if file_size == "8192" or file_size == "4194304":
+ file_size = None
+
+ return file_size
+
+
+def parse_timestamp(line):
+ timestamp = None
+
+ match = re.search(r"logging timestamp (\S+)", line, re.M)
+ if match:
+ timestamp = match.group(1)
+
+ return timestamp
+
+
+def parse_name(line, dest):
+ name = None
+
+ if dest is not None:
+ if dest == "logfile":
+ match = re.search(r"logging logfile (\S+)", line, re.M)
+ if match:
+ name = match.group(1)
+ else:
+ pass
+
+ return name
+
+
+def parse_remote_server(line, dest):
+ remote_server = None
+
+ if dest and dest == "server":
+ match = re.search(r"logging server (\S+)", line, re.M)
+ if match:
+ remote_server = match.group(1)
+
+ return remote_server
+
+
+def parse_dest_level(line, dest, name):
+ dest_level = None
+
+ def parse_match(match):
+ level = None
+ if match:
+ if int(match.group(1)) in range(0, 8):
+ level = match.group(1)
+ else:
+ pass
+ return level
+
+ if dest and dest != "server":
+ if dest == "logfile":
+ match = re.search(r"logging logfile {0} (\S+)".format(name), line, re.M)
+ if match:
+ dest_level = parse_match(match)
+
+ elif dest == "server":
+ match = re.search(r"logging server (?:\S+) (\d+)", line, re.M)
+ if match:
+ dest_level = parse_match(match)
+ else:
+ match = re.search(r"logging {0} (\S+)".format(dest), line, re.M)
+ if match:
+ dest_level = parse_match(match)
+
+ return dest_level
+
+
+def parse_facility_level(line, facility, dest):
+ facility_level = None
+
+ if dest == "server":
+ match = re.search(r"logging server (?:\S+) (\d+)", line, re.M)
+ if match:
+ facility_level = match.group(1)
+
+ elif facility is not None:
+ match = re.search(r"logging level {0} (\S+)".format(facility), line, re.M)
+ if match:
+ facility_level = match.group(1)
+
+ return facility_level
+
+
+def parse_facility(line):
+ facility = None
+
+ match = re.search(
+ r"logging server (?:\S+) (?:\d+) (?:\S+) (?:\S+) (?:\S+) (\S+)",
+ line,
+ re.M,
+ )
+ if match:
+ facility = match.group(1)
+
+ return facility
+
+
+def parse_use_vrf(line, dest):
+ use_vrf = None
+
+ if dest and dest == "server":
+ match = re.search(r"logging server (?:\S+) (?:\d+) use-vrf (\S+)", line, re.M)
+ if match:
+ use_vrf = match.group(1)
+
+ return use_vrf
+
+
+def parse_interface(line):
+ interface = None
+
+ match = re.search(r"logging source-interface (\S*)", line, re.M)
+ if match:
+ interface = match.group(1)
+
+ return interface
+
+
+def map_config_to_obj(module):
+ obj = []
+
+ data = get_config(module, flags=[" all | section logging"])
+
+ for line in data.split("\n"):
+ if re.search(r"no (\S+)", line, re.M):
+ state = "absent"
+ else:
+ state = "present"
+
+ match = re.search(r"logging (\S+)", line, re.M)
+ if state == "present" and match:
+ event_status = None
+ name = None
+ dest_level = None
+ dest = None
+ facility = None
+ remote_server = None
+ facility_link_status = None
+ file_size = None
+ facility_level = None
+
+ if match.group(1) in DEST_GROUP:
+ dest = match.group(1)
+
+ name = parse_name(line, dest)
+ remote_server = parse_remote_server(line, dest)
+ dest_level = parse_dest_level(line, dest, name)
+
+ if dest == "server":
+ facility = parse_facility(line)
+
+ facility_level = parse_facility_level(line, facility, dest)
+
+ if dest == "logfile":
+ file_size = parse_file_size(line, name, dest_level)
+
+ elif match.group(1) == "level":
+ match_facility = re.search(r"logging level (\S+)", line, re.M)
+ facility = match_facility.group(1)
+
+ level = parse_facility_level(line, facility, dest)
+ if level.isdigit():
+ facility_level = level
+ else:
+ facility_link_status = parse_facility_link_status(line, facility, level)
+
+ elif match.group(1) == "event" and state == "present":
+ event = parse_event(line)
+ if event:
+ status = parse_event_status(line, event)
+ if status:
+ event_status = event + "-" + status
+ else:
+ continue
+
+ else:
+ pass
+
+ obj.append(
+ {
+ "dest": dest,
+ "remote_server": remote_server,
+ "use_vrf": parse_use_vrf(line, dest),
+ "name": name,
+ "facility": facility,
+ "dest_level": dest_level,
+ "facility_level": facility_level,
+ "interface": parse_interface(line),
+ "facility_link_status": facility_link_status,
+ "event": event_status,
+ "file_size": file_size,
+ "message": parse_message(line),
+ "timestamp": parse_timestamp(line),
+ },
+ )
+
+ cmd = [
+ {
+ "command": "show logging | section enabled | section console",
+ "output": "text",
+ },
+ {
+ "command": "show logging | section enabled | section monitor",
+ "output": "text",
+ },
+ ]
+
+ default_data = run_commands(module, cmd)
+
+ for line in default_data:
+ flag = False
+ match = re.search(
+ r"Logging (\w+):(?:\s+) (?:\w+) (?:\W)Severity: (\w+)",
+ str(line),
+ re.M,
+ )
+ if match:
+ if match.group(1) == "console" and match.group(2) == "critical":
+ dest_level = "2"
+ flag = True
+ elif match.group(1) == "monitor" and match.group(2) == "notifications":
+ dest_level = "5"
+ flag = True
+ if flag:
+ obj.append(
+ {
+ "dest": match.group(1),
+ "remote_server": None,
+ "name": None,
+ "facility": None,
+ "dest_level": dest_level,
+ "facility_level": None,
+ "use_vrf": None,
+ "interface": None,
+ "facility_link_status": None,
+ "event": None,
+ "file_size": None,
+ "message": None,
+ "timestamp": None,
+ },
+ )
+
+ return obj
+
+
+def map_params_to_obj(module):
+ obj = []
+
+ if "aggregate" in module.params and module.params["aggregate"]:
+ args = {
+ "dest": "",
+ "remote_server": "",
+ "use_vrf": "",
+ "name": "",
+ "facility": "",
+ "dest_level": "",
+ "facility_level": "",
+ "interface": "",
+ "facility_link_status": None,
+ "event": None,
+ "file_size": None,
+ "message": None,
+ "timestamp": None,
+ }
+
+ for c in module.params["aggregate"]:
+ d = c.copy()
+
+ for key in args:
+ if key not in d:
+ d[key] = None
+
+ if d["dest_level"] is not None:
+ d["dest_level"] = str(d["dest_level"])
+
+ if d["facility_level"] is not None:
+ d["facility_level"] = str(d["facility_level"])
+
+ if d["interface"]:
+ d["interface"] = normalize_interface(d["interface"])
+
+ if "state" not in d:
+ d["state"] = module.params["state"]
+
+ if d["file_size"]:
+ d["file_size"] = str(d["file_size"])
+
+ obj.append(d)
+
+ else:
+ dest_level = None
+ facility_level = None
+ file_size = None
+
+ if module.params["dest_level"] is not None:
+ dest_level = str(module.params["dest_level"])
+
+ if module.params["facility_level"] is not None:
+ facility_level = str(module.params["facility_level"])
+
+ if module.params["file_size"] is not None:
+ file_size = str(module.params["file_size"])
+
+ obj.append(
+ {
+ "dest": module.params["dest"],
+ "remote_server": module.params["remote_server"],
+ "use_vrf": module.params["use_vrf"],
+ "name": module.params["name"],
+ "facility": module.params["facility"],
+ "dest_level": dest_level,
+ "facility_level": facility_level,
+ "interface": normalize_interface(module.params["interface"]),
+ "state": module.params["state"],
+ "facility_link_status": module.params["facility_link_status"],
+ "event": module.params["event"],
+ "message": module.params["interface_message"],
+ "file_size": file_size,
+ "timestamp": module.params["timestamp"],
+ },
+ )
+ return obj
+
+
+def merge_wants(wants, want):
+ if not wants:
+ wants = list()
+
+ for w in want:
+ w = copy.copy(w)
+ state = w["state"]
+ del w["state"]
+
+ if state == "absent":
+ if w in wants:
+ wants.remove(w)
+ elif w not in wants:
+ wants.append(w)
+
+ return wants
+
+
+def absent(h):
+ h["state"] = "absent"
+ return h
+
+
+def outliers(haves, wants):
+ wants = list(wants)
+ return [absent(h) for h in haves if not (h in wants or wants.append(h))]
+
+
+def main():
+ """main entry point for module execution"""
+ argument_spec = dict(
+ dest=dict(choices=DEST_GROUP),
+ name=dict(),
+ facility=dict(),
+ remote_server=dict(),
+ use_vrf=dict(),
+ dest_level=dict(type="int", aliases=["level"]),
+ facility_level=dict(type="int"),
+ interface=dict(),
+ facility_link_status=dict(
+ choices=[
+ "link-down-notif",
+ "link-down-error",
+ "link-up-notif",
+ "link-up-error",
+ ],
+ ),
+ event=dict(
+ choices=[
+ "link-enable",
+ "link-default",
+ "trunk-enable",
+ "trunk-default",
+ ],
+ ),
+ interface_message=dict(choices=["add-interface-description"]),
+ file_size=dict(type="int"),
+ timestamp=dict(choices=["microseconds", "milliseconds", "seconds"]),
+ state=dict(default="present", choices=["present", "absent"]),
+ aggregate=dict(type="list", elements="dict"),
+ purge=dict(default=False, type="bool"),
+ )
+
+ required_if = [
+ ("dest", "logfile", ["name"]),
+ ("dest", "server", ["remote_server"]),
+ ]
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ required_if=required_if,
+ supports_check_mode=True,
+ )
+
+ warnings = list()
+
+ result = {"changed": False}
+ if warnings:
+ result["warnings"] = warnings
+
+ want = map_params_to_obj(module)
+ merged_wants = merge_wants(read_module_context(module), want)
+ have = map_config_to_obj(module)
+
+ commands = map_obj_to_commands(module, (want, have))
+ result["commands"] = commands
+
+ if commands:
+ if not module.check_mode:
+ load_config(module, commands)
+ result["changed"] = True
+
+ save_module_context(module, merged_wants)
+
+ if module.params.get("purge"):
+ pcommands = map_obj_to_commands(module, (outliers(have, merged_wants), have))
+ if pcommands:
+ if not module.check_mode:
+ load_config(module, pcommands)
+ result["changed"] = True
+ result["commands"] += pcommands
+
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_logging_global.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_logging_global.py
new file mode 100644
index 00000000..1c060b01
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_logging_global.py
@@ -0,0 +1,735 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# Copyright 2021 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+"""
+The module file for nxos_logging_global
+"""
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+DOCUMENTATION = """
+module: nxos_logging_global
+short_description: Logging resource module.
+description:
+- This module manages logging configuration on devices running Cisco NX-OS.
+version_added: 2.5.0
+notes:
+- Tested against NX-OS 9.3.6 on Cisco Nexus Switches.
+- Limited Support for Cisco MDS
+- This module works with connection C(network_cli) and C(httpapi).
+- Tested against Cisco MDS NX-OS 9.2(2) with connection C(network_cli).
+author: Nilashish Chakraborty (@NilashishC)
+options:
+ running_config:
+ description:
+ - This option is used only with state I(parsed).
+ - The value of this option should be the output received from the NX-OS device
+ by executing the command B(show running-config | include logging).
+ - The state I(parsed) reads the configuration from C(running_config) option and
+ transforms it into Ansible structured data as per the resource module's argspec
+ and the value is then returned in the I(parsed) key within the result.
+ type: str
+ config:
+ description: A dictionary of logging configuration.
+ type: dict
+ suboptions:
+ console:
+ description: Set console logging parameters.
+ type: dict
+ suboptions:
+ state:
+ description: Enable or disable monitor logging.
+ type: str
+ choices: ["enabled", "disabled"]
+ severity: &sev
+ description: Set severity severity for console.
+ type: str
+ choices:
+ - emergency
+ - alert
+ - critical
+ - error
+ - warning
+ - notification
+ - informational
+ - debugging
+ event:
+ description: Interface events.
+ type: dict
+ suboptions:
+ link_status:
+ description: UPDOWN and CHANGE messages.
+ type: dict
+ suboptions: &event
+ enable:
+ description: To enable logging overriding port severity configuration.
+ type: bool
+ default:
+ description: Default logging configuration used by interfaces not explicitly configured.
+ type: bool
+ trunk_status:
+ description: TRUNK status messages.
+ type: dict
+ suboptions: *event
+ history:
+ description: Modifies severity severity or size for history table.
+ type: dict
+ suboptions:
+ severity: *sev
+ size:
+ description: Set history table size.
+ type: int
+ ip:
+ description:
+ - IP configuration.
+ - This option is unsupported on MDS switches.
+ type: dict
+ suboptions:
+ access_list:
+ description: Access-List.
+ type: dict
+ suboptions:
+ cache:
+ description: Set caching settings.
+ type: dict
+ suboptions:
+ entries:
+ description: Maximum number of log entries cached in software.
+ type: int
+ interval:
+ description: Log-update interval (in sec).
+ type: int
+ threshold:
+ description: Log-update threshold (number of hits)
+ type: int
+ detailed:
+ description: Detailed ACL information.
+ type: bool
+ include:
+ description: Include additional fields in syslogs.
+ type: dict
+ suboptions:
+ sgt:
+ description: Include source group tag info in syslogs.
+ type: bool
+ facilities:
+ description: Facility parameter for syslog messages.
+ type: list
+ elements: dict
+ suboptions:
+ facility:
+ description: Facility name.
+ type: str
+ severity: *sev
+ logfile:
+ description: Set file logging.
+ type: dict
+ suboptions:
+ state:
+ description: Enable or disable logfile.
+ type: str
+ choices: ["enabled", "disabled"]
+ name:
+ description: Logfile name.
+ type: str
+ severity: *sev
+ persistent_threshold:
+ description:
+ - Set persistent logging utilization alert threshold in percentage.
+ - This option is unsupported on MDS switches.
+ type: int
+ size:
+ description: Enter the logfile size in bytes.
+ type: int
+ module:
+ description: Set module(linecard) logging.
+ type: dict
+ suboptions:
+ state:
+ description: Enable or disable module logging.
+ type: str
+ choices: ["enabled", "disabled"]
+ severity: *sev
+ monitor:
+ description: Set terminal line(monitor) logging severity.
+ type: dict
+ suboptions:
+ state:
+ description: Enable or disable monitor logging.
+ type: str
+ choices: ["enabled", "disabled"]
+ severity: *sev
+ origin_id:
+ description: Enable origin information for Remote Syslog Server.
+ type: dict
+ suboptions:
+ hostname:
+ description:
+ - Use hostname as origin-id of logging messages.
+ - This option is mutually exclusive with I(ip) and I(string).
+ type: bool
+ ip:
+ description:
+ - Use ip address as origin-id of logging messages.
+ - This option is mutually exclusive with I(hostname) and I(string).
+ type: str
+ string:
+ description:
+ - Use text string as origin-id of logging messages.
+ - This option is mutually exclusive with I(hostname) and I(ip).
+ type: str
+ rate_limit:
+ description: Enable or disable rate limit for log messages.
+ type: str
+ choices: ["enabled", "disabled"]
+ rfc_strict:
+ description:
+ - Set RFC to which messages should compliant.
+ - Syslogs will be compliant to RFC 5424.
+ - This option is unsupported on MDS switches.
+ type: bool
+ hosts:
+ description: Enable forwarding to Remote Syslog Servers.
+ type: list
+ elements: dict
+ suboptions:
+ host:
+ description: Hostname/IPv4/IPv6 address of the Remote Syslog Server.
+ type: str
+ severity: *sev
+ facility:
+ description: Facility to use when forwarding to server.
+ type: str
+ port:
+ description: Destination Port when forwarding to remote server.
+ type: int
+ secure:
+ description: Enable secure connection to remote server.
+ type: dict
+ suboptions:
+ trustpoint:
+ description: Trustpoint configuration.
+ type: dict
+ suboptions:
+ client_identity:
+ description:
+ - Client Identity certificate for mutual authentication.
+ - Trustpoint to use for client certificate authentication.
+ type: str
+ use_vrf:
+ description:
+ - Display per-VRF information.
+ - This option is unsupported on MDS switches.
+ type: str
+ source_interface:
+ description:
+ - Enable Source-Interface for Remote Syslog Server.
+ - This option is unsupported on MDS switches.
+ type: str
+ timestamp:
+ description: Set logging timestamp granularity.
+ type: str
+ choices: ["microseconds", "milliseconds", "seconds"]
+ state:
+ description:
+ - The state the configuration should be left in.
+ - The states I(replaced) and I(overridden) have identical
+ behaviour for this module.
+ - Refer to examples for more details.
+ type: str
+ choices:
+ - merged
+ - replaced
+ - overridden
+ - deleted
+ - parsed
+ - gathered
+ - rendered
+ default: merged
+"""
+EXAMPLES = """
+# Using merged
+
+# Before state:
+# -------------
+# nxos-9k-rdo# show running-config | include logging
+# nxos-9k-rdo#
+
+- name: Merge the provided configuration with the existing running configuration
+ cisco.nxos.nxos_logging_global:
+ config:
+ console:
+ severity: error
+ monitor:
+ severity: warning
+ ip:
+ access_list:
+ cache:
+ entries: 16384
+ interval: 200
+ threshold: 5000
+ facilities:
+ - facility: auth
+ severity: critical
+ - facility: ospfv3
+ severity: alert
+ - facility: ftp
+ severity: informational
+ hosts:
+ - host: 203.0.113.100
+ severity: alert
+ use_vrf: management
+ - host: 203.0.113.101
+ severity: error
+ facility: local6
+ use_vrf: default
+ origin_id:
+ hostname: True
+
+# Task output
+# -------------
+# before: {}
+#
+# commands:
+# - "logging console 3"
+# - "logging monitor 4"
+# - "logging ip access-list cache entries 16384"
+# - "logging ip access-list cache interval 200"
+# - "logging ip access-list cache threshold 5000"
+# - "logging severity auth 2"
+# - "logging severity ospfv3 1"
+# - "logging severity ftp 6"
+# - "logging server 203.0.113.100 1 use-vrf management"
+# - "logging server 203.0.113.101 3 facility local6 use-vrf default"
+# - "logging origin-id hostname"
+#
+# after:
+# console:
+# severity: error
+# facilities:
+# - facility: auth
+# severity: critical
+# - facility: ftp
+# severity: informational
+# - facility: ospfv3
+# severity: alert
+# ip:
+# access_list:
+# cache:
+# entries: 16384
+# interval: 200
+# threshold: 5000
+# monitor:
+# severity: warning
+# origin_id:
+# hostname: true
+# hosts:
+# - severity: alert
+# host: 203.0.113.100
+# use_vrf: management
+# - facility: local6
+# severity: error
+# host: 203.0.113.101
+# use_vrf: default
+
+# After state:
+# ------------
+# nxos-9k-rdo# show running-config | include logging
+# logging console 3
+# logging monitor 4
+# logging ip access-list cache entries 16384
+# logging ip access-list cache interval 200
+# logging ip access-list cache threshold 5000
+# logging severity auth 2
+# logging severity ospfv3 1
+# logging severity ftp 6
+# logging origin-id hostname
+# logging server 203.0.113.100 1 use-vrf management
+# logging server 203.0.113.101 3 use-vrf default facility local6
+
+# Using replaced
+
+# Before state:
+# ------------
+# nxos-9k-rdo# show running-config | include logging
+# logging console 3
+# logging monitor 4
+# logging ip access-list cache entries 16384
+# logging ip access-list cache interval 200
+# logging ip access-list cache threshold 5000
+# logging severity auth 2
+# logging severity ospfv3 1
+# logging severity ftp 6
+# logging origin-id hostname
+# logging server 203.0.113.100 1 use-vrf management
+# logging server 203.0.113.101 3 use-vrf default facility local6
+
+- name: Replace logging configurations with provided config
+ cisco.nxos.nxos_logging_global:
+ config:
+ monitor:
+ severity: warning
+ ip:
+ access_list:
+ cache:
+ entries: 4096
+ facilities:
+ - facility: auth
+ severity: critical
+ - facility: ospfv3
+ severity: alert
+ - facility: ftp
+ severity: informational
+ hosts:
+ - host: 203.0.113.101
+ severity: error
+ facility: local6
+ use_vrf: default
+ - host: 198.51.100.101
+ severity: alert
+ port: 6538
+ use_vrf: management
+ origin_id:
+ ip: 192.0.2.100
+ state: replaced
+
+# Task output
+# -------------
+# before:
+# console:
+# severity: error
+# facilities:
+# - facility: auth
+# severity: critical
+# - facility: ftp
+# severity: informational
+# - facility: ospfv3
+# severity: alert
+# ip:
+# access_list:
+# cache:
+# entries: 16384
+# interval: 200
+# threshold: 5000
+# monitor:
+# severity: warning
+# origin_id:
+# hostname: true
+# hosts:
+# - severity: alert
+# host: 203.0.113.100
+# use_vrf: management
+# - facility: local6
+# severity: error
+# host: 203.0.113.101
+# use_vrf: default
+#
+# commands:
+# - "logging console"
+# - "logging ip access-list cache entries 4096"
+# - "no logging ip access-list cache interval 200"
+# - "no logging ip access-list cache threshold 5000"
+# - "no logging origin-id hostname"
+# - "logging origin-id ip 192.0.2.100"
+# - "logging server 198.51.100.101 1 port 6538 use-vrf management"
+# - "no logging server 203.0.113.100 1 use-vrf management"
+#
+# after:
+# facilities:
+# - facility: auth
+# severity: critical
+# - facility: ftp
+# severity: informational
+# - facility: ospfv3
+# severity: alert
+# ip:
+# access_list:
+# cache:
+# entries: 4096
+# monitor:
+# severity: warning
+# origin_id:
+# ip: 192.0.2.100
+# hosts:
+# - severity: alert
+# port: 6538
+# host: 198.51.100.101
+# use_vrf: management
+# - facility: local6
+# severity: error
+# host: 203.0.113.101
+# use_vrf: default
+#
+# After state:
+# ------------
+# nxos-9k-rdo# show running-config | include logging
+# logging monitor 4
+# logging ip access-list cache entries 4096
+# logging severity auth 2
+# logging severity ospfv3 1
+# logging severity ftp 6
+# logging origin-id ip 192.0.2.100
+# logging server 203.0.113.101 3 use-vrf default facility local6
+# logging server 198.51.100.101 1 port 6538 use-vrf management
+
+# Using deleted to delete all logging configurations
+
+# Before state:
+# ------------
+# nxos-9k-rdo# show running-config | include logging
+# logging console 3
+# logging monitor 4
+# logging ip access-list cache entries 16384
+# logging ip access-list cache interval 200
+# logging ip access-list cache threshold 5000
+# logging severity auth 2
+# logging severity ospfv3 1
+# logging severity ftp 6
+# logging origin-id hostname
+# logging server 203.0.113.100 1 use-vrf management
+# logging server 203.0.113.101 3 use-vrf default facility local6
+
+- name: Delete all logging configuration
+ cisco.nxos.nxos_logging_global:
+ state: deleted
+
+# Task output
+# -------------
+# before:
+# console:
+# severity: error
+# facilities:
+# - facility: auth
+# severity: critical
+# - facility: ftp
+# severity: informational
+# - facility: ospfv3
+# severity: alert
+# ip:
+# access_list:
+# cache:
+# entries: 16384
+# interval: 200
+# threshold: 5000
+# monitor:
+# severity: warning
+# origin_id:
+# hostname: true
+# hosts:
+# - severity: alert
+# host: 203.0.113.100
+# use_vrf: management
+# - facility: local6
+# severity: error
+# host: 203.0.113.101
+# use_vrf: default
+#
+# commands:
+# - "logging console"
+# - "logging monitor"
+# - "no logging ip access-list cache entries 16384"
+# - "no logging ip access-list cache interval 200"
+# - "no logging ip access-list cache threshold 5000"
+# - "no logging origin-id hostname"
+# - "no logging severity auth 2"
+# - "no logging severity ospfv3 1"
+# - "no logging severity ftp 6"
+# - "no logging server 203.0.113.100 1 use-vrf management"
+# - "no logging server 203.0.113.101 3 facility local6 use-vrf default"
+#
+# after: {}
+
+# Using rendered
+
+- name: Render platform specific configuration lines with state rendered (without connecting to the device)
+ cisco.nxos.nxos_logging_global:
+ config:
+ console:
+ severity: error
+ monitor:
+ severity: warning
+ ip:
+ access_list:
+ cache:
+ entries: 16384
+ interval: 200
+ threshold: 5000
+ facilities:
+ - facility: auth
+ severity: critical
+ - facility: ospfv3
+ severity: alert
+ - facility: ftp
+ severity: informational
+ hosts:
+ - host: 203.0.113.100
+ severity: alert
+ use_vrf: management
+ - host: 203.0.113.101
+ severity: error
+ facility: local6
+ use_vrf: default
+ origin_id:
+ hostname: True
+
+# Task Output (redacted)
+# -----------------------
+# rendered:
+# - "logging console 3"
+# - "logging monitor 4"
+# - "logging ip access-list cache entries 16384"
+# - "logging ip access-list cache interval 200"
+# - "logging ip access-list cache threshold 5000"
+# - "logging severity auth 2"
+# - "logging severity ospfv3 1"
+# - "logging severity ftp 6"
+# - "logging server 203.0.113.100 1 use-vrf management"
+# - "logging server 203.0.113.101 3 facility local6 use-vrf default"
+# - "logging origin-id hostname"
+
+# Using parsed
+
+# parsed.cfg
+# ------------
+# logging console 3
+# logging monitor 4
+# logging ip access-list cache entries 16384
+# logging ip access-list cache interval 200
+# logging ip access-list cache threshold 5000
+# logging severity auth 2
+# logging severity ospfv3 1
+# logging severity ftp 6
+# logging origin-id hostname
+# logging server 203.0.113.100 1 use-vrf management
+# logging server 203.0.113.101 3 use-vrf default facility local6
+
+- name: Parse externally provided logging configuration
+ cisco.nxos.nxos_logging_global:
+ running_config: "{{ lookup('file', './fixtures/parsed.cfg') }}"
+ state: parsed
+
+# Task output (redacted)
+# -----------------------
+# parsed:
+# console:
+# severity: error
+# facilities:
+# - facility: auth
+# severity: critical
+# - facility: ftp
+# severity: informational
+# - facility: ospfv3
+# severity: alert
+# ip:
+# access_list:
+# cache:
+# entries: 16384
+# interval: 200
+# threshold: 5000
+# monitor:
+# severity: warning
+# origin_id:
+# hostname: true
+# hosts:
+# - severity: alert
+# host: 203.0.113.100
+# use_vrf: management
+# - facility: local6
+# severity: error
+# host: 203.0.113.101
+# use_vrf: default
+"""
+
+RETURN = """
+before:
+ description: The configuration prior to the module execution.
+ returned: when state is I(merged), I(replaced), I(overridden), I(deleted) or I(purged)
+ type: dict
+ sample: >
+ This output will always be in the same format as the
+ module argspec.
+after:
+ description: The resulting configuration after module execution.
+ returned: when changed
+ type: dict
+ sample: >
+ This output will always be in the same format as the
+ module argspec.
+commands:
+ description: The set of commands pushed to the remote device.
+ returned: when state is I(merged), I(replaced), I(overridden), I(deleted) or I(purged)
+ type: list
+ sample:
+ - "logging console 3"
+ - "logging monitor 4"
+ - "logging ip access-list cache entries 16384"
+ - "logging ip access-list cache interval 200"
+ - "logging ip access-list cache threshold 5000"
+rendered:
+ description: The provided configuration in the task rendered in device-native format (offline).
+ returned: when state is I(rendered)
+ type: list
+ sample:
+ - "logging ip access-list cache entries 4096"
+ - "no logging ip access-list cache interval 200"
+ - "no logging ip access-list cache threshold 5000"
+ - "no logging origin-id hostname"
+ - "logging origin-id ip 192.0.2.100"
+ - "logging server 198.51.100.101 1 port 6538 use-vrf management"
+gathered:
+ description: Facts about the network resource gathered from the remote device as structured data.
+ returned: when state is I(gathered)
+ type: list
+ sample: >
+ This output will always be in the same format as the
+ module argspec.
+parsed:
+ description: The device native config provided in I(running_config) option parsed into structured data as per module argspec.
+ returned: when state is I(parsed)
+ type: list
+ sample: >
+ This output will always be in the same format as the
+ module argspec.
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.logging_global.logging_global import (
+ Logging_globalArgs,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.config.logging_global.logging_global import (
+ Logging_global,
+)
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ module = AnsibleModule(
+ argument_spec=Logging_globalArgs.argument_spec,
+ mutually_exclusive=[["config", "running_config"]],
+ required_if=[
+ ["state", "merged", ["config"]],
+ ["state", "replaced", ["config"]],
+ ["state", "overridden", ["config"]],
+ ["state", "rendered", ["config"]],
+ ["state", "parsed", ["running_config"]],
+ ],
+ supports_check_mode=True,
+ )
+
+ result = Logging_global(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_ntp.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_ntp.py
new file mode 100644
index 00000000..046436d4
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_ntp.py
@@ -0,0 +1,446 @@
+#!/usr/bin/python
+# Copyright: Ansible Project
+# 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
+
+
+DOCUMENTATION = """
+module: nxos_ntp
+extends_documentation_fragment:
+- cisco.nxos.nxos
+short_description: Manages core NTP configuration.
+notes:
+- Limited Support for Cisco MDS
+description:
+- Manages core NTP configuration.
+version_added: 1.0.0
+deprecated:
+ alternative: nxos_ntp_global
+ why: Updated module released with more functionality.
+ removed_at_date: '2024-01-01'
+author:
+- Jason Edelman (@jedelman8)
+options:
+ server:
+ description:
+ - Network address of NTP server.
+ type: str
+ peer:
+ description:
+ - Network address of NTP peer.
+ type: str
+ key_id:
+ description:
+ - Authentication key identifier to use with given NTP server or peer or keyword
+ 'default'.
+ type: str
+ prefer:
+ description:
+ - Makes given NTP server or peer the preferred NTP server or peer for the device.
+ choices:
+ - enabled
+ - disabled
+ type: str
+ vrf_name:
+ description:
+ - Makes the device communicate with the given NTP server or peer over a specific
+ VRF or keyword 'default'.
+ type: str
+ source_addr:
+ description:
+ - Local source address from which NTP messages are sent or keyword 'default'.
+ type: str
+ source_int:
+ description:
+ - Local source interface from which NTP messages are sent. Must be fully qualified
+ interface name or keyword 'default'
+ type: str
+ state:
+ description:
+ - Manage the state of the resource.
+ default: present
+ choices:
+ - present
+ - absent
+ type: str
+"""
+
+EXAMPLES = """
+# Set NTP Server with parameters
+- cisco.nxos.nxos_ntp:
+ server: 1.2.3.4
+ key_id: 32
+ prefer: enabled
+ host: '{{ inventory_hostname }}'
+ username: '{{ un }}'
+ password: '{{ pwd }}'
+"""
+
+RETURN = """
+proposed:
+ description: k/v pairs of parameters passed into module
+ returned: always
+ type: dict
+ sample: {"address": "192.0.2.2", "key_id": "48",
+ "peer_type": "server", "prefer": "enabled",
+ "source": "192.0.2.3", "source_type": "source"}
+existing:
+ description:
+ - k/v pairs of existing ntp server/peer
+ returned: always
+ type: dict
+ sample: {"address": "192.0.2.2", "key_id": "32",
+ "peer_type": "server", "prefer": "enabled",
+ "source": "ethernet2/1", "source_type": "source-interface"}
+end_state:
+ description: k/v pairs of ntp info after module execution
+ returned: always
+ type: dict
+ sample: {"address": "192.0.2.2", "key_id": "48",
+ "peer_type": "server", "prefer": "enabled",
+ "source": "192.0.2.3", "source_type": "source"}
+updates:
+ description: command sent to the device
+ returned: always
+ type: list
+ sample: ["ntp server 192.0.2.2 prefer key 48",
+ "no ntp source-interface ethernet2/1", "ntp source 192.0.2.3"]
+changed:
+ description: check to see if a change was made on the device
+ returned: always
+ type: bool
+ sample: true
+"""
+
+import re
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ load_config,
+ run_commands,
+)
+
+
+def execute_show_command(command, module, command_type="cli_show"):
+ if "show run" not in command:
+ output = "json"
+ else:
+ output = "text"
+
+ commands = [{"command": command, "output": output}]
+ return run_commands(module, commands)
+
+
+def flatten_list(command_lists):
+ flat_command_list = []
+ for command in command_lists:
+ if isinstance(command, list):
+ flat_command_list.extend(command)
+ else:
+ flat_command_list.append(command)
+ return flat_command_list
+
+
+def get_ntp_source(module):
+ source_type = None
+ source = None
+ command = "show run | inc ntp.source"
+ output = execute_show_command(command, module, command_type="cli_show_ascii")
+
+ if output:
+ try:
+ if "interface" in output[0]:
+ source_type = "source-interface"
+ else:
+ source_type = "source"
+ source = output[0].split()[2].lower()
+ except (AttributeError, IndexError):
+ source_type = None
+ source = None
+
+ return source_type, source
+
+
+def get_ntp_peer(module):
+ command = "show run | inc ntp.(server|peer)"
+ ntp_peer_list = []
+ response = execute_show_command(command, module, command_type="cli_show_ascii")
+
+ if response:
+ if isinstance(response, list):
+ ntp = response[0]
+ else:
+ ntp = response
+ if ntp:
+ ntp_regex = (
+ r".*ntp\s(server\s(?P<address>\S+)|peer\s(?P<peer_address>\S+))"
+ r"\s*((?P<prefer>prefer)\s*)?(use-vrf\s(?P<vrf_name>\S+)\s*)?"
+ r"(key\s(?P<key_id>\d+))?.*"
+ )
+
+ split_ntp = ntp.splitlines()
+ for peer_line in split_ntp:
+ if "access-group" in peer_line:
+ continue
+ ntp_peer = {}
+ try:
+ peer_address = None
+ vrf_name = "default"
+ prefer = None
+ key_id = None
+ match_ntp = re.match(ntp_regex, peer_line, re.DOTALL)
+ group_ntp = match_ntp.groupdict()
+
+ address = group_ntp["address"]
+ peer_address = group_ntp["peer_address"]
+ prefer = group_ntp["prefer"]
+ vrf_name = group_ntp["vrf_name"]
+ key_id = group_ntp["key_id"]
+
+ if prefer is not None:
+ prefer = "enabled"
+ else:
+ prefer = "disabled"
+
+ if address is not None:
+ peer_type = "server"
+ elif peer_address is not None:
+ peer_type = "peer"
+ address = peer_address
+
+ args = dict(
+ peer_type=peer_type,
+ address=address,
+ prefer=prefer,
+ vrf_name=vrf_name,
+ key_id=key_id,
+ )
+
+ ntp_peer = dict((k, v) for k, v in args.items())
+ ntp_peer_list.append(ntp_peer)
+ except AttributeError:
+ ntp_peer_list = []
+
+ return ntp_peer_list
+
+
+def get_ntp_existing(address, peer_type, module):
+ peer_dict = {}
+ peer_server_list = []
+
+ peer_list = get_ntp_peer(module)
+ for peer in peer_list:
+ if peer["address"] == address:
+ peer_dict.update(peer)
+ else:
+ peer_server_list.append(peer)
+
+ source_type, source = get_ntp_source(module)
+
+ if source_type is not None and source is not None:
+ peer_dict["source_type"] = source_type
+ peer_dict["source"] = source
+
+ return (peer_dict, peer_server_list)
+
+
+def set_ntp_server_peer(peer_type, address, prefer, key_id, vrf_name):
+ command_strings = []
+
+ if prefer:
+ command_strings.append(" prefer")
+ if key_id:
+ command_strings.append(" key {0}".format(key_id))
+ if vrf_name:
+ command_strings.append(" use-vrf {0}".format(vrf_name))
+
+ command_strings.insert(0, "ntp {0} {1}".format(peer_type, address))
+
+ command = "".join(command_strings)
+
+ return command
+
+
+def config_ntp(delta, existing):
+ if (
+ delta.get("address")
+ or delta.get("peer_type")
+ or delta.get("vrf_name")
+ or delta.get("key_id")
+ or delta.get("prefer")
+ ):
+ address = delta.get("address", existing.get("address"))
+ peer_type = delta.get("peer_type", existing.get("peer_type"))
+ key_id = delta.get("key_id", existing.get("key_id"))
+ prefer = delta.get("prefer", existing.get("prefer"))
+ vrf_name = delta.get("vrf_name", existing.get("vrf_name"))
+ if delta.get("key_id") == "default":
+ key_id = None
+ else:
+ peer_type = None
+ prefer = None
+
+ source_type = delta.get("source_type")
+ source = delta.get("source")
+
+ if prefer:
+ if prefer == "enabled":
+ prefer = True
+ elif prefer == "disabled":
+ prefer = False
+
+ if source:
+ source_type = delta.get("source_type", existing.get("source_type"))
+
+ ntp_cmds = []
+ if peer_type:
+ if existing.get("peer_type") and existing.get("address"):
+ ntp_cmds.append(
+ "no ntp {0} {1}".format(existing.get("peer_type"), existing.get("address")),
+ )
+ ntp_cmds.append(set_ntp_server_peer(peer_type, address, prefer, key_id, vrf_name))
+ if source:
+ existing_source_type = existing.get("source_type")
+ existing_source = existing.get("source")
+ if existing_source_type and source_type != existing_source_type:
+ ntp_cmds.append("no ntp {0} {1}".format(existing_source_type, existing_source))
+ if source == "default":
+ if existing_source_type and existing_source:
+ ntp_cmds.append("no ntp {0} {1}".format(existing_source_type, existing_source))
+ else:
+ ntp_cmds.append("ntp {0} {1}".format(source_type, source))
+
+ return ntp_cmds
+
+
+def main():
+ argument_spec = dict(
+ server=dict(type="str"),
+ peer=dict(type="str"),
+ key_id=dict(type="str"),
+ prefer=dict(type="str", choices=["enabled", "disabled"]),
+ vrf_name=dict(type="str"),
+ source_addr=dict(type="str"),
+ source_int=dict(type="str"),
+ state=dict(choices=["absent", "present"], default="present"),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ mutually_exclusive=[["server", "peer"], ["source_addr", "source_int"]],
+ supports_check_mode=True,
+ )
+
+ warnings = list()
+
+ server = module.params["server"] or None
+ peer = module.params["peer"] or None
+ key_id = module.params["key_id"]
+ prefer = module.params["prefer"]
+ vrf_name = module.params["vrf_name"]
+ source_addr = module.params["source_addr"]
+ source_int = module.params["source_int"]
+ state = module.params["state"]
+
+ if source_int is not None:
+ source_int = source_int.lower()
+
+ if server:
+ peer_type = "server"
+ address = server
+ elif peer:
+ peer_type = "peer"
+ address = peer
+ else:
+ peer_type = None
+ address = None
+
+ source_type = None
+ source = None
+ if source_addr:
+ source_type = "source"
+ source = source_addr
+ elif source_int:
+ source_type = "source-interface"
+ source = source_int
+
+ if key_id or vrf_name or prefer:
+ if not server and not peer:
+ module.fail_json(msg="Please supply the server or peer parameter")
+
+ args = dict(
+ peer_type=peer_type,
+ address=address,
+ key_id=key_id,
+ prefer=prefer,
+ vrf_name=vrf_name,
+ source_type=source_type,
+ source=source,
+ )
+
+ proposed = dict((k, v) for k, v in args.items() if v is not None)
+
+ existing, peer_server_list = get_ntp_existing(address, peer_type, module)
+
+ end_state = existing
+ changed = False
+ commands = []
+
+ if state == "present":
+ delta = dict(set(proposed.items()).difference(existing.items()))
+ if delta.get("key_id") and delta.get("key_id") == "default":
+ if not existing.get("key_id"):
+ delta.pop("key_id")
+ if delta:
+ command = config_ntp(delta, existing)
+ if command:
+ commands.append(command)
+
+ elif state == "absent":
+ if existing.get("peer_type") and existing.get("address"):
+ command = "no ntp {0} {1}".format(existing["peer_type"], existing["address"])
+ if command:
+ commands.append([command])
+
+ existing_source_type = existing.get("source_type")
+ existing_source = existing.get("source")
+ proposed_source_type = proposed.get("source_type")
+ proposed_source = proposed.get("source")
+
+ if proposed_source_type:
+ if proposed_source_type == existing_source_type:
+ if proposed_source == existing_source:
+ command = "no ntp {0} {1}".format(existing_source_type, existing_source)
+ if command:
+ commands.append([command])
+
+ cmds = flatten_list(commands)
+ if cmds:
+ if module.check_mode:
+ module.exit_json(changed=True, commands=cmds)
+ else:
+ changed = True
+ load_config(module, cmds)
+ end_state = get_ntp_existing(address, peer_type, module)[0]
+ if "configure" in cmds:
+ cmds.pop(0)
+
+ results = {}
+ results["proposed"] = proposed
+ results["existing"] = existing
+ results["updates"] = cmds
+ results["changed"] = changed
+ results["warnings"] = warnings
+ results["end_state"] = end_state
+ results["peer_server_list"] = peer_server_list
+
+ module.exit_json(**results)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_ntp_auth.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_ntp_auth.py
new file mode 100644
index 00000000..3e564381
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_ntp_auth.py
@@ -0,0 +1,336 @@
+#!/usr/bin/python
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_ntp_auth
+extends_documentation_fragment:
+- cisco.nxos.nxos
+short_description: Manages NTP authentication.
+description:
+- Manages NTP authentication.
+version_added: 1.0.0
+deprecated:
+ alternative: nxos_ntp_global
+ why: Updated module released with more functionality.
+ removed_at_date: '2024-01-01'
+author:
+- Jason Edelman (@jedelman8)
+notes:
+- Tested against NXOSv 7.3.(0)D1(1) on VIRL
+- Limited Support for Cisco MDS
+- If C(state=absent), the module will remove the given key configuration if it exists.
+- If C(state=absent) and C(authentication=on), authentication will be turned off.
+options:
+ key_id:
+ description:
+ - Authentication key identifier (numeric).
+ type: str
+ md5string:
+ description:
+ - MD5 String.
+ type: str
+ auth_type:
+ description:
+ - Whether the given md5string is in cleartext or has been encrypted. If in cleartext,
+ the device will encrypt it before storing it.
+ default: text
+ choices:
+ - text
+ - encrypt
+ type: str
+ trusted_key:
+ description:
+ - Whether the given key is required to be supplied by a time source for the device
+ to synchronize to the time source.
+ choices:
+ - 'false'
+ - 'true'
+ default: 'false'
+ type: str
+ authentication:
+ description:
+ - Turns NTP authentication on or off.
+ choices:
+ - "on"
+ - "off"
+ type: str
+ state:
+ description:
+ - Manage the state of the resource.
+ default: present
+ choices:
+ - present
+ - absent
+ type: str
+"""
+
+EXAMPLES = """
+# Basic NTP authentication configuration
+- cisco.nxos.nxos_ntp_auth:
+ key_id: 32
+ md5string: hello
+ auth_type: text
+"""
+
+RETURN = """
+commands:
+ description: command sent to the device
+ returned: always
+ type: list
+ sample: ["ntp authentication-key 32 md5 helloWorld 0", "ntp trusted-key 32"]
+"""
+
+
+import re
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ load_config,
+ run_commands,
+)
+
+
+def execute_show_command(command, module):
+ if "show run" not in command:
+ command = {"command": command, "output": "json"}
+ else:
+ command = {"command": command, "output": "text"}
+
+ return run_commands(module, [command])
+
+
+def flatten_list(command_lists):
+ flat_command_list = []
+ for command in command_lists:
+ if isinstance(command, list):
+ flat_command_list.extend(command)
+ else:
+ flat_command_list.append(command)
+ return flat_command_list
+
+
+def get_ntp_auth(module):
+ command = "show ntp authentication-status"
+
+ body = execute_show_command(command, module)[0]
+ ntp_auth_str = body["authentication"]
+
+ if "enabled" in ntp_auth_str:
+ ntp_auth = True
+ else:
+ ntp_auth = False
+
+ return ntp_auth
+
+
+def get_ntp_trusted_key(module):
+ trusted_key_list = []
+ command = "show run | inc ntp.trusted-key"
+
+ trusted_key_str = execute_show_command(command, module)[0]
+ if trusted_key_str:
+ trusted_keys = trusted_key_str.splitlines()
+
+ else:
+ trusted_keys = []
+
+ for line in trusted_keys:
+ if line:
+ trusted_key_list.append(str(line.split()[2]))
+
+ return trusted_key_list
+
+
+def get_ntp_auth_key(key_id, module):
+ authentication_key = {}
+ command = "show run | inc ntp.authentication-key.{0}".format(key_id)
+ auth_regex = (
+ r".*ntp\sauthentication-key\s(?P<key_id>\d+)\smd5\s(?P<md5string>\S+)\s(?P<atype>\S+).*"
+ )
+
+ body = execute_show_command(command, module)[0]
+
+ try:
+ match_authentication = re.match(auth_regex, body, re.DOTALL)
+ group_authentication = match_authentication.groupdict()
+ authentication_key["key_id"] = group_authentication["key_id"]
+ authentication_key["md5string"] = group_authentication["md5string"]
+ if group_authentication["atype"] == "7":
+ authentication_key["auth_type"] = "encrypt"
+ else:
+ authentication_key["auth_type"] = "text"
+ except (AttributeError, TypeError):
+ authentication_key = {}
+
+ return authentication_key
+
+
+def get_ntp_auth_info(key_id, module):
+ auth_info = get_ntp_auth_key(key_id, module)
+ trusted_key_list = get_ntp_trusted_key(module)
+ auth_power = get_ntp_auth(module)
+
+ if key_id in trusted_key_list:
+ auth_info["trusted_key"] = "true"
+ else:
+ auth_info["trusted_key"] = "false"
+
+ if auth_power:
+ auth_info["authentication"] = "on"
+ else:
+ auth_info["authentication"] = "off"
+
+ return auth_info
+
+
+def auth_type_to_num(auth_type):
+ if auth_type == "encrypt":
+ return "7"
+ else:
+ return "0"
+
+
+def set_ntp_auth_key(key_id, md5string, auth_type, trusted_key, authentication):
+ ntp_auth_cmds = []
+ if key_id and md5string:
+ auth_type_num = auth_type_to_num(auth_type)
+ ntp_auth_cmds.append(
+ "ntp authentication-key {0} md5 {1} {2}".format(key_id, md5string, auth_type_num),
+ )
+
+ if trusted_key == "true":
+ ntp_auth_cmds.append("ntp trusted-key {0}".format(key_id))
+ elif trusted_key == "false":
+ ntp_auth_cmds.append("no ntp trusted-key {0}".format(key_id))
+
+ if authentication == "on":
+ ntp_auth_cmds.append("ntp authenticate")
+ elif authentication == "off":
+ ntp_auth_cmds.append("no ntp authenticate")
+
+ return ntp_auth_cmds
+
+
+def remove_ntp_auth_key(key_id, md5string, auth_type, trusted_key, authentication):
+ auth_remove_cmds = []
+ if key_id:
+ auth_type_num = auth_type_to_num(auth_type)
+ auth_remove_cmds.append(
+ "no ntp authentication-key {0} md5 {1} {2}".format(key_id, md5string, auth_type_num),
+ )
+
+ if authentication:
+ auth_remove_cmds.append("no ntp authenticate")
+ return auth_remove_cmds
+
+
+def main():
+ argument_spec = dict(
+ key_id=dict(type="str"),
+ md5string=dict(type="str"),
+ auth_type=dict(choices=["text", "encrypt"], default="text"),
+ trusted_key=dict(choices=["true", "false"], default="false"),
+ authentication=dict(choices=["on", "off"]),
+ state=dict(choices=["absent", "present"], default="present"),
+ )
+
+ module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)
+
+ warnings = list()
+
+ key_id = module.params["key_id"]
+ md5string = module.params["md5string"]
+ auth_type = module.params["auth_type"]
+ trusted_key = module.params["trusted_key"]
+ authentication = module.params["authentication"]
+ state = module.params["state"]
+
+ if key_id:
+ if not trusted_key and not md5string:
+ module.fail_json(msg="trusted_key or md5string MUST be specified")
+
+ args = dict(
+ key_id=key_id,
+ md5string=md5string,
+ auth_type=auth_type,
+ trusted_key=trusted_key,
+ authentication=authentication,
+ )
+
+ changed = False
+ proposed = dict((k, v) for k, v in args.items() if v is not None)
+
+ existing = get_ntp_auth_info(key_id, module)
+ end_state = existing
+
+ delta = dict(set(proposed.items()).difference(existing.items()))
+
+ commands = []
+ if state == "present":
+ if delta:
+ command = set_ntp_auth_key(
+ key_id,
+ md5string,
+ delta.get("auth_type"),
+ delta.get("trusted_key"),
+ delta.get("authentication"),
+ )
+ if command:
+ commands.append(command)
+ elif state == "absent":
+ auth_toggle = None
+ if existing.get("authentication") == "on":
+ auth_toggle = True
+ if not existing.get("key_id"):
+ key_id = None
+ command = remove_ntp_auth_key(key_id, md5string, auth_type, trusted_key, auth_toggle)
+ if command:
+ commands.append(command)
+
+ cmds = flatten_list(commands)
+ if cmds:
+ if module.check_mode:
+ module.exit_json(changed=True, commands=cmds)
+ else:
+ load_config(module, cmds)
+ end_state = get_ntp_auth_info(key_id, module)
+ delta = dict(set(end_state.items()).difference(existing.items()))
+ if delta or (len(existing) != len(end_state)):
+ changed = True
+ if "configure" in cmds:
+ cmds.pop(0)
+
+ results = {}
+ results["proposed"] = proposed
+ results["existing"] = existing
+ results["updates"] = cmds
+ results["changed"] = changed
+ results["warnings"] = warnings
+ results["end_state"] = end_state
+
+ module.exit_json(**results)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_ntp_global.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_ntp_global.py
new file mode 100644
index 00000000..e99fbef8
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_ntp_global.py
@@ -0,0 +1,736 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# Copyright 2021 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+"""
+The module file for nxos_ntp_global
+"""
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+DOCUMENTATION = """
+module: nxos_ntp_global
+short_description: NTP Global resource module.
+description:
+- This module manages ntp configuration on devices running Cisco NX-OS.
+version_added: 2.6.0
+notes:
+- Tested against NX-OS 9.3.6 on Cisco Nexus Switches.
+- This module works with connection C(network_cli) and C(httpapi).
+- Tested against Cisco MDS NX-OS 9.2(2) with connection C(network_cli).
+author: Nilashish Chakraborty (@NilashishC)
+options:
+ running_config:
+ description:
+ - This option is used only with state I(parsed).
+ - The value of this option should be the output received from the NX-OS device
+ by executing the command B(show running-config ntp).
+ - The state I(parsed) reads the configuration from C(running_config) option and
+ transforms it into Ansible structured data as per the resource module's argspec
+ and the value is then returned in the I(parsed) key within the result.
+ type: str
+ config:
+ description: A dict of ntp configuration.
+ type: dict
+ suboptions:
+ access_group:
+ description:
+ - NTP access-group.
+ - This option is unsupported on MDS switches.
+ type: dict
+ suboptions:
+ match_all:
+ description: Scan ACLs present in all ntp access groups.
+ type: bool
+ peer:
+ description: Access-group peer.
+ type: list
+ elements: dict
+ suboptions:
+ access_list:
+ description: Name of access list.
+ type: str
+ query_only:
+ description: Access-group query-only.
+ type: list
+ elements: dict
+ suboptions:
+ access_list:
+ description: Name of access list.
+ type: str
+ serve:
+ description: Access-group serve.
+ type: list
+ elements: dict
+ suboptions:
+ access_list:
+ description: Name of access list.
+ type: str
+ serve_only:
+ description: Access-group serve-only.
+ type: list
+ elements: dict
+ suboptions:
+ access_list:
+ description: Name of access list.
+ type: str
+ allow:
+ description: Enable/Disable the packets.
+ type: dict
+ suboptions:
+ control:
+ description: Control mode packets.
+ type: dict
+ suboptions:
+ rate_limit:
+ description: Rate-limit delay.
+ type: int
+ private:
+ description: Enable/Disable Private mode packets.
+ type: bool
+ authenticate:
+ description: Enable/Disable authentication.
+ type: bool
+ authentication_keys:
+ description: NTP authentication key.
+ type: list
+ elements: dict
+ suboptions:
+ id:
+ description: Authentication key number (range 1-65535).
+ type: int
+ key:
+ description: Authentication key.
+ type: str
+ encryption:
+ description:
+ - 0 for Clear text
+ - 7 for Encrypted
+ type: int
+ logging:
+ description: Enable/Disable logging of NTPD Events.
+ type: bool
+ master:
+ description:
+ - Act as NTP master clock.
+ - This option is unsupported on MDS switches.
+ type: dict
+ suboptions:
+ stratum:
+ description: Stratum number.
+ type: int
+ passive:
+ description:
+ - NTP passive command.
+ - This option is unsupported on MDS switches.
+ type: bool
+ peers:
+ description: NTP Peers.
+ type: list
+ elements: dict
+ suboptions:
+ peer:
+ description: Hostname/IP address of the NTP Peer.
+ type: str
+ key_id:
+ description: Keyid to be used while communicating to this server.
+ type: int
+ maxpoll:
+ description:
+ - Maximum interval to poll a peer.
+ - Poll interval in secs to a power of 2.
+ type: int
+ minpoll:
+ description:
+ - Minimum interval to poll a peer.
+ - Poll interval in secs to a power of 2.
+ type: int
+ prefer:
+ description:
+ - Preferred Server.
+ type: bool
+ vrf:
+ description:
+ - Display per-VRF information.
+ - This option is unsupported on MDS switches.
+ type: str
+ aliases: ["use_vrf"]
+ servers:
+ description: NTP servers.
+ type: list
+ elements: dict
+ suboptions:
+ server:
+ description: Hostname/IP address of the NTP Peer.
+ type: str
+ key_id:
+ description: Keyid to be used while communicating to this server.
+ type: int
+ maxpoll:
+ description:
+ - Maximum interval to poll a peer.
+ - Poll interval in secs to a power of 2.
+ type: int
+ minpoll:
+ description:
+ - Minimum interval to poll a peer.
+ - Poll interval in secs to a power of 2.
+ type: int
+ prefer:
+ description:
+ - Preferred Server.
+ type: bool
+ vrf:
+ description:
+ - Display per-VRF information.
+ - This option is not applicable for MDS switches.
+ type: str
+ aliases: ["use_vrf"]
+ source:
+ description:
+ - Source of NTP packets.
+ - This option is unsupported on MDS switches.
+ type: str
+ source_interface:
+ description: Source interface sending NTP packets.
+ type: str
+ trusted_keys:
+ description: NTP trusted-key number.
+ type: list
+ elements: dict
+ suboptions:
+ key_id:
+ description: Trusted-Key number.
+ type: int
+ state:
+ description:
+ - The state the configuration should be left in.
+ - The states I(replaced) and I(overridden) have identical
+ behaviour for this module.
+ - Please refer to examples for more details.
+ type: str
+ choices:
+ - merged
+ - replaced
+ - overridden
+ - deleted
+ - parsed
+ - gathered
+ - rendered
+ default: merged
+"""
+
+EXAMPLES = """
+# Using merged
+
+# Before state:
+# -------------
+# nxos-9k-rdo# show running-config ntp
+# nxos-9k-rdo#
+
+- name: Merge the provided configuration with the existing running configuration
+ cisco.nxos.nxos_ntp_global: &id001
+ config:
+ access_group:
+ peer:
+ - access_list: PeerAcl1
+ serve:
+ - access_list: ServeAcl1
+ authenticate: True
+ authentication_keys:
+ - id: 1001
+ key: vagwwtKfkv
+ encryption: 7
+ - id: 1002
+ key: vagwwtKfkvgthz
+ encryption: 7
+ logging: True
+ master:
+ stratum: 2
+ peers:
+ - peer: 192.0.2.1
+ key_id: 1
+ maxpoll: 15
+ minpoll: 5
+ vrf: default
+ - peer: 192.0.2.2
+ key_id: 2
+ prefer: True
+ vrf: siteA
+ servers:
+ - server: 198.51.100.1
+ key_id: 2
+ vrf: default
+ - server: 203.0.113.1
+ key_id: 1
+ vrf: siteB
+
+# Task output
+# -------------
+# before: {}
+#
+# commands:
+# - "ntp authenticate"
+# - "ntp logging"
+# - "ntp master 2"
+# - "ntp authentication-keys 1001 md5 vagwwtKfkv 7"
+# - "ntp authentication-keys 1002 md5 vagwwtKfkvgthz 7"
+# - "ntp peer 192.0.2.1 use-vrf default key 1 minpoll 5 maxpoll 15"
+# - "ntp peer 192.0.2.2 prefer use-vrf siteA key 2"
+# - "ntp server 198.51.100.1 use-vrf default key 2"
+# - "ntp server 203.0.113.1 use-vrf siteB key 1"
+# - "ntp access-group peer PeerAcl1"
+# - "ntp access-group serve ServeAcl1"
+#
+# after:
+# access_group:
+# peer:
+# - access_list: PeerAcl1
+# serve:
+# - access_list: ServeAcl1
+# authenticate: True
+# authentication_keys:
+# - id: 1001
+# key: vagwwtKfkv
+# encryption: 7
+# - id: 1002
+# key: vagwwtKfkvgthz
+# encryption: 7
+# logging: True
+# master:
+# stratum: 2
+# peers:
+# - peer: 192.0.2.1
+# key_id: 1
+# maxpoll: 15
+# minpoll: 5
+# vrf: default
+# - peer: 192.0.2.2
+# key_id: 2
+# prefer: True
+# vrf: siteA
+# servers:
+# - server: 198.51.100.1
+# key_id: 2
+# vrf: default
+# - server: 203.0.113.1
+# key_id: 1
+# vrf: siteB
+
+# After state:
+# ------------
+# nxos-9k-rdo# show running-config ntp
+# ntp authenticate
+# ntp logging
+# ntp master 2
+# ntp authentication-keys 1001 md5 vagwwtKfkv 7
+# ntp authentication-keys 1002 md5 vagwwtKfkvgthz 7
+# ntp peer 192.0.2.1 use-vrf default key 1 minpoll 5 maxpoll 15
+# ntp peer 192.0.2.2 prefer use-vrf siteA key 2
+# ntp server 198.51.100.1 use-vrf default key 2
+# ntp server 203.0.113.1 use-vrf siteB key 1
+# ntp access-group peer PeerAcl1
+# ntp access-group serve ServeAcl1
+
+# Using replaced
+
+# Before state:
+# ------------
+# nxos-9k-rdo# show running-config ntp
+# ntp authenticate
+# ntp logging
+# ntp master 2
+# ntp authentication-keys 1001 md5 vagwwtKfkv 7
+# ntp authentication-keys 1002 md5 vagwwtKfkvgthz 7
+# ntp peer 192.0.2.1 use-vrf default key 1 minpoll 5 maxpoll 15
+# ntp peer 192.0.2.2 prefer use-vrf siteA key 2
+# ntp server 198.51.100.1 use-vrf default key 2
+# ntp server 203.0.113.1 use-vrf siteB key 1
+# ntp access-group peer PeerAcl1
+# ntp access-group serve ServeAcl1
+
+- name: Replace logging global configurations of listed logging global with provided configurations
+ cisco.nxos.nxos_ntp_global:
+ config:
+ access_group:
+ peer:
+ - access_list: PeerAcl2
+ serve:
+ - access_list: ServeAcl2
+ logging: True
+ master:
+ stratum: 2
+ peers:
+ - peer: 192.0.2.1
+ key_id: 1
+ maxpoll: 15
+ minpoll: 5
+ vrf: default
+ - peer: 192.0.2.5
+ key_id: 2
+ prefer: True
+ vrf: siteA
+ servers:
+ - server: 198.51.100.1
+ key_id: 2
+ vrf: default
+ state: replaced
+
+# Task output
+# -------------
+# before:
+# access_group:
+# peer:
+# - access_list: PeerAcl1
+# serve:
+# - access_list: ServeAcl1
+# authenticate: True
+# authentication_keys:
+# - id: 1001
+# key: vagwwtKfkv
+# encryption: 7
+# - id: 1002
+# key: vagwwtKfkvgthz
+# encryption: 7
+# logging: True
+# master:
+# stratum: 2
+# peers:
+# - peer: 192.0.2.1
+# key_id: 1
+# maxpoll: 15
+# minpoll: 5
+# vrf: default
+# - peer: 192.0.2.2
+# key_id: 2
+# prefer: True
+# vrf: siteA
+# servers:
+# - server: 198.51.100.1
+# key_id: 2
+# vrf: default
+# - server: 203.0.113.1
+# key_id: 1
+# vrf: siteB
+#
+# commands:
+# - "no ntp authenticate"
+# - "no ntp authentication-keys 1001 md5 vagwwtKfkv 7"
+# - "no ntp authentication-keys 1002 md5 vagwwtKfkvgthz 7"
+# - "ntp peer 192.0.2.5 prefer use-vrf siteA key 2"
+# - "no ntp peer 192.0.2.2 prefer use-vrf siteA key 2"
+# - "no ntp server 203.0.113.1 use-vrf siteB key 1"
+# - "ntp access-group peer PeerAcl2"
+# - "no ntp access-group peer PeerAcl1"
+# - "ntp access-group serve ServeAcl2"
+# - "no ntp access-group serve ServeAcl1"
+#
+# after:
+# access_group:
+# peer:
+# - access_list: PeerAcl2
+# serve:
+# - access_list: ServeAcl2
+# logging: True
+# master:
+# stratum: 2
+# peers:
+# - peer: 192.0.2.1
+# key_id: 1
+# maxpoll: 15
+# minpoll: 5
+# vrf: default
+# - peer: 192.0.2.5
+# key_id: 2
+# prefer: True
+# vrf: siteA
+# servers:
+# - server: 198.51.100.1
+# key_id: 2
+# vrf: default
+
+# After state:
+# ------------
+# nxos-9k-rdo# show running-config ntp
+# ntp logging
+# ntp master 2
+# ntp peer 192.0.2.1 use-vrf default key 1 minpoll 5 maxpoll 15
+# ntp peer 192.0.2.5 prefer use-vrf siteA key 2
+# ntp server 198.51.100.1 use-vrf default key 2
+# ntp access-group peer PeerAcl2
+# ntp access-group serve ServeAcl2
+
+# Using deleted to delete all logging configurations
+
+# Before state:
+# ------------
+# nxos-9k-rdo# show running-config ntp
+
+- name: Delete all logging configuration
+ cisco.nxos.nxos_ntp_global:
+ state: deleted
+
+# Task output
+# -------------
+# before:
+# access_group:
+# peer:
+# - access_list: PeerAcl1
+# serve:
+# - access_list: ServeAcl1
+# authenticate: True
+# authentication_keys:
+# - id: 1001
+# key: vagwwtKfkv
+# encryption: 7
+# - id: 1002
+# key: vagwwtKfkvgthz
+# encryption: 7
+# logging: True
+# master:
+# stratum: 2
+# peers:
+# - peer: 192.0.2.1
+# key_id: 1
+# maxpoll: 15
+# minpoll: 5
+# vrf: default
+# - peer: 192.0.2.2
+# key_id: 2
+# prefer: True
+# vrf: siteA
+# servers:
+# - server: 198.51.100.1
+# key_id: 2
+# vrf: default
+# - server: 203.0.113.1
+# key_id: 1
+# vrf: siteB
+#
+# commands:
+# - "no ntp authenticate"
+# - "no ntp logging"
+# - "no ntp master 2"
+# - "no ntp authentication-keys 1001 md5 vagwwtKfkv 7"
+# - "no ntp authentication-keys 1002 md5 vagwwtKfkvgthz 7"
+# - "no ntp peer 192.0.2.1 use-vrf default key 1 minpoll 5 maxpoll 15"
+# - "no ntp peer 192.0.2.2 prefer use-vrf siteA key 2"
+# - "no ntp server 198.51.100.1 use-vrf default key 2"
+# - "no ntp server 203.0.113.1 use-vrf siteB key 1"
+# - "no ntp access-group peer PeerAcl1"
+# - "no ntp access-group serve ServeAcl1"
+#
+# after: {}
+
+# After state:
+# ------------
+# nxos-9k-rdo# show running-config ntp
+# nxos-9k-rdo#
+
+# Using rendered
+
+- name: Render platform specific configuration lines with state rendered (without connecting to the device)
+ cisco.nxos.nxos_ntp_global:
+ config:
+ access_group:
+ peer:
+ - access_list: PeerAcl1
+ serve:
+ - access_list: ServeAcl1
+ authenticate: True
+ authentication_keys:
+ - id: 1001
+ key: vagwwtKfkv
+ encryption: 7
+ - id: 1002
+ key: vagwwtKfkvgthz
+ encryption: 7
+ logging: True
+ master:
+ stratum: 2
+ peers:
+ - peer: 192.0.2.1
+ key_id: 1
+ maxpoll: 15
+ minpoll: 5
+ vrf: default
+ - peer: 192.0.2.2
+ key_id: 2
+ prefer: True
+ vrf: siteA
+ servers:
+ - server: 198.51.100.1
+ key_id: 2
+ vrf: default
+ - server: 203.0.113.1
+ key_id: 1
+ vrf: siteB
+ state: rendered
+
+# Task Output (redacted)
+# -----------------------
+# rendered:
+# - "ntp authenticate"
+# - "ntp logging"
+# - "ntp master 2"
+# - "ntp authentication-keys 1001 md5 vagwwtKfkv 7"
+# - "ntp authentication-keys 1002 md5 vagwwtKfkvgthz 7"
+# - "ntp peer 192.0.2.1 use-vrf default key 1 minpoll 5 maxpoll 15"
+# - "ntp peer 192.0.2.2 prefer use-vrf siteA key 2"
+# - "ntp server 198.51.100.1 use-vrf default key 2"
+# - "ntp server 203.0.113.1 use-vrf siteB key 1"
+# - "ntp access-group peer PeerAcl1"
+# - "ntp access-group serve ServeAcl1"
+
+# Using parsed
+
+# parsed.cfg
+# ------------
+# ntp authenticate
+# ntp logging
+# ntp master 2
+# ntp authentication-keys 1001 md5 vagwwtKfkv 7
+# ntp authentication-keys 1002 md5 vagwwtKfkvgthz 7
+# ntp peer 192.0.2.1 use-vrf default key 1 minpoll 5 maxpoll 15
+# ntp peer 192.0.2.2 prefer use-vrf siteA key 2
+# ntp server 198.51.100.1 use-vrf default key 2
+# ntp server 203.0.113.1 use-vrf siteB key 1
+# ntp access-group peer PeerAcl1
+# ntp access-group serve ServeAcl1
+
+- name: Parse externally provided ntp configuration
+ cisco.nxos.nxos_ntp_global:
+ running_config: "{{ lookup('file', './fixtures/parsed.cfg') }}"
+ state: parsed
+
+# Task output (redacted)
+# -----------------------
+# parsed:
+# access_group:
+# peer:
+# - access_list: PeerAcl1
+# serve:
+# - access_list: ServeAcl1
+# authenticate: True
+# authentication_keys:
+# - id: 1001
+# key: vagwwtKfkv
+# encryption: 7
+# - id: 1002
+# key: vagwwtKfkvgthz
+# encryption: 7
+# logging: True
+# master:
+# stratum: 2
+# peers:
+# - peer: 192.0.2.1
+# key_id: 1
+# maxpoll: 15
+# minpoll: 5
+# vrf: default
+# - peer: 192.0.2.2
+# key_id: 2
+# prefer: True
+# vrf: siteA
+# servers:
+# - server: 198.51.100.1
+# key_id: 2
+# vrf: default
+# - server: 203.0.113.1
+# key_id: 1
+# vrf: siteB
+"""
+
+RETURN = """
+before:
+ description: The configuration prior to the module execution.
+ returned: when I(state) is C(merged), C(replaced), C(overridden), C(deleted) or C(purged)
+ type: dict
+ sample: >
+ This output will always be in the same format as the
+ module argspec.
+after:
+ description: The resulting configuration after module execution.
+ returned: when changed
+ type: dict
+ sample: >
+ This output will always be in the same format as the
+ module argspec.
+commands:
+ description: The set of commands pushed to the remote device.
+ returned: when I(state) is C(merged), C(replaced), C(overridden), C(deleted) or C(purged)
+ type: list
+ sample:
+ - ntp master stratum 2
+ - ntp peer 198.51.100.1 use-vrf test maxpoll 7
+ - ntp authentication-key 10 md5 wawyhanx2 7
+ - ntp access-group peer PeerAcl1
+ - ntp access-group peer PeerAcl2
+ - ntp access-group query-only QueryAcl1
+rendered:
+ description: The provided configuration in the task rendered in device-native format (offline).
+ returned: when I(state) is C(rendered)
+ type: list
+ sample:
+ - ntp master stratum 2
+ - ntp peer 198.51.100.1 use-vrf test maxpoll 7
+ - ntp authentication-key 10 md5 wawyhanx2 7
+ - ntp access-group peer PeerAcl1
+ - ntp access-group peer PeerAcl2
+ - ntp access-group query-only QueryAcl1
+gathered:
+ description: Facts about the network resource gathered from the remote device as structured data.
+ returned: when I(state) is C(gathered)
+ type: list
+ sample: >
+ This output will always be in the same format as the
+ module argspec.
+parsed:
+ description: The device native config provided in I(running_config) option parsed into structured data as per module argspec.
+ returned: when I(state) is C(parsed)
+ type: list
+ sample: >
+ This output will always be in the same format as the
+ module argspec.
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.ntp_global.ntp_global import (
+ Ntp_globalArgs,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.config.ntp_global.ntp_global import (
+ Ntp_global,
+)
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ module = AnsibleModule(
+ argument_spec=Ntp_globalArgs.argument_spec,
+ mutually_exclusive=[["config", "running_config"]],
+ required_if=[
+ ["state", "merged", ["config"]],
+ ["state", "replaced", ["config"]],
+ ["state", "overridden", ["config"]],
+ ["state", "rendered", ["config"]],
+ ["state", "parsed", ["running_config"]],
+ ],
+ supports_check_mode=True,
+ )
+
+ result = Ntp_global(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_ntp_options.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_ntp_options.py
new file mode 100644
index 00000000..28fd1aac
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_ntp_options.py
@@ -0,0 +1,173 @@
+#!/usr/bin/python
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_ntp_options
+extends_documentation_fragment:
+- cisco.nxos.nxos
+short_description: Manages NTP options.
+description:
+- Manages NTP options, e.g. authoritative server and logging.
+version_added: 1.0.0
+deprecated:
+ alternative: nxos_ntp_global
+ why: Updated module released with more functionality.
+ removed_at_date: '2024-01-01'
+author:
+- Jason Edelman (@jedelman8)
+notes:
+- Tested against NXOSv 7.3.(0)D1(1) on VIRL
+- Limited Support for Cisco MDS
+- When C(state=absent), master and logging will be set to False and stratum will be
+ removed as well
+options:
+ master:
+ description:
+ - Sets whether the device is an authoritative NTP server.
+ type: bool
+ stratum:
+ description:
+ - If C(master=true), an optional stratum can be supplied (1-15). The device default
+ is 8.
+ type: str
+ logging:
+ description:
+ - Sets whether NTP logging is enabled on the device.
+ type: bool
+ state:
+ description:
+ - Manage the state of the resource.
+ default: present
+ choices:
+ - present
+ - absent
+ type: str
+"""
+EXAMPLES = """
+# Basic NTP options configuration
+- cisco.nxos.nxos_ntp_options:
+ master: true
+ stratum: 12
+ logging: false
+ host: '{{ inventory_hostname }}'
+ username: '{{ un }}'
+ password: '{{ pwd }}'
+"""
+
+RETURN = """
+updates:
+ description: command sent to the device
+ returned: always
+ type: list
+ sample: ["no ntp logging", "ntp master 12"]
+"""
+import re
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ load_config,
+ run_commands,
+)
+
+
+def get_current(module):
+ cmd = "show running-config | inc ntp"
+
+ master = False
+ logging = False
+ stratum = None
+
+ output = run_commands(module, ({"command": cmd, "output": "text"}))[0]
+
+ if output:
+ match = re.search(r"^ntp master(?: (\d+))", output, re.M)
+ if match:
+ master = True
+ stratum = match.group(1)
+ logging = "ntp logging" in output.lower()
+
+ return {"master": master, "stratum": stratum, "logging": logging}
+
+
+def main():
+ argument_spec = dict(
+ master=dict(required=False, type="bool"),
+ stratum=dict(required=False, type="str"),
+ logging=dict(required=False, type="bool"),
+ state=dict(choices=["absent", "present"], default="present"),
+ )
+
+ module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)
+
+ warnings = list()
+
+ master = module.params["master"]
+ stratum = module.params["stratum"]
+ logging = module.params["logging"]
+ state = module.params["state"]
+
+ if stratum and master is False:
+ if stratum != 8:
+ module.fail_json(msg="master MUST be True when stratum is changed")
+
+ current = get_current(module)
+
+ result = {"changed": False}
+
+ commands = list()
+
+ if state == "absent":
+ if current["master"]:
+ commands.append("no ntp master")
+ if current["logging"]:
+ commands.append("no ntp logging")
+
+ elif state == "present":
+ if master and not current["master"]:
+ commands.append("ntp master")
+ elif master is False and current["master"]:
+ commands.append("no ntp master")
+ if stratum and stratum != current["stratum"]:
+ commands.append("ntp master %s" % stratum)
+
+ if logging and not current["logging"]:
+ commands.append("ntp logging")
+ elif logging is False and current["logging"]:
+ commands.append("no ntp logging")
+
+ result["commands"] = commands
+ result["updates"] = commands
+
+ if commands:
+ if not module.check_mode:
+ load_config(module, commands)
+ result["changed"] = True
+
+ result["warnings"] = warnings
+
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_nxapi.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_nxapi.py
new file mode 100644
index 00000000..cfd7cc87
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_nxapi.py
@@ -0,0 +1,424 @@
+#!/usr/bin/python
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+# pylint: skip-file
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_nxapi
+extends_documentation_fragment:
+- cisco.nxos.nxos
+author: Peter Sprygada (@privateip)
+short_description: Manage NXAPI configuration on an NXOS device.
+notes:
+- Limited Support for Cisco MDS
+description:
+- Configures the NXAPI feature on devices running Cisco NXOS. The NXAPI feature is
+ absent from the configuration by default. Since this module manages the NXAPI feature
+ it only supports the use of the C(Cli) transport.
+version_added: 1.0.0
+options:
+ http_port:
+ description:
+ - Configure the port with which the HTTP server will listen on for requests. By
+ default, NXAPI will bind the HTTP service to the standard HTTP port 80. This
+ argument accepts valid port values in the range of 1 to 65535.
+ required: false
+ default: 80
+ type: int
+ http:
+ description:
+ - Controls the operating state of the HTTP protocol as one of the underlying transports
+ for NXAPI. By default, NXAPI will enable the HTTP transport when the feature
+ is first configured. To disable the use of the HTTP transport, set the value
+ of this argument to False.
+ required: false
+ default: true
+ type: bool
+ aliases:
+ - enable_http
+ https_port:
+ description:
+ - Configure the port with which the HTTPS server will listen on for requests. By
+ default, NXAPI will bind the HTTPS service to the standard HTTPS port 443. This
+ argument accepts valid port values in the range of 1 to 65535.
+ required: false
+ default: 443
+ type: int
+ https:
+ description:
+ - Controls the operating state of the HTTPS protocol as one of the underlying
+ transports for NXAPI. By default, NXAPI will disable the HTTPS transport when
+ the feature is first configured. To enable the use of the HTTPS transport,
+ set the value of this argument to True.
+ required: false
+ default: false
+ type: bool
+ aliases:
+ - enable_https
+ sandbox:
+ description:
+ - The NXAPI feature provides a web base UI for developers for entering commands. This
+ feature is initially disabled when the NXAPI feature is configured for the first
+ time. When the C(sandbox) argument is set to True, the developer sandbox URL
+ will accept requests and when the value is set to False, the sandbox URL is
+ unavailable. This is supported on NX-OS 7K series.
+ required: false
+ type: bool
+ aliases:
+ - enable_sandbox
+ state:
+ description:
+ - The C(state) argument controls whether or not the NXAPI feature is configured
+ on the remote device. When the value is C(present) the NXAPI feature configuration
+ is present in the device running-config. When the values is C(absent) the feature
+ configuration is removed from the running-config.
+ choices:
+ - present
+ - absent
+ required: false
+ default: present
+ type: str
+ ssl_strong_ciphers:
+ description:
+ - Controls the use of whether strong or weak ciphers are configured. By default,
+ this feature is disabled and weak ciphers are configured. To enable the use
+ of strong ciphers, set the value of this argument to True.
+ required: false
+ default: false
+ type: bool
+ tlsv1_0:
+ description:
+ - Controls the use of the Transport Layer Security version 1.0 is configured. By
+ default, this feature is enabled. To disable the use of TLSV1.0, set the value
+ of this argument to True.
+ required: false
+ default: true
+ type: bool
+ tlsv1_1:
+ description:
+ - Controls the use of the Transport Layer Security version 1.1 is configured. By
+ default, this feature is disabled. To enable the use of TLSV1.1, set the value
+ of this argument to True.
+ required: false
+ default: false
+ type: bool
+ tlsv1_2:
+ description:
+ - Controls the use of the Transport Layer Security version 1.2 is configured. By
+ default, this feature is disabled. To enable the use of TLSV1.2, set the value
+ of this argument to True.
+ required: false
+ default: false
+ type: bool
+"""
+
+EXAMPLES = """
+- name: Enable NXAPI access with default configuration
+ cisco.nxos.nxos_nxapi:
+ state: present
+
+- name: Enable NXAPI with no HTTP, HTTPS at port 9443 and sandbox disabled
+ cisco.nxos.nxos_nxapi:
+ enable_http: false
+ https_port: 9443
+ https: yes
+ enable_sandbox: no
+
+- name: remove NXAPI configuration
+ cisco.nxos.nxos_nxapi:
+ state: absent
+"""
+
+RETURN = """
+updates:
+ description:
+ - Returns the list of commands that need to be pushed into the remote
+ device to satisfy the arguments
+ returned: always
+ type: list
+ sample: ['no feature nxapi']
+"""
+import re
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ get_capabilities,
+ load_config,
+ run_commands,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.utils.utils import Version
+
+
+def check_args(module, warnings, capabilities):
+ network_api = capabilities.get("network_api", "nxapi")
+ if network_api == "nxapi":
+ module.fail_json(msg="module not supported over nxapi transport")
+
+ os_platform = capabilities["device_info"]["network_os_platform"]
+ if "7K" not in os_platform and module.params["sandbox"]:
+ module.fail_json(
+ msg="sandbox or enable_sandbox is supported on NX-OS 7K series of switches",
+ )
+
+ state = module.params["state"]
+
+ if state == "started":
+ module.params["state"] = "present"
+ warnings.append(
+ "state=started is deprecated and will be removed in a "
+ "a future release. Please use state=present instead",
+ )
+ elif state == "stopped":
+ module.params["state"] = "absent"
+ warnings.append(
+ "state=stopped is deprecated and will be removed in a "
+ "a future release. Please use state=absent instead",
+ )
+
+ for key in ["http_port", "https_port"]:
+ if module.params[key] is not None:
+ if not 1 <= module.params[key] <= 65535:
+ module.fail_json(msg="%s must be between 1 and 65535" % key)
+
+ return warnings
+
+
+def map_obj_to_commands(want, have, module, warnings, capabilities):
+ send_commands = list()
+ commands = dict()
+ os_platform = None
+ os_version = None
+
+ device_info = capabilities.get("device_info")
+ if device_info:
+ os_version = device_info.get("network_os_version")
+ if os_version:
+ os_version = os_version[:3]
+ os_platform = device_info.get("network_os_platform")
+ if os_platform:
+ os_platform = os_platform[:3]
+
+ def needs_update(x):
+ return want.get(x) is not None and (want.get(x) != have.get(x))
+
+ if needs_update("state"):
+ if want["state"] == "absent":
+ return ["no feature nxapi"]
+ send_commands.append("feature nxapi")
+ elif want["state"] == "absent":
+ return send_commands
+
+ for parameter in ["http", "https"]:
+ port_param = parameter + "_port"
+ if needs_update(parameter):
+ if want.get(parameter) is False:
+ commands[parameter] = "no nxapi %s" % parameter
+ else:
+ commands[parameter] = "nxapi %s port %s" % (
+ parameter,
+ want.get(port_param),
+ )
+
+ if needs_update(port_param) and want.get(parameter) is True:
+ commands[parameter] = "nxapi %s port %s" % (
+ parameter,
+ want.get(port_param),
+ )
+
+ if needs_update("sandbox"):
+ commands["sandbox"] = "nxapi sandbox"
+ if not want["sandbox"]:
+ commands["sandbox"] = "no %s" % commands["sandbox"]
+
+ if os_platform and os_version:
+ if (os_platform == "N9K" or os_platform == "N3K") and Version(os_version) >= "9.2":
+ if needs_update("ssl_strong_ciphers"):
+ commands["ssl_strong_ciphers"] = "nxapi ssl ciphers weak"
+ if want["ssl_strong_ciphers"] is True:
+ commands["ssl_strong_ciphers"] = "no nxapi ssl ciphers weak"
+
+ have_ssl_protocols = ""
+ want_ssl_protocols = ""
+ for key, value in {
+ "tlsv1_2": "TLSv1.2",
+ "tlsv1_1": "TLSv1.1",
+ "tlsv1_0": "TLSv1",
+ }.items():
+ if needs_update(key):
+ if want.get(key) is True:
+ want_ssl_protocols = " ".join([want_ssl_protocols, value])
+ elif have.get(key) is True:
+ have_ssl_protocols = " ".join([have_ssl_protocols, value])
+
+ if len(want_ssl_protocols) > 0:
+ commands["ssl_protocols"] = "nxapi ssl protocols%s" % (
+ " ".join([want_ssl_protocols, have_ssl_protocols])
+ )
+ else:
+ warnings.append(
+ "os_version and/or os_platform keys from "
+ "platform capabilities are not available. "
+ "Any NXAPI SSL optional arguments will be ignored",
+ )
+
+ send_commands.extend(commands.values())
+
+ return send_commands
+
+
+def parse_http(data):
+ http_res = [r"nxapi http port (\d+)"]
+ http_port = None
+
+ for regex in http_res:
+ match = re.search(regex, data, re.M)
+ if match:
+ http_port = int(match.group(1))
+ break
+
+ return {"http": http_port is not None, "http_port": http_port}
+
+
+def parse_https(data):
+ https_res = [r"nxapi https port (\d+)"]
+ https_port = None
+
+ for regex in https_res:
+ match = re.search(regex, data, re.M)
+ if match:
+ https_port = int(match.group(1))
+ break
+
+ return {"https": https_port is not None, "https_port": https_port}
+
+
+def parse_sandbox(data):
+ sandbox = [item for item in data.split("\n") if re.search(r".*sandbox.*", item)]
+ value = False
+ if sandbox and sandbox[0] == "nxapi sandbox":
+ value = True
+ return {"sandbox": value}
+
+
+def parse_ssl_strong_ciphers(data):
+ ciphers_res = [r"(\w+) nxapi ssl ciphers weak"]
+ value = None
+
+ for regex in ciphers_res:
+ match = re.search(regex, data, re.M)
+ if match:
+ value = match.group(1)
+ break
+
+ return {"ssl_strong_ciphers": value == "no"}
+
+
+def parse_ssl_protocols(data):
+ tlsv1_0 = re.search(r"(?<!\S)TLSv1(?!\S)", data, re.M) is not None
+ tlsv1_1 = re.search(r"(?<!\S)TLSv1.1(?!\S)", data, re.M) is not None
+ tlsv1_2 = re.search(r"(?<!\S)TLSv1.2(?!\S)", data, re.M) is not None
+
+ return {"tlsv1_0": tlsv1_0, "tlsv1_1": tlsv1_1, "tlsv1_2": tlsv1_2}
+
+
+def map_config_to_obj(module):
+ out = run_commands(module, ["show run all | inc nxapi"], check_rc=False)[0]
+ match = re.search(r"no feature nxapi", out, re.M)
+ # There are two possible outcomes when nxapi is disabled on nxos platforms.
+ # 1. Nothing is displayed in the running config.
+ # 2. The 'no feature nxapi' command is displayed in the running config.
+ if match or out == "":
+ return {"state": "absent"}
+
+ out = str(out).strip()
+
+ obj = {"state": "present"}
+ obj.update(parse_http(out))
+ obj.update(parse_https(out))
+ obj.update(parse_sandbox(out))
+ obj.update(parse_ssl_strong_ciphers(out))
+ obj.update(parse_ssl_protocols(out))
+
+ return obj
+
+
+def map_params_to_obj(module):
+ obj = {
+ "http": module.params["http"],
+ "http_port": module.params["http_port"],
+ "https": module.params["https"],
+ "https_port": module.params["https_port"],
+ "sandbox": module.params["sandbox"],
+ "state": module.params["state"],
+ "ssl_strong_ciphers": module.params["ssl_strong_ciphers"],
+ "tlsv1_0": module.params["tlsv1_0"],
+ "tlsv1_1": module.params["tlsv1_1"],
+ "tlsv1_2": module.params["tlsv1_2"],
+ }
+
+ return obj
+
+
+def main():
+ """main entry point for module execution"""
+ argument_spec = dict(
+ http=dict(aliases=["enable_http"], type="bool", default=True),
+ http_port=dict(type="int", default=80),
+ https=dict(aliases=["enable_https"], type="bool", default=False),
+ https_port=dict(type="int", default=443),
+ sandbox=dict(aliases=["enable_sandbox"], type="bool"),
+ state=dict(default="present", choices=["present", "absent"]),
+ ssl_strong_ciphers=dict(type="bool", default=False),
+ tlsv1_0=dict(type="bool", default=True),
+ tlsv1_1=dict(type="bool", default=False),
+ tlsv1_2=dict(type="bool", default=False),
+ )
+
+ module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)
+
+ warnings = list()
+ warning_msg = "Module nxos_nxapi currently defaults to configure 'http port 80'. "
+ warning_msg += "Default behavior is changing to configure 'https port 443'"
+ warning_msg += " when params 'http, http_port, https, https_port' are not set in the playbook"
+ module.deprecate(msg=warning_msg, date="2022-06-01", collection_name="cisco.nxos")
+
+ capabilities = get_capabilities(module)
+
+ check_args(module, warnings, capabilities)
+
+ want = map_params_to_obj(module)
+ have = map_config_to_obj(module)
+
+ commands = map_obj_to_commands(want, have, module, warnings, capabilities)
+
+ result = {"changed": False, "warnings": warnings, "commands": commands}
+
+ if commands:
+ if not module.check_mode:
+ load_config(module, commands)
+ result["changed"] = True
+
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_ospf_interfaces.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_ospf_interfaces.py
new file mode 100644
index 00000000..c0a754c7
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_ospf_interfaces.py
@@ -0,0 +1,1455 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# Copyright 2020 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+"""
+The module file for nxos_ospf_interfaces
+"""
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+DOCUMENTATION = """
+module: nxos_ospf_interfaces
+version_added: 1.3.0
+short_description: OSPF Interfaces Resource Module.
+description:
+- This module manages OSPF(v2/v3) configuration of interfaces on devices running Cisco NX-OS.
+notes:
+- Unsupported for Cisco MDS
+author: Nilashish Chakraborty (@NilashishC)
+options:
+ running_config:
+ description:
+ - This option is used only with state I(parsed).
+ - The value of this option should be the output received from the NX-OS device
+ by executing the command B(show running-config | section "^interface").
+ - The state I(parsed) reads the configuration from C(running_config) option and
+ transforms it into Ansible structured data as per the resource module's argspec
+ and the value is then returned in the I(parsed) key within the result.
+ type: str
+ config:
+ description: A list of OSPF configuration for interfaces.
+ type: list
+ elements: dict
+ suboptions:
+ name:
+ description:
+ - Name/Identifier of the interface.
+ type: str
+ required: True
+ address_family:
+ description:
+ - OSPF settings on the interfaces in address-family context.
+ type: list
+ elements: dict
+ suboptions:
+ afi:
+ description:
+ - Address Family Identifier (AFI) for OSPF settings on the interfaces.
+ type: str
+ choices: ['ipv4', 'ipv6']
+ required: True
+ processes:
+ description:
+ - Interfaces configuration for an OSPF process.
+ type: list
+ elements: dict
+ suboptions:
+ process_id:
+ description:
+ - OSPF process tag.
+ type: str
+ required: True
+ area:
+ description:
+ - Area associated with interface.
+ type: dict
+ suboptions:
+ area_id:
+ description:
+ - Area ID in IP address format.
+ type: str
+ required: True
+ secondaries:
+ description:
+ - Do not include secondary IPv4/IPv6 addresses.
+ type: bool
+ multi_areas:
+ description:
+ - Multi-Areas associated with interface.
+ - Valid values are Area Ids as an integer or IP address.
+ type: list
+ elements: str
+ multi_areas:
+ description:
+ - Multi-Areas associated with interface (not tied to OSPF process).
+ - Valid values are Area Ids as an integer or IP address.
+ type: list
+ elements: str
+ authentication:
+ description:
+ - Authentication settings on the interface.
+ type: dict
+ suboptions:
+ key_chain:
+ description:
+ - Authentication password key-chain.
+ type: str
+ message_digest:
+ description:
+ - Use message-digest authentication.
+ type: bool
+ enable:
+ description:
+ - Enable/disable authentication on the interface.
+ type: bool
+ null_auth:
+ description:
+ - Use null(disable) authentication.
+ type: bool
+ authentication_key:
+ description:
+ - Configure the authentication key for the interface.
+ type: dict
+ suboptions:
+ encryption:
+ description:
+ - 0 Specifies an UNENCRYPTED authentication key will follow.
+ - 3 Specifies an 3DES ENCRYPTED authentication key will follow.
+ - 7 Specifies a Cisco type 7 ENCRYPTED authentication key will follow.
+ type: int
+ key:
+ description:
+ - Authentication key.
+ - Valid values are Cisco type 7 ENCRYPTED password, 3DES ENCRYPTED password
+ and UNENCRYPTED (cleartext) password based on the value of encryption key.
+ type: str
+ required: True
+ message_digest_key:
+ description:
+ - Message digest authentication password (key) settings.
+ type: dict
+ suboptions:
+ key_id:
+ description:
+ - Key ID.
+ type: int
+ required: True
+ encryption:
+ description:
+ - 0 Specifies an UNENCRYPTED ospf password (key) will follow.
+ - 3 Specifies an 3DES ENCRYPTED ospf password (key) will follow.
+ - 7 Specifies a Cisco type 7 ENCRYPTED the ospf password (key) will follow.
+ type: int
+ key:
+ description:
+ - Authentication key.
+ - Valid values are Cisco type 7 ENCRYPTED password, 3DES ENCRYPTED password
+ and UNENCRYPTED (cleartext) password based on the value of encryption key.
+ type: str
+ required: True
+ cost:
+ description:
+ - Cost associated with interface.
+ type: int
+ dead_interval:
+ description:
+ - Dead interval value (in seconds).
+ type: int
+ hello_interval:
+ description:
+ - Hello interval value (in seconds).
+ type: int
+ instance:
+ description:
+ - Instance identifier.
+ type: int
+ mtu_ignore:
+ description:
+ - Enable/disable OSPF MTU mismatch detection.
+ type: bool
+ network:
+ description:
+ - Network type.
+ type: str
+ choices: ["broadcast", "point-to-point"]
+ passive_interface:
+ description:
+ - Suppress routing updates on the interface.
+ - This option is mutually exclusive with I(default_passive_interface).
+ type: bool
+ default_passive_interface:
+ description:
+ - Set passive-interface attribute on this interface to default.
+ - This option is mutually exclusive with I(passive_interface).
+ type: bool
+ priority:
+ description:
+ - Router priority.
+ type: int
+ retransmit_interval:
+ description:
+ - Packet retransmission interval.
+ type: int
+ shutdown:
+ description:
+ - Shutdown OSPF on this interface.
+ type: bool
+ transmit_delay:
+ description:
+ - Packet transmission delay.
+ type: int
+ state:
+ description:
+ - The state the configuration should be left in.
+ type: str
+ choices:
+ - merged
+ - replaced
+ - overridden
+ - deleted
+ - gathered
+ - parsed
+ - rendered
+ default: merged
+"""
+EXAMPLES = """
+# Using merged
+
+# Before state:
+# -------------
+# NXOS# show running-config | section ^interface
+# interface Ethernet1/1
+# no switchport
+# interface Ethernet1/2
+# no switchport
+# interface Ethernet1/3
+# no switchport
+
+- name: Merge the provided configuration with the existing running configuration
+ cisco.nxos.nxos_ospf_interfaces:
+ config:
+ - name: Ethernet1/1
+ address_family:
+ - afi: ipv4
+ processes:
+ - process_id: "100"
+ area:
+ area_id: 1.1.1.1
+ secondaries: False
+ multi_areas:
+ - 11.11.11.11
+ - afi: ipv6
+ processes:
+ - process_id: "200"
+ area:
+ area_id: 2.2.2.2
+ multi_areas:
+ - 21.0.0.0
+ - process_id: "300"
+ multi_areas:
+ - 50.50.50.50
+ multi_areas:
+ - 16.10.10.10
+ - name: Ethernet1/2
+ address_family:
+ - afi: ipv4
+ authentication:
+ enable: True
+ key_chain: test-1
+ message_digest_key:
+ key_id: 10
+ encryption: 3
+ key: abc01d272be25d29
+ cost: 100
+ - afi: ipv6
+ network: broadcast
+ shutdown: True
+ - name: Ethernet1/3
+ address_family:
+ - afi: ipv4
+ authentication_key:
+ encryption: 7
+ key: 12090404011C03162E
+ state: merged
+
+# Task output
+# -------------
+# "before": [
+# {
+# "name": "Ethernet1/1"
+# },
+# {
+# "name": "Ethernet1/2"
+# },
+# {
+# "name": "Ethernet1/3"
+# },
+# ]
+#
+# "commands": [
+# "interface Ethernet1/1",
+# "ip router ospf multi-area 11.11.11.11",
+# "ip router ospf 100 area 1.1.1.1 secondaries none",
+# "ipv6 router ospfv3 multi-area 16.10.10.10",
+# "ipv6 router ospfv3 200 area 2.2.2.2",
+# "ipv6 router ospfv3 200 multi-area 21.0.0.0",
+# "ipv6 router ospfv3 300 multi-area 50.50.50.50",
+# "interface Ethernet1/2",
+# "ip ospf authentication key-chain test-1",
+# "ip ospf authentication",
+# "ip ospf message-digest-key 10 md5 3 abc01d272be25d29",
+# "ip ospf cost 100",
+# "ospfv3 network broadcast",
+# "ospfv3 shutdown",
+# "interface Ethernet1/3",
+# "ip ospf authentication-key 7 12090404011C03162E"
+# ]
+#
+# "after": [
+# {
+# "address_family": [
+# {
+# "afi": "ipv4",
+# "multi_areas": [
+# "11.11.11.11"
+# ],
+# "processes": [
+# {
+# "area": {
+# "area_id": "1.1.1.1",
+# "secondaries": false
+# },
+# "process_id": "100"
+# }
+# ]
+# },
+# {
+# "afi": "ipv6",
+# "multi_areas": [
+# "16.10.10.10"
+# ],
+# "processes": [
+# {
+# "area": {
+# "area_id": "2.2.2.2"
+# },
+# "multi_areas": [
+# "21.0.0.0"
+# ],
+# "process_id": "200"
+# },
+# {
+# "multi_areas": [
+# "50.50.50.50"
+# ],
+# "process_id": "300"
+# }
+# ]
+# }
+# ],
+# "name": "Ethernet1/1"
+# },
+# {
+# "address_family": [
+# {
+# "afi": "ipv4",
+# "authentication": {
+# "enable": true,
+# "key_chain": "test-1"
+# },
+# "cost": 100,
+# "message_digest_key": {
+# "encryption": 3,
+# "key": "abc01d272be25d29",
+# "key_id": 10
+# }
+# },
+# {
+# "afi": "ipv6",
+# "network": "broadcast",
+# "shutdown": true
+# }
+# ],
+# "name": "Ethernet1/2"
+# },
+# {
+# "address_family": [
+# {
+# "afi": "ipv4",
+# "authentication_key": {
+# "encryption": 7,
+# "key": "12090404011C03162E"
+# }
+# }
+# ],
+# "name": "Ethernet1/3"
+# },
+# ]
+
+# After state:
+# -------------
+# NXOS# show running-config | section ^interface
+# interface Ethernet1/1
+# no switchport
+# ip router ospf 100 area 1.1.1.1 secondaries none
+# ip router ospf multi-area 11.11.11.11
+# ipv6 router ospfv3 200 area 2.2.2.2
+# ipv6 router ospfv3 multi-area 16.10.10.10
+# ipv6 router ospfv3 200 multi-area 21.0.0.0
+# ipv6 router ospfv3 300 multi-area 50.50.50.50
+# interface Ethernet1/2
+# no switchport
+# ip ospf authentication
+# ip ospf authentication key-chain test-1
+# ip ospf message-digest-key 10 md5 3 abc01d272be25d29
+# ip ospf cost 100
+# ospfv3 network broadcast
+# ospfv3 shutdown
+# interface Ethernet1/3
+# no switchport
+# ip ospf authentication-key 7 12090404011C03162E
+
+
+# Using replaced
+
+# Before state:
+# ------------
+# NXOS# show running-config | section ^interface
+# interface Ethernet1/1
+# no switchport
+# ip router ospf 100 area 1.1.1.1 secondaries none
+# ip router ospf multi-area 11.11.11.11
+# ipv6 router ospfv3 200 area 2.2.2.2
+# ipv6 router ospfv3 multi-area 16.10.10.10
+# ipv6 router ospfv3 200 multi-area 21.0.0.0
+# ipv6 router ospfv3 300 multi-area 50.50.50.50
+# interface Ethernet1/2
+# no switchport
+# ip ospf authentication
+# ip ospf authentication key-chain test-1
+# ip ospf message-digest-key 10 md5 3 abc01d272be25d29
+# ip ospf cost 100
+# ospfv3 network broadcast
+# ospfv3 shutdown
+# interface Ethernet1/3
+# no switchport
+# ip ospf authentication-key 7 12090404011C03162E
+
+- name: Replace OSPF configurations of listed interfaces with provided configurations
+ cisco.nxos.nxos_ospf_interfaces:
+ config:
+ - name: Ethernet1/1
+ address_family:
+ - afi: ipv4
+ processes:
+ - process_id: "100"
+ area:
+ area_id: 1.1.1.1
+ secondaries: False
+ multi_areas:
+ - 11.11.11.12
+ - name: Ethernet1/3
+ state: replaced
+
+# Task output
+# -------------
+# "before": [
+# {
+# "address_family": [
+# {
+# "afi": "ipv4",
+# "multi_areas": [
+# "11.11.11.11"
+# ],
+# "processes": [
+# {
+# "area": {
+# "area_id": "1.1.1.1",
+# "secondaries": false
+# },
+# "process_id": "100"
+# }
+# ]
+# },
+# {
+# "afi": "ipv6",
+# "multi_areas": [
+# "16.10.10.10"
+# ],
+# "processes": [
+# {
+# "area": {
+# "area_id": "2.2.2.2"
+# },
+# "multi_areas": [
+# "21.0.0.0"
+# ],
+# "process_id": "200"
+# },
+# {
+# "multi_areas": [
+# "50.50.50.50"
+# ],
+# "process_id": "300"
+# }
+# ]
+# }
+# ],
+# "name": "Ethernet1/1"
+# },
+# {
+# "address_family": [
+# {
+# "afi": "ipv4",
+# "authentication": {
+# "enable": true,
+# "key_chain": "test-1"
+# },
+# "cost": 100,
+# "message_digest_key": {
+# "encryption": 3,
+# "key": "abc01d272be25d29",
+# "key_id": 10
+# }
+# },
+# {
+# "afi": "ipv6",
+# "network": "broadcast",
+# "shutdown": true
+# }
+# ],
+# "name": "Ethernet1/2"
+# },
+# {
+# "address_family": [
+# {
+# "afi": "ipv4",
+# "authentication_key": {
+# "encryption": 7,
+# "key": "12090404011C03162E"
+# }
+# }
+# ],
+# "name": "Ethernet1/3"
+# },
+# ]
+#
+# "commands": [
+# "interface Ethernet1/1",
+# "ip router ospf multi-area 11.11.11.12",
+# "no ip router ospf multi-area 11.11.11.11",
+# "no ipv6 router ospfv3 multi-area 16.10.10.10",
+# "no ipv6 router ospfv3 200 area 2.2.2.2",
+# "no ipv6 router ospfv3 200 multi-area 21.0.0.0",
+# "no ipv6 router ospfv3 300 multi-area 50.50.50.50",
+# "interface Ethernet1/3",
+# "no ip ospf authentication-key 7 12090404011C03162E"
+# ]
+#
+# "after": [
+# {
+# "address_family": [
+# {
+# "afi": "ipv4",
+# "multi_areas": [
+# "11.11.11.12"
+# ],
+# "processes": [
+# {
+# "area": {
+# "area_id": "1.1.1.1",
+# "secondaries": false
+# },
+# "process_id": "100"
+# }
+# ]
+# }
+# ],
+# "name": "Ethernet1/1"
+# },
+# {
+# "address_family": [
+# {
+# "afi": "ipv4",
+# "authentication": {
+# "enable": true,
+# "key_chain": "test-1"
+# },
+# "cost": 100,
+# "message_digest_key": {
+# "encryption": 3,
+# "key": "abc01d272be25d29",
+# "key_id": 10
+# }
+# },
+# {
+# "afi": "ipv6",
+# "network": "broadcast",
+# "shutdown": true
+# }
+# ],
+# "name": "Ethernet1/2"
+# },
+# {
+# "name": "Ethernet1/3"
+# },
+#
+# After state:
+# -------------
+# NXOS# show running-config | section ^interface
+# interface Ethernet1/1
+# no switchport
+# ip router ospf 100 area 1.1.1.1 secondaries none
+# ip router ospf multi-area 11.11.11.12
+# interface Ethernet1/2
+# no switchport
+# ip ospf authentication
+# ip ospf authentication key-chain test-1
+# ip ospf message-digest-key 10 md5 3 abc01d272be25d29
+# ip ospf cost 100
+# ospfv3 network broadcast
+# ospfv3 shutdown
+# interface Ethernet1/3
+# no switchport
+
+
+# Using overridden
+
+# Before state:
+# ------------
+# NXOS# show running-config | section ^interface
+# interface Ethernet1/1
+# no switchport
+# ip router ospf 100 area 1.1.1.1 secondaries none
+# ip router ospf multi-area 11.11.11.11
+# ipv6 router ospfv3 200 area 2.2.2.2
+# ipv6 router ospfv3 multi-area 16.10.10.10
+# ipv6 router ospfv3 200 multi-area 21.0.0.0
+# ipv6 router ospfv3 300 multi-area 50.50.50.50
+# interface Ethernet1/2
+# no switchport
+# ip ospf authentication
+# ip ospf authentication key-chain test-1
+# ip ospf message-digest-key 10 md5 3 abc01d272be25d29
+# ip ospf cost 100
+# ospfv3 network broadcast
+# ospfv3 shutdown
+# interface Ethernet1/3
+# no switchport
+# ip ospf authentication-key 7 12090404011C03162E
+
+- name: Override all OSPF interfaces configuration with provided configuration
+ cisco.nxos.nxos_ospf_interfaces:
+ config:
+ - name: Ethernet1/1
+ address_family:
+ - afi: ipv4
+ processes:
+ - process_id: "100"
+ area:
+ area_id: 1.1.1.1
+ secondaries: False
+ multi_areas:
+ - 11.11.11.12
+ state: overridden
+
+# Task output
+# -------------
+# "before": [
+# {
+# "address_family": [
+# {
+# "afi": "ipv4",
+# "multi_areas": [
+# "11.11.11.11"
+# ],
+# "processes": [
+# {
+# "area": {
+# "area_id": "1.1.1.1",
+# "secondaries": false
+# },
+# "process_id": "100"
+# }
+# ]
+# },
+# {
+# "afi": "ipv6",
+# "multi_areas": [
+# "16.10.10.10"
+# ],
+# "processes": [
+# {
+# "area": {
+# "area_id": "2.2.2.2"
+# },
+# "multi_areas": [
+# "21.0.0.0"
+# ],
+# "process_id": "200"
+# },
+# {
+# "multi_areas": [
+# "50.50.50.50"
+# ],
+# "process_id": "300"
+# }
+# ]
+# }
+# ],
+# "name": "Ethernet1/1"
+# },
+# {
+# "address_family": [
+# {
+# "afi": "ipv4",
+# "authentication": {
+# "enable": true,
+# "key_chain": "test-1"
+# },
+# "cost": 100,
+# "message_digest_key": {
+# "encryption": 3,
+# "key": "abc01d272be25d29",
+# "key_id": 10
+# }
+# },
+# {
+# "afi": "ipv6",
+# "network": "broadcast",
+# "shutdown": true
+# }
+# ],
+# "name": "Ethernet1/2"
+# },
+# {
+# "address_family": [
+# {
+# "afi": "ipv4",
+# "authentication_key": {
+# "encryption": 7,
+# "key": "12090404011C03162E"
+# }
+# }
+# ],
+# "name": "Ethernet1/3"
+# },
+# ]
+#
+# "commands": [
+# "interface Ethernet1/2",
+# "no ip ospf authentication key-chain test-1",
+# "no ip ospf authentication",
+# "no ip ospf message-digest-key 10 md5 3 abc01d272be25d29",
+# "no ip ospf cost 100",
+# "no ospfv3 network broadcast",
+# "no ospfv3 shutdown",
+# "interface Ethernet1/3",
+# "no ip ospf authentication-key 7 12090404011C03162E",
+# "interface Ethernet1/1",
+# "ip router ospf multi-area 11.11.11.12",
+# "no ip router ospf multi-area 11.11.11.11",
+# "no ipv6 router ospfv3 multi-area 16.10.10.10",
+# "no ipv6 router ospfv3 200 area 2.2.2.2",
+# "no ipv6 router ospfv3 200 multi-area 21.0.0.0",
+# "no ipv6 router ospfv3 300 multi-area 50.50.50.50"
+# ]
+#
+# "after": [
+# {
+# "address_family": [
+# {
+# "afi": "ipv4",
+# "multi_areas": [
+# "11.11.11.12"
+# ],
+# "processes": [
+# {
+# "area": {
+# "area_id": "1.1.1.1",
+# "secondaries": false
+# },
+# "process_id": "100"
+# }
+# ]
+# }
+# ],
+# "name": "Ethernet1/1"
+# },
+# {
+# "name": "Ethernet1/2"
+# },
+# {
+# "name": "Ethernet1/3"
+# },
+# ]
+
+# After state:
+# -------------
+# NXOS# show running-config | section ^interface
+# interface Ethernet1/1
+# no switchport
+# ip router ospf 100 area 1.1.1.1 secondaries none
+# ip router ospf multi-area 11.11.11.12
+# interface Ethernet1/2
+# no switchport
+# interface Ethernet1/3
+# no switchport
+
+# Using deleted to delete OSPF config of a single interface
+
+# Before state:
+# ------------
+# NXOS# show running-config | section ^interface
+# interface Ethernet1/1
+# no switchport
+# ip router ospf 100 area 1.1.1.1 secondaries none
+# ip router ospf multi-area 11.11.11.11
+# ipv6 router ospfv3 200 area 2.2.2.2
+# ipv6 router ospfv3 multi-area 16.10.10.10
+# ipv6 router ospfv3 200 multi-area 21.0.0.0
+# ipv6 router ospfv3 300 multi-area 50.50.50.50
+# interface Ethernet1/2
+# no switchport
+# ip ospf authentication
+# ip ospf authentication key-chain test-1
+# ip ospf message-digest-key 10 md5 3 abc01d272be25d29
+# ip ospf cost 100
+# ospfv3 network broadcast
+# ospfv3 shutdown
+# interface Ethernet1/3
+# no switchport
+# ip ospf authentication-key 7 12090404011C03162E
+
+- name: Delete OSPF config from a single interface
+ cisco.nxos.nxos_ospf_interfaces:
+ config:
+ - name: Ethernet1/1
+ state: deleted
+
+# Task output
+# -------------
+# "before": [
+# {
+# "address_family": [
+# {
+# "afi": "ipv4",
+# "multi_areas": [
+# "11.11.11.11"
+# ],
+# "processes": [
+# {
+# "area": {
+# "area_id": "1.1.1.1",
+# "secondaries": false
+# },
+# "process_id": "100"
+# }
+# ]
+# },
+# {
+# "afi": "ipv6",
+# "multi_areas": [
+# "16.10.10.10"
+# ],
+# "processes": [
+# {
+# "area": {
+# "area_id": "2.2.2.2"
+# },
+# "multi_areas": [
+# "21.0.0.0"
+# ],
+# "process_id": "200"
+# },
+# {
+# "multi_areas": [
+# "50.50.50.50"
+# ],
+# "process_id": "300"
+# }
+# ]
+# }
+# ],
+# "name": "Ethernet1/1"
+# },
+# {
+# "address_family": [
+# {
+# "afi": "ipv4",
+# "authentication": {
+# "enable": true,
+# "key_chain": "test-1"
+# },
+# "cost": 100,
+# "message_digest_key": {
+# "encryption": 3,
+# "key": "abc01d272be25d29",
+# "key_id": 10
+# }
+# },
+# {
+# "afi": "ipv6",
+# "network": "broadcast",
+# "shutdown": true
+# }
+# ],
+# "name": "Ethernet1/2"
+# },
+# {
+# "address_family": [
+# {
+# "afi": "ipv4",
+# "authentication_key": {
+# "encryption": 7,
+# "key": "12090404011C03162E"
+# }
+# }
+# ],
+# "name": "Ethernet1/3"
+# },
+# ]
+#
+# "commands": [
+# "interface Ethernet1/1",
+# "no ip router ospf multi-area 11.11.11.11",
+# "no ip router ospf 100 area 1.1.1.1 secondaries none",
+# "no ipv6 router ospfv3 multi-area 16.10.10.10",
+# "no ipv6 router ospfv3 200 area 2.2.2.2",
+# "no ipv6 router ospfv3 200 multi-area 21.0.0.0",
+# "no ipv6 router ospfv3 300 multi-area 50.50.50.50"
+# ]
+#
+# "before": [
+# {
+# "name": "Ethernet1/1"
+# },
+# {
+# "address_family": [
+# {
+# "afi": "ipv4",
+# "authentication": {
+# "enable": true,
+# "key_chain": "test-1"
+# },
+# "cost": 100,
+# "message_digest_key": {
+# "encryption": 3,
+# "key": "abc01d272be25d29",
+# "key_id": 10
+# }
+# },
+# {
+# "afi": "ipv6",
+# "network": "broadcast",
+# "shutdown": true
+# }
+# ],
+# "name": "Ethernet1/2"
+# },
+# {
+# "address_family": [
+# {
+# "afi": "ipv4",
+# "authentication_key": {
+# "encryption": 7,
+# "key": "12090404011C03162E"
+# }
+# }
+# ],
+# "name": "Ethernet1/3"
+# },
+# ]
+
+# After state:
+# ------------
+# NXOS# show running-config | section ^interface
+# interface Ethernet1/1
+# no switchport
+# interface Ethernet1/2
+# no switchport
+# ip ospf authentication
+# ip ospf authentication key-chain test-1
+# ip ospf message-digest-key 10 md5 3 abc01d272be25d29
+# ip ospf cost 100
+# ospfv3 network broadcast
+# ospfv3 shutdown
+# interface Ethernet1/3
+# no switchport
+# ip ospf authentication-key 7 12090404011C03162E
+
+# Using deleted to delete OSPF config from all interfaces
+
+# Before state:
+# ------------
+# NXOS# show running-config | section ^interface
+# interface Ethernet1/1
+# no switchport
+# ip router ospf 100 area 1.1.1.1 secondaries none
+# ip router ospf multi-area 11.11.11.11
+# ipv6 router ospfv3 200 area 2.2.2.2
+# ipv6 router ospfv3 multi-area 16.10.10.10
+# ipv6 router ospfv3 200 multi-area 21.0.0.0
+# ipv6 router ospfv3 300 multi-area 50.50.50.50
+# interface Ethernet1/2
+# no switchport
+# ip ospf authentication
+# ip ospf authentication key-chain test-1
+# ip ospf message-digest-key 10 md5 3 abc01d272be25d29
+# ip ospf cost 100
+# ospfv3 network broadcast
+# ospfv3 shutdown
+# interface Ethernet1/3
+# no switchport
+# ip ospf authentication-key 7 12090404011C03162E
+
+- name: Delete OSPF config from all interfaces
+ cisco.nxos.nxos_ospf_interfaces:
+ state: deleted
+
+# Task output
+# -------------
+# "before": [
+# {
+# "address_family": [
+# {
+# "afi": "ipv4",
+# "multi_areas": [
+# "11.11.11.11"
+# ],
+# "processes": [
+# {
+# "area": {
+# "area_id": "1.1.1.1",
+# "secondaries": false
+# },
+# "process_id": "100"
+# }
+# ]
+# },
+# {
+# "afi": "ipv6",
+# "multi_areas": [
+# "16.10.10.10"
+# ],
+# "processes": [
+# {
+# "area": {
+# "area_id": "2.2.2.2"
+# },
+# "multi_areas": [
+# "21.0.0.0"
+# ],
+# "process_id": "200"
+# },
+# {
+# "multi_areas": [
+# "50.50.50.50"
+# ],
+# "process_id": "300"
+# }
+# ]
+# }
+# ],
+# "name": "Ethernet1/1"
+# },
+# {
+# "address_family": [
+# {
+# "afi": "ipv4",
+# "authentication": {
+# "enable": true,
+# "key_chain": "test-1"
+# },
+# "cost": 100,
+# "message_digest_key": {
+# "encryption": 3,
+# "key": "abc01d272be25d29",
+# "key_id": 10
+# }
+# },
+# {
+# "afi": "ipv6",
+# "network": "broadcast",
+# "shutdown": true
+# }
+# ],
+# "name": "Ethernet1/2"
+# },
+# {
+# "address_family": [
+# {
+# "afi": "ipv4",
+# "authentication_key": {
+# "encryption": 7,
+# "key": "12090404011C03162E"
+# }
+# }
+# ],
+# "name": "Ethernet1/3"
+# },
+# ]
+#
+# "commands": [
+# "interface Ethernet1/1",
+# "no ip router ospf multi-area 11.11.11.11",
+# "no ip router ospf 100 area 1.1.1.1 secondaries none",
+# "no ipv6 router ospfv3 multi-area 16.10.10.10",
+# "no ipv6 router ospfv3 200 area 2.2.2.2",
+# "no ipv6 router ospfv3 200 multi-area 21.0.0.0",
+# "no ipv6 router ospfv3 300 multi-area 50.50.50.50",
+# "interface Ethernet1/2",
+# "no ip ospf authentication key-chain test-1",
+# "no ip ospf authentication",
+# "no ip ospf message-digest-key 10 md5 3 abc01d272be25d29",
+# "no ip ospf cost 100",
+# "no ospfv3 network broadcast",
+# "no ospfv3 shutdown",
+# "interface Ethernet1/3",
+# "no ip ospf authentication-key 7 12090404011C03162E"
+# ]
+#
+# "after": [
+# {
+# "name": "Ethernet1/1"
+# },
+# {
+# "name": "Ethernet1/2"
+# },
+# {
+# "name": "Ethernet1/3"
+# },
+# ]
+
+# After state:
+# ------------
+# NXOS# show running-config | section ^interface
+# interface Ethernet1/1
+# no switchport
+# interface Ethernet1/2
+# no switchport
+# interface Ethernet1/3
+# no switchport
+
+# Using rendered
+
+- name: Render platform specific configuration lines with state rendered (without connecting to the device)
+ cisco.nxos.nxos_ospf_interfaces:
+ config:
+ - name: Ethernet1/1
+ address_family:
+ - afi: ipv4
+ processes:
+ - process_id: "100"
+ area:
+ area_id: 1.1.1.1
+ secondaries: False
+ multi_areas:
+ - 11.11.11.11
+ - afi: ipv6
+ processes:
+ - process_id: "200"
+ area:
+ area_id: 2.2.2.2
+ multi_areas:
+ - 21.0.0.0
+ - process_id: "300"
+ multi_areas:
+ - 50.50.50.50
+ multi_areas:
+ - 16.10.10.10
+ - name: Ethernet1/2
+ address_family:
+ - afi: ipv4
+ authentication:
+ enable: True
+ key_chain: test-1
+ message_digest_key:
+ key_id: 10
+ encryption: 3
+ key: abc01d272be25d29
+ cost: 100
+ - afi: ipv6
+ network: broadcast
+ shutdown: True
+ - name: Ethernet1/3
+ address_family:
+ - afi: ipv4
+ authentication_key:
+ encryption: 7
+ key: 12090404011C03162E
+ state: rendered
+
+# Task Output (redacted)
+# -----------------------
+# "rendered": [
+# "interface Ethernet1/1",
+# "ip router ospf multi-area 11.11.11.11",
+# "ip router ospf 100 area 1.1.1.1 secondaries none",
+# "ipv6 router ospfv3 multi-area 16.10.10.10",
+# "ipv6 router ospfv3 200 area 2.2.2.2",
+# "ipv6 router ospfv3 200 multi-area 21.0.0.0",
+# "ipv6 router ospfv3 300 multi-area 50.50.50.50",
+# "interface Ethernet1/2",
+# "ip ospf authentication key-chain test-1",
+# "ip ospf authentication",
+# "ip ospf message-digest-key 10 md5 3 abc01d272be25d29",
+# "ip ospf cost 100",
+# "ospfv3 network broadcast",
+# "ospfv3 shutdown",
+# "interface Ethernet1/3",
+# "ip ospf authentication-key 7 12090404011C03162E"
+# ]
+
+# Using parsed
+
+# parsed.cfg
+# ------------
+# interface Ethernet1/1
+# ip router ospf 100 area 1.1.1.1 secondaries none
+# ip router ospf multi-area 11.11.11.11
+# ipv6 router ospfv3 200 area 2.2.2.2
+# ipv6 router ospfv3 200 multi-area 21.0.0.0
+# ipv6 router ospfv3 300 multi-area 50.50.50.50
+# ipv6 router ospfv3 multi-area 16.10.10.10
+# interface Ethernet1/2
+# ip ospf authentication
+# ip ospf authentication key-chain test-1
+# ip ospf message-digest-key 10 md5 3 abc01d272be25d29
+# ip ospf cost 100
+# ospfv3 network broadcast
+# ospfv3 shutdown
+# interface Ethernet1/3
+# ip ospf authentication-key 7 12090404011C03162E
+
+- name: arse externally provided OSPF interfaces config
+ cisco.nxos.nxos_ospf_interfaces:
+ running_config: "{{ lookup('file', 'ospf_interfaces.cfg') }}"
+ state: parsed
+
+# Task output (redacted)
+# -----------------------
+# "parsed": [
+# {
+# "address_family": [
+# {
+# "afi": "ipv4",
+# "multi_areas": [
+# "11.11.11.11"
+# ],
+# "processes": [
+# {
+# "area": {
+# "area_id": "1.1.1.1",
+# "secondaries": false
+# },
+# "process_id": "100"
+# }
+# ]
+# },
+# {
+# "afi": "ipv6",
+# "multi_areas": [
+# "16.10.10.10"
+# ],
+# "processes": [
+# {
+# "area": {
+# "area_id": "2.2.2.2"
+# },
+# "multi_areas": [
+# "21.0.0.0"
+# ],
+# "process_id": "200"
+# },
+# {
+# "multi_areas": [
+# "50.50.50.50"
+# ],
+# "process_id": "300"
+# }
+# ]
+# }
+# ],
+# "name": "Ethernet1/1"
+# },
+# {
+# "address_family": [
+# {
+# "afi": "ipv4",
+# "authentication": {
+# "enable": true,
+# "key_chain": "test-1"
+# },
+# "cost": 100,
+# "message_digest_key": {
+# "encryption": 3,
+# "key": "abc01d272be25d29",
+# "key_id": 10
+# }
+# },
+# {
+# "afi": "ipv6",
+# "network": "broadcast",
+# "shutdown": true
+# }
+# ],
+# "name": "Ethernet1/2"
+# },
+# {
+# "address_family": [
+# {
+# "afi": "ipv4",
+# "authentication_key": {
+# "encryption": 7,
+# "key": "12090404011C03162E"
+# }
+# }
+# ],
+# "name": "Ethernet1/3"
+# },
+# ]
+
+# Using gathered
+
+# On-box config
+
+# NXOS# show running-config | section ^interface
+# interface Ethernet1/1
+# no switchport
+# ip router ospf 100 area 1.1.1.1 secondaries none
+# ip router ospf multi-area 11.11.11.12
+# interface Ethernet1/2
+# no switchport
+# ip ospf authentication
+# ip ospf authentication key-chain test-1
+# ip ospf message-digest-key 10 md5 3 abc01d272be25d29
+# ip ospf cost 100
+# ospfv3 network broadcast
+# ospfv3 shutdown
+# interface Ethernet1/3
+# no switchport
+
+# Task output (redacted)
+# -----------------------
+# "gathered": [
+# {
+# "address_family": [
+# {
+# "afi": "ipv4",
+# "multi_areas": [
+# "11.11.11.12"
+# ],
+# "processes": [
+# {
+# "area": {
+# "area_id": "1.1.1.1",
+# "secondaries": false
+# },
+# "process_id": "100"
+# }
+# ]
+# }
+# ],
+# "name": "Ethernet1/1"
+# },
+# {
+# "address_family": [
+# {
+# "afi": "ipv4",
+# "authentication": {
+# "enable": true,
+# "key_chain": "test-1"
+# },
+# "cost": 100,
+# "message_digest_key": {
+# "encryption": 3,
+# "key": "abc01d272be25d29",
+# "key_id": 10
+# }
+# },
+# {
+# "afi": "ipv6",
+# "network": "broadcast",
+# "shutdown": true
+# }
+# ],
+# "name": "Ethernet1/2"
+# },
+# {
+# "name": "Ethernet1/3"
+# },
+"""
+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:
+ - interface Ethernet1/1
+ - ip router ospf multi-area 11.11.11.11
+ - ip router ospf 100 area 1.1.1.1 secondaries none
+ - no ipv6 router ospfv3 multi-area 16.10.10.10
+ - ipv6 router ospfv3 200 area 2.2.2.2
+ - ipv6 router ospfv3 200 multi-area 21.0.0.0
+ - ipv6 router ospfv3 300 multi-area 50.50.50.50
+ - interface Ethernet1/2
+ - no ip ospf authentication key-chain test-1
+ - ip ospf authentication
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.ospf_interfaces.ospf_interfaces import (
+ Ospf_interfacesArgs,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.config.ospf_interfaces.ospf_interfaces import (
+ Ospf_interfaces,
+)
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ module = AnsibleModule(
+ argument_spec=Ospf_interfacesArgs.argument_spec,
+ mutually_exclusive=[["config", "running_config"]],
+ required_if=[
+ ["state", "merged", ["config"]],
+ ["state", "replaced", ["config"]],
+ ["state", "overridden", ["config"]],
+ ["state", "rendered", ["config"]],
+ ["state", "parsed", ["running_config"]],
+ ],
+ supports_check_mode=True,
+ )
+
+ result = Ospf_interfaces(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_ospfv2.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_ospfv2.py
new file mode 100644
index 00000000..174640a1
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_ospfv2.py
@@ -0,0 +1,1984 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# Copyright 2020 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 nxos_ospfv2
+"""
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_ospfv2
+short_description: OSPFv2 resource module
+description:
+- This module manages OSPFv2 configuration on devices running Cisco NX-OS.
+version_added: 1.0.0
+notes:
+- Tested against NX-OS 7.0(3)I5(1).
+- Unsupported for Cisco MDS
+- This module works with connection C(network_cli) and C(httpapi).
+author: Nilashish Chakraborty (@NilashishC)
+options:
+ running_config:
+ description:
+ - This option is used only with state I(parsed).
+ - The value of this option should be the output received from the NX-OS device
+ by executing the command B(show running-config | section "^router ospf .*").
+ - The state I(parsed) reads the configuration from C(running_config) option and
+ transforms it into Ansible structured data as per the resource module's argspec
+ and the value is then returned in the I(parsed) key within the result.
+ type: str
+ config:
+ description: A list of OSPF process configuration.
+ type: dict
+ suboptions:
+ processes:
+ description:
+ - A list of OSPF instances' configurations.
+ type: list
+ elements: dict
+ suboptions:
+ areas:
+ description:
+ - Configure properties of OSPF Areas.
+ type: list
+ elements: dict
+ suboptions:
+ area_id:
+ description:
+ - The Area ID in IP Address format.
+ type: str
+ required: true
+ authentication:
+ description:
+ - Authentication settings for the Area.
+ type: dict
+ suboptions:
+ set:
+ description:
+ - Set authentication for the area.
+ type: bool
+ message_digest:
+ description:
+ - Use message-digest authentication.
+ type: bool
+ default_cost:
+ description:
+ - Specify the default cost for default summary LSA.
+ type: int
+ filter_list:
+ description:
+ - Filter prefixes between OSPF areas.
+ type: list
+ elements: dict
+ suboptions:
+ route_map:
+ description:
+ - The Route-map name.
+ type: str
+ required: true
+ direction:
+ description:
+ - The direction to apply the route map.
+ type: str
+ choices: [in, out]
+ required: true
+ nssa:
+ description:
+ - NSSA settings for the area.
+ type: dict
+ suboptions:
+ set:
+ description:
+ - Configure area as NSSA.
+ type: bool
+ default_information_originate:
+ description:
+ - Originate Type-7 default LSA into NSSA area.
+ type: bool
+ no_redistribution:
+ description:
+ - Do not send redistributed LSAs into NSSA area.
+ type: bool
+ no_summary:
+ description:
+ - Do not send summary LSAs into NSSA area.
+ type: bool
+ translate:
+ description:
+ - Translate LSA.
+ type: dict
+ suboptions:
+ type7:
+ description:
+ - Translate from Type 7 to Type 5.
+ type: dict
+ suboptions:
+ always:
+ description:
+ - Always translate LSAs
+ type: bool
+ never:
+ description:
+ - Never translate LSAs
+ type: bool
+ supress_fa:
+ description:
+ - Suppress forwarding address in translated LSAs.
+ type: bool
+ ranges:
+ description:
+ - Configure an address range for the area.
+ type: list
+ elements: dict
+ suboptions:
+ prefix:
+ description:
+ - IP in Prefix format (x.x.x.x/len)
+ type: str
+ required: true
+ cost:
+ description:
+ - Cost to use for the range.
+ type: int
+ not_advertise:
+ description:
+ - Suppress advertising the specified range.
+ type: bool
+ stub:
+ description:
+ - Settings for configuring the area as a stub.
+ type: dict
+ suboptions:
+ set:
+ description:
+ - Configure the area as a stub.
+ type: bool
+ no_summary:
+ description:
+ - Prevent ABR from sending summary LSAs into stub area.
+ type: bool
+ auto_cost:
+ description:
+ - Calculate OSPF cost according to bandwidth.
+ type: dict
+ suboptions:
+ reference_bandwidth:
+ description:
+ - Reference bandwidth used to assign OSPF cost.
+ type: int
+ required: true
+ unit:
+ description:
+ - Specify in which unit the reference bandwidth is specified.
+ type: str
+ required: true
+ choices: [Gbps, Mbps]
+ bfd:
+ description:
+ - Enable BFD on all OSPF interfaces.
+ type: bool
+ default_information:
+ description:
+ - Control distribution of default routes.
+ type: dict
+ suboptions:
+ originate:
+ description:
+ - Distribute a default route.
+ type: dict
+ suboptions:
+ set:
+ description:
+ - Enable distribution of default route.
+ type: bool
+ always:
+ description:
+ - Always advertise a default route.
+ type: bool
+ route_map:
+ description:
+ - Policy to control distribution of default routes
+ type: str
+ default_metric:
+ description:
+ - Specify default metric for redistributed routes.
+ type: int
+ distance:
+ description:
+ - Configure the OSPF administrative distance.
+ type: int
+ flush_routes:
+ description:
+ - Flush routes on a non-graceful controlled restart.
+ type: bool
+ graceful_restart:
+ description:
+ - Configure graceful restart.
+ type: dict
+ suboptions:
+ set:
+ description:
+ - Enable graceful-restart.
+ type: bool
+ grace_period:
+ description:
+ - Configure maximum interval to restart gracefully.
+ type: int
+ helper_disable:
+ description:
+ - Enable/Disable helper mode.
+ type: bool
+ isolate:
+ description:
+ - Isolate this router from OSPF perspective.
+ type: bool
+ log_adjacency_changes:
+ description:
+ - Log changes in adjacency state.
+ type: dict
+ suboptions:
+ log:
+ description:
+ - Enable/disable logging changes in adjacency state.
+ type: bool
+ detail:
+ description:
+ - Notify all state changes.
+ type: bool
+ max_lsa:
+ description:
+ - Feature to limit the number of non-self-originated LSAs.
+ type: dict
+ suboptions:
+ max_non_self_generated_lsa:
+ description:
+ - Set the maximum number of non self-generated LSAs.
+ type: int
+ required: true
+ threshold:
+ description:
+ - Threshold value (%) at which to generate a warning message.
+ type: int
+ ignore_count:
+ description:
+ - Set count on how many times adjacencies can be suppressed.
+ type: int
+ ignore_time:
+ description:
+ - Set time during which all adjacencies are suppressed.
+ type: int
+ reset_time:
+ description:
+ - Set number of minutes after which ignore-count is reset to zero.
+ type: int
+ warning_only:
+ description:
+ - Log a warning message when limit is exceeded.
+ type: bool
+ max_metric:
+ description:
+ - Maximize the cost metric.
+ type: dict
+ suboptions:
+ router_lsa:
+ description:
+ - Router LSA configuration.
+ type: dict
+ suboptions:
+ set:
+ description:
+ - Set router-lsa attribute.
+ type: bool
+ external_lsa:
+ description:
+ - External LSA configuration.
+ type: dict
+ suboptions:
+ set:
+ description:
+ - Set external-lsa attribute.
+ type: bool
+ max_metric_value:
+ description:
+ - Set max metric value for external LSAs.
+ type: int
+ include_stub:
+ description:
+ - Advertise Max metric for Stub links as well.
+ type: bool
+ on_startup:
+ description:
+ - Effective only at startup.
+ type: dict
+ suboptions:
+ set:
+ description:
+ - Set on-startup attribute.
+ type: bool
+ wait_period:
+ description:
+ - Wait period in seconds after startup.
+ type: int
+ wait_for_bgp_asn:
+ description:
+ - ASN of BGP to wait for.
+ type: int
+ summary_lsa:
+ description:
+ - Summary LSAs configuration.
+ type: dict
+ suboptions:
+ set:
+ description:
+ - Set summary-lsa attribute.
+ type: bool
+ max_metric_value:
+ description:
+ - Max metric value for summary LSAs.
+ type: int
+ maximum_paths:
+ description:
+ - Maximum paths per destination.
+ type: int
+ mpls:
+ description:
+ - OSPF MPLS configuration settings.
+ type: dict
+ suboptions:
+ traffic_eng:
+ description:
+ - OSPF MPLS Traffic Engineering commands.
+ type: dict
+ suboptions:
+ areas:
+ description:
+ - List of Area IDs.
+ type: list
+ elements: dict
+ suboptions:
+ area_id:
+ description:
+ - Area Id in ip address format.
+ type: str
+ multicast_intact:
+ description:
+ - MPLS TE multicast support.
+ type: bool
+ router_id:
+ description:
+ - Router ID associated with TE.
+ type: str
+ name_lookup:
+ description:
+ - Display OSPF router ids as DNS names.
+ type: bool
+ passive_interface:
+ description:
+ - Suppress routing updates on the interface.
+ type: dict
+ suboptions:
+ default:
+ description:
+ - Interfaces passive by default.
+ type: bool
+ process_id:
+ description:
+ - The OSPF process tag.
+ type: str
+ required: true
+ redistribute:
+ description:
+ - Redistribute information from another routing protocol.
+ type: list
+ elements: dict
+ suboptions:
+ protocol:
+ description:
+ - The name of the protocol.
+ type: str
+ choices: [bgp, direct, eigrp, isis, lisp, ospf, rip, static]
+ required: true
+ id:
+ description:
+ - The identifier for the protocol specified.
+ type: str
+ route_map:
+ description:
+ - The route map policy to constrain redistribution.
+ type: str
+ required: true
+ rfc1583compatibility:
+ description:
+ - Configure 1583 compatibility for external path preferences.
+ type: bool
+ router_id:
+ description:
+ - Set OSPF process router-id.
+ type: str
+ shutdown:
+ description:
+ - Shutdown the OSPF protocol instance.
+ type: bool
+ summary_address:
+ description:
+ - Configure route summarization for redistribution.
+ type: list
+ elements: dict
+ suboptions:
+ prefix:
+ description:
+ - IP prefix in format x.x.x.x/ml.
+ type: str
+ required: true
+ not_advertise:
+ description:
+ - Suppress advertising the specified summary.
+ type: bool
+ tag:
+ description:
+ - A 32-bit tag value.
+ type: int
+ table_map:
+ description:
+ - Policy for filtering/modifying OSPF routes before sending them to RIB.
+ type: dict
+ suboptions:
+ name:
+ description:
+ - The Route Map name.
+ type: str
+ required: true
+ filter:
+ description:
+ - Block the OSPF routes from being sent to RIB.
+ type: bool
+ timers:
+ description:
+ - Configure timer related constants.
+ type: dict
+ suboptions:
+ lsa_arrival:
+ description:
+ - Mimimum interval between arrival of a LSA.
+ type: int
+ lsa_group_pacing:
+ description:
+ - LSA group refresh/maxage interval.
+ type: int
+ throttle:
+ description:
+ - Configure throttle related constants.
+ type: dict
+ suboptions:
+ lsa:
+ description:
+ - Set rate-limiting for LSA generation.
+ type: dict
+ suboptions:
+ start_interval:
+ description:
+ - The start interval.
+ type: int
+ hold_interval:
+ description:
+ - The hold interval.
+ type: int
+ max_interval:
+ description:
+ - The max interval.
+ type: int
+ spf:
+ description:
+ - Set OSPF SPF timers.
+ type: dict
+ suboptions:
+ initial_spf_delay:
+ description:
+ - Initial SPF schedule delay in milliseconds.
+ type: int
+ min_hold_time:
+ description:
+ - Minimum hold time between SPF calculations.
+ type: int
+ max_wait_time:
+ description:
+ - Maximum wait time between SPF calculations.
+ type: int
+ vrfs:
+ description:
+ - Configure VRF specific OSPF settings.
+ type: list
+ elements: dict
+ suboptions:
+ areas:
+ description:
+ - Configure properties of OSPF Areas.
+ type: list
+ elements: dict
+ suboptions:
+ area_id:
+ description:
+ - The Area ID in IP Address format.
+ type: str
+ required: true
+ authentication:
+ description:
+ - Authentication settings for the Area.
+ type: dict
+ suboptions:
+ set:
+ description:
+ - Set authentication for the area.
+ type: bool
+ message_digest:
+ description:
+ - Use message-digest authentication.
+ type: bool
+ default_cost:
+ description:
+ - Specify the default cost for default summary LSA.
+ type: int
+ filter_list:
+ description:
+ - Filter prefixes between OSPF areas.
+ type: list
+ elements: dict
+ suboptions:
+ route_map:
+ description:
+ - The Route-map name.
+ type: str
+ required: true
+ direction:
+ description:
+ - The direction to apply the route map.
+ type: str
+ choices: [in, out]
+ required: true
+ nssa:
+ description:
+ - NSSA settings for the area.
+ type: dict
+ suboptions:
+ set:
+ description:
+ - Configure area as NSSA.
+ type: bool
+ default_information_originate:
+ description:
+ - Originate Type-7 default LSA into NSSA area.
+ type: bool
+ no_redistribution:
+ description:
+ - Do not send redistributed LSAs into NSSA area.
+ type: bool
+ no_summary:
+ description:
+ - Do not send summary LSAs into NSSA area.
+ type: bool
+ translate:
+ description:
+ - Translate LSA.
+ type: dict
+ suboptions:
+ type7:
+ description:
+ - Translate from Type 7 to Type 5.
+ type: dict
+ suboptions:
+ always:
+ description:
+ - Always translate LSAs
+ type: bool
+ never:
+ description:
+ - Never translate LSAs
+ type: bool
+ supress_fa:
+ description:
+ - Suppress forwarding address in translated LSAs.
+ type: bool
+ ranges:
+ description:
+ - Configure an address range for the area.
+ type: list
+ elements: dict
+ suboptions:
+ prefix:
+ description:
+ - IP in Prefix format (x.x.x.x/len)
+ type: str
+ required: true
+ cost:
+ description:
+ - Cost to use for the range.
+ type: int
+ not_advertise:
+ description:
+ - Suppress advertising the specified range.
+ type: bool
+ stub:
+ description:
+ - Settings for configuring the area as a stub.
+ type: dict
+ suboptions:
+ set:
+ description:
+ - Configure the area as a stub.
+ type: bool
+ no_summary:
+ description:
+ - Prevent ABR from sending summary LSAs into stub area.
+ type: bool
+ auto_cost:
+ description:
+ - Calculate OSPF cost according to bandwidth.
+ type: dict
+ suboptions:
+ reference_bandwidth:
+ description:
+ - Reference bandwidth used to assign OSPF cost.
+ type: int
+ required: true
+ unit:
+ description:
+ - Specify in which unit the reference bandwidth is specified.
+ type: str
+ required: True
+ choices: [Gbps, Mbps]
+ bfd:
+ description:
+ - Enable BFD on all OSPF interfaces.
+ type: bool
+ default_information:
+ description:
+ - Control distribution of default routes.
+ type: dict
+ suboptions:
+ originate:
+ description:
+ - Distribute a default route.
+ type: dict
+ suboptions:
+ set:
+ description:
+ - Enable distribution of default route.
+ type: bool
+ always:
+ description:
+ - Always advertise a default route.
+ type: bool
+ route_map:
+ description:
+ - Policy to control distribution of default routes
+ type: str
+ default_metric:
+ description:
+ - Specify default metric for redistributed routes.
+ type: int
+ distance:
+ description:
+ - Configure the OSPF administrative distance.
+ type: int
+ down_bit_ignore:
+ description:
+ - Configure a PE router to ignore the DN bit for network summary,
+ external and NSSA external LSA.
+ type: bool
+ capability:
+ description:
+ - OSPF capability settings.
+ type: dict
+ suboptions:
+ vrf_lite:
+ description:
+ - Enable VRF-lite capability settings.
+ type: dict
+ suboptions:
+ set:
+ description:
+ - Enable VRF-lite support.
+ type: bool
+ evpn:
+ description:
+ - Ethernet VPN.
+ type: bool
+ graceful_restart:
+ description:
+ - Configure graceful restart.
+ type: dict
+ suboptions:
+ set:
+ description:
+ - Enable graceful-restart.
+ type: bool
+ grace_period:
+ description:
+ - Configure maximum interval to restart gracefully.
+ type: int
+ helper_disable:
+ description:
+ - Enable/Disable helper mode.
+ type: bool
+ log_adjacency_changes:
+ description:
+ - Log changes in adjacency state.
+ type: dict
+ suboptions:
+ log:
+ description:
+ - Enable/disable logging changes in adjacency state.
+ type: bool
+ detail:
+ description:
+ - Notify all state changes.
+ type: bool
+ max_lsa:
+ description:
+ - Feature to limit the number of non-self-originated LSAs.
+ type: dict
+ suboptions:
+ max_non_self_generated_lsa:
+ description:
+ - Set the maximum number of non self-generated LSAs.
+ type: int
+ required: true
+ threshold:
+ description:
+ - Threshold value (%) at which to generate a warning message.
+ type: int
+ ignore_count:
+ description:
+ - Set count on how many times adjacencies can be suppressed.
+ type: int
+ ignore_time:
+ description:
+ - Set time during which all adjacencies are suppressed.
+ type: int
+ reset_time:
+ description:
+ - Set number of minutes after which ignore-count is reset to zero.
+ type: int
+ warning_only:
+ description:
+ - Log a warning message when limit is exceeded.
+ type: bool
+ max_metric:
+ description:
+ - Maximize the cost metric.
+ type: dict
+ suboptions:
+ router_lsa:
+ description:
+ - Router LSA configuration.
+ type: dict
+ suboptions:
+ set:
+ description:
+ - Set router-lsa attribute.
+ type: bool
+ external_lsa:
+ description:
+ - External LSA configuration.
+ type: dict
+ suboptions:
+ set:
+ description:
+ - Set external-lsa attribute.
+ type: bool
+ max_metric_value:
+ description:
+ - Set max metric value for external LSAs.
+ type: int
+ include_stub:
+ description:
+ - Advertise Max metric for Stub links as well.
+ type: bool
+ on_startup:
+ description:
+ - Effective only at startup.
+ type: dict
+ suboptions:
+ set:
+ description:
+ - Set on-startup attribute.
+ type: bool
+ wait_period:
+ description:
+ - Wait period in seconds after startup.
+ type: int
+ wait_for_bgp_asn:
+ description:
+ - ASN of BGP to wait for.
+ type: int
+ summary_lsa:
+ description:
+ - Summary LSAs configuration.
+ type: dict
+ suboptions:
+ set:
+ description:
+ - Set summary-lsa attribute.
+ type: bool
+ max_metric_value:
+ description:
+ - Max metric value for summary LSAs.
+ type: int
+ maximum_paths:
+ description:
+ - Maximum paths per destination.
+ type: int
+ name_lookup:
+ description:
+ - Display OSPF router ids as DNS names.
+ type: bool
+ passive_interface:
+ description:
+ - Suppress routing updates on the interface.
+ type: dict
+ suboptions:
+ default:
+ description:
+ - Interfaces passive by default.
+ type: bool
+ redistribute:
+ description:
+ - Redistribute information from another routing protocol.
+ type: list
+ elements: dict
+ suboptions:
+ protocol:
+ description:
+ - The name of the protocol.
+ type: str
+ choices: [bgp, direct, eigrp, isis, lisp, ospf, rip, static]
+ required: true
+ id:
+ description:
+ - The identifier for the protocol specified.
+ type: str
+ route_map:
+ description:
+ - The route map policy to constrain redistribution.
+ type: str
+ required: true
+ rfc1583compatibility:
+ description:
+ - Configure 1583 compatibility for external path preferences.
+ type: bool
+ router_id:
+ description:
+ - Set OSPF process router-id.
+ type: str
+ shutdown:
+ description:
+ - Shutdown the OSPF protocol instance.
+ type: bool
+ summary_address:
+ description:
+ - Configure route summarization for redistribution.
+ type: list
+ elements: dict
+ suboptions:
+ prefix:
+ description:
+ - IP prefix in format x.x.x.x/ml.
+ type: str
+ required: true
+ not_advertise:
+ description:
+ - Suppress advertising the specified summary.
+ type: bool
+ tag:
+ description:
+ - A 32-bit tag value.
+ type: int
+ table_map:
+ description:
+ - Policy for filtering/modifying OSPF routes before sending them to
+ RIB.
+ type: dict
+ suboptions:
+ name:
+ description:
+ - The Route Map name.
+ type: str
+ required: true
+ filter:
+ description:
+ - Block the OSPF routes from being sent to RIB.
+ type: bool
+ timers:
+ description:
+ - Configure timer related constants.
+ type: dict
+ suboptions:
+ lsa_arrival:
+ description:
+ - Mimimum interval between arrival of a LSA.
+ type: int
+ lsa_group_pacing:
+ description:
+ - LSA group refresh/maxage interval.
+ type: int
+ throttle:
+ description:
+ - Configure throttle related constants.
+ type: dict
+ suboptions:
+ lsa:
+ description:
+ - Set rate-limiting for LSA generation.
+ type: dict
+ suboptions:
+ start_interval:
+ description:
+ - The start interval.
+ type: int
+ hold_interval:
+ description:
+ - The hold interval.
+ type: int
+ max_interval:
+ description:
+ - The max interval.
+ type: int
+ spf:
+ description:
+ - Set OSPF SPF timers.
+ type: dict
+ suboptions:
+ initial_spf_delay:
+ description:
+ - Initial SPF schedule delay in milliseconds.
+ type: int
+ min_hold_time:
+ description:
+ - Minimum hold time between SPF calculations.
+ type: int
+ max_wait_time:
+ description:
+ - Maximum wait time between SPF calculations.
+ type: int
+ vrf:
+ description:
+ - Name/Identifier of the VRF.
+ type: str
+ required: true
+ state:
+ description:
+ - The state the configuration should be left in.
+ type: str
+ choices:
+ - merged
+ - replaced
+ - overridden
+ - deleted
+ - gathered
+ - parsed
+ - rendered
+ default: merged
+
+"""
+EXAMPLES = """
+# Using merged
+
+# Before state:
+# -------------
+# nxos-9k-rdo# sh running-config | section "^router ospf .*"
+# nxos-9k-rdo#
+
+- name: Merge the provided configuration with the existing running configuration
+ cisco.nxos.nxos_ospfv2:
+ config:
+ processes:
+ - process_id: 100
+ router_id: 203.0.113.20
+ - process_id: 102
+ router_id: 198.51.100.1
+ areas:
+ - area_id: 0.0.0.100
+ filter_list:
+ - route_map: rmap_1
+ direction: in
+ - route_map: rmap_2
+ direction: out
+ ranges:
+ - prefix: 198.51.100.64/27
+ not_advertise: true
+ - prefix: 198.51.100.96/27
+ cost: 120
+ - area_id: 0.0.0.101
+ authentication:
+ message_digest: true
+ redistribute:
+ - protocol: eigrp
+ id: 120
+ route_map: rmap_1
+ - protocol: direct
+ route_map: ospf102-direct-connect
+ vrfs:
+ - vrf: zone1
+ router_id: 198.51.100.129
+ redistribute:
+ - protocol: static
+ route_map: zone1-static-connect
+ summary_address:
+ - prefix: 198.51.100.128/27
+ tag: 121
+ - prefix: 198.51.100.160/27
+ areas:
+ - area_id: 0.0.0.102
+ nssa:
+ default_information_originate: true
+ no_summary: true
+ - area_id: 0.0.0.103
+ nssa:
+ no_summary: true
+ translate:
+ type7:
+ always: true
+ - vrf: zone2
+ auto_cost:
+ reference_bandwidth: 45
+ unit: Gbps
+ state: merged
+
+# Task output
+# -------------
+# before: {}
+#
+# commands:
+# - router ospf 102
+# - router-id 198.51.100.1
+# - redistribute eigrp 120 route-map rmap_1
+# - redistribute direct route-map ospf102-direct-connect
+# - area 0.0.0.100 filter-list route-map rmap_1 in
+# - area 0.0.0.100 filter-list route-map rmap_2 out
+# - area 0.0.0.100 range 198.51.100.64/27 not-advertise
+# - area 0.0.0.100 range 198.51.100.96/27 cost 120
+# - area 0.0.0.101 authentication message-digest
+# - vrf zone1
+# - router-id 198.51.100.129
+# - summary-address 198.51.100.128/27 tag 121
+# - summary-address 198.51.100.160/27
+# - redistribute static route-map zone1-static-connect
+# - area 0.0.0.102 nssa no-summary default-information-originate
+# - area 0.0.0.103 nssa no-summary
+# - area 0.0.0.103 nssa translate type7 always
+# - vrf zone2
+# - auto-cost reference-bandwidth 45 Gbps
+# - router ospf 100
+# - router-id 203.0.113.20
+#
+# after:
+# processes:
+# - process_id: "100"
+# router_id: 203.0.113.20
+# - areas:
+# - area_id: 0.0.0.100
+# filter_list:
+# - direction: out
+# route_map: rmap_2
+# - direction: in
+# route_map: rmap_1
+# ranges:
+# - not_advertise: true
+# prefix: 198.51.100.64/27
+# - cost: 120
+# prefix: 198.51.100.96/27
+# - area_id: 0.0.0.101
+# authentication:
+# message_digest: true
+# process_id: "102"
+# redistribute:
+# - protocol: direct
+# route_map: ospf102-direct-connect
+# - id: "120"
+# protocol: eigrp
+# route_map: rmap_1
+# router_id: 198.51.100.1
+# vrfs:
+# - areas:
+# - area_id: 0.0.0.102
+# nssa:
+# default_information_originate: true
+# no_summary: true
+# - area_id: 0.0.0.103
+# nssa:
+# no_summary: true
+# translate:
+# type7:
+# always: true
+# redistribute:
+# - protocol: static
+# route_map: zone1-static-connect
+# router_id: 198.51.100.129
+# vrf: zone1
+# - auto_cost:
+# reference_bandwidth: 45
+# unit: Gbps
+# vrf: zone2
+#
+
+# After state:
+# ------------
+# nxos-9k-rdo# sh running-config | section "^router ospf .*"
+# router ospf 100
+# router-id 203.0.113.20
+# router ospf 102
+# router-id 198.51.100.1
+# redistribute direct route-map ospf102-direct-connect
+# redistribute eigrp 120 route-map rmap_1
+# area 0.0.0.100 filter-list route-map rmap_2 out
+# area 0.0.0.100 filter-list route-map rmap_1 in
+# area 0.0.0.100 range 198.51.100.64/27 not-advertise
+# area 0.0.0.100 range 198.51.100.96/27 cost 120
+# area 0.0.0.101 authentication message-digest
+# vrf zone1
+# router-id 198.51.100.129
+# area 0.0.0.102 nssa no-summary default-information-originate
+# area 0.0.0.103 nssa no-summary
+# area 0.0.0.103 nssa translate type7 always
+# redistribute static route-map zone1-static-connect
+# summary-address 198.51.100.128/27 tag 121
+# summary-address 198.51.100.160/27
+# vrf zone2
+# auto-cost reference-bandwidth 45 Gbps
+
+# Using replaced
+
+# Before state:
+# ------------
+# nxos-9k-rdo# sh running-config | section "^router ospf .*"
+# router ospf 100
+# router-id 203.0.113.20
+# router ospf 102
+# router-id 198.51.100.1
+# redistribute direct route-map ospf102-direct-connect
+# redistribute eigrp 120 route-map rmap_1
+# area 0.0.0.100 filter-list route-map rmap_2 out
+# area 0.0.0.100 filter-list route-map rmap_1 in
+# area 0.0.0.100 range 198.51.100.64/27 not-advertise
+# area 0.0.0.100 range 198.51.100.96/27 cost 120
+# area 0.0.0.101 authentication message-digest
+# vrf zone1
+# router-id 198.51.100.129
+# area 0.0.0.102 nssa no-summary default-information-originate
+# area 0.0.0.103 nssa no-summary
+# area 0.0.0.103 nssa translate type7 always
+# redistribute static route-map zone1-static-connect
+# summary-address 198.51.100.128/27 tag 121
+# summary-address 198.51.100.160/27
+# vrf zone2
+# auto-cost reference-bandwidth 45 Gbps
+
+- name: Replace device configurations of listed OSPF processes with provided configurations
+ cisco.nxos.nxos_ospfv2:
+ config:
+ processes:
+ - process_id: 102
+ router_id: 198.51.100.1
+ areas:
+ - area_id: 0.0.0.100
+ filter_list:
+ - route_map: rmap_8
+ direction: in
+ ranges:
+ - prefix: 198.51.100.64/27
+ not_advertise: true
+ - area_id: 0.0.0.101
+ stub:
+ no_summary: true
+ redistribute:
+ - protocol: eigrp
+ id: 130
+ route_map: rmap_1
+ - protocol: direct
+ route_map: ospf102-direct-connect
+ vrfs:
+ - vrf: zone1
+ router_id: 198.51.100.129
+ redistribute:
+ - protocol: bgp
+ id: 65563
+ route_map: zone1-bgp-connect
+ areas:
+ - area_id: 0.0.0.102
+ nssa:
+ default_information_originate: true
+ no_summary: true
+ state: replaced
+
+# Task output
+# -------------
+# before:
+# processes:
+# - process_id: "100"
+# router_id: 203.0.113.20
+# - areas:
+# - area_id: 0.0.0.100
+# filter_list:
+# - direction: out
+# route_map: rmap_2
+# - direction: in
+# route_map: rmap_1
+# ranges:
+# - not_advertise: true
+# prefix: 198.51.100.64/27
+# - cost: 120
+# prefix: 198.51.100.96/27
+# - area_id: 0.0.0.101
+# authentication:
+# message_digest: true
+# process_id: "102"
+# redistribute:
+# - protocol: direct
+# route_map: ospf102-direct-connect
+# - id: "120"
+# protocol: eigrp
+# route_map: rmap_1
+# router_id: 198.51.100.1
+# vrfs:
+# - areas:
+# - area_id: 0.0.0.102
+# nssa:
+# default_information_originate: true
+# no_summary: true
+# - area_id: 0.0.0.103
+# nssa:
+# no_summary: true
+# translate:
+# type7:
+# always: true
+# redistribute:
+# - protocol: static
+# route_map: zone1-static-connect
+# router_id: 198.51.100.129
+# vrf: zone1
+# - auto_cost:
+# reference_bandwidth: 45
+# unit: Gbps
+# vrf: zone2
+#
+# commands:
+# - router ospf 102
+# - redistribute eigrp 130 route-map rmap_1
+# - no redistribute eigrp 120 route-map rmap_1
+# - area 0.0.0.100 filter-list route-map rmap_8 in
+# - no area 0.0.0.100 filter-list route-map rmap_2 out
+# - no area 0.0.0.100 range 198.51.100.96/27
+# - no area 0.0.0.101 authentication
+# - area 0.0.0.101 stub no-summary
+# - vrf zone1
+# - no summary-address 198.51.100.128/27 tag 121
+# - no summary-address 198.51.100.160/27
+# - redistribute bgp 65563 route-map zone1-bgp-connect
+# - no redistribute static route-map zone1-static-connect
+# - no area 0.0.0.103 nssa
+# - no area 0.0.0.103 nssa translate type7 always
+# - no vrf zone2
+#
+# after:
+# processes:
+# - process_id: "100"
+# router_id: 203.0.113.20
+# - areas:
+# - area_id: 0.0.0.101
+# stub:
+# no_summary: true
+# - area_id: 0.0.0.100
+# filter_list:
+# - direction: in
+# route_map: rmap_8
+# ranges:
+# - not_advertise: true
+# prefix: 198.51.100.64/27
+# process_id: "102"
+# redistribute:
+# - protocol: direct
+# route_map: ospf102-direct-connect
+# - id: "130"
+# protocol: eigrp
+# route_map: rmap_1
+# router_id: 198.51.100.1
+# vrfs:
+# - areas:
+# - area_id: 0.0.0.102
+# nssa:
+# default_information_originate: true
+# no_summary: true
+# redistribute:
+# - id: "65563"
+# protocol: bgp
+# route_map: zone1-bgp-connect
+# router_id: 198.51.100.129
+# vrf: zone1
+
+# After state:
+# ------------
+# nxos-9k-rdo# sh running-config | section "^router ospf .*"
+# router ospf 100
+# router-id 203.0.113.20
+# router ospf 102
+# router-id 198.51.100.1
+# area 0.0.0.101 stub no-summary
+# redistribute direct route-map ospf102-direct-connect
+# redistribute eigrp 130 route-map rmap_1
+# area 0.0.0.100 filter-list route-map rmap_8 in
+# area 0.0.0.100 range 198.51.100.64/27 not-advertise
+# vrf zone1
+# router-id 198.51.100.129
+# area 0.0.0.102 nssa no-summary default-information-originate
+# redistribute bgp 65563 route-map zone1-bgp-connect
+
+# Using overridden
+
+# Before state:
+# ------------
+# nxos-9k-rdo# sh running-config | section "^router ospf .*"
+# router ospf 100
+# router-id 203.0.113.20
+# router ospf 102
+# router-id 198.51.100.1
+# redistribute direct route-map ospf102-direct-connect
+# redistribute eigrp 120 route-map rmap_1
+# area 0.0.0.100 filter-list route-map rmap_2 out
+# area 0.0.0.100 filter-list route-map rmap_1 in
+# area 0.0.0.100 range 198.51.100.64/27 not-advertise
+# area 0.0.0.100 range 198.51.100.96/27 cost 120
+# area 0.0.0.101 authentication message-digest
+# vrf zone1
+# router-id 198.51.100.129
+# area 0.0.0.102 nssa no-summary default-information-originate
+# area 0.0.0.103 nssa no-summary
+# area 0.0.0.103 nssa translate type7 always
+# redistribute static route-map zone1-static-connect
+# summary-address 198.51.100.128/27 tag 121
+# summary-address 198.51.100.160/27
+# vrf zone2
+# auto-cost reference-bandwidth 45 Gbps
+
+- name: Override all OSPF configuration with provided configuration
+ cisco.nxos.nxos_ospfv2:
+ config:
+ processes:
+ - process_id: 104
+ router_id: 203.0.113.20
+ - process_id: 102
+ router_id: 198.51.100.1
+ shutdown: true
+ state: overridden
+
+# Task output
+# -------------
+# before:
+# processes:
+# - process_id: "100"
+# router_id: 203.0.113.20
+# - areas:
+# - area_id: 0.0.0.100
+# filter_list:
+# - direction: out
+# route_map: rmap_2
+# - direction: in
+# route_map: rmap_1
+# ranges:
+# - not_advertise: true
+# prefix: 198.51.100.64/27
+# - cost: 120
+# prefix: 198.51.100.96/27
+# - area_id: 0.0.0.101
+# authentication:
+# message_digest: true
+# process_id: "102"
+# redistribute:
+# - protocol: direct
+# route_map: ospf102-direct-connect
+# - id: "120"
+# protocol: eigrp
+# route_map: rmap_1
+# router_id: 198.51.100.1
+# vrfs:
+# - areas:
+# - area_id: 0.0.0.102
+# nssa:
+# default_information_originate: true
+# no_summary: true
+# - area_id: 0.0.0.103
+# nssa:
+# no_summary: true
+# translate:
+# type7:
+# always: true
+# redistribute:
+# - protocol: static
+# route_map: zone1-static-connect
+# router_id: 198.51.100.129
+# vrf: zone1
+# - auto_cost:
+# reference_bandwidth: 45
+# unit: Gbps
+# vrf: zone2
+#
+# commands:
+# - no router ospf 100
+# - router ospf 104
+# - router-id 203.0.113.20
+# - router ospf 102
+# - shutdown
+# - no redistribute direct route-map ospf102-direct-connect
+# - no redistribute eigrp 120 route-map rmap_1
+# - no area 0.0.0.100 filter-list route-map rmap_2 out
+# - no area 0.0.0.100 filter-list route-map rmap_1 in
+# - no area 0.0.0.100 range 198.51.100.64/27
+# - no area 0.0.0.100 range 198.51.100.96/27
+# - no area 0.0.0.101 authentication
+# - no vrf zone1
+# - no vrf zone2
+#
+# after:
+# processes:
+# - process_id: "102"
+# router_id: 198.51.100.1
+# shutdown: true
+# - process_id: "104"
+# router_id: 203.0.113.20
+
+# After state:
+# ------------
+# nxos-9k-rdo# sh running-config | section "^router ospf .*"
+# router ospf 102
+# router-id 198.51.100.1
+# shutdown
+# router ospf 104
+# router-id 203.0.113.20
+
+# Using deleted to delete a single OSPF process
+
+# Before state:
+# ------------
+# nxos-9k-rdo# sh running-config | section "^router ospf .*"
+# router ospf 100
+# router-id 203.0.113.20
+# router ospf 102
+# router-id 198.51.100.1
+# redistribute direct route-map ospf102-direct-connect
+# redistribute eigrp 120 route-map rmap_1
+# area 0.0.0.100 filter-list route-map rmap_2 out
+# area 0.0.0.100 filter-list route-map rmap_1 in
+# area 0.0.0.100 range 198.51.100.64/27 not-advertise
+# area 0.0.0.100 range 198.51.100.96/27 cost 120
+# area 0.0.0.101 authentication message-digest
+# vrf zone1
+# router-id 198.51.100.129
+# area 0.0.0.102 nssa no-summary default-information-originate
+# area 0.0.0.103 nssa no-summary
+# area 0.0.0.103 nssa translate type7 always
+# redistribute static route-map zone1-static-connect
+# summary-address 198.51.100.128/27 tag 121
+# summary-address 198.51.100.160/27
+# vrf zone2
+# auto-cost reference-bandwidth 45 Gbps
+
+- name: Delete a single OSPF process
+ cisco.nxos.nxos_ospfv2:
+ config:
+ processes:
+ - process_id: 102
+ state: deleted
+
+# Task output
+# -------------
+# before:
+# processes:
+# - process_id: "100"
+# router_id: 203.0.113.20
+# - areas:
+# - area_id: 0.0.0.100
+# filter_list:
+# - direction: out
+# route_map: rmap_2
+# - direction: in
+# route_map: rmap_1
+# ranges:
+# - not_advertise: true
+# prefix: 198.51.100.64/27
+# - cost: 120
+# prefix: 198.51.100.96/27
+# - area_id: 0.0.0.101
+# authentication:
+# message_digest: true
+# process_id: "102"
+# redistribute:
+# - protocol: direct
+# route_map: ospf102-direct-connect
+# - id: "120"
+# protocol: eigrp
+# route_map: rmap_1
+# router_id: 198.51.100.1
+# vrfs:
+# - areas:
+# - area_id: 0.0.0.102
+# nssa:
+# default_information_originate: true
+# no_summary: true
+# - area_id: 0.0.0.103
+# nssa:
+# no_summary: true
+# translate:
+# type7:
+# always: true
+# redistribute:
+# - protocol: static
+# route_map: zone1-static-connect
+# router_id: 198.51.100.129
+# vrf: zone1
+# - auto_cost:
+# reference_bandwidth: 45
+# unit: Gbps
+# vrf: zone2
+#
+# commands:
+# - no router ospf 102
+#
+# after:
+# processes:
+# - process_id: "100"
+# router_id: 203.0.113.20
+
+# After state:
+# ------------
+# nxos-9k-rdo# sh running-config | section "^router ospf .*"
+# router ospf 100
+# router-id 203.0.113.20
+
+# Using deleted all OSPF processes from the device
+
+# Before state:
+# ------------
+# nxos-9k-rdo# sh running-config | section "^router ospf .*"
+# router ospf 100
+# router-id 203.0.113.20
+# router ospf 102
+# router-id 198.51.100.1
+# redistribute direct route-map ospf102-direct-connect
+# redistribute eigrp 120 route-map rmap_1
+# area 0.0.0.100 filter-list route-map rmap_2 out
+# area 0.0.0.100 filter-list route-map rmap_1 in
+# area 0.0.0.100 range 198.51.100.64/27 not-advertise
+# area 0.0.0.100 range 198.51.100.96/27 cost 120
+# area 0.0.0.101 authentication message-digest
+# vrf zone1
+# router-id 198.51.100.129
+# area 0.0.0.102 nssa no-summary default-information-originate
+# area 0.0.0.103 nssa no-summary
+# area 0.0.0.103 nssa translate type7 always
+# redistribute static route-map zone1-static-connect
+# summary-address 198.51.100.128/27 tag 121
+# summary-address 198.51.100.160/27
+# vrf zone2
+# auto-cost reference-bandwidth 45 Gbps
+
+- name: Delete all OSPF processes from the device
+ cisco.nxos.nxos_ospfv2:
+ state: deleted
+
+# Task output
+# -------------
+# before:
+# processes:
+# - process_id: "100"
+# router_id: 203.0.113.20
+# - areas:
+# - area_id: 0.0.0.100
+# filter_list:
+# - direction: out
+# route_map: rmap_2
+# - direction: in
+# route_map: rmap_1
+# ranges:
+# - not_advertise: true
+# prefix: 198.51.100.64/27
+# - cost: 120
+# prefix: 198.51.100.96/27
+# - area_id: 0.0.0.101
+# authentication:
+# message_digest: true
+# process_id: "102"
+# redistribute:
+# - protocol: direct
+# route_map: ospf102-direct-connect
+# - id: "120"
+# protocol: eigrp
+# route_map: rmap_1
+# router_id: 198.51.100.1
+# vrfs:
+# - areas:
+# - area_id: 0.0.0.102
+# nssa:
+# default_information_originate: true
+# no_summary: true
+# - area_id: 0.0.0.103
+# nssa:
+# no_summary: true
+# translate:
+# type7:
+# always: true
+# redistribute:
+# - protocol: static
+# route_map: zone1-static-connect
+# router_id: 198.51.100.129
+# vrf: zone1
+# - auto_cost:
+# reference_bandwidth: 45
+# unit: Gbps
+# vrf: zone2
+#
+# commands:
+# - no router ospf 100
+# - no router ospf 102
+#
+# after: {}
+
+# After state:
+# ------------
+# nxos-9k-rdo# sh running-config | section "^router ospf .*"
+# nxos-9k-rdo#
+
+# Using rendered
+
+- name: Render platform specific configuration lines (without connecting to the device)
+ cisco.nxos.nxos_ospfv2:
+ config:
+ processes:
+ - process_id: 100
+ router_id: 203.0.113.20
+ - process_id: 102
+ router_id: 198.51.100.1
+ areas:
+ - area_id: 0.0.0.100
+ filter_list:
+ - route_map: rmap_1
+ direction: in
+ - route_map: rmap_2
+ direction: out
+ ranges:
+ - prefix: 198.51.100.64/27
+ not_advertise: true
+ - prefix: 198.51.100.96/27
+ cost: 120
+ - area_id: 0.0.0.101
+ authentication:
+ message_digest: true
+ redistribute:
+ - protocol: eigrp
+ id: 120
+ route_map: rmap_1
+ - protocol: direct
+ route_map: ospf102-direct-connect
+ vrfs:
+ - vrf: zone1
+ router_id: 198.51.100.129
+ redistribute:
+ - protocol: static
+ route_map: zone1-static-connect
+ summary_address:
+ - prefix: 198.51.100.128/27
+ tag: 121
+ - prefix: 198.51.100.160/27
+ areas:
+ - area_id: 0.0.0.102
+ nssa:
+ default_information_originate: true
+ no_summary: true
+ - area_id: 0.0.0.103
+ nssa:
+ no_summary: true
+ translate:
+ type7:
+ always: true
+ - vrf: zone2
+ auto_cost:
+ reference_bandwidth: 45
+ unit: Gbps
+ state: rendered
+
+# Task Output (redacted)
+# -----------------------
+# rendered:
+# - router ospf 100
+# - router-id 203.0.113.20
+# - router ospf 102
+# - router-id 198.51.100.1
+# - redistribute eigrp 120 route-map rmap_1
+# - redistribute direct route-map ospf102-direct-connect
+# - area 0.0.0.100 filter-list route-map rmap_1 in
+# - area 0.0.0.100 filter-list route-map rmap_2 out
+# - area 0.0.0.100 range 198.51.100.64/27 not-advertise
+# - area 0.0.0.100 range 198.51.100.96/27 cost 120
+# - area 0.0.0.101 authentication message-digest
+# - vrf zone1
+# - router-id 198.51.100.129
+# - summary-address 198.51.100.128/27 tag 121
+# - summary-address 198.51.100.160/27
+# - redistribute static route-map zone1-static-connect
+# - area 0.0.0.102 nssa no-summary default-information-originate
+# - area 0.0.0.103 nssa no-summary
+# - area 0.0.0.103 nssa translate type7 always
+# - vrf zone2
+# - auto-cost reference-bandwidth 45 Gbps
+
+# Using parsed
+
+# parsed.cfg
+# ------------
+# router ospf 100
+# router-id 192.0.100.1
+# area 0.0.0.101 nssa no-summary no-redistribution
+# area 0.0.0.102 stub no-summary
+# redistribute direct route-map ospf-direct-connect
+# redistribute eigrp 120 route-map rmap_1
+# area 0.0.0.100 filter-list route-map rmap_2 out
+# area 0.0.0.100 filter-list route-map rmap_1 in
+# area 0.0.0.100 range 192.0.2.0/24 not-advertise
+# area 0.0.0.100 range 192.0.3.0/24 cost 120
+# area 0.0.0.100 authentication message-digest
+# vrf zone1
+# router-id 192.0.100.2
+# area 0.0.100.1 nssa no-summary no-redistribution
+# redistribute static route-map zone1-direct-connect
+# summary-address 10.0.0.0/24 tag 120
+# summary-address 11.0.0.0/24 not-advertise
+# vrf zone2
+# auto-cost reference-bandwidth 45 Gbps
+# down-bit-ignore
+# capability vrf-lite evpn
+# shutdown
+# router ospf 102
+# router-id 198.54.100.1
+# shutdown
+# vrf zone2
+# summary-address 192.0.8.0/24 tag 120
+# vrf zone4
+# shutdown
+
+- name: Parse externally provided OSPFv2 config
+ cisco.nxos.nxos_ospfv2:
+ running_config: "{{ lookup('file', 'ospfv2.cfg') }}"
+ state: parsed
+
+# Task output (redacted)
+# -----------------------
+# parsed:
+# processes:
+# - process_id: "100"
+# areas:
+# - area_id: 0.0.0.101
+# nssa:
+# no_redistribution: true
+# no_summary: true
+# - area_id: 0.0.0.102
+# stub:
+# no_summary: true
+# - area_id: 0.0.0.100
+# authentication:
+# message_digest: true
+# filter_list:
+# - direction: out
+# route_map: rmap_2
+# - direction: in
+# route_map: rmap_1
+# ranges:
+# - not_advertise: true
+# prefix: 192.0.2.0/24
+# - cost: 120
+# prefix: 192.0.3.0/24
+# redistribute:
+# - protocol: direct
+# route_map: ospf-direct-connect
+# - id: "120"
+# protocol: eigrp
+# route_map: rmap_1
+# router_id: 192.0.100.1
+# vrfs:
+# - vrf: zone1
+# areas:
+# - area_id: 0.0.100.1
+# nssa:
+# no_redistribution: true
+# no_summary: true
+# redistribute:
+# - protocol: static
+# route_map: zone1-direct-connect
+# router_id: 192.0.100.2
+# summary_address:
+# - prefix: 10.0.0.0/24
+# tag: 120
+# - not_advertise: true
+# prefix: 11.0.0.0/24
+# - vrf: zone2
+# auto_cost:
+# reference_bandwidth: 45
+# unit: Gbps
+# capability:
+# vrf_lite:
+# evpn: true
+# down_bit_ignore: true
+# shutdown: true
+# - process_id: "102"
+# router_id: 198.54.100.1
+# shutdown: true
+# vrfs:
+# - vrf: zone2
+# summary_address:
+# - prefix: 192.0.8.0/24
+# tag: 120
+# - vrf: zone4
+# shutdown: true
+
+# Using gathered
+
+- name: Gather OSPFv2 facts using gathered
+ cisco.nxos.nxos_ospfv2:
+ state: gathered
+
+# Task output (redacted)
+# -----------------------
+# gathered:
+# processes:
+# - process_id: "102"
+# areas:
+# - area_id: 0.0.0.101
+# stub:
+# no_summary: true
+# - area_id: 0.0.0.100
+# filter_list:
+# - direction: in
+# route_map: rmap_8
+# ranges:
+# - not_advertise: true
+# prefix: 198.51.100.64/27
+# redistribute:
+# - protocol: direct
+# route_map: ospf102-direct-connect
+# - id: "130"
+# protocol: eigrp
+# route_map: rmap_1
+# router_id: 198.51.100.1
+# vrfs:
+# - vrf: zone1
+# areas:
+# - area_id: 0.0.0.102
+# nssa:
+# default_information_originate: true
+# no_summary: true
+# redistribute:
+# - id: "65563"
+# protocol: bgp
+# route_map: zone1-bgp-connect
+# router_id: 198.51.100.129
+#
+"""
+RETURN = """
+before:
+ description: The configuration prior to the model invocation.
+ returned: always
+ type: dict
+ 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: dict
+ 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 ospf 102"
+ - "router-id 198.54.100.1"
+ - "router ospf 100"
+ - "router-id 192.0.100.1"
+ - "redistribute eigrp 120 route-map rmap_1"
+ - "redistribute direct route-map ospf-direct-connect"
+ - "area 0.0.0.100 filter-list route-map rmap_1 in"
+ - "area 0.0.0.100 filter-list route-map rmap_2 out"
+ - "area 0.0.0.100 range 192.0.2.0/24 not-advertise"
+ - "area 0.0.0.100 range 192.0.3.0/24 cost 120"
+ - "vrf zone1"
+ - "router-id 192.0.100.2"
+ - "summary-address 10.0.0.0/24 tag 121"
+ - "summary-address 11.0.0.0/24"
+ - "redistribute static route-map zone1-direct-connect"
+ - "vrf zone2"
+ - "auto-cost reference-bandwidth 45 Gbps"
+ - "capability vrf-lite evpn"
+"""
+
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.ospfv2.ospfv2 import (
+ Ospfv2Args,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.config.ospfv2.ospfv2 import (
+ Ospfv2,
+)
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ required_if = [
+ ("state", "merged", ("config",)),
+ ("state", "replaced", ("config",)),
+ ("state", "overridden", ("config",)),
+ ("state", "rendered", ("config",)),
+ ("state", "parsed", ("running_config",)),
+ ]
+ module = AnsibleModule(
+ argument_spec=Ospfv2Args.argument_spec,
+ required_if=required_if,
+ supports_check_mode=True,
+ )
+
+ result = Ospfv2(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_ospfv3.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_ospfv3.py
new file mode 100644
index 00000000..27d9ff65
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_ospfv3.py
@@ -0,0 +1,1702 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# Copyright 2020 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 nxos_ospfv3
+"""
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+DOCUMENTATION = """
+module: nxos_ospfv3
+short_description: OSPFv3 resource module
+description:
+- This module manages OSPFv3 configuration on devices running Cisco NX-OS.
+version_added: 1.2.0
+notes:
+- Tested against NX-OS 7.0(3)I5(1).
+- Unsupported for Cisco MDS
+- This module works with connection C(network_cli) and C(httpapi).
+author: Nilashish Chakraborty (@NilashishC)
+options:
+ running_config:
+ description:
+ - This option is used only with state I(parsed).
+ - The value of this option should be the output received from the NX-OS device
+ by executing the command B(show running-config | section "^router ospfv3").
+ - The state I(parsed) reads the configuration from C(running_config) option and
+ transforms it into Ansible structured data as per the resource module's argspec
+ and the value is then returned in the I(parsed) key within the result.
+ type: str
+ config:
+ description: A list of OSPFv3 process configuration.
+ type: dict
+ suboptions:
+ processes:
+ description:
+ - A list of OSPFv3 instances' configurations.
+ type: list
+ elements: dict
+ suboptions:
+ address_family:
+ description:
+ - IPv6 unicast address-family OSPFv3 settings.
+ type: dict
+ suboptions:
+ afi:
+ description:
+ - Configure OSPFv3 settings under IPv6 address-family.
+ type: str
+ choices: ['ipv6']
+ safi:
+ description:
+ - Configure OSPFv3 settings under IPv6 unicast address-family.
+ type: str
+ choices: ['unicast']
+ areas:
+ description:
+ - Configure properties of OSPF Areas under address-family.
+ type: list
+ elements: dict
+ suboptions:
+ area_id:
+ description:
+ - The Area ID in IP Address format.
+ type: str
+ required: True
+ default_cost:
+ description:
+ - Specify the default cost.
+ type: int
+ filter_list:
+ description:
+ - Filter prefixes between OSPF areas.
+ type: list
+ elements: dict
+ suboptions:
+ route_map:
+ description:
+ - The Route-map name.
+ type: str
+ required: True
+ direction:
+ description:
+ - The direction to apply the route map.
+ type: str
+ choices: [in, out]
+ required: True
+ ranges:
+ description:
+ - Configure an address range for the area.
+ type: list
+ elements: dict
+ suboptions:
+ prefix:
+ description:
+ - IP in Prefix format (x.x.x.x/len)
+ type: str
+ required: True
+ cost:
+ description:
+ - Cost to use for the range.
+ type: int
+ not_advertise:
+ description:
+ - Suppress advertising the specified range.
+ type: bool
+ default_information:
+ description:
+ - Control distribution of default routes.
+ type: dict
+ suboptions:
+ originate:
+ description:
+ - Distribute a default route.
+ type: dict
+ suboptions:
+ set:
+ description:
+ - Enable distribution of default route.
+ type: bool
+ always:
+ description:
+ - Always advertise a default route.
+ type: bool
+ route_map:
+ description:
+ - Policy to control distribution of default routes
+ type: str
+ distance:
+ description:
+ - Configure the OSPF administrative distance.
+ type: int
+ maximum_paths:
+ description:
+ - Maximum paths per destination.
+ type: int
+ redistribute:
+ description:
+ - Redistribute information from another routing protocol.
+ type: list
+ elements: dict
+ suboptions:
+ protocol:
+ description:
+ - The name of the protocol.
+ type: str
+ choices: [bgp, direct, eigrp, isis, lisp, ospfv3, rip, static]
+ required: True
+ id:
+ description:
+ - The identifier for the protocol specified.
+ type: str
+ route_map:
+ description:
+ - The route map policy to constrain redistribution.
+ type: str
+ required: True
+ summary_address:
+ description:
+ - Configure route summarization for redistribution.
+ type: list
+ elements: dict
+ suboptions:
+ prefix:
+ description:
+ - IPv6 prefix format 'xxxx:xxxx/ml', 'xxxx:xxxx::/ml' or 'xxxx::xx/128'
+ type: str
+ required: True
+ not_advertise:
+ description:
+ - Suppress advertising the specified summary.
+ type: bool
+ tag:
+ description:
+ - A 32-bit tag value.
+ type: int
+ table_map:
+ description:
+ - Policy for filtering/modifying OSPF routes before sending them to
+ RIB.
+ type: dict
+ suboptions:
+ name:
+ description:
+ - The Route Map name.
+ type: str
+ required: True
+ filter:
+ description:
+ - Block the OSPF routes from being sent to RIB.
+ type: bool
+ timers:
+ description:
+ - Configure timer related constants.
+ type: dict
+ suboptions:
+ throttle:
+ description:
+ - Configure throttle related constants.
+ type: dict
+ suboptions:
+ spf:
+ description:
+ - Set OSPF SPF timers.
+ type: dict
+ suboptions:
+ initial_spf_delay:
+ description:
+ - Initial SPF schedule delay in milliseconds.
+ type: int
+ min_hold_time:
+ description:
+ - Minimum hold time between SPF calculations.
+ type: int
+ max_wait_time:
+ description:
+ - Maximum wait time between SPF calculations.
+ type: int
+ areas:
+ description:
+ - Configure properties of OSPF Areas.
+ type: list
+ elements: dict
+ suboptions:
+ area_id:
+ description:
+ - The Area ID in IP Address format.
+ type: str
+ required: True
+ nssa:
+ description:
+ - NSSA settings for the area.
+ type: dict
+ suboptions:
+ set:
+ description:
+ - Configure area as NSSA.
+ type: bool
+ default_information_originate:
+ description:
+ - Originate Type-7 default LSA into NSSA area.
+ type: bool
+ no_redistribution:
+ description:
+ - Do not send redistributed LSAs into NSSA area.
+ type: bool
+ no_summary:
+ description:
+ - Do not send summary LSAs into NSSA area.
+ type: bool
+ route_map:
+ description:
+ - Policy to control distribution of default route.
+ type: str
+ translate:
+ description:
+ - Translate LSA.
+ type: dict
+ suboptions:
+ type7:
+ description:
+ - Translate from Type 7 to Type 5.
+ type: dict
+ suboptions:
+ always:
+ description:
+ - Always translate LSAs
+ type: bool
+ never:
+ description:
+ - Never translate LSAs
+ type: bool
+ supress_fa:
+ description:
+ - Suppress forwarding address in translated LSAs.
+ type: bool
+ stub:
+ description:
+ - Settings for configuring the area as a stub.
+ type: dict
+ suboptions:
+ set:
+ description:
+ - Configure the area as a stub.
+ type: bool
+ no_summary:
+ description:
+ - Prevent ABR from sending summary LSAs into stub area.
+ type: bool
+ auto_cost:
+ description:
+ - Calculate OSPF cost according to bandwidth.
+ type: dict
+ suboptions:
+ reference_bandwidth:
+ description:
+ - Reference bandwidth used to assign OSPF cost.
+ type: int
+ required: True
+ unit:
+ description:
+ - Specify in which unit the reference bandwidth is specified.
+ type: str
+ required: True
+ choices: [Gbps, Mbps]
+ flush_routes:
+ description:
+ - Flush routes on a non-graceful controlled restart.
+ type: bool
+ graceful_restart:
+ description:
+ - Configure graceful restart.
+ type: dict
+ suboptions:
+ set:
+ description:
+ - Enable graceful-restart.
+ type: bool
+ grace_period:
+ description:
+ - Configure maximum interval to restart gracefully.
+ type: int
+ helper_disable:
+ description:
+ - Enable/Disable helper mode.
+ type: bool
+ planned_only:
+ description:
+ - Enable graceful restart only for a planned restart
+ type: bool
+ isolate:
+ description:
+ - Isolate this router from OSPF perspective.
+ type: bool
+ log_adjacency_changes:
+ description:
+ - Log changes in adjacency state.
+ type: dict
+ suboptions:
+ log:
+ description:
+ - Enable/disable logging changes in adjacency state.
+ type: bool
+ detail:
+ description:
+ - Notify all state changes.
+ type: bool
+ max_lsa:
+ description:
+ - Feature to limit the number of non-self-originated LSAs.
+ type: dict
+ suboptions:
+ max_non_self_generated_lsa:
+ description:
+ - Set the maximum number of non self-generated LSAs.
+ type: int
+ required: True
+ threshold:
+ description:
+ - Threshold value (%) at which to generate a warning message.
+ type: int
+ ignore_count:
+ description:
+ - Set count on how many times adjacencies can be suppressed.
+ type: int
+ ignore_time:
+ description:
+ - Set time during which all adjacencies are suppressed.
+ type: int
+ reset_time:
+ description:
+ - Set number of minutes after which ignore-count is reset to zero.
+ type: int
+ warning_only:
+ description:
+ - Log a warning message when limit is exceeded.
+ type: bool
+ max_metric:
+ description:
+ - Maximize the cost metric.
+ type: dict
+ suboptions:
+ router_lsa:
+ description:
+ - Router LSA configuration.
+ type: dict
+ suboptions:
+ set:
+ description:
+ - Set router-lsa attribute.
+ type: bool
+ external_lsa:
+ description:
+ - External LSA configuration.
+ type: dict
+ suboptions:
+ set:
+ description:
+ - Set external-lsa attribute.
+ type: bool
+ max_metric_value:
+ description:
+ - Set max metric value for external LSAs.
+ type: int
+ stub_prefix_lsa:
+ description:
+ - Advertise Max metric for Stub links as well.
+ type: bool
+ on_startup:
+ description:
+ - Effective only at startup.
+ type: dict
+ suboptions:
+ set:
+ description:
+ - Set on-startup attribute.
+ type: bool
+ wait_period:
+ description:
+ - Wait period in seconds after startup.
+ type: int
+ wait_for_bgp_asn:
+ description:
+ - ASN of BGP to wait for.
+ type: int
+ inter_area_prefix_lsa:
+ description:
+ - Inter-area-prefix LSAs configuration.
+ type: dict
+ suboptions:
+ set:
+ description:
+ - Set summary-lsa attribute.
+ type: bool
+ max_metric_value:
+ description:
+ - Max metric value for summary LSAs.
+ type: int
+ name_lookup:
+ description:
+ - Display OSPF router ids as DNS names.
+ type: bool
+ passive_interface:
+ description:
+ - Suppress routing updates on the interface.
+ type: dict
+ suboptions:
+ default:
+ description:
+ - Interfaces passive by default.
+ type: bool
+ process_id:
+ description:
+ - The OSPF process tag.
+ type: str
+ required: True
+ router_id:
+ description:
+ - Set OSPF process router-id.
+ type: str
+ shutdown:
+ description:
+ - Shutdown the OSPF protocol instance.
+ type: bool
+ timers:
+ description:
+ - Configure timer related constants.
+ type: dict
+ suboptions:
+ lsa_arrival:
+ description:
+ - Mimimum interval between arrival of a LSA.
+ type: int
+ lsa_group_pacing:
+ description:
+ - LSA group refresh/maxage interval.
+ type: int
+ throttle:
+ description:
+ - Configure throttle related constants.
+ type: dict
+ suboptions:
+ lsa:
+ description:
+ - Set rate-limiting for LSA generation.
+ type: dict
+ suboptions:
+ start_interval:
+ description:
+ - The start interval.
+ type: int
+ hold_interval:
+ description:
+ - The hold interval.
+ type: int
+ max_interval:
+ description:
+ - The max interval.
+ type: int
+ vrfs:
+ description:
+ - Configure VRF specific OSPF settings.
+ type: list
+ elements: dict
+ suboptions:
+ areas:
+ description:
+ - Configure properties of OSPF Areas.
+ type: list
+ elements: dict
+ suboptions:
+ area_id:
+ description:
+ - The Area ID in IP Address format.
+ type: str
+ required: True
+ nssa:
+ description:
+ - NSSA settings for the area.
+ type: dict
+ suboptions:
+ set:
+ description:
+ - Configure area as NSSA.
+ type: bool
+ default_information_originate:
+ description:
+ - Originate Type-7 default LSA into NSSA area.
+ type: bool
+ no_redistribution:
+ description:
+ - Do not send redistributed LSAs into NSSA area.
+ type: bool
+ no_summary:
+ description:
+ - Do not send summary LSAs into NSSA area.
+ type: bool
+ route_map:
+ description:
+ - Policy to control distribution of default route.
+ type: str
+ translate:
+ description:
+ - Translate LSA.
+ type: dict
+ suboptions:
+ type7:
+ description:
+ - Translate from Type 7 to Type 5.
+ type: dict
+ suboptions:
+ always:
+ description:
+ - Always translate LSAs
+ type: bool
+ never:
+ description:
+ - Never translate LSAs
+ type: bool
+ supress_fa:
+ description:
+ - Suppress forwarding address in translated LSAs.
+ type: bool
+ stub:
+ description:
+ - Settings for configuring the area as a stub.
+ type: dict
+ suboptions:
+ set:
+ description:
+ - Configure the area as a stub.
+ type: bool
+ no_summary:
+ description:
+ - Prevent ABR from sending summary LSAs into stub area.
+ type: bool
+ auto_cost:
+ description:
+ - Calculate OSPF cost according to bandwidth.
+ type: dict
+ suboptions:
+ reference_bandwidth:
+ description:
+ - Reference bandwidth used to assign OSPF cost.
+ type: int
+ required: True
+ unit:
+ description:
+ - Specify in which unit the reference bandwidth is specified.
+ type: str
+ required: True
+ choices: [Gbps, Mbps]
+ graceful_restart:
+ description:
+ - Configure graceful restart.
+ type: dict
+ suboptions:
+ set:
+ description:
+ - Enable graceful-restart.
+ type: bool
+ grace_period:
+ description:
+ - Configure maximum interval to restart gracefully.
+ type: int
+ helper_disable:
+ description:
+ - Enable/Disable helper mode.
+ type: bool
+ planned_only:
+ description:
+ - Enable graceful restart only for a planned restart
+ type: bool
+ log_adjacency_changes:
+ description:
+ - Log changes in adjacency state.
+ type: dict
+ suboptions:
+ log:
+ description:
+ - Enable/disable logging changes in adjacency state.
+ type: bool
+ detail:
+ description:
+ - Notify all state changes.
+ type: bool
+ max_lsa:
+ description:
+ - Feature to limit the number of non-self-originated LSAs.
+ type: dict
+ suboptions:
+ max_non_self_generated_lsa:
+ description:
+ - Set the maximum number of non self-generated LSAs.
+ type: int
+ required: True
+ threshold:
+ description:
+ - Threshold value (%) at which to generate a warning message.
+ type: int
+ ignore_count:
+ description:
+ - Set count on how many times adjacencies can be suppressed.
+ type: int
+ ignore_time:
+ description:
+ - Set time during which all adjacencies are suppressed.
+ type: int
+ reset_time:
+ description:
+ - Set number of minutes after which ignore-count is reset to zero.
+ type: int
+ warning_only:
+ description:
+ - Log a warning message when limit is exceeded.
+ type: bool
+ max_metric:
+ description:
+ - Maximize the cost metric.
+ type: dict
+ suboptions:
+ router_lsa:
+ description:
+ - Router LSA configuration.
+ type: dict
+ suboptions:
+ set:
+ description:
+ - Set router-lsa attribute.
+ type: bool
+ external_lsa:
+ description:
+ - External LSA configuration.
+ type: dict
+ suboptions:
+ set:
+ description:
+ - Set external-lsa attribute.
+ type: bool
+ max_metric_value:
+ description:
+ - Set max metric value for external LSAs.
+ type: int
+ stub_prefix_lsa:
+ description:
+ - Advertise Max metric for Stub links as well.
+ type: bool
+ on_startup:
+ description:
+ - Effective only at startup.
+ type: dict
+ suboptions:
+ set:
+ description:
+ - Set on-startup attribute.
+ type: bool
+ wait_period:
+ description:
+ - Wait period in seconds after startup.
+ type: int
+ wait_for_bgp_asn:
+ description:
+ - ASN of BGP to wait for.
+ type: int
+ inter_area_prefix_lsa:
+ description:
+ - Inter-area-prefix LSAs configuration.
+ type: dict
+ suboptions:
+ set:
+ description:
+ - Set summary-lsa attribute.
+ type: bool
+ max_metric_value:
+ description:
+ - Max metric value for summary LSAs.
+ type: int
+ name_lookup:
+ description:
+ - Display OSPF router ids as DNS names.
+ type: bool
+ passive_interface:
+ description:
+ - Suppress routing updates on the interface.
+ type: dict
+ suboptions:
+ default:
+ description:
+ - Interfaces passive by default.
+ type: bool
+ router_id:
+ description:
+ - Set OSPF process router-id.
+ type: str
+ shutdown:
+ description:
+ - Shutdown the OSPF protocol instance.
+ type: bool
+ timers:
+ description:
+ - Configure timer related constants.
+ type: dict
+ suboptions:
+ lsa_arrival:
+ description:
+ - Mimimum interval between arrival of a LSA.
+ type: int
+ lsa_group_pacing:
+ description:
+ - LSA group refresh/maxage interval.
+ type: int
+ throttle:
+ description:
+ - Configure throttle related constants.
+ type: dict
+ suboptions:
+ lsa:
+ description:
+ - Set rate-limiting for LSA generation.
+ type: dict
+ suboptions:
+ start_interval:
+ description:
+ - The start interval.
+ type: int
+ hold_interval:
+ description:
+ - The hold interval.
+ type: int
+ max_interval:
+ description:
+ - The max interval.
+ type: int
+ vrf:
+ description:
+ - Name/Identifier of the VRF.
+ type: str
+ required: True
+ state:
+ description:
+ - The state the configuration should be left in.
+ type: str
+ choices:
+ - merged
+ - replaced
+ - overridden
+ - deleted
+ - gathered
+ - parsed
+ - rendered
+ default: merged
+"""
+EXAMPLES = """
+# Using merged
+
+# Before state:
+# -------------
+# nxos-9k-rdo# sh running-config | section "^router ospfv3"
+# nxos-9k-rdo#
+
+- name: Merge the provided configuration with the existing running configuration
+ cisco.nxos.nxos_ospfv3:
+ config:
+ processes:
+ - process_id: 100
+ router_id: 203.0.113.20
+ - process_id: 102
+ router_id: 198.51.100.1
+ address_family:
+ afi: ipv6
+ safi: unicast
+ areas:
+ - area_id: 0.0.0.100
+ filter_list:
+ - route_map: rmap_1
+ direction: in
+ - route_map: rmap_2
+ direction: out
+ ranges:
+ - prefix: 2001:db2::/32
+ not_advertise: true
+ - prefix: 2001:db3::/32
+ cost: 120
+ redistribute:
+ - protocol: eigrp
+ id: 120
+ route_map: rmap_1
+ - protocol: direct
+ route_map: ospf102-direct-connect
+ vrfs:
+ - vrf: zone1
+ router_id: 198.51.100.129
+ areas:
+ - area_id: 0.0.0.102
+ nssa:
+ default_information_originate: true
+ no_summary: true
+ - area_id: 0.0.0.103
+ nssa:
+ no_summary: true
+ translate:
+ type7:
+ always: true
+ - vrf: zone2
+ auto_cost:
+ reference_bandwidth: 45
+ unit: Gbps
+ state: merged
+
+# Task output
+# -------------
+# before: {}
+#
+# commands:
+# - router ospf 102
+# - router-id 198.51.100.1
+# - address-family ipv6 unicast
+# - redistribute eigrp 120 route-map rmap_1
+# - redistribute direct route-map ospf102-direct-connect
+# - area 0.0.0.100 filter-list route-map rmap_1 in
+# - area 0.0.0.100 filter-list route-map rmap_2 out
+# - area 0.0.0.100 range 2001:db2::/32 not-advertise
+# - area 0.0.0.100 range 2001:db3::/32 cost 120
+# - vrf zone1
+# - router-id 198.51.100.129
+# - area 0.0.0.102 nssa no-summary default-information-originate
+# - area 0.0.0.103 nssa no-summary
+# - area 0.0.0.103 nssa translate type7 always
+# - vrf zone2
+# - auto-cost reference-bandwidth 45 Gbps
+# - router ospf 100
+# - router-id 203.0.113.20
+#
+# after:
+# processes:
+# - process_id: "100"
+# router_id: 203.0.113.20
+# - address_family:
+# afi: ipv4
+# safi: unicast
+# areas:
+# - area_id: 0.0.0.100
+# filter_list:
+# - direction: out
+# route_map: rmap_2
+# - direction: in
+# route_map: rmap_1
+# ranges:
+# - not_advertise: true
+# prefix: 2001:db2::/32
+# - cost: 120
+# prefix: 2001:db3::/32
+# redistribute:
+# - protocol: direct
+# route_map: ospf102-direct-connect
+# - id: "120"
+# protocol: eigrp
+# route_map: rmap_1
+# process_id: "102"
+# router_id: 198.51.100.1
+# vrfs:
+# - areas:
+# - area_id: 0.0.0.102
+# nssa:
+# default_information_originate: true
+# no_summary: true
+# - area_id: 0.0.0.103
+# nssa:
+# no_summary: true
+# translate:
+# type7:
+# always: true
+# router_id: 198.51.100.129
+# vrf: zone1
+# - auto_cost:
+# reference_bandwidth: 45
+# unit: Gbps
+# vrf: zone2
+#
+
+# After state:
+# ------------
+# nxos-9k-rdo# sh running-config | section "^router ospfv3"
+# router ospfv3 100
+# router-id 203.0.113.20
+# router ospfv3 102
+# router-id 198.51.100.1
+# address-family ipv6 unicast
+# redistribute direct route-map ospf102-direct-connect
+# redistribute eigrp 120 route-map rmap_1
+# area 0.0.0.100 filter-list route-map rmap_2 out
+# area 0.0.0.100 filter-list route-map rmap_1 in
+# area 0.0.0.100 range 2001:db2::/32 not-advertise
+# area 0.0.0.100 range 2001:db3::/32 cost 120
+# vrf zone1
+# router-id 198.51.100.129
+# area 0.0.0.102 nssa no-summary default-information-originate
+# area 0.0.0.103 nssa no-summary
+# area 0.0.0.103 nssa translate type7 always
+# vrf zone2
+# auto-cost reference-bandwidth 45 Gbps
+
+# Using replaced
+
+# Before state:
+# ------------
+# nxos-9k-rdo# sh running-config | section "^router ospfv3"
+# router ospfv3 100
+# router-id 203.0.113.20
+# router ospfv3 102
+# router-id 198.51.100.1
+# address-family upv6 unicast
+# redistribute direct route-map ospf102-direct-connect
+# redistribute eigrp 120 route-map rmap_1
+# area 0.0.0.100 filter-list route-map rmap_2 out
+# area 0.0.0.100 filter-list route-map rmap_1 in
+# area 0.0.0.100 range 2001:db2::/32 not-advertise
+# area 0.0.0.100 range 2001:db3::/32 cost 120
+# vrf zone1
+# router-id 198.51.100.129
+# area 0.0.0.102 nssa no-summary default-information-originate
+# area 0.0.0.103 nssa no-summary
+# area 0.0.0.103 nssa translate type7 always
+# vrf zone2
+# auto-cost reference-bandwidth 45 Gbps
+
+- name: Replace device configurations of listed OSPFv3 processes with provided configurations
+ cisco.nxos.nxos_ospfv3:
+ config:
+ processes:
+ - process_id: 102
+ router_id: 198.51.100.1
+ address_family:
+ afi: ipv6
+ safi: unicast
+ areas:
+ - area_id: 0.0.0.100
+ filter_list:
+ - route_map: rmap_8
+ direction: in
+ ranges:
+ - not_advertise: true
+ prefix: 2001:db2::/32
+ redistribute:
+ - protocol: eigrp
+ id: 130
+ route_map: rmap_1
+ - protocol: direct
+ route_map: ospf102-direct-connect
+ vrfs:
+ - vrf: zone1
+ router_id: 198.51.100.129
+ areas:
+ - area_id: 0.0.0.102
+ nssa:
+ default_information_originate: True
+ no_summary: True
+ state: replaced
+
+# Task output
+# -------------
+# before:
+# processes:
+# - process_id: "100"
+# router_id: 203.0.113.20
+# - address_family:
+# afi: ipv4
+# safi: unicast
+# areas:
+# - area_id: 0.0.0.100
+# filter_list:
+# - direction: out
+# route_map: rmap_2
+# - direction: in
+# route_map: rmap_1
+# ranges:
+# - not_advertise: true
+# prefix: 2001:db2::/32
+# - cost: 120
+# prefix: 2001:db3::/32
+# redistribute:
+# - protocol: direct
+# route_map: ospf102-direct-connect
+# - id: "120"
+# protocol: eigrp
+# route_map: rmap_1
+# process_id: "102"
+# router_id: 198.51.100.1
+# vrfs:
+# - areas:
+# - area_id: 0.0.0.102
+# nssa:
+# default_information_originate: true
+# no_summary: true
+# - area_id: 0.0.0.103
+# nssa:
+# no_summary: true
+# translate:
+# type7:
+# always: true
+# router_id: 198.51.100.129
+# vrf: zone1
+# - auto_cost:
+# reference_bandwidth: 45
+# unit: Gbps
+# vrf: zone2
+#
+# commands:
+# - router ospf 102
+# - address-family ipv6 unicast
+# - redistribute eigrp 130 route-map rmap_1
+# - no redistribute eigrp 120 route-map rmap_1
+# - area 0.0.0.100 filter-list route-map rmap_8 in
+# - no area 0.0.0.100 filter-list route-map rmap_2 out
+# - no area 0.0.0.100 range 2001:db3::/32
+# - vrf zone1
+# - no area 0.0.0.103 nssa
+# - no area 0.0.0.103 nssa translate type7 always
+# - no vrf zone2
+#
+# after:
+# processes:
+# - process_id: "100"
+# router_id: 203.0.113.20
+# - address_family:
+# afi: ipv6
+# safi: unicast
+# areas:
+# - area_id: 0.0.0.100
+# filter_list:
+# - direction: in
+# route_map: rmap_8
+# ranges:
+# - not_advertise: true
+# prefix: 2001:db2::/32
+# redistribute:
+# - protocol: direct
+# route_map: ospf102-direct-connect
+# - id: "130"
+# protocol: eigrp
+# route_map: rmap_1
+# process_id: "102"
+# router_id: 198.51.100.1
+# vrfs:
+# - areas:
+# - area_id: 0.0.0.102
+# nssa:
+# default_information_originate: true
+# no_summary: true
+# router_id: 198.51.100.129
+# vrf: zone1
+
+# After state:
+# ------------
+# nxos-9k-rdo# sh running-config | section "^router ospfv3"
+# router ospfv3 100
+# router-id 203.0.113.20
+# router ospfv3 102
+# router-id 198.51.100.1
+# address-family ipv6 unicast
+# redistribute direct route-map ospf102-direct-connect
+# redistribute eigrp 130 route-map rmap_1
+# area 0.0.0.100 filter-list route-map rmap_8 in
+# area 0.0.0.100 range 198.51.100.64/27 not-advertise
+# vrf zone1
+# router-id 198.51.100.129
+# area 0.0.0.102 nssa no-summary default-information-originate
+
+# Using overridden
+
+# Before state:
+# ------------
+# nxos-9k-rdo# sh running-config | section "^router ospfv3"
+# router ospfv3 100
+# router-id 203.0.113.20
+# router ospfv3 102
+# router-id 198.51.100.1
+# address-family ipv6 unicast
+# redistribute direct route-map ospf102-direct-connect
+# redistribute eigrp 120 route-map rmap_1
+# area 0.0.0.100 filter-list route-map rmap_2 out
+# area 0.0.0.100 filter-list route-map rmap_1 in
+# area 0.0.0.100 range 2001:db2::/32 not-advertise
+# area 0.0.0.100 range 2001:db3::/32 cost 120
+# vrf zone1
+# router-id 198.51.100.129
+# area 0.0.0.102 nssa no-summary default-information-originate
+# area 0.0.0.103 nssa no-summary
+# area 0.0.0.103 nssa translate type7 always
+# vrf zone2
+# auto-cost reference-bandwidth 45 Gbps
+
+- name: Override all OSPFv3 configuration with provided configuration
+ cisco.nxos.nxos_ospfv3:
+ config:
+ processes:
+ - process_id: 104
+ router_id: 203.0.113.20
+ - process_id: 102
+ router_id: 198.51.100.1
+ shutdown: true
+ state: overridden
+
+# Task output
+# -------------
+# before:
+# processes:
+# - process_id: "100"
+# router_id: 203.0.113.20
+# - address_family:
+# afi: ipv4
+# safi: unicast
+# areas:
+# - area_id: 0.0.0.100
+# filter_list:
+# - direction: out
+# route_map: rmap_2
+# - direction: in
+# route_map: rmap_1
+# ranges:
+# - not_advertise: true
+# prefix: 2001:db2::/32
+# - cost: 120
+# prefix: 2001:db3::/32
+# redistribute:
+# - protocol: direct
+# route_map: ospf102-direct-connect
+# - id: "120"
+# protocol: eigrp
+# route_map: rmap_1
+# process_id: "102"
+# router_id: 198.51.100.1
+# vrfs:
+# - areas:
+# - area_id: 0.0.0.102
+# nssa:
+# default_information_originate: true
+# no_summary: true
+# - area_id: 0.0.0.103
+# nssa:
+# no_summary: true
+# translate:
+# type7:
+# always: true
+# router_id: 198.51.100.129
+# vrf: zone1
+# - auto_cost:
+# reference_bandwidth: 45
+# unit: Gbps
+# vrf: zone2
+#
+# commands:
+# - no router ospfv3 100
+# - router ospfv3 104
+# - router-id 203.0.113.20
+# - router ospfv3 102
+# - shutdown
+# - address-family ipv6 unicast
+# - no redistribute direct route-map ospf102-direct-connect
+# - no redistribute eigrp 120 route-map rmap_1
+# - no area 0.0.0.100 filter-list route-map rmap_2 out
+# - no area 0.0.0.100 filter-list route-map rmap_1 in
+# - no area 0.0.0.100 range 2001:db2::/32
+# - no area 0.0.0.100 range 2001:db3::/32
+# - no vrf zone1
+# - no vrf zone2
+#
+# after:
+# processes:
+# - process_id: "102"
+# router_id: 198.51.100.1
+# shutdown: true
+# address_family:
+# afi: ipv6
+# safi: unicast
+# - process_id: "104"
+# router_id: 203.0.113.20
+
+# After state:
+# ------------
+# nxos-9k-rdo# sh running-config | section "^router ospfv3"
+# router ospfv3 102
+# router-id 198.51.100.1
+# address-family ipv6 unicast
+# shutdown
+# router ospfv3 104
+# router-id 203.0.113.20
+
+# Using deleted to delete a single OSPF process
+
+# Before state:
+# ------------
+# nxos-9k-rdo# sh running-config | section "^router ospf .*"
+# router ospfv3 100
+# router-id 203.0.113.20
+# router ospfv3 102
+# router-id 198.51.100.1
+# address-family ipv6 unicast
+# redistribute direct route-map ospf102-direct-connect
+# redistribute eigrp 120 route-map rmap_1
+# area 0.0.0.100 filter-list route-map rmap_2 out
+# area 0.0.0.100 filter-list route-map rmap_1 in
+# area 0.0.0.100 range 2001:db2::/32 not-advertise
+# area 0.0.0.100 range 2001:db3::/32 cost 120
+# vrf zone1
+# router-id 198.51.100.129
+# area 0.0.0.102 nssa no-summary default-information-originate
+# area 0.0.0.103 nssa no-summary
+# area 0.0.0.103 nssa translate type7 always
+# vrf zone2
+# auto-cost reference-bandwidth 45 Gbps
+
+- name: Delete a single OSPFv3 process
+ cisco.nxos.nxos_ospfv3:
+ config:
+ processes:
+ - process_id: 102
+ state: deleted
+
+# Task output
+# -------------
+# before:
+# processes:
+# - process_id: "100"
+# router_id: 203.0.113.20
+# - address_family:
+# afi: ipv4
+# safi: unicast
+# areas:
+# - area_id: 0.0.0.100
+# filter_list:
+# - direction: out
+# route_map: rmap_2
+# - direction: in
+# route_map: rmap_1
+# ranges:
+# - not_advertise: true
+# prefix: 2001:db2::/32
+# - cost: 120
+# prefix: 2001:db3::/32
+# redistribute:
+# - protocol: direct
+# route_map: ospf102-direct-connect
+# - id: "120"
+# protocol: eigrp
+# route_map: rmap_1
+# process_id: "102"
+# router_id: 198.51.100.1
+# vrfs:
+# - areas:
+# - area_id: 0.0.0.102
+# nssa:
+# default_information_originate: true
+# no_summary: true
+# - area_id: 0.0.0.103
+# nssa:
+# no_summary: true
+# translate:
+# type7:
+# always: true
+# router_id: 198.51.100.129
+# vrf: zone1
+# - auto_cost:
+# reference_bandwidth: 45
+# unit: Gbps
+# vrf: zone2
+#
+# commands:
+# - no router ospfv3 102
+#
+# after:
+# processes:
+# - process_id: "100"
+# router_id: 203.0.113.20
+
+# After state:
+# ------------
+# nxos-9k-rdo# sh running-config | section "^router ospfv3"
+# router ospfv3 100
+# router-id 203.0.113.20
+
+# Using deleted all OSPFv3 processes from the device
+
+# Before state:
+# ------------
+# nxos-9k-rdo# sh running-config | section "^router ospfv3"
+# router ospfv3 100
+# router-id 203.0.113.20
+# router ospfv3 102
+# router-id 198.51.100.1
+# address-family ipv6 unicast
+# redistribute direct route-map ospf102-direct-connect
+# redistribute eigrp 120 route-map rmap_1
+# area 0.0.0.100 filter-list route-map rmap_2 out
+# area 0.0.0.100 filter-list route-map rmap_1 in
+# area 0.0.0.100 range 2001:db2::/32 not-advertise
+# area 0.0.0.100 range 2001:db3::/32 cost 120
+# vrf zone1
+# router-id 198.51.100.129
+# area 0.0.0.102 nssa no-summary default-information-originate
+# area 0.0.0.103 nssa no-summary
+# area 0.0.0.103 nssa translate type7 always
+# vrf zone2
+# auto-cost reference-bandwidth 45 Gbps
+
+- name: Delete all OSPFv3 processes from the device
+ cisco.nxos.nxos_ospfv3:
+ state: deleted
+
+# Task output
+# -------------
+# before:
+# processes:
+# - process_id: "100"
+# router_id: 203.0.113.20
+# - address_family:
+# afi: ipv4
+# safi: unicast
+# areas:
+# - area_id: 0.0.0.100
+# filter_list:
+# - direction: out
+# route_map: rmap_2
+# - direction: in
+# route_map: rmap_1
+# ranges:
+# - not_advertise: true
+# prefix: 2001:db2::/32
+# - cost: 120
+# prefix: 2001:db3::/32
+# redistribute:
+# - protocol: direct
+# route_map: ospf102-direct-connect
+# - id: "120"
+# protocol: eigrp
+# route_map: rmap_1
+# process_id: "102"
+# router_id: 198.51.100.1
+# vrfs:
+# - areas:
+# - area_id: 0.0.0.102
+# nssa:
+# default_information_originate: true
+# no_summary: true
+# - area_id: 0.0.0.103
+# nssa:
+# no_summary: true
+# translate:
+# type7:
+# always: true
+# router_id: 198.51.100.129
+# vrf: zone1
+# - auto_cost:
+# reference_bandwidth: 45
+# unit: Gbps
+# vrf: zone2
+#
+# commands:
+# - no router ospfv3 100
+# - no router ospfv3 102
+#
+# after: {}
+
+# After state:
+# ------------
+# nxos-9k-rdo# sh running-config | section "^router ospfv3"
+# nxos-9k-rdo#
+
+# Using rendered
+
+- name: Render platform specific configuration lines with state rendered (without connecting to the device)
+ cisco.nxos.nxos_ospfv3:
+ config:
+ processes:
+ - process_id: 100
+ router_id: 203.0.113.20
+ - process_id: 102
+ router_id: 198.51.100.1
+ address_family:
+ afi: ipv6
+ safi: unicast
+ areas:
+ - area_id: 0.0.0.100
+ filter_list:
+ - route_map: rmap_1
+ direction: in
+ - route_map: rmap_2
+ direction: out
+ ranges:
+ - prefix: 2001:db2::/32
+ not_advertise: true
+ - prefix: 2001:db3::/32
+ cost: 120
+ redistribute:
+ - protocol: eigrp
+ id: 120
+ route_map: rmap_1
+ - protocol: direct
+ route_map: ospf102-direct-connect
+ vrfs:
+ - vrf: zone1
+ router_id: 198.51.100.129
+ areas:
+ - area_id: 0.0.0.102
+ nssa:
+ default_information_originate: true
+ no_summary: true
+ - area_id: 0.0.0.103
+ nssa:
+ no_summary: true
+ translate:
+ type7:
+ always: true
+ - vrf: zone2
+ auto_cost:
+ reference_bandwidth: 45
+ unit: Gbps
+ state: rendered
+
+# Task Output (redacted)
+# -----------------------
+# rendered:
+# - router ospfv3 100
+# - router-id 203.0.113.20
+# - router ospfv3 102
+# - router-id 198.51.100.1
+# - address-family ipv6 unicast
+# - redistribute eigrp 120 route-map rmap_1
+# - redistribute direct route-map ospf102-direct-connect
+# - area 0.0.0.100 filter-list route-map rmap_1 in
+# - area 0.0.0.100 filter-list route-map rmap_2 out
+# - area 0.0.0.100 range 2001:db2::/32 not-advertise
+# - area 0.0.0.100 range 2001:db3::/32 cost 120
+# - vrf zone1
+# - router-id 198.51.100.129
+# - area 0.0.0.102 nssa no-summary default-information-originate
+# - area 0.0.0.103 nssa no-summary
+# - area 0.0.0.103 nssa translate type7 always
+# - vrf zone2
+# - auto-cost reference-bandwidth 45 Gbps
+
+# Using parsed
+
+# parsed.cfg
+# ------------
+# router ospfv3 100
+# router-id 192.0.100.1
+# address-family ipv6 unicast
+# redistribute direct route-map ospf-direct-connect
+# redistribute eigrp 120 route-map rmap_1
+# area 0.0.0.100 filter-list route-map rmap_2 out
+# area 0.0.0.100 filter-list route-map rmap_1 in
+# area 0.0.0.100 range 2001:db2::/32 not-advertise
+# area 0.0.0.100 range 2001:db3::/32 cost 120
+# vrf zone1
+# router-id 198.51.100.129
+# area 0.0.100.1 nssa no-summary no-redistribution
+# router ospfv3 102
+# router-id 198.54.100.1
+# shutdown
+
+- name: Parse externally provided OSPFv3 config
+ cisco.nxos.nxos_ospfv3:
+ running_config: "{{ lookup('file', 'ospfv2.cfg') }}"
+ state: parsed
+
+# Task output (redacted)
+# -----------------------
+# parsed:
+# processes:
+# - process_id: "100"
+# address_family:
+# afi: ipv6
+# safi: unicast
+# areas:
+# - area_id: 0.0.0.101
+# nssa:
+# no_redistribution: true
+# no_summary: true
+# - area_id: 0.0.0.102
+# stub:
+# no_summary: true
+# filter_list:
+# - direction: out
+# route_map: rmap_2
+# - direction: in
+# route_map: rmap_1
+# ranges:
+# - not_advertise: true
+# prefix: 192.0.2.0/24
+# - cost: 120
+# prefix: 192.0.3.0/24
+# redistribute:
+# - protocol: direct
+# route_map: ospf-direct-connect
+# - id: "120"
+# protocol: eigrp
+# route_map: rmap_1
+# router_id: 192.0.100.1
+# vrfs:
+# - vrf: zone1
+# areas:
+# - area_id: 0.0.100.1
+# nssa:
+# no_redistribution: true
+# no_summary: true
+# router_id: 192.0.100.2
+# - process_id: "102"
+# router_id: 198.54.100.1
+# shutdown: True
+
+# Using gathered
+
+- name: Gather OSPFv3 facts using gathered
+ cisco.nxos.nxos_ospfv3:
+ state: gathered
+
+# Task output (redacted)
+# -----------------------
+# gathered:
+# processes:
+# - process_id: "100"
+# router_id: 203.0.113.20
+# - address_family:
+# afi: ipv4
+# safi: unicast
+# areas:
+# - area_id: 0.0.0.100
+# filter_list:
+# - direction: out
+# route_map: rmap_2
+# - direction: in
+# route_map: rmap_1
+# ranges:
+# - not_advertise: true
+# prefix: 2001:db2::/32
+# - cost: 120
+# prefix: 2001:db3::/32
+# redistribute:
+# - protocol: direct
+# route_map: ospf102-direct-connect
+# - id: "120"
+# protocol: eigrp
+# route_map: rmap_1
+# process_id: "102"
+# router_id: 198.51.100.1
+# vrfs:
+# - areas:
+# - area_id: 0.0.0.102
+# nssa:
+# default_information_originate: true
+# no_summary: true
+# - area_id: 0.0.0.103
+# nssa:
+# no_summary: true
+# translate:
+# type7:
+# always: true
+# router_id: 198.51.100.129
+# vrf: zone1
+# - auto_cost:
+# reference_bandwidth: 45
+# unit: Gbps
+# vrf: zone2
+#
+"""
+RETURN = """
+before:
+ description: The configuration prior to the model invocation.
+ returned: always
+ type: dict
+ 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: dict
+ 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 ospfv3 102"
+ - "router-id 198.54.100.1"
+ - "router ospfv3 100"
+ - "router-id 192.0.100.1"
+ - "address-family ipv6 unicast"
+ - "redistribute eigrp 120 route-map rmap_1"
+ - "redistribute direct route-map ospf-direct-connect"
+ - "area 0.0.0.100 filter-list route-map rmap_1 in"
+ - "area 0.0.0.100 filter-list route-map rmap_2 out"
+ - "area 0.0.0.100 range 2001:db2::/32 not-advertise"
+ - "area 0.0.0.100 range 2001:db3::/32 cost 120"
+ - "vrf zone1"
+ - "router-id 192.0.100.2"
+ - "vrf zone2"
+ - "auto-cost reference-bandwidth 45 Gbps"
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.ospfv3.ospfv3 import (
+ Ospfv3Args,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.config.ospfv3.ospfv3 import (
+ Ospfv3,
+)
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ module = AnsibleModule(
+ argument_spec=Ospfv3Args.argument_spec,
+ mutually_exclusive=[["config", "running_config"]],
+ required_if=[
+ ["state", "merged", ["config"]],
+ ["state", "replaced", ["config"]],
+ ["state", "overridden", ["config"]],
+ ["state", "rendered", ["config"]],
+ ["state", "parsed", ["running_config"]],
+ ],
+ supports_check_mode=True,
+ )
+
+ result = Ospfv3(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_overlay_global.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_overlay_global.py
new file mode 100644
index 00000000..70b25246
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_overlay_global.py
@@ -0,0 +1,194 @@
+#!/usr/bin/python
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_overlay_global
+extends_documentation_fragment:
+- cisco.nxos.nxos
+short_description: Configures anycast gateway MAC of the switch.
+description:
+- Configures anycast gateway MAC of the switch.
+version_added: 1.0.0
+author: Gabriele Gerbino (@GGabriele)
+notes:
+- Tested against NXOSv 7.3.(0)D1(1) on VIRL
+- Unsupported for Cisco MDS
+- Default restores params default value
+- Supported MAC address format are "E.E.E", "EE-EE-EE-EE-EE-EE", "EE:EE:EE:EE:EE:EE"
+ and "EEEE.EEEE.EEEE"
+options:
+ anycast_gateway_mac:
+ description:
+ - Anycast gateway mac of the switch.
+ required: true
+ type: str
+"""
+
+EXAMPLES = """
+- cisco.nxos.nxos_overlay_global:
+ anycast_gateway_mac: b.b.b
+"""
+
+RETURN = """
+commands:
+ description: commands sent to the device
+ returned: always
+ type: list
+ sample: ["fabric forwarding anycast-gateway-mac 000B.000B.000B"]
+"""
+
+import re
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import (
+ CustomNetworkConfig,
+)
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ get_config,
+ load_config,
+)
+
+
+PARAM_TO_COMMAND_KEYMAP = {"anycast_gateway_mac": "fabric forwarding anycast-gateway-mac"}
+
+
+def get_existing(module, args):
+ existing = {}
+ config = str(get_config(module))
+
+ for arg in args:
+ command = PARAM_TO_COMMAND_KEYMAP[arg]
+ has_command = re.findall(r"(?:{0}\s)(?P<value>.*)$".format(command), config, re.M)
+ value = ""
+ if has_command:
+ value = has_command[0]
+ existing[arg] = value
+
+ return existing
+
+
+def apply_key_map(key_map, table):
+ new_dict = {}
+ for key, value in table.items():
+ new_key = key_map.get(key)
+ if value:
+ new_dict[new_key] = value
+ return new_dict
+
+
+def get_commands(module, existing, proposed, candidate):
+ commands = list()
+ proposed_commands = apply_key_map(PARAM_TO_COMMAND_KEYMAP, proposed)
+ existing_commands = apply_key_map(PARAM_TO_COMMAND_KEYMAP, existing)
+
+ for key, proposed in proposed_commands.items():
+ existing_value = existing_commands.get(key)
+ if proposed == "default" and existing_value:
+ commands.append("no {0} {1}".format(key, existing_value))
+ elif "anycast-gateway-mac" in key and proposed != "default":
+ proposed = normalize_mac(proposed, module)
+ existing_value = normalize_mac(existing_value, module)
+ if proposed != existing_value:
+ command = "{0} {1}".format(key, proposed)
+ commands.append(command)
+ if commands:
+ candidate.add(commands, parents=[])
+
+
+def normalize_mac(proposed_mac, module):
+ if proposed_mac is None:
+ return ""
+ try:
+ if "-" in proposed_mac:
+ splitted_mac = proposed_mac.split("-")
+ if len(splitted_mac) != 6:
+ raise ValueError
+
+ for octect in splitted_mac:
+ if len(octect) != 2:
+ raise ValueError
+
+ elif "." in proposed_mac:
+ splitted_mac = []
+ splitted_dot_mac = proposed_mac.split(".")
+ if len(splitted_dot_mac) != 3:
+ raise ValueError
+
+ for octect in splitted_dot_mac:
+ if len(octect) > 4:
+ raise ValueError
+ else:
+ octect_len = len(octect)
+ padding = 4 - octect_len
+ splitted_mac.append(octect.zfill(padding + 1))
+
+ elif ":" in proposed_mac:
+ splitted_mac = proposed_mac.split(":")
+ if len(splitted_mac) != 6:
+ raise ValueError
+
+ for octect in splitted_mac:
+ if len(octect) != 2:
+ raise ValueError
+ else:
+ raise ValueError
+ except ValueError:
+ module.fail_json(msg="Invalid MAC address format", proposed_mac=proposed_mac)
+
+ joined_mac = "".join(splitted_mac)
+ # fmt: off
+ mac = [joined_mac[i: i + 4] for i in range(0, len(joined_mac), 4)]
+ # fmt: on
+ return ".".join(mac).upper()
+
+
+def main():
+ argument_spec = dict(anycast_gateway_mac=dict(required=True, type="str"))
+
+ module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)
+
+ warnings = list()
+ result = {"changed": False, "commands": [], "warnings": warnings}
+
+ args = PARAM_TO_COMMAND_KEYMAP.keys()
+
+ existing = get_existing(module, args)
+ proposed = dict((k, v) for k, v in module.params.items() if v is not None and k in args)
+
+ candidate = CustomNetworkConfig(indent=3)
+ get_commands(module, existing, proposed, candidate)
+
+ if candidate:
+ candidate = candidate.items_text()
+ result["commands"] = candidate
+
+ if not module.check_mode:
+ load_config(module, candidate)
+ result["changed"] = True
+
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_pim.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_pim.py
new file mode 100644
index 00000000..d9fc6f46
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_pim.py
@@ -0,0 +1,216 @@
+#!/usr/bin/python
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_pim
+extends_documentation_fragment:
+- cisco.nxos.nxos
+short_description: Manages configuration of a PIM instance.
+description:
+- Manages configuration of a Protocol Independent Multicast (PIM) instance.
+notes:
+- Unsupported for Cisco MDS
+version_added: 1.0.0
+author: Gabriele Gerbino (@GGabriele)
+options:
+ bfd:
+ description:
+ - Enables BFD on all PIM interfaces.
+ - "Dependency: ''feature bfd''"
+ type: str
+ choices:
+ - enable
+ - disable
+ ssm_range:
+ description:
+ - Configure group ranges for Source Specific Multicast (SSM). Valid values are
+ multicast addresses or the keyword C(none) or keyword C(default). C(none) removes
+ all SSM group ranges. C(default) will set ssm_range to the default multicast
+ address. If you set multicast address, please ensure that it is not the same
+ as the C(default), otherwise use the C(default) option.
+ type: list
+ default: []
+ elements: str
+"""
+EXAMPLES = """
+- name: Configure ssm_range, enable bfd
+ cisco.nxos.nxos_pim:
+ bfd: enable
+ ssm_range: 224.0.0.0/8
+
+- name: Set to default
+ cisco.nxos.nxos_pim:
+ ssm_range: default
+
+- name: Remove all ssm group ranges
+ cisco.nxos.nxos_pim:
+ ssm_range: none
+"""
+
+RETURN = """
+commands:
+ description: commands sent to the device
+ returned: always
+ type: list
+ sample:
+ - ip pim bfd
+ - ip pim ssm range 224.0.0.0/8
+"""
+
+
+import re
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import (
+ CustomNetworkConfig,
+)
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ get_config,
+ load_config,
+)
+
+
+PARAM_TO_COMMAND_KEYMAP = {
+ "bfd": "ip pim bfd",
+ "ssm_range": "ip pim ssm range",
+}
+
+
+def get_existing(module, args):
+ existing = {}
+ config = str(get_config(module))
+
+ for arg in args:
+ if "ssm_range" in arg:
+ # <value> may be 'n.n.n.n/s', 'none', or 'default'
+ m = re.search(
+ r"ssm range (?P<value>(?:[\s\d.\/]+|none|default))?$",
+ config,
+ re.M,
+ )
+ if m:
+ # Remove rsvd SSM range
+ value = m.group("value").replace("232.0.0.0/8", "")
+ existing[arg] = value.split()
+
+ elif "bfd" in arg and "ip pim bfd" in config:
+ existing[arg] = "enable"
+
+ return existing
+
+
+def apply_key_map(key_map, table):
+ new_dict = {}
+ for key, value in table.items():
+ new_key = key_map.get(key)
+ if value is not None:
+ new_dict[new_key] = value
+ return new_dict
+
+
+def get_commands(module, existing, proposed, candidate):
+ commands = list()
+ proposed_commands = apply_key_map(PARAM_TO_COMMAND_KEYMAP, proposed)
+
+ for key, value in proposed_commands.items():
+ command = ""
+ if key == "ip pim ssm range":
+ if value == "default":
+ # no cmd needs a value but the actual value does not matter
+ command = "no ip pim ssm range none"
+ elif value == "none":
+ command = "ip pim ssm range none"
+ elif value:
+ command = "ip pim ssm range {0}".format(value)
+ elif key == "ip pim bfd":
+ no_cmd = "no " if value == "disable" else ""
+ command = no_cmd + key
+
+ if command:
+ commands.append(command)
+
+ if commands:
+ candidate.add(commands, parents=[])
+
+
+def main():
+ argument_spec = dict(
+ bfd=dict(required=False, type="str", choices=["enable", "disable"]),
+ ssm_range=dict(required=False, type="list", default=[], elements="str"),
+ )
+
+ module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)
+ warnings = list()
+ result = {"changed": False, "commands": [], "warnings": warnings}
+
+ params = module.params
+ args = [k for k in PARAM_TO_COMMAND_KEYMAP.keys() if params[k] is not None]
+
+ # SSM syntax check
+ if "ssm_range" in args:
+ for item in params["ssm_range"]:
+ if re.search("none|default", item):
+ break
+ if len(item.split(".")) != 4:
+ module.fail_json(
+ msg="Valid ssm_range values are multicast addresses "
+ "or the keyword 'none' or the keyword 'default'.",
+ )
+
+ existing = get_existing(module, args)
+ proposed_args = dict((k, v) for k, v in params.items() if k in args)
+
+ proposed = {}
+ for key, value in proposed_args.items():
+ if key == "ssm_range":
+ if value and value[0] == "default":
+ if existing.get(key):
+ proposed[key] = "default"
+ else:
+ v = sorted(set([str(i) for i in value]))
+ ex = sorted(set([str(i) for i in existing.get(key, [])]))
+ if v != ex:
+ proposed[key] = " ".join(str(s) for s in v)
+
+ elif key == "bfd":
+ if value != existing.get("bfd", "disable"):
+ proposed[key] = value
+
+ elif value != existing.get(key):
+ proposed[key] = value
+
+ candidate = CustomNetworkConfig(indent=3)
+ get_commands(module, existing, proposed, candidate)
+
+ if candidate:
+ candidate = candidate.items_text()
+ result["commands"] = candidate
+ result["changed"] = True
+ load_config(module, candidate)
+
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_pim_interface.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_pim_interface.py
new file mode 100644
index 00000000..4d0f1052
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_pim_interface.py
@@ -0,0 +1,604 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+
+DOCUMENTATION = """
+module: nxos_pim_interface
+extends_documentation_fragment:
+- cisco.nxos.nxos
+short_description: Manages PIM interface configuration.
+description:
+- Manages PIM interface configuration settings.
+version_added: 1.0.0
+author:
+- Jason Edelman (@jedelman8)
+notes:
+- Tested against NXOSv 7.3.(0)D1(1) on VIRL
+- Unsupported for Cisco MDS
+- When C(state=default), supported params will be reset to a default state. These
+ include C(dr_prio), C(hello_auth_key), C(hello_interval), C(jp_policy_out), C(jp_policy_in),
+ C(jp_type_in), C(jp_type_out), C(border), C(neighbor_policy), C(neighbor_type).
+- The C(hello_auth_key) param is not idempotent.
+- C(hello_auth_key) only supports clear text passwords.
+- When C(state=absent), pim interface configuration will be set to defaults and pim-sm
+ will be disabled on the interface.
+- PIM must be enabled on the device to use this module.
+- This module is for Layer 3 interfaces.
+options:
+ interface:
+ description:
+ - Full name of the interface such as Ethernet1/33.
+ type: str
+ required: true
+ sparse:
+ description:
+ - Enable/disable sparse-mode on the interface.
+ type: bool
+ default: false
+ bfd:
+ description:
+ - Enables BFD for PIM at the interface level. This overrides the bfd variable
+ set at the pim global level.
+ - Valid values are 'enable', 'disable' or 'default'.
+ - "Dependency: ''feature bfd''"
+ type: str
+ choices:
+ - enable
+ - disable
+ - default
+ dr_prio:
+ description:
+ - Configures priority for PIM DR election on interface.
+ type: str
+ hello_auth_key:
+ description:
+ - Authentication for hellos on this interface.
+ type: str
+ hello_interval:
+ description:
+ - Hello interval in milliseconds or seconds for this interface.
+ - Use the option I(hello_interval_ms) to specify if the given value is in
+ milliseconds or seconds. The default is seconds.
+ type: int
+ hello_interval_ms:
+ description:
+ - Specifies that the hello_interval is in milliseconds.
+ - When set to True, this indicates that the user is providing the
+ hello_interval in milliseconds and hence, no conversion is required.
+ type: bool
+ version_added: 2.0.0
+ jp_policy_out:
+ description:
+ - Policy for join-prune messages (outbound).
+ type: str
+ jp_policy_in:
+ description:
+ - Policy for join-prune messages (inbound).
+ type: str
+ jp_type_out:
+ description:
+ - Type of policy mapped to C(jp_policy_out).
+ type: str
+ choices:
+ - prefix
+ - routemap
+ jp_type_in:
+ description:
+ - Type of policy mapped to C(jp_policy_in).
+ type: str
+ choices:
+ - prefix
+ - routemap
+ border:
+ description:
+ - Configures interface to be a boundary of a PIM domain.
+ type: bool
+ default: false
+ neighbor_policy:
+ description:
+ - Configures a neighbor policy for filtering adjacencies.
+ type: str
+ neighbor_type:
+ description:
+ - Type of policy mapped to neighbor_policy.
+ type: str
+ choices:
+ - prefix
+ - routemap
+ state:
+ description:
+ - Manages desired state of the resource.
+ type: str
+ choices:
+ - present
+ - absent
+ - default
+ default: present
+"""
+EXAMPLES = """
+- name: Ensure PIM is not running on the interface
+ cisco.nxos.nxos_pim_interface:
+ interface: eth1/33
+ state: absent
+
+- name: Ensure the interface has pim-sm enabled with the appropriate priority and
+ hello interval
+ cisco.nxos.nxos_pim_interface:
+ interface: eth1/33
+ dr_prio: 10
+ hello_interval: 40
+ state: present
+
+- name: Ensure join-prune policies exist
+ cisco.nxos.nxos_pim_interface:
+ interface: eth1/33
+ jp_policy_in: JPIN
+ jp_policy_out: JPOUT
+ jp_type_in: routemap
+ jp_type_out: routemap
+
+- name: disable bfd on the interface
+ cisco.nxos.nxos_pim_interface:
+ interface: eth1/33
+ bfd: disable
+
+- name: Ensure defaults are in place
+ cisco.nxos.nxos_pim_interface:
+ interface: eth1/33
+ state: default
+"""
+
+RETURN = r"""
+commands:
+ description: command sent to the device
+ returned: always
+ type: list
+ sample: ["interface eth1/33",
+ "ip pim neighbor-policy test",
+ "ip pim bfd-instance disable",
+ "ip pim neighbor-policy test"
+ ]
+"""
+
+import re
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ get_config,
+ get_interface_type,
+ load_config,
+ run_commands,
+)
+
+
+PARAM_TO_COMMAND_KEYMAP = {
+ "interface": "",
+ "bfd": "ip pim bfd-instance",
+ "sparse": "ip pim sparse-mode",
+ "dr_prio": "ip pim dr-priority {0}",
+ "hello_interval": "ip pim hello-interval {0}",
+ "hello_auth_key": "ip pim hello-authentication ah-md5 {0}",
+ "border": "ip pim border",
+ "jp_policy_out": "ip pim jp-policy prefix-list {0} out",
+ "jp_policy_in": "ip pim jp-policy prefix-list {0} in",
+ "jp_type_in": "",
+ "jp_type_out": "",
+ "neighbor_policy": "ip pim neighbor-policy prefix-list {0}",
+ "neighbor_type": "",
+}
+
+PARAM_TO_DEFAULT_KEYMAP = {
+ "bfd": "default",
+ "dr_prio": "1",
+ "hello_interval": "30000",
+ "sparse": False,
+ "border": False,
+ "hello_auth_key": False,
+}
+
+BFD_KEYMAP = {
+ None: None,
+ "default": "no ip pim bfd-instance",
+ "disable": "ip pim bfd-instance disable",
+ "enable": "ip pim bfd-instance",
+}
+
+
+def execute_show_command(command, module, text=False):
+ if text:
+ cmds = [{"command": command, "output": "text"}]
+ else:
+ cmds = [{"command": command, "output": "json"}]
+
+ return run_commands(module, cmds)
+
+
+def flatten_list(command_lists):
+ flat_command_list = []
+ for command in command_lists:
+ if isinstance(command, list):
+ flat_command_list.extend(command)
+ else:
+ flat_command_list.append(command)
+ return flat_command_list
+
+
+def local_existing(gexisting):
+ jp_bidir = False
+ isauth = False
+ if gexisting:
+ jp_bidir = gexisting.get("jp_bidir")
+ isauth = gexisting.get("isauth")
+ if jp_bidir and isauth:
+ gexisting.pop("jp_bidir")
+ gexisting.pop("isauth")
+
+ return gexisting, jp_bidir, isauth
+
+
+def get_interface_mode(interface, intf_type, module):
+ mode = "unknown"
+ command = "show interface {0}".format(interface)
+ body = execute_show_command(command, module)
+
+ try:
+ interface_table = body[0]["TABLE_interface"]["ROW_interface"]
+ except (KeyError, AttributeError, IndexError):
+ return mode
+
+ if intf_type in ["ethernet", "portchannel"]:
+ mode = str(interface_table.get("eth_mode", "layer3"))
+ if mode in ["access", "trunk"]:
+ mode = "layer2"
+ elif mode == "routed":
+ mode = "layer3"
+ elif intf_type in ["loopback", "svi"]:
+ mode = "layer3"
+ return mode
+
+
+def get_pim_interface(module, interface):
+ pim_interface = {}
+ body = get_config(module, flags=["interface {0}".format(interface)])
+
+ pim_interface["bfd"] = "default"
+ pim_interface["neighbor_type"] = None
+ pim_interface["neighbor_policy"] = None
+ pim_interface["jp_policy_in"] = None
+ pim_interface["jp_policy_out"] = None
+ pim_interface["jp_type_in"] = None
+ pim_interface["jp_type_out"] = None
+ pim_interface["jp_bidir"] = False
+ pim_interface["isauth"] = False
+
+ if body:
+ all_lines = body.splitlines()
+
+ for each in all_lines:
+ if "jp-policy" in each:
+ policy_name = re.search(
+ r"ip pim jp-policy(?: prefix-list)? (\S+)(?: \S+)?",
+ each,
+ ).group(1)
+ if "prefix-list" in each:
+ ptype = "prefix"
+ else:
+ ptype = "routemap"
+ if "out" in each:
+ pim_interface["jp_policy_out"] = policy_name
+ pim_interface["jp_type_out"] = ptype
+ elif "in" in each:
+ pim_interface["jp_policy_in"] = policy_name
+ pim_interface["jp_type_in"] = ptype
+ else:
+ pim_interface["jp_policy_in"] = policy_name
+ pim_interface["jp_policy_out"] = policy_name
+ pim_interface["jp_bidir"] = True
+ elif "neighbor-policy" in each:
+ pim_interface["neighbor_policy"] = re.search(
+ r"ip pim neighbor-policy(?: prefix-list)? (\S+)",
+ each,
+ ).group(1)
+ if "prefix-list" in each:
+ pim_interface["neighbor_type"] = "prefix"
+ else:
+ pim_interface["neighbor_type"] = "routemap"
+ elif "ah-md5" in each:
+ pim_interface["isauth"] = True
+ elif "sparse-mode" in each:
+ pim_interface["sparse"] = True
+ elif "bfd-instance" in each:
+ m = re.search(r"ip pim bfd-instance(?P<disable> disable)?", each)
+ if m:
+ pim_interface["bfd"] = "disable" if m.group("disable") else "enable"
+ elif "border" in each:
+ pim_interface["border"] = True
+ elif "hello-interval" in each:
+ pim_interface["hello_interval"] = re.search(
+ r"ip pim hello-interval (\d+)",
+ body,
+ ).group(1)
+ elif "dr-priority" in each:
+ pim_interface["dr_prio"] = re.search(r"ip pim dr-priority (\d+)", body).group(1)
+
+ return pim_interface
+
+
+def fix_delta(delta, existing):
+ for key in list(delta):
+ if key in ["dr_prio", "hello_interval", "sparse", "border"]:
+ if delta.get(key) == PARAM_TO_DEFAULT_KEYMAP.get(key) and existing.get(key) is None:
+ delta.pop(key)
+ return delta
+
+
+def config_pim_interface(delta, existing, jp_bidir, isauth):
+ command = None
+ commands = []
+
+ delta = fix_delta(delta, existing)
+
+ if jp_bidir:
+ if delta.get("jp_policy_in") or delta.get("jp_policy_out"):
+ if existing.get("jp_type_in") == "prefix":
+ command = "no ip pim jp-policy prefix-list {0}".format(existing.get("jp_policy_in"))
+ else:
+ command = "no ip pim jp-policy {0}".format(existing.get("jp_policy_in"))
+ if command:
+ commands.append(command)
+
+ for k, v in delta.items():
+ if k in [
+ "bfd",
+ "dr_prio",
+ "hello_interval",
+ "hello_auth_key",
+ "border",
+ "sparse",
+ ]:
+ if k == "bfd":
+ command = BFD_KEYMAP[v]
+ elif v:
+ command = PARAM_TO_COMMAND_KEYMAP.get(k).format(v)
+ elif k == "hello_auth_key":
+ if isauth:
+ command = "no ip pim hello-authentication ah-md5"
+ else:
+ command = "no " + PARAM_TO_COMMAND_KEYMAP.get(k).format(v)
+
+ if command:
+ commands.append(command)
+ elif k in [
+ "neighbor_policy",
+ "jp_policy_in",
+ "jp_policy_out",
+ "neighbor_type",
+ ]:
+ if k in ["neighbor_policy", "neighbor_type"]:
+ temp = delta.get("neighbor_policy") or existing.get("neighbor_policy")
+ if delta.get("neighbor_type") == "prefix":
+ command = PARAM_TO_COMMAND_KEYMAP.get(k).format(temp)
+ elif delta.get("neighbor_type") == "routemap":
+ command = "ip pim neighbor-policy {0}".format(temp)
+ elif existing.get("neighbor_type") == "prefix":
+ command = PARAM_TO_COMMAND_KEYMAP.get(k).format(temp)
+ elif existing.get("neighbor_type") == "routemap":
+ command = "ip pim neighbor-policy {0}".format(temp)
+ elif k in ["jp_policy_in", "jp_type_in"]:
+ temp = delta.get("jp_policy_in") or existing.get("jp_policy_in")
+ if delta.get("jp_type_in") == "prefix":
+ command = PARAM_TO_COMMAND_KEYMAP.get(k).format(temp)
+ elif delta.get("jp_type_in") == "routemap":
+ command = "ip pim jp-policy {0} in".format(temp)
+ elif existing.get("jp_type_in") == "prefix":
+ command = PARAM_TO_COMMAND_KEYMAP.get(k).format(temp)
+ elif existing.get("jp_type_in") == "routemap":
+ command = "ip pim jp-policy {0} in".format(temp)
+ elif k in ["jp_policy_out", "jp_type_out"]:
+ temp = delta.get("jp_policy_out") or existing.get("jp_policy_out")
+ if delta.get("jp_type_out") == "prefix":
+ command = PARAM_TO_COMMAND_KEYMAP.get(k).format(temp)
+ elif delta.get("jp_type_out") == "routemap":
+ command = "ip pim jp-policy {0} out".format(temp)
+ elif existing.get("jp_type_out") == "prefix":
+ command = PARAM_TO_COMMAND_KEYMAP.get(k).format(temp)
+ elif existing.get("jp_type_out") == "routemap":
+ command = "ip pim jp-policy {0} out".format(temp)
+ if command:
+ commands.append(command)
+ command = None
+
+ if "no ip pim sparse-mode" in commands:
+ # sparse is long-running on some platforms, process it last
+ commands.remove("no ip pim sparse-mode")
+ commands.append("no ip pim sparse-mode")
+ return commands
+
+
+def get_pim_interface_defaults():
+ args = dict(
+ dr_prio=PARAM_TO_DEFAULT_KEYMAP.get("dr_prio"),
+ bfd=PARAM_TO_DEFAULT_KEYMAP.get("bfd"),
+ border=PARAM_TO_DEFAULT_KEYMAP.get("border"),
+ sparse=PARAM_TO_DEFAULT_KEYMAP.get("sparse"),
+ hello_interval=PARAM_TO_DEFAULT_KEYMAP.get("hello_interval"),
+ hello_auth_key=PARAM_TO_DEFAULT_KEYMAP.get("hello_auth_key"),
+ )
+
+ default = dict((param, value) for (param, value) in args.items() if value is not None)
+
+ return default
+
+
+def default_pim_interface_policies(existing, jp_bidir):
+ commands = []
+
+ if jp_bidir:
+ if existing.get("jp_policy_in") or existing.get("jp_policy_out"):
+ if existing.get("jp_type_in") == "prefix":
+ command = "no ip pim jp-policy prefix-list {0}".format(existing.get("jp_policy_in"))
+ if command:
+ commands.append(command)
+
+ elif not jp_bidir:
+ command = None
+ for k in existing:
+ if k == "jp_policy_in":
+ if existing.get("jp_policy_in"):
+ if existing.get("jp_type_in") == "prefix":
+ command = "no ip pim jp-policy prefix-list {0} in".format(
+ existing.get("jp_policy_in"),
+ )
+ else:
+ command = "no ip pim jp-policy {0} in".format(existing.get("jp_policy_in"))
+ elif k == "jp_policy_out":
+ if existing.get("jp_policy_out"):
+ if existing.get("jp_type_out") == "prefix":
+ command = "no ip pim jp-policy prefix-list {0} out".format(
+ existing.get("jp_policy_out"),
+ )
+ else:
+ command = "no ip pim jp-policy {0} out".format(
+ existing.get("jp_policy_out"),
+ )
+ if command:
+ commands.append(command)
+ command = None
+
+ if existing.get("neighbor_policy"):
+ command = "no ip pim neighbor-policy"
+ commands.append(command)
+
+ return commands
+
+
+def config_pim_interface_defaults(existing, jp_bidir, isauth):
+ command = []
+
+ # returns a dict
+ defaults = get_pim_interface_defaults()
+ delta = dict(set(defaults.items()).difference(existing.items()))
+ if delta:
+ # returns a list
+ command = config_pim_interface(delta, existing, jp_bidir, isauth)
+ comm = default_pim_interface_policies(existing, jp_bidir)
+ if comm:
+ for each in comm:
+ command.append(each)
+
+ return command
+
+
+def normalize_proposed_values(proposed, module):
+ keys = proposed.keys()
+ if "bfd" in keys:
+ # bfd is a tri-state string: enable, disable, default
+ proposed["bfd"] = proposed["bfd"].lower()
+ if "hello_interval" in keys:
+ hello_interval = proposed["hello_interval"]
+ if not module.params["hello_interval_ms"]:
+ hello_interval = hello_interval * 1000
+ proposed["hello_interval"] = str(hello_interval)
+
+
+def main():
+ argument_spec = dict(
+ interface=dict(type="str", required=True),
+ sparse=dict(type="bool", default=False),
+ dr_prio=dict(type="str"),
+ hello_auth_key=dict(type="str", no_log=True),
+ hello_interval=dict(type="int"),
+ hello_interval_ms=dict(type="bool"),
+ jp_policy_out=dict(type="str"),
+ jp_policy_in=dict(type="str"),
+ jp_type_out=dict(type="str", choices=["prefix", "routemap"]),
+ jp_type_in=dict(type="str", choices=["prefix", "routemap"]),
+ bfd=dict(type="str", choices=["enable", "disable", "default"]),
+ border=dict(type="bool", default=False),
+ neighbor_policy=dict(type="str"),
+ neighbor_type=dict(type="str", choices=["prefix", "routemap"]),
+ state=dict(
+ type="str",
+ default="present",
+ choices=["absent", "default", "present"],
+ ),
+ )
+
+ required_by = {"hello_interval_ms": "hello_interval"}
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_by=required_by,
+ )
+
+ warnings = list()
+ results = {"changed": False, "commands": [], "warnings": warnings}
+
+ state = module.params["state"]
+ interface = module.params["interface"]
+ jp_type_in = module.params["jp_type_in"]
+ jp_type_out = module.params["jp_type_out"]
+ jp_policy_in = module.params["jp_policy_in"]
+ jp_policy_out = module.params["jp_policy_out"]
+ neighbor_policy = module.params["neighbor_policy"]
+ neighbor_type = module.params["neighbor_type"]
+
+ intf_type = get_interface_type(interface)
+ if get_interface_mode(interface, intf_type, module) == "layer2":
+ module.fail_json(msg="this module only works on Layer 3 interfaces.")
+
+ if jp_policy_in:
+ if not jp_type_in:
+ module.fail_json(msg="jp_type_in required when using jp_policy_in.")
+ if jp_policy_out:
+ if not jp_type_out:
+ module.fail_json(msg="jp_type_out required when using jp_policy_out.")
+ if neighbor_policy:
+ if not neighbor_type:
+ module.fail_json(msg="neighbor_type required when using neighbor_policy.")
+
+ get_existing = get_pim_interface(module, interface)
+ existing, jp_bidir, isauth = local_existing(get_existing)
+
+ args = PARAM_TO_COMMAND_KEYMAP.keys()
+ proposed = dict((k, v) for k, v in module.params.items() if v is not None and k in args)
+ normalize_proposed_values(proposed, module)
+
+ delta = dict(set(proposed.items()).difference(existing.items()))
+
+ commands = []
+ if state == "present":
+ if delta:
+ command = config_pim_interface(delta, existing, jp_bidir, isauth)
+ if command:
+ commands.append(command)
+ elif state == "default" or state == "absent":
+ defaults = config_pim_interface_defaults(existing, jp_bidir, isauth)
+ if defaults:
+ commands.append(defaults)
+
+ if commands:
+ commands.insert(0, ["interface {0}".format(interface)])
+
+ cmds = flatten_list(commands)
+ if cmds:
+ results["changed"] = True
+ if not module.check_mode:
+ load_config(module, cmds)
+ if "configure" in cmds:
+ cmds.pop(0)
+
+ results["commands"] = cmds
+
+ module.exit_json(**results)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_pim_rp_address.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_pim_rp_address.py
new file mode 100644
index 00000000..fe9989ed
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_pim_rp_address.py
@@ -0,0 +1,248 @@
+#!/usr/bin/python
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_pim_rp_address
+extends_documentation_fragment:
+- cisco.nxos.nxos
+short_description: Manages configuration of an PIM static RP address instance.
+description:
+- Manages configuration of an Protocol Independent Multicast (PIM) static rendezvous
+ point (RP) address instance.
+version_added: 1.0.0
+author: Gabriele Gerbino (@GGabriele)
+notes:
+- Tested against NXOSv 7.3.(0)D1(1) on VIRL
+- Unsupported for Cisco MDS
+- C(state=absent) is currently not supported on all platforms.
+options:
+ rp_address:
+ description:
+ - Configures a Protocol Independent Multicast (PIM) static rendezvous point (RP)
+ address. Valid values are unicast addresses.
+ required: true
+ type: str
+ group_list:
+ description:
+ - Group range for static RP. Valid values are multicast addresses.
+ type: str
+ prefix_list:
+ description:
+ - Prefix list policy for static RP. Valid values are prefix-list policy names.
+ type: str
+ route_map:
+ description:
+ - Route map policy for static RP. Valid values are route-map policy names.
+ type: str
+ bidir:
+ description:
+ - Group range is treated in PIM bidirectional mode.
+ type: bool
+ state:
+ description:
+ - Specify desired state of the resource.
+ default: present
+ choices:
+ - present
+ - absent
+ type: str
+"""
+EXAMPLES = """
+- cisco.nxos.nxos_pim_rp_address:
+ rp_address: 10.1.1.20
+ state: present
+"""
+
+RETURN = """
+commands:
+ description: commands sent to the device
+ returned: always
+ type: list
+ sample: ["router bgp 65535", "vrf test", "router-id 192.0.2.1"]
+"""
+
+
+import re
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import (
+ CustomNetworkConfig,
+)
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ get_config,
+ load_config,
+)
+
+
+def get_existing(module, args, gl):
+ existing = {}
+ config = str(get_config(module))
+ address = module.params["rp_address"]
+
+ pim_address_re = r"ip pim rp-address (?P<value>.*)$"
+ for line in re.findall(pim_address_re, config, re.M):
+ values = line.split()
+ if values[0] != address:
+ continue
+ if gl and "group-list" not in line:
+ continue
+ elif not gl and "group-list" in line:
+ if "224.0.0.0/4" not in line: # ignore default group-list
+ continue
+
+ existing["bidir"] = existing.get("bidir") or "bidir" in line
+ if len(values) > 2:
+ value = values[2]
+ if values[1] == "route-map":
+ existing["route_map"] = value
+ elif values[1] == "prefix-list":
+ existing["prefix_list"] = value
+ elif values[1] == "group-list":
+ if value != "224.0.0.0/4": # ignore default group-list
+ existing["group_list"] = value
+
+ return existing
+
+
+def state_present(module, existing, proposed, candidate):
+ address = module.params["rp_address"]
+ command = "ip pim rp-address {0}".format(address)
+ if module.params["group_list"] and not proposed.get("group_list"):
+ command += " group-list " + module.params["group_list"]
+ if module.params["prefix_list"]:
+ if not proposed.get("prefix_list"):
+ command += " prefix-list " + module.params["prefix_list"]
+ if module.params["route_map"]:
+ if not proposed.get("route_map"):
+ command += " route-map " + module.params["route_map"]
+ commands = build_command(proposed, command)
+ if commands:
+ candidate.add(commands, parents=[])
+
+
+def build_command(param_dict, command):
+ for param in ["group_list", "prefix_list", "route_map"]:
+ if param_dict.get(param):
+ command += " {0} {1}".format(param.replace("_", "-"), param_dict.get(param))
+ if param_dict.get("bidir"):
+ command += " bidir"
+ return [command]
+
+
+def state_absent(module, existing, candidate):
+ address = module.params["rp_address"]
+
+ commands = []
+ command = "no ip pim rp-address {0}".format(address)
+ if module.params["group_list"] == existing.get("group_list"):
+ commands = build_command(existing, command)
+ elif not module.params["group_list"]:
+ commands = [command]
+
+ if commands:
+ candidate.add(commands, parents=[])
+
+
+def get_proposed(pargs, existing):
+ proposed = {}
+
+ for key, value in pargs.items():
+ if key != "rp_address":
+ if str(value).lower() == "true":
+ value = True
+ elif str(value).lower() == "false":
+ value = False
+
+ if existing.get(key) != value:
+ proposed[key] = value
+
+ return proposed
+
+
+def main():
+ argument_spec = dict(
+ rp_address=dict(required=True, type="str"),
+ group_list=dict(required=False, type="str"),
+ prefix_list=dict(required=False, type="str"),
+ route_map=dict(required=False, type="str"),
+ bidir=dict(required=False, type="bool"),
+ state=dict(choices=["present", "absent"], default="present", required=False),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ mutually_exclusive=[
+ ["group_list", "route_map"],
+ ["group_list", "prefix_list"],
+ ["route_map", "prefix_list"],
+ ],
+ supports_check_mode=True,
+ )
+
+ warnings = list()
+ result = {"changed": False, "commands": [], "warnings": warnings}
+
+ state = module.params["state"]
+
+ args = ["rp_address", "group_list", "prefix_list", "route_map", "bidir"]
+
+ proposed_args = dict((k, v) for k, v in module.params.items() if v is not None and k in args)
+
+ if module.params["group_list"]:
+ existing = get_existing(module, args, True)
+ proposed = get_proposed(proposed_args, existing)
+
+ else:
+ existing = get_existing(module, args, False)
+ proposed = get_proposed(proposed_args, existing)
+
+ candidate = CustomNetworkConfig(indent=3)
+ if state == "present" and (proposed or not existing):
+ state_present(module, existing, proposed, candidate)
+ elif state == "absent" and existing:
+ state_absent(module, existing, candidate)
+
+ if candidate:
+ candidate = candidate.items_text()
+ result["commands"] = candidate
+ result["changed"] = True
+ msgs = load_config(module, candidate, True)
+ if msgs:
+ for item in msgs:
+ if item:
+ if isinstance(item, dict):
+ err_str = item["clierror"]
+ else:
+ err_str = item
+ if "No policy was configured" in err_str:
+ if state == "absent":
+ addr = module.params["rp_address"]
+ new_cmd = "no ip pim rp-address {0}".format(addr)
+ load_config(module, new_cmd)
+
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_ping.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_ping.py
new file mode 100644
index 00000000..31c8517a
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_ping.py
@@ -0,0 +1,255 @@
+#!/usr/bin/python
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_ping
+extends_documentation_fragment:
+- cisco.nxos.nxos
+short_description: Tests reachability using ping from Nexus switch.
+description:
+- Tests reachability using ping from switch to a remote destination.
+- For a general purpose network module, see the M(ansible.netcommon.net_ping) module.
+- For Windows targets, use the M(ansible.windows.win_ping) module instead.
+- For targets running Python, use the M(ansible.builtin.ping) module instead.
+version_added: 1.0.0
+author:
+- Jason Edelman (@jedelman8)
+- Gabriele Gerbino (@GGabriele)
+options:
+ dest:
+ description:
+ - IP address or hostname (resolvable by switch) of remote node.
+ required: true
+ type: str
+ count:
+ description:
+ - Number of packets to send.
+ default: 5
+ type: int
+ source:
+ description:
+ - Source IP Address or hostname (resolvable by switch)
+ type: str
+ vrf:
+ description:
+ - Outgoing VRF.
+ type: str
+ df_bit:
+ description:
+ - Set the DF bit.
+ default: false
+ type: bool
+ size:
+ description:
+ - Size of packets to send.
+ type: int
+ state:
+ description:
+ - Determines if the expected result is success or fail.
+ choices:
+ - absent
+ - present
+ default: present
+ type: str
+notes:
+- Unsupported for Cisco MDS
+- For a general purpose network module, see the M(ansible.netcommon.net_ping) module.
+- For Windows targets, use the M(ansible.windows.win_ping) module instead.
+- For targets running Python, use the M(ansible.builtin.ping) module instead.
+"""
+
+EXAMPLES = """
+- name: Test reachability to 8.8.8.8 using mgmt vrf
+ cisco.nxos.nxos_ping:
+ dest: 8.8.8.8
+ vrf: management
+ host: 68.170.147.165
+
+- name: Test reachability to a few different public IPs using mgmt vrf
+ cisco.nxos.nxos_ping:
+ dest: "{{ item }}"
+ vrf: management
+ host: 68.170.147.165
+ with_items:
+ - 8.8.8.8
+ - 4.4.4.4
+ - 198.6.1.4
+
+- name: Test reachability to 8.8.8.8 using mgmt vrf, size and df-bit
+ cisco.nxos.nxos_ping:
+ dest: 8.8.8.8
+ df_bit: true
+ size: 1400
+ vrf: management
+"""
+
+RETURN = """
+commands:
+ description: Show the command sent
+ returned: always
+ type: list
+ sample: ["ping 8.8.8.8 count 2 vrf management"]
+rtt:
+ description: Show RTT stats
+ returned: always
+ type: dict
+ sample: {"avg": 6.264, "max": 6.564, "min": 5.978}
+packets_rx:
+ description: Packets successfully received
+ returned: always
+ type: int
+ sample: 2
+packets_tx:
+ description: Packets successfully transmitted
+ returned: always
+ type: int
+ sample: 2
+packet_loss:
+ description: Percentage of packets lost
+ returned: always
+ type: str
+ sample: "0.00%"
+"""
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import run_commands
+
+
+def get_summary(results_list, reference_point):
+ summary_string = results_list[reference_point + 1]
+ summary_list = summary_string.split(",")
+
+ summary = dict(
+ packets_tx=int(summary_list[0].split("packets")[0].strip()),
+ packets_rx=int(summary_list[1].split("packets")[0].strip()),
+ packet_loss=summary_list[2].split("packet")[0].strip(),
+ )
+
+ if "bytes from" not in results_list[reference_point - 2]:
+ ping_pass = False
+ else:
+ ping_pass = True
+
+ return summary, ping_pass
+
+
+def get_rtt(results_list, packet_loss, location):
+ rtt = dict(min=None, avg=None, max=None)
+
+ if packet_loss != "100.00%":
+ rtt_string = results_list[location]
+ base = rtt_string.split("=")[1]
+ rtt_list = base.split("/")
+
+ rtt["min"] = float(rtt_list[0].lstrip())
+ rtt["avg"] = float(rtt_list[1])
+ rtt["max"] = float(rtt_list[2][:-3])
+
+ return rtt
+
+
+def get_statistics_summary_line(response_as_list):
+ for each in response_as_list:
+ if "---" in each:
+ index = response_as_list.index(each)
+ return index
+
+
+def get_ping_results(command, module):
+ cmd = {"command": command, "output": "text"}
+ ping = run_commands(module, [cmd])[0]
+
+ if not ping:
+ module.fail_json(
+ msg="An unexpected error occurred. Check all params.",
+ command=command,
+ destination=module.params["dest"],
+ vrf=module.params["vrf"],
+ source=module.params["source"],
+ size=module.params["size"],
+ df_bit=module.params["df_bit"],
+ )
+
+ elif "can't bind to address" in ping:
+ module.fail_json(msg="Can't bind to source address.", command=command)
+ elif "bad context" in ping:
+ module.fail_json(
+ msg="Wrong VRF name inserted.",
+ command=command,
+ vrf=module.params["vrf"],
+ )
+ else:
+ splitted_ping = ping.split("\n")
+ reference_point = get_statistics_summary_line(splitted_ping)
+ summary, ping_pass = get_summary(splitted_ping, reference_point)
+ rtt = get_rtt(splitted_ping, summary["packet_loss"], reference_point + 2)
+
+ return (summary, rtt, ping_pass)
+
+
+def main():
+ argument_spec = dict(
+ dest=dict(required=True),
+ count=dict(required=False, default=5, type="int"),
+ vrf=dict(required=False),
+ source=dict(required=False),
+ size=dict(required=False, type="int"),
+ df_bit=dict(required=False, default=False, type="bool"),
+ state=dict(required=False, choices=["present", "absent"], default="present"),
+ )
+
+ module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)
+
+ destination = module.params["dest"]
+ state = module.params["state"]
+ size = module.params["size"]
+ df_bit = module.params["df_bit"]
+
+ ping_command = "ping {0}".format(destination)
+ for command in ["count", "source", "vrf"]:
+ arg = module.params[command]
+ if arg:
+ ping_command += " {0} {1}".format(command, arg)
+
+ if size:
+ ping_command += " packet-size {0}".format(size)
+
+ if df_bit:
+ ping_command += " df-bit"
+
+ summary, rtt, ping_pass = get_ping_results(ping_command, module)
+
+ results = summary
+ results["rtt"] = rtt
+ results["commands"] = [ping_command]
+
+ if ping_pass and state == "absent":
+ module.fail_json(msg="Ping succeeded unexpectedly")
+ elif not ping_pass and state == "present":
+ module.fail_json(msg="Ping failed unexpectedly")
+
+ module.exit_json(**results)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_prefix_lists.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_prefix_lists.py
new file mode 100644
index 00000000..cbd2415c
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_prefix_lists.py
@@ -0,0 +1,842 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# Copyright 2021 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+"""
+The module file for nxos_prefix_lists
+"""
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+DOCUMENTATION = """
+module: nxos_prefix_lists
+short_description: Prefix-Lists resource module.
+description:
+- This module manages prefix-lists configuration on devices running Cisco NX-OS.
+version_added: 2.4.0
+notes:
+- Tested against NX-OS 9.3.6.
+- Unsupported for Cisco MDS
+- This module works with connection C(network_cli) and C(httpapi).
+author: Nilashish Chakraborty (@NilashishC)
+options:
+ running_config:
+ description:
+ - This option is used only with state I(parsed).
+ - The value of this option should be the output received from the NX-OS device
+ by executing the command B(show running-config | section '^ip(.*) prefix-list').
+ - The state I(parsed) reads the configuration from C(running_config) option and
+ transforms it into Ansible structured data as per the resource module's argspec
+ and the value is then returned in the I(parsed) key within the result.
+ type: str
+ config:
+ description: A list of prefix-list configuration.
+ type: list
+ elements: dict
+ suboptions:
+ afi:
+ description:
+ - The Address Family Identifier (AFI) for the prefix-lists.
+ type: str
+ choices: ["ipv4", "ipv6"]
+ prefix_lists:
+ description: List of prefix-list configurations.
+ type: list
+ elements: dict
+ suboptions:
+ name:
+ description: Name of the prefix-list.
+ type: str
+ description:
+ description: Description of the prefix list
+ type: str
+ entries:
+ description: List of configurations for the specified prefix-list
+ type: list
+ elements: dict
+ suboptions:
+ sequence:
+ description: Sequence Number.
+ type: int
+ action:
+ description: Prefix-List permit or deny.
+ type: str
+ choices: ["permit", "deny"]
+ prefix:
+ description: IP or IPv6 prefix in A.B.C.D/LEN or A:B::C:D/LEN format.
+ type: str
+ eq:
+ description: Exact prefix length to be matched.
+ type: int
+ ge:
+ description: Minimum prefix length to be matched.
+ type: int
+ le:
+ description: Maximum prefix length to be matched.
+ type: int
+ mask:
+ description: Explicit match mask.
+ type: str
+ state:
+ description:
+ - The state the configuration should be left in.
+ - Refer to examples for more details.
+ - With state I(replaced), for the listed prefix-lists,
+ sequences that are in running-config but not in the task are negated.
+ - With state I(overridden), all prefix-lists that are in running-config but
+ not in the task are negated.
+ - Please refer to examples for more details.
+ type: str
+ choices:
+ - merged
+ - replaced
+ - overridden
+ - deleted
+ - parsed
+ - gathered
+ - rendered
+ default: merged
+"""
+EXAMPLES = """
+# Using merged
+
+# Before state:
+# -------------
+# nxos-9k-rdo# show running-config | section 'ip(.*) prefix-list'
+# nxos-9k-rdo#
+
+- name: Merge the provided configuration with the existing running configuration
+ cisco.nxos.nxos_prefix_lists:
+ config:
+ - afi: ipv4
+ prefix_lists:
+ - name: AllowPrefix
+ description: allows engineering IPv4 networks
+ entries:
+ - sequence: 10
+ action: permit
+ prefix: 192.0.2.0/23
+ eq: 24
+ - sequence: 20
+ action: permit
+ prefix: 198.51.100.128/26
+ - name: DenyPrefix
+ description: denies lab IPv4 networks
+ entries:
+ - sequence: 20
+ action: deny
+ prefix: 203.0.113.0/24
+ le: 25
+
+ - afi: ipv6
+ prefix_lists:
+ - name: AllowIPv6Prefix
+ description: allows engineering IPv6 networks
+ entries:
+ - sequence: 8
+ action: permit
+ prefix: "2001:db8:400::/38"
+ - sequence: 20
+ action: permit
+ prefix: "2001:db8:8000::/35"
+ le: 37
+
+# Task output
+# -------------
+# before: []
+#
+# commands:
+# - "ipv6 prefix-list AllowIPv6Prefix description allows engineering IPv6 networks"
+# - "ipv6 prefix-list AllowIPv6Prefix seq 8 permit 2001:db8:400::/38"
+# - "ipv6 prefix-list AllowIPv6Prefix seq 20 permit 2001:db8:8000::/35 le 37"
+# - "ip prefix-list AllowPrefix description allows engineering IPv4 networks"
+# - "ip prefix-list AllowPrefix seq 10 permit 192.0.2.0/23 eq 24"
+# - "ip prefix-list AllowPrefix seq 20 permit 198.51.100.128/26"
+# - "ip prefix-list DenyPrefix description denies lab IPv4 networks"
+# - "ip prefix-list DenyPrefix seq 20 deny 203.0.113.0/24 le 25"
+#
+# after:
+# - afi: ipv4
+# prefix_lists:
+# - description: allows engineering IPv4 networks
+# entries:
+# - sequence: 10
+# action: permit
+# prefix: 192.0.2.0/23
+# eq: 24
+# - sequence: 20
+# action: permit
+# prefix: 198.51.100.128/26
+# name: AllowPrefix
+# - description: denies lab IPv4 networks
+# entries:
+# - sequence: 20
+# action: deny
+# prefix: 203.0.113.0/24
+# le: 25
+# name: DenyPrefix
+#
+# - afi: ipv6
+# prefix_lists:
+# - description: allows engineering IPv6 networks
+# entries:
+# - sequence: 8
+# action: permit
+# prefix: "2001:db8:400::/38"
+# - sequence: 20
+# action: permit
+# prefix: "2001:db8:8000::/35"
+# le: 37
+# name: AllowIPv6Prefix
+
+# After state:
+# ------------
+# nxos-9k-rdo# show running-config | section 'ip(.*) prefix-list'
+# ip prefix-list AllowPrefix description allows engineering IPv4 networks
+# ip prefix-list AllowPrefix seq 10 permit 192.0.2.0/23 eq 24
+# ip prefix-list AllowPrefix seq 20 permit 198.51.100.128/26
+# ip prefix-list DenyPrefix description denies lab IPv4 networks
+# ip prefix-list DenyPrefix seq 20 deny 203.0.113.0/24 le 25
+# ipv6 prefix-list AllowIPv6Prefix description allows engineering IPv6 networks
+# ipv6 prefix-list AllowIPv6Prefix seq 8 permit 2001:db8:400::/38
+# ipv6 prefix-list AllowIPv6Prefix seq 20 permit 2001:db8:8000::/35 le 37
+
+# Using replaced
+
+# Before state:
+# ------------
+# nxos-9k-rdo# show running-config | section 'ip(.*) prefix-list'
+# ip prefix-list AllowPrefix description allows engineering IPv4 networks
+# ip prefix-list AllowPrefix seq 10 permit 192.0.2.0/23 eq 24
+# ip prefix-list AllowPrefix seq 20 permit 198.51.100.128/26
+# ip prefix-list DenyPrefix description denies lab IPv4 networks
+# ip prefix-list DenyPrefix seq 20 deny 203.0.113.0/24 le 25
+# ipv6 prefix-list AllowIPv6Prefix description allows engineering IPv6 networks
+# ipv6 prefix-list AllowIPv6Prefix seq 8 permit 2001:db8:400::/38
+# ipv6 prefix-list AllowIPv6Prefix seq 20 permit 2001:db8:8000::/35 le 37
+
+- name: Replace prefix-lists configurations of listed prefix-lists with provided configurations
+ cisco.nxos.nxos_prefix_lists:
+ config:
+ - afi: ipv4
+ prefix_lists:
+ - name: AllowPrefix
+ description: allows engineering IPv4 networks
+ entries:
+ - sequence: 10
+ action: permit
+ prefix: 203.0.113.64/27
+
+ - sequence: 30
+ action: permit
+ prefix: 203.0.113.96/27
+ - name: AllowPrefix2Stub
+ description: allow other engineering IPv4 network
+ state: replaced
+
+# Task output
+# -------------
+# before:
+# - afi: ipv4
+# prefix_lists:
+# - description: allows engineering IPv4 networks
+# entries:
+# - sequence: 10
+# action: permit
+# prefix: 192.0.2.0/23
+# eq: 24
+# - sequence: 20
+# action: permit
+# prefix: 198.51.100.128/26
+# name: AllowPrefix
+# - description: denies lab IPv4 networks
+# entries:
+# - sequence: 20
+# action: deny
+# prefix: 203.0.113.0/24
+# le: 25
+# name: DenyPrefix
+#
+# - afi: ipv6
+# prefix_lists:
+# - description: allows engineering IPv6 networks
+# entries:
+# - sequence: 8
+# action: permit
+# prefix: "2001:db8:400::/38"
+# - sequence: 20
+# action: permit
+# prefix: "2001:db8:8000::/35"
+# le: 37
+# name: AllowIPv6Prefix
+#
+# commands:
+# - "no ip prefix-list AllowPrefix seq 10 permit 192.0.2.0/23 eq 24"
+# - "ip prefix-list AllowPrefix seq 10 permit 203.0.113.64/27"
+# - "ip prefix-list AllowPrefix seq 30 permit 203.0.113.96/27"
+# - "no ip prefix-list AllowPrefix seq 20 permit 198.51.100.128/26"
+# - "ip prefix-list AllowPrefix2Stub description allow other engineering IPv4 network"
+#
+# after:
+# - afi: ipv4
+# prefix_lists:
+# - description: allows engineering IPv4 networks
+# entries:
+# - sequence: 10
+# action: permit
+# prefix: 203.0.113.64/27
+# - sequence: 30
+# action: permit
+# prefix: 203.0.113.96/27
+# name: AllowPrefix
+# - description: allow other engineering IPv4 network
+# name: AllowPrefix2Stub
+# - description: denies lab IPv4 networks
+# entries:
+# - sequence: 20
+# action: deny
+# prefix: 203.0.113.0/24
+# le: 25
+# name: DenyPrefix
+#
+# - afi: ipv6
+# prefix_lists:
+# - description: allows engineering IPv6 networks
+# entries:
+# - sequence: 8
+# action: permit
+# prefix: "2001:db8:400::/38"
+# - sequence: 20
+# action: permit
+# prefix: "2001:db8:8000::/35"
+# le: 37
+# name: AllowIPv6Prefix
+#
+# After state:
+# ------------
+# nxos-9k-rdo# show running-config | section 'ip(.*) prefix-list'
+# ip prefix-list AllowPrefix description allows engineering IPv4 networks
+# ip prefix-list AllowPrefix seq 10 permit 203.0.113.64/27
+# ip prefix-list AllowPrefix seq 30 permit 203.0.113.96/27
+# ip prefix-list AllowPrefix2Stub description allow other engineering IPv4 network
+# ip prefix-list DenyPrefix description denies lab IPv4 networks
+# ip prefix-list DenyPrefix seq 20 deny 203.0.113.0/24 le 25
+# ipv6 prefix-list AllowIPv6Prefix description allows engineering IPv6 networks
+# ipv6 prefix-list AllowIPv6Prefix seq 8 permit 2001:db8:400::/38
+# ipv6 prefix-list AllowIPv6Prefix seq 20 permit 2001:db8:8000::/35 le 37
+
+# Using overridden
+
+# Before state:
+# ------------
+# nxos-9k-rdo# show running-config | section 'ip(.*) prefix-list'
+# ip prefix-list AllowPrefix description allows engineering IPv4 networks
+# ip prefix-list AllowPrefix seq 10 permit 192.0.2.0/23 eq 24
+# ip prefix-list AllowPrefix seq 20 permit 198.51.100.128/26
+# ip prefix-list DenyPrefix description denies lab IPv4 networks
+# ip prefix-list DenyPrefix seq 20 deny 203.0.113.0/24 le 25
+# ipv6 prefix-list AllowIPv6Prefix description allows engineering IPv6 networks
+# ipv6 prefix-list AllowIPv6Prefix seq 8 permit 2001:db8:400::/38
+# ipv6 prefix-list AllowIPv6Prefix seq 20 permit 2001:db8:8000::/35 le 37
+
+- name: Override all prefix-lists configuration with provided configuration
+ cisco.nxos.nxos_prefix_lists: &id003
+ config:
+ - afi: ipv4
+ prefix_lists:
+ - name: AllowPrefix
+ description: allows engineering IPv4 networks
+ entries:
+ - sequence: 10
+ action: permit
+ prefix: 203.0.113.64/27
+
+ - sequence: 30
+ action: permit
+ prefix: 203.0.113.96/27
+ - name: AllowPrefix2Stub
+ description: allow other engineering IPv4 network
+ state: overridden
+
+# Task output
+# -------------
+# before:
+# - afi: ipv4
+# prefix_lists:
+# - description: allows engineering IPv4 networks
+# entries:
+# - sequence: 10
+# action: permit
+# prefix: 192.0.2.0/23
+# eq: 24
+# - sequence: 20
+# action: permit
+# prefix: 198.51.100.128/26
+# name: AllowPrefix
+# - description: denies lab IPv4 networks
+# entries:
+# - sequence: 20
+# action: deny
+# prefix: 203.0.113.0/24
+# le: 25
+# name: DenyPrefix
+#
+# - afi: ipv6
+# prefix_lists:
+# - description: allows engineering IPv6 networks
+# entries:
+# - sequence: 8
+# action: permit
+# prefix: "2001:db8:400::/38"
+# - sequence: 20
+# action: permit
+# prefix: "2001:db8:8000::/35"
+# le: 37
+# name: AllowIPv6Prefix
+#
+# commands:
+# - "no ip prefix-list AllowPrefix seq 10 permit 192.0.2.0/23 eq 24"
+# - "ip prefix-list AllowPrefix seq 10 permit 203.0.113.64/27"
+# - "ip prefix-list AllowPrefix seq 30 permit 203.0.113.96/27"
+# - "no ip prefix-list AllowPrefix seq 20 permit 198.51.100.128/26"
+# - "ip prefix-list AllowPrefix2Stub description allow other engineering IPv4 network"
+# - "no ip prefix-list DenyPrefix"
+# - "no ipv6 prefix-list AllowIPv6Prefix"
+#
+# after:
+# - afi: ipv4
+# prefix_lists:
+# - name: AllowPrefix
+# description: allows engineering IPv4 networks
+# entries:
+# - sequence: 10
+# action: permit
+# prefix: 203.0.113.64/27
+#
+# - sequence: 30
+# action: permit
+# prefix: 203.0.113.96/27
+# - name: AllowPrefix2Stub
+# description: allow other engineering IPv4 network
+#
+# After state:
+# ------------
+# nxos-9k-rdo# show running-config | section 'ip(.*) prefix-list'
+# ip prefix-list AllowPrefix description allows engineering IPv4 networks
+# ip prefix-list AllowPrefix seq 10 permit 203.0.113.64/27
+# ip prefix-list AllowPrefix seq 30 permit 203.0.113.96/27
+# ip prefix-list AllowPrefix2Stub description allow other engineering IPv4 network
+
+# Using deleted to delete a all prefix lists for an AFI
+
+# Before state:
+# ------------
+# nxos-9k-rdo# show running-config | section 'ip(.*) prefix-list'
+# ip prefix-list AllowPrefix description allows engineering IPv4 networks
+# ip prefix-list AllowPrefix seq 10 permit 192.0.2.0/23 eq 24
+# ip prefix-list AllowPrefix seq 20 permit 198.51.100.128/26
+# ip prefix-list DenyPrefix description denies lab IPv4 networks
+# ip prefix-list DenyPrefix seq 20 deny 203.0.113.0/24 le 25
+# ipv6 prefix-list AllowIPv6Prefix description allows engineering IPv6 networks
+# ipv6 prefix-list AllowIPv6Prefix seq 8 permit 2001:db8:400::/38
+# ipv6 prefix-list AllowIPv6Prefix seq 20 permit 2001:db8:8000::/35 le 37
+
+- name: Delete all prefix-lists for an AFI
+ cisco.nxos.nxos_prefix_lists:
+ config:
+ - afi: ipv4
+ state: deleted
+ register: result
+
+# Task output
+# -------------
+# before:
+# - afi: ipv4
+# prefix_lists:
+# - description: allows engineering IPv4 networks
+# entries:
+# - sequence: 10
+# action: permit
+# prefix: 192.0.2.0/23
+# eq: 24
+# - sequence: 20
+# action: permit
+# prefix: 198.51.100.128/26
+# name: AllowPrefix
+# - description: denies lab IPv4 networks
+# entries:
+# - sequence: 20
+# action: deny
+# prefix: 203.0.113.0/24
+# le: 25
+# name: DenyPrefix
+#
+# - afi: ipv6
+# prefix_lists:
+# - description: allows engineering IPv6 networks
+# entries:
+# - sequence: 8
+# action: permit
+# prefix: "2001:db8:400::/38"
+# - sequence: 20
+# action: permit
+# prefix: "2001:db8:8000::/35"
+# le: 37
+# name: AllowIPv6Prefix
+#
+# commands:
+# - "no ip prefix-list AllowPrefix"
+# - "no ip prefix-list DenyPrefix"
+#
+# after:
+# - afi: ipv6
+# prefix_lists:
+# - description: allows engineering IPv6 networks
+# entries:
+# - sequence: 8
+# action: permit
+# prefix: "2001:db8:400::/38"
+# - sequence: 20
+# action: permit
+# prefix: "2001:db8:8000::/35"
+# le: 37
+# name: AllowIPv6Prefix
+#
+# After state:
+# ------------
+# nxos-9k-rdo# show running-config | section 'ip(.*) prefix-list'
+# ipv6 prefix-list AllowIPv6Prefix description allows engineering IPv6 networks
+# ipv6 prefix-list AllowIPv6Prefix seq 8 permit 2001:db8:400::/38
+# ipv6 prefix-list AllowIPv6Prefix seq 20 permit 2001:db8:8000::/35 le 37
+
+# Using deleted to delete a single prefix-list
+
+# Before state:
+# ------------
+# nxos-9k-rdo# show running-config | section 'ip(.*) prefix-list'
+# ip prefix-list AllowPrefix description allows engineering IPv4 networks
+# ip prefix-list AllowPrefix seq 10 permit 192.0.2.0/23 eq 24
+# ip prefix-list AllowPrefix seq 20 permit 198.51.100.128/26
+# ip prefix-list DenyPrefix description denies lab IPv4 networks
+# ip prefix-list DenyPrefix seq 20 deny 203.0.113.0/24 le 25
+# ipv6 prefix-list AllowIPv6Prefix description allows engineering IPv6 networks
+# ipv6 prefix-list AllowIPv6Prefix seq 8 permit 2001:db8:400::/38
+# ipv6 prefix-list AllowIPv6Prefix seq 20 permit 2001:db8:8000::/35 le 37
+
+- name: Delete a single prefix-list
+ cisco.nxos.nxos_prefix_lists:
+ config:
+ - afi: ipv4
+ prefix_lists:
+ - name: AllowPrefix
+ state: deleted
+
+# Task output
+# -------------
+# before:
+# - afi: ipv4
+# prefix_lists:
+# - description: allows engineering IPv4 networks
+# entries:
+# - sequence: 10
+# action: permit
+# prefix: 192.0.2.0/23
+# eq: 24
+# - sequence: 20
+# action: permit
+# prefix: 198.51.100.128/26
+# name: AllowPrefix
+# - description: denies lab IPv4 networks
+# entries:
+# - sequence: 20
+# action: deny
+# prefix: 203.0.113.0/24
+# le: 25
+# name: DenyPrefix
+#
+# - afi: ipv6
+# prefix_lists:
+# - description: allows engineering IPv6 networks
+# entries:
+# - sequence: 8
+# action: permit
+# prefix: "2001:db8:400::/38"
+# - sequence: 20
+# action: permit
+# prefix: "2001:db8:8000::/35"
+# le: 37
+# name: AllowIPv6Prefix
+#
+# commands:
+# - "no ip prefix-list AllowPrefix"
+#
+# after:
+# - afi: ipv4
+# prefix_lists:
+# - description: denies lab IPv4 networks
+# entries:
+# - sequence: 20
+# action: deny
+# prefix: 203.0.113.0/24
+# le: 25
+# name: DenyPrefix
+#
+# - afi: ipv6
+# prefix_lists:
+# - description: allows engineering IPv6 networks
+# entries:
+# - sequence: 8
+# action: permit
+# prefix: "2001:db8:400::/38"
+# - sequence: 20
+# action: permit
+# prefix: "2001:db8:8000::/35"
+# le: 37
+# name: AllowIPv6Prefix
+#
+# After state:
+# ------------
+# nxos-9k-rdo# show running-config | section 'ip(.*) prefix-list'
+# ip prefix-list DenyPrefix description denies lab IPv4 networks
+# ip prefix-list DenyPrefix seq 20 deny 203.0.113.0/24 le 25
+# ipv6 prefix-list AllowIPv6Prefix description allows engineering IPv6 networks
+# ipv6 prefix-list AllowIPv6Prefix seq 8 permit 2001:db8:400::/38
+# ipv6 prefix-list AllowIPv6Prefix seq 20 permit 2001:db8:8000::/35 le 37
+
+# Using deleted to delete all prefix-lists from the device
+
+# Before state:
+# ------------
+# nxos-9k-rdo# show running-config | section 'ip(.*) prefix-list'
+# ip prefix-list AllowPrefix description allows engineering IPv4 networks
+# ip prefix-list AllowPrefix seq 10 permit 192.0.2.0/23 eq 24
+# ip prefix-list AllowPrefix seq 20 permit 198.51.100.128/26
+# ip prefix-list DenyPrefix description denies lab IPv4 networks
+# ip prefix-list DenyPrefix seq 20 deny 203.0.113.0/24 le 25
+# ipv6 prefix-list AllowIPv6Prefix description allows engineering IPv6 networks
+# ipv6 prefix-list AllowIPv6Prefix seq 8 permit 2001:db8:400::/38
+# ipv6 prefix-list AllowIPv6Prefix seq 20 permit 2001:db8:8000::/35 le 37
+
+- name: Delete all prefix-lists
+ cisco.nxos.nxos_prefix_lists:
+ state: deleted
+
+# Task output
+# -------------
+# before:
+# - afi: ipv4
+# prefix_lists:
+# - description: allows engineering IPv4 networks
+# entries:
+# - sequence: 10
+# action: permit
+# prefix: 192.0.2.0/23
+# eq: 24
+# - sequence: 20
+# action: permit
+# prefix: 198.51.100.128/26
+# name: AllowPrefix
+# - description: denies lab IPv4 networks
+# entries:
+# - sequence: 20
+# action: deny
+# prefix: 203.0.113.0/24
+# le: 25
+# name: DenyPrefix
+#
+# - afi: ipv6
+# prefix_lists:
+# - description: allows engineering IPv6 networks
+# entries:
+# - sequence: 8
+# action: permit
+# prefix: "2001:db8:400::/38"
+# - sequence: 20
+# action: permit
+# prefix: "2001:db8:8000::/35"
+# le: 37
+# name: AllowIPv6Prefix
+#
+# commands:
+# - "no ip prefix-list AllowPrefix"
+# - "no ip prefix-list DenyPrefix"
+# - "no ipv6 prefix-list AllowIPv6Prefix"
+#
+# after: []
+#
+# After state:
+# ------------
+# nxos-9k-rdo# show running-config | section 'ip(.*) prefix-list'
+# nxos-9k-rdo#
+
+# Using rendered
+
+- name: Render platform specific configuration lines with state rendered (without connecting to the device)
+ cisco.nxos.nxos_prefix_lists: &id001
+ config:
+ - afi: ipv4
+ prefix_lists:
+ - name: AllowPrefix
+ description: allows engineering IPv4 networks
+ entries:
+ - sequence: 10
+ action: permit
+ prefix: 192.0.2.0/23
+ eq: 24
+ - sequence: 20
+ action: permit
+ prefix: 198.51.100.128/26
+ - name: DenyPrefix
+ description: denies lab IPv4 networks
+ entries:
+ - sequence: 20
+ action: deny
+ prefix: 203.0.113.0/24
+ le: 25
+
+ - afi: ipv6
+ prefix_lists:
+ - name: AllowIPv6Prefix
+ description: allows engineering IPv6 networks
+ entries:
+ - sequence: 8
+ action: permit
+ prefix: "2001:db8:400::/38"
+ - sequence: 20
+ action: permit
+ prefix: "2001:db8:8000::/35"
+ le: 37
+ state: rendered
+
+# Task Output (redacted)
+# -----------------------
+# rendered:
+# - afi: ipv4
+# prefix_lists:
+# - description: allows engineering IPv4 networks
+# entries:
+# - sequence: 10
+# action: permit
+# prefix: 192.0.2.0/23
+# eq: 24
+# - sequence: 20
+# action: permit
+# prefix: 198.51.100.128/26
+# name: AllowPrefix
+# - description: denies lab IPv4 networks
+# entries:
+# - sequence: 20
+# action: deny
+# prefix: 203.0.113.0/24
+# le: 25
+# name: DenyPrefix
+#
+# - afi: ipv6
+# prefix_lists:
+# - description: allows engineering IPv6 networks
+# entries:
+# - sequence: 8
+# action: permit
+# prefix: "2001:db8:400::/38"
+# - sequence: 20
+# action: permit
+# prefix: "2001:db8:8000::/35"
+# le: 37
+# name: AllowIPv6Prefix
+
+# Using parsed
+
+# parsed.cfg
+# ------------
+# ip prefix-list AllowPrefix description allows engineering IPv4 networks
+# ip prefix-list AllowPrefix seq 10 permit 192.0.2.0/23 eq 24
+# ip prefix-list AllowPrefix seq 20 permit 198.51.100.128/26
+# ip prefix-list DenyPrefix description denies lab IPv4 networks
+# ip prefix-list DenyPrefix seq 20 deny 203.0.113.0/24 le 25
+# ipv6 prefix-list AllowIPv6Prefix description allows engineering IPv6 networks
+# ipv6 prefix-list AllowIPv6Prefix seq 8 permit 2001:db8:400::/38
+# ipv6 prefix-list AllowIPv6Prefix seq 20 permit 2001:db8:8000::/35 le 37
+
+- name: Parse externally provided prefix-lists configuration
+ register: result
+ cisco.nxos.nxos_prefix_lists:
+ running_config: "{{ lookup('file', './parsed.cfg') }}"
+ state: parsed
+
+# Task output (redacted)
+# -----------------------
+# parsed:
+# - afi: ipv4
+# prefix_lists:
+# - description: allows engineering IPv4 networks
+# entries:
+# - sequence: 10
+# action: permit
+# prefix: 192.0.2.0/23
+# eq: 24
+# - sequence: 20
+# action: permit
+# prefix: 198.51.100.128/26
+# name: AllowPrefix
+# - description: denies lab IPv4 networks
+# entries:
+# - sequence: 20
+# action: deny
+# prefix: 203.0.113.0/24
+# le: 25
+# name: DenyPrefix
+#
+# - afi: ipv6
+# prefix_lists:
+# - description: allows engineering IPv6 networks
+# entries:
+# - sequence: 8
+# action: permit
+# prefix: "2001:db8:400::/38"
+# - sequence: 20
+# action: permit
+# prefix: "2001:db8:8000::/35"
+# le: 37
+# name: AllowIPv6Prefix
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.prefix_lists.prefix_lists import (
+ Prefix_listsArgs,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.config.prefix_lists.prefix_lists import (
+ Prefix_lists,
+)
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ module = AnsibleModule(
+ argument_spec=Prefix_listsArgs.argument_spec,
+ mutually_exclusive=[["config", "running_config"]],
+ required_if=[
+ ["state", "merged", ["config"]],
+ ["state", "replaced", ["config"]],
+ ["state", "overridden", ["config"]],
+ ["state", "rendered", ["config"]],
+ ["state", "parsed", ["running_config"]],
+ ],
+ supports_check_mode=True,
+ )
+
+ result = Prefix_lists(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_reboot.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_reboot.py
new file mode 100644
index 00000000..51d0d70a
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_reboot.py
@@ -0,0 +1,89 @@
+#!/usr/bin/python
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_reboot
+extends_documentation_fragment:
+- cisco.nxos.nxos
+short_description: Reboot a network device.
+description:
+- Reboot a network device.
+version_added: 1.0.0
+author:
+- Jason Edelman (@jedelman8)
+- Gabriele Gerbino (@GGabriele)
+notes:
+- Tested against NXOSv 7.3.(0)D1(1) on VIRL
+- Tested against Cisco MDS NX-OS 9.2(1)
+- The module will fail due to timeout issues, but the reboot will be performed anyway.
+options:
+ confirm:
+ description:
+ - Safeguard boolean. Set to true if you're sure you want to reboot.
+ required: false
+ default: false
+ type: bool
+"""
+
+EXAMPLES = """
+- cisco.nxos.nxos_reboot:
+ confirm: true
+"""
+
+RETURN = """
+rebooted:
+ description: Whether the device was instructed to reboot.
+ returned: success
+ type: bool
+ sample: true
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import load_config
+
+
+def reboot(module):
+ cmds = "terminal dont-ask ; reload"
+ opts = {"ignore_timeout": True}
+ load_config(module, cmds, False, opts)
+
+
+def main():
+ argument_spec = dict(confirm=dict(default=False, type="bool"))
+
+ module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)
+
+ warnings = list()
+ results = dict(changed=False, warnings=warnings)
+
+ if module.params["confirm"]:
+ if not module.check_mode:
+ reboot(module)
+ results["changed"] = True
+
+ module.exit_json(**results)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_rollback.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_rollback.py
new file mode 100644
index 00000000..4b26919d
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_rollback.py
@@ -0,0 +1,130 @@
+#!/usr/bin/python
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_rollback
+extends_documentation_fragment:
+- cisco.nxos.nxos
+short_description: Set a checkpoint or rollback to a checkpoint.
+description:
+- This module offers the ability to set a configuration checkpoint file or rollback
+ to a configuration checkpoint file on Cisco NXOS switches.
+version_added: 1.0.0
+author:
+- Jason Edelman (@jedelman8)
+- Gabriele Gerbino (@GGabriele)
+notes:
+- Tested against NXOSv 7.3.(0)D1(1) on VIRL
+- Unsupported for Cisco MDS
+- Sometimes C(transport=nxapi) may cause a timeout error.
+options:
+ checkpoint_file:
+ description:
+ - Name of checkpoint file to create. Mutually exclusive with rollback_to.
+ type: str
+ rollback_to:
+ description:
+ - Name of checkpoint file to rollback to. Mutually exclusive with checkpoint_file.
+ type: str
+"""
+
+EXAMPLES = """
+- cisco.nxos.nxos_rollback:
+ checkpoint_file: backup.cfg
+ username: '{{ un }}'
+ password: '{{ pwd }}'
+ host: '{{ inventory_hostname }}'
+- cisco.nxos.nxos_rollback:
+ rollback_to: backup.cfg
+ username: '{{ un }}'
+ password: '{{ pwd }}'
+ host: '{{ inventory_hostname }}'
+"""
+
+RETURN = """
+filename:
+ description: The filename of the checkpoint/rollback file.
+ returned: success
+ type: str
+ sample: 'backup.cfg'
+status:
+ description: Which operation took place and whether it was successful.
+ returned: success
+ type: str
+ sample: 'rollback executed'
+"""
+
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import run_commands
+
+
+def checkpoint(filename, module):
+ commands = [
+ {"command": "terminal dont-ask", "output": "text"},
+ {"command": "checkpoint file %s" % filename, "output": "text"},
+ ]
+ run_commands(module, commands)
+
+
+def rollback(filename, module):
+ commands = [
+ {
+ "command": "rollback running-config file %s" % filename,
+ "output": "text",
+ },
+ ]
+ run_commands(module, commands)
+
+
+def main():
+ argument_spec = dict(checkpoint_file=dict(required=False), rollback_to=dict(required=False))
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ mutually_exclusive=[["checkpoint_file", "rollback_to"]],
+ supports_check_mode=False,
+ )
+
+ checkpoint_file = module.params["checkpoint_file"]
+ rollback_to = module.params["rollback_to"]
+
+ status = None
+ filename = None
+ changed = False
+
+ if checkpoint_file:
+ checkpoint(checkpoint_file, module)
+ status = "checkpoint file created"
+ elif rollback_to:
+ rollback(rollback_to, module)
+ status = "rollback executed"
+ changed = True
+ filename = rollback_to or checkpoint_file
+
+ module.exit_json(changed=changed, status=status, filename=filename)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_route_maps.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_route_maps.py
new file mode 100644
index 00000000..111cc4da
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_route_maps.py
@@ -0,0 +1,1648 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# Copyright 2021 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+"""
+The module file for nxos_route_maps
+"""
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+DOCUMENTATION = """
+module: nxos_route_maps
+short_description: Route Maps resource module.
+description:
+- This module manages route maps configuration on devices running Cisco NX-OS.
+version_added: 2.2.0
+notes:
+- Tested against NX-OS 9.3.6.
+- Unsupported for Cisco MDS
+- This module works with connection C(network_cli) and C(httpapi).
+author: Nilashish Chakraborty (@NilashishC)
+options:
+ running_config:
+ description:
+ - This option is used only with state I(parsed).
+ - The value of this option should be the output received from the NX-OS device
+ by executing the command B(show running-config | section '^route-map').
+ - The state I(parsed) reads the configuration from C(running_config) option and
+ transforms it into Ansible structured data as per the resource module's argspec
+ and the value is then returned in the I(parsed) key within the result.
+ type: str
+ config:
+ description: A list of route-map configuration.
+ type: list
+ elements: dict
+ suboptions:
+ route_map:
+ description: Route-map name.
+ type: str
+ entries:
+ description: List of entries (identified by sequence number) for this route-map.
+ type: list
+ elements: dict
+ suboptions:
+ sequence:
+ description: Sequence to insert to/delete from existing route-map entry.
+ type: int
+ action:
+ description: Route map denies or permits set operations.
+ type: str
+ choices: ["deny", "permit"]
+ continue_sequence:
+ description: Continue on a different entry within the route-map.
+ type: int
+ description:
+ description: Description of the route-map.
+ type: str
+ match:
+ description: Match values from routing table.
+ type: dict
+ suboptions:
+ as_number:
+ description: Match BGP peer AS number.
+ type: dict
+ suboptions:
+ asn:
+ description: AS number.
+ type: list
+ elements: str
+ as_path_list:
+ description: AS path access list name.
+ type: list
+ elements: str
+ as_path:
+ description: Match BGP AS path access-list.
+ type: list
+ elements: str
+ community:
+ description: Match BGP community list.
+ type: dict
+ suboptions:
+ community_list:
+ description: Community list.
+ type: list
+ elements: str
+ exact_match:
+ description: Do exact matching of communities.
+ type: bool
+ evpn:
+ description: Match BGP EVPN Routes.
+ type: dict
+ suboptions:
+ route_types:
+ description: Match route type for evpn route.
+ type: list
+ elements: str
+ extcommunity:
+ description: Match BGP community list.
+ type: dict
+ suboptions:
+ extcommunity_list:
+ description: Extended Community list.
+ type: list
+ elements: str
+ exact_match:
+ description: Do exact matching of extended communities.
+ type: bool
+ interfaces:
+ description: Match first hop interface of route.
+ type: list
+ elements: str
+ ip:
+ description: Configure IP specific information.
+ type: dict
+ suboptions: &id001
+ address:
+ description: Match address of route or match packet.
+ type: dict
+ suboptions:
+ access_list:
+ description: IP access-list name (for use in route-maps for PBR only).
+ type: str
+ prefix_lists:
+ description: Match entries of prefix-lists.
+ type: list
+ elements: str
+ multicast:
+ description: Match multicast attributes.
+ type: dict
+ suboptions:
+ source:
+ description: Multicast source address.
+ type: str
+ group:
+ description:
+ - Multicast Group prefix.
+ - Mutually exclusive with group_range.
+ type: dict
+ suboptions:
+ prefix:
+ description: IPv4 group prefix.
+ type: str
+ group_range:
+ description:
+ - Multicast Group address range.
+ - Mutually exclusive with group.
+ type: dict
+ suboptions:
+ first:
+ description: First Group address.
+ type: str
+ last:
+ description: Last Group address.
+ type: str
+ rp:
+ description: Rendezvous point.
+ type: dict
+ suboptions:
+ prefix:
+ description: IPv4 rendezvous prefix.
+ type: str
+ rp_type:
+ description: Multicast rendezvous point type.
+ type: str
+ choices: ["ASM", "Bidir"]
+ next_hop:
+ description: Match next-hop address of route.
+ type: dict
+ suboptions:
+ prefix_lists:
+ description: Match entries of prefix-lists.
+ type: list
+ elements: str
+ route_source:
+ description: Match advertising source address of route.
+ type: dict
+ suboptions:
+ prefix_lists:
+ description: Match entries of prefix-lists.
+ type: list
+ elements: str
+ ipv6:
+ description: Configure IPv6 specific information.
+ type: dict
+ suboptions: *id001
+ mac_list:
+ description: Match entries of mac-lists.
+ type: list
+ elements: str
+ metric:
+ description: Match metric of route.
+ type: list
+ elements: int
+ ospf_area:
+ description: Match ospf area.
+ type: list
+ elements: int
+ route_types:
+ description: Match route-type of route.
+ type: list
+ elements: str
+ choices: ["external", "inter-area", "internal", "intra-area", "level-1", "level-2", "local", "nssa-external", "type-1", "type-2"]
+ source_protocol:
+ description: Match source protocol.
+ type: list
+ elements: str
+ tags:
+ description: Match tag of route.
+ type: list
+ elements: int
+ set:
+ description: Set values in destination routing protocol.
+ type: dict
+ suboptions:
+ as_path:
+ description: Prepend string for a BGP AS-path attribute.
+ type: dict
+ suboptions:
+ prepend:
+ description: Prepend to the AS-Path.
+ type: dict
+ suboptions:
+ as_number:
+ description: AS number.
+ type: list
+ elements: str
+ last_as:
+ description: Number of last-AS prepends.
+ type: int
+ tag:
+ description: Set the tag as an AS-path attribute.
+ type: bool
+ comm_list:
+ description: Set BGP community list (for deletion).
+ type: str
+ community:
+ description: Set BGP community attribute.
+ type: dict
+ suboptions:
+ additive:
+ description: Add to existing community.
+ type: bool
+ graceful_shutdown:
+ description: Graceful Shutdown (well-known community).
+ type: bool
+ internet:
+ description: Internet (well-known community).
+ type: bool
+ local_as:
+ description: Do not send outside local AS (well-known community).
+ type: bool
+ no_advertise:
+ description: Do not advertise to any peer (well-known community).
+ type: bool
+ no_export:
+ description: Do not export to next AS (well-known community).
+ type: bool
+ number:
+ description: "Community number aa:nn format"
+ type: list
+ elements: str
+ dampening:
+ description: Set BGP route flap dampening parameters.
+ type: dict
+ suboptions:
+ half_life:
+ description: Half-life time for the penalty.
+ type: int
+ start_reuse_route:
+ description: Value to start reusing a route.
+ type: int
+ start_suppress_route:
+ description: Value to start suppressing a route.
+ type: int
+ max_suppress_time:
+ description: Maximum suppress time for stable route.
+ type: int
+ distance:
+ description: Configure administrative distance.
+ type: dict
+ suboptions:
+ igp_ebgp_routes:
+ description: Administrative distance for IGP or EBGP routes
+ type: int
+ internal_routes:
+ description: Distance for internal routes.
+ type: int
+ local_routes:
+ description: Distance for local routes.
+ type: int
+ evpn:
+ description: Set BGP EVPN Routes.
+ type: dict
+ suboptions:
+ gateway_ip:
+ description:
+ - Set gateway IP for type 5 EVPN routes.
+ - Cannot set ip and use-nexthop in the same route-map sequence.
+ type: dict
+ suboptions:
+ ip:
+ description: Gateway IP address.
+ type: str
+ use_nexthop:
+ description: Use nexthop address as gateway IP.
+ type: bool
+ extcomm_list:
+ description: Set BGP extcommunity list (for deletion).
+ type: str
+ forwarding_address:
+ description: Set the forwarding address.
+ type: bool
+ null_interface:
+ description: Output Null interface.
+ type: str
+ ip:
+ description: Configure IP features.
+ type: dict
+ suboptions: &id002
+ address:
+ description: Specify IP address.
+ type: dict
+ suboptions:
+ prefix_list:
+ description: Name of prefix list (Max Size 63).
+ type: str
+ precedence:
+ description: Set precedence field.
+ type: str
+ ipv6:
+ description: Configure IPv6 features.
+ type: dict
+ suboptions: *id002
+ label_index:
+ description: Set Segment Routing (SR) label index of route.
+ type: int
+ level:
+ description: Where to import route.
+ type: str
+ choices: ["level-1", "level-1-2", "level-2"]
+ local_preference:
+ description: BGP local preference path attribute.
+ type: int
+ metric:
+ description: Set metric for destination routing protocol.
+ type: dict
+ suboptions:
+ bandwidth:
+ description: Metric value or Bandwidth in Kbits per second (Max Size 11).
+ type: int
+ igrp_delay_metric:
+ description: IGRP delay metric.
+ type: int
+ igrp_reliability_metric:
+ description: IGRP reliability metric where 255 is 100 percent reliable.
+ type: int
+ igrp_effective_bandwidth_metric:
+ description: IGRP Effective bandwidth metric (Loading) 255 is 100%.
+ type: int
+ igrp_mtu:
+ description: IGRP MTU of the path.
+ type: int
+ metric_type:
+ description: Type of metric for destination routing protocol.
+ type: str
+ choices: ["external", "internal", "type-1", "type-2"]
+ nssa_only:
+ description: OSPF NSSA Areas.
+ type: bool
+ origin:
+ description: BGP origin code.
+ type: str
+ choices: ["egp", "igp", "incomplete"]
+ path_selection:
+ description: Path selection criteria for BGP.
+ type: str
+ choices: ["all", "backup", "best2", "multipaths"]
+ tag:
+ description: Tag value for destination routing protocol.
+ type: int
+ weight:
+ description: BGP weight for routing table.
+ type: int
+ state:
+ description:
+ - The state the configuration should be left in.
+ - With state I(replaced), for the listed route-maps,
+ sequences that are in running-config but not in the task are negated.
+ - With state I(overridden), all route-maps that are in running-config but
+ not in the task are negated.
+ - Please refer to examples for more details.
+ type: str
+ choices:
+ - merged
+ - replaced
+ - overridden
+ - deleted
+ - parsed
+ - gathered
+ - rendered
+ default: merged
+"""
+EXAMPLES = """
+# Using merged
+
+# Before state:
+# -------------
+# nxos-9k-rdo# show running-config | section "^route-map"
+# nxos-9k-rdo#
+
+- name: Merge the provided configuration with the existing running configuration
+ cisco.nxos.nxos_route_maps:
+ config:
+ - route_map: rmap1
+ entries:
+ - sequence: 10
+ action: permit
+ description: rmap1-10-permit
+ match:
+ ip:
+ address:
+ access_list: acl_1
+ as_path: Allow40
+ as_number:
+ asn: 65564
+
+ - sequence: 20
+ action: deny
+ description: rmap1-20-deny
+ match:
+ community:
+ community_list:
+ - BGPCommunity1
+ - BGPCommunity2
+ ip:
+ address:
+ prefix_lists:
+ - AllowPrefix1
+ - AllowPrefix2
+ set:
+ dampening:
+ half_life: 30
+ start_reuse_route: 1500
+ start_suppress_route: 10000
+ max_suppress_time: 120
+
+ - route_map: rmap2
+ entries:
+ - sequence: 20
+ action: permit
+ description: rmap2-20-permit
+ continue_sequence: 40
+ match:
+ ipv6:
+ address:
+ prefix_lists: AllowIPv6Prefix
+ interfaces: "{{ nxos_int1 }}"
+ set:
+ as_path:
+ prepend:
+ as_number:
+ - 65563
+ - 65568
+ - 65569
+ comm_list: BGPCommunity
+
+ - sequence: 40
+ action: deny
+ description: rmap2-40-deny
+ match:
+ route_types:
+ - level-1
+ - level-2
+ tags: 2
+ ip:
+ multicast:
+ rp:
+ prefix: 192.0.2.0/24
+ rp_type: ASM
+ source: 203.0.113.0/24
+ group_range:
+ first: 239.0.0.1
+ last: 239.255.255.255
+ state: merged
+
+# Task output
+# -------------
+# before: []
+#
+# commands:
+# - "route-map rmap1 permit 10"
+# - "match as-number 65564"
+# - "match as-path Allow40"
+# - "match ip address acl_1"
+# - "description rmap1-10-permit"
+# - "route-map rmap1 deny 20"
+# - "match community BGPCommunity1 BGPCommunity2"
+# - "match ip address prefix-list AllowPrefix1 AllowPrefix2"
+# - "description rmap1-20-deny"
+# - "set dampening 30 1500 10000 120"
+# - "route-map rmap2 permit 20"
+# - "match interface Ethernet1/1"
+# - "match ipv6 address prefix-list AllowIPv6Prefix"
+# - "set as-path prepend 65563 65568 65569"
+# - "description rmap2-20-permit"
+# - "continue 40"
+# - "set comm-list BGPCommunity delete"
+# - "route-map rmap2 deny 40"
+# - "match ip multicast source 203.0.113.0/24 group-range 239.0.0.1 to 239.255.255.255 rp 192.0.2.0/24 rp-type ASM"
+# - "match route-type level-1 level-2"
+# - "match tag 2"
+# - "description rmap2-40-deny"
+#
+# after:
+# - route_map: rmap1
+# entries:
+# - action: permit
+# description: rmap1-10-permit
+# match:
+# as_number:
+# asn:
+# - '65564'
+# as_path:
+# - Allow40
+# ip:
+# address:
+# access_list: acl_1
+# sequence: 10
+#
+# - action: deny
+# description: rmap1-20-deny
+# match:
+# community:
+# community_list:
+# - BGPCommunity1
+# - BGPCommunity2
+# ip:
+# address:
+# prefix_lists:
+# - AllowPrefix1
+# - AllowPrefix2
+# sequence: 20
+# set:
+# dampening:
+# half_life: 30
+# max_suppress_time: 120
+# start_reuse_route: 1500
+# start_suppress_route: 10000
+#
+# - route_map: rmap2
+# entries:
+# - action: permit
+# continue_sequence: 40
+# description: rmap2-20-permit
+# match:
+# interfaces:
+# - Ethernet1/1
+# ipv6:
+# address:
+# prefix_lists:
+# - AllowIPv6Prefix
+# sequence: 20
+# set:
+# as_path:
+# prepend:
+# as_number:
+# - '65563'
+# - '65568'
+# - '65569'
+# comm_list: BGPCommunity
+#
+# - action: deny
+# description: rmap2-40-deny
+# match:
+# ip:
+# multicast:
+# group_range:
+# first: 239.0.0.1
+# last: 239.255.255.255
+# rp:
+# prefix: 192.0.2.0/24
+# rp_type: ASM
+# source: 203.0.113.0/24
+# route_types:
+# - level-1
+# - level-2
+# tags:
+# - 2
+# sequence: 40
+
+# After state:
+# ------------
+# nxos-9k-rdo# show running-config | section "^route-map"
+# route-map rmap1 permit 10
+# match as-number 65564
+# match as-path Allow40
+# match ip address acl_1
+# description rmap1-10-permit
+# route-map rmap1 deny 20
+# match community BGPCommunity1 BGPCommunity2
+# match ip address prefix-list AllowPrefix1 AllowPrefix2
+# description rmap1-20-deny
+# set dampening 30 1500 10000 120
+# route-map rmap2 permit 20
+# match interface Ethernet1/1
+# match ipv6 address prefix-list AllowIPv6Prefix
+# set as-path prepend 65563 65568 65569
+# description rmap2-20-permit
+# continue 40
+# set comm-list BGPCommunity delete
+# route-map rmap2 deny 40
+# match ip multicast source 203.0.113.0/24 group-range 239.0.0.1 to 239.255.255.255 rp 192.0.2.0/24 rp-type ASM
+# match route-type level-1 level-2
+# match tag 2
+# description rmap2-40-deny
+
+# Using replaced
+# (for the listed route-map(s), sequences that are in running-config but not in the task are negated)
+
+# Before state:
+# ------------
+# nxos-9k-rdo# show running-config | section "^route-map"
+# route-map rmap1 permit 10
+# match as-number 65564
+# match as-path Allow40
+# match ip address acl_1
+# description rmap1-10-permit
+# route-map rmap1 deny 20
+# match community BGPCommunity1 BGPCommunity2
+# match ip address prefix-list AllowPrefix1 AllowPrefix2
+# description rmap1-20-deny
+# set dampening 30 1500 10000 120
+# route-map rmap2 permit 20
+# match interface Ethernet1/1
+# match ipv6 address prefix-list AllowIPv6Prefix
+# set as-path prepend 65563 65568 65569
+# description rmap2-20-permit
+# continue 40
+# set comm-list BGPCommunity delete
+# route-map rmap2 deny 40
+# match ip multicast source 203.0.113.0/24 group-range 239.0.0.1 to 239.255.255.255 rp 192.0.2.0/24 rp-type ASM
+# match route-type level-1 level-2
+# match tag 2
+# description rmap2-40-deny
+
+- name: Replace route-maps configurations of listed route-maps with provided configurations
+ cisco.nxos.nxos_route_maps:
+ config:
+ - route_map: rmap1
+ entries:
+ - sequence: 20
+ action: deny
+ description: rmap1-20-deny
+ match:
+ community:
+ community_list:
+ - BGPCommunity4
+ - BGPCommunity5
+ ip:
+ address:
+ prefix_lists:
+ - AllowPrefix1
+ set:
+ community:
+ local_as: True
+ state: replaced
+
+# Task output
+# -------------
+# before:
+# - route_map: rmap1
+# entries:
+# - action: permit
+# description: rmap1-10-permit
+# match:
+# as_number:
+# asn:
+# - '65564'
+# as_path:
+# - Allow40
+# ip:
+# address:
+# access_list: acl_1
+# sequence: 10
+#
+# - action: deny
+# description: rmap1-20-deny
+# match:
+# community:
+# community_list:
+# - BGPCommunity1
+# - BGPCommunity2
+# ip:
+# address:
+# prefix_lists:
+# - AllowPrefix1
+# - AllowPrefix2
+# sequence: 20
+# set:
+# dampening:
+# half_life: 30
+# max_suppress_time: 120
+# start_reuse_route: 1500
+# start_suppress_route: 10000
+#
+# - route_map: rmap2
+# entries:
+# - action: permit
+# continue_sequence: 40
+# description: rmap2-20-permit
+# match:
+# interfaces:
+# - Ethernet1/1
+# ipv6:
+# address:
+# prefix_lists:
+# - AllowIPv6Prefix
+# sequence: 20
+# set:
+# as_path:
+# prepend:
+# as_number:
+# - '65563'
+# - '65568'
+# - '65569'
+# comm_list: BGPCommunity
+#
+# - action: deny
+# description: rmap2-40-deny
+# match:
+# ip:
+# multicast:
+# group_range:
+# first: 239.0.0.1
+# last: 239.255.255.255
+# rp:
+# prefix: 192.0.2.0/24
+# rp_type: ASM
+# source: 203.0.113.0/24
+# route_types:
+# - level-1
+# - level-2
+# tags:
+# - 2
+# sequence: 40
+#
+# commands:
+# - no route-map rmap1 permit 10
+# - route-map rmap1 deny 20
+# - no match community BGPCommunity1 BGPCommunity2
+# - match community BGPCommunity4 BGPCommunity5
+# - no match ip address prefix-list AllowPrefix1 AllowPrefix2
+# - match ip address prefix-list AllowPrefix1
+# - no set dampening 30 1500 10000 120
+# - set community local-AS
+#
+# after:
+# - route_map: rmap1
+# entries:
+# - sequence: 20
+# action: deny
+# description: rmap1-20-deny
+# match:
+# community:
+# community_list:
+# - BGPCommunity4
+# - BGPCommunity5
+# ip:
+# address:
+# prefix_lists:
+# - AllowPrefix1
+# set:
+# community:
+# local_as: True
+#
+# - route_map: rmap2
+# entries:
+# - action: permit
+# continue_sequence: 40
+# description: rmap2-20-permit
+# match:
+# interfaces:
+# - Ethernet1/1
+# ipv6:
+# address:
+# prefix_lists:
+# - AllowIPv6Prefix
+# sequence: 20
+# set:
+# as_path:
+# prepend:
+# as_number:
+# - '65563'
+# - '65568'
+# - '65569'
+# comm_list: BGPCommunity
+#
+# - action: deny
+# description: rmap2-40-deny
+# match:
+# ip:
+# multicast:
+# group_range:
+# first: 239.0.0.1
+# last: 239.255.255.255
+# rp:
+# prefix: 192.0.2.0/24
+# rp_type: ASM
+# source: 203.0.113.0/24
+# route_types:
+# - level-1
+# - level-2
+# tags:
+# - 2
+# sequence: 40
+#
+
+# After state:
+# ------------
+# nxos-9k-rdo# show running-config | section "^route-map"
+# route-map rmap1 deny 20
+# description rmap1-20-deny
+# match community BGPCommunity4 BGPCommunity5
+# match ip address prefix-list AllowPrefix1
+# set community local-AS
+# route-map rmap2 permit 20
+# match interface Ethernet1/1
+# match ipv6 address prefix-list AllowIPv6Prefix
+# set as-path prepend 65563 65568 65569
+# description rmap2-20-permit
+# continue 40
+# set comm-list BGPCommunity delete
+# route-map rmap2 deny 40
+# match ip multicast source 203.0.113.0/24 group-range 239.0.0.1 to 239.255.255.255 rp 192.0.2.0/24 rp-type ASM
+# match route-type level-1 level-2
+# match tag 2
+# description rmap2-40-deny
+
+# Using overridden
+
+# Before state:
+# ------------
+# nxos-9k-rdo# show running-config | section "^route-map"
+# route-map rmap1 permit 10
+# match as-number 65564
+# match as-path Allow40
+# match ip address acl_1
+# description rmap1-10-permit
+# route-map rmap1 deny 20
+# match community BGPCommunity1 BGPCommunity2
+# match ip address prefix-list AllowPrefix1 AllowPrefix2
+# description rmap1-20-deny
+# set dampening 30 1500 10000 120
+# route-map rmap2 permit 20
+# match interface Ethernet1/1
+# match ipv6 address prefix-list AllowIPv6Prefix
+# set as-path prepend 65563 65568 65569
+# description rmap2-20-permit
+# continue 40
+# set comm-list BGPCommunity delete
+# route-map rmap2 deny 40
+# match ip multicast source 203.0.113.0/24 group-range 239.0.0.1 to 239.255.255.255 rp 192.0.2.0/24 rp-type ASM
+# match route-type level-1 level-2
+# match tag 2
+# description rmap2-40-deny
+
+- name: Override all route-maps configuration with provided configuration
+ cisco.nxos.nxos_route_maps:
+ config:
+ - route_map: rmap1
+ entries:
+ - sequence: 20
+ action: deny
+ description: rmap1-20-deny
+ match:
+ community:
+ community_list:
+ - BGPCommunity4
+ - BGPCommunity5
+ ip:
+ address:
+ prefix_lists:
+ - AllowPrefix1
+ set:
+ community:
+ local_as: True
+ state: overridden
+
+# Task output
+# -------------
+# before:
+# - route_map: rmap1
+# entries:
+# - action: permit
+# description: rmap1-10-permit
+# match:
+# as_number:
+# asn:
+# - '65564'
+# as_path:
+# - Allow40
+# ip:
+# address:
+# access_list: acl_1
+# sequence: 10
+#
+# - action: deny
+# description: rmap1-20-deny
+# match:
+# community:
+# community_list:
+# - BGPCommunity1
+# - BGPCommunity2
+# ip:
+# address:
+# prefix_lists:
+# - AllowPrefix1
+# - AllowPrefix2
+# sequence: 20
+# set:
+# dampening:
+# half_life: 30
+# max_suppress_time: 120
+# start_reuse_route: 1500
+# start_suppress_route: 10000
+#
+# - route_map: rmap2
+# entries:
+# - action: permit
+# continue_sequence: 40
+# description: rmap2-20-permit
+# match:
+# interfaces:
+# - Ethernet1/1
+# ipv6:
+# address:
+# prefix_lists:
+# - AllowIPv6Prefix
+# sequence: 20
+# set:
+# as_path:
+# prepend:
+# as_number:
+# - '65563'
+# - '65568'
+# - '65569'
+# comm_list: BGPCommunity
+#
+# - action: deny
+# description: rmap2-40-deny
+# match:
+# ip:
+# multicast:
+# group_range:
+# first: 239.0.0.1
+# last: 239.255.255.255
+# rp:
+# prefix: 192.0.2.0/24
+# rp_type: ASM
+# source: 203.0.113.0/24
+# route_types:
+# - level-1
+# - level-2
+# tags:
+# - 2
+# sequence: 40
+#
+# commands:
+# - no route-map rmap1 permit 10
+# - route-map rmap1 deny 20
+# - no match community BGPCommunity1 BGPCommunity2
+# - match community BGPCommunity4 BGPCommunity5
+# - no match ip address prefix-list AllowPrefix1 AllowPrefix2
+# - match ip address prefix-list AllowPrefix1
+# - no set dampening 30 1500 10000 120
+# - set community local-AS
+# - no route-map rmap2 permit 20
+# - no route-map rmap2 deny 40
+#
+# after:
+# - route_map: rmap1
+# entries:
+# - sequence: 20
+# action: deny
+# description: rmap1-20-deny
+# match:
+# community:
+# community_list:
+# - BGPCommunity4
+# - BGPCommunity5
+# ip:
+# address:
+# prefix_lists:
+# - AllowPrefix1
+# set:
+# community:
+# local_as: True
+#
+# After state:
+# ------------
+# nxos-9k-rdo# sh running-config | section "^route-map"
+# route-map rmap1 deny 20
+# description rmap1-20-deny
+# match community BGPCommunity4 BGPCommunity5
+# match ip address prefix-list AllowPrefix1
+# set community local-AS
+
+# Using deleted to delete a single route-map
+
+# Before state:
+# ------------
+# nxos-9k-rdo# show running-config | section "^route-map"
+# route-map rmap1 permit 10
+# match as-number 65564
+# match as-path Allow40
+# match ip address acl_1
+# description rmap1-10-permit
+# route-map rmap1 deny 20
+# match community BGPCommunity1 BGPCommunity2
+# match ip address prefix-list AllowPrefix1 AllowPrefix2
+# description rmap1-20-deny
+# set dampening 30 1500 10000 120
+# route-map rmap2 permit 20
+# match interface Ethernet1/1
+# match ipv6 address prefix-list AllowIPv6Prefix
+# set as-path prepend 65563 65568 65569
+# description rmap2-20-permit
+# continue 40
+# set comm-list BGPCommunity delete
+# route-map rmap2 deny 40
+# match ip multicast source 203.0.113.0/24 group-range 239.0.0.1 to 239.255.255.255 rp 192.0.2.0/24 rp-type ASM
+# match route-type level-1 level-2
+# match tag 2
+# description rmap2-40-deny
+
+- name: Delete single route-map
+ cisco.nxos.nxos_route_maps:
+ config:
+ - route_map: rmap1
+ state: deleted
+
+# Task output
+# -------------
+# before:
+# - route_map: rmap1
+# entries:
+# - action: permit
+# description: rmap1-10-permit
+# match:
+# as_number:
+# asn:
+# - '65564'
+# as_path:
+# - Allow40
+# ip:
+# address:
+# access_list: acl_1
+# sequence: 10
+#
+# - action: deny
+# description: rmap1-20-deny
+# match:
+# community:
+# community_list:
+# - BGPCommunity1
+# - BGPCommunity2
+# ip:
+# address:
+# prefix_lists:
+# - AllowPrefix1
+# - AllowPrefix2
+# sequence: 20
+# set:
+# dampening:
+# half_life: 30
+# max_suppress_time: 120
+# start_reuse_route: 1500
+# start_suppress_route: 10000
+#
+# - route_map: rmap2
+# entries:
+# - action: permit
+# continue_sequence: 40
+# description: rmap2-20-permit
+# match:
+# interfaces:
+# - Ethernet1/1
+# ipv6:
+# address:
+# prefix_lists:
+# - AllowIPv6Prefix
+# sequence: 20
+# set:
+# as_path:
+# prepend:
+# as_number:
+# - '65563'
+# - '65568'
+# - '65569'
+# comm_list: BGPCommunity
+#
+# - action: deny
+# description: rmap2-40-deny
+# match:
+# ip:
+# multicast:
+# group_range:
+# first: 239.0.0.1
+# last: 239.255.255.255
+# rp:
+# prefix: 192.0.2.0/24
+# rp_type: ASM
+# source: 203.0.113.0/24
+# route_types:
+# - level-1
+# - level-2
+# tags:
+# - 2
+# sequence: 40
+#
+# commands:
+# - no route-map rmap1 permit 10
+# - no route-map rmap1 deny 20
+#
+# after:
+# - route_map: rmap2
+# entries:
+# - action: permit
+# continue_sequence: 40
+# description: rmap2-20-permit
+# match:
+# interfaces:
+# - Ethernet1/1
+# ipv6:
+# address:
+# prefix_lists:
+# - AllowIPv6Prefix
+# sequence: 20
+# set:
+# as_path:
+# prepend:
+# as_number:
+# - '65563'
+# - '65568'
+# - '65569'
+# comm_list: BGPCommunity
+#
+# - action: deny
+# description: rmap2-40-deny
+# match:
+# ip:
+# multicast:
+# group_range:
+# first: 239.0.0.1
+# last: 239.255.255.255
+# rp:
+# prefix: 192.0.2.0/24
+# rp_type: ASM
+# source: 203.0.113.0/24
+# route_types:
+# - level-1
+# - level-2
+# tags:
+# - 2
+# sequence: 40
+#
+# After state:
+# ------------
+# nxos-9k-rdo# sh running-config | section "^route-map"
+# route-map rmap2 permit 20
+# match interface Ethernet1/1
+# match ipv6 address prefix-list AllowIPv6Prefix
+# set as-path prepend 65563 65568 65569
+# description rmap2-20-permit
+# continue 40
+# set comm-list BGPCommunity delete
+# route-map rmap2 deny 40
+# match ip multicast source 203.0.113.0/24 group-range 239.0.0.1 to 239.255.255.255 rp 192.0.2.0/24 rp-type ASM
+# match route-type level-1 level-2
+# match tag 2
+# description rmap2-40-deny
+
+# Using deleted to delete all route-maps from the device running-config
+
+# Before state:
+# ------------
+# nxos-9k-rdo# show running-config | section "^route-map"
+# route-map rmap1 permit 10
+# match as-number 65564
+# match as-path Allow40
+# match ip address acl_1
+# description rmap1-10-permit
+# route-map rmap1 deny 20
+# match community BGPCommunity1 BGPCommunity2
+# match ip address prefix-list AllowPrefix1 AllowPrefix2
+# description rmap1-20-deny
+# set dampening 30 1500 10000 120
+# route-map rmap2 permit 20
+# match interface Ethernet1/1
+# match ipv6 address prefix-list AllowIPv6Prefix
+# set as-path prepend 65563 65568 65569
+# description rmap2-20-permit
+# continue 40
+# set comm-list BGPCommunity delete
+# route-map rmap2 deny 40
+# match ip multicast source 203.0.113.0/24 group-range 239.0.0.1 to 239.255.255.255 rp 192.0.2.0/24 rp-type ASM
+# match route-type level-1 level-2
+# match tag 2
+# description rmap2-40-deny
+
+- name: Delete all route-maps
+ cisco.nxos.nxos_route_maps:
+ state: deleted
+
+# Task output
+# -------------
+# before:
+# - route_map: rmap1
+# entries:
+# - action: permit
+# description: rmap1-10-permit
+# match:
+# as_number:
+# asn:
+# - '65564'
+# as_path:
+# - Allow40
+# ip:
+# address:
+# access_list: acl_1
+# sequence: 10
+#
+# - action: deny
+# description: rmap1-20-deny
+# match:
+# community:
+# community_list:
+# - BGPCommunity1
+# - BGPCommunity2
+# ip:
+# address:
+# prefix_lists:
+# - AllowPrefix1
+# - AllowPrefix2
+# sequence: 20
+# set:
+# dampening:
+# half_life: 30
+# max_suppress_time: 120
+# start_reuse_route: 1500
+# start_suppress_route: 10000
+#
+# - route_map: rmap2
+# entries:
+# - action: permit
+# continue_sequence: 40
+# description: rmap2-20-permit
+# match:
+# interfaces:
+# - Ethernet1/1
+# ipv6:
+# address:
+# prefix_lists:
+# - AllowIPv6Prefix
+# sequence: 20
+# set:
+# as_path:
+# prepend:
+# as_number:
+# - '65563'
+# - '65568'
+# - '65569'
+# comm_list: BGPCommunity
+#
+# - action: deny
+# description: rmap2-40-deny
+# match:
+# ip:
+# multicast:
+# group_range:
+# first: 239.0.0.1
+# last: 239.255.255.255
+# rp:
+# prefix: 192.0.2.0/24
+# rp_type: ASM
+# source: 203.0.113.0/24
+# route_types:
+# - level-1
+# - level-2
+# tags:
+# - 2
+# sequence: 40
+#
+# commands:
+# - no route-map rmap1 permit 10
+# - no route-map rmap1 deny 20
+# - no route-map rmap2 permit 20
+# - no route-map rmap2 deny 40
+#
+# after: []
+#
+# After state:
+# ------------
+# nxos-9k-rdo# sh running-config | section "^route-map"
+
+- name: Render platform specific configuration lines with state rendered (without connecting to the device)
+ cisco.nxos.nxos_route_maps:
+ config:
+ - route_map: rmap1
+ entries:
+ - sequence: 10
+ action: permit
+ description: rmap1-10-permit
+ match:
+ ip:
+ address:
+ access_list: acl_1
+ as_path: Allow40
+ as_number:
+ asn: 65564
+
+ - sequence: 20
+ action: deny
+ description: rmap1-20-deny
+ match:
+ community:
+ community_list:
+ - BGPCommunity1
+ - BGPCommunity2
+ ip:
+ address:
+ prefix_lists:
+ - AllowPrefix1
+ - AllowPrefix2
+ set:
+ dampening:
+ half_life: 30
+ start_reuse_route: 1500
+ start_suppress_route: 10000
+ max_suppress_time: 120
+
+ - route_map: rmap2
+ entries:
+ - sequence: 20
+ action: permit
+ description: rmap2-20-permit
+ continue_sequence: 40
+ match:
+ ipv6:
+ address:
+ prefix_lists: AllowIPv6Prefix
+ interfaces: "{{ nxos_int1 }}"
+ set:
+ as_path:
+ prepend:
+ as_number:
+ - 65563
+ - 65568
+ - 65569
+ comm_list: BGPCommunity
+
+ - sequence: 40
+ action: deny
+ description: rmap2-40-deny
+ match:
+ route_types:
+ - level-1
+ - level-2
+ tags: 2
+ ip:
+ multicast:
+ rp:
+ prefix: 192.0.2.0/24
+ rp_type: ASM
+ source: 203.0.113.0/24
+ group_range:
+ first: 239.0.0.1
+ last: 239.255.255.255
+ state: rendered
+
+# Task Output (redacted)
+# -----------------------
+# rendered:
+# - "route-map rmap1 permit 10"
+# - "match as-number 65564"
+# - "match as-path Allow40"
+# - "match ip address acl_1"
+# - "description rmap1-10-permit"
+# - "route-map rmap1 deny 20"
+# - "match community BGPCommunity1 BGPCommunity2"
+# - "match ip address prefix-list AllowPrefix1 AllowPrefix2"
+# - "description rmap1-20-deny"
+# - "set dampening 30 1500 10000 120"
+# - "route-map rmap2 permit 20"
+# - "match interface Ethernet1/1"
+# - "match ipv6 address prefix-list AllowIPv6Prefix"
+# - "set as-path prepend 65563 65568 65569"
+# - "description rmap2-20-permit"
+# - "continue 40"
+# - "set comm-list BGPCommunity delete"
+# - "route-map rmap2 deny 40"
+# - "match ip multicast source 203.0.113.0/24 group-range 239.0.0.1 to 239.255.255.255 rp 192.0.2.0/24 rp-type ASM"
+# - "match route-type level-1 level-2"
+# - "match tag 2"
+# - "description rmap2-40-deny"
+
+# Using parsed
+
+# parsed.cfg
+# ------------
+# route-map rmap1 permit 10
+# match as-number 65564
+# match as-path Allow40
+# match ip address acl_1
+# description rmap1-10-permit
+# route-map rmap1 deny 20
+# match community BGPCommunity1 BGPCommunity2
+# match ip address prefix-list AllowPrefix1 AllowPrefix2
+# description rmap1-20-deny
+# set dampening 30 1500 10000 120
+# route-map rmap2 permit 20
+# match interface Ethernet1/1
+# match ipv6 address prefix-list AllowIPv6Prefix
+# set as-path prepend 65563 65568 65569
+# description rmap2-20-permit
+# continue 40
+# set comm-list BGPCommunity delete
+# route-map rmap2 deny 40
+# match ip multicast source 203.0.113.0/24 group-range 239.0.0.1 to 239.255.255.255 rp 192.0.2.0/24 rp-type ASM
+# match route-type level-1 level-2
+# match tag 2
+# description rmap2-40-deny
+
+- name: Parse externally provided route-maps configuration
+ cisco.nxos.nxos_route_maps:
+ running_config: "{{ lookup('file', './fixtures/parsed.cfg') }}"
+ state: parsed
+
+# Task output (redacted)
+# -----------------------
+# parsed:
+# - route_map: rmap1
+# entries:
+# - action: permit
+# description: rmap1-10-permit
+# match:
+# as_number:
+# asn:
+# - '65564'
+# as_path:
+# - Allow40
+# ip:
+# address:
+# access_list: acl_1
+# sequence: 10
+#
+# - action: deny
+# description: rmap1-20-deny
+# match:
+# community:
+# community_list:
+# - BGPCommunity1
+# - BGPCommunity2
+# ip:
+# address:
+# prefix_lists:
+# - AllowPrefix1
+# - AllowPrefix2
+# sequence: 20
+# set:
+# dampening:
+# half_life: 30
+# max_suppress_time: 120
+# start_reuse_route: 1500
+# start_suppress_route: 10000
+#
+# - route_map: rmap2
+# entries:
+# - action: permit
+# continue_sequence: 40
+# description: rmap2-20-permit
+# match:
+# interfaces:
+# - Ethernet1/1
+# ipv6:
+# address:
+# prefix_lists:
+# - AllowIPv6Prefix
+# sequence: 20
+# set:
+# as_path:
+# prepend:
+# as_number:
+# - '65563'
+# - '65568'
+# - '65569'
+# comm_list: BGPCommunity
+#
+# - action: deny
+# description: rmap2-40-deny
+# match:
+# ip:
+# multicast:
+# group_range:
+# first: 239.0.0.1
+# last: 239.255.255.255
+# rp:
+# prefix: 192.0.2.0/24
+# rp_type: ASM
+# source: 203.0.113.0/24
+# route_types:
+# - level-1
+# - level-2
+# tags:
+# - 2
+# sequence: 40
+
+# Using gathered
+
+# Existing route-map config
+# ---------------------------
+# nxos-9k-rdo# show running-config | section "^route-map"
+# route-map rmap1 permit 10
+# match as-number 65564
+# match as-path Allow40
+# match ip address acl_1
+# description rmap1-10-permit
+# route-map rmap2 permit 20
+# match interface Ethernet1/1
+# match ipv6 address prefix-list AllowIPv6Prefix
+# set as-path prepend 65563 65568 65569
+# description rmap2-20-permit
+# continue 40
+# set comm-list BGPCommunity delete
+
+- name: Gather route-maps facts using gathered
+ cisco.nxos.nxos_route_maps:
+ state: gathered
+
+# gathered:
+# - route_map: rmap1
+# entries:
+# - action: permit
+# description: rmap1-10-permit
+# match:
+# as_number:
+# asn:
+# - '65564'
+# as_path:
+# - Allow40
+# ip:
+# address:
+# access_list: acl_1
+# sequence: 10
+#
+# - route_map: rmap2
+# entries:
+# - action: permit
+# continue_sequence: 40
+# description: rmap2-20-permit
+# match:
+# interfaces:
+# - Ethernet1/1
+# ipv6:
+# address:
+# prefix_lists:
+# - AllowIPv6Prefix
+# sequence: 20
+# set:
+# as_path:
+# prepend:
+# as_number:
+# - '65563'
+# - '65568'
+# - '65569'
+# comm_list: BGPCommunity
+#
+"""
+
+RETURN = """
+before:
+ description: The configuration prior to the model invocation.
+ returned: always
+ type: dict
+ 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: dict
+ 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:
+ - "route-map rmap1 permit 10"
+ - "match as-number 65564"
+ - "match as-path Allow40"
+ - "match ip address acl_1"
+ - "description rmap1-10-permit"
+ - "route-map rmap1 deny 20"
+ - "match community BGPCommunity1 BGPCommunity2"
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.route_maps.route_maps import (
+ Route_mapsArgs,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.config.route_maps.route_maps import (
+ Route_maps,
+)
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ module = AnsibleModule(
+ argument_spec=Route_mapsArgs.argument_spec,
+ mutually_exclusive=[["config", "running_config"]],
+ required_if=[
+ ["state", "merged", ["config"]],
+ ["state", "replaced", ["config"]],
+ ["state", "overridden", ["config"]],
+ ["state", "rendered", ["config"]],
+ ["state", "parsed", ["running_config"]],
+ ],
+ supports_check_mode=True,
+ )
+
+ result = Route_maps(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_rpm.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_rpm.py
new file mode 100644
index 00000000..7d7ab4db
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_rpm.py
@@ -0,0 +1,396 @@
+#!/usr/bin/python
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_rpm
+extends_documentation_fragment:
+- cisco.nxos.nxos
+short_description: Install patch or feature rpms on Cisco NX-OS devices.
+description:
+- Install software maintenance upgrade (smu) RPMS and 3rd party RPMS on Cisco NX-OS
+ devices.
+version_added: 1.0.0
+author: Sai Chintalapudi (@saichint)
+notes:
+- Tested against NXOSv 7.0(3)I2(5), 7.0(3)I4(6), 7.0(3)I5(3), 7.0(3)I6(1), 7.0(3)I7(3)
+- Unsupported for Cisco MDS
+- For patches, the minimum platform version needed is 7.0(3)I2(5)
+- For feature rpms, the minimum platform version needed is 7.0(3)I6(1)
+- The module manages the entire RPM lifecycle (Add, activate, commit, deactivate,
+ remove)
+- For reload patches, this module is NOT idempotent until the patch is committed.
+options:
+ pkg:
+ description:
+ - Name of the RPM package.
+ type: str
+ file_system:
+ description:
+ - The remote file system of the device. If omitted, devices that support a file_system
+ parameter will use their default values.
+ default: bootflash
+ type: str
+ aggregate:
+ description:
+ - List of RPM/patch definitions.
+ type: list
+ elements: dict
+ suboptions:
+ pkg:
+ description:
+ - Name of the RPM package.
+ required: True
+ type: str
+ file_system:
+ description:
+ - The remote file system of the device. If omitted, devices that support a file_system
+ parameter will use their default values.
+ type: str
+ state:
+ description:
+ - If the state is present, the rpm will be installed, If the state is absent,
+ it will be removed.
+ choices:
+ - present
+ - absent
+ type: str
+ state:
+ description:
+ - If the state is present, the rpm will be installed, If the state is absent,
+ it will be removed.
+ default: present
+ choices:
+ - present
+ - absent
+ type: str
+"""
+
+EXAMPLES = """
+- cisco.nxos.nxos_rpm:
+ pkg: nxos.sample-n9k_ALL-1.0.0-7.0.3.I7.3.lib32_n9000.rpm
+"""
+
+RETURN = """
+commands:
+ description: commands sent to the device
+ returned: always
+ type: list
+ sample: ["install add bootflash:nxos.sample-n9k_ALL-1.0.0-7.0.3.I7.3.lib32_n9000.rpm forced",
+ "install activate nxos.sample-n9k_ALL-1.0.0-7.0.3.I7.3.lib32_n9000 forced",
+ "install commit nxos.sample-n9k_ALL-1.0.0-7.0.3.I7.3.lib32_n9000"]
+"""
+
+
+import time
+
+from copy import deepcopy
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import (
+ remove_default_spec,
+)
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ load_config,
+ run_commands,
+)
+
+
+def execute_show_command(command, module):
+ iteration = 0
+ cmds = [{"command": command, "output": "text"}]
+
+ while iteration < 10:
+ body = run_commands(module, cmds)[0]
+ if body:
+ return body
+ else:
+ time.sleep(2)
+ iteration += 1
+
+
+def remote_file_exists(module, dst, file_system):
+ command = "dir {0}:/{1}".format(file_system, dst)
+ body = execute_show_command(command, module)
+ if "No such file" in body:
+ return False
+ return True
+
+
+def config_cmd_operation(module, cmd):
+ iteration = 0
+ while iteration < 10:
+ msg = load_config(module, [cmd], True)
+ if msg:
+ if (
+ "another install operation is in progress" in msg[0].lower()
+ or "failed" in msg[0].lower()
+ ):
+ time.sleep(2)
+ iteration += 1
+ else:
+ return
+ else:
+ return
+
+
+def validate_operation(module, show_cmd, cfg_cmd, pkg, pkg_not_present):
+ iteration = 0
+ while iteration < 10:
+ body = execute_show_command(show_cmd, module)
+ if pkg_not_present:
+ if pkg not in body:
+ return
+ else:
+ if pkg in body:
+ return
+ time.sleep(2)
+ iteration += 1
+
+ err = 'Operation "{0}" Failed'.format(cfg_cmd)
+ module.fail_json(msg=err)
+
+
+def add_operation(module, show_cmd, file_system, full_pkg, pkg):
+ cmd = "install add {0}:{1}".format(file_system, full_pkg)
+ config_cmd_operation(module, cmd)
+ validate_operation(module, show_cmd, cmd, pkg, False)
+ return cmd
+
+
+def activate_operation(module, show_cmd, pkg):
+ cmd = "install activate {0} forced".format(pkg)
+ config_cmd_operation(module, cmd)
+ validate_operation(module, show_cmd, cmd, pkg, False)
+ return cmd
+
+
+def activate_reload(module, pkg, flag):
+ iteration = 0
+ if flag:
+ cmd = "install activate {0} forced".format(pkg)
+ else:
+ cmd = "install deactivate {0} forced".format(pkg)
+ opts = {"ignore_timeout": True}
+ while iteration < 10:
+ msg = load_config(module, [cmd], True, opts)
+ if msg:
+ if isinstance(msg[0], int):
+ if msg[0] == -32603:
+ return cmd
+ elif isinstance(msg[0], str):
+ if "socket is closed" in msg[0].lower():
+ return cmd
+ if (
+ "another install operation is in progress" in msg[0].lower()
+ or "failed" in msg[0].lower()
+ ):
+ time.sleep(2)
+ iteration += 1
+
+
+def commit_operation(module, show_cmd, pkg, flag):
+ cmd = "install commit {0}".format(pkg)
+ config_cmd_operation(module, cmd)
+ validate_operation(module, show_cmd, cmd, pkg, flag)
+ return cmd
+
+
+def deactivate_operation(module, show_cmd, pkg, flag):
+ cmd = "install deactivate {0} forced".format(pkg)
+ config_cmd_operation(module, cmd)
+ validate_operation(module, show_cmd, cmd, pkg, flag)
+ return cmd
+
+
+def terminal_operation(module, config):
+ if config:
+ cmd = "terminal dont-ask"
+ else:
+ cmd = "no terminal dont-ask"
+ config_cmd_operation(module, cmd)
+ return cmd
+
+
+def remove_operation(module, show_cmd, pkg):
+ commands = []
+ commands.append(terminal_operation(module, True))
+ cmd = "install remove {0} forced".format(pkg)
+ config_cmd_operation(module, cmd)
+ validate_operation(module, show_cmd, cmd, pkg, True)
+ commands.append(cmd)
+ commands.append(terminal_operation(module, False))
+ return commands
+
+
+def install_remove_rpm(module, full_pkg, file_system, state):
+ commands = []
+ reload_patch = False
+
+ splitted_pkg = full_pkg.split(".")
+ pkg = ".".join(splitted_pkg[0:-1])
+
+ show_inactive = "show install inactive"
+ show_active = "show install active"
+ show_commit = "show install committed"
+ show_patches = "show install patches"
+ show_pkg_info = "show install pkg-info {0}".format(pkg)
+
+ if state == "present":
+ inactive_body = execute_show_command(show_inactive, module)
+ active_body = execute_show_command(show_active, module)
+
+ if pkg not in inactive_body and pkg not in active_body:
+ commands.append(add_operation(module, show_inactive, file_system, full_pkg, pkg))
+
+ patch_type_body = execute_show_command(show_pkg_info, module)
+ if patch_type_body and "Patch Type : reload" in patch_type_body:
+ # This is reload smu/patch rpm
+ reload_patch = True
+
+ if pkg not in active_body:
+ if reload_patch:
+ commands.append(activate_reload(module, pkg, True))
+ return commands
+ else:
+ commands.append(activate_operation(module, show_active, pkg))
+
+ commit_body = execute_show_command(show_commit, module)
+ if pkg not in commit_body:
+ patch_body = execute_show_command(show_patches, module)
+ if pkg in patch_body:
+ # This is smu/patch rpm
+ commands.append(commit_operation(module, show_commit, pkg, False))
+ else:
+ err = 'Operation "install activate {0} forced" Failed'.format(pkg)
+ module.fail_json(msg=err)
+
+ else:
+ commit_body = execute_show_command(show_commit, module)
+ active_body = execute_show_command(show_active, module)
+
+ patch_type_body = execute_show_command(show_pkg_info, module)
+ if patch_type_body and "Patch Type : reload" in patch_type_body:
+ # This is reload smu/patch rpm
+ reload_patch = True
+
+ if pkg in commit_body and pkg in active_body:
+ if reload_patch:
+ commands.append(activate_reload(module, pkg, False))
+ return commands
+ else:
+ commands.append(deactivate_operation(module, show_active, pkg, True))
+ commit_body = execute_show_command(show_commit, module)
+ if pkg in commit_body:
+ # This is smu/patch rpm
+ commands.append(commit_operation(module, show_commit, pkg, True))
+ commands.extend(remove_operation(module, show_inactive, pkg))
+
+ elif pkg in commit_body:
+ # This is smu/patch rpm
+ commands.append(commit_operation(module, show_commit, pkg, True))
+ commands.extend(remove_operation(module, show_inactive, pkg))
+
+ elif pkg in active_body:
+ # This is smu/patch rpm
+ if reload_patch:
+ commands.append(activate_reload(module, pkg, False))
+ return commands
+ else:
+ commands.append(deactivate_operation(module, show_inactive, pkg, False))
+ commands.extend(remove_operation(module, show_inactive, pkg))
+
+ else:
+ inactive_body = execute_show_command(show_inactive, module)
+ if pkg in inactive_body:
+ commands.extend(remove_operation(module, show_inactive, pkg))
+
+ return commands
+
+
+def main():
+ element_spec = dict(
+ pkg=dict(type="str"),
+ file_system=dict(type="str", default="bootflash"),
+ state=dict(choices=["absent", "present"], default="present"),
+ )
+
+ aggregate_spec = deepcopy(element_spec)
+ aggregate_spec["pkg"] = dict(required=True)
+
+ # remove default in aggregate spec, to handle common arguments
+ remove_default_spec(aggregate_spec)
+
+ argument_spec = dict(aggregate=dict(type="list", elements="dict", options=aggregate_spec))
+
+ argument_spec.update(element_spec)
+ required_one_of = [["pkg", "aggregate"]]
+ mutually_exclusive = [["pkg", "aggregate"]]
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ required_one_of=required_one_of,
+ mutually_exclusive=mutually_exclusive,
+ supports_check_mode=False,
+ )
+
+ warnings = list()
+ results = {"changed": False, "commands": [], "warnings": warnings}
+
+ aggregate = module.params.get("aggregate")
+ objects = []
+ if aggregate:
+ for item in aggregate:
+ for key in item:
+ if item.get(key) is None:
+ item[key] = module.params[key]
+
+ d = item.copy()
+ objects.append(d)
+ else:
+ objects.append(
+ {
+ "pkg": module.params["pkg"],
+ "file_system": module.params["file_system"],
+ "state": module.params["state"],
+ },
+ )
+
+ for obj in objects:
+ if obj["state"] == "present":
+ remote_exists = remote_file_exists(module, obj["pkg"], file_system=obj["file_system"])
+
+ if not remote_exists:
+ module.fail_json(msg="The requested package doesn't exist on the device")
+
+ cmds = install_remove_rpm(module, obj["pkg"], obj["file_system"], obj["state"])
+
+ if cmds:
+ results["changed"] = True
+ results["commands"].extend(cmds)
+
+ module.exit_json(**results)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_snapshot.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_snapshot.py
new file mode 100644
index 00000000..9697a128
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_snapshot.py
@@ -0,0 +1,413 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+
+DOCUMENTATION = """
+module: nxos_snapshot
+extends_documentation_fragment:
+- cisco.nxos.nxos
+short_description: Manage snapshots of the running states of selected features.
+description:
+- Create snapshots of the running states of selected features, add new show commands
+ for snapshot creation, delete and compare existing snapshots.
+version_added: 1.0.0
+author:
+- Gabriele Gerbino (@GGabriele)
+notes:
+- Tested against NXOSv 7.3.(0)D1(1) on VIRL
+- Unsupported for Cisco MDS
+- C(transport=cli) may cause timeout errors.
+- The C(element_key1) and C(element_key2) parameter specify the tags used to distinguish
+ among row entries. In most cases, only the element_key1 parameter needs to specified
+ to be able to distinguish among row entries.
+- C(action=compare) will always store a comparison report on a local file.
+options:
+ action:
+ description:
+ - Define what snapshot action the module would perform.
+ required: true
+ choices:
+ - add
+ - compare
+ - create
+ - delete
+ - delete_all
+ type: str
+ snapshot_name:
+ description:
+ - Snapshot name, to be used when C(action=create) or C(action=delete).
+ type: str
+ description:
+ description:
+ - Snapshot description to be used when C(action=create).
+ type: str
+ snapshot1:
+ description:
+ - First snapshot to be used when C(action=compare).
+ type: str
+ snapshot2:
+ description:
+ - Second snapshot to be used when C(action=compare).
+ type: str
+ comparison_results_file:
+ description:
+ - Name of the file where snapshots comparison will be stored when C(action=compare).
+ type: str
+ compare_option:
+ description:
+ - Snapshot options to be used when C(action=compare).
+ choices:
+ - summary
+ - ipv4routes
+ - ipv6routes
+ type: str
+ section:
+ description:
+ - Used to name the show command output, to be used when C(action=add).
+ type: str
+ show_command:
+ description:
+ - Specify a new show command, to be used when C(action=add).
+ type: str
+ row_id:
+ description:
+ - Specifies the tag of each row entry of the show command's XML output, to be
+ used when C(action=add).
+ type: str
+ element_key1:
+ description:
+ - Specify the tags used to distinguish among row entries, to be used when C(action=add).
+ type: str
+ element_key2:
+ description:
+ - Specify the tags used to distinguish among row entries, to be used when C(action=add).
+ type: str
+ save_snapshot_locally:
+ description:
+ - Specify to locally store a new created snapshot, to be used when C(action=create).
+ type: bool
+ default: no
+ path:
+ description:
+ - Specify the path of the file where new created snapshot or snapshots comparison
+ will be stored, to be used when C(action=create) and C(save_snapshot_locally=true)
+ or C(action=compare).
+ default: ./
+ type: str
+"""
+
+EXAMPLES = """
+# Create a snapshot and store it locally
+- cisco.nxos.nxos_snapshot:
+ action: create
+ snapshot_name: test_snapshot
+ description: Done with Ansible
+ save_snapshot_locally: true
+ path: /home/user/snapshots/
+
+# Delete a snapshot
+- cisco.nxos.nxos_snapshot:
+ action: delete
+ snapshot_name: test_snapshot
+
+# Delete all existing snapshots
+- cisco.nxos.nxos_snapshot:
+ action: delete_all
+
+# Add a show command for snapshots creation
+- cisco.nxos.nxos_snapshot:
+ section: myshow
+ show_command: show ip interface brief
+ row_id: ROW_intf
+ element_key1: intf-name
+
+# Compare two snapshots
+- cisco.nxos.nxos_snapshot:
+ action: compare
+ snapshot1: pre_snapshot
+ snapshot2: post_snapshot
+ comparison_results_file: compare_snapshots.txt
+ compare_option: summary
+ path: ../snapshot_reports/
+"""
+
+RETURN = """
+commands:
+ description: commands sent to the device
+ returned: verbose mode
+ type: list
+ sample: ["snapshot create post_snapshot Post-snapshot"]
+"""
+
+import os
+import re
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ load_config,
+ run_commands,
+)
+
+
+def execute_show_command(command, module):
+ command = [{"command": command, "output": "text"}]
+
+ return run_commands(module, command)
+
+
+def get_existing(module):
+ existing = []
+ command = "show snapshots"
+
+ body = execute_show_command(command, module)[0]
+ if body:
+ split_body = body.splitlines()
+ snapshot_regex = (
+ r"(?P<name>\S+)\s+(?P<date>\w+\s+\w+\s+\d+\s+\d+"
+ r":\d+:\d+\s+\d+)\s+(?P<description>.*)"
+ )
+ for snapshot in split_body:
+ temp = {}
+ try:
+ match_snapshot = re.match(snapshot_regex, snapshot, re.DOTALL)
+ snapshot_group = match_snapshot.groupdict()
+ temp["name"] = snapshot_group["name"]
+ temp["date"] = snapshot_group["date"]
+ temp["description"] = snapshot_group["description"]
+ existing.append(temp)
+ except AttributeError:
+ pass
+
+ return existing
+
+
+def action_create(module, existing_snapshots):
+ commands = list()
+ exist = False
+ for snapshot in existing_snapshots:
+ if module.params["snapshot_name"] == snapshot["name"]:
+ exist = True
+
+ if exist is False:
+ commands.append(
+ "snapshot create {0} {1}".format(
+ module.params["snapshot_name"],
+ module.params["description"],
+ ),
+ )
+
+ return commands
+
+
+def action_add(module, existing_snapshots):
+ commands = list()
+ command = "show snapshot sections"
+ sections = []
+ body = execute_show_command(command, module)[0]
+
+ if body:
+ section_regex = r".*\[(?P<section>\S+)\].*"
+ split_body = body.split("\n\n")
+ for section in split_body:
+ temp = {}
+ for line in section.splitlines():
+ try:
+ match_section = re.match(section_regex, section, re.DOTALL)
+ temp["section"] = match_section.groupdict()["section"]
+ except (AttributeError, KeyError):
+ pass
+
+ if "show command" in line:
+ temp["show_command"] = line.split("show command: ")[1]
+ elif "row id" in line:
+ temp["row_id"] = line.split("row id: ")[1]
+ elif "key1" in line:
+ temp["element_key1"] = line.split("key1: ")[1]
+ elif "key2" in line:
+ temp["element_key2"] = line.split("key2: ")[1]
+
+ if temp:
+ sections.append(temp)
+
+ proposed = {
+ "section": module.params["section"],
+ "show_command": module.params["show_command"],
+ "row_id": module.params["row_id"],
+ "element_key1": module.params["element_key1"],
+ "element_key2": module.params["element_key2"] or "-",
+ }
+
+ if proposed not in sections:
+ if module.params["element_key2"]:
+ commands.append(
+ 'snapshot section add {0} "{1}" {2} {3} {4}'.format(
+ module.params["section"],
+ module.params["show_command"],
+ module.params["row_id"],
+ module.params["element_key1"],
+ module.params["element_key2"],
+ ),
+ )
+ else:
+ commands.append(
+ 'snapshot section add {0} "{1}" {2} {3}'.format(
+ module.params["section"],
+ module.params["show_command"],
+ module.params["row_id"],
+ module.params["element_key1"],
+ ),
+ )
+
+ return commands
+
+
+def action_compare(module, existing_snapshots):
+ command = "show snapshot compare {0} {1}".format(
+ module.params["snapshot1"],
+ module.params["snapshot2"],
+ )
+
+ if module.params["compare_option"]:
+ command += " {0}".format(module.params["compare_option"])
+
+ body = execute_show_command(command, module)[0]
+ return body
+
+
+def action_delete(module, existing_snapshots):
+ commands = list()
+
+ exist = False
+ for snapshot in existing_snapshots:
+ if module.params["snapshot_name"] == snapshot["name"]:
+ exist = True
+
+ if exist:
+ commands.append("snapshot delete {0}".format(module.params["snapshot_name"]))
+
+ return commands
+
+
+def action_delete_all(module, existing_snapshots):
+ commands = list()
+ if existing_snapshots:
+ commands.append("snapshot delete all")
+ return commands
+
+
+def invoke(name, *args, **kwargs):
+ func = globals().get(name)
+ if func:
+ return func(*args, **kwargs)
+
+
+def write_on_file(content, filename, module):
+ path = module.params["path"]
+ if path[-1] != "/":
+ path += "/"
+ filepath = "{0}{1}".format(path, filename)
+ try:
+ report = open(filepath, "w")
+ report.write(content)
+ report.close()
+ except Exception:
+ module.fail_json(msg="Error while writing on file.")
+
+ return filepath
+
+
+def main():
+ argument_spec = dict(
+ action=dict(
+ required=True,
+ choices=["create", "add", "compare", "delete", "delete_all"],
+ ),
+ snapshot_name=dict(type="str"),
+ description=dict(type="str"),
+ snapshot1=dict(type="str"),
+ snapshot2=dict(type="str"),
+ compare_option=dict(choices=["summary", "ipv4routes", "ipv6routes"]),
+ comparison_results_file=dict(type="str"),
+ section=dict(type="str"),
+ show_command=dict(type="str"),
+ row_id=dict(type="str"),
+ element_key1=dict(type="str", no_log=False),
+ element_key2=dict(type="str", no_log=False),
+ save_snapshot_locally=dict(type="bool", default=False),
+ path=dict(type="str", default="./"),
+ )
+
+ required_if = [
+ (
+ "action",
+ "compare",
+ ["snapshot1", "snapshot2", "comparison_results_file"],
+ ),
+ ("action", "create", ["snapshot_name", "description"]),
+ (
+ "action",
+ "add",
+ ["section", "show_command", "row_id", "element_key1"],
+ ),
+ ("action", "delete", ["snapshot_name"]),
+ ]
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ required_if=required_if,
+ supports_check_mode=True,
+ )
+
+ action = module.params["action"]
+ comparison_results_file = module.params["comparison_results_file"]
+
+ if not os.path.isdir(module.params["path"]):
+ module.fail_json(msg="{0} is not a valid directory name.".format(module.params["path"]))
+
+ existing_snapshots = invoke("get_existing", module)
+ action_results = invoke("action_%s" % action, module, existing_snapshots)
+
+ result = {"changed": False, "commands": []}
+
+ if not module.check_mode:
+ if action == "compare":
+ result["commands"] = []
+
+ if module.params["path"] and comparison_results_file:
+ snapshot1 = module.params["snapshot1"]
+ snapshot2 = module.params["snapshot2"]
+ compare_option = module.params["compare_option"]
+ command = "show snapshot compare {0} {1}".format(snapshot1, snapshot2)
+ if compare_option:
+ command += " {0}".format(compare_option)
+ content = execute_show_command(command, module)[0]
+ if content:
+ write_on_file(content, comparison_results_file, module)
+ else:
+ if action_results:
+ load_config(module, action_results)
+ result["commands"] = action_results
+ result["changed"] = True
+
+ if (
+ action == "create"
+ and module.params["path"]
+ and module.params["save_snapshot_locally"]
+ ):
+ command = "show snapshot dump {0} | json".format(module.params["snapshot_name"])
+ content = execute_show_command(command, module)[0]
+ if content:
+ write_on_file(str(content), module.params["snapshot_name"], module)
+
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_snmp_community.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_snmp_community.py
new file mode 100644
index 00000000..4e94b1f0
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_snmp_community.py
@@ -0,0 +1,254 @@
+#!/usr/bin/python
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_snmp_community
+extends_documentation_fragment:
+- cisco.nxos.nxos
+short_description: (deprecated, removed after 2024-01-01) Manages SNMP community configs.
+description:
+- Manages SNMP community configuration.
+version_added: 1.0.0
+deprecated:
+ alternative: nxos_snmp_server
+ why: Updated modules released with more functionality
+ removed_at_date: '2024-01-01'
+author:
+- Jason Edelman (@jedelman8)
+- Gabriele Gerbino (@GGabriele)
+notes:
+- Tested against NXOSv 7.3.(0)D1(1) on VIRL
+- Limited Support for Cisco MDS
+options:
+ community:
+ description:
+ - Case-sensitive community string.
+ required: true
+ type: str
+ access:
+ description:
+ - Access type for community.
+ choices:
+ - ro
+ - rw
+ type: str
+ group:
+ description:
+ - Group to which the community belongs.
+ type: str
+ acl:
+ description:
+ - ACL name to filter snmp requests or keyword 'default'.
+ type: str
+ state:
+ description:
+ - Manage the state of the resource.
+ default: present
+ choices:
+ - present
+ - absent
+ type: str
+"""
+
+EXAMPLES = """
+# ensure snmp community is configured
+- cisco.nxos.nxos_snmp_community:
+ community: TESTING7
+ group: network-operator
+ state: present
+"""
+
+RETURN = """
+commands:
+ description: commands sent to the device
+ returned: always
+ type: list
+ sample: ["snmp-server community TESTING7 group network-operator"]
+"""
+
+import re
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ load_config,
+ run_commands,
+)
+
+
+def execute_show_command(command, module):
+ if "show run" not in command:
+ output = "json"
+ else:
+ output = "text"
+ cmds = [{"command": command, "output": output}]
+
+ body = run_commands(module, cmds)
+ return body
+
+
+def flatten_list(command_lists):
+ flat_command_list = []
+ for command in command_lists:
+ if isinstance(command, list):
+ flat_command_list.extend(command)
+ else:
+ flat_command_list.append(command)
+ return flat_command_list
+
+
+def get_snmp_groups(module):
+ data = execute_show_command("show snmp group", module)[0]
+ group_list = []
+
+ try:
+ group_table = data["TABLE_role"]["ROW_role"]
+ for group in group_table:
+ group_list.append(group["role_name"])
+ except (KeyError, AttributeError):
+ pass
+
+ return group_list
+
+
+def get_snmp_community(module, name):
+ command = "show run snmp all | grep word-exp {0}".format(name)
+ data = execute_show_command(command, module)[0]
+ community_dict = {}
+
+ if not data:
+ return community_dict
+
+ community_re = r"snmp-server community (\S+)"
+ mo = re.search(community_re, data)
+ if mo:
+ community_name = mo.group(1)
+ else:
+ return community_dict
+
+ community_dict["group"] = None
+ group_re = r"snmp-server community {0} group (\S+)".format(community_name)
+ mo = re.search(group_re, data)
+ if mo:
+ community_dict["group"] = mo.group(1)
+
+ community_dict["acl"] = None
+ acl_re = r"snmp-server community {0} use-acl (\S+)".format(community_name)
+ mo = re.search(acl_re, data)
+ if mo:
+ community_dict["acl"] = mo.group(1)
+
+ return community_dict
+
+
+def config_snmp_community(delta, community):
+ CMDS = {
+ "group": "snmp-server community {0} group {group}",
+ "acl": "snmp-server community {0} use-acl {acl}",
+ "no_acl": "no snmp-server community {0} use-acl {no_acl}",
+ }
+ commands = []
+ for k in delta.keys():
+ cmd = CMDS.get(k).format(community, **delta)
+ if cmd:
+ if "group" in cmd:
+ commands.insert(0, cmd)
+ else:
+ commands.append(cmd)
+ cmd = None
+ return commands
+
+
+def main():
+ argument_spec = dict(
+ community=dict(required=True, type="str"),
+ access=dict(choices=["ro", "rw"]),
+ group=dict(type="str"),
+ acl=dict(type="str"),
+ state=dict(choices=["absent", "present"], default="present"),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ required_one_of=[["access", "group"]],
+ mutually_exclusive=[["access", "group"]],
+ supports_check_mode=True,
+ )
+
+ warnings = list()
+ results = {"changed": False, "commands": [], "warnings": warnings}
+
+ access = module.params["access"]
+ group = module.params["group"]
+ community = module.params["community"]
+ acl = module.params["acl"]
+ state = module.params["state"]
+
+ if access:
+ if access == "ro":
+ group = "network-operator"
+ elif access == "rw":
+ group = "network-admin"
+
+ # group check - ensure group being configured exists on the device
+ configured_groups = get_snmp_groups(module)
+
+ if group not in configured_groups:
+ module.fail_json(msg="Group not on switch. Please add before moving forward")
+
+ existing = get_snmp_community(module, community)
+ args = dict(group=group, acl=acl)
+ proposed = dict((k, v) for k, v in args.items() if v is not None)
+ delta = dict(set(proposed.items()).difference(existing.items()))
+ if delta.get("acl") == "default":
+ delta.pop("acl")
+ if existing.get("acl"):
+ delta["no_acl"] = existing.get("acl")
+
+ commands = []
+
+ if state == "absent":
+ if existing:
+ command = "no snmp-server community {0}".format(community)
+ commands.append(command)
+ elif state == "present":
+ if delta:
+ command = config_snmp_community(dict(delta), community)
+ commands.append(command)
+
+ cmds = flatten_list(commands)
+
+ if cmds:
+ results["changed"] = True
+ if not module.check_mode:
+ load_config(module, cmds)
+
+ if "configure" in cmds:
+ cmds.pop(0)
+ results["commands"] = cmds
+
+ module.exit_json(**results)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_snmp_contact.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_snmp_contact.py
new file mode 100644
index 00000000..49dbc8a8
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_snmp_contact.py
@@ -0,0 +1,151 @@
+#!/usr/bin/python
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_snmp_contact
+extends_documentation_fragment:
+- cisco.nxos.nxos
+short_description: (deprecated, removed after 2024-01-01) Manages SNMP contact info.
+description:
+- Manages SNMP contact information.
+version_added: 1.0.0
+deprecated:
+ alternative: nxos_snmp_server
+ why: Updated modules released with more functionality
+ removed_at_date: '2024-01-01'
+author:
+- Jason Edelman (@jedelman8)
+- Gabriele Gerbino (@GGabriele)
+notes:
+- Tested against NXOSv 7.3.(0)D1(1) on VIRL
+- Limited Support for Cisco MDS
+- C(state=absent) removes the contact configuration if it is configured.
+options:
+ contact:
+ description:
+ - Contact information.
+ required: true
+ type: str
+ state:
+ description:
+ - Manage the state of the resource.
+ default: present
+ choices:
+ - present
+ - absent
+ type: str
+"""
+
+EXAMPLES = """
+# ensure snmp contact is configured
+- cisco.nxos.nxos_snmp_contact:
+ contact: Test
+ state: present
+"""
+
+RETURN = """
+commands:
+ description: commands sent to the device
+ returned: always
+ type: list
+ sample: ["snmp-server contact New_Test"]
+"""
+
+
+import re
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ load_config,
+ run_commands,
+)
+
+
+def execute_show_command(command, module):
+ command = {"command": command, "output": "text"}
+
+ return run_commands(module, command)
+
+
+def flatten_list(command_lists):
+ flat_command_list = []
+ for command in command_lists:
+ if isinstance(command, list):
+ flat_command_list.extend(command)
+ else:
+ flat_command_list.append(command)
+ return flat_command_list
+
+
+def get_snmp_contact(module):
+ contact = {}
+ contact_regex = r"^\s*snmp-server\scontact\s(?P<contact>.+)$"
+
+ body = execute_show_command("show run snmp", module)[0]
+ match_contact = re.search(contact_regex, body, re.M)
+ if match_contact:
+ contact["contact"] = match_contact.group("contact")
+
+ return contact
+
+
+def main():
+ argument_spec = dict(
+ contact=dict(required=True, type="str"),
+ state=dict(choices=["absent", "present"], default="present"),
+ )
+
+ module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)
+
+ warnings = list()
+ results = {"changed": False, "commands": [], "warnings": warnings}
+
+ contact = module.params["contact"]
+ state = module.params["state"]
+
+ existing = get_snmp_contact(module)
+ commands = []
+
+ if state == "absent":
+ if existing and existing["contact"] == contact:
+ commands.append("no snmp-server contact")
+ elif state == "present":
+ if not existing or existing["contact"] != contact:
+ commands.append("snmp-server contact {0}".format(contact))
+
+ cmds = flatten_list(commands)
+ if cmds:
+ results["changed"] = True
+ if not module.check_mode:
+ load_config(module, cmds)
+
+ if "configure" in cmds:
+ cmds.pop(0)
+ results["commands"] = cmds
+
+ module.exit_json(**results)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_snmp_host.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_snmp_host.py
new file mode 100644
index 00000000..3c23374c
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_snmp_host.py
@@ -0,0 +1,513 @@
+#!/usr/bin/python
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_snmp_host
+extends_documentation_fragment:
+- cisco.nxos.nxos
+short_description: (deprecated, removed after 2024-01-01) Manages SNMP host configuration.
+description:
+- Manages SNMP host configuration parameters.
+version_added: 1.0.0
+deprecated:
+ alternative: nxos_snmp_server
+ why: Updated modules released with more functionality
+ removed_at_date: '2024-01-01'
+author:
+- Jason Edelman (@jedelman8)
+- Gabriele Gerbino (@GGabriele)
+notes:
+- Tested against NXOSv 7.3.(0)D1(1) on VIRL
+- Limited Support for Cisco MDS
+- C(state=absent) removes the host configuration if it is configured.
+options:
+ snmp_host:
+ description:
+ - IP address of hostname of target host.
+ required: true
+ type: str
+ version:
+ description:
+ - SNMP version. If this is not specified, v1 is used.
+ choices:
+ - v1
+ - v2c
+ - v3
+ type: str
+ v3:
+ description:
+ - Use this when verion is v3. SNMPv3 Security level.
+ choices:
+ - noauth
+ - auth
+ - priv
+ type: str
+ community:
+ description:
+ - Community string or v3 username.
+ type: str
+ udp:
+ description:
+ - UDP port number (0-65535).
+ default: '162'
+ type: str
+ snmp_type:
+ description:
+ - type of message to send to host. If this is not specified, trap type is used.
+ choices:
+ - trap
+ - inform
+ type: str
+ vrf:
+ description:
+ - VRF to use to source traffic to source. If state = absent, the vrf is removed.
+ type: str
+ vrf_filter:
+ description:
+ - Name of VRF to filter. If state = absent, the vrf is removed from the filter.
+ type: str
+ src_intf:
+ description:
+ - Source interface. Must be fully qualified interface name. If state = absent,
+ the interface is removed.
+ type: str
+ state:
+ description:
+ - Manage the state of the resource. If state = present, the host is added to the
+ configuration. If only vrf and/or vrf_filter and/or src_intf are given, they
+ will be added to the existing host configuration. If state = absent, the host
+ is removed if community parameter is given. It is possible to remove only vrf
+ and/or src_int and/or vrf_filter by providing only those parameters and no community
+ parameter.
+ default: present
+ choices:
+ - present
+ - absent
+ type: str
+"""
+
+EXAMPLES = """
+# ensure snmp host is configured
+- cisco.nxos.nxos_snmp_host:
+ snmp_host: 192.0.2.3
+ community: TESTING
+ state: present
+"""
+
+RETURN = """
+commands:
+ description: commands sent to the device
+ returned: always
+ type: list
+ sample: ["snmp-server host 192.0.2.3 filter-vrf another_test_vrf"]
+"""
+
+
+import re
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ load_config,
+ run_commands,
+)
+
+
+def execute_show_command(command, module):
+ command = {"command": command, "output": "json"}
+
+ return run_commands(module, command)
+
+
+def apply_key_map(key_map, table):
+ new_dict = {}
+ for key, value in table.items():
+ new_key = key_map.get(key)
+ if new_key:
+ value = table.get(key)
+ if value:
+ new_dict[new_key] = str(value)
+ else:
+ new_dict[new_key] = value
+ return new_dict
+
+
+def flatten_list(command_lists):
+ flat_command_list = []
+ for command in command_lists:
+ if isinstance(command, list):
+ flat_command_list.extend(command)
+ else:
+ flat_command_list.append(command)
+ return flat_command_list
+
+
+def get_snmp_host(host, udp, module):
+ body = execute_show_command("show snmp host", module)
+
+ host_map = {
+ "port": "udp",
+ "version": "version",
+ "level": "v3",
+ "type": "snmp_type",
+ "secname": "community",
+ }
+
+ host_map_5k = {
+ "port": "udp",
+ "version": "version",
+ "sec_level": "v3",
+ "notif_type": "snmp_type",
+ "commun_or_user": "community",
+ }
+
+ resource = {}
+
+ if body:
+ try:
+ resource_table = body[0]["TABLE_host"]["ROW_host"]
+
+ if isinstance(resource_table, dict):
+ resource_table = [resource_table]
+
+ for each in resource_table:
+ key = str(each["host"]) + "_" + str(each["port"]).strip()
+ src = each.get("src_intf")
+ host_resource = apply_key_map(host_map, each)
+
+ if src:
+ host_resource["src_intf"] = src
+ if re.search(r"interface:", src):
+ host_resource["src_intf"] = src.split(":")[1].strip()
+
+ vrf_filt = each.get("TABLE_vrf_filters")
+ if vrf_filt:
+ vrf_filter = vrf_filt["ROW_vrf_filters"]["vrf_filter"].split(":")[1].split(",")
+ filters = [vrf.strip() for vrf in vrf_filter]
+ host_resource["vrf_filter"] = filters
+
+ vrf = each.get("vrf")
+ if vrf:
+ host_resource["vrf"] = vrf.split(":")[1].strip()
+ resource[key] = host_resource
+ except KeyError:
+ # Handle the 5K case
+ try:
+ resource_table = body[0]["TABLE_hosts"]["ROW_hosts"]
+
+ if isinstance(resource_table, dict):
+ resource_table = [resource_table]
+
+ for each in resource_table:
+ key = str(each["address"]) + "_" + str(each["port"]).strip()
+ src = each.get("src_intf")
+ host_resource = apply_key_map(host_map_5k, each)
+
+ if src:
+ host_resource["src_intf"] = src
+ if re.search(r"interface:", src):
+ host_resource["src_intf"] = src.split(":")[1].strip()
+
+ vrf = each.get("use_vrf_name")
+ if vrf:
+ host_resource["vrf"] = vrf.strip()
+
+ vrf_filt = each.get("TABLE_filter_vrf")
+ if vrf_filt:
+ vrf_filter = vrf_filt["ROW_filter_vrf"]["filter_vrf_name"].split(",")
+ filters = [vrf.strip() for vrf in vrf_filter]
+ host_resource["vrf_filter"] = filters
+
+ resource[key] = host_resource
+ except (KeyError, AttributeError, TypeError):
+ return resource
+ except (AttributeError, TypeError):
+ return resource
+
+ find = resource.get(host + "_" + udp)
+
+ if find:
+ fix_find = {}
+ for key, value in find.items():
+ if isinstance(value, str):
+ fix_find[key] = value.strip()
+ else:
+ fix_find[key] = value
+ return fix_find
+
+ return {}
+
+
+def remove_snmp_host(host, udp, existing):
+ commands = []
+ if existing["version"] == "v3":
+ existing["version"] = "3"
+ command = "no snmp-server host {0} {snmp_type} version \
+ {version} {v3} {community} udp-port {1}".format(
+ host, udp, **existing
+ )
+
+ elif existing["version"] == "v2c":
+ existing["version"] = "2c"
+ command = "no snmp-server host {0} {snmp_type} version \
+ {version} {community} udp-port {1}".format(
+ host, udp, **existing
+ )
+
+ elif existing["version"] == "v1":
+ existing["version"] = "1"
+ command = "no snmp-server host {0} {snmp_type} version \
+ {version} {community} udp-port {1}".format(
+ host, udp, **existing
+ )
+
+ if command:
+ commands.append(command)
+ return commands
+
+
+def remove_vrf(host, udp, proposed, existing):
+ commands = []
+ if existing.get("vrf"):
+ commands.append(
+ "no snmp-server host {0} use-vrf \
+ {1} udp-port {2}".format(
+ host,
+ proposed.get("vrf"),
+ udp,
+ ),
+ )
+ return commands
+
+
+def remove_filter(host, udp, proposed, existing):
+ commands = []
+ if existing.get("vrf_filter"):
+ if proposed.get("vrf_filter") in existing.get("vrf_filter"):
+ commands.append(
+ "no snmp-server host {0} filter-vrf \
+ {1} udp-port {2}".format(
+ host,
+ proposed.get("vrf_filter"),
+ udp,
+ ),
+ )
+ return commands
+
+
+def remove_src(host, udp, proposed, existing):
+ commands = []
+ if existing.get("src_intf"):
+ commands.append(
+ "no snmp-server host {0} source-interface \
+ {1} udp-port {2}".format(
+ host,
+ proposed.get("src_intf"),
+ udp,
+ ),
+ )
+ return commands
+
+
+def config_snmp_host(delta, udp, proposed, existing, module):
+ commands = []
+ command_builder = []
+ host = proposed["snmp_host"]
+ cmd = "snmp-server host {0}".format(proposed["snmp_host"])
+
+ snmp_type = delta.get("snmp_type")
+ version = delta.get("version")
+ ver = delta.get("v3")
+ community = delta.get("community")
+
+ command_builder.append(cmd)
+ if any([snmp_type, version, ver, community]):
+ type_string = snmp_type or existing.get("type")
+ if type_string:
+ command_builder.append(type_string)
+
+ version = version or existing.get("version")
+ if version:
+ if version == "v1":
+ vn = "1"
+ elif version == "v2c":
+ vn = "2c"
+ elif version == "v3":
+ vn = "3"
+
+ version_string = "version {0}".format(vn)
+ command_builder.append(version_string)
+
+ if ver:
+ ver_string = ver or existing.get("v3")
+ command_builder.append(ver_string)
+
+ if community:
+ community_string = community or existing.get("community")
+ command_builder.append(community_string)
+
+ udp_string = " udp-port {0}".format(udp)
+ command_builder.append(udp_string)
+
+ cmd = " ".join(command_builder)
+
+ commands.append(cmd)
+
+ CMDS = {
+ "vrf_filter": "snmp-server host {0} filter-vrf {vrf_filter} udp-port {1}",
+ "vrf": "snmp-server host {0} use-vrf {vrf} udp-port {1}",
+ "src_intf": "snmp-server host {0} source-interface {src_intf} udp-port {1}",
+ }
+
+ for key in delta:
+ command = CMDS.get(key)
+ if command:
+ cmd = command.format(host, udp, **delta)
+ commands.append(cmd)
+ return commands
+
+
+def main():
+ argument_spec = dict(
+ snmp_host=dict(required=True, type="str"),
+ community=dict(type="str"),
+ udp=dict(type="str", default="162"),
+ version=dict(choices=["v1", "v2c", "v3"]),
+ src_intf=dict(type="str"),
+ v3=dict(choices=["noauth", "auth", "priv"]),
+ vrf_filter=dict(type="str"),
+ vrf=dict(type="str"),
+ snmp_type=dict(choices=["trap", "inform"]),
+ state=dict(choices=["absent", "present"], default="present"),
+ )
+
+ module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)
+
+ warnings = list()
+ results = {"changed": False, "commands": [], "warnings": warnings}
+
+ snmp_host = module.params["snmp_host"]
+ community = module.params["community"]
+ udp = module.params["udp"]
+ version = module.params["version"]
+ src_intf = module.params["src_intf"]
+ v3 = module.params["v3"]
+ vrf_filter = module.params["vrf_filter"]
+ vrf = module.params["vrf"]
+ snmp_type = module.params["snmp_type"]
+ state = module.params["state"]
+
+ existing = get_snmp_host(snmp_host, udp, module)
+
+ if version is None:
+ if existing:
+ version = existing.get("version")
+ else:
+ version = "v1"
+
+ if snmp_type is None:
+ if existing:
+ snmp_type = existing.get("snmp_type")
+ else:
+ snmp_type = "trap"
+
+ if v3 is None:
+ if version == "v3" and existing:
+ v3 = existing.get("v3")
+
+ if snmp_type == "inform" and version == "v1":
+ module.fail_json(msg="inform requires snmp v2c or v3")
+
+ if (version == "v1" or version == "v2c") and v3:
+ module.fail_json(msg='param: "v3" should not be used when ' "using version v1 or v2c")
+
+ if not any([vrf_filter, vrf, src_intf]):
+ if not all([snmp_type, version, community, udp]):
+ module.fail_json(
+ msg="when not configuring options like "
+ "vrf_filter, vrf, and src_intf,"
+ "the following params are required: "
+ "type, version, community",
+ )
+
+ if version == "v3" and v3 is None:
+ module.fail_json(
+ msg="when using version=v3, the param v3 "
+ "(options: auth, noauth, priv) is also required",
+ )
+
+ # existing returns the list of vrfs configured for a given host
+ # checking to see if the proposed is in the list
+ store = existing.get("vrf_filter")
+ if existing and store:
+ if vrf_filter not in existing["vrf_filter"]:
+ existing["vrf_filter"] = None
+ else:
+ existing["vrf_filter"] = vrf_filter
+ commands = []
+
+ args = dict(
+ community=community,
+ snmp_host=snmp_host,
+ udp=udp,
+ version=version,
+ src_intf=src_intf,
+ vrf_filter=vrf_filter,
+ v3=v3,
+ vrf=vrf,
+ snmp_type=snmp_type,
+ )
+ proposed = dict((k, v) for k, v in args.items() if v is not None)
+
+ if state == "absent" and existing:
+ if proposed.get("community"):
+ commands.append(remove_snmp_host(snmp_host, udp, existing))
+ else:
+ if proposed.get("src_intf"):
+ commands.append(remove_src(snmp_host, udp, proposed, existing))
+ if proposed.get("vrf"):
+ commands.append(remove_vrf(snmp_host, udp, proposed, existing))
+ if proposed.get("vrf_filter"):
+ commands.append(remove_filter(snmp_host, udp, proposed, existing))
+
+ elif state == "present":
+ delta = dict(set(proposed.items()).difference(existing.items()))
+ if delta:
+ command = config_snmp_host(delta, udp, proposed, existing, module)
+ commands.append(command)
+
+ cmds = flatten_list(commands)
+ if cmds:
+ results["changed"] = True
+ if not module.check_mode:
+ load_config(module, cmds)
+
+ if "configure" in cmds:
+ cmds.pop(0)
+ results["commands"] = cmds
+
+ module.exit_json(**results)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_snmp_location.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_snmp_location.py
new file mode 100644
index 00000000..d7526a9e
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_snmp_location.py
@@ -0,0 +1,156 @@
+#!/usr/bin/python
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_snmp_location
+extends_documentation_fragment:
+- cisco.nxos.nxos
+short_description: (deprecated, removed after 2024-01-01) Manages SNMP location information.
+description:
+- Manages SNMP location configuration.
+version_added: 1.0.0
+deprecated:
+ alternative: nxos_snmp_server
+ why: Updated modules released with more functionality
+ removed_at_date: '2024-01-01'
+author:
+- Jason Edelman (@jedelman8)
+- Gabriele Gerbino (@GGabriele)
+notes:
+- Tested against NXOSv 7.3.(0)D1(1) on VIRL
+- Limited Support for Cisco MDS
+options:
+ location:
+ description:
+ - Location information.
+ required: true
+ type: str
+ state:
+ description:
+ - Manage the state of the resource.
+ required: false
+ default: present
+ choices:
+ - present
+ - absent
+ type: str
+"""
+
+EXAMPLES = """
+# ensure snmp location is configured
+- cisco.nxos.nxos_snmp_location:
+ location: Test
+ state: present
+
+# ensure snmp location is not configured
+- cisco.nxos.nxos_snmp_location:
+ location: Test
+ state: absent
+"""
+
+RETURN = """
+commands:
+ description: commands sent to the device
+ returned: always
+ type: list
+ sample: ["snmp-server location New_Test"]
+"""
+
+
+import re
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ load_config,
+ run_commands,
+)
+
+
+def execute_show_command(command, module):
+ command = {"command": command, "output": "text"}
+
+ return run_commands(module, command)
+
+
+def flatten_list(command_lists):
+ flat_command_list = []
+ for command in command_lists:
+ if isinstance(command, list):
+ flat_command_list.extend(command)
+ else:
+ flat_command_list.append(command)
+ return flat_command_list
+
+
+def get_snmp_location(module):
+ location = {}
+ location_regex = r"^\s*snmp-server\s+location\s+(?P<location>.+)$"
+
+ body = execute_show_command("show run snmp", module)[0]
+ match_location = re.search(location_regex, body, re.M)
+ if match_location:
+ location["location"] = match_location.group("location")
+
+ return location
+
+
+def main():
+ argument_spec = dict(
+ location=dict(required=True, type="str"),
+ state=dict(choices=["absent", "present"], default="present"),
+ )
+
+ module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)
+
+ warnings = list()
+ results = {"changed": False, "commands": [], "warnings": warnings}
+
+ location = module.params["location"]
+ state = module.params["state"]
+
+ existing = get_snmp_location(module)
+ commands = []
+
+ if state == "absent":
+ if existing and existing["location"] == location:
+ commands.append("no snmp-server location")
+ elif state == "present":
+ if not existing or existing["location"] != location:
+ commands.append("snmp-server location {0}".format(location))
+
+ cmds = flatten_list(commands)
+ if cmds:
+ results["changed"] = True
+ if not module.check_mode:
+ load_config(module, cmds)
+
+ if "configure" in cmds:
+ cmds.pop(0)
+ results["commands"] = cmds
+
+ module.exit_json(**results)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_snmp_server.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_snmp_server.py
new file mode 100644
index 00000000..7354dc1e
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_snmp_server.py
@@ -0,0 +1,1480 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# Copyright 2021 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+"""
+The module file for nxos_snmp_server
+"""
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+DOCUMENTATION = """
+module: nxos_snmp_server
+short_description: SNMP Server resource module.
+description:
+- This module manages SNMP server configuration on devices running Cisco NX-OS.
+version_added: 2.8.0
+notes:
+- Tested against NX-OS 9.3.6 on Cisco Nexus Switches.
+- This module works with connection C(network_cli) and C(httpapi).
+- Tested against Cisco MDS NX-OS 9.2(2) with connection C(network_cli).
+author: Nilashish Chakraborty (@NilashishC)
+options:
+ running_config:
+ description:
+ - This option is used only with state I(parsed).
+ - The value of this option should be the output received from the NX-OS device
+ by executing the command B(show running-config | section '^snmp-server').
+ - The state I(parsed) reads the configuration from C(running_config) option and
+ transforms it into Ansible structured data as per the resource module's argspec
+ and the value is then returned in the I(parsed) key within the result.
+ type: str
+ config:
+ description: A dict of SNMP server configuration
+ type: dict
+ suboptions:
+ aaa_user:
+ description: Set duration for which aaa-cached snmp user exists.
+ type: dict
+ suboptions:
+ cache_timeout:
+ description: Timeout for which aaa-cached user exists(in secs).
+ type: int
+ communities:
+ description: Set community string and access privs.
+ type: list
+ elements: dict
+ suboptions:
+ name:
+ description: SNMP community string (Max Size 32).
+ type: str
+ aliases: ["community"]
+ group:
+ description: Group to which the community belongs.
+ type: str
+ ro:
+ description: Read-only access with this community string.
+ type: bool
+ rw:
+ description: Read-write access with this community string.
+ type: bool
+ use_ipv4acl:
+ description:
+ - Specify IPv4 ACL, the ACL name specified must be IPv4 ACL.
+ - This option is unsupported on MDS switches.
+ type: str
+ use_ipv6acl:
+ description:
+ - Specify IPv6 ACL, the ACL name specified after must be IPv6 ACL.
+ - This option is unsupported on MDS switches.
+ type: str
+ contact:
+ description: Modify sysContact.
+ type: str
+ context:
+ description: SNMP context to be mapped.
+ type: dict
+ suboptions:
+ name:
+ description: Name of the SNMP context (Max Size 32).
+ type: str
+ instance:
+ description: Name of the protocol instance (Max Size 32).
+ type: str
+ topology:
+ description: Topology associated with the SNMP context.
+ type: str
+ vrf:
+ description:
+ - VRF associated with the SNMP context.
+ - This option is unsupported on MDS switches.
+ type: str
+ counter:
+ description:
+ - Configure port counter configuration.
+ - This option is unsupported on MDS switches.
+ type: dict
+ suboptions:
+ cache:
+ description: Port stats cache.
+ type: dict
+ suboptions:
+ enable:
+ description: Enable port stats cache.
+ type: bool
+ timeout:
+ description: Timeout for which cached port stats exists(in secs).
+ type: int
+ drop:
+ description:
+ - Silently drop unknown v3 user packets.
+ - This option is unsupported on MDS switches.
+ type: dict
+ suboptions:
+ unknown_engine_id:
+ description: Unknown v3 engine id.
+ type: bool
+ unknown_user:
+ description: Unknown v3 user.
+ type: bool
+ traps:
+ description: Enable SNMP Traps
+ type: dict
+ suboptions:
+ aaa:
+ description: AAA traps
+ type: dict
+ suboptions:
+ enable:
+ description: Enable AAA traps.
+ type: bool
+ server_state_change:
+ description: AAA server state change notification.
+ type: bool
+ bgp:
+ description: SNMP BGP traps.
+ type: dict
+ suboptions:
+ enable:
+ description: Enable SNMP BGP traps.
+ type: bool
+ bridge:
+ description:
+ - Bridge traps.
+ - This option is unsupported on MDS switches.
+ type: dict
+ suboptions:
+ enable:
+ description: Enable bridge traps.
+ type: bool
+ newroot:
+ description: Enable SNMP STP Bridge MIB newroot traps.
+ type: bool
+ topologychange:
+ description: Enable SNMP STP Bridge MIB topologychange traps.
+ type: bool
+ callhome:
+ description: Callhome traps.
+ type: dict
+ suboptions:
+ enable:
+ description:
+ - Enable callhome traps.
+ - This option is unsupported on MDS switches.
+ type: bool
+ event_notify:
+ description: Callhome External Event Notification.
+ type: bool
+ smtp_send_fail:
+ description: SMTP Message Send Fail notification.
+ type: bool
+ cfs:
+ description: CFS traps.
+ type: dict
+ suboptions:
+ enable:
+ description:
+ - Enable cfs traps.
+ - This option is unsupported on MDS switches.
+ type: bool
+ merge_failure:
+ description: Merge failure notification.
+ type: bool
+ state_change_notif:
+ description: State change notification.
+ type: bool
+ config:
+ description: Config traps.
+ type: dict
+ suboptions:
+ enable:
+ description:
+ - Enable config traps.
+ - This option is unsupported on MDS switches.
+ type: bool
+ ccmCLIRunningConfigChanged:
+ description: Running config change trap.
+ type: bool
+ entity:
+ description: Entity traps.
+ type: dict
+ suboptions:
+ enable:
+ description: Enable entity traps.
+ type: bool
+ cefcMIBEnableStatusNotification:
+ description: CefcMIBEnableStatusNotification.
+ type: bool
+ entity_fan_status_change:
+ description: Entity Fan Status Change.
+ type: bool
+ entity_mib_change:
+ description: Entity MIB change.
+ type: bool
+ entity_module_inserted:
+ description: Entity Module Inserted.
+ type: bool
+ entity_module_removed:
+ description: Entity Module Removed.
+ type: bool
+ entity_module_status_change:
+ description: Entity Module Status Change.
+ type: bool
+ entity_power_out_change:
+ description: Entity Power Out Change.
+ type: bool
+ entity_power_status_change:
+ description: Entity Power Status Change.
+ type: bool
+ entity_sensor:
+ description: Entity sensor.
+ type: bool
+ entity_unrecognised_module:
+ description: Entity Unrecognised Module.
+ type: bool
+ feature_control:
+ description: Feature-Control traps.
+ type: dict
+ suboptions:
+ enable:
+ description:
+ - Enable feature-control traps.
+ - This option is unsupported on MDS switches.
+ type: bool
+ featureOpStatusChange:
+ description: Feature operation status change notification.
+ type: bool
+ ciscoFeatOpStatusChange:
+ description: Feature operation status change Notification.
+ type: bool
+ generic:
+ description: Generic traps.
+ type: dict
+ suboptions:
+ enable:
+ description:
+ - Enable generic traps.
+ - This option is unsupported on MDS switches.
+ type: bool
+ coldStart:
+ description: Generic coldStart trap.
+ type: bool
+ warmStart:
+ description: Generic warmStart trap.
+ type: bool
+ license:
+ description: License traps.
+ type: dict
+ suboptions:
+ enable:
+ description:
+ - Enable license traps.
+ - This option is unsupported on MDS switches.
+ type: bool
+ notify_license_expiry:
+ description: License Expiry Notification.
+ type: bool
+ notify_license_expiry_warning:
+ description: License Expiry Warning Notification.
+ type: bool
+ notify_licensefile_missing:
+ description: License File Missing Notification.
+ type: bool
+ notify_no_license_for_feature:
+ description: No License installed for feature Notification.
+ type: bool
+ link:
+ description: Link traps.
+ type: dict
+ suboptions:
+ enable:
+ description:
+ - Enable link traps.
+ - This option is unsupported on MDS switches.
+ type: bool
+ cErrDisableInterfaceEventRev1:
+ description:
+ - Err-disable state notification.
+ - This option is unsupported on MDS switches.
+ type: bool
+ cieLinkDown:
+ description: Cisco extended link state down notification.
+ type: bool
+ cieLinkUp:
+ description: Cisco extended link state up notification.
+ type: bool
+ cisco_xcvr_mon_status_chg:
+ description: Cisco interface transceiver monitor status change notification.
+ type: bool
+ cmn_mac_move_notification:
+ description:
+ - Mac addr move trap.
+ - This option is unsupported on MDS switches.
+ type: bool
+ delayed_link_state_change:
+ description: Delayed link state change.
+ type: bool
+ extended_linkDown:
+ description: IETF extended link state down notification.
+ type: bool
+ extended_linkUp:
+ description: IETF extended link state up notification.
+ type: bool
+ linkDown:
+ description: IETF Link state down notification.
+ type: bool
+ linkUp:
+ description: IETF Link state up notification.
+ type: bool
+ mmode:
+ description:
+ - MMode traps.
+ - This option is unsupported on MDS switches.
+ type: dict
+ suboptions:
+ enable:
+ description: Enable mmode traps.
+ type: bool
+ cseMaintModeChangeNotify:
+ description: Maint Mode Change Notification.
+ type: bool
+ cseNormalModeChangeNotify:
+ description: Normal Mode Change Notification.
+ type: bool
+ ospf:
+ description: SNMP OSPF traps.
+ type: dict
+ suboptions:
+ enable:
+ description: Enable SNMP OSPF traps.
+ type: bool
+ ospfv3:
+ description: SNMP OSPFv3 traps.
+ type: dict
+ suboptions:
+ enable:
+ description: Enable SNMP OSPFv3 traps.
+ type: bool
+ rf:
+ description: RF traps.
+ type: dict
+ suboptions:
+ enable:
+ description:
+ - Enable rf traps.
+ - This option is unsupported on MDS switches.
+ type: bool
+ redundancy_framework:
+ description: Redundancy_Framework (RF) Sup switchover MIB.
+ type: bool
+ rmon:
+ description: RMON traps.
+ type: dict
+ suboptions:
+ enable:
+ description:
+ - Enable rmon traps.
+ - This option is unsupported on MDS switches.
+ type: bool
+ fallingAlarm:
+ description: Rmon falling alarm.
+ type: bool
+ hcFallingAlarm:
+ description: High capacity Rmon falling alarm.
+ type: bool
+ hcRisingAlarm:
+ description: High capacity Rmon rising alarm.
+ type: bool
+ risingAlarm:
+ description: Rmon rising alarm.
+ type: bool
+ snmp:
+ description: SNMP traps.
+ type: dict
+ suboptions:
+ enable:
+ description:
+ - Enable snmp traps.
+ - This option is unsupported on MDS switches.
+ type: bool
+ authentication:
+ description: SNMP authentication trap.
+ type: bool
+ storm_control:
+ description: Storm-Control traps.
+ type: dict
+ suboptions:
+ enable:
+ description:
+ - Enable storm-control traps.
+ - This option is unsupported on MDS switches.
+ type: bool
+ cpscEventRev1:
+ description:
+ - Port-Storm-Control-Event.
+ - This option is unsupported on MDS switches.
+ type: bool
+ trap_rate:
+ description: Number of traps per minute.
+ type: bool
+ stpx:
+ description:
+ - Stpx traps.
+ - This option is unsupported on MDS switches.
+ type: dict
+ suboptions:
+ enable:
+ description: Enable stpx traps.
+ type: bool
+ inconsistency:
+ description: Enable SNMP STPX MIB InconsistencyUpdate traps.
+ type: bool
+ loop_inconsistency:
+ description: Enable SNMP STPX MIB LoopInconsistencyUpdate traps.
+ type: bool
+ root_inconsistency:
+ description: Enable SNMP STPX MIB RootInconsistencyUpdate traps.
+ type: bool
+ syslog:
+ description: Enable syslog traps.
+ type: dict
+ suboptions:
+ enable:
+ description:
+ - Enable syslog traps.
+ - This option is unsupported on MDS switches.
+ type: bool
+ message_generated:
+ description: Message Generated Notification.
+ type: bool
+ sysmgr:
+ description: Sysmgr traps.
+ type: dict
+ suboptions:
+ enable:
+ description:
+ - Enable sysmgr traps.
+ - This option is unsupported on MDS switches.
+ type: bool
+ cseFailSwCoreNotifyExtended:
+ description: Software Core Notification.
+ type: bool
+ system:
+ description: System traps.
+ type: dict
+ suboptions:
+ enable:
+ description:
+ - Enable system traps.
+ - This option is unsupported on MDS switches.
+ type: bool
+ clock_change_notification:
+ description: Clock-change-notification traps.
+ type: bool
+ upgrade:
+ description: Upgrade traps.
+ type: dict
+ suboptions:
+ enable:
+ description:
+ - Enable upgrade traps.
+ - This option is unsupported on MDS switches.
+ type: bool
+ upgradeJobStatusNotify:
+ description: Upgrade Job Status Notification.
+ type: bool
+ upgradeOpNotifyOnCompletion:
+ description: Upgrade Global Status Notification.
+ type: bool
+ vtp:
+ description:
+ - VTP traps.
+ - This option is unsupported on MDS switches.
+ type: dict
+ suboptions:
+ enable:
+ description: Enable VTP traps.
+ type: bool
+ notifs:
+ description:
+ - Enable vtpConfigRevNumberError vtpConfigDigestEnable vtpConfigRevNumberError vtpConfigDigestError
+ vtpServerDisabled vtpVersionOneDeviceDetected vlanTrunkPortDynamicStatusChange vtpLocalModeChanged
+ vtpVersionInUseChanged notification.
+ type: bool
+ vlancreate:
+ description: Enable vtpVlanCreated notification.
+ type: bool
+ vlandelete:
+ description: Enable vtpVlanDeleted notification.
+ type: bool
+ engine_id:
+ description:
+ - Configure a local SNMPv3 engineID.
+ - This option is unsupported on MDS switches.
+ type: dict
+ suboptions:
+ local:
+ description: EngineID of the local agent.
+ type: str
+ global_enforce_priv:
+ description: Globally enforce privacy for all the users.
+ type: bool
+ hosts:
+ description:
+ - Specify hosts to receive SNMP notifications.
+ - SNMP hosts config lines that appear separately in running-config must be added as individual dictionaries.
+ type: list
+ elements: dict
+ suboptions:
+ host:
+ description: IPv4 or IPv6 address or DNS Name of SNMP notification host.
+ type: str
+ community:
+ description: SNMP community string or SNMPv3 user name (Max Size 32).
+ type: str
+ filter_vrf:
+ description:
+ - Filters notifications to the notification host receiver based on the configured VRF.
+ - This option is unsupported on MDS switches.
+ type: str
+ informs:
+ description: Send Inform messages to this host.
+ type: bool
+ source_interface:
+ description: Source interface to be used for sending out SNMP notifications to this host.
+ type: str
+ traps:
+ description: Send Traps messages to this host.
+ type: bool
+ use_vrf:
+ description:
+ - Configures SNMP to use the selected VRF to communicate with the host receiver.
+ - This option is unsupported on MDS switches.
+ type: str
+ version:
+ description: SNMP version to use for notification messages.
+ type: str
+ choices: ["1", "2c", "3"]
+ auth:
+ description: Use the SNMPv3 authNoPriv Security Level.
+ type: str
+ priv:
+ description: Use the SNMPv3 authPriv Security Level.
+ type: str
+ udp_port:
+ description: The notification host's UDP port number.
+ type: int
+ location:
+ description: Modify sysLocation.
+ type: str
+ mib:
+ description: Mib access parameters.
+ type: dict
+ suboptions:
+ community_map:
+ description: SNMP community.
+ type: dict
+ suboptions:
+ community:
+ description: SNMP community string (Max Size 32).
+ type: str
+ context:
+ description: Name of the SNMP context (Max Size 32).
+ type: str
+ packetsize:
+ description: Largest SNMP packet size
+ type: int
+ protocol:
+ description: Snmp protocol operations.
+ type: dict
+ suboptions:
+ enable:
+ description: Enable/Disable snmp protocol operations.
+ type: bool
+ source_interface:
+ description:
+ - Source interface to be used for sending out SNMP notifications.
+ - This option is unsupported on MDS switches.
+ type: dict
+ suboptions:
+ informs:
+ description: SNMP Inform notifications for which this source interface needs to be used.
+ type: str
+ traps:
+ description: SNMP Trap notifications for which this source interface needs to be used.
+ type: str
+ system_shutdown:
+ description: Configure snmp-server for reload(2).
+ type: bool
+ tcp_session:
+ description: Enable one time authentication for snmp over tcp session.
+ type: dict
+ suboptions:
+ enable:
+ description:
+ - Enable tcp-session.
+ - This option is unsupported on MDS switches.
+ type: bool
+ auth:
+ description: Enable one time authentication for snmp over tcp session.
+ type: bool
+ users:
+ description: Define users who can access the SNMP engine.
+ type: dict
+ suboptions:
+ auth:
+ description: SNMP User authentication related settings
+ type: list
+ elements: dict
+ suboptions:
+ user:
+ description: Name of the user (Max Size 28).
+ type: str
+ group:
+ description: Group name (ignored for notif target user) (Max Size 28).
+ type: str
+ authentication:
+ description: Authentication parameters for the user.
+ type: dict
+ suboptions:
+ algorithm:
+ description: Select algorithm for authentication.
+ type: str
+ choices: ["md5", "sha", "sha-256"]
+ password:
+ description:
+ - Authentication password for user (Max Size 127).
+ - If this value is localized, it has to be enclosed in quotes in the task.
+ type: str
+ engine_id:
+ description:
+ - EngineID for configuring notif target user (for V3 informs).
+ - This value needs to be enclosed in quotes in the task.
+ type: str
+ localized_key:
+ description: Specifies whether the passwords are in localized key format.
+ type: bool
+ localizedv2_key:
+ description: Specifies whether the passwords are in localized V2 key format.
+ type: bool
+ priv:
+ description: Encryption parameters for the user.
+ type: dict
+ suboptions:
+ privacy_password:
+ description:
+ - Privacy password for user (Max Size 130).
+ - If this value is localized, it has to be enclosed in quotes in the task.
+ type: str
+ aes_128:
+ description: Use 128-bit AES algorithm for privacy.
+ type: bool
+ use_acls:
+ description: Set IPv4 and IPv6 ACL to use.
+ type: list
+ elements: dict
+ suboptions:
+ user:
+ description: Name of the user (Max Size 28).
+ type: str
+ ipv4:
+ description: Specify IPv4 ACL, the ACL name specified after must be IPv4 ACL.
+ type: str
+ ipv6:
+ description: Specify IPv6 ACL, the ACL name specified after must be IPv6 ACL.
+ type: str
+ state:
+ description:
+ - The state the configuration should be left in.
+ - The states C(replaced) and C(overridden) have identical behaviour for this module.
+ - Please refer to examples for more details.
+ type: str
+ choices:
+ - merged
+ - replaced
+ - overridden
+ - deleted
+ - parsed
+ - gathered
+ - rendered
+ default: merged
+"""
+
+EXAMPLES = """
+# Using merged
+
+# Before state:
+# -------------
+# nxos-9k-rdo# show running-config | section "^snmp-server"
+# snmp-server user admin network-admin auth md5 0xcbde46b02c46e0bcd3ac5af6a8b13da9 priv 0xcbde46b02c46e0bcd3ac5af6a8b13da9 localizedkey
+
+- name: Merge the provided configuration with the existing running configuration
+ cisco.nxos.nxos_snmp_server:
+ config:
+ aaa_user:
+ cache_timeout: 36000
+ communities:
+ - community: public
+ group: network-operator
+ - community: private
+ group: network-admin
+ contact: nxosswitchadmin@localhost
+ location: serverroom-1
+ traps:
+ aaa:
+ server_state_change: True
+ system:
+ clock_change_notification: True
+ hosts:
+ - host: 192.0.2.1
+ traps: True
+ version: '1'
+ community: public
+ - host: 192.0.2.1
+ source_interface: Ethernet1/1
+ - host: 192.0.2.2
+ informs: True
+ version: '3'
+ auth: NMS
+ users:
+ auth:
+ - user: snmp_user_1
+ group: network-operator
+ authentication:
+ algorithm: md5
+ password: '0x5632724fb8ac3699296af26281e1d0f1'
+ localized_key: True
+ - user: snmp_user_2
+ group: network-operator
+ authentication:
+ algorithm: md5
+ password: '0x5632724fb8ac3699296af26281e1d0f1'
+ localized_key: True
+ priv:
+ privacy_password: '0x5632724fb8ac3699296af26281e1d0f1'
+ aes_128: True
+ use_acls:
+ - user: snmp_user_1
+ ipv4: acl1
+ ipv6: acl2
+ - user: snmp_user_2
+ ipv4: acl3
+ ipv6: acl4
+
+# Task output
+# -------------
+# before:
+# users:
+# auth:
+# - user: admin
+# group: network-admin
+# authentication:
+# algorithm: md5
+# password: "0xcbde46b02c46e0bcd3ac5af6a8b13da9"
+# localized_key: True
+# priv:
+# privacy_password: "0xcbde46b02c46e0bcd3ac5af6a8b13da9"
+#
+# commands:
+# - snmp-server contact nxosswitchadmin@localhost
+# - snmp-server location serverroom-1
+# - snmp-server aaa-user cache-timeout 36000
+# - snmp-server user snmp_user_1 network-operator auth md5 0x5632724fb8ac3699296af26281e1d0f1 localizedkey
+# - snmp-server user snmp_user_2 network-operator auth md5 0x5632724fb8ac3699296af26281e1d0f1 priv aes-128 0x5632724fb8ac3699296af26281e1d0f1 localizedkey
+# - snmp-server user snmp_user_1 use-ipv4acl acl1 use-ipv6acl acl2
+# - snmp-server user snmp_user_2 use-ipv4acl acl3 use-ipv6acl acl4
+# - snmp-server host 192.0.2.1 traps version 1 public
+# - snmp-server host 192.0.2.1 source-interface Ethernet1/1
+# - snmp-server host 192.0.2.2 informs version 3 auth NMS
+# - snmp-server community private group network-admin
+# - snmp-server community public group network-operator
+# - snmp-server enable traps aaa server-state-change
+# - snmp-server enable traps system Clock-change-notification
+#
+# after:
+# aaa_user:
+# cache_timeout: 36000
+# communities:
+# - community: private
+# group: network-admin
+# - community: public
+# group: network-operator
+# contact: nxosswitchadmin@localhost
+# location: serverroom-1
+# traps:
+# aaa:
+# server_state_change: True
+# system:
+# clock_change_notification: True
+# hosts:
+# - host: 192.0.2.1
+# traps: true
+# version: "1"
+# community: public
+#
+# - host: 192.0.2.1
+# source_interface: Ethernet1/1
+#
+# - host: 192.0.2.2
+# informs: true
+# version: "3"
+# auth: NMS
+# users:
+# auth:
+# - user: admin
+# group: network-admin
+# authentication:
+# algorithm: md5
+# password: "0xcbde46b02c46e0bcd3ac5af6a8b13da9"
+# localized_key: True
+# priv:
+# privacy_password: "0xcbde46b02c46e0bcd3ac5af6a8b13da9"
+#
+# - user: snmp_user_1
+# group: network-operator
+# authentication:
+# algorithm: md5
+# password: "0x5632724fb8ac3699296af26281e1d0f1"
+# localized_key: True
+#
+# - authentication:
+# algorithm: md5
+# localized_key: true
+# password: "0x5632724fb8ac3699296af26281e1d0f1"
+# priv:
+# aes_128: true
+# privacy_password: "0x5632724fb8ac3699296af26281e1d0f1"
+# group: network-operator
+# user: snmp_user_2
+#
+# use_acls:
+# - user: snmp_user_1
+# ipv4: acl1
+# ipv6: acl2
+# - user: snmp_user_2
+# ipv4: acl3
+# ipv6: acl4
+
+# After state:
+# ------------
+# nxos-9k-rdo# show running-config | section "^snmp-server"
+# snmp-server contact nxosswitchadmin@localhost
+# snmp-server location serverroom-1
+# snmp-server aaa-user cache-timeout 36000
+# snmp-server user admin network-admin auth md5 0xcbde46b02c46e0bcd3ac5af6a8b13da9 priv 0xcbde46b02c46e0bcd3ac5af6a8b13da9 localizedkey
+# snmp-server user snmp_user_1 network-operator auth md5 0x5632724fb8ac3699296af26281e1d0f1 localizedkey
+# snmp-server user snmp_user_2 network-operator auth md5 0x5632724fb8ac3699296af26281e1d0f1 priv aes-128 0x5632724fb8ac3699296af26281e1d0f1 localizedkey
+# snmp-server user snmp_user_1 use-ipv4acl acl1 use-ipv6acl acl2
+# snmp-server user snmp_user_2 use-ipv4acl acl3 use-ipv6acl acl4
+# snmp-server host 192.0.2.1 traps version 1 public
+# snmp-server host 192.0.2.1 source-interface Ethernet1/1
+# snmp-server host 192.0.2.2 informs version 3 auth NMS
+# snmp-server community private group network-admin
+# snmp-server community public group network-operator
+# snmp-server enable traps aaa server-state-change
+# snmp-server enable traps system Clock-change-notification
+
+# Using replaced
+
+# Before state:
+# ------------
+# nxos-9k-rdo# show running-config | section "^snmp-server"
+# snmp-server contact nxosswitchadmin@localhost
+# snmp-server location serverroom-1
+# snmp-server aaa-user cache-timeout 36000
+# snmp-server user admin network-admin auth md5 0xcbde46b02c46e0bcd3ac5af6a8b13da9 priv 0xcbde46b02c46e0bcd3ac5af6a8b13da9 localizedkey
+# snmp-server user snmp_user_1 network-operator auth md5 0x5632724fb8ac3699296af26281e1d0f1 localizedkey
+# snmp-server user snmp_user_2 network-operator auth md5 0x5632724fb8ac3699296af26281e1d0f1 priv aes-128 0x5632724fb8ac3699296af26281e1d0f1 localizedkey
+# snmp-server user snmp_user_1 use-ipv4acl acl1 use-ipv6acl acl2
+# snmp-server user snmp_user_2 use-ipv4acl acl3 use-ipv6acl acl4
+# snmp-server host 192.0.2.1 traps version 1 public
+# snmp-server host 192.0.2.1 source-interface Ethernet1/1
+# snmp-server host 192.0.2.2 informs version 3 auth NMS
+# snmp-server community private group network-admin
+# snmp-server community public group network-operator
+# snmp-server enable traps aaa server-state-change
+# snmp-server enable traps system Clock-change-notification
+
+- name: Replace snmp-server configurations of listed snmp-server with provided configurations
+ cisco.nxos.nxos_snmp_server:
+ config:
+ aaa_user:
+ cache_timeout: 36000
+ communities:
+ - community: public
+ group: network-operator
+ - community: secret
+ group: network-operator
+ contact: nxosswitchadmin2@localhost
+ location: serverroom-2
+ traps:
+ aaa:
+ server_state_change: True
+ hosts:
+ - host: 192.0.2.1
+ traps: True
+ version: '1'
+ community: public
+ - host: 192.0.2.1
+ source_interface: Ethernet1/1
+ - host: 192.0.3.2
+ informs: True
+ version: '3'
+ auth: NMS
+ users:
+ auth:
+ - user: admin
+ group: network-admin
+ authentication:
+ algorithm: md5
+ password: "0xcbde46b02c46e0bcd3ac5af6a8b13da9"
+ localized_key: True
+ priv:
+ privacy_password: "0xcbde46b02c46e0bcd3ac5af6a8b13da9"
+
+ - user: snmp_user_1
+ group: network-operator
+ authentication:
+ algorithm: md5
+ password: '0x5632724fb8ac3699296af26281e1d0f1'
+ localized_key: True
+
+ - user: snmp_user_2
+ group: network-operator
+ authentication:
+ algorithm: md5
+ password: '0x5632724fb8ac3699296af26281e1d0f1'
+ localized_key: True
+ priv:
+ privacy_password: '0x5632724fb8ac3699296af26281e1d0f1'
+ aes_128: True
+ use_acls:
+ - user: snmp_user_1
+ ipv4: acl1
+ ipv6: acl2
+ state: replaced
+
+# Task output
+# -------------
+# before:
+# aaa_user:
+# cache_timeout: 36000
+# communities:
+# - community: private
+# group: network-admin
+# - community: public
+# group: network-operator
+# contact: nxosswitchadmin@localhost
+# location: serverroom-1
+# traps:
+# aaa:
+# server_state_change: True
+# system:
+# clock_change_notification: True
+# hosts:
+# - host: 192.0.2.1
+# traps: true
+# version: "1"
+# community: public
+#
+# - host: 192.0.2.1
+# source_interface: Ethernet1/1
+#
+# - host: 192.0.2.2
+# informs: true
+# version: "3"
+# auth: NMS
+# users:
+# auth:
+# - user: admin
+# group: network-admin
+# authentication:
+# algorithm: md5
+# password: "0xcbde46b02c46e0bcd3ac5af6a8b13da9"
+# localized_key: True
+# priv:
+# privacy_password: "0xcbde46b02c46e0bcd3ac5af6a8b13da9"
+#
+# - user: snmp_user_1
+# group: network-operator
+# authentication:
+# algorithm: md5
+# password: "0x5632724fb8ac3699296af26281e1d0f1"
+# localized_key: True
+#
+# - authentication:
+# algorithm: md5
+# localized_key: true
+# password: "0x5632724fb8ac3699296af26281e1d0f1"
+# priv:
+# aes_128: true
+# privacy_password: "0x5632724fb8ac3699296af26281e1d0f1"
+# group: network-operator
+# user: snmp_user_2
+#
+# use_acls:
+# - user: snmp_user_1
+# ipv4: acl1
+# ipv6: acl2
+# - user: snmp_user_2
+# ipv4: acl3
+# ipv6: acl4
+#
+# commands:
+# - snmp-server contact nxosswitchadmin2@localhost
+# - no snmp-server enable traps system Clock-change-notification
+# - snmp-server location serverroom-2
+# - no snmp-server user snmp_user_2 use-ipv4acl acl3 use-ipv6acl acl4
+# - no snmp-server host 192.0.2.2 informs version 3 auth NMS
+# - snmp-server host 192.0.3.2 informs version 3 auth NMS
+# - no snmp-server community private group network-admin
+# - snmp-server community secret group network-operator
+#
+# after:
+# aaa_user:
+# cache_timeout: 36000
+# communities:
+# - community: public
+# group: network-operator
+# - community: secret
+# group: network-operator
+# contact: nxosswitchadmin2@localhost
+# location: serverroom-2
+# traps:
+# aaa:
+# server_state_change: True
+# hosts:
+# - host: 192.0.2.1
+# traps: True
+# version: '1'
+# community: public
+# - host: 192.0.2.1
+# source_interface: Ethernet1/1
+# - host: 192.0.3.2
+# informs: True
+# version: '3'
+# auth: NMS
+# users:
+# auth:
+# - user: admin
+# group: network-admin
+# authentication:
+# algorithm: md5
+# password: "0xcbde46b02c46e0bcd3ac5af6a8b13da9"
+# localized_key: True
+# priv:
+# privacy_password: "0xcbde46b02c46e0bcd3ac5af6a8b13da9"
+#
+# - user: snmp_user_1
+# group: network-operator
+# authentication:
+# algorithm: md5
+# password: '0x5632724fb8ac3699296af26281e1d0f1'
+# localized_key: True
+#
+# - user: snmp_user_2
+# group: network-operator
+# authentication:
+# algorithm: md5
+# password: '0x5632724fb8ac3699296af26281e1d0f1'
+# localized_key: True
+# priv:
+# privacy_password: '0x5632724fb8ac3699296af26281e1d0f1'
+# aes_128: True
+#
+# use_acls:
+# - user: snmp_user_1
+# ipv4: acl1
+# ipv6: acl2
+#
+
+# After state:
+# ------------
+# nxos-9k-rdo# show running-config | section "^snmp-server"
+# snmp-server contact nxosswitchadmin2@localhost
+# snmp-server location serverroom-2
+# snmp-server aaa-user cache-timeout 36000
+# snmp-server user admin network-admin auth md5 0xcbde46b02c46e0bcd3ac5af6a8b13da9 priv 0xcbde46b02c46e0bcd3ac5af6a8b13da9 localizedkey
+# snmp-server user snmp_user_1 network-operator auth md5 0x5632724fb8ac3699296af26281e1d0f1 localizedkey
+# snmp-server user snmp_user_2 network-operator auth md5 0x5632724fb8ac3699296af26281e1d0f1 priv aes-128 0x5632724fb8ac3699296af26281e1d0f1 localizedkey
+# snmp-server user snmp_user_1 use-ipv4acl acl1 use-ipv6acl acl2
+# snmp-server user snmp_user_2 use-ipv4acl acl3 use-ipv6acl acl4
+# snmp-server host 192.0.2.1 traps version 1 public
+# snmp-server host 192.0.2.1 source-interface Ethernet1/1
+# snmp-server host 192.0.2.2 informs version 3 auth NMS
+# snmp-server community secret group network-operator
+# snmp-server community public group network-operator
+# snmp-server enable traps aaa server-state-change
+# snmp-server enable traps system Clock-change-notification
+
+# Using deleted
+
+# Before state:
+# ------------
+# nxos-9k-rdo# show running-config | section "^snmp-server"
+# snmp-server contact nxosswitchadmin@localhost
+# snmp-server location serverroom-1
+# snmp-server aaa-user cache-timeout 36000
+# snmp-server user admin network-admin auth md5 0xcbde46b02c46e0bcd3ac5af6a8b13da9 priv 0xcbde46b02c46e0bcd3ac5af6a8b13da9 localizedkey
+# snmp-server user snmp_user_1 network-operator auth md5 0x5632724fb8ac3699296af26281e1d0f1 localizedkey
+# snmp-server user snmp_user_2 network-operator auth md5 0x5632724fb8ac3699296af26281e1d0f1 priv aes-128 0x5632724fb8ac3699296af26281e1d0f1 localizedkey
+# snmp-server user snmp_user_1 use-ipv4acl acl1 use-ipv6acl acl2
+# snmp-server user snmp_user_2 use-ipv4acl acl3 use-ipv6acl acl4
+# snmp-server host 192.0.2.1 traps version 1 public
+# snmp-server host 192.0.2.1 source-interface Ethernet1/1
+# snmp-server host 192.0.2.2 informs version 3 auth NMS
+# snmp-server community private group network-admin
+# snmp-server community public group network-operator
+# snmp-server enable traps aaa server-state-change
+# snmp-server enable traps system Clock-change-notification
+
+- name: Delete SNMP Server configurations from the device (admin user will not be deleted)
+ cisco.nxos.nxos_snmp_server:
+ state: deleted
+
+# Task output
+# -------------
+# before:
+# aaa_user:
+# cache_timeout: 36000
+# communities:
+# - community: private
+# group: network-admin
+# - community: public
+# group: network-operator
+# contact: nxosswitchadmin@localhost
+# location: serverroom-1
+# traps:
+# aaa:
+# server_state_change: True
+# system:
+# clock_change_notification: True
+# hosts:
+# - host: 192.0.2.1
+# traps: true
+# version: "1"
+# community: public
+#
+# - host: 192.0.2.1
+# source_interface: Ethernet1/1
+#
+# - host: 192.0.2.2
+# informs: true
+# version: "3"
+# auth: NMS
+# users:
+# auth:
+# - user: admin
+# group: network-admin
+# authentication:
+# algorithm: md5
+# password: "0xcbde46b02c46e0bcd3ac5af6a8b13da9"
+# localized_key: True
+# priv:
+# privacy_password: "0xcbde46b02c46e0bcd3ac5af6a8b13da9"
+#
+# - user: snmp_user_1
+# group: network-operator
+# authentication:
+# algorithm: md5
+# password: "0x5632724fb8ac3699296af26281e1d0f1"
+# localized_key: True
+#
+# - authentication:
+# algorithm: md5
+# localized_key: true
+# password: "0x5632724fb8ac3699296af26281e1d0f1"
+# priv:
+# aes_128: true
+# privacy_password: "0x5632724fb8ac3699296af26281e1d0f1"
+# group: network-operator
+# user: snmp_user_2
+#
+# use_acls:
+# - user: snmp_user_1
+# ipv4: acl1
+# ipv6: acl2
+# - user: snmp_user_2
+# ipv4: acl3
+# ipv6: acl4
+#
+# commands:
+# - no snmp-server contact nxosswitchadmin@localhost
+# - no snmp-server location serverroom-1
+# - no snmp-server aaa-user cache-timeout 36000
+# - no snmp-server user admin network-admin auth md5 0xcbde46b02c46e0bcd3ac5af6a8b13da9 priv 0xcbde46b02c46e0bcd3ac5af6a8b13da9 localizedkey
+# - no snmp-server user snmp_user_1 network-operator auth md5 0x5632724fb8ac3699296af26281e1d0f1 localizedkey
+# - no snmp-server user snmp_user_2 network-operator auth md5 0x5632724fb8ac3699296af26281e1d0f1 priv aes-128 0x5632724fb8ac3699296af26281e1d0f1 localizedkey
+# - no snmp-server user snmp_user_1 use-ipv4acl acl1 use-ipv6acl acl2
+# - no snmp-server user snmp_user_2 use-ipv4acl acl3 use-ipv6acl acl4
+# - no snmp-server host 192.0.2.1 traps version 1 public
+# - no snmp-server host 192.0.2.1 source-interface Ethernet1/1
+# - no snmp-server host 192.0.2.2 informs version 3 auth NMS
+# - no snmp-server community private group network-admin
+# - no snmp-server community public group network-operator
+# - no snmp-server enable traps aaa server-state-change
+# - no snmp-server enable traps system Clock-change-notification
+#
+# after:
+# users:
+# auth:
+# - user: admin
+# group: network-admin
+# authentication:
+# algorithm: md5
+# password: "0xcbde46b02c46e0bcd3ac5af6a8b13da9"
+# localized_key: True
+# priv:
+# privacy_password: "0xcbde46b02c46e0bcd3ac5af6a8b13da9"
+
+# After state:
+# ------------
+# nxos-9k-rdo# show running-config | section "^snmp-server"
+# snmp-server user admin network-admin auth md5 0xcbde46b02c46e0bcd3ac5af6a8b13da9 priv 0xcbde46b02c46e0bcd3ac5af6a8b13da9 localizedkey
+
+# Using rendered
+# ---------------
+
+- name: Render platform specific configuration lines with state rendered (without connecting to the device)
+ cisco.nxos.nxos_snmp_server:
+ config:
+ aaa_user:
+ cache_timeout: 36000
+ communities:
+ - community: public
+ group: network-operator
+ - community: private
+ group: network-admin
+ contact: nxosswitchadmin@localhost
+ location: serverroom-1
+ traps:
+ aaa:
+ server_state_change: True
+ system:
+ clock_change_notification: True
+ hosts:
+ - host: 192.0.2.1
+ traps: True
+ version: '1'
+ community: public
+ - host: 192.0.2.1
+ source_interface: Ethernet1/1
+ - host: 192.0.2.2
+ informs: True
+ version: '3'
+ auth: NMS
+ users:
+ auth:
+ - user: snmp_user_1
+ group: network-operator
+ authentication:
+ algorithm: md5
+ password: '0x5632724fb8ac3699296af26281e1d0f1'
+ localized_key: True
+ - user: snmp_user_2
+ group: network-operator
+ authentication:
+ algorithm: md5
+ password: '0x5632724fb8ac3699296af26281e1d0f1'
+ localized_key: True
+ priv:
+ privacy_password: '0x5632724fb8ac3699296af26281e1d0f1'
+ aes_128: True
+ use_acls:
+ - user: snmp_user_1
+ ipv4: acl1
+ ipv6: acl2
+ - user: snmp_user_2
+ ipv4: acl3
+ ipv6: acl4
+ state: rendered
+
+
+# Task Output (redacted)
+# -----------------------
+# rendered:
+# - snmp-server contact nxosswitchadmin@localhost
+# - snmp-server location serverroom-1
+# - snmp-server aaa-user cache-timeout 36000
+# - snmp-server user snmp_user_1 network-operator auth md5 0x5632724fb8ac3699296af26281e1d0f1 localizedkey
+# - snmp-server user snmp_user_2 network-operator auth md5 0x5632724fb8ac3699296af26281e1d0f1 priv aes-128 0x5632724fb8ac3699296af26281e1d0f1 localizedkey
+# - snmp-server user snmp_user_1 use-ipv4acl acl1 use-ipv6acl acl2
+# - snmp-server user snmp_user_2 use-ipv4acl acl3 use-ipv6acl acl4
+# - snmp-server host 192.0.2.1 traps version 1 public
+# - snmp-server host 192.0.2.1 source-interface Ethernet1/1
+# - snmp-server host 192.0.2.2 informs version 3 auth NMS
+# - snmp-server community private group network-admin
+# - snmp-server community public group network-operator
+# - snmp-server enable traps aaa server-state-change
+# - snmp-server enable traps system Clock-change-notification
+
+# Using parsed
+
+# parsed.cfg
+# ------------
+# snmp-server contact nxosswitchadmin@localhost
+# snmp-server location serverroom-1
+# snmp-server aaa-user cache-timeout 36000
+# snmp-server user snmp_user_1 network-operator auth md5 0x5632724fb8ac3699296af26281e1d0f1 localizedkey
+# snmp-server user snmp_user_2 network-operator auth md5 0x5632724fb8ac3699296af26281e1d0f1 priv aes-128 0x5632724fb8ac3699296af26281e1d0f1 localizedkey
+# snmp-server user snmp_user_1 use-ipv4acl acl1 use-ipv6acl acl2
+# snmp-server user snmp_user_2 use-ipv4acl acl3 use-ipv6acl acl4
+# snmp-server host 192.0.2.1 traps version 1 public
+# snmp-server host 192.0.2.1 source-interface Ethernet1/1
+# snmp-server host 192.0.2.2 informs version 3 auth NMS
+# snmp-server community private group network-admin
+# snmp-server community public group network-operator
+# snmp-server enable traps aaa server-state-change
+# snmp-server enable traps system Clock-change-notification
+
+- name: Parse externally provided snmp-server configuration
+ cisco.nxos.nxos_snmp_server:
+ running_config: "{{ lookup('file', './parsed.cfg') }}"
+ state: parsed
+
+# Task output (redacted)
+# -----------------------
+# parsed:
+# aaa_user:
+# cache_timeout: 36000
+# communities:
+# - community: private
+# group: network-admin
+# - community: public
+# group: network-operator
+# contact: nxosswitchadmin@localhost
+# location: serverroom-1
+# traps:
+# aaa:
+# server_state_change: True
+# system:
+# clock_change_notification: True
+# hosts:
+# - host: 192.0.2.1
+# traps: true
+# version: "1"
+# community: public
+#
+# - host: 192.0.2.1
+# source_interface: Ethernet1/1
+#
+# - host: 192.0.2.2
+# informs: true
+# version: "3"
+# auth: NMS
+# users:
+# auth:
+# - user: snmp_user_1
+# group: network-operator
+# authentication:
+# algorithm: md5
+# password: "0x5632724fb8ac3699296af26281e1d0f1"
+# localized_key: True
+#
+# - authentication:
+# algorithm: md5
+# localized_key: true
+# password: "0x5632724fb8ac3699296af26281e1d0f1"
+# priv:
+# aes_128: true
+# privacy_password: "0x5632724fb8ac3699296af26281e1d0f1"
+# group: network-operator
+# user: snmp_user_2
+#
+# use_acls:
+# - user: snmp_user_1
+# ipv4: acl1
+# ipv6: acl2
+# - user: snmp_user_2
+# ipv4: acl3
+# ipv6: acl4
+#
+"""
+
+RETURN = """
+before:
+ description: The configuration prior to the module execution.
+ returned: when I(state) is C(merged), C(replaced), C(overridden), C(deleted) or C(purged)
+ type: dict
+ sample: >
+ This output will always be in the same format as the
+ module argspec.
+after:
+ description: The resulting configuration after module execution.
+ returned: when changed
+ type: dict
+ sample: >
+ This output will always be in the same format as the
+ module argspec.
+commands:
+ description: The set of commands pushed to the remote device.
+ returned: when I(state) is C(merged), C(replaced), C(overridden), C(deleted) or C(purged)
+ type: list
+ sample:
+ - sample command 1
+ - sample command 2
+ - sample command 3
+rendered:
+ description: The provided configuration in the task rendered in device-native format (offline).
+ returned: when I(state) is C(rendered)
+ type: list
+ sample:
+ - sample command 1
+ - sample command 2
+ - sample command 3
+gathered:
+ description: Facts about the network resource gathered from the remote device as structured data.
+ returned: when I(state) is C(gathered)
+ type: list
+ sample: >
+ This output will always be in the same format as the
+ module argspec.
+parsed:
+ description: The device native config provided in I(running_config) option parsed into structured data as per module argspec.
+ returned: when I(state) is C(parsed)
+ type: list
+ sample: >
+ This output will always be in the same format as the
+ module argspec.
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.snmp_server.snmp_server import (
+ Snmp_serverArgs,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.config.snmp_server.snmp_server import (
+ Snmp_server,
+)
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ module = AnsibleModule(
+ argument_spec=Snmp_serverArgs.argument_spec,
+ mutually_exclusive=[["config", "running_config"]],
+ required_if=[
+ ["state", "merged", ["config"]],
+ ["state", "replaced", ["config"]],
+ ["state", "overridden", ["config"]],
+ ["state", "rendered", ["config"]],
+ ["state", "parsed", ["running_config"]],
+ ],
+ supports_check_mode=True,
+ )
+
+ result = Snmp_server(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_snmp_traps.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_snmp_traps.py
new file mode 100644
index 00000000..fce1a8a2
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_snmp_traps.py
@@ -0,0 +1,319 @@
+#!/usr/bin/python
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_snmp_traps
+extends_documentation_fragment:
+- cisco.nxos.nxos
+short_description: (deprecated, removed after 2024-01-01) Manages SNMP traps.
+description:
+- Manages SNMP traps configurations.
+version_added: 1.0.0
+deprecated:
+ alternative: nxos_snmp_server
+ why: Updated modules released with more functionality
+ removed_at_date: '2024-01-01'
+author:
+- Jason Edelman (@jedelman8)
+notes:
+- Tested against NXOSv 7.3.(0)D1(1) on VIRL
+- Limited Support for Cisco MDS
+- This module works at the group level for traps. If you need to only enable/disable
+ 1 specific trap within a group, use the M(cisco.nxos.nxos_command) module.
+- Be aware that you can set a trap only for an enabled feature.
+options:
+ group:
+ description:
+ - Case sensitive group.
+ required: true
+ choices:
+ - aaa
+ - bfd
+ - bgp
+ - bridge
+ - callhome
+ - cfs
+ - config
+ - eigrp
+ - entity
+ - feature-control
+ - generic
+ - hsrp
+ - license
+ - link
+ - lldp
+ - mmode
+ - ospf
+ - pim
+ - rf
+ - rmon
+ - snmp
+ - storm-control
+ - stpx
+ - switchfabric
+ - syslog
+ - sysmgr
+ - system
+ - upgrade
+ - vtp
+ - all
+ type: str
+ state:
+ description:
+ - Manage the state of the resource.
+ required: false
+ default: enabled
+ choices:
+ - enabled
+ - disabled
+ type: str
+"""
+
+EXAMPLES = """
+# ensure lldp trap configured
+- cisco.nxos.nxos_snmp_traps:
+ group: lldp
+ state: enabled
+
+# ensure lldp trap is not configured
+- cisco.nxos.nxos_snmp_traps:
+ group: lldp
+ state: disabled
+"""
+
+RETURN = """
+commands:
+ description: command sent to the device
+ returned: always
+ type: list
+ sample: "snmp-server enable traps lldp ;"
+"""
+
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ get_capabilities,
+ load_config,
+ run_commands,
+)
+
+
+def get_platform_id(module):
+ info = get_capabilities(module).get("device_info", {})
+ return info.get("network_os_platform", "")
+
+
+def execute_show_command(command, module):
+ command = {"command": command, "output": "text"}
+
+ return run_commands(module, command)
+
+
+def flatten_list(command_lists):
+ flat_command_list = []
+ for command in command_lists:
+ if isinstance(command, list):
+ flat_command_list.extend(command)
+ else:
+ flat_command_list.append(command)
+ return flat_command_list
+
+
+def get_snmp_traps(group, module):
+ body = execute_show_command("show run snmp all", module)[0].split("\n")
+
+ resource = {}
+ feature_list = [
+ "aaa",
+ "bfd",
+ "bgp",
+ "bridge",
+ "callhome",
+ "cfs",
+ "config",
+ "eigrp",
+ "entity",
+ "feature-control",
+ "generic",
+ "hsrp",
+ "license",
+ "link",
+ "lldp",
+ "mmode",
+ "ospf",
+ "pim",
+ "rf",
+ "rmon",
+ "snmp",
+ "storm-control",
+ "stpx",
+ "switchfabric",
+ "syslog",
+ "sysmgr",
+ "system",
+ "upgrade",
+ "vtp",
+ ]
+
+ if "all" in group and "N3K-C35" in get_platform_id(module):
+ module.warn("Platform does not support bfd traps; bfd ignored for 'group: all' request")
+ feature_list.remove("bfd")
+
+ for each in feature_list:
+ for line in body:
+ if each == "ospf":
+ # ospf behaves differently when routers are present
+ if "snmp-server enable traps ospf" == line:
+ resource[each] = True
+ break
+ else:
+ if "enable traps {0}".format(each) in line:
+ if "no " in line:
+ resource[each] = False
+ break
+ else:
+ resource[each] = True
+
+ for each in feature_list:
+ if resource.get(each) is None:
+ # on some platforms, the 'no' cmd does not
+ # show up and so check if the feature is enabled
+ body = execute_show_command("show run | inc feature", module)[0]
+ if "feature {0}".format(each) in body:
+ resource[each] = False
+
+ find = resource.get(group, None)
+
+ if group == "all".lower():
+ return resource
+ elif find is not None:
+ trap_resource = {group: find}
+ return trap_resource
+ else:
+ # if 'find' is None, it means that 'group' is a
+ # currently disabled feature.
+ return {}
+
+
+def get_trap_commands(group, state, existing, module):
+ commands = []
+ enabled = False
+ disabled = False
+
+ if group == "all":
+ if state == "disabled":
+ for feature in existing:
+ if existing[feature]:
+ trap_command = "no snmp-server enable traps {0}".format(feature)
+ commands.append(trap_command)
+
+ elif state == "enabled":
+ for feature in existing:
+ if existing[feature] is False:
+ trap_command = "snmp-server enable traps {0}".format(feature)
+ commands.append(trap_command)
+
+ else:
+ if group in existing:
+ if existing[group]:
+ enabled = True
+ else:
+ disabled = True
+
+ if state == "disabled" and enabled:
+ commands.append(["no snmp-server enable traps {0}".format(group)])
+ elif state == "enabled" and disabled:
+ commands.append(["snmp-server enable traps {0}".format(group)])
+ else:
+ module.fail_json(msg="{0} is not a currently " "enabled feature.".format(group))
+
+ return commands
+
+
+def main():
+ argument_spec = dict(
+ state=dict(choices=["enabled", "disabled"], default="enabled"),
+ group=dict(
+ choices=[
+ "aaa",
+ "bfd",
+ "bgp",
+ "bridge",
+ "callhome",
+ "cfs",
+ "config",
+ "eigrp",
+ "entity",
+ "feature-control",
+ "generic",
+ "hsrp",
+ "license",
+ "link",
+ "lldp",
+ "mmode",
+ "ospf",
+ "pim",
+ "rf",
+ "rmon",
+ "snmp",
+ "storm-control",
+ "stpx",
+ "switchfabric",
+ "syslog",
+ "sysmgr",
+ "system",
+ "upgrade",
+ "vtp",
+ "all",
+ ],
+ required=True,
+ ),
+ )
+
+ module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)
+
+ warnings = list()
+ results = {"changed": False, "commands": [], "warnings": warnings}
+
+ group = module.params["group"].lower()
+ state = module.params["state"]
+
+ existing = get_snmp_traps(group, module)
+
+ commands = get_trap_commands(group, state, existing, module)
+ cmds = flatten_list(commands)
+ if cmds:
+ results["changed"] = True
+ if not module.check_mode:
+ load_config(module, cmds)
+
+ if "configure" in cmds:
+ cmds.pop(0)
+ results["commands"] = cmds
+
+ module.exit_json(**results)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_snmp_user.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_snmp_user.py
new file mode 100644
index 00000000..a3b0b5fa
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_snmp_user.py
@@ -0,0 +1,412 @@
+#!/usr/bin/python
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_snmp_user
+extends_documentation_fragment:
+- cisco.nxos.nxos
+short_description: (deprecated, removed after 2024-01-01) Manages SNMP users for monitoring.
+description:
+- Manages SNMP user configuration.
+version_added: 1.0.0
+deprecated:
+ alternative: nxos_snmp_server
+ why: Updated modules released with more functionality
+ removed_at_date: '2024-01-01'
+author:
+- Jason Edelman (@jedelman8)
+notes:
+- Tested against NXOSv 7.3.(0)D1(1) on VIRL
+- Limited Support for Cisco MDS
+- Authentication parameters not idempotent.
+options:
+ user:
+ description:
+ - Name of the user.
+ required: true
+ type: str
+ group:
+ description:
+ - Group to which the user will belong to. If state = present, and the user is
+ existing, the group is added to the user. If the user is not existing, user
+ entry is created with this group argument. If state = absent, only the group
+ is removed from the user entry. However, to maintain backward compatibility,
+ if the existing user belongs to only one group, and if group argument is same
+ as the existing user's group, then the user entry also is deleted.
+ type: str
+ authentication:
+ description:
+ - Authentication parameters for the user.
+ choices:
+ - md5
+ - sha
+ type: str
+ pwd:
+ description:
+ - Authentication password when using md5 or sha. This is not idempotent
+ type: str
+ privacy:
+ description:
+ - Privacy password for the user. This is not idempotent
+ type: str
+ encrypt:
+ description:
+ - Enables AES-128 bit encryption when using privacy password.
+ type: bool
+ state:
+ description:
+ - Manage the state of the resource.
+ default: present
+ choices:
+ - present
+ - absent
+ type: str
+"""
+
+EXAMPLES = """
+- cisco.nxos.nxos_snmp_user:
+ user: ntc
+ group: network-operator
+ authentication: md5
+ pwd: test_password
+"""
+
+RETURN = """
+commands:
+ description: commands sent to the device
+ returned: always
+ type: list
+ sample: ["snmp-server user ntc network-operator auth md5 test_password"]
+"""
+
+import re
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ load_config,
+ run_commands,
+)
+
+
+def execute_show_command(command, module, text=False):
+ command = {"command": command, "output": "json"}
+ if text:
+ command["output"] = "text"
+
+ return run_commands(module, command)
+
+
+def flatten_list(command_lists):
+ flat_command_list = []
+ for command in command_lists:
+ if isinstance(command, list):
+ flat_command_list.extend(command)
+ else:
+ flat_command_list.append(command)
+ return flat_command_list
+
+
+def get_snmp_groups(module):
+ data = execute_show_command("show snmp group", module)[0]
+ group_list = []
+
+ try:
+ group_table = data["TABLE_role"]["ROW_role"]
+ for group in group_table:
+ group_list.append(group["role_name"])
+ except (KeyError, AttributeError):
+ return group_list
+
+ return group_list
+
+
+def get_snmp_user(user, module):
+ command = "show snmp user {0}".format(user)
+ body = execute_show_command(command, module, text=True)
+ body_text = body[0]
+
+ if "No such entry" not in body[0]:
+ body = execute_show_command(command, module)
+
+ resource = {}
+ try:
+ # The TABLE and ROW keys differ between NXOS platforms.
+ if body[0].get("TABLE_snmp_user"):
+ tablekey = "TABLE_snmp_user"
+ rowkey = "ROW_snmp_user"
+ tablegrpkey = "TABLE_snmp_group_names"
+ rowgrpkey = "ROW_snmp_group_names"
+ authkey = "auth_protocol"
+ privkey = "priv_protocol"
+ grpkey = "group_names"
+ elif body[0].get("TABLE_snmp_users"):
+ tablekey = "TABLE_snmp_users"
+ rowkey = "ROW_snmp_users"
+ tablegrpkey = "TABLE_groups"
+ rowgrpkey = "ROW_groups"
+ authkey = "auth"
+ privkey = "priv"
+ grpkey = "group"
+
+ rt = body[0][tablekey][rowkey]
+ # on some older platforms, all groups except the 1st one
+ # are in list elements by themselves and they are
+ # indexed by 'user'. This is due to a platform bug.
+ # Get first element if rt is a list due to the bug
+ # or if there is no bug, parse rt directly
+ if isinstance(rt, list):
+ resource_table = rt[0]
+ else:
+ resource_table = rt
+
+ resource["user"] = user
+ resource["authentication"] = str(resource_table[authkey]).strip()
+ encrypt = str(resource_table[privkey]).strip()
+ if encrypt.startswith("aes"):
+ resource["encrypt"] = "aes-128"
+ else:
+ resource["encrypt"] = "none"
+
+ groups = []
+ if tablegrpkey in resource_table:
+ group_table = resource_table[tablegrpkey][rowgrpkey]
+ try:
+ for group in group_table:
+ groups.append(str(group[grpkey]).strip())
+ except TypeError:
+ groups.append(str(group_table[grpkey]).strip())
+
+ # Now for the platform bug case, get the groups
+ if isinstance(rt, list):
+ # remove 1st element from the list as this is parsed already
+ rt.pop(0)
+ # iterate through other elements indexed by
+ # 'user' and add it to groups.
+ for each in rt:
+ groups.append(each["user"].strip())
+
+ # Some 'F' platforms use 'group' key instead
+ elif "group" in resource_table:
+ # single group is a string, multiple groups in a list
+ groups = resource_table["group"]
+ if isinstance(groups, str):
+ groups = [groups]
+
+ resource["group"] = groups
+
+ except (KeyError, AttributeError, IndexError, TypeError):
+ if not resource and body_text and "No such entry" not in body_text:
+ # 6K and other platforms may not return structured output;
+ # attempt to get state from text output
+ resource = get_non_structured_snmp_user(body_text)
+
+ return resource
+
+
+def get_non_structured_snmp_user(body_text):
+ # This method is a workaround for platforms that don't support structured
+ # output for 'show snmp user <foo>'. This workaround may not work on all
+ # platforms. Sample non-struct output:
+ #
+ # User Auth Priv(enforce) Groups acl_filter
+ # ____ ____ _____________ ______ __________
+ # sample1 no no network-admin ipv4:my_acl
+ # network-operator
+ # priv-11
+ # -OR-
+ # sample2 md5 des(no) priv-15
+ # -OR-
+ # sample3 md5 aes-128(no) network-admin
+ resource = {}
+ output = body_text.rsplit("__________")[-1]
+ pat = re.compile(
+ r"^(?P<user>\S+)\s+"
+ r"(?P<auth>\S+)\s+"
+ r"(?P<priv>[\w\d-]+)(?P<enforce>\([\w\d-]+\))*\s+"
+ r"(?P<group>\S+)",
+ re.M,
+ )
+ m = re.search(pat, output)
+ if not m:
+ return resource
+ resource["user"] = m.group("user")
+ resource["auth"] = m.group("auth")
+ resource["encrypt"] = "aes-128" if "aes" in str(m.group("priv")) else "none"
+
+ resource["group"] = [m.group("group")]
+ more_groups = re.findall(r"^\s+([\w\d-]+)\s*$", output, re.M)
+ if more_groups:
+ resource["group"] += more_groups
+
+ return resource
+
+
+def remove_snmp_user(user, group=None):
+ if group:
+ return ["no snmp-server user {0} {1}".format(user, group)]
+ else:
+ return ["no snmp-server user {0}".format(user)]
+
+
+def config_snmp_user(proposed, user, reset):
+ if reset:
+ commands = remove_snmp_user(user)
+ else:
+ commands = []
+
+ if proposed.get("group"):
+ cmd = "snmp-server user {0} {group}".format(user, **proposed)
+ else:
+ cmd = "snmp-server user {0}".format(user)
+
+ auth = proposed.get("authentication", None)
+ pwd = proposed.get("pwd", None)
+
+ if auth and pwd:
+ cmd += " auth {authentication} {pwd}".format(**proposed)
+
+ encrypt = proposed.get("encrypt", None)
+ privacy = proposed.get("privacy", None)
+
+ if encrypt and privacy:
+ cmd += " priv {encrypt} {privacy}".format(**proposed)
+ elif privacy:
+ cmd += " priv {privacy}".format(**proposed)
+
+ if cmd:
+ commands.append(cmd)
+
+ return commands
+
+
+def main():
+ argument_spec = dict(
+ user=dict(required=True, type="str"),
+ group=dict(type="str"),
+ pwd=dict(type="str", no_log=True),
+ privacy=dict(type="str"),
+ authentication=dict(choices=["md5", "sha"]),
+ encrypt=dict(type="bool"),
+ state=dict(choices=["absent", "present"], default="present"),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ required_together=[["authentication", "pwd"], ["encrypt", "privacy"]],
+ supports_check_mode=True,
+ )
+
+ warnings = list()
+ results = {"changed": False, "commands": [], "warnings": warnings}
+
+ user = module.params["user"]
+ group = module.params["group"]
+ pwd = module.params["pwd"]
+ privacy = module.params["privacy"]
+ encrypt = module.params["encrypt"]
+ authentication = module.params["authentication"]
+ state = module.params["state"]
+
+ if privacy and encrypt:
+ if not pwd and authentication:
+ module.fail_json(
+ msg="pwd and authentication must be provided " "when using privacy and encrypt",
+ )
+
+ if group and group not in get_snmp_groups(module):
+ module.fail_json(msg="group not configured yet on switch.")
+
+ existing = get_snmp_user(user, module)
+
+ if state == "present" and existing:
+ if group:
+ if group not in existing["group"]:
+ existing["group"] = None
+ else:
+ existing["group"] = group
+ else:
+ existing["group"] = None
+
+ commands = []
+
+ if state == "absent" and existing:
+ if group:
+ if group in existing["group"]:
+ if len(existing["group"]) == 1:
+ commands.append(remove_snmp_user(user))
+ else:
+ commands.append(remove_snmp_user(user, group))
+ else:
+ commands.append(remove_snmp_user(user))
+
+ elif state == "present":
+ reset = False
+
+ args = dict(
+ user=user,
+ pwd=pwd,
+ group=group,
+ privacy=privacy,
+ encrypt=encrypt,
+ authentication=authentication,
+ )
+ proposed = dict((k, v) for k, v in args.items() if v is not None)
+
+ if not existing:
+ if encrypt:
+ proposed["encrypt"] = "aes-128"
+ commands.append(config_snmp_user(proposed, user, reset))
+
+ elif existing:
+ if encrypt and not existing["encrypt"].startswith("aes"):
+ reset = True
+ proposed["encrypt"] = "aes-128"
+
+ delta = dict(set(proposed.items()).difference(existing.items()))
+
+ if delta.get("pwd"):
+ delta["authentication"] = authentication
+
+ if delta and encrypt:
+ delta["encrypt"] = "aes-128"
+
+ if delta:
+ command = config_snmp_user(delta, user, reset)
+ commands.append(command)
+
+ cmds = flatten_list(commands)
+ if cmds:
+ results["changed"] = True
+ if not module.check_mode:
+ load_config(module, cmds)
+
+ if "configure" in cmds:
+ cmds.pop(0)
+ results["commands"] = cmds
+
+ module.exit_json(**results)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_static_routes.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_static_routes.py
new file mode 100644
index 00000000..15b8e690
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_static_routes.py
@@ -0,0 +1,477 @@
+#!/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 nxos_static_routes
+"""
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_static_routes
+short_description: Static routes resource module
+description: This module configures and manages the attributes of static routes on
+ Cisco NX-OS platforms.
+version_added: 1.0.0
+author: Adharsh Srivats Rangarajan (@adharshsrivatsr)
+notes:
+- Tested against NX-OS 7.3.(0)D1(1) on VIRL
+- Unsupported for Cisco MDS
+- When a route is configured for a non-existent VRF, the VRF is created and the route
+ is added to it.
+- When deleting routes for a VRF, all routes inside the VRF are deleted, but the VRF
+ is not deleted.
+options:
+ running_config:
+ description:
+ - This option is used only with state I(parsed).
+ - The value of this option should be the output received from the NX-OS device
+ by executing the following commands in order B(show running-config | include
+ '^ip(v6)* route') and B(show running-config | section '^vrf context').
+ - The state I(parsed) reads the configuration from C(running_config) option and
+ transforms it into Ansible structured data as per the resource module's argspec
+ and the value is then returned in the I(parsed) key within the result.
+ type: str
+ config:
+ description:
+ - A list of configurations for static routes
+ 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
+ routes:
+ description: A dictionary that specifies the static route configurations
+ elements: dict
+ type: list
+ suboptions:
+ dest:
+ description:
+ - Destination prefix of static route
+ - The address format is <ipv4/v6 address>/<mask>
+ - The mask is number in range 0-32 for IPv4 and in range 0-128 for
+ IPv6
+ type: str
+ required: true
+ next_hops:
+ description:
+ - Details of route to be taken
+ type: list
+ elements: dict
+ suboptions:
+ forward_router_address:
+ description:
+ - IP address of the next hop router
+ type: str
+ # required: True
+ interface:
+ description:
+ - Outgoing interface to take. For anything except 'Null0', then
+ next hop IP address should also be configured.
+ type: str
+ admin_distance:
+ description:
+ - Preference or administrative distance of route (range 1-255)
+ type: int
+ route_name:
+ description:
+ - Name of the static route
+ type: str
+ tag:
+ description:
+ - Route tag value (numeric)
+ type: int
+ track:
+ description:
+ - Track value (range 1 - 512). Track must already be configured
+ on the device before adding the route.
+ type: int
+ dest_vrf:
+ description:
+ - VRF of the destination
+ type: str
+ state:
+ description:
+ - The state the configuration should be left in
+ type: str
+ choices:
+ - deleted
+ - merged
+ - overridden
+ - replaced
+ - gathered
+ - rendered
+ - parsed
+ default: merged
+
+"""
+EXAMPLES = """
+# Using deleted:
+
+# Before state:
+# -------------
+#
+# ip route 192.0.2.32/28 192.0.2.12 name new_route
+# ip route 192.0.2.26/24 192.0.2.13 tag 12
+
+- name: Delete all routes
+ cisco.nxos.nxos_static_routes:
+ state: deleted
+
+# After state:
+# ------------
+#
+
+
+# Before state:
+# ------------
+#
+# ip route 192.0.2.16/28 192.0.2.24 name new_route
+# ip route 192.0.2.80/28 192.0.2.26 tag 12
+# vrf context trial_vrf
+# ip route 192.0.2.64/28 192.0.2.22 tag 4
+# ip route 192.0.2.64/28 192.0.2.23 name merged_route 1
+# ipv6 route 2200:10::/36 2048:ae12::1 vrf dest 5
+
+- name: Delete routes based on VRF
+ cisco.nxos.nxos_static_routes:
+ config:
+ - vrf: trial_vrf
+ state: deleted
+
+# After state:
+# -----------
+# ip route 192.0.2.16/28 192.0.2.24 name new_route
+# ip route 192.0.2.80/28 192.0.2.26 tag 12
+# vrf context trial_vrf
+
+
+# Before state:
+# ------------
+#
+# ip route 192.0.2.16/28 192.0.2.24 name new_route
+# ip route 192.0.2.80/28 192.0.2.26 tag 12
+# vrf context trial_vrf
+# ip route 192.0.2.64/28 192.0.2.22 tag 4
+# ip route 192.0.2.64/28 192.0.2.23 name merged_route 1
+# ipv6 route 2200:10::/36 2048:ae12::1 vrf dest 5
+
+- name: Delete routes based on AFI in a VRF
+ cisco.nxos.nxos_static_routes:
+ config:
+ - vrf: trial_vrf
+ address_families:
+ - afi: ipv4
+ state: deleted
+
+# After state:
+# -----------
+# ip route 192.0.2.16/28 192.0.2.24 name new_route
+# ip route 192.0.2.80/28 192.0.2.26 tag 12
+# vrf context trial_vrf
+# ipv6 route 2200:10::/36 2048:ae12::1 vrf dest 5
+
+
+# Before state:
+# -----------
+# ip route 192.0.2.16/28 192.0.2.24 name new_route
+# vrf context trial_vrf
+# ipv6 route 2200:10::/36 2048:ae12::1 vrf dest 5
+
+
+# Using merged
+
+# Before state:
+# -------------
+#
+
+- name: Merge new static route configuration
+ cisco.nxos.nxos_static_routes:
+ config:
+ - vrf: trial_vrf
+ address_families:
+ - afi: ipv4
+ routes:
+ - dest: 192.0.2.64/24
+ next_hops:
+ - forward_router_address: 192.0.2.22
+ tag: 4
+ admin_distance: 2
+
+ - address_families:
+ - afi: ipv4
+ routes:
+ - dest: 192.0.2.16/24
+ next_hops:
+ - forward_router_address: 192.0.2.24
+ route_name: new_route
+ - afi: ipv6
+ routes:
+ - dest: 2001:db8::/64
+ next_hops:
+ - interface: eth1/3
+ forward_router_address: 2001:db8::12
+ state: merged
+
+# After state:
+# ------------
+#
+# ip route 192.0.2.16/24 192.0.2.24 name new_route
+# ipv6 route 2001:db8::/64 Ethernet1/3 2001:db8::12
+# vrf context trial_vrf
+# ip route 192.0.2.0/24 192.0.2.22 tag 4 2
+
+
+# Using overridden:
+
+# Before state:
+# -------------
+#
+# ip route 192.0.2.16/28 192.0.2.24 name new_route
+# ip route 192.0.2.80/28 192.0.2.26 tag 12
+# vrf context trial_vrf
+# ip route 192.0.2.64/28 192.0.2.22 tag 4
+# ip route 192.0.2.64/28 192.0.2.23 name merged_route 1
+
+- name: Overriden existing static route configuration with new configuration
+ cisco.nxos.nxos_static_routes:
+ config:
+ - vrf: trial_vrf
+ address_families:
+ - afi: ipv4
+ routes:
+ - dest: 192.0.2.16/28
+ next_hops:
+ - forward_router_address: 192.0.2.23
+ route_name: overridden_route1
+ admin_distance: 3
+
+ - forward_router_address: 192.0.2.45
+ route_name: overridden_route2
+ dest_vrf: destinationVRF
+ interface: Ethernet1/2
+ state: overridden
+
+# After state:
+# ------------
+#
+# ip route 192.0.2.16/28 192.0.2.23 name replaced_route1 3
+# ip route 192.0.2.16/28 Ethernet1/2 192.0.2.45 vrf destinationVRF name replaced_route2
+
+
+# Using replaced:
+
+# Before state:
+# ------------
+# ip route 192.0.2.16/28 192.0.2.24 name new_route
+# ip route 192.0.2.80/28 192.0.2.26 tag 12
+# vrf context trial_vrf
+# ip route 192.0.2.64/28 192.0.2.22 tag 4
+# ip route 192.0.2.64/28 192.0.2.23 name merged_route 1
+
+- name: Replaced the existing static configuration of a prefix with new configuration
+ cisco.nxos.nxos_static_routes:
+ config:
+ - address_families:
+ - afi: ipv4
+ routes:
+ - dest: 192.0.2.16/28
+ next_hops:
+ - forward_router_address: 192.0.2.23
+ route_name: replaced_route1
+ admin_distance: 3
+
+ - forward_router_address: 192.0.2.45
+ route_name: replaced_route2
+ dest_vrf: destinationVRF
+ interface: Ethernet1/2
+ state: replaced
+
+# After state:
+# -----------
+# ip route 192.0.2.16/28 192.0.2.23 name replaced_route1 3
+# ip route 192.0.2.16/28 Ethernet1/2 192.0.2.45 vrf destinationVRF name replaced_route2
+# ip route 192.0.2.80/28 192.0.2.26 tag 12
+# vrf context trial_vrf
+# ip route 192.0.2.64/28 192.0.2.22 tag 4
+# ip route 192.0.2.64/28 192.0.2.23 name merged_route 1
+
+
+# Using gathered:
+
+# Before state:
+# -------------
+# ipv6 route 2001:db8:12::/32 2001:db8::12
+# vrf context Test
+# ip route 192.0.2.48/28 192.0.2.13
+# ip route 192.0.2.48/28 192.0.2.14 5
+
+- name: Gather the exisitng condiguration
+ cisco.nxos.nxos_static_routes:
+ state: gathered
+
+# returns:
+# gathered:
+# - vrf: Test
+# address_families:
+# - afi: ipv4
+# routes:
+# - dest: 192.0.2.48/28
+# next_hops:
+# - forward_router_address: 192.0.2.13
+#
+# - forward_router_address: 192.0.2.14
+# admin_distance: 5
+#
+# - address_families:
+# - afi: ipv6
+# routes:
+# - dest: 2001:db8:12::/32
+# next_hops:
+# - forward_router_address: 2001:db8::12
+
+
+# Using rendered:
+
+- name: Render required configuration to be pushed to the device
+ cisco.nxos.nxos_static_routes:
+ config:
+ - address_families:
+ - afi: ipv4
+ routes:
+ - dest: 192.0.2.48/28
+ next_hops:
+ - forward_router_address: 192.0.2.13
+
+ - afi: ipv6
+ routes:
+ - dest: 2001:db8::/64
+ next_hops:
+ - interface: eth1/3
+ forward_router_address: 2001:db8::12
+ state: rendered
+
+# returns
+# rendered:
+# vrf context default
+# ip route 192.0.2.48/28 192.0.2.13
+# ipv6 route 2001:db8::/64 Ethernet1/3 2001:db8::12
+
+
+# Using parsed
+
+- name: Parse the config to structured data
+ cisco.nxos.nxos_static_routes:
+ running_config: |
+ ipv6 route 2002:db8:12::/32 2002:db8:12::1
+ vrf context Test
+ ip route 192.0.2.48/28 192.0.2.13
+ ip route 192.0.2.48/28 192.0.2.14 5
+
+# returns:
+# parsed:
+# - vrf: Test
+# address_families:
+# - afi: ipv4
+# routes:
+# - dest: 192.0.2.48/28
+# next_hops:
+# - forward_router_address: 192.0.2.13
+#
+# - forward_router_address: 192.0.2.14
+# admin_distance: 5
+#
+# - address_families:
+# - afi: ipv6
+# routes:
+# - dest: 2002:db8:12::/32
+# next_hops:
+# - forward_router_address: 2002:db8:12::1
+
+
+"""
+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: ['ip route 192.0.2.48/28 192.0.2.12 Ethernet1/2 name sample_route',
+ 'ipv6 route 2001:db8:3000::/36 2001:db8:200:2::2', 'vrf context test','ip route 192.0.2.48/28 192.0.2.121']
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.static_routes.static_routes import (
+ Static_routesArgs,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.config.static_routes.static_routes import (
+ Static_routes,
+)
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ module = AnsibleModule(argument_spec=Static_routesArgs.argument_spec, supports_check_mode=True)
+
+ result = Static_routes(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_system.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_system.py
new file mode 100644
index 00000000..df4bbde0
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_system.py
@@ -0,0 +1,399 @@
+#!/usr/bin/python
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_system
+extends_documentation_fragment:
+- cisco.nxos.nxos
+author: Peter Sprygada (@privateip)
+short_description: Manage the system attributes on Cisco NXOS devices
+notes:
+- Unsupported for Cisco MDS
+description:
+- This module provides declarative management of node system attributes on Cisco NXOS
+ devices. It provides an option to configure host system parameters or remove those
+ parameters from the device active configuration.
+version_added: 1.0.0
+options:
+ hostname:
+ description:
+ - Configure the device hostname parameter. This option takes an ASCII string value
+ or keyword 'default'
+ type: str
+ domain_name:
+ description:
+ - Configures the default domain name suffix to be used when referencing this node
+ by its FQDN. This argument accepts either a list of domain names or a list
+ of dicts that configure the domain name and VRF name or keyword 'default'. See
+ examples.
+ type: list
+ elements: raw
+ domain_lookup:
+ description:
+ - Enables or disables the DNS lookup feature in Cisco NXOS. This argument accepts
+ boolean values. When enabled, the system will try to resolve hostnames using
+ DNS and when disabled, hostnames will not be resolved.
+ type: bool
+ domain_search:
+ description:
+ - Configures a list of domain name suffixes to search when performing DNS name
+ resolution. This argument accepts either a list of domain names or a list of
+ dicts that configure the domain name and VRF name or keyword 'default'. See
+ examples.
+ type: list
+ elements: raw
+ name_servers:
+ description:
+ - List of DNS name servers by IP address to use to perform name resolution lookups. This
+ argument accepts either a list of DNS servers or a list of hashes that configure
+ the name server and VRF name or keyword 'default'. See examples.
+ type: list
+ elements: raw
+ system_mtu:
+ description:
+ - Specifies the mtu, must be an integer or keyword 'default'.
+ type: str
+ state:
+ description:
+ - State of the configuration values in the device's current active configuration. When
+ set to I(present), the values should be configured in the device active configuration
+ and when set to I(absent) the values should not be in the device active configuration
+ default: present
+ choices:
+ - present
+ - absent
+ type: str
+"""
+
+EXAMPLES = """
+- name: configure hostname and domain-name
+ cisco.nxos.nxos_system:
+ hostname: nxos01
+ domain_name: test.example.com
+
+- name: remove configuration
+ cisco.nxos.nxos_system:
+ state: absent
+
+- name: configure name servers
+ cisco.nxos.nxos_system:
+ name_servers:
+ - 8.8.8.8
+ - 8.8.4.4
+
+- name: configure name servers with VRF support
+ cisco.nxos.nxos_system:
+ name_servers:
+ - {server: 8.8.8.8, vrf: mgmt}
+ - {server: 8.8.4.4, vrf: mgmt}
+"""
+
+RETURN = """
+commands:
+ description: The list of configuration mode commands to send to the device
+ returned: always
+ type: list
+ sample:
+ - hostname nxos01
+ - ip domain-name test.example.com
+"""
+import re
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils.six import iteritems
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import (
+ NetworkConfig,
+)
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import (
+ ComplexList,
+)
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ get_config,
+ load_config,
+)
+
+
+_CONFIGURED_VRFS = None
+
+
+def has_vrf(module, vrf):
+ global _CONFIGURED_VRFS
+ if _CONFIGURED_VRFS is not None:
+ return vrf in _CONFIGURED_VRFS
+ config = get_config(module)
+ _CONFIGURED_VRFS = re.findall(r"vrf context (\S+)", config)
+ return vrf in _CONFIGURED_VRFS
+
+
+def map_obj_to_commands(want, have, module):
+ commands = list()
+ state = module.params["state"]
+
+ def needs_update(x):
+ return want.get(x) and (want.get(x) != have.get(x))
+
+ def difference(x, y, z):
+ return [item for item in x[z] if item not in y[z]]
+
+ def remove(cmd, commands, vrf=None):
+ if vrf:
+ commands.append("vrf context %s" % vrf)
+ commands.append(cmd)
+ if vrf:
+ commands.append("exit")
+
+ def add(cmd, commands, vrf=None):
+ if vrf:
+ if not has_vrf(module, vrf):
+ module.fail_json(msg="invalid vrf name %s" % vrf)
+ return remove(cmd, commands, vrf)
+
+ if state == "absent":
+ if have["hostname"]:
+ commands.append("no hostname")
+
+ for item in have["domain_name"]:
+ cmd = "no ip domain-name %s" % item["name"]
+ remove(cmd, commands, item["vrf"])
+
+ for item in have["domain_search"]:
+ cmd = "no ip domain-list %s" % item["name"]
+ remove(cmd, commands, item["vrf"])
+
+ for item in have["name_servers"]:
+ cmd = "no ip name-server %s" % item["server"]
+ remove(cmd, commands, item["vrf"])
+
+ if have["system_mtu"]:
+ commands.append("no system jumbomtu")
+
+ if state == "present":
+ if needs_update("hostname"):
+ if want["hostname"] == "default":
+ if have["hostname"]:
+ commands.append("no hostname")
+ else:
+ commands.append("hostname %s" % want["hostname"])
+
+ if want.get("domain_lookup") is not None:
+ if have.get("domain_lookup") != want.get("domain_lookup"):
+ cmd = "ip domain-lookup"
+ if want["domain_lookup"] is False:
+ cmd = "no %s" % cmd
+ commands.append(cmd)
+
+ if want["domain_name"]:
+ if want.get("domain_name")[0]["name"] == "default":
+ if have["domain_name"]:
+ for item in have["domain_name"]:
+ cmd = "no ip domain-name %s" % item["name"]
+ remove(cmd, commands, item["vrf"])
+ else:
+ for item in difference(have, want, "domain_name"):
+ cmd = "no ip domain-name %s" % item["name"]
+ remove(cmd, commands, item["vrf"])
+ for item in difference(want, have, "domain_name"):
+ cmd = "ip domain-name %s" % item["name"]
+ add(cmd, commands, item["vrf"])
+
+ if want["domain_search"]:
+ if want.get("domain_search")[0]["name"] == "default":
+ if have["domain_search"]:
+ for item in have["domain_search"]:
+ cmd = "no ip domain-list %s" % item["name"]
+ remove(cmd, commands, item["vrf"])
+ else:
+ for item in difference(have, want, "domain_search"):
+ cmd = "no ip domain-list %s" % item["name"]
+ remove(cmd, commands, item["vrf"])
+ for item in difference(want, have, "domain_search"):
+ cmd = "ip domain-list %s" % item["name"]
+ add(cmd, commands, item["vrf"])
+
+ if want["name_servers"]:
+ if want.get("name_servers")[0]["server"] == "default":
+ if have["name_servers"]:
+ for item in have["name_servers"]:
+ cmd = "no ip name-server %s" % item["server"]
+ remove(cmd, commands, item["vrf"])
+ else:
+ for item in difference(have, want, "name_servers"):
+ cmd = "no ip name-server %s" % item["server"]
+ remove(cmd, commands, item["vrf"])
+ for item in difference(want, have, "name_servers"):
+ cmd = "ip name-server %s" % item["server"]
+ add(cmd, commands, item["vrf"])
+
+ if needs_update("system_mtu"):
+ if want["system_mtu"] == "default":
+ if have["system_mtu"]:
+ commands.append("no system jumbomtu")
+ else:
+ commands.append("system jumbomtu %s" % want["system_mtu"])
+
+ return commands
+
+
+def parse_hostname(config):
+ match = re.search(r"^hostname (\S+)", config, re.M)
+ if match:
+ return match.group(1)
+
+
+def parse_domain_name(config, vrf_config):
+ objects = list()
+ match = re.search(r"^ip domain-name (\S+)", config, re.M)
+ if match:
+ objects.append({"name": match.group(1), "vrf": None})
+
+ for vrf, cfg in iteritems(vrf_config):
+ match = re.search(r"ip domain-name (\S+)", cfg, re.M)
+ if match:
+ objects.append({"name": match.group(1), "vrf": vrf})
+
+ return objects
+
+
+def parse_domain_search(config, vrf_config):
+ objects = list()
+
+ for item in re.findall(r"^ip domain-list (\S+)", config, re.M):
+ objects.append({"name": item, "vrf": None})
+
+ for vrf, cfg in iteritems(vrf_config):
+ for item in re.findall(r"ip domain-list (\S+)", cfg, re.M):
+ objects.append({"name": item, "vrf": vrf})
+
+ return objects
+
+
+def parse_name_servers(config, vrf_config, vrfs):
+ objects = list()
+
+ match = re.search("^ip name-server (.+)$", config, re.M)
+ if match and "use-vrf" not in match.group(1):
+ for addr in match.group(1).split(" "):
+ objects.append({"server": addr, "vrf": None})
+
+ for vrf, cfg in iteritems(vrf_config):
+ vrf_match = re.search("ip name-server (.+)", cfg, re.M)
+ if vrf_match:
+ for addr in vrf_match.group(1).split(" "):
+ objects.append({"server": addr, "vrf": vrf})
+
+ return objects
+
+
+def parse_system_mtu(config):
+ match = re.search(r"^system jumbomtu (\d+)", config, re.M)
+ if match:
+ return match.group(1)
+
+
+def map_config_to_obj(module):
+ config = get_config(module)
+ configobj = NetworkConfig(indent=2, contents=config)
+
+ vrf_config = {}
+
+ vrfs = re.findall(r"^vrf context (\S+)$", config, re.M)
+ for vrf in vrfs:
+ config_data = configobj.get_block_config(path=["vrf context %s" % vrf])
+ vrf_config[vrf] = config_data
+
+ return {
+ "hostname": parse_hostname(config),
+ "domain_lookup": "no ip domain-lookup" not in config,
+ "domain_name": parse_domain_name(config, vrf_config),
+ "domain_search": parse_domain_search(config, vrf_config),
+ "name_servers": parse_name_servers(config, vrf_config, vrfs),
+ "system_mtu": parse_system_mtu(config),
+ }
+
+
+def map_params_to_obj(module):
+ obj = {
+ "hostname": module.params["hostname"],
+ "domain_lookup": module.params["domain_lookup"],
+ "system_mtu": module.params["system_mtu"],
+ }
+
+ domain_name = ComplexList(dict(name=dict(key=True), vrf=dict()), module)
+
+ domain_search = ComplexList(dict(name=dict(key=True), vrf=dict()), module)
+
+ name_servers = ComplexList(dict(server=dict(key=True), vrf=dict()), module)
+
+ for arg, cast in [
+ ("domain_name", domain_name),
+ ("domain_search", domain_search),
+ ("name_servers", name_servers),
+ ]:
+ if module.params[arg] is not None:
+ obj[arg] = cast(module.params[arg])
+ else:
+ obj[arg] = None
+
+ return obj
+
+
+def main():
+ """main entry point for module execution"""
+ argument_spec = dict(
+ hostname=dict(),
+ domain_lookup=dict(type="bool"),
+ # { name: <str>, vrf: <str> }
+ domain_name=dict(type="list", elements="raw"),
+ # {name: <str>, vrf: <str> }
+ domain_search=dict(type="list", elements="raw"),
+ # { server: <str>; vrf: <str> }
+ name_servers=dict(type="list", elements="raw"),
+ system_mtu=dict(type="str"),
+ state=dict(default="present", choices=["present", "absent"]),
+ )
+
+ module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)
+
+ warnings = list()
+
+ result = {"changed": False}
+ if warnings:
+ result["warnings"] = warnings
+
+ want = map_params_to_obj(module)
+ have = map_config_to_obj(module)
+
+ commands = map_obj_to_commands(want, have, module)
+ result["commands"] = commands
+
+ if commands:
+ if not module.check_mode:
+ load_config(module, commands)
+ result["changed"] = True
+
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_telemetry.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_telemetry.py
new file mode 100644
index 00000000..7498ff88
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_telemetry.py
@@ -0,0 +1,339 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# Copyright 2019 Cisco and/or its affiliates.
+# 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 nxos_telemetry
+"""
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_telemetry
+short_description: TELEMETRY resource module
+description: Manages Telemetry Monitoring Service (TMS) configuration
+version_added: 1.0.0
+author: Mike Wiebe (@mikewiebe)
+notes:
+- Supported on N9k Version 7.0(3)I7(5) and later.
+- Unsupported for Cisco MDS
+options:
+ config:
+ description: The provided configuration
+ type: dict
+ suboptions:
+ certificate:
+ type: dict
+ description:
+ - Certificate SSL/TLS and hostname values.
+ - Value must be a dict defining values for keys (key and hostname).
+ suboptions:
+ key:
+ description:
+ - Certificate key
+ type: str
+ hostname:
+ description:
+ - Certificate hostname
+ type: str
+ compression:
+ type: str
+ description:
+ - Destination profile compression method.
+ choices:
+ - gzip
+ source_interface:
+ type: str
+ description:
+ - Destination profile source interface.
+ - Valid value is a str representing the source interface name.
+ vrf:
+ type: str
+ description:
+ - Destination profile vrf.
+ - Valid value is a str representing the vrf name.
+ destination_groups:
+ type: list
+ description:
+ - List of telemetry destination groups.
+ elements: raw
+ suboptions:
+ id:
+ type: str
+ description:
+ - Destination group identifier.
+ - Value must be an integer or string representing the destination group identifier.
+ destination:
+ type: dict
+ description:
+ - Group destination ipv4, port, protocol and encoding values.
+ - Value must be a dict defining values for keys (ip, port, protocol, encoding).
+ suboptions:
+ ip:
+ type: str
+ description:
+ - Destination group IP address.
+ port:
+ type: int
+ description:
+ - Destination group port number.
+ protocol:
+ type: str
+ description:
+ - Destination group protocol.
+ choices:
+ - HTTP
+ - TCP
+ - UDP
+ - gRPC
+ encoding:
+ type: str
+ description:
+ - Destination group encoding.
+ choices:
+ - GPB
+ - JSON
+ sensor_groups:
+ type: list
+ description:
+ - List of telemetry sensor groups.
+ elements: raw
+ suboptions:
+ id:
+ type: str
+ description:
+ - Sensor group identifier.
+ - Value must be a integer or a string representing the sensor group identifier.
+ data_source:
+ type: str
+ description:
+ - Telemetry data source.
+ choices:
+ - NX-API
+ - DME
+ - YANG
+ path:
+ type: dict
+ description:
+ - Telemetry sensor path.
+ - Value must be a dict defining values for keys (name, depth, filter_condition,
+ query_condition).
+ - Mandatory Keys (name)
+ - Optional Keys (depth, filter_condition, query_condition)
+ suboptions:
+ name:
+ type: str
+ description:
+ - Sensor group path name.
+ depth:
+ type: str
+ description:
+ - Sensor group depth.
+ filter_condition:
+ type: str
+ description:
+ - Sensor group filter condition.
+ query_condition:
+ type: str
+ description:
+ - Sensor group query condition.
+ subscriptions:
+ type: list
+ description:
+ - List of telemetry subscriptions.
+ elements: raw
+ suboptions:
+ id:
+ type: str
+ description:
+ - Subscription identifier.
+ - Value must be an integer or string representing the subscription identifier.
+ destination_group:
+ type: str
+ description:
+ - Associated destination group.
+ sensor_group:
+ type: dict
+ description:
+ - Associated sensor group.
+ - Value must be a dict defining values for keys (id, sample_interval).
+ suboptions:
+ id:
+ type: str
+ description:
+ - Associated sensor group id.
+ sample_interval:
+ type: int
+ description:
+ - Associated sensor group id sample interval.
+ state:
+ description:
+ - Final configuration state
+ type: str
+ choices:
+ - merged
+ - replaced
+ - deleted
+ - gathered
+ default: merged
+
+"""
+EXAMPLES = """
+# Using deleted
+# This action will delete all telemetry configuration on the device
+
+- name: Delete Telemetry Configuration
+ cisco.nxos.nxos_telemetry:
+ state: deleted
+
+
+# Using merged
+# This action will merge telemetry configuration defined in the playbook with
+# telemetry configuration that is already on the device.
+
+- name: Merge Telemetry Configuration
+ cisco.nxos.nxos_telemetry:
+ config:
+ certificate:
+ key: /bootflash/server.key
+ hostname: localhost
+ compression: gzip
+ source_interface: Ethernet1/1
+ vrf: management
+ destination_groups:
+ - id: 2
+ destination:
+ ip: 192.168.0.2
+ port: 50001
+ protocol: gRPC
+ encoding: GPB
+ - id: 55
+ destination:
+ ip: 192.168.0.55
+ port: 60001
+ protocol: gRPC
+ encoding: GPB
+ sensor_groups:
+ - id: 1
+ data_source: NX-API
+ path:
+ name: '"show lldp neighbors detail"'
+ depth: 0
+ - id: 55
+ data_source: DME
+ path:
+ name: sys/ch
+ depth: unbounded
+ filter_condition: ne(eqptFt.operSt,"ok")
+ subscriptions:
+ - id: 5
+ destination_group: 55
+ sensor_group:
+ id: 1
+ sample_interval: 1000
+ - id: 6
+ destination_group: 2
+ sensor_group:
+ id: 55
+ sample_interval: 2000
+ state: merged
+
+
+# Using replaced
+# This action will replace telemetry configuration on the device with the
+# telemetry configuration defined in the playbook.
+
+- name: Override Telemetry Configuration
+ cisco.nxos.nxos_telemetry:
+ config:
+ certificate:
+ key: /bootflash/server.key
+ hostname: localhost
+ compression: gzip
+ source_interface: Ethernet1/1
+ vrf: management
+ destination_groups:
+ - id: 2
+ destination:
+ ip: 192.168.0.2
+ port: 50001
+ protocol: gRPC
+ encoding: GPB
+ subscriptions:
+ - id: 5
+ destination_group: 55
+ state: replaced
+
+
+"""
+RETURN = """
+before:
+ description: The configuration as structured data prior to module invocation.
+ returned: always
+ type: dict
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+after:
+ description: The configuration as structured data after module completion.
+ returned: when changed
+ type: dict
+ 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: ['command 1', 'command 2', 'command 3']
+"""
+
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.telemetry.telemetry import (
+ TelemetryArgs,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.config.telemetry.telemetry import (
+ Telemetry,
+)
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ module = AnsibleModule(argument_spec=TelemetryArgs.argument_spec, supports_check_mode=True)
+
+ result = Telemetry(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_udld.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_udld.py
new file mode 100644
index 00000000..83955e15
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_udld.py
@@ -0,0 +1,254 @@
+#!/usr/bin/python
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_udld
+extends_documentation_fragment:
+- cisco.nxos.nxos
+short_description: Manages UDLD global configuration params.
+description:
+- Manages UDLD global configuration params.
+version_added: 1.0.0
+author:
+- Jason Edelman (@jedelman8)
+notes:
+- Tested against NXOSv 7.3.(0)D1(1) on VIRL
+- Unsupported for Cisco MDS
+- Module will fail if the udld feature has not been previously enabled.
+options:
+ aggressive:
+ description:
+ - Toggles aggressive mode.
+ choices:
+ - enabled
+ - disabled
+ type: str
+ msg_time:
+ description:
+ - Message time in seconds for UDLD packets or keyword 'default'.
+ type: str
+ reset:
+ description:
+ - Ability to reset all ports shut down by UDLD. 'state' parameter cannot be 'absent'
+ when this is present.
+ type: bool
+ state:
+ description:
+ - Manage the state of the resource. When set to 'absent', aggressive and msg_time
+ are set to their default values.
+ default: present
+ choices:
+ - present
+ - absent
+ type: str
+"""
+EXAMPLES = """
+# ensure udld aggressive mode is globally disabled and se global message interval is 20
+- cisco.nxos.nxos_udld:
+ aggressive: disabled
+ msg_time: 20
+ host: '{{ inventory_hostname }}'
+ username: '{{ un }}'
+ password: '{{ pwd }}'
+
+# Ensure agg mode is globally enabled and msg time is 15
+- cisco.nxos.nxos_udld:
+ aggressive: enabled
+ msg_time: 15
+ host: '{{ inventory_hostname }}'
+ username: '{{ un }}'
+ password: '{{ pwd }}'
+"""
+
+RETURN = """
+proposed:
+ description: k/v pairs of parameters passed into module
+ returned: always
+ type: dict
+ sample: {"aggressive": "enabled", "msg_time": "40"}
+existing:
+ description:
+ - k/v pairs of existing udld configuration
+ returned: always
+ type: dict
+ sample: {"aggressive": "disabled", "msg_time": "15"}
+end_state:
+ description: k/v pairs of udld configuration after module execution
+ returned: always
+ type: dict
+ sample: {"aggressive": "enabled", "msg_time": "40"}
+updates:
+ description: command sent to the device
+ returned: always
+ type: list
+ sample: ["udld message-time 40", "udld aggressive"]
+changed:
+ description: check to see if a change was made on the device
+ returned: always
+ type: bool
+ sample: true
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ load_config,
+ run_commands,
+)
+
+
+PARAM_TO_DEFAULT_KEYMAP = {"msg_time": "15"}
+
+
+def flatten_list(command_lists):
+ flat_command_list = []
+ for command in command_lists:
+ if isinstance(command, list):
+ flat_command_list.extend(command)
+ else:
+ flat_command_list.append(command)
+ return flat_command_list
+
+
+def apply_key_map(key_map, table):
+ new_dict = {}
+ for key, value in table.items():
+ new_key = key_map.get(key)
+ if new_key:
+ value = table.get(key)
+ if value:
+ new_dict[new_key] = str(value)
+ else:
+ new_dict[new_key] = value
+ return new_dict
+
+
+def get_commands_config_udld_global(delta, reset, existing):
+ commands = []
+ for param, value in delta.items():
+ if param == "aggressive":
+ command = "udld aggressive" if value == "enabled" else "no udld aggressive"
+ commands.append(command)
+ elif param == "msg_time":
+ if value == "default":
+ if existing.get("msg_time") != PARAM_TO_DEFAULT_KEYMAP.get("msg_time"):
+ commands.append("no udld message-time")
+ else:
+ commands.append("udld message-time " + value)
+ if reset:
+ command = "udld reset"
+ commands.append(command)
+ return commands
+
+
+def get_commands_remove_udld_global(existing):
+ commands = []
+ if existing.get("aggressive") == "enabled":
+ command = "no udld aggressive"
+ commands.append(command)
+ if existing.get("msg_time") != PARAM_TO_DEFAULT_KEYMAP.get("msg_time"):
+ command = "no udld message-time"
+ commands.append(command)
+ return commands
+
+
+def get_udld_global(module):
+ command = "show udld global | json"
+ udld_table = run_commands(module, [command])[0]
+
+ status = str(udld_table.get("udld-global-mode", None))
+ if status == "enabled-aggressive":
+ aggressive = "enabled"
+ else:
+ aggressive = "disabled"
+
+ interval = str(udld_table.get("message-interval", None))
+ udld = dict(msg_time=interval, aggressive=aggressive)
+
+ return udld
+
+
+def main():
+ argument_spec = dict(
+ aggressive=dict(required=False, choices=["enabled", "disabled"]),
+ msg_time=dict(required=False, type="str"),
+ reset=dict(required=False, type="bool"),
+ state=dict(choices=["absent", "present"], default="present"),
+ )
+
+ module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)
+
+ warnings = list()
+
+ aggressive = module.params["aggressive"]
+ msg_time = module.params["msg_time"]
+ reset = module.params["reset"]
+ state = module.params["state"]
+
+ if reset and state == "absent":
+ module.fail_json(msg="state must be present when using reset flag.")
+
+ args = dict(aggressive=aggressive, msg_time=msg_time, reset=reset)
+ proposed = dict((k, v) for k, v in args.items() if v is not None)
+
+ existing = get_udld_global(module)
+ end_state = existing
+
+ delta = set(proposed.items()).difference(existing.items())
+ changed = False
+
+ commands = []
+ if state == "present":
+ if delta:
+ command = get_commands_config_udld_global(dict(delta), reset, existing)
+ commands.append(command)
+
+ elif state == "absent":
+ command = get_commands_remove_udld_global(existing)
+ if command:
+ commands.append(command)
+
+ cmds = flatten_list(commands)
+ if cmds:
+ if module.check_mode:
+ module.exit_json(changed=True, commands=cmds)
+ else:
+ changed = True
+ load_config(module, cmds)
+ end_state = get_udld_global(module)
+ if "configure" in cmds:
+ cmds.pop(0)
+
+ results = {}
+ results["proposed"] = proposed
+ results["existing"] = existing
+ results["end_state"] = end_state
+ results["updates"] = cmds
+ results["changed"] = changed
+ results["warnings"] = warnings
+
+ module.exit_json(**results)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_udld_interface.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_udld_interface.py
new file mode 100644
index 00000000..b78ed5c7
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_udld_interface.py
@@ -0,0 +1,301 @@
+#!/usr/bin/python
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_udld_interface
+extends_documentation_fragment:
+- cisco.nxos.nxos
+short_description: Manages UDLD interface configuration params.
+description:
+- Manages UDLD interface configuration params.
+version_added: 1.0.0
+author:
+- Jason Edelman (@jedelman8)
+notes:
+- Tested against NXOSv 7.3.(0)D1(1) on VIRL
+- Unsupported for Cisco MDS
+- Feature UDLD must be enabled on the device to use this module.
+options:
+ mode:
+ description:
+ - Manages UDLD mode for an interface.
+ required: true
+ choices:
+ - enabled
+ - disabled
+ - aggressive
+ type: str
+ interface:
+ description:
+ - FULL name of the interface, i.e. Ethernet1/1-
+ required: true
+ type: str
+ state:
+ description:
+ - Manage the state of the resource.
+ required: false
+ default: present
+ choices:
+ - present
+ - absent
+ type: str
+"""
+EXAMPLES = """
+# ensure Ethernet1/1 is configured to be in aggressive mode
+- cisco.nxos.nxos_udld_interface:
+ interface: Ethernet1/1
+ mode: aggressive
+ state: present
+ host: '{{ inventory_hostname }}'
+ username: '{{ un }}'
+ password: '{{ pwd }}'
+
+# Remove the aggressive config only if it's currently in aggressive mode and then disable udld (switch default)
+- cisco.nxos.nxos_udld_interface:
+ interface: Ethernet1/1
+ mode: aggressive
+ state: absent
+ host: '{{ inventory_hostname }}'
+ username: '{{ un }}'
+ password: '{{ pwd }}'
+
+# ensure Ethernet1/1 has aggressive mode enabled
+- cisco.nxos.nxos_udld_interface:
+ interface: Ethernet1/1
+ mode: enabled
+ host: '{{ inventory_hostname }}'
+ username: '{{ un }}'
+ password: '{{ pwd }}'
+"""
+
+RETURN = """
+proposed:
+ description: k/v pairs of parameters passed into module
+ returned: always
+ type: dict
+ sample: {"mode": "enabled"}
+existing:
+ description:
+ - k/v pairs of existing configuration
+ returned: always
+ type: dict
+ sample: {"mode": "aggressive"}
+end_state:
+ description: k/v pairs of configuration after module execution
+ returned: always
+ type: dict
+ sample: {"mode": "enabled"}
+updates:
+ description: command sent to the device
+ returned: always
+ type: list
+ sample: ["interface ethernet1/33",
+ "no udld aggressive ; no udld disable"]
+changed:
+ description: check to see if a change was made on the device
+ returned: always
+ type: bool
+ sample: true
+"""
+
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ load_config,
+ run_commands,
+)
+
+
+def flatten_list(command_lists):
+ flat_command_list = []
+ for command in command_lists:
+ if isinstance(command, list):
+ flat_command_list.extend(command)
+ else:
+ flat_command_list.append(command)
+ return flat_command_list
+
+
+def get_udld_interface(module, interface):
+ command = "show run udld all | section " + interface.title() + "$"
+ interface_udld = {}
+ mode = None
+ mode_str = None
+ try:
+ body = run_commands(module, [{"command": command, "output": "text"}])[0]
+ if "aggressive" in body:
+ mode = "aggressive"
+ mode_str = "aggressive"
+ elif "no udld enable" in body:
+ mode = "disabled"
+ mode_str = "no udld enable"
+ elif "no udld disable" in body:
+ mode = "enabled"
+ mode_str = "no udld disable"
+ elif "udld disable" in body:
+ mode = "disabled"
+ mode_str = "udld disable"
+ elif "udld enable" in body:
+ mode = "enabled"
+ mode_str = "udld enable"
+ interface_udld["mode"] = mode
+
+ except (KeyError, AttributeError, IndexError):
+ interface_udld = {}
+
+ return interface_udld, mode_str
+
+
+def get_commands_config_udld_interface1(delta, interface, module, existing):
+ commands = []
+ mode = delta["mode"]
+ if mode == "aggressive":
+ commands.append("udld aggressive")
+ else:
+ commands.append("no udld aggressive")
+ commands.insert(0, "interface {0}".format(interface))
+
+ return commands
+
+
+def get_commands_config_udld_interface2(delta, interface, module, existing):
+ commands = []
+ existing, mode_str = get_udld_interface(module, interface)
+ mode = delta["mode"]
+ if mode == "enabled":
+ if mode_str == "no udld enable":
+ command = "udld enable"
+ else:
+ command = "no udld disable"
+ else:
+ if mode_str == "no udld disable":
+ command = "udld disable"
+ else:
+ command = "no udld enable"
+ if command:
+ commands.append(command)
+ commands.insert(0, "interface {0}".format(interface))
+
+ return commands
+
+
+def get_commands_remove_udld_interface(delta, interface, module, existing):
+ commands = []
+ existing, mode_str = get_udld_interface(module, interface)
+
+ mode = delta["mode"]
+ if mode == "aggressive":
+ command = "no udld aggressive"
+ else:
+ if mode == "enabled":
+ if mode_str == "udld enable":
+ command = "no udld enable"
+ else:
+ command = "udld disable"
+ elif mode == "disabled":
+ if mode_str == "no udld disable":
+ command = "udld disable"
+ else:
+ command = "no udld enable"
+ if command:
+ commands.append(command)
+ commands.insert(0, "interface {0}".format(interface))
+
+ return commands
+
+
+def main():
+ argument_spec = dict(
+ mode=dict(choices=["enabled", "disabled", "aggressive"], required=True),
+ interface=dict(type="str", required=True),
+ state=dict(choices=["absent", "present"], default="present"),
+ )
+
+ module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)
+
+ warnings = list()
+
+ interface = module.params["interface"].lower()
+ mode = module.params["mode"]
+ state = module.params["state"]
+
+ proposed = dict(mode=mode)
+ existing, mode_str = get_udld_interface(module, interface)
+ end_state = existing
+
+ delta = dict(set(proposed.items()).difference(existing.items()))
+
+ changed = False
+ commands = []
+ cmds = []
+ if state == "present":
+ if delta:
+ command = get_commands_config_udld_interface1(delta, interface, module, existing)
+ commands.append(command)
+ cmds = flatten_list(commands)
+ if module.check_mode:
+ module.exit_json(changed=True, commands=cmds)
+ else:
+ changed = True
+ load_config(module, cmds)
+
+ if delta["mode"] == "enabled" or delta["mode"] == "disabled":
+ commands = []
+ command = get_commands_config_udld_interface2(delta, interface, module, existing)
+ commands.append(command)
+ cmds = flatten_list(commands)
+ if module.check_mode:
+ module.exit_json(changed=True, commands=cmds)
+ else:
+ load_config(module, cmds)
+
+ else:
+ common = set(proposed.items()).intersection(existing.items())
+ if common:
+ command = get_commands_remove_udld_interface(dict(common), interface, module, existing)
+ cmds = flatten_list(commands)
+ if module.check_mode:
+ module.exit_json(changed=True, commands=cmds)
+ else:
+ changed = True
+ load_config(module, cmds)
+
+ if not module.check_mode:
+ end_state, mode_str = get_udld_interface(module, interface)
+ if "configure" in cmds:
+ cmds.pop(0)
+
+ results = {}
+ results["proposed"] = proposed
+ results["existing"] = existing
+ results["end_state"] = end_state
+ results["updates"] = cmds
+ results["changed"] = changed
+ results["warnings"] = warnings
+
+ module.exit_json(**results)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_user.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_user.py
new file mode 100644
index 00000000..83c22a76
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_user.py
@@ -0,0 +1,473 @@
+#!/usr/bin/python
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_user
+extends_documentation_fragment:
+- cisco.nxos.nxos
+author: Peter Sprygada (@privateip)
+notes:
+- Limited Support for Cisco MDS
+short_description: Manage the collection of local users on Nexus devices
+description:
+- This module provides declarative management of the local usernames configured on
+ Cisco Nexus devices. It allows playbooks to manage either individual usernames
+ or the collection of usernames in the current running config. It also supports
+ purging usernames from the configuration that are not explicitly defined.
+version_added: 1.0.0
+options:
+ aggregate:
+ description:
+ - The set of username objects to be configured on the remote Cisco Nexus device. The
+ list entries can either be the username or a hash of username and properties. This
+ argument is mutually exclusive with the C(name) argument.
+ aliases:
+ - users
+ - collection
+ type: list
+ elements: dict
+ suboptions:
+ name:
+ description:
+ - The username to be configured on the remote Cisco Nexus device. This argument
+ accepts a string value and is mutually exclusive with the C(aggregate) argument.
+ type: str
+ configured_password:
+ description:
+ - The password to be configured on the network device. The password needs to be
+ provided in cleartext and it will be encrypted on the device.
+ type: str
+ update_password:
+ description:
+ - Since passwords are encrypted in the device running config, this argument will
+ instruct the module when to change the password. When set to C(always), the
+ password will always be updated in the device and when set to C(on_create) the
+ password will be updated only if the username is created.
+ choices:
+ - on_create
+ - always
+ type: str
+ roles:
+ description:
+ - The C(role) argument configures the role for the username in the device running
+ configuration. The argument accepts a string value defining the role name. This
+ argument does not check if the role has been configured on the device.
+ aliases:
+ - role
+ type: list
+ elements: str
+ sshkey:
+ description:
+ - The C(sshkey) argument defines the SSH public key to configure for the username. This
+ argument accepts a valid SSH key value.
+ type: str
+ state:
+ description:
+ - The C(state) argument configures the state of the username definition as it
+ relates to the device operational configuration. When set to I(present), the
+ username(s) should be configured in the device active configuration and when
+ set to I(absent) the username(s) should not be in the device active configuration
+ choices:
+ - present
+ - absent
+ type: str
+ name:
+ description:
+ - The username to be configured on the remote Cisco Nexus device. This argument
+ accepts a string value and is mutually exclusive with the C(aggregate) argument.
+ type: str
+ configured_password:
+ description:
+ - The password to be configured on the network device. The password needs to be
+ provided in cleartext and it will be encrypted on the device.
+ type: str
+ update_password:
+ description:
+ - Since passwords are encrypted in the device running config, this argument will
+ instruct the module when to change the password. When set to C(always), the
+ password will always be updated in the device and when set to C(on_create) the
+ password will be updated only if the username is created.
+ default: always
+ choices:
+ - on_create
+ - always
+ type: str
+ roles:
+ description:
+ - The C(role) argument configures the role for the username in the device running
+ configuration. The argument accepts a string value defining the role name. This
+ argument does not check if the role has been configured on the device.
+ aliases:
+ - role
+ type: list
+ elements: str
+ sshkey:
+ description:
+ - The C(sshkey) argument defines the SSH public key to configure for the username. This
+ argument accepts a valid SSH key value.
+ type: str
+ purge:
+ description:
+ - The C(purge) argument instructs the module to consider the resource definition
+ absolute. It will remove any previously configured usernames on the device
+ with the exception of the `admin` user which cannot be deleted per nxos constraints.
+ type: bool
+ default: no
+ state:
+ description:
+ - The C(state) argument configures the state of the username definition as it
+ relates to the device operational configuration. When set to I(present), the
+ username(s) should be configured in the device active configuration and when
+ set to I(absent) the username(s) should not be in the device active configuration
+ default: present
+ choices:
+ - present
+ - absent
+ type: str
+"""
+
+EXAMPLES = """
+- name: create a new user
+ cisco.nxos.nxos_user:
+ name: ansible
+ sshkey: "{{ lookup('file', '~/.ssh/id_rsa.pub') }}"
+ state: present
+
+- name: remove all users except admin
+ cisco.nxos.nxos_user:
+ purge: yes
+
+- name: set multiple users role
+ cisco.nxos.nxos_user:
+ aggregate:
+ - name: netop
+ - name: netend
+ role: network-operator
+ state: present
+"""
+
+RETURN = """
+commands:
+ description: The list of configuration mode commands to send to the device
+ returned: always
+ type: list
+ sample:
+ - name ansible
+ - name ansible password password
+"""
+import re
+
+from copy import deepcopy
+from functools import partial
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils.six import iteritems
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import (
+ remove_default_spec,
+ to_list,
+)
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ get_config,
+ load_config,
+ run_commands,
+)
+
+
+BUILTIN_ROLES = [
+ "network-admin",
+ "network-operator",
+ "vdc-admin",
+ "vdc-operator",
+ "priv-15",
+ "priv-14",
+ "priv-13",
+ "priv-12",
+ "priv-11",
+ "priv-10",
+ "priv-9",
+ "priv-8",
+ "priv-7",
+ "priv-6",
+ "priv-5",
+ "priv-4",
+ "priv-3",
+ "priv-2",
+ "priv-1",
+ "priv-0",
+]
+
+
+def get_custom_roles(module):
+ return re.findall(
+ r"^role name (\S+)",
+ get_config(module, flags=["| include '^role name'"]),
+ re.M,
+ )
+
+
+def validate_roles(value, module):
+ valid_roles = BUILTIN_ROLES + get_custom_roles(module)
+ for item in value:
+ if item not in valid_roles:
+ module.fail_json(msg="invalid role specified")
+
+
+def map_obj_to_commands(updates, module):
+ commands = list()
+ update_password = module.params["update_password"]
+
+ for update in updates:
+ want, have = update
+
+ def needs_update(x):
+ return want.get(x) and (want.get(x) != have.get(x))
+
+ def add(x):
+ return commands.append("username %s %s" % (want["name"], x))
+
+ def remove(x):
+ return commands.append("no username %s %s" % (want["name"], x))
+
+ def configure_roles():
+ if want["roles"]:
+ if have:
+ for item in set(have["roles"]).difference(want["roles"]):
+ remove("role %s" % item)
+
+ for item in set(want["roles"]).difference(have["roles"]):
+ add("role %s" % item)
+ else:
+ for item in want["roles"]:
+ add("role %s" % item)
+
+ return True
+ return False
+
+ if want["state"] == "absent":
+ commands.append("no username %s" % want["name"])
+ continue
+
+ roles_configured = False
+ if want["state"] == "present" and not have:
+ roles_configured = configure_roles()
+ if not roles_configured:
+ commands.append("username %s" % want["name"])
+
+ if needs_update("configured_password"):
+ if update_password == "always" or not have:
+ add("password %s" % want["configured_password"])
+
+ if needs_update("sshkey"):
+ add("sshkey %s" % want["sshkey"])
+
+ if not roles_configured:
+ configure_roles()
+
+ return commands
+
+
+def parse_password(data):
+ if not data.get("remote_login"):
+ return "<PASSWORD>"
+
+
+def parse_roles(data):
+ configured_roles = None
+ if "TABLE_role" in data:
+ configured_roles = data.get("TABLE_role")["ROW_role"]
+
+ roles = list()
+ if configured_roles:
+ for item in to_list(configured_roles):
+ roles.append(item["role"])
+ return roles
+
+
+def map_config_to_obj(module):
+ out = run_commands(module, [{"command": "show user-account", "output": "json"}])
+ data = out[0]
+
+ objects = list()
+
+ for item in to_list(data["TABLE_template"]["ROW_template"]):
+ objects.append(
+ {
+ "name": item["usr_name"],
+ "configured_password": parse_password(item),
+ "sshkey": item.get("sshkey_info"),
+ "roles": parse_roles(item),
+ "state": "present",
+ },
+ )
+ return objects
+
+
+def get_param_value(key, item, module):
+ # if key doesn't exist in the item, get it from module.params
+ if not item.get(key):
+ value = module.params[key]
+
+ # if key does exist, do a type check on it to validate it
+ else:
+ value_type = module.argument_spec[key].get("type", "str")
+ type_checker = module._CHECK_ARGUMENT_TYPES_DISPATCHER[value_type]
+ type_checker(item[key])
+ value = item[key]
+
+ return value
+
+
+def map_params_to_obj(module):
+ aggregate = module.params["aggregate"]
+ if not aggregate:
+ if not module.params["name"] and module.params["purge"]:
+ return list()
+ elif not module.params["name"]:
+ module.fail_json(msg="username is required")
+ else:
+ collection = [{"name": module.params["name"]}]
+ else:
+ collection = list()
+ for item in aggregate:
+ if not isinstance(item, dict):
+ collection.append({"name": item})
+ elif "name" not in item:
+ module.fail_json(msg="name is required")
+ else:
+ collection.append(item)
+
+ objects = list()
+
+ for item in collection:
+ get_value = partial(get_param_value, item=item, module=module)
+ item.update(
+ {
+ "configured_password": get_value("configured_password"),
+ "sshkey": get_value("sshkey"),
+ "roles": get_value("roles"),
+ "state": get_value("state"),
+ },
+ )
+
+ for key, value in iteritems(item):
+ if value:
+ # validate the param value (if validator func exists)
+ validator = globals().get("validate_%s" % key)
+ if all((value, validator)):
+ validator(value, module)
+
+ objects.append(item)
+
+ return objects
+
+
+def update_objects(want, have):
+ updates = list()
+ for entry in want:
+ item = next((i for i in have if i["name"] == entry["name"]), None)
+ if all((item is None, entry["state"] == "present")):
+ updates.append((entry, {}))
+ elif item:
+ for key, value in iteritems(entry):
+ if value and value != item[key]:
+ updates.append((entry, item))
+ return updates
+
+
+def main():
+ """main entry point for module execution"""
+ element_spec = dict(
+ name=dict(),
+ configured_password=dict(no_log=True),
+ update_password=dict(default="always", choices=["on_create", "always"]),
+ roles=dict(type="list", aliases=["role"], elements="str"),
+ sshkey=dict(no_log=False),
+ state=dict(default="present", choices=["present", "absent"]),
+ )
+
+ aggregate_spec = deepcopy(element_spec)
+
+ # remove default in aggregate spec, to handle common arguments
+ remove_default_spec(aggregate_spec)
+
+ argument_spec = dict(
+ aggregate=dict(
+ type="list",
+ elements="dict",
+ options=aggregate_spec,
+ aliases=["collection", "users"],
+ ),
+ purge=dict(type="bool", default=False),
+ )
+
+ argument_spec.update(element_spec)
+
+ mutually_exclusive = [("name", "aggregate")]
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ mutually_exclusive=mutually_exclusive,
+ supports_check_mode=True,
+ )
+
+ result = {"changed": False, "warnings": []}
+
+ want = map_params_to_obj(module)
+ have = map_config_to_obj(module)
+
+ commands = map_obj_to_commands(update_objects(want, have), module)
+
+ if module.params["purge"]:
+ want_users = [x["name"] for x in want]
+ have_users = [x["name"] for x in have]
+ for item in set(have_users).difference(want_users):
+ if item != "admin":
+ item = item.replace("\\", "\\\\")
+ commands.append("no username %s" % item)
+
+ result["commands"] = commands
+
+ # the nxos cli prevents this by rule so capture it and display
+ # a nice failure message
+ if "no username admin" in commands:
+ module.fail_json(msg="cannot delete the `admin` account")
+
+ if commands:
+ if not module.check_mode:
+ responses = load_config(module, commands)
+ for resp in responses:
+ if resp.lower().startswith("wrong password"):
+ module.fail_json(msg=resp)
+ else:
+ result["warnings"].extend(
+ [x[9:] for x in resp.splitlines() if x.startswith("WARNING: ")],
+ )
+
+ result["changed"] = True
+
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_vlans.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_vlans.py
new file mode 100644
index 00000000..cac276b6
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_vlans.py
@@ -0,0 +1,439 @@
+#!/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 nxos_vlans
+"""
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_vlans
+short_description: VLANs resource module
+description: This module creates and manages VLAN configurations on Cisco NX-OS.
+version_added: 1.0.0
+author: Trishna Guha (@trishnaguha)
+notes:
+- Tested against NXOS 7.3.(0)D1(1) on VIRL
+- Unsupported for Cisco MDS
+options:
+ running_config:
+ description:
+ - This option is used only with state I(parsed).
+ - The value of this option should be the output received from the NX-OS device
+ by executing the commands B(show vlans | json-pretty) and B(show running-config
+ | section ^vlan) in order and delimited by a line.
+ - The state I(parsed) reads the configuration from C(running_config) option and
+ transforms it into Ansible structured data as per the resource module's argspec
+ and the value is then returned in the I(parsed) key within the result.
+ type: str
+ config:
+ description: A dictionary of Vlan options
+ type: list
+ elements: dict
+ suboptions:
+ vlan_id:
+ description:
+ - Vlan ID.
+ type: int
+ required: true
+ name:
+ description:
+ - Name of VLAN.
+ type: str
+ state:
+ description:
+ - Manage operational state of the vlan.
+ type: str
+ choices:
+ - active
+ - suspend
+ enabled:
+ description:
+ - Manage administrative state of the vlan.
+ type: bool
+ mode:
+ description:
+ - Set vlan mode to classical ethernet or fabricpath. This is a valid option
+ for Nexus 5000, 6000 and 7000 series.
+ type: str
+ choices:
+ - ce
+ - fabricpath
+ mapped_vni:
+ description:
+ - The Virtual Network Identifier (VNI) ID that is mapped to the VLAN.
+ type: int
+ state:
+ description:
+ - The state of the configuration after module completion.
+ - The state I(overridden) would override the configuration of all the
+ VLANs on the device (including VLAN 1) with the provided configuration in
+ the task. Use caution with this state.
+ type: str
+ choices:
+ - merged
+ - replaced
+ - overridden
+ - deleted
+ - gathered
+ - rendered
+ - parsed
+ default: merged
+
+"""
+EXAMPLES = """
+# Using merged
+
+# Before state:
+# -------------
+# vlan 1
+
+- name: Merge provided configuration with device configuration.
+ cisco.nxos.nxos_vlans:
+ config:
+ - vlan_id: 5
+ name: test-vlan5
+ - vlan_id: 10
+ enabled: false
+ state: merged
+
+# After state:
+# ------------
+# vlan 5
+# name test-vlan5
+# state active
+# no shutdown
+# vlan 10
+# state active
+# shutdown
+
+
+# Using replaced
+
+# Before state:
+# -------------
+# vlan 1
+# vlan 5
+# name test-vlan5
+# vlan 10
+# shutdown
+
+- name: Replace device configuration of specified vlan with provided configuration.
+ cisco.nxos.nxos_vlans:
+ config:
+ - vlan_id: 5
+ name: test-vlan
+ enabled: false
+ - vlan_id: 10
+ enabled: false
+ state: replaced
+
+# After state:
+# ------------
+# vlan 1
+# vlan 5
+# name test-vlan
+# state active
+# shutdown
+# vlan 10
+# state active
+# shutdown
+
+
+# Using overridden
+
+# Before state:
+# -------------
+# vlan 1
+# vlan 3
+# name testing
+# vlan 5
+# name test-vlan5
+# shutdown
+# vlan 10
+# shutdown
+
+- name: Override device configuration of all vlans with provided configuration.
+ cisco.nxos.nxos_vlans:
+ config:
+ - vlan_id: 5
+ name: test-vlan
+ - vlan_id: 10
+ state: active
+ state: overridden
+
+# After state:
+# ------------
+# vlan 5
+# name test-vlan
+# state active
+# no shutdown
+# vlan 10
+# state active
+# no shutdown
+
+
+# Using deleted
+
+# Before state:
+# -------------
+# vlan 1
+# vlan 5
+# vlan 10
+
+- name: Delete vlans.
+ cisco.nxos.nxos_vlans:
+ config:
+ - vlan_id: 5
+ - vlan_id: 10
+ state: deleted
+
+# After state:
+# ------------
+#
+
+# Using rendered
+
+- name: Use rendered state to convert task input to device specific commands
+ cisco.nxos.nxos_vlans:
+ config:
+ - vlan_id: 5
+ name: vlan5
+ mapped_vni: 100
+
+ - vlan_id: 6
+ name: vlan6
+ state: suspend
+ state: rendered
+
+# Task Output (redacted)
+# -----------------------
+
+# rendered:
+# - vlan 5
+# - name vlan5
+# - vn-segment 100
+# - vlan 6
+# - name vlan6
+# - state suspend
+
+# Using parsed
+
+# parsed.cfg
+# ------------
+# {
+# "TABLE_vlanbrief": {
+# "ROW_vlanbrief": [
+# {
+# "vlanshowbr-vlanid": "1",
+# "vlanshowbr-vlanid-utf": "1",
+# "vlanshowbr-vlanname": "default",
+# "vlanshowbr-vlanstate": "active",
+# "vlanshowbr-shutstate": "noshutdown"
+# },
+# {
+# "vlanshowbr-vlanid": "5",
+# "vlanshowbr-vlanid-utf": "5",
+# "vlanshowbr-vlanname": "vlan5",
+# "vlanshowbr-vlanstate": "suspend",
+# "vlanshowbr-shutstate": "noshutdown"
+# },
+# {
+# "vlanshowbr-vlanid": "6",
+# "vlanshowbr-vlanid-utf": "6",
+# "vlanshowbr-vlanname": "VLAN0006",
+# "vlanshowbr-vlanstate": "active",
+# "vlanshowbr-shutstate": "noshutdown"
+# },
+# {
+# "vlanshowbr-vlanid": "7",
+# "vlanshowbr-vlanid-utf": "7",
+# "vlanshowbr-vlanname": "vlan7",
+# "vlanshowbr-vlanstate": "active",
+# "vlanshowbr-shutstate": "noshutdown"
+# }
+# ]
+# },
+# "TABLE_mtuinfo": {
+# "ROW_mtuinfo": [
+# {
+# "vlanshowinfo-vlanid": "1",
+# "vlanshowinfo-media-type": "enet",
+# "vlanshowinfo-vlanmode": "ce-vlan"
+# },
+# {
+# "vlanshowinfo-vlanid": "5",
+# "vlanshowinfo-media-type": "enet",
+# "vlanshowinfo-vlanmode": "ce-vlan"
+# },
+# {
+# "vlanshowinfo-vlanid": "6",
+# "vlanshowinfo-media-type": "enet",
+# "vlanshowinfo-vlanmode": "ce-vlan"
+# },
+# {
+# "vlanshowinfo-vlanid": "7",
+# "vlanshowinfo-media-type": "enet",
+# "vlanshowinfo-vlanmode": "ce-vlan"
+# }
+# ]
+# }
+# }
+#
+# vlan 1,5-7
+# vlan 5
+# state suspend
+# name vlan5
+# vlan 7
+# name vlan7
+# vn-segment 100
+
+- name: Use parsed state to convert externally supplied config to structured format
+ cisco.nxos.nxos_vlans:
+ running_config: "{{ lookup('file', 'parsed.cfg') }}"
+ state: parsed
+
+# Task output (redacted)
+# -----------------------
+
+# parsed:
+# - vlan_id: 5
+# enabled: True
+# mode: "ce"
+# name: "vlan5"
+# state: suspend
+#
+# - vlan_id: 6
+# enabled: True
+# mode: "ce"
+# state: active
+#
+# - vlan_id: 7
+# enabled: True
+# mode: "ce"
+# name: "vlan7"
+# state: active
+# mapped_vni: 100
+
+# Using gathered
+
+# Existing device config state
+# -------------------------------
+# nxos-9k# show vlan | json
+# {"TABLE_vlanbrief": {"ROW_vlanbrief": [{"vlanshowbr-vlanid": "1", "vlanshowbr-vlanid-utf": "1", "vlanshowbr-vlanname": "default", "vlanshowbr-vlanstate
+# ": "active", "vlanshowbr-shutstate": "noshutdown"}, {"vlanshowbr-vlanid": "5", "vlanshowbr-vlanid-utf": "5", "vlanshowbr-vlanname": "vlan5", "vlanshowb
+# r-vlanstate": "suspend", "vlanshowbr-shutstate": "noshutdown"}, {"vlanshowbr-vlanid": "6", "vlanshowbr-vlanid-utf": "6", "vlanshowbr-vlanname": "VLAN00
+# 06", "vlanshowbr-vlanstate": "active", "vlanshowbr-shutstate": "noshutdown"}, {"vlanshowbr-vlanid": "7", "vlanshowbr-vlanid-utf": "7", "vlanshowbr-vlan
+# name": "vlan7", "vlanshowbr-vlanstate": "active", "vlanshowbr-shutstate": "shutdown"}]}, "TABLE_mtuinfo": {"ROW_mtuinfo": [{"vlanshowinfo-vlanid": "1",
+# "vlanshowinfo-media-type": "enet", "vlanshowinfo-vlanmode": "ce-vlan"}, {"vlanshowinfo-vlanid": "5", "vlanshowinfo-media-type": "enet", "vlanshowinfo-
+# vlanmode": "ce-vlan"}, {"vlanshowinfo-vlanid": "6", "vlanshowinfo-media-type": "enet", "vlanshowinfo-vlanmode": "ce-vlan"}, {"vlanshowinfo-vlanid": "7"
+# , "vlanshowinfo-media-type": "enet", "vlanshowinfo-vlanmode": "ce-vlan"}]}}
+#
+# nxos-9k# show running-config | section ^vlan
+# vlan 1,5-7
+# vlan 5
+# state suspend
+# name vlan5
+# vlan 7
+# shutdown
+# name vlan7
+# vn-segment 190
+
+- name: Gather vlans facts from the device using nxos_vlans
+ cisco.nxos.nxos_vlans:
+ state: gathered
+
+# Task output (redacted)
+# -----------------------
+# gathered:
+# - vlan_id: 5
+# enabled: True
+# mode: "ce"
+# name: "vlan5"
+# state: suspend
+#
+# - vlan_id: 6
+# enabled: True
+# mode: "ce"
+# state: active
+#
+# - vlan_id: 7
+# enabled: False
+# mode: "ce"
+# name: "vlan7"
+# state: active
+# mapped_vni: 190
+"""
+RETURN = """
+before:
+ description: The configuration as structured data prior to module invocation.
+ returned: always
+ type: list
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+after:
+ description: The configuration as structured data after module completion.
+ returned: when changed
+ type: list
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+commands:
+ description: The set of commands pushed to the remote device.
+ returned: always
+ type: list
+ sample: ['vlan 5', 'name test-vlan5', 'state suspend']
+"""
+
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.vlans.vlans import (
+ VlansArgs,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.config.vlans.vlans import (
+ Vlans,
+)
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ module = AnsibleModule(argument_spec=VlansArgs.argument_spec, supports_check_mode=True)
+
+ result = Vlans(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_vpc.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_vpc.py
new file mode 100644
index 00000000..f660efc1
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_vpc.py
@@ -0,0 +1,470 @@
+#!/usr/bin/python
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_vpc
+extends_documentation_fragment:
+- cisco.nxos.nxos
+short_description: Manages global VPC configuration
+description:
+- Manages global VPC configuration
+version_added: 1.0.0
+author:
+- Jason Edelman (@jedelman8)
+- Gabriele Gerbino (@GGabriele)
+notes:
+- Tested against NXOSv 7.3.(0)D1(1) on VIRL
+- Unsupported for Cisco MDS
+- The feature vpc must be enabled before this module can be used
+- If not using management vrf, vrf must be globally on the device before using in
+ the pkl config
+- Although source IP isn't required on the command line it is required when using
+ this module. The PKL VRF must also be configured prior to using this module.
+- Both pkl_src and pkl_dest are needed when changing PKL VRF.
+options:
+ domain:
+ description:
+ - VPC domain
+ required: true
+ type: str
+ role_priority:
+ description:
+ - Role priority for device. Remember lower is better.
+ type: str
+ system_priority:
+ description:
+ - System priority device. Remember they must match between peers.
+ type: str
+ pkl_src:
+ description:
+ - Source IP address used for peer keepalive link
+ type: str
+ pkl_dest:
+ description:
+ - Destination (remote) IP address used for peer keepalive link
+ - pkl_dest is required whenever pkl options are used.
+ type: str
+ pkl_vrf:
+ description:
+ - VRF used for peer keepalive link
+ - The VRF must exist on the device before using pkl_vrf.
+ - "(Note) 'default' is an overloaded term: Default vrf context for pkl_vrf is
+ 'management'; 'pkl_vrf: default' refers to the literal 'default' rib."
+ type: str
+ peer_gw:
+ description:
+ - Enables/Disables peer gateway
+ type: bool
+ auto_recovery:
+ description:
+ - Enables/Disables auto recovery on platforms that support disable
+ - timers are not modifiable with this attribute
+ - mutually exclusive with auto_recovery_reload_delay
+ type: bool
+ auto_recovery_reload_delay:
+ description:
+ - Manages auto-recovery reload-delay timer in seconds
+ - mutually exclusive with auto_recovery
+ type: str
+ delay_restore:
+ description:
+ - manages delay restore command and config value in seconds
+ type: str
+ delay_restore_interface_vlan:
+ description:
+ - manages delay restore interface-vlan command and config value in seconds
+ - not supported on all platforms
+ type: str
+ delay_restore_orphan_port:
+ description:
+ - manages delay restore orphan-port command and config value in seconds
+ - not supported on all platforms
+ type: str
+ state:
+ description:
+ - Manages desired state of the resource
+ default: present
+ choices:
+ - present
+ - absent
+ type: str
+"""
+
+EXAMPLES = """
+- name: configure a simple asn
+ cisco.nxos.nxos_vpc:
+ domain: 100
+ role_priority: 1000
+ system_priority: 2000
+ pkl_dest: 192.168.100.4
+ pkl_src: 10.1.100.20
+ peer_gw: true
+ auto_recovery: true
+
+- name: configure
+ cisco.nxos.nxos_vpc:
+ domain: 100
+ role_priority: 32667
+ system_priority: 2000
+ peer_gw: true
+ pkl_src: 10.1.100.2
+ pkl_dest: 192.168.100.4
+ auto_recovery: true
+
+- name: Configure VPC with delay restore and existing keepalive VRF
+ cisco.nxos.nxos_vpc:
+ domain: 10
+ role_priority: 28672
+ system_priority: 2000
+ delay_restore: 180
+ peer_gw: true
+ pkl_src: 1.1.1.2
+ pkl_dest: 1.1.1.1
+ pkl_vrf: vpckeepalive
+ auto_recovery: true
+"""
+
+RETURN = """
+commands:
+ description: commands sent to the device
+ returned: always
+ type: list
+ sample: ["vpc domain 100",
+ "peer-keepalive destination 192.168.100.4 source 10.1.100.20 vrf management",
+ "auto-recovery", "peer-gateway"]
+"""
+
+import re
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ get_config,
+ load_config,
+ run_commands,
+)
+
+
+CONFIG_ARGS = {
+ "role_priority": "role priority {role_priority}",
+ "system_priority": "system-priority {system_priority}",
+ "delay_restore": "delay restore {delay_restore}",
+ "delay_restore_interface_vlan": "delay restore interface-vlan {delay_restore_interface_vlan}",
+ "delay_restore_orphan_port": "delay restore orphan-port {delay_restore_orphan_port}",
+ "peer_gw": "{peer_gw} peer-gateway",
+ "auto_recovery": "{auto_recovery} auto-recovery",
+ "auto_recovery_reload_delay": "auto-recovery reload-delay {auto_recovery_reload_delay}",
+}
+
+PARAM_TO_DEFAULT_KEYMAP = {
+ "delay_restore": "60",
+ "delay_restore_interface_vlan": "10",
+ "delay_restore_orphan_port": "0",
+ "role_priority": "32667",
+ "system_priority": "32667",
+ "peer_gw": False,
+ "auto_recovery_reload_delay": 240,
+}
+
+
+def flatten_list(command_lists):
+ flat_command_list = []
+ for command in command_lists:
+ if isinstance(command, list):
+ flat_command_list.extend(command)
+ else:
+ flat_command_list.append(command)
+ return flat_command_list
+
+
+def get_vrf_list(module):
+ try:
+ body = run_commands(module, ["show vrf all | json"])[0]
+ vrf_table = body["TABLE_vrf"]["ROW_vrf"]
+ except (KeyError, AttributeError):
+ return []
+
+ vrf_list = []
+ if vrf_table:
+ for each in vrf_table:
+ vrf_list.append(str(each["vrf_name"].lower()))
+
+ return vrf_list
+
+
+def get_auto_recovery_default(module):
+ auto = False
+ data = run_commands(module, ["show inventory | json"])[0]
+ pid = data["TABLE_inv"]["ROW_inv"][0]["productid"]
+ if re.search(r"N7K", pid):
+ auto = True
+ elif re.search(r"N9K", pid):
+ data = run_commands(module, ["show hardware | json"])[0]
+ ver = data["kickstart_ver_str"]
+ if re.search(r"7.0\(3\)F", ver):
+ auto = True
+
+ return auto
+
+
+def get_vpc(module):
+ body = run_commands(module, ["show vpc | json"])[0]
+ if body:
+ domain = str(body["vpc-domain-id"])
+ else:
+ body = run_commands(module, ["show run vpc | inc domain"])[0]
+ if body:
+ domain = body.split()[2]
+ else:
+ domain = "not configured"
+
+ vpc = {}
+ if domain != "not configured":
+ run = get_config(module, flags=["vpc all"])
+ if run:
+ vpc["domain"] = domain
+ for key in PARAM_TO_DEFAULT_KEYMAP.keys():
+ vpc[key] = PARAM_TO_DEFAULT_KEYMAP.get(key)
+ vpc["auto_recovery"] = get_auto_recovery_default(module)
+ vpc_list = run.split("\n")
+ for each in vpc_list:
+ if "role priority" in each:
+ line = each.split()
+ vpc["role_priority"] = line[-1]
+ if "system-priority" in each:
+ line = each.split()
+ vpc["system_priority"] = line[-1]
+ if re.search(r"delay restore \d+", each):
+ line = each.split()
+ vpc["delay_restore"] = line[-1]
+ if "delay restore interface-vlan" in each:
+ line = each.split()
+ vpc["delay_restore_interface_vlan"] = line[-1]
+ if "delay restore orphan-port" in each:
+ line = each.split()
+ vpc["delay_restore_orphan_port"] = line[-1]
+ if "auto-recovery" in each:
+ vpc["auto_recovery"] = False if "no " in each else True
+ line = each.split()
+ vpc["auto_recovery_reload_delay"] = line[-1]
+ if "peer-gateway" in each:
+ vpc["peer_gw"] = False if "no " in each else True
+ if "peer-keepalive destination" in each:
+ # destination is reqd; src & vrf are optional
+ m = re.search(
+ r"destination (?P<pkl_dest>[\d.]+)"
+ r"(?:.* source (?P<pkl_src>[\d.]+))*"
+ r"(?:.* vrf (?P<pkl_vrf>\S+))*",
+ each,
+ )
+ if m:
+ for pkl in m.groupdict().keys():
+ if m.group(pkl):
+ vpc[pkl] = m.group(pkl)
+ return vpc
+
+
+def pkl_dependencies(module, delta, existing):
+ """peer-keepalive dependency checking.
+ 1. 'destination' is required with all pkl configs.
+ 2. If delta has optional pkl keywords present, then all optional pkl
+ keywords in existing must be added to delta, otherwise the device cli
+ will remove those values when the new config string is issued.
+ 3. The desired behavior for this set of properties is to merge changes;
+ therefore if an optional pkl property exists on the device but not
+ in the playbook, then that existing property should be retained.
+ Example:
+ CLI: peer-keepalive dest 10.1.1.1 source 10.1.1.2 vrf orange
+ Playbook: {pkl_dest: 10.1.1.1, pkl_vrf: blue}
+ Result: peer-keepalive dest 10.1.1.1 source 10.1.1.2 vrf blue
+ """
+ pkl_existing = [i for i in existing.keys() if i.startswith("pkl")]
+ for pkl in pkl_existing:
+ param = module.params.get(pkl)
+ if not delta.get(pkl):
+ if param and param == existing[pkl]:
+ # delta is missing this param because it's idempotent;
+ # however another pkl command has changed; therefore
+ # explicitly add it to delta so that the cli retains it.
+ delta[pkl] = existing[pkl]
+ elif param is None and existing[pkl]:
+ # retain existing pkl commands even if not in playbook
+ delta[pkl] = existing[pkl]
+
+
+def get_commands_to_config_vpc(module, vpc, domain, existing):
+ vpc = dict(vpc)
+
+ domain_only = vpc.get("domain")
+
+ commands = []
+ if "pkl_dest" in vpc:
+ pkl_command = "peer-keepalive destination {pkl_dest}".format(**vpc)
+ if "pkl_src" in vpc:
+ pkl_command += " source {pkl_src}".format(**vpc)
+ if "pkl_vrf" in vpc:
+ pkl_command += " vrf {pkl_vrf}".format(**vpc)
+ commands.append(pkl_command)
+
+ if "auto_recovery" in vpc:
+ if not vpc.get("auto_recovery"):
+ vpc["auto_recovery"] = "no"
+ else:
+ vpc["auto_recovery"] = ""
+
+ if "peer_gw" in vpc:
+ if not vpc.get("peer_gw"):
+ vpc["peer_gw"] = "no"
+ else:
+ vpc["peer_gw"] = ""
+
+ for param in vpc:
+ command = CONFIG_ARGS.get(param)
+ if command is not None:
+ command = command.format(**vpc).strip()
+ if "peer-gateway" in command:
+ commands.append("terminal dont-ask")
+ commands.append(command)
+
+ if commands or domain_only:
+ commands.insert(0, "vpc domain {0}".format(domain))
+ return commands
+
+
+def main():
+ argument_spec = dict(
+ domain=dict(required=True, type="str"),
+ role_priority=dict(required=False, type="str"),
+ system_priority=dict(required=False, type="str"),
+ pkl_src=dict(required=False),
+ pkl_dest=dict(required=False),
+ pkl_vrf=dict(required=False),
+ peer_gw=dict(required=False, type="bool"),
+ auto_recovery=dict(required=False, type="bool"),
+ auto_recovery_reload_delay=dict(required=False, type="str"),
+ delay_restore=dict(required=False, type="str"),
+ delay_restore_interface_vlan=dict(required=False, type="str"),
+ delay_restore_orphan_port=dict(required=False, type="str"),
+ state=dict(choices=["absent", "present"], default="present"),
+ )
+
+ mutually_exclusive = [("auto_recovery", "auto_recovery_reload_delay")]
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ mutually_exclusive=mutually_exclusive,
+ supports_check_mode=True,
+ )
+
+ warnings = list()
+ results = {"changed": False, "warnings": warnings}
+
+ domain = module.params["domain"]
+ role_priority = module.params["role_priority"]
+ system_priority = module.params["system_priority"]
+ pkl_src = module.params["pkl_src"]
+ pkl_dest = module.params["pkl_dest"]
+ pkl_vrf = module.params["pkl_vrf"]
+ peer_gw = module.params["peer_gw"]
+ auto_recovery = module.params["auto_recovery"]
+ auto_recovery_reload_delay = module.params["auto_recovery_reload_delay"]
+ delay_restore = module.params["delay_restore"]
+ delay_restore_interface_vlan = module.params["delay_restore_interface_vlan"]
+ delay_restore_orphan_port = module.params["delay_restore_orphan_port"]
+ state = module.params["state"]
+
+ args = dict(
+ domain=domain,
+ role_priority=role_priority,
+ system_priority=system_priority,
+ pkl_src=pkl_src,
+ pkl_dest=pkl_dest,
+ pkl_vrf=pkl_vrf,
+ peer_gw=peer_gw,
+ auto_recovery=auto_recovery,
+ auto_recovery_reload_delay=auto_recovery_reload_delay,
+ delay_restore=delay_restore,
+ delay_restore_interface_vlan=delay_restore_interface_vlan,
+ delay_restore_orphan_port=delay_restore_orphan_port,
+ )
+
+ if not pkl_dest:
+ if pkl_src:
+ module.fail_json(msg="dest IP for peer-keepalive is required" " when src IP is present")
+ elif pkl_vrf:
+ if pkl_vrf != "management":
+ module.fail_json(
+ msg="dest and src IP for peer-keepalive are required" " when vrf is present",
+ )
+ else:
+ module.fail_json(
+ msg="dest IP for peer-keepalive is required" " when vrf is present",
+ )
+ if pkl_vrf:
+ if pkl_vrf.lower() not in get_vrf_list(module):
+ module.fail_json(
+ msg="The VRF you are trying to use for the peer "
+ "keepalive link is not on device yet. Add it"
+ " first, please.",
+ )
+ proposed = dict((k, v) for k, v in args.items() if v is not None)
+ existing = get_vpc(module)
+
+ commands = []
+ if state == "present":
+ delta = {}
+ for key, value in proposed.items():
+ if str(value).lower() == "default" and key != "pkl_vrf":
+ # 'default' is a reserved word for vrf
+ value = PARAM_TO_DEFAULT_KEYMAP.get(key)
+ if existing.get(key) != value:
+ delta[key] = value
+
+ if delta:
+ pkl_dependencies(module, delta, existing)
+ command = get_commands_to_config_vpc(module, delta, domain, existing)
+ commands.append(command)
+ elif state == "absent":
+ if existing:
+ if domain != existing["domain"]:
+ module.fail_json(
+ msg="You are trying to remove a domain that " "does not exist on the device",
+ )
+ else:
+ commands.append("terminal dont-ask")
+ commands.append("no vpc domain {0}".format(domain))
+
+ cmds = flatten_list(commands)
+ results["commands"] = cmds
+
+ if cmds:
+ results["changed"] = True
+ if not module.check_mode:
+ load_config(module, cmds)
+ if "configure" in cmds:
+ cmds.pop(0)
+
+ module.exit_json(**results)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_vpc_interface.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_vpc_interface.py
new file mode 100644
index 00000000..5e318457
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_vpc_interface.py
@@ -0,0 +1,342 @@
+#!/usr/bin/python
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_vpc_interface
+extends_documentation_fragment:
+- cisco.nxos.nxos
+short_description: Manages interface VPC configuration
+description:
+- Manages interface VPC configuration
+version_added: 1.0.0
+author:
+- Jason Edelman (@jedelman8)
+- Gabriele Gerbino (@GGabriele)
+notes:
+- Tested against NXOSv 7.3.(0)D1(1) on VIRL
+- Unsupported for Cisco MDS
+- Either vpc or peer_link param is required, but not both.
+- C(state=absent) removes whatever VPC config is on a port-channel if one exists.
+- Re-assigning a vpc or peerlink from one portchannel to another is not supported. The
+ module will force the user to unconfigure an existing vpc/pl before configuring
+ the same value on a new portchannel
+options:
+ portchannel:
+ description:
+ - Group number of the portchannel that will be configured.
+ required: true
+ type: str
+ vpc:
+ description:
+ - VPC group/id that will be configured on associated portchannel.
+ type: str
+ peer_link:
+ description:
+ - Set to true/false for peer link config on associated portchannel.
+ type: bool
+ state:
+ description:
+ - Manages desired state of the resource.
+ choices:
+ - present
+ - absent
+ default: present
+ type: str
+"""
+
+EXAMPLES = """
+- cisco.nxos.nxos_vpc_interface:
+ portchannel: 10
+ vpc: 100
+"""
+
+RETURN = """
+commands:
+ description: commands sent to the device
+ returned: always
+ type: list
+ sample: ["interface port-channel100", "vpc 10"]
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ get_config,
+ load_config,
+ run_commands,
+)
+
+
+def flatten_list(command_lists):
+ flat_command_list = []
+ for command in command_lists:
+ if isinstance(command, list):
+ flat_command_list.extend(command)
+ else:
+ flat_command_list.append(command)
+ return flat_command_list
+
+
+def get_portchannel_list(module):
+ portchannels = []
+ pc_list = []
+
+ try:
+ body = run_commands(module, ["show port-channel summary | json"])[0]
+ pc_list = body["TABLE_channel"]["ROW_channel"]
+ except (KeyError, AttributeError, TypeError):
+ return portchannels
+
+ if pc_list:
+ if isinstance(pc_list, dict):
+ pc_list = [pc_list]
+
+ for pc in pc_list:
+ portchannels.append(pc["group"])
+
+ return portchannels
+
+
+def get_existing_portchannel_to_vpc_mappings(module):
+ pc_vpc_mapping = {}
+
+ try:
+ body = run_commands(module, ["show vpc brief | json"])[0]
+ vpc_table = body["TABLE_vpc"]["ROW_vpc"]
+ except (KeyError, AttributeError, TypeError):
+ vpc_table = None
+
+ if vpc_table:
+ if isinstance(vpc_table, dict):
+ vpc_table = [vpc_table]
+
+ for vpc in vpc_table:
+ pc_vpc_mapping[str(vpc["vpc-id"])] = str(vpc["vpc-ifindex"])
+
+ return pc_vpc_mapping
+
+
+def peer_link_exists(module):
+ found = False
+ run = get_config(module, flags=["vpc"])
+
+ vpc_list = run.split("\n")
+ for each in vpc_list:
+ if "peer-link" in each:
+ found = True
+ return found
+
+
+def get_active_vpc_peer_link(module):
+ peer_link = None
+
+ try:
+ body = run_commands(module, ["show vpc brief | json"])[0]
+ peer_link = body["TABLE_peerlink"]["ROW_peerlink"]["peerlink-ifindex"]
+ except (KeyError, AttributeError, TypeError):
+ return peer_link
+
+ return peer_link
+
+
+def get_portchannel_vpc_config(module, portchannel):
+ peer_link_pc = None
+ peer_link = False
+ vpc = ""
+ pc = ""
+ config = {}
+
+ try:
+ body = run_commands(module, ["show vpc brief | json"])[0]
+ table = body["TABLE_peerlink"]["ROW_peerlink"]
+ except (KeyError, AttributeError, TypeError):
+ table = {}
+
+ if table:
+ peer_link_pc = table.get("peerlink-ifindex", None)
+
+ if peer_link_pc:
+ plpc = str(peer_link_pc[2:])
+ if portchannel == plpc:
+ config["portchannel"] = portchannel
+ config["peer-link"] = True
+ config["vpc"] = vpc
+
+ mapping = get_existing_portchannel_to_vpc_mappings(module)
+
+ for existing_vpc, port_channel in mapping.items():
+ port_ch = str(port_channel[2:])
+ if port_ch == portchannel:
+ pc = port_ch
+ vpc = str(existing_vpc)
+
+ config["portchannel"] = pc
+ config["peer-link"] = peer_link
+ config["vpc"] = vpc
+
+ return config
+
+
+def get_commands_to_config_vpc_interface(portchannel, delta, config_value, existing):
+ commands = []
+
+ if not delta.get("peer-link") and existing.get("peer-link"):
+ commands.append("no vpc peer-link")
+ commands.insert(0, "interface port-channel{0}".format(portchannel))
+
+ elif delta.get("peer-link") and not existing.get("peer-link"):
+ commands.append("vpc peer-link")
+ commands.insert(0, "interface port-channel{0}".format(portchannel))
+
+ elif delta.get("vpc") and not existing.get("vpc"):
+ command = "vpc {0}".format(config_value)
+ commands.append(command)
+ commands.insert(0, "interface port-channel{0}".format(portchannel))
+
+ return commands
+
+
+def state_present(portchannel, delta, config_value, existing):
+ commands = []
+
+ command = get_commands_to_config_vpc_interface(portchannel, delta, config_value, existing)
+ commands.append(command)
+
+ return commands
+
+
+def state_absent(portchannel, existing):
+ commands = []
+ if existing.get("vpc"):
+ command = "no vpc"
+ commands.append(command)
+ elif existing.get("peer-link"):
+ command = "no vpc peer-link"
+ commands.append(command)
+ if commands:
+ commands.insert(0, "interface port-channel{0}".format(portchannel))
+
+ return commands
+
+
+def main():
+ argument_spec = dict(
+ portchannel=dict(required=True, type="str"),
+ vpc=dict(required=False, type="str"),
+ peer_link=dict(required=False, type="bool"),
+ state=dict(choices=["absent", "present"], default="present"),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ mutually_exclusive=[["vpc", "peer_link"]],
+ supports_check_mode=True,
+ )
+
+ warnings = list()
+ commands = []
+ results = {"changed": False, "warnings": warnings}
+
+ portchannel = module.params["portchannel"]
+ vpc = module.params["vpc"]
+ peer_link = module.params["peer_link"]
+ state = module.params["state"]
+
+ args = {"portchannel": portchannel, "vpc": vpc, "peer-link": peer_link}
+ active_peer_link = None
+
+ if portchannel not in get_portchannel_list(module):
+ if not portchannel.isdigit() or int(portchannel) not in get_portchannel_list(module):
+ module.fail_json(
+ msg="The portchannel you are trying to make a"
+ " VPC or PL is not created yet. "
+ "Create it first!",
+ )
+ if vpc:
+ mapping = get_existing_portchannel_to_vpc_mappings(module)
+
+ if vpc in mapping and portchannel != mapping[vpc].strip("Po"):
+ module.fail_json(
+ msg="This vpc is already configured on "
+ "another portchannel. Remove it first "
+ "before trying to assign it here. ",
+ existing_portchannel=mapping[vpc],
+ )
+
+ for vpcid, existing_pc in mapping.items():
+ if portchannel == existing_pc.strip("Po") and vpcid != vpc:
+ module.fail_json(
+ msg="This portchannel already has another"
+ " VPC configured. Remove it first "
+ "before assigning this one",
+ existing_vpc=vpcid,
+ )
+
+ if peer_link_exists(module):
+ active_peer_link = get_active_vpc_peer_link(module)
+ if active_peer_link[-2:] == portchannel:
+ module.fail_json(
+ msg="That port channel is the current "
+ "PEER LINK. Remove it if you want it"
+ " to be a VPC",
+ )
+ config_value = vpc
+
+ elif peer_link is not None:
+ if peer_link_exists(module):
+ active_peer_link = get_active_vpc_peer_link(module)[2::]
+ if active_peer_link != portchannel:
+ if peer_link:
+ module.fail_json(
+ msg="A peer link already exists on" " the device. Remove it first",
+ current_peer_link="Po{0}".format(active_peer_link),
+ )
+ config_value = "peer-link"
+
+ proposed = dict((k, v) for k, v in args.items() if v is not None)
+ existing = get_portchannel_vpc_config(module, portchannel)
+
+ if state == "present":
+ delta = dict(set(proposed.items()).difference(existing.items()))
+ if delta:
+ commands = state_present(portchannel, delta, config_value, existing)
+
+ elif state == "absent" and existing:
+ commands = state_absent(portchannel, existing)
+
+ cmds = flatten_list(commands)
+ if cmds:
+ if module.check_mode:
+ module.exit_json(changed=True, commands=cmds)
+ else:
+ load_config(module, cmds)
+ results["changed"] = True
+ if "configure" in cmds:
+ cmds.pop(0)
+
+ results["commands"] = cmds
+ module.exit_json(**results)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_vrf.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_vrf.py
new file mode 100644
index 00000000..66fa43ab
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_vrf.py
@@ -0,0 +1,616 @@
+#!/usr/bin/python
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_vrf
+extends_documentation_fragment:
+- cisco.nxos.nxos
+short_description: Manages global VRF configuration.
+description:
+- This module provides declarative management of VRFs on CISCO NXOS network devices.
+version_added: 1.0.0
+author:
+- Jason Edelman (@jedelman8)
+- Gabriele Gerbino (@GGabriele)
+- Trishna Guha (@trishnaguha)
+notes:
+- Tested against NXOSv 7.3.(0)D1(1) on VIRL
+- Unsupported for Cisco MDS
+- Cisco NX-OS creates the default VRF by itself. Therefore, you're not allowed to
+ use default as I(vrf) name in this module.
+- C(vrf) name must be shorter than 32 chars.
+- VRF names are not case sensible in NX-OS. Anyway, the name is stored just like it's
+ inserted by the user and it'll not be changed again unless the VRF is removed and
+ re-created. i.e. C(vrf=NTC) will create a VRF named NTC, but running it again with
+ C(vrf=ntc) will not cause a configuration change.
+options:
+ name:
+ description:
+ - Name of VRF to be managed.
+ aliases:
+ - vrf
+ type: str
+ admin_state:
+ description:
+ - Administrative state of the VRF.
+ default: up
+ choices:
+ - up
+ - down
+ type: str
+ vni:
+ description:
+ - Specify virtual network identifier. Valid values are Integer or keyword 'default'.
+ type: str
+ rd:
+ description:
+ - VPN Route Distinguisher (RD). Valid values are a string in one of the route-distinguisher
+ formats (ASN2:NN, ASN4:NN, or IPV4:NN); the keyword 'auto', or the keyword 'default'.
+ type: str
+ interfaces:
+ description:
+ - List of interfaces to check the VRF has been configured correctly or keyword
+ 'default'.
+ type: list
+ elements: str
+ associated_interfaces:
+ description:
+ - This is a intent option and checks the operational state of the for given vrf
+ C(name) for associated interfaces. If the value in the C(associated_interfaces)
+ does not match with the operational state of vrf interfaces on device it will
+ result in failure.
+ type: list
+ elements: str
+ aggregate:
+ description: List of VRFs definitions.
+ type: list
+ elements: dict
+ suboptions:
+ name:
+ description:
+ - Name of VRF to be managed.
+ aliases:
+ - vrf
+ type: str
+ admin_state:
+ description:
+ - Administrative state of the VRF.
+ choices:
+ - up
+ - down
+ type: str
+ vni:
+ description:
+ - Specify virtual network identifier. Valid values are Integer or keyword 'default'.
+ type: str
+ rd:
+ description:
+ - VPN Route Distinguisher (RD). Valid values are a string in one of the route-distinguisher
+ formats (ASN2:NN, ASN4:NN, or IPV4:NN); the keyword 'auto', or the keyword 'default'.
+ type: str
+ interfaces:
+ description:
+ - List of interfaces to check the VRF has been configured correctly or keyword
+ 'default'.
+ type: list
+ elements: str
+ associated_interfaces:
+ description:
+ - This is a intent option and checks the operational state of the for given vrf
+ C(name) for associated interfaces. If the value in the C(associated_interfaces)
+ does not match with the operational state of vrf interfaces on device it will
+ result in failure.
+ type: list
+ elements: str
+ state:
+ description:
+ - Manages desired state of the resource.
+ choices:
+ - present
+ - absent
+ type: str
+ description:
+ description:
+ - Description of the VRF or keyword 'default'.
+ type: str
+ delay:
+ description:
+ - Time in seconds to wait before checking for the operational state on remote
+ device. This wait is applicable for operational state arguments.
+ type: int
+ purge:
+ description:
+ - Purge VRFs not defined in the I(aggregate) parameter.
+ type: bool
+ default: no
+ state:
+ description:
+ - Manages desired state of the resource.
+ default: present
+ choices:
+ - present
+ - absent
+ type: str
+ description:
+ description:
+ - Description of the VRF or keyword 'default'.
+ type: str
+ delay:
+ description:
+ - Time in seconds to wait before checking for the operational state on remote
+ device. This wait is applicable for operational state arguments.
+ default: 10
+ type: int
+"""
+
+EXAMPLES = """
+- name: Ensure ntc VRF exists on switch
+ cisco.nxos.nxos_vrf:
+ name: ntc
+ description: testing
+ state: present
+
+- name: Aggregate definition of VRFs
+ cisco.nxos.nxos_vrf:
+ aggregate:
+ - {name: test1, description: Testing, admin_state: down}
+ - {name: test2, interfaces: Ethernet1/2}
+
+- name: Aggregate definitions of VRFs with Purge
+ cisco.nxos.nxos_vrf:
+ aggregate:
+ - {name: ntc1, description: purge test1}
+ - {name: ntc2, description: purge test2}
+ state: present
+ purge: yes
+
+- name: Delete VRFs exist on switch
+ cisco.nxos.nxos_vrf:
+ aggregate:
+ - {name: ntc1}
+ - {name: ntc2}
+ state: absent
+
+- name: Assign interfaces to VRF declaratively
+ cisco.nxos.nxos_vrf:
+ name: test1
+ interfaces:
+ - Ethernet2/3
+ - Ethernet2/5
+
+- name: Check interfaces assigned to VRF
+ cisco.nxos.nxos_vrf:
+ name: test1
+ associated_interfaces:
+ - Ethernet2/3
+ - Ethernet2/5
+
+- name: Ensure VRF is tagged with interface Ethernet2/5 only (Removes from Ethernet2/3)
+ cisco.nxos.nxos_vrf:
+ name: test1
+ interfaces:
+ - Ethernet2/5
+
+- name: Delete VRF
+ cisco.nxos.nxos_vrf:
+ name: ntc
+ state: absent
+"""
+
+RETURN = """
+commands:
+ description: commands sent to the device
+ returned: always
+ type: list
+ sample:
+ - vrf context ntc
+ - no shutdown
+ - interface Ethernet1/2
+ - no switchport
+ - vrf member test2
+"""
+
+import re
+import time
+
+from copy import deepcopy
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import (
+ remove_default_spec,
+)
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ get_interface_type,
+ load_config,
+ run_commands,
+)
+
+
+def search_obj_in_list(name, lst):
+ for o in lst:
+ if o["name"] == name:
+ return o
+
+
+def execute_show_command(command, module):
+ if "show run" not in command:
+ output = "json"
+ else:
+ output = "text"
+ cmds = [{"command": command, "output": output}]
+ body = run_commands(module, cmds)
+ return body
+
+
+def get_existing_vrfs(module):
+ objs = list()
+ command = "show vrf all"
+ try:
+ body = execute_show_command(command, module)[0]
+ except IndexError:
+ return list()
+ try:
+ vrf_table = body["TABLE_vrf"]["ROW_vrf"]
+ except (TypeError, IndexError, KeyError):
+ return list()
+
+ if isinstance(vrf_table, list):
+ for vrf in vrf_table:
+ obj = {}
+ obj["name"] = vrf["vrf_name"]
+ objs.append(obj)
+
+ elif isinstance(vrf_table, dict):
+ obj = {}
+ obj["name"] = vrf_table["vrf_name"]
+ objs.append(obj)
+
+ return objs
+
+
+def map_obj_to_commands(updates, module):
+ commands = list()
+ want, have = updates
+ state = module.params["state"]
+ purge = module.params["purge"]
+
+ args = ("rd", "description", "vni")
+
+ for w in want:
+ name = w["name"]
+ admin_state = w["admin_state"]
+ vni = w["vni"]
+ interfaces = w.get("interfaces") or []
+ if purge:
+ state = "absent"
+ else:
+ state = w["state"]
+ del w["state"]
+
+ obj_in_have = search_obj_in_list(name, have)
+ if state == "absent" and obj_in_have:
+ commands.append("no vrf context {0}".format(name))
+
+ elif state == "present":
+ if not obj_in_have:
+ commands.append("vrf context {0}".format(name))
+ for item in args:
+ candidate = w.get(item)
+ if candidate and candidate != "default":
+ cmd = item + " " + str(candidate)
+ commands.append(cmd)
+ if admin_state == "up":
+ commands.append("no shutdown")
+ elif admin_state == "down":
+ commands.append("shutdown")
+ commands.append("exit")
+
+ if interfaces and interfaces[0] != "default":
+ for i in interfaces:
+ commands.append("interface {0}".format(i))
+ if get_interface_type(i) in (
+ "ethernet",
+ "portchannel",
+ ):
+ commands.append("no switchport")
+ commands.append("vrf member {0}".format(name))
+
+ else:
+ # If vni is already configured on vrf, unconfigure it first.
+ if vni:
+ if obj_in_have.get("vni") and vni != obj_in_have.get("vni"):
+ commands.append("no vni {0}".format(obj_in_have.get("vni")))
+
+ for item in args:
+ candidate = w.get(item)
+ if candidate == "default":
+ if obj_in_have.get(item):
+ cmd = "no " + item + " " + obj_in_have.get(item)
+ commands.append(cmd)
+ elif candidate and candidate != obj_in_have.get(item):
+ cmd = item + " " + str(candidate)
+ commands.append(cmd)
+ if admin_state and admin_state != obj_in_have.get("admin_state"):
+ if admin_state == "up":
+ commands.append("no shutdown")
+ elif admin_state == "down":
+ commands.append("shutdown")
+
+ if commands:
+ commands.insert(0, "vrf context {0}".format(name))
+ commands.append("exit")
+
+ if interfaces and interfaces[0] != "default":
+ if not obj_in_have["interfaces"]:
+ for i in interfaces:
+ commands.append("vrf context {0}".format(name))
+ commands.append("exit")
+ commands.append("interface {0}".format(i))
+ if get_interface_type(i) in (
+ "ethernet",
+ "portchannel",
+ ):
+ commands.append("no switchport")
+ commands.append("vrf member {0}".format(name))
+
+ elif set(interfaces) != set(obj_in_have["interfaces"]):
+ missing_interfaces = list(set(interfaces) - set(obj_in_have["interfaces"]))
+ for i in missing_interfaces:
+ commands.append("vrf context {0}".format(name))
+ commands.append("exit")
+ commands.append("interface {0}".format(i))
+ if get_interface_type(i) in (
+ "ethernet",
+ "portchannel",
+ ):
+ commands.append("no switchport")
+ commands.append("vrf member {0}".format(name))
+
+ superfluous_interfaces = list(
+ set(obj_in_have["interfaces"]) - set(interfaces),
+ )
+ for i in superfluous_interfaces:
+ commands.append("vrf context {0}".format(name))
+ commands.append("exit")
+ commands.append("interface {0}".format(i))
+ if get_interface_type(i) in (
+ "ethernet",
+ "portchannel",
+ ):
+ commands.append("no switchport")
+ commands.append("no vrf member {0}".format(name))
+ elif interfaces and interfaces[0] == "default":
+ if obj_in_have["interfaces"]:
+ for i in obj_in_have["interfaces"]:
+ commands.append("vrf context {0}".format(name))
+ commands.append("exit")
+ commands.append("interface {0}".format(i))
+ if get_interface_type(i) in (
+ "ethernet",
+ "portchannel",
+ ):
+ commands.append("no switchport")
+ commands.append("no vrf member {0}".format(name))
+
+ if purge:
+ existing = get_existing_vrfs(module)
+ if existing:
+ for h in existing:
+ if h["name"] in ("default", "management"):
+ pass
+ else:
+ obj_in_want = search_obj_in_list(h["name"], want)
+ if not obj_in_want:
+ commands.append("no vrf context {0}".format(h["name"]))
+
+ return commands
+
+
+def validate_vrf(name, module):
+ if name:
+ name = name.strip()
+ if name == "default":
+ module.fail_json(msg="cannot use default as name of a VRF")
+ elif len(name) > 32:
+ module.fail_json(msg="VRF name exceeded max length of 32", name=name)
+ else:
+ return name
+
+
+def map_params_to_obj(module):
+ obj = []
+ aggregate = module.params.get("aggregate")
+ if aggregate:
+ for item in aggregate:
+ for key in item:
+ if item.get(key) is None:
+ item[key] = module.params[key]
+
+ d = item.copy()
+ d["name"] = validate_vrf(d["name"], module)
+ obj.append(d)
+ else:
+ obj.append(
+ {
+ "name": validate_vrf(module.params["name"], module),
+ "description": module.params["description"],
+ "vni": module.params["vni"],
+ "rd": module.params["rd"],
+ "admin_state": module.params["admin_state"],
+ "state": module.params["state"],
+ "interfaces": module.params["interfaces"],
+ "associated_interfaces": module.params["associated_interfaces"],
+ },
+ )
+ return obj
+
+
+def get_value(arg, config, module):
+ extra_arg_regex = re.compile(r"(?:{0}\s)(?P<value>.*)$".format(arg), re.M)
+ value = ""
+ if arg in config:
+ value = extra_arg_regex.search(config).group("value")
+ return value
+
+
+def map_config_to_obj(want, element_spec, module):
+ objs = list()
+
+ for w in want:
+ obj = deepcopy(element_spec)
+ del obj["delay"]
+ del obj["state"]
+
+ command = "show vrf {0}".format(w["name"])
+ try:
+ body = execute_show_command(command, module)[0]
+ vrf_table = body["TABLE_vrf"]["ROW_vrf"]
+ except (TypeError, IndexError):
+ return list()
+
+ name = vrf_table["vrf_name"]
+ obj["name"] = name
+ obj["admin_state"] = vrf_table["vrf_state"].lower()
+
+ command = "show run all | section vrf.context.{0}".format(name)
+ body = execute_show_command(command, module)[0]
+ extra_params = ["vni", "rd", "description"]
+ for param in extra_params:
+ obj[param] = get_value(param, body, module)
+
+ obj["interfaces"] = []
+ command = "show vrf {0} interface".format(name)
+ try:
+ body = execute_show_command(command, module)[0]
+ vrf_int = body["TABLE_if"]["ROW_if"]
+ except (TypeError, IndexError):
+ vrf_int = None
+
+ if vrf_int:
+ if isinstance(vrf_int, list):
+ for i in vrf_int:
+ intf = i["if_name"]
+ obj["interfaces"].append(intf)
+ elif isinstance(vrf_int, dict):
+ intf = vrf_int["if_name"]
+ obj["interfaces"].append(intf)
+
+ objs.append(obj)
+ return objs
+
+
+def check_declarative_intent_params(want, module, element_spec, result):
+ have = None
+ is_delay = False
+
+ for w in want:
+ if w.get("associated_interfaces") is None:
+ continue
+
+ if result["changed"] and not is_delay:
+ time.sleep(module.params["delay"])
+ is_delay = True
+
+ if have is None:
+ have = map_config_to_obj(want, element_spec, module)
+
+ for i in w["associated_interfaces"]:
+ obj_in_have = search_obj_in_list(w["name"], have)
+
+ if obj_in_have:
+ interfaces = obj_in_have.get("interfaces")
+ if interfaces is not None and i not in interfaces:
+ module.fail_json(msg="Interface %s not configured on vrf %s" % (i, w["name"]))
+
+
+def vrf_error_check(module, commands, responses):
+ """Checks for VRF config errors and executes a retry in some circumstances."""
+ pattern = "ERROR: Deletion of VRF .* in progress"
+ if re.search(pattern, str(responses)):
+ # Allow delay/retry for VRF changes
+ time.sleep(15)
+ responses = load_config(module, commands, opts={"catch_clierror": True})
+ if re.search(pattern, str(responses)):
+ module.fail_json(msg="VRF config (and retry) failure: %s " % responses)
+ module.warn("VRF config delayed by VRF deletion - passed on retry")
+
+
+def main():
+ """main entry point for module execution"""
+ element_spec = dict(
+ name=dict(type="str", aliases=["vrf"]),
+ description=dict(type="str"),
+ vni=dict(type="str"),
+ rd=dict(type="str"),
+ admin_state=dict(type="str", default="up", choices=["up", "down"]),
+ interfaces=dict(type="list", elements="str"),
+ associated_interfaces=dict(type="list", elements="str"),
+ delay=dict(type="int", default=10),
+ state=dict(type="str", default="present", choices=["present", "absent"]),
+ )
+
+ aggregate_spec = deepcopy(element_spec)
+
+ # remove default in aggregate spec, to handle common arguments
+ remove_default_spec(aggregate_spec)
+
+ argument_spec = dict(
+ aggregate=dict(type="list", elements="dict", options=aggregate_spec),
+ purge=dict(type="bool", default=False),
+ )
+
+ argument_spec.update(element_spec)
+
+ required_one_of = [["name", "aggregate"]]
+ mutually_exclusive = [["name", "aggregate"]]
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ required_one_of=required_one_of,
+ mutually_exclusive=mutually_exclusive,
+ supports_check_mode=True,
+ )
+
+ warnings = list()
+ result = {"changed": False}
+ if warnings:
+ result["warnings"] = warnings
+
+ want = map_params_to_obj(module)
+ have = map_config_to_obj(want, element_spec, module)
+
+ commands = map_obj_to_commands((want, have), module)
+ result["commands"] = commands
+
+ if commands and not module.check_mode:
+ responses = load_config(module, commands, opts={"catch_clierror": True})
+ vrf_error_check(module, commands, responses)
+ result["changed"] = True
+
+ check_declarative_intent_params(want, module, element_spec, result)
+
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_vrf_af.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_vrf_af.py
new file mode 100644
index 00000000..bf155ce8
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_vrf_af.py
@@ -0,0 +1,274 @@
+#!/usr/bin/python
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_vrf_af
+extends_documentation_fragment:
+- cisco.nxos.nxos
+short_description: Manages VRF AF.
+description:
+- Manages VRF AF
+version_added: 1.0.0
+author: Gabriele Gerbino (@GGabriele)
+notes:
+- Tested against NXOSv 7.3.(0)D1(1) on VIRL
+- Unsupported for Cisco MDS
+- Default, where supported, restores params default value.
+- In case of C(state=absent) the address-family configuration will be absent. Therefore
+ the options C(route_target_both_auto_evpn) and C(route_targets) are ignored.
+options:
+ vrf:
+ description:
+ - Name of the VRF.
+ required: true
+ type: str
+ afi:
+ description:
+ - Address-Family Identifier (AFI).
+ required: true
+ choices:
+ - ipv4
+ - ipv6
+ type: str
+ route_target_both_auto_evpn:
+ description:
+ - Enable/Disable the EVPN route-target 'auto' setting for both import and export
+ target communities.
+ type: bool
+ route_targets:
+ description:
+ - Specify the route-targets which should be imported and/or exported under the
+ AF. This argument accepts a list of dicts that specify the route-target, the
+ direction (import|export|both) and state of each route-target. Default direction
+ is C(direction=both). See examples.
+ suboptions:
+ rt:
+ description:
+ - Defines the route-target itself
+ required: true
+ type: str
+ direction:
+ description:
+ - Indicates the direction of the route-target (import|export|both)
+ choices:
+ - import
+ - export
+ - both
+ default: both
+ type: str
+ state:
+ description:
+ - Determines whether the route-target with the given direction should be present
+ or not on the device.
+ choices:
+ - present
+ - absent
+ default: present
+ type: str
+ elements: dict
+ type: list
+ state:
+ description:
+ - Determines whether the config should be present or not on the device.
+ default: present
+ choices:
+ - present
+ - absent
+ type: str
+"""
+
+EXAMPLES = """
+- cisco.nxos.nxos_vrf_af:
+ vrf: ntc
+ afi: ipv4
+ route_target_both_auto_evpn: true
+ state: present
+
+- cisco.nxos.nxos_vrf_af:
+ vrf: ntc
+ afi: ipv4
+ route_targets:
+ - rt: 65000:1000
+ direction: import
+ - rt: 65001:1000
+ direction: import
+
+- cisco.nxos.nxos_vrf_af:
+ vrf: ntc
+ afi: ipv4
+ route_targets:
+ - rt: 65000:1000
+ direction: import
+ - rt: 65001:1000
+ state: absent
+
+- cisco.nxos.nxos_vrf_af:
+ vrf: ntc
+ afi: ipv4
+ route_targets:
+ - rt: 65000:1000
+ direction: export
+ - rt: 65001:1000
+ direction: export
+
+- cisco.nxos.nxos_vrf_af:
+ vrf: ntc
+ afi: ipv4
+ route_targets:
+ - rt: 65000:1000
+ direction: export
+ state: absent
+
+- cisco.nxos.nxos_vrf_af:
+ vrf: ntc
+ afi: ipv4
+ route_targets:
+ - rt: 65000:1000
+ direction: both
+ state: present
+ - rt: 65001:1000
+ direction: import
+ state: present
+ - rt: 65002:1000
+ direction: both
+ state: absent
+"""
+
+RETURN = """
+commands:
+ description: commands sent to the device
+ returned: always
+ type: list
+ sample: ["vrf context ntc", "address-family ipv4 unicast"]
+"""
+import re
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import (
+ NetworkConfig,
+)
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ get_capabilities,
+ get_config,
+ load_config,
+)
+
+
+def match_current_rt(rt, direction, current, rt_commands):
+ command = "route-target %s %s" % (direction, rt.get("rt"))
+ match = re.findall(command, current, re.M)
+ want = bool(rt.get("state") != "absent")
+ if not match and want:
+ rt_commands.append(command)
+ elif match and not want:
+ rt_commands.append("no %s" % command)
+ return rt_commands
+
+
+def main():
+ argument_spec = dict(
+ vrf=dict(required=True),
+ afi=dict(required=True, choices=["ipv4", "ipv6"]),
+ route_target_both_auto_evpn=dict(required=False, type="bool"),
+ state=dict(choices=["present", "absent"], default="present"),
+ route_targets=dict(
+ type="list",
+ elements="dict",
+ options=dict(
+ rt=dict(type="str", required=True),
+ direction=dict(choices=["import", "export", "both"], default="both"),
+ state=dict(choices=["present", "absent"], default="present"),
+ ),
+ ),
+ )
+
+ module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)
+
+ warnings = list()
+
+ result = {"changed": False, "warnings": warnings}
+
+ config_text = get_config(module)
+ config = NetworkConfig(indent=2, contents=config_text)
+
+ path = [
+ "vrf context %s" % module.params["vrf"],
+ "address-family %s unicast" % module.params["afi"],
+ ]
+
+ try:
+ current = config.get_block_config(path)
+ except ValueError:
+ current = None
+
+ commands = list()
+ if current and module.params["state"] == "absent":
+ commands.append("no address-family %s unicast" % module.params["afi"])
+
+ elif module.params["state"] == "present":
+ rt_commands = list()
+
+ if not current:
+ commands.append("address-family %s unicast" % module.params["afi"])
+ current = ""
+
+ have_auto_evpn = "route-target both auto evpn" in current
+ if module.params["route_target_both_auto_evpn"] is not None:
+ want_auto_evpn = bool(module.params["route_target_both_auto_evpn"])
+ if want_auto_evpn and not have_auto_evpn:
+ commands.append("route-target both auto evpn")
+ elif have_auto_evpn and not want_auto_evpn:
+ commands.append("no route-target both auto evpn")
+
+ if module.params["route_targets"] is not None:
+ for rt in module.params["route_targets"]:
+ if rt.get("direction") == "both" or not rt.get("direction"):
+ platform = get_capabilities(module)["device_info"]["network_os_platform"]
+ if platform.startswith("N9K") and rt.get("rt") == "auto":
+ rt_commands = match_current_rt(rt, "both", current, rt_commands)
+ else:
+ rt_commands = match_current_rt(rt, "import", current, rt_commands)
+ rt_commands = match_current_rt(rt, "export", current, rt_commands)
+ else:
+ rt_commands = match_current_rt(rt, rt.get("direction"), current, rt_commands)
+
+ if rt_commands:
+ commands.extend(rt_commands)
+
+ if commands and current:
+ commands.insert(0, "address-family %s unicast" % module.params["afi"])
+
+ if commands:
+ commands.insert(0, "vrf context %s" % module.params["vrf"])
+ if not module.check_mode:
+ load_config(module, commands)
+ result["changed"] = True
+
+ result["commands"] = commands
+
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_vrf_interface.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_vrf_interface.py
new file mode 100644
index 00000000..9e9a0cef
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_vrf_interface.py
@@ -0,0 +1,267 @@
+#!/usr/bin/python
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_vrf_interface
+extends_documentation_fragment:
+- cisco.nxos.nxos
+short_description: Manages interface specific VRF configuration.
+description:
+- Manages interface specific VRF configuration.
+version_added: 1.0.0
+author:
+- Jason Edelman (@jedelman8)
+- Gabriele Gerbino (@GGabriele)
+notes:
+- Tested against NXOSv 7.3.(0)D1(1) on VIRL
+- Unsupported for Cisco MDS
+- VRF needs to be added globally with M(cisco.nxos.nxos_vrf) before adding a VRF to an interface.
+- Remove a VRF from an interface will still remove all L3 attributes just as it does
+ from CLI.
+- VRF is not read from an interface until IP address is configured on that interface.
+options:
+ vrf:
+ description:
+ - Name of VRF to be managed.
+ required: true
+ type: str
+ interface:
+ description:
+ - Full name of interface to be managed, i.e. Ethernet1/1.
+ required: true
+ type: str
+ state:
+ description:
+ - Manages desired state of the resource.
+ required: false
+ default: present
+ choices:
+ - present
+ - absent
+ type: str
+"""
+
+EXAMPLES = """
+- name: Ensure vrf ntc exists on Eth1/1
+ cisco.nxos.nxos_vrf_interface:
+ vrf: ntc
+ interface: Ethernet1/1
+ state: present
+
+- name: Ensure ntc VRF does not exist on Eth1/1
+ cisco.nxos.nxos_vrf_interface:
+ vrf: ntc
+ interface: Ethernet1/1
+ state: absent
+"""
+
+RETURN = """
+commands:
+ description: commands sent to the device
+ returned: always
+ type: list
+ sample: ["interface loopback16", "vrf member ntc"]
+"""
+import re
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ get_capabilities,
+ get_interface_type,
+ load_config,
+ normalize_interface,
+ run_commands,
+)
+
+
+def execute_show_command(command, module):
+ if "show run" not in command:
+ output = "json"
+ else:
+ output = "text"
+ cmds = [{"command": command, "output": output}]
+ return run_commands(module, cmds)[0]
+
+
+def get_interface_mode(interface, intf_type, module):
+ command = "show interface {0}".format(interface)
+ interface = {}
+ mode = "unknown"
+
+ if intf_type in ["ethernet", "portchannel"]:
+ body = execute_show_command(command, module)
+ try:
+ interface_table = body["TABLE_interface"]["ROW_interface"]
+ except KeyError:
+ return mode
+
+ if interface_table and isinstance(interface_table, dict):
+ mode = str(interface_table.get("eth_mode", "layer3"))
+ if mode == "access" or mode == "trunk":
+ mode = "layer2"
+ else:
+ return mode
+
+ elif intf_type == "loopback" or intf_type == "svi":
+ mode = "layer3"
+ return mode
+
+
+def get_vrf_list(module):
+ command = "show vrf all"
+ vrf_list = []
+ body = execute_show_command(command, module)
+
+ try:
+ vrf_table = body["TABLE_vrf"]["ROW_vrf"]
+ except (KeyError, AttributeError):
+ return vrf_list
+
+ for each in vrf_table:
+ vrf_list.append(str(each["vrf_name"]))
+
+ return vrf_list
+
+
+def get_interface_info(interface, module):
+ if not interface.startswith("loopback"):
+ interface = interface.capitalize()
+
+ command = "show run interface {0}".format(interface)
+ vrf_regex = r".*vrf\s+member\s+(?P<vrf>\S+).*"
+
+ try:
+ body = execute_show_command(command, module)
+ match_vrf = re.match(vrf_regex, body, re.DOTALL)
+ group_vrf = match_vrf.groupdict()
+ vrf = group_vrf["vrf"]
+ except (AttributeError, TypeError):
+ return ""
+
+ return vrf
+
+
+def is_default(interface, module):
+ command = "show run interface {0}".format(interface)
+
+ try:
+ body = execute_show_command(command, module)
+ raw_list = body.split("\n")
+ if raw_list[-1].startswith("interface"):
+ return True
+ else:
+ return False
+
+ except (KeyError, IndexError):
+ return "DNE"
+
+
+def main():
+ argument_spec = dict(
+ vrf=dict(required=True),
+ interface=dict(type="str", required=True),
+ state=dict(default="present", choices=["present", "absent"], required=False),
+ )
+
+ module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)
+
+ warnings = list()
+ results = {"changed": False, "commands": [], "warnings": warnings}
+
+ vrf = module.params["vrf"]
+ interface = normalize_interface(module.params["interface"])
+ state = module.params["state"]
+
+ device_info = get_capabilities(module)
+ network_api = device_info.get("network_api", "nxapi")
+
+ current_vrfs = get_vrf_list(module)
+ if vrf not in current_vrfs:
+ warnings.append("The VRF is not present/active on the device. Use nxos_vrf to fix this.")
+
+ intf_type = get_interface_type(interface)
+ if intf_type != "ethernet" and network_api == "cliconf":
+ if is_default(interface, module) == "DNE":
+ module.fail_json(
+ msg="interface does not exist on switch. Verify "
+ "switch platform or create it first with "
+ "nxos_interfaces if it's a logical interface",
+ )
+
+ mode = get_interface_mode(interface, intf_type, module)
+ if mode == "layer2":
+ module.fail_json(
+ msg="Ensure interface is a Layer 3 port before "
+ "configuring a VRF on an interface. You can "
+ "use nxos_interfaces",
+ )
+
+ current_vrf = get_interface_info(interface, module)
+ existing = dict(interface=interface, vrf=current_vrf)
+ changed = False
+
+ if not existing["vrf"]:
+ pass
+ elif vrf != existing["vrf"] and state == "absent":
+ module.fail_json(
+ msg="The VRF you are trying to remove "
+ "from the interface does not exist "
+ "on that interface.",
+ interface=interface,
+ proposed_vrf=vrf,
+ existing_vrf=existing["vrf"],
+ )
+
+ commands = []
+ if existing:
+ if state == "absent":
+ if existing and vrf == existing["vrf"]:
+ command = "no vrf member {0}".format(vrf)
+ commands.append(command)
+
+ elif state == "present":
+ if existing["vrf"] != vrf:
+ command = "vrf member {0}".format(vrf)
+ commands.append(command)
+
+ if commands:
+ commands.insert(0, "interface {0}".format(interface))
+
+ if commands:
+ if module.check_mode:
+ module.exit_json(changed=True, commands=commands)
+ else:
+ load_config(module, commands)
+ changed = True
+ if "configure" in commands:
+ commands.pop(0)
+
+ results["commands"] = commands
+ results["changed"] = changed
+
+ module.exit_json(**results)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_vrrp.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_vrrp.py
new file mode 100644
index 00000000..b5a026c4
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_vrrp.py
@@ -0,0 +1,432 @@
+#!/usr/bin/python
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_vrrp
+extends_documentation_fragment:
+- cisco.nxos.nxos
+short_description: Manages VRRP configuration on NX-OS switches.
+description:
+- Manages VRRP configuration on NX-OS switches.
+version_added: 1.0.0
+author:
+- Jason Edelman (@jedelman8)
+- Gabriele Gerbino (@GGabriele)
+notes:
+- Tested against NXOSv 7.3.(0)D1(1) on VIRL
+- Unsupported for Cisco MDS
+- VRRP feature needs to be enabled first on the system.
+- SVIs must exist before using this module.
+- Interface must be a L3 port before using this module.
+- C(state=absent) removes the VRRP group if it exists on the device.
+- VRRP cannot be configured on loopback interfaces.
+options:
+ group:
+ description:
+ - VRRP group number.
+ required: true
+ type: str
+ interface:
+ description:
+ - Full name of interface that is being managed for VRRP.
+ required: true
+ type: str
+ interval:
+ description:
+ - Time interval between advertisement or 'default' keyword
+ required: false
+ type: str
+ priority:
+ description:
+ - VRRP priority or 'default' keyword
+ type: str
+ preempt:
+ description:
+ - Enable/Disable preempt.
+ type: bool
+ vip:
+ description:
+ - VRRP virtual IP address or 'default' keyword
+ type: str
+ authentication:
+ description:
+ - Clear text authentication string or 'default' keyword
+ type: str
+ admin_state:
+ description:
+ - Used to enable or disable the VRRP process.
+ choices:
+ - shutdown
+ - no shutdown
+ - default
+ default: shutdown
+ type: str
+ state:
+ description:
+ - Specify desired state of the resource.
+ default: present
+ choices:
+ - present
+ - absent
+ type: str
+"""
+
+EXAMPLES = """
+- name: Ensure vrrp group 100 and vip 10.1.100.1 is on vlan10
+ cisco.nxos.nxos_vrrp:
+ interface: vlan10
+ group: 100
+ vip: 10.1.100.1
+
+- name: Ensure removal of the vrrp group config
+ cisco.nxos.nxos_vrrp:
+ interface: vlan10
+ group: 100
+ vip: 10.1.100.1
+ state: absent
+
+- name: Re-config with more params
+ cisco.nxos.nxos_vrrp:
+ interface: vlan10
+ group: 100
+ vip: 10.1.100.1
+ preempt: false
+ priority: 130
+ authentication: AUTHKEY
+"""
+
+RETURN = """
+commands:
+ description: commands sent to the device
+ returned: always
+ type: list
+ sample: ["interface vlan10", "vrrp 150", "address 10.1.15.1",
+ "authentication text testing", "no shutdown"]
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ get_capabilities,
+ get_interface_type,
+ load_config,
+ run_commands,
+)
+
+
+PARAM_TO_DEFAULT_KEYMAP = {
+ "priority": "100",
+ "interval": "1",
+ "vip": "0.0.0.0",
+ "admin_state": "shutdown",
+}
+
+
+def execute_show_command(command, module):
+ if "show run" not in command:
+ output = "json"
+ else:
+ output = "text"
+
+ commands = [{"command": command, "output": output}]
+ return run_commands(module, commands)[0]
+
+
+def apply_key_map(key_map, table):
+ new_dict = {}
+ for key, value in table.items():
+ new_key = key_map.get(key)
+ if new_key:
+ if value:
+ new_dict[new_key] = str(value)
+ else:
+ new_dict[new_key] = value
+ return new_dict
+
+
+def is_default(interface, module):
+ command = "show run interface {0}".format(interface)
+
+ try:
+ body = execute_show_command(command, module)
+ if "invalid" in body.lower():
+ return "DNE"
+ else:
+ raw_list = body.split("\n")
+ if raw_list[-1].startswith("interface"):
+ return True
+ else:
+ return False
+ except KeyError:
+ return "DNE"
+
+
+def get_interface_mode(interface, intf_type, module):
+ command = "show interface {0}".format(interface)
+ interface = {}
+ mode = "unknown"
+ body = execute_show_command(command, module)
+ interface_table = body["TABLE_interface"]["ROW_interface"]
+ name = interface_table.get("interface")
+
+ if intf_type in ["ethernet", "portchannel"]:
+ mode = str(interface_table.get("eth_mode", "layer3"))
+
+ if mode == "access" or mode == "trunk":
+ mode = "layer2"
+ elif intf_type == "svi":
+ mode = "layer3"
+
+ return mode, name
+
+
+def get_vrr_status(group, module, interface):
+ command = "show run all | section interface.{0}$".format(interface)
+ body = execute_show_command(command, module)
+ vrf_index = None
+ admin_state = "shutdown"
+
+ if body:
+ splitted_body = body.splitlines()
+ for index in range(0, len(splitted_body) - 1):
+ if splitted_body[index].strip() == "vrrp {0}".format(group):
+ vrf_index = index
+ vrf_section = splitted_body[vrf_index::]
+
+ for line in vrf_section:
+ if line.strip() == "no shutdown":
+ admin_state = "no shutdown"
+ break
+
+ return admin_state
+
+
+def get_existing_vrrp(interface, group, module, name):
+ command = "show vrrp detail interface {0}".format(interface)
+ body = execute_show_command(command, module)
+ vrrp = {}
+
+ vrrp_key = {
+ "sh_group_id": "group",
+ "sh_vip_addr": "vip",
+ "sh_priority": "priority",
+ "sh_group_preempt": "preempt",
+ "sh_auth_text": "authentication",
+ "sh_adv_interval": "interval",
+ }
+
+ try:
+ vrrp_table = body["TABLE_vrrp_group"]
+ except (AttributeError, IndexError, TypeError):
+ return {}
+
+ if isinstance(vrrp_table, dict):
+ vrrp_table = [vrrp_table]
+
+ for each_vrrp in vrrp_table:
+ vrrp_row = each_vrrp["ROW_vrrp_group"]
+ parsed_vrrp = apply_key_map(vrrp_key, vrrp_row)
+
+ if parsed_vrrp["preempt"] == "Disable":
+ parsed_vrrp["preempt"] = False
+ elif parsed_vrrp["preempt"] == "Enable":
+ parsed_vrrp["preempt"] = True
+
+ if parsed_vrrp["group"] == group:
+ parsed_vrrp["admin_state"] = get_vrr_status(group, module, name)
+ return parsed_vrrp
+
+ return vrrp
+
+
+def get_commands_config_vrrp(delta, existing, group):
+ commands = []
+
+ CMDS = {
+ "priority": "priority {0}",
+ "preempt": "preempt",
+ "vip": "address {0}",
+ "interval": "advertisement-interval {0}",
+ "auth": "authentication text {0}",
+ "admin_state": "{0}",
+ }
+
+ for arg in ["vip", "priority", "interval", "admin_state"]:
+ val = delta.get(arg)
+ if val == "default":
+ val = PARAM_TO_DEFAULT_KEYMAP.get(arg)
+ if val != existing.get(arg):
+ commands.append((CMDS.get(arg)).format(val))
+ elif val:
+ commands.append((CMDS.get(arg)).format(val))
+
+ preempt = delta.get("preempt")
+ auth = delta.get("authentication")
+
+ if preempt:
+ commands.append(CMDS.get("preempt"))
+ elif preempt is False:
+ commands.append("no " + CMDS.get("preempt"))
+ if auth:
+ if auth != "default":
+ commands.append((CMDS.get("auth")).format(auth))
+ elif existing.get("authentication"):
+ commands.append("no authentication")
+
+ if commands:
+ commands.insert(0, "vrrp {0}".format(group))
+
+ return commands
+
+
+def flatten_list(command_lists):
+ flat_command_list = []
+ for command in command_lists:
+ if isinstance(command, list):
+ flat_command_list.extend(command)
+ else:
+ flat_command_list.append(command)
+ return flat_command_list
+
+
+def validate_params(param, module):
+ value = module.params[param]
+
+ if param == "group":
+ try:
+ if int(value) < 1 or int(value) > 255:
+ raise ValueError
+ except ValueError:
+ module.fail_json(
+ msg="Warning! 'group' must be an integer between" " 1 and 255",
+ group=value,
+ )
+ elif param == "priority":
+ try:
+ if int(value) < 1 or int(value) > 254:
+ raise ValueError
+ except ValueError:
+ module.fail_json(
+ msg="Warning! 'priority' must be an integer " "between 1 and 254",
+ priority=value,
+ )
+
+
+def main():
+ argument_spec = dict(
+ group=dict(required=True, type="str"),
+ interface=dict(required=True),
+ interval=dict(required=False, type="str"),
+ priority=dict(required=False, type="str"),
+ preempt=dict(required=False, type="bool"),
+ vip=dict(required=False, type="str"),
+ admin_state=dict(
+ required=False,
+ type="str",
+ choices=["shutdown", "no shutdown", "default"],
+ default="shutdown",
+ ),
+ authentication=dict(required=False, type="str", no_log=True),
+ state=dict(choices=["absent", "present"], required=False, default="present"),
+ )
+ module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)
+
+ warnings = list()
+ results = {"changed": False, "commands": [], "warnings": warnings}
+
+ state = module.params["state"]
+ interface = module.params["interface"].lower()
+ group = module.params["group"]
+ priority = module.params["priority"]
+ interval = module.params["interval"]
+ preempt = module.params["preempt"]
+ vip = module.params["vip"]
+ authentication = module.params["authentication"]
+ admin_state = module.params["admin_state"]
+
+ device_info = get_capabilities(module)
+ network_api = device_info.get("network_api", "nxapi")
+
+ if state == "present" and not vip:
+ module.fail_json(msg='the "vip" param is required when state=present')
+
+ intf_type = get_interface_type(interface)
+ if intf_type != "ethernet" and network_api == "cliconf":
+ if is_default(interface, module) == "DNE":
+ module.fail_json(
+ msg="That interface does not exist yet. Create " "it first.",
+ interface=interface,
+ )
+ if intf_type == "loopback":
+ module.fail_json(
+ msg="Loopback interfaces don't support VRRP.",
+ interface=interface,
+ )
+
+ mode, name = get_interface_mode(interface, intf_type, module)
+ if mode == "layer2":
+ module.fail_json(
+ msg="That interface is a layer2 port.\nMake it " "a layer 3 port first.",
+ interface=interface,
+ )
+
+ args = dict(
+ group=group,
+ priority=priority,
+ preempt=preempt,
+ vip=vip,
+ authentication=authentication,
+ interval=interval,
+ admin_state=admin_state,
+ )
+
+ proposed = dict((k, v) for k, v in args.items() if v is not None)
+ existing = get_existing_vrrp(interface, group, module, name)
+
+ commands = []
+
+ if state == "present":
+ delta = dict(set(proposed.items()).difference(existing.items()))
+ if delta:
+ command = get_commands_config_vrrp(delta, existing, group)
+ if command:
+ commands.append(command)
+ elif state == "absent":
+ if existing:
+ commands.append(["no vrrp {0}".format(group)])
+
+ if commands:
+ commands.insert(0, ["interface {0}".format(interface)])
+ commands = flatten_list(commands)
+ results["commands"] = commands
+ results["changed"] = True
+ if not module.check_mode:
+ load_config(module, commands)
+ if "configure" in commands:
+ commands.pop(0)
+
+ module.exit_json(**results)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_vsan.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_vsan.py
new file mode 100644
index 00000000..d95d95a9
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_vsan.py
@@ -0,0 +1,354 @@
+#!/usr/bin/python
+# Copyright: Ansible Project
+# 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
+
+
+DOCUMENTATION = """
+module: nxos_vsan
+short_description: Configuration of vsan for Cisco NXOS MDS Switches.
+description:
+- Configuration of vsan for Cisco MDS NXOS.
+version_added: 1.0.0
+author:
+- Suhas Bharadwaj (@srbharadwaj) (subharad@cisco.com)
+notes:
+- Tested against Cisco MDS NX-OS 8.4(1)
+options:
+ vsan:
+ description:
+ - List of vsan details to be added or removed
+ type: list
+ elements: dict
+ suboptions:
+ id:
+ description:
+ - Vsan id
+ required: true
+ type: int
+ name:
+ description:
+ - Name of the vsan
+ type: str
+ suspend:
+ description:
+ - suspend the vsan if True
+ type: bool
+ remove:
+ description:
+ - Removes the vsan if True
+ type: bool
+ interface:
+ description:
+ - List of vsan's interfaces to be added
+ type: list
+ elements: str
+"""
+
+EXAMPLES = """
+- name: Test that vsan module works
+ cisco.nxos.nxos_vsan:
+ vsan:
+ - id: 922
+ interface:
+ - fc1/1
+ - fc1/2
+ - port-channel 1
+ name: vsan-SAN-A
+ remove: false
+ suspend: false
+ - id: 923
+ interface:
+ - fc1/11
+ - fc1/21
+ - port-channel 2
+ name: vsan-SAN-B
+ remove: false
+ suspend: true
+ - id: 1923
+ name: vsan-SAN-Old
+ remove: true
+"""
+
+RETURN = """
+commands:
+ description: commands sent to the device
+ returned: always
+ type: list
+ sample:
+ - terminal dont-ask
+ - vsan database
+ - vsan 922 interface fc1/40
+ - vsan 922 interface port-channel 155
+ - no terminal dont-ask
+"""
+
+import re
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ load_config,
+ run_commands,
+)
+
+
+__metaclass__ = type
+
+
+class Vsan(object):
+ def __init__(self, vsanid):
+ self.vsanid = vsanid
+ self.vsanname = None
+ self.vsanstate = None
+ self.vsanoperstate = None
+ self.vsaninterfaces = []
+
+
+class GetVsanInfoFromSwitch(object):
+ """docstring for GetVsanInfoFromSwitch"""
+
+ def __init__(self, module):
+ self.module = module
+ self.vsaninfo = {}
+ self.processShowVsan()
+ self.processShowVsanMembership()
+
+ def execute_show_vsan_cmd(self):
+ output = execute_show_command("show vsan", self.module)[0]
+ return output
+
+ def execute_show_vsan_mem_cmd(self):
+ output = execute_show_command("show vsan membership", self.module)[0]
+ return output
+
+ def processShowVsan(self):
+ patv = r"^vsan\s+(\d+)\s+information"
+ patnamestate = "name:(.*)state:(.*)"
+ patoperstate = "operational state:(.*)"
+
+ output = self.execute_show_vsan_cmd().split("\n")
+ for o in output:
+ z = re.match(patv, o.strip())
+ if z:
+ v = z.group(1).strip()
+ self.vsaninfo[v] = Vsan(v)
+
+ z1 = re.match(patnamestate, o.strip())
+ if z1:
+ n = z1.group(1).strip()
+ s = z1.group(2).strip()
+ self.vsaninfo[v].vsanname = n
+ self.vsaninfo[v].vsanstate = s
+
+ z2 = re.match(patoperstate, o.strip())
+ if z2:
+ oper = z2.group(1).strip()
+ self.vsaninfo[v].vsanoperstate = oper
+
+ # 4094/4079 vsan is always present
+ self.vsaninfo["4079"] = Vsan("4079")
+ self.vsaninfo["4094"] = Vsan("4094")
+
+ def processShowVsanMembership(self):
+ patv = r"^vsan\s+(\d+).*"
+ output = self.execute_show_vsan_mem_cmd().split("\n")
+ memlist = []
+ v = None
+ for o in output:
+ z = re.match(patv, o.strip())
+ if z:
+ if v is not None:
+ self.vsaninfo[v].vsaninterfaces = memlist
+ memlist = []
+ v = z.group(1)
+ if "interfaces" not in o:
+ llist = o.strip().split()
+ memlist = memlist + llist
+ self.vsaninfo[v].vsaninterfaces = memlist
+
+ def getVsanInfoObjects(self):
+ return self.vsaninfo
+
+
+def execute_show_command(command, module, command_type="cli_show"):
+ output = "text"
+ commands = [{"command": command, "output": output}]
+ return run_commands(module, commands)
+
+
+def flatten_list(command_lists):
+ flat_command_list = []
+ for command in command_lists:
+ if isinstance(command, list):
+ flat_command_list.extend(command)
+ else:
+ flat_command_list.append(command)
+ return flat_command_list
+
+
+def main():
+ vsan_element_spec = dict(
+ id=dict(required=True, type="int"),
+ name=dict(type="str"),
+ remove=dict(type="bool"),
+ suspend=dict(type="bool"),
+ interface=dict(type="list", elements="str"),
+ )
+
+ argument_spec = dict(vsan=dict(type="list", elements="dict", options=vsan_element_spec))
+
+ module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)
+ warnings = list()
+ messages = list()
+ commands_executed = list()
+ result = {"changed": False}
+
+ obj = GetVsanInfoFromSwitch(module)
+ dictSwVsanObjs = obj.getVsanInfoObjects()
+
+ commands = []
+ vsan_list = module.params["vsan"]
+
+ for eachvsan in vsan_list:
+ vsanid = str(eachvsan["id"])
+ vsanname = eachvsan["name"]
+ vsanremove = eachvsan["remove"]
+ vsansuspend = eachvsan["suspend"]
+ vsaninterface_list = eachvsan["interface"]
+
+ if int(vsanid) < 1 or int(vsanid) >= 4095:
+ module.fail_json(
+ msg=vsanid + " - This is an invalid vsan. Supported vsan range is 1-4094",
+ )
+
+ if vsanid in dictSwVsanObjs.keys():
+ sw_vsanid = vsanid
+ sw_vsanname = dictSwVsanObjs[vsanid].vsanname
+ sw_vsanstate = dictSwVsanObjs[vsanid].vsanstate
+ sw_vsaninterfaces = dictSwVsanObjs[vsanid].vsaninterfaces
+ else:
+ sw_vsanid = None
+ sw_vsanname = None
+ sw_vsanstate = None
+ sw_vsaninterfaces = []
+
+ if vsanremove:
+ # Negative case:
+ if vsanid == "4079" or vsanid == "4094":
+ messages.append(str(vsanid) + " is a reserved vsan, hence cannot be removed")
+ continue
+ if vsanid == sw_vsanid:
+ commands.append("no vsan " + str(vsanid))
+ messages.append("deleting the vsan " + str(vsanid))
+ else:
+ messages.append(
+ "There is no vsan "
+ + str(vsanid)
+ + " present in the switch. Hence there is nothing to delete",
+ )
+ continue
+ else:
+ # Negative case:
+ if vsanid == "4079" or vsanid == "4094":
+ messages.append(
+ str(vsanid) + " is a reserved vsan, and always present on the switch",
+ )
+ else:
+ if vsanid == sw_vsanid:
+ messages.append(
+ "There is already a vsan "
+ + str(vsanid)
+ + " present in the switch. Hence there is nothing to configure",
+ )
+ else:
+ commands.append("vsan " + str(vsanid))
+ messages.append("creating vsan " + str(vsanid))
+
+ if vsanname is not None:
+ # Negative case:
+ if vsanid == "4079" or vsanid == "4094":
+ messages.append(str(vsanid) + " is a reserved vsan, and cannot be renamed")
+ else:
+ if vsanname == sw_vsanname:
+ messages.append(
+ "There is already a vsan "
+ + str(vsanid)
+ + " present in the switch, which has the name "
+ + vsanname
+ + " Hence there is nothing to configure",
+ )
+ else:
+ commands.append("vsan " + str(vsanid) + " name " + vsanname)
+ messages.append("setting vsan name to " + vsanname + " for vsan " + str(vsanid))
+
+ if vsansuspend:
+ # Negative case:
+ if vsanid == "4079" or vsanid == "4094":
+ messages.append(str(vsanid) + " is a reserved vsan, and cannot be suspended")
+ else:
+ if sw_vsanstate == "suspended":
+ messages.append(
+ "There is already a vsan "
+ + str(vsanid)
+ + " present in the switch, which is in suspended state ",
+ )
+ else:
+ commands.append("vsan " + str(vsanid) + " suspend")
+ messages.append("suspending the vsan " + str(vsanid))
+ else:
+ if sw_vsanstate == "active":
+ messages.append(
+ "There is already a vsan "
+ + str(vsanid)
+ + " present in the switch, which is in active state ",
+ )
+ else:
+ commands.append("no vsan " + str(vsanid) + " suspend")
+ messages.append("no suspending the vsan " + str(vsanid))
+
+ if vsaninterface_list is not None:
+ for each_interface_name in vsaninterface_list:
+ # For fcip,port-channel,vfc-port-channel need to remove the
+ # extra space to compare
+ temp = re.sub(" +", "", each_interface_name)
+ if temp in sw_vsaninterfaces:
+ messages.append(
+ each_interface_name
+ + " is already present in the vsan "
+ + str(vsanid)
+ + " interface list",
+ )
+ else:
+ commands.append("vsan " + str(vsanid) + " interface " + each_interface_name)
+ messages.append(
+ "adding interface " + each_interface_name + " to vsan " + str(vsanid),
+ )
+
+ if len(commands) != 0:
+ commands = ["terminal dont-ask"] + ["vsan database"] + commands + ["no terminal dont-ask"]
+
+ cmds = flatten_list(commands)
+ commands_executed = cmds
+
+ if commands_executed:
+ if module.check_mode:
+ module.exit_json(
+ changed=False,
+ commands=commands_executed,
+ msg="Check Mode: No cmds issued to the hosts",
+ )
+ else:
+ result["changed"] = True
+ load_config(module, commands_executed)
+
+ result["messages"] = messages
+ result["commands"] = commands_executed
+ result["warnings"] = warnings
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_vtp_domain.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_vtp_domain.py
new file mode 100644
index 00000000..d1e3c39f
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_vtp_domain.py
@@ -0,0 +1,215 @@
+#!/usr/bin/python
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_vtp_domain
+extends_documentation_fragment:
+- cisco.nxos.nxos
+short_description: Manages VTP domain configuration.
+description:
+- Manages VTP domain configuration.
+version_added: 1.0.0
+author:
+- Gabriele Gerbino (@GGabriele)
+notes:
+- Tested against NXOSv 7.3.(0)D1(1) on VIRL
+- Unsupported for Cisco MDS
+- VTP feature must be active on the device to use this module.
+- This module is used to manage only VTP domain names.
+- VTP domain names are case-sensible.
+- If it's never been configured before, VTP version is set to 1 by default. Otherwise,
+ it leaves the previous configured version untouched. Use M(cisco.nxos.nxos_vtp_version)
+ to change it.
+- Use this in combination with M(cisco.nxos.nxos_vtp_password) and M(cisco.nxos.nxos_vtp_version)
+ to fully manage VTP operations.
+options:
+ domain:
+ description:
+ - VTP domain name.
+ required: true
+ type: str
+"""
+
+EXAMPLES = """
+# ENSURE VTP DOMAIN IS CONFIGURED
+- cisco.nxos.nxos_vtp_domain:
+ domain: ntc
+ host: '{{ inventory_hostname }}'
+ username: '{{ un }}'
+ password: '{{ pwd }}'
+"""
+
+
+RETURN = """
+proposed:
+ description: k/v pairs of parameters passed into module
+ returned: always
+ type: dict
+ sample: {"domain": "ntc"}
+existing:
+ description:
+ - k/v pairs of existing vtp domain
+ returned: always
+ type: dict
+ sample: {"domain": "testing", "version": "2", "vtp_password": "password"}
+end_state:
+ description: k/v pairs of vtp domain after module execution
+ returned: always
+ type: dict
+ sample: {"domain": "ntc", "version": "2", "vtp_password": "password"}
+updates:
+ description: command sent to the device
+ returned: always
+ type: list
+ sample: ["vtp domain ntc"]
+changed:
+ description: check to see if a change was made on the device
+ returned: always
+ type: bool
+ sample: true
+"""
+
+
+import re
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ get_capabilities,
+ load_config,
+ run_commands,
+)
+
+
+def execute_show_command(command, module, output="json"):
+ cmds = [{"command": command, "output": output}]
+ body = run_commands(module, cmds)
+ return body
+
+
+def flatten_list(command_lists):
+ flat_command_list = []
+ for command in command_lists:
+ if isinstance(command, list):
+ flat_command_list.extend(command)
+ else:
+ flat_command_list.append(command)
+ return flat_command_list
+
+
+def get_vtp_config(module):
+ command = "show vtp status"
+ body = execute_show_command(command, module, "text")[0]
+ vtp_parsed = {}
+
+ if body:
+ version_regex = r".*VTP version running\s+:\s+(?P<version>\d).*"
+ domain_regex = r".*VTP Domain Name\s+:\s+(?P<domain>\S+).*"
+
+ try:
+ match_version = re.match(version_regex, body, re.DOTALL)
+ version = match_version.groupdict()["version"]
+ except AttributeError:
+ version = ""
+
+ try:
+ match_domain = re.match(domain_regex, body, re.DOTALL)
+ domain = match_domain.groupdict()["domain"]
+ except AttributeError:
+ domain = ""
+
+ if domain and version:
+ vtp_parsed["domain"] = domain
+ vtp_parsed["version"] = version
+ vtp_parsed["vtp_password"] = get_vtp_password(module)
+
+ return vtp_parsed
+
+
+def get_vtp_password(module):
+ command = "show vtp password"
+ output = "json"
+ cap = get_capabilities(module)["device_info"]["network_os_model"]
+ if re.search(r"Nexus 6", cap):
+ output = "text"
+
+ body = execute_show_command(command, module, output)[0]
+
+ if output == "json":
+ password = body.get("passwd", "")
+ else:
+ password = ""
+ rp = r"VTP Password: (\S+)"
+ mo = re.search(rp, body)
+ if mo:
+ password = mo.group(1)
+
+ return str(password)
+
+
+def main():
+ argument_spec = dict(domain=dict(type="str", required=True))
+
+ module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)
+
+ warnings = list()
+
+ domain = module.params["domain"]
+
+ existing = get_vtp_config(module)
+ end_state = existing
+
+ args = dict(domain=domain)
+
+ changed = False
+ proposed = dict((k, v) for k, v in args.items() if v is not None)
+ delta = dict(set(proposed.items()).difference(existing.items()))
+
+ commands = []
+ if delta:
+ commands.append(["vtp domain {0}".format(domain)])
+
+ cmds = flatten_list(commands)
+ if cmds:
+ if module.check_mode:
+ module.exit_json(changed=True, commands=cmds)
+ else:
+ changed = True
+ load_config(module, cmds)
+ end_state = get_vtp_config(module)
+ if "configure" in cmds:
+ cmds.pop(0)
+
+ results = {}
+ results["proposed"] = proposed
+ results["existing"] = existing
+ results["end_state"] = end_state
+ results["updates"] = cmds
+ results["changed"] = changed
+ results["warnings"] = warnings
+
+ module.exit_json(**results)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_vtp_password.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_vtp_password.py
new file mode 100644
index 00000000..c6e9cabd
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_vtp_password.py
@@ -0,0 +1,277 @@
+#!/usr/bin/python
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_vtp_password
+extends_documentation_fragment:
+- cisco.nxos.nxos
+short_description: Manages VTP password configuration.
+description:
+- Manages VTP password configuration.
+version_added: 1.0.0
+author:
+- Gabriele Gerbino (@GGabriele)
+notes:
+- Tested against NXOSv 7.3.(0)D1(1) on VIRL
+- Unsupported for Cisco MDS
+- VTP feature must be active on the device to use this module.
+- This module is used to manage only VTP passwords.
+- Use this in combination with M(cisco.nxos.nxos_vtp_domain) and M(cisco.nxos.nxos_vtp_version) to fully
+ manage VTP operations.
+- You can set/remove password only if a VTP domain already exist.
+- If C(state=absent) and no C(vtp_password) is provided, it remove the current VTP
+ password.
+- If C(state=absent) and C(vtp_password) is provided, the proposed C(vtp_password)
+ has to match the existing one in order to remove it.
+options:
+ vtp_password:
+ description:
+ - VTP password
+ type: str
+ state:
+ description:
+ - Manage the state of the resource
+ default: present
+ choices:
+ - present
+ - absent
+ type: str
+"""
+
+EXAMPLES = """
+# ENSURE VTP PASSWORD IS SET
+- cisco.nxos.nxos_vtp_password:
+ state: present
+ host: '{{ inventory_hostname }}'
+ username: '{{ un }}'
+ password: '{{ pwd }}'
+
+# ENSURE VTP PASSWORD IS REMOVED
+- cisco.nxos.nxos_vtp_password:
+ state: absent
+ host: '{{ inventory_hostname }}'
+ username: '{{ un }}'
+ password: '{{ pwd }}'
+"""
+
+RETURN = """
+proposed:
+ description: k/v pairs of parameters passed into module
+ returned: always
+ type: dict
+ sample: {"vtp_password": "new_ntc"}
+existing:
+ description:
+ - k/v pairs of existing vtp
+ returned: always
+ type: dict
+ sample: {"domain": "ntc", "version": "1", "vtp_password": "ntc"}
+end_state:
+ description: k/v pairs of vtp after module execution
+ returned: always
+ type: dict
+ sample: {"domain": "ntc", "version": "1", "vtp_password": "new_ntc"}
+updates:
+ description: command sent to the device
+ returned: always
+ type: list
+ sample: ["vtp password new_ntc"]
+changed:
+ description: check to see if a change was made on the device
+ returned: always
+ type: bool
+ sample: true
+"""
+
+import re
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ get_capabilities,
+ load_config,
+ run_commands,
+)
+
+
+def execute_show_command(command, module, output="json"):
+ cmds = [{"command": command, "output": output}]
+ body = run_commands(module, cmds)
+ return body
+
+
+def flatten_list(command_lists):
+ flat_command_list = []
+ for command in command_lists:
+ if isinstance(command, list):
+ flat_command_list.extend(command)
+ else:
+ flat_command_list.append(command)
+ return flat_command_list
+
+
+def apply_key_map(key_map, table):
+ new_dict = {}
+ for key, value in table.items():
+ new_key = key_map.get(key)
+ if new_key:
+ value = table.get(key)
+ if value:
+ new_dict[new_key] = str(value)
+ else:
+ new_dict[new_key] = value
+ return new_dict
+
+
+def get_vtp_config(module):
+ command = "show vtp status"
+
+ body = execute_show_command(command, module, "text")[0]
+ vtp_parsed = {}
+
+ if body:
+ version_regex = r".*VTP version running\s+:\s+(?P<version>\d).*"
+ domain_regex = r".*VTP Domain Name\s+:\s+(?P<domain>\S+).*"
+
+ try:
+ match_version = re.match(version_regex, body, re.DOTALL)
+ version = match_version.groupdict()["version"]
+ except AttributeError:
+ version = ""
+
+ try:
+ match_domain = re.match(domain_regex, body, re.DOTALL)
+ domain = match_domain.groupdict()["domain"]
+ except AttributeError:
+ domain = ""
+
+ if domain and version:
+ vtp_parsed["domain"] = domain
+ vtp_parsed["version"] = version
+ vtp_parsed["vtp_password"] = get_vtp_password(module)
+
+ return vtp_parsed
+
+
+def get_vtp_password(module):
+ command = "show vtp password"
+ output = "json"
+ cap = get_capabilities(module)["device_info"]["network_os_model"]
+ if re.search(r"Nexus 6", cap):
+ output = "text"
+
+ body = execute_show_command(command, module, output)[0]
+
+ if output == "json":
+ password = body.get("passwd", "")
+ else:
+ password = ""
+ rp = r"VTP Password: (\S+)"
+ mo = re.search(rp, body)
+ if mo:
+ password = mo.group(1)
+
+ return str(password)
+
+
+def main():
+ argument_spec = dict(
+ vtp_password=dict(type="str", no_log=True),
+ state=dict(choices=["absent", "present"], default="present"),
+ )
+
+ module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)
+
+ warnings = list()
+
+ vtp_password = module.params["vtp_password"] or None
+ state = module.params["state"]
+
+ existing = get_vtp_config(module)
+ end_state = existing
+
+ args = dict(vtp_password=vtp_password)
+
+ changed = False
+ proposed = dict((k, v) for k, v in args.items() if v is not None)
+ delta = dict(set(proposed.items()).difference(existing.items()))
+
+ commands = []
+ if state == "absent":
+ # if vtp_password is not set, some devices returns '\\' or the string 'None'
+ if (
+ not existing["vtp_password"]
+ or existing["vtp_password"] == "\\"
+ or existing["vtp_password"] == "None"
+ ):
+ pass
+ elif vtp_password is not None:
+ if existing["vtp_password"] == proposed["vtp_password"]:
+ commands.append(["no vtp password"])
+ else:
+ module.fail_json(
+ msg="Proposed vtp password doesn't match "
+ "current vtp password. It cannot be "
+ "removed when state=absent. If you are "
+ "trying to change the vtp password, use "
+ "state=present.",
+ )
+ else:
+ if not existing.get("domain"):
+ module.fail_json(msg="Cannot remove a vtp password " "before vtp domain is set.")
+
+ elif existing["vtp_password"] != ("\\"):
+ commands.append(["no vtp password"])
+
+ elif state == "present":
+ if delta:
+ if not existing.get("domain"):
+ module.fail_json(msg="Cannot set vtp password " "before vtp domain is set.")
+
+ else:
+ commands.append(["vtp password {0}".format(vtp_password)])
+
+ cmds = flatten_list(commands)
+ if cmds:
+ if module.check_mode:
+ module.exit_json(changed=True, commands=cmds)
+ else:
+ changed = True
+ load_config(module, cmds)
+ end_state = get_vtp_config(module)
+ if "configure" in cmds:
+ cmds.pop(0)
+
+ results = {}
+ results["proposed"] = proposed
+ results["existing"] = existing
+ results["end_state"] = end_state
+ results["updates"] = cmds
+ results["changed"] = changed
+ results["warnings"] = warnings
+
+ module.exit_json(**results)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_vtp_version.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_vtp_version.py
new file mode 100644
index 00000000..8c5305a0
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_vtp_version.py
@@ -0,0 +1,210 @@
+#!/usr/bin/python
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_vtp_version
+extends_documentation_fragment:
+- cisco.nxos.nxos
+short_description: Manages VTP version configuration.
+description:
+- Manages VTP version configuration.
+version_added: 1.0.0
+author:
+- Gabriele Gerbino (@GGabriele)
+notes:
+- Tested against NXOSv 7.3.(0)D1(1) on VIRL
+- Unsupported for Cisco MDS
+- VTP feature must be active on the device to use this module.
+- This module is used to manage only VTP version.
+- Use this in combination with M(cisco.nxos.nxos_vtp_password) and M(cisco.nxos.nxos_vtp_version)
+ to fully manage VTP operations.
+options:
+ version:
+ description:
+ - VTP version number.
+ required: true
+ choices:
+ - '1'
+ - '2'
+ type: str
+"""
+EXAMPLES = """
+# ENSURE VTP VERSION IS 2
+- cisco.nxos.nxos_vtp_version:
+ version: 2
+ host: '{{ inventory_hostname }}'
+ username: '{{ un }}'
+ password: '{{ pwd }}'
+"""
+
+RETURN = """
+proposed:
+ description: k/v pairs of parameters passed into module
+ returned: always
+ type: dict
+ sample: {"version": "2"}
+existing:
+ description:
+ - k/v pairs of existing vtp
+ returned: always
+ type: dict
+ sample: {"domain": "testing", "version": "1", "vtp_password": "password"}
+end_state:
+ description: k/v pairs of vtp after module execution
+ returned: always
+ type: dict
+ sample: {"domain": "testing", "version": "2", "vtp_password": "password"}
+updates:
+ description: command sent to the device
+ returned: always
+ type: list
+ sample: ["vtp version 2"]
+changed:
+ description: check to see if a change was made on the device
+ returned: always
+ type: bool
+ sample: true
+"""
+import re
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ get_capabilities,
+ load_config,
+ run_commands,
+)
+
+
+def execute_show_command(command, module, output="json"):
+ cmds = [{"command": command, "output": output}]
+ body = run_commands(module, cmds)
+ return body
+
+
+def flatten_list(command_lists):
+ flat_command_list = []
+ for command in command_lists:
+ if isinstance(command, list):
+ flat_command_list.extend(command)
+ else:
+ flat_command_list.append(command)
+ return flat_command_list
+
+
+def get_vtp_config(module):
+ command = "show vtp status"
+ body = execute_show_command(command, module, "text")[0]
+ vtp_parsed = {}
+
+ if body:
+ version_regex = r".*VTP version running\s+:\s+(?P<version>\d).*"
+ domain_regex = r".*VTP Domain Name\s+:\s+(?P<domain>\S+).*"
+
+ try:
+ match_version = re.match(version_regex, body, re.DOTALL)
+ version = match_version.groupdict()["version"]
+ except AttributeError:
+ version = ""
+
+ try:
+ match_domain = re.match(domain_regex, body, re.DOTALL)
+ domain = match_domain.groupdict()["domain"]
+ except AttributeError:
+ domain = ""
+
+ if domain and version:
+ vtp_parsed["domain"] = domain
+ vtp_parsed["version"] = version
+ vtp_parsed["vtp_password"] = get_vtp_password(module)
+
+ return vtp_parsed
+
+
+def get_vtp_password(module):
+ command = "show vtp password"
+ output = "json"
+ cap = get_capabilities(module)["device_info"]["network_os_model"]
+ if re.search(r"Nexus 6", cap):
+ output = "text"
+
+ body = execute_show_command(command, module, output)[0]
+
+ if output == "json":
+ password = body.get("passwd", "")
+ else:
+ password = ""
+ rp = r"VTP Password: (\S+)"
+ mo = re.search(rp, body)
+ if mo:
+ password = mo.group(1)
+
+ return str(password)
+
+
+def main():
+ argument_spec = dict(version=dict(type="str", choices=["1", "2"], required=True))
+
+ module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)
+
+ warnings = list()
+
+ version = module.params["version"]
+
+ existing = get_vtp_config(module)
+ end_state = existing
+
+ args = dict(version=version)
+
+ changed = False
+ proposed = dict((k, v) for k, v in args.items() if v is not None)
+ delta = dict(set(proposed.items()).difference(existing.items()))
+
+ commands = []
+ if delta:
+ commands.append(["vtp version {0}".format(version)])
+
+ cmds = flatten_list(commands)
+ if cmds:
+ if module.check_mode:
+ module.exit_json(changed=True, commands=cmds)
+ else:
+ changed = True
+ load_config(module, cmds)
+ end_state = get_vtp_config(module)
+ if "configure" in cmds:
+ cmds.pop(0)
+
+ results = {}
+ results["proposed"] = proposed
+ results["existing"] = existing
+ results["end_state"] = end_state
+ results["updates"] = cmds
+ results["changed"] = changed
+ results["warnings"] = warnings
+
+ module.exit_json(**results)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_vxlan_vtep.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_vxlan_vtep.py
new file mode 100644
index 00000000..ce311388
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_vxlan_vtep.py
@@ -0,0 +1,458 @@
+#!/usr/bin/python
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_vxlan_vtep
+extends_documentation_fragment:
+- cisco.nxos.nxos
+short_description: Manages VXLAN Network Virtualization Endpoint (NVE).
+description:
+- Manages VXLAN Network Virtualization Endpoint (NVE) overlay interface that terminates
+ VXLAN tunnels.
+version_added: 1.0.0
+author: Gabriele Gerbino (@GGabriele)
+notes:
+- Tested against NXOSv 7.3.(0)D1(1) on VIRL
+- Unsupported for Cisco MDS
+- The module is used to manage NVE properties, not to create NVE interfaces. Use M(cisco.nxos.nxos_interfaces)
+ if you wish to do so.
+- C(state=absent) removes the interface.
+- Default, where supported, restores params default value.
+options:
+ interface:
+ description:
+ - Interface name for the VXLAN Network Virtualization Endpoint.
+ required: true
+ type: str
+ description:
+ description:
+ - Description of the NVE interface.
+ type: str
+ host_reachability:
+ description:
+ - Specify mechanism for host reachability advertisement. A Boolean value of 'true'
+ indicates that BGP will be used for host reachability advertisement. A Boolean
+ value of 'false' indicates that no protocol is used for host reachability advertisement.
+ Other host reachability advertisement protocols (e.g. OpenFlow, controller, etc.) are not
+ supported.
+ type: bool
+ shutdown:
+ description:
+ - Administratively shutdown the NVE interface.
+ type: bool
+ source_interface:
+ description:
+ - Specify the loopback interface whose IP address should be used for the NVE interface.
+ type: str
+ source_interface_hold_down_time:
+ description:
+ - Suppresses advertisement of the NVE loopback address until the overlay has converged.
+ type: str
+ global_mcast_group_L3:
+ description:
+ - Global multicast IP prefix for L3 VNIs or the keyword 'default'. This is available on
+ Nexus 9000 series switches running NX-OS software release 9.2(x) or higher.
+ type: str
+ global_mcast_group_L2:
+ description:
+ - Global multicast IP prefix for L2 VNIs or the keyword 'default'. This is available on
+ Nexus 9000 series switches running NX-OS software release 9.2(x) or higher.
+ type: str
+ global_suppress_arp:
+ description:
+ - Enables ARP suppression for all VNIs. This is available on NX-OS 9K series running
+ 9.2.x or higher.
+ type: bool
+ global_ingress_replication_bgp:
+ description:
+ - Configures ingress replication protocol as bgp for all VNIs. This is available on Nexus
+ 9000 series switches running NX-OS software release 9.2(x) or higher.
+ type: bool
+ state:
+ description:
+ - Determines whether the config should be present or not on the device.
+ default: present
+ choices:
+ - present
+ - absent
+ type: str
+ multisite_border_gateway_interface:
+ description:
+ - Specify the loopback interface whose IP address should be used for the NVE
+ Multisite Border-gateway Interface. This is available on specific Nexus 9000
+ series switches running NX-OS 7.0(3)I7(x) or higher. Specify "default" to remove
+ an existing gateway config.
+ type: str
+ version_added: 1.1.0
+"""
+EXAMPLES = """
+- cisco.nxos.nxos_vxlan_vtep:
+ interface: nve1
+ description: default
+ host_reachability: true
+ source_interface: Loopback0
+ source_interface_hold_down_time: 30
+ shutdown: default
+ multisite_border_gateway_interface: Loopback0
+"""
+
+RETURN = """
+commands:
+ description: commands sent to the device
+ returned: always
+ type: list
+ sample: ["interface nve1", "source-interface loopback0",
+ "source-interface hold-down-time 30", "description simple description",
+ "shutdown", "host-reachability protocol bgp",
+ "multisite border-gateway interface loopback0"]
+"""
+
+import re
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import (
+ CustomNetworkConfig,
+)
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ get_config,
+ load_config,
+ run_commands,
+)
+
+
+BOOL_PARAMS = [
+ "shutdown",
+ "host_reachability",
+ "global_ingress_replication_bgp",
+ "global_suppress_arp",
+]
+PARAM_TO_COMMAND_KEYMAP = {
+ "description": "description",
+ "global_suppress_arp": "global suppress-arp",
+ "global_ingress_replication_bgp": "global ingress-replication protocol bgp",
+ "global_mcast_group_L3": "global mcast-group L3",
+ "global_mcast_group_L2": "global mcast-group L2",
+ "host_reachability": "host-reachability protocol bgp",
+ "interface": "interface",
+ "shutdown": "shutdown",
+ "source_interface": "source-interface",
+ "source_interface_hold_down_time": "source-interface hold-down-time",
+ "multisite_border_gateway_interface": "multisite border-gateway interface",
+}
+PARAM_TO_DEFAULT_KEYMAP = {
+ "description": False,
+ "shutdown": True,
+ "source_interface_hold_down_time": "180",
+}
+
+
+def get_value(arg, config, module):
+ if arg in BOOL_PARAMS:
+ REGEX = re.compile(r"\s+{0}\s*$".format(PARAM_TO_COMMAND_KEYMAP[arg]), re.M)
+ NO_SHUT_REGEX = re.compile(r"\s+no shutdown\s*$", re.M)
+ value = False
+ if arg == "shutdown":
+ try:
+ if NO_SHUT_REGEX.search(config):
+ value = False
+ elif REGEX.search(config):
+ value = True
+ except TypeError:
+ value = False
+ else:
+ try:
+ if REGEX.search(config):
+ value = True
+ except TypeError:
+ value = False
+ else:
+ REGEX = re.compile(
+ r"(?:{0}\s)(?P<value>.*)$".format(PARAM_TO_COMMAND_KEYMAP[arg]),
+ re.M,
+ )
+ NO_DESC_REGEX = re.compile(r"\s+{0}\s*$".format("no description"), re.M)
+ SOURCE_INTF_REGEX = re.compile(
+ r"(?:{0}\s)(?P<value>\S+)$".format(PARAM_TO_COMMAND_KEYMAP[arg]),
+ re.M,
+ )
+ value = ""
+ if arg == "description":
+ if NO_DESC_REGEX.search(config):
+ value = False
+ elif PARAM_TO_COMMAND_KEYMAP[arg] in config:
+ value = REGEX.search(config).group("value").strip()
+ elif arg == "source_interface":
+ for line in config.splitlines():
+ try:
+ if PARAM_TO_COMMAND_KEYMAP[arg] in config:
+ value = SOURCE_INTF_REGEX.search(config).group("value").strip()
+ break
+ except AttributeError:
+ value = ""
+ elif arg == "global_mcast_group_L2":
+ for line in config.splitlines():
+ try:
+ if "global mcast-group" in line and "L2" in line:
+ value = line.split()[2].strip()
+ break
+ except AttributeError:
+ value = ""
+ elif arg == "global_mcast_group_L3":
+ for line in config.splitlines():
+ try:
+ if "global mcast-group" in line and "L3" in line:
+ value = line.split()[2].strip()
+ break
+ except AttributeError:
+ value = ""
+ elif arg == "multisite_border_gateway_interface":
+ for line in config.splitlines():
+ try:
+ if PARAM_TO_COMMAND_KEYMAP[arg] in config:
+ value = SOURCE_INTF_REGEX.search(config).group("value").strip()
+ break
+ except AttributeError:
+ value = ""
+ else:
+ if PARAM_TO_COMMAND_KEYMAP[arg] in config:
+ value = REGEX.search(config).group("value").strip()
+ return value
+
+
+def get_existing(module, args):
+ existing = {}
+ netcfg = CustomNetworkConfig(indent=2, contents=get_config(module, flags=["all"]))
+
+ interface_string = "interface {0}".format(module.params["interface"].lower())
+ parents = [interface_string]
+ config = netcfg.get_section(parents)
+
+ if config:
+ for arg in args:
+ existing[arg] = get_value(arg, config, module)
+
+ existing["interface"] = module.params["interface"].lower()
+ else:
+ if interface_string in str(netcfg):
+ existing["interface"] = module.params["interface"].lower()
+ for arg in args:
+ existing[arg] = ""
+ return existing
+
+
+def apply_key_map(key_map, table):
+ new_dict = {}
+ for key, value in table.items():
+ new_key = key_map.get(key)
+ if new_key:
+ value = table.get(key)
+ if value:
+ new_dict[new_key] = value
+ else:
+ new_dict[new_key] = value
+ return new_dict
+
+
+def fix_commands(commands, module):
+ source_interface_command = ""
+ no_source_interface_command = ""
+ no_host_reachability_command = ""
+ host_reachability_command = ""
+
+ for command in commands:
+ if "no source-interface hold-down-time" in command:
+ pass
+ elif "source-interface hold-down-time" in command:
+ pass
+ elif "no source-interface" in command:
+ no_source_interface_command = command
+ elif "source-interface" in command:
+ source_interface_command = command
+ elif "no host-reachability" in command:
+ no_host_reachability_command = command
+ elif "host-reachability" in command:
+ host_reachability_command = command
+
+ if host_reachability_command:
+ commands.pop(commands.index(host_reachability_command))
+ commands.insert(0, host_reachability_command)
+
+ if source_interface_command:
+ commands.pop(commands.index(source_interface_command))
+ commands.insert(0, source_interface_command)
+
+ if no_host_reachability_command:
+ commands.pop(commands.index(no_host_reachability_command))
+ commands.append(no_host_reachability_command)
+
+ if no_source_interface_command:
+ commands.pop(commands.index(no_source_interface_command))
+ commands.append(no_source_interface_command)
+
+ commands.insert(0, "terminal dont-ask")
+ return commands
+
+
+def gsa_tcam_check(module):
+ """
+ global_suppress_arp is an N9k-only command that requires TCAM resources.
+ This method checks the current TCAM allocation.
+ Note that changing tcam_size requires a switch reboot to take effect.
+ """
+ cmds = [{"command": "show hardware access-list tcam region", "output": "json"}]
+ body = run_commands(module, cmds)
+ if body:
+ tcam_region = body[0]["TCAM_Region"]["TABLE_Sizes"]["ROW_Sizes"]
+ if bool(
+ [
+ i
+ for i in tcam_region
+ if i["type"].startswith("Ingress ARP-Ether ACL") and i["tcam_size"] == "0"
+ ],
+ ):
+ msg = (
+ "'show hardware access-list tcam region' indicates 'ARP-Ether' tcam size is 0 (no allocated resources). "
+ + "'global_suppress_arp' will be rejected by device."
+ )
+ module.fail_json(msg=msg)
+
+
+def state_present(module, existing, proposed, candidate):
+ commands = list()
+ proposed_commands = apply_key_map(PARAM_TO_COMMAND_KEYMAP, proposed)
+ existing_commands = apply_key_map(PARAM_TO_COMMAND_KEYMAP, existing)
+ for key, value in proposed_commands.items():
+ if value is True:
+ commands.append(key)
+
+ elif value is False:
+ commands.append("no {0}".format(key))
+
+ elif value == "default":
+ if existing_commands.get(key):
+ existing_value = existing_commands.get(key)
+ if "global mcast-group" in key:
+ commands.append("no {0}".format(key))
+ else:
+ commands.append("no {0} {1}".format(key, existing_value))
+ else:
+ if key.replace(" ", "_").replace("-", "_") in BOOL_PARAMS:
+ commands.append("no {0}".format(key.lower()))
+ module.exit_json(commands=commands)
+ else:
+ if "L2" in key:
+ commands.append("global mcast-group " + value + " L2")
+ elif "L3" in key:
+ commands.append("global mcast-group " + value + " L3")
+ else:
+ command = "{0} {1}".format(key, value.lower())
+ commands.append(command)
+
+ if commands:
+ commands = fix_commands(commands, module)
+ parents = ["interface {0}".format(module.params["interface"].lower())]
+ candidate.add(commands, parents=parents)
+ else:
+ if not existing and module.params["interface"]:
+ commands = ["interface {0}".format(module.params["interface"].lower())]
+ candidate.add(commands, parents=[])
+
+
+def state_absent(module, existing, proposed, candidate):
+ commands = ["no interface {0}".format(module.params["interface"].lower())]
+ candidate.add(commands, parents=[])
+
+
+def main():
+ argument_spec = dict(
+ interface=dict(required=True, type="str"),
+ description=dict(required=False, type="str"),
+ host_reachability=dict(required=False, type="bool"),
+ global_ingress_replication_bgp=dict(required=False, type="bool"),
+ global_suppress_arp=dict(required=False, type="bool"),
+ global_mcast_group_L2=dict(required=False, type="str"),
+ global_mcast_group_L3=dict(required=False, type="str"),
+ shutdown=dict(required=False, type="bool"),
+ source_interface=dict(required=False, type="str"),
+ source_interface_hold_down_time=dict(required=False, type="str"),
+ state=dict(choices=["present", "absent"], default="present", required=False),
+ multisite_border_gateway_interface=dict(required=False, type="str"),
+ )
+
+ mutually_exclusive = [("global_ingress_replication_bgp", "global_mcast_group_L2")]
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ mutually_exclusive=mutually_exclusive,
+ supports_check_mode=True,
+ )
+
+ warnings = list()
+ result = {"changed": False, "commands": [], "warnings": warnings}
+
+ state = module.params["state"]
+
+ args = PARAM_TO_COMMAND_KEYMAP.keys()
+
+ existing = get_existing(module, args)
+ proposed_args = dict((k, v) for k, v in module.params.items() if v is not None and k in args)
+ proposed = {}
+ for key, value in proposed_args.items():
+ if key != "interface":
+ if str(value).lower() == "default":
+ value = PARAM_TO_DEFAULT_KEYMAP.get(key)
+ if value is None:
+ if key in BOOL_PARAMS:
+ value = False
+ else:
+ value = "default"
+ if str(existing.get(key)).lower() != str(value).lower():
+ proposed[key] = value
+
+ candidate = CustomNetworkConfig(indent=3)
+
+ if proposed.get("global_suppress_arp"):
+ gsa_tcam_check(module)
+ if state == "present":
+ if not existing:
+ warnings.append(
+ "The proposed NVE interface did not exist. "
+ "It's recommended to use nxos_interfaces to create "
+ "all logical interfaces.",
+ )
+ state_present(module, existing, proposed, candidate)
+ elif state == "absent" and existing:
+ state_absent(module, existing, proposed, candidate)
+
+ if candidate:
+ candidate = candidate.items_text()
+ result["commands"] = candidate
+ result["changed"] = True
+ load_config(module, candidate)
+
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_vxlan_vtep_vni.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_vxlan_vtep_vni.py
new file mode 100644
index 00000000..d58bd6c9
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_vxlan_vtep_vni.py
@@ -0,0 +1,452 @@
+#!/usr/bin/python
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_vxlan_vtep_vni
+extends_documentation_fragment:
+- cisco.nxos.nxos
+short_description: Creates a Virtual Network Identifier member (VNI)
+description:
+- Creates a Virtual Network Identifier member (VNI) for an NVE overlay interface.
+version_added: 1.0.0
+author: Gabriele Gerbino (@GGabriele)
+notes:
+- Tested against NXOSv 7.3.(0)D1(1) on VIRL
+- Unsupported for Cisco MDS
+- default, where supported, restores params default value.
+options:
+ interface:
+ description:
+ - Interface name for the VXLAN Network Virtualization Endpoint.
+ required: true
+ type: str
+ vni:
+ description:
+ - ID of the Virtual Network Identifier.
+ required: true
+ type: str
+ assoc_vrf:
+ description:
+ - This attribute is used to identify and separate processing VNIs that are associated
+ with a VRF and used for routing. The VRF and VNI specified with this command
+ must match the configuration of the VNI under the VRF.
+ type: bool
+ ingress_replication:
+ description:
+ - Specifies mechanism for host reachability advertisement.
+ choices:
+ - bgp
+ - static
+ - default
+ type: str
+ multicast_group:
+ description:
+ - The multicast group (range) of the VNI. Valid values are string and keyword
+ 'default'.
+ type: str
+ peer_list:
+ description:
+ - Set the ingress-replication static peer list. Valid values are an array, a space-separated
+ string of ip addresses, or the keyword 'default'.
+ type: list
+ elements: str
+ suppress_arp:
+ description:
+ - Suppress arp under layer 2 VNI.
+ type: bool
+ suppress_arp_disable:
+ description:
+ - Overrides the global ARP suppression config. This is available on NX-OS 9K series
+ running 9.2.x or higher.
+ type: bool
+ state:
+ description:
+ - Determines whether the config should be present or not on the device.
+ default: present
+ choices:
+ - present
+ - absent
+ type: str
+ multisite_ingress_replication:
+ description:
+ - Enables multisite ingress replication.
+ choices:
+ - disable
+ - enable
+ - optimized
+ type: str
+ version_added: 1.1.0
+"""
+EXAMPLES = """
+- cisco.nxos.nxos_vxlan_vtep_vni:
+ interface: nve1
+ vni: 6000
+ ingress_replication: default
+ multisite_ingress_replication: enable
+"""
+
+RETURN = """
+commands:
+ description: commands sent to the device
+ returned: always
+ type: list
+ sample: ["interface nve1", "member vni 6000", "multisite ingress-replication"]
+"""
+
+import re
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import (
+ CustomNetworkConfig,
+)
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ get_config,
+ load_config,
+)
+
+
+BOOL_PARAMS = ["assoc_vrf", "suppress_arp", "suppress_arp_disable"]
+PARAM_TO_DEFAULT_KEYMAP = {
+ "multicast_group": "",
+ "peer_list": [],
+ "ingress_replication": "",
+}
+PARAM_TO_COMMAND_KEYMAP = {
+ "assoc_vrf": "associate-vrf",
+ "interface": "interface",
+ "vni": "member vni",
+ "ingress_replication": "ingress-replication protocol",
+ "multicast_group": "mcast-group",
+ "peer_list": "peer-ip",
+ "suppress_arp": "suppress-arp",
+ "suppress_arp_disable": "suppress-arp disable",
+ "multisite_ingress_replication": "multisite ingress-replication",
+}
+
+
+def get_value(arg, config, module):
+ command = PARAM_TO_COMMAND_KEYMAP[arg]
+ command_val_re = re.compile(r"(?:{0}\s)(?P<value>.*)$".format(command), re.M)
+
+ if arg in BOOL_PARAMS:
+ command_re = re.compile(r"\s+{0}\s*$".format(command), re.M)
+ value = False
+ if command_re.search(config):
+ value = True
+ elif arg == "peer_list":
+ has_command_val = command_val_re.findall(config, re.M)
+ value = []
+ if has_command_val:
+ value = has_command_val
+ elif arg == "multisite_ingress_replication":
+ has_command = re.search(r"^\s+{0}$".format(command), config, re.M)
+ has_command_val = command_val_re.search(config, re.M)
+ value = "disable"
+ if has_command:
+ value = "enable"
+ elif has_command_val:
+ value = "optimized"
+ else:
+ value = ""
+ has_command_val = command_val_re.search(config, re.M)
+ if has_command_val:
+ value = has_command_val.group("value")
+ return value
+
+
+def check_interface(module, netcfg):
+ config = str(netcfg)
+
+ has_interface = re.search(r"(?:interface nve)(?P<value>.*)$", config, re.M)
+ value = ""
+ if has_interface:
+ value = "nve{0}".format(has_interface.group("value"))
+
+ return value
+
+
+def get_existing(module, args):
+ existing = {}
+ netcfg = CustomNetworkConfig(indent=2, contents=get_config(module))
+
+ interface_exist = check_interface(module, netcfg)
+ if interface_exist:
+ parents = ["interface {0}".format(interface_exist)]
+ temp_config = netcfg.get_section(parents)
+
+ if "member vni {0} associate-vrf".format(module.params["vni"]) in temp_config:
+ parents.append("member vni {0} associate-vrf".format(module.params["vni"]))
+ config = netcfg.get_section(parents)
+ elif "member vni {0}".format(module.params["vni"]) in temp_config:
+ parents.append("member vni {0}".format(module.params["vni"]))
+ config = netcfg.get_section(parents)
+ else:
+ config = {}
+
+ if config:
+ for arg in args:
+ if arg not in ["interface", "vni"]:
+ existing[arg] = get_value(arg, config, module)
+ existing["interface"] = interface_exist
+ existing["vni"] = module.params["vni"]
+
+ return existing, interface_exist
+
+
+def apply_key_map(key_map, table):
+ new_dict = {}
+ for key, value in table.items():
+ new_key = key_map.get(key)
+ if new_key:
+ new_dict[new_key] = value
+ return new_dict
+
+
+def state_present(module, existing, proposed, candidate):
+ commands = list()
+ proposed_commands = apply_key_map(PARAM_TO_COMMAND_KEYMAP, proposed)
+ existing_commands = apply_key_map(PARAM_TO_COMMAND_KEYMAP, existing)
+
+ for key, value in proposed_commands.items():
+ if key == "associate-vrf":
+ command = "member vni {0} {1}".format(module.params["vni"], key)
+ if not value:
+ command = "no {0}".format(command)
+ commands.append(command)
+
+ elif key == "peer-ip" and value != []:
+ for peer in value:
+ commands.append("{0} {1}".format(key, peer))
+
+ elif key == "mcast-group" and value != existing_commands.get(key):
+ commands.append("no {0}".format(key))
+ vni_command = "member vni {0}".format(module.params["vni"])
+ if vni_command not in commands:
+ commands.append("member vni {0}".format(module.params["vni"]))
+ if value != PARAM_TO_DEFAULT_KEYMAP.get("multicast_group", "default"):
+ commands.append("{0} {1}".format(key, value))
+
+ elif key == "ingress-replication protocol" and value != existing_commands.get(key):
+ evalue = existing_commands.get(key)
+ dvalue = PARAM_TO_DEFAULT_KEYMAP.get("ingress_replication", "default")
+ if value != dvalue:
+ if evalue and evalue != dvalue:
+ commands.append("no {0} {1}".format(key, evalue))
+ commands.append("{0} {1}".format(key, value))
+ else:
+ if evalue:
+ commands.append("no {0} {1}".format(key, evalue))
+
+ elif value is True:
+ commands.append(key)
+ elif value is False:
+ commands.append("no {0}".format(key))
+ elif value == "default" or value == []:
+ if existing_commands.get(key):
+ existing_value = existing_commands.get(key)
+ if key == "peer-ip":
+ for peer in existing_value:
+ commands.append("no {0} {1}".format(key, peer))
+ else:
+ commands.append("no {0} {1}".format(key, existing_value))
+ else:
+ if key.replace(" ", "_").replace("-", "_") in BOOL_PARAMS:
+ commands.append("no {0}".format(key.lower()))
+ elif key == "multisite ingress-replication" and value != existing_commands.get(key):
+ vni_command = "member vni {0}".format(module.params["vni"])
+ if vni_command not in commands:
+ commands.append("member vni {0}".format(module.params["vni"]))
+ if value == "disable":
+ command = "no {0}".format(key)
+ commands.append(command)
+ elif value == "enable":
+ command = "{0}".format(key)
+ commands.append(command)
+ elif value == "optimized":
+ command = "{0} {1}".format(key, value)
+ commands.append(command)
+ else:
+ command = "{0} {1}".format(key, value.lower())
+ commands.append(command)
+
+ if commands:
+ vni_command = "member vni {0}".format(module.params["vni"])
+ ingress_replications_command = "ingress-replication protocol static"
+ ingress_replicationb_command = "ingress-replication protocol bgp"
+ ingress_replicationns_command = "no ingress-replication protocol static"
+ ingress_replicationnb_command = "no ingress-replication protocol bgp"
+ interface_command = "interface {0}".format(module.params["interface"])
+
+ if any(
+ c in commands
+ for c in (
+ ingress_replications_command,
+ ingress_replicationb_command,
+ ingress_replicationnb_command,
+ ingress_replicationns_command,
+ )
+ ):
+ static_level_cmds = [cmd for cmd in commands if "peer" in cmd]
+ parents = [interface_command, vni_command]
+ commands = [cmd for cmd in commands if "peer" not in cmd]
+ for cmd in commands:
+ parents.append(cmd)
+ candidate.add(static_level_cmds, parents=parents)
+
+ elif "peer-ip" in commands[0]:
+ static_level_cmds = list(commands)
+ parents = [
+ interface_command,
+ vni_command,
+ ingress_replications_command,
+ ]
+ candidate.add(static_level_cmds, parents=parents)
+
+ if vni_command in commands:
+ parents = [interface_command]
+ commands.remove(vni_command)
+ if module.params["assoc_vrf"] is None:
+ parents.append(vni_command)
+ candidate.add(commands, parents=parents)
+
+
+def state_absent(module, existing, proposed, candidate):
+ if existing["assoc_vrf"]:
+ commands = ["no member vni {0} associate-vrf".format(module.params["vni"])]
+ else:
+ commands = ["no member vni {0}".format(module.params["vni"])]
+ parents = ["interface {0}".format(module.params["interface"])]
+ candidate.add(commands, parents=parents)
+
+
+def main():
+ argument_spec = dict(
+ interface=dict(required=True, type="str"),
+ vni=dict(required=True, type="str"),
+ assoc_vrf=dict(required=False, type="bool"),
+ multicast_group=dict(required=False, type="str"),
+ peer_list=dict(required=False, type="list", elements="str"),
+ suppress_arp=dict(required=False, type="bool"),
+ suppress_arp_disable=dict(required=False, type="bool"),
+ ingress_replication=dict(required=False, type="str", choices=["bgp", "static", "default"]),
+ state=dict(choices=["present", "absent"], default="present", required=False),
+ multisite_ingress_replication=dict(
+ required=False,
+ type="str",
+ choices=["enable", "optimized", "disable"],
+ ),
+ )
+
+ mutually_exclusive = [
+ ("suppress_arp", "suppress_arp_disable"),
+ ("assoc_vrf", "multicast_group"),
+ ("assoc_vrf", "suppress_arp"),
+ ("assoc_vrf", "suppress_arp_disable"),
+ ("assoc_vrf", "ingress_replication"),
+ ]
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ mutually_exclusive=mutually_exclusive,
+ supports_check_mode=True,
+ )
+
+ warnings = list()
+ result = {"changed": False, "commands": [], "warnings": warnings}
+
+ if module.params["peer_list"]:
+ if (
+ module.params["peer_list"][0] != "default"
+ and module.params["ingress_replication"] != "static"
+ ):
+ module.fail_json(
+ msg="ingress_replication=static is required " "when using peer_list param",
+ )
+ else:
+ peer_list = module.params["peer_list"]
+ if peer_list[0] == "default":
+ module.params["peer_list"] = "default"
+ else:
+ stripped_peer_list = list(map(str.strip, peer_list))
+ module.params["peer_list"] = stripped_peer_list
+
+ if (
+ module.params["multisite_ingress_replication"] == "enable"
+ or module.params["multisite_ingress_replication"] == "optimized"
+ ):
+ if module.params["ingress_replication"] == "static":
+ module.fail_json(
+ msg="ingress_replication=static is not allowed "
+ "when using multisite_ingress_replication",
+ )
+
+ state = module.params["state"]
+ args = PARAM_TO_COMMAND_KEYMAP.keys()
+ existing, interface_exist = get_existing(module, args)
+
+ if state == "present":
+ if not interface_exist:
+ module.fail_json(
+ msg="The proposed NVE interface does not exist. Use nxos_interface to create it first.",
+ )
+ elif interface_exist != module.params["interface"]:
+ module.fail_json(msg="Only 1 NVE interface is allowed on the switch.")
+ elif state == "absent":
+ if interface_exist != module.params["interface"]:
+ module.exit_json(**result)
+ elif existing and existing["vni"] != module.params["vni"]:
+ module.fail_json(
+ msg="ERROR: VNI delete failed: Could not find vni node for {0}".format(
+ module.params["vni"],
+ ),
+ existing_vni=existing["vni"],
+ )
+
+ proposed_args = dict((k, v) for k, v in module.params.items() if v is not None and k in args)
+
+ proposed = {}
+ for key, value in proposed_args.items():
+ if key in ["multicast_group", "peer_list", "ingress_replication"]:
+ if str(value).lower() == "default":
+ value = PARAM_TO_DEFAULT_KEYMAP.get(key, "default")
+ if key != "interface" and existing.get(key) != value:
+ proposed[key] = value
+
+ candidate = CustomNetworkConfig(indent=3)
+ if state == "present":
+ state_present(module, existing, proposed, candidate)
+ elif existing and state == "absent":
+ state_absent(module, existing, proposed, candidate)
+
+ if candidate:
+ candidate = candidate.items_text()
+ result["changed"] = True
+ result["commands"] = candidate
+ if not module.check_mode:
+ load_config(module, candidate)
+
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_zone_zoneset.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_zone_zoneset.py
new file mode 100644
index 00000000..7c9fba30
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_zone_zoneset.py
@@ -0,0 +1,888 @@
+#!/usr/bin/python
+# Copyright: Ansible Project
+# 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
+
+
+DOCUMENTATION = """
+module: nxos_zone_zoneset
+short_description: Configuration of zone/zoneset for Cisco NXOS MDS Switches.
+description:
+- Configuration of zone/zoneset for Cisco MDS NXOS.
+version_added: 1.0.0
+author:
+- Suhas Bharadwaj (@srbharadwaj) (subharad@cisco.com)
+notes:
+- Tested against Cisco MDS NX-OS 8.4(1)
+options:
+ zone_zoneset_details:
+ description:
+ - List of zone/zoneset details to be added or removed
+ type: list
+ elements: dict
+ suboptions:
+ vsan:
+ description:
+ - vsan id
+ required: true
+ type: int
+ mode:
+ description:
+ - mode of the zone for the vsan
+ choices:
+ - enhanced
+ - basic
+ type: str
+ default_zone:
+ description:
+ - default zone behaviour for the vsan
+ choices:
+ - permit
+ - deny
+ type: str
+ smart_zoning:
+ description:
+ - Removes the vsan if True
+ type: bool
+ zone:
+ description:
+ - List of zone options for that vsan
+ type: list
+ elements: dict
+ suboptions:
+ name:
+ description:
+ - name of the zone
+ required: true
+ type: str
+ remove:
+ description:
+ - Deletes the zone if True
+ type: bool
+ default: false
+ members:
+ description:
+ - Members of the zone that needs to be removed or added
+ type: list
+ elements: dict
+ suboptions:
+ pwwn:
+ description:
+ - pwwn member of the zone, use alias 'device_alias' as option for
+ device_alias member
+ aliases:
+ - device_alias
+ required: true
+ type: str
+ remove:
+ description:
+ - Removes member from the zone if True
+ type: bool
+ default: false
+ devtype:
+ description:
+ - devtype of the zone member used along with Smart zoning config
+ choices:
+ - initiator
+ - target
+ - both
+ type: str
+ zoneset:
+ description:
+ - List of zoneset options for the vsan
+ type: list
+ elements: dict
+ suboptions:
+ name:
+ description:
+ - name of the zoneset
+ required: true
+ type: str
+ remove:
+ description:
+ - Removes zoneset if True
+ type: bool
+ default: false
+ action:
+ description:
+ - activates/de-activates the zoneset
+ choices:
+ - activate
+ - deactivate
+ type: str
+ members:
+ description:
+ - Members of the zoneset that needs to be removed or added
+ type: list
+ elements: dict
+ suboptions:
+ name:
+ description:
+ - name of the zone that needs to be added to the zoneset or removed
+ from the zoneset
+ required: true
+ type: str
+ remove:
+ description:
+ - Removes zone member from the zoneset
+ type: bool
+ default: false
+"""
+
+EXAMPLES = """
+- name: Test that zone/zoneset module works
+ cisco.nxos.nxos_zone_zoneset:
+ zone_zoneset_details:
+ - mode: enhanced
+ vsan: 22
+ zone:
+ - members:
+ - pwwn: 11:11:11:11:11:11:11:11
+ - device_alias: test123
+ - pwwn: 61:61:62:62:12:12:12:12
+ remove: true
+ name: zoneA
+ - members:
+ - pwwn: 10:11:11:11:11:11:11:11
+ - pwwn: 62:62:62:62:21:21:21:21
+ name: zoneB
+ - name: zoneC
+ remove: true
+ zoneset:
+ - action: activate
+ members:
+ - name: zoneA
+ - name: zoneB
+ - name: zoneC
+ remove: true
+ name: zsetname1
+ - action: deactivate
+ name: zsetTestExtra
+ remove: true
+ - mode: basic
+ smart_zoning: true
+ vsan: 21
+ zone:
+ - members:
+ - devtype: both
+ pwwn: 11:11:11:11:11:11:11:11
+ - pwwn: 62:62:62:62:12:12:12:12
+ - devtype: both
+ pwwn: 92:62:62:62:12:12:1a:1a
+ remove: true
+ name: zone21A
+ - members:
+ - pwwn: 10:11:11:11:11:11:11:11
+ - pwwn: 62:62:62:62:21:21:21:21
+ name: zone21B
+ zoneset:
+ - action: activate
+ members:
+ - name: zone21A
+ - name: zone21B
+ name: zsetname212
+
+"""
+
+RETURN = """
+commands:
+ description: commands sent to the device
+ returned: always
+ type: list
+ sample:
+ - terminal dont-ask
+ - zone name zoneA vsan 923
+ - member pwwn 11:11:11:11:11:11:11:11
+ - no member device-alias test123
+ - zone commit vsan 923
+ - no terminal dont-ask
+messages:
+ description: debug messages
+ returned: always
+ type: list
+ sample:
+ - "zone mode is already enhanced ,no change in zone mode configuration for vsan 922"
+ - "zone member '11:11:11:11:11:11:11:11' is already present in zone 'zoneA' in vsan 922 hence nothing to add"
+ - "zone member 'test123' is already present in zone 'zoneA' in vsan 922 hence nothing to add"
+ - "zone member '61:61:62:62:12:12:12:12' is not present in zone 'zoneA' in vsan 922 hence nothing to remove"
+ - "zone member '10:11:11:11:11:11:11:11' is already present in zone 'zoneB' in vsan 922 hence nothing to add"
+ - "zone member '62:62:62:62:21:21:21:21' is already present in zone 'zoneB' in vsan 922 hence nothing to add"
+ - "zone 'zoneC' is not present in vsan 922 , so nothing to remove"
+"""
+
+
+import re
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ load_config,
+ run_commands,
+)
+
+
+__metaclass__ = type
+
+
+class ShowZonesetActive(object):
+ """docstring for ShowZonesetActive"""
+
+ def __init__(self, module, vsan):
+ self.vsan = vsan
+ self.module = module
+ self.activeZSName = None
+ self.parseCmdOutput()
+
+ def execute_show_zoneset_active_cmd(self):
+ command = "show zoneset active vsan " + str(self.vsan) + " | grep zoneset"
+ output = execute_show_command(command, self.module)[0]
+ return output
+
+ def parseCmdOutput(self):
+ patZoneset = r"zoneset name (\S+) vsan " + str(self.vsan)
+ output = self.execute_show_zoneset_active_cmd().split("\n")
+ if len(output) == 0:
+ return
+ else:
+ for line in output:
+ line = line.strip()
+ mzs = re.match(patZoneset, line.strip())
+ if mzs:
+ self.activeZSName = mzs.group(1).strip()
+ return
+
+ def isZonesetActive(self, zsname):
+ if zsname == self.activeZSName:
+ return True
+ return False
+
+
+class ShowZoneset(object):
+ """docstring for ShowZoneset"""
+
+ def __init__(self, module, vsan):
+ self.vsan = vsan
+ self.module = module
+ self.zsDetails = {}
+ self.parseCmdOutput()
+
+ def execute_show_zoneset_cmd(self):
+ command = "show zoneset vsan " + str(self.vsan)
+ output = execute_show_command(command, self.module)[0]
+ return output
+
+ def parseCmdOutput(self):
+ patZoneset = r"zoneset name (\S+) vsan " + str(self.vsan)
+ patZone = r"zone name (\S+) vsan " + str(self.vsan)
+ output = self.execute_show_zoneset_cmd().split("\n")
+ for line in output:
+ line = line.strip()
+ mzs = re.match(patZoneset, line.strip())
+ mz = re.match(patZone, line.strip())
+ if mzs:
+ zonesetname = mzs.group(1).strip()
+ self.zsDetails[zonesetname] = []
+ continue
+ elif mz:
+ zonename = mz.group(1).strip()
+ v = self.zsDetails[zonesetname]
+ v.append(zonename)
+ self.zsDetails[zonesetname] = v
+
+ def isZonesetPresent(self, zsname):
+ return zsname in self.zsDetails.keys()
+
+ def isZonePresentInZoneset(self, zsname, zname):
+ if zsname in self.zsDetails.keys():
+ return zname in self.zsDetails[zsname]
+ return False
+
+
+class ShowZone(object):
+ """docstring for ShowZone"""
+
+ def __init__(self, module, vsan):
+ self.vsan = vsan
+ self.module = module
+ self.zDetails = {}
+ self.parseCmdOutput()
+
+ def execute_show_zone_vsan_cmd(self):
+ command = "show zone vsan " + str(self.vsan)
+ output = execute_show_command(command, self.module)[0]
+ return output
+
+ def parseCmdOutput(self):
+ patZone = r"zone name (\S+) vsan " + str(self.vsan)
+ output = self.execute_show_zone_vsan_cmd().split("\n")
+ for line in output:
+ line = re.sub(r"[\[].*?[\]]", "", line)
+ line = " ".join(line.strip().split())
+ if "init" in line:
+ line = line.replace("init", "initiator")
+ m = re.match(patZone, line)
+ if m:
+ zonename = m.group(1).strip()
+ self.zDetails[zonename] = []
+ continue
+ else:
+ # For now we support only pwwn and device-alias under zone
+ # Ideally should use 'supported_choices'....but maybe next
+ # time.
+ if "pwwn" in line or "device-alias" in line:
+ v = self.zDetails[zonename]
+ v.append(line)
+ self.zDetails[zonename] = v
+
+ def isZonePresent(self, zname):
+ return zname in self.zDetails.keys()
+
+ def isZoneMemberPresent(self, zname, cmd):
+ if zname in self.zDetails.keys():
+ zonememlist = self.zDetails[zname]
+ for eachline in zonememlist:
+ if cmd in eachline:
+ return True
+ return False
+
+ def get_zDetails(self):
+ return self.zDetails
+
+
+class ShowZoneStatus(object):
+ """docstring for ShowZoneStatus"""
+
+ def __init__(self, module, vsan):
+ self.vsan = vsan
+ self.vsanAbsent = False
+ self.module = module
+ self.default_zone = ""
+ self.mode = ""
+ self.session = ""
+ self.sz = ""
+ self.locked = False
+ self.update()
+
+ def execute_show_zone_status_cmd(self):
+ command = "show zone status vsan " + str(self.vsan)
+ output = execute_show_command(command, self.module)[0]
+ return output
+
+ def update(self):
+ output = self.execute_show_zone_status_cmd().split("\n")
+
+ patfordefzone = "VSAN: " + str(self.vsan) + r" default-zone:\s+(\S+).*"
+ patformode = r".*mode:\s+(\S+).*"
+ patforsession = r"^session:\s+(\S+).*"
+ patforsz = r".*smart-zoning:\s+(\S+).*"
+ for line in output:
+ if "is not configured" in line:
+ self.vsanAbsent = True
+ break
+ mdefz = re.match(patfordefzone, line.strip())
+ mmode = re.match(patformode, line.strip())
+ msession = re.match(patforsession, line.strip())
+ msz = re.match(patforsz, line.strip())
+
+ if mdefz:
+ self.default_zone = mdefz.group(1)
+ if mmode:
+ self.mode = mmode.group(1)
+ if msession:
+ self.session = msession.group(1)
+ if self.session != "none":
+ self.locked = True
+ if msz:
+ self.sz = msz.group(1)
+
+ def isLocked(self):
+ return self.locked
+
+ def getDefaultZone(self):
+ return self.default_zone
+
+ def getMode(self):
+ return self.mode
+
+ def getSmartZoningStatus(self):
+ return self.sz
+
+ def isVsanAbsent(self):
+ return self.vsanAbsent
+
+
+def execute_show_command(command, module, command_type="cli_show"):
+ output = "text"
+ commands = [{"command": command, "output": output}]
+ return run_commands(module, commands)
+
+
+def flatten_list(command_lists):
+ flat_command_list = []
+ for command in command_lists:
+ if isinstance(command, list):
+ flat_command_list.extend(command)
+ else:
+ flat_command_list.append(command)
+ return flat_command_list
+
+
+def getMemType(supported_choices, allmemkeys, default="pwwn"):
+ for eachchoice in supported_choices:
+ if eachchoice in allmemkeys:
+ return eachchoice
+ return default
+
+
+def main():
+ supported_choices = ["device_alias"]
+ zone_member_spec = dict(
+ pwwn=dict(required=True, type="str", aliases=["device_alias"]),
+ devtype=dict(type="str", choices=["initiator", "target", "both"]),
+ remove=dict(type="bool", default=False),
+ )
+
+ zone_spec = dict(
+ name=dict(required=True, type="str"),
+ members=dict(type="list", elements="dict", options=zone_member_spec),
+ remove=dict(type="bool", default=False),
+ )
+
+ zoneset_member_spec = dict(
+ name=dict(required=True, type="str"),
+ remove=dict(type="bool", default=False),
+ )
+
+ zoneset_spec = dict(
+ name=dict(type="str", required=True),
+ members=dict(type="list", elements="dict", options=zoneset_member_spec),
+ remove=dict(type="bool", default=False),
+ action=dict(type="str", choices=["activate", "deactivate"]),
+ )
+
+ zonedetails_spec = dict(
+ vsan=dict(required=True, type="int"),
+ mode=dict(type="str", choices=["enhanced", "basic"]),
+ default_zone=dict(type="str", choices=["permit", "deny"]),
+ smart_zoning=dict(type="bool"),
+ zone=dict(type="list", elements="dict", options=zone_spec),
+ zoneset=dict(type="list", elements="dict", options=zoneset_spec),
+ )
+
+ argument_spec = dict(
+ zone_zoneset_details=dict(type="list", elements="dict", options=zonedetails_spec),
+ )
+
+ module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)
+
+ warnings = list()
+ messages = list()
+ commands = list()
+ result = {"changed": False}
+
+ commands_executed = []
+ listOfZoneDetails = module.params["zone_zoneset_details"]
+ for eachZoneZonesetDetail in listOfZoneDetails:
+ vsan = eachZoneZonesetDetail["vsan"]
+ op_mode = eachZoneZonesetDetail["mode"]
+ op_default_zone = eachZoneZonesetDetail["default_zone"]
+ op_smart_zoning = eachZoneZonesetDetail["smart_zoning"]
+ op_zone = eachZoneZonesetDetail["zone"]
+ op_zoneset = eachZoneZonesetDetail["zoneset"]
+
+ # Step1: execute show zone status and get
+ shZoneStatusObj = ShowZoneStatus(module, vsan)
+ sw_default_zone = shZoneStatusObj.getDefaultZone()
+ sw_mode = shZoneStatusObj.getMode()
+ sw_smart_zoning = shZoneStatusObj.getSmartZoningStatus()
+
+ if sw_smart_zoning.lower() == "Enabled".lower():
+ sw_smart_zoning_bool = True
+ else:
+ sw_smart_zoning_bool = False
+
+ if shZoneStatusObj.isVsanAbsent():
+ module.fail_json(
+ msg="Vsan " + str(vsan) + " is not present in the switch. Hence cannot procced.",
+ )
+
+ if shZoneStatusObj.isLocked():
+ module.fail_json(
+ msg="zone has acquired lock on the switch for vsan "
+ + str(vsan)
+ + ". Hence cannot procced.",
+ )
+
+ # Process zone default zone options
+ if op_default_zone is not None:
+ if op_default_zone != sw_default_zone:
+ if op_default_zone == "permit":
+ commands_executed.append("zone default-zone permit vsan " + str(vsan))
+ messages.append(
+ "default zone configuration changed from deny to permit for vsan "
+ + str(vsan),
+ )
+ else:
+ commands_executed.append("no zone default-zone permit vsan " + str(vsan))
+ messages.append(
+ "default zone configuration changed from permit to deny for vsan "
+ + str(vsan),
+ )
+ else:
+ messages.append(
+ "default zone is already "
+ + op_default_zone
+ + " ,no change in default zone configuration for vsan "
+ + str(vsan),
+ )
+
+ # Process zone mode options
+ if op_mode is not None:
+ if op_mode != sw_mode:
+ if op_mode == "enhanced":
+ commands_executed.append("zone mode enhanced vsan " + str(vsan))
+ messages.append(
+ "zone mode configuration changed from basic to enhanced for vsan "
+ + str(vsan),
+ )
+ else:
+ commands_executed.append("no zone mode enhanced vsan " + str(vsan))
+ messages.append(
+ "zone mode configuration changed from enhanced to basic for vsan "
+ + str(vsan),
+ )
+ else:
+ messages.append(
+ "zone mode is already "
+ + op_mode
+ + " ,no change in zone mode configuration for vsan "
+ + str(vsan),
+ )
+
+ # Process zone smart-zone options
+ if op_smart_zoning is not None:
+ if op_smart_zoning != sw_smart_zoning_bool:
+ if op_smart_zoning:
+ commands_executed.append("zone smart-zoning enable vsan " + str(vsan))
+ messages.append("smart-zoning enabled for vsan " + str(vsan))
+ else:
+ commands_executed.append("no zone smart-zoning enable vsan " + str(vsan))
+ messages.append("smart-zoning disabled for vsan " + str(vsan))
+ else:
+ messages.append(
+ "smart-zoning is already set to "
+ + sw_smart_zoning
+ + " , no change in smart-zoning configuration for vsan "
+ + str(vsan),
+ )
+
+ # Process zone member options
+ # TODO: Obviously this needs to be cleaned up properly, as there are a lot of ifelse statements which is bad
+ # Will take it up later becoz of time constraints
+ if op_zone is not None:
+ shZoneObj = ShowZone(module, vsan)
+ for eachzone in op_zone:
+ zname = eachzone["name"]
+ zmembers = eachzone["members"]
+ removeflag = eachzone["remove"]
+ if removeflag:
+ if shZoneObj.isZonePresent(zname):
+ messages.append("zone '" + zname + "' is removed from vsan " + str(vsan))
+ commands_executed.append("no zone name " + zname + " vsan " + str(vsan))
+ else:
+ messages.append(
+ "zone '"
+ + zname
+ + "' is not present in vsan "
+ + str(vsan)
+ + " , so nothing to remove",
+ )
+ else:
+ if zmembers is None:
+ if shZoneObj.isZonePresent(zname):
+ messages.append(
+ "zone '" + zname + "' is already present in vsan " + str(vsan),
+ )
+ else:
+ commands_executed.append("zone name " + zname + " vsan " + str(vsan))
+ messages.append("zone '" + zname + "' is created in vsan " + str(vsan))
+ else:
+ cmdmemlist = []
+ for eachmem in zmembers:
+ memtype = getMemType(supported_choices, eachmem.keys())
+ cmd = memtype.replace("_", "-") + " " + eachmem[memtype]
+ if op_smart_zoning or sw_smart_zoning_bool:
+ if eachmem["devtype"] is not None:
+ cmd = cmd + " " + eachmem["devtype"]
+ if eachmem["remove"]:
+ if shZoneObj.isZonePresent(zname):
+ if shZoneObj.isZoneMemberPresent(zname, cmd):
+ cmd = "no member " + cmd
+ cmdmemlist.append(cmd)
+ if op_smart_zoning and eachmem["devtype"] is not None:
+ messages.append(
+ "removing zone member '"
+ + eachmem[memtype]
+ + " of device type '"
+ + eachmem["devtype"]
+ + "' from zone '"
+ + zname
+ + "' in vsan "
+ + str(vsan),
+ )
+ else:
+ messages.append(
+ "removing zone member '"
+ + eachmem[memtype]
+ + "' from zone '"
+ + zname
+ + "' in vsan "
+ + str(vsan),
+ )
+ else:
+ if op_smart_zoning and eachmem["devtype"] is not None:
+ messages.append(
+ "zone member '"
+ + eachmem[memtype]
+ + "' of device type '"
+ + eachmem["devtype"]
+ + "' is not present in zone '"
+ + zname
+ + "' in vsan "
+ + str(vsan)
+ + " hence nothing to remove",
+ )
+ else:
+ messages.append(
+ "zone member '"
+ + eachmem[memtype]
+ + "' is not present in zone '"
+ + zname
+ + "' in vsan "
+ + str(vsan)
+ + " hence nothing to remove",
+ )
+ else:
+ messages.append(
+ "zone '"
+ + zname
+ + "' is not present in vsan "
+ + str(vsan)
+ + " , hence cannot remove the members",
+ )
+
+ else:
+ if shZoneObj.isZoneMemberPresent(zname, cmd):
+ if op_smart_zoning and eachmem["devtype"] is not None:
+ messages.append(
+ "zone member '"
+ + eachmem[memtype]
+ + "' of device type '"
+ + eachmem["devtype"]
+ + "' is already present in zone '"
+ + zname
+ + "' in vsan "
+ + str(vsan)
+ + " hence nothing to add",
+ )
+ else:
+ messages.append(
+ "zone member '"
+ + eachmem[memtype]
+ + "' is already present in zone '"
+ + zname
+ + "' in vsan "
+ + str(vsan)
+ + " hence nothing to add",
+ )
+ else:
+ cmd = "member " + cmd
+ cmdmemlist.append(cmd)
+ if op_smart_zoning and eachmem["devtype"] is not None:
+ messages.append(
+ "adding zone member '"
+ + eachmem[memtype]
+ + "' of device type '"
+ + eachmem["devtype"]
+ + "' to zone '"
+ + zname
+ + "' in vsan "
+ + str(vsan),
+ )
+ else:
+ messages.append(
+ "adding zone member '"
+ + eachmem[memtype]
+ + "' to zone '"
+ + zname
+ + "' in vsan "
+ + str(vsan),
+ )
+ if len(cmdmemlist) != 0:
+ commands_executed.append("zone name " + zname + " vsan " + str(vsan))
+ commands_executed = commands_executed + cmdmemlist
+
+ # Process zoneset member options
+ if op_zoneset is not None:
+ dactcmd = []
+ actcmd = []
+ shZonesetObj = ShowZoneset(module, vsan)
+ shZonesetActiveObj = ShowZonesetActive(module, vsan)
+ for eachzoneset in op_zoneset:
+ zsetname = eachzoneset["name"]
+ zsetmembers = eachzoneset["members"]
+ removeflag = eachzoneset["remove"]
+ actionflag = eachzoneset["action"]
+ if removeflag:
+ if shZonesetObj.isZonesetPresent(zsetname):
+ messages.append(
+ "zoneset '" + zsetname + "' is removed from vsan " + str(vsan),
+ )
+ commands_executed.append(
+ "no zoneset name " + zsetname + " vsan " + str(vsan),
+ )
+ else:
+ messages.append(
+ "zoneset '"
+ + zsetname
+ + "' is not present in vsan "
+ + str(vsan)
+ + " ,hence there is nothing to remove",
+ )
+ else:
+ if zsetmembers is not None:
+ cmdmemlist = []
+ for eachzsmem in zsetmembers:
+ zsetmem_name = eachzsmem["name"]
+ zsetmem_removeflag = eachzsmem["remove"]
+ if zsetmem_removeflag:
+ if shZonesetObj.isZonePresentInZoneset(zsetname, zsetmem_name):
+ cmd = "no member " + zsetmem_name
+ cmdmemlist.append(cmd)
+ messages.append(
+ "removing zoneset member '"
+ + zsetmem_name
+ + "' from zoneset '"
+ + zsetname
+ + "' in vsan "
+ + str(vsan),
+ )
+ else:
+ messages.append(
+ "zoneset member '"
+ + zsetmem_name
+ + "' is not present in zoneset '"
+ + zsetname
+ + "' in vsan "
+ + str(vsan)
+ + " ,hence there is nothing to remove",
+ )
+ else:
+ if shZonesetObj.isZonePresentInZoneset(zsetname, zsetmem_name):
+ messages.append(
+ "zoneset member '"
+ + zsetmem_name
+ + "' is already present in zoneset '"
+ + zsetname
+ + "' in vsan "
+ + str(vsan)
+ + " ,hence there is nothing to add",
+ )
+ else:
+ cmd = "member " + zsetmem_name
+ cmdmemlist.append(cmd)
+ messages.append(
+ "adding zoneset member '"
+ + zsetmem_name
+ + "' to zoneset '"
+ + zsetname
+ + "' in vsan "
+ + str(vsan),
+ )
+ if len(cmdmemlist) != 0:
+ commands_executed.append(
+ "zoneset name " + zsetname + " vsan " + str(vsan),
+ )
+ commands_executed = commands_executed + cmdmemlist
+ else:
+ if shZonesetObj.isZonesetPresent(zsetname):
+ messages.append(
+ "zoneset '"
+ + zsetname
+ + "' is already present in vsan "
+ + str(vsan),
+ )
+ else:
+ commands_executed.append(
+ "zoneset name " + zsetname + " vsan " + str(vsan),
+ )
+ messages.append(
+ "zoneset '" + zsetname + "' is created in vsan " + str(vsan),
+ )
+
+ # Process zoneset activate options
+ if actionflag == "deactivate":
+ if shZonesetActiveObj.isZonesetActive(zsetname):
+ messages.append(
+ "deactivating zoneset '" + zsetname + "' in vsan " + str(vsan),
+ )
+ dactcmd.append(
+ "no zoneset activate name " + zsetname + " vsan " + str(vsan),
+ )
+ else:
+ messages.append(
+ "zoneset '"
+ + zsetname
+ + "' in vsan "
+ + str(vsan)
+ + " is not activated, hence cannot deactivate",
+ )
+ elif actionflag == "activate":
+ if commands_executed:
+ messages.append(
+ "activating zoneset '" + zsetname + "' in vsan " + str(vsan),
+ )
+ actcmd.append("zoneset activate name " + zsetname + " vsan " + str(vsan))
+ else:
+ messages.append(
+ "no changes to existing zoneset '"
+ + zsetname
+ + "' in vsan "
+ + str(vsan)
+ + " hence activate action is ignored",
+ )
+ commands_executed = commands_executed + dactcmd + actcmd
+
+ if commands_executed:
+ if op_mode == "enhanced":
+ commands_executed.append("zone commit vsan " + str(vsan))
+ elif op_mode is None:
+ if sw_mode == "enhanced":
+ commands_executed.append("zone commit vsan " + str(vsan))
+
+ if commands_executed:
+ commands_executed = ["terminal dont-ask"] + commands_executed + ["no terminal dont-ask"]
+
+ cmds = flatten_list(commands_executed)
+ if cmds:
+ if module.check_mode:
+ module.exit_json(
+ changed=False,
+ commands=cmds,
+ msg="Check Mode: No cmds issued to the hosts",
+ )
+ else:
+ result["changed"] = True
+ commands = commands + cmds
+ load_config(module, cmds)
+
+ result["messages"] = messages
+ result["commands"] = commands_executed
+ result["warnings"] = warnings
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/storage/__init__.py b/ansible_collections/cisco/nxos/plugins/modules/storage/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/storage/__init__.py
diff --git a/ansible_collections/cisco/nxos/plugins/modules/storage/nxos_devicealias.py b/ansible_collections/cisco/nxos/plugins/modules/storage/nxos_devicealias.py
new file mode 100644
index 00000000..71d4ebb6
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/storage/nxos_devicealias.py
@@ -0,0 +1,550 @@
+#!/usr/bin/python
+# Copyright: Ansible Project
+# 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
+
+
+DOCUMENTATION = """
+module: nxos_devicealias
+short_description: Configuration of device alias for Cisco NXOS MDS Switches.
+description:
+- Configuration of device alias for Cisco MDS NXOS.
+version_added: 1.0.0
+author:
+- Suhas Bharadwaj (@srbharadwaj) (subharad@cisco.com)
+notes:
+- Tested against Cisco MDS NX-OS 8.4(1)
+options:
+ distribute:
+ description:
+ - Enable/Disable device-alias distribution
+ type: bool
+ mode:
+ description:
+ - Mode of devices-alias, basic or enhanced
+ choices:
+ - basic
+ - enhanced
+ type: str
+ da:
+ description:
+ - List of device-alias to be added or removed
+ type: list
+ elements: dict
+ suboptions:
+ name:
+ description:
+ - Name of the device-alias to be added or removed
+ required: true
+ type: str
+ pwwn:
+ description:
+ - pwwn to which the name needs to be associated with
+ type: str
+ remove:
+ description:
+ - Removes the device-alias if set to True
+ type: bool
+ default: false
+ rename:
+ description:
+ - List of device-alias to be renamed
+ type: list
+ elements: dict
+ suboptions:
+ old_name:
+ description:
+ - Old name of the device-alias that needs to be renamed
+ required: true
+ type: str
+ new_name:
+ description:
+ - New name of the device-alias
+ required: true
+ type: str
+"""
+
+EXAMPLES = """
+- name: Test that device alias module works
+ cisco.nxos.nxos_devicealias:
+ da:
+ - name: test1_add
+ pwwn: 56:2:22:11:22:88:11:67
+ - name: test2_add
+ pwwn: 65:22:22:11:22:22:11:d
+ - name: dev1
+ remove: true
+ - name: dev2
+ remove: true
+ distribute: true
+ mode: enhanced
+ rename:
+ - new_name: bcd
+ old_name: abc
+ - new_name: bcd1
+ old_name: abc1
+
+
+"""
+
+RETURN = """
+commands:
+ description: commands sent to the device
+ returned: always
+ type: list
+ sample:
+ - terminal dont-ask
+ - device-alias database
+ - device-alias name somename pwwn 10:00:00:00:89:a1:01:03
+ - device-alias name somename1 pwwn 10:00:00:00:89:a1:02:03
+ - device-alias commit
+ - no terminal dont-ask
+"""
+
+import string
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ load_config,
+ run_commands,
+)
+
+
+__metaclass__ = type
+
+VALID_DA_CHARS = ("-", "_", "$", "^")
+
+
+class showDeviceAliasStatus(object):
+ """docstring for showDeviceAliasStatus"""
+
+ def __init__(self, module):
+ self.module = module
+ self.distribute = ""
+ self.mode = ""
+ self.locked = False
+ self.update()
+
+ def execute_show_cmd(self, cmd):
+ output = execute_show_command(cmd, self.module)[0]
+ return output
+
+ def update(self):
+ command = "show device-alias status"
+ output = self.execute_show_cmd(command).split("\n")
+ for o in output:
+ if "Fabric Distribution" in o:
+ self.distribute = o.split(":")[1].strip().lower()
+ if "Mode" in o:
+ self.mode = o.split("Mode:")[1].strip().lower()
+ if "Locked" in o:
+ self.locked = True
+
+ def isLocked(self):
+ return self.locked
+
+ def getDistribute(self):
+ return self.distribute
+
+ def getMode(self):
+ return self.mode
+
+
+class showDeviceAliasDatabase(object):
+ """docstring for showDeviceAliasDatabase"""
+
+ def __init__(self, module):
+ self.module = module
+ self.da_dict = {}
+ self.update()
+
+ def execute_show_cmd(self, cmd):
+ output = execute_show_command(cmd, self.module)[0]
+ return output
+
+ def update(self):
+ command = "show device-alias database"
+ # output = execute_show_command(command, self.module)[0].split("\n")
+ output = self.execute_show_cmd(command)
+ self.da_list = output.split("\n")
+ for eachline in self.da_list:
+ if "device-alias" in eachline:
+ sv = eachline.strip().split()
+ self.da_dict[sv[2]] = sv[4]
+
+ def isNameInDaDatabase(self, name):
+ return name in self.da_dict.keys()
+
+ def isPwwnInDaDatabase(self, pwwn):
+ newpwwn = ":".join(["0" + str(ep) if len(ep) == 1 else ep for ep in pwwn.split(":")])
+ return newpwwn in self.da_dict.values()
+
+ def isNamePwwnPresentInDatabase(self, name, pwwn):
+ newpwwn = ":".join(["0" + str(ep) if len(ep) == 1 else ep for ep in pwwn.split(":")])
+ if name in self.da_dict.keys():
+ if newpwwn == self.da_dict[name]:
+ return True
+ return False
+
+ def getPwwnByName(self, name):
+ if name in self.da_dict.keys():
+ return self.da_dict[name]
+ else:
+ return None
+
+ def getNameByPwwn(self, pwwn):
+ newpwwn = ":".join(["0" + str(ep) if len(ep) == 1 else ep for ep in pwwn.split(":")])
+ for n, p in self.da_dict.items():
+ if p == newpwwn:
+ return n
+ return None
+
+
+def isPwwnValid(pwwn):
+ pwwnsplit = pwwn.split(":")
+ if len(pwwnsplit) != 8:
+ return False
+ for eachpwwnsplit in pwwnsplit:
+ if len(eachpwwnsplit) > 2 or len(eachpwwnsplit) < 1:
+ return False
+ if not all(c in string.hexdigits for c in eachpwwnsplit):
+ return False
+ return True
+
+
+def isNameValid(name):
+ if not name[0].isalpha():
+ # Illegal first character. Name must start with a letter
+ return False
+ if len(name) > 64:
+ return False
+ for character in name:
+ if not character.isalnum() and character not in VALID_DA_CHARS:
+ return False
+ return True
+
+
+def execute_show_command(command, module, command_type="cli_show"):
+ output = "text"
+ commands = [{"command": command, "output": output}]
+ out = run_commands(module, commands)
+ return out
+
+
+def flatten_list(command_lists):
+ flat_command_list = []
+ for command in command_lists:
+ if isinstance(command, list):
+ flat_command_list.extend(command)
+ else:
+ flat_command_list.append(command)
+ return flat_command_list
+
+
+def main():
+ element_spec = dict(
+ name=dict(required=True, type="str"),
+ pwwn=dict(type="str"),
+ remove=dict(type="bool", default=False),
+ )
+
+ element_spec_rename = dict(
+ old_name=dict(required=True, type="str"),
+ new_name=dict(required=True, type="str"),
+ )
+
+ argument_spec = dict(
+ distribute=dict(type="bool"),
+ mode=dict(type="str", choices=["enhanced", "basic"]),
+ da=dict(type="list", elements="dict", options=element_spec),
+ rename=dict(type="list", elements="dict", options=element_spec_rename),
+ )
+
+ module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)
+
+ warnings = list()
+ messages = list()
+ commands_to_execute = list()
+ result = {"changed": False}
+
+ distribute = module.params["distribute"]
+ mode = module.params["mode"]
+ da = module.params["da"]
+ rename = module.params["rename"]
+
+ # Step 0.0: Validate syntax of name and pwwn
+ # Also validate syntax of rename arguments
+ if da is not None:
+ for eachdict in da:
+ name = eachdict["name"]
+ pwwn = eachdict["pwwn"]
+ remove = eachdict["remove"]
+ if pwwn is not None:
+ pwwn = pwwn.lower()
+ if not remove:
+ if pwwn is None:
+ module.fail_json(
+ msg="This device alias name "
+ + str(name)
+ + " which needs to be added, does not have pwwn specified. Please specify a valid pwwn",
+ )
+ if not isNameValid(name):
+ module.fail_json(
+ msg="This pwwn name is invalid : "
+ + str(name)
+ + ". Note that name cannot be more than 64 alphanumeric chars, "
+ + "it must start with a letter, and can only contain these characters: "
+ + ", ".join(["'{0}'".format(c) for c in VALID_DA_CHARS]),
+ )
+ if not isPwwnValid(pwwn):
+ module.fail_json(
+ msg="This pwwn is invalid : "
+ + str(pwwn)
+ + ". Please check that its a valid pwwn",
+ )
+ if rename is not None:
+ for eachdict in rename:
+ oldname = eachdict["old_name"]
+ newname = eachdict["new_name"]
+ if not isNameValid(oldname):
+ module.fail_json(
+ msg="This pwwn name is invalid : "
+ + str(oldname)
+ + ". Note that name cannot be more than 64 alphanumeric chars, "
+ + "it must start with a letter, and can only contain these characters: "
+ + ", ".join(["'{0}'".format(c) for c in VALID_DA_CHARS]),
+ )
+ if not isNameValid(newname):
+ module.fail_json(
+ msg="This pwwn name is invalid : "
+ + str(newname)
+ + ". Note that name cannot be more than 64 alphanumeric chars, "
+ + "it must start with a letter, and can only contain these characters: "
+ + ", ".join(["'{0}'".format(c) for c in VALID_DA_CHARS]),
+ )
+
+ # Step 0.1: Check DA status
+ shDAStausObj = showDeviceAliasStatus(module)
+ d = shDAStausObj.getDistribute()
+ m = shDAStausObj.getMode()
+ if shDAStausObj.isLocked():
+ module.fail_json(msg="device-alias has acquired lock on the switch. Hence cannot procced.")
+
+ # Step 1: Process distribute
+ commands = []
+ if distribute is not None:
+ if distribute:
+ # playbook has distribute as True(enabled)
+ if d == "disabled":
+ # but switch distribute is disabled(false), so set it to
+ # true(enabled)
+ commands.append("device-alias distribute")
+ messages.append("device-alias distribute changed from disabled to enabled")
+ else:
+ messages.append(
+ "device-alias distribute remains unchanged. current distribution mode is enabled",
+ )
+ else:
+ # playbook has distribute as False(disabled)
+ if d == "enabled":
+ # but switch distribute is enabled(true), so set it to
+ # false(disabled)
+ commands.append("no device-alias distribute")
+ messages.append("device-alias distribute changed from enabled to disabled")
+ else:
+ messages.append(
+ "device-alias distribute remains unchanged. current distribution mode is disabled",
+ )
+
+ cmds = flatten_list(commands)
+ if cmds:
+ commands_to_execute = commands_to_execute + cmds
+ if module.check_mode:
+ # Check mode implemented at the da_add/da_remove stage
+ pass
+ else:
+ result["changed"] = True
+ load_config(module, cmds)
+
+ # Step 2: Process mode
+ commands = []
+ if mode is not None:
+ if mode == "basic":
+ # playbook has mode as basic
+ if m == "enhanced":
+ # but switch mode is enhanced, so set it to basic
+ commands.append("no device-alias mode enhanced")
+ messages.append("device-alias mode changed from enhanced to basic")
+ else:
+ messages.append("device-alias mode remains unchanged. current mode is basic")
+
+ else:
+ # playbook has mode as enhanced
+ if m == "basic":
+ # but switch mode is basic, so set it to enhanced
+ commands.append("device-alias mode enhanced")
+ messages.append("device-alias mode changed from basic to enhanced")
+ else:
+ messages.append("device-alias mode remains unchanged. current mode is enhanced")
+
+ if commands:
+ if distribute:
+ commands.append("device-alias commit")
+ commands = ["terminal dont-ask"] + commands + ["no terminal dont-ask"]
+ else:
+ if distribute is None and d == "enabled":
+ commands.append("device-alias commit")
+ commands = ["terminal dont-ask"] + commands + ["no terminal dont-ask"]
+
+ cmds = flatten_list(commands)
+
+ if cmds:
+ commands_to_execute = commands_to_execute + cmds
+ if module.check_mode:
+ # Check mode implemented at the end
+ pass
+ else:
+ result["changed"] = True
+ load_config(module, cmds)
+
+ # Step 3: Process da
+ commands = []
+ shDADatabaseObj = showDeviceAliasDatabase(module)
+ if da is not None:
+ da_remove_list = []
+ da_add_list = []
+ for eachdict in da:
+ name = eachdict["name"]
+ pwwn = eachdict["pwwn"]
+ remove = eachdict["remove"]
+ if pwwn is not None:
+ pwwn = pwwn.lower()
+ if remove:
+ if shDADatabaseObj.isNameInDaDatabase(name):
+ commands.append("no device-alias name " + name)
+ da_remove_list.append(name)
+ else:
+ messages.append(
+ name
+ + " - This device alias name is not in switch device-alias database, hence cannot be removed.",
+ )
+ else:
+ if shDADatabaseObj.isNamePwwnPresentInDatabase(name, pwwn):
+ messages.append(
+ name
+ + " : "
+ + pwwn
+ + " - This device alias name,pwwn is already in switch device-alias database, hence nothing to configure",
+ )
+ else:
+ if shDADatabaseObj.isNameInDaDatabase(name):
+ module.fail_json(
+ msg=name
+ + " - This device alias name is already present in switch device-alias database but assigned to another pwwn ("
+ + shDADatabaseObj.getPwwnByName(name)
+ + ") hence cannot be added",
+ )
+
+ elif shDADatabaseObj.isPwwnInDaDatabase(pwwn):
+ module.fail_json(
+ msg=pwwn
+ + " - This device alias pwwn is already present in switch device-alias database but assigned to another name ("
+ + shDADatabaseObj.getNameByPwwn(pwwn)
+ + ") hence cannot be added",
+ )
+
+ else:
+ commands.append("device-alias name " + name + " pwwn " + pwwn)
+ da_add_list.append(name)
+
+ if len(da_add_list) != 0 or len(da_remove_list) != 0:
+ commands = ["device-alias database"] + commands
+ if distribute:
+ commands.append("device-alias commit")
+ commands = ["terminal dont-ask"] + commands + ["no terminal dont-ask"]
+ else:
+ if distribute is None and d == "enabled":
+ commands.append("device-alias commit")
+ commands = ["terminal dont-ask"] + commands + ["no terminal dont-ask"]
+
+ cmds = flatten_list(commands)
+ if cmds:
+ commands_to_execute = commands_to_execute + cmds
+ if module.check_mode:
+ # Check mode implemented at the end
+ pass
+ else:
+ result["changed"] = True
+ load_config(module, cmds)
+ if len(da_remove_list) != 0:
+ messages.append(
+ "the required device-alias were removed. " + ",".join(da_remove_list),
+ )
+ if len(da_add_list) != 0:
+ messages.append(
+ "the required device-alias were added. " + ",".join(da_add_list),
+ )
+
+ # Step 5: Process rename
+ commands = []
+ if rename is not None:
+ for eachdict in rename:
+ oldname = eachdict["old_name"]
+ newname = eachdict["new_name"]
+ if shDADatabaseObj.isNameInDaDatabase(newname):
+ module.fail_json(
+ changed=False,
+ commands=cmds,
+ msg=newname
+ + " - this name is already present in the device-alias database, hence we cannot rename "
+ + oldname
+ + " with this one",
+ )
+ if shDADatabaseObj.isNameInDaDatabase(oldname):
+ commands.append("device-alias rename " + oldname + " " + newname)
+ else:
+ module.fail_json(
+ changed=False,
+ commands=cmds,
+ msg=oldname
+ + " - this name is not present in the device-alias database, hence we cannot rename.",
+ )
+
+ if len(commands) != 0:
+ commands = ["device-alias database"] + commands
+ if distribute:
+ commands.append("device-alias commit")
+ commands = ["terminal dont-ask"] + commands + ["no terminal dont-ask"]
+ else:
+ if distribute is None and d == "enabled":
+ commands.append("device-alias commit")
+ commands = ["terminal dont-ask"] + commands + ["no terminal dont-ask"]
+ cmds = flatten_list(commands)
+ if cmds:
+ commands_to_execute = commands_to_execute + cmds
+ if module.check_mode:
+ # Check mode implemented at the end
+ pass
+ else:
+ result["changed"] = True
+ load_config(module, cmds)
+
+ # Step END: check for 'check' mode
+ if module.check_mode:
+ module.exit_json(
+ changed=False,
+ commands=commands_to_execute,
+ msg="Check Mode: No cmds issued to the hosts",
+ )
+
+ result["messages"] = messages
+ result["commands"] = commands_to_execute
+ result["warnings"] = warnings
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/storage/nxos_vsan.py b/ansible_collections/cisco/nxos/plugins/modules/storage/nxos_vsan.py
new file mode 100644
index 00000000..d95d95a9
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/storage/nxos_vsan.py
@@ -0,0 +1,354 @@
+#!/usr/bin/python
+# Copyright: Ansible Project
+# 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
+
+
+DOCUMENTATION = """
+module: nxos_vsan
+short_description: Configuration of vsan for Cisco NXOS MDS Switches.
+description:
+- Configuration of vsan for Cisco MDS NXOS.
+version_added: 1.0.0
+author:
+- Suhas Bharadwaj (@srbharadwaj) (subharad@cisco.com)
+notes:
+- Tested against Cisco MDS NX-OS 8.4(1)
+options:
+ vsan:
+ description:
+ - List of vsan details to be added or removed
+ type: list
+ elements: dict
+ suboptions:
+ id:
+ description:
+ - Vsan id
+ required: true
+ type: int
+ name:
+ description:
+ - Name of the vsan
+ type: str
+ suspend:
+ description:
+ - suspend the vsan if True
+ type: bool
+ remove:
+ description:
+ - Removes the vsan if True
+ type: bool
+ interface:
+ description:
+ - List of vsan's interfaces to be added
+ type: list
+ elements: str
+"""
+
+EXAMPLES = """
+- name: Test that vsan module works
+ cisco.nxos.nxos_vsan:
+ vsan:
+ - id: 922
+ interface:
+ - fc1/1
+ - fc1/2
+ - port-channel 1
+ name: vsan-SAN-A
+ remove: false
+ suspend: false
+ - id: 923
+ interface:
+ - fc1/11
+ - fc1/21
+ - port-channel 2
+ name: vsan-SAN-B
+ remove: false
+ suspend: true
+ - id: 1923
+ name: vsan-SAN-Old
+ remove: true
+"""
+
+RETURN = """
+commands:
+ description: commands sent to the device
+ returned: always
+ type: list
+ sample:
+ - terminal dont-ask
+ - vsan database
+ - vsan 922 interface fc1/40
+ - vsan 922 interface port-channel 155
+ - no terminal dont-ask
+"""
+
+import re
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ load_config,
+ run_commands,
+)
+
+
+__metaclass__ = type
+
+
+class Vsan(object):
+ def __init__(self, vsanid):
+ self.vsanid = vsanid
+ self.vsanname = None
+ self.vsanstate = None
+ self.vsanoperstate = None
+ self.vsaninterfaces = []
+
+
+class GetVsanInfoFromSwitch(object):
+ """docstring for GetVsanInfoFromSwitch"""
+
+ def __init__(self, module):
+ self.module = module
+ self.vsaninfo = {}
+ self.processShowVsan()
+ self.processShowVsanMembership()
+
+ def execute_show_vsan_cmd(self):
+ output = execute_show_command("show vsan", self.module)[0]
+ return output
+
+ def execute_show_vsan_mem_cmd(self):
+ output = execute_show_command("show vsan membership", self.module)[0]
+ return output
+
+ def processShowVsan(self):
+ patv = r"^vsan\s+(\d+)\s+information"
+ patnamestate = "name:(.*)state:(.*)"
+ patoperstate = "operational state:(.*)"
+
+ output = self.execute_show_vsan_cmd().split("\n")
+ for o in output:
+ z = re.match(patv, o.strip())
+ if z:
+ v = z.group(1).strip()
+ self.vsaninfo[v] = Vsan(v)
+
+ z1 = re.match(patnamestate, o.strip())
+ if z1:
+ n = z1.group(1).strip()
+ s = z1.group(2).strip()
+ self.vsaninfo[v].vsanname = n
+ self.vsaninfo[v].vsanstate = s
+
+ z2 = re.match(patoperstate, o.strip())
+ if z2:
+ oper = z2.group(1).strip()
+ self.vsaninfo[v].vsanoperstate = oper
+
+ # 4094/4079 vsan is always present
+ self.vsaninfo["4079"] = Vsan("4079")
+ self.vsaninfo["4094"] = Vsan("4094")
+
+ def processShowVsanMembership(self):
+ patv = r"^vsan\s+(\d+).*"
+ output = self.execute_show_vsan_mem_cmd().split("\n")
+ memlist = []
+ v = None
+ for o in output:
+ z = re.match(patv, o.strip())
+ if z:
+ if v is not None:
+ self.vsaninfo[v].vsaninterfaces = memlist
+ memlist = []
+ v = z.group(1)
+ if "interfaces" not in o:
+ llist = o.strip().split()
+ memlist = memlist + llist
+ self.vsaninfo[v].vsaninterfaces = memlist
+
+ def getVsanInfoObjects(self):
+ return self.vsaninfo
+
+
+def execute_show_command(command, module, command_type="cli_show"):
+ output = "text"
+ commands = [{"command": command, "output": output}]
+ return run_commands(module, commands)
+
+
+def flatten_list(command_lists):
+ flat_command_list = []
+ for command in command_lists:
+ if isinstance(command, list):
+ flat_command_list.extend(command)
+ else:
+ flat_command_list.append(command)
+ return flat_command_list
+
+
+def main():
+ vsan_element_spec = dict(
+ id=dict(required=True, type="int"),
+ name=dict(type="str"),
+ remove=dict(type="bool"),
+ suspend=dict(type="bool"),
+ interface=dict(type="list", elements="str"),
+ )
+
+ argument_spec = dict(vsan=dict(type="list", elements="dict", options=vsan_element_spec))
+
+ module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)
+ warnings = list()
+ messages = list()
+ commands_executed = list()
+ result = {"changed": False}
+
+ obj = GetVsanInfoFromSwitch(module)
+ dictSwVsanObjs = obj.getVsanInfoObjects()
+
+ commands = []
+ vsan_list = module.params["vsan"]
+
+ for eachvsan in vsan_list:
+ vsanid = str(eachvsan["id"])
+ vsanname = eachvsan["name"]
+ vsanremove = eachvsan["remove"]
+ vsansuspend = eachvsan["suspend"]
+ vsaninterface_list = eachvsan["interface"]
+
+ if int(vsanid) < 1 or int(vsanid) >= 4095:
+ module.fail_json(
+ msg=vsanid + " - This is an invalid vsan. Supported vsan range is 1-4094",
+ )
+
+ if vsanid in dictSwVsanObjs.keys():
+ sw_vsanid = vsanid
+ sw_vsanname = dictSwVsanObjs[vsanid].vsanname
+ sw_vsanstate = dictSwVsanObjs[vsanid].vsanstate
+ sw_vsaninterfaces = dictSwVsanObjs[vsanid].vsaninterfaces
+ else:
+ sw_vsanid = None
+ sw_vsanname = None
+ sw_vsanstate = None
+ sw_vsaninterfaces = []
+
+ if vsanremove:
+ # Negative case:
+ if vsanid == "4079" or vsanid == "4094":
+ messages.append(str(vsanid) + " is a reserved vsan, hence cannot be removed")
+ continue
+ if vsanid == sw_vsanid:
+ commands.append("no vsan " + str(vsanid))
+ messages.append("deleting the vsan " + str(vsanid))
+ else:
+ messages.append(
+ "There is no vsan "
+ + str(vsanid)
+ + " present in the switch. Hence there is nothing to delete",
+ )
+ continue
+ else:
+ # Negative case:
+ if vsanid == "4079" or vsanid == "4094":
+ messages.append(
+ str(vsanid) + " is a reserved vsan, and always present on the switch",
+ )
+ else:
+ if vsanid == sw_vsanid:
+ messages.append(
+ "There is already a vsan "
+ + str(vsanid)
+ + " present in the switch. Hence there is nothing to configure",
+ )
+ else:
+ commands.append("vsan " + str(vsanid))
+ messages.append("creating vsan " + str(vsanid))
+
+ if vsanname is not None:
+ # Negative case:
+ if vsanid == "4079" or vsanid == "4094":
+ messages.append(str(vsanid) + " is a reserved vsan, and cannot be renamed")
+ else:
+ if vsanname == sw_vsanname:
+ messages.append(
+ "There is already a vsan "
+ + str(vsanid)
+ + " present in the switch, which has the name "
+ + vsanname
+ + " Hence there is nothing to configure",
+ )
+ else:
+ commands.append("vsan " + str(vsanid) + " name " + vsanname)
+ messages.append("setting vsan name to " + vsanname + " for vsan " + str(vsanid))
+
+ if vsansuspend:
+ # Negative case:
+ if vsanid == "4079" or vsanid == "4094":
+ messages.append(str(vsanid) + " is a reserved vsan, and cannot be suspended")
+ else:
+ if sw_vsanstate == "suspended":
+ messages.append(
+ "There is already a vsan "
+ + str(vsanid)
+ + " present in the switch, which is in suspended state ",
+ )
+ else:
+ commands.append("vsan " + str(vsanid) + " suspend")
+ messages.append("suspending the vsan " + str(vsanid))
+ else:
+ if sw_vsanstate == "active":
+ messages.append(
+ "There is already a vsan "
+ + str(vsanid)
+ + " present in the switch, which is in active state ",
+ )
+ else:
+ commands.append("no vsan " + str(vsanid) + " suspend")
+ messages.append("no suspending the vsan " + str(vsanid))
+
+ if vsaninterface_list is not None:
+ for each_interface_name in vsaninterface_list:
+ # For fcip,port-channel,vfc-port-channel need to remove the
+ # extra space to compare
+ temp = re.sub(" +", "", each_interface_name)
+ if temp in sw_vsaninterfaces:
+ messages.append(
+ each_interface_name
+ + " is already present in the vsan "
+ + str(vsanid)
+ + " interface list",
+ )
+ else:
+ commands.append("vsan " + str(vsanid) + " interface " + each_interface_name)
+ messages.append(
+ "adding interface " + each_interface_name + " to vsan " + str(vsanid),
+ )
+
+ if len(commands) != 0:
+ commands = ["terminal dont-ask"] + ["vsan database"] + commands + ["no terminal dont-ask"]
+
+ cmds = flatten_list(commands)
+ commands_executed = cmds
+
+ if commands_executed:
+ if module.check_mode:
+ module.exit_json(
+ changed=False,
+ commands=commands_executed,
+ msg="Check Mode: No cmds issued to the hosts",
+ )
+ else:
+ result["changed"] = True
+ load_config(module, commands_executed)
+
+ result["messages"] = messages
+ result["commands"] = commands_executed
+ result["warnings"] = warnings
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/storage/nxos_zone_zoneset.py b/ansible_collections/cisco/nxos/plugins/modules/storage/nxos_zone_zoneset.py
new file mode 100644
index 00000000..7c9fba30
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/storage/nxos_zone_zoneset.py
@@ -0,0 +1,888 @@
+#!/usr/bin/python
+# Copyright: Ansible Project
+# 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
+
+
+DOCUMENTATION = """
+module: nxos_zone_zoneset
+short_description: Configuration of zone/zoneset for Cisco NXOS MDS Switches.
+description:
+- Configuration of zone/zoneset for Cisco MDS NXOS.
+version_added: 1.0.0
+author:
+- Suhas Bharadwaj (@srbharadwaj) (subharad@cisco.com)
+notes:
+- Tested against Cisco MDS NX-OS 8.4(1)
+options:
+ zone_zoneset_details:
+ description:
+ - List of zone/zoneset details to be added or removed
+ type: list
+ elements: dict
+ suboptions:
+ vsan:
+ description:
+ - vsan id
+ required: true
+ type: int
+ mode:
+ description:
+ - mode of the zone for the vsan
+ choices:
+ - enhanced
+ - basic
+ type: str
+ default_zone:
+ description:
+ - default zone behaviour for the vsan
+ choices:
+ - permit
+ - deny
+ type: str
+ smart_zoning:
+ description:
+ - Removes the vsan if True
+ type: bool
+ zone:
+ description:
+ - List of zone options for that vsan
+ type: list
+ elements: dict
+ suboptions:
+ name:
+ description:
+ - name of the zone
+ required: true
+ type: str
+ remove:
+ description:
+ - Deletes the zone if True
+ type: bool
+ default: false
+ members:
+ description:
+ - Members of the zone that needs to be removed or added
+ type: list
+ elements: dict
+ suboptions:
+ pwwn:
+ description:
+ - pwwn member of the zone, use alias 'device_alias' as option for
+ device_alias member
+ aliases:
+ - device_alias
+ required: true
+ type: str
+ remove:
+ description:
+ - Removes member from the zone if True
+ type: bool
+ default: false
+ devtype:
+ description:
+ - devtype of the zone member used along with Smart zoning config
+ choices:
+ - initiator
+ - target
+ - both
+ type: str
+ zoneset:
+ description:
+ - List of zoneset options for the vsan
+ type: list
+ elements: dict
+ suboptions:
+ name:
+ description:
+ - name of the zoneset
+ required: true
+ type: str
+ remove:
+ description:
+ - Removes zoneset if True
+ type: bool
+ default: false
+ action:
+ description:
+ - activates/de-activates the zoneset
+ choices:
+ - activate
+ - deactivate
+ type: str
+ members:
+ description:
+ - Members of the zoneset that needs to be removed or added
+ type: list
+ elements: dict
+ suboptions:
+ name:
+ description:
+ - name of the zone that needs to be added to the zoneset or removed
+ from the zoneset
+ required: true
+ type: str
+ remove:
+ description:
+ - Removes zone member from the zoneset
+ type: bool
+ default: false
+"""
+
+EXAMPLES = """
+- name: Test that zone/zoneset module works
+ cisco.nxos.nxos_zone_zoneset:
+ zone_zoneset_details:
+ - mode: enhanced
+ vsan: 22
+ zone:
+ - members:
+ - pwwn: 11:11:11:11:11:11:11:11
+ - device_alias: test123
+ - pwwn: 61:61:62:62:12:12:12:12
+ remove: true
+ name: zoneA
+ - members:
+ - pwwn: 10:11:11:11:11:11:11:11
+ - pwwn: 62:62:62:62:21:21:21:21
+ name: zoneB
+ - name: zoneC
+ remove: true
+ zoneset:
+ - action: activate
+ members:
+ - name: zoneA
+ - name: zoneB
+ - name: zoneC
+ remove: true
+ name: zsetname1
+ - action: deactivate
+ name: zsetTestExtra
+ remove: true
+ - mode: basic
+ smart_zoning: true
+ vsan: 21
+ zone:
+ - members:
+ - devtype: both
+ pwwn: 11:11:11:11:11:11:11:11
+ - pwwn: 62:62:62:62:12:12:12:12
+ - devtype: both
+ pwwn: 92:62:62:62:12:12:1a:1a
+ remove: true
+ name: zone21A
+ - members:
+ - pwwn: 10:11:11:11:11:11:11:11
+ - pwwn: 62:62:62:62:21:21:21:21
+ name: zone21B
+ zoneset:
+ - action: activate
+ members:
+ - name: zone21A
+ - name: zone21B
+ name: zsetname212
+
+"""
+
+RETURN = """
+commands:
+ description: commands sent to the device
+ returned: always
+ type: list
+ sample:
+ - terminal dont-ask
+ - zone name zoneA vsan 923
+ - member pwwn 11:11:11:11:11:11:11:11
+ - no member device-alias test123
+ - zone commit vsan 923
+ - no terminal dont-ask
+messages:
+ description: debug messages
+ returned: always
+ type: list
+ sample:
+ - "zone mode is already enhanced ,no change in zone mode configuration for vsan 922"
+ - "zone member '11:11:11:11:11:11:11:11' is already present in zone 'zoneA' in vsan 922 hence nothing to add"
+ - "zone member 'test123' is already present in zone 'zoneA' in vsan 922 hence nothing to add"
+ - "zone member '61:61:62:62:12:12:12:12' is not present in zone 'zoneA' in vsan 922 hence nothing to remove"
+ - "zone member '10:11:11:11:11:11:11:11' is already present in zone 'zoneB' in vsan 922 hence nothing to add"
+ - "zone member '62:62:62:62:21:21:21:21' is already present in zone 'zoneB' in vsan 922 hence nothing to add"
+ - "zone 'zoneC' is not present in vsan 922 , so nothing to remove"
+"""
+
+
+import re
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ load_config,
+ run_commands,
+)
+
+
+__metaclass__ = type
+
+
+class ShowZonesetActive(object):
+ """docstring for ShowZonesetActive"""
+
+ def __init__(self, module, vsan):
+ self.vsan = vsan
+ self.module = module
+ self.activeZSName = None
+ self.parseCmdOutput()
+
+ def execute_show_zoneset_active_cmd(self):
+ command = "show zoneset active vsan " + str(self.vsan) + " | grep zoneset"
+ output = execute_show_command(command, self.module)[0]
+ return output
+
+ def parseCmdOutput(self):
+ patZoneset = r"zoneset name (\S+) vsan " + str(self.vsan)
+ output = self.execute_show_zoneset_active_cmd().split("\n")
+ if len(output) == 0:
+ return
+ else:
+ for line in output:
+ line = line.strip()
+ mzs = re.match(patZoneset, line.strip())
+ if mzs:
+ self.activeZSName = mzs.group(1).strip()
+ return
+
+ def isZonesetActive(self, zsname):
+ if zsname == self.activeZSName:
+ return True
+ return False
+
+
+class ShowZoneset(object):
+ """docstring for ShowZoneset"""
+
+ def __init__(self, module, vsan):
+ self.vsan = vsan
+ self.module = module
+ self.zsDetails = {}
+ self.parseCmdOutput()
+
+ def execute_show_zoneset_cmd(self):
+ command = "show zoneset vsan " + str(self.vsan)
+ output = execute_show_command(command, self.module)[0]
+ return output
+
+ def parseCmdOutput(self):
+ patZoneset = r"zoneset name (\S+) vsan " + str(self.vsan)
+ patZone = r"zone name (\S+) vsan " + str(self.vsan)
+ output = self.execute_show_zoneset_cmd().split("\n")
+ for line in output:
+ line = line.strip()
+ mzs = re.match(patZoneset, line.strip())
+ mz = re.match(patZone, line.strip())
+ if mzs:
+ zonesetname = mzs.group(1).strip()
+ self.zsDetails[zonesetname] = []
+ continue
+ elif mz:
+ zonename = mz.group(1).strip()
+ v = self.zsDetails[zonesetname]
+ v.append(zonename)
+ self.zsDetails[zonesetname] = v
+
+ def isZonesetPresent(self, zsname):
+ return zsname in self.zsDetails.keys()
+
+ def isZonePresentInZoneset(self, zsname, zname):
+ if zsname in self.zsDetails.keys():
+ return zname in self.zsDetails[zsname]
+ return False
+
+
+class ShowZone(object):
+ """docstring for ShowZone"""
+
+ def __init__(self, module, vsan):
+ self.vsan = vsan
+ self.module = module
+ self.zDetails = {}
+ self.parseCmdOutput()
+
+ def execute_show_zone_vsan_cmd(self):
+ command = "show zone vsan " + str(self.vsan)
+ output = execute_show_command(command, self.module)[0]
+ return output
+
+ def parseCmdOutput(self):
+ patZone = r"zone name (\S+) vsan " + str(self.vsan)
+ output = self.execute_show_zone_vsan_cmd().split("\n")
+ for line in output:
+ line = re.sub(r"[\[].*?[\]]", "", line)
+ line = " ".join(line.strip().split())
+ if "init" in line:
+ line = line.replace("init", "initiator")
+ m = re.match(patZone, line)
+ if m:
+ zonename = m.group(1).strip()
+ self.zDetails[zonename] = []
+ continue
+ else:
+ # For now we support only pwwn and device-alias under zone
+ # Ideally should use 'supported_choices'....but maybe next
+ # time.
+ if "pwwn" in line or "device-alias" in line:
+ v = self.zDetails[zonename]
+ v.append(line)
+ self.zDetails[zonename] = v
+
+ def isZonePresent(self, zname):
+ return zname in self.zDetails.keys()
+
+ def isZoneMemberPresent(self, zname, cmd):
+ if zname in self.zDetails.keys():
+ zonememlist = self.zDetails[zname]
+ for eachline in zonememlist:
+ if cmd in eachline:
+ return True
+ return False
+
+ def get_zDetails(self):
+ return self.zDetails
+
+
+class ShowZoneStatus(object):
+ """docstring for ShowZoneStatus"""
+
+ def __init__(self, module, vsan):
+ self.vsan = vsan
+ self.vsanAbsent = False
+ self.module = module
+ self.default_zone = ""
+ self.mode = ""
+ self.session = ""
+ self.sz = ""
+ self.locked = False
+ self.update()
+
+ def execute_show_zone_status_cmd(self):
+ command = "show zone status vsan " + str(self.vsan)
+ output = execute_show_command(command, self.module)[0]
+ return output
+
+ def update(self):
+ output = self.execute_show_zone_status_cmd().split("\n")
+
+ patfordefzone = "VSAN: " + str(self.vsan) + r" default-zone:\s+(\S+).*"
+ patformode = r".*mode:\s+(\S+).*"
+ patforsession = r"^session:\s+(\S+).*"
+ patforsz = r".*smart-zoning:\s+(\S+).*"
+ for line in output:
+ if "is not configured" in line:
+ self.vsanAbsent = True
+ break
+ mdefz = re.match(patfordefzone, line.strip())
+ mmode = re.match(patformode, line.strip())
+ msession = re.match(patforsession, line.strip())
+ msz = re.match(patforsz, line.strip())
+
+ if mdefz:
+ self.default_zone = mdefz.group(1)
+ if mmode:
+ self.mode = mmode.group(1)
+ if msession:
+ self.session = msession.group(1)
+ if self.session != "none":
+ self.locked = True
+ if msz:
+ self.sz = msz.group(1)
+
+ def isLocked(self):
+ return self.locked
+
+ def getDefaultZone(self):
+ return self.default_zone
+
+ def getMode(self):
+ return self.mode
+
+ def getSmartZoningStatus(self):
+ return self.sz
+
+ def isVsanAbsent(self):
+ return self.vsanAbsent
+
+
+def execute_show_command(command, module, command_type="cli_show"):
+ output = "text"
+ commands = [{"command": command, "output": output}]
+ return run_commands(module, commands)
+
+
+def flatten_list(command_lists):
+ flat_command_list = []
+ for command in command_lists:
+ if isinstance(command, list):
+ flat_command_list.extend(command)
+ else:
+ flat_command_list.append(command)
+ return flat_command_list
+
+
+def getMemType(supported_choices, allmemkeys, default="pwwn"):
+ for eachchoice in supported_choices:
+ if eachchoice in allmemkeys:
+ return eachchoice
+ return default
+
+
+def main():
+ supported_choices = ["device_alias"]
+ zone_member_spec = dict(
+ pwwn=dict(required=True, type="str", aliases=["device_alias"]),
+ devtype=dict(type="str", choices=["initiator", "target", "both"]),
+ remove=dict(type="bool", default=False),
+ )
+
+ zone_spec = dict(
+ name=dict(required=True, type="str"),
+ members=dict(type="list", elements="dict", options=zone_member_spec),
+ remove=dict(type="bool", default=False),
+ )
+
+ zoneset_member_spec = dict(
+ name=dict(required=True, type="str"),
+ remove=dict(type="bool", default=False),
+ )
+
+ zoneset_spec = dict(
+ name=dict(type="str", required=True),
+ members=dict(type="list", elements="dict", options=zoneset_member_spec),
+ remove=dict(type="bool", default=False),
+ action=dict(type="str", choices=["activate", "deactivate"]),
+ )
+
+ zonedetails_spec = dict(
+ vsan=dict(required=True, type="int"),
+ mode=dict(type="str", choices=["enhanced", "basic"]),
+ default_zone=dict(type="str", choices=["permit", "deny"]),
+ smart_zoning=dict(type="bool"),
+ zone=dict(type="list", elements="dict", options=zone_spec),
+ zoneset=dict(type="list", elements="dict", options=zoneset_spec),
+ )
+
+ argument_spec = dict(
+ zone_zoneset_details=dict(type="list", elements="dict", options=zonedetails_spec),
+ )
+
+ module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)
+
+ warnings = list()
+ messages = list()
+ commands = list()
+ result = {"changed": False}
+
+ commands_executed = []
+ listOfZoneDetails = module.params["zone_zoneset_details"]
+ for eachZoneZonesetDetail in listOfZoneDetails:
+ vsan = eachZoneZonesetDetail["vsan"]
+ op_mode = eachZoneZonesetDetail["mode"]
+ op_default_zone = eachZoneZonesetDetail["default_zone"]
+ op_smart_zoning = eachZoneZonesetDetail["smart_zoning"]
+ op_zone = eachZoneZonesetDetail["zone"]
+ op_zoneset = eachZoneZonesetDetail["zoneset"]
+
+ # Step1: execute show zone status and get
+ shZoneStatusObj = ShowZoneStatus(module, vsan)
+ sw_default_zone = shZoneStatusObj.getDefaultZone()
+ sw_mode = shZoneStatusObj.getMode()
+ sw_smart_zoning = shZoneStatusObj.getSmartZoningStatus()
+
+ if sw_smart_zoning.lower() == "Enabled".lower():
+ sw_smart_zoning_bool = True
+ else:
+ sw_smart_zoning_bool = False
+
+ if shZoneStatusObj.isVsanAbsent():
+ module.fail_json(
+ msg="Vsan " + str(vsan) + " is not present in the switch. Hence cannot procced.",
+ )
+
+ if shZoneStatusObj.isLocked():
+ module.fail_json(
+ msg="zone has acquired lock on the switch for vsan "
+ + str(vsan)
+ + ". Hence cannot procced.",
+ )
+
+ # Process zone default zone options
+ if op_default_zone is not None:
+ if op_default_zone != sw_default_zone:
+ if op_default_zone == "permit":
+ commands_executed.append("zone default-zone permit vsan " + str(vsan))
+ messages.append(
+ "default zone configuration changed from deny to permit for vsan "
+ + str(vsan),
+ )
+ else:
+ commands_executed.append("no zone default-zone permit vsan " + str(vsan))
+ messages.append(
+ "default zone configuration changed from permit to deny for vsan "
+ + str(vsan),
+ )
+ else:
+ messages.append(
+ "default zone is already "
+ + op_default_zone
+ + " ,no change in default zone configuration for vsan "
+ + str(vsan),
+ )
+
+ # Process zone mode options
+ if op_mode is not None:
+ if op_mode != sw_mode:
+ if op_mode == "enhanced":
+ commands_executed.append("zone mode enhanced vsan " + str(vsan))
+ messages.append(
+ "zone mode configuration changed from basic to enhanced for vsan "
+ + str(vsan),
+ )
+ else:
+ commands_executed.append("no zone mode enhanced vsan " + str(vsan))
+ messages.append(
+ "zone mode configuration changed from enhanced to basic for vsan "
+ + str(vsan),
+ )
+ else:
+ messages.append(
+ "zone mode is already "
+ + op_mode
+ + " ,no change in zone mode configuration for vsan "
+ + str(vsan),
+ )
+
+ # Process zone smart-zone options
+ if op_smart_zoning is not None:
+ if op_smart_zoning != sw_smart_zoning_bool:
+ if op_smart_zoning:
+ commands_executed.append("zone smart-zoning enable vsan " + str(vsan))
+ messages.append("smart-zoning enabled for vsan " + str(vsan))
+ else:
+ commands_executed.append("no zone smart-zoning enable vsan " + str(vsan))
+ messages.append("smart-zoning disabled for vsan " + str(vsan))
+ else:
+ messages.append(
+ "smart-zoning is already set to "
+ + sw_smart_zoning
+ + " , no change in smart-zoning configuration for vsan "
+ + str(vsan),
+ )
+
+ # Process zone member options
+ # TODO: Obviously this needs to be cleaned up properly, as there are a lot of ifelse statements which is bad
+ # Will take it up later becoz of time constraints
+ if op_zone is not None:
+ shZoneObj = ShowZone(module, vsan)
+ for eachzone in op_zone:
+ zname = eachzone["name"]
+ zmembers = eachzone["members"]
+ removeflag = eachzone["remove"]
+ if removeflag:
+ if shZoneObj.isZonePresent(zname):
+ messages.append("zone '" + zname + "' is removed from vsan " + str(vsan))
+ commands_executed.append("no zone name " + zname + " vsan " + str(vsan))
+ else:
+ messages.append(
+ "zone '"
+ + zname
+ + "' is not present in vsan "
+ + str(vsan)
+ + " , so nothing to remove",
+ )
+ else:
+ if zmembers is None:
+ if shZoneObj.isZonePresent(zname):
+ messages.append(
+ "zone '" + zname + "' is already present in vsan " + str(vsan),
+ )
+ else:
+ commands_executed.append("zone name " + zname + " vsan " + str(vsan))
+ messages.append("zone '" + zname + "' is created in vsan " + str(vsan))
+ else:
+ cmdmemlist = []
+ for eachmem in zmembers:
+ memtype = getMemType(supported_choices, eachmem.keys())
+ cmd = memtype.replace("_", "-") + " " + eachmem[memtype]
+ if op_smart_zoning or sw_smart_zoning_bool:
+ if eachmem["devtype"] is not None:
+ cmd = cmd + " " + eachmem["devtype"]
+ if eachmem["remove"]:
+ if shZoneObj.isZonePresent(zname):
+ if shZoneObj.isZoneMemberPresent(zname, cmd):
+ cmd = "no member " + cmd
+ cmdmemlist.append(cmd)
+ if op_smart_zoning and eachmem["devtype"] is not None:
+ messages.append(
+ "removing zone member '"
+ + eachmem[memtype]
+ + " of device type '"
+ + eachmem["devtype"]
+ + "' from zone '"
+ + zname
+ + "' in vsan "
+ + str(vsan),
+ )
+ else:
+ messages.append(
+ "removing zone member '"
+ + eachmem[memtype]
+ + "' from zone '"
+ + zname
+ + "' in vsan "
+ + str(vsan),
+ )
+ else:
+ if op_smart_zoning and eachmem["devtype"] is not None:
+ messages.append(
+ "zone member '"
+ + eachmem[memtype]
+ + "' of device type '"
+ + eachmem["devtype"]
+ + "' is not present in zone '"
+ + zname
+ + "' in vsan "
+ + str(vsan)
+ + " hence nothing to remove",
+ )
+ else:
+ messages.append(
+ "zone member '"
+ + eachmem[memtype]
+ + "' is not present in zone '"
+ + zname
+ + "' in vsan "
+ + str(vsan)
+ + " hence nothing to remove",
+ )
+ else:
+ messages.append(
+ "zone '"
+ + zname
+ + "' is not present in vsan "
+ + str(vsan)
+ + " , hence cannot remove the members",
+ )
+
+ else:
+ if shZoneObj.isZoneMemberPresent(zname, cmd):
+ if op_smart_zoning and eachmem["devtype"] is not None:
+ messages.append(
+ "zone member '"
+ + eachmem[memtype]
+ + "' of device type '"
+ + eachmem["devtype"]
+ + "' is already present in zone '"
+ + zname
+ + "' in vsan "
+ + str(vsan)
+ + " hence nothing to add",
+ )
+ else:
+ messages.append(
+ "zone member '"
+ + eachmem[memtype]
+ + "' is already present in zone '"
+ + zname
+ + "' in vsan "
+ + str(vsan)
+ + " hence nothing to add",
+ )
+ else:
+ cmd = "member " + cmd
+ cmdmemlist.append(cmd)
+ if op_smart_zoning and eachmem["devtype"] is not None:
+ messages.append(
+ "adding zone member '"
+ + eachmem[memtype]
+ + "' of device type '"
+ + eachmem["devtype"]
+ + "' to zone '"
+ + zname
+ + "' in vsan "
+ + str(vsan),
+ )
+ else:
+ messages.append(
+ "adding zone member '"
+ + eachmem[memtype]
+ + "' to zone '"
+ + zname
+ + "' in vsan "
+ + str(vsan),
+ )
+ if len(cmdmemlist) != 0:
+ commands_executed.append("zone name " + zname + " vsan " + str(vsan))
+ commands_executed = commands_executed + cmdmemlist
+
+ # Process zoneset member options
+ if op_zoneset is not None:
+ dactcmd = []
+ actcmd = []
+ shZonesetObj = ShowZoneset(module, vsan)
+ shZonesetActiveObj = ShowZonesetActive(module, vsan)
+ for eachzoneset in op_zoneset:
+ zsetname = eachzoneset["name"]
+ zsetmembers = eachzoneset["members"]
+ removeflag = eachzoneset["remove"]
+ actionflag = eachzoneset["action"]
+ if removeflag:
+ if shZonesetObj.isZonesetPresent(zsetname):
+ messages.append(
+ "zoneset '" + zsetname + "' is removed from vsan " + str(vsan),
+ )
+ commands_executed.append(
+ "no zoneset name " + zsetname + " vsan " + str(vsan),
+ )
+ else:
+ messages.append(
+ "zoneset '"
+ + zsetname
+ + "' is not present in vsan "
+ + str(vsan)
+ + " ,hence there is nothing to remove",
+ )
+ else:
+ if zsetmembers is not None:
+ cmdmemlist = []
+ for eachzsmem in zsetmembers:
+ zsetmem_name = eachzsmem["name"]
+ zsetmem_removeflag = eachzsmem["remove"]
+ if zsetmem_removeflag:
+ if shZonesetObj.isZonePresentInZoneset(zsetname, zsetmem_name):
+ cmd = "no member " + zsetmem_name
+ cmdmemlist.append(cmd)
+ messages.append(
+ "removing zoneset member '"
+ + zsetmem_name
+ + "' from zoneset '"
+ + zsetname
+ + "' in vsan "
+ + str(vsan),
+ )
+ else:
+ messages.append(
+ "zoneset member '"
+ + zsetmem_name
+ + "' is not present in zoneset '"
+ + zsetname
+ + "' in vsan "
+ + str(vsan)
+ + " ,hence there is nothing to remove",
+ )
+ else:
+ if shZonesetObj.isZonePresentInZoneset(zsetname, zsetmem_name):
+ messages.append(
+ "zoneset member '"
+ + zsetmem_name
+ + "' is already present in zoneset '"
+ + zsetname
+ + "' in vsan "
+ + str(vsan)
+ + " ,hence there is nothing to add",
+ )
+ else:
+ cmd = "member " + zsetmem_name
+ cmdmemlist.append(cmd)
+ messages.append(
+ "adding zoneset member '"
+ + zsetmem_name
+ + "' to zoneset '"
+ + zsetname
+ + "' in vsan "
+ + str(vsan),
+ )
+ if len(cmdmemlist) != 0:
+ commands_executed.append(
+ "zoneset name " + zsetname + " vsan " + str(vsan),
+ )
+ commands_executed = commands_executed + cmdmemlist
+ else:
+ if shZonesetObj.isZonesetPresent(zsetname):
+ messages.append(
+ "zoneset '"
+ + zsetname
+ + "' is already present in vsan "
+ + str(vsan),
+ )
+ else:
+ commands_executed.append(
+ "zoneset name " + zsetname + " vsan " + str(vsan),
+ )
+ messages.append(
+ "zoneset '" + zsetname + "' is created in vsan " + str(vsan),
+ )
+
+ # Process zoneset activate options
+ if actionflag == "deactivate":
+ if shZonesetActiveObj.isZonesetActive(zsetname):
+ messages.append(
+ "deactivating zoneset '" + zsetname + "' in vsan " + str(vsan),
+ )
+ dactcmd.append(
+ "no zoneset activate name " + zsetname + " vsan " + str(vsan),
+ )
+ else:
+ messages.append(
+ "zoneset '"
+ + zsetname
+ + "' in vsan "
+ + str(vsan)
+ + " is not activated, hence cannot deactivate",
+ )
+ elif actionflag == "activate":
+ if commands_executed:
+ messages.append(
+ "activating zoneset '" + zsetname + "' in vsan " + str(vsan),
+ )
+ actcmd.append("zoneset activate name " + zsetname + " vsan " + str(vsan))
+ else:
+ messages.append(
+ "no changes to existing zoneset '"
+ + zsetname
+ + "' in vsan "
+ + str(vsan)
+ + " hence activate action is ignored",
+ )
+ commands_executed = commands_executed + dactcmd + actcmd
+
+ if commands_executed:
+ if op_mode == "enhanced":
+ commands_executed.append("zone commit vsan " + str(vsan))
+ elif op_mode is None:
+ if sw_mode == "enhanced":
+ commands_executed.append("zone commit vsan " + str(vsan))
+
+ if commands_executed:
+ commands_executed = ["terminal dont-ask"] + commands_executed + ["no terminal dont-ask"]
+
+ cmds = flatten_list(commands_executed)
+ if cmds:
+ if module.check_mode:
+ module.exit_json(
+ changed=False,
+ commands=cmds,
+ msg="Check Mode: No cmds issued to the hosts",
+ )
+ else:
+ result["changed"] = True
+ commands = commands + cmds
+ load_config(module, cmds)
+
+ result["messages"] = messages
+ result["commands"] = commands_executed
+ result["warnings"] = warnings
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()