Skip to content

Commit aeb83ef

Browse files
committed
feat(llm): restart action
1 parent d1dec1d commit aeb83ef

20 files changed

Lines changed: 1257 additions & 46 deletions

File tree

cmd/climc/shell/llm/llm.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ func init() {
1616
cmd.Perform("syncstatus", new(options.LLMIdOptions))
1717
cmd.BatchPerform("stop", new(options.LLMStopOptions))
1818
cmd.BatchPerform("start", new(options.LLMStartOptions))
19+
cmd.BatchPerform("restart", new(options.LLMRestartOptions))
1920
cmd.Get("probed-models", new(options.LLMIdOptions))
2021
cmd.Get("url", new(options.LLMIdOptions))
2122
cmd.Custom(shell.CustomActionGet, "available-network", new(options.LLMAvailableNetworkOptions))

pkg/apis/llm/llm.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,3 +175,30 @@ func (info LLMMountDirInfo) ToOverlay() apis.ContainerVolumeMountDiskPostOverlay
175175

176176
type LLMSyncStatusInput struct {
177177
}
178+
179+
type LLMRestartInput struct {
180+
}
181+
182+
type LLMRestartTaskInput struct {
183+
LLMId string
184+
ResetDataDisk bool
185+
LLMStatus string
186+
SkuId string
187+
ImageId string
188+
BackupName string
189+
Property []string
190+
191+
RebindVolumeId string
192+
OnlyStop bool
193+
}
194+
195+
type LLMChangeNetworkInput struct {
196+
BandwidthMb int `json:"bandwidth_mb"`
197+
WhitePrefxies []string `json:"white_prefxies"`
198+
}
199+
200+
type LLMVolumeInput struct {
201+
LLMId string `json:"llm_id"`
202+
VolumeId string `json:"volume_id"`
203+
AutoStart bool `json:"auto_start"`
204+
}

pkg/apis/llm/llm_const.go

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,15 +33,39 @@ const (
3333
/* 运行 */
3434
LLM_STATUS_RUNNING = "running"
3535

36+
/* 开始解绑磁盘 */
37+
LLM_STATUS_START_UNBIND = "start_unbind"
38+
/* 正在解绑磁盘 */
39+
LLM_STATUS_UNBINDING = "unbinding"
40+
/* 解绑磁盘失败 */
41+
LLM_STATUS_UNBIND_FAIL = "unbind_fail"
42+
43+
/* 开始挂载磁盘 */
44+
LLM_STATUS_START_BIND = "start_bind"
45+
/* 正在挂载磁盘 */
46+
LLM_STATUS_BINDING = "binding"
47+
/* 挂载磁盘失败 */
48+
LLM_STATUS_BIND_FAIL = "bind_fail"
49+
50+
/* 开始重启 */
51+
LLM_STATUS_START_RESTART = "start_restart"
52+
/* 正在重启 */
53+
LLM_STATUS_RESTARTING = "restarting"
54+
/* 重启失败 */
55+
LLM_STATUS_RESTART_FAILED = "restart_fail"
56+
57+
/* 开始删除 */
58+
LLM_STATUS_START_DELETE = "start_delete"
59+
/* 正在删除 */
60+
LLM_STATUS_DELETING = "deleting"
61+
/* 删除失败 */
62+
LLM_STATUS_DELETE_FAILED = "delete_fail"
63+
3664
/* 删除 */
3765
LLM_STATUS_DELETED = "deleted"
3866

3967
LLM_LLM_STATUS_NO_SERVER = "no_server"
4068
LLM_LLM_STATUS_NO_CONTAINER = "no_container"
41-
42-
LLM_STATUS_START_DELETE = "start_delete"
43-
LLM_STATUS_DELETING = "deleting"
44-
LLM_STATUS_DELETE_FAILED = "delete_fail"
4569
)
4670

4771
type TQuickModelMethod string

pkg/apis/notify/const.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ const (
139139
TOPIC_RESOURCE_VM_INTEGRITY_CHECK = "vm_integrity"
140140
TOPIC_RESOURCE_PROJECT = "project"
141141
TOPIC_RESOURCE_CLOUDPHONE = "cloudphone"
142+
TOPIC_RESOURCE_LLM = "llm"
142143

143144
SUBSCRIBER_TYPE_ROLE = "role"
144145
SUBSCRIBER_TYPE_ROBOT = "robot"

pkg/llm/drivers/llm_container/base_driver.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55

66
"yunion.io/x/pkg/errors"
77

8+
computeapi "yunion.io/x/onecloud/pkg/apis/compute"
89
api "yunion.io/x/onecloud/pkg/apis/llm"
910
"yunion.io/x/onecloud/pkg/cloudcommon/validators"
1011
"yunion.io/x/onecloud/pkg/httperrors"
@@ -94,3 +95,17 @@ func (b *baseDriver) ValidateUpdateData(ctx context.Context, userCred mcclient.T
9495
input.MountedModels = mountedModels
9596
return input, nil
9697
}
98+
99+
func MatchContainerToUpdateByName(ctr *computeapi.SContainer, podCtrs []*computeapi.PodContainerCreateInput) (*computeapi.PodContainerCreateInput, error) {
100+
ctrName := ctr.Name
101+
for _, podCtr := range podCtrs {
102+
if podCtr.Name == ctrName {
103+
return podCtr, nil
104+
}
105+
}
106+
return nil, errors.Wrapf(errors.ErrNotFound, "container %s not found", ctrName)
107+
}
108+
109+
func (b *baseDriver) MatchContainerToUpdate(ctr *computeapi.SContainer, podCtrs []*computeapi.PodContainerCreateInput) (*computeapi.PodContainerCreateInput, error) {
110+
return MatchContainerToUpdateByName(ctr, podCtrs)
111+
}

pkg/llm/drivers/llm_container/dify.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,10 @@ func (d *dify) GetContainerSpecs(ctx context.Context, llm *models.SLLM, image *m
172172
return models.GetDifyContainersByNameAndSku(llm.GetName(), sku, nil, spec.(*api.LLMSpecDify))
173173
}
174174

175+
func (d *dify) MatchContainerToUpdate(ctr *computeapi.SContainer, podCtrs []*computeapi.PodContainerCreateInput) (*computeapi.PodContainerCreateInput, error) {
176+
return MatchContainerToUpdateByName(ctr, podCtrs)
177+
}
178+
175179
// StartLLM is a no-op for Dify; all services are started by their container entrypoints.
176180
func (d *dify) StartLLM(ctx context.Context, userCred mcclient.TokenCredential, llm *models.SLLM) error {
177181
return nil

pkg/llm/models/llm.go

Lines changed: 221 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,18 @@ import (
1515
commonapi "yunion.io/x/onecloud/pkg/apis"
1616
computeapi "yunion.io/x/onecloud/pkg/apis/compute"
1717
api "yunion.io/x/onecloud/pkg/apis/llm"
18+
"yunion.io/x/onecloud/pkg/apis/notify"
19+
notifyapi "yunion.io/x/onecloud/pkg/apis/notify"
1820
"yunion.io/x/onecloud/pkg/cloudcommon/db"
1921
"yunion.io/x/onecloud/pkg/cloudcommon/db/taskman"
22+
"yunion.io/x/onecloud/pkg/cloudcommon/notifyclient"
2023
"yunion.io/x/onecloud/pkg/httperrors"
2124
"yunion.io/x/onecloud/pkg/llm/options"
2225
llmutils "yunion.io/x/onecloud/pkg/llm/utils"
2326
"yunion.io/x/onecloud/pkg/mcclient"
2427
"yunion.io/x/onecloud/pkg/mcclient/auth"
2528
"yunion.io/x/onecloud/pkg/mcclient/modules/compute"
29+
baseoptions "yunion.io/x/onecloud/pkg/mcclient/options"
2630
computeoptions "yunion.io/x/onecloud/pkg/mcclient/options/compute"
2731
"yunion.io/x/onecloud/pkg/util/stringutils2"
2832
)
@@ -478,10 +482,31 @@ func (llm *SLLM) PerformStart(ctx context.Context, userCred mcclient.TokenCreden
478482
return nil, errors.Wrapf(errors.ErrInvalidStatus, "llm id: %s status: %s", llm.Id, llm.Status)
479483
}
480484

481-
if err := llm.StartStartTask(ctx, userCred, ""); err != nil {
482-
return nil, errors.Wrap(err, "StartStartTask")
485+
_, err := llm.GetVolume()
486+
if err != nil {
487+
if errors.Cause(err) == sql.ErrNoRows {
488+
return nil, errors.Wrapf(errors.ErrNotSupported, "llm id: %s missing volume", llm.Id)
489+
}
490+
return nil, errors.Wrap(err, "GetVolume")
491+
}
492+
taskinput := &api.LLMRestartTaskInput{
493+
LLMId: llm.Id,
494+
LLMStatus: api.LLM_STATUS_READY,
495+
}
496+
_, err = llm.StartRestartTask(ctx, userCred, taskinput, "")
497+
if err != nil {
498+
return nil, errors.Wrap(err, "StartRestartTask")
499+
}
500+
return nil, nil
501+
}
502+
503+
func (d *SLLM) StartStartTaskInternal(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string) error {
504+
d.SetStatus(ctx, userCred, computeapi.VM_STARTING, "")
505+
task, err := taskman.TaskManager.NewTask(ctx, "LLMStartTask", d, userCred, nil, parentTaskId, "", nil)
506+
if err != nil {
507+
return errors.Wrapf(err, "NewTask")
483508
}
484-
return jsonutils.Marshal(nil), nil
509+
return task.ScheduleRun(nil)
485510
}
486511

487512
func (llm *SLLM) StartStartTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string) error {
@@ -504,6 +529,96 @@ func (llm *SLLM) PerformStop(ctx context.Context, userCred mcclient.TokenCredent
504529
return nil, nil
505530
}
506531

532+
func (llm *SLLM) ValidateRestartInput(ctx context.Context, userCred mcclient.TokenCredential, input *api.LLMRestartInput) (*api.LLMRestartTaskInput, error) {
533+
if len(llm.CmpId) == 0 {
534+
return nil, errors.Wrap(errors.ErrInvalidStatus, "empty cmp_id")
535+
}
536+
537+
srv, err := llm.GetServer(ctx)
538+
if err != nil {
539+
return nil, errors.Wrap(err, "GetServer")
540+
}
541+
542+
if (llm.Status != api.LLM_STATUS_READY && llm.Status != api.LLM_STATUS_RUNNING) || (srv.Status != computeapi.VM_READY && !utils.IsInArray(srv.Status, computeapi.VM_RUNNING_STATUS)) {
543+
return nil, errors.Wrapf(errors.ErrInvalidStatus, "invalid llm status %s", llm.Status)
544+
}
545+
546+
return &api.LLMRestartTaskInput{}, nil
547+
}
548+
549+
func (llm *SLLM) PerformRestart(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input *api.LLMRestartInput) (jsonutils.JSONObject, error) {
550+
taskInput, err := llm.ValidateRestartInput(ctx, userCred, input)
551+
if err != nil {
552+
return nil, errors.Wrap(err, "ValidateRestartInput")
553+
}
554+
_, err = llm.StartRestartTask(ctx, userCred, taskInput, "")
555+
if err != nil {
556+
return nil, errors.Wrap(err, "StartRestartTask")
557+
}
558+
return nil, nil
559+
}
560+
561+
func (llm *SLLM) StartRestartTask(ctx context.Context, userCred mcclient.TokenCredential, params *api.LLMRestartTaskInput, parentTaskId string) (*taskman.STask, error) {
562+
key := "perform_restart"
563+
if params.ResetDataDisk {
564+
key = "perform_reset"
565+
}
566+
llm.SetStatus(ctx, userCred, api.LLM_STATUS_START_RESTART, key)
567+
taskName := "LLMRestartTask"
568+
if params.ResetDataDisk {
569+
taskName = "LLMResetTask"
570+
}
571+
params.LLMId = llm.Id
572+
task, err := taskman.TaskManager.NewTask(ctx, taskName, llm, userCred, jsonutils.Marshal(params).(*jsonutils.JSONDict), parentTaskId, "", nil)
573+
if err != nil {
574+
return nil, errors.Wrap(err, "NewTask")
575+
}
576+
if err := task.ScheduleRun(nil); err != nil {
577+
return nil, errors.Wrap(err, "ScheduleRun")
578+
}
579+
return task, nil
580+
}
581+
582+
func (llm *SLLM) PerformReset(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input *api.LLMRestartInput) (jsonutils.JSONObject, error) {
583+
taskInput, err := llm.ValidateRestartInput(ctx, userCred, input)
584+
if err != nil {
585+
return nil, errors.Wrap(err, "ValidateRestartInput")
586+
}
587+
_, err = llm.StartResetTask(ctx, userCred, taskInput, "")
588+
if err != nil {
589+
return nil, errors.Wrap(err, "StartRestartTask")
590+
}
591+
return nil, nil
592+
}
593+
594+
func (llm *SLLM) StartResetTask(ctx context.Context, userCred mcclient.TokenCredential, params *api.LLMRestartTaskInput, parentTaskId string) (*taskman.STask, error) {
595+
llm.SetStatus(ctx, userCred, api.LLM_STATUS_START_RESTART, "perform_reset")
596+
task, err := taskman.TaskManager.NewTask(ctx, "LLMResetTask", llm, userCred, jsonutils.Marshal(params).(*jsonutils.JSONDict), parentTaskId, "", nil)
597+
if err != nil {
598+
return nil, errors.Wrapf(err, "NewTask")
599+
}
600+
if err := task.ScheduleRun(nil); err != nil {
601+
return nil, errors.Wrap(err, "ScheduleRun")
602+
}
603+
return task, nil
604+
}
605+
606+
func (llm *SLLM) NotifyRequest(ctx context.Context, userCred mcclient.TokenCredential, action notify.SAction, model jsonutils.JSONObject, success bool) {
607+
obj := func(ctx context.Context, details *jsonutils.JSONDict) {}
608+
if model != nil {
609+
obj = func(ctx context.Context, details *jsonutils.JSONDict) {
610+
details.Set("customize_details", model)
611+
}
612+
}
613+
notifyclient.EventNotify(ctx, userCred, notifyclient.SEventNotifyParam{
614+
Obj: llm,
615+
Action: action,
616+
ObjDetailsDecorator: obj,
617+
IsFail: !success,
618+
ResourceType: notifyapi.TOPIC_RESOURCE_LLM,
619+
})
620+
}
621+
507622
func (llm *SLLM) StartLLMStopTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string) error {
508623
task, err := taskman.TaskManager.NewTask(ctx, "LLMStopTask", llm, userCred, nil, parentTaskId, "", nil)
509624
if err != nil {
@@ -628,3 +743,106 @@ func (man *SLLMManager) GetAvailableNetwork(ctx context.Context, userCred mcclie
628743

629744
return ret, nil
630745
}
746+
747+
func (llm *SLLM) StartBindVolumeTask(ctx context.Context, userCred mcclient.TokenCredential, volumeId string, autoStart bool, parenentTaskId string) (*taskman.STask, error) {
748+
llm.SetStatus(ctx, userCred, api.LLM_STATUS_START_BIND, "perform bind volume")
749+
params := api.LLMVolumeInput{
750+
LLMId: llm.Id,
751+
VolumeId: volumeId,
752+
AutoStart: autoStart,
753+
}
754+
task, err := taskman.TaskManager.NewTask(ctx, "LLMAttachTask", llm, userCred, jsonutils.Marshal(params).(*jsonutils.JSONDict), parenentTaskId, "", nil)
755+
if err != nil {
756+
return nil, errors.Wrap(err, "NewTask")
757+
}
758+
err = task.ScheduleRun(nil)
759+
if err != nil {
760+
return nil, errors.Wrap(err, "ScheduleRun")
761+
}
762+
return task, nil
763+
}
764+
765+
func (llm *SLLM) ChangeServerNetworkConfig(ctx context.Context, bandwidth int, whitePrefixes []string, noSync bool) error {
766+
s := auth.GetAdminSession(ctx, options.Options.Region)
767+
params := baseoptions.BaseListOptions{}
768+
params.Scope = "max"
769+
limit := 0
770+
params.Limit = &limit
771+
serverNicObjs, err := compute.Servernetworks.ListDescendent(s, llm.CmpId, jsonutils.Marshal(params))
772+
if err != nil {
773+
return errors.Wrap(err, "compute.Servernetworks.ListDescendent")
774+
} else if len(serverNicObjs.Data) == 0 {
775+
return errors.Wrap(httperrors.ErrEmptyRequest, "compute.Servernetworks.ListDescendent")
776+
}
777+
gns := computeapi.GuestnetworkDetails{}
778+
err = serverNicObjs.Data[0].Unmarshal(&gns)
779+
if err != nil {
780+
return errors.Wrap(err, "Unmarshal GuestnetworkDetails")
781+
}
782+
if gns.BwLimit != bandwidth {
783+
// need to change bandwidth
784+
params := computeapi.ServerChangeBandwidthInput{}
785+
params.Mac = gns.MacAddr
786+
params.Index = 0
787+
params.Bandwidth = bandwidth
788+
params.NoSync = &noSync
789+
_, err := compute.Servers.PerformAction(s, llm.CmpId, "change-bandwidth", jsonutils.Marshal(params))
790+
if err != nil {
791+
return errors.Wrap(err, "compute.Servers.PerformAction change-bandwidth")
792+
}
793+
}
794+
/*if len(adbWhitePrefixes) > 0 {
795+
for _, pm := range gns.PortMappings {
796+
if pm.Port == apis.PHONE_ADB_PORT {
797+
// verify adb port remote ips
798+
remoteIps := stringutils2.NewSortedStrings(pm.RemoteIps)
799+
remoteIps2 := stringutils2.NewSortedStrings(adbWhitePrefixes)
800+
if !stringutils2.Equals(remoteIps, remoteIps2) {
801+
// need to update remote Ips
802+
params := computeapi.GuestnetworkUpdateInput{}
803+
for i := range gns.PortMappings {
804+
npm := gns.PortMappings[i]
805+
if gns.PortMappings[i].Port == apis.PHONE_ADB_PORT {
806+
npm.RemoteIps = adbWhitePrefixes
807+
}
808+
params.PortMappings = append(params.PortMappings, npm)
809+
}
810+
_, err := compute.Servernetworks.Update(s, gns.GuestId, gns.NetworkId, nil, jsonutils.Marshal(params))
811+
if err != nil {
812+
return errors.Wrap(err, "Servernetworks.Update")
813+
}
814+
}
815+
break
816+
}
817+
}
818+
}*/
819+
return nil
820+
}
821+
822+
func (llm *SLLM) PerformNetConfig(
823+
ctx context.Context,
824+
userCred mcclient.TokenCredential,
825+
query jsonutils.JSONObject,
826+
input api.LLMChangeNetworkInput,
827+
) (jsonutils.JSONObject, error) {
828+
err := llm.ChangeServerNetworkConfig(ctx, input.BandwidthMb, input.WhitePrefxies, false)
829+
if err != nil {
830+
return nil, errors.Wrap(err, "changeServerNetworkConfig")
831+
}
832+
833+
if llm.BandwidthMb != input.BandwidthMb {
834+
_, err := db.Update(llm, func() error {
835+
llm.BandwidthMb = input.BandwidthMb
836+
return nil
837+
})
838+
if err != nil {
839+
return nil, errors.Wrap(err, "update")
840+
}
841+
}
842+
843+
return nil, nil
844+
}
845+
846+
func (llm *SLLM) purgeModelList() error {
847+
return GetLLMInstantModelManager().purgeModelList(llm.Id)
848+
}

0 commit comments

Comments
 (0)