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_vlans 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 22 23 24class Vlans(ConfigBase): 25 """ 26 The ios_vlans class 27 """ 28 29 gather_subset = [ 30 '!all', 31 '!min', 32 ] 33 34 gather_network_resources = [ 35 'vlans', 36 ] 37 38 def __init__(self, module): 39 super(Vlans, self).__init__(module) 40 41 def get_interfaces_facts(self): 42 """ Get the 'facts' (the current configuration) 43 44 :rtype: A dictionary 45 :returns: The current configuration as a dictionary 46 """ 47 facts, _warnings = Facts(self._module).get_facts(self.gather_subset, self.gather_network_resources) 48 interfaces_facts = facts['ansible_network_resources'].get('vlans') 49 if not interfaces_facts: 50 return [] 51 return interfaces_facts 52 53 def execute_module(self): 54 """ Execute the module 55 56 :rtype: A dictionary 57 :returns: The result from module execution 58 """ 59 result = {'changed': False} 60 commands = list() 61 warnings = list() 62 63 existing_interfaces_facts = self.get_interfaces_facts() 64 commands.extend(self.set_config(existing_interfaces_facts)) 65 if commands: 66 if not self._module.check_mode: 67 self._connection.edit_config(commands) 68 result['changed'] = True 69 result['commands'] = commands 70 71 changed_interfaces_facts = self.get_interfaces_facts() 72 73 result['before'] = existing_interfaces_facts 74 if result['changed']: 75 result['after'] = changed_interfaces_facts 76 77 result['warnings'] = warnings 78 return result 79 80 def set_config(self, existing_interfaces_facts): 81 """ Collect the configuration from the args passed to the module, 82 collect the current configuration (as a dict from facts) 83 84 :rtype: A list 85 :returns: the commands necessary to migrate the current configuration 86 to the desired configuration 87 """ 88 want = self._module.params['config'] 89 have = existing_interfaces_facts 90 resp = self.set_state(want, have) 91 return to_list(resp) 92 93 def set_state(self, want, have): 94 """ Select the appropriate function based on the state provided 95 96 :param want: the desired configuration as a dictionary 97 :param have: the current configuration as a dictionary 98 :rtype: A list 99 :returns: the commands necessary to migrate the current configuration 100 to the desired configuration 101 """ 102 state = self._module.params['state'] 103 if state in ('overridden', 'merged', 'replaced') and not want: 104 self._module.fail_json(msg='value of config parameter must not be empty for state {0}'.format(state)) 105 106 if state == 'overridden': 107 commands = self._state_overridden(want, have, state) 108 elif state == 'deleted': 109 commands = self._state_deleted(want, have, state) 110 elif state == 'merged': 111 commands = self._state_merged(want, have) 112 elif state == 'replaced': 113 commands = self._state_replaced(want, have) 114 return commands 115 116 def _state_replaced(self, want, have): 117 """ The command generator when state is replaced 118 119 :rtype: A list 120 :returns: the commands necessary to migrate the current configuration 121 to the desired configuration 122 """ 123 commands = [] 124 125 check = False 126 for each in want: 127 for every in have: 128 if every['vlan_id'] == each['vlan_id']: 129 check = True 130 break 131 else: 132 continue 133 if check: 134 commands.extend(self._set_config(each, every)) 135 else: 136 commands.extend(self._set_config(each, dict())) 137 138 return commands 139 140 def _state_overridden(self, want, have, state): 141 """ The command generator when state is overridden 142 143 :rtype: A list 144 :returns: the commands necessary to migrate the current configuration 145 to the desired configuration 146 """ 147 commands = [] 148 149 want_local = want 150 for each in have: 151 count = 0 152 for every in want_local: 153 if each['vlan_id'] == every['vlan_id']: 154 break 155 count += 1 156 else: 157 # We didn't find a matching desired state, which means we can 158 # pretend we recieved an empty desired state. 159 commands.extend(self._clear_config(every, each, state)) 160 continue 161 commands.extend(self._set_config(every, each)) 162 # as the pre-existing VLAN are now configured by 163 # above set_config call, deleting the respective 164 # VLAN entry from the want_local list 165 del want_local[count] 166 167 # Iterating through want_local list which now only have new VLANs to be 168 # configured 169 for each in want_local: 170 commands.extend(self._set_config(each, dict())) 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 check = False 184 for each in want: 185 for every in have: 186 if each.get('vlan_id') == every.get('vlan_id'): 187 check = True 188 break 189 else: 190 continue 191 if check: 192 commands.extend(self._set_config(each, every)) 193 else: 194 commands.extend(self._set_config(each, dict())) 195 196 return commands 197 198 def _state_deleted(self, want, have, state): 199 """ The command generator when state is deleted 200 201 :rtype: A list 202 :returns: the commands necessary to remove the current configuration 203 of the provided objects 204 """ 205 commands = [] 206 207 if want: 208 check = False 209 for each in want: 210 for every in have: 211 if each.get('vlan_id') == every.get('vlan_id'): 212 check = True 213 break 214 else: 215 check = False 216 continue 217 if check: 218 commands.extend(self._clear_config(each, every, state)) 219 else: 220 for each in have: 221 commands.extend(self._clear_config(dict(), each, state)) 222 223 return commands 224 225 def remove_command_from_config_list(self, vlan, cmd, commands): 226 if vlan not in commands and cmd != 'vlan': 227 commands.insert(0, vlan) 228 elif cmd == 'vlan': 229 commands.append('no %s' % vlan) 230 return commands 231 commands.append('no %s' % cmd) 232 return commands 233 234 def add_command_to_config_list(self, vlan_id, cmd, commands): 235 if vlan_id not in commands: 236 commands.insert(0, vlan_id) 237 if cmd not in commands: 238 commands.append(cmd) 239 240 def _set_config(self, want, have): 241 # Set the interface config based on the want and have config 242 commands = [] 243 vlan = 'vlan {0}'.format(want.get('vlan_id')) 244 245 # Get the diff b/w want n have 246 want_dict = dict_to_set(want) 247 have_dict = dict_to_set(have) 248 diff = want_dict - have_dict 249 250 if diff: 251 name = dict(diff).get('name') 252 state = dict(diff).get('state') 253 shutdown = dict(diff).get('shutdown') 254 mtu = dict(diff).get('mtu') 255 remote_span = dict(diff).get('remote_span') 256 if name: 257 cmd = 'name {0}'.format(name) 258 self.add_command_to_config_list(vlan, cmd, commands) 259 if state: 260 cmd = 'state {0}'.format(state) 261 self.add_command_to_config_list(vlan, cmd, commands) 262 if mtu: 263 cmd = 'mtu {0}'.format(mtu) 264 self.add_command_to_config_list(vlan, cmd, commands) 265 if remote_span: 266 self.add_command_to_config_list(vlan, 'remote-span', commands) 267 if shutdown == 'enabled': 268 self.add_command_to_config_list(vlan, 'shutdown', commands) 269 elif shutdown == 'disabled': 270 self.add_command_to_config_list(vlan, 'no shutdown', commands) 271 272 return commands 273 274 def _clear_config(self, want, have, state): 275 # Delete the interface config based on the want and have config 276 commands = [] 277 vlan = 'vlan {0}'.format(have.get('vlan_id')) 278 279 if have.get('vlan_id') and 'default' not in have.get('name')\ 280 and (have.get('vlan_id') != want.get('vlan_id') or state == 'deleted'): 281 self.remove_command_from_config_list(vlan, 'vlan', commands) 282 elif 'default' not in have.get('name'): 283 if have.get('mtu') != want.get('mtu'): 284 self.remove_command_from_config_list(vlan, 'mtu', commands) 285 if have.get('remote_span') != want.get('remote_span') and want.get('remote_span'): 286 self.remove_command_from_config_list(vlan, 'remote-span', commands) 287 if have.get('shutdown') != want.get('shutdown') and want.get('shutdown'): 288 self.remove_command_from_config_list(vlan, 'shutdown', commands) 289 if have.get('state') != want.get('state') and want.get('state'): 290 self.remove_command_from_config_list(vlan, 'state', commands) 291 292 return commands 293