#11558: Disable sync button if RQ worker not running

This commit is contained in:
jeremystretch 2023-03-20 15:12:11 -04:00
parent 13d604d44e
commit 08bdb54cb4
6 changed files with 43 additions and 12 deletions

View File

@ -16,7 +16,6 @@ from django.utils.translation import gettext as _
from extras.models import JobResult from extras.models import JobResult
from netbox.models import PrimaryModel from netbox.models import PrimaryModel
from netbox.models.features import ChangeLoggingMixin
from netbox.registry import registry from netbox.registry import registry
from utilities.files import sha256_hash from utilities.files import sha256_hash
from utilities.querysets import RestrictedQuerySet from utilities.querysets import RestrictedQuerySet
@ -116,6 +115,7 @@ class DataSource(PrimaryModel):
""" """
# Set the status to "syncing" # Set the status to "syncing"
self.status = DataSourceStatusChoices.QUEUED self.status = DataSourceStatusChoices.QUEUED
DataSource.objects.filter(pk=self.pk).update(status=self.status)
# Enqueue a sync job # Enqueue a sync job
job_result = JobResult.enqueue_job( job_result = JobResult.enqueue_job(
@ -137,8 +137,8 @@ class DataSource(PrimaryModel):
""" """
Create/update/delete child DataFiles as necessary to synchronize with the remote source. Create/update/delete child DataFiles as necessary to synchronize with the remote source.
""" """
if not self.ready_for_sync: if self.status == DataSourceStatusChoices.SYNCING:
raise SyncError(f"Cannot initiate sync; data source not ready/enabled") raise SyncError(f"Cannot initiate sync; syncing already in progress.")
# Emit the pre_sync signal # Emit the pre_sync signal
pre_sync.send(sender=self.__class__, instance=self) pre_sync.send(sender=self.__class__, instance=self)

View File

@ -3,6 +3,7 @@ from django.shortcuts import get_object_or_404, redirect
from netbox.views import generic from netbox.views import generic
from netbox.views.generic.base import BaseObjectView from netbox.views.generic.base import BaseObjectView
from utilities.rqworker import get_queue_for_model, get_workers_for_queue
from utilities.utils import count_related from utilities.utils import count_related
from utilities.views import register_model_view from utilities.views import register_model_view
from . import filtersets, forms, tables from . import filtersets, forms, tables
@ -31,7 +32,11 @@ class DataSourceView(generic.ObjectView):
(DataFile.objects.restrict(request.user, 'view').filter(source=instance), 'source_id'), (DataFile.objects.restrict(request.user, 'view').filter(source=instance), 'source_id'),
) )
queue_name = get_queue_for_model(DataSource)
sync_enabled = bool(get_workers_for_queue(queue_name))
return { return {
'sync_enabled': sync_enabled,
'related_models': related_models, 'related_models': related_models,
} }

View File

