mirror of
https://git.selfprivacy.org/SelfPrivacy/selfprivacy-rest-api.git
synced 2024-11-17 16:09:14 +00:00
feature(backups): lock and unlock at will
This commit is contained in:
parent
52336b885d
commit
752a0b807e
|
@ -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:
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -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:
|
||||||
|
if handle is None or handle.stdout is None:
|
||||||
|
raise ValueError("could not run command: ", command)
|
||||||
|
|
||||||
|
try:
|
||||||
for line in iter(handle.stdout.readline, ""):
|
for line in iter(handle.stdout.readline, ""):
|
||||||
if "NOTICE:" not in line:
|
if "NOTICE:" not in line:
|
||||||
yield line
|
yield line
|
||||||
|
except GeneratorExit:
|
||||||
|
handle.kill()
|
||||||
|
|
||||||
|
|
||||||
def sync(src_path: str, dest_path: str):
|
def sync(src_path: str, dest_path: str):
|
||||||
|
|
|
@ -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()
|
||||||
|
|
Loading…
Reference in a new issue