package placement import ( "strings" "testing" "gitea.mynodes.xyz/ilker/llmgw/internal/agent/discovery" "gitea.mynodes.xyz/ilker/llmgw/internal/control/inventory" ) // withDisk gives a node a local filesystem of the stated size. func withDisk(n inventory.Node, freeGB, totalGB int64) inventory.Node { n.Storage = []discovery.Storage{{ Path: "/var/lib/llmgw-models", FreeBytes: freeGB << 30, TotalBytes: totalGB << 30, }} return n } // weightsProfile is a model that would have to be fetched: it names a source // and says how much room that source needs. func weightsProfile(source string, diskGB int) Profile { return Profile{Name: "cpu", Engine: "llamacpp", Source: source, MinDiskGB: diskGB} } func TestANodeWithoutRoomForTheWeightsIsNotACandidate(t *testing.T) { // The failure this replaces: the node is picked, the agent starts // downloading, and it dies partway through with a disk-full error that // names neither the model nor the scheduler decision that caused it. s := New(store(withDisk(node("full", "", 0), 2, 60))) res := s.Place([]Profile{weightsProfile("https://models/x.gguf", 20)}, nil) if res.Placement != nil { t.Fatalf("placed on a node with 2 GB free for a 20 GB model") } if !strings.Contains(res.Explain(), "needs 20") { t.Errorf("the reason does not say what was needed:\n%s", res.Explain()) } } func TestRoomIsNotAskedForTwiceOnANodeThatAlreadyHasTheWeights(t *testing.T) { // A node holding the file needs no space to hold it again. Without this, // the one node best placed to run a model — because it already has it — is // the one most likely to be rejected, since holding the file is what used // the space up. const source = "/var/lib/llmgw-models/big.gguf" n := withDisk(node("holder", "", 0), 2, 60) n.Weights = []discovery.LocalWeights{{Path: source, SizeBytes: 40 << 30}} s := New(store(n)) res := s.Place([]Profile{weightsProfile(source, 20)}, nil) if res.Placement == nil { t.Fatalf("a node already holding the weights was rejected for lack of room:\n%s", res.Explain()) } } func TestANodeThatReportsNoDiskIsStillACandidate(t *testing.T) { // An agent too old to report storage, and a node with no model directories, // both report none. Excluding them would mean a control plane upgrade // quietly empties every node in a fleet that has not upgraded yet — a worse // failure than the one this check prevents. s := New(store(node("silent", "", 0))) res := s.Place([]Profile{weightsProfile("https://models/x.gguf", 20)}, nil) if res.Placement == nil { t.Fatalf("a node that reported no storage was treated as a full one:\n%s", res.Explain()) } } func TestWithoutADeclaredSizeDiskIsNotConsidered(t *testing.T) { // Every profile that existed before this field has no number, and must // keep being placed exactly as it was. s := New(store(withDisk(node("full", "", 0), 0, 60))) res := s.Place([]Profile{{Name: "cpu", Engine: "llamacpp", Source: "https://models/x.gguf"}}, nil) if res.Placement == nil { t.Fatalf("a profile with no declared size was rejected on disk:\n%s", res.Explain()) } } func TestSpaceOnASharedFilerDoesNotCountAsRoom(t *testing.T) { // The node cannot spend it: it belongs to everyone mounting the filer. A // node whose only storage is shared therefore reports no local disk, which // lands in the same place as reporting nothing — unknown, so not excluded. n := node("filer-only", "", 0) n.Storage = []discovery.Storage{{ Path: "/mnt/filer", FreeBytes: 500 << 30, TotalBytes: 900 << 30, Shared: true, }} if free, total := n.LocalDisk(); free != 0 || total != 0 { t.Fatalf("shared space counted as local: %d/%d", free, total) } }