mirror of
https://github.com/yunionio/cloudpods.git
synced 2026-08-29 03:51:54 +08:00
fc6079ed86
- Wait for disk/desktop status after resize via backup worker - Centralize volume SizeMB update with resize action log - Expose vCPU/memory fields in LLM customize columns
275 lines
8.3 KiB
Go
275 lines
8.3 KiB
Go
package models
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
|
|
"yunion.io/x/jsonutils"
|
|
"yunion.io/x/log"
|
|
"yunion.io/x/pkg/errors"
|
|
"yunion.io/x/pkg/util/httputils"
|
|
"yunion.io/x/pkg/utils"
|
|
|
|
commonapi "yunion.io/x/onecloud/pkg/apis"
|
|
computeapi "yunion.io/x/onecloud/pkg/apis/compute"
|
|
imageapi "yunion.io/x/onecloud/pkg/apis/image"
|
|
api "yunion.io/x/onecloud/pkg/apis/llm"
|
|
"yunion.io/x/onecloud/pkg/cloudcommon/db"
|
|
"yunion.io/x/onecloud/pkg/cloudcommon/db/taskman"
|
|
"yunion.io/x/onecloud/pkg/httperrors"
|
|
"yunion.io/x/onecloud/pkg/llm/options"
|
|
"yunion.io/x/onecloud/pkg/mcclient"
|
|
"yunion.io/x/onecloud/pkg/mcclient/auth"
|
|
"yunion.io/x/onecloud/pkg/mcclient/modules/compute"
|
|
"yunion.io/x/onecloud/pkg/mcclient/modules/image"
|
|
"yunion.io/x/onecloud/pkg/util/logclient"
|
|
)
|
|
|
|
func init() {
|
|
GetVolumeManager()
|
|
}
|
|
|
|
var volumeManager *SVolumeManager
|
|
|
|
func GetVolumeManager() *SVolumeManager {
|
|
if volumeManager != nil {
|
|
return volumeManager
|
|
}
|
|
volumeManager = &SVolumeManager{
|
|
SVirtualResourceBaseManager: db.NewVirtualResourceBaseManager(
|
|
SVolume{},
|
|
"volumes_tbl",
|
|
"llm_volume",
|
|
"llm_volumes",
|
|
),
|
|
}
|
|
volumeManager.SetVirtualObject(volumeManager)
|
|
return volumeManager
|
|
}
|
|
|
|
type SVolumeManager struct {
|
|
db.SVirtualResourceBaseManager
|
|
SMountedModelsResourceManager
|
|
}
|
|
|
|
type SVolume struct {
|
|
db.SVirtualResourceBase
|
|
SMountedModelsResource
|
|
|
|
LLMId string `width:"128" charset:"ascii" nullable:"true" list:"user" create:"admin_optional" update:"user"`
|
|
// 存储类型
|
|
StorageType string `width:"16" charset:"ascii" nullable:"true" list:"user" create:"admin_optional" update:"user"`
|
|
// 模板ID
|
|
TemplateId string `width:"128" charset:"ascii" nullable:"true" list:"user" create:"admin_optional" update:"user"`
|
|
// size in MB
|
|
SizeMB int `nullable:"false" default:"0" create:"optional" list:"user" update:"user"`
|
|
CmpId string `width:"128" charset:"ascii" nullable:"true" list:"user"`
|
|
Containers api.ContainerVolumeRelations `charset:"utf8" nullable:"true" list:"user" create:"optional"`
|
|
}
|
|
|
|
func (volume *SVolume) StartDeleteTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string) error {
|
|
task, err := taskman.TaskManager.NewTask(ctx, "VolumeDeleteTask", volume, userCred, nil, parentTaskId, "", nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
volume.SetStatus(ctx, userCred, commonapi.STATUS_DELETING, "")
|
|
task.ScheduleRun(nil)
|
|
return nil
|
|
}
|
|
|
|
func (volume *SVolume) RealDelete(ctx context.Context, userCred mcclient.TokenCredential) error {
|
|
return volume.SVirtualResourceBase.Delete(ctx, userCred)
|
|
}
|
|
|
|
func (volume *SVolume) StartResizeTask(ctx context.Context, userCred mcclient.TokenCredential, input api.VolumeResizeTaskInput, parentTaskId string) (*taskman.STask, error) {
|
|
volume.SetStatus(ctx, userCred, computeapi.DISK_START_RESIZE, "StartResizeTask")
|
|
params := jsonutils.Marshal(input).(*jsonutils.JSONDict)
|
|
task, err := taskman.TaskManager.NewTask(ctx, "VolumeResizeTask", volume, userCred, params, parentTaskId, "", nil)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "NewTask")
|
|
}
|
|
if err := task.ScheduleRun(nil); err != nil {
|
|
return nil, errors.Wrap(err, "ScheduleRun")
|
|
}
|
|
return task, nil
|
|
}
|
|
|
|
func fetchImage(ctx context.Context, userCred mcclient.TokenCredential, imageId string) (*imageapi.ImageDetails, error) {
|
|
s := auth.GetSession(ctx, userCred, options.Options.Region)
|
|
imgObj, err := image.Images.Get(s, imageId, nil)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "Image.Get %s", imageId)
|
|
}
|
|
img := imageapi.ImageDetails{}
|
|
err = imgObj.Unmarshal(&img)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "Unmarshal")
|
|
}
|
|
return &img, nil
|
|
}
|
|
|
|
func (volume *SVolume) UpdateMountedModelFullNames(mountModels []string) error {
|
|
_, err := db.Update(volume, func() error {
|
|
volume.MountedModels = mountModels
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return errors.Wrap(err, "update volume mounted_apps")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (volume *SVolume) GetDisk(ctx context.Context) (*computeapi.DiskDetails, error) {
|
|
if len(volume.CmpId) == 0 {
|
|
return nil, errors.ErrInvalidStatus
|
|
}
|
|
s := auth.GetAdminSession(ctx, "")
|
|
disk := computeapi.DiskDetails{}
|
|
resp, err := compute.Disks.GetById(s, volume.CmpId, jsonutils.Marshal(map[string]interface{}{
|
|
"scope": "max",
|
|
}))
|
|
if err != nil {
|
|
if httputils.ErrorCode(err) == 404 {
|
|
return nil, errors.Wrapf(errors.ErrNotFound, "GetById %s", volume.CmpId)
|
|
}
|
|
return nil, errors.Wrap(err, "fetch disk")
|
|
}
|
|
resp.Unmarshal(&disk)
|
|
return &disk, nil
|
|
}
|
|
|
|
func (volume *SVolume) WaitDiskStatus(ctx context.Context, userCred mcclient.TokenCredential, targetStatus []string, timeoutSecs int) (*computeapi.DiskDetails, error) {
|
|
expire := time.Now().Add(time.Second * time.Duration(timeoutSecs))
|
|
for time.Now().Before(expire) {
|
|
disk, err := volume.GetDisk(ctx)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "GetDisk")
|
|
}
|
|
if utils.IsInArray(disk.Status, targetStatus) {
|
|
return disk, nil
|
|
}
|
|
if strings.Contains(disk.Status, "fail") {
|
|
return nil, errors.Wrap(errors.ErrInvalidStatus, disk.Status)
|
|
}
|
|
time.Sleep(2 * time.Second)
|
|
}
|
|
return nil, errors.Wrapf(httperrors.ErrTimeout, "wait disk status %s timeout", targetStatus)
|
|
}
|
|
|
|
func (volume *SVolume) GetLLM() *SLLM {
|
|
if len(volume.LLMId) == 0 {
|
|
return nil
|
|
}
|
|
obj, err := GetLLMManager().FetchById(volume.LLMId)
|
|
if err != nil {
|
|
log.Errorf("Volume %s fetch llm %s error %s", volume.Id, volume.LLMId, err)
|
|
return nil
|
|
}
|
|
return obj.(*SLLM)
|
|
}
|
|
|
|
func (volume *SVolume) PerformReset(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.VolumePerformResetInput) (*api.LLMBatchPerformOutput, error) {
|
|
if volume.Status != computeapi.DISK_READY {
|
|
return nil, errors.Wrapf(httperrors.ErrInvalidStatus, "invalid status %s", volume.Status)
|
|
}
|
|
task, err := volume.StartResetTask(ctx, userCred, input, "")
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "StartResetTask")
|
|
}
|
|
return &api.LLMBatchPerformOutput{
|
|
Data: []api.LLMPerformOutput{
|
|
{
|
|
Id: volume.Id,
|
|
Name: volume.Name,
|
|
RequestStatus: http.StatusOK,
|
|
TaskId: task.Id,
|
|
},
|
|
},
|
|
Task: task,
|
|
}, nil
|
|
}
|
|
|
|
func (volume *SVolume) StartResetTask(ctx context.Context, userCred mcclient.TokenCredential, input api.VolumePerformResetInput, parentTaskId string) (*taskman.STask, error) {
|
|
volume.SetStatus(ctx, userCred, api.VOLUME_STATUS_START_RESET, "StartResetTask")
|
|
params := jsonutils.Marshal(input).(*jsonutils.JSONDict)
|
|
task, err := taskman.TaskManager.NewTask(ctx, "VolumeResetTask", volume, userCred, params, parentTaskId, "")
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "NewTask")
|
|
}
|
|
err = task.ScheduleRun(nil)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "ScheduleRun")
|
|
}
|
|
return task, nil
|
|
}
|
|
|
|
func (volume *SVolume) DoReset(ctx context.Context, userCred mcclient.TokenCredential, templateId, backupId *string, sizeGb int) error {
|
|
// clear desktop app list
|
|
{
|
|
llm := volume.GetLLM()
|
|
if llm != nil {
|
|
llm.purgeModelList()
|
|
}
|
|
}
|
|
// clear mounts
|
|
{
|
|
_, err := db.Update(volume, func() error {
|
|
volume.MountedModels = nil
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return errors.Wrap(err, "update volume mounted_apps")
|
|
}
|
|
}
|
|
|
|
// reset disk
|
|
s := auth.GetSession(ctx, userCred, options.Options.Region)
|
|
params := computeapi.DiskRebuildInput{}
|
|
params.TemplateId = templateId
|
|
params.BackupId = backupId
|
|
emptyStr := ""
|
|
if params.BackupId == nil {
|
|
params.BackupId = &emptyStr
|
|
}
|
|
if sizeGb > 0 {
|
|
size := fmt.Sprintf("%dG", sizeGb)
|
|
params.Size = &size
|
|
}
|
|
|
|
log.Debugf("need to reset phone data disk ... %s %dG", jsonutils.Marshal(params), sizeGb)
|
|
|
|
_, err := compute.Disks.PerformAction(s, volume.CmpId, "rebuild", jsonutils.Marshal(params))
|
|
if err != nil {
|
|
return errors.Wrap(err, "rebuild disk")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (volume *SVolume) UpdateSize(ctx context.Context, userCred mcclient.TokenCredential, sizeMb int) error {
|
|
if volume.SizeMB == sizeMb {
|
|
return nil
|
|
}
|
|
oldSize := volume.SizeMB
|
|
_, err := db.Update(volume, func() error {
|
|
volume.SizeMB = sizeMb
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
logclient.AddSimpleActionLog(volume, logclient.ACT_RESIZE, err, userCred, false)
|
|
return errors.Wrap(err, "update volume size")
|
|
}
|
|
notes := struct {
|
|
OldSizeMb int `json:"old_size_mb"`
|
|
NewSizeMb int `json:"new_size_mb"`
|
|
}{
|
|
OldSizeMb: oldSize,
|
|
NewSizeMb: sizeMb,
|
|
}
|
|
logclient.AddSimpleActionLog(volume, logclient.ACT_RESIZE, jsonutils.Marshal(notes), userCred, true)
|
|
return nil
|
|
}
|