@ -30,6 +30,7 @@ from netbox.models.features import (
TagsMixin, WebhooksMixin, TagsMixin, WebhooksMixin,
) )
from utilities.querysets import RestrictedQuerySet from utilities.querysets import RestrictedQuerySet
from utilities.rqworker import get_queue_for_model
from utilities.utils import render_jinja2 from utilities.utils import render_jinja2
__all__ = ( __all__ = (
@ -730,7 +731,7 @@ class JobResult(models.Model):
schedule_at: Schedule the job to be executed at the passed date and time schedule_at: Schedule the job to be executed at the passed date and time
interval: Recurrence interval (in minutes) interval: Recurrence interval (in minutes)
""" """
rq_queue_name = get_config().QUEUE_MAPPINGS.get(obj_type.model, RQ_QUEUE_DEFAULT) rq_queue_name = get_queue_for_model(obj_type.model)
queue = django_rq.get_queue(rq_queue_name) queue = django_rq.get_queue(rq_queue_name)
status = JobResultStatusChoices.STATUS_SCHEDULED if schedule_at else JobResultStatusChoices.STATUS_PENDING status = JobResultStatusChoices.STATUS_SCHEDULED if schedule_at else JobResultStatusChoices.STATUS_PENDING
job_result: JobResult = JobResult.objects.create( job_result: JobResult = JobResult.objects.create(

View File

@ -6,14 +6,13 @@ from django.http import Http404, HttpResponseBadRequest, HttpResponseForbidden,
from django.shortcuts import get_object_or_404, redirect, render from django.shortcuts import get_object_or_404, redirect, render
from django.urls import reverse from django.urls import reverse
from django.views.generic import View from django.views.generic import View
from django_rq.queues import get_connection
from rq import Worker
from extras.dashboard.forms import DashboardWidgetAddForm, DashboardWidgetForm from extras.dashboard.forms import DashboardWidgetAddForm, DashboardWidgetForm
from extras.dashboard.utils import get_widget_class from extras.dashboard.utils import get_widget_class
from netbox.views import generic from netbox.views import generic
from utilities.forms import ConfirmationForm, get_field_value from utilities.forms import ConfirmationForm, get_field_value
from utilities.htmx import is_htmx from utilities.htmx import is_htmx
from utilities.rqworker import get_workers_for_queue
from utilities.templatetags.builtins.filters import render_markdown from utilities.templatetags.builtins.filters import render_markdown
from utilities.utils import copy_safe_request, count_related, get_viewname, normalize_querydict, shallow_compare_dict from utilities.utils import copy_safe_request, count_related, get_viewname, normalize_querydict, shallow_compare_dict
from utilities.views import ContentTypePermissionRequiredMixin, register_model_view from utilities.views import ContentTypePermissionRequiredMixin, register_model_view
@ -863,7 +862,7 @@ class ReportView(ContentTypePermissionRequiredMixin, View):
if form.is_valid(): if form.is_valid():
# Allow execution only if RQ worker process is running # Allow execution only if RQ worker process is running
if not Worker.count(get_connection('default')): if not get_workers_for_queue('default'):
messages.error(request, "Unable to run report: RQ worker process not running.") messages.error(request, "Unable to run report: RQ worker process not running.")
return render(request, 'extras/report.html', { return render(request, 'extras/report.html', {
'report': report, 'report': report,
@ -994,7 +993,7 @@ class ScriptView(ContentTypePermissionRequiredMixin, GetScriptMixin, View):
form = script.as_form(request.POST, request.FILES) form = script.as_form(request.POST, request.FILES)
# Allow execution only if RQ worker process is running # Allow execution only if RQ worker process is running
if not Worker.count(get_connection('default')): if not get_workers_for_queue('default'):
messages.error(request, "Unable to run script: RQ worker process not running.") messages.error(request, "Unable to run script: RQ worker process not running.")
elif form.is_valid(): elif form.is_valid():

View File

@ -6,7 +6,7 @@
{% block extra_controls %} {% block extra_controls %}
{% if perms.core.sync_datasource %} {% if perms.core.sync_datasource %}
{% if object.ready_for_sync %} {% if sync_enabled and object.ready_for_sync %}
<form action="{% url 'core:datasource_sync' pk=object.pk %}" method="post"> <form action="{% url 'core:datasource_sync' pk=object.pk %}" method="post">
{% csrf_token %} {% csrf_token %}
<button type="submit" class="btn btn-sm btn-primary"> <button type="submit" class="btn btn-sm btn-primary">
@ -14,9 +14,11 @@
</button> </button>
</form> </form>
{% else %} {% else %}
<button class="btn btn-sm btn-primary" disabled> <span class="inline-block" tabindex="0" data-bs-toggle="tooltip" data-bs-delay="100" data-bs-placement="bottom" title="Unable to sync: No RQ worker running">
<i class="mdi mdi-sync" aria-hidden="true"></i> Sync <button class="btn btn-sm btn-primary" disabled>
</button> <i class="mdi mdi-sync" aria-hidden="true"></i> Sync
</button>
</span>
{% endif %} {% endif %}
{% endif %} {% endif %}
{% endblock %} {% endblock %}

View File

@ -0,0 +1,24 @@
from django_rq.queues import get_connection
from rq import Worker
from netbox.config import get_config
from netbox.constants import RQ_QUEUE_DEFAULT
__all__ = (
'get_queue_for_model',
'get_workers_for_queue',
)
def get_queue_for_model(model):
"""
Return the configured queue name for jobs associated with the given model.
"""
return get_config().QUEUE_MAPPINGS.get(model, RQ_QUEUE_DEFAULT)
def get_workers_for_queue(queue_name):
"""
Returns True if a worker process is currently servicing the specified queue.
"""
return Worker.count(get_connection(queue_name))