Example #1
0
    def __init__(self, _config, peerClass=ExaBGPPeerWorker):
        log.debug("Instantiating Manager")

        self.config = _config
        self.peerClass = peerClass

        # RTC is defaults to being enabled
        self.config['enable_rtc'] = getBoolean(
            self.config.get('enable_rtc', True))

        self.routeTableManager = RouteTableManager()
        self.routeTableManager.start()

        if 'local_address' not in self.config:
            raise Exception("config needs a local_address")

        if 'my_as' not in self.config:
            raise Exception("config needs a my_as")
        self.config['my_as'] = int(self.config['my_as'])

        if 'peer_as' in self.config:
            raise Exception("config must omit peer_as, because only iBGP "
                            "is supported yet")
        self.config['peer_as'] = self.config['my_as']

        self.peers = {}
        if self.config['peers']:
            peersAddresses = [
                x.strip() for x in self.config['peers'].strip().split(",")
            ]
            for peerAddress in peersAddresses:
                log.debug("Creating a peer worker for %s", peerAddress)
                peerWorker = self.peerClass(self, None, peerAddress,
                                            self.config)
                self.peers[peerAddress] = peerWorker
                peerWorker.start()

        self.trackedSubs = dict()

        # we need a .name since we'll masquerade as a routeEntry source
        self.name = "BGPManager"
Example #2
0
    def __init__(self, _config, peerClass=ExaBGPPeerWorker):
        log.debug("Instantiating Manager")

        self.config = _config
        self.peerClass = peerClass

        # RTC is defaults to being enabled
        self.config['enable_rtc'] = getBoolean(self.config.get('enable_rtc',
                                                               True))

        self.routeTableManager = RouteTableManager()
        self.routeTableManager.start()

        if 'local_address' not in self.config:
            raise Exception("config needs a local_address")

        if 'my_as' not in self.config:
            raise Exception("config needs a my_as")
        self.config['my_as'] = int(self.config['my_as'])

        if 'peer_as' in self.config:
            raise Exception("config must omit peer_as, because only iBGP "
                            "is supported yet")
        self.config['peer_as'] = self.config['my_as']

        self.peers = {}
        if self.config['peers']:
            peersAddresses = [x.strip() for x in
                              self.config['peers'].strip().split(",")]
            for peerAddress in peersAddresses:
                log.debug("Creating a peer worker for %s", peerAddress)
                peerWorker = self.peerClass(
                    self, None, peerAddress, self.config)
                self.peers[peerAddress] = peerWorker
                peerWorker.start()

        self.trackedSubs = dict()

        # we need a .name since we'll masquerade as a routeEntry source
        self.name = "BGPManager"
Example #3
0
class Manager(LookingGlass):

    def __init__(self, _config, peerClass=ExaBGPPeerWorker):
        log.debug("Instantiating Manager")

        self.config = _config
        self.peerClass = peerClass

        # RTC is defaults to being enabled
        self.config['enable_rtc'] = getBoolean(self.config.get('enable_rtc',
                                                               True))

        self.routeTableManager = RouteTableManager()
        self.routeTableManager.start()

        if 'local_address' not in self.config:
            raise Exception("config needs a local_address")

        if 'my_as' not in self.config:
            raise Exception("config needs a my_as")
        self.config['my_as'] = int(self.config['my_as'])

        if 'peer_as' in self.config:
            raise Exception("config must omit peer_as, because only iBGP "
                            "is supported yet")
        self.config['peer_as'] = self.config['my_as']

        self.peers = {}
        if self.config['peers']:
            peersAddresses = [x.strip() for x in
                              self.config['peers'].strip().split(",")]
            for peerAddress in peersAddresses:
                log.debug("Creating a peer worker for %s", peerAddress)
                peerWorker = self.peerClass(
                    self, None, peerAddress, self.config)
                self.peers[peerAddress] = peerWorker
                peerWorker.start()

        self.trackedSubs = dict()

        # we need a .name since we'll masquerade as a routeEntry source
        self.name = "BGPManager"

    @logDecorator.log
    def stop(self):
        for peer in self.peers.itervalues():
            peer.stop()
        self.routeTableManager.stop()
        for peer in self.peers.itervalues():
            peer.join()
        self.routeTableManager.join()

    def _pushEvent(self, routeEvent):
        log.debug("push event to RouteTableManager")
        self.routeTableManager.enqueue(routeEvent)

    def cleanup(self, worker):
        log.debug("push cleanup event for worker %s to RouteTableManager",
                  worker.name)
        self.routeTableManager.enqueue(WorkerCleanupEvent(worker))

        # TODO(tmmorin): withdraw RTC routes corresponding to worker
        # subscriptions -- currently ok since VPNInstance._stop() calls
        # unsubscribe

    def getLocalAddress(self):
        try:
            return self.config['local_address']
        except KeyError:
            log.error("BGPManager config has no localAddress defined")
            return "0.0.0.0"

    def routeEventSubUnsub(self, subobj):
        if isinstance(subobj, Subscription):
            self._routeEventSubscribe(subobj)
        elif isinstance(subobj, Unsubscription):
            self._routeEventUnsubscribe(subobj)
        else:
            assert(False)

    @logDecorator.log
    def _routeEventSubscribe(self, subscription):

        self.routeTableManager.enqueue(subscription)

        # synthesize a RouteEvent for a RouteTarget constraint route
        if (self.config['enable_rtc'] and not isinstance(subscription.worker,
                                                         BGPPeerWorker)):

            firstWorkerForSubscription = self._trackedSubscriptionsAddWorker(
                subscription)

            # FIXME: not excellent to hardcode this here
            if ((subscription.safi in (SAFI.mpls_vpn, SAFI.evpn))
                    and firstWorkerForSubscription):
                routeEvent = RouteEvent(
                    RouteEvent.ADVERTISE,
                    self._subscription2RTCRouteEntry(subscription), self)
                log.debug(
                    "Based on subscription => synthesized RTC %s", routeEvent)
                self.routeTableManager.enqueue(routeEvent)
            else:
                log.debug("No need to synthesize an RTC route "
                          "(firstWorkerForSubscription:%s) ",
                          firstWorkerForSubscription)

    @logDecorator.log
    def _routeEventUnsubscribe(self, unsubscription):

        self.routeTableManager.enqueue(unsubscription)

        if (self.config['enable_rtc'] and not isinstance(unsubscription.worker,
                                                         BGPPeerWorker)):

            wasLastWorkerForSubscription = \
                self._trackedSubscriptionsRemoveWorker(unsubscription)

            # FIXME: not excellent to hardcode this here
            if ((unsubscription.safi in (SAFI.mpls_vpn, SAFI.evpn))
                    and wasLastWorkerForSubscription):
                # synthesize a withdraw RouteEvent for a RouteTarget constraint
                # route
                routeEvent = RouteEvent(
                    RouteEvent.WITHDRAW,
                    self._subscription2RTCRouteEntry(unsubscription), self)
                log.debug("Based on unsubscription => synthesized withdraw"
                          " for RTC %s", routeEvent)
                self.routeTableManager.enqueue(routeEvent)
            else:
                log.debug("No need to synthesize an RTC route "
                          "(wasLastWorkerForSubscription:%s) ",
                          wasLastWorkerForSubscription)

    # FIXME: this can be subject to races
    def _trackedSubscriptionsAddWorker(self, subs):
        '''returns 1 if this is the first worker subscribed'''

        result = 0
        if (subs.afi, subs.safi, subs.routeTarget) not in self.trackedSubs:
            self.trackedSubs[(subs.afi, subs.safi, subs.routeTarget)] = set()
            result = 1

        self.trackedSubs[
            (subs.afi, subs.safi, subs.routeTarget)].add(subs.worker)

        return result

    # FIXME: this can be subject to races
    def _trackedSubscriptionsRemoveWorker(self, subs):
        '''returns 1 if this was the last worker subscribed'''

        self.trackedSubs[(subs.afi, subs.safi, subs.routeTarget)
                         ].remove(subs.worker)

        if len(self.trackedSubs[(subs.afi, subs.safi, subs.routeTarget)]) == 0:
            del self.trackedSubs[(subs.afi, subs.safi, subs.routeTarget)]
            return 1
        else:
            return 0

    def _subscription2RTCRouteEntry(self, subscription):

        route = Route(RouteTargetConstraint(AFI(AFI.ipv4), SAFI(
            SAFI.rtc), self.config['my_as'], subscription.routeTarget))
        nh = Inet(
            1, socket.inet_pton(socket.AF_INET, self.config['local_address']))
        route.attributes.add(NextHop(nh))

        routeEntry = RouteEntry(AFI(AFI.ipv4), SAFI(
            SAFI.rtc), [], route.nlri, route.attributes, self)

        return routeEntry

    # Looking Glass Functions ###################

    def getLGMap(self):
        return {"peers":   (LGMap.COLLECTION,
                            (self.getLGPeerList, self.getLGPeerPathItem)),
                "routes":  (LGMap.FORWARD, self.routeTableManager),
                "workers": (LGMap.FORWARD, self.routeTableManager), }

    def getEstablishedPeersCount(self):
        return reduce(lambda count, peer: count +
                      (isinstance(peer, BGPPeerWorker) and
                       peer.isEstablished()),
                      self.peers.itervalues(), 0)

    def getLGPeerList(self):
        return [{"id": peer.peerAddress,
                 "state": peer.fsm.state} for peer in self.peers.itervalues()]

    def getLGPeerPathItem(self, pathItem):
        return self.peers[pathItem]
 def setUp(self):
     super(TestRouteTableManager, self).setUp()
     self.routeTableManager = RouteTableManager()
     self.routeTableManager.start()
     self.setEventTargetWorker(self.routeTableManager)
