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

Early import project api

This commit is contained in:
Julien Duponchelle 2016-07-21 14:48:13 +02:00
parent 14f6bd60fb
commit b5ab53bbe9
No known key found for this signature in database
GPG Key ID: CE8B29639E07F5E8
8 changed files with 221 additions and 4 deletions

View File

@ -286,6 +286,20 @@ class Controller:
yield from project.open() yield from project.open()
return project return project
def get_free_project_name(self, base_name):
"""
Generate a free project name base on the base name
"""
names = [ p.name for p in self._projects.values() ]
if base_name not in names:
return base_name
i = 1
while "{}-{}".format(base_name, i) in names:
i += 1
if i > 1000000:
raise aiohttp.web.HTTPConflict(text="A project name could not be allocated (node limit reached?)")
return "{}-{}".format(base_name, i)
@property @property
def projects(self): def projects(self):
""" """

View File

@ -0,0 +1,69 @@
#!/usr/bin/env python
#
# Copyright (C) 2016 GNS3 Technologies Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
import json
import uuid
import asyncio
import zipfile
import aiohttp
from ..config import Config
"""
Handle the import of project from a .gns3project
"""
@asyncio.coroutine
def import_project(controller, project_id, stream, gns3vm=True):
"""
Import a project contain in a zip file
You need to handle OSerror exceptions
:param stream: A io.BytesIO of the zipfile
:param gns3vm: True move Docker, IOU and Qemu to the GNS3 VM
:returns: Project
"""
server_config = Config.instance().get_section_config("Server")
projects_path = os.path.expanduser(server_config.get("projects_path", "~/GNS3/projects"))
os.makedirs(projects_path, exist_ok=True)
with zipfile.ZipFile(stream) as myzip:
try:
topology = json.loads(myzip.read("project.gns3").decode())
# If the project name is already used we generate a new one
topology["name"] = controller.get_free_project_name(topology["name"])
except KeyError:
raise aiohttp.web.HTTPConflict(text="Can't import topology the .gns3 is corrupted or missing")
path = os.path.join(projects_path, topology["name"])
os.makedirs(path)
myzip.extractall(path)
dot_gns3_path = os.path.join(path, topology["name"] + ".gns3")
# We change the project_id to avoid erasing the project
topology["project_id"] = project_id
with open(dot_gns3_path, "w+") as f:
json.dump(topology, f, indent=4)
os.remove(os.path.join(path, "project.gns3"))
project = yield from controller.load_project(dot_gns3_path)
return project

View File

@ -409,7 +409,7 @@ class ProjectHandler:
# We write the content to a temporary location and after we extract it all. # We write the content to a temporary location and after we extract it all.
# It could be more optimal to stream this but it is not implemented in Python. # It could be more optimal to stream this but it is not implemented in Python.
# Spooled means the file is temporary kept in memory until max_size is reached # Spooled means the file is temporary kept in memory until max_size is reached
try: try:
with tempfile.SpooledTemporaryFile(max_size=10000) as temp: with tempfile.SpooledTemporaryFile(max_size=10000) as temp:
while True: while True:

View File

