Skip to content

Commit bb0596a

Browse files
engine-manager: let the uninstaller keep a model store the user moved
The uninstaller took only the port from a user's override. The running engine manager reads the whole override, so a reset kept a models_dir the user had pointed elsewhere while the uninstaller kept only the bundled default -- and removed the user's store if it sat inside a removal target. Take models_dir along with the port. Both say where the engine's things are rather than what to run or delete: a store decides only what removal keeps. The overlay is checked with the same validation startup applies, so a store that would hold a removal target, or a templated one, is ignored whole. Signed-off-by: Terve <ntervalon@nvidia.com>
1 parent 9cbb91f commit bb0596a

3 files changed

Lines changed: 88 additions & 27 deletions

File tree

‎services/nvpair-engine-manager/main.go‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ func userPaths() (manifestDir, installBase string) {
168168
func runUninstallManaged() {
169169
manifestDir, installBase := userPaths()
170170
reg := buildBundledRegistry()
171-
reg.applyPortOverrides(manifestDir)
171+
reg.applyLocationOverrides(manifestDir)
172172
exec := NewExecutor(reg, NewReporter(nil), func(string, any) {}, installBase)
173173
ctx, cancel := context.WithTimeout(context.Background(), uninstallManagedTimeout)
174174
defer cancel()

‎services/nvpair-engine-manager/portoverride_test.go‎

Lines changed: 56 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -159,20 +159,29 @@ func TestWriteJSONAtomicReplacesExistingFile(t *testing.T) {
159159
}
160160
}
161161

162-
// TestUninstallerTakesOnlyThePortFromAnOverride checks the uninstaller looks for
163-
// an engine on the port the user moved it to, and takes nothing else from the
164-
// override. The uninstaller runs elevated on Windows, and the override is a file
165-
// any process running as the user can write.
166-
func TestUninstallerTakesOnlyThePortFromAnOverride(t *testing.T) {
167-
host := runtime.GOOS + "/" + runtime.GOARCH
168-
bundled, ok := buildBundledRegistry().Get("ollama")
162+
// bundledHostPlatform returns an engine's bundled platform for this host, or
163+
// skips the test where the engine has none.
164+
func bundledHostPlatform(t *testing.T, engine string) Platform {
165+
t.Helper()
166+
bundled, ok := buildBundledRegistry().Get(engine)
169167
if !ok {
170-
t.Fatal("no bundled ollama manifest")
168+
t.Fatalf("no bundled %s manifest", engine)
171169
}
172-
want, ok := bundled.Platforms[host]
170+
platform, ok := bundled.Platforms[runtime.GOOS+"/"+runtime.GOARCH]
173171
if !ok {
174-
t.Skipf("ollama has no manifest for %s", host)
172+
t.Skipf("%s has no manifest for this host", engine)
175173
}
174+
return platform
175+
}
176+
177+
// TestUninstallerTakesOnlyLocationsFromAnOverride checks the uninstaller looks
178+
// for an engine on the port the user moved it to and keeps the model store they
179+
// moved it to, and takes nothing else from the override. The uninstaller runs
180+
// elevated on Windows, and the override is a file any process running as the
181+
// user can write.
182+
func TestUninstallerTakesOnlyLocationsFromAnOverride(t *testing.T) {
183+
host := runtime.GOOS + "/" + runtime.GOARCH
184+
want := bundledHostPlatform(t, "ollama")
176185
dir := t.TempDir()
177186
override := map[string]any{
178187
"engine": "ollama",
@@ -190,7 +199,7 @@ func TestUninstallerTakesOnlyThePortFromAnOverride(t *testing.T) {
190199
}
191200

192201
reg := buildBundledRegistry()
193-
reg.applyPortOverrides(dir)
202+
reg.applyLocationOverrides(dir)
194203
got, ok := reg.Get("ollama")
195204
if !ok {
196205
t.Fatal("ollama vanished from the registry")
@@ -200,8 +209,41 @@ func TestUninstallerTakesOnlyThePortFromAnOverride(t *testing.T) {
200209
if platform.Runtime.Port != 21002 {
201210
t.Errorf("port %d, want the host platform's override, 21002", platform.Runtime.Port)
202211
}
203-
if platform.ModelsDir != want.ModelsDir || !reflect.DeepEqual(platform.Uninstall, want.Uninstall) {
204-
t.Errorf("took more than the port from the override: models_dir %q, uninstall %+v",
205-
platform.ModelsDir, platform.Uninstall)
212+
if platform.ModelsDir != "~/somewhere-else" {
213+
t.Errorf("models_dir %q, want the override's store", platform.ModelsDir)
214+
}
215+
if !reflect.DeepEqual(platform.Uninstall, want.Uninstall) {
216+
t.Errorf("took the uninstall commands from the override: %+v", platform.Uninstall)
217+
}
218+
}
219+
220+
// TestUninstallerIgnoresAnOverrideStoreHoldingWhatItRemoves checks an override
221+
// whose model store would contain a removal target is ignored whole, as startup
222+
// ignores it, rather than leaving the uninstaller with a store it must refuse
223+
// to remove around.
224+
func TestUninstallerIgnoresAnOverrideStoreHoldingWhatItRemoves(t *testing.T) {
225+
host := runtime.GOOS + "/" + runtime.GOARCH
226+
want := bundledHostPlatform(t, "lmstudio")
227+
dir := t.TempDir()
228+
override := map[string]any{
229+
"engine": "lmstudio",
230+
"models_dir": "~",
231+
"runtime": map[string]any{"port": 21003},
232+
}
233+
if err := writeJSONAtomic(filepath.Join(dir, "lmstudio.json"), override); err != nil {
234+
t.Fatal(err)
235+
}
236+
237+
reg := buildBundledRegistry()
238+
reg.applyLocationOverrides(dir)
239+
got, ok := reg.Get("lmstudio")
240+
if !ok {
241+
t.Fatal("lmstudio vanished from the registry")
242+
}
243+
platform := got.Platforms[host]
244+
245+
if platform.ModelsDir != want.ModelsDir || platform.Runtime.Port != want.Runtime.Port {
246+
t.Errorf("applied an invalid override: models_dir %q, port %d; want the bundled %q, %d",
247+
platform.ModelsDir, platform.Runtime.Port, want.ModelsDir, want.Runtime.Port)
206248
}
207249
}

‎services/nvpair-engine-manager/registry.go‎

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -468,12 +468,15 @@ func (r *Registry) LoadOverrideDir(dir string) error {
468468
return nil
469469
}
470470

471-
// applyPortOverrides takes each engine's port from the user's override
472-
// manifest and nothing else from it. The uninstaller runs with privileges the
473-
// override's author may not have, so the commands it runs and the paths it
474-
// deletes stay bundled. A port decides only where it looks for the engine still
475-
// running, which is wherever the user moved it to.
476-
func (r *Registry) applyPortOverrides(dir string) {
471+
// applyLocationOverrides takes from each user override only where the engine's
472+
// things are: its port and its model store. The uninstaller runs with
473+
// privileges the override's author may not have, so the commands it runs and
474+
// the paths it deletes stay bundled. A port decides where it looks for the
475+
// engine still running, and a model store decides what removal keeps — the
476+
// same store the running engine manager, which reads the whole override,
477+
// already keeps. An override that would leave the platform invalid is ignored,
478+
// as it is at startup.
479+
func (r *Registry) applyLocationOverrides(dir string) {
477480
if dir == "" {
478481
return
479482
}
@@ -488,23 +491,39 @@ func (r *Registry) applyPortOverrides(dir string) {
488491
}
489492
var override struct {
490493
Engine string `json:"engine"`
494+
ModelsDir string `json:"models_dir"`
491495
Runtime portOnly `json:"runtime"`
492496
Platforms map[string]struct {
493-
Runtime portOnly `json:"runtime"`
497+
ModelsDir string `json:"models_dir"`
498+
Runtime portOnly `json:"runtime"`
494499
} `json:"platforms"`
495500
}
496501
if err := json.Unmarshal(data, &override); err != nil || override.Engine != name {
497502
continue
498503
}
504+
platform, ok := m.Platforms[host]
505+
if !ok {
506+
continue
507+
}
508+
hostOverride := override.Platforms[host]
499509
port := override.Runtime.Port
500-
if hostPort := override.Platforms[host].Runtime.Port; hostPort != 0 {
501-
port = hostPort
510+
if hostOverride.Runtime.Port != 0 {
511+
port = hostOverride.Runtime.Port
502512
}
503-
platform, ok := m.Platforms[host]
504-
if !ok || port <= 0 || port > 65535 {
513+
if port > 0 && port <= 65535 {
514+
platform.Runtime.Port = port
515+
}
516+
store := override.ModelsDir
517+
if hostOverride.ModelsDir != "" {
518+
store = hostOverride.ModelsDir
519+
}
520+
if store != "" {
521+
platform.ModelsDir = store
522+
}
523+
if err := platform.validate(host); err != nil {
524+
slog.Warn("ignoring an override's port and model store", "engine", name, "err", err)
505525
continue
506526
}
507-
platform.Runtime.Port = port
508527
m.Platforms[host] = platform
509528
}
510529
}

0 commit comments

Comments
 (0)