Example #1
0
 def testSquareWithThreeConsecutiveChangesAndMultipleRequirements(
         self, expected_lsa_count=5):
     self.log_test_name()
     dag = IGPGraph([('D2', 'B1'), ('B1', 'T1'), ('T1', 'T2'), ('T2', 'B2'),
                     ('B2', 'D1')])
     self._test(self.gadgets.square, {
         '3_8': dag,
         '8_3': dag.reverse(copy=True)
     }, expected_lsa_count)
Example #2
0
 def testSquareWithThreeConsecutiveChangesAndMultipleRequirements(self):
     log.warning('Testing SquareWithThreeConsecutiveChanges'
                 'AndMultipleRequirements')
     dag = IGPGraph([('D2', 'B1'), ('B1', 'T1'), ('T1', 'T2'), ('T2', 'B2'),
                     ('B2', 'D1')])
     self._test(self.gadgets.square, {
         '3_8': dag,
         '8_3': dag.reverse(copy=True)
     }, 5)
 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)
Example #4
0
 def testSquareWithThreeConsecutiveChangesAndMultipleRequirements(self):
     log.warning('Testing SquareWithThreeConsecutiveChanges'
                 'AndMultipleRequirements')
     dag = IGPGraph([('D2', 'B1'),
                     ('B1', 'T1'),
                     ('T1', 'T2'),
                     ('T2', 'B2'),
                     ('B2', 'D1')])
     self._test(self.gadgets.square,
                {'3_8': dag, '8_3': dag.reverse(copy=True)},
                5)
Example #5
0
 def testSquareWithThreeConsecutiveChangesAndMultipleRequirements(
         self, expected_lsa_count=5):
     self.log_test_name()
     dag = IGPGraph([('D2', 'B1'),
                     ('B1', 'T1'),
                     ('T1', 'T2'),
                     ('T2', 'B2'),
                     ('B2', 'D1')])
     self._test(self.gadgets.square,
                {'3_8': dag, '8_3': dag.reverse(copy=True)},
                expected_lsa_count)
Example #6
0
 def _setUpDoubleDiamond(self):
     #  + --------19--------- +
     #  |                     |
     #  H1 ---10--- Y1        |
     #    \         |         |
     #    15        5         |
     #     \        |         |
     #     Y2 -10-  X --100-- D --1000-- 1/8
     #              |         |
     #     H2---2---+         |
     #     /                  |
     #    6                   |
     #   /                    |
     #  A -------- 17 --------+
     self.ddiamond = g = IGPGraph()
     self._add_edge(g, 'H1', 'D', metric=19)
     self._add_edge(g, 'H1', 'Y1', metric=10)
     self._add_edge(g, 'Y1', 'X', metric=5)
     self._add_edge(g, 'H1', 'Y2', metric=15)
     self._add_edge(g, 'Y2', 'X', metric=10)
     self._add_edge(g, 'A', 'H2', metric=6)
     self._add_edge(g, 'H2', 'X', metric=2)
     self._add_edge(g, 'A', 'D', metric=17)
     self._add_edge(g, 'X', 'D', metric=100)
     for _, data in g.nodes_iter(data=True):
         data['router'] = True
Example #7
0
 def _setUpPaperGadget(self):
     # H1 -- 19 -- A1 ---------+
     #  |                      |
     #  +-- 10 ----+           2
     #             |           |
     #  H2 -- 2 -- X -- 100 -- Y
     #  |         / \          |
     #  6  H3 -- 2   \         |
     #  |   |        8         |
     #  |   6----+  /         17
     #  |        | /           |
     #  +--------A2------------+
     #
     self.paper_gadget = g = IGPGraph()
     self._add_edge(g, 'H1', 'A1', 19)
     self._add_edge(g, 'H1', 'X', 10)
     self._add_edge(g, 'A1', 'Y', 2)
     self._add_edge(g, 'X', 'Y', 100)
     self._add_edge(g, 'X', 'H2', 2)
     self._add_edge(g, 'X', 'H3', 2)
     self._add_edge(g, 'X', 'A2', 8)
     self._add_edge(g, 'H3', 'A2', 6)
     self._add_edge(g, 'H2', 'A2', 6)
     self._add_edge(g, 'Y', 'A2', 17)
     for _, data in g.nodes_iter(data=True):
         data['router'] = True
Example #8
0
 def testSquareWithThreeConsecutiveChanges(self, expected_lsa_count=3):
     self.log_test_name()
     self._test(
         self.gadgets.square, {
             '3_8':
             IGPGraph([('D2', 'B1'), ('B1', 'T1'), ('T1', 'T2'),
                       ('T2', 'B2'), ('B2', 'D1')])
         }, expected_lsa_count)
Example #9
0
 def testDiamond(self, expected_lsa_count=2):
     self.log_test_name()
     self._test(
         self.gadgets.diamond, {
             '3_8':
             IGPGraph([('A', 'Y1'), ('A', 'Y2'), ('Y2', 'X'), ('Y1', 'X'),
                       ('X', 'D'), ('O', 'D')])
         }, expected_lsa_count)
