57 lines
1.8 KiB
Go
57 lines
1.8 KiB
Go
package backup
|
|
|
|
import (
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// R-1252 (review round 2, HUNT-2 H1): a rejected backup block never deletes second-drive copies. RunTier2 reconciles
|
|
// only on a valid classification (tier2CaptureSetFull's `classified`); for a REJECTED block it says so on the app's
|
|
// status (note.tier2.settings_unreadable_kept) and tells the operator once a day through the existing operator-only
|
|
// digest (backup_run_failures, leg tier2-settings-unreadable — no new event type, so no hub change). Pinned by
|
|
// r1252_rejected_block_keeps_copies_test.go.
|
|
|
|
type tier2UnreadableWatch struct {
|
|
mu sync.Mutex
|
|
notify func(app, why string)
|
|
sent map[string]string // app → the UTC day it was last sent
|
|
now func() time.Time
|
|
}
|
|
|
|
// SetTier2SettingsUnreadableNotify wires the operator signal (main.go → notifier). INIT-ONLY.
|
|
func (m *Manager) SetTier2SettingsUnreadableNotify(fn func(app, why string)) {
|
|
m.tier2Unreadable.mu.Lock()
|
|
m.tier2Unreadable.notify = fn
|
|
m.tier2Unreadable.mu.Unlock()
|
|
}
|
|
|
|
// backupBlockRejected asks the stack provider why the app's backup block was rejected ("" = valid or absent).
|
|
func (m *Manager) backupBlockRejected(stack string) string {
|
|
if r, ok := m.stackProvider.(interface{ BackupBlockRejected(string) string }); ok {
|
|
return r.BackupBlockRejected(stack)
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// notifyTier2SettingsUnreadable sends the operator signal at most once per app per UTC day.
|
|
func (m *Manager) notifyTier2SettingsUnreadable(app, why string) {
|
|
w := &m.tier2Unreadable
|
|
w.mu.Lock()
|
|
now := time.Now
|
|
if w.now != nil {
|
|
now = w.now
|
|
}
|
|
day := now().UTC().Format("2006-01-02")
|
|
if w.notify == nil || w.sent[app] == day {
|
|
w.mu.Unlock()
|
|
return
|
|
}
|
|
if w.sent == nil {
|
|
w.sent = map[string]string{}
|
|
}
|
|
w.sent[app] = day
|
|
fn := w.notify
|
|
w.mu.Unlock()
|
|
fn(app, why)
|
|
}
|