backups: record that a config was checked, not only that it changed
The stale-backup card could not be built as designed, and the reason is more important than the card. Dedup means an unchanged configuration writes no revision, so collectedat moves only on a CHANGE. A machine stable for six months has a six-month-old newest revision and is perfectly healthy. Keying a staleness card on revision age would have flagged most of the fleet - exactly the noise that makes a board worth ignoring. Underneath that: ShopDB could not distinguish those cases at all. On a no-op the server returned "unchanged" and wrote nothing, so "we checked yesterday and it matched" was discarded. That fact is the one thing a backup system must be able to prove, and the only record of it was a line in a log file on the PC. lastseenat records the check rather than the change. Touched on every matching post including the no-op; set on creation, since a new revision has by definition just been seen; backfilled from collectedat or createdat so existing rows start from the last moment the config can be PROVEN current, rather than from now - claiming a check that never happened would be worse than silence. The card keys on it, one row per CHAIN rather than per asset: a machine with two part markers can have one still reporting while the other stopped, and a per-asset view would report the machine as fine. It stays deliberately silent about assets never backed up, because whether one SHOULD be is a question only the manifest can answer, and guessing would list a hundred healthy machines. The rule lives in services/staleness.py rather than the route, so it is testable without an auth layer in the way - the same split retention.py uses. Threshold is backups_staledays, default 3, and 0 disables the card.
This commit is contained in:
@@ -89,6 +89,16 @@ class BackupRevision(db.Model):
|
||||
|
||||
createdat = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
|
||||
|
||||
# When this configuration was last CONFIRMED still current, which is not the
|
||||
# same as when it last changed. Dedup means an unchanged config writes no
|
||||
# revision, so collectedat only ever moves on a change - a machine whose
|
||||
# settings have been stable for six months has a six-month-old newest
|
||||
# revision and is perfectly healthy. Without this column ShopDB cannot tell
|
||||
# that machine from one whose backup stopped running six months ago, which
|
||||
# is the one question a backup system has to be able to answer. Touched on
|
||||
# every matching post, including the no-op that writes nothing else.
|
||||
lastseenat = db.Column(db.DateTime, nullable=True)
|
||||
|
||||
__table_args__ = (
|
||||
db.Index('ixbackuprevisionsassetkind', 'assetid', 'backupkind'),
|
||||
)
|
||||
@@ -138,6 +148,10 @@ class BackupRevision(db.Model):
|
||||
# BROWSER-LOCAL and the timestamp silently shifts by the viewer's
|
||||
# offset before any site-timezone formatting is applied.
|
||||
'collectedat': _utciso(self.collectedat),
|
||||
# When the config was last CONFIRMED current, versus when it last
|
||||
# changed. The history view needs both or a stable machine looks
|
||||
# abandoned.
|
||||
'lastseenat': _utciso(self.lastseenat),
|
||||
'createdat': _utciso(self.createdat),
|
||||
}
|
||||
if includepayload:
|
||||
|
||||
Reference in New Issue
Block a user