From fc14deee1bdf45e862ba83ac44fcb59899ecb0dc Mon Sep 17 00:00:00 2001 From: Julien Duponchelle Date: Tue, 21 Jul 2015 16:14:03 +0200 Subject: [PATCH] Restore images & projects tarballs --- gns3server/handlers/upload_handler.py | 58 ++++++++++++++------- gns3server/templates/index.html | 2 +- gns3server/templates/layout.html | 8 +-- gns3server/templates/upload.html | 2 + tests/handlers/test_upload.py | 72 ++++++++++++++++++++++++++- 5 files changed, 119 insertions(+), 23 deletions(-) diff --git a/gns3server/handlers/upload_handler.py b/gns3server/handlers/upload_handler.py index 0e8036b8..716b52a1 100644 --- a/gns3server/handlers/upload_handler.py +++ b/gns3server/handlers/upload_handler.py @@ -62,23 +62,29 @@ class UploadHandler: response.redirect("/upload") return - if data["type"] not in ["IOU", "IOURC", "QEMU", "IOS"]: - raise aiohttp.web.HTTPForbidden("You are not authorized to upload this kind of image {}".format(data["type"])) - - if data["type"] == "IOURC": - destination_dir = os.path.expanduser("~/") - destination_path = os.path.join(destination_dir, ".iourc") - else: - destination_dir = os.path.join(UploadHandler.image_directory(), data["type"]) - destination_path = os.path.join(destination_dir, data["file"].filename) + if data["type"] not in ["IOU", "IOURC", "QEMU", "IOS", "IMAGES", "PROJECTS"]: + raise aiohttp.web.HTTPForbidden(text="You are not authorized to upload this kind of image {}".format(data["type"])) + try: - os.makedirs(destination_dir, exist_ok=True) - with open(destination_path, "wb+") as f: - chunk = data["file"].file.read() - f.write(chunk) - st = os.stat(destination_path) - os.chmod(destination_path, st.st_mode | stat.S_IXUSR) + if data["type"] == "IMAGES": + UploadHandler._restore_directory(data["file"], UploadHandler.image_directory()) + elif data["type"] == "PROJECTS": + UploadHandler._restore_directory(data["file"], UploadHandler.project_directory()) + else: + if data["type"] == "IOURC": + destination_dir = os.path.expanduser("~/") + destination_path = os.path.join(destination_dir, ".iourc") + else: + destination_dir = os.path.join(UploadHandler.image_directory(), data["type"]) + destination_path = os.path.join(destination_dir, data["file"].filename) + os.makedirs(destination_dir, exist_ok=True) + with open(destination_path, "wb+") as f: + chunk = data["file"].file.read() + f.write(chunk) + st = os.stat(destination_path) + os.chmod(destination_path, st.st_mode | stat.S_IXUSR) except OSError as e: + print(e) response.html("Could not upload file: {}".format(e)) response.set_status(200) return @@ -86,7 +92,7 @@ class UploadHandler: @classmethod @Route.get( - r"/upload/backup/images.tar", + r"/backup/images.tar", description="Backup GNS3 images", api_version=None ) @@ -95,16 +101,34 @@ class UploadHandler: @classmethod @Route.get( - r"/upload/backup/projects.tar", + r"/backup/projects.tar", description="Backup GNS3 projects", api_version=None ) def backup_images(request, response): yield from UploadHandler._backup_directory(request, response, UploadHandler.project_directory()) + @staticmethod + def _restore_directory(file, directory): + """ + Extract from HTTP stream the content of a tar + """ + destination_path = os.path.join(directory, "archive.tar") + os.makedirs(directory, exist_ok=True) + with open(destination_path, "wb+") as f: + chunk = file.file.read() + f.write(chunk) + t = tarfile.open(destination_path) + t.extractall(directory) + t.close() + os.remove(destination_path) + @staticmethod @asyncio.coroutine def _backup_directory(request, response, directory): + """ + Return a tar archive from a directory + """ response.content_type = 'application/x-gtar' response.set_status(200) response.enable_chunked_encoding() diff --git a/gns3server/templates/index.html b/gns3server/templates/index.html index f0fa4304..aa0e14f5 100644 --- a/gns3server/templates/index.html +++ b/gns3server/templates/index.html @@ -6,6 +6,6 @@ {% endblock %} diff --git a/gns3server/templates/layout.html b/gns3server/templates/layout.html index bcb38145..cc451233 100644 --- a/gns3server/templates/layout.html +++ b/gns3server/templates/layout.html @@ -5,11 +5,13 @@
- Home + Home | - Backup images + Upload + | + Backup images | - Backup projects + Backup projects
{% block body %}{% endblock %} diff --git a/gns3server/templates/upload.html b/gns3server/templates/upload.html index a894e467..91db249e 100644 --- a/gns3server/templates/upload.html +++ b/gns3server/templates/upload.html @@ -8,6 +8,8 @@ + +

