diff --git a/gns3server/controller/notification.py b/gns3server/controller/notification.py index 7190195d..53790b8b 100644 --- a/gns3server/controller/notification.py +++ b/gns3server/controller/notification.py @@ -110,9 +110,9 @@ class Notification: self.project_emit("node.updated", node.__json__()) except (aiohttp.web.HTTPNotFound, aiohttp.web.HTTPForbidden): # Project closing return - # elif action == "ping": - # event["compute_id"] = compute_id - # self.project_emit(action, event) + elif action == "ping": + event["compute_id"] = compute_id + self.project_emit(action, event) else: self.project_emit(action, event, project_id) diff --git a/gns3server/handlers/api/controller/project_handler.py b/gns3server/handlers/api/controller/project_handler.py index 69d45ed0..8a1ad4d9 100644 --- a/gns3server/handlers/api/controller/project_handler.py +++ b/gns3server/handlers/api/controller/project_handler.py @@ -220,31 +220,28 @@ class ProjectHandler: async def notification(request, response): controller = Controller.instance() - project_id = request.match_info["project_id"] + project = controller.get_project(request.match_info["project_id"]) response.content_type = "application/json" response.set_status(200) response.enable_chunked_encoding() await response.prepare(request) - log.info("New client has connected to the notification stream for project ID '{}' (HTTP long-polling method)".format(project_id)) + log.info("New client has connected to the notification stream for project ID '{}' (HTTP long-polling method)".format(project.id)) try: - with controller.notification.project_queue(project_id) as queue: + with controller.notification.project_queue(project.id) as queue: while True: msg = await queue.get_json(5) await response.write(("{}\n".format(msg)).encode("utf-8")) finally: - log.info("Client has disconnected from notification for project ID '{}' (HTTP long-polling method)".format(project_id)) - try: - project = controller.get_project(project_id) - if project.auto_close: - # To avoid trouble with client connecting disconnecting we sleep few seconds before checking - # if someone else is not connected - await asyncio.sleep(5) - if not controller.notification.project_has_listeners(project.id): - log.info("Project '{}' is automatically closing due to no client listening".format(project.id)) - await project.close() - except aiohttp.web.HTTPNotFound: - pass + log.info("Client has disconnected from notification for project ID '{}' (HTTP long-polling method)".format(project.id)) + if project.auto_close: + # To avoid trouble with client connecting disconnecting we sleep few seconds before checking + # if someone else is not connected + await asyncio.sleep(5) + if not controller.notification.project_has_listeners(project.id): + log.info("Project '{}' is automatically closing due to no client listening".format(project.id)) + await project.close() + @Route.get( r"/projects/{project_id}/notifications/ws", @@ -259,36 +256,32 @@ class ProjectHandler: async def notification_ws(request, response): controller = Controller.instance() - project_id = request.match_info["project_id"] + project = controller.get_project(request.match_info["project_id"]) ws = aiohttp.web.WebSocketResponse() await ws.prepare(request) request.app['websockets'].add(ws) asyncio.ensure_future(process_websocket(ws)) - log.info("New client has connected to the notification stream for project ID '{}' (WebSocket method)".format(project_id)) + log.info("New client has connected to the notification stream for project ID '{}' (WebSocket method)".format(project.id)) try: - with controller.notification.project_queue(project_id) as queue: + with controller.notification.project_queue(project.id) as queue: while True: notification = await queue.get_json(5) if ws.closed: break await ws.send_str(notification) finally: - log.info("Client has disconnected from notification stream for project ID '{}' (WebSocket method)".format(project_id)) + log.info("Client has disconnected from notification stream for project ID '{}' (WebSocket method)".format(project.id)) if not ws.closed: await ws.close() request.app['websockets'].discard(ws) - try: - project = controller.get_project(project_id) - if project.auto_close: - # To avoid trouble with client connecting disconnecting we sleep few seconds before checking - # if someone else is not connected - await asyncio.sleep(5) - if not controller.notification.project_has_listeners(project_id): - log.info("Project '{}' is automatically closing due to no client listening".format(project.id)) - await project.close() - except aiohttp.web.HTTPNotFound: - pass + if project.auto_close: + # To avoid trouble with client connecting disconnecting we sleep few seconds before checking + # if someone else is not connected + await asyncio.sleep(5) + if not controller.notification.project_has_listeners(project.id): + log.info("Project '{}' is automatically closing due to no client listening".format(project.id)) + await project.close() return ws diff --git a/tests/controller/test_link.py b/tests/controller/test_link.py index 7fc4d5da..c64c4815 100644 --- a/tests/controller/test_link.py +++ b/tests/controller/test_link.py @@ -68,7 +68,7 @@ def test_add_node(async_run, project, compute): link = Link(project) link.create = AsyncioMagicMock() - link._project.controller.notification.project_emit = MagicMock() + link._project.emit_notification = MagicMock() project.dump = AsyncioMagicMock() async_run(link.add_node(node1, 0, 4)) assert link._nodes == [ @@ -87,7 +87,7 @@ def test_add_node(async_run, project, compute): } ] assert project.dump.called - assert not link._project.controller.notification.project_emit.called + assert not link._project.emit_notification.called assert not link.create.called @@ -97,7 +97,7 @@ def test_add_node(async_run, project, compute): async_run(link.add_node(node2, 0, 4)) assert link.create.called - link._project.controller.notification.project_emit.assert_called_with("link.created", link.__json__()) + link._project.emit_notification.assert_called_with("link.created", link.__json__()) assert link in node2.links @@ -112,7 +112,7 @@ def test_add_node_already_connected(async_run, project, compute): link = Link(project) link.create = AsyncioMagicMock() - link._project.controller.notification.project_emit = MagicMock() + link._project.emit_notification = MagicMock() async_run(link.add_node(node1, 0, 4)) node2 = Node(project, compute, "node2", node_type="qemu") node2._ports = [EthernetPort("E0", 0, 0, 4)] @@ -133,7 +133,7 @@ def test_add_node_cloud(async_run, project, compute): link = Link(project) link.create = AsyncioMagicMock() - link._project.controller.notification.project_emit = MagicMock() + link._project.emit_notification = MagicMock() async_run(link.add_node(node1, 0, 4)) async_run(link.add_node(node2, 0, 4)) @@ -150,7 +150,7 @@ def test_add_node_cloud_to_cloud(async_run, project, compute): link = Link(project) link.create = AsyncioMagicMock() - link._project.controller.notification.project_emit = MagicMock() + link._project.emit_notification = MagicMock() async_run(link.add_node(node1, 0, 4)) with pytest.raises(aiohttp.web.HTTPConflict): @@ -166,7 +166,7 @@ def test_add_node_same_node(async_run, project, compute): link = Link(project) link.create = AsyncioMagicMock() - link._project.controller.notification.project_emit = MagicMock() + link._project.emit_notification = MagicMock() async_run(link.add_node(node1, 0, 4)) with pytest.raises(aiohttp.web.HTTPConflict): @@ -184,7 +184,7 @@ def test_add_node_serial_to_ethernet(async_run, project, compute): link = Link(project) link.create = AsyncioMagicMock() - link._project.controller.notification.project_emit = MagicMock() + link._project.emit_notification = MagicMock() async_run(link.add_node(node1, 0, 4)) with pytest.raises(aiohttp.web.HTTPConflict): @@ -295,25 +295,25 @@ def test_default_capture_file_name(project, compute, async_run): assert link.default_capture_file_name() == "Hello_0-4_to_w0rld_1-3.pcap" -def test_start_capture(link, async_run, tmpdir, project, controller): +def test_start_capture(link, async_run, tmpdir): async def fake_reader(): return AsyncioBytesIO() link.read_pcap_from_source = fake_reader - controller._notification = MagicMock() + link._project.emit_notification = MagicMock() async_run(link.start_capture(capture_file_name="test.pcap")) assert link._capturing assert link._capture_file_name == "test.pcap" - controller._notification.project_emit.assert_called_with("link.updated", link.__json__()) + link._project.emit_notification.assert_called_with("link.updated", link.__json__()) -def test_stop_capture(link, async_run, tmpdir, project, controller): +def test_stop_capture(link, async_run, tmpdir): link._capturing = True - controller._notification = MagicMock() + link._project.emit_notification = MagicMock() async_run(link.stop_capture()) assert link._capturing is False - controller._notification.project_emit.assert_called_with("link.updated", link.__json__()) + link._project.emit_notification.assert_called_with("link.updated", link.__json__()) def test_delete(async_run, project, compute): @@ -322,7 +322,7 @@ def test_delete(async_run, project, compute): link = Link(project) link.create = AsyncioMagicMock() - link._project.controller.notification.project_emit = MagicMock() + link._project.emit_notification = MagicMock() project.dump = AsyncioMagicMock() async_run(link.add_node(node1, 0, 4)) @@ -342,7 +342,7 @@ def test_update_filters(async_run, project, compute): link = Link(project) link.create = AsyncioMagicMock() - link._project.controller.notification.project_emit = MagicMock() + link._project.emit_notification = MagicMock() project.dump = AsyncioMagicMock() async_run(link.add_node(node1, 0, 4)) diff --git a/tests/controller/test_node.py b/tests/controller/test_node.py index 25bd1ed2..7ed82881 100644 --- a/tests/controller/test_node.py +++ b/tests/controller/test_node.py @@ -353,23 +353,23 @@ def test_update_properties(node, compute, project, async_run, controller): #controller._notification.emit.assert_called_with("node.updated", node_notif) -def test_update_only_controller(node, controller, compute, project, async_run): +def test_update_only_controller(node, controller, compute, async_run): """ When updating property used only on controller we don't need to call the compute """ compute.put = AsyncioMagicMock() - controller._notification = AsyncioMagicMock() + node._project.emit_notification = AsyncioMagicMock() async_run(node.update(x=42)) assert not compute.put.called assert node.x == 42 - controller._notification.project_emit.assert_called_with("node.updated", node.__json__()) + node._project.emit_notification.assert_called_with("node.updated", node.__json__()) # If nothing change a second notif should not be send - controller._notification = AsyncioMagicMock() + node._project.emit_notification = AsyncioMagicMock() async_run(node.update(x=42)) - assert not controller._notification.project_emit.called + assert not node._project.emit_notification.called def test_update_no_changes(node, compute, project, async_run): diff --git a/tests/controller/test_notification.py b/tests/controller/test_notification.py index be7d7016..1b2a2386 100644 --- a/tests/controller/test_notification.py +++ b/tests/controller/test_notification.py @@ -45,7 +45,7 @@ def test_emit_to_all(async_run, controller, project): Send an event to all if we don't have a project id in the event """ notif = controller.notification - with notif.project_queue(project) as queue: + with notif.project_queue(project.id) as queue: assert len(notif._project_listeners[project.id]) == 1 async_run(queue.get(0.1)) # ping notif.project_emit('test', {}) @@ -60,7 +60,7 @@ def test_emit_to_project(async_run, controller, project): Send an event to a project listeners """ notif = controller.notification - with notif.project_queue(project) as queue: + with notif.project_queue(project.id) as queue: assert len(notif._project_listeners[project.id]) == 1 async_run(queue.get(0.1)) # ping # This event has not listener @@ -74,20 +74,20 @@ def test_emit_to_project(async_run, controller, project): def test_dispatch(async_run, controller, project): notif = controller.notification - with notif.project_queue(project) as queue: + with notif.project_queue(project.id) as queue: assert len(notif._project_listeners[project.id]) == 1 async_run(queue.get(0.1)) # ping - async_run(notif.dispatch("test", {}, compute_id=1)) + async_run(notif.dispatch("test", {}, project_id=project.id, compute_id=1)) msg = async_run(queue.get(5)) assert msg == ('test', {}, {}) def test_dispatch_ping(async_run, controller, project): notif = controller.notification - with notif.project_queue(project) as queue: + with notif.project_queue(project.id) as queue: assert len(notif._project_listeners[project.id]) == 1 async_run(queue.get(0.1)) # ping - async_run(notif.dispatch("ping", {}, compute_id=12)) + async_run(notif.dispatch("ping", {}, project_id=project.id, compute_id=12)) msg = async_run(queue.get(5)) assert msg == ('ping', {'compute_id': 12}, {}) @@ -99,7 +99,7 @@ def test_dispatch_node_updated(async_run, controller, node, project): """ notif = controller.notification - with notif.project_queue(project) as queue: + with notif.project_queue(project.id) as queue: assert len(notif._project_listeners[project.id]) == 1 async_run(queue.get(0.1)) # ping async_run(notif.dispatch("node.updated", { @@ -108,6 +108,7 @@ def test_dispatch_node_updated(async_run, controller, node, project): "name": "hello", "startup_config": "ip 192" }, + project_id=project.id, compute_id=1)) assert node.name == "hello" action, event, _ = async_run(queue.get(5)) diff --git a/tests/controller/test_project.py b/tests/controller/test_project.py index a94a0204..9fdd2936 100644 --- a/tests/controller/test_project.py +++ b/tests/controller/test_project.py @@ -86,11 +86,11 @@ def test_json(tmpdir): def test_update(controller, async_run): project = Project(controller=controller, name="Hello") - controller._notification = MagicMock() + project.emit_notification = MagicMock() assert project.name == "Hello" async_run(project.update(name="World")) assert project.name == "World" - controller.notification.project_emit.assert_any_call("project.updated", project.__json__()) + project.emit_notification.assert_any_call("project.updated", project.__json__()) def test_update_on_compute(controller, async_run): @@ -99,7 +99,7 @@ def test_update_on_compute(controller, async_run): compute.id = "local" project = Project(controller=controller, name="Test") project._project_created_on_compute = [compute] - controller._notification = MagicMock() + project.emit_notification = MagicMock() async_run(project.update(variables=variables)) @@ -154,7 +154,7 @@ def test_add_node_local(async_run, controller): compute = MagicMock() compute.id = "local" project = Project(controller=controller, name="Test") - controller._notification = MagicMock() + project.emit_notification = MagicMock() response = MagicMock() response.json = {"console": 2048} @@ -174,7 +174,7 @@ def test_add_node_local(async_run, controller): 'name': 'test'}, timeout=1200) assert compute in project._project_created_on_compute - controller.notification.project_emit.assert_any_call("node.created", node.__json__()) + project.emit_notification.assert_any_call("node.created", node.__json__()) def test_add_node_non_local(async_run, controller): @@ -184,7 +184,7 @@ def test_add_node_non_local(async_run, controller): compute = MagicMock() compute.id = "remote" project = Project(controller=controller, name="Test") - controller._notification = MagicMock() + project.emit_notification = MagicMock() response = MagicMock() response.json = {"console": 2048} @@ -202,7 +202,7 @@ def test_add_node_non_local(async_run, controller): 'name': 'test'}, timeout=1200) assert compute in project._project_created_on_compute - controller.notification.project_emit.assert_any_call("node.created", node.__json__()) + project.emit_notification.assert_any_call("node.created", node.__json__()) def test_add_node_from_template(async_run, controller): @@ -212,7 +212,7 @@ def test_add_node_from_template(async_run, controller): compute = MagicMock() compute.id = "local" project = Project(controller=controller, name="Test") - controller._notification = MagicMock() + project.emit_notification = MagicMock() template = Template(str(uuid.uuid4()), { "compute_id": "local", "name": "Test", @@ -234,7 +234,7 @@ def test_add_node_from_template(async_run, controller): }) assert compute in project._project_created_on_compute - controller.notification.project_emit.assert_any_call("node.created", node.__json__()) + project.emit_notification.assert_any_call("node.created", node.__json__()) def test_delete_node(async_run, controller): @@ -243,7 +243,7 @@ def test_delete_node(async_run, controller): """ compute = MagicMock() project = Project(controller=controller, name="Test") - controller._notification = MagicMock() + project.emit_notification = MagicMock() response = MagicMock() response.json = {"console": 2048} @@ -255,7 +255,7 @@ def test_delete_node(async_run, controller): assert node.id not in project._nodes compute.delete.assert_any_call('/projects/{}/vpcs/nodes/{}'.format(project.id, node.id)) - controller.notification.project_emit.assert_any_call("node.deleted", node.__json__()) + project.emit_notification.assert_any_call("node.deleted", node.__json__()) def test_delete_node_delete_link(async_run, controller): @@ -264,7 +264,7 @@ def test_delete_node_delete_link(async_run, controller): """ compute = MagicMock() project = Project(controller=controller, name="Test") - controller._notification = MagicMock() + project.emit_notification = MagicMock() response = MagicMock() response.json = {"console": 2048} @@ -280,8 +280,8 @@ def test_delete_node_delete_link(async_run, controller): assert link.id not in project._links compute.delete.assert_any_call('/projects/{}/vpcs/nodes/{}'.format(project.id, node.id)) - controller.notification.project_emit.assert_any_call("node.deleted", node.__json__()) - controller.notification.project_emit.assert_any_call("link.deleted", link.__json__()) + project.emit_notification.assert_any_call("node.deleted", node.__json__()) + project.emit_notification.assert_any_call("link.deleted", link.__json__()) def test_get_node(async_run, controller): @@ -331,14 +331,14 @@ def test_add_link(async_run, project, controller): vm1._ports = [EthernetPort("E0", 0, 3, 1)] vm2 = async_run(project.add_node(compute, "test2", None, node_type="vpcs", properties={"startup_config": "test.cfg"})) vm2._ports = [EthernetPort("E0", 0, 4, 2)] - controller._notification = MagicMock() + project.emit_notification = MagicMock() link = async_run(project.add_link()) async_run(link.add_node(vm1, 3, 1)) with asyncio_patch("gns3server.controller.udp_link.UDPLink.create") as mock_udp_create: async_run(link.add_node(vm2, 4, 2)) assert mock_udp_create.called assert len(link._nodes) == 2 - controller.notification.project_emit.assert_any_call("link.created", link.__json__()) + project.emit_notification.assert_any_call("link.created", link.__json__()) def test_list_links(async_run, project): @@ -379,18 +379,18 @@ def test_delete_link(async_run, project, controller): assert len(project._links) == 0 link = async_run(project.add_link()) assert len(project._links) == 1 - controller._notification = MagicMock() + project.emit_notification = MagicMock() async_run(project.delete_link(link.id)) - controller.notification.project_emit.assert_any_call("link.deleted", link.__json__()) + project.emit_notification.assert_any_call("link.deleted", link.__json__()) assert len(project._links) == 0 def test_add_drawing(async_run, project, controller): - controller.notification.project_emit = MagicMock() + project.emit_notification = MagicMock() drawing = async_run(project.add_drawing(None, svg="")) assert len(project._drawings) == 1 - controller.notification.project_emit.assert_any_call("drawing.created", drawing.__json__()) + project.emit_notification.assert_any_call("drawing.created", drawing.__json__()) def test_get_drawing(async_run, project): @@ -413,9 +413,9 @@ def test_delete_drawing(async_run, project, controller): assert len(project._drawings) == 0 drawing = async_run(project.add_drawing()) assert len(project._drawings) == 1 - controller._notification = MagicMock() + project.emit_notification = MagicMock() async_run(project.delete_drawing(drawing.id)) - controller.notification.project_emit.assert_any_call("drawing.deleted", drawing.__json__()) + project.emit_notification.assert_any_call("drawing.deleted", drawing.__json__()) assert len(project._drawings) == 0 @@ -478,10 +478,10 @@ def test_open_close(async_run, controller): async_run(project.open()) assert not project.start_all.called assert project.status == "opened" - controller._notification = MagicMock() + project.emit_notification = MagicMock() async_run(project.close()) assert project.status == "closed" - controller.notification.project_emit.assert_any_call("project.closed", project.__json__()) + project.emit_notification.assert_any_call("project.closed", project.__json__()) def test_open_auto_start(async_run, controller):