CI / test (push) Successful in 1m23s
Placement had no idea how much memory a node has or a model needs. A CPU profile has no card to be free, so eligibleNodes accepted every ready node: Qwen2.5-Coder 14B, whose working set is about 24 GB, would have been placed just as happily on one of the 8 GB nodes, which reports itself perfectly healthy right up until the OOM killer arrives. node_selector was yesterday's stopgap and it is the operator restating by hand something the system can know. Agents now report MemTotal, profiles declare min_memory_gb, and placement filters on it, naming what was short — "has 8 GB of memory, needs 24" is actionable where "no candidates were considered" is not. The label selector stays, because "this node is for coding models" is a real thing to want to say that memory does not express. Two deliberate non-exclusions. A profile with no requirement fits anywhere, which is every profile that exists today. And a node that reports no memory is still usable: an agent older than the field says nothing, and letting a control plane upgrade quietly empty half a fleet is a worse failure than the one this prevents. ctl nodes grows a MEM column showing "-" rather than 0 for a node that did not say, because an old agent is not a small machine. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
40 lines
1.2 KiB
Go
40 lines
1.2 KiB
Go
package discovery
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
)
|
|
|
|
func TestMemTotalIsReadFromMeminfo(t *testing.T) {
|
|
dir := t.TempDir()
|
|
p := filepath.Join(dir, "meminfo")
|
|
// The real shape, including the fields before and after, so the parser is
|
|
// not tested against something tidier than it will see.
|
|
if err := os.WriteFile(p, []byte(
|
|
"MemTotal: 32876244 kB\nMemFree: 1234 kB\nMemAvailable: 5678 kB\n"), 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got, want := memTotalMBFrom(p), 32876244/1024; got != want {
|
|
t.Errorf("memTotalMBFrom = %d, want %d", got, want)
|
|
}
|
|
}
|
|
|
|
func TestUnreadableMeminfoReportsUnknownRatherThanZeroMemory(t *testing.T) {
|
|
// Zero means "did not say" everywhere downstream, and placement treats it
|
|
// as no constraint. A node with genuinely no memory is not a case.
|
|
if got := memTotalMBFrom(filepath.Join(t.TempDir(), "absent")); got != 0 {
|
|
t.Errorf("got %d for a missing file", got)
|
|
}
|
|
dir := t.TempDir()
|
|
p := filepath.Join(dir, "meminfo")
|
|
for _, junk := range []string{"", "MemTotal:\n", "MemTotal: not-a-number kB\n", "Other: 1 kB\n"} {
|
|
if err := os.WriteFile(p, []byte(junk), 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got := memTotalMBFrom(p); got != 0 {
|
|
t.Errorf("got %d for %q", got, junk)
|
|
}
|
|
}
|
|
}
|