def read_prev_placement(networkx, templates): # create empty overlays for all templates prev_embedding = {} # dict: template -> overlay for t in templates: prev_embedding[t] = Overlay(t, [], []) # only read and recreate placement (not edges or flows) for v in networkx.nodes.data(): node_id = v[0] node_attr = v[1] for vnf in node_attr['available_sf']: # find component that matches the VNF name (in any of the templates) for t in templates: # use first matching component (assuming it's only in one template); here, "vnf" is the vnf's name component = get_component(t, vnf) if component is not None: # add new instance to overlay of corresponding template (source components need src_flows being set) if component.source: prev_embedding[t].instances.append( Instance(component, node_id, src_flows=[])) else: prev_embedding[t].instances.append( Instance(component, node_id)) break return prev_embedding
def read_prev_embedding(file, templates, nodes, links): # create shortest paths shortest_paths = sp.all_pairs_shortest_paths(nodes, links) # create empty overlays for all templates prev_embedding = {} # dict: template -> overlay for t in templates: prev_embedding[t] = Overlay(t, [], []) with open(file, "r") as f: yaml_file = yaml.load(f, yaml.SafeLoader) # read and create VNF instances of previous embedding for vnf in yaml_file["placement"]["vnfs"]: # find component that matches the VNF name (in any of the templates) for t in templates: # use first matching component (assuming it's only in one template) if vnf["name"] in [c.name for c in t.components]: component = list(filter(lambda x: x.name == vnf["name"], t.components))[0] # add new instance to overlay of corresponding template (source components need src_flows being set) if component.source: prev_embedding[t].instances.append(Instance(component, vnf["node"], src_flows=[])) else: prev_embedding[t].instances.append(Instance(component, vnf["node"])) break # TODO: read and create flows. otherwise, adding edges really doesn't make a difference in the heuristic # read and create edges of previous embedding for edge in yaml_file["placement"]["vlinks"]: instances = [i for ol in prev_embedding.values() for i in ol.instances] # try to get source and dest instance from list of instances try: source = list(filter(lambda x: x.component.name == edge["src_vnf"] and x.location == edge["src_node"], instances))[0] dest = list(filter(lambda x: x.component.name == edge["dest_vnf"] and x.location == edge["dest_node"], instances))[0] # if the vnfs don't exist in prev_embedding (eg, through incorrect input), ignore the edge except IndexError: # print("No matching VNFs in prev_embedding for edge from {} to {}. # Ignoring the edge.".format(source, dest)) continue # skip and continue with next edge # get arc from templates by matching against source and dest components for t in templates: if source.component in t.components and dest.component in t.components: # assume t has an arc source->dest if both components are in t arc = list(filter(lambda x: x.source == source.component and x.dest == dest.component, t.arcs))[0] # add new edge to overlay of corresponding template edge = Edge(arc, source, dest) prev_embedding[t].edges.append(edge) edge.paths.append(shortest_paths[(source.location, dest.location)][0]) return prev_embedding
def read_result(file, components, arcs): reading_instances, reading_edges = False, False instances, edges = [], [] with open(file, "r") as sources_file: reader = csv.reader((row for row in sources_file), delimiter="\t") for row in reader: # empty line always means end of a segment if len(row) == 0: reading_instances = False reading_edges = False continue # start reading instances if row[0].startswith("# instances:"): reading_instances = True elif row[0].startswith("# edges:"): reading_edges = True # read instances: only set relevant attributes if reading_instances and len(row) == 2: component = list(filter(lambda x: x.name == row[0], components))[0] src_flows = None if component.source: src_flows = [] instances.append(Instance(component, row[1], src_flows)) # read edges: only set relevant attributes + mapped flows if reading_edges and len(row) == 4: arc = list(filter(lambda x: str(x) == row[0], arcs))[0] # store tuples of (arc, start_node, end_node, flow_id) edges.append((arc, row[1], row[2], row[3])) return instances, edges
def map_flow2edge(overlay, start_instance, arc, flow, flow_dr, tabu): # determine if the instances of the destination component are fixed => if so, cannot place new instances fixed = False for i in overlay.instances: if i.component == arc.dest and i.fixed: fixed = True break best_node = find_best_node(overlay, start_instance.location, arc, flow_dr, fixed, tabu) if best_node is None: logger.error(f"No suitable node found. Cannot compute placement.") return False # if the instance at best node already exists (e.g., from forward dir), just connect to it, else create anew # look for existing instance instance_exists = False for i in overlay.instances: if i.component == arc.dest and i.location == best_node: instance_exists = True dest_instance = i break # create new instance if none exists in the overlay if not instance_exists: dest_instance = Instance(arc.dest, best_node) overlay.instances.append(dest_instance) # print("\tAdded new instance {} at best node {} (may exist in other overlays)".format(dest_instance, # best_node)) logger.info( "\tAdded new instance {} at best node {} (may exist in other overlays)" .format(dest_instance, best_node)) # check if edge to dest_instance already exists edge_exists = False if instance_exists: if dest_instance in start_instance.edges_out.keys(): edge_exists = True edge = start_instance.edges_out[dest_instance] # if it doesn't exist, create a new edge and assign a path (shortest path) if not edge_exists: edge = Edge(arc, start_instance, dest_instance) overlay.edges.append(edge) edge.paths.append(shortest_paths[(start_instance.location, dest_instance.location)][0]) # map flow to edge flow.dr[edge] = flow_dr edge.flows.append(flow) # print("\tMapped flow {} (dr {}) to edge {} (new: {})".format(flow, flow_dr, edge, not edge_exists)) logger.info("\tMapped flow {} (dr {}) to edge {} (new: {})".format( flow, flow_dr, edge, not edge_exists)) return True
def __deepcopy__(self, memodict={}): new_overlay = Overlay(self.template, [], []) # empty overlay instance_dict = { } # dict of old to new instances; for easy access when adding the edges flow_dict = {} # same for old to new flows # add new instances with same attributes (component, etc) but without edges_in/out for i in self.instances: # copy src_flows new_src_flows = None if i.src_flows: new_src_flows = [] for f in i.src_flows: new_flow = Flow(f.id, f.src_dr) flow_dict[f] = new_flow new_src_flows.append(new_flow) # copy instances new_instance = Instance(i.component, i.location, new_src_flows, i.fixed) new_overlay.instances.append(new_instance) instance_dict[i] = new_instance # add new edges in topological order => sets edges_in/out etc automatically for e in self.topological_order(True): # source/dest from new_overlay's instances using the instance_dict new_source = instance_dict[e.source] new_dest = instance_dict[e.dest] # create new edge with references to the new instances and manually set the remaining attributes new_edge = Edge(e.arc, new_source, new_dest) new_edge.direction = e.direction new_edge.paths = copy.deepcopy( e.paths, memodict) # deepcopy for copying list of lists # copy and update flows for f in e.flows: new_flow = flow_dict[f] new_edge.flows.append(new_flow) new_flow.dr[new_edge] = f.dr[e] if new_edge.source.component.stateful: new_flow.passed_stateful[ new_edge.source.component] = new_edge.source elif new_edge.dest.component.stateful: new_flow.passed_stateful[ new_edge.dest.component] = new_edge.dest new_overlay.edges.append(new_edge) return new_overlay
def solve(arg_nodes, arg_links, templates, prev_overlays, sources, fixed, arg_shortest_paths, tabu=set()): # print("Previous overlays:") # for ol in prev_overlays.values(): # ol.print() # tabu_string = "" # for i in tabu: # tabu_string += "({},{}) ".format(i[0], i[1]) # print("Tabu list: {}".format(tabu_string)) # write global variables global nodes, links, shortest_paths, overlays nodes = arg_nodes links = arg_links shortest_paths = arg_shortest_paths # keep previous overlays of templates that still exist overlays = {t: ol for t, ol in prev_overlays.items() if t in templates} # create empty overlays for new templates for t in templates: if t not in overlays.keys(): overlays[t] = Overlay(t, [], []) # print("Created empty overlay for new template {}".format(t)) logger.info("Created empty overlay for new template {}".format(t)) # remove all instances of fixed components => curr fixed instances added again later; prev fixed instances removed fixed_components = {f.component for f in fixed} fixed_instances = { i for ol in overlays.values() for i in ol.instances if i.component in fixed_components } # print("Remove any existing fixed instances:", *fixed_instances, sep=" ") for i in fixed_instances: remove_instance(i) # embed templates sequentially in given order for t in templates: # print("\n-Embedding template: {}-".format(t)) logger.info("-Embedding template: {}-".format(t)) own_sources = [src for src in sources if src.component in t.components] update_sources(overlays[t], own_sources) # add fixed instances that match template t's components for f in fixed: if f.component in t.components: fixed_instance = Instance(f.component, f.location, fixed=True) if fixed_instance not in overlays[t].instances: overlays[t].instances.append(fixed_instance) # print("Added fixed instance of {} at {}".format(f.component, f.location)) logger.info("Added fixed instance of {} at {}".format( f.component, f.location)) # iterate over all instances in topological order; start in forward direction then switch to backward i = 0 direction = "forward" while i < len(overlays[t].topological_order()): instance = overlays[t].topological_order()[i] # #print("Topological order:", *overlays[t].topological_order(), sep=" ") # remove unused instances (except fixed instances) if not instance.fixed: if not instance.used(direction, overlays[t]): # print("Removed unused instance {} from overlay of {}".format(instance, t)) logger.info( "Removed unused instance {} from overlay of {}".format( instance, t)) remove_instance(instance, overlays[t]) continue # switch direction at the first instance of an end component (bc outgoing not ingoing direction considered) if instance.component.end: direction = "backward" # get outgoing flows (and their dr) for each output out_flows = instance.out_flows(direction) for k in range(len(out_flows)): arc = out_arc(t, instance.component, k, direction) # when a component is adapted for reuse, it has separate outputs for the arcs of different templates if arc is None: # for output k, this template has no arc => skip to next output # print("{}'s outgoing arc at output {} in {} direction belongs to a different template. # The output is skipped".format(instance, k, direction)) logger.debug( "{}'s outgoing arc at output {} in {} direction belongs to a different template. " "The output is skipped".format(instance, k, direction)) continue update_flow_mapping(overlays[t], instance, arc, out_flows[k], tabu) # print("Updated the flow mapping along arc {} at {}\n".format(arc, instance)) logger.info( "Updated the flow mapping along arc {} at {}\n".format( arc, instance)) i += 1 # print() if overlays[t].empty(): del overlays[t] # print("Deleted empty overlay of {}".format(t)) logger.info("Deleted empty overlay of {}".format(t)) # else: # overlays[t].print() # print("Topological order:", *overlays[t].topological_order(), sep=" ") # print() return overlays
def update_sources(overlay, sources): # reset passed_stateful for all flows (set up to date later) and remove outdated flows # print("Reset passed_stateful for all flows of template {}".format(overlay.template)) src_flows = {f for src in sources for f in src.flows} mapped_flows = {f for e in overlay.edges for f in e.flows } | {f for src in sources for f in src.flows} for f in mapped_flows: f.passed_stateful.clear() if f not in src_flows: remove_flow(overlay, f) # add/update source instances for src in sources: # get existing source instance at the location src_exists = False for i in overlay.instances: if i.component == src.component and i.location == src.location: src_exists = True break # update or add source instance depending on whether such an instance already exists or not if src_exists: # remove outdated flows for f in i.src_flows: if f not in src.flows: i.src_flows.remove(f) for e in f.dr: e.flows.remove(f) f.dr.clear() f.passed_stateful.clear() # update or add new flows for f in src.flows: # if the flow already exists, keep the existing flow and only update its src_dr if f in i.src_flows: new_src_dr = f.src_dr f = i.src_flows[i.src_flows.index( f)] # get existing flow object in i.src_flows f.src_dr = new_src_dr # else add the new flow else: i.src_flows.append(f) f.passed_stateful[i.component] = i # print("Updated/checked src_flows of existing source instance {}".format(i)) logger.info( "Updated/checked src_flows of existing source instance {}". format(i)) else: src_instance = Instance(src.component, src.location, src.flows) overlay.instances.append(src_instance) # print("Added new source instance {}".format(src_instance)) logger.info("Added new source instance {}".format(src_instance)) # remove old source instances without source source_instances = [i for i in overlay.instances if i.component.source] for src in source_instances: corresponding_sources = { s for s in sources if s.component == src.component and s.location == src.location } if len(corresponding_sources) == 0: # print("Remove source instance {} without corresponding source".format(src)) logger.info( "Remove source instance {} without corresponding source". format(src)) remove_instance(src)
def solve(arg_nodes, arg_links, templates, prev_overlays, sources, fixed, arg_obj): # write global variables global nodes, links, prev_instances, obj nodes = arg_nodes links = arg_links # copy previous instances (attributes like edges_in etc are not needed and not copied) prev_instances = { Instance(i.component, i.location, i.src_flows) for ol in prev_overlays.values() for i in ol.instances } obj = arg_obj # print input # print("Templates:", *templates, sep=" ") # print("Sources:", *sources, sep=" ") # print("Fixed instances:", *fixed, sep=" ") # print("Previous instances:", *prev_instances, sep=" ") # pre-computation of shortest paths start_init = time.time() shortest_paths = sp.all_pairs_shortest_paths(nodes, links) init_time = time.time() - start_init # print("Time for pre-computation of shortest paths: {}s\n".format(init_time)) logger.info( "Time for pre-computation of shortest paths: {}s\n".format(init_time)) start_heuristic = time.time() # get total source data rate for each source component (for sorting the templates later) src_drs = defaultdict(int) # default = 0 for src in sources: src_drs[src.component] += src.total_flow_dr() # sort templates with decreasing weight: heaviest/most difficult templates get embedded first templates.sort(key=lambda t: t.weight(src_drs[t.source()]), reverse=True) # print("Templates sorted to start with heaviest:", *templates, sep=" ") # initial solution # print("\n----- Initial solution -----") logger.info("----- Initial solution -----") overlays = heuristic.solve(arg_nodes, arg_links, templates, prev_overlays, sources, fixed, shortest_paths) obj_value = objective_value(overlays) # print("Objective value of initial solution: {}".format(obj_value)) # print("Runtime for initial solution: {}".format(time.time() - start_heuristic)) logger.info("Objective value of initial solution: {}".format(obj_value)) logger.info("Runtime for initial solution: {}\n".format(time.time() - start_heuristic)) # iterative improvement if len(nodes.ids) > 1: # doesn't work for networks with just 1 node # print("\n----- Iterative improvement -----") logger.info("----- Iterative improvement -----") overlays = improvement.improve(arg_nodes, arg_links, templates, overlays, sources, fixed, shortest_paths) obj_value = objective_value(overlays) runtime = time.time() - start_heuristic # print("Objective value after improvement: {}".format(obj_value)) # print("Heuristic runtime: {}s".format(runtime)) logger.info("Objective value after improvement: {}".format(obj_value)) logger.info("Heuristic runtime: {}s".format(runtime)) else: runtime = time.time() - start_heuristic # print("Skip iterative improvement for network with just 1 node") logger.info("Skip iterative improvement for network with just 1 node") # calculate changed instances for writing result curr_instances = {i for ol in overlays.values() for i in ol.instances} changed = prev_instances ^ curr_instances # instances that were added or removed return init_time, runtime, obj_value, changed, overlays