From cecf2f501424f0c52e623fb909d980b6f5121361 Mon Sep 17 00:00:00 2001 From: Julien Duponchelle Date: Sat, 21 Feb 2015 00:15:56 +0100 Subject: [PATCH] Async qemu monitor reading --- gns3server/handlers/qemu_handler.py | 22 ++++++++++++++++++++- gns3server/modules/qemu/qemu_vm.py | 29 ++++++++++++++-------------- gns3server/server.py | 2 ++ tests/api/test_qemu.py | 7 +++++++ tests/modules/qemu/test_qemu_vm.py | 30 +++++++++++++++++++++++++++++ 5 files changed, 74 insertions(+), 16 deletions(-) diff --git a/gns3server/handlers/qemu_handler.py b/gns3server/handlers/qemu_handler.py index 4f8cb16c..c170f7f6 100644 --- a/gns3server/handlers/qemu_handler.py +++ b/gns3server/handlers/qemu_handler.py @@ -205,7 +205,7 @@ class QEMUHandler: 400: "Invalid request", 404: "Instance doesn't exist" }, - description="Reload a Qemu.instance") + description="Suspend a Qemu.instance") def suspend(request, response): qemu_manager = Qemu.instance() @@ -213,6 +213,26 @@ class QEMUHandler: yield from vm.suspend() response.set_status(204) + @classmethod + @Route.post( + r"/projects/{project_id}/qemu/vms/{vm_id}/resume", + parameters={ + "project_id": "UUID for the project", + "vm_id": "UUID for the instance", + }, + status_codes={ + 204: "Instance resumed", + 400: "Invalid request", + 404: "Instance doesn't exist" + }, + description="Resume a Qemu.instance") + def resume(request, response): + + qemu_manager = Qemu.instance() + vm = qemu_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"]) + yield from vm.resume() + response.set_status(204) + @Route.post( r"/projects/{project_id}/qemu/vms/{vm_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/nio", parameters={ diff --git a/gns3server/modules/qemu/qemu_vm.py b/gns3server/modules/qemu/qemu_vm.py index 2464b725..6b491c76 100644 --- a/gns3server/modules/qemu/qemu_vm.py +++ b/gns3server/modules/qemu/qemu_vm.py @@ -612,13 +612,12 @@ class QemuVM(BaseVM): self._stop_cpulimit() @asyncio.coroutine - def _control_vm(self, command, expected=None, timeout=30): + def _control_vm(self, command, expected=None): """ Executes a command with QEMU monitor when this VM is running. :param command: QEMU monitor command (e.g. info status, stop etc.) :params expected: An array with the string attended (Default None) - :param timeout: how long to wait for QEMU monitor :returns: result of the command (Match object or None) """ @@ -627,25 +626,29 @@ class QemuVM(BaseVM): if self.is_running() and self._monitor: log.debug("Execute QEMU monitor command: {}".format(command)) try: - tn = telnetlib.Telnet(self._monitor_host, self._monitor, timeout=timeout) + reader, writer = yield from asyncio.open_connection("127.0.0.1", self._monitor) except OSError as e: log.warn("Could not connect to QEMU monitor: {}".format(e)) return result try: - tn.write(command.encode('ascii') + b"\n") - time.sleep(0.1) + writer.write(command.encode('ascii') + b"\n") except OSError as e: log.warn("Could not write to QEMU monitor: {}".format(e)) - tn.close() + writer.close() return result if expected: try: - ind, match, dat = tn.expect(list=expected, timeout=timeout) - if match: - result = match + while result is None: + line = yield from reader.readline() + if not line: + break + for expect in expected: + if expect in line: + result = line + break except EOFError as e: log.warn("Could not read from QEMU monitor: {}".format(e)) - tn.close() + writer.close() return result @asyncio.coroutine @@ -667,11 +670,7 @@ class QemuVM(BaseVM): :returns: status (string) """ - result = None - - match = yield from self._control_vm("info status", [b"running", b"paused"]) - if match: - result = match.group(0).decode('ascii') + result = yield from self._control_vm("info status", [b"running", b"paused"]) return result @asyncio.coroutine diff --git a/gns3server/server.py b/gns3server/server.py index 1d7214f0..81731d43 100644 --- a/gns3server/server.py +++ b/gns3server/server.py @@ -142,6 +142,8 @@ class Server: @asyncio.coroutine def start_shell(self): from ptpython.repl import embed + from gns3server.modules import Qemu + yield from embed(globals(), locals(), return_asyncio_coroutine=True, patch_stdout=True) def run(self): diff --git a/tests/api/test_qemu.py b/tests/api/test_qemu.py index 92501bc4..f0fe64d0 100644 --- a/tests/api/test_qemu.py +++ b/tests/api/test_qemu.py @@ -103,6 +103,13 @@ def test_qemu_suspend(server, vm): assert response.status == 204 +def test_qemu_resume(server, vm): + with asyncio_patch("gns3server.modules.qemu.qemu_vm.QemuVM.resume", return_value=True) as mock: + response = server.post("/projects/{project_id}/qemu/vms/{vm_id}/resume".format(project_id=vm["project_id"], vm_id=vm["vm_id"])) + assert mock.called + assert response.status == 204 + + def test_qemu_delete(server, vm): with asyncio_patch("gns3server.modules.qemu.Qemu.delete_vm", return_value=True) as mock: response = server.delete("/projects/{project_id}/qemu/vms/{vm_id}".format(project_id=vm["project_id"], vm_id=vm["vm_id"])) diff --git a/tests/modules/qemu/test_qemu_vm.py b/tests/modules/qemu/test_qemu_vm.py index 1a59acca..a174f7fd 100644 --- a/tests/modules/qemu/test_qemu_vm.py +++ b/tests/modules/qemu/test_qemu_vm.py @@ -203,3 +203,33 @@ def test_json(vm, project): json = vm.__json__() assert json["name"] == vm.name assert json["project_id"] == project.id + + +def test_control_vm(vm, loop): + + vm._process = MagicMock() + vm._monitor = 4242 + reader = MagicMock() + writer = MagicMock() + with asyncio_patch("asyncio.open_connection", return_value=(reader, writer)) as open_connect: + res = loop.run_until_complete(asyncio.async(vm._control_vm("test"))) + assert writer.write.called_with("test") + assert res is None + + +def test_control_vm_expect_text(vm, loop): + + vm._process = MagicMock() + vm._monitor = 4242 + reader = MagicMock() + writer = MagicMock() + with asyncio_patch("asyncio.open_connection", return_value=(reader, writer)) as open_connect: + + future = asyncio.Future() + future.set_result("epic product") + reader.readline.return_value = future + + res = loop.run_until_complete(asyncio.async(vm._control_vm("test", ["epic"]))) + assert writer.write.called_with("test") + + assert res == "epic product"