Ignore status reports older than the stored generation
ci/woodpecker/pr/pre-commit Pipeline was successful
ci/woodpecker/pr/build Pipeline was successful
ci/woodpecker/pr/test Pipeline was successful

This commit is contained in:
2026-10-05 13:56:23 +11:00
parent f2197c2189
commit 7245ff6643
2 changed files with 29 additions and 5 deletions
+8 -4
View File
@@ -281,13 +281,17 @@ func (s *Store) UpsertDevice(ctx context.Context, d model.Device) error {
}
// RecordDeviceStatus stores an agent's apply report. reported_generation only
// advances on applied; every report updates the last-status columns.
// advances on applied; the last-status columns only take reports at or above
// the stored status generation, so late or replayed reports never clobber newer state.
func (s *Store) RecordDeviceStatus(ctx context.Context, name string, r model.StatusReport) error {
tag, err := s.pool.Exec(ctx, `
UPDATE devices SET
reported_generation = CASE WHEN $2 = 'applied' THEN $3 ELSE reported_generation END,
status = $2, status_generation = $3, status_error = $4,
status_at = now(), last_seen = now()
reported_generation = CASE WHEN $2 = 'applied' THEN GREATEST(reported_generation, $3) ELSE reported_generation END,
status = CASE WHEN $3 >= status_generation THEN $2 ELSE status END,
status_error = CASE WHEN $3 >= status_generation THEN $4 ELSE status_error END,
status_at = CASE WHEN $3 >= status_generation THEN now() ELSE status_at END,
status_generation = GREATEST(status_generation, $3),
last_seen = now()
WHERE name = $1`,
name, string(r.Status), r.Generation, r.Error)
if err != nil {
+21 -1
View File
@@ -196,7 +196,27 @@ func TestRecordDeviceStatus(t *testing.T) {
t.Errorf("failed must not advance applied generation: %+v", got)
}
if err := s.RecordDeviceStatus(ctx, "fw1", model.StatusReport{Status: "bogus", Generation: 9}); err == nil {
got = report(model.StatusReport{Status: model.StatusApplied, Generation: 9})
if got.AppliedGeneration != 9 || got.Status != model.StatusApplied || got.Generation != 9 {
t.Errorf("applied 9: %+v", got)
}
got = report(model.StatusReport{Status: model.StatusApplied, Generation: 4})
if got.AppliedGeneration != 9 || got.Status != model.StatusApplied || got.Generation != 9 {
t.Errorf("older applied must not regress: %+v", got)
}
got = report(model.StatusReport{Status: model.StatusReverted, Generation: 8, Error: "stale"})
if got.AppliedGeneration != 9 || got.Status != model.StatusApplied || got.Generation != 9 || got.Error != "" {
t.Errorf("older reverted must not overwrite newer status: %+v", got)
}
got = report(model.StatusReport{Status: model.StatusReverted, Generation: 9, Error: "lost api"})
if got.AppliedGeneration != 9 || got.Status != model.StatusReverted || got.Generation != 9 || got.Error != "lost api" {
t.Errorf("same-generation reverted after applied must update: %+v", got)
}
if err := s.RecordDeviceStatus(ctx, "fw1", model.StatusReport{Status: "bogus", Generation: 10}); err == nil {
t.Error("db should reject unknown status")
}
if err := s.RecordDeviceStatus(ctx, "nope", model.StatusReport{Status: model.StatusApplied}); err != store.ErrNotFound {