Compare commits

...
21 Commits
Author SHA1 Message Date
Jeremy bdb23d6d3b 1.3.8 2015-07-27 12:10:01 -06:00
Jeremy 5986849b67 Catch ProcessLookupError when updating iouyap config. Fixes #255. 2015-07-27 11:40:01 -06:00
Jeremy 76fe0cb316 Fixes IOS adapters and WICS cannot be removed. Fixes #282. 2015-07-26 15:51:55 -06:00
Jeremy 4aeb451ffd Makes sure the loop is running when closing the app. 2015-07-26 15:28:14 -06:00
Jeremy 7b001e4d36 Catch GeneratorExit exception. Fixes #231. 2015-07-25 16:46:23 -06:00
Jeremy b1b602db3b Merge remote-tracking branch 'origin/master' 2015-07-25 16:39:45 -06:00
Jeremy cf5af8a6f7 Fixes missing chipset info for VirtualBox VM (maybe some older VirtualBox version don't have it). Fixes #254. 2015-07-25 16:39:38 -06:00
Julien Duponchelle 4814bc6c65 Fix IOURC upload 2015-07-23 11:36:11 +02:00
Jeremy 2655d426c2 Backport: option to drop nvram & disk files for IOS routers in order to save disk space. 2015-07-22 16:02:08 -06:00
Jeremy f1bbacecd6 Backport: Remove timeout to wait for connections to finish. 2015-07-21 18:00:31 -06:00
Jeremy a8142a04f8 Merge remote-tracking branch 'origin/master' 2015-07-21 16:02:52 -06:00
Jeremy 19425b1e9c Backport: Fixes RuntimeError: Event loop is closed. 2015-07-21 16:02:44 -06:00
Jeremy Grossmann 1b066bef92 Merge pull request #272 from GNS3/backup_upload_images_projects
Backup upload images projects
2015-07-21 11:21:12 -07:00
Julien Duponchelle 6bf7a6aa38 Fix after jeremy feedback 2015-07-21 20:19:29 +02:00
Julien Duponchelle fc14deee1b Restore images & projects tarballs 2015-07-21 16:14:03 +02:00
Julien Duponchelle 270017d945 Allow user to backup projects and images 2015-07-21 14:20:58 +02:00
Jeremy Grossmann 23a03a24de Merge pull request #271 from lykinsbd/master
Update gns3.conf.upstart
2015-07-20 22:44:40 -07:00
Jeremy 439e0df058 Bind host on 0.0.0.0 when checking for a free UDP port. 2015-07-20 19:27:14 -06:00
Brett Lykins c94dc1ec07 Update gns3.conf.upstart
Fixing start-stop-daemon syntax so that it accepts the aruments
2015-07-20 10:42:34 -05:00
Julien Duponchelle 2ccb30eeb5 Fix Incorrect vboxmanage sudo command
Fix #247
2015-06-26 10:47:52 +02:00
Julien Duponchelle 389788cfdc 1.3.8 dev 2015-06-22 20:19:38 +02:00
20 changed files with 382 additions and 43 deletions

No files matched your search

+17
View File
@@ -1,5 +1,22 @@
# Change Log
## 1.3.8 27/07/2015
* Catch ProcessLookupError when updating iouyap config. Fixes #255.
* Fixes IOS adapters and WICS cannot be removed. Fixes #282.
* Makes sure the loop is running when closing the app.
* Catch GeneratorExit exception. Fixes #231.
* Fixes missing chipset info for VirtualBox VM. Fixes #254.
* Fixes IOURC upload.
* Restore images & projects tarballs
* Allow users to backup projects and images.
* Update gns3.conf.upstart.
* Fix incorrect vboxmanage sudo command.
* Backport from 1.4: option to drop nvram & disk files for IOS routers in order to save disk space.
* Backport from 1.4: Remove timeout to wait for connections to finish.
* Backport from 1.4: Fixes RuntimeError: Event loop is closed.
* Backport from 1.4: Bind host on 0.0.0.0 when checking for a free UDP port.
## 1.3.7 22/06/2015
* Prevent install on Python 2
+1 -1
View File
@@ -51,7 +51,7 @@ class CrashReport:
Report crash to a third party service
"""
DSN = "sync+https://3d569add80c74d4faecf90836bcdc1b5:3b5aa0c47c1847bc8d019b5e52ebdd1a@app.getsentry.com/38482"
DSN = "sync+https://c06a407cbe0d4036be7873b54ebfa0ef:d6129d3fe91a4ea0b96ecbcf99678b1b@app.getsentry.com/38482"
if hasattr(sys, "frozen"):
cacert = os.path.join(os.getcwd(), "cacert.pem")
if os.path.isfile(cacert):
+85 -14
View File
@@ -18,6 +18,9 @@
import os
import aiohttp
import stat
import io
import tarfile
import asyncio
from ..config import Config
from ..web.route import Route
@@ -59,29 +62,97 @@ 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"] 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"]))
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:
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:
response.html("Could not upload file: {}".format(e))
response.set_status(200)
return
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
def image_directory():
server_config = Config.instance().get_section_config("Server")
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"))
+1 -1
View File
@@ -113,7 +113,7 @@ class BaseManager:
for future in done:
try:
future.result()
except Exception as e:
except (Exception, GeneratorExit) as e:
log.error("Could not close VM {}".format(e), exc_info=1)
continue
+7 -4
View File
@@ -130,7 +130,7 @@ class Dynamips(BaseManager):
for future in done:
try:
future.result()
except Exception as e:
except (Exception, GeneratorExit) as e:
log.error("Could not stop device hypervisor {}".format(e), exc_info=1)
continue
@@ -154,7 +154,7 @@ class Dynamips(BaseManager):
for future in done:
try:
future.result()
except Exception as e:
except (Exception, GeneratorExit) as e:
log.error("Could not delete device {}".format(e), exc_info=1)
@asyncio.coroutine
@@ -171,8 +171,11 @@ class Dynamips(BaseManager):
files = glob.glob(os.path.join(project_dir, "*.ghost"))
files += glob.glob(os.path.join(project_dir, "*_lock"))
files += glob.glob(os.path.join(project_dir, "ilt_*"))
files += glob.glob(os.path.join(project_dir, "c[0-9][0-9][0-9][0-9]_*_rommon_vars"))
files += glob.glob(os.path.join(project_dir, "c[0-9][0-9][0-9][0-9]_*_ssa"))
files += glob.glob(os.path.join(project_dir, "c[0-9][0-9][0-9][0-9]_i[0-9]*_rommon_vars"))
files += glob.glob(os.path.join(project_dir, "c[0-9][0-9][0-9][0-9]_i[0-9]*_ssa"))
files += glob.glob(os.path.join(project_dir, "c[0-9][0-9][0-9][0-9]_i[0-9]*_log.txt"))
files += glob.glob(os.path.join(project_dir, "c[0-9][0-9][0-9][0-9]_i[0-9]*_rom"))
files += glob.glob(os.path.join(project_dir, "c[0-9][0-9][0-9][0-9]_i[0-9]*_bootflash"))
for file in files:
try:
log.debug("Deleting file {}".format(file))
+67 -6
View File
@@ -87,6 +87,7 @@ class Router(BaseVM):
self._exec_area = 64 # 64 MB on other systems
self._disk0 = 0 # Megabytes
self._disk1 = 0 # Megabytes
self._auto_delete_disks = False
self._aux = aux
self._mac_addr = ""
self._system_id = "FTX0945W0MY" # processor board ID in IOS
@@ -145,6 +146,7 @@ class Router(BaseVM):
"exec_area": self._exec_area,
"disk0": self._disk0,
"disk1": self._disk1,
"auto_delete_disks": self._auto_delete_disks,
"console": self._console,
"aux": self._aux,
"mac_addr": self._mac_addr,
@@ -158,7 +160,7 @@ class Router(BaseVM):
for slot in self._slots:
if slot:
slot = str(slot)
router_info["slot" + str(slot_number)] = slot
router_info["slot" + str(slot_number)] = slot
slot_number += 1
# add the wics
@@ -166,6 +168,8 @@ class Router(BaseVM):
for wic_slot_number in range(0, len(self._slots[0].wics)):
if self._slots[0].wics[wic_slot_number]:
router_info["wic" + str(wic_slot_number)] = str(self._slots[0].wics[wic_slot_number])
else:
router_info["wic" + str(wic_slot_number)] = None
return router_info
@@ -182,6 +186,16 @@ class Router(BaseVM):
@asyncio.coroutine
def create(self):
# delete any previous file with same Dynamips identifier
project_dir = os.path.join(self.project.module_working_directory(self.manager.module_name.lower()))
for file in glob.glob(os.path.join(project_dir, "c[0-9][0-9][0-9][0-9]_i{}_*".format(self._dynamips_id))):
try:
log.debug("Deleting file {}".format(file))
yield from wait_run_in_executor(os.remove, file)
except OSError as e:
log.warn("Could not delete file {}: {}".format(file, e))
continue
if not self._hypervisor:
module_workdir = self.project.module_working_directory(self.manager.module_name.lower())
self._hypervisor = yield from self.manager.start_new_hypervisor(working_dir=module_workdir)
@@ -342,6 +356,20 @@ class Router(BaseVM):
pass
yield from self.hypervisor.stop()
if self._auto_delete_disks:
# delete nvram and disk files
project_dir = os.path.join(self.project.module_working_directory(self.manager.module_name.lower()))
files = glob.glob(os.path.join(project_dir, "{}_i{}_disk[0-1]".format(self.platform, self.dynamips_id)))
files += glob.glob(os.path.join(project_dir, "{}_i{}_slot[0-1]".format(self.platform, self.dynamips_id)))
files += glob.glob(os.path.join(project_dir, "{}_i{}_nvram".format(self.platform, self.dynamips_id)))
files += glob.glob(os.path.join(project_dir, "{}_i{}_flash[0-1]".format(self.platform, self.dynamips_id)))
for file in files:
try:
log.debug("Deleting file {}".format(file))
yield from wait_run_in_executor(os.remove, file)
except OSError as e:
log.warn("Could not delete file {}: {}".format(file, e))
continue
self._closed = True
@property
@@ -851,6 +879,30 @@ class Router(BaseVM):
new_disk1=disk1))
self._disk1 = disk1
@property
def auto_delete_disks(self):
"""
Returns True if auto delete disks is enabled on this router.
:returns: boolean either auto delete disks is activated or not
"""
return self._auto_delete_disks
@asyncio.coroutine
def set_auto_delete_disks(self, auto_delete_disks):
"""
Enable/disable use of auto delete disks
:param auto_delete_disks: activate/deactivate auto delete disks (boolean)
"""
if auto_delete_disks:
log.info('Router "{name}" [{id}]: auto delete disks enabled'.format(name=self._name, id=self._id))
else:
log.info('Router "{name}" [{id}]: auto delete disks disabled'.format(name=self._name, id=self._id))
self._auto_delete_disks = auto_delete_disks
@asyncio.coroutine
def set_console(self, console):
"""
@@ -1178,11 +1230,20 @@ class Router(BaseVM):
if not adapter.port_exists(port_number):
raise DynamipsError("Port {port_number} does not exist in adapter {adapter}".format(adapter=adapter,
port_number=port_number))
yield from self._hypervisor.send('vm slot_add_nio_binding "{name}" {slot_number} {port_number} {nio}'.format(name=self._name,
slot_number=slot_number,
port_number=port_number,
nio=nio))
try:
yield from self._hypervisor.send('vm slot_add_nio_binding "{name}" {slot_number} {port_number} {nio}'.format(name=self._name,
slot_number=slot_number,
port_number=port_number,
nio=nio))
except DynamipsError:
# in case of error try to remove and add the nio binding
yield from self._hypervisor.send('vm slot_remove_nio_binding "{name}" {slot_number} {port_number}'.format(name=self._name,
slot_number=slot_number,
port_number=port_number))
yield from self._hypervisor.send('vm slot_add_nio_binding "{name}" {slot_number} {port_number} {nio}'.format(name=self._name,
slot_number=slot_number,
port_number=port_number,
nio=nio))
log.info('Router "{name}" [{id}]: NIO {nio_name} bound to port {slot_number}/{port_number}'.format(name=self._name,
id=self._id,
+16 -5
View File
@@ -862,7 +862,10 @@ class IOUVM(BaseVM):
port_number=port_number))
if self.is_iouyap_running():
self._update_iouyap_config()
os.kill(self._iouyap_process.pid, signal.SIGHUP)
try:
os.kill(self._iouyap_process.pid, signal.SIGHUP)
except ProcessLookupError:
log.error("Could not update iouyap configuration: process (PID={}) not found".format(self._iouyap_process.pid))
def adapter_remove_nio_binding(self, adapter_number, port_number):
"""
@@ -894,8 +897,10 @@ class IOUVM(BaseVM):
port_number=port_number))
if self.is_iouyap_running():
self._update_iouyap_config()
os.kill(self._iouyap_process.pid, signal.SIGHUP)
try:
os.kill(self._iouyap_process.pid, signal.SIGHUP)
except ProcessLookupError:
log.error("Could not update iouyap configuration: process (PID={}) not found".format(self._iouyap_process.pid))
return nio
@property
@@ -1052,7 +1057,10 @@ class IOUVM(BaseVM):
if self.is_iouyap_running():
self._update_iouyap_config()
os.kill(self._iouyap_process.pid, signal.SIGHUP)
try:
os.kill(self._iouyap_process.pid, signal.SIGHUP)
except ProcessLookupError:
log.error("Could not update iouyap configuration: process (PID={}) not found".format(self._iouyap_process.pid))
@asyncio.coroutine
def stop_capture(self, adapter_number, port_number):
@@ -1085,4 +1093,7 @@ class IOUVM(BaseVM):
port_number=port_number))
if self.is_iouyap_running():
self._update_iouyap_config()
os.kill(self._iouyap_process.pid, signal.SIGHUP)
try:
os.kill(self._iouyap_process.pid, signal.SIGHUP)
except ProcessLookupError:
log.error("Could not update iouyap configuration: process (PID={}) not found".format(self._iouyap_process.pid))
+2 -1
View File
@@ -34,8 +34,9 @@ class PortManager:
def __init__(self, host="127.0.0.1"):
self._console_host = host
self._udp_host = host
# UDP host must be 0.0.0.0, reason: https://github.com/GNS3/gns3-server/issues/265
self._udp_host = "0.0.0.0"
self._used_tcp_ports = set()
self._used_udp_ports = set()
+1 -1
View File
@@ -363,7 +363,7 @@ class Project:
for future in done:
try:
future.result()
except Exception as e:
except (Exception, GeneratorExit) as e:
log.error("Could not close VM or device {}".format(e), exc_info=1)
if cleanup and os.path.exists(self.path):
+1 -1
View File
@@ -97,7 +97,7 @@ class VirtualBox(BaseManager):
vbox_user = self.config.get_section_config("VirtualBox").get("vbox_user")
if vbox_user:
# TODO: test & review this part
sudo_command = "sudo -i -u {}".format(vbox_user) + " ".join(command)
sudo_command = "sudo -i -u {} ".format(vbox_user) + " ".join(command)
process = yield from asyncio.create_subprocess_shell(sudo_command, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE)
else:
process = yield from asyncio.create_subprocess_exec(*command, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE)
@@ -574,10 +574,11 @@ class VirtualBoxVM(BaseVM):
# check the maximum number of adapters supported by the VM
vm_info = yield from self._get_vm_info()
chipset = vm_info["chipset"]
maximum_adapters = 8
if chipset == "ich9":
maximum_adapters = int(self._system_properties["Maximum ICH9 Network Adapter count"])
if "chipset" in vm_info:
chipset = vm_info["chipset"]
if chipset == "ich9":
maximum_adapters = int(self._system_properties["Maximum ICH9 Network Adapter count"])
return maximum_adapters
def _get_pipe_name(self):
+12
View File
@@ -120,6 +120,10 @@ VM_CREATE_SCHEMA = {
"description": "disk1 size in MB",
"type": "integer"
},
"auto_delete_disks": {
"description": "automatically delete nvram and disk files",
"type": "boolean"
},
"console": {
"description": "console TCP port",
"type": "integer",
@@ -337,6 +341,10 @@ VM_UPDATE_SCHEMA = {
"description": "disk1 size in MB",
"type": "integer"
},
"auto_delete_disks": {
"description": "automatically delete nvram and disk files",
"type": "boolean"
},
"console": {
"description": "console TCP port",
"type": "integer",
@@ -591,6 +599,10 @@ VM_OBJECT_SCHEMA = {
"description": "disk1 size in MB",
"type": "integer"
},
"auto_delete_disks": {
"description": "automatically delete nvram and disk files",
"type": "boolean"
},
"console": {
"description": "console TCP port",
"type": "integer",
+13 -1
View File
@@ -86,6 +86,7 @@ class Server:
if self._handler:
yield from self._handler.finish_connections()
self._handler = None
for module in MODULES:
log.debug("Unloading module {}".format(module.__name__))
@@ -175,6 +176,10 @@ class Server:
yield from embed(globals(), locals(), return_asyncio_coroutine=True, patch_stdout=True)
def _exit_handling(self):
"""
Makes sure the asyncio loop is closed.
"""
def close_asyncio_loop():
loop = None
try:
@@ -226,7 +231,8 @@ class Server:
log.info("Starting server on {}:{}".format(self._host, self._port))
self._handler = app.make_handler(handler=RequestHandler)
self._loop.run_until_complete(self._run_application(self._handler, ssl_context))
server = self._run_application(self._handler, ssl_context)
self._loop.run_until_complete(server)
self._signal_handling()
self._exit_handling()
@@ -245,3 +251,9 @@ class Server:
# on Windows when the process gets the SIGBREAK signal
# TypeError: async() takes 1 positional argument but 3 were given
log.warning("TypeError exception in the loop {}".format(e))
finally:
if self._handler and self._loop.is_running():
self._loop.run_until_complete(self._handler.finish_connections())
server.close()
if self._loop.is_running():
self._loop.run_until_complete(app.finish())
+1 -1
View File
@@ -6,6 +6,6 @@
<ul>
<li><a href="http://community.gns3.com">Community</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>
{% endblock %}
+9
View File
@@ -4,6 +4,15 @@
<title>GNS3 Server</title>
</head>
<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 %}
</body>
<small>
+2
View File
@@ -8,6 +8,8 @@
<option value="IOURC">IOU licence (iourc)</option>
<option value="IOS">IOS</option>
<option value="QEMU">Qemu</option>
<option value="IMAGES">GNS3 images backup (.tar)</option>
<option value="PROJECTS">GNS3 projects backup (.tar)</option>
</select>
<br />
<br />
+2 -2
View File
@@ -23,5 +23,5 @@
# or negative for a release candidate or beta (after the base version
# number has been incremented)
__version__ = "1.3.7"
__version_info__ = (1, 3, 7, 0)
__version__ = "1.3.8"
__version_info__ = (1, 3, 8, 0)
+1 -1
View File
@@ -6,7 +6,7 @@ stop on shutdown
script
echo $$ > /var/run/gns3.pid
exec start-stop-daemon --start -c gns3 --exec /usr/local/bin/gns3server --log /var/log/gns3.log
exec start-stop-daemon --start -c gns3 --exec /usr/local/bin/gns3server -- --log /var/log/gns3.log
end script
pre-start script
+4 -1
View File
@@ -88,7 +88,10 @@ class Query:
except ValueError:
response.json = None
else:
response.html = response.body.decode("utf-8")
try:
response.html = response.body.decode("utf-8")
except UnicodeDecodeError:
response.html = None
else:
response.json = {}
response.html = ""
+136
View File
@@ -17,10 +17,15 @@
import aiohttp
import asyncio
import os
import tarfile
from unittest.mock import patch
from gns3server.config import Config
def test_index_upload(server):
response = server.get('/upload', api_version=None)
assert response.status == 200
@@ -44,3 +49,134 @@ def test_upload(server, tmpdir):
assert f.read() == "TEST"
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'