Files
llmgw/internal/web/admin_nodeengine_test.go
T
Ilker ManapandClaude Opus 5.5 4f280a3546 Give each GPU node a vLLM build it can run, chosen on the cluster page
A node that cannot run the CUDA 13 build installs the same version's
CUDA 12.9 build from vLLM's GitHub release, with PyTorch's cu129 torch;
the install computes on the GPU before it counts. The build can be
chosen per node — auto, cu13, cu129, none — on the cluster page, with
ctl engine-build, or over the admin API; the control plane keeps the
choice and tells the node at once.

Refs #2.

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

76 lines
2.9 KiB
Go

package web
import (
"context"
"io"
"log/slog"
"net/http"
"net/url"
"strings"
"testing"
"time"
"gitea.mynodes.xyz/ilker/llmgw/internal/control/adminapi"
"gitea.mynodes.xyz/ilker/llmgw/internal/control/inventory"
"gitea.mynodes.xyz/ilker/llmgw/internal/store"
)
// buildFleet is one GPU node whose vLLM build was chosen, and one CPU node.
type buildFleet struct{ fakeFleet }
func (buildFleet) Nodes() []adminapi.NodeView {
return []adminapi.NodeView{
{ID: "gpu07", State: "ready", GPUTotal: 8, GPUFree: 8,
EngineBuilds: map[string]string{"vllm": "cu129"},
Provision: []inventory.EngineInstall{{Engine: "vllm", Version: "0.30.0", Variant: "cu129", State: "installing"}}},
{ID: "cpu01", State: "ready"},
}
}
type recordedChoice struct{ node, engine, build, by string }
type fakeEngineAdmin struct{ got []recordedChoice }
func (f *fakeEngineAdmin) SetNodeEngine(_ context.Context, node, engine, build, by string) error {
f.got = append(f.got, recordedChoice{node, engine, build, by})
return nil
}
func TestTheVLLMBuildIsChosenOnTheClusterPage(t *testing.T) {
a := fakeAccounts{user: store.User{ID: "u1", Email: "a@example.com", IsAdmin: true}}
p := NewAccountPages(a, nil, nil, "v0", slog.New(slog.NewTextHandler(io.Discard, nil)))
mux := http.NewServeMux()
mux.HandleFunc("GET /admin", p.AdminPage(Dashboard(buildFleet{}, nil, "v1", time.Second, false, NavExtras{}, nil)))
fe := &fakeEngineAdmin{}
p.RegisterNodeEngines(mux, fe)
body := mirrorGet(t, mux, "/admin").Body.String()
if !strings.Contains(body, `action="/admin/nodes/gpu07/engines"`) ||
!strings.Contains(body, `<option value="cu129" selected>CUDA 12.9</option>`) {
t.Fatalf("no choice of build for the GPU node, with its current one selected: %s", body)
}
if strings.Contains(body, `action="/admin/nodes/cpu01/engines"`) {
t.Error("a node with no GPU was offered a vLLM build")
}
if !strings.Contains(body, "CUDA 12.9 installing") {
t.Error("what is installed is not shown beside the choice")
}
// The read-only page on the admin listener has no session, so no form.
_, ro := get(t, Dashboard(buildFleet{}, nil, "v1", time.Second, true, NavExtras{}, nil), "http://127.0.0.1:9090/", nil)
if strings.Contains(ro, "/engines\"") {
t.Error("the admin listener's page offers a change it cannot sign")
}
w := mirrorPost(t, mux, "/admin/nodes/gpu07/engines", url.Values{"csrf": {"tok"}, "engine": {"vllm"}, "build": {"none"}})
if w.Code != http.StatusSeeOther || len(fe.got) != 1 || fe.got[0] != (recordedChoice{"gpu07", "vllm", "none", "a@example.com"}) {
t.Fatalf("status %d, chose %+v", w.Code, fe.got)
}
if loc := w.Header().Get("Location"); !strings.Contains(loc, "notice=") {
t.Errorf("redirected to %s, saying nothing", loc)
}
if w := mirrorPost(t, mux, "/admin/nodes/gpu07/engines", url.Values{"csrf": {"bad"}, "engine": {"vllm"}, "build": {"none"}}); w.Code != http.StatusBadRequest || len(fe.got) != 1 {
t.Errorf("a forged form was accepted: %d", w.Code)
}
}