85 lines
2.3 KiB
Go
85 lines
2.3 KiB
Go
// Command validator-agent runs on a validator VM. It is stateless: every
|
|||
|
|
// decision it makes is driven by polling the Control API, per the
|
||
|
|
// deployment constraint that validators must be able to restart freely
|
||
|
|
// without any local state to reconcile.
|
||
|
|
package main
|
||
|
|
|
||
|
|
import (
|
||
|
|
"context"
|
||
|
|
"flag"
|
||
|
|
"log/slog"
|
||
|
|
"net"
|
||
|
|
"os"
|
||
|
|
"os/signal"
|
||
|
|
"strconv"
|
||
|
|
"strings"
|
||
|
|
"syscall"
|
||
|
|
|
||
|
|
"cloudipvalidator/internal/agentcore"
|
||
|
|
"cloudipvalidator/internal/config"
|
||
|
|
)
|
||
|
|
|
||
|
|
func main() {
|
||
|
|
configPath := flag.String("config", "configs/validator-agent.yaml", "path to validator-agent config file")
|
||
|
|
stubPorts := flag.String("stub-ports", "", "TEST ONLY: comma-separated TCP ports to accept-and-close on, standing in for the validator's real listening services in the offline end-to-end harness (see docs/LOCAL_E2E.md)")
|
||
|
|
flag.Parse()
|
||
|
|
|
||
|
|
log := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelInfo}))
|
||
|
|
|
||
|
|
cfg, err := config.LoadValidatorAgent(*configPath)
|
||
|
|
if err != nil {
|
||
|
|
log.Error("load config", "err", err)
|
||
|
|
os.Exit(1)
|
||
|
|
}
|
||
|
|
|
||
|
|
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
|
||
|
|
defer stop()
|
||
|
|
|
||
|
|
if *stubPorts != "" {
|
||
|
|
startStubListeners(ctx, log, *stubPorts)
|
||
|
|
}
|
||
|
|
|
||
|
|
agent := agentcore.New(cfg, log)
|
||
|
|
if err := agent.Run(ctx); err != nil && err != context.Canceled {
|
||
|
|
log.Error("agent stopped", "err", err)
|
||
|
|
os.Exit(1)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// startStubListeners binds trivial accept-and-close TCP listeners on the
|
||
|
|
// given ports, standing in for the base-minimum services (22/80/443/8080)
|
||
|
|
// a real validator would already run, so an offline prober has something
|
||
|
|
// to successfully connect to. ICMP needs no stub: the kernel answers echo
|
||
|
|
// requests to any locally-bound address on its own.
|
||
|
|
func startStubListeners(ctx context.Context, log *slog.Logger, portsCSV string) {
|
||
|
|
for _, p := range strings.Split(portsCSV, ",") {
|
||
|
|
p = strings.TrimSpace(p)
|
||
|
|
if p == "" {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
port, err := strconv.Atoi(p)
|
||
|
|
if err != nil {
|
||
|
|
log.Error("invalid stub port", "value", p, "err", err)
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
ln, err := net.Listen("tcp", ":"+strconv.Itoa(port))
|
||
|
|
if err != nil {
|
||
|
|
log.Error("stub listener", "port", port, "err", err)
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
log.Info("stub listener up", "port", port)
|
||
|
|
go func() {
|
||
|
|
<-ctx.Done()
|
||
|
|
ln.Close()
|
||
|
|
}()
|
||
|
|
go func() {
|
||
|
|
for {
|
||
|
|
conn, err := ln.Accept()
|
||
|
|
if err != nil {
|
||
|
|
return
|
||
|
|
}
|
||
|
|
conn.Close()
|
||
|
|
}
|
||
|
|
}()
|
||
|
|
}
|
||
|
|
}
|