Files
traefik-llmgateway/integration/traefik/dynamic.yml.tmpl
T
lukaszraczylo 25578fb282 test: v0.2 integration coverage
Extends the integration suite (task 5) to exercise retry, cache, images/audio, and admin under real Traefik+Yaegi:

- mock/main.go: a /flaky/v1/chat/completions endpoint failing the first
  request per process with 503 then succeeding (atomic counter, response
  carries mock_attempt for verification); /v1/images/generations returning
  a canned OpenAI images response; /v1/audio/speech returning binary bytes
  under audio/mpeg; gemini mode's :predict returning canned Imagen
  predictions.
- dynamic.yml.tmpl: retry{enabled:true,attempts:1}, cache{enabled:true,
  ttl:2m}, admin{enabled:true}; a flaky provider pointed at the new mock
  endpoint; an admin:true inline user; alice and admin both given a
  generous-but-real per-user limits block so their own request counters
  are trackable via the usage API.
- integration_test.go: TestRetryFlakyRecovers (client 200, exactly two
  upstream hits proven via mock_attempt), TestResponseCache (miss then
  hit, tokensPerDay unchanged across the hit via the admin usage API),
  TestImagesGenerations (openai native forward + gemini Imagen
  translation), TestAudioSpeech (binary round trip, Content-Type
  forwarded), TestAdminDashboard (unauthenticated shell, admin vs
  non-admin gate, usage reflecting this suite's own traffic).

make integration exits 0 (verified across multiple fresh runs).
2026-08-20 20:14:38 +01:00

144 lines
5.5 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 test 8'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. 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"