mirror of
https://github.com/lukaszraczylo/traefik-llmgateway.git
synced 2026-10-12 04:54:45 +00:00
feat(admin): exclude admin traffic from usage statistics
Operator directive (progress ledger, 2026-08-21): admin requests must
not touch req/min-req/day statistics. handleAdminAPI no longer calls
checkAndCount for GET /admin/api/overview or GET /admin/api/usage (it
already skipped it for GET /admin/api/usage/history) - admin traffic
must never appear in usage statistics, which measure real LLM traffic
only. identify()'s group return is now discarded (u, _, ok :=), since
buildLimitScopes is no longer built here.
Direct consequence, documented in the new doc comment: an admin's own
requestsPerMinute/requestsPerDay limit, if configured, is no longer
enforced against admin-route traffic either - accepted trade-off
(admin-gated, cheap reads; a self-inflicted, not shared, resource
cost).
admin_test.go: TestAdmin_RateLimitApplies (asserted a 2nd request
429s) inverted to TestAdmin_NeverRateLimitedAndCountersUntouched - 5x
requests to both overview and usage against an admin key pinned at
RequestsPerMinute:1, asserting every one is 200 and the admin's own
req/min counter stays exactly 0.
integration/integration_test.go: TestAdmin_Dashboard's admin-user
assertion inverted from requestsPerDay > 0 ("admin API calls count
too") to requestsPerDay == 0 ("admin routes never call
checkAndCount") - the admin key is used throughout the whole suite
exclusively to hit /admin/api/*, so it must show exactly 0 despite
issuing many requests by the time this test runs.
This commit is contained in:
@@ -100,21 +100,23 @@ func (g *Gateway) handleAdmin(sw *statusTrackingWriter, r *http.Request) {
|
||||
|
||||
// handleAdminAPI is the gate for the three /admin/api/* JSON routes,
|
||||
// applying spec §4's gate order: unauthenticated → 401, authenticated
|
||||
// non-admin → 403, admin → serve. GET /admin/api/overview and GET
|
||||
// /admin/api/usage count request counters via checkAndCount before
|
||||
// serving, like every other authenticated route: an admin over their own
|
||||
// req/min limit gets a 429 here exactly as they would on any other route
|
||||
// (spec §4's "Accounting: admin routes count request counters like any
|
||||
// authed route").
|
||||
// non-admin → 403, admin → serve.
|
||||
//
|
||||
// GET /admin/api/usage/history (v0.2 data-layer task) is the one
|
||||
// exception: it skips checkAndCount entirely. The operator-directed
|
||||
// removal of admin-route stat counting (progress ledger, 2026-08-20)
|
||||
// lands for every /admin/api/* route in a later task; this new route is
|
||||
// built consistent with that direction from the start, rather than
|
||||
// counted now and un-counted later.
|
||||
// None of the three routes call checkAndCount (operator directive:
|
||||
// progress ledger, 2026-08-20 — "admin requests must NOT touch req/min,
|
||||
// req/day statistics"): admin traffic must never appear in usage
|
||||
// statistics, which exist to measure real LLM traffic only. GET
|
||||
// /admin/api/usage/history was built this way from the start (v0.2
|
||||
// data-layer task); this change extends the same treatment to GET
|
||||
// /admin/api/overview and GET /admin/api/usage, which previously counted
|
||||
// like any other authenticated route (spec §4 amended accordingly). An
|
||||
// admin's own req/min or req/day limit, if configured, is therefore never
|
||||
// enforced against admin-route traffic either — the accepted trade-off
|
||||
// the operator directive names: these are admin-gated, cheap reads, and
|
||||
// an admin holder polling the dashboard aggressively is a self-inflicted,
|
||||
// not a shared, resource cost.
|
||||
func (g *Gateway) handleAdminAPI(sw *statusTrackingWriter, r *http.Request) {
|
||||
u, grp, ok := g.auth.identify(r)
|
||||
u, _, ok := g.auth.identify(r)
|
||||
g.logAuthEvent(ok, authEventUserName(u), r)
|
||||
if !ok {
|
||||
writeOAIError(sw, http.StatusUnauthorized, "authentication_error", "invalid or missing API key")
|
||||
@@ -125,21 +127,13 @@ func (g *Gateway) handleAdminAPI(sw *statusTrackingWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if r.URL.Path == adminUsageHistoryPath {
|
||||
g.serveAdminUsageHistory(sw, r)
|
||||
return
|
||||
}
|
||||
|
||||
if violation := g.limiter.checkAndCount(buildLimitScopes(u, grp)); violation != nil {
|
||||
writeLimitViolation(sw, violation)
|
||||
return
|
||||
}
|
||||
|
||||
switch r.URL.Path {
|
||||
case adminOverviewPath:
|
||||
g.serveAdminOverview(sw)
|
||||
case adminUsagePath:
|
||||
g.serveAdminUsage(sw)
|
||||
case adminUsageHistoryPath:
|
||||
g.serveAdminUsageHistory(sw, r)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user