diff --git a/modules/mesh-recovery/internal/recovery/fakes_test.go b/modules/mesh-recovery/internal/recovery/fakes_test.go index a38f086f8..48796d2cb 100644 --- a/modules/mesh-recovery/internal/recovery/fakes_test.go +++ b/modules/mesh-recovery/internal/recovery/fakes_test.go @@ -122,6 +122,8 @@ func (s *fakeSeats) Streams(context.Context, string, string, string) ([]string, return s.streams, nil } +func (s *fakeSeats) RunningFingerprint(context.Context, string) (string, error) { return "fp-run", nil } + func (s *fakeSeats) Backup(context.Context, string) (string, error) { if s.backup == "" { return "", errors.New("no repository snapshot holds it") diff --git a/modules/mesh-recovery/internal/recovery/points.go b/modules/mesh-recovery/internal/recovery/points.go index 9256efd47..18e5ec14c 100644 --- a/modules/mesh-recovery/internal/recovery/points.go +++ b/modules/mesh-recovery/internal/recovery/points.go @@ -28,6 +28,8 @@ func (e *Engine) run(ctx context.Context, a *Act) { e.expire(a.ID) case KindRestore: err = e.restore(ctx, a) + case KindAside: + err = e.aside(ctx, a) case KindRestoreAside: err = e.restoreAside(ctx, a) case KindDiscardAside: diff --git a/modules/mesh-recovery/internal/recovery/recovery.go b/modules/mesh-recovery/internal/recovery/recovery.go index bdd0d05d8..91c1ab8f7 100644 --- a/modules/mesh-recovery/internal/recovery/recovery.go +++ b/modules/mesh-recovery/internal/recovery/recovery.go @@ -76,6 +76,8 @@ type Seats interface { HandAct(caller, asker, what, why string) error // Streams are the bus names of a module's kept data at a fingerprint, from its catalog graph entry (§554 Bus names). Streams(ctx context.Context, module, kept, fingerprint string) ([]string, error) + // RunningFingerprint is the fingerprint the node-engine's placement runs for the module (A-123). + RunningFingerprint(ctx context.Context, module string) (string, error) // Backup brings the points directory of the module's item back beside its path through the backup holder and // answers where (§516); an error when no repository snapshot holds it. Backup(ctx context.Context, module string) (dir string, err error) diff --git a/modules/mesh-recovery/internal/recovery/restore.go b/modules/mesh-recovery/internal/recovery/restore.go index cfec63109..744a2eaf6 100644 --- a/modules/mesh-recovery/internal/recovery/restore.go +++ b/modules/mesh-recovery/internal/recovery/restore.go @@ -35,7 +35,7 @@ type asideRecord struct { } func (r asideRecord) aside() seat.Aside { - return seat.Aside{ID: r.ID, Name: strings.Join(r.Streams, ","), Size: r.Size} + return seat.Aside{ID: r.ID, Names: r.Streams, Size: r.Size} } func (e *Engine) asideRoot(module, kept string) (string, error) { @@ -279,6 +279,29 @@ func (e *Engine) readAside(module, kept, id string) (*asideRecord, error) { return &rec, nil } +// aside is the hand verb `aside` (§516, §554, A-123): it sets the live kept data aside without restoring it, taking the bus +// names from the catalog entry of the fingerprint the node-engine's placement runs for the module, and removes the live +// objects once the aside is whole and checked (a move, not a removal: the data stays in the aside until it is discarded or +// put back by `restore-aside`); it ends with `set-aside`. +func (e *Engine) aside(ctx context.Context, a *Act) error { + fp, err := e.Seats.RunningFingerprint(ctx, a.Module) + if err != nil { + return err + } + a.Fingerprint = fp + names, err := e.Seats.Streams(ctx, a.Module, a.Kept, fp) + if err != nil { + return err + } + rec, err := e.setAside(ctx, a, names, true) + if err != nil { + return err + } + x := rec.aside() + e.say(seat.EventSetAside, seat.SetAsideEvent{Act: a.ID, At: e.now(), ID: x.ID, Names: x.Names, Size: x.Size}, a.Module, a.Kept) + return nil +} + // restoreAside is `restore-aside` (§566): it puts an aside whose record ends at `removed` back under the live names it // was taken from, refusing while a live object of those names exists, and ends with `restored`. func (e *Engine) restoreAside(ctx context.Context, a *Act) error { @@ -312,7 +335,7 @@ func (e *Engine) discardAside(a *Act) error { return err } x := rec.aside() - e.say(seat.EventAsideDiscarded, seat.AsideDiscardedEvent{Act: a.ID, At: e.now(), ID: x.ID, Name: x.Name, Size: x.Size}, a.Module, a.Kept) + e.say(seat.EventAsideDiscarded, seat.AsideDiscardedEvent{Act: a.ID, At: e.now(), ID: x.ID, Names: x.Names, Size: x.Size}, a.Module, a.Kept) return nil } diff --git a/modules/mesh-recovery/internal/recovery/restore_test.go b/modules/mesh-recovery/internal/recovery/restore_test.go index 1945a1823..2242705b1 100644 --- a/modules/mesh-recovery/internal/recovery/restore_test.go +++ b/modules/mesh-recovery/internal/recovery/restore_test.go @@ -232,3 +232,26 @@ func TestRestoreAsideRefusesLiveNamesAndDiscardAsideRemovesTheDirectory(t *testi t.Errorf("asides %+v", kp.Asides) } } + +// The hand verb aside sets the live data aside whole and removes it, naming the streams as a list, and restore-aside puts +// it back once no live name exists (§516, §554, §566, A-123, A-124). +func TestAsideMovesTheLiveDataIntoAnAsideAndRestoreAsidePutsItBack(t *testing.T) { + r := newRig(t) + ans := r.do(recovery.Act{Kind: recovery.KindAside, Module: "app", Kept: "notes", Why: "look at it", Caller: "tools", Asker: "op"}) + ev := r.s.named(seat.EventSetAside) + if ans.Accepted == "" || len(ev) != 1 { + t.Fatalf("answer %+v events %+v", ans, r.s.events) + } + sa := ev[0].Body.(seat.SetAsideEvent) + if sa.Act != ans.Accepted || len(sa.Names) != 2 || sa.Size == 0 || len(r.b.streams) != 0 { + t.Errorf("set-aside %+v streams %v", sa, r.b.streams) + } + if got := r.e.Unrestored(); len(got) != 1 { + t.Errorf("unrestored %+v", got) + } + r.s.events = nil + r.do(recovery.Act{Kind: recovery.KindRestoreAside, Module: "app", Kept: "notes", AsideID: sa.ID, Why: "done looking", Caller: "tools"}) + if lastRestored(t, r).Outcome != "done" || r.b.streams["app_notes"] != "hello" || len(r.e.Unrestored()) != 0 { + t.Errorf("streams %v", r.b.streams) + } +}