Example #10
0
 def testDiamond(self):
     log.warning('Testing Diamond')
     self._test(
         self.gadgets.diamond, {
             '3_8':
             IGPGraph([('A', 'Y1'), ('A', 'Y2'), ('Y2', 'X'), ('Y1', 'X'),
                       ('X', 'D'), ('O', 'D')])
         }, 2)
Example #11
0
 def testSquareWithThreeConsecutiveChanges(self):
     log.warning('Testing SquareWithThreeConsecutiveChanges')
     self._test(
         self.gadgets.square, {
             '3_8':
             IGPGraph([('D2', 'B1'), ('B1', 'T1'), ('T1', 'T2'),
                       ('T2', 'B2'), ('B2', 'D1')])
         }, 3)
Example #12
0
 def testDoubleDiamond(self):
     log.warning('Testing DoubleDiamond')
     self._test(
         self.gadgets.ddiamond, {
             '1_8':
             IGPGraph([('H1', 'Y1'), ('H1', 'Y2'), ('Y1', 'X'), ('Y2', 'X'),
                       ('H2', 'X'), ('X', 'D')])
         }, 3)
Example #13
0
 def testPaperGadget(self, expected_lsa_count=1):
     self.log_test_name()
     self._test(
         self.gadgets.paper_gadget, {
             '3_8':
             IGPGraph([('H1', 'X'), ('H2', 'X'), ('H3', 'X'), ('X', 'Y'),
                       ('A1', 'Y'), ('A2', 'Y')])
         }, expected_lsa_count)
Example #14
0
 def testPaperGadget(self):
     log.warning('Testing PaperGadget')
     self._test(
         self.gadgets.paper_gadget, {
             '3_8':
             IGPGraph([('H1', 'X'), ('H2', 'X'), ('H3', 'X'), ('X', 'Y'),
                       ('A1', 'Y'), ('A2', 'Y')])
         }, 1)
Example #15
0
 def testDoubleDiamond(self, expected_lsa_count=3):
     self.log_test_name()
     self._test(
         self.gadgets.ddiamond, {
             '1_8':
             IGPGraph([('H1', 'Y1'), ('H1', 'Y2'), ('Y1', 'X'), ('Y2', 'X'),
                       ('H2', 'X'), ('X', 'D')])
         }, expected_lsa_count)
Example #16
0
 def __init__(self):
     self.BASE_NET = ip_network(CFG.get(DEFAULTSECT, 'base_net'))
     self.private_addresses = PrivateAddressStore(CFG.get(DEFAULTSECT,
                                                          'private_ips'))
     self.last_line = ''
     self.leader_watchdog = None
     self.transaction = False
     self.uncommitted_changes = 0
     self.graph = IGPGraph()
     self._lsdb = {NetworkLSA.TYPE: {},
                   RouterLSA.TYPE: {},
                   ASExtLSA.TYPE: {}}
     self.controllers = defaultdict(list)  # controller nr : ip_list
     self.listener = {}
     self.keep_running = True
     self.queue = Queue()
     self.processing_thread = start_daemon_thread(
             target=self.process_lsa, name='lsa processing thread')
Example #17
0
 def testParallel(self):
     log.warning('Testing Parallel')
     self._test(
         self.gadgets.parallel, {
             '3_8':
             IGPGraph([('A2', 'B2'), ('B2', 'C2'), ('C2', 'D2'),
                       ('D2', 'D1'), ('D1', 'C1'), ('C1', 'B1'),
                       ('B1', 'A1'), ('A1', 'D')])
         }, 4)
    def simple_path_requirement(self, prefix, path):
        """Add a path requirement for the given prefix.

        :param path: The ordered list of routerid composing the path.
                     E.g. for path = [A, B, C], the following edges will be
                     used as requirements: [](A, B), (B, C), (C, D)]"""
        self.fwd_dags[prefix] = IGPGraph(
                [(s, d) for s, d in zip(path[:-1], path[1:])])
        self.refresh_lsas()
 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)
Example #20
0
 def testParallel(self, expected_lsa_count=4):
     self.log_test_name()
     self._test(
         self.gadgets.parallel, {
             '3_8':
             IGPGraph([('A2', 'B2'), ('B2', 'C2'), ('C2', 'D2'),
                       ('D2', 'D1'), ('D1', 'C1'), ('C1', 'B1'),
                       ('B1', 'A1'), ('A1', 'D')])
         }, expected_lsa_count)
Example #21
0
 def __init__(self):
     self.BASE_NET = ip_network(CFG.get(DEFAULTSECT, 'base_net'))
     self.private_addresses = PrivateAddressStore(
         CFG.get(DEFAULTSECT, 'private_ips'))
     self.last_line = ''
     self.leader_watchdog = None
     self.transaction = None
     self.graph = IGPGraph()
     self.routers = {}  # router-id : lsa
     self.networks = {}  # DR IP : lsa
     self.ext_networks = {}  # (router-id, dest) : lsa
     self.controllers = defaultdict(list)  # controller nr : ip_list
     self.listener = {}
     self.keep_running = True
     self.queue = Queue()
     self.processing_thread = Thread(target=self.process_lsa,
                                     name="lsa_processing_thread")
     self.processing_thread.setDaemon(True)
     self.processing_thread.start()
