#!/usr/bin/python # -*- coding: utf-8 -*- # 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_interface extends_documentation_fragment: - cisco.nxos.nxos short_description: (deprecated, removed after 2022-06-01) Manages physical attributes of interfaces. description: - Manages physical attributes of interfaces of NX-OS switches. version_added: 1.0.0 deprecated: alternative: nxos_interfaces why: Updated modules released with more functionality removed_at_date: '2022-06-01' author: - Jason Edelman (@jedelman8) - Trishna Guha (@trishnaguha) notes: - Tested against NXOSv 7.3.(0)D1(1) on VIRL - Unsupported for Cisco MDS - This module is also used to create logical interfaces such as svis and loopbacks. - Be cautious of platform specific idiosyncrasies. For example, when you default a loopback interface, the admin state toggles on certain versions of NX-OS. - The M(cisco.nxos.nxos_overlay_global) C(anycast_gateway_mac) attribute must be set before setting the C(fabric_forwarding_anycast_gateway) property. options: name: description: - Full name of interface, i.e. Ethernet1/1, port-channel10. aliases: - interface type: str interface_type: description: - Interface type to be unconfigured from the device. choices: - loopback - portchannel - svi - nve type: str speed: description: - Interface link speed. Applicable for ethernet interface only. type: str admin_state: description: - Administrative state of the interface. default: up choices: - up - down type: str description: description: - Interface description. type: str mode: description: - Manage Layer 2 or Layer 3 state of the interface. This option is supported for ethernet and portchannel interface. Applicable for ethernet and portchannel interface 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 interface only. type: str ip_forward: description: - Enable/Disable ip forward feature on SVIs. choices: - enable - disable type: str fabric_forwarding_anycast_gateway: description: - Associate SVI with anycast gateway under VLAN configuration mode. Applicable for SVI interface only. type: bool duplex: description: - Interface link status. Applicable for ethernet interface only. choices: - full - half - auto type: str tx_rate: description: - Transmit rate in bits per second (bps). - This is state check parameter only. - Supports conditionals, see U(https://docs.ansible.com/ansible/latest/network/user_guide/network_working_with_command_output.html#conditionals-in-networking-modules) type: str rx_rate: description: - Receiver rate in bits per second (bps). - This is state check parameter only. - Supports conditionals, see U(https://docs.ansible.com/ansible/latest/network/user_guide/network_working_with_command_output.html#conditionals-in-networking-modules) type: str neighbors: description: - Check the operational state of given interface C(name) for LLDP neighbor. - The following suboptions are available. This is state check parameter only. type: list elements: dict suboptions: host: description: - LLDP neighbor host for given interface C(name). type: str port: description: - LLDP neighbor port to which given interface C(name) is connected. type: str aggregate: description: List of Interfaces definitions. type: list elements: dict suboptions: name: description: - Full name of interface, i.e. Ethernet1/1, port-channel10. required: true type: str interface_type: description: - Interface type to be unconfigured from the device. choices: - loopback - portchannel - svi - nve type: str speed: description: - Interface link speed. Applicable for ethernet interface only. type: str admin_state: description: - Administrative state of the interface. choices: - up - down type: str description: description: - Interface description. type: str mode: description: - Manage Layer 2 or Layer 3 state of the interface. This option is supported for ethernet and portchannel interface. Applicable for ethernet and portchannel interface 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 interface only. type: str ip_forward: description: - Enable/Disable ip forward feature on SVIs. choices: - enable - disable type: str fabric_forwarding_anycast_gateway: description: - Associate SVI with anycast gateway under VLAN configuration mode. Applicable for SVI interface only. type: bool duplex: description: - Interface link status. Applicable for ethernet interface only. choices: - full - half - auto type: str tx_rate: description: - Transmit rate in bits per second (bps). - This is state check parameter only. - Supports conditionals, see U(https://docs.ansible.com/ansible/latest/network/user_guide/network_working_with_command_output.html#conditionals-in-networking-modules) type: str rx_rate: description: - Receiver rate in bits per second (bps). - This is state check parameter only. - Supports conditionals, see U(https://docs.ansible.com/ansible/latest/network/user_guide/network_working_with_command_output.html#conditionals-in-networking-modules) type: str neighbors: description: - Check the operational state of given interface C(name) for LLDP neighbor. - The following suboptions are available. This is state check parameter only. type: list elements: dict suboptions: host: description: - LLDP neighbor host for given interface C(name). type: str port: description: - LLDP neighbor port to which given interface C(name) is connected. type: str state: description: - Specify desired state of the resource. choices: - present - absent - 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 state: description: - Specify desired state of the resource. default: present choices: - present - absent - 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 an interface is a Layer 3 port and that it has the proper description cisco.nxos.nxos_interface: name: Ethernet1/1 description: Configured by Ansible mode: layer3 - name: Admin down an interface cisco.nxos.nxos_interface: name: Ethernet2/1 admin_state: down - name: Remove all loopback interfaces cisco.nxos.nxos_interface: name: loopback state: absent - name: Remove all logical interfaces cisco.nxos.nxos_interface: interface_type: '{{ item }} ' state: absent loop: - loopback - portchannel - svi - nve - name: Admin up all loopback interfaces cisco.nxos.nxos_interface: name: loopback 0-1023 admin_state: up - name: Admin down all loopback interfaces cisco.nxos.nxos_interface: name: loopback 0-1023 admin_state: down - name: Check neighbors intent arguments cisco.nxos.nxos_interface: name: Ethernet2/3 neighbors: - port: Ethernet2/3 host: abc.mycompany.com - name: Add interface using aggregate cisco.nxos.nxos_interface: aggregate: - {name: Ethernet0/1, mtu: 256, description: test-interface-1} - {name: Ethernet0/2, mtu: 516, description: test-interface-2} duplex: full speed: 100 state: present - name: Delete interface using aggregate cisco.nxos.nxos_interface: aggregate: - name: Loopback9 - name: Loopback10 state: absent - name: Check intent arguments cisco.nxos.nxos_interface: name: Ethernet0/2 state: up tx_rate: ge(0) rx_rate: le(0) """ RETURN = """ commands: description: command list sent to the device returned: always type: list sample: - interface Ethernet2/3 - mtu 1500 - speed 10 """ import re import time from copy import deepcopy from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import ( load_config, run_commands, ) from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import ( nxos_argument_spec, normalize_interface, ) from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import ( get_interface_type, ) from ansible.module_utils.basic import AnsibleModule from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ( conditional, remove_default_spec, ) 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, check_rc=False) if body and "Invalid" in body[0]: return [] else: return body def search_obj_in_list(name, lst): for o in lst: if o["name"] == name: return o return None def get_interfaces_dict(module): """Gets all active interfaces on a given switch""" try: body = execute_show_command("show interface", module)[0] except IndexError: return {} interfaces = { "ethernet": [], "svi": [], "loopback": [], "management": [], "portchannel": [], "nve": [], "unknown": [], } if body: interface_list = body["TABLE_interface"]["ROW_interface"] for index in interface_list: intf = index["interface"] intf_type = get_interface_type(intf) interfaces[intf_type].append(intf) return interfaces def get_vlan_interface_attributes(name, intf_type, module): """Returns dictionary that has two k/v pairs: admin_state & description if not an svi, returns None """ command = "show run interface {0} all".format(name) try: body = execute_show_command(command, module)[0] except (IndexError, TypeError): return None if body: command_list = body.split("\n") desc = None admin_state = "down" for each in command_list: if "description" in each: desc = each.lstrip().split("description")[1].lstrip() elif "no shutdown" in each: admin_state = "up" return dict(description=desc, admin_state=admin_state) else: return None def get_interface_type_removed_cmds(interfaces): commands = [] for interface in interfaces: if interface != "Vlan1": commands.append("no interface {0}".format(interface)) return commands def get_admin_state(admin_state): command = "" if admin_state == "up": command = "no shutdown" elif admin_state == "down": command = "shutdown" return command def is_default_interface(name, module): """Checks to see if interface exists and if it is a default config""" command = "show run interface {0}".format(name) try: body = execute_show_command(command, module)[0] except (IndexError, TypeError): body = "" if body: raw_list = body.split("\n") found = False for line in raw_list: if line.startswith("interface"): found = True if found and line and not line.startswith("interface"): return False return True else: return "DNE" def add_command_to_interface(interface, cmd, commands): if interface not in commands: commands.append(interface) commands.append(cmd) def map_obj_to_commands(updates, module): commands = list() commands2 = list() want, have = updates args = ("speed", "description", "duplex", "mtu") for w in want: name = w["name"] mode = w["mode"] ip_forward = w["ip_forward"] fabric_forwarding_anycast_gateway = w[ "fabric_forwarding_anycast_gateway" ] admin_state = w["admin_state"] state = w["state"] interface_type = w["interface_type"] del w["state"] if name: w["interface_type"] = None if interface_type: obj_in_have = {} if state in ("present", "default"): module.fail_json( msg="The interface_type param can be used only with state absent." ) else: obj_in_have = search_obj_in_list(name, have) is_default = is_default_interface(name, module) if name: interface = "interface " + name if state == "absent": if obj_in_have: commands.append("no interface {0}".format(name)) elif interface_type and not obj_in_have: intfs = get_interfaces_dict(module)[interface_type] cmds = get_interface_type_removed_cmds(intfs) commands.extend(cmds) elif state == "present": if obj_in_have: # Don't run switchport command for loopback and svi interfaces if get_interface_type(name) in ("ethernet", "portchannel"): if mode == "layer2" and mode != obj_in_have.get("mode"): add_command_to_interface( interface, "switchport", commands ) elif mode == "layer3" and mode != obj_in_have.get("mode"): add_command_to_interface( interface, "no switchport", commands ) if admin_state == "up" and admin_state != obj_in_have.get( "admin_state" ): add_command_to_interface( interface, "no shutdown", commands ) elif admin_state == "down" and admin_state != obj_in_have.get( "admin_state" ): add_command_to_interface(interface, "shutdown", commands) if ip_forward == "enable" and ip_forward != obj_in_have.get( "ip_forward" ): add_command_to_interface(interface, "ip forward", commands) elif ip_forward == "disable" and ip_forward != obj_in_have.get( "ip forward" ): add_command_to_interface( interface, "no ip forward", commands ) if ( fabric_forwarding_anycast_gateway is True and obj_in_have.get("fabric_forwarding_anycast_gateway") is False ): add_command_to_interface( interface, "fabric forwarding mode anycast-gateway", commands, ) elif ( fabric_forwarding_anycast_gateway is False and obj_in_have.get("fabric_forwarding_anycast_gateway") is True ): add_command_to_interface( interface, "no fabric forwarding mode anycast-gateway", commands, ) for item in args: candidate = w.get(item) if candidate and candidate != obj_in_have.get(item): cmd = item + " " + str(candidate) add_command_to_interface(interface, cmd, commands) if name and get_interface_type(name) == "ethernet": if mode != obj_in_have.get("mode"): admin_state = w.get("admin_state") or obj_in_have.get( "admin_state" ) if admin_state: c1 = "interface {0}".format( normalize_interface(w["name"]) ) c2 = get_admin_state(admin_state) commands2.append(c1) commands2.append(c2) else: commands.append(interface) # Don't run switchport command for loopback and svi interfaces if get_interface_type(name) in ("ethernet", "portchannel"): if mode == "layer2": commands.append("switchport") elif mode == "layer3": commands.append("no switchport") if admin_state == "up": commands.append("no shutdown") elif admin_state == "down": commands.append("shutdown") if ip_forward == "enable": commands.append("ip forward") elif ip_forward == "disable": commands.append("no ip forward") if fabric_forwarding_anycast_gateway is True: commands.append("fabric forwarding mode anycast-gateway") elif fabric_forwarding_anycast_gateway is False: commands.append( "no fabric forwarding mode anycast-gateway" ) for item in args: candidate = w.get(item) if candidate: commands.append(item + " " + str(candidate)) elif state == "default": if is_default is False: commands.append("default interface {0}".format(name)) elif is_default == "DNE": module.exit_json( msg="interface you are trying to default does not exist" ) return commands, commands2 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() name = d["name"] d["name"] = normalize_interface(name) obj.append(d) else: obj.append( { "name": normalize_interface(module.params["name"]), "description": module.params["description"], "speed": module.params["speed"], "mode": module.params["mode"], "mtu": module.params["mtu"], "duplex": module.params["duplex"], "ip_forward": module.params["ip_forward"], "fabric_forwarding_anycast_gateway": module.params[ "fabric_forwarding_anycast_gateway" ], "admin_state": module.params["admin_state"], "state": module.params["state"], "interface_type": module.params["interface_type"], "tx_rate": module.params["tx_rate"], "rx_rate": module.params["rx_rate"], "neighbors": module.params["neighbors"], } ) return obj def map_config_to_obj(want, module): objs = list() for w in want: obj = dict( name=None, description=None, admin_state=None, speed=None, mtu=None, mode=None, duplex=None, interface_type=None, ip_forward=None, fabric_forwarding_anycast_gateway=None, ) if not w["name"]: return obj command = "show interface {0}".format(w["name"]) try: body = execute_show_command(command, module)[0] except IndexError: return list() if body: try: interface_table = body["TABLE_interface"]["ROW_interface"] except (KeyError, TypeError): return list() if interface_table: if interface_table.get("eth_mode") == "fex-fabric": module.fail_json( msg='nxos_interface does not support interfaces with mode "fex-fabric"' ) intf_type = get_interface_type(w["name"]) if intf_type in ["portchannel", "ethernet"]: mode = interface_table.get("eth_mode") if mode in ("access", "trunk", "dot1q-tunnel"): obj["mode"] = "layer2" elif mode in ("routed", "layer3"): obj["mode"] = "layer3" else: obj["mode"] = "layer3" if intf_type == "ethernet": obj["name"] = normalize_interface( interface_table.get("interface") ) obj["admin_state"] = interface_table.get("admin_state") obj["description"] = interface_table.get("desc") obj["mtu"] = interface_table.get("eth_mtu") obj["duplex"] = interface_table.get("eth_duplex") command = "show run interface {0}".format(obj["name"]) body = execute_show_command(command, module)[0] speed_match = re.search(r"speed (\d+)", body) if speed_match is None: obj["speed"] = "auto" else: obj["speed"] = speed_match.group(1) duplex_match = re.search(r"duplex (\S+)", body) if duplex_match is None: obj["duplex"] = "auto" else: obj["duplex"] = duplex_match.group(1) if "ip forward" in body: obj["ip_forward"] = "enable" else: obj["ip_forward"] = "disable" elif intf_type == "svi": obj["name"] = normalize_interface( interface_table.get("interface") ) attributes = get_vlan_interface_attributes( obj["name"], intf_type, module ) obj["admin_state"] = str( attributes.get("admin_state", "nxapibug") ) obj["description"] = str( attributes.get("description", "nxapi_bug") ) obj["mtu"] = interface_table.get("svi_mtu") command = "show run interface {0}".format(obj["name"]) body = execute_show_command(command, module)[0] if "ip forward" in body: obj["ip_forward"] = "enable" else: obj["ip_forward"] = "disable" if "fabric forwarding mode anycast-gateway" in body: obj["fabric_forwarding_anycast_gateway"] = True else: obj["fabric_forwarding_anycast_gateway"] = False elif intf_type in ("loopback", "management", "nve"): obj["name"] = normalize_interface( interface_table.get("interface") ) obj["admin_state"] = interface_table.get("admin_state") if obj["admin_state"] is None and intf_type == "loopback": # Some platforms don't have the 'admin_state' key. # For loopback interfaces it's safe to use the # 'state' key instead. obj["admin_state"] = interface_table.get("state") obj["description"] = interface_table.get("desc") elif intf_type == "portchannel": obj["name"] = normalize_interface( interface_table.get("interface") ) obj["admin_state"] = interface_table.get("admin_state") obj["description"] = interface_table.get("desc") obj["mtu"] = interface_table.get("eth_mtu") if obj["admin_state"] is None: # Some nxos platforms do not have the 'admin_state' key. # Use the 'state_rsn_desc' key instead to determine the # admin state of the interface. state_description = interface_table.get("state_rsn_desc") if state_description == "Administratively down": obj["admin_state"] = "down" elif state_description is not None: obj["admin_state"] = "up" objs.append(obj) return objs def check_declarative_intent_params(module, want): failed_conditions = [] have_neighbors = None for w in want: if w["interface_type"]: continue want_tx_rate = w.get("tx_rate") want_rx_rate = w.get("rx_rate") want_neighbors = w.get("neighbors") if not (want_tx_rate or want_rx_rate or want_neighbors): continue time.sleep(module.params["delay"]) cmd = [ { "command": "show interface {0}".format(w["name"]), "output": "text", } ] try: out = run_commands(module, cmd, check_rc=False)[0] except (AttributeError, IndexError, TypeError): out = "" if want_tx_rate: match = re.search(r"output rate (\d+)", out, re.M) have_tx_rate = None if match: have_tx_rate = match.group(1) if have_tx_rate is None or not conditional( want_tx_rate, have_tx_rate.strip(), cast=int ): failed_conditions.append("tx_rate " + want_tx_rate) if want_rx_rate: match = re.search(r"input rate (\d+)", out, re.M) have_rx_rate = None if match: have_rx_rate = match.group(1) if have_rx_rate is None or not conditional( want_rx_rate, have_rx_rate.strip(), cast=int ): failed_conditions.append("rx_rate " + want_rx_rate) if want_neighbors: have_host = [] have_port = [] if have_neighbors is None: cmd = [ { "command": "show lldp neighbors interface {0} detail".format( w["name"] ), "output": "text", } ] output = run_commands(module, cmd, check_rc=False) if output: have_neighbors = output[0] else: have_neighbors = "" if ( have_neighbors and "Total entries displayed: 0" not in have_neighbors ): for line in have_neighbors.strip().split("\n"): if line.startswith("Port Description"): have_port.append(line.split(": ")[1]) if line.startswith("System Name"): have_host.append(line.split(": ")[1]) for item in want_neighbors: host = item.get("host") port = item.get("port") if host and host not in have_host: failed_conditions.append("host " + host) if port and port not in have_port: failed_conditions.append("port " + port) return failed_conditions def main(): """main entry point for module execution""" neighbors_spec = dict(host=dict(), port=dict()) element_spec = dict( name=dict(aliases=["interface"]), admin_state=dict(default="up", choices=["up", "down"]), description=dict(), speed=dict(), mode=dict(choices=["layer2", "layer3"]), mtu=dict(), duplex=dict(choices=["full", "half", "auto"]), interface_type=dict(choices=["loopback", "portchannel", "svi", "nve"]), ip_forward=dict(choices=["enable", "disable"]), fabric_forwarding_anycast_gateway=dict(type="bool"), tx_rate=dict(), rx_rate=dict(), neighbors=dict(type="list", elements="dict", options=neighbors_spec), delay=dict(default=10, type="int"), state=dict( choices=["absent", "present", "default"], default="present" ), ) aggregate_spec = deepcopy(element_spec) aggregate_spec["name"] = 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, mutually_exclusive=[["name", "interface_type"]], ) ) argument_spec.update(element_spec) argument_spec.update(nxos_argument_spec) required_one_of = [["name", "aggregate", "interface_type"]] mutually_exclusive = [["name", "aggregate"], ["name", "interface_type"]] 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, module) commands = [] commands1, commands2 = map_obj_to_commands((want, have), module) commands.extend(commands1) if commands: if not module.check_mode: load_config(module, commands) result["changed"] = True # if the mode changes from L2 to L3, the admin state # seems to change after the API call, so adding a second API # call to ensure it's in the desired state. if commands2: load_config(module, commands2) commands.extend(commands2) commands = [cmd for cmd in commands if cmd != "configure"] result["commands"] = commands if result["changed"]: failed_conditions = check_declarative_intent_params(module, want) if failed_conditions: msg = "One or more conditional statements have not been satisfied" module.fail_json(msg=msg, failed_conditions=failed_conditions) module.exit_json(**result) if __name__ == "__main__": main()