From 1e371016413dc1512f2549c32e0130b8e298dd1b Mon Sep 17 00:00:00 2001 From: grossmj Date: Fri, 23 Jun 2017 12:00:33 +0200 Subject: [PATCH 1/5] Allow IOU 64-bit images. --- gns3server/compute/iou/iou_vm.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gns3server/compute/iou/iou_vm.py b/gns3server/compute/iou/iou_vm.py index 4768614f..3f51ad6a 100644 --- a/gns3server/compute/iou/iou_vm.py +++ b/gns3server/compute/iou/iou_vm.py @@ -181,9 +181,9 @@ class IOUVM(BaseNode): except OSError as e: raise IOUError("Cannot read ELF header for IOU image '{}': {}".format(self._path, e)) - # IOU images must start with the ELF magic number, be 32-bit, little endian + # IOU images must start with the ELF magic number, be 32-bit or 64-bit, little endian # and have an ELF version of 1 normal IOS image are big endian! - if elf_header_start != b'\x7fELF\x01\x01\x01': + if elf_header_start != b'\x7fELF\x01\x01\x01' and elf_header_start != b'\x7fELF\x02\x01\x01': raise IOUError("'{}' is not a valid IOU image".format(self._path)) if not os.access(self._path, os.X_OK): From c6f9ec37586e77fee77d9d0be44b8bf9fcaf7378 Mon Sep 17 00:00:00 2001 From: ziajka Date: Wed, 5 Jul 2017 10:36:58 +0200 Subject: [PATCH 2/5] More information on Docker WebSocket error --- gns3server/compute/docker/docker_vm.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gns3server/compute/docker/docker_vm.py b/gns3server/compute/docker/docker_vm.py index f384d5aa..5473074a 100644 --- a/gns3server/compute/docker/docker_vm.py +++ b/gns3server/compute/docker/docker_vm.py @@ -502,6 +502,8 @@ class DockerVM(BaseNode): msg = yield from ws.receive() if msg.tp == aiohttp.MsgType.text: out.feed_data(msg.data.encode()) + elif msg.tp == aiohttp.MsgType.error: + log.critical("Docker WebSocket Error: {}".format(msg.data)) else: out.feed_eof() ws.close() From 8e8b8bc5a59b785b0a1a325bc10b24e99451c568 Mon Sep 17 00:00:00 2001 From: ziajka Date: Thu, 6 Jul 2017 10:13:00 +0200 Subject: [PATCH 3/5] WebSocket binary mode support for docker --- gns3server/compute/docker/docker_vm.py | 6 ++++-- tests/compute/docker/test_docker_vm.py | 25 +++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/gns3server/compute/docker/docker_vm.py b/gns3server/compute/docker/docker_vm.py index 5473074a..257fc062 100644 --- a/gns3server/compute/docker/docker_vm.py +++ b/gns3server/compute/docker/docker_vm.py @@ -500,9 +500,11 @@ class DockerVM(BaseNode): while True: msg = yield from ws.receive() - if msg.tp == aiohttp.MsgType.text: + if msg.tp == aiohttp.MsgType.TEXT: out.feed_data(msg.data.encode()) - elif msg.tp == aiohttp.MsgType.error: + if msg.tp == aiohttp.MsgType.BINARY: + out.feed_data(msg.data) + elif msg.tp == aiohttp.MsgType.ERROR: log.critical("Docker WebSocket Error: {}".format(msg.data)) else: out.feed_eof() diff --git a/tests/compute/docker/test_docker_vm.py b/tests/compute/docker/test_docker_vm.py index 6ca6fd14..b638e542 100644 --- a/tests/compute/docker/test_docker_vm.py +++ b/tests/compute/docker/test_docker_vm.py @@ -20,6 +20,7 @@ import pytest import uuid import sys import os +from aiohttp._ws_impl import WSMsgType from tests.utils import asyncio_patch, AsyncioMagicMock from gns3server.ubridge.ubridge_error import UbridgeNamespaceError @@ -904,3 +905,27 @@ def test_fix_permission(vm, loop): loop.run_until_complete(vm._fix_permissions()) mock_exec.assert_called_with('docker', 'exec', 'e90e34656842', '/gns3/bin/busybox', 'sh', '-c', '(/gns3/bin/busybox find "/etc" -depth -print0 | /gns3/bin/busybox xargs -0 /gns3/bin/busybox stat -c \'%a:%u:%g:%n\' > "/etc/.gns3_perms") && /gns3/bin/busybox chmod -R u+rX "/etc" && /gns3/bin/busybox chown {}:{} -R "/etc"'.format(os.getuid(), os.getgid())) assert process.wait.called + + +def test_read_console_output_with_binary_mode(vm, loop): + class InputStreamMock(object): + def __init__(self): + self.sent = False + + @asyncio.coroutine + def receive(self): + if not self.sent: + self.sent = True + return MagicMock(tp=WSMsgType.BINARY, data=b"test") + else: + return MagicMock(tp=WSMsgType.CLOSE) + + def close(self): + pass + + input_stream = InputStreamMock() + output_stream = MagicMock() + + with asyncio_patch('gns3server.compute.docker.docker_vm.DockerVM.stop'): + loop.run_until_complete(asyncio.async(vm._read_console_output(input_stream, output_stream))) + output_stream.feed_data.assert_called_once_with(b"test") From e0f0adf3c878f30c00dd55aef19d773039ef1dca Mon Sep 17 00:00:00 2001 From: ziajka Date: Thu, 6 Jul 2017 11:24:55 +0200 Subject: [PATCH 4/5] Added preferred Docker API version. Fixes #2136 --- gns3server/compute/docker/__init__.py | 17 +++++++++-- tests/compute/docker/test_docker.py | 41 +++++++++++++++++++++++++-- 2 files changed, 53 insertions(+), 5 deletions(-) diff --git a/gns3server/compute/docker/__init__.py b/gns3server/compute/docker/__init__.py index 18ad331f..b85f473a 100644 --- a/gns3server/compute/docker/__init__.py +++ b/gns3server/compute/docker/__init__.py @@ -36,6 +36,7 @@ log = logging.getLogger(__name__) # Be carefull to keep it consistent DOCKER_MINIMUM_API_VERSION = "1.25" DOCKER_MINIMUM_VERSION = "1.13" +DOCKER_PREFERRED_API_VERSION = "1.30" class Docker(BaseManager): @@ -50,6 +51,7 @@ class Docker(BaseManager): self.ubridge_lock = asyncio.Lock() self._connector = None self._session = None + self._api_version = DOCKER_MINIMUM_API_VERSION @asyncio.coroutine def _check_connection(self): @@ -61,8 +63,17 @@ class Docker(BaseManager): except (aiohttp.errors.ClientOSError, FileNotFoundError): self._connected = False raise DockerError("Can't connect to docker daemon") - if parse_version(version["ApiVersion"]) < parse_version(DOCKER_MINIMUM_API_VERSION): - raise DockerError("Docker version is {}. GNS3 requires a minimum version of {}".format(version["Version"], DOCKER_MINIMUM_VERSION)) + + docker_version = parse_version(version['ApiVersion']) + + if docker_version < parse_version(DOCKER_MINIMUM_API_VERSION): + raise DockerError( + "Docker version is {}. GNS3 requires a minimum version of {}".format( + version["Version"], DOCKER_MINIMUM_VERSION)) + + preferred_api_version = parse_version(DOCKER_PREFERRED_API_VERSION) + if docker_version >= preferred_api_version: + self._api_version = DOCKER_PREFERRED_API_VERSION def connector(self): if self._connector is None or self._connector.closed: @@ -165,7 +176,7 @@ class Docker(BaseManager): :returns: Websocket """ - url = "http://docker/v" + DOCKER_MINIMUM_API_VERSION + "/" + path + url = "http://docker/v" + self._api_version + "/" + path connection = yield from aiohttp.ws_connect(url, connector=self.connector(), origin="http://docker", diff --git a/tests/compute/docker/test_docker.py b/tests/compute/docker/test_docker.py index 095a25de..db43c84c 100644 --- a/tests/compute/docker/test_docker.py +++ b/tests/compute/docker/test_docker.py @@ -17,10 +17,10 @@ import pytest import asyncio -from unittest.mock import MagicMock +from unittest.mock import MagicMock, patch from tests.utils import asyncio_patch, AsyncioMagicMock -from gns3server.compute.docker import Docker +from gns3server.compute.docker import Docker, DOCKER_PREFERRED_API_VERSION, DOCKER_MINIMUM_API_VERSION from gns3server.compute.docker.docker_error import DockerError, DockerHttp404Error @@ -162,3 +162,40 @@ def test_pull_image(loop): with asyncio_patch("gns3server.compute.docker.Docker.http_query", return_value=mock_query) as mock: images = loop.run_until_complete(asyncio.async(Docker.instance().pull_image("ubuntu"))) mock.assert_called_with("POST", "images/create", params={"fromImage": "ubuntu"}, timeout=None) + + +def test_docker_check_connection_docker_minimum_version(vm, loop): + response = { + 'ApiVersion': '1.01', + 'Version': '1.12' + } + + with patch("gns3server.compute.docker.Docker.connector"), \ + asyncio_patch("gns3server.compute.docker.Docker.query", return_value=response): + vm._connected = False + with pytest.raises(DockerError): + loop.run_until_complete(asyncio.async(vm._check_connection())) + + +def test_docker_check_connection_docker_preferred_version_against_newer(vm, loop): + response = { + 'ApiVersion': '1.31' + } + + with patch("gns3server.compute.docker.Docker.connector"), \ + asyncio_patch("gns3server.compute.docker.Docker.query", return_value=response): + vm._connected = False + loop.run_until_complete(asyncio.async(vm._check_connection())) + assert vm._api_version == DOCKER_PREFERRED_API_VERSION + + +def test_docker_check_connection_docker_preferred_version_against_older(vm, loop): + response = { + 'ApiVersion': '1.27', + } + + with patch("gns3server.compute.docker.Docker.connector"), \ + asyncio_patch("gns3server.compute.docker.Docker.query", return_value=response): + vm._connected = False + loop.run_until_complete(asyncio.async(vm._check_connection())) + assert vm._api_version == DOCKER_MINIMUM_API_VERSION \ No newline at end of file From 450c089b6d3d3a7fad435d9c16dc250049869be0 Mon Sep 17 00:00:00 2001 From: Julien Duponchelle Date: Tue, 11 Jul 2017 15:28:01 +0200 Subject: [PATCH 5/5] Test if a snapshot name already exists This fix random test failure when testing snapshots. It seem under high load sometimes the previous snapshot folder was not visible on disk. Perhaps a test isolation issue but I don't see how. But in any case it's better to test if the name is not already use. Fix #1118 --- gns3server/controller/project.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/gns3server/controller/project.py b/gns3server/controller/project.py index c185cccd..93e8f31c 100644 --- a/gns3server/controller/project.py +++ b/gns3server/controller/project.py @@ -510,6 +510,9 @@ class Project: :param name: Name of the snapshot """ + if name in [snap.name for snap in self.snapshots.values()]: + raise aiohttp.web_exceptions.HTTPConflict(text="The snapshot {} already exist".format(name)) + snapshot = Snapshot(self, name=name) try: if os.path.exists(snapshot.path):