gates / gates (push) Successful in 31s
H4: a failed definition write in the off-site restore writes the live definition back before the start;
a failed write-back holds the app (restore_mixed).
H5: a put-back whose stop fails pours nothing, keeps the copy and holds the app (off-site, local, power-cut);
a failed definition write-back keeps the copy (D20); a failed 'volume rm -f' fails that volume (no merge).
H6: TryBeginRestoreOp/RestoreBusy: check every operation and record the restore in one step under m.mu;
every web handler and the kept loads begin through it; install-time kept load asks about the mirror too.
H8: SweepUnusedRestoreCopies every 6 h: removes a copy only with no hold, no marker naming it, no restore
running, and older than 14 days.
Commit only; rides the next controller release.
61 lines
1.9 KiB
Go
61 lines
1.9 KiB
Go
package web
|
|
|
|
import (
|
|
"go/ast"
|
|
"go/parser"
|
|
"go/token"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"gitea.dooplex.hu/admin/felhom-controller/internal/backup"
|
|
)
|
|
|
|
// R-1256 H6: every restore handler records its op through beginRestoreOp, which asks about every other operation in
|
|
// the same step (backup.TryBeginRestoreOp). A handler that calls BeginRestoreOp directly re-opens the window between
|
|
// the early refusal and the record. RED-PROOF: put one `s.backupMgr.BeginRestoreOp(...)` back → FAILS, naming the file.
|
|
func TestR1256_H6_NoHandlerBeginsARestoreUnchecked(t *testing.T) {
|
|
files, _ := filepath.Glob("*.go")
|
|
for _, f := range files {
|
|
if strings.HasSuffix(f, "_test.go") {
|
|
continue
|
|
}
|
|
src, err := os.ReadFile(f)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
fs := token.NewFileSet()
|
|
af, err := parser.ParseFile(fs, f, src, 0)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
ast.Inspect(af, func(n ast.Node) bool {
|
|
if sel, ok := n.(*ast.SelectorExpr); ok && sel.Sel.Name == "BeginRestoreOp" {
|
|
t.Errorf("%s: a direct BeginRestoreOp — begin through s.beginRestoreOp (R-1256 H6)", fs.Position(sel.Pos()))
|
|
}
|
|
return true
|
|
})
|
|
}
|
|
}
|
|
|
|
// The handler's begin refuses with the household's sentence when a second-drive copy began after the early check.
|
|
func TestR1256_H6_TheBeginRefusesWithTheCopySentence(t *testing.T) {
|
|
s, _, _ := newOffboxWebServer(t)
|
|
if _, blocked := s.restoreOpBlocked(); blocked {
|
|
t.Fatal("fixture: nothing runs")
|
|
}
|
|
done := backup.MarkTier2CopyForTest("immich")
|
|
msg, ok := s.beginRestoreOp("offbox-reconstitute", "immich")
|
|
done()
|
|
if ok || !strings.Contains(msg, "második meghajtóra") {
|
|
t.Fatalf("the begin must refuse with the copy named; ok=%v %q", ok, msg)
|
|
}
|
|
if s.backupMgr.RestoreStatus().Running {
|
|
t.Fatal("a refused begin recorded a restore")
|
|
}
|
|
if _, ok := s.beginRestoreOp("offbox-reconstitute", "immich"); !ok {
|
|
t.Fatal("after the copy the begin must work")
|
|
}
|
|
}
|