mirror of
https://github.com/netbox-community/netbox.git
synced 2024-05-10 07:54:54 +00:00
#2006: Prevent script/report execution if no RQ worker is running
This commit is contained in:
@ -3,11 +3,13 @@ from collections import OrderedDict
|
|||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.db.models import Count
|
from django.db.models import Count
|
||||||
from django.http import Http404
|
from django.http import Http404
|
||||||
|
from django_rq.queues import get_connection
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
from rest_framework.decorators import action
|
from rest_framework.decorators import action
|
||||||
from rest_framework.exceptions import PermissionDenied
|
from rest_framework.exceptions import PermissionDenied
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.viewsets import ReadOnlyModelViewSet, ViewSet
|
from rest_framework.viewsets import ReadOnlyModelViewSet, ViewSet
|
||||||
|
from rq import Worker
|
||||||
|
|
||||||
from extras import filters
|
from extras import filters
|
||||||
from extras.choices import JobResultStatusChoices
|
from extras.choices import JobResultStatusChoices
|
||||||
@ -17,6 +19,7 @@ from extras.models import (
|
|||||||
from extras.reports import get_report, get_reports, run_report
|
from extras.reports import get_report, get_reports, run_report
|
||||||
from extras.scripts import get_script, get_scripts, run_script
|
from extras.scripts import get_script, get_scripts, run_script
|
||||||
from utilities.api import IsAuthenticatedOrLoginNotRequired, ModelViewSet
|
from utilities.api import IsAuthenticatedOrLoginNotRequired, ModelViewSet
|
||||||
|
from utilities.exceptions import RQWorkerNotRunningException
|
||||||
from utilities.metadata import ContentTypeMetadata
|
from utilities.metadata import ContentTypeMetadata
|
||||||
from utilities.utils import copy_safe_request
|
from utilities.utils import copy_safe_request
|
||||||
from . import serializers
|
from . import serializers
|
||||||
@ -219,11 +222,14 @@ class ReportViewSet(ViewSet):
|
|||||||
"""
|
"""
|
||||||
Run a Report identified as "<module>.<script>" and return the pending JobResult as the result
|
Run a Report identified as "<module>.<script>" and return the pending JobResult as the result
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Check that the user has permission to run reports.
|
# Check that the user has permission to run reports.
|
||||||
if not request.user.has_perm('extras.run_script'):
|
if not request.user.has_perm('extras.run_script'):
|
||||||
raise PermissionDenied("This user does not have permission to run reports.")
|
raise PermissionDenied("This user does not have permission to run reports.")
|
||||||
|
|
||||||
|
# Check that at least one RQ worker is running
|
||||||
|
if not Worker.count(get_connection('default')):
|
||||||
|
raise RQWorkerNotRunningException()
|
||||||
|
|
||||||
# Retrieve and run the Report. This will create a new JobResult.
|
# Retrieve and run the Report. This will create a new JobResult.
|
||||||
report = self._retrieve_report(pk)
|
report = self._retrieve_report(pk)
|
||||||
report_content_type = ContentType.objects.get(app_label='extras', model='report')
|
report_content_type = ContentType.objects.get(app_label='extras', model='report')
|
||||||
@ -299,6 +305,10 @@ class ScriptViewSet(ViewSet):
|
|||||||
script = self._get_script(pk)()
|
script = self._get_script(pk)()
|
||||||
input_serializer = serializers.ScriptInputSerializer(data=request.data)
|
input_serializer = serializers.ScriptInputSerializer(data=request.data)
|
||||||
|
|
||||||
|
# Check that at least one RQ worker is running
|
||||||
|
if not Worker.count(get_connection('default')):
|
||||||
|
raise RQWorkerNotRunningException()
|
||||||
|
|
||||||
if input_serializer.is_valid():
|
if input_serializer.is_valid():
|
||||||
data = input_serializer.data['data']
|
data = input_serializer.data['data']
|
||||||
commit = input_serializer.data['commit']
|
commit = input_serializer.data['commit']
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
import time
|
|
||||||
|
|
||||||
from django import template
|
from django import template
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.contrib import messages
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.db.models import Count, Prefetch, Q
|
from django.db.models import Count, Prefetch, Q
|
||||||
from django.http import Http404, HttpResponseForbidden
|
from django.http import Http404, HttpResponseForbidden
|
||||||
from django.shortcuts import get_object_or_404, redirect, render
|
from django.shortcuts import get_object_or_404, redirect, render
|
||||||
from django.views.generic import View
|
from django.views.generic import View
|
||||||
|
from django_rq.queues import get_connection
|
||||||
from django_tables2 import RequestConfig
|
from django_tables2 import RequestConfig
|
||||||
|
from rq import Worker
|
||||||
|
|
||||||
from dcim.models import DeviceRole, Platform, Region, Site
|
from dcim.models import DeviceRole, Platform, Region, Site
|
||||||
from tenancy.models import Tenant, TenantGroup
|
from tenancy.models import Tenant, TenantGroup
|
||||||
@ -21,7 +22,7 @@ from utilities.views import (
|
|||||||
from virtualization.models import Cluster, ClusterGroup
|
from virtualization.models import Cluster, ClusterGroup
|
||||||
from . import filters, forms, tables
|
from . import filters, forms, tables
|
||||||
from .choices import JobResultStatusChoices
|
from .choices import JobResultStatusChoices
|
||||||
from .models import ConfigContext, ImageAttachment, ObjectChange, Report, JobResult, Script, Tag, TaggedItem
|
from .models import ConfigContext, ImageAttachment, ObjectChange, JobResult, Tag
|
||||||
from .reports import get_report, get_reports, run_report
|
from .reports import get_report, get_reports, run_report
|
||||||
from .scripts import get_scripts, run_script
|
from .scripts import get_scripts, run_script
|
||||||
|
|
||||||
@ -388,9 +389,13 @@ class ReportView(GetReportMixin, ContentTypePermissionRequiredMixin, View):
|
|||||||
return HttpResponseForbidden()
|
return HttpResponseForbidden()
|
||||||
|
|
||||||
report = self._get_report(name, module)
|
report = self._get_report(name, module)
|
||||||
|
|
||||||
form = ConfirmationForm(request.POST)
|
form = ConfirmationForm(request.POST)
|
||||||
if form.is_valid():
|
|
||||||
|
# Allow execution only if RQ worker process is running
|
||||||
|
if not Worker.count(get_connection('default')):
|
||||||
|
messages.error(request, "Unable to run report: RQ worker process not running.")
|
||||||
|
|
||||||
|
elif form.is_valid():
|
||||||
|
|
||||||
# Run the Report. A new JobResult is created.
|
# Run the Report. A new JobResult is created.
|
||||||
report_content_type = ContentType.objects.get(app_label='extras', model='report')
|
report_content_type = ContentType.objects.get(app_label='extras', model='report')
|
||||||
@ -504,7 +509,11 @@ class ScriptView(ContentTypePermissionRequiredMixin, GetScriptMixin, View):
|
|||||||
script = self._get_script(name, module)
|
script = self._get_script(name, module)
|
||||||
form = script.as_form(request.POST, request.FILES)
|
form = script.as_form(request.POST, request.FILES)
|
||||||
|
|
||||||
if form.is_valid():
|
# Allow execution only if RQ worker process is running
|
||||||
|
if not Worker.count(get_connection('default')):
|
||||||
|
messages.error(request, "Unable to run script: RQ worker process not running.")
|
||||||
|
|
||||||
|
elif form.is_valid():
|
||||||
commit = form.cleaned_data.pop('_commit')
|
commit = form.cleaned_data.pop('_commit')
|
||||||
|
|
||||||
script_content_type = ContentType.objects.get(app_label='extras', model='script')
|
script_content_type = ContentType.objects.get(app_label='extras', model='script')
|
||||||
|
@ -1,5 +1,19 @@
|
|||||||
|
from rest_framework import status
|
||||||
|
from rest_framework.exceptions import APIException
|
||||||
|
|
||||||
|
|
||||||
class AbortTransaction(Exception):
|
class AbortTransaction(Exception):
|
||||||
"""
|
"""
|
||||||
A dummy exception used to trigger a database transaction rollback.
|
A dummy exception used to trigger a database transaction rollback.
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class RQWorkerNotRunningException(APIException):
|
||||||
|
"""
|
||||||
|
Indicates the temporary inability to enqueue a new task (e.g. custom script execution) because no RQ worker
|
||||||
|
processes are currently running.
|
||||||
|
"""
|
||||||
|
status_code = status.HTTP_503_SERVICE_UNAVAILABLE
|
||||||
|
default_detail = 'Unable to process request: RQ worker process not running.'
|
||||||
|
default_code = 'rq_worker_not_running'
|
||||||
|
Reference in New Issue
Block a user