diff --git a/aleksis/core/apps.py b/aleksis/core/apps.py index b2816a3857847b55e665f7881fc205bf06717913..42edc4bceaa61a55de310f59cea74dc270a3cf0b 100644 --- a/aleksis/core/apps.py +++ b/aleksis/core/apps.py @@ -53,9 +53,17 @@ class CoreConfig(AppConfig): self._load_data_checks() - from .health_checks import DataChecksHealthCheckBackend + from .health_checks import ( + BackupJobHealthCheck, + DataChecksHealthCheckBackend, + DbBackupAgeHealthCheck, + MediaBackupAgeHealthCheck, + ) plugin_dir.register(DataChecksHealthCheckBackend) + plugin_dir.register(DbBackupAgeHealthCheck) + plugin_dir.register(MediaBackupAgeHealthCheck) + plugin_dir.register(BackupJobHealthCheck) @classmethod def _load_data_checks(cls): diff --git a/aleksis/core/health_checks.py b/aleksis/core/health_checks.py index e3239087a17e5fed6c59441e9c6a219e86deb006..49677765f6a2fcc52eafe227027ee381ff6da4c9 100644 --- a/aleksis/core/health_checks.py +++ b/aleksis/core/health_checks.py @@ -1,5 +1,11 @@ +from datetime import datetime + +from django.conf import settings from django.utils.translation import gettext as _ +from dbbackup import utils as dbbackup_utils +from dbbackup.storage import get_storage +from django_celery_results.models import TaskResult from health_check.backends import BaseHealthCheckBackend from aleksis.core.models import DataCheckResult @@ -16,3 +22,54 @@ class DataChecksHealthCheckBackend(BaseHealthCheckBackend): def identifier(self): return self.__class__.__name__ + + +class BaseBackupHealthCheck(BaseHealthCheckBackend): + """Common base class for backup age checks.""" + + critical_service = False + content_type = None + configured_seconds = None + + def check_status(self): + storage = get_storage() + backups = storage.list_backups(content_type=self.content_type) + if backups: + last_backup = backups[-1] + last_backup_time = dbbackup_utils.filename_to_date(last_backup) + time_gone_since_backup = last_backup_time - datetime.now() + + # Check if backup is older than configured time + if time_gone_since_backup.seconds > self.configured_seconds: + self.add_error(_(f"Last backup {time_gone_since_backup}!")) + else: + self.add_error(_("No backup found!")) + + +class DbBackupAgeHealthCheck(BaseBackupHealthCheck): + """Checks if last backup file is less than configured seconds ago.""" + + content_type = "db" + configured_seconds = settings.DBBACKUP_CHECK_SECONDS + + +class MediaBackupAgeHealthCheck(BaseBackupHealthCheck): + """Checks if last backup file is less than configured seconds ago.""" + + content_type = "media" + configured_seconds = settings.MEDIABACKUP_CHECK_SECONDS + + +class BackupJobHealthCheck(BaseHealthCheckBackend): + """Checks if last backup file is less than configured seconds ago.""" + + critical_service = False + + def check_status(self): + task = TaskResult.objects.filter(task_name="aleksis.core.tasks.backup_data").last() + + # Check if state is success + if not task: + self.add_error(_("No backup result found!")) + elif task and task.status != "SUCCESS": + self.add_error(_(f"{task.status} - {task.result}")) diff --git a/aleksis/core/settings.py b/aleksis/core/settings.py index b82e5367d2547da46e8f570c92d6c7a2ffc1d779..15e0e20fbdb34c9483ef343f237a9cf3b44353b4 100644 --- a/aleksis/core/settings.py +++ b/aleksis/core/settings.py @@ -792,4 +792,7 @@ HEALTH_CHECK = { "MEMORY_MIN": _settings.get("health.memory_min_mb", 500), } +DBBACKUP_CHECK_SECONDS = _settings.get("backup.database.check_seconds", 7200) +MEDIABACKUP_CHECK_SECONDS = _settings.get("backup.media.check_seconds", 7200) + PROMETHEUS_EXPORT_MIGRATIONS = False