lolly/internal/proxy/health_test.go

333 lines
8.2 KiB
Go
Raw Permalink 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.

// 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 应大于 0got: %d", ew)
}
}