gns3-server/gns3server/controller/snapshot.py

141 lines
5.0 KiB
Python
Raw Normal View History

#!/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 shutil
import tempfile
import aiofiles
import zipfile
import time
from datetime import datetime, timezone
2020-10-02 06:37:50 +00:00
from .controller_error import ControllerError
from ..utils.asyncio import wait_run_in_executor
from ..utils.asyncio import aiozipstream
from .export_project import export_project
from .import_project import import_project
import logging
2021-04-13 09:16:50 +00:00
log = logging.getLogger(__name__)
class Snapshot:
"""
A snapshot object
"""
def __init__(self, project, name=None, filename=None):
assert filename or name, "You need to pass a name or a filename"
2021-04-13 09:16:50 +00:00
self._id = str(
uuid.uuid4()
) # We don't need to keep id between project loading because they are use only as key for operation like delete, update.. but have no impact on disk
self._project = project
if name:
self._name = name
2024-05-09 10:23:32 +00:00
self._created_at = datetime.now(timezone.utc).timestamp()
2021-04-13 09:16:50 +00:00
filename = (
self._name
+ "_"
Merge branch '2.2' into 3.0 # Conflicts: # CHANGELOG # dev-requirements.txt # gns3server/compute/qemu/__init__.py # gns3server/compute/vmware/__init__.py # gns3server/controller/snapshot.py # gns3server/crash_report.py # gns3server/handlers/api/compute/atm_switch_handler.py # gns3server/run.py # gns3server/static/web-ui/26.77d4bfd104f37c42e028.js # gns3server/static/web-ui/index.html # gns3server/static/web-ui/runtime.415291667f70565cd8ef.js # gns3server/utils/__init__.py # gns3server/utils/images.py # gns3server/utils/interfaces.py # gns3server/version.py # gns3server/web/web_server.py # pytest.ini # requirements.txt # scripts/update-bundled-web-ui.sh # setup.py # tests/api/routes/compute/test_dynamips_nodes.py # tests/compute/builtin/nodes/test_cloud.py # tests/compute/docker/test_docker.py # tests/compute/docker/test_docker_vm.py # tests/compute/dynamips/test_dynamips_manager.py # tests/compute/dynamips/test_dynamips_router.py # tests/compute/iou/test_iou_vm.py # tests/compute/qemu/test_qcow2.py # tests/compute/qemu/test_qemu_manager.py # tests/compute/qemu/test_qemu_vm.py # tests/compute/test_base_node.py # tests/compute/test_manager.py # tests/compute/test_project.py # tests/compute/traceng/test_traceng_vm.py # tests/compute/virtualbox/test_virtualbox_manager.py # tests/compute/virtualbox/test_virtualbox_vm.py # tests/compute/vmware/test_vmware_manager.py # tests/compute/vmware/test_vmware_vm.py # tests/compute/vpcs/test_vpcs_vm.py # tests/conftest.py # tests/controller/gns3vm/test_remote_gns3_vm.py # tests/controller/gns3vm/test_virtualbox_gns3_vm.py # tests/controller/gns3vm/test_vmware_gns3_vm.py # tests/controller/test_export_project.py # tests/controller/test_gns3vm.py # tests/controller/test_import_project.py # tests/test_config.py # tests/utils/test_asyncio.py # tests/utils/test_images.py # tests/web/test_response.py
2024-05-16 10:53:39 +00:00
+ datetime.fromtimestamp(self._created_at, tz=timezone.utc).replace(tzinfo=None).strftime("%d%m%y_%H%M%S")
2021-04-13 09:16:50 +00:00
+ ".gns3project"
)
else:
self._name = filename.rsplit("_", 2)[0]
datestring = filename.replace(self._name + "_", "").split(".")[0]
self._created_at = (datetime.strptime(datestring, "%d%m%y_%H%M%S").replace(tzinfo=timezone.utc).timestamp())
self._path = os.path.join(project.path, "snapshots", filename)
@property
def id(self):
return self._id
@property
def name(self):
return self._name
@property
def path(self):
return self._path
@property
def created_at(self):
return int(self._created_at)
async def create(self):
"""
Create the snapshot
"""
if os.path.exists(self.path):
2021-04-13 09:07:58 +00:00
raise ControllerError(f"The snapshot file '{self.name}' already exists")
snapshot_directory = os.path.join(self._project.path, "snapshots")
try:
os.makedirs(snapshot_directory, exist_ok=True)
except OSError as e:
2021-04-13 09:07:58 +00:00
raise ControllerError(f"Could not create the snapshot directory '{snapshot_directory}': {e}")
try:
begin = time.time()
with tempfile.TemporaryDirectory(dir=snapshot_directory) as tmpdir:
# Do not compress the snapshots
with aiozipstream.ZipFile(compression=zipfile.ZIP_STORED) as zstream:
await export_project(zstream, self._project, tmpdir, keep_compute_ids=True, allow_all_nodes=True)
2021-04-13 09:16:50 +00:00
async with aiofiles.open(self.path, "wb") as f:
async for chunk in zstream:
await f.write(chunk)
2021-04-13 09:07:58 +00:00
log.info(f"Snapshot '{self.name}' created in {time.time() - begin:.4f} seconds")
except (ValueError, OSError, RuntimeError) as e:
2021-04-13 09:07:58 +00:00
raise ControllerError(f"Could not create snapshot file '{self.path}': {e}")
async def restore(self):
"""
Restore the snapshot
"""
await self._project.delete_on_computes()
# We don't send close notification to clients because the close / open dance is purely internal
await self._project.close(ignore_notification=True)
try:
# delete the current project files
project_files_path = os.path.join(self._project.path, "project-files")
if os.path.exists(project_files_path):
await wait_run_in_executor(shutil.rmtree, project_files_path)
with open(self._path, "rb") as f:
project = await import_project(self._project.controller, self._project.id, f, location=self._project.path,
auto_start=self._project.auto_start, auto_open=self._project.auto_open,
auto_close=self._project.auto_close)
except (OSError, PermissionError) as e:
2020-10-02 06:37:50 +00:00
raise ControllerError(str(e))
await project.open()
2021-04-17 14:04:28 +00:00
self._project.emit_notification("snapshot.restored", self.asdict())
return self._project
2021-04-17 14:04:28 +00:00
def asdict(self):
return {
"snapshot_id": self._id,
"name": self._name,
"created_at": int(self._created_at),
2021-04-13 09:16:50 +00:00
"project_id": self._project.id,
}