(bugfix) Preserve scheduled backup warning status
Update the scheduler to reflect the actual scheduled BackupRun status after a run completes, so prune warnings are shown as schedule warnings instead of being reported as successful schedule executions.
This commit is contained in:
@@ -10,7 +10,7 @@ from django.core.management.base import BaseCommand
|
|||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
from pobsync_backend.models import ScheduleConfig
|
from pobsync_backend.models import BackupRun, ScheduleConfig
|
||||||
from pobsync_backend.scheduler import due_key, is_due
|
from pobsync_backend.scheduler import due_key, is_due
|
||||||
|
|
||||||
|
|
||||||
@@ -52,12 +52,13 @@ class Command(BaseCommand):
|
|||||||
if not is_due(schedule.cron_expr, now):
|
if not is_due(schedule.cron_expr, now):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
schedule_started_at = timezone.now()
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
locked = ScheduleConfig.objects.select_for_update().get(pk=schedule.pk)
|
locked = ScheduleConfig.objects.select_for_update().get(pk=schedule.pk)
|
||||||
if locked.last_due_key == current_due_key:
|
if locked.last_due_key == current_due_key:
|
||||||
continue
|
continue
|
||||||
locked.last_due_key = current_due_key
|
locked.last_due_key = current_due_key
|
||||||
locked.last_started_at = timezone.now()
|
locked.last_started_at = schedule_started_at
|
||||||
locked.last_status = "running"
|
locked.last_status = "running"
|
||||||
locked.save(update_fields=["last_due_key", "last_started_at", "last_status", "updated_at"])
|
locked.save(update_fields=["last_due_key", "last_started_at", "last_status", "updated_at"])
|
||||||
|
|
||||||
@@ -72,6 +73,7 @@ class Command(BaseCommand):
|
|||||||
prune_max_delete=schedule.prune_max_delete,
|
prune_max_delete=schedule.prune_max_delete,
|
||||||
prune_protect_bases=schedule.prune_protect_bases,
|
prune_protect_bases=schedule.prune_protect_bases,
|
||||||
)
|
)
|
||||||
|
status = _latest_scheduled_run_status(host_id=schedule.host_id, started_at=schedule_started_at) or status
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
status = "failed"
|
status = "failed"
|
||||||
self.stderr.write(f"{schedule.host.host}: {type(exc).__name__}: {exc}")
|
self.stderr.write(f"{schedule.host.host}: {type(exc).__name__}: {exc}")
|
||||||
@@ -83,3 +85,16 @@ class Command(BaseCommand):
|
|||||||
ran += 1
|
ran += 1
|
||||||
|
|
||||||
return ran
|
return ran
|
||||||
|
|
||||||
|
|
||||||
|
def _latest_scheduled_run_status(*, host_id: int, started_at) -> str | None:
|
||||||
|
run = (
|
||||||
|
BackupRun.objects.filter(
|
||||||
|
host_id=host_id,
|
||||||
|
run_type=BackupRun.RunType.SCHEDULED,
|
||||||
|
created_at__gte=started_at,
|
||||||
|
)
|
||||||
|
.order_by("-created_at", "-id")
|
||||||
|
.first()
|
||||||
|
)
|
||||||
|
return run.status if run is not None else None
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ from zoneinfo import ZoneInfo
|
|||||||
from django.test import SimpleTestCase, TestCase
|
from django.test import SimpleTestCase, TestCase
|
||||||
|
|
||||||
from pobsync_backend.management.commands.run_pobsync_scheduler import Command
|
from pobsync_backend.management.commands.run_pobsync_scheduler import Command
|
||||||
from pobsync_backend.models import HostConfig, ScheduleConfig
|
from pobsync_backend.models import BackupRun, HostConfig, ScheduleConfig
|
||||||
from pobsync_backend.scheduler import due_key, is_due, next_due_after
|
from pobsync_backend.scheduler import due_key, is_due, next_due_after
|
||||||
|
|
||||||
|
|
||||||
@@ -64,3 +64,30 @@ class SchedulerCommandTests(TestCase):
|
|||||||
self.assertEqual(call.call_count, 1)
|
self.assertEqual(call.call_count, 1)
|
||||||
schedule = ScheduleConfig.objects.get(host=host)
|
schedule = ScheduleConfig.objects.get(host=host)
|
||||||
self.assertEqual(schedule.last_status, "success")
|
self.assertEqual(schedule.last_status, "success")
|
||||||
|
|
||||||
|
def test_run_due_records_warning_status_from_scheduled_backup_run(self) -> None:
|
||||||
|
host = HostConfig.objects.create(host="web-01", address="web-01.example.test")
|
||||||
|
ScheduleConfig.objects.create(host=host, cron_expr="* * * * *", prune=True, prune_max_delete=1)
|
||||||
|
|
||||||
|
def create_warning_run(*args, **kwargs) -> None:
|
||||||
|
BackupRun.objects.create(
|
||||||
|
host=host,
|
||||||
|
run_type=BackupRun.RunType.SCHEDULED,
|
||||||
|
status=BackupRun.Status.WARNING,
|
||||||
|
result={
|
||||||
|
"ok": True,
|
||||||
|
"prune": {
|
||||||
|
"ok": False,
|
||||||
|
"type": "ConfigError",
|
||||||
|
"error": "Refusing to delete 2 snapshots (exceeds --max-delete=1)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
command = Command()
|
||||||
|
with patch("pobsync_backend.management.commands.run_pobsync_scheduler.call_command", side_effect=create_warning_run):
|
||||||
|
count = command._run_due(prefix=Path("/opt/pobsync"), dry_run=False)
|
||||||
|
|
||||||
|
self.assertEqual(count, 1)
|
||||||
|
schedule = ScheduleConfig.objects.get(host=host)
|
||||||
|
self.assertEqual(schedule.last_status, "warning")
|
||||||
|
|||||||
Reference in New Issue
Block a user