1# -*- coding: utf-8 -*-
2# Copyright 2019 Red Hat Inc.
3# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
4"""
5The iosxr_interfaces class
6It is in this file where the current configuration (as dict)
7is compared to the provided configuration (as dict) and the command set
8necessary to bring the current configuration to it's desired end-state is
9created
10"""
11
12from __future__ import absolute_import, division, print_function
13__metaclass__ = type
14
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.iosxr.facts.facts import Facts
19from ansible.module_utils.network.iosxr.utils.utils import get_interface_type, dict_to_set
20from ansible.module_utils.network.iosxr.utils.utils import remove_command_from_config_list, add_command_to_config_list
21from ansible.module_utils.network.iosxr.utils.utils import filter_dict_having_none_value, remove_duplicate_interface
22
23
24class Interfaces(ConfigBase):
25    """
26    The iosxr_interfaces class
27    """
28
29    gather_subset = [
30        '!all',
31        '!min',
32    ]
33
34    gather_network_resources = [
35        'interfaces',
36    ]
37
38    params = ('description', 'mtu', 'speed', 'duplex')
39
40    def __init__(self, module):
41        super(Interfaces, self).__init__(module)
42
43    def get_interfaces_facts(self):
44        """ Get the 'facts' (the current configuration)
45        :rtype: A dictionary
46        :returns: The current configuration as a dictionary
47        """
48        facts, _warnings = Facts(self._module).get_facts(self.gather_subset, self.gather_network_resources)
49        interfaces_facts = facts['ansible_network_resources'].get('interfaces')
50        if not interfaces_facts:
51            return []
52        return interfaces_facts
53
54    def execute_module(self):
55        """ Execute the module
56        :rtype: A dictionary
57        :returns: The result from module execution
58        """
59        result = {'changed': False}
60        commands = list()
61        warnings = list()
62
63        existing_interfaces_facts = self.get_interfaces_facts()
64        commands.extend(self.set_config(existing_interfaces_facts))
65        if commands:
66            if not self._module.check_mode:
67                self._connection.edit_config(commands)
68            result['changed'] = True
69        result['commands'] = commands
70
71        changed_interfaces_facts = self.get_interfaces_facts()
72
73        result['before'] = existing_interfaces_facts
74        if result['changed']:
75            result['after'] = changed_interfaces_facts
76
77        result['warnings'] = warnings
78        return result
79
80    def set_config(self, existing_interfaces_facts):
81        """ Collect the configuration from the args passed to the module,
82            collect the current configuration (as a dict from facts)
83        :rtype: A list
84        :returns: the commands necessary to migrate the current configuration
85                  to the desired configuration
86        """
87        want = self._module.params['config']
88        have = existing_interfaces_facts
89        resp = self.set_state(want, have)
90
91        return to_list(resp)
92
93    def set_state(self, want, have):
94        """ Select the appropriate function based on the state provided
95        :param want: the desired configuration as a dictionary
96        :param have: the current configuration as a dictionary
97        :rtype: A list
98        :returns: the commands necessary to migrate the current configuration
99                  to the desired configuration
100        """
101        commands = []
102        state = self._module.params['state']
103        if state in ('overridden', 'merged', 'replaced') and not want:
104            self._module.fail_json(msg='value of config parameter must not be empty for state {0}'.format(state))
105
106        if state == 'overridden':
107            commands = self._state_overridden(want, have)
108        elif state == 'deleted':
109            commands = self._state_deleted(want, have)
110        elif state == 'merged':
111            commands = self._state_merged(want, have)
112        elif state == 'replaced':
113            commands = self._state_replaced(want, have)
114
115        return commands
116
117    def _state_replaced(self, want, have):
118        """ The command generator when state is replaced
119        :rtype: A list
120        :returns: the commands necessary to migrate the current configuration
121                  to the desired configuration
122        """
123        commands = []
124
125        for interface in want:
126            for each in have:
127                if each['name'] == interface['name']:
128                    break
129                elif interface['name'] in each['name']:
130                    break
131            else:
132                continue
133            have_dict = filter_dict_having_none_value(interface, each)
134            want = dict()
135            commands.extend(self._clear_config(want, have_dict))
136            commands.extend(self._set_config(interface, each))
137        # Remove the duplicate interface call
138        commands = remove_duplicate_interface(commands)
139
140        return commands
141
142    def _state_overridden(self, want, have):
143        """ The command generator when state is overridden
144        :rtype: A list
145        :returns: the commands necessary to migrate the current configuration
146                  to the desired configuration
147        """
148        commands = []
149
150        for each in have:
151            for interface in want:
152                if each['name'] == interface['name']:
153                    break
154                elif interface['name'] in each['name']:
155                    break
156            else:
157                # We didn't find a matching desired state, which means we can
158                # pretend we recieved an empty desired state.
159                interface = dict(name=each['name'])
160                commands.extend(self._clear_config(interface, each))
161                continue
162            have_dict = filter_dict_having_none_value(interface, each)
163            want = dict()
164            commands.extend(self._clear_config(want, have_dict))
165            commands.extend(self._set_config(interface, each))
166        # Remove the duplicate interface call
167        commands = remove_duplicate_interface(commands)
168
169        return commands
170
171    def _state_merged(self, want, have):
172        """ The command generator when state is merged
173        :rtype: A list
174        :returns: the commands necessary to merge the provided into
175                  the current configuration
176        """
177        commands = []
178
179        for interface in want:
180            for each in have:
181                if each['name'] == interface['name']:
182                    break
183                elif interface['name'] in each['name']:
184                    break
185            else:
186                continue
187            commands.extend(self._set_config(interface, each))
188
189        return commands
190
191    def _state_deleted(self, want, have):
192        """ The command generator when state is deleted
193        :rtype: A list
194        :returns: the commands necessary to remove the current configuration
195                  of the provided objects
196        """
197        commands = []
198
199        if want:
200            for interface in want:
201                for each in have:
202                    if each['name'] == interface['name']:
203                        break
204                    elif interface['name'] in each['name']:
205                        break
206                else:
207                    continue
208                interface = dict(name=interface['name'])
209                commands.extend(self._clear_config(interface, each))
210        else:
211            for each in have:
212                want = dict()
213                commands.extend(self._clear_config(want, each))
214
215        return commands
216
217    def _set_config(self, want, have):
218        # Set the interface config based on the want and have config
219        commands = []
220        interface = 'interface ' + want['name']
221
222        # Get the diff b/w want and have
223        want_dict = dict_to_set(want)
224        have_dict = dict_to_set(have)
225        diff = want_dict - have_dict
226
227        if diff:
228            diff = dict(diff)
229            for item in self.params:
230                if diff.get(item):
231                    cmd = item + ' ' + str(want.get(item))
232                    add_command_to_config_list(interface, cmd, commands)
233            if diff.get('enabled'):
234                add_command_to_config_list(interface, 'no shutdown', commands)
235            elif diff.get('enabled') is False:
236                add_command_to_config_list(interface, 'shutdown', commands)
237
238        return commands
239
240    def _clear_config(self, want, have):
241        # Delete the interface config based on the want and have config
242        commands = []
243
244        if want.get('name'):
245            interface_type = get_interface_type(want['name'])
246            interface = 'interface ' + want['name']
247        else:
248            interface_type = get_interface_type(have['name'])
249            interface = 'interface ' + have['name']
250
251        if have.get('description') and want.get('description') != have.get('description'):
252            remove_command_from_config_list(interface, 'description', commands)
253        if not have.get('enabled') and want.get('enabled') != have.get('enabled'):
254            # if enable is False set enable as True which is the default behavior
255            remove_command_from_config_list(interface, 'shutdown', commands)
256
257        if interface_type.lower() == 'gigabitethernet':
258            if have.get('speed') and have.get('speed') != 'auto' and want.get('speed') != have.get('speed'):
259                remove_command_from_config_list(interface, 'speed', commands)
260            if have.get('duplex') and have.get('duplex') != 'auto' and want.get('duplex') != have.get('duplex'):
261                remove_command_from_config_list(interface, 'duplex', commands)
262            if have.get('mtu') and want.get('mtu') != have.get('mtu'):
263                remove_command_from_config_list(interface, 'mtu', commands)
264
265        return commands
266