# 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 testtools
from unittest import mock

from networking_bagpipe.bagpipe_bgp import engine
from networking_bagpipe.bagpipe_bgp.engine import bgp_peer_worker as bpw
from networking_bagpipe.bagpipe_bgp.engine import exa
from networking_bagpipe.bagpipe_bgp.engine import route_table_manager as rtm
from networking_bagpipe.bagpipe_bgp.engine import worker
from networking_bagpipe.tests.unit.bagpipe_bgp import base as t


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


class TestRouteTableManager(testtools.TestCase, t.BaseTestBagPipeBGP):

    def setUp(self):
        super().setUp()
        self.rtm = rtm.RouteTableManager(mock.Mock(), mock.Mock())
        self.rtm.start()
        self.set_event_target_worker(self.rtm)

    def tearDown(self):
        super().tearDown()
        self.rtm.stop()
        self.rtm.join()

    def _new_worker(self, worker_name, worker_type):
        worker = mock.Mock(spec=worker_type, name=worker_name)
        worker.name = worker_name
        worker.enqueue = mock.Mock()
        worker._rtm_matches = set()
        worker._rtm_route_entries = set()
        return worker

    def _worker_subscriptions(self, worker, rts,
                              afi=exa.AFI(exa.AFI.ipv4),
                              safi=exa.SAFI(exa.SAFI.mpls_vpn)):
        for rt in rts:
            subscribe = engine.Subscription(afi, safi, rt, worker)
            self.rtm._on_event(subscribe)

    def _worker_unsubscriptions(self, worker, rts,
                                afi=exa.AFI(exa.AFI.ipv4),
                                safi=exa.SAFI(exa.SAFI.mpls_vpn)):
        for rt in rts:
            unsubscribe = engine.Unsubscription(afi, safi, rt, worker)
            self.rtm._on_event(unsubscribe)

    def _check_subscriptions(self, worker, matches):
        for match in matches:
            self.assertIn(match, worker._rtm_matches,
                          "Subscription not found")

    def _check_unsubscriptions(self, worker, matches):
        if '_rtm_matches' not in worker.__dict__:
            return
        for match in matches:
            self.assertNotIn(match, worker._rtm_matches,
                             "Subscription found while it should not: %s" %
                             worker._rtm_matches)

    def _check_events_calls(self, events, advertised_routes, withdrawn_nlris):
        # checks that each advertise event in 'events' is in advertised_routes,
        # that each withdraw event in 'events' is in withdrawn_nlris
        # and that all events in withdrawn_nlris and advertised_routes are in
        # 'events'

        for (call_args, _) in events:
            if (call_args[0].type == engine.RouteEvent.ADVERTISE):
                self.assertIn(call_args[0].route_entry, advertised_routes,
                              "Bad advertised route")
                advertised_routes.remove(call_args[0].route_entry)
            else:  # WITHDRAW
                self.assertIn(call_args[0].route_entry.nlri, withdrawn_nlris,
                              "Bad withdrawn route")
                withdrawn_nlris.remove(call_args[0].route_entry.nlri)
        self.assertEqual(0, len(advertised_routes), "some routes not advert'd")
        self.assertEqual(0, len(withdrawn_nlris), "some routes not withdrawn")

    def test_a1_subscriptions_with_no_route_to_synthesize(self):
        # Worker1 subscribes to RT1 and RT2
        worker1 = self._new_worker("worker.Worker-1", worker.Worker)
        self._worker_subscriptions(worker1, [t.RT1, t.RT2])
        # check subscriptions
        self._check_subscriptions(worker1, [MATCH1, MATCH2])

    def test_a1_check_first_last_local_worker_callback(self):
        bgp_worker1 = self._new_worker("worker.Worker-1", bpw.BGPPeerWorker)
        self._worker_subscriptions(bgp_worker1, [t.RT1])
        self._wait()
        self.assertEqual(
            0,
            self.rtm.first_local_subscriber_callback.call_count,
            "first_local_subscriber_callback should not have been called "
            " (non local worker)")

        worker1 = self._new_worker("worker.Worker-1", worker.Worker)
        self._worker_subscriptions(worker1, [t.RT1])
        self.assertEqual(
            1,
            self.rtm.first_local_subscriber_callback.call_count,
            "first_local_subscriber_callback should have been called")

        worker2 = self._new_worker("worker.Worker-2", worker.Worker)
        self._worker_subscriptions(worker2, [t.RT1])
        self.assertEqual(
            1,
            self.rtm.first_local_subscriber_callback.call_count,
            "first_local_subscriber_callback should not have been called a "
            "second time")

        self._worker_unsubscriptions(worker2, [t.RT1])
        self.assertEqual(
            0,
            self.rtm.last_local_subscriber_callback.call_count,
            "last_local_subscriber_callback should not have been called")

        self._worker_unsubscriptions(worker1, [t.RT1])
        self.assertEqual(
            1,
            self.rtm.last_local_subscriber_callback.call_count,
            "last_local_subscriber_callback should have been called")

        self._worker_unsubscriptions(bgp_worker1, [t.RT1])
        self.assertEqual(
            1,
            self.rtm.last_local_subscriber_callback.call_count,
            "last_local_subscriber_callback should not have been called "
            " (non local worker)")

    def test_a2_subscriptions_with_route_to_synthesize(self):
        # BGPPeerWorker1 advertises a route for RT1 and RT2
        bgp_peer_worker1 = self._new_worker("BGPWorker1", bpw.BGPPeerWorker)
        evt1 = self._new_route_event(engine.RouteEvent.ADVERTISE, t.NLRI1,
                                     [t.RT1, t.RT2], bgp_peer_worker1, t.NH1)
        # BGPPeerWorker1 advertises an other route for RT2
        evt2 = self._new_route_event(engine.RouteEvent.ADVERTISE, t.NLRI2,
                                     [t.RT2], bgp_peer_worker1, t.NH1)
        # BGPPeerWorker1 subscribes to RT1
        self._worker_subscriptions(bgp_peer_worker1, [t.RT1])
        # Worker1 subscribes to RT1 and RT2
        worker1 = self._new_worker("worker.Worker-1", worker.Worker)
        self._worker_subscriptions(worker1, [t.RT1, t.RT2])
        # Worker2 subscribes to RT1
        worker2 = self._new_worker("worker.Worker-2", worker.Worker)
        self._worker_subscriptions(worker2, [t.RT1])
        # Worker3 subscribes to RT3
        worker3 = self._new_worker("worker.Worker-3", worker.Worker)
        self._worker_subscriptions(worker3, [t.RT3])
        # BGPPeerWorker2 subscribes to RT1
        bgp_peer_worker2 = self._new_worker("BGPWorker2", bpw.BGPPeerWorker)
        self._worker_subscriptions(bgp_peer_worker2, [t.RT1])
        # Waiting for RouteTableManager thread finishes to process the
        # subscription
        self._wait()
        # check route entry synthesized
        self.assertEqual(0, bgp_peer_worker1.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, bgp_peer_worker2.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._check_events_calls(worker1.enqueue.call_args_list,
                                 [evt1.route_entry, evt2.route_entry], [])
        self.assertEqual(1, worker2.enqueue.call_count,
                         "1 advertise event should be synthesized to Worker2")
        self._check_events_calls(
            worker2.enqueue.call_args_list, [evt1.route_entry], [])

    def test_a3_resubscription(self):
        # BGPPeerWorker1 advertises a route for RT1 and RT2
        bgp_peer_worker1 = self._new_worker("BGPWorker1", bpw.BGPPeerWorker)
        route_event = self._new_route_event(engine.RouteEvent.ADVERTISE,
                                            t.NLRI1, [t.RT1, t.RT2],
                                            bgp_peer_worker1, t.NH1)
        # Worker1 subscribes to RT1 and RT2
        worker1 = self._new_worker("worker.Worker-1", worker.Worker)
        self._worker_subscriptions(worker1, [t.RT1, t.RT2])
        # Worker1 subscribes again to RT1
        self._worker_subscriptions(worker1, [t.RT1])
        # Worker2 subscribes to RT1
        worker2 = self._new_worker("worker.Worker-2", worker.Worker)
        self._worker_subscriptions(worker2, [t.RT1])
        # Worker1 subscribes again to RT2
        self._worker_subscriptions(worker2, [t.RT2])
        # check route entry synthesized
        self.assertEqual(1, worker1.enqueue.call_count,
                         "1 route advertised should be synthesized to Worker1")
        self._check_events_calls(worker1.enqueue.call_args_list,
                                 [route_event.route_entry], [])
        self.assertEqual(1, worker2.enqueue.call_count,
                         "1 route advertised should be synthesized to Worker2")
        self._check_events_calls(worker2.enqueue.call_args_list,
                                 [route_event.route_entry], [])

    def test_a4_two_subscriptions(self):
        # Worker1 subscribes to RT1
        worker1 = self._new_worker("worker.Worker-1", worker.Worker)
        self._worker_subscriptions(worker1, [t.RT1])

        # Worker2 subscribes to RT1
        worker2 = self._new_worker("worker.Worker-2", worker.Worker)
        self._worker_subscriptions(worker2, [t.RT1])

        # Worker2 advertises a route to RT1
        self._new_route_event(engine.RouteEvent.ADVERTISE, t.NLRI1,
                              [t.RT1], worker2, t.NH1)

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

    def test_b1_unsubscription_with_no_route_to_synthesize(self):
        # Worker1 subscribes to RT1 and RT2
        worker1 = self._new_worker("worker.Worker-1", worker.Worker)
        self._worker_subscriptions(worker1, [t.RT1, t.RT2])
        # BGPPeerWorker1 subscribes to RT1 and RT2
        bgp_peer_worker1 = self._new_worker("BGPWorker1", bpw.BGPPeerWorker)
        self._worker_subscriptions(bgp_peer_worker1, [t.RT1, t.RT2])
        # Worker1 unsubscribes to RT1
        self._worker_unsubscriptions(worker1, [t.RT1])
        # BGPPeerWorker1 unsubscribes to RT1 and RT2
        self._worker_unsubscriptions(bgp_peer_worker1, [t.RT1, t.RT2])
        # check subscription/unsubscriptions
        self._check_unsubscriptions(worker1, [MATCH1])
        self._check_subscriptions(worker1, [MATCH2])
        self._check_unsubscriptions(bgp_peer_worker1, [MATCH1, MATCH2])

    def test_b2_unsubscription_with_route_to_synthesize(self):
        # BGPPeerWorker1 advertises a route for RT1
        bgp_peer_worker1 = self._new_worker("BGPWorker1", bpw.BGPPeerWorker)
        evt1 = self._new_route_event(engine.RouteEvent.ADVERTISE,
                                     t.NLRI1, [t.RT1, t.RT2],
                                     bgp_peer_worker1, t.NH1)
        # BGPPeerWorker1 advertises an other route for RT2
        evt2 = self._new_route_event(engine.RouteEvent.ADVERTISE,
                                     t.NLRI2, [t.RT2], bgp_peer_worker1, t.NH1)
        # BGPPeerWorker1 subscribes to RT1
        self._worker_subscriptions(bgp_peer_worker1, [t.RT1])
        # Worker1 subscribes to RT1 and RT2
        worker1 = self._new_worker("worker.Worker-1", worker.Worker)
        self._worker_subscriptions(worker1, [t.RT1, t.RT2])
        # Worker2 subscribes to RT2
        worker2 = self._new_worker("worker.Worker-2", worker.Worker)
        self._worker_subscriptions(worker2, [t.RT2])
        # Worker3 subscribes to RT3
        worker3 = self._new_worker("worker.Worker-3", worker.Worker)
        self._worker_subscriptions(worker3, [t.RT3])
        # BGPPeerWorker2 subscribes to RT1
        bgp_peer_worker2 = self._new_worker("BGPWorker2", bpw.BGPPeerWorker)
        self._worker_subscriptions(bgp_peer_worker2, [t.RT1])
        # Workers and BGPPeerWorker unsubscriptions
        self._worker_unsubscriptions(bgp_peer_worker1, [t.RT1])
        self._worker_unsubscriptions(worker1, [t.RT1])
        self._worker_unsubscriptions(worker2, [t.RT2])
        self._worker_unsubscriptions(worker3, [t.RT3])
        self._worker_unsubscriptions(bgp_peer_worker2, [t.RT1])
        # Waiting for RouteTableManager thread finishes to process the
        # subscription
        self._wait()
        # check route entry synthesized
        self.assertEqual(0, bgp_peer_worker1.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, bgp_peer_worker2.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._check_events_calls(worker1.enqueue.call_args_list,
                                 [evt1.route_entry, evt2.route_entry], [])
        self.assertEqual(4, worker2.enqueue.call_count,
                         "4 events should be synthesized to Worker2: "
                         "2 advertise and 2 withdraw")
        self._check_events_calls(worker2.enqueue.call_args_list,
                                 [evt1.route_entry, evt2.route_entry],
                                 [evt1.route_entry.nlri,
                                  evt2.route_entry.nlri])

    def test_b3_unsubscription_not_registered(self):
        # Worker1 subscribes to RT1
        worker1 = self._new_worker("worker.Worker-1", worker.Worker)
        self._worker_subscriptions(worker1, [t.RT1])
        # Worker1 unsubscribes to RT2
        self._worker_unsubscriptions(worker1, [t.RT2])
        # BGPPeerWorker1 unsubscribes to RT1
        bgp_peer_worker1 = self._new_worker("BGPWorker1", bpw.BGPPeerWorker)
        self._worker_unsubscriptions(bgp_peer_worker1, [t.RT1, t.RT2])
        # check subscription/unsubscriptions
        self._check_subscriptions(worker1, [MATCH1])
        self._check_unsubscriptions(bgp_peer_worker1, [MATCH1, MATCH2])

    def test_c1_route_advertise_by_worker_without_propagation(self):
        # Worker1 advertises a route for RT1 and RT2
        worker1 = self._new_worker("worker.Worker-1", worker.Worker)
        route_event = self._new_route_event(engine.RouteEvent.ADVERTISE,
                                            t.NLRI1, [t.RT1, t.RT2],
                                            worker1, t.NH1)
        # check route entry has been inserted
        self.assertIn(route_event.route_entry, worker1._rtm_route_entries,
                      "Route entry not found")

    def test_c2_route_withdraw_by_worker_without_propagation(self):
        # Worker1 advertises then withdraws a route
        worker1 = self._new_worker("worker.Worker-1", worker.Worker)
        self._new_route_event(engine.RouteEvent.ADVERTISE, t.NLRI1,
                              [t.RT1, t.RT2], worker1, t.NH1)
        route_event = self._new_route_event(engine.RouteEvent.WITHDRAW,
                                            t.NLRI1, [t.RT1], worker1, t.NH1)
        # check route entry has been removed
        self.assertNotIn(route_event.route_entry, worker1._rtm_route_entries,
                         "Route entry found")

    def test_c3_route_advertise_by_bgp_peer_with_propagation(self):
        # Worker1 subscribes to RT1
        worker1 = self._new_worker("worker.Worker-1", worker.Worker)
        self._worker_subscriptions(worker1, [t.RT1])
        # Worker2 subscribes to RT2
        worker2 = self._new_worker("worker.Worker-2", worker.Worker)
        self._worker_subscriptions(worker2, [t.RT2])
        # BGPPeerWorker1 subscribes to RT1 and RT2
        bgp_peer_worker1 = self._new_worker("BGPWorker1", bpw.BGPPeerWorker)
        self._worker_subscriptions(bgp_peer_worker1, [t.RT1, t.RT2])
        # BGPPeerWorker2 subscribes to RT1 and RT2
        bgp_peer_worker2 = self._new_worker("BGPWorker2", bpw.BGPPeerWorker)
        self._worker_subscriptions(bgp_peer_worker2, [t.RT1, t.RT2])
        # BGPPeerWorker1 advertises a route for RT1
        route_event = self._new_route_event(engine.RouteEvent.ADVERTISE,
                                            t.NLRI1, [t.RT1], bgp_peer_worker1,
                                            t.NH1)
        # check route_event propagation
        self.assertEqual(1, worker1.enqueue.call_count,
                         "1 route should be propagated to Worker1")
        self._check_events_calls(worker1.enqueue.call_args_list,
                                 [route_event.route_entry], [])
        self.assertEqual(0, worker2.enqueue.call_count,
                         "no route should be propagated to Worker2")
        self.assertEqual(0, bgp_peer_worker1.enqueue.call_count,
                         "Route should not be propagated to its source")
        self.assertEqual(0, bgp_peer_worker2.enqueue.call_count,
                         "Route should not be propagated between BGP workers")

    def test_c4_route_withdraw_by_peer_worker_with_propagation(self):
        # Worker1 subscribes to RT1
        worker1 = self._new_worker("worker.Worker-1", worker.Worker)
        self._worker_subscriptions(worker1, [t.RT1])
        # Worker2 subscribes to RT2
        worker2 = self._new_worker("worker.Worker-2", worker.Worker)
        self._worker_subscriptions(worker2, [t.RT2])
        # Worker3 subscribes to RT3
        worker3 = self._new_worker("worker.Worker-3", worker.Worker)
        self._worker_subscriptions(worker3, [t.RT3])
        # BGPPeerWorker1 subscribes to RT1
        bgp_peer_worker1 = self._new_worker("BGPWorker1", bpw.BGPPeerWorker)
        self._worker_subscriptions(bgp_peer_worker1, [t.RT1])
        # BGPPeerWorker2 subscribes to RT2
        bgp_peer_worker2 = self._new_worker("BGPWorker2", bpw.BGPPeerWorker)
        self._worker_subscriptions(bgp_peer_worker2, [t.RT2])
        # BGPPeerWorker1 advertises a route for RT1 and RT2
        route_eventA = self._new_route_event(engine.RouteEvent.ADVERTISE,
                                             t.NLRI1, [t.RT1, t.RT2],
                                             bgp_peer_worker1, t.NH1)
        # BGPPeerWorker1 withdraw previous route (without RT
        route_eventW = self._new_route_event(engine.RouteEvent.WITHDRAW,
                                             t.NLRI1, [],
                                             bgp_peer_worker1, t.NH1)
        # check route_event propagation
        self.assertEqual(2, worker1.enqueue.call_count,
                         "2 routes should be propagated to Worker1")
        self._check_events_calls(worker1.enqueue.call_args_list,
                                 [route_eventA.route_entry],
                                 [route_eventW.route_entry.nlri])
        self.assertEqual(2, worker2.enqueue.call_count,
                         "2 routes should be propagated to Worker2")
        self._check_events_calls(worker2.enqueue.call_args_list,
                                 [route_eventA.route_entry],
                                 [route_eventW.route_entry.nlri])
        self.assertEqual(0, worker3.enqueue.call_count,
                         "No route should be propagated to Worker3")
        self.assertEqual(0, bgp_peer_worker1.enqueue.call_count,
                         "Route should not be propagated to its source")
        self.assertEqual(0, bgp_peer_worker2.enqueue.call_count,
                         "Route should not be propagated between BGP workers")

    def test_c5_route_update_by_bgp_peer_with_withdraw_propagation(self):
        # Worker1 subscribes to RT1
        worker1 = self._new_worker("worker.Worker-1", worker.Worker)
        self._worker_subscriptions(worker1, [t.RT1])
        # Worker2 subscribes to RT2
        worker2 = self._new_worker("worker.Worker-2", worker.Worker)
        self._worker_subscriptions(worker2, [t.RT2])
        # Worker3 subscribes to RT3
        worker3 = self._new_worker("worker.Worker-3", worker.Worker)
        self._worker_subscriptions(worker3, [t.RT3])
        # BGPPeerWorker1 advertises a route for RT1, RT2 and RT3
        bgp_peer_worker1 = self._new_worker("BGPWorker1", bpw.BGPPeerWorker)
        evt1 = self._new_route_event(engine.RouteEvent.ADVERTISE, t.NLRI1,
                                     [t.RT1, t.RT2, t.RT3],
                                     bgp_peer_worker1, t.NH1)
        # BGPPeerWorker1 advertises the same nlri with attributes NH and RTs
        # modification
        evt2 = self._new_route_event(engine.RouteEvent.ADVERTISE, t.NLRI1,
                                     [t.RT1, t.RT2],
                                     bgp_peer_worker1, t.NH2)
        # check route event propagation
        # TO DO : check route_event.replaced_route
        self.assertEqual(0, bgp_peer_worker1.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._check_events_calls(worker1.enqueue.call_args_list,
                                 [evt1.route_entry, evt2.route_entry], [])
        self.assertEqual(2, worker2.enqueue.call_count,
                         "2 routes should be advertised to Worker2")
        self._check_events_calls(worker2.enqueue.call_args_list,
                                 [evt1.route_entry, evt2.route_entry], [])
        self.assertEqual(2, worker3.enqueue.call_count,
                         "2 routes should be advertised/withdrawn to Worker3")
        self._check_events_calls(worker3.enqueue.call_args_list,
                                 [evt1.route_entry], [evt1.route_entry.nlri])

    def test_c6_route_readvertised(self):
        # Worker1 subscribes to RT1
        worker1 = self._new_worker("worker.Worker-1", worker.Worker)
        self._worker_subscriptions(worker1, [t.RT1, t.RT2, t.RT3])
        # BGPPeerWorker1 advertises a route for RT1, RT2 and RT3
        bgp_peer_worker1 = self._new_worker("BGPWorker1", bpw.BGPPeerWorker)
        evt1 = self._new_route_event(engine.RouteEvent.ADVERTISE, t.NLRI1,
                                     [t.RT1, t.RT2], bgp_peer_worker1, t.NH1)
        # BGPPeerWorker1 advertises the same nlri with same attributes and RTs
        self._new_route_event(engine.RouteEvent.ADVERTISE, t.NLRI1,
                              [t.RT1, t.RT2], bgp_peer_worker1, t.NH1)
        # check route event propagation
        self.assertEqual(0, bgp_peer_worker1.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._check_events_calls(worker1.enqueue.call_args_list,
                                 [evt1.route_entry], [])

    def test_c7_route_withdraw_not_registered(self):
        # Worker1 subscribes to RT1
        worker1 = self._new_worker("worker.Worker-1", worker.Worker)
        self._worker_subscriptions(worker1, [t.RT1, t.RT2])
        # BGPPeerWorker1 advertises a route for RT1 and RT2
        bgp_peer_worker1 = self._new_worker("BGPWorker1", bpw.BGPPeerWorker)
        evt1 = self._new_route_event(engine.RouteEvent.ADVERTISE, t.NLRI1,
                                     [t.RT1, t.RT2], bgp_peer_worker1, t.NH1)
        # BGPPeerWorker1 withdraw a not registered route (without RT
        self._new_route_event(engine.RouteEvent.WITHDRAW, t.NLRI2, [],
                              bgp_peer_worker1, t.NH1)
        # Waiting for RouteTableManager thread finishes to process route_event
        self._wait()
        # check route_event propagation
        self.assertEqual(1, worker1.enqueue.call_count,
                         "1 route1 should be propagated to Worker1")
        self._check_events_calls(worker1.enqueue.call_args_list,
                                 [evt1.route_entry], [])
        self.assertEqual(0, bgp_peer_worker1.enqueue.call_count,
                         "Route should not be propagated back to its source")

    def test_d1_worker_cleanup(self):
        # Worker1 subscribes to RT1
        worker1 = self._new_worker("worker.Worker-1", worker.Worker)
        self._worker_subscriptions(worker1, [t.RT1])
        # Worker2 subscribes to RT2
        worker2 = self._new_worker("worker.Worker-2", worker.Worker)
        self._worker_subscriptions(worker2, [t.RT2])
        # BGPPeerWorker1 subscribes to RT1 and RT2
        bgp_peer_worker1 = self._new_worker("BGPWorker1", bpw.BGPPeerWorker)
        self._worker_subscriptions(bgp_peer_worker1, [t.RT1, t.RT2])
        # BGPPeerWorker1 advertises a route for RT1 and RT2
        evt1 = self._new_route_event(engine.RouteEvent.ADVERTISE, t.NLRI1,
                                     [t.RT1, t.RT2], bgp_peer_worker1, t.NH1)
        # BGPPeerWorker1 advertises an other route for RT2
        evt2 = self._new_route_event(engine.RouteEvent.ADVERTISE, t.NLRI2,
                                     [t.RT2], bgp_peer_worker1, t.NH1)
        # Cleanup Worker1
        self.rtm.enqueue(engine.WorkerCleanupEvent(bgp_peer_worker1))
        # Waiting for RouteTableManager thread finishes to process the
        # subscriptions
        self._wait()

        self.assertEqual(
            0,
            self.rtm.last_local_subscriber_callback.call_count,
            "last_local_subscriber_callback should not have been called "
            " (non local worker)")

        # check unsubscriptions
        self._check_unsubscriptions(bgp_peer_worker1, [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._check_events_calls(worker1.enqueue.call_args_list,
                                 [evt1.route_entry], [evt1.route_entry.nlri])
        self.assertEqual(4, worker2.enqueue.call_count,
                         "4 routes should be advert/withdraw to Worker2")
        self._check_events_calls(worker2.enqueue.call_args_list,
                                 [evt1.route_entry, evt2.route_entry],
                                 [evt1.route_entry.nlri,
                                  evt2.route_entry.nlri])
        # Check route entries have been removed for BGPPeerWorker1
        self.assertNotIn(evt1.route_entry, bgp_peer_worker1._rtm_route_entries,
                         "Route entry found")
        self.assertNotIn(evt2.route_entry, bgp_peer_worker1._rtm_route_entries,
                         "Route entry found")

        # Cleanup Worker1
        self.rtm.enqueue(engine.WorkerCleanupEvent(worker1))

        self._wait()
        # check that last local subscriber callback for RT1 is called
        self.assertEqual(1, self.rtm.last_local_subscriber_callback.call_count)

    def test_e1_dump_state(self):
        # BGPPeerWorker1 advertises a route for RT1 and RT2
        bgp_peer_worker1 = self._new_worker("BGPWorker1", bpw.BGPPeerWorker)
        self._new_route_event(engine.RouteEvent.ADVERTISE, t.NLRI1,
                              [t.RT1, t.RT2], bgp_peer_worker1, t.NH1)
        # BGPPeerWorker1 advertises an other route for RT2
        self._new_route_event(engine.RouteEvent.ADVERTISE, t.NLRI2,
                              [t.RT2], bgp_peer_worker1, t.NH1)
        # BGPPeerWorker1 subscribes to RT1
        self._worker_subscriptions(bgp_peer_worker1, [t.RT1])
        # Worker1 subscribes to RT1 and RT2
        worker1 = self._new_worker("worker.Worker-1", worker.Worker)
        self._worker_subscriptions(worker1, [t.RT1, t.RT2])
        # Worker2 subscribes to RT1
        worker2 = self._new_worker("worker.Worker-2", worker.Worker)
        self._worker_subscriptions(worker2, [t.RT1])
        # Worker3 subscribes to RT3
        worker3 = self._new_worker("worker.Worker-3", worker.Worker)
        self._worker_subscriptions(worker3, [t.RT3])

        self.rtm._dump_state()

    def test_7_matches(self):
        m1a = rtm.Match(exa.AFI(exa.AFI.ipv4),
                        exa.SAFI(exa.SAFI.mpls_vpn),
                        exa.RouteTarget(64512, 1))
        m1b = rtm.Match(exa.AFI(exa.AFI.ipv4),
                        exa.SAFI(exa.SAFI.mpls_vpn),
                        exa.RouteTarget(64512, 1))
        m1c = rtm.Match(exa.AFI(exa.AFI.ipv4),
                        exa.SAFI(exa.SAFI.mpls_vpn),
                        exa.RouteTarget(64512, 1, False))
        m2 = rtm.Match(exa.AFI(exa.AFI.ipv4),
                       exa.SAFI(exa.SAFI.mpls_vpn),
                       exa.RouteTarget(64512, 2))
        m3 = rtm.Match(exa.AFI(exa.AFI.ipv4),
                       exa.SAFI(exa.SAFI.mpls_vpn),
                       exa.RouteTarget(64513, 1))

        self.assertEqual(hash(m1a), hash(m1b))
        self.assertEqual(hash(m1a), hash(m1c))
        self.assertNotEqual(hash(m1a), hash(m2))
        self.assertNotEqual(hash(m1a), hash(m3))

        self.assertEqual(m1a, m1b)
        self.assertEqual(m1a, m1c)
        self.assertNotEqual(m1a, m2)
        self.assertNotEqual(m1a, m3)

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

        w1 = self._new_worker("Worker1", worker.Worker)

        subscribe = engine.Subscription(exa.AFI(exa.AFI.ipv4),
                                        exa.SAFI(exa.SAFI.mpls_vpn),
                                        None, w1)
        self.rtm.enqueue(subscribe)

        w2 = self._new_worker("Worker2", worker.Worker)

        route_event = engine.RouteEvent(
            engine.RouteEvent.ADVERTISE,
            engine.RouteEntry(t.NLRI1, None, exa.Attributes()),
            w2)

        self.rtm.enqueue(route_event)

        self._wait()

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