From fd92189d5181b8b4a2071331d0ee2f29e06d58b0 Mon Sep 17 00:00:00 2001 From: grossmj Date: Wed, 19 Nov 2014 10:22:09 -0700 Subject: [PATCH 01/16] Restore dock widgets. --- gns3server/modules/virtualbox/virtualbox_vm.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gns3server/modules/virtualbox/virtualbox_vm.py b/gns3server/modules/virtualbox/virtualbox_vm.py index 1a3597c1..96b03216 100644 --- a/gns3server/modules/virtualbox/virtualbox_vm.py +++ b/gns3server/modules/virtualbox/virtualbox_vm.py @@ -322,8 +322,8 @@ class VirtualBoxVM(object): self._allocated_console_ports.remove(self.console) if self._linked_clone: + hdd_table = [] if os.path.exists(self._working_dir): - hdd_table = [] hdd_files = self._get_all_hdd_files() vm_info = self._get_vm_info() for entry, value in vm_info.items(): From 95f5c73e33b9a31a0c51127a3b96765026ba4979 Mon Sep 17 00:00:00 2001 From: Jeremy Date: Wed, 19 Nov 2014 19:28:21 -0700 Subject: [PATCH 02/16] Bump to version 1.2 --- gns3server/version.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gns3server/version.py b/gns3server/version.py index 579a7caa..8c007afb 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.2.dev3" -__version_info__ = (1, 2, 0, 99) +__version__ = "1.2" +__version_info__ = (1, 2, 0, 0) From 3bd88178a095622c00432e6ebf5974f59e25c0f4 Mon Sep 17 00:00:00 2001 From: Jeremy Date: Thu, 20 Nov 2014 19:01:00 -0700 Subject: [PATCH 03/16] Bump to version 1.2.1.dev1 and fixes vboxmanage lookup on Windows. --- gns3server/version.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gns3server/version.py b/gns3server/version.py index 8c007afb..67180411 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.2" -__version_info__ = (1, 2, 0, 0) +__version__ = "1.2.1.dev1" +__version_info__ = (1, 2, 1, 99) From 09948a366f6c31caa216ebe6c57769f5a68303c5 Mon Sep 17 00:00:00 2001 From: Jeremy Date: Sat, 22 Nov 2014 17:45:04 -0700 Subject: [PATCH 04/16] Use SubprocessError to catch Subprocess exceptions. --- gns3server/modules/dynamips/hypervisor.py | 2 +- gns3server/modules/iou/iou_device.py | 8 ++++---- gns3server/modules/qemu/__init__.py | 2 +- gns3server/modules/qemu/qemu_vm.py | 6 +++--- gns3server/modules/virtualbox/__init__.py | 4 +--- gns3server/modules/virtualbox/virtualbox_vm.py | 4 ++-- gns3server/modules/vpcs/vpcs_device.py | 4 ++-- 7 files changed, 14 insertions(+), 16 deletions(-) diff --git a/gns3server/modules/dynamips/hypervisor.py b/gns3server/modules/dynamips/hypervisor.py index 20e49741..ac9af618 100644 --- a/gns3server/modules/dynamips/hypervisor.py +++ b/gns3server/modules/dynamips/hypervisor.py @@ -212,7 +212,7 @@ class Hypervisor(DynamipsHypervisor): cwd=self._working_dir) log.info("Dynamips started PID={}".format(self._process.pid)) self._started = True - except OSError as e: + except subprocess.SubprocessError as e: log.error("could not start Dynamips: {}".format(e)) raise DynamipsError("could not start Dynamips: {}".format(e)) diff --git a/gns3server/modules/iou/iou_device.py b/gns3server/modules/iou/iou_device.py index d12a7b45..0988f226 100644 --- a/gns3server/modules/iou/iou_device.py +++ b/gns3server/modules/iou/iou_device.py @@ -509,7 +509,7 @@ class IOUDevice(object): cwd=self._working_dir) log.info("iouyap started PID={}".format(self._iouyap_process.pid)) - except OSError as e: + except subprocess.SubprocessError as e: iouyap_stdout = self.read_iouyap_stdout() log.error("could not start iouyap: {}\n{}".format(e, iouyap_stdout)) raise IOUError("Could not start iouyap: {}\n{}".format(e, iouyap_stdout)) @@ -521,7 +521,7 @@ class IOUDevice(object): try: output = subprocess.check_output(["ldd", self._path]) - except (FileNotFoundError, subprocess.CalledProcessError) as e: + except (FileNotFoundError, subprocess.SubprocessError) as e: log.warn("could not determine the shared library dependencies for {}: {}".format(self._path, e)) return @@ -583,7 +583,7 @@ class IOUDevice(object): self._started = True except FileNotFoundError as e: raise IOUError("could not start IOU: {}: 32-bit binary support is probably not installed".format(e)) - except OSError as e: + except subprocess.SubprocessError as e: iou_stdout = self.read_iou_stdout() log.error("could not start IOU {}: {}\n{}".format(self._path, e, iou_stdout)) raise IOUError("could not start IOU {}: {}\n{}".format(self._path, e, iou_stdout)) @@ -761,7 +761,7 @@ class IOUDevice(object): command.extend(["-l"]) else: raise IOUError("layer 1 keepalive messages are not supported by {}".format(os.path.basename(self._path))) - except (OSError, subprocess.CalledProcessError) as e: + except subprocess.SubprocessError as e: log.warn("could not determine if layer 1 keepalive messages are supported by {}: {}".format(os.path.basename(self._path), e)) def _build_command(self): diff --git a/gns3server/modules/qemu/__init__.py b/gns3server/modules/qemu/__init__.py index 37998cdc..77334270 100644 --- a/gns3server/modules/qemu/__init__.py +++ b/gns3server/modules/qemu/__init__.py @@ -608,7 +608,7 @@ class Qemu(IModule): return version else: raise QemuError("Could not determine the Qemu version for {}".format(qemu_path)) - except (OSError, subprocess.CalledProcessError) as e: + except subprocess.SubprocessError as e: raise QemuError("Error while looking for the Qemu version: {}".format(e)) @IModule.route("qemu.qemu_list") diff --git a/gns3server/modules/qemu/qemu_vm.py b/gns3server/modules/qemu/qemu_vm.py index ce1242c8..d5009fd4 100644 --- a/gns3server/modules/qemu/qemu_vm.py +++ b/gns3server/modules/qemu/qemu_vm.py @@ -612,7 +612,7 @@ class QemuVM(object): cwd=self._working_dir) log.info("QEMU VM instance {} started PID={}".format(self._id, self._process.pid)) self._started = True - except OSError as e: + except subprocess.SubprocessError as e: stdout = self.read_stdout() log.error("could not start QEMU {}: {}\n{}".format(self._qemu_path, e, stdout)) raise QemuError("could not start QEMU {}: {}\n{}".format(self._qemu_path, e, stdout)) @@ -782,7 +782,7 @@ class QemuVM(object): retcode = subprocess.call([qemu_img_path, "create", "-f", "qcow2", hda_disk, "128M"]) log.info("{} returned with {}".format(qemu_img_path, retcode)) - except OSError as e: + except subprocess.SubprocessError as e: raise QemuError("Could not create disk image {}".format(e)) options.extend(["-hda", hda_disk]) @@ -794,7 +794,7 @@ class QemuVM(object): "backing_file={}".format(self._hdb_disk_image), "-f", "qcow2", hdb_disk]) log.info("{} returned with {}".format(qemu_img_path, retcode)) - except OSError as e: + except subprocess.SubprocessError as e: raise QemuError("Could not create disk image {}".format(e)) options.extend(["-hdb", hdb_disk]) diff --git a/gns3server/modules/virtualbox/__init__.py b/gns3server/modules/virtualbox/__init__.py index 649b4022..e32a301b 100644 --- a/gns3server/modules/virtualbox/__init__.py +++ b/gns3server/modules/virtualbox/__init__.py @@ -717,10 +717,8 @@ class VirtualBox(IModule): try: result = subprocess.check_output(command, stderr=subprocess.STDOUT, universal_newlines=True, timeout=30) - except subprocess.CalledProcessError as e: + except subprocess.SubprocessError as e: raise VirtualBoxError("Could not execute VBoxManage {}".format(e)) - except subprocess.TimeoutExpired: - raise VirtualBoxError("VBoxManage has timed out") return result @IModule.route("virtualbox.vm_list") diff --git a/gns3server/modules/virtualbox/virtualbox_vm.py b/gns3server/modules/virtualbox/virtualbox_vm.py index 96b03216..4503fcae 100644 --- a/gns3server/modules/virtualbox/virtualbox_vm.py +++ b/gns3server/modules/virtualbox/virtualbox_vm.py @@ -558,8 +558,8 @@ class VirtualBoxVM(object): raise VirtualBoxError("{}".format(virtualbox_error)) else: raise VirtualBoxError("{}".format(e)) - except subprocess.TimeoutExpired: - raise VirtualBoxError("VBoxManage has timed out") + except subprocess.SubprocessError as e: + raise VirtualBoxError("Could not execute VBoxManage: {}".format(e)) return result.splitlines() def _get_vm_info(self): diff --git a/gns3server/modules/vpcs/vpcs_device.py b/gns3server/modules/vpcs/vpcs_device.py index cd89b00e..377e9bf0 100644 --- a/gns3server/modules/vpcs/vpcs_device.py +++ b/gns3server/modules/vpcs/vpcs_device.py @@ -346,7 +346,7 @@ class VPCSDevice(object): raise VPCSError("VPCS executable version must be >= 0.5b1") else: raise VPCSError("Could not determine the VPCS version for {}".format(self._path)) - except (OSError, subprocess.CalledProcessError) as e: + except subprocess.SubprocessError as e: raise VPCSError("Error while looking for the VPCS version: {}".format(e)) def start(self): @@ -386,7 +386,7 @@ class VPCSDevice(object): creationflags=flags) log.info("VPCS instance {} started PID={}".format(self._id, self._process.pid)) self._started = True - except OSError as e: + except subprocess.SubprocessError as e: vpcs_stdout = self.read_vpcs_stdout() log.error("could not start VPCS {}: {}\n{}".format(self._path, e, vpcs_stdout)) raise VPCSError("could not start VPCS {}: {}\n{}".format(self._path, e, vpcs_stdout)) From 6ef614103eb17571b98b2b7d694bc38aa59a3add Mon Sep 17 00:00:00 2001 From: Jeremy Date: Mon, 24 Nov 2014 11:15:30 -0700 Subject: [PATCH 05/16] Ignore inaccessible VirtualBox VMs. --- gns3server/modules/virtualbox/__init__.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/gns3server/modules/virtualbox/__init__.py b/gns3server/modules/virtualbox/__init__.py index e32a301b..644c4d25 100644 --- a/gns3server/modules/virtualbox/__init__.py +++ b/gns3server/modules/virtualbox/__init__.py @@ -751,7 +751,13 @@ class VirtualBox(IModule): for line in result.splitlines(): vmname, uuid = line.rsplit(' ', 1) vmname = vmname.strip('"') - extra_data = self._execute_vboxmanage([vboxmanage_path, "getextradata", vmname, "GNS3/Clone"]).strip() + if vmname == "": + continue # ignore inaccessible VMs + try: + extra_data = self._execute_vboxmanage([vboxmanage_path, "getextradata", vmname, "GNS3/Clone"]).strip() + except VirtualBoxError as e: + self.send_custom_error(str(e)) + return if not extra_data == "Value: yes": vms.append(vmname) From 4918675cd51c0df4d791ee57bd6554f4ff396d2d Mon Sep 17 00:00:00 2001 From: Jeremy Date: Mon, 24 Nov 2014 11:44:27 -0700 Subject: [PATCH 06/16] Fixes Qemu version detection. --- gns3server/modules/qemu/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gns3server/modules/qemu/__init__.py b/gns3server/modules/qemu/__init__.py index 77334270..aaf36352 100644 --- a/gns3server/modules/qemu/__init__.py +++ b/gns3server/modules/qemu/__init__.py @@ -601,8 +601,8 @@ class Qemu(IModule): if sys.platform.startswith("win"): return "" try: - output = subprocess.check_output([qemu_path, "--version"]) - match = re.search("QEMU emulator version ([0-9a-z\-\.]+)", output.decode("utf-8")) + output = subprocess.check_output([qemu_path, "-version"]) + match = re.search("version\s+([0-9a-z\-\.]+)", output.decode("utf-8")) if match: version = match.group(1) return version From d97ba1172853d7ec92144b6683dcfc0facca25d6 Mon Sep 17 00:00:00 2001 From: Jeremy Date: Mon, 24 Nov 2014 17:02:00 -0700 Subject: [PATCH 07/16] Fixes C7200 IO cards insert/remove issues and makes C7200-IO-FE the default. --- gns3server/modules/dynamips/backends/vm.py | 6 +++--- gns3server/modules/dynamips/nodes/c7200.py | 4 ++-- gns3server/modules/dynamips/nodes/router.py | 4 ++-- gns3server/server.py | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/gns3server/modules/dynamips/backends/vm.py b/gns3server/modules/dynamips/backends/vm.py index f0834809..e43767c9 100644 --- a/gns3server/modules/dynamips/backends/vm.py +++ b/gns3server/modules/dynamips/backends/vm.py @@ -73,8 +73,8 @@ import logging log = logging.getLogger(__name__) -ADAPTER_MATRIX = {"C7200_IO_2FE": C7200_IO_2FE, - "C7200_IO_FE": C7200_IO_FE, +ADAPTER_MATRIX = {"C7200-IO-2FE": C7200_IO_2FE, + "C7200-IO-FE": C7200_IO_FE, "C7200-IO-GE-E": C7200_IO_GE_E, "NM-16ESW": NM_16ESW, "NM-1E": NM_1E, @@ -468,7 +468,7 @@ class VM(object): except DynamipsError as e: self.send_custom_error(str(e)) return - elif name.startswith("slot") and value == None: + elif name.startswith("slot") and value is None: slot_id = int(name[-1]) if router.slots[slot_id]: try: diff --git a/gns3server/modules/dynamips/nodes/c7200.py b/gns3server/modules/dynamips/nodes/c7200.py index 0dd7127b..a473f954 100644 --- a/gns3server/modules/dynamips/nodes/c7200.py +++ b/gns3server/modules/dynamips/nodes/c7200.py @@ -22,7 +22,7 @@ http://github.com/GNS3/dynamips/blob/master/README.hypervisor#L294 from ..dynamips_error import DynamipsError from .router import Router -from ..adapters.c7200_io_2fe import C7200_IO_2FE +from ..adapters.c7200_io_fe import C7200_IO_FE from ..adapters.c7200_io_ge_e import C7200_IO_GE_E import logging @@ -70,7 +70,7 @@ class C7200(Router): if npe == "npe-g2": self.slot_add_binding(0, C7200_IO_GE_E()) else: - self.slot_add_binding(0, C7200_IO_2FE()) + self.slot_add_binding(0, C7200_IO_FE()) def defaults(self): """ diff --git a/gns3server/modules/dynamips/nodes/router.py b/gns3server/modules/dynamips/nodes/router.py index ea32ac12..eb665d7f 100644 --- a/gns3server/modules/dynamips/nodes/router.py +++ b/gns3server/modules/dynamips/nodes/router.py @@ -1324,7 +1324,7 @@ class Router(object): adapter=current_adapter)) # Only c7200, c3600 and c3745 (NM-4T only) support new adapter while running - if self.is_running() and not (self._platform == 'c7200' + if self.is_running() and not ((self._platform == 'c7200' and not str(adapter).startswith('C7200')) and not (self._platform == 'c3600' and self.chassis == '3660') and not (self._platform == 'c3745' and adapter == 'NM-4T')): raise DynamipsError("Adapter {adapter} cannot be added while router {name} is running".format(adapter=adapter, @@ -1369,7 +1369,7 @@ class Router(object): slot_id=slot_id)) # Only c7200, c3600 and c3745 (NM-4T only) support to remove adapter while running - if self.is_running() and not (self._platform == 'c7200' + if self.is_running() and not ((self._platform == 'c7200' and not str(adapter).startswith('C7200')) and not (self._platform == 'c3600' and self.chassis == '3660') and not (self._platform == 'c3745' and adapter == 'NM-4T')): raise DynamipsError("Adapter {adapter} cannot be removed while router {name} is running".format(adapter=adapter, diff --git a/gns3server/server.py b/gns3server/server.py index e178c9f9..5d748dc2 100644 --- a/gns3server/server.py +++ b/gns3server/server.py @@ -184,7 +184,6 @@ class Server(object): except KeyError: log.info("Missing cloud.conf - disabling HTTP auth and SSL") - router = self._create_zmq_router() # Add our JSON-RPC Websocket handler to Tornado self.handlers.extend([(r"/", JSONRPCWebSocket, dict(zmq_router=router))]) @@ -208,6 +207,7 @@ class Server(object): if parse_version(tornado.version) >= parse_version("3.1"): kwargs["max_buffer_size"] = 524288000 # 500 MB file upload limit + tornado_app.listen(self._port, **kwargs) except OSError as e: if e.errno == errno.EADDRINUSE: # socket already in use From 183a6aed448423f59f3cb2c19137286e275b57e2 Mon Sep 17 00:00:00 2001 From: Jeremy Date: Wed, 26 Nov 2014 15:07:15 -0700 Subject: [PATCH 08/16] Do not use universal_newlines in subprocess. --- gns3server/modules/virtualbox/__init__.py | 4 ++-- gns3server/modules/virtualbox/virtualbox_vm.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/gns3server/modules/virtualbox/__init__.py b/gns3server/modules/virtualbox/__init__.py index 644c4d25..fde9784b 100644 --- a/gns3server/modules/virtualbox/__init__.py +++ b/gns3server/modules/virtualbox/__init__.py @@ -716,10 +716,10 @@ class VirtualBox(IModule): """ try: - result = subprocess.check_output(command, stderr=subprocess.STDOUT, universal_newlines=True, timeout=30) + result = subprocess.check_output(command, stderr=subprocess.STDOUT, timeout=30) except subprocess.SubprocessError as e: raise VirtualBoxError("Could not execute VBoxManage {}".format(e)) - return result + return result.decode("utf-8") @IModule.route("virtualbox.vm_list") def vm_list(self, request): diff --git a/gns3server/modules/virtualbox/virtualbox_vm.py b/gns3server/modules/virtualbox/virtualbox_vm.py index 4503fcae..cf1dc3a6 100644 --- a/gns3server/modules/virtualbox/virtualbox_vm.py +++ b/gns3server/modules/virtualbox/virtualbox_vm.py @@ -550,17 +550,17 @@ class VirtualBoxVM(object): command.extend(args) log.debug("Execute vboxmanage command: {}".format(command)) try: - result = subprocess.check_output(command, stderr=subprocess.STDOUT, universal_newlines=True, timeout=timeout) + result = subprocess.check_output(command, stderr=subprocess.STDOUT, timeout=timeout) except subprocess.CalledProcessError as e: if e.output: # only the first line of the output is useful - virtualbox_error = e.output.splitlines()[0] + virtualbox_error = e.output.decode("utf-8").splitlines()[0] raise VirtualBoxError("{}".format(virtualbox_error)) else: raise VirtualBoxError("{}".format(e)) except subprocess.SubprocessError as e: raise VirtualBoxError("Could not execute VBoxManage: {}".format(e)) - return result.splitlines() + return result.decode("utf-8").splitlines() def _get_vm_info(self): """ From 632134a02ae3ebd67f6b123d137805bb97b97501 Mon Sep 17 00:00:00 2001 From: grossmj Date: Sat, 29 Nov 2014 14:11:51 -0700 Subject: [PATCH 09/16] Support for older Qemu versions like the 0.11.0 on Windows. --- gns3server/modules/qemu/__init__.py | 4 +- gns3server/modules/qemu/qemu_vm.py | 74 +++++++++++++++++++++++++---- gns3server/modules/qemu/schemas.py | 4 ++ 3 files changed, 72 insertions(+), 10 deletions(-) diff --git a/gns3server/modules/qemu/__init__.py b/gns3server/modules/qemu/__init__.py index aaf36352..18c005ee 100644 --- a/gns3server/modules/qemu/__init__.py +++ b/gns3server/modules/qemu/__init__.py @@ -626,6 +626,7 @@ class Qemu(IModule): if sys.platform.startswith("win"): # add specific Windows paths paths.append(os.path.join(os.getcwd(), "qemu")) + paths.append(os.path.join(os.getcwd(), "qemu-0.11.0")) if "PROGRAMFILES(X86)" in os.environ and os.path.exists(os.environ["PROGRAMFILES(X86)"]): paths.append(os.path.join(os.environ["PROGRAMFILES(X86)"], "qemu")) if "PROGRAMFILES" in os.environ and os.path.exists(os.environ["PROGRAMFILES"]): @@ -640,8 +641,7 @@ class Qemu(IModule): qemu_path = os.path.join(path, f) version = self._get_qemu_version(qemu_path) qemus.append({"path": qemu_path, "version": version}) - except (OSError, QemuError) as e: - log.warn("Could not find QEMU version for {}: {}".format(path, e)) + except OSError: continue response = {"qemus": qemus} diff --git a/gns3server/modules/qemu/qemu_vm.py b/gns3server/modules/qemu/qemu_vm.py index d5009fd4..bbe80c4b 100644 --- a/gns3server/modules/qemu/qemu_vm.py +++ b/gns3server/modules/qemu/qemu_vm.py @@ -19,6 +19,7 @@ QEMU VM instance. """ +import sys import os import shutil import random @@ -107,6 +108,7 @@ class QemuVM(object): self._initrd = "" self._kernel_image = "" self._kernel_command_line = "" + self._legacy_networking = False working_dir_path = os.path.join(working_dir, "qemu", "vm-{}".format(self._id)) @@ -152,7 +154,8 @@ class QemuVM(object): "console": self._console, "initrd": self._initrd, "kernel_image": self._kernel_image, - "kernel_command_line": self._kernel_command_line} + "kernel_command_line": self._kernel_command_line, + "legacy_networking": self._legacy_networking} return qemu_defaults @@ -437,6 +440,30 @@ class QemuVM(object): id=self._id, adapter_type=adapter_type)) + @property + def legacy_networking(self): + """ + Returns either QEMU legacy networking commands are used. + + :returns: boolean + """ + + return self._legacy_networking + + @legacy_networking.setter + def legacy_networking(self, legacy_networking): + """ + Sets either QEMU legacy networking commands are used. + + :param legacy_networking: boolean + """ + + if legacy_networking: + log.info("QEMU VM {name} [id={id}] has enabled legacy networking".format(name=self._name, id=self._id)) + else: + log.info("QEMU VM {name} [id={id}] has disabled legacy networking".format(name=self._name, id=self._id)) + self._legacy_networking = legacy_networking + @property def ram(self): """ @@ -617,6 +644,24 @@ class QemuVM(object): log.error("could not start QEMU {}: {}\n{}".format(self._qemu_path, e, stdout)) raise QemuError("could not start QEMU {}: {}\n{}".format(self._qemu_path, e, stdout)) + # change the process priority + if sys.platform.startswith("win"): + try: + import win32api + import win32con + import win32process + except ImportError: + log.error("pywin32 must be installed to change the priority class for QEMU VM {}".format(self._name)) + else: + log.info("setting QEMU VM {} priority class to BELOW_NORMAL".format(self._name)) + handle = win32api.OpenProcess(win32con.PROCESS_ALL_ACCESS, 0, self._process.pid) + win32process.SetPriorityClass(handle, win32process.BELOW_NORMAL_PRIORITY_CLASS) + else: + try: + subprocess.call(['renice', '-n', '19', '-p', str(self._process.pid)]) + except subprocess.SubprocessError as e: + log.error("could not change process priority for QEMU VM {}: {}".format(self._name, e)) + def stop(self): """ Stops this QEMU VM. @@ -819,16 +864,29 @@ class QemuVM(object): for adapter in self._ethernet_adapters: #TODO: let users specify a base mac address mac = "00:00:ab:%02x:%02x:%02d" % (random.randint(0x00, 0xff), random.randint(0x00, 0xff), adapter_id) - network_options.extend(["-device", "{},mac={},netdev=gns3-{}".format(self._adapter_type, mac, adapter_id)]) + if self._legacy_networking: + network_options.extend(["-net", "nic,vlan={},macaddr={},model={}".format(adapter_id, mac, self._adapter_type)]) + else: + network_options.extend(["-device", "{},mac={},netdev=gns3-{}".format(self._adapter_type, mac, adapter_id)]) nio = adapter.get_nio(0) if nio and isinstance(nio, NIO_UDP): - network_options.extend(["-netdev", "socket,id=gns3-{},udp={}:{},localaddr={}:{}".format(adapter_id, - nio.rhost, - nio.rport, - self._host, - nio.lport)]) + if self._legacy_networking: + network_options.extend(["-net", "udp,vlan={},sport={},dport={},daddr={}".format(adapter_id, + nio.lport, + nio.rport, + nio.rhost)]) + + else: + network_options.extend(["-netdev", "socket,id=gns3-{},udp={}:{},localaddr={}:{}".format(adapter_id, + nio.rhost, + nio.rport, + self._host, + nio.lport)]) else: - network_options.extend(["-netdev", "user,id=gns3-{}".format(adapter_id)]) + if self._legacy_networking: + network_options.extend(["-net", "user,vlan={}".format(adapter_id)]) + else: + network_options.extend(["-netdev", "user,id=gns3-{}".format(adapter_id)]) adapter_id += 1 return network_options diff --git a/gns3server/modules/qemu/schemas.py b/gns3server/modules/qemu/schemas.py index adc766f8..425cb1c4 100644 --- a/gns3server/modules/qemu/schemas.py +++ b/gns3server/modules/qemu/schemas.py @@ -124,6 +124,10 @@ QEMU_UPDATE_SCHEMA = { "description": "Path to the image in the cloud object store", "type": "string", }, + "legacy_networking": { + "description": "Use QEMU legagy networking commands (-net syntax)", + "type": "boolean", + }, "options": { "description": "additional QEMU options", "type": "string", From fed02ee16724ad711aeb91be84da76fbb457b4bc Mon Sep 17 00:00:00 2001 From: grossmj Date: Sat, 29 Nov 2014 16:42:57 -0700 Subject: [PATCH 10/16] Adds default path for VBoxManage on Mac OS X. --- gns3server/modules/virtualbox/__init__.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/gns3server/modules/virtualbox/__init__.py b/gns3server/modules/virtualbox/__init__.py index fde9784b..4a3fb40f 100644 --- a/gns3server/modules/virtualbox/__init__.py +++ b/gns3server/modules/virtualbox/__init__.py @@ -62,8 +62,13 @@ class VirtualBox(IModule): # get the vboxmanage location self._vboxmanage_path = None - if sys.platform.startswith("win") and "VBOX_INSTALL_PATH" in os.environ: - self._vboxmanage_path = os.path.join(os.environ["VBOX_INSTALL_PATH"], "VBoxManage.exe") + if sys.platform.startswith("win"): + if "VBOX_INSTALL_PATH" in os.environ: + self._vboxmanage_path = os.path.join(os.environ["VBOX_INSTALL_PATH"], "VBoxManage.exe") + elif "VBOX_MSI_INSTALL_PATH" in os.environ: + self._vboxmanage_path = os.path.join(os.environ["VBOX_MSI_INSTALL_PATH"], "VBoxManage.exe") + elif sys.platform.startswith("darwin"): + self._vboxmanage_path = "/Applications/VirtualBox.app/Contents/MacOS/VBoxManage" else: config = Config.instance() vbox_config = config.get_section_config(name.upper()) From 1e01c85be9eece3f60e116375c853ae6c82a70db Mon Sep 17 00:00:00 2001 From: Jeremy Date: Tue, 2 Dec 2014 14:49:39 -0700 Subject: [PATCH 11/16] Change search paths for Qemu on Windows. --- gns3server/modules/qemu/__init__.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/gns3server/modules/qemu/__init__.py b/gns3server/modules/qemu/__init__.py index 18c005ee..c4ad2968 100644 --- a/gns3server/modules/qemu/__init__.py +++ b/gns3server/modules/qemu/__init__.py @@ -625,8 +625,13 @@ class Qemu(IModule): # look for Qemu binaries in the current working directory and $PATH if sys.platform.startswith("win"): # add specific Windows paths - paths.append(os.path.join(os.getcwd(), "qemu")) - paths.append(os.path.join(os.getcwd(), "qemu-0.11.0")) + if hasattr(sys, "frozen"): + # add any qemu dir in the same location as gns3server.exe to the list of paths + exec_dir = os.path.dirname(os.path.abspath(sys.executable)) + for f in os.listdir(exec_dir): + if f.lower().startswith("qemu"): + paths.append(os.path.join(exec_dir, f)) + if "PROGRAMFILES(X86)" in os.environ and os.path.exists(os.environ["PROGRAMFILES(X86)"]): paths.append(os.path.join(os.environ["PROGRAMFILES(X86)"], "qemu")) if "PROGRAMFILES" in os.environ and os.path.exists(os.environ["PROGRAMFILES"]): @@ -637,7 +642,9 @@ class Qemu(IModule): for path in paths: try: for f in os.listdir(path): - if (f.startswith("qemu-system") or f == "qemu" or f == "qemu.exe") and os.access(os.path.join(path, f), os.X_OK): + if (f.startswith("qemu-system") 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)): qemu_path = os.path.join(path, f) version = self._get_qemu_version(qemu_path) qemus.append({"path": qemu_path, "version": version}) From 8f53d51c05d9f2139786c3f7bcc1ba1b434bdec9 Mon Sep 17 00:00:00 2001 From: Jeremy Date: Tue, 2 Dec 2014 18:12:37 -0700 Subject: [PATCH 12/16] Support for CPU throttling and process priority for Qemu. --- gns3server/modules/qemu/qemu_vm.py | 157 +++++++++++++++++++++++++---- gns3server/modules/qemu/schemas.py | 17 +++- 2 files changed, 155 insertions(+), 19 deletions(-) diff --git a/gns3server/modules/qemu/qemu_vm.py b/gns3server/modules/qemu/qemu_vm.py index bbe80c4b..19f4be58 100644 --- a/gns3server/modules/qemu/qemu_vm.py +++ b/gns3server/modules/qemu/qemu_vm.py @@ -90,6 +90,7 @@ class QemuVM(object): self._command = [] self._started = False self._process = None + self._cpulimit_process = None self._stdout_file = "" self._console_host = console_host self._console_start_port_range = console_start_port_range @@ -109,6 +110,8 @@ class QemuVM(object): self._kernel_image = "" self._kernel_command_line = "" self._legacy_networking = False + self._cpu_throttling = 0 # means no CPU throttling + self._process_priority = "low" working_dir_path = os.path.join(working_dir, "qemu", "vm-{}".format(self._id)) @@ -155,7 +158,10 @@ class QemuVM(object): "initrd": self._initrd, "kernel_image": self._kernel_image, "kernel_command_line": self._kernel_command_line, - "legacy_networking": self._legacy_networking} + "legacy_networking": self._legacy_networking, + "cpu_throttling": self._cpu_throttling, + "process_priority": self._process_priority + } return qemu_defaults @@ -464,6 +470,56 @@ class QemuVM(object): log.info("QEMU VM {name} [id={id}] has disabled legacy networking".format(name=self._name, id=self._id)) self._legacy_networking = legacy_networking + @property + def cpu_throttling(self): + """ + Returns the percentage of CPU allowed. + + :returns: integer + """ + + return self._cpu_throttling + + @cpu_throttling.setter + def cpu_throttling(self, cpu_throttling): + """ + Sets the percentage of CPU allowed. + + :param cpu_throttling: integer + """ + + log.info("QEMU VM {name} [id={id}] has set the percentage of CPU allowed to {cpu}".format(name=self._name, + id=self._id, + cpu=cpu_throttling)) + self._cpu_throttling = cpu_throttling + self._stop_cpulimit() + if cpu_throttling: + self._set_cpu_throttling() + + @property + def process_priority(self): + """ + Returns the process priority. + + :returns: string + """ + + return self._process_priority + + @process_priority.setter + def process_priority(self, process_priority): + """ + Sets the process priority. + + :param process_priority: string + """ + + log.info("QEMU VM {name} [id={id}] has set the process priority to {priority}".format(name=self._name, + id=self._id, + priority=process_priority)) + self._process_priority = process_priority + + @property def ram(self): """ @@ -579,6 +635,84 @@ class QemuVM(object): kernel_command_line=kernel_command_line)) self._kernel_command_line = kernel_command_line + def _set_process_priority(self): + """ + Changes the process priority + """ + + if sys.platform.startswith("win"): + try: + import win32api + import win32con + import win32process + except ImportError: + log.error("pywin32 must be installed to change the priority class for QEMU VM {}".format(self._name)) + else: + log.info("setting QEMU VM {} priority class to BELOW_NORMAL".format(self._name)) + handle = win32api.OpenProcess(win32con.PROCESS_ALL_ACCESS, 0, self._process.pid) + if self._process_priority == "realtime": + priority = win32process.REALTIME_PRIORITY_CLASS + elif self._process_priority == "very high": + priority = win32process.HIGH_PRIORITY_CLASS + elif self._process_priority == "high": + priority = win32process.ABOVE_NORMAL_PRIORITY_CLASS + elif self._process_priority == "low": + priority = win32process.BELOW_NORMAL_PRIORITY_CLASS + elif self._process_priority == "very low": + priority = win32process.IDLE_PRIORITY_CLASS + else: + priority = win32process.NORMAL_PRIORITY_CLASS + win32process.SetPriorityClass(handle, priority) + else: + if self._process_priority == "realtime": + priority = -20 + elif self._process_priority == "very high": + priority = -15 + elif self._process_priority == "high": + priority = -5 + elif self._process_priority == "low": + priority = 5 + elif self._process_priority == "very low": + priority = 19 + else: + priority = 0 + try: + subprocess.call(['renice', '-n', str(priority), '-p', str(self._process.pid)]) + except subprocess.SubprocessError as e: + log.error("could not change process priority for QEMU VM {}: {}".format(self._name, e)) + + def _stop_cpulimit(self): + """ + Stops the cpulimit process. + """ + + if self._cpulimit_process and self._cpulimit_process.poll() is None: + self._cpulimit_process.kill() + try: + self._process.wait(1) + except subprocess.TimeoutExpired: + log.error("could not kill cpulimit process {}".format(self._cpulimit_process.pid)) + + def _set_cpu_throttling(self): + """ + Limits the CPU usage for current QEMU process. + """ + + if not self.is_running(): + return + + try: + if sys.platform.startswith("win") and hasattr(sys, "frozen"): + cpulimit_exec = os.path.join(os.path.dirname(os.path.abspath(sys.executable)), "cpulimit", "cpulimit.exe") + else: + cpulimit_exec = "cpulimit" + subprocess.Popen([cpulimit_exec, "--lazy", "--pid={}".format(self._process.pid), "--limit={}".format(self._cpu_throttling)], cwd=self._working_dir) + log.info("CPU throttled to {}%".format(self._cpu_throttling)) + except FileNotFoundError: + raise QemuError("cpulimit could not be found, please deactivate CPU throttling") + except subprocess.SubprocessError as e: + raise QemuError("Could not throttle CPU: {}".format(e)) + def start(self): """ Starts this QEMU VM. @@ -644,23 +778,9 @@ class QemuVM(object): log.error("could not start QEMU {}: {}\n{}".format(self._qemu_path, e, stdout)) raise QemuError("could not start QEMU {}: {}\n{}".format(self._qemu_path, e, stdout)) - # change the process priority - if sys.platform.startswith("win"): - try: - import win32api - import win32con - import win32process - except ImportError: - log.error("pywin32 must be installed to change the priority class for QEMU VM {}".format(self._name)) - else: - log.info("setting QEMU VM {} priority class to BELOW_NORMAL".format(self._name)) - handle = win32api.OpenProcess(win32con.PROCESS_ALL_ACCESS, 0, self._process.pid) - win32process.SetPriorityClass(handle, win32process.BELOW_NORMAL_PRIORITY_CLASS) - else: - try: - subprocess.call(['renice', '-n', '19', '-p', str(self._process.pid)]) - except subprocess.SubprocessError as e: - log.error("could not change process priority for QEMU VM {}: {}".format(self._name, e)) + self._set_process_priority() + if self._cpu_throttling: + self._set_cpu_throttling() def stop(self): """ @@ -680,6 +800,7 @@ class QemuVM(object): self._process.pid)) self._process = None self._started = False + self._stop_cpulimit() def suspend(self): """ diff --git a/gns3server/modules/qemu/schemas.py b/gns3server/modules/qemu/schemas.py index 425cb1c4..5807d2a7 100644 --- a/gns3server/modules/qemu/schemas.py +++ b/gns3server/modules/qemu/schemas.py @@ -128,8 +128,23 @@ QEMU_UPDATE_SCHEMA = { "description": "Use QEMU legagy networking commands (-net syntax)", "type": "boolean", }, + "cpu_throttling": { + "description": "Percentage of CPU allowed for QEMU", + "minimum": 0, + "maximum": 800, + "type": "integer", + }, + "process_priority": { + "description": "Process priority for QEMU", + "enum": ["realtime", + "very high", + "high", + "normal", + "low", + "very low"] + }, "options": { - "description": "additional QEMU options", + "description": "Additional QEMU options", "type": "string", }, }, From 5a468888c846ee19bd109e3a9b8f37161dc8bb57 Mon Sep 17 00:00:00 2001 From: Jeremy Date: Tue, 2 Dec 2014 18:52:28 -0700 Subject: [PATCH 13/16] Bump version to 1.2.1.dev2 --- gns3server/modules/qemu/qemu_vm.py | 4 ++-- gns3server/version.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gns3server/modules/qemu/qemu_vm.py b/gns3server/modules/qemu/qemu_vm.py index 19f4be58..57a5b42d 100644 --- a/gns3server/modules/qemu/qemu_vm.py +++ b/gns3server/modules/qemu/qemu_vm.py @@ -689,7 +689,7 @@ class QemuVM(object): if self._cpulimit_process and self._cpulimit_process.poll() is None: self._cpulimit_process.kill() try: - self._process.wait(1) + self._process.wait(3) except subprocess.TimeoutExpired: log.error("could not kill cpulimit process {}".format(self._cpulimit_process.pid)) @@ -709,7 +709,7 @@ class QemuVM(object): subprocess.Popen([cpulimit_exec, "--lazy", "--pid={}".format(self._process.pid), "--limit={}".format(self._cpu_throttling)], cwd=self._working_dir) log.info("CPU throttled to {}%".format(self._cpu_throttling)) except FileNotFoundError: - raise QemuError("cpulimit could not be found, please deactivate CPU throttling") + raise QemuError("cpulimit could not be found, please install it or deactivate CPU throttling") except subprocess.SubprocessError as e: raise QemuError("Could not throttle CPU: {}".format(e)) diff --git a/gns3server/version.py b/gns3server/version.py index 67180411..6ea7e12e 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.2.1.dev1" +__version__ = "1.2.1.dev2" __version_info__ = (1, 2, 1, 99) From f58c7960c901d16c2519a623d220241fea0934ef Mon Sep 17 00:00:00 2001 From: Jeremy Date: Thu, 4 Dec 2014 12:25:49 -0700 Subject: [PATCH 14/16] Use bundled Qemu on Windows and OSX by default and checks if remote server are registered. --- gns3server/modules/qemu/__init__.py | 2 ++ scripts/ws_client.py | 46 +++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) create mode 100644 scripts/ws_client.py diff --git a/gns3server/modules/qemu/__init__.py b/gns3server/modules/qemu/__init__.py index c4ad2968..4816dfc8 100644 --- a/gns3server/modules/qemu/__init__.py +++ b/gns3server/modules/qemu/__init__.py @@ -639,6 +639,8 @@ class Qemu(IModule): elif sys.platform.startswith("darwin"): # add specific locations on Mac OS X regardless of what's in $PATH paths.extend(["/usr/local/bin", "/opt/local/bin"]) + if hasattr(sys, "frozen"): + paths.append(os.path.abspath(os.path.join(os.getcwd(), "../../../qemu/bin/"))) for path in paths: try: for f in os.listdir(path): diff --git a/scripts/ws_client.py b/scripts/ws_client.py new file mode 100644 index 00000000..675bc7c3 --- /dev/null +++ b/scripts/ws_client.py @@ -0,0 +1,46 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2014 GNS3 Technologies Inc. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +from ws4py.client.threadedclient import WebSocketClient + + +class WSClient(WebSocketClient): + + def opened(self): + + print("Connection successful with {}:{}".format(self.host, self.port)) + + self.send('{"jsonrpc": 2.0, "method": "dynamips.settings", "params": {"path": "/usr/local/bin/dynamips", "allocate_hypervisor_per_device": true, "working_dir": "/tmp/gns3-1b4grwm3-files", "udp_end_port_range": 20000, "sparse_memory_support": true, "allocate_hypervisor_per_ios_image": true, "aux_start_port_range": 2501, "use_local_server": true, "hypervisor_end_port_range": 7700, "aux_end_port_range": 3000, "mmap_support": true, "console_start_port_range": 2001, "console_end_port_range": 2500, "hypervisor_start_port_range": 7200, "ghost_ios_support": true, "memory_usage_limit_per_hypervisor": 1024, "jit_sharing_support": false, "udp_start_port_range": 10001}}') + self.send('{"jsonrpc": 2.0, "method": "dynamips.vm.create", "id": "e8caf5be-de3d-40dd-80b9-ab6df8029570", "params": {"image": "/home/grossmj/GNS3/images/IOS/c3725-advipservicesk9-mz.124-15.T14.image", "name": "R1", "platform": "c3725", "ram": 256}}') + + def closed(self, code, reason=None): + + print("Closed down. Code: {} Reason: {}".format(code, reason)) + + def received_message(self, m): + + print(m) + if len(m) == 175: + self.close(reason='Bye bye') + +if __name__ == '__main__': + try: + ws = WSClient('ws://localhost:8000/', protocols=['http-only', 'chat']) + ws.connect() + ws.run_forever() + except KeyboardInterrupt: + ws.close() \ No newline at end of file From ba357b0541b507b887d66fff7e5b30818b6bf119 Mon Sep 17 00:00:00 2001 From: Jeremy Date: Thu, 4 Dec 2014 12:49:40 -0700 Subject: [PATCH 15/16] Bump version to 1.2.1 --- gns3server/version.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gns3server/version.py b/gns3server/version.py index 6ea7e12e..db1b0e16 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.2.1.dev2" -__version_info__ = (1, 2, 1, 99) +__version__ = "1.2.1" +__version_info__ = (1, 2, 1, 0) From fc04a94dcebd7f9ac9a0125ee43c849a14ad3387 Mon Sep 17 00:00:00 2001 From: Jeremy Date: Fri, 5 Dec 2014 13:53:30 -0700 Subject: [PATCH 16/16] Bump version to 1.2.2.dev1 --- gns3server/version.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gns3server/version.py b/gns3server/version.py index db1b0e16..aad30941 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.2.1" -__version_info__ = (1, 2, 1, 0) +__version__ = "1.2.2.dev1" +__version_info__ = (1, 2, 2, 0)