From fd22cd836124c45c6c80f0c890d668c58f1e7111 Mon Sep 17 00:00:00 2001 From: Julien Duponchelle Date: Tue, 2 Feb 2016 18:25:17 +0100 Subject: [PATCH] Send command line used to start the VM to client Add a command_line attribute to the VM object with the command line used to start the VM. Now /start return the object in order to get this new attribute. And the HTTP status code is 200 instead of 204 because 204 disallow body. Support: * Qemu * Dynamips * IOU Ref https://github.com/GNS3/gns3-gui/issues/513 --- gns3server/handlers/api/iou_handler.py | 5 +++-- gns3server/handlers/api/qemu_handler.py | 7 ++++--- gns3server/handlers/api/vpcs_handler.py | 5 +++-- gns3server/modules/base_vm.py | 12 ++++++++++++ gns3server/modules/iou/iou_vm.py | 11 ++++++----- gns3server/modules/qemu/qemu_vm.py | 8 ++++---- gns3server/modules/vpcs/vpcs_vm.py | 11 ++++++----- gns3server/schemas/iou.py | 7 ++++++- gns3server/schemas/qemu.py | 7 ++++++- gns3server/schemas/vpcs.py | 6 +++++- tests/handlers/api/test_iou.py | 5 +++-- tests/handlers/api/test_qemu.py | 3 ++- tests/handlers/api/test_vpcs.py | 3 ++- tests/modules/iou/test_iou_vm.py | 4 ++-- tests/modules/qemu/test_qemu_vm.py | 3 ++- tests/modules/vpcs/test_vpcs_vm.py | 1 + 16 files changed, 67 insertions(+), 31 deletions(-) diff --git a/gns3server/handlers/api/iou_handler.py b/gns3server/handlers/api/iou_handler.py index 5606cd8b..495008e4 100644 --- a/gns3server/handlers/api/iou_handler.py +++ b/gns3server/handlers/api/iou_handler.py @@ -148,11 +148,12 @@ class IOUHandler: "vm_id": "UUID for the instance" }, status_codes={ - 204: "Instance started", + 200: "Instance started", 400: "Invalid request", 404: "Instance doesn't exist" }, input=IOU_START_SCHEMA, + output=IOU_OBJECT_SCHEMA, description="Start a IOU instance") def start(request, response): @@ -166,7 +167,7 @@ class IOUHandler: print(vm.iourc_path) yield from vm.start() - response.set_status(204) + response.json(vm) @classmethod @Route.post( diff --git a/gns3server/handlers/api/qemu_handler.py b/gns3server/handlers/api/qemu_handler.py index a02f8693..c64d1904 100644 --- a/gns3server/handlers/api/qemu_handler.py +++ b/gns3server/handlers/api/qemu_handler.py @@ -146,11 +146,12 @@ class QEMUHandler: "vm_id": "UUID for the instance" }, status_codes={ - 204: "Instance started", + 200: "Instance started", 400: "Invalid request", 404: "Instance doesn't exist" }, - description="Start a Qemu VM instance") + description="Start a Qemu VM instance", + output=QEMU_OBJECT_SCHEMA) def start(request, response): qemu_manager = Qemu.instance() @@ -161,7 +162,7 @@ class QEMUHandler: if pm.check_hardware_virtualization(vm) is False: raise HTTPConflict(text="Cannot start VM with KVM enabled because hardware virtualization (VT-x/AMD-V) is already used by another software like VMware or VirtualBox") yield from vm.start() - response.set_status(204) + response.json(vm) @classmethod @Route.post( diff --git a/gns3server/handlers/api/vpcs_handler.py b/gns3server/handlers/api/vpcs_handler.py index ad22ac2b..59dd1dcd 100644 --- a/gns3server/handlers/api/vpcs_handler.py +++ b/gns3server/handlers/api/vpcs_handler.py @@ -130,13 +130,14 @@ class VPCSHandler: 400: "Invalid request", 404: "Instance doesn't exist" }, - description="Start a VPCS instance") + description="Start a VPCS instance", + output=VPCS_OBJECT_SCHEMA) def start(request, response): vpcs_manager = VPCS.instance() vm = vpcs_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"]) yield from vm.start() - response.set_status(204) + response.json(vm) @classmethod @Route.post( diff --git a/gns3server/modules/base_vm.py b/gns3server/modules/base_vm.py index c62e9bb8..5e01dbc7 100644 --- a/gns3server/modules/base_vm.py +++ b/gns3server/modules/base_vm.py @@ -58,6 +58,7 @@ class BaseVM: self._hw_virtualization = False self._ubridge_hypervisor = None self._vm_status = "stopped" + self._command_line = "" if self._console is not None: if console_type == "vnc": @@ -94,6 +95,17 @@ class BaseVM: self._vm_status = status self._project.emit("vm.{}".format(status), self) + @property + def command_line(self): + """Return command used to start the VM""" + + return self._command_line + + @command_line.setter + def command_line(self, command_line): + + self._command_line = command_line + @property def project(self): """ diff --git a/gns3server/modules/iou/iou_vm.py b/gns3server/modules/iou/iou_vm.py index 0e4786ac..6f35281b 100644 --- a/gns3server/modules/iou/iou_vm.py +++ b/gns3server/modules/iou/iou_vm.py @@ -72,7 +72,6 @@ class IOUVM(BaseVM): super().__init__(name, vm_id, project, manager, console=console) - self._command = [] self._iouyap_process = None self._iou_process = None self._iou_stdout_file = "" @@ -220,7 +219,8 @@ class IOUVM(BaseVM): "startup_config": self.relative_startup_config_file, "private_config": self.relative_private_config_file, "iourc_path": self.iourc_path, - "use_default_iou_values": self._use_default_iou_values} + "use_default_iou_values": self._use_default_iou_values, + "command_line": self.command_line} # return the relative path if the IOU image is in the images_path directory iou_vm_info["path"] = self.manager.get_relative_image_path(self.path) @@ -502,13 +502,14 @@ class IOUVM(BaseVM): if "IOURC" not in os.environ: env["IOURC"] = iourc_path - self._command = yield from self._build_command() + command = yield from self._build_command() try: - log.info("Starting IOU: {}".format(self._command)) + log.info("Starting IOU: {}".format(command)) self._iou_stdout_file = os.path.join(self.working_dir, "iou.log") log.info("Logging to {}".format(self._iou_stdout_file)) with open(self._iou_stdout_file, "w", encoding="utf-8") as fd: - self._iou_process = yield from asyncio.create_subprocess_exec(*self._command, + self.command_line = ' '.join(command) + self._iou_process = yield from asyncio.create_subprocess_exec(*command, stdout=fd, stderr=subprocess.STDOUT, cwd=self.working_dir, diff --git a/gns3server/modules/qemu/qemu_vm.py b/gns3server/modules/qemu/qemu_vm.py index 6a1995a1..2917107e 100644 --- a/gns3server/modules/qemu/qemu_vm.py +++ b/gns3server/modules/qemu/qemu_vm.py @@ -68,7 +68,6 @@ class QemuVM(BaseVM): self._host = server_config.get("host", "127.0.0.1") self._monitor_host = server_config.get("monitor_host", "127.0.0.1") self._linked_clone = linked_clone - self._command = [] self._process = None self._cpulimit_process = None self._monitor = None @@ -867,15 +866,16 @@ class QemuVM(BaseVM): # check if there is enough RAM to run self.check_available_ram(self.ram) - self._command = yield from self._build_command() - command_string = " ".join(shlex.quote(s) for s in self._command) + command = yield from self._build_command() + command_string = " ".join(shlex.quote(s) for s in command) try: log.info("Starting QEMU with: {}".format(command_string)) self._stdout_file = os.path.join(self.working_dir, "qemu.log") log.info("logging to {}".format(self._stdout_file)) with open(self._stdout_file, "w", encoding="utf-8") as fd: fd.write("Start QEMU with {}\n\nExecution log:\n".format(command_string)) - self._process = yield from asyncio.create_subprocess_exec(*self._command, + self.command_line = ' '.join(command) + self._process = yield from asyncio.create_subprocess_exec(*command, stdout=fd, stderr=subprocess.STDOUT, cwd=self.working_dir) diff --git a/gns3server/modules/vpcs/vpcs_vm.py b/gns3server/modules/vpcs/vpcs_vm.py index 3e57659d..d4fa36c4 100644 --- a/gns3server/modules/vpcs/vpcs_vm.py +++ b/gns3server/modules/vpcs/vpcs_vm.py @@ -59,7 +59,6 @@ class VPCSVM(BaseVM): def __init__(self, name, vm_id, project, manager, console=None, startup_script=None): super().__init__(name, vm_id, project, manager, console=console) - self._command = [] self._process = None self._vpcs_stdout_file = "" self._vpcs_version = None @@ -115,7 +114,8 @@ class VPCSVM(BaseVM): "console": self._console, "project_id": self.project.id, "startup_script": self.startup_script, - "startup_script_path": self.relative_startup_script} + "startup_script_path": self.relative_startup_script, + "command_line": self.command_line} @property def relative_startup_script(self): @@ -223,16 +223,17 @@ class VPCSVM(BaseVM): if not self._ethernet_adapter.get_nio(0): raise VPCSError("This VPCS instance must be connected in order to start") - self._command = self._build_command() + command = self._build_command() try: - log.info("Starting VPCS: {}".format(self._command)) + log.info("Starting VPCS: {}".format(command)) self._vpcs_stdout_file = os.path.join(self.working_dir, "vpcs.log") log.info("Logging to {}".format(self._vpcs_stdout_file)) flags = 0 if sys.platform.startswith("win32"): flags = subprocess.CREATE_NEW_PROCESS_GROUP with open(self._vpcs_stdout_file, "w", encoding="utf-8") as fd: - self._process = yield from asyncio.create_subprocess_exec(*self._command, + self.command_line = ' '.join(command) + self._process = yield from asyncio.create_subprocess_exec(*command, stdout=fd, stderr=subprocess.STDOUT, cwd=self.working_dir, diff --git a/gns3server/schemas/iou.py b/gns3server/schemas/iou.py index cd6a9f4a..b5c64f92 100644 --- a/gns3server/schemas/iou.py +++ b/gns3server/schemas/iou.py @@ -254,11 +254,16 @@ IOU_OBJECT_SCHEMA = { "iourc_path": { "description": "Path of the iourc file used by remote servers", "type": ["string", "null"] + }, + "command_line": { + "description": "Last command line used by GNS3 to start QEMU", + "type": "string" } }, "additionalProperties": False, "required": ["name", "vm_id", "console", "project_id", "path", "md5sum", "serial_adapters", "ethernet_adapters", - "ram", "nvram", "l1_keepalives", "startup_config", "private_config", "use_default_iou_values"] + "ram", "nvram", "l1_keepalives", "startup_config", "private_config", "use_default_iou_values", + "command_line"] } IOU_CAPTURE_SCHEMA = { diff --git a/gns3server/schemas/qemu.py b/gns3server/schemas/qemu.py index a50f9271..5adac051 100644 --- a/gns3server/schemas/qemu.py +++ b/gns3server/schemas/qemu.py @@ -558,6 +558,10 @@ QEMU_OBJECT_SCHEMA = { "description": "Additional QEMU options", "type": "string", }, + "command_line": { + "description": "Last command line used by GNS3 to start QEMU", + "type": "string" + } }, "additionalProperties": False, "required": ["vm_id", @@ -598,7 +602,8 @@ QEMU_OBJECT_SCHEMA = { "cpu_throttling", "process_priority", "options", - "vm_directory"] + "vm_directory", + "command_line"] } QEMU_BINARY_FILTER_SCHEMA = { diff --git a/gns3server/schemas/vpcs.py b/gns3server/schemas/vpcs.py index e53dc079..bdaae578 100644 --- a/gns3server/schemas/vpcs.py +++ b/gns3server/schemas/vpcs.py @@ -121,7 +121,11 @@ VPCS_OBJECT_SCHEMA = { "description": "Path of the VPCS startup script relative to project directory", "type": ["string", "null"] }, + "command_line": { + "description": "Last command line used by GNS3 to start QEMU", + "type": "string" + } }, "additionalProperties": False, - "required": ["name", "vm_id", "status", "console", "project_id", "startup_script_path"] + "required": ["name", "vm_id", "status", "console", "project_id", "startup_script_path", "command_line"] } diff --git a/tests/handlers/api/test_iou.py b/tests/handlers/api/test_iou.py index 9b8f137c..60c39d88 100644 --- a/tests/handlers/api/test_iou.py +++ b/tests/handlers/api/test_iou.py @@ -139,7 +139,8 @@ def test_iou_start(server, vm): with asyncio_patch("gns3server.modules.iou.iou_vm.IOUVM.start", return_value=True) as mock: response = server.post("/projects/{project_id}/iou/vms/{vm_id}/start".format(project_id=vm["project_id"], vm_id=vm["vm_id"])) assert mock.called - assert response.status == 204 + assert response.status == 200 + assert response.json["name"] == "PC TEST 1" def test_iou_start_with_iourc(server, vm, tmpdir): @@ -148,7 +149,7 @@ def test_iou_start_with_iourc(server, vm, tmpdir): with asyncio_patch("gns3server.modules.iou.iou_vm.IOUVM.start", return_value=True) as mock: response = server.post("/projects/{project_id}/iou/vms/{vm_id}/start".format(project_id=vm["project_id"], vm_id=vm["vm_id"]), body=body, example=True) assert mock.called - assert response.status == 204 + assert response.status == 200 response = server.get("/projects/{project_id}/iou/vms/{vm_id}".format(project_id=vm["project_id"], vm_id=vm["vm_id"])) assert response.status == 200 diff --git a/tests/handlers/api/test_qemu.py b/tests/handlers/api/test_qemu.py index f2f07553..07073e60 100644 --- a/tests/handlers/api/test_qemu.py +++ b/tests/handlers/api/test_qemu.py @@ -114,7 +114,8 @@ def test_qemu_start(server, vm): with asyncio_patch("gns3server.modules.qemu.qemu_vm.QemuVM.start", return_value=True) as mock: response = server.post("/projects/{project_id}/qemu/vms/{vm_id}/start".format(project_id=vm["project_id"], vm_id=vm["vm_id"]), example=True) assert mock.called - assert response.status == 204 + assert response.status == 200 + assert response.json["name"] == "PC TEST 1" def test_qemu_stop(server, vm): diff --git a/tests/handlers/api/test_vpcs.py b/tests/handlers/api/test_vpcs.py index 22117d61..2e5cb928 100644 --- a/tests/handlers/api/test_vpcs.py +++ b/tests/handlers/api/test_vpcs.py @@ -102,7 +102,8 @@ def test_vpcs_start(server, vm): with asyncio_patch("gns3server.modules.vpcs.vpcs_vm.VPCSVM.start", return_value=True) as mock: response = server.post("/projects/{project_id}/vpcs/vms/{vm_id}/start".format(project_id=vm["project_id"], vm_id=vm["vm_id"]), example=True) assert mock.called - assert response.status == 204 + assert response.status == 200 + assert response.json["name"] == "PC TEST 1" def test_vpcs_stop(server, vm): diff --git a/tests/modules/iou/test_iou_vm.py b/tests/modules/iou/test_iou_vm.py index 6a4503ea..4eb63019 100644 --- a/tests/modules/iou/test_iou_vm.py +++ b/tests/modules/iou/test_iou_vm.py @@ -112,11 +112,11 @@ def test_start(loop, vm, monkeypatch): with asyncio_patch("gns3server.modules.iou.iou_vm.IOUVM._check_iou_licence", return_value=True): with asyncio_patch("gns3server.modules.iou.iou_vm.IOUVM._start_ioucon", return_value=True): with asyncio_patch("gns3server.modules.iou.iou_vm.IOUVM._start_iouyap", return_value=True): - with asyncio_patch("asyncio.create_subprocess_exec", return_value=mock_process): + with asyncio_patch("asyncio.create_subprocess_exec", return_value=mock_process) as mock_exec: mock_process.returncode = None loop.run_until_complete(asyncio.async(vm.start())) assert vm.is_running() - + assert vm.command_line == ' '.join(mock_exec.call_args[0]) def test_start_with_iourc(loop, vm, monkeypatch, tmpdir): diff --git a/tests/modules/qemu/test_qemu_vm.py b/tests/modules/qemu/test_qemu_vm.py index 1a36a62b..209f270c 100644 --- a/tests/modules/qemu/test_qemu_vm.py +++ b/tests/modules/qemu/test_qemu_vm.py @@ -112,9 +112,10 @@ def test_is_running(vm, running_subprocess_mock): def test_start(loop, vm, running_subprocess_mock): - with asyncio_patch("asyncio.create_subprocess_exec", return_value=running_subprocess_mock): + with asyncio_patch("asyncio.create_subprocess_exec", return_value=running_subprocess_mock) as mock: loop.run_until_complete(asyncio.async(vm.start())) assert vm.is_running() + assert vm.command_line == ' '.join(mock.call_args[0]) def test_stop(loop, vm, running_subprocess_mock): diff --git a/tests/modules/vpcs/test_vpcs_vm.py b/tests/modules/vpcs/test_vpcs_vm.py index 3d7d729c..63305897 100644 --- a/tests/modules/vpcs/test_vpcs_vm.py +++ b/tests/modules/vpcs/test_vpcs_vm.py @@ -107,6 +107,7 @@ def test_start(loop, vm): '-t', '127.0.0.1') assert vm.is_running() + assert vm.command_line == ' '.join(mock_exec.call_args[0]) (action, event) = queue.get_nowait() assert action == "vm.started" assert event == vm