mirror of
https://github.com/yunionio/cloudpods.git
synced 2026-05-06 21:52:54 +08:00
feat(llm): add hostpaths for llm-sku (#24693)
This commit is contained in:
77
pkg/apis/llm/host_path.go
Normal file
77
pkg/apis/llm/host_path.go
Normal file
@@ -0,0 +1,77 @@
|
||||
package llm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"yunion.io/x/jsonutils"
|
||||
"yunion.io/x/pkg/gotypes"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/apis"
|
||||
)
|
||||
|
||||
func init() {
|
||||
gotypes.RegisterSerializable(reflect.TypeOf(&HostPath{}), func() gotypes.ISerializable {
|
||||
return &HostPath{}
|
||||
})
|
||||
gotypes.RegisterSerializable(reflect.TypeOf(&HostPaths{}), func() gotypes.ISerializable {
|
||||
return &HostPaths{}
|
||||
})
|
||||
gotypes.RegisterSerializable(reflect.TypeOf(&ContainerHostPathRelations{}), func() gotypes.ISerializable {
|
||||
return &ContainerHostPathRelations{}
|
||||
})
|
||||
}
|
||||
|
||||
type ContainerHostPathRelation struct {
|
||||
MountPath string `json:"mount_path"`
|
||||
ReadOnly bool `json:"read_only"`
|
||||
Propagation apis.ContainerMountPropagation `json:"propagation,omitempty"`
|
||||
FsUser *int64 `json:"fs_user"`
|
||||
FsGroup *int64 `json:"fs_group"`
|
||||
}
|
||||
|
||||
// key is string format of integer
|
||||
type ContainerHostPathRelations map[string]*ContainerHostPathRelation
|
||||
|
||||
func (s ContainerHostPathRelations) String() string {
|
||||
return jsonutils.Marshal(s).String()
|
||||
}
|
||||
|
||||
func (s ContainerHostPathRelations) IsZero() bool {
|
||||
return len(s) == 0
|
||||
}
|
||||
|
||||
type HostPath struct {
|
||||
Type apis.ContainerVolumeMountHostPathType `json:"type"`
|
||||
Path string `json:"path"`
|
||||
AutoCreate bool `json:"auto_create"`
|
||||
AutoCreateConfig *apis.ContainerVolumeMountHostPathAutoCreateConfig `json:"auto_create_config,omitempty"`
|
||||
// Container index to mount path relation
|
||||
Containers ContainerHostPathRelations `json:"containers"`
|
||||
}
|
||||
|
||||
func (s HostPath) String() string {
|
||||
return jsonutils.Marshal(s).String()
|
||||
}
|
||||
|
||||
func (s HostPath) IsZero() bool {
|
||||
return len(s.Path) == 0
|
||||
}
|
||||
|
||||
func (s HostPath) GetHostPathByContainer(containerIndex int) *ContainerHostPathRelation {
|
||||
if len(s.Containers) == 0 {
|
||||
return nil
|
||||
}
|
||||
key := fmt.Sprintf("%d", containerIndex)
|
||||
return s.Containers[key]
|
||||
}
|
||||
|
||||
type HostPaths []HostPath
|
||||
|
||||
func (s HostPaths) String() string {
|
||||
return jsonutils.Marshal(s).String()
|
||||
}
|
||||
|
||||
func (s HostPaths) IsZero() bool {
|
||||
return len(s) == 0
|
||||
}
|
||||
@@ -138,6 +138,7 @@ type LLMSKuBaseCreateInput struct {
|
||||
Bandwidth int `json:"bandwidth"`
|
||||
|
||||
Volumes *Volumes `json:"volumes"`
|
||||
HostPaths *HostPaths `json:"host_paths"`
|
||||
PortMappings *PortMappings `json:"port_mappings"`
|
||||
Devices *Devices `json:"devices"`
|
||||
Envs *Envs `json:"envs"`
|
||||
@@ -152,11 +153,12 @@ type LLMSkuBaseUpdateInput struct {
|
||||
|
||||
// RequstSyncImage *bool `json:"request_sync_image"`
|
||||
|
||||
DiskSize *int `json:"disk_size" yunion-deprecated-by:"disk_size_mb"`
|
||||
DiskSizeMB *int `json:"disk_size_mb"`
|
||||
TemplateId *string `json:"template_id"`
|
||||
StorageType *string `json:"storage_type"`
|
||||
Volumes *Volumes `json:"volumes"`
|
||||
DiskSize *int `json:"disk_size" yunion-deprecated-by:"disk_size_mb"`
|
||||
DiskSizeMB *int `json:"disk_size_mb"`
|
||||
TemplateId *string `json:"template_id"`
|
||||
StorageType *string `json:"storage_type"`
|
||||
Volumes *Volumes `json:"volumes"`
|
||||
HostPaths *HostPaths `json:"host_paths"`
|
||||
|
||||
Bandwidth *int `json:"bandwidth"`
|
||||
PortMappings *PortMappings `json:"port_mappings"`
|
||||
|
||||
@@ -345,6 +345,48 @@ func GetDiskVolumeMounts(vols *api.Volumes, containerIndex int, postOverlays []*
|
||||
return mounts
|
||||
}
|
||||
|
||||
func GetHostPathVolumeMounts(hostPaths *api.HostPaths, containerIndex int) []*apis.ContainerVolumeMount {
|
||||
if hostPaths == nil {
|
||||
return nil
|
||||
}
|
||||
mounts := make([]*apis.ContainerVolumeMount, 0)
|
||||
for idx, hostPath := range *hostPaths {
|
||||
hostPathRelation := hostPath.GetHostPathByContainer(containerIndex)
|
||||
if hostPathRelation == nil {
|
||||
continue
|
||||
}
|
||||
mounts = append(mounts, &apis.ContainerVolumeMount{
|
||||
UniqueName: fmt.Sprintf("host-path-%d-%d-%s", idx, containerIndex, hostPathRelation.MountPath),
|
||||
Type: apis.CONTAINER_VOLUME_MOUNT_TYPE_HOST_PATH,
|
||||
MountPath: hostPathRelation.MountPath,
|
||||
ReadOnly: hostPathRelation.ReadOnly,
|
||||
Propagation: hostPathRelation.Propagation,
|
||||
HostPath: &apis.ContainerVolumeMountHostPath{
|
||||
Type: hostPath.Type,
|
||||
Path: hostPath.Path,
|
||||
AutoCreate: hostPath.AutoCreate,
|
||||
AutoCreateConfig: hostPath.AutoCreateConfig,
|
||||
},
|
||||
FsUser: hostPathRelation.FsUser,
|
||||
FsGroup: hostPathRelation.FsGroup,
|
||||
})
|
||||
}
|
||||
return mounts
|
||||
}
|
||||
|
||||
func AppendLLMSkuVolumeMounts(containers []*computeapi.PodContainerCreateInput, skuBase *SLLMSkuBase, postOverlays []*apis.ContainerVolumeMountDiskPostOverlay) {
|
||||
if skuBase == nil {
|
||||
return
|
||||
}
|
||||
for idx := range containers {
|
||||
if containers[idx] == nil {
|
||||
continue
|
||||
}
|
||||
containers[idx].VolumeMounts = append(containers[idx].VolumeMounts, GetDiskVolumeMounts(skuBase.Volumes, idx, postOverlays)...)
|
||||
containers[idx].VolumeMounts = append(containers[idx].VolumeMounts, GetHostPathVolumeMounts(skuBase.HostPaths, idx)...)
|
||||
}
|
||||
}
|
||||
|
||||
// 取消自动删除
|
||||
func (llm *SLLMBase) Delete(ctx context.Context, userCred mcclient.TokenCredential) error {
|
||||
return nil
|
||||
|
||||
@@ -150,5 +150,9 @@ func GetLLMContainerInstantModelDriver(typ llm.LLMContainerType) (ILLMContainerI
|
||||
|
||||
// GetDriverPodContainers returns the container(s) for the given driver. If the driver implements ILLMContainerDriverMultiContainer, GetContainerSpecs is used; otherwise a single-element slice from GetContainerSpec is returned.
|
||||
func GetDriverPodContainers(ctx context.Context, drv ILLMContainerDriver, llm *SLLM, image *SLLMImage, sku *SLLMSku, props []string, devices []computeapi.SIsolatedDevice, diskId string) []*computeapi.PodContainerCreateInput {
|
||||
return drv.GetContainerSpecs(ctx, llm, image, sku, props, devices, diskId)
|
||||
containers := drv.GetContainerSpecs(ctx, llm, image, sku, props, devices, diskId)
|
||||
if sku != nil {
|
||||
AppendLLMSkuVolumeMounts(containers, &sku.SLLMSkuBase, nil)
|
||||
}
|
||||
return containers
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ type SLLMSkuBase struct {
|
||||
Cpu int `nullable:"false" default:"1" create:"optional" list:"user" update:"user"`
|
||||
Memory int `nullable:"false" default:"512" create:"optional" list:"user" update:"user"`
|
||||
Volumes *api.Volumes `charset:"utf8" length:"medium" nullable:"true" list:"user" update:"user" create:"optional"`
|
||||
HostPaths *api.HostPaths `charset:"utf8" length:"medium" nullable:"true" list:"user" update:"user" create:"optional"`
|
||||
PortMappings *api.PortMappings `charset:"utf8" length:"medium" nullable:"true" list:"user" update:"user" create:"optional"`
|
||||
Devices *api.Devices `charset:"utf8" length:"medium" nullable:"true" list:"user" update:"user" create:"optional"`
|
||||
Envs *api.Envs `charset:"utf8" nullable:"true" list:"user" update:"user" create:"optional"`
|
||||
|
||||
@@ -25,6 +25,7 @@ type LLMSkuBaseCreateOptions struct {
|
||||
TemplateId string
|
||||
PortMappings []string `help:"port mapping in the format of protocol:port[:prefix][:first_port_offset][:env_key=env_value], e.g. tcp:5555:192.168.0.0/16:5:WOLF_BASE_PORT=20000"`
|
||||
Devices []string `help:"device info in the format of model[:path[:dev_type]], e.g. 'GeForce RTX 4060'"`
|
||||
HostPaths []string `json:"-" help:"host path mount in format path=<host_path>,type=<directory|file>,container_index=<index>,mount_path=<container_path>[,auto_create=<bool>][,read_only=<bool>][,propagation=<private|rslave|rshared>][,fs_user=<uid>][,fs_group=<gid>][,uid=<uid>][,gid=<gid>][,permissions=<mode>]; repeatable"`
|
||||
|
||||
Env []string `help:"env in format of key=value"`
|
||||
Property []string `help:"extra properties of key=value, e.g. tango32=true"`
|
||||
@@ -44,6 +45,9 @@ func (o *LLMSkuBaseCreateOptions) Params(dict *jsonutils.JSONDict) error {
|
||||
dict.Set("volumes", jsonutils.Marshal(vols))
|
||||
fetchPortmappings(o.PortMappings, dict)
|
||||
fetchDevices(o.Devices, dict)
|
||||
if err := fetchHostPaths(o.HostPaths, dict); err != nil {
|
||||
return err
|
||||
}
|
||||
fetchEnvs(o.Env, dict)
|
||||
fetchProperties(o.Property, dict)
|
||||
|
||||
@@ -66,6 +70,7 @@ type LLMSkuBaseUpdateOptions struct {
|
||||
// Fps *int
|
||||
PortMappings []string `help:"port mapping in the format of protocol:port[:prefix][:first_port_offset], e.g. tcp:5555:192.168.0.0/16,10.10.0.0/16:1000"`
|
||||
Devices []string `help:"device info in the format of model[:path[:dev_type]], e.g. QuadraT2A:/dev/nvme1n1, Device::VASTAITECH_GPU"`
|
||||
HostPaths []string `json:"-" help:"host path mount in format path=<host_path>,type=<directory|file>,container_index=<index>,mount_path=<container_path>[,auto_create=<bool>][,read_only=<bool>][,propagation=<private|rslave|rshared>][,fs_user=<uid>][,fs_group=<gid>][,uid=<uid>][,gid=<gid>][,permissions=<mode>]; repeatable"`
|
||||
Env []string `help:"env in the format of key=value, e.g. AUTHENTICATION_PATH=/bupt-test/"`
|
||||
Property []string `help:"extra properties of key=value, e.g. tango32=true"`
|
||||
|
||||
@@ -92,12 +97,206 @@ func (o *LLMSkuBaseUpdateOptions) Params(dict *jsonutils.JSONDict) error {
|
||||
|
||||
fetchPortmappings(o.PortMappings, dict)
|
||||
fetchDevices(o.Devices, dict)
|
||||
if err := fetchHostPaths(o.HostPaths, dict); err != nil {
|
||||
return err
|
||||
}
|
||||
fetchEnvs(o.Env, dict)
|
||||
fetchProperties(o.Property, dict)
|
||||
// fetchMountedApps(o.MountedApps, dict)
|
||||
return nil
|
||||
}
|
||||
|
||||
type hostPathCliSpec struct {
|
||||
Path string
|
||||
Type apis.ContainerVolumeMountHostPathType
|
||||
ContainerIndex int
|
||||
MountPath string
|
||||
ReadOnly bool
|
||||
Propagation apis.ContainerMountPropagation
|
||||
FsUser *int64
|
||||
FsGroup *int64
|
||||
AutoCreate bool
|
||||
AutoCreateConfig *apis.ContainerVolumeMountHostPathAutoCreateConfig
|
||||
}
|
||||
|
||||
func fetchHostPaths(hostPathStrs []string, dict *jsonutils.JSONDict) error {
|
||||
hostPaths, err := parseHostPaths(hostPathStrs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(hostPaths) > 0 {
|
||||
dict.Set("host_paths", jsonutils.Marshal(hostPaths))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseHostPaths(hostPathStrs []string) (api.HostPaths, error) {
|
||||
if len(hostPathStrs) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
ret := make(api.HostPaths, 0)
|
||||
groupIndex := make(map[string]int)
|
||||
for _, item := range hostPathStrs {
|
||||
spec, err := parseHostPath(item)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
key := getHostPathGroupKey(spec)
|
||||
idx, ok := groupIndex[key]
|
||||
if !ok {
|
||||
ret = append(ret, api.HostPath{
|
||||
Type: spec.Type,
|
||||
Path: spec.Path,
|
||||
AutoCreate: spec.AutoCreate,
|
||||
AutoCreateConfig: spec.AutoCreateConfig,
|
||||
Containers: make(api.ContainerHostPathRelations),
|
||||
})
|
||||
idx = len(ret) - 1
|
||||
groupIndex[key] = idx
|
||||
}
|
||||
containerKey := strconv.Itoa(spec.ContainerIndex)
|
||||
if _, exists := ret[idx].Containers[containerKey]; exists {
|
||||
return nil, fmt.Errorf("duplicate host_path container_index %d for path %q", spec.ContainerIndex, spec.Path)
|
||||
}
|
||||
ret[idx].Containers[containerKey] = &api.ContainerHostPathRelation{
|
||||
MountPath: spec.MountPath,
|
||||
ReadOnly: spec.ReadOnly,
|
||||
Propagation: spec.Propagation,
|
||||
FsUser: spec.FsUser,
|
||||
FsGroup: spec.FsGroup,
|
||||
}
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func parseHostPath(item string) (*hostPathCliSpec, error) {
|
||||
parts := strings.Split(item, ",")
|
||||
fields := make(map[string]string, len(parts))
|
||||
for _, part := range parts {
|
||||
part = strings.TrimSpace(part)
|
||||
if part == "" {
|
||||
continue
|
||||
}
|
||||
idx := strings.Index(part, "=")
|
||||
if idx <= 0 {
|
||||
return nil, fmt.Errorf("invalid host path %q, expected key=value", item)
|
||||
}
|
||||
key := strings.TrimSpace(part[:idx])
|
||||
val := strings.TrimSpace(part[idx+1:])
|
||||
if key == "" {
|
||||
return nil, fmt.Errorf("invalid host path %q, empty key", item)
|
||||
}
|
||||
fields[key] = val
|
||||
}
|
||||
|
||||
spec := &hostPathCliSpec{
|
||||
Type: apis.CONTAINER_VOLUME_MOUNT_HOST_PATH_TYPE_DIRECTORY,
|
||||
}
|
||||
for key, val := range fields {
|
||||
switch key {
|
||||
case "path":
|
||||
spec.Path = val
|
||||
case "type":
|
||||
spec.Type = apis.ContainerVolumeMountHostPathType(val)
|
||||
case "container_index":
|
||||
idx, err := strconv.Atoi(val)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid host path container_index %q: %w", val, err)
|
||||
}
|
||||
spec.ContainerIndex = idx
|
||||
case "mount_path":
|
||||
spec.MountPath = val
|
||||
case "read_only":
|
||||
b, err := strconv.ParseBool(val)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid host path read_only %q: %w", val, err)
|
||||
}
|
||||
spec.ReadOnly = b
|
||||
case "auto_create":
|
||||
b, err := strconv.ParseBool(val)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid host path auto_create %q: %w", val, err)
|
||||
}
|
||||
spec.AutoCreate = b
|
||||
case "propagation":
|
||||
if !apis.ContainerMountPropagations.Has(val) {
|
||||
return nil, fmt.Errorf("invalid host path propagation %q", val)
|
||||
}
|
||||
spec.Propagation = apis.ContainerMountPropagation(val)
|
||||
case "fs_user":
|
||||
fsUser, err := strconv.ParseInt(val, 10, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid host path fs_user %q: %w", val, err)
|
||||
}
|
||||
spec.FsUser = &fsUser
|
||||
case "fs_group":
|
||||
fsGroup, err := strconv.ParseInt(val, 10, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid host path fs_group %q: %w", val, err)
|
||||
}
|
||||
spec.FsGroup = &fsGroup
|
||||
case "uid", "gid", "permissions":
|
||||
if spec.AutoCreateConfig == nil {
|
||||
spec.AutoCreateConfig = &apis.ContainerVolumeMountHostPathAutoCreateConfig{}
|
||||
}
|
||||
spec.AutoCreate = true
|
||||
switch key {
|
||||
case "uid":
|
||||
uid, err := strconv.ParseUint(val, 10, 32)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid host path uid %q: %w", val, err)
|
||||
}
|
||||
spec.AutoCreateConfig.Uid = uint(uid)
|
||||
case "gid":
|
||||
gid, err := strconv.ParseUint(val, 10, 32)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid host path gid %q: %w", val, err)
|
||||
}
|
||||
spec.AutoCreateConfig.Gid = uint(gid)
|
||||
case "permissions":
|
||||
spec.AutoCreateConfig.Permissions = val
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid host path key %q", key)
|
||||
}
|
||||
}
|
||||
|
||||
if spec.Path == "" {
|
||||
return nil, fmt.Errorf("invalid host path %q, missing path", item)
|
||||
}
|
||||
if spec.MountPath == "" {
|
||||
return nil, fmt.Errorf("invalid host path %q, missing mount_path", item)
|
||||
}
|
||||
if !isValidHostPathType(spec.Type) {
|
||||
return nil, fmt.Errorf("invalid host path type %q", spec.Type)
|
||||
}
|
||||
if _, ok := fields["container_index"]; !ok {
|
||||
return nil, fmt.Errorf("invalid host path %q, missing container_index", item)
|
||||
}
|
||||
return spec, nil
|
||||
}
|
||||
|
||||
func getHostPathGroupKey(spec *hostPathCliSpec) string {
|
||||
uid := uint(0)
|
||||
gid := uint(0)
|
||||
permissions := ""
|
||||
if spec.AutoCreateConfig != nil {
|
||||
uid = spec.AutoCreateConfig.Uid
|
||||
gid = spec.AutoCreateConfig.Gid
|
||||
permissions = spec.AutoCreateConfig.Permissions
|
||||
}
|
||||
return fmt.Sprintf("%s|%s|%t|%d|%d|%s", spec.Path, spec.Type, spec.AutoCreate, uid, gid, permissions)
|
||||
}
|
||||
|
||||
func isValidHostPathType(hostPathType apis.ContainerVolumeMountHostPathType) bool {
|
||||
switch hostPathType {
|
||||
case apis.CONTAINER_VOLUME_MOUNT_HOST_PATH_TYPE_DIRECTORY, apis.CONTAINER_VOLUME_MOUNT_HOST_PATH_TYPE_FILE:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func fetchPortmappings(pmStrs []string, dict *jsonutils.JSONDict) {
|
||||
pms := make([]api.PortMapping, 0)
|
||||
for _, pm := range pmStrs {
|
||||
|
||||
Reference in New Issue
Block a user