From 09aa31fb438c441501b2bfba86c2cc87678da909 Mon Sep 17 00:00:00 2001 From: grossmj Date: Wed, 16 Sep 2015 06:09:14 -0600 Subject: [PATCH 1/7] Do not automatically delete Dynamips bootflash file because they are necessary to restore VLANs on the c3600 platform. --- gns3server/modules/dynamips/__init__.py | 2 -- gns3server/modules/dynamips/nodes/router.py | 2 ++ 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gns3server/modules/dynamips/__init__.py b/gns3server/modules/dynamips/__init__.py index c8b3d953..bc888b23 100644 --- a/gns3server/modules/dynamips/__init__.py +++ b/gns3server/modules/dynamips/__init__.py @@ -172,9 +172,7 @@ class Dynamips(BaseManager): files += glob.glob(os.path.join(project_dir, "*_lock")) files += glob.glob(os.path.join(project_dir, "ilt_*")) files += glob.glob(os.path.join(project_dir, "c[0-9][0-9][0-9][0-9]_i[0-9]*_rommon_vars")) - files += glob.glob(os.path.join(project_dir, "c[0-9][0-9][0-9][0-9]_i[0-9]*_ssa")) files += glob.glob(os.path.join(project_dir, "c[0-9][0-9][0-9][0-9]_i[0-9]*_log.txt")) - files += glob.glob(os.path.join(project_dir, "c[0-9][0-9][0-9][0-9]_i[0-9]*_bootflash")) for file in files: try: log.debug("Deleting file {}".format(file)) diff --git a/gns3server/modules/dynamips/nodes/router.py b/gns3server/modules/dynamips/nodes/router.py index cf6dec6d..bf25b6a6 100644 --- a/gns3server/modules/dynamips/nodes/router.py +++ b/gns3server/modules/dynamips/nodes/router.py @@ -352,6 +352,8 @@ class Router(BaseVM): files += glob.glob(os.path.join(project_dir, "{}_i{}_nvram".format(self.platform, self.dynamips_id))) files += glob.glob(os.path.join(project_dir, "{}_i{}_flash[0-1]".format(self.platform, self.dynamips_id))) files += glob.glob(os.path.join(project_dir, "{}_i{}_rom".format(self.platform, self.dynamips_id))) + files += glob.glob(os.path.join(project_dir, "{}_i{}_bootflash".format(self.platform, self.dynamips_id))) + files += glob.glob(os.path.join(project_dir, "{}_i{}_ssa").format(self.platform, self.dynamips_id)) for file in files: try: log.debug("Deleting file {}".format(file)) From e63e3280a1b2d05817e9104784c976ffc086857b Mon Sep 17 00:00:00 2001 From: grossmj Date: Tue, 29 Sep 2015 06:56:01 -0600 Subject: [PATCH 2/7] Prevent launching a packet capture with a non-ASCII path when using Dynamips. --- gns3server/handlers/api/dynamips_vm_handler.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/gns3server/handlers/api/dynamips_vm_handler.py b/gns3server/handlers/api/dynamips_vm_handler.py index cfb919ba..8bb32bf5 100644 --- a/gns3server/handlers/api/dynamips_vm_handler.py +++ b/gns3server/handlers/api/dynamips_vm_handler.py @@ -16,6 +16,7 @@ # along with this program. If not, see . import os +import sys import base64 from ...web.route import Route @@ -317,6 +318,14 @@ class DynamipsVMHandler: slot_number = int(request.match_info["adapter_number"]) port_number = int(request.match_info["port_number"]) pcap_file_path = os.path.join(vm.project.capture_working_directory(), request.json["capture_file_name"]) + + if sys.platform.startswith('win'): + #FIXME: Dynamips (Cygwin actually) doesn't like non ascii paths on Windows + try: + pcap_file_path.encode('ascii') + except UnicodeEncodeError: + raise DynamipsError('The capture file path "{}" must only contain ASCII (English) characters'.format(pcap_file_path)) + yield from vm.start_capture(slot_number, port_number, pcap_file_path, request.json["data_link_type"]) response.json({"pcap_file_path": pcap_file_path}) From 80d99ec395d8213b88ce6155b4c84a35da783813 Mon Sep 17 00:00:00 2001 From: grossmj Date: Tue, 29 Sep 2015 14:15:01 -0600 Subject: [PATCH 3/7] Fixes some minor issues. --- gns3server/modules/base_manager.py | 5 +++-- gns3server/modules/dynamips/__init__.py | 5 ++++- gns3server/modules/virtualbox/virtualbox_vm.py | 2 +- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/gns3server/modules/base_manager.py b/gns3server/modules/base_manager.py index 056ad8fc..ad12a784 100644 --- a/gns3server/modules/base_manager.py +++ b/gns3server/modules/base_manager.py @@ -237,7 +237,7 @@ class BaseManager: @asyncio.coroutine def close_vm(self, vm_id): """ - Delete a VM + Close a VM :param vm_id: VM identifier @@ -305,7 +305,8 @@ class BaseManager: vm = yield from self.close_vm(vm_id) vm.project.mark_vm_for_destruction(vm) - del self._vms[vm.id] + if vm.id in self._vms: + del self._vms[vm.id] return vm @staticmethod diff --git a/gns3server/modules/dynamips/__init__.py b/gns3server/modules/dynamips/__init__.py index bc888b23..d2cc958e 100644 --- a/gns3server/modules/dynamips/__init__.py +++ b/gns3server/modules/dynamips/__init__.py @@ -357,7 +357,10 @@ class Dynamips(BaseManager): ghost_ios_support = self.config.get_section_config("Dynamips").getboolean("ghost_ios_support", True) if ghost_ios_support: with (yield from Dynamips._ghost_ios_lock): - yield from self._set_ghost_ios(vm) + try: + yield from self._set_ghost_ios(vm) + except GeneratorExit: + log.warning("Could not create ghost IOS image {} (GeneratorExit)".format(vm.name)) @asyncio.coroutine def create_nio(self, node, nio_settings): diff --git a/gns3server/modules/virtualbox/virtualbox_vm.py b/gns3server/modules/virtualbox/virtualbox_vm.py index 08a567fd..350bd92e 100644 --- a/gns3server/modules/virtualbox/virtualbox_vm.py +++ b/gns3server/modules/virtualbox/virtualbox_vm.py @@ -279,7 +279,7 @@ class VirtualBoxVM(BaseVM): try: with open(hdd_info_file, "r", encoding="utf-8") as f: hdd_table = json.load(f) - except OSError as e: + except (ValueError, OSError) as e: raise VirtualBoxError("Could not read HDD info file: {}".format(e)) for hdd_info in hdd_table: From 970f22a83e005d561d2a60df0b8f3fb2358f9f79 Mon Sep 17 00:00:00 2001 From: grossmj Date: Sun, 4 Oct 2015 06:41:39 -0600 Subject: [PATCH 4/7] Use the correct UDP tunnel Qemu syntax for version > 1.1.0 when legacy networking is enabled. --- gns3server/modules/qemu/__init__.py | 37 +++++++++++++++++++---------- gns3server/modules/qemu/qemu_vm.py | 29 ++++++++++++++++++---- 2 files changed, 49 insertions(+), 17 deletions(-) diff --git a/gns3server/modules/qemu/__init__.py b/gns3server/modules/qemu/__init__.py index 960a0beb..0e9fe9b7 100644 --- a/gns3server/modules/qemu/__init__.py +++ b/gns3server/modules/qemu/__init__.py @@ -86,7 +86,7 @@ class Qemu(BaseManager): os.access(os.path.join(path, f), os.X_OK) and \ os.path.isfile(os.path.join(path, f)): qemu_path = os.path.join(path, f) - version = yield from Qemu._get_qemu_version(qemu_path) + version = yield from Qemu.get_qemu_version(qemu_path) qemus.append({"path": qemu_path, "version": version}) except OSError: continue @@ -95,7 +95,7 @@ class Qemu(BaseManager): @staticmethod @asyncio.coroutine - def _get_qemu_version(qemu_path): + def get_qemu_version(qemu_path): """ Gets the Qemu version. @@ -103,17 +103,30 @@ class Qemu(BaseManager): """ if sys.platform.startswith("win"): + # Qemu on Windows doesn't return anything with parameter -version + # look for a version number in version.txt file in the same directory instead + version_file = os.path.join(os.path.dirname(qemu_path), "version.txt") + if os.path.isfile(version_file): + try: + with open(version_file, "rb") as file: + version = file.read().decode("utf-8").strip() + match = re.search("[0-9\.]+", version) + if match: + return version + except (UnicodeDecodeError, OSError) as e: + log.warn("could not read {}: {}".format(version_file, e)) return "" - try: - output = yield from subprocess_check_output(qemu_path, "-version") - match = re.search("version\s+([0-9a-z\-\.]+)", output) - if match: - version = match.group(1) - return version - else: - raise QemuError("Could not determine the Qemu version for {}".format(qemu_path)) - except subprocess.SubprocessError as e: - raise QemuError("Error while looking for the Qemu version: {}".format(e)) + else: + try: + output = yield from subprocess_check_output(qemu_path, "-version") + match = re.search("version\s+([0-9a-z\-\.]+)", output) + if match: + version = match.group(1) + return version + else: + raise QemuError("Could not determine the Qemu version for {}".format(qemu_path)) + except subprocess.SubprocessError as e: + raise QemuError("Error while looking for the Qemu version: {}".format(e)) @staticmethod def get_legacy_vm_workdir(legacy_vm_id, name): diff --git a/gns3server/modules/qemu/qemu_vm.py b/gns3server/modules/qemu/qemu_vm.py index 94d0d334..afe7bebc 100644 --- a/gns3server/modules/qemu/qemu_vm.py +++ b/gns3server/modules/qemu/qemu_vm.py @@ -28,6 +28,7 @@ import shlex import asyncio import socket +from pkg_resources import parse_version from .qemu_error import QemuError from ..adapters.ethernet_adapter import EthernetAdapter from ..nios.nio_udp import NIOUDP @@ -990,6 +991,14 @@ class QemuVM(BaseVM): network_options = [] network_options.extend(["-net", "none"]) # we do not want any user networking back-end if no adapter is connected. + + patched_qemu = False + if self._legacy_networking: + version = yield from self.manager.get_qemu_version(self.qemu_path) + if version and parse_version(version) < parse_version("1.1.0"): + # this is a patched Qemu if version is below 1.1.0 + patched_qemu = True + for adapter_number, adapter in enumerate(self._ethernet_adapters): # TODO: let users specify a base mac address mac = "00:00:ab:%s:%s:%02x" % (self.id[-4:-2], self.id[-2:], adapter_number) @@ -999,11 +1008,21 @@ class QemuVM(BaseVM): if nio: network_options.extend(["-net", "nic,vlan={},macaddr={},model={}".format(adapter_number, mac, self._adapter_type)]) if isinstance(nio, NIOUDP): - network_options.extend(["-net", "udp,vlan={},name=gns3-{},sport={},dport={},daddr={}".format(adapter_number, - adapter_number, - nio.lport, - nio.rport, - nio.rhost)]) + if patched_qemu: + # use patched Qemu syntax + network_options.extend(["-net", "udp,vlan={},name=gns3-{},sport={},dport={},daddr={}".format(adapter_number, + adapter_number, + nio.lport, + nio.rport, + nio.rhost)]) + else: + # use UDP tunnel support added in Qemu 1.1.0 + network_options.extend(["-net", "socket,vlan={},name=gns3-{},udp={}:{},localaddr={}:{}".format(adapter_number, + adapter_number, + nio.rhost, + nio.rport, + self._host, + nio.lport)]) elif isinstance(nio, NIOTAP): network_options.extend(["-net", "tap,name=gns3-{},ifname={}".format(adapter_number, nio.tap_device)]) elif isinstance(nio, NIONAT): From 3f86df5169af982c46465f89547b8e88ee8ebf51 Mon Sep 17 00:00:00 2001 From: grossmj Date: Sun, 4 Oct 2015 07:00:47 -0600 Subject: [PATCH 5/7] Fixes uncalled coroutine. --- gns3server/modules/qemu/qemu_vm.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gns3server/modules/qemu/qemu_vm.py b/gns3server/modules/qemu/qemu_vm.py index afe7bebc..211fa5d2 100644 --- a/gns3server/modules/qemu/qemu_vm.py +++ b/gns3server/modules/qemu/qemu_vm.py @@ -987,6 +987,7 @@ class QemuVM(BaseVM): return options + @asyncio.coroutine def _network_options(self): network_options = [] @@ -1070,8 +1071,7 @@ class QemuVM(BaseVM): command = [self.qemu_path] command.extend(["-name", self._name]) command.extend(["-m", str(self._ram)]) - disk_options = yield from self._disk_options() - command.extend(disk_options) + command.extend((yield from self._disk_options())) command.extend(self._linux_boot_options()) command.extend(self._serial_options()) command.extend(self._monitor_options()) @@ -1081,7 +1081,7 @@ class QemuVM(BaseVM): command.extend(shlex.split(additional_options)) except ValueError as e: QemuError("Invalid additional options: {} error {}".format(additional_options, e)) - command.extend(self._network_options()) + command.extend((yield from self._network_options())) command.extend(self._graphic()) return command From fc5afa6676342d908ac11a2d025372a4be4d9a74 Mon Sep 17 00:00:00 2001 From: grossmj Date: Sun, 4 Oct 2015 14:20:44 -0600 Subject: [PATCH 6/7] Catch ProcessLookupError in Qemu VM. --- gns3server/modules/qemu/qemu_vm.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gns3server/modules/qemu/qemu_vm.py b/gns3server/modules/qemu/qemu_vm.py index 211fa5d2..616b74dd 100644 --- a/gns3server/modules/qemu/qemu_vm.py +++ b/gns3server/modules/qemu/qemu_vm.py @@ -611,6 +611,8 @@ class QemuVM(BaseVM): log.error("Cannot stop the Qemu process: {}".format(e)) if self._process.returncode is None: log.warn('QEMU VM "{}" with PID={} is still running'.format(self._name, self._process.pid)) + except ProcessLookupError: + pass self._process = None self._started = False self._stop_cpulimit() From bfe9c117bacaf4b810c078735d37601239485040 Mon Sep 17 00:00:00 2001 From: Julien Duponchelle Date: Mon, 5 Oct 2015 09:14:55 +0200 Subject: [PATCH 7/7] Fix tests --- tests/modules/qemu/test_qemu_manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/modules/qemu/test_qemu_manager.py b/tests/modules/qemu/test_qemu_manager.py index 572fb347..5d1315ce 100644 --- a/tests/modules/qemu/test_qemu_manager.py +++ b/tests/modules/qemu/test_qemu_manager.py @@ -27,7 +27,7 @@ from tests.utils import asyncio_patch def test_get_qemu_version(loop): with asyncio_patch("gns3server.modules.qemu.subprocess_check_output", return_value="QEMU emulator version 2.2.0, Copyright (c) 2003-2008 Fabrice Bellard") as mock: - version = loop.run_until_complete(asyncio.async(Qemu._get_qemu_version("/tmp/qemu-test"))) + version = loop.run_until_complete(asyncio.async(Qemu.get_qemu_version("/tmp/qemu-test"))) if sys.platform.startswith("win"): assert version == "" else: