Explorar o código

Closes #4173: Return graceful error message when webhook queuing fails

Jeremy Stretch %!s(int64=6) %!d(string=hai) anos
pai
achega
9128dc961c

+ 4 - 0
docs/release-notes/version-2.7.md

@@ -1,5 +1,9 @@
 # v2.7.8 (FUTURE)
 # v2.7.8 (FUTURE)
 
 
+## Enhancements
+
+* [#4173](https://github.com/netbox-community/netbox/issues/4173) - Return graceful error message when webhook queuing fails
+
 ## Bug Fixes
 ## Bug Fixes
 
 
 * [#4224](https://github.com/netbox-community/netbox/issues/4224) - Fix display of rear device image if front image is not defined 
 * [#4224](https://github.com/netbox-community/netbox/issues/4224) - Fix display of rear device image if front image is not defined 

+ 14 - 1
netbox/extras/middleware.py

@@ -5,11 +5,14 @@ from copy import deepcopy
 from datetime import timedelta
 from datetime import timedelta
 
 
 from django.conf import settings
 from django.conf import settings
+from django.contrib import messages
 from django.db.models.signals import pre_delete, post_save
 from django.db.models.signals import pre_delete, post_save
 from django.utils import timezone
 from django.utils import timezone
 from django_prometheus.models import model_deletes, model_inserts, model_updates
 from django_prometheus.models import model_deletes, model_inserts, model_updates
+from redis.exceptions import RedisError
 
 
 from extras.utils import is_taggable
 from extras.utils import is_taggable
+from utilities.api import is_api_request
 from utilities.querysets import DummyQuerySet
 from utilities.querysets import DummyQuerySet
 from .choices import ObjectChangeActionChoices
 from .choices import ObjectChangeActionChoices
 from .models import ObjectChange
 from .models import ObjectChange
@@ -99,6 +102,7 @@ class ObjectChangeMiddleware(object):
             return response
             return response
 
 
         # Create records for any cached objects that were changed.
         # Create records for any cached objects that were changed.
+        redis_failed = False
         for instance, action in _thread_locals.changed_objects:
         for instance, action in _thread_locals.changed_objects:
 
 
             # Refresh cached custom field values
             # Refresh cached custom field values
@@ -114,7 +118,16 @@ class ObjectChangeMiddleware(object):
                 objectchange.save()
                 objectchange.save()
 
 
             # Enqueue webhooks
             # Enqueue webhooks
-            enqueue_webhooks(instance, request.user, request.id, action)
+            try:
+                enqueue_webhooks(instance, request.user, request.id, action)
+            except RedisError as e:
+                if not redis_failed and not is_api_request(request):
+                    messages.error(
+                        request,
+                        "There was an error processing webhooks for this request. Check that the Redis service is "
+                        "running and reachable. The full error details were: {}".format(e)
+                    )
+                    redis_failed = True
 
 
             # Increment metric counters
             # Increment metric counters
             if action == ObjectChangeActionChoices.ACTION_CREATE:
             if action == ObjectChangeActionChoices.ACTION_CREATE:

+ 9 - 0
netbox/utilities/api.py

@@ -6,6 +6,7 @@ from django.contrib.contenttypes.models import ContentType
 from django.core.exceptions import FieldError, MultipleObjectsReturned, ObjectDoesNotExist
 from django.core.exceptions import FieldError, MultipleObjectsReturned, ObjectDoesNotExist
 from django.db.models import ManyToManyField, ProtectedError
 from django.db.models import ManyToManyField, ProtectedError
 from django.http import Http404
 from django.http import Http404
+from django.urls import reverse
 from rest_framework.exceptions import APIException
 from rest_framework.exceptions import APIException
 from rest_framework.permissions import BasePermission
 from rest_framework.permissions import BasePermission
 from rest_framework.relations import PrimaryKeyRelatedField, RelatedField
 from rest_framework.relations import PrimaryKeyRelatedField, RelatedField
@@ -41,6 +42,14 @@ def get_serializer_for_model(model, prefix=''):
         )
         )
 
 
 
 
+def is_api_request(request):
+    """
+    Return True of the request is being made via the REST API.
+    """
+    api_path = reverse('api-root')
+    return request.path_info.startswith(api_path)
+
+
 #
 #
 # Authentication
 # Authentication
 #
 #

+ 2 - 2
netbox/utilities/middleware.py

@@ -5,6 +5,7 @@ from django.db import ProgrammingError
 from django.http import Http404, HttpResponseRedirect
 from django.http import Http404, HttpResponseRedirect
 from django.urls import reverse
 from django.urls import reverse
 
 
+from .api import is_api_request
 from .views import server_error
 from .views import server_error
 
 
 
 
@@ -38,9 +39,8 @@ class APIVersionMiddleware(object):
         self.get_response = get_response
         self.get_response = get_response
 
 
     def __call__(self, request):
     def __call__(self, request):
-        api_path = reverse('api-root')
         response = self.get_response(request)
         response = self.get_response(request)
-        if request.path_info.startswith(api_path):
+        if is_api_request(request):
             response['API-Version'] = settings.REST_FRAMEWORK_VERSION
             response['API-Version'] = settings.REST_FRAMEWORK_VERSION
         return response
         return response