From bd09dab0b817b6c55f05aeffa65843d21a91948c Mon Sep 17 00:00:00 2001 From: Richard O'Dwyer Date: Sat, 6 Jan 2024 23:58:59 +0200 Subject: [PATCH] Adds Redis Sentinel backend --- .../contrib/redis_sentinel/__init__.py | 4 ++ health_check/contrib/redis_sentinel/apps.py | 12 ++++ .../contrib/redis_sentinel/backends.py | 62 +++++++++++++++++++ 3 files changed, 78 insertions(+) create mode 100644 health_check/contrib/redis_sentinel/__init__.py create mode 100644 health_check/contrib/redis_sentinel/apps.py create mode 100644 health_check/contrib/redis_sentinel/backends.py diff --git a/health_check/contrib/redis_sentinel/__init__.py b/health_check/contrib/redis_sentinel/__init__.py new file mode 100644 index 00000000..7df735be --- /dev/null +++ b/health_check/contrib/redis_sentinel/__init__.py @@ -0,0 +1,4 @@ +import django + +if django.VERSION < (3, 2): + default_app_config = "health_check.contrib.redis.apps.HealthCheckConfig" diff --git a/health_check/contrib/redis_sentinel/apps.py b/health_check/contrib/redis_sentinel/apps.py new file mode 100644 index 00000000..890f0080 --- /dev/null +++ b/health_check/contrib/redis_sentinel/apps.py @@ -0,0 +1,12 @@ +from django.apps import AppConfig + +from health_check.plugins import plugin_dir + + +class HealthCheckConfig(AppConfig): + name = "health_check.contrib.redis_sentinel" + + def ready(self): + from .backends import RedisSentinelHealthCheck + + plugin_dir.register(RedisSentinelHealthCheck) diff --git a/health_check/contrib/redis_sentinel/backends.py b/health_check/contrib/redis_sentinel/backends.py new file mode 100644 index 00000000..7bd9d34c --- /dev/null +++ b/health_check/contrib/redis_sentinel/backends.py @@ -0,0 +1,62 @@ +import logging + +from django.conf import settings +from redis import exceptions, Sentinel + +from health_check.backends import BaseHealthCheckBackend +from health_check.exceptions import ServiceUnavailable + +logger = logging.getLogger(__name__) + + +class RedisSentinelHealthCheck(BaseHealthCheckBackend): + """Health check for Redis Sentinel.""" + + redis_sentinels = getattr(settings, 'REDIS_SENTINELS', ()) + redis_master_name = getattr(settings, 'REDIS_MASTER_NAME', 'mymaster') + redis_sentinels_healthcheck_socket_timeout = getattr( + settings, 'REDIS_SENTINELS_HEALTHCHECK_SOCKET_TIMEOUT', 1000 + ) + + def check_status(self, subset=None): + """Check Redis service by pinging the redis instance with a redis connection.""" + logger.debug( + 'Got %s as the redis_sentinels. Connecting to redis...', + self.redis_sentinels, + ) + + logger.debug('Attempting to connect to redis...') + try: + client = self._get_redis_client( + sentinels=self.redis_sentinels, + master_name=self.redis_master_name, + timeout=self.redis_sentinels_healthcheck_socket_timeout, + ) + client.ping() + except ConnectionRefusedError as e: + self.add_error( + ServiceUnavailable( + 'Unable to connect to Redis: Connection was refused.' + ), + e, + ) + except exceptions.TimeoutError as e: + self.add_error( + ServiceUnavailable('Unable to connect to Redis: Timeout.'), e + ) + except exceptions.ConnectionError as e: + self.add_error( + ServiceUnavailable( + 'Unable to connect to Redis: Connection Error' + ), + e, + ) + except BaseException as e: + self.add_error(ServiceUnavailable('Unknown error'), e) + else: + logger.debug('Connection established. Redis is healthy.') + + @staticmethod + def _get_redis_client(sentinels, master_name, timeout): + sentinel = Sentinel(sentinels, socket_timeout=timeout) + return sentinel.master_for(master_name, socket_timeout=timeout)