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 diff --git a/gns3server/handlers/api/project_handler.py b/gns3server/handlers/api/project_handler.py index a5018db3..09099184 100644 --- a/gns3server/handlers/api/project_handler.py +++ b/gns3server/handlers/api/project_handler.py @@ -96,9 +96,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/handlers/api/virtualbox_handler.py b/gns3server/handlers/api/virtualbox_handler.py index e8466a5e..5036bef7 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/main.py b/gns3server/main.py index 97eb064f..f000c7ca 100644 --- a/gns3server/main.py +++ b/gns3server/main.py @@ -71,5 +71,6 @@ def main(): from gns3server.run import run run() + if __name__ == '__main__': main() diff --git a/gns3server/modules/dynamips/nodes/router.py b/gns3server/modules/dynamips/nodes/router.py index 3a6d15af..b4f15d72 100644 --- a/gns3server/modules/dynamips/nodes/router.py +++ b/gns3server/modules/dynamips/nodes/router.py @@ -851,7 +851,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. diff --git a/gns3server/modules/project.py b/gns3server/modules/project.py index fc191221..5ed73dd0 100644 --- a/gns3server/modules/project.py +++ b/gns3server/modules/project.py @@ -150,13 +150,17 @@ 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: - 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): diff --git a/gns3server/modules/qemu/qemu_vm.py b/gns3server/modules/qemu/qemu_vm.py index 8c240b5a..f27c2518 100644 --- a/gns3server/modules/qemu/qemu_vm.py +++ b/gns3server/modules/qemu/qemu_vm.py @@ -756,6 +756,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: @@ -766,7 +768,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 @@ -800,6 +801,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/__init__.py b/gns3server/modules/virtualbox/__init__.py index 7d542faf..766239be 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/gns3server/modules/virtualbox/virtualbox_vm.py b/gns3server/modules/virtualbox/virtualbox_vm.py index a306db85..ac1bb2dd 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)) @@ -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. @@ -861,10 +862,14 @@ 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)) + 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)) @@ -883,7 +888,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/gns3server/modules/vpcs/vpcs_vm.py b/gns3server/modules/vpcs/vpcs_vm.py index 044e808f..9888c22a 100644 --- a/gns3server/modules/vpcs/vpcs_vm.py +++ b/gns3server/modules/vpcs/vpcs_vm.py @@ -269,7 +269,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 diff --git a/gns3server/web/logger.py b/gns3server/web/logger.py index 1fe40ffa..32a746df 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) diff --git a/tests/handlers/api/test_network.py b/tests/handlers/api/test_network.py index d675d4f6..dec3a436 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.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 diff --git a/tests/handlers/api/test_project.py b/tests/handlers/api/test_project.py index 432401b1..852c27fd 100644 --- a/tests/handlers/api/test_project.py +++ b/tests/handlers/api/test_project.py @@ -23,6 +23,7 @@ import uuid import os import asyncio import aiohttp + from unittest.mock import patch from tests.utils import asyncio_patch @@ -91,18 +92,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 7e268d59..1ee24fe1 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(): diff --git a/tests/modules/virtualbox/test_virtualbox_manager.py b/tests/modules/virtualbox/test_virtualbox_manager.py index f6185bed..84e06870 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,30 @@ 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} + ] 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)))