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