From 65ee6d40bda1aa0618d6a08fd91096c24a228161 Mon Sep 17 00:00:00 2001 From: yusing Date: Thu, 4 Dec 2025 15:19:10 +0800 Subject: [PATCH] refactor(healthcheck): streamline health check configuration and defaults - Moved health check constants from common package alongside type definition. - Updated health check configuration to use struct directly instead of pointers. - Introduced global default health check config --- internal/common/constants.go | 8 ----- internal/config/types/config.go | 5 +++ .../entrypoint/entrypoint_benchmark_test.go | 4 +-- internal/route/fileserver.go | 1 + internal/route/route.go | 18 +++-------- internal/route/route_test.go | 14 +++++++- internal/types/healthcheck_config.go | 32 +++++++++++++------ internal/types/routes.go | 2 +- .../watcher/health/monitor/agent_proxied.go | 2 +- internal/watcher/health/monitor/docker.go | 2 +- internal/watcher/health/monitor/fileserver.go | 2 +- internal/watcher/health/monitor/http.go | 2 +- internal/watcher/health/monitor/monitor.go | 9 +++--- .../watcher/health/monitor/monitor_test.go | 14 ++++---- internal/watcher/health/monitor/raw.go | 2 +- 15 files changed, 66 insertions(+), 51 deletions(-) diff --git a/internal/common/constants.go b/internal/common/constants.go index d0b35549..6c542b3b 100644 --- a/internal/common/constants.go +++ b/internal/common/constants.go @@ -1,9 +1,5 @@ package common -import ( - "time" -) - // file, folder structure const ( @@ -38,10 +34,6 @@ var RequiredDirectories = []string{ const DockerHostFromEnv = "$DOCKER_HOST" const ( - HealthCheckIntervalDefault = 5 * time.Second - HealthCheckTimeoutDefault = 5 * time.Second - HealthCheckDownNotifyDelayDefault = 15 * time.Second - WakeTimeoutDefault = "3m" StopTimeoutDefault = "3m" StopMethodDefault = "stop" diff --git a/internal/config/types/config.go b/internal/config/types/config.go index 21f8ce23..3cc72aed 100644 --- a/internal/config/types/config.go +++ b/internal/config/types/config.go @@ -14,6 +14,7 @@ import ( "github.com/yusing/godoxy/internal/notif" "github.com/yusing/godoxy/internal/proxmox" "github.com/yusing/godoxy/internal/serialization" + "github.com/yusing/godoxy/internal/types" gperr "github.com/yusing/goutils/errs" ) @@ -25,8 +26,12 @@ type ( Providers Providers `json:"providers"` MatchDomains []string `json:"match_domains" validate:"domain_name"` Homepage homepage.Config `json:"homepage"` + Defaults Defaults `json:"defaults"` TimeoutShutdown int `json:"timeout_shutdown" validate:"gte=0"` } + Defaults struct { + HealthCheck types.HealthCheckConfig `json:"healthcheck"` + } Providers struct { Files []string `json:"include" yaml:"include,omitempty" validate:"dive,filepath"` Docker map[string]string `json:"docker" yaml:"docker,omitempty" validate:"non_empty_docker_keys,dive,unix_addr|url"` diff --git a/internal/entrypoint/entrypoint_benchmark_test.go b/internal/entrypoint/entrypoint_benchmark_test.go index 562bab5c..2b432199 100644 --- a/internal/entrypoint/entrypoint_benchmark_test.go +++ b/internal/entrypoint/entrypoint_benchmark_test.go @@ -82,7 +82,7 @@ func BenchmarkEntrypointReal(b *testing.B) { Scheme: routeTypes.SchemeHTTP, Host: host, Port: route.Port{Proxy: portInt}, - HealthCheck: &types.HealthCheckConfig{Disable: true}, + HealthCheck: types.HealthCheckConfig{Disable: true}, } err = r.Validate() @@ -125,7 +125,7 @@ func BenchmarkEntrypoint(b *testing.B) { Port: route.Port{ Proxy: 8080, }, - HealthCheck: &types.HealthCheckConfig{ + HealthCheck: types.HealthCheckConfig{ Disable: true, }, } diff --git a/internal/route/fileserver.go b/internal/route/fileserver.go index bc15d21f..80c531a6 100644 --- a/internal/route/fileserver.go +++ b/internal/route/fileserver.go @@ -9,6 +9,7 @@ import ( gphttp "github.com/yusing/godoxy/internal/net/gphttp" "github.com/yusing/godoxy/internal/net/gphttp/middleware" "github.com/yusing/godoxy/internal/route/routes" + "github.com/yusing/godoxy/internal/types" "github.com/yusing/godoxy/internal/watcher/health/monitor" gperr "github.com/yusing/goutils/errs" "github.com/yusing/goutils/task" diff --git a/internal/route/route.go b/internal/route/route.go index 83841a37..f9f8a8e0 100644 --- a/internal/route/route.go +++ b/internal/route/route.go @@ -16,6 +16,7 @@ import ( "github.com/docker/docker/api/types/container" "github.com/rs/zerolog/log" "github.com/yusing/godoxy/agent/pkg/agent" + config "github.com/yusing/godoxy/internal/config/types" "github.com/yusing/godoxy/internal/docker" "github.com/yusing/godoxy/internal/homepage" homepagecfg "github.com/yusing/godoxy/internal/homepage/types" @@ -50,7 +51,7 @@ type ( PathPatterns []string `json:"path_patterns,omitempty" extensions:"x-nullable"` Rules rules.Rules `json:"rules,omitempty" extension:"x-nullable"` RuleFile string `json:"rule_file,omitempty" extensions:"x-nullable"` - HealthCheck *types.HealthCheckConfig `json:"healthcheck,omitempty" extensions:"x-nullable"` // null on load-balancer routes + HealthCheck types.HealthCheckConfig `json:"healthcheck,omitempty" extensions:"x-nullable"` // null on load-balancer routes LoadBalance *types.LoadBalancerConfig `json:"load_balance,omitempty" extensions:"x-nullable"` Middlewares map[string]types.LabelMap `json:"middlewares,omitempty" extensions:"x-nullable"` Homepage *homepage.ItemConfig `json:"homepage"` @@ -498,7 +499,7 @@ func (r *Route) IdlewatcherConfig() *types.IdlewatcherConfig { return r.Idlewatcher } -func (r *Route) HealthCheckConfig() *types.HealthCheckConfig { +func (r *Route) HealthCheckConfig() types.HealthCheckConfig { return r.HealthCheck } @@ -772,17 +773,7 @@ func (r *Route) Finalize() { } r.Port.Listening, r.Port.Proxy = lp, pp - - if r.HealthCheck == nil { - r.HealthCheck = types.DefaultHealthConfig() - } - - if r.HealthCheck.Interval == 0 { - r.HealthCheck.Interval = common.HealthCheckIntervalDefault - } - if r.HealthCheck.Timeout == 0 { - r.HealthCheck.Timeout = common.HealthCheckTimeoutDefault - } + r.HealthCheck.ApplyDefaults(config.ActiveConfig.Load().Defaults.HealthCheck) } func (r *Route) FinalizeHomepageConfig() { @@ -795,7 +786,6 @@ func (r *Route) FinalizeHomepageConfig() { if r.Homepage == nil { r.Homepage = &homepage.ItemConfig{ Show: true, - Name: r.Alias, } } diff --git a/internal/route/route_test.go b/internal/route/route_test.go index f4664ef1..2bc86261 100644 --- a/internal/route/route_test.go +++ b/internal/route/route_test.go @@ -2,6 +2,7 @@ package route import ( "testing" + "time" "github.com/docker/docker/api/types/container" "github.com/yusing/godoxy/internal/common" @@ -41,7 +42,7 @@ func TestRouteValidate(t *testing.T) { Scheme: route.SchemeHTTP, Host: "example.com", Port: route.Port{Proxy: 80}, - HealthCheck: &types.HealthCheckConfig{ + HealthCheck: types.HealthCheckConfig{ Disable: true, }, LoadBalance: &types.LoadBalancerConfig{ @@ -180,3 +181,14 @@ func TestRouteAgent(t *testing.T) { expect.NoError(t, err, "Validate should not return error for valid route with agent") expect.NotNil(t, r.GetAgent(), "GetAgent should return agent") } + +func TestRouteApplyingHealthCheckDefaults(t *testing.T) { + hc := types.HealthCheckConfig{} + hc.ApplyDefaults(types.HealthCheckConfig{ + Interval: 15 * time.Second, + Timeout: 10 * time.Second, + }) + + expect.Equal(t, hc.Interval, 15*time.Second) + expect.Equal(t, hc.Timeout, 10*time.Second) +} diff --git a/internal/types/healthcheck_config.go b/internal/types/healthcheck_config.go index 3301f4c2..fceddef6 100644 --- a/internal/types/healthcheck_config.go +++ b/internal/types/healthcheck_config.go @@ -3,25 +3,39 @@ package types import ( "context" "time" - - "github.com/yusing/godoxy/internal/common" ) type HealthCheckConfig struct { Disable bool `json:"disable,omitempty" aliases:"disabled"` - Path string `json:"path,omitempty" validate:"omitempty,uri,startswith=/"` UseGet bool `json:"use_get,omitempty"` + Path string `json:"path,omitempty" validate:"omitempty,uri,startswith=/"` Interval time.Duration `json:"interval" validate:"omitempty,min=1s" swaggertype:"primitive,integer"` Timeout time.Duration `json:"timeout" validate:"omitempty,min=1s" swaggertype:"primitive,integer"` - Retries int64 `json:"retries"` // <0: immediate, >=0: threshold + Retries int64 `json:"retries"` // <0: immediate, 0: default, >0: threshold BaseContext func() context.Context `json:"-"` } // @name HealthCheckConfig -func DefaultHealthConfig() *HealthCheckConfig { - return &HealthCheckConfig{ - Interval: common.HealthCheckIntervalDefault, - Timeout: common.HealthCheckTimeoutDefault, - Retries: int64(common.HealthCheckDownNotifyDelayDefault / common.HealthCheckIntervalDefault), +const ( + HealthCheckIntervalDefault = 5 * time.Second + HealthCheckTimeoutDefault = 5 * time.Second + HealthCheckDownNotifyDelayDefault = 15 * time.Second +) + +func (hc *HealthCheckConfig) ApplyDefaults(defaults HealthCheckConfig) { + if hc.Interval == 0 { + hc.Interval = defaults.Interval + if hc.Interval == 0 { + hc.Interval = HealthCheckIntervalDefault + } + } + if hc.Timeout == 0 { + hc.Timeout = defaults.Timeout + if hc.Timeout == 0 { + hc.Timeout = HealthCheckTimeoutDefault + } + } + if hc.Retries == 0 { + hc.Retries = int64(HealthCheckDownNotifyDelayDefault / hc.Interval) } } diff --git a/internal/types/routes.go b/internal/types/routes.go index cc9d482d..bc8ac7c4 100644 --- a/internal/types/routes.go +++ b/internal/types/routes.go @@ -29,7 +29,7 @@ type ( Started() <-chan struct{} IdlewatcherConfig() *IdlewatcherConfig - HealthCheckConfig() *HealthCheckConfig + HealthCheckConfig() HealthCheckConfig LoadBalanceConfig() *LoadBalancerConfig HomepageItem() homepage.Item DisplayName() string diff --git a/internal/watcher/health/monitor/agent_proxied.go b/internal/watcher/health/monitor/agent_proxied.go index 7b782144..332d6d56 100644 --- a/internal/watcher/health/monitor/agent_proxied.go +++ b/internal/watcher/health/monitor/agent_proxied.go @@ -45,7 +45,7 @@ func (target *AgentCheckHealthTarget) displayURL() *url.URL { } } -func NewAgentProxiedMonitor(agent *agentPkg.AgentConfig, config *types.HealthCheckConfig, target *AgentCheckHealthTarget) *AgentProxiedMonitor { +func NewAgentProxiedMonitor(agent *agentPkg.AgentConfig, config types.HealthCheckConfig, target *AgentCheckHealthTarget) *AgentProxiedMonitor { mon := &AgentProxiedMonitor{ agent: agent, } diff --git a/internal/watcher/health/monitor/docker.go b/internal/watcher/health/monitor/docker.go index fba9ee44..1bbea8f7 100644 --- a/internal/watcher/health/monitor/docker.go +++ b/internal/watcher/health/monitor/docker.go @@ -25,7 +25,7 @@ type DockerHealthMonitor struct { const dockerFailuresThreshold = 3 -func NewDockerHealthMonitor(client *docker.SharedClient, containerID, alias string, config *types.HealthCheckConfig, fallback types.HealthChecker) *DockerHealthMonitor { +func NewDockerHealthMonitor(client *docker.SharedClient, containerID, alias string, config types.HealthCheckConfig, fallback types.HealthChecker) *DockerHealthMonitor { mon := new(DockerHealthMonitor) mon.client = client mon.containerID = containerID diff --git a/internal/watcher/health/monitor/fileserver.go b/internal/watcher/health/monitor/fileserver.go index 93d500d2..cd1d7568 100644 --- a/internal/watcher/health/monitor/fileserver.go +++ b/internal/watcher/health/monitor/fileserver.go @@ -12,7 +12,7 @@ type FileServerHealthMonitor struct { path string } -func NewFileServerHealthMonitor(config *types.HealthCheckConfig, path string) *FileServerHealthMonitor { +func NewFileServerHealthMonitor(config types.HealthCheckConfig, path string) *FileServerHealthMonitor { mon := &FileServerHealthMonitor{path: path} mon.monitor = newMonitor(nil, config, mon.CheckHealth) return mon diff --git a/internal/watcher/health/monitor/http.go b/internal/watcher/health/monitor/http.go index fa608b21..b74cc879 100644 --- a/internal/watcher/health/monitor/http.go +++ b/internal/watcher/health/monitor/http.go @@ -29,7 +29,7 @@ var pinger = &fasthttp.Client{ NoDefaultUserAgentHeader: true, } -func NewHTTPHealthMonitor(url *url.URL, config *types.HealthCheckConfig) *HTTPHealthMonitor { +func NewHTTPHealthMonitor(url *url.URL, config types.HealthCheckConfig) *HTTPHealthMonitor { mon := new(HTTPHealthMonitor) mon.monitor = newMonitor(url, config, mon.CheckHealth) if config.UseGet { diff --git a/internal/watcher/health/monitor/monitor.go b/internal/watcher/health/monitor/monitor.go index e1fbf908..ffb62145 100644 --- a/internal/watcher/health/monitor/monitor.go +++ b/internal/watcher/health/monitor/monitor.go @@ -10,7 +10,7 @@ import ( "github.com/rs/zerolog" "github.com/rs/zerolog/log" - "github.com/yusing/godoxy/internal/common" + config "github.com/yusing/godoxy/internal/config/types" "github.com/yusing/godoxy/internal/docker" "github.com/yusing/godoxy/internal/notif" "github.com/yusing/godoxy/internal/types" @@ -24,7 +24,7 @@ type ( HealthCheckFunc func() (result types.HealthCheckResult, err error) monitor struct { service string - config *types.HealthCheckConfig + config types.HealthCheckConfig url synk.Value[*url.URL] status synk.Value[types.HealthStatus] @@ -73,6 +73,7 @@ func NewMonitor(r types.Route) types.HealthMonCheck { return mon } +func newMonitor(u *url.URL, cfg types.HealthCheckConfig, healthCheckFunc HealthCheckFunc) *monitor { cfg.ApplyDefaults(config.DefaultConfig().Defaults.HealthCheck) mon := &monitor{ config: cfg, @@ -196,7 +197,7 @@ func (mon *monitor) URL() *url.URL { // Config implements HealthChecker. func (mon *monitor) Config() *types.HealthCheckConfig { - return mon.config + return &mon.config } // Status implements HealthMonitor. @@ -237,7 +238,7 @@ func (mon *monitor) MarshalJSON() ([]byte, error) { res := mon.lastResult.Load() return (&types.HealthJSONRepr{ Name: mon.service, - Config: mon.config, + Config: &mon.config, Status: mon.status.Load(), Started: mon.startTime, Uptime: mon.Uptime(), diff --git a/internal/watcher/health/monitor/monitor_test.go b/internal/watcher/health/monitor/monitor_test.go index 6129cf6c..db4f1415 100644 --- a/internal/watcher/health/monitor/monitor_test.go +++ b/internal/watcher/health/monitor/monitor_test.go @@ -28,7 +28,7 @@ func (t *testNotificationTracker) getStats() (up, down int, last string) { } // Create test monitor with mock health checker - returns both monitor and tracker -func createTestMonitor(config *types.HealthCheckConfig, checkFunc HealthCheckFunc) (*monitor, *testNotificationTracker) { +func createTestMonitor(config types.HealthCheckConfig, checkFunc HealthCheckFunc) (*monitor, *testNotificationTracker) { testURL, _ := url.Parse("http://localhost:8080") mon := newMonitor(testURL, config, checkFunc) @@ -56,7 +56,7 @@ func createTestMonitor(config *types.HealthCheckConfig, checkFunc HealthCheckFun } func TestNotification_ImmediateNotifyAfterZero(t *testing.T) { - config := &types.HealthCheckConfig{ + config := types.HealthCheckConfig{ Interval: 100 * time.Millisecond, Timeout: 50 * time.Millisecond, Retries: -1, // Immediate notification @@ -91,7 +91,7 @@ func TestNotification_ImmediateNotifyAfterZero(t *testing.T) { } func TestNotification_WithNotifyAfterThreshold(t *testing.T) { - config := &types.HealthCheckConfig{ + config := types.HealthCheckConfig{ Interval: 50 * time.Millisecond, Timeout: 50 * time.Millisecond, Retries: 2, // Notify after 2 consecutive failures @@ -130,7 +130,7 @@ func TestNotification_WithNotifyAfterThreshold(t *testing.T) { } func TestNotification_ServiceRecoversBeforeThreshold(t *testing.T) { - config := &types.HealthCheckConfig{ + config := types.HealthCheckConfig{ Interval: 100 * time.Millisecond, Timeout: 50 * time.Millisecond, Retries: 3, // Notify after 3 consecutive failures @@ -179,7 +179,7 @@ func TestNotification_ServiceRecoversBeforeThreshold(t *testing.T) { } func TestNotification_ConsecutiveFailureReset(t *testing.T) { - config := &types.HealthCheckConfig{ + config := types.HealthCheckConfig{ Interval: 100 * time.Millisecond, Timeout: 50 * time.Millisecond, Retries: 2, // Notify after 2 consecutive failures @@ -240,7 +240,7 @@ func TestNotification_ConsecutiveFailureReset(t *testing.T) { } func TestNotification_ContextCancellation(t *testing.T) { - config := &types.HealthCheckConfig{ + config := types.HealthCheckConfig{ Interval: 100 * time.Millisecond, Timeout: 50 * time.Millisecond, Retries: 1, @@ -279,7 +279,7 @@ func TestNotification_ContextCancellation(t *testing.T) { } func TestImmediateUpNotification(t *testing.T) { - config := &types.HealthCheckConfig{ + config := types.HealthCheckConfig{ Interval: 100 * time.Millisecond, Timeout: 50 * time.Millisecond, Retries: 2, // NotifyAfter should not affect up notifications diff --git a/internal/watcher/health/monitor/raw.go b/internal/watcher/health/monitor/raw.go index 72275533..fcde7255 100644 --- a/internal/watcher/health/monitor/raw.go +++ b/internal/watcher/health/monitor/raw.go @@ -17,7 +17,7 @@ type ( } ) -func NewRawHealthMonitor(url *url.URL, config *types.HealthCheckConfig) *RawHealthMonitor { +func NewRawHealthMonitor(url *url.URL, config types.HealthCheckConfig) *RawHealthMonitor { mon := new(RawHealthMonitor) mon.monitor = newMonitor(url, config, mon.CheckHealth) mon.dialer = &net.Dialer{