BKP8: backed-up lists an item put off for a restore with the restore's act and since when

Builds: design 47 G §302, §563, §664
This commit is contained in:
2026-11-12 05:07:04 +01:00
parent d708998a53
commit 4f7c0ba534
5 changed files with 117 additions and 2 deletions
+2 -2
View File
@@ -626,7 +626,7 @@ func (b *Backups) BackedUp(ctx context.Context, module string) ([]ModuleBackups,
}
listed[d.Module] = true
m := ModuleBackups{Module: d.Module, Runs: len(d.Runs), Paths: d.Paths, Retention: inForce, Thinned: thinned,
Data: itemReports(items[d.Module], measured[d.Module], d.Paths, good[d.Module]),
Data: b.withPutOff(d.Module, itemReports(items[d.Module], measured[d.Module], d.Paths, good[d.Module])),
HandTaken: handKept(d.Module, snaps, goodNights, inForce)}
if n, ok := nights[d.Module]; ok {
m.LastNight = &n
@@ -645,7 +645,7 @@ func (b *Backups) BackedUp(ctx context.Context, module string) ([]ModuleBackups,
if listed[name] || (module != "" && name != module) {
continue
}
out = append(out, ModuleBackups{Module: name, Paths: []string{}, HandTaken: []HandKept{}, Data: itemReports(its, measured[name], nil, time.Time{})})
out = append(out, ModuleBackups{Module: name, Paths: []string{}, HandTaken: []HandKept{}, Data: b.withPutOff(name, itemReports(its, measured[name], nil, time.Time{}))})
}
return out, nil
}
@@ -37,6 +37,8 @@ type ItemReport struct {
Peak *int64 `json:"peak_bytes,omitempty"`
PeakDay string `json:"peak_day,omitempty"`
LastBackup *time.Time `json:"last_backup,omitempty"`
// PutOff is set while the item waits for a restore of the recovery module: its act and since when (§563).
PutOff *PutOff `json:"put_off,omitempty"`
}
var safePath = regexp.MustCompile(`^/[^\s'"\\$` + "`" + `]*$`)
@@ -166,9 +166,12 @@ func (b *Backups) beginItems(ctx context.Context, module string) (func(err error
if err := end(ItemPutOff, map[string]any{"restore": act}); err != nil {
return nothing, fmt.Errorf("the put-off of %s's items could not be said: %w", module, err)
}
b.keepPutOff(items, act)
if err := b.Bus.WaitRestored(ctx, act); err != nil {
b.clearPutOff(module)
return nothing, fmt.Errorf("the end of the restore %s could not be waited for: %w", act, err)
}
b.clearPutOff(module)
if err := started(); err != nil {
return nothing, fmt.Errorf("the start of %s's items could not be said: %w", module, err)
}
@@ -15,6 +15,8 @@ type itemBusFake struct {
restoring []string // what each read of the recovery module's restores answers, in turn
unended []ItemRef
runFailure error
// waiting runs while the holder waits for a restore's restored.
waiting func()
}
func (f *itemBusFake) Say(event string, ref ItemRef, body map[string]any) error {
@@ -46,6 +48,9 @@ func (f *itemBusFake) Restoring(module string) (string, error) {
}
func (f *itemBusFake) WaitRestored(_ context.Context, act string) error {
f.log = append(f.log, "restored "+act)
if f.waiting != nil {
f.waiting()
}
return nil
}
@@ -102,6 +107,20 @@ func TestEveryItemOfABackedUpModuleSaysItsStartAndItsEnd(t *testing.T) {
func TestTheBackupOfTheBusGivesWayToARestoreThatRuns(t *testing.T) {
bus := &itemBusFake{restoring: []string{"act-7"}}
b := itemsBackups(t, bus, "postgres", nil)
// While it waits, `backed-up` lists the item put off with the restore's act and since when (BKP8, §302, §664).
bus.waiting = func() {
got, err := b.BackedUp(context.Background(), "postgres")
must(t, err)
var store *ItemReport
for i := range got[0].Data {
if got[0].Data[i].ID == "store" {
store = &got[0].Data[i]
}
}
if store == nil || store.PutOff == nil || store.PutOff.Restore != "act-7" || !store.PutOff.Since.Equal(b.Now()) {
t.Errorf("backed-up says the item as %+v", store)
}
}
if n := b.one(context.Background(), declaredOf(t, b, "postgres"), false); !n.OK {
t.Fatalf("%+v", n)
}
@@ -114,6 +133,13 @@ func TestTheBackupOfTheBusGivesWayToARestoreThatRuns(t *testing.T) {
if !reflect.DeepEqual(bus.log, want) {
t.Fatalf("said %q", bus.log)
}
got, err := b.BackedUp(context.Background(), "postgres")
must(t, err)
for _, it := range got[0].Data {
if it.PutOff != nil {
t.Errorf("%s is still put off once it ran: %+v", it.ID, it.PutOff)
}
}
}
// Design 47 G §563 (BKP4): at its start the holder ends every item it said started and never ended, failed with the error stopped.
@@ -0,0 +1,84 @@
package main
// An item put off for a restore (design 47 G §563, §302, §664): `backed-up` lists it with the restore's act and since when. The
// night's process keeps it in the module's state directory while the item waits, and the tools' runtime reads it there.
import (
"encoding/json"
"os"
"path/filepath"
"time"
)
// PutOff says an item gave way to a restore: the act of that restore and since when.
type PutOff struct {
Restore string `json:"restore"`
Since time.Time `json:"since"`
}
type putOffItem struct {
Module string `json:"module"`
Item string `json:"item"`
PutOff
}
func (b *Backups) putOffFile() string { return filepath.Join(b.Where.State, "put-off.json") }
func (b *Backups) readPutOff() []putOffItem {
raw, err := os.ReadFile(b.putOffFile())
if err != nil {
return nil
}
var all []putOffItem
_ = json.Unmarshal(raw, &all)
return all
}
func (b *Backups) writePutOff(all []putOffItem) {
if len(all) == 0 {
if err := os.Remove(b.putOffFile()); err != nil && !os.IsNotExist(err) {
b.Say("the items put off could not be cleared: %v", err)
}
return
}
raw, _ := json.MarshalIndent(all, "", " ")
if err := os.WriteFile(b.putOffFile(), append(raw, '\n'), 0o600); err != nil {
b.Say("the items put off could not be recorded: %v", err)
}
}
// keepPutOff records a module's items as put off for a restore, since now.
func (b *Backups) keepPutOff(items []ItemRef, act string) {
all := b.readPutOff()
for _, ref := range items {
all = append(all, putOffItem{Module: ref.Module, Item: ref.Item, PutOff: PutOff{Restore: act, Since: b.Now().UTC()}})
}
b.writePutOff(all)
}
// clearPutOff forgets a module's items put off: they started again.
func (b *Backups) clearPutOff(module string) {
var keep []putOffItem
for _, p := range b.readPutOff() {
if p.Module != module {
keep = append(keep, p)
}
}
b.writePutOff(keep)
}
// withPutOff says on a module's item reports which items are put off.
func (b *Backups) withPutOff(module string, reports []ItemReport) []ItemReport {
for _, p := range b.readPutOff() {
if p.Module != module {
continue
}
for i := range reports {
if reports[i].ID == p.Item {
put := p.PutOff
reports[i].PutOff = &put
}
}
}
return reports
}