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>
94 lines
2.9 KiB
Go
94 lines
2.9 KiB
Go
package store
|
|
|
|
import (
|
|
"context"
|
|
"time"
|
|
)
|
|
|
|
// FacilityHold is a domain taken out of placement: rack:r12-07.
|
|
type FacilityHold struct {
|
|
Domain string
|
|
Reason string
|
|
Drain bool
|
|
At time.Time
|
|
By string
|
|
}
|
|
|
|
// FacilityHolds are every domain held, oldest first.
|
|
func (s *DB) FacilityHolds(ctx context.Context) ([]FacilityHold, error) {
|
|
rows, err := s.db.QueryContext(ctx,
|
|
`SELECT domain, reason, drain, held_at, held_by FROM facility_holds ORDER BY held_at, domain`)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
var out []FacilityHold
|
|
for rows.Next() {
|
|
var h FacilityHold
|
|
var drain, at int64
|
|
if err := rows.Scan(&h.Domain, &h.Reason, &drain, &at, &h.By); err != nil {
|
|
return nil, err
|
|
}
|
|
h.Drain, h.At = drain != 0, time.UnixMilli(at)
|
|
out = append(out, h)
|
|
}
|
|
return out, rows.Err()
|
|
}
|
|
|
|
// SetFacilityHold holds a domain, or changes the reason it is held for.
|
|
func (s *DB) SetFacilityHold(ctx context.Context, h FacilityHold) error {
|
|
drain := 0
|
|
if h.Drain {
|
|
drain = 1
|
|
}
|
|
_, err := s.db.ExecContext(ctx,
|
|
`INSERT INTO facility_holds (domain, reason, drain, held_at, held_by) VALUES (?, ?, ?, ?, ?)
|
|
ON CONFLICT (domain) DO UPDATE SET reason = excluded.reason, drain = excluded.drain,
|
|
held_at = excluded.held_at, held_by = excluded.held_by`,
|
|
h.Domain, h.Reason, drain, h.At.UnixMilli(), h.By)
|
|
return err
|
|
}
|
|
|
|
// DeleteFacilityHold releases a domain; releasing one not held is no error.
|
|
func (s *DB) DeleteFacilityHold(ctx context.Context, domain string) error {
|
|
_, err := s.db.ExecContext(ctx, `DELETE FROM facility_holds WHERE domain = ?`, domain)
|
|
return err
|
|
}
|
|
|
|
// NodeLabels are the labels set through the control plane: node id, then
|
|
// key, then value.
|
|
func (s *DB) NodeLabels(ctx context.Context) (map[string]map[string]string, error) {
|
|
rows, err := s.db.QueryContext(ctx, `SELECT node_id, key, value FROM node_labels`)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
out := map[string]map[string]string{}
|
|
for rows.Next() {
|
|
var node, key, value string
|
|
if err := rows.Scan(&node, &key, &value); err != nil {
|
|
return nil, err
|
|
}
|
|
if out[node] == nil {
|
|
out[node] = map[string]string{}
|
|
}
|
|
out[node][key] = value
|
|
}
|
|
return out, rows.Err()
|
|
}
|
|
|
|
// SetNodeLabel sets one label on a node; an empty value removes it, which
|
|
// leaves the agent's own value for the key, if it has one, in force again.
|
|
func (s *DB) SetNodeLabel(ctx context.Context, node, key, value, by string) error {
|
|
if value == "" {
|
|
_, err := s.db.ExecContext(ctx, `DELETE FROM node_labels WHERE node_id = ? AND key = ?`, node, key)
|
|
return err
|
|
}
|
|
_, err := s.db.ExecContext(ctx,
|
|
`INSERT INTO node_labels (node_id, key, value, updated_at, updated_by) VALUES (?, ?, ?, ?, ?)
|
|
ON CONFLICT (node_id, key) DO UPDATE SET value = excluded.value,
|
|
updated_at = excluded.updated_at, updated_by = excluded.updated_by`,
|
|
node, key, value, time.Now().UnixMilli(), by)
|
|
return err
|
|
}
|