Compare commits
No commits in common. "b45ddb291f9d6acc67707194dec10f03f4e2e78e" and "4e9d9b9cb0d6b652b18f305dd060fa5742701e1b" have entirely different histories.
b45ddb291f
...
4e9d9b9cb0
192
API.md
192
API.md
|
|
@ -1,192 +0,0 @@
|
|||
# API 文档
|
||||
|
||||
## 总体设计
|
||||
|
||||
### 基本HTTP请求
|
||||
|
||||
本系统后端API设计遵循RESTful规范,对于一般数据类型的增删改查使用同一URL下的POST、DELETE、PUT和GET方法进行访问。
|
||||
对于POST和PUT方法,待传输数据放在请求体中,以Json格式进行传输。DELETE和GET方法使用URL参数传递必要数据。POST为上传新数据,使用POST时数据中ID字段将被忽略。
|
||||
|
||||
后端返回数据也全部为Json格式,使用HTTP状态码标识请求状态。当状态码为200时可以认为该请求成功达到了预期的结果。若请求失败,则状态码不为200,并且返回数据中包含`msg`
|
||||
字段对发生的错误进行了简要说明。
|
||||
|
||||
一次失败的请求返回数据如下所示,其状态码为401(StatusUnauthorized)。
|
||||
|
||||
```json
|
||||
{
|
||||
"ok": false,
|
||||
"msg": "username error"
|
||||
}
|
||||
```
|
||||
|
||||
一般数据类型包含`ID`字段,类型为整数。DELETE方法可用此字段表示将要删除的对象。删除一个镜像的请求如下命令所示。
|
||||
|
||||
```bash
|
||||
curl -X DELETE https://ms.educg.net/admin/image?ID=1
|
||||
```
|
||||
|
||||
### 资源查询请求
|
||||
|
||||
对单个资源查询的方式与删除类似,可使用该数据结构中的某些字段对数据进行查询,返回结果为Json格式。
|
||||
|
||||
同时对该类型的多个资源进行查询时,URL一般为该资源类型的复数形式,如镜像的查询URL为`image`
|
||||
,那么同时获得多个镜像的查询的URL为`images`。特殊情况另行说明。
|
||||
对多个数据进行查询时,URL中除了必要的过滤条件外,还可包含分页参数`page`和`pageSize`,均为整数类型。返回结果中`data`
|
||||
字段为查询结果数据,`page`、`pageCount`和`pageSize`用于控制分页。
|
||||
|
||||
```json
|
||||
{
|
||||
"page": 1,
|
||||
"pageCount": 100,
|
||||
"pageSize": 2,
|
||||
"data": [
|
||||
{
|
||||
"ID": 2,
|
||||
"Other fields": "Other Values"
|
||||
},
|
||||
{
|
||||
"ID": 3
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### 数据结构
|
||||
数据结构使用Golang语言格式进行定义,其中`gorm.Model`为公用字段,定义如下:
|
||||
```go
|
||||
type Model struct {
|
||||
ID uint `gorm:"primarykey"`
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
DeletedAt DeletedAt `gorm:"index"`
|
||||
}
|
||||
```
|
||||
|
||||
数据结构中包含的Comment字段为该数据的简单描述,Doc字段为该数据的详细文档,文档应支持Markdown格式保存和渲染。
|
||||
在所有创建对象的请求(POST)中,ID字段将被忽略,创建成功后自动分配ID。
|
||||
|
||||
## 镜像管理
|
||||
|
||||
数据结构:
|
||||
|
||||
```go
|
||||
type ImageModel struct {
|
||||
gorm.Model
|
||||
Name string
|
||||
Image string
|
||||
Comment string
|
||||
Command string
|
||||
Env string
|
||||
Port string
|
||||
UserName string
|
||||
IsPublished bool
|
||||
Doc string
|
||||
}
|
||||
```
|
||||
CURD:
|
||||
+ POST:/image
|
||||
+ PUT: /image
|
||||
+ DELETE: /image?ID=1
|
||||
+ GET /images?UserName=cg_wmj # 查询某用户的镜像
|
||||
+ GET /images?IsPublish=1 # 查询所有公开镜像
|
||||
|
||||
## 数据集管理
|
||||
|
||||
训练数据,数据结构:
|
||||
```go
|
||||
type DataModel struct {
|
||||
gorm.Model
|
||||
Type string
|
||||
Name string
|
||||
Comment string
|
||||
Doc string
|
||||
UserName string
|
||||
AccessInfo string
|
||||
IsPublished bool
|
||||
}
|
||||
```
|
||||
其中`Type`字段为数据集的类型,表示该数据集的访问方式,目前仅支持`Type=="dir"`访问目录格式数据,后续可能支持mysql等数据库。
|
||||
本系统中,对本地文件的访问使用Webdav协议。
|
||||
`AccessInfo`字段为该数据集的访问方式,在`Type=="dir"`时为该目录的路径。
|
||||
例如某数据集对象中,`AccessInfo=='/some/data/dir/'`,同时文件服务器地址为`https://ms.educg.net/files/ `,那么通过WebDav协议访问`https://ms.educg.net/files/some/data/dir/ `即可访问该目录。
|
||||
|
||||
|
||||
CURD:
|
||||
+ POST:/data
|
||||
+ PUT: /data
|
||||
+ DELETE: /data?ID=1
|
||||
+ GET /data?UserName=cg_wmj
|
||||
+ GET /data?IsPublish=1
|
||||
|
||||
## 项目管理
|
||||
|
||||
数据结构
|
||||
```go
|
||||
type ProjectModel struct {
|
||||
gorm.Model
|
||||
ProjectName string
|
||||
UserName string
|
||||
GroupName string
|
||||
Type string
|
||||
Comment string
|
||||
Doc string
|
||||
IsPublic bool
|
||||
|
||||
Image ImageModel
|
||||
TrainData []*ProjectTrainDataModel `gorm:"foreignKey:ProjectID"`
|
||||
|
||||
|
||||
UserPath string // 用户目录挂载位置
|
||||
CodePath string // 代码目录挂载位置
|
||||
ModelPath string // 模型目录挂载位置
|
||||
|
||||
Running bool `gorm:"-:all"`
|
||||
}
|
||||
|
||||
|
||||
type ProjectTrainDataModel struct {
|
||||
BaseModel[ProjectTrainDataModel]
|
||||
Project *ProjectModel
|
||||
Data *DataModel
|
||||
MountPath string
|
||||
}
|
||||
```
|
||||
|
||||
其中`Type`字段为项目的类型,可选值为`jupyter`、`jupyterlab`、`jupyternotebook`、`novnc`、`vscode`、`http`。
|
||||
|
||||
在创建或更新项目时,`TrainData`字段应该为DataModel结构的列表,其中每一个DataModel仅有`ID`即可。
|
||||
|
||||
CURD:
|
||||
+ POST:/project
|
||||
+ PUT: /project
|
||||
+ DELETE: /project?ID=1
|
||||
+ GET /projects?UserName=cg_wmj
|
||||
+ GET /projects?IsPublish=1
|
||||
|
||||
## 运行项目 (尚未实现)
|
||||
|
||||
数据结构
|
||||
```go
|
||||
type RunningProject struct {
|
||||
ID int
|
||||
Project *ProjectModel
|
||||
StartTime *time.Time
|
||||
UserName string
|
||||
Nodes int
|
||||
Cpus int
|
||||
Memory int
|
||||
Gpus int
|
||||
GpuMemory int
|
||||
URL string
|
||||
ControlURL string
|
||||
}
|
||||
```
|
||||
|
||||
运行一个项目,前端发送POST请求时URL可留空,若该项目成功启动,则POST的返回内容中将包含正确的URL。
|
||||
通过此URL即可访问项目服务。ControlURL为容器服务客户端连接地址,可通过本地客户端提供更多功能。
|
||||
|
||||
CURD:
|
||||
+ POST:/running_project
|
||||
+ DELETE: /running_project?ID=1
|
||||
+ GET /running_projects?UserName=cg_wmj
|
||||
1
Makefile
1
Makefile
|
|
@ -23,7 +23,6 @@ restart: docker
|
|||
|
||||
install-dev: push
|
||||
scp -i C:${HOMEPATH}/.ssh/id_rsa install-dev.yaml root@192.168.131.195:/root/install-backend-dev.yaml
|
||||
ssh -i C:${HOMEPATH}/.ssh/id_rsa root@192.168.131.195 kubectl delete -f /root/install-backend-dev.yaml
|
||||
ssh -i C:${HOMEPATH}/.ssh/id_rsa root@192.168.131.195 kubectl apply -f /root/install-backend-dev.yaml
|
||||
|
||||
push: docker
|
||||
|
|
|
|||
|
|
@ -18,10 +18,3 @@ EOF
|
|||
|
||||
kubectl get secret dev-secret -o yaml
|
||||
```
|
||||
|
||||
|
||||
## 持续查看Log
|
||||
|
||||
```bash
|
||||
while true; do bash -c "kubectl get pods | grep k8s-manager-deployment | awk '{print \$1}' | xargs kubectl logs -f"; sleep 1; done
|
||||
```
|
||||
|
|
@ -22,7 +22,7 @@ spec:
|
|||
- containerPort: 8080
|
||||
env:
|
||||
- name: GIN_MODE
|
||||
value: debug
|
||||
value: release
|
||||
- name: URL_PREFIX
|
||||
value: admin
|
||||
- name: DB_HOST
|
||||
|
|
@ -70,7 +70,6 @@ metadata:
|
|||
name: manager-ingress
|
||||
annotations:
|
||||
nginx.ingress.kubernetes.io/rewrite-target: /$2
|
||||
nginx.ingress.kubernetes.io/proxy-body-size: 10g
|
||||
spec:
|
||||
ingressClassName: nginx
|
||||
rules:
|
||||
|
|
|
|||
10
main.go
10
main.go
|
|
@ -73,16 +73,6 @@ func main() {
|
|||
|
||||
r.POST("/fork", controller.ForkApp)
|
||||
|
||||
// controller.AddWebdavHandler(r)
|
||||
|
||||
r.POST("/image", controller.AddImage)
|
||||
r.PUT("/image", controller.UpdateImage)
|
||||
r.DELETE("/image", controller.DeleteImage)
|
||||
r.GET("/images", controller.GetImages)
|
||||
|
||||
controller.AddDataController(r)
|
||||
controller.AddProjectController(r)
|
||||
|
||||
err := r.Run(":8000")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
|
|
|
|||
|
|
@ -1,65 +0,0 @@
|
|||
package controller
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/gin-gonic/gin"
|
||||
"k8s-manager/src/model"
|
||||
"k8s-manager/src/utils"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type BaseController[T model.ModalType, PT model.ModalInterface[T]] struct {
|
||||
}
|
||||
|
||||
func (BaseController[T, PT]) Add(t *T) (utils.OKType, error) {
|
||||
PT(t).Create()
|
||||
return utils.OK, nil
|
||||
}
|
||||
|
||||
func (BaseController[T, PT]) Delete(ID int) (utils.OKType, error) {
|
||||
data := PT(new(T))
|
||||
data.SetID(uint(ID))
|
||||
data.Delete()
|
||||
return utils.OK, nil
|
||||
}
|
||||
|
||||
func (BaseController[T, PT]) Update(username string, t *T) (utils.OKType, error) {
|
||||
data := PT(t)
|
||||
oldData := PT(new(T))
|
||||
oldData.SetID(data.GetID())
|
||||
oldData.Fill()
|
||||
if oldData.GetUserName() != username {
|
||||
return nil, errors.New("wrong username")
|
||||
}
|
||||
data.Save()
|
||||
return utils.OK, nil
|
||||
}
|
||||
|
||||
func (BaseController[T, PT]) Gets(c *gin.Context) {
|
||||
username := c.MustGet("username").(string)
|
||||
var ans []any
|
||||
query := map[string]interface{}{}
|
||||
if c.Query("UserName") != "" {
|
||||
if username != c.Query("UserName") {
|
||||
utils.GinError(c, http.StatusUnauthorized, "username error")
|
||||
return
|
||||
}
|
||||
query["UserName"] = username
|
||||
} else if c.Query("IsPublic") == "1" {
|
||||
query["IsPublic"] = true
|
||||
} else {
|
||||
utils.GinError(c, http.StatusBadRequest, "param error.")
|
||||
return
|
||||
}
|
||||
ans = PT(new(T)).Gets(query)
|
||||
c.JSON(http.StatusOK, gin.H{"data": ans})
|
||||
}
|
||||
|
||||
//func AddCRUDRoute[T model.ModalType](r *gin.Engine, path string) {
|
||||
// t := BaseController[T, *T]{}
|
||||
// typeName := reflect.TypeOf((*T)(nil)).Name()
|
||||
// utils.RouteWrapper(r, "POST", path, t.Add, []string{typeName})
|
||||
// utils.RouteWrapper(r, "PUT", path, t.Update, []string{"username", typeName})
|
||||
// utils.RouteWrapper(r, "DELETE", path, t.Delete, []string{"ID"})
|
||||
// r.Handle("GET", path, t.Gets)
|
||||
//}
|
||||
|
|
@ -1,57 +0,0 @@
|
|||
package controller
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/gin-gonic/gin"
|
||||
"k8s-manager/src/model"
|
||||
"k8s-manager/src/utils"
|
||||
"math/rand"
|
||||
)
|
||||
|
||||
func AddData(username string, data *model.DataModel) (utils.OKType, error) {
|
||||
data.UserName = username
|
||||
data.AccessInfo = fmt.Sprintf("/data/%s-%s-%d", data.UserName, data.Name, rand.Int())
|
||||
data.Create()
|
||||
return utils.OK, nil
|
||||
}
|
||||
|
||||
func DeleteData(username string, ID uint) (utils.OKType, error) {
|
||||
oldData := model.DataModel{}
|
||||
oldData.SetID(ID)
|
||||
oldData.Fill()
|
||||
if username != oldData.GetUserName() {
|
||||
return nil, errors.New("wrong username")
|
||||
}
|
||||
oldData.Delete()
|
||||
return utils.OK, nil
|
||||
}
|
||||
|
||||
func UpdateData(username string, data *model.DataModel) (utils.OKType, error) {
|
||||
oldData := &model.DataModel{}
|
||||
oldData.SetID(data.ID)
|
||||
oldData.Fill()
|
||||
if username != oldData.GetUserName() {
|
||||
return nil, errors.New("wrong username")
|
||||
}
|
||||
data.Save(oldData)
|
||||
return utils.OK, nil
|
||||
}
|
||||
|
||||
func GetData(UserName string, IsPublish bool) (gin.H, error) {
|
||||
data := model.DataModel{}
|
||||
data.UserName = UserName
|
||||
data.IsPublished = IsPublish
|
||||
if UserName == "" && !IsPublish {
|
||||
return gin.H{"data": []string{}}, nil
|
||||
}
|
||||
ans := data.Gets()
|
||||
return gin.H{"data": ans}, nil
|
||||
}
|
||||
|
||||
func AddDataController(r *gin.Engine) {
|
||||
utils.RouteWrapper(r, "POST", "/data", AddData, []string{"username", "1"})
|
||||
utils.RouteWrapper(r, "DELETE", "/data", DeleteData, []string{"username", "ID"})
|
||||
utils.RouteWrapper(r, "PUT", "/data", UpdateData, []string{"username", "1"})
|
||||
utils.RouteWrapper(r, "GET", "/data", GetData, []string{"UserName", "IsPublished"})
|
||||
}
|
||||
|
|
@ -6,7 +6,6 @@ import (
|
|||
"k8s-manager/src/model"
|
||||
utils "k8s-manager/src/utils"
|
||||
"net/http"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func AddImage(c *gin.Context) {
|
||||
|
|
@ -21,23 +20,18 @@ func AddImage(c *gin.Context) {
|
|||
}
|
||||
|
||||
func DeleteImage(c *gin.Context) {
|
||||
id := c.Query("ID")
|
||||
if id == "" {
|
||||
utils.GinError(c, http.StatusBadRequest, "no id")
|
||||
image := &model.ImageModel{}
|
||||
if utils.GinErrorCheck(c, c.BindJSON(image)) {
|
||||
return
|
||||
}
|
||||
username := c.MustGet("username").(string)
|
||||
idi, err := strconv.Atoi(id)
|
||||
if utils.GinErrorCheck(c, err) {
|
||||
return
|
||||
}
|
||||
oldImage := &model.ImageModel{Model: gorm.Model{ID: uint(idi)}}
|
||||
oldImage := &model.ImageModel{Model: gorm.Model{ID: image.ID}}
|
||||
oldImage.Fill()
|
||||
if oldImage.UserName != username {
|
||||
utils.GinError(c, http.StatusUnauthorized, "username error")
|
||||
return
|
||||
}
|
||||
oldImage.Delete()
|
||||
image.Delete()
|
||||
}
|
||||
|
||||
func UpdateImage(c *gin.Context) {
|
||||
|
|
@ -55,20 +49,13 @@ func UpdateImage(c *gin.Context) {
|
|||
image.Save()
|
||||
}
|
||||
|
||||
func GetImages(c *gin.Context) {
|
||||
func MyImages(c *gin.Context) {
|
||||
username := c.MustGet("username").(string)
|
||||
var Images []model.ImageModel
|
||||
if c.Query("UserName") != "" {
|
||||
if username != c.Query("UserName") {
|
||||
utils.GinError(c, http.StatusUnauthorized, "username error")
|
||||
return
|
||||
}
|
||||
Images = model.GetMyImages(username)
|
||||
} else if c.Query("IsPublic") != "" {
|
||||
Images = model.GetPublicImages()
|
||||
} else {
|
||||
utils.GinError(c, http.StatusBadRequest, "param error.")
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"data": Images})
|
||||
myImages := model.GetMyImages(username)
|
||||
c.JSON(http.StatusOK, gin.H{"images": myImages})
|
||||
}
|
||||
|
||||
func PublicImages(c *gin.Context) {
|
||||
myImages := model.GetPublicImages()
|
||||
c.JSON(http.StatusOK, gin.H{"images": myImages})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,54 +0,0 @@
|
|||
package controller
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/gin-gonic/gin"
|
||||
"k8s-manager/src/model"
|
||||
"k8s-manager/src/utils"
|
||||
)
|
||||
|
||||
func AddProject(username string, proj *model.ProjectModel) (utils.OKType, error) {
|
||||
proj.UserName = username
|
||||
proj.Create()
|
||||
return utils.OK, nil
|
||||
}
|
||||
|
||||
func DeleteProject(username string, ID uint) (utils.OKType, error) {
|
||||
old := &model.ProjectModel{}
|
||||
old.ID = ID
|
||||
old.Fill()
|
||||
if username != old.UserName {
|
||||
return nil, errors.New("wrong username")
|
||||
}
|
||||
old.Delete()
|
||||
return utils.OK, nil
|
||||
}
|
||||
|
||||
func UpdateProject(username string, proj *model.ProjectModel) (utils.OKType, error) {
|
||||
old := &model.ProjectModel{}
|
||||
old.ID = proj.ID
|
||||
old.Fill()
|
||||
if username != old.GetUserName() {
|
||||
return nil, errors.New("wrong username")
|
||||
}
|
||||
proj.Save(old)
|
||||
return utils.OK, nil
|
||||
}
|
||||
|
||||
func GetProjects(UserName string, IsPublished bool) (gin.H, error) {
|
||||
proj := &model.ProjectModel{}
|
||||
proj.UserName = UserName
|
||||
proj.IsPublished = IsPublished
|
||||
if UserName == "" && !IsPublished {
|
||||
return gin.H{"data": []string{}}, nil
|
||||
}
|
||||
ans := proj.Gets()
|
||||
return gin.H{"data": ans}, nil
|
||||
}
|
||||
|
||||
func AddProjectController(r *gin.Engine) {
|
||||
utils.RouteWrapper(r, "POST", "/project", AddProject, []string{"username", "1"})
|
||||
utils.RouteWrapper(r, "DELETE", "/project", DeleteProject, []string{"username", "ID"})
|
||||
utils.RouteWrapper(r, "PUT", "/project", UpdateProject, []string{"username", "1"})
|
||||
utils.RouteWrapper(r, "GET", "/projects", GetProjects, []string{"UserName", "IsPublished"})
|
||||
}
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
package controller
|
||||
|
||||
import "k8s-manager/src/model"
|
||||
|
||||
func CreateRunningProject(r *model.RunningProject) (*model.RunningProject, error) {
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
|
@ -1,52 +0,0 @@
|
|||
package controller
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"golang.org/x/net/webdav"
|
||||
"net/http"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var fs *webdav.Handler
|
||||
var fsLock sync.Mutex
|
||||
|
||||
func WebDav(c *gin.Context) {
|
||||
if fs == nil {
|
||||
fsLock.Lock()
|
||||
if fs == nil {
|
||||
fs = &webdav.Handler{
|
||||
Prefix: "/webdav",
|
||||
FileSystem: webdav.Dir("/mnt/CGdata/ms"),
|
||||
LockSystem: webdav.NewMemLS(),
|
||||
Logger: func(h *http.Request, e error) {
|
||||
log.Error(e)
|
||||
},
|
||||
}
|
||||
}
|
||||
fsLock.Unlock()
|
||||
}
|
||||
log.Info("Webdav ", c.Request.Method, c.Request.URL.Path)
|
||||
fs.ServeHTTP(c.Writer, c.Request)
|
||||
}
|
||||
|
||||
func AddWebdavHandler(r *gin.Engine) {
|
||||
methods := []string{
|
||||
"OPTIONS",
|
||||
"GET", "HEAD", "POST",
|
||||
"DELETE",
|
||||
"PUT",
|
||||
"MKCOL",
|
||||
"COPY", "MOVE",
|
||||
"LOCK",
|
||||
"UNLOCK",
|
||||
"PROPFIND",
|
||||
"PROPPATCH",
|
||||
}
|
||||
|
||||
for _, m := range methods {
|
||||
r.Handle(m, "/webdav", WebDav)
|
||||
r.Handle(m, "/webdav/*path", WebDav)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -48,10 +48,6 @@ func JWTAuthMiddleware(c *gin.Context) {
|
|||
// 登录过程,不验证身份
|
||||
return
|
||||
}
|
||||
if strings.HasPrefix(c.Request.URL.Path, "/webdav") {
|
||||
// Webdav暂时不验证身份
|
||||
return
|
||||
}
|
||||
authHeader := c.Request.Header.Get("Authorization")
|
||||
if authHeader == "" {
|
||||
var err error
|
||||
|
|
|
|||
|
|
@ -0,0 +1,197 @@
|
|||
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"
|
||||
}
|
||||
|
|
@ -1,94 +0,0 @@
|
|||
package model
|
||||
|
||||
import (
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/clause"
|
||||
k8s_manager "k8s-manager/src"
|
||||
"reflect"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type ModalType interface {
|
||||
DataModel | ImageModel
|
||||
}
|
||||
|
||||
type ModalInterface[T ModalType] interface {
|
||||
*T
|
||||
Create()
|
||||
Fill()
|
||||
Delete()
|
||||
Save()
|
||||
Gets(map[string]interface{}) []any
|
||||
|
||||
GetUserName() string
|
||||
SetUserName(string)
|
||||
SetID(uint)
|
||||
GetID() uint
|
||||
}
|
||||
|
||||
type BaseModel[T any] struct {
|
||||
gorm.Model
|
||||
}
|
||||
|
||||
func (b *BaseModel[T]) Fill() {
|
||||
db := k8s_manager.DB()
|
||||
p := (*T)(unsafe.Pointer(b))
|
||||
db.Preload(clause.Associations).First(p, b.ID)
|
||||
}
|
||||
|
||||
func (b *BaseModel[T]) Create() {
|
||||
db := k8s_manager.DB()
|
||||
p := (*T)(unsafe.Pointer(b))
|
||||
db.Create(p)
|
||||
}
|
||||
|
||||
func (b *BaseModel[T]) Save(old *T) {
|
||||
db := k8s_manager.DB()
|
||||
p := (*T)(unsafe.Pointer(b))
|
||||
db.Save(p)
|
||||
}
|
||||
|
||||
func (b *BaseModel[T]) Delete() {
|
||||
db := k8s_manager.DB()
|
||||
p := (*T)(unsafe.Pointer(b))
|
||||
db.Delete(p)
|
||||
}
|
||||
|
||||
func (b *BaseModel[T]) Gets() []T {
|
||||
db := k8s_manager.DB()
|
||||
ans := make([]T, 0)
|
||||
p := (*T)(unsafe.Pointer(b))
|
||||
db.Preload(clause.Associations).Where(p).Find(&ans)
|
||||
return ans
|
||||
}
|
||||
|
||||
//func (b *BaseModel[T]) Get(query map[string]interface{}) *T {
|
||||
// db := k8s_manager.DB()
|
||||
// p := (*T)(unsafe.Pointer(b))
|
||||
// db.Where(query).First(p)
|
||||
// return p
|
||||
//}
|
||||
|
||||
func (b *BaseModel[T]) SetUserName(name string) {
|
||||
p := (*T)(unsafe.Pointer(b))
|
||||
a := reflect.ValueOf(p)
|
||||
reflect.Indirect(a).FieldByName("UserName").SetString(name)
|
||||
}
|
||||
|
||||
func (b *BaseModel[T]) SetID(ID uint) {
|
||||
p := (*T)(unsafe.Pointer(b))
|
||||
a := reflect.ValueOf(p)
|
||||
reflect.Indirect(a).FieldByName("ID").SetUint(uint64(ID))
|
||||
}
|
||||
|
||||
func (b *BaseModel[T]) GetUserName() string {
|
||||
p := (*T)(unsafe.Pointer(b))
|
||||
a := reflect.ValueOf(p)
|
||||
return reflect.Indirect(a).FieldByName("UserName").String()
|
||||
}
|
||||
|
||||
func (b *BaseModel[T]) GetID() uint {
|
||||
p := (*T)(unsafe.Pointer(b))
|
||||
a := reflect.ValueOf(p)
|
||||
return uint(reflect.Indirect(a).FieldByName("ID").Uint())
|
||||
}
|
||||
|
|
@ -1,16 +1,23 @@
|
|||
package model
|
||||
|
||||
import (
|
||||
"gorm.io/gorm"
|
||||
k8s_manager "k8s-manager/src"
|
||||
)
|
||||
|
||||
type DataModel struct {
|
||||
BaseModel[DataModel]
|
||||
Type string
|
||||
Name string
|
||||
Comment string
|
||||
Doc string
|
||||
UserName string
|
||||
IsPublished bool
|
||||
AccessInfo string
|
||||
gorm.Model
|
||||
Type string
|
||||
Name string
|
||||
Comment string
|
||||
AccessInfo string
|
||||
}
|
||||
|
||||
func (*DataModel) TableName() string {
|
||||
return "data"
|
||||
}
|
||||
|
||||
func (d *DataModel) Fill() {
|
||||
db := k8s_manager.DB()
|
||||
db.First(d, d.ID)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import (
|
|||
|
||||
type ImageModel struct {
|
||||
gorm.Model
|
||||
Name string
|
||||
Image string
|
||||
Comment string
|
||||
Command string
|
||||
|
|
@ -15,7 +14,6 @@ type ImageModel struct {
|
|||
Port string
|
||||
UserName string
|
||||
IsPublished bool
|
||||
Doc string
|
||||
}
|
||||
|
||||
func (*ImageModel) TableName() string {
|
||||
|
|
@ -42,13 +40,6 @@ func (i *ImageModel) Delete() {
|
|||
db.Delete(i)
|
||||
}
|
||||
|
||||
func GetImages(query map[string]interface{}) []ImageModel {
|
||||
db := k8s_manager.DB()
|
||||
ans := make([]ImageModel, 0)
|
||||
db.Where(query).Find(&ans)
|
||||
return ans
|
||||
}
|
||||
|
||||
func GetMyImages(username string) []ImageModel {
|
||||
db := k8s_manager.DB()
|
||||
ans := make([]ImageModel, 0)
|
||||
|
|
|
|||
|
|
@ -1,272 +1,34 @@
|
|||
package model
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
k8s_manager "k8s-manager/src"
|
||||
"k8s-manager/src/utils"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"strings"
|
||||
"gorm.io/gorm"
|
||||
"time"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
type ProjectModel struct {
|
||||
BaseModel[ProjectModel]
|
||||
gorm.Model
|
||||
ProjectName string
|
||||
UserName string
|
||||
GroupName string
|
||||
Type string
|
||||
Comment string
|
||||
Doc string
|
||||
IsPublished bool
|
||||
|
||||
ImageID int
|
||||
Image ImageModel
|
||||
TrainData []*ProjectTrainDataModel `gorm:"foreignKey:ProjectID"`
|
||||
|
||||
UserPath string
|
||||
CodePath string
|
||||
ModelPath string
|
||||
|
||||
Running bool `gorm:"-:all"`
|
||||
TrainData []*DataModel `gorm:"many2many:projects_data"`
|
||||
}
|
||||
|
||||
func (*ProjectModel) TableName() string {
|
||||
return "projects"
|
||||
}
|
||||
|
||||
type ProjectTrainDataModel struct {
|
||||
BaseModel[ProjectTrainDataModel]
|
||||
ProjectID int
|
||||
Project *ProjectModel
|
||||
DataID int
|
||||
Data *DataModel
|
||||
MountPath string
|
||||
}
|
||||
|
||||
func (*ProjectTrainDataModel) TableName() string {
|
||||
return "project_train_data"
|
||||
}
|
||||
|
||||
//func (p *ProjectModel) Fill() {
|
||||
// db := k8s_manager.DB()
|
||||
// db.Preload(clause.Associations).First(p, p.ID)
|
||||
//}
|
||||
//
|
||||
//func (p *ProjectModel) Create() {
|
||||
// db := k8s_manager.DB()
|
||||
// db.Create(p)
|
||||
//}
|
||||
|
||||
func (p *ProjectModel) Delete() {
|
||||
db := k8s_manager.DB()
|
||||
for _, d := range p.TrainData {
|
||||
db.Delete(d)
|
||||
}
|
||||
db.Delete(p)
|
||||
}
|
||||
|
||||
func (p *ProjectModel) Save(oldProject *ProjectModel) {
|
||||
db := k8s_manager.DB()
|
||||
//oldProject := &ProjectModel{}
|
||||
//oldProject.ID = p.ID
|
||||
//oldProject.Fill()
|
||||
|
||||
//toAdd := utils.Difference(p.TrainData, oldProject.TrainData, func(a, b ProjectTrainDataModel) bool { return a.ID == b.ID })
|
||||
//for t := range toAdd {
|
||||
// db.Create(t)
|
||||
//}
|
||||
//
|
||||
toDel := utils.Difference(oldProject.TrainData, p.TrainData, func(a, b *ProjectTrainDataModel) bool { return a.ID == b.ID })
|
||||
for _, t := range toDel {
|
||||
db.Delete(t)
|
||||
}
|
||||
|
||||
toSave := utils.Intersect(p.TrainData, oldProject.TrainData, func(a, b *ProjectTrainDataModel) bool { return a.ID == b.ID })
|
||||
for _, t := range toSave {
|
||||
t.Project = p
|
||||
db.Save(t)
|
||||
}
|
||||
|
||||
db.Save(p)
|
||||
}
|
||||
|
||||
//func (p *ProjectModel) Gets() []ProjectModel {
|
||||
// db := k8s_manager.DB()
|
||||
// var ans []ProjectModel
|
||||
// db.Model(&ProjectModel{}).Preload(clause.Associations).Where(p).Find(&ans)
|
||||
// return ans
|
||||
//}
|
||||
|
||||
type RunningProject struct {
|
||||
Project *ProjectModel
|
||||
StartTime *time.Time
|
||||
UserName string
|
||||
Nodes int
|
||||
Cpus int
|
||||
Memory int
|
||||
Gpus int
|
||||
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
|
||||
Project *ProjectModel
|
||||
StartTime *time.Time
|
||||
UserName string
|
||||
Nodes int
|
||||
Cpus int
|
||||
Memory int
|
||||
Gpus int
|
||||
GpuMemory int
|
||||
}
|
||||
|
|
|
|||
|
|
@ -73,53 +73,24 @@ func (c *K8sClient) DeploymentExists(deploymentName string) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
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) {
|
||||
func (c *K8sClient) UpdateDeployment(app *model.AppModel) {
|
||||
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,
|
||||
},
|
||||
},
|
||||
})
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
||||
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(),
|
||||
|
|
@ -137,19 +108,36 @@ func (c *K8sClient) UpdateDeployment(app *model.RunningProject) {
|
|||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{{
|
||||
Name: app.ContainerName(),
|
||||
Image: app.Project.Image.Image,
|
||||
Image: app.Image,
|
||||
Command: app.GetCommandOrBlack(),
|
||||
Env: append(app.GetEnv(), v1.EnvVar{
|
||||
Name: "URL_PREFIX",
|
||||
Value: app.URLPrefix(),
|
||||
}),
|
||||
VolumeMounts: volumeMounts,
|
||||
Ports: containerPorts,
|
||||
VolumeMounts: []v1.VolumeMount{
|
||||
{
|
||||
Name: app.PersistentVolumeMountName(),
|
||||
ReadOnly: false,
|
||||
MountPath: app.PersistentPath,
|
||||
},
|
||||
},
|
||||
Ports: containerPorts,
|
||||
SecurityContext: &v1.SecurityContext{
|
||||
RunAsUser: utils.UnnamedPointer(int64(0)),
|
||||
},
|
||||
}},
|
||||
Volumes: volumes,
|
||||
Volumes: []v1.Volume{
|
||||
{
|
||||
Name: app.PersistentVolumeMountName(),
|
||||
VolumeSource: v1.VolumeSource{
|
||||
NFS: &v1.NFSVolumeSource{
|
||||
Server: k8s_manager.GetConfig().NFSHost,
|
||||
Path: app.NFSPath(),
|
||||
ReadOnly: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -178,7 +166,7 @@ func (c *K8sClient) ServiceExists(servername string) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
func (c *K8sClient) UpdateService(app *model.RunningProject) {
|
||||
func (c *K8sClient) UpdateService(app *model.AppModel) {
|
||||
ports := app.GetPorts()
|
||||
if len(ports) == 0 {
|
||||
return
|
||||
|
|
@ -222,19 +210,19 @@ func (c *K8sClient) UpdateService(app *model.RunningProject) {
|
|||
|
||||
}
|
||||
|
||||
func (c *K8sClient) CreateApp(app *model.RunningProject) {
|
||||
func (c *K8sClient) CreateApp(app *model.AppModel) {
|
||||
c.UpdateDeployment(app)
|
||||
c.UpdateService(app)
|
||||
c.UpdateIngress(app)
|
||||
}
|
||||
|
||||
func (c *K8sClient) DeleteApp(app *model.RunningProject) {
|
||||
func (c *K8sClient) DeleteApp(app *model.AppModel) {
|
||||
c.DeleteDeployment(app.DeploymentName())
|
||||
c.DeleteAppService(app)
|
||||
c.DeleteIngress(app.IngressName())
|
||||
}
|
||||
|
||||
func (c *K8sClient) DeleteAppService(app *model.RunningProject) {
|
||||
func (c *K8sClient) DeleteAppService(app *model.AppModel) {
|
||||
k, v := app.GetLabel()
|
||||
label := labels.FormatLabels(map[string]string{k: v})
|
||||
svcs, err := c.CoreV1().Services(c.Namespace).List(context.Background(), metav1.ListOptions{
|
||||
|
|
@ -306,7 +294,7 @@ func (c *K8sClient) IngressExists(name string) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
func (c *K8sClient) UpdateIngress(a *model.RunningProject) {
|
||||
func (c *K8sClient) UpdateIngress(a *model.AppModel) {
|
||||
ports := a.GetPorts()
|
||||
if len(ports) == 0 {
|
||||
return
|
||||
|
|
@ -375,7 +363,7 @@ func (c *K8sClient) DeleteIngress(name string) {
|
|||
}
|
||||
|
||||
/*
|
||||
func (c *K8sClient) PersistentVolumeExists(app *RunningProject) bool {
|
||||
func (c *K8sClient) PersistentVolumeExists(app *AppModel) bool {
|
||||
_, err := c.CoreV1().PersistentVolumes().Get(context.Background(), app.PersistentVolumeName(), metav1.GetOptions{})
|
||||
if err == nil {
|
||||
return true
|
||||
|
|
@ -386,7 +374,7 @@ func (c *K8sClient) PersistentVolumeExists(app *RunningProject) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
func (c *K8sClient) CreatePersistentVolume(app *RunningProject) {
|
||||
func (c *K8sClient) CreatePersistentVolume(app *AppModel) {
|
||||
cap1, _ := resource.ParseQuantity("1Gi")
|
||||
pv := &v1.PersistentVolume{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
|
|
@ -416,7 +404,7 @@ func (c *K8sClient) CreatePersistentVolume(app *RunningProject) {
|
|||
}
|
||||
}
|
||||
|
||||
func (c *K8sClient) PersistentVolumeClaimExists(app *RunningProject) bool {
|
||||
func (c *K8sClient) PersistentVolumeClaimExists(app *AppModel) bool {
|
||||
_, err := c.CoreV1().PersistentVolumeClaims(c.Namespace).Get(context.Background(), app.PersistentVolumeClaimName(), metav1.GetOptions{})
|
||||
if err == nil {
|
||||
return true
|
||||
|
|
@ -427,7 +415,7 @@ func (c *K8sClient) PersistentVolumeClaimExists(app *RunningProject) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
func (c *K8sClient) CreatePersistentVolumeClaim(app *RunningProject) {
|
||||
func (c *K8sClient) CreatePersistentVolumeClaim(app *AppModel) {
|
||||
pvc := &v1.PersistentVolumeClaim{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: app.PersistentVolumeClaimName(),
|
||||
|
|
|
|||
|
|
@ -1,31 +0,0 @@
|
|||
package utils
|
||||
|
||||
func Difference[T any](a, b []T, com func(T, T) bool) []T {
|
||||
var ans []T
|
||||
for _, x := range a {
|
||||
unique := true
|
||||
for _, y := range b {
|
||||
if com(x, y) {
|
||||
unique = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if unique {
|
||||
ans = append(ans, x)
|
||||
}
|
||||
}
|
||||
return ans
|
||||
}
|
||||
|
||||
func Intersect[T any](a, b []T, com func(T, T) bool) []T {
|
||||
var ans []T
|
||||
for _, x := range a {
|
||||
for _, y := range b {
|
||||
if com(x, y) {
|
||||
ans = append(ans, x)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return ans
|
||||
}
|
||||
|
|
@ -1,146 +0,0 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/gin-gonic/gin"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func UnnamedPointer[T any](v T) *T {
|
||||
return &v
|
||||
}
|
||||
|
||||
func GinError(c *gin.Context, code int, msg string) {
|
||||
c.JSON(code, gin.H{"ok": false, "msg": msg})
|
||||
}
|
||||
|
||||
func GinOk(c *gin.Context, msg string) {
|
||||
c.JSON(http.StatusOK, gin.H{"ok": true, "msg": msg})
|
||||
}
|
||||
|
||||
func GinOkData(c *gin.Context, data interface{}) {
|
||||
c.JSON(http.StatusOK, gin.H{"ok": true, "data": data})
|
||||
}
|
||||
|
||||
func GinErrorCheck(c *gin.Context, err error) bool {
|
||||
if err != nil {
|
||||
GinError(c, http.StatusBadRequest, err.Error())
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func setValue(data string, value reflect.Value) error {
|
||||
switch value.Kind() {
|
||||
case reflect.Bool:
|
||||
v, err := strconv.ParseBool(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
value.SetBool(v)
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
v, err := strconv.ParseInt(data, 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
value.SetInt(v)
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
v, err := strconv.ParseUint(data, 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
value.SetUint(v)
|
||||
case reflect.Float32, reflect.Float64:
|
||||
v, err := strconv.ParseFloat(data, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
value.SetFloat(v)
|
||||
case reflect.Pointer:
|
||||
return setValue(data, reflect.Indirect(value))
|
||||
case reflect.String:
|
||||
value.SetString(data)
|
||||
case reflect.Struct:
|
||||
err := json.Unmarshal([]byte(data), value.Pointer())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
log.Errorf("utils.setValue unsupported type %s", value.Type().Name())
|
||||
return errors.New("unsupported type")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type OKType = map[string]any
|
||||
|
||||
var OK = OKType{"ok": true}
|
||||
|
||||
func routeWrapperHandler(handler any, names []string) func(ctx *gin.Context) {
|
||||
return func(c *gin.Context) {
|
||||
handlerFunc := reflect.ValueOf(handler)
|
||||
handlerFuncType := handlerFunc.Type()
|
||||
handlerFuncParamLen := handlerFuncType.NumIn()
|
||||
if handlerFuncParamLen != len(names) {
|
||||
panic("Handler func Param len != len(names)")
|
||||
}
|
||||
inputArgs := make([]reflect.Value, handlerFuncParamLen)
|
||||
for i := 0; i < handlerFuncParamLen; i += 1 {
|
||||
paraType := handlerFuncType.In(i)
|
||||
var paraValue reflect.Value
|
||||
if paraType.Kind() == reflect.Pointer {
|
||||
paraValue = reflect.New(paraType.Elem())
|
||||
} else {
|
||||
paraValue = reflect.New(paraType)
|
||||
}
|
||||
v, ok := c.Get(names[i])
|
||||
if ok {
|
||||
t := reflect.ValueOf(v)
|
||||
paraValue.Elem().Set(t)
|
||||
} else {
|
||||
vs := c.Param(names[i])
|
||||
if vs == "" {
|
||||
vs = c.Query(names[i])
|
||||
}
|
||||
if vs != "" {
|
||||
err := setValue(vs, paraValue)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Cannot set value")
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
if paraValue.Kind() == reflect.Struct || (paraType.Kind() == reflect.Pointer && paraType.Elem().Kind() == reflect.Struct) {
|
||||
err := c.BindJSON(paraValue.Interface())
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"ok": false,
|
||||
"msg": fmt.Sprintf("Cannot BindJson %s: %s", paraType.Name(), err.Error())})
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if paraType.Kind() == reflect.Pointer {
|
||||
inputArgs[i] = paraValue
|
||||
} else {
|
||||
inputArgs[i] = paraValue.Elem()
|
||||
}
|
||||
}
|
||||
out := handlerFunc.Call(inputArgs)
|
||||
var ret = out[0].Interface()
|
||||
if out[1].Interface() != nil {
|
||||
var err = out[1].Interface().(error)
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"ok": false, "msg": err.Error()})
|
||||
} else {
|
||||
c.JSON(http.StatusOK, ret)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func RouteWrapper(r *gin.Engine, method string, path string, handler any, names []string) {
|
||||
r.Handle(method, path, routeWrapperHandler(handler, names))
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func UnnamedPointer[T any](v T) *T {
|
||||
return &v
|
||||
}
|
||||
|
||||
func GinError(c *gin.Context, code int, msg string) {
|
||||
c.JSON(code, gin.H{"ok": false, "msg": msg})
|
||||
}
|
||||
|
||||
func GinOk(c *gin.Context, msg string) {
|
||||
c.JSON(http.StatusOK, gin.H{"ok": true, "msg": msg})
|
||||
}
|
||||
|
||||
func GinOkData(c *gin.Context, data interface{}) {
|
||||
c.JSON(http.StatusOK, gin.H{"ok": true, "data": data})
|
||||
}
|
||||
|
||||
func GinErrorCheck(c *gin.Context, err error) bool {
|
||||
if err != nil {
|
||||
GinError(c, http.StatusBadRequest, err.Error())
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
package main
|
||||
|
||||
func main() {
|
||||
//controller.BaseController[model.DataModel, *model.DataModel]{}.Add(nil)
|
||||
}
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/gin-gonic/gin"
|
||||
"k8s-manager/src/model"
|
||||
"k8s-manager/src/utils"
|
||||
)
|
||||
|
||||
func DataHandler(model model.DataModel, q1 string, q2 int) (map[string]any, error) {
|
||||
s, _ := json.Marshal(model)
|
||||
fmt.Println(string(s))
|
||||
fmt.Printf("Call DataHandler %s %d\n", q1, q2)
|
||||
return gin.H{"name": model.Name}, nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
r := gin.Default()
|
||||
utils.RouteWrapper(r, "GET", "/index", DataHandler, []string{"model", "q1", "q2"})
|
||||
err := r.Run(":9000")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
k8s_manager "k8s-manager/src"
|
||||
"k8s-manager/src/model"
|
||||
|
|
@ -9,10 +10,9 @@ import (
|
|||
func CreateTables() {
|
||||
db := k8s_manager.DB()
|
||||
err := db.Migrator().AutoMigrate(
|
||||
//&model.ProjectModel{},
|
||||
//&model.ImageModel{},
|
||||
//&model.DataModel{},
|
||||
&model.ProjectTrainDataModel{},
|
||||
&model.ProjectModel{},
|
||||
&model.ImageModel{},
|
||||
&model.DataModel{},
|
||||
)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
|
|
@ -22,10 +22,9 @@ func CreateTables() {
|
|||
|
||||
func main() {
|
||||
k8s_manager.InitConfig()
|
||||
CreateTables()
|
||||
// db := k8s_manager.DB()
|
||||
// p := model.ProjectModel{}
|
||||
// db.Preload("TrainData").Where("user_name = ?", "wmj").Find(&p)
|
||||
// j, _ := json.Marshal(&p)
|
||||
// fmt.Println(string(j))
|
||||
db := k8s_manager.DB()
|
||||
p := model.ProjectModel{}
|
||||
db.Preload("TrainData").Where("user_name = ?", "wmj").Find(&p)
|
||||
j, _ := json.Marshal(&p)
|
||||
fmt.Println(string(j))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +0,0 @@
|
|||
FROM ugeek/webdav:amd64
|
||||
|
||||
COPY entrypoint.sh /
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
TAG = docker.educg.net/cg/webdav:v1.0
|
||||
|
||||
build:
|
||||
docker build -t $(TAG) .
|
||||
|
||||
push:
|
||||
docker push $(TAG)
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
if [ -n "$USERNAME" ] && [ -n "$PASSWORD" ]
|
||||
then
|
||||
htpasswd -bc /etc/nginx/htpasswd $USERNAME $PASSWORD
|
||||
echo Done.
|
||||
else
|
||||
echo Using no auth.
|
||||
sed -i 's%auth_basic "Restricted";% %g' /etc/nginx/conf.d/default.conf
|
||||
sed -i 's%auth_basic_user_file htpasswd;% %g' /etc/nginx/conf.d/default.conf
|
||||
fi
|
||||
|
|
@ -1,71 +0,0 @@
|
|||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: cg-webdav
|
||||
labels:
|
||||
app: cg-webdav
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: cg-webdav
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: cg-webdav
|
||||
spec:
|
||||
containers:
|
||||
- name: cg-webdav-container
|
||||
# image: bytemark/webdav:2.4
|
||||
image: docker.educg.net/cg/webdav:v1.0
|
||||
# command: ["/bin/bash"]
|
||||
# args: ["-c", "sleep 365d"]
|
||||
ports:
|
||||
- containerPort: 80
|
||||
env:
|
||||
- name: TZ
|
||||
value: Asia/Shanghai
|
||||
volumeMounts:
|
||||
- name: webdav-nfs
|
||||
mountPath: /media
|
||||
# mountPath: /var/lib/dav
|
||||
volumes:
|
||||
- name: webdav-nfs
|
||||
nfs:
|
||||
server: 192.168.252.250
|
||||
path: /mnt/CGdata/ms
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: cg-webdav-svc
|
||||
labels:
|
||||
name: cg-webdav-svc
|
||||
spec:
|
||||
ports:
|
||||
- port: 80
|
||||
targetPort: 80
|
||||
selector:
|
||||
app: cg-webdav
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: webdav-ingress
|
||||
annotations:
|
||||
nginx.ingress.kubernetes.io/rewrite-target: /$2
|
||||
nginx.ingress.kubernetes.io/proxy-body-size: 10g
|
||||
nginx.ingress.kubernetes.io/proxy-buffering: "on"
|
||||
nginx.ingress.kubernetes.io/proxy-buffer-size: "1024k"
|
||||
spec:
|
||||
ingressClassName: nginx
|
||||
rules:
|
||||
- http:
|
||||
paths:
|
||||
- path: /files(/|$)(.*)
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: cg-webdav-svc
|
||||
port:
|
||||
number: 80
|
||||
Loading…
Reference in New Issue