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 ios_lacp_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"""
13
14from __future__ import absolute_import, division, print_function
15__metaclass__ = type
16
17
18from ansible.module_utils.network.common.cfg.base import ConfigBase
19from ansible.module_utils.network.common.utils import to_list
20from ansible.module_utils.network.ios.facts.facts import Facts
21from ansible.module_utils.network.ios.utils.utils import dict_to_set
22from ansible.module_utils.network.ios.utils.utils import remove_command_from_config_list, add_command_to_config_list
23from ansible.module_utils.network.ios.utils.utils import filter_dict_having_none_value, remove_duplicate_interface
24
25
26class Lacp_Interfaces(ConfigBase):
27    """
28    The ios_lacp_interfaces class
29    """
30
31    gather_subset = [
32        '!all',
33        '!min',
34    ]
35
36    gather_network_resources = [
37        'lacp_interfaces',
38    ]
39
40    def __init__(self, module):
41        super(Lacp_Interfaces, self).__init__(module)
42
43    def get_lacp_interfaces_facts(self):
44        """ Get the 'facts' (the current configuration)
45
46        :rtype: A dictionary
47        :returns: The current configuration as a dictionary
48        """
49        facts, _warnings = Facts(self._module).get_facts(self.gather_subset, self.gather_network_resources)
50        lacp_interfaces_facts = facts['ansible_network_resources'].get('lacp_interfaces')
51
52        if not lacp_interfaces_facts:
53            return []
54        return lacp_interfaces_facts
55
56    def execute_module(self):
57        """ Execute the module
58
59        :rtype: A dictionary
60        :returns: The result from module execution
61        """
62        result = {'changed': False}
63        commands = list()
64        warnings = list()
65
66        existing_lacp_interfaces_facts = self.get_lacp_interfaces_facts()
67        commands.extend(self.set_config(existing_lacp_interfaces_facts))
68        if commands:
69            if not self._module.check_mode:
70                self._connection.edit_config(commands)
71            result['changed'] = True
72        result['commands'] = commands
73
74        changed_lacp_interfaces_facts = self.get_lacp_interfaces_facts()
75
76        result['before'] = existing_lacp_interfaces_facts
77        if result['changed']:
78            result['after'] = changed_lacp_interfaces_facts
79
80        result['warnings'] = warnings
81
82        return result
83
84    def set_config(self, existing_lacp_interfaces_facts):
85        """ Collect the configuration from the args passed to the module,
86            collect the current configuration (as a dict from facts)
87
88        :rtype: A list
89        :returns: the commands necessary to migrate the current configuration
90                  to the desired configuration
91        """
92        want = self._module.params['config']
93        have = existing_lacp_interfaces_facts
94        resp = self.set_state(want, have)
95
96        return to_list(resp)
97
98    def set_state(self, want, have):
99        """ Select the appropriate function based on the state provided
100
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        commands = []
108
109        state = self._module.params['state']
110        if state in ('overridden', 'merged', 'replaced') and not want:
111            self._module.fail_json(msg='value of config parameter must not be empty for state {0}'.format(state))
112
113        if state == 'overridden':
114            commands = self._state_overridden(want, have)
115        elif state == 'deleted':
116            commands = self._state_deleted(want, have)
117        elif state == 'merged':
118            commands = self._state_merged(want, have)
119        elif state == 'replaced':
120            commands = self._state_replaced(want, have)
121
122        return commands
123
124    def _state_replaced(self, want, have):
125        """ The command generator when state is replaced
126
127        :rtype: A list
128        :returns: the commands necessary to migrate the current configuration
129                  to the desired configuration
130        """
131        commands = []
132
133        for interface in want:
134            for each in have:
135                if each['name'] == interface['name']:
136                    break
137            else:
138                continue
139            have_dict = filter_dict_having_none_value(interface, each)
140            commands.extend(self._clear_config(dict(), have_dict))
141            commands.extend(self._set_config(interface, each))
142        # Remove the duplicate interface call
143        commands = remove_duplicate_interface(commands)
144
145        return commands
146
147    def _state_overridden(self, want, have):
148        """ The command generator when state is overridden
149
150        :rtype: A list
151        :returns: the commands necessary to migrate the current configuration
152                  to the desired configuration
153        """
154        commands = []
155
156        for each in have:
157            for interface in want:
158                if each['name'] == interface['name']:
159                    break
160            else:
161                # We didn't find a matching desired state, which means we can
162                # pretend we recieved an empty desired state.
163                interface = dict(name=each['name'])
164                commands.extend(self._clear_config(interface, each))
165                continue
166            have_dict = filter_dict_having_none_value(interface, each)
167            commands.extend(self._clear_config(dict(), have_dict))
168            commands.extend(self._set_config(interface, each))
169        # Remove the duplicate interface call
170        commands = remove_duplicate_interface(commands)
171
172        return commands
173
174    def _state_merged(self, want, have):
175        """ The command generator when state is merged
176
177        :rtype: A list
178        :returns: the commands necessary to merge the provided into
179                  the current configuration
180        """
181        commands = []
182
183        for interface in want:
184            for each in have:
185                if interface['name'] == each['name']:
186                    break
187            else:
188                continue
189            commands.extend(self._set_config(interface, each))
190
191        return commands
192
193    def _state_deleted(self, want, have):
194        """ The command generator when state is deleted
195
196        :rtype: A list
197        :returns: the commands necessary to remove the current configuration
198                  of the provided objects
199        """
200        commands = []
201
202        if want:
203            for interface in want:
204                for each in have:
205                    if each['name'] == interface['name']:
206                        break
207                else:
208                    continue
209                interface = dict(name=interface['name'])
210                commands.extend(self._clear_config(interface, each))
211        else:
212            for each in have:
213                commands.extend(self._clear_config(dict(), 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 ' + have['name']
221
222        want_dict = dict_to_set(want)
223        have_dict = dict_to_set(have)
224        diff = want_dict - have_dict
225
226        if diff:
227            port_priotity = dict(diff).get('port_priority')
228            max_bundle = dict(diff).get('max_bundle')
229            fast_switchover = dict(diff).get('fast_switchover')
230            if port_priotity:
231                cmd = 'lacp port-priority {0}'.format(port_priotity)
232                add_command_to_config_list(interface, cmd, commands)
233            if max_bundle:
234                cmd = 'lacp max-bundle {0}'.format(max_bundle)
235                add_command_to_config_list(interface, cmd, commands)
236            if fast_switchover:
237                cmd = 'lacp fast-switchover'
238                add_command_to_config_list(interface, cmd, commands)
239
240        return commands
241
242    def _clear_config(self, want, have):
243        # Delete the interface config based on the want and have config
244        commands = []
245        if want.get('name'):
246            interface = 'interface ' + want['name']
247        else:
248            interface = 'interface ' + have['name']
249
250        if have.get('port_priority') and have.get('port_priority') != want.get('port_priority'):
251            cmd = 'lacp port-priority'
252            remove_command_from_config_list(interface, cmd, commands)
253        if have.get('max_bundle') and have.get('max_bundle') != want.get('max_bundle'):
254            cmd = 'lacp max-bundle'
255            remove_command_from_config_list(interface, cmd, commands)
256        if have.get('fast_switchover'):
257            cmd = 'lacp fast-switchover'
258            remove_command_from_config_list(interface, cmd, commands)
259
260        return commands
261