From 4fc0a999ea8eb4c638b9c3bced40ea2ef67caf0d Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 30 Nov 2023 19:36:41 -0500 Subject: [PATCH] Closes #14365: Introduce job_start and job_end signals (#14393) * Introduce job_start and job_end signals, and receivers to process event rules * Complete signals documentation --- docs/development/signals.md | 24 ++++++++++++++++++++++++ netbox/core/models/jobs.py | 25 +++++-------------------- netbox/core/signals.py | 6 ++++++ netbox/extras/signals.py | 26 ++++++++++++++++++++++++++ 4 files changed, 61 insertions(+), 20 deletions(-) diff --git a/docs/development/signals.md b/docs/development/signals.md index 8a5d8e43f..8783b74a3 100644 --- a/docs/development/signals.md +++ b/docs/development/signals.md @@ -9,3 +9,27 @@ This signal is sent by models which inherit from `CustomValidationMixin` at the ### Receivers * `extras.signals.run_custom_validators()` + +## core.job_start + +This signal is sent whenever a [background job](../features/background-jobs.md) is started. + +### Receivers + +* `extras.signals.process_job_start_event_rules()` + +## core.job_end + +This signal is sent whenever a [background job](../features/background-jobs.md) is terminated. + +### Receivers + +* `extras.signals.process_job_end_event_rules()` + +## core.pre_sync + +This signal is sent when the [DataSource](../models/core/datasource.md) model's `sync()` method is called. + +## core.post_sync + +This signal is sent when a [DataSource](../models/core/datasource.md) finishes synchronizing. diff --git a/netbox/core/models/jobs.py b/netbox/core/models/jobs.py index af8191df5..e91be980c 100644 --- a/netbox/core/models/jobs.py +++ b/netbox/core/models/jobs.py @@ -12,6 +12,7 @@ from django.utils.translation import gettext as _ from core.choices import JobStatusChoices from core.models import ContentType +from core.signals import job_end, job_start from extras.constants import EVENT_JOB_END, EVENT_JOB_START from netbox.config import get_config from netbox.constants import RQ_QUEUE_DEFAULT @@ -168,8 +169,8 @@ class Job(models.Model): self.status = JobStatusChoices.STATUS_RUNNING self.save() - # Handle events - self.process_event(event=EVENT_JOB_START) + # Send signal + job_start.send(self) def terminate(self, status=JobStatusChoices.STATUS_COMPLETED, error=None): """ @@ -186,8 +187,8 @@ class Job(models.Model): self.completed = timezone.now() self.save() - # Handle events - self.process_event(event=EVENT_JOB_END) + # Send signal + job_end.send(self) @classmethod def enqueue(cls, func, instance, name='', user=None, schedule_at=None, interval=None, **kwargs): @@ -223,19 +224,3 @@ class Job(models.Model): queue.enqueue(func, job_id=str(job.job_id), job=job, **kwargs) return job - - def process_event(self, event): - """ - Process any EventRules relevant to the passed job event (i.e. start or stop). - """ - from extras.models import EventRule - from extras.events import process_event_rules - - # Fetch any event rules matching this object type and action - event_rules = EventRule.objects.filter( - **{f'type_{event}': True}, - content_types=self.object_type, - enabled=True - ) - - process_event_rules(event_rules, self.object_type.model, event, self.data, self.user.username) diff --git a/netbox/core/signals.py b/netbox/core/signals.py index cd1633a1a..f884a27b4 100644 --- a/netbox/core/signals.py +++ b/netbox/core/signals.py @@ -4,10 +4,16 @@ from django.dispatch import Signal, receiver from .models import ConfigRevision __all__ = ( + 'job_end', + 'job_start', 'post_sync', 'pre_sync', ) +# Job signals +job_start = Signal() +job_end = Signal() + # DataSource signals pre_sync = Signal() post_sync = Signal() diff --git a/netbox/extras/signals.py b/netbox/extras/signals.py index 184ee6d9b..42204f86e 100644 --- a/netbox/extras/signals.py +++ b/netbox/extras/signals.py @@ -8,6 +8,10 @@ from django.dispatch import receiver, Signal from django.utils.translation import gettext_lazy as _ from django_prometheus.models import model_deletes, model_inserts, model_updates +from core.signals import job_end, job_start +from extras.constants import EVENT_JOB_END, EVENT_JOB_START +from extras.events import process_event_rules +from extras.models import EventRule from extras.validators import CustomValidator from netbox.config import get_config from netbox.context import current_request, events_queue @@ -235,3 +239,25 @@ def validate_assigned_tags(sender, instance, action, model, pk_set, **kwargs): for tag in model.objects.filter(pk__in=pk_set, object_types__isnull=False).prefetch_related('object_types'): if ct not in tag.object_types.all(): raise AbortRequest(f"Tag {tag} cannot be assigned to {ct.model} objects.") + + +# +# Event rules +# + +@receiver(job_start) +def process_job_start_event_rules(sender, **kwargs): + """ + Process event rules for jobs starting. + """ + event_rules = EventRule.objects.filter(type_job_start=True, enabled=True, content_types=sender.object_type) + process_event_rules(event_rules, sender.object_type.model, EVENT_JOB_START, sender.data, sender.user.username) + + +@receiver(job_end) +def process_job_end_event_rules(sender, **kwargs): + """ + Process event rules for jobs terminating. + """ + event_rules = EventRule.objects.filter(type_job_end=True, enabled=True, content_types=sender.object_type) + process_event_rules(event_rules, sender.object_type.model, EVENT_JOB_END, sender.data, sender.user.username)