Skip to content

Commit 0ddd688

Browse files
authored
Merge pull request #504 from chaitin/feat/vm-status-resolver
统一后端 VM 状态判定入口
2 parents 1089625 + 8081e16 commit 0ddd688

File tree

6 files changed

+200
-62
lines changed

6 files changed

+200
-62
lines changed

backend/biz/host/usecase/host.go

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727
"github.com/chaitin/MonkeyCode/backend/pkg/entx"
2828
"github.com/chaitin/MonkeyCode/backend/pkg/random"
2929
"github.com/chaitin/MonkeyCode/backend/pkg/taskflow"
30+
"github.com/chaitin/MonkeyCode/backend/pkg/vmstatus"
3031
"github.com/chaitin/MonkeyCode/backend/templates"
3132
)
3233

@@ -225,12 +226,14 @@ func (h *HostUsecase) List(ctx context.Context, uid uuid.UUID) (*domain.HostList
225226
dHost := cvt.From(host, &domain.Host{Status: status})
226227
dHost.IsDefault = dHost.GetIsDefault(user)
227228
dHost.VirtualMachines = cvt.Iter(host.Edges.Vms, func(_ int, vm *db.VirtualMachine) *domain.VirtualMachine {
228-
status := taskflow.VirtualMachineStatusOffline
229-
if vmonline.OnlineMap[vm.ID] {
230-
status = taskflow.VirtualMachineStatusOnline
231-
}
232229
return cvt.From(vm, &domain.VirtualMachine{
233-
Status: status,
230+
Status: vmstatus.Resolve(vmstatus.Input{
231+
Online: vmonline.OnlineMap[vm.ID],
232+
Conditions: vm.Conditions.Conditions,
233+
IsRecycled: vm.IsRecycled,
234+
CreatedAt: vm.CreatedAt,
235+
Now: time.Now(),
236+
}),
234237
})
235238
})
236239

@@ -476,17 +479,14 @@ func (h *HostUsecase) VMInfo(ctx context.Context, uid uuid.UUID, id string) (*do
476479
return nil, err
477480
}
478481

479-
status := taskflow.VirtualMachineStatusOffline
480-
if info, err := h.taskflow.VirtualMachiner().Info(ctx, taskflow.VirtualMachineInfoReq{
481-
ID: vm.ID,
482-
UserID: vm.UserID.String(),
483-
}); err == nil && info != nil && info.Status != taskflow.VirtualMachineStatusUnknown {
484-
status = info.Status
485-
} else if vmonline.OnlineMap[vm.ID] {
486-
status = taskflow.VirtualMachineStatusOnline
487-
}
488482
dvm := cvt.From(vm, &domain.VirtualMachine{
489-
Status: status,
483+
Status: vmstatus.Resolve(vmstatus.Input{
484+
Online: vmonline.OnlineMap[vm.ID],
485+
Conditions: vm.Conditions.Conditions,
486+
IsRecycled: vm.IsRecycled,
487+
CreatedAt: vm.CreatedAt,
488+
Now: time.Now(),
489+
}),
490490
})
491491

492492
if dvm.Host != nil {

backend/biz/task/usecase/task.go

Lines changed: 7 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,14 @@ import (
2323
"github.com/chaitin/MonkeyCode/backend/consts"
2424
"github.com/chaitin/MonkeyCode/backend/db"
2525
"github.com/chaitin/MonkeyCode/backend/domain"
26-
"github.com/chaitin/MonkeyCode/backend/ent/types"
2726
"github.com/chaitin/MonkeyCode/backend/errcode"
2827
"github.com/chaitin/MonkeyCode/backend/pkg/cvt"
2928
"github.com/chaitin/MonkeyCode/backend/pkg/entx"
3029
"github.com/chaitin/MonkeyCode/backend/pkg/lifecycle"
3130
"github.com/chaitin/MonkeyCode/backend/pkg/loki"
3231
"github.com/chaitin/MonkeyCode/backend/pkg/notify/dispatcher"
3332
"github.com/chaitin/MonkeyCode/backend/pkg/taskflow"
33+
"github.com/chaitin/MonkeyCode/backend/pkg/vmstatus"
3434
"github.com/chaitin/MonkeyCode/backend/templates"
3535
)
3636

@@ -128,27 +128,12 @@ func (a *TaskUsecase) Info(ctx context.Context, user *domain.User, id uuid.UUID)
128128
IDs: []string{vm.ID},
129129
})
130130
a.logger.With("resp", resp, "id", vm.ID).DebugContext(ctx, "is online check")
131-
132-
if resp != nil && resp.OnlineMap[vm.ID] {
133-
vm.Status = taskflow.VirtualMachineStatusOnline
134-
} else {
135-
vm.Status = taskflow.VirtualMachineStatusPending
136-
137-
for _, cond := range vm.Conditions {
138-
switch cond.Type {
139-
case types.ConditionTypeFailed:
140-
vm.Status = taskflow.VirtualMachineStatusOffline
141-
case types.ConditionTypeHibernated:
142-
if strings.ToLower(strings.TrimSpace(cond.Reason)) == "hibernated" {
143-
vm.Status = taskflow.VirtualMachineStatusHibernated
144-
}
145-
case types.ConditionTypeReady:
146-
if time.Since(time.Unix(vm.CreatedAt, 0)) > 2*time.Minute {
147-
vm.Status = taskflow.VirtualMachineStatusOffline
148-
}
149-
}
150-
}
151-
}
131+
vm.Status = vmstatus.Resolve(vmstatus.Input{
132+
Online: resp != nil && resp.OnlineMap[vm.ID],
133+
Conditions: vm.Conditions,
134+
CreatedAt: time.Unix(vm.CreatedAt, 0),
135+
Now: time.Now(),
136+
})
152137
}
153138

