v0.238.1: the nightly backup leaves an app alone WHILE it is being updated, not only once it is held (slice 4 follow-up)
gates / gates (push) Successful in 13s
gates / gates (push) Successful in 13s
Found live in v0.238.0 Scenario F on demo-hp: during an update's 5-minute health wait the app is not yet held, and the periodic recovery-unit capture at 10:17:09 wrote the never-started definition (alpine:3.20) into its PRIMARY unit, 53 s before the hold landed. The Tier-2 mirror the hold names survived only because Tier 2 runs daily; a nightly Tier 2 inside a verify window would have mirrored the broken definition over the copy the customer is told to restore from. backup.Manager.isHeld — consulted by the capture sweep, the Tier-2 run and the volume dump — is now also true while a guarded update is moving the app, via SetUpdatingCheck wired in main.go to stacks.Manager.IsUpdating. Test with positive control + red-proof; wiring pinned. Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
@@ -166,3 +166,30 @@ func TestSlice4_NightlyLegsLeaveAHeldAppAlone(t *testing.T) {
|
||||
t.Errorf("positive control: the unheld app must still be mirrored, got %v", mirrored)
|
||||
}
|
||||
}
|
||||
|
||||
// v0.238.1 — the gap Scenario F found live: during the update's health wait the app is not yet held,
|
||||
// and the periodic capture wrote the never-started new definition into its primary unit. An app a
|
||||
// guarded update is moving must be left alone by all three nightly legs, exactly like a held one.
|
||||
//
|
||||
// COMPANION RED-PROOF (REPORT.md): delete the updatingCheck clause from isHeld — the updating app is
|
||||
// then dumped, captured and mirrored, and this test fails.
|
||||
func TestSlice4_NightlyLegsLeaveAnAppMidUpdateAlone(t *testing.T) {
|
||||
h := newAdmissionHarness(t, "updating", "free")
|
||||
h.m.settings = slice4Settings(t)
|
||||
h.m.SetUpdatingCheck(func(name string) bool { return name == "updating" })
|
||||
h.m.runVolumeDumps()
|
||||
h.m.captureAllRecoveryUnits()
|
||||
var mirrored []string
|
||||
h.m.perAppTier2 = func(name string) error { mirrored = append(mirrored, name); return nil }
|
||||
h.m.RunAllTier2()
|
||||
for _, list := range [][]string{h.volDumped, h.prov.stopped, h.prov.infoHits, mirrored} {
|
||||
for _, n := range list {
|
||||
if n == "updating" {
|
||||
t.Fatalf("a nightly leg touched an app MID-UPDATE (dumped=%v stopped=%v captured=%v mirrored=%v)", h.volDumped, h.prov.stopped, h.prov.infoHits, mirrored)
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(mirrored) != 1 || mirrored[0] != "free" {
|
||||
t.Errorf("positive control: the app not being updated must still be mirrored, got %v", mirrored)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user