1# -*- coding: utf-8 -*-
2# Copyright 2019 Red Hat
3# GNU General Public License v3.0+
4# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
5"""
6The eos_l2_interfaces class
7It is in this file where the current configuration (as dict)
8is compared to the provided configuration (as dict) and the command set
9necessary to bring the current configuration to it's desired end-state is
10created
11"""
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, param_list_to_dict
18from ansible.module_utils.network.eos.facts.facts import Facts
19from ansible.module_utils.network.eos.utils.utils import normalize_interface
20
21
22class L2_interfaces(ConfigBase):
23    """
24    The eos_l2_interfaces class
25    """
26
27    gather_subset = [
28        '!all',
29        '!min',
30    ]
31
32    gather_network_resources = [
33        'l2_interfaces',
34    ]
35
36    def get_l2_interfaces_facts(self):
37        """ Get the 'facts' (the current configuration)
38
39        :rtype: A dictionary
40        :returns: The current configuration as a dictionary
41        """
42        facts, _warnings = Facts(self._module).get_facts(self.gather_subset, self.gather_network_resources)
43        l2_interfaces_facts = facts['ansible_network_resources'].get('l2_interfaces')
44        if not l2_interfaces_facts:
45            return []
46        return l2_interfaces_facts
47
48    def execute_module(self):
49        """ Execute the module
50
51        :rtype: A dictionary
52        :returns: The result from module execution
53        """
54        result = {'changed': False}
55        commands = list()
56        warnings = list()
57
58        existing_l2_interfaces_facts = self.get_l2_interfaces_facts()
59        commands.extend(self.set_config(existing_l2_interfaces_facts))
60        if commands:
61            if not self._module.check_mode:
62                self._connection.edit_config(commands)
63            result['changed'] = True
64        result['commands'] = commands
65
66        changed_l2_interfaces_facts = self.get_l2_interfaces_facts()
67
68        result['before'] = existing_l2_interfaces_facts
69        if result['changed']:
70            result['after'] = changed_l2_interfaces_facts
71
72        result['warnings'] = warnings
73        return result
74
75    def set_config(self, existing_l2_interfaces_facts):
76        """ Collect the configuration from the args passed to the module,
77            collect the current configuration (as a dict from facts)
78
79        :rtype: A list
80        :returns: the commands necessary to migrate the current configuration
81                  to the desired configuration
82        """
83        want = self._module.params['config']
84        have = existing_l2_interfaces_facts
85        resp = self.set_state(want, have)
86        return to_list(resp)
87
88    def set_state(self, want, have):
89        """ Select the appropriate function based on the state provided
90
91        :param want: the desired configuration as a dictionary
92        :param have: the current configuration as a dictionary
93        :rtype: A list
94        :returns: the commands necessary to migrate the current configuration
95                  to the desired configuration
96        """
97        state = self._module.params['state']
98        want = param_list_to_dict(want)
99        have = param_list_to_dict(have)
100        if state == 'overridden':
101            commands = self._state_overridden(want, have)
102        elif state == 'deleted':
103            commands = self._state_deleted(want, have)
104        elif state == 'merged':
105            commands = self._state_merged(want, have)
106        elif state == 'replaced':
107            commands = self._state_replaced(want, have)
108        return commands
109
110    @staticmethod
111    def _state_replaced(want, have):
112        """ The command generator when state is replaced
113
114        :rtype: A list
115        :returns: the commands necessary to migrate the current configuration
116                  to the desired configuration
117        """
118        commands = []
119        for key, desired in want.items():
120            interface_name = normalize_interface(key)
121            if interface_name in have:
122                extant = have[interface_name]
123            else:
124                extant = dict()
125
126            intf_commands = set_interface(desired, extant)
127            intf_commands.extend(clear_interface(desired, extant))
128
129            if intf_commands:
130                commands.append("interface {0}".format(interface_name))
131                commands.extend(intf_commands)
132
133        return commands
134
135    @staticmethod
136    def _state_overridden(want, have):
137        """ The command generator when state is overridden
138
139        :rtype: A list
140        :returns: the commands necessary to migrate the current configuration
141                  to the desired configuration
142        """
143        commands = []
144        for key, extant in have.items():
145            if key in want:
146                desired = want[key]
147            else:
148                desired = dict()
149
150            intf_commands = set_interface(desired, extant)
151            intf_commands.extend(clear_interface(desired, extant))
152
153            if intf_commands:
154                commands.append("interface {0}".format(key))
155                commands.extend(intf_commands)
156
157        return commands
158
159    @staticmethod
160    def _state_merged(want, have):
161        """ The command generator when state is merged
162
163        :rtype: A list
164        :returns: the commands necessary to merge the provided into
165                  the current configuration
166        """
167        commands = []
168        for key, desired in want.items():
169            interface_name = normalize_interface(key)
170            if interface_name in have:
171                extant = have[interface_name]
172            else:
173                extant = dict()
174
175            intf_commands = set_interface(desired, extant)
176
177            if intf_commands:
178                commands.append("interface {0}".format(interface_name))
179                commands.extend(intf_commands)
180
181        return commands
182
183    @staticmethod
184    def _state_deleted(want, have):
185        """ The command generator when state is deleted
186
187        :rtype: A list
188        :returns: the commands necessary to remove the current configuration
189                  of the provided objects
190        """
191        commands = []
192        for key in want:
193            desired = dict()
194            if key in have:
195                extant = have[key]
196            else:
197                continue
198
199            intf_commands = clear_interface(desired, extant)
200
201            if intf_commands:
202                commands.append("interface {0}".format(key))
203                commands.extend(intf_commands)
204
205        return commands
206
207
208def set_interface(want, have):
209    commands = []
210    wants_access = want.get("access")
211    if wants_access:
212        access_vlan = wants_access.get("vlan")
213        if access_vlan and access_vlan != have.get("access", {}).get("vlan"):
214            commands.append("switchport access vlan {0}".format(access_vlan))
215
216    wants_trunk = want.get("trunk")
217    if wants_trunk:
218        has_trunk = have.get("trunk", {})
219        native_vlan = wants_trunk.get("native_vlan")
220        if native_vlan and native_vlan != has_trunk.get("native_vlan"):
221            commands.append("switchport trunk native vlan {0}".format(native_vlan))
222
223        allowed_vlans = want['trunk'].get("trunk_allowed_vlans")
224        if allowed_vlans:
225            allowed_vlans = ','.join(allowed_vlans)
226            commands.append("switchport trunk allowed vlan {0}".format(allowed_vlans))
227    return commands
228
229
230def clear_interface(want, have):
231    commands = []
232    if "access" in have and not want.get('access'):
233        commands.append("no switchport access vlan")
234
235    has_trunk = have.get("trunk") or {}
236    wants_trunk = want.get("trunk") or {}
237    if "trunk_allowed_vlans" in has_trunk and "trunk_allowed_vlans" not in wants_trunk:
238        commands.append("no switchport trunk allowed vlan")
239    if "native_vlan" in has_trunk and "native_vlan" not in wants_trunk:
240        commands.append("no switchport trunk native vlan")
241    return commands
242