Example #22
0
    def build_graph(self):
        self.controllers.clear()
        new_graph = IGPGraph()
        # Rebuild the graph from the LSDB
        for lsa in chain(self.routers.itervalues(),
                         self.networks.itervalues(),
                         self.ext_networks.itervalues()):

            if is_expired_lsa(lsa):
                log.debug("LSA %s is too old (%d) ignoring it!",
                          lsa, lsa.age)
            else:
                lsa.apply(new_graph, self)
        # Contract all IPs to their respective router-id
        for rlsa in self.routers.itervalues():
            rlsa.contract_graph(new_graph,
                                self.private_addresses
                                .addresses_of(rlsa.routerid))
        # Figure out the controllers layout
        controller_prefix = CFG.getint(DEFAULTSECT, 'controller_prefixlen')
        # Group by controller and log them
        for ip in new_graph.nodes_iter():
            try:
                addr = ip_address(ip)
            except ValueError:
                continue  # Have a prefix
            if addr in self.BASE_NET:
                """1. Compute address diff to remove base_net
                   2. Right shift to remove host bits
                   3. Mask with controller mask"""
                cid = (((int(addr) - int(self.BASE_NET.network_address)) >>
                        self.BASE_NET.max_prefixlen - controller_prefix) &
                       ((1 << controller_prefix) - 1))
                self.controllers[cid].append(ip)
        # Contract them on the graph
        for id, ips in self.controllers.iteritems():
            cname = 'C_%s' % id
            new_graph.add_controller(cname)
            new_graph.contract(cname, ips)
        # Remove generated self loops
        new_graph.remove_edges_from(new_graph.selfloop_edges())
        self.apply_secondary_addresses(new_graph)
        return new_graph
Example #23
0
 def _setUpWeird(self):
     #     +-----D-----+
     #    /      |      \
     #   2       2       2
     #  /        |        \
     # A -- 4 -- B -- 2 -- C
     self.weird = g = IGPGraph()
     self._add_edge(g, 'A', 'B', 4)
     self._add_edge(g, 'B', 'C', 2)
     self._add_edge(g, 'D', 'C', 2)
     self._add_edge(g, 'D', 'B', 2)
     self._add_edge(g, 'D', 'A', 2)
Example #24
0
    def _setUpTrapezoid(self):
        #  R1 -- 100 -- E1 -- 10 -+
        #   |                     |
        #  100                    D
        #   |                     |
        #  R2 -- 10  -- E2 -- 10 -+

        self.trap = g = IGPGraph()
        self._add_edge(g, 'R1', 'E1', metric=100)
        self._add_edge(g, 'R1', 'R2', metric=100)
        self._add_edge(g, 'R2', 'E2', metric=10)
        self._add_edge(g, 'E1', 'D', metric=10)
        self._add_edge(g, 'E2', 'D', metric=10)
Example #25
0
 def _setUpWeird(self):
     #     +-----D-----+
     #    /      |      \
     #   2       2       2
     #  /        |        \
     # A -- 4 -- B -- 2 -- C
     self.weird = g = IGPGraph()
     self._add_edge(g, 'A', 'B', 4)
     self._add_edge(g, 'B', 'C', 2)
     self._add_edge(g, 'D', 'C', 2)
     self._add_edge(g, 'D', 'B', 2)
     self._add_edge(g, 'D', 'A', 2)
     for _, data in g.nodes_iter(data=True):
         data['router'] = True
Example #26
0
    def _setUpTrapezoid(self):
        #  R1 -- 100 -- E1 -- 10 -+
        #   |                     |
        #  100                    D
        #   |                     |
        #  R2 -- 10  -- E2 -- 10 -+

        self.trap = g = IGPGraph()
        self._add_edge(g, 'R1', 'E1', metric=100)
        self._add_edge(g, 'R1', 'R2', metric=100)
        self._add_edge(g, 'R2', 'E2', metric=10)
        self._add_edge(g, 'E1', 'D', metric=10)
        self._add_edge(g, 'E2', 'D', metric=10)
        for _, data in g.nodes_iter(data=True):
            data['router'] = True
Example #27
0
 def testTrapezoidWithEcmp(self, expected_lsa_count=3):
     self.log_test_name()
     self._test(
         self.gadgets.trap,
         {
             '2_8':
             IGPGraph([
                 ('R1', 'R2'),
                 ('R2', 'E2'),
                 ('E2', 'D'),
                 # ECMP on E1
                 ('E1', 'D'),
                 ('E1', 'R1')
             ])
         },
         expected_lsa_count)
Example #28
0
 def testTrapezoidWithEcmp(self):
     log.warning('Testing TrapezoidWithEcmp')
     self._test(
         self.gadgets.trap,
         {
             '2_8':
             IGPGraph([
                 ('R1', 'R2'),
                 ('R2', 'E2'),
                 ('E2', 'D'),
                 # ECMP on E1
                 ('E1', 'D'),
                 ('E1', 'R1')
             ])
         },
         3)
