diff options
author | Chayim <chayim@users.noreply.github.com> | 2021-08-29 11:21:01 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-08-29 11:21:01 +0300 |
commit | 7c77883596e9e28c2d04298bf15ad9f947dd907f (patch) | |
tree | 1a9f6f8e5b0e07cf8856c337a6c16b0a00057b84 | |
parent | 8cfea4137851fa49c7d211b782800dd696b67c39 (diff) | |
download | redis-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-x | redis/client.py | 4 | ||||
-rw-r--r-- | redis/commands.py | 53 | ||||
-rw-r--r-- | redis/sentinel.py | 21 | ||||
-rw-r--r-- | tests/test_sentinel.py | 23 |
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') |