From 221a35baae3bd14eaf176bb4630842d96902a3ee Mon Sep 17 00:00:00 2001 From: grossmj Date: Thu, 14 May 2015 20:54:38 -0600 Subject: [PATCH 01/25] Adds NAT NIO in device schema validation so they can return an error that it is not supported. --- gns3server/schemas/dynamips_device.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/gns3server/schemas/dynamips_device.py b/gns3server/schemas/dynamips_device.py index 9f173c49..201cd805 100644 --- a/gns3server/schemas/dynamips_device.py +++ b/gns3server/schemas/dynamips_device.py @@ -218,6 +218,16 @@ DEVICE_NIO_SCHEMA = { "required": ["type", "ethernet_device"], "additionalProperties": False }, + "NAT": { + "description": "NAT Network Input/Output", + "properties": { + "type": { + "enum": ["nio_nat"] + }, + }, + "required": ["type"], + "additionalProperties": False + }, "TAP": { "description": "TAP Network Input/Output", "properties": { @@ -291,6 +301,7 @@ DEVICE_NIO_SCHEMA = { {"$ref": "#/definitions/UDP"}, {"$ref": "#/definitions/Ethernet"}, {"$ref": "#/definitions/LinuxEthernet"}, + {"$ref": "#/definitions/NAT"}, {"$ref": "#/definitions/TAP"}, {"$ref": "#/definitions/UNIX"}, {"$ref": "#/definitions/VDE"}, From 0c5b753211b8052d7758699c1de7d42722cf8d03 Mon Sep 17 00:00:00 2001 From: Julien Duponchelle Date: Sun, 17 May 2015 12:45:09 +0200 Subject: [PATCH 02/25] Add the fault handler in order to try to get a proper crash stack --- gns3server/crash_report.py | 4 ++++ gns3server/main.py | 2 ++ 2 files changed, 6 insertions(+) diff --git a/gns3server/crash_report.py b/gns3server/crash_report.py index 23ee7bc0..2cefe0e4 100644 --- a/gns3server/crash_report.py +++ b/gns3server/crash_report.py @@ -19,6 +19,10 @@ import os import sys import struct import platform +import faulthandler + +# Display a traceback in case of segfault crash. Usefull when frozen +faulthandler.enable() try: import raven diff --git a/gns3server/main.py b/gns3server/main.py index e0bf9af2..2cdf3653 100644 --- a/gns3server/main.py +++ b/gns3server/main.py @@ -190,6 +190,8 @@ def main(): port = int(server_config["port"]) server = Server.instance(host, port) try: + import faulthandler + faulthandler._sigsegv() server.run() except OSError as e: # This is to ignore OSError: [WinError 0] The operation completed successfully exception on Windows. From 8c9758d16bea7d52a5bbace2788dda3e3d3e08a1 Mon Sep 17 00:00:00 2001 From: Julien Duponchelle Date: Sun, 17 May 2015 23:10:50 +0200 Subject: [PATCH 03/25] I'm stupid... Remove fake segfault --- gns3server/main.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/gns3server/main.py b/gns3server/main.py index 2cdf3653..e0bf9af2 100644 --- a/gns3server/main.py +++ b/gns3server/main.py @@ -190,8 +190,6 @@ def main(): port = int(server_config["port"]) server = Server.instance(host, port) try: - import faulthandler - faulthandler._sigsegv() server.run() except OSError as e: # This is to ignore OSError: [WinError 0] The operation completed successfully exception on Windows. From 9fa873751d2f6332299ded02abd980c339886bcd Mon Sep 17 00:00:00 2001 From: Julien Duponchelle Date: Mon, 18 May 2015 11:58:56 +0200 Subject: [PATCH 04/25] Fix crash launching qemu on OSX from another location. It's append only when frozen an you launch the server by hand. Fix #194 --- 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 6dd532ba..70c1bf55 100644 --- a/gns3server/modules/qemu/__init__.py +++ b/gns3server/modules/qemu/__init__.py @@ -74,7 +74,11 @@ class Qemu(BaseManager): # add specific locations on Mac OS X regardless of what's in $PATH paths.extend(["/usr/local/bin", "/opt/local/bin"]) if hasattr(sys, "frozen"): - paths.append(os.path.abspath(os.path.join(os.getcwd(), "../../../qemu/bin/"))) + try: + paths.append(os.path.abspath(os.path.join(os.getcwd(), "../../../qemu/bin/"))) + # If the user run the server by hand from outside + except FileNotFoundError: + paths.append(["/Applications/GNS3.app/Contents/Resources/qemu/bin"]) for path in paths: try: for f in os.listdir(path): From f8d95291faca9445fc956053a8c914fb7e447fd7 Mon Sep 17 00:00:00 2001 From: Julien Duponchelle Date: Thu, 21 May 2015 10:45:07 +0200 Subject: [PATCH 05/25] Test interfaces (it seem it's crash on Travis) --- tests/utils/test_interfaces.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 tests/utils/test_interfaces.py diff --git a/tests/utils/test_interfaces.py b/tests/utils/test_interfaces.py new file mode 100644 index 00000000..9e899872 --- /dev/null +++ b/tests/utils/test_interfaces.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2014 GNS3 Technologies Inc. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + + +from gns3server.utils.interfaces import interfaces + + +def test_interfaces(): + # This test should pass on all platforms without crash + assert isinstance(interfaces(), list) From 21cc41fd168a5e117781d2fbafa47e63ffff359b Mon Sep 17 00:00:00 2001 From: Julien Duponchelle Date: Thu, 21 May 2015 11:05:04 +0200 Subject: [PATCH 06/25] Drop coveralls because it's create trouble to tests run on Windows --- dev-requirements.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/dev-requirements.txt b/dev-requirements.txt index af76ab92..45c6d0e3 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -6,4 +6,3 @@ pep8==1.5.7 pytest-timeout pytest-capturelog pytest-cov -python-coveralls From 4d50d00b3e47fa85359ef7a276b0f68de3129dd8 Mon Sep 17 00:00:00 2001 From: Julien Duponchelle Date: Thu, 21 May 2015 11:46:55 +0200 Subject: [PATCH 07/25] Fix test suite on Windows --- tests/handlers/api/test_vpcs.py | 4 ++-- tests/modules/vpcs/test_vpcs_vm.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/handlers/api/test_vpcs.py b/tests/handlers/api/test_vpcs.py index 473a533e..5da78bc1 100644 --- a/tests/handlers/api/test_vpcs.py +++ b/tests/handlers/api/test_vpcs.py @@ -42,7 +42,7 @@ def test_vpcs_get(server, project, vm): assert response.route == "/projects/{project_id}/vpcs/vms/{vm_id}" assert response.json["name"] == "PC TEST 1" assert response.json["project_id"] == project.id - assert response.json["startup_script_path"] == None + assert response.json["startup_script_path"] is None def test_vpcs_create_startup_script(server, project): @@ -51,7 +51,7 @@ def test_vpcs_create_startup_script(server, project): assert response.route == "/projects/{project_id}/vpcs/vms" assert response.json["name"] == "PC TEST 1" assert response.json["project_id"] == project.id - assert response.json["startup_script"] == "ip 192.168.1.2\necho TEST" + assert response.json["startup_script"] == os.linesep.join(["ip 192.168.1.2", "echo TEST"]) assert response.json["startup_script_path"] == "startup.vpc" diff --git a/tests/modules/vpcs/test_vpcs_vm.py b/tests/modules/vpcs/test_vpcs_vm.py index c16e10f0..ad954640 100644 --- a/tests/modules/vpcs/test_vpcs_vm.py +++ b/tests/modules/vpcs/test_vpcs_vm.py @@ -190,7 +190,7 @@ def test_update_startup_script_h(vm): def test_get_startup_script(vm): - content = "echo GNS3 VPCS\nip 192.168.1.2\n" + content = os.linesep.join(["echo GNS3 VPCS", "ip 192.168.1.2"]) vm.startup_script = content assert vm.startup_script == content From 33bca1a85ccf3d1bf91de7dca8689d9216abb8d3 Mon Sep 17 00:00:00 2001 From: Julien Duponchelle Date: Thu, 21 May 2015 12:01:37 +0200 Subject: [PATCH 08/25] Fix tests on Windows --- tests/modules/vpcs/test_vpcs_vm.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/modules/vpcs/test_vpcs_vm.py b/tests/modules/vpcs/test_vpcs_vm.py index ad954640..e68559fe 100644 --- a/tests/modules/vpcs/test_vpcs_vm.py +++ b/tests/modules/vpcs/test_vpcs_vm.py @@ -192,7 +192,7 @@ def test_update_startup_script_h(vm): def test_get_startup_script(vm): content = os.linesep.join(["echo GNS3 VPCS", "ip 192.168.1.2"]) vm.startup_script = content - assert vm.startup_script == content + assert vm.startup_script == "echo GNS3 VPCS\nip 192.168.1.2" def test_get_startup_script_using_default_script(vm): From 08f82e02a069c2b7bc95d2ffde6ee09d98776d45 Mon Sep 17 00:00:00 2001 From: grossmj Date: Mon, 25 May 2015 19:07:12 -0600 Subject: [PATCH 09/25] Fixes TAP connection when using VPCS. --- gns3server/modules/vpcs/vpcs_vm.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/gns3server/modules/vpcs/vpcs_vm.py b/gns3server/modules/vpcs/vpcs_vm.py index 2f001ced..7321f0c6 100644 --- a/gns3server/modules/vpcs/vpcs_vm.py +++ b/gns3server/modules/vpcs/vpcs_vm.py @@ -390,6 +390,9 @@ class VPCSVM(BaseVM): command = [self.vpcs_path] command.extend(["-p", str(self._console)]) # listen to console port + command.extend(["-m", str(self._manager.get_mac_id(self.id))]) # the unique ID is used to set the MAC address offset + command.extend(["-i", "1"]) # option to start only one VPC instance + command.extend(["-F"]) # option to avoid the daemonization of VPCS nio = self._ethernet_adapter.get_nio(0) if nio: @@ -404,10 +407,6 @@ class VPCSVM(BaseVM): command.extend(["-e"]) command.extend(["-d", nio.tap_device]) - command.extend(["-m", str(self._manager.get_mac_id(self.id))]) # the unique ID is used to set the MAC address offset - command.extend(["-i", "1"]) # option to start only one VPC instance - command.extend(["-F"]) # option to avoid the daemonization of VPCS - if self.script_file: command.extend([os.path.basename(self.script_file)]) return command From 8636d3e3377f6cd7a3309c16f595980893c58755 Mon Sep 17 00:00:00 2001 From: Julien Duponchelle Date: Tue, 26 May 2015 09:48:01 +0200 Subject: [PATCH 10/25] Use setter for the qemu_path (allow to pass only the binary name) --- gns3server/modules/qemu/qemu_vm.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gns3server/modules/qemu/qemu_vm.py b/gns3server/modules/qemu/qemu_vm.py index 69015251..e93e4630 100644 --- a/gns3server/modules/qemu/qemu_vm.py +++ b/gns3server/modules/qemu/qemu_vm.py @@ -69,7 +69,7 @@ class QemuVM(BaseVM): self._stdout_file = "" # QEMU VM settings - self._qemu_path = qemu_path + self.qemu_path = qemu_path self._hda_disk_image = "" self._hdb_disk_image = "" self._hdc_disk_image = "" From dfd18f948354d20c1838ce137adc2b6b6bb30ef5 Mon Sep 17 00:00:00 2001 From: Julien Duponchelle Date: Tue, 26 May 2015 10:56:35 +0200 Subject: [PATCH 11/25] Travis install netifaces --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index f52a902e..7fa6ede7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,6 +7,7 @@ before_install: - sudo add-apt-repository ppa:gns3/ppa -y - sudo apt-get update -q - sudo apt-get install dynamips + - sudo apt-get install python3-netifaces install: - python setup.py install From c03c66ec48815ec68a5987dd82c2283058b9aa0c Mon Sep 17 00:00:00 2001 From: Julien Duponchelle Date: Tue, 26 May 2015 11:35:06 +0200 Subject: [PATCH 12/25] Fix tests crash on travis --- .travis.yml | 1 - tests/handlers/api/test_network.py | 5 +++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 7fa6ede7..f52a902e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,7 +7,6 @@ before_install: - sudo add-apt-repository ppa:gns3/ppa -y - sudo apt-get update -q - sudo apt-get install dynamips - - sudo apt-get install python3-netifaces install: - python setup.py install diff --git a/tests/handlers/api/test_network.py b/tests/handlers/api/test_network.py index d675d4f6..1bad3a8a 100644 --- a/tests/handlers/api/test_network.py +++ b/tests/handlers/api/test_network.py @@ -15,6 +15,9 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +import os +import pytest + def test_udp_allocation(server, project): response = server.post('/projects/{}/ports/udp'.format(project.id), {}, example=True) @@ -22,6 +25,8 @@ def test_udp_allocation(server, project): assert response.json == {'udp_port': 10000} +# Netfifaces is not available on Travis +@pytest.skipif(os.environ.get("TRAVIS", False)) def test_interfaces(server): response = server.get('/interfaces', example=True) assert response.status == 200 From 4216724d0babbcf1c2e08938dcf8c3b194803ee5 Mon Sep 17 00:00:00 2001 From: Julien Duponchelle Date: Tue, 26 May 2015 12:00:13 +0200 Subject: [PATCH 13/25] Give a reason for travis skip test --- tests/handlers/api/test_network.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/handlers/api/test_network.py b/tests/handlers/api/test_network.py index 1bad3a8a..0818fecd 100644 --- a/tests/handlers/api/test_network.py +++ b/tests/handlers/api/test_network.py @@ -26,7 +26,7 @@ def test_udp_allocation(server, project): # Netfifaces is not available on Travis -@pytest.skipif(os.environ.get("TRAVIS", False)) +@pytest.mark.skipif(os.environ.get("TRAVIS", False), reason="Not supported on Travis") def test_interfaces(server): response = server.get('/interfaces', example=True) assert response.status == 200 From 50d7a4f3353825cb6cb480ea7ebaec1f778772ce Mon Sep 17 00:00:00 2001 From: Julien Duponchelle Date: Tue, 26 May 2015 13:05:37 +0200 Subject: [PATCH 14/25] Catch encoding errors in windows logger --- gns3server/web/logger.py | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/gns3server/web/logger.py b/gns3server/web/logger.py index 1fe40ffa..609bcbd3 100644 --- a/gns3server/web/logger.py +++ b/gns3server/web/logger.py @@ -81,12 +81,30 @@ class ColouredStreamHandler(logging.StreamHandler): self.handleError(record) +class WinStreamHandler(logging.StreamHandler): + + def emit(self, record): + + if sys.stdin.encoding != "utf-8": + record = record + + stream = self.stream + try: + msg = self.formatter.format(record, stream.isatty()) + stream.write(msg.encode(stream.encoding, errors="replace").decode(stream.encoding)) + stream.write(self.terminator) + self.flush() + pass + except Exception: + self.handleError(record) + + def init_logger(level, logfile=None, quiet=False): if logfile and len(logfile) > 0: stream_handler = logging.FileHandler(logfile) stream_handler.formatter = ColouredFormatter("{asctime} {levelname} {filename}:{lineno} {message}", "%Y-%m-%d %H:%M:%S", "{") elif sys.platform.startswith("win"): - stream_handler = logging.StreamHandler(sys.stdout) + stream_handler = WinStreamHandler(sys.stdout) stream_handler.formatter = ColouredFormatter("{asctime} {levelname} {filename}:{lineno} {message}", "%Y-%m-%d %H:%M:%S", "{") else: stream_handler = ColouredStreamHandler(sys.stdout) From d31420b3e1ad8b01568e04bb4345cc1f9eb51fde Mon Sep 17 00:00:00 2001 From: Julien Duponchelle Date: Tue, 26 May 2015 13:06:08 +0200 Subject: [PATCH 15/25] Script for starting gns3server in development mode on Windows --- gns3server.bat | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 gns3server.bat diff --git a/gns3server.bat b/gns3server.bat new file mode 100644 index 00000000..ff132074 --- /dev/null +++ b/gns3server.bat @@ -0,0 +1,2 @@ +SET PYTHONPATH=. +python.exe gns3server/main.py --debug --local From 528bb7a7c6d4787fe6892c26eb400cd6cac88b0b Mon Sep 17 00:00:00 2001 From: Julien Duponchelle Date: Tue, 26 May 2015 13:26:07 +0200 Subject: [PATCH 16/25] Do not return error if we can't remove the old project directory --- gns3server/modules/project.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gns3server/modules/project.py b/gns3server/modules/project.py index c162e73e..7018d1b7 100644 --- a/gns3server/modules/project.py +++ b/gns3server/modules/project.py @@ -152,7 +152,7 @@ class Project: try: shutil.rmtree(old_path) except OSError as e: - raise aiohttp.web.HTTPConflict(text="Can't remove temporary directory {}: {}".format(old_path, e)) + log.warn("Can't remove temporary directory {}: {}".format(old_path, e)) @property def name(self): From c98bcedd39e53acf10e2c974cc6644902765c4b0 Mon Sep 17 00:00:00 2001 From: Julien Duponchelle Date: Tue, 26 May 2015 15:20:14 +0200 Subject: [PATCH 17/25] Cleanup the temporary project after modules have been notified of the path change --- gns3server/handlers/api/project_handler.py | 2 ++ gns3server/modules/project.py | 12 +++++--- tests/handlers/api/test_project.py | 34 +++++++++++++++++++--- tests/modules/test_project.py | 4 --- 4 files changed, 40 insertions(+), 12 deletions(-) diff --git a/gns3server/handlers/api/project_handler.py b/gns3server/handlers/api/project_handler.py index a67d9c57..85257423 100644 --- a/gns3server/handlers/api/project_handler.py +++ b/gns3server/handlers/api/project_handler.py @@ -84,9 +84,11 @@ class ProjectHandler: project.name = request.json.get("name", project.name) project_path = request.json.get("path", project.path) if project_path != project.path: + old_path = project.path project.path = project_path for module in MODULES: yield from module.instance().project_moved(project) + yield from project.clean_old_path(old_path) # Very important we need to remove temporary flag after moving the project project.temporary = request.json.get("temporary", project.temporary) response.json(project) diff --git a/gns3server/modules/project.py b/gns3server/modules/project.py index 7018d1b7..0c87cc37 100644 --- a/gns3server/modules/project.py +++ b/gns3server/modules/project.py @@ -146,11 +146,15 @@ class Project: self._path = path self._update_temporary_file() - # The order of operation is important because we want to avoid losing - # data - if old_path: + @asyncio.coroutine + def clean_old_path(self, old_path): + """ + Called after a project location change. All the modules should + have been notified before + """ + if self._temporary: try: - shutil.rmtree(old_path) + yield from wait_run_in_executor(shutil.rmtree, old_path) except OSError as e: log.warn("Can't remove temporary directory {}: {}".format(old_path, e)) diff --git a/tests/handlers/api/test_project.py b/tests/handlers/api/test_project.py index cd0bc419..6f255221 100644 --- a/tests/handlers/api/test_project.py +++ b/tests/handlers/api/test_project.py @@ -20,6 +20,7 @@ This test suite check /project endpoint """ import uuid +import os from unittest.mock import patch from tests.utils import asyncio_patch @@ -85,18 +86,43 @@ def test_update_temporary_project(server): assert response.json["temporary"] is False -def test_update_path_project(server, tmpdir): +def test_update_path_project_temporary(server, tmpdir): + + os.makedirs(str(tmpdir / "a")) + os.makedirs(str(tmpdir / "b")) with patch("gns3server.modules.project.Project.is_local", return_value=True): - response = server.post("/projects", {"name": "first_name"}) + response = server.post("/projects", {"name": "first_name", "path": str(tmpdir / "a"), "temporary": True}) assert response.status == 201 assert response.json["name"] == "first_name" - query = {"name": "second_name", "path": str(tmpdir)} + query = {"name": "second_name", "path": str(tmpdir / "b")} response = server.put("/projects/{project_id}".format(project_id=response.json["project_id"]), query, example=True) assert response.status == 200 - assert response.json["path"] == str(tmpdir) + assert response.json["path"] == str(tmpdir / "b") assert response.json["name"] == "second_name" + assert not os.path.exists(str(tmpdir / "a")) + assert os.path.exists(str(tmpdir / "b")) + + +def test_update_path_project_non_temporary(server, tmpdir): + + os.makedirs(str(tmpdir / "a")) + os.makedirs(str(tmpdir / "b")) + + with patch("gns3server.modules.project.Project.is_local", return_value=True): + response = server.post("/projects", {"name": "first_name", "path": str(tmpdir / "a")}) + assert response.status == 201 + assert response.json["name"] == "first_name" + query = {"name": "second_name", "path": str(tmpdir / "b")} + response = server.put("/projects/{project_id}".format(project_id=response.json["project_id"]), query, example=True) + assert response.status == 200 + assert response.json["path"] == str(tmpdir / "b") + assert response.json["name"] == "second_name" + + assert os.path.exists(str(tmpdir / "a")) + assert os.path.exists(str(tmpdir / "b")) + def test_update_path_project_non_local(server, tmpdir): diff --git a/tests/modules/test_project.py b/tests/modules/test_project.py index c4eaee4b..68896361 100644 --- a/tests/modules/test_project.py +++ b/tests/modules/test_project.py @@ -73,10 +73,6 @@ def test_changing_path_temporary_flag(tmpdir): assert os.path.exists(os.path.join(p.path, ".gns3_temporary")) p.path = str(tmpdir) - p.temporary = False - assert not os.path.exists(os.path.join(p.path, ".gns3_temporary")) - assert not os.path.exists(os.path.join(str(tmpdir), ".gns3_temporary")) - assert not os.path.exists(original_path) def test_temporary_path(): From ea67f4aeb97b790ba21e12aa916fc5adff32187e Mon Sep 17 00:00:00 2001 From: Julien Duponchelle Date: Tue, 26 May 2015 11:51:24 +0200 Subject: [PATCH 18/25] Test ok on Windows --- tests/modules/vpcs/test_vpcs_vm.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/modules/vpcs/test_vpcs_vm.py b/tests/modules/vpcs/test_vpcs_vm.py index e68559fe..df62957f 100644 --- a/tests/modules/vpcs/test_vpcs_vm.py +++ b/tests/modules/vpcs/test_vpcs_vm.py @@ -190,9 +190,9 @@ def test_update_startup_script_h(vm): def test_get_startup_script(vm): - content = os.linesep.join(["echo GNS3 VPCS", "ip 192.168.1.2"]) + content = "echo GNS3 VPCS\nip 192.168.1.2" vm.startup_script = content - assert vm.startup_script == "echo GNS3 VPCS\nip 192.168.1.2" + assert vm.startup_script == os.linesep.join(["echo GNS3 VPCS","ip 192.168.1.2"]) def test_get_startup_script_using_default_script(vm): From ecf4e91e5528780689172572ddfda0934ea87778 Mon Sep 17 00:00:00 2001 From: Julien Duponchelle Date: Wed, 27 May 2015 16:21:18 +0200 Subject: [PATCH 19/25] Ignore VirtualBox VM Name with a carriage return in name Add tests for get_list of VirtualBox Fix #200 --- gns3server/modules/virtualbox/__init__.py | 2 ++ .../virtualbox/test_virtualbox_manager.py | 34 ++++++++++++++++++- 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/gns3server/modules/virtualbox/__init__.py b/gns3server/modules/virtualbox/__init__.py index 94c62435..509b7182 100644 --- a/gns3server/modules/virtualbox/__init__.py +++ b/gns3server/modules/virtualbox/__init__.py @@ -168,6 +168,8 @@ class VirtualBox(BaseManager): vms = [] result = yield from self.execute("list", ["vms"]) for line in result: + if line[0] != '"' or line[-1:] != "}": + continue # Broken output (perhaps a carriage return in VM name vmname, _ = line.rsplit(' ', 1) vmname = vmname.strip('"') if vmname == "": diff --git a/tests/modules/virtualbox/test_virtualbox_manager.py b/tests/modules/virtualbox/test_virtualbox_manager.py index f6185bed..54289554 100644 --- a/tests/modules/virtualbox/test_virtualbox_manager.py +++ b/tests/modules/virtualbox/test_virtualbox_manager.py @@ -20,10 +20,14 @@ import pytest import tempfile import os import stat +import asyncio + + +from unittest.mock import patch from gns3server.modules.virtualbox import VirtualBox from gns3server.modules.virtualbox.virtualbox_error import VirtualBoxError -from unittest.mock import patch +from tests.utils import asyncio_patch @pytest.fixture(scope="module") @@ -65,3 +69,31 @@ def test_vboxmanage_path(manager, tmpdir): tmpfile = tempfile.NamedTemporaryFile() with patch("gns3server.config.Config.get_section_config", return_value={"vboxmanage_path": path}): assert manager.find_vboxmanage() == path + + +def test_get_list(manager, loop): + vm_list = ['"Windows 8.1" {27b4d095-ff5f-4ac4-bb9d-5f2c7861c1f1}', + '"Carriage', + 'Return" {27b4d095-ff5f-4ac4-bb9d-5f2c7861c1f1}', + '"" {42b4d095-ff5f-4ac4-bb9d-5f2c7861c1f1}', + '"Linux Microcore 4.7.1" {ccd8c50b-c172-457d-99fa-dd69371ede0e}' + ] + + @asyncio.coroutine + def execute_mock(cmd, args): + if cmd == "list": + return vm_list + else: + if args[0] == "Windows 8.1": + return ["memory=512"] + elif args[0] == "Linux Microcore 4.7.1": + return ["memory=256"] + assert False, "Unknow {} {}".format(cmd, args) + + with asyncio_patch("gns3server.modules.virtualbox.VirtualBox.execute") as mock: + mock.side_effect = execute_mock + vms = loop.run_until_complete(asyncio.async(manager.get_list())) + assert vms == [ + {"vmname": "Windows 8.1", "ram": 512}, + {"vmname": "Linux Microcore 4.7.1", "ram": 256} + ] From 7cad25eb1ad3d2f09a95c015f200c9becce0cdf2 Mon Sep 17 00:00:00 2001 From: Julien Duponchelle Date: Wed, 27 May 2015 16:38:57 +0200 Subject: [PATCH 20/25] Raise a VirtualBox error if adapter doesn't exists Fix #195 --- gns3server/modules/virtualbox/virtualbox_vm.py | 8 ++++---- tests/modules/virtualbox/test_virtualbox_vm.py | 6 ++++++ 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/gns3server/modules/virtualbox/virtualbox_vm.py b/gns3server/modules/virtualbox/virtualbox_vm.py index 320ed9c7..b9b91424 100644 --- a/gns3server/modules/virtualbox/virtualbox_vm.py +++ b/gns3server/modules/virtualbox/virtualbox_vm.py @@ -799,7 +799,7 @@ class VirtualBoxVM(BaseVM): try: adapter = self._ethernet_adapters[adapter_number] - except IndexError: + except KeyError: raise VirtualBoxError("Adapter {adapter_number} doesn't exist on VirtualBox VM '{name}'".format(name=self.name, adapter_number=adapter_number)) @@ -830,7 +830,7 @@ class VirtualBoxVM(BaseVM): try: adapter = self._ethernet_adapters[adapter_number] - except IndexError: + except KeyError: raise VirtualBoxError("Adapter {adapter_number} doesn't exist on VirtualBox VM '{name}'".format(name=self.name, adapter_number=adapter_number)) @@ -861,7 +861,7 @@ class VirtualBoxVM(BaseVM): try: adapter = self._ethernet_adapters[adapter_number] - except IndexError: + except KeyError: raise VirtualBoxError("Adapter {adapter_number} doesn't exist on VirtualBox VM '{name}'".format(name=self.name, adapter_number=adapter_number)) @@ -883,7 +883,7 @@ class VirtualBoxVM(BaseVM): try: adapter = self._ethernet_adapters[adapter_number] - except IndexError: + except KeyError: raise VirtualBoxError("Adapter {adapter_number} doesn't exist on VirtualBox VM '{name}'".format(name=self.name, adapter_number=adapter_number)) diff --git a/tests/modules/virtualbox/test_virtualbox_vm.py b/tests/modules/virtualbox/test_virtualbox_vm.py index 8b91431e..58540e49 100644 --- a/tests/modules/virtualbox/test_virtualbox_vm.py +++ b/tests/modules/virtualbox/test_virtualbox_vm.py @@ -54,3 +54,9 @@ def test_vm_invalid_virtualbox_api_version(loop, project, manager): with pytest.raises(VirtualBoxError): vm = VirtualBoxVM("test", "00010203-0405-0607-0809-0a0b0c0d0e0f", project, manager, "test", False) loop.run_until_complete(asyncio.async(vm.create())) + + +def test_vm_adapter_add_nio_binding_adapter_not_exist(loop, vm, manager, free_console_port): + nio = manager.create_nio(manager.vboxmanage_path, {"type": "nio_udp", "lport": free_console_port, "rport": free_console_port, "rhost": "192.168.1.2"}) + with pytest.raises(VirtualBoxError): + loop.run_until_complete(asyncio.async(vm.adapter_add_nio_binding(15, nio))) From 8abf22ef24d15a19be2bf04cf861f6801fa67e7d Mon Sep 17 00:00:00 2001 From: Julien Duponchelle Date: Wed, 27 May 2015 16:45:39 +0200 Subject: [PATCH 21/25] Skip network interfaces on Travis --- tests/handlers/api/test_network.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/handlers/api/test_network.py b/tests/handlers/api/test_network.py index 0818fecd..dec3a436 100644 --- a/tests/handlers/api/test_network.py +++ b/tests/handlers/api/test_network.py @@ -26,7 +26,7 @@ def test_udp_allocation(server, project): # Netfifaces is not available on Travis -@pytest.mark.skipif(os.environ.get("TRAVIS", False), reason="Not supported on Travis") +@pytest.mark.skipif(os.environ.get("TRAVIS", False) is not False, reason="Not supported on Travis") def test_interfaces(server): response = server.get('/interfaces', example=True) assert response.status == 200 From a86bac42146e75e9a5c983bcfd984bd16b3cc662 Mon Sep 17 00:00:00 2001 From: Julien Duponchelle Date: Wed, 27 May 2015 17:21:15 +0200 Subject: [PATCH 22/25] Catch VPCS kill errors Fix #199 --- gns3server/modules/vpcs/vpcs_vm.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/gns3server/modules/vpcs/vpcs_vm.py b/gns3server/modules/vpcs/vpcs_vm.py index 7321f0c6..78cc644d 100644 --- a/gns3server/modules/vpcs/vpcs_vm.py +++ b/gns3server/modules/vpcs/vpcs_vm.py @@ -254,7 +254,10 @@ class VPCSVM(BaseVM): 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() + try: + self._process.kill() + except OSError as e: + raise VPCSError("Can not stop the VPCS process: {}".format(e)) self._process = None self._started = False From 74ee73581a8609952742ebafd2d2d040dc7a1517 Mon Sep 17 00:00:00 2001 From: Julien Duponchelle Date: Wed, 27 May 2015 17:34:01 +0200 Subject: [PATCH 23/25] Fix crash if you pass an invalid hostname Fix #198 --- gns3server/main.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/gns3server/main.py b/gns3server/main.py index e0bf9af2..2d5b27cd 100644 --- a/gns3server/main.py +++ b/gns3server/main.py @@ -186,7 +186,13 @@ def main(): Project.clean_project_directory() CrashReport.instance() - host = server_config["host"] + + try: + host = server_config["host"].encode("idna").decode() + except UnicodeError: + log.critical("Invalid hostname %s", server_config["host"]) + return + port = int(server_config["port"]) server = Server.instance(host, port) try: From 91c0f05a4e0801b7a457e68e7f0e0e56fca6b264 Mon Sep 17 00:00:00 2001 From: grossmj Date: Wed, 27 May 2015 10:17:46 -0600 Subject: [PATCH 24/25] Fixes bug: couldn't set PCMCIA disk1 size for IOS routers. --- gns3server/modules/dynamips/nodes/router.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gns3server/modules/dynamips/nodes/router.py b/gns3server/modules/dynamips/nodes/router.py index 5816d175..4efee4d0 100644 --- a/gns3server/modules/dynamips/nodes/router.py +++ b/gns3server/modules/dynamips/nodes/router.py @@ -836,7 +836,7 @@ class Router(BaseVM): return self._disk1 @asyncio.coroutine - def disk1(self, disk1): + def set_disk1(self, disk1): """ Sets the size (MB) for PCMCIA disk1. From 0476f2932e068e7eeadab0e59fd5e674ef8290b7 Mon Sep 17 00:00:00 2001 From: grossmj Date: Wed, 27 May 2015 13:56:27 -0600 Subject: [PATCH 25/25] Prevent users to add links to running Qemu VMs and start a capture on running VirtualBox VMs. --- gns3server/handlers/api/virtualbox_handler.py | 2 +- gns3server/modules/qemu/qemu_vm.py | 4 +++- gns3server/modules/virtualbox/virtualbox_vm.py | 5 +++++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/gns3server/handlers/api/virtualbox_handler.py b/gns3server/handlers/api/virtualbox_handler.py index 62cc4e6a..38274a3a 100644 --- a/gns3server/handlers/api/virtualbox_handler.py +++ b/gns3server/handlers/api/virtualbox_handler.py @@ -344,7 +344,7 @@ class VirtualBoxHandler: vm = vbox_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"]) adapter_number = int(request.match_info["adapter_number"]) pcap_file_path = os.path.join(vm.project.capture_working_directory(), request.json["capture_file_name"]) - vm.start_capture(adapter_number, pcap_file_path) + yield from vm.start_capture(adapter_number, pcap_file_path) response.json({"pcap_file_path": pcap_file_path}) @Route.post( diff --git a/gns3server/modules/qemu/qemu_vm.py b/gns3server/modules/qemu/qemu_vm.py index e93e4630..73c18d2e 100644 --- a/gns3server/modules/qemu/qemu_vm.py +++ b/gns3server/modules/qemu/qemu_vm.py @@ -741,6 +741,8 @@ class QemuVM(BaseVM): adapter_number=adapter_number)) if self.is_running(): + raise QemuError("Sorry, adding a link to a started Qemu VM is not supported.") + # FIXME: does the code below work? very undocumented feature... # dynamically configure an UDP tunnel on the QEMU VM adapter if nio and isinstance(nio, NIOUDP): if self._legacy_networking: @@ -751,7 +753,6 @@ class QemuVM(BaseVM): nio.rport, nio.rhost)) else: - # FIXME: does it work? very undocumented feature... # Apparently there is a bug in Qemu... # netdev_add [user|tap|socket|hubport|netmap],id=str[,prop=value][,...] -- add host network device # netdev_del id -- remove host network device @@ -785,6 +786,7 @@ class QemuVM(BaseVM): adapter_number=adapter_number)) if self.is_running(): + # FIXME: does the code below work? very undocumented feature... # dynamically disable the QEMU VM adapter yield from self._control_vm("host_net_remove {} gns3-{}".format(adapter_number, adapter_number)) yield from self._control_vm("host_net_add user vlan={},name=gns3-{}".format(adapter_number, adapter_number)) diff --git a/gns3server/modules/virtualbox/virtualbox_vm.py b/gns3server/modules/virtualbox/virtualbox_vm.py index b9b91424..ddeb6089 100644 --- a/gns3server/modules/virtualbox/virtualbox_vm.py +++ b/gns3server/modules/virtualbox/virtualbox_vm.py @@ -851,6 +851,7 @@ class VirtualBoxVM(BaseVM): adapter_number=adapter_number)) return nio + @asyncio.coroutine def start_capture(self, adapter_number, output_file): """ Starts a packet capture. @@ -865,6 +866,10 @@ class VirtualBoxVM(BaseVM): raise VirtualBoxError("Adapter {adapter_number} doesn't exist on VirtualBox VM '{name}'".format(name=self.name, adapter_number=adapter_number)) + vm_state = yield from self._get_vm_state() + if vm_state == "running" or vm_state == "paused" or vm_state == "stuck": + raise VirtualBoxError("Sorry, packet capturing on a started VirtualBox VM is not supported.") + nio = adapter.get_nio(0) if nio.capturing: raise VirtualBoxError("Packet capture is already activated on adapter {adapter_number}".format(adapter_number=adapter_number))