| @@ -1,6 +1,7 @@ | |||
| package models | |||
| import ( | |||
| "code.gitea.io/gitea/modules/log" | |||
| "encoding/json" | |||
| "errors" | |||
| "fmt" | |||
| @@ -27,6 +28,7 @@ type Cloudbrain struct { | |||
| Status string `xorm:"INDEX"` | |||
| UserID int64 `xorm:"INDEX"` | |||
| RepoID int64 `xorm:"INDEX"` | |||
| SubTaskName string `xorm:"INDEX"` | |||
| CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` | |||
| UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` | |||
| @@ -52,6 +54,17 @@ type TaskRole struct { | |||
| Command string `json:"command"` | |||
| NeedIBDevice bool `json:"needIBDevice"` | |||
| IsMainRole bool `json:"isMainRole"` | |||
| UseNNI bool `json:"useNNI"` | |||
| } | |||
| type StHostPath struct { | |||
| Path string `json:"path"` | |||
| MountPath string `json:"mountPath"` | |||
| ReadOnly bool `json:"readOnly"` | |||
| } | |||
| type Volume struct { | |||
| HostPath StHostPath `json:"hostPath"` | |||
| } | |||
| type CreateJobParams struct { | |||
| @@ -60,6 +73,7 @@ type CreateJobParams struct { | |||
| GpuType string `json:"gpuType"` | |||
| Image string `json:"image"` | |||
| TaskRoles []TaskRole `json:"taskRoles"` | |||
| Volumes []Volume `json:"volumes"` | |||
| } | |||
| type CreateJobResult struct { | |||
| @@ -74,6 +88,12 @@ type GetJobResult struct { | |||
| Payload map[string]interface{} `json:"payload"` | |||
| } | |||
| type GetImagesResult struct { | |||
| Code string `json:"code"` | |||
| Msg string `json:"msg"` | |||
| Payload map[string]ImageInfo `json:"payload"` | |||
| } | |||
| type CloudbrainsOptions struct { | |||
| ListOptions | |||
| RepoID int64 // include all repos if empty | |||
| @@ -173,6 +193,36 @@ func ConvertToJobResultPayload(input map[string]interface{}) (JobResultPayload, | |||
| return jobResultPayload, err | |||
| } | |||
| type ImagesResultPayload struct { | |||
| Images []struct { | |||
| ID int `json:"id"` | |||
| Name string `json:"name"` | |||
| Place string `json:"place"` | |||
| Description string `json:"description"` | |||
| Provider string `json:"provider"` | |||
| Createtime string `json:"createtime"` | |||
| Remark string `json:"remark"` | |||
| } `json:"taskStatuses"` | |||
| } | |||
| type ImageInfo struct { | |||
| ID int `json:"id"` | |||
| Name string `json:"name"` | |||
| Place string `json:"place"` | |||
| Description string `json:"description"` | |||
| Provider string `json:"provider"` | |||
| Createtime string `json:"createtime"` | |||
| Remark string `json:"remark"` | |||
| } | |||
| func ConvertToImagesResultPayload(input map[string]ImageInfo) (ImagesResultPayload, error) { | |||
| for _,info := range input { | |||
| log.Info(info.Name) | |||
| } | |||
| var res ImagesResultPayload | |||
| return res, nil | |||
| } | |||
| func Cloudbrains(opts *CloudbrainsOptions) ([]*Cloudbrain, int64, error) { | |||
| sess := x.NewSession() | |||
| defer sess.Close() | |||
| @@ -3,20 +3,32 @@ package cloudbrain | |||
| import ( | |||
| "errors" | |||
| "code.gitea.io/gitea/models" | |||
| "code.gitea.io/gitea/modules/context" | |||
| "code.gitea.io/gitea/modules/log" | |||
| ) | |||
| const ( | |||
| Command = `pip3 install jupyterlab==1.1.4;service ssh stop;jupyter lab --no-browser --ip=0.0.0.0 --allow-root --notebook-dir=\"/userhome\" --port=80 --NotebookApp.token=\"\" --LabApp.allow_origin=\"self https://cloudbrain.pcl.ac.cn\"` | |||
| CodeMountPath = "/code" | |||
| DataSetMountPath = "/dataset" | |||
| ModelMountPath = "/model" | |||
| SubTaskName = "task1" | |||
| DebugGPUType = "DEBUG" | |||
| "code.gitea.io/gitea/models" | |||
| ) | |||
| func GenerateTask(ctx *context.Context, jobName, image, command string) error { | |||
| jobResult, err := CreateJob(jobName, models.CreateJobParams{ | |||
| JobName: jobName, | |||
| RetryCount: 1, | |||
| GpuType: "dgx", | |||
| GpuType: DebugGPUType, | |||
| Image: image, | |||
| TaskRoles: []models.TaskRole{ | |||
| { | |||
| Name: "task1", | |||
| Name: SubTaskName, | |||
| TaskNumber: 1, | |||
| MinSucceededTaskCount: 1, | |||
| MinFailedTaskCount: 1, | |||
| @@ -27,6 +39,30 @@ func GenerateTask(ctx *context.Context, jobName, image, command string) error { | |||
| Command: command, | |||
| NeedIBDevice: false, | |||
| IsMainRole: false, | |||
| UseNNI: false, | |||
| }, | |||
| }, | |||
| Volumes: []models.Volume{ | |||
| { | |||
| HostPath: models.StHostPath{ | |||
| Path: "", | |||
| MountPath: CodeMountPath, | |||
| ReadOnly: true, | |||
| }, | |||
| }, | |||
| { | |||
| HostPath: models.StHostPath{ | |||
| Path: "", | |||
| MountPath: DataSetMountPath, | |||
| ReadOnly: false, | |||
| }, | |||
| }, | |||
| { | |||
| HostPath: models.StHostPath{ | |||
| Path: "", | |||
| MountPath: ModelMountPath, | |||
| ReadOnly: true, | |||
| }, | |||
| }, | |||
| }, | |||
| }) | |||
| @@ -34,6 +70,7 @@ func GenerateTask(ctx *context.Context, jobName, image, command string) error { | |||
| return err | |||
| } | |||
| if jobResult.Code != "S000" { | |||
| log.Error("CreateJob(%s) failed:%s", jobName, jobResult.Msg) | |||
| return errors.New(jobResult.Msg) | |||
| } | |||
| @@ -44,6 +81,7 @@ func GenerateTask(ctx *context.Context, jobName, image, command string) error { | |||
| RepoID: ctx.Repo.Repository.ID, | |||
| JobID: jobID, | |||
| JobName: jobName, | |||
| SubTaskName: SubTaskName, | |||
| }) | |||
| if err != nil { | |||
| @@ -118,3 +118,34 @@ sendjob: | |||
| return &getJobResult, nil | |||
| } | |||
| func GetImages() (*models.GetImagesResult, error) { | |||
| checkSetting() | |||
| client := getRestyClient() | |||
| var getImagesResult models.GetImagesResult | |||
| retry := 0 | |||
| sendjob: | |||
| res, err := client.R(). | |||
| SetHeader("Content-Type", "application/json"). | |||
| SetAuthToken(TOKEN). | |||
| SetResult(&getImagesResult). | |||
| Get(HOST + "/rest-server/api/v1/image/list/") | |||
| if err != nil { | |||
| return nil, fmt.Errorf("resty GetJob: %s", err) | |||
| } | |||
| if getImagesResult.Code == "S401" && retry < 1 { | |||
| retry++ | |||
| _ = loginCloudbrain() | |||
| goto sendjob | |||
| } | |||
| if getImagesResult.Code != "S000" { | |||
| return &getImagesResult, fmt.Errorf("getImgesResult err: %s", res.String()) | |||
| } | |||
| return &getImagesResult, nil | |||
| } | |||
| @@ -68,8 +68,23 @@ func CloudBrainNew(ctx *context.Context) { | |||
| t := time.Now() | |||
| var jobName = cutString(ctx.User.Name, 5) + t.Format("2006010215") + strconv.Itoa(int(t.Unix()))[:5] | |||
| ctx.Data["job_name"] = jobName | |||
| ctx.Data["image"] = "192.168.202.74:5000/user-images/deepo:v2.0" | |||
| ctx.Data["command"] = `pip3 install jupyterlab==1.1.4;service ssh stop;jupyter lab --no-browser --ip=0.0.0.0 --allow-root --notebook-dir=\"/userhome\" --port=80 --NotebookApp.token=\"\" --LabApp.allow_origin=\"self https://cloudbrain.pcl.ac.cn\"` | |||
| result, err := cloudbrain.GetImages() | |||
| if err != nil { | |||
| ctx.Data["error"] = err.Error() | |||
| } | |||
| if result != nil { | |||
| //models.ConvertToImagesResultPayload(result.Payload) | |||
| ctx.Data["image"] = result.Payload | |||
| } else { | |||
| ctx.Data["image"] = "192.168.202.74:5000/user-images/deepo:v2.0" | |||
| } | |||
| ctx.Data["command"] = cloudbrain.Command | |||
| ctx.Data["code_path"] = cloudbrain.CodeMountPath | |||
| ctx.Data["dataset_path"] = cloudbrain.DataSetMountPath | |||
| ctx.Data["model_path"] = cloudbrain.ModelMountPath | |||
| ctx.HTML(200, tplCloudBrainNew) | |||
| } | |||
| @@ -35,25 +35,39 @@ | |||
| {{range .Tasks}} | |||
| <div class="ui grid item"> | |||
| <div class="row"> | |||
| <div class="seven wide column"> | |||
| <div class="five wide column"> | |||
| <a class="title" href="{{$.Link}}/{{.JobID}}"> | |||
| <span class="fitted">{{svg "octicon-tasklist" 16}}</span> | |||
| <span class="fitted">{{.JobName}}</span> | |||
| </a> | |||
| </div> | |||
| <div class="four wide column job-status" id="{{.JobID}}" data-repopath="{{$.RepoRelPath}}" data-jobid="{{.JobID}}"> | |||
| <div class="three wide column job-status" id="{{.JobID}}" data-repopath="{{$.RepoRelPath}}" data-jobid="{{.JobID}}"> | |||
| {{.Status}} | |||
| </div> | |||
| <div class="three wide column"> | |||
| <span class="ui text center">{{svg "octicon-flame" 16}} {{TimeSinceUnix .CreatedUnix $.Lang}}</span> | |||
| </div> | |||
| <div class="two wide column"> | |||
| <div class="one wide column"> | |||
| <span class="ui text center clipboard"> | |||
| <a class="title" href="{{$.Link}}/{{.JobID}}"> | |||
| <span class="fitted">查看</span> | |||
| </a> | |||
| </span> | |||
| </div> | |||
| <div class="one wide column"> | |||
| <span class="ui text center clipboard"> | |||
| <a class="title" href="{{$.Link}}/{{.JobID}}"> | |||
| <span class="fitted">调试</span> | |||
| </a> | |||
| </span> | |||
| </div> | |||
| <div class="two wide column"> | |||
| <span class="ui text center clipboard"> | |||
| <a class="title" href="{{$.Link}}/{{.JobID}}"> | |||
| <span class="fitted">镜像提交</span> | |||
| </a> | |||
| </span> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| {{end}} | |||
| @@ -16,20 +16,46 @@ | |||
| <input name="job_name" id="cloudbrain_job_name" placeholder="任务名称" value="{{.job_name}}" tabindex="3" autofocus required maxlength="255"> | |||
| </div> | |||
| <br> | |||
| <!--> | |||
| <div class="inline required field"> | |||
| <label>镜像</label> | |||
| <input name="image" id="cloudbrain_image" placeholder="输入镜像" value="{{.image}}" tabindex="3" autofocus required maxlength="255"> | |||
| </div> | |||
| <--> | |||
| <div class="inline required field"> | |||
| <label>镜像</label> | |||
| <select id="cloudbrain_image" style='width:385px'> | |||
| {{range .image}} | |||
| <option name="image">{{.Place}}</option> | |||
| {{end}} | |||
| </select> | |||
| </div> | |||
| <div class="inline required field"> | |||
| <label>数据集</label> | |||
| <input name="dataset" id="cloudbrain_dataset" placeholder="选择数据集" value="{{.image}}" tabindex="3" autofocus required maxlength="255"> | |||
| </div> | |||
| <div class="inline required field"> | |||
| <label>数据集存放路径</label> | |||
| <input name="dataset_path" id="cloudbrain_dataset_path" value="{{.dataset_path}}" tabindex="3" autofocus required maxlength="255" disabled="disabled" style="background:#CCCCCC"> | |||
| </div> | |||
| <div class="inline required field"> | |||
| <label>模型存放路径</label> | |||
| <input name="model_path" id="cloudbrain_model_path" value="{{.model_path}}" tabindex="3" autofocus required maxlength="255" disabled="disabled" style="background:#CCCCCC"> | |||
| </div> | |||
| <div class="inline required field"> | |||
| <label>代码存放路径</label> | |||
| <input name="code_path" id="cloudbrain_code_path" value="{{.code_path}}" tabindex="3" autofocus required maxlength="255" disabled="disabled" style="background:#CCCCCC"> | |||
| </div> | |||
| <div class="inline required field"> | |||
| <label>启动命令</label> | |||
| <textarea name="command" rows="10">{{.command}}</textarea> | |||
| <textarea name="command" rows="10" disabled="disabled" style="background:#CCCCCC">{{.command}}</textarea> | |||
| </div> | |||
| <div class="inline field"> | |||
| <label></label> | |||
| <button class="ui green button"> | |||
| {{.i18n.Tr "repo.cloudbrain.new"}} | |||
| </button> | |||
| <a class="ui button" href="/">Cancel</a> | |||
| <a class="ui button" href="/">{{.i18n.Tr "repo.cloudbrain.cancel"}}</a> | |||
| </div> | |||
| </div> | |||
| </form> | |||