From 34f5a6f82c34cc2151ea25bb5e79fe72dffbe24c Mon Sep 17 00:00:00 2001 From: Julien Duponchelle Date: Mon, 20 Mar 2017 23:50:31 +0100 Subject: [PATCH] Fix Session is closed when listing docker images Fix #955 --- gns3server/compute/docker/__init__.py | 35 +++++++++++------ tests/compute/docker/test_docker.py | 56 ++++++++++++++------------- 2 files changed, 52 insertions(+), 39 deletions(-) diff --git a/gns3server/compute/docker/__init__.py b/gns3server/compute/docker/__init__.py index 722f7e69..956f679a 100644 --- a/gns3server/compute/docker/__init__.py +++ b/gns3server/compute/docker/__init__.py @@ -46,31 +46,43 @@ class Docker(BaseManager): self._connected = False # Allow locking during ubridge operations self.ubridge_lock = asyncio.Lock() + self._version_checked = False self._session = None + self._connector = None @asyncio.coroutine - def connector(self): - if not self._connected or self._connector.closed: - if not sys.platform.startswith("linux"): - raise DockerError("Docker is supported only on Linux") - + def session(self): + if not self._connected or self._session.closed: try: - self._connector = aiohttp.connector.UnixConnector(self._server_url, conn_timeout=2) self._connected = True + connector = self.connector() + self._session = aiohttp.ClientSession(connector=connector) version = yield from self.query("GET", "version") 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 API version is {}. GNS3 requires a minimum API version of {}".format(version["ApiVersion"], DOCKER_MINIMUM_API_VERSION)) + return self._session + + def connector(self): + if self._connector is None or self._connector.closed: + if not sys.platform.startswith("linux"): + raise DockerError("Docker is supported only on Linux") + try: + self._connector = aiohttp.connector.UnixConnector(self._server_url, conn_timeout=2) + except (aiohttp.errors.ClientOSError, FileNotFoundError): + raise DockerError("Can't connect to docker daemon") return self._connector @asyncio.coroutine def unload(self): yield from super().unload() if self._connected: - self._connector.close() + if self._session and not self._session.closed: + yield from self._session.close() + if self._connector and not self._connector.closed: + yield from self._connector.close() @asyncio.coroutine def query(self, method, path, data={}, params={}): @@ -108,9 +120,8 @@ class Docker(BaseManager): data = json.dumps(data) url = "http://docker/" + path try: - if self._session is None or self._session.closed is True: - self._session = aiohttp.ClientSession(connector=(yield from self.connector())) - response = yield from self._session.request( + session = yield from self.session() + response = yield from session.request( method, url, params=params, @@ -147,7 +158,7 @@ class Docker(BaseManager): url = "http://docker/" + path connection = yield from aiohttp.ws_connect(url, - connector=(yield from self.connector()), + connector=self.connector(), origin="http://docker", autoping=True) return connection diff --git a/tests/compute/docker/test_docker.py b/tests/compute/docker/test_docker.py index ee5de061..1492836b 100644 --- a/tests/compute/docker/test_docker.py +++ b/tests/compute/docker/test_docker.py @@ -19,7 +19,7 @@ import pytest import asyncio from unittest.mock import MagicMock -from tests.utils import asyncio_patch +from tests.utils import asyncio_patch, AsyncioMagicMock from gns3server.compute.docker import Docker from gns3server.compute.docker.docker_error import DockerError @@ -28,6 +28,8 @@ from gns3server.compute.docker.docker_error import DockerError def vm(): vm = Docker() vm._connected = True + vm._session = MagicMock() + vm._session.closed = False return vm @@ -42,14 +44,14 @@ def test_query_success(loop, vm): return b'{"c": false}' response.read.side_effect = read - with asyncio_patch("aiohttp.client.ClientSession.request", return_value=response) as mock: - data = loop.run_until_complete(asyncio.async(vm.query("POST", "test", data={"a": True}, params={"b": 1}))) - mock.assert_called_with('POST', - 'http://docker/test', - data='{"a": true}', - headers={'content-type': 'application/json'}, - params={'b': 1}, - timeout=300) + vm._session.request = AsyncioMagicMock(return_value=response) + data = loop.run_until_complete(asyncio.async(vm.query("POST", "test", data={"a": True}, params={"b": 1}))) + vm._session.request.assert_called_with('POST', + 'http://docker/test', + data='{"a": true}', + headers={'content-type': 'application/json'}, + params={'b': 1}, + timeout=300) assert data == {"c": False} @@ -64,15 +66,15 @@ def test_query_error(loop, vm): return b"NOT FOUND" response.read.side_effect = read - with asyncio_patch("aiohttp.client.ClientSession.request", return_value=response) as mock: - with pytest.raises(DockerError): - data = loop.run_until_complete(asyncio.async(vm.query("POST", "test", data={"a": True}, params={"b": 1}))) - mock.assert_called_with('POST', - 'http://docker/test', - data='{"a": true}', - headers={'content-type': 'application/json'}, - params={'b': 1}, - timeout=300) + vm._session.request = AsyncioMagicMock(return_value=response) + with pytest.raises(DockerError): + data = loop.run_until_complete(asyncio.async(vm.query("POST", "test", data={"a": True}, params={"b": 1}))) + vm._session.request.assert_called_with('POST', + 'http://docker/test', + data='{"a": true}', + headers={'content-type': 'application/json'}, + params={'b': 1}, + timeout=300) def test_query_error_json(loop, vm): @@ -85,15 +87,15 @@ def test_query_error_json(loop, vm): return b'{"message": "Error"}' response.read.side_effect = read - with asyncio_patch("aiohttp.client.ClientSession.request", return_value=response) as mock: - with pytest.raises(DockerError): - data = loop.run_until_complete(asyncio.async(vm.query("POST", "test", data={"a": True}, params={"b": 1}))) - mock.assert_called_with('POST', - 'http://docker/test', - data='{"a": true}', - headers={'content-type': 'application/json'}, - params={'b': 1}, - timeout=300) + vm._session.request = AsyncioMagicMock(return_value=response) + with pytest.raises(DockerError): + data = loop.run_until_complete(asyncio.async(vm.query("POST", "test", data={"a": True}, params={"b": 1}))) + vm._session.request.assert_called_with('POST', + 'http://docker/test', + data='{"a": true}', + headers={'content-type': 'application/json'}, + params={'b': 1}, + timeout=300) def test_list_images(loop):