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