1#!/usr/bin/python 2# Copyright: Ansible Project 3# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 4 5from __future__ import absolute_import, division, print_function 6__metaclass__ = type 7 8 9ANSIBLE_METADATA = {'metadata_version': '1.1', 10 'status': ['preview'], 11 'supported_by': 'community'} 12 13 14DOCUMENTATION = """ 15--- 16module: icx_interface 17version_added: "2.9" 18author: "Ruckus Wireless (@Commscope)" 19short_description: Manage Interface on Ruckus ICX 7000 series switches 20description: 21 - This module provides declarative management of Interfaces 22 on ruckus icx devices. 23notes: 24 - Tested against ICX 10.1. 25 - For information on using ICX platform, see L(the ICX OS Platform Options guide,../network/user_guide/platform_icx.html). 26options: 27 name: 28 description: 29 - Name of the Interface. 30 type: str 31 description: 32 description: 33 - Name of the description. 34 type: str 35 enabled: 36 description: 37 - Interface link status 38 default: yes 39 type: bool 40 speed: 41 description: 42 - Interface link speed/duplex 43 choices: ['10-full', '10-half', '100-full', '100-half', '1000-full', '1000-full-master', 44 '1000-full-slave', '10g-full', '10g-full-master', '10g-full-slave', '2500-full', '2500-full-master', 45 '2500-full-slave', '5g-full', '5g-full-master', '5g-full-slave', 'auto'] 46 type: str 47 stp: 48 description: 49 - enable/disable stp for the interface 50 type: bool 51 tx_rate: 52 description: 53 - Transmit rate in bits per second (bps). 54 - This is state check parameter only. 55 - Supports conditionals, see L(Conditionals in Networking Modules,../network/user_guide/network_working_with_command_output.html) 56 type: str 57 rx_rate: 58 description: 59 - Receiver rate in bits per second (bps). 60 - This is state check parameter only. 61 - Supports conditionals, see L(Conditionals in Networking Modules,../network/user_guide/network_working_with_command_output.html) 62 type: str 63 neighbors: 64 description: 65 - Check the operational state of given interface C(name) for CDP/LLDP neighbor. 66 - The following suboptions are available. 67 type: list 68 suboptions: 69 host: 70 description: 71 - "CDP/LLDP neighbor host for given interface C(name)." 72 type: str 73 port: 74 description: 75 - "CDP/LLDP neighbor port to which given interface C(name) is connected." 76 type: str 77 delay: 78 description: 79 - Time in seconds to wait before checking for the operational state on remote 80 device. This wait is applicable for operational state argument which are 81 I(state) with values C(up)/C(down), I(tx_rate) and I(rx_rate). 82 type: int 83 default: 10 84 state: 85 description: 86 - State of the Interface configuration, C(up) means present and 87 operationally up and C(down) means present and operationally C(down) 88 default: present 89 type: str 90 choices: ['present', 'absent', 'up', 'down'] 91 power: 92 description: 93 - Inline power on Power over Ethernet (PoE) ports. 94 type: dict 95 suboptions: 96 by_class: 97 description: 98 - "The range is 0-4" 99 - "The power limit based on class value for given interface C(name)" 100 choices: ['0', '1', '2', '3', '4'] 101 type: str 102 limit: 103 description: 104 - "The range is 1000-15400|30000mW. For PoH ports the range is 1000-95000mW" 105 - "The power limit based on actual power value for given interface C(name)" 106 type: str 107 priority: 108 description: 109 - "The range is 1 (highest) to 3 (lowest)" 110 - "The priority for power management or given interface C(name)" 111 choices: ['1', '2', '3'] 112 type: str 113 enabled: 114 description: 115 - "enable/disable the poe of the given interface C(name)" 116 default: no 117 type: bool 118 aggregate: 119 description: 120 - List of Interfaces definitions. 121 type: list 122 suboptions: 123 name: 124 description: 125 - Name of the Interface. 126 type: str 127 description: 128 description: 129 - Name of the description. 130 type: str 131 enabled: 132 description: 133 - Interface link status 134 type: bool 135 speed: 136 description: 137 - Interface link speed/duplex 138 choices: ['10-full', '10-half', '100-full', '100-half', '1000-full', '1000-full-master', 139 '1000-full-slave', '10g-full', '10g-full-master', '10g-full-slave', '2500-full', '2500-full-master', 140 '2500-full-slave', '5g-full', '5g-full-master', '5g-full-slave', 'auto'] 141 type: str 142 stp: 143 description: 144 - enable/disable stp for the interface 145 type: bool 146 tx_rate: 147 description: 148 - Transmit rate in bits per second (bps). 149 - This is state check parameter only. 150 - Supports conditionals, see L(Conditionals in Networking Modules,../network/user_guide/network_working_with_command_output.html) 151 type: str 152 rx_rate: 153 description: 154 - Receiver rate in bits per second (bps). 155 - This is state check parameter only. 156 - Supports conditionals, see L(Conditionals in Networking Modules,../network/user_guide/network_working_with_command_output.html) 157 type: str 158 neighbors: 159 description: 160 - Check the operational state of given interface C(name) for CDP/LLDP neighbor. 161 - The following suboptions are available. 162 type: list 163 suboptions: 164 host: 165 description: 166 - "CDP/LLDP neighbor host for given interface C(name)." 167 type: str 168 port: 169 description: 170 - "CDP/LLDP neighbor port to which given interface C(name) is connected." 171 type: str 172 delay: 173 description: 174 - Time in seconds to wait before checking for the operational state on remote 175 device. This wait is applicable for operational state argument which are 176 I(state) with values C(up)/C(down), I(tx_rate) and I(rx_rate). 177 type: int 178 state: 179 description: 180 - State of the Interface configuration, C(up) means present and 181 operationally up and C(down) means present and operationally C(down) 182 type: str 183 choices: ['present', 'absent', 'up', 'down'] 184 check_running_config: 185 description: 186 - Check running configuration. This can be set as environment variable. 187 - Module will use environment variable value(default:True), unless it is overridden, by specifying it as module parameter. 188 type: bool 189 power: 190 description: 191 - Inline power on Power over Ethernet (PoE) ports. 192 type: dict 193 suboptions: 194 by_class: 195 description: 196 - "The range is 0-4" 197 - "The power limit based on class value for given interface C(name)" 198 choices: ['0', '1', '2', '3', '4'] 199 type: str 200 limit: 201 description: 202 - "The range is 1000-15400|30000mW. For PoH ports the range is 1000-95000mW" 203 - "The power limit based on actual power value for given interface C(name)" 204 type: str 205 priority: 206 description: 207 - "The range is 1 (highest) to 3 (lowest)" 208 - "The priority for power management or given interface C(name)" 209 choices: ['1', '2', '3'] 210 type: str 211 enabled: 212 description: 213 - "enable/disable the poe of the given interface C(name)" 214 type: bool 215 check_running_config: 216 description: 217 - Check running configuration. This can be set as environment variable. 218 - Module will use environment variable value(default:True), unless it is overridden, 219 by specifying it as module parameter. 220 default: yes 221 type: bool 222""" 223 224EXAMPLES = """ 225- name: enable ethernet port and set name 226 icx_interface: 227 name: ethernet 1/1/1 228 description: interface-1 229 stp: true 230 enabled: true 231 232- name: disable ethernet port 1/1/1 233 icx_interface: 234 name: ethernet 1/1/1 235 enabled: false 236 237- name: enable ethernet port range, set name and speed. 238 icx_interface: 239 name: ethernet 1/1/1 to 1/1/10 240 description: interface-1 241 speed: 100-full 242 enabled: true 243 244- name: enable poe. Set class. 245 icx_interface: 246 name: ethernet 1/1/1 247 power: 248 by_class: 2 249 250- name: configure poe limit of interface 251 icx_interface: 252 name: ethernet 1/1/1 253 power: 254 limit: 10000 255 256- name: disable poe of interface 257 icx_interface: 258 name: ethernet 1/1/1 259 power: 260 enabled: false 261 262- name: set lag name for a range of lags 263 icx_interface: 264 name: lag 1 to 10 265 description: test lags 266 267- name: Disable lag 268 icx_interface: 269 name: lag 1 270 enabled: false 271 272- name: enable management interface 273 icx_interface: 274 name: management 1 275 enabled: true 276 277- name: enable loopback interface 278 icx_interface: 279 name: loopback 10 280 enabled: true 281 282- name: Add interface using aggregate 283 icx_interface: 284 aggregate: 285 - { name: ethernet 1/1/1, description: test-interface-1, power: { by_class: 2 } } 286 - { name: ethernet 1/1/3, description: test-interface-3} 287 speed: 10-full 288 enabled: true 289 290- name: Check tx_rate, rx_rate intent arguments 291 icx_interface: 292 name: ethernet 1/1/10 293 state: up 294 tx_rate: ge(0) 295 rx_rate: le(0) 296 297- name: Check neighbors intent arguments 298 icx_interface: 299 name: ethernet 1/1/10 300 neighbors: 301 - port: 1/1/5 302 host: netdev 303""" 304 305RETURN = """ 306commands: 307 description: The list of configuration mode commands to send to the device. 308 returned: always 309 type: list 310 sample: 311 - interface ethernet 1/1/1 312 - port-name interface-1 313 - state present 314 - speed-duplex 100-full 315 - inline power priority 1 316""" 317 318import re 319from copy import deepcopy 320from time import sleep 321from ansible.module_utils._text import to_text 322from ansible.module_utils.basic import AnsibleModule, env_fallback 323from ansible.module_utils.network.common.config import NetworkConfig 324from ansible.module_utils.network.icx.icx import load_config, get_config 325from ansible.module_utils.connection import Connection, ConnectionError, exec_command 326from ansible.module_utils.network.common.utils import conditional, remove_default_spec 327 328 329def parse_enable(configobj, name): 330 cfg = configobj['interface %s' % name] 331 cfg = '\n'.join(cfg.children) 332 match = re.search(r'^disable', cfg, re.M) 333 if match: 334 return True 335 else: 336 return False 337 338 339def parse_power_argument(configobj, name): 340 cfg = configobj['interface %s' % name] 341 cfg = '\n'.join(cfg.children) 342 match = re.search(r'(^inline power|^inline power(.*))+$', cfg, re.M) 343 if match: 344 return match.group(1) 345 346 347def parse_config_argument(configobj, name, arg=None): 348 cfg = configobj['interface %s' % name] 349 cfg = '\n'.join(cfg.children) 350 match = re.search(r'%s (.+)$' % arg, cfg, re.M) 351 if match: 352 return match.group(1) 353 354 355def parse_stp_arguments(module, item): 356 rc, out, err = exec_command(module, 'show interfaces ' + item) 357 match = re.search(r'STP configured to (\S+),', out, re.S) 358 if match: 359 return True if match.group(1) == "ON" else False 360 361 362def search_obj_in_list(name, lst): 363 for o in lst: 364 if o['name'] == name: 365 return o 366 367 return None 368 369 370def validate_power(module, power): 371 count = 0 372 for item in power: 373 if power.get(item) is not None: 374 count += 1 375 if count > 1: 376 module.fail_json(msg='power parameters are mutually exclusive: class,limit,priority,enabled') 377 378 379def add_command_to_interface(interface, cmd, commands): 380 if interface not in commands: 381 commands.append(interface) 382 commands.append(cmd) 383 384 385def map_config_to_obj(module): 386 compare = module.params['check_running_config'] 387 config = get_config(module, None, compare) 388 configobj = NetworkConfig(indent=1, contents=config) 389 match = re.findall(r'^interface (.+)$', config, re.M) 390 391 if not match: 392 return list() 393 394 instances = list() 395 396 for item in set(match): 397 obj = { 398 'name': item, 399 'port-name': parse_config_argument(configobj, item, 'port-name'), 400 'speed-duplex': parse_config_argument(configobj, item, 'speed-duplex'), 401 'stp': parse_stp_arguments(module, item), 402 'disable': True if parse_enable(configobj, item) else False, 403 'power': parse_power_argument(configobj, item), 404 'state': 'present' 405 } 406 instances.append(obj) 407 return instances 408 409 410def parse_poe_config(poe, power): 411 if poe.get('by_class') is not None: 412 power += 'power-by-class %s' % poe.get('by_class') 413 elif poe.get('limit') is not None: 414 power += 'power-limit %s' % poe.get('limit') 415 elif poe.get('priority') is not None: 416 power += 'priority %s' % poe.get('priority') 417 elif poe.get('enabled'): 418 power = 'inline power' 419 elif poe.get('enabled') is False: 420 power = 'no inline power' 421 return power 422 423 424def map_params_to_obj(module): 425 obj = [] 426 aggregate = module.params.get('aggregate') 427 if aggregate: 428 for item in aggregate: 429 for key in item: 430 if item.get(key) is None: 431 item[key] = module.params[key] 432 433 item['port-name'] = item.pop('description') 434 item['speed-duplex'] = item.pop('speed') 435 poe = item.get('power') 436 if poe: 437 438 validate_power(module, poe) 439 power = 'inline power' + ' ' 440 power_arg = parse_poe_config(poe, power) 441 item.update({'power': power_arg}) 442 443 d = item.copy() 444 445 if d['enabled']: 446 d['disable'] = False 447 else: 448 d['disable'] = True 449 450 obj.append(d) 451 452 else: 453 params = { 454 'name': module.params['name'], 455 'port-name': module.params['description'], 456 'speed-duplex': module.params['speed'], 457 'stp': module.params['stp'], 458 'delay': module.params['delay'], 459 'state': module.params['state'], 460 'tx_rate': module.params['tx_rate'], 461 'rx_rate': module.params['rx_rate'], 462 'neighbors': module.params['neighbors'] 463 } 464 poe = module.params.get('power') 465 if poe: 466 validate_power(module, poe) 467 power = 'inline power' + ' ' 468 power_arg = parse_poe_config(poe, power) 469 params.update({'power': power_arg}) 470 471 if module.params['enabled']: 472 params.update({'disable': False}) 473 else: 474 params.update({'disable': True}) 475 476 obj.append(params) 477 return obj 478 479 480def map_obj_to_commands(updates): 481 commands = list() 482 want, have = updates 483 484 args = ('speed-duplex', 'port-name', 'power', 'stp') 485 for w in want: 486 name = w['name'] 487 disable = w['disable'] 488 state = w['state'] 489 490 obj_in_have = search_obj_in_list(name, have) 491 interface = 'interface ' + name 492 493 if state == 'absent' and have == []: 494 commands.append('no ' + interface) 495 496 elif state == 'absent' and obj_in_have: 497 commands.append('no ' + interface) 498 499 elif state in ('present', 'up', 'down'): 500 if obj_in_have: 501 for item in args: 502 candidate = w.get(item) 503 running = obj_in_have.get(item) 504 if candidate == 'no inline power' and running is None: 505 candidate = None 506 if candidate != running: 507 if candidate: 508 if item == 'power': 509 cmd = str(candidate) 510 elif item == 'stp': 511 cmd = 'spanning-tree' if candidate else 'no spanning-tree' 512 else: 513 cmd = item + ' ' + str(candidate) 514 add_command_to_interface(interface, cmd, commands) 515 516 if disable and not obj_in_have.get('disable', False): 517 add_command_to_interface(interface, 'disable', commands) 518 elif not disable and obj_in_have.get('disable', False): 519 add_command_to_interface(interface, 'enable', commands) 520 else: 521 commands.append(interface) 522 for item in args: 523 value = w.get(item) 524 if value: 525 if item == 'power': 526 commands.append(str(value)) 527 elif item == 'stp': 528 cmd = 'spanning-tree' if item else 'no spanning-tree' 529 else: 530 commands.append(item + ' ' + str(value)) 531 532 if disable: 533 commands.append('disable') 534 if disable is False: 535 commands.append('enable') 536 537 return commands 538 539 540def check_declarative_intent_params(module, want, result): 541 failed_conditions = [] 542 have_neighbors_lldp = None 543 have_neighbors_cdp = None 544 for w in want: 545 want_state = w.get('state') 546 want_tx_rate = w.get('tx_rate') 547 want_rx_rate = w.get('rx_rate') 548 want_neighbors = w.get('neighbors') 549 550 if want_state not in ('up', 'down') and not want_tx_rate and not want_rx_rate and not want_neighbors: 551 continue 552 553 if result['changed']: 554 sleep(w['delay']) 555 556 command = 'show interfaces %s' % w['name'] 557 rc, out, err = exec_command(module, command) 558 559 if rc != 0: 560 module.fail_json(msg=to_text(err, errors='surrogate_then_replace'), command=command, rc=rc) 561 562 if want_state in ('up', 'down'): 563 match = re.search(r'%s (\w+)' % 'line protocol is', out, re.M) 564 have_state = None 565 if match: 566 have_state = match.group(1) 567 if have_state is None or not conditional(want_state, have_state.strip()): 568 failed_conditions.append('state ' + 'eq(%s)' % want_state) 569 570 if want_tx_rate: 571 match = re.search(r'%s (\d+)' % 'output rate:', out, re.M) 572 have_tx_rate = None 573 if match: 574 have_tx_rate = match.group(1) 575 576 if have_tx_rate is None or not conditional(want_tx_rate, have_tx_rate.strip(), cast=int): 577 failed_conditions.append('tx_rate ' + want_tx_rate) 578 579 if want_rx_rate: 580 match = re.search(r'%s (\d+)' % 'input rate:', out, re.M) 581 have_rx_rate = None 582 if match: 583 have_rx_rate = match.group(1) 584 585 if have_rx_rate is None or not conditional(want_rx_rate, have_rx_rate.strip(), cast=int): 586 failed_conditions.append('rx_rate ' + want_rx_rate) 587 588 if want_neighbors: 589 have_host = [] 590 have_port = [] 591 592 if have_neighbors_lldp is None: 593 rc, have_neighbors_lldp, err = exec_command(module, 'show lldp neighbors detail') 594 if rc != 0: 595 module.fail_json(msg=to_text(err, errors='surrogate_then_replace'), command=command, rc=rc) 596 if have_neighbors_lldp: 597 lines = have_neighbors_lldp.strip().split('Local port: ') 598 599 for line in lines: 600 field = line.split('\n') 601 if field[0].strip() == w['name'].split(' ')[1]: 602 for item in field: 603 match = re.search(r'\s*\+\s+System name\s+:\s+"(.*)"', item, re.M) 604 if match: 605 have_host.append(match.group(1)) 606 607 match = re.search(r'\s*\+\s+Port description\s+:\s+"(.*)"', item, re.M) 608 if match: 609 have_port.append(match.group(1)) 610 611 for item in want_neighbors: 612 host = item.get('host') 613 port = item.get('port') 614 if host and host not in have_host: 615 failed_conditions.append('host ' + host) 616 if port and port not in have_port: 617 failed_conditions.append('port ' + port) 618 return failed_conditions 619 620 621def main(): 622 """ main entry point for module execution 623 """ 624 power_spec = dict( 625 by_class=dict(choices=['0', '1', '2', '3', '4']), 626 limit=dict(type='str'), 627 priority=dict(choices=['1', '2', '3']), 628 enabled=dict(type='bool') 629 ) 630 neighbors_spec = dict( 631 host=dict(), 632 port=dict() 633 ) 634 element_spec = dict( 635 name=dict(), 636 description=dict(), 637 enabled=dict(default=True, type='bool'), 638 speed=dict(type='str', choices=['10-full', '10-half', '100-full', '100-half', '1000-full', '1000-full-master', 639 '1000-full-slave', '10g-full', '10g-full-master', '10g-full-slave', '2500-full', '2500-full-master', 640 '2500-full-slave', '5g-full', '5g-full-master', '5g-full-slave', 'auto']), 641 stp=dict(type='bool'), 642 tx_rate=dict(), 643 rx_rate=dict(), 644 neighbors=dict(type='list', elements='dict', options=neighbors_spec), 645 delay=dict(default=10, type='int'), 646 state=dict(default='present', 647 choices=['present', 'absent', 'up', 'down']), 648 power=dict(type='dict', options=power_spec), 649 check_running_config=dict(default=True, type='bool', fallback=(env_fallback, ['ANSIBLE_CHECK_ICX_RUNNING_CONFIG'])) 650 ) 651 aggregate_spec = deepcopy(element_spec) 652 aggregate_spec['name'] = dict(required=True) 653 654 remove_default_spec(aggregate_spec) 655 656 argument_spec = dict( 657 aggregate=dict(type='list', elements='dict', options=aggregate_spec), 658 ) 659 argument_spec.update(element_spec) 660 661 required_one_of = [['name', 'aggregate']] 662 mutually_exclusive = [['name', 'aggregate']] 663 664 module = AnsibleModule(argument_spec=argument_spec, 665 required_one_of=required_one_of, 666 mutually_exclusive=mutually_exclusive, 667 supports_check_mode=True) 668 warnings = list() 669 result = {} 670 result['changed'] = False 671 if warnings: 672 result['warnings'] = warnings 673 exec_command(module, 'skip') 674 want = map_params_to_obj(module) 675 have = map_config_to_obj(module) 676 commands = map_obj_to_commands((want, have)) 677 result['commands'] = commands 678 679 if commands: 680 if not module.check_mode: 681 load_config(module, commands) 682 result['changed'] = True 683 684 failed_conditions = check_declarative_intent_params(module, want, result) 685 686 if failed_conditions: 687 msg = 'One or more conditional statements have not been satisfied' 688 module.fail_json(msg=msg, failed_conditions=failed_conditions) 689 690 module.exit_json(**result) 691 692 693if __name__ == '__main__': 694 main() 695