Files
mesh-catalog/modules/restic/cmd/restic-backups/good.go
T
jschoubben 35ff67bda0
mesh/delivery-group group feat/774-l3-reland rejected: composed together: the checker's verdict at the members' heads (b48c90fea31f): integration: mesh-self-ch…
mesh/delivery closed: a newer head of the same pull request
mesh/check failed: integration: mesh-self-check/cmd/mesh-self-check.TestTheSelfCheckWholeRunsOnTheBus: the run answered {Run:doctor-1793351737-1 At:20…
Design 47's last milestones: the keeper, self-check, healers, hand-acts, provisioner, vault, artifact sweep, disk readings, credential forms (issues 774, 1952, 1953, 1954, 1955)
One commit with exactly the tree of the reviewed head 35b4509c0a, on main 12c2574c9d, so two compiled programs added and removed on the branch leave no trace in history (issue 1577).
2026-10-30 02:21:51 +01:00

129 lines
3.9 KiB
Go

package main
// **Each item's last good night, on the bus** (novox/hq ADR 0571 clause 3, ADR 0564 §6): beside good.json, the module's
// own state `good` (health.GoodRow), one key `<node>.<module>.<item>`, written when a night's files change, so the
// self-check's probe of a stale backup reads it there.
import (
"encoding/json"
"path/filepath"
"strings"
"syscall"
"time"
"unsafe"
"git.novox.be/novox/mesh-sdk/go/health"
)
// GoodRows is the rows of this machine (health.GoodRow): every item a module declares that a path of the module's backup
// covers, with the module's newest good night.
func (b *Backups) GoodRows() (map[string]health.GoodRow, error) {
items, err := b.Items()
if err != nil {
return nil, err
}
declared, err := b.Declared()
if err != nil {
return nil, err
}
paths := map[string][]string{}
for _, d := range declared {
paths[d.Module] = d.Paths
}
good := b.Good()
out := map[string]health.GoodRow{}
for module, its := range items {
for _, it := range its {
if it.Class == "cache" || it.CoveredBy == "" {
continue
}
covered := false
for _, p := range paths[module] {
covered = covered || p == it.CoveredBy
}
if !covered {
continue
}
out[health.GoodKey(b.Node, module, it.ID)] = health.GoodRow{V: health.GoodVersion, Node: b.Node, Module: module,
Item: it.ID, CoveredBy: it.CoveredBy, LastGood: good[module].UTC()}
}
}
return out, nil
}
// keepGood writes the rows that differ from what was last written, when a night succeeded (recordGood). A bus that
// cannot be reached loses nothing: good.json is the local copy, and the next good night writes the rows again.
func (b *Backups) keepGood() {
if b.PutGood == nil {
return
}
rows, err := b.GoodRows()
if err != nil {
b.Say("the last good nights could not be read: %v", err)
return
}
b.goodMu.Lock()
defer b.goodMu.Unlock()
if b.goodWrote == nil {
b.goodWrote = map[string][]byte{}
}
for k, r := range rows {
raw, _ := json.Marshal(r)
if string(b.goodWrote[k]) == string(raw) {
continue
}
if err := b.PutGood(k, r); err != nil {
b.Say("the last good night of %s was not put on the bus: %v", k, err)
continue
}
b.goodWrote[k] = raw
}
}
// watchGood writes the rows once at start and then each time the night's process changes what it keeps in the state
// directory (good.json, the nights' file), as the file system says it: the night runs as the operator account, which a
// process with a bus account of its own may not (ADR 0436 §2), so the tools' runtime, which has the module's account,
// writes at the night's event instead of the night itself. No timer.
func watchGood(b *Backups) {
time.Sleep(20 * time.Second) // the runtime serves first
b.keepGood()
for {
err := whenWritten(b.Where.State, func(name string) bool { return name == "good.json" || name == filepath.Base(b.nightsFile()) }, b.keepGood)
b.Say("watching the state directory for a night failed (%v); trying again in a minute", err)
time.Sleep(time.Minute)
b.keepGood()
}
}
// whenWritten calls then each time a file of dir that match names is written or put in place, until the watch fails.
func whenWritten(dir string, match func(name string) bool, then func()) error {
fd, err := syscall.InotifyInit1(syscall.IN_CLOEXEC)
if err != nil {
return err
}
defer syscall.Close(fd)
if _, err := syscall.InotifyAddWatch(fd, dir, syscall.IN_CLOSE_WRITE|syscall.IN_MOVED_TO); err != nil {
return err
}
buf := make([]byte, 64*syscall.SizeofInotifyEvent+4096)
for {
n, err := syscall.Read(fd, buf)
if err != nil {
if err == syscall.EINTR {
continue
}
return err
}
heard := false
for off := 0; off+syscall.SizeofInotifyEvent <= n; {
ev := (*syscall.InotifyEvent)(unsafe.Pointer(&buf[off]))
name := strings.TrimRight(string(buf[off+syscall.SizeofInotifyEvent:off+syscall.SizeofInotifyEvent+int(ev.Len)]), "\x00")
heard = heard || match(name)
off += syscall.SizeofInotifyEvent + int(ev.Len)
}
if heard {
then()
}
}
}