added the base which is ai code and this will be tailored (and have been a little) for specifice yejayekhoob.com use case and tested extensively under short period of time
This commit is contained in:
151
main.go
Normal file
151
main.go
Normal file
@@ -0,0 +1,151 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
HealthURL string
|
||||
StaticIP string
|
||||
NormalInterval time.Duration
|
||||
FailInterval time.Duration
|
||||
Timeout time.Duration
|
||||
MaxRetries int
|
||||
BindPrimaryConf string
|
||||
BindFailoverConf string
|
||||
BindActiveConf string
|
||||
Verbose bool
|
||||
}
|
||||
|
||||
type State int
|
||||
|
||||
const (
|
||||
StatePrimary State = iota
|
||||
StateFailover
|
||||
)
|
||||
|
||||
func main() {
|
||||
cfg := parseFlags()
|
||||
|
||||
logger := log.New(os.Stdout, "", log.LstdFlags)
|
||||
if !cfg.Verbose {
|
||||
logger.SetOutput(os.Stdout)
|
||||
logger.SetFlags(0)
|
||||
}
|
||||
|
||||
currentState := StatePrimary
|
||||
failureCount := 0
|
||||
successCount := 0
|
||||
|
||||
for {
|
||||
healthy := checkHealth(cfg)
|
||||
|
||||
if currentState == StatePrimary {
|
||||
if healthy {
|
||||
failureCount = 0
|
||||
sleep(cfg.NormalInterval)
|
||||
continue
|
||||
}
|
||||
|
||||
failureCount++
|
||||
if cfg.Verbose {
|
||||
logger.Println("Health check failed:", failureCount)
|
||||
}
|
||||
|
||||
if failureCount >= cfg.MaxRetries {
|
||||
logger.Println("Primary confirmed DOWN. Switching to failover.")
|
||||
err := SwitchBindConfig(cfg.BindFailoverConf, cfg.BindActiveConf)
|
||||
if err != nil {
|
||||
logger.Println("Failed to switch BIND config:", err)
|
||||
}
|
||||
currentState = StateFailover
|
||||
failureCount = 0
|
||||
}
|
||||
|
||||
sleep(cfg.FailInterval)
|
||||
continue
|
||||
}
|
||||
|
||||
// FAILOVER MODE
|
||||
if healthy {
|
||||
successCount++
|
||||
if cfg.Verbose {
|
||||
logger.Println("Primary recovery attempt:", successCount)
|
||||
}
|
||||
|
||||
if successCount >= cfg.MaxRetries {
|
||||
logger.Println("Primary confirmed UP. Reverting DNS.")
|
||||
err := SwitchBindConfig(cfg.BindPrimaryConf, cfg.BindActiveConf)
|
||||
if err != nil {
|
||||
logger.Println("Failed to revert BIND config:", err)
|
||||
}
|
||||
currentState = StatePrimary
|
||||
successCount = 0
|
||||
}
|
||||
} else {
|
||||
successCount = 0
|
||||
}
|
||||
|
||||
sleep(cfg.NormalInterval)
|
||||
}
|
||||
}
|
||||
|
||||
func parseFlags() *Config {
|
||||
cfg := &Config{}
|
||||
|
||||
flag.StringVar(&cfg.HealthURL, "url", "/nginx-health", "Health endpoint path")
|
||||
flag.StringVar(&cfg.StaticIP, "ip", "", "Static IP to check")
|
||||
flag.DurationVar(&cfg.NormalInterval, "interval", 120*time.Second, "Normal check interval")
|
||||
flag.DurationVar(&cfg.FailInterval, "fail-interval", 10*time.Second, "Interval during failure detection")
|
||||
flag.DurationVar(&cfg.Timeout, "timeout", 5*time.Second, "HTTP timeout")
|
||||
flag.IntVar(&cfg.MaxRetries, "retries", 5, "Consecutive retries before state change")
|
||||
flag.StringVar(&cfg.BindPrimaryConf, "primary-conf", "/etc/bind/db.primary", "Primary zone config")
|
||||
flag.StringVar(&cfg.BindFailoverConf, "failover-conf", "/etc/bind/db.failover", "Failover zone config")
|
||||
flag.StringVar(&cfg.BindActiveConf, "active-conf", "/etc/bind/db.active", "Active zone config path")
|
||||
flag.BoolVar(&cfg.Verbose, "v", false, "Verbose logging")
|
||||
|
||||
flag.Parse()
|
||||
|
||||
if cfg.StaticIP == "" {
|
||||
log.Fatal("Static IP must be provided")
|
||||
}
|
||||
|
||||
return cfg
|
||||
}
|
||||
|
||||
func checkHealth(cfg *Config) bool {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), cfg.Timeout)
|
||||
defer cancel()
|
||||
|
||||
dialer := &net.Dialer{}
|
||||
transport := &http.Transport{
|
||||
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
return dialer.DialContext(ctx, "tcp", net.JoinHostPort(cfg.StaticIP, "80"))
|
||||
},
|
||||
}
|
||||
|
||||
client := &http.Client{
|
||||
Transport: transport,
|
||||
Timeout: cfg.Timeout,
|
||||
}
|
||||
|
||||
req, _ := http.NewRequestWithContext(ctx, "GET", "http://"+cfg.StaticIP+cfg.HealthURL, nil)
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
return resp.StatusCode == 200
|
||||
}
|
||||
|
||||
func sleep(d time.Duration) {
|
||||
time.Sleep(d)
|
||||
}
|
||||
Reference in New Issue
Block a user