lolly/internal/integration/e2e_failover_bench_test.go
xfy b5c3c0954f
Some checks failed
CI / Test (push) Failing after 23s
CI / Build (push) Has been skipped
fix: address repository review issues
Security:
- Enforce client_max_body_size in HTTP/2 and HTTP/3 adapters before buffering
- Use trusted-proxy-aware client IP extraction for access control and rate limiting
- Include Host and Vary headers in proxy cache key to prevent cache poisoning
- Harden static file path traversal check with filepath.Clean + prefix validation
- Honor proxy_ssl config for WebSocket upstream TLS

Proxy/handler bugs:
- Decrement WebSocket connection count immediately on return (not deferred in retry loop)
- Remove ineffective headersPool
- Coalesce concurrent background cache refreshes with singleflight

Dev/build:
- Fix Makefile run and test-config targets
- Remove broken wget healthcheck from docker-compose
- Add missing integration build tags
- Fix go vet warnings in tests

CI:
- Run golangci-lint and integration tests in Gitea Actions

Refs: docs/superpowers/plans/2026-06-17-fix-review-issues.md
2026-06-17 11:17:53 +08:00

293 lines
6.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//go:build integration
// Package integration 提供后端故障切换 E2E 基准测试。
//
// 该文件测试负载均衡器剔除/恢复后端的开销。
//
// 测试场景:
// - 健康后端正常选择
// - 后端标记不健康后的剔除开销
// - 后端重新标记健康后的恢复开销
//
// 作者xfy
package integration
import (
"strconv"
"sync/atomic"
"testing"
"time"
"github.com/valyala/fasthttp"
"rua.plus/lolly/internal/config"
"rua.plus/lolly/internal/loadbalance"
"rua.plus/lolly/internal/proxy"
)
// setupFailoverBackends 创建多后端用于故障切换测试。
//
// 参数:
// - count: 后端数量
// - healthyCount: 初始健康后端数量
//
// 返回值:
// - targets: 目标列表
// - cleanups: 清理函数列表
func setupFailoverBackends(b *testing.B, count, healthyCount int) ([]*loadbalance.Target, []func()) {
b.Helper()
targets := make([]*loadbalance.Target, count)
cleanups := make([]func(), count)
for i := range count {
addr, cleanup := setupNetworkBackend(b, fasthttp.StatusOK, []byte(`{"backend":`+strconv.Itoa(i)+`}`))
cleanups[i] = cleanup
targets[i] = &loadbalance.Target{
URL: "http://" + addr,
Weight: 1,
}
// 设置健康状态
if i < healthyCount {
targets[i].Healthy.Store(true)
} else {
targets[i].Healthy.Store(false)
}
}
return targets, cleanups
}
// BenchmarkE2EFailover_NormalSelect 测试健康后端正常选择。
//
// 所有后端健康时的负载均衡选择开销。
func BenchmarkE2EFailover_NormalSelect(b *testing.B) {
targets, cleanups := setupFailoverBackends(b, 5, 5)
defer func() {
for _, c := range cleanups {
c()
}
}()
cfg := &config.ProxyConfig{
Path: "/api",
LoadBalance: "round_robin",
Timeout: config.ProxyTimeout{
Connect: 5 * time.Second,
Read: 30 * time.Second,
Write: 30 * time.Second,
},
}
p, err := proxy.NewProxy(cfg, targets, nil, nil)
if err != nil {
b.Fatalf("NewProxy() error: %v", err)
}
warmupProxy(p, "/api/test", 10)
b.ReportAllocs()
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
ctx := &fasthttp.RequestCtx{}
ctx.Request.SetRequestURI("/api/test")
ctx.Request.Header.SetMethod(fasthttp.MethodGet)
p.ServeHTTP(ctx)
}
})
}
// BenchmarkE2EFailover_OneUnhealthy 测试一个后端不健康。
//
// 4/5 后端健康时的选择开销。
func BenchmarkE2EFailover_OneUnhealthy(b *testing.B) {
targets, cleanups := setupFailoverBackends(b, 5, 4)
defer func() {
for _, c := range cleanups {
c()
}
}()
cfg := &config.ProxyConfig{
Path: "/api",
LoadBalance: "round_robin",
Timeout: config.ProxyTimeout{
Connect: 5 * time.Second,
Read: 30 * time.Second,
Write: 30 * time.Second,
},
}
p, err := proxy.NewProxy(cfg, targets, nil, nil)
if err != nil {
b.Fatalf("NewProxy() error: %v", err)
}
warmupProxy(p, "/api/test", 10)
b.ReportAllocs()
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
ctx := &fasthttp.RequestCtx{}
ctx.Request.SetRequestURI("/api/test")
ctx.Request.Header.SetMethod(fasthttp.MethodGet)
p.ServeHTTP(ctx)
}
})
}
// BenchmarkE2EFailover_MostUnhealthy 测试多数后端不健康。
//
// 1/5 后端健康时的选择开销(剔除开销增大)。
func BenchmarkE2EFailover_MostUnhealthy(b *testing.B) {
targets, cleanups := setupFailoverBackends(b, 5, 1)
defer func() {
for _, c := range cleanups {
c()
}
}()
cfg := &config.ProxyConfig{
Path: "/api",
LoadBalance: "round_robin",
Timeout: config.ProxyTimeout{
Connect: 5 * time.Second,
Read: 30 * time.Second,
Write: 30 * time.Second,
},
}
p, err := proxy.NewProxy(cfg, targets, nil, nil)
if err != nil {
b.Fatalf("NewProxy() error: %v", err)
}
warmupProxy(p, "/api/test", 10)
b.ReportAllocs()
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
ctx := &fasthttp.RequestCtx{}
ctx.Request.SetRequestURI("/api/test")
ctx.Request.Header.SetMethod(fasthttp.MethodGet)
p.ServeHTTP(ctx)
}
})
}
// BenchmarkE2EFailover_DynamicToggle 测试动态健康状态切换。
//
// 模拟后端健康状态在测试中变化。
func BenchmarkE2EFailover_DynamicToggle(b *testing.B) {
targets, cleanups := setupFailoverBackends(b, 3, 3)
defer func() {
for _, c := range cleanups {
c()
}
}()
cfg := &config.ProxyConfig{
Path: "/api",
LoadBalance: "round_robin",
Timeout: config.ProxyTimeout{
Connect: 5 * time.Second,
Read: 30 * time.Second,
Write: 30 * time.Second,
},
}
p, err := proxy.NewProxy(cfg, targets, nil, nil)
if err != nil {
b.Fatalf("NewProxy() error: %v", err)
}
warmupProxy(p, "/api/test", 10)
var toggleCounter atomic.Uint64
b.ReportAllocs()
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
// 每 100 次请求切换一个后端的健康状态
count := toggleCounter.Add(1)
if count%100 == 0 {
targetIdx := int(count/100) % len(targets)
current := targets[targetIdx].Healthy.Load()
targets[targetIdx].Healthy.Store(!current)
}
ctx := &fasthttp.RequestCtx{}
ctx.Request.SetRequestURI("/api/test")
ctx.Request.Header.SetMethod(fasthttp.MethodGet)
p.ServeHTTP(ctx)
}
})
}
// BenchmarkE2EFailover_AllUnhealthy 测试所有后端不健康。
//
// 无可用后端时的选择开销(应该返回错误)。
func BenchmarkE2EFailover_AllUnhealthy(b *testing.B) {
targets, cleanups := setupFailoverBackends(b, 3, 0)
defer func() {
for _, c := range cleanups {
c()
}
}()
cfg := &config.ProxyConfig{
Path: "/api",
LoadBalance: "round_robin",
Timeout: config.ProxyTimeout{
Connect: 5 * time.Second,
Read: 30 * time.Second,
Write: 30 * time.Second,
},
}
p, err := proxy.NewProxy(cfg, targets, nil, nil)
if err != nil {
b.Fatalf("NewProxy() error: %v", err)
}
b.ReportAllocs()
b.ResetTimer()
for b.Loop() {
ctx := &fasthttp.RequestCtx{}
ctx.Request.SetRequestURI("/api/test")
ctx.Request.Header.SetMethod(fasthttp.MethodGet)
p.ServeHTTP(ctx)
}
}
// BenchmarkE2EFailover_SelectOnly 测试纯选择开销(无实际请求)。
//
// 验证负载均衡器选择逻辑的分配。
func BenchmarkE2EFailover_SelectOnly(b *testing.B) {
targets := make([]*loadbalance.Target, 5)
for i := range 5 {
targets[i] = &loadbalance.Target{
URL: "http://backend" + strconv.Itoa(i) + ":8080",
Weight: 1,
}
targets[i].Healthy.Store(true)
}
rr := loadbalance.NewRoundRobin()
b.ReportAllocs()
b.ResetTimer()
for b.Loop() {
_ = rr.Select(targets)
}
}