Files
traefik-llmgateway/integration/traefik/dynamic.yml.tmpl
T
lukaszraczylo f49c51b341 fix(docs,integration): example env completeness, flaky reset, cache key assertion
Task 5 re-review fixes: 1 Important + 3 folded.

1. (Important) examples/kubernetes.yaml was construction-broken on
   copy-paste: the inline dashboard-admin user resolves env:LLMGW_ADMIN_KEY
   at construction time regardless of admin.enabled, but the Helm env
   snippet never listed it. Added it there, alongside a comment on the
   admin user explaining the env var is required either way. Fixed the
   two reversed above/below cross-references (kubernetes.yaml:90/:115)
   and reworded the misleading "self-contained" comment on the inline
   admin user. Verified with a throwaway round-trip test covering: no env
   vars set fails, every documented env var set succeeds (admin.enabled
   staying false as committed), and — the actual bug — every var set
   EXCEPT LLMGW_ADMIN_KEY still fails even with admin disabled.

2. README: Admin section's "(disabled still 404s)" now points at the
   same 404/passthroughUnknown wording the section already uses above it.
   Caching section's "block absent entirely is a construction error" now
   says "not enabled" — auth.go's actual check is !cfg.Cache.Enabled,
   which is identically true whether the block is omitted or present with
   enabled: false.

3. integration/mock/main.go: added POST /flaky/reset (handleFlakyReset),
   reachable through the "flaky" provider's own native passthrough route,
   resetting the atomic hit counter on demand. TestRetryFlakyRecovers now
   calls it at the start of the test, removing the make integration-keep
   re-run trap the old doc comment warned about instead of fixing.
   dynamic.yml.tmpl's stale "test 8" comment now reads "task 5".

4. integration_test.go: added redisKeys (docker compose exec redis-cli
   KEYS, mirroring flushRedis's own pattern) and a
   TestResponseCache assertion that redis holds at least one
   llmgw:cache:* key immediately after the miss request.

go test ./... -count=1, gofmt, vet, make yaegi-check all pass; make
integration exits 0 across two fresh full runs.
2026-08-20 20:30:08 +01:00

146 lines
5.7 KiB
Cheetah

http:
routers:
llmgateway-router:
rule: "PathPrefix(`/`)"
entryPoints: ["web"]
service: dummy-service
middlewares: ["llmgateway"]
priority: 1
llmgateway-real-router:
rule: "Host(`real.localhost`)"
entryPoints: ["web"]
service: dummy-service
middlewares: ["llmgateway-real"]
priority: 100
services:
# Placeholder upstream. Every route the plugin recognizes is
# short-circuited by the middleware itself; this backend is never
# actually reached by the requests the integration suite sends.
dummy-service:
loadBalancer:
servers:
- url: "http://mockopenai:8080"
middlewares:
llmgateway:
plugin:
llmgateway:
providers:
openai:
type: openai
baseUrl: "http://mockopenai:8080"
apiKey: "mock-openai-key"
discovery: true
models: ["gpt-mock"]
anthropic:
type: anthropic
baseUrl: "http://mockanthropic:8080"
apiKey: "mock-anthropic-key"
models: ["claude-mock"]
gemini:
type: gemini
baseUrl: "http://mockgemini:8080"
apiKey: "mock-gemini-key"
models: ["gemini-mock"]
# Backs task 5's retry coverage only: baseUrl points at the
# SAME mockopenai container, under its dedicated /flaky prefix
# (see integration/mock/main.go's handleFlakyOpenAIChat), so
# a.baseURL+"/v1/chat/completions" (provider_openai.go)
# resolves to /flaky/v1/chat/completions, and this provider's
# own native passthrough route (/flaky/reset) reaches
# handleFlakyReset the same way. discovery is left off — GET
# /flaky/v1/models isn't served — so the explicit "flaky-mock"
# model id below is the only way this provider's model is
# known.
flaky:
type: openai
baseUrl: "http://mockopenai:8080/flaky"
apiKey: "mock-openai-key"
models: ["flaky-mock"]
groups:
eng:
limits:
requestsPerMinute: 1000
limited:
limits:
requestsPerMinute: 3
users:
inline:
# limits.requestsPerDay is set (generously — every test in
# this suite together never approaches it) purely so
# buildLimitScopes (routes_unified.go/routes_media.go/
# admin.go) builds a "user" counter scope for alice at all:
# a nil per-user Limits means only the group's own counters
# ever increment, which would make the admin usage test's
# "alice's own request counters are > 0" assertion always
# read zero regardless of how much traffic she generated.
- name: alice
group: eng
apiKey: "sk-int-alice"
limits:
requestsPerDay: 1000000
# Admin-flagged user for the admin dashboard integration
# coverage (spec §4, v0.2): same "eng" group as alice, so
# polling /admin/api/* repeatedly never hits a rate limit
# meant for a different scenario. limits.requestsPerDay is
# set for the same reason as alice's above.
- name: admin
group: eng
apiKey: "sk-int-admin"
admin: true
limits:
requestsPerDay: 1000000
file: "/users/users.json"
redis:
address: "redis:6379"
mcpServers:
tool:
url: "http://mocktool:8080"
# v0.2 features (spec §1, §2, §4), all opt-in and off by default
# in v0.1: enabled here so the integration suite exercises every
# one under real Traefik+Yaegi, not just go test.
retry:
enabled: true
attempts: 1 # one retry after the first try — matches handleFlakyOpenAIChat's single forced 503
cache:
enabled: true
ttl: "2m"
admin:
enabled: true
# Second, separate middleware instance carrying only the optional
# real-upstream smoke test's "uni" provider (task 7, INTEGRATION_REAL=1
# gated). Kept isolated from the mock-backed "llmgateway" middleware
# above so the always-on tests never depend on network egress.
llmgateway-real:
plugin:
llmgateway:
providers:
real:
type: openai
baseUrl: "__INTEGRATION_REAL_BASEURL__"
apiKey: ""
# Explicit rather than discovered: resolution must not depend
# on this container reaching the real upstream at Traefik
# startup (offline dev machines, CI with no egress). The
# actual chat completion call in task 7 still goes out live.
#
# The provider is deliberately NOT named "uni": the upstream
# gateway's own model ids carry a "uni/" prefix, and a
# provider of that name would make the plugin strip it
# (provider-prefix resolution) before proxying. With a
# non-colliding name the whole slash-bearing id below is a
# bare model id and is forwarded verbatim.
models: ["uni/deepseek-v4-flash-0731"]
groups:
eng:
limits:
requestsPerMinute: 1000
users:
inline:
- name: alice-real
group: eng
apiKey: "sk-int-real"