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
16__metaclass__ = type
17
18
19from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base import (
20    ConfigBase,
21)
22from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import (
23    to_list,
24)
25from ansible_collections.cisco.ios.plugins.module_utils.network.ios.facts.facts import (
26    Facts,
27)
28from ansible_collections.cisco.ios.plugins.module_utils.network.ios.utils.utils import (
29    dict_to_set,
30    normalize_interface,
31)
32from ansible_collections.cisco.ios.plugins.module_utils.network.ios.utils.utils import (
33    remove_command_from_config_list,
34    add_command_to_config_list,
35)
36from ansible_collections.cisco.ios.plugins.module_utils.network.ios.utils.utils import (
37    filter_dict_having_none_value,
38    remove_duplicate_interface,
39)
40
41
42class Lldp_Interfaces(ConfigBase):
43    """
44    The ios_lldp_interfaces class
45    """
46
47    gather_subset = ["!all", "!min"]
48
49    gather_network_resources = ["lldp_interfaces"]
50
51    def __init__(self, module):
52        super(Lldp_Interfaces, self).__init__(module)
53
54    def get_lldp_interfaces_facts(self, data=None):
55        """ Get the 'facts' (the current configuration)
56
57        :rtype: A dictionary
58        :returns: The current configuration as a dictionary
59        """
60        facts, _warnings = Facts(self._module).get_facts(
61            self.gather_subset, self.gather_network_resources, data=data
62        )
63        lldp_interfaces_facts = facts["ansible_network_resources"].get(
64            "lldp_interfaces"
65        )
66        if not lldp_interfaces_facts:
67            return []
68        return lldp_interfaces_facts
69
70    def execute_module(self):
71        """ Execute the module
72
73        :rtype: A dictionary
74        :returns: The result from module execution
75        """
76        result = {"changed": False}
77        commands = list()
78        warnings = list()
79
80        if self.state in self.ACTION_STATES:
81            existing_lldp_interfaces_facts = self.get_lldp_interfaces_facts()
82        else:
83            existing_lldp_interfaces_facts = []
84        if self.state in self.ACTION_STATES or self.state == "rendered":
85            commands.extend(self.set_config(existing_lldp_interfaces_facts))
86        if commands and self.state in self.ACTION_STATES:
87            if not self._module.check_mode:
88                self._connection.edit_config(commands)
89            result["changed"] = True
90        if self.state in self.ACTION_STATES:
91            result["commands"] = commands
92
93        if self.state in self.ACTION_STATES or self.state == "gathered":
94            changed_lldp_interfaces_facts = self.get_lldp_interfaces_facts()
95        elif self.state == "rendered":
96            result["rendered"] = commands
97        elif self.state == "parsed":
98            running_config = self._module.params["running_config"]
99            if not running_config:
100                self._module.fail_json(
101                    msg="value of running_config parameter must not be empty for state parsed"
102                )
103            result["parsed"] = self.get_lldp_interfaces_facts(
104                data=running_config
105            )
106        else:
107            changed_lldp_interfaces_facts = []
108
109        if self.state in self.ACTION_STATES:
110            result["before"] = existing_lldp_interfaces_facts
111            if result["changed"]:
112                result["after"] = changed_lldp_interfaces_facts
113        elif self.state == "gathered":
114            result["gathered"] = changed_lldp_interfaces_facts
115
116        result["warnings"] = warnings
117
118        return result
119
120    def set_config(self, existing_lldp_interfaces_facts):
121        """ Collect the configuration from the args passed to the module,
122            collect the current configuration (as a dict from facts)
123
124        :rtype: A list
125        :returns: the commands necessary to migrate the current configuration
126                  to the desired configuration
127        """
128        config = self._module.params.get("config")
129        want = []
130        if config:
131            for each in config:
132                each.update({"name": normalize_interface(each["name"])})
133                want.append(each)
134        have = existing_lldp_interfaces_facts
135        resp = self.set_state(want, have)
136
137        return to_list(resp)
138
139    def set_state(self, want, have):
140        """ Select the appropriate function based on the state provided
141
142        :param want: the desired configuration as a dictionary
143        :param have: the current configuration as a dictionary
144        :rtype: A list
145        :returns: the commands necessary to migrate the current configuration
146                  to the desired configuration
147        """
148
149        if (
150            self.state in ("overridden", "merged", "replaced", "rendered")
151            and not want
152        ):
153            self._module.fail_json(
154                msg="value of config parameter must not be empty for state {0}".format(
155                    self.state
156                )
157            )
158
159        if self.state == "overridden":
160            commands = self._state_overridden(want, have)
161        elif self.state == "deleted":
162            commands = self._state_deleted(want, have)
163        elif self.state in ("merged", "rendered"):
164            commands = self._state_merged(want, have)
165        elif self.state == "replaced":
166            commands = self._state_replaced(want, have)
167
168        return commands
169
170    def _state_replaced(self, want, have):
171        """ The command generator when state is replaced
172
173        :rtype: A list
174        :returns: the commands necessary to migrate the current configuration
175                  to the desired configuration
176        """
177        commands = []
178
179        for interface in want:
180            for each in have:
181                if each["name"] == interface["name"]:
182                    break
183            else:
184                continue
185            have_dict = filter_dict_having_none_value(interface, each)
186            commands.extend(self._clear_config(dict(), have_dict))
187            commands.extend(self._set_config(interface, each))
188        # Remove the duplicate interface call
189        commands = remove_duplicate_interface(commands)
190
191        return commands
192
193    def _state_overridden(self, want, have):
194        """ The command generator when state is overridden
195
196        :rtype: A list
197        :returns: the commands necessary to migrate the current configuration
198                  to the desired configuration
199        """
200        commands = []
201
202        for each in have:
203            for interface in want:
204                if each["name"] == interface["name"]:
205                    break
206            else:
207                # We didn't find a matching desired state, which means we can
208                # pretend we received an empty desired state.
209                interface = dict(name=each["name"])
210                commands.extend(self._clear_config(interface, each))
211                continue
212            have_dict = filter_dict_having_none_value(interface, each)
213            commands.extend(self._clear_config(dict(), have_dict))
214            commands.extend(self._set_config(interface, each))
215        # Remove the duplicate interface call
216        commands = remove_duplicate_interface(commands)
217
218        return commands
219
220    def _state_merged(self, want, have):
221        """ The command generator when state is merged
222
223        :rtype: A list
224        :returns: the commands necessary to merge the provided into
225                  the current configuration
226        """
227        commands = []
228
229        for interface in want:
230            for each in have:
231                if interface["name"] == each["name"]:
232                    break
233            else:
234                if self.state == "rendered":
235                    commands.extend(self._set_config(interface, dict()))
236                continue
237            commands.extend(self._set_config(interface, each))
238
239        return commands
240
241    def _state_deleted(self, want, have):
242        """ The command generator when state is deleted
243
244        :rtype: A list
245        :returns: the commands necessary to remove the current configuration
246                  of the provided objects
247        """
248        commands = []
249
250        if want:
251            for interface in want:
252                for each in have:
253                    if each["name"] == interface["name"]:
254                        break
255                else:
256                    continue
257                interface = dict(name=interface["name"])
258                commands.extend(self._clear_config(interface, each))
259        else:
260            for each in have:
261                commands.extend(self._clear_config(dict(), each))
262
263        return commands
264
265    def _set_config(self, want, have):
266        # Set the interface config based on the want and have config
267        commands = []
268
269        interface = "interface " + want["name"]
270        # Get the diff b/w want and have
271        want_dict = dict_to_set(want)
272        have_dict = dict_to_set(have)
273        diff = want_dict - have_dict
274
275        if diff:
276            diff = dict(diff)
277            receive = diff.get("receive")
278            transmit = diff.get("transmit")
279            med_tlv_select = diff.get("med_tlv_select")
280            tlv_select = diff.get("tlv_select")
281            if receive:
282                cmd = "lldp receive"
283                add_command_to_config_list(interface, cmd, commands)
284            elif receive is False:
285                cmd = "no lldp receive"
286                add_command_to_config_list(interface, cmd, commands)
287            if transmit:
288                cmd = "lldp transmit"
289                add_command_to_config_list(interface, cmd, commands)
290            elif transmit is False:
291                cmd = "no lldp transmit"
292                add_command_to_config_list(interface, cmd, commands)
293
294            if med_tlv_select:
295                med_tlv_select = dict(med_tlv_select)
296                if med_tlv_select.get("inventory_management"):
297                    add_command_to_config_list(
298                        interface,
299                        "lldp med-tlv-select inventory-management",
300                        commands,
301                    )
302            if tlv_select:
303                tlv_select = dict(tlv_select)
304                if tlv_select.get("power_management"):
305                    add_command_to_config_list(
306                        interface, "lldp tlv-select power-management", commands
307                    )
308
309        return commands
310
311    def _clear_config(self, want, have):
312        # Delete the interface config based on the want and have config
313        commands = []
314        if want.get("name"):
315            interface = "interface " + want["name"]
316        else:
317            interface = "interface " + have["name"]
318
319        if have.get("receive") and have.get("receive") != want.get("receive"):
320            cmd = "lldp receive"
321            remove_command_from_config_list(interface, cmd, commands)
322        if have.get("transmit") and have.get("transmit") != want.get(
323            "transmit"
324        ):
325            cmd = "lldp transmit"
326            remove_command_from_config_list(interface, cmd, commands)
327
328        return commands
329