@@ -1,6 +1,7 @@ | |||||
package models | package models | ||||
import ( | import ( | ||||
"code.gitea.io/gitea/modules/log" | |||||
"encoding/json" | "encoding/json" | ||||
"errors" | "errors" | ||||
"fmt" | "fmt" | ||||
@@ -27,6 +28,7 @@ type Cloudbrain struct { | |||||
Status string `xorm:"INDEX"` | Status string `xorm:"INDEX"` | ||||
UserID int64 `xorm:"INDEX"` | UserID int64 `xorm:"INDEX"` | ||||
RepoID int64 `xorm:"INDEX"` | RepoID int64 `xorm:"INDEX"` | ||||
SubTaskName string `xorm:"INDEX"` | |||||
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` | CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` | ||||
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` | UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` | ||||
@@ -52,6 +54,17 @@ type TaskRole struct { | |||||
Command string `json:"command"` | Command string `json:"command"` | ||||
NeedIBDevice bool `json:"needIBDevice"` | NeedIBDevice bool `json:"needIBDevice"` | ||||
IsMainRole bool `json:"isMainRole"` | 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 { | type CreateJobParams struct { | ||||
@@ -60,6 +73,7 @@ type CreateJobParams struct { | |||||
GpuType string `json:"gpuType"` | GpuType string `json:"gpuType"` | ||||
Image string `json:"image"` | Image string `json:"image"` | ||||
TaskRoles []TaskRole `json:"taskRoles"` | TaskRoles []TaskRole `json:"taskRoles"` | ||||
Volumes []Volume `json:"volumes"` | |||||
} | } | ||||
type CreateJobResult struct { | type CreateJobResult struct { | ||||
@@ -74,6 +88,12 @@ type GetJobResult struct { | |||||
Payload map[string]interface{} `json:"payload"` | 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 { | type CloudbrainsOptions struct { | ||||
ListOptions | ListOptions | ||||
RepoID int64 // include all repos if empty | RepoID int64 // include all repos if empty | ||||
@@ -173,6 +193,36 @@ func ConvertToJobResultPayload(input map[string]interface{}) (JobResultPayload, | |||||
return jobResultPayload, err | 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) { | func Cloudbrains(opts *CloudbrainsOptions) ([]*Cloudbrain, int64, error) { | ||||
sess := x.NewSession() | sess := x.NewSession() | ||||
defer sess.Close() | defer sess.Close() | ||||
@@ -3,20 +3,32 @@ package cloudbrain | |||||
import ( | import ( | ||||
"errors" | "errors" | ||||
"code.gitea.io/gitea/models" | |||||
"code.gitea.io/gitea/modules/context" | "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 { | func GenerateTask(ctx *context.Context, jobName, image, command string) error { | ||||
jobResult, err := CreateJob(jobName, models.CreateJobParams{ | jobResult, err := CreateJob(jobName, models.CreateJobParams{ | ||||
JobName: jobName, | JobName: jobName, | ||||
RetryCount: 1, | RetryCount: 1, | ||||
GpuType: "dgx", | |||||
GpuType: DebugGPUType, | |||||
Image: image, | Image: image, | ||||
TaskRoles: []models.TaskRole{ | TaskRoles: []models.TaskRole{ | ||||
{ | { | ||||
Name: "task1", | |||||
Name: SubTaskName, | |||||
TaskNumber: 1, | TaskNumber: 1, | ||||
MinSucceededTaskCount: 1, | MinSucceededTaskCount: 1, | ||||
MinFailedTaskCount: 1, | MinFailedTaskCount: 1, | ||||
@@ -27,6 +39,30 @@ func GenerateTask(ctx *context.Context, jobName, image, command string) error { | |||||
Command: command, | Command: command, | ||||
NeedIBDevice: false, | NeedIBDevice: false, | ||||
IsMainRole: 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 | return err | ||||
} | } | ||||
if jobResult.Code != "S000" { | if jobResult.Code != "S000" { | ||||
log.Error("CreateJob(%s) failed:%s", jobName, jobResult.Msg) | |||||
return errors.New(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, | RepoID: ctx.Repo.Repository.ID, | ||||
JobID: jobID, | JobID: jobID, | ||||
JobName: jobName, | JobName: jobName, | ||||
SubTaskName: SubTaskName, | |||||
}) | }) | ||||
if err != nil { | if err != nil { | ||||
@@ -118,3 +118,34 @@ sendjob: | |||||
return &getJobResult, nil | 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() | t := time.Now() | ||||
var jobName = cutString(ctx.User.Name, 5) + t.Format("2006010215") + strconv.Itoa(int(t.Unix()))[:5] | var jobName = cutString(ctx.User.Name, 5) + t.Format("2006010215") + strconv.Itoa(int(t.Unix()))[:5] | ||||
ctx.Data["job_name"] = jobName | 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) | ctx.HTML(200, tplCloudBrainNew) | ||||
} | } | ||||
@@ -35,25 +35,39 @@ | |||||
{{range .Tasks}} | {{range .Tasks}} | ||||
<div class="ui grid item"> | <div class="ui grid item"> | ||||
<div class="row"> | <div class="row"> | ||||
<div class="seven wide column"> | |||||
<div class="five wide column"> | |||||
<a class="title" href="{{$.Link}}/{{.JobID}}"> | <a class="title" href="{{$.Link}}/{{.JobID}}"> | ||||
<span class="fitted">{{svg "octicon-tasklist" 16}}</span> | <span class="fitted">{{svg "octicon-tasklist" 16}}</span> | ||||
<span class="fitted">{{.JobName}}</span> | <span class="fitted">{{.JobName}}</span> | ||||
</a> | </a> | ||||
</div> | </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}} | {{.Status}} | ||||
</div> | </div> | ||||
<div class="three wide column"> | <div class="three wide column"> | ||||
<span class="ui text center">{{svg "octicon-flame" 16}} {{TimeSinceUnix .CreatedUnix $.Lang}}</span> | <span class="ui text center">{{svg "octicon-flame" 16}} {{TimeSinceUnix .CreatedUnix $.Lang}}</span> | ||||
</div> | </div> | ||||
<div class="two wide column"> | |||||
<div class="one wide column"> | |||||
<span class="ui text center clipboard"> | <span class="ui text center clipboard"> | ||||
<a class="title" href="{{$.Link}}/{{.JobID}}"> | <a class="title" href="{{$.Link}}/{{.JobID}}"> | ||||
<span class="fitted">查看</span> | <span class="fitted">查看</span> | ||||
</a> | </a> | ||||
</span> | </span> | ||||
</div> | </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> | ||||
</div> | </div> | ||||
{{end}} | {{end}} | ||||
@@ -16,20 +16,46 @@ | |||||
<input name="job_name" id="cloudbrain_job_name" placeholder="任务名称" value="{{.job_name}}" tabindex="3" autofocus required maxlength="255"> | <input name="job_name" id="cloudbrain_job_name" placeholder="任务名称" value="{{.job_name}}" tabindex="3" autofocus required maxlength="255"> | ||||
</div> | </div> | ||||
<br> | <br> | ||||
<!--> | |||||
<div class="inline required field"> | <div class="inline required field"> | ||||
<label>镜像</label> | <label>镜像</label> | ||||
<input name="image" id="cloudbrain_image" placeholder="输入镜像" value="{{.image}}" tabindex="3" autofocus required maxlength="255"> | <input name="image" id="cloudbrain_image" placeholder="输入镜像" value="{{.image}}" tabindex="3" autofocus required maxlength="255"> | ||||
</div> | </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"> | <div class="inline required field"> | ||||
<label>启动命令</label> | <label>启动命令</label> | ||||
<textarea name="command" rows="10">{{.command}}</textarea> | |||||
<textarea name="command" rows="10" disabled="disabled" style="background:#CCCCCC">{{.command}}</textarea> | |||||
</div> | </div> | ||||
<div class="inline field"> | <div class="inline field"> | ||||
<label></label> | <label></label> | ||||
<button class="ui green button"> | <button class="ui green button"> | ||||
{{.i18n.Tr "repo.cloudbrain.new"}} | {{.i18n.Tr "repo.cloudbrain.new"}} | ||||
</button> | </button> | ||||
<a class="ui button" href="/">Cancel</a> | |||||
<a class="ui button" href="/">{{.i18n.Tr "repo.cloudbrain.cancel"}}</a> | |||||
</div> | </div> | ||||
</div> | </div> | ||||
</form> | </form> | ||||