功能:补充固定场景助手与GPU健康诊断能力
This commit is contained in:
1 parent
9f05aa20ea
commit
b6026f9759
29 files changed
+1333
-22
No files matched your search
@@ -35,6 +35,129 @@ type gpuPrepareState struct {
|
||||
UPID string `json:"upid,omitempty"`
|
||||
}
|
||||
|
||||
type gpuExclusiveMember struct {
|
||||
DesktopID string `json:"desktopId"`
|
||||
Name string `json:"name"`
|
||||
VMID int `json:"vmid"`
|
||||
PowerState string `json:"powerState"`
|
||||
PowerObserved bool `json:"powerObserved"`
|
||||
ActiveSessions int `json:"activeSessions"`
|
||||
ActiveTasks int `json:"activeTasks"`
|
||||
Conflicts []string `json:"conflicts"`
|
||||
Eligible bool `json:"eligible"`
|
||||
}
|
||||
type gpuExclusiveStatus struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
Node string `json:"node"`
|
||||
VMIDs []int `json:"vmids"`
|
||||
ObservedAt time.Time `json:"observedAt"`
|
||||
LockAvailable bool `json:"lockAvailable"`
|
||||
GroupComplete bool `json:"groupComplete"`
|
||||
Members []gpuExclusiveMember `json:"members"`
|
||||
Conflicts []string `json:"conflicts"`
|
||||
AllocationReady bool `json:"allocationReady"`
|
||||
ReleaseReady bool `json:"releaseReady"`
|
||||
ReleasePolicy string `json:"releasePolicy"`
|
||||
ActiveTasks []model.Task `json:"activeTasks"`
|
||||
}
|
||||
|
||||
// exclusiveGPUStatus is a read-only preflight view. It never changes VM power
|
||||
// or task state; it is used by the advanced console before allocation/release.
|
||||
func (s *Server) exclusiveGPUStatus(c *gin.Context) {
|
||||
node := strings.TrimSpace(c.Query("node"))
|
||||
status := gpuExclusiveStatus{ObservedAt: time.Now().UTC(), Node: s.Config.ExclusiveGPUNode, VMIDs: append([]int{}, s.Config.ExclusiveGPUVMIDs...), Members: []gpuExclusiveMember{}, Conflicts: []string{}, ActiveTasks: []model.Task{}, ReleasePolicy: "仅在无活动会话、无未完成任务且锁可用时可释放;不会自动关机或重放操作"}
|
||||
if node != "" && (len(node) > 100 || strings.ContainsAny(node, "/\\?#")) {
|
||||
s.fail(c, 400, "invalid_node", "节点名称无效")
|
||||
return
|
||||
}
|
||||
if status.Node == "" || len(status.VMIDs) == 0 {
|
||||
c.JSON(200, status)
|
||||
return
|
||||
}
|
||||
if node != "" && node != status.Node {
|
||||
status.Conflicts = []string{"当前节点未配置独占显卡组"}
|
||||
c.JSON(200, status)
|
||||
return
|
||||
}
|
||||
status.Enabled = true
|
||||
tx, err := s.DB.BeginTxx(c.Request.Context(), &sql.TxOptions{ReadOnly: true})
|
||||
if err != nil {
|
||||
s.dbError(c, err)
|
||||
return
|
||||
}
|
||||
defer tx.Rollback()
|
||||
locked, err := tryGPULock(c.Request.Context(), tx)
|
||||
if err != nil {
|
||||
s.dbError(c, err)
|
||||
return
|
||||
}
|
||||
status.LockAvailable = locked
|
||||
if !locked {
|
||||
status.Conflicts = append(status.Conflicts, "独占显卡锁当前被占用")
|
||||
}
|
||||
var members []model.Desktop
|
||||
members, err = s.gpuMembers(c.Request.Context(), tx)
|
||||
if err != nil {
|
||||
status.Conflicts = append(status.Conflicts, "独占显卡组成员不完整")
|
||||
} else {
|
||||
status.GroupComplete = true
|
||||
}
|
||||
var tasks []model.Task
|
||||
if err = tx.SelectContext(c.Request.Context(), &tasks, `SELECT * FROM tasks WHERE kind='gpu-prepare' AND status IN ('queued','running','timeout') ORDER BY created_at DESC`); err == nil {
|
||||
status.ActiveTasks = tasks
|
||||
if len(tasks) > 0 {
|
||||
status.Conflicts = append(status.Conflicts, "存在未完成或结果未核对的显卡切换任务")
|
||||
}
|
||||
}
|
||||
pve := s.currentPVE()
|
||||
for _, d := range members {
|
||||
m := gpuExclusiveMember{DesktopID: d.ID, Name: d.Name, VMID: d.VMID, PowerState: d.PowerState, Conflicts: []string{}}
|
||||
var n int
|
||||
if err = tx.GetContext(c.Request.Context(), &n, `SELECT count(*) FROM terminals WHERE session_desktop_id=$1 AND last_seen>now()-interval '90 seconds'`, d.ID); err == nil {
|
||||
m.ActiveSessions = n
|
||||
}
|
||||
if err = tx.GetContext(c.Request.Context(), &n, `SELECT count(*) FROM tasks WHERE target_id=$1 AND status IN ('queued','running','timeout') AND kind!='terminal'`, d.ID); err == nil {
|
||||
m.ActiveTasks = n
|
||||
}
|
||||
if m.ActiveSessions > 0 {
|
||||
m.Conflicts = append(m.Conflicts, "存在活跃终端会话")
|
||||
}
|
||||
if m.ActiveTasks > 0 {
|
||||
m.Conflicts = append(m.Conflicts, "存在未完成管理任务")
|
||||
}
|
||||
if pve != nil {
|
||||
power, powerErr := pve.Power(c.Request.Context(), d.Node, d.VMID)
|
||||
if powerErr == nil {
|
||||
m.PowerState, m.PowerObserved = power, true
|
||||
} else {
|
||||
m.PowerState = "unknown"
|
||||
m.Conflicts = append(m.Conflicts, "无法读取 PVE 实际电源状态")
|
||||
}
|
||||
} else {
|
||||
m.PowerState = "unknown"
|
||||
m.Conflicts = append(m.Conflicts, "尚未配置 PVE")
|
||||
}
|
||||
if m.PowerObserved && m.PowerState != "stopped" {
|
||||
m.Conflicts = append(m.Conflicts, "虚拟机当前仍在运行")
|
||||
}
|
||||
m.Eligible = len(m.Conflicts) == 0 && m.PowerState == "stopped"
|
||||
status.Members = append(status.Members, m)
|
||||
}
|
||||
if !status.GroupComplete {
|
||||
status.Conflicts = append(status.Conflicts, "数据库中的独占显卡组成员数量与配置不一致")
|
||||
}
|
||||
allEligible := len(status.Members) == len(status.VMIDs)
|
||||
for _, member := range status.Members {
|
||||
if !member.Eligible {
|
||||
allEligible = false
|
||||
break
|
||||
}
|
||||
}
|
||||
status.AllocationReady = status.Enabled && status.GroupComplete && status.LockAvailable && len(status.ActiveTasks) == 0 && len(status.Conflicts) == 0 && allEligible
|
||||
status.ReleaseReady = status.Enabled && status.GroupComplete && status.LockAvailable && len(status.ActiveTasks) == 0 && allEligible
|
||||
c.JSON(200, status)
|
||||
}
|
||||
|
||||
func (s *Server) exclusiveGPU(d model.Desktop) bool {
|
||||
if d.Provider != "proxmox" || d.Node != s.Config.ExclusiveGPUNode {
|
||||
return false
|
||||
|
||||
Reference in new issue
Block a user