Example #1
0
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
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
Example #4
0
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