| @@ -531,11 +531,12 @@ type ResourceSpecs struct { | |||
| } | |||
| type ResourceSpec struct { | |||
| Id int `json:"id"` | |||
| CpuNum int `json:"cpu"` | |||
| GpuNum int `json:"gpu"` | |||
| MemMiB int `json:"memMiB"` | |||
| ShareMemMiB int `json:"shareMemMiB"` | |||
| Id int `json:"id"` | |||
| CpuNum int `json:"cpu"` | |||
| GpuNum int `json:"gpu"` | |||
| MemMiB int `json:"memMiB"` | |||
| ShareMemMiB int `json:"shareMemMiB"` | |||
| UnitPrice int64 `json:"unitPrice"` | |||
| } | |||
| type FlavorInfos struct { | |||
| @@ -543,9 +544,10 @@ type FlavorInfos struct { | |||
| } | |||
| type FlavorInfo struct { | |||
| Id int `json:"id"` | |||
| Value string `json:"value"` | |||
| Desc string `json:"desc"` | |||
| Id int `json:"id"` | |||
| Value string `json:"value"` | |||
| Desc string `json:"desc"` | |||
| UnitPrice int64 `json:"unitPrice"` | |||
| } | |||
| type ImageInfosModelArts struct { | |||
| @@ -1692,3 +1694,12 @@ func CloudbrainAll(opts *CloudbrainsOptions) ([]*CloudbrainInfo, int64, error) { | |||
| return cloudbrains, count, nil | |||
| } | |||
| func GetStartedCloudbrainTaskByUpdatedUnix(startTime, endTime time.Time) ([]Cloudbrain, error) { | |||
| r := make([]Cloudbrain, 0) | |||
| err := x.Where("updated_unix >= ? and updated_unix <= ? and start_time > 0", startTime.Unix(), endTime.Unix()).Find(&r) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| return r, nil | |||
| } | |||
| @@ -148,7 +148,7 @@ func init() { | |||
| new(TaskAccomplishLog), | |||
| new(RewardOperateRecord), | |||
| new(LimitConfig), | |||
| new(PeriodicTask), | |||
| new(RewardPeriodicTask), | |||
| new(PointAccountLog), | |||
| new(PointAccount), | |||
| ) | |||
| @@ -1,28 +0,0 @@ | |||
| package models | |||
| import "code.gitea.io/gitea/modules/timeutil" | |||
| type PeriodicTaskStatus int | |||
| // Possible PeriodicTaskStatus types. | |||
| const ( | |||
| PeriodicTaskStatusRunning PointAccountStatus = iota + 1 // 1 | |||
| PeriodicTaskStatusSuccess // 2 | |||
| PeriodicTaskStatusFailed // 3 | |||
| ) | |||
| type PeriodicTask struct { | |||
| ID int64 `xorm:"pk autoincr"` | |||
| Type string `xorm:"NOT NULL"` | |||
| OperateRecordId int64 `xorm:"INDEX NOT NULL"` | |||
| IntervalSecond int64 `xorm:"NOT NULL"` | |||
| PointsAmount int64 `xorm:"NOT NULL"` | |||
| NextExecuteTime timeutil.TimeStamp | |||
| SuccessCount int `xorm:"NOT NULL default 0"` | |||
| FailedCount int `xorm:"NOT NULL default 0"` | |||
| Status string `xorm:"NOT NULL"` | |||
| ExitCode string | |||
| CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` | |||
| FinishedUnix timeutil.TimeStamp `xorm:"INDEX"` | |||
| UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` | |||
| } | |||
| @@ -6,12 +6,27 @@ import ( | |||
| "xorm.io/builder" | |||
| ) | |||
| type SourceType string | |||
| const ( | |||
| SourceTypeAccomplishTask string = "ACCOMPLISH_TASK" | |||
| SourceTypeAdminOperate = "ADMIN_OPERATE" | |||
| SourceTypeRunCloudbrainTask = "RUN_CLOUBRAIN_TASK" | |||
| SourceTypeAccomplishTask SourceType = "ACCOMPLISH_TASK" | |||
| SourceTypeAdminOperate SourceType = "ADMIN_OPERATE" | |||
| SourceTypeRunCloudbrainTask SourceType = "RUN_CLOUDBRAIN_TASK" | |||
| ) | |||
| func (r SourceType) Name() string { | |||
| switch r { | |||
| case SourceTypeAccomplishTask: | |||
| return "ACCOMPLISH_TASK" | |||
| case SourceTypeAdminOperate: | |||
| return "ADMIN_OPERATE" | |||
| case SourceTypeRunCloudbrainTask: | |||
| return "RUN_CLOUDBRAIN_TASK" | |||
| default: | |||
| return "" | |||
| } | |||
| } | |||
| type RewardType string | |||
| const ( | |||
| @@ -66,6 +81,17 @@ func (r RewardOperateType) Show() string { | |||
| } | |||
| } | |||
| func GetRewardOperateTypeInstance(s string) RewardOperateType { | |||
| switch s { | |||
| case OperateTypeIncrease.Name(): | |||
| return OperateTypeIncrease | |||
| case OperateTypeDecrease.Name(): | |||
| return OperateTypeDecrease | |||
| default: | |||
| return "" | |||
| } | |||
| } | |||
| const ( | |||
| OperateTypeIncrease RewardOperateType = "INCREASE" | |||
| OperateTypeDecrease RewardOperateType = "DECREASE" | |||
| @@ -80,20 +106,19 @@ const ( | |||
| const Semicolon = ";" | |||
| type RewardOperateRecord struct { | |||
| ID int64 `xorm:"pk autoincr"` | |||
| RecordId string `xorm:"INDEX NOT NULL"` | |||
| UserId int64 `xorm:"INDEX NOT NULL"` | |||
| Amount int64 `xorm:"NOT NULL"` | |||
| RewardType string `xorm:"NOT NULL"` | |||
| SourceType string `xorm:"NOT NULL"` | |||
| SourceId string `xorm:"INDEX NOT NULL"` | |||
| RequestId string `xorm:"INDEX NOT NULL"` | |||
| OperateType string `xorm:"NOT NULL"` | |||
| CycleIntervalSeconds int64 `xorm:"NOT NULL default 0"` | |||
| Status string `xorm:"NOT NULL"` | |||
| Remark string | |||
| CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` | |||
| UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` | |||
| ID int64 `xorm:"pk autoincr"` | |||
| RecordId string `xorm:"INDEX NOT NULL"` | |||
| UserId int64 `xorm:"INDEX NOT NULL"` | |||
| Amount int64 `xorm:"NOT NULL"` | |||
| RewardType string `xorm:"NOT NULL"` | |||
| SourceType string `xorm:"NOT NULL"` | |||
| SourceId string `xorm:"INDEX NOT NULL"` | |||
| RequestId string `xorm:"INDEX NOT NULL"` | |||
| OperateType string `xorm:"NOT NULL"` | |||
| Status string `xorm:"NOT NULL"` | |||
| Remark string | |||
| CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` | |||
| UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` | |||
| } | |||
| func getPointOperateRecord(tl *RewardOperateRecord) (*RewardOperateRecord, error) { | |||
| @@ -106,10 +131,18 @@ func getPointOperateRecord(tl *RewardOperateRecord) (*RewardOperateRecord, error | |||
| return tl, nil | |||
| } | |||
| func GetPointOperateRecordBySourceTypeAndRequestId(sourceType, requestId string) (*RewardOperateRecord, error) { | |||
| func GetPointOperateRecordBySourceTypeAndRequestId(sourceType, requestId, operateType string) (*RewardOperateRecord, error) { | |||
| t := &RewardOperateRecord{ | |||
| SourceType: sourceType, | |||
| RequestId: requestId, | |||
| OperateType: operateType, | |||
| } | |||
| return getPointOperateRecord(t) | |||
| } | |||
| func GetPointOperateRecordByRecordId(recordId string) (*RewardOperateRecord, error) { | |||
| t := &RewardOperateRecord{ | |||
| SourceType: sourceType, | |||
| RequestId: requestId, | |||
| RecordId: recordId, | |||
| } | |||
| return getPointOperateRecord(t) | |||
| } | |||
| @@ -140,14 +173,13 @@ func SumRewardAmountInTaskPeriod(rewardType string, sourceType string, userId in | |||
| } | |||
| type RewardOperateContext struct { | |||
| SourceType string | |||
| SourceId string | |||
| Remark string | |||
| Reward Reward | |||
| TargetUserId int64 | |||
| RequestId string | |||
| OperateType RewardOperateType | |||
| CycleIntervalSeconds int64 | |||
| SourceType SourceType | |||
| SourceId string | |||
| Remark string | |||
| Reward Reward | |||
| TargetUserId int64 | |||
| RequestId string | |||
| OperateType RewardOperateType | |||
| } | |||
| type Reward struct { | |||
| @@ -0,0 +1,114 @@ | |||
| package models | |||
| import ( | |||
| "code.gitea.io/gitea/modules/timeutil" | |||
| "time" | |||
| ) | |||
| type PeriodicTaskStatus int | |||
| const ( | |||
| PeriodicTaskStatusRunning = iota + 1 // 1 | |||
| PeriodicTaskStatusFinished // 2 | |||
| ) | |||
| type PeriodType string | |||
| const ( | |||
| PeriodType30MinutesFree1HourCost PeriodType = "30MF1HC" | |||
| ) | |||
| func (r PeriodType) Name() string { | |||
| switch r { | |||
| case PeriodType30MinutesFree1HourCost: | |||
| return "30MF1HC" | |||
| default: | |||
| return "" | |||
| } | |||
| } | |||
| type RewardPeriodicTask struct { | |||
| ID int64 `xorm:"pk autoincr"` | |||
| OperateRecordId string `xorm:"INDEX NOT NULL"` | |||
| DelaySeconds int64 | |||
| IntervalSeconds int64 | |||
| Amount int64 `xorm:"NOT NULL"` | |||
| NextExecuteTime timeutil.TimeStamp `xorm:"INDEX NOT NULL"` | |||
| SuccessCount int `xorm:"NOT NULL default 0"` | |||
| Status int `xorm:"NOT NULL"` | |||
| CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` | |||
| FinishedUnix timeutil.TimeStamp `xorm:"INDEX"` | |||
| UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` | |||
| } | |||
| type StartPeriodicTaskOpts struct { | |||
| SourceType SourceType | |||
| SourceId string | |||
| Remark string | |||
| TargetUserId int64 | |||
| RequestId string | |||
| OperateType RewardOperateType | |||
| Delay time.Duration | |||
| Interval time.Duration | |||
| UnitAmount int64 | |||
| RewardType RewardType | |||
| StartTime time.Time | |||
| } | |||
| func InsertPeriodicTask(tl *RewardPeriodicTask) (int64, error) { | |||
| return x.Insert(tl) | |||
| } | |||
| func GetRunningRewardTask(now time.Time) ([]RewardPeriodicTask, error) { | |||
| r := make([]RewardPeriodicTask, 0) | |||
| err := x.Where("next_execute_time <= ? and status = ?", now.Unix(), PeriodicTaskStatusRunning).Find(&r) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| return r, err | |||
| } | |||
| func IncrRewardTaskSuccessCount(t RewardPeriodicTask, count int64, nextTime timeutil.TimeStamp) error { | |||
| sess := x.NewSession() | |||
| defer sess.Close() | |||
| _, err := sess.Exec("update reward_periodic_task set success_count = success_count + ? , next_execute_time = ?, updated_unix = ? where id = ?", count, nextTime, timeutil.TimeStampNow(), t.ID) | |||
| if err != nil { | |||
| sess.Rollback() | |||
| return err | |||
| } | |||
| _, err = sess.Exec("update reward_operate_record set amount = amount + ? ,updated_unix = ? where record_id = ?", count*t.Amount, timeutil.TimeStampNow(), t.OperateRecordId) | |||
| if err != nil { | |||
| sess.Rollback() | |||
| return err | |||
| } | |||
| sess.Commit() | |||
| return nil | |||
| } | |||
| func GetPeriodicTaskBySourceIdAndType(sourceType SourceType, sourceId string, operateType RewardOperateType) (*RewardPeriodicTask, error) { | |||
| r := RewardPeriodicTask{} | |||
| _, err := x.SQL("select rpt.* from reward_periodic_task rpt "+ | |||
| "inner join reward_operate_record ror on rpt.operate_record_id = ror.record_id"+ | |||
| " where ror.source_type = ? and source_id = ? and operate_type = ? ", sourceType.Name(), sourceId, operateType.Name()).Get(&r) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| return &r, nil | |||
| } | |||
| func StopPeriodicTask(taskId int64, operateRecordId string, stopTime time.Time) error { | |||
| sess := x.NewSession() | |||
| defer sess.Close() | |||
| _, err := sess.Where("id = ? and status = ?", taskId, PeriodicTaskStatusRunning).Update(&RewardPeriodicTask{Status: PeriodicTaskStatusFinished, FinishedUnix: timeutil.TimeStamp(stopTime.Unix())}) | |||
| if err != nil { | |||
| sess.Rollback() | |||
| return err | |||
| } | |||
| _, err = sess.Where("record_id = ? and status = ?", operateRecordId, OperateStatusOperating).Update(&RewardOperateRecord{Status: OperateStatusSucceeded}) | |||
| if err != nil { | |||
| sess.Rollback() | |||
| return err | |||
| } | |||
| sess.Commit() | |||
| return nil | |||
| } | |||
| @@ -22,6 +22,7 @@ type CreateModelArtsNotebookForm struct { | |||
| Description string `form:"description"` | |||
| Flavor string `form:"flavor" binding:"Required"` | |||
| ImageId string `form:"image_id" binding:"Required"` | |||
| ResourceSpecId int `form:"resource_spec_id"` | |||
| } | |||
| func (f *CreateModelArtsNotebookForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { | |||
| @@ -46,6 +47,7 @@ type CreateModelArtsTrainJobForm struct { | |||
| VersionName string `form:"version_name" binding:"Required"` | |||
| FlavorName string `form:"flaver_names" binding:"Required"` | |||
| EngineName string `form:"engine_names" binding:"Required"` | |||
| ResourceSpecId int `form:"resource_spec_id"` | |||
| } | |||
| type CreateModelArtsInferenceJobForm struct { | |||
| @@ -71,6 +73,7 @@ type CreateModelArtsInferenceJobForm struct { | |||
| ModelName string `form:"model_name" binding:"Required"` | |||
| ModelVersion string `form:"model_version" binding:"Required"` | |||
| CkptName string `form:"ckpt_name" binding:"Required"` | |||
| ResourceSpecId int `form:"resource_spec_id"` | |||
| } | |||
| func (f *CreateModelArtsTrainJobForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { | |||
| @@ -0,0 +1,19 @@ | |||
| package context | |||
| import ( | |||
| "code.gitea.io/gitea/services/reward/point/account" | |||
| "gitea.com/macaron/macaron" | |||
| ) | |||
| // PointAccount returns a macaron to get request user's point account | |||
| func PointAccount() macaron.Handler { | |||
| return func(ctx *Context) { | |||
| a, err := account.GetAccount(ctx.User.ID) | |||
| if err != nil { | |||
| ctx.ServerError("GetPointAccount", err) | |||
| return | |||
| } | |||
| ctx.Data["PointAccount"] = a | |||
| ctx.Next() | |||
| } | |||
| } | |||
| @@ -5,6 +5,7 @@ | |||
| package cron | |||
| import ( | |||
| "code.gitea.io/gitea/services/reward" | |||
| "context" | |||
| "time" | |||
| @@ -207,6 +208,28 @@ func registerSyncCloudbrainStatus() { | |||
| }) | |||
| } | |||
| func registerRewardPeriodTask() { | |||
| RegisterTaskFatal("reward_period_task", &BaseConfig{ | |||
| Enabled: true, | |||
| RunAtStart: true, | |||
| Schedule: "@every 5m", | |||
| }, func(ctx context.Context, _ *models.User, _ Config) error { | |||
| reward.StartRewardTask() | |||
| return nil | |||
| }) | |||
| } | |||
| func registerCloudbrainPointDeductTask() { | |||
| RegisterTaskFatal("cloudbrain_point_deduct_task", &BaseConfig{ | |||
| Enabled: true, | |||
| RunAtStart: true, | |||
| Schedule: "@every 1m", | |||
| }, func(ctx context.Context, _ *models.User, _ Config) error { | |||
| reward.StartCloudbrainPointDeductTask() | |||
| return nil | |||
| }) | |||
| } | |||
| func initBasicTasks() { | |||
| registerUpdateMirrorTask() | |||
| registerRepoHealthCheck() | |||
| @@ -227,4 +250,7 @@ func initBasicTasks() { | |||
| registerSyncCloudbrainStatus() | |||
| registerHandleOrgStatistic() | |||
| registerRewardPeriodTask() | |||
| registerCloudbrainPointDeductTask() | |||
| } | |||
| @@ -96,6 +96,7 @@ type GenerateTrainJobReq struct { | |||
| VersionCount int | |||
| EngineName string | |||
| TotalVersionCount int | |||
| ResourceSpecId int | |||
| } | |||
| type GenerateInferenceJobReq struct { | |||
| @@ -127,6 +128,7 @@ type GenerateInferenceJobReq struct { | |||
| ModelVersion string | |||
| CkptName string | |||
| ResultUrl string | |||
| ResourceSpecId int | |||
| } | |||
| type VersionInfo struct { | |||
| @@ -1,11 +1,16 @@ | |||
| package redis_key | |||
| import "fmt" | |||
| const REWARD_REDIS_PREFIX = "reward" | |||
| func RewardSendLock(requestId string, sourceType string) string { | |||
| return KeyJoin(REWARD_REDIS_PREFIX, requestId, sourceType, "send") | |||
| func RewardOperateLock(requestId string, sourceType string, operateType string) string { | |||
| return KeyJoin(REWARD_REDIS_PREFIX, requestId, sourceType, operateType, "send") | |||
| } | |||
| func RewardOperateNotification() string { | |||
| return KeyJoin(REWARD_REDIS_PREFIX, "operate", "notification") | |||
| } | |||
| func RewardTaskRunningLock(taskId int64) string { | |||
| return KeyJoin(REWARD_REDIS_PREFIX, "periodic_task", fmt.Sprint(taskId), "lock") | |||
| } | |||
| @@ -548,6 +548,9 @@ var ( | |||
| WechatQRCodeExpireSeconds int | |||
| WechatAuthSwitch bool | |||
| //point config | |||
| CloudBrainTaskPointPaySwitch bool | |||
| //nginx proxy | |||
| PROXYURL string | |||
| RadarMap = struct { | |||
| @@ -1374,7 +1377,10 @@ func NewContext() { | |||
| WechatAppId = sec.Key("APP_ID").MustString("wxba77b915a305a57d") | |||
| WechatAppSecret = sec.Key("APP_SECRET").MustString("e48e13f315adc32749ddc7057585f198") | |||
| WechatQRCodeExpireSeconds = sec.Key("QR_CODE_EXPIRE_SECONDS").MustInt(120) | |||
| WechatAuthSwitch = sec.Key("AUTH_SWITCH").MustBool(true) | |||
| WechatAuthSwitch = sec.Key("AUTH_SWITCH").MustBool(false) | |||
| sec = Cfg.Section("point") | |||
| CloudBrainTaskPointPaySwitch = sec.Key("CLOUDBRAIN_PAY_SWITCH").MustBool(false) | |||
| SetRadarMapConfig() | |||
| @@ -2,6 +2,7 @@ package repo | |||
| import ( | |||
| "bufio" | |||
| "code.gitea.io/gitea/services/reward" | |||
| "encoding/json" | |||
| "errors" | |||
| "fmt" | |||
| @@ -229,6 +230,13 @@ func CloudBrainCreate(ctx *context.Context, form auth.CreateCloudBrainForm) { | |||
| command = commandTrain | |||
| } | |||
| if !reward.IsPointBalanceEnough(ctx.User.ID, jobType, resourceSpecId) { | |||
| log.Error("point balance is not enough,userId=%d jobType=%s resourceSpecId=%d", ctx.User.ID, jobType, resourceSpecId) | |||
| cloudBrainNewDataPrepare(ctx) | |||
| ctx.RenderWithErr("point balance not enough", tpl, &form) | |||
| return | |||
| } | |||
| tasks, err := models.GetCloudbrainsByDisplayJobName(repo.ID, jobType, displayJobName) | |||
| if err == nil { | |||
| if len(tasks) != 0 { | |||
| @@ -308,6 +316,13 @@ func CloudBrainRestart(ctx *context.Context) { | |||
| var status = string(models.JobWaiting) | |||
| task := ctx.Cloudbrain | |||
| for { | |||
| if !reward.IsPointBalanceEnough(ctx.User.ID, task.JobType, task.ResourceSpecId) { | |||
| log.Error("point balance is not enough,userId=%d jobType=%s resourceSpecId=%d", ctx.User.ID, task.JobType, task.ResourceSpecId) | |||
| resultCode = "-1" | |||
| errorMsg = "insufficient points balance" | |||
| break | |||
| } | |||
| if task.Status != string(models.JobStopped) && task.Status != string(models.JobSucceeded) && task.Status != string(models.JobFailed) { | |||
| log.Error("the job(%s) is not stopped", task.JobName, ctx.Data["MsgID"]) | |||
| resultCode = "-1" | |||
| @@ -842,7 +857,6 @@ func CloudBrainStop(ctx *context.Context) { | |||
| errorMsg = "system error" | |||
| break | |||
| } | |||
| status = task.Status | |||
| break | |||
| } | |||
| @@ -1845,6 +1859,13 @@ func BenchMarkAlgorithmCreate(ctx *context.Context, form auth.CreateCloudBrainFo | |||
| repo := ctx.Repo.Repository | |||
| if !reward.IsPointBalanceEnough(ctx.User.ID, string(models.JobTypeBenchmark), resourceSpecId) { | |||
| log.Error("point balance is not enough,userId=%d jobType=%s resourceSpecId=%d", ctx.User.ID, string(models.JobTypeBenchmark), resourceSpecId) | |||
| cloudBrainNewDataPrepare(ctx) | |||
| ctx.RenderWithErr("point balance not enough", tplCloudBrainBenchmarkNew, &form) | |||
| return | |||
| } | |||
| tasks, err := models.GetCloudbrainsByDisplayJobName(repo.ID, string(models.JobTypeBenchmark), displayJobName) | |||
| if err == nil { | |||
| if len(tasks) != 0 { | |||
| @@ -2000,6 +2021,13 @@ func ModelBenchmarkCreate(ctx *context.Context, form auth.CreateCloudBrainForm) | |||
| tpl := tplCloudBrainBenchmarkNew | |||
| command := cloudbrain.Command | |||
| if !reward.IsPointBalanceEnough(ctx.User.ID, jobType, resourceSpecId) { | |||
| log.Error("point balance is not enough,userId=%d jobType=%s resourceSpecId=%d", ctx.User.ID, jobType, resourceSpecId) | |||
| cloudBrainNewDataPrepare(ctx) | |||
| ctx.RenderWithErr("point balance not enough", tpl, &form) | |||
| return | |||
| } | |||
| tasks, err := models.GetCloudbrainsByDisplayJobName(repo.ID, jobType, displayJobName) | |||
| if err == nil { | |||
| if len(tasks) != 0 { | |||
| @@ -2,6 +2,7 @@ package repo | |||
| import ( | |||
| "archive/zip" | |||
| "code.gitea.io/gitea/services/reward" | |||
| "encoding/json" | |||
| "errors" | |||
| "fmt" | |||
| @@ -204,7 +205,14 @@ func Notebook2Create(ctx *context.Context, form auth.CreateModelArtsNotebookForm | |||
| flavor := form.Flavor | |||
| imageId := form.ImageId | |||
| repo := ctx.Repo.Repository | |||
| resourceSpecId := form.ResourceSpecId | |||
| if !reward.IsPointBalanceEnough(ctx.User.ID, string(models.JobTypeDebug), resourceSpecId) { | |||
| log.Error("point balance is not enough,userId=%d jobType=%s resourceSpecId=%d", ctx.User.ID, string(models.JobTypeBenchmark), resourceSpecId) | |||
| cloudBrainNewDataPrepare(ctx) | |||
| ctx.RenderWithErr("point balance not enough", tplModelArtsNotebookNew, &form) | |||
| return | |||
| } | |||
| count, err := models.GetCloudbrainNotebookCountByUserID(ctx.User.ID) | |||
| if err != nil { | |||
| log.Error("GetCloudbrainNotebookCountByUserID failed:%v", err, ctx.Data["MsgID"]) | |||
| @@ -418,6 +426,13 @@ func NotebookManage(ctx *context.Context) { | |||
| errorMsg = "you have no right to restart the job" | |||
| break | |||
| } | |||
| if !reward.IsPointBalanceEnough(ctx.User.ID, task.JobType, task.ResourceSpecId) { | |||
| log.Error("point balance is not enough,userId=%d jobType=%s resourceSpecId=%d", ctx.User.ID, task.JobType, task.ResourceSpecId) | |||
| resultCode = "-1" | |||
| errorMsg = "point balance not enough" | |||
| break | |||
| return | |||
| } | |||
| count, err := models.GetCloudbrainNotebookCountByUserID(ctx.User.ID) | |||
| if err != nil { | |||
| @@ -985,7 +1000,14 @@ func TrainJobCreate(ctx *context.Context, form auth.CreateModelArtsTrainJobForm) | |||
| FlavorName := form.FlavorName | |||
| VersionCount := modelarts.VersionCount | |||
| EngineName := form.EngineName | |||
| resourceSpecId := form.ResourceSpecId | |||
| if !reward.IsPointBalanceEnough(ctx.User.ID, string(models.JobTypeTrain), resourceSpecId) { | |||
| log.Error("point balance is not enough,userId=%d jobType=%s resourceSpecId=%d", ctx.User.ID, string(models.JobTypeBenchmark), resourceSpecId) | |||
| cloudBrainNewDataPrepare(ctx) | |||
| ctx.RenderWithErr("point balance not enough", tplModelArtsTrainJobNew, &form) | |||
| return | |||
| } | |||
| count, err := models.GetCloudbrainTrainJobCountByUserID(ctx.User.ID) | |||
| if err != nil { | |||
| log.Error("GetCloudbrainTrainJobCountByUserID failed:%v", err, ctx.Data["MsgID"]) | |||
| @@ -1161,6 +1183,7 @@ func TrainJobCreate(ctx *context.Context, form auth.CreateModelArtsTrainJobForm) | |||
| EngineName: EngineName, | |||
| VersionCount: VersionCount, | |||
| TotalVersionCount: modelarts.TotalVersionCount, | |||
| ResourceSpecId: resourceSpecId, | |||
| } | |||
| //将params转换Parameters.Parameter,出错时返回给前端 | |||
| @@ -1716,7 +1739,6 @@ func TrainJobStop(ctx *context.Context) { | |||
| ctx.RenderWithErr(err.Error(), tplModelArtsTrainJobIndex, nil) | |||
| return | |||
| } | |||
| ctx.Redirect(setting.AppSubURL + ctx.Repo.RepoLink + "/modelarts/train-job?listType=" + listType) | |||
| } | |||
| @@ -1825,9 +1847,16 @@ func InferenceJobCreate(ctx *context.Context, form auth.CreateModelArtsInference | |||
| modelName := form.ModelName | |||
| modelVersion := form.ModelVersion | |||
| ckptName := form.CkptName | |||
| resourceSpecId := form.ResourceSpecId | |||
| ckptUrl := form.TrainUrl + form.CkptName | |||
| if !reward.IsPointBalanceEnough(ctx.User.ID, string(models.JobTypeInference), resourceSpecId) { | |||
| log.Error("point balance is not enough,userId=%d jobType=%s resourceSpecId=%d", ctx.User.ID, string(models.JobTypeBenchmark), resourceSpecId) | |||
| inferenceJobErrorNewDataPrepare(ctx, form) | |||
| ctx.RenderWithErr("point balance not enough", tplModelArtsInferenceJobNew, &form) | |||
| return | |||
| } | |||
| count, err := models.GetCloudbrainInferenceJobCountByUserID(ctx.User.ID) | |||
| if err != nil { | |||
| log.Error("GetCloudbrainInferenceJobCountByUserID failed:%v", err, ctx.Data["MsgID"]) | |||
| @@ -1973,6 +2002,7 @@ func InferenceJobCreate(ctx *context.Context, form auth.CreateModelArtsInference | |||
| ModelVersion: modelVersion, | |||
| CkptName: ckptName, | |||
| ResultUrl: resultObsPath, | |||
| ResourceSpecId: resourceSpecId, | |||
| } | |||
| err = modelarts.GenerateInferenceJob(ctx, req) | |||
| @@ -1068,7 +1068,7 @@ func RegisterRoutes(m *macaron.Macaron) { | |||
| m.Get("/models", reqRepoCloudBrainReader, repo.CloudBrainShowModels) | |||
| m.Get("/download_model", cloudbrain.AdminOrJobCreaterRight, repo.CloudBrainDownloadModel) | |||
| }) | |||
| m.Get("/create", reqWechatBind, reqRepoCloudBrainWriter, repo.CloudBrainNew) | |||
| m.Get("/create", reqWechatBind, reqRepoCloudBrainWriter, context.PointAccount(), repo.CloudBrainNew) | |||
| m.Post("/create", reqWechatBind, reqRepoCloudBrainWriter, bindIgnErr(auth.CreateCloudBrainForm{}), repo.CloudBrainCreate) | |||
| m.Group("/benchmark", func() { | |||
| @@ -1079,7 +1079,7 @@ func RegisterRoutes(m *macaron.Macaron) { | |||
| m.Post("/del", cloudbrain.AdminOrOwnerOrJobCreaterRight, repo.BenchmarkDel) | |||
| m.Get("/rate", reqRepoCloudBrainReader, repo.GetRate) | |||
| }) | |||
| m.Get("/create", reqWechatBind, reqRepoCloudBrainWriter, repo.CloudBrainBenchmarkNew) | |||
| m.Get("/create", reqWechatBind, reqRepoCloudBrainWriter, context.PointAccount(), repo.CloudBrainBenchmarkNew) | |||
| m.Post("/create", reqWechatBind, reqRepoCloudBrainWriter, bindIgnErr(auth.CreateCloudBrainForm{}), repo.CloudBrainBenchmarkCreate) | |||
| m.Get("/get_child_types", repo.GetChildTypes) | |||
| }) | |||
| @@ -1093,7 +1093,7 @@ func RegisterRoutes(m *macaron.Macaron) { | |||
| //m.Get("/create_version", reqWechatBind, cloudbrain.AdminOrJobCreaterRightForTrain, repo.TrainJobNewVersion) | |||
| //m.Post("/create_version", reqWechatBind, cloudbrain.AdminOrJobCreaterRightForTrain, bindIgnErr(auth.CreateModelArtsTrainJobForm{}), repo.TrainJobCreateVersion) | |||
| }) | |||
| m.Get("/create", reqWechatBind, reqRepoCloudBrainWriter, repo.CloudBrainTrainJobNew) | |||
| m.Get("/create", reqWechatBind, reqRepoCloudBrainWriter, context.PointAccount(), repo.CloudBrainTrainJobNew) | |||
| m.Post("/create", reqWechatBind, reqRepoCloudBrainWriter, bindIgnErr(auth.CreateCloudBrainForm{}), repo.CloudBrainCreate) | |||
| }) | |||
| }, context.RepoRef()) | |||
| @@ -1141,7 +1141,7 @@ func RegisterRoutes(m *macaron.Macaron) { | |||
| m.Post("/:action", reqRepoCloudBrainWriter, repo.NotebookManage) | |||
| m.Post("/del", cloudbrain.AdminOrOwnerOrJobCreaterRight, repo.NotebookDel) | |||
| }) | |||
| m.Get("/create", reqWechatBind, reqRepoCloudBrainWriter, repo.NotebookNew) | |||
| m.Get("/create", reqWechatBind, reqRepoCloudBrainWriter, context.PointAccount(), repo.NotebookNew) | |||
| m.Post("/create", reqWechatBind, reqRepoCloudBrainWriter, bindIgnErr(auth.CreateModelArtsNotebookForm{}), repo.Notebook2Create) | |||
| }) | |||
| @@ -1155,7 +1155,7 @@ func RegisterRoutes(m *macaron.Macaron) { | |||
| m.Get("/create_version", reqWechatBind, cloudbrain.AdminOrJobCreaterRightForTrain, repo.TrainJobNewVersion) | |||
| m.Post("/create_version", reqWechatBind, cloudbrain.AdminOrJobCreaterRightForTrain, bindIgnErr(auth.CreateModelArtsTrainJobForm{}), repo.TrainJobCreateVersion) | |||
| }) | |||
| m.Get("/create", reqWechatBind, reqRepoCloudBrainWriter, repo.TrainJobNew) | |||
| m.Get("/create", reqWechatBind, reqRepoCloudBrainWriter, context.PointAccount(), repo.TrainJobNew) | |||
| m.Post("/create", reqWechatBind, reqRepoCloudBrainWriter, bindIgnErr(auth.CreateModelArtsTrainJobForm{}), repo.TrainJobCreate) | |||
| m.Get("/para-config-list", reqRepoCloudBrainReader, repo.TrainJobGetConfigList) | |||
| @@ -1168,7 +1168,7 @@ func RegisterRoutes(m *macaron.Macaron) { | |||
| m.Get("/result_download", cloudbrain.AdminOrJobCreaterRightForTrain, repo.ResultDownload) | |||
| m.Get("/downloadall", repo.DownloadMultiResultFile) | |||
| }) | |||
| m.Get("/create", reqWechatBind, reqRepoCloudBrainWriter, repo.InferenceJobNew) | |||
| m.Get("/create", reqWechatBind, reqRepoCloudBrainWriter, context.PointAccount(), repo.InferenceJobNew) | |||
| m.Post("/create", reqWechatBind, reqRepoCloudBrainWriter, bindIgnErr(auth.CreateModelArtsInferenceJobForm{}), repo.InferenceJobCreate) | |||
| }) | |||
| }, context.RepoRef()) | |||
| @@ -0,0 +1,128 @@ | |||
| package reward | |||
| import ( | |||
| "code.gitea.io/gitea/models" | |||
| "code.gitea.io/gitea/modules/log" | |||
| "code.gitea.io/gitea/modules/setting" | |||
| "code.gitea.io/gitea/services/reward/point/account" | |||
| "encoding/json" | |||
| "fmt" | |||
| "time" | |||
| ) | |||
| var ( | |||
| ResourceSpecs *models.ResourceSpecs | |||
| TrainResourceSpecs *models.ResourceSpecs | |||
| ) | |||
| //IsPointBalanceEnough check whether the user's point balance is bigger than task unit price | |||
| func IsPointBalanceEnough(targetUserId int64, jobType string, resourceSpecId int) bool { | |||
| if !setting.CloudBrainTaskPointPaySwitch { | |||
| return true | |||
| } | |||
| spec := getResourceSpec(jobType, resourceSpecId) | |||
| if spec == nil { | |||
| return true | |||
| } | |||
| a, error := account.GetAccount(targetUserId) | |||
| if error != nil { | |||
| return false | |||
| } | |||
| return a.Balance >= spec.UnitPrice | |||
| } | |||
| func StartCloudBrainPointDeductTask(task models.Cloudbrain) { | |||
| if !setting.CloudBrainTaskPointPaySwitch { | |||
| return | |||
| } | |||
| spec := getResourceSpec(task.JobType, task.ResourceSpecId) | |||
| if spec == nil || spec.UnitPrice == 0 { | |||
| return | |||
| } | |||
| StartPeriodicTask(&models.StartPeriodicTaskOpts{ | |||
| SourceType: models.SourceTypeRunCloudbrainTask, | |||
| SourceId: getCloudBrainPointTaskSourceId(task), | |||
| TargetUserId: task.UserID, | |||
| RequestId: getCloudBrainPointTaskSourceId(task), | |||
| OperateType: models.OperateTypeDecrease, | |||
| Delay: 30 * time.Minute, | |||
| Interval: 60 * time.Minute, | |||
| UnitAmount: spec.UnitPrice, | |||
| RewardType: models.RewardTypePoint, | |||
| StartTime: time.Unix(int64(task.StartTime), 0), | |||
| }) | |||
| } | |||
| func StopCloudBrainPointDeductTask(task models.Cloudbrain) { | |||
| StopPeriodicTask(models.SourceTypeRunCloudbrainTask, getCloudBrainPointTaskSourceId(task), models.OperateTypeDecrease) | |||
| } | |||
| func getCloudBrainPointTaskSourceId(task models.Cloudbrain) string { | |||
| return models.SourceTypeRunCloudbrainTask.Name() + "_" + task.JobType + "_" + fmt.Sprint(task.Type) + "_" + fmt.Sprint(task.ID) | |||
| } | |||
| func getResourceSpec(jobType string, resourceSpecId int) *models.ResourceSpec { | |||
| if jobType == string(models.JobTypeTrain) { | |||
| if TrainResourceSpecs == nil { | |||
| json.Unmarshal([]byte(setting.TrainResourceSpecs), &TrainResourceSpecs) | |||
| } | |||
| for _, spec := range TrainResourceSpecs.ResourceSpec { | |||
| if resourceSpecId == spec.Id { | |||
| return spec | |||
| } | |||
| } | |||
| } else { | |||
| if ResourceSpecs == nil { | |||
| json.Unmarshal([]byte(setting.ResourceSpecs), &ResourceSpecs) | |||
| } | |||
| for _, spec := range ResourceSpecs.ResourceSpec { | |||
| if resourceSpecId == spec.Id { | |||
| return spec | |||
| } | |||
| } | |||
| } | |||
| return nil | |||
| } | |||
| var firstTimeFlag = true | |||
| func StartCloudbrainPointDeductTask() { | |||
| defer func() { | |||
| if err := recover(); err != nil { | |||
| combinedErr := fmt.Errorf("%s\n%s", err, log.Stack(2)) | |||
| log.Error("PANIC:%v", combinedErr) | |||
| } | |||
| }() | |||
| log.Debug("try to run CloudbrainPointDeductTask") | |||
| end := time.Now() | |||
| start := end.Add(5 * time.Minute) | |||
| if firstTimeFlag { | |||
| //When it is executed for the first time, it needs to process the tasks of the last 1 hours. | |||
| //This is done to prevent the application from hanging for a long time | |||
| start = end.Add(1 * time.Hour) | |||
| firstTimeFlag = false | |||
| } | |||
| taskList, err := models.GetStartedCloudbrainTaskByUpdatedUnix(start, end) | |||
| if err != nil { | |||
| log.Error("GetStartedCloudbrainTaskByUpdatedUnix error. %v", err) | |||
| return | |||
| } | |||
| if taskList == nil || len(taskList) == 0 { | |||
| log.Debug("No cloudbrain task need handled") | |||
| return | |||
| } | |||
| for _, t := range taskList { | |||
| if int64(t.StartTime) <= end.Unix() && int64(t.StartTime) >= start.Unix() { | |||
| StartCloudBrainPointDeductTask(t) | |||
| } | |||
| if int64(t.EndTime) <= end.Unix() && int64(t.EndTime) >= start.Unix() { | |||
| StopCloudBrainPointDeductTask(t) | |||
| } | |||
| } | |||
| } | |||
| @@ -21,7 +21,7 @@ type RewardOperator interface { | |||
| Operate(ctx *models.RewardOperateContext) error | |||
| } | |||
| func Send(ctx *models.RewardOperateContext) error { | |||
| func Operate(ctx *models.RewardOperateContext) error { | |||
| defer func() { | |||
| if err := recover(); err != nil { | |||
| combinedErr := fmt.Errorf("%s\n%s", err, log.Stack(2)) | |||
| @@ -33,7 +33,7 @@ func Send(ctx *models.RewardOperateContext) error { | |||
| return errors.New("param incorrect") | |||
| } | |||
| //add lock | |||
| var rewardLock = redis_lock.NewDistributeLock(redis_key.RewardSendLock(ctx.RequestId, ctx.SourceType)) | |||
| var rewardLock = redis_lock.NewDistributeLock(redis_key.RewardOperateLock(ctx.RequestId, ctx.SourceType.Name(), ctx.OperateType.Name())) | |||
| isOk, err := rewardLock.Lock(3 * time.Second) | |||
| if err != nil { | |||
| return err | |||
| @@ -45,7 +45,7 @@ func Send(ctx *models.RewardOperateContext) error { | |||
| defer rewardLock.UnLock() | |||
| //is handled before? | |||
| isHandled, err := isHandled(ctx.SourceType, ctx.RequestId) | |||
| isHandled, err := isHandled(ctx.SourceType.Name(), ctx.RequestId, ctx.OperateType.Name()) | |||
| if err != nil { | |||
| log.Error("reward is handled error,%v", err) | |||
| return err | |||
| @@ -61,9 +61,11 @@ func Send(ctx *models.RewardOperateContext) error { | |||
| return errors.New("operator of reward type is not exist") | |||
| } | |||
| //is limited? | |||
| if isLimited := operator.IsLimited(ctx); isLimited { | |||
| return nil | |||
| if ctx.OperateType == models.OperateTypeIncrease { | |||
| //is limited? | |||
| if isLimited := operator.IsLimited(ctx); isLimited { | |||
| return nil | |||
| } | |||
| } | |||
| //new reward operate record | |||
| @@ -76,15 +78,12 @@ func Send(ctx *models.RewardOperateContext) error { | |||
| //operate | |||
| if err := operator.Operate(ctx); err != nil { | |||
| updateAwardOperateRecordStatus(ctx.SourceType, ctx.RequestId, models.OperateStatusOperating, models.OperateStatusFailed) | |||
| updateAwardOperateRecordStatus(ctx.SourceType.Name(), ctx.RequestId, models.OperateStatusOperating, models.OperateStatusFailed) | |||
| return err | |||
| } | |||
| //if not a cycle operate,update status to success | |||
| if ctx.CycleIntervalSeconds == 0 { | |||
| updateAwardOperateRecordStatus(ctx.SourceType, ctx.RequestId, models.OperateStatusOperating, models.OperateStatusSucceeded) | |||
| NotifyRewardOperation(ctx.TargetUserId, ctx.Reward.Amount, ctx.Reward.Type, ctx.OperateType) | |||
| } | |||
| updateAwardOperateRecordStatus(ctx.SourceType.Name(), ctx.RequestId, models.OperateStatusOperating, models.OperateStatusSucceeded) | |||
| NotifyRewardOperation(ctx.TargetUserId, ctx.Reward.Amount, ctx.Reward.Type, ctx.OperateType) | |||
| return nil | |||
| } | |||
| @@ -99,8 +98,8 @@ func GetOperator(rewardType models.RewardType) RewardOperator { | |||
| return RewardOperatorMap[rewardType.Name()] | |||
| } | |||
| func isHandled(sourceType string, requestId string) (bool, error) { | |||
| _, err := models.GetPointOperateRecordBySourceTypeAndRequestId(sourceType, requestId) | |||
| func isHandled(sourceType string, requestId string, operateType string) (bool, error) { | |||
| _, err := models.GetPointOperateRecordBySourceTypeAndRequestId(sourceType, requestId, operateType) | |||
| if err != nil { | |||
| if models.IsErrRecordNotExist(err) { | |||
| return false, nil | |||
| @@ -113,17 +112,36 @@ func isHandled(sourceType string, requestId string) (bool, error) { | |||
| func initAwardOperateRecord(ctx *models.RewardOperateContext) (string, error) { | |||
| record := &models.RewardOperateRecord{ | |||
| RecordId: util.UUID(), | |||
| UserId: ctx.TargetUserId, | |||
| Amount: ctx.Reward.Amount, | |||
| RewardType: ctx.Reward.Type.Name(), | |||
| SourceType: ctx.SourceType, | |||
| SourceId: ctx.SourceId, | |||
| RequestId: ctx.RequestId, | |||
| OperateType: ctx.OperateType.Name(), | |||
| CycleIntervalSeconds: ctx.CycleIntervalSeconds, | |||
| Status: models.OperateStatusOperating, | |||
| Remark: ctx.Remark, | |||
| RecordId: util.UUID(), | |||
| UserId: ctx.TargetUserId, | |||
| Amount: ctx.Reward.Amount, | |||
| RewardType: ctx.Reward.Type.Name(), | |||
| SourceType: ctx.SourceType.Name(), | |||
| SourceId: ctx.SourceId, | |||
| RequestId: ctx.RequestId, | |||
| OperateType: ctx.OperateType.Name(), | |||
| Status: models.OperateStatusOperating, | |||
| Remark: ctx.Remark, | |||
| } | |||
| _, err := models.InsertAwardOperateRecord(record) | |||
| if err != nil { | |||
| return "", err | |||
| } | |||
| return record.RecordId, nil | |||
| } | |||
| func createPeriodicRewardOperateRecord(ctx *models.StartPeriodicTaskOpts) (string, error) { | |||
| record := &models.RewardOperateRecord{ | |||
| RecordId: util.UUID(), | |||
| UserId: ctx.TargetUserId, | |||
| Amount: 0, | |||
| RewardType: ctx.RewardType.Name(), | |||
| SourceType: ctx.SourceType.Name(), | |||
| SourceId: ctx.SourceId, | |||
| RequestId: ctx.RequestId, | |||
| OperateType: ctx.OperateType.Name(), | |||
| Status: models.OperateStatusOperating, | |||
| Remark: ctx.Remark, | |||
| } | |||
| _, err := models.InsertAwardOperateRecord(record) | |||
| if err != nil { | |||
| @@ -139,3 +157,78 @@ func updateAwardOperateRecordStatus(sourceType, requestId, oldStatus, newStatus | |||
| } | |||
| return nil | |||
| } | |||
| func StartPeriodicTaskAsyn(opts *models.StartPeriodicTaskOpts) { | |||
| go StartPeriodicTask(opts) | |||
| } | |||
| func StartPeriodicTask(opts *models.StartPeriodicTaskOpts) error { | |||
| defer func() { | |||
| if err := recover(); err != nil { | |||
| combinedErr := fmt.Errorf("%s\n%s", err, log.Stack(2)) | |||
| log.Error("PANIC:%v", combinedErr) | |||
| } | |||
| }() | |||
| //add lock | |||
| var rewardLock = redis_lock.NewDistributeLock(redis_key.RewardOperateLock(opts.RequestId, opts.SourceType.Name(), opts.OperateType.Name())) | |||
| isOk, err := rewardLock.Lock(3 * time.Second) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| if !isOk { | |||
| log.Info("duplicated operate request,targetUserId=%d requestId=%s", opts.TargetUserId, opts.RequestId) | |||
| return nil | |||
| } | |||
| defer rewardLock.UnLock() | |||
| //is handled before? | |||
| isHandled, err := isHandled(opts.SourceType.Name(), opts.RequestId, opts.OperateType.Name()) | |||
| if err != nil { | |||
| log.Error("operate is handled error,%v", err) | |||
| return err | |||
| } | |||
| if isHandled { | |||
| log.Info("operate has been handled,opts=%+v", opts) | |||
| return nil | |||
| } | |||
| //new reward operate record | |||
| recordId, err := createPeriodicRewardOperateRecord(opts) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| if err = NewRewardPeriodicTask(recordId, opts); err != nil { | |||
| updateAwardOperateRecordStatus(opts.SourceType.Name(), opts.RequestId, models.OperateStatusOperating, models.OperateStatusFailed) | |||
| return err | |||
| } | |||
| return nil | |||
| } | |||
| func StopPeriodicTaskAsyn(sourceType models.SourceType, sourceId string, operateType models.RewardOperateType) { | |||
| go StopPeriodicTask(sourceType, sourceId, operateType) | |||
| } | |||
| func StopPeriodicTask(sourceType models.SourceType, sourceId string, operateType models.RewardOperateType) error { | |||
| defer func() { | |||
| if err := recover(); err != nil { | |||
| combinedErr := fmt.Errorf("%s\n%s", err, log.Stack(2)) | |||
| log.Error("PANIC:%v", combinedErr) | |||
| } | |||
| }() | |||
| task, err := models.GetPeriodicTaskBySourceIdAndType(sourceType, sourceId, operateType) | |||
| if err != nil { | |||
| log.Error("StopPeriodicTask. GetPeriodicTaskBySourceIdAndType error. %v", err) | |||
| return err | |||
| } | |||
| if task == nil { | |||
| log.Info("Periodic task is not exist") | |||
| return nil | |||
| } | |||
| if task.Status == models.PeriodicTaskStatusFinished { | |||
| log.Info("Periodic task is finished") | |||
| return nil | |||
| } | |||
| now := time.Now() | |||
| RunRewardTask(*task, now) | |||
| return models.StopPeriodicTask(task.ID, task.OperateRecordId, now) | |||
| } | |||
| @@ -0,0 +1,103 @@ | |||
| package reward | |||
| import ( | |||
| "code.gitea.io/gitea/models" | |||
| "code.gitea.io/gitea/modules/log" | |||
| "code.gitea.io/gitea/modules/redis/redis_key" | |||
| "code.gitea.io/gitea/modules/redis/redis_lock" | |||
| "code.gitea.io/gitea/modules/timeutil" | |||
| "fmt" | |||
| "time" | |||
| ) | |||
| func NewRewardPeriodicTask(operateRecordId string, opts *models.StartPeriodicTaskOpts) error { | |||
| task := &models.RewardPeriodicTask{} | |||
| task.DelaySeconds = int64(opts.Delay.Seconds()) | |||
| task.IntervalSeconds = int64(opts.Interval.Seconds()) | |||
| task.Amount = opts.UnitAmount | |||
| task.OperateRecordId = operateRecordId | |||
| task.Status = models.PeriodicTaskStatusRunning | |||
| task.NextExecuteTime = timeutil.TimeStamp(opts.StartTime.Add(opts.Delay).Unix()) | |||
| _, err := models.InsertPeriodicTask(task) | |||
| return err | |||
| } | |||
| func StartRewardTask() { | |||
| defer func() { | |||
| if err := recover(); err != nil { | |||
| combinedErr := fmt.Errorf("%s\n%s", err, log.Stack(2)) | |||
| log.Error("PANIC:%v", combinedErr) | |||
| } | |||
| }() | |||
| log.Debug("try to run reward tasks") | |||
| now := time.Now() | |||
| taskList, err := models.GetRunningRewardTask(now) | |||
| if err != nil { | |||
| log.Error("GetRunningRewardTask error. %v", err) | |||
| return | |||
| } | |||
| if taskList == nil || len(taskList) == 0 { | |||
| log.Debug("No GetRunningRewardTask need handled") | |||
| return | |||
| } | |||
| for _, t := range taskList { | |||
| RunRewardTask(t, now) | |||
| } | |||
| } | |||
| func RunRewardTask(t models.RewardPeriodicTask, now time.Time) { | |||
| lock := redis_lock.NewDistributeLock(redis_key.RewardTaskRunningLock(t.ID)) | |||
| isOk, _ := lock.LockWithWait(3*time.Second, 3*time.Second) | |||
| if !isOk { | |||
| log.Error("get RewardTaskRunningLock failed,t=%+v", t) | |||
| return | |||
| } | |||
| defer lock.UnLock() | |||
| record, err := models.GetPointOperateRecordByRecordId(t.OperateRecordId) | |||
| if err != nil { | |||
| log.Error("RunRewardTask. GetPointOperateRecordByRecordId error. %v", err) | |||
| return | |||
| } | |||
| if record.Status != models.OperateStatusOperating { | |||
| log.Info("RunRewardTask. operate record is finished,record=%+v", record) | |||
| return | |||
| } | |||
| n, nextTime := countExecuteTimes(t, now) | |||
| if n == 0 { | |||
| return | |||
| } | |||
| //get operator | |||
| operator := GetOperator(models.GetRewardTypeInstance(record.RewardType)) | |||
| if operator == nil { | |||
| log.Error("RunRewardTask. operator of reward type is not exist") | |||
| return | |||
| } | |||
| err = operator.Operate(&models.RewardOperateContext{ | |||
| SourceType: models.SourceTypeRunCloudbrainTask, | |||
| SourceId: t.OperateRecordId, | |||
| Reward: models.Reward{ | |||
| Amount: n * t.Amount, | |||
| Type: models.GetRewardTypeInstance(record.RewardType), | |||
| }, | |||
| TargetUserId: record.UserId, | |||
| OperateType: models.GetRewardOperateTypeInstance(record.OperateType), | |||
| }) | |||
| if err != nil { | |||
| log.Error("RunRewardTask.operator operate error.%v", err) | |||
| return | |||
| } | |||
| models.IncrRewardTaskSuccessCount(t, n, nextTime) | |||
| } | |||
| func countExecuteTimes(t models.RewardPeriodicTask, now time.Time) (int64, timeutil.TimeStamp) { | |||
| interval := t.IntervalSeconds | |||
| nextTime := int64(t.NextExecuteTime) | |||
| if nextTime > now.Unix() { | |||
| return 0, 0 | |||
| } | |||
| diff := now.Unix() - nextTime | |||
| n := diff/interval + 1 | |||
| newNextTime := timeutil.TimeStamp(nextTime + n*interval) | |||
| return n, newNextTime | |||
| } | |||
| @@ -18,13 +18,12 @@ type PointOperator struct { | |||
| } | |||
| func (operator *PointOperator) IsLimited(ctx *models.RewardOperateContext) bool { | |||
| realAmount, err := limiter.CheckLimitWithFillUp(ctx.SourceType, models.LimitTypeRewardPoint, ctx.TargetUserId, ctx.Reward.Amount) | |||
| realAmount, err := limiter.CheckLimitWithFillUp(ctx.SourceType.Name(), models.LimitTypeRewardPoint, ctx.TargetUserId, ctx.Reward.Amount) | |||
| if err != nil { | |||
| return true | |||
| } | |||
| if realAmount < ctx.Reward.Amount { | |||
| ctx.Remark = models.AppendRemark(ctx.Remark, fmt.Sprintf(LossMsg, ctx.Reward.Amount, realAmount)) | |||
| ctx.Reward.Amount = realAmount | |||
| } | |||
| return false | |||
| @@ -51,7 +51,7 @@ func accomplish(userId int64, taskType string) error { | |||
| } | |||
| //reward | |||
| reward.Send(&models.RewardOperateContext{ | |||
| reward.Operate(&models.RewardOperateContext{ | |||
| SourceType: models.SourceTypeAccomplishTask, | |||
| SourceId: logId, | |||
| Reward: models.Reward{ | |||