hub 0.167.0 + DooPlex push: the hub DB snapshot every 6 hours (D34, operator yes 2026-10-23, R-232 G5) — slots 02/08/14/20, catch-up 6 h, push :30 after each, refuse a copy older than 2 h; tests red-proved
gates / gates (push) Successful in 16m20s
gates / gates (push) Successful in 16m20s
This commit is contained in:
1 parent
83388c088a
commit
be7e97dc1a
8 files changed
+111
-28
No files matched your search
+7
-5
@@ -944,9 +944,9 @@ func main() {
|
||||
}
|
||||
}()
|
||||
|
||||
// Nightly DB snapshot (R-173, v0.136.0, `05` §16.3): VACUUM INTO <data>/snapshots/, keep 2. DooPlex's
|
||||
// felhom-hub-db-backup.service picks up the newest at 02:30, checks, encrypts and pushes it to ep0. A start-up
|
||||
// catch-up runs one when the newest is older than 24 h (a pod that was down at 02:00).
|
||||
// DB snapshot every 6 hours (R-173, v0.136.0, `05` §16.3; D34 v0.167.0): VACUUM INTO <data>/snapshots/, keep 2.
|
||||
// DooPlex's felhom-hub-db-backup.service picks up the newest at :30 after each slot, checks, encrypts and pushes it
|
||||
// to ep0. A start-up catch-up runs one when the newest is older than one slot gap (a pod that was down at a slot).
|
||||
snapMaker := &dbsnap.Maker{Store: dataStore, Dir: filepath.Join(cfg.Server.DataDir, "snapshots"), Logger: logger}
|
||||
runSnapshot := func() {
|
||||
sctx, cancel := context.WithTimeout(ctx, 10*time.Minute)
|
||||
@@ -955,10 +955,12 @@ func main() {
|
||||
logger.Printf("[ERROR] db snapshot failed: %v", err)
|
||||
}
|
||||
}
|
||||
if dbsnap.NeedsCatchUp(snapMaker.Dir, time.Now(), 24*time.Hour) {
|
||||
if dbsnap.NeedsCatchUp(snapMaker.Dir, time.Now(), dbsnap.CatchUpAge) {
|
||||
go runSnapshot()
|
||||
}
|
||||
go scheduleDaily(ctx, "db-snapshot", "02:00", runSnapshot, logger)
|
||||
for _, at := range dbsnap.Slots {
|
||||
go scheduleDaily(ctx, "db-snapshot "+at, at, runSnapshot, logger)
|
||||
}
|
||||
|
||||
// Backup deadline checker — runs daily at 05:00 Budapest
|
||||
go scheduleDaily(ctx, "deadline-check", "05:00", func() {
|
||||
|
||||
@@ -4,13 +4,13 @@ import (
|
||||
"go/ast"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"strconv"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// R-173 seam wiring: main() must schedule the nightly db snapshot — a scheduleDaily call named "db-snapshot" at
|
||||
// "02:00", before DooPlex's 02:30 push — and must run the start-up catch-up (dbsnap.NeedsCatchUp). Without them the
|
||||
// dbsnap package exists and DooPlex pushes nothing new. RED-PROOF: delete either call → this test fails.
|
||||
// R-173 seam wiring, D34: main() must schedule a db snapshot at every dbsnap.Slots time — a `for _, at := range
|
||||
// dbsnap.Slots` loop calling scheduleDaily(ctx, <name>, at, …) — and must run the start-up catch-up
|
||||
// (dbsnap.NeedsCatchUp with dbsnap.CatchUpAge). Without them the dbsnap package exists and DooPlex pushes nothing
|
||||
// new. RED-PROOF: delete the loop, schedule a literal time instead, or pass 24*time.Hour → this test fails.
|
||||
func TestR173_MainSchedulesTheDBSnapshot(t *testing.T) {
|
||||
f, err := parser.ParseFile(token.NewFileSet(), "main.go", nil, 0)
|
||||
if err != nil {
|
||||
@@ -18,30 +18,39 @@ func TestR173_MainSchedulesTheDBSnapshot(t *testing.T) {
|
||||
}
|
||||
scheduled, catchUp := false, false
|
||||
ast.Inspect(f, func(n ast.Node) bool {
|
||||
if r, ok := n.(*ast.RangeStmt); ok {
|
||||
sel, ok := r.X.(*ast.SelectorExpr)
|
||||
v, vok := r.Value.(*ast.Ident)
|
||||
if ok && vok && sel.Sel.Name == "Slots" {
|
||||
ast.Inspect(r.Body, func(m ast.Node) bool {
|
||||
c, ok := m.(*ast.CallExpr)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
if id, ok := c.Fun.(*ast.Ident); ok && id.Name == "scheduleDaily" && len(c.Args) >= 3 {
|
||||
if a, ok := c.Args[2].(*ast.Ident); ok && a.Name == v.Name {
|
||||
scheduled = true
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
}
|
||||
c, ok := n.(*ast.CallExpr)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
if id, ok := c.Fun.(*ast.Ident); ok && id.Name == "scheduleDaily" && len(c.Args) >= 3 {
|
||||
name, _ := c.Args[1].(*ast.BasicLit)
|
||||
at, _ := c.Args[2].(*ast.BasicLit)
|
||||
if name != nil && at != nil {
|
||||
n, _ := strconv.Unquote(name.Value)
|
||||
a, _ := strconv.Unquote(at.Value)
|
||||
if n == "db-snapshot" && a == "02:00" {
|
||||
scheduled = true
|
||||
}
|
||||
if sel, ok := c.Fun.(*ast.SelectorExpr); ok && sel.Sel.Name == "NeedsCatchUp" && len(c.Args) == 3 {
|
||||
if a, ok := c.Args[2].(*ast.SelectorExpr); ok && a.Sel.Name == "CatchUpAge" {
|
||||
catchUp = true
|
||||
}
|
||||
}
|
||||
if sel, ok := c.Fun.(*ast.SelectorExpr); ok && sel.Sel.Name == "NeedsCatchUp" {
|
||||
catchUp = true
|
||||
}
|
||||
return true
|
||||
})
|
||||
if !scheduled {
|
||||
t.Error(`cmd/hub/main.go never calls scheduleDaily(ctx, "db-snapshot", "02:00", …) — no nightly snapshot`)
|
||||
t.Error("cmd/hub/main.go never schedules a snapshot at each dbsnap.Slots time — no 6-hourly snapshot (D34)")
|
||||
}
|
||||
if !catchUp {
|
||||
t.Error("cmd/hub/main.go never calls dbsnap.NeedsCatchUp — a pod down at 02:00 leaves a stale snapshot")
|
||||
t.Error("cmd/hub/main.go never calls dbsnap.NeedsCatchUp(…, dbsnap.CatchUpAge) — a pod down at a slot leaves a stale snapshot")
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user