agent endpoints: audit cleanups (drop dead Registration.Endpoints, test the fallback flow)

Remove the write-only Registration.Endpoints field (matching goes through
Manifest; the multi-node layer no longer replicates the raw route set). Add
Front-level fallback tests (fires with the resolved identity, declined -> 404
with a local worker or 503 without), and fix two stale comments.
This commit is contained in:
Théo Monnom
2026-09-03 00:54:51 -07:00
parent 41d45b2c3d
commit ab578277af
5 changed files with 77 additions and 10 deletions
+6 -3
View File
@@ -101,8 +101,8 @@ func (f *Front) WithFallback(fb Fallback) *Front {
return f
}
// writeUnavailable writes a 503 with a Retry-After hint, the response the front
// returns when no worker can currently serve a request it did route.
// writeUnavailable writes a 503 with a Retry-After hint: no local worker can
// serve the request and no fallback placed it elsewhere.
func (f *Front) writeUnavailable(w http.ResponseWriter, msg string) {
w.Header().Set("Retry-After", "1")
http.Error(w, msg, http.StatusServiceUnavailable)
@@ -195,7 +195,10 @@ func (f *Front) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}
}
if route == nil && f.fallback != nil {
// nothing local can serve: hand off before the local status mapping
// nothing local matched: hand off to the multi-node fallback (relay to a
// node holding the deployment) before the local status mapping. The
// serving node's relay listener installs no fallback of its own, so a
// relayed request is served or errored there and never re-relays.
if f.fallback(w, r, &FallbackRequest{
APIKey: apiKey, Authenticated: authenticated,
AgentName: agentName, Deployment: deployment,
+71
View File
@@ -0,0 +1,71 @@
// Copyright 2026 LiveKit, Inc.
package endpoint
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/require"
"github.com/livekit/protocol/livekit"
"github.com/livekit/protocol/logger"
)
func fallbackFront(t *testing.T, fb Fallback, withWorker bool) *Front {
reg := NewRegistry()
if withWorker {
m, err := ParseManifest([]*livekit.AgentHttp_AgentEndpoint{
{Path: "/known", Methods: []string{"GET"}, Public: true},
})
require.NoError(t, err)
r := &Registration{WorkerID: "w1", APIKey: "proj", AgentName: "a", Deployment: "d", Manifest: m}
r.SetSession(&fakeSession{})
require.NoError(t, reg.Register(r))
}
f := NewFront(reg, func(*http.Request) (string, bool) { return "proj", true }, logger.GetLogger())
if fb != nil {
f = f.WithFallback(fb)
}
return f
}
func serveFront(f *Front, path string) *httptest.ResponseRecorder {
w := httptest.NewRecorder()
f.ServeHTTP(w, httptest.NewRequest(http.MethodGet, PathPrefix+"a/d"+path, nil))
return w
}
// a path no local worker matches hands off to the fallback, which is given the
// resolved identity; when the fallback serves, the front writes nothing itself.
func TestFrontFallbackFires(t *testing.T) {
var got *FallbackRequest
f := fallbackFront(t, func(w http.ResponseWriter, _ *http.Request, fr *FallbackRequest) bool {
got = fr
w.WriteHeader(http.StatusTeapot) // stands in for a relayed response
return true
}, true)
w := serveFront(f, "/unknown")
require.Equal(t, http.StatusTeapot, w.Code)
require.NotNil(t, got)
require.Equal(t, "proj", got.APIKey)
require.True(t, got.Authenticated)
require.Equal(t, "a", got.AgentName)
require.Equal(t, "d", got.Deployment)
}
// a declined fallback with a local worker present falls through to the front's
// own 404 for the unmatched path.
func TestFrontFallbackDeclinedMapsStatus(t *testing.T) {
f := fallbackFront(t, func(http.ResponseWriter, *http.Request, *FallbackRequest) bool { return false }, true)
require.Equal(t, http.StatusNotFound, serveFront(f, "/unknown").Code)
}
// a declined fallback with no local worker for the deployment falls through to
// 503.
func TestFrontFallbackDeclinedNoCandidates(t *testing.T) {
f := fallbackFront(t, func(http.ResponseWriter, *http.Request, *FallbackRequest) bool { return false }, false)
require.Equal(t, http.StatusServiceUnavailable, serveFront(f, "/unknown").Code)
}
-5
View File
@@ -19,8 +19,6 @@ import (
"errors"
"slices"
"sync"
"github.com/livekit/protocol/livekit"
)
// DefaultDeployment is the URL segment that addresses workers registered with an
@@ -53,9 +51,6 @@ type Registration struct {
AgentName string
Deployment string
Manifest *Manifest
// Endpoints is the raw manifest as declared, kept so a multi-node layer can
// replicate the route set for remote path matching without re-deriving it.
Endpoints []*livekit.AgentHttp_AgentEndpoint
// Draining is provided by the control-plane layer that owns the worker; a
// draining worker takes no new streams. Worker selection uses live in-flight
-1
View File
@@ -91,7 +91,6 @@ func handleSession(reg *endpoint.Registry, sess *webtransport.Session) {
AgentName: rw.GetAgentName(),
Deployment: rw.GetDeployment(),
Manifest: manifest,
Endpoints: rw.GetEndpoints(),
}
registration.SetSession(endpoint.NewWebTransportSession(sess, endpoint.DefaultMaxStreams))
_ = reg.Register(registration)
-1
View File
@@ -354,7 +354,6 @@ func (h *AgentHandler) registerEndpoints(w *agent.Worker, sess endpoint.Session)
AgentName: w.AgentName,
Deployment: w.Deployment,
Manifest: manifest,
Endpoints: w.Endpoints,
Draining: w.Draining,
}
reg.SetSession(sess)