浏览代码

Add CONFIGCONTEXT_CACHING config parameter

Jeremy Stretch 13 小时之前
父节点
当前提交
9951f032b1

+ 1 - 0
docs/configuration/index.md

@@ -23,6 +23,7 @@ Some configuration parameters are primarily controlled via NetBox's admin interf
 * [`BANNER_TOP`](./miscellaneous.md#banner_top)
 * [`CHANGELOG_RETAIN_CREATE_LAST_UPDATE`](./miscellaneous.md#changelog_retain_create_last_update)
 * [`CHANGELOG_RETENTION`](./miscellaneous.md#changelog_retention)
+* [`CONFIGCONTEXT_CACHING`](./miscellaneous.md#configcontext_caching)
 * [`CUSTOM_VALIDATORS`](./data-validation.md#custom_validators)
 * [`DEFAULT_USER_PREFERENCES`](./default-values.md#default_user_preferences)
 * [`ENFORCE_GLOBAL_UNIQUE`](./miscellaneous.md#enforce_global_unique)

+ 13 - 0
docs/configuration/miscellaneous.md

@@ -115,6 +115,19 @@ If enabled, a change log record will not be created when an object is updated wi
 
 ---
 
+## CONFIGCONTEXT_CACHING
+
+!!! tip "Dynamic Configuration Parameter"
+
+Default: `True`
+
+Enables pre-rendering and caching of [config context](../features/context-data.md) data for devices and virtual machines. When enabled, each object's merged context data is rendered by a background job whenever a relevant change occurs and served from a local cache on subsequent reads, greatly improving read performance in environments with many config contexts. When disabled, config context is rendered on demand for every request (the legacy behavior).
+
+!!! note
+    If this parameter is toggled off and later back on after relevant changes have been made, cached data may be stale until each affected object or config context is next modified. Reads always remain correct while disabled, as they render on demand.
+
+---
+
 ## DATA_UPLOAD_MAX_MEMORY_SIZE
 
 Default: `2621440` (2.5 MB)

+ 1 - 1
docs/features/context-data.md

@@ -97,4 +97,4 @@ NetBox pre-renders each device's and virtual machine's merged context data and s
 
 When such a change occurs, NetBox immediately marks the affected caches as invalid and enqueues a non-blocking job to repopulate them. During the brief window between invalidation and re-render, requests for the affected object's config context fall back to the original on-demand rendering path, so the data returned is always correct — never stale — but may be slightly slower for that window. Once the background job completes, reads are served from the cache.
 
-No configuration is required to use this caching layer; it is always active.
+This behavior is controlled by the [`CONFIGCONTEXT_CACHING`](../configuration/miscellaneous.md#configcontext_caching) configuration parameter, which is enabled by default. Setting it to `False` disables pre-rendering entirely and reverts to rendering config context on demand for every request.

+ 5 - 0
netbox/extras/cache.py

@@ -12,6 +12,7 @@ from django.db.models import Q
 from dcim.models import Device
 from extras.jobs import RenderConfigContextJob
 from extras.models.tags import TaggedItem
+from netbox.config import get_config
 from virtualization.models import VirtualMachine
 
 
@@ -24,6 +25,10 @@ def invalidate_config_context_for_objects(model_label, pks):
         model_label: 'dcim.device' or 'virtualization.virtualmachine'.
         pks: Any iterable of object PKs (queryset, list, set, generator). An empty iterable is a no-op.
     """
+    # When caching is disabled, there's nothing to invalidate or repopulate.
+    if not get_config().CONFIGCONTEXT_CACHING:
+        return
+
     pks = list(pks)
     if not pks:
         return

+ 8 - 5
netbox/extras/models/configs.py

@@ -12,6 +12,7 @@ from jsonschema.exceptions import ValidationError as JSONValidationError
 
 from extras.models.mixins import RenderTemplateMixin
 from extras.querysets import ConfigContextQuerySet
+from netbox.config import get_config
 from netbox.models import ChangeLoggedModel, PrimaryModel
 from netbox.models.features import CloningMixin, CustomLinksMixin, ExportTemplatesMixin, SyncedDataMixin, TagsMixin
 from netbox.models.mixins import OwnerMixin
@@ -340,12 +341,14 @@ class ConfigContextModel(models.Model):
 
     def get_config_context(self):
         """
-        Return the merged config context for this object. If a pre-rendered cache is present
-        (`_config_context_data`), return it directly. Otherwise, fall back to rendering on demand.
+        Return the merged config context for this object. When caching is enabled (the
+        CONFIGCONTEXT_CACHING parameter) and a pre-rendered cache is present (`_config_context_data`),
+        return it directly. Otherwise, fall back to rendering on demand.
         """
-        cached = getattr(self, '_config_context_data', None)
-        if cached is not None:
-            return cached
+        if get_config().CONFIGCONTEXT_CACHING:
+            cached = getattr(self, '_config_context_data', None)
+            if cached is not None:
+                return cached
         return self.render_config_context()
 
     def render_config_context(self):

+ 32 - 1
netbox/extras/tests/test_configcontext_cache.py

@@ -1,6 +1,6 @@
 from unittest import mock
 
-from django.test import TestCase
+from django.test import TestCase, override_settings
 
 from dcim.models import Device, DeviceRole, DeviceType, Manufacturer, Site
 from extras.cache import invalidate_config_context_for_objects
@@ -202,3 +202,34 @@ class CacheHelperTest(TestCase):
 
     def test_invalidate_with_empty_args_is_noop(self):
         invalidate_config_context_for_objects('dcim.device', [])
+
+
+@override_settings(CONFIGCONTEXT_CACHING=False)
+class ConfigContextCachingDisabledTest(TestCase):
+    """
+    With the CONFIGCONTEXT_CACHING feature flag off, reads must always render on demand (ignoring
+    any pre-rendered cache) and invalidation must be a no-op.
+    """
+
+    @classmethod
+    def setUpTestData(cls):
+        manufacturer = Manufacturer.objects.create(name='Mfr', slug='mfr')
+        devicetype = DeviceType.objects.create(manufacturer=manufacturer, model='DT', slug='dt')
+        role = DeviceRole.objects.create(name='Role', slug='role')
+        site = Site.objects.create(name='Site', slug='site')
+        cls.device = Device.objects.create(
+            name='Device', device_type=devicetype, role=role, site=site,
+        )
+        ConfigContext.objects.create(name='CC', weight=100, data={'rendered': True})
+
+    def test_read_ignores_cache_and_renders_on_demand(self):
+        # Even with a (stale) populated cache, the read path renders on demand.
+        _set_cache(self.device, {'stale': 'value'})
+        device = Device.objects.get(pk=self.device.pk)
+        self.assertEqual(device.get_config_context(), {'rendered': True})
+
+    def test_invalidation_is_noop(self):
+        _set_cache(self.device, {'cached': True})
+        invalidate_config_context_for_objects('dcim.device', [self.device.pk])
+        # Cache is left untouched; no background job machinery runs.
+        self.assertEqual(_get_cache(self.device), {'cached': True})

+ 10 - 0
netbox/netbox/config/parameters.py

@@ -218,6 +218,16 @@ PARAMS = (
         description=_("Enable the GraphQL API"),
         field=forms.BooleanField
     ),
+    ConfigParam(
+        name='CONFIGCONTEXT_CACHING',
+        label=_('Config context caching'),
+        default=True,
+        description=_(
+            "Pre-render and cache config context data for devices and virtual machines. Disable to render "
+            "config context on demand for every request."
+        ),
+        field=forms.BooleanField
+    ),
     ConfigParam(
         name='JOB_RETENTION',
         label=_('Job result retention'),