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

This commit is contained in:
admin committed 2026-10-23 09:12:08 +02:00
1 parent 83388c088a
commit be7e97dc1a
8 files changed
+111 -28

No files matched your search

+7 -5
View File
@@ -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() {
+27 -18
View File
@@ -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")
}
}