def _state_overridden(want, have): """ The command generator when state is overridden :rtype: A list :returns: the commands necessary to migrate the current configuration to the desired configuration """ commands = [] for vlan_id, extant in have.items(): if vlan_id in want: desired = want[vlan_id] else: desired = dict() add_config = dict_diff(extant, desired) del_config = dict_diff(desired, extant) commands.extend(generate_commands(vlan_id, add_config, del_config)) # Handle vlans not already in config new_vlans = [vlan_id for vlan_id in want if vlan_id not in have] for vlan_id in new_vlans: desired = want[vlan_id] extant = dict(vlan_id=vlan_id) add_config = dict_diff(extant, desired) commands.extend(generate_commands(vlan_id, add_config, {})) return commands
def _render_updates(self, want, have): commands = [] temp_have_members = have.pop('members', None) temp_want_members = want.pop('members', None) updates = dict_diff(have, want) if temp_have_members: have['members'] = temp_have_members if temp_want_members: want['members'] = temp_want_members commands.extend(self._add_bond_members(want, have)) if updates: for key, value in iteritems(updates): if value: if key == 'arp_monitor': commands.extend( self._add_arp_monitor(updates, key, want, have) ) else: commands.append(self._compute_command(have['name'], key, str(value))) return commands
def _state_merged(self, want, have): """ The requests generator when state is merged :rtype: A list :returns: the requests necessary to merge the provided into the current configuration """ requests = [] request_patch = deepcopy(self.VLAN_PATCH) for w in want: if w.get('vlan_id'): h = search_obj_in_list(w['vlan_id'], have, 'vlan_id') if h: if dict_diff(w, h): request_body = self._update_patch_request(w) request_patch["data"]["openconfig-vlan:vlans"][ "vlan"].append(request_body) else: request_post = self._update_post_request(w) requests.append(request_post) if len(request_patch["data"]["openconfig-vlan:vlans"]["vlan"]): request_patch["data"] = json.dumps(request_patch["data"]) requests.append(request_patch) return requests
def _state_replaced(self, want, have): """ The command generator when state is replaced :rtype: A list :returns: the commands necessary to migrate the current configuration to the desired configuration """ cmds = [] obj_in_have = search_obj_in_list(want['name'], have, 'name') if obj_in_have: diff = dict_diff(want, obj_in_have) else: diff = want merged_cmds = self.set_commands(want, have) if 'name' not in diff: diff['name'] = want['name'] replaced_cmds = [] if obj_in_have: replaced_cmds = self.del_attribs(diff) if replaced_cmds or merged_cmds: for cmd in set(replaced_cmds).intersection(set(merged_cmds)): merged_cmds.remove(cmd) cmds.extend(replaced_cmds) cmds.extend(merged_cmds) return cmds
def _state_replaced(self, want, have): """ The command generator when state is replaced. Scope is limited to vlan objects defined in the playbook. :rtype: A list :returns: The minimum command set required to migrate the current configuration to the desired configuration. """ obj_in_have = search_obj_in_list(want['vlan_id'], have, 'vlan_id') if obj_in_have: # ignore states that are already reset, then diff what's left obj_in_have = self.remove_default_states(obj_in_have) diff = dict_diff(want, obj_in_have) # Remove merge items from diff; what's left will be used to # remove states not specified in the playbook for k in dict(set(want.items()) - set(obj_in_have.items())).keys(): diff.pop(k, None) else: diff = want # merged_cmds: 'want' cmds to update 'have' states that don't match # replaced_cmds: remaining 'have' cmds that need to be reset to default merged_cmds = self.set_commands(want, have) replaced_cmds = [] if obj_in_have: # Remaining diff items are used to reset states to default replaced_cmds = self.del_attribs(diff) cmds = [] if replaced_cmds or merged_cmds: cmds += ['vlan %s' % str(want['vlan_id'])] cmds += merged_cmds + replaced_cmds return cmds
def _render_updates(self, want, have): commands = [] if have: temp_have_legacy_protos = have.pop('legacy_protocols', None) else: have = {} temp_want_legacy_protos = want.pop('legacy_protocols', None) updates = dict_diff(have, want) if have and temp_have_legacy_protos: have['legacy_protocols'] = temp_have_legacy_protos if not have and temp_want_legacy_protos: want['legacy_protocols'] = temp_want_legacy_protos commands.extend(self._add_lldp_protocols(want, have)) if updates: for key, value in iteritems(updates): if value: if key == 'enable': commands.append(self._compute_command()) elif key == 'address': commands.append( self._compute_command('management-address', str(value))) elif key == 'snmp': if value == 'disable': commands.append( self._compute_command(key, remove=True)) else: commands.append( self._compute_command(key, str(value))) return commands
def get_diff(self, comparable, base): diff = {} if not base: diff = comparable else: diff = dict_diff(base, comparable) return diff
def _state_replaced(self, w, have): """ The command generator when state is replaced :rtype: A list :returns: the commands necessary to migrate the current configuration to the desired configuration """ commands = [] obj_in_have = flatten_dict(search_obj_in_list(w['name'], have, 'name')) if obj_in_have: diff = dict_diff(w, obj_in_have) else: diff = w merged_commands = self.set_commands(w, have) if 'name' not in diff: diff['name'] = w['name'] wkeys = w.keys() dkeys = diff.keys() for k in wkeys: if k in self.exclude_params and k in dkeys: del diff[k] replaced_commands = self.del_attribs(diff) if merged_commands: cmds = set(replaced_commands).intersection(set(merged_commands)) for cmd in cmds: merged_commands.remove(cmd) commands.extend(replaced_commands) commands.extend(merged_commands) return commands
def _state_overridden(self, want, have): """ The request generator when state is overridden :rtype: A list :returns: the requests necessary to migrate the current configuration to the desired configuration """ requests = [] request_patch = deepcopy(self.VLAN_PATCH) have_copy = [] for w in want: if w.get('vlan_id'): h = search_obj_in_list(w['vlan_id'], have, 'vlan_id') if h: if dict_diff(w, h): request_body = self._update_patch_request(w) request_patch["data"]["openconfig-vlan:vlans"][ "vlan"].append(request_body) have_copy.append(h) else: request_post = self._update_post_request(w) requests.append(request_post) for h in have: if h not in have_copy and h['vlan_id'] != 1: request_delete = self._update_delete_request(h) requests.append(request_delete) if len(request_patch["data"]["openconfig-vlan:vlans"]["vlan"]): request_patch["data"] = json.dumps(request_patch["data"]) requests.append(request_patch) return requests
def _add_location(self, name, want_item, have_item): commands = [] have_dict = {} have_ca = {} set_cmd = name + ' location ' want_location_type = want_item.get('location') or {} have_location_type = have_item.get('location') or {} if want_location_type['coordinate_based']: want_dict = want_location_type.get('coordinate_based') or {} if is_dict_element_present(have_location_type, 'coordinate_based'): have_dict = have_location_type.get('coordinate_based') or {} location_type = 'coordinate-based' updates = dict_diff(have_dict, want_dict) for key, value in iteritems(updates): if value: commands.append( self._compute_command(set_cmd + location_type, key, str(value)) ) elif want_location_type['civic_based']: location_type = 'civic-based' want_dict = want_location_type.get('civic_based') or {} want_ca = want_dict.get('ca_info') or [] if is_dict_element_present(have_location_type, 'civic_based'): have_dict = have_location_type.get('civic_based') or {} have_ca = have_dict.get('ca_info') or [] if want_dict['country_code'] != have_dict['country_code']: commands.append( self._compute_command( set_cmd + location_type, 'country-code', str(want_dict['country_code']) ) ) else: commands.append( self._compute_command( set_cmd + location_type, 'country-code', str(want_dict['country_code']) ) ) commands.extend(self._add_civic_address(name, want_ca, have_ca)) elif want_location_type['elin']: location_type = 'elin' if is_dict_element_present(have_location_type, 'elin'): if want_location_type.get('elin') != have_location_type.get('elin'): commands.append( self._compute_command( set_cmd + location_type, value=str(want_location_type['elin']) ) ) else: commands.append( self._compute_command( set_cmd + location_type, value=str(want_location_type['elin']) ) ) return commands
def _state_merged(self, want, have): """ The command generator when state is merged :rtype: A list :returns: the commands necessary to merge the provided into the current configuration """ commands = [] want_copy = deepcopy(remove_empties(want)) have_copy = deepcopy(have) want_vifs = want_copy.pop('vifs', []) have_vifs = have_copy.pop('vifs', []) updates = dict_diff(have_copy, want_copy) if updates: for key, value in iteritems(updates): commands.append( self._compute_commands(key=key, value=value, interface=want_copy['name'])) if want_vifs: for want_vif in want_vifs: have_vif = search_obj_in_list(want_vif['vlan_id'], have_vifs, key='vlan_id') if not have_vif: have_vif = { 'vlan_id': want_vif['vlan_id'], 'enabled': True } vif_updates = dict_diff(have_vif, want_vif) if vif_updates: for key, value in iteritems(vif_updates): commands.append( self._compute_commands(key=key, value=value, interface=want_copy['name'], vif=want_vif['vlan_id'])) return commands
def test_dict_diff(): base = dict(obj2=dict(), b1=True, b2=False, b3=False, one=1, two=2, three=3, obj1=dict(key1=1, key2=2), l1=[1, 3], l2=[1, 2, 3], l4=[4], nested=dict(n1=dict(n2=2))) other = dict(b1=True, b2=False, b3=True, b4=True, one=1, three=4, four=4, obj1=dict(key1=2), l1=[2, 1], l2=[3, 2, 1], l3=[1], nested=dict(n1=dict(n2=2, n3=3))) result = dict_diff(base, other) # string assertions assert 'one' not in result assert 'two' not in result assert result['three'] == 4 assert result['four'] == 4 # dict assertions assert 'obj1' in result assert 'key1' in result['obj1'] assert 'key2' not in result['obj1'] # list assertions assert result['l1'] == [2, 1] assert 'l2' not in result assert result['l3'] == [1] assert 'l4' not in result # nested assertions assert 'obj1' in result assert result['obj1']['key1'] == 2 assert 'key2' not in result['obj1'] # bool assertions assert 'b1' not in result assert 'b2' not in result assert result['b3'] assert result['b4']
def _state_overridden(want, have): """ The command generator when state is overridden :rtype: A list :returns: the commands necessary to migrate the current configuration to the desired configuration """ commands = [] for key, extant in have.items(): if key in want: desired = want[key] else: desired = dict() add_config = dict_diff(extant, desired) del_config = dict_diff(desired, extant) commands.extend(generate_commands(key, add_config, del_config)) return commands
def _state_replaced(want, have): """ The command generator when state is replaced :rtype: A list :returns: the commands necessary to migrate the current configuration to the desired configuration """ commands = [] for vlan_id, desired in want.items(): if vlan_id in have: extant = have[vlan_id] else: extant = dict() add_config = dict_diff(extant, desired) del_config = dict_diff(desired, extant) commands.extend(generate_commands(vlan_id, add_config, del_config)) return commands
def set_config(want, have): commands = [] to_set = dict_diff(have, want) for member in to_set.get("members", []): channel_id = want["name"][12:] commands.extend([ "interface {0}".format(member["member"]), "channel-group {0} mode {1}".format(channel_id, member["mode"]), ]) return commands
def _state_merged(self, want, have): """ The command generator when state is merged :rtype: A list :returns: the commands necessary to merge the provided into the current configuration """ commands = [] diff = dict_diff(have, want) commands.extend(self.set_commands(diff)) return commands
def _state_replaced(want, have): """ The command generator when state is replaced :rtype: A list :returns: the commands necessary to migrate the current configuration to the desired configuration """ commands = [] for key, desired in want.items(): interface_name = normalize_interface(key) if interface_name in have: extant = have[interface_name] else: extant = dict() add_config = dict_diff(extant, desired) del_config = dict_diff(desired, extant) commands.extend(generate_commands(key, add_config, del_config)) return commands
def remove_config(want, have): commands = [] if not want.get("members"): return ["no interface {0}".format(want["name"])] to_remove = dict_diff(want, have) for member in to_remove.get("members", []): commands.extend([ "interface {0}".format(member["member"]), "no channel-group", ]) return commands
def _state_replaced(want, have): """ The command generator when state is replaced :rtype: A list :returns: the commands necessary to migrate the current configuration to the desired configuration """ commands = [] to_set = dict_diff(have, want) if 'system' in to_set: system = to_set['system'] if 'priority' in system: commands.append('lacp system-priority {0}'.format(system['priority'])) to_del = dict_diff(want, have) if 'system' in to_del: system = to_del['system'] system_set = to_set.get('system', {}) if 'priority' in system and 'priority' not in system_set: commands.append('no lacp system-priority') return commands
def _state_merged(self, want, have): """ The command generator when state is merged :rtype: A list :returns: the commands necessary to merge the provided into the current configuration """ commands = [] updates = dict_diff(have, want) if updates: for key, value in iteritems(flatten_dict(remove_empties(updates))): commands.append(self._compute_commands(key, value)) return commands
def _state_merged(want, have): """ The command generator when state is merged :rtype: A list :returns: the commands necessary to merge the provided into the current configuration """ commands = [] to_set = dict_diff(have, want) if 'system' in to_set: system = to_set['system'] if 'priority' in system: commands.append('lacp system-priority {0}'.format(system['priority'])) return commands
def _state_deleted(want, have): """ The command generator when state is deleted :rtype: A list :returns: the commands necessary to remove the current configuration of the provided objects """ commands = [] to_del = dict_diff(want, have) if 'system' in to_del: system = to_del['system'] if 'priority' in system: commands.append('no lacp system-priority') return commands
def _state_replaced(self, w, have): """ The command generator when state is replaced :rtype: A list :returns: the commands necessary to migrate the current configuration to the desired configuration """ commands = [] name = w['name'] obj_in_have = search_obj_in_list(name, have, 'name') if obj_in_have: # If 'w' does not specify mode then intf may need to change to its # default mode, however default mode may depend on sysdef. if not w.get('mode') and re.search('Ethernet|port-channel', name): sysdefs = self.intf_defs['sysdefs'] sysdef_mode = sysdefs['mode'] if obj_in_have.get('mode') != sysdef_mode: w['mode'] = sysdef_mode diff = dict_diff(w, obj_in_have) else: diff = w merged_commands = self.set_commands(w, have) if merged_commands: # merged_commands: # - These commands are changes specified by the playbook. # - merged_commands apply to both existing and new objects # replaced_commands: # - These are the unspecified commands, used to reset any params # that are not already set to default states # - replaced_commands should only be used on 'have' objects # (interfaces that already exist) if obj_in_have: if 'name' not in diff: diff['name'] = name wkeys = w.keys() dkeys = diff.keys() for k in wkeys: if k in self.exclude_params and k in dkeys: del diff[k] replaced_commands = self.del_attribs(diff) cmds = set(replaced_commands).intersection(set(merged_commands)) for cmd in cmds: merged_commands.remove(cmd) commands.extend(replaced_commands) commands.extend(merged_commands) return commands
def _state_merged(want, have): """ The command generator when state is merged :rtype: A list :returns: the commands necessary to merge the provided into the current configuration """ commands = [] updates = dict_diff(have, want) if updates: for key, value in iteritems( flatten_dict(remove_empties(updates['system']))): commands.append('lacp system {0} {1}'.format( key.replace('address', 'mac'), value)) return commands
def _state_replaced(self, want, have): """ The command generator when state is replaced :rtype: A list :returns: the commands necessary to migrate the current configuration to the desired configuration """ commands = [] merge_dict = dict_diff(have, want) # merge_dict will contain new and unique values in want delete_dict = self.find_delete_params(have, want) self._module.params['state'] = 'deleted' commands.extend(self._state_deleted(delete_dict)) # delete self._module.params['state'] = 'merged' commands.extend(self.set_commands(merge_dict)) # merge self._module.params['state'] = 'replaced' return commands
def diff_list_of_dicts(self, want, have): if not want: want = [] if not have: have = [] diff = [] for w_item in want: h_item = search_obj_in_list(w_item['member'], have, key='member') or {} delta = dict_diff(h_item, w_item) if delta: if 'member' not in delta.keys(): delta['member'] = w_item['member'] diff.append(delta) return diff
def _state_merged(want, have): """ The command generator when state is merged :rtype: A list :returns: the commands necessary to merge the provided into the current configuration """ commands = [] for vlan_id, desired in want.items(): if vlan_id in have: extant = have[vlan_id] else: extant = dict() add_config = dict_diff(extant, desired) commands.extend(generate_commands(vlan_id, add_config, {})) return commands
def _state_merged(self, want, have): """ The command generator when state is merged :rtype: A list :returns: the commands necessary to merge the provided into the current configuration """ commands = [] if not have: have = {'name': want['name']} for key, value in iteritems( flatten_dict(remove_empties(dict_diff(have, want)))): commands.append(self._compute_commands(key, value)) if commands: pad_commands(commands, want['name']) return commands
def diff_list_of_dicts(w, h): """ Returns a list containing diff between two list of dictionaries """ if not w: w = [] if not h: h = [] diff = [] for w_item in w: h_item = search_obj_in_list(w_item['member'], h, key='member') or {} d = dict_diff(h_item, w_item) if d: if 'member' not in d.keys(): d['member'] = w_item['member'] diff.append(d) return diff
def _state_deleted(want, have): """ The command generator when state is deleted :rtype: A list :returns: the commands necessary to remove the current configuration of the provided objects """ commands = [] for key in want: desired = dict() if key in have: extant = have[key] else: continue del_config = dict_diff(desired, extant) commands.extend(generate_commands(key, {}, del_config)) return commands