# vim: tabstop=4 shiftwidth=4 softtabstop=4
# encoding: utf-8

# Copyright 2014 Orange
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""

.. module:: test_route_table_manager
   :synopsis: module that defines several test cases for the
              route_table_manager module.
   In particular, unit tests for RouteTableManager class.
   Setup : Start RouteTableManager thread instance.
   TearDown : Stop RouteTableManager thread instance.
   RouteTableManager is in charge to maintain the list of Workers
   (TrackerWorker, ExaBGPPeerWorker, BGPPeerWorker) subscriptions, to
   process BGB route events and to dispatch BGP routes to the Workers according
   to their subscriptions.
   Mock class is used to stub Workers.
   Tests are organized as follow :
   - testAx use cases to test worker subscriptions to route target (or match)
     1- with no route to synthesize
     2- with routes to synthesize <=> advertise existing routes to the new
        worker subscription according following rules :
        - route should not be synthesized to its source,
        - route should not be synthesized between BGPPeerWorkers
        - route should not be synthesized if already send to worker for
          another RT
     For both use cases check that (worker, match) is correctly recorded by
     RouteTableManager
     Other test cases : re-subscription to check routes are not synthesized
   - testBx use cases to test worker unsubscriptions to match (without and with
     routes to synthesize) :
     same rules should be applied to generate withdraw events.
     Check (worker, match) has been deleted
     Other test cases : unsubscription to match or by worker not registered
   - testCx to test the processing of route event generated by a worker.
     2 types of route event : advertise (new or update) or withdraw
     1- advertise or withdraw a route entry without event propagation to
        workers: check that entry is recorded/deleted
     2- advertise (new or update) or withdraw with event propagation:
        to test dispatching of route events to the workers according to
        their subscriptions.
     Other test cases : withdraw of a not registered route, advertise of the
     same route (same attr and RTs)
   - testDx : to test worker cleanup
   - testEx : to test dumpState

"""

import logging

import mock

from testtools import TestCase

from bagpipe.bgp.tests import BaseTestBagPipeBGP, RT1, RT2, RT3, \
    NLRI1, NLRI2, NH1, NH2

from bagpipe.bgp.engine import RouteEvent
from bagpipe.bgp.engine import RouteEntry
from bagpipe.bgp.engine import Subscription
from bagpipe.bgp.engine import Unsubscription
from bagpipe.bgp.engine.worker import Worker
from bagpipe.bgp.engine.bgp_peer_worker import BGPPeerWorker
from bagpipe.bgp.engine.route_table_manager import RouteTableManager
from bagpipe.bgp.engine.route_table_manager import Match
from bagpipe.bgp.engine.route_table_manager import WorkerCleanupEvent

from bagpipe.exabgp.message.update.attributes import Attributes
from bagpipe.exabgp.structure.address import AFI, SAFI

log = logging.getLogger()

MATCH1 = Match(AFI(AFI.ipv4), SAFI(SAFI.mpls_vpn), RT1)
MATCH2 = Match(AFI(AFI.ipv4), SAFI(SAFI.mpls_vpn), RT2)
MATCH3 = Match(AFI(AFI.ipv4), SAFI(SAFI.mpls_vpn), RT3)


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")
