def _run(self):
        loop = asyncio.new_event_loop()
        asyncio.set_event_loop(loop)  # Related to the current thread only.
        self._loop = loop

        async def enter_stack(stack):
            manager = await stack.enter_async_context(
                AgileMeshNetworkManager(**self._amn_kwargs)
            )
            return manager

        stack = AsyncExitStack()
        try:
            self._manager = loop.run_until_complete(enter_stack(stack))
            self._thread_started_event.set()
            self._start_initialization_event.wait()
            self._manager.start_initialization()
            loop.run_forever()
        except Exception as e:
            logger.error("Uncaught exception in the AMN event loop", exc_info=True)
            # TODO shutdown the app? It's harmful to keep it alive at this point.
            # Obviously, everything is horribly broken: there's no manager
            # controlling the app.
        finally:
            logger.info("Shutting down AMN event loop")
            loop.run_until_complete(stack.aclose())
            loop.run_until_complete(loop.shutdown_asyncgens())
            pending = asyncio.Task.all_tasks()
            loop.run_until_complete(asyncio.gather(*pending, return_exceptions=True))
            loop.close()
Esempio n. 2
0
class ManagerTestCase(unittest.TestCase):
    maxDiff = None  # unittest: show full diff on assertion failure

    def setUp(self):
        self.loop = asyncio.new_event_loop()
        asyncio.set_event_loop(self.loop)

        self.server = MockupDB(auto_ismaster={"maxWireVersion": 6})
        self.server.run()
        self.server.autoresponds(
            Command("find", "switch_collection",
                    namespace="topology_database"),
            {
                "cursor": {
                    "id":
                    0,
                    "firstBatch": [{
                        **d, "_id": i
                    } for i, d in enumerate(TOPOLOGY_DATABASE_DATA)],
                }
            },
        )

        self._stack = AsyncExitStack()

        td = self._stack.enter_context(tempfile.TemporaryDirectory())
        self.rpc_unix_sock = os.path.join(td, "l.sock")

        self._stack.enter_context(
            patch.object(settings, "REMOTE_DATABASE_MONGO_URI",
                         self.server.uri))
        self._stack.enter_context(
            patch.object(settings, "NEGOTIATOR_RPC_UNIX_SOCK_PATH",
                         self.rpc_unix_sock))
        self._stack.enter_context(
            patch("agile_mesh_network.ryu.amn_manager.OVSManager",
                  DummyOVSManager))
        self._stack.enter_context(
            # To avoid automatic connection to a relay.
            patch.object(settings, "IS_RELAY", True))

        self._stack.enter_context(
            patch.object(events_scheduler, "RyuAppEventLoopScheduler"))
        self.ryu_ev_loop_scheduler = events_scheduler.RyuAppEventLoopScheduler(
        )
        self._stack.enter_context(self.ryu_ev_loop_scheduler)

        async def command_cb(session, msg):
            assert isinstance(msg, RPCCommand)
            await self._rpc_command_cb(msg)

        self.rpc_server = self.loop.run_until_complete(
            self._stack.enter_async_context(
                RPCUnixServer(self.rpc_unix_sock, command_cb)))

    async def _rpc_command_cb(self, msg: RPCCommand):
        self.assertEqual(msg.name, "dump_tunnels_state")
        await msg.respond({"tunnels": []})

    def tearDown(self):
        self.loop.run_until_complete(self._stack.aclose())

        self.loop.run_until_complete(self.loop.shutdown_asyncgens())
        self.loop.close()

        self.server.stop()

    def test_topology_database_sync(self):
        async def f():
            async with AgileMeshNetworkManager(
                    ryu_ev_loop_scheduler=self.ryu_ev_loop_scheduler
            ) as manager:
                manager.start_initialization()

                topology_database = manager.topology_database
                local_database = topology_database.local
                await local_database.is_filled_event.wait()
                self.assertTrue(local_database.is_filled)

                self.assertListEqual(
                    topology_database.find_random_relay_switches(),
                    [SwitchEntity.from_dict(SWITCH_ENTITY_RELAY_DATA)],
                )

                with self.assertRaises(KeyError):
                    topology_database.find_switch_by_mac(UNK_MAC)

                self.assertEqual(
                    topology_database.find_switch_by_mac(
                        SWITCH_ENTITY_BOARD_DATA["mac"]),
                    SwitchEntity.from_dict(SWITCH_ENTITY_BOARD_DATA),
                )

                self.assertListEqual(
                    topology_database.find_switches_by_mac_list([]), [])
                self.assertListEqual(
                    topology_database.find_switches_by_mac_list([UNK_MAC]), [])
                self.assertListEqual(
                    topology_database.find_switches_by_mac_list(
                        [UNK_MAC, SWITCH_ENTITY_BOARD_DATA["mac"]]),
                    [SwitchEntity.from_dict(SWITCH_ENTITY_BOARD_DATA)],
                )

                # TODO after resync extra tunnels/flows are destroyed

        self.loop.run_until_complete(asyncio.wait_for(f(), timeout=3))

    def test_rpc(self):
        async def f():
            rpc_responses = iter([
                ("dump_tunnels_state", {
                    "tunnels": [TUNNEL_MODEL_BOARD_DATA]
                }),
                (
                    "create_tunnel",
                    {
                        "tunnel":
                        TUNNEL_MODEL_RELAY_DATA,
                        "tunnels": [
                            TUNNEL_MODEL_BOARD_DATA,
                            TUNNEL_MODEL_RELAY_DATA,
                        ],
                    },
                ),
                (
                    "create_tunnel",
                    {
                        "tunnel": TUNNEL_MODEL_BOARD_DATA,
                        "tunnels": [TUNNEL_MODEL_BOARD_DATA],
                    },
                ),
            ])

            async def _rpc_command_cb(msg: RPCCommand):
                name, resp = next(rpc_responses)
                self.assertEqual(msg.name, name)
                await msg.respond(resp)

            with ExitStack() as stack:
                stack.enter_context(
                    patch.object(self, "_rpc_command_cb", _rpc_command_cb))
                stack.enter_context(patch.object(settings, "IS_RELAY", False))

                async with AgileMeshNetworkManager(
                        ryu_ev_loop_scheduler=self.ryu_ev_loop_scheduler
                ) as manager:
                    manager.start_initialization()
                    await manager._initialization_task

                    self.assertDictEqual({}, manager._tunnel_creation_tasks)

                    # Don't attempt to connect to unknown macs.
                    manager.ask_for_tunnel(UNK_MAC)
                    self.assertDictEqual({}, manager._tunnel_creation_tasks)

                    # Connect to a switch, ensure that the task is cleaned up.
                    manager.ask_for_tunnel(SECOND_MAC)
                    await next(iter(manager._tunnel_creation_tasks.values()))
                    self.assertDictEqual({}, manager._tunnel_creation_tasks)

                    # Send a broadcast
                    await next(iter(self.rpc_server.sessions)).issue_broadcast(
                        "tunnel_created",
                        {
                            "tunnel": TUNNEL_MODEL_RELAY_DATA,
                            "tunnels": [TUNNEL_MODEL_RELAY_DATA],
                        },
                    )
                    await asyncio.sleep(0.001)

                    # TODO unknown tunnels after resync are dropped via RPC

            expected_event_calls = [
                # Initialization list:
                [TunnelModel.from_dict(TUNNEL_MODEL_BOARD_DATA)],
                # Initialization relay tunnel:
                [
                    TunnelModel.from_dict(TUNNEL_MODEL_BOARD_DATA),
                    TunnelModel.from_dict(TUNNEL_MODEL_RELAY_DATA),
                ],
                # ask_for_tunnel:
                [TunnelModel.from_dict(TUNNEL_MODEL_BOARD_DATA)],
                # Broadcast:
                [TunnelModel.from_dict(TUNNEL_MODEL_RELAY_DATA)],
            ]
            for (args, kwargs), ev_expected in zip_equal(
                    self.ryu_ev_loop_scheduler.send_event_to_observers.
                    call_args_list,
                    expected_event_calls,
            ):
                ev = args[0]
                self.assertListEqual(
                    sorted(t for t, _ in ev.mac_to_tunswitch.values()),
                    sorted(ev_expected),
                )

        self.loop.run_until_complete(asyncio.wait_for(f(), timeout=3))

    def test_flows(self):
        async def f():
            async with AgileMeshNetworkManager(
                    ryu_ev_loop_scheduler=self.ryu_ev_loop_scheduler
            ) as manager:
                manager.start_initialization()
                # TODO missing flows from RPC sync are added
                # TODO after packet in a tunnel creation request is sent
                # TODO after tunnel creation a flow is set up
                pass

        self.loop.run_until_complete(asyncio.wait_for(f(), timeout=3))