From ce7ac0505a780b8eddd5f15c6b2bbd03ddc1e5d3 Mon Sep 17 00:00:00 2001 From: Julien Duponchelle Date: Mon, 1 Feb 2016 17:46:05 +0100 Subject: [PATCH 01/11] 1.4.1 --- CHANGELOG | 16 ++++++++++++++++ gns3server/version.py | 4 ++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 1693fb3a..cc33c9aa 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,21 @@ # Change Log +## 1.4.1 01/02/2016 +* VMware raise error if version is not found +* For topologies before 1.4 manage qemu missing +* Fixes issue with packet capture on VMware VMs. Fixes #396. +* Fixes concurrency issue when closing multiple VMware linked clone VMs. Fixes #410. +* Fixes "can only use tap interfaces that both already exist and are up". Fixes #399. +* Send machine stats via the notification stream +* Check for /dev/kvm instead of kvm-ok +* Show a warning when starting ASA8 +* Fix error when setting Qemu VM boot to 'cd' (HDD or CD/DVD-ROM) +* Fixed the VMware default VM location on Windows, so that it doesn't assume the "Documents" folder is within the %USERPROFILE% folder, and also support Windows Server's folder (which is "My Virtual Machines" instead of "Virtual Machines"). +* Improve dynamips startup_config dump +* Dump environnement to server debug log +* Fix usage of qemu 0.10 on Windows +* Show hostname when the hostname is missing in the iourc.txt + ## 1.4.0 12/01/2016 * Release 1.4.0 diff --git a/gns3server/version.py b/gns3server/version.py index 9514c2ba..267e3460 100644 --- a/gns3server/version.py +++ b/gns3server/version.py @@ -23,5 +23,5 @@ # or negative for a release candidate or beta (after the base version # number has been incremented) -__version__ = "1.4.1dev2" -__version_info__ = (1, 4, 1, -99) +__version__ = "1.4.1" +__version_info__ = (1, 4, 1, 0) From 53d60bc71a7e51e137f56ed97a70331ae2e3261f Mon Sep 17 00:00:00 2001 From: Julien Duponchelle Date: Mon, 1 Feb 2016 17:48:10 +0100 Subject: [PATCH 02/11] 1.4.2dev1 --- gns3server/version.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gns3server/version.py b/gns3server/version.py index 267e3460..feeac6c0 100644 --- a/gns3server/version.py +++ b/gns3server/version.py @@ -23,5 +23,5 @@ # or negative for a release candidate or beta (after the base version # number has been incremented) -__version__ = "1.4.1" -__version_info__ = (1, 4, 1, 0) +__version__ = "1.4.2dev1" +__version_info__ = (1, 4, 2, -99) From 71c3bda0a5b3cd70684acb636208f4a79b55c4ff Mon Sep 17 00:00:00 2001 From: Julien Duponchelle Date: Tue, 2 Feb 2016 10:05:08 +0100 Subject: [PATCH 03/11] Fix crash if you have a { in your user name Fix #414 --- gns3server/modules/dynamips/nodes/router.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gns3server/modules/dynamips/nodes/router.py b/gns3server/modules/dynamips/nodes/router.py index 9ebbbac8..2821169c 100644 --- a/gns3server/modules/dynamips/nodes/router.py +++ b/gns3server/modules/dynamips/nodes/router.py @@ -367,7 +367,7 @@ class Router(BaseVM): files += glob.glob(os.path.join(glob.escape(project_dir), "{}_i{}_flash[0-1]".format(self.platform, self.dynamips_id))) files += glob.glob(os.path.join(glob.escape(project_dir), "{}_i{}_rom".format(self.platform, self.dynamips_id))) files += glob.glob(os.path.join(glob.escape(project_dir), "{}_i{}_bootflash".format(self.platform, self.dynamips_id))) - files += glob.glob(os.path.join(glob.escape(project_dir), "{}_i{}_ssa").format(self.platform, self.dynamips_id)) + files += glob.glob(os.path.join(glob.escape(project_dir), "{}_i{}_ssa".format(self.platform, self.dynamips_id))) for file in files: try: log.debug("Deleting file {}".format(file)) From fd22cd836124c45c6c80f0c890d668c58f1e7111 Mon Sep 17 00:00:00 2001 From: Julien Duponchelle Date: Tue, 2 Feb 2016 18:25:17 +0100 Subject: [PATCH 04/11] 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 From 537122daba365c64e4e7e5d156e1466d85777f8e Mon Sep 17 00:00:00 2001 From: Julien Duponchelle Date: Wed, 3 Feb 2016 16:38:46 +0100 Subject: [PATCH 05/11] Do not list qemu binary with -spice in the name It's create confusion and you can enable spice with the -spice options. Fix https://github.com/GNS3/gns3-gui/issues/981 --- gns3server/modules/qemu/__init__.py | 2 ++ tests/modules/qemu/test_qemu_manager.py | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/gns3server/modules/qemu/__init__.py b/gns3server/modules/qemu/__init__.py index 7d7ddb58..4ae77754 100644 --- a/gns3server/modules/qemu/__init__.py +++ b/gns3server/modules/qemu/__init__.py @@ -117,6 +117,8 @@ class Qemu(BaseManager): for path in Qemu.paths_list(): try: for f in os.listdir(path): + if f.endswith("-spice"): + continue if (f.startswith("qemu-system") or f.startswith("qemu-kvm") or f == "qemu" or f == "qemu.exe") and \ os.access(os.path.join(path, f), os.X_OK) and \ os.path.isfile(os.path.join(path, f)): diff --git a/tests/modules/qemu/test_qemu_manager.py b/tests/modules/qemu/test_qemu_manager.py index 7852c66e..a032e82e 100644 --- a/tests/modules/qemu/test_qemu_manager.py +++ b/tests/modules/qemu/test_qemu_manager.py @@ -50,7 +50,7 @@ def test_get_qemu_version(loop): def test_binary_list(loop): - files_to_create = ["qemu-system-x86", "qemu-system-x42", "qemu-kvm", "hello"] + files_to_create = ["qemu-system-x86", "qemu-system-x42", "qemu-kvm", "hello", "qemu-system-x86_64-spice"] for file_to_create in files_to_create: path = os.path.join(os.environ["PATH"], file_to_create) @@ -70,6 +70,7 @@ def test_binary_list(loop): assert {"path": os.path.join(os.environ["PATH"], "qemu-kvm"), "version": version} in qemus assert {"path": os.path.join(os.environ["PATH"], "qemu-system-x42"), "version": version} in qemus assert {"path": os.path.join(os.environ["PATH"], "hello"), "version": version} not in qemus + assert {"path": os.path.join(os.environ["PATH"], "qemu-system-x86_64-spice"), "version": version} not in qemus qemus = loop.run_until_complete(asyncio.async(Qemu.binary_list(["x86"]))) From 40261ec99c6d8ea37bd3bd3ebbd7c9cf009d75ef Mon Sep 17 00:00:00 2001 From: grossmj Date: Wed, 3 Feb 2016 16:26:15 -0700 Subject: [PATCH 06/11] Fixes VDE not working #345. --- gns3server/modules/dynamips/nios/nio_vde.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gns3server/modules/dynamips/nios/nio_vde.py b/gns3server/modules/dynamips/nios/nio_vde.py index 6ac04259..099db5a3 100644 --- a/gns3server/modules/dynamips/nios/nio_vde.py +++ b/gns3server/modules/dynamips/nios/nio_vde.py @@ -48,9 +48,9 @@ class NIOVDE(NIO): @asyncio.coroutine def create(self): - self._hypervisor.send("nio create_vde {name} {control} {local}".format(name=self._name, - control=self._control_file, - local=self._local_file)) + yield from self._hypervisor.send("nio create_vde {name} {control} {local}".format(name=self._name, + control=self._control_file, + local=self._local_file)) log.info("NIO VDE {name} created with control={control}, local={local}".format(name=self._name, control=self._control_file, From 4673424da7a7d58e57b9d656f465539f4d854afd Mon Sep 17 00:00:00 2001 From: grossmj Date: Wed, 3 Feb 2016 18:15:33 -0700 Subject: [PATCH 07/11] Check for VMware VIX library version. Fixes #413. --- gns3server/modules/vmware/__init__.py | 26 ++++++++++++++++++++++++++ gns3server/modules/vmware/vmware_vm.py | 1 + 2 files changed, 27 insertions(+) diff --git a/gns3server/modules/vmware/__init__.py b/gns3server/modules/vmware/__init__.py index b7f5cf88..40196ee1 100644 --- a/gns3server/modules/vmware/__init__.py +++ b/gns3server/modules/vmware/__init__.py @@ -31,6 +31,7 @@ import codecs from collections import OrderedDict from gns3server.utils.interfaces import interfaces from gns3server.utils.asyncio import subprocess_check_output +from pkg_resources import parse_version log = logging.getLogger(__name__) @@ -358,6 +359,31 @@ class VMware(BaseManager): return stdout_data.decode("utf-8", errors="ignore").splitlines() + @asyncio.coroutine + def check_vmrun_version(self): + + with (yield from self._execute_lock): + vmrun_path = self.vmrun_path + if not vmrun_path: + vmrun_path = self.find_vmrun() + + try: + output = yield from subprocess_check_output(vmrun_path) + match = re.search("vmrun version ([0-9\.]+)", output) + version = None + if match: + version = match.group(1) + log.debug("VMware vmrun version {} detected".format(version)) + if parse_version(version) < parse_version("1.13"): + # VMware VIX library version must be at least >= 1.13 + raise VMwareError("VMware vmrun executable version must be >= version 1.13") + if version is None: + log.warning("Could not find VMware vmrun version. Output: {}".format(output)) + raise VMwareError("Could not find VMware vmrun version. Output: {}".format(output)) + except (OSError, subprocess.SubprocessError) as e: + log.error("Error while looking for the VMware vmrun version: {}".format(e)) + raise VMwareError("Error while looking for the VMware vmrun version: {}".format(e)) + @asyncio.coroutine def remove_from_vmware_inventory(self, vmx_path): """ diff --git a/gns3server/modules/vmware/vmware_vm.py b/gns3server/modules/vmware/vmware_vm.py index 8460a08a..516c674c 100644 --- a/gns3server/modules/vmware/vmware_vm.py +++ b/gns3server/modules/vmware/vmware_vm.py @@ -142,6 +142,7 @@ class VMwareVM(BaseVM): Creates this VM and handle linked clones. """ + yield from self.manager.check_vmrun_version() if self._linked_clone and not os.path.exists(os.path.join(self.working_dir, os.path.basename(self._vmx_path))): # create the base snapshot for linked clones base_snapshot_name = "GNS3 Linked Base for clones" From 4f61443b20758b80555952bfea69aadcd24e39a5 Mon Sep 17 00:00:00 2001 From: grossmj Date: Wed, 3 Feb 2016 19:08:41 -0700 Subject: [PATCH 08/11] Always look at the registry to find vmrun.exe on Windows. --- gns3server/modules/vmware/__init__.py | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/gns3server/modules/vmware/__init__.py b/gns3server/modules/vmware/__init__.py index 40196ee1..6f0212cf 100644 --- a/gns3server/modules/vmware/__init__.py +++ b/gns3server/modules/vmware/__init__.py @@ -97,21 +97,11 @@ class VMware(BaseManager): if sys.platform.startswith("win"): vmrun_path = shutil.which("vmrun") if vmrun_path is None: - # look for vmrun.exe in default VMware Workstation directory - vmrun_ws = os.path.expandvars(r"%PROGRAMFILES(X86)%\VMware\VMware Workstation\vmrun.exe") - if os.path.exists(vmrun_ws): - vmrun_path = vmrun_ws - else: - # look for vmrun.exe using the directory listed in the registry - vmrun_path = self._find_vmrun_registry(r"SOFTWARE\Wow6432Node\VMware, Inc.\VMware Workstation") + # look for vmrun.exe using the VMware Workstation directory listed in the registry + vmrun_path = self._find_vmrun_registry(r"SOFTWARE\Wow6432Node\VMware, Inc.\VMware Workstation") if vmrun_path is None: - # look for vmrun.exe in default VMware VIX directory - vmrun_vix = os.path.expandvars(r"%PROGRAMFILES(X86)%\VMware\VMware VIX\vmrun.exe") - if os.path.exists(vmrun_vix): - vmrun_path = vmrun_vix - else: - # look for vmrun.exe using the directory listed in the registry - vmrun_path = self._find_vmrun_registry(r"SOFTWARE\Wow6432Node\VMware, Inc.\VMware VIX") + # look for vmrun.exe using the VIX directory listed in the registry + vmrun_path = self._find_vmrun_registry(r"SOFTWARE\Wow6432Node\VMware, Inc.\VMware VIX") elif sys.platform.startswith("darwin"): vmrun_path = "/Applications/VMware Fusion.app/Contents/Library/vmrun" else: From 5bee927481c680a2d72d33e886eb4e9960f4dffc Mon Sep 17 00:00:00 2001 From: Julien Duponchelle Date: Thu, 4 Feb 2016 11:46:05 +0100 Subject: [PATCH 09/11] Disallow creating project with " in the path It's not supported by dynamips. Fix https://github.com/GNS3/gns3-gui/issues/987 --- gns3server/modules/project.py | 5 ++++- tests/modules/test_project.py | 7 +++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/gns3server/modules/project.py b/gns3server/modules/project.py index 26604066..8682490b 100644 --- a/gns3server/modules/project.py +++ b/gns3server/modules/project.py @@ -141,7 +141,10 @@ class Project: if hasattr(self, "_path"): if path != self._path and self.is_local() is False: - raise aiohttp.web.HTTPForbidden(text="You are not allowed to modify the project directory location") + raise aiohttp.web.HTTPForbidden(text="You are not allowed to modify the project directory path") + + if '"' in path: + raise aiohttp.web.HTTPForbidden(text="You are not allowed to use \" in the project directory path. It's not supported by Dynamips.") self._path = path self._update_temporary_file() diff --git a/tests/modules/test_project.py b/tests/modules/test_project.py index 1ee24fe1..1a9a1b93 100644 --- a/tests/modules/test_project.py +++ b/tests/modules/test_project.py @@ -102,6 +102,13 @@ def test_changing_path_not_allowed(tmpdir): p.path = str(tmpdir) +def test_changing_path_with_quote_not_allowed(tmpdir): + with patch("gns3server.modules.project.Project.is_local", return_value=True): + with pytest.raises(aiohttp.web.HTTPForbidden): + p = Project() + p.path = str(tmpdir / "project\"53") + + def test_json(tmpdir): p = Project() assert p.__json__() == {"name": p.name, "location": p.location, "path": p.path, "project_id": p.id, "temporary": False} From 373113545fb3a24e8e179959497d1400967ca0a7 Mon Sep 17 00:00:00 2001 From: Julien Duponchelle Date: Fri, 5 Feb 2016 10:06:34 +0100 Subject: [PATCH 10/11] Code cleanup --- gns3server/modules/iou/iou_vm.py | 12 +++++----- gns3server/modules/port_manager.py | 10 ++++----- gns3server/modules/qemu/qemu_vm.py | 36 +++++++++++++++--------------- tests/handlers/api/base.py | 8 +++---- tests/modules/test_port_manager.py | 3 ++- 5 files changed, 34 insertions(+), 35 deletions(-) diff --git a/gns3server/modules/iou/iou_vm.py b/gns3server/modules/iou/iou_vm.py index 6f35281b..649103a6 100644 --- a/gns3server/modules/iou/iou_vm.py +++ b/gns3server/modules/iou/iou_vm.py @@ -403,9 +403,9 @@ class IOUVM(BaseVM): raise IOUError("Hostname \"{}\" not found in iourc file {}".format(hostname, self.iourc_path)) user_ioukey = config["license"][hostname] if user_ioukey[-1:] != ';': - raise IOUError("IOU key not ending with ; in iourc file".format(self.iourc_path)) + raise IOUError("IOU key not ending with ; in iourc file {}".format(self.iourc_path)) if len(user_ioukey) != 17: - raise IOUError("IOU key length is not 16 characters in iourc file".format(self.iourc_path)) + raise IOUError("IOU key length is not 16 characters in iourc file {}".format(self.iourc_path)) user_ioukey = user_ioukey[:16] # We can't test this because it's mean distributing a valid licence key @@ -1255,10 +1255,10 @@ class IOUVM(BaseVM): nio.startPacketCapture(output_file, data_link_type) log.info('IOU "{name}" [{id}]: starting packet capture on {adapter_number}/{port_number} to {output_file}'.format(name=self._name, - id=self._id, - adapter_number=adapter_number, - port_number=port_number, - output_file=output_file)) + id=self._id, + adapter_number=adapter_number, + port_number=port_number, + output_file=output_file)) if self.is_iouyap_running(): self._update_iouyap_config() diff --git a/gns3server/modules/port_manager.py b/gns3server/modules/port_manager.py index e6aab094..c0b168b1 100644 --- a/gns3server/modules/port_manager.py +++ b/gns3server/modules/port_manager.py @@ -142,16 +142,14 @@ class PortManager: if end_port < start_port: raise HTTPConflict(text="Invalid port range {}-{}".format(start_port, end_port)) - last_exception = None for port in range(start_port, end_port + 1): if port in ignore_ports: continue - last_exception try: - PortManager._check_port(host, port, socket_type) - return port + PortManager._check_port(host, port, socket_type) + return port except OSError as e: last_exception = e if port + 1 == end_port: @@ -163,6 +161,7 @@ class PortManager: end_port, host, last_exception)) + @staticmethod def _check_port(host, port, socket_type): """ @@ -182,7 +181,6 @@ class PortManager: s.bind(sa) # the port is available if bind is a success return True - def get_free_tcp_port(self, project, port_range_start=None, port_range_end=None): """ Get an available TCP port and reserve it @@ -291,7 +289,7 @@ class PortManager: """ if port in self._used_udp_ports: - raise HTTPConflict(text="UDP port {} already in use on host".format(port, self._console_host)) + raise HTTPConflict(text="UDP port {} already in use on host {}".format(port, self._console_host)) if port < self._udp_port_range[0] or port > self._udp_port_range[1]: raise HTTPConflict(text="UDP port {} is outside the range {}-{}".format(port, self._udp_port_range[0], self._udp_port_range[1])) self._used_udp_ports.add(port) diff --git a/gns3server/modules/qemu/qemu_vm.py b/gns3server/modules/qemu/qemu_vm.py index 2917107e..996451a5 100644 --- a/gns3server/modules/qemu/qemu_vm.py +++ b/gns3server/modules/qemu/qemu_vm.py @@ -1084,24 +1084,24 @@ class QemuVM(BaseVM): raise QemuError("Sorry, adding a link to a started Qemu VM is not supported.") # FIXME: does the code below work? very undocumented feature... # dynamically configure an UDP tunnel on the QEMU VM adapter - if nio and isinstance(nio, NIOUDP): - if self._legacy_networking: - yield from self._control_vm("host_net_remove {} gns3-{}".format(adapter_number, adapter_number)) - yield from self._control_vm("host_net_add udp vlan={},name=gns3-{},sport={},dport={},daddr={}".format(adapter_number, - adapter_number, - nio.lport, - nio.rport, - nio.rhost)) - else: - # Apparently there is a bug in Qemu... - # netdev_add [user|tap|socket|hubport|netmap],id=str[,prop=value][,...] -- add host network device - # netdev_del id -- remove host network device - yield from self._control_vm("netdev_del gns3-{}".format(adapter_number)) - yield from self._control_vm("netdev_add socket,id=gns3-{},udp={}:{},localaddr={}:{}".format(adapter_number, - nio.rhost, - nio.rport, - self._host, - nio.lport)) + # if nio and isinstance(nio, NIOUDP): + # if self._legacy_networking: + # yield from self._control_vm("host_net_remove {} gns3-{}".format(adapter_number, adapter_number)) + # yield from self._control_vm("host_net_add udp vlan={},name=gns3-{},sport={},dport={},daddr={}".format(adapter_number, + # adapter_number, + # nio.lport, + # nio.rport, + # nio.rhost)) + # else: + # # Apparently there is a bug in Qemu... + # # netdev_add [user|tap|socket|hubport|netmap],id=str[,prop=value][,...] -- add host network device + # # netdev_del id -- remove host network device + # yield from self._control_vm("netdev_del gns3-{}".format(adapter_number)) + # yield from self._control_vm("netdev_add socket,id=gns3-{},udp={}:{},localaddr={}:{}".format(adapter_number, + # nio.rhost, + # nio.rport, + # self._host, + # nio.lport)) adapter.add_nio(0, nio) log.info('QEMU VM "{name}" [{id}]: {nio} added to adapter {adapter_number}'.format(name=self._name, diff --git a/tests/handlers/api/base.py b/tests/handlers/api/base.py index 06972171..cfa9f848 100644 --- a/tests/handlers/api/base.py +++ b/tests/handlers/api/base.py @@ -61,20 +61,20 @@ class Query: body = json.dumps(body) @asyncio.coroutine - def go(future): + def go_request(future): response = yield from aiohttp.request(method, self.get_url(path, api_version), data=body) future.set_result(response) future = asyncio.Future() - asyncio.async(go(future)) + asyncio.async(go_request(future)) self._loop.run_until_complete(future) response = future.result() @asyncio.coroutine - def go(future, response): + def go_read(future, response): response = yield from response.read() future.set_result(response) future = asyncio.Future() - asyncio.async(go(future, response)) + asyncio.async(go_read(future, response)) self._loop.run_until_complete(future) response.body = future.result() x_route = response.headers.get('X-Route', None) diff --git a/tests/modules/test_port_manager.py b/tests/modules/test_port_manager.py index 791bc8a6..c41ad664 100644 --- a/tests/modules/test_port_manager.py +++ b/tests/modules/test_port_manager.py @@ -42,7 +42,7 @@ def test_reserve_tcp_port_outside_range(): assert mock_emit.call_args[0][0] == "log.warning" -def test_reserve_tcp_port_already_used(): +def test_reserve_tcp_port_already_used_by_another_program(): """ This test simulate a scenario where the port is already taken by another programm on the server @@ -65,6 +65,7 @@ def test_reserve_tcp_port_already_used(): assert port != 2001 assert mock_emit.call_args[0][0] == "log.warning" + def test_reserve_tcp_port_already_used(): """ This test simulate a scenario where the port is already taken From 3fc64bced2df2d417681786de64fa4e37ddaf981 Mon Sep 17 00:00:00 2001 From: Julien Duponchelle Date: Fri, 5 Feb 2016 10:29:27 +0100 Subject: [PATCH 11/11] Fix a crash if you create a file named IOS in the image dir Fix #419 --- gns3server/modules/dynamips/__init__.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/gns3server/modules/dynamips/__init__.py b/gns3server/modules/dynamips/__init__.py index a246c564..ac5aef26 100644 --- a/gns3server/modules/dynamips/__init__.py +++ b/gns3server/modules/dynamips/__init__.py @@ -710,10 +710,12 @@ class Dynamips(BaseManager): """ image_dir = self.get_images_directory() + if not os.path.exists(image_dir): + return [] try: files = os.listdir(image_dir) - except FileNotFoundError: - return [] + except OSError as e: + raise DynamipsError("Can not list {}: {}".format(image_dir, str(e))) files.sort() images = [] for filename in files: @@ -724,7 +726,6 @@ class Dynamips(BaseManager): # read the first 7 bytes of the file. elf_header_start = f.read(7) except OSError as e: - print(e) continue # valid IOS images must start with the ELF magic number, be 32-bit, big endian and have an ELF version of 1 if elf_header_start == b'\x7fELF\x01\x02\x01':