23a8ef3de4
gates / gates (push) Successful in 18s
No automatic undo: a customer guest cannot be snapshotted (R-837, measured). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
64 lines
1.8 KiB
Go
64 lines
1.8 KiB
Go
package localapi
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"gitea.dooplex.hu/admin/felhom-agent/internal/backup"
|
|
)
|
|
|
|
// The OS leg (agent v0.140.0) runs after a SUCCESSFUL primary backup, and only then; and it runs BEFORE the
|
|
// host-wide heavy-op gate is released, so a restore-test cannot start in the middle of it (`11` C10).
|
|
// Red-proof: drop the `b.Success &&` guard and the failed-backup sub-case fails; move the call after release()
|
|
// and the gate sub-case fails.
|
|
func TestAfterPrimaryBackup(t *testing.T) {
|
|
run := func(t *testing.T, failErr string) (calls []int, gateHeld bool) {
|
|
gate := &backup.InFlight{}
|
|
b := &fakeBackups{failErr: failErr}
|
|
srv := newTestServerS(t, &fakeGuests{}, b, &fakeStore{}, nil)
|
|
srv.inFlight = gate
|
|
var mu sync.Mutex
|
|
done := make(chan struct{}, 1)
|
|
srv.SetAfterPrimaryBackup(func(_ context.Context, vmid int) {
|
|
rel, _, ok := gate.TryAcquire("probe")
|
|
mu.Lock()
|
|
calls = append(calls, vmid)
|
|
gateHeld = !ok
|
|
mu.Unlock()
|
|
if ok {
|
|
rel()
|
|
}
|
|
done <- struct{}{}
|
|
})
|
|
h := srv.Handler()
|
|
if do(t, h, "POST", "/backup", "A", "").Code != http.StatusAccepted {
|
|
t.Fatal("POST /backup not accepted")
|
|
}
|
|
select {
|
|
case <-done:
|
|
case <-time.After(500 * time.Millisecond):
|
|
}
|
|
time.Sleep(20 * time.Millisecond)
|
|
mu.Lock()
|
|
defer mu.Unlock()
|
|
return calls, gateHeld
|
|
}
|
|
t.Run("success runs the leg under the gate", func(t *testing.T) {
|
|
calls, held := run(t, "")
|
|
if len(calls) != 1 {
|
|
t.Fatalf("the leg ran %d time(s), want 1", len(calls))
|
|
}
|
|
if !held {
|
|
t.Fatal("the heavy-op gate was free while the leg ran — a restore-test could overlap it")
|
|
}
|
|
})
|
|
t.Run("a failed backup runs nothing", func(t *testing.T) {
|
|
if calls, _ := run(t, "vzdump exploded"); len(calls) != 0 {
|
|
t.Fatalf("the leg ran after a FAILED backup: %v", calls)
|
|
}
|
|
})
|
|
}
|