Skip to content

Commit c33c010

Browse files
authored
Merge pull request #509 from chaitin/fix/backend-lifecycle-vm-failed-20260416
fix(backend): 统一 VM 回调到生命周期
2 parents 79d0de0 + 5984581 commit c33c010

6 files changed

Lines changed: 135 additions & 24 deletions

File tree

backend/biz/host/handler/v1/internal.go

Lines changed: 19 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,7 @@ type InternalHostHandler struct {
3434
teamRepo domain.TeamHostRepo
3535
redis *redis.Client
3636
cache *cache.Cache
37-
hook domain.InternalHook // 可选,由内部项目通过 WithInternalHook 注入
38-
taskLifecycle *lifecycle.Manager[uuid.UUID, consts.TaskStatus, lifecycle.TaskMetadata]
37+
vmLifecycle *lifecycle.Manager[string, lifecycle.VMState, lifecycle.VMMetadata]
3938
hostUsecase domain.HostUsecase
4039
taskConns *ws.TaskConn
4140
projectUsecase domain.ProjectUsecase
@@ -51,18 +50,13 @@ func NewInternalHostHandler(i *do.Injector) (*InternalHostHandler, error) {
5150
teamRepo: do.MustInvoke[domain.TeamHostRepo](i),
5251
redis: do.MustInvoke[*redis.Client](i),
5352
cache: cache.New(15*time.Minute, 10*time.Minute),
54-
taskLifecycle: do.MustInvoke[*lifecycle.Manager[uuid.UUID, consts.TaskStatus, lifecycle.TaskMetadata]](i),
53+
vmLifecycle: do.MustInvoke[*lifecycle.Manager[string, lifecycle.VMState, lifecycle.VMMetadata]](i),
5554
hostUsecase: do.MustInvoke[domain.HostUsecase](i),
5655
taskConns: do.MustInvoke[*ws.TaskConn](i),
5756
projectUsecase: do.MustInvoke[domain.ProjectUsecase](i),
5857
tokenProvider: do.MustInvoke[*gituc.TokenProvider](i),
5958
}
6059

61-
// 可选注入 InternalHook
62-
if hook, err := do.Invoke[domain.InternalHook](i); err == nil {
63-
h.hook = hook
64-
}
65-
6660
g := w.Group("/internal")
6761
g.POST("/check-token", web.BindHandler(h.CheckToken))
6862
g.POST("/host-info", web.BindHandler(h.ReportHostInfo))
@@ -350,16 +344,13 @@ func (h *InternalHostHandler) VmReady(c *web.Context, req taskflow.VirtualMachin
350344
continue
351345
}
352346

353-
if t.Kind == consts.TaskTypeReview && t.SubType == consts.TaskSubTypePrReview {
354-
} else {
355-
if err := h.taskLifecycle.Transition(c.Request().Context(), t.ID, consts.TaskStatusProcessing, lifecycle.TaskMetadata{
356-
TaskID: t.ID,
357-
UserID: t.UserID,
358-
}); err != nil {
359-
h.logger.With("task", t, "error", err).ErrorContext(c.Request().Context(), "failed to transition task to processing")
360-
}
347+
if err := h.vmLifecycle.Transition(c.Request().Context(), vm.ID, lifecycle.VMStateRunning, lifecycle.VMMetadata{
348+
VMID: vm.ID,
349+
TaskID: &t.ID,
350+
UserID: t.UserID,
351+
}); err != nil {
352+
h.logger.With("task", t, "error", err).ErrorContext(c.Request().Context(), "failed to transition vm to running")
361353
}
362-
363354
}
364355

365356
return c.Success(nil)
@@ -384,15 +375,19 @@ func (h *InternalHostHandler) VmConditions(c *web.Context, req taskflow.VirtualM
384375
return err
385376
}
386377

387-
// 条件失败时通过 hook 通知内部项目(任务状态转换等)
388-
if h.hook != nil {
378+
for _, task := range vm.Edges.Tasks {
389379
for _, cond := range req.Conditions {
390-
if cond.Type == string(etypes.ConditionTypeFailed) {
391-
if err := h.hook.OnVmConditionFailed(c.Request().Context(), vm.ID); err != nil {
392-
h.logger.With("error", err).ErrorContext(c.Request().Context(), "hook OnVmConditionFailed failed")
393-
}
394-
break
380+
if cond.Type != string(etypes.ConditionTypeFailed) {
381+
continue
382+
}
383+
if err := h.vmLifecycle.Transition(c.Request().Context(), vm.ID, lifecycle.VMStateFailed, lifecycle.VMMetadata{
384+
VMID: vm.ID,
385+
TaskID: &task.ID,
386+
UserID: task.UserID,
387+
}); err != nil {
388+
h.logger.With("task", task, "error", err).ErrorContext(c.Request().Context(), "failed to transition vm to failed")
395389
}
390+
break
396391
}
397392
}
398393

backend/go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ require (
66
entgo.io/ent v0.14.5
77
github.com/GoYoko/web v1.6.0
88
github.com/ackcoder/go-cap v1.1.3
9+
github.com/alicebob/miniredis/v2 v2.35.0
910
github.com/aliyun/alibabacloud-nls-go-sdk v1.1.1
1011
github.com/coder/websocket v1.8.14
1112
github.com/gogo/protobuf v1.3.2
@@ -83,6 +84,7 @@ require (
8384
github.com/subosito/gotenv v1.6.0 // indirect
8485
github.com/valyala/bytebufferpool v1.0.0 // indirect
8586
github.com/valyala/fasttemplate v1.2.2 // indirect
87+
github.com/yuin/gopher-lua v1.1.1 // indirect
8688
github.com/zclconf/go-cty v1.14.4 // indirect
8789
github.com/zclconf/go-cty-yaml v1.1.0 // indirect
8890
go.opentelemetry.io/auto/sdk v1.2.1 // indirect

backend/go.sum

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ github.com/ackcoder/go-cap v1.1.3 h1:rHIZEmyOM/KlXJQxGoy3UHpzpeUhw+V8qa/OoEaJR7A
1616
github.com/ackcoder/go-cap v1.1.3/go.mod h1:NRffl9i4+VPdgAgMT4G62cXakEyCyZtXg9ZMX3/RsDA=
1717
github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo=
1818
github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
19+
github.com/alicebob/miniredis/v2 v2.35.0 h1:QwLphYqCEAo1eu1TqPRN2jgVMPBweeQcR21jeqDCONI=
20+
github.com/alicebob/miniredis/v2 v2.35.0/go.mod h1:TcL7YfarKPGDAthEtl5NBeHZfeUQj6OXMm/+iu5cLMM=
1921
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1376 h1:lExo7heZgdFn5AbaNJEllbA0KSJ/Z8T7MphvMREJOOo=
2022
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1376/go.mod h1:9CMdKNL3ynIGPpfTcdwTvIm8SGuAZYYC4jFVSSvE1YQ=
2123
github.com/aliyun/alibabacloud-nls-go-sdk v1.1.1 h1:LjItoNZuu5xHlsByFo+kr3nGa4LRIESCGWhfurayxBg=
@@ -243,6 +245,8 @@ github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQ
243245
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
244246
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
245247
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
248+
github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M=
249+
github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw=
246250
github.com/zclconf/go-cty v1.14.4 h1:uXXczd9QDGsgu0i/QFR/hzI5NYCHLf6NQw/atrbnhq8=
247251
github.com/zclconf/go-cty v1.14.4/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE=
248252
github.com/zclconf/go-cty-yaml v1.1.0 h1:nP+jp0qPHv2IhUVqmQSzjvqAWcObN0KBkUl2rWBdig0=
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package lifecycle
2+
3+
import (
4+
"context"
5+
"log/slog"
6+
7+
"github.com/google/uuid"
8+
"github.com/samber/do"
9+
10+
"github.com/chaitin/MonkeyCode/backend/consts"
11+
)
12+
13+
// VMTaskHook 将 VM 生命周期状态同步到任务生命周期。
14+
type VMTaskHook struct {
15+
taskLifecycle *Manager[uuid.UUID, consts.TaskStatus, TaskMetadata]
16+
logger *slog.Logger
17+
}
18+
19+
func NewVMTaskHook(i *do.Injector) *VMTaskHook {
20+
return &VMTaskHook{
21+
taskLifecycle: do.MustInvoke[*Manager[uuid.UUID, consts.TaskStatus, TaskMetadata]](i),
22+
logger: do.MustInvoke[*slog.Logger](i).With("hook", "vm-task-hook"),
23+
}
24+
}
25+
26+
func (h *VMTaskHook) Name() string { return "vm-task-hook" }
27+
func (h *VMTaskHook) Priority() int { return 100 }
28+
func (h *VMTaskHook) Async() bool { return false }
29+
30+
func (h *VMTaskHook) OnStateChange(ctx context.Context, _ string, _ VMState, to VMState, metadata VMMetadata) error {
31+
if metadata.TaskID == nil {
32+
return nil
33+
}
34+
35+
var target consts.TaskStatus
36+
switch to {
37+
case VMStateRunning:
38+
target = consts.TaskStatusProcessing
39+
case VMStateFailed:
40+
target = consts.TaskStatusError
41+
default:
42+
return nil
43+
}
44+
45+
h.logger.InfoContext(ctx, "sync task lifecycle from vm lifecycle", "task_id", metadata.TaskID, "state", target)
46+
return h.taskLifecycle.Transition(ctx, *metadata.TaskID, target, TaskMetadata{
47+
TaskID: *metadata.TaskID,
48+
UserID: metadata.UserID,
49+
})
50+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package lifecycle
2+
3+
import (
4+
"context"
5+
"io"
6+
"log/slog"
7+
"testing"
8+
9+
"github.com/alicebob/miniredis/v2"
10+
"github.com/google/uuid"
11+
"github.com/redis/go-redis/v9"
12+
13+
"github.com/chaitin/MonkeyCode/backend/consts"
14+
)
15+
16+
func TestVMTaskHook_OnStateChange_FailedTransitionsTaskToError(t *testing.T) {
17+
mr, err := miniredis.Run()
18+
if err != nil {
19+
t.Fatalf("miniredis.Run() error = %v", err)
20+
}
21+
defer mr.Close()
22+
23+
rdb := redis.NewClient(&redis.Options{Addr: mr.Addr()})
24+
defer rdb.Close()
25+
26+
taskLifecycle := NewManager[uuid.UUID, consts.TaskStatus, TaskMetadata](
27+
rdb,
28+
WithLogger[uuid.UUID, consts.TaskStatus, TaskMetadata](slog.New(slog.NewTextHandler(io.Discard, nil))),
29+
WithTransitions[uuid.UUID, consts.TaskStatus, TaskMetadata](TaskTransitions()),
30+
)
31+
32+
taskID := uuid.New()
33+
userID := uuid.New()
34+
meta := TaskMetadata{TaskID: taskID, UserID: userID}
35+
if err := taskLifecycle.Transition(context.Background(), taskID, consts.TaskStatusPending, meta); err != nil {
36+
t.Fatalf("taskLifecycle.Transition(pending) error = %v", err)
37+
}
38+
39+
hook := &VMTaskHook{
40+
taskLifecycle: taskLifecycle,
41+
logger: slog.New(slog.NewTextHandler(io.Discard, nil)),
42+
}
43+
44+
if err := hook.OnStateChange(context.Background(), "vm-1", VMStatePending, VMStateFailed, VMMetadata{
45+
VMID: "vm-1",
46+
TaskID: &taskID,
47+
UserID: userID,
48+
}); err != nil {
49+
t.Fatalf("OnStateChange() error = %v", err)
50+
}
51+
52+
state, err := taskLifecycle.GetState(context.Background(), taskID)
53+
if err != nil {
54+
t.Fatalf("taskLifecycle.GetState() error = %v", err)
55+
}
56+
if state != consts.TaskStatusError {
57+
t.Fatalf("task state = %s, want %s", state, consts.TaskStatusError)
58+
}
59+
}

backend/pkg/register.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,7 @@ func RegisterInfra(i *do.Injector, w ...*web.Web) error {
231231
)
232232

233233
lc.Register(
234+
lifecycle.NewVMTaskHook(i),
234235
lifecycle.NewVMRecycleHook(i),
235236
)
236237

0 commit comments

Comments
 (0)