def check_fwd_dags(fwd_req, topo, lsas, solver): correct = True topo = topo.copy() # Check that the topology/dag contain the destinations, otherwise add it for dest, dag in fwd_req.iteritems(): dest_in_dag = dest in dag dest_in_graph = dest in topo if not dest_in_dag or not dest_in_graph: if not dest_in_dag: sinks = ssu.find_sink(dag) else: sinks = dag.predecessors(dest) for s in sinks: if not dest_in_dag: dag.add_edge(s, dest) if not dest_in_graph: topo.add_edge(s, dest, metric=solver.new_edge_metric) fake_nodes = {} local_fake_nodes = collections.defaultdict(list) f_ids = set() for lsa in lsas: if lsa.cost > 0: if not lsa.node: # We added a pure fake LSA continue f_id = '__f_%s_%s_%s' % (lsa.node, lsa.nh, lsa.dest) f_ids.add(f_id) fake_nodes[(lsa.node, f_id, lsa.dest)] = lsa.nh cost = topo[lsa.node][lsa.nh]['metric'] topo.add_edge(lsa.node, f_id, metric=cost) topo.add_edge(f_id, lsa.dest, metric=lsa.cost - cost) log.debug('Added a globally-visible fake node: ' '%s - %s - %s - %s - %s [-> %s]', lsa.node, cost, f_id, lsa.cost - cost, lsa.dest, lsa.nh) else: local_fake_nodes[(lsa.node, lsa.dest)].append(lsa.nh) log.debug('Added a locally-visible fake node: %s -> %s', lsa.node, lsa.nh) spt = ssu.all_shortest_paths(topo, metric='metric') for dest, req_dag in fwd_req.iteritems(): log.info('Validating requirements for dest %s', dest) dag = IGPGraph() for n in filter(lambda n: n not in fwd_req, topo): if n in f_ids: continue log.debug('Checking paths of %s', n) for p in spt[n][0][dest]: log.debug('Reported path: %s', p) for u, v in zip(p[:-1], p[1:]): try: # Are we using a globally-visible fake node? nh = fake_nodes[(u, v, dest)] log.debug('%s uses the globally-visible fake node %s ' 'to get to %s', u, v, nh) dag.add_edge(u, nh) # Replace by correct next-hop break except KeyError: # Are we using a locally-visible one? nh = local_fake_nodes[(u, dest)] if nh: log.debug('%s uses a locally-visible fake node' ' to get to %s', u, nh) for h in nh: dag.add_edge(u, h) # Replace by true nh break else: dag.add_edge(u, v) # Otherwise follow the SP # Now that we have the current fwing dag, compare to the requirements for n in req_dag: successors = set(dag.successors(n)) req_succ = set(req_dag.successors(n)) if successors ^ req_succ: log.error('The successor sets for node %s differ, ' 'REQ: %s, CURRENT: %s', n, req_succ, successors) correct = False predecessors = set(dag.predecessors(n)) req_pred = set(req_dag.predecessors(n)) # Also requires to have a non-null successor sets to take into # account the fact that the destination will have new adjacencies # through fake nodes if predecessors ^ req_pred and successors: log.error('The predecessors sets for %s differ, ' 'REQ: %s, CURRENT: %s', n, req_pred, predecessors) correct = False if correct: log.info('All forwarding requirements are enforced!') return correct
class SouthboundListener(ShapeshifterProxy): """This basic controller maintains a structure describing the IGP topology and listens for changes.""" def __init__(self, *args, **kwargs): super(SouthboundListener, self).__init__(*args, **kwargs) self.igp_graph = IGPGraph() self.dirty = False self.json_proxy = SJMPClient(hostname=CFG.get(DEFAULTSECT, 'json_hostname'), port=CFG.getint(DEFAULTSECT, 'json_port'), target=self) self.quagga_manager = ProxyCloner(FakeNodeProxy, self.json_proxy) def run(self): """Connect the the southbound controller. This call will not return unless the connection is halted.""" log.info('Connecting to server ...') self.json_proxy.communicate() def stop(self): """Stop the connection to the southbound controller""" self.json_proxy.stop() def bootstrap_graph(self, graph, node_properties): self.igp_graph.clear() self.igp_graph.add_edges_from(graph) for _, _, d in self.igp_graph.edges_iter(data=True): sanitize_edge_data(d) self.update_node_properties(**node_properties) log.debug('Bootstrapped graph with edges: %s and properties: %s', self.igp_graph.edges(data=True), node_properties) self.received_initial_graph() self.graph_changed() def received_initial_graph(self): """Called when the initial graph has been bootstrapped, before calling graph_changed""" pass def add_edge(self, source, destination, properties={'metric': 1}): properties = sanitize_edge_data(properties) # metric is added twice to support backward-compat. self.igp_graph.add_edge(source, destination, properties) log.debug('Added edge: %s-%s@%s', source, destination, properties) # Only trigger an update if the link is bidirectional self.dirty = self.igp_graph.has_edge(destination, source) def commit(self): log.debug('End of graph update') if self.dirty: self.dirty = False self.graph_changed() @abc.abstractmethod def graph_changed(self): """Called when the IGP graph has changed.""" def remove_edge(self, source, destination): # TODO: pay attention to re-add the symmetric edge if only one way # crashed try: self.igp_graph.remove_edge(source, destination) log.debug('Removed edge %s-%s', source, destination) self.igp_graph.remove_edge(destination, source) log.debug('Removed edge %s-%s', destination, source) except nx.NetworkXError: # This means that we had already removed both side of the edge # earlier or that the adjacency was not fully established before # going down pass else: self.dirty = True def update_node_properties(self, **properties): log.debug('Updating node propeties: %s', properties) for node, data in properties.iteritems(): self.igp_graph.node[node].update(data) self.dirty = self.dirty or properties
def check_fwd_dags(fwd_req, topo, lsas, solver): correct = True topo = topo.copy() # Check that the topology/dag contain the destinations, otherwise add it for dest, dag in fwd_req.iteritems(): dest_in_dag = dest in dag dest_in_graph = dest in topo if not dest_in_dag or not dest_in_graph: if not dest_in_dag: sinks = ssu.find_sink(dag) else: sinks = dag.predecessors(dest) for s in sinks: if not dest_in_dag: dag.add_edge(s, dest) if not dest_in_graph: topo.add_edge(s, dest, metric=solver.new_edge_metric) fake_nodes = {} local_fake_nodes = collections.defaultdict(list) f_ids = set() for lsa in lsas: if lsa.cost > 0: f_id = '__f_%s_%s_%s' % (lsa.node, lsa.nh, lsa.dest) f_ids.add(f_id) fake_nodes[(lsa.node, f_id, lsa.dest)] = lsa.nh cost = topo[lsa.node][lsa.nh]['metric'] topo.add_edge(lsa.node, f_id, metric=cost) topo.add_edge(f_id, lsa.dest, metric=lsa.cost - cost) log.debug( 'Added a globally-visible fake node: ' '%s - %s - %s - %s - %s [-> %s]', lsa.node, cost, f_id, lsa.cost - cost, lsa.dest, lsa.nh) else: local_fake_nodes[(lsa.node, lsa.dest)].append(lsa.nh) log.debug('Added a locally-visible fake node: %s -> %s', lsa.node, lsa.nh) spt = ssu.all_shortest_paths(topo, metric='metric') for dest, req_dag in fwd_req.iteritems(): log.info('Validating requirements for dest %s', dest) dag = IGPGraph() for n in filter(lambda n: n not in fwd_req, topo): if n in f_ids: continue log.debug('Checking paths of %s', n) for p in spt[n][0][dest]: log.debug('Reported path: %s', p) for u, v in zip(p[:-1], p[1:]): try: # Are we using a globally-visible fake node? nh = fake_nodes[(u, v, dest)] log.debug( '%s uses the globally-visible fake node %s ' 'to get to %s', u, v, nh) dag.add_edge(u, nh) # Replace by correct next-hop break except KeyError: # Are we using a locally-visible one? nh = local_fake_nodes[(u, dest)] if nh: log.debug( '%s uses a locally-visible fake node' ' to get to %s', u, nh) for h in nh: dag.add_edge(u, h) # Replace by true nh break else: dag.add_edge(u, v) # Otherwise follow the SP # Now that we have the current fwing dag, compare to the requirements for n in req_dag: successors = set(dag.successors(n)) req_succ = set(req_dag.successors(n)) if successors ^ req_succ: log.error( 'The successor sets for node %s differ, ' 'REQ: %s, CURRENT: %s', n, req_succ, successors) correct = False predecessors = set(dag.predecessors(n)) req_pred = set(req_dag.predecessors(n)) # Also requires to have a non-null successor sets to take into # account the fact that the destination will have new adjacencies # through fake nodes if predecessors ^ req_pred and successors: log.error( 'The predecessors sets for %s differ, ' 'REQ: %s, CURRENT: %s', n, req_pred, predecessors) correct = False if correct: log.info('All forwarding requirements are enforced!') return correct