Files
mesh-catalog/modules/restic/cmd/restic-backups/good.go
T

99 lines
2.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`, one key `<node>.<module>.<item>` for every item a module declares that a backup path covers, written
// when it changes, so the self-check's probe of a stale backup reads it there and no part reads the controller's records.
import (
"encoding/json"
"time"
stdio "git.novox.be/novox/mesh-sdk/go"
)
// GoodState is the state, GoodVersion the layout's version.
const (
GoodState = "good"
GoodVersion = 1
)
// GoodRow is one key: the item, the path whose snapshot keeps it, and the newest good night of its module (zero before
// the first). Moves to mesh-sdk's health package with the readers that share it.
type GoodRow struct {
V int `json:"v"`
Node string `json:"node"`
Module string `json:"module"`
Item string `json:"item"`
CoveredBy string `json:"covered_by"`
LastGood time.Time `json:"last_good,omitempty"`
}
// GoodKey is a row's key.
func GoodKey(node, module, item string) string { return node + "." + module + "." + item }
// GoodRows is the rows of this machine: 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]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]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[GoodKey(b.Node, module, it.ID)] = GoodRow{V: GoodVersion, Node: b.Node, Module: module, Item: it.ID,
CoveredBy: it.CoveredBy, LastGood: good[module].UTC()}
}
}
return out, nil
}
// syncGood writes the rows that differ from what it last wrote, once a minute while it runs (good.json is a file of
// the night's process, so a minute is how fast it can hear of a night); put is the state's write.
func syncGood(b *Backups, put func(key string, row GoodRow) error) {
time.Sleep(20 * time.Second) // the runtime serves first
wrote := map[string][]byte{}
for {
rows, err := b.GoodRows()
if err != nil {
b.Say("the last good nights could not be read: %v", err)
}
for k, r := range rows {
raw, _ := json.Marshal(r)
if string(wrote[k]) == string(raw) {
continue
}
if err := put(k, r); err != nil {
b.Say("the last good night of %s was not put: %v", k, err)
continue
}
wrote[k] = raw
}
time.Sleep(time.Minute)
}
}
func busGood(key string, row GoodRow) error {
_, err := stdio.State(GoodState).Put(key, row)
return err
}