fix(R-1232): the tier due-check counts only this box's archives (own key) — a reinstalled box's first whole-guest copy is not a week late
gates / gates (push) Successful in 1m20s
gates / gates (push) Successful in 1m20s
Red-proved (the old box's archive was counted). Commit only.
This commit is contained in:
+8
-1
@@ -1,4 +1,11 @@
|
||||
## Unreleased (next: v0.167.0) — the BYO SSH clean-up in the SAME update (R-1285) (2026-10-26) — committed, not released
|
||||
## Unreleased (next: v0.167.0) — the BYO SSH clean-up in the SAME update (R-1285); a reinstalled box's whole-guest tier is due at once (R-1232) — committed, not released
|
||||
|
||||
- **R-1232 (P2):** `backup.BackupRunner.NewestArchiveTime` — the tier due-check — counts only archives written with THIS
|
||||
storage's key on an encrypted storage (the R-727 discriminator; the archive carries no host id). Before: a reinstalled
|
||||
box shares the customer's PBS namespace and guest number with the old box, the old box's archive read as „the last
|
||||
backup", and the whole-guest tier waited up to its 168-h cadence (the 2026-10-23 test night). A key lookup failure is
|
||||
an error (the due-check reads UNKNOWN), never a foreign age. Tests `TestR1232_*` (foreign only → none; own → its time;
|
||||
an unencrypted dir → unfiltered), red-proved (case 1 returned the old box's time).
|
||||
|
||||
- **The cause (measured 2026-10-26 on demo-hp, felhom.eu R-1285):** a box gets `agent_update` and `agent_config_update` in
|
||||
one pass; the bundle job runs in the OLD process (no policy pass in it) and the restart into the new agent comes a
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
package backup
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"gitea.dooplex.hu/admin/felhom-agent/internal/proxmox"
|
||||
)
|
||||
|
||||
// R-1232 — the 2026-10-23 test night: a reinstalled box (new install, new PBS key, same customer namespace, same guest
|
||||
// number 9201) found the OLD box's archive (2.6 days old) when the due-check asked „when did this tier last land?", so
|
||||
// the whole-guest tier read „not due" for up to its 168-h cadence and the ep0 copy stayed the OLD box's. The due-check
|
||||
// now counts only archives written with THIS storage's key (the R-727 discriminator; the archive carries no host id).
|
||||
// The CONSEQUENCE asserted: with only a foreign archive the answer is „none found" (the tier is due); with the box's
|
||||
// own archive, that archive's time. COMPANION RED-PROOF: drop the key filter in NewestArchiveTime → case 1 returns the
|
||||
// old box's time.
|
||||
func TestR1232_TheDueCheckCountsOnlyThisBoxsArchives(t *testing.T) {
|
||||
oldBox := proxmox.StorageContent{VolID: "felhom-pbsdr:backup/ct/9201/2026-10-20T21:10:00Z", Content: "backup", VMID: 9201,
|
||||
Size: 3490689830, CTime: time.Date(2026, 10, 20, 21, 10, 0, 0, time.UTC).Unix(), Encrypted: earlierBox1}
|
||||
api := &fakeBackupAPI{
|
||||
storages: []proxmox.Storage{{Storage: "felhom-pbsdr", Type: "pbs", EncryptionKey: thisBoxKey}},
|
||||
content: []proxmox.StorageContent{oldBox},
|
||||
}
|
||||
r := NewBackupRunner(api, "felhom-pbsdr", proxmox.ModeSnapshot, "", "keep-last=1", quiet())
|
||||
|
||||
// 1. The fresh box has written nothing yet: the old box's archive must not count.
|
||||
when, found, err := r.NewestArchiveTime(context.Background(), 9201)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if found {
|
||||
t.Fatalf("the due-check counted ANOTHER box's archive (%s) as this tier's last backup — the tier would wait a week", when)
|
||||
}
|
||||
// 2. After the box's own first copy, that copy is the answer.
|
||||
own := proxmox.StorageContent{VolID: "felhom-pbsdr:backup/ct/9201/2026-10-24T02:30:00Z", Content: "backup", VMID: 9201,
|
||||
Size: 3600000000, CTime: time.Date(2026, 10, 24, 2, 30, 0, 0, time.UTC).Unix(), Encrypted: thisBoxKey}
|
||||
api.content = append(api.content, own)
|
||||
when, found, err = r.NewestArchiveTime(context.Background(), 9201)
|
||||
if err != nil || !found || when.Unix() != own.CTime {
|
||||
t.Fatalf("want this box's own archive time %d, got %v found=%v err=%v", own.CTime, when, found, err)
|
||||
}
|
||||
}
|
||||
|
||||
// An unencrypted storage (a local dir) holds only this box's vzdumps: no key filter, the newest counts.
|
||||
func TestR1232_UnencryptedStorageCountsEveryArchive(t *testing.T) {
|
||||
api := &fakeBackupAPI{
|
||||
storages: []proxmox.Storage{{Storage: "local", Type: "dir"}},
|
||||
content: []proxmox.StorageContent{{VolID: "local:backup/vzdump-lxc-9201-2026_10_25-21_27_05.tar.zst", Content: "backup", VMID: 9201, Size: 955425507, CTime: 1793050025}},
|
||||
}
|
||||
r := NewBackupRunner(api, "local", proxmox.ModeSnapshot, "", "keep-last=1", quiet())
|
||||
if _, found, err := r.NewestArchiveTime(context.Background(), 9201); err != nil || !found {
|
||||
t.Fatalf("an unencrypted local archive was not counted: found=%v err=%v", found, err)
|
||||
}
|
||||
}
|
||||
@@ -609,11 +609,23 @@ func (r *BackupRunner) NewestArchiveTime(ctx context.Context, vmid int) (time.Ti
|
||||
if err != nil {
|
||||
return time.Time{}, false, err
|
||||
}
|
||||
// R-1232: on an ENCRYPTED storage only archives written with THIS storage's key are this box's (the R-727
|
||||
// discriminator — the archive carries no host id). A reinstalled box shares the customer's namespace and guest
|
||||
// number with the old box, and the old box's archive made the tier read „not due" for a whole cadence (168 h).
|
||||
// A key lookup failure is returned: the due-check reads UNKNOWN, never a foreign archive's age.
|
||||
ownKey, err := r.storageKeyFingerprint(ctx, r.target)
|
||||
if err != nil {
|
||||
return time.Time{}, false, fmt.Errorf("reading the key fingerprint of storage %s: %w", r.target, err)
|
||||
}
|
||||
var best int64 = -1
|
||||
for _, e := range contents {
|
||||
if e.Content != "backup" || e.VMID != vmid {
|
||||
continue
|
||||
}
|
||||
if ownKey != "" && !strings.EqualFold(e.Encrypted, ownKey) {
|
||||
r.noteNotAGuestBackupOnce(e, fmt.Sprintf("written by another box (key %s, this box's key %s) — not this tier's last backup (R-1232)", shortFP(e.Encrypted), shortFP(ownKey)))
|
||||
continue
|
||||
}
|
||||
if ok, why := archivePlausiblyComplete(e); !ok {
|
||||
r.warnRejectedArchiveOnce(e, why)
|
||||
continue
|
||||
|
||||
Reference in New Issue
Block a user