R-1288: a restore of a set-aside unit sets the replaced install's unit aside at once, with no alarm. R-1282: the later media restart time is stored and resumed at start, once. Red-proved. Commit only.
gates / gates (push) Successful in 1m26s

This commit is contained in:
2026-10-25 21:37:55 +01:00
parent a79a292d72
commit b5863a96eb
8 changed files with 274 additions and 13 deletions
@@ -320,3 +320,56 @@ func TestD47_ASetAsideTellsTheOperatorOnce(t *testing.T) {
t.Fatalf("the operator hears once, with both identities: %v", got)
}
}
// R-1288, the Tester 1 order measured 2026-10-25 on 0.343.0: install A backed up → reinstall B → B's first backup sets
// A's unit aside (one alarm) and writes B's own unit → the household restores A's set-aside copy. Before: the next
// backup found B's unit and set it aside with a SECOND alarm. Now B's unit is set aside AT the restore, quietly, and
// the next backup writes A's unit with nothing more set aside.
// RED-PROOF: drop the retireReplacedUnit call → after the restore the primary place still holds B's unit, and the
// next capture alarms a second time.
func TestR1288_ARestoreOfASetAsideUnitRetiresTheReplacedUnitQuietly(t *testing.T) {
r := d47Setup(t, "aaaa000000000001")
var alarms []string
r.m.SetUnitSetAsideNotify(func(stack, unitID, liveID, dest string) { alarms = append(alarms, unitID) })
r.reinstall("bbbb000000000002")
if err := r.m.captureRecoveryUnit("tandoor", false); err != nil { // B's first backup: A set aside, B's unit written
t.Fatal(err)
}
if len(alarms) != 1 || len(r.setAside(t)) != 1 {
t.Fatalf("precondition: one set-aside, one alarm; got alarms %v, set-aside %v", alarms, r.setAside(t))
}
r.m.discoverDBs = func(context.Context) ([]DiscoveredDB, error) {
return []DiscoveredDB{{StackName: "tandoor", ContainerName: "tandoor-postgres", DBType: DBTypePostgres}}, nil
}
r.m.importDBDump = func(context.Context, DiscoveredDB, string) error { return nil }
r.m.volumeReplayFrom = func(string, string) (int, error) { return 1, nil }
r.m.undoVolumeProbeFn = func(string) (int64, bool, error) { return 10, true, nil }
r.m.undoVolumeTarFn = func(vol, dir string) error {
return os.WriteFile(filepath.Join(dir, vol+".tar"), []byte("live"), 0o600)
}
ks := r.sett.GetKeptCopies()
if _, _, err := r.m.RestoreSetAsideUnit(ks[0].ID); err != nil {
t.Fatalf("restore of A's set-aside unit: %v", err)
}
if r.prov.info.InstallID != "aaaa000000000001" {
t.Fatalf("precondition: the restored app is install A, got %q", r.prov.info.InstallID)
}
if exists, id, _ := unitOwnerOf(r.unitDir()); exists {
t.Fatalf("after the restore the primary place still holds install %q's unit — it must be set aside at the restore", id)
}
if got := len(r.setAside(t)); got != 2 {
t.Fatalf("set-aside units after the restore: %d, want 2 (A's, kept as the household's copy, and B's)", got)
}
if len(alarms) != 1 {
t.Fatalf("the restore the household chose must not alarm: alarms %v", alarms)
}
if !strings.Contains(r.logs.String(), "replaced by the restored install") {
t.Fatal("the restore's set-aside must say why in one line")
}
if err := r.m.captureRecoveryUnit("tandoor", false); err != nil { // the next backup: A's own unit, nothing set aside
t.Fatal(err)
}
if len(alarms) != 1 || len(r.setAside(t)) != 2 {
t.Fatalf("the next backup set something aside again: alarms %v, set-aside %v", alarms, r.setAside(t))
}
}