From b2901d6b4831e9141b7b5b547d3c6d9e50a1a2b9 Mon Sep 17 00:00:00 2001 From: Tobias Bengfort Date: Tue, 24 Aug 2021 16:36:18 +0200 Subject: [PATCH 1/2] add jobs app --- scheduler/jobs/helpers.py | 70 +++++++++++++++++++++++ scheduler/jobs/migrations/0001_initial.py | 22 +++++++ scheduler/jobs/migrations/__init__.py | 0 scheduler/jobs/models.py | 34 +++++++++++ scheduler/jobs/views.py | 50 ++++++++++++++++ scheduler/settings/default.py | 1 + scheduler/urls.py | 2 + 7 files changed, 179 insertions(+) create mode 100644 scheduler/jobs/helpers.py create mode 100644 scheduler/jobs/migrations/0001_initial.py create mode 100644 scheduler/jobs/migrations/__init__.py create mode 100644 scheduler/jobs/models.py create mode 100644 scheduler/jobs/views.py diff --git a/scheduler/jobs/helpers.py b/scheduler/jobs/helpers.py new file mode 100644 index 0000000..7323cb6 --- /dev/null +++ b/scheduler/jobs/helpers.py @@ -0,0 +1,70 @@ +# (c) 2020 MPIB , +# +# This file is part of castellum-scheduler. +# +# castellum-scheduler is free software; you can redistribute it and/or +# modify it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# Castellum is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public +# License along with Castellum. If not, see +# . + +from time import sleep + +from django.db import DatabaseError + +from .models import Job + + +def request(data, timeout=10, interval=1): + job = Job.objects.create(data=data) + t = 0 + while True: + sleep(interval) + t += interval + job = Job.objects.get(pk=job.pk) + + if job.state == Job.FINISHED: + job.delete() + return True + if job.state == Job.ERROR: + job.delete() + return False + if t > timeout: + job.delete() + raise TimeoutError + + +def listen(interval=1): + while True: + for job in Job.objects.filter(state=Job.NEW): + try: + job.state = Job.STARTED + job.save(force_update=True) + except DatabaseError: + # Job has already timed out + continue + yield job.pk, job.data + + sleep(interval) + + +def response(pk, success): + try: + job = Job.objects.get(pk=pk, state=Job.STARTED) + except Job.DoesNotExist: + raise KeyError + + job.state = Job.FINISHED if success else Job.ERROR + try: + job.save(force_update=True) + except DatabaseError: + # Job has already timed out + pass diff --git a/scheduler/jobs/migrations/0001_initial.py b/scheduler/jobs/migrations/0001_initial.py new file mode 100644 index 0000000..411fc2b --- /dev/null +++ b/scheduler/jobs/migrations/0001_initial.py @@ -0,0 +1,22 @@ +# Generated by Django 3.2.6 on 2021-08-24 15:44 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Job', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('data', models.TextField(blank=True)), + ('state', models.SmallIntegerField(choices=[(0, 'new'), (1, 'started'), (2, 'finished'), (3, 'errored')], default=0)), + ], + ), + ] diff --git a/scheduler/jobs/migrations/__init__.py b/scheduler/jobs/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/scheduler/jobs/models.py b/scheduler/jobs/models.py new file mode 100644 index 0000000..95a6a78 --- /dev/null +++ b/scheduler/jobs/models.py @@ -0,0 +1,34 @@ +# (c) 2020 MPIB , +# +# This file is part of castellum-scheduler. +# +# castellum-scheduler is free software; you can redistribute it and/or +# modify it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# Castellum is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public +# License along with Castellum. If not, see +# . + +from django.db import models + + +class Job(models.Model): + NEW = 0 + STARTED = 1 + FINISHED = 2 + ERROR = 3 + + data = models.TextField(blank=True) + state = models.SmallIntegerField(choices=[ + (NEW, 'new'), + (STARTED, 'started'), + (FINISHED, 'finished'), + (ERROR, 'errored'), + ], default=NEW) diff --git a/scheduler/jobs/views.py b/scheduler/jobs/views.py new file mode 100644 index 0000000..2b7e20d --- /dev/null +++ b/scheduler/jobs/views.py @@ -0,0 +1,50 @@ +# (c) 2020 MPIB , +# +# This file is part of castellum-scheduler. +# +# castellum-scheduler is free software; you can redistribute it and/or +# modify it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# Castellum is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public +# License along with Castellum. If not, see +# . + +from django.conf import settings +from django.core.exceptions import PermissionDenied +from django.http import Http404 +from django.http import HttpResponse +from django.http import StreamingHttpResponse +from django.utils.decorators import method_decorator +from django.views.decorators.csrf import csrf_exempt +from django.views.generic import View + +from . import helpers + + +@method_decorator(csrf_exempt, 'dispatch') +class JobView(View): + def dispatch(self, request, *args, **kwargs): + if request.headers.get('Authorization') != 'token ' + settings.API_TOKEN: + raise PermissionDenied + return super().dispatch(request, *args, **kwargs) + + def stream(self): + for id, data in helpers.listen(): + yield 'id: {}\ndata: {}\n\n'.format(id, data) + + def get(self, request, *args, **kwargs): + return StreamingHttpResponse(self.stream(), content_type='text/event-stream') + + def post(self, request, *args, **kwargs): + try: + helpers.response(request.GET['id'], 'success' in request.GET) + except KeyError: + raise Http404 + return HttpResponse(status=204) diff --git a/scheduler/settings/default.py b/scheduler/settings/default.py index 8a263da..0d7d28f 100644 --- a/scheduler/settings/default.py +++ b/scheduler/settings/default.py @@ -16,6 +16,7 @@ INSTALLED_APPS = [ 'django.contrib.flatpages', 'bootstrap4', 'scheduler.main', + 'scheduler.jobs', ] SITE_ID = 1 diff --git a/scheduler/urls.py b/scheduler/urls.py index 01e3418..2862516 100644 --- a/scheduler/urls.py +++ b/scheduler/urls.py @@ -24,6 +24,7 @@ from django.urls import path from django.urls import re_path from django.views.i18n import JavaScriptCatalog +from .jobs.views import JobView from .main.views import InvitationApiView from .main.views import InvitationUpdateView from .main.views import ScheduleCreateView @@ -47,6 +48,7 @@ urlpatterns = [ InvitationApiView.as_view(), name='api-invitation', ), + path('api/jobs/', JobView.as_view(), name='jobs'), path('login/', LoginView.as_view(), name='login'), path('logout/', LogoutView.as_view(), name='logout'), path('admin/', admin.site.urls), -- GitLab From 10f3ed3317db61f4306aacb890930d9e7c67a636 Mon Sep 17 00:00:00 2001 From: Tobias Bengfort Date: Tue, 24 Aug 2021 17:49:35 +0200 Subject: [PATCH 2/2] use jobs instead of requests to ping castellum --- scheduler/main/views.py | 16 +++++++++------- setup.cfg | 1 - 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/scheduler/main/views.py b/scheduler/main/views.py index be429ca..87253f8 100644 --- a/scheduler/main/views.py +++ b/scheduler/main/views.py @@ -16,6 +16,7 @@ # License along with Castellum. If not, see # . +import json from collections import OrderedDict from django.conf import settings @@ -36,7 +37,7 @@ from django.views.generic import ListView from django.views.generic import UpdateView from django.views.generic import View -import requests +from scheduler.jobs import helpers as jobs from .forms import ScheduleForm from .models import Invitation @@ -124,12 +125,13 @@ class InvitationUpdateView(UpdateView): def form_valid(self, form, *args): ok = True response = super().form_valid(form, *args) - if settings.PING_URL: - r = requests.post(settings.PING_URL.format( - schedule_id=self.object.schedule.id, - token=self.object.token, - )) - ok = r.ok + try: + ok = jobs.request(json.dumps({ + 'schedule': self.object.schedule.id, + 'token': self.object.token, + })) + except TimeoutError: + ok = False if ok: messages.success(self.request, _('Your booking has been saved.')) else: diff --git a/setup.cfg b/setup.cfg index 426fb32..c291ebc 100644 --- a/setup.cfg +++ b/setup.cfg @@ -11,7 +11,6 @@ install_requires = Django == 3.2.6 django-bootstrap4 == 3.0.1 django-npm == 1.0.0 - requests == 2.26.0 [options.extras_require] test = -- GitLab