@ -18,10 +18,12 @@
import os import os
import aiohttp import aiohttp
import asyncio import asyncio
import tempfile
from gns3server.web.route import Route from gns3server.web.route import Route
from gns3server.controller import Controller from gns3server.controller import Controller
from gns3server.controller.project import Project
from gns3server.controller.import_project import import_project
from gns3server.config import Config from gns3server.config import Config
@ -251,6 +253,39 @@ class ProjectHandler:
yield from response.write_eof() yield from response.write_eof()
@Route.post(
r"/projects/{project_id}/import",
description="Import a project from a portable archive",
parameters={
"project_id": "Project UUID",
},
raw=True,
output=PROJECT_OBJECT_SCHEMA,
status_codes={
200: "Project imported",
403: "Forbidden to import project"
})
def import_project(request, response):
controller = Controller.instance()
# We write the content to a temporary location and after we extract it all.
# It could be more optimal to stream this but it is not implemented in Python.
# Spooled means the file is temporary kept in memory until max_size is reached
try:
with tempfile.SpooledTemporaryFile(max_size=10000) as temp:
while True:
packet = yield from request.content.read(512)
if not packet:
break
temp.write(packet)
project = yield from import_project(controller, request.match_info["project_id"], temp, gns3vm=bool(int(request.GET.get("gns3vm", "1"))))
except OSError as e:
raise aiohttp.web.HTTPInternalServerError(text="Could not import the project: {}".format(e))
response.json(project)
response.set_status(201)
@Route.get( @Route.get(
r"/projects/{project_id}/files/{path:.+}", r"/projects/{project_id}/files/{path:.+}",
description="Get a file from a project. Beware you have warranty to be able to access only to file global to the project (for example README.txt)", description="Get a file from a project. Beware you have warranty to be able to access only to file global to the project (for example README.txt)",
@ -332,3 +367,5 @@ class ProjectHandler:
raise aiohttp.web.HTTPNotFound() raise aiohttp.web.HTTPNotFound()
except PermissionError: except PermissionError:
raise aiohttp.web.HTTPForbidden() raise aiohttp.web.HTTPForbidden()

View File

@ -335,3 +335,13 @@ def test_load_project(controller, async_run, tmpdir):
with asyncio_patch("gns3server.controller.Controller.add_project") as mock_add_project: with asyncio_patch("gns3server.controller.Controller.add_project") as mock_add_project:
project = async_run(controller.load_project(str(tmpdir / "test.gns3"))) project = async_run(controller.load_project(str(tmpdir / "test.gns3")))
assert not mock_add_project.called assert not mock_add_project.called
def test_get_free_project_name(controller, async_run):
async_run(controller.add_project(project_id=str(uuid.uuid4()), name="Test"))
assert controller.get_free_project_name("Test") == "Test-1"
async_run(controller.add_project(project_id=str(uuid.uuid4()), name="Test-1"))
assert controller.get_free_project_name("Test") == "Test-2"
assert controller.get_free_project_name("Hello") == "Hello"

View File

@ -0,0 +1,69 @@
#!/usr/bin/env python
#
# Copyright (C) 2016 GNS3 Technologies Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
import uuid
import json
import zipfile
from gns3server.controller.project import Project
from gns3server.controller.import_project import import_project
def test_import_project(async_run, tmpdir, controller):
project_id = str(uuid.uuid4())
topology = {
"project_id": str(uuid.uuid4()),
"name": "test",
"topology": {
},
"version": "2.0.0"
}
with open(str(tmpdir / "project.gns3"), 'w+') as f:
json.dump(topology, f)
with open(str(tmpdir / "b.png"), 'w+') as f:
f.write("B")
zip_path = str(tmpdir / "project.zip")
with zipfile.ZipFile(zip_path, 'w') as myzip:
myzip.write(str(tmpdir / "project.gns3"), "project.gns3")
myzip.write(str(tmpdir / "b.png"), "b.png")
myzip.write(str(tmpdir / "b.png"), "project-files/dynamips/test")
myzip.write(str(tmpdir / "b.png"), "project-files/qemu/test")
with open(zip_path, "rb") as f:
project = async_run(import_project(controller, project_id, f))
assert project.name == "test"
assert project.id == project_id # The project should changed
assert os.path.exists(os.path.join(project.path, "b.png"))
assert not os.path.exists(os.path.join(project.path, "project.gns3"))
assert os.path.exists(os.path.join(project.path, "test.gns3"))
assert os.path.exists(os.path.join(project.path, "project-files/dynamips/test"))
assert os.path.exists(os.path.join(project.path, "project-files/qemu/test"))
# A new project name is generated when you import twice the same name
with open(zip_path, "rb") as f:
project = async_run(import_project(controller, str(uuid.uuid4()), f))
assert project.name != "test"

View File

@ -221,12 +221,12 @@ def test_export(http_compute, tmpdir, loop, project):
assert content == b"hello" assert content == b"hello"
def test_import(http_compute, tmpdir, loop, project): def test_import(http_compute, tmpdir, loop):
with zipfile.ZipFile(str(tmpdir / "test.zip"), 'w') as myzip: with zipfile.ZipFile(str(tmpdir / "test.zip"), 'w') as myzip:
myzip.writestr("demo", b"hello") myzip.writestr("demo", b"hello")
project_id = project.id project_id = str(uuid.uuid4())
with open(str(tmpdir / "test.zip"), "rb") as f: with open(str(tmpdir / "test.zip"), "rb") as f:
response = http_compute.post("/projects/{project_id}/import".format(project_id=project_id), body=f.read(), raw=True) response = http_compute.post("/projects/{project_id}/import".format(project_id=project_id), body=f.read(), raw=True)

View File

@ -202,3 +202,21 @@ def test_write_file(http_controller, tmpdir, project):
response = http_controller.post("/projects/{project_id}/files/../hello".format(project_id=project.id), raw=True) response = http_controller.post("/projects/{project_id}/files/../hello".format(project_id=project.id), raw=True)
assert response.status == 403 assert response.status == 403
def test_import(http_controller, tmpdir, controller):
with zipfile.ZipFile(str(tmpdir / "test.zip"), 'w') as myzip:
myzip.writestr("project.gns3", b'{"version": "2.0.0", "name": "test"}')
myzip.writestr("demo", b"hello")
project_id = str(uuid.uuid4())
with open(str(tmpdir / "test.zip"), "rb") as f:
response = http_controller.post("/projects/{project_id}/import".format(project_id=project_id), body=f.read(), raw=True)
assert response.status == 201
project = controller.get_project(project_id)
with open(os.path.join(project.path, "demo")) as f:
content = f.read()
assert content == "hello"