Add API endpoint to pause a worker

This commit is contained in:
simon987 2019-05-05 19:09:45 -04:00
parent 8f10567bd0
commit 72c8e18044
8 changed files with 378 additions and 231 deletions

View File

@ -86,6 +86,7 @@ func New() *WebAPI {
api.router.POST("/worker/create", LogRequestMiddleware(api.CreateWorker)) api.router.POST("/worker/create", LogRequestMiddleware(api.CreateWorker))
api.router.POST("/worker/update", LogRequestMiddleware(api.UpdateWorker)) api.router.POST("/worker/update", LogRequestMiddleware(api.UpdateWorker))
api.router.POST("/worker/set_paused", LogRequestMiddleware(api.WorkerSetPaused))
api.router.GET("/worker/get/:id", LogRequestMiddleware(api.GetWorker)) api.router.GET("/worker/get/:id", LogRequestMiddleware(api.GetWorker))
api.router.GET("/worker/stats", LogRequestMiddleware(api.GetAllWorkerStats)) api.router.GET("/worker/stats", LogRequestMiddleware(api.GetAllWorkerStats))

View File

@ -269,6 +269,11 @@ type UpdateWorkerRequest struct {
Alias string `json:"alias"` Alias string `json:"alias"`
} }
type WorkerSetPausedRequest struct {
Worker int64 `json:"worker"`
Paused bool `json:"paused"`
}
type CreateWorkerRequest struct { type CreateWorkerRequest struct {
Alias string `json:"alias"` Alias string `json:"alias"`
} }

View File

