Allow plugins to register system jobs

This commit is contained in:
Alexander Haase 2024-10-07 18:37:18 +02:00 committed by Jeremy Stretch
parent 39e5c0d24c
commit d7b03ee47e
5 changed files with 67 additions and 3 deletions

View File

@ -31,12 +31,20 @@ You can schedule the background job from within your code (e.g. from a model's `
### Attributes
`JobRunner` attributes are defined under a class named `Meta` within the job. These are optional, but encouraged.
`JobRunner` attributes are defined under a class named `Meta` within the job. These are optional (unless specified otherwise), but encouraged.
#### `name`
This is the human-friendly names of your background job. If omitted, the class name will be used.
#### `enabled`
When the `JobRunner` is defined as [system job](#system-jobs), this attribute controls whether a job will be scheduled. By default, this attribute is `True`.
#### `interval` *(required for system jobs)*
When the `JobRunner` is defined as [system job](#system-jobs), this attribute controls the interval of the scheduled job.
### Scheduled Jobs
As described above, jobs can be scheduled for immediate execution or at any later time using the `enqueue()` method. However, for management purposes, the `enqueue_once()` method allows a job to be scheduled exactly once avoiding duplicates. If a job is already scheduled for a particular instance, a second one won't be scheduled, respecting thread safety. An example use case would be to schedule a periodic task that is bound to an instance in general, but not to any event of that instance (such as updates). The parameters of the `enqueue_once()` method are identical to those of `enqueue()`.
@ -62,6 +70,33 @@ class MyModel(NetBoxModel):
MyTestJob.enqueue(instance=self)
```
### System Jobs
Some plugins may implement background jobs that are decoupled from any object and the request-response cycle. Typical use cases would be housekeeping tasks or synchronization jobs. These can be created using *system jobs*. The `JobRunner` class has everything included to provide this type of job as well. Just add the appropriate metadata to let NetBox schedule all background jobs automatically.
!!! info
All system jobs are automatically scheduled just before the `./manage.py rqworker` command is started and the job queue is processed. The schedules are also checked at each restart of this process.
#### Example
```python title="jobs.py"
from netbox.jobs import JobRunner
from .models import MyModel
class MyHousekeepingJob(JobRunner):
class Meta:
name = "My Housekeeping Job"
interval = 60 # every 60 minutes
def run(self, *args, **kwargs):
MyModel.objects.filter(foo='bar').delete()
system_jobs = (
MyHousekeepingJob,
)
```
## Task queues
Three task queues of differing priority are defined by default:

View File

@ -18,6 +18,6 @@ backends = [MyDataBackend]
```
!!! tip
The path to the list of search indexes can be modified by setting `data_backends` in the PluginConfig instance.
The path to the list of data backends can be modified by setting `data_backends` in the PluginConfig instance.
::: netbox.data_backends.DataBackend

View File

@ -8,7 +8,7 @@ from packaging import version
from netbox.registry import registry
from netbox.search import register_search
from netbox.utils import register_data_backend
from netbox.utils import register_data_backend, register_system_job
from .navigation import *
from .registration import *
from .templates import *
@ -26,6 +26,7 @@ registry['plugins'].update({
DEFAULT_RESOURCE_PATHS = {
'search_indexes': 'search.indexes',
'data_backends': 'data_backends.backends',
'system_jobs': 'jobs.system_jobs',
'graphql_schema': 'graphql.schema',
'menu': 'navigation.menu',
'menu_items': 'navigation.menu_items',
@ -73,6 +74,7 @@ class PluginConfig(AppConfig):
# Optional plugin resources
search_indexes = None
data_backends = None
system_jobs = None
graphql_schema = None
menu = None
menu_items = None
@ -111,6 +113,11 @@ class PluginConfig(AppConfig):
for backend in data_backends:
register_data_backend()(backend)
# Register system jobs (if defined)
system_jobs = self._load_resource('system_jobs') or []
for job in system_jobs:
register_system_job()(job)
# Register template content (if defined)
if template_extensions := self._load_resource('template_extensions'):
register_template_extensions(template_extensions)

View File

@ -0,0 +1,14 @@
from netbox.jobs import JobRunner
class DummySystemJob(JobRunner):
class Meta:
interval = 60
def run(self, *args, **kwargs):
pass
system_jobs = (
DummySystemJob,
)

View File

@ -7,6 +7,7 @@ from django.urls import reverse
from netbox.tests.dummy_plugin import config as dummy_config
from netbox.tests.dummy_plugin.data_backends import DummyBackend
from netbox.tests.dummy_plugin.jobs import DummySystemJob
from netbox.plugins.navigation import PluginMenu
from netbox.plugins.utils import get_plugin_config
from netbox.graphql.schema import Query
@ -130,6 +131,13 @@ class PluginTest(TestCase):
self.assertIn('dummy', registry['data_backends'])
self.assertIs(registry['data_backends']['dummy'], DummyBackend)
def test_system_jobs(self):
"""
Check registered system jobs.
"""
self.assertIn(DummySystemJob.name, registry['system_jobs'])
self.assertIs(registry['system_jobs'][DummySystemJob.name], DummySystemJob)
def test_queues(self):
"""
Check that plugin queues are registered with the accurate name.