feat: M3 load balancing + health check (model/render/admin API status/UI)

This commit is contained in:
2026-08-17 17:45:03 +08:00
parent bd3a62a017
commit 467c7124ab
10 changed files with 658 additions and 33 deletions

View File

@ -159,3 +159,108 @@ func TestRenderHTTPSUsesCustomDomains(t *testing.T) {
t.Fatalf("customDomains = %+v", p.CustomDomains)
}
}
func TestRenderLoadBalancerAndHealthCheck(t *testing.T) {
remote := store.Remote{Name: "srv", IP: "1.2.3.4", Port: 7000, Token: "secret"}
data, err := Render(remote, []Proxy{
{Name: "web1", Type: "tcp", LocalIP: "127.0.0.1", LocalPort: 8080, RemotePort: 8080,
LBGroup: "web", LBGroupKey: "k",
HealthCheckType: "tcp", HealthCheckTimeout: 3, HealthCheckMaxFailed: 2, HealthCheckInterval: 5},
})
if err != nil {
t.Fatal(err)
}
var cfg Config
if err := json.Unmarshal(data, &cfg); err != nil {
t.Fatal(err)
}
p := cfg.Proxies[0]
if p.LoadBalancer == nil || p.LoadBalancer.Group != "web" || p.LoadBalancer.GroupKey != "k" {
t.Fatalf("loadBalancer = %+v", p.LoadBalancer)
}
if p.HealthCheck == nil || p.HealthCheck.Type != "tcp" || p.HealthCheck.IntervalSeconds != 5 ||
p.HealthCheck.MaxFailed != 2 || p.HealthCheck.TimeoutSeconds != 3 {
t.Fatalf("healthCheck = %+v", p.HealthCheck)
}
}
func TestRenderHealthCheckHTTPWithHeaders(t *testing.T) {
remote := store.Remote{Name: "srv", IP: "1.2.3.4", Port: 7000}
data, err := Render(remote, []Proxy{
{Name: "web", Type: "http", LocalIP: "127.0.0.1", LocalPort: 8080,
CustomDomains: []string{"app.example.com"},
HealthCheckType: "http",
HealthCheckPath: "/healthz",
HealthCheckTimeout: 2,
HealthCheckMaxFailed: 3,
HealthCheckInterval: 7,
HTTPHeaders: map[string]string{"X-Probe": "1"}},
})
if err != nil {
t.Fatal(err)
}
var cfg Config
_ = json.Unmarshal(data, &cfg)
p := cfg.Proxies[0]
if p.HealthCheck == nil || p.HealthCheck.Type != "http" || p.HealthCheck.Path != "/healthz" ||
p.HealthCheck.IntervalSeconds != 7 {
t.Fatalf("healthCheck = %+v", p.HealthCheck)
}
if p.HealthCheck.HTTPHeaders == nil || len(p.HealthCheck.HTTPHeaders) == 0 ||
p.HealthCheck.HTTPHeaders[0].Name != "X-Probe" || p.HealthCheck.HTTPHeaders[0].Value != "1" {
t.Fatalf("healthCheck.httpHeaders = %+v", p.HealthCheck.HTTPHeaders)
}
// http proxy must still use customDomains and no remotePort
if len(p.CustomDomains) != 1 || p.CustomDomains[0] != "app.example.com" || p.RemotePort != 0 {
t.Fatalf("http proxy routing = customDomains=%v remotePort=%d", p.CustomDomains, p.RemotePort)
}
}
func TestRenderHealthCheckOmittedWhenOff(t *testing.T) {
remote := store.Remote{Name: "srv", IP: "1.2.3.4", Port: 7000}
data, err := Render(remote, []Proxy{
{Name: "web", Type: "tcp", LocalIP: "127.0.0.1", LocalPort: 8080, RemotePort: 8080},
})
if err != nil {
t.Fatal(err)
}
var cfg Config
_ = json.Unmarshal(data, &cfg)
p := cfg.Proxies[0]
if p.LoadBalancer != nil || p.HealthCheck != nil {
t.Fatalf("lb/health should be omitted, got lb=%+v hc=%+v", p.LoadBalancer, p.HealthCheck)
}
}
func TestRenderAdminWebServer(t *testing.T) {
remote := store.Remote{Name: "srv", IP: "1.2.3.4", Port: 7000, Token: "secret",
AdminAddr: "127.0.0.1", AdminPort: 7400, AdminUser: "admin", AdminPassword: "pw"}
data, err := Render(remote, nil)
if err != nil {
t.Fatal(err)
}
var cfg Config
if err := json.Unmarshal(data, &cfg); err != nil {
t.Fatal(err)
}
if cfg.WebServer == nil {
t.Fatal("webServer missing when AdminPort set")
}
if cfg.WebServer.Port != 7400 || cfg.WebServer.Addr != "127.0.0.1" ||
cfg.WebServer.User != "admin" || cfg.WebServer.Password != "pw" {
t.Fatalf("webServer = %+v", cfg.WebServer)
}
}
func TestRenderAdminWebServerOmitted(t *testing.T) {
remote := store.Remote{Name: "srv", IP: "1.2.3.4", Port: 7000}
data, err := Render(remote, nil)
if err != nil {
t.Fatal(err)
}
var cfg Config
_ = json.Unmarshal(data, &cfg)
if cfg.WebServer != nil {
t.Fatalf("webServer should be omitted, got %+v", cfg.WebServer)
}
}