@ -200,6 +200,14 @@ func (api *WebAPI) GetTaskFromProject(r *Request) {
return return
} }
if worker.Paused {
r.Json(JsonResponse{
Ok: false,
Message: "A manager has paused you",
}, 400)
return
}
project, err := strconv.ParseInt(r.Ctx.UserValue("project").(string), 10, 64) project, err := strconv.ParseInt(r.Ctx.UserValue("project").(string), 10, 64)
if err != nil || project <= 0 { if err != nil || project <= 0 {
r.Json(JsonResponse{ r.Json(JsonResponse{

View File

@ -126,6 +126,54 @@ func (api *WebAPI) UpdateWorker(r *Request) {
} }
} }
func (api *WebAPI) WorkerSetPaused(r *Request) {
sess := api.Session.StartFasthttp(r.Ctx)
manager := sess.Get("manager")
if manager == nil || !manager.(*storage.Manager).WebsiteAdmin {
r.Json(JsonResponse{
Ok: false,
Message: "Unauthorized",
}, 403)
return
}
req := &WorkerSetPausedRequest{}
err := json.Unmarshal(r.Ctx.Request.Body(), req)
if err != nil {
r.Json(JsonResponse{
Ok: false,
Message: "Could not parse request",
}, 400)
return
}
worker := api.Database.GetWorker(req.Worker)
if worker == nil {
r.Json(JsonResponse{
Ok: false,
Message: "Invalid worker",
}, 400)
return
}
worker.Paused = req.Paused
ok := api.Database.UpdateWorker(worker)
if ok {
r.OkJson(JsonResponse{
Ok: true,
})
} else {
r.OkJson(JsonResponse{
Ok: false,
Message: "Could not update worker",
})
}
}
func (api *WebAPI) GetAllWorkerStats(r *Request) { func (api *WebAPI) GetAllWorkerStats(r *Request) {
stats := api.Database.GetAllWorkerStats() stats := api.Database.GetAllWorkerStats()

View File

@ -1,179 +1,180 @@
DROP TABLE IF EXISTS worker, project, task, log_entry, DROP TABLE IF EXISTS worker, project, task, log_entry,
worker_access, manager, manager_has_role_on_project, project_monitoring_snapshot, worker_access, manager, manager_has_role_on_project, project_monitoring_snapshot,
worker_verifies_task; worker_verifies_task;
CREATE TABLE worker CREATE TABLE worker
( (
id SERIAL PRIMARY KEY NOT NULL, id SERIAL PRIMARY KEY NOT NULL,
alias TEXT NOT NULL, alias TEXT NOT NULL,
created INTEGER NOT NULL, created INTEGER NOT NULL,
secret BYTEA NOT NULL, secret BYTEA NOT NULL,
closed_task_count INTEGER DEFAULT 0 NOT NULL closed_task_count INTEGER DEFAULT 0 NOT NULL,
paused boolean NOT NULL DEFAULT false
); );
CREATE TABLE project CREATE TABLE project
( (
id SERIAL PRIMARY KEY NOT NULL, id SERIAL PRIMARY KEY NOT NULL,
priority INTEGER DEFAULT 0 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), chain INT DEFAULT NULL REFERENCES project (id),
public boolean NOT NULL, public boolean NOT NULL,
hidden boolean NOT NULL, hidden boolean NOT NULL,
paused boolean NOT NULL, paused boolean NOT NULL,
name TEXT UNIQUE NOT NULL, name TEXT UNIQUE NOT NULL,
clone_url TEXT NOT NULL, clone_url TEXT NOT NULL,
git_repo TEXT NOT NULL, git_repo TEXT NOT NULL,
version TEXT NOT NULL, version TEXT NOT NULL,
motd TEXT NOT NULL, motd TEXT NOT NULL,
secret TEXT NOT NULL DEFAULT '{}', secret TEXT NOT NULL DEFAULT '{}',
webhook_secret TEXT NOT NULL, webhook_secret TEXT NOT NULL,
assign_rate DOUBLE PRECISION NOT NULL, assign_rate DOUBLE PRECISION NOT NULL,
submit_rate DOUBLE PRECISION NOT NULL submit_rate DOUBLE PRECISION NOT NULL
); );
CREATE TABLE worker_access CREATE TABLE worker_access
( (
worker INTEGER REFERENCES worker (id), worker INTEGER REFERENCES worker (id),
project INTEGER REFERENCES project (id), project INTEGER REFERENCES project (id),
role_assign boolean, role_assign boolean,
role_submit boolean, role_submit boolean,
request boolean, request boolean,
primary key (worker, project) primary key (worker, project)
); );
CREATE TABLE task CREATE TABLE task
( (
hash64 BIGINT DEFAULT NULL, hash64 BIGINT DEFAULT NULL,
id SERIAL PRIMARY KEY, id SERIAL PRIMARY KEY,
project INTEGER REFERENCES project (id), project INTEGER REFERENCES project (id),
assignee INTEGER REFERENCES worker (id), assignee INTEGER REFERENCES worker (id),
max_assign_time INTEGER DEFAULT 0, max_assign_time INTEGER DEFAULT 0,
assign_time INTEGER DEFAULT NULL, assign_time INTEGER DEFAULT NULL,
verification_count INTEGER DEFAULT 0, verification_count INTEGER DEFAULT 0,
priority SMALLINT DEFAULT 0, priority SMALLINT DEFAULT 0,
retries SMALLINT DEFAULT 0, retries SMALLINT DEFAULT 0,
max_retries SMALLINT, max_retries SMALLINT,
status SMALLINT DEFAULT 1, status SMALLINT DEFAULT 1,
recipe TEXT, recipe TEXT,
UNIQUE (project, hash64) UNIQUE (project, hash64)
); );
CREATE TABLE worker_verifies_task CREATE TABLE worker_verifies_task
( (
verification_hash BIGINT NOT NULL, verification_hash BIGINT NOT NULL,
task BIGINT REFERENCES task (id) ON DELETE CASCADE NOT NULL, task BIGINT REFERENCES task (id) ON DELETE CASCADE NOT NULL,
worker INT REFERENCES worker (id) NOT NULL worker INT REFERENCES worker (id) NOT NULL
); );
CREATE TABLE log_entry CREATE TABLE log_entry
( (
level INTEGER NOT NULL, level INTEGER NOT NULL,
message TEXT NOT NULL, message TEXT NOT NULL,
message_data TEXT NOT NULL, message_data TEXT NOT NULL,
timestamp INTEGER NOT NULL timestamp INTEGER NOT NULL
); );
CREATE TABLE manager CREATE TABLE manager
( (
id SERIAL PRIMARY KEY, id SERIAL PRIMARY KEY,
register_time INTEGER NOT NULL, register_time INTEGER NOT NULL,
tracker_admin BOOLEAN NOT NULL, tracker_admin BOOLEAN NOT NULL,
username TEXT UNIQUE NOT NULL, username TEXT UNIQUE NOT NULL,
password BYTEA NOT NULL password BYTEA NOT NULL
); );
CREATE TABLE manager_has_role_on_project CREATE TABLE manager_has_role_on_project
( (
manager INTEGER REFERENCES manager (id) NOT NULL, manager INTEGER REFERENCES manager (id) NOT NULL,
role SMALLINT NOT NULL, role SMALLINT NOT NULL,
project INTEGER REFERENCES project (id) NOT NULL, project INTEGER REFERENCES project (id) NOT NULL,
PRIMARY KEY (manager, project) PRIMARY KEY (manager, project)
); );
CREATE TABLE project_monitoring_snapshot CREATE TABLE project_monitoring_snapshot
( (
project INT REFERENCES project (id) NOT NULL, project INT REFERENCES project (id) NOT NULL,
new_task_count INT NOT NULL, new_task_count INT NOT NULL,
failed_task_count INT NOT NULL, failed_task_count INT NOT NULL,
closed_task_count INT NOT NULL, closed_task_count INT NOT NULL,
awaiting_verification_task_count INT NOT NULL, awaiting_verification_task_count INT NOT NULL,
worker_access_count INT NOT NULL, worker_access_count INT NOT NULL,
timestamp INT NOT NULL timestamp INT NOT NULL
); );
CREATE OR REPLACE FUNCTION on_task_delete_proc() RETURNS TRIGGER AS CREATE OR REPLACE FUNCTION on_task_delete_proc() RETURNS TRIGGER AS
$$ $$
DECLARE DECLARE
chain INTEGER; chain INTEGER;
BEGIN BEGIN
if OLD.assignee IS NOT NULL THEN if OLD.assignee IS NOT NULL THEN
UPDATE project UPDATE project
SET closed_task_count=closed_task_count + 1 SET closed_task_count=closed_task_count + 1
WHERE id = OLD.project returning project.chain into chain; WHERE id = OLD.project returning project.chain into chain;
UPDATE worker SET closed_task_count=closed_task_count + 1 WHERE id = OLD.assignee; UPDATE worker SET closed_task_count=closed_task_count + 1 WHERE id = OLD.assignee;
IF chain != 0 THEN IF chain != 0 THEN
INSERT into task (hash64, project, assignee, max_assign_time, assign_time, verification_count, INSERT into task (hash64, project, assignee, max_assign_time, assign_time, verification_count,
priority, retries, max_retries, status, recipe) priority, retries, max_retries, status, recipe)
VALUES (old.hash64, chain, NULL, old.max_assign_time, NULL, VALUES (old.hash64, chain, NULL, old.max_assign_time, NULL,
old.verification_count, old.priority, 0, old.max_retries, 1, old.verification_count, old.priority, 0, old.max_retries, 1,
old.recipe) old.recipe)
ON CONFLICT DO NOTHING; ON CONFLICT DO NOTHING;
end if;
end if; end if;
end if; RETURN OLD;
RETURN OLD;
END; END;
$$ LANGUAGE 'plpgsql'; $$ LANGUAGE 'plpgsql';
CREATE TRIGGER on_task_delete CREATE TRIGGER on_task_delete
BEFORE DELETE BEFORE DELETE
ON task ON task
FOR EACH ROW FOR EACH ROW
EXECUTE PROCEDURE on_task_delete_proc(); EXECUTE PROCEDURE on_task_delete_proc();
CREATE OR REPLACE FUNCTION on_manager_insert() RETURNS TRIGGER AS CREATE OR REPLACE FUNCTION on_manager_insert() RETURNS TRIGGER AS
$$ $$
BEGIN BEGIN
IF NEW.id = 1 THEN IF NEW.id = 1 THEN
UPDATE manager SET tracker_admin= TRUE WHERE id = 1; UPDATE manager SET tracker_admin= TRUE WHERE id = 1;
end if; end if;
RETURN NEW; RETURN NEW;
END; END;
$$ LANGUAGE 'plpgsql'; $$ LANGUAGE 'plpgsql';
CREATE TRIGGER on_manager_insert CREATE TRIGGER on_manager_insert
AFTER INSERT AFTER INSERT
ON manager ON manager
FOR EACH ROW FOR EACH ROW
EXECUTE PROCEDURE on_manager_insert(); EXECUTE PROCEDURE on_manager_insert();
CREATE OR REPLACE FUNCTION release_task_ok(wid INT, tid INT, ver BIGINT) RETURNS BOOLEAN AS CREATE OR REPLACE FUNCTION release_task_ok(wid INT, tid INT, ver BIGINT) RETURNS BOOLEAN AS
$$ $$
DECLARE DECLARE
res INT = NULL; res INT = NULL;
BEGIN BEGIN
DELETE FROM task WHERE id = tid AND assignee = wid AND verification_count < 2 RETURNING project INTO res; DELETE FROM task WHERE id = tid AND assignee = wid AND verification_count < 2 RETURNING project INTO res;
IF res IS NULL THEN
INSERT INTO worker_verifies_task (worker, verification_hash, task)
SELECT wid, ver, task.id
FROM task
WHERE assignee = wid
AND task.id = tid;
DELETE
FROM task
WHERE id = tid
AND assignee = wid
AND (SELECT COUNT(*) as vcnt
FROM worker_verifies_task wvt
WHERE task = tid
GROUP BY wvt.verification_hash
ORDER BY vcnt DESC
LIMIT 1) >= task.verification_count RETURNING task.id INTO res;
IF res IS NULL THEN IF res IS NULL THEN
UPDATE task SET assignee= NULL WHERE id = tid AND assignee = wid; INSERT INTO worker_verifies_task (worker, verification_hash, task)
end if; SELECT wid, ver, task.id
end if; FROM task
WHERE assignee = wid
AND task.id = tid;
RETURN res IS NOT NULL; DELETE
FROM task
WHERE id = tid
AND assignee = wid
AND (SELECT COUNT(*) as vcnt
FROM worker_verifies_task wvt
WHERE task = tid
GROUP BY wvt.verification_hash
ORDER BY vcnt DESC
LIMIT 1) >= task.verification_count RETURNING task.id INTO res;
IF res IS NULL THEN
UPDATE task SET assignee= NULL WHERE id = tid AND assignee = wid;
end if;
end if;
RETURN res IS NOT NULL;
END; END;
$$ LANGUAGE 'plpgsql'; $$ LANGUAGE 'plpgsql';

View File

@ -9,6 +9,7 @@ type Worker struct {
Created int64 `json:"created"` Created int64 `json:"created"`
Alias string `json:"alias,omitempty"` Alias string `json:"alias,omitempty"`
Secret []byte `json:"secret"` Secret []byte `json:"secret"`
Paused bool `json:"paused"`
} }
type WorkerStats struct { type WorkerStats struct {
@ -96,8 +97,8 @@ func (database *Database) GrantAccess(workerId int64, projectId int64) bool {
func (database *Database) UpdateWorker(worker *Worker) bool { func (database *Database) UpdateWorker(worker *Worker) bool {
db := database.getDB() db := database.getDB()
res, err := db.Exec(`UPDATE worker SET alias=$1 WHERE id=$2`, res, err := db.Exec(`UPDATE worker SET alias=$1, paused=$2 WHERE id=$3`,
worker.Alias, worker.Id) worker.Alias, worker.Paused, worker.Id)
handleErr(err) handleErr(err)
rowsAffected, _ := res.RowsAffected() rowsAffected, _ := res.RowsAffected()

View File

@ -6,6 +6,7 @@ import (
"github.com/simon987/task_tracker/client" "github.com/simon987/task_tracker/client"
"github.com/simon987/task_tracker/storage" "github.com/simon987/task_tracker/storage"
"net/http" "net/http"
"strings"
"testing" "testing"
) )
@ -29,6 +30,10 @@ func TestCreateGetWorker(t *testing.T) {
if w.Alias != "my_worker_alias" { if w.Alias != "my_worker_alias" {
t.Error() t.Error()
} }
if w.Paused != false {
t.Error()
}
} }
func TestGetWorkerNotFound(t *testing.T) { func TestGetWorkerNotFound(t *testing.T) {
@ -68,6 +73,10 @@ func TestUpdateAliasValid(t *testing.T) {
if w.Alias != "new alias" { if w.Alias != "new alias" {
t.Error() t.Error()
} }
if w.Paused != false {
t.Error()
}
} }
func TestCreateWorkerAliasInvalid(t *testing.T) { func TestCreateWorkerAliasInvalid(t *testing.T) {
@ -109,6 +118,74 @@ func TestInvalidAccessRequest(t *testing.T) {
} }
} }
func TestAssignTaskWhenPaused(t *testing.T) {
w := genWid()
pid := createProjectAsAdmin(api.CreateProjectRequest{
Name: "testassigntaskwhenpaused",
CloneUrl: "testassigntaskwhenpaused",
GitRepo: "testassigntaskwhenpaused",
}).Content.Id
requestAccess(api.CreateWorkerAccessRequest{
Submit: true,
Assign: true,
Project: pid,
}, w)
acceptAccessRequest(pid, w.Id, testAdminCtx)
r := createTask(api.SubmitTaskRequest{
Project: pid,
Recipe: "a",
Hash64: 1,
}, w)
if r.Ok != true {
t.Error()
}
pauseWorker(&api.WorkerSetPausedRequest{
Paused: true,
Worker: w.Id,
}, testAdminCtx)
resp := getTaskFromProject(pid, w)
if resp.Ok != false {
t.Error()
}
if !strings.Contains(resp.Message, "paused") {
t.Error()
}
}
func TestPauseInvalidWorker(t *testing.T) {
r := pauseWorker(&api.WorkerSetPausedRequest{
Paused: true,
Worker: 9999111,
}, testAdminCtx)
if r.Ok != false {
t.Error()
}
}
func TestPauseUnauthorized(t *testing.T) {
w := genWid()
r := pauseWorker(&api.WorkerSetPausedRequest{
Paused: true,
Worker: w.Id,
}, testUserCtx)
if r.Ok != false {
t.Error()
}
}
func createWorker(req api.CreateWorkerRequest) (ar client.CreateWorkerResponse) { func createWorker(req api.CreateWorkerRequest) (ar client.CreateWorkerResponse) {
r := Post("/worker/create", req, nil, nil) r := Post("/worker/create", req, nil, nil)
UnmarshalResponse(r, &ar) UnmarshalResponse(r, &ar)
@ -147,8 +224,13 @@ func rejectAccessRequest(pid int64, wid int64, s *http.Client) (ar api.JsonRespo
} }
func updateWorker(request api.UpdateWorkerRequest, w *storage.Worker) (ar api.JsonResponse) { func updateWorker(request api.UpdateWorkerRequest, w *storage.Worker) (ar api.JsonResponse) {
r := Post("/worker/update", request, w, nil) r := Post("/worker/update", request, w, nil)
UnmarshalResponse(r, &ar) UnmarshalResponse(r, &ar)
return return
} }
func pauseWorker(request *api.WorkerSetPausedRequest, s *http.Client) (ar api.JsonResponse) {
r := Post("/worker/set_paused", request, nil, s)
UnmarshalResponse(r, &ar)
return
}

View File

@ -1,180 +1,181 @@
DROP TABLE IF EXISTS worker, project, task, log_entry, DROP TABLE IF EXISTS worker, project, task, log_entry,
worker_access, manager, manager_has_role_on_project, project_monitoring_snapshot, worker_access, manager, manager_has_role_on_project, project_monitoring_snapshot,
worker_verifies_task; worker_verifies_task;
CREATE TABLE worker CREATE TABLE worker
( (
id SERIAL PRIMARY KEY NOT NULL, id SERIAL PRIMARY KEY NOT NULL,
alias TEXT NOT NULL, alias TEXT NOT NULL,
created INTEGER NOT NULL, created INTEGER NOT NULL,
secret BYTEA NOT NULL, secret BYTEA NOT NULL,
closed_task_count INTEGER DEFAULT 0 NOT NULL closed_task_count INTEGER DEFAULT 0 NOT NULL,
paused boolean NOT NULL DEFAULT false
); );
CREATE TABLE project CREATE TABLE project
( (
id SERIAL PRIMARY KEY NOT NULL, id SERIAL PRIMARY KEY NOT NULL,
priority INTEGER DEFAULT 0 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), chain INT DEFAULT NULL REFERENCES project (id),
public boolean NOT NULL, public boolean NOT NULL,
hidden boolean NOT NULL, hidden boolean NOT NULL,
paused boolean NOT NULL, paused boolean NOT NULL,
name TEXT UNIQUE NOT NULL, name TEXT UNIQUE NOT NULL,
clone_url TEXT NOT NULL, clone_url TEXT NOT NULL,
git_repo TEXT NOT NULL, git_repo TEXT NOT NULL,
version TEXT NOT NULL, version TEXT NOT NULL,
motd TEXT NOT NULL, motd TEXT NOT NULL,
secret TEXT NOT NULL DEFAULT '{}', secret TEXT NOT NULL DEFAULT '{}',
webhook_secret TEXT NOT NULL, webhook_secret TEXT NOT NULL,
assign_rate DOUBLE PRECISION NOT NULL, assign_rate DOUBLE PRECISION NOT NULL,
submit_rate DOUBLE PRECISION NOT NULL submit_rate DOUBLE PRECISION NOT NULL
); );
CREATE TABLE worker_access CREATE TABLE worker_access
( (
worker INTEGER REFERENCES worker (id), worker INTEGER REFERENCES worker (id),
project INTEGER REFERENCES project (id), project INTEGER REFERENCES project (id),
role_assign boolean, role_assign boolean,
role_submit boolean, role_submit boolean,
request boolean, request boolean,
primary key (worker, project) primary key (worker, project)
); );
CREATE TABLE task CREATE TABLE task
( (
hash64 BIGINT DEFAULT NULL, hash64 BIGINT DEFAULT NULL,
id SERIAL PRIMARY KEY, id SERIAL PRIMARY KEY,
project INTEGER REFERENCES project (id), project INTEGER REFERENCES project (id),
assignee INTEGER REFERENCES worker (id), assignee INTEGER REFERENCES worker (id),
max_assign_time INTEGER DEFAULT 0, max_assign_time INTEGER DEFAULT 0,
assign_time INTEGER DEFAULT NULL, assign_time INTEGER DEFAULT NULL,
verification_count INTEGER DEFAULT 0, verification_count INTEGER DEFAULT 0,
priority SMALLINT DEFAULT 0, priority SMALLINT DEFAULT 0,
retries SMALLINT DEFAULT 0, retries SMALLINT DEFAULT 0,
max_retries SMALLINT, max_retries SMALLINT,
status SMALLINT DEFAULT 1, status SMALLINT DEFAULT 1,
recipe TEXT, recipe TEXT,
UNIQUE (project, hash64) UNIQUE (project, hash64)
); );
CREATE TABLE worker_verifies_task CREATE TABLE worker_verifies_task
( (
verification_hash BIGINT NOT NULL, verification_hash BIGINT NOT NULL,
task BIGINT REFERENCES task (id) ON DELETE CASCADE NOT NULL, task BIGINT REFERENCES task (id) ON DELETE CASCADE NOT NULL,
worker INT REFERENCES worker (id) NOT NULL worker INT REFERENCES worker (id) NOT NULL
); );
CREATE TABLE log_entry CREATE TABLE log_entry
( (
level INTEGER NOT NULL, level INTEGER NOT NULL,
message TEXT NOT NULL, message TEXT NOT NULL,
message_data TEXT NOT NULL, message_data TEXT NOT NULL,
timestamp INTEGER NOT NULL timestamp INTEGER NOT NULL
); );
CREATE TABLE manager CREATE TABLE manager
( (
id SERIAL PRIMARY KEY, id SERIAL PRIMARY KEY,
register_time INTEGER NOT NULL, register_time INTEGER NOT NULL,
tracker_admin BOOLEAN NOT NULL, tracker_admin BOOLEAN NOT NULL,
username TEXT UNIQUE NOT NULL, username TEXT UNIQUE NOT NULL,
password BYTEA NOT NULL password BYTEA NOT NULL
); );
CREATE TABLE manager_has_role_on_project CREATE TABLE manager_has_role_on_project
( (
manager INTEGER REFERENCES manager (id) NOT NULL, manager INTEGER REFERENCES manager (id) NOT NULL,
role SMALLINT NOT NULL, role SMALLINT NOT NULL,
project INTEGER REFERENCES project (id) NOT NULL, project INTEGER REFERENCES project (id) NOT NULL,
PRIMARY KEY (manager, project) PRIMARY KEY (manager, project)
); );
CREATE TABLE project_monitoring_snapshot CREATE TABLE project_monitoring_snapshot
( (
project INT REFERENCES project (id) NOT NULL, project INT REFERENCES project (id) NOT NULL,
new_task_count INT NOT NULL, new_task_count INT NOT NULL,
failed_task_count INT NOT NULL, failed_task_count INT NOT NULL,
closed_task_count INT NOT NULL, closed_task_count INT NOT NULL,
awaiting_verification_task_count INT NOT NULL, awaiting_verification_task_count INT NOT NULL,
worker_access_count INT NOT NULL, worker_access_count INT NOT NULL,
timestamp INT NOT NULL timestamp INT NOT NULL
); );
CREATE OR REPLACE FUNCTION on_task_delete_proc() RETURNS TRIGGER AS CREATE OR REPLACE FUNCTION on_task_delete_proc() RETURNS TRIGGER AS
$$ $$
DECLARE DECLARE
chain INTEGER; chain INTEGER;
BEGIN BEGIN
if OLD.assignee IS NOT NULL THEN if OLD.assignee IS NOT NULL THEN
UPDATE project UPDATE project
SET closed_task_count=closed_task_count + 1 SET closed_task_count=closed_task_count + 1
WHERE id = OLD.project returning project.chain into chain; WHERE id = OLD.project returning project.chain into chain;
UPDATE worker SET closed_task_count=closed_task_count + 1 WHERE id = OLD.assignee; UPDATE worker SET closed_task_count=closed_task_count + 1 WHERE id = OLD.assignee;
IF chain != 0 THEN IF chain != 0 THEN
INSERT into task (hash64, project, assignee, max_assign_time, assign_time, verification_count, INSERT into task (hash64, project, assignee, max_assign_time, assign_time, verification_count,
priority, retries, max_retries, status, recipe) priority, retries, max_retries, status, recipe)
VALUES (old.hash64, chain, NULL, old.max_assign_time, NULL, VALUES (old.hash64, chain, NULL, old.max_assign_time, NULL,
old.verification_count, old.priority, 0, old.max_retries, 1, old.verification_count, old.priority, 0, old.max_retries, 1,
old.recipe) old.recipe)
ON CONFLICT DO NOTHING; ON CONFLICT DO NOTHING;
end if;
end if; end if;
end if; RETURN OLD;
RETURN OLD;
END; END;
$$ LANGUAGE 'plpgsql'; $$ LANGUAGE 'plpgsql';
CREATE TRIGGER on_task_delete CREATE TRIGGER on_task_delete
BEFORE DELETE BEFORE DELETE
ON task ON task
FOR EACH ROW FOR EACH ROW
EXECUTE PROCEDURE on_task_delete_proc(); EXECUTE PROCEDURE on_task_delete_proc();
CREATE OR REPLACE FUNCTION on_manager_insert() RETURNS TRIGGER AS CREATE OR REPLACE FUNCTION on_manager_insert() RETURNS TRIGGER AS
$$ $$
BEGIN BEGIN
IF NEW.id = 1 THEN IF NEW.id = 1 THEN
UPDATE manager SET tracker_admin= TRUE WHERE id = 1; UPDATE manager SET tracker_admin= TRUE WHERE id = 1;
end if; end if;
RETURN NEW; RETURN NEW;
END; END;
$$ LANGUAGE 'plpgsql'; $$ LANGUAGE 'plpgsql';
CREATE TRIGGER on_manager_insert CREATE TRIGGER on_manager_insert
AFTER INSERT AFTER INSERT
ON manager ON manager
FOR EACH ROW FOR EACH ROW
EXECUTE PROCEDURE on_manager_insert(); EXECUTE PROCEDURE on_manager_insert();
CREATE OR REPLACE FUNCTION release_task_ok(wid INT, tid INT, ver BIGINT) RETURNS BOOLEAN AS CREATE OR REPLACE FUNCTION release_task_ok(wid INT, tid INT, ver BIGINT) RETURNS BOOLEAN AS
$$ $$
DECLARE DECLARE
res INT = NULL; res INT = NULL;
BEGIN BEGIN
DELETE FROM task WHERE id = tid AND assignee = wid AND verification_count < 2 RETURNING project INTO res; DELETE FROM task WHERE id = tid AND assignee = wid AND verification_count < 2 RETURNING project INTO res;
IF res IS NULL THEN
INSERT INTO worker_verifies_task (worker, verification_hash, task)
SELECT wid, ver, task.id
FROM task
WHERE assignee = wid
AND task.id = tid;
DELETE
FROM task
WHERE id = tid
AND assignee = wid
AND (SELECT COUNT(*) as vcnt
FROM worker_verifies_task wvt
WHERE task = tid
GROUP BY wvt.verification_hash
ORDER BY vcnt DESC
LIMIT 1) >= task.verification_count RETURNING task.id INTO res;
IF res IS NULL THEN IF res IS NULL THEN
UPDATE task SET assignee= NULL WHERE id = tid AND assignee = wid; INSERT INTO worker_verifies_task (worker, verification_hash, task)
end if; SELECT wid, ver, task.id
end if; FROM task
WHERE assignee = wid
AND task.id = tid;
RETURN res IS NOT NULL; DELETE
FROM task
WHERE id = tid
AND assignee = wid
AND (SELECT COUNT(*) as vcnt
FROM worker_verifies_task wvt
WHERE task = tid
GROUP BY wvt.verification_hash
ORDER BY vcnt DESC
LIMIT 1) >= task.verification_count RETURNING task.id INTO res;
IF res IS NULL THEN
UPDATE task SET assignee= NULL WHERE id = tid AND assignee = wid;
end if;
end if;
RETURN res IS NOT NULL;
END; END;
$$ LANGUAGE 'plpgsql'; $$ LANGUAGE 'plpgsql';