diff --git a/selfprivacy_api/backup/backuppers/restic_backupper.py b/selfprivacy_api/backup/backuppers/restic_backupper.py index 6c3dbcc..022bda7 100644 --- a/selfprivacy_api/backup/backuppers/restic_backupper.py +++ b/selfprivacy_api/backup/backuppers/restic_backupper.py @@ -256,6 +256,7 @@ class ResticBackupper(AbstractBackupper): if "created restic repository" not in output: raise ValueError("cannot init a repo: " + output) + @unlocked_repo def is_initted(self) -> bool: command = self.restic_command( "check", @@ -267,9 +268,10 @@ class ResticBackupper(AbstractBackupper): shell=False, stderr=subprocess.STDOUT, ) as handle: - # communication forces to complete and for returncode to get defined output = handle.communicate()[0].decode("utf-8") if handle.returncode != 0: + if "unable to create lock" in output: + raise ValueError("Stale lock detected: ", output) return False return True @@ -319,6 +321,7 @@ class ResticBackupper(AbstractBackupper): except Exception as e: raise ValueError("could not lock repository") from e + @unlocked_repo def restored_size(self, snapshot_id: str) -> int: """ Size of a snapshot diff --git a/tests/test_graphql/test_backup.py b/tests/test_graphql/test_backup.py index b575b5b..da4da7a 100644 --- a/tests/test_graphql/test_backup.py +++ b/tests/test_graphql/test_backup.py @@ -776,6 +776,21 @@ def test_double_lock_unlock(backups, dummy_service): def test_operations_while_locked(backups, dummy_service): + # Stale lock prevention test + + # consider making it fully at the level of backupper? + # because this is where prevention lives? + # Backups singleton is here only so that we can run this against B2, S3 and whatever + # But maybe it is not necessary (if restic treats them uniformly enough) + Backups.provider().backupper.lock() snap = Backups.back_up(dummy_service) assert snap is not None + + Backups.provider().backupper.lock() + # using lowlevel to make sure no caching interferes + assert Backups.provider().backupper.is_initted() is True + + # check that no locks were left + Backups.provider().backupper.lock() + Backups.provider().backupper.unlock()