Example #29
0
 def _setUpParallelTracks(self):
     #    A2--B2--C2--D2
     #   /|   |   |   |
     #  D-A1--B1--C1--D1
     self.parallel = g = IGPGraph()
     self._add_edge(g, 'D', 'A1', 2)
     self._add_edge(g, 'D', 'A2', 2)
     self._add_edge(g, 'B2', 'A2', 2)
     self._add_edge(g, 'B1', 'A1', 2)
     self._add_edge(g, 'B1', 'C1', 2)
     self._add_edge(g, 'B2', 'C2', 2)
     self._add_edge(g, 'C2', 'D2', 2)
     self._add_edge(g, 'C1', 'D1', 2)
     self._add_edge(g, 'D2', 'D1', 2)
     self._add_edge(g, 'C2', 'C1', 2)
     self._add_edge(g, 'B2', 'B1', 2)
     self._add_edge(g, 'A2', 'A1', 2)
Example #30
0
 def _setUpSquare(self):
     self.square = g = IGPGraph()
     # T1  --10--  T2
     #  |    \       |
     #  10     5    100
     #  |        \   |
     #  B1  --3--   B2  --100--D1
     #  |
     # 100
     #  |
     #  D2
     self._add_edge(g, 'B1', 'B2', metric=3)
     self._add_edge(g, 'T1', 'B1', metric=10)
     self._add_edge(g, 'T2', 'T1', metric=10)
     self._add_edge(g, 'B2', 'T1', metric=5)
     self._add_edge(g, 'T2', 'B2', metric=100)
     self._add_edge(g, 'D1', 'B2', metric=100)
     self._add_edge(g, 'D2', 'B1', metric=100)
Example #31
0
 def commit_change(self):
     """
         @API
         commit the changes, and applied the requirements
         entered for the current session
     """
     for prefix in self.change_pfx:
         tmp = []
         if not self.simple_req[prefix]:
             self.remove_dag_requirement(prefix)
         else:
             for item in self.simple_req[prefix]:
                 for s, d in zip(item.path[:-1], item.path[1:]):
                     if (s, d) not in tmp:
                         tmp.append((s, d))
             LOG.debug('add_dag_requirement')
             self.add_dag_requirement(prefix, IGPGraph(tmp))
     del self.change_pfx[:]
     self.refresh_augmented_topo()
Example #32
0
 def _setUpParallelTracks(self):
     #    A2--B2--C2--D2
     #   /|   |   |   |
     #  D-A1--B1--C1--D1
     self.parallel = g = IGPGraph()
     self._add_edge(g, 'D', 'A1', 2)
     self._add_edge(g, 'D', 'A2', 2)
     self._add_edge(g, 'B2', 'A2', 2)
     self._add_edge(g, 'B1', 'A1', 2)
     self._add_edge(g, 'B1', 'C1', 2)
     self._add_edge(g, 'B2', 'C2', 2)
     self._add_edge(g, 'C2', 'D2', 2)
     self._add_edge(g, 'C1', 'D1', 2)
     self._add_edge(g, 'D2', 'D1', 2)
     self._add_edge(g, 'C2', 'C1', 2)
     self._add_edge(g, 'B2', 'B1', 2)
     self._add_edge(g, 'A2', 'A1', 2)
     for _, data in g.nodes_iter(data=True):
         data['router'] = True
Example #33
0
 def __init__(self):
     self.BASE_NET = ip_network(CFG.get(DEFAULTSECT, 'base_net'))
     self.private_addresses = PrivateAddressStore(CFG.get(DEFAULTSECT,
                                                          'private_ips'))
     self.last_line = ''
     self.leader_watchdog = None
     self.transaction = None
     self.graph = IGPGraph()
     self.routers = {}  # router-id : lsa
     self.networks = {}  # DR IP : lsa
     self.ext_networks = {}  # (router-id, dest) : lsa
     self.controllers = defaultdict(list)  # controller nr : ip_list
     self.listener = {}
     self.keep_running = True
     self.queue = Queue()
     self.processing_thread = Thread(target=self.process_lsa,
                                     name="lsa_processing_thread")
     self.processing_thread.setDaemon(True)
     self.processing_thread.start()
Example #34
0
 def _setUpDiamond(self):
     #  A  ---5---  Y1
     #  | \         |
     #  | 10        10
     #  |  \        |
     #  |  Y2 -15-- X ---50--- D
     #  |           |          |
     #  25 +--30----+          |
     #  | /                    |
     #  O -------- 10 ---------+
     self.diamond = g = IGPGraph()
     self._add_edge(g, 'A', 'Y1', metric=5)
     self._add_edge(g, 'Y1', 'X', metric=10)
     self._add_edge(g, 'A', 'Y2', metric=10)
     self._add_edge(g, 'Y2', 'X', metric=15)
     self._add_edge(g, 'X', 'D', metric=50)
     self._add_edge(g, 'A', 'O', metric=25)
     self._add_edge(g, 'X', 'O', metric=30)
     self._add_edge(g, 'D', 'O', metric=10)
