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