class TestRouteTableManager(TestCase, BaseTestBagPipeBGP):
    def setUp(self):
        super(TestRouteTableManager, self).setUp()
        self.routeTableManager = RouteTableManager()
        self.routeTableManager.start()
        self.setEventTargetWorker(self.routeTableManager)

    def tearDown(self):
        super(TestRouteTableManager, self).tearDown()
        self.routeTableManager.stop()
        self.routeTableManager.join()

    def _newworker(self, workerName, workerType):
        worker = mock.Mock(spec=workerType)
        worker.name = workerName
        worker.enqueue = mock.Mock()
        return worker

    def _workerSubscriptions(self,
                             worker,
                             rts,
                             afi=AFI(AFI.ipv4),
                             safi=SAFI(SAFI.mpls_vpn)):
        for rt in rts:
            subscribe = Subscription(afi, safi, rt, worker)
            self.routeTableManager.enqueue(subscribe)

    def _workerUnsubscriptions(self,
                               worker,
                               rts,
                               afi=AFI(AFI.ipv4),
                               safi=SAFI(SAFI.mpls_vpn)):
        for rt in rts:
            unsubscribe = Unsubscription(afi, safi, rt, worker)
            self.routeTableManager.enqueue(unsubscribe)

    def _checkSubscriptions(self, worker, matches):
        for match in matches:
            subs = self.routeTableManager.getWorkerSubscriptions(worker)
            self.assertIn(match, subs, "Subscription not found")

    def _checkUnsubscriptions(self, worker, matches):
        for match in matches:
            subs = self.routeTableManager.getWorkerSubscriptions(worker)
            self.assertNotIn(match, subs, "Subscription not found")

    def _checkCalls(self, worker, entry):
        self.assertTrue(
            entry in self.routeTableManager.getWorkerRouteEntries(worker),
            "Route entry not found")

    def _checkNoRouteEntry(self, worker, entry):
        self.assertTrue(
            entry not in self.routeTableManager.getWorkerRouteEntries(worker),
            "Route entry found")

    def _checkEventsCalls(self, events, advertisedRoutes, withdrawnNLRIs):
        '''
        checks that each advertise event in 'events' is in advertisedRoutes,
        that each withdraw event in 'events' is in withdrawnNLRIs
        and that all events in withdrawnNLRIs and advertisedRoutes are in
        'events'
        '''
        for (callArgs, _) in events:
            if (callArgs[0].type == RouteEvent.ADVERTISE):
                self.assertIn(callArgs[0].routeEntry, advertisedRoutes,
                              "Bad advertised route")
                advertisedRoutes.remove(callArgs[0].routeEntry)
            else:  # WITHDRAW
                self.assertIn(callArgs[0].routeEntry.nlri, withdrawnNLRIs,
                              "Bad withdrawn route")
                withdrawnNLRIs.remove(callArgs[0].routeEntry.nlri)
        self.assertEqual(0, len(advertisedRoutes), "some routes not advert'd")
        self.assertEqual(0, len(withdrawnNLRIs), "some routes not withdrawn")

    def testA1_SubscriptionsWithNoRouteTosynthesize(self):
        # Worker1 subscribes to RT1 and RT2
        worker1 = self._newworker("Worker-1", Worker)
        self._workerSubscriptions(worker1, [RT1, RT2])
        # Waiting for RouteTableManager thread finishes to process the
        # subscriptions
        self._wait()
        # check subscriptions
        self._checkSubscriptions(worker1, [MATCH1, MATCH2])

    def testA2_SubscriptionsWithRouteTosynthesize(self):
        # BGPPeerWorker1 advertises a route for RT1 and RT2
        bgpPeerWorker1 = self._newworker("BGPWorker1", BGPPeerWorker)
        evt1 = self._newRouteEvent(RouteEvent.ADVERTISE, NLRI1, [RT1, RT2],
                                   bgpPeerWorker1, NH1)
        # BGPPeerWorker1 advertises an other route for RT2
        evt2 = self._newRouteEvent(RouteEvent.ADVERTISE, NLRI2, [RT2],
                                   bgpPeerWorker1, NH1)
        # BGPPeerWorker1 subscribes to RT1
        self._workerSubscriptions(bgpPeerWorker1, [RT1])
        # Worker1 subscribes to RT1 and RT2
        worker1 = self._newworker("Worker-1", Worker)
        self._workerSubscriptions(worker1, [RT1, RT2])
        # Worker2 subscribes to RT1
        worker2 = self._newworker("Worker-2", Worker)
        self._workerSubscriptions(worker2, [RT1])
        # Worker3 subscribes to RT3
        worker3 = self._newworker("Worker-3", Worker)
        self._workerSubscriptions(worker3, [RT3])
        # BGPPeerWorker2 subscribes to RT1
        bgpPeerWorker2 = self._newworker("BGPWorker2", BGPPeerWorker)
        self._workerSubscriptions(bgpPeerWorker2, [RT1])
        # Waiting for RouteTableManager thread finishes to process the
        # subscription
        self._wait()
        # check route entry synthesized
        self.assertEqual(0, bgpPeerWorker1.enqueue.call_count,
                         "Route should not be synthesized to its source")
        self.assertEqual(0, worker3.enqueue.call_count,
                         "no route should be synthesized to Worker3")
        self.assertEqual(
            0, bgpPeerWorker2.enqueue.call_count,
            "Route should not be synthesized between BGP workers")
        self.assertEqual(
            2, worker1.enqueue.call_count,
            "2 advertise events should be synthesized to Worker1")
        self._checkEventsCalls(worker1.enqueue.call_args_list,
                               [evt1.routeEntry, evt2.routeEntry], [])
        self.assertEqual(1, worker2.enqueue.call_count,
                         "1 advertise event should be synthesized to Worker2")
        self._checkEventsCalls(worker2.enqueue.call_args_list,
                               [evt1.routeEntry], [])

    def testA3_ReSubscription(self):
        # BGPPeerWorker1 advertises a route for RT1 and RT2
        bgpPeerWorker1 = self._newworker("BGPWorker1", BGPPeerWorker)
        routeEvent = self._newRouteEvent(RouteEvent.ADVERTISE, NLRI1,
                                         [RT1, RT2], bgpPeerWorker1, NH1)
        # Worker1 subscribes to RT1 and RT2
        worker1 = self._newworker("Worker-1", Worker)
        self._workerSubscriptions(worker1, [RT1, RT2])
        # Worker1 subscribes again to RT1
        self._workerSubscriptions(worker1, [RT1])
        # Worker2 subscribes to RT1
        worker2 = self._newworker("Worker-2", Worker)
        self._workerSubscriptions(worker2, [RT1])
        # Worker1 subscribes again to RT2
        self._workerSubscriptions(worker2, [RT2])
        # Waiting for RouteTableManager thread finishes to process the
        # subscription
        self._wait()
        # check route entry synthesized
        self.assertEqual(
            1, worker1.enqueue.call_count,
            "1 route advertised should be synthesized to Worker1")
        self._checkEventsCalls(worker1.enqueue.call_args_list,
                               [routeEvent.routeEntry], [])
        self.assertEqual(
            1, worker2.enqueue.call_count,
            "1 route advertised should be synthesized to Worker2")
        self._checkEventsCalls(worker2.enqueue.call_args_list,
                               [routeEvent.routeEntry], [])

    def testB1_UnsubscriptionWithNoRouteTosynthesize(self):
        # Worker1 subscribes to RT1 and RT2
        worker1 = self._newworker("Worker-1", Worker)
        self._workerSubscriptions(worker1, [RT1, RT2])
        # BGPPeerWorker1 subscribes to RT1 and RT2
        bgpPeerWorker1 = self._newworker("BGPWorker1", BGPPeerWorker)
        self._workerSubscriptions(bgpPeerWorker1, [RT1, RT2])
        # Worker1 unsubscribes to RT1
        self._workerUnsubscriptions(worker1, [RT1])
        # BGPPeerWorker1 unsubscribes to RT1 and RT2
        self._workerUnsubscriptions(bgpPeerWorker1, [RT1, RT2])
        # Waiting for RouteTableManager thread finishes to process the
        # subscription
        self._wait()
        # check subscription/unsubscriptions
        self._checkUnsubscriptions(worker1, [MATCH1])
        self._checkSubscriptions(worker1, [MATCH2])
        self._checkUnsubscriptions(bgpPeerWorker1, [MATCH1, MATCH2])

    def testB2_UnsubscriptionWithRouteTosynthesize(self):
        # BGPPeerWorker1 advertises a route for RT1
        bgpPeerWorker1 = self._newworker("BGPWorker1", BGPPeerWorker)
        evt1 = self._newRouteEvent(RouteEvent.ADVERTISE, NLRI1, [RT1, RT2],
                                   bgpPeerWorker1, NH1)
        # BGPPeerWorker1 advertises an other route for RT2
        evt2 = self._newRouteEvent(RouteEvent.ADVERTISE, NLRI2, [RT2],
                                   bgpPeerWorker1, NH1)
        # BGPPeerWorker1 subscribes to RT1
        self._workerSubscriptions(bgpPeerWorker1, [RT1])
        # Worker1 subscribes to RT1 and RT2
        worker1 = self._newworker("Worker-1", Worker)
        self._workerSubscriptions(worker1, [RT1, RT2])
        # Worker2 subscribes to RT2
        worker2 = self._newworker("Worker-2", Worker)
        self._workerSubscriptions(worker2, [RT2])
        # Worker3 subscribes to RT3
        worker3 = self._newworker("Worker-3", Worker)
        self._workerSubscriptions(worker3, [RT3])
        # BGPPeerWorker2 subscribes to RT1
        bgpPeerWorker2 = self._newworker("BGPWorker2", BGPPeerWorker)
        self._workerSubscriptions(bgpPeerWorker2, [RT1])
        # Workers and BGPPeerWorker unsubscriptions
        self._workerUnsubscriptions(bgpPeerWorker1, [RT1])
        self._workerUnsubscriptions(worker1, [RT1])
        self._workerUnsubscriptions(worker2, [RT2])
        self._workerUnsubscriptions(worker3, [RT3])
        self._workerUnsubscriptions(bgpPeerWorker2, [RT1])
        # Waiting for RouteTableManager thread finishes to process the
        # subscription
        self._wait()
        # check route entry synthesized
        self.assertEqual(0, bgpPeerWorker1.enqueue.call_count,
                         "Route should not be synthesized to its source")
        self.assertEqual(0, worker3.enqueue.call_count,
                         "no route should be synthesized to Worker3")
        self.assertEqual(
            0, bgpPeerWorker2.enqueue.call_count,
            "Route should not be synthesized between "
            "BGPPeerWorkers")
        self.assertEqual(2, worker1.enqueue.call_count,
                         "2 advertise event should be synthesized to Worker1")
        self._checkEventsCalls(worker1.enqueue.call_args_list,
                               [evt1.routeEntry, evt2.routeEntry], [])
        self.assertEqual(
            4, worker2.enqueue.call_count,
            "4 events should be synthesized to Worker2: "
            "2 advertise and 2 withdraw")
        self._checkEventsCalls(worker2.enqueue.call_args_list,
                               [evt1.routeEntry, evt2.routeEntry],
                               [evt1.routeEntry.nlri, evt2.routeEntry.nlri])

    def testB3_UnsubscriptionNotRegistered(self):
        # Worker1 subscribes to RT1
        worker1 = self._newworker("Worker-1", Worker)
        self._workerSubscriptions(worker1, [RT1])
        # Worker1 unsubscribes to RT2
        self._workerUnsubscriptions(worker1, [RT2])
        # BGPPeerWorker1 unsubscribes to RT1
        bgpPeerWorker1 = self._newworker("BGPWorker1", BGPPeerWorker)
        self._workerUnsubscriptions(bgpPeerWorker1, [RT1, RT2])
        # Waiting for RouteTableManager thread finishes to process the
        # subscription
        self._wait()
        # check subscription/unsubscriptions
        self._checkSubscriptions(worker1, [MATCH1])
        self._checkUnsubscriptions(bgpPeerWorker1, [MATCH1, MATCH2])

    def testC1_routeAdvertiseByWorkerWithoutPropagation(self):
        # Worker1 advertises a route for RT1 and RT2
        worker1 = self._newworker("Worker-1", Worker)
        routeEvent = self._newRouteEvent(RouteEvent.ADVERTISE, NLRI1,
                                         [RT1, RT2], worker1, NH1)
        # check route entry has been inserted
        self._checkCalls(worker1, routeEvent.routeEntry)

    def testC2_routeWithdrawByWorkerWithoutPropagation(self):
        # Worker1 advertises then withdraws a route
        worker1 = self._newworker("Worker-1", Worker)
        self._newRouteEvent(RouteEvent.ADVERTISE, NLRI1, [RT1, RT2], worker1,
                            NH1)
        routeEvent = self._newRouteEvent(RouteEvent.WITHDRAW, NLRI1, [RT1],
                                         worker1, NH1)
        # check route entry has been removed
        self._checkNoRouteEntry(worker1, routeEvent.routeEntry)

    def testC3_routeAdvertiseByBGPPeerWithPropagation(self):
        # Worker1 subscribes to RT1
        worker1 = self._newworker("Worker-1", Worker)
        self._workerSubscriptions(worker1, [RT1])
        # Worker2 subscribes to RT2
        worker2 = self._newworker("Worker-2", Worker)
        self._workerSubscriptions(worker2, [RT2])
        # BGPPeerWorker1 subscribes to RT1 and RT2
        bgpPeerWorker1 = self._newworker("BGPWorker1", BGPPeerWorker)
        self._workerSubscriptions(bgpPeerWorker1, [RT1, RT2])
        # BGPPeerWorker2 subscribes to RT1 and RT2
        bgpPeerWorker2 = self._newworker("BGPWorker2", BGPPeerWorker)
        self._workerSubscriptions(bgpPeerWorker2, [RT1, RT2])
        # BGPPeerWorker1 advertises a route for RT1
        routeEvent = self._newRouteEvent(RouteEvent.ADVERTISE, NLRI1, [RT1],
                                         bgpPeerWorker1, NH1)
        # check routeEvent propagation
        self.assertEqual(1, worker1.enqueue.call_count,
                         "1 route should be propagated to Worker1")
        self._checkEventsCalls(worker1.enqueue.call_args_list,
                               [routeEvent.routeEntry], [])
        self.assertEqual(0, worker2.enqueue.call_count,
                         "no route should be propagated to Worker2")
        self.assertEqual(0, bgpPeerWorker1.enqueue.call_count,
                         "Route should not be propagated to its source")
        self.assertEqual(0, bgpPeerWorker2.enqueue.call_count,
                         "Route should not be propagated between BGP workers")

    def testC4_routeWithdrawByPeerWorkerWithPropagation(self):
        # Worker1 subscribes to RT1
        worker1 = self._newworker("Worker-1", Worker)
        self._workerSubscriptions(worker1, [RT1])
        # Worker2 subscribes to RT2
        worker2 = self._newworker("Worker-2", Worker)
        self._workerSubscriptions(worker2, [RT2])
        # Worker3 subscribes to RT3
        worker3 = self._newworker("Worker-3", Worker)
        self._workerSubscriptions(worker3, [RT3])
        # BGPPeerWorker1 subscribes to RT1
        bgpPeerWorker1 = self._newworker("BGPWorker1", BGPPeerWorker)
        self._workerSubscriptions(bgpPeerWorker1, [RT1])
        # BGPPeerWorker2 subscribes to RT2
        bgpPeerWorker2 = self._newworker("BGPWorker2", BGPPeerWorker)
        self._workerSubscriptions(bgpPeerWorker2, [RT2])
        # BGPPeerWorker1 advertises a route for RT1 and RT2
        routeEventA = self._newRouteEvent(RouteEvent.ADVERTISE, NLRI1,
                                          [RT1, RT2], bgpPeerWorker1, NH1)
        # BGPPeerWorker1 withdraw previous route (without RT
        routeEventW = self._newRouteEvent(RouteEvent.WITHDRAW, NLRI1, [],
                                          bgpPeerWorker1, NH1)
        # check routeEvent propagation
        self.assertEqual(2, worker1.enqueue.call_count,
                         "2 routes should be propagated to Worker1")
        self._checkEventsCalls(worker1.enqueue.call_args_list,
                               [routeEventA.routeEntry],
                               [routeEventW.routeEntry.nlri])
        self.assertEqual(2, worker2.enqueue.call_count,
                         "2 routes should be propagated to Worker2")
        self._checkEventsCalls(worker2.enqueue.call_args_list,
                               [routeEventA.routeEntry],
                               [routeEventW.routeEntry.nlri])
        self.assertEqual(0, worker3.enqueue.call_count,
                         "No route should be propagated to Worker3")
        self.assertEqual(0, bgpPeerWorker1.enqueue.call_count,
                         "Route should not be propagated to its source")
        self.assertEqual(0, bgpPeerWorker2.enqueue.call_count,
                         "Route should not be propagated between BGP workers")

    def testC5_routeUpdateByBGPPeerWithWithdrawPropagation(self):
        # Worker1 subscribes to RT1
        worker1 = self._newworker("Worker-1", Worker)
        self._workerSubscriptions(worker1, [RT1])
        # Worker2 subscribes to RT2
        worker2 = self._newworker("Worker-2", Worker)
        self._workerSubscriptions(worker2, [RT2])
        # Worker3 subscribes to RT3
        worker3 = self._newworker("Worker-3", Worker)
        self._workerSubscriptions(worker3, [RT3])
        # BGPPeerWorker1 advertises a route for RT1, RT2 and RT3
        bgpPeerWorker1 = self._newworker("BGPWorker1", BGPPeerWorker)
        evt1 = self._newRouteEvent(RouteEvent.ADVERTISE, NLRI1,
                                   [RT1, RT2, RT3], bgpPeerWorker1, NH1)
        # BGPPeerWorker1 advertises the same nlri with attributes NH and RTs
        # modification
        evt2 = self._newRouteEvent(RouteEvent.ADVERTISE, NLRI1, [RT1, RT2],
                                   bgpPeerWorker1, NH2)
        # check route event propagation
        # TO DO : check routeEvent.replacedRoute
        self.assertEqual(0, bgpPeerWorker1.enqueue.call_count,
                         "Route should not be propagated to its source")
        self.assertEqual(2, worker1.enqueue.call_count,
                         "2 routes should be advertised to Worker1")
        self._checkEventsCalls(worker1.enqueue.call_args_list,
                               [evt1.routeEntry, evt2.routeEntry], [])
        self.assertEqual(2, worker2.enqueue.call_count,
                         "2 routes should be advertised to Worker2")
        self._checkEventsCalls(worker2.enqueue.call_args_list,
                               [evt1.routeEntry, evt2.routeEntry], [])
        self.assertEqual(2, worker3.enqueue.call_count,
                         "2 routes should be advert/withdraw to Worker3")
        self._checkEventsCalls(worker3.enqueue.call_args_list,
                               [evt1.routeEntry], [evt1.routeEntry.nlri])

    def testC6_routeReadvertised(self):
        # Worker1 subscribes to RT1
        worker1 = self._newworker("Worker-1", Worker)
        self._workerSubscriptions(worker1, [RT1, RT2, RT3])
        # BGPPeerWorker1 advertises a route for RT1, RT2 and RT3
        bgpPeerWorker1 = self._newworker("BGPWorker1", BGPPeerWorker)
        evt1 = self._newRouteEvent(RouteEvent.ADVERTISE, NLRI1, [RT1, RT2],
                                   bgpPeerWorker1, NH1)
        # BGPPeerWorker1 advertises the same nlri with same attributes and RTs
        self._newRouteEvent(RouteEvent.ADVERTISE, NLRI1, [RT1, RT2],
                            bgpPeerWorker1, NH1)
        # check route event propagation
        self.assertEqual(0, bgpPeerWorker1.enqueue.call_count,
                         "Route should not be propagated to its source")
        self.assertEqual(1, worker1.enqueue.call_count,
                         "only 1 route should be advertised to Worker1")
        self._checkEventsCalls(worker1.enqueue.call_args_list,
                               [evt1.routeEntry], [])

    def testC7_routeWithdrawNotRegistered(self):
        # Worker1 subscribes to RT1
        worker1 = self._newworker("Worker-1", Worker)
        self._workerSubscriptions(worker1, [RT1, RT2])
        # BGPPeerWorker1 advertises a route for RT1 and RT2
        bgpPeerWorker1 = self._newworker("BGPWorker1", BGPPeerWorker)
        evt1 = self._newRouteEvent(RouteEvent.ADVERTISE, NLRI1, [RT1, RT2],
                                   bgpPeerWorker1, NH1)
        # BGPPeerWorker1 withdraw a not registered route (without RT
        self._newRouteEvent(RouteEvent.WITHDRAW, NLRI2, [], bgpPeerWorker1,
                            NH1)
        # Waiting for RouteTableManager thread finishes to process routeEvent
        self._wait()
        # check routeEvent propagation
        self.assertEqual(1, worker1.enqueue.call_count,
                         "1 route1 should be propagated to Worker1")
        self._checkEventsCalls(worker1.enqueue.call_args_list,
                               [evt1.routeEntry], [])
        self.assertEqual(0, bgpPeerWorker1.enqueue.call_count,
                         "Route should not be propagated back to its source")

    def testD1_WorkerCleanup(self):
        # Worker1 subscribes to RT1
        worker1 = self._newworker("Worker-1", Worker)
        self._workerSubscriptions(worker1, [RT1])
        # Worker2 subscribes to RT2
        worker2 = self._newworker("Worker-2", Worker)
        self._workerSubscriptions(worker2, [RT2])
        # BGPPeerWorker1 subscribes to RT1 and RT2
        bgpPeerWorker1 = self._newworker("BGPWorker1", BGPPeerWorker)
        self._workerSubscriptions(bgpPeerWorker1, [RT1, RT2])
        # BGPPeerWorker1 advertises a route for RT1 and RT2
        evt1 = self._newRouteEvent(RouteEvent.ADVERTISE, NLRI1, [RT1, RT2],
                                   bgpPeerWorker1, NH1)
        # BGPPeerWorker1 advertises an other route for RT2
        evt2 = self._newRouteEvent(RouteEvent.ADVERTISE, NLRI2, [RT2],
                                   bgpPeerWorker1, NH1)
        # Cleanup Worker1
        self.routeTableManager.enqueue(WorkerCleanupEvent(bgpPeerWorker1))
        # Waiting for RouteTableManager thread finishes to process the
        # subscriptions
        self._wait()
        # check unsubscriptions
        self._checkUnsubscriptions(bgpPeerWorker1, [MATCH1, MATCH2])
        # Check route synthesize to Worker1 and Worker2
        self.assertEqual(2, worker1.enqueue.call_count,
                         "2 routes should be advert/withdraw to Worker1")
        self._checkEventsCalls(worker1.enqueue.call_args_list,
                               [evt1.routeEntry], [evt1.routeEntry.nlri])
        self.assertEqual(4, worker2.enqueue.call_count,
                         "4 routes should be advert/withdraw to Worker2")
        self._checkEventsCalls(worker2.enqueue.call_args_list,
                               [evt1.routeEntry, evt2.routeEntry],
                               [evt1.routeEntry.nlri, evt2.routeEntry.nlri])
        # Check route entries have been removed for BGPPeerWorker1
        self._checkNoRouteEntry(bgpPeerWorker1, evt1.routeEntry)
        self._checkNoRouteEntry(bgpPeerWorker1, evt2.routeEntry)

    def testE1_DumpState(self):
        # BGPPeerWorker1 advertises a route for RT1 and RT2
        bgpPeerWorker1 = self._newworker("BGPWorker1", BGPPeerWorker)
        self._newRouteEvent(RouteEvent.ADVERTISE, NLRI1, [RT1, RT2],
                            bgpPeerWorker1, NH1)
        # BGPPeerWorker1 advertises an other route for RT2
        self._newRouteEvent(RouteEvent.ADVERTISE, NLRI2, [RT2], bgpPeerWorker1,
                            NH1)
        # BGPPeerWorker1 subscribes to RT1
        self._workerSubscriptions(bgpPeerWorker1, [RT1])
        # Worker1 subscribes to RT1 and RT2
        worker1 = self._newworker("Worker-1", Worker)
        self._workerSubscriptions(worker1, [RT1, RT2])
        # Worker2 subscribes to RT1
        worker2 = self._newworker("Worker-2", Worker)
        self._workerSubscriptions(worker2, [RT1])
        # Worker3 subscribes to RT3
        worker3 = self._newworker("Worker-3", Worker)
        self._workerSubscriptions(worker3, [RT3])

        # Waiting for RouteTableManager thread finishes to process the
        # subscriptions and route event processing
        self._wait()

        self.routeTableManager._dumpState()

    def testF1_testEmptyRT(self):
        # worker advertises a route with no RT

        w1 = self._newworker("Worker1", Worker)

        subscribe = Subscription(AFI(AFI.ipv4), SAFI(SAFI.mpls_vpn), None, w1)
        self.routeTableManager.enqueue(subscribe)

        w2 = self._newworker("Worker2", Worker)

        routeEvent = RouteEvent(
            RouteEvent.ADVERTISE,
            RouteEntry(AFI(AFI.ipv4), SAFI(SAFI.mpls_vpn), None, NLRI1,
                       Attributes(), w2), w2)

        self.routeTableManager.enqueue(routeEvent)

        self._wait()

        self.assertEqual(
            1, w1.enqueue.call_count,
            "1 route advertised should be synthesized to Worker1")
