fix(backup): keep backup successes recorded while seeding reconciles targets
RetainBackupTargets now takes the time the declaration was read and keeps entries updated after it, so a target declared and backed up during startup seeding is not dropped.
- commit
- f8072b0ceb272936a65300f0e29e96dcc0442925
- author
- brice · · 2026-10-17 06:59 UTC
- parent
- 229d05e
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
7 files · +56 -23
M internal/adapters/out/telemetry/backup_metrics.go +15 -10
| @@ -24,13 +24,15 @@ func targetAttrs(ref domain.BackupTargetRef) []attribute.KeyValue { | |||
| 24 | 24 | } | |
| 25 | 25 | ||
| 26 | 26 | type backupLastSuccess struct { | |
| 27 | - | at time.Time | |
| 28 | - | size int64 | |
| 27 | + | at time.Time | |
| 28 | + | size int64 | |
| 29 | + | updated time.Time // when this entry was written, for retain | |
| 29 | 30 | } | |
| 30 | 31 | ||
| 31 | 32 | // backupState holds the last success per target, exported by observable | |
| 32 | 33 | // gauges at each collection. | |
| 33 | 34 | type backupState struct { | |
| 35 | + | now func() time.Time | |
| 34 | 36 | mu sync.Mutex | |
| 35 | 37 | last map[domain.BackupTargetRef]backupLastSuccess | |
| 36 | 38 | nextRun map[domain.BackupKind]func() time.Time | |
| @@ -38,6 +40,7 @@ type backupState struct { | |||
| 38 | 40 | ||
| 39 | 41 | func newBackupState() *backupState { | |
| 40 | 42 | return &backupState{ | |
| 43 | + | now: time.Now, | |
| 41 | 44 | last: map[domain.BackupTargetRef]backupLastSuccess{}, | |
| 42 | 45 | nextRun: map[domain.BackupKind]func() time.Time{}, | |
| 43 | 46 | } | |
| @@ -54,21 +57,23 @@ func (s *backupState) remember(run domain.BackupRunRecord) { | |||
| 54 | 57 | if prev, ok := s.last[key]; ok && !run.StartedAt.After(prev.at) { | |
| 55 | 58 | return | |
| 56 | 59 | } | |
| 57 | - | s.last[key] = backupLastSuccess{at: run.StartedAt, size: run.SizeBytes} | |
| 60 | + | s.last[key] = backupLastSuccess{at: run.StartedAt, size: run.SizeBytes, updated: s.now()} | |
| 58 | 61 | } | |
| 59 | 62 | ||
| 60 | - | // retain drops the last success of every target of kind not in targets. | |
| 61 | - | func (s *backupState) retain(kind domain.BackupKind, targets []domain.BackupTargetRef) { | |
| 63 | + | // retain drops the last success of every target of kind not in targets, | |
| 64 | + | // unless it was updated after asOf. | |
| 65 | + | func (s *backupState) retain(kind domain.BackupKind, targets []domain.BackupTargetRef, asOf time.Time) { | |
| 62 | 66 | keep := make(map[domain.BackupTargetRef]struct{}, len(targets)) | |
| 63 | 67 | for _, ref := range targets { | |
| 64 | 68 | keep[ref] = struct{}{} | |
| 65 | 69 | } | |
| 66 | 70 | s.mu.Lock() | |
| 67 | 71 | defer s.mu.Unlock() | |
| 68 | - | for key := range s.last { | |
| 69 | - | if _, ok := keep[key]; key.Kind == kind && !ok { | |
| 70 | - | delete(s.last, key) | |
| 72 | + | for key, last := range s.last { | |
| 73 | + | if _, ok := keep[key]; key.Kind != kind || ok || last.updated.After(asOf) { | |
| 74 | + | continue | |
| 71 | 75 | } | |
| 76 | + | delete(s.last, key) | |
| 72 | 77 | } | |
| 73 | 78 | } | |
| 74 | 79 | ||
| @@ -140,8 +145,8 @@ func (m *Metrics) ObserveStoredBackup(run domain.BackupRunRecord) { | |||
| 140 | 145 | } | |
| 141 | 146 | ||
| 142 | 147 | // RetainBackupTargets implements out.BackupMetrics. | |
| 143 | - | func (m *Metrics) RetainBackupTargets(kind domain.BackupKind, targets []domain.BackupTargetRef) { | |
| 144 | - | m.backups.retain(kind, targets) | |
| 148 | + | func (m *Metrics) RetainBackupTargets(kind domain.BackupKind, targets []domain.BackupTargetRef, asOf time.Time) { | |
| 149 | + | m.backups.retain(kind, targets, asOf) | |
| 145 | 150 | } | |
| 146 | 151 | ||
| 147 | 152 | // ObserveBackupNextRun exports the next planned run of one backup kind. | |
| @@ -24,13 +24,15 @@ func targetAttrs(ref domain.BackupTargetRef) []attribute.KeyValue { | |||||
| 24 | } | 24 | } | ||
| 25 | 25 | ||||
| 26 | type backupLastSuccess struct { | 26 | type backupLastSuccess struct { | ||
| 27 | - | at time.Time | 27 | + | at time.Time |
| 28 | - | size int64 | 28 | + | size int64 |
| 29 | + | updated time.Time // when this entry was written, for retain | |||
| 29 | } | 30 | } | ||
| 30 | 31 | ||||
| 31 | // backupState holds the last success per target, exported by observable | 32 | // backupState holds the last success per target, exported by observable | ||
| 32 | // gauges at each collection. | 33 | // gauges at each collection. | ||
| 33 | type backupState struct { | 34 | type backupState struct { | ||
| 35 | + | now func() time.Time | |||
| 34 | mu sync.Mutex | 36 | mu sync.Mutex | ||
| 35 | last map[domain.BackupTargetRef]backupLastSuccess | 37 | last map[domain.BackupTargetRef]backupLastSuccess | ||
| 36 | nextRun map[domain.BackupKind]func() time.Time | 38 | nextRun map[domain.BackupKind]func() time.Time | ||
| @@ -38,6 +40,7 @@ type backupState struct { | |||||
| 38 | 40 | ||||
| 39 | func newBackupState() *backupState { | 41 | func newBackupState() *backupState { | ||
| 40 | return &backupState{ | 42 | return &backupState{ | ||
| 43 | + | now: time.Now, | |||
| 41 | last: map[domain.BackupTargetRef]backupLastSuccess{}, | 44 | last: map[domain.BackupTargetRef]backupLastSuccess{}, | ||
| 42 | nextRun: map[domain.BackupKind]func() time.Time{}, | 45 | nextRun: map[domain.BackupKind]func() time.Time{}, | ||
| 43 | } | 46 | } | ||
| @@ -54,21 +57,23 @@ func (s *backupState) remember(run domain.BackupRunRecord) { | |||||
| 54 | if prev, ok := s.last[key]; ok && !run.StartedAt.After(prev.at) { | 57 | if prev, ok := s.last[key]; ok && !run.StartedAt.After(prev.at) { | ||
| 55 | return | 58 | return | ||
| 56 | } | 59 | } | ||
| 57 | - | s.last[key] = backupLastSuccess{at: run.StartedAt, size: run.SizeBytes} | 60 | + | s.last[key] = backupLastSuccess{at: run.StartedAt, size: run.SizeBytes, updated: s.now()} |
| 58 | } | 61 | } | ||
| 59 | 62 | ||||
| 60 | - | // retain drops the last success of every target of kind not in targets. | 63 | + | // retain drops the last success of every target of kind not in targets, |
| 61 | - | func (s *backupState) retain(kind domain.BackupKind, targets []domain.BackupTargetRef) { | 64 | + | // unless it was updated after asOf. |
| 65 | + | func (s *backupState) retain(kind domain.BackupKind, targets []domain.BackupTargetRef, asOf time.Time) { | |||
| 62 | keep := make(map[domain.BackupTargetRef]struct{}, len(targets)) | 66 | keep := make(map[domain.BackupTargetRef]struct{}, len(targets)) | ||
| 63 | for _, ref := range targets { | 67 | for _, ref := range targets { | ||
| 64 | keep[ref] = struct{}{} | 68 | keep[ref] = struct{}{} | ||
| 65 | } | 69 | } | ||
| 66 | s.mu.Lock() | 70 | s.mu.Lock() | ||
| 67 | defer s.mu.Unlock() | 71 | defer s.mu.Unlock() | ||
| 68 | - | for key := range s.last { | 72 | + | for key, last := range s.last { |
| 69 | - | if _, ok := keep[key]; key.Kind == kind && !ok { | 73 | + | if _, ok := keep[key]; key.Kind != kind || ok || last.updated.After(asOf) { |
| 70 | - | delete(s.last, key) | 74 | + | continue |
| 71 | } | 75 | } | ||
| 76 | + | delete(s.last, key) | |||
| 72 | } | 77 | } | ||
| 73 | } | 78 | } | ||
| 74 | 79 | ||||
| @@ -140,8 +145,8 @@ func (m *Metrics) ObserveStoredBackup(run domain.BackupRunRecord) { | |||||
| 140 | } | 145 | } | ||
| 141 | 146 | ||||
| 142 | // RetainBackupTargets implements out.BackupMetrics. | 147 | // RetainBackupTargets implements out.BackupMetrics. | ||
| 143 | - | func (m *Metrics) RetainBackupTargets(kind domain.BackupKind, targets []domain.BackupTargetRef) { | 148 | + | func (m *Metrics) RetainBackupTargets(kind domain.BackupKind, targets []domain.BackupTargetRef, asOf time.Time) { |
| 144 | - | m.backups.retain(kind, targets) | 149 | + | m.backups.retain(kind, targets, asOf) |
| 145 | } | 150 | } | ||
| 146 | 151 | ||||
| 147 | // ObserveBackupNextRun exports the next planned run of one backup kind. | 152 | // ObserveBackupNextRun exports the next planned run of one backup kind. | ||
M internal/adapters/out/telemetry/backup_metrics_test.go +18 -1
| @@ -131,12 +131,29 @@ func TestRetainBackupTargets_ForgetsUndeclaredTargetsOfThatKindOnly(t *testing.T | |||
| 131 | 131 | m.ObserveStoredBackup(domain.BackupRunRecord{BackupTargetRef: ref, Succeeded: true, StartedAt: at}) | |
| 132 | 132 | } | |
| 133 | 133 | ||
| 134 | - | m.RetainBackupTargets(domain.BackupKindVolume, []domain.BackupTargetRef{keep}) | |
| 134 | + | m.RetainBackupTargets(domain.BackupKindVolume, []domain.BackupTargetRef{keep}, time.Now()) | |
| 135 | 135 | ||
| 136 | 136 | assert.Equal(t, map[string]float64{"data": float64(at.Unix()), "orders": float64(at.Unix())}, | |
| 137 | 137 | gaugeByTarget(t, collect(t, reader)["gordon.backup.last_success_timestamp_seconds"])) | |
| 138 | 138 | } | |
| 139 | 139 | ||
| 140 | + | // TestRetainBackupTargets_KeepsSuccessRecordedAfterTheSnapshot covers a | |
| 141 | + | // target declared and backed up while seeding read an older declaration. | |
| 142 | + | func TestRetainBackupTargets_KeepsSuccessRecordedAfterTheSnapshot(t *testing.T) { | |
| 143 | + | m, reader := newTestMetrics(t) | |
| 144 | + | clock := time.Date(2026, 10, 17, 8, 0, 0, 0, time.UTC) | |
| 145 | + | m.backups.now = func() time.Time { return clock } | |
| 146 | + | snapshot := clock | |
| 147 | + | clock = clock.Add(time.Second) | |
| 148 | + | fresh := domain.BackupTargetRef{Kind: domain.BackupKindVolume, App: "new", Service: "api", Target: "data"} | |
| 149 | + | m.RecordBackupRun(context.Background(), domain.BackupRunRecord{BackupTargetRef: fresh, Succeeded: true, StartedAt: clock}) | |
| 150 | + | ||
| 151 | + | m.RetainBackupTargets(domain.BackupKindVolume, nil, snapshot) | |
| 152 | + | ||
| 153 | + | assert.Equal(t, map[string]float64{"data": float64(clock.Unix())}, | |
| 154 | + | gaugeByTarget(t, collect(t, reader)["gordon.backup.last_success_timestamp_seconds"])) | |
| 155 | + | } | |
| 156 | + | ||
| 140 | 157 | func TestObserveBackupNextRun(t *testing.T) { | |
| 141 | 158 | m, reader := newTestMetrics(t) | |
| 142 | 159 | next := time.Date(2026, 10, 18, 1, 30, 0, 0, time.UTC) | |
| @@ -131,12 +131,29 @@ func TestRetainBackupTargets_ForgetsUndeclaredTargetsOfThatKindOnly(t *testing.T | |||||
| 131 | m.ObserveStoredBackup(domain.BackupRunRecord{BackupTargetRef: ref, Succeeded: true, StartedAt: at}) | 131 | m.ObserveStoredBackup(domain.BackupRunRecord{BackupTargetRef: ref, Succeeded: true, StartedAt: at}) | ||
| 132 | } | 132 | } | ||
| 133 | 133 | ||||
| 134 | - | m.RetainBackupTargets(domain.BackupKindVolume, []domain.BackupTargetRef{keep}) | 134 | + | m.RetainBackupTargets(domain.BackupKindVolume, []domain.BackupTargetRef{keep}, time.Now()) |
| 135 | 135 | ||||
| 136 | assert.Equal(t, map[string]float64{"data": float64(at.Unix()), "orders": float64(at.Unix())}, | 136 | assert.Equal(t, map[string]float64{"data": float64(at.Unix()), "orders": float64(at.Unix())}, | ||
| 137 | gaugeByTarget(t, collect(t, reader)["gordon.backup.last_success_timestamp_seconds"])) | 137 | gaugeByTarget(t, collect(t, reader)["gordon.backup.last_success_timestamp_seconds"])) | ||
| 138 | } | 138 | } | ||
| 139 | 139 | ||||
| 140 | + | // TestRetainBackupTargets_KeepsSuccessRecordedAfterTheSnapshot covers a | |||
| 141 | + | // target declared and backed up while seeding read an older declaration. | |||
| 142 | + | func TestRetainBackupTargets_KeepsSuccessRecordedAfterTheSnapshot(t *testing.T) { | |||
| 143 | + | m, reader := newTestMetrics(t) | |||
| 144 | + | clock := time.Date(2026, 10, 17, 8, 0, 0, 0, time.UTC) | |||
| 145 | + | m.backups.now = func() time.Time { return clock } | |||
| 146 | + | snapshot := clock | |||
| 147 | + | clock = clock.Add(time.Second) | |||
| 148 | + | fresh := domain.BackupTargetRef{Kind: domain.BackupKindVolume, App: "new", Service: "api", Target: "data"} | |||
| 149 | + | m.RecordBackupRun(context.Background(), domain.BackupRunRecord{BackupTargetRef: fresh, Succeeded: true, StartedAt: clock}) | |||
| 150 | + | ||||
| 151 | + | m.RetainBackupTargets(domain.BackupKindVolume, nil, snapshot) | |||
| 152 | + | ||||
| 153 | + | assert.Equal(t, map[string]float64{"data": float64(clock.Unix())}, | |||
| 154 | + | gaugeByTarget(t, collect(t, reader)["gordon.backup.last_success_timestamp_seconds"])) | |||
| 155 | + | } | |||
| 156 | + | ||||
| 140 | func TestObserveBackupNextRun(t *testing.T) { | 157 | func TestObserveBackupNextRun(t *testing.T) { | ||
| 141 | m, reader := newTestMetrics(t) | 158 | m, reader := newTestMetrics(t) | ||
| 142 | next := time.Date(2026, 10, 18, 1, 30, 0, 0, time.UTC) | 159 | next := time.Date(2026, 10, 18, 1, 30, 0, 0, time.UTC) | ||
M internal/boundaries/out/backup_metrics.go +4 -2
| @@ -2,6 +2,7 @@ package out | |||
| 2 | 2 | ||
| 3 | 3 | import ( | |
| 4 | 4 | "context" | |
| 5 | + | "time" | |
| 5 | 6 | ||
| 6 | 7 | "github.com/bnema/gordon/internal/domain" | |
| 7 | 8 | ) | |
| @@ -18,6 +19,7 @@ type BackupMetrics interface { | |||
| 18 | 19 | ObserveStoredBackup(run domain.BackupRunRecord) | |
| 19 | 20 | // RetainBackupTargets forgets the last success of every target of kind | |
| 20 | 21 | // that is not in targets, so a removed app or backup declaration stops | |
| 21 | - | // being reported. | |
| 22 | - | RetainBackupTargets(kind domain.BackupKind, targets []domain.BackupTargetRef) | |
| 22 | + | // being reported. targets is the declaration read at asOf: a target | |
| 23 | + | // updated after asOf is kept, since it may have been declared since. | |
| 24 | + | RetainBackupTargets(kind domain.BackupKind, targets []domain.BackupTargetRef, asOf time.Time) | |
| 23 | 25 | } | |
| @@ -2,6 +2,7 @@ package out | |||||
| 2 | 2 | ||||
| 3 | import ( | 3 | import ( | ||
| 4 | "context" | 4 | "context" | ||
| 5 | + | "time" | |||
| 5 | 6 | ||||
| 6 | "github.com/bnema/gordon/internal/domain" | 7 | "github.com/bnema/gordon/internal/domain" | ||
| 7 | ) | 8 | ) | ||
| @@ -18,6 +19,7 @@ type BackupMetrics interface { | |||||
| 18 | ObserveStoredBackup(run domain.BackupRunRecord) | 19 | ObserveStoredBackup(run domain.BackupRunRecord) | ||
| 19 | // RetainBackupTargets forgets the last success of every target of kind | 20 | // RetainBackupTargets forgets the last success of every target of kind | ||
| 20 | // that is not in targets, so a removed app or backup declaration stops | 21 | // that is not in targets, so a removed app or backup declaration stops | ||
| 21 | - | // being reported. | 22 | + | // being reported. targets is the declaration read at asOf: a target |
| 22 | - | RetainBackupTargets(kind domain.BackupKind, targets []domain.BackupTargetRef) | 23 | + | // updated after asOf is kept, since it may have been declared since. |
| 24 | + | RetainBackupTargets(kind domain.BackupKind, targets []domain.BackupTargetRef, asOf time.Time) | |||
| 23 | } | 25 | } | ||
M internal/boundaries/out/mocks/mock_backup_metrics.go +13 -6
| @@ -6,6 +6,7 @@ package mocks | |||
| 6 | 6 | ||
| 7 | 7 | import ( | |
| 8 | 8 | "context" | |
| 9 | + | "time" | |
| 9 | 10 | ||
| 10 | 11 | "github.com/bnema/gordon/internal/domain" | |
| 11 | 12 | mock "github.com/stretchr/testify/mock" | |
| @@ -134,8 +135,8 @@ func (_c *MockBackupMetrics_RecordBackupRun_Call) RunAndReturn(run func(ctx cont | |||
| 134 | 135 | } | |
| 135 | 136 | ||
| 136 | 137 | // RetainBackupTargets provides a mock function for the type MockBackupMetrics | |
| 137 | - | func (_mock *MockBackupMetrics) RetainBackupTargets(kind domain.BackupKind, targets []domain.BackupTargetRef) { | |
| 138 | - | _mock.Called(kind, targets) | |
| 138 | + | func (_mock *MockBackupMetrics) RetainBackupTargets(kind domain.BackupKind, targets []domain.BackupTargetRef, asOf time.Time) { | |
| 139 | + | _mock.Called(kind, targets, asOf) | |
| 139 | 140 | return | |
| 140 | 141 | } | |
| 141 | 142 | ||
| @@ -147,11 +148,12 @@ type MockBackupMetrics_RetainBackupTargets_Call struct { | |||
| 147 | 148 | // RetainBackupTargets is a helper method to define mock.On call | |
| 148 | 149 | // - kind domain.BackupKind | |
| 149 | 150 | // - targets []domain.BackupTargetRef | |
| 150 | - | func (_e *MockBackupMetrics_Expecter) RetainBackupTargets(kind any, targets any) *MockBackupMetrics_RetainBackupTargets_Call { | |
| 151 | - | return &MockBackupMetrics_RetainBackupTargets_Call{Call: _e.mock.On("RetainBackupTargets", kind, targets)} | |
| 151 | + | // - asOf time.Time | |
| 152 | + | func (_e *MockBackupMetrics_Expecter) RetainBackupTargets(kind any, targets any, asOf any) *MockBackupMetrics_RetainBackupTargets_Call { | |
| 153 | + | return &MockBackupMetrics_RetainBackupTargets_Call{Call: _e.mock.On("RetainBackupTargets", kind, targets, asOf)} | |
| 152 | 154 | } | |
| 153 | 155 | ||
| 154 | - | func (_c *MockBackupMetrics_RetainBackupTargets_Call) Run(run func(kind domain.BackupKind, targets []domain.BackupTargetRef)) *MockBackupMetrics_RetainBackupTargets_Call { | |
| 156 | + | func (_c *MockBackupMetrics_RetainBackupTargets_Call) Run(run func(kind domain.BackupKind, targets []domain.BackupTargetRef, asOf time.Time)) *MockBackupMetrics_RetainBackupTargets_Call { | |
| 155 | 157 | _c.Call.Run(func(args mock.Arguments) { | |
| 156 | 158 | var arg0 domain.BackupKind | |
| 157 | 159 | if args[0] != nil { | |
| @@ -161,9 +163,14 @@ func (_c *MockBackupMetrics_RetainBackupTargets_Call) Run(run func(kind domain.B | |||
| 161 | 163 | if args[1] != nil { | |
| 162 | 164 | arg1 = args[1].([]domain.BackupTargetRef) | |
| 163 | 165 | } | |
| 166 | + | var arg2 time.Time | |
| 167 | + | if args[2] != nil { | |
| 168 | + | arg2 = args[2].(time.Time) | |
| 169 | + | } | |
| 164 | 170 | run( | |
| 165 | 171 | arg0, | |
| 166 | 172 | arg1, | |
| 173 | + | arg2, | |
| 167 | 174 | ) | |
| 168 | 175 | }) | |
| 169 | 176 | return _c | |
| @@ -174,7 +181,7 @@ func (_c *MockBackupMetrics_RetainBackupTargets_Call) Return() *MockBackupMetric | |||
| 174 | 181 | return _c | |
| 175 | 182 | } | |
| 176 | 183 | ||
| 177 | - | func (_c *MockBackupMetrics_RetainBackupTargets_Call) RunAndReturn(run func(kind domain.BackupKind, targets []domain.BackupTargetRef)) *MockBackupMetrics_RetainBackupTargets_Call { | |
| 184 | + | func (_c *MockBackupMetrics_RetainBackupTargets_Call) RunAndReturn(run func(kind domain.BackupKind, targets []domain.BackupTargetRef, asOf time.Time)) *MockBackupMetrics_RetainBackupTargets_Call { | |
| 178 | 185 | _c.Run(run) | |
| 179 | 186 | return _c | |
| 180 | 187 | } | |
| @@ -6,6 +6,7 @@ package mocks | |||||
| 6 | 6 | ||||
| 7 | import ( | 7 | import ( | ||
| 8 | "context" | 8 | "context" | ||
| 9 | + | "time" | |||
| 9 | 10 | ||||
| 10 | "github.com/bnema/gordon/internal/domain" | 11 | "github.com/bnema/gordon/internal/domain" | ||
| 11 | mock "github.com/stretchr/testify/mock" | 12 | mock "github.com/stretchr/testify/mock" | ||
| @@ -134,8 +135,8 @@ func (_c *MockBackupMetrics_RecordBackupRun_Call) RunAndReturn(run func(ctx cont | |||||
| 134 | } | 135 | } | ||
| 135 | 136 | ||||
| 136 | // RetainBackupTargets provides a mock function for the type MockBackupMetrics | 137 | // RetainBackupTargets provides a mock function for the type MockBackupMetrics | ||
| 137 | - | func (_mock *MockBackupMetrics) RetainBackupTargets(kind domain.BackupKind, targets []domain.BackupTargetRef) { | 138 | + | func (_mock *MockBackupMetrics) RetainBackupTargets(kind domain.BackupKind, targets []domain.BackupTargetRef, asOf time.Time) { |
| 138 | - | _mock.Called(kind, targets) | 139 | + | _mock.Called(kind, targets, asOf) |
| 139 | return | 140 | return | ||
| 140 | } | 141 | } | ||
| 141 | 142 | ||||
| @@ -147,11 +148,12 @@ type MockBackupMetrics_RetainBackupTargets_Call struct { | |||||
| 147 | // RetainBackupTargets is a helper method to define mock.On call | 148 | // RetainBackupTargets is a helper method to define mock.On call | ||
| 148 | // - kind domain.BackupKind | 149 | // - kind domain.BackupKind | ||
| 149 | // - targets []domain.BackupTargetRef | 150 | // - targets []domain.BackupTargetRef | ||
| 150 | - | func (_e *MockBackupMetrics_Expecter) RetainBackupTargets(kind any, targets any) *MockBackupMetrics_RetainBackupTargets_Call { | 151 | + | // - asOf time.Time |
| 151 | - | return &MockBackupMetrics_RetainBackupTargets_Call{Call: _e.mock.On("RetainBackupTargets", kind, targets)} | 152 | + | func (_e *MockBackupMetrics_Expecter) RetainBackupTargets(kind any, targets any, asOf any) *MockBackupMetrics_RetainBackupTargets_Call { |
| 153 | + | return &MockBackupMetrics_RetainBackupTargets_Call{Call: _e.mock.On("RetainBackupTargets", kind, targets, asOf)} | |||
| 152 | } | 154 | } | ||
| 153 | 155 | ||||
| 154 | - | func (_c *MockBackupMetrics_RetainBackupTargets_Call) Run(run func(kind domain.BackupKind, targets []domain.BackupTargetRef)) *MockBackupMetrics_RetainBackupTargets_Call { | 156 | + | func (_c *MockBackupMetrics_RetainBackupTargets_Call) Run(run func(kind domain.BackupKind, targets []domain.BackupTargetRef, asOf time.Time)) *MockBackupMetrics_RetainBackupTargets_Call { |
| 155 | _c.Call.Run(func(args mock.Arguments) { | 157 | _c.Call.Run(func(args mock.Arguments) { | ||
| 156 | var arg0 domain.BackupKind | 158 | var arg0 domain.BackupKind | ||
| 157 | if args[0] != nil { | 159 | if args[0] != nil { | ||
| @@ -161,9 +163,14 @@ func (_c *MockBackupMetrics_RetainBackupTargets_Call) Run(run func(kind domain.B | |||||
| 161 | if args[1] != nil { | 163 | if args[1] != nil { | ||
| 162 | arg1 = args[1].([]domain.BackupTargetRef) | 164 | arg1 = args[1].([]domain.BackupTargetRef) | ||
| 163 | } | 165 | } | ||
| 166 | + | var arg2 time.Time | |||
| 167 | + | if args[2] != nil { | |||
| 168 | + | arg2 = args[2].(time.Time) | |||
| 169 | + | } | |||
| 164 | run( | 170 | run( | ||
| 165 | arg0, | 171 | arg0, | ||
| 166 | arg1, | 172 | arg1, | ||
| 173 | + | arg2, | |||
| 167 | ) | 174 | ) | ||
| 168 | }) | 175 | }) | ||
| 169 | return _c | 176 | return _c | ||
| @@ -174,7 +181,7 @@ func (_c *MockBackupMetrics_RetainBackupTargets_Call) Return() *MockBackupMetric | |||||
| 174 | return _c | 181 | return _c | ||
| 175 | } | 182 | } | ||
| 176 | 183 | ||||
| 177 | - | func (_c *MockBackupMetrics_RetainBackupTargets_Call) RunAndReturn(run func(kind domain.BackupKind, targets []domain.BackupTargetRef)) *MockBackupMetrics_RetainBackupTargets_Call { | 184 | + | func (_c *MockBackupMetrics_RetainBackupTargets_Call) RunAndReturn(run func(kind domain.BackupKind, targets []domain.BackupTargetRef, asOf time.Time)) *MockBackupMetrics_RetainBackupTargets_Call { |
| 178 | _c.Run(run) | 185 | _c.Run(run) | ||
| 179 | return _c | 186 | return _c | ||
| 180 | } | 187 | } | ||
M internal/usecase/backup/metrics_test.go +2 -2
| @@ -87,7 +87,7 @@ func TestVolumeService_SeedMetrics(t *testing.T) { | |||
| 87 | 87 | {App: "gone", VolumeName: "old", StartedAt: started, SizeBytes: 1}, | |
| 88 | 88 | }} | |
| 89 | 89 | metrics := outmocks.NewMockBackupMetrics(t) | |
| 90 | - | metrics.EXPECT().RetainBackupTargets(domain.BackupKindVolume, []domain.BackupTargetRef{shopData}).Once() | |
| 90 | + | metrics.EXPECT().RetainBackupTargets(domain.BackupKindVolume, []domain.BackupTargetRef{shopData}, mock.Anything).Once() | |
| 91 | 91 | metrics.EXPECT().ObserveStoredBackup(domain.BackupRunRecord{ | |
| 92 | 92 | BackupTargetRef: shopData, Succeeded: true, SizeBytes: 42, StartedAt: started, | |
| 93 | 93 | }).Once() | |
| @@ -166,7 +166,7 @@ func TestService_SeedMetrics(t *testing.T) { | |||
| 166 | 166 | {App: "shop", Service: "api", DBName: "orders", StartedAt: started, SizeBytes: 7}, | |
| 167 | 167 | {App: "shop", Service: "api", DBName: "dropped", StartedAt: started, SizeBytes: 1}, | |
| 168 | 168 | }, nil).Once() | |
| 169 | - | metrics.EXPECT().RetainBackupTargets(domain.BackupKindDatabase, []domain.BackupTargetRef{shopOrders}).Once() | |
| 169 | + | metrics.EXPECT().RetainBackupTargets(domain.BackupKindDatabase, []domain.BackupTargetRef{shopOrders}, mock.Anything).Once() | |
| 170 | 170 | metrics.EXPECT().ObserveStoredBackup(domain.BackupRunRecord{ | |
| 171 | 171 | BackupTargetRef: shopOrders, Succeeded: true, SizeBytes: 7, StartedAt: started, | |
| 172 | 172 | }).Once() | |
| @@ -87,7 +87,7 @@ func TestVolumeService_SeedMetrics(t *testing.T) { | |||||
| 87 | {App: "gone", VolumeName: "old", StartedAt: started, SizeBytes: 1}, | 87 | {App: "gone", VolumeName: "old", StartedAt: started, SizeBytes: 1}, | ||
| 88 | }} | 88 | }} | ||
| 89 | metrics := outmocks.NewMockBackupMetrics(t) | 89 | metrics := outmocks.NewMockBackupMetrics(t) | ||
| 90 | - | metrics.EXPECT().RetainBackupTargets(domain.BackupKindVolume, []domain.BackupTargetRef{shopData}).Once() | 90 | + | metrics.EXPECT().RetainBackupTargets(domain.BackupKindVolume, []domain.BackupTargetRef{shopData}, mock.Anything).Once() |
| 91 | metrics.EXPECT().ObserveStoredBackup(domain.BackupRunRecord{ | 91 | metrics.EXPECT().ObserveStoredBackup(domain.BackupRunRecord{ | ||
| 92 | BackupTargetRef: shopData, Succeeded: true, SizeBytes: 42, StartedAt: started, | 92 | BackupTargetRef: shopData, Succeeded: true, SizeBytes: 42, StartedAt: started, | ||
| 93 | }).Once() | 93 | }).Once() | ||
| @@ -166,7 +166,7 @@ func TestService_SeedMetrics(t *testing.T) { | |||||
| 166 | {App: "shop", Service: "api", DBName: "orders", StartedAt: started, SizeBytes: 7}, | 166 | {App: "shop", Service: "api", DBName: "orders", StartedAt: started, SizeBytes: 7}, | ||
| 167 | {App: "shop", Service: "api", DBName: "dropped", StartedAt: started, SizeBytes: 1}, | 167 | {App: "shop", Service: "api", DBName: "dropped", StartedAt: started, SizeBytes: 1}, | ||
| 168 | }, nil).Once() | 168 | }, nil).Once() | ||
| 169 | - | metrics.EXPECT().RetainBackupTargets(domain.BackupKindDatabase, []domain.BackupTargetRef{shopOrders}).Once() | 169 | + | metrics.EXPECT().RetainBackupTargets(domain.BackupKindDatabase, []domain.BackupTargetRef{shopOrders}, mock.Anything).Once() |
| 170 | metrics.EXPECT().ObserveStoredBackup(domain.BackupRunRecord{ | 170 | metrics.EXPECT().ObserveStoredBackup(domain.BackupRunRecord{ | ||
| 171 | BackupTargetRef: shopOrders, Succeeded: true, SizeBytes: 7, StartedAt: started, | 171 | BackupTargetRef: shopOrders, Succeeded: true, SizeBytes: 7, StartedAt: started, | ||
| 172 | }).Once() | 172 | }).Once() | ||
M internal/usecase/backup/service.go +2 -1
| @@ -68,6 +68,7 @@ func (s *Service) SeedMetrics(ctx context.Context) error { | |||
| 68 | 68 | if s.metrics == nil { | |
| 69 | 69 | return nil | |
| 70 | 70 | } | |
| 71 | + | asOf := time.Now() | |
| 71 | 72 | targets, err := s.allTargets(ctx) | |
| 72 | 73 | if err != nil { | |
| 73 | 74 | return fmt.Errorf("seed backup metrics: %w", err) | |
| @@ -79,7 +80,7 @@ func (s *Service) SeedMetrics(ctx context.Context) error { | |||
| 79 | 80 | refs = append(refs, ref) | |
| 80 | 81 | declared[ref] = struct{}{} | |
| 81 | 82 | } | |
| 82 | - | s.metrics.RetainBackupTargets(domain.BackupKindDatabase, refs) | |
| 83 | + | s.metrics.RetainBackupTargets(domain.BackupKindDatabase, refs, asOf) | |
| 83 | 84 | ||
| 84 | 85 | jobs, err := s.ListBackups(ctx, "") | |
| 85 | 86 | if err != nil { | |
| @@ -68,6 +68,7 @@ func (s *Service) SeedMetrics(ctx context.Context) error { | |||||
| 68 | if s.metrics == nil { | 68 | if s.metrics == nil { | ||
| 69 | return nil | 69 | return nil | ||
| 70 | } | 70 | } | ||
| 71 | + | asOf := time.Now() | |||
| 71 | targets, err := s.allTargets(ctx) | 72 | targets, err := s.allTargets(ctx) | ||
| 72 | if err != nil { | 73 | if err != nil { | ||
| 73 | return fmt.Errorf("seed backup metrics: %w", err) | 74 | return fmt.Errorf("seed backup metrics: %w", err) | ||
| @@ -79,7 +80,7 @@ func (s *Service) SeedMetrics(ctx context.Context) error { | |||||
| 79 | refs = append(refs, ref) | 80 | refs = append(refs, ref) | ||
| 80 | declared[ref] = struct{}{} | 81 | declared[ref] = struct{}{} | ||
| 81 | } | 82 | } | ||
| 82 | - | s.metrics.RetainBackupTargets(domain.BackupKindDatabase, refs) | 83 | + | s.metrics.RetainBackupTargets(domain.BackupKindDatabase, refs, asOf) |
| 83 | 84 | ||||
| 84 | jobs, err := s.ListBackups(ctx, "") | 85 | jobs, err := s.ListBackups(ctx, "") | ||
| 85 | if err != nil { | 86 | if err != nil { | ||
M internal/usecase/backup/volume_service.go +2 -1
| @@ -64,6 +64,7 @@ func (s *VolumeService) SeedMetrics(ctx context.Context) error { | |||
| 64 | 64 | if s.metrics == nil { | |
| 65 | 65 | return nil | |
| 66 | 66 | } | |
| 67 | + | asOf := time.Now() | |
| 67 | 68 | targets, err := s.allVolumeTargets(ctx) | |
| 68 | 69 | if err != nil { | |
| 69 | 70 | return fmt.Errorf("seed volume backup metrics: %w", err) | |
| @@ -75,7 +76,7 @@ func (s *VolumeService) SeedMetrics(ctx context.Context) error { | |||
| 75 | 76 | refs = append(refs, ref) | |
| 76 | 77 | byVolume[target.App+"/"+target.VolumeName] = ref | |
| 77 | 78 | } | |
| 78 | - | s.metrics.RetainBackupTargets(domain.BackupKindVolume, refs) | |
| 79 | + | s.metrics.RetainBackupTargets(domain.BackupKindVolume, refs, asOf) | |
| 79 | 80 | ||
| 80 | 81 | archives, err := s.storage.LatestVolumeArchives(ctx) | |
| 81 | 82 | if err != nil { | |
| @@ -64,6 +64,7 @@ func (s *VolumeService) SeedMetrics(ctx context.Context) error { | |||||
| 64 | if s.metrics == nil { | 64 | if s.metrics == nil { | ||
| 65 | return nil | 65 | return nil | ||
| 66 | } | 66 | } | ||
| 67 | + | asOf := time.Now() | |||
| 67 | targets, err := s.allVolumeTargets(ctx) | 68 | targets, err := s.allVolumeTargets(ctx) | ||
| 68 | if err != nil { | 69 | if err != nil { | ||
| 69 | return fmt.Errorf("seed volume backup metrics: %w", err) | 70 | return fmt.Errorf("seed volume backup metrics: %w", err) | ||
| @@ -75,7 +76,7 @@ func (s *VolumeService) SeedMetrics(ctx context.Context) error { | |||||
| 75 | refs = append(refs, ref) | 76 | refs = append(refs, ref) | ||
| 76 | byVolume[target.App+"/"+target.VolumeName] = ref | 77 | byVolume[target.App+"/"+target.VolumeName] = ref | ||
| 77 | } | 78 | } | ||
| 78 | - | s.metrics.RetainBackupTargets(domain.BackupKindVolume, refs) | 79 | + | s.metrics.RetainBackupTargets(domain.BackupKindVolume, refs, asOf) |
| 79 | 80 | ||||
| 80 | archives, err := s.storage.LatestVolumeArchives(ctx) | 81 | archives, err := s.storage.LatestVolumeArchives(ctx) | ||
| 81 | if err != nil { | 82 | if err != nil { | ||