154139
if stat, err := a.repo.Stat(ctx, id); err == nil {

backend/biz/team/usecase/host.go

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"github.com/chaitin/MonkeyCode/backend/domain"
1919
"github.com/chaitin/MonkeyCode/backend/pkg/cvt"
2020
"github.com/chaitin/MonkeyCode/backend/pkg/taskflow"
21+
"github.com/chaitin/MonkeyCode/backend/pkg/vmstatus"
2122
)
2223

2324
// TeamHostUsecase 团队宿主机业务逻辑层
@@ -103,12 +104,14 @@ func (u *TeamHostUsecase) List(ctx context.Context, teamUser *domain.TeamUser) (
103104
})
104105

105106
dHost.VirtualMachines = cvt.Iter(host.Edges.Vms, func(_ int, vm *db.VirtualMachine) *domain.VirtualMachine {
106-
status := taskflow.VirtualMachineStatusOffline
107-
if vmonline.OnlineMap[vm.ID] {
108-
status = taskflow.VirtualMachineStatusOnline
109-
}
110107
return cvt.From(vm, &domain.VirtualMachine{
111-
Status: status,
108+
Status: vmstatus.Resolve(vmstatus.Input{
109+
Online: vmonline.OnlineMap[vm.ID],
110+
Conditions: vm.Conditions.Conditions,
111+
IsRecycled: vm.IsRecycled,
112+
CreatedAt: vm.CreatedAt,
113+
Now: time.Now(),
114+
}),
112115
})
113116
})
114117

backend/domain/host.go

Lines changed: 8 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
etypes "github.com/chaitin/MonkeyCode/backend/ent/types"
1313
"github.com/chaitin/MonkeyCode/backend/pkg/cvt"
1414
"github.com/chaitin/MonkeyCode/backend/pkg/taskflow"
15+
"github.com/chaitin/MonkeyCode/backend/pkg/vmstatus"
1516
)
1617

