Files
uptop/internal/cluster/cluster.go
T
lerko 50f77da131
CI / test (pull_request) Successful in 1m43s
CI / lint (pull_request) Successful in 1m17s
CI / vulncheck (pull_request) Successful in 51s
refactor: extract magic numbers to named constants
Replace inline numeric literals with named constants across 14 files:
server timeouts/rate limits, cluster thresholds/intervals, DB pool
sizes, alert/dial timeouts, TUI uptime thresholds, node status
thresholds, and state history limits.
2026-06-27 15:44:03 -04:00

87 lines
2.0 KiB
Go

package cluster
import (
"context"
"fmt"
"net/http"
"strings"
"time"
"gitea.lerkolabs.com/lerkolabs/uptop/internal/monitor"
)
type Config struct {
Mode string // "leader" or "follower"
PeerURL string // URL of the Leader (e.g., http://primary:8080)
SharedKey string // Security Key
}
func Start(ctx context.Context, cfg Config, eng *monitor.Engine) {
if cfg.Mode == "leader" {
fmt.Println("Cluster: Running as LEADER (Active)")
if cfg.SharedKey != "" {
fmt.Println("WARNING: Cluster mode enabled. Ensure the HTTP server is behind a TLS-terminating proxy.")
}
eng.SetActive(true)
return
}
if cfg.Mode == "follower" {
fmt.Println("Cluster: Running as FOLLOWER (Passive)")
if cfg.PeerURL != "" && !strings.HasPrefix(cfg.PeerURL, "https://") {
fmt.Println("WARNING: Cluster peer URL is not HTTPS. Cluster secret will be sent in cleartext.")
}
eng.SetActive(false)
go runFollowerLoop(ctx, cfg, eng)
}
// "probe" mode is handled directly in main.go before cluster.Start is called
}
const (
followerTimeout = 2 * time.Second
leaderFailureThreshold = 3
followerRetryInterval = 5 * time.Second
)
func runFollowerLoop(ctx context.Context, cfg Config, eng *monitor.Engine) {
client := http.Client{Timeout: followerTimeout}
failures := 0
threshold := leaderFailureThreshold
for {
select {
case <-time.After(followerRetryInterval):
case <-ctx.Done():
return
}
req, _ := http.NewRequest("GET", cfg.PeerURL+"/api/health", nil)
if cfg.SharedKey != "" {
req.Header.Set("X-Uptop-Secret", cfg.SharedKey)
}
resp, err := client.Do(req)
isLeaderHealthy := false
if err == nil {
isLeaderHealthy = resp.StatusCode == http.StatusOK
_ = resp.Body.Close()
}
if isLeaderHealthy {
failures = 0
if eng.IsActive() {
eng.SetActive(false)
eng.AddLog("Cluster: Leader detected. Switching to PASSIVE.")
}
} else {
failures++
if failures >= threshold && !eng.IsActive() {
eng.SetActive(true)
eng.AddLog("Cluster: Leader Unreachable. Switching to ACTIVE.")
}
}
}
}