Files
llmgw/internal/agent/settings_test.go
T
Ilker ManapandClaude Opus 5.5 dc243f8840 The nodes' agent settings kept with the cluster's, agent.yaml down to what an agent needs to start
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>
2026-10-08 18:06:36 +02:00

89 lines
2.7 KiB
Go

package agent
import (
"errors"
"os"
"path/filepath"
"sync/atomic"
"testing"
"time"
llmgwv1 "gitea.mynodes.xyz/ilker/llmgw/api/gen/llmgw/v1"
)
func settingsAgent(t *testing.T, running string) (*Agent, chan struct{}) {
t.Helper()
oldFile, oldExit := SettingsFile, exitForUpgrade
SettingsFile = filepath.Join(t.TempDir(), "agent-settings.yaml")
exited := make(chan struct{}, 1)
exitForUpgrade = func() { exited <- struct{}{} }
t.Cleanup(func() { SettingsFile, exitForUpgrade = oldFile, oldExit })
cfg := Config{Server: "127.0.0.1:7443", NodeID: "n1", WeightsCache: t.TempDir(), EnginesDir: t.TempDir(), Settings: running}
cfg.Superv.WorkDir = t.TempDir()
a, err := New(cfg, nil, nil)
if err != nil {
t.Fatal(err)
}
return a, exited
}
// exitedSoon is whether the agent exited within a second: it waits a
// moment for its log line to leave first.
func exitedSoon(ch chan struct{}) bool {
select {
case <-ch:
return true
case <-time.After(time.Second):
return false
}
}
func TestAnAgentOnItsSettingsStaysAndOneOffThemStartsAgain(t *testing.T) {
a, exited := settingsAgent(t, "heartbeat_interval: 5s\n")
a.applySettings(&llmgwv1.AgentSettings{Yaml: "heartbeat_interval: 5s\n"})
if exitedSoon(exited) || ReadSettings() != nil {
t.Error("settings it already runs on restarted it")
}
a.applySettings(&llmgwv1.AgentSettings{Yaml: "heartbeat_interval: 2s\n"})
if !exitedSoon(exited) || string(ReadSettings()) != "heartbeat_interval: 2s\n" {
t.Errorf("new settings: kept %q", ReadSettings())
}
}
func TestSettingsThatWouldNotLoadAreNotTaken(t *testing.T) {
a, exited := settingsAgent(t, "")
a.SetSettingsCheck(func([]byte) error { return errors.New("fabric ib-a lists no HCAs") })
a.applySettings(&llmgwv1.AgentSettings{Yaml: "fabrics: [{name: ib-a}]\n"})
if exitedSoon(exited) || ReadSettings() != nil {
t.Error("settings that do not load were taken")
}
}
func TestAChangedAgentYamlThatLoadsStartsTheAgentAgain(t *testing.T) {
a, exited := settingsAgent(t, "")
old := watchEvery
watchEvery = 10 * time.Millisecond
t.Cleanup(func() { watchEvery = old })
p := filepath.Join(t.TempDir(), "agent.yaml")
_ = os.WriteFile(p, []byte("node_id: n1\n"), 0o600)
var loads atomic.Bool
check := func() error {
if loads.Load() {
return nil
}
return errors.New("broken")
}
go a.WatchFile(t.Context(), p, check)
time.Sleep(30 * time.Millisecond)
// Saved broken: reported, not restarted into.
_ = os.WriteFile(p, []byte("node_id: [\n"), 0o600)
if exitedSoon(exited) {
t.Fatal("a broken agent.yaml restarted the agent")
}
loads.Store(true)
_ = os.WriteFile(p, []byte("node_id: n1\nlabels: {a: b}\n"), 0o600)
if !exitedSoon(exited) {
t.Error("a changed agent.yaml did not restart the agent")
}
}