mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-09-29 14:06:36 +08:00
215 lines
6.3 KiB
Go
215 lines
6.3 KiB
Go
package agent
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"log"
|
|
"strings"
|
|
"time"
|
|
|
|
"atsflare-agent/internal/config"
|
|
"atsflare-agent/internal/protocol"
|
|
"atsflare-agent/internal/state"
|
|
)
|
|
|
|
type HeartbeatService interface {
|
|
Register(ctx context.Context, payload protocol.NodePayload) (*protocol.RegisterNodeResponse, error)
|
|
Heartbeat(ctx context.Context, payload protocol.NodePayload) (*protocol.AgentSettings, error)
|
|
SetToken(token string)
|
|
}
|
|
|
|
type SyncService interface {
|
|
SyncOnStartup(ctx context.Context) error
|
|
SyncOnce(ctx context.Context) error
|
|
}
|
|
|
|
type Updater interface {
|
|
CheckAndUpdate(ctx context.Context, repo string) error
|
|
}
|
|
|
|
type Runner struct {
|
|
Config *config.Config
|
|
StateStore *state.Store
|
|
HeartbeatService HeartbeatService
|
|
SyncService SyncService
|
|
Updater Updater
|
|
|
|
autoUpdate bool
|
|
updateNow bool
|
|
updateRepo string
|
|
}
|
|
|
|
func (r *Runner) Run(ctx context.Context) error {
|
|
nodeID, err := r.StateStore.EnsureNodeID()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
log.Printf("agent runner started: node_id=%s node=%s ip=%s", nodeID, r.Config.NodeName, r.Config.NodeIP)
|
|
if r.hasAgentToken() {
|
|
if err = r.SyncService.SyncOnStartup(ctx); err != nil {
|
|
r.recordSyncError(err)
|
|
log.Printf("agent startup sync failed: %v", err)
|
|
} else {
|
|
log.Printf("agent startup sync completed")
|
|
}
|
|
settings, hbErr := r.HeartbeatService.Heartbeat(ctx, r.nodePayload(nodeID))
|
|
if hbErr != nil {
|
|
log.Printf("agent startup heartbeat failed: %v", hbErr)
|
|
} else {
|
|
log.Printf("agent startup heartbeat succeeded: node_id=%s", nodeID)
|
|
r.applySettings(settings)
|
|
}
|
|
} else if err = r.tryRegister(ctx, &nodeID); err != nil {
|
|
log.Printf("agent initial discovery register failed: %v", err)
|
|
}
|
|
|
|
heartbeatTicker := time.NewTicker(r.Config.HeartbeatInterval.Duration())
|
|
defer heartbeatTicker.Stop()
|
|
syncTicker := time.NewTicker(r.Config.SyncInterval.Duration())
|
|
defer syncTicker.Stop()
|
|
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
log.Printf("agent runner shutting down: %v", ctx.Err())
|
|
return ctx.Err()
|
|
case <-heartbeatTicker.C:
|
|
if !r.hasAgentToken() {
|
|
if err = r.tryRegister(ctx, &nodeID); err != nil {
|
|
log.Printf("agent discovery register failed: %v", err)
|
|
}
|
|
continue
|
|
}
|
|
settings, hbErr := r.HeartbeatService.Heartbeat(ctx, r.nodePayload(nodeID))
|
|
if hbErr != nil {
|
|
log.Printf("agent heartbeat failed: %v", hbErr)
|
|
} else {
|
|
if changed := r.applySettings(settings); changed {
|
|
heartbeatTicker.Reset(r.Config.HeartbeatInterval.Duration())
|
|
syncTicker.Reset(r.Config.SyncInterval.Duration())
|
|
}
|
|
r.tryAutoUpdate(ctx)
|
|
}
|
|
case <-syncTicker.C:
|
|
if !r.hasAgentToken() {
|
|
continue
|
|
}
|
|
log.Printf("agent sync tick: node_id=%s", nodeID)
|
|
if err = r.SyncService.SyncOnce(ctx); err != nil {
|
|
r.recordSyncError(err)
|
|
log.Printf("agent sync failed: %v", err)
|
|
} else {
|
|
log.Printf("agent sync completed")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (r *Runner) hasAgentToken() bool {
|
|
return strings.TrimSpace(r.Config.AgentToken) != ""
|
|
}
|
|
|
|
func (r *Runner) applySettings(settings *protocol.AgentSettings) bool {
|
|
if settings == nil {
|
|
return false
|
|
}
|
|
changed := false
|
|
if settings.HeartbeatInterval > 0 {
|
|
newInterval := config.MillisecondDuration(time.Duration(settings.HeartbeatInterval) * time.Millisecond)
|
|
if newInterval != r.Config.HeartbeatInterval {
|
|
log.Printf("agent heartbeat interval updated: %s -> %s", r.Config.HeartbeatInterval, newInterval)
|
|
r.Config.HeartbeatInterval = newInterval
|
|
changed = true
|
|
}
|
|
}
|
|
if settings.SyncInterval > 0 {
|
|
newInterval := config.MillisecondDuration(time.Duration(settings.SyncInterval) * time.Millisecond)
|
|
if newInterval != r.Config.SyncInterval {
|
|
log.Printf("agent sync interval updated: %s -> %s", r.Config.SyncInterval, newInterval)
|
|
r.Config.SyncInterval = newInterval
|
|
changed = true
|
|
}
|
|
}
|
|
r.autoUpdate = settings.AutoUpdate
|
|
r.updateNow = settings.UpdateNow
|
|
r.updateRepo = strings.TrimSpace(settings.UpdateRepo)
|
|
return changed
|
|
}
|
|
|
|
func (r *Runner) tryAutoUpdate(ctx context.Context) {
|
|
shouldCheck := r.autoUpdate || r.updateNow
|
|
r.updateNow = false
|
|
if !shouldCheck || r.Updater == nil || r.updateRepo == "" {
|
|
return
|
|
}
|
|
if err := r.Updater.CheckAndUpdate(ctx, r.updateRepo); err != nil {
|
|
log.Printf("agent update check failed: %v", err)
|
|
}
|
|
}
|
|
|
|
func (r *Runner) tryRegister(ctx context.Context, nodeID *string) error {
|
|
if strings.TrimSpace(r.Config.DiscoveryToken) == "" {
|
|
return errors.New("agent_token 为空且未配置 discovery_token")
|
|
}
|
|
log.Printf("agent discovery registration started")
|
|
response, err := r.HeartbeatService.Register(ctx, r.nodePayload(*nodeID))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if response == nil || strings.TrimSpace(response.AgentToken) == "" || strings.TrimSpace(response.NodeID) == "" {
|
|
return errors.New("discovery register response 缺少 node_id 或 agent_token")
|
|
}
|
|
snapshot, err := r.StateStore.Load()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
snapshot.NodeID = response.NodeID
|
|
if err = r.StateStore.Save(snapshot); err != nil {
|
|
return err
|
|
}
|
|
r.Config.AgentToken = response.AgentToken
|
|
r.Config.DiscoveryToken = ""
|
|
if err = r.Config.Save(); err != nil {
|
|
return err
|
|
}
|
|
r.HeartbeatService.SetToken(response.AgentToken)
|
|
*nodeID = response.NodeID
|
|
log.Printf("agent discovery registration succeeded: node_id=%s", response.NodeID)
|
|
if err = r.SyncService.SyncOnStartup(ctx); err != nil {
|
|
r.recordSyncError(err)
|
|
log.Printf("agent post-register startup sync failed: %v", err)
|
|
} else {
|
|
log.Printf("agent post-register startup sync completed")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (r *Runner) recordSyncError(err error) {
|
|
if err == nil || r.StateStore == nil {
|
|
return
|
|
}
|
|
snapshot, loadErr := r.StateStore.Load()
|
|
if loadErr != nil {
|
|
log.Printf("load state before recording sync error failed: %v", loadErr)
|
|
return
|
|
}
|
|
snapshot.LastError = err.Error()
|
|
log.Printf("recording sync error into state: %s", snapshot.LastError)
|
|
if saveErr := r.StateStore.Save(snapshot); saveErr != nil {
|
|
log.Printf("save state after sync error failed: %v", saveErr)
|
|
}
|
|
}
|
|
|
|
func (r *Runner) nodePayload(nodeID string) protocol.NodePayload {
|
|
snapshot, _ := r.StateStore.Load()
|
|
return protocol.NodePayload{
|
|
NodeID: nodeID,
|
|
Name: r.Config.NodeName,
|
|
IP: r.Config.NodeIP,
|
|
AgentVersion: r.Config.AgentVersion,
|
|
NginxVersion: r.Config.NginxVersion,
|
|
CurrentVersion: snapshot.CurrentVersion,
|
|
LastError: snapshot.LastError,
|
|
}
|
|
}
|