From 9afe756462219f95ce4314b4f3e6ecdcdf9903fc Mon Sep 17 00:00:00 2001 From: grossmj Date: Tue, 14 Nov 2017 18:22:59 +0700 Subject: [PATCH 01/12] Add low disk space warning when creating a new project. --- gns3server/compute/project_manager.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/gns3server/compute/project_manager.py b/gns3server/compute/project_manager.py index cce5af261..28c517d13 100644 --- a/gns3server/compute/project_manager.py +++ b/gns3server/compute/project_manager.py @@ -16,9 +16,14 @@ # along with this program. If not, see . import aiohttp +import psutil +import platform from .project import Project from uuid import UUID +import logging +log = logging.getLogger(__name__) + class ProjectManager: @@ -70,6 +75,26 @@ class ProjectManager: raise aiohttp.web.HTTPNotFound(text="Project ID {} doesn't exist".format(project_id)) return self._projects[project_id] + def _check_available_disk_space(self, project): + """ + Sends a warning notification if disk space is getting low. + + :param project: project instance + """ + + try: + used_disk_space = psutil.disk_usage(project.path).percent + except FileNotFoundError: + log.warning('Could not find "{}" when checking for used disk space'.format(project.path)) + return + # send a warning if used disk space is >= 90% + if used_disk_space >= 90: + message = 'Only {}% or less of disk space detected in "{}" on "{}"'.format(used_disk_space, + project.path, + platform.node()) + log.warning(message) + project.emit("log.warning", {"message": message}) + def create_project(self, name=None, project_id=None, path=None): """ Create a project and keep a references to it in project manager. @@ -80,6 +105,7 @@ class ProjectManager: if project_id is not None and project_id in self._projects: return self._projects[project_id] project = Project(name=name, project_id=project_id, path=path) + self._check_available_disk_space(project) self._projects[project.id] = project return project From b14e2e73eda871b16206a92c0a0585ed6a04b26c Mon Sep 17 00:00:00 2001 From: grossmj Date: Wed, 15 Nov 2017 16:41:33 +0700 Subject: [PATCH 02/12] Fix "Can't use VirtualBox VM when an interface is managed by VirtualBox". Fixes #2335. --- .../compute/virtualbox/virtualbox_vm.py | 39 +++++++++++++------ 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/gns3server/compute/virtualbox/virtualbox_vm.py b/gns3server/compute/virtualbox/virtualbox_vm.py index 16b1bf23e..1e20ebafe 100644 --- a/gns3server/compute/virtualbox/virtualbox_vm.py +++ b/gns3server/compute/virtualbox/virtualbox_vm.py @@ -846,10 +846,8 @@ class VirtualBoxVM(BaseNode): nio = self._local_udp_tunnels[adapter_number][0] if nio: - if not self._use_any_adapter and attachment not in ("none", "null", "generic"): - raise VirtualBoxError("Attachment ({}) already configured on adapter {}. " - "Please set it to 'Not attached' to allow GNS3 to use it.".format(attachment, - adapter_number + 1)) + if not self._use_any_adapter and attachment in ("nat", "bridged", "intnet", "hostonly", "natnetwork"): + continue yield from self._modify_vm("--nictrace{} off".format(adapter_number + 1)) vbox_adapter_type = "82540EM" @@ -972,23 +970,40 @@ class VirtualBoxVM(BaseNode): raise VirtualBoxError("Adapter {adapter_number} doesn't exist on VirtualBox VM '{name}'".format(name=self.name, adapter_number=adapter_number)) + # check if trying to connect to a nat, bridged, host-only or any other special adapter + nic_attachments = yield from self._get_nic_attachements(self._maximum_adapters) + attachment = nic_attachments[adapter_number] + if attachment in ("nat", "bridged", "intnet", "hostonly", "natnetwork"): + if not self._use_any_adapter: + raise VirtualBoxError("Attachment '{attachment}' is already configured on adapter {adapter_number}. " + "Please remove it or allow VirtualBox VM '{name}' to use any adapter.".format(attachment=attachment, + adapter_number=adapter_number, + name=self.name)) + elif self.is_running(): + # dynamically configure an UDP tunnel attachment if the VM is already running + local_nio = self._local_udp_tunnels[adapter_number][0] + if local_nio and isinstance(local_nio, NIOUDP): + yield from self._control_vm("nic{} generic UDPTunnel".format(adapter_number + 1)) + yield from self._control_vm("nicproperty{} sport={}".format(adapter_number + 1, local_nio.lport)) + yield from self._control_vm("nicproperty{} dest={}".format(adapter_number + 1, local_nio.rhost)) + yield from self._control_vm("nicproperty{} dport={}".format(adapter_number + 1, local_nio.rport)) + yield from self._control_vm("setlinkstate{} on".format(adapter_number + 1)) + if self.is_running(): try: yield from self.add_ubridge_udp_connection("VBOX-{}-{}".format(self._id, adapter_number), self._local_udp_tunnels[adapter_number][1], nio) except KeyError: - raise VirtualBoxError("Adapter {adapter_number} doesn't exist on VirtualBox VM '{name}'".format( - name=self.name, - adapter_number=adapter_number)) + raise VirtualBoxError("Adapter {adapter_number} doesn't exist on VirtualBox VM '{name}'".format(name=self.name, + adapter_number=adapter_number)) yield from self._control_vm("setlinkstate{} on".format(adapter_number + 1)) adapter.add_nio(0, nio) - log.info("VirtualBox VM '{name}' [{id}]: {nio} added to adapter {adapter_number}".format( - name=self.name, - id=self.id, - nio=nio, - adapter_number=adapter_number)) + log.info("VirtualBox VM '{name}' [{id}]: {nio} added to adapter {adapter_number}".format(name=self.name, + id=self.id, + nio=nio, + adapter_number=adapter_number)) @asyncio.coroutine def adapter_update_nio_binding(self, adapter_number, nio): From 8c11b649e5489dd13f65e59ff8242b9f5a34e04f Mon Sep 17 00:00:00 2001 From: grossmj Date: Wed, 15 Nov 2017 16:55:19 +0700 Subject: [PATCH 03/12] Update warning messages when connecting to non custom adapter for VMware VMs. --- gns3server/compute/vmware/vmware_vm.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/gns3server/compute/vmware/vmware_vm.py b/gns3server/compute/vmware/vmware_vm.py index bf5359e60..37311f5ed 100644 --- a/gns3server/compute/vmware/vmware_vm.py +++ b/gns3server/compute/vmware/vmware_vm.py @@ -736,13 +736,20 @@ class VMwareVM(BaseNode): self._read_vmx_file() # check if trying to connect to a nat, bridged or host-only adapter - if not self._use_any_adapter and self._get_vmx_setting("ethernet{}.present".format(adapter_number), "TRUE"): + if self._get_vmx_setting("ethernet{}.present".format(adapter_number), "TRUE"): # check for the connection type connection_type = "ethernet{}.connectiontype".format(adapter_number) if connection_type in self._vmx_pairs and self._vmx_pairs[connection_type] in ("nat", "bridged", "hostonly"): - raise VMwareError("Attachment ({}) already configured on network adapter {}. " - "Please remove it or allow GNS3 to use any adapter.".format(self._vmx_pairs[connection_type], - adapter_number)) + if not self._use_any_adapter: + raise VMwareError("Attachment '{attachment}' is already configured on network adapter {adapter_number}. " + "Please remove it or allow VMware VM '{name}' to use any adapter.".format(attachment=self._vmx_pairs[connection_type], + adapter_number=adapter_number, + name=self.name)) + elif self.is_running(): + raise VMwareError("Attachment '{attachment}' is configured on network adapter {adapter_number}. " + "Please stop VMware VM '{name}' to link to this adapter and allow GNS3 to change the attachment type.".format(attachment=self._vmx_pairs[connection_type], + adapter_number=adapter_number, + name=self.name)) adapter.add_nio(0, nio) if self._started and self._ubridge_hypervisor: From 61acb939a48a4156c44b40fac0b9d73ce1f294d6 Mon Sep 17 00:00:00 2001 From: ziajka Date: Wed, 15 Nov 2017 13:20:31 +0100 Subject: [PATCH 04/12] Update pyup config to use 2.2 branch --- .pyup.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pyup.yml b/.pyup.yml index 62a42f2ed..4f1bbaec5 100644 --- a/.pyup.yml +++ b/.pyup.yml @@ -1,2 +1,2 @@ branch: - 2.0 + 2.2 From 967798a7de7040de38bfd4314537917d6d9a71d4 Mon Sep 17 00:00:00 2001 From: grossmj Date: Thu, 16 Nov 2017 14:54:37 +0700 Subject: [PATCH 05/12] Check and fix corrupt Qemu disk images. Fixes #2301. --- gns3server/compute/qemu/qemu_vm.py | 43 +++++++++++++++++++++++------- 1 file changed, 33 insertions(+), 10 deletions(-) diff --git a/gns3server/compute/qemu/qemu_vm.py b/gns3server/compute/qemu/qemu_vm.py index 3268bf721..097e7a479 100644 --- a/gns3server/compute/qemu/qemu_vm.py +++ b/gns3server/compute/qemu/qemu_vm.py @@ -32,6 +32,7 @@ import gns3server import subprocess from gns3server.utils import parse_version +from gns3server.utils.asyncio import subprocess_check_output from .qemu_error import QemuError from ..adapters.ethernet_adapter import EthernetAdapter from ..nios.nio_udp import NIOUDP @@ -1360,6 +1361,15 @@ class QemuVM(BaseNode): return qemu_img_path + @asyncio.coroutine + def _qemu_img_exec(self, command): + command_string = " ".join(shlex.quote(s) for s in command) + log.info("Executing qemu-img with: {}".format(command_string)) + process = yield from asyncio.create_subprocess_exec(*command) + retcode = yield from process.wait() + log.info("{} returned with {}".format(self._get_qemu_img(), retcode)) + return retcode + @asyncio.coroutine def _disk_options(self): options = [] @@ -1381,29 +1391,42 @@ class QemuVM(BaseNode): raise QemuError("{} disk image '{}' linked to '{}' is not accessible".format(disk_name, disk_image, os.path.realpath(disk_image))) else: raise QemuError("{} disk image '{}' is not accessible".format(disk_name, disk_image)) + else: + try: + # check for corrupt disk image + retcode = yield from self._qemu_img_exec([qemu_img_path, "check", disk_image]) + if retcode == 3: + # image has leaked clusters, but is not corrupted, let's try to fix it + log.warning("Qemu image {} has leaked clusters".format(disk_image)) + if (yield from self._qemu_img_exec([qemu_img_path, "check", "-r", "leaks", "{}".format(disk_image)])) == 3: + self.project.emit("log.warning", {"message": "Qemu image '{}' has leaked clusters and could not be fixed".format(disk_image)}) + elif retcode == 2: + # image is corrupted, let's try to fix it + log.warning("Qemu image {} is corrupted".format(disk_image)) + if (yield from self._qemu_img_exec([qemu_img_path, "check", "-r", "all", "{}".format(disk_image)])) == 2: + self.project.emit("log.warning", {"message": "Qemu image '{}' is corrupted and could not be fixed".format(disk_image)}) + except (OSError, subprocess.SubprocessError) as e: + raise QemuError("Could not check '{}' disk image: {}".format(disk_name, e)) + if self.linked_clone: disk = os.path.join(self.working_dir, "{}_disk.qcow2".format(disk_name)) if not os.path.exists(disk): # create the disk try: command = [qemu_img_path, "create", "-o", "backing_file={}".format(disk_image), "-f", "qcow2", disk] - command_string = " ".join(shlex.quote(s) for s in command) - log.info("Executing qemu-img with: {}".format(command_string)) - process = yield from asyncio.create_subprocess_exec(*command) - retcode = yield from process.wait() - if retcode is not None and retcode != 0: - raise QemuError("Could not create {} disk image: qemu-img returned with {}".format(disk_name, + retcode = yield from self._qemu_img_exec(command) + if retcode: + raise QemuError("Could not create '{}' disk image: qemu-img returned with {}".format(disk_name, retcode)) - log.info("{} returned with {}".format(qemu_img_path, retcode)) except (OSError, subprocess.SubprocessError) as e: - raise QemuError("Could not create {} disk image: {}".format(disk_name, e)) + raise QemuError("Could not create '{}' disk image: {}".format(disk_name, e)) else: - # The disk exists we check if the clone work + # The disk exists we check if the clone works try: qcow2 = Qcow2(disk) yield from qcow2.rebase(qemu_img_path, disk_image) except (Qcow2Error, OSError) as e: - raise QemuError("Could not use qcow2 disk image {} for {} {}".format(disk_image, disk_name, e)) + raise QemuError("Could not use qcow2 disk image '{}' for {} {}".format(disk_image, disk_name, e)) else: disk = disk_image From e1fe34ca07417d81f7701525e79f3490042a456e Mon Sep 17 00:00:00 2001 From: grossmj Date: Thu, 16 Nov 2017 16:52:19 +0700 Subject: [PATCH 06/12] Use the correct NVRAM amount when pushing private config to IOU. --- gns3server/compute/iou/iou_vm.py | 30 +++++++++++++++++-- .../handlers/api/compute/iou_handler.py | 8 +++++ 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/gns3server/compute/iou/iou_vm.py b/gns3server/compute/iou/iou_vm.py index 0dcf0c883..a604eac84 100644 --- a/gns3server/compute/iou/iou_vm.py +++ b/gns3server/compute/iou/iou_vm.py @@ -65,7 +65,7 @@ class IOUVM(BaseNode): :param console: TCP console port """ - def __init__(self, name, node_id, project, manager, console=None): + def __init__(self, name, node_id, project, manager, path=None, console=None): super().__init__(name, node_id, project, manager, console=console) @@ -73,8 +73,8 @@ class IOUVM(BaseNode): self._telnet_server = None self._iou_stdout_file = "" self._started = False - self._path = None self._nvram_watcher = None + self._path = self.manager.get_abs_image_path(path) # IOU settings self._ethernet_adapters = [] @@ -137,6 +137,7 @@ class IOUVM(BaseNode): """ self._path = self.manager.get_abs_image_path(path) + log.info('IOU "{name}" [{id}]: IOU image updated to "{path}"'.format(name=self._name, id=self._id, path=self._path)) @property def use_default_iou_values(self): @@ -162,6 +163,28 @@ class IOUVM(BaseNode): else: log.info('IOU "{name}" [{id}]: does not use the default IOU image values'.format(name=self._name, id=self._id)) + @asyncio.coroutine + def update_default_iou_values(self): + """ + Finds the default RAM and NVRAM values for the IOU image. + """ + + try: + output = yield from gns3server.utils.asyncio.subprocess_check_output(self._path, "-h", cwd=self.working_dir, stderr=True) + match = re.search("-n \s+Size of nvram in Kb \(default ([0-9]+)KB\)", output) + if match: + self.nvram = int(match.group(1)) + match = re.search("-m \s+Megabytes of router memory \(default ([0-9]+)MB\)", output) + if match: + self.ram = int(match.group(1)) + except (ValueError, OSError, subprocess.SubprocessError) as e: + log.warning("could not find default RAM and NVRAM values for {}: {}".format(os.path.basename(self._path), e)) + + @asyncio.coroutine + def create(self): + + yield from self.update_default_iou_values() + def _check_requirements(self): """ Checks the IOU image. @@ -479,6 +502,9 @@ class IOUVM(BaseNode): yield from self._start_ubridge() self._create_netmap_config() + if self.use_default_iou_values: + # make sure we have the default nvram amount to correctly push the configs + yield from self.update_default_iou_values() self._push_configs_to_nvram() # check if there is enough RAM to run diff --git a/gns3server/handlers/api/compute/iou_handler.py b/gns3server/handlers/api/compute/iou_handler.py index 1dc4057aa..d0e38f7ec 100644 --- a/gns3server/handlers/api/compute/iou_handler.py +++ b/gns3server/handlers/api/compute/iou_handler.py @@ -59,6 +59,7 @@ class IOUHandler: vm = yield from iou.create_node(request.json.pop("name"), request.match_info["project_id"], request.json.get("node_id"), + path=request.json.get("path"), console=request.json.get("console")) for name, value in request.json.items(): @@ -67,6 +68,8 @@ class IOUHandler: continue if name == "private_config_content" and (vm.private_config_content and len(vm.private_config_content) > 0): continue + if request.json.get("use_default_iou_values") and (name == "ram" or name == "nvram"): + continue setattr(vm, name, value) response.set_status(201) response.json(vm) @@ -113,6 +116,11 @@ class IOUHandler: for name, value in request.json.items(): if hasattr(vm, name) and getattr(vm, name) != value: setattr(vm, name, value) + + if vm.use_default_iou_values: + # update the default IOU values in case the image or use_default_iou_values have changed + # this is important to have the correct NVRAM amount in order to correctly push the configs to the NVRAM + yield from vm.update_default_iou_values() vm.updated() response.json(vm) From cf14deb2facabd9160c08e2a3b09e81da7122a4e Mon Sep 17 00:00:00 2001 From: grossmj Date: Thu, 16 Nov 2017 17:07:51 +0700 Subject: [PATCH 07/12] Fix IOU tests. --- tests/handlers/api/compute/test_iou.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/handlers/api/compute/test_iou.py b/tests/handlers/api/compute/test_iou.py index 7ce965128..74147a8e7 100644 --- a/tests/handlers/api/compute/test_iou.py +++ b/tests/handlers/api/compute/test_iou.py @@ -79,7 +79,7 @@ def test_iou_create_with_params(http_compute, project, base_params): params["ethernet_adapters"] = 0 params["l1_keepalives"] = True params["startup_config_content"] = "hostname test" - params["use_default_iou_values"] = True + params["use_default_iou_values"] = False response = http_compute.post("/projects/{project_id}/iou/nodes".format(project_id=project.id), params, example=True) assert response.status == 201 @@ -91,7 +91,7 @@ def test_iou_create_with_params(http_compute, project, base_params): assert response.json["ram"] == 1024 assert response.json["nvram"] == 512 assert response.json["l1_keepalives"] is True - assert response.json["use_default_iou_values"] is True + assert response.json["use_default_iou_values"] is False with open(startup_config_file(project, response.json)) as f: assert f.read() == "hostname test" From 035a078b5e99bec6943156e0a256dc7fa05c44e8 Mon Sep 17 00:00:00 2001 From: grossmj Date: Fri, 17 Nov 2017 18:13:34 +0700 Subject: [PATCH 08/12] Show qemu-img stdout in case of an error. --- gns3server/compute/base_manager.py | 4 ++-- gns3server/compute/qemu/qemu_vm.py | 35 +++++++++++++++++++++++++----- gns3server/controller/__init__.py | 2 +- 3 files changed, 32 insertions(+), 9 deletions(-) diff --git a/gns3server/compute/base_manager.py b/gns3server/compute/base_manager.py index dd3d7a6fc..d233fb8de 100644 --- a/gns3server/compute/base_manager.py +++ b/gns3server/compute/base_manager.py @@ -537,8 +537,8 @@ class BaseManager: directory = self.get_images_directory() path = os.path.abspath(os.path.join(directory, *os.path.split(filename))) if os.path.commonprefix([directory, path]) != directory: - raise aiohttp.web.HTTPForbidden(text="Could not write image: {}, {} is forbiden".format(filename, path)) - log.info("Writting image file %s", path) + raise aiohttp.web.HTTPForbidden(text="Could not write image: {}, {} is forbidden".format(filename, path)) + log.info("Writing image file %s", path) try: remove_checksum(path) # We store the file under his final name only when the upload is finished diff --git a/gns3server/compute/qemu/qemu_vm.py b/gns3server/compute/qemu/qemu_vm.py index 097e7a479..358795287 100644 --- a/gns3server/compute/qemu/qemu_vm.py +++ b/gns3server/compute/qemu/qemu_vm.py @@ -75,6 +75,7 @@ class QemuVM(BaseNode): self._cpulimit_process = None self._monitor = None self._stdout_file = "" + self._qemu_img_stdout_file = "" self._execute_lock = asyncio.Lock() self._local_udp_tunnels = {} @@ -1283,7 +1284,21 @@ class QemuVM(BaseNode): with open(self._stdout_file, "rb") as file: output = file.read().decode("utf-8", errors="replace") except OSError as e: - log.warn("Could not read {}: {}".format(self._stdout_file, e)) + log.warning("Could not read {}: {}".format(self._stdout_file, e)) + return output + + def read_qemu_img_stdout(self): + """ + Reads the standard output of the QEMU-IMG process. + """ + + output = "" + if self._qemu_img_stdout_file: + try: + with open(self._qemu_img_stdout_file, "rb") as file: + output = file.read().decode("utf-8", errors="replace") + except OSError as e: + log.warning("Could not read {}: {}".format(self._qemu_img_stdout_file, e)) return output def is_running(self): @@ -1363,9 +1378,13 @@ class QemuVM(BaseNode): @asyncio.coroutine def _qemu_img_exec(self, command): + + self._qemu_img_stdout_file = os.path.join(self.working_dir, "qemu-img.log") + log.info("logging to {}".format(self._qemu_img_stdout_file)) command_string = " ".join(shlex.quote(s) for s in command) log.info("Executing qemu-img with: {}".format(command_string)) - process = yield from asyncio.create_subprocess_exec(*command) + with open(self._qemu_img_stdout_file, "w", encoding="utf-8") as fd: + process = yield from asyncio.create_subprocess_exec(*command, stdout=fd, stderr=subprocess.STDOUT, cwd=self.working_dir) retcode = yield from process.wait() log.info("{} returned with {}".format(self._get_qemu_img(), retcode)) return retcode @@ -1406,7 +1425,8 @@ class QemuVM(BaseNode): if (yield from self._qemu_img_exec([qemu_img_path, "check", "-r", "all", "{}".format(disk_image)])) == 2: self.project.emit("log.warning", {"message": "Qemu image '{}' is corrupted and could not be fixed".format(disk_image)}) except (OSError, subprocess.SubprocessError) as e: - raise QemuError("Could not check '{}' disk image: {}".format(disk_name, e)) + stdout = self.read_qemu_img_stdout() + raise QemuError("Could not check '{}' disk image: {}\n{}".format(disk_name, e, stdout)) if self.linked_clone: disk = os.path.join(self.working_dir, "{}_disk.qcow2".format(disk_name)) @@ -1416,10 +1436,13 @@ class QemuVM(BaseNode): command = [qemu_img_path, "create", "-o", "backing_file={}".format(disk_image), "-f", "qcow2", disk] retcode = yield from self._qemu_img_exec(command) if retcode: - raise QemuError("Could not create '{}' disk image: qemu-img returned with {}".format(disk_name, - retcode)) + stdout = self.read_qemu_img_stdout() + raise QemuError("Could not create '{}' disk image: qemu-img returned with {}\n{}".format(disk_name, + retcode, + stdout)) except (OSError, subprocess.SubprocessError) as e: - raise QemuError("Could not create '{}' disk image: {}".format(disk_name, e)) + stdout = self.read_qemu_img_stdout() + raise QemuError("Could not create '{}' disk image: {}\n{}".format(disk_name, e, stdout)) else: # The disk exists we check if the clone works try: diff --git a/gns3server/controller/__init__.py b/gns3server/controller/__init__.py index c506d3dff..3f1b1c203 100644 --- a/gns3server/controller/__init__.py +++ b/gns3server/controller/__init__.py @@ -587,7 +587,7 @@ class Controller: @property def projects(self): """ - :returns: The dictionary of computes managed by GNS3 + :returns: The dictionary of projects managed by GNS3 """ return self._projects From cfbcc1194d07c032ce912720fd97c159d2db5c59 Mon Sep 17 00:00:00 2001 From: grossmj Date: Sat, 18 Nov 2017 16:22:29 +0700 Subject: [PATCH 09/12] Avoid duplicate "-nographic" option. --- gns3server/compute/qemu/qemu_vm.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/gns3server/compute/qemu/qemu_vm.py b/gns3server/compute/qemu/qemu_vm.py index 358795287..b1a1e41ad 100644 --- a/gns3server/compute/qemu/qemu_vm.py +++ b/gns3server/compute/qemu/qemu_vm.py @@ -1603,7 +1603,9 @@ class QemuVM(BaseNode): return [] if len(os.environ.get("DISPLAY", "")) > 0: return [] - return ["-nographic"] + if "-nographic" not in self._options: + return ["-nographic"] + return [] def _run_with_kvm(self, qemu_path, options): """ From e08aebf1f0c0765a55d82da40b6f9909818ecc94 Mon Sep 17 00:00:00 2001 From: grossmj Date: Sat, 18 Nov 2017 17:36:11 +0700 Subject: [PATCH 10/12] Implement variable replacement for Qemu VM options. --- gns3server/compute/qemu/qemu_vm.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/gns3server/compute/qemu/qemu_vm.py b/gns3server/compute/qemu/qemu_vm.py index b1a1e41ad..3ddfc2d54 100644 --- a/gns3server/compute/qemu/qemu_vm.py +++ b/gns3server/compute/qemu/qemu_vm.py @@ -1636,6 +1636,11 @@ class QemuVM(BaseNode): """ additional_options = self._options.strip() + additional_options = additional_options.replace("%vm-name%", self._name) + additional_options = additional_options.replace("%vm-id%", self._id) + additional_options = additional_options.replace("%project-id%", self.project.id) + additional_options = additional_options.replace("%project-path%", self.project.path) + print(additional_options) command = [self.qemu_path] command.extend(["-name", self._name]) command.extend(["-m", "{}M".format(self._ram)]) From 068077c5944332af0bfd565e75969a6adb049c53 Mon Sep 17 00:00:00 2001 From: grossmj Date: Sat, 18 Nov 2017 17:36:39 +0700 Subject: [PATCH 11/12] Remove forgotten debug. --- gns3server/compute/qemu/qemu_vm.py | 1 - 1 file changed, 1 deletion(-) diff --git a/gns3server/compute/qemu/qemu_vm.py b/gns3server/compute/qemu/qemu_vm.py index 3ddfc2d54..41a0fb509 100644 --- a/gns3server/compute/qemu/qemu_vm.py +++ b/gns3server/compute/qemu/qemu_vm.py @@ -1640,7 +1640,6 @@ class QemuVM(BaseNode): additional_options = additional_options.replace("%vm-id%", self._id) additional_options = additional_options.replace("%project-id%", self.project.id) additional_options = additional_options.replace("%project-path%", self.project.path) - print(additional_options) command = [self.qemu_path] command.extend(["-name", self._name]) command.extend(["-m", "{}M".format(self._ram)]) From 3e2e8e61f734eea52604117b33017bb9c33f90f6 Mon Sep 17 00:00:00 2001 From: grossmj Date: Sun, 19 Nov 2017 12:39:37 +0700 Subject: [PATCH 12/12] New option: require KVM. If false, Qemu VMs will not be prevented to run without KVM. --- conf/gns3_server.conf | 2 ++ gns3server/compute/qemu/qemu_vm.py | 5 ++++- scripts/remote-install.sh | 2 ++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/conf/gns3_server.conf b/conf/gns3_server.conf index 83ab86ecd..e8e984319 100644 --- a/conf/gns3_server.conf +++ b/conf/gns3_server.conf @@ -63,3 +63,5 @@ license_check = True [Qemu] ; !! Remember to add the gns3 user to the KVM group, otherwise you will not have read / write permssions to /dev/kvm !! enable_kvm = True +; Require KVM to be installed in order to start VMs +require_kvm = True \ No newline at end of file diff --git a/gns3server/compute/qemu/qemu_vm.py b/gns3server/compute/qemu/qemu_vm.py index 41a0fb509..74ea2dc67 100644 --- a/gns3server/compute/qemu/qemu_vm.py +++ b/gns3server/compute/qemu/qemu_vm.py @@ -1624,7 +1624,10 @@ class QemuVM(BaseNode): return False if not os.path.exists("/dev/kvm"): - raise QemuError("KVM acceleration cannot be used (/dev/kvm doesn't exist). You can turn off KVM support in the gns3_server.conf by adding enable_kvm = false to the [Qemu] section.") + if self.manager.config.get_section_config("Qemu").getboolean("require_kvm", True): + raise QemuError("KVM acceleration cannot be used (/dev/kvm doesn't exist). You can turn off KVM support in the gns3_server.conf by adding enable_kvm = false to the [Qemu] section.") + else: + return False return True return False diff --git a/scripts/remote-install.sh b/scripts/remote-install.sh index b126bf39c..1ea888ca4 100644 --- a/scripts/remote-install.sh +++ b/scripts/remote-install.sh @@ -212,6 +212,7 @@ report_errors = True [Qemu] enable_kvm = True +require_kvm = True EOFC chown -R gns3:gns3 /etc/gns3 @@ -298,6 +299,7 @@ report_errors = True [Qemu] enable_kvm = True +require_kvm = True EOFSERVER log "Install packages for Open VPN"