Skip to content

Commit d77d45e

Browse files
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 <[email protected]>
1 parent a24864b commit d77d45e

File tree

6 files changed

+43
-12
lines changed

6 files changed

+43
-12
lines changed

netbox/dcim/api/views.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
from netbox.api.metadata import ContentTypeMetadata
2121
from netbox.api.pagination import StripCountAnnotationsPaginator
2222
from netbox.api.renderers import TextRenderer
23-
from netbox.api.viewsets import NetBoxModelViewSet
23+
from netbox.api.viewsets import NetBoxModelViewSet, MPTTLockedMixin
2424
from netbox.api.viewsets.mixins import SequentialBulkCreatesMixin
2525
from netbox.constants import NESTED_SERIALIZER_PREFIX
2626
from utilities.api import get_serializer_for_model
@@ -98,7 +98,7 @@ def paths(self, request, pk):
9898
# Regions
9999
#
100100

101-
class RegionViewSet(NetBoxModelViewSet):
101+
class RegionViewSet(MPTTLockedMixin, NetBoxModelViewSet):
102102
queryset = Region.objects.add_related_count(
103103
Region.objects.all(),
104104
Site,
@@ -114,7 +114,7 @@ class RegionViewSet(NetBoxModelViewSet):
114114
# Site groups
115115
#
116116

117-
class SiteGroupViewSet(NetBoxModelViewSet):
117+
class SiteGroupViewSet(MPTTLockedMixin, NetBoxModelViewSet):
118118
queryset = SiteGroup.objects.add_related_count(
119119
SiteGroup.objects.all(),
120120
Site,
@@ -149,7 +149,7 @@ class SiteViewSet(NetBoxModelViewSet):
149149
# Locations
150150
#
151151

152-
class LocationViewSet(NetBoxModelViewSet):
152+
class LocationViewSet(MPTTLockedMixin, NetBoxModelViewSet):
153153
queryset = Location.objects.add_related_count(
154154
Location.objects.add_related_count(
155155
Location.objects.all(),
@@ -350,7 +350,7 @@ class DeviceBayTemplateViewSet(NetBoxModelViewSet):
350350
filterset_class = filtersets.DeviceBayTemplateFilterSet
351351

352352

353-
class InventoryItemTemplateViewSet(NetBoxModelViewSet):
353+
class InventoryItemTemplateViewSet(MPTTLockedMixin, NetBoxModelViewSet):
354354
queryset = InventoryItemTemplate.objects.prefetch_related('device_type__manufacturer', 'role')
355355
serializer_class = serializers.InventoryItemTemplateSerializer
356356
filterset_class = filtersets.InventoryItemTemplateFilterSet
@@ -538,7 +538,7 @@ class DeviceBayViewSet(NetBoxModelViewSet):
538538
brief_prefetch_fields = ['device']
539539

540540

541-
class InventoryItemViewSet(NetBoxModelViewSet):
541+
class InventoryItemViewSet(MPTTLockedMixin, NetBoxModelViewSet):
542542
queryset = InventoryItem.objects.prefetch_related('device', 'manufacturer', 'tags')
543543
serializer_class = serializers.InventoryItemSerializer
544544
filterset_class = filtersets.InventoryItemFilterSet

netbox/netbox/api/viewsets/__init__.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
from django.core.exceptions import ObjectDoesNotExist, PermissionDenied
44
from django.db import transaction
55
from django.db.models import ProtectedError
6+
from django_pglocks import advisory_lock
7+
from netbox.constants import ADVISORY_LOCK_KEYS
68
from rest_framework import mixins as drf_mixins
79
from rest_framework.response import Response
810
from rest_framework.viewsets import GenericViewSet
@@ -157,3 +159,22 @@ def perform_destroy(self, instance):
157159
logger.info(f"Deleting {model._meta.verbose_name} {instance} (PK: {instance.pk})")
158160

159161
return super().perform_destroy(instance)
162+
163+
164+
class MPTTLockedMixin:
165+
"""
166+
Puts pglock on objects that derive from MPTTModel for parallel API calling.
167+
Note: If adding this to a view, must add the model name to ADVISORY_LOCK_KEYS
168+
"""
169+
170+
def create(self, request, *args, **kwargs):
171+
with advisory_lock(ADVISORY_LOCK_KEYS[self.queryset.model._meta.model_name]):
172+
return super().create(request, *args, **kwargs)
173+
174+
def update(self, request, *args, **kwargs):
175+
with advisory_lock(ADVISORY_LOCK_KEYS[self.queryset.model._meta.model_name]):
176+
return super().update(request, *args, **kwargs)
177+
178+
def destroy(self, request, *args, **kwargs):
179+
with advisory_lock(ADVISORY_LOCK_KEYS[self.queryset.model._meta.model_name]):
180+
return super().destroy(request, *args, **kwargs)

netbox/netbox/constants.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,19 @@
1111
# When adding a new key, pick something arbitrary and unique so that it is easily searchable in
1212
# query logs.
1313
ADVISORY_LOCK_KEYS = {
14+
# Available object locks
1415
'available-prefixes': 100100,
1516
'available-ips': 100200,
1617
'available-vlans': 100300,
1718
'available-asns': 100400,
19+
20+
# MPTT locks
21+
'region': 105100,
22+
'sitegroup': 105200,
23+
'location': 105300,
24+
'tenantgroup': 105400,
25+
'contactgroup': 105500,
26+
'wirelesslangroup': 105600,
27+
'inventoryitem': 105700,
28+
'inventoryitemtemplate': 105800,
1829
}

netbox/tenancy/api/views.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from circuits.models import Circuit
44
from dcim.models import Device, Rack, Site
55
from ipam.models import IPAddress, Prefix, VLAN, VRF
6-
from netbox.api.viewsets import NetBoxModelViewSet
6+
from netbox.api.viewsets import NetBoxModelViewSet, MPTTLockedMixin
77
from tenancy import filtersets
88
from tenancy.models import *
99
from utilities.utils import count_related
@@ -23,7 +23,7 @@ def get_view_name(self):
2323
# Tenants
2424
#
2525

26-
class TenantGroupViewSet(NetBoxModelViewSet):
26+
class TenantGroupViewSet(MPTTLockedMixin, NetBoxModelViewSet):
2727
queryset = TenantGroup.objects.add_related_count(
2828
TenantGroup.objects.all(),
2929
Tenant,
@@ -58,7 +58,7 @@ class TenantViewSet(NetBoxModelViewSet):
5858
# Contacts
5959
#
6060

61-
class ContactGroupViewSet(NetBoxModelViewSet):
61+
class ContactGroupViewSet(MPTTLockedMixin, NetBoxModelViewSet):
6262
queryset = ContactGroup.objects.add_related_count(
6363
ContactGroup.objects.all(),
6464
Contact,

netbox/wireless/api/views.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from rest_framework.routers import APIRootView
22

3-
from netbox.api.viewsets import NetBoxModelViewSet
3+
from netbox.api.viewsets import NetBoxModelViewSet, MPTTLockedMixin
44
from wireless import filtersets
55
from wireless.models import *
66
from . import serializers
@@ -14,7 +14,7 @@ def get_view_name(self):
1414
return 'Wireless'
1515

1616

17-
class WirelessLANGroupViewSet(NetBoxModelViewSet):
17+
class WirelessLANGroupViewSet(MPTTLockedMixin, NetBoxModelViewSet):
1818
queryset = WirelessLANGroup.objects.add_related_count(
1919
WirelessLANGroup.objects.all(),
2020
WirelessLAN,

netbox/wireless/models.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
from django.db import models
33
from django.urls import reverse
44
from django.utils.translation import gettext_lazy as _
5-
from mptt.models import MPTTModel
65

76
from dcim.choices import LinkStatusChoices
87
from dcim.constants import WIRELESS_IFACE_TYPES

0 commit comments

Comments
 (0)