feat: link backup runs to snapshot records
Add a nullable SnapshotRecord foreign key to BackupRun and populate it when run_pobsync_backup records a completed or failed snapshot. Keep the existing snapshot_path for audit compatibility while making run-to-snapshot navigation explicit in the database and admin.
This commit is contained in:
@@ -50,9 +50,10 @@ class HostConfigAdmin(admin.ModelAdmin):
|
||||
|
||||
@admin.register(BackupRun)
|
||||
class BackupRunAdmin(admin.ModelAdmin):
|
||||
list_display = ("host", "run_type", "status", "started_at", "ended_at", "snapshot_path")
|
||||
list_display = ("host", "run_type", "status", "started_at", "ended_at", "snapshot")
|
||||
list_filter = ("run_type", "status", "started_at")
|
||||
search_fields = ("host__host", "snapshot_path")
|
||||
search_fields = ("host__host", "snapshot_path", "snapshot__dirname", "snapshot__path")
|
||||
autocomplete_fields = ("snapshot",)
|
||||
|
||||
|
||||
@admin.register(SnapshotRecord)
|
||||
|
||||
@@ -64,23 +64,26 @@ class Command(BaseCommand):
|
||||
rsync = result.get("rsync") if isinstance(result.get("rsync"), dict) else {}
|
||||
run.rsync_exit_code = rsync.get("exit_code")
|
||||
run.result = result
|
||||
snapshot_record = None
|
||||
if run.snapshot_path:
|
||||
snapshot_path = Path(run.snapshot_path)
|
||||
try:
|
||||
kind = infer_snapshot_kind(snapshot_path)
|
||||
snapshot_record, _created = upsert_snapshot_record(host=host, kind=kind, snapshot_dir=snapshot_path)
|
||||
except ValueError:
|
||||
snapshot_record = None
|
||||
run.snapshot = snapshot_record
|
||||
run.save(
|
||||
update_fields=[
|
||||
"status",
|
||||
"ended_at",
|
||||
"snapshot_path",
|
||||
"snapshot",
|
||||
"base_path",
|
||||
"rsync_exit_code",
|
||||
"result",
|
||||
],
|
||||
)
|
||||
if run.snapshot_path:
|
||||
snapshot_path = Path(run.snapshot_path)
|
||||
try:
|
||||
kind = infer_snapshot_kind(snapshot_path)
|
||||
upsert_snapshot_record(host=host, kind=kind, snapshot_dir=snapshot_path)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
if result.get("ok"):
|
||||
self.stdout.write(self.style.SUCCESS(f"Backup completed for {host.host}."))
|
||||
|
||||
24
src/pobsync_backend/migrations/0004_backuprun_snapshot.py
Normal file
24
src/pobsync_backend/migrations/0004_backuprun_snapshot.py
Normal file
@@ -0,0 +1,24 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("pobsync_backend", "0003_structured_config_fields"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="backuprun",
|
||||
name="snapshot",
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name="backup_runs",
|
||||
to="pobsync_backend.snapshotrecord",
|
||||
),
|
||||
),
|
||||
]
|
||||
@@ -82,6 +82,13 @@ class BackupRun(models.Model):
|
||||
started_at = models.DateTimeField(null=True, blank=True)
|
||||
ended_at = models.DateTimeField(null=True, blank=True)
|
||||
snapshot_path = models.CharField(max_length=1024, blank=True)
|
||||
snapshot = models.ForeignKey(
|
||||
"SnapshotRecord",
|
||||
on_delete=models.SET_NULL,
|
||||
null=True,
|
||||
blank=True,
|
||||
related_name="backup_runs",
|
||||
)
|
||||
base_path = models.CharField(max_length=1024, blank=True)
|
||||
rsync_exit_code = models.IntegerField(null=True, blank=True)
|
||||
result = models.JSONField(default=dict, blank=True)
|
||||
|
||||
@@ -43,8 +43,10 @@ class RunBackupRecordsSnapshotTests(TestCase):
|
||||
call_command("run_pobsync_backup", host.host, prefix=str(Path(tmp) / "home"), stdout=StringIO())
|
||||
|
||||
self.assertEqual(BackupRun.objects.count(), 1)
|
||||
run = BackupRun.objects.get()
|
||||
self.assertEqual(SnapshotRecord.objects.count(), 1)
|
||||
record = SnapshotRecord.objects.get()
|
||||
self.assertEqual(run.snapshot, record)
|
||||
self.assertEqual(record.host, host)
|
||||
self.assertEqual(record.kind, "scheduled")
|
||||
self.assertEqual(record.status, "success")
|
||||
@@ -74,6 +76,7 @@ class RunBackupRecordsSnapshotTests(TestCase):
|
||||
run = BackupRun.objects.get()
|
||||
self.assertEqual(run.status, BackupRun.Status.FAILED)
|
||||
record = SnapshotRecord.objects.get()
|
||||
self.assertEqual(run.snapshot, record)
|
||||
self.assertEqual(record.kind, "incomplete")
|
||||
self.assertEqual(record.status, "failed")
|
||||
|
||||
@@ -99,4 +102,5 @@ class RunBackupRecordsSnapshotTests(TestCase):
|
||||
)
|
||||
|
||||
self.assertEqual(BackupRun.objects.count(), 1)
|
||||
self.assertIsNone(BackupRun.objects.get().snapshot)
|
||||
self.assertEqual(SnapshotRecord.objects.count(), 0)
|
||||
|
||||
Reference in New Issue
Block a user