Example #6
0
class Manager(LookingGlass):
    def __init__(self, _config, peerClass=ExaBGPPeerWorker):
        log.debug("Instantiating Manager")

        self.config = _config
        self.peerClass = peerClass

        # RTC is defaults to being enabled
        self.config['enable_rtc'] = getBoolean(
            self.config.get('enable_rtc', True))

        self.routeTableManager = RouteTableManager()
        self.routeTableManager.start()

        if 'local_address' not in self.config:
            raise Exception("config needs a local_address")

        if 'my_as' not in self.config:
            raise Exception("config needs a my_as")
        self.config['my_as'] = int(self.config['my_as'])

        if 'peer_as' in self.config:
            raise Exception("config must omit peer_as, because only iBGP "
                            "is supported yet")
        self.config['peer_as'] = self.config['my_as']

        self.peers = {}
        if self.config['peers']:
            peersAddresses = [
                x.strip() for x in self.config['peers'].strip().split(",")
            ]
            for peerAddress in peersAddresses:
                log.debug("Creating a peer worker for %s", peerAddress)
                peerWorker = self.peerClass(self, None, peerAddress,
                                            self.config)
                self.peers[peerAddress] = peerWorker
                peerWorker.start()

        self.trackedSubs = dict()

        # we need a .name since we'll masquerade as a routeEntry source
        self.name = "BGPManager"

    @logDecorator.log
    def stop(self):
        for peer in self.peers.itervalues():
            peer.stop()
        self.routeTableManager.stop()
        for peer in self.peers.itervalues():
            peer.join()
        self.routeTableManager.join()

    def _pushEvent(self, routeEvent):
        log.debug("push event to RouteTableManager")
        self.routeTableManager.enqueue(routeEvent)

    def cleanup(self, worker):
        log.debug("push cleanup event for worker %s to RouteTableManager",
                  worker.name)
        self.routeTableManager.enqueue(WorkerCleanupEvent(worker))

        # TODO(tmmorin): withdraw RTC routes corresponding to worker
        # subscriptions -- currently ok since VPNInstance._stop() calls
        # unsubscribe

    def getLocalAddress(self):
        try:
            return self.config['local_address']
        except KeyError:
            log.error("BGPManager config has no localAddress defined")
            return "0.0.0.0"

    def routeEventSubUnsub(self, subobj):
        if isinstance(subobj, Subscription):
            self._routeEventSubscribe(subobj)
        elif isinstance(subobj, Unsubscription):
            self._routeEventUnsubscribe(subobj)
        else:
            assert (False)

    @logDecorator.log
    def _routeEventSubscribe(self, subscription):

        self.routeTableManager.enqueue(subscription)

        # synthesize a RouteEvent for a RouteTarget constraint route
        if (self.config['enable_rtc']
                and not isinstance(subscription.worker, BGPPeerWorker)):

            firstWorkerForSubscription = self._trackedSubscriptionsAddWorker(
                subscription)

            # FIXME: not excellent to hardcode this here
            if ((subscription.safi in (SAFI.mpls_vpn, SAFI.evpn))
                    and firstWorkerForSubscription):
                routeEvent = RouteEvent(
                    RouteEvent.ADVERTISE,
                    self._subscription2RTCRouteEntry(subscription), self)
                log.debug("Based on subscription => synthesized RTC %s",
                          routeEvent)
                self.routeTableManager.enqueue(routeEvent)
            else:
                log.debug(
                    "No need to synthesize an RTC route "
                    "(firstWorkerForSubscription:%s) ",
                    firstWorkerForSubscription)

    @logDecorator.log
    def _routeEventUnsubscribe(self, unsubscription):

        self.routeTableManager.enqueue(unsubscription)

        if (self.config['enable_rtc']
                and not isinstance(unsubscription.worker, BGPPeerWorker)):

            wasLastWorkerForSubscription = \
                self._trackedSubscriptionsRemoveWorker(unsubscription)

            # FIXME: not excellent to hardcode this here
            if ((unsubscription.safi in (SAFI.mpls_vpn, SAFI.evpn))
                    and wasLastWorkerForSubscription):
                # synthesize a withdraw RouteEvent for a RouteTarget constraint
                # route
                routeEvent = RouteEvent(
                    RouteEvent.WITHDRAW,
                    self._subscription2RTCRouteEntry(unsubscription), self)
                log.debug(
                    "Based on unsubscription => synthesized withdraw"
                    " for RTC %s", routeEvent)
                self.routeTableManager.enqueue(routeEvent)
            else:
                log.debug(
                    "No need to synthesize an RTC route "
                    "(wasLastWorkerForSubscription:%s) ",
                    wasLastWorkerForSubscription)

    # FIXME: this can be subject to races
    def _trackedSubscriptionsAddWorker(self, subs):
        '''returns 1 if this is the first worker subscribed'''

        result = 0
        if (subs.afi, subs.safi, subs.routeTarget) not in self.trackedSubs:
            self.trackedSubs[(subs.afi, subs.safi, subs.routeTarget)] = set()
            result = 1

        self.trackedSubs[(subs.afi, subs.safi,
                          subs.routeTarget)].add(subs.worker)

        return result

    # FIXME: this can be subject to races
    def _trackedSubscriptionsRemoveWorker(self, subs):
        '''returns 1 if this was the last worker subscribed'''

        self.trackedSubs[(subs.afi, subs.safi,
                          subs.routeTarget)].remove(subs.worker)

        if len(self.trackedSubs[(subs.afi, subs.safi, subs.routeTarget)]) == 0:
            del self.trackedSubs[(subs.afi, subs.safi, subs.routeTarget)]
            return 1
        else:
            return 0

    def _subscription2RTCRouteEntry(self, subscription):

        route = Route(
            RouteTargetConstraint(AFI(AFI.ipv4), SAFI(SAFI.rtc),
                                  self.config['my_as'],
                                  subscription.routeTarget))
        nh = Inet(
            1, socket.inet_pton(socket.AF_INET, self.config['local_address']))
        route.attributes.add(NextHop(nh))

        routeEntry = RouteEntry(AFI(AFI.ipv4), SAFI(SAFI.rtc), [], route.nlri,
                                route.attributes, self)

        return routeEntry

    # Looking Glass Functions ###################

    def getLGMap(self):
        return {
            "peers":
            (LGMap.COLLECTION, (self.getLGPeerList, self.getLGPeerPathItem)),
            "routes": (LGMap.FORWARD, self.routeTableManager),
            "workers": (LGMap.FORWARD, self.routeTableManager),
        }

    def getEstablishedPeersCount(self):
        return reduce(
            lambda count, peer: count +
            (isinstance(peer, BGPPeerWorker) and peer.isEstablished()),
            self.peers.itervalues(), 0)

    def getLGPeerList(self):
        return [{
            "id": peer.peerAddress,
            "state": peer.fsm.state
        } for peer in self.peers.itervalues()]

    def getLGPeerPathItem(self, pathItem):
        return self.peers[pathItem]
 def setUp(self):
     super(TestRouteTableManager, self).setUp()
     self.routeTableManager = RouteTableManager()
     self.routeTableManager.start()
     self.setEventTargetWorker(self.routeTableManager)
