reconcile: tier-aware restore-task deadline (S4.1 unattended offsite restore-test)
A WAN (pbs-tier) restore of a large guest exceeds the restore-task wait's 10m default → the wait expired mid-restore, teardown fired against a still-restoring (not-yet-pool-associated) scratch guest → leak + a phantom VM.Allocate 403. - RestoreTestSpec.RestoreTaskTimeout (0→10m default); the restore WaitTask passes it. Local tier unchanged (10m). - config RestoreTestPBSRestoreTimeoutSeconds + accessor (default 120m). - main restoreTaskTimeout(cfg,tier): configured PBS timeout only when tier==pbs, else 0. Both scheduler + selftest spec builds. - Tests + WaitOptions red-proof + accessor contract. The "grant scratch-band VM.Allocate" follow-up is diagnosed not blind-applied: the scratch is restored INTO /pool/felhom (ACL already grants VM.Allocate), so the earlier 403 was a consequence of the timeout. No ACL/host-install change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
This commit is contained in:
@@ -779,6 +779,16 @@ func storageTier(ctx context.Context, px *proxmox.Client, storageID string) stri
|
||||
return "local"
|
||||
}
|
||||
|
||||
// restoreTaskTimeout returns the tier-aware restore-task wait: the generous configured PBS timeout
|
||||
// for a WAN (pbs-tier) restore, else 0 (→ WaitOptions' 10m default) for a local restore. S4.1: a
|
||||
// too-short wait kills a WAN restore mid-flight → mid-restore teardown → leaked scratch.
|
||||
func restoreTaskTimeout(cfg config.Config, tier string) time.Duration {
|
||||
if tier == "pbs" {
|
||||
return cfg.Backup.RestoreTestPBSRestoreTimeout()
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// readTrimmed reads a file and trims surrounding whitespace/newline (for the .pw secret).
|
||||
func readTrimmed(path string) (string, error) {
|
||||
b, err := os.ReadFile(path)
|
||||
@@ -811,12 +821,16 @@ func buildRestoreTestScheduler(cfg config.Config, px *proxmox.Client, engine *re
|
||||
Runner: engine,
|
||||
Pick: runner.PickRestoreCandidate,
|
||||
Store: store,
|
||||
Spec: reconcile.RestoreTestSpec{
|
||||
RestoreStorage: cfg.Backup.RestoreStorage,
|
||||
ScratchMin: min,
|
||||
ScratchMax: max,
|
||||
SourceTier: storageTier(context.Background(), px, target),
|
||||
},
|
||||
Spec: func() reconcile.RestoreTestSpec {
|
||||
tier := storageTier(context.Background(), px, target)
|
||||
return reconcile.RestoreTestSpec{
|
||||
RestoreStorage: cfg.Backup.RestoreStorage,
|
||||
ScratchMin: min,
|
||||
ScratchMax: max,
|
||||
SourceTier: tier,
|
||||
RestoreTaskTimeout: restoreTaskTimeout(cfg, tier),
|
||||
}
|
||||
}(),
|
||||
Cadence: cadence,
|
||||
Logger: logger,
|
||||
})
|
||||
@@ -1249,9 +1263,11 @@ func runSelftestRestoreTest(ctx context.Context, cfg config.Config, logger *slog
|
||||
}
|
||||
min, max := cfg.Backup.ScratchBand()
|
||||
fmt.Printf(" restoring %s into scratch band [%d,%d] on %s …\n", archive, min, max, cfg.Backup.RestoreStorage)
|
||||
rtTier := storageTier(ctx, px, target)
|
||||
res := engine.RunRestoreTest(ctx, reconcile.RestoreTestSpec{
|
||||
Archive: archive, RestoreStorage: cfg.Backup.RestoreStorage,
|
||||
ScratchMin: min, ScratchMax: max, SourceTier: storageTier(ctx, px, target),
|
||||
ScratchMin: min, ScratchMax: max, SourceTier: rtTier,
|
||||
RestoreTaskTimeout: restoreTaskTimeout(cfg, rtTier),
|
||||
})
|
||||
printJSON("restore-test record", backup.ToHubRestoreTest(res, time.Now().UTC()))
|
||||
if res.Skipped {
|
||||
|
||||
Reference in New Issue
Block a user