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 iosxr_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 it's 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    search_obj_in_list,
25    dict_diff,
26    remove_empties,
27)
28from ansible_collections.cisco.iosxr.plugins.module_utils.network.iosxr.facts.facts import (
29    Facts,
30)
31from ansible.module_utils.six import iteritems
32from ansible_collections.cisco.iosxr.plugins.module_utils.network.iosxr.utils.utils import (
33    dict_delete,
34    pad_commands,
35    flatten_dict,
36)
37
38
39class Lldp_interfaces(ConfigBase):
40    """
41    The iosxr_lldp_interfaces class
42    """
43
44    gather_subset = ["!all", "!min"]
45
46    gather_network_resources = ["lldp_interfaces"]
47
48    def __init__(self, module):
49        super(Lldp_interfaces, self).__init__(module)
50
51    def get_lldp_interfaces_facts(self, data=None):
52        """ Get the 'facts' (the current configuration)
53
54        :rtype: A dictionary
55        :returns: The current configuration as a dictionary
56        """
57        facts, _warnings = Facts(self._module).get_facts(
58            self.gather_subset, self.gather_network_resources, data=data
59        )
60        lldp_interfaces_facts = facts["ansible_network_resources"].get(
61            "lldp_interfaces"
62        )
63        if not lldp_interfaces_facts:
64            return []
65        return lldp_interfaces_facts
66
67    def execute_module(self):
68        """ Execute the module
69
70        :rtype: A dictionary
71        :returns: The result from module execution
72        """
73        result = {"changed": False}
74        warnings = list()
75        commands = list()
76
77        if self.state in self.ACTION_STATES:
78            existing_lldp_interfaces_facts = self.get_lldp_interfaces_facts()
79        else:
80            existing_lldp_interfaces_facts = []
81
82        if self.state in self.ACTION_STATES or self.state == "rendered":
83            commands.extend(self.set_config(existing_lldp_interfaces_facts))
84
85        if commands and self.state in self.ACTION_STATES:
86            if not self._module.check_mode:
87                self._connection.edit_config(commands)
88            result["changed"] = True
89
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
98        elif self.state == "parsed":
99            running_config = self._module.params["running_config"]
100            if not running_config:
101                self._module.fail_json(
102                    msg="value of running_config parameter must not be empty for state parsed"
103                )
104            result["parsed"] = self.get_lldp_interfaces_facts(
105                data=running_config
106            )
107
108        if self.state in self.ACTION_STATES:
109            result["before"] = existing_lldp_interfaces_facts
110            if result["changed"]:
111                result["after"] = changed_lldp_interfaces_facts
112        elif self.state == "gathered":
113            result["gathered"] = changed_lldp_interfaces_facts
114
115        result["warnings"] = warnings
116        return result
117
118    def set_config(self, existing_lldp_interfaces_facts):
119        """ Collect the configuration from the args passed to the module,
120            collect the current configuration (as a dict from facts)
121
122        :rtype: A list
123        :returns: the commands necessary to migrate the current configuration
124                  to the desired configuration
125        """
126        want = self._module.params["config"]
127        have = existing_lldp_interfaces_facts
128        resp = self.set_state(want, have)
129        return to_list(resp)
130
131    def set_state(self, want, have):
132        """ Select the appropriate function based on the state provided
133
134        :param want: the desired configuration as a dictionary
135        :param have: the current configuration as a dictionary
136        :rtype: A list
137        :returns: the commands necessary to migrate the current configuration
138                  to the desired configuration
139        """
140        state = self._module.params["state"]
141        commands = []
142        if (
143            state in ("overridden", "merged", "replaced", "rendered")
144            and not want
145        ):
146            self._module.fail_json(
147                msg="value of config parameter must not be empty for state {0}".format(
148                    state
149                )
150            )
151
152        if state == "overridden":
153            commands.extend(self._state_overridden(want, have))
154
155        elif state == "deleted":
156            if not want:
157                for intf in have:
158                    commands.extend(
159                        self._state_deleted({"name": intf["name"]}, intf)
160                    )
161            else:
162                for item in want:
163                    obj_in_have = search_obj_in_list(item["name"], have)
164                    commands.extend(self._state_deleted(item, obj_in_have))
165
166        else:
167            for item in want:
168                name = item["name"]
169                obj_in_have = search_obj_in_list(name, have)
170
171                if state in ("merged", "rendered"):
172                    commands.extend(self._state_merged(item, obj_in_have))
173
174                elif state == "replaced":
175                    commands.extend(self._state_replaced(item, obj_in_have))
176
177        return commands
178
179    def _state_replaced(self, want, have):
180        """ The command generator when state is replaced
181
182        :rtype: A list
183        :returns: the commands necessary to migrate the current configuration
184                  to the desired configuration
185        """
186        commands = []
187        replaced_commands = []
188        merged_commands = []
189
190        if have:
191            replaced_commands = self._state_deleted(want, have)
192
193        merged_commands = self._state_merged(want, have)
194
195        if merged_commands and replaced_commands:
196            del merged_commands[0]
197
198        commands.extend(replaced_commands)
199        commands.extend(merged_commands)
200
201        return commands
202
203    def _state_overridden(self, want, have):
204        """ The command generator when state is overridden
205
206        :rtype: A list
207        :returns: the commands necessary to migrate the current configuration
208                  to the desired configuration
209        """
210        commands = []
211
212        for intf in have:
213            intf_in_want = search_obj_in_list(intf["name"], want)
214            if not intf_in_want:
215                commands.extend(
216                    self._state_deleted({"name": intf["name"]}, intf)
217                )
218
219        for intf in want:
220            intf_in_have = search_obj_in_list(intf["name"], have)
221            commands.extend(self._state_replaced(intf, intf_in_have))
222
223        return commands
224
225    def _state_merged(self, want, have):
226        """ The command generator when state is merged
227
228        :rtype: A list
229        :returns: the commands necessary to merge the provided into
230                  the current configuration
231        """
232        commands = []
233        if not have:
234            have = {"name": want["name"]}
235
236        for key, value in iteritems(
237            flatten_dict(remove_empties(dict_diff(have, want)))
238        ):
239            commands.append(self._compute_commands(key, value))
240
241        if commands:
242            pad_commands(commands, want["name"])
243
244        return commands
245
246    def _state_deleted(self, want, have):
247        """ The command generator when state is deleted
248
249        :rtype: A list
250        :returns: the commands necessary to remove the current configuration
251                  of the provided objects
252        """
253        commands = []
254
255        for key, value in iteritems(
256            flatten_dict(dict_delete(have, remove_empties(want)))
257        ):
258            commands.append(self._compute_commands(key, value, remove=True))
259
260        if commands:
261            pad_commands(commands, have["name"])
262
263        return commands
264
265    def _compute_commands(self, key, value=None, remove=False):
266        if key == "mac_address":
267            cmd = "lldp destination mac-address {0}".format(value)
268            if remove:
269                return "no {0}".format(cmd)
270            else:
271                return cmd
272
273        else:
274            cmd = "lldp {0} disable".format(key)
275            if not value and not remove:
276                return cmd
277            elif (value and not remove) or (not value and remove):
278                return "no {0}".format(cmd)
279