1
0
mirror of https://github.com/GNS3/gns3-server synced 2024-12-26 16:58:28 +00:00

Merge pull request #272 from GNS3/backup_upload_images_projects

Backup upload images projects
This commit is contained in:
Jeremy Grossmann 2015-07-21 11:21:12 -07:00
commit 1b066bef92
6 changed files with 237 additions and 16 deletions

View File

@ -18,6 +18,9 @@
import os import os
import aiohttp import aiohttp
import stat import stat
import io
import tarfile
import asyncio
from ..config import Config from ..config import Config
from ..web.route import Route from ..web.route import Route
@ -59,16 +62,21 @@ class UploadHandler:
response.redirect("/upload") response.redirect("/upload")
return return
if data["type"] not in ["IOU", "IOURC", "QEMU", "IOS"]: if data["type"] not in ["IOU", "IOURC", "QEMU", "IOS", "IMAGES", "PROJECTS"]:
raise aiohttp.web.HTTPForbidden("You are not authorized to upload this kind of image {}".format(data["type"])) raise aiohttp.web.HTTPForbidden(text="You are not authorized to upload this kind of image {}".format(data["type"]))
try:
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": if data["type"] == "IOURC":
destination_dir = os.path.expanduser("~/") destination_dir = os.path.expanduser("~/")
destination_path = os.path.join(destination_dir, ".iourc") destination_path = os.path.join(destination_dir, ".iourc")
else: else:
destination_dir = os.path.join(UploadHandler.image_directory(), data["type"]) destination_dir = os.path.join(UploadHandler.image_directory(), data["type"])
destination_path = os.path.join(destination_dir, data["file"].filename) destination_path = os.path.join(destination_dir, data["file"].filename)
try:
os.makedirs(destination_dir, exist_ok=True) os.makedirs(destination_dir, exist_ok=True)
with open(destination_path, "wb+") as f: with open(destination_path, "wb+") as f:
chunk = data["file"].file.read() chunk = data["file"].file.read()
@ -81,7 +89,70 @@ class UploadHandler:
return return
response.redirect("/upload") response.redirect("/upload")
@classmethod
@Route.get(
r"/backup/images.tar",
description="Backup GNS3 images",
api_version=None
)
def backup_images(request, response):
yield from UploadHandler._backup_directory(request, response, UploadHandler.image_directory())
@classmethod
@Route.get(
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()
# Very important: do not send a content length otherwise QT close the connection but curl can consume the Feed
response.content_length = None
response.start(request)
buffer = io.BytesIO()
with tarfile.open('arch.tar', 'w', fileobj=buffer) as tar:
for root, dirs, files in os.walk(directory):
for file in files:
path = os.path.join(root, file)
tar.add(os.path.join(root, file), arcname=os.path.relpath(path, directory))
response.write(buffer.getvalue())
yield from response.drain()
buffer.truncate(0)
buffer.seek(0)
yield from response.write_eof()
@staticmethod @staticmethod
def image_directory(): def image_directory():
server_config = Config.instance().get_section_config("Server") server_config = Config.instance().get_section_config("Server")
return os.path.expanduser(server_config.get("images_path", "~/GNS3/images")) return os.path.expanduser(server_config.get("images_path", "~/GNS3/images"))
@staticmethod
def project_directory():
server_config = Config.instance().get_section_config("Server")
return os.path.expanduser(server_config.get("projects_path", "~/GNS3/projects"))

View File

@ -6,6 +6,6 @@
<ul> <ul>
<li><a href="http://community.gns3.com">Community</a></li> <li><a href="http://community.gns3.com">Community</a></li>
<li><a href="http://api.gns3.net">API documentation</a></li> <li><a href="http://api.gns3.net">API documentation</a></li>
<li><a href="/upload">Upload images</a></li> <li><a href="/upload">Upload images & backup</a></li>
</ul> </ul>
{% endblock %} {% endblock %}

View File

@ -4,6 +4,15 @@
<title>GNS3 Server</title> <title>GNS3 Server</title>
</head> </head>
<body> <body>
<div>
<a href="/">Home</a>
|
<a href="/upload">Upload</a>
|
<a href="/backup/images.tar">Backup images</a>
|
<a href="/backup/projects.tar">Backup projects</a>
</div>
{% block body %}{% endblock %} {% block body %}{% endblock %}
</body> </body>
<small> <small>

View File

@ -8,6 +8,8 @@
<option value="IOURC">IOU licence (iourc)</option> <option value="IOURC">IOU licence (iourc)</option>
<option value="IOS">IOS</option> <option value="IOS">IOS</option>
<option value="QEMU">Qemu</option> <option value="QEMU">Qemu</option>
<option value="IMAGES">GNS3 images backup (.tar)</option>
<option value="PROJECTS">GNS3 projects backup (.tar)</option>
</select> </select>
<br /> <br />
<br /> <br />

View File

@ -88,7 +88,10 @@ class Query:
except ValueError: except ValueError:
response.json = None response.json = None
else: else:
try:
response.html = response.body.decode("utf-8") response.html = response.body.decode("utf-8")
except UnicodeDecodeError:
response.html = None
else: else:
response.json = {} response.json = {}
response.html = "" response.html = ""

View File

@ -17,10 +17,15 @@
import aiohttp import aiohttp
import asyncio
import os import os
import tarfile
from unittest.mock import patch from unittest.mock import patch
from gns3server.config import Config from gns3server.config import Config
def test_index_upload(server): def test_index_upload(server):
response = server.get('/upload', api_version=None) response = server.get('/upload', api_version=None)
assert response.status == 200 assert response.status == 200
@ -44,3 +49,134 @@ def test_upload(server, tmpdir):
assert f.read() == "TEST" assert f.read() == "TEST"
assert "test2" in response.body.decode("utf-8") 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))
os.makedirs(str(tmpdir / 'QEMU'))
with open(str(tmpdir / 'QEMU' / 'a.img'), 'w+') as f:
f.write('hello')
with open(str(tmpdir / 'QEMU' / 'b.img'), 'w+') as f:
f.write('world')
response = server.get('/backup/images.tar', api_version=None, raw=True)
assert response.status == 200
assert response.headers['CONTENT-TYPE'] == 'application/x-gtar'
with open(str(tmpdir / 'images.tar'), 'wb+') as f:
print(len(response.body))
f.write(response.body)
tar = tarfile.open(str(tmpdir / 'images.tar'), 'r')
os.makedirs(str(tmpdir / 'extract'))
os.chdir(str(tmpdir / 'extract'))
# Extract to current working directory
tar.extractall()
tar.close()
assert os.path.exists(os.path.join('QEMU', 'a.img'))
open(os.path.join('QEMU', 'a.img')).read() == 'hello'
assert os.path.exists(os.path.join('QEMU', 'b.img'))
open(os.path.join('QEMU', 'b.img')).read() == 'world'
def test_backup_projects(server, tmpdir, loop):
Config.instance().set('Server', 'projects_path', str(tmpdir))
os.makedirs(str(tmpdir / 'a'))
with open(str(tmpdir / 'a' / 'a.gns3'), 'w+') as f:
f.write('hello')
os.makedirs(str(tmpdir / 'b'))
with open(str(tmpdir / 'b' / 'b.gns3'), 'w+') as f:
f.write('world')
response = server.get('/backup/projects.tar', api_version=None, raw=True)
assert response.status == 200
assert response.headers['CONTENT-TYPE'] == 'application/x-gtar'
with open(str(tmpdir / 'projects.tar'), 'wb+') as f:
print(len(response.body))
f.write(response.body)
tar = tarfile.open(str(tmpdir / 'projects.tar'), 'r')
os.makedirs(str(tmpdir / 'extract'))
os.chdir(str(tmpdir / 'extract'))
# Extract to current working directory
tar.extractall()
tar.close()
assert os.path.exists(os.path.join('a', 'a.gns3'))
open(os.path.join('a', 'a.gns3')).read() == 'hello'
assert os.path.exists(os.path.join('b', 'b.gns3'))
open(os.path.join('b', 'b.gns3')).read() == 'world'