controller v0.241.0: a bind-data app leans on off-site before its own unit; the hold names what the copy holds (R-479)
gates / gates (push) Successful in 13s
gates / gates (push) Successful in 13s
Operator ruling 2026-09-13. An app with classified binds walks second drive -> off-site -> own unit (its unit holds no files); volume apps keep 2 -> 1 -> 3. RestoreHold.CopyHolds records what the chosen copy holds and the sentence ends with it; older holds keep their tier-only sentence. Tests on both halves; red-proof: a layout-blind order fails the bind case.
This commit is contained in:
@@ -113,6 +113,56 @@ const (
|
||||
// then off-site. The first tier holding a copy the caller ACCEPTS is chosen.
|
||||
var updateTierOrder = []int{UpdateTierSecondDrive, UpdateTierLocal, UpdateTierOffsite}
|
||||
|
||||
// updateTierOrderBindData (R-479, operator ruling 2026-09-13, v0.241.0) is the order for an app whose
|
||||
// DATA lives in bind-mounted files outside its recovery unit: second drive, OFF-SITE, own unit. The own
|
||||
// unit then holds the definition and the database dumps but not the files, so a route back that names
|
||||
// it would restore settings and not data — measured on demo-hp with gokapi (v0.239.0: „a beállítások
|
||||
// visszaálltak … adatot nem"). Off-site carries the mandatory file legs; it comes before the unit.
|
||||
var updateTierOrderBindData = []int{UpdateTierSecondDrive, UpdateTierOffsite, UpdateTierLocal}
|
||||
|
||||
// DataOutsideUnit reports whether the app keeps data in bind-mounted files that the recovery unit does
|
||||
// not hold — i.e. the app has classified binds. Nil provider or no binds → false (the unit holds the
|
||||
// data: named volumes and database dumps). Pinned by TestR479_.
|
||||
func (m *Manager) DataOutsideUnit(stackName string) bool {
|
||||
if m == nil || m.stackProvider == nil {
|
||||
return false
|
||||
}
|
||||
binds, has := m.stackProvider.GetStackClassifiedBinds(stackName)
|
||||
return has && len(binds) > 0
|
||||
}
|
||||
|
||||
// UpdateTierOrderFor is the tier order the update walks for this app (R-475 / R-479).
|
||||
func (m *Manager) UpdateTierOrderFor(stackName string) []int {
|
||||
if m.DataOutsideUnit(stackName) {
|
||||
return updateTierOrderBindData
|
||||
}
|
||||
return updateTierOrder
|
||||
}
|
||||
|
||||
// UpdateCopyHolds is the customer phrase for what a copy on `tier` holds for this app — the second half
|
||||
// of the R-479 ruling: the hold sentence names WHAT the chosen copy holds, not only where it is.
|
||||
func (m *Manager) UpdateCopyHolds(stackName string, tier int) string {
|
||||
outside := m.DataOutsideUnit(stackName)
|
||||
switch tier {
|
||||
case UpdateTierLocal:
|
||||
if outside {
|
||||
return "csak a beállításokat és az adatbázist tartalmazza, a fájlokat nem"
|
||||
}
|
||||
return "a beállításokat, az adatbázist és az adatköteteket tartalmazza"
|
||||
case UpdateTierSecondDrive:
|
||||
if outside {
|
||||
return "a beállításokat, az adatbázist és a fájlokat tartalmazza"
|
||||
}
|
||||
return "a beállításokat, az adatbázist és az adatköteteket tartalmazza"
|
||||
case UpdateTierOffsite:
|
||||
if outside {
|
||||
return "a beállításokat, az adatbázist és a fájlokat tartalmazza"
|
||||
}
|
||||
return "a beállításokat, az adatbázist és az adatköteteket tartalmazza"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// UpdateTierLabel is a tier's name in the customer's hold sentence. "" for an unknown tier.
|
||||
func UpdateTierLabel(tier int) string {
|
||||
switch tier {
|
||||
@@ -147,7 +197,7 @@ type UpdateTierPoint struct {
|
||||
// makes "the age rule applies to whichever tier is chosen" one rule, not three (R-475 Scenario M).
|
||||
func (m *Manager) UpdateRestorePoints(ctx context.Context, stackName string, accept func(UpdateTierPoint) bool) (UpdateTierPoint, bool, []UpdateTierPoint) {
|
||||
var seen []UpdateTierPoint
|
||||
for _, tier := range updateTierOrder {
|
||||
for _, tier := range m.UpdateTierOrderFor(stackName) {
|
||||
p, ok := m.updateTierPoint(ctx, stackName, tier)
|
||||
if !ok {
|
||||
continue
|
||||
@@ -411,6 +461,12 @@ func (m *Manager) WriteUpdateSafetyDump(ctx context.Context, stackName string) (
|
||||
// (R-364). Since v0.239.0 (R-475) it names the tier: the copy may be on any of three, and each is
|
||||
// restored from a different place on the Mentések page.
|
||||
const UpdateHoldFmt = "A(z) %s frissítése %s-kor nem sikerült, és az alkalmazás nem indult el az új verzióval. " +
|
||||
"Az alkalmazás biztonsági okból leállítva marad, hogy az adatai ne sérüljenek. " +
|
||||
"Visszaállítható a Mentések oldalon ebből a biztonsági mentésből: %s, %s — ez a másolat %s."
|
||||
|
||||
// UpdateHoldTierFmt is the v0.239.0–v0.240.0 sentence, kept for a hold that recorded a tier but not
|
||||
// what the copy holds (CopyHolds empty).
|
||||
const UpdateHoldTierFmt = "A(z) %s frissítése %s-kor nem sikerült, és az alkalmazás nem indult el az új verzióval. " +
|
||||
"Az alkalmazás biztonsági okból leállítva marad, hogy az adatai ne sérüljenek. " +
|
||||
"Visszaállítható a Mentések oldalon ebből a biztonsági mentésből: %s, %s."
|
||||
|
||||
@@ -448,6 +504,13 @@ func fmtHoldTime(rfc3339 string) string {
|
||||
// that the next restart button will quietly start again. The caller logs it at ERROR and keeps the
|
||||
// failure on the page.
|
||||
func (m *Manager) HoldAfterFailedUpdate(stackName string, at time.Time, copyDate time.Time, copyTier int) error {
|
||||
return m.HoldAfterFailedUpdateHolding(stackName, at, copyDate, copyTier, "")
|
||||
}
|
||||
|
||||
// HoldAfterFailedUpdateHolding is HoldAfterFailedUpdate with the R-479 phrase for what the copy holds;
|
||||
// "" records none (the tier-only sentence). The adapter in main.go computes the phrase with
|
||||
// UpdateCopyHolds at hold time.
|
||||
func (m *Manager) HoldAfterFailedUpdateHolding(stackName string, at time.Time, copyDate time.Time, copyTier int, copyHolds string) error {
|
||||
if m == nil || m.settings == nil {
|
||||
return fmt.Errorf("no settings wired — the update hold for %s cannot be persisted", stackName)
|
||||
}
|
||||
@@ -459,11 +522,12 @@ func (m *Manager) HoldAfterFailedUpdate(stackName string, at time.Time, copyDate
|
||||
if !copyDate.IsZero() {
|
||||
h.CopyDate = copyDate.UTC().Format(time.RFC3339)
|
||||
h.CopyTier = copyTier
|
||||
h.CopyHolds = copyHolds
|
||||
}
|
||||
if err := m.settings.SetRestoreHold(h); err != nil {
|
||||
return fmt.Errorf("persisting the update hold for %s: %w", stackName, err)
|
||||
}
|
||||
m.logger.Printf("[WARN] [backup] %s is HELD STOPPED after a failed update (restore point: tier %d %q, %s)", stackName, h.CopyTier, UpdateTierLabel(h.CopyTier), h.CopyDate)
|
||||
m.logger.Printf("[WARN] [backup] %s is HELD STOPPED after a failed update (restore point: tier %d %q, %s; holds: %q)", stackName, h.CopyTier, UpdateTierLabel(h.CopyTier), h.CopyDate, h.CopyHolds)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user