RLTest 0.7.14__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- RLTest/Enterprise/CcsMock.py +183 -0
- RLTest/Enterprise/Dmc.py +47 -0
- RLTest/Enterprise/EnterpriseClusterEnv.py +158 -0
- RLTest/Enterprise/__init__.py +5 -0
- RLTest/Enterprise/binaryrepo.py +71 -0
- RLTest/__init__.py +10 -0
- RLTest/__main__.py +1026 -0
- RLTest/_version.py +8 -0
- RLTest/debuggers.py +81 -0
- RLTest/env.py +646 -0
- RLTest/exists_redis.py +122 -0
- RLTest/loader.py +174 -0
- RLTest/random_port.py +63 -0
- RLTest/redis_cluster.py +241 -0
- RLTest/redis_enterprise_cluster.py +127 -0
- RLTest/redis_std.py +649 -0
- RLTest/utils.py +181 -0
- rltest-0.7.14.dist-info/LICENSE +29 -0
- rltest-0.7.14.dist-info/METADATA +296 -0
- rltest-0.7.14.dist-info/RECORD +22 -0
- rltest-0.7.14.dist-info/WHEEL +4 -0
- rltest-0.7.14.dist-info/entry_points.txt +3 -0
RLTest/exists_redis.py
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
|
|
2
|
+
from __future__ import print_function
|
|
3
|
+
import redis
|
|
4
|
+
import subprocess
|
|
5
|
+
import sys
|
|
6
|
+
import os
|
|
7
|
+
import platform
|
|
8
|
+
from .utils import Colors, wait_for_conn
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
MASTER = 1
|
|
12
|
+
SLAVE = 2
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ExistsRedisEnv(object):
|
|
16
|
+
def __init__(self, addr='localhost:6379', password = None, **kwargs):
|
|
17
|
+
self.host, self.port = addr.split(':')
|
|
18
|
+
self.port = int(self.port)
|
|
19
|
+
self.password = password
|
|
20
|
+
self.useTLS = kwargs['useTLS']
|
|
21
|
+
self.decodeResponses = kwargs.get('decodeResponses', False)
|
|
22
|
+
|
|
23
|
+
@property
|
|
24
|
+
def has_interactive_debugger(self):
|
|
25
|
+
return False
|
|
26
|
+
|
|
27
|
+
def _printEnvData(self, prefix='', role=MASTER):
|
|
28
|
+
print(Colors.Yellow(prefix + 'addr: %s:%d' % (self.host, self.port)))
|
|
29
|
+
|
|
30
|
+
def printEnvData(self, prefix=''):
|
|
31
|
+
print(Colors.Yellow(prefix + 'master:'))
|
|
32
|
+
self._printEnvData(prefix + '\t', MASTER)
|
|
33
|
+
|
|
34
|
+
def startEnv(self, masters = True, slaves = True):
|
|
35
|
+
if not self.isUp():
|
|
36
|
+
raise Exception('env is not up')
|
|
37
|
+
|
|
38
|
+
def stopEnv(self, masters = True, slaves = True):
|
|
39
|
+
pass
|
|
40
|
+
|
|
41
|
+
def getConnection(self, shardId=1):
|
|
42
|
+
return redis.StrictRedis(self.host, self.port, password=self.password, decode_responses=self.decodeResponses)
|
|
43
|
+
|
|
44
|
+
def getSlaveConnection(self):
|
|
45
|
+
raise Exception('asked for slave connection but no slave exists')
|
|
46
|
+
|
|
47
|
+
# List of nodes that initial bootstrapping can be done from
|
|
48
|
+
def getMasterNodesList(self):
|
|
49
|
+
node_info = {"host": None, "port": None, "unix_socket_path": None, "password": None}
|
|
50
|
+
node_info["password"] = self.password
|
|
51
|
+
node_info["host"] = self.host
|
|
52
|
+
node_info["port"] = self.port
|
|
53
|
+
return [node_info]
|
|
54
|
+
|
|
55
|
+
# List containing a connection for each of the master nodes
|
|
56
|
+
def getOSSMasterNodesConnectionList(self):
|
|
57
|
+
return [self.getConnection()]
|
|
58
|
+
|
|
59
|
+
def getConnectionByKey(self, key, command):
|
|
60
|
+
return self.getConnection()
|
|
61
|
+
|
|
62
|
+
def _waitForBgsaveToFinish(self):
|
|
63
|
+
# on new Redis version (6.2 and above)
|
|
64
|
+
# flush trigger background rdb save
|
|
65
|
+
# waiting for rdbsave to finish
|
|
66
|
+
while True:
|
|
67
|
+
if not self.getConnection().execute_command('info', 'Persistence')['rdb_bgsave_in_progress']:
|
|
68
|
+
break
|
|
69
|
+
|
|
70
|
+
def flush(self):
|
|
71
|
+
self.getConnection().flushall()
|
|
72
|
+
self._waitForBgsaveToFinish()
|
|
73
|
+
|
|
74
|
+
def _waitForChild(self, conns):
|
|
75
|
+
import time
|
|
76
|
+
# Wait until file is available
|
|
77
|
+
for con in conns:
|
|
78
|
+
while True:
|
|
79
|
+
info = con.info('persistence')
|
|
80
|
+
if info['aof_rewrite_scheduled'] or info['aof_rewrite_in_progress']:
|
|
81
|
+
time.sleep(0.1)
|
|
82
|
+
else:
|
|
83
|
+
break
|
|
84
|
+
|
|
85
|
+
def dumpAndReload(self, restart=False, shardId=1, timeout_sec=0):
|
|
86
|
+
self._waitForBgsaveToFinish()
|
|
87
|
+
conn = self.getConnection()
|
|
88
|
+
conn.save()
|
|
89
|
+
try:
|
|
90
|
+
conn.execute_command('DEBUG', 'RELOAD')
|
|
91
|
+
except redis.RedisError as err:
|
|
92
|
+
raise err
|
|
93
|
+
|
|
94
|
+
def broadcast(self, *cmd):
|
|
95
|
+
try:
|
|
96
|
+
self.getConnection().execute_command(*cmd)
|
|
97
|
+
except Exception as e:
|
|
98
|
+
print(e)
|
|
99
|
+
|
|
100
|
+
def checkExitCode(self):
|
|
101
|
+
return True
|
|
102
|
+
|
|
103
|
+
def isUp(self):
|
|
104
|
+
return self.getConnection().ping()
|
|
105
|
+
|
|
106
|
+
def isTLS(self):
|
|
107
|
+
return self.useTLS
|
|
108
|
+
|
|
109
|
+
def exists(self, val):
|
|
110
|
+
return self.getConnection().exists(val)
|
|
111
|
+
|
|
112
|
+
def hmset(self, *args):
|
|
113
|
+
return self.getConnection().hmset(*args)
|
|
114
|
+
|
|
115
|
+
def keys(self, reg):
|
|
116
|
+
return self.getConnection().keys(reg)
|
|
117
|
+
|
|
118
|
+
def isUnixSocket(self):
|
|
119
|
+
return False
|
|
120
|
+
|
|
121
|
+
def isTcp(self):
|
|
122
|
+
return True
|
RLTest/loader.py
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
from __future__ import print_function
|
|
2
|
+
import os
|
|
3
|
+
import sys
|
|
4
|
+
import importlib.util
|
|
5
|
+
import inspect
|
|
6
|
+
from RLTest.utils import Colors
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class TestFunction(object):
|
|
10
|
+
is_class = False
|
|
11
|
+
|
|
12
|
+
def __init__(self, filename, symbol, modulename):
|
|
13
|
+
self.filename = filename
|
|
14
|
+
self.symbol = symbol
|
|
15
|
+
self.modulename = modulename
|
|
16
|
+
self.is_method = False
|
|
17
|
+
self.name = '{}:{}'.format(self.modulename, symbol)
|
|
18
|
+
|
|
19
|
+
def initialize(self):
|
|
20
|
+
module_spec = importlib.util.spec_from_file_location(self.modulename, self.filename)
|
|
21
|
+
module = importlib.util.module_from_spec(module_spec)
|
|
22
|
+
sys.modules[self.modulename] = module
|
|
23
|
+
module_spec.loader.exec_module(module)
|
|
24
|
+
obj = getattr(module, self.symbol)
|
|
25
|
+
self.target = obj
|
|
26
|
+
|
|
27
|
+
def shortname(self):
|
|
28
|
+
return self.target.__name__
|
|
29
|
+
|
|
30
|
+
class TestMethod(object):
|
|
31
|
+
is_class = False
|
|
32
|
+
|
|
33
|
+
def __init__(self, obj, name):
|
|
34
|
+
self.target = obj
|
|
35
|
+
self.name = name
|
|
36
|
+
self.is_method = True
|
|
37
|
+
|
|
38
|
+
def initialize(self):
|
|
39
|
+
pass
|
|
40
|
+
|
|
41
|
+
def shortname(self):
|
|
42
|
+
return self.target.__name__
|
|
43
|
+
|
|
44
|
+
class TestClass(object):
|
|
45
|
+
is_class = True
|
|
46
|
+
|
|
47
|
+
def __init__(self, filename, symbol, modulename, functions):
|
|
48
|
+
self.filename = filename
|
|
49
|
+
self.symbol = symbol
|
|
50
|
+
self.modulename = modulename
|
|
51
|
+
self.functions = functions
|
|
52
|
+
self.name = '{}:{}'.format(self.modulename, symbol)
|
|
53
|
+
|
|
54
|
+
def initialize(self):
|
|
55
|
+
module_spec = importlib.util.spec_from_file_location(self.modulename, self.filename)
|
|
56
|
+
module = importlib.util.module_from_spec(module_spec)
|
|
57
|
+
sys.modules[self.modulename] = module
|
|
58
|
+
module_spec.loader.exec_module(module)
|
|
59
|
+
obj = getattr(module, self.symbol)
|
|
60
|
+
self.clsname = obj.__name__
|
|
61
|
+
self.cls = obj
|
|
62
|
+
|
|
63
|
+
def create_instance(self, *args, **kwargs):
|
|
64
|
+
return self.cls(*args, **kwargs)
|
|
65
|
+
|
|
66
|
+
def get_functions(self, instance):
|
|
67
|
+
fns = []
|
|
68
|
+
for mname in self.functions:
|
|
69
|
+
bound = getattr(instance, mname)
|
|
70
|
+
if not callable(bound):
|
|
71
|
+
continue
|
|
72
|
+
fns.append(TestMethod(bound,
|
|
73
|
+
name='{}:{}.{}'.format(self.modulename, self.clsname, mname)))
|
|
74
|
+
return fns
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class TestLoader(object):
|
|
78
|
+
def __init__(self):
|
|
79
|
+
self.tests = []
|
|
80
|
+
|
|
81
|
+
def load_spec(self, arg):
|
|
82
|
+
# if arg is a list, load its elements
|
|
83
|
+
if isinstance(arg, list):
|
|
84
|
+
for spec in arg:
|
|
85
|
+
self.load_spec(spec)
|
|
86
|
+
return
|
|
87
|
+
|
|
88
|
+
# See what kind of spec this is!
|
|
89
|
+
"""
|
|
90
|
+
Load tests from single argument form, e.g. foo.py:BarBaz
|
|
91
|
+
"""
|
|
92
|
+
if ':' in arg:
|
|
93
|
+
filename, varname = arg.split(':')
|
|
94
|
+
else:
|
|
95
|
+
filename = arg
|
|
96
|
+
varname = None
|
|
97
|
+
if os.path.isdir(filename):
|
|
98
|
+
sys.path.append(filename)
|
|
99
|
+
self.scan_dir(filename)
|
|
100
|
+
return
|
|
101
|
+
|
|
102
|
+
# Ensure the path is in sys.path
|
|
103
|
+
dirname = os.path.abspath(os.path.dirname(filename))
|
|
104
|
+
if dirname not in sys.path:
|
|
105
|
+
sys.path.append(dirname)
|
|
106
|
+
|
|
107
|
+
module_name, _ = os.path.splitext(os.path.basename(filename))
|
|
108
|
+
toplevel_filter, subfilter = None, None
|
|
109
|
+
if varname:
|
|
110
|
+
if '.' in varname:
|
|
111
|
+
toplevel_filter, subfilter = varname.split('.')
|
|
112
|
+
else:
|
|
113
|
+
toplevel_filter = varname
|
|
114
|
+
|
|
115
|
+
self.load_files(dirname, module_name, toplevel_filter, subfilter)
|
|
116
|
+
|
|
117
|
+
def load_files(self, module_dir, module_name, toplevel_filter=None, subfilter=None):
|
|
118
|
+
filename = '%s/%s.py' % (module_dir, module_name)
|
|
119
|
+
try:
|
|
120
|
+
module_spec = importlib.util.spec_from_file_location(module_name, filename)
|
|
121
|
+
module = importlib.util.module_from_spec(module_spec)
|
|
122
|
+
sys.modules[module_name] = module
|
|
123
|
+
module_spec.loader.exec_module(module)
|
|
124
|
+
for symbol in dir(module):
|
|
125
|
+
if not self.filter_modulevar(symbol, toplevel_filter):
|
|
126
|
+
continue
|
|
127
|
+
|
|
128
|
+
obj = getattr(module, symbol)
|
|
129
|
+
if inspect.isclass(obj):
|
|
130
|
+
methnames = [mname for mname in dir(obj)
|
|
131
|
+
if self.filter_method(mname, subfilter)]
|
|
132
|
+
self.tests.append(TestClass(filename, symbol, module_name, methnames))
|
|
133
|
+
elif inspect.isfunction(obj):
|
|
134
|
+
self.tests.append(TestFunction(filename, symbol, module_name))
|
|
135
|
+
except OSError as e:
|
|
136
|
+
print(Colors.Red("Can't access file %s." % filename))
|
|
137
|
+
raise e
|
|
138
|
+
except Exception as e:
|
|
139
|
+
print(Colors.Red("Problems in file %s: %s" % (filename, e)))
|
|
140
|
+
raise e
|
|
141
|
+
|
|
142
|
+
def scan_dir(self, testdir):
|
|
143
|
+
for filename in os.listdir(testdir):
|
|
144
|
+
if filename.startswith('test') and filename.endswith('.py'):
|
|
145
|
+
module_name, ext = os.path.splitext(filename)
|
|
146
|
+
self.load_files(testdir, module_name)
|
|
147
|
+
|
|
148
|
+
def filter_modulevar(self, candidate, toplevel_filter):
|
|
149
|
+
if not candidate.lower().startswith('test'):
|
|
150
|
+
return False
|
|
151
|
+
if toplevel_filter and candidate != toplevel_filter:
|
|
152
|
+
return False
|
|
153
|
+
return True
|
|
154
|
+
|
|
155
|
+
def filter_method(self, candidate, subfilter):
|
|
156
|
+
if not candidate.lower().startswith('test'):
|
|
157
|
+
return False
|
|
158
|
+
if subfilter and candidate != subfilter:
|
|
159
|
+
return False
|
|
160
|
+
|
|
161
|
+
return True
|
|
162
|
+
|
|
163
|
+
def __iter__(self):
|
|
164
|
+
return iter(self.tests)
|
|
165
|
+
|
|
166
|
+
def print_tests(self):
|
|
167
|
+
tests = []
|
|
168
|
+
for t in self.tests:
|
|
169
|
+
if t.is_class:
|
|
170
|
+
for m in t.functions:
|
|
171
|
+
tests.append(f"{t.name}.{m}")
|
|
172
|
+
else:
|
|
173
|
+
tests.append(t.name)
|
|
174
|
+
print(*sorted(tests), sep='\n')
|
RLTest/random_port.py
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import random
|
|
2
|
+
import socket
|
|
3
|
+
import struct
|
|
4
|
+
import fcntl
|
|
5
|
+
import os
|
|
6
|
+
import errno
|
|
7
|
+
import json
|
|
8
|
+
|
|
9
|
+
def _check_alive(pid):
|
|
10
|
+
try:
|
|
11
|
+
os.kill(pid, 0)
|
|
12
|
+
return True
|
|
13
|
+
except OSError as e:
|
|
14
|
+
if e.errno == errno.EPERM:
|
|
15
|
+
return True
|
|
16
|
+
return False
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def register_port(port):
|
|
20
|
+
fp = open('/tmp/rltest_portfile.lock', 'a+')
|
|
21
|
+
fcntl.flock(fp.fileno(), fcntl.LOCK_EX)
|
|
22
|
+
fp.seek(0, 2) # seek from end
|
|
23
|
+
if fp.tell() == 0:
|
|
24
|
+
entries = {}
|
|
25
|
+
else:
|
|
26
|
+
fp.seek(0, 0)
|
|
27
|
+
entries = json.load(fp)
|
|
28
|
+
# remove not responsive processes
|
|
29
|
+
entries = { p:pid for p, pid in entries.items() if _check_alive(pid) }
|
|
30
|
+
|
|
31
|
+
if str(port) in entries:
|
|
32
|
+
ret = False
|
|
33
|
+
else:
|
|
34
|
+
entries[str(port)] = os.getpid()
|
|
35
|
+
ret = True
|
|
36
|
+
|
|
37
|
+
fp.seek(0, 0)
|
|
38
|
+
fp.truncate()
|
|
39
|
+
json.dump(entries, fp)
|
|
40
|
+
fp.close()
|
|
41
|
+
return ret
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def get_random_port():
|
|
45
|
+
for _ in range(10000):
|
|
46
|
+
p = random.randint(10000, 20000)
|
|
47
|
+
# Try to open and bind the socket
|
|
48
|
+
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP)
|
|
49
|
+
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
50
|
+
s.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER, struct.pack('ii', 1, 0))
|
|
51
|
+
try:
|
|
52
|
+
s.bind(('', p))
|
|
53
|
+
s.close()
|
|
54
|
+
if not register_port(p):
|
|
55
|
+
continue
|
|
56
|
+
return p
|
|
57
|
+
except Exception as e:
|
|
58
|
+
if hasattr(e, 'errno') and e.errno in (errno.EADDRINUSE, errno.EADDRNOTAVAIL):
|
|
59
|
+
pass
|
|
60
|
+
else:
|
|
61
|
+
raise e
|
|
62
|
+
|
|
63
|
+
raise Exception('Could not find open port to listen on!')
|
RLTest/redis_cluster.py
ADDED
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
from __future__ import print_function
|
|
2
|
+
|
|
3
|
+
from .redis_std import StandardEnv
|
|
4
|
+
from redis.cluster import ClusterNode
|
|
5
|
+
import redis
|
|
6
|
+
import time
|
|
7
|
+
from RLTest.utils import Colors
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ClusterEnv(object):
|
|
11
|
+
def __init__(self, **kwargs):
|
|
12
|
+
self.shards = []
|
|
13
|
+
self.envIsUp = False
|
|
14
|
+
self.envIsHealthy = False
|
|
15
|
+
self.modulePath = kwargs['modulePath']
|
|
16
|
+
self.moduleArgs = kwargs['moduleArgs']
|
|
17
|
+
self.password = kwargs['password']
|
|
18
|
+
self.shardsCount = kwargs.pop('shardsCount')
|
|
19
|
+
useSlaves = kwargs.get('useSlaves', False)
|
|
20
|
+
self.useTLS = kwargs['useTLS']
|
|
21
|
+
self.decodeResponses = kwargs.get('decodeResponses', False)
|
|
22
|
+
self.tlsPassphrase = kwargs.get('tlsPassphrase', None)
|
|
23
|
+
self.protocol = kwargs.get('protocol', 2)
|
|
24
|
+
self.terminateRetries = kwargs.get('terminateRetries', None)
|
|
25
|
+
self.terminateRetrySecs = kwargs.get('terminateRetrySecs', None)
|
|
26
|
+
startPort = kwargs.pop('port', 10000)
|
|
27
|
+
totalRedises = self.shardsCount * (2 if useSlaves else 1)
|
|
28
|
+
randomizePorts = kwargs.pop('randomizePorts', False)
|
|
29
|
+
for i in range(0, totalRedises, (2 if useSlaves else 1)):
|
|
30
|
+
port = 0 if randomizePorts else startPort
|
|
31
|
+
shard = StandardEnv(port=port, serverId=(i + 1),
|
|
32
|
+
clusterEnabled=True, **kwargs)
|
|
33
|
+
self.shards.append(shard)
|
|
34
|
+
startPort += 2
|
|
35
|
+
|
|
36
|
+
def printEnvData(self, prefix=''):
|
|
37
|
+
print(Colors.Yellow(prefix + 'info:'))
|
|
38
|
+
print(Colors.Yellow(prefix + '\tshards count:%d' % len(self.shards)))
|
|
39
|
+
if self.modulePath:
|
|
40
|
+
print(Colors.Yellow(prefix + '\tzip module path:%s' % self.modulePath))
|
|
41
|
+
if self.moduleArgs:
|
|
42
|
+
print(Colors.Yellow(prefix + '\tmodule args:%s' % self.moduleArgs))
|
|
43
|
+
for i, shard in enumerate(self.shards):
|
|
44
|
+
print(Colors.Yellow(prefix + 'shard: %d' % (i + 1)))
|
|
45
|
+
shard.printEnvData(prefix + '\t')
|
|
46
|
+
|
|
47
|
+
def getInformationBeforeDispose(self):
|
|
48
|
+
return [shard.getInformationBeforeDispose() for shard in self.shards]
|
|
49
|
+
|
|
50
|
+
def getInformationAfterDispose(self):
|
|
51
|
+
return [shard.getInformationAfterDispose() for shard in self.shards]
|
|
52
|
+
|
|
53
|
+
def waitCluster(self, timeout_sec=40):
|
|
54
|
+
st = time.time()
|
|
55
|
+
ok = 0
|
|
56
|
+
|
|
57
|
+
while st + timeout_sec > time.time():
|
|
58
|
+
ok = 0
|
|
59
|
+
for shard in self.shards:
|
|
60
|
+
con = shard.getConnection()
|
|
61
|
+
try:
|
|
62
|
+
status = con.execute_command('CLUSTER', 'INFO')
|
|
63
|
+
except Exception as e:
|
|
64
|
+
print('got error on cluster info, will try again, %s' % str(e))
|
|
65
|
+
continue
|
|
66
|
+
if 'cluster_state:ok' in str(status):
|
|
67
|
+
ok += 1
|
|
68
|
+
if ok == len(self.shards):
|
|
69
|
+
for shard in self.shards:
|
|
70
|
+
try:
|
|
71
|
+
shard.getConnection().execute_command('FT.CLUSTERREFRESH')
|
|
72
|
+
except Exception:
|
|
73
|
+
pass
|
|
74
|
+
try:
|
|
75
|
+
shard.getConnection().execute_command('SEARCH.CLUSTERREFRESH')
|
|
76
|
+
except Exception:
|
|
77
|
+
pass
|
|
78
|
+
return
|
|
79
|
+
|
|
80
|
+
time.sleep(0.1)
|
|
81
|
+
raise RuntimeError(
|
|
82
|
+
"Cluster OK wait loop timed out after %s seconds" % timeout_sec)
|
|
83
|
+
|
|
84
|
+
def startEnv(self, masters=True, slaves=True):
|
|
85
|
+
if self.envIsUp == True:
|
|
86
|
+
return # env is already up
|
|
87
|
+
try:
|
|
88
|
+
for shard in self.shards:
|
|
89
|
+
shard.startEnv(masters, slaves)
|
|
90
|
+
except Exception:
|
|
91
|
+
for shard in self.shards:
|
|
92
|
+
shard.stopEnv()
|
|
93
|
+
raise
|
|
94
|
+
|
|
95
|
+
slots_per_node = int(16384 / len(self.shards)) + 1
|
|
96
|
+
for i, shard in enumerate(self.shards):
|
|
97
|
+
con = shard.getConnection()
|
|
98
|
+
for s in self.shards:
|
|
99
|
+
con.execute_command('CLUSTER', 'MEET',
|
|
100
|
+
'127.0.0.1', s.getMasterPort())
|
|
101
|
+
|
|
102
|
+
start_slot = i * slots_per_node
|
|
103
|
+
end_slot = start_slot + slots_per_node
|
|
104
|
+
if end_slot > 16384:
|
|
105
|
+
end_slot = 16384
|
|
106
|
+
|
|
107
|
+
try:
|
|
108
|
+
con.execute_command('CLUSTER', 'ADDSLOTS', *(str(x)
|
|
109
|
+
for x in range(start_slot, end_slot)))
|
|
110
|
+
except Exception:
|
|
111
|
+
pass
|
|
112
|
+
|
|
113
|
+
self.waitCluster()
|
|
114
|
+
self.envIsUp = True
|
|
115
|
+
self.envIsHealthy = True
|
|
116
|
+
|
|
117
|
+
def stopEnvWithSegFault(self, masters=True, slaves=True):
|
|
118
|
+
for shard in self.shards:
|
|
119
|
+
shard.stopEnvWithSegFault(masters, slaves)
|
|
120
|
+
|
|
121
|
+
def stopEnv(self, masters=True, slaves=True):
|
|
122
|
+
self.envIsUp = False
|
|
123
|
+
self.envIsHealthy = False
|
|
124
|
+
for shard in self.shards:
|
|
125
|
+
shard.stopEnv(masters, slaves)
|
|
126
|
+
self.envIsUp = self.envIsUp or shard.envIsUp
|
|
127
|
+
self.envIsHealthy = self.envIsHealthy and shard.envIsUp
|
|
128
|
+
|
|
129
|
+
def getConnection(self, shardId=1):
|
|
130
|
+
return self.shards[shardId - 1].getConnection()
|
|
131
|
+
|
|
132
|
+
def getClusterConnection(self):
|
|
133
|
+
statupNode = [ClusterNode(a['host'], a['port']) for a in self.getMasterNodesList()]
|
|
134
|
+
if self.useTLS:
|
|
135
|
+
return redis.RedisCluster(
|
|
136
|
+
ssl=True,
|
|
137
|
+
ssl_keyfile=self.shards[0].getTLSKeyFile(),
|
|
138
|
+
ssl_certfile=self.shards[0].getTLSCertFile(),
|
|
139
|
+
ssl_cert_reqs=None,
|
|
140
|
+
ssl_ca_certs=self.shards[0].getTLSCACertFile(),
|
|
141
|
+
ssl_password=self.tlsPassphrase,
|
|
142
|
+
password=self.password,
|
|
143
|
+
startup_nodes=statupNode,
|
|
144
|
+
decode_responses=self.decodeResponses,
|
|
145
|
+
protocol=self.protocol,
|
|
146
|
+
terminateRetries=self.terminateRetries, terminateRetrySecs=self.terminateRetrySecs
|
|
147
|
+
)
|
|
148
|
+
else:
|
|
149
|
+
return redis.RedisCluster(
|
|
150
|
+
startup_nodes=statupNode,
|
|
151
|
+
decode_responses=self.decodeResponses, password=self.password,
|
|
152
|
+
protocol=self.protocol,
|
|
153
|
+
terminateRetries=self.terminateRetries, terminateRetrySecs=self.terminateRetrySecs)
|
|
154
|
+
|
|
155
|
+
def getSlaveConnection(self):
|
|
156
|
+
raise Exception('unsupported')
|
|
157
|
+
|
|
158
|
+
# List of nodes that initial bootstrapping can be done from
|
|
159
|
+
def getMasterNodesList(self):
|
|
160
|
+
full_master_list = []
|
|
161
|
+
for shard in self.shards:
|
|
162
|
+
node_info_list = shard.getMasterNodesList()
|
|
163
|
+
full_master_list.append(node_info_list[0])
|
|
164
|
+
return full_master_list
|
|
165
|
+
|
|
166
|
+
# List containing a connection for each of the master nodes
|
|
167
|
+
def getOSSMasterNodesConnectionList(self):
|
|
168
|
+
full_master_connection_list = []
|
|
169
|
+
for shard in self.shards:
|
|
170
|
+
full_master_connection_list.append(shard.getConnection())
|
|
171
|
+
return full_master_connection_list
|
|
172
|
+
|
|
173
|
+
# Gets a cluster connection by key. On std redis the default connection is returned.
|
|
174
|
+
def getConnectionByKey(self, key, command):
|
|
175
|
+
clusterConn = self.getClusterConnection()
|
|
176
|
+
target_node = clusterConn._determine_nodes(command, key) # we will always which will give us the node responsible for the key
|
|
177
|
+
return clusterConn.get_redis_connection(target_node[0])
|
|
178
|
+
|
|
179
|
+
def addShardToCluster(self, redisBinaryPath, output_files_format, **kwargs):
|
|
180
|
+
kwargs.pop('port')
|
|
181
|
+
port = self.shards[-1].port + 2 # use a fresh port
|
|
182
|
+
self.shardsCount += 1
|
|
183
|
+
new_shard = StandardEnv(redisBinaryPath, port, outputFilesFormat=output_files_format,
|
|
184
|
+
serverId=self.shardsCount, clusterEnabled=True, **kwargs)
|
|
185
|
+
try:
|
|
186
|
+
new_shard.startEnv()
|
|
187
|
+
except Exception:
|
|
188
|
+
new_shard.stopEnv()
|
|
189
|
+
raise
|
|
190
|
+
self.shards.append(new_shard)
|
|
191
|
+
# Notify other shards that the new shard is available and wait for the topology change to be acknowledged.
|
|
192
|
+
conn = new_shard.getConnection()
|
|
193
|
+
for s in self.shards:
|
|
194
|
+
conn.execute_command('CLUSTER', 'MEET', '127.0.0.1', s.getMasterPort())
|
|
195
|
+
self.waitCluster()
|
|
196
|
+
|
|
197
|
+
def flush(self):
|
|
198
|
+
self.getClusterConnection().flushall()
|
|
199
|
+
|
|
200
|
+
def dumpAndReload(self, restart=False, shardId=None, timeout_sec=40):
|
|
201
|
+
if shardId is None:
|
|
202
|
+
for shard in self.shards:
|
|
203
|
+
shard.dumpAndReload(restart=restart)
|
|
204
|
+
self.waitCluster(timeout_sec=timeout_sec)
|
|
205
|
+
else:
|
|
206
|
+
self.shards[shardId -
|
|
207
|
+
1].dumpAndReload(restart=restart, shardId=None, timeout_sec=timeout_sec)
|
|
208
|
+
|
|
209
|
+
def broadcast(self, *cmd):
|
|
210
|
+
for shard in self.shards:
|
|
211
|
+
shard.broadcast(*cmd)
|
|
212
|
+
|
|
213
|
+
def checkExitCode(self):
|
|
214
|
+
for shard in self.shards:
|
|
215
|
+
if not shard.checkExitCode():
|
|
216
|
+
return False
|
|
217
|
+
return True
|
|
218
|
+
|
|
219
|
+
def isUp(self):
|
|
220
|
+
return self.envIsUp or self.envIsHealthy and self.waitCluster()
|
|
221
|
+
|
|
222
|
+
def isHealthy(self):
|
|
223
|
+
return self.envIsHealthy
|
|
224
|
+
|
|
225
|
+
def isUnixSocket(self):
|
|
226
|
+
return False
|
|
227
|
+
|
|
228
|
+
def isTcp(self):
|
|
229
|
+
return True
|
|
230
|
+
|
|
231
|
+
def isTLS(self):
|
|
232
|
+
return self.useTLS
|
|
233
|
+
|
|
234
|
+
def exists(self, val):
|
|
235
|
+
return self.getClusterConnection().exists(val)
|
|
236
|
+
|
|
237
|
+
def hmset(self, *args):
|
|
238
|
+
return self.getClusterConnection().hmset(*args)
|
|
239
|
+
|
|
240
|
+
def keys(self, reg):
|
|
241
|
+
return self.getClusterConnection().keys(reg)
|