1# Copyright (C) 2019  Red Hat, Inc.
2#
3# This program is free software: you can redistribute it and/or modify
4# it under the terms of the GNU General Public License as published by
5# the Free Software Foundation, either version 3 of the License, or
6# (at your option) any later version.
7#
8# This program is distributed in the hope that it will be useful,
9# but WITHOUT ANY WARRANTY; without even the implied warranty of
10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11# GNU General Public License for more details.
12#
13# You should have received a copy of the GNU General Public License
14# along with this program.  If not, see <http://www.gnu.org/licenses/>.
15"""
16The junos_vlans class
17It is in this file where the current configuration (as dict)
18is compared to the provided configuration (as dict) and the command set
19necessary to bring the current configuration to it's desired end-state is
20created
21"""
22from __future__ import absolute_import, division, print_function
23__metaclass__ = type
24
25
26from ansible.module_utils.network.common.cfg.base import ConfigBase
27from ansible.module_utils.network.common.utils import to_list
28from ansible.module_utils.network.junos.facts.facts import Facts
29from ansible.module_utils.network.junos.junos import (locked_config,
30                                                      load_config,
31                                                      commit_configuration,
32                                                      discard_changes,
33                                                      tostring)
34from ansible.module_utils.network.common.netconf import (build_root_xml_node,
35                                                         build_child_xml_node)
36
37
38class Vlans(ConfigBase):
39    """
40    The junos_vlans class
41    """
42
43    gather_subset = [
44        '!all',
45        '!min',
46    ]
47
48    gather_network_resources = [
49        'vlans',
50    ]
51
52    def __init__(self, module):
53        super(Vlans, self).__init__(module)
54
55    def get_vlans_facts(self):
56        """ Get the 'facts' (the current configuration)
57
58        :rtype: A dictionary
59        :returns: The current configuration as a dictionary
60        """
61        facts, _warnings = Facts(self._module).get_facts(
62            self.gather_subset, self.gather_network_resources)
63        vlans_facts = facts['ansible_network_resources'].get('vlans')
64        if not vlans_facts:
65            return []
66        return vlans_facts
67
68    def execute_module(self):
69        """ Execute the module
70
71        :rtype: A dictionary
72        :returns: The result from module execution
73        """
74        result = {'changed': False}
75        warnings = list()
76
77        existing_vlans_facts = self.get_vlans_facts()
78        config_xmls = self.set_config(existing_vlans_facts)
79
80        with locked_config(self._module):
81            for config_xml in to_list(config_xmls):
82                diff = load_config(self._module, config_xml, warnings)
83
84            commit = not self._module.check_mode
85            if diff:
86                if commit:
87                    commit_configuration(self._module)
88                else:
89                    discard_changes(self._module)
90                result['changed'] = True
91
92                if self._module._diff:
93                    result['diff'] = {'prepared': diff}
94
95        result['commands'] = config_xmls
96
97        changed_vlans_facts = self.get_vlans_facts()
98
99        result['before'] = existing_vlans_facts
100        if result['changed']:
101            result['after'] = changed_vlans_facts
102
103        result['warnings'] = warnings
104        return result
105
106    def set_config(self, existing_vlans_facts):
107        """ Collect the configuration from the args passed to the module,
108            collect the current configuration (as a dict from facts)
109
110        :rtype: A list
111        :returns: the commands necessary to migrate the current configuration
112                  to the desired configuration
113        """
114        want = self._module.params['config']
115        have = existing_vlans_facts
116        resp = self.set_state(want, have)
117        return to_list(resp)
118
119    def set_state(self, want, have):
120        """ Select the appropriate function based on the state provided
121
122        :param want: the desired configuration as a dictionary
123        :param have: the current configuration as a dictionary
124        :rtype: A list
125        :returns: the commands necessary to migrate the current configuration
126                  to the desired configuration
127        """
128        root = build_root_xml_node('vlans')
129        state = self._module.params['state']
130        if state == 'overridden':
131            config_xmls = self._state_overridden(want, have)
132        elif state == 'deleted':
133            config_xmls = self._state_deleted(want, have)
134        elif state == 'merged':
135            config_xmls = self._state_merged(want, have)
136        elif state == 'replaced':
137            config_xmls = self._state_replaced(want, have)
138
139        for xml in config_xmls:
140            root.append(xml)
141
142        return tostring(root)
143
144    def _state_replaced(self, want, have):
145        """ The command generator when state is replaced
146
147        :rtype: A list
148        :returns: the xml necessary to migrate the current configuration
149                  to the desired configuration
150        """
151        intf_xml = []
152        intf_xml.extend(self._state_deleted(want, have))
153        intf_xml.extend(self._state_merged(want, have))
154        return intf_xml
155
156    def _state_overridden(self, want, have):
157        """ The command generator when state is overridden
158
159        :rtype: A list
160        :returns: the xml necessary to migrate the current configuration
161                  to the desired configuration
162        """
163        intf_xml = []
164        intf_xml.extend(self._state_deleted(have, have))
165        intf_xml.extend(self._state_merged(want, have))
166        return intf_xml
167
168    def _state_merged(self, want, have):
169        """ The command generator when state is merged
170
171        :rtype: A list
172        :returns: the xml necessary to merge the provided into
173                  the current configuration
174        """
175        intf_xml = []
176
177        for config in want:
178            vlan_name = str(config['name'])
179            vlan_id = str(config['vlan_id'])
180            vlan_description = config.get('description')
181            vlan_root = build_root_xml_node('vlan')
182            build_child_xml_node(vlan_root, 'name', vlan_name)
183            build_child_xml_node(vlan_root, 'vlan-id', vlan_id)
184            if vlan_description:
185                build_child_xml_node(vlan_root, 'description',
186                                                vlan_description)
187            intf_xml.append(vlan_root)
188        return intf_xml
189
190    def _state_deleted(self, want, have):
191        """ The command generator when state is deleted
192
193        :rtype: A list
194        :returns: the xml necessary to remove the current configuration
195                  of the provided objects
196        """
197        intf_xml = []
198
199        if not want:
200            want = have
201
202        for config in want:
203            vlan_name = config['name']
204            vlan_root = build_root_xml_node('vlan')
205            vlan_root.attrib.update({'delete': 'delete'})
206            build_child_xml_node(vlan_root, 'name', vlan_name)
207            intf_xml.append(vlan_root)
208        return intf_xml
209