(feature) Add host backup preflight gates
Introduce a host preflight layer that separates dry-run blockers from real backup blockers. Show the effective per-host backup configuration in Django before queueing a run. Block real backup queueing when failed host checks remain, while still allowing dry-runs when only local storage preparation is missing.
This commit is contained in:
@@ -29,8 +29,9 @@ from .forms import (
|
||||
ScheduleConfigForm,
|
||||
SshCredentialForm,
|
||||
)
|
||||
from .host_ops import collect_host_checks, ensure_host_directories
|
||||
from .host_ops import ensure_host_directories
|
||||
from .models import BackupRun, GlobalConfig, HostConfig, ScheduleConfig, SnapshotRecord, SshCredential
|
||||
from .preflight import collect_backup_gate, effective_host_config_preview
|
||||
from .retention import run_sql_retention_apply, run_sql_retention_plan
|
||||
from .self_check import collect_self_checks, summarize_self_checks
|
||||
from .scheduler import next_due_after
|
||||
@@ -260,14 +261,15 @@ def create_host_config(request):
|
||||
@staff_member_required
|
||||
def host_detail(request, host: str):
|
||||
host_config = get_object_or_404(HostConfig, host=host)
|
||||
global_config = GlobalConfig.objects.filter(name="default").first()
|
||||
schedule = _schedule_for_host(host_config)
|
||||
queued_runs = host_config.runs.filter(status=BackupRun.Status.QUEUED)
|
||||
running_runs = host_config.runs.filter(status=BackupRun.Status.RUNNING)
|
||||
active_run = host_config.runs.filter(
|
||||
status__in=[BackupRun.Status.QUEUED, BackupRun.Status.RUNNING]
|
||||
).order_by("created_at", "id").first()
|
||||
has_global_config = GlobalConfig.objects.filter(name="default").exists()
|
||||
host_checks = collect_host_checks(host_config)
|
||||
has_global_config = global_config is not None
|
||||
backup_gate = collect_backup_gate(host_config, global_config)
|
||||
stats_summary = collect_host_stats(host=host_config, limit=10)
|
||||
context = {
|
||||
"host": host_config,
|
||||
@@ -275,11 +277,14 @@ def host_detail(request, host: str):
|
||||
"next_run_at": _next_run_for_schedule(schedule, host_config),
|
||||
"scheduler_timezone": timezone.get_current_timezone_name(),
|
||||
"discovery": inspect_snapshot_discovery(host=host_config),
|
||||
"host_checks": host_checks,
|
||||
"host_check_summary": summarize_self_checks(host_checks),
|
||||
"host_checks": backup_gate.checks,
|
||||
"host_check_summary": summarize_self_checks(backup_gate.checks),
|
||||
"backup_gate": backup_gate,
|
||||
"effective_config": effective_host_config_preview(host_config, global_config) if global_config else {},
|
||||
"stats_summary": stats_summary,
|
||||
"manual_backup_form": ManualBackupForm(initial=_default_manual_backup_initial(host_config)),
|
||||
"can_queue_backup": host_config.enabled and has_global_config,
|
||||
"can_queue_dry_run": host_config.enabled and has_global_config and backup_gate.can_queue_dry_run and active_run is None,
|
||||
"can_queue_real_backup": host_config.enabled and has_global_config and backup_gate.can_queue_real and active_run is None,
|
||||
"has_global_config": has_global_config,
|
||||
"active_run": active_run,
|
||||
"latest_runs": host_config.runs.select_related("snapshot").order_by("-created_at")[:10],
|
||||
@@ -338,7 +343,8 @@ def queue_manual_backup(request, host: str):
|
||||
if not host_config.enabled:
|
||||
messages.error(request, f"Cannot queue backup for disabled host {host_config.host}.")
|
||||
return redirect("host_detail", host=host_config.host)
|
||||
if not GlobalConfig.objects.filter(name="default").exists():
|
||||
global_config = GlobalConfig.objects.filter(name="default").first()
|
||||
if global_config is None:
|
||||
messages.error(request, "Create the default global config before queueing backups.")
|
||||
return redirect("host_detail", host=host_config.host)
|
||||
|
||||
@@ -347,6 +353,17 @@ def queue_manual_backup(request, host: str):
|
||||
messages.error(request, "Manual backup options are invalid.")
|
||||
return redirect("host_detail", host=host_config.host)
|
||||
|
||||
backup_gate = collect_backup_gate(host_config, global_config)
|
||||
if form.cleaned_data["dry_run"]:
|
||||
if not backup_gate.can_queue_dry_run:
|
||||
blockers = ", ".join(check.name for check in backup_gate.dry_run_blockers)
|
||||
messages.error(request, f"Cannot queue dry-run until failed preflight checks are resolved: {blockers}.")
|
||||
return redirect("host_detail", host=host_config.host)
|
||||
elif not backup_gate.can_queue_real:
|
||||
blockers = ", ".join(check.name for check in backup_gate.real_blockers)
|
||||
messages.error(request, f"Cannot queue real backup until failed preflight checks are resolved: {blockers}.")
|
||||
return redirect("host_detail", host=host_config.host)
|
||||
|
||||
run = queue_backup_run(
|
||||
host=host_config,
|
||||
dry_run=form.cleaned_data["dry_run"],
|
||||
|
||||
Reference in New Issue
Block a user