R-1251 (P1): a pending put-back blocks every start — a cut restore's copy on an unbound drive waits (a restore hold), is poured back when the drive binds before any start; a failed put-back holds the app (H2); Stop during a restore still puts back (H7). Commit only, unreleased.
gates / gates (push) Successful in 1m18s

This commit is contained in:
2026-10-24 19:07:02 +02:00
parent 2449223f10
commit bb2809dc68
16 changed files with 847 additions and 24 deletions
@@ -18,7 +18,8 @@ func TestR704_ClearUpdateHoldClearsTheInstallsHoldsOnly(t *testing.T) {
for _, c := range []struct {
reason string
want bool
}{{HoldReasonUpdateFailed, true}, {HoldReasonUnhealthyStop, true}, {HoldReasonRestoreFailed, false}} {
}{{HoldReasonUpdateFailed, true}, {HoldReasonUnhealthyStop, true}, {HoldReasonRestoreFailed, false},
{HoldReasonPutBackPending, true}, {HoldReasonRestoreMixed, false}} { // R-1251: a waiting put-back ends with the install
if err := s.SetRestoreHold(RestoreHold{Stack: "app", At: "2026-09-13T19:56:32Z", Reason: c.reason}); err != nil {
t.Fatal(err)
}
+7 -1
View File
@@ -1988,6 +1988,10 @@ const (
// rolled back, the live definition written back, and the app is held: its files and volumes are the
// snapshot's, its database the pre-restore one. Cleared like a restore hold — by the operator.
HoldReasonRestoreMixed = "restore_mixed"
// HoldReasonPutBackPending (R-1251): a restore was cut after its copy of the live data was taken, and the copy
// is on a drive that was not bound when the box came back. Nothing may start the app until the copy is poured
// back (backup.AppStopGuard.ResumePutBack); SafetyDump holds the copy's folder. Cleared by the put-back.
HoldReasonPutBackPending = "putback_pending"
)
// SetRestoreHold records a hold. Modelled on SetDisconnected: a condition, plus what it is holding.
@@ -2044,11 +2048,13 @@ func (s *Settings) ClearRestoreHold(stack string) (bool, error) {
// stays: that one means a database was left in an unknown state and is cleared by the operator
// (`-clear-restore-hold`), never by a removal or an install. Returns whether a hold was cleared.
// Pinned by TestR704_ClearUpdateHoldClearsTheInstallsHoldsOnly.
// R-1251: a put-back still waiting for its drive (HoldReasonPutBackPending) belongs to the install too — its copy is
// of the removed install's volumes, and a later install of the same name must not wait for it.
func (s *Settings) ClearUpdateHold(stack string) (bool, error) {
s.mu.Lock()
defer s.mu.Unlock()
h, ok := s.RestoreHolds[stack]
if !ok || (h.Reason != HoldReasonUpdateFailed && h.Reason != HoldReasonUnhealthyStop) {
if !ok || (h.Reason != HoldReasonUpdateFailed && h.Reason != HoldReasonUnhealthyStop && h.Reason != HoldReasonPutBackPending) {
return false, nil
}
delete(s.RestoreHolds, stack)