This commit is contained in:
simon987 2019-01-12 19:52:51 -05:00
parent 83276ce8b0
commit a2b5de0e01
10 changed files with 490 additions and 109 deletions

View File

@ -19,7 +19,7 @@ type Info struct {
Version string `json:"version"`
}
var info = Info {
var info = Info{
Name: "task_tracker",
Version: "1.0",
}
@ -55,7 +55,8 @@ func New() *WebAPI {
api.router.GET("/project/get/:id", LogRequest(api.ProjectGet))
api.router.POST("/task/create", LogRequest(api.TaskCreate))
api.router.GET("/task/get/", LogRequest(api.TaskGet))
api.router.GET("/task/get/:project", LogRequest(api.TaskGetFromProject))
api.router.GET("/task/get", LogRequest(api.TaskGet))
return api
}

View File

@ -10,6 +10,7 @@ type CreateProjectRequest struct {
Name string `json:"name"`
GitUrl string `json:"git_url"`
Version string `json:"version"`
Priority int64 `json:"priority"`
}
type CreateProjectResponse struct {
@ -33,6 +34,7 @@ func (api *WebAPI) ProjectCreate(r *Request) {
Name: createReq.Name,
Version: createReq.Version,
GitUrl: createReq.GitUrl,
Priority: createReq.Priority,
}
if isValidProject(project) {
@ -41,7 +43,7 @@ func (api *WebAPI) ProjectCreate(r *Request) {
if err != nil {
r.Json(CreateProjectResponse{
Ok: false,
Message:err.Error(),
Message: err.Error(),
}, 500)
} else {
r.OkJson(CreateProjectResponse{
@ -81,7 +83,7 @@ func (api *WebAPI) ProjectGet(r *Request) {
if project != nil {
r.OkJson(GetProjectResponse{
Ok: true,
Project:project,
Project: project,
})
} else {
r.Json(GetProjectResponse{

View File

@ -1,19 +1,29 @@
package api
import (
"errors"
"github.com/Sirupsen/logrus"
"github.com/google/uuid"
"src/task_tracker/storage"
"strconv"
)
type CreateTaskRequest struct {
Project int64 `json:"project"`
MaxRetries int64 `json:"max_retries"`
Recipe string `json:"recipe"`
Priority int64 `json:"priority"`
}
type CreateTaskResponse struct {
Ok bool
Message string
Ok bool `json:"ok"`
Message string `json:"message,omitempty"`
}
type GetTaskResponse struct {
Ok bool `json:"ok"`
Message string `json:"message,omitempty"`
Task *storage.Task `json:"task,omitempty"`
}
func (api *WebAPI) TaskCreate(r *Request) {
@ -22,13 +32,13 @@ func (api *WebAPI) TaskCreate(r *Request) {
if r.GetJson(&createReq) {
task := &storage.Task{
Project:createReq.Project,
MaxRetries: createReq.MaxRetries,
Recipe:createReq.Recipe,
Recipe: createReq.Recipe,
Priority: createReq.Priority,
}
if isTaskValid(task) {
err := api.Database.SaveTask(task)
err := api.Database.SaveTask(task, createReq.Project)
if err != nil {
r.Json(CreateTaskResponse{
@ -49,7 +59,6 @@ func (api *WebAPI) TaskCreate(r *Request) {
Message: "Invalid task",
}, 400)
}
}
}
@ -57,9 +66,6 @@ func isTaskValid(task *storage.Task) bool {
if task.MaxRetries < 0 {
return false
}
if task.Project <= 0 {
return false
}
if len(task.Recipe) <= 0 {
return false
}
@ -67,7 +73,67 @@ func isTaskValid(task *storage.Task) bool {
return true
}
func (api *WebAPI) TaskGetFromProject(r *Request) {
worker, err := api.workerFromQueryArgs(r)
if err != nil {
r.Json(GetTaskResponse{
Ok: false,
Message: err.Error(),
}, 403)
return
}
project, err := strconv.Atoi(r.Ctx.UserValue("project").(string))
handleErr(err, r)
task := api.Database.GetTaskFromProject(worker, int64(project))
r.OkJson(GetTaskResponse{
Ok: true,
Task: task,
})
}
func (api *WebAPI) TaskGet(r *Request) {
worker, err := api.workerFromQueryArgs(r)
if err != nil {
r.Json(GetTaskResponse{
Ok: false,
Message: err.Error(),
}, 403)
return
}
task := api.Database.GetTask(worker)
r.OkJson(GetTaskResponse{
Ok: true,
Task: task,
})
}
func (api WebAPI) workerFromQueryArgs(r *Request) (*storage.Worker, error) {
widStr := string(r.Ctx.QueryArgs().Peek("wid"))
wid, err := uuid.Parse(widStr)
if err != nil {
logrus.WithError(err).WithFields(logrus.Fields{
"wid": widStr,
}).Warn("Can't parse wid")
return nil, err
}
worker := api.Database.GetWorker(wid)
if worker == nil {
logrus.WithError(err).WithFields(logrus.Fields{
"wid": widStr,
}).Warn("Can't parse wid")
return nil, errors.New("worker id does not match any valid worker")
}
return worker, nil
}

View File

@ -13,7 +13,7 @@ type CreateWorkerRequest struct {
type CreateWorkerResponse struct {
Ok bool `json:"ok"`
Message string `json:"message,omitempty"`
WorkerId string `json:"id,omitempty"`
WorkerId uuid.UUID `json:"id,omitempty"`
}
type GetWorkerResponse struct {
@ -36,7 +36,7 @@ func (api *WebAPI) WorkerCreate(r *Request) {
} else {
r.OkJson(CreateWorkerResponse{
Ok: true,
WorkerId: id.String(),
WorkerId: id,
})
}
@ -59,7 +59,7 @@ func (api *WebAPI) WorkerGet(r *Request) {
r.Json(GetWorkerResponse{
Ok: false,
Message:err.Error(),
Message: err.Error(),
}, 400)
return
}
@ -69,12 +69,12 @@ func (api *WebAPI) WorkerGet(r *Request) {
if worker != nil {
r.OkJson(GetWorkerResponse{
Ok: true,
Worker:worker,
Worker: worker,
})
} else {
r.Json(GetWorkerResponse{
Ok: false,
Message:"Worker not found",
Message: "Worker not found",
}, 404)
}
}

View File

@ -19,12 +19,13 @@ CREATE TABLE worker
(
id TEXT PRIMARY KEY,
created INTEGER,
identity INTEGER REFERENCES workerIdentity(id)
identity INTEGER REFERENCES workerIdentity (id)
);
CREATE TABLE project
(
id SERIAL PRIMARY KEY,
priority INTEGER DEFAULT 0,
name TEXT UNIQUE,
git_url TEXT,
version TEXT
@ -32,12 +33,14 @@ CREATE TABLE project
CREATE TABLE task
(
id TEXT PRIMARY KEY,
id SERIAL PRIMARY KEY,
priority INTEGER DEFAULT 0,
project INTEGER REFERENCES project (id),
assignee TEXT REFERENCES worker (id),
retries INTEGER DEFAULT 0,
max_retries INTEGER,
status Status DEFAULT 'new'
status Status DEFAULT 'new',
recipe TEXT
);

View File

@ -7,6 +7,7 @@ import (
type Project struct {
Id int64 `json:"id"`
Priority int64 `json:"priority"`
Name string `json:"name"`
GitUrl string `json:"git_url"`
Version string `json:"version"`
@ -23,8 +24,8 @@ func (database *Database) SaveProject(project *Project) (int64, error) {
func saveProject(project *Project, db *sql.DB) (int64, error) {
row := db.QueryRow("INSERT INTO project (name, git_url, version) VALUES ($1,$2,$3) RETURNING id",
project.Name, project.GitUrl, project.Version)
row := db.QueryRow("INSERT INTO project (name, git_url, version, priority) VALUES ($1,$2,$3, $4) RETURNING id",
project.Name, project.GitUrl, project.Version, project.Priority)
var id int64
err := row.Scan(&id)
@ -57,10 +58,10 @@ func getProject(id int64, db *sql.DB) *Project {
project := &Project{}
row := db.QueryRow("SELECT id, name, git_url, version FROM project WHERE id=$1",
row := db.QueryRow("SELECT id, name, git_url, version, priority FROM project WHERE id=$1",
id)
err := row.Scan(&project.Id, &project.Name, &project.GitUrl, &project.Version)
err := row.Scan(&project.Id, &project.Name, &project.GitUrl, &project.Version, &project.Priority)
if err != nil {
logrus.WithFields(logrus.Fields{
"id": id,

View File

@ -7,30 +7,32 @@ import (
)
type Task struct {
Id int64
Project int64
Assignee uuid.UUID
Retries int64
MaxRetries int64
Status string
Recipe string
Id int64 `json:"id"`
Priority int64 `json:"priority"`
Project *Project `json:"project"`
Assignee uuid.UUID `json:"assignee"`
Retries int64 `json:"retries"`
MaxRetries int64 `json:"max_retries"`
Status string `json:"status"`
Recipe string `json:"recipe"`
}
func (database *Database) SaveTask(task *Task) error {
func (database *Database) SaveTask(task *Task, project int64) error {
db := database.getDB()
taskErr := saveTask(task, db)
taskErr := saveTask(task, project, db)
err := db.Close()
handleErr(err)
return taskErr
}
func saveTask(task *Task, db *sql.DB) error {
func saveTask(task *Task, project int64, db *sql.DB) error {
res, err := db.Exec("INSERT INTO task (project, max_retries, recipe) "+
"VALUES ($1,$2,$3)",
task.Project, task.MaxRetries, task.Recipe)
res, err := db.Exec(`
INSERT INTO task (project, max_retries, recipe, priority)
VALUES ($1,$2,$3,$4)`,
project, task.MaxRetries, task.Recipe, task.Priority)
if err != nil {
logrus.WithError(err).WithFields(logrus.Fields{
"task": task,
@ -48,3 +50,123 @@ func saveTask(task *Task, db *sql.DB) error {
return nil
}
func (database *Database) GetTask(worker *Worker) *Task {
db := database.getDB()
task := getTask(worker, db)
err := db.Close()
handleErr(err)
return task
}
func getTask(worker *Worker, db *sql.DB) *Task {
row := db.QueryRow(`
UPDATE task
SET assignee=$1
WHERE id IN
(
SELECT task.id
FROM task
INNER JOIN project p on task.project = p.id
WHERE assignee IS NULL
ORDER BY p.priority DESC, task.priority DESC
LIMIT 1
)
RETURNING id`, worker.Id)
var id int64
err := row.Scan(&id)
if err != nil {
logrus.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 * FROM task
INNER JOIN project ON task.project = project.id
WHERE task.id=$1`, id)
task := scanTask(row)
logrus.WithFields(logrus.Fields{
"id": id,
"task": task,
}).Trace("Database.getTaskById SELECT task")
return task
}
func (database *Database) GetTaskFromProject(worker *Worker, project int64) *Task {
db := database.getDB()
task := getTaskFromProject(worker, project, db)
err := db.Close()
handleErr(err)
return task
}
func getTaskFromProject(worker *Worker, projectId int64, db *sql.DB) *Task {
row := db.QueryRow(`
UPDATE task
SET assignee=$1
WHERE id IN
(
SELECT task.id
FROM task
INNER JOIN project p on task.project = p.id
WHERE assignee IS NULL AND p.id=$2
ORDER BY p.priority DESC, task.priority DESC
LIMIT 1
)
RETURNING id`, worker.Id, projectId)
var id int64
err := row.Scan(&id)
if err != nil {
logrus.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 scanTask(row *sql.Row) *Task {
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, &project.Id,
&project.Priority, &project.Name, &project.GitUrl, &project.Version)
handleErr(err)
return task
}

View File

@ -15,6 +15,7 @@ func TestCreateGetProject(t *testing.T) {
Name: "Test name",
GitUrl: "http://github.com/test/test",
Version: "Test Version",
Priority: 123,
})
id := resp.Id
@ -29,26 +30,27 @@ func TestCreateGetProject(t *testing.T) {
getResp, _ := getProject(id)
if getResp.Project.Id != id {
t.Fail()
t.Error()
}
if getResp.Project.Name != "Test name" {
t.Fail()
t.Error()
}
if getResp.Project.Version != "Test Version" {
t.Fail()
t.Error()
}
if getResp.Project.GitUrl != "http://github.com/test/test" {
t.Fail()
t.Error()
}
if getResp.Project.Priority != 123 {
t.Error()
}
}
func TestCreateProjectInvalid(t *testing.T) {
resp := createProject(api.CreateProjectRequest{
})
resp := createProject(api.CreateProjectRequest{})
if resp.Ok != false {
t.Fail()

View File

@ -2,6 +2,8 @@ package test
import (
"encoding/json"
"fmt"
"github.com/google/uuid"
"io/ioutil"
"src/task_tracker/api"
"testing"
@ -14,13 +16,12 @@ func TestCreateTaskValid(t *testing.T) {
Name: "Some Test name",
Version: "Test Version",
GitUrl: "http://github.com/test/test",
})
resp := createTask(api.CreateTaskRequest{
Project:1,
Project: 1,
Recipe: "{}",
MaxRetries:3,
MaxRetries: 3,
})
if resp.Ok != true {
@ -31,9 +32,9 @@ func TestCreateTaskValid(t *testing.T) {
func TestCreateTaskInvalidProject(t *testing.T) {
resp := createTask(api.CreateTaskRequest{
Project:123456,
Project: 123456,
Recipe: "{}",
MaxRetries:3,
MaxRetries: 3,
})
if resp.Ok != false {
@ -48,8 +49,8 @@ func TestCreateTaskInvalidProject(t *testing.T) {
func TestCreateTaskInvalidRetries(t *testing.T) {
resp := createTask(api.CreateTaskRequest{
Project:1,
MaxRetries:-1,
Project: 1,
MaxRetries: -1,
})
if resp.Ok != false {
@ -61,6 +62,158 @@ func TestCreateTaskInvalidRetries(t *testing.T) {
}
}
func TestCreateGetTask(t *testing.T) {
//Make sure there is always a project for id:1
resp := createProject(api.CreateProjectRequest{
Name: "My project",
Version: "1.0",
GitUrl: "http://github.com/test/test",
Priority: 999,
})
createTask(api.CreateTaskRequest{
Project: resp.Id,
Recipe: "{\"url\":\"test\"}",
MaxRetries: 3,
Priority: 9999,
})
taskResp := getTaskFromProject(resp.Id, genWid())
if taskResp.Ok != true {
t.Error()
}
if taskResp.Task.Priority != 9999 {
t.Error()
}
if taskResp.Task.Id == 0 {
t.Error()
}
if taskResp.Task.Recipe != "{\"url\":\"test\"}" {
t.Error()
}
if taskResp.Task.Status != "new" {
t.Error()
}
if taskResp.Task.MaxRetries != 3 {
t.Error()
}
if taskResp.Task.Project.Id != resp.Id {
t.Error()
}
if taskResp.Task.Project.Priority != 999 {
t.Error()
}
if taskResp.Task.Project.Version != "1.0" {
t.Error()
}
if taskResp.Task.Project.GitUrl != "http://github.com/test/test" {
t.Error()
}
}
func createTasks(prefix string) (int64, int64) {
lowP := createProject(api.CreateProjectRequest{
Name: prefix + "low",
Version: "1.0",
GitUrl: "http://github.com/test/test",
Priority: 1,
})
highP := createProject(api.CreateProjectRequest{
Name: prefix + "high",
Version: "1.0",
GitUrl: "http://github.com/test/test",
Priority: 999,
})
createTask(api.CreateTaskRequest{
Project: lowP.Id,
Recipe: "low1",
Priority: 0,
})
createTask(api.CreateTaskRequest{
Project: lowP.Id,
Recipe: "low2",
Priority: 1,
})
createTask(api.CreateTaskRequest{
Project: highP.Id,
Recipe: "high1",
Priority: 100,
})
createTask(api.CreateTaskRequest{
Project: highP.Id,
Recipe: "high2",
Priority: 101,
})
return lowP.Id, highP.Id
}
func TestTaskProjectPriority(t *testing.T) {
wid := genWid()
l, h := createTasks("withProject")
t1 := getTaskFromProject(l, wid)
t2 := getTaskFromProject(l, wid)
t3 := getTaskFromProject(h, wid)
t4 := getTaskFromProject(h, wid)
if t1.Task.Recipe != "low2" {
t.Error()
}
if t2.Task.Recipe != "low1" {
t.Error()
}
if t3.Task.Recipe != "high2" {
t.Error()
}
if t4.Task.Recipe != "high1" {
t.Error()
}
}
func TestTaskPriority(t *testing.T) {
wid := genWid()
// Clean other tasks
for i := 0; i < 20; i++ {
getTask(wid)
}
createTasks("")
t1 := getTask(wid)
t2 := getTask(wid)
t3 := getTask(wid)
t4 := getTask(wid)
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 TestNoMoreTasks(t *testing.T) {
wid := genWid()
for i := 0; i < 30; i++ {
getTask(wid)
}
}
func createTask(request api.CreateTaskRequest) *api.CreateTaskResponse {
r := Post("/task/create", request)
@ -72,3 +225,27 @@ func createTask(request api.CreateTaskRequest) *api.CreateTaskResponse {
return &resp
}
func getTask(wid *uuid.UUID) *api.GetTaskResponse {
r := Get(fmt.Sprintf("/task/get?wid=%s", wid))
var resp api.GetTaskResponse
data, _ := ioutil.ReadAll(r.Body)
err := json.Unmarshal(data, &resp)
handleErr(err)
return &resp
}
func getTaskFromProject(project int64, wid *uuid.UUID) *api.GetTaskResponse {
r := Get(fmt.Sprintf("/task/get/%d?wid=%s", project, wid))
var resp api.GetTaskResponse
data, _ := ioutil.ReadAll(r.Body)
err := json.Unmarshal(data, &resp)
handleErr(err)
return &resp
}

View File

@ -3,6 +3,7 @@ package test
import (
"encoding/json"
"fmt"
"github.com/google/uuid"
"io/ioutil"
"net/http"
"src/task_tracker/api"
@ -21,13 +22,13 @@ func TestCreateGetWorker(t *testing.T) {
t.Fail()
}
getResp, r := getWorker(resp.WorkerId)
getResp, r := getWorker(resp.WorkerId.String())
if r.StatusCode != 200 {
t.Fail()
}
if resp.WorkerId != getResp.Worker.Id.String() {
if resp.WorkerId != getResp.Worker.Id {
t.Fail()
}
}
@ -81,3 +82,9 @@ func getWorker(id string) (*api.GetWorkerResponse, *http.Response) {
return resp, r
}
func genWid() *uuid.UUID {
resp, _ := createWorker(api.CreateWorkerRequest{})
return &resp.WorkerId
}