1#!/usr/bin/python 2# -*- coding: utf-8 -*- 3 4# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 5 6from __future__ import absolute_import, division, print_function 7__metaclass__ = type 8 9ANSIBLE_METADATA = {'metadata_version': '1.1', 10 'status': ['deprecated'], 11 'supported_by': 'network'} 12 13DOCUMENTATION = """ 14--- 15module: nxos_interface 16extends_documentation_fragment: nxos 17version_added: "2.1" 18short_description: Manages physical attributes of interfaces. 19description: 20 - Manages physical attributes of interfaces of NX-OS switches. 21deprecated: 22 removed_in: '2.13' 23 alternative: nxos_interfaces 24 why: Updated modules released with more functionality 25author: 26 - Jason Edelman (@jedelman8) 27 - Trishna Guha (@trishnaguha) 28notes: 29 - Tested against NXOSv 7.3.(0)D1(1) on VIRL 30 - This module is also used to create logical interfaces such as 31 svis and loopbacks. 32 - Be cautious of platform specific idiosyncrasies. For example, 33 when you default a loopback interface, the admin state toggles 34 on certain versions of NX-OS. 35 - The M(nxos_overlay_global) C(anycast_gateway_mac) attribute must be 36 set before setting the C(fabric_forwarding_anycast_gateway) property. 37options: 38 name: 39 description: 40 - Full name of interface, i.e. Ethernet1/1, port-channel10. 41 required: true 42 aliases: [interface] 43 interface_type: 44 description: 45 - Interface type to be unconfigured from the device. 46 choices: ['loopback', 'portchannel', 'svi', 'nve'] 47 version_added: 2.2 48 speed: 49 description: 50 - Interface link speed. Applicable for ethernet interface only. 51 version_added: 2.5 52 admin_state: 53 description: 54 - Administrative state of the interface. 55 default: up 56 choices: ['up','down'] 57 description: 58 description: 59 - Interface description. 60 mode: 61 description: 62 - Manage Layer 2 or Layer 3 state of the interface. 63 This option is supported for ethernet and portchannel interface. 64 Applicable for ethernet and portchannel interface only. 65 choices: ['layer2','layer3'] 66 mtu: 67 description: 68 - MTU for a specific interface. Must be an even number between 576 and 9216. 69 Applicable for ethernet interface only. 70 version_added: 2.5 71 ip_forward: 72 description: 73 - Enable/Disable ip forward feature on SVIs. 74 choices: ['enable','disable'] 75 version_added: 2.2 76 fabric_forwarding_anycast_gateway: 77 description: 78 - Associate SVI with anycast gateway under VLAN configuration mode. 79 Applicable for SVI interface only. 80 type: bool 81 version_added: 2.2 82 duplex: 83 description: 84 - Interface link status. Applicable for ethernet interface only. 85 default: auto 86 choices: ['full', 'half', 'auto'] 87 version_added: 2.5 88 tx_rate: 89 description: 90 - Transmit rate in bits per second (bps). 91 - This is state check parameter only. 92 - Supports conditionals, see L(Conditionals in Networking Modules,../network/user_guide/network_working_with_command_output.html) 93 version_added: 2.5 94 rx_rate: 95 description: 96 - Receiver rate in bits per second (bps). 97 - This is state check parameter only. 98 - Supports conditionals, see L(Conditionals in Networking Modules,../network/user_guide/network_working_with_command_output.html) 99 version_added: 2.5 100 neighbors: 101 description: 102 - Check the operational state of given interface C(name) for LLDP neighbor. 103 - The following suboptions are available. This is state check parameter only. 104 suboptions: 105 host: 106 description: 107 - "LLDP neighbor host for given interface C(name)." 108 port: 109 description: 110 - "LLDP neighbor port to which given interface C(name) is connected." 111 version_added: 2.5 112 aggregate: 113 description: List of Interfaces definitions. 114 version_added: 2.5 115 state: 116 description: 117 - Specify desired state of the resource. 118 default: present 119 choices: ['present','absent','default'] 120 delay: 121 description: 122 - Time in seconds to wait before checking for the operational state on remote 123 device. This wait is applicable for operational state arguments. 124 default: 10 125""" 126 127EXAMPLES = """ 128- name: Ensure an interface is a Layer 3 port and that it has the proper description 129 nxos_interface: 130 name: Ethernet1/1 131 description: 'Configured by Ansible' 132 mode: layer3 133 134- name: Admin down an interface 135 nxos_interface: 136 name: Ethernet2/1 137 admin_state: down 138 139- name: Remove all loopback interfaces 140 nxos_interface: 141 name: loopback 142 state: absent 143 144- name: Remove all logical interfaces 145 nxos_interface: 146 interface_type: "{{ item }} " 147 state: absent 148 loop: 149 - loopback 150 - portchannel 151 - svi 152 - nve 153 154- name: Admin up all loopback interfaces 155 nxos_interface: 156 name: loopback 0-1023 157 admin_state: up 158 159- name: Admin down all loopback interfaces 160 nxos_interface: 161 name: loopback 0-1023 162 admin_state: down 163 164- name: Check neighbors intent arguments 165 nxos_interface: 166 name: Ethernet2/3 167 neighbors: 168 - port: Ethernet2/3 169 host: abc.mycompany.com 170 171- name: Add interface using aggregate 172 nxos_interface: 173 aggregate: 174 - { name: Ethernet0/1, mtu: 256, description: test-interface-1 } 175 - { name: Ethernet0/2, mtu: 516, description: test-interface-2 } 176 duplex: full 177 speed: 100 178 state: present 179 180- name: Delete interface using aggregate 181 nxos_interface: 182 aggregate: 183 - name: Loopback9 184 - name: Loopback10 185 state: absent 186 187- name: Check intent arguments 188 nxos_interface: 189 name: Ethernet0/2 190 state: up 191 tx_rate: ge(0) 192 rx_rate: le(0) 193""" 194 195RETURN = """ 196commands: 197 description: command list sent to the device 198 returned: always 199 type: list 200 sample: 201 - interface Ethernet2/3 202 - mtu 1500 203 - speed 10 204""" 205 206import re 207import time 208 209from copy import deepcopy 210 211from ansible.module_utils.network.nxos.nxos import load_config, run_commands 212from ansible.module_utils.network.nxos.nxos import nxos_argument_spec, normalize_interface 213from ansible.module_utils.network.nxos.nxos import get_interface_type 214from ansible.module_utils.basic import AnsibleModule 215from ansible.module_utils.network.common.utils import conditional, remove_default_spec 216 217 218def execute_show_command(command, module): 219 if 'show run' not in command: 220 output = 'json' 221 else: 222 output = 'text' 223 cmds = [{ 224 'command': command, 225 'output': output, 226 }] 227 body = run_commands(module, cmds, check_rc=False) 228 if body and "Invalid" in body[0]: 229 return [] 230 else: 231 return body 232 233 234def search_obj_in_list(name, lst): 235 for o in lst: 236 if o['name'] == name: 237 return o 238 239 return None 240 241 242def get_interfaces_dict(module): 243 """Gets all active interfaces on a given switch 244 """ 245 try: 246 body = execute_show_command('show interface', module)[0] 247 except IndexError: 248 return {} 249 250 interfaces = { 251 'ethernet': [], 252 'svi': [], 253 'loopback': [], 254 'management': [], 255 'portchannel': [], 256 'nve': [], 257 'unknown': [] 258 } 259 260 if body: 261 interface_list = body['TABLE_interface']['ROW_interface'] 262 for index in interface_list: 263 intf = index['interface'] 264 intf_type = get_interface_type(intf) 265 interfaces[intf_type].append(intf) 266 267 return interfaces 268 269 270def get_vlan_interface_attributes(name, intf_type, module): 271 """ Returns dictionary that has two k/v pairs: 272 admin_state & description if not an svi, returns None 273 """ 274 command = 'show run interface {0} all'.format(name) 275 try: 276 body = execute_show_command(command, module)[0] 277 except (IndexError, TypeError): 278 return None 279 if body: 280 command_list = body.split('\n') 281 desc = None 282 admin_state = 'down' 283 for each in command_list: 284 if 'description' in each: 285 desc = each.lstrip().split("description")[1].lstrip() 286 elif 'no shutdown' in each: 287 admin_state = 'up' 288 return dict(description=desc, admin_state=admin_state) 289 else: 290 return None 291 292 293def get_interface_type_removed_cmds(interfaces): 294 commands = [] 295 296 for interface in interfaces: 297 if interface != 'Vlan1': 298 commands.append('no interface {0}'.format(interface)) 299 300 return commands 301 302 303def get_admin_state(admin_state): 304 command = '' 305 if admin_state == 'up': 306 command = 'no shutdown' 307 elif admin_state == 'down': 308 command = 'shutdown' 309 return command 310 311 312def is_default_interface(name, module): 313 """Checks to see if interface exists and if it is a default config 314 """ 315 command = 'show run interface {0}'.format(name) 316 317 try: 318 body = execute_show_command(command, module)[0] 319 except (IndexError, TypeError) as e: 320 body = '' 321 322 if body: 323 raw_list = body.split('\n') 324 found = False 325 for line in raw_list: 326 if line.startswith('interface'): 327 found = True 328 if found and line and not line.startswith('interface'): 329 return False 330 return True 331 332 else: 333 return 'DNE' 334 335 336def add_command_to_interface(interface, cmd, commands): 337 if interface not in commands: 338 commands.append(interface) 339 commands.append(cmd) 340 341 342def map_obj_to_commands(updates, module): 343 commands = list() 344 commands2 = list() 345 want, have = updates 346 347 args = ('speed', 'description', 'duplex', 'mtu') 348 for w in want: 349 name = w['name'] 350 mode = w['mode'] 351 ip_forward = w['ip_forward'] 352 fabric_forwarding_anycast_gateway = w['fabric_forwarding_anycast_gateway'] 353 admin_state = w['admin_state'] 354 state = w['state'] 355 interface_type = w['interface_type'] 356 del w['state'] 357 if name: 358 w['interface_type'] = None 359 360 if interface_type: 361 obj_in_have = {} 362 if state in ('present', 'default'): 363 module.fail_json(msg='The interface_type param can be used only with state absent.') 364 else: 365 obj_in_have = search_obj_in_list(name, have) 366 is_default = is_default_interface(name, module) 367 368 if name: 369 interface = 'interface ' + name 370 371 if state == 'absent': 372 if obj_in_have: 373 commands.append('no interface {0}'.format(name)) 374 elif interface_type and not obj_in_have: 375 intfs = get_interfaces_dict(module)[interface_type] 376 cmds = get_interface_type_removed_cmds(intfs) 377 commands.extend(cmds) 378 379 elif state == 'present': 380 if obj_in_have: 381 # Don't run switchport command for loopback and svi interfaces 382 if get_interface_type(name) in ('ethernet', 'portchannel'): 383 if mode == 'layer2' and mode != obj_in_have.get('mode'): 384 add_command_to_interface(interface, 'switchport', commands) 385 elif mode == 'layer3' and mode != obj_in_have.get('mode'): 386 add_command_to_interface(interface, 'no switchport', commands) 387 388 if admin_state == 'up' and admin_state != obj_in_have.get('admin_state'): 389 add_command_to_interface(interface, 'no shutdown', commands) 390 elif admin_state == 'down' and admin_state != obj_in_have.get('admin_state'): 391 add_command_to_interface(interface, 'shutdown', commands) 392 393 if ip_forward == 'enable' and ip_forward != obj_in_have.get('ip_forward'): 394 add_command_to_interface(interface, 'ip forward', commands) 395 elif ip_forward == 'disable' and ip_forward != obj_in_have.get('ip forward'): 396 add_command_to_interface(interface, 'no ip forward', commands) 397 398 if (fabric_forwarding_anycast_gateway is True and 399 obj_in_have.get('fabric_forwarding_anycast_gateway') is False): 400 add_command_to_interface(interface, 'fabric forwarding mode anycast-gateway', commands) 401 402 elif (fabric_forwarding_anycast_gateway is False and 403 obj_in_have.get('fabric_forwarding_anycast_gateway') is True): 404 add_command_to_interface(interface, 'no fabric forwarding mode anycast-gateway', commands) 405 406 for item in args: 407 candidate = w.get(item) 408 if candidate and candidate != obj_in_have.get(item): 409 cmd = item + ' ' + str(candidate) 410 add_command_to_interface(interface, cmd, commands) 411 412 if name and get_interface_type(name) == 'ethernet': 413 if mode != obj_in_have.get('mode'): 414 admin_state = w.get('admin_state') or obj_in_have.get('admin_state') 415 if admin_state: 416 c1 = 'interface {0}'.format(normalize_interface(w['name'])) 417 c2 = get_admin_state(admin_state) 418 commands2.append(c1) 419 commands2.append(c2) 420 421 else: 422 commands.append(interface) 423 # Don't run switchport command for loopback and svi interfaces 424 if get_interface_type(name) in ('ethernet', 'portchannel'): 425 if mode == 'layer2': 426 commands.append('switchport') 427 elif mode == 'layer3': 428 commands.append('no switchport') 429 430 if admin_state == 'up': 431 commands.append('no shutdown') 432 elif admin_state == 'down': 433 commands.append('shutdown') 434 435 if ip_forward == 'enable': 436 commands.append('ip forward') 437 elif ip_forward == 'disable': 438 commands.append('no ip forward') 439 440 if fabric_forwarding_anycast_gateway is True: 441 commands.append('fabric forwarding mode anycast-gateway') 442 443 elif fabric_forwarding_anycast_gateway is False: 444 commands.append('no fabric forwarding mode anycast-gateway') 445 446 for item in args: 447 candidate = w.get(item) 448 if candidate: 449 commands.append(item + ' ' + str(candidate)) 450 451 elif state == 'default': 452 if is_default is False: 453 commands.append('default interface {0}'.format(name)) 454 elif is_default == 'DNE': 455 module.exit_json(msg='interface you are trying to default does not exist') 456 457 return commands, commands2 458 459 460def map_params_to_obj(module): 461 obj = [] 462 aggregate = module.params.get('aggregate') 463 if aggregate: 464 for item in aggregate: 465 for key in item: 466 if item.get(key) is None: 467 item[key] = module.params[key] 468 469 d = item.copy() 470 name = d['name'] 471 d['name'] = normalize_interface(name) 472 obj.append(d) 473 474 else: 475 obj.append({ 476 'name': normalize_interface(module.params['name']), 477 'description': module.params['description'], 478 'speed': module.params['speed'], 479 'mode': module.params['mode'], 480 'mtu': module.params['mtu'], 481 'duplex': module.params['duplex'], 482 'ip_forward': module.params['ip_forward'], 483 'fabric_forwarding_anycast_gateway': module.params['fabric_forwarding_anycast_gateway'], 484 'admin_state': module.params['admin_state'], 485 'state': module.params['state'], 486 'interface_type': module.params['interface_type'], 487 'tx_rate': module.params['tx_rate'], 488 'rx_rate': module.params['rx_rate'], 489 'neighbors': module.params['neighbors'] 490 }) 491 492 return obj 493 494 495def map_config_to_obj(want, module): 496 objs = list() 497 498 for w in want: 499 obj = dict(name=None, description=None, admin_state=None, speed=None, 500 mtu=None, mode=None, duplex=None, interface_type=None, 501 ip_forward=None, fabric_forwarding_anycast_gateway=None) 502 503 if not w['name']: 504 return obj 505 506 command = 'show interface {0}'.format(w['name']) 507 try: 508 body = execute_show_command(command, module)[0] 509 except IndexError: 510 return list() 511 if body: 512 try: 513 interface_table = body['TABLE_interface']['ROW_interface'] 514 except (KeyError, TypeError): 515 return list() 516 517 if interface_table: 518 if interface_table.get('eth_mode') == 'fex-fabric': 519 module.fail_json(msg='nxos_interface does not support interfaces with mode "fex-fabric"') 520 521 intf_type = get_interface_type(w['name']) 522 523 if intf_type in ['portchannel', 'ethernet']: 524 mode = interface_table.get('eth_mode') 525 if mode in ('access', 'trunk', 'dot1q-tunnel'): 526 obj['mode'] = 'layer2' 527 elif mode in ('routed', 'layer3'): 528 obj['mode'] = 'layer3' 529 else: 530 obj['mode'] = 'layer3' 531 532 if intf_type == 'ethernet': 533 obj['name'] = normalize_interface(interface_table.get('interface')) 534 obj['admin_state'] = interface_table.get('admin_state') 535 obj['description'] = interface_table.get('desc') 536 obj['mtu'] = interface_table.get('eth_mtu') 537 obj['duplex'] = interface_table.get('eth_duplex') 538 speed = interface_table.get('eth_speed') 539 540 command = 'show run interface {0}'.format(obj['name']) 541 body = execute_show_command(command, module)[0] 542 543 speed_match = re.search(r'speed (\d+)', body) 544 if speed_match is None: 545 obj['speed'] = 'auto' 546 else: 547 obj['speed'] = speed_match.group(1) 548 549 duplex_match = re.search(r'duplex (\S+)', body) 550 if duplex_match is None: 551 obj['duplex'] = 'auto' 552 else: 553 obj['duplex'] = duplex_match.group(1) 554 555 if 'ip forward' in body: 556 obj['ip_forward'] = 'enable' 557 else: 558 obj['ip_forward'] = 'disable' 559 560 elif intf_type == 'svi': 561 obj['name'] = normalize_interface(interface_table.get('interface')) 562 attributes = get_vlan_interface_attributes(obj['name'], intf_type, module) 563 obj['admin_state'] = str(attributes.get('admin_state', 564 'nxapibug')) 565 obj['description'] = str(attributes.get('description', 566 'nxapi_bug')) 567 obj['mtu'] = interface_table.get('svi_mtu') 568 569 command = 'show run interface {0}'.format(obj['name']) 570 body = execute_show_command(command, module)[0] 571 if 'ip forward' in body: 572 obj['ip_forward'] = 'enable' 573 else: 574 obj['ip_forward'] = 'disable' 575 if 'fabric forwarding mode anycast-gateway' in body: 576 obj['fabric_forwarding_anycast_gateway'] = True 577 else: 578 obj['fabric_forwarding_anycast_gateway'] = False 579 580 elif intf_type in ('loopback', 'management', 'nve'): 581 obj['name'] = normalize_interface(interface_table.get('interface')) 582 obj['admin_state'] = interface_table.get('admin_state') 583 if obj['admin_state'] is None and intf_type == 'loopback': 584 # Some platforms don't have the 'admin_state' key. 585 # For loopback interfaces it's safe to use the 586 # 'state' key instead. 587 obj['admin_state'] = interface_table.get('state') 588 obj['description'] = interface_table.get('desc') 589 590 elif intf_type == 'portchannel': 591 obj['name'] = normalize_interface(interface_table.get('interface')) 592 obj['admin_state'] = interface_table.get('admin_state') 593 obj['description'] = interface_table.get('desc') 594 obj['mtu'] = interface_table.get('eth_mtu') 595 596 if obj['admin_state'] is None: 597 # Some nxos platforms do not have the 'admin_state' key. 598 # Use the 'state_rsn_desc' key instead to determine the 599 # admin state of the interface. 600 state_description = interface_table.get('state_rsn_desc') 601 if state_description == 'Administratively down': 602 obj['admin_state'] = 'down' 603 elif state_description is not None: 604 obj['admin_state'] = 'up' 605 606 objs.append(obj) 607 608 return objs 609 610 611def check_declarative_intent_params(module, want): 612 failed_conditions = [] 613 have_neighbors = None 614 for w in want: 615 if w['interface_type']: 616 continue 617 want_tx_rate = w.get('tx_rate') 618 want_rx_rate = w.get('rx_rate') 619 want_neighbors = w.get('neighbors') 620 if not (want_tx_rate or want_rx_rate or want_neighbors): 621 continue 622 623 time.sleep(module.params['delay']) 624 625 cmd = [{'command': 'show interface {0}'.format(w['name']), 'output': 'text'}] 626 627 try: 628 out = run_commands(module, cmd, check_rc=False)[0] 629 except (AttributeError, IndexError, TypeError): 630 out = '' 631 632 if want_tx_rate: 633 match = re.search(r'output rate (\d+)', out, re.M) 634 have_tx_rate = None 635 636 if match: 637 have_tx_rate = match.group(1) 638 639 if have_tx_rate is None or not conditional(want_tx_rate, have_tx_rate.strip(), cast=int): 640 failed_conditions.append('tx_rate ' + want_tx_rate) 641 642 if want_rx_rate: 643 match = re.search(r'input rate (\d+)', out, re.M) 644 have_rx_rate = None 645 646 if match: 647 have_rx_rate = match.group(1) 648 649 if have_rx_rate is None or not conditional(want_rx_rate, have_rx_rate.strip(), cast=int): 650 failed_conditions.append('rx_rate ' + want_rx_rate) 651 652 if want_neighbors: 653 have_host = [] 654 have_port = [] 655 if have_neighbors is None: 656 cmd = [{'command': 'show lldp neighbors interface {0} detail'.format(w['name']), 'output': 'text'}] 657 output = run_commands(module, cmd, check_rc=False) 658 if output: 659 have_neighbors = output[0] 660 else: 661 have_neighbors = '' 662 if have_neighbors and 'Total entries displayed: 0' not in have_neighbors: 663 for line in have_neighbors.strip().split('\n'): 664 if line.startswith('Port Description'): 665 have_port.append(line.split(': ')[1]) 666 if line.startswith('System Name'): 667 have_host.append(line.split(': ')[1]) 668 669 for item in want_neighbors: 670 host = item.get('host') 671 port = item.get('port') 672 if host and host not in have_host: 673 failed_conditions.append('host ' + host) 674 if port and port not in have_port: 675 failed_conditions.append('port ' + port) 676 677 return failed_conditions 678 679 680def main(): 681 """ main entry point for module execution 682 """ 683 neighbors_spec = dict( 684 host=dict(), 685 port=dict() 686 ) 687 688 element_spec = dict( 689 name=dict(aliases=['interface']), 690 admin_state=dict(default='up', choices=['up', 'down']), 691 description=dict(), 692 speed=dict(), 693 mode=dict(choices=['layer2', 'layer3']), 694 mtu=dict(), 695 duplex=dict(choices=['full', 'half', 'auto']), 696 interface_type=dict(choices=['loopback', 'portchannel', 'svi', 'nve']), 697 ip_forward=dict(choices=['enable', 'disable']), 698 fabric_forwarding_anycast_gateway=dict(type='bool'), 699 tx_rate=dict(), 700 rx_rate=dict(), 701 neighbors=dict(type='list', elements='dict', options=neighbors_spec), 702 delay=dict(default=10, type='int'), 703 state=dict(choices=['absent', 'present', 'default'], default='present') 704 ) 705 706 aggregate_spec = deepcopy(element_spec) 707 aggregate_spec['name'] = dict(required=True) 708 709 # remove default in aggregate spec, to handle common arguments 710 remove_default_spec(aggregate_spec) 711 712 argument_spec = dict( 713 aggregate=dict(type='list', elements='dict', options=aggregate_spec, 714 mutually_exclusive=[['name', 'interface_type']]) 715 ) 716 717 argument_spec.update(element_spec) 718 argument_spec.update(nxos_argument_spec) 719 720 required_one_of = [['name', 'aggregate', 'interface_type']] 721 mutually_exclusive = [['name', 'aggregate'], 722 ['name', 'interface_type']] 723 724 module = AnsibleModule(argument_spec=argument_spec, 725 required_one_of=required_one_of, 726 mutually_exclusive=mutually_exclusive, 727 supports_check_mode=True) 728 warnings = list() 729 730 result = {'changed': False} 731 if warnings: 732 result['warnings'] = warnings 733 734 want = map_params_to_obj(module) 735 have = map_config_to_obj(want, module) 736 737 commands = [] 738 commands1, commands2 = map_obj_to_commands((want, have), module) 739 commands.extend(commands1) 740 741 if commands: 742 if not module.check_mode: 743 load_config(module, commands) 744 result['changed'] = True 745 # if the mode changes from L2 to L3, the admin state 746 # seems to change after the API call, so adding a second API 747 # call to ensure it's in the desired state. 748 if commands2: 749 load_config(module, commands2) 750 commands.extend(commands2) 751 commands = [cmd for cmd in commands if cmd != 'configure'] 752 result['commands'] = commands 753 754 if result['changed']: 755 failed_conditions = check_declarative_intent_params(module, want) 756 757 if failed_conditions: 758 msg = 'One or more conditional statements have not been satisfied' 759 module.fail_json(msg=msg, failed_conditions=failed_conditions) 760 761 module.exit_json(**result) 762 763 764if __name__ == '__main__': 765 main() 766