feat(stargate): expose control API for config and routing state
Nobody has claimed this yet.
- Dominant language
- Go
- Stars
- 218
- Forks
- 72
- Avg merge
- 1d 12h
- Merged PRs (30d)
- 427
Description
Stargate debug APIs plan
Goal
Expose the selected Stargate pod's configuration and routing snapshots through
the same load balancer and listener that serve ListModels.
Add these routes:
- GET /debug/config
- GET /debug/routing_state/
- GET /debug/routing_state/{model_id}
- GET /debug/routing_state/{model_id}?routing_key={routing_key}
Every response includes the selected pod's stargate_id. The response is local
to that pod. It is not an aggregate of all Stargate replicas.
Design
Configuration
Keep one owned DebugConfig value in the model-discovery server state. Build it
once from the same effective values used to start Stargate. DebugConfig does
not contain stargate_id; the response envelope is the only source of pod
identity.
GET /debug/config clones that value and returns:
{
"stargate_id": "stargate-pod-name",
"config": { ... }
}
Include all effective configuration that is useful for debugging. Omit secret
values, private key contents, certificate contents, and authentication tokens.
The response has these direct groups:
- listeners: actual bound and advertised addresses
- discovery: mode, peers, DNS settings, and polling or heartbeat intervals
- lifecycle: readiness, drain, and registration timeouts
- transport: connectivity mode, tunnel protocol, connection counts, TLS mode,
and transport timeouts - proxy: retry counts and replay settings
- load_balancer: the effective default and per-model policy loaded at startup
- telemetry: endpoint, service name, enabled state, and whether auth is
configured - worker_auth: endpoint, mode, enabled state, and whether credentials are
configured
Represent durations as integer milliseconds and enum values as stable strings.
Represent secret-backed settings with configured booleans, never secret values
or secret file contents.
Capture normalized arguments before startup consumes them, actual addresses
after binding, and the parsed load-balancer policy from the same value used to
construct routing. Assemble one DebugConfig after those values exist. Do not
add a separate configuration framework or runtime reload path.
Routing state
Add one routing-state method:
debug_snapshot() -> HashMap<RoutingTargetKey, DebugTargetSnapshot>
The method directly walks the existing routing target map and clones its
current contents into owned, serializable debug values:
- Clone each RoutingTargetKey and target handle from the top-level map.
- For each target, hold its existing generation lock while walking its current
cluster map. - For each cluster, call one new cluster debug_snapshot helper that holds the
existing cluster generation lock once and clones both the effective aggregate
and every current backend. - Release all routing locks before returning from debug_snapshot.
DebugTargetSnapshot contains only cluster data. RoutingTargetKey remains the
single source of model_id and routing_key. The owned view contains:
- clusters
- cluster stats, lowercase status, rtt_ms, snapshot_age_ms, and active backend
count - every backend's ID, URL, full ModelStats, lowercase status, rtt_ms,
snapshot_age_ms, and tunnel direction
Capture one Instant::now() at the start of the top-level clone and use it for
every snapshot_age_ms value. Use saturating elapsed conversion.
The full handler calls debug_snapshot and returns every map entry:
{
"stargate_id": "stargate-pod-name",
"routing_targets": [ ... ]
}
The model handler calls the same debug_snapshot method, then retains entries
whose RoutingTargetKey.model_id matches the path parameter. If routing_key is
present, it also retains entries with that exact routing key. An empty
routing_key selects the unscoped target. After filtering, move identity from
each map key into the response item. Sort targets by model_id and routing_key,
clusters by cluster ID, and backends by backend ID.
This is an eventually consistent debug view. It does not retry, revalidate
generations, create a cross-target transaction, or claim an atomic observation
of the whole process. A target's cluster map is cloned under the target lock.
Each cluster aggregate and backend list are cloned under one nested cluster
lock, using the existing target-then-cluster lock order.
Implement the model route with Axum's catch-all path capture so model IDs
containing slash remain one value. Use Axum's decoded Path value directly; do
not decode it a second time. The empty model ID remains visible in the full
view but has no model-route spelling because /debug/routing_state/ is the
collection route. Do not add custom identifier encoding or a load-balancer
fallback.
Serving
Build tonic::service::Routes from StargateModelDiscoveryServer, convert it with
into_axum_router(), add the debug routes to that router, and serve the existing
model-discovery TcpListener with axum::serve and the existing shutdown signal.
ListModels must continue to work on the same listener.
Expose ordinary HTTP GET traffic through the existing load balancer that
already serves ListModels. Update that existing resource only. Do not create a
second load balancer or expose the routes on the inference proxy or backend
control-plane listeners.
Use the access control already applied to the ListModels load balancer. This
change does not add an application bearer-token system or a new NetworkPolicy.
Remove the old /debug/state route from the inference HTTP proxy.
Implementation
0. Identify the existing ListModels load balancer
Before code changes, record the exact resource, owner, listener, backend port,
and access control that currently expose ListModels. This is a blocking check,
not a new design project.
The implementation is complete only when that resource routes ListModels and
the debug HTTP requests to the same model-discovery listener. If it is owned
outside this repository, the linked deployment change is part of completion,
not a follow-up note.
1. Clone the routing snapshots
Update the routing-state module under:
- src/libraries/rust/stargate/crates/stargate/src/routing_state/
Add the small owned debug snapshot structs and three direct methods:
- RoutingLifecycle::debug_snapshot clones the top-level target map.
- RoutingTargetState::debug_snapshot holds the target lock while cloning its
current cluster map. - RoutedClusterState::debug_snapshot holds the cluster lock once while cloning
the effective aggregate and backend values.
The public state method returns the one owned
HashMap<RoutingTargetKey, DebugTargetSnapshot>. These are implementation-local
helpers for the three existing lock levels, not a new routing abstraction.
Do not change routing updates, registration ownership, reservations, load
balancing, or snapshot lifecycle.
2. Capture the full non-secret configuration
Expand the existing DebugConfig into the groups listed in the API contract.
Construct it once from the effective startup values. Keep stargate_id in the
server state and response envelope, not inside DebugConfig.
3. Add the HTTP handlers
Move the existing debug configuration support out of the inference proxy and
place it with the new debug handlers.
The handlers:
- clone DebugConfig or call debug_snapshot
- apply the optional model and routing-key filter
- convert the retained map keys and values to sorted response arrays
- attach stargate_id
- return Axum Json
No pagination, streaming, capped writer, semaphore, retry loop, custom error
framework, or new debug-only metric families are part of this change. Apply the
existing request logging, tracing, and RED telemetry to the fixed debug route
names.
4. Serve HTTP beside ListModels
Update runtime/server_tasks.rs with the Tonic Routes to Axum construction
described above.
Keep the existing listener address, task ownership, graceful shutdown, and
pod-local state.
5. Update the existing ListModels load balancer
Update the resource identified in step 0 to allow HTTP GET requests to the two
debug resources on that same backend and port.
Do not create another Service.
Tests
Add focused tests only:
- debug_snapshot clones every current target, cluster, backend, and ModelStats
value from a representative routing map. - One cluster debug_snapshot call returns a matching effective aggregate and
backend list. - A target clone returns all clusters from one captured target map.
- GET /debug/routing_state/ returns the complete cloned map and stargate_id.
- The model route returns all routing keys for only that model.
- The routing_key query returns the exact model and routing-key pair.
- Slash-bearing model IDs work through Axum and the real load balancer. Literal
percent sequences are decoded exactly once. - Exact JSON tests cover rtt_ms, snapshot_age_ms, lowercase status, full
ModelStats, and deterministic ordering. - GET /debug/config returns every documented effective DebugConfig group and
top-level stargate_id without secret values or duplicate pod identity. - ListModels still works over gRPC on the combined listener.
- Unknown gRPC services keep Tonic's unimplemented fallback, and graceful
shutdown still stops the combined listener. - The existing ListModels load balancer passes both ListModels and the debug
HTTP requests. - With two Stargate replicas, the returned stargate_id identifies which pod's
local snapshot was returned. - The inference proxy no longer serves /debug/state or the new debug routes.
Do not duplicate routing lifecycle, heartbeat, reservation, or concurrency
tests that already cover how the snapshot map is maintained.
Verification
Run:
cargo fmt --manifest-path src/libraries/rust/stargate/Cargo.toml --all --check
cargo test --manifest-path src/libraries/rust/stargate/Cargo.toml -p stargate
bazel test //src/libraries/rust/stargate/crates/stargate:stargate_test --test_output=streamed
git diff --check
Run the existing deployment render tests for the resource that owns the
ListModels load balancer.
Acceptance criteria
- The existing ListModels load balancer exposes /debug/config and
/debug/routing_state/. - The required model route and optional routing-key filter work.
- Every response includes stargate_id.
- The routing response is a direct owned clone of the selected pod's current
routing snapshot map, including all cluster and backend stats. Target identity
comes only from RoutingTargetKey. - Each target map is cloned under its target lock, and each cluster aggregate
and backend list are cloned under one cluster lock. - The configuration response returns the documented effective non-secret config
with no duplicate stargate_id. - The actual existing ListModels load balancer serves both gRPC ListModels and
debug HTTP requests. - ListModels and existing routing behavior remain unchanged.
Non-goals
- Aggregating state across Stargate pods.
- Strong or atomic consistency across routing targets.
- Pagination, streaming, response limits, or debug-specific load shedding.
- A new authentication, authorization, secret-rotation, or NetworkPolicy
subsystem. - New routing abstractions or changes to the routing data model.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by identifying the existing ListModels load balancer resource, then read src/libraries/rust/stargate/crates/stargate/src/routing_state/ and runtime/server_tasks.rs. Run the listed Rust formatting and test commands before changing routing snapshots, debug handlers, and the combined listener. Done means the existing load balancer serves the debug routes and ListModels while the focused tests cover cloning, filtering, ordering, identity, and shutdown.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- kubernetes, rust
- Domain
- api, backend, devops
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100