Jelajahi Sumber

Optimize config queries

jeremystretch 4 tahun lalu
induk
melakukan
fbf91dda7d

+ 2 - 3
netbox/dcim/api/views.py

@@ -1,7 +1,6 @@
 import socket
 import socket
 from collections import OrderedDict
 from collections import OrderedDict
 
 
-from django.conf import settings
 from django.http import Http404, HttpResponse, HttpResponseForbidden
 from django.http import Http404, HttpResponse, HttpResponseForbidden
 from django.shortcuts import get_object_or_404
 from django.shortcuts import get_object_or_404
 from drf_yasg import openapi
 from drf_yasg import openapi
@@ -21,7 +20,7 @@ from netbox.api.authentication import IsAuthenticatedOrLoginNotRequired
 from netbox.api.exceptions import ServiceUnavailable
 from netbox.api.exceptions import ServiceUnavailable
 from netbox.api.metadata import ContentTypeMetadata
 from netbox.api.metadata import ContentTypeMetadata
 from netbox.api.views import ModelViewSet
 from netbox.api.views import ModelViewSet
-from netbox.config import Config
+from netbox.config import get_config
 from utilities.api import get_serializer_for_model
 from utilities.api import get_serializer_for_model
 from utilities.utils import count_related, decode_dict
 from utilities.utils import count_related, decode_dict
 from virtualization.models import VirtualMachine
 from virtualization.models import VirtualMachine
@@ -459,7 +458,7 @@ class DeviceViewSet(ConfigContextQuerySetMixin, CustomFieldModelViewSet):
         napalm_methods = request.GET.getlist('method')
         napalm_methods = request.GET.getlist('method')
         response = OrderedDict([(m, None) for m in napalm_methods])
         response = OrderedDict([(m, None) for m in napalm_methods])
 
 
-        config = Config()
+        config = get_config()
         username = config.NAPALM_USERNAME
         username = config.NAPALM_USERNAME
         password = config.NAPALM_PASSWORD
         password = config.NAPALM_PASSWORD
         timeout = config.NAPALM_TIMEOUT
         timeout = config.NAPALM_TIMEOUT

+ 2 - 2
netbox/dcim/models/racks.py

@@ -14,7 +14,7 @@ from dcim.choices import *
 from dcim.constants import *
 from dcim.constants import *
 from dcim.svg import RackElevationSVG
 from dcim.svg import RackElevationSVG
 from extras.utils import extras_features
 from extras.utils import extras_features
-from netbox.config import Config
+from netbox.config import get_config
 from netbox.models import OrganizationalModel, PrimaryModel
 from netbox.models import OrganizationalModel, PrimaryModel
 from utilities.choices import ColorChoices
 from utilities.choices import ColorChoices
 from utilities.fields import ColorField, NaturalOrderingField
 from utilities.fields import ColorField, NaturalOrderingField
@@ -394,7 +394,7 @@ class Rack(PrimaryModel):
         """
         """
         elevation = RackElevationSVG(self, user=user, include_images=include_images, base_url=base_url)
         elevation = RackElevationSVG(self, user=user, include_images=include_images, base_url=base_url)
         if unit_width is None or unit_height is None:
         if unit_width is None or unit_height is None:
-            config = Config()
+            config = get_config()
             unit_width = unit_width or config.RACK_ELEVATION_DEFAULT_UNIT_WIDTH
             unit_width = unit_width or config.RACK_ELEVATION_DEFAULT_UNIT_WIDTH
             unit_height = unit_height or config.RACK_ELEVATION_DEFAULT_UNIT_HEIGHT
             unit_height = unit_height or config.RACK_ELEVATION_DEFAULT_UNIT_HEIGHT
 
 

+ 2 - 3
netbox/ipam/api/mixins.py

@@ -1,4 +1,3 @@
-from django.conf import settings
 from django.core.exceptions import ObjectDoesNotExist, PermissionDenied
 from django.core.exceptions import ObjectDoesNotExist, PermissionDenied
 from django.db import transaction
 from django.db import transaction
 from django.shortcuts import get_object_or_404
 from django.shortcuts import get_object_or_404
@@ -9,7 +8,7 @@ from rest_framework.decorators import action
 from rest_framework.response import Response
 from rest_framework.response import Response
 
 
 from ipam.models import *
 from ipam.models import *
-from netbox.config import Config
+from netbox.config import get_config
 from utilities.constants import ADVISORY_LOCK_KEYS
 from utilities.constants import ADVISORY_LOCK_KEYS
 from . import serializers
 from . import serializers
 
 
