Go reverse proxy for ollama.com that balances requests across multiple API keys with round-robin and failover on 429/5xx. Exposes both native Ollama API (/api/*) and OpenAI-compatible (/v1/*) passthrough. - Round-robin balancer with per-account cooldown (60s default) - Pre-stream failover: 429 → cooldown + next account; 5xx → next account - Streaming invariant: once 2xx starts streaming, no account switch - SSE (text/event-stream) and NDJSON passthrough with http.Flusher - CLI: accounts add/list/remove/set-base-url, serve, version - Accounts stored in ~/.config/ollama-proxy/accounts.json (chmod 0600) - systemd unit (User=dueattendant149, 127.0.0.1:11435, Restart=always) - 43 tests (unit + integration with httptest upstream) - opencode integration: custom provider 'ocp' with explicit model list - Requires NO_PROXY=127.0.0.1,localhost when HTTP_PROXY is set
103 lines
2.7 KiB
Go
103 lines
2.7 KiB
Go
package config
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestDefaultServerConfig(t *testing.T) {
|
|
c := DefaultServerConfig()
|
|
if c.Addr != "127.0.0.1:11435" {
|
|
t.Errorf("Addr = %q", c.Addr)
|
|
}
|
|
if c.BaseURL != "https://ollama.com" {
|
|
t.Errorf("BaseURL = %q", c.BaseURL)
|
|
}
|
|
if c.Cooldown != 60*time.Second {
|
|
t.Errorf("Cooldown = %v", c.Cooldown)
|
|
}
|
|
if c.Retries != 3 {
|
|
t.Errorf("Retries = %d", c.Retries)
|
|
}
|
|
if c.LogLevel != "info" {
|
|
t.Errorf("LogLevel = %q", c.LogLevel)
|
|
}
|
|
}
|
|
|
|
func TestServerConfig_Validate(t *testing.T) {
|
|
good := DefaultServerConfig()
|
|
if err := good.Validate(); err != nil {
|
|
t.Errorf("default config invalid: %v", err)
|
|
}
|
|
|
|
cases := []struct {
|
|
name string
|
|
mut func(*ServerConfig)
|
|
}{
|
|
{"empty addr", func(c *ServerConfig) { c.Addr = "" }},
|
|
{"empty base_url", func(c *ServerConfig) { c.BaseURL = "" }},
|
|
{"bad scheme", func(c *ServerConfig) { c.BaseURL = "ftp://x" }},
|
|
{"negative cooldown", func(c *ServerConfig) { c.Cooldown = -1 * time.Second }},
|
|
{"zero retries", func(c *ServerConfig) { c.Retries = 0 }},
|
|
{"bad log level", func(c *ServerConfig) { c.LogLevel = "trace" }},
|
|
}
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
c := DefaultServerConfig()
|
|
tc.mut(&c)
|
|
if err := c.Validate(); err == nil {
|
|
t.Errorf("Validate(%s) = nil, want error", tc.name)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestServerConfig_ApplyEnv(t *testing.T) {
|
|
t.Setenv("OLLAMA_PROXY_ADDR", "0.0.0.0:9000")
|
|
t.Setenv("OLLAMA_PROXY_BASE_URL", "https://staging.example.com")
|
|
t.Setenv("OLLAMA_PROXY_COOLDOWN", "120s")
|
|
t.Setenv("OLLAMA_PROXY_RETRIES", "5")
|
|
t.Setenv("OLLAMA_PROXY_LOG_LEVEL", "debug")
|
|
|
|
c := DefaultServerConfig()
|
|
c.ApplyEnv()
|
|
|
|
if c.Addr != "0.0.0.0:9000" {
|
|
t.Errorf("Addr = %q", c.Addr)
|
|
}
|
|
if c.BaseURL != "https://staging.example.com" {
|
|
t.Errorf("BaseURL = %q", c.BaseURL)
|
|
}
|
|
if c.Cooldown != 120*time.Second {
|
|
t.Errorf("Cooldown = %v", c.Cooldown)
|
|
}
|
|
if c.Retries != 5 {
|
|
t.Errorf("Retries = %d", c.Retries)
|
|
}
|
|
if c.LogLevel != "debug" {
|
|
t.Errorf("LogLevel = %q", c.LogLevel)
|
|
}
|
|
}
|
|
|
|
func TestServerConfig_ApplyEnv_IgnoresInvalid(t *testing.T) {
|
|
t.Setenv("OLLAMA_PROXY_COOLDOWN", "not-a-duration")
|
|
t.Setenv("OLLAMA_PROXY_RETRIES", "NaN")
|
|
t.Setenv("OLLAMA_PROXY_LOG_LEVEL", "trace")
|
|
t.Setenv("OLLAMA_PROXY_ADDR", "")
|
|
|
|
c := DefaultServerConfig()
|
|
c.ApplyEnv()
|
|
|
|
if c.Cooldown != 60*time.Second {
|
|
t.Errorf("bad cooldown env should be ignored, got %v", c.Cooldown)
|
|
}
|
|
if c.Retries != 3 {
|
|
t.Errorf("bad retries env should be ignored, got %d", c.Retries)
|
|
}
|
|
if c.LogLevel != "info" {
|
|
t.Errorf("bad log_level env should be ignored, got %q", c.LogLevel)
|
|
}
|
|
if c.Addr != "127.0.0.1:11435" {
|
|
t.Errorf("empty addr env should keep default, got %q", c.Addr)
|
|
}
|
|
}
|