diff --git a/CHANGELOG b/CHANGELOG index 059c85b7..9e50db48 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,19 @@ # Change Log +## 1.5.0rc2 15/06/2016 + +* Fix black screen with Qt app in Docker container +* Detect when command in the container exit +* Docker when the aux console exit and restart it +* Pass by default the environment variable container=docker +* Fix busybox binary location +* Avoid loosing console port for Docker +* Workaround a crash in x11vnc +* Delete volume when dropping the container +* Catch connection reset in ioucon +* Delete vlan.dat for L2IOL during config import. Fixes #1285. +* Copy original ressources from VOLUMES + ## 1.5.0rc1 01/06/2016 * Save an restore docker permission diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 74bba784..408ccb52 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -18,6 +18,10 @@ it on https://github.com/GNS3/gns3-gui we will take care of the triage. For bugs specific to the GNS3 VM, please report on https://github.com/GNS3/gns3-vm +## Security issues + +For security issues please keep it private and send an email to developers@gns3.net + ## Asking for new features The best is to start a discussion on the community website in order to get feedback diff --git a/docs/general.rst b/docs/general.rst index 6a53c115..add09715 100644 --- a/docs/general.rst +++ b/docs/general.rst @@ -274,5 +274,6 @@ Previous versions API version 1 ------------- -Shipped with GNS3 1.3, 1.4 and 1.5. This API doesn't support the controller system. +Shipped with GNS3 1.3, 1.4 and 1.5. +This API doesn't support the controller system and save used a commit system instead of live save. diff --git a/gns3server/compute/docker/docker_vm.py b/gns3server/compute/docker/docker_vm.py index b73ea734..70b43d37 100644 --- a/gns3server/compute/docker/docker_vm.py +++ b/gns3server/compute/docker/docker_vm.py @@ -279,7 +279,7 @@ class DockerVM(BaseNode): "Binds": self._mount_binds(image_infos) }, "Volumes": {}, - "Env": [], + "Env": ["container=docker"], # Systemd compliant: https://github.com/GNS3/gns3-server/issues/573 "Cmd": [], "Entrypoint": image_infos.get("Config", {"Entrypoint": []})["Entrypoint"] } @@ -306,6 +306,7 @@ class DockerVM(BaseNode): if self._console_type == "vnc": yield from self._start_vnc() + params["Env"].append("QT_GRAPHICSSYSTEM=native") # To fix a Qt issue: https://github.com/GNS3/gns3-server/issues/556 params["Env"].append("DISPLAY=:{}".format(self._display)) params["HostConfig"]["Binds"].append("/tmp/.X11-unix/:/tmp/.X11-unix/") @@ -384,7 +385,7 @@ class DockerVM(BaseNode): # We can not use the API because docker doesn't expose a websocket api for exec # https://github.com/GNS3/gns3-gui/issues/1039 process = yield from asyncio.subprocess.create_subprocess_exec( - "docker", "exec", "-i", self._cid, "/gns3/bin/busybox", "script", "-qfc", "/gns3/bin/busybox sh", "/dev/null", + "docker", "exec", "-i", self._cid, "/gns3/bin/busybox", "script", "-qfc", "while true; do /gns3/bin/busybox sh; done", "/dev/null", stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.STDOUT, stdin=asyncio.subprocess.PIPE) @@ -493,7 +494,9 @@ class DockerVM(BaseNode): out.feed_eof() ws.close() break + yield from self.stop() + @asyncio.coroutine def is_running(self): """Checks if the container is running. @@ -535,21 +538,21 @@ class DockerVM(BaseNode): if state == "paused": yield from self.unpause() - yield from self._fix_permissions() - - # t=5 number of seconds to wait before killing the container - try: - yield from self.manager.query("POST", "containers/{}/stop".format(self._cid), params={"t": 5}) - log.info("Docker container '{name}' [{image}] stopped".format(name=self._name, image=self._image)) - except DockerHttp304Error: - # Container is already stopped - pass - finally: - self.status = "stopped" + if state != "stopped": + yield from self._fix_permissions() + # t=5 number of seconds to wait before killing the container + try: + yield from self.manager.query("POST", "containers/{}/stop".format(self._cid), params={"t": 5}) + log.info("Docker container '{name}' [{image}] stopped".format( + name=self._name, image=self._image)) + except DockerHttp304Error: + # Container is already stopped + pass # Ignore runtime error because when closing the server except RuntimeError as e: log.debug("Docker runtime error when closing: {}".format(str(e))) return + self.status = "stopped" @asyncio.coroutine def pause(self): diff --git a/gns3server/controller/__init__.py b/gns3server/controller/__init__.py index ca64408c..b665529e 100644 --- a/gns3server/controller/__init__.py +++ b/gns3server/controller/__init__.py @@ -217,9 +217,10 @@ class Controller: topo_data.pop("revision") topo_data.pop("type") - project = yield from self.add_project(path=os.path.dirname(path), status="closed", **topo_data) + project = yield from self.add_project(path=os.path.dirname(path), status="closed", filename=os.path.basename(path), **topo_data) if load: yield from project.open() + return project @property def projects(self): diff --git a/gns3server/controller/project.py b/gns3server/controller/project.py index b7df4d69..7b8c3768 100644 --- a/gns3server/controller/project.py +++ b/gns3server/controller/project.py @@ -43,7 +43,7 @@ class Project: :param status: Status of the project (opened / closed) """ - def __init__(self, name=None, project_id=None, path=None, controller=None, status="opened"): + def __init__(self, name=None, project_id=None, path=None, controller=None, status="opened", filename=None): self._controller = controller self._name = name @@ -60,6 +60,13 @@ class Project: if path is None: path = os.path.join(get_default_project_directory(), self._id) self.path = path + + if filename is None and name is None: + self._filename = "project.gns3" + elif filename is not None: + self._filename = filename + else: + self._filename = self.name + ".gns3" self.reset() def reset(self): @@ -322,14 +329,8 @@ class Project: raise aiohttp.web.HTTPInternalServerError(text="Could not create project directory: {}".format(e)) return path - def _filename(self): - if self.name is None: - return "untitled.gns3" - else: - return self.name + ".gns3" - def _topology_file(self): - return os.path.join(self.path, self._filename()) + return os.path.join(self.path, self._filename) @asyncio.coroutine def open(self): @@ -373,6 +374,6 @@ class Project: "name": self._name, "project_id": self._id, "path": self._path, - "filename": self._filename(), + "filename": self._filename, "status": self._status } diff --git a/gns3server/handlers/api/controller/server_handler.py b/gns3server/handlers/api/controller/server_handler.py index 69d9e91c..298c3c9a 100644 --- a/gns3server/handlers/api/controller/server_handler.py +++ b/gns3server/handlers/api/controller/server_handler.py @@ -49,7 +49,7 @@ class ServerHandler: # close all the projects first controller = Controller.instance() - projects = controller.projects + projects = controller.projects.values() tasks = [] for project in projects: diff --git a/gns3server/run.py b/gns3server/run.py index 5a8b4c1d..702a2c74 100644 --- a/gns3server/run.py +++ b/gns3server/run.py @@ -102,6 +102,7 @@ def parse_arguments(argv): parser.add_argument("--log", help="send output to logfile instead of console") parser.add_argument("--daemon", action="store_true", help="start as a daemon") parser.add_argument("--pid", help="store process pid") + parser.add_argument("--udpdiscovery", action="store_true", help="allow the server to be discover on the network") args = parser.parse_args(argv) if args.config: @@ -121,6 +122,7 @@ def parse_arguments(argv): "quiet": config.getboolean("quiet", False), "debug": config.getboolean("debug", False), "logfile": config.getboolean("logfile", ""), + "udp_discovery": config.getboolean("udp_discovery", False), } parser.set_defaults(**defaults) @@ -142,6 +144,7 @@ def set_config(args): server_config["record"] = args.record server_config["debug"] = str(args.debug) server_config["shell"] = str(args.shell) + server_config["udp_discovery"] = str(args.udpdiscovery) config.set_section_config("Server", server_config) diff --git a/gns3server/web/web_server.py b/gns3server/web/web_server.py index b6818adf..10d88226 100644 --- a/gns3server/web/web_server.py +++ b/gns3server/web/web_server.py @@ -59,6 +59,7 @@ class WebServer: self._handler = None self._start_time = time.time() self._port_manager = PortManager(host) + self._running = False @staticmethod def instance(host=None, port=None): @@ -298,7 +299,7 @@ class WebServer: if server_config.getboolean("shell"): asyncio.async(self.start_shell()) - if sys.platform.startswith("linux"): + if sys.platform.startswith("linux") and server_config.getboolean("udp_discovery"): # UDP discovery is only supported on Linux udp_server_discovery = threading.Thread(target=self._udp_server_discovery, daemon=True) udp_server_discovery.start() diff --git a/tests/compute/docker/test_docker_vm.py b/tests/compute/docker/test_docker_vm.py index 49254269..0bf611d0 100644 --- a/tests/compute/docker/test_docker_vm.py +++ b/tests/compute/docker/test_docker_vm.py @@ -105,6 +105,7 @@ def test_create(loop, project, manager): "Hostname": "test", "Image": "ubuntu:latest", "Env": [ + "container=docker", "GNS3_MAX_ETHERNET=eth0", "GNS3_VOLUMES=/etc/network" ], @@ -143,6 +144,7 @@ def test_create_with_tag(loop, project, manager): "Hostname": "test", "Image": "ubuntu:16.04", "Env": [ + "container=docker", "GNS3_MAX_ETHERNET=eth0", "GNS3_VOLUMES=/etc/network" ], @@ -185,8 +187,10 @@ def test_create_vnc(loop, project, manager): "Hostname": "test", "Image": "ubuntu:latest", "Env": [ + "container=docker", "GNS3_MAX_ETHERNET=eth0", "GNS3_VOLUMES=/etc/network", + "QT_GRAPHICSSYSTEM=native", "DISPLAY=:42" ], "Entrypoint": ["/gns3/init.sh"], @@ -229,6 +233,7 @@ def test_create_start_cmd(loop, project, manager): "Hostname": "test", "Image": "ubuntu:latest", "Env": [ + "container=docker", "GNS3_MAX_ETHERNET=eth0", "GNS3_VOLUMES=/etc/network" ] @@ -261,6 +266,7 @@ def test_create_environment(loop, project, manager): "Privileged": True }, "Env": [ + "container=docker", "GNS3_MAX_ETHERNET=eth0", "GNS3_VOLUMES=/etc/network", "YES=1", @@ -320,6 +326,7 @@ def test_create_image_not_available(loop, project, manager): "Hostname": "test", "Image": "ubuntu:latest", "Env": [ + "container=docker", "GNS3_MAX_ETHERNET=eth0", "GNS3_VOLUMES=/etc/network" ], @@ -540,6 +547,7 @@ def test_update(loop, vm): "Hostname": "test", "Image": "ubuntu:latest", "Env": [ + "container=docker", "GNS3_MAX_ETHERNET=eth0", "GNS3_VOLUMES=/etc/network" ], @@ -608,6 +616,7 @@ def test_update_running(loop, vm): "Hostname": "test", "Image": "ubuntu:latest", "Env": [ + "container=docker", "GNS3_MAX_ETHERNET=eth0", "GNS3_VOLUMES=/etc/network" ], @@ -904,7 +913,7 @@ def test_start_aux(vm, loop): with asyncio_patch("asyncio.subprocess.create_subprocess_exec", return_value=MagicMock()) as mock_exec: loop.run_until_complete(asyncio.async(vm._start_aux())) - mock_exec.assert_called_with('docker', 'exec', '-i', 'e90e34656842', '/gns3/bin/busybox', 'script', '-qfc', '/gns3/bin/busybox sh', '/dev/null', stderr=asyncio.subprocess.STDOUT, stdin=asyncio.subprocess.PIPE, stdout=asyncio.subprocess.PIPE) + mock_exec.assert_called_with('docker', 'exec', '-i', 'e90e34656842', '/gns3/bin/busybox', 'script', '-qfc', 'while true; do /gns3/bin/busybox sh; done', '/dev/null', stderr=asyncio.subprocess.STDOUT, stdin=asyncio.subprocess.PIPE, stdout=asyncio.subprocess.PIPE) def test_create_network_interfaces(vm): diff --git a/tests/controller/test_controller.py b/tests/controller/test_controller.py index dd48bca1..34caa19c 100644 --- a/tests/controller/test_controller.py +++ b/tests/controller/test_controller.py @@ -188,7 +188,7 @@ def test_close(controller, async_run): def test_load_project(controller, async_run, tmpdir): data = { - "name": "Test", + "name": "Experience", "project_id": "c8d07a5a-134f-4c3f-8599-e35eac85eb17", "revision": 5, "type": "topology", @@ -242,11 +242,12 @@ def test_load_project(controller, async_run, tmpdir): controller._computes["my_remote"] = MagicMock() with asyncio_patch("gns3server.controller.node.Node.create") as mock_node_create: - async_run(controller.load_project(str(tmpdir / "test.gns3"))) + project = async_run(controller.load_project(str(tmpdir / "test.gns3"))) + assert project._topology_file() == str(tmpdir / "test.gns3") controller.add_compute.assert_called_with(compute_id='my_remote', host='127.0.0.1', name='My remote', port=3080, protocol='http') project = controller.get_project('c8d07a5a-134f-4c3f-8599-e35eac85eb17') - assert project.name == "Test" + assert project.name == "Experience" assert project.path == str(tmpdir) link = project.get_link("c44331d2-2da4-490d-9aad-7f5c126ae271") assert len(link.nodes) == 2 diff --git a/tests/controller/test_project.py b/tests/controller/test_project.py index 567a356a..9d29050e 100644 --- a/tests/controller/test_project.py +++ b/tests/controller/test_project.py @@ -43,7 +43,7 @@ def test_affect_uuid(): def test_json(tmpdir): p = Project() - assert p.__json__() == {"name": p.name, "project_id": p.id, "path": p.path, "status": "opened", "filename": "untitled.gns3"} + assert p.__json__() == {"name": p.name, "project_id": p.id, "path": p.path, "status": "opened", "filename": "project.gns3"} def test_path(tmpdir):