1# Copyright 2019 Red Hat 2# GNU General Public License v3.0+ 3# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 4""" 5The vyos_lag_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""" 11from __future__ import absolute_import, division, print_function 12__metaclass__ = type 13from ansible.module_utils.network.common.cfg.base import ConfigBase 14from ansible.module_utils.network.vyos.facts.facts import Facts 15from ansible.module_utils.network.common.utils import to_list, dict_diff 16from ansible.module_utils.six import iteritems 17from ansible.module_utils.network. \ 18 vyos.utils.utils import search_obj_in_list, \ 19 get_lst_diff_for_dicts, list_diff_want_only, list_diff_have_only 20 21 22class Lag_interfaces(ConfigBase): 23 """ 24 The vyos_lag_interfaces class 25 """ 26 27 gather_subset = [ 28 '!all', 29 '!min', 30 ] 31 32 gather_network_resources = [ 33 'lag_interfaces', 34 ] 35 36 params = ['arp_monitor', 'hash_policy', 'members', 'mode', 'name', 'primary'] 37 38 def __init__(self, module): 39 super(Lag_interfaces, self).__init__(module) 40 41 def get_lag_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, 48 self.gather_network_resources) 49 lag_interfaces_facts = facts['ansible_network_resources'].get('lag_interfaces') 50 if not lag_interfaces_facts: 51 return [] 52 return lag_interfaces_facts 53 54 def execute_module(self): 55 """ Execute the module 56 57 :rtype: A dictionary 58 :returns: The result from module execution 59 """ 60 result = {'changed': False} 61 commands = list() 62 warnings = list() 63 existing_lag_interfaces_facts = self.get_lag_interfaces_facts() 64 commands.extend(self.set_config(existing_lag_interfaces_facts)) 65 if commands: 66 if self._module.check_mode: 67 resp = self._connection.edit_config(commands, commit=False) 68 else: 69 resp = self._connection.edit_config(commands) 70 result['changed'] = True 71 72 result['commands'] = commands 73 74 if self._module._diff: 75 result['diff'] = resp['diff'] if result['changed'] else None 76 77 changed_lag_interfaces_facts = self.get_lag_interfaces_facts() 78 79 result['before'] = existing_lag_interfaces_facts 80 if result['changed']: 81 result['after'] = changed_lag_interfaces_facts 82 83 result['warnings'] = warnings 84 return result 85 86 def set_config(self, existing_lag_interfaces_facts): 87 """ Collect the configuration from the args passed to the module, 88 collect the current configuration (as a dict from facts) 89 90 :rtype: A list 91 :returns: the commands necessary to migrate the current configuration 92 to the desired configuration 93 """ 94 want = self._module.params['config'] 95 have = existing_lag_interfaces_facts 96 resp = self.set_state(want, have) 97 return to_list(resp) 98 99 def set_state(self, want, have): 100 """ Select the appropriate function based on the state provided 101 102 :param want: the desired configuration as a dictionary 103 :param have: the current configuration as a dictionary 104 :rtype: A list 105 :returns: the commands necessary to migrate the current configuration 106 to the desired configuration 107 """ 108 commands = [] 109 state = self._module.params['state'] 110 if state in ('merged', 'replaced', 'overridden') and not want: 111 self._module.fail_json(msg='config is required for state {0}'.format(state)) 112 if state == 'overridden': 113 commands.extend(self._state_overridden(want, have)) 114 elif state == 'deleted': 115 if want: 116 for want_item in want: 117 name = want_item['name'] 118 obj_in_have = search_obj_in_list(name, have) 119 commands.extend(self._state_deleted(obj_in_have)) 120 else: 121 for have_item in have: 122 commands.extend(self._state_deleted(have_item)) 123 else: 124 for want_item in want: 125 name = want_item['name'] 126 obj_in_have = search_obj_in_list(name, have) 127 if state == 'merged': 128 commands.extend(self._state_merged(want_item, obj_in_have)) 129 elif state == 'replaced': 130 commands.extend(self._state_replaced(want_item, obj_in_have)) 131 return commands 132 133 def _state_replaced(self, want, have): 134 """ The command generator when state is replaced 135 136 :rtype: A list 137 :returns: the commands necessary to migrate the current configuration 138 to the desired configuration 139 """ 140 commands = [] 141 if have: 142 commands.extend(self._render_del_commands(want, have)) 143 commands.extend(self._state_merged(want, have)) 144 return commands 145 146 def _state_overridden(self, want, have): 147 """ The command generator when state is overridden 148 149 :rtype: A list 150 :returns: the commands necessary to migrate the current configuration 151 to the desired configuration 152 """ 153 commands = [] 154 for have_item in have: 155 lag_name = have_item['name'] 156 obj_in_want = search_obj_in_list(lag_name, want) 157 if not obj_in_want: 158 commands.extend(self._purge_attribs(have_item)) 159 160 for want_item in want: 161 name = want_item['name'] 162 obj_in_have = search_obj_in_list(name, have) 163 commands.extend(self._state_replaced(want_item, obj_in_have)) 164 return commands 165 166 def _state_merged(self, want, have): 167 """ The command generator when state is merged 168 169 :rtype: A list 170 :returns: the commands necessary to merge the provided into 171 the current configuration 172 """ 173 commands = [] 174 if have: 175 commands.extend(self._render_updates(want, have)) 176 else: 177 commands.extend(self._render_set_commands(want)) 178 return commands 179 180 def _state_deleted(self, have): 181 """ The command generator when state is deleted 182 183 :rtype: A list 184 :returns: the commands necessary to remove the current configuration 185 of the provided objects 186 """ 187 commands = [] 188 if have: 189 commands.extend(self._purge_attribs(have)) 190 return commands 191 192 def _render_updates(self, want, have): 193 commands = [] 194 195 temp_have_members = have.pop('members', None) 196 temp_want_members = want.pop('members', None) 197 198 updates = dict_diff(have, want) 199 200 if temp_have_members: 201 have['members'] = temp_have_members 202 if temp_want_members: 203 want['members'] = temp_want_members 204 205 commands.extend(self._add_bond_members(want, have)) 206 207 if updates: 208 for key, value in iteritems(updates): 209 if value: 210 if key == 'arp_monitor': 211 commands.extend( 212 self._add_arp_monitor(updates, key, want, have) 213 ) 214 else: 215 commands.append(self._compute_command(have['name'], key, str(value))) 216 return commands 217 218 def _render_set_commands(self, want): 219 commands = [] 220 have = [] 221 222 params = Lag_interfaces.params 223 224 for attrib in params: 225 value = want[attrib] 226 if value: 227 if attrib == 'arp_monitor': 228 commands.extend( 229 self._add_arp_monitor(want, attrib, want, have) 230 ) 231 elif attrib == 'members': 232 commands.extend( 233 self._add_bond_members(want, have) 234 ) 235 elif attrib != 'name': 236 commands.append( 237 self._compute_command(want['name'], attrib, value=str(value)) 238 ) 239 return commands 240 241 def _purge_attribs(self, have): 242 commands = [] 243 for item in Lag_interfaces.params: 244 if have.get(item): 245 if item == 'members': 246 commands.extend( 247 self._delete_bond_members(have) 248 ) 249 elif item != 'name': 250 commands.append( 251 self._compute_command(have['name'], attrib=item, remove=True) 252 ) 253 return commands 254 255 def _render_del_commands(self, want, have): 256 commands = [] 257 258 params = Lag_interfaces.params 259 for attrib in params: 260 if attrib == 'members': 261 commands.extend( 262 self._update_bond_members(attrib, want, have) 263 ) 264 elif attrib == 'arp_monitor': 265 commands.extend( 266 self._update_arp_monitor(attrib, want, have) 267 ) 268 elif have.get(attrib) and not want.get(attrib): 269 commands.append( 270 self._compute_command(have['name'], attrib, remove=True) 271 ) 272 return commands 273 274 def _add_bond_members(self, want, have): 275 commands = [] 276 diff_members = get_lst_diff_for_dicts(want, have, 'members') 277 if diff_members: 278 for key in diff_members: 279 commands.append( 280 self._compute_command(key['member'], 'bond-group', want['name'], type='ethernet') 281 ) 282 return commands 283 284 def _add_arp_monitor(self, updates, key, want, have): 285 commands = [] 286 arp_monitor = updates.get(key) or {} 287 diff_targets = self._get_arp_monitor_target_diff(want, have, key, 'target') 288 289 if 'interval' in arp_monitor: 290 commands.append( 291 self._compute_command( 292 key=want['name'] + ' arp-monitor', attrib='interval', value=str(arp_monitor['interval']) 293 ) 294 ) 295 if diff_targets: 296 for target in diff_targets: 297 commands.append( 298 self._compute_command(key=want['name'] + ' arp-monitor', attrib='target', value=target) 299 ) 300 return commands 301 302 def _delete_bond_members(self, have): 303 commands = [] 304 for member in have['members']: 305 commands.append( 306 self._compute_command( 307 member['member'], 'bond-group', have['name'], remove=True, type='ethernet' 308 ) 309 ) 310 return commands 311 312 def _update_arp_monitor(self, key, want, have): 313 commands = [] 314 want_arp_target = [] 315 have_arp_target = [] 316 want_arp_monitor = want.get(key) or {} 317 have_arp_monitor = have.get(key) or {} 318 319 if want_arp_monitor and 'target' in want_arp_monitor: 320 want_arp_target = want_arp_monitor['target'] 321 322 if have_arp_monitor and 'target' in have_arp_monitor: 323 have_arp_target = have_arp_monitor['target'] 324 325 if 'interval' in have_arp_monitor and not want_arp_monitor: 326 commands.append( 327 self._compute_command( 328 key=have['name'] + ' arp-monitor', attrib='interval', remove=True 329 ) 330 ) 331 if 'target' in have_arp_monitor: 332 target_diff = list_diff_have_only(want_arp_target, have_arp_target) 333 if target_diff: 334 for target in target_diff: 335 commands.append( 336 self._compute_command( 337 key=have['name'] + ' arp-monitor', attrib='target', value=target, remove=True 338 ) 339 ) 340 341 return commands 342 343 def _update_bond_members(self, key, want, have): 344 commands = [] 345 want_members = want.get(key) or [] 346 have_members = have.get(key) or [] 347 348 members_diff = list_diff_have_only(want_members, have_members) 349 if members_diff: 350 for member in members_diff: 351 commands.append( 352 self._compute_command( 353 member['member'], 'bond-group', have['name'], True, 'ethernet' 354 ) 355 ) 356 return commands 357 358 def _get_arp_monitor_target_diff(self, want_list, have_list, dict_name, lst): 359 want_arp_target = [] 360 have_arp_target = [] 361 362 want_arp_monitor = want_list.get(dict_name) or {} 363 if want_arp_monitor and lst in want_arp_monitor: 364 want_arp_target = want_arp_monitor[lst] 365 366 if not have_list: 367 diff = want_arp_target 368 else: 369 have_arp_monitor = have_list.get(dict_name) or {} 370 if have_arp_monitor and lst in have_arp_monitor: 371 have_arp_target = have_arp_monitor[lst] 372 373 diff = list_diff_want_only(want_arp_target, have_arp_target) 374 return diff 375 376 def _compute_command(self, key, attrib, value=None, remove=False, type='bonding'): 377 if remove: 378 cmd = 'delete interfaces ' + type 379 else: 380 cmd = 'set interfaces ' + type 381 cmd += (' ' + key) 382 if attrib == 'arp_monitor': 383 attrib = 'arp-monitor' 384 elif attrib == 'hash_policy': 385 attrib = 'hash-policy' 386 cmd += (' ' + attrib) 387 if value: 388 cmd += (" '" + value + "'") 389 return cmd 390