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_lag_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.utils import to_list, dict_diff
17
18from ansible.module_utils.network.common.cfg.base import ConfigBase
19from ansible.module_utils.network.eos.facts.facts import Facts
20from ansible.module_utils.network.eos.utils.utils import normalize_interface
21
22
23class Lag_interfaces(ConfigBase):
24    """
25    The eos_lag_interfaces class
26    """
27
28    gather_subset = [
29        '!all',
30        '!min',
31    ]
32
33    gather_network_resources = [
34        'lag_interfaces',
35    ]
36
37    def get_lag_interfaces_facts(self):
38        """ Get the 'facts' (the current configuration)
39
40        :rtype: A dictionary
41        :returns: The current configuration as a dictionary
42        """
43        facts, _warnings = Facts(self._module).get_facts(self.gather_subset, self.gather_network_resources)
44        lag_interfaces_facts = facts['ansible_network_resources'].get('lag_interfaces')
45        if not lag_interfaces_facts:
46            return []
47        return lag_interfaces_facts
48
49    def execute_module(self):
50        """ Execute the module
51
52        :rtype: A dictionary
53        :returns: The result from module execution
54        """
55        result = {'changed': False}
56        commands = list()
57        warnings = list()
58
59        existing_lag_interfaces_facts = self.get_lag_interfaces_facts()
60        commands.extend(self.set_config(existing_lag_interfaces_facts))
61        if commands:
62            if not self._module.check_mode:
63                self._connection.edit_config(commands)
64            result['changed'] = True
65        result['commands'] = commands
66
67        changed_lag_interfaces_facts = self.get_lag_interfaces_facts()
68
69        result['before'] = existing_lag_interfaces_facts
70        if result['changed']:
71            result['after'] = changed_lag_interfaces_facts
72
73        result['warnings'] = warnings
74        return result
75
76    def set_config(self, existing_lag_interfaces_facts):
77        """ Collect the configuration from the args passed to the module,
78            collect the current configuration (as a dict from facts)
79
80        :rtype: A list
81        :returns: the commands necessary to migrate the current configuration
82                  to the desired configuration
83        """
84        want = self._module.params['config']
85        have = existing_lag_interfaces_facts
86        resp = self.set_state(want, have)
87        return to_list(resp)
88
89    def set_state(self, want, have):
90        """ Select the appropriate function based on the state provided
91
92        :param want: the desired configuration as a dictionary
93        :param have: the current configuration as a dictionary
94        :rtype: A list
95        :returns: the commands necessary to migrate the current configuration
96                  to the desired configuration
97        """
98        state = self._module.params['state']
99        if state == 'overridden':
100            commands = self._state_overridden(want, have)
101        elif state == 'deleted':
102            commands = self._state_deleted(want, have)
103        elif state == 'merged':
104            commands = self._state_merged(want, have)
105        elif state == 'replaced':
106            commands = self._state_replaced(want, have)
107        return commands
108
109    @staticmethod
110    def _state_replaced(want, have):
111        """ The command generator when state is replaced
112        :rtype: A list
113        :returns: the commands necessary to migrate the current configuration
114                  to the desired configuration
115        """
116        commands = []
117        for interface in want:
118            interface_name = normalize_interface(interface["name"])
119            for extant in have:
120                if extant["name"] == interface_name:
121                    break
122            else:
123                extant = dict(name=interface_name)
124
125            commands.extend(set_config(interface, extant))
126            commands.extend(remove_config(interface, extant))
127
128        return commands
129
130    @staticmethod
131    def _state_overridden(want, have):
132        """ The command generator when state is overridden
133        :rtype: A list
134        :returns: the commands necessary to migrate the current configuration
135                  to the desired configuration
136        """
137        commands = []
138        for extant in have:
139            for interface in want:
140                if normalize_interface(interface["name"]) == extant["name"]:
141                    break
142            else:
143                interface = dict(name=extant["name"])
144            commands.extend(remove_config(interface, extant))
145
146        for interface in want:
147            interface_name = normalize_interface(interface["name"])
148            for extant in have:
149                if extant["name"] == interface_name:
150                    break
151            else:
152                extant = dict(name=interface_name)
153            commands.extend(set_config(interface, extant))
154
155        return commands
156
157    @staticmethod
158    def _state_merged(want, have):
159        """ The command generator when state is merged
160        :rtype: A list
161        :returns: the commands necessary to merge the provided into
162                  the current configuration
163        """
164        commands = []
165        for interface in want:
166            interface_name = normalize_interface(interface["name"])
167            for extant in have:
168                if extant["name"] == interface_name:
169                    break
170            else:
171                extant = dict(name=interface_name)
172
173            commands.extend(set_config(interface, extant))
174
175        return commands
176
177    @staticmethod
178    def _state_deleted(want, have):
179        """ The command generator when state is deleted
180        :rtype: A list
181        :returns: the commands necessary to remove the current configuration
182                  of the provided objects
183        """
184        commands = []
185        for interface in want:
186            interface_name = normalize_interface(interface["name"])
187            for extant in have:
188                if extant["name"] == interface_name:
189                    break
190            else:
191                extant = dict(name=interface_name)
192
193            # Clearing all args, send empty dictionary
194            interface = dict(name=interface_name)
195            commands.extend(remove_config(interface, extant))
196
197        return commands
198
199
200def set_config(want, have):
201    commands = []
202    to_set = dict_diff(have, want)
203    for member in to_set.get("members", []):
204        channel_id = want["name"][12:]
205        commands.extend([
206            "interface {0}".format(member["member"]),
207            "channel-group {0} mode {1}".format(channel_id, member["mode"]),
208        ])
209
210    return commands
211
212
213def remove_config(want, have):
214    commands = []
215    if not want.get("members"):
216        return ["no interface {0}".format(want["name"])]
217
218    to_remove = dict_diff(want, have)
219    for member in to_remove.get("members", []):
220        commands.extend([
221            "interface {0}".format(member["member"]),
222            "no channel-group",
223        ])
224
225    return commands
226