Files
llmgw/internal/cli/serverinit_cluster_test.go
T
Ilker ManapandClaude Opus 5.5 42fa602247 Make a new control plane serve a model by itself
server init now also:
- runs a model mirror beside the control plane (/srv/models, bytes on
  :8081 at an address nodes reach, management API on loopback so it
  needs no certificate) and adds it to server.yaml;
- makes the machine a node of its own control plane: issues its agent
  certificate, writes agent.yaml labelled as the control plane, starts
  llmgw-agent, and waits for it to register and install llama.cpp;
- checks for NVIDIA GPUs with nvidia-smi, and reports a card on the PCI
  bus whose driver does not answer rather than ignoring it;
- fetches Qwen2.5 0.5B (Q4_K_M) into the mirror and adds it to the
  catalog pinned to this node: a llama.cpp profile per GPU class found,
  with a CPU profile as the fallback, then waits for it to serve.

--no-mirror, --no-node and --no-model leave these out. Every step looks
before it acts, so a rerun finishes what an interrupted one started.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-28 16:49:55 +02:00

90 lines
2.6 KiB
Go

package cli
import (
"os"
"path/filepath"
"strings"
"testing"
"gitea.mynodes.xyz/ilker/llmgw/internal/config"
"gitea.mynodes.xyz/ilker/llmgw/internal/control/catalog"
)
func TestTheInitAgentUnitIsTheShippedUnit(t *testing.T) {
b, err := os.ReadFile("../../deploy/systemd/llmgw-agent.service")
if err != nil {
t.Fatal(err)
}
if string(b) != agentUnit {
t.Error("deploy/systemd/llmgw-agent.service and the unit 'server init' writes have drifted")
}
}
func TestTheStarterUsesTheGPUAndFallsBackToCPUOnThisMachine(t *testing.T) {
m := starterModel("http://10.0.0.5:8081/qwen2.5-0.5b-instruct/starter/q.gguf", []string{"4090-24g"})
c, err := catalog.New([]catalog.Model{m})
if err != nil {
t.Fatalf("the starter is not a valid catalog entry: %v", err)
}
got, _ := c.Resolve(starterName)
if len(got.Profiles) != 2 {
t.Fatalf("profiles = %+v", got.Profiles)
}
gpu, cpu := got.Profiles[0], got.Profiles[1]
if gpu.GPUClass != "4090-24g" || gpu.Priority <= cpu.Priority {
t.Errorf("the GPU profile should come first: %+v", gpu)
}
if cpu.GPUClass != "" || cpu.Engine != "llamacpp" {
t.Errorf("the fallback should be llama.cpp on CPU: %+v", cpu)
}
for _, p := range got.Profiles {
if p.NodeSelector[localLabelKey] != localLabelValue {
t.Errorf("profile %s is not pinned to this machine", p.Name)
}
}
if got.Scaling.Min != 1 {
t.Error("the starter should be running, not waiting to be asked for")
}
if m := starterModel("http://x/q.gguf", nil); len(m.Profiles) != 1 || m.Profiles[0].Name != "cpu" {
t.Errorf("without a GPU only the CPU profile: %+v", m.Profiles)
}
}
func TestTheMirrorBlockIsAddedAndReads(t *testing.T) {
in, out := newTestInit(t)
if err := in.run(t.Context()); err != nil {
t.Fatalf("%v\n%s", err, out)
}
if err := appendMirrorBlock(in.configPath()); err != nil {
t.Fatal(err)
}
cfg, err := config.Load(in.configPath())
if err != nil {
t.Fatal(err)
}
if cfg.Mirror.API != "http://"+mirrorAPI {
t.Errorf("mirror.api = %q", cfg.Mirror.API)
}
if err := cfg.Validate(); err != nil {
t.Error(err)
}
backups, _ := filepath.Glob(in.configPath() + ".bak-*")
if len(backups) != 1 {
t.Errorf("backups = %v", backups)
}
}
func TestTheMirrorUnitAdvertisesAnAddressNodesCanReach(t *testing.T) {
in := &serverInit{hosts: []string{"10.0.0.140"}}
u := in.mirrorUnit()
if !strings.Contains(u, "--base http://10.0.0.140:8081") {
t.Errorf("base: %s", u)
}
// The management port can delete models; nothing but this machine may
// reach it, which is what lets it go without a certificate.
if !strings.Contains(u, "--api-listen 127.0.0.1:8082") {
t.Errorf("the management API is not on loopback: %s", u)
}
}