Example #35
0
 def __init__(self):
     self.BASE_NET = ip_network(CFG.get(DEFAULTSECT, 'base_net'))
     self.private_address_network = ip_network(CFG.get(DEFAULTSECT,
                                               'private_net'))
     try:
         with open(CFG.get(DEFAULTSECT, 'private_ips'), 'r') as f:
             self.private_address_binding = json.load(f)
             self.router_private_address = {}
             for subnets in self.private_address_binding.itervalues():
                 for rid, ip in subnets.iteritems():
                     try:
                         iplist = self.router_private_address[rid]
                     except KeyError:
                         iplist = self.router_private_address[rid] = []
                     # Enable single private address as string
                     if isinstance(ip, str):
                         ip = [ip]
                     iplist.extend(ip)
     except Exception as e:
         log.warning('Incorrect private IP addresses binding file')
         log.warning(str(e))
         self.private_address_binding = {}
         self.router_private_address = {}
     self.last_line = ''
     self.leader_watchdog = None
     self.transaction = None
     self.graph = IGPGraph()
     self.routers = {}  # router-id : lsa
     self.networks = {}  # DR IP : lsa
     self.ext_networks = {}  # (router-id, dest) : lsa
     self.controllers = defaultdict(list)  # controller nr : ip_list
     self.listener = {}
     self.keep_running = True
     self.queue = Queue()
     self.processing_thread = Thread(target=self.process_lsa,
                                     name="lsa_processing_thread")
     self.processing_thread.setDaemon(True)
     self.processing_thread.start()
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 #37
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
Example #38
0
class LSDB(object):

    def __init__(self):
        self.BASE_NET = ip_network(CFG.get(DEFAULTSECT, 'base_net'))
        self.private_addresses = PrivateAddressStore(CFG.get(DEFAULTSECT,
                                                             'private_ips'))
        self.last_line = ''
        self.leader_watchdog = None
        self.transaction = False
        self.uncommitted_changes = 0
        self.graph = IGPGraph()
        self._lsdb = {NetworkLSA.TYPE: {},
                      RouterLSA.TYPE: {},
                      ASExtLSA.TYPE: {}}
        self.controllers = defaultdict(list)  # controller nr : ip_list
        self.listener = {}
        self.keep_running = True
        self.queue = Queue()
        self.processing_thread = start_daemon_thread(
                target=self.process_lsa, name='lsa processing thread')

    @property
    def routers(self):
        return self._lsdb[RouterLSA.TYPE]

    @property
    def networks(self):
        return self._lsdb[NetworkLSA.TYPE]

    @property
    def ext_networks(self):
        return self._lsdb[ASExtLSA.TYPE]

    def set_leader_watchdog(self, wd):
        self.leader_watchdog = wd

    def get_leader(self):
        return min(self.controllers.iterkeys()) if self.controllers else None

    def stop(self):
        for l in self.listener.values():
            l.session.stop()
        self.keep_running = False
        self.queue.put('')

    def lsdb(self, lsa):
        return self._lsdb.get(lsa.TYPE, None)

    def register_change_listener(self, listener):
        try:
            del self.listener[listener]
            log.info('Shapeshifter disconnected.')
        except KeyError:
            log.info('Shapeshifter connected.')
            l = ProxyCloner(ShapeshifterProxy, listener)
            self.listener[listener] = l
            l.bootstrap_graph(graph=[(u, v, d)
                                     for u, v, d in self.graph.export_edges()
                                     ],
                              node_properties={n: data for n, data in
                                               self.graph.nodes_iter(data=True)
                                               })

    def commit_change(self, line):
        # Check that this is not a duplicate of a previous update ...
        if self.last_line == line or not line:
            return
        self.queue.put(line)

    def forwarding_address_of(self, src, dst):
        """
        Return the forwarding address for a src, dst pair.
        If src is specified, return the private 'link-local' address of
        the src-dst link, otherwise return a 'public' IP belonging to dst
        :param src: the source node of the link towards the FA, possibly null
        :param dst: the node owning the forwarding address
        :return: forwarding address (str)
                or None if no compatible address was found
        """
        # If we have a src address, we want the set of private IPs
        # Otherwise we want any IP of dst
        if src:
            try:
                return self.graph[src][dst]['dst_address']
            except KeyError as e:
                log.error("Couldn't resolve local forwarding of %s-%s, missing"
                          " key %s", src, dst, e)
        else:
            try:
                data = filter(lambda v: v is not None,
                              (self.graph[dst][succ].get('src_address', None)
                               for succ in self.graph.successors_iter(dst)))
                if data:
                    return min(data)
                log.error("Cannot use %s as nexthop as it has no physical "
                          "link to other routers!", dst)
            except KeyError:
                log.error("Couldn't find nexthop %s when resolving global "
                          "forwarding address", dst)
        return None

    def remove_lsa(self, lsa):
        lsdb = self.lsdb(lsa)
        try:
            del lsdb[lsa.key()]
        except (KeyError, TypeError):  # LSA not found, lsdb is None
            pass

    def add_lsa(self, lsa):
        lsdb = self.lsdb(lsa)
        try:
            lsdb[lsa.key()] = lsa
        except TypeError:  # LSDB is None
            pass

    def get_current_seq_number(self, lsa):
        try:
            return self.lsdb(lsa)[lsa.key()].seqnum
        except (KeyError, TypeError):  # LSA not found, LSDB is None
            return None

    def is_old_seqnum(self, lsa):
        """Return whether the lsa is older than the copy in the lsdb is any"""
        c_seqnum = self.get_current_seq_number(lsa)
        new_seqnum = lsa.seqnum
        return (c_seqnum and  # LSA already present in the LSDB
                not is_newer_seqnum(new_seqnum, c_seqnum) and
                # We allow duplicate as they are used to flush LSAs
                new_seqnum != c_seqnum)

    def handle_lsa_line(self, line):
        """We received a line describing an lsa, handle it"""
        action, lsa_info = line.split(SEP_ACTION)
        if action == BEGIN:
            self.start_transaction()
        elif action == COMMIT:
            self.reset_transaction()
        else:  # ADD/REM LSA messages
            lsa = parse_lsa(lsa_info)
            log.debug('Parsed %s: %s [%d]', action, lsa, lsa.seqnum)
            # Sanity checks
            if self.is_old_seqnum(lsa):
                log.debug("OLD seqnum for LSA, ignoring it...")
                action = None

            # Perform the update if it is still applicable
            if action == REM:
                self.remove_lsa(lsa)
                self.uncommitted_changes += 1
            elif action == ADD:
                self.add_lsa(lsa)
                self.uncommitted_changes += 1

    def process_lsa(self):
        """Parse new LSAs, and update the graph if needed"""
        while self.keep_running:
            try:
                line = self.queue.get(timeout=5)
                self.queue.task_done()
                if not line:
                    continue
                self.handle_lsa_line(line)
            except Empty:
                self.reset_transaction()
            if (self.queue.empty() and  # Try to empty the queue before update
                    not self.transaction and self.uncommitted_changes):
                self.commit()

    def reset_transaction(self):
        """Reset the transaction"""
        self.transaction = False

    def start_transaction(self):
        """Record a new LSA transaction"""
        self.transaction = True

    def commit(self):
        """Updates have been made on the LSDB, update the graph"""
        # Update graph accordingly
        new_graph = self.build_graph()
        # Compute graph difference and update it
        self.update_graph(new_graph)
        self.uncommitted_changes = 0

    def __str__(self):
        strs = [str(lsa) for lsa in chain(self.routers.values(),
                                          self.networks.values(),
                                          self.ext_networks.values())]
        strs.insert(0, '* LSDB Content [%d]:' % len(strs))
        return '\n'.join(strs)

    def build_graph(self):
        self.controllers.clear()
        new_graph = IGPGraph()
        # Rebuild the graph from the LSDB
        for lsa in chain(self.routers.itervalues(),
                         self.networks.itervalues(),
                         self.ext_networks.itervalues()):

            if is_expired_lsa(lsa):
                log.debug("LSA %s is too old (%d) ignoring it!",
                          lsa, lsa.age)
            else:
                lsa.apply(new_graph, self)
        # Contract all IPs to their respective router-id
        for rlsa in self.routers.itervalues():
            rlsa.contract_graph(new_graph,
                                self.private_addresses
                                .addresses_of(rlsa.routerid))
        # Figure out the controllers layout
        controller_prefix = CFG.getint(DEFAULTSECT, 'controller_prefixlen')
        # Group by controller and log them
        for ip in new_graph.nodes_iter():
            try:
                addr = ip_address(ip)
            except ValueError:
                continue  # Have a prefix
            if addr in self.BASE_NET:
                """1. Compute address diff to remove base_net
                   2. Right shift to remove host bits
                   3. Mask with controller mask"""
                cid = (((int(addr) - int(self.BASE_NET.network_address)) >>
                        self.BASE_NET.max_prefixlen - controller_prefix) &
                       ((1 << controller_prefix) - 1))
                self.controllers[cid].append(ip)
        # Contract them on the graph
        for id, ips in self.controllers.iteritems():
            cname = 'C_%s' % id
            new_graph.add_controller(cname)
            new_graph.contract(cname, ips)
        # Remove generated self loops
        new_graph.remove_edges_from(new_graph.selfloop_edges())
        self.apply_secondary_addresses(new_graph)
        return new_graph

    def update_graph(self, new_graph):
        self.leader_watchdog.check_leader(self.get_leader())
        added_edges = new_graph.difference(self.graph)
        removed_edges = self.graph.difference(new_graph)
        node_prop_diff = {n: data
                          for n, data in new_graph.nodes_iter(data=True)
                          if n not in self.graph or
                          (data.viewitems() - self.graph.node[n].viewitems())}
        # Propagate differences
        if added_edges or removed_edges or node_prop_diff:
            log.debug('Pushing changes')
            for u, v in added_edges:
                self.for_all_listeners('add_edge', u, v,
                                       new_graph.export_edge_data(u, v))
            for u, v in removed_edges:
                self.for_all_listeners('remove_edge', u, v)
            if node_prop_diff:
                self.for_all_listeners('update_node_properties',
                                       **node_prop_diff)
            if CFG.getboolean(DEFAULTSECT, 'draw_graph'):
                new_graph.draw(CFG.get(DEFAULTSECT, 'graph_loc'))
            self.graph = new_graph
            log.info('LSA update yielded +%d -%d edges changes, '
                      '%d node property changes', len(added_edges),
                      len(removed_edges), len(node_prop_diff))
            self.for_all_listeners('commit')

    def for_all_listeners(self, funcname, *args, **kwargs):
        """Apply funcname to all listeners"""
        f = methodcaller(funcname, *args, **kwargs)
        map(f, self.listener.itervalues())

    def apply_secondary_addresses(self, graph):
        for src, dst in graph.router_links:
            try:
                graph[src][dst]['dst_address'] = self.private_addresses\
                                                .addresses_of(dst, src)
            except KeyError:
                log.debug('%(src)-%(dst)s does not yet exists on the graph'
                          ', ignoring private addresses.', locals())
                pass
