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>
76 lines
2.9 KiB
Go
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)
|
|
}
|
|
}
|