diff --git a/gns3server/handlers/api/dynamips_vm_handler.py b/gns3server/handlers/api/dynamips_vm_handler.py index 13461897..52698495 100644 --- a/gns3server/handlers/api/dynamips_vm_handler.py +++ b/gns3server/handlers/api/dynamips_vm_handler.py @@ -436,3 +436,16 @@ class DynamipsVMHandler: vms = yield from dynamips_manager.list_images() response.set_status(200) response.json(vms) + + @Route.post( + r"/dynamips/vms/{filename}", + status_codes={ + 204: "Image uploaded", + }, + raw=True, + description="Upload Dynamips image.") + def upload_vm(request, response): + + dynamips_manager = Dynamips.instance() + yield from dynamips_manager.write_image(request.match_info["filename"], request.content) + response.set_status(204) diff --git a/gns3server/handlers/api/iou_handler.py b/gns3server/handlers/api/iou_handler.py index d87e7537..520decc2 100644 --- a/gns3server/handlers/api/iou_handler.py +++ b/gns3server/handlers/api/iou_handler.py @@ -330,3 +330,16 @@ class IOUHandler: vms = yield from iou_manager.list_images() response.set_status(200) response.json(vms) + + @Route.post( + r"/iou/vms/{filename}", + status_codes={ + 204: "Image uploaded", + }, + raw=True, + description="Upload IOU image.") + def upload_vm(request, response): + + iou_manager = IOU.instance() + yield from iou_manager.write_image(request.match_info["filename"], request.content) + response.set_status(204) diff --git a/gns3server/handlers/api/qemu_handler.py b/gns3server/handlers/api/qemu_handler.py index 1b84fb4c..3b0db867 100644 --- a/gns3server/handlers/api/qemu_handler.py +++ b/gns3server/handlers/api/qemu_handler.py @@ -305,3 +305,16 @@ class QEMUHandler: vms = yield from qemu_manager.list_images() response.set_status(200) response.json(vms) + + @Route.post( + r"/qemu/vms/{filename}", + status_codes={ + 204: "Image uploaded", + }, + raw=True, + description="Upload Qemu image.") + def upload_vm(request, response): + + qemu_manager = Qemu.instance() + yield from qemu_manager.write_image(request.match_info["filename"], request.content) + response.set_status(204) diff --git a/gns3server/modules/base_manager.py b/gns3server/modules/base_manager.py index 24a33066..6da7a8bb 100644 --- a/gns3server/modules/base_manager.py +++ b/gns3server/modules/base_manager.py @@ -432,3 +432,20 @@ class BaseManager: """ raise NotImplementedError + + @asyncio.coroutine + def write_image(self, filename, stream): + directory = self.get_images_directory() + path = os.path.join(directory, os.path.basename(filename)) + log.info("Writting image file %s", path) + try: + os.makedirs(directory, exist_ok=True) + with open(path, 'wb+') as f: + while True: + packet = yield from stream.read(512) + if not packet: + break + f.write(packet) + os.chmod(path, stat.S_IWRITE | stat.S_IREAD | stat.S_IEXEC) + except OSError as e: + raise aiohttp.web.HTTPConflict(text="Could not write image: {} to {}".format(filename, e)) diff --git a/gns3server/web/route.py b/gns3server/web/route.py index f376b820..7601bd6b 100644 --- a/gns3server/web/route.py +++ b/gns3server/web/route.py @@ -89,6 +89,7 @@ class Route(object): output_schema = kw.get("output", {}) input_schema = kw.get("input", {}) api_version = kw.get("api_version", 1) + raw = kw.get("raw", False) # If it's a JSON api endpoint just register the endpoint an do nothing if api_version is None: @@ -119,8 +120,9 @@ class Route(object): # This block is executed at each method call # Non API call - if api_version is None: + if api_version is None or raw is True: response = Response(request=request, route=route, output_schema=output_schema) + yield from func(request, response) return response diff --git a/requirements.txt b/requirements.txt index c12c2071..546c71ff 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ netifaces==0.10.4 jsonschema==2.4.0 python-dateutil==2.3 -aiohttp==0.14.4 +aiohttp==0.15.3 Jinja2==2.7.3 raven==5.2.0 diff --git a/setup.py b/setup.py index f72b781d..b251e106 100644 --- a/setup.py +++ b/setup.py @@ -34,12 +34,12 @@ class PyTest(TestCommand): sys.exit(errcode) -dependencies = ["aiohttp>=0.14.4", +dependencies = ["aiohttp>=0.15.3", "jsonschema>=2.4.0", "Jinja2>=2.7.3", "raven>=5.2.0"] -#if not sys.platform.startswith("win"): +# if not sys.platform.startswith("win"): # dependencies.append("netifaces==0.10.4") if sys.version_info == (3, 3): diff --git a/tests/handlers/api/test_dynamips.py b/tests/handlers/api/test_dynamips.py index 8b096071..9deb65af 100644 --- a/tests/handlers/api/test_dynamips.py +++ b/tests/handlers/api/test_dynamips.py @@ -146,3 +146,22 @@ def test_vms(server, tmpdir, fake_dynamips): response = server.get("/dynamips/vms") assert response.status == 200 assert response.json == [{"filename": "7200.bin"}] + + +def test_upload_vm(server, tmpdir): + with patch("gns3server.modules.Dynamips.get_images_directory", return_value=str(tmpdir),): + response = server.post("/dynamips/vms/test2", body="TEST", raw=True) + assert response.status == 204 + + with open(str(tmpdir / "test2")) as f: + assert f.read() == "TEST" + + +def test_upload_vm_permission_denied(server, tmpdir): + with open(str(tmpdir / "test2"), "w+") as f: + f.write("") + os.chmod(str(tmpdir / "test2"), 0) + + with patch("gns3server.modules.Dynamips.get_images_directory", return_value=str(tmpdir),): + response = server.post("/dynamips/vms/test2", body="TEST", raw=True) + assert response.status == 409 diff --git a/tests/handlers/api/test_iou.py b/tests/handlers/api/test_iou.py index 7b91ef62..962cd81d 100644 --- a/tests/handlers/api/test_iou.py +++ b/tests/handlers/api/test_iou.py @@ -20,6 +20,7 @@ import os import stat import sys import uuid +import aiohttp from tests.utils import asyncio_patch from unittest.mock import patch, MagicMock, PropertyMock @@ -311,9 +312,28 @@ def test_get_initial_config_with_config_file(server, project, vm): assert response.json["content"] == "TEST" -def test_vms(server, vm, tmpdir, fake_iou_bin): +def test_vms(server, tmpdir, fake_iou_bin): - with patch("gns3server.modules.IOU.get_images_directory", return_value=str(tmpdir), example=True): - response = server.get("/iou/vms") + with patch("gns3server.modules.IOU.get_images_directory", return_value=str(tmpdir)): + response = server.get("/iou/vms", example=True) assert response.status == 200 assert response.json == [{"filename": "iou.bin"}] + + +def test_upload_vm(server, tmpdir): + with patch("gns3server.modules.IOU.get_images_directory", return_value=str(tmpdir),): + response = server.post("/iou/vms/test2", body="TEST", raw=True) + assert response.status == 204 + + with open(str(tmpdir / "test2")) as f: + assert f.read() == "TEST" + + +def test_upload_vm_permission_denied(server, tmpdir): + with open(str(tmpdir / "test2"), "w+") as f: + f.write("") + os.chmod(str(tmpdir / "test2"), 0) + + with patch("gns3server.modules.IOU.get_images_directory", return_value=str(tmpdir),): + response = server.post("/iou/vms/test2", body="TEST", raw=True) + assert response.status == 409 diff --git a/tests/handlers/api/test_qemu.py b/tests/handlers/api/test_qemu.py index 4af9065f..cf08992a 100644 --- a/tests/handlers/api/test_qemu.py +++ b/tests/handlers/api/test_qemu.py @@ -200,3 +200,22 @@ def test_vms(server, tmpdir, fake_qemu_vm): response = server.get("/qemu/vms") assert response.status == 200 assert response.json == [{"filename": "linux.img"}] + + +def test_upload_vm(server, tmpdir): + with patch("gns3server.modules.Qemu.get_images_directory", return_value=str(tmpdir),): + response = server.post("/qemu/vms/test2", body="TEST", raw=True) + assert response.status == 204 + + with open(str(tmpdir / "test2")) as f: + assert f.read() == "TEST" + + +def test_upload_vm_permission_denied(server, tmpdir): + with open(str(tmpdir / "test2"), "w+") as f: + f.write("") + os.chmod(str(tmpdir / "test2"), 0) + + with patch("gns3server.modules.Qemu.get_images_directory", return_value=str(tmpdir),): + response = server.post("/qemu/vms/test2", body="TEST", raw=True) + assert response.status == 409