From 6b93017068ac6ea539de37706aecada82f6d413f Mon Sep 17 00:00:00 2001
From: Julian Rother <julianr@fsmpi.rwth-aachen.de>
Date: Thu, 4 Jan 2018 19:51:37 +0100
Subject: [PATCH] Implemented job canceling

---
 db_schema.sql                |  3 ++-
 jobs.py                      | 26 ++++++++++++++++++--------
 server.py                    |  2 +-
 templates/jobs_overview.html |  8 +++++++-
 4 files changed, 28 insertions(+), 11 deletions(-)

diff --git a/db_schema.sql b/db_schema.sql
index e9e0a11..67227e4 100644
--- a/db_schema.sql
+++ b/db_schema.sql
@@ -296,7 +296,8 @@ CREATE TABLE IF NOT EXISTS `jobs` (
   `last_ping` datetime NOT NULL DEFAULT '',
   `worker` text DEFAULT NULL,
   `data` text NOT NULL DEFAULT '{}',
-  `status` text NOT NULL DEFAULT '{}'
+  `status` text NOT NULL DEFAULT '{}',
+  `canceled` INTEGER DEFAULT 0
 );
 
 CREATE TABLE IF NOT EXISTS `responsible` (
diff --git a/jobs.py b/jobs.py
index 86b5519..9fb8070 100644
--- a/jobs.py
+++ b/jobs.py
@@ -18,9 +18,16 @@ def job_handler(*types, state='finished'):
 def schedule_job(jobtype, data=None, priority=0, queue="default"):
 	if not data:
 		data = {}
-	modify('INSERT INTO jobs (type, priority, queue, data, time_created) VALUES (?, ?, ?, ?, ?)',
+	return modify('INSERT INTO jobs (type, priority, queue, data, time_created) VALUES (?, ?, ?, ?, ?)',
 			jobtype, priority, queue, json.dumps(data, default=date_json_handler), datetime.now())
 
+def cancel_job(job_id):
+	modify('UPDATE jobs SET state = "deleted" WHERE id = ? AND state = "ready"', job_id)
+	modify('UPDATE jobs SET canceled = 1 WHERE id = ?', job_id)
+
+def restart_job(job_id):
+	modify('UPDATE jobs SET state = "ready" WHERE id = ? AND state = "failed" AND NOT canceled', job_id)
+
 @app.route('/internal/jobs/overview')
 @register_navbar('Jobs', iconlib='fa', icon='suitcase')
 @mod_required
@@ -61,12 +68,12 @@ def jobs_action(action, jobid=None):
 		query('UPDATE jobs SET state="deleted" WHERE state = "failed" AND (id = ? OR ? IS NULL)', jobid, jobid)
 	elif action == 'retry_failed':
 		query('UPDATE jobs SET state="ready" WHERE state = "failed" AND (id = ? OR ? IS NULL)', jobid, jobid)
-	elif action == 'copy':
-		if jobid:
-			query("INSERT INTO jobs SELECT NULL, type, priority, queue, 'ready', '', '' , ?, '', NULL, data, '{}' FROM jobs where ID=?;", datetime.now(), jobid)
-	elif action == 'delete':
-		if jobid:
-			query('UPDATE jobs SET state="deleted" WHERE id = ?', jobid)
+	elif action == 'copy' and jobid:
+		query("INSERT INTO jobs SELECT NULL, type, priority, queue, 'ready', '', '' , ?, '', NULL, data, '{}' FROM jobs where ID=?;", datetime.now(), jobid)
+	elif action == 'delete' and jobid:
+		query('UPDATE jobs SET state="deleted" WHERE id = ?', jobid)
+	elif action == 'cancel' and jobid:
+		cancel_job(jobid)
 	return redirect(request.values.get('ref', url_for('jobs_overview')))
 
 def jobs_api_token_required(func):
@@ -118,7 +125,10 @@ def jobs_ping(id):
 			func(id, job['type'], json.loads(job['data']), state, json.loads(job['status']))
 		except Exception:
 			traceback.print_exc()
-	return 'OK', 200
+	if job['canceled']:
+		return 'Job canceled', 205
+	else:
+		return 'OK', 200
 
 @app.route('/internal/jobs/api/worker/<hostname>/schedule', methods=['POST'])
 @jobs_api_token_required
diff --git a/server.py b/server.py
index 1c536d0..b762ae1 100644
--- a/server.py
+++ b/server.py
@@ -486,7 +486,7 @@ def dbstatus():
 def date_json_handler(obj):
 	return obj.isoformat() if hasattr(obj, 'isoformat') else obj
 
-from jobs import job_handler, schedule_job
+from jobs import job_handler, schedule_job, cancel_job, restart_job
 from edit import edit_handler
 import feeds
 import importer
diff --git a/templates/jobs_overview.html b/templates/jobs_overview.html
index 87aa4a0..2f099a4 100644
--- a/templates/jobs_overview.html
+++ b/templates/jobs_overview.html
@@ -192,7 +192,7 @@
 								<td>{{i.priority}}</td>
 								<td>{{i.worker}}</td>
 								<td>{{i.last_ping}}</td>
-								<td>{{i.state}}</td>
+								<td>{{i.state}}{% if i.canceled %} (canceled) {% endif %}</td>
 								<td>{{i.time_created}}</td>
 								<td>{{i.time_finished}}</td>
 								<td>{{i.time_scheduled}}</td>
@@ -223,6 +223,12 @@
 													<span class="glyphicon glyphicon-trash" aria-hidden="true"></span>
 												</a>
 											</li>
+										{% elif i.state == "running" %}
+											<li>
+												<a class="btn btn-default" {% if i.canceled %}disabled="disabled"{% endif %} href="{{url_for('jobs_action', action='cancel', jobid=i.id, ref=request.url)}}" title="Abbrechen" style="background-color: red;">
+													<span class="glyphicon glyphicon-stop" aria-hidden="true"></span>
+												</a>
+											</li>
 										{% endif %}
 									{% endif %}
 									</ul>
-- 
GitLab