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:
1 parent
ad80ee749a
commit
3911cc6f52
3 files changed
+75
-1
No files matched your search
@@ -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