(feature) improve snapshot discovery visibility in Django
Add a discovery preflight that reports the configured backup root, host root, and snapshot directory counts before importing anything. Show discovery status on host detail pages so missing mounts or mismatched host directories are visible from the UI. Warn clearly when discovery scans zero snapshots, including whether the host backup directory is missing or simply empty.
This commit is contained in:
@@ -12,6 +12,7 @@ from pobsync.util import write_yaml_atomic
|
||||
from pobsync_backend.models import GlobalConfig, HostConfig, SnapshotRecord
|
||||
from pobsync_backend.snapshot_discovery import (
|
||||
discover_snapshots,
|
||||
inspect_snapshot_discovery,
|
||||
parse_snapshot_datetime,
|
||||
resolve_base_links,
|
||||
upsert_snapshot_record,
|
||||
@@ -63,6 +64,30 @@ class SnapshotDiscoveryTests(TestCase):
|
||||
self.assertEqual(record.kind, "scheduled")
|
||||
self.assertEqual(record.started_at, datetime(2026, 5, 19, 2, 15, tzinfo=timezone.utc))
|
||||
|
||||
def test_inspect_snapshot_discovery_reports_missing_global_config(self) -> None:
|
||||
host = HostConfig.objects.create(host="web-01", address="web-01.example.test")
|
||||
|
||||
result = inspect_snapshot_discovery(host=host)
|
||||
|
||||
self.assertFalse(result["ok"])
|
||||
self.assertEqual(result["reason"], "missing_global_config")
|
||||
self.assertEqual(result["total_candidates"], 0)
|
||||
|
||||
def test_inspect_snapshot_discovery_counts_snapshot_directories(self) -> None:
|
||||
with TemporaryDirectory() as tmp:
|
||||
backup_root = Path(tmp) / "backups"
|
||||
GlobalConfig.objects.create(name="default", backup_root=str(backup_root))
|
||||
host = HostConfig.objects.create(host="web-01", address="web-01.example.test")
|
||||
(backup_root / host.host / "scheduled" / "20260519-021500Z__ABCDEFGH").mkdir(parents=True)
|
||||
(backup_root / host.host / "manual" / "20260519-031500Z__MANUAL01").mkdir(parents=True)
|
||||
|
||||
result = inspect_snapshot_discovery(host=host)
|
||||
|
||||
self.assertTrue(result["ok"])
|
||||
self.assertEqual(result["reason"], "ready")
|
||||
self.assertEqual(result["total_candidates"], 2)
|
||||
self.assertEqual(result["kind_counts"], {"scheduled": 1, "manual": 1, "incomplete": 0})
|
||||
|
||||
def test_discovery_links_snapshot_to_base_record(self) -> None:
|
||||
with TemporaryDirectory() as tmp:
|
||||
backup_root = Path(tmp) / "backups"
|
||||
|
||||
@@ -218,10 +218,28 @@ class ViewTests(TestCase):
|
||||
self.assertContains(response, "Edit schedule")
|
||||
self.assertContains(response, "Edit config")
|
||||
self.assertContains(response, "Queue Manual Backup")
|
||||
self.assertContains(response, "Snapshot Discovery")
|
||||
self.assertContains(response, reverse("queue_manual_backup", args=[host.host]))
|
||||
self.assertContains(response, reverse("run_detail", args=[BackupRun.objects.get().id]))
|
||||
self.assertContains(response, reverse("snapshot_detail", args=[snapshot.id]))
|
||||
|
||||
def test_host_detail_renders_discovery_status_for_existing_snapshot_dirs(self) -> None:
|
||||
self.client.force_login(self.staff_user)
|
||||
with TemporaryDirectory() as tmp:
|
||||
backup_root = Path(tmp) / "backups"
|
||||
GlobalConfig.objects.create(name="default", backup_root=str(backup_root))
|
||||
host = HostConfig.objects.create(host="web-01", address="web-01.example.test")
|
||||
(backup_root / host.host / "scheduled" / "20260519-021500Z__ABCDEFGH").mkdir(parents=True)
|
||||
(backup_root / host.host / ".incomplete" / "20260519-031500Z__BROKEN01").mkdir(parents=True)
|
||||
|
||||
response = self.client.get(reverse("host_detail", args=[host.host]))
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertContains(response, f"Host root:</strong> {backup_root / host.host}")
|
||||
self.assertContains(response, "Found 2 snapshot directories")
|
||||
self.assertContains(response, "scheduled 1")
|
||||
self.assertContains(response, "incomplete 1")
|
||||
|
||||
def test_host_detail_returns_404_for_unknown_host(self) -> None:
|
||||
self.client.force_login(self.staff_user)
|
||||
|
||||
@@ -352,6 +370,20 @@ class ViewTests(TestCase):
|
||||
self.assertContains(response, "Snapshot discovery scanned 1 items")
|
||||
self.assertTrue(SnapshotRecord.objects.filter(host=host, dirname=snapshot_dir.name).exists())
|
||||
|
||||
def test_discover_host_snapshots_warns_when_host_root_is_missing(self) -> None:
|
||||
self.client.force_login(self.staff_user)
|
||||
with TemporaryDirectory() as tmp:
|
||||
backup_root = Path(tmp) / "backups"
|
||||
GlobalConfig.objects.create(name="default", backup_root=str(backup_root))
|
||||
host = HostConfig.objects.create(host="web-01", address="web-01.example.test")
|
||||
|
||||
response = self.client.post(reverse("discover_host_snapshots", args=[host.host]), follow=True)
|
||||
|
||||
self.assertRedirects(response, reverse("host_detail", args=[host.host]))
|
||||
self.assertContains(response, "Snapshot discovery scanned 0 items")
|
||||
self.assertContains(response, "Host backup directory does not exist yet")
|
||||
self.assertFalse(SnapshotRecord.objects.exists())
|
||||
|
||||
def test_discover_host_snapshots_requires_post(self) -> None:
|
||||
self.client.force_login(self.staff_user)
|
||||
host = HostConfig.objects.create(host="web-01", address="web-01.example.test")
|
||||
|
||||
Reference in New Issue
Block a user