summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChayim <chayim@users.noreply.github.com>2021-08-29 11:21:01 +0300
committerGitHub <noreply@github.com>2021-08-29 11:21:01 +0300
commit7c77883596e9e28c2d04298bf15ad9f947dd907f (patch)
tree1a9f6f8e5b0e07cf8856c337a6c16b0a00057b84
parent8cfea4137851fa49c7d211b782800dd696b67c39 (diff)
downloadredis-py-7c77883596e9e28c2d04298bf15ad9f947dd907f.tar.gz
Merged new sentinel commands from #834 (#1550)
* Merged new sentinel commands from #835 Thanks you @otherpirate for the contribution! * Added an execute wrapper and tests. The tests ensure that the function is called. Nothing more since we do not currently have enough testing support for sentinel
-rwxr-xr-xredis/client.py4
-rw-r--r--redis/commands.py53
-rw-r--r--redis/sentinel.py21
-rw-r--r--tests/test_sentinel.py23
4 files changed, 96 insertions, 5 deletions
diff --git a/redis/client.py b/redis/client.py
index ab9246d..f6ca071 100755
--- a/redis/client.py
+++ b/redis/client.py
@@ -675,10 +675,14 @@ class Redis(Commands, object):
'SCRIPT FLUSH': bool_ok,
'SCRIPT KILL': bool_ok,
'SCRIPT LOAD': str_if_bytes,
+ 'SENTINEL CKQUORUM': bool_ok,
+ 'SENTINEL FAILOVER': bool_ok,
+ 'SENTINEL FLUSHCONFIG': bool_ok,
'SENTINEL GET-MASTER-ADDR-BY-NAME': parse_sentinel_get_master,
'SENTINEL MASTER': parse_sentinel_master,
'SENTINEL MASTERS': parse_sentinel_masters,
'SENTINEL MONITOR': bool_ok,
+ 'SENTINEL RESET': bool_ok,
'SENTINEL REMOVE': bool_ok,
'SENTINEL SENTINELS': parse_sentinel_slaves_and_sentinels,
'SENTINEL SET': bool_ok,
diff --git a/redis/commands.py b/redis/commands.py
index 003b0f1..3d5670e 100644
--- a/redis/commands.py
+++ b/redis/commands.py
@@ -2950,7 +2950,7 @@ class BitFieldOperation:
return self.client.execute_command(*command)
-class SentinalCommands:
+class SentinelCommands:
"""
A class containing the commands specific to redis sentinal. This class is
to be used as a mixin.
@@ -2993,3 +2993,54 @@ class SentinalCommands:
def sentinel_slaves(self, service_name):
"Returns a list of slaves for ``service_name``"
return self.execute_command('SENTINEL SLAVES', service_name)
+
+ def sentinel_reset(self, pattern):
+ """
+ This command will reset all the masters with matching name.
+ The pattern argument is a glob-style pattern.
+
+ The reset process clears any previous state in a master (including a
+ failover in progress), and removes every slave and sentinel already
+ discovered and associated with the master.
+ """
+ return self.execute_command('SENTINEL RESET', pattern, once=True)
+
+ def sentinel_failover(self, new_master_name):
+ """
+ Force a failover as if the master was not reachable, and without
+ asking for agreement to other Sentinels (however a new version of the
+ configuration will be published so that the other Sentinels will
+ update their configurations).
+ """
+ return self.execute_command('SENTINEL FAILOVER', new_master_name)
+
+ def sentinel_ckquorum(self, new_master_name):
+ """
+ Check if the current Sentinel configuration is able to reach the
+ quorum needed to failover a master, and the majority needed to
+ authorize the failover.
+
+ This command should be used in monitoring systems to check if a
+ Sentinel deployment is ok.
+ """
+ return self.execute_command('SENTINEL CKQUORUM',
+ new_master_name,
+ once=True)
+
+ def sentinel_flushconfig(self):
+ """
+ Force Sentinel to rewrite its configuration on disk, including the
+ current Sentinel state.
+
+ Normally Sentinel rewrites the configuration every time something
+ changes in its state (in the context of the subset of the state which
+ is persisted on disk across restart).
+ However sometimes it is possible that the configuration file is lost
+ because of operation errors, disk failures, package upgrade scripts or
+ configuration managers. In those cases a way to to force Sentinel to
+ rewrite the configuration file is handy.
+
+ This command works even if the previous configuration file is
+ completely missing.
+ """
+ return self.execute_command('SENTINEL FLUSHCONFIG')
diff --git a/redis/sentinel.py b/redis/sentinel.py
index d321348..6456b8e 100644
--- a/redis/sentinel.py
+++ b/redis/sentinel.py
@@ -2,7 +2,7 @@ import random
import weakref
from redis.client import Redis
-from redis.commands import SentinalCommands
+from redis.commands import SentinelCommands
from redis.connection import ConnectionPool, Connection
from redis.exceptions import (ConnectionError, ResponseError, ReadOnlyError,
TimeoutError)
@@ -133,7 +133,7 @@ class SentinelConnectionPool(ConnectionPool):
raise SlaveNotFoundError('No slave found for %r' % (self.service_name))
-class Sentinel(SentinalCommands, object):
+class Sentinel(SentinelCommands, object):
"""
Redis Sentinel cluster client
@@ -179,6 +179,23 @@ class Sentinel(SentinalCommands, object):
self.min_other_sentinels = min_other_sentinels
self.connection_kwargs = connection_kwargs
+ def execute_command(self, *args, **kwargs):
+ """
+ Execute Sentinel command in sentinel nodes.
+ once - If set to True, then execute the resulting command on a single
+ node at random, rather than across the entire sentinel cluster.
+ """
+ once = bool(kwargs.get('once', False))
+ if 'once' in kwargs.keys():
+ kwargs.pop('once')
+
+ if once:
+ for sentinel in self.sentinels:
+ sentinel.execute_command(*args, **kwargs)
+ else:
+ random.choice(self.sentinels).execute_command(*args, **kwargs)
+ return True
+
def __repr__(self):
sentinel_addresses = []
for sentinel in self.sentinels:
diff --git a/tests/test_sentinel.py b/tests/test_sentinel.py
index 64a7c47..54cf262 100644
--- a/tests/test_sentinel.py
+++ b/tests/test_sentinel.py
@@ -30,9 +30,15 @@ class SentinelTestClient:
return []
return self.cluster.slaves
+ def execute_command(self, *args, **kwargs):
+ # wrapper purely to validate the calls don't explode
+ from redis.client import bool_ok
+ return bool_ok
+
class SentinelTestCluster:
- def __init__(self, service_name='mymaster', ip='127.0.0.1', port=6379):
+ def __init__(self, servisentinel_ce_name='mymaster', ip='127.0.0.1',
+ port=6379):
self.clients = {}
self.master = {
'ip': ip,
@@ -42,7 +48,7 @@ class SentinelTestCluster:
'is_odown': False,
'num-other-sentinels': 0,
}
- self.service_name = service_name
+ self.service_name = servisentinel_ce_name
self.slaves = []
self.nodes_down = set()
self.nodes_timeout = set()
@@ -198,3 +204,16 @@ def test_slave_round_robin(cluster, sentinel, master_ip):
assert next(rotator) == (master_ip, 6379)
with pytest.raises(SlaveNotFoundError):
next(rotator)
+
+
+def test_ckquorum(cluster, sentinel):
+ assert sentinel.sentinel_ckquorum("mymaster")
+
+
+def test_flushconfig(cluster, sentinel):
+ assert sentinel.sentinel_flushconfig()
+
+
+def test_reset(cluster, sentinel):
+ cluster.master['is_odown'] = True
+ assert sentinel.sentinel_reset('mymaster')