From 40ad1b5ce44e74b5a2f7e7d1dd736dcba3f923a4 Mon Sep 17 00:00:00 2001 From: Houkime <> Date: Wed, 12 Jul 2023 16:43:26 +0000 Subject: [PATCH] feature(backups): stop services before backups --- selfprivacy_api/backup/__init__.py | 38 +++++++++++++++++++++++------- tests/test_graphql/test_backup.py | 12 +++++++++- 2 files changed, 41 insertions(+), 9 deletions(-) diff --git a/selfprivacy_api/backup/__init__.py b/selfprivacy_api/backup/__init__.py index ddfd6be..3bbd721 100644 --- a/selfprivacy_api/backup/__init__.py +++ b/selfprivacy_api/backup/__init__.py @@ -6,7 +6,7 @@ from typing import List, Optional from selfprivacy_api.utils import ReadUserData, WriteUserData from selfprivacy_api.services import get_service_by_id -from selfprivacy_api.services.service import Service +from selfprivacy_api.services.service import Service, ServiceStatus, StoppedService from selfprivacy_api.jobs import Jobs, JobStatus, Job @@ -35,6 +35,18 @@ DEFAULT_JSON_PROVIDER = { } +class NotDeadError(AssertionError): + def __init__(self, service: Service): + self.service_name = service.get_id() + + def __str__(self): + return f""" + Service {self.service_name} should be either stopped or dead from an error before we back up. + Normally, this error is unreachable because we do try ensure this. + Apparently, not this time. + """ + + class Backups: """A stateless controller class for backups""" @@ -193,13 +205,15 @@ class Backups: Jobs.update(job, status=JobStatus.RUNNING) try: - service.pre_backup() - snapshot = Backups.provider().backupper.start_backup( - folders, - tag, - ) - Backups._store_last_snapshot(tag, snapshot) - service.post_restore() + with StoppedService(service): + Backups.assert_dead(service) # to be extra sure + service.pre_backup() + snapshot = Backups.provider().backupper.start_backup( + folders, + tag, + ) + Backups._store_last_snapshot(tag, snapshot) + service.post_restore() except Exception as e: Jobs.update(job, status=JobStatus.ERROR) raise e @@ -465,3 +479,11 @@ class Backups: repo_id="", ) Storage.store_provider(provider) + + @staticmethod + def assert_dead(service: Service): + # if we backup the service that is failing to restore it to the + # previous snapshot, its status can be FAILED + # And obviously restoring a failed service is the moun route + if service.get_status() not in [ServiceStatus.INACTIVE, ServiceStatus.FAILED]: + raise NotDeadError(service) diff --git a/tests/test_graphql/test_backup.py b/tests/test_graphql/test_backup.py index 573480c..319fb53 100644 --- a/tests/test_graphql/test_backup.py +++ b/tests/test_graphql/test_backup.py @@ -303,7 +303,17 @@ def test_snapshots_by_id(backups, dummy_service): assert Backups.get_snapshot_by_id(snap2.id).id == snap2.id -def test_backup_service_task(backups, dummy_service): +@pytest.fixture(params=["instant_server_stop", "delayed_server_stop"]) +def simulated_service_stopping_delay(request) -> float: + if request.param == "instant_server_stop": + return 0.0 + else: + return 0.3 + + +def test_backup_service_task(backups, dummy_service, simulated_service_stopping_delay): + dummy_service.set_delay(simulated_service_stopping_delay) + handle = start_backup(dummy_service) handle(blocking=True)