Example #39
0
class LSDB(object):

    BASE_NET = ip_network(CFG.get(DEFAULTSECT, 'base_net'))

    def __init__(self):
        self.BASE_NET = ip_network(CFG.get(DEFAULTSECT, 'base_net'))
        self.private_address_network = ip_network(CFG.get(DEFAULTSECT,
                                                  'private_net'))
        try:
            with open(CFG.get(DEFAULTSECT, 'private_ips'), 'r') as f:
                self.private_address_binding = json.load(f)
                self.router_private_address = {}
                for subnets in self.private_address_binding.itervalues():
                    for rid, ip in subnets.iteritems():
                        try:
                            iplist = self.router_private_address[rid]
                        except KeyError:
                            iplist = self.router_private_address[rid] = []
                        # Enable single private address as string
                        if isinstance(ip, str):
                            ip = [ip]
                        iplist.extend(ip)
        except Exception as e:
            log.warning('Incorrect private IP addresses binding file')
            log.warning(str(e))
            self.private_address_binding = {}
            self.router_private_address = {}
        self.last_line = ''
        self.leader_watchdog = None
        self.transaction = None
        self.graph = IGPGraph()
        self.routers = {}  # router-id : lsa
        self.networks = {}  # DR IP : lsa
        self.ext_networks = {}  # (router-id, dest) : lsa
        self.controllers = defaultdict(list)  # controller nr : ip_list
        self.listener = {}
        self.keep_running = True
        self.queue = Queue()
        self.processing_thread = Thread(target=self.process_lsa,
                                        name="lsa_processing_thread")
        self.processing_thread.setDaemon(True)
        self.processing_thread.start()

    def set_leader_watchdog(self, wd):
        self.leader_watchdog = wd

    def get_leader(self):
        return min(self.controllers.iterkeys()) if self.controllers else None

    def stop(self):
        for l in self.listener.values():
            l.session.stop()
        self.keep_running = False
        self.queue.put('')

    def lsdb(self, lsa):
        if lsa.TYPE == RouterLSA.TYPE:
            return self.routers
        elif lsa.TYPE == NetworkLSA.TYPE:
            return self.networks
        elif lsa.TYPE == ASExtLSA.TYPE:
            return self.ext_networks

    def register_change_listener(self, listener):
        try:
            del self.listener[listener]
            log.info('Shapeshifter disconnected.')
        except KeyError:
            log.info('Shapeshifter connected.')
            l = ProxyCloner(ShapeshifterProxy, listener)
            self.listener[listener] = l
            l.bootstrap_graph(graph=[(u, v, d.get('metric', -1))
                                     for u, v, d in self.graph.edges(data=True)
                                     ],
                              node_properties={n: data for n, data in
                                               self.graph.nodes_iter(data=True)
                                               })

    @staticmethod
    def extract_lsa_properties(lsa_part):
        d = {}
        for prop in lsa_part.split(SEP_INTER_FIELD):
            if not prop:
                continue
            key, val = prop.split(SEP_INTRA_FIELD)
            d[key] = val
        return d

    def commit_change(self, line):
        # Check that this is not a duplicate of a previous update ...
        if self.last_line == line:
            return
        self.queue.put(line)

    def forwarding_address_of(self, src, dst):
        """
        Return the forwarding address for a src, dst pair. If src is specified, return
        the private 'link-local' address of the src-dst link, otherwise return a 'public'
        IP belonging to dst
        :param src: the source node of the link towards the FA, possibly null
        :param dst: the node owning the forwarding address
        :return: forwarding address (str) or None if no compatible address was found
        """
        try:
            return self.graph[src][dst]['dst_address'] if src \
                else self.graph[dst][self.graph.neighbors(dst)[0]]['src_address']
        except KeyError:
            log.debug('%s-%s not found in graph', src, dst)
            return None

    def remove_lsa(self, lsa):
        lsdb = self.lsdb(lsa)
        try:
            del lsdb[lsa.key()]
        except KeyError:
            pass

    def add_lsa(self, lsa):
        lsdb = self.lsdb(lsa)
        lsdb[lsa.key()] = lsa

    def process_lsa(self):
        while self.keep_running:
            commit = False
            try:
                line = self.queue.get(timeout=5)
                if not line:
                    self.queue.task_done()
                    continue
                # Start parsing the LSA log
                action, lsa_info = line.split(SEP_ACTION)
                if action == BEGIN:
                    self.transaction = Transaction()
                elif action == COMMIT:
                    if self.transaction:
                        self.transaction.commit(self)
                        self.transaction = None
                        commit = True
                else:
                    lsa_parts = [self.extract_lsa_properties(part)
                                 for part in lsa_info.split(SEP_GROUP) if part]
                    lsa = LSA.parse(LSAHeader.parse(lsa_parts.pop(0)),
                                    lsa_parts)
                    log.debug('Parsed %s: %s', action, lsa)
                    if action == REM:
                        if not self.transaction:
                            self.remove_lsa(lsa)
                        else:
                            self.transaction.remove_lsa(lsa)
                    elif action == ADD:
                        if not self.transaction:
                            self.add_lsa(lsa)
                        else:
                            self.transaction.add_lsa(lsa)
                    if lsa.push_update_on_remove() or not action == REM:
                        commit = True
                self.queue.task_done()
            except Empty:
                if self.transaction:
                    log.debug('Splitting transaction due to timeout')
                    self.transaction.commit(self)
                    self.transaction = Transaction()
                    commit = True
            if commit:
                # Update graph accordingly
                new_graph = self.build_graph()
                # Compute graph difference and update it
                self.update_graph(new_graph)

    def __str__(self):
        strs = [str(lsa) for lsa in chain(self.routers.values(),
                                          self.networks.values(),
                                          self.ext_networks.values())]
        strs.insert(0, '* LSDB Content [%d]:' % len(strs))
        return '\n'.join(strs)

    def build_graph(self):
        self.controllers.clear()
        new_graph = IGPGraph()
        # Rebuild the graph from the LSDB
        for lsa in chain(self.routers.values(),
                         self.networks.values(),
                         self.ext_networks.values()):
            lsa.apply(new_graph, self)
        # Contract all IPs to their respective router-id
        for lsa in self.routers.values():
            lsa.contract_graph(new_graph, self.router_private_address.get(
                lsa.routerid, []))
        # Figure out the controllers layout
        controller_prefix = CFG.getint(DEFAULTSECT, 'controller_prefixlen')
        # Group by controller and log them
        for ip in new_graph.nodes_iter():
            try:
                addr = ip_address(ip)
            except ValueError:
                continue  # Have a prefix
            if addr in self.BASE_NET:
                """1. Compute address diff to remove base_net
                   2. Right shift to remove host bits
                   3. Mask with controller mask
                """
                id = (((int(addr) - int(self.BASE_NET.network_address)) >>
                       self.BASE_NET.max_prefixlen - controller_prefix) &
                      ((1 << controller_prefix) - 1))
                self.controllers[id].append(ip)
        # Contract them on the graph
        for id, ips in self.controllers.iteritems():
            cname = 'C_%s' % id
            new_graph.add_controller(cname)
            new_graph.contract(cname, ips)
        # Remove generated self loops
        new_graph.remove_edges_from(new_graph.selfloop_edges())
        self.apply_secondary_addresses(new_graph)
        return new_graph

    def update_graph(self, new_graph):
        self.leader_watchdog.check_leader(self.get_leader())
        added_edges = new_graph.difference(self.graph)
        removed_edges = self.graph.difference(new_graph)
        node_prop_diff = {n: data
                          for n, data in new_graph.nodes_iter(data=True)
                          if n not in self.graph or
                          (data.viewitems() - self.graph.node[n].viewitems())}
        # Propagate differences
        if added_edges or removed_edges or node_prop_diff:
            log.debug('Pushing changes')
            for u, v in added_edges:
                self.for_all_listeners('add_edge',
                                       u, v, metric=new_graph.metric(u, v))
            for u, v in removed_edges:
                self.for_all_listeners('remove_edge', u, v)
            if node_prop_diff:
                self.for_all_listeners('update_node_properties',
                                       **node_prop_diff)
            if CFG.getboolean(DEFAULTSECT, 'draw_graph'):
                new_graph.draw(CFG.get(DEFAULTSECT, 'graph_loc'))
            self.graph = new_graph
            log.info('LSA update yielded +%d -%d edges changes, '
                    '%d node property changes', len(added_edges),
                    len(removed_edges), len(node_prop_diff))
            self.for_all_listeners('commit')

    def for_all_listeners(self, funcname, *args, **kwargs):
        """Apply funcname to all listeners"""
        for i in self.listener.itervalues():
            getattr(i, funcname)(*args, **kwargs)

    def apply_secondary_addresses(self, graph):
        for subnet in self.private_address_binding.itervalues():
            for dst, ip in subnet.iteritems():
                for src in subnet.iterkeys():
                    if src == dst:
                        continue
                    try:
                        graph[src][dst]['dst_address'] = ip
                    except KeyError:
                        pass