mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-26 05:29:45 +00:00
Isolate lock-evidence fixture keys without clearing guest fences
Retained full-suite proof reports an inherited agent-cooldown before the invalid-UTF8 lock case dispatches any command. Give each synthetic case a distinct base path, keeping all clients in that case on its shared key. A same-listener control retains the prior uncertainty fence unchanged. No runtime guest guard, native target or assertion is weakened. Change-source: pulse-maintainer
This commit is contained in:
@@ -69,6 +69,12 @@ criterion. It records elapsed time and both targets' request counts; unchanged
|
||||
pin, admission and routing assertions remain required. Runtime timeouts and
|
||||
separate timeout/cancellation controls are unchanged. This is not a diagnosis
|
||||
or dismissal of the retained earlier one-second timeout.
|
||||
Lock-evidence fixture cases use independent base-path namespaces while each
|
||||
case's clients share the same guard key. A recycled loopback port cannot inherit
|
||||
an earlier synthetic guest's cooldown. `TestGuestAgentLockEvidenceFixtureIsolation`
|
||||
shares one listener and retains an earlier uncertainty fence unchanged while an
|
||||
independent fixture completes its lock/command checks. No runtime fence is
|
||||
cleared, expired or weakened; native guest recovery is not established.
|
||||
This establishes source/consumer behaviour, not native recovery or attribution
|
||||
of a guest, PBS or certificate report.
|
||||
|
||||
|
||||
@@ -70,6 +70,12 @@ criterion. It records elapsed time and both targets' request counts; unchanged
|
||||
pin, admission and routing assertions remain required. Runtime timeouts and
|
||||
separate timeout/cancellation controls are unchanged. This is not a diagnosis
|
||||
or dismissal of the retained earlier one-second timeout.
|
||||
Lock-evidence fixture cases use independent base-path namespaces while each
|
||||
case's clients share the same guard key. A recycled loopback port cannot inherit
|
||||
an earlier synthetic guest's cooldown. `TestGuestAgentLockEvidenceFixtureIsolation`
|
||||
shares one listener and retains an earlier uncertainty fence unchanged while an
|
||||
independent fixture completes its lock/command checks. No runtime fence is
|
||||
cleared, expired or weakened; native guest recovery is not established.
|
||||
This establishes source/consumer behaviour, not native recovery or attribution
|
||||
of a guest, PBS or certificate report.
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ func TestGuestAgentLockEvidenceRejectsAmbiguity(t *testing.T) {
|
||||
backupAgentPayload(w, r)
|
||||
}))
|
||||
defer server.Close()
|
||||
c := backupTestClient(t, server.URL)
|
||||
c := backupTestClient(t, lockEvidenceFixtureEndpoint(t, server.URL))
|
||||
if err := read(context.Background(), c, 105); GuestAgentDeferredReason(err) != "lock-unverified" {
|
||||
t.Errorf("ambiguous lock admitted/published a command: %v", err)
|
||||
}
|
||||
@@ -68,7 +68,7 @@ func TestGuestAgentLockEvidenceRejectsAmbiguity(t *testing.T) {
|
||||
healthy.Store(true)
|
||||
// A completed command's failed lock check is not transport
|
||||
// uncertainty. A new diagnostic must reverify and resume.
|
||||
diagnostic := backupTestClient(t, server.URL)
|
||||
diagnostic := backupTestClient(t, lockEvidenceFixtureEndpoint(t, server.URL))
|
||||
if err := read(context.Background(), diagnostic, 105); err != nil {
|
||||
t.Fatalf("complete unlocked config did not resume: %v", err)
|
||||
}
|
||||
@@ -116,7 +116,7 @@ func TestGuestAgentLockEvidenceRejectsRedirects(t *testing.T) {
|
||||
}
|
||||
}))
|
||||
defer server.Close()
|
||||
c := backupTestClient(t, server.URL)
|
||||
c := backupTestClient(t, lockEvidenceFixtureEndpoint(t, server.URL))
|
||||
if err := read(context.Background(), c, 105); GuestAgentDeferredReason(err) != "lock-unverified" {
|
||||
t.Errorf("redirected lock admitted/published a command: %v", err)
|
||||
}
|
||||
@@ -145,7 +145,7 @@ func TestGuestAgentLockEvidenceRejectsRedirects(t *testing.T) {
|
||||
t.Errorf("ordinary redirected requests = %d, want %d", redirected.Load(), wantRedirected)
|
||||
}
|
||||
healthy.Store(true)
|
||||
if err := read(context.Background(), backupTestClient(t, server.URL), 105); err != nil {
|
||||
if err := read(context.Background(), backupTestClient(t, lockEvidenceFixtureEndpoint(t, server.URL)), 105); err != nil {
|
||||
t.Fatalf("valid same-target config did not resume: %v", err)
|
||||
}
|
||||
if commands.Load() != want+1 || redirected.Load() != wantRedirected {
|
||||
@@ -186,7 +186,7 @@ func TestGuestAgentLockEvidenceHealthyConfigAndSessionRecovery(t *testing.T) {
|
||||
}
|
||||
}))
|
||||
defer server.Close()
|
||||
c, err := NewClient(ClientConfig{Host: server.URL, User: "fixture@pam", Password: "fixture-password", Timeout: time.Second})
|
||||
c, err := NewClient(ClientConfig{Host: lockEvidenceFixtureEndpoint(t, server.URL), User: "fixture@pam", Password: "fixture-password", Timeout: time.Second})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -199,3 +199,52 @@ func TestGuestAgentLockEvidenceHealthyConfigAndSessionRecovery(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Shared guest fences intentionally outlive a closed fixture listener. A
|
||||
// recycled loopback port must not make an independent lock case inherit an
|
||||
// earlier case's VM-wide uncertainty. Every client within a case keeps its key.
|
||||
func lockEvidenceFixtureEndpoint(t *testing.T, serverURL string) string {
|
||||
t.Helper()
|
||||
return serverURL + "/lock-evidence-fixture/" + strings.ReplaceAll(t.Name(), "/", "-")
|
||||
}
|
||||
|
||||
func TestGuestAgentLockEvidenceFixtureIsolation(t *testing.T) {
|
||||
var configs, commands atomic.Int32
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if strings.HasSuffix(r.URL.Path, "/config") {
|
||||
configs.Add(1)
|
||||
fmt.Fprint(w, `{"data":{}}`)
|
||||
return
|
||||
}
|
||||
commands.Add(1)
|
||||
backupAgentPayload(w, r)
|
||||
}))
|
||||
defer server.Close()
|
||||
// Deliberately share the listener; no OS port-reuse timing is needed.
|
||||
old := backupTestClient(t, server.URL+"/previous-lock-fixture")
|
||||
release, err := acquireGuestAgent(old.baseURL, 105)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
release(true)
|
||||
key, valid := guestAgentEndpointKey(old.baseURL)
|
||||
if !valid {
|
||||
t.Fatal("invalid old fixture key")
|
||||
}
|
||||
guestAgentGuards.Lock()
|
||||
retained := guestAgentGuards.entries[guestAgentGuardKey{endpoint: key, vmid: 105}]
|
||||
guestAgentGuards.Unlock()
|
||||
current := backupTestClient(t, lockEvidenceFixtureEndpoint(t, server.URL))
|
||||
if _, err := current.GetVMAgentInfo(context.Background(), "node", 105); err != nil {
|
||||
t.Fatalf("independent lock fixture inherited earlier uncertainty: %v", err)
|
||||
}
|
||||
if _, err := old.GetVMAgentInfo(context.Background(), "node", 105); GuestAgentDeferredReason(err) != "agent-cooldown" {
|
||||
t.Fatalf("isolation weakened the original fixture fence: %v", err)
|
||||
}
|
||||
guestAgentGuards.Lock()
|
||||
unchanged := guestAgentGuards.entries[guestAgentGuardKey{endpoint: key, vmid: 105}] == retained
|
||||
guestAgentGuards.Unlock()
|
||||
if !unchanged || commands.Load() != 1 || configs.Load() != 2 {
|
||||
t.Fatal("isolation changed a retained fence or dispatched extra work")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user