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