mirror of
https://github.com/simon987/task_tracker.git
synced 2025-04-18 18:06:41 +00:00
Add per project rate limit
This commit is contained in:
parent
9acf6e27c1
commit
3415f95337
16
api/main.go
16
api/main.go
@ -9,15 +9,18 @@ import (
|
||||
"github.com/simon987/task_tracker/config"
|
||||
"github.com/simon987/task_tracker/storage"
|
||||
"github.com/valyala/fasthttp"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type WebAPI struct {
|
||||
server *fasthttp.Server
|
||||
router *fasthttprouter.Router
|
||||
Database *storage.Database
|
||||
SessionConfig sessions.Config
|
||||
Session *sessions.Sessions
|
||||
Cron *cron.Cron
|
||||
server *fasthttp.Server
|
||||
router *fasthttprouter.Router
|
||||
Database *storage.Database
|
||||
SessionConfig sessions.Config
|
||||
Session *sessions.Sessions
|
||||
Cron *cron.Cron
|
||||
AssignLimiters sync.Map
|
||||
SubmitLimiters sync.Map
|
||||
}
|
||||
|
||||
type RequestHandler func(*Request)
|
||||
@ -98,7 +101,6 @@ func New() *WebAPI {
|
||||
|
||||
api.router.POST("/task/submit", LogRequestMiddleware(api.SubmitTask))
|
||||
api.router.GET("/task/get/:project", LogRequestMiddleware(api.GetTaskFromProject))
|
||||
api.router.GET("/task/get", LogRequestMiddleware(api.GetTask))
|
||||
api.router.POST("/task/release", LogRequestMiddleware(api.ReleaseTask))
|
||||
|
||||
api.router.POST("/git/receivehook", LogRequestMiddleware(api.ReceiveGitWebHook))
|
||||
|
@ -3,6 +3,7 @@ package api
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/simon987/task_tracker/storage"
|
||||
"golang.org/x/time/rate"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -12,9 +13,10 @@ const (
|
||||
)
|
||||
|
||||
type JsonResponse struct {
|
||||
Ok bool `json:"ok"`
|
||||
Message string `json:"message,omitempty"`
|
||||
Content interface{} `json:"content,omitempty"`
|
||||
Ok bool `json:"ok"`
|
||||
Message string `json:"message,omitempty"`
|
||||
RateLimitDelay string `json:"rate_limit_delay,omitempty"`
|
||||
Content interface{} `json:"content,omitempty"`
|
||||
}
|
||||
|
||||
type GitPayload struct {
|
||||
@ -115,15 +117,17 @@ type GetSnapshotsResponse struct {
|
||||
}
|
||||
|
||||
type CreateProjectRequest struct {
|
||||
Name string `json:"name"`
|
||||
CloneUrl string `json:"clone_url"`
|
||||
GitRepo string `json:"git_repo"`
|
||||
Version string `json:"version"`
|
||||
Priority int64 `json:"priority"`
|
||||
Motd string `json:"motd"`
|
||||
Public bool `json:"public"`
|
||||
Hidden bool `json:"hidden"`
|
||||
Chain int64 `json:"chain"`
|
||||
Name string `json:"name"`
|
||||
CloneUrl string `json:"clone_url"`
|
||||
GitRepo string `json:"git_repo"`
|
||||
Version string `json:"version"`
|
||||
Priority int64 `json:"priority"`
|
||||
Motd string `json:"motd"`
|
||||
Public bool `json:"public"`
|
||||
Hidden bool `json:"hidden"`
|
||||
Chain int64 `json:"chain"`
|
||||
AssignRate rate.Limit `json:"assign_rate"`
|
||||
SubmitRate rate.Limit `json:"submit_rate"`
|
||||
}
|
||||
|
||||
func (req *CreateProjectRequest) isValid() bool {
|
||||
@ -140,15 +144,17 @@ func (req *CreateProjectRequest) isValid() bool {
|
||||
}
|
||||
|
||||
type UpdateProjectRequest struct {
|
||||
Name string `json:"name"`
|
||||
CloneUrl string `json:"clone_url"`
|
||||
GitRepo string `json:"git_repo"`
|
||||
Priority int64 `json:"priority"`
|
||||
Motd string `json:"motd"`
|
||||
Public bool `json:"public"`
|
||||
Hidden bool `json:"hidden"`
|
||||
Chain int64 `json:"chain"`
|
||||
Paused bool `json:"paused"`
|
||||
Name string `json:"name"`
|
||||
CloneUrl string `json:"clone_url"`
|
||||
GitRepo string `json:"git_repo"`
|
||||
Priority int64 `json:"priority"`
|
||||
Motd string `json:"motd"`
|
||||
Public bool `json:"public"`
|
||||
Hidden bool `json:"hidden"`
|
||||
Chain int64 `json:"chain"`
|
||||
Paused bool `json:"paused"`
|
||||
AssignRate rate.Limit `json:"assign_rate"`
|
||||
SubmitRate rate.Limit `json:"submit_rate"`
|
||||
}
|
||||
|
||||
func (req *UpdateProjectRequest) isValid() bool {
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/google/uuid"
|
||||
"github.com/simon987/task_tracker/storage"
|
||||
"golang.org/x/time/rate"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
@ -62,16 +63,25 @@ func (api *WebAPI) CreateProject(r *Request) {
|
||||
}, 400)
|
||||
return
|
||||
}
|
||||
|
||||
if createReq.AssignRate == 0 {
|
||||
createReq.AssignRate = rate.Inf
|
||||
}
|
||||
if createReq.SubmitRate == 0 {
|
||||
createReq.SubmitRate = rate.Inf
|
||||
}
|
||||
project := &storage.Project{
|
||||
Name: createReq.Name,
|
||||
Version: createReq.Version,
|
||||
CloneUrl: createReq.CloneUrl,
|
||||
GitRepo: createReq.GitRepo,
|
||||
Priority: createReq.Priority,
|
||||
Motd: createReq.Motd,
|
||||
Public: createReq.Public,
|
||||
Hidden: createReq.Hidden,
|
||||
Chain: createReq.Chain,
|
||||
Name: createReq.Name,
|
||||
Version: createReq.Version,
|
||||
CloneUrl: createReq.CloneUrl,
|
||||
GitRepo: createReq.GitRepo,
|
||||
Priority: createReq.Priority,
|
||||
Motd: createReq.Motd,
|
||||
Public: createReq.Public,
|
||||
Hidden: createReq.Hidden,
|
||||
Chain: createReq.Chain,
|
||||
AssignRate: createReq.AssignRate,
|
||||
SubmitRate: createReq.SubmitRate,
|
||||
}
|
||||
|
||||
if !createReq.isValid() {
|
||||
@ -156,16 +166,18 @@ func (api *WebAPI) UpdateProject(r *Request) {
|
||||
}
|
||||
|
||||
project := &storage.Project{
|
||||
Id: id,
|
||||
Name: updateReq.Name,
|
||||
CloneUrl: updateReq.CloneUrl,
|
||||
GitRepo: updateReq.GitRepo,
|
||||
Priority: updateReq.Priority,
|
||||
Motd: updateReq.Motd,
|
||||
Public: updateReq.Public,
|
||||
Hidden: updateReq.Hidden,
|
||||
Chain: updateReq.Chain,
|
||||
Paused: updateReq.Paused,
|
||||
Id: id,
|
||||
Name: updateReq.Name,
|
||||
CloneUrl: updateReq.CloneUrl,
|
||||
GitRepo: updateReq.GitRepo,
|
||||
Priority: updateReq.Priority,
|
||||
Motd: updateReq.Motd,
|
||||
Public: updateReq.Public,
|
||||
Hidden: updateReq.Hidden,
|
||||
Chain: updateReq.Chain,
|
||||
Paused: updateReq.Paused,
|
||||
AssignRate: updateReq.AssignRate,
|
||||
SubmitRate: updateReq.SubmitRate,
|
||||
}
|
||||
sess := api.Session.StartFasthttp(r.Ctx)
|
||||
manager := sess.Get("manager")
|
||||
|
40
api/rate.go
Normal file
40
api/rate.go
Normal file
@ -0,0 +1,40 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"golang.org/x/time/rate"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (api *WebAPI) ReserveSubmit(pid int64) *rate.Reservation {
|
||||
|
||||
limiter, ok := api.SubmitLimiters.Load(pid)
|
||||
if !ok {
|
||||
project := api.Database.GetProject(pid)
|
||||
if project == nil {
|
||||
return &rate.Reservation{}
|
||||
}
|
||||
|
||||
limiter = rate.NewLimiter(project.SubmitRate, 1)
|
||||
api.SubmitLimiters.Store(pid, limiter)
|
||||
}
|
||||
|
||||
return limiter.(*rate.Limiter).ReserveN(time.Now(), 1)
|
||||
}
|
||||
|
||||
func (api *WebAPI) ReserveAssign(pid int64) *rate.Reservation {
|
||||
|
||||
limiter, ok := api.AssignLimiters.Load(pid)
|
||||
if !ok {
|
||||
project := api.Database.GetProject(pid)
|
||||
if project == nil {
|
||||
return &rate.Reservation{}
|
||||
}
|
||||
|
||||
limiter = rate.NewLimiter(project.AssignRate, 1)
|
||||
api.AssignLimiters.Store(pid, limiter)
|
||||
fmt.Printf("Create")
|
||||
}
|
||||
|
||||
return limiter.(*rate.Limiter).ReserveN(time.Now(), 1)
|
||||
}
|
63
api/task.go
63
api/task.go
@ -11,6 +11,7 @@ import (
|
||||
"github.com/dchest/siphash"
|
||||
"github.com/simon987/task_tracker/storage"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (api *WebAPI) SubmitTask(r *Request) {
|
||||
@ -53,6 +54,17 @@ func (api *WebAPI) SubmitTask(r *Request) {
|
||||
return
|
||||
}
|
||||
|
||||
reservation := api.ReserveSubmit(createReq.Project)
|
||||
delay := reservation.DelayFrom(time.Now()).Seconds()
|
||||
if delay > 0 {
|
||||
r.Json(JsonResponse{
|
||||
Ok: false,
|
||||
Message: "Too many requests",
|
||||
RateLimitDelay: strconv.FormatFloat(delay, 'f', -1, 64),
|
||||
}, 429)
|
||||
return
|
||||
}
|
||||
|
||||
if createReq.UniqueString != "" {
|
||||
//TODO: Load key from config
|
||||
createReq.Hash64 = int64(siphash.Hash(1, 2, []byte(createReq.UniqueString)))
|
||||
@ -65,6 +77,7 @@ func (api *WebAPI) SubmitTask(r *Request) {
|
||||
Ok: false,
|
||||
Message: err.Error(),
|
||||
}, 400)
|
||||
reservation.Cancel()
|
||||
} else {
|
||||
r.OkJson(JsonResponse{
|
||||
Ok: true,
|
||||
@ -84,47 +97,33 @@ func (api *WebAPI) GetTaskFromProject(r *Request) {
|
||||
}
|
||||
|
||||
project, err := strconv.ParseInt(r.Ctx.UserValue("project").(string), 10, 64)
|
||||
handleErr(err, r)
|
||||
task := api.Database.GetTaskFromProject(worker, project)
|
||||
|
||||
if task == nil {
|
||||
|
||||
r.OkJson(JsonResponse{
|
||||
Ok: false,
|
||||
Message: "No task available",
|
||||
})
|
||||
|
||||
} else {
|
||||
|
||||
r.OkJson(JsonResponse{
|
||||
Ok: true,
|
||||
Content: GetTaskResponse{
|
||||
Task: task,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (api *WebAPI) GetTask(r *Request) {
|
||||
|
||||
worker, err := api.validateSignature(r)
|
||||
if err != nil {
|
||||
if err != nil || project <= 0 {
|
||||
r.Json(JsonResponse{
|
||||
Ok: false,
|
||||
Message: err.Error(),
|
||||
}, 403)
|
||||
Message: "Invalid project id",
|
||||
}, 400)
|
||||
return
|
||||
}
|
||||
|
||||
task := api.Database.GetTask(worker)
|
||||
if task == nil {
|
||||
reservation := api.ReserveAssign(project)
|
||||
delay := reservation.DelayFrom(time.Now()).Seconds()
|
||||
if delay > 0 {
|
||||
r.Json(JsonResponse{
|
||||
Ok: false,
|
||||
Message: "Too many requests",
|
||||
RateLimitDelay: strconv.FormatFloat(delay, 'f', -1, 64),
|
||||
}, 429)
|
||||
return
|
||||
}
|
||||
|
||||
task := api.Database.GetTaskFromProject(worker, project)
|
||||
|
||||
if task == nil {
|
||||
r.OkJson(JsonResponse{
|
||||
Ok: false,
|
||||
Message: "No task available",
|
||||
})
|
||||
|
||||
reservation.CancelAt(time.Now())
|
||||
} else {
|
||||
|
||||
r.OkJson(JsonResponse{
|
||||
@ -134,8 +133,8 @@ func (api *WebAPI) GetTask(r *Request) {
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
func (api WebAPI) validateSignature(r *Request) (*storage.Worker, error) {
|
||||
|
||||
widStr := string(r.Ctx.Request.Header.Peek("X-Worker-Id"))
|
||||
|
2
jenkins/Jenkinsfile
vendored
2
jenkins/Jenkinsfile
vendored
@ -68,7 +68,7 @@ pipeline {
|
||||
node('master') {
|
||||
unstash 'webdist'
|
||||
unstash 'apidist'
|
||||
sshCommand remote: remote, command: "rm -rf tt_api config.yml schema.sql webroot deploy.sh"
|
||||
sshCommand remote: remote, command: "cd task_tracker && rm -rf tt_api config.yml schema.sql webroot deploy.sh"
|
||||
sshPut remote: remote, from: 'tt_api', into: 'task_tracker/tt_api'
|
||||
sshPut remote: remote, from: 'config.yml', into: 'task_tracker/config.yml'
|
||||
sshPut remote: remote, from: 'schema.sql', into: 'task_tracker/schema.sql'
|
||||
|
23
schema.sql
23
schema.sql
@ -15,17 +15,20 @@ CREATE TABLE project
|
||||
(
|
||||
id SERIAL PRIMARY KEY NOT NULL,
|
||||
priority INTEGER DEFAULT 0 NOT NULL,
|
||||
closed_task_count INT DEFAULT 0 NOT NULL,
|
||||
closed_task_count INT DEFAULT 0 NOT NULL,
|
||||
chain INT DEFAULT NULL REFERENCES project (id),
|
||||
public boolean NOT NULL,
|
||||
hidden boolean NOT NULL,
|
||||
paused boolean NOT NULL,
|
||||
name TEXT UNIQUE NOT NULL,
|
||||
clone_url TEXT NOT NULL,
|
||||
git_repo TEXT NOT NULL,
|
||||
version TEXT NOT NULL,
|
||||
motd TEXT NOT NULL,
|
||||
secret TEXT NOT NULL DEFAULT '{}'
|
||||
public boolean NOT NULL,
|
||||
hidden boolean NOT NULL,
|
||||
paused boolean NOT NULL,
|
||||
name TEXT UNIQUE NOT NULL,
|
||||
clone_url TEXT NOT NULL,
|
||||
git_repo TEXT NOT NULL,
|
||||
version TEXT NOT NULL,
|
||||
motd TEXT NOT NULL,
|
||||
secret TEXT NOT NULL DEFAULT '{}',
|
||||
webhook_secret TEXT NOT NULL,
|
||||
assign_rate DOUBLE PRECISION NOT NULL,
|
||||
submit_rate DOUBLE PRECISION NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE worker_access
|
||||
|
@ -3,21 +3,24 @@ package storage
|
||||
import (
|
||||
"database/sql"
|
||||
"github.com/Sirupsen/logrus"
|
||||
"golang.org/x/time/rate"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Project struct {
|
||||
Id int64 `json:"id"`
|
||||
Priority int64 `json:"priority"`
|
||||
Name string `json:"name"`
|
||||
CloneUrl string `json:"clone_url"`
|
||||
GitRepo string `json:"git_repo"`
|
||||
Version string `json:"version"`
|
||||
Motd string `json:"motd"`
|
||||
Public bool `json:"public"`
|
||||
Hidden bool `json:"hidden"`
|
||||
Chain int64 `json:"chain"`
|
||||
Paused bool `json:"paused"`
|
||||
Id int64 `json:"id"`
|
||||
Priority int64 `json:"priority"`
|
||||
Name string `json:"name"`
|
||||
CloneUrl string `json:"clone_url"`
|
||||
GitRepo string `json:"git_repo"`
|
||||
Version string `json:"version"`
|
||||
Motd string `json:"motd"`
|
||||
Public bool `json:"public"`
|
||||
Hidden bool `json:"hidden"`
|
||||
Chain int64 `json:"chain"`
|
||||
Paused bool `json:"paused"`
|
||||
AssignRate rate.Limit `json:"assign_rate"`
|
||||
SubmitRate rate.Limit `json:"submit_rate"`
|
||||
}
|
||||
|
||||
type AssignedTasks struct {
|
||||
@ -29,10 +32,11 @@ func (database *Database) SaveProject(project *Project, webhookSecret string) (i
|
||||
db := database.getDB()
|
||||
|
||||
row := db.QueryRow(`INSERT INTO project (name, git_repo, clone_url, version, priority,
|
||||
motd, public, hidden, chain, paused, webhook_secret)
|
||||
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,NULLIF($9, 0),$10,$11) RETURNING id`,
|
||||
motd, public, hidden, chain, paused, webhook_secret, assign_rate, submit_rate)
|
||||
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,NULLIF($9, 0),$10,$11,$12,$13) RETURNING id`,
|
||||
project.Name, project.GitRepo, project.CloneUrl, project.Version, project.Priority, project.Motd,
|
||||
project.Public, project.Hidden, project.Chain, project.Paused, webhookSecret)
|
||||
project.Public, project.Hidden, project.Chain, project.Paused, webhookSecret, project.AssignRate,
|
||||
project.SubmitRate)
|
||||
|
||||
var id int64
|
||||
err := row.Scan(&id)
|
||||
@ -58,7 +62,7 @@ func (database *Database) GetProject(id int64) *Project {
|
||||
|
||||
db := database.getDB()
|
||||
row := db.QueryRow(`SELECT id, priority, name, clone_url, git_repo, version,
|
||||
motd, public, hidden, COALESCE(chain, 0), paused
|
||||
motd, public, hidden, COALESCE(chain, 0), paused, assign_rate, submit_rate
|
||||
FROM project WHERE id=$1`, id)
|
||||
|
||||
project, err := scanProject(row)
|
||||
@ -81,7 +85,7 @@ func scanProject(row *sql.Row) (*Project, error) {
|
||||
|
||||
p := &Project{}
|
||||
err := row.Scan(&p.Id, &p.Priority, &p.Name, &p.CloneUrl, &p.GitRepo, &p.Version,
|
||||
&p.Motd, &p.Public, &p.Hidden, &p.Chain, &p.Paused)
|
||||
&p.Motd, &p.Public, &p.Hidden, &p.Chain, &p.Paused, &p.AssignRate, &p.SubmitRate)
|
||||
|
||||
return p, err
|
||||
}
|
||||
@ -90,7 +94,8 @@ func (database *Database) GetProjectWithRepoName(repoName string) *Project {
|
||||
|
||||
db := database.getDB()
|
||||
row := db.QueryRow(`SELECT id, priority, name, clone_url, git_repo, version,
|
||||
motd, public, hidden, COALESCE(chain, 0), paused FROM project WHERE LOWER(git_repo)=$1`,
|
||||
motd, public, hidden, COALESCE(chain, 0), paused, assign_rate, submit_rate
|
||||
FROM project WHERE LOWER(git_repo)=$1`,
|
||||
strings.ToLower(repoName))
|
||||
|
||||
project, err := scanProject(row)
|
||||
@ -109,11 +114,13 @@ func (database *Database) UpdateProject(project *Project) error {
|
||||
db := database.getDB()
|
||||
|
||||
res, err := db.Exec(`UPDATE project
|
||||
SET (priority, name, clone_url, git_repo, version, motd, public, hidden, chain, paused) =
|
||||
($1,$2,$3,$4,$5,$6,$7,$8,NULLIF($9, 0), $10)
|
||||
WHERE id=$11`,
|
||||
SET (priority, name, clone_url, git_repo, version, motd, public, hidden, chain, paused,
|
||||
assign_rate, submit_rate) =
|
||||
($1,$2,$3,$4,$5,$6,$7,$8,NULLIF($9, 0), $10,$11,$12)
|
||||
WHERE id=$13`,
|
||||
project.Priority, project.Name, project.CloneUrl, project.GitRepo, project.Version, project.Motd,
|
||||
project.Public, project.Hidden, project.Chain, project.Paused, project.Id)
|
||||
project.Public, project.Hidden, project.Chain, project.Paused, project.AssignRate, project.SubmitRate,
|
||||
project.Id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/Sirupsen/logrus"
|
||||
@ -69,75 +68,6 @@ func (database *Database) SaveTask(task *Task, project int64, hash64 int64, wid
|
||||
return nil
|
||||
}
|
||||
|
||||
func (database *Database) GetTask(worker *Worker) *Task {
|
||||
|
||||
db := database.getDB()
|
||||
|
||||
row := db.QueryRow(`
|
||||
UPDATE task
|
||||
SET assignee=$1, assign_time=extract(epoch from now() at time zone 'utc')
|
||||
WHERE id IN
|
||||
(
|
||||
SELECT task.id
|
||||
FROM task
|
||||
INNER JOIN project project on task.project = project.id
|
||||
LEFT JOIN worker_verifies_task wvt on task.id = wvt.task AND wvt.worker=$1
|
||||
WHERE NOT project.paused AND assignee IS NULL AND task.status=1
|
||||
AND (project.public OR (
|
||||
SELECT a.role_assign AND not a.request FROM worker_access a WHERE a.worker=$1 AND a.project=project.id
|
||||
))
|
||||
AND wvt.task IS NULL
|
||||
ORDER BY project.priority DESC, task.priority DESC
|
||||
LIMIT 1
|
||||
)
|
||||
RETURNING id`, worker.Id)
|
||||
var id int64
|
||||
|
||||
err := row.Scan(&id)
|
||||
if err != nil {
|
||||
logrus.WithError(err).WithFields(logrus.Fields{
|
||||
"worker": worker,
|
||||
}).Trace("No task available")
|
||||
return nil
|
||||
}
|
||||
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"id": id,
|
||||
"worker": worker,
|
||||
}).Trace("Database.getTask UPDATE task")
|
||||
|
||||
task := getTaskById(id, db)
|
||||
|
||||
return task
|
||||
}
|
||||
|
||||
func getTaskById(id int64, db *sql.DB) *Task {
|
||||
|
||||
row := db.QueryRow(`
|
||||
SELECT task.id, task.priority, task.project, assignee, retries, max_retries,
|
||||
status, recipe, max_assign_time, assign_time, verification_count, project.priority, project.name,
|
||||
project.clone_url, project.git_repo, project.version, project.motd, project.public, COALESCE(project.chain,0) FROM task
|
||||
INNER JOIN project project ON task.project = project.id
|
||||
WHERE task.id=$1`, id)
|
||||
project := &Project{}
|
||||
task := &Task{}
|
||||
task.Project = project
|
||||
|
||||
err := row.Scan(&task.Id, &task.Priority, &project.Id, &task.Assignee,
|
||||
&task.Retries, &task.MaxRetries, &task.Status, &task.Recipe, &task.MaxAssignTime,
|
||||
&task.AssignTime, &task.VerificationCount, &project.Priority, &project.Name,
|
||||
&project.CloneUrl, &project.GitRepo, &project.Version, &project.Motd, &project.Public,
|
||||
&project.Chain)
|
||||
handleErr(err)
|
||||
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"id": id,
|
||||
"task": task,
|
||||
}).Trace("Database.getTaskById SELECT task")
|
||||
|
||||
return task
|
||||
}
|
||||
|
||||
func (database Database) ReleaseTask(id int64, workerId int64, result TaskResult, verification int64) bool {
|
||||
|
||||
db := database.getDB()
|
||||
@ -205,12 +135,24 @@ func (database *Database) GetTaskFromProject(worker *Worker, projectId int64) *T
|
||||
return nil
|
||||
}
|
||||
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"id": id,
|
||||
"worker": worker,
|
||||
}).Trace("Database.getTask UPDATE task")
|
||||
row = db.QueryRow(`
|
||||
SELECT task.id, task.priority, task.project, assignee, retries, max_retries,
|
||||
status, recipe, max_assign_time, assign_time, verification_count, project.priority, project.name,
|
||||
project.clone_url, project.git_repo, project.version, project.motd, project.public, COALESCE(project.chain,0),
|
||||
project.assign_rate, project.submit_rate
|
||||
FROM task
|
||||
INNER JOIN project project ON task.project = project.id
|
||||
WHERE task.id=$1`, id)
|
||||
project := &Project{}
|
||||
task := &Task{}
|
||||
task.Project = project
|
||||
|
||||
task := getTaskById(id, db)
|
||||
err = row.Scan(&task.Id, &task.Priority, &project.Id, &task.Assignee,
|
||||
&task.Retries, &task.MaxRetries, &task.Status, &task.Recipe, &task.MaxAssignTime,
|
||||
&task.AssignTime, &task.VerificationCount, &project.Priority, &project.Name,
|
||||
&project.CloneUrl, &project.GitRepo, &project.Version, &project.Motd, &project.Public,
|
||||
&project.Chain, &project.AssignRate, &project.SubmitRate)
|
||||
handleErr(err)
|
||||
|
||||
return task
|
||||
}
|
||||
|
@ -13,14 +13,16 @@ import (
|
||||
func TestCreateGetProject(t *testing.T) {
|
||||
|
||||
resp := createProjectAsAdmin(api.CreateProjectRequest{
|
||||
Name: "Test name",
|
||||
CloneUrl: "http://github.com/test/test",
|
||||
GitRepo: "drone/webhooktest",
|
||||
Version: "Test Version",
|
||||
Priority: 123,
|
||||
Motd: "motd",
|
||||
Public: true,
|
||||
Hidden: false,
|
||||
Name: "Test name",
|
||||
CloneUrl: "http://github.com/test/test",
|
||||
GitRepo: "drone/webhooktest",
|
||||
Version: "Test Version",
|
||||
Priority: 123,
|
||||
Motd: "motd",
|
||||
Public: true,
|
||||
Hidden: false,
|
||||
AssignRate: 10.0,
|
||||
SubmitRate: 20.0,
|
||||
})
|
||||
|
||||
id := resp.Content.Id
|
||||
@ -64,6 +66,12 @@ func TestCreateGetProject(t *testing.T) {
|
||||
if getResp.Project.Hidden != false {
|
||||
t.Error()
|
||||
}
|
||||
if getResp.Project.SubmitRate != 20.0 {
|
||||
t.Error()
|
||||
}
|
||||
if getResp.Project.AssignRate != 10.0 {
|
||||
t.Error()
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateProjectInvalid(t *testing.T) {
|
||||
@ -107,24 +115,28 @@ func TestGetProjectNotFound(t *testing.T) {
|
||||
func TestUpdateProjectValid(t *testing.T) {
|
||||
|
||||
pid := createProjectAsAdmin(api.CreateProjectRequest{
|
||||
Public: true,
|
||||
Version: "versionA",
|
||||
Motd: "MotdA",
|
||||
Name: "NameA",
|
||||
CloneUrl: "CloneUrlA",
|
||||
GitRepo: "GitRepoA",
|
||||
Priority: 1,
|
||||
Public: true,
|
||||
Version: "versionA",
|
||||
Motd: "MotdA",
|
||||
Name: "NameA",
|
||||
CloneUrl: "CloneUrlA",
|
||||
GitRepo: "GitRepoA",
|
||||
Priority: 1,
|
||||
AssignRate: 3,
|
||||
SubmitRate: 3,
|
||||
}).Content.Id
|
||||
|
||||
updateResp := updateProject(api.UpdateProjectRequest{
|
||||
Priority: 2,
|
||||
GitRepo: "GitRepoB",
|
||||
CloneUrl: "CloneUrlB",
|
||||
Name: "NameB",
|
||||
Motd: "MotdB",
|
||||
Public: false,
|
||||
Hidden: true,
|
||||
Paused: true,
|
||||
Priority: 2,
|
||||
GitRepo: "GitRepoB",
|
||||
CloneUrl: "CloneUrlB",
|
||||
Name: "NameB",
|
||||
Motd: "MotdB",
|
||||
Public: false,
|
||||
Hidden: true,
|
||||
Paused: true,
|
||||
AssignRate: 1,
|
||||
SubmitRate: 2,
|
||||
}, pid, testAdminCtx)
|
||||
|
||||
if updateResp.Ok != true {
|
||||
@ -154,6 +166,12 @@ func TestUpdateProjectValid(t *testing.T) {
|
||||
if proj.Project.Paused != true {
|
||||
t.Error()
|
||||
}
|
||||
if proj.Project.AssignRate != 1 {
|
||||
t.Error()
|
||||
}
|
||||
if proj.Project.SubmitRate != 2 {
|
||||
t.Error()
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateProjectInvalid(t *testing.T) {
|
||||
|
80
test/api_rate_test.go
Normal file
80
test/api_rate_test.go
Normal file
@ -0,0 +1,80 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/simon987/task_tracker/api"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestAssignRateLimit(t *testing.T) {
|
||||
|
||||
project := createProjectAsAdmin(api.CreateProjectRequest{
|
||||
SubmitRate: 2,
|
||||
AssignRate: 2,
|
||||
Name: "testassignratelimit",
|
||||
GitRepo: "testassignratelimit",
|
||||
CloneUrl: "testassignratelimit",
|
||||
}).Content.Id
|
||||
|
||||
w := genWid()
|
||||
requestAccess(api.CreateWorkerAccessRequest{
|
||||
Project: project,
|
||||
Submit: true,
|
||||
Assign: true,
|
||||
}, w)
|
||||
acceptAccessRequest(project, w.Id, testAdminCtx)
|
||||
|
||||
for i := 0; i < 3; i++ {
|
||||
createTask(api.SubmitTaskRequest{
|
||||
Project: project,
|
||||
Recipe: fmt.Sprintf("%d", i),
|
||||
}, w)
|
||||
}
|
||||
|
||||
var lastResp TaskAR
|
||||
for i := 0; i < 3; i++ {
|
||||
lastResp = getTaskFromProject(project, w)
|
||||
}
|
||||
|
||||
if lastResp.Ok != false {
|
||||
t.Error()
|
||||
}
|
||||
if len(lastResp.Message) <= 0 {
|
||||
t.Error()
|
||||
}
|
||||
}
|
||||
|
||||
func TestSubmitRateLimit(t *testing.T) {
|
||||
|
||||
project := createProjectAsAdmin(api.CreateProjectRequest{
|
||||
SubmitRate: 2,
|
||||
AssignRate: 2,
|
||||
Name: "testsubmitratlimit",
|
||||
GitRepo: "testsubmitratelimit",
|
||||
CloneUrl: "testsubmitratelimit",
|
||||
}).Content.Id
|
||||
|
||||
w := genWid()
|
||||
requestAccess(api.CreateWorkerAccessRequest{
|
||||
Project: project,
|
||||
Submit: true,
|
||||
Assign: true,
|
||||
}, w)
|
||||
acceptAccessRequest(project, w.Id, testAdminCtx)
|
||||
|
||||
var lastResp api.JsonResponse
|
||||
for i := 0; i < 2; i++ {
|
||||
lastResp = createTask(api.SubmitTaskRequest{
|
||||
Project: project,
|
||||
Recipe: fmt.Sprintf("%d", i),
|
||||
}, w)
|
||||
}
|
||||
|
||||
if lastResp.Ok != false {
|
||||
t.Error()
|
||||
}
|
||||
if len(lastResp.Message) <= 0 {
|
||||
t.Error()
|
||||
}
|
||||
|
||||
}
|
@ -57,7 +57,7 @@ func TestCreateTaskInvalidProject(t *testing.T) {
|
||||
|
||||
func TestGetTaskInvalidWid(t *testing.T) {
|
||||
|
||||
resp := getTask(nil)
|
||||
resp := getTaskFromProject(testProject, genWid())
|
||||
|
||||
if resp.Ok != false {
|
||||
t.Error()
|
||||
@ -70,7 +70,7 @@ func TestGetTaskInvalidWid(t *testing.T) {
|
||||
|
||||
func TestGetTaskInvalidWorker(t *testing.T) {
|
||||
|
||||
resp := getTask(&storage.Worker{
|
||||
resp := getTaskFromProject(testProject, &storage.Worker{
|
||||
Id: -1,
|
||||
})
|
||||
|
||||
@ -138,12 +138,14 @@ func TestCreateTaskInvalidRecipe(t *testing.T) {
|
||||
func TestCreateGetTask(t *testing.T) {
|
||||
|
||||
pid := createProjectAsAdmin(api.CreateProjectRequest{
|
||||
Name: "My project",
|
||||
Version: "1.0",
|
||||
CloneUrl: "http://github.com/test/test",
|
||||
GitRepo: "myrepo",
|
||||
Priority: 999,
|
||||
Public: true,
|
||||
Name: "My project",
|
||||
Version: "1.0",
|
||||
CloneUrl: "http://github.com/test/test",
|
||||
GitRepo: "myrepo",
|
||||
Priority: 999,
|
||||
Public: true,
|
||||
AssignRate: 2,
|
||||
SubmitRate: 2,
|
||||
}).Content.Id
|
||||
|
||||
worker := genWid()
|
||||
@ -197,6 +199,12 @@ func TestCreateGetTask(t *testing.T) {
|
||||
if taskResp.Task.Project.Public != true {
|
||||
t.Error()
|
||||
}
|
||||
if taskResp.Task.Project.AssignRate == 1 {
|
||||
t.Error()
|
||||
}
|
||||
if taskResp.Task.Project.SubmitRate != 2 {
|
||||
t.Error()
|
||||
}
|
||||
}
|
||||
|
||||
func createTasks(prefix string) (int64, int64) {
|
||||
@ -279,36 +287,6 @@ func TestTaskProjectPriority(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestTaskPriority(t *testing.T) {
|
||||
|
||||
wid := genWid()
|
||||
|
||||
// Clean other tasks
|
||||
for i := 0; i < 20; i++ {
|
||||
getTask(wid)
|
||||
}
|
||||
|
||||
createTasks("")
|
||||
|
||||
t1 := getTask(wid).Content
|
||||
t2 := getTask(wid).Content
|
||||
t3 := getTask(wid).Content
|
||||
t4 := getTask(wid).Content
|
||||
|
||||
if t1.Task.Recipe != "high2" {
|
||||
t.Error()
|
||||
}
|
||||
if t2.Task.Recipe != "high1" {
|
||||
t.Error()
|
||||
}
|
||||
if t3.Task.Recipe != "low2" {
|
||||
t.Error()
|
||||
}
|
||||
if t4.Task.Recipe != "low1" {
|
||||
t.Error()
|
||||
}
|
||||
}
|
||||
|
||||
func TestTaskNoAccess(t *testing.T) {
|
||||
|
||||
worker := genWid()
|
||||
@ -400,15 +378,6 @@ func TestTaskHasAccess(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestNoMoreTasks(t *testing.T) {
|
||||
|
||||
worker := genWid()
|
||||
|
||||
for i := 0; i < 15; i++ {
|
||||
getTask(worker)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReleaseTaskSuccess(t *testing.T) {
|
||||
|
||||
worker := genWid()
|
||||
|
@ -16,17 +16,19 @@ CREATE TABLE project
|
||||
id SERIAL PRIMARY KEY NOT NULL,
|
||||
priority INTEGER DEFAULT 0 NOT NULL,
|
||||
closed_task_count INT DEFAULT 0 NOT NULL,
|
||||
chain INT DEFAULT NULL REFERENCES project (id),
|
||||
public boolean NOT NULL,
|
||||
hidden boolean NOT NULL,
|
||||
paused boolean NOT NULL,
|
||||
name TEXT UNIQUE NOT NULL,
|
||||
clone_url TEXT NOT NULL,
|
||||
git_repo TEXT NOT NULL,
|
||||
version TEXT NOT NULL,
|
||||
motd TEXT NOT NULL,
|
||||
secret TEXT NOT NULL DEFAULT '{}',
|
||||
webhook_secret TEXT NOT NULL
|
||||
chain INT DEFAULT NULL REFERENCES project (id),
|
||||
public boolean NOT NULL,
|
||||
hidden boolean NOT NULL,
|
||||
paused boolean NOT NULL,
|
||||
name TEXT UNIQUE NOT NULL,
|
||||
clone_url TEXT NOT NULL,
|
||||
git_repo TEXT NOT NULL,
|
||||
version TEXT NOT NULL,
|
||||
motd TEXT NOT NULL,
|
||||
secret TEXT NOT NULL DEFAULT '{}',
|
||||
webhook_secret TEXT NOT NULL,
|
||||
assign_rate DOUBLE PRECISION NOT NULL,
|
||||
submit_rate DOUBLE PRECISION NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE worker_access
|
||||
|
Loading…
x
Reference in New Issue
Block a user