@@ -161,7 +160,7 @@ class AvailableIPsMixin:
 
 
         # Determine the maximum number of IPs to return
         # Determine the maximum number of IPs to return
         else:
         else:
-            config = Config()
+            config = get_config()
             PAGINATE_COUNT = config.PAGINATE_COUNT
             PAGINATE_COUNT = config.PAGINATE_COUNT
             MAX_PAGE_SIZE = config.MAX_PAGE_SIZE
             MAX_PAGE_SIZE = config.MAX_PAGE_SIZE
             try:
             try:

+ 3 - 3
netbox/ipam/models/ip.py

@@ -16,7 +16,7 @@ from ipam.fields import IPNetworkField, IPAddressField
 from ipam.managers import IPAddressManager
 from ipam.managers import IPAddressManager
 from ipam.querysets import PrefixQuerySet
 from ipam.querysets import PrefixQuerySet
 from ipam.validators import DNSValidator
 from ipam.validators import DNSValidator
-from netbox.config import Config
+from netbox.config import get_config
 from utilities.querysets import RestrictedQuerySet
 from utilities.querysets import RestrictedQuerySet
 from virtualization.models import VirtualMachine
 from virtualization.models import VirtualMachine
 
 
@@ -316,7 +316,7 @@ class Prefix(PrimaryModel):
                 })
                 })
 
 
             # Enforce unique IP space (if applicable)
             # Enforce unique IP space (if applicable)
-            if (self.vrf is None and Config().ENFORCE_GLOBAL_UNIQUE) or (self.vrf and self.vrf.enforce_unique):
+            if (self.vrf is None and get_config().ENFORCE_GLOBAL_UNIQUE) or (self.vrf and self.vrf.enforce_unique):
                 duplicate_prefixes = self.get_duplicates()
                 duplicate_prefixes = self.get_duplicates()
                 if duplicate_prefixes:
                 if duplicate_prefixes:
                     raise ValidationError({
                     raise ValidationError({
@@ -811,7 +811,7 @@ class IPAddress(PrimaryModel):
                 })
                 })
 
 
             # Enforce unique IP space (if applicable)
             # Enforce unique IP space (if applicable)