1718
// HostUsecase 主机业务逻辑接口
@@ -167,29 +168,16 @@ func (v *VirtualMachine) From(vm *db.VirtualMachine) *VirtualMachine {
167168
v.Version = vm.Version
168169
v.EnvironmentID = vm.EnvironmentID
169170
v.CreatedAt = vm.CreatedAt.Unix()
170-
171171
if vm.Conditions != nil {
172172
v.Conditions = vm.Conditions.Conditions
173-
if v.Status != taskflow.VirtualMachineStatusOnline {
174-
v.Status = taskflow.VirtualMachineStatusPending
175-
for _, cond := range vm.Conditions.Conditions {
176-
switch cond.Type {
177-
case etypes.ConditionTypeFailed:
178-
v.Status = taskflow.VirtualMachineStatusOffline
179-
case etypes.ConditionTypeHibernated:
180-
v.Status = taskflow.VirtualMachineStatusHibernated
181-
case etypes.ConditionTypeReady:
182-
if time.Since(time.UnixMilli(cond.LastTransitionTime)) > 3*time.Minute {
183-
v.Status = taskflow.VirtualMachineStatusOffline
184-
}
185-
}
186-
}
187-
}
188-
}
189-
190-
if vm.IsRecycled {
191-
v.Status = taskflow.VirtualMachineStatusOffline
192173
}
174+
v.Status = vmstatus.Resolve(vmstatus.Input{
175+
Online: v.Status == taskflow.VirtualMachineStatusOnline,
176+
Conditions: v.Conditions,
177+
IsRecycled: vm.IsRecycled,
178+
CreatedAt: vm.CreatedAt,
179+
Now: time.Now(),
180+
})
193181

194182
switch vm.TTLKind {
195183
case consts.CountDown:

backend/pkg/vmstatus/status.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package vmstatus
2+
3+
import (
4+
"time"
5+
6+
etypes "github.com/chaitin/MonkeyCode/backend/ent/types"
7+
"github.com/chaitin/MonkeyCode/backend/pkg/taskflow"
8+
)
9+
10+
const readyTimeout = 3 * time.Minute
11+
12+
type Input struct {
13+
Online bool
14+
Conditions []*etypes.Condition
15+
IsRecycled bool
16+
CreatedAt time.Time
17+
Now time.Time
18+
}
19+
20+
func Resolve(input Input) taskflow.VirtualMachineStatus {
21+
if input.IsRecycled {
22+
return taskflow.VirtualMachineStatusOffline
23+
}
24+
25+
if input.Online {
26+
return taskflow.VirtualMachineStatusOnline
27+
}
28+
29+
for _, cond := range input.Conditions {
30+
switch cond.Type {
31+
case etypes.ConditionTypeFailed:
32+
return taskflow.VirtualMachineStatusOffline
33+
case etypes.ConditionTypeHibernated:
34+
return taskflow.VirtualMachineStatusHibernated
35+
case etypes.ConditionTypeReady:
36+
if !input.CreatedAt.IsZero() && input.Now.Sub(input.CreatedAt) > readyTimeout {
37+
return taskflow.VirtualMachineStatusOffline
38+
}
39+
}
40+
}
41+
42+
return taskflow.VirtualMachineStatusPending
43+
}
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
package vmstatus
2+
3+
import (
4+
"reflect"
5+
"testing"
6+
"time"
7+
8+
etypes "github.com/chaitin/MonkeyCode/backend/ent/types"
9+
"github.com/chaitin/MonkeyCode/backend/pkg/taskflow"
10+
)
11+
12+
func TestInputDoesNotExposeReportedStatus(t *testing.T) {
13+
if _, ok := reflect.TypeOf(Input{}).FieldByName("ReportedStatus"); ok {
14+
t.Fatal("Input should not expose ReportedStatus")
15+
}
16+
}
17+
18+
func TestResolve(t *testing.T) {
19+
now := time.Date(2026, 4, 15, 12, 0, 0, 0, time.UTC)
20+
21+
tests := []struct {
22+
name string
23+
input Input
24+
want taskflow.VirtualMachineStatus
25+
}{
26+
{
27+
name: "is recycled overrides everything",
28+
input: Input{
29+
Online: true,
30+
IsRecycled: true,
31+
CreatedAt: now.Add(-10 * time.Minute),
32+
Now: now,
33+
},
34+
want: taskflow.VirtualMachineStatusOffline,
35+
},
36+
{
37+
name: "online returns online before conditions",
38+
input: Input{
39+
Online: true,
40+
Conditions: []*etypes.Condition{
41+
{Type: etypes.ConditionTypeFailed},
42+
},
43+
CreatedAt: now.Add(-10 * time.Minute),
44+
Now: now,
45+
},
46+
want: taskflow.VirtualMachineStatusOnline,
47+
},
48+
{
49+
name: "online returns online",
50+
input: Input{
51+
Online: true,
52+
CreatedAt: now.Add(-10 * time.Minute),
53+
Now: now,
54+
},
55+
want: taskflow.VirtualMachineStatusOnline,
56+
},
57+
{
58+
name: "failed condition returns offline",
59+
input: Input{
60+
Conditions: []*etypes.Condition{
61+
{Type: etypes.ConditionTypeFailed},
62+
},
63+
CreatedAt: now.Add(-10 * time.Minute),
64+
Now: now,
65+
},
66+
want: taskflow.VirtualMachineStatusOffline,
67+
},
68+
{
69+
name: "hibernated condition returns hibernated",
70+
input: Input{
71+
Conditions: []*etypes.Condition{
72+
{Type: etypes.ConditionTypeHibernated},
73+
},
74+
CreatedAt: now.Add(-10 * time.Minute),
75+
Now: now,
76+
},
77+
want: taskflow.VirtualMachineStatusHibernated,
78+
},
79+
{
80+
name: "ready older than three minutes returns offline",
81+
input: Input{
82+
Conditions: []*etypes.Condition{
83+
{Type: etypes.ConditionTypeReady},
84+
},
85+
CreatedAt: now.Add(-3*time.Minute - time.Second),
86+
Now: now,
87+
},
88+
want: taskflow.VirtualMachineStatusOffline,
89+
},
90+
{
91+
name: "ready within three minutes stays pending",
92+
input: Input{
93+
Conditions: []*etypes.Condition{
94+
{Type: etypes.ConditionTypeReady},
95+
},
96+
CreatedAt: now.Add(-2 * time.Minute),
97+
Now: now,
98+
},
99+
want: taskflow.VirtualMachineStatusPending,
100+
},
101+
{
102+
name: "defaults to pending",
103+
input: Input{
104+
CreatedAt: now,
105+
Now: now,
106+
},
107+
want: taskflow.VirtualMachineStatusPending,
108+
},
109+
}
110+
111+
for _, tt := range tests {
112+
t.Run(tt.name, func(t *testing.T) {
113+
got := Resolve(tt.input)
114+
if got != tt.want {
115+
t.Fatalf("Resolve() = %q, want %q", got, tt.want)
116+
}
117+
})
118+
}
119+
}

0 commit comments

Comments
 (0)