agents.default and agents.nodes.<id> in the cluster's settings hold everything agent.yaml can say but the agent's id, control planes, address and certificate, which stay in the file and are refused there. The control plane sends each node its settings on register and, a second apart, when they change; an agent whose settings changed checks they load beside its file, keeps them and starts again on them, engines serving throughout. An edited agent.yaml that loads restarts the agent too. History, audit, ctl config, the console and the portal's settings page cover them as the rest of the settings. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
110 lines
3.4 KiB
Go
110 lines
3.4 KiB
Go
package agent
|
|
|
|
import (
|
|
"context"
|
|
"crypto/sha256"
|
|
"os"
|
|
"path/filepath"
|
|
"time"
|
|
|
|
llmgwv1 "gitea.mynodes.xyz/ilker/llmgw/api/gen/llmgw/v1"
|
|
)
|
|
|
|
// The cluster's settings for this agent (BACKLOG E3): what agent.yaml can
|
|
// say but the agent's id, control planes, address and certificate, kept by
|
|
// the control planes and sent over the stream. Kept here beside agent.yaml,
|
|
// which then holds only what the agent needs to start; at start the two are
|
|
// read together, these over the file.
|
|
|
|
// SettingsFile keeps the settings the control plane last sent.
|
|
var SettingsFile = "/var/lib/llmgw-agent/agent-settings.yaml"
|
|
|
|
// ReadSettings is what SettingsFile holds, nil when nothing.
|
|
func ReadSettings() []byte {
|
|
b, err := os.ReadFile(SettingsFile)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
return b
|
|
}
|
|
|
|
// SetSettingsCheck says how to tell whether settings would load beside this
|
|
// agent's own file; settings that would not are not taken.
|
|
func (a *Agent) SetSettingsCheck(f func([]byte) error) { a.settingsCheck = f }
|
|
|
|
// applySettings takes the settings the control plane sent: nothing when they
|
|
// are what this agent runs on, and otherwise kept and started again on.
|
|
func (a *Agent) applySettings(m *llmgwv1.AgentSettings) {
|
|
next := m.GetYaml()
|
|
if next == a.cfg.Settings {
|
|
return
|
|
}
|
|
if a.settingsCheck != nil {
|
|
if err := a.settingsCheck([]byte(next)); err != nil {
|
|
// Kept running on what it has: settings that will not load
|
|
// here must not take the node out of service.
|
|
a.log.Error("the cluster's settings for this agent do not load beside its agent.yaml; running on what it has",
|
|
"err", err)
|
|
return
|
|
}
|
|
}
|
|
if err := os.MkdirAll(filepath.Dir(SettingsFile), 0o755); err != nil {
|
|
a.log.Error("could not keep the cluster's settings for this agent", "err", err)
|
|
return
|
|
}
|
|
tmp := SettingsFile + ".new"
|
|
if err := os.WriteFile(tmp, []byte(next), 0o600); err != nil {
|
|
a.log.Error("could not keep the cluster's settings for this agent", "err", err)
|
|
return
|
|
}
|
|
if err := os.Rename(tmp, SettingsFile); err != nil {
|
|
a.log.Error("could not keep the cluster's settings for this agent", "err", err)
|
|
return
|
|
}
|
|
a.log.Info("the cluster's settings for this agent changed; starting again on them, the engines keep serving")
|
|
time.Sleep(300 * time.Millisecond)
|
|
exitForUpgrade()
|
|
}
|
|
|
|
// WatchFile starts the agent again when its configuration file changes and
|
|
// still loads: agent.yaml is read only at start, and a change to it that
|
|
// waited for somebody to restart the agent was a change nobody saw take
|
|
// effect.
|
|
func (a *Agent) WatchFile(ctx context.Context, path string, loads func() error) {
|
|
if path == "" {
|
|
return
|
|
}
|
|
sum := func() [32]byte {
|
|
b, _ := os.ReadFile(path) //nolint:gosec // the agent's own configuration
|
|
return sha256.Sum256(b)
|
|
}
|
|
was := sum()
|
|
t := time.NewTicker(watchEvery)
|
|
defer t.Stop()
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
return
|
|
case <-t.C:
|
|
}
|
|
now := sum()
|
|
if now == was {
|
|
continue
|
|
}
|
|
if err := loads(); err != nil {
|
|
// Once per change: an editor saving halfway must not stop the
|
|
// agent, and a broken file is reported, not restarted into.
|
|
a.log.Error("agent.yaml changed and does not load; running on what it had", "path", path, "err", err)
|
|
was = now
|
|
continue
|
|
}
|
|
a.log.Info("agent.yaml changed; starting again on it, the engines keep serving", "path", path)
|
|
time.Sleep(300 * time.Millisecond)
|
|
exitForUpgrade()
|
|
return
|
|
}
|
|
}
|
|
|
|
// watchEvery is how often agent.yaml is looked at.
|
|
var watchEvery = 10 * time.Second
|