diff --git a/docs/release-notes/version-2.7.md b/docs/release-notes/version-2.7.md index a7ff3a182..8b10aa76b 100644 --- a/docs/release-notes/version-2.7.md +++ b/docs/release-notes/version-2.7.md @@ -1,5 +1,9 @@ # v2.7.8 (FUTURE) +## Enhancements + +* [#4173](https://github.com/netbox-community/netbox/issues/4173) - Return graceful error message when webhook queuing fails + ## Bug Fixes * [#4224](https://github.com/netbox-community/netbox/issues/4224) - Fix display of rear device image if front image is not defined diff --git a/netbox/extras/middleware.py b/netbox/extras/middleware.py index 3624e11a5..2eec5afc6 100644 --- a/netbox/extras/middleware.py +++ b/netbox/extras/middleware.py @@ -5,11 +5,14 @@ from copy import deepcopy from datetime import timedelta from django.conf import settings +from django.contrib import messages from django.db.models.signals import pre_delete, post_save from django.utils import timezone from django_prometheus.models import model_deletes, model_inserts, model_updates +from redis.exceptions import RedisError from extras.utils import is_taggable +from utilities.api import is_api_request from utilities.querysets import DummyQuerySet from .choices import ObjectChangeActionChoices from .models import ObjectChange @@ -99,6 +102,7 @@ class ObjectChangeMiddleware(object): return response # Create records for any cached objects that were changed. + redis_failed = False for instance, action in _thread_locals.changed_objects: # Refresh cached custom field values @@ -114,7 +118,16 @@ class ObjectChangeMiddleware(object): objectchange.save() # 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 if action == ObjectChangeActionChoices.ACTION_CREATE: diff --git a/netbox/utilities/api.py b/netbox/utilities/api.py index 95de2a25d..72a5735de 100644 --- a/netbox/utilities/api.py +++ b/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.db.models import ManyToManyField, ProtectedError from django.http import Http404 +from django.urls import reverse from rest_framework.exceptions import APIException from rest_framework.permissions import BasePermission 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 # diff --git a/netbox/utilities/middleware.py b/netbox/utilities/middleware.py index 564771821..6cbf4ed17 100644 --- a/netbox/utilities/middleware.py +++ b/netbox/utilities/middleware.py @@ -5,6 +5,7 @@ from django.db import ProgrammingError from django.http import Http404, HttpResponseRedirect from django.urls import reverse +from .api import is_api_request from .views import server_error @@ -38,9 +39,8 @@ class APIVersionMiddleware(object): self.get_response = get_response def __call__(self, request): - api_path = reverse('api-root') response = self.get_response(request) - if request.path_info.startswith(api_path): + if is_api_request(request): response['API-Version'] = settings.REST_FRAMEWORK_VERSION return response