Files
llmgw/internal/web/mirror_detail_test.go
T
Ilker ManapandClaude Opus 5.5 663770226e Find LoRA adapters on Hugging Face and attach them from the mirror page
An adapters view in the explorer, filtered by the model a held model was
made from; GGUF files told apart as adapter or model by their first bytes;
held adapter files marked; and a model's card attaches and detaches held
adapters of its architecture, refusing models and mismatched architectures.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-01 07:19:08 +02:00

336 lines
14 KiB
Go

package web
import (
"context"
"log/slog"
"net/http"
"net/url"
"strings"
"testing"
"gitea.mynodes.xyz/ilker/llmgw/internal/control/adminapi"
"gitea.mynodes.xyz/ilker/llmgw/internal/control/catalog"
"gitea.mynodes.xyz/ilker/llmgw/internal/gguf"
"gitea.mynodes.xyz/ilker/llmgw/internal/mirror"
"gitea.mynodes.xyz/ilker/llmgw/internal/store"
)
// roomyFleet runs one replica of "coder" on n2 and has room for `more`.
type roomyFleet struct {
fakeFleet
more int
blocked []string
asked int
preempt int
preempts []string
}
func (f *roomyFleet) Nodes() []adminapi.NodeView {
return []adminapi.NodeView{{ID: "n1", MemoryGB: 16}, {ID: "n2", MemoryGB: 32}}
}
func (f *roomyFleet) Models() []adminapi.ModelView {
return []adminapi.ModelView{{Name: "coder", Min: 1, Max: 1, Ready: 1, Total: 1, Desired: 1}}
}
func (f *roomyFleet) Replicas(string) []adminapi.ReplicaView {
return []adminapi.ReplicaView{{ID: "coder-abc", Model: "coder", State: "ready", AgeSec: 3700,
EngineKey: "secret-engine-key",
Shards: []adminapi.ShardView{{Node: "n2", CPUCores: 3.5, MemoryBytes: 6 << 30}}}}
}
func (f *roomyFleet) Headroom(model string, limit int) (adminapi.HeadroomView, bool) {
f.asked = limit
more := min(f.more, limit)
h := adminapi.HeadroomView{Model: model, Current: 1, More: more}
for range more {
h.Nodes = append(h.Nodes, []string{"n1"})
}
if more < limit {
h.Blocked = f.blocked
} else {
h.Limited = true
}
h.ByPreempting, h.Preempts = f.preempt, f.preempts
return h, true
}
func detailPage(t *testing.T, fleet *roomyFleet, cat *fakeCatalog) func(string) string {
t.Helper()
m := &fakeMirror{models: []mirror.Status{
{Model: "coder", Version: "v1", File: "c.gguf", State: mirror.Present, BytesDone: 5 << 30},
}}
mux := mirrorPageDeps(t, MirrorDeps{Mirror: m, Catalog: cat, Fleet: fleet}, true)
return func(path string) string { return mirrorGet(t, mux, path).Body.String() }
}
func coderCatalog() *fakeCatalog {
return &fakeCatalog{sources: map[string][]catalog.Model{"http://mirror:8081/coder/v1/c.gguf": {
{Name: "coder", Scaling: catalog.Scaling{Min: 1, Max: 1}}}}}
}
func TestAHeldModelOpensToWhereItRunsAndHowMuchRoomIsLeft(t *testing.T) {
fleet := &roomyFleet{more: 2, blocked: []string{"n2: 2 GB free of 32, profile needs 6"}}
get := detailPage(t, fleet, coderCatalog())
closed := get("/admin/mirror")
if strings.Contains(closed, "held-card") {
t.Error("a card is open before anything was clicked")
}
if !strings.Contains(closed, `href="/admin/mirror?tab=held&amp;open=coder%3av1"`) {
t.Error("the model's name does not open its card")
}
body := get("/admin/mirror?tab=held&open=coder:v1")
for _, want := range []string{
"held-card", `<a href="/admin/nodes/n2">n2</a>`, "32 GB", "coder-abc", "1h01m", "3.5 cores", "6.0 GiB",
"Room for 2 more now, on n1, n1.", "n2: 2 GB free of 32, profile needs 6",
`name="min" value="1" min="0" max="3"`, `action="/admin/mirror/replicas"`,
`name="open" value="coder:v1"`,
} {
if !strings.Contains(body, want) {
t.Errorf("the card is missing %q", want)
}
}
if strings.Contains(body, "secret-engine-key") {
t.Error("the engine's key reached the page")
}
if strings.Contains(body, `http-equiv="refresh"`) {
t.Error("a page with an open form reloads under the operator")
}
}
func TestAReplicaCountTheClusterHasNoRoomForIsRefused(t *testing.T) {
fleet := &roomyFleet{more: 1, blocked: []string{"n1: 1 GB free of 16, profile needs 6"}}
cat := coderCatalog()
mux := mirrorPageDeps(t, MirrorDeps{Mirror: &fakeMirror{models: []mirror.Status{
{Model: "coder", Version: "v1", File: "c.gguf", State: mirror.Present, BytesDone: 1},
}}, Catalog: cat, Fleet: fleet}, true)
post := func(lo, hi string) string {
return mirrorPost(t, mux, "/admin/mirror/replicas", url.Values{"csrf": {"tok"},
"model": {"coder"}, "min": {lo}, "max": {hi}, "open": {"coder:v1"}}).Body.String()
}
body := post("3", "3")
if len(cat.scaled) != 0 {
t.Fatalf("three always-on replicas were set with room for two: %+v", cat.lastScale)
}
if !strings.Contains(body, "can always run at most 2 now") || !strings.Contains(body, "1 GB free of 16") {
t.Errorf("the refusal should say how many fit and why: %s", body)
}
if !strings.Contains(body, "held-card") {
t.Error("the card closed on the refusal")
}
body = post("2", "4")
if len(cat.scaled) != 1 || cat.lastScale.Min != 2 || cat.lastScale.Max != 4 {
t.Fatalf("what fits was not set: %+v", cat.lastScale)
}
if !strings.Contains(body, "coder runs 2, and up to 4 under load; there is room for 2 now") {
t.Errorf("a ceiling above the room should be taken and said: %s", body)
}
post("0", "2")
if cat.lastScale.Min != 0 || cat.lastScale.Max != 2 || cat.lastScale.IdleTimeout == 0 {
t.Errorf("on demand was set as %+v", cat.lastScale)
}
for _, bad := range [][2]string{{"3", "2"}, {"-1", "1"}, {"x", "1"}, {"1", "100"}} {
n := len(cat.scaled)
post(bad[0], bad[1])
if len(cat.scaled) != n {
t.Errorf("min %s max %s was accepted", bad[0], bad[1])
}
}
}
func TestAModelSetInConfigurationIsShownButNotChanged(t *testing.T) {
cat := coderCatalog()
cat.declared = "coder"
body := detailPage(t, &roomyFleet{more: 20}, cat)("/admin/mirror?open=coder:v1")
if strings.Contains(body, `action="/admin/mirror/replicas"`) || !strings.Contains(body, "change its replicas there") {
t.Error("a declared model offered a change that would revert")
}
if !strings.Contains(body, "Room for at least 16 more") {
t.Error("room that reached the limit asked should read as at least")
}
}
func TestAHeldRowSaysHowItsModelRunsWithoutOpening(t *testing.T) {
body := detailPage(t, &roomyFleet{more: 1}, coderCatalog())("/admin/mirror")
if strings.Contains(body, "held-card") {
t.Fatal("the card opened by itself")
}
if !strings.Contains(body, `title="replicas ready / wanted">1/1 · <span class="node" title="n2">n2</span></span>`) {
t.Errorf("the row does not say where its replicas run: %s", body)
}
}
func TestRunningOnNamesEachNodeOnceAndCountsWhatIsStarting(t *testing.T) {
nodes, starting := runningOn([]adminapi.ReplicaView{
{State: "ready", Shards: []adminapi.ShardView{{Node: "n2"}}},
{State: "starting", Shards: []adminapi.ShardView{{Node: "n1"}}},
{State: "ready", Shards: []adminapi.ShardView{{Node: "n2"}}},
})
if strings.Join(nodes, ",") != "n1,n2" || starting != 1 {
t.Errorf("nodes %v, starting %d", nodes, starting)
}
}
func TestNodeNamesLoseOnlyWhatEveryNodeShares(t *testing.T) {
for _, c := range []struct {
ids []string
want string
}{
{[]string{"llmgw-node01", "llmgw-node02", "llmgw-node06"}, "llmgw-"},
{[]string{"gpu-a.dc1", "gpu-b.dc1"}, "gpu-"},
{[]string{"n1", "n2"}, ""},
{[]string{"llmgw-node01"}, ""},
{[]string{"alpha", "beta"}, ""},
} {
if got := sharedPrefix(c.ids); got != c.want {
t.Errorf("%v: %q, expected %q", c.ids, got, c.want)
}
}
}
func settingsPage(t *testing.T, cat *fakeCatalog, withQuotas bool) *http.ServeMux {
t.Helper()
a := fakeAccounts{user: store.User{ID: "u1", Email: "a@example.com", IsAdmin: true}}
p := NewAccountPages(a, nil, nil, "v0", slog.New(slog.DiscardHandler))
if withQuotas {
p.SetCapacity(&fakeCapacity{})
}
mux := http.NewServeMux()
p.RegisterAdminMirror(mux, MirrorDeps{Mirror: &fakeMirror{models: []mirror.Status{
{Model: "coder", Version: "v1", File: "c.gguf", State: mirror.Present, BytesDone: 1},
}}, Catalog: cat, Fleet: &roomyFleet{more: 3}})
return mux
}
func TestACardSetsClassOwnerAndReservationAndOnlyWhatChanged(t *testing.T) {
cat := coderCatalog()
cat.sources["http://mirror:8081/coder/v1/c.gguf"][0].Profiles = []catalog.Profile{{Name: "cpu", MinMemoryGB: 12, CPUCores: 4}}
mux := settingsPage(t, cat, true)
card := mirrorGet(t, mux, "/admin/mirror?open=coder:v1").Body.String()
for _, want := range []string{`name="class"`, `<option value="" selected>default (standard)</option>`,
`name="owner"`, `<option value="team:t1">Acme / ml</option>`, `name="memory" value="12"`, `name="cores" value="4"`} {
if !strings.Contains(card, want) {
t.Errorf("the card lacks %q", want)
}
}
post := func(v url.Values) string {
v.Set("csrf", "tok")
v.Set("model", "coder")
v.Set("open", "coder:v1")
return mirrorPost(t, mux, "/admin/mirror/replicas", v).Body.String()
}
same := url.Values{"min": {"1"}, "max": {"1"}, "class": {""}, "owner": {""}, "memory": {"12"}, "cores": {"4"}}
if body := post(same); !strings.Contains(body, "coder: nothing changed") ||
len(cat.classes)+len(cat.owners)+len(cat.resources)+len(cat.scaled) != 0 {
t.Errorf("an unchanged form changed something: %v %v %v %v", cat.classes, cat.owners, cat.resources, cat.scaled)
}
changed := url.Values{"min": {"1"}, "max": {"1"}, "class": {"critical"}, "owner": {"team:t1"}, "memory": {"14"}, "cores": {"4"}}
body := post(changed)
if strings.Join(cat.resources, ",") != "coder=14/4" || strings.Join(cat.classes, ",") != "coder=critical" ||
strings.Join(cat.owners, ",") != "coder=team:t1" || len(cat.scaled) != 0 {
t.Errorf("applied %v %v %v %v", cat.resources, cat.classes, cat.owners, cat.scaled)
}
if !strings.Contains(body, "a replica reserves 14 GB and 4 cores") || !strings.Contains(body, "counts against team:t1") {
t.Errorf("the change was not said: %s", body)
}
// Without capacity quotas there is no owner to choose, and none is sent.
plain := mirrorGet(t, settingsPage(t, coderCatalog(), false), "/admin/mirror?open=coder:v1").Body.String()
if strings.Contains(plain, `name="owner"`) {
t.Error("an owner choice with no quotas to count against")
}
}
func TestRoomAHigherClassWouldMakeIsOfferedAndSaid(t *testing.T) {
fleet := &roomyFleet{more: 0, preempt: 1, preempts: []string{"chat"}, blocked: []string{"n1: full"}}
cat := coderCatalog()
mux := mirrorPageDeps(t, MirrorDeps{Mirror: &fakeMirror{models: []mirror.Status{
{Model: "coder", Version: "v1", File: "c.gguf", State: mirror.Present, BytesDone: 1},
}}, Catalog: cat, Fleet: fleet}, true)
card := mirrorGet(t, mux, "/admin/mirror?open=coder:v1").Body.String()
if !strings.Contains(card, "No free room for another replica now.") ||
!strings.Contains(card, "1 by stopping replicas its class outranks: chat.") ||
!strings.Contains(card, `name="min" value="1" min="0" max="2"`) {
t.Errorf("the card should offer the room preemption makes, and say whose")
}
body := mirrorPost(t, mux, "/admin/mirror/replicas", url.Values{"csrf": {"tok"},
"model": {"coder"}, "min": {"2"}, "max": {"2"}, "open": {"coder:v1"}}).Body.String()
if len(cat.scaled) != 1 || cat.lastScale.Min != 2 {
t.Fatalf("two were refused though preemption makes room: %s", body)
}
if !strings.Contains(body, "replicas of chat will be stopped") {
t.Errorf("stopping somebody else's replica should be said: %s", body)
}
body = mirrorPost(t, mux, "/admin/mirror/replicas", url.Values{"csrf": {"tok"},
"model": {"coder"}, "min": {"3"}, "max": {"3"}, "open": {"coder:v1"}}).Body.String()
if len(cat.scaled) != 1 || !strings.Contains(body, "at most 2") {
t.Errorf("three is past even what preemption makes: %s", body)
}
}
func TestAnAdapterHeldInTheMirrorIsAttachedFromItsModelsCard(t *testing.T) {
prev := kindOf
t.Cleanup(func() { kindOf = prev })
kindOf = func(_ context.Context, u string) (gguf.Header, error) {
switch {
case strings.Contains(u, "/sql/"):
return gguf.Header{Architecture: "qwen2", Type: "adapter"}, nil
case strings.Contains(u, "/llama-lora/"):
return gguf.Header{Architecture: "llama", Type: "adapter"}, nil
}
return gguf.Header{Architecture: "qwen2"}, nil
}
cat := coderCatalog()
coder := &cat.sources["http://mirror:8081/coder/v1/c.gguf"][0]
coder.Source = "http://mirror:8081/coder/v1/c.gguf"
coder.Profiles = []catalog.Profile{{Name: "cpu", Engine: "llamacpp", MinMemoryGB: 12}}
m := &fakeMirror{models: []mirror.Status{
{Model: "coder", Version: "v1", File: "c.gguf", State: mirror.Present, BytesDone: 1,
Source: "https://huggingface.co/bartowski/Qwen2.5-Coder-7B-Instruct-GGUF/resolve/abc/c.gguf"},
{Model: "sql", Version: "v1", File: "sql.gguf", State: mirror.Present, BytesDone: 1},
{Model: "llama-lora", Version: "v1", File: "l.gguf", State: mirror.Present, BytesDone: 1},
}}
mux := mirrorPageDeps(t, MirrorDeps{Mirror: m, Catalog: cat, Fleet: &roomyFleet{more: 1}}, true)
list := mirrorGet(t, mux, "/admin/mirror").Body.String()
if strings.Count(list, ">LoRA adapter</span>") != 2 {
t.Error("the two adapter files are not marked as adapters in the list")
}
card := mirrorGet(t, mux, "/admin/mirror?open=coder:v1").Body.String()
if !strings.Contains(card, `<option value="http://mirror:8081/sql/v1/sql.gguf" data-suggest="coder-sql">`) {
t.Error("the qwen2 adapter is not offered for the qwen2 model")
}
if strings.Contains(card, "llama-lora") && strings.Contains(card, `value="http://mirror:8081/llama-lora/v1/l.gguf"`) {
t.Error("an adapter for another architecture is offered")
}
if !strings.Contains(card, "view=adapters&amp;from=bartowski%2FQwen2.5-Coder-7B-Instruct-GGUF") {
t.Error("finding adapters should start from the model's own repository")
}
post := func(v url.Values) string {
v.Set("csrf", "tok")
v.Set("model", "coder")
v.Set("open", "coder:v1")
return mirrorPost(t, mux, "/admin/mirror/adapters", v).Body.String()
}
body := post(url.Values{"op": {"add"}, "name": {"coder-sql"}, "source": {"http://mirror:8081/sql/v1/sql.gguf"}, "scale": {"0.8"}})
if len(cat.adapterSets) != 1 || len(cat.adapterSets[0]) != 1 || cat.adapterSets[0][0].Scale != 0.8 {
t.Fatalf("attached %+v: %s", cat.adapterSets, body)
}
if !strings.Contains(body, "adapter coder-sql added") {
t.Errorf("the attach was not said: %s", body)
}
for name, src := range map[string]string{"another architecture": "http://mirror:8081/llama-lora/v1/l.gguf",
"a model": "http://mirror:8081/coder/v1/c.gguf"} {
n := len(cat.adapterSets)
out := post(url.Values{"op": {"add"}, "name": {"x"}, "source": {src}})
if len(cat.adapterSets) != n || !strings.Contains(out, `class="error"`) {
t.Errorf("%s was attached", name)
}
}
}