summaryrefslogtreecommitdiff
path: root/redis/sentinel.py
diff options
context:
space:
mode:
authorVitja Makarov <vitja.makarov@gmail.com>2013-08-22 20:13:05 +0400
committerVitja Makarov <vitja.makarov@gmail.com>2013-09-04 23:34:56 +0400
commit46e475d9b95501a46c7abba9f05d415e27e9a62d (patch)
tree7b4c30c5a6149b445d5df57b75945d038c9ddf4b /redis/sentinel.py
parent4df884101d72cf2c59bc3e7c6a9ec83eb4fc5166 (diff)
downloadredis-py-46e475d9b95501a46c7abba9f05d415e27e9a62d.tar.gz
Add redis.sentinel module
Diffstat (limited to 'redis/sentinel.py')
-rw-r--r--redis/sentinel.py120
1 files changed, 120 insertions, 0 deletions
diff --git a/redis/sentinel.py b/redis/sentinel.py
new file mode 100644
index 0000000..bdc7ca2
--- /dev/null
+++ b/redis/sentinel.py
@@ -0,0 +1,120 @@
+import random
+
+from redis.client import StrictRedis
+from redis.connection import ConnectionPool, Connection
+from redis.exceptions import ConnectionError, ResponseError
+
+
+class MasterNotFoundError(ConnectionError):
+ pass
+
+
+class SlaveNotFoundError(ConnectionError):
+ pass
+
+
+class SentinelManagedConnection(Connection):
+ def __init__(self, **kwargs):
+ self.connection_pool = kwargs.pop('connection_pool')
+ super(SentinelManagedConnection, self).__init__(**kwargs)
+
+ def connect(self):
+ self.host, self.port = self.connection_pool.discover()
+ super(SentinelManagedConnection, self).connect()
+
+
+class SentinelConnectionPool(ConnectionPool):
+ def __init__(self, service_name, sentinel_manager, **kwargs):
+ kwargs['connection_class'] = kwargs.get(
+ 'connection_class', SentinelManagedConnection)
+ self.is_master = kwargs.pop('is_master', True)
+ super(SentinelConnectionPool, self).__init__(**kwargs)
+ self.connection_kwargs['connection_pool'] = self
+ self.service_name = service_name
+ self.master_address = None
+ self.sentinel_manager = sentinel_manager
+
+ def discover_master(self):
+ master_address = self.sentinel_manager.discover_master(
+ self.service_name)
+ if self.master_address is None:
+ self.master_address = master_address
+ elif master_address != self.master_address:
+ # Master address change disconnect
+ self.disconnect()
+ return master_address
+
+ def discover_slave(self):
+ return self.sentinel_manager.discover_slave(self.service_name)
+
+ def discover(self):
+ if self.is_master:
+ return self.discover_master()
+ else:
+ return self.discover_slave()
+
+
+class Sentinel(object):
+ def __init__(self, sentinels, password=None, socket_timeout=None,
+ min_other_sentinels=0):
+ self.sentinels = [StrictRedis(hostname, port, password=password,
+ socket_timeout=socket_timeout)
+ for hostname, port in sentinels]
+ self.min_other_sentinels = min_other_sentinels
+
+ def check_master_state(self, state, service_name):
+ if not state['is_master'] or state['is_sdown'] or state['is_odown']:
+ return False
+ # Check whether our sentinel doesn't see others
+ if state['num-other-sentinels'] < self.min_other_sentinels:
+ return False
+ return True
+
+ def discover_master(self, service_name):
+ for sentinel_no, sentinel in enumerate(self.sentinels):
+ try:
+ masters = sentinel.sentinel_masters()
+ except ConnectionError:
+ continue
+ state = masters.get(service_name)
+ if state and self.check_master_state(state, service_name):
+ # Put this sentinel at the top of the list
+ self.sentinels[0], self.sentinels[sentinel_no] = (
+ sentinel, self.sentinels[0])
+ return state['ip'], state['port']
+ raise MasterNotFoundError("No master found for %r" % (service_name,))
+
+ def choose_slave(self, slaves):
+ slaves_alive = []
+ for slave in slaves:
+ if slave['is_odown'] or slave['is_sdown']:
+ continue
+ slaves_alive.append(slave)
+ if slaves_alive:
+ return random.choice(slaves_alive)
+
+ def discover_slave(self, service_name, use_master=True):
+ for sentinel in self.sentinels:
+ try:
+ slaves = sentinel.sentinel_slaves(service_name)
+ except (ConnectionError, ResponseError):
+ continue
+ slave = self.choose_slave(slaves)
+ if slave is not None:
+ return slave['ip'], slave['port']
+ if use_master:
+ try:
+ return self.discover_master(service_name)
+ except MasterNotFoundError:
+ pass
+ raise SlaveNotFoundError("No slave found for %r" % (service_name,))
+
+ def master_for(self, service_name, redis_class=StrictRedis, **kwargs):
+ kwargs['is_master'] = True
+ connection_pool = SentinelConnectionPool(service_name, self, **kwargs)
+ return redis_class(connection_pool=connection_pool)
+
+ def slave_for(self, service_name, redis_class=StrictRedis, **kwargs):
+ kwargs['is_master'] = False
+ connection_pool = SentinelConnectionPool(service_name, self, **kwargs)
+ return redis_class(connection_pool=connection_pool)