Skip to content

Commit dd7331c

Browse files
authored
Merge pull request #57 from xu16601526267/fix/stop-button-backend
Fix deploy.delete compatibility for model-name stop actions
2 parents 81da609 + 093b02e commit dd7331c

2 files changed

Lines changed: 130 additions & 3 deletions

File tree

cmd/aima/tooldeps_deploy.go

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -451,6 +451,15 @@ func buildDeployDeps(ac *appContext, deps *mcp.ToolDeps,
451451

452452
deps.DeployDelete = func(ctx context.Context, name string) error {
453453
matches := findMatchingDeployments(ctx, name, nil, rt, nativeRt, dockerRt)
454+
if len(matches) == 0 {
455+
// Backward-compatible fallback: some UI paths pass the model name
456+
// instead of the concrete deployment name.
457+
suppressRecentlyDeleted := loadDeletedDeploymentSuppressor(ctx, db)
458+
modelStatus, statusErr := findDeploymentStatus(ctx, name, suppressRecentlyDeleted, rt, nativeRt, dockerRt)
459+
if statusErr == nil && modelStatus != nil && modelStatus.Name != "" {
460+
matches = findMatchingDeployments(ctx, modelStatus.Name, nil, rt, nativeRt, dockerRt)
461+
}
462+
}
454463
if len(matches) == 0 {
455464
return fmt.Errorf("deployment %q not found", name)
456465
}
@@ -472,6 +481,7 @@ func buildDeployDeps(ac *appContext, deps *mcp.ToolDeps,
472481
deletedAt := time.Now()
473482
tombstoneKeys := []string{name}
474483
seenKeys := map[string]struct{}{normalizeDeletedDeploymentKey(name): {}}
484+
verificationQueries := []string{name}
475485
rememberKey := func(key string) {
476486
norm := normalizeDeletedDeploymentKey(key)
477487
if norm == "" {
@@ -483,6 +493,18 @@ func buildDeployDeps(ac *appContext, deps *mcp.ToolDeps,
483493
seenKeys[norm] = struct{}{}
484494
tombstoneKeys = append(tombstoneKeys, key)
485495
}
496+
rememberVerificationQuery := func(key string) {
497+
norm := normalizeDeletedDeploymentKey(key)
498+
if norm == "" {
499+
return
500+
}
501+
for _, existing := range verificationQueries {
502+
if normalizeDeletedDeploymentKey(existing) == norm {
503+
return
504+
}
505+
}
506+
verificationQueries = append(verificationQueries, key)
507+
}
486508

487509
for _, match := range matches {
488510
if match.Runtime == nil || match.Status == nil {
@@ -492,11 +514,16 @@ func buildDeployDeps(ac *appContext, deps *mcp.ToolDeps,
492514
return fmt.Errorf("delete deployment %q on %s: %w", match.Status.Name, match.Runtime.Name(), err)
493515
}
494516
rememberKey(match.Status.Name)
495-
rememberKey(deploymentModelKey(match.Status))
517+
modelKey := deploymentModelKey(match.Status)
518+
rememberKey(modelKey)
519+
rememberVerificationQuery(match.Status.Name)
520+
rememberVerificationQuery(modelKey)
496521
}
497522

498-
if remaining := findMatchingDeployments(ctx, name, nil, rt, nativeRt, dockerRt); len(remaining) > 0 {
499-
return fmt.Errorf("delete deployment %q: deployment still active after delete (%s)", name, summarizeMatchedDeployments(remaining))
523+
for _, query := range verificationQueries {
524+
if remaining := findMatchingDeployments(ctx, query, nil, rt, nativeRt, dockerRt); len(remaining) > 0 {
525+
return fmt.Errorf("delete deployment %q: deployment still active after delete (%s)", name, summarizeMatchedDeployments(remaining))
526+
}
500527
}
501528

502529
for _, key := range tombstoneKeys {

cmd/aima/uat_chain_test.go

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
type deleteTrackingRuntime struct {
2323
name string
2424
status map[string]*aimaRuntime.DeploymentStatus
25+
aliases map[string]string
2526
list []*aimaRuntime.DeploymentStatus
2627
delErrs map[string]error
2728
deleted []string
@@ -49,6 +50,9 @@ func (r *deleteTrackingRuntime) Delete(_ context.Context, name string) error {
4950
}
5051

5152
func (r *deleteTrackingRuntime) Status(_ context.Context, name string) (*aimaRuntime.DeploymentStatus, error) {
53+
if target, ok := r.aliases[name]; ok {
54+
name = target
55+
}
5256
if s, ok := r.status[name]; ok {
5357
return s, nil
5458
}
@@ -215,6 +219,102 @@ func TestDeployDeleteFailsWhenDeploymentStillListedAfterDelete(t *testing.T) {
215219
}
216220
}
217221

222+
func TestDeployDeleteFallsBackFromModelNameToResolvedDeploymentName(t *testing.T) {
223+
ctx := context.Background()
224+
db, err := state.Open(ctx, ":memory:")
225+
if err != nil {
226+
t.Fatalf("Open: %v", err)
227+
}
228+
defer db.Close()
229+
230+
deploy := &aimaRuntime.DeploymentStatus{
231+
Name: "qwen3-6-35b-a3b-sglang",
232+
Phase: "running",
233+
Ready: true,
234+
Runtime: "docker",
235+
}
236+
dockerRt := &deleteTrackingRuntime{
237+
name: "docker",
238+
status: map[string]*aimaRuntime.DeploymentStatus{deploy.Name: deploy},
239+
aliases: map[string]string{"qwen3.6-35b-a3b": deploy.Name},
240+
}
241+
242+
deps := &mcp.ToolDeps{}
243+
buildDeployDeps(&appContext{
244+
db: db,
245+
rt: dockerRt,
246+
dockerRt: dockerRt,
247+
proxy: proxy.NewServer(),
248+
}, deps,
249+
func(context.Context, string, func(string, string), func(int64, int64)) error { return nil },
250+
func(context.Context, string, string, string, map[string]any, bool, func(string, string), func(engine.ProgressEvent), func(int64, int64)) (json.RawMessage, error) {
251+
return nil, nil
252+
},
253+
)
254+
255+
if err := deps.DeployDelete(ctx, "qwen3.6-35b-a3b"); err != nil {
256+
t.Fatalf("DeployDelete: %v", err)
257+
}
258+
if got := dockerRt.deleted; len(got) != 1 || got[0] != deploy.Name {
259+
t.Fatalf("docker delete sequence = %v, want [%s]", got, deploy.Name)
260+
}
261+
}
262+
263+
func TestDeployDeleteFallbackFailsWhenResolvedDeploymentStillActive(t *testing.T) {
264+
ctx := context.Background()
265+
db, err := state.Open(ctx, ":memory:")
266+
if err != nil {
267+
t.Fatalf("Open: %v", err)
268+
}
269+
defer db.Close()
270+
271+
deploy := &aimaRuntime.DeploymentStatus{
272+
Name: "qwen3-6-35b-a3b-sglang",
273+
Phase: "running",
274+
Ready: true,
275+
Runtime: "docker",
276+
Labels: map[string]string{
277+
"aima.dev/model": "qwen3-6-35b-a3b",
278+
"aima.dev/engine": "sglang",
279+
},
280+
}
281+
dockerRt := &deleteTrackingRuntime{
282+
name: "docker",
283+
status: map[string]*aimaRuntime.DeploymentStatus{deploy.Name: deploy},
284+
aliases: map[string]string{"qwen3.6-35b-a3b": deploy.Name},
285+
keep: map[string]bool{deploy.Name: true},
286+
}
287+
288+
deps := &mcp.ToolDeps{}
289+
buildDeployDeps(&appContext{
290+
db: db,
291+
rt: dockerRt,
292+
dockerRt: dockerRt,
293+
proxy: proxy.NewServer(),
294+
}, deps,
295+
func(context.Context, string, func(string, string), func(int64, int64)) error { return nil },
296+
func(context.Context, string, string, string, map[string]any, bool, func(string, string), func(engine.ProgressEvent), func(int64, int64)) (json.RawMessage, error) {
297+
return nil, nil
298+
},
299+
)
300+
301+
err = deps.DeployDelete(ctx, "qwen3.6-35b-a3b")
302+
if err == nil {
303+
t.Fatal("DeployDelete error = nil, want verification failure")
304+
}
305+
if !strings.Contains(err.Error(), "still active after delete") {
306+
t.Fatalf("DeployDelete error = %v, want still-active verification failure", err)
307+
}
308+
309+
tombstones, err := db.ListDeletedDeploymentsSince(ctx, time.Now().Add(-time.Minute))
310+
if err != nil {
311+
t.Fatalf("ListDeletedDeploymentsSince: %v", err)
312+
}
313+
if len(tombstones) != 0 {
314+
t.Fatalf("deleted deployment tombstones = %v, want empty on failed delete", tombstones)
315+
}
316+
}
317+
218318
func TestDeployListAndStatusExposeTopLevelModelAndEngine(t *testing.T) {
219319
ctx := context.Background()
220320
db, err := state.Open(ctx, ":memory:")

0 commit comments

Comments
 (0)