feat(multiplayer): acknowledge supervisor shutdown

This commit is contained in:
Josh Creek
2026-09-01 16:54:40 +01:00
parent cc12260225
commit 75cb8faac4
4 changed files with 136 additions and 1 deletions
+76
View File
@@ -629,6 +629,82 @@ func TestRunDrainsBeforeChildExit(t *testing.T) {
}
}
func TestRunAcknowledgesControlledShutdownWithWorkloadCredential(t *testing.T) {
marker := filepath.Join(t.TempDir(), "drained")
tokenPath := filepath.Join(t.TempDir(), "token")
if err := os.WriteFile(tokenPath, []byte("workload-secret"), 0600); err != nil {
t.Fatal(err)
}
var shutdownCalls int
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case "/drain":
if r.Header.Get("Authorization") != "Bearer run-secret" {
w.WriteHeader(http.StatusUnauthorized)
return
}
if err := os.WriteFile(marker, []byte("drained"), 0600); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusAccepted)
case "/v1/servers/server-1/shutdown":
if r.Header.Get("Authorization") != "Bearer workload-secret" || r.Header.Get("Idempotency-Key") == "" {
w.WriteHeader(http.StatusUnauthorized)
return
}
shutdownCalls++
w.WriteHeader(http.StatusNoContent)
default:
w.WriteHeader(http.StatusNotFound)
}
}))
defer server.Close()
s, err := New(Config{
Command: []string{"/bin/sh", "-c", "while [ ! -f '" + marker + "' ]; do sleep 0.01; done"},
DrainURL: server.URL + "/drain", DrainToken: "run-secret", ControlPlaneURL: server.URL,
WorkloadTokenPath: tokenPath, ServerID: "server-1", MatchID: "match-1", ProtocolVersion: 1,
ImageDigest: "sha256:aa",
})
if err != nil {
t.Fatal(err)
}
ctx, cancel := context.WithCancel(context.Background())
time.AfterFunc(30*time.Millisecond, cancel)
if err := s.Run(ctx, time.Second); err != nil {
t.Fatalf("graceful run: %v", err)
}
if shutdownCalls != 1 {
t.Fatalf("shutdown calls = %d, want 1", shutdownCalls)
}
}
func TestRunDoesNotAcknowledgeWhenLocalDrainFails(t *testing.T) {
var shutdownCalls int
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/v1/servers/server-1/shutdown" {
shutdownCalls++
}
w.WriteHeader(http.StatusInternalServerError)
}))
defer server.Close()
s, err := New(Config{
Command: []string{"/bin/sh", "-c", "sleep 5"},
DrainURL: server.URL + "/drain", DrainToken: "run-secret", ControlPlaneURL: server.URL,
WorkloadTokenPath: filepath.Join(t.TempDir(), "missing-token"), ServerID: "server-1", MatchID: "match-1",
ProtocolVersion: 1, ImageDigest: "sha256:aa",
})
if err != nil {
t.Fatal(err)
}
ctx, cancel := context.WithCancel(context.Background())
time.AfterFunc(30*time.Millisecond, cancel)
err = s.Run(ctx, 50*time.Millisecond)
if err == nil || shutdownCalls != 0 {
t.Fatalf("failed drain result=%v shutdown calls=%d", err, shutdownCalls)
}
}
func TestRunForceKillsUnresponsiveChildAtDeadline(t *testing.T) {
s, err := New(Config{Command: []string{"/bin/sh", "-c", "trap '' TERM; sleep 5"}})
if err != nil {