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) if ( "speed" in add_config.keys() and "duplex" not in add_config.keys() ): add_config.update({"duplex": desired.get("duplex")}) 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 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) if ( "speed" in add_config.keys() and "duplex" not in add_config.keys() ): add_config.update({"duplex": desired.get("duplex")}) commands.extend(generate_commands(key, add_config, del_config)) return commands
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. if not have_ace: return dict_diff({}, want_ace) for h_a in have_ace: d = dict_diff(want_ace, h_a) if not d: break return d
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 = [] for name, value in iteritems(want): if name in have: to_clear = dict_diff(value, have[name]) commands.extend(_clear_config(name, to_clear)) to_set = dict_diff(have[name], value) commands.extend(_set_config(name, to_set)) return remove_duplicate_interface(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 _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_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_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 _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 _compare_seq(self, afi, w, h): wl_child = {} hl_child = {} parser = ["prefixlist.entry", "prefixlist.resequence"] for seq, ent in iteritems(w): seq_diff = {} wl_child = {"afi": afi, "prefix_lists": {"entries": {seq: ent}}} if h.get(seq): hl_child = { "afi": afi, "prefix_lists": { "entries": { seq: h.pop(seq) } }, } seq_diff = dict_diff( hl_child["prefix_lists"]["entries"][seq], wl_child["prefix_lists"]["entries"][seq], ) if seq_diff: if self.state == "merged": self._module.fail_json( msg="Sequence number " + str(seq) + " is already present. Use replaced/overridden operation to change the configuration" ) self.compare(parsers="prefixlist.entry", want={}, have=hl_child) self.compare(parsers=parser, want=wl_child, have=hl_child)
def _set_config(name, want, have, module): commands = [] diff = dict_diff(have, want) if diff.get('trunk') and diff.get('access'): module.fail_json(msg='Interface should either be trunk or access') if diff.get('access'): value = diff['access'] if not have.get('access'): commands.append('switchport mode access') if value['vlan'] != have.get('access', {}).get('vlan'): commands.append('switchport access vlan {}'.format(value['vlan'])) elif diff.get('trunk'): value = diff['trunk'] if not have.get('trunk'): commands.append('switchport mode trunk') if value.get('allowed_vlans'): for vlan in value.get('allowed_vlans'): if vlan not in have.get('trunk', {}).get('allowed_vlans', []): commands.append( 'switchport trunk allowed vlan add {}'.format(vlan)) if value.get('native_vlan') and value.get('native_vlan') != have.get( 'trunk', {}).get('native_vlan'): commands.append('switchport trunk native vlan {}'.format( value['native_vlan'])) if commands: commands.insert(0, 'interface {}'.format(name)) return commands
def _state_merged(self, want, have): """ The request generator when state is merged :rtype: A list :returns: the requests necessary to merge the provided into the current configuration """ requests = [] commands = [] for w in want: for h in have: if w['name'] == h['name']: diff = dict_diff(h, w) if diff: interface_request, interface_command = self._update_patch_request( diff, h, w) for request in interface_request: if request["data"].get( "openconfig-interfaces:config") and len( request["data"] ["openconfig-interfaces:config"]): request['data'] = json.dumps(request['data']) requests.append(request) elif request['data'].get( 'openconfig-if-ethernet:config') and len( request['data'] ['openconfig-if-ethernet:config']): request['data'] = json.dumps(request['data']) requests.append(request) commands.extend(interface_command) break return requests, 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 = 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, True) if 'name' not in diff: diff['name'] = w['name'] dkeys = diff.keys() for k in w.copy(): 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 _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(): 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_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 _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 _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 get_diff(self, comparable, base): diff = {} if not base: diff = comparable else: diff = dict_diff(base, comparable) return diff
def _compare_lists(self, want, have): """ Handles list attributes from config_data """ for x in [ "communities", "community_maps", "correlator.rule_sets", "correlator.rules", "context", "groups", "hosts", "interfaces", "mib_object_lists", "mib_schema", "mib_bulkstat_transfer_ids", "users", "targets", ]: wantx = want.get(x, {}) havex = have.get(x, {}) if "." in x: complex_parser = x.split(".") wantx = want.get(complex_parser[0], {}).get(complex_parser[1], {}) havex = have.get(complex_parser[0], {}).get(complex_parser[1], {}) if x in [ "interfaces", "correlator.rules", "mib_schema", "mib_bulkstat_transfer_ids", ]: # handling complex parsers for replaced and overridden state for key, wentry in iteritems(wantx): hentry = havex.pop(key, {}) updates = dict_diff(hentry, wentry) if updates and x in [ "interfaces", "mib_schema", "mib_bulkstat_transfer_ids", ]: updates.update(name=wentry["name"]) self.addcmd(updates, x) elif updates and x == "correlator.rules": updates.update(rule_name=wentry["rule_name"]) self.addcmd(updates, x) else: for key, wentry in iteritems(wantx): hentry = havex.pop(key, {}) if wentry != hentry: self.addcmd(wentry, x) for key, hentry in iteritems(havex): self.addcmd(hentry, x, negate=True)
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 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_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(name=key) 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_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 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 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 get_updated_ace(w, h): # gives the ace to be updated in case of merge update. if not dict_diff(w, h): return w_updated = w.copy() for hkey in h.keys(): if hkey not in w.keys(): w_updated.update({hkey: h[hkey]}) else: w_updated.update({hkey: w[hkey]}) return w_updated