diff --git a/docs/plugins/development/background-jobs.md b/docs/plugins/development/background-jobs.md index fb9d3511d..e950c26e6 100644 --- a/docs/plugins/development/background-jobs.md +++ b/docs/plugins/development/background-jobs.md @@ -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: diff --git a/docs/plugins/development/data-backends.md b/docs/plugins/development/data-backends.md index 8b7226a41..0c6d44d95 100644 --- a/docs/plugins/development/data-backends.md +++ b/docs/plugins/development/data-backends.md @@ -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 diff --git a/netbox/netbox/plugins/__init__.py b/netbox/netbox/plugins/__init__.py index e2f0f22fc..76bb9d26b 100644 --- a/netbox/netbox/plugins/__init__.py +++ b/netbox/netbox/plugins/__init__.py @@ -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) diff --git a/netbox/netbox/tests/dummy_plugin/jobs.py b/netbox/netbox/tests/dummy_plugin/jobs.py new file mode 100644 index 000000000..fbb08c186 --- /dev/null +++ b/netbox/netbox/tests/dummy_plugin/jobs.py @@ -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, +) diff --git a/netbox/netbox/tests/test_plugins.py b/netbox/netbox/tests/test_plugins.py index ba44378c5..0fe8549a8 100644 --- a/netbox/netbox/tests/test_plugins.py +++ b/netbox/netbox/tests/test_plugins.py @@ -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.