Phase2
Some checks failed
CI / Node — lint, typecheck, test, build (push) Failing after 10s
CI / Go — node-agent tests (push) Failing after 8s
CI / Go — edge-gateway build (push) Successful in 13s

This commit is contained in:
smueller
2026-06-26 12:01:25 +02:00
parent 58961000eb
commit c4077d4673
93 changed files with 4099 additions and 165 deletions

View File

@@ -6,28 +6,43 @@ import (
"os"
"os/signal"
"syscall"
"time"
"github.com/hexahost/gamecloud/node-agent/internal/config"
dockerpkg "github.com/hexahost/gamecloud/node-agent/internal/docker"
"github.com/hexahost/gamecloud/node-agent/internal/health"
"github.com/hexahost/gamecloud/node-agent/internal/runtime"
"github.com/hexahost/gamecloud/node-agent/internal/ws"
)
const Version = "0.1.0-phase0"
const Version = "0.2.0-phase2"
// Agent coordinates the node agent lifecycle until shutdown.
type Agent struct {
cfg *config.Config
log *slog.Logger
health *health.Server
docker *dockerpkg.Client
ws *ws.Client
}
// New creates an agent instance from configuration.
func New(cfg *config.Config, log *slog.Logger) *Agent {
func New(cfg *config.Config, log *slog.Logger) (*Agent, error) {
dockerClient, err := dockerpkg.New(cfg.DockerSocket)
if err != nil {
return nil, err
}
wsClient := ws.NewClient(cfg, log, nil, Version)
runtimeMgr := runtime.NewManager(dockerClient, wsClient, log)
wsClient.SetRuntime(runtimeMgr)
return &Agent{
cfg: cfg,
log: log,
health: health.NewServer(cfg.HealthListenAddr, Version),
}
docker: dockerClient,
ws: wsClient,
}, nil
}
// Run starts the agent loop and blocks until context cancellation or signal.
@@ -39,7 +54,7 @@ func (a *Agent) Run(ctx context.Context) error {
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
defer signal.Stop(sigCh)
errCh := make(chan error, 1)
errCh := make(chan error, 2)
go func() {
a.log.Info("health server listening", "addr", a.cfg.HealthListenAddr)
if err := a.health.ListenAndServe(); err != nil {
@@ -47,6 +62,10 @@ func (a *Agent) Run(ctx context.Context) error {
}
}()
go func() {
errCh <- a.ws.Run(ctx)
}()
a.health.SetReady(true)
a.log.Info("agent started",
"node_id", a.cfg.NodeID,
@@ -54,34 +73,33 @@ func (a *Agent) Run(ctx context.Context) error {
"version", Version,
)
ticker := time.NewTicker(a.cfg.HeartbeatInterval)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return a.shutdown(ctx.Err())
return a.shutdown(nil)
case sig := <-sigCh:
a.log.Info("shutdown signal received", "signal", sig.String())
return a.shutdown(nil)
cancel()
case err := <-errCh:
return err
case <-ticker.C:
a.log.Debug("heartbeat tick",
"node_id", a.cfg.NodeID,
"interval", a.cfg.HeartbeatInterval.String(),
)
if ctx.Err() != nil {
return a.shutdown(nil)
}
if err != nil {
return err
}
}
}
}
func (a *Agent) shutdown(reason error) error {
a.health.SetReady(false)
a.log.Info("agent shutting down", "grace", a.cfg.ShutdownGracePeriod.String())
a.log.Info("agent shutting down")
timer := time.NewTimer(a.cfg.ShutdownGracePeriod)
defer timer.Stop()
<-timer.C
if a.docker != nil {
if err := a.docker.Close(); err != nil {
a.log.Warn("docker client close failed", "error", err)
}
}
if reason != nil && reason != context.Canceled {
a.log.Warn("shutdown with error", "error", reason)