1#!/usr/local/bin/python3.8
2#
3# Copyright: Ansible Project
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
9DOCUMENTATION = '''
10---
11module: onyx_lldp_interface
12author: "Samer Deeb (@samerd)"
13short_description: Manage LLDP interfaces configuration on Mellanox ONYX network devices
14description:
15  - This module provides declarative management of LLDP interfaces
16    configuration on Mellanox ONYX network devices.
17options:
18  name:
19    description:
20      - Name of the interface LLDP should be configured on.
21  aggregate:
22    description: List of interfaces LLDP should be configured on.
23  purge:
24    description:
25      - Purge interfaces not defined in the aggregate parameter.
26    type: bool
27    default: false
28  state:
29    description:
30      - State of the LLDP configuration.
31    default: present
32    choices: ['present', 'absent', 'enabled', 'disabled']
33'''
34
35EXAMPLES = """
36- name: Configure LLDP on specific interfaces
37  onyx_lldp_interface:
38    name: Eth1/1
39    state: present
40
41- name: Disable LLDP on specific interfaces
42  onyx_lldp_interface:
43    name: Eth1/1
44    state: disabled
45
46- name: Enable LLDP on specific interfaces
47  onyx_lldp_interface:
48    name: Eth1/1
49    state: enabled
50
51- name: Delete LLDP on specific interfaces
52  onyx_lldp_interface:
53    name: Eth1/1
54    state: absent
55
56- name: Create aggregate of LLDP interface configurations
57  onyx_lldp_interface:
58    aggregate:
59    - { name: Eth1/1 }
60    - { name: Eth1/2 }
61    state: present
62
63- name: Delete aggregate of LLDP interface configurations
64  onyx_lldp_interface:
65    aggregate:
66    - { name: Eth1/1 }
67    - { name: Eth1/2 }
68    state: absent
69"""
70
71RETURN = """
72commands:
73  description: The list of configuration mode commands to send to the device
74  returned: always.
75  type: list
76  sample:
77    - interface ethernet 1/1 lldp transmit
78    - interface ethernet 1/1 lldp receive
79"""
80import re
81from copy import deepcopy
82
83from ansible.module_utils.basic import AnsibleModule
84from ansible.module_utils.six import iteritems
85from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import remove_default_spec
86
87from ansible_collections.mellanox.onyx.plugins.module_utils.network.onyx.onyx import BaseOnyxModule
88from ansible_collections.mellanox.onyx.plugins.module_utils.network.onyx.onyx import show_cmd
89
90
91class OnyxLldpInterfaceModule(BaseOnyxModule):
92    IF_NAME_REGEX = re.compile(r"^(Eth\d+\/\d+|Eth\d+\/\d+\d+)$")
93    _purge = False
94
95    @classmethod
96    def _get_element_spec(cls):
97        return dict(
98            name=dict(type='str'),
99            state=dict(default='present',
100                       choices=['present', 'absent', 'enabled', 'disabled']),
101        )
102
103    @classmethod
104    def _get_aggregate_spec(cls, element_spec):
105        aggregate_spec = deepcopy(element_spec)
106        aggregate_spec['name'] = dict(required=True)
107
108        # remove default in aggregate spec, to handle common arguments
109        remove_default_spec(aggregate_spec)
110        return aggregate_spec
111
112    def init_module(self):
113        """ module initialization
114        """
115        element_spec = self._get_element_spec()
116        aggregate_spec = self._get_aggregate_spec(element_spec)
117        argument_spec = dict(
118            aggregate=dict(type='list', elements='dict',
119                           options=aggregate_spec),
120            purge=dict(default=False, type='bool'),
121        )
122        argument_spec.update(element_spec)
123        required_one_of = [['name', 'aggregate']]
124        mutually_exclusive = [['name', 'aggregate']]
125        self._module = AnsibleModule(
126            argument_spec=argument_spec,
127            required_one_of=required_one_of,
128            mutually_exclusive=mutually_exclusive,
129            supports_check_mode=True)
130
131    def get_required_config(self):
132        self._required_config = list()
133        module_params = self._module.params
134        aggregate = module_params.get('aggregate')
135        self._purge = module_params.get('purge', False)
136        if aggregate:
137            for item in aggregate:
138                for key in item:
139                    if item.get(key) is None:
140                        item[key] = module_params[key]
141                self.validate_param_values(item, item)
142                req_item = item.copy()
143                self._required_config.append(req_item)
144        else:
145            params = {
146                'name': module_params['name'],
147                'state': module_params['state'],
148            }
149            self.validate_param_values(params)
150            self._required_config.append(params)
151
152    def _create_if_lldp_data(self, if_name, if_lldp_data):
153        return {
154            'name': if_name,
155            'receive': self.get_config_attr(if_lldp_data, 'Receive'),
156            'transmit': self.get_config_attr(if_lldp_data, 'Transmit'),
157        }
158
159    def _get_lldp_config(self):
160        return show_cmd(self._module, "show lldp interfaces")
161
162    def load_current_config(self):
163        # called in base class in run function
164        self._current_config = dict()
165        lldp_config = self._get_lldp_config()
166        if not lldp_config:
167            return
168        for if_name, if_lldp_data in iteritems(lldp_config):
169            match = self.IF_NAME_REGEX.match(if_name)
170            if not match:
171                continue
172            if if_lldp_data:
173                if_lldp_data = if_lldp_data[0]
174                self._current_config[if_name] = \
175                    self._create_if_lldp_data(if_name, if_lldp_data)
176
177    def _get_interface_cmd_name(self, if_name):
178        return if_name.replace("Eth", "ethernet ")
179
180    def _add_if_lldp_commands(self, if_name, flag, enable):
181        cmd_prefix = "interface %s " % self._get_interface_cmd_name(if_name)
182        lldp_cmd = "lldp %s" % flag
183        if not enable:
184            lldp_cmd = 'no %s' % lldp_cmd
185        self._commands.append(cmd_prefix + lldp_cmd)
186
187    def _gen_lldp_commands(self, if_name, req_state, curr_conf):
188        curr_receive = curr_conf.get('receive')
189        curr_transmit = curr_conf.get('transmit')
190        enable = (req_state == 'Enabled')
191        if curr_receive != req_state:
192            flag = 'receive'
193            self._add_if_lldp_commands(if_name, flag, enable)
194        if curr_transmit != req_state:
195            flag = 'transmit'
196            self._add_if_lldp_commands(if_name, flag, enable)
197
198    def generate_commands(self):
199        req_interfaces = set()
200        for req_conf in self._required_config:
201            state = req_conf['state']
202            if_name = req_conf['name']
203            if state in ('absent', 'disabled'):
204                req_state = 'Disabled'
205            else:
206                req_interfaces.add(if_name)
207                req_state = 'Enabled'
208            curr_conf = self._current_config.get(if_name, {})
209            self._gen_lldp_commands(if_name, req_state, curr_conf)
210        if self._purge:
211            for if_name, curr_conf in iteritems(self._current_config):
212                if if_name not in req_interfaces:
213                    req_state = 'Disabled'
214                    self._gen_lldp_commands(if_name, req_state, curr_conf)
215
216
217def main():
218    """ main entry point for module execution
219    """
220    OnyxLldpInterfaceModule.main()
221
222
223if __name__ == '__main__':
224    main()
225