diff --git a/flake.nix b/flake.nix index f82fcf5..e33a3e9 100644 --- a/flake.nix +++ b/flake.nix @@ -24,6 +24,7 @@ pylsp-mypy python-lsp-black python-lsp-server + pyflakes typer # for strawberry ] ++ strawberry-graphql.optional-dependencies.cli)); diff --git a/selfprivacy_api/backup/jobs.py b/selfprivacy_api/backup/jobs.py index 0aacd86..975f258 100644 --- a/selfprivacy_api/backup/jobs.py +++ b/selfprivacy_api/backup/jobs.py @@ -14,6 +14,10 @@ def backup_job_type(service: Service) -> str: return f"{job_type_prefix(service)}.backup" +def autobackup_job_type() -> str: + return "backups.autobackup" + + def restore_job_type(service: Service) -> str: return f"{job_type_prefix(service)}.restore" @@ -36,6 +40,17 @@ def is_something_running_for(service: Service) -> bool: return len(running_jobs) != 0 +def add_autobackup_job(services: List[Service]) -> Job: + service_names = [s.get_display_name() for s in services] + pretty_service_list: str = ", ".join(service_names) + job = Jobs.add( + type_id=autobackup_job_type(), + name="Automatic backup", + description=f"Scheduled backup for services: {pretty_service_list}", + ) + return job + + def add_backup_job(service: Service) -> Job: if is_something_running_for(service): message = ( @@ -78,12 +93,14 @@ def get_job_by_type(type_id: str) -> Optional[Job]: JobStatus.RUNNING, ]: return job + return None def get_failed_job_by_type(type_id: str) -> Optional[Job]: for job in Jobs.get_jobs(): if job.type_id == type_id and job.status == JobStatus.ERROR: return job + return None def get_backup_job(service: Service) -> Optional[Job]: diff --git a/selfprivacy_api/backup/tasks.py b/selfprivacy_api/backup/tasks.py index c0f6a1d..706b41c 100644 --- a/selfprivacy_api/backup/tasks.py +++ b/selfprivacy_api/backup/tasks.py @@ -12,9 +12,9 @@ from selfprivacy_api.models.backup.snapshot import Snapshot from selfprivacy_api.utils.huey import huey from huey import crontab -from selfprivacy_api.services.service import Service from selfprivacy_api.services import get_service_by_id from selfprivacy_api.backup import Backups +from selfprivacy_api.backup.jobs import add_autobackup_job from selfprivacy_api.jobs import Jobs, JobStatus, Job @@ -72,26 +72,42 @@ def restore_snapshot( return True -def do_autobackup(): +def do_autobackup() -> None: """ Body of autobackup task, broken out to test it For some reason, we cannot launch periodic huey tasks inside tests """ time = datetime.utcnow().replace(tzinfo=timezone.utc) - for service in Backups.services_to_back_up(time): - handle = start_backup(service.get_id(), BackupReason.AUTO) - # To be on safe side, we do not do it in parallel - handle(blocking=True) + services_to_back_up = Backups.services_to_back_up(time) + job = add_autobackup_job(services_to_back_up) + + progress_per_service = 100 // len(services_to_back_up) + progress = 0 + Jobs.update(job, JobStatus.RUNNING, progress=progress) + + for service in services_to_back_up: + try: + Backups.back_up(service, BackupReason.AUTO) + except Exception as error: + Jobs.update( + job, + status=JobStatus.ERROR, + error=type(error).__name__ + ": " + str(error), + ) + return + progress = progress + progress_per_service + Jobs.update(job, JobStatus.RUNNING, progress=progress) + + Jobs.update(job, JobStatus.FINISHED) @huey.periodic_task(validate_datetime=validate_datetime) -def automatic_backup() -> bool: +def automatic_backup() -> None: """ The worker periodic task that starts the automatic backup process. """ do_autobackup() - return True @huey.periodic_task(crontab(hour="*/" + str(SNAPSHOT_CACHE_TTL_HOURS))) diff --git a/selfprivacy_api/utils/network.py b/selfprivacy_api/utils/network.py index b5d76ec..e6985c4 100644 --- a/selfprivacy_api/utils/network.py +++ b/selfprivacy_api/utils/network.py @@ -21,9 +21,9 @@ def get_ip4() -> str: def get_ip6() -> Optional[str]: """Get IPv6 address""" try: - ip6_addresses = subprocess.check_output(["ip", "addr", "show", "dev", "eth0"]).decode( - "utf-8" - ) + ip6_addresses = subprocess.check_output( + ["ip", "addr", "show", "dev", "eth0"] + ).decode("utf-8") ip6_addresses = re.findall(r"inet6 (\S+)\/\d+", ip6_addresses) for address in ip6_addresses: if ipaddress.IPv6Address(address).is_global: diff --git a/tests/test_autobackup.py b/tests/test_autobackup.py index 410694b..22f0d21 100644 --- a/tests/test_autobackup.py +++ b/tests/test_autobackup.py @@ -14,11 +14,11 @@ from selfprivacy_api.graphql.common_types.backup import ( from selfprivacy_api.backup import Backups, Snapshot from selfprivacy_api.backup.tasks import ( prune_autobackup_snapshots, - automatic_backup, do_autobackup, ) +from selfprivacy_api.backup.jobs import autobackup_job_type -from tests.test_backup import backups +from tests.test_backup import backups, assert_job_finished from tests.test_graphql.test_services import only_dummy_service @@ -74,6 +74,7 @@ def test_autobackup_taskbody(backups, only_dummy_service): backup_period = 13 # minutes assert Backups.get_all_snapshots() == [] + assert_job_finished(autobackup_job_type(), count=0) Backups.set_autobackup_period_minutes(backup_period) assert Backups.is_time_to_backup_service(dummy_service, now) @@ -88,6 +89,8 @@ def test_autobackup_taskbody(backups, only_dummy_service): assert snapshots[0].service_name == dummy_service.get_id() assert snapshots[0].reason == BackupReason.AUTO + assert_job_finished(autobackup_job_type(), count=1) + def test_autobackup_timer_periods(backups, dummy_service): now = datetime.now(timezone.utc)