feature(backups): lock and unlock at will

This commit is contained in:
Houkime 2023-08-07 13:33:10 +00:00
parent 52336b885d
commit 752a0b807e
3 changed files with 78 additions and 6 deletions

View file

@ -227,6 +227,24 @@ class ResticBackupper(AbstractBackupper):
raise ValueError("cannot init a repo: " + output) raise ValueError("cannot init a repo: " + output)
def is_initted(self) -> bool: def is_initted(self) -> bool:
command = self.restic_command(
"check",
)
with subprocess.Popen(
command,
stdout=subprocess.PIPE,
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:
return False
return True
def unlock(self) -> None:
"""Remove stale locks."""
command = self.restic_command( command = self.restic_command(
"unlock", "unlock",
) )
@ -235,10 +253,41 @@ class ResticBackupper(AbstractBackupper):
command, command,
stdout=subprocess.PIPE, stdout=subprocess.PIPE,
shell=False, shell=False,
stderr=subprocess.STDOUT,
) as handle: ) as handle:
# communication forces to complete and for returncode to get defined
output = handle.communicate()[0].decode("utf-8")
if handle.returncode != 0: if handle.returncode != 0:
return False raise ValueError("cannot unlock the backup repository: ", output)
return True
def lock(self) -> None:
"""
Introduce a stale lock.
Mainly for testing purposes.
Double lock is supposed to fail
"""
command = self.restic_command(
"check",
)
# using temporary cache in /run/user/1000/restic-check-cache-817079729
# repository 9639c714 opened (repository version 2) successfully, password is correct
# created new cache in /run/user/1000/restic-check-cache-817079729
# create exclusive lock for repository
# load indexes
# check all packs
# check snapshots, trees and blobs
# [0:00] 100.00% 1 / 1 snapshots
# no errors were found
try:
for line in output_yielder(command):
if "indexes" in line:
break
if "unable" in line:
raise ValueError(line)
except Exception as e:
raise ValueError("could not lock repository") from e
def restored_size(self, snapshot_id: str) -> int: def restored_size(self, snapshot_id: str) -> int:
""" """

View file

@ -1,8 +1,10 @@
import subprocess import subprocess
from os.path import exists from os.path import exists
from typing import Generator
def output_yielder(command): def output_yielder(command) -> Generator[str, None, None]:
"""Note: If you break during iteration, it kills the process"""
with subprocess.Popen( with subprocess.Popen(
command, command,
shell=False, shell=False,
@ -10,9 +12,15 @@ def output_yielder(command):
stderr=subprocess.STDOUT, stderr=subprocess.STDOUT,
universal_newlines=True, universal_newlines=True,
) as handle: ) as handle:
for line in iter(handle.stdout.readline, ""): if handle is None or handle.stdout is None:
if "NOTICE:" not in line: raise ValueError("could not run command: ", command)
yield line
try:
for line in iter(handle.stdout.readline, ""):
if "NOTICE:" not in line:
yield line
except GeneratorExit:
handle.kill()
def sync(src_path: str, dest_path: str): def sync(src_path: str, dest_path: str):

View file

@ -758,3 +758,18 @@ def test_move_blocks_backups(backups, dummy_service, restore_strategy):
with pytest.raises(ValueError): with pytest.raises(ValueError):
Backups.restore_snapshot(snap, restore_strategy) Backups.restore_snapshot(snap, restore_strategy)
def test_double_lock_unlock(backups, dummy_service):
# notice that introducing stale locks is only safe for other tests if we erase repo in between
# which we do at the time of writing this test
Backups.provider().backupper.lock()
with pytest.raises(ValueError):
Backups.provider().backupper.lock()
Backups.provider().backupper.unlock()
Backups.provider().backupper.lock()
Backups.provider().backupper.unlock()
Backups.provider().backupper.unlock()