mirror of
https://github.com/GNS3/gns3-server
synced 2024-12-30 18:50:58 +00:00
Merge pull request #2393 from GNS3/feature/keep-compute-ids
Option to keep the compute IDs unchanged when exporting a project
This commit is contained in:
commit
b48bd92da3
@ -32,7 +32,7 @@ log = logging.getLogger(__name__)
|
|||||||
CHUNK_SIZE = 1024 * 8 # 8KB
|
CHUNK_SIZE = 1024 * 8 # 8KB
|
||||||
|
|
||||||
|
|
||||||
async def export_project(zstream, project, temporary_dir, include_images=False, include_snapshots=False, keep_compute_id=False, allow_all_nodes=False, reset_mac_addresses=False):
|
async def export_project(zstream, project, temporary_dir, include_images=False, include_snapshots=False, keep_compute_ids=False, allow_all_nodes=False, reset_mac_addresses=False):
|
||||||
"""
|
"""
|
||||||
Export a project to a zip file.
|
Export a project to a zip file.
|
||||||
|
|
||||||
@ -44,9 +44,9 @@ async def export_project(zstream, project, temporary_dir, include_images=False,
|
|||||||
:param temporary_dir: A temporary dir where to store intermediate data
|
:param temporary_dir: A temporary dir where to store intermediate data
|
||||||
:param include_images: save OS images to the zip file
|
:param include_images: save OS images to the zip file
|
||||||
:param include_snapshots: save snapshots to the zip file
|
:param include_snapshots: save snapshots to the zip file
|
||||||
:param keep_compute_id: If false replace all compute id by local (standard behavior for .gns3project to make it portable)
|
:param keep_compute_ids: If false replace all compute IDs y local (standard behavior for .gns3project to make it portable)
|
||||||
:param allow_all_nodes: Allow all nodes type to be include in the zip even if not portable
|
:param allow_all_nodes: Allow all nodes type to be included in the zip even if not portable
|
||||||
:param reset_mac_addresses: Reset MAC addresses for every nodes.
|
:param reset_mac_addresses: Reset MAC addresses for each node.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# To avoid issue with data not saved we disallow the export of a running project
|
# To avoid issue with data not saved we disallow the export of a running project
|
||||||
@ -62,7 +62,7 @@ async def export_project(zstream, project, temporary_dir, include_images=False,
|
|||||||
# First we process the .gns3 in order to be sure we don't have an error
|
# First we process the .gns3 in order to be sure we don't have an error
|
||||||
for file in os.listdir(project._path):
|
for file in os.listdir(project._path):
|
||||||
if file.endswith(".gns3"):
|
if file.endswith(".gns3"):
|
||||||
await _patch_project_file(project, os.path.join(project._path, file), zstream, include_images, keep_compute_id, allow_all_nodes, temporary_dir, reset_mac_addresses)
|
await _patch_project_file(project, os.path.join(project._path, file), zstream, include_images, keep_compute_ids, allow_all_nodes, temporary_dir, reset_mac_addresses)
|
||||||
|
|
||||||
# Export the local files
|
# Export the local files
|
||||||
for root, dirs, files in os.walk(project._path, topdown=True, followlinks=False):
|
for root, dirs, files in os.walk(project._path, topdown=True, followlinks=False):
|
||||||
@ -170,7 +170,7 @@ def _is_exportable(path, include_snapshots=False):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
async def _patch_project_file(project, path, zstream, include_images, keep_compute_id, allow_all_nodes, temporary_dir, reset_mac_addresses):
|
async def _patch_project_file(project, path, zstream, include_images, keep_compute_ids, allow_all_nodes, temporary_dir, reset_mac_addresses):
|
||||||
"""
|
"""
|
||||||
Patch a project file (.gns3) to export a project.
|
Patch a project file (.gns3) to export a project.
|
||||||
The .gns3 file is renamed to project.gns3
|
The .gns3 file is renamed to project.gns3
|
||||||
@ -197,7 +197,7 @@ async def _patch_project_file(project, path, zstream, include_images, keep_compu
|
|||||||
if not allow_all_nodes and node["node_type"] in ["virtualbox", "vmware"]:
|
if not allow_all_nodes and node["node_type"] in ["virtualbox", "vmware"]:
|
||||||
raise aiohttp.web.HTTPConflict(text="Projects with a {} node cannot be exported".format(node["node_type"]))
|
raise aiohttp.web.HTTPConflict(text="Projects with a {} node cannot be exported".format(node["node_type"]))
|
||||||
|
|
||||||
if not keep_compute_id:
|
if not keep_compute_ids:
|
||||||
node["compute_id"] = "local" # To make project portable all node by default run on local
|
node["compute_id"] = "local" # To make project portable all node by default run on local
|
||||||
|
|
||||||
if "properties" in node and node["node_type"] != "docker":
|
if "properties" in node and node["node_type"] != "docker":
|
||||||
@ -215,7 +215,7 @@ async def _patch_project_file(project, path, zstream, include_images, keep_compu
|
|||||||
if value is None or value.strip() == '':
|
if value is None or value.strip() == '':
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if not keep_compute_id: # If we keep the original compute we can keep the image path
|
if not keep_compute_ids: # If we keep the original compute we can keep the image path
|
||||||
node["properties"][prop] = os.path.basename(value)
|
node["properties"][prop] = os.path.basename(value)
|
||||||
|
|
||||||
if include_images is True:
|
if include_images is True:
|
||||||
@ -225,7 +225,7 @@ async def _patch_project_file(project, path, zstream, include_images, keep_compu
|
|||||||
'image_type': node['node_type']
|
'image_type': node['node_type']
|
||||||
})
|
})
|
||||||
|
|
||||||
if not keep_compute_id:
|
if not keep_compute_ids:
|
||||||
topology["topology"]["computes"] = [] # Strip compute information because could contain secret info like password
|
topology["topology"]["computes"] = [] # Strip compute information because could contain secret info like password
|
||||||
|
|
||||||
local_images = set([i['image'] for i in images if i['compute_id'] == 'local'])
|
local_images = set([i['image'] for i in images if i['compute_id'] == 'local'])
|
||||||
|
@ -38,7 +38,7 @@ Handle the import of project from a .gns3project
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
async def import_project(controller, project_id, stream, location=None, name=None, keep_compute_id=False,
|
async def import_project(controller, project_id, stream, location=None, name=None, keep_compute_ids=False,
|
||||||
auto_start=False, auto_open=False, auto_close=True):
|
auto_start=False, auto_open=False, auto_close=True):
|
||||||
"""
|
"""
|
||||||
Import a project contain in a zip file
|
Import a project contain in a zip file
|
||||||
@ -50,7 +50,7 @@ async def import_project(controller, project_id, stream, location=None, name=Non
|
|||||||
:param stream: A io.BytesIO of the zipfile
|
:param stream: A io.BytesIO of the zipfile
|
||||||
:param location: Directory for the project if None put in the default directory
|
:param location: Directory for the project if None put in the default directory
|
||||||
:param name: Wanted project name, generate one from the .gns3 if None
|
:param name: Wanted project name, generate one from the .gns3 if None
|
||||||
:param keep_compute_id: If true do not touch the compute id
|
:param keep_compute_ids: keep compute IDs unchanged
|
||||||
|
|
||||||
:returns: Project
|
:returns: Project
|
||||||
"""
|
"""
|
||||||
@ -124,7 +124,7 @@ async def import_project(controller, project_id, stream, location=None, name=Non
|
|||||||
drawing["drawing_id"] = str(uuid.uuid4())
|
drawing["drawing_id"] = str(uuid.uuid4())
|
||||||
|
|
||||||
# Modify the compute id of the node depending of compute capacity
|
# Modify the compute id of the node depending of compute capacity
|
||||||
if not keep_compute_id:
|
if not keep_compute_ids:
|
||||||
# For some VM type we move them to the GNS3 VM if possible
|
# For some VM type we move them to the GNS3 VM if possible
|
||||||
# unless it's a linux host without GNS3 VM
|
# unless it's a linux host without GNS3 VM
|
||||||
if not sys.platform.startswith("linux") or controller.has_compute("vm"):
|
if not sys.platform.startswith("linux") or controller.has_compute("vm"):
|
||||||
|
@ -1066,7 +1066,7 @@ class Project:
|
|||||||
with tempfile.TemporaryDirectory(dir=working_dir) as tmpdir:
|
with tempfile.TemporaryDirectory(dir=working_dir) as tmpdir:
|
||||||
# Do not compress the exported project when duplicating
|
# Do not compress the exported project when duplicating
|
||||||
with aiozipstream.ZipFile(compression=zipfile.ZIP_STORED) as zstream:
|
with aiozipstream.ZipFile(compression=zipfile.ZIP_STORED) as zstream:
|
||||||
await export_project(zstream, self, tmpdir, keep_compute_id=True, allow_all_nodes=True, reset_mac_addresses=reset_mac_addresses)
|
await export_project(zstream, self, tmpdir, keep_compute_ids=True, allow_all_nodes=True, reset_mac_addresses=reset_mac_addresses)
|
||||||
|
|
||||||
# export the project to a temporary location
|
# export the project to a temporary location
|
||||||
project_path = os.path.join(tmpdir, "project.gns3p")
|
project_path = os.path.join(tmpdir, "project.gns3p")
|
||||||
@ -1077,7 +1077,7 @@ class Project:
|
|||||||
|
|
||||||
# import the temporary project
|
# import the temporary project
|
||||||
with open(project_path, "rb") as f:
|
with open(project_path, "rb") as f:
|
||||||
project = await import_project(self._controller, str(uuid.uuid4()), f, location=location, name=name, keep_compute_id=True)
|
project = await import_project(self._controller, str(uuid.uuid4()), f, location=location, name=name, keep_compute_ids=True)
|
||||||
|
|
||||||
log.info("Project '{}' duplicated in {:.4f} seconds".format(project.name, time.time() - begin))
|
log.info("Project '{}' duplicated in {:.4f} seconds".format(project.name, time.time() - begin))
|
||||||
except (ValueError, OSError, UnicodeEncodeError) as e:
|
except (ValueError, OSError, UnicodeEncodeError) as e:
|
||||||
|
@ -96,7 +96,7 @@ class Snapshot:
|
|||||||
with tempfile.TemporaryDirectory(dir=snapshot_directory) as tmpdir:
|
with tempfile.TemporaryDirectory(dir=snapshot_directory) as tmpdir:
|
||||||
# Do not compress the snapshots
|
# Do not compress the snapshots
|
||||||
with aiozipstream.ZipFile(compression=zipfile.ZIP_STORED) as zstream:
|
with aiozipstream.ZipFile(compression=zipfile.ZIP_STORED) as zstream:
|
||||||
await export_project(zstream, self._project, tmpdir, keep_compute_id=True, allow_all_nodes=True)
|
await export_project(zstream, self._project, tmpdir, keep_compute_ids=True, allow_all_nodes=True)
|
||||||
async with aiofiles.open(self.path, 'wb') as f:
|
async with aiofiles.open(self.path, 'wb') as f:
|
||||||
async for chunk in zstream:
|
async for chunk in zstream:
|
||||||
await f.write(chunk)
|
await f.write(chunk)
|
||||||
|
@ -319,6 +319,10 @@ class ProjectHandler:
|
|||||||
reset_mac_addresses = True
|
reset_mac_addresses = True
|
||||||
else:
|
else:
|
||||||
reset_mac_addresses = False
|
reset_mac_addresses = False
|
||||||
|
if request.query.get("keep_compute_ids", "no").lower() == "yes":
|
||||||
|
keep_compute_ids = True
|
||||||
|
else:
|
||||||
|
keep_compute_ids = False
|
||||||
|
|
||||||
compression_query = request.query.get("compression", "zip").lower()
|
compression_query = request.query.get("compression", "zip").lower()
|
||||||
if compression_query == "zip":
|
if compression_query == "zip":
|
||||||
@ -336,9 +340,17 @@ class ProjectHandler:
|
|||||||
working_dir = os.path.abspath(os.path.join(project.path, os.pardir))
|
working_dir = os.path.abspath(os.path.join(project.path, os.pardir))
|
||||||
with tempfile.TemporaryDirectory(dir=working_dir) as tmpdir:
|
with tempfile.TemporaryDirectory(dir=working_dir) as tmpdir:
|
||||||
with aiozipstream.ZipFile(compression=compression) as zstream:
|
with aiozipstream.ZipFile(compression=compression) as zstream:
|
||||||
await export_project(zstream, project, tmpdir, include_snapshots=include_snapshots, include_images=include_images, reset_mac_addresses=reset_mac_addresses)
|
await export_project(
|
||||||
|
zstream,
|
||||||
|
project,
|
||||||
|
tmpdir,
|
||||||
|
include_snapshots=include_snapshots,
|
||||||
|
include_images=include_images,
|
||||||
|
reset_mac_addresses=reset_mac_addresses,
|
||||||
|
keep_compute_ids=keep_compute_ids
|
||||||
|
)
|
||||||
|
|
||||||
# We need to do that now because export could failed and raise an HTTP error
|
# We need to do that now because export could fail and raise an HTTP error
|
||||||
# that why response start need to be the later possible
|
# that why response start need to be the later possible
|
||||||
response.content_type = 'application/gns3project'
|
response.content_type = 'application/gns3project'
|
||||||
response.headers['CONTENT-DISPOSITION'] = 'attachment; filename="{}.gns3project"'.format(project.name)
|
response.headers['CONTENT-DISPOSITION'] = 'attachment; filename="{}.gns3project"'.format(project.name)
|
||||||
@ -350,7 +362,7 @@ class ProjectHandler:
|
|||||||
|
|
||||||
log.info("Project '{}' exported in {:.4f} seconds".format(project.name, time.time() - begin))
|
log.info("Project '{}' exported in {:.4f} seconds".format(project.name, time.time() - begin))
|
||||||
|
|
||||||
# Will be raise if you have no space left or permission issue on your temporary directory
|
# Will be raised if you have no space left or permission issue on your temporary directory
|
||||||
# RuntimeError: something was wrong during the zip process
|
# RuntimeError: something was wrong during the zip process
|
||||||
except (ValueError, OSError, RuntimeError) as e:
|
except (ValueError, OSError, RuntimeError) as e:
|
||||||
raise aiohttp.web.HTTPNotFound(text="Cannot export project: {}".format(str(e)))
|
raise aiohttp.web.HTTPNotFound(text="Cannot export project: {}".format(str(e)))
|
||||||
|
@ -325,7 +325,7 @@ async def test_export_with_images(tmpdir, project):
|
|||||||
myzip.getinfo("images/IOS/test.image")
|
myzip.getinfo("images/IOS/test.image")
|
||||||
|
|
||||||
|
|
||||||
async def test_export_keep_compute_id(tmpdir, project):
|
async def test_export_keep_compute_ids(tmpdir, project):
|
||||||
"""
|
"""
|
||||||
If we want to restore the same computes we could ask to keep them
|
If we want to restore the same computes we could ask to keep them
|
||||||
in the file
|
in the file
|
||||||
@ -354,7 +354,7 @@ async def test_export_keep_compute_id(tmpdir, project):
|
|||||||
json.dump(data, f)
|
json.dump(data, f)
|
||||||
|
|
||||||
with aiozipstream.ZipFile() as z:
|
with aiozipstream.ZipFile() as z:
|
||||||
await export_project(z, project, str(tmpdir), keep_compute_id=True)
|
await export_project(z, project, str(tmpdir), keep_compute_ids=True)
|
||||||
await write_file(str(tmpdir / 'zipfile.zip'), z)
|
await write_file(str(tmpdir / 'zipfile.zip'), z)
|
||||||
|
|
||||||
with zipfile.ZipFile(str(tmpdir / 'zipfile.zip')) as myzip:
|
with zipfile.ZipFile(str(tmpdir / 'zipfile.zip')) as myzip:
|
||||||
@ -458,7 +458,7 @@ async def test_export_with_ignoring_snapshots(tmpdir, project):
|
|||||||
Path(os.path.join(snapshots_dir, 'snap.gns3project')).touch()
|
Path(os.path.join(snapshots_dir, 'snap.gns3project')).touch()
|
||||||
|
|
||||||
with aiozipstream.ZipFile() as z:
|
with aiozipstream.ZipFile() as z:
|
||||||
await export_project(z, project, str(tmpdir), keep_compute_id=True)
|
await export_project(z, project, str(tmpdir), keep_compute_ids=True)
|
||||||
await write_file(str(tmpdir / 'zipfile.zip'), z)
|
await write_file(str(tmpdir / 'zipfile.zip'), z)
|
||||||
|
|
||||||
with zipfile.ZipFile(str(tmpdir / 'zipfile.zip')) as myzip:
|
with zipfile.ZipFile(str(tmpdir / 'zipfile.zip')) as myzip:
|
||||||
|
@ -449,7 +449,7 @@ async def test_import_node_id(linux_platform, tmpdir, controller):
|
|||||||
assert os.path.exists(os.path.join(project.path, "project-files", "iou", topo["topology"]["nodes"][0]["node_id"], "startup.cfg"))
|
assert os.path.exists(os.path.join(project.path, "project-files", "iou", topo["topology"]["nodes"][0]["node_id"], "startup.cfg"))
|
||||||
|
|
||||||
|
|
||||||
async def test_import_keep_compute_id(windows_platform, tmpdir, controller):
|
async def test_import_keep_compute_ids(windows_platform, tmpdir, controller):
|
||||||
"""
|
"""
|
||||||
On linux host IOU should be moved to the GNS3 VM
|
On linux host IOU should be moved to the GNS3 VM
|
||||||
"""
|
"""
|
||||||
@ -487,7 +487,7 @@ async def test_import_keep_compute_id(windows_platform, tmpdir, controller):
|
|||||||
myzip.write(str(tmpdir / "project.gns3"), "project.gns3")
|
myzip.write(str(tmpdir / "project.gns3"), "project.gns3")
|
||||||
|
|
||||||
with open(zip_path, "rb") as f:
|
with open(zip_path, "rb") as f:
|
||||||
project = await import_project(controller, project_id, f, keep_compute_id=True)
|
project = await import_project(controller, project_id, f, keep_compute_ids=True)
|
||||||
|
|
||||||
with open(os.path.join(project.path, "test.gns3")) as f:
|
with open(os.path.join(project.path, "test.gns3")) as f:
|
||||||
topo = json.load(f)
|
topo = json.load(f)
|
||||||
|
Loading…
Reference in New Issue
Block a user