canceling jobs and tasks
authorMaciej Tronowski <mtro@man.poznan.pl>
Tue, 28 Apr 2015 09:02:02 +0000 (11:02 +0200)
committerMaciej Tronowski <mtro@man.poznan.pl>
Tue, 28 Apr 2015 09:02:02 +0000 (11:02 +0200)
qcg/models.py
qcg/service.py
qcg/templates/qcg/job.html
qcg/templates/qcg/task.html
qcg/urls.py
qcg/views.py

index f452874..fa5065b 100644 (file)
@@ -78,6 +78,10 @@ class Job(models.Model):
 
         return attrs
 
+    @property
+    def terminated(self):
+        return self.get_status_display() in [JobStatus.FINISHED, JobStatus.FAILED, JobStatus.CANCELED]
+
 
 class Task(models.Model):
     STATUS_CHOICES = list(enumerate(field for field in dir(TaskStatus) if not field.startswith('__')))
@@ -159,6 +163,10 @@ class Task(models.Model):
     def short_host_names(self):
         return {alloc.host_name.split('.', 1)[0] for alloc in self.allocations.all()}
 
+    @property
+    def terminated(self):
+        return self.get_status_display() in [TaskStatus.FINISHED, TaskStatus.FAILED, TaskStatus.CANCELED]
+
 
 class Allocation(models.Model):
     STATUS_CHOICES = list(enumerate(field for field in dir(AllocationType) if not field.startswith('__')))
index f950aac..c87f40a 100644 (file)
@@ -85,7 +85,7 @@ def update_user_data(user, proxy):
 
 @transaction.atomic
 def update_job(job, proxy):
-    if job.get_status_display() in [JobStatus.FINISHED, JobStatus.FAILED, JobStatus.CANCELED]:
+    if job.terminated:
         return
 
     ts = time.time()
@@ -164,3 +164,19 @@ def submit_job(params, proxy):
     job = desc.submit()
 
     return job.job_id
+
+
+def cancel(obj, proxy):
+    ts = time.time()
+    QCG.start()
+
+    qcg_obj = obj.qcg_job if isinstance(obj, Job) else obj.qcg_task
+    qcg_obj.credential = Credential(proxy)
+
+    jts = time.time()
+    qcg_obj.cancel()
+    elapsed_cancel = time.time() - jts
+
+    elapsed = time.time() - ts
+    elapsed_py = elapsed - elapsed_cancel
+    logger.info('(%.3f) OBJ = %s (%.3f), TIME = %.3f', elapsed, obj, elapsed_cancel, elapsed_py)
index 114a329..0189ef2 100644 (file)
@@ -7,6 +7,15 @@
         <li class="active">{{ job.job_id }}</li>
     </ol>
 
+    <div class="pull-right">
+        {% if not job.terminated %}
+            <form action="{% url 'job_cancel' job.job_id %}" method="post">
+                {% csrf_token %}
+                <button type="submit" class="btn btn-default">Anuluj zadanie</button>
+            </form>
+        {% endif %}
+    </div>
+
     <h1 class="page-header">{% block title %}Job {{ job.job_id }}{% endblock %}</h1>
 
     <div role="tabpanel">
index 396b29e..8615cb8 100644 (file)
         <li class="active">{{ task.task_id }}</li>
     </ol>
 
+    <div class="pull-right">
+        {% if not task.terminated %}
+            <form action="{% url 'task_cancel' task.job.job_id task.task_id %}" method="post">
+                {% csrf_token %}
+                <button type="submit" class="btn btn-default">Anuluj zadanie</button>
+            </form>
+        {% endif %}
+    </div>
+
     <h1 class="page-header">{% block title %}Task {{ task.task_id }}{% endblock %}</h1>
 
     <div role="tabpanel">
index 798950b..1353522 100644 (file)
@@ -14,6 +14,9 @@ urlpatterns = patterns('',
     url(r'^$', views.index, name='index'),
     url(r'^jobs/$', views.jobs_list, name='jobs'),
     url(r'^job/submit/$', views.job_submit, name='job_submit'),
+    url(r'^job/cancel/(?P<job_id>[\w]+)/$', views.job_cancel, name='job_cancel'),
+    url(r'^task/cancel(?P<job_id>[\w]+)/(?P<task_id>[\w]+)/$', views.task_cancel, name='task_cancel'),
+
     url(r'^job/(?P<job_id>[\w]+)/?$', views.job_details, name='job'),
     url(r'^job/(?P<job_id>[\w]+)/(?P<task_id>[\w]+)/?$', views.task_details, name='task'),
 
index 8905339..89ddfa4 100644 (file)
@@ -13,15 +13,17 @@ from django.shortcuts import render, get_object_or_404, redirect
 from django.utils.html import format_html
 from django.utils.http import urlencode
 from django.utils.timezone import UTC
+from django.views.decorators.http import require_POST
 from django_openid_auth.views import make_consumer
 from openid.extensions import ax
+from pyqcg.utils.qcg_types import PyqcgException
 
 from filex.forms import HostPathNameForm, RenameForm, ArchiveForm, HostPathForm
 from filex.ftp import FTPOperation, FTPError
 from filex.views import make_url
 from qcg.forms import FiltersForm, ColumnsForm, JobDescriptionForm, EnvFormSet
 from qcg.utils import paginator_context
-from qcg.service import update_user_data, submit_job, update_job
+from qcg.service import update_user_data, submit_job, update_job, cancel
 
 
 def index(request):
@@ -188,6 +190,31 @@ def job_submit(request):
     return render(request, 'qcg/job_submit.html', {'form': form, 'env_formset': env_formset, 'errors': errors})
 
 
+@require_POST
+@login_required
+def job_cancel(request, job_id):
+    return obj_cancel(request, get_object_or_404(request.user.jobs, job_id=job_id))
+
+
+@require_POST
+@login_required
+def task_cancel(request, job_id, task_id):
+    return obj_cancel(request, get_object_or_404(request.user.tasks, job__job_id=job_id, task_id=task_id))
+
+
+def obj_cancel(request, obj):
+    try:
+        cancel(obj, request.session['proxy'])
+    except PyqcgException as e:
+        messages.error(request, format_html('<span class="glyphicon glyphicon-info-sign" aria-hidden="true"></span> {}',
+                                            e.message))
+    else:
+        messages.success(request, format_html('<span class="glyphicon glyphicon-info-sign" aria-hidden="true"></span> '
+                                              'Zadanie anulowano.'))
+
+    return redirect(obj)
+
+
 @login_required
 def gridftp(request):
     return render(request, 'qcg/gridftp.html',