mirror of
https://github.com/netbox-community/netbox.git
synced 2024-05-10 07:54:54 +00:00
12336 make region API calls atomic (#13942)
* 12336 make region API calls atomic * 12336 switch to pg locks * 12336 add locks to all views using mptt models * 12336 fix ADVISORY_LOCK_KEYS reference * 12336 review changes * Tweak advisory lock numbering --------- Co-authored-by: Jeremy Stretch <jstretch@netboxlabs.com>
This commit is contained in:
@ -20,7 +20,7 @@ from netbox.api.authentication import IsAuthenticatedOrLoginNotRequired
|
|||||||
from netbox.api.metadata import ContentTypeMetadata
|
from netbox.api.metadata import ContentTypeMetadata
|
||||||
from netbox.api.pagination import StripCountAnnotationsPaginator
|
from netbox.api.pagination import StripCountAnnotationsPaginator
|
||||||
from netbox.api.renderers import TextRenderer
|
from netbox.api.renderers import TextRenderer
|
||||||
from netbox.api.viewsets import NetBoxModelViewSet
|
from netbox.api.viewsets import NetBoxModelViewSet, MPTTLockedMixin
|
||||||
from netbox.api.viewsets.mixins import SequentialBulkCreatesMixin
|
from netbox.api.viewsets.mixins import SequentialBulkCreatesMixin
|
||||||
from netbox.constants import NESTED_SERIALIZER_PREFIX
|
from netbox.constants import NESTED_SERIALIZER_PREFIX
|
||||||
from utilities.api import get_serializer_for_model
|
from utilities.api import get_serializer_for_model
|
||||||
@ -98,7 +98,7 @@ class PassThroughPortMixin(object):
|
|||||||
# Regions
|
# Regions
|
||||||
#
|
#
|
||||||
|
|
||||||
class RegionViewSet(NetBoxModelViewSet):
|
class RegionViewSet(MPTTLockedMixin, NetBoxModelViewSet):
|
||||||
queryset = Region.objects.add_related_count(
|
queryset = Region.objects.add_related_count(
|
||||||
Region.objects.all(),
|
Region.objects.all(),
|
||||||
Site,
|
Site,
|
||||||
@ -114,7 +114,7 @@ class RegionViewSet(NetBoxModelViewSet):
|
|||||||
# Site groups
|
# Site groups
|
||||||
#
|
#
|
||||||
|
|
||||||
class SiteGroupViewSet(NetBoxModelViewSet):
|
class SiteGroupViewSet(MPTTLockedMixin, NetBoxModelViewSet):
|
||||||
queryset = SiteGroup.objects.add_related_count(
|
queryset = SiteGroup.objects.add_related_count(
|
||||||
SiteGroup.objects.all(),
|
SiteGroup.objects.all(),
|
||||||
Site,
|
Site,
|
||||||
@ -149,7 +149,7 @@ class SiteViewSet(NetBoxModelViewSet):
|
|||||||
# Locations
|
# Locations
|
||||||
#
|
#
|
||||||
|
|
||||||
class LocationViewSet(NetBoxModelViewSet):
|
class LocationViewSet(MPTTLockedMixin, NetBoxModelViewSet):
|
||||||
queryset = Location.objects.add_related_count(
|
queryset = Location.objects.add_related_count(
|
||||||
Location.objects.add_related_count(
|
Location.objects.add_related_count(
|
||||||
Location.objects.all(),
|
Location.objects.all(),
|
||||||
@ -350,7 +350,7 @@ class DeviceBayTemplateViewSet(NetBoxModelViewSet):
|
|||||||
filterset_class = filtersets.DeviceBayTemplateFilterSet
|
filterset_class = filtersets.DeviceBayTemplateFilterSet
|
||||||
|
|
||||||
|
|
||||||
class InventoryItemTemplateViewSet(NetBoxModelViewSet):
|
class InventoryItemTemplateViewSet(MPTTLockedMixin, NetBoxModelViewSet):
|
||||||
queryset = InventoryItemTemplate.objects.prefetch_related('device_type__manufacturer', 'role')
|
queryset = InventoryItemTemplate.objects.prefetch_related('device_type__manufacturer', 'role')
|
||||||
serializer_class = serializers.InventoryItemTemplateSerializer
|
serializer_class = serializers.InventoryItemTemplateSerializer
|
||||||
filterset_class = filtersets.InventoryItemTemplateFilterSet
|
filterset_class = filtersets.InventoryItemTemplateFilterSet
|
||||||
@ -538,7 +538,7 @@ class DeviceBayViewSet(NetBoxModelViewSet):
|
|||||||
brief_prefetch_fields = ['device']
|
brief_prefetch_fields = ['device']
|
||||||
|
|
||||||
|
|
||||||
class InventoryItemViewSet(NetBoxModelViewSet):
|
class InventoryItemViewSet(MPTTLockedMixin, NetBoxModelViewSet):
|
||||||
queryset = InventoryItem.objects.prefetch_related('device', 'manufacturer', 'tags')
|
queryset = InventoryItem.objects.prefetch_related('device', 'manufacturer', 'tags')
|
||||||
serializer_class = serializers.InventoryItemSerializer
|
serializer_class = serializers.InventoryItemSerializer
|
||||||
filterset_class = filtersets.InventoryItemFilterSet
|
filterset_class = filtersets.InventoryItemFilterSet
|
||||||
|
@ -3,6 +3,8 @@ import logging
|
|||||||
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.db.models import ProtectedError
|
from django.db.models import ProtectedError
|
||||||
|
from django_pglocks import advisory_lock
|
||||||
|
from netbox.constants import ADVISORY_LOCK_KEYS
|
||||||
from rest_framework import mixins as drf_mixins
|
from rest_framework import mixins as drf_mixins
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.viewsets import GenericViewSet
|
from rest_framework.viewsets import GenericViewSet
|
||||||
@ -157,3 +159,22 @@ class NetBoxModelViewSet(
|
|||||||
logger.info(f"Deleting {model._meta.verbose_name} {instance} (PK: {instance.pk})")
|
logger.info(f"Deleting {model._meta.verbose_name} {instance} (PK: {instance.pk})")
|
||||||
|
|
||||||
return super().perform_destroy(instance)
|
return super().perform_destroy(instance)
|
||||||
|
|
||||||
|
|
||||||
|
class MPTTLockedMixin:
|
||||||
|
"""
|
||||||
|
Puts pglock on objects that derive from MPTTModel for parallel API calling.
|
||||||
|
Note: If adding this to a view, must add the model name to ADVISORY_LOCK_KEYS
|
||||||
|
"""
|
||||||
|
|
||||||
|
def create(self, request, *args, **kwargs):
|
||||||
|
with advisory_lock(ADVISORY_LOCK_KEYS[self.queryset.model._meta.model_name]):
|
||||||
|
return super().create(request, *args, **kwargs)
|
||||||
|
|
||||||
|
def update(self, request, *args, **kwargs):
|
||||||
|
with advisory_lock(ADVISORY_LOCK_KEYS[self.queryset.model._meta.model_name]):
|
||||||
|
return super().update(request, *args, **kwargs)
|
||||||
|
|
||||||
|
def destroy(self, request, *args, **kwargs):
|
||||||
|
with advisory_lock(ADVISORY_LOCK_KEYS[self.queryset.model._meta.model_name]):
|
||||||
|
return super().destroy(request, *args, **kwargs)
|
||||||
|
@ -11,8 +11,19 @@ RQ_QUEUE_LOW = 'low'
|
|||||||
# When adding a new key, pick something arbitrary and unique so that it is easily searchable in
|
# When adding a new key, pick something arbitrary and unique so that it is easily searchable in
|
||||||
# query logs.
|
# query logs.
|
||||||
ADVISORY_LOCK_KEYS = {
|
ADVISORY_LOCK_KEYS = {
|
||||||
|
# Available object locks
|
||||||
'available-prefixes': 100100,
|
'available-prefixes': 100100,
|
||||||
'available-ips': 100200,
|
'available-ips': 100200,
|
||||||
'available-vlans': 100300,
|
'available-vlans': 100300,
|
||||||
'available-asns': 100400,
|
'available-asns': 100400,
|
||||||
|
|
||||||
|
# MPTT locks
|
||||||
|
'region': 105100,
|
||||||
|
'sitegroup': 105200,
|
||||||
|
'location': 105300,
|
||||||
|
'tenantgroup': 105400,
|
||||||
|
'contactgroup': 105500,
|
||||||
|
'wirelesslangroup': 105600,
|
||||||
|
'inventoryitem': 105700,
|
||||||
|
'inventoryitemtemplate': 105800,
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@ from rest_framework.routers import APIRootView
|
|||||||
from circuits.models import Circuit
|
from circuits.models import Circuit
|
||||||
from dcim.models import Device, Rack, Site
|
from dcim.models import Device, Rack, Site
|
||||||
from ipam.models import IPAddress, Prefix, VLAN, VRF
|
from ipam.models import IPAddress, Prefix, VLAN, VRF
|
||||||
from netbox.api.viewsets import NetBoxModelViewSet
|
from netbox.api.viewsets import NetBoxModelViewSet, MPTTLockedMixin
|
||||||
from tenancy import filtersets
|
from tenancy import filtersets
|
||||||
from tenancy.models import *
|
from tenancy.models import *
|
||||||
from utilities.utils import count_related
|
from utilities.utils import count_related
|
||||||
@ -23,7 +23,7 @@ class TenancyRootView(APIRootView):
|
|||||||
# Tenants
|
# Tenants
|
||||||
#
|
#
|
||||||
|
|
||||||
class TenantGroupViewSet(NetBoxModelViewSet):
|
class TenantGroupViewSet(MPTTLockedMixin, NetBoxModelViewSet):
|
||||||
queryset = TenantGroup.objects.add_related_count(
|
queryset = TenantGroup.objects.add_related_count(
|
||||||
TenantGroup.objects.all(),
|
TenantGroup.objects.all(),
|
||||||
Tenant,
|
Tenant,
|
||||||
@ -58,7 +58,7 @@ class TenantViewSet(NetBoxModelViewSet):
|
|||||||
# Contacts
|
# Contacts
|
||||||
#
|
#
|
||||||
|
|
||||||
class ContactGroupViewSet(NetBoxModelViewSet):
|
class ContactGroupViewSet(MPTTLockedMixin, NetBoxModelViewSet):
|
||||||
queryset = ContactGroup.objects.add_related_count(
|
queryset = ContactGroup.objects.add_related_count(
|
||||||
ContactGroup.objects.all(),
|
ContactGroup.objects.all(),
|
||||||
Contact,
|
Contact,
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
from rest_framework.routers import APIRootView
|
from rest_framework.routers import APIRootView
|
||||||
|
|
||||||
from netbox.api.viewsets import NetBoxModelViewSet
|
from netbox.api.viewsets import NetBoxModelViewSet, MPTTLockedMixin
|
||||||
from wireless import filtersets
|
from wireless import filtersets
|
||||||
from wireless.models import *
|
from wireless.models import *
|
||||||
from . import serializers
|
from . import serializers
|
||||||
@ -14,7 +14,7 @@ class WirelessRootView(APIRootView):
|
|||||||
return 'Wireless'
|
return 'Wireless'
|
||||||
|
|
||||||
|
|
||||||
class WirelessLANGroupViewSet(NetBoxModelViewSet):
|
class WirelessLANGroupViewSet(MPTTLockedMixin, NetBoxModelViewSet):
|
||||||
queryset = WirelessLANGroup.objects.add_related_count(
|
queryset = WirelessLANGroup.objects.add_related_count(
|
||||||
WirelessLANGroup.objects.all(),
|
WirelessLANGroup.objects.all(),
|
||||||
WirelessLAN,
|
WirelessLAN,
|
||||||
|
@ -2,7 +2,6 @@ from django.core.exceptions import ValidationError
|
|||||||
from django.db import models
|
from django.db import models
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from mptt.models import MPTTModel
|
|
||||||
|
|
||||||
from dcim.choices import LinkStatusChoices
|
from dcim.choices import LinkStatusChoices
|
||||||
from dcim.constants import WIRELESS_IFACE_TYPES
|
from dcim.constants import WIRELESS_IFACE_TYPES
|
||||||
|
Reference in New Issue
Block a user