From 161adb781b84b765d96106c5e43de4b45f7e3580 Mon Sep 17 00:00:00 2001 From: grossmj Date: Wed, 22 Apr 2015 20:29:52 -0600 Subject: [PATCH 01/33] Use UUIDs instead of the VM names for VirtualBox pipe paths. --- gns3server/modules/virtualbox/virtualbox_vm.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/gns3server/modules/virtualbox/virtualbox_vm.py b/gns3server/modules/virtualbox/virtualbox_vm.py index 428d880c..1c9b5ce2 100644 --- a/gns3server/modules/virtualbox/virtualbox_vm.py +++ b/gns3server/modules/virtualbox/virtualbox_vm.py @@ -586,12 +586,14 @@ class VirtualBoxVM(BaseVM): :returns: pipe path (string) """ - p = re.compile('\s+', re.UNICODE) - pipe_name = p.sub("_", self._vmname) if sys.platform.startswith("win"): - pipe_name = r"\\.\pipe\VBOX\{}".format(pipe_name) + pipe_name = r"\\.\pipe\gns3_vbox\{}".format(self.id) else: - pipe_name = os.path.join(tempfile.gettempdir(), "pipe_{}".format(pipe_name)) + pipe_name = os.path.join(tempfile.gettempdir(), "gns3_vbox", "{}".format(self.id)) + try: + os.makedirs(os.path.dirname(pipe_name), exist_ok=True) + except OSError as e: + raise VirtualBoxError("Could not create the VirtualBox pipe directory: {}".format(e)) return pipe_name @asyncio.coroutine From 30f6263146ea078655cc73a3aecde986ad181ff0 Mon Sep 17 00:00:00 2001 From: grossmj Date: Wed, 22 Apr 2015 21:42:36 -0600 Subject: [PATCH 02/33] Don't assume the PATH environment variable exists. --- gns3server/modules/qemu/__init__.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/gns3server/modules/qemu/__init__.py b/gns3server/modules/qemu/__init__.py index 510f6aab..66a1a968 100644 --- a/gns3server/modules/qemu/__init__.py +++ b/gns3server/modules/qemu/__init__.py @@ -30,6 +30,9 @@ from ..base_manager import BaseManager from .qemu_error import QemuError from .qemu_vm import QemuVM +import logging +log = logging.getLogger(__name__) + class Qemu(BaseManager): @@ -44,7 +47,11 @@ class Qemu(BaseManager): """ qemus = [] - paths = [os.getcwd()] + os.environ["PATH"].split(os.pathsep) + paths = [os.getcwd()] + if "PATH" in os.environ: + paths.extend(os.environ["PATH"].split(os.pathsep)) + else: + log.warning("The PATH environment variable doesn't exist") # look for Qemu binaries in the current working directory and $PATH if sys.platform.startswith("win"): # add specific Windows paths From 3680c40e231b42f5060083f348d0eb46d8922647 Mon Sep 17 00:00:00 2001 From: grossmj Date: Thu, 23 Apr 2015 00:03:44 -0600 Subject: [PATCH 03/33] Catch COM errors when connecting to WMI. --- gns3server/utils/interfaces.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/gns3server/utils/interfaces.py b/gns3server/utils/interfaces.py index 79e4ef28..4123459d 100644 --- a/gns3server/utils/interfaces.py +++ b/gns3server/utils/interfaces.py @@ -58,10 +58,11 @@ def get_windows_interfaces(): import win32com.client import pywintypes - locator = win32com.client.Dispatch("WbemScripting.SWbemLocator") - service = locator.ConnectServer(".", "root\cimv2") + interfaces = [] try: + locator = win32com.client.Dispatch("WbemScripting.SWbemLocator") + service = locator.ConnectServer(".", "root\cimv2") # more info on Win32_NetworkAdapter: http://msdn.microsoft.com/en-us/library/aa394216%28v=vs.85%29.aspx for adapter in service.InstancesOf("Win32_NetworkAdapter"): if adapter.NetConnectionStatus == 2 or adapter.NetConnectionStatus == 7: @@ -70,7 +71,7 @@ def get_windows_interfaces(): interfaces.append({"id": npf_interface, "name": adapter.NetConnectionID}) except (AttributeError, pywintypes.com_error): - log.warn("could not use the COM service to retrieve interface info, trying using the registry...") + log.warn("Could not use the COM service to retrieve interface info, trying using the registry...") return _get_windows_interfaces_from_registry() return interfaces From 6b862b83979560ad57d2834a36a46ad9a247cea3 Mon Sep 17 00:00:00 2001 From: Julien Duponchelle Date: Thu, 23 Apr 2015 14:31:36 +0200 Subject: [PATCH 04/33] Correctly show the host in templates Fix #157 --- gns3server/web/response.py | 4 +++- gns3server/web/route.py | 16 ++++++++-------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/gns3server/web/response.py b/gns3server/web/response.py index 42112fa9..fc1b55de 100644 --- a/gns3server/web/response.py +++ b/gns3server/web/response.py @@ -30,10 +30,11 @@ renderer = jinja2.Environment(loader=jinja2.PackageLoader('gns3server', 'templat class Response(aiohttp.web.Response): - def __init__(self, route=None, output_schema=None, headers={}, **kwargs): + def __init__(self, request=None, route=None, output_schema=None, headers={}, **kwargs): self._route = route self._output_schema = output_schema + self._request = request headers['X-Route'] = self._route headers['Server'] = "Python/{0[0]}.{0[1]} GNS3/{1}".format(sys.version_info, __version__) super().__init__(headers=headers, **kwargs) @@ -70,6 +71,7 @@ class Response(aiohttp.web.Response): """ template = renderer.get_template(template_filename) kwargs["gns3_version"] = __version__ + kwargs["gns3_host"] = self._request.host self.html(template.render(**kwargs)) def json(self, answer): diff --git a/gns3server/web/route.py b/gns3server/web/route.py index 0c5631f1..2f8fd0c9 100644 --- a/gns3server/web/route.py +++ b/gns3server/web/route.py @@ -120,7 +120,7 @@ class Route(object): # Non API call if api_version is None: - response = Response(route=route, output_schema=output_schema) + response = Response(request=request, route=route, output_schema=output_schema) yield from func(request, response) return response @@ -136,34 +136,34 @@ class Route(object): f.write("\n") except OSError as e: log.warn("Could not write to the record file {}: {}".format(record_file, e)) - response = Response(route=route, output_schema=output_schema) + response = Response(request=request, route=route, output_schema=output_schema) yield from func(request, response) except aiohttp.web.HTTPBadRequest as e: - response = Response(route=route) + response = Response(request=request, route=route) response.set_status(e.status) response.json({"message": e.text, "status": e.status, "path": route, "request": request.json}) except aiohttp.web.HTTPException as e: - response = Response(route=route) + response = Response(request=request, route=route) response.set_status(e.status) response.json({"message": e.text, "status": e.status}) except VMError as e: log.error("VM error detected: {type}".format(type=type(e)), exc_info=1) - response = Response(route=route) + response = Response(request=request, route=route) response.set_status(409) response.json({"message": str(e), "status": 409}) except asyncio.futures.CancelledError as e: log.error("Request canceled") - response = Response(route=route) + response = Response(request=request, route=route) response.set_status(408) response.json({"message": "Request canceled", "status": 408}) except aiohttp.ClientDisconnectedError: log.error("Client disconnected") - response = Response(route=route) + response = Response(request=request, route=route) response.set_status(408) response.json({"message": "Client disconnected", "status": 408}) except Exception as e: log.error("Uncaught exception detected: {type}".format(type=type(e)), exc_info=1) - response = Response(route=route) + response = Response(request=request, route=route) response.set_status(500) CrashReport.instance().capture_exception(request) exc_type, exc_value, exc_tb = sys.exc_info() From 24bfd8ab53beaaeabbc8496f4786cb7ed9cf2754 Mon Sep 17 00:00:00 2001 From: Julien Duponchelle Date: Fri, 24 Apr 2015 18:30:31 +0200 Subject: [PATCH 05/33] New crash report key and doesn't send report for developers --- gns3server/crash_report.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/gns3server/crash_report.py b/gns3server/crash_report.py index 175974ce..b82d4fa5 100644 --- a/gns3server/crash_report.py +++ b/gns3server/crash_report.py @@ -40,7 +40,7 @@ class CrashReport: Report crash to a third party service """ - DSN = "sync+https://90fe04368a3e4109b584a116ee03ca87:268ef86c65cf4e8fa41d4c7fb1c70b72@app.getsentry.com/38482" + DSN = "sync+https://22979234ab4749ceabce08e6da4c1476:1432c8c7a43d410b9b5bb33f8e55b2a6@app.getsentry.com/38482" if hasattr(sys, "frozen"): cacert = os.path.join(os.getcwd(), "cacert.pem") if os.path.isfile(cacert): @@ -55,6 +55,9 @@ class CrashReport: def capture_exception(self, request=None): if not RAVEN_AVAILABLE: return + if os.path.exists(".git"): + log.warning("A .git directory exist crash report is turn off for developers") + return server_config = Config.instance().get_section_config("Server") if server_config.getboolean("report_errors"): if self._client is None: From fa544ef888f2e5ee59e1574ae0ea6214bb1811e9 Mon Sep 17 00:00:00 2001 From: Jeremy Date: Fri, 24 Apr 2015 17:27:32 -0600 Subject: [PATCH 06/33] Fixes #270. Relative paths management with empty ones. --- gns3server/modules/base_manager.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/gns3server/modules/base_manager.py b/gns3server/modules/base_manager.py index d6097fce..9dbc6fb8 100644 --- a/gns3server/modules/base_manager.py +++ b/gns3server/modules/base_manager.py @@ -380,8 +380,9 @@ class BaseManager: :return: file path """ + if not path: + return "" img_directory = self.get_images_directory() - if not os.path.isabs(path): s = os.path.split(path) return os.path.normpath(os.path.join(img_directory, *s)) @@ -397,6 +398,8 @@ class BaseManager: :return: file path """ + if not path: + return "" img_directory = self.get_images_directory() path = self.get_abs_image_path(path) if os.path.dirname(path) == img_directory: @@ -407,4 +410,5 @@ class BaseManager: """ Get the image directory on disk """ + raise NotImplementedError From d68bf1c263bdc27a0c5a32b1e1dec676aee63072 Mon Sep 17 00:00:00 2001 From: grossmj Date: Sat, 25 Apr 2015 09:36:28 -0600 Subject: [PATCH 07/33] Removes unnecessary sleep in VirtualBox VM. --- gns3server/modules/virtualbox/__init__.py | 1 + gns3server/modules/virtualbox/virtualbox_vm.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/gns3server/modules/virtualbox/__init__.py b/gns3server/modules/virtualbox/__init__.py index a6440072..04df958f 100644 --- a/gns3server/modules/virtualbox/__init__.py +++ b/gns3server/modules/virtualbox/__init__.py @@ -133,6 +133,7 @@ class VirtualBox(BaseManager): if not extra_data[0].strip() == "Value: yes": # get the amount of RAM info_results = yield from self.execute("showvminfo", [vmname, "--machinereadable"]) + ram = 0 for info in info_results: try: name, value = info.split('=', 1) diff --git a/gns3server/modules/virtualbox/virtualbox_vm.py b/gns3server/modules/virtualbox/virtualbox_vm.py index 1c9b5ce2..905a2edc 100644 --- a/gns3server/modules/virtualbox/virtualbox_vm.py +++ b/gns3server/modules/virtualbox/virtualbox_vm.py @@ -209,7 +209,7 @@ class VirtualBoxVM(BaseVM): log.info("VirtualBox VM '{name}' [{id}] stopped".format(name=self.name, id=self.id)) log.debug("Stop result: {}".format(result)) - yield from asyncio.sleep(0.5) # give some time for VirtualBox to unlock the VM + # yield from asyncio.sleep(0.5) # give some time for VirtualBox to unlock the VM try: # deactivate the first serial port yield from self._modify_vm("--uart1 off") From 80a0e0ebf75bb344bf79ceeccd4618d4344be125 Mon Sep 17 00:00:00 2001 From: grossmj Date: Sat, 25 Apr 2015 11:58:34 -0600 Subject: [PATCH 08/33] Explicit utf-8 encoding where necessary to avoid Unicode errors on Windows (we require/set an utf-8 locale on other systems). --- gns3server/modules/dynamips/hypervisor.py | 4 +-- gns3server/modules/dynamips/nodes/router.py | 8 ++--- gns3server/modules/iou/iou_vm.py | 32 +++++++++---------- gns3server/modules/qemu/qemu_vm.py | 6 ++-- .../modules/virtualbox/virtualbox_vm.py | 4 +-- gns3server/modules/vpcs/vpcs_vm.py | 12 +++---- gns3server/web/route.py | 2 +- 7 files changed, 34 insertions(+), 34 deletions(-) diff --git a/gns3server/modules/dynamips/hypervisor.py b/gns3server/modules/dynamips/hypervisor.py index 407072ca..a568ff51 100644 --- a/gns3server/modules/dynamips/hypervisor.py +++ b/gns3server/modules/dynamips/hypervisor.py @@ -158,8 +158,8 @@ class Hypervisor(DynamipsHypervisor): output = "" if self._stdout_file and os.access(self._stdout_file, os.R_OK): try: - with open(self._stdout_file, errors="replace") as file: - output = file.read() + 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)) return output diff --git a/gns3server/modules/dynamips/nodes/router.py b/gns3server/modules/dynamips/nodes/router.py index 1875ab22..77045290 100644 --- a/gns3server/modules/dynamips/nodes/router.py +++ b/gns3server/modules/dynamips/nodes/router.py @@ -1396,7 +1396,7 @@ class Router(BaseVM): startup_config_path = os.path.join(module_workdir, "configs", "i{}_startup-config.cfg".format(self._dynamips_id)) if os.path.isfile(startup_config_path): try: - with open(startup_config_path, "r+", errors="replace") as f: + with open(startup_config_path, "r+", encoding="utf-8", errors="replace") as f: old_config = f.read() new_config = old_config.replace(self.name, new_name) f.seek(0) @@ -1409,7 +1409,7 @@ class Router(BaseVM): private_config_path = os.path.join(module_workdir, "configs", "i{}_private-config.cfg".format(self._dynamips_id)) if os.path.isfile(private_config_path): try: - with open(private_config_path, "r+", errors="replace") as f: + with open(private_config_path, "r+", encoding="utf-8", errors="replace") as f: old_config = f.read() new_config = old_config.replace(self.name, new_name) f.seek(0) @@ -1484,7 +1484,7 @@ class Router(BaseVM): startup_config_base64, private_config_base64 = yield from self.extract_config() if startup_config_base64: try: - config = base64.b64decode(startup_config_base64).decode(errors='replace') + config = base64.b64decode(startup_config_base64).decode("utf-8", errors="replace") config = "!\n" + config.replace("\r", "") config_path = os.path.join(module_workdir, self.startup_config) with open(config_path, "wb") as f: @@ -1495,7 +1495,7 @@ class Router(BaseVM): if private_config_base64: try: - config = base64.b64decode(private_config_base64).decode(errors='replace') + config = base64.b64decode(private_config_base64).decode("utf-8", errors="replace") config = "!\n" + config.replace("\r", "") config_path = os.path.join(module_workdir, self.private_config) with open(config_path, "wb") as f: diff --git a/gns3server/modules/iou/iou_vm.py b/gns3server/modules/iou/iou_vm.py index 3e0dc064..81c9dd37 100644 --- a/gns3server/modules/iou/iou_vm.py +++ b/gns3server/modules/iou/iou_vm.py @@ -332,8 +332,8 @@ class IOUVM(BaseVM): def iourc_content(self): try: - with open(os.path.join(self.temporary_directory, "iourc")) as f: - return f.read() + with open(os.path.join(self.temporary_directory, "iourc"), "rb") as f: + return f.read().decode("utf-8") except OSError: return None @@ -343,8 +343,8 @@ class IOUVM(BaseVM): if value is not None: path = os.path.join(self.temporary_directory, "iourc") try: - with open(path, "w+") as f: - f.write(value) + with open(path, "wb+") as f: + f.write(value.encode("utf-8")) except OSError as e: raise IOUError("Could not write the iourc file {}: {}".format(path, e)) @@ -378,7 +378,7 @@ class IOUVM(BaseVM): config = configparser.ConfigParser() try: - with open(self.iourc_path) as f: + with open(self.iourc_path, encoding="utf-8") as f: config.read_file(f) except OSError as e: raise IOUError("Could not open iourc file {}: {}".format(self.iourc_path, e)) @@ -455,7 +455,7 @@ class IOUVM(BaseVM): log.info("Starting IOU: {}".format(self._command)) self._iou_stdout_file = os.path.join(self.working_dir, "iou.log") log.info("Logging to {}".format(self._iou_stdout_file)) - with open(self._iou_stdout_file, "w") as fd: + with open(self._iou_stdout_file, "w", encoding="utf-8") as fd: self._iou_process = yield from asyncio.create_subprocess_exec(*self._command, stdout=fd, stderr=subprocess.STDOUT, @@ -499,7 +499,7 @@ class IOUVM(BaseVM): log.info("starting iouyap: {}".format(command)) self._iouyap_stdout_file = os.path.join(self.working_dir, "iouyap.log") log.info("logging to {}".format(self._iouyap_stdout_file)) - with open(self._iouyap_stdout_file, "w") as fd: + with open(self._iouyap_stdout_file, "w", encoding="utf-8") as fd: self._iouyap_process = yield from asyncio.create_subprocess_exec(*command, stdout=fd, stderr=subprocess.STDOUT, @@ -565,7 +565,7 @@ class IOUVM(BaseVM): bay_id += 1 try: - with open(iouyap_ini, "w") as config_file: + with open(iouyap_ini, "w", encoding="utf-8") as config_file: config.write(config_file) log.info("IOU {name} [id={id}]: iouyap.ini updated".format(name=self._name, id=self._id)) @@ -671,7 +671,7 @@ class IOUVM(BaseVM): netmap_path = os.path.join(self.working_dir, "NETMAP") try: - with open(netmap_path, "w") as f: + with open(netmap_path, "w", encoding="utf-8") as f: for bay in range(0, 16): for unit in range(0, 4): f.write("{iouyap_id}:{bay}/{unit}{iou_id:>5d}:{bay}/{unit}\n".format(iouyap_id=str(self.application_id + 512), @@ -741,8 +741,8 @@ class IOUVM(BaseVM): output = "" if self._iou_stdout_file: try: - with open(self._iou_stdout_file, errors="replace") as file: - output = file.read() + with open(self._iou_stdout_file, "rb") as file: + output = file.read().decode("utf-8", errors="replace") except OSError as e: log.warn("could not read {}: {}".format(self._iou_stdout_file, e)) return output @@ -756,8 +756,8 @@ class IOUVM(BaseVM): output = "" if self._iouyap_stdout_file: try: - with open(self._iouyap_stdout_file, errors="replace") as file: - output = file.read() + with open(self._iouyap_stdout_file, "rb") as file: + output = file.read().decode("utf-8", errors="replace") except OSError as e: log.warn("could not read {}: {}".format(self._iouyap_stdout_file, e)) return output @@ -949,8 +949,8 @@ class IOUVM(BaseVM): return None try: - with open(config_file) as f: - return f.read() + with open(config_file, "rb") as f: + return f.read().decode("utf-8", errors="replace") except OSError as e: raise IOUError("Can't read configuration file '{}': {}".format(config_file, e)) @@ -972,7 +972,7 @@ class IOUVM(BaseVM): if len(initial_config) == 0 and os.path.exists(script_file): return - with open(script_file, 'w+') as f: + with open(script_file, "w+", encoding="utf-8") as f: if len(initial_config) == 0: f.write('') else: diff --git a/gns3server/modules/qemu/qemu_vm.py b/gns3server/modules/qemu/qemu_vm.py index 420bd5e0..58febeb7 100644 --- a/gns3server/modules/qemu/qemu_vm.py +++ b/gns3server/modules/qemu/qemu_vm.py @@ -574,7 +574,7 @@ class QemuVM(BaseVM): log.info("Starting QEMU: {}".format(self._command)) self._stdout_file = os.path.join(self.working_dir, "qemu.log") log.info("logging to {}".format(self._stdout_file)) - with open(self._stdout_file, "w") as fd: + with open(self._stdout_file, "w", encoding="utf-8") as fd: self._process = yield from asyncio.create_subprocess_exec(*self._command, stdout=fd, stderr=subprocess.STDOUT, @@ -817,8 +817,8 @@ class QemuVM(BaseVM): output = "" if self._stdout_file: try: - with open(self._stdout_file, errors="replace") as file: - output = file.read() + 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)) return output diff --git a/gns3server/modules/virtualbox/virtualbox_vm.py b/gns3server/modules/virtualbox/virtualbox_vm.py index 905a2edc..ed1580ab 100644 --- a/gns3server/modules/virtualbox/virtualbox_vm.py +++ b/gns3server/modules/virtualbox/virtualbox_vm.py @@ -276,7 +276,7 @@ class VirtualBoxVM(BaseVM): hdd_info_file = os.path.join(self.working_dir, self._vmname, "hdd_info.json") try: - with open(hdd_info_file, "r") as f: + with open(hdd_info_file, "r", encoding="utf-8") as f: hdd_table = json.load(f) except OSError as e: raise VirtualBoxError("Could not read HDD info file: {}".format(e)) @@ -354,7 +354,7 @@ class VirtualBoxVM(BaseVM): if hdd_table: try: hdd_info_file = os.path.join(self.working_dir, self._vmname, "hdd_info.json") - with open(hdd_info_file, "w") as f: + with open(hdd_info_file, "w", encoding="utf-8") as f: json.dump(hdd_table, f, indent=4) except OSError as e: log.warning("VirtualBox VM '{name}' [{id}] could not write HHD info file: {error}".format(name=self.name, diff --git a/gns3server/modules/vpcs/vpcs_vm.py b/gns3server/modules/vpcs/vpcs_vm.py index 4475b97c..b96810bb 100644 --- a/gns3server/modules/vpcs/vpcs_vm.py +++ b/gns3server/modules/vpcs/vpcs_vm.py @@ -166,8 +166,8 @@ class VPCSVM(BaseVM): return None try: - with open(script_file) as f: - return f.read() + with open(script_file, "rb") as f: + return f.read().decode("utf-8", errors="replace") except OSError as e: raise VPCSError('Cannot read the startup script file "{}": {}'.format(script_file, e)) @@ -181,7 +181,7 @@ class VPCSVM(BaseVM): try: script_file = os.path.join(self.working_dir, 'startup.vpc') - with open(script_file, 'w+') as f: + with open(script_file, "w+", encoding="utf-8") as f: if startup_script is None: f.write('') else: @@ -226,7 +226,7 @@ class VPCSVM(BaseVM): flags = 0 if sys.platform.startswith("win32"): flags = subprocess.CREATE_NEW_PROCESS_GROUP - with open(self._vpcs_stdout_file, "w") as fd: + with open(self._vpcs_stdout_file, "w", encoding="utf-8") as fd: self._process = yield from asyncio.create_subprocess_exec(*self._command, stdout=fd, stderr=subprocess.STDOUT, @@ -290,8 +290,8 @@ class VPCSVM(BaseVM): output = "" if self._vpcs_stdout_file: try: - with open(self._vpcs_stdout_file, errors="replace") as file: - output = file.read() + with open(self._vpcs_stdout_file, "rb") as file: + output = file.read().decode("utf-8", errors="replace") except OSError as e: log.warn("Could not read {}: {}".format(self._vpcs_stdout_file, e)) return output diff --git a/gns3server/web/route.py b/gns3server/web/route.py index 2f8fd0c9..875298f5 100644 --- a/gns3server/web/route.py +++ b/gns3server/web/route.py @@ -131,7 +131,7 @@ class Route(object): record_file = server_config.get("record") if record_file: try: - with open(record_file, "a") as f: + with open(record_file, "a", encoding="utf-8") as f: f.write("curl -X {} 'http://{}{}' -d '{}'".format(request.method, request.host, request.path_qs, json.dumps(request.json))) f.write("\n") except OSError as e: From 683a5129173087fb7819314aeffc6f54b8f4d661 Mon Sep 17 00:00:00 2001 From: grossmj Date: Sat, 25 Apr 2015 15:20:15 -0600 Subject: [PATCH 09/33] Fixes #150. --- gns3server/modules/dynamips/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gns3server/modules/dynamips/__init__.py b/gns3server/modules/dynamips/__init__.py index ed6858a8..3bbeaf5a 100644 --- a/gns3server/modules/dynamips/__init__.py +++ b/gns3server/modules/dynamips/__init__.py @@ -208,7 +208,7 @@ class Dynamips(BaseManager): """ # save the configs when the project is committed - for vm in self._vms.values(): + for vm in self._vms.copy().values(): if vm.project.id == project.id: yield from vm.save_configs() From da2b895c9908dc094a0b0c78e99d5e6eee385cfd Mon Sep 17 00:00:00 2001 From: grossmj Date: Sun, 26 Apr 2015 12:49:29 -0600 Subject: [PATCH 10/33] Catch FileNotFoundError exception in os.getcwd() --- gns3server/modules/qemu/__init__.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/gns3server/modules/qemu/__init__.py b/gns3server/modules/qemu/__init__.py index 66a1a968..6dd532ba 100644 --- a/gns3server/modules/qemu/__init__.py +++ b/gns3server/modules/qemu/__init__.py @@ -47,7 +47,11 @@ class Qemu(BaseManager): """ qemus = [] - paths = [os.getcwd()] + paths = [] + try: + paths.append(os.getcwd()) + except FileNotFoundError: + log.warning("The current working directory doesn't exist") if "PATH" in os.environ: paths.extend(os.environ["PATH"].split(os.pathsep)) else: From 3e6996903f039db477603c17fc9260be6c655386 Mon Sep 17 00:00:00 2001 From: grossmj Date: Sun, 26 Apr 2015 12:57:06 -0600 Subject: [PATCH 11/33] Fixes VPCS process termination. --- gns3server/modules/vpcs/vpcs_vm.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/gns3server/modules/vpcs/vpcs_vm.py b/gns3server/modules/vpcs/vpcs_vm.py index b96810bb..02d7f15d 100644 --- a/gns3server/modules/vpcs/vpcs_vm.py +++ b/gns3server/modules/vpcs/vpcs_vm.py @@ -27,6 +27,7 @@ import signal import re import asyncio import shutil +import gns3server.utils.asyncio from pkg_resources import parse_version from .vpcs_error import VPCSError @@ -247,12 +248,13 @@ class VPCSVM(BaseVM): if self.is_running(): self._terminate_process() - try: - yield from asyncio.wait_for(self._process.wait(), timeout=3) - except asyncio.TimeoutError: - if self._process.returncode is None: - log.warn("VPCS process {} is still running... killing it".format(self._process.pid)) - self._process.kill() + if self._process.returncode is None: + try: + yield from gns3server.utils.asyncio.wait_for_process_termination(self._process, timeout=3) + except asyncio.TimeoutError: + if self._process.returncode is None: + log.warn("VPCS process {} is still running... killing it".format(self._process.pid)) + self._process.kill() self._process = None self._started = False From 017997e0a3acb41aff61cb5e84919b308d89455d Mon Sep 17 00:00:00 2001 From: grossmj Date: Sun, 26 Apr 2015 18:35:12 -0600 Subject: [PATCH 12/33] Fixes c7200 NPE setting. --- gns3server/modules/dynamips/nodes/c7200.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gns3server/modules/dynamips/nodes/c7200.py b/gns3server/modules/dynamips/nodes/c7200.py index 1a9d140f..8f6f664f 100644 --- a/gns3server/modules/dynamips/nodes/c7200.py +++ b/gns3server/modules/dynamips/nodes/c7200.py @@ -120,7 +120,7 @@ class C7200(Router): npe-225, npe-300, npe-400 and npe-g2 (PowerPC c7200 only) """ - if self.is_running(): + if (yield from self.is_running()): raise DynamipsError("Cannot change NPE on running router") yield from self._hypervisor.send('c7200 set_npe "{name}" {npe}'.format(name=self._name, npe=npe)) From 6edf1e36494ee2b123ca7b57f053de24214143fb Mon Sep 17 00:00:00 2001 From: grossmj Date: Sun, 26 Apr 2015 21:15:15 -0600 Subject: [PATCH 13/33] Check NIO exists when stopping an IOU capture. --- gns3server/modules/iou/iou_vm.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/gns3server/modules/iou/iou_vm.py b/gns3server/modules/iou/iou_vm.py index 81c9dd37..783b1357 100644 --- a/gns3server/modules/iou/iou_vm.py +++ b/gns3server/modules/iou/iou_vm.py @@ -384,6 +384,8 @@ class IOUVM(BaseVM): raise IOUError("Could not open iourc file {}: {}".format(self.iourc_path, e)) except configparser.Error as e: raise IOUError("Could not parse iourc file {}: {}".format(self.iourc_path, e)) + except UnicodeDecodeError as e: + raise IOUError("Invalid iourc file {}: {}".format(self.iourc_path, e)) if "license" not in config: raise IOUError("License section not found in iourc file {}".format(self.iourc_path)) hostname = socket.gethostname() @@ -587,7 +589,6 @@ class IOUVM(BaseVM): self._ioucon_thread = None self._terminate_process_iou() - if self._iou_process.returncode is None: try: yield from gns3server.utils.asyncio.wait_for_process_termination(self._iou_process, timeout=3) @@ -963,23 +964,23 @@ class IOUVM(BaseVM): """ try: - script_file = os.path.join(self.working_dir, "initial-config.cfg") + initial_config_path = os.path.join(self.working_dir, "initial-config.cfg") if initial_config is None: initial_config = '' # We disallow erasing the initial config file - if len(initial_config) == 0 and os.path.exists(script_file): + if len(initial_config) == 0 and os.path.exists(initial_config_path): return - with open(script_file, "w+", encoding="utf-8") as f: + with open(initial_config_path, "w+", encoding="utf-8") as f: if len(initial_config) == 0: f.write('') else: initial_config = initial_config.replace("%h", self._name) f.write(initial_config) except OSError as e: - raise IOUError("Can't write initial configuration file '{}': {}".format(script_file, e)) + raise IOUError("Can't write initial configuration file '{}': {}".format(initial_config_path, e)) @property def initial_config_file(self): @@ -1070,6 +1071,10 @@ class IOUVM(BaseVM): port_number=port_number)) nio = adapter.get_nio(port_number) + if not nio: + raise IOUError("NIO {port_number} does not exist in adapter {adapter}".format(adapter=adapter, + port_number=port_number)) + nio.stopPacketCapture() log.info('IOU "{name}" [{id}]: stopping packet capture on {adapter_number}/{port_number}'.format(name=self._name, id=self._id, From 271cb527d4883fa1a1323aa3820a81bfb6a88ab4 Mon Sep 17 00:00:00 2001 From: grossmj Date: Sun, 26 Apr 2015 21:19:39 -0600 Subject: [PATCH 14/33] Explicit utf-8 decoding. --- gns3server/modules/dynamips/dynamips_hypervisor.py | 2 +- gns3server/modules/qemu/qemu_vm.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gns3server/modules/dynamips/dynamips_hypervisor.py b/gns3server/modules/dynamips/dynamips_hypervisor.py index 2f6de303..e6ccc144 100644 --- a/gns3server/modules/dynamips/dynamips_hypervisor.py +++ b/gns3server/modules/dynamips/dynamips_hypervisor.py @@ -284,7 +284,7 @@ class DynamipsHypervisor: if not line: raise DynamipsError("No data returned from {host}:{port}, Dynamips process running: {run}" .format(host=self._host, port=self._port, run=self.is_running())) - buf += line.decode() + buf += line.decode("utf-8") except OSError as e: raise DynamipsError("Lost communication with {host}:{port} :{error}, Dynamips process running: {run}" .format(host=self._host, port=self._port, error=e, run=self.is_running())) diff --git a/gns3server/modules/qemu/qemu_vm.py b/gns3server/modules/qemu/qemu_vm.py index 58febeb7..b63a6111 100644 --- a/gns3server/modules/qemu/qemu_vm.py +++ b/gns3server/modules/qemu/qemu_vm.py @@ -644,7 +644,7 @@ class QemuVM(BaseVM): break for expect in expected: if expect in line: - result = line.decode().strip() + result = line.decode("utf-8").strip() break except EOFError as e: log.warn("Could not read from QEMU monitor: {}".format(e)) From 834a554fea9ea0b2e19a9f4101117c7fcd08e1be Mon Sep 17 00:00:00 2001 From: Julien Duponchelle Date: Mon, 27 Apr 2015 10:14:46 +0200 Subject: [PATCH 15/33] Fix VPCS tests --- tests/modules/vpcs/test_vpcs_vm.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/modules/vpcs/test_vpcs_vm.py b/tests/modules/vpcs/test_vpcs_vm.py index 693da5f8..352cb943 100644 --- a/tests/modules/vpcs/test_vpcs_vm.py +++ b/tests/modules/vpcs/test_vpcs_vm.py @@ -97,7 +97,8 @@ def test_stop(loop, vm): loop.run_until_complete(asyncio.async(vm.start())) assert vm.is_running() - loop.run_until_complete(asyncio.async(vm.stop())) + with asyncio_patch("gns3server.utils.asyncio.wait_for_process_termination"): + loop.run_until_complete(asyncio.async(vm.stop())) assert vm.is_running() is False process.terminate.assert_called_with() @@ -117,7 +118,9 @@ def test_reload(loop, vm): vm.port_add_nio_binding(0, nio) loop.run_until_complete(asyncio.async(vm.start())) assert vm.is_running() - loop.run_until_complete(asyncio.async(vm.reload())) + + with asyncio_patch("gns3server.utils.asyncio.wait_for_process_termination"): + loop.run_until_complete(asyncio.async(vm.reload())) assert vm.is_running() is True process.terminate.assert_called_with() From 4df95efdec9018a065b6179c18dc7ffdc1834d7a Mon Sep 17 00:00:00 2001 From: Julien Duponchelle Date: Mon, 27 Apr 2015 15:09:42 +0200 Subject: [PATCH 16/33] Skip IOU test on Windows Fix #159 --- README.rst | 3 +++ gns3server/modules/__init__.py | 8 +++++--- tests/handlers/api/test_iou.py | 3 +++ tests/modules/iou/test_iou_manager.py | 6 +++++- tests/modules/iou/test_iou_vm.py | 12 +++++++++--- 5 files changed, 25 insertions(+), 7 deletions(-) diff --git a/README.rst b/README.rst index 091b5fc9..d70fa7d2 100644 --- a/README.rst +++ b/README.rst @@ -93,6 +93,9 @@ Windows Please use our all-in-one installer. +If you install it via source you need to install also: +https://sourceforge.net/projects/pywin32/ + Mac OS X -------- diff --git a/gns3server/modules/__init__.py b/gns3server/modules/__init__.py index 6a2953ae..928bb1a9 100644 --- a/gns3server/modules/__init__.py +++ b/gns3server/modules/__init__.py @@ -25,6 +25,8 @@ from .qemu import Qemu MODULES = [VPCS, VirtualBox, Dynamips, Qemu] if sys.platform.startswith("linux") or hasattr(sys, "_called_from_test") or os.environ.get("PYTEST_BUILD_DOCUMENTATION") == "1": - # IOU runs only on Linux - from .iou import IOU - MODULES.append(IOU) + + # IOU runs only on Linux but testsuite work on UNIX platform + if not sys.platform.startswith("win"): + from .iou import IOU + MODULES.append(IOU) diff --git a/tests/handlers/api/test_iou.py b/tests/handlers/api/test_iou.py index bc0f8da9..643ebc34 100644 --- a/tests/handlers/api/test_iou.py +++ b/tests/handlers/api/test_iou.py @@ -18,10 +18,13 @@ import pytest import os import stat +import sys from tests.utils import asyncio_patch from unittest.mock import patch, MagicMock, PropertyMock +pytestmark = pytest.mark.skipif(sys.platform.startswith("win"), reason="Not supported on Windows") + @pytest.fixture def fake_iou_bin(tmpdir): diff --git a/tests/modules/iou/test_iou_manager.py b/tests/modules/iou/test_iou_manager.py index 84b3b7e2..7d40fdf1 100644 --- a/tests/modules/iou/test_iou_manager.py +++ b/tests/modules/iou/test_iou_manager.py @@ -20,9 +20,13 @@ import pytest from unittest.mock import patch import uuid import os +import sys +pytestmark = pytest.mark.skipif(sys.platform.startswith("win"), reason="Not supported on Windows") + +if not sys.platform.startswith("win"): + from gns3server.modules.iou import IOU -from gns3server.modules.iou import IOU from gns3server.modules.iou.iou_error import IOUError from gns3server.modules.project_manager import ProjectManager diff --git a/tests/modules/iou/test_iou_vm.py b/tests/modules/iou/test_iou_vm.py index c66cfb99..398c2215 100644 --- a/tests/modules/iou/test_iou_vm.py +++ b/tests/modules/iou/test_iou_vm.py @@ -21,13 +21,19 @@ import asyncio import os import stat import socket +import sys from tests.utils import asyncio_patch from unittest.mock import patch, MagicMock, PropertyMock -from gns3server.modules.iou.iou_vm import IOUVM -from gns3server.modules.iou.iou_error import IOUError -from gns3server.modules.iou import IOU + +pytestmark = pytest.mark.skipif(sys.platform.startswith("win"), reason="Not supported on Windows") + +if not sys.platform.startswith("win"): + from gns3server.modules.iou.iou_vm import IOUVM + from gns3server.modules.iou.iou_error import IOUError + from gns3server.modules.iou import IOU + from gns3server.config import Config From d5ae4750e904102c6b54459690dfd56d072c8028 Mon Sep 17 00:00:00 2001 From: Julien Duponchelle Date: Mon, 27 Apr 2015 16:21:56 +0200 Subject: [PATCH 17/33] Do not load IOU handler on Windows during tests Fix #159 --- gns3server/handlers/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/gns3server/handlers/__init__.py b/gns3server/handlers/__init__.py index 307eece3..53d65e69 100644 --- a/gns3server/handlers/__init__.py +++ b/gns3server/handlers/__init__.py @@ -31,4 +31,6 @@ from gns3server.handlers.upload_handler import UploadHandler from gns3server.handlers.index_handler import IndexHandler if sys.platform.startswith("linux") or hasattr(sys, "_called_from_test") or os.environ.get("PYTEST_BUILD_DOCUMENTATION") == "1": - from gns3server.handlers.api.iou_handler import IOUHandler + # IOU runs only on Linux but testsuite work on UNIX platform + if not sys.platform.startswith("win"): + from gns3server.handlers.api.iou_handler import IOUHandler From 324a4f73d0a675b680935e70fa3fe14e076146f1 Mon Sep 17 00:00:00 2001 From: Julien Duponchelle Date: Fri, 24 Apr 2015 15:12:58 +0200 Subject: [PATCH 18/33] Do not erase the IOU config --- gns3server/handlers/api/iou_handler.py | 2 ++ gns3server/modules/iou/iou_vm.py | 2 +- tests/handlers/api/test_iou.py | 24 +++++++++++++++++++++++- 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/gns3server/handlers/api/iou_handler.py b/gns3server/handlers/api/iou_handler.py index 3314e580..477d31d6 100644 --- a/gns3server/handlers/api/iou_handler.py +++ b/gns3server/handlers/api/iou_handler.py @@ -58,6 +58,8 @@ class IOUHandler: for name, value in request.json.items(): if hasattr(vm, name) and getattr(vm, name) != value: + if name == "initial_config_content" and (vm.initial_config_content and len(vm.initial_config_content) > 0): + continue setattr(vm, name, value) response.set_status(201) response.json(vm) diff --git a/gns3server/modules/iou/iou_vm.py b/gns3server/modules/iou/iou_vm.py index 783b1357..b7214bd4 100644 --- a/gns3server/modules/iou/iou_vm.py +++ b/gns3server/modules/iou/iou_vm.py @@ -973,7 +973,7 @@ class IOUVM(BaseVM): if len(initial_config) == 0 and os.path.exists(initial_config_path): return - with open(initial_config_path, "w+", encoding="utf-8") as f: + with open(initial_config_path, 'w+', encoding='utf-8') as f: if len(initial_config) == 0: f.write('') else: diff --git a/tests/handlers/api/test_iou.py b/tests/handlers/api/test_iou.py index 643ebc34..ebc3c5d1 100644 --- a/tests/handlers/api/test_iou.py +++ b/tests/handlers/api/test_iou.py @@ -19,6 +19,7 @@ import pytest import os import stat import sys +import uuid from tests.utils import asyncio_patch from unittest.mock import patch, MagicMock, PropertyMock @@ -94,11 +95,32 @@ def test_iou_create_with_params(server, project, base_params): assert "initial-config.cfg" in response.json["initial_config"] with open(initial_config_file(project, response.json)) as f: - assert f.read() == params["initial_config_content"] + assert f.read() == "hostname test" assert "iourc" in response.json["iourc_path"] +def test_iou_create_initial_config_already_exist(server, project, base_params): + """We don't erase an initial config if already exist at project creation""" + + vm_id = str(uuid.uuid4()) + initial_config_file_path = initial_config_file(project, {'vm_id': vm_id}) + with open(initial_config_file_path, 'w+') as f: + f.write("echo hello") + + params = base_params + params["vm_id"] = vm_id + params["initial_config_content"] = "hostname test" + + response = server.post("/projects/{project_id}/iou/vms".format(project_id=project.id), params, example=True) + assert response.status == 201 + assert response.route == "/projects/{project_id}/iou/vms" + + assert "initial-config.cfg" in response.json["initial_config"] + with open(initial_config_file(project, response.json)) as f: + assert f.read() == "echo hello" + + def test_iou_get(server, project, vm): response = server.get("/projects/{project_id}/iou/vms/{vm_id}".format(project_id=vm["project_id"], vm_id=vm["vm_id"]), example=True) assert response.status == 200 From b6a935aeb8ceeb240518c6a072c9b16c881e9a07 Mon Sep 17 00:00:00 2001 From: Jeremy Date: Mon, 27 Apr 2015 14:19:17 -0600 Subject: [PATCH 19/33] Return an explicit error when a NIO type is not supported by a VM. --- .../handlers/api/dynamips_vm_handler.py | 6 +- gns3server/handlers/api/iou_handler.py | 9 +- gns3server/handlers/api/qemu_handler.py | 10 +- gns3server/handlers/api/virtualbox_handler.py | 10 +- gns3server/handlers/api/vpcs_handler.py | 10 +- gns3server/schemas/dynamips_vm.py | 141 ---------------- gns3server/schemas/iou.py | 72 -------- gns3server/schemas/nio.py | 158 ++++++++++++++++++ gns3server/schemas/qemu.py | 56 ------- gns3server/schemas/virtualbox.py | 40 ----- gns3server/schemas/vpcs.py | 56 ------- 11 files changed, 188 insertions(+), 380 deletions(-) create mode 100644 gns3server/schemas/nio.py diff --git a/gns3server/handlers/api/dynamips_vm_handler.py b/gns3server/handlers/api/dynamips_vm_handler.py index 8d96f98b..ff3c3707 100644 --- a/gns3server/handlers/api/dynamips_vm_handler.py +++ b/gns3server/handlers/api/dynamips_vm_handler.py @@ -19,11 +19,11 @@ import os import base64 from ...web.route import Route +from ...schemas.nio import NIO_SCHEMA from ...schemas.dynamips_vm import VM_CREATE_SCHEMA from ...schemas.dynamips_vm import VM_UPDATE_SCHEMA from ...schemas.dynamips_vm import VM_CAPTURE_SCHEMA from ...schemas.dynamips_vm import VM_OBJECT_SCHEMA -from ...schemas.dynamips_vm import VM_NIO_SCHEMA from ...schemas.dynamips_vm import VM_CONFIGS_SCHEMA from ...modules.dynamips import Dynamips from ...modules.project_manager import ProjectManager @@ -256,8 +256,8 @@ class DynamipsVMHandler: 404: "Instance doesn't exist" }, description="Add a NIO to a Dynamips VM instance", - input=VM_NIO_SCHEMA, - output=VM_NIO_SCHEMA) + input=NIO_SCHEMA, + output=NIO_SCHEMA) def create_nio(request, response): dynamips_manager = Dynamips.instance() diff --git a/gns3server/handlers/api/iou_handler.py b/gns3server/handlers/api/iou_handler.py index 3314e580..b8378bb8 100644 --- a/gns3server/handlers/api/iou_handler.py +++ b/gns3server/handlers/api/iou_handler.py @@ -19,10 +19,10 @@ import os from aiohttp.web import HTTPConflict from ...web.route import Route +from ...schemas.nio import NIO_SCHEMA from ...schemas.iou import IOU_CREATE_SCHEMA from ...schemas.iou import IOU_UPDATE_SCHEMA from ...schemas.iou import IOU_OBJECT_SCHEMA -from ...schemas.iou import IOU_NIO_SCHEMA from ...schemas.iou import IOU_CAPTURE_SCHEMA from ...schemas.iou import IOU_INITIAL_CONFIG_SCHEMA from ...modules.iou import IOU @@ -200,12 +200,15 @@ class IOUHandler: 404: "Instance doesn't exist" }, description="Add a NIO to a IOU instance", - input=IOU_NIO_SCHEMA, - output=IOU_NIO_SCHEMA) + input=NIO_SCHEMA, + output=NIO_SCHEMA) def create_nio(request, response): iou_manager = IOU.instance() vm = iou_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"]) + nio_type = request.json["type"] + if nio_type not in ("nio_udp", "nio_tap", "nio_generic_ethernet"): + raise HTTPConflict(text="NIO of type {} is not supported".format(nio_type)) nio = iou_manager.create_nio(vm.iouyap_path, request.json) vm.adapter_add_nio_binding(int(request.match_info["adapter_number"]), int(request.match_info["port_number"]), nio) response.set_status(201) diff --git a/gns3server/handlers/api/qemu_handler.py b/gns3server/handlers/api/qemu_handler.py index 714149d6..704b02d7 100644 --- a/gns3server/handlers/api/qemu_handler.py +++ b/gns3server/handlers/api/qemu_handler.py @@ -15,11 +15,12 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +from aiohttp.web import HTTPConflict from ...web.route import Route +from ...schemas.nio import NIO_SCHEMA from ...schemas.qemu import QEMU_CREATE_SCHEMA from ...schemas.qemu import QEMU_UPDATE_SCHEMA from ...schemas.qemu import QEMU_OBJECT_SCHEMA -from ...schemas.qemu import QEMU_NIO_SCHEMA from ...schemas.qemu import QEMU_BINARY_LIST_SCHEMA from ...modules.qemu import Qemu @@ -239,12 +240,15 @@ class QEMUHandler: 404: "Instance doesn't exist" }, description="Add a NIO to a Qemu VM instance", - input=QEMU_NIO_SCHEMA, - output=QEMU_NIO_SCHEMA) + input=NIO_SCHEMA, + output=NIO_SCHEMA) def create_nio(request, response): qemu_manager = Qemu.instance() vm = qemu_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"]) + nio_type = request.json["type"] + if nio_type != "nio_udp": + raise HTTPConflict(text="NIO of type {} is not supported".format(nio_type)) nio = qemu_manager.create_nio(vm.qemu_path, request.json) yield from vm.adapter_add_nio_binding(int(request.match_info["adapter_number"]), nio) response.set_status(201) diff --git a/gns3server/handlers/api/virtualbox_handler.py b/gns3server/handlers/api/virtualbox_handler.py index 008ac2e6..58160cb4 100644 --- a/gns3server/handlers/api/virtualbox_handler.py +++ b/gns3server/handlers/api/virtualbox_handler.py @@ -16,10 +16,11 @@ # along with this program. If not, see . import os +from aiohttp.web import HTTPConflict from ...web.route import Route +from ...schemas.nio import NIO_SCHEMA from ...schemas.virtualbox import VBOX_CREATE_SCHEMA from ...schemas.virtualbox import VBOX_UPDATE_SCHEMA -from ...schemas.virtualbox import VBOX_NIO_SCHEMA from ...schemas.virtualbox import VBOX_CAPTURE_SCHEMA from ...schemas.virtualbox import VBOX_OBJECT_SCHEMA from ...modules.virtualbox import VirtualBox @@ -286,12 +287,15 @@ class VirtualBoxHandler: 404: "Instance doesn't exist" }, description="Add a NIO to a VirtualBox VM instance", - input=VBOX_NIO_SCHEMA, - output=VBOX_NIO_SCHEMA) + input=NIO_SCHEMA, + output=NIO_SCHEMA) def create_nio(request, response): vbox_manager = VirtualBox.instance() vm = vbox_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"]) + nio_type = request.json["type"] + if nio_type != "nio_udp": + raise HTTPConflict(text="NIO of type {} is not supported".format(nio_type)) nio = vbox_manager.create_nio(vbox_manager.vboxmanage_path, request.json) yield from vm.adapter_add_nio_binding(int(request.match_info["adapter_number"]), nio) response.set_status(201) diff --git a/gns3server/handlers/api/vpcs_handler.py b/gns3server/handlers/api/vpcs_handler.py index 588ff50c..ad22ac2b 100644 --- a/gns3server/handlers/api/vpcs_handler.py +++ b/gns3server/handlers/api/vpcs_handler.py @@ -15,11 +15,12 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +from aiohttp.web import HTTPConflict from ...web.route import Route +from ...schemas.nio import NIO_SCHEMA from ...schemas.vpcs import VPCS_CREATE_SCHEMA from ...schemas.vpcs import VPCS_UPDATE_SCHEMA from ...schemas.vpcs import VPCS_OBJECT_SCHEMA -from ...schemas.vpcs import VPCS_NIO_SCHEMA from ...modules.vpcs import VPCS @@ -191,12 +192,15 @@ class VPCSHandler: 404: "Instance doesn't exist" }, description="Add a NIO to a VPCS instance", - input=VPCS_NIO_SCHEMA, - output=VPCS_NIO_SCHEMA) + input=NIO_SCHEMA, + output=NIO_SCHEMA) def create_nio(request, response): vpcs_manager = VPCS.instance() vm = vpcs_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"]) + nio_type = request.json["type"] + if nio_type not in ("nio_udp", "nio_tap"): + raise HTTPConflict(text="NIO of type {} is not supported".format(nio_type)) nio = vpcs_manager.create_nio(vm.vpcs_path, request.json) vm.port_add_nio_binding(int(request.match_info["port_number"]), nio) response.set_status(201) diff --git a/gns3server/schemas/dynamips_vm.py b/gns3server/schemas/dynamips_vm.py index 8888f6a6..25efd419 100644 --- a/gns3server/schemas/dynamips_vm.py +++ b/gns3server/schemas/dynamips_vm.py @@ -473,147 +473,6 @@ VM_UPDATE_SCHEMA = { "additionalProperties": False, } -VM_NIO_SCHEMA = { - "$schema": "http://json-schema.org/draft-04/schema#", - "description": "Request validation to add a NIO for a Dynamips VM instance", - "type": "object", - "definitions": { - "UDP": { - "description": "UDP Network Input/Output", - "properties": { - "type": { - "enum": ["nio_udp"] - }, - "lport": { - "description": "Local port", - "type": "integer", - "minimum": 1, - "maximum": 65535 - }, - "rhost": { - "description": "Remote host", - "type": "string", - "minLength": 1 - }, - "rport": { - "description": "Remote port", - "type": "integer", - "minimum": 1, - "maximum": 65535 - } - }, - "required": ["type", "lport", "rhost", "rport"], - "additionalProperties": False - }, - "Ethernet": { - "description": "Generic Ethernet Network Input/Output", - "properties": { - "type": { - "enum": ["nio_generic_ethernet"] - }, - "ethernet_device": { - "description": "Ethernet device name e.g. eth0", - "type": "string", - "minLength": 1 - }, - }, - "required": ["type", "ethernet_device"], - "additionalProperties": False - }, - "LinuxEthernet": { - "description": "Linux Ethernet Network Input/Output", - "properties": { - "type": { - "enum": ["nio_linux_ethernet"] - }, - "ethernet_device": { - "description": "Ethernet device name e.g. eth0", - "type": "string", - "minLength": 1 - }, - }, - "required": ["type", "ethernet_device"], - "additionalProperties": False - }, - "TAP": { - "description": "TAP Network Input/Output", - "properties": { - "type": { - "enum": ["nio_tap"] - }, - "tap_device": { - "description": "TAP device name e.g. tap0", - "type": "string", - "minLength": 1 - }, - }, - "required": ["type", "tap_device"], - "additionalProperties": False - }, - "UNIX": { - "description": "UNIX Network Input/Output", - "properties": { - "type": { - "enum": ["nio_unix"] - }, - "local_file": { - "description": "path to the UNIX socket file (local)", - "type": "string", - "minLength": 1 - }, - "remote_file": { - "description": "path to the UNIX socket file (remote)", - "type": "string", - "minLength": 1 - }, - }, - "required": ["type", "local_file", "remote_file"], - "additionalProperties": False - }, - "VDE": { - "description": "VDE Network Input/Output", - "properties": { - "type": { - "enum": ["nio_vde"] - }, - "control_file": { - "description": "path to the VDE control file", - "type": "string", - "minLength": 1 - }, - "local_file": { - "description": "path to the VDE control file", - "type": "string", - "minLength": 1 - }, - }, - "required": ["type", "control_file", "local_file"], - "additionalProperties": False - }, - "NULL": { - "description": "NULL Network Input/Output", - "properties": { - "type": { - "enum": ["nio_null"] - }, - }, - "required": ["type"], - "additionalProperties": False - }, - }, - "oneOf": [ - {"$ref": "#/definitions/UDP"}, - {"$ref": "#/definitions/Ethernet"}, - {"$ref": "#/definitions/LinuxEthernet"}, - {"$ref": "#/definitions/TAP"}, - {"$ref": "#/definitions/UNIX"}, - {"$ref": "#/definitions/VDE"}, - {"$ref": "#/definitions/NULL"}, - ], - "additionalProperties": True, - "required": ["type"] -} - VM_CAPTURE_SCHEMA = { "$schema": "http://json-schema.org/draft-04/schema#", "description": "Request validation to start a packet capture on a Dynamips VM instance port", diff --git a/gns3server/schemas/iou.py b/gns3server/schemas/iou.py index 011607ba..40420994 100644 --- a/gns3server/schemas/iou.py +++ b/gns3server/schemas/iou.py @@ -214,78 +214,6 @@ IOU_OBJECT_SCHEMA = { "required": ["name", "vm_id", "console", "project_id", "path", "serial_adapters", "ethernet_adapters", "ram", "nvram", "l1_keepalives", "initial_config", "use_default_iou_values"] } -IOU_NIO_SCHEMA = { - "$schema": "http://json-schema.org/draft-04/schema#", - "description": "Request validation to add a NIO for a IOU instance", - "type": "object", - "definitions": { - "UDP": { - "description": "UDP Network Input/Output", - "properties": { - "type": { - "enum": ["nio_udp"] - }, - "lport": { - "description": "Local port", - "type": "integer", - "minimum": 1, - "maximum": 65535 - }, - "rhost": { - "description": "Remote host", - "type": "string", - "minLength": 1 - }, - "rport": { - "description": "Remote port", - "type": "integer", - "minimum": 1, - "maximum": 65535 - } - }, - "required": ["type", "lport", "rhost", "rport"], - "additionalProperties": False - }, - "Ethernet": { - "description": "Generic Ethernet Network Input/Output", - "properties": { - "type": { - "enum": ["nio_generic_ethernet"] - }, - "ethernet_device": { - "description": "Ethernet device name e.g. eth0", - "type": "string", - "minLength": 1 - }, - }, - "required": ["type", "ethernet_device"], - "additionalProperties": False - }, - "TAP": { - "description": "TAP Network Input/Output", - "properties": { - "type": { - "enum": ["nio_tap"] - }, - "tap_device": { - "description": "TAP device name e.g. tap0", - "type": "string", - "minLength": 1 - }, - }, - "required": ["type", "tap_device"], - "additionalProperties": False - }, - }, - "oneOf": [ - {"$ref": "#/definitions/UDP"}, - {"$ref": "#/definitions/Ethernet"}, - {"$ref": "#/definitions/TAP"}, - ], - "additionalProperties": True, - "required": ["type"] -} - IOU_CAPTURE_SCHEMA = { "$schema": "http://json-schema.org/draft-04/schema#", "description": "Request validation to start a packet capture on a IOU instance", diff --git a/gns3server/schemas/nio.py b/gns3server/schemas/nio.py new file mode 100644 index 00000000..52a97c66 --- /dev/null +++ b/gns3server/schemas/nio.py @@ -0,0 +1,158 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2015 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 . + + +NIO_SCHEMA = { + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "Request validation to add a NIO for a VM instance", + "type": "object", + "definitions": { + "UDP": { + "description": "UDP Network Input/Output", + "properties": { + "type": { + "enum": ["nio_udp"] + }, + "lport": { + "description": "Local port", + "type": "integer", + "minimum": 1, + "maximum": 65535 + }, + "rhost": { + "description": "Remote host", + "type": "string", + "minLength": 1 + }, + "rport": { + "description": "Remote port", + "type": "integer", + "minimum": 1, + "maximum": 65535 + } + }, + "required": ["type", "lport", "rhost", "rport"], + "additionalProperties": False + }, + "Ethernet": { + "description": "Generic Ethernet Network Input/Output", + "properties": { + "type": { + "enum": ["nio_generic_ethernet"] + }, + "ethernet_device": { + "description": "Ethernet device name e.g. eth0", + "type": "string", + "minLength": 1 + }, + }, + "required": ["type", "ethernet_device"], + "additionalProperties": False + }, + "LinuxEthernet": { + "description": "Linux Ethernet Network Input/Output", + "properties": { + "type": { + "enum": ["nio_linux_ethernet"] + }, + "ethernet_device": { + "description": "Ethernet device name e.g. eth0", + "type": "string", + "minLength": 1 + }, + }, + "required": ["type", "ethernet_device"], + "additionalProperties": False + }, + "TAP": { + "description": "TAP Network Input/Output", + "properties": { + "type": { + "enum": ["nio_tap"] + }, + "tap_device": { + "description": "TAP device name e.g. tap0", + "type": "string", + "minLength": 1 + }, + }, + "required": ["type", "tap_device"], + "additionalProperties": False + }, + "UNIX": { + "description": "UNIX Network Input/Output", + "properties": { + "type": { + "enum": ["nio_unix"] + }, + "local_file": { + "description": "path to the UNIX socket file (local)", + "type": "string", + "minLength": 1 + }, + "remote_file": { + "description": "path to the UNIX socket file (remote)", + "type": "string", + "minLength": 1 + }, + }, + "required": ["type", "local_file", "remote_file"], + "additionalProperties": False + }, + "VDE": { + "description": "VDE Network Input/Output", + "properties": { + "type": { + "enum": ["nio_vde"] + }, + "control_file": { + "description": "path to the VDE control file", + "type": "string", + "minLength": 1 + }, + "local_file": { + "description": "path to the VDE control file", + "type": "string", + "minLength": 1 + }, + }, + "required": ["type", "control_file", "local_file"], + "additionalProperties": False + }, + "NULL": { + "description": "NULL Network Input/Output", + "properties": { + "type": { + "enum": ["nio_null"] + }, + }, + "required": ["type"], + "additionalProperties": False + }, + }, + "oneOf": [ + {"$ref": "#/definitions/UDP"}, + {"$ref": "#/definitions/Ethernet"}, + {"$ref": "#/definitions/LinuxEthernet"}, + {"$ref": "#/definitions/TAP"}, + {"$ref": "#/definitions/UNIX"}, + {"$ref": "#/definitions/VDE"}, + {"$ref": "#/definitions/NULL"}, + ], + "additionalProperties": True, + "required": ["type"] +} diff --git a/gns3server/schemas/qemu.py b/gns3server/schemas/qemu.py index dfa45a07..d8a5005b 100644 --- a/gns3server/schemas/qemu.py +++ b/gns3server/schemas/qemu.py @@ -211,62 +211,6 @@ QEMU_UPDATE_SCHEMA = { "additionalProperties": False, } -QEMU_NIO_SCHEMA = { - "$schema": "http://json-schema.org/draft-04/schema#", - "description": "Request validation to add a NIO for a QEMU instance", - "type": "object", - "definitions": { - "UDP": { - "description": "UDP Network Input/Output", - "properties": { - "type": { - "enum": ["nio_udp"] - }, - "lport": { - "description": "Local port", - "type": "integer", - "minimum": 1, - "maximum": 65535 - }, - "rhost": { - "description": "Remote host", - "type": "string", - "minLength": 1 - }, - "rport": { - "description": "Remote port", - "type": "integer", - "minimum": 1, - "maximum": 65535 - } - }, - "required": ["type", "lport", "rhost", "rport"], - "additionalProperties": False - }, - "Ethernet": { - "description": "Generic Ethernet Network Input/Output", - "properties": { - "type": { - "enum": ["nio_generic_ethernet"] - }, - "ethernet_device": { - "description": "Ethernet device name e.g. eth0", - "type": "string", - "minLength": 1 - }, - }, - "required": ["type", "ethernet_device"], - "additionalProperties": False - }, - }, - "oneOf": [ - {"$ref": "#/definitions/UDP"}, - {"$ref": "#/definitions/Ethernet"}, - ], - "additionalProperties": True, - "required": ["type"] -} - QEMU_OBJECT_SCHEMA = { "$schema": "http://json-schema.org/draft-04/schema#", "description": "Request validation for a QEMU VM instance", diff --git a/gns3server/schemas/virtualbox.py b/gns3server/schemas/virtualbox.py index 930cbbbe..8e57c35c 100644 --- a/gns3server/schemas/virtualbox.py +++ b/gns3server/schemas/virtualbox.py @@ -139,46 +139,6 @@ VBOX_UPDATE_SCHEMA = { "additionalProperties": False, } -VBOX_NIO_SCHEMA = { - "$schema": "http://json-schema.org/draft-04/schema#", - "description": "Request validation to add a NIO for a VirtualBox VM instance", - "type": "object", - "definitions": { - "UDP": { - "description": "UDP Network Input/Output", - "properties": { - "type": { - "enum": ["nio_udp"] - }, - "lport": { - "description": "Local port", - "type": "integer", - "minimum": 1, - "maximum": 65535 - }, - "rhost": { - "description": "Remote host", - "type": "string", - "minLength": 1 - }, - "rport": { - "description": "Remote port", - "type": "integer", - "minimum": 1, - "maximum": 65535 - } - }, - "required": ["type", "lport", "rhost", "rport"], - "additionalProperties": False - }, - }, - "oneOf": [ - {"$ref": "#/definitions/UDP"}, - ], - "additionalProperties": True, - "required": ["type"] -} - VBOX_CAPTURE_SCHEMA = { "$schema": "http://json-schema.org/draft-04/schema#", "description": "Request validation to start a packet capture on a VirtualBox VM instance port", diff --git a/gns3server/schemas/vpcs.py b/gns3server/schemas/vpcs.py index 14cad8be..05d60d98 100644 --- a/gns3server/schemas/vpcs.py +++ b/gns3server/schemas/vpcs.py @@ -75,62 +75,6 @@ VPCS_UPDATE_SCHEMA = { "additionalProperties": False, } -VPCS_NIO_SCHEMA = { - "$schema": "http://json-schema.org/draft-04/schema#", - "description": "Request validation to add a NIO for a VPCS instance", - "type": "object", - "definitions": { - "UDP": { - "description": "UDP Network Input/Output", - "properties": { - "type": { - "enum": ["nio_udp"] - }, - "lport": { - "description": "Local port", - "type": "integer", - "minimum": 1, - "maximum": 65535 - }, - "rhost": { - "description": "Remote host", - "type": "string", - "minLength": 1 - }, - "rport": { - "description": "Remote port", - "type": "integer", - "minimum": 1, - "maximum": 65535 - } - }, - "required": ["type", "lport", "rhost", "rport"], - "additionalProperties": False - }, - "TAP": { - "description": "TAP Network Input/Output", - "properties": { - "type": { - "enum": ["nio_tap"] - }, - "tap_device": { - "description": "TAP device name e.g. tap0", - "type": "string", - "minLength": 1 - }, - }, - "required": ["type", "tap_device"], - "additionalProperties": False - }, - }, - "oneOf": [ - {"$ref": "#/definitions/UDP"}, - {"$ref": "#/definitions/TAP"}, - ], - "additionalProperties": True, - "required": ["type"] -} - VPCS_OBJECT_SCHEMA = { "$schema": "http://json-schema.org/draft-04/schema#", "description": "VPCS instance", From f208b472a10f4c3b96ce0b3a7c331b4214629202 Mon Sep 17 00:00:00 2001 From: Jeremy Date: Mon, 27 Apr 2015 14:38:15 -0600 Subject: [PATCH 20/33] TAP interface support for QEMU VMs. Fixes #153. --- gns3server/handlers/api/qemu_handler.py | 2 +- gns3server/modules/base_manager.py | 5 ++-- gns3server/modules/qemu/qemu_vm.py | 33 +++++++++++++++---------- 3 files changed, 24 insertions(+), 16 deletions(-) diff --git a/gns3server/handlers/api/qemu_handler.py b/gns3server/handlers/api/qemu_handler.py index 704b02d7..6ffe59ba 100644 --- a/gns3server/handlers/api/qemu_handler.py +++ b/gns3server/handlers/api/qemu_handler.py @@ -247,7 +247,7 @@ class QEMUHandler: qemu_manager = Qemu.instance() vm = qemu_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"]) nio_type = request.json["type"] - if nio_type != "nio_udp": + if nio_type not in ("nio_udp", "nio_tap"): raise HTTPConflict(text="NIO of type {} is not supported".format(nio_type)) nio = qemu_manager.create_nio(vm.qemu_path, request.json) yield from vm.adapter_add_nio_binding(int(request.match_info["adapter_number"]), nio) diff --git a/gns3server/modules/base_manager.py b/gns3server/modules/base_manager.py index 9dbc6fb8..378b45d9 100644 --- a/gns3server/modules/base_manager.py +++ b/gns3server/modules/base_manager.py @@ -364,8 +364,9 @@ class BaseManager: nio = NIOUDP(lport, rhost, rport) elif nio_settings["type"] == "nio_tap": tap_device = nio_settings["tap_device"] - if not self._has_privileged_access(executable): - raise aiohttp.web.HTTPForbidden(text="{} has no privileged access to {}.".format(executable, tap_device)) + #FIXME: check for permissions on tap device + #if not self._has_privileged_access(executable): + # raise aiohttp.web.HTTPForbidden(text="{} has no privileged access to {}.".format(executable, tap_device)) nio = NIOTAP(tap_device) elif nio_settings["type"] == "nio_generic_ethernet": nio = NIOGenericEthernet(nio_settings["ethernet_device"]) diff --git a/gns3server/modules/qemu/qemu_vm.py b/gns3server/modules/qemu/qemu_vm.py index b63a6111..85788a80 100644 --- a/gns3server/modules/qemu/qemu_vm.py +++ b/gns3server/modules/qemu/qemu_vm.py @@ -32,6 +32,7 @@ import socket from .qemu_error import QemuError from ..adapters.ethernet_adapter import EthernetAdapter from ..nios.nio_udp import NIOUDP +from ..nios.nio_tap import NIOTAP from ..base_vm import BaseVM from ...schemas.qemu import QEMU_OBJECT_SCHEMA @@ -995,19 +996,25 @@ class QemuVM(BaseVM): else: network_options.extend(["-device", "{},mac={},netdev=gns3-{}".format(self._adapter_type, mac, adapter_number)]) nio = adapter.get_nio(0) - if nio and isinstance(nio, NIOUDP): - if self._legacy_networking: - network_options.extend(["-net", "udp,vlan={},name=gns3-{},sport={},dport={},daddr={}".format(adapter_number, - adapter_number, - nio.lport, - nio.rport, - nio.rhost)]) - else: - network_options.extend(["-netdev", "socket,id=gns3-{},udp={}:{},localaddr={}:{}".format(adapter_number, - nio.rhost, - nio.rport, - self._host, - nio.lport)]) + if nio: + if isinstance(nio, NIOUDP): + if self._legacy_networking: + network_options.extend(["-net", "udp,vlan={},name=gns3-{},sport={},dport={},daddr={}".format(adapter_number, + adapter_number, + nio.lport, + nio.rport, + nio.rhost)]) + else: + network_options.extend(["-netdev", "socket,id=gns3-{},udp={}:{},localaddr={}:{}".format(adapter_number, + nio.rhost, + nio.rport, + self._host, + nio.lport)]) + elif isinstance(nio, NIOTAP): + if self._legacy_networking: + network_options.extend(["-net", "tap,name=gns3-{},ifname={}".format(adapter_number, nio.tap_device)]) + else: + network_options.extend(["-netdev", "tap,id=gns3-{},ifname={}".format(adapter_number, nio.tap_device)]) else: if self._legacy_networking: network_options.extend(["-net", "user,vlan={},name=gns3-{}".format(adapter_number, adapter_number)]) From bf3444933ee8bdebf5fa7a62f614cd1276765ec1 Mon Sep 17 00:00:00 2001 From: Julien Duponchelle Date: Mon, 27 Apr 2015 22:54:24 +0200 Subject: [PATCH 21/33] Fix test qemu now raise 409 if nio_ethernet --- tests/handlers/api/test_qemu.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/tests/handlers/api/test_qemu.py b/tests/handlers/api/test_qemu.py index b06189f5..9d715f7d 100644 --- a/tests/handlers/api/test_qemu.py +++ b/tests/handlers/api/test_qemu.py @@ -150,10 +150,7 @@ def test_qemu_nio_create_ethernet(server, vm): "ethernet_device": "eth0", }, example=True) - assert response.status == 201 - assert response.route == "/projects/{project_id}/qemu/vms/{vm_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/nio" - assert response.json["type"] == "nio_generic_ethernet" - assert response.json["ethernet_device"] == "eth0" + assert response.status == 409 def test_qemu_delete_nio(server, vm): From 77f54848e389193a908e37389561323c9ca18c4c Mon Sep 17 00:00:00 2001 From: Julien Duponchelle Date: Mon, 27 Apr 2015 23:12:13 +0200 Subject: [PATCH 22/33] Fix some tests on Windows --- gns3server/modules/project.py | 1 + gns3server/modules/vpcs/vpcs_vm.py | 6 +-- .../modules/dynamips/test_dynamips_manager.py | 3 +- tests/modules/iou/test_iou_manager.py | 2 +- tests/modules/qemu/test_qemu_manager.py | 21 +++++++--- tests/modules/qemu/test_qemu_vm.py | 18 ++++++--- tests/modules/test_manager.py | 40 +++++++++---------- tests/modules/vpcs/test_vpcs_vm.py | 13 +++++- 8 files changed, 66 insertions(+), 38 deletions(-) diff --git a/gns3server/modules/project.py b/gns3server/modules/project.py index 582ef2ae..edb1b6cf 100644 --- a/gns3server/modules/project.py +++ b/gns3server/modules/project.py @@ -102,6 +102,7 @@ class Project: server_config = Config.instance().get_section_config("Server") path = os.path.expanduser(server_config.get("projects_path", "~/GNS3/projects")) + path = os.path.normpath(path) try: os.makedirs(path, exist_ok=True) except OSError as e: diff --git a/gns3server/modules/vpcs/vpcs_vm.py b/gns3server/modules/vpcs/vpcs_vm.py index 02d7f15d..8287a65d 100644 --- a/gns3server/modules/vpcs/vpcs_vm.py +++ b/gns3server/modules/vpcs/vpcs_vm.py @@ -182,12 +182,12 @@ class VPCSVM(BaseVM): try: script_file = os.path.join(self.working_dir, 'startup.vpc') - with open(script_file, "w+", encoding="utf-8") as f: + with open(script_file, "wb+") as f: if startup_script is None: - f.write('') + f.write(b'') else: startup_script = startup_script.replace("%h", self._name) - f.write(startup_script) + f.write(startup_script.encode("utf-8")) except OSError as e: raise VPCSError('Cannot write the startup script file "{}": {}'.format(self.script_file, e)) diff --git a/tests/modules/dynamips/test_dynamips_manager.py b/tests/modules/dynamips/test_dynamips_manager.py index e0ddf9e1..00d48150 100644 --- a/tests/modules/dynamips/test_dynamips_manager.py +++ b/tests/modules/dynamips/test_dynamips_manager.py @@ -18,6 +18,7 @@ import pytest import tempfile +import sys from gns3server.modules.dynamips import Dynamips from gns3server.modules.dynamips.dynamips_error import DynamipsError @@ -36,7 +37,7 @@ def test_vm_invalid_dynamips_path(manager): with pytest.raises(DynamipsError): manager.find_dynamips() - +@pytest.mark.skipif(sys.platform.startswith("win"), reason="Not supported by Windows") def test_vm_non_executable_dynamips_path(manager): tmpfile = tempfile.NamedTemporaryFile() with patch("gns3server.config.Config.get_section_config", return_value={"dynamips_path": tmpfile.name}): diff --git a/tests/modules/iou/test_iou_manager.py b/tests/modules/iou/test_iou_manager.py index 7d40fdf1..0e888c77 100644 --- a/tests/modules/iou/test_iou_manager.py +++ b/tests/modules/iou/test_iou_manager.py @@ -26,8 +26,8 @@ pytestmark = pytest.mark.skipif(sys.platform.startswith("win"), reason="Not supp if not sys.platform.startswith("win"): from gns3server.modules.iou import IOU + from gns3server.modules.iou.iou_error import IOUError -from gns3server.modules.iou.iou_error import IOUError from gns3server.modules.project_manager import ProjectManager diff --git a/tests/modules/qemu/test_qemu_manager.py b/tests/modules/qemu/test_qemu_manager.py index fcdfe477..572fb347 100644 --- a/tests/modules/qemu/test_qemu_manager.py +++ b/tests/modules/qemu/test_qemu_manager.py @@ -18,6 +18,7 @@ import os import stat import asyncio +import sys from gns3server.modules.qemu import Qemu from tests.utils import asyncio_patch @@ -27,7 +28,10 @@ def test_get_qemu_version(loop): with asyncio_patch("gns3server.modules.qemu.subprocess_check_output", return_value="QEMU emulator version 2.2.0, Copyright (c) 2003-2008 Fabrice Bellard") as mock: version = loop.run_until_complete(asyncio.async(Qemu._get_qemu_version("/tmp/qemu-test"))) - assert version == "2.2.0" + if sys.platform.startswith("win"): + assert version == "" + else: + assert version == "2.2.0" def test_binary_list(loop): @@ -43,12 +47,17 @@ def test_binary_list(loop): with asyncio_patch("gns3server.modules.qemu.subprocess_check_output", return_value="QEMU emulator version 2.2.0, Copyright (c) 2003-2008 Fabrice Bellard") as mock: qemus = loop.run_until_complete(asyncio.async(Qemu.binary_list())) - assert {"path": os.path.join(os.environ["PATH"], "qemu-system-x86"), "version": "2.2.0"} in qemus - assert {"path": os.path.join(os.environ["PATH"], "qemu-kvm"), "version": "2.2.0"} in qemus - assert {"path": os.path.join(os.environ["PATH"], "qemu-system-x42"), "version": "2.2.0"} in qemus - assert {"path": os.path.join(os.environ["PATH"], "hello"), "version": "2.2.0"} not in qemus + if sys.platform.startswith("win"): + version = "" + else: + version = "2.2.0" + + assert {"path": os.path.join(os.environ["PATH"], "qemu-system-x86"), "version": version} in qemus + assert {"path": os.path.join(os.environ["PATH"], "qemu-kvm"), "version": version} in qemus + assert {"path": os.path.join(os.environ["PATH"], "qemu-system-x42"), "version": version} in qemus + assert {"path": os.path.join(os.environ["PATH"], "hello"), "version": version} not in qemus def test_get_legacy_vm_workdir(): - assert Qemu.get_legacy_vm_workdir(42, "bla") == "qemu/vm-42" + assert Qemu.get_legacy_vm_workdir(42, "bla") == os.path.join("qemu", "vm-42") diff --git a/tests/modules/qemu/test_qemu_vm.py b/tests/modules/qemu/test_qemu_vm.py index 03f5c81b..a654c622 100644 --- a/tests/modules/qemu/test_qemu_vm.py +++ b/tests/modules/qemu/test_qemu_vm.py @@ -19,6 +19,7 @@ import pytest import aiohttp import asyncio import os +import sys import stat import re from tests.utils import asyncio_patch @@ -51,7 +52,10 @@ def fake_qemu_img_binary(): @pytest.fixture def fake_qemu_binary(): - bin_path = os.path.join(os.environ["PATH"], "qemu_x42") + if sys.platform.startswith("win"): + bin_path = os.path.join(os.environ["PATH"], "qemu_x42.EXE") + else: + bin_path = os.path.join(os.environ["PATH"], "qemu_x42") with open(bin_path, "w+") as f: f.write("1") os.chmod(bin_path, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR) @@ -178,8 +182,9 @@ def test_set_qemu_path(vm, tmpdir, fake_qemu_binary): f.write("1") # Raise because file is not executable - with pytest.raises(QemuError): - vm.qemu_path = path + if not sys.platform.startswith("win"): + with pytest.raises(QemuError): + vm.qemu_path = path os.chmod(path, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR) @@ -204,6 +209,7 @@ def test_disk_options(vm, loop, fake_qemu_img_binary): assert args == (fake_qemu_img_binary, "create", "-f", "qcow2", os.path.join(vm.working_dir, "flash.qcow2"), "256M") +@pytest.mark.skipif(sys.platform.startswith("win"), reason="Not supported on Windows") def test_set_process_priority(vm, loop, fake_qemu_img_binary): with asyncio_patch("asyncio.create_subprocess_exec", return_value=MagicMock()) as process: @@ -273,7 +279,7 @@ def test_build_command(vm, loop, fake_qemu_binary, port_manager): "user,id=gns3-0" ] - +@pytest.mark.skipif(sys.platform.startswith("win"), reason="Not supported on Windows") def test_build_command_without_display(vm, loop, fake_qemu_binary): os.environ["DISPLAY"] = "" @@ -282,7 +288,9 @@ def test_build_command_without_display(vm, loop, fake_qemu_binary): assert "-nographic" in cmd -def test_build_command_witht_invalid_options(vm, loop, fake_qemu_binary): +# Windows accept this kind of mistake +@pytest.mark.skipif(sys.platform.startswith("win"), reason="Not supported on Windows") +def test_build_command_with_invalid_options(vm, loop, fake_qemu_binary): vm.options = "'test" with pytest.raises(QemuError): diff --git a/tests/modules/test_manager.py b/tests/modules/test_manager.py index 19d360e4..9adbc888 100644 --- a/tests/modules/test_manager.py +++ b/tests/modules/test_manager.py @@ -22,15 +22,15 @@ from unittest.mock import patch from gns3server.modules.vpcs import VPCS -from gns3server.modules.iou import IOU +from gns3server.modules.qemu import Qemu @pytest.fixture(scope="function") -def iou(port_manager): - IOU._instance = None - iou = IOU.instance() - iou.port_manager = port_manager - return iou +def qemu(port_manager): + Qemu._instance = None + qemu = Qemu.instance() + qemu.port_manager = port_manager + return qemu def test_create_vm_new_topology(loop, project, port_manager): @@ -93,31 +93,31 @@ def test_create_vm_old_topology(loop, project, tmpdir, port_manager): assert f.read() == "1" -def test_get_abs_image_path(iou, tmpdir): - os.makedirs(str(tmpdir / "IOU")) +def test_get_abs_image_path(qemu, tmpdir): + os.makedirs(str(tmpdir / "QEMU")) path1 = str(tmpdir / "test1.bin") open(path1, 'w+').close() - path2 = str(tmpdir / "IOU" / "test2.bin") + path2 = str(tmpdir / "QEMU" / "test2.bin") open(path2, 'w+').close() with patch("gns3server.config.Config.get_section_config", return_value={"images_path": str(tmpdir)}): - assert iou.get_abs_image_path(path1) == path1 - assert iou.get_abs_image_path(path2) == path2 - assert iou.get_abs_image_path("test2.bin") == path2 - assert iou.get_abs_image_path("../test1.bin") == path1 + assert qemu.get_abs_image_path(path1) == path1 + assert qemu.get_abs_image_path(path2) == path2 + assert qemu.get_abs_image_path("test2.bin") == path2 + assert qemu.get_abs_image_path("../test1.bin") == path1 -def test_get_relative_image_path(iou, tmpdir): - os.makedirs(str(tmpdir / "IOU")) +def test_get_relative_image_path(qemu, tmpdir): + os.makedirs(str(tmpdir / "Qemu")) path1 = str(tmpdir / "test1.bin") open(path1, 'w+').close() - path2 = str(tmpdir / "IOU" / "test2.bin") + path2 = str(tmpdir / "QEMU" / "test2.bin") open(path2, 'w+').close() with patch("gns3server.config.Config.get_section_config", return_value={"images_path": str(tmpdir)}): - assert iou.get_relative_image_path(path1) == path1 - assert iou.get_relative_image_path(path2) == "test2.bin" - assert iou.get_relative_image_path("test2.bin") == "test2.bin" - assert iou.get_relative_image_path("../test1.bin") == path1 + assert qemu.get_relative_image_path(path1) == path1 + assert qemu.get_relative_image_path(path2) == "test2.bin" + assert qemu.get_relative_image_path("test2.bin") == "test2.bin" + assert qemu.get_relative_image_path("../test1.bin") == path1 diff --git a/tests/modules/vpcs/test_vpcs_vm.py b/tests/modules/vpcs/test_vpcs_vm.py index 352cb943..bf4889c5 100644 --- a/tests/modules/vpcs/test_vpcs_vm.py +++ b/tests/modules/vpcs/test_vpcs_vm.py @@ -19,6 +19,7 @@ import pytest import aiohttp import asyncio import os +import sys from tests.utils import asyncio_patch @@ -100,7 +101,11 @@ def test_stop(loop, vm): with asyncio_patch("gns3server.utils.asyncio.wait_for_process_termination"): loop.run_until_complete(asyncio.async(vm.stop())) assert vm.is_running() is False - process.terminate.assert_called_with() + + if sys.platform.startswith("win"): + process.send_signal.assert_called_with(1) + else: + process.terminate.assert_called_with() def test_reload(loop, vm): @@ -122,7 +127,11 @@ def test_reload(loop, vm): with asyncio_patch("gns3server.utils.asyncio.wait_for_process_termination"): loop.run_until_complete(asyncio.async(vm.reload())) assert vm.is_running() is True - process.terminate.assert_called_with() + + if sys.platform.startswith("win"): + process.send_signal.assert_called_with(1) + else: + process.terminate.assert_called_with() def test_add_nio_binding_udp(vm): From 3f26ada08146faa8dc8f4a90bf19813b122fe92f Mon Sep 17 00:00:00 2001 From: Julien Duponchelle Date: Mon, 27 Apr 2015 23:20:01 +0200 Subject: [PATCH 23/33] Comment broken test --- tests/modules/vpcs/test_vpcs_vm.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/modules/vpcs/test_vpcs_vm.py b/tests/modules/vpcs/test_vpcs_vm.py index bf4889c5..3a0e811f 100644 --- a/tests/modules/vpcs/test_vpcs_vm.py +++ b/tests/modules/vpcs/test_vpcs_vm.py @@ -147,13 +147,13 @@ def test_add_nio_binding_tap(vm): assert nio.tap_device == "test" -def test_add_nio_binding_tap_no_privileged_access(vm): - with patch("gns3server.modules.base_manager.BaseManager._has_privileged_access", return_value=False): - with pytest.raises(aiohttp.web.HTTPForbidden): - nio = VPCS.instance().create_nio(vm.vpcs_path, {"type": "nio_tap", "tap_device": "test"}) - vm.port_add_nio_binding(0, nio) - assert vm._ethernet_adapter.ports[0] is None - +# def test_add_nio_binding_tap_no_privileged_access(vm): +# with patch("gns3server.modules.base_manager.BaseManager._has_privileged_access", return_value=False): +# with pytest.raises(aiohttp.web.HTTPForbidden): +# nio = VPCS.instance().create_nio(vm.vpcs_path, {"type": "nio_tap", "tap_device": "test"}) +# vm.port_add_nio_binding(0, nio) +# assert vm._ethernet_adapter.ports[0] is None +# def test_port_remove_nio_binding(vm): nio = VPCS.instance().create_nio(vm.vpcs_path, {"type": "nio_udp", "lport": 4242, "rport": 4243, "rhost": "127.0.0.1"}) From e7ae1776f4425333c9b2d711baeee715ca80629e Mon Sep 17 00:00:00 2001 From: Julien Duponchelle Date: Mon, 27 Apr 2015 23:28:12 +0200 Subject: [PATCH 24/33] Final fixes for windows test suite --- tests/modules/vpcs/test_vpcs_vm.py | 4 ++-- tests/utils/test_asyncio.py | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/modules/vpcs/test_vpcs_vm.py b/tests/modules/vpcs/test_vpcs_vm.py index 3a0e811f..c16e10f0 100644 --- a/tests/modules/vpcs/test_vpcs_vm.py +++ b/tests/modules/vpcs/test_vpcs_vm.py @@ -202,8 +202,8 @@ def test_get_startup_script_using_default_script(vm): vm._script_file = None filepath = os.path.join(vm.working_dir, 'startup.vpc') - with open(filepath, 'w+') as f: - assert f.write(content) + with open(filepath, 'wb+') as f: + assert f.write(content.encode("utf-8")) assert vm.startup_script == content assert vm.script_file == filepath diff --git a/tests/utils/test_asyncio.py b/tests/utils/test_asyncio.py index 96fbde7b..b4fc7ae6 100644 --- a/tests/utils/test_asyncio.py +++ b/tests/utils/test_asyncio.py @@ -18,6 +18,7 @@ import asyncio import pytest +import sys from unittest.mock import MagicMock from gns3server.utils.asyncio import wait_run_in_executor, subprocess_check_output, wait_for_process_termination @@ -43,6 +44,7 @@ def test_exception_wait_run_in_executor(loop): result = loop.run_until_complete(asyncio.async(exec)) +@pytest.mark.skipif(sys.platform.startswith("win"), reason="Not supported on Windows") def test_subprocess_check_output(loop, tmpdir, restore_original_path): path = str(tmpdir / "test") From 8503472c7715e11b6e6b3c42098bf96f9999cfb0 Mon Sep 17 00:00:00 2001 From: Jeremy Date: Mon, 27 Apr 2015 17:10:32 -0600 Subject: [PATCH 25/33] Close VirtualBox VM linked clone disks after the VM is unregistered. Fixes #145. --- gns3server/modules/virtualbox/__init__.py | 1 - gns3server/modules/virtualbox/virtualbox_vm.py | 8 ++++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/gns3server/modules/virtualbox/__init__.py b/gns3server/modules/virtualbox/__init__.py index 04df958f..24f2f684 100644 --- a/gns3server/modules/virtualbox/__init__.py +++ b/gns3server/modules/virtualbox/__init__.py @@ -110,7 +110,6 @@ class VirtualBox(BaseManager): raise VirtualBoxError("VBoxManage has timed out after {} seconds!".format(timeout)) if process.returncode: - # only the first line of the output is useful vboxmanage_error = stderr_data.decode("utf-8", errors="ignore") raise VirtualBoxError("VirtualBox has returned an error: {}".format(vboxmanage_error)) diff --git a/gns3server/modules/virtualbox/virtualbox_vm.py b/gns3server/modules/virtualbox/virtualbox_vm.py index ed1580ab..e0a181be 100644 --- a/gns3server/modules/virtualbox/virtualbox_vm.py +++ b/gns3server/modules/virtualbox/virtualbox_vm.py @@ -321,6 +321,7 @@ class VirtualBoxVM(BaseVM): if self._linked_clone: hdd_table = [] + hdd_files_to_close = [] if os.path.exists(self.working_dir): hdd_files = yield from self._get_all_hdd_files() vm_info = yield from self._get_vm_info() @@ -331,6 +332,7 @@ class VirtualBoxVM(BaseVM): port = match.group(2) device = match.group(3) if value in hdd_files: + hdd_files_to_close.append(value) log.info("VirtualBox VM '{name}' [{id}] detaching HDD {controller} {port} {device}".format(name=self.name, id=self.id, controller=controller, @@ -351,6 +353,12 @@ class VirtualBoxVM(BaseVM): log.info("VirtualBox VM '{name}' [{id}] unregistering".format(name=self.name, id=self.id)) yield from self.manager.execute("unregistervm", [self._name]) + for hdd_file in hdd_files_to_close: + log.info("VirtualBox VM '{name}' [{id}] closing disk {disk}".format(name=self.name, + id=self.id, + disk=os.path.basename(hdd_file))) + yield from self.manager.execute("closemedium", ["disk", hdd_file]) + if hdd_table: try: hdd_info_file = os.path.join(self.working_dir, self._vmname, "hdd_info.json") From a884af983faaf49ac62cdd5d2819ac7ed8b76e2e Mon Sep 17 00:00:00 2001 From: grossmj Date: Mon, 27 Apr 2015 22:23:27 -0600 Subject: [PATCH 26/33] Avoid Cygwin warning with VPCS on Windows. --- gns3server/modules/vpcs/vpcs_vm.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gns3server/modules/vpcs/vpcs_vm.py b/gns3server/modules/vpcs/vpcs_vm.py index 8287a65d..cb7229d7 100644 --- a/gns3server/modules/vpcs/vpcs_vm.py +++ b/gns3server/modules/vpcs/vpcs_vm.py @@ -409,7 +409,7 @@ class VPCSVM(BaseVM): command.extend(["-F"]) # option to avoid the daemonization of VPCS if self.script_file: - command.extend([self.script_file]) + command.extend([os.path.basename(self.script_file)]) return command @property From bad740d32a289801a2df57bdad425225c532db41 Mon Sep 17 00:00:00 2001 From: Julien Duponchelle Date: Tue, 28 Apr 2015 15:31:00 +0200 Subject: [PATCH 27/33] Fix test on Linux --- tests/modules/test_manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/modules/test_manager.py b/tests/modules/test_manager.py index 9adbc888..b75927dd 100644 --- a/tests/modules/test_manager.py +++ b/tests/modules/test_manager.py @@ -109,7 +109,7 @@ def test_get_abs_image_path(qemu, tmpdir): def test_get_relative_image_path(qemu, tmpdir): - os.makedirs(str(tmpdir / "Qemu")) + os.makedirs(str(tmpdir / "QEMU")) path1 = str(tmpdir / "test1.bin") open(path1, 'w+').close() From cc03017739f1baa070ea070b3c47f871a357afc9 Mon Sep 17 00:00:00 2001 From: grossmj Date: Tue, 28 Apr 2015 12:02:21 -0600 Subject: [PATCH 28/33] Cleanup the VirtualBox Media Manager after closing a project. Fixes #145. --- gns3server/modules/virtualbox/__init__.py | 39 +++++++++++++++++++ .../modules/virtualbox/virtualbox_vm.py | 8 ---- 2 files changed, 39 insertions(+), 8 deletions(-) diff --git a/gns3server/modules/virtualbox/__init__.py b/gns3server/modules/virtualbox/__init__.py index 24f2f684..8e7754fb 100644 --- a/gns3server/modules/virtualbox/__init__.py +++ b/gns3server/modules/virtualbox/__init__.py @@ -115,6 +115,45 @@ class VirtualBox(BaseManager): return stdout_data.decode("utf-8", errors="ignore").splitlines() + @asyncio.coroutine + def _find_inaccessible_hdd_files(self): + """ + Finds inaccessible disk files (to clean up the VirtualBox media manager) + """ + + hdds = [] + properties = yield from self.execute("list", ["hdds"]) + flag_inaccessible = False + for prop in properties: + try: + name, value = prop.split(':', 1) + except ValueError: + continue + if name.strip() == "State" and value.strip() == "inaccessible": + flag_inaccessible = True + if flag_inaccessible and name.strip() == "Location": + hdds.append(value.strip()) + flag_inaccessible = False + return reversed(hdds) + + @asyncio.coroutine + def project_closed(self, project): + """ + Called when a project is closed. + + :param project: Project instance + """ + + yield from super().project_closed(project) + hdd_files_to_close = yield from self._find_inaccessible_hdd_files() + for hdd_file in hdd_files_to_close: + log.info("Closing VirtualBox VM disk file {}".format(os.path.basename(hdd_file))) + try: + yield from self.execute("closemedium", ["disk", hdd_file]) + except VirtualBoxError as e: + log.warning("Could not close VirtualBox VM disk file {}: {error}".format(os.path.basename(hdd_file), e)) + continue + @asyncio.coroutine def get_list(self): """ diff --git a/gns3server/modules/virtualbox/virtualbox_vm.py b/gns3server/modules/virtualbox/virtualbox_vm.py index e0a181be..ed1580ab 100644 --- a/gns3server/modules/virtualbox/virtualbox_vm.py +++ b/gns3server/modules/virtualbox/virtualbox_vm.py @@ -321,7 +321,6 @@ class VirtualBoxVM(BaseVM): if self._linked_clone: hdd_table = [] - hdd_files_to_close = [] if os.path.exists(self.working_dir): hdd_files = yield from self._get_all_hdd_files() vm_info = yield from self._get_vm_info() @@ -332,7 +331,6 @@ class VirtualBoxVM(BaseVM): port = match.group(2) device = match.group(3) if value in hdd_files: - hdd_files_to_close.append(value) log.info("VirtualBox VM '{name}' [{id}] detaching HDD {controller} {port} {device}".format(name=self.name, id=self.id, controller=controller, @@ -353,12 +351,6 @@ class VirtualBoxVM(BaseVM): log.info("VirtualBox VM '{name}' [{id}] unregistering".format(name=self.name, id=self.id)) yield from self.manager.execute("unregistervm", [self._name]) - for hdd_file in hdd_files_to_close: - log.info("VirtualBox VM '{name}' [{id}] closing disk {disk}".format(name=self.name, - id=self.id, - disk=os.path.basename(hdd_file))) - yield from self.manager.execute("closemedium", ["disk", hdd_file]) - if hdd_table: try: hdd_info_file = os.path.join(self.working_dir, self._vmname, "hdd_info.json") From 1b4613fbaffe7f7880ab5b9ded7cfb669f776576 Mon Sep 17 00:00:00 2001 From: Julien Duponchelle Date: Tue, 28 Apr 2015 21:05:08 +0200 Subject: [PATCH 29/33] Version 1.3.2 --- CHANGELOG | 32 ++++++++++++++++++++++++++++++++ gns3server/version.py | 4 ++-- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 63391c35..de9e5d3d 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,37 @@ # Change Log +## 1.3.2 28/04/2015 + +* Cleanup the VirtualBox Media Manager after closing a project. +* Avoid Cygwin warning with VPCS on Windows. +* Close VirtualBox VM linked clone disks after the VM is unregistered. +* TAP interface support for QEMU VMs. +* Return an explicit error when a NIO type is not supported by a VM. +* Do not erase the IOU config +* Explicit utf-8 decoding. +* Check NIO exists when stopping an IOU capture. +* Fixes c7200 NPE setting. +* Fixes VPCS process termination. +* Catch FileNotFoundError exception in os.getcwd() +* Explicit utf-8 encoding where necessary to avoid Unicode errors on Windows (we require/set an utf-8 locale on other systems). +* Fixes #270. Relative paths management with empty ones. +* New crash report key and doesn't send report for developers +* Catch COM errors when connecting to WMI. +* Don't assume the PATH environment variable exists. +* Use UUIDs instead of the VM names for VirtualBox pipe paths. +* Add --log options for daemon support +* Basic upstart script +* Add qemu-kvm to the list of binary +* Fix IOU licence check flag +* Config paths are not used when updating Dynamips or IOU VM settings. +* Fixes initial-configs that were not restored when opening a project containing IOU VMs. +* Prevent parallel execution of VBox commands +* Fix a crash when in some cases you can't access to VBOX state +* Fix crash if VirtualBox doesn't return API version +* Fix a crash in VirtualBox vm creation +* Allocate random names for Dynamips NIOs. +* Explicitly delete Dynamips NIOs and unmap VCs for ATM and Frame-Relay switches. + ## 1.3.1 11/04/2015 * Release diff --git a/gns3server/version.py b/gns3server/version.py index 136f51bb..4cb4b35d 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.3.2.dev1" -__version_info__ = (1, 3, 2, -99) +__version__ = "1.3.2" +__version_info__ = (1, 3, 2, 0) From 461e3ce53f4c020bdcd980127cb0e9d62dbabe83 Mon Sep 17 00:00:00 2001 From: Julien Duponchelle Date: Tue, 28 Apr 2015 21:49:48 +0200 Subject: [PATCH 30/33] 1.3.3dev1 --- gns3server/version.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gns3server/version.py b/gns3server/version.py index 4cb4b35d..8214d8ac 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.3.2" -__version_info__ = (1, 3, 2, 0) +__version__ = "1.3.3dev1" +__version_info__ = (1, 3, 3, -99) From 0311a0086ef664dde3c23696cfa6d8c7deef0716 Mon Sep 17 00:00:00 2001 From: grossmj Date: Tue, 28 Apr 2015 22:16:15 -0600 Subject: [PATCH 31/33] Fixes typo. --- gns3server/modules/virtualbox/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gns3server/modules/virtualbox/__init__.py b/gns3server/modules/virtualbox/__init__.py index 8e7754fb..ce1faece 100644 --- a/gns3server/modules/virtualbox/__init__.py +++ b/gns3server/modules/virtualbox/__init__.py @@ -151,7 +151,7 @@ class VirtualBox(BaseManager): try: yield from self.execute("closemedium", ["disk", hdd_file]) except VirtualBoxError as e: - log.warning("Could not close VirtualBox VM disk file {}: {error}".format(os.path.basename(hdd_file), e)) + log.warning("Could not close VirtualBox VM disk file {}: {}".format(os.path.basename(hdd_file), e)) continue @asyncio.coroutine From e75fbc9d735d68bf42f2ec41c2cd0fab7cb26f78 Mon Sep 17 00:00:00 2001 From: Julien Duponchelle Date: Wed, 29 Apr 2015 11:15:06 +0200 Subject: [PATCH 32/33] Catch connection reset errors Fix #162 --- gns3server/web/route.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/gns3server/web/route.py b/gns3server/web/route.py index 875298f5..f376b820 100644 --- a/gns3server/web/route.py +++ b/gns3server/web/route.py @@ -161,6 +161,11 @@ class Route(object): response = Response(request=request, route=route) response.set_status(408) response.json({"message": "Client disconnected", "status": 408}) + except ConnectionResetError: + log.error("Client connection reset") + response = Response(request=request, route=route) + response.set_status(408) + response.json({"message": "Connection reset", "status": 408}) except Exception as e: log.error("Uncaught exception detected: {type}".format(type=type(e)), exc_info=1) response = Response(request=request, route=route) From b9bc73fd0109a6195c714c0e910bcfd0805b2192 Mon Sep 17 00:00:00 2001 From: Julien Duponchelle Date: Wed, 29 Apr 2015 14:21:02 +0200 Subject: [PATCH 33/33] Do not crash when closing a project if VirtualBox is not accessible Fix #164 --- gns3server/modules/virtualbox/__init__.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/gns3server/modules/virtualbox/__init__.py b/gns3server/modules/virtualbox/__init__.py index ce1faece..94c62435 100644 --- a/gns3server/modules/virtualbox/__init__.py +++ b/gns3server/modules/virtualbox/__init__.py @@ -122,7 +122,12 @@ class VirtualBox(BaseManager): """ hdds = [] - properties = yield from self.execute("list", ["hdds"]) + try: + properties = yield from self.execute("list", ["hdds"]) + # If VirtualBox is not available we have no inaccessible hdd + except VirtualBoxError: + return hdds + flag_inaccessible = False for prop in properties: try: