Files
llmgw/internal/control/agentsvc/controlplanes.go
T
Ilker ManapandClaude Opus 5.5 aa3efcebbe Add a standby control plane with server add, and draw the screens as windows
server add gives another machine this llmgw, a certificate, the
configuration and store address, lets it into the store through Patroni, and
starts it; --vip keeps a floating address on the leader with keepalived,
following a new /leader endpoint. Nodes learn every control plane from the
leader and notice a dead one by HTTP/2 ping instead of after the kernel's
fifteen minutes. The setup, console, node and init screens are drawn full
screen in one frame: title bar, sidebar, bordered panel, key bar, dialogs.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-03 22:29:12 +02:00

66 lines
1.8 KiB
Go

package agentsvc
import (
"context"
"slices"
"time"
llmgwv1 "gitea.mynodes.xyz/ilker/llmgw/api/gen/llmgw/v1"
)
// SetControlPlanes gives the service where the list of control planes comes
// from. Every agent is told it when it registers, and again when it changes.
func (s *Service) SetControlPlanes(list func(context.Context) ([]string, error)) {
s.mu.Lock()
s.controlPlanes = list
s.mu.Unlock()
}
// controlPlanesMsg is the list as an agent is sent it, or nil when there is
// none to send: a cluster with no list recorded keeps what its agents were
// configured with.
func (s *Service) controlPlanesMsg(ctx context.Context) *llmgwv1.ControlMessage {
s.mu.RLock()
list := s.controlPlanes
s.mu.RUnlock()
if list == nil {
return nil
}
servers, err := list(ctx)
if err != nil || len(servers) == 0 {
return nil
}
return &llmgwv1.ControlMessage{Msg: &llmgwv1.ControlMessage_ControlPlanes{
ControlPlanes: &llmgwv1.ControlPlanes{Servers: servers}}}
}
// WatchControlPlanes tells every connected agent the list when it changes,
// checking every so often. A control plane added by `llmgw server add` reaches
// the nodes within that time, without their being touched.
func (s *Service) WatchControlPlanes(ctx context.Context, every time.Duration) {
var last []string
t := time.NewTicker(every)
defer t.Stop()
for {
if msg := s.controlPlanesMsg(ctx); msg != nil {
now := msg.GetControlPlanes().GetServers()
if !slices.Equal(now, last) {
for _, node := range s.ConnectedNodes() {
if err := s.send(node, msg); err != nil {
s.log.Warn("could not tell a node the control planes", "node", node, "err", err)
}
}
if last != nil {
s.log.Info("told the nodes the control planes changed", "servers", now)
}
last = now
}
}
select {
case <-ctx.Done():
return
case <-t.C:
}
}
}