From b2c7e8b73a3cf59ddd67206da6314e120f3de7ee Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Mon, 21 Aug 2023 12:45:31 +0000 Subject: [PATCH] feature(backups): caps for autobackups --- selfprivacy_api/backup/__init__.py | 37 ++++++++++++++++++++++++++++++ selfprivacy_api/backup/storage.py | 13 +++++++++++ tests/test_graphql/test_backup.py | 25 ++++++++++++++++++++ 3 files changed, 75 insertions(+) diff --git a/selfprivacy_api/backup/__init__.py b/selfprivacy_api/backup/__init__.py index 3b141fa..b16f089 100644 --- a/selfprivacy_api/backup/__init__.py +++ b/selfprivacy_api/backup/__init__.py @@ -283,7 +283,10 @@ class Backups: service_name, reason=reason, ) + Backups._store_last_snapshot(service_name, snapshot) + if reason == BackupReason.AUTO: + Backups._prune_auto_snaps(service) service.post_restore() except Exception as error: Jobs.update(job, status=JobStatus.ERROR) @@ -292,6 +295,40 @@ class Backups: Jobs.update(job, status=JobStatus.FINISHED) return snapshot + @staticmethod + def _auto_snaps(service): + return [ + snap + for snap in Backups.get_snapshots(service) + if snap.reason == BackupReason.AUTO + ] + + @staticmethod + def _prune_auto_snaps(service) -> None: + max = Backups.max_auto_snapshots() + if max == -1: + return + + auto_snaps = Backups._auto_snaps(service) + if len(auto_snaps) > max: + n_to_kill = len(auto_snaps) - max + sorted_snaps = sorted(auto_snaps, key=lambda s: s.created_at) + snaps_to_kill = sorted_snaps[:n_to_kill] + for snap in snaps_to_kill: + Backups.forget_snapshot(snap) + + @staticmethod + def set_max_auto_snapshots(value: int) -> None: + """everything <=0 means unlimited""" + if value <= 0: + value = -1 + Storage.set_max_auto_snapshots(value) + + @staticmethod + def max_auto_snapshots() -> int: + """-1 means unlimited""" + return Storage.max_auto_snapshots() + # Restoring @staticmethod diff --git a/selfprivacy_api/backup/storage.py b/selfprivacy_api/backup/storage.py index d46f584..1a0091f 100644 --- a/selfprivacy_api/backup/storage.py +++ b/selfprivacy_api/backup/storage.py @@ -26,6 +26,7 @@ REDIS_INITTED_CACHE = "backups:repo_initted" REDIS_PROVIDER_KEY = "backups:provider" REDIS_AUTOBACKUP_PERIOD_KEY = "backups:autobackup_period" +REDIS_AUTOBACKUP_MAX_KEY = "backups:autobackup_cap" redis = RedisPool().get_connection() @@ -39,6 +40,7 @@ class Storage: redis.delete(REDIS_PROVIDER_KEY) redis.delete(REDIS_AUTOBACKUP_PERIOD_KEY) redis.delete(REDIS_INITTED_CACHE) + redis.delete(REDIS_AUTOBACKUP_MAX_KEY) prefixes_to_clean = [ REDIS_SNAPSHOTS_PREFIX, @@ -175,3 +177,14 @@ class Storage: def mark_as_uninitted(): """Marks the repository as initialized""" redis.delete(REDIS_INITTED_CACHE) + + @staticmethod + def set_max_auto_snapshots(value: int): + redis.set(REDIS_AUTOBACKUP_MAX_KEY, value) + + @staticmethod + def max_auto_snapshots(): + if redis.exists(REDIS_AUTOBACKUP_MAX_KEY): + return int(redis.get(REDIS_AUTOBACKUP_MAX_KEY)) + else: + return -1 diff --git a/tests/test_graphql/test_backup.py b/tests/test_graphql/test_backup.py index 16933b8..781468a 100644 --- a/tests/test_graphql/test_backup.py +++ b/tests/test_graphql/test_backup.py @@ -298,6 +298,31 @@ def test_backup_reasons(backups, dummy_service): assert snaps[0].reason == BackupReason.AUTO +def test_too_many_auto(backups, dummy_service): + assert Backups.max_auto_snapshots() == -1 + Backups.set_max_auto_snapshots(2) + assert Backups.max_auto_snapshots() == 2 + + snap = Backups.back_up(dummy_service, BackupReason.AUTO) + assert len(Backups.get_snapshots(dummy_service)) == 1 + snap2 = Backups.back_up(dummy_service, BackupReason.AUTO) + assert len(Backups.get_snapshots(dummy_service)) == 2 + snap3 = Backups.back_up(dummy_service, BackupReason.AUTO) + assert len(Backups.get_snapshots(dummy_service)) == 2 + + snaps = Backups.get_snapshots(dummy_service) + + assert snap2 in snaps + assert snap3 in snaps + assert snap not in snaps + + Backups.set_max_auto_snapshots(-1) + snap4 = Backups.back_up(dummy_service, BackupReason.AUTO) + snaps = Backups.get_snapshots(dummy_service) + assert len(snaps) == 3 + assert snap4 in snaps + + def folder_files(folder): return [ path.join(folder, filename)