111 lines
2.5 KiB
Go
111 lines
2.5 KiB
Go
package agent
|
|
|
|
import (
|
|
"context"
|
|
"log/slog"
|
|
"os"
|
|
"os/signal"
|
|
"syscall"
|
|
|
|
"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.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, 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.
|
|
func (a *Agent) Run(ctx context.Context) error {
|
|
ctx, cancel := context.WithCancel(ctx)
|
|
defer cancel()
|
|
|
|
sigCh := make(chan os.Signal, 1)
|
|
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
|
|
defer signal.Stop(sigCh)
|
|
|
|
errCh := make(chan error, 2)
|
|
go func() {
|
|
a.log.Info("health server listening", "addr", a.cfg.HealthListenAddr)
|
|
if err := a.health.ListenAndServe(); err != nil {
|
|
errCh <- err
|
|
}
|
|
}()
|
|
|
|
go func() {
|
|
errCh <- a.ws.Run(ctx)
|
|
}()
|
|
|
|
a.health.SetReady(true)
|
|
a.log.Info("agent started",
|
|
"node_id", a.cfg.NodeID,
|
|
"control_plane", a.cfg.ControlPlaneURL,
|
|
"version", Version,
|
|
)
|
|
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
return a.shutdown(nil)
|
|
case sig := <-sigCh:
|
|
a.log.Info("shutdown signal received", "signal", sig.String())
|
|
cancel()
|
|
case err := <-errCh:
|
|
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")
|
|
|
|
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)
|
|
return reason
|
|
}
|
|
a.log.Info("agent stopped")
|
|
return nil
|
|
}
|