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('__')))
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('__')))
@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()
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)
<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">
<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">
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'),
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):
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',