Files
llmgw/internal/web/node_page_test.go
T
Ilker ManapandClaude Opus 5.5 5bbc619a31 Map every InfiniBand fabric, draw it, and place replicas by it
The control plane asks one node on each physical fabric to run
ibnetdiscover every five minutes and parses the text itself, so a
format the parser has not met is a control plane upgrade, not one on
every node. From the map it works out the switch tiers (leaf, spine,
core, by distance from the adapters), which adapter is which node's,
which cables trained below the rate the rest of their kind run at, and
whether the rails line up: every node's Nth adapter on its own leaf.

A page per fabric (/admin/fabrics) draws it: switches in their tiers,
adapters fanned out under their leaves, cables coloured by rate and
bundled, degraded ones marching red with a marker, and the cluster's
own adapters live - down ports glowing red, and particles running each
cable as fast as its adapter is sending and receiving, from the
counters. It zooms, pans, fades what a hovered switch or adapter does
not touch, opens a panel of detail on a click, colours by rail, finds a
node, and refreshes every fifteen seconds. The data is also at
/api/v1/fabrics/{id}/topology.

Placement reads the map: a replica spanning nodes takes nodes of one
leaf together when it can, and nodes whose cable trained short last.

Static assets are named by their content, as the stylesheet already
was.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-29 08:46:23 +02:00

148 lines
5.2 KiB
Go

