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 get_ace_diff(want_ace, have_ace): # gives the diff of the aces passed. for h_a in have_ace: d = dict_diff(want_ace, h_a) if not d: break return d
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 _set_commands(self, want_ace, have_ace): """A helped method that checks if there is a delta between the `have_ace` and `want_ace`. If there is a delta then it calls `_compute_commands` to create the ACE line. :rtype: A string :returns: An ACE generated from a structured ACE dictionary via a call to `_compute_commands` """ if 'line' in want_ace: if want_ace['line'] != have_ace.get('line'): return self._compute_commands(want_ace) else: if ('prefix' in want_ace.get('source', {})) or ('prefix' in want_ace.get('destination', {})): self._prepare_for_diff(want_ace) protocol_opt_delta = {} delta = dict_diff(have_ace, want_ace) # `dict_diff` doesn't work properly for `protocol_options` diff, # so we need to handle it separately if want_ace.get('protocol_options', {}): protocol_opt_delta = set(flatten_dict(have_ace.get('protocol_options', {}))) ^ \ set(flatten_dict(want_ace.get('protocol_options', {}))) if delta or protocol_opt_delta: want_ace = self._dict_merge(have_ace, want_ace) return self._compute_commands(want_ace)
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 _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 _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_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 get_diff(self, comparable, base): diff = {} if not base: diff = comparable else: diff = dict_diff(base, comparable) return diff
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 _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_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 = 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 _render_updates(self, want, have, opr=True): """ This function takes the diff between want and have and invokes the appropriate functions to create the commands to update the attributes. :param want: :param have: :return: list of commands """ commands = [] want_nh = want.get('next_hops') or [] # delete static route operation per destination if not opr and not want_nh: commands.append( self._compute_command(dest=want['dest'], remove=True)) else: temp_have_next_hops = have.pop('next_hops', None) temp_want_next_hops = want.pop('next_hops', None) updates = dict_diff(have, want) if temp_have_next_hops: have['next_hops'] = temp_have_next_hops if temp_want_next_hops: want['next_hops'] = temp_want_next_hops commands.extend(self._add_next_hop(want, have, opr=opr)) if opr and updates: for key, value in iteritems(updates): if value: if key == 'blackhole_config': commands.extend( self._add_blackhole(key, want, have)) 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 = [] have_copy = [] for w in want: for h in have: if w["name"] == h["name"]: if dict_diff(w, h): l2_request = self._update_patch_request(w, h) l2_request["data"] = json.dumps(l2_request["data"]) requests.append(l2_request) have_copy.append(h) break for h in have: if h not in have_copy: l2_delete = self._update_delete_request(h) if l2_delete["path"]: l2_delete["data"] = json.dumps(l2_delete["data"]) requests.append(l2_delete) return requests
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 _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 test_dict_diff(self): 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 self.assertNotIn('one', result) self.assertNotIn('two', result) self.assertEqual(result['three'], 4) self.assertEqual(result['four'], 4) # dict assertions self.assertIn('obj1', result) self.assertIn('key1', result['obj1']) self.assertNotIn('key2', result['obj1']) # list assertions self.assertEqual(result['l1'], [2, 1]) self.assertNotIn('l2', result) self.assertEqual(result['l3'], [1]) self.assertNotIn('l4', result) # nested assertions self.assertIn('obj1', result) self.assertEqual(result['obj1']['key1'], 2) self.assertNotIn('key2', result['obj1']) # bool assertions self.assertNotIn('b1', result) self.assertNotIn('b2', result) self.assertTrue(result['b3']) self.assertTrue(result['b4'])
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 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_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 _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 set_commands(self, want, have): commands = [] obj_in_have = flatten_dict( search_obj_in_list(want['name'], have, 'name')) if not obj_in_have: commands = self.add_commands(flatten_dict(want)) else: diff = dict_diff(obj_in_have, want) if diff: diff.update({'name': want['name']}) commands = self.add_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_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 = [] 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