# -*- coding: utf-8 -*- import logging from django.conf import settings from django.core.exceptions import ImproperlyConfigured from redis.sentinel import Sentinel from django_redis.client import DefaultClient DJANGO_REDIS_LOGGER = getattr(settings, "DJANGO_REDIS_LOGGER", False) DJANGO_REDIS_CLOSE_CONNECTION = getattr(settings, "DJANGO_REDIS_CLOSE_CONNECTION", False) class SentinelClient(DefaultClient): """ Sentinel client object extending django-redis DefaultClient """ def __init__(self, server, params, backend): """ Slightly different logic than connection to multiple Redis servers. Reserve only one write and read descriptors, as they will be closed on exit anyway. """ super(SentinelClient, self).__init__(server, params, backend) self._client = None self._connection_string = server self.log = logging.getLogger((DJANGO_REDIS_LOGGER or __name__)) def parse_connection_string(self, constring): """ Parse connection string in format: master_name/sentinel_server:port,sentinel_server:port/db_id Returns master name, list of tuples with pair (host, port) and db_id """ try: connection_params = constring.split('://') if len(connection_params) > 1: connection_params = connection_params[1].split('/') master_name = connection_params[0] servers = [ host_port.split(':') for host_port in connection_params[1].split(',') ] sentinel_hosts = [(host, int(port)) for host, port in servers] db = connection_params[2] except (ValueError, TypeError, IndexError): raise ImproperlyConfigured("Incorrect format '%s'" % (constring)) return master_name, sentinel_hosts, db def get_client(self, write=True, tried=(), show_index=False): """ Method used to obtain a raw redis client. This function is used by almost all cache backend operations to obtain a native redis client/connection instance. """ if self._client is None: self._client = self.connect() if show_index: return self._client, 0 else: return self._client def connect(self, index=0): """ Creates a redis connection with connection pool. """ master_name, sentinel_hosts, db = self.parse_connection_string( self._connection_string) sentinel_timeout = self._options.get('SENTINEL_TIMEOUT', 1) password = self._options.get('PASSWORD', None) sentinel = Sentinel( sentinel_hosts, socket_timeout=sentinel_timeout, password=password) return sentinel.master_for( master_name, socket_timeout=sentinel_timeout, db=db, ) def close(self, **kwargs): """ Closing old connections, as master may change in time of inactivity. """ if DJANGO_REDIS_CLOSE_CONNECTION and self._client is not None: del self._client