slice 8B.2 (agent): emit snapshotted phase at storage-snapshot moment (v0.13.0)
BackupRunner.BackupWithSnapshotHook tails the task log for the 'create storage snapshot' marker (snapshot mode only) and fires onSnapshot once; localapi flips /backup/status to 'snapshotted' before 'done' so the controller resumes early. Phase 0 validated on PVE 9.2.2: marker confirmed, downtime ~24s->~1s (934MB). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
package backup
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"gitea.dooplex.hu/admin/felhom-agent/internal/proxmox"
|
||||
)
|
||||
|
||||
// 8B.2: in snapshot mode, the runner fires onSnapshot when the storage-snapshot marker appears in
|
||||
// the task log — mid-backup, before completion.
|
||||
func TestBackupWithSnapshotHook_FiresOnMarker(t *testing.T) {
|
||||
old := snapshotWatchInterval
|
||||
snapshotWatchInterval = 2 * time.Millisecond
|
||||
defer func() { snapshotWatchInterval = old }()
|
||||
|
||||
api := &fakeBackupAPI{
|
||||
vzdumpUPID: "UPID:backup",
|
||||
waitGate: make(chan struct{}), // hold the backup open so the watcher gets to poll
|
||||
logLines: []string{"INFO: backup mode: snapshot", "INFO: create storage snapshot 'vzdump'"},
|
||||
content: []proxmox.StorageContent{{VolID: "local:backup/vzdump-lxc-9001-x", Content: "backup", VMID: 9001, Size: 100, CTime: 1}},
|
||||
}
|
||||
r := NewBackupRunner(api, "local", "", "", quiet())
|
||||
|
||||
var fired int32
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
_, _ = r.BackupWithSnapshotHook(context.Background(), 9001, func() { atomic.StoreInt32(&fired, 1) })
|
||||
close(done)
|
||||
}()
|
||||
|
||||
// the watcher should fire onSnapshot well before we release the backup
|
||||
deadline := time.Now().Add(time.Second)
|
||||
for time.Now().Before(deadline) && atomic.LoadInt32(&fired) == 0 {
|
||||
time.Sleep(2 * time.Millisecond)
|
||||
}
|
||||
if atomic.LoadInt32(&fired) != 1 {
|
||||
t.Fatal("onSnapshot did not fire on the storage-snapshot marker")
|
||||
}
|
||||
close(api.waitGate) // let the backup complete
|
||||
<-done
|
||||
}
|
||||
|
||||
// Stop/downgraded mode → no storage-snapshot marker → onSnapshot never fires (the 8B.2 fallback).
|
||||
func TestBackupWithSnapshotHook_StopMode_NeverFires(t *testing.T) {
|
||||
old := snapshotWatchInterval
|
||||
snapshotWatchInterval = 2 * time.Millisecond
|
||||
defer func() { snapshotWatchInterval = old }()
|
||||
|
||||
api := &fakeBackupAPI{
|
||||
vzdumpUPID: "UPID:backup",
|
||||
waitGate: make(chan struct{}),
|
||||
logLines: []string{"INFO: backup mode: stop"}, // downgraded; no snapshot marker
|
||||
content: []proxmox.StorageContent{{VolID: "local:backup/vzdump-lxc-9001-x", Content: "backup", VMID: 9001, Size: 100, CTime: 1}},
|
||||
}
|
||||
r := NewBackupRunner(api, "local", "", "", quiet())
|
||||
|
||||
var fired int32
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
_, _ = r.BackupWithSnapshotHook(context.Background(), 9001, func() { atomic.StoreInt32(&fired, 1) })
|
||||
close(done)
|
||||
}()
|
||||
time.Sleep(40 * time.Millisecond) // the watcher polls several times + sees stop mode → returns
|
||||
if atomic.LoadInt32(&fired) != 0 {
|
||||
t.Fatal("onSnapshot fired in stop mode (must not)")
|
||||
}
|
||||
close(api.waitGate)
|
||||
<-done
|
||||
}
|
||||
Reference in New Issue
Block a user