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:
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user