package web
import (
"net/http"
"strings"
"testing"
"time"
"gitea.mynodes.xyz/ilker/llmgw/internal/control/adminapi"
"gitea.mynodes.xyz/ilker/llmgw/internal/control/topology"
"gitea.mynodes.xyz/ilker/llmgw/internal/promq"
)
// nodeMux mounts the page where it lives, so the path carries the node.
func nodeMux(src Source, h History) http.Handler {
mux := http.NewServeMux()
if h == nil {
mux.HandleFunc("GET /admin/nodes/{id}", NodePage(src, nil, time.Hour, false, "v1", nil))
} else {
mux.HandleFunc("GET /admin/nodes/{id}", NodePage(src, h, time.Hour, false, "v1", nil))
}
return mux
}
// busySource is fakeSource with a node that has been measured.
type busySource struct{ fakeSource }
func (busySource) Nodes() []adminapi.NodeView {
n := fakeSource{}.Nodes()[0]
n.Load = &adminapi.LoadView{CPUPercent: 62, CPUs: 8, Load1: 3.25, Load5: 2, Load15: 1,
MemUsed: 6 << 30, MemTotal: 8 << 30, MemPercent: 75}
n.IBPorts = []adminapi.IBPortView{
{HCA: "mlx5_0", Port: 1, State: "ACTIVE", PhysState: "LinkUp", RateGbps: 400, Active: true,
Fabric: "ib-a", TxBytes: 4 << 30},
{HCA: "mlx5_1", Port: 1, State: "DOWN", PhysState: "Polling", Errors: map[string]uint64{"link_downed": 7}},
}
return []adminapi.NodeView{n}
}
func (busySource) Replicas(string) []adminapi.ReplicaView {
r := fakeSource{}.Replicas("")
r[0].Shards[0].CPUCores, r[0].Shards[0].MemoryBytes = 4, 5<<30
return r
}
func TestANodesPageShowsHowBusyItIsAndWhatIsUsingIt(t *testing.T) {
h := &fakeHistory{byExpr: map[string][]promq.Series{
`llmgw_node_cpu_utilization_percent{node="llmgw-node01"} / 100`: {ts("llmgw-node01", 0.2, 0.6)},
}}
code, body := get(t, nodeMux(busySource{}, h), "http://x/admin/nodes/llmgw-node01", nil)
if code != http.StatusOK {
t.Fatalf("status %d", code)
}
for _, want := range []string{
"62%", "CPU · 8 cores", "RAM in use", "3.25",
// The engine and its share: four of eight cores is half the machine.
"<td>served</td>", "4.0 cores", "width: 50%",
`hx-get="/admin/nodes/llmgw-node01"`,
} {
if !strings.Contains(body, want) {
t.Errorf("missing %q", want)
}
}
// Every chart is about this node, and nothing else.
for _, q := range h.asked {
if !strings.Contains(q, `"llmgw-node01"`) && !strings.Contains(q, `served-1`) {
t.Errorf("a query not scoped to the node: %s", q)
}
}
}
func TestAnUnknownNodeIsNotFound(t *testing.T) {
h := &fakeHistory{}
code, _ := get(t, nodeMux(busySource{}, h), "http://x/admin/nodes/nope", nil)
if code != http.StatusNotFound {
t.Errorf("status %d", code)
}
// Nothing was asked of the metrics store for a name nobody knows.
if len(h.asked) != 0 {
t.Errorf("queried for an unknown node: %v", h.asked)
}
}
func TestANodeNotMeasuredLatelySaysSoRatherThanZero(t *testing.T) {
_, body := get(t, nodeMux(fakeSource{}, nil), "http://x/admin/nodes/llmgw-node01", nil)
if !strings.Contains(body, "not measured lately") {
t.Error("an unmeasured node should say so")
}
if !strings.Contains(body, "No metrics store is configured") {
t.Error("without a metrics store the page should say where history comes from")
}
}
func TestANodesPageShowsItsFabricPorts(t *testing.T) {
_, body := get(t, nodeMux(busySource{}, nil), "http://x/admin/nodes/llmgw-node01", nil)
for _, want := range []string{
"<h2>Fabric</h2>", "On ib-a ·", "<code>mlx5_0/1</code>", "400 Gb/s", "4.0 GiB",
`<td class="bad">DOWN <span class="note">Polling</span></td>`, "link_downed 7",
} {
if !strings.Contains(body, want) {
t.Errorf("missing %q", want)
}
}
}
// topoSource is busySource with a mapped fabric.
type topoSource struct{ busySource }
func (topoSource) Topologies() []adminapi.TopologyView {
return []adminapi.TopologyView{{ID: "sm-0x01", Key: "sm:0x01", Label: "fabric of subnet manager 0x01", Declared: []string{"ib-a"}}}
}
func (s topoSource) Topology(id string) (adminapi.TopologyView, bool) {
if id != "sm-0x01" {
return adminapi.TopologyView{}, false
}
t := s.Topologies()[0]
t.Map = &topology.View{Tiers: 2, Switches: []topology.SwitchView{{GUID: "0xs1", Name: "leaf-01</script><script>alert(1)", Tier: 1}}}
return t, true
}
func TestAFabricsPageCarriesItsMapSafely(t *testing.T) {
mux := http.NewServeMux()
mux.HandleFunc("GET /admin/fabrics", FabricsPage(topoSource{}, false, "v1"))
mux.HandleFunc("GET /admin/fabrics/{id}", FabricsPage(topoSource{}, false, "v1"))
mux.HandleFunc("GET /admin/fabrics/{id}/topology.json", FabricTopologyJSON(topoSource{}))
code, body := get(t, mux, "http://x/admin/fabrics", nil)
if code != 200 {
t.Fatalf("status %d", code)
}
for _, want := range []string{`id="fabric-map"`, `data-src="/admin/fabrics/sm-0x01/topology.json"`, "fabric.js?v=", `id="fabric-data"`} {
if !strings.Contains(body, want) {
t.Errorf("missing %q", want)
}
}
// A switch named to break out of the script it is carried in cannot.
if strings.Contains(body, "</script><script>alert(1)") {
t.Error("a switch description closed the data script")
}
if code, _ := get(t, mux, "http://x/admin/fabrics/nope", nil); code != 404 {
t.Errorf("unknown fabric: %d", code)
}
if code, body := get(t, mux, "http://x/admin/fabrics/sm-0x01/topology.json", nil); code != 200 || !strings.Contains(body, `"tiers":2`) {
t.Errorf("json: %d %s", code, body)
}
}