R-1251: sync the restore copy's folders before the marker names it (a cut left undo.json as .tmp on Tester 1); the interrupted-restore record says the put-back waits, not 'run it again'. Commit only.
gates / gates (push) Successful in 1m24s

This commit is contained in:
2026-10-24 19:36:32 +02:00
parent bb2809dc68
commit 062f9b41d9
12 changed files with 141 additions and 16 deletions
@@ -318,3 +318,44 @@ func TestR1251_ACompletedRestoreDropsTheWaitingPutBack(t *testing.T) {
t.Fatal("a completed restore must drop the waiting put-back hold")
}
}
// MEASURED on Tester 1 (2026-10-24, audits/restore-holes-2026-10-24/P1-tester1/): a power cut 2.1 s after the copy was
// recorded left its journal as undo.json.tmp — the marker named a copy the start could not read. The copy's folders
// must be synced BEFORE the marker names it. RED-PROOF: with the syncUndoDirs call removed from persistUndoForCrash this
// fails on „no folder was synced before the marker named the copy".
func TestR1251_TheCopyIsOnDiskBeforeTheMarkerNamesIt(t *testing.T) {
root := t.TempDir()
marker := filepath.Join(root, "appstop-state.json")
copyDir := filepath.Join(root, "drive", "backups", "restore-undo", "app", "20261024T171928Z")
if err := os.MkdirAll(copyDir, 0o700); err != nil {
t.Fatal(err)
}
g := NewAppStopGuard(marker, log.New(io.Discard, "", 0))
if err := g.Begin("unit-restore:app", ReasonUnitRestore, []string{"app"}); err != nil {
t.Fatal(err)
}
m := &Manager{logger: log.New(io.Discard, "", 0), appStop: g}
var synced []string
markerNamedCopyAtSync := false
orig := syncUndoDirs
t.Cleanup(func() { syncUndoDirs = orig })
syncUndoDirs = func(dir string) error {
b, _ := os.ReadFile(marker)
markerNamedCopyAtSync = strings.Contains(string(b), dir)
synced = append(synced, dir)
return orig(dir)
}
m.persistUndoForCrash("app", &liveVolumeCopy{dir: copyDir, copied: []string{"app_data"}})
if len(synced) != 1 || synced[0] != copyDir {
t.Fatalf("no folder was synced before the marker named the copy: %v", synced)
}
if markerNamedCopyAtSync {
t.Fatal("the marker already named the copy when its folders were synced — the order is the fix")
}
if b, _ := os.ReadFile(marker); !strings.Contains(string(b), copyDir) {
t.Fatal("the copy must still be recorded in the marker")
}
if _, err := os.Stat(filepath.Join(copyDir, "undo.json")); err != nil {
t.Fatal("the journal must be in place")
}
}