From 8ad632976f2d29bef0947ffd94a811c6c9d188b0 Mon Sep 17 00:00:00 2001 From: grossmj Date: Thu, 14 Mar 2019 17:09:53 +0700 Subject: [PATCH 01/11] Release v2.2.0a2 --- CHANGELOG | 22 ++++++++++++++++++++++ gns3server/crash_report.py | 2 +- gns3server/version.py | 4 ++-- 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 5ce33d3c..5b41f6c0 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,27 @@ # Change Log +## 2.2.0a2 14/03/2019 + +* Web-UI v2019.1.0-alpha.1 +* Update docs for update-bundled-web-ui.sh +* Fix issue when loading and quickly closing a project and opening it again. Fixes #1501. +* Disable unreliable nested virtualization check. +* Fix issue not checking build number on Windows. +* Change Hyper-V requirement checks. +* Early support for symbol themes. +* Re-order handlers in order to prevent CORS +* Download custom appliance symbols from GitHub Fix symbol cache issue. Ref https://github.com/GNS3/gns3-gui/issues/2671 Fix temporary directory for symbols was not deleted Fix temporary appliance file was not deleted +* Option to export snapshots. +* Support tags versioned WebUI when bundling +* Support selecting a compression type when exporting a project. +* Change how VPCS executable is searched. +* Use aiofiles where relevant. +* Update paths for binaries moved to the MacOS directory in GNS3.app +* Locked state should not be used when duplicating a node. +* Handle locking/unlocking items independently from the layer position. +* Use aiozipstream for snapshots. Fix tests. +* Project duplication support. + ## 2.2.0a1 29/01/2019 * Restore reload support for nodes. diff --git a/gns3server/crash_report.py b/gns3server/crash_report.py index 938885f0..c5a9fc4b 100644 --- a/gns3server/crash_report.py +++ b/gns3server/crash_report.py @@ -58,7 +58,7 @@ class CrashReport: Report crash to a third party service """ - DSN = "https://f5e2d00b16764b7d8bb996a9c8980185:4c01020e90ee4657956bc680a25f9959@sentry.io/38482" + DSN = "https://b53e4cbcb01c453f9728dbf891424d18:061bfafc9c184f9bb2012d394f860fc4@sentry.io/38482" if hasattr(sys, "frozen"): cacert = get_resource("cacert.pem") if cacert is not None and os.path.isfile(cacert): diff --git a/gns3server/version.py b/gns3server/version.py index dfb19bce..6e3bee86 100644 --- a/gns3server/version.py +++ b/gns3server/version.py @@ -23,8 +23,8 @@ # or negative for a release candidate or beta (after the base version # number has been incremented) -__version__ = "2.2.0dev6" -__version_info__ = (2, 2, 0, 99) +__version__ = "2.2.0a2" +__version_info__ = (2, 2, 0, -99) # If it's a git checkout try to add the commit if "dev" in __version__: From 23b568678a1c7d7332f2ad5f92b9767693cb0fc4 Mon Sep 17 00:00:00 2001 From: grossmj Date: Thu, 14 Mar 2019 23:27:11 +0700 Subject: [PATCH 02/11] Development on 2.2.0dev7 --- gns3server/version.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gns3server/version.py b/gns3server/version.py index 6e3bee86..84f36b0f 100644 --- a/gns3server/version.py +++ b/gns3server/version.py @@ -23,8 +23,8 @@ # or negative for a release candidate or beta (after the base version # number has been incremented) -__version__ = "2.2.0a2" -__version_info__ = (2, 2, 0, -99) +__version__ = "2.2.0dev7" +__version_info__ = (2, 2, 0, 99) # If it's a git checkout try to add the commit if "dev" in __version__: From 3a73d0154705a551c01ebaa4524c2c35163348f4 Mon Sep 17 00:00:00 2001 From: grossmj Date: Fri, 15 Mar 2019 13:14:55 +0700 Subject: [PATCH 03/11] Fix exception when emitting event from controller. Ref https://github.com/GNS3/gns3-gui/issues/2737 --- gns3server/controller/compute.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gns3server/controller/compute.py b/gns3server/controller/compute.py index 2244eded..3fc9fc31 100644 --- a/gns3server/controller/compute.py +++ b/gns3server/controller/compute.py @@ -405,7 +405,7 @@ class Compute: raise aiohttp.web.HTTPConflict(text=msg) else: msg = "{}\nUsing different versions may result in unexpected problems. Please use at your own risk.".format(msg) - self._controller.notification.emit("log.warning", {"message": msg}) + self._controller.notification.controller_emit("log.warning", {"message": msg}) self._notifications = asyncio.gather(self._connect_notification()) self._connected = True From 4e396ac690cf55aadc5a8e91cdc2f652e5248075 Mon Sep 17 00:00:00 2001 From: grossmj Date: Mon, 18 Mar 2019 15:30:59 +0700 Subject: [PATCH 04/11] Save the GNS3 VM settings even if the GNS3 VM cannot be stopped. --- gns3server/controller/gns3vm/__init__.py | 8 +++++--- gns3server/controller/gns3vm/hyperv_gns3_vm.py | 2 ++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/gns3server/controller/gns3vm/__init__.py b/gns3server/controller/gns3vm/__init__.py index 8b6f7468..885916e1 100644 --- a/gns3server/controller/gns3vm/__init__.py +++ b/gns3server/controller/gns3vm/__init__.py @@ -212,9 +212,11 @@ class GNS3VM: new_settings = copy.copy(self._settings) new_settings.update(settings) if self.settings != new_settings: - await self._stop() - self._settings = settings - self._controller.save() + try: + await self._stop() + finally: + self._settings = settings + self._controller.save() if self.enable: await self.start() else: diff --git a/gns3server/controller/gns3vm/hyperv_gns3_vm.py b/gns3server/controller/gns3vm/hyperv_gns3_vm.py index 0509652f..a072e61d 100644 --- a/gns3server/controller/gns3vm/hyperv_gns3_vm.py +++ b/gns3server/controller/gns3vm/hyperv_gns3_vm.py @@ -239,6 +239,8 @@ class HyperVGNS3VM(BaseGNS3VM): log.info("GNS3 VM has been started") # Get the guest IP address + # LIS (Linux Integration Services) must be installed on the guest + # See https://oitibs.com/hyper-v-lis-on-ubuntu-18-04/ for details. trial = 120 guest_ip_address = "" log.info("Waiting for GNS3 VM IP") From 03401a477ef4841e31dbdf3a62281167addd83d4 Mon Sep 17 00:00:00 2001 From: grossmj Date: Mon, 18 Mar 2019 15:33:37 +0700 Subject: [PATCH 05/11] Fix issue when images are not uploaded from appliance wizard. Ref https://github.com/GNS3/gns3-gui/issues/2738 --- gns3server/controller/compute.py | 10 ++++------ gns3server/handlers/api/controller/compute_handler.py | 5 +++-- gns3server/utils/images.py | 2 +- tests/controller/test_compute.py | 4 +--- 4 files changed, 9 insertions(+), 12 deletions(-) diff --git a/gns3server/controller/compute.py b/gns3server/controller/compute.py index 3fc9fc31..aa191af2 100644 --- a/gns3server/controller/compute.py +++ b/gns3server/controller/compute.py @@ -27,7 +27,6 @@ import io from operator import itemgetter from ..utils import parse_version -from ..utils.images import list_images from ..utils.asyncio import locking from ..controller.controller_error import ControllerError from ..version import __version__, __version_info__ @@ -571,8 +570,7 @@ class Compute: async def images(self, type): """ - Return the list of images available for this type on controller - and on the compute node. + Return the list of images available for this type on the compute node. """ images = [] @@ -581,9 +579,9 @@ class Compute: try: if type in ["qemu", "dynamips", "iou"]: - for local_image in list_images(type): - if local_image['filename'] not in [i['filename'] for i in images]: - images.append(local_image) + #for local_image in list_images(type): + # if local_image['filename'] not in [i['filename'] for i in images]: + # images.append(local_image) images = sorted(images, key=itemgetter('filename')) else: images = sorted(images, key=itemgetter('image')) diff --git a/gns3server/handlers/api/controller/compute_handler.py b/gns3server/handlers/api/controller/compute_handler.py index 2214428a..fccc63f8 100644 --- a/gns3server/handlers/api/controller/compute_handler.py +++ b/gns3server/handlers/api/controller/compute_handler.py @@ -82,13 +82,14 @@ class ComputeHandler: @Route.get( r"/computes/{compute_id}/{emulator}/images", parameters={ - "compute_id": "Compute UUID" + "compute_id": "Compute UUID", + "emulator": "Emulator type" }, status_codes={ 200: "OK", 404: "Instance doesn't exist" }, - description="Return the list of images available on compute and controller for this emulator type") + description="Return the list of images available on compute for this emulator type") async def images(request, response): controller = Controller.instance() compute = controller.get_compute(request.match_info["compute_id"]) diff --git a/gns3server/utils/images.py b/gns3server/utils/images.py index 2af1858e..21bfb9a8 100644 --- a/gns3server/utils/images.py +++ b/gns3server/utils/images.py @@ -139,7 +139,7 @@ def images_directories(type): paths.append(directory) # Compatibility with old topologies we look in parent directory paths.append(img_dir) - # Return only the existings paths + # Return only the existing paths return [force_unix_path(p) for p in paths if os.path.exists(p)] diff --git a/tests/controller/test_compute.py b/tests/controller/test_compute.py index f03b680b..ab0f6094 100644 --- a/tests/controller/test_compute.py +++ b/tests/controller/test_compute.py @@ -348,7 +348,7 @@ def test_forward_post(compute, async_run): def test_images(compute, async_run, images_dir): """ - Will return image on compute and on controller + Will return image on compute """ response = MagicMock() response.status = 200 @@ -357,14 +357,12 @@ def test_images(compute, async_run, images_dir): "path": "linux.qcow2", "md5sum": "d41d8cd98f00b204e9800998ecf8427e", "filesize": 0}]).encode()) - open(os.path.join(images_dir, "QEMU", "asa.qcow2"), "w+").close() with asyncio_patch("aiohttp.ClientSession.request", return_value=response) as mock: images = async_run(compute.images("qemu")) mock.assert_called_with("GET", "https://example.com:84/v2/compute/qemu/images", auth=None, data=None, headers={'content-type': 'application/json'}, chunked=None, timeout=None) async_run(compute.close()) assert images == [ - {"filename": "asa.qcow2", "path": "asa.qcow2", "md5sum": "d41d8cd98f00b204e9800998ecf8427e", "filesize": 0}, {"filename": "linux.qcow2", "path": "linux.qcow2", "md5sum": "d41d8cd98f00b204e9800998ecf8427e", "filesize": 0} ] From 8e8985c69f621ec63a6029468cc29475bc785849 Mon Sep 17 00:00:00 2001 From: grossmj Date: Mon, 18 Mar 2019 17:53:14 +0700 Subject: [PATCH 06/11] Fix vcpus configuration for GNS3 VM on VMware. Ref #2738. --- gns3server/controller/gns3vm/vmware_gns3_vm.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/gns3server/controller/gns3vm/vmware_gns3_vm.py b/gns3server/controller/gns3vm/vmware_gns3_vm.py index 2931de6d..a02fd26b 100644 --- a/gns3server/controller/gns3vm/vmware_gns3_vm.py +++ b/gns3server/controller/gns3vm/vmware_gns3_vm.py @@ -72,18 +72,19 @@ class VMwareGNS3VM(BaseGNS3VM): if ram % 4 != 0: raise GNS3VMError("Allocated memory {} for the GNS3 VM must be a multiple of 4".format(ram)) - available_vcpus = psutil.cpu_count() + available_vcpus = psutil.cpu_count(logical=True) if vcpus > available_vcpus: raise GNS3VMError("You have allocated too many vCPUs for the GNS3 VM! (max available is {} vCPUs)".format(available_vcpus)) try: pairs = VMware.parse_vmware_file(self._vmx_path) - pairs["numvcpus"] = str(vcpus) - cores_per_sockets = int(available_vcpus / psutil.cpu_count(logical=False)) - if cores_per_sockets >= 1: - pairs["cpuid.corespersocket"] = str(cores_per_sockets) - pairs["memsize"] = str(ram) - VMware.write_vmx_file(self._vmx_path, pairs) + if vcpus > 1: + pairs["numvcpus"] = str(vcpus) + cores_per_sockets = int(vcpus / psutil.cpu_count(logical=False)) + if cores_per_sockets > 1: + pairs["cpuid.corespersocket"] = str(cores_per_sockets) + pairs["memsize"] = str(ram) + VMware.write_vmx_file(self._vmx_path, pairs) log.info("GNS3 VM vCPU count set to {} and RAM amount set to {}".format(vcpus, ram)) except OSError as e: raise GNS3VMError('Could not read/write VMware VMX file "{}": {}'.format(self._vmx_path, e)) From 3daa3f450b2401275d4cbf2b4b6798ef91d77612 Mon Sep 17 00:00:00 2001 From: grossmj Date: Mon, 18 Mar 2019 18:05:40 +0700 Subject: [PATCH 07/11] Fix IOU symlink issue on remote servers. --- gns3server/compute/iou/iou_vm.py | 7 +++++++ gns3server/compute/project.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/gns3server/compute/iou/iou_vm.py b/gns3server/compute/iou/iou_vm.py index 08a43e54..83c5befa 100644 --- a/gns3server/compute/iou/iou_vm.py +++ b/gns3server/compute/iou/iou_vm.py @@ -674,6 +674,13 @@ class IOUVM(BaseNode): pass self._iou_process = None + try: + symlink = os.path.join(self.working_dir, os.path.basename(self.path)) + if os.path.islink(symlink): + os.unlink(symlink) + except OSError as e: + log.warning("Could not delete symbolic link: {}".format(e)) + self._started = False self.save_configs() diff --git a/gns3server/compute/project.py b/gns3server/compute/project.py index 35623986..38b96522 100644 --- a/gns3server/compute/project.py +++ b/gns3server/compute/project.py @@ -407,7 +407,7 @@ class Project: """ files = [] - for dirpath, dirnames, filenames in os.walk(self.path): + for dirpath, dirnames, filenames in os.walk(self.path, followlinks=False): for filename in filenames: if not filename.endswith(".ghost"): path = os.path.relpath(dirpath, self.path) From e291ec1eb902d0ae42e48d375e166efe8af6daf2 Mon Sep 17 00:00:00 2001 From: grossmj Date: Mon, 18 Mar 2019 18:05:40 +0700 Subject: [PATCH 08/11] Fix IOU symlink issue on remote servers. --- gns3server/compute/iou/iou_vm.py | 7 +++++++ gns3server/compute/project.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/gns3server/compute/iou/iou_vm.py b/gns3server/compute/iou/iou_vm.py index 53cfe25d..e4b5e8c0 100644 --- a/gns3server/compute/iou/iou_vm.py +++ b/gns3server/compute/iou/iou_vm.py @@ -662,6 +662,13 @@ class IOUVM(BaseNode): pass self._iou_process = None + try: + symlink = os.path.join(self.working_dir, os.path.basename(self.path)) + if os.path.islink(symlink): + os.unlink(symlink) + except OSError as e: + log.warning("Could not delete symbolic link: {}".format(e)) + self._started = False self.save_configs() diff --git a/gns3server/compute/project.py b/gns3server/compute/project.py index ee2b1c19..f94c0ca3 100644 --- a/gns3server/compute/project.py +++ b/gns3server/compute/project.py @@ -415,7 +415,7 @@ class Project: """ files = [] - for dirpath, dirnames, filenames in os.walk(self.path): + for dirpath, dirnames, filenames in os.walk(self.path, followlinks=False): for filename in filenames: if not filename.endswith(".ghost"): path = os.path.relpath(dirpath, self.path) From dd211bce528135c7d68aa2eae5a7ab286910c34f Mon Sep 17 00:00:00 2001 From: grossmj Date: Mon, 18 Mar 2019 23:29:18 +0700 Subject: [PATCH 09/11] Fix VBoxManage fails if VM has specific special characters in name. Fixes #2739 --- gns3server/compute/virtualbox/__init__.py | 12 +++-- .../compute/virtualbox/virtualbox_vm.py | 46 +++++++++++-------- .../virtualbox/test_virtualbox_manager.py | 7 +-- .../compute/virtualbox/test_virtualbox_vm.py | 1 + 4 files changed, 41 insertions(+), 25 deletions(-) diff --git a/gns3server/compute/virtualbox/__init__.py b/gns3server/compute/virtualbox/__init__.py index daace868..2ae0b729 100644 --- a/gns3server/compute/virtualbox/__init__.py +++ b/gns3server/compute/virtualbox/__init__.py @@ -20,6 +20,7 @@ VirtualBox server module. """ import os +import re import sys import shutil import asyncio @@ -177,14 +178,17 @@ class VirtualBox(BaseManager): for line in result: if len(line) == 0 or line[0] != '"' or line[-1:] != "}": continue # Broken output (perhaps a carriage return in VM name) - vmname, _ = line.rsplit(' ', 1) - vmname = vmname.strip('"') + match = re.search(r"\"(.*)\"\ {(.*)}", line) + if not match: + continue + vmname = match.group(1) + uuid = match.group(2) if vmname == "": continue # ignore inaccessible VMs - extra_data = await self.execute("getextradata", [vmname, "GNS3/Clone"]) + extra_data = await self.execute("getextradata", [uuid, "GNS3/Clone"]) if allow_clone or len(extra_data) == 0 or not extra_data[0].strip() == "Value: yes": # get the amount of RAM - info_results = await self.execute("showvminfo", [vmname, "--machinereadable"]) + info_results = await self.execute("showvminfo", [uuid, "--machinereadable"]) ram = 0 for info in info_results: try: diff --git a/gns3server/compute/virtualbox/virtualbox_vm.py b/gns3server/compute/virtualbox/virtualbox_vm.py index 3c95e0db..9372de08 100644 --- a/gns3server/compute/virtualbox/virtualbox_vm.py +++ b/gns3server/compute/virtualbox/virtualbox_vm.py @@ -57,6 +57,7 @@ class VirtualBoxVM(BaseNode): super().__init__(name, node_id, project, manager, console=console, linked_clone=linked_clone, console_type=console_type) + self._uuid = None # UUID in VirtualBox self._maximum_adapters = 8 self._system_properties = {} self._telnet_server = None @@ -116,7 +117,7 @@ class VirtualBoxVM(BaseNode): :returns: state (string) """ - results = await self.manager.execute("showvminfo", [self._vmname, "--machinereadable"]) + results = await self.manager.execute("showvminfo", [self._uuid, "--machinereadable"]) for info in results: if '=' in info: name, value = info.split('=', 1) @@ -134,7 +135,7 @@ class VirtualBoxVM(BaseNode): """ args = shlex.split(params) - result = await self.manager.execute("controlvm", [self._vmname] + args) + result = await self.manager.execute("controlvm", [self._uuid] + args) return result async def _modify_vm(self, params): @@ -145,7 +146,7 @@ class VirtualBoxVM(BaseNode): """ args = shlex.split(params) - await self.manager.execute("modifyvm", [self._vmname] + args) + await self.manager.execute("modifyvm", [self._uuid] + args) async def _check_duplicate_linked_clone(self): """ @@ -174,6 +175,7 @@ class VirtualBoxVM(BaseNode): await asyncio.sleep(1) async def create(self): + if not self.linked_clone: await self._check_duplicate_linked_clone() @@ -184,21 +186,29 @@ class VirtualBoxVM(BaseNode): raise VirtualBoxError("The VirtualBox API version is lower than 4.3") log.info("VirtualBox VM '{name}' [{id}] created".format(name=self.name, id=self.id)) + vm_info = await self._get_vm_info() + if "memory" in vm_info: + self._ram = int(vm_info["memory"]) + if "UUID" in vm_info: + self._uuid = vm_info["UUID"] + if not self._uuid: + raise VirtualBoxError("Could not find any UUID for VM '{}'".format(self._vmname)) + if self.linked_clone: if self.id and os.path.isdir(os.path.join(self.working_dir, self._vmname)): self._patch_vm_uuid() await self.manager.execute("registervm", [self._linked_vbox_file()]) await self._reattach_linked_hdds() + vm_info = await self._get_vm_info() + self._uuid = vm_info.get("UUID") + if not self._uuid: + raise VirtualBoxError("Could not find any UUID for VM '{}'".format(self._vmname)) else: await self._create_linked_clone() if self._adapters: await self.set_adapters(self._adapters) - vm_info = await self._get_vm_info() - if "memory" in vm_info: - self._ram = int(vm_info["memory"]) - def _linked_vbox_file(self): return os.path.join(self.working_dir, self._vmname, self._vmname + ".vbox") @@ -266,7 +276,7 @@ class VirtualBoxVM(BaseNode): # check if there is enough RAM to run self.check_available_ram(self.ram) - args = [self._vmname] + args = [self._uuid] if self._headless: args.extend(["--type", "headless"]) result = await self.manager.execute("startvm", args) @@ -275,9 +285,9 @@ class VirtualBoxVM(BaseNode): log.debug("Start result: {}".format(result)) # add a guest property to let the VM know about the GNS3 name - await self.manager.execute("guestproperty", ["set", self._vmname, "NameInGNS3", self.name]) + await self.manager.execute("guestproperty", ["set", self._uuid, "NameInGNS3", self.name]) # add a guest property to let the VM know about the GNS3 project directory - await self.manager.execute("guestproperty", ["set", self._vmname, "ProjectDirInGNS3", self.working_dir]) + await self.manager.execute("guestproperty", ["set", self._uuid, "ProjectDirInGNS3", self.working_dir]) await self._start_ubridge() for adapter_number in range(0, self._adapters): @@ -739,7 +749,7 @@ class VirtualBoxVM(BaseNode): """ vm_info = {} - results = await self.manager.execute("showvminfo", [self._vmname, "--machinereadable"]) + results = await self.manager.execute("showvminfo", ["--machinereadable", "--", self._vmname]) # "--" is to protect against vm names containing the "-" character for info in results: try: name, value = info.split('=', 1) @@ -775,7 +785,7 @@ class VirtualBoxVM(BaseNode): # set server mode with a pipe on the first serial port pipe_name = self._get_pipe_name() - args = [self._vmname, "--uartmode1", "server", pipe_name] + args = [self._uuid, "--uartmode1", "server", pipe_name] await self.manager.execute("modifyvm", args) async def _storage_attach(self, params): @@ -786,7 +796,7 @@ class VirtualBoxVM(BaseNode): """ args = shlex.split(params) - await self.manager.execute("storageattach", [self._vmname] + args) + await self.manager.execute("storageattach", [self._uuid] + args) async def _get_nic_attachements(self, maximum_adapters): """ @@ -850,7 +860,7 @@ class VirtualBoxVM(BaseNode): vbox_adapter_type = "82545EM" if adapter_type == "Paravirtualized Network (virtio-net)": vbox_adapter_type = "virtio" - args = [self._vmname, "--nictype{}".format(adapter_number + 1), vbox_adapter_type] + args = [self._uuid, "--nictype{}".format(adapter_number + 1), vbox_adapter_type] await self.manager.execute("modifyvm", args) if isinstance(nio, NIOUDP): @@ -888,10 +898,10 @@ class VirtualBoxVM(BaseNode): gns3_snapshot_exists = True if not gns3_snapshot_exists: - result = await self.manager.execute("snapshot", [self._vmname, "take", "GNS3 Linked Base for clones"]) + result = await self.manager.execute("snapshot", [self._uuid, "take", "GNS3 Linked Base for clones"]) log.debug("GNS3 snapshot created: {}".format(result)) - args = [self._vmname, + args = [self._uuid, "--snapshot", "GNS3 Linked Base for clones", "--options", @@ -906,12 +916,12 @@ class VirtualBoxVM(BaseNode): log.debug("VirtualBox VM: {} cloned".format(result)) self._vmname = self._name - await self.manager.execute("setextradata", [self._vmname, "GNS3/Clone", "yes"]) + await self.manager.execute("setextradata", [self._uuid, "GNS3/Clone", "yes"]) # We create a reset snapshot in order to simplify life of user who want to rollback their VM # Warning: Do not document this it's seem buggy we keep it because Raizo students use it. try: - args = [self._vmname, "take", "reset"] + args = [self._uuid, "take", "reset"] result = await self.manager.execute("snapshot", args) log.debug("Snapshot 'reset' created: {}".format(result)) # It seem sometimes this failed due to internal race condition of Vbox diff --git a/tests/compute/virtualbox/test_virtualbox_manager.py b/tests/compute/virtualbox/test_virtualbox_manager.py index f37f2d9e..a079698f 100644 --- a/tests/compute/virtualbox/test_virtualbox_manager.py +++ b/tests/compute/virtualbox/test_virtualbox_manager.py @@ -74,7 +74,7 @@ def test_vboxmanage_path(manager, tmpdir): def test_list_vms(manager, loop): vm_list = ['"Windows 8.1" {27b4d095-ff5f-4ac4-bb9d-5f2c7861c1f1}', '"Carriage', - 'Return" {27b4d095-ff5f-4ac4-bb9d-5f2c7861c1f1}', + 'Return" {27b4d095-ff5f-4ac4-bb9d-5f2c7861c3f3}', '', '"" {42b4d095-ff5f-4ac4-bb9d-5f2c7861c1f1}', '"Linux Microcore 4.7.1" {ccd8c50b-c172-457d-99fa-dd69371ede0e}'] @@ -83,9 +83,10 @@ def test_list_vms(manager, loop): if cmd == "list": return vm_list else: - if args[0] == "Windows 8.1": + print(args) + if args[0] == "27b4d095-ff5f-4ac4-bb9d-5f2c7861c1f1": return ["memory=512"] - elif args[0] == "Linux Microcore 4.7.1": + elif args[0] == "ccd8c50b-c172-457d-99fa-dd69371ede0e": return ["memory=256"] assert False, "Unknow {} {}".format(cmd, args) diff --git a/tests/compute/virtualbox/test_virtualbox_vm.py b/tests/compute/virtualbox/test_virtualbox_vm.py index e3e44525..4b73463f 100644 --- a/tests/compute/virtualbox/test_virtualbox_vm.py +++ b/tests/compute/virtualbox/test_virtualbox_vm.py @@ -75,6 +75,7 @@ def test_rename_vmname(project, manager, async_run): def test_vm_valid_virtualbox_api_version(loop, project, manager): with asyncio_patch("gns3server.compute.virtualbox.VirtualBox.execute", return_value=["API version: 4_3"]): vm = VirtualBoxVM("test", "00010203-0405-0607-0809-0a0b0c0d0e0f", project, manager, "test", False) + vm._uuid = "00010203-0405-0607-0809-0a0b0c0d0e0f" loop.run_until_complete(asyncio.ensure_future(vm.create())) From 1f1d93d078366dbc52a14b6d6e913a58e8c3c5f3 Mon Sep 17 00:00:00 2001 From: grossmj Date: Wed, 20 Mar 2019 16:23:30 +0800 Subject: [PATCH 10/11] Deactivate the embedded shell for Ethernet switch. Ref #1424 #1556 --- .../compute/dynamips/nodes/ethernet_switch.py | 86 +++++++++---------- gns3server/utils/asyncio/embed_shell.py | 6 +- gns3server/web/web_server.py | 3 + requirements.txt | 1 - 4 files changed, 49 insertions(+), 47 deletions(-) diff --git a/gns3server/compute/dynamips/nodes/ethernet_switch.py b/gns3server/compute/dynamips/nodes/ethernet_switch.py index 09a6ad21..cabf96a9 100644 --- a/gns3server/compute/dynamips/nodes/ethernet_switch.py +++ b/gns3server/compute/dynamips/nodes/ethernet_switch.py @@ -22,7 +22,7 @@ http://github.com/GNS3/dynamips/blob/master/README.hypervisor#L558 import asyncio from gns3server.utils import parse_version -from gns3server.utils.asyncio.embed_shell import EmbedShell, create_telnet_shell +#from gns3server.utils.asyncio.embed_shell import EmbedShell, create_telnet_shell from .device import Device @@ -34,36 +34,36 @@ import logging log = logging.getLogger(__name__) -class EthernetSwitchConsole(EmbedShell): - """ - Console for the ethernet switch - """ - - def __init__(self, node): - super().__init__(welcome_message="Welcome to GNS3 builtin Ethernet switch.\n\nType help for available commands\n") - self._node = node - - async def mac(self): - """ - Show MAC address table - """ - res = 'Port Mac VLAN\n' - result = (await self._node._hypervisor.send('ethsw show_mac_addr_table {}'.format(self._node.name))) - for line in result: - mac, vlan, nio = line.replace(' ', ' ').split(' ') - mac = mac.replace('.', '') - mac = "{}:{}:{}:{}:{}:{}".format( - mac[0:2], - mac[2:4], - mac[4:6], - mac[6:8], - mac[8:10], - mac[10:12]) - for port_number, switch_nio in self._node.nios.items(): - if switch_nio.name == nio: - res += 'Ethernet' + str(port_number) + ' ' + mac + ' ' + vlan + '\n' - break - return res +# class EthernetSwitchConsole(EmbedShell): +# """ +# Console for the ethernet switch +# """ +# +# def __init__(self, node): +# super().__init__(welcome_message="Welcome to GNS3 builtin Ethernet switch.\n\nType help for available commands\n") +# self._node = node +# +# async def mac(self): +# """ +# Show MAC address table +# """ +# res = 'Port Mac VLAN\n' +# result = (await self._node._hypervisor.send('ethsw show_mac_addr_table {}'.format(self._node.name))) +# for line in result: +# mac, vlan, nio = line.replace(' ', ' ').split(' ') +# mac = mac.replace('.', '') +# mac = "{}:{}:{}:{}:{}:{}".format( +# mac[0:2], +# mac[2:4], +# mac[4:6], +# mac[6:8], +# mac[8:10], +# mac[10:12]) +# for port_number, switch_nio in self._node.nios.items(): +# if switch_nio.name == nio: +# res += 'Ethernet' + str(port_number) + ' ' + mac + ' ' + vlan + '\n' +# break +# return res class EthernetSwitch(Device): @@ -85,8 +85,8 @@ class EthernetSwitch(Device): self._nios = {} self._mappings = {} self._telnet_console = None - self._telnet_shell = None - self._telnet_server = None + #self._telnet_shell = None + #self._telnet_server = None self._console = console self._console_type = console_type @@ -177,13 +177,13 @@ class EthernetSwitch(Device): await self._hypervisor.send('ethsw create "{}"'.format(self._name)) log.info('Ethernet switch "{name}" [{id}] has been created'.format(name=self._name, id=self._id)) - self._telnet_shell = EthernetSwitchConsole(self) - self._telnet_shell.prompt = self._name + '> ' - self._telnet = create_telnet_shell(self._telnet_shell) - try: - self._telnet_server = (await asyncio.start_server(self._telnet.run, self._manager.port_manager.console_host, self.console)) - except OSError as e: - self.project.emit("log.warning", {"message": "Could not start Telnet server on socket {}:{}: {}".format(self._manager.port_manager.console_host, self.console, e)}) + #self._telnet_shell = EthernetSwitchConsole(self) + #self._telnet_shell.prompt = self._name + '> ' + #self._telnet = create_telnet_shell(self._telnet_shell) + #try: + # self._telnet_server = (await asyncio.start_server(self._telnet.run, self._manager.port_manager.console_host, self.console)) + #except OSError as e: + # self.project.emit("log.warning", {"message": "Could not start Telnet server on socket {}:{}: {}".format(self._manager.port_manager.console_host, self.console, e)}) self._hypervisor.devices.append(self) async def set_name(self, new_name): @@ -227,9 +227,9 @@ class EthernetSwitch(Device): Deletes this Ethernet switch. """ - await self._telnet.close() - if self._telnet_server: - self._telnet_server.close() + #await self._telnet.close() + #if self._telnet_server: + # self._telnet_server.close() for nio in self._nios.values(): if nio: diff --git a/gns3server/utils/asyncio/embed_shell.py b/gns3server/utils/asyncio/embed_shell.py index 156e6979..322777ad 100644 --- a/gns3server/utils/asyncio/embed_shell.py +++ b/gns3server/utils/asyncio/embed_shell.py @@ -32,8 +32,8 @@ from prompt_toolkit.shortcuts import create_prompt_application, create_asyncio_e from prompt_toolkit.terminal.vt100_output import Vt100_Output from prompt_toolkit.input import StdinInput -from .telnet_server import AsyncioTelnetServer, TelnetConnection -from .input_stream import InputStream +from gns3server.utils.asyncio.telnet_server import AsyncioTelnetServer, TelnetConnection +from gns3server.utils.asyncio.input_stream import InputStream class EmbedShell: @@ -344,7 +344,7 @@ if __name__ == '__main__': else: return 'world\n' - return (await world()) + return await world() # Demo using telnet shell = Demo(welcome_message="Welcome!\n") diff --git a/gns3server/web/web_server.py b/gns3server/web/web_server.py index 9b7c6e19..3cf37849 100644 --- a/gns3server/web/web_server.py +++ b/gns3server/web/web_server.py @@ -181,6 +181,9 @@ class WebServer: return ssl_context async def start_shell(self): + + log.error("The embedded shell has been deactivated in this version of GNS3") + return try: from ptpython.repl import embed except ImportError: diff --git a/requirements.txt b/requirements.txt index 207656e1..343dd0f7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,6 +6,5 @@ async_generator>=1.10 Jinja2>=2.7.3 raven>=5.23.0 psutil>=3.0.0 -prompt-toolkit==1.0.15 async-timeout==3.0.1 distro>=1.3.0 From 343f223a8362eb4db168f4126ee289b22581599b Mon Sep 17 00:00:00 2001 From: grossmj Date: Wed, 20 Mar 2019 17:04:02 +0800 Subject: [PATCH 11/11] Fix tests after deactivating the embedded shell for Ethernet switch. Ref #1424 #1556 --- .../compute/dynamips/test_ethernet_switch.py | 16 ++-- tests/utils/asyncio/test_embed_shell.py | 84 +++++++++---------- 2 files changed, 50 insertions(+), 50 deletions(-) diff --git a/tests/compute/dynamips/test_ethernet_switch.py b/tests/compute/dynamips/test_ethernet_switch.py index 47e7c361..dd0d5551 100644 --- a/tests/compute/dynamips/test_ethernet_switch.py +++ b/tests/compute/dynamips/test_ethernet_switch.py @@ -16,7 +16,7 @@ # along with this program. If not, see . from tests.utils import AsyncioMagicMock -from gns3server.compute.dynamips.nodes.ethernet_switch import EthernetSwitchConsole +#from gns3server.compute.dynamips.nodes.ethernet_switch import EthernetSwitchConsole from gns3server.compute.nios.nio_udp import NIOUDP @@ -28,10 +28,10 @@ def test_mac_command(async_run): node.nios[0].name = "Ethernet0" node.nios[1] = NIOUDP(55, "127.0.0.1", 56) node.nios[1].name = "Ethernet1" - node._hypervisor.send = AsyncioMagicMock(return_value=["0050.7966.6801 1 Ethernet0", "0050.7966.6802 1 Ethernet1"]) - console = EthernetSwitchConsole(node) - assert async_run(console.mac()) == \ - "Port Mac VLAN\n" \ - "Ethernet0 00:50:79:66:68:01 1\n" \ - "Ethernet1 00:50:79:66:68:02 1\n" - node._hypervisor.send.assert_called_with("ethsw show_mac_addr_table Test") + #node._hypervisor.send = AsyncioMagicMock(return_value=["0050.7966.6801 1 Ethernet0", "0050.7966.6802 1 Ethernet1"]) + #console = EthernetSwitchConsole(node) + #assert async_run(console.mac()) == \ + # "Port Mac VLAN\n" \ + # "Ethernet0 00:50:79:66:68:01 1\n" \ + # "Ethernet1 00:50:79:66:68:02 1\n" + #node._hypervisor.send.assert_called_with("ethsw show_mac_addr_table Test") diff --git a/tests/utils/asyncio/test_embed_shell.py b/tests/utils/asyncio/test_embed_shell.py index 3f53c472..706292c3 100644 --- a/tests/utils/asyncio/test_embed_shell.py +++ b/tests/utils/asyncio/test_embed_shell.py @@ -17,7 +17,7 @@ import asyncio -from gns3server.utils.asyncio.embed_shell import EmbedShell +#from gns3server.utils.asyncio.embed_shell import EmbedShell #FIXME: this is broken with recent Python >= 3.6 # def test_embed_shell_help(async_run): @@ -39,44 +39,44 @@ from gns3server.utils.asyncio.embed_shell import EmbedShell # assert async_run(app._parse_command('? hello')) == 'hello: The hello world function\n\nThe hello usage\n' -def test_embed_shell_execute(async_run): - class Application(EmbedShell): - - async def hello(self): - """ - The hello world function - - The hello usage - """ - return 'world' - reader = asyncio.StreamReader() - writer = asyncio.StreamReader() - app = Application(reader, writer) - assert async_run(app._parse_command('hello')) == 'world' - - -def test_embed_shell_welcome(async_run, loop): - reader = asyncio.StreamReader() - writer = asyncio.StreamReader() - app = EmbedShell(reader, writer, welcome_message="Hello") - task = loop.create_task(app.run()) - assert async_run(writer.read(5)) == b"Hello" - task.cancel() - try: - loop.run_until_complete(task) - except asyncio.CancelledError: - pass - - -def test_embed_shell_prompt(async_run, loop): - reader = asyncio.StreamReader() - writer = asyncio.StreamReader() - app = EmbedShell(reader, writer) - app.prompt = "gbash# " - task = loop.create_task(app.run()) - assert async_run(writer.read(7)) == b"gbash# " - task.cancel() - try: - loop.run_until_complete(task) - except asyncio.CancelledError: - pass +# def test_embed_shell_execute(async_run): +# class Application(EmbedShell): +# +# async def hello(self): +# """ +# The hello world function +# +# The hello usage +# """ +# return 'world' +# reader = asyncio.StreamReader() +# writer = asyncio.StreamReader() +# app = Application(reader, writer) +# assert async_run(app._parse_command('hello')) == 'world' +# +# +# def test_embed_shell_welcome(async_run, loop): +# reader = asyncio.StreamReader() +# writer = asyncio.StreamReader() +# app = EmbedShell(reader, writer, welcome_message="Hello") +# task = loop.create_task(app.run()) +# assert async_run(writer.read(5)) == b"Hello" +# task.cancel() +# try: +# loop.run_until_complete(task) +# except asyncio.CancelledError: +# pass +# +# +# def test_embed_shell_prompt(async_run, loop): +# reader = asyncio.StreamReader() +# writer = asyncio.StreamReader() +# app = EmbedShell(reader, writer) +# app.prompt = "gbash# " +# task = loop.create_task(app.run()) +# assert async_run(writer.read(7)) == b"gbash# " +# task.cancel() +# try: +# loop.run_until_complete(task) +# except asyncio.CancelledError: +# pass