class TestRouteTableManager(TestCase, BaseTestBagPipeBGP):

    def setUp(self):
        super(TestRouteTableManager, self).setUp()
        self.routeTableManager = RouteTableManager()
        self.routeTableManager.start()
        self.setEventTargetWorker(self.routeTableManager)

    def tearDown(self):
        super(TestRouteTableManager, self).tearDown()
        self.routeTableManager.stop()
        self.routeTableManager.join()

    def _newworker(self, workerName, workerType):
        worker = mock.Mock(spec=workerType)
        worker.name = workerName
        worker.enqueue = mock.Mock()
        return worker

    def _workerSubscriptions(self, worker, rts,
                             afi=AFI(AFI.ipv4), safi=SAFI(SAFI.mpls_vpn)):
        for rt in rts:
            subscribe = Subscription(afi, safi, rt, worker)
            self.routeTableManager.enqueue(subscribe)

    def _workerUnsubscriptions(self, worker, rts,
                               afi=AFI(AFI.ipv4), safi=SAFI(SAFI.mpls_vpn)):
        for rt in rts:
            unsubscribe = Unsubscription(afi, safi, rt, worker)
            self.routeTableManager.enqueue(unsubscribe)

    def _checkSubscriptions(self, worker, matches):
        for match in matches:
            subs = self.routeTableManager.getWorkerSubscriptions(worker)
            self.assertIn(match, subs, "Subscription not found")

    def _checkUnsubscriptions(self, worker, matches):
        for match in matches:
            subs = self.routeTableManager.getWorkerSubscriptions(worker)
            self.assertNotIn(match, subs, "Subscription not found")

    def _checkCalls(self, worker, entry):
        self.assertTrue(
            entry in self.routeTableManager.getWorkerRouteEntries(worker),
            "Route entry not found")

    def _checkNoRouteEntry(self, worker, entry):
        self.assertTrue(
            entry not in self.routeTableManager.getWorkerRouteEntries(worker),
            "Route entry found")

    def _checkEventsCalls(self, events, advertisedRoutes, withdrawnNLRIs):
        '''
        checks that each advertise event in 'events' is in advertisedRoutes,
        that each withdraw event in 'events' is in withdrawnNLRIs
        and that all events in withdrawnNLRIs and advertisedRoutes are in
        'events'
        '''
        for (callArgs, _) in events:
            if (callArgs[0].type == RouteEvent.ADVERTISE):
                self.assertIn(callArgs[0].routeEntry, advertisedRoutes,
                              "Bad advertised route")
                advertisedRoutes.remove(callArgs[0].routeEntry)
            else:  # WITHDRAW
                self.assertIn(callArgs[0].routeEntry.nlri, withdrawnNLRIs,
                              "Bad withdrawn route")
                withdrawnNLRIs.remove(callArgs[0].routeEntry.nlri)
        self.assertEqual(0, len(advertisedRoutes), "some routes not advert'd")
        self.assertEqual(0, len(withdrawnNLRIs), "some routes not withdrawn")

    def testA1_SubscriptionsWithNoRouteTosynthesize(self):
        # Worker1 subscribes to RT1 and RT2
        worker1 = self._newworker("Worker-1", Worker)
        self._workerSubscriptions(worker1, [RT1, RT2])
        # Waiting for RouteTableManager thread finishes to process the
        # subscriptions
        self._wait()
        # check subscriptions
        self._checkSubscriptions(worker1, [MATCH1, MATCH2])

    def testA2_SubscriptionsWithRouteTosynthesize(self):
        # BGPPeerWorker1 advertises a route for RT1 and RT2
        bgpPeerWorker1 = self._newworker("BGPWorker1", BGPPeerWorker)
        evt1 = self._newRouteEvent(RouteEvent.ADVERTISE, NLRI1,
                                   [RT1, RT2], bgpPeerWorker1, NH1)
        # BGPPeerWorker1 advertises an other route for RT2
        evt2 = self._newRouteEvent(RouteEvent.ADVERTISE, NLRI2,
                                   [RT2], bgpPeerWorker1, NH1)
        # BGPPeerWorker1 subscribes to RT1
        self._workerSubscriptions(bgpPeerWorker1, [RT1])
        # Worker1 subscribes to RT1 and RT2
        worker1 = self._newworker("Worker-1", Worker)
        self._workerSubscriptions(worker1, [RT1, RT2])
        # Worker2 subscribes to RT1
        worker2 = self._newworker("Worker-2", Worker)
        self._workerSubscriptions(worker2, [RT1])
        # Worker3 subscribes to RT3
        worker3 = self._newworker("Worker-3", Worker)
        self._workerSubscriptions(worker3, [RT3])
        # BGPPeerWorker2 subscribes to RT1
        bgpPeerWorker2 = self._newworker("BGPWorker2", BGPPeerWorker)
        self._workerSubscriptions(bgpPeerWorker2, [RT1])
        # Waiting for RouteTableManager thread finishes to process the
        # subscription
        self._wait()
        # check route entry synthesized
        self.assertEqual(0, bgpPeerWorker1.enqueue.call_count,
                         "Route should not be synthesized to its source")
        self.assertEqual(0, worker3.enqueue.call_count,
                         "no route should be synthesized to Worker3")
        self.assertEqual(0, bgpPeerWorker2.enqueue.call_count,
                         "Route should not be synthesized between BGP workers")
        self.assertEqual(2, worker1.enqueue.call_count,
                         "2 advertise events should be synthesized to Worker1")
        self._checkEventsCalls(worker1.enqueue.call_args_list,
                               [evt1.routeEntry, evt2.routeEntry], [])
        self.assertEqual(1, worker2.enqueue.call_count,
                         "1 advertise event should be synthesized to Worker2")
        self._checkEventsCalls(
            worker2.enqueue.call_args_list, [evt1.routeEntry], [])

    def testA3_ReSubscription(self):
        # BGPPeerWorker1 advertises a route for RT1 and RT2
        bgpPeerWorker1 = self._newworker(
            "BGPWorker1", BGPPeerWorker)
        routeEvent = self._newRouteEvent(RouteEvent.ADVERTISE, NLRI1,
                                         [RT1, RT2], bgpPeerWorker1, NH1)
        # Worker1 subscribes to RT1 and RT2
        worker1 = self._newworker("Worker-1", Worker)
        self._workerSubscriptions(worker1, [RT1, RT2])
        # Worker1 subscribes again to RT1
        self._workerSubscriptions(worker1, [RT1])
        # Worker2 subscribes to RT1
        worker2 = self._newworker("Worker-2", Worker)
        self._workerSubscriptions(worker2, [RT1])
        # Worker1 subscribes again to RT2
        self._workerSubscriptions(worker2, [RT2])
        # Waiting for RouteTableManager thread finishes to process the
        # subscription
        self._wait()
        # check route entry synthesized
        self.assertEqual(1, worker1.enqueue.call_count,
                         "1 route advertised should be synthesized to Worker1")
        self._checkEventsCalls(worker1.enqueue.call_args_list,
                               [routeEvent.routeEntry], [])
        self.assertEqual(1, worker2.enqueue.call_count,
                         "1 route advertised should be synthesized to Worker2")
        self._checkEventsCalls(worker2.enqueue.call_args_list,
                               [routeEvent.routeEntry], [])

    def testB1_UnsubscriptionWithNoRouteTosynthesize(self):
        # Worker1 subscribes to RT1 and RT2
        worker1 = self._newworker("Worker-1", Worker)
        self._workerSubscriptions(worker1, [RT1, RT2])
        # BGPPeerWorker1 subscribes to RT1 and RT2
        bgpPeerWorker1 = self._newworker("BGPWorker1", BGPPeerWorker)
        self._workerSubscriptions(bgpPeerWorker1, [RT1, RT2])
        # Worker1 unsubscribes to RT1
        self._workerUnsubscriptions(worker1, [RT1])
        # BGPPeerWorker1 unsubscribes to RT1 and RT2
        self._workerUnsubscriptions(bgpPeerWorker1, [RT1, RT2])
        # Waiting for RouteTableManager thread finishes to process the
        # subscription
        self._wait()
        # check subscription/unsubscriptions
        self._checkUnsubscriptions(worker1, [MATCH1])
        self._checkSubscriptions(worker1, [MATCH2])
        self._checkUnsubscriptions(bgpPeerWorker1, [MATCH1, MATCH2])

    def testB2_UnsubscriptionWithRouteTosynthesize(self):
        # BGPPeerWorker1 advertises a route for RT1
        bgpPeerWorker1 = self._newworker(
            "BGPWorker1", BGPPeerWorker)
        evt1 = self._newRouteEvent(RouteEvent.ADVERTISE, NLRI1, [RT1, RT2],
                                   bgpPeerWorker1, NH1)
        # BGPPeerWorker1 advertises an other route for RT2
        evt2 = self._newRouteEvent(RouteEvent.ADVERTISE, NLRI2, [RT2],
                                   bgpPeerWorker1, NH1)
        # BGPPeerWorker1 subscribes to RT1
        self._workerSubscriptions(bgpPeerWorker1, [RT1])
        # Worker1 subscribes to RT1 and RT2
        worker1 = self._newworker("Worker-1", Worker)
        self._workerSubscriptions(worker1, [RT1, RT2])
        # Worker2 subscribes to RT2
        worker2 = self._newworker("Worker-2", Worker)
        self._workerSubscriptions(worker2, [RT2])
        # Worker3 subscribes to RT3
        worker3 = self._newworker("Worker-3", Worker)
        self._workerSubscriptions(worker3, [RT3])
        # BGPPeerWorker2 subscribes to RT1
        bgpPeerWorker2 = self._newworker("BGPWorker2", BGPPeerWorker)
        self._workerSubscriptions(bgpPeerWorker2, [RT1])
        # Workers and BGPPeerWorker unsubscriptions
        self._workerUnsubscriptions(bgpPeerWorker1, [RT1])
        self._workerUnsubscriptions(worker1, [RT1])
        self._workerUnsubscriptions(worker2, [RT2])
        self._workerUnsubscriptions(worker3, [RT3])
        self._workerUnsubscriptions(bgpPeerWorker2, [RT1])
        # Waiting for RouteTableManager thread finishes to process the
        # subscription
        self._wait()
        # check route entry synthesized
        self.assertEqual(0, bgpPeerWorker1.enqueue.call_count,
                         "Route should not be synthesized to its source")
        self.assertEqual(0, worker3.enqueue.call_count,
                         "no route should be synthesized to Worker3")
        self.assertEqual(0, bgpPeerWorker2.enqueue.call_count,
                         "Route should not be synthesized between "
                         "BGPPeerWorkers")
        self.assertEqual(2, worker1.enqueue.call_count,
                         "2 advertise event should be synthesized to Worker1")
        self._checkEventsCalls(worker1.enqueue.call_args_list,
                               [evt1.routeEntry, evt2.routeEntry], [])
        self.assertEqual(4, worker2.enqueue.call_count,
                         "4 events should be synthesized to Worker2: "
                         "2 advertise and 2 withdraw")
        self._checkEventsCalls(worker2.enqueue.call_args_list,
                               [evt1.routeEntry, evt2.routeEntry],
                               [evt1.routeEntry.nlri, evt2.routeEntry.nlri])

    def testB3_UnsubscriptionNotRegistered(self):
        # Worker1 subscribes to RT1
        worker1 = self._newworker("Worker-1", Worker)
        self._workerSubscriptions(worker1, [RT1])
        # Worker1 unsubscribes to RT2
        self._workerUnsubscriptions(worker1, [RT2])
        # BGPPeerWorker1 unsubscribes to RT1
        bgpPeerWorker1 = self._newworker("BGPWorker1", BGPPeerWorker)
        self._workerUnsubscriptions(bgpPeerWorker1, [RT1, RT2])
        # Waiting for RouteTableManager thread finishes to process the
        # subscription
        self._wait()
        # check subscription/unsubscriptions
        self._checkSubscriptions(worker1, [MATCH1])
        self._checkUnsubscriptions(bgpPeerWorker1, [MATCH1, MATCH2])

    def testC1_routeAdvertiseByWorkerWithoutPropagation(self):
        # Worker1 advertises a route for RT1 and RT2
        worker1 = self._newworker("Worker-1", Worker)
        routeEvent = self._newRouteEvent(RouteEvent.ADVERTISE, NLRI1,
                                         [RT1, RT2], worker1, NH1)
        # check route entry has been inserted
        self._checkCalls(worker1, routeEvent.routeEntry)

    def testC2_routeWithdrawByWorkerWithoutPropagation(self):
        # Worker1 advertises then withdraws a route
        worker1 = self._newworker("Worker-1", Worker)
        self._newRouteEvent(RouteEvent.ADVERTISE, NLRI1,
                            [RT1, RT2], worker1, NH1)
        routeEvent = self._newRouteEvent(RouteEvent.WITHDRAW, NLRI1,
                                         [RT1], worker1, NH1)
        # check route entry has been removed
        self._checkNoRouteEntry(worker1, routeEvent.routeEntry)

    def testC3_routeAdvertiseByBGPPeerWithPropagation(self):
        # Worker1 subscribes to RT1
        worker1 = self._newworker("Worker-1", Worker)
        self._workerSubscriptions(worker1, [RT1])
        # Worker2 subscribes to RT2
        worker2 = self._newworker("Worker-2", Worker)
        self._workerSubscriptions(worker2, [RT2])
        # BGPPeerWorker1 subscribes to RT1 and RT2
        bgpPeerWorker1 = self._newworker("BGPWorker1", BGPPeerWorker)
        self._workerSubscriptions(bgpPeerWorker1, [RT1, RT2])
        # BGPPeerWorker2 subscribes to RT1 and RT2
        bgpPeerWorker2 = self._newworker("BGPWorker2", BGPPeerWorker)
        self._workerSubscriptions(bgpPeerWorker2, [RT1, RT2])
        # BGPPeerWorker1 advertises a route for RT1
        routeEvent = self._newRouteEvent(RouteEvent.ADVERTISE, NLRI1,
                                         [RT1], bgpPeerWorker1, NH1)
        # check routeEvent propagation
        self.assertEqual(1, worker1.enqueue.call_count,
                         "1 route should be propagated to Worker1")
        self._checkEventsCalls(worker1.enqueue.call_args_list,
                               [routeEvent.routeEntry], [])
        self.assertEqual(0, worker2.enqueue.call_count,
                         "no route should be propagated to Worker2")
        self.assertEqual(0, bgpPeerWorker1.enqueue.call_count,
                         "Route should not be propagated to its source")
        self.assertEqual(0, bgpPeerWorker2.enqueue.call_count,
                         "Route should not be propagated between BGP workers")

    def testC4_routeWithdrawByPeerWorkerWithPropagation(self):
        # Worker1 subscribes to RT1
        worker1 = self._newworker("Worker-1", Worker)
        self._workerSubscriptions(worker1, [RT1])
        # Worker2 subscribes to RT2
        worker2 = self._newworker("Worker-2", Worker)
        self._workerSubscriptions(worker2, [RT2])
        # Worker3 subscribes to RT3
        worker3 = self._newworker("Worker-3", Worker)
        self._workerSubscriptions(worker3, [RT3])
        # BGPPeerWorker1 subscribes to RT1
        bgpPeerWorker1 = self._newworker("BGPWorker1", BGPPeerWorker)
        self._workerSubscriptions(bgpPeerWorker1, [RT1])
        # BGPPeerWorker2 subscribes to RT2
        bgpPeerWorker2 = self._newworker("BGPWorker2", BGPPeerWorker)
        self._workerSubscriptions(bgpPeerWorker2, [RT2])
        # BGPPeerWorker1 advertises a route for RT1 and RT2
        routeEventA = self._newRouteEvent(RouteEvent.ADVERTISE, NLRI1,
                                          [RT1, RT2], bgpPeerWorker1, NH1)
        # BGPPeerWorker1 withdraw previous route (without RT
        routeEventW = self._newRouteEvent(RouteEvent.WITHDRAW, NLRI1,
                                          [], bgpPeerWorker1, NH1)
        # check routeEvent propagation
        self.assertEqual(2, worker1.enqueue.call_count,
                         "2 routes should be propagated to Worker1")
        self._checkEventsCalls(worker1.enqueue.call_args_list,
                               [routeEventA.routeEntry],
                               [routeEventW.routeEntry.nlri])
        self.assertEqual(2, worker2.enqueue.call_count,
                         "2 routes should be propagated to Worker2")
        self._checkEventsCalls(worker2.enqueue.call_args_list,
                               [routeEventA.routeEntry],
                               [routeEventW.routeEntry.nlri])
        self.assertEqual(0, worker3.enqueue.call_count,
                         "No route should be propagated to Worker3")
        self.assertEqual(0, bgpPeerWorker1.enqueue.call_count,
                         "Route should not be propagated to its source")
        self.assertEqual(0, bgpPeerWorker2.enqueue.call_count,
                         "Route should not be propagated between BGP workers")

    def testC5_routeUpdateByBGPPeerWithWithdrawPropagation(self):
        # Worker1 subscribes to RT1
        worker1 = self._newworker("Worker-1", Worker)
        self._workerSubscriptions(worker1, [RT1])
        # Worker2 subscribes to RT2
        worker2 = self._newworker("Worker-2", Worker)
        self._workerSubscriptions(worker2, [RT2])
        # Worker3 subscribes to RT3
        worker3 = self._newworker("Worker-3", Worker)
        self._workerSubscriptions(worker3, [RT3])
        # BGPPeerWorker1 advertises a route for RT1, RT2 and RT3
        bgpPeerWorker1 = self._newworker(
            "BGPWorker1", BGPPeerWorker)
        evt1 = self._newRouteEvent(RouteEvent.ADVERTISE, NLRI1,
                                   [RT1, RT2, RT3], bgpPeerWorker1, NH1)
        # BGPPeerWorker1 advertises the same nlri with attributes NH and RTs
        # modification
        evt2 = self._newRouteEvent(RouteEvent.ADVERTISE, NLRI1,
                                   [RT1, RT2], bgpPeerWorker1, NH2)
        # check route event propagation
        # TO DO : check routeEvent.replacedRoute
        self.assertEqual(0, bgpPeerWorker1.enqueue.call_count,
                         "Route should not be propagated to its source")
        self.assertEqual(2, worker1.enqueue.call_count,
                         "2 routes should be advertised to Worker1")
        self._checkEventsCalls(worker1.enqueue.call_args_list,
                               [evt1.routeEntry, evt2.routeEntry], [])
        self.assertEqual(2, worker2.enqueue.call_count,
                         "2 routes should be advertised to Worker2")
        self._checkEventsCalls(worker2.enqueue.call_args_list,
                               [evt1.routeEntry, evt2.routeEntry], [])
        self.assertEqual(2, worker3.enqueue.call_count,
                         "2 routes should be advert/withdraw to Worker3")
        self._checkEventsCalls(worker3.enqueue.call_args_list,
                               [evt1.routeEntry], [evt1.routeEntry.nlri])

    def testC6_routeReadvertised(self):
        # Worker1 subscribes to RT1
        worker1 = self._newworker("Worker-1", Worker)
        self._workerSubscriptions(worker1, [RT1, RT2, RT3])
        # BGPPeerWorker1 advertises a route for RT1, RT2 and RT3
        bgpPeerWorker1 = self._newworker("BGPWorker1", BGPPeerWorker)
        evt1 = self._newRouteEvent(RouteEvent.ADVERTISE, NLRI1, [RT1, RT2],
                                   bgpPeerWorker1, NH1)
        # BGPPeerWorker1 advertises the same nlri with same attributes and RTs
        self._newRouteEvent(RouteEvent.ADVERTISE, NLRI1, [RT1, RT2],
                            bgpPeerWorker1, NH1)
        # check route event propagation
        self.assertEqual(0, bgpPeerWorker1.enqueue.call_count,
                         "Route should not be propagated to its source")
        self.assertEqual(1, worker1.enqueue.call_count,
                         "only 1 route should be advertised to Worker1")
        self._checkEventsCalls(worker1.enqueue.call_args_list,
                               [evt1.routeEntry], [])

    def testC7_routeWithdrawNotRegistered(self):
        # Worker1 subscribes to RT1
        worker1 = self._newworker("Worker-1", Worker)
        self._workerSubscriptions(worker1, [RT1, RT2])
        # BGPPeerWorker1 advertises a route for RT1 and RT2
        bgpPeerWorker1 = self._newworker(
            "BGPWorker1", BGPPeerWorker)
        evt1 = self._newRouteEvent(RouteEvent.ADVERTISE, NLRI1, [RT1, RT2],
                                   bgpPeerWorker1, NH1)
        # BGPPeerWorker1 withdraw a not registered route (without RT
        self._newRouteEvent(RouteEvent.WITHDRAW, NLRI2, [],
                            bgpPeerWorker1, NH1)
        # Waiting for RouteTableManager thread finishes to process routeEvent
        self._wait()
        # check routeEvent propagation
        self.assertEqual(1, worker1.enqueue.call_count,
                         "1 route1 should be propagated to Worker1")
        self._checkEventsCalls(worker1.enqueue.call_args_list,
                               [evt1.routeEntry], [])
        self.assertEqual(0, bgpPeerWorker1.enqueue.call_count,
                         "Route should not be propagated back to its source")

    def testD1_WorkerCleanup(self):
        # Worker1 subscribes to RT1
        worker1 = self._newworker("Worker-1", Worker)
        self._workerSubscriptions(worker1, [RT1])
        # Worker2 subscribes to RT2
        worker2 = self._newworker("Worker-2", Worker)
        self._workerSubscriptions(worker2, [RT2])
        # BGPPeerWorker1 subscribes to RT1 and RT2
        bgpPeerWorker1 = self._newworker(
            "BGPWorker1", BGPPeerWorker)
        self._workerSubscriptions(bgpPeerWorker1, [RT1, RT2])
        # BGPPeerWorker1 advertises a route for RT1 and RT2
        evt1 = self._newRouteEvent(RouteEvent.ADVERTISE, NLRI1, [RT1, RT2],
                                   bgpPeerWorker1, NH1)
        # BGPPeerWorker1 advertises an other route for RT2
        evt2 = self._newRouteEvent(RouteEvent.ADVERTISE, NLRI2, [RT2],
                                   bgpPeerWorker1, NH1)
        # Cleanup Worker1
        self.routeTableManager.enqueue(WorkerCleanupEvent(bgpPeerWorker1))
        # Waiting for RouteTableManager thread finishes to process the
        # subscriptions
        self._wait()
        # check unsubscriptions
        self._checkUnsubscriptions(bgpPeerWorker1, [MATCH1, MATCH2])
        # Check route synthesize to Worker1 and Worker2
        self.assertEqual(2, worker1.enqueue.call_count,
                         "2 routes should be advert/withdraw to Worker1")
        self._checkEventsCalls(worker1.enqueue.call_args_list,
                               [evt1.routeEntry], [evt1.routeEntry.nlri])
        self.assertEqual(4, worker2.enqueue.call_count,
                         "4 routes should be advert/withdraw to Worker2")
        self._checkEventsCalls(worker2.enqueue.call_args_list,
                               [evt1.routeEntry, evt2.routeEntry],
                               [evt1.routeEntry.nlri, evt2.routeEntry.nlri])
        # Check route entries have been removed for BGPPeerWorker1
        self._checkNoRouteEntry(bgpPeerWorker1, evt1.routeEntry)
        self._checkNoRouteEntry(bgpPeerWorker1, evt2.routeEntry)

    def testE1_DumpState(self):
        # BGPPeerWorker1 advertises a route for RT1 and RT2
        bgpPeerWorker1 = self._newworker("BGPWorker1", BGPPeerWorker)
        self._newRouteEvent(RouteEvent.ADVERTISE, NLRI1,
                            [RT1, RT2], bgpPeerWorker1, NH1)
        # BGPPeerWorker1 advertises an other route for RT2
        self._newRouteEvent(RouteEvent.ADVERTISE, NLRI2,
                            [RT2], bgpPeerWorker1, NH1)
        # BGPPeerWorker1 subscribes to RT1
        self._workerSubscriptions(bgpPeerWorker1, [RT1])
        # Worker1 subscribes to RT1 and RT2
        worker1 = self._newworker("Worker-1", Worker)
        self._workerSubscriptions(worker1, [RT1, RT2])
        # Worker2 subscribes to RT1
        worker2 = self._newworker("Worker-2", Worker)
        self._workerSubscriptions(worker2, [RT1])
        # Worker3 subscribes to RT3
        worker3 = self._newworker("Worker-3", Worker)
        self._workerSubscriptions(worker3, [RT3])

        # Waiting for RouteTableManager thread finishes to process the
        # subscriptions and route event processing
        self._wait()

        self.routeTableManager._dumpState()

    def testF1_testEmptyRT(self):
        # worker advertises a route with no RT

        w1 = self._newworker("Worker1", Worker)

        subscribe = Subscription(AFI(AFI.ipv4),
                                 SAFI(SAFI.mpls_vpn),
                                 None, w1)
        self.routeTableManager.enqueue(subscribe)

        w2 = self._newworker("Worker2", Worker)

        routeEvent = RouteEvent(RouteEvent.ADVERTISE, RouteEntry(
            AFI(AFI.ipv4), SAFI(SAFI.mpls_vpn), None, NLRI1, Attributes(), w2), w2)

        self.routeTableManager.enqueue(routeEvent)

        self._wait()

        self.assertEqual(1, w1.enqueue.call_count,
                         "1 route advertised should be synthesized to Worker1")