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 junos_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"""
13from __future__ import absolute_import, division, print_function
14
15__metaclass__ = type
16
17from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base import (
18    ConfigBase,
19)
20from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import (
21    to_list,
22)
23from ansible_collections.junipernetworks.junos.plugins.module_utils.network.junos.facts.facts import (
24    Facts,
25)
26from ansible_collections.junipernetworks.junos.plugins.module_utils.network.junos.junos import (
27    locked_config,
28    load_config,
29    commit_configuration,
30    discard_changes,
31    tostring,
32)
33from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.netconf import (
34    build_root_xml_node,
35    build_child_xml_node,
36    build_subtree,
37)
38
39
40class Lldp_interfaces(ConfigBase):
41    """
42    The junos_lldp_interfaces class
43    """
44
45    gather_subset = ["!all", "!min"]
46
47    gather_network_resources = ["lldp_interfaces"]
48
49    def __init__(self, module):
50        super(Lldp_interfaces, self).__init__(module)
51
52    def get_lldp_interfaces_facts(self, data=None):
53        """ Get the 'facts' (the current configuration)
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        :rtype: A dictionary
70        :returns: The result from module execution
71        """
72        result = {"changed": False}
73        state = self._module.params["state"]
74        warnings = list()
75
76        if self.state in self.ACTION_STATES:
77            existing_lldp_interfaces_facts = self.get_lldp_interfaces_facts()
78        else:
79            existing_lldp_interfaces_facts = []
80        if state == "gathered":
81            existing_lldp_interfaces_facts = self.get_lldp_interfaces_facts()
82            result["gathered"] = existing_lldp_interfaces_facts
83        elif self.state == "parsed":
84            running_config = self._module.params["running_config"]
85            if not running_config:
86                self._module.fail_json(
87                    msg="value of running_config parameter must not be empty for state parsed"
88                )
89            result["parsed"] = self.get_lldp_interfaces_facts(
90                data=running_config
91            )
92        elif self.state == "rendered":
93            config_xmls = self.set_config(existing_lldp_interfaces_facts)
94            if config_xmls:
95                result["rendered"] = config_xmls[0]
96            else:
97                result["rendered"] = ""
98
99        else:
100            config_xmls = self.set_config(existing_lldp_interfaces_facts)
101            with locked_config(self._module):
102                for config_xml in to_list(config_xmls):
103                    diff = load_config(self._module, config_xml, [])
104
105                commit = not self._module.check_mode
106                if diff:
107                    if commit:
108                        commit_configuration(self._module)
109                    else:
110                        discard_changes(self._module)
111                    result["changed"] = True
112
113                    if self._module._diff:
114                        result["diff"] = {"prepared": diff}
115
116            result["commands"] = config_xmls
117
118            changed_lldp_interfaces_facts = self.get_lldp_interfaces_facts()
119
120            result["before"] = existing_lldp_interfaces_facts
121            if result["changed"]:
122                result["after"] = changed_lldp_interfaces_facts
123
124            result["warnings"] = warnings
125
126        return result
127
128    def set_config(self, existing_lldp_interfaces_facts):
129        """ Collect the configuration from the args passed to the module,
130            collect the current configuration (as a dict from facts)
131        :rtype: A list
132        :returns: the commands necessary to migrate the current configuration
133                  to the desired configuration
134        """
135        want = self._module.params["config"]
136        have = existing_lldp_interfaces_facts
137        resp = self.set_state(want, have)
138        return to_list(resp)
139
140    def set_state(self, want, have):
141        """ Select the appropriate function based on the state provided
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        root = build_root_xml_node("protocols")
149        lldp_intf_ele = build_subtree(root, "lldp")
150
151        state = self._module.params["state"]
152        if (
153            state in ("merged", "replaced", "overridden", "rendered")
154            and not want
155        ):
156            self._module.fail_json(
157                msg="value of config parameter must not be empty for state {0}".format(
158                    state
159                )
160            )
161        if state == "overridden":
162            config_xmls = self._state_overridden(want, have)
163        elif state == "deleted":
164            config_xmls = self._state_deleted(want, have)
165        elif state in ("merged", "rendered"):
166            config_xmls = self._state_merged(want, have)
167        elif state == "replaced":
168            config_xmls = self._state_replaced(want, have)
169
170        for xml in config_xmls:
171            lldp_intf_ele.append(xml)
172
173        return tostring(root)
174
175    def _state_replaced(self, want, have):
176        """ The xml configuration generator when state is replaced
177        :rtype: A list
178        :returns: the xml configuration necessary to migrate the current configuration
179                  to the desired configuration
180        """
181        lldp_intf_xml = []
182        lldp_intf_xml.extend(self._state_deleted(want, have))
183        lldp_intf_xml.extend(self._state_merged(want, have))
184
185        return lldp_intf_xml
186
187    def _state_overridden(self, want, have):
188        """ The xml configuration generator when state is overridden
189        :rtype: A list
190        :returns: the xml configuration necessary to migrate the current configuration
191                  to the desired configuration
192        """
193        lldp_intf_xmls_obj = []
194
195        # replace interface config with data in want
196        lldp_intf_xmls_obj.extend(self._state_replaced(want, have))
197
198        # delete interface config if interface in have not present in want
199        delete_obj = []
200        for have_obj in have:
201            for want_obj in want:
202                if have_obj["name"] == want_obj["name"]:
203                    break
204            else:
205                delete_obj.append(have_obj)
206
207        if len(delete_obj):
208            lldp_intf_xmls_obj.extend(self._state_deleted(delete_obj, have))
209
210        return lldp_intf_xmls_obj
211
212    def _state_merged(self, want, have):
213        """ The xml configuration generator when state is merged
214         :rtype: A list
215         :returns: the xml configuration necessary to merge the provided into
216                   the current configuration
217         """
218        lldp_intf_xml = []
219        for config in want:
220            lldp_intf_root = build_root_xml_node("interface")
221
222            if config.get("name"):
223                build_child_xml_node(lldp_intf_root, "name", config["name"])
224
225            if config.get("enabled") is not None:
226                if config["enabled"] is False:
227                    build_child_xml_node(lldp_intf_root, "disable")
228                else:
229                    build_child_xml_node(
230                        lldp_intf_root, "disable", None, {"delete": "delete"}
231                    )
232            else:
233                build_child_xml_node(
234                    lldp_intf_root, "disable", None, {"delete": "delete"}
235                )
236            lldp_intf_xml.append(lldp_intf_root)
237        return lldp_intf_xml
238
239    def _state_deleted(self, want, have):
240        """ The xml configuration generator when state is deleted
241        :rtype: A list
242        :returns: the xml configuration necessary to remove the current configuration
243                  of the provided objects
244        """
245        lldp_intf_xml = []
246        intf_obj = want
247
248        if not intf_obj:
249            # delete lldp interfaces attribute from all the existing interface
250            intf_obj = have
251
252        for config in intf_obj:
253            lldp_intf_root = build_root_xml_node("interface")
254            lldp_intf_root.attrib.update({"delete": "delete"})
255            build_child_xml_node(lldp_intf_root, "name", config["name"])
256
257            lldp_intf_xml.append(lldp_intf_root)
258
259        return lldp_intf_xml
260