Nodes say where they are with llmgw.io/site, room, row, rack and pdu labels, from agent.yaml or `llmgw ctl label` (kept in the store, over the agent's). Placement spreads a model's replicas across racks and PDUs, keeps a multi-node replica within one domain when its profile says keep_within, places nothing in a domain held with `llmgw ctl facility hold` (optionally draining it), and refuses a shard that would take a domain past its power cap from facility.budgets, counting declared, derived and measured draw. Explain-placement names the domain, and keeps the rejections of a placed replica; `ctl nodes` gains LOCATION and --domain; `ctl facility` lists the domains; the console's Nodes view shows and sets location and holds. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
69 lines
2.6 KiB
Go
69 lines
2.6 KiB
Go
package adminapi
|
|
|
|
import (
|
|
"net/http"
|
|
"slices"
|
|
"testing"
|
|
|
|
"gitea.mynodes.xyz/ilker/llmgw/internal/control/facility"
|
|
)
|
|
|
|
func TestARackIsLabelledHeldShownAndReleasedThroughTheAPI(t *testing.T) {
|
|
a, ctl := setup(t, testModels(), gpuNode("gpu01", 8), gpuNode("gpu02", 8))
|
|
|
|
var lv NodeLabelsView
|
|
if code := call(t, a, http.MethodPost, "/api/v1/nodes/gpu01/labels",
|
|
`{"set":{"llmgw.io/rack":"r12-07","llmgw.io/pdu":"p1,p2"}}`, &lv); code != http.StatusOK {
|
|
t.Fatalf("labelling: status %d", code)
|
|
}
|
|
if lv.Location == nil || lv.Location.Rack != "r12-07" || len(lv.Location.PDUs) != 2 {
|
|
t.Fatalf("location after labelling: %+v", lv.Location)
|
|
}
|
|
if code := call(t, a, http.MethodPost, "/api/v1/nodes/gpu01/labels", `{"set":{"llmgw.io/rak":"x"}}`, nil); code != http.StatusBadRequest {
|
|
t.Errorf("a misspelt location label: status %d, want 400", code)
|
|
}
|
|
|
|
var hv HoldView
|
|
if code := call(t, a, http.MethodPost, "/api/v1/facility/holds",
|
|
`{"domain":"rack:r12-07","reason":"PDU A replacement","drain":true}`, &hv); code != http.StatusOK {
|
|
t.Fatalf("holding: status %d", code)
|
|
}
|
|
if !slices.Equal(hv.Nodes, []string{"gpu01"}) {
|
|
t.Errorf("drained %v, want gpu01", hv.Nodes)
|
|
}
|
|
|
|
var fv FacilityView
|
|
call(t, a, http.MethodGet, "/api/v1/facility", "", &fv)
|
|
var rack *DomainView
|
|
for i, d := range fv.Domains {
|
|
if d.Domain == "rack:r12-07" {
|
|
rack = &fv.Domains[i]
|
|
}
|
|
}
|
|
if rack == nil || rack.Held == nil || rack.Held.Reason != "PDU A replacement" || !slices.Equal(rack.Nodes, []string{"gpu01"}) {
|
|
t.Fatalf("the rack in the facility view: %+v", rack)
|
|
}
|
|
if !slices.Equal(fv.Unlocated, []string{"gpu02"}) {
|
|
t.Errorf("unlocated %v, want gpu02", fv.Unlocated)
|
|
}
|
|
|
|
var nodes []NodeView
|
|
call(t, a, http.MethodGet, "/api/v1/nodes", "", &nodes)
|
|
if nodes[0].Held != "rack r12-07 is held: PDU A replacement" || nodes[0].HeldDomain != "rack:r12-07" {
|
|
t.Errorf("gpu01 in the node list: held %q (%q)", nodes[0].Held, nodes[0].HeldDomain)
|
|
}
|
|
|
|
if code := call(t, a, http.MethodDelete, "/api/v1/facility/holds/rack:r12-07", "", &hv); code != http.StatusOK {
|
|
t.Fatalf("releasing: status %d", code)
|
|
}
|
|
if _, held := ctl.Inventory().Facility().HeldIn([]facility.Domain{"rack:r12-07"}); held {
|
|
t.Error("released, still held")
|
|
}
|
|
if code := call(t, a, http.MethodDelete, "/api/v1/facility/holds/rack:r12-07", "", nil); code != http.StatusNotFound {
|
|
t.Errorf("releasing again: status %d, want 404", code)
|
|
}
|
|
if code := call(t, a, http.MethodPost, "/api/v1/facility/holds", `{"domain":"r12-07"}`, nil); code != http.StatusBadRequest {
|
|
t.Errorf("a domain with no level: status %d, want 400", code)
|
|
}
|
|
}
|