| @@ -1080,3 +1080,8 @@ BASE_PATH = attachment/ | |||||
| [modelarts] | [modelarts] | ||||
| ENDPOINT = 112.95.163.80 | ENDPOINT = 112.95.163.80 | ||||
| PROJECT_ID = edfccf24aace4e17a56da6bcbb55a5aa | |||||
| PROJECT_NAME = cn-south-222_test | |||||
| USERNAME = test1 | |||||
| PASSWORD = Qizhi@test. | |||||
| DOMAIN = cn-south-222 | |||||
| @@ -14,6 +14,7 @@ import ( | |||||
| type CloudbrainStatus string | type CloudbrainStatus string | ||||
| type JobType string | type JobType string | ||||
| type ModelArtsJobStatus string | |||||
| const ( | const ( | ||||
| JobWaiting CloudbrainStatus = "WAITING" | JobWaiting CloudbrainStatus = "WAITING" | ||||
| @@ -24,6 +25,22 @@ const ( | |||||
| JobTypeDebug JobType = "DEBUG" | JobTypeDebug JobType = "DEBUG" | ||||
| JobTypeBenchmark JobType = "BENCHMARK" | JobTypeBenchmark JobType = "BENCHMARK" | ||||
| ModelArtsCreateQueue ModelArtsJobStatus = "CREATE_QUEUING" //免费资源创建排队中 | |||||
| ModelArtsCreating ModelArtsJobStatus = "CREATING" //创建中 | |||||
| ModelArtsCreateFailed ModelArtsJobStatus = "CREATE_FAILED" //创建失败 | |||||
| ModelArtsStartQueuing ModelArtsJobStatus = "START_QUEUING" //免费资源启动排队中 | |||||
| ModelArtsReadyToStart ModelArtsJobStatus = "READY_TO_START" //免费资源等待启动 | |||||
| ModelArtsStarting ModelArtsJobStatus = "STARTING" //启动中 | |||||
| ModelArtsRestarting ModelArtsJobStatus = "RESTARTING" //重启中 | |||||
| ModelArtsStartFailed ModelArtsJobStatus = "START_FAILED" //启动失败 | |||||
| ModelArtsRunning ModelArtsJobStatus = "RUNNING" //运行中 | |||||
| ModelArtsStopping ModelArtsJobStatus = "STOPPING" //停止中 | |||||
| ModelArtsStopped ModelArtsJobStatus = "STOPPED" //停止 | |||||
| ModelArtsUnavailable ModelArtsJobStatus = "UNAVAILABLE" //故障 | |||||
| ModelArtsDeleted ModelArtsJobStatus = "DELETED" //已删除 | |||||
| ModelArtsResizing ModelArtsJobStatus = "RESIZING" //规格变更中 | |||||
| ModelArtsResizFailed ModelArtsJobStatus = "RESIZE_FAILED" //规格变更失败 | |||||
| ) | ) | ||||
| type Cloudbrain struct { | type Cloudbrain struct { | ||||
| @@ -41,6 +58,7 @@ type Cloudbrain struct { | |||||
| UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` | UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` | ||||
| DeletedAt time.Time `xorm:"deleted"` | DeletedAt time.Time `xorm:"deleted"` | ||||
| CanDebug bool `xorm:"-"` | CanDebug bool `xorm:"-"` | ||||
| Type int `xorm:"INDEX DEFAULT 0"` | |||||
| User *User `xorm:"-"` | User *User `xorm:"-"` | ||||
| Repo *Repository `xorm:"-"` | Repo *Repository `xorm:"-"` | ||||
| @@ -112,6 +130,7 @@ type CloudbrainsOptions struct { | |||||
| SortType string | SortType string | ||||
| CloudbrainIDs []int64 | CloudbrainIDs []int64 | ||||
| // JobStatus CloudbrainStatus | // JobStatus CloudbrainStatus | ||||
| Type int | |||||
| } | } | ||||
| type TaskPod struct { | type TaskPod struct { | ||||
| TaskRoleStatus struct { | TaskRoleStatus struct { | ||||
| @@ -257,6 +276,137 @@ type StopJobResult struct { | |||||
| Msg string `json:"msg"` | Msg string `json:"msg"` | ||||
| } | } | ||||
| type CreateNotebookParams struct { | |||||
| JobName string `json:"name"` | |||||
| Description string `json:"description"` | |||||
| ProfileID string `json:"profile_id"` | |||||
| Flavor string `json:"flavor"` | |||||
| Spec Spec `json:"spec"` | |||||
| Workspace Workspace `json:"workspace"` | |||||
| } | |||||
| type Workspace struct { | |||||
| ID string `json:"id"` | |||||
| } | |||||
| type Spec struct { | |||||
| Storage Storage `json:"storage"` | |||||
| AutoStop AutoStop `json:"auto_stop"` | |||||
| } | |||||
| type AutoStop struct { | |||||
| Enable bool `json:"enable"` | |||||
| Duration int `json:"duration"` | |||||
| } | |||||
| type Storage struct { | |||||
| Type string `json:"type"` | |||||
| Location Location `json:"location"` | |||||
| } | |||||
| type Location struct { | |||||
| Path string `json:"path"` | |||||
| } | |||||
| type CreateNotebookResult struct { | |||||
| ErrorCode string `json:"error_code"` | |||||
| ErrorMsg string `json:"error_msg"` | |||||
| ID string `json:"id"` | |||||
| Name string `json:"name"` | |||||
| Description string `json:"description"` | |||||
| Status string `json:"status"` | |||||
| CreationTimestamp string `json:"creation_timestamp"` | |||||
| LatestUpdateTimestamp string `json:"latest_update_timestamp"` | |||||
| Profile struct { | |||||
| ID string `json:"id"` | |||||
| Name string `json:"name"` | |||||
| Description string `json:"description"` | |||||
| DeType string `json:"de_type"` | |||||
| FlavorType string `json:"flavor_type"` | |||||
| } `json:"profile"` | |||||
| Flavor string `json:"flavor"` | |||||
| FlavorDetails struct{ | |||||
| Name string `json:"name"` | |||||
| Status string `json:"status"` | |||||
| QueuingNum int `json:"queuing_num"` | |||||
| QueueLeftTime int `json:"queue_left_time"` //s | |||||
| Duration int `json:"duration"` //auto_stop_time s | |||||
| } `json:"flavor_details"` | |||||
| } | |||||
| type GetNotebookResult struct { | |||||
| ErrorCode string `json:"error_code"` | |||||
| ErrorMsg string `json:"error_msg"` | |||||
| ID string `json:"id"` | |||||
| Name string `json:"name"` | |||||
| Description string `json:"description"` | |||||
| Status string `json:"status"` | |||||
| CreationTimestamp string `json:"creation_timestamp"` | |||||
| LatestUpdateTimestamp string `json:"latest_update_timestamp"` | |||||
| Profile struct { | |||||
| ID string `json:"id"` | |||||
| Name string `json:"name"` | |||||
| Description string `json:"description"` | |||||
| DeType string `json:"de_type"` | |||||
| FlavorType string `json:"flavor_type"` | |||||
| } `json:"profile"` | |||||
| Flavor string `json:"flavor"` | |||||
| FlavorDetails struct{ | |||||
| Name string `json:"name"` | |||||
| Status string `json:"status"` | |||||
| QueuingNum int `json:"queuing_num"` | |||||
| QueueLeftTime int `json:"queue_left_time"` //s | |||||
| Duration int `json:"duration"` //auto_stop_time s | |||||
| } `json:"flavor_details"` | |||||
| QueuingInfo struct{ | |||||
| ID string `json:"id"` | |||||
| Name string `json:"name"` | |||||
| Flavor string `json:"flavor"` | |||||
| DeType string `json:"de_type"` | |||||
| Status string `json:"status"` | |||||
| BeginTimestamp int `json:"begin_timestamp"`//time of instance begin in queue | |||||
| RemainTime int `json:"remain_time"` //remain time of instance | |||||
| EndTimestamp int `json:"end_timestamp"` // | |||||
| Rank int `json:"rank"` //rank of instance in queue | |||||
| } `json:"queuing_info"` | |||||
| } | |||||
| type GetTokenParams struct { | |||||
| Auth Auth `json:auth` | |||||
| } | |||||
| type Auth struct { | |||||
| Identity Identity `json:identity` | |||||
| Scope Scope `json:scope` | |||||
| } | |||||
| type Scope struct { | |||||
| Project Project `json:project` | |||||
| } | |||||
| type Project struct { | |||||
| Name string `json:name` | |||||
| } | |||||
| type Identity struct { | |||||
| Methods []string `json:"methods"` | |||||
| Password Password `json:password` | |||||
| } | |||||
| type Password struct { | |||||
| User NotebookUser `json:user` | |||||
| } | |||||
| type NotebookUser struct { | |||||
| Name string `json:name` | |||||
| Password string `json:"password"` | |||||
| Domain Domain `json:domain` | |||||
| } | |||||
| type Domain struct { | |||||
| Name string `json:name` | |||||
| } | |||||
| 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() | ||||
| @@ -280,6 +430,12 @@ func Cloudbrains(opts *CloudbrainsOptions) ([]*Cloudbrain, int64, error) { | |||||
| ) | ) | ||||
| } | } | ||||
| if (opts.Type) >= 0 { | |||||
| cond = cond.And( | |||||
| builder.Eq{"cloudbrain.type": opts.Type}, | |||||
| ) | |||||
| } | |||||
| // switch opts.JobStatus { | // switch opts.JobStatus { | ||||
| // case JobWaiting: | // case JobWaiting: | ||||
| // cond.And(builder.Eq{"cloudbrain.status": int(JobWaiting)}) | // cond.And(builder.Eq{"cloudbrain.status": int(JobWaiting)}) | ||||
| @@ -2045,8 +2045,8 @@ func SyncExternalUsers(ctx context.Context, updateExisting bool) error { | |||||
| func GetBlockChainUnSuccessUsers() ([]*User, error) { | func GetBlockChainUnSuccessUsers() ([]*User, error) { | ||||
| users := make([]*User, 0, 10) | users := make([]*User, 0, 10) | ||||
| err := x.Where("public_key is null"). | |||||
| Or("private_key is null"). | |||||
| err := x.Where("public_key = ''"). | |||||
| Or("private_key = ''"). | |||||
| Find(&users) | Find(&users) | ||||
| return users, err | return users, err | ||||
| } | } | ||||
| @@ -0,0 +1,42 @@ | |||||
| // based on https://github.com/golang/go/blob/master/src/net/url/url.go | |||||
| // Copyright 2009 The Go Authors. All rights reserved. | |||||
| // Use of this source code is governed by a BSD-style | |||||
| // license that can be found in the LICENSE file. | |||||
| package core | |||||
| func shouldEscape(c byte) bool { | |||||
| if 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' || '0' <= c && c <= '9' || c == '_' || c == '-' || c == '~' || c == '.' { | |||||
| return false | |||||
| } | |||||
| return true | |||||
| } | |||||
| func escape(s string) string { | |||||
| hexCount := 0 | |||||
| for i := 0; i < len(s); i++ { | |||||
| c := s[i] | |||||
| if shouldEscape(c) { | |||||
| hexCount++ | |||||
| } | |||||
| } | |||||
| if hexCount == 0 { | |||||
| return s | |||||
| } | |||||
| t := make([]byte, len(s)+2*hexCount) | |||||
| j := 0 | |||||
| for i := 0; i < len(s); i++ { | |||||
| switch c := s[i]; { | |||||
| case shouldEscape(c): | |||||
| t[j] = '%' | |||||
| t[j+1] = "0123456789ABCDEF"[c>>4] | |||||
| t[j+2] = "0123456789ABCDEF"[c&15] | |||||
| j += 3 | |||||
| default: | |||||
| t[j] = s[i] | |||||
| j++ | |||||
| } | |||||
| } | |||||
| return string(t) | |||||
| } | |||||
| @@ -0,0 +1,208 @@ | |||||
| // HWS API Gateway Signature | |||||
| // based on https://github.com/datastream/aws/blob/master/signv4.go | |||||
| // Copyright (c) 2014, Xianjie | |||||
| package core | |||||
| import ( | |||||
| "bytes" | |||||
| "crypto/hmac" | |||||
| "crypto/sha256" | |||||
| "fmt" | |||||
| "io/ioutil" | |||||
| "net/http" | |||||
| "sort" | |||||
| "strings" | |||||
| "time" | |||||
| ) | |||||
| const ( | |||||
| BasicDateFormat = "20060102T150405Z" | |||||
| Algorithm = "SDK-HMAC-SHA256" | |||||
| HeaderXDate = "X-Sdk-Date" | |||||
| HeaderHost = "host" | |||||
| HeaderAuthorization = "Authorization" | |||||
| HeaderContentSha256 = "X-Sdk-Content-Sha256" | |||||
| ) | |||||
| func hmacsha256(key []byte, data string) ([]byte, error) { | |||||
| h := hmac.New(sha256.New, []byte(key)) | |||||
| if _, err := h.Write([]byte(data)); err != nil { | |||||
| return nil, err | |||||
| } | |||||
| return h.Sum(nil), nil | |||||
| } | |||||
| // Build a CanonicalRequest from a regular request string | |||||
| // | |||||
| // CanonicalRequest = | |||||
| // HTTPRequestMethod + '\n' + | |||||
| // CanonicalURI + '\n' + | |||||
| // CanonicalQueryString + '\n' + | |||||
| // CanonicalHeaders + '\n' + | |||||
| // SignedHeaders + '\n' + | |||||
| // HexEncode(Hash(RequestPayload)) | |||||
| func CanonicalRequest(r *http.Request, signedHeaders []string) (string, error) { | |||||
| var hexencode string | |||||
| var err error | |||||
| if hex := r.Header.Get(HeaderContentSha256); hex != "" { | |||||
| hexencode = hex | |||||
| } else { | |||||
| data, err := RequestPayload(r) | |||||
| if err != nil { | |||||
| return "", err | |||||
| } | |||||
| hexencode, err = HexEncodeSHA256Hash(data) | |||||
| if err != nil { | |||||
| return "", err | |||||
| } | |||||
| } | |||||
| return fmt.Sprintf("%s\n%s\n%s\n%s\n%s\n%s", r.Method, CanonicalURI(r), CanonicalQueryString(r), CanonicalHeaders(r, signedHeaders), strings.Join(signedHeaders, ";"), hexencode), err | |||||
| } | |||||
| // CanonicalURI returns request uri | |||||
| func CanonicalURI(r *http.Request) string { | |||||
| pattens := strings.Split(r.URL.Path, "/") | |||||
| var uri []string | |||||
| for _, v := range pattens { | |||||
| uri = append(uri, escape(v)) | |||||
| } | |||||
| urlpath := strings.Join(uri, "/") | |||||
| if len(urlpath) == 0 || urlpath[len(urlpath)-1] != '/' { | |||||
| urlpath = urlpath + "/" | |||||
| } | |||||
| return urlpath | |||||
| } | |||||
| // CanonicalQueryString | |||||
| func CanonicalQueryString(r *http.Request) string { | |||||
| var keys []string | |||||
| query := r.URL.Query() | |||||
| for key := range query { | |||||
| keys = append(keys, key) | |||||
| } | |||||
| sort.Strings(keys) | |||||
| var a []string | |||||
| for _, key := range keys { | |||||
| k := escape(key) | |||||
| sort.Strings(query[key]) | |||||
| for _, v := range query[key] { | |||||
| kv := fmt.Sprintf("%s=%s", k, escape(v)) | |||||
| a = append(a, kv) | |||||
| } | |||||
| } | |||||
| queryStr := strings.Join(a, "&") | |||||
| r.URL.RawQuery = queryStr | |||||
| return queryStr | |||||
| } | |||||
| // CanonicalHeaders | |||||
| func CanonicalHeaders(r *http.Request, signerHeaders []string) string { | |||||
| var a []string | |||||
| header := make(map[string][]string) | |||||
| for k, v := range r.Header { | |||||
| header[strings.ToLower(k)] = v | |||||
| } | |||||
| for _, key := range signerHeaders { | |||||
| value := header[key] | |||||
| if strings.EqualFold(key, HeaderHost) { | |||||
| value = []string{r.Host} | |||||
| } | |||||
| sort.Strings(value) | |||||
| for _, v := range value { | |||||
| a = append(a, key+":"+strings.TrimSpace(v)) | |||||
| } | |||||
| } | |||||
| return fmt.Sprintf("%s\n", strings.Join(a, "\n")) | |||||
| } | |||||
| // SignedHeaders | |||||
| func SignedHeaders(r *http.Request) []string { | |||||
| var a []string | |||||
| for key := range r.Header { | |||||
| a = append(a, strings.ToLower(key)) | |||||
| } | |||||
| sort.Strings(a) | |||||
| return a | |||||
| } | |||||
| // RequestPayload | |||||
| func RequestPayload(r *http.Request) ([]byte, error) { | |||||
| if r.Body == nil { | |||||
| return []byte(""), nil | |||||
| } | |||||
| b, err := ioutil.ReadAll(r.Body) | |||||
| if err != nil { | |||||
| return []byte(""), err | |||||
| } | |||||
| r.Body = ioutil.NopCloser(bytes.NewBuffer(b)) | |||||
| return b, err | |||||
| } | |||||
| // Create a "String to Sign". | |||||
| func StringToSign(canonicalRequest string, t time.Time) (string, error) { | |||||
| hash := sha256.New() | |||||
| _, err := hash.Write([]byte(canonicalRequest)) | |||||
| if err != nil { | |||||
| return "", err | |||||
| } | |||||
| return fmt.Sprintf("%s\n%s\n%x", | |||||
| Algorithm, t.UTC().Format(BasicDateFormat), hash.Sum(nil)), nil | |||||
| } | |||||
| // Create the HWS Signature. | |||||
| func SignStringToSign(stringToSign string, signingKey []byte) (string, error) { | |||||
| hm, err := hmacsha256(signingKey, stringToSign) | |||||
| return fmt.Sprintf("%x", hm), err | |||||
| } | |||||
| // HexEncodeSHA256Hash returns hexcode of sha256 | |||||
| func HexEncodeSHA256Hash(body []byte) (string, error) { | |||||
| hash := sha256.New() | |||||
| if body == nil { | |||||
| body = []byte("") | |||||
| } | |||||
| _, err := hash.Write(body) | |||||
| return fmt.Sprintf("%x", hash.Sum(nil)), err | |||||
| } | |||||
| // Get the finalized value for the "Authorization" header. The signature parameter is the output from SignStringToSign | |||||
| func AuthHeaderValue(signature, accessKey string, signedHeaders []string) string { | |||||
| return fmt.Sprintf("%s Access=%s, SignedHeaders=%s, Signature=%s", Algorithm, accessKey, strings.Join(signedHeaders, ";"), signature) | |||||
| } | |||||
| // Signature HWS meta | |||||
| type Signer struct { | |||||
| Key string | |||||
| Secret string | |||||
| } | |||||
| // SignRequest set Authorization header | |||||
| func (s *Signer) Sign(r *http.Request) error { | |||||
| var t time.Time | |||||
| var err error | |||||
| var dt string | |||||
| if dt = r.Header.Get(HeaderXDate); dt != "" { | |||||
| t, err = time.Parse(BasicDateFormat, dt) | |||||
| } | |||||
| if err != nil || dt == "" { | |||||
| t = time.Now() | |||||
| r.Header.Set(HeaderXDate, t.UTC().Format(BasicDateFormat)) | |||||
| } | |||||
| signedHeaders := SignedHeaders(r) | |||||
| canonicalRequest, err := CanonicalRequest(r, signedHeaders) | |||||
| if err != nil { | |||||
| return err | |||||
| } | |||||
| stringToSign, err := StringToSign(canonicalRequest, t) | |||||
| if err != nil { | |||||
| return err | |||||
| } | |||||
| signature, err := SignStringToSign(stringToSign, []byte(s.Secret)) | |||||
| if err != nil { | |||||
| return err | |||||
| } | |||||
| authValue := AuthHeaderValue(signature, s.Key, signedHeaders) | |||||
| r.Header.Set(HeaderAuthorization, authValue) | |||||
| return nil | |||||
| } | |||||
| @@ -5,7 +5,6 @@ import ( | |||||
| "gitea.com/macaron/macaron" | "gitea.com/macaron/macaron" | ||||
| ) | ) | ||||
| // CreateDatasetForm form for dataset page | |||||
| type CreateCloudBrainForm struct { | type CreateCloudBrainForm struct { | ||||
| JobName string `form:"job_name" binding:"Required"` | JobName string `form:"job_name" binding:"Required"` | ||||
| Image string `form:"image" binding:"Required"` | Image string `form:"image" binding:"Required"` | ||||
| @@ -0,0 +1,18 @@ | |||||
| package auth | |||||
| import ( | |||||
| "gitea.com/macaron/binding" | |||||
| "gitea.com/macaron/macaron" | |||||
| ) | |||||
| type CreateModelArtsForm struct { | |||||
| JobName string `form:"job_name" binding:"Required"` | |||||
| Image string `form:"image" binding:"Required"` | |||||
| Command string `form:"command" binding:"Required"` | |||||
| Attachment string `form:"attachment" binding:"Required"` | |||||
| JobType string `form:"job_type" binding:"Required"` | |||||
| } | |||||
| func (f *CreateModelArtsForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { | |||||
| return validate(errs, ctx.Data, f, ctx.Locale) | |||||
| } | |||||
| @@ -98,6 +98,7 @@ func GenerateTask(ctx *context.Context, jobName, image, command, uuid, codePath, | |||||
| JobName: jobName, | JobName: jobName, | ||||
| SubTaskName: SubTaskName, | SubTaskName: SubTaskName, | ||||
| JobType: jobType, | JobType: jobType, | ||||
| Type: models.TypeCloudBrainOne, | |||||
| }) | }) | ||||
| if err != nil { | if err != nil { | ||||
| @@ -0,0 +1,65 @@ | |||||
| package modelarts | |||||
| import ( | |||||
| "code.gitea.io/gitea/modules/setting" | |||||
| "path" | |||||
| "code.gitea.io/gitea/models" | |||||
| "code.gitea.io/gitea/modules/context" | |||||
| "code.gitea.io/gitea/modules/log" | |||||
| ) | |||||
| const ( | |||||
| storageTypeOBS = "obs" | |||||
| autoStopDuration = 4 * 60 *60 | |||||
| flavor = "modelarts.kat1.xlarge" | |||||
| profileID = "Python3-ascend910-arm" | |||||
| subTaskName = "task1" | |||||
| DataSetMountPath = "/home/ma-user/work" | |||||
| ) | |||||
| func GenerateTask(ctx *context.Context, jobName, uuid string) error { | |||||
| dataActualPath := setting.Bucket + "/" + setting.BasePath + path.Join(uuid[0:1], uuid[1:2]) + "/" | |||||
| jobResult, err := CreateJob(models.CreateNotebookParams{ | |||||
| JobName: jobName, | |||||
| Description:"", | |||||
| ProfileID: profileID, | |||||
| Flavor: flavor, | |||||
| Spec: models.Spec{ | |||||
| Storage: models.Storage{ | |||||
| Type: storageTypeOBS, | |||||
| Location:models.Location{ | |||||
| Path: dataActualPath, | |||||
| }, | |||||
| }, | |||||
| AutoStop: models.AutoStop{ | |||||
| Enable: true, | |||||
| Duration: autoStopDuration, | |||||
| }, | |||||
| }, | |||||
| }) | |||||
| if err != nil { | |||||
| log.Error("CreateJob failed:", err.Error()) | |||||
| return err | |||||
| } | |||||
| err = models.CreateCloudbrain(&models.Cloudbrain{ | |||||
| Status: string(models.JobWaiting), | |||||
| UserID: ctx.User.ID, | |||||
| RepoID: ctx.Repo.Repository.ID, | |||||
| JobID: jobResult.ID, | |||||
| JobName: jobName, | |||||
| SubTaskName: subTaskName, | |||||
| JobType: string(models.JobTypeDebug), | |||||
| Type: models.TypeCloudBrainTwo, | |||||
| }) | |||||
| if err != nil { | |||||
| return err | |||||
| } | |||||
| return nil | |||||
| } | |||||
| @@ -0,0 +1,240 @@ | |||||
| package modelarts | |||||
| import ( | |||||
| "code.gitea.io/gitea/modules/log" | |||||
| "fmt" | |||||
| "net/http" | |||||
| "code.gitea.io/gitea/models" | |||||
| "code.gitea.io/gitea/modules/setting" | |||||
| "github.com/go-resty/resty/v2" | |||||
| ) | |||||
| var ( | |||||
| restyClient *resty.Client | |||||
| HOST string | |||||
| TOKEN string | |||||
| ) | |||||
| const ( | |||||
| methodPassword = "password" | |||||
| urlGetToken = "/v3/auth/tokens" | |||||
| urlNotebook = "/demanager/instances" | |||||
| urlQuaryNotebook = "/demanager/instances" | |||||
| ) | |||||
| func getRestyClient() *resty.Client { | |||||
| if restyClient == nil { | |||||
| restyClient = resty.New() | |||||
| } | |||||
| return restyClient | |||||
| } | |||||
| func checkSetting() { | |||||
| if len(HOST) != 0 && len(TOKEN) != 0 && restyClient != nil { | |||||
| return | |||||
| } | |||||
| getToken() | |||||
| } | |||||
| func getToken() error { | |||||
| HOST = setting.ModelArtsHost | |||||
| client := getRestyClient() | |||||
| params := models.GetTokenParams{ | |||||
| Auth: models.Auth{ | |||||
| Identity: models.Identity{ | |||||
| Methods: []string{methodPassword}, | |||||
| Password: models.Password{ | |||||
| User: models.NotebookUser{ | |||||
| Name: setting.ModelArtsUsername, | |||||
| Password: setting.ModelArtsPassword, | |||||
| Domain: models.Domain{ | |||||
| Name: setting.ModelArtsDomain, | |||||
| }, | |||||
| }, | |||||
| }, | |||||
| }, | |||||
| Scope: models.Scope{ | |||||
| Project: models.Project{ | |||||
| Name: setting.ProjectName, | |||||
| }, | |||||
| }, | |||||
| }, | |||||
| } | |||||
| res, err := client.R(). | |||||
| SetHeader("Content-Type", "application/json"). | |||||
| SetBody(params). | |||||
| Post(HOST + urlGetToken) | |||||
| if err != nil { | |||||
| return fmt.Errorf("resty getToken: %s", err) | |||||
| } | |||||
| if res.StatusCode() != http.StatusCreated { | |||||
| return fmt.Errorf("getToken failed:%s", res.String()) | |||||
| } | |||||
| TOKEN = res.Header().Get("X-Subject-Token") | |||||
| return nil | |||||
| } | |||||
| func CreateJob(createJobParams models.CreateNotebookParams) (*models.CreateNotebookResult, error) { | |||||
| checkSetting() | |||||
| client := getRestyClient() | |||||
| var result models.CreateNotebookResult | |||||
| retry := 0 | |||||
| sendjob: | |||||
| res, err := client.R(). | |||||
| SetHeader("Content-Type", "application/json"). | |||||
| SetAuthToken(TOKEN). | |||||
| SetBody(createJobParams). | |||||
| SetResult(&result). | |||||
| Post(HOST + "/v1/" + setting.ProjectID + urlNotebook) | |||||
| if err != nil { | |||||
| return nil, fmt.Errorf("resty create job: %s", err) | |||||
| } | |||||
| if res.StatusCode() == http.StatusUnauthorized && retry < 1 { | |||||
| retry++ | |||||
| _ = getToken() | |||||
| goto sendjob | |||||
| } | |||||
| if len(result.ErrorCode) != 0 { | |||||
| log.Error("CreateJob failed(%s): %s", result.ErrorCode, result.ErrorMsg) | |||||
| return &result, fmt.Errorf("CreateJob failed(%s): %s", result.ErrorCode, result.ErrorMsg) | |||||
| } | |||||
| return &result, nil | |||||
| } | |||||
| func GetJob(jobID string) (*models.GetNotebookResult, error) { | |||||
| checkSetting() | |||||
| client := getRestyClient() | |||||
| var result models.GetNotebookResult | |||||
| retry := 0 | |||||
| sendjob: | |||||
| res, err := client.R(). | |||||
| SetHeader("Content-Type", "application/json"). | |||||
| SetAuthToken(TOKEN). | |||||
| SetResult(&result). | |||||
| Get(HOST + "/v1/" + setting.ProjectID + urlNotebook + jobID) | |||||
| if err != nil { | |||||
| return nil, fmt.Errorf("resty GetJob: %v", err) | |||||
| } | |||||
| if res.StatusCode() == http.StatusUnauthorized && retry < 1 { | |||||
| retry++ | |||||
| _ = getToken() | |||||
| goto sendjob | |||||
| } | |||||
| if len(result.ErrorCode) != 0 { | |||||
| return &result, fmt.Errorf("CreateJob failed(%s): %s", result.ErrorCode, result.ErrorMsg) | |||||
| } | |||||
| return &result, 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 GetImages: %v", err) | |||||
| } | |||||
| if res.StatusCode() == http.StatusUnauthorized && retry < 1 { | |||||
| retry++ | |||||
| _ = getToken() | |||||
| goto sendjob | |||||
| } | |||||
| //if len(result.ErrorCode) != 0 { | |||||
| // return &result, fmt.Errorf("CreateJob failed(%s): %s", result.ErrorCode, result.ErrorMsg) | |||||
| //} | |||||
| return &getImagesResult, nil | |||||
| } | |||||
| func CommitImage(jobID string, params models.CommitImageParams) error { | |||||
| checkSetting() | |||||
| client := getRestyClient() | |||||
| var result models.CommitImageResult | |||||
| retry := 0 | |||||
| sendjob: | |||||
| res, err := client.R(). | |||||
| SetHeader("Content-Type", "application/json"). | |||||
| SetAuthToken(TOKEN). | |||||
| SetBody(params). | |||||
| SetResult(&result). | |||||
| Post(HOST + "/rest-server/api/v1/jobs/" + jobID + "/commitImage") | |||||
| if err != nil { | |||||
| return fmt.Errorf("resty CommitImage: %v", err) | |||||
| } | |||||
| if res.StatusCode() == http.StatusUnauthorized && retry < 1 { | |||||
| retry++ | |||||
| _ = getToken() | |||||
| goto sendjob | |||||
| } | |||||
| //if len(result.ErrorCode) != 0 { | |||||
| // return &result, fmt.Errorf("CreateJob failed(%s): %s", result.ErrorCode, result.ErrorMsg) | |||||
| //} | |||||
| return nil | |||||
| } | |||||
| func StopJob(jobID string) error { | |||||
| checkSetting() | |||||
| client := getRestyClient() | |||||
| var result models.StopJobResult | |||||
| retry := 0 | |||||
| sendjob: | |||||
| res, err := client.R(). | |||||
| SetHeader("Content-Type", "application/json"). | |||||
| SetAuthToken(TOKEN). | |||||
| SetResult(&result). | |||||
| Delete(HOST + "/rest-server/api/v1/jobs/" + jobID) | |||||
| if err != nil { | |||||
| return fmt.Errorf("resty StopJob: %v", err) | |||||
| } | |||||
| if res.StatusCode() == http.StatusUnauthorized && retry < 1 { | |||||
| retry++ | |||||
| _ = getToken() | |||||
| goto sendjob | |||||
| } | |||||
| //if len(result.ErrorCode) != 0 { | |||||
| // return &result, fmt.Errorf("CreateJob failed(%s): %s", result.ErrorCode, result.ErrorMsg) | |||||
| //} | |||||
| return nil | |||||
| } | |||||
| @@ -456,6 +456,14 @@ var ( | |||||
| Location string | Location string | ||||
| BasePath string | BasePath string | ||||
| //RealPath string | //RealPath string | ||||
| //modelarts config | |||||
| ModelArtsHost string | |||||
| ProjectID string | |||||
| ProjectName string | |||||
| ModelArtsUsername string | |||||
| ModelArtsPassword string | |||||
| ModelArtsDomain string | |||||
| ) | ) | ||||
| // DateLang transforms standard language locale name to corresponding value in datetime plugin. | // DateLang transforms standard language locale name to corresponding value in datetime plugin. | ||||
| @@ -1148,6 +1156,14 @@ func NewContext() { | |||||
| Bucket = sec.Key("BUCKET").MustString("testopendata") | Bucket = sec.Key("BUCKET").MustString("testopendata") | ||||
| Location = sec.Key("LOCATION").MustString("cn-south-222") | Location = sec.Key("LOCATION").MustString("cn-south-222") | ||||
| BasePath = sec.Key("BASE_PATH").MustString("attachment/") | BasePath = sec.Key("BASE_PATH").MustString("attachment/") | ||||
| sec = Cfg.Section("modelarts") | |||||
| ModelArtsHost = sec.Key("ENDPOINT").MustString("112.95.163.80") | |||||
| ProjectID = sec.Key("PROJECT_ID").MustString("") | |||||
| ProjectName = sec.Key("PROJECT_NAME").MustString("") | |||||
| ModelArtsUsername = sec.Key("USERNAME").MustString("") | |||||
| ModelArtsPassword = sec.Key("PASSWORD").MustString("") | |||||
| ModelArtsDomain = sec.Key("DOMAIN").MustString("cn-south-222") | |||||
| } | } | ||||
| func loadInternalToken(sec *ini.Section) string { | func loadInternalToken(sec *ini.Section) string { | ||||
| @@ -46,7 +46,7 @@ func CloudBrainIndex(ctx *context.Context) { | |||||
| PageSize: setting.UI.IssuePagingNum, | PageSize: setting.UI.IssuePagingNum, | ||||
| }, | }, | ||||
| RepoID: repo.ID, | RepoID: repo.ID, | ||||
| // SortType: sortType, | |||||
| Type: models.TypeCloudBrainOne, | |||||
| }) | }) | ||||
| if err != nil { | if err != nil { | ||||
| ctx.ServerError("Cloudbrain", err) | ctx.ServerError("Cloudbrain", err) | ||||
| @@ -0,0 +1,250 @@ | |||||
| package repo | |||||
| import ( | |||||
| "code.gitea.io/gitea/modules/modelarts" | |||||
| "errors" | |||||
| "strconv" | |||||
| "time" | |||||
| "code.gitea.io/gitea/models" | |||||
| "code.gitea.io/gitea/modules/auth" | |||||
| "code.gitea.io/gitea/modules/base" | |||||
| "code.gitea.io/gitea/modules/cloudbrain" | |||||
| "code.gitea.io/gitea/modules/context" | |||||
| "code.gitea.io/gitea/modules/log" | |||||
| "code.gitea.io/gitea/modules/setting" | |||||
| ) | |||||
| const ( | |||||
| tplModelArtsIndex base.TplName = "repo/modelarts/index" | |||||
| tplModelArtsNew base.TplName = "repo/modelarts/new" | |||||
| tplModelArtsShow base.TplName = "repo/modelarts/show" | |||||
| ) | |||||
| // MustEnableDataset check if repository enable internal cb | |||||
| func MustEnableModelArts(ctx *context.Context) { | |||||
| if !ctx.Repo.CanRead(models.UnitTypeCloudBrain) { | |||||
| ctx.NotFound("MustEnableCloudbrain", nil) | |||||
| return | |||||
| } | |||||
| } | |||||
| func ModelArtsIndex(ctx *context.Context) { | |||||
| MustEnableCloudbrain(ctx) | |||||
| repo := ctx.Repo.Repository | |||||
| page := ctx.QueryInt("page") | |||||
| if page <= 0 { | |||||
| page = 1 | |||||
| } | |||||
| ciTasks, count, err := models.Cloudbrains(&models.CloudbrainsOptions{ | |||||
| ListOptions: models.ListOptions{ | |||||
| Page: page, | |||||
| PageSize: setting.UI.IssuePagingNum, | |||||
| }, | |||||
| RepoID: repo.ID, | |||||
| // SortType: sortType, | |||||
| }) | |||||
| if err != nil { | |||||
| ctx.ServerError("Cloudbrain", err) | |||||
| return | |||||
| } | |||||
| timestamp := time.Now().Unix() | |||||
| for i, task := range ciTasks { | |||||
| if task.Status == string(models.JobRunning) && (timestamp-int64(task.CreatedUnix) > 30) { | |||||
| ciTasks[i].CanDebug = true | |||||
| } else { | |||||
| ciTasks[i].CanDebug = false | |||||
| } | |||||
| } | |||||
| pager := context.NewPagination(int(count), setting.UI.IssuePagingNum, page, 5) | |||||
| pager.SetDefaultParams(ctx) | |||||
| ctx.Data["Page"] = pager | |||||
| ctx.Data["PageIsCloudBrain"] = true | |||||
| ctx.Data["Tasks"] = ciTasks | |||||
| ctx.HTML(200, tplCloudBrainIndex) | |||||
| } | |||||
| func ModelArtsNew(ctx *context.Context) { | |||||
| ctx.Data["PageIsCloudBrain"] = true | |||||
| t := time.Now() | |||||
| var jobName = cutString(ctx.User.Name, 5) + t.Format("2006010215") + strconv.Itoa(int(t.Unix()))[5:] | |||||
| ctx.Data["job_name"] = jobName | |||||
| //attachs, err := models.GetAllUserAttachments(ctx.User.ID) | |||||
| //if err != nil { | |||||
| // ctx.ServerError("GetAllUserAttachments failed:", err) | |||||
| // return | |||||
| //} | |||||
| //ctx.Data["attachments"] = attachs | |||||
| //ctx.Data["command"] = cloudbrain.Command | |||||
| //ctx.Data["code_path"] = cloudbrain.CodeMountPath | |||||
| ctx.Data["dataset_path"] = modelarts.DataSetMountPath | |||||
| //ctx.Data["model_path"] = cloudbrain.ModelMountPath | |||||
| ctx.HTML(200, tplModelArtsNew) | |||||
| } | |||||
| func ModelArtsCreate(ctx *context.Context, form auth.CreateCloudBrainForm) { | |||||
| ctx.Data["PageIsCloudBrain"] = true | |||||
| jobName := form.JobName | |||||
| uuid := form.Attachment | |||||
| //repo := ctx.Repo.Repository | |||||
| err := modelarts.GenerateTask(ctx, jobName, uuid) | |||||
| if err != nil { | |||||
| ctx.RenderWithErr(err.Error(), tplCloudBrainNew, &form) | |||||
| return | |||||
| } | |||||
| ctx.Redirect(setting.AppSubURL + ctx.Repo.RepoLink + "/modelarts") | |||||
| } | |||||
| func ModelArtsShow(ctx *context.Context) { | |||||
| ctx.Data["PageIsCloudBrain"] = true | |||||
| var jobID = ctx.Params(":jobid") | |||||
| task, err := models.GetCloudbrainByJobID(jobID) | |||||
| if err != nil { | |||||
| ctx.Data["error"] = err.Error() | |||||
| } | |||||
| //result, err := modelarts.GetJob(jobID) | |||||
| result, err := cloudbrain.GetJob(jobID) | |||||
| if err != nil { | |||||
| ctx.Data["error"] = err.Error() | |||||
| } | |||||
| if result != nil { | |||||
| jobRes, _ := models.ConvertToJobResultPayload(result.Payload) | |||||
| ctx.Data["result"] = jobRes | |||||
| taskRoles := jobRes.TaskRoles | |||||
| taskRes, _ := models.ConvertToTaskPod(taskRoles[cloudbrain.SubTaskName].(map[string]interface{})) | |||||
| ctx.Data["taskRes"] = taskRes | |||||
| task.Status = taskRes.TaskStatuses[0].State | |||||
| task.ContainerID = taskRes.TaskStatuses[0].ContainerID | |||||
| task.ContainerIp = taskRes.TaskStatuses[0].ContainerIP | |||||
| err = models.UpdateJob(task) | |||||
| if err != nil { | |||||
| ctx.Data["error"] = err.Error() | |||||
| } | |||||
| } | |||||
| ctx.Data["task"] = task | |||||
| ctx.Data["jobID"] = jobID | |||||
| ctx.HTML(200, tplCloudBrainShow) | |||||
| } | |||||
| func ModelArtsDebug(ctx *context.Context) { | |||||
| var jobID = ctx.Params(":jobid") | |||||
| task, err := models.GetCloudbrainByJobID(jobID) | |||||
| if err != nil { | |||||
| ctx.ServerError("GetCloudbrainByJobID failed", err) | |||||
| return | |||||
| } | |||||
| //https://console.ai.pcl.cn/modelarts/internal/hub/notebook/user/DE-afcdf674-6489-11eb-bfe7-0255ac100057/lab | |||||
| //debugUrl := setting.DebugServerHost + "jpylab_" + task.JobID + "_" + task.SubTaskName | |||||
| debugUrl := "https://console.ai.pcl.cn/modelarts/internal/hub/notebook/user/" + task.JobID + "/lab" | |||||
| ctx.Redirect(debugUrl) | |||||
| } | |||||
| func ModelArtsCommitImage(ctx *context.Context, form auth.CommitImageCloudBrainForm) { | |||||
| var jobID = ctx.Params(":jobid") | |||||
| task, err := models.GetCloudbrainByJobID(jobID) | |||||
| if err != nil { | |||||
| ctx.JSON(200, map[string]string{ | |||||
| "result_code": "-1", | |||||
| "error_msg": "GetCloudbrainByJobID failed", | |||||
| }) | |||||
| return | |||||
| } | |||||
| err = cloudbrain.CommitImage(jobID, models.CommitImageParams{ | |||||
| Ip: task.ContainerIp, | |||||
| TaskContainerId: task.ContainerID, | |||||
| ImageDescription: form.Description, | |||||
| ImageTag: form.Tag, | |||||
| }) | |||||
| if err != nil { | |||||
| log.Error("CommitImage(%s) failed:", task.JobName, err.Error()) | |||||
| ctx.JSON(200, map[string]string{ | |||||
| "result_code": "-1", | |||||
| "error_msg": "CommitImage failed", | |||||
| }) | |||||
| return | |||||
| } | |||||
| ctx.JSON(200, map[string]string{ | |||||
| "result_code": "0", | |||||
| "error_msg": "", | |||||
| }) | |||||
| } | |||||
| func ModelArtsStop(ctx *context.Context) { | |||||
| var jobID = ctx.Params(":jobid") | |||||
| log.Info(jobID) | |||||
| task, err := models.GetCloudbrainByJobID(jobID) | |||||
| if err != nil { | |||||
| ctx.ServerError("GetCloudbrainByJobID failed", err) | |||||
| return | |||||
| } | |||||
| if task.Status != string(models.JobRunning) { | |||||
| log.Error("the job(%s) is not running", task.JobName) | |||||
| ctx.ServerError("the job is not running", errors.New("the job is not running")) | |||||
| return | |||||
| } | |||||
| err = cloudbrain.StopJob(jobID) | |||||
| if err != nil { | |||||
| log.Error("StopJob(%s) failed:%v", task.JobName, err.Error()) | |||||
| ctx.ServerError("StopJob failed", err) | |||||
| return | |||||
| } | |||||
| task.Status = string(models.JobStopped) | |||||
| err = models.UpdateJob(task) | |||||
| if err != nil { | |||||
| ctx.ServerError("UpdateJob failed", err) | |||||
| return | |||||
| } | |||||
| ctx.Redirect(setting.AppSubURL + ctx.Repo.RepoLink + "/cloudbrain") | |||||
| } | |||||
| func ModelArtsDel(ctx *context.Context) { | |||||
| var jobID = ctx.Params(":jobid") | |||||
| task, err := models.GetCloudbrainByJobID(jobID) | |||||
| if err != nil { | |||||
| ctx.ServerError("GetCloudbrainByJobID failed", err) | |||||
| return | |||||
| } | |||||
| if task.Status != string(models.JobStopped) { | |||||
| log.Error("the job(%s) has not been stopped", task.JobName) | |||||
| ctx.ServerError("the job has not been stopped", errors.New("the job has not been stopped")) | |||||
| return | |||||
| } | |||||
| err = models.DeleteJob(task) | |||||
| if err != nil { | |||||
| ctx.ServerError("DeleteJob failed", err) | |||||
| return | |||||
| } | |||||
| ctx.Redirect(setting.AppSubURL + ctx.Repo.RepoLink + "/cloudbrain") | |||||
| } | |||||
| func ModelArtsBenchmark(ctx *context.Context) { | |||||
| var jobID = ctx.Params(":jobid") | |||||
| _, err := models.GetCloudbrainByJobID(jobID) | |||||
| if err != nil { | |||||
| ctx.ServerError("GetCloudbrainByJobID failed", err) | |||||
| return | |||||
| } | |||||
| ctx.Redirect(setting.BenchmarkServerHost) | |||||
| } | |||||
| @@ -915,6 +915,18 @@ func RegisterRoutes(m *macaron.Macaron) { | |||||
| m.Post("/create", reqRepoCloudBrainWriter, bindIgnErr(auth.CreateCloudBrainForm{}), repo.CloudBrainCreate) | m.Post("/create", reqRepoCloudBrainWriter, bindIgnErr(auth.CreateCloudBrainForm{}), repo.CloudBrainCreate) | ||||
| }, context.RepoRef()) | }, context.RepoRef()) | ||||
| m.Group("/modelarts", func() { | |||||
| m.Get("", reqRepoCloudBrainReader, repo.ModelArtsIndex) | |||||
| m.Group("/:jobid", func() { | |||||
| m.Get("", reqRepoCloudBrainReader, repo.ModelArtsShow) | |||||
| m.Get("/debug", reqRepoCloudBrainReader, repo.ModelArtsDebug) | |||||
| //m.Post("/stop", reqRepoCloudBrainWriter, repo.CloudBrainStop) | |||||
| //m.Post("/del", reqRepoCloudBrainWriter, repo.CloudBrainDel) | |||||
| }) | |||||
| m.Get("/create", reqRepoCloudBrainWriter, repo.ModelArtsNew) | |||||
| m.Post("/create", reqRepoCloudBrainWriter, bindIgnErr(auth.CreateModelArtsForm{}), repo.ModelArtsCreate) | |||||
| }, context.RepoRef()) | |||||
| m.Group("/blockchain", func() { | m.Group("/blockchain", func() { | ||||
| m.Get("", repo.BlockChainIndex) | m.Get("", repo.BlockChainIndex) | ||||
| }, context.RepoRef()) | }, context.RepoRef()) | ||||