333 lines
8.2 KiB
Go
333 lines
8.2 KiB
Go
// Package proxy 提供健康检查功能的测试。
|
||
//
|
||
// 该文件测试健康检查模块的各项功能,包括:
|
||
// - 健康检查器创建
|
||
// - 默认值应用
|
||
// - 自定义配置
|
||
// - 负值配置处理
|
||
// - 零值配置处理
|
||
// - 启动和停止控制
|
||
// - 目标健康检查
|
||
// - 超时处理
|
||
// - 连接失败处理
|
||
// - 标记不健康
|
||
//
|
||
// 作者:xfy
|
||
package proxy
|
||
|
||
import (
|
||
"net/http"
|
||
"net/http/httptest"
|
||
"testing"
|
||
"time"
|
||
|
||
"rua.plus/lolly/internal/config"
|
||
"rua.plus/lolly/internal/loadbalance"
|
||
)
|
||
|
||
// TestCheckTarget 测试 checkTarget 方法。
|
||
func TestCheckTarget(t *testing.T) {
|
||
t.Run("健康响应", func(t *testing.T) {
|
||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||
if r.URL.Path != healthPath {
|
||
t.Errorf("请求路径 = %q, want %q", r.URL.Path, healthPath)
|
||
}
|
||
w.WriteHeader(http.StatusOK)
|
||
}))
|
||
defer server.Close()
|
||
|
||
target := &loadbalance.Target{
|
||
URL: server.URL,
|
||
}
|
||
target.Healthy.Store(false)
|
||
|
||
checker := NewHealthChecker([]*loadbalance.Target{target}, &config.HealthCheckConfig{
|
||
Interval: 1 * time.Hour,
|
||
Timeout: 5 * time.Second,
|
||
Path: healthPath,
|
||
})
|
||
|
||
checker.checkTarget(target)
|
||
|
||
if !target.Healthy.Load() {
|
||
t.Error("健康响应后 target 应标记为 healthy")
|
||
}
|
||
})
|
||
|
||
t.Run("不健康响应", func(t *testing.T) {
|
||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||
w.WriteHeader(http.StatusInternalServerError)
|
||
}))
|
||
defer server.Close()
|
||
|
||
target := &loadbalance.Target{
|
||
URL: server.URL,
|
||
}
|
||
target.Healthy.Store(true)
|
||
|
||
checker := NewHealthChecker([]*loadbalance.Target{target}, &config.HealthCheckConfig{
|
||
Interval: 1 * time.Hour,
|
||
Timeout: 5 * time.Second,
|
||
Path: healthPath,
|
||
})
|
||
|
||
checker.checkTarget(target)
|
||
|
||
if target.Healthy.Load() {
|
||
t.Error("5xx 响应后 target 应标记为 unhealthy")
|
||
}
|
||
})
|
||
|
||
t.Run("超时", func(t *testing.T) {
|
||
server := httptest.NewServer(http.HandlerFunc(func(_ http.ResponseWriter, _ *http.Request) {
|
||
time.Sleep(100 * time.Millisecond)
|
||
}))
|
||
defer server.Close()
|
||
|
||
target := &loadbalance.Target{
|
||
URL: server.URL,
|
||
}
|
||
target.Healthy.Store(true)
|
||
|
||
checker := NewHealthChecker([]*loadbalance.Target{target}, &config.HealthCheckConfig{
|
||
Interval: 1 * time.Hour,
|
||
Timeout: 10 * time.Millisecond,
|
||
Path: healthPath,
|
||
})
|
||
|
||
checker.checkTarget(target)
|
||
|
||
if target.Healthy.Load() {
|
||
t.Error("超时后 target 应标记为 unhealthy")
|
||
}
|
||
})
|
||
|
||
t.Run("连接失败", func(t *testing.T) {
|
||
target := &loadbalance.Target{
|
||
URL: "http://invalid-host-that-does-not-exist:99999",
|
||
}
|
||
target.Healthy.Store(true)
|
||
|
||
checker := NewHealthChecker([]*loadbalance.Target{target}, &config.HealthCheckConfig{
|
||
Interval: 1 * time.Hour,
|
||
Timeout: 100 * time.Millisecond,
|
||
Path: healthPath,
|
||
})
|
||
|
||
checker.checkTarget(target)
|
||
|
||
if target.Healthy.Load() {
|
||
t.Error("连接失败后 target 应标记为 unhealthy")
|
||
}
|
||
})
|
||
|
||
t.Run("3xx 重定向响应", func(t *testing.T) {
|
||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||
w.WriteHeader(http.StatusMovedPermanently)
|
||
}))
|
||
defer server.Close()
|
||
|
||
target := &loadbalance.Target{
|
||
URL: server.URL,
|
||
}
|
||
target.Healthy.Store(true)
|
||
|
||
checker := NewHealthChecker([]*loadbalance.Target{target}, &config.HealthCheckConfig{
|
||
Interval: 1 * time.Hour,
|
||
Timeout: 5 * time.Second,
|
||
Path: healthPath,
|
||
})
|
||
|
||
checker.checkTarget(target)
|
||
|
||
if target.Healthy.Load() {
|
||
t.Error("3xx 响应后 target 应标记为 unhealthy")
|
||
}
|
||
})
|
||
|
||
t.Run("4xx 客户端错误响应", func(t *testing.T) {
|
||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||
w.WriteHeader(http.StatusNotFound)
|
||
}))
|
||
defer server.Close()
|
||
|
||
target := &loadbalance.Target{
|
||
URL: server.URL,
|
||
}
|
||
target.Healthy.Store(true)
|
||
|
||
checker := NewHealthChecker([]*loadbalance.Target{target}, &config.HealthCheckConfig{
|
||
Interval: 1 * time.Hour,
|
||
Timeout: 5 * time.Second,
|
||
Path: healthPath,
|
||
})
|
||
|
||
checker.checkTarget(target)
|
||
|
||
if target.Healthy.Load() {
|
||
t.Error("4xx 响应后 target 应标记为 unhealthy")
|
||
}
|
||
})
|
||
|
||
t.Run("2xx 成功响应", func(t *testing.T) {
|
||
tests := []struct {
|
||
name string
|
||
statusCode int
|
||
}{
|
||
{"200 OK", http.StatusOK},
|
||
{"201 Created", http.StatusCreated},
|
||
{"204 No Content", http.StatusNoContent},
|
||
}
|
||
|
||
for _, tt := range tests {
|
||
t.Run(tt.name, func(t *testing.T) {
|
||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||
w.WriteHeader(tt.statusCode)
|
||
}))
|
||
defer server.Close()
|
||
|
||
target := &loadbalance.Target{
|
||
URL: server.URL,
|
||
}
|
||
target.Healthy.Store(false)
|
||
|
||
checker := NewHealthChecker([]*loadbalance.Target{target}, &config.HealthCheckConfig{
|
||
Interval: 1 * time.Hour,
|
||
Timeout: 5 * time.Second,
|
||
Path: healthPath,
|
||
})
|
||
|
||
checker.checkTarget(target)
|
||
|
||
if !target.Healthy.Load() {
|
||
t.Errorf("%d 响应后 target 应标记为 healthy", tt.statusCode)
|
||
}
|
||
})
|
||
}
|
||
})
|
||
}
|
||
|
||
// TestMarkUnhealthy 测试 MarkUnhealthy 方法。
|
||
func TestMarkUnhealthy(t *testing.T) {
|
||
t.Run("标记不健康", func(t *testing.T) {
|
||
target := &loadbalance.Target{
|
||
URL: "http://backend1:8080",
|
||
}
|
||
target.Healthy.Store(true)
|
||
|
||
checker := NewHealthChecker([]*loadbalance.Target{target}, &config.HealthCheckConfig{
|
||
Interval: 1 * time.Hour,
|
||
Timeout: 5 * time.Second,
|
||
Path: healthPath,
|
||
})
|
||
|
||
checker.MarkUnhealthy(target)
|
||
|
||
if target.Healthy.Load() {
|
||
t.Error("MarkUnhealthy 后 target 应标记为 unhealthy")
|
||
}
|
||
})
|
||
|
||
t.Run("已不健康的 target 再次标记", func(t *testing.T) {
|
||
target := &loadbalance.Target{
|
||
URL: "http://backend1:8080",
|
||
}
|
||
target.Healthy.Store(false)
|
||
|
||
checker := NewHealthChecker([]*loadbalance.Target{target}, &config.HealthCheckConfig{
|
||
Interval: 1 * time.Hour,
|
||
Timeout: 5 * time.Second,
|
||
Path: healthPath,
|
||
})
|
||
|
||
checker.MarkUnhealthy(target)
|
||
|
||
if target.Healthy.Load() {
|
||
t.Error("MarkUnhealthy 后 target 应保持 unhealthy 状态")
|
||
}
|
||
})
|
||
|
||
t.Run("多 target 场景", func(t *testing.T) {
|
||
target1 := &loadbalance.Target{
|
||
URL: "http://backend1:8080",
|
||
}
|
||
target1.Healthy.Store(true)
|
||
target2 := &loadbalance.Target{
|
||
URL: "http://backend2:8080",
|
||
}
|
||
target2.Healthy.Store(true)
|
||
|
||
checker := NewHealthChecker([]*loadbalance.Target{target1, target2}, &config.HealthCheckConfig{
|
||
Interval: 1 * time.Hour,
|
||
Timeout: 5 * time.Second,
|
||
Path: healthPath,
|
||
})
|
||
|
||
checker.MarkUnhealthy(target1)
|
||
|
||
if target1.Healthy.Load() {
|
||
t.Error("target1 应标记为 unhealthy")
|
||
}
|
||
if !target2.Healthy.Load() {
|
||
t.Error("target2 应保持 healthy")
|
||
}
|
||
})
|
||
}
|
||
|
||
// TestMarkUnhealthy_WithSlowStartManager 测试 MarkUnhealthy 与 SlowStartManager 集成。
|
||
func TestMarkUnhealthy_WithSlowStartManager(t *testing.T) {
|
||
target := &loadbalance.Target{
|
||
URL: "http://127.0.0.1:8080",
|
||
Weight: 100,
|
||
}
|
||
target.Healthy.Store(true)
|
||
target.SlowStart = 30 * time.Second
|
||
|
||
checker := NewHealthChecker([]*loadbalance.Target{target}, &config.HealthCheckConfig{
|
||
Interval: 1 * time.Hour,
|
||
Timeout: 5 * time.Second,
|
||
Path: "/health",
|
||
SlowStart: 30 * time.Second,
|
||
})
|
||
|
||
// 先标记为健康以初始化慢启动
|
||
checker.MarkHealthy(target)
|
||
|
||
// 标记目标为不健康
|
||
checker.MarkUnhealthy(target)
|
||
|
||
if target.Healthy.Load() {
|
||
t.Error("target 应标记为 unhealthy")
|
||
}
|
||
}
|
||
|
||
// TestMarkHealthy_WithSlowStartManager 测试 MarkHealthy 与 SlowStartManager 集成。
|
||
func TestMarkHealthy_WithSlowStartManager(t *testing.T) {
|
||
target := &loadbalance.Target{
|
||
URL: "http://127.0.0.1:8080",
|
||
Weight: 100,
|
||
}
|
||
target.Healthy.Store(false)
|
||
target.SlowStart = 30 * time.Second
|
||
|
||
checker := NewHealthChecker([]*loadbalance.Target{target}, &config.HealthCheckConfig{
|
||
Interval: 1 * time.Hour,
|
||
Timeout: 5 * time.Second,
|
||
Path: "/health",
|
||
SlowStart: 30 * time.Second,
|
||
})
|
||
|
||
// 标记目标为健康
|
||
checker.MarkHealthy(target)
|
||
|
||
if !target.Healthy.Load() {
|
||
t.Error("target 应标记为 healthy")
|
||
}
|
||
|
||
// 验证慢启动已开始(EffectiveWeight 应被设置为 1)
|
||
ew := target.EffectiveWeight.Load()
|
||
if ew <= 0 {
|
||
t.Errorf("慢启动 EffectiveWeight 应大于 0,got: %d", ew)
|
||
}
|
||
}
|