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_l2_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 22 23 24class L2_Interfaces(ConfigBase): 25 """ 26 The iosxr_interfaces class 27 """ 28 29 gather_subset = [ 30 '!all', 31 '!min', 32 ] 33 34 gather_network_resources = [ 35 'l2_interfaces', 36 ] 37 38 def get_l2_interfaces_facts(self): 39 """ Get the 'facts' (the current configuration) 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 l2_interfaces_facts = facts['ansible_network_resources'].get('l2_interfaces') 45 46 if not l2_interfaces_facts: 47 return [] 48 return l2_interfaces_facts 49 50 def execute_module(self): 51 """ Execute the module 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_l2_interfaces_facts = self.get_l2_interfaces_facts() 60 commands.extend(self.set_config(existing_l2_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_l2_interfaces_facts = self.get_l2_interfaces_facts() 68 69 result['before'] = existing_l2_interfaces_facts 70 if result['changed']: 71 result['after'] = changed_l2_interfaces_facts 72 73 result['warnings'] = warnings 74 return result 75 76 def set_config(self, existing_l2_interfaces_facts): 77 """ Collect the configuration from the args passed to the module, 78 collect the current configuration (as a dict from facts) 79 :rtype: A list 80 :returns: the commands necessary to migrate the current configuration 81 to the desired configuration 82 """ 83 want = self._module.params['config'] 84 have = existing_l2_interfaces_facts 85 resp = self.set_state(want, have) 86 return to_list(resp) 87 88 def set_state(self, want, have): 89 """ Select the appropriate function based on the state provided 90 :param want: the desired configuration as a dictionary 91 :param have: the current configuration as a dictionary 92 :rtype: A list 93 :returns: the commands necessary to migrate the current configuration 94 to the desired configuration 95 """ 96 commands = [] 97 98 state = self._module.params['state'] 99 100 if state in ('overridden', 'merged', 'replaced') and not want: 101 self._module.fail_json(msg='value of config parameter must not be empty for state {0}'.format(state)) 102 103 if state == 'overridden': 104 commands = self._state_overridden(want, have, self._module) 105 elif state == 'deleted': 106 commands = self._state_deleted(want, have) 107 elif state == 'merged': 108 commands = self._state_merged(want, have, self._module) 109 elif state == 'replaced': 110 commands = self._state_replaced(want, have, self._module) 111 112 return commands 113 114 def _state_replaced(self, want, have, module): 115 """ The command generator when state is replaced 116 :rtype: A list 117 :returns: the commands necessary to migrate the current configuration 118 to the desired configuration 119 """ 120 commands = [] 121 122 for interface in want: 123 interface['name'] = normalize_interface(interface['name']) 124 for each in have: 125 if each['name'] == interface['name']: 126 break 127 else: 128 commands.extend(self._set_config(interface, {}, module)) 129 continue 130 have_dict = filter_dict_having_none_value(interface, each) 131 commands.extend(self._clear_config(dict(), have_dict)) 132 commands.extend(self._set_config(interface, each, module)) 133 # Remove the duplicate interface call 134 commands = remove_duplicate_interface(commands) 135 136 return commands 137 138 def _state_overridden(self, want, have, module): 139 """ The command generator when state is overridden 140 :rtype: A list 141 :returns: the commands necessary to migrate the current configuration 142 to the desired configuration 143 """ 144 commands = [] 145 not_in_have = set() 146 in_have = set() 147 for each in have: 148 for interface in want: 149 interface['name'] = normalize_interface(interface['name']) 150 if each['name'] == interface['name']: 151 in_have.add(interface['name']) 152 break 153 elif interface['name'] != each['name']: 154 not_in_have.add(interface['name']) 155 else: 156 # We didn't find a matching desired state, which means we can 157 # pretend we recieved an empty desired state. 158 interface = dict(name=each['name']) 159 commands.extend(self._clear_config(interface, each)) 160 continue 161 have_dict = filter_dict_having_none_value(interface, each) 162 commands.extend(self._clear_config(dict(), have_dict)) 163 commands.extend(self._set_config(interface, each, module)) 164 # Add the want interface that's not already configured in have interface 165 for each in (not_in_have - in_have): 166 for every in want: 167 interface = 'interface {0}'.format(every['name']) 168 if each and interface not in commands: 169 commands.extend(self._set_config(every, {}, module)) 170 171 # Remove the duplicate interface call 172 commands = remove_duplicate_interface(commands) 173 174 return commands 175 176 def _state_merged(self, want, have, module): 177 """ The command generator when state is merged 178 :rtype: A list 179 :returns: the commands necessary to merge the provided into 180 the current configuration 181 """ 182 commands = [] 183 184 for interface in want: 185 interface['name'] = normalize_interface(interface['name']) 186 for each in have: 187 if each['name'] == interface['name']: 188 break 189 elif interface['name'] in each['name']: 190 break 191 else: 192 commands.extend(self._set_config(interface, {}, 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 _set_config(self, want, have, module): 226 # Set the interface config based on the want and have config 227 commands = [] 228 interface = 'interface ' + want['name'] 229 l2_protocol_bool = False 230 231 # Get the diff b/w want and have 232 want_dict = dict_to_set(want) 233 have_dict = dict_to_set(have) 234 diff = want_dict - have_dict 235 236 if diff: 237 # For merging with already configured l2protocol 238 if have.get('l2protocol') and len(have.get('l2protocol')) > 1: 239 l2_protocol_diff = [] 240 for each in want.get('l2protocol'): 241 for every in have.get('l2protocol'): 242 if every == each: 243 break 244 if each not in have.get('l2protocol'): 245 l2_protocol_diff.append(each) 246 l2_protocol_bool = True 247 l2protocol = tuple(l2_protocol_diff) 248 else: 249 l2protocol = {} 250 251 diff = dict(diff) 252 wants_native = diff.get('native_vlan') 253 l2transport = diff.get('l2transport') 254 q_vlan = diff.get('q_vlan') 255 propagate = diff.get('propagate') 256 if l2_protocol_bool is False: 257 l2protocol = diff.get('l2protocol') 258 259 if wants_native: 260 cmd = 'dot1q native vlan {0}'.format(wants_native) 261 add_command_to_config_list(interface, cmd, commands) 262 263 if l2transport or l2protocol: 264 for each in l2protocol: 265 each = dict(each) 266 if isinstance(each, dict): 267 cmd = 'l2transport l2protocol {0} {1}'.format(list(each.keys())[0], list(each.values())[0]) 268 add_command_to_config_list(interface, cmd, commands) 269 if propagate and not have.get('propagate'): 270 cmd = 'l2transport propagate remote-status' 271 add_command_to_config_list(interface, cmd, commands) 272 elif want.get('l2transport') is False and (want.get('l2protocol') or want.get('propagate')): 273 module.fail_json(msg='L2transport L2protocol or Propagate can only be configured when ' 274 'L2transport set to True!') 275 276 if q_vlan and '.' in interface: 277 q_vlans = (" ".join(map(str, want.get('q_vlan')))) 278 if q_vlans != have.get('q_vlan'): 279 cmd = 'dot1q vlan {0}'.format(q_vlans) 280 add_command_to_config_list(interface, cmd, commands) 281 282 return commands 283 284 def _clear_config(self, want, have): 285 # Delete the interface config based on the want and have config 286 commands = [] 287 288 if want.get('name'): 289 interface = 'interface ' + want['name'] 290 else: 291 interface = 'interface ' + have['name'] 292 if have.get('native_vlan'): 293 remove_command_from_config_list(interface, 'dot1q native vlan', commands) 294 295 if have.get('q_vlan'): 296 remove_command_from_config_list(interface, 'encapsulation dot1q', commands) 297 298 if have.get('l2protocol') and (want.get('l2protocol') is None or want.get('propagate') is None): 299 if 'no l2transport' not in commands: 300 remove_command_from_config_list(interface, 'l2transport', commands) 301 elif have.get('l2transport') and have.get('l2transport') != want.get('l2transport'): 302 if 'no l2transport' not in commands: 303 remove_command_from_config_list(interface, 'l2transport', commands) 304 305 return commands 306