-            if (self.vrf is None and Config().ENFORCE_GLOBAL_UNIQUE) or (self.vrf and self.vrf.enforce_unique):
+            if (self.vrf is None and get_config().ENFORCE_GLOBAL_UNIQUE) or (self.vrf and self.vrf.enforce_unique):
                 duplicate_ips = self.get_duplicates()
                 duplicate_ips = self.get_duplicates()
                 if duplicate_ips and (
                 if duplicate_ips and (
                         self.role not in IPADDRESS_ROLES_NONUNIQUE or
                         self.role not in IPADDRESS_ROLES_NONUNIQUE or

+ 3 - 4
netbox/netbox/api/pagination.py

@@ -1,8 +1,7 @@
-from django.conf import settings
 from django.db.models import QuerySet
 from django.db.models import QuerySet
 from rest_framework.pagination import LimitOffsetPagination
 from rest_framework.pagination import LimitOffsetPagination
 
 
-from netbox.config import Config
+from netbox.config import get_config
 
 
 
 
 class OptionalLimitOffsetPagination(LimitOffsetPagination):
 class OptionalLimitOffsetPagination(LimitOffsetPagination):
@@ -12,7 +11,7 @@ class OptionalLimitOffsetPagination(LimitOffsetPagination):
     MAX_PAGE_SIZE has been set to 0 or None.
     MAX_PAGE_SIZE has been set to 0 or None.
     """
     """
     def __init__(self):
     def __init__(self):
-        self.default_limit = Config().PAGINATE_COUNT
+        self.default_limit = get_config().PAGINATE_COUNT
 
 
     def paginate_queryset(self, queryset, request, view=None):
     def paginate_queryset(self, queryset, request, view=None):
 
 
@@ -44,7 +43,7 @@ class OptionalLimitOffsetPagination(LimitOffsetPagination):
                 if limit < 0:
                 if limit < 0:
                     raise ValueError()
                     raise ValueError()
                 # Enforce maximum page size, if defined
                 # Enforce maximum page size, if defined
-                MAX_PAGE_SIZE = Config().MAX_PAGE_SIZE
+                MAX_PAGE_SIZE = get_config().MAX_PAGE_SIZE
                 if MAX_PAGE_SIZE:
                 if MAX_PAGE_SIZE:
                     return MAX_PAGE_SIZE if limit == 0 else min(limit, MAX_PAGE_SIZE)
                     return MAX_PAGE_SIZE if limit == 0 else min(limit, MAX_PAGE_SIZE)
                 return limit
                 return limit

+ 30 - 2
netbox/netbox/config/__init__.py

@@ -1,14 +1,41 @@
+import logging
+import threading
+
 from django.conf import settings
 from django.conf import settings
 from django.core.cache import cache
 from django.core.cache import cache
 
 
 from .parameters import PARAMS
 from .parameters import PARAMS
 
 
 __all__ = (
 __all__ = (
-    'Config',
+    'clear_config',
     'ConfigItem',
     'ConfigItem',
+    'get_config',
     'PARAMS',
     'PARAMS',
 )
 )
 
 
+_thread_locals = threading.local()
+
+logger = logging.getLogger('netbox.config')
+
+
+def get_config():
+    """
+    Return the current NetBox configuration, pulling it from cache if not already loaded in memory.
+    """
+    if not hasattr(_thread_locals, 'config'):
+        _thread_locals.config = Config()
+        logger.debug("Initialized configuration")
+    return _thread_locals.config
+
+
+def clear_config():
+    """
+    Delete the currently loaded configuration, if any.
+    """
+    if hasattr(_thread_locals, 'config'):
+        del _thread_locals.config
+        logger.debug("Cleared configuration")
+
 
 
 class Config:
 class Config:
     """
     """
@@ -19,6 +46,7 @@ class Config:
         self.config = cache.get('config')
         self.config = cache.get('config')
         self.version = cache.get('config_version')
         self.version = cache.get('config_version')
         self.defaults = {param.name: param.default for param in PARAMS}
         self.defaults = {param.name: param.default for param in PARAMS}
+        logger.debug("Loaded configuration data from cache")
 
 
     def __getattr__(self, item):
     def __getattr__(self, item):
 
 
@@ -46,5 +74,5 @@ class ConfigItem:
         self.item = item
         self.item = item
 
 
     def __call__(self):
     def __call__(self):
-        config = Config()
+        config = get_config()
         return getattr(config, self.item)
         return getattr(config, self.item)

+ 2 - 2
netbox/netbox/context_processors.py

@@ -1,7 +1,7 @@
 from django.conf import settings as django_settings
 from django.conf import settings as django_settings
 
 
 from extras.registry import registry
 from extras.registry import registry
-from netbox.config import Config
+from netbox.config import get_config
 
 
 
 
 def settings_and_registry(request):
 def settings_and_registry(request):
@@ -10,7 +10,7 @@ def settings_and_registry(request):
     """
     """
     return {
     return {
         'settings': django_settings,
         'settings': django_settings,
-        'config': Config(),
+        'config': get_config(),
         'registry': registry,
         'registry': registry,
         'preferences': request.user.config if request.user.is_authenticated else {},
         'preferences': request.user.config if request.user.is_authenticated else {},
     }
     }

+ 18 - 4
netbox/netbox/middleware.py

@@ -11,11 +11,12 @@ from django.http import Http404, HttpResponseRedirect
 from django.urls import reverse
 from django.urls import reverse
 
 
 from extras.context_managers import change_logging
 from extras.context_managers import change_logging
+from netbox.config import clear_config
 from netbox.views import server_error
 from netbox.views import server_error
 from utilities.api import is_api_request, rest_api_server_error
 from utilities.api import is_api_request, rest_api_server_error
 
 
 
 
-class LoginRequiredMiddleware(object):
+class LoginRequiredMiddleware:
     """
     """
     If LOGIN_REQUIRED is True, redirect all non-authenticated users to the login page.
     If LOGIN_REQUIRED is True, redirect all non-authenticated users to the login page.
     """
     """
@@ -114,7 +115,7 @@ class RemoteUserMiddleware(RemoteUserMiddleware_):
         return groups
         return groups
 
 
 
 
-class ObjectChangeMiddleware(object):
+class ObjectChangeMiddleware:
     """
     """
     This middleware performs three functions in response to an object being created, updated, or deleted:
     This middleware performs three functions in response to an object being created, updated, or deleted:
 
 
@@ -144,7 +145,7 @@ class ObjectChangeMiddleware(object):
         return response
         return response
 
 
 
 
-class APIVersionMiddleware(object):
+class APIVersionMiddleware:
     """
     """
     If the request is for an API endpoint, include the API version as a response header.
     If the request is for an API endpoint, include the API version as a response header.
     """
     """
@@ -159,7 +160,20 @@ class APIVersionMiddleware(object):
         return response
         return response
 
 
 
 
-class ExceptionHandlingMiddleware(object):
+class DynamicConfigMiddleware:
+    """
+    Store the cached NetBox configuration in thread-local storage for the duration of the request.
+    """
+    def __init__(self, get_response):
+        self.get_response = get_response
+
+    def __call__(self, request):
+        response = self.get_response(request)
+        clear_config()
+        return response
+
+
+class ExceptionHandlingMiddleware:
     """
     """
     Intercept certain exceptions which are likely indicative of installation issues and provide helpful instructions
     Intercept certain exceptions which are likely indicative of installation issues and provide helpful instructions
     to the user.
     to the user.

+ 1 - 0
netbox/netbox/settings.py

@@ -335,6 +335,7 @@ MIDDLEWARE = [
     'netbox.middleware.ExceptionHandlingMiddleware',
     'netbox.middleware.ExceptionHandlingMiddleware',
     'netbox.middleware.RemoteUserMiddleware',
     'netbox.middleware.RemoteUserMiddleware',
     'netbox.middleware.LoginRequiredMiddleware',
     'netbox.middleware.LoginRequiredMiddleware',
+    'netbox.middleware.DynamicConfigMiddleware',
     'netbox.middleware.APIVersionMiddleware',
     'netbox.middleware.APIVersionMiddleware',
     'netbox.middleware.ObjectChangeMiddleware',
     'netbox.middleware.ObjectChangeMiddleware',
     'django_prometheus.middleware.PrometheusAfterMiddleware',
     'django_prometheus.middleware.PrometheusAfterMiddleware',

+ 2 - 2
netbox/users/views.py

@@ -13,7 +13,7 @@ from django.utils.http import is_safe_url
 from django.views.decorators.debug import sensitive_post_parameters
 from django.views.decorators.debug import sensitive_post_parameters
 from django.views.generic import View
 from django.views.generic import View
 
 
-from netbox.config import Config
+from netbox.config import get_config
 from utilities.forms import ConfirmationForm
 from utilities.forms import ConfirmationForm
 from .forms import LoginForm, PasswordChangeForm, TokenForm
 from .forms import LoginForm, PasswordChangeForm, TokenForm
 from .models import Token
 from .models import Token
@@ -53,7 +53,7 @@ class LoginView(View):
 
 
             # If maintenance mode is enabled, assume the database is read-only, and disable updating the user's
             # If maintenance mode is enabled, assume the database is read-only, and disable updating the user's
             # last_login time upon authentication.
             # last_login time upon authentication.
-            if Config().MAINTENANCE_MODE:
+            if get_config().MAINTENANCE_MODE:
                 logger.warning("Maintenance mode enabled: disabling update of most recent login time")
                 logger.warning("Maintenance mode enabled: disabling update of most recent login time")
                 user_logged_in.disconnect(update_last_login, dispatch_uid='update_last_login')
                 user_logged_in.disconnect(update_last_login, dispatch_uid='update_last_login')
 
 

+ 4 - 4
netbox/utilities/paginator.py

@@ -1,6 +1,6 @@
 from django.core.paginator import Paginator, Page
 from django.core.paginator import Paginator, Page
 
 
-from netbox.config import Config
+from netbox.config import get_config
 
 
 
 
 class EnhancedPaginator(Paginator):
 class EnhancedPaginator(Paginator):
@@ -14,9 +14,9 @@ class EnhancedPaginator(Paginator):
         try:
         try:
             per_page = int(per_page)
             per_page = int(per_page)
             if per_page < 1:
             if per_page < 1:
-                per_page = Config().PAGINATE_COUNT
+                per_page = get_config().PAGINATE_COUNT
         except ValueError:
         except ValueError:
-            per_page = Config().PAGINATE_COUNT
+            per_page = get_config().PAGINATE_COUNT
 
 
         # Set orphans count based on page size
         # Set orphans count based on page size
         if orphans is None and per_page <= 50:
         if orphans is None and per_page <= 50:
@@ -66,7 +66,7 @@ def get_paginate_count(request):
 
 
     Return the lesser of the calculated value and MAX_PAGE_SIZE.
     Return the lesser of the calculated value and MAX_PAGE_SIZE.
     """
     """
-    config = Config()
+    config = get_config()
 
 
     if 'per_page' in request.GET:
     if 'per_page' in request.GET:
         try:
         try:

+ 2 - 2
netbox/utilities/templatetags/helpers.py

@@ -14,7 +14,7 @@ from django.utils.html import strip_tags
 from django.utils.safestring import mark_safe
 from django.utils.safestring import mark_safe
 from markdown import markdown
 from markdown import markdown
 
 
-from netbox.config import Config
+from netbox.config import get_config
 from utilities.forms import get_selected_values, TableConfigForm
 from utilities.forms import get_selected_values, TableConfigForm
 from utilities.utils import foreground_color
 from utilities.utils import foreground_color
 
 
@@ -45,7 +45,7 @@ def render_markdown(value):
     value = strip_tags(value)
     value = strip_tags(value)
 
 
     # Sanitize Markdown links
     # Sanitize Markdown links
-    schemes = '|'.join(Config().ALLOWED_URL_SCHEMES)
+    schemes = '|'.join(get_config().ALLOWED_URL_SCHEMES)
     pattern = fr'\[(.+)\]\((?!({schemes})).*:(.+)\)'
     pattern = fr'\[(.+)\]\((?!({schemes})).*:(.+)\)'
     value = re.sub(pattern, '[\\1](\\3)', value, flags=re.IGNORECASE)
     value = re.sub(pattern, '[\\1](\\3)', value, flags=re.IGNORECASE)
 
 

+ 2 - 2
netbox/utilities/tests/test_api.py

@@ -9,7 +9,7 @@ from dcim.models import Region, Site
 from extras.choices import CustomFieldTypeChoices
 from extras.choices import CustomFieldTypeChoices
 from extras.models import CustomField
 from extras.models import CustomField
 from ipam.models import VLAN
 from ipam.models import VLAN
-from netbox.config import Config
+from netbox.config import get_config
 from utilities.testing import APITestCase, disable_warnings
 from utilities.testing import APITestCase, disable_warnings
 
 
 
 
@@ -137,7 +137,7 @@ class APIPaginationTestCase(APITestCase):
 
 
     def test_default_page_size(self):
     def test_default_page_size(self):
         response = self.client.get(self.url, format='json', **self.header)
         response = self.client.get(self.url, format='json', **self.header)
-        page_size = Config().PAGINATE_COUNT
+        page_size = get_config().PAGINATE_COUNT
         self.assertLess(page_size, 100, "Default page size not sufficient for data set")
         self.assertLess(page_size, 100, "Default page size not sufficient for data set")
 
 
         self.assertHttpStatus(response, status.HTTP_200_OK)
         self.assertHttpStatus(response, status.HTTP_200_OK)

+ 2 - 2
netbox/utilities/validators.py

@@ -3,7 +3,7 @@ import re
 from django.core.exceptions import ValidationError
 from django.core.exceptions import ValidationError
 from django.core.validators import _lazy_re_compile, BaseValidator, URLValidator
 from django.core.validators import _lazy_re_compile, BaseValidator, URLValidator
 
 
-from netbox.config import Config
+from netbox.config import get_config
 
 
 
 
 class EnhancedURLValidator(URLValidator):
 class EnhancedURLValidator(URLValidator):
@@ -24,7 +24,7 @@ class EnhancedURLValidator(URLValidator):
     def __init__(self, schemes=None, **kwargs):
     def __init__(self, schemes=None, **kwargs):
         super().__init__(**kwargs)
         super().__init__(**kwargs)
         if schemes is not None:
         if schemes is not None:
-            self.schemes = Config().ALLOWED_URL_SCHEMES
+            self.schemes = get_config().ALLOWED_URL_SCHEMES
 
 
 
 
 class ExclusionValidator(BaseValidator):
 class ExclusionValidator(BaseValidator):

+ 2 - 2
netbox/virtualization/models.py

@@ -8,7 +8,7 @@ from dcim.models import BaseInterface, Device
 from extras.models import ConfigContextModel
 from extras.models import ConfigContextModel
 from extras.querysets import ConfigContextModelQuerySet
 from extras.querysets import ConfigContextModelQuerySet
 from extras.utils import extras_features
 from extras.utils import extras_features
-from netbox.config import Config
+from netbox.config import get_config
 from netbox.models import OrganizationalModel, PrimaryModel
 from netbox.models import OrganizationalModel, PrimaryModel
 from utilities.fields import NaturalOrderingField
 from utilities.fields import NaturalOrderingField
 from utilities.ordering import naturalize_interface
 from utilities.ordering import naturalize_interface
@@ -340,7 +340,7 @@ class VirtualMachine(PrimaryModel, ConfigContextModel):
 
 
     @property
     @property
     def primary_ip(self):
     def primary_ip(self):
-        if Config().PREFER_IPV4 and self.primary_ip4:
+        if get_config().PREFER_IPV4 and self.primary_ip4:
             return self.primary_ip4
             return self.primary_ip4
         elif self.primary_ip6:
         elif self.primary_ip6:
             return self.primary_ip6
             return self.primary_ip6