mirror of
https://github.com/GNS3/gns3-server.git
synced 2026-08-27 12:30:13 +03:00
Last adjustments for import/export
This commit is contained in:
parent
6f7730c455
commit
2670c68467
@ -32,7 +32,16 @@ log = logging.getLogger(__name__)
|
||||
CHUNK_SIZE = 1024 * 8 # 8KB
|
||||
|
||||
|
||||
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):
|
||||
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.
|
||||
|
||||
@ -46,7 +55,7 @@ async def export_project(zstream, project, temporary_dir, include_images=False,
|
||||
:param include_snapshots: save snapshots to the zip file
|
||||
: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 included in the zip even if not portable
|
||||
:param reset_mac_addresses: Reset MAC addresses for each node.
|
||||
: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
|
||||
@ -62,7 +71,16 @@ 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
|
||||
for file in os.listdir(project._path):
|
||||
if file.endswith(".gns3"):
|
||||
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)
|
||||
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
|
||||
for root, dirs, files in os.walk(project._path, topdown=True, followlinks=False):
|
||||
@ -171,7 +189,16 @@ def _is_exportable(path, include_snapshots=False):
|
||||
return True
|
||||
|
||||
|
||||
async def _patch_project_file(project, path, zstream, include_images, keep_compute_ids, 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.
|
||||
The .gns3 file is renamed to project.gns3
|
||||
@ -244,7 +271,7 @@ async def _patch_project_file(project, path, zstream, include_images, keep_compu
|
||||
for compute_id, image_type, image in remote_images:
|
||||
await _export_remote_images(project, compute_id, image_type, image, zstream, temporary_dir)
|
||||
|
||||
zstream.writestr("project.gns3", json.dumps(topology).encode())
|
||||
zstream.writestr("project.gns3", json.dumps(topology, indent=4, sort_keys=True).encode())
|
||||
return images
|
||||
|
||||
|
||||
|
||||
@ -45,12 +45,11 @@ async def import_project(
|
||||
stream,
|
||||
location=None,
|
||||
name=None,
|
||||
reset_mac_addresses=False,
|
||||
keep_compute_ids=False,
|
||||
restoring_snapshot=False,
|
||||
project_name=None,
|
||||
auto_start=False,
|
||||
auto_open=False,
|
||||
auto_close=True
|
||||
auto_close=True,
|
||||
):
|
||||
"""
|
||||
Import a project contain in a zip file
|
||||
@ -62,8 +61,8 @@ async def import_project(
|
||||
:param stream: A io.BytesIO of the zipfile
|
||||
: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 reset_mac_addresses: Reset MAC addresses for each node
|
||||
:param keep_compute_ids: keep compute IDs unchanged
|
||||
:param restoring_snapshot: True if the project is imported as part of a snapshot restore, False otherwise
|
||||
:param project_name: Original project name when restoring a snapshot
|
||||
|
||||
:returns: Project
|
||||
@ -82,12 +81,18 @@ async def import_project(
|
||||
|
||||
try:
|
||||
topology = json.loads(project_file)
|
||||
if not project_name:
|
||||
# We import the project on top of an existing project (snapshots)
|
||||
if topology["project_id"] == project_id:
|
||||
project_name = topology["name"]
|
||||
log.info("Restoring snapshot for project '{}', snapshot name: '{}'".format(project_id, project_name))
|
||||
restoring_snapshot = True
|
||||
else:
|
||||
# If the project name is already used we generate a new one
|
||||
if name:
|
||||
project_name = controller.get_free_project_name(name)
|
||||
else:
|
||||
project_name = controller.get_free_project_name(topology["name"])
|
||||
restoring_snapshot = False
|
||||
except (ValueError, KeyError):
|
||||
raise aiohttp.web.HTTPConflict(text="Cannot import project, the project.gns3 file is corrupted")
|
||||
|
||||
@ -116,8 +121,8 @@ async def import_project(
|
||||
topology["auto_close"] = auto_close
|
||||
|
||||
if not restoring_snapshot:
|
||||
# Do not re-generate IDs if we are restoring a snapshot because they should be the same as the main project
|
||||
regenerate_ids(topology, path, reset_mac_addresses=True)
|
||||
# Do not re-generate IDs if we are restoring a snapshot because they should be the same in a project
|
||||
regenerate_topology_ids(topology, path, reset_mac_addresses=reset_mac_addresses)
|
||||
|
||||
# Modify the compute id of the node depending on compute capacity
|
||||
if not keep_compute_ids:
|
||||
@ -129,14 +134,6 @@ async def import_project(
|
||||
node["compute_id"] = "vm"
|
||||
else:
|
||||
# Round-robin through available compute resources.
|
||||
# computes = []
|
||||
# for compute_id in controller.computes:
|
||||
# compute = controller.get_compute(compute_id)
|
||||
# # only use the local compute or any connected compute
|
||||
# if compute_id == "local" or compute.connected:
|
||||
# computes.append(compute_id)
|
||||
# else:
|
||||
# log.warning(compute.name, "is not connected!")
|
||||
compute_nodes = itertools.cycle(controller.computes)
|
||||
for node in topology["topology"]["nodes"]:
|
||||
node["compute_id"] = next(compute_nodes)
|
||||
@ -165,7 +162,7 @@ async def import_project(
|
||||
|
||||
snapshots_path = os.path.join(path, "snapshots")
|
||||
if not restoring_snapshot and os.path.exists(snapshots_path):
|
||||
await update_snapshots(snapshots_path, path, project_name, project_id)
|
||||
await update_snapshots(snapshots_path, path, project_name, project_id, reset_mac_addresses=reset_mac_addresses)
|
||||
|
||||
project = await controller.load_project(dot_gns3_path, load=False)
|
||||
return project
|
||||
@ -189,9 +186,10 @@ def _create_symbolic_links(zip_file, path):
|
||||
except OSError as e:
|
||||
raise aiohttp.web.HTTPConflict(text=f"Cannot create symbolic link: {e}")
|
||||
|
||||
def regenerate_ids(topology, new_project_path, reset_mac_addresses=False):
|
||||
def regenerate_topology_ids(topology, new_project_path, reset_mac_addresses=False):
|
||||
"""
|
||||
Regenerate IDs in the topology and move the files of the nodes to match the new IDs
|
||||
Regenerate IDs in the topology and move the files of the nodes to match the new IDs.
|
||||
This is necessary because IDs must be unique across projects.
|
||||
|
||||
:param topology: topology content
|
||||
:param new_project_path: new project path
|
||||
@ -289,7 +287,7 @@ async def _import_images(controller, images_path):
|
||||
await wait_run_in_executor(shutil.move, path, dst)
|
||||
|
||||
|
||||
async def update_snapshots(snapshots_dir, project_path, project_name, project_id):
|
||||
async def update_snapshots(snapshots_dir, project_path, project_name, project_id, reset_mac_addresses=True):
|
||||
"""
|
||||
Load the snapshots and update their project name and project ID to be the same as the main project.
|
||||
Regenerate all the node, link and drawing IDs
|
||||
@ -319,7 +317,7 @@ async def update_snapshots(snapshots_dir, project_path, project_name, project_id
|
||||
topology = json.load(f)
|
||||
topology["name"] = project_name
|
||||
topology["project_id"] = project_id
|
||||
regenerate_ids(topology, project_path, reset_mac_addresses=False)
|
||||
regenerate_topology_ids(topology, project_path, reset_mac_addresses)
|
||||
with open(topology_file_path, "w+", encoding="utf-8") as f:
|
||||
json.dump(topology, f, indent=4, sort_keys=True)
|
||||
except OSError as e:
|
||||
|
||||
@ -382,6 +382,7 @@ class Node:
|
||||
await self.parse_node_response(response.json)
|
||||
return True
|
||||
trial += 1
|
||||
return False
|
||||
|
||||
async def update(self, **kwargs):
|
||||
"""
|
||||
|
||||
@ -45,7 +45,7 @@ from ..utils.asyncio import locking
|
||||
from ..utils.asyncio import aiozipstream
|
||||
from ..utils.asyncio import wait_run_in_executor
|
||||
from .export_project import export_project
|
||||
from .import_project import import_project, update_snapshots, regenerate_ids
|
||||
from .import_project import import_project, update_snapshots, regenerate_topology_ids
|
||||
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
@ -819,7 +819,7 @@ class Project:
|
||||
self._snapshot_conf.append(snapshot.__json__())
|
||||
try:
|
||||
with open(self._snapshot_conf_path, 'w+') as f:
|
||||
json.dump(self._snapshot_conf, f, indent=4, sort_keys=True)
|
||||
json.dump(self._snapshot_conf, f, indent=4)
|
||||
except OSError as e:
|
||||
log.error("Cannot write snapshot config '{}': {}".format(self._snapshot_conf_path, e))
|
||||
|
||||
@ -1126,7 +1126,14 @@ class Project:
|
||||
with tempfile.TemporaryDirectory(dir=working_dir) as tmpdir:
|
||||
# Do not compress the exported project when duplicating
|
||||
with aiozipstream.ZipFile(compression=zipfile.ZIP_STORED) as zstream:
|
||||
await export_project(zstream, self, tmpdir, keep_compute_ids=True, allow_all_nodes=True, reset_mac_addresses=reset_mac_addresses)
|
||||
await export_project(
|
||||
zstream,
|
||||
self,
|
||||
tmpdir,
|
||||
keep_compute_ids=True,
|
||||
include_snapshots=True,
|
||||
allow_all_nodes=True
|
||||
)
|
||||
|
||||
# export the project to a temporary location
|
||||
project_path = os.path.join(tmpdir, "project.gns3p")
|
||||
@ -1136,9 +1143,17 @@ class Project:
|
||||
await f.write(chunk)
|
||||
|
||||
new_project_id = str(uuid.uuid4())
|
||||
# import the temporary project
|
||||
# import the duplicated project
|
||||
with open(project_path, "rb") as f:
|
||||
project = await import_project(self._controller, new_project_id, f, location=location, name=name, keep_compute_ids=True)
|
||||
project = await import_project(
|
||||
self._controller,
|
||||
new_project_id,
|
||||
f,
|
||||
location=location,
|
||||
name=name,
|
||||
reset_mac_addresses=reset_mac_addresses,
|
||||
keep_compute_ids=True
|
||||
)
|
||||
|
||||
log.info("Project '{}': duplicated in {:.4f} seconds".format(project.name, time.time() - begin))
|
||||
except (ValueError, OSError, UnicodeEncodeError) as e:
|
||||
@ -1157,7 +1172,7 @@ class Project:
|
||||
|
||||
:param name: Name of the new project. A new one will be generated in case of conflicts
|
||||
:param location: Parent directory of the new project
|
||||
:param reset_mac_addresses: Reset MAC addresses for the new project
|
||||
:param reset_mac_addresses: Reset MAC addresses for the duplicated project
|
||||
"""
|
||||
|
||||
# remote replication is not supported with remote computes
|
||||
@ -1187,7 +1202,7 @@ class Project:
|
||||
topology["auto_close"] = False
|
||||
|
||||
# regenerate IDs for the duplicated project
|
||||
regenerate_ids(topology, new_project_path, reset_mac_addresses)
|
||||
regenerate_topology_ids(topology, new_project_path, reset_mac_addresses)
|
||||
|
||||
# dump the updated .gns3 project file
|
||||
dot_gns3_path = new_project_path.joinpath('{}.gns3'.format(project_name))
|
||||
@ -1303,11 +1318,13 @@ class Project:
|
||||
data['z'] = z
|
||||
data['locked'] = False # duplicated node must not be locked
|
||||
new_node_uuid = str(uuid.uuid4())
|
||||
new_node = await self.add_node(node.compute,
|
||||
node.name,
|
||||
new_node_uuid,
|
||||
node_type=node_type,
|
||||
**data)
|
||||
new_node = await self.add_node(
|
||||
node.compute,
|
||||
node.name,
|
||||
new_node_uuid,
|
||||
node_type=node_type,
|
||||
**data
|
||||
)
|
||||
try:
|
||||
await node.post("/duplicate", timeout=None, data={
|
||||
"destination_node_id": new_node_uuid
|
||||
|
||||
@ -135,6 +135,7 @@ class Snapshot:
|
||||
await self._project.close(ignore_notification=True)
|
||||
|
||||
try:
|
||||
begin = time.time()
|
||||
# delete the current project files
|
||||
project_files_path = os.path.join(self._project.path, "project-files")
|
||||
if os.path.exists(project_files_path):
|
||||
@ -145,12 +146,11 @@ class Snapshot:
|
||||
self._project.id,
|
||||
f,
|
||||
location=self._project.path,
|
||||
project_name=self._project.name,
|
||||
restoring_snapshot=True,
|
||||
auto_start=self._project.auto_start,
|
||||
auto_open=self._project.auto_open,
|
||||
auto_close=self._project.auto_close
|
||||
)
|
||||
log.info("Snapshot '{}' restored in {:.4f} seconds".format(self.name, time.time() - begin))
|
||||
except (OSError, PermissionError) as e:
|
||||
raise aiohttp.web.HTTPConflict(text=str(e))
|
||||
await project.open()
|
||||
@ -161,8 +161,8 @@ class Snapshot:
|
||||
return {
|
||||
"snapshot_id": self._id,
|
||||
"name": self._name,
|
||||
"description": self._description,
|
||||
"created_at": self._created_at,
|
||||
"description": self._description,
|
||||
"filename": self._filename,
|
||||
"project_id": self._project.id
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user