From e6a1cd15eb105be755a4a28b29a5e2f8948de7c9 Mon Sep 17 00:00:00 2001 From: Athmane Madjoudj Date: Jul 22 2017 22:33:55 +0000 Subject: Add a patch to support aiohttp >= 2 --- diff --git a/gns3-server-2.0.3-add-aiohttpd2-support.patch b/gns3-server-2.0.3-add-aiohttpd2-support.patch new file mode 100644 index 0000000..c8a582e --- /dev/null +++ b/gns3-server-2.0.3-add-aiohttpd2-support.patch @@ -0,0 +1,620 @@ +diff -ru gns3-server-2.0.3/gns3server/compute/docker/docker_vm.py gns3-server-2.0.3.patched-aiohttp/gns3server/compute/docker/docker_vm.py +--- gns3-server-2.0.3/gns3server/compute/docker/docker_vm.py 2017-06-13 09:35:50.000000000 +0100 ++++ gns3-server-2.0.3.patched-aiohttp/gns3server/compute/docker/docker_vm.py 2017-07-22 20:34:41.817104084 +0100 +@@ -500,7 +500,7 @@ + + while True: + msg = yield from ws.receive() +- if msg.tp == aiohttp.MsgType.text: ++ if msg.tp == aiohttp.WSMsgType.text: + out.feed_data(msg.data.encode()) + else: + out.feed_eof() +diff -ru gns3-server-2.0.3/gns3server/compute/docker/__init__.py gns3-server-2.0.3.patched-aiohttp/gns3server/compute/docker/__init__.py +--- gns3-server-2.0.3/gns3server/compute/docker/__init__.py 2017-06-13 09:35:50.000000000 +0100 ++++ gns3-server-2.0.3.patched-aiohttp/gns3server/compute/docker/__init__.py 2017-07-22 20:34:41.816104059 +0100 +@@ -58,7 +58,7 @@ + self._connected = True + connector = self.connector() + version = yield from self.query("GET", "version") +- except (aiohttp.errors.ClientOSError, FileNotFoundError): ++ except (aiohttp.ClientOSError, FileNotFoundError): + self._connected = False + raise DockerError("Can't connect to docker daemon") + if parse_version(version["ApiVersion"]) < parse_version(DOCKER_MINIMUM_API_VERSION): +@@ -70,7 +70,7 @@ + raise DockerError("Docker is supported only on Linux") + try: + self._connector = aiohttp.connector.UnixConnector(self._server_url, conn_timeout=2, limit=None) +- except (aiohttp.errors.ClientOSError, FileNotFoundError): ++ except (aiohttp.ClientOSError, FileNotFoundError): + raise DockerError("Can't connect to docker daemon") + return self._connector + +@@ -79,7 +79,7 @@ + yield from super().unload() + if self._connected: + if self._connector and not self._connector.closed: +- yield from self._connector.close() ++ self._connector.close() + + @asyncio.coroutine + def query(self, method, path, data={}, params={}): +@@ -195,7 +195,7 @@ + while True: + try: + chunk = yield from response.content.read(1024) +- except aiohttp.errors.ServerDisconnectedError: ++ except aiohttp.ServerDisconnectedError: + break + if not chunk: + break +diff -ru gns3-server-2.0.3/gns3server/controller/compute.py gns3-server-2.0.3.patched-aiohttp/gns3server/controller/compute.py +--- gns3-server-2.0.3/gns3server/controller/compute.py 2017-06-13 09:35:50.000000000 +0100 ++++ gns3-server-2.0.3.patched-aiohttp/gns3server/controller/compute.py 2017-07-22 22:57:47.174802899 +0100 +@@ -421,15 +421,15 @@ + """ + try: + self._ws = yield from self._session().ws_connect(self._getUrl("/notifications/ws"), auth=self._auth) +- except (aiohttp.errors.WSServerHandshakeError, aiohttp.errors.ClientResponseError): ++ except (aiohttp.WSServerHandshakeError, aiohttp.ClientResponseError): + self._ws = None + while self._ws is not None: + try: + response = yield from self._ws.receive() +- except aiohttp.errors.WSServerHandshakeError: ++ except aiohttp.WSServerHandshakeError: + self._ws = None + break +- if response.tp == aiohttp.MsgType.closed or response.tp == aiohttp.MsgType.error or response.data is None: ++ if response.tp == aiohttp.WSMsgType.closed or response.tp == aiohttp.WSMsgType.error or response.data is None: + self._connected = False + break + msg = json.loads(response.data) +@@ -474,7 +474,7 @@ + url = self._getUrl(path) + headers = {} + headers['content-type'] = 'application/json' +- chunked = False ++ chunked = None + if data == {}: + data = None + elif data is not None: +@@ -500,12 +500,12 @@ + + data = send_data(data) + else: +- data = json.dumps(data) ++ data = json.dumps(data).encode("utf-8") + try: + response = yield from self._session().request(method, url, headers=headers, data=data, auth=self._auth, chunked=chunked, timeout=timeout) + except asyncio.TimeoutError as e: + raise ComputeError("Timeout error when connecting to {}".format(url)) +- except (aiohttp.errors.ClientOSError, aiohttp.errors.ClientRequestError, aiohttp.errors.ServerDisconnectedError, aiohttp.ClientResponseError, ValueError) as e: ++ except (aiohttp.ClientError, aiohttp.ServerDisconnectedError, ValueError) as e: + raise ComputeError(str(e)) + body = yield from response.read() + if body and not raw: +@@ -582,7 +582,7 @@ + try: + action = "/{}/{}".format(type, path) + res = yield from self.http_query(method, action, data=data, timeout=None) +- except aiohttp.errors.DisconnectedError: ++ except aiohttp.ServerDisconnectedError: + log.error("Connection lost to %s during %s %s", self._id, method, action) + raise aiohttp.web.HTTPGatewayTimeout() + return res.json +diff -ru gns3-server-2.0.3/gns3server/controller/gns3vm/virtualbox_gns3_vm.py gns3-server-2.0.3.patched-aiohttp/gns3server/controller/gns3vm/virtualbox_gns3_vm.py +--- gns3-server-2.0.3/gns3server/controller/gns3vm/virtualbox_gns3_vm.py 2017-06-13 09:35:50.000000000 +0100 ++++ gns3-server-2.0.3.patched-aiohttp/gns3server/controller/gns3vm/virtualbox_gns3_vm.py 2017-07-22 20:34:41.819104132 +0100 +@@ -228,7 +228,7 @@ + try: + resp = None + resp = yield from session.get('http://127.0.0.1:{}/v2/compute/network/interfaces'.format(api_port)) +- except (OSError, aiohttp.errors.ClientHttpProcessingError, TimeoutError, asyncio.TimeoutError): ++ except (OSError, aiohttp.ClientError, TimeoutError, asyncio.TimeoutError): + pass + + if resp: +diff -ru gns3-server-2.0.3/gns3server/controller/node.py gns3-server-2.0.3.patched-aiohttp/gns3server/controller/node.py +--- gns3-server-2.0.3/gns3server/controller/node.py 2017-06-13 09:35:50.000000000 +0100 ++++ gns3-server-2.0.3.patched-aiohttp/gns3server/controller/node.py 2017-07-22 20:34:41.819104132 +0100 +@@ -428,7 +428,7 @@ + try: + yield from self.post("/stop", timeout=240, dont_connect=True) + # We don't care if a node is down at this step +- except (ComputeError, aiohttp.errors.ClientHttpProcessingError, aiohttp.web.HTTPError): ++ except (ComputeError, aiohttp.ClientError, aiohttp.web.HTTPError): + pass + except asyncio.TimeoutError: + raise aiohttp.web.HTTPRequestTimeout(text="Timeout when stopping {}".format(self._name)) +diff -ru gns3-server-2.0.3/gns3server/crash_report.py gns3-server-2.0.3.patched-aiohttp/gns3server/crash_report.py +--- gns3-server-2.0.3/gns3server/crash_report.py 2017-06-13 09:35:50.000000000 +0100 ++++ gns3-server-2.0.3.patched-aiohttp/gns3server/crash_report.py 2017-07-22 20:34:41.820104157 +0100 +@@ -45,7 +45,10 @@ + # Display a traceback in case of segfault crash. Usefull when frozen + # Not enabled by default for security reason + log.info("Enable catching segfault") +- faulthandler.enable() ++ try: ++ faulthandler.enable() ++ except Exception: ++ pass # Could fail when loaded into tests + + + class CrashReport: +diff -ru gns3-server-2.0.3/gns3server/handlers/api/compute/notification_handler.py gns3-server-2.0.3.patched-aiohttp/gns3server/handlers/api/compute/notification_handler.py +--- gns3-server-2.0.3/gns3server/handlers/api/compute/notification_handler.py 2017-06-13 09:35:50.000000000 +0100 ++++ gns3-server-2.0.3.patched-aiohttp/gns3server/handlers/api/compute/notification_handler.py 2017-07-22 20:34:41.820104157 +0100 +@@ -16,8 +16,7 @@ + # along with this program. If not, see . + + import asyncio +-import aiohttp.errors +- ++import aiohttp + from aiohttp.web import WebSocketResponse + from gns3server.web.route import Route + from gns3server.compute.notification_manager import NotificationManager +@@ -30,7 +29,7 @@ + """ + try: + yield from ws.receive() +- except aiohttp.errors.WSServerHandshakeError: ++ except aiohttp.WSServerHandshakeError: + pass + + +diff -ru gns3-server-2.0.3/gns3server/handlers/api/compute/project_handler.py gns3-server-2.0.3.patched-aiohttp/gns3server/handlers/api/compute/project_handler.py +--- gns3-server-2.0.3/gns3server/handlers/api/compute/project_handler.py 2017-06-13 09:35:50.000000000 +0100 ++++ gns3-server-2.0.3.patched-aiohttp/gns3server/handlers/api/compute/project_handler.py 2017-07-22 20:34:41.821104181 +0100 +@@ -157,7 +157,6 @@ + response.set_status(200) + response.enable_chunked_encoding() + # Very important: do not send a content length otherwise QT closes the connection (curl can consume the feed) +- response.content_length = None + + response.start(request) + queue = project.get_listen_queue() +@@ -240,11 +239,10 @@ + response.set_status(200) + response.enable_chunked_encoding() + # Very important: do not send a content length otherwise QT closes the connection (curl can consume the feed) +- response.content_length = None + + try: + with open(path, "rb") as f: +- response.start(request) ++ yield from response.prepare(request) + while True: + data = f.read(4096) + if not data: +@@ -274,7 +272,7 @@ + path = request.match_info["path"] + path = os.path.normpath(path) + +- # Raise an error if user try to escape ++ # Raise an error if user try to escape + if path[0] == ".": + raise aiohttp.web.HTTPForbidden + path = os.path.join(project.path, path) +@@ -283,11 +281,10 @@ + response.set_status(200) + response.enable_chunked_encoding() + # Very important: do not send a content length otherwise QT closes the connection (curl can consume the feed) +- response.content_length = None + + try: + with open(path, "rb") as f: +- response.start(request) ++ yield from response.prepare(request) + while True: + data = f.read(4096) + if not data: +@@ -358,8 +355,7 @@ + response.headers['CONTENT-DISPOSITION'] = 'attachment; filename="{}.gns3project"'.format(project.name) + response.enable_chunked_encoding() + # Very important: do not send a content length otherwise QT closes the connection (curl can consume the feed) +- response.content_length = None +- response.start(request) ++ yield from response.prepare(request) + + include_images = bool(int(request.json.get("include_images", "0"))) + for data in project.export(include_images=include_images): +diff -ru gns3-server-2.0.3/gns3server/handlers/api/controller/link_handler.py gns3-server-2.0.3.patched-aiohttp/gns3server/handlers/api/controller/link_handler.py +--- gns3-server-2.0.3/gns3server/handlers/api/controller/link_handler.py 2017-06-13 09:35:50.000000000 +0100 ++++ gns3-server-2.0.3.patched-aiohttp/gns3server/handlers/api/controller/link_handler.py 2017-07-22 20:34:41.822104206 +0100 +@@ -179,8 +179,7 @@ + response.set_status(200) + response.enable_chunked_encoding() + # Very important: do not send a content length otherwise QT closes the connection (curl can consume the feed) +- response.content_length = None +- response.start(request) ++ yield from response.prepare(request) + + while True: + chunk = f.read(4096) +diff -ru gns3-server-2.0.3/gns3server/handlers/api/controller/node_handler.py gns3-server-2.0.3.patched-aiohttp/gns3server/handlers/api/controller/node_handler.py +--- gns3-server-2.0.3/gns3server/handlers/api/controller/node_handler.py 2017-06-13 09:35:50.000000000 +0100 ++++ gns3-server-2.0.3.patched-aiohttp/gns3server/handlers/api/controller/node_handler.py 2017-07-22 20:34:41.822104206 +0100 +@@ -351,8 +351,7 @@ + response.set_status(200) + response.content_type = "application/octet-stream" + response.enable_chunked_encoding() +- response.content_length = None +- response.start(request) ++ yield from response.prepare(request) + + response.write(res.body) + yield from response.write_eof() +diff -ru gns3-server-2.0.3/gns3server/handlers/api/controller/project_handler.py gns3-server-2.0.3.patched-aiohttp/gns3server/handlers/api/controller/project_handler.py +--- gns3-server-2.0.3/gns3server/handlers/api/controller/project_handler.py 2017-06-13 09:35:50.000000000 +0100 ++++ gns3-server-2.0.3.patched-aiohttp/gns3server/handlers/api/controller/project_handler.py 2017-07-22 20:34:41.826104303 +0100 +@@ -17,7 +17,6 @@ + + import os + import aiohttp +-import aiohttp.errors + import asyncio + import tempfile + +@@ -46,7 +45,7 @@ + """ + try: + yield from ws.receive() +- except aiohttp.errors.WSServerHandshakeError: ++ except aiohttp.WSServerHandshakeError: + pass + + +@@ -211,9 +210,8 @@ + response.set_status(200) + response.enable_chunked_encoding() + # Very important: do not send a content length otherwise QT closes the connection (curl can consume the feed) +- response.content_length = None + +- response.start(request) ++ yield from response.prepare(request) + with controller.notification.queue(project) as queue: + while True: + try: +@@ -294,8 +292,7 @@ + response.headers['CONTENT-DISPOSITION'] = 'attachment; filename="{}.gns3project"'.format(project.name) + response.enable_chunked_encoding() + # Very important: do not send a content length otherwise QT closes the connection (curl can consume the feed) +- response.content_length = None +- response.start(request) ++ yield from response.prepare(request) + + for data in datas: + response.write(data) +@@ -407,11 +404,10 @@ + response.set_status(200) + response.enable_chunked_encoding() + # Very important: do not send a content length otherwise QT closes the connection (curl can consume the feed) +- response.content_length = None + + try: + with open(path, "rb") as f: +- response.start(request) ++ yield from response.prepare(request) + while True: + data = f.read(4096) + if not data: +diff -ru gns3-server-2.0.3/gns3server/web/response.py gns3-server-2.0.3.patched-aiohttp/gns3server/web/response.py +--- gns3-server-2.0.3/gns3server/web/response.py 2017-06-13 09:35:50.000000000 +0100 ++++ gns3-server-2.0.3.patched-aiohttp/gns3server/web/response.py 2017-07-22 20:34:41.828104352 +0100 +@@ -120,10 +120,10 @@ + + st = os.stat(path) + self.last_modified = st.st_mtime +- self.content_length = st.st_size ++ self.enable_chunked_encoding() + + with open(path, 'rb') as fobj: +- self.start(self._request) ++ yield from self.prepare(self._request) + chunk_size = 4096 + chunk = fobj.read(chunk_size) + while chunk: +diff -ru gns3-server-2.0.3/gns3server/web/route.py gns3-server-2.0.3.patched-aiohttp/gns3server/web/route.py +--- gns3-server-2.0.3/gns3server/web/route.py 2017-06-13 09:35:50.000000000 +0100 ++++ gns3-server-2.0.3.patched-aiohttp/gns3server/web/route.py 2017-07-22 20:34:41.829104377 +0100 +@@ -220,16 +220,11 @@ + response = Response(request=request, route=route) + response.set_status(408) + response.json({"message": "Request canceled", "status": 408}) +- except aiohttp.ClientDisconnectedError: +- log.warn("Client disconnected") ++ except aiohttp.ClientError: ++ log.warn("Client error") + response = Response(request=request, route=route) + response.set_status(408) +- response.json({"message": "Client disconnected", "status": 408}) +- except ConnectionResetError: +- log.error("Client connection reset") +- response = Response(request=request, route=route) +- response.set_status(408) +- response.json({"message": "Connection reset", "status": 408}) ++ response.json({"message": "Client error", "status": 408}) + except Exception as e: + log.error("Uncaught exception detected: {type}".format(type=type(e)), exc_info=1) + response = Response(request=request, route=route) +diff -ru gns3-server-2.0.3/gns3server/web/web_server.py gns3-server-2.0.3.patched-aiohttp/gns3server/web/web_server.py +--- gns3-server-2.0.3/gns3server/web/web_server.py 2017-06-13 09:35:50.000000000 +0100 ++++ gns3-server-2.0.3.patched-aiohttp/gns3server/web/web_server.py 2017-07-22 23:30:12.827593098 +0100 +@@ -43,10 +43,6 @@ + import logging + log = logging.getLogger(__name__) + +-if not aiohttp.__version__.startswith("1.3"): +- raise RuntimeError("You need aiohttp 1.3 for running GNS3") +- +- + class WebServer: + + def __init__(self, host, port): +diff -ru gns3-server-2.0.3/requirements.txt gns3-server-2.0.3.patched-aiohttp/requirements.txt +--- gns3-server-2.0.3/requirements.txt 2017-06-13 09:35:50.000000000 +0100 ++++ gns3-server-2.0.3.patched-aiohttp/requirements.txt 2017-07-22 23:08:18.539033543 +0100 +@@ -1,9 +1,9 @@ +-jsonschema>=2.4.0 +-aiohttp>=1.3.5,<=1.4.0 # pyup: ignore +-aiohttp-cors==0.5.1 # pyup: ignore +-yarl>=0.9.8,<0.10 # pyup: ignore +-Jinja2>=2.7.3 +-raven>=5.23.0 +-psutil>=3.0.0 +-zipstream>=1.1.4 +-typing>=3.5.3.0 # Otherwise yarl fail with python 3.4 ++jsonschema ++aiohttp ++aiohttp-cors ++yarl ++Jinja2 ++raven ++psutil ++zipstream ++typing +diff -ru gns3-server-2.0.3/scripts/random_query.py gns3-server-2.0.3.patched-aiohttp/scripts/random_query.py +--- gns3-server-2.0.3/scripts/random_query.py 2017-06-13 09:35:50.000000000 +0100 ++++ gns3-server-2.0.3.patched-aiohttp/scripts/random_query.py 2017-07-22 20:34:41.829104377 +0100 +@@ -227,7 +227,7 @@ + try: + j = await error.response.json() + die("%s %s invalid status %d:\n%s", error.method, error.path, error.response.status, json.dumps(j, indent=4)) +- except (ValueError, aiohttp.errors.ServerDisconnectedError): ++ except (ValueError, aiohttp.ServerDisconnectedError): + die("%s %s invalid status %d", error.method, error.path, error.response.status) + + +diff -ru gns3-server-2.0.3/tests/controller/test_compute.py gns3-server-2.0.3.patched-aiohttp/tests/controller/test_compute.py +--- gns3-server-2.0.3/tests/controller/test_compute.py 2017-06-13 09:35:50.000000000 +0100 ++++ gns3-server-2.0.3.patched-aiohttp/tests/controller/test_compute.py 2017-07-22 20:34:41.830104401 +0100 +@@ -75,7 +75,7 @@ + response.status = 200 + + async_run(compute.post("/projects", {"a": "b"})) +- mock.assert_called_with("POST", "https://example.com:84/v2/compute/projects", data='{"a": "b"}', headers={'content-type': 'application/json'}, auth=None, chunked=False, timeout=20) ++ mock.assert_called_with("POST", "https://example.com:84/v2/compute/projects", data=b'{"a": "b"}', headers={'content-type': 'application/json'}, auth=None, chunked=None, timeout=20) + assert compute._auth is None + + +@@ -87,7 +87,7 @@ + compute.user = "root" + compute.password = "toor" + async_run(compute.post("/projects", {"a": "b"})) +- mock.assert_called_with("POST", "https://example.com:84/v2/compute/projects", data='{"a": "b"}', headers={'content-type': 'application/json'}, auth=compute._auth, chunked=False, timeout=20) ++ mock.assert_called_with("POST", "https://example.com:84/v2/compute/projects", data=b'{"a": "b"}', headers={'content-type': 'application/json'}, auth=compute._auth, chunked=None, timeout=20) + assert compute._auth.login == "root" + assert compute._auth.password == "toor" + +@@ -100,8 +100,8 @@ + response.status = 200 + with asyncio_patch("aiohttp.ClientSession.request", return_value=response) as mock: + async_run(compute.post("/projects", {"a": "b"})) +- mock.assert_any_call("GET", "https://example.com:84/v2/compute/capabilities", headers={'content-type': 'application/json'}, data=None, auth=None, chunked=False, timeout=20) +- mock.assert_any_call("POST", "https://example.com:84/v2/compute/projects", data='{"a": "b"}', headers={'content-type': 'application/json'}, auth=None, chunked=False, timeout=20) ++ mock.assert_any_call("GET", "https://example.com:84/v2/compute/capabilities", headers={'content-type': 'application/json'}, data=None, auth=None, chunked=None, timeout=20) ++ mock.assert_any_call("POST", "https://example.com:84/v2/compute/projects", data=b'{"a": "b"}', headers={'content-type': 'application/json'}, auth=None, chunked=None, timeout=20) + assert compute._connected + assert compute._capabilities["version"] == __version__ + controller.notification.emit.assert_called_with("compute.updated", compute.__json__()) +@@ -122,8 +122,8 @@ + response.status = 200 + with asyncio_patch("aiohttp.ClientSession.request", return_value=response) as mock: + async_run(compute.post("/projects", {"a": "b"})) +- mock.assert_any_call("GET", "https://example.com:84/v2/compute/capabilities", headers={'content-type': 'application/json'}, data=None, auth=None, chunked=False, timeout=20) +- mock.assert_any_call("POST", "https://example.com:84/v2/compute/projects", data='{"a": "b"}', headers={'content-type': 'application/json'}, auth=None, chunked=False, timeout=20) ++ mock.assert_any_call("GET", "https://example.com:84/v2/compute/capabilities", headers={'content-type': 'application/json'}, data=None, auth=None, chunked=None, timeout=20) ++ mock.assert_any_call("POST", "https://example.com:84/v2/compute/projects", data=b'{"a": "b"}', headers={'content-type': 'application/json'}, auth=None, chunked=None, timeout=20) + + assert controller.gns3vm.start.called + assert compute._connected +@@ -139,7 +139,7 @@ + with asyncio_patch("aiohttp.ClientSession.request", return_value=response) as mock: + with pytest.raises(aiohttp.web.HTTPConflict): + async_run(compute.post("/projects", {"a": "b"})) +- mock.assert_any_call("GET", "https://example.com:84/v2/compute/capabilities", headers={'content-type': 'application/json'}, data=None, auth=None, chunked=False, timeout=20) ++ mock.assert_any_call("GET", "https://example.com:84/v2/compute/capabilities", headers={'content-type': 'application/json'}, data=None, auth=None, chunked=None, timeout=20) + + + def test_compute_httpQueryNotConnectedNonGNS3Server(compute, async_run): +@@ -150,7 +150,7 @@ + with asyncio_patch("aiohttp.ClientSession.request", return_value=response) as mock: + with pytest.raises(aiohttp.web.HTTPConflict): + async_run(compute.post("/projects", {"a": "b"})) +- mock.assert_any_call("GET", "https://example.com:84/v2/compute/capabilities", headers={'content-type': 'application/json'}, data=None, auth=None, chunked=False, timeout=20) ++ mock.assert_any_call("GET", "https://example.com:84/v2/compute/capabilities", headers={'content-type': 'application/json'}, data=None, auth=None, chunked=None, timeout=20) + + + def test_compute_httpQueryNotConnectedNonGNS3Server2(compute, async_run): +@@ -161,7 +161,7 @@ + with asyncio_patch("aiohttp.ClientSession.request", return_value=response) as mock: + with pytest.raises(aiohttp.web.HTTPConflict): + async_run(compute.post("/projects", {"a": "b"})) +- mock.assert_any_call("GET", "https://example.com:84/v2/compute/capabilities", headers={'content-type': 'application/json'}, data=None, auth=None, chunked=False, timeout=20) ++ mock.assert_any_call("GET", "https://example.com:84/v2/compute/capabilities", headers={'content-type': 'application/json'}, data=None, auth=None, chunked=None, timeout=20) + + + def test_compute_httpQueryError(compute, async_run): +@@ -190,7 +190,7 @@ + + project = Project(name="Test") + async_run(compute.post("/projects", project)) +- mock.assert_called_with("POST", "https://example.com:84/v2/compute/projects", data=json.dumps(project.__json__()), headers={'content-type': 'application/json'}, auth=None, chunked=False, timeout=20) ++ mock.assert_called_with("POST", "https://example.com:84/v2/compute/projects", data=json.dumps(project.__json__()), headers={'content-type': 'application/json'}, auth=None, chunked=None, timeout=20) + + + def test_connectNotification(compute, async_run): +@@ -205,11 +205,11 @@ + if call == 1: + response = MagicMock() + response.data = '{"action": "test", "event": {"a": 1}}' +- response.tp = aiohttp.MsgType.text ++ response.tp = aiohttp.WSMsgType.text + return response + else: + response = MagicMock() +- response.tp = aiohttp.MsgType.closed ++ response.tp = aiohttp.WSMsgType.closed + return response + + compute._controller._notification = MagicMock() +@@ -238,11 +238,11 @@ + if call == 1: + response = MagicMock() + response.data = '{"action": "ping", "event": {"cpu_usage_percent": 35.7, "memory_usage_percent": 80.7}}' +- response.tp = aiohttp.MsgType.text ++ response.tp = aiohttp.WSMsgType.text + return response + else: + response = MagicMock() +- response.tp = aiohttp.MsgType.closed ++ response.tp = aiohttp.WSMsgType.closed + return response + + compute._controller._notification = MagicMock() +@@ -325,7 +325,7 @@ + response.status = 200 + with asyncio_patch("aiohttp.ClientSession.request", return_value=response) as mock: + async_run(compute.forward("GET", "qemu", "images")) +- mock.assert_called_with("GET", "https://example.com:84/v2/compute/qemu/images", auth=None, data=None, headers={'content-type': 'application/json'}, chunked=False, timeout=None) ++ mock.assert_called_with("GET", "https://example.com:84/v2/compute/qemu/images", auth=None, data=None, headers={'content-type': 'application/json'}, chunked=None, timeout=None) + + + def test_forward_404(compute, async_run): +@@ -341,7 +341,7 @@ + response.status = 200 + with asyncio_patch("aiohttp.ClientSession.request", return_value=response) as mock: + async_run(compute.forward("POST", "qemu", "img", data={"id": 42})) +- mock.assert_called_with("POST", "https://example.com:84/v2/compute/qemu/img", auth=None, data='{"id": 42}', headers={'content-type': 'application/json'}, chunked=False, timeout=None) ++ mock.assert_called_with("POST", "https://example.com:84/v2/compute/qemu/img", auth=None, data=b'{"id": 42}', headers={'content-type': 'application/json'}, chunked=None, timeout=None) + + + def test_images(compute, async_run, images_dir): +@@ -358,7 +358,7 @@ + open(os.path.join(images_dir, "QEMU", "asa.qcow2"), "w+").close() + with asyncio_patch("aiohttp.ClientSession.request", return_value=response) as mock: + images = async_run(compute.images("qemu")) +- mock.assert_called_with("GET", "https://example.com:84/v2/compute/qemu/images", auth=None, data=None, headers={'content-type': 'application/json'}, chunked=False, timeout=None) ++ mock.assert_called_with("GET", "https://example.com:84/v2/compute/qemu/images", auth=None, data=None, headers={'content-type': 'application/json'}, chunked=None, timeout=None) + + assert images == [ + {"filename": "asa.qcow2", "path": "asa.qcow2", "md5sum": "d41d8cd98f00b204e9800998ecf8427e", "filesize": 0}, +@@ -373,7 +373,7 @@ + response.status = 200 + with asyncio_patch("aiohttp.ClientSession.request", return_value=response) as mock: + assert async_run(compute.list_files(project)) == res +- mock.assert_any_call("GET", "https://example.com:84/v2/compute/projects/{}/files".format(project.id), auth=None, chunked=False, data=None, headers={'content-type': 'application/json'}, timeout=120) ++ mock.assert_any_call("GET", "https://example.com:84/v2/compute/projects/{}/files".format(project.id), auth=None, chunked=None, data=None, headers={'content-type': 'application/json'}, timeout=120) + + + def test_interfaces(project, async_run, compute): +@@ -392,7 +392,7 @@ + response.status = 200 + with asyncio_patch("aiohttp.ClientSession.request", return_value=response) as mock: + assert async_run(compute.interfaces()) == res +- mock.assert_any_call("GET", "https://example.com:84/v2/compute/network/interfaces", auth=None, chunked=False, data=None, headers={'content-type': 'application/json'}, timeout=20) ++ mock.assert_any_call("GET", "https://example.com:84/v2/compute/network/interfaces", auth=None, chunked=None, data=None, headers={'content-type': 'application/json'}, timeout=20) + + + def test_get_ip_on_same_subnet(controller, async_run): +diff -ru gns3-server-2.0.3/tests/handlers/api/base.py gns3-server-2.0.3.patched-aiohttp/tests/handlers/api/base.py +--- gns3-server-2.0.3/tests/handlers/api/base.py 2017-06-13 09:35:50.000000000 +0100 ++++ gns3-server-2.0.3.patched-aiohttp/tests/handlers/api/base.py 2017-07-22 20:34:41.830104401 +0100 +@@ -85,26 +85,16 @@ + - example if True the session is included inside documentation + - raw do not JSON encode the query + """ ++ return self._loop.run_until_complete(asyncio.async(self._async_fetch(method, path, body=body, **kwargs))) ++ ++ @asyncio.coroutine ++ def _async_fetch(self, method, path, body=None, **kwargs): + if body is not None and not kwargs.get("raw", False): + body = json.dumps(body) + +- @asyncio.coroutine +- def go_request(future): +- response = yield from aiohttp.request(method, self.get_url(path), data=body) +- future.set_result(response) +- future = asyncio.Future() +- asyncio.async(go_request(future)) +- self._loop.run_until_complete(future) +- response = future.result() +- +- @asyncio.coroutine +- def go_read(future, response): +- response = yield from response.read() +- future.set_result(response) +- future = asyncio.Future() +- asyncio.async(go_read(future, response)) +- self._loop.run_until_complete(future) +- response.body = future.result() ++ connector = aiohttp.TCPConnector() ++ response = yield from aiohttp.request(method, self.get_url(path), data=body, loop=self._loop, connector=connector) ++ response.body = yield from response.read() + x_route = response.headers.get('X-Route', None) + if x_route is not None: + response.route = x_route.replace("/v{}".format(self._api_version), "") +diff -ru gns3-server-2.0.3/tests/handlers/api/controller/test_project.py gns3-server-2.0.3.patched-aiohttp/tests/handlers/api/controller/test_project.py +--- gns3-server-2.0.3/tests/handlers/api/controller/test_project.py 2017-06-13 09:35:50.000000000 +0100 ++++ gns3-server-2.0.3.patched-aiohttp/tests/handlers/api/controller/test_project.py 2017-07-22 20:34:41.831104426 +0100 +@@ -129,19 +129,18 @@ + assert response.json["project_id"] == project.id + + +-def test_notification(http_controller, project, controller, loop): ++def test_notification(http_controller, project, controller, loop, async_run): + @asyncio.coroutine +- def go(future): +- response = yield from aiohttp.request("GET", http_controller.get_url("/projects/{project_id}/notifications".format(project_id=project.id))) ++ def go(): ++ connector = aiohttp.TCPConnector() ++ response = yield from aiohttp.request("GET", http_controller.get_url("/projects/{project_id}/notifications".format(project_id=project.id)), connector=connector) + response.body = yield from response.content.read(200) + controller.notification.emit("node.created", {"a": "b"}) +- response.body += yield from response.content.read(50) ++ response.body += yield from response.content.readany() + response.close() +- future.set_result(response) ++ return response + +- future = asyncio.Future() +- asyncio.async(go(future)) +- response = loop.run_until_complete(future) ++ response = async_run(asyncio.async(go())) + assert response.status == 200 + assert b'"action": "ping"' in response.body + assert b'"cpu_usage_percent"' in response.body +diff -ru gns3-server-2.0.3/tests/handlers/api/controller/test_symbol.py gns3-server-2.0.3.patched-aiohttp/tests/handlers/api/controller/test_symbol.py +--- gns3-server-2.0.3/tests/handlers/api/controller/test_symbol.py 2017-06-13 09:35:50.000000000 +0100 ++++ gns3-server-2.0.3.patched-aiohttp/tests/handlers/api/controller/test_symbol.py 2017-07-22 20:34:41.832104450 +0100 +@@ -35,7 +35,6 @@ + def test_get(http_controller): + response = http_controller.get('/symbols/' + urllib.parse.quote(':/symbols/firewall.svg') + '/raw') + assert response.status == 200 +- assert response.headers['CONTENT-LENGTH'] == '9381' + assert response.headers['CONTENT-TYPE'] == 'image/svg+xml' + assert '' in response.html + diff --git a/gns3-server.spec b/gns3-server.spec index 536b8ab..763ec98 100644 --- a/gns3-server.spec +++ b/gns3-server.spec @@ -1,12 +1,13 @@ Name: gns3-server Version: 2.0.3 -Release: 1%{?dist} +Release: 2%{?dist} Summary: Graphical Network Simulator 3 License: GPLv3 URL: http://gns3.com Source0: https://github.com/GNS3/gns3-server/archive/v%{version}/%{name}-%{version}.tar.gz Source1: gns3.service +Patch0: gns3-server-2.0.3-add-aiohttpd2-support.patch BuildArch: noarch BuildRequires: python3-devel @@ -42,7 +43,7 @@ Requires: %{name} = %{version}-%{release} %prep -%autosetup +%autosetup -p1 # Temporary workarounds sed -i '/typing/d' requirements.txt @@ -114,6 +115,9 @@ exit 0 %systemd_postun_with_restart gns3.service %changelog +* Sat Jul 22 2017 Athmane Madjoudj - 2.0.3-2 +- Add a patch to support aiohttp >= 2 + * Sat Jul 15 2017 Athmane Madjoudj - 2.0.3-1 - Update to 2.0.3