@@ -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> | |||