Skip to content

Commit 7c77883

Browse files
authored
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
1 parent 8cfea41 commit 7c77883

File tree

4 files changed

+96
-5
lines changed

4 files changed

+96
-5
lines changed

redis/client.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -675,10 +675,14 @@ class Redis(Commands, object):
675675
'SCRIPT FLUSH': bool_ok,
676676
'SCRIPT KILL': bool_ok,
677677
'SCRIPT LOAD': str_if_bytes,
678+
'SENTINEL CKQUORUM': bool_ok,
679+
'SENTINEL FAILOVER': bool_ok,
680+
'SENTINEL FLUSHCONFIG': bool_ok,
678681
'SENTINEL GET-MASTER-ADDR-BY-NAME': parse_sentinel_get_master,
679682
'SENTINEL MASTER': parse_sentinel_master,
680683
'SENTINEL MASTERS': parse_sentinel_masters,
681684
'SENTINEL MONITOR': bool_ok,
685+
'SENTINEL RESET': bool_ok,
682686
'SENTINEL REMOVE': bool_ok,
683687
'SENTINEL SENTINELS': parse_sentinel_slaves_and_sentinels,
684688
'SENTINEL SET': bool_ok,

redis/commands.py

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2950,7 +2950,7 @@ def execute(self):
29502950
return self.client.execute_command(*command)
29512951

29522952

2953-
class SentinalCommands:
2953+
class SentinelCommands:
29542954
"""
29552955
A class containing the commands specific to redis sentinal. This class is
29562956
to be used as a mixin.
@@ -2993,3 +2993,54 @@ def sentinel_set(self, name, option, value):
29932993
def sentinel_slaves(self, service_name):
29942994
"Returns a list of slaves for ``service_name``"
29952995
return self.execute_command('SENTINEL SLAVES', service_name)
2996+
2997+
def sentinel_reset(self, pattern):
2998+
"""
2999+
This command will reset all the masters with matching name.
3000+
The pattern argument is a glob-style pattern.
3001+
3002+
The reset process clears any previous state in a master (including a
3003+
failover in progress), and removes every slave and sentinel already
3004+
discovered and associated with the master.
3005+
"""
3006+
return self.execute_command('SENTINEL RESET', pattern, once=True)
3007+
3008+
def sentinel_failover(self, new_master_name):
3009+
"""
3010+
Force a failover as if the master was not reachable, and without
3011+
asking for agreement to other Sentinels (however a new version of the
3012+
configuration will be published so that the other Sentinels will
3013+
update their configurations).
3014+
"""
3015+
return self.execute_command('SENTINEL FAILOVER', new_master_name)
3016+
3017+
def sentinel_ckquorum(self, new_master_name):
3018+
"""
3019+
Check if the current Sentinel configuration is able to reach the
3020+
quorum needed to failover a master, and the majority needed to
3021+
authorize the failover.
3022+
3023+
This command should be used in monitoring systems to check if a
3024+
Sentinel deployment is ok.
3025+
"""
3026+
return self.execute_command('SENTINEL CKQUORUM',
3027+
new_master_name,
3028+
once=True)
3029+
3030+
def sentinel_flushconfig(self):
3031+
"""
3032+
Force Sentinel to rewrite its configuration on disk, including the
3033+
current Sentinel state.
3034+
3035+
Normally Sentinel rewrites the configuration every time something
3036+
changes in its state (in the context of the subset of the state which
3037+
is persisted on disk across restart).
3038+
However sometimes it is possible that the configuration file is lost
3039+
because of operation errors, disk failures, package upgrade scripts or
3040+
configuration managers. In those cases a way to to force Sentinel to
3041+
rewrite the configuration file is handy.
3042+
3043+
This command works even if the previous configuration file is
3044+
completely missing.
3045+
"""
3046+
return self.execute_command('SENTINEL FLUSHCONFIG')

redis/sentinel.py

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import weakref
33

44
from redis.client import Redis
5-
from redis.commands import SentinalCommands
5+
from redis.commands import SentinelCommands
66
from redis.connection import ConnectionPool, Connection
77
from redis.exceptions import (ConnectionError, ResponseError, ReadOnlyError,
88
TimeoutError)
@@ -133,7 +133,7 @@ def rotate_slaves(self):
133133
raise SlaveNotFoundError('No slave found for %r' % (self.service_name))
134134

135135

136-
class Sentinel(SentinalCommands, object):
136+
class Sentinel(SentinelCommands, object):
137137
"""
138138
Redis Sentinel cluster client
139139
@@ -179,6 +179,23 @@ def __init__(self, sentinels, min_other_sentinels=0, sentinel_kwargs=None,
179179
self.min_other_sentinels = min_other_sentinels
180180
self.connection_kwargs = connection_kwargs
181181

182+
def execute_command(self, *args, **kwargs):
183+
"""
184+
Execute Sentinel command in sentinel nodes.
185+
once - If set to True, then execute the resulting command on a single
186+
node at random, rather than across the entire sentinel cluster.
187+
"""
188+
once = bool(kwargs.get('once', False))
189+
if 'once' in kwargs.keys():
190+
kwargs.pop('once')
191+
192+
if once:
193+
for sentinel in self.sentinels:
194+
sentinel.execute_command(*args, **kwargs)
195+
else:
196+
random.choice(self.sentinels).execute_command(*args, **kwargs)
197+
return True
198+
182199
def __repr__(self):
183200
sentinel_addresses = []
184201
for sentinel in self.sentinels:

tests/test_sentinel.py

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,15 @@ def sentinel_slaves(self, master_name):
3030
return []
3131
return self.cluster.slaves
3232

33+
def execute_command(self, *args, **kwargs):
34+
# wrapper purely to validate the calls don't explode
35+
from redis.client import bool_ok
36+
return bool_ok
37+
3338

3439
class SentinelTestCluster:
35-
def __init__(self, service_name='mymaster', ip='127.0.0.1', port=6379):
40+
def __init__(self, servisentinel_ce_name='mymaster', ip='127.0.0.1',
41+
port=6379):
3642
self.clients = {}
3743
self.master = {
3844
'ip': ip,
@@ -42,7 +48,7 @@ def __init__(self, service_name='mymaster', ip='127.0.0.1', port=6379):
4248
'is_odown': False,
4349
'num-other-sentinels': 0,
4450
}
45-
self.service_name = service_name
51+
self.service_name = servisentinel_ce_name
4652
self.slaves = []
4753
self.nodes_down = set()
4854
self.nodes_timeout = set()
@@ -198,3 +204,16 @@ def test_slave_round_robin(cluster, sentinel, master_ip):
198204
assert next(rotator) == (master_ip, 6379)
199205
with pytest.raises(SlaveNotFoundError):
200206
next(rotator)
207+
208+
209+
def test_ckquorum(cluster, sentinel):
210+
assert sentinel.sentinel_ckquorum("mymaster")
211+
212+
213+
def test_flushconfig(cluster, sentinel):
214+
assert sentinel.sentinel_flushconfig()
215+
216+
217+
def test_reset(cluster, sentinel):
218+
cluster.master['is_odown'] = True
219+
assert sentinel.sentinel_reset('mymaster')

0 commit comments

Comments
 (0)