diff --git a/tests/handlers/test_upload.py b/tests/handlers/test_upload.py index 8d3a8b66..2ad8d49c 100644 --- a/tests/handlers/test_upload.py +++ b/tests/handlers/test_upload.py @@ -51,6 +51,74 @@ def test_upload(server, tmpdir): assert "test2" in response.body.decode("utf-8") +def test_upload_images_backup(server, tmpdir): + Config.instance().set("Server", "images_path", str(tmpdir / 'images')) + os.makedirs(str(tmpdir / 'images' / 'IOU')) + # An old IOU image that we need to replace + with open(str(tmpdir / 'images' / 'IOU' / 'b.img'), 'w+') as f: + f.write('bad') + + os.makedirs(str(tmpdir / 'old' / 'QEMU')) + with open(str(tmpdir / 'old' / 'QEMU' / 'a.img'), 'w+') as f: + f.write('hello') + os.makedirs(str(tmpdir / 'old' / 'IOU')) + with open(str(tmpdir / 'old' / 'IOU' / 'b.img'), 'w+') as f: + f.write('world') + + os.chdir(str(tmpdir / 'old')) + with tarfile.open(str(tmpdir / 'test.tar'), 'w') as tar: + tar.add('.', recursive=True) + + body = aiohttp.FormData() + body.add_field('type', 'IMAGES') + body.add_field('file', open(str(tmpdir / 'test.tar'), 'rb'), content_type='application/x-gtar', filename='test.tar') + response = server.post('/upload', api_version=None, body=body, raw=True) + assert response.status == 200 + + with open(str(tmpdir / 'images' / 'QEMU' / 'a.img')) as f: + assert f.read() == 'hello' + with open(str(tmpdir / 'images' / 'IOU' / 'b.img')) as f: + assert f.read() == 'world' + + assert 'a.img' in response.body.decode('utf-8') + assert 'b.img' in response.body.decode('utf-8') + assert not os.path.exists(str(tmpdir / 'images' / 'archive.tar')) + + +def test_upload_projects_backup(server, tmpdir): + Config.instance().set("Server", "projects_path", str(tmpdir / 'projects')) + os.makedirs(str(tmpdir / 'projects' / 'b')) + # An old b image that we need to replace + with open(str(tmpdir / 'projects' / 'b' / 'b.img'), 'w+') as f: + f.write('bad') + + os.makedirs(str(tmpdir / 'old' / 'a')) + with open(str(tmpdir / 'old' / 'a' / 'a.img'), 'w+') as f: + f.write('hello') + os.makedirs(str(tmpdir / 'old' / 'b')) + with open(str(tmpdir / 'old' / 'b' / 'b.img'), 'w+') as f: + f.write('world') + + os.chdir(str(tmpdir / 'old')) + with tarfile.open(str(tmpdir / 'test.tar'), 'w') as tar: + tar.add('.', recursive=True) + + body = aiohttp.FormData() + body.add_field('type', 'PROJECTS') + body.add_field('file', open(str(tmpdir / 'test.tar'), 'rb'), content_type='application/x-gtar', filename='test.tar') + response = server.post('/upload', api_version=None, body=body, raw=True) + assert response.status == 200 + + with open(str(tmpdir / 'projects' / 'a' / 'a.img')) as f: + assert f.read() == 'hello' + with open(str(tmpdir / 'projects' / 'b' / 'b.img')) as f: + assert f.read() == 'world' + + assert 'a.img' not in response.body.decode('utf-8') + assert 'b.img' not in response.body.decode('utf-8') + assert not os.path.exists(str(tmpdir / 'projects' / 'archive.tar')) + + def test_backup_images(server, tmpdir, loop): Config.instance().set('Server', 'images_path', str(tmpdir)) @@ -60,7 +128,7 @@ def test_backup_images(server, tmpdir, loop): with open(str(tmpdir / 'QEMU' / 'b.img'), 'w+') as f: f.write('world') - response = server.get('/upload/backup/images.tar', api_version=None, raw=True) + response = server.get('/backup/images.tar', api_version=None, raw=True) assert response.status == 200 assert response.headers['CONTENT-TYPE'] == 'application/x-gtar' @@ -92,7 +160,7 @@ def test_backup_projects(server, tmpdir, loop): with open(str(tmpdir / 'b' / 'b.gns3'), 'w+') as f: f.write('world') - response = server.get('/upload/backup/projects.tar', api_version=None, raw=True) + response = server.get('/backup/projects.tar', api_version=None, raw=True) assert response.status == 200 assert response.headers['CONTENT-TYPE'] == 'application/x-gtar'