1
0
mirror of https://github.com/GNS3/gns3-server synced 2024-11-28 11:18:11 +00:00

Merge remote-tracking branch 'origin/master' into unstable

This commit is contained in:
Julien Duponchelle 2015-07-23 11:35:15 +02:00
commit cbc859a03b
6 changed files with 240 additions and 21 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
@ -60,34 +63,102 @@ 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"]))
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)
try: try:
os.makedirs(destination_dir, exist_ok=True) if data["type"] == "IMAGES":
remove_checksum(destination_path) UploadHandler._restore_directory(data["file"], UploadHandler.image_directory())
with open(destination_path, "wb+") as f: elif data["type"] == "PROJECTS":
while True: UploadHandler._restore_directory(data["file"], UploadHandler.project_directory())
chunk = data["file"].file.read(512) else:
if not chunk: if data["type"] == "IOURC":
break destination_dir = os.path.expanduser("~/")
f.write(chunk) destination_path = os.path.join(destination_dir, ".iourc")
md5sum(destination_path) else:
st = os.stat(destination_path) destination_dir = os.path.join(UploadHandler.image_directory(), data["type"])
os.chmod(destination_path, st.st_mode | stat.S_IXUSR) destination_path = os.path.join(destination_dir, data["file"].filename)
os.makedirs(destination_dir, exist_ok=True)
remove_checksum(destination_path)
with open(destination_path, "wb+") as f:
while True:
chunk = data["file"].file.read(512)
if not chunk:
break
f.write(chunk)
md5sum(destination_path)
st = os.stat(destination_path)
os.chmod(destination_path, st.st_mode | stat.S_IXUSR)
except OSError as e: except OSError as e:
response.html("Could not upload file: {}".format(e)) response.html("Could not upload file: {}".format(e))
response.set_status(200) response.set_status(200)
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

@ -7,6 +7,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

@ -25,6 +25,8 @@ function onSubmit() {
<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:
response.html = response.body.decode("utf-8") try:
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,8 +17,12 @@
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
@ -91,3 +95,133 @@ def test_upload_previous_checksum(server, tmpdir):
with open(str(tmpdir / "QEMU" / "test2.md5sum")) as f: with open(str(tmpdir / "QEMU" / "test2.md5sum")) as f:
checksum = f.read() checksum = f.read()
assert checksum == "ae187e1febee2a150b64849c32d566ca" assert checksum == "ae187e1febee2a150b64849c32d566ca"
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'