1#
2# -*- coding: utf-8 -*-
3# Copyright 2019 Red Hat
4# GNU General Public License v3.0+
5# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
6"""
7The ios_lldp_interfaces class
8It is in this file where the current configuration (as dict)
9is compared to the provided configuration (as dict) and the command set
10necessary to bring the current configuration to its desired end-state is
11created
12"""
13
14from __future__ import absolute_import, division, print_function
15__metaclass__ = type
16
17
18from ansible.module_utils.network.common.cfg.base import ConfigBase
19from ansible.module_utils.network.common.utils import to_list
20from ansible.module_utils.network.ios.facts.facts import Facts
21from ansible.module_utils.network.ios.utils.utils import dict_to_set
22from ansible.module_utils.network.ios.utils.utils import remove_command_from_config_list, add_command_to_config_list
23from ansible.module_utils.network.ios.utils.utils import filter_dict_having_none_value, remove_duplicate_interface
24
25
26class Lldp_Interfaces(ConfigBase):
27    """
28    The ios_lldp_interfaces class
29    """
30
31    gather_subset = [
32        '!all',
33        '!min',
34    ]
35
36    gather_network_resources = [
37        'lldp_interfaces',
38    ]
39
40    def __init__(self, module):
41        super(Lldp_Interfaces, self).__init__(module)
42
43    def get_lldp_interfaces_facts(self):
44        """ Get the 'facts' (the current configuration)
45
46        :rtype: A dictionary
47        :returns: The current configuration as a dictionary
48        """
49        facts, _warnings = Facts(self._module).get_facts(self.gather_subset, self.gather_network_resources)
50        lldp_interfaces_facts = facts['ansible_network_resources'].get('lldp_interfaces')
51
52        if not lldp_interfaces_facts:
53            return []
54        return lldp_interfaces_facts
55
56    def execute_module(self):
57        """ Execute the module
58
59        :rtype: A dictionary
60        :returns: The result from module execution
61        """
62        result = {'changed': False}
63        commands = list()
64        warnings = list()
65
66        existing_lldp_interfaces_facts = self.get_lldp_interfaces_facts()
67        commands.extend(self.set_config(existing_lldp_interfaces_facts))
68        if commands:
69            if not self._module.check_mode:
70                self._connection.edit_config(commands)
71            result['changed'] = True
72        result['commands'] = commands
73
74        changed_lldp_interfaces_facts = self.get_lldp_interfaces_facts()
75
76        result['before'] = existing_lldp_interfaces_facts
77        if result['changed']:
78            result['after'] = changed_lldp_interfaces_facts
79
80        result['warnings'] = warnings
81
82        return result
83
84    def set_config(self, existing_lldp_interfaces_facts):
85        """ Collect the configuration from the args passed to the module,
86            collect the current configuration (as a dict from facts)
87
88        :rtype: A list
89        :returns: the commands necessary to migrate the current configuration
90                  to the desired configuration
91        """
92        want = self._module.params['config']
93        have = existing_lldp_interfaces_facts
94        resp = self.set_state(want, have)
95
96        return to_list(resp)
97
98    def set_state(self, want, have):
99        """ Select the appropriate function based on the state provided
100
101        :param want: the desired configuration as a dictionary
102        :param have: the current configuration as a dictionary
103        :rtype: A list
104        :returns: the commands necessary to migrate the current configuration
105                  to the desired configuration
106        """
107        state = self._module.params['state']
108        if state in ('overridden', 'merged', 'replaced') and not want:
109            self._module.fail_json(msg='value of config parameter must not be empty for state {0}'.format(state))
110
111        if state == 'overridden':
112            commands = self._state_overridden(want, have)
113        elif state == 'deleted':
114            commands = self._state_deleted(want, have)
115        elif state == 'merged':
116            commands = self._state_merged(want, have)
117        elif state == 'replaced':
118            commands = self._state_replaced(want, have)
119
120        return commands
121
122    def _state_replaced(self, want, have):
123        """ The command generator when state is replaced
124
125        :rtype: A list
126        :returns: the commands necessary to migrate the current configuration
127                  to the desired configuration
128        """
129        commands = []
130
131        for interface in want:
132            for each in have:
133                if each['name'] == interface['name']:
134                    break
135            else:
136                continue
137            have_dict = filter_dict_having_none_value(interface, each)
138            commands.extend(self._clear_config(dict(), have_dict))
139            commands.extend(self._set_config(interface, each))
140        # Remove the duplicate interface call
141        commands = remove_duplicate_interface(commands)
142
143        return commands
144
145    def _state_overridden(self, want, have):
146        """ The command generator when state is overridden
147
148        :rtype: A list
149        :returns: the commands necessary to migrate the current configuration
150                  to the desired configuration
151        """
152        commands = []
153
154        for each in have:
155            for interface in want:
156                if each['name'] == interface['name']:
157                    break
158            else:
159                # We didn't find a matching desired state, which means we can
160                # pretend we recieved an empty desired state.
161                interface = dict(name=each['name'])
162                commands.extend(self._clear_config(interface, each))
163                continue
164            have_dict = filter_dict_having_none_value(interface, each)
165            commands.extend(self._clear_config(dict(), have_dict))
166            commands.extend(self._set_config(interface, each))
167        # Remove the duplicate interface call
168        commands = remove_duplicate_interface(commands)
169
170        return commands
171
172    def _state_merged(self, want, have):
173        """ The command generator when state is merged
174
175        :rtype: A list
176        :returns: the commands necessary to merge the provided into
177                  the current configuration
178        """
179        commands = []
180
181        for interface in want:
182            for each in have:
183                if interface['name'] == each['name']:
184                    break
185            else:
186                continue
187            commands.extend(self._set_config(interface, each))
188
189        return commands
190
191    def _state_deleted(self, want, have):
192        """ The command generator when state is deleted
193
194        :rtype: A list
195        :returns: the commands necessary to remove the current configuration
196                  of the provided objects
197        """
198        commands = []
199
200        if want:
201            for interface in want:
202                for each in have:
203                    if each['name'] == interface['name']:
204                        break
205                else:
206                    continue
207                interface = dict(name=interface['name'])
208                commands.extend(self._clear_config(interface, each))
209        else:
210            for each in have:
211                commands.extend(self._clear_config(dict(), each))
212
213        return commands
214
215    def _set_config(self, want, have):
216        # Set the interface config based on the want and have config
217        commands = []
218
219        interface = 'interface ' + have['name']
220        # Get the diff b/w want and have
221        want_dict = dict_to_set(want)
222        have_dict = dict_to_set(have)
223        diff = want_dict - have_dict
224
225        if diff:
226            diff = dict(diff)
227            receive = diff.get('receive')
228            transmit = diff.get('transmit')
229            med_tlv_select = diff.get('med_tlv_select')
230            tlv_select = diff.get('tlv_select')
231            if receive:
232                cmd = 'lldp receive'
233                add_command_to_config_list(interface, cmd, commands)
234            elif receive is False:
235                cmd = 'no lldp receive'
236                add_command_to_config_list(interface, cmd, commands)
237            if transmit:
238                cmd = 'lldp transmit'
239                add_command_to_config_list(interface, cmd, commands)
240            elif transmit is False:
241                cmd = 'no lldp transmit'
242                add_command_to_config_list(interface, cmd, commands)
243
244            if med_tlv_select:
245                med_tlv_select = dict(med_tlv_select)
246                if med_tlv_select.get('inventory_management'):
247                    add_command_to_config_list(interface, 'lldp med-tlv-select inventory-management', commands)
248            if tlv_select:
249                tlv_select = dict(tlv_select)
250                if tlv_select.get('power_management'):
251                    add_command_to_config_list(interface, 'lldp tlv-select power-management', commands)
252
253        return commands
254
255    def _clear_config(self, want, have):
256        # Delete the interface config based on the want and have config
257        commands = []
258        if want.get('name'):
259            interface = 'interface ' + want['name']
260        else:
261            interface = 'interface ' + have['name']
262
263        if have.get('receive') and have.get('receive') != want.get('receive'):
264            cmd = 'lldp receive'
265            remove_command_from_config_list(interface, cmd, commands)
266        if have.get('transmit') and have.get('transmit') != want.get('transmit'):
267            cmd = 'lldp transmit'
268            remove_command_from_config_list(interface, cmd, commands)
269
270        return commands
271