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>
66 lines
1.8 KiB
Go
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:
|
|
}
|
|
}
|
|
}
|