diff --git a/CHANGELOG b/CHANGELOG index f598184c..9762117b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,38 @@ # Change Log +## 2.0.0 beta 4 16/02/2017 + +* Lock aiohttp to 1.2.0 because 1.3 create bug with Qt +* Avoid a crash in some conditions when reading the serial console +* Disallow export of project with VirtualBox linked clone +* Fix linked_clone property lost during topology convert +* Catch permission error when restoring a snapshot +* Fix a rare crash when closing a project +* Fix error when you have error on your filesystem during project convertion +* Catch error when we can't access to a unix socket +* If we can't resolve compute name return 0.0.0.0 +* Raise an error if you put an invalid key in node name +* Improve a lot project loading speed +* Fix a potential crash +* Fix the server don't start if a remote is unavailable +* Do not crash if you pass {name} in name +* Fix import/export of dynamips configuration +* Simplify conversion process from 1.3 to 2.0 +* Prevent corruption of VM in VirtualBox when using linked clone +* Fix creation of qemu img +* Fix rare race condition when stopping ubridge +* Prevent renaming of a running VirtualBox linked VM +* Avoid crash when you broke your system permissions +* Do not crash when you broke permission on your file system during execution +* Fix a crash when you broke permission on your file system +* Fix a rare race condition when exporting debug informations +* Do not try to start the GNS3 VM if the name is none +* Fix version check for VPCS +* Fix pcap for PPP link with IOU +* Correct link are not connected to the correct ethernet switch port after conversion +* Fix an error if you don't have permissions on your symbols directory +* Fix an error when converting some topologies from 1.3 + ## 2.0.0 beta 3 19/01/2017 * Force the dependency on typing because otherwise it's broke on 3.4 @@ -45,7 +78,7 @@ * Replace JSONDecodeError by ValueError (Python 3.4 compatibility) * Catch an error when we can't create the IOU directory -## 1.5.3 12/01/2016 +## 1.5.3 12/01/2017 * Fix sporadically systemd is unable to start gns3-server diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..fec7d333 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,34 @@ +# Dockerfile for GNS3 server development + +FROM ubuntu:16.04 + +ENV DEBIAN_FRONTEND noninteractive + +# Set the locale +RUN locale-gen en_US.UTF-8 +ENV LANG en_US.UTF-8 +ENV LANGUAGE en_US:en +ENV LC_ALL en_US.UTF-8 + +RUN apt-get update && apt-get install -y software-properties-common +RUN add-apt-repository ppa:gns3/ppa +RUN apt-get update && apt-get install -y \ + python3-pip \ + python3-dev \ + qemu-system-x86 \ + qemu-system-arm \ + qemu-kvm \ + libvirt-bin \ + x11vnc + +# Install uninstall to install dependencies +RUN apt-get install -y vpcs ubridge + +ADD . /server +WORKDIR /server + +RUN pip3 install -r /server/requirements.txt + +EXPOSE 3080 + +CMD python3 -m gns3server --local diff --git a/README.rst b/README.rst index 81ffc9d5..e3ebfde5 100644 --- a/README.rst +++ b/README.rst @@ -71,6 +71,16 @@ To run tests use: py.test -v +Docker container +**************** + +For development you can run the GNS3 server in a container + +.. code:: bash + + bash scripts/docker_dev_server.sh + + Run as daemon (Unix only) ************************** diff --git a/gns3server/compute/virtualbox/virtualbox_vm.py b/gns3server/compute/virtualbox/virtualbox_vm.py index 801988e2..e1d520dc 100644 --- a/gns3server/compute/virtualbox/virtualbox_vm.py +++ b/gns3server/compute/virtualbox/virtualbox_vm.py @@ -19,14 +19,16 @@ VirtualBox VM instance. """ -import sys -import shlex import re import os -import tempfile +import sys import json +import uuid +import shlex +import shutil import socket import asyncio +import tempfile import xml.etree.ElementTree as ET from gns3server.utils import parse_version @@ -209,7 +211,16 @@ class VirtualBoxVM(BaseNode): if os.path.exists(self._linked_vbox_file()): tree = ET.parse(self._linked_vbox_file()) machine = tree.getroot().find("{http://www.virtualbox.org/}Machine") - if machine is not None: + if machine is not None and machine.get("uuid") != "{" + self.id + "}": + + for image in tree.getroot().findall("{http://www.virtualbox.org/}Image"): + currentSnapshot = machine.get("currentSnapshot") + if currentSnapshot: + newSnapshot = re.sub("\{.*\}", "{" + str(uuid.uuid4()) + "}", currentSnapshot) + shutil.move(os.path.join(self.working_dir, self._vmname, "Snapshots", currentSnapshot) + ".vdi", + os.path.join(self.working_dir, self._vmname, "Snapshots", newSnapshot) + ".vdi") + image.set("uuid", newSnapshot) + machine.set("uuid", "{" + self.id + "}") tree.write(self._linked_vbox_file()) diff --git a/gns3server/controller/__init__.py b/gns3server/controller/__init__.py index 581a9897..d0726713 100644 --- a/gns3server/controller/__init__.py +++ b/gns3server/controller/__init__.py @@ -367,7 +367,8 @@ class Controller: return project def remove_project(self, project): - del self._projects[project.id] + if project.id in self._projects: + del self._projects[project.id] @asyncio.coroutine def load_project(self, path, load=True): diff --git a/gns3server/controller/export_project.py b/gns3server/controller/export_project.py index 6fb0dd64..25c167e2 100644 --- a/gns3server/controller/export_project.py +++ b/gns3server/controller/export_project.py @@ -136,6 +136,8 @@ def _export_project_file(project, path, z, include_images, keep_compute_id, allo if "topology" in topology: if "nodes" in topology["topology"]: for node in topology["topology"]["nodes"]: + if node["node_type"] == "virtualbox" and node.get("properties", {}).get("linked_clone"): + raise aiohttp.web.HTTPConflict(text="Topology with a linked {} clone could not be exported. Use qemu instead.".format(node["node_type"])) if not allow_all_nodes and node["node_type"] in ["virtualbox", "vmware", "cloud"]: raise aiohttp.web.HTTPConflict(text="Topology with a {} could not be exported".format(node["node_type"])) diff --git a/gns3server/controller/project.py b/gns3server/controller/project.py index 8796e028..5bf23744 100644 --- a/gns3server/controller/project.py +++ b/gns3server/controller/project.py @@ -536,7 +536,7 @@ class Project: @asyncio.coroutine def close(self, ignore_notification=False): yield from self.stop_all() - for compute in self._project_created_on_compute: + for compute in list(self._project_created_on_compute): try: yield from compute.post("/projects/{}/close".format(self._id), dont_connect=True) # We don't care if a compute is down at this step diff --git a/gns3server/controller/snapshot.py b/gns3server/controller/snapshot.py index 217a16b7..4163a6b1 100644 --- a/gns3server/controller/snapshot.py +++ b/gns3server/controller/snapshot.py @@ -20,6 +20,7 @@ import os import uuid import shutil import asyncio +import aiohttp.web from datetime import datetime, timezone @@ -80,10 +81,13 @@ class Snapshot: # We don't send close notif to clients because the close / open dance is purely internal yield from self._project.close(ignore_notification=True) self._project.controller.notification.emit("snapshot.restored", self.__json__()) - if os.path.exists(os.path.join(self._project.path, "project-files")): - shutil.rmtree(os.path.join(self._project.path, "project-files")) - with open(self._path, "rb") as f: - project = yield from import_project(self._project.controller, self._project.id, f, location=self._project.path) + try: + if os.path.exists(os.path.join(self._project.path, "project-files")): + shutil.rmtree(os.path.join(self._project.path, "project-files")) + with open(self._path, "rb") as f: + project = yield from import_project(self._project.controller, self._project.id, f, location=self._project.path) + except (OSError, PermissionError) as e: + raise aiohttp.web.HTTPConflict(text=str(e)) yield from project.open() return project diff --git a/gns3server/controller/topology.py b/gns3server/controller/topology.py index 3f7a1540..c188e9a9 100644 --- a/gns3server/controller/topology.py +++ b/gns3server/controller/topology.py @@ -321,10 +321,12 @@ def _convert_1_3_later(topo, topo_path): node["properties"]["ram"] = PLATFORMS_DEFAULT_RAM[old_node["type"].lower()] elif old_node["type"] == "VMwareVM": node["node_type"] = "vmware" + node["properties"]["linked_clone"] = old_node.get("linked_clone", False) if node["symbol"] is None: node["symbol"] = ":/symbols/vmware_guest.svg" elif old_node["type"] == "VirtualBoxVM": node["node_type"] = "virtualbox" + node["properties"]["linked_clone"] = old_node.get("linked_clone", False) if node["symbol"] is None: node["symbol"] = ":/symbols/vbox_guest.svg" elif old_node["type"] == "IOUDevice": diff --git a/gns3server/crash_report.py b/gns3server/crash_report.py index ecd23733..7edc2865 100644 --- a/gns3server/crash_report.py +++ b/gns3server/crash_report.py @@ -53,7 +53,7 @@ class CrashReport: Report crash to a third party service """ - DSN = "sync+https://b7430bad849c4b88b3a928032d6cce5e:f140bfdd2ebb4bf4b929c002b45b2357@sentry.io/38482" + DSN = "sync+https://83564b27a6f6475488a3eb74c78f1760:ed5ac7c6d3f7428d960a84da98450b69@sentry.io/38482" if hasattr(sys, "frozen"): cacert = get_resource("cacert.pem") if cacert is not None and os.path.isfile(cacert): diff --git a/gns3server/utils/asyncio/serial.py b/gns3server/utils/asyncio/serial.py index 48d12bc0..c118a87e 100644 --- a/gns3server/utils/asyncio/serial.py +++ b/gns3server/utils/asyncio/serial.py @@ -34,6 +34,7 @@ class SerialReaderWriterProtocol(asyncio.Protocol): def __init__(self): self._output = asyncio.StreamReader() + self._closed = False self.transport = None def read(self, n=-1): @@ -54,9 +55,11 @@ class SerialReaderWriterProtocol(asyncio.Protocol): self.transport = transport def data_received(self, data): - self._output.feed_data(data) + if not self._closed: + self._output.feed_data(data) def close(self): + self._closed = True self._output.feed_eof() diff --git a/gns3server/version.py b/gns3server/version.py index aff38c2d..b72f512a 100644 --- a/gns3server/version.py +++ b/gns3server/version.py @@ -25,3 +25,14 @@ __version__ = "2.1.0dev1" __version_info__ = (2, 1, 0, -99) + +# If it's a git checkout try to add the commit +if "dev" in __version__: + try: + import os + import subprocess + if os.path.exists(os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", ".git")): + r = subprocess.run(["git", "rev-parse", "--short", "HEAD"], stdout=subprocess.PIPE).stdout.decode().strip("\n") + __version__ += "-" + r + except Exception as e: + print(e) diff --git a/requirements.txt b/requirements.txt index f79cf434..222e4b5a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ jsonschema>=2.4.0 -aiohttp>=1.2.0 +aiohttp==1.2.0 aiohttp_cors>=0.4.0 -yarl>=0.8.1 +yarl>=0.9.6 typing>=3.5.3.0 # Otherwise yarl fail with python 3.4 Jinja2>=2.7.3 raven>=5.23.0 diff --git a/scripts/docker_dev_server.sh b/scripts/docker_dev_server.sh new file mode 100644 index 00000000..a2f6e784 --- /dev/null +++ b/scripts/docker_dev_server.sh @@ -0,0 +1,23 @@ +#!/bin/sh +# +# Copyright (C) 2016 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 . + +# A docker server use for localy test a remote GNS3 server + +docker build -t gns3-server . +docker run -i -h gns3vm -p 8001:8001/tcp -t gns3-server python3 -m gns3server --local --port 8001 + + diff --git a/tests/controller/test_export_project.py b/tests/controller/test_export_project.py index 65b1e80f..2b6b6610 100644 --- a/tests/controller/test_export_project.py +++ b/tests/controller/test_export_project.py @@ -190,9 +190,9 @@ def test_export_disallow_some_type(tmpdir, project, async_run): topology = { "topology": { "nodes": [ - { - "node_type": "virtualbox" - } + { + "node_type": "cloud" + } ] } } @@ -202,6 +202,24 @@ def test_export_disallow_some_type(tmpdir, project, async_run): with pytest.raises(aiohttp.web.HTTPConflict): z = async_run(export_project(project, str(tmpdir))) + z = async_run(export_project(project, str(tmpdir), allow_all_nodes=True)) + + # VirtualBox is always disallowed + topology = { + "topology": { + "nodes": [ + { + "node_type": "virtualbox", + "properties": { + "linked_clone": True + } + } + ] + } + } + with open(os.path.join(path, "test.gns3"), 'w+') as f: + json.dump(topology, f) + with pytest.raises(aiohttp.web.HTTPConflict): z = async_run(export_project(project, str(tmpdir), allow_all_nodes=True)) @@ -215,18 +233,18 @@ def test_export_fix_path(tmpdir, project, async_run): topology = { "topology": { "nodes": [ - { - "properties": { - "image": "/tmp/c3725-adventerprisek9-mz.124-25d.image" - }, - "node_type": "dynamips" - }, { - "properties": { - "image": "gns3/webterm:lastest" - }, - "node_type": "docker" - } + "properties": { + "image": "/tmp/c3725-adventerprisek9-mz.124-25d.image" + }, + "node_type": "dynamips" + }, + { + "properties": { + "image": "gns3/webterm:lastest" + }, + "node_type": "docker" + } ] } } diff --git a/tests/controller/test_project.py b/tests/controller/test_project.py index 1fec7320..4422c0d1 100644 --- a/tests/controller/test_project.py +++ b/tests/controller/test_project.py @@ -427,7 +427,7 @@ def test_duplicate(project, async_run, controller): remote_vpcs = async_run(project.add_node(compute, "test", None, node_type="vpcs", properties={"startup_config": "test.cfg"})) # We allow node not allowed for standard import / export - remote_virtualbox = async_run(project.add_node(compute, "test", None, node_type="virtualbox", properties={"startup_config": "test.cfg"})) + remote_virtualbox = async_run(project.add_node(compute, "test", None, node_type="vmware", properties={"startup_config": "test.cfg"})) new_project = async_run(project.duplicate(name="Hello")) assert new_project.id != project.id diff --git a/tests/topologies/1_5_virtualbox/after/1_5_virtualbox.gns3 b/tests/topologies/1_5_virtualbox/after/1_5_virtualbox.gns3 index 1f591b24..ba17e46d 100644 --- a/tests/topologies/1_5_virtualbox/after/1_5_virtualbox.gns3 +++ b/tests/topologies/1_5_virtualbox/after/1_5_virtualbox.gns3 @@ -34,6 +34,7 @@ "port_segment_size": 0, "first_port_name": null, "properties": { + "linked_clone": false, "acpi_shutdown": false, "adapter_type": "Intel PRO/1000 MT Desktop (82540EM)", "adapters": 1, diff --git a/tests/topologies/1_5_vmware/after/1_5_vmware.gns3 b/tests/topologies/1_5_vmware/after/1_5_vmware.gns3 index 98a25f62..8238605c 100644 --- a/tests/topologies/1_5_vmware/after/1_5_vmware.gns3 +++ b/tests/topologies/1_5_vmware/after/1_5_vmware.gns3 @@ -34,6 +34,7 @@ "port_segment_size": 0, "first_port_name": null, "properties": { + "linked_clone": false, "acpi_shutdown": false, "adapter_type": "e1000", "adapters": 1,