mirror of
https://github.com/GNS3/gns3-server.git
synced 2026-11-03 14:01:15 +02:00
Update snapshots when duplicating a project
This commit is contained in:
4 files changed
+115
-113
No files matched your search
@@ -15,7 +15,6 @@
|
||||
# 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 sys
|
||||
import re
|
||||
import os
|
||||
import json
|
||||
@@ -46,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, _move_node_file
|
||||
from .import_project import import_project, update_snapshots, regenerate_ids
|
||||
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
@@ -794,18 +793,15 @@ class Project:
|
||||
continue
|
||||
self._snapshots[snapshot.id] = snapshot
|
||||
else:
|
||||
# Create the snapshot instances from the snapshot config file
|
||||
# Create the Snapshot instances from the snapshot config file
|
||||
for snapshot_entry in self._snapshot_conf:
|
||||
try:
|
||||
name = snapshot_entry["name"]
|
||||
snapshot_id = snapshot_entry["snapshot_id"]
|
||||
created_at = snapshot_entry["created_at"]
|
||||
filename = snapshot_entry["filename"]
|
||||
path = os.path.join(snapshot_dir, filename)
|
||||
path = os.path.join(snapshot_dir, snapshot_entry["filename"])
|
||||
if not os.path.isfile(path):
|
||||
log.warning("Snapshot file '{}' does not exist".format(path))
|
||||
continue
|
||||
snapshot = Snapshot(self, name=name, filename=filename, snapshot_id=snapshot_id, created_at=created_at)
|
||||
snapshot_entry.pop("project_id")
|
||||
snapshot = Snapshot(self, **snapshot_entry)
|
||||
self._snapshots[snapshot.id] = snapshot
|
||||
except KeyError:
|
||||
log.error("Invalid entry in snapshot config file: {}".format(snapshot_entry))
|
||||
@@ -823,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)
|
||||
json.dump(self._snapshot_conf, f, indent=4, sort_keys=True)
|
||||
except OSError as e:
|
||||
log.error("Cannot write snapshot config '{}': {}".format(self._snapshot_conf_path, e))
|
||||
|
||||
@@ -938,7 +934,7 @@ class Project:
|
||||
def _get_default_project_directory(cls):
|
||||
"""
|
||||
Return the default location for the project directory
|
||||
depending of the operating system
|
||||
depending on the operating system
|
||||
"""
|
||||
|
||||
server_config = Config.instance().get_section_config("Server")
|
||||
@@ -1139,11 +1135,12 @@ class Project:
|
||||
async for chunk in zstream:
|
||||
await f.write(chunk)
|
||||
|
||||
new_project_id = str(uuid.uuid4())
|
||||
# import the temporary project
|
||||
with open(project_path, "rb") as f:
|
||||
project = await import_project(self._controller, str(uuid.uuid4()), f, location=location, name=name, keep_compute_ids=True)
|
||||
project = await import_project(self._controller, new_project_id, 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:
|
||||
raise aiohttp.web.HTTPConflict(text="Cannot duplicate project: {}".format(str(e)))
|
||||
|
||||
@@ -1152,6 +1149,62 @@ class Project:
|
||||
|
||||
return project
|
||||
|
||||
async def _fast_duplication(self, name=None, location=None, reset_mac_addresses=True):
|
||||
"""
|
||||
Fast duplication of a project.
|
||||
|
||||
Copy the project files directly rather than in an import-export fashion.
|
||||
|
||||
: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
|
||||
"""
|
||||
|
||||
# remote replication is not supported with remote computes
|
||||
for compute in self.computes:
|
||||
if compute.id != "local":
|
||||
log.warning("Fast duplication is not supported with remote compute: '{}'".format(compute.id))
|
||||
return None
|
||||
# work dir
|
||||
p_work = pathlib.Path(location or self.path).parent.absolute()
|
||||
t0 = time.time()
|
||||
new_project_id = str(uuid.uuid4())
|
||||
if location:
|
||||
new_project_path = p_work.joinpath(location)
|
||||
else:
|
||||
new_project_path = p_work.joinpath(new_project_id)
|
||||
# copy dir
|
||||
await wait_run_in_executor(shutil.copytree, self.path, new_project_path.as_posix(), symlinks=True, ignore_dangling_symlinks=True)
|
||||
log.info("Project content copied from '{}' to '{}' in {}s".format(self.path, new_project_path, time.time() - t0))
|
||||
topology = json.loads(new_project_path.joinpath('{}.gns3'.format(self.name)).read_bytes())
|
||||
project_name = name or topology["name"]
|
||||
# If the project name is already used we generate a new one
|
||||
project_name = self.controller.get_free_project_name(project_name)
|
||||
topology["name"] = project_name
|
||||
# To avoid unexpected behavior (project start without manual operations just after import)
|
||||
topology["auto_start"] = False
|
||||
topology["auto_open"] = False
|
||||
topology["auto_close"] = False
|
||||
|
||||
# regenerate IDs for the duplicated project
|
||||
regenerate_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))
|
||||
topology["project_id"] = new_project_id
|
||||
with open(dot_gns3_path, "w+") as f:
|
||||
json.dump(topology, f, indent=4, sort_keys=True)
|
||||
|
||||
# update the snapshots with new IDs
|
||||
snapshots_dir = os.path.join(new_project_path, "snapshots")
|
||||
if os.path.isdir(snapshots_dir):
|
||||
await update_snapshots(snapshots_dir, new_project_path, project_name, new_project_id)
|
||||
|
||||
os.remove(new_project_path.joinpath('{}.gns3'.format(self.name)))
|
||||
project = await self.controller.load_project(dot_gns3_path, load=False)
|
||||
log.info("Project '{}': fast duplicated in {:.4f} seconds".format(project.name, time.time() - t0))
|
||||
return project
|
||||
|
||||
def is_running(self):
|
||||
"""
|
||||
If a node is started or paused return True
|
||||
@@ -1302,72 +1355,4 @@ class Project:
|
||||
def __repr__(self):
|
||||
return "<gns3server.controller.Project {} {}>".format(self._name, self._id)
|
||||
|
||||
async def _fast_duplication(self, name=None, location=None, reset_mac_addresses=True):
|
||||
"""
|
||||
Fast duplication of a project.
|
||||
|
||||
Copy the project files directly rather than in an import-export fashion.
|
||||
|
||||
: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
|
||||
"""
|
||||
|
||||
# remote replication is not supported with remote computes
|
||||
for compute in self.computes:
|
||||
if compute.id != "local":
|
||||
log.warning("Fast duplication is not supported with remote compute: '{}'".format(compute.id))
|
||||
return None
|
||||
# work dir
|
||||
p_work = pathlib.Path(location or self.path).parent.absolute()
|
||||
t0 = time.time()
|
||||
new_project_id = str(uuid.uuid4())
|
||||
if location:
|
||||
new_project_path = p_work.joinpath(location)
|
||||
else:
|
||||
new_project_path = p_work.joinpath(new_project_id)
|
||||
# copy dir
|
||||
await wait_run_in_executor(shutil.copytree, self.path, new_project_path.as_posix(), symlinks=True, ignore_dangling_symlinks=True)
|
||||
log.info("Project content copied from '{}' to '{}' in {}s".format(self.path, new_project_path, time.time() - t0))
|
||||
topology = json.loads(new_project_path.joinpath('{}.gns3'.format(self.name)).read_bytes())
|
||||
project_name = name or topology["name"]
|
||||
# If the project name is already used we generate a new one
|
||||
project_name = self.controller.get_free_project_name(project_name)
|
||||
topology["name"] = project_name
|
||||
# To avoid unexpected behavior (project start without manual operations just after import)
|
||||
topology["auto_start"] = False
|
||||
topology["auto_open"] = False
|
||||
topology["auto_close"] = False
|
||||
# change node ID
|
||||
node_old_to_new = {}
|
||||
for node in topology["topology"]["nodes"]:
|
||||
new_node_id = str(uuid.uuid4())
|
||||
if "node_id" in node:
|
||||
node_old_to_new[node["node_id"]] = new_node_id
|
||||
_move_node_file(new_project_path, node["node_id"], new_node_id)
|
||||
node["node_id"] = new_node_id
|
||||
if reset_mac_addresses:
|
||||
if "properties" in node:
|
||||
for prop, value in node["properties"].items():
|
||||
# reset the MAC address
|
||||
if prop in ("mac_addr", "mac_address"):
|
||||
node["properties"][prop] = None
|
||||
# change link ID
|
||||
for link in topology["topology"]["links"]:
|
||||
link["link_id"] = str(uuid.uuid4())
|
||||
for node in link["nodes"]:
|
||||
node["node_id"] = node_old_to_new[node["node_id"]]
|
||||
# Generate new drawings id
|
||||
for drawing in topology["topology"]["drawings"]:
|
||||
drawing["drawing_id"] = str(uuid.uuid4())
|
||||
|
||||
# And we dump the updated.gns3
|
||||
dot_gns3_path = new_project_path.joinpath('{}.gns3'.format(project_name))
|
||||
topology["project_id"] = new_project_id
|
||||
with open(dot_gns3_path, "w+") as f:
|
||||
json.dump(topology, f, indent=4)
|
||||
|
||||
os.remove(new_project_path.joinpath('{}.gns3'.format(self.name)))
|
||||
project = await self.controller.load_project(dot_gns3_path, load=False)
|
||||
log.info("Project '{}' fast duplicated in {:.4f} seconds".format(project.name, time.time() - t0))
|
||||
return project
|
||||
Reference in new issue
Block a user