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__metaclass__ = type
15
16from ansible.module_utils.network.common.cfg.base import ConfigBase
17from ansible.module_utils.network.common.utils import to_list
18from ansible.module_utils.network.junos.facts.facts import Facts
19from ansible.module_utils.network.junos.junos import locked_config, load_config, commit_configuration, discard_changes, tostring
20from ansible.module_utils.network.common.netconf import build_root_xml_node, build_child_xml_node, build_subtree
21
22
23class Lldp_interfaces(ConfigBase):
24    """
25    The junos_lldp_interfaces class
26    """
27
28    gather_subset = [
29        '!all',
30        '!min',
31    ]
32
33    gather_network_resources = [
34        'lldp_interfaces',
35    ]
36
37    def __init__(self, module):
38        super(Lldp_interfaces, self).__init__(module)
39
40    def get_lldp_interfaces_facts(self):
41        """ Get the 'facts' (the current configuration)
42        :rtype: A dictionary
43        :returns: The current configuration as a dictionary
44        """
45        facts, _warnings = Facts(self._module).get_facts(self.gather_subset, self.gather_network_resources)
46        lldp_interfaces_facts = facts['ansible_network_resources'].get('lldp_interfaces')
47        if not lldp_interfaces_facts:
48            return []
49        return lldp_interfaces_facts
50
51    def execute_module(self):
52        """ Execute the module
53        :rtype: A dictionary
54        :returns: The result from module execution
55        """
56        result = {'changed': False}
57        warnings = list()
58
59        existing_lldp_interfaces_facts = self.get_lldp_interfaces_facts()
60        config_xmls = self.set_config(existing_lldp_interfaces_facts)
61
62        with locked_config(self._module):
63            for config_xml in to_list(config_xmls):
64                diff = load_config(self._module, config_xml, warnings)
65
66            commit = not self._module.check_mode
67            if diff:
68                if commit:
69                    commit_configuration(self._module)
70                else:
71                    discard_changes(self._module)
72                result['changed'] = True
73
74                if self._module._diff:
75                    result['diff'] = {'prepared': diff}
76
77        result['commands'] = config_xmls
78
79        changed_lldp_interfaces_facts = self.get_lldp_interfaces_facts()
80
81        result['before'] = existing_lldp_interfaces_facts
82        if result['changed']:
83            result['after'] = changed_lldp_interfaces_facts
84
85        return result
86
87    def set_config(self, existing_lldp_interfaces_facts):
88        """ Collect the configuration from the args passed to the module,
89            collect the current configuration (as a dict from facts)
90        :rtype: A list
91        :returns: the commands necessary to migrate the current configuration
92                  to the desired configuration
93        """
94        want = self._module.params['config']
95        have = existing_lldp_interfaces_facts
96        resp = self.set_state(want, have)
97        return to_list(resp)
98
99    def set_state(self, want, have):
100        """ Select the appropriate function based on the state provided
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        root = build_root_xml_node('protocols')
108        lldp_intf_ele = build_subtree(root, 'lldp')
109
110        state = self._module.params['state']
111        if state == 'overridden':
112            config_xmls = self._state_overridden(want, have)
113        elif state == 'deleted':
114            config_xmls = self._state_deleted(want, have)
115        elif state == 'merged':
116            config_xmls = self._state_merged(want, have)
117        elif state == 'replaced':
118            config_xmls = self._state_replaced(want, have)
119
120        for xml in config_xmls:
121            lldp_intf_ele.append(xml)
122
123        return tostring(root)
124
125    def _state_replaced(self, want, have):
126        """ The xml configuration generator when state is replaced
127        :rtype: A list
128        :returns: the xml configuration necessary to migrate the current configuration
129                  to the desired configuration
130        """
131        lldp_intf_xml = []
132        lldp_intf_xml.extend(self._state_deleted(want, have))
133        lldp_intf_xml.extend(self._state_merged(want, have))
134
135        return lldp_intf_xml
136
137    def _state_overridden(self, want, have):
138        """ The xml configuration generator when state is overridden
139        :rtype: A list
140        :returns: the xml configuration necessary to migrate the current configuration
141                  to the desired configuration
142        """
143        lldp_intf_xmls_obj = []
144
145        # replace interface config with data in want
146        lldp_intf_xmls_obj.extend(self._state_replaced(want, have))
147
148        # delete interface config if interface in have not present in want
149        delete_obj = []
150        for have_obj in have:
151            for want_obj in want:
152                if have_obj['name'] == want_obj['name']:
153                    break
154            else:
155                delete_obj.append(have_obj)
156
157        if len(delete_obj):
158            lldp_intf_xmls_obj.extend(self._state_deleted(delete_obj, have))
159
160        return lldp_intf_xmls_obj
161
162    def _state_merged(self, want, have):
163        """ The xml configuration generator when state is merged
164         :rtype: A list
165         :returns: the xml configuration necessary to merge the provided into
166                   the current configuration
167         """
168        lldp_intf_xml = []
169        for config in want:
170            lldp_intf_root = build_root_xml_node('interface')
171
172            if config.get('name'):
173                build_child_xml_node(lldp_intf_root, 'name', config['name'])
174
175            if config.get('enabled') is not None:
176                if config['enabled'] is False:
177                    build_child_xml_node(lldp_intf_root, 'disable')
178                else:
179                    build_child_xml_node(lldp_intf_root, 'disable', None, {'delete': 'delete'})
180            else:
181                build_child_xml_node(lldp_intf_root, 'disable', None, {'delete': 'delete'})
182            lldp_intf_xml.append(lldp_intf_root)
183        return lldp_intf_xml
184
185    def _state_deleted(self, want, have):
186        """ The xml configuration generator when state is deleted
187        :rtype: A list
188        :returns: the xml configuration necessary to remove the current configuration
189                  of the provided objects
190        """
191        lldp_intf_xml = []
192        intf_obj = want
193
194        if not intf_obj:
195            # delete lldp interfaces attribute from all the existing interface
196            intf_obj = have
197
198        for config in intf_obj:
199            lldp_intf_root = build_root_xml_node('interface')
200            lldp_intf_root.attrib.update({'delete': 'delete'})
201            build_child_xml_node(lldp_intf_root, 'name', config['name'])
202
203            lldp_intf_xml.append(lldp_intf_root)
204
205        return lldp_intf_xml
206