running_project_model

This commit is contained in:
w-mj 2023-04-04 22:08:55 +08:00
parent b1ab2df214
commit b45ddb291f
No known key found for this signature in database
GPG Key ID: 3A2CB5BE2F835897
5 changed files with 236 additions and 242 deletions

23
API.md
View File

@ -64,6 +64,7 @@ type Model struct {
```
数据结构中包含的Comment字段为该数据的简单描述Doc字段为该数据的详细文档文档应支持Markdown格式保存和渲染。
在所有创建对象的请求POSTID字段将被忽略创建成功后自动分配ID。
## 镜像管理
@ -90,7 +91,7 @@ CURD:
+ GET /images?UserName=cg_wmj # 查询某用户的镜像
+ GET /images?IsPublish=1 # 查询所有公开镜像
## 数据集管理 (尚未实现)
## 数据集管理
训练数据,数据结构:
```go
@ -118,7 +119,7 @@ CURD:
+ GET /data?UserName=cg_wmj
+ GET /data?IsPublish=1
## 项目管理 (尚未实现)
## 项目管理
数据结构
```go
@ -132,9 +133,8 @@ type ProjectModel struct {
Doc string
IsPublic bool
ImageID int
Image ImageModel
TrainData []*DataModel `gorm:"many2many:projects_data"`
Image ImageModel
TrainData []*ProjectTrainDataModel `gorm:"foreignKey:ProjectID"`
UserPath string // 用户目录挂载位置
@ -143,6 +143,14 @@ type ProjectModel struct {
Running bool `gorm:"-:all"`
}
type ProjectTrainDataModel struct {
BaseModel[ProjectTrainDataModel]
Project *ProjectModel
Data *DataModel
MountPath string
}
```
其中`Type`字段为项目的类型,可选值为`jupyter`、`jupyterlab`、`jupyternotebook`、`novnc`、`vscode`、`http`。
@ -161,6 +169,7 @@ CURD:
数据结构
```go
type RunningProject struct {
ID int
Project *ProjectModel
StartTime *time.Time
UserName string
@ -177,9 +186,7 @@ type RunningProject struct {
运行一个项目前端发送POST请求时URL可留空若该项目成功启动则POST的返回内容中将包含正确的URL。
通过此URL即可访问项目服务。ControlURL为容器服务客户端连接地址可通过本地客户端提供更多功能。
此数据结构中没有ID字段删除时使用其对应的ProjectModel的ID。
CURD:
+ POST/running_project
+ DELETE: /running_project?ProjectID=1
+ DELETE: /running_project?ID=1
+ GET /running_projects?UserName=cg_wmj

View File

@ -0,0 +1,8 @@
package controller
import "k8s-manager/src/model"
func CreateRunningProject(r *model.RunningProject) (*model.RunningProject, error) {
return r, nil
}

View File

@ -1,197 +0,0 @@
package model
import (
"fmt"
"k8s-manager/src"
v1 "k8s.io/api/core/v1"
"strings"
"time"
"unicode"
)
type AppModel struct {
Id int
AppName string
UserName string
Image string
Command string
Comment string
Env string
SvcName string
SvcRemovePrefix bool
Ports string
PersistentPath string
OriginId int
CreateTime time.Time
UpdateTime time.Time
IsPublished bool
AppType string
}
func (*AppModel) TableName() string {
return "app"
}
func GetApps(username string) []AppModel {
db := k8s_manager.DB()
ans := make([]AppModel, 0)
db.Where("user_name = ?", username).Find(&ans)
return ans
}
func (a *AppModel) Fill() error {
db := k8s_manager.DB()
res := db.First(a, a.Id)
return res.Error
}
func (a *AppModel) fillDefaults() {
if a.SvcName == "" {
a.SvcName = "svc"
}
a.SvcName = strings.TrimPrefix(a.SvcName, a.NamePrefix()+"-")
if a.PersistentPath == "" {
a.PersistentPath = "/data/"
}
}
func (a *AppModel) Update() error {
a.fillDefaults()
a.UpdateTime = time.Now()
db := k8s_manager.DB()
db.Save(a)
return nil
}
func (a *AppModel) Create() error {
a.fillDefaults()
a.CreateTime = time.Now()
a.UpdateTime = time.Now()
db := k8s_manager.DB()
db.Create(a)
return nil
}
func (a *AppModel) Delete() error {
db := k8s_manager.DB()
db.Delete(a, a.Id)
return nil
}
func (a *AppModel) NamePrefix() string {
t := "app"
if a.OriginId > 0 {
t = "fork"
}
prefix := fmt.Sprintf("%s-%s-%s-%d", t, a.UserName, a.AppName, a.Id)
ans := strings.Builder{}
for _, c := range prefix {
if (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '-' {
ans.WriteRune(c)
} else {
ans.WriteString(fmt.Sprintf("-%d-", c))
}
}
return ans.String()
}
func (a *AppModel) PodName() string {
return fmt.Sprintf("%s-pod", a.NamePrefix())
}
func (a *AppModel) ContainerName() string {
return fmt.Sprintf("%s-container", a.NamePrefix())
}
func (a *AppModel) ServiceName() string {
if len(a.GetPorts()) == 0 {
return ""
}
if strings.TrimSpace(a.SvcName) != "" {
return fmt.Sprintf("%s-%s", a.NamePrefix(), a.SvcName)
}
return a.DefaultServiceName()
}
func (a *AppModel) DefaultServiceName() string {
return fmt.Sprintf("%s-svc", a.NamePrefix())
}
func (a *AppModel) DeploymentName() string {
return fmt.Sprintf("%s-deployment", a.NamePrefix())
}
func (a *AppModel) GetCommandOrBlack() []string {
if strings.TrimSpace(a.Command) != "" {
t := a.Command
t = strings.ReplaceAll(t, "$URL_PREFIX", "svc/"+a.URLPrefix())
return []string{"/bin/sh", "-c", t}
} else {
return nil
}
}
func (a *AppModel) GetLabel() (string, string) {
return "cg-label", fmt.Sprintf("%s-label", a.NamePrefix())
}
func (a *AppModel) GetEnv() []v1.EnvVar {
if strings.TrimSpace(a.Env) == "" {
return nil
}
sep1 := strings.Split(a.Env, " ")
ans := make([]v1.EnvVar, len(sep1))
for i, v := range sep1 {
kv := strings.SplitN(v, "=", 2)
ans[i].Name = kv[0]
if len(kv) == 2 {
ans[i].Value = kv[1]
}
}
return ans
}
func (a *AppModel) GetPorts() []int32 {
ans := make([]int32, 0)
var cur int32 = 0
for _, c := range a.Ports {
if unicode.IsNumber(c) {
cur = cur*10 + (c - '0')
} else if cur > 0 {
ans = append(ans, cur)
cur = 0
}
}
if cur > 0 {
ans = append(ans, cur)
}
return ans
}
func (a *AppModel) IngressName() string {
return fmt.Sprintf("%s-ingress", a.NamePrefix())
}
func (a *AppModel) URLPrefix() string {
return a.ServiceName()
}
func (a *AppModel) PersistentVolumeName() string {
return a.UserName + "-pv"
}
func (a *AppModel) NFSPath() string {
return k8s_manager.GetConfig().NFSPath + "/" + a.UserName + "/" + a.AppName
}
func (a *AppModel) PersistentVolumeClaimName() string {
return a.NamePrefix() + "-pvc"
}
func (a *AppModel) StorageClassName() string {
return a.NamePrefix() + "-storage-class"
}
func (a *AppModel) PersistentVolumeMountName() string {
return a.NamePrefix() + "-pvm"
}

View File

@ -1,9 +1,13 @@
package model
import (
"fmt"
k8s_manager "k8s-manager/src"
"k8s-manager/src/utils"
v1 "k8s.io/api/core/v1"
"strings"
"time"
"unicode"
)
type ProjectModel struct {
@ -105,4 +109,164 @@ type RunningProject struct {
GpuMemory int
URL string
ControlURL string
ID int
SvcRemovePrefix bool
}
func (a *RunningProject) NamePrefix() string {
t := "app"
prefix := fmt.Sprintf("%s-%s-%s-%d", t, a.UserName, a.Project.ProjectName, a.ID)
ans := strings.Builder{}
for _, c := range prefix {
if (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '-' {
ans.WriteRune(c)
} else {
ans.WriteString(fmt.Sprintf("-%d-", c))
}
}
return ans.String()
}
func (a *RunningProject) PodName() string {
return fmt.Sprintf("%s-pod", a.NamePrefix())
}
func (a *RunningProject) ContainerName() string {
return fmt.Sprintf("%s-container", a.NamePrefix())
}
func (a *RunningProject) ServiceName() string {
if len(a.GetPorts()) == 0 {
return ""
}
return a.DefaultServiceName()
}
func (a *RunningProject) DefaultServiceName() string {
return fmt.Sprintf("%s-svc", a.NamePrefix())
}
func (a *RunningProject) DeploymentName() string {
return fmt.Sprintf("%s-deployment", a.NamePrefix())
}
func (a *RunningProject) GetCommandOrBlack() []string {
if strings.TrimSpace(a.Project.Image.Command) != "" {
t := a.Project.Image.Command
t = strings.ReplaceAll(t, "$URL_PREFIX", "svc/"+a.URLPrefix())
return []string{"/bin/sh", "-c", t}
} else {
return nil
}
}
func (a *RunningProject) GetLabel() (string, string) {
return "cg-label", fmt.Sprintf("%s-label", a.NamePrefix())
}
func (a *RunningProject) GetEnv() []v1.EnvVar {
if strings.TrimSpace(a.Project.Image.Env) == "" {
return nil
}
sep1 := strings.Split(a.Project.Image.Env, " ")
ans := make([]v1.EnvVar, len(sep1))
for i, v := range sep1 {
kv := strings.SplitN(v, "=", 2)
ans[i].Name = kv[0]
if len(kv) == 2 {
ans[i].Value = kv[1]
}
}
return ans
}
func (a *RunningProject) GetPorts() []int32 {
ans := make([]int32, 0)
var cur int32 = 0
for _, c := range a.Project.Image.Port {
if unicode.IsNumber(c) {
cur = cur*10 + (c - '0')
} else if cur > 0 {
ans = append(ans, cur)
cur = 0
}
}
if cur > 0 {
ans = append(ans, cur)
}
return ans
}
func (a *RunningProject) IngressName() string {
return fmt.Sprintf("%s-ingress", a.NamePrefix())
}
func (a *RunningProject) URLPrefix() string {
return a.ServiceName()
}
func (a *RunningProject) PersistentVolumeName() string {
return a.UserName + "-pv"
}
func (a *RunningProject) NFSPath() string {
return k8s_manager.GetConfig().NFSPath + "/" + a.UserName + "/" + a.Project.ProjectName
}
func (a *RunningProject) PersistentVolumeClaimName() string {
return a.NamePrefix() + "-pvc"
}
func (a *RunningProject) StorageClassName() string {
return a.NamePrefix() + "-storage-class"
}
func (a *RunningProject) PersistentVolumeMountName() string {
return a.NamePrefix() + "-pvm"
}
type NFSMount struct {
Name string
From string
To string
ReadOnly bool
}
func (a *RunningProject) createNFSMount(name string, from string, to string, readonly bool) NFSMount {
return NFSMount{
Name: fmt.Sprintf("%s-nfs-%s", a.NamePrefix(), name),
From: from,
To: to,
ReadOnly: readonly,
}
}
func (a *RunningProject) GetNFSMounts() []NFSMount {
var ans []NFSMount
if a.Project.UserPath != "" {
ans = append(ans, a.createNFSMount("user",
fmt.Sprintf("/users/%s/home", a.UserName), a.Project.UserPath, false))
}
if a.Project.CodePath != "" {
ans = append(ans, a.createNFSMount("code",
fmt.Sprintf("/users/%s/code/%s", a.UserName, a.Project.ProjectName), a.Project.CodePath, false))
}
if a.Project.CodePath != "" {
ans = append(ans, a.createNFSMount("model",
fmt.Sprintf("/users/%s/model/%s", a.UserName, a.Project.ProjectName), a.Project.ModelPath, false))
}
for i, m := range a.Project.TrainData {
if m.Data.Type != "dir" {
continue
}
ans = append(ans, a.createNFSMount(
fmt.Sprintf("%s-%d", m.Data.Name, i),
m.Data.AccessInfo,
m.MountPath,
true,
),
)
}
return ans
}

View File

@ -73,24 +73,53 @@ func (c *K8sClient) DeploymentExists(deploymentName string) bool {
return false
}
func (c *K8sClient) UpdateDeployment(app *model.AppModel) {
labelKey, labelValue := app.GetLabel()
l := map[string]string{labelKey: labelValue}
if _, err := os.Stat(app.NFSPath()); os.IsNotExist(err) {
err := os.MkdirAll(app.NFSPath(), 0777)
if err != nil {
log.WithError(err).Error("Make nfs dir error")
func checkDirs(mounts []model.NFSMount) {
for _, n := range mounts {
if _, err := os.Stat(n.From); os.IsNotExist(err) {
err := os.MkdirAll(n.From, 0777)
if err != nil {
log.WithError(err).Error("Make nfs dir error")
}
}
}
}
func (c *K8sClient) UpdateDeployment(app *model.RunningProject) {
labelKey, labelValue := app.GetLabel()
l := map[string]string{labelKey: labelValue}
nfsDirs := app.GetNFSMounts()
checkDirs(nfsDirs)
var volumeMounts []v1.VolumeMount
var volumes []v1.Volume
for _, n := range nfsDirs {
volumeMounts = append(volumeMounts, v1.VolumeMount{
Name: n.Name,
MountPath: n.To,
ReadOnly: n.ReadOnly,
})
volumes = append(volumes, v1.Volume{
Name: n.Name,
VolumeSource: v1.VolumeSource{
NFS: &v1.NFSVolumeSource{
Server: k8s_manager.GetConfig().NFSPath,
Path: n.From,
ReadOnly: n.ReadOnly,
},
},
})
}
podLabels := map[string]string{
labelKey: labelValue,
"cg-deployment": app.DeploymentName(),
}
containerPorts := funk.Map(app.GetPorts(), func(port int32) v1.ContainerPort {
return v1.ContainerPort{
ContainerPort: port,
}
}).([]v1.ContainerPort)
deployment := &appv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Name: app.DeploymentName(),
@ -108,36 +137,19 @@ func (c *K8sClient) UpdateDeployment(app *model.AppModel) {
Spec: v1.PodSpec{
Containers: []v1.Container{{
Name: app.ContainerName(),
Image: app.Image,
Image: app.Project.Image.Image,
Command: app.GetCommandOrBlack(),
Env: append(app.GetEnv(), v1.EnvVar{
Name: "URL_PREFIX",
Value: app.URLPrefix(),
}),
VolumeMounts: []v1.VolumeMount{
{
Name: app.PersistentVolumeMountName(),
ReadOnly: false,
MountPath: app.PersistentPath,
},
},
Ports: containerPorts,
VolumeMounts: volumeMounts,
Ports: containerPorts,
SecurityContext: &v1.SecurityContext{
RunAsUser: utils.UnnamedPointer(int64(0)),
},
}},
Volumes: []v1.Volume{
{
Name: app.PersistentVolumeMountName(),
VolumeSource: v1.VolumeSource{
NFS: &v1.NFSVolumeSource{
Server: k8s_manager.GetConfig().NFSHost,
Path: app.NFSPath(),
ReadOnly: false,
},
},
},
},
Volumes: volumes,
},
},
},
@ -166,7 +178,7 @@ func (c *K8sClient) ServiceExists(servername string) bool {
return false
}
func (c *K8sClient) UpdateService(app *model.AppModel) {
func (c *K8sClient) UpdateService(app *model.RunningProject) {
ports := app.GetPorts()
if len(ports) == 0 {
return
@ -210,19 +222,19 @@ func (c *K8sClient) UpdateService(app *model.AppModel) {
}
func (c *K8sClient) CreateApp(app *model.AppModel) {
func (c *K8sClient) CreateApp(app *model.RunningProject) {
c.UpdateDeployment(app)
c.UpdateService(app)
c.UpdateIngress(app)
}
func (c *K8sClient) DeleteApp(app *model.AppModel) {
func (c *K8sClient) DeleteApp(app *model.RunningProject) {
c.DeleteDeployment(app.DeploymentName())
c.DeleteAppService(app)
c.DeleteIngress(app.IngressName())
}
func (c *K8sClient) DeleteAppService(app *model.AppModel) {
func (c *K8sClient) DeleteAppService(app *model.RunningProject) {
k, v := app.GetLabel()
label := labels.FormatLabels(map[string]string{k: v})
svcs, err := c.CoreV1().Services(c.Namespace).List(context.Background(), metav1.ListOptions{
@ -294,7 +306,7 @@ func (c *K8sClient) IngressExists(name string) bool {
return false
}
func (c *K8sClient) UpdateIngress(a *model.AppModel) {
func (c *K8sClient) UpdateIngress(a *model.RunningProject) {
ports := a.GetPorts()
if len(ports) == 0 {
return
@ -363,7 +375,7 @@ func (c *K8sClient) DeleteIngress(name string) {
}
/*
func (c *K8sClient) PersistentVolumeExists(app *AppModel) bool {
func (c *K8sClient) PersistentVolumeExists(app *RunningProject) bool {
_, err := c.CoreV1().PersistentVolumes().Get(context.Background(), app.PersistentVolumeName(), metav1.GetOptions{})
if err == nil {
return true
@ -374,7 +386,7 @@ func (c *K8sClient) PersistentVolumeExists(app *AppModel) bool {
return false
}
func (c *K8sClient) CreatePersistentVolume(app *AppModel) {
func (c *K8sClient) CreatePersistentVolume(app *RunningProject) {
cap1, _ := resource.ParseQuantity("1Gi")
pv := &v1.PersistentVolume{
ObjectMeta: metav1.ObjectMeta{
@ -404,7 +416,7 @@ func (c *K8sClient) CreatePersistentVolume(app *AppModel) {
}
}
func (c *K8sClient) PersistentVolumeClaimExists(app *AppModel) bool {
func (c *K8sClient) PersistentVolumeClaimExists(app *RunningProject) bool {
_, err := c.CoreV1().PersistentVolumeClaims(c.Namespace).Get(context.Background(), app.PersistentVolumeClaimName(), metav1.GetOptions{})
if err == nil {
return true
@ -415,7 +427,7 @@ func (c *K8sClient) PersistentVolumeClaimExists(app *AppModel) bool {
return false
}
func (c *K8sClient) CreatePersistentVolumeClaim(app *AppModel) {
func (c *K8sClient) CreatePersistentVolumeClaim(app *RunningProject) {
pvc := &v1.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{
Name: app.PersistentVolumeClaimName(),