1# 2# -*- coding: utf-8 -*- 3# Copyright 2019 Red Hat Inc. 4# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 5""" 6The ios_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 16 17from ansible.module_utils.network.common.cfg.base import ConfigBase 18from ansible.module_utils.network.common.utils import to_list 19from ansible.module_utils.network.ios.facts.facts import Facts 20from ansible.module_utils.network.ios.utils.utils import get_interface_type, dict_to_set 21from ansible.module_utils.network.ios.utils.utils import remove_command_from_config_list, add_command_to_config_list 22from ansible.module_utils.network.ios.utils.utils import filter_dict_having_none_value, remove_duplicate_interface 23 24 25class Interfaces(ConfigBase): 26 """ 27 The ios_interfaces class 28 """ 29 30 gather_subset = [ 31 '!all', 32 '!min', 33 ] 34 35 gather_network_resources = [ 36 'interfaces', 37 ] 38 39 params = ('description', 'mtu', 'speed', 'duplex') 40 41 def __init__(self, module): 42 super(Interfaces, self).__init__(module) 43 44 def get_interfaces_facts(self): 45 """ Get the 'facts' (the current configuration) 46 47 :rtype: A dictionary 48 :returns: The current configuration as a dictionary 49 """ 50 facts, _warnings = Facts(self._module).get_facts(self.gather_subset, self.gather_network_resources) 51 interfaces_facts = facts['ansible_network_resources'].get('interfaces') 52 if not interfaces_facts: 53 return [] 54 55 return interfaces_facts 56 57 def execute_module(self): 58 """ Execute the module 59 60 :rtype: A dictionary 61 :returns: The result from moduel execution 62 """ 63 result = {'changed': False} 64 commands = list() 65 warnings = list() 66 67 existing_interfaces_facts = self.get_interfaces_facts() 68 commands.extend(self.set_config(existing_interfaces_facts)) 69 70 if commands: 71 if not self._module.check_mode: 72 self._connection.edit_config(commands) 73 result['changed'] = True 74 result['commands'] = commands 75 76 changed_interfaces_facts = self.get_interfaces_facts() 77 78 result['before'] = existing_interfaces_facts 79 if result['changed']: 80 result['after'] = changed_interfaces_facts 81 result['warnings'] = warnings 82 83 return result 84 85 def set_config(self, existing_interfaces_facts): 86 """ Collect the configuration from the args passed to the module, 87 collect the current configuration (as a dict from facts) 88 89 :rtype: A list 90 :returns: the commands necessary to migrate the current configuration 91 to the deisred configuration 92 """ 93 want = self._module.params['config'] 94 have = existing_interfaces_facts 95 resp = self.set_state(want, have) 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 deisred 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 :param want: the desired configuration as a dictionary 128 :param have: the current configuration as a dictionary 129 :param interface_type: interface type 130 :rtype: A list 131 :returns: the commands necessary to migrate the current configuration 132 to the deisred configuration 133 """ 134 commands = [] 135 136 for interface in want: 137 for each in have: 138 if each['name'] == interface['name']: 139 break 140 elif interface['name'] in each['name']: 141 break 142 else: 143 # configuring non-existing interface 144 commands.extend(self._set_config(interface, dict())) 145 continue 146 have_dict = filter_dict_having_none_value(interface, each) 147 commands.extend(self._clear_config(dict(), have_dict)) 148 commands.extend(self._set_config(interface, each)) 149 # Remove the duplicate interface call 150 commands = remove_duplicate_interface(commands) 151 152 return commands 153 154 def _state_overridden(self, want, have): 155 """ The command generator when state is overridden 156 157 :param want: the desired configuration as a dictionary 158 :param obj_in_have: the current configuration as a dictionary 159 :rtype: A list 160 :returns: the commands necessary to migrate the current configuration 161 to the desired configuration 162 """ 163 commands = [] 164 165 for each in have: 166 for interface in want: 167 count = 0 168 if each['name'] == interface['name']: 169 break 170 elif interface['name'] in each['name']: 171 break 172 count += 1 173 else: 174 # We didn't find a matching desired state, which means we can 175 # pretend we recieved an empty desired state. 176 interface = dict(name=each['name']) 177 commands.extend(self._clear_config(interface, each)) 178 continue 179 have_dict = filter_dict_having_none_value(interface, each) 180 commands.extend(self._clear_config(dict(), have_dict)) 181 commands.extend(self._set_config(interface, each)) 182 # as the pre-existing interface are now configured by 183 # above set_config call, deleting the respective 184 # interface entry from the want list 185 del want[count] 186 187 # Iterating through want list which now only have new interfaces to be 188 # configured 189 for each in want: 190 commands.extend(self._set_config(each, dict())) 191 # Remove the duplicate interface call 192 commands = remove_duplicate_interface(commands) 193 194 return commands 195 196 def _state_merged(self, want, have): 197 """ The command generator when state is merged 198 199 :param want: the additive configuration as a dictionary 200 :param obj_in_have: the current configuration as a dictionary 201 :rtype: A list 202 :returns: the commands necessary to merge the provided into 203 the current configuration 204 """ 205 commands = [] 206 207 for interface in want: 208 for each in have: 209 if each['name'] == interface['name']: 210 break 211 else: 212 # configuring non-existing interface 213 commands.extend(self._set_config(interface, dict())) 214 continue 215 commands.extend(self._set_config(interface, each)) 216 217 return commands 218 219 def _state_deleted(self, want, have): 220 """ The command generator when state is deleted 221 222 :param want: the objects from which the configuration should be removed 223 :param obj_in_have: the current configuration as a dictionary 224 :param interface_type: interface type 225 :rtype: A list 226 :returns: the commands necessary to remove the current configuration 227 of the provided objects 228 """ 229 commands = [] 230 231 if want: 232 for interface in want: 233 for each in have: 234 if each['name'] == interface['name']: 235 break 236 else: 237 continue 238 interface = dict(name=interface['name']) 239 commands.extend(self._clear_config(interface, each)) 240 else: 241 for each in have: 242 want = dict() 243 commands.extend(self._clear_config(want, each)) 244 245 return commands 246 247 def _set_config(self, want, have): 248 # Set the interface config based on the want and have config 249 commands = [] 250 interface = 'interface ' + want['name'] 251 252 # Get the diff b/w want and have 253 want_dict = dict_to_set(want) 254 have_dict = dict_to_set(have) 255 diff = want_dict - have_dict 256 257 if diff: 258 diff = dict(diff) 259 for item in self.params: 260 if diff.get(item): 261 cmd = item + ' ' + str(want.get(item)) 262 add_command_to_config_list(interface, cmd, commands) 263 if diff.get('enabled'): 264 add_command_to_config_list(interface, 'no shutdown', commands) 265 elif diff.get('enabled') is False: 266 add_command_to_config_list(interface, 'shutdown', commands) 267 268 return commands 269 270 def _clear_config(self, want, have): 271 # Delete the interface config based on the want and have config 272 commands = [] 273 274 if want.get('name'): 275 interface_type = get_interface_type(want['name']) 276 interface = 'interface ' + want['name'] 277 else: 278 interface_type = get_interface_type(have['name']) 279 interface = 'interface ' + have['name'] 280 281 if have.get('description') and want.get('description') != have.get('description'): 282 remove_command_from_config_list(interface, 'description', commands) 283 if not have.get('enabled') and want.get('enabled') != have.get('enabled'): 284 # if enable is False set enable as True which is the default behavior 285 remove_command_from_config_list(interface, 'shutdown', commands) 286 287 if interface_type.lower() == 'gigabitethernet': 288 if have.get('speed') and have.get('speed') != 'auto' and want.get('speed') != have.get('speed'): 289 remove_command_from_config_list(interface, 'speed', commands) 290 if have.get('duplex') and have.get('duplex') != 'auto' and want.get('duplex') != have.get('duplex'): 291 remove_command_from_config_list(interface, 'duplex', commands) 292 if have.get('mtu') and want.get('mtu') != have.get('mtu'): 293 remove_command_from_config_list(interface, 'mtu', commands) 294 295 return commands 296