fix(backups): backup strategies were unused
continuous-integration/drone/push Build is failing Details

pull/35/head
Inex Code 2023-07-18 20:15:22 +03:00
parent 20f3e5c564
commit 8b504993d0
3 changed files with 54 additions and 22 deletions

View File

@ -1,12 +1,15 @@
from datetime import datetime, timedelta from datetime import datetime, timedelta
from operator import add from os import statvfs
from os import statvfs, path, walk
from typing import List, Optional from typing import List, Optional
from selfprivacy_api.utils import ReadUserData, WriteUserData from selfprivacy_api.utils import ReadUserData, WriteUserData
from selfprivacy_api.services import get_service_by_id from selfprivacy_api.services import get_service_by_id
from selfprivacy_api.services.service import Service, ServiceStatus, StoppedService from selfprivacy_api.services.service import (
Service,
ServiceStatus,
StoppedService,
)
from selfprivacy_api.jobs import Jobs, JobStatus, Job from selfprivacy_api.jobs import Jobs, JobStatus, Job
@ -41,16 +44,17 @@ class NotDeadError(AssertionError):
def __str__(self): def __str__(self):
return f""" return f"""
Service {self.service_name} should be either stopped or dead from an error before we back up. Service {self.service_name} should be either stopped or dead from
Normally, this error is unreachable because we do try ensure this. an error before we back up.
Apparently, not this time. Normally, this error is unreachable because we do try ensure this.
""" Apparently, not this time.
"""
class Backups: class Backups:
"""A stateless controller class for backups""" """A stateless controller class for backups"""
### Providers # Providers
@staticmethod @staticmethod
def provider(): def provider():
@ -172,7 +176,7 @@ class Backups:
user_data["backup"] = DEFAULT_JSON_PROVIDER user_data["backup"] = DEFAULT_JSON_PROVIDER
### Init # Init
@staticmethod @staticmethod
def init_repo(): def init_repo():
@ -191,7 +195,7 @@ class Backups:
return False return False
### Backup # Backup
@staticmethod @staticmethod
def back_up(service: Service): def back_up(service: Service):
@ -221,7 +225,8 @@ class Backups:
Jobs.update(job, status=JobStatus.FINISHED) Jobs.update(job, status=JobStatus.FINISHED)
return snapshot return snapshot
### Restoring # Restoring
@staticmethod @staticmethod
def _ensure_queued_restore_job(service, snapshot) -> Job: def _ensure_queued_restore_job(service, snapshot) -> Job:
job = get_restore_job(service) job = get_restore_job(service)
@ -237,12 +242,17 @@ class Backups:
Jobs.update(job, status=JobStatus.RUNNING) Jobs.update(job, status=JobStatus.RUNNING)
try: try:
Backups._restore_service_from_snapshot(service, snapshot.id, verify=False) Backups._restore_service_from_snapshot(
service,
snapshot.id,
verify=False,
)
except Exception as e: except Exception as e:
Backups._restore_service_from_snapshot( Backups._restore_service_from_snapshot(
service, failsafe_snapshot.id, verify=False service, failsafe_snapshot.id, verify=False
) )
raise e raise e
# TODO: Do we really have to forget this snapshot? — Inex
Backups.forget_snapshot(failsafe_snapshot) Backups.forget_snapshot(failsafe_snapshot)
@staticmethod @staticmethod
@ -295,8 +305,9 @@ class Backups:
else: else:
raise NotImplementedError( raise NotImplementedError(
""" """
We do not know if there is enough space for restoration because there is some novel restore strategy used! We do not know if there is enough space for restoration because
This is a developer's fault, open a issue please there is some novel restore strategy used!
This is a developer's fault, open an issue please
""" """
) )
available_space = Backups.space_usable_for_service(service) available_space = Backups.space_usable_for_service(service)
@ -307,15 +318,20 @@ class Backups:
) )
@staticmethod @staticmethod
def _restore_service_from_snapshot(service: Service, snapshot_id: str, verify=True): def _restore_service_from_snapshot(
service: Service,
snapshot_id: str,
verify=True,
):
folders = service.get_folders() folders = service.get_folders()
Backups.provider().backupper.restore_from_backup( Backups.provider().backupper.restore_from_backup(
snapshot_id, snapshot_id,
folders, folders,
verify=verify,
) )
### Snapshots # Snapshots
@staticmethod @staticmethod
def get_snapshots(service: Service) -> List[Snapshot]: def get_snapshots(service: Service) -> List[Snapshot]:
@ -377,7 +393,7 @@ class Backups:
# expiring cache entry # expiring cache entry
Storage.cache_snapshot(snapshot) Storage.cache_snapshot(snapshot)
### Autobackup # Autobackup
@staticmethod @staticmethod
def is_autobackup_enabled(service: Service) -> bool: def is_autobackup_enabled(service: Service) -> bool:
@ -472,7 +488,7 @@ class Backups:
) )
] ]
### Helpers # Helpers
@staticmethod @staticmethod
def space_usable_for_service(service: Service) -> int: def space_usable_for_service(service: Service) -> int:
@ -501,5 +517,8 @@ class Backups:
# if we backup the service that is failing to restore it to the # if we backup the service that is failing to restore it to the
# previous snapshot, its status can be FAILED # previous snapshot, its status can be FAILED
# And obviously restoring a failed service is the moun route # And obviously restoring a failed service is the moun route
if service.get_status() not in [ServiceStatus.INACTIVE, ServiceStatus.FAILED]: if service.get_status() not in [
ServiceStatus.INACTIVE,
ServiceStatus.FAILED,
]:
raise NotDeadError(service) raise NotDeadError(service)

View File

@ -30,7 +30,12 @@ class AbstractBackupper(ABC):
raise NotImplementedError raise NotImplementedError
@abstractmethod @abstractmethod
def restore_from_backup(self, snapshot_id: str, folders: List[str], verify=True): def restore_from_backup(
self,
snapshot_id: str,
folders: List[str],
verify=True,
):
"""Restore a target folder using a snapshot""" """Restore a target folder using a snapshot"""
raise NotImplementedError raise NotImplementedError

View File

@ -81,7 +81,7 @@ class ResticBackupper(AbstractBackupper):
mount_command.insert(0, "nohup") mount_command.insert(0, "nohup")
handle = subprocess.Popen(mount_command, stdout=subprocess.DEVNULL, shell=False) handle = subprocess.Popen(mount_command, stdout=subprocess.DEVNULL, shell=False)
sleep(2) sleep(2)
if not "ids" in listdir(dir): if "ids" not in listdir(dir):
raise IOError("failed to mount dir ", dir) raise IOError("failed to mount dir ", dir)
return handle return handle
@ -211,7 +211,12 @@ class ResticBackupper(AbstractBackupper):
except ValueError as e: except ValueError as e:
raise ValueError("cannot restore a snapshot: " + output) from e raise ValueError("cannot restore a snapshot: " + output) from e
def restore_from_backup(self, snapshot_id, folders: List[str], verify=True): def restore_from_backup(
self,
snapshot_id,
folders: List[str],
verify=True,
):
""" """
Restore from backup with restic Restore from backup with restic
""" """
@ -236,6 +241,9 @@ class ResticBackupper(AbstractBackupper):
dst = folder dst = folder
sync(src, dst) sync(src, dst)
if not verify:
self.unmount_repo(dir)
def do_restore(self, snapshot_id, target="/", verify=False): def do_restore(self, snapshot_id, target="/", verify=False):
"""barebones restic restore""" """barebones restic restore"""
restore_command = self.restic_command( restore_command = self.restic_command(