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_l3_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 normalize_interface, 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 22from ansible.module_utils.network.iosxr.utils.utils import validate_n_expand_ipv4, validate_ipv6 23 24 25class L3_Interfaces(ConfigBase): 26 """ 27 The iosxr_l3_interfaces class 28 """ 29 30 gather_subset = [ 31 '!all', 32 '!min', 33 ] 34 35 gather_network_resources = [ 36 'l3_interfaces', 37 ] 38 39 def get_l3_interfaces_facts(self): 40 """ Get the 'facts' (the current configuration) 41 :rtype: A dictionary 42 :returns: The current configuration as a dictionary 43 """ 44 facts, _warnings = Facts(self._module).get_facts(self.gather_subset, self.gather_network_resources) 45 l3_interfaces_facts = facts['ansible_network_resources'].get('l3_interfaces') 46 if not l3_interfaces_facts: 47 return [] 48 49 return l3_interfaces_facts 50 51 def execute_module(self): 52 """ Execute the module 53 :rtype: A dictionary 54 :returns: The result from module execution 55 """ 56 result = {'changed': False} 57 commands = list() 58 warnings = list() 59 60 existing_l3_interfaces_facts = self.get_l3_interfaces_facts() 61 commands.extend(self.set_config(existing_l3_interfaces_facts)) 62 if commands: 63 if not self._module.check_mode: 64 self._connection.edit_config(commands) 65 result['changed'] = True 66 result['commands'] = commands 67 68 changed_l3_interfaces_facts = self.get_l3_interfaces_facts() 69 70 result['before'] = existing_l3_interfaces_facts 71 if result['changed']: 72 result['after'] = changed_l3_interfaces_facts 73 74 result['warnings'] = warnings 75 return result 76 77 def set_config(self, existing_l3_interfaces_facts): 78 """ Collect the configuration from the args passed to the module, 79 collect the current configuration (as a dict from facts) 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_l3_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 :param want: the desired configuration as a dictionary 92 :param have: the current configuration as a dictionary 93 :rtype: A list 94 :returns: the commands necessary to migrate the current configuration 95 to the desired configuration 96 """ 97 commands = [] 98 99 state = self._module.params['state'] 100 101 if state in ('overridden', 'merged', 'replaced') and not want: 102 self._module.fail_json(msg='value of config parameter must not be empty for state {0}'.format(state)) 103 104 if state == 'overridden': 105 commands = self._state_overridden(want, have, self._module) 106 elif state == 'deleted': 107 commands = self._state_deleted(want, have) 108 elif state == 'merged': 109 commands = self._state_merged(want, have, self._module) 110 elif state == 'replaced': 111 commands = self._state_replaced(want, have, self._module) 112 113 return commands 114 115 def _state_replaced(self, want, have, module): 116 """ The command generator when state is replaced 117 :rtype: A list 118 :returns: the commands necessary to migrate the current configuration 119 to the desired configuration 120 """ 121 commands = [] 122 123 for interface in want: 124 interface['name'] = normalize_interface(interface['name']) 125 for each in have: 126 if each['name'] == interface['name']: 127 break 128 else: 129 commands.extend(self._set_config(interface, dict(), module)) 130 continue 131 have_dict = filter_dict_having_none_value(interface, each) 132 commands.extend(self._clear_config(dict(), have_dict)) 133 commands.extend(self._set_config(interface, each, module)) 134 # Remove the duplicate interface call 135 commands = remove_duplicate_interface(commands) 136 137 return commands 138 139 def _state_overridden(self, want, have, module): 140 """ The command generator when state is overridden 141 :rtype: A list 142 :returns: the commands necessary to migrate the current configuration 143 to the desired configuration 144 """ 145 commands = [] 146 not_in_have = set() 147 in_have = set() 148 149 for each in have: 150 for interface in want: 151 interface['name'] = normalize_interface(interface['name']) 152 if each['name'] == interface['name']: 153 in_have.add(interface['name']) 154 break 155 elif interface['name'] != each['name']: 156 not_in_have.add(interface['name']) 157 else: 158 # We didn't find a matching desired state, which means we can 159 # pretend we recieved an empty desired state. 160 interface = dict(name=each['name']) 161 kwargs = {'want': interface, 'have': each} 162 commands.extend(self._clear_config(**kwargs)) 163 continue 164 have_dict = filter_dict_having_none_value(interface, each) 165 commands.extend(self._clear_config(dict(), have_dict)) 166 commands.extend(self._set_config(interface, each, module)) 167 # Add the want interface that's not already configured in have interface 168 for each in (not_in_have - in_have): 169 for every in want: 170 interface = 'interface {0}'.format(every['name']) 171 if each and interface not in commands: 172 commands.extend(self._set_config(every, {}, module)) 173 # Remove the duplicate interface call 174 commands = remove_duplicate_interface(commands) 175 176 return commands 177 178 def _state_merged(self, want, have, module): 179 """ The command generator when state is merged 180 :rtype: A list 181 :returns: the commands necessary to merge the provided into 182 the current configuration 183 """ 184 commands = [] 185 186 for interface in want: 187 interface['name'] = normalize_interface(interface['name']) 188 for each in have: 189 if each['name'] == interface['name']: 190 break 191 else: 192 commands.extend(self._set_config(interface, dict(), module)) 193 continue 194 commands.extend(self._set_config(interface, each, module)) 195 196 return commands 197 198 def _state_deleted(self, want, have): 199 """ The command generator when state is deleted 200 :rtype: A list 201 :returns: the commands necessary to remove the current configuration 202 of the provided objects 203 """ 204 commands = [] 205 206 if want: 207 for interface in want: 208 interface['name'] = normalize_interface(interface['name']) 209 for each in have: 210 if each['name'] == interface['name']: 211 break 212 elif interface['name'] in each['name']: 213 break 214 else: 215 continue 216 interface = dict(name=interface['name']) 217 commands.extend(self._clear_config(interface, each)) 218 else: 219 for each in have: 220 want = dict() 221 commands.extend(self._clear_config(want, each)) 222 223 return commands 224 225 def verify_diff_again(self, want, have): 226 """ 227 Verify the IPV4 difference again as sometimes due to 228 change in order of set, set difference may result into change, 229 when there's actually no difference between want and have 230 :param want: want_dict IPV4 231 :param have: have_dict IPV4 232 :return: diff 233 """ 234 diff = False 235 for each in want: 236 each_want = dict(each) 237 for every in have: 238 every_have = dict(every) 239 if each_want.get('address') != every_have.get('address') and \ 240 each_want.get('secondary') != every_have.get('secondary') and \ 241 len(each_want.keys()) == len(every_have.keys()): 242 diff = True 243 break 244 elif each_want.get('address') != every_have.get('address') and len(each_want.keys()) == len( 245 every_have.keys()): 246 diff = True 247 break 248 if diff: 249 break 250 251 return diff 252 253 def _set_config(self, want, have, module): 254 # Set the interface config based on the want and have config 255 commands = [] 256 interface = 'interface ' + want['name'] 257 258 # To handle L3 IPV4 configuration 259 if want.get("ipv4"): 260 for each in want.get("ipv4"): 261 if each.get('address') != 'dhcp': 262 ip_addr_want = validate_n_expand_ipv4(module, each) 263 each['address'] = ip_addr_want 264 265 # Get the diff b/w want and have 266 want_dict = dict_to_set(want) 267 have_dict = dict_to_set(have) 268 269 # To handle L3 IPV4 configuration 270 want_ipv4 = dict(want_dict).get('ipv4') 271 have_ipv4 = dict(have_dict).get('ipv4') 272 if want_ipv4: 273 if have_ipv4: 274 diff_ipv4 = set(want_ipv4) - set(dict(have_dict).get('ipv4')) 275 if diff_ipv4: 276 diff_ipv4 = diff_ipv4 if self.verify_diff_again(want_ipv4, have_ipv4) else () 277 else: 278 diff_ipv4 = set(want_ipv4) 279 for each in diff_ipv4: 280 ipv4_dict = dict(each) 281 if ipv4_dict.get('address') != 'dhcp': 282 cmd = "ipv4 address {0}".format(ipv4_dict['address']) 283 if ipv4_dict.get("secondary"): 284 cmd += " secondary" 285 add_command_to_config_list(interface, cmd, commands) 286 287 # To handle L3 IPV6 configuration 288 want_ipv6 = dict(want_dict).get('ipv6') 289 have_ipv6 = dict(have_dict).get('ipv6') 290 if want_ipv6: 291 if have_ipv6: 292 diff_ipv6 = set(want_ipv6) - set(have_ipv6) 293 else: 294 diff_ipv6 = set(want_ipv6) 295 for each in diff_ipv6: 296 ipv6_dict = dict(each) 297 validate_ipv6(ipv6_dict.get('address'), module) 298 cmd = "ipv6 address {0}".format(ipv6_dict.get('address')) 299 add_command_to_config_list(interface, cmd, commands) 300 301 return commands 302 303 def _clear_config(self, want, have): 304 # Delete the interface config based on the want and have config 305 count = 0 306 commands = [] 307 if want.get('name'): 308 interface = 'interface ' + want['name'] 309 else: 310 interface = 'interface ' + have['name'] 311 312 if have.get('ipv4') and want.get('ipv4'): 313 for each in have.get('ipv4'): 314 if each.get('secondary') and not (want.get('ipv4')[count].get('secondary')): 315 cmd = 'ipv4 address {0} secondary'.format(each.get('address')) 316 remove_command_from_config_list(interface, cmd, commands) 317 count += 1 318 if have.get('ipv4') and not (want.get('ipv4')): 319 remove_command_from_config_list(interface, 'ipv4 address', commands) 320 if have.get('ipv6') and not (want.get('ipv6')): 321 remove_command_from_config_list(interface, 'ipv6 address', commands) 322 323 return commands 324