mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-21 19:47:20 -06:00
Use context vars instead of thread-local storage for change logging
This commit is contained in:
parent
8400509358
commit
cd8943144b
@ -3,8 +3,7 @@ from contextlib import contextmanager
|
|||||||
from django.db.models.signals import m2m_changed, pre_delete, post_save
|
from django.db.models.signals import m2m_changed, pre_delete, post_save
|
||||||
|
|
||||||
from extras.signals import clear_webhooks, clear_webhook_queue, handle_changed_object, handle_deleted_object
|
from extras.signals import clear_webhooks, clear_webhook_queue, handle_changed_object, handle_deleted_object
|
||||||
from netbox import thread_locals
|
from netbox.context import current_request, webhooks_queue
|
||||||
from netbox.request_context import set_request
|
|
||||||
from .webhooks import flush_webhooks
|
from .webhooks import flush_webhooks
|
||||||
|
|
||||||
|
|
||||||
@ -16,8 +15,8 @@ def change_logging(request):
|
|||||||
|
|
||||||
:param request: WSGIRequest object with a unique `id` set
|
:param request: WSGIRequest object with a unique `id` set
|
||||||
"""
|
"""
|
||||||
set_request(request)
|
current_request.set(request)
|
||||||
thread_locals.webhook_queue = []
|
webhooks_queue.set([])
|
||||||
|
|
||||||
# Connect our receivers to the post_save and post_delete signals.
|
# Connect our receivers to the post_save and post_delete signals.
|
||||||
post_save.connect(handle_changed_object, dispatch_uid='handle_changed_object')
|
post_save.connect(handle_changed_object, dispatch_uid='handle_changed_object')
|
||||||
@ -35,8 +34,8 @@ def change_logging(request):
|
|||||||
clear_webhooks.disconnect(clear_webhook_queue, dispatch_uid='clear_webhook_queue')
|
clear_webhooks.disconnect(clear_webhook_queue, dispatch_uid='clear_webhook_queue')
|
||||||
|
|
||||||
# Flush queued webhooks to RQ
|
# Flush queued webhooks to RQ
|
||||||
flush_webhooks(thread_locals.webhook_queue)
|
flush_webhooks(webhooks_queue.get())
|
||||||
del thread_locals.webhook_queue
|
|
||||||
|
|
||||||
# Clear the request from thread-local storage
|
# Clear context vars
|
||||||
set_request(None)
|
current_request.set(None)
|
||||||
|
webhooks_queue.set([])
|
||||||
|
@ -7,9 +7,8 @@ from django.dispatch import receiver, Signal
|
|||||||
from django_prometheus.models import model_deletes, model_inserts, model_updates
|
from django_prometheus.models import model_deletes, model_inserts, model_updates
|
||||||
|
|
||||||
from extras.validators import CustomValidator
|
from extras.validators import CustomValidator
|
||||||
from netbox import thread_locals
|
|
||||||
from netbox.config import get_config
|
from netbox.config import get_config
|
||||||
from netbox.request_context import get_request
|
from netbox.context import current_request, webhooks_queue
|
||||||
from netbox.signals import post_clean
|
from netbox.signals import post_clean
|
||||||
from .choices import ObjectChangeActionChoices
|
from .choices import ObjectChangeActionChoices
|
||||||
from .models import ConfigRevision, CustomField, ObjectChange
|
from .models import ConfigRevision, CustomField, ObjectChange
|
||||||
@ -30,7 +29,7 @@ def handle_changed_object(sender, instance, **kwargs):
|
|||||||
if not hasattr(instance, 'to_objectchange'):
|
if not hasattr(instance, 'to_objectchange'):
|
||||||
return
|
return
|
||||||
|
|
||||||
request = get_request()
|
request = current_request.get()
|
||||||
m2m_changed = False
|
m2m_changed = False
|
||||||
|
|
||||||
def is_same_object(instance, webhook_data):
|
def is_same_object(instance, webhook_data):
|
||||||
@ -69,13 +68,14 @@ def handle_changed_object(sender, instance, **kwargs):
|
|||||||
objectchange.save()
|
objectchange.save()
|
||||||
|
|
||||||
# If this is an M2M change, update the previously queued webhook (from post_save)
|
# If this is an M2M change, update the previously queued webhook (from post_save)
|
||||||
webhook_queue = thread_locals.webhook_queue
|
queue = webhooks_queue.get()
|
||||||
if m2m_changed and webhook_queue and is_same_object(instance, webhook_queue[-1]):
|
if m2m_changed and queue and is_same_object(instance, queue[-1]):
|
||||||
instance.refresh_from_db() # Ensure that we're working with fresh M2M assignments
|
instance.refresh_from_db() # Ensure that we're working with fresh M2M assignments
|
||||||
webhook_queue[-1]['data'] = serialize_for_webhook(instance)
|
queue[-1]['data'] = serialize_for_webhook(instance)
|
||||||
webhook_queue[-1]['snapshots']['postchange'] = get_snapshots(instance, action)['postchange']
|
queue[-1]['snapshots']['postchange'] = get_snapshots(instance, action)['postchange']
|
||||||
else:
|
else:
|
||||||
enqueue_object(webhook_queue, instance, request.user, request.id, action)
|
enqueue_object(queue, instance, request.user, request.id, action)
|
||||||
|
webhooks_queue.set(queue)
|
||||||
|
|
||||||
# Increment metric counters
|
# Increment metric counters
|
||||||
if action == ObjectChangeActionChoices.ACTION_CREATE:
|
if action == ObjectChangeActionChoices.ACTION_CREATE:
|
||||||
@ -91,7 +91,7 @@ def handle_deleted_object(sender, instance, **kwargs):
|
|||||||
if not hasattr(instance, 'to_objectchange'):
|
if not hasattr(instance, 'to_objectchange'):
|
||||||
return
|
return
|
||||||
|
|
||||||
request = get_request()
|
request = current_request.get()
|
||||||
|
|
||||||
# Record an ObjectChange if applicable
|
# Record an ObjectChange if applicable
|
||||||
if hasattr(instance, 'to_objectchange'):
|
if hasattr(instance, 'to_objectchange'):
|
||||||
@ -101,8 +101,9 @@ def handle_deleted_object(sender, instance, **kwargs):
|
|||||||
objectchange.save()
|
objectchange.save()
|
||||||
|
|
||||||
# Enqueue webhooks
|
# Enqueue webhooks
|
||||||
webhook_queue = thread_locals.webhook_queue
|
queue = webhooks_queue.get()
|
||||||
enqueue_object(webhook_queue, instance, request.user, request.id, ObjectChangeActionChoices.ACTION_DELETE)
|
enqueue_object(queue, instance, request.user, request.id, ObjectChangeActionChoices.ACTION_DELETE)
|
||||||
|
webhooks_queue.set(queue)
|
||||||
|
|
||||||
# Increment metric counters
|
# Increment metric counters
|
||||||
model_deletes.labels(instance._meta.model_name).inc()
|
model_deletes.labels(instance._meta.model_name).inc()
|
||||||
@ -113,10 +114,8 @@ def clear_webhook_queue(sender, **kwargs):
|
|||||||
Delete any queued webhooks (e.g. because of an aborted bulk transaction)
|
Delete any queued webhooks (e.g. because of an aborted bulk transaction)
|
||||||
"""
|
"""
|
||||||
logger = logging.getLogger('webhooks')
|
logger = logging.getLogger('webhooks')
|
||||||
webhook_queue = thread_locals.webhook_queue
|
logger.info(f"Clearing {len(webhooks_queue.get())} queued webhooks ({sender})")
|
||||||
|
webhooks_queue.set([])
|
||||||
logger.info(f"Clearing {len(webhook_queue)} queued webhooks ({sender})")
|
|
||||||
webhook_queue.clear()
|
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
|
@ -1,3 +0,0 @@
|
|||||||
import threading
|
|
||||||
|
|
||||||
thread_locals = threading.local()
|
|
10
netbox/netbox/context.py
Normal file
10
netbox/netbox/context.py
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
from contextvars import ContextVar
|
||||||
|
|
||||||
|
__all__ = (
|
||||||
|
'current_request',
|
||||||
|
'webhooks_queue',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
current_request = ContextVar('current_request')
|
||||||
|
webhooks_queue = ContextVar('webhooks_queue')
|
@ -1,9 +0,0 @@
|
|||||||
from netbox import thread_locals
|
|
||||||
|
|
||||||
|
|
||||||
def set_request(request):
|
|
||||||
thread_locals.request = request
|
|
||||||
|
|
||||||
|
|
||||||
def get_request():
|
|
||||||
return getattr(thread_locals, 'request', None)
|
|
Loading…
Reference in New Issue
Block a user