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/redis_std.py
ADDED
|
@@ -0,0 +1,649 @@
|
|
|
1
|
+
from __future__ import print_function
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import subprocess
|
|
5
|
+
import sys
|
|
6
|
+
import time
|
|
7
|
+
import uuid
|
|
8
|
+
import platform
|
|
9
|
+
import psutil
|
|
10
|
+
import signal
|
|
11
|
+
import redis
|
|
12
|
+
|
|
13
|
+
from .random_port import get_random_port
|
|
14
|
+
from .utils import Colors, wait_for_conn, fix_modules, fix_modulesArgs
|
|
15
|
+
|
|
16
|
+
MASTER = 'master'
|
|
17
|
+
SLAVE = 'slave'
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class StandardEnv(object):
|
|
21
|
+
def __init__(self, redisBinaryPath, port=6379, modulePath=None, moduleArgs=None, outputFilesFormat=None,
|
|
22
|
+
dbDirPath=None, useSlaves=False, serverId=1, password=None, libPath=None, clusterEnabled=False, decodeResponses=False,
|
|
23
|
+
useAof=False, useRdbPreamble=True, debugger=None, sanitizer=None, noCatch=False, noLog=False, unix=False, verbose=False, useTLS=False,
|
|
24
|
+
tlsCertFile=None, tlsKeyFile=None, tlsCaCertFile=None, clusterNodeTimeout=None, tlsPassphrase=None, enableDebugCommand=False, protocol=2,
|
|
25
|
+
terminateRetries=None, terminateRetrySecs=None, enableProtectedConfigs=False, enableModuleCommand=False, loglevel=None,
|
|
26
|
+
redisConfigFile=None
|
|
27
|
+
):
|
|
28
|
+
self.uuid = uuid.uuid4().hex
|
|
29
|
+
self.redisBinaryPath = os.path.expanduser(redisBinaryPath) if redisBinaryPath.startswith(
|
|
30
|
+
'~/') else redisBinaryPath
|
|
31
|
+
|
|
32
|
+
self.modulePath = fix_modules(modulePath)
|
|
33
|
+
self.moduleArgs = fix_modulesArgs(self.modulePath, moduleArgs, haveSeqs=False)
|
|
34
|
+
self.outputFilesFormat = self.uuid + '.' + outputFilesFormat
|
|
35
|
+
self.useSlaves = useSlaves
|
|
36
|
+
self.masterServerId = serverId
|
|
37
|
+
self.password = password
|
|
38
|
+
self.clusterEnabled = clusterEnabled
|
|
39
|
+
self.decodeResponses = decodeResponses
|
|
40
|
+
self.useAof = useAof
|
|
41
|
+
self.useRdbPreamble = useRdbPreamble
|
|
42
|
+
self.envIsUp = False
|
|
43
|
+
self.debugger = debugger
|
|
44
|
+
self.sanitizer = sanitizer
|
|
45
|
+
self.noCatch = noCatch
|
|
46
|
+
self.noLog = noLog
|
|
47
|
+
self.loglevel = loglevel
|
|
48
|
+
self.environ = os.environ.copy()
|
|
49
|
+
self.useUnix = unix
|
|
50
|
+
self.dbDirPath = dbDirPath
|
|
51
|
+
self.masterProcess = None
|
|
52
|
+
self.masterStdout = None
|
|
53
|
+
self.masterStderr = None
|
|
54
|
+
self.masterExitCode = None
|
|
55
|
+
self.slaveProcess = None
|
|
56
|
+
self.slaveStdout = None
|
|
57
|
+
self.slaveStderr = None
|
|
58
|
+
self.slaveExitCode = None
|
|
59
|
+
self.verbose = verbose
|
|
60
|
+
self.role = MASTER
|
|
61
|
+
self.useTLS = useTLS
|
|
62
|
+
self.tlsCertFile = tlsCertFile
|
|
63
|
+
self.tlsKeyFile = tlsKeyFile
|
|
64
|
+
self.tlsCaCertFile = tlsCaCertFile
|
|
65
|
+
self.clusterNodeTimeout = clusterNodeTimeout
|
|
66
|
+
self.tlsPassphrase = tlsPassphrase
|
|
67
|
+
self.enableDebugCommand = enableDebugCommand
|
|
68
|
+
self.enableModuleCommand = enableModuleCommand
|
|
69
|
+
self.enableProtectedConfigs = enableProtectedConfigs
|
|
70
|
+
self.protocol = protocol
|
|
71
|
+
self.terminateRetries = terminateRetries
|
|
72
|
+
self.terminateRetrySecs = terminateRetrySecs
|
|
73
|
+
self.redisConfigFile = redisConfigFile
|
|
74
|
+
|
|
75
|
+
if port > 0:
|
|
76
|
+
self.port = port
|
|
77
|
+
self.slavePort = port + 1 if self.useSlaves else 0
|
|
78
|
+
elif port == 0:
|
|
79
|
+
self.port = get_random_port()
|
|
80
|
+
self.slavePort = get_random_port() if self.useSlaves else 0
|
|
81
|
+
else:
|
|
82
|
+
self.port = -1
|
|
83
|
+
self.slavePort = -1
|
|
84
|
+
|
|
85
|
+
if self.has_interactive_debugger and serverId > 1:
|
|
86
|
+
assert self.noCatch and not self.useSlaves and not self.clusterEnabled
|
|
87
|
+
|
|
88
|
+
if self.useUnix:
|
|
89
|
+
if self.clusterEnabled:
|
|
90
|
+
raise ValueError('Unix sockets cannot be used with cluster mode')
|
|
91
|
+
self.port = -1
|
|
92
|
+
|
|
93
|
+
if self.useTLS:
|
|
94
|
+
if self.useUnix:
|
|
95
|
+
raise ValueError('Unix sockets cannot be used with TLS enabled mode')
|
|
96
|
+
if self.tlsCertFile is None:
|
|
97
|
+
raise ValueError('When useTLS option is True tlsCertFile must be defined')
|
|
98
|
+
if os.path.isfile(self.tlsCertFile) is False:
|
|
99
|
+
raise ValueError(
|
|
100
|
+
'When useTLS option is True tlsCertFile must exist. specified path {}'.format(self.tlsCertFile))
|
|
101
|
+
if self.tlsKeyFile is None:
|
|
102
|
+
raise ValueError('When useTLS option is True tlsKeyFile must be defined')
|
|
103
|
+
if os.path.isfile(self.tlsKeyFile) is False:
|
|
104
|
+
raise ValueError(
|
|
105
|
+
'When useTLS option is True tlsKeyFile must exist. specified path {}'.format(self.tlsKeyFile))
|
|
106
|
+
if self.tlsCaCertFile is None:
|
|
107
|
+
raise ValueError('When useTLS option is True tlsCaCertFile must be defined')
|
|
108
|
+
if not os.path.isfile(self.tlsCaCertFile):
|
|
109
|
+
raise ValueError(
|
|
110
|
+
'When useTLS option is True tlsCaCertFile must exist. specified path {}'.format(self.tlsCaCertFile))
|
|
111
|
+
|
|
112
|
+
if libPath:
|
|
113
|
+
self.libPath = os.path.expanduser(libPath) if libPath.startswith('~/') else libPath
|
|
114
|
+
else:
|
|
115
|
+
self.libPath = None
|
|
116
|
+
if self.libPath:
|
|
117
|
+
if 'LD_LIBRARY_PATH' in self.environ.keys():
|
|
118
|
+
self.environ['LD_LIBRARY_PATH'] = self.libPath + ":" + self.environ['LD_LIBRARY_PATH']
|
|
119
|
+
else:
|
|
120
|
+
self.environ['LD_LIBRARY_PATH'] = self.libPath
|
|
121
|
+
|
|
122
|
+
self.masterCmdArgs = self.createCmdArgs(MASTER)
|
|
123
|
+
self.masterOSEnv = self.createCmdOSEnv(MASTER)
|
|
124
|
+
if self.useSlaves:
|
|
125
|
+
self.slaveServerId = serverId + 1
|
|
126
|
+
self.slaveCmdArgs = self.createCmdArgs(SLAVE)
|
|
127
|
+
self.slaveOSEnv = self.createCmdOSEnv(SLAVE)
|
|
128
|
+
|
|
129
|
+
self.envIsHealthy = True
|
|
130
|
+
|
|
131
|
+
def _getFileName(self, role, suffix):
|
|
132
|
+
return (self.outputFilesFormat + suffix) % (
|
|
133
|
+
'master-%d' % self.masterServerId if role == MASTER else 'slave-%d' % self.slaveServerId)
|
|
134
|
+
|
|
135
|
+
def _getValgrindFilePath(self, role):
|
|
136
|
+
return os.path.join(self.dbDirPath, self._getFileName(role, '.valgrind.log'))
|
|
137
|
+
|
|
138
|
+
def getMasterPort(self):
|
|
139
|
+
return self.port
|
|
140
|
+
|
|
141
|
+
def getPassword(self):
|
|
142
|
+
return self.password
|
|
143
|
+
|
|
144
|
+
def getUnixPath(self, role):
|
|
145
|
+
basename = '{}-{}.sock'.format(self.uuid, role)
|
|
146
|
+
return os.path.abspath(os.path.join(self.dbDirPath, basename))
|
|
147
|
+
|
|
148
|
+
def getTLSCertFile(self):
|
|
149
|
+
return os.path.abspath(self.tlsCertFile)
|
|
150
|
+
|
|
151
|
+
def getTLSKeyFile(self):
|
|
152
|
+
return os.path.abspath(self.tlsKeyFile)
|
|
153
|
+
|
|
154
|
+
def getTLSCACertFile(self):
|
|
155
|
+
return os.path.abspath(self.tlsCaCertFile)
|
|
156
|
+
|
|
157
|
+
@property
|
|
158
|
+
def has_interactive_debugger(self):
|
|
159
|
+
return self.debugger and self.debugger.is_interactive
|
|
160
|
+
|
|
161
|
+
def _getRedisVersion(self):
|
|
162
|
+
options = {
|
|
163
|
+
'stderr': subprocess.PIPE,
|
|
164
|
+
'stdin': subprocess.PIPE,
|
|
165
|
+
'stdout': subprocess.PIPE,
|
|
166
|
+
}
|
|
167
|
+
p = subprocess.Popen(args=[self.redisBinaryPath, '--version'], **options)
|
|
168
|
+
while p.poll() is None:
|
|
169
|
+
time.sleep(0.1)
|
|
170
|
+
exit_code = p.poll()
|
|
171
|
+
if exit_code != 0:
|
|
172
|
+
raise Exception('Could not extract Redis version')
|
|
173
|
+
out, err = p.communicate()
|
|
174
|
+
out = out.decode('utf-8')
|
|
175
|
+
v = out[out.find("v=") + 2:out.find("sha=") - 1].split('.')
|
|
176
|
+
return int(v[0]) * 10000 + int(v[1]) * 100 + int(v[2])
|
|
177
|
+
|
|
178
|
+
def createCmdArgs(self, role):
|
|
179
|
+
cmdArgs = []
|
|
180
|
+
if self.debugger:
|
|
181
|
+
cmdArgs += self.debugger.generate_command(self._getValgrindFilePath(role) if not self.noCatch else None)
|
|
182
|
+
|
|
183
|
+
cmdArgs += [self.redisBinaryPath]
|
|
184
|
+
|
|
185
|
+
if self.redisConfigFile:
|
|
186
|
+
cmdArgs += [self.redisConfigFile]
|
|
187
|
+
|
|
188
|
+
if self.port > -1:
|
|
189
|
+
if self.useTLS:
|
|
190
|
+
cmdArgs += ['--port', str(0), '--tls-port', str(self.getPort(role))]
|
|
191
|
+
else:
|
|
192
|
+
cmdArgs += ['--port', str(self.getPort(role))]
|
|
193
|
+
else:
|
|
194
|
+
cmdArgs += ['--port', str(0), '--unixsocket', self.getUnixPath(role)]
|
|
195
|
+
|
|
196
|
+
if self.modulePath:
|
|
197
|
+
if self.moduleArgs and len(self.modulePath) != len(self.moduleArgs):
|
|
198
|
+
print(Colors.Bred('Number of module args sets in Env does not match number of modules'))
|
|
199
|
+
print(self.modulePath)
|
|
200
|
+
print(self.moduleArgs)
|
|
201
|
+
sys.exit(1)
|
|
202
|
+
for pos, module in enumerate(self.modulePath):
|
|
203
|
+
cmdArgs += ['--loadmodule', module]
|
|
204
|
+
if self.moduleArgs:
|
|
205
|
+
module_args = self.moduleArgs[pos]
|
|
206
|
+
if module_args:
|
|
207
|
+
# make sure there are no spaces within args
|
|
208
|
+
args = []
|
|
209
|
+
for arg in module_args:
|
|
210
|
+
if arg.strip() != '':
|
|
211
|
+
args += arg.split(' ')
|
|
212
|
+
cmdArgs += args
|
|
213
|
+
|
|
214
|
+
if self.noLog:
|
|
215
|
+
cmdArgs += ['--logfile', '/dev/null']
|
|
216
|
+
elif self.outputFilesFormat is not None and not self.noCatch:
|
|
217
|
+
cmdArgs += ['--logfile', self._getFileName(role, '.log')]
|
|
218
|
+
if self.loglevel is not None:
|
|
219
|
+
cmdArgs += ['--loglevel', self.loglevel]
|
|
220
|
+
if self.outputFilesFormat is not None:
|
|
221
|
+
cmdArgs += ['--dbfilename', self._getFileName(role, '.rdb')]
|
|
222
|
+
if role == SLAVE:
|
|
223
|
+
cmdArgs += ['--slaveof', 'localhost', str(self.port)]
|
|
224
|
+
if self.password:
|
|
225
|
+
cmdArgs += ['--masterauth', self.password]
|
|
226
|
+
if self.password:
|
|
227
|
+
cmdArgs += ['--requirepass', self.password]
|
|
228
|
+
if self.clusterEnabled and role is not SLAVE:
|
|
229
|
+
# creating .cluster.conf in /tmp as lock fails on NFS
|
|
230
|
+
cmdArgs += ['--cluster-enabled', 'yes', '--cluster-config-file', '/tmp/' + self._getFileName(role, '.cluster.conf'),
|
|
231
|
+
'--cluster-node-timeout', '5000' if self.clusterNodeTimeout is None else str(self.clusterNodeTimeout)]
|
|
232
|
+
if self.useTLS:
|
|
233
|
+
cmdArgs += ['--tls-cluster', 'yes']
|
|
234
|
+
if self.useAof:
|
|
235
|
+
cmdArgs += ['--appendonly', 'yes']
|
|
236
|
+
cmdArgs += ['--appendfilename', self._getFileName(role, '.aof')]
|
|
237
|
+
if not self.useRdbPreamble:
|
|
238
|
+
cmdArgs += ['--aof-use-rdb-preamble', 'no']
|
|
239
|
+
if self.useTLS:
|
|
240
|
+
cmdArgs += ['--tls-cert-file', self.getTLSCertFile()]
|
|
241
|
+
cmdArgs += ['--tls-key-file', self.getTLSKeyFile()]
|
|
242
|
+
cmdArgs += ['--tls-ca-cert-file', self.getTLSCACertFile()]
|
|
243
|
+
if self.tlsPassphrase:
|
|
244
|
+
cmdArgs += ['--tls-key-file-pass', self.tlsPassphrase]
|
|
245
|
+
|
|
246
|
+
cmdArgs += ['--tls-replication', 'yes']
|
|
247
|
+
|
|
248
|
+
if self._getRedisVersion() > 70000:
|
|
249
|
+
if self.enableDebugCommand:
|
|
250
|
+
cmdArgs += ['--enable-debug-command', 'yes']
|
|
251
|
+
if self.enableProtectedConfigs:
|
|
252
|
+
cmdArgs += ['--enable-protected-configs', 'yes']
|
|
253
|
+
if self.enableModuleCommand:
|
|
254
|
+
cmdArgs += ['--enable-module-command', 'yes']
|
|
255
|
+
return cmdArgs
|
|
256
|
+
|
|
257
|
+
def createCmdOSEnv(self, role):
|
|
258
|
+
if self.sanitizer != 'addr' and self.sanitizer != 'address':
|
|
259
|
+
return self.environ
|
|
260
|
+
osenv = self.environ.copy()
|
|
261
|
+
san_log = self._getFileName(role, '.asan.log')
|
|
262
|
+
asan_options = osenv.get("ASAN_OPTIONS")
|
|
263
|
+
osenv["ASAN_OPTIONS"] = "{OPT}:log_path={DIR}".format(OPT=asan_options, DIR=san_log)
|
|
264
|
+
return osenv
|
|
265
|
+
|
|
266
|
+
def waitForRedisToStart(self, con, proc):
|
|
267
|
+
wait_for_conn(con, proc, retries=1000 if self.debugger else 200)
|
|
268
|
+
self._waitForAOFChild(con)
|
|
269
|
+
|
|
270
|
+
def getPid(self, role):
|
|
271
|
+
return self.masterProcess.pid if role == MASTER else self.slaveProcess.pid
|
|
272
|
+
|
|
273
|
+
def getPort(self, role):
|
|
274
|
+
return self.port if role == MASTER else self.slavePort
|
|
275
|
+
|
|
276
|
+
def getServerId(self, role):
|
|
277
|
+
return self.masterServerId if role == MASTER else self.slaveServerId
|
|
278
|
+
|
|
279
|
+
def _printEnvData(self, prefix='', role=MASTER):
|
|
280
|
+
print(Colors.Yellow(prefix + 'pid: %d' % (self.getPid(role))))
|
|
281
|
+
if self.useUnix:
|
|
282
|
+
print(Colors.Yellow(prefix + 'unix_socket_path: %s' % (self.getUnixPath(role))))
|
|
283
|
+
else:
|
|
284
|
+
print(Colors.Yellow(prefix + 'port: %d' % (self.getPort(role))))
|
|
285
|
+
print(Colors.Yellow(prefix + 'binary path: %s' % (self.redisBinaryPath)))
|
|
286
|
+
print(Colors.Yellow(prefix + 'server id: %d' % (self.getServerId(role))))
|
|
287
|
+
print(Colors.Yellow(prefix + 'using debugger: {}'.format(bool(self.debugger))))
|
|
288
|
+
if self.modulePath:
|
|
289
|
+
print(Colors.Yellow(prefix + 'module: %s' % (self.modulePath)))
|
|
290
|
+
if self.moduleArgs:
|
|
291
|
+
print(Colors.Yellow(prefix + 'module args: %s' % (self.moduleArgs)))
|
|
292
|
+
if self.outputFilesFormat:
|
|
293
|
+
print(Colors.Yellow(prefix + 'log file: %s' % (self._getFileName(role, '.log'))))
|
|
294
|
+
print(Colors.Yellow(prefix + 'db file name: %s' % self._getFileName(role, '.rdb')))
|
|
295
|
+
if self.dbDirPath:
|
|
296
|
+
print(Colors.Yellow(prefix + 'db dir path: %s' % (self.dbDirPath)))
|
|
297
|
+
if self.libPath:
|
|
298
|
+
print(Colors.Yellow(prefix + 'library path: %s' % (self.libPath)))
|
|
299
|
+
if self.useTLS:
|
|
300
|
+
print(Colors.Yellow(prefix + 'TLS Cert File: %s' % (self.getTLSCertFile())))
|
|
301
|
+
print(Colors.Yellow(prefix + 'TLS Key File: %s' % (self.getTLSKeyFile())))
|
|
302
|
+
print(Colors.Yellow(prefix + 'TLS CA Cert File: %s' % (self.getTLSCACertFile())))
|
|
303
|
+
|
|
304
|
+
def printEnvData(self, prefix=''):
|
|
305
|
+
print(Colors.Yellow(prefix + 'master:'))
|
|
306
|
+
self._printEnvData(prefix + '\t', MASTER)
|
|
307
|
+
if self.useSlaves:
|
|
308
|
+
print(Colors.Yellow(prefix + 'slave:'))
|
|
309
|
+
self._printEnvData(prefix + '\t', SLAVE)
|
|
310
|
+
|
|
311
|
+
def getInformationBeforeDispose(self):
|
|
312
|
+
res = {}
|
|
313
|
+
instances = [(MASTER, self.getConnection(), self.masterProcess)]
|
|
314
|
+
if self.useSlaves:
|
|
315
|
+
instances.append((SLAVE, self.getSlaveConnection(), self.slaveProcess))
|
|
316
|
+
for role, conn, proc in instances:
|
|
317
|
+
info = None
|
|
318
|
+
try:
|
|
319
|
+
info = conn.execute_command('info', 'everything')
|
|
320
|
+
except redis.exceptions.RedisError:
|
|
321
|
+
pass
|
|
322
|
+
res[role] = {
|
|
323
|
+
'info': info
|
|
324
|
+
}
|
|
325
|
+
return res
|
|
326
|
+
|
|
327
|
+
def getInformationAfterDispose(self):
|
|
328
|
+
res = {}
|
|
329
|
+
instances = [(MASTER, self.masterStdout, self.masterStderr)]
|
|
330
|
+
if self.useSlaves:
|
|
331
|
+
instances.append((SLAVE, self.slaveStdout, self.slaveStderr))
|
|
332
|
+
for role, stdout, stderr in instances:
|
|
333
|
+
stdoutStr = None
|
|
334
|
+
stderrStr = None
|
|
335
|
+
logs = None
|
|
336
|
+
try:
|
|
337
|
+
stdoutStr = stdout.read().decode('utf8')
|
|
338
|
+
except (NameError, AttributeError):
|
|
339
|
+
pass
|
|
340
|
+
|
|
341
|
+
try:
|
|
342
|
+
stderrStr = stderr.read().decode('utf8')
|
|
343
|
+
except (NameError, AttributeError):
|
|
344
|
+
pass
|
|
345
|
+
|
|
346
|
+
try:
|
|
347
|
+
with open(os.path.join(self.dbDirPath, self._getFileName(role, '.log'))) as f:
|
|
348
|
+
logs = f.read()
|
|
349
|
+
except os.FileNoteFoundError:
|
|
350
|
+
pass
|
|
351
|
+
|
|
352
|
+
res[role] = {
|
|
353
|
+
'stdout': stdoutStr,
|
|
354
|
+
'stderr': stderrStr,
|
|
355
|
+
'logs': logs,
|
|
356
|
+
}
|
|
357
|
+
return res
|
|
358
|
+
|
|
359
|
+
def startEnv(self, masters = True, slaves = True):
|
|
360
|
+
if self.envIsUp and self.envIsHealthy:
|
|
361
|
+
return # env is already up
|
|
362
|
+
stdoutPipe = subprocess.PIPE
|
|
363
|
+
stderrPipe = subprocess.PIPE
|
|
364
|
+
stdinPipe = subprocess.PIPE
|
|
365
|
+
if self.noCatch:
|
|
366
|
+
stdoutPipe = sys.stdout
|
|
367
|
+
stderrPipe = sys.stderr
|
|
368
|
+
|
|
369
|
+
if self.has_interactive_debugger:
|
|
370
|
+
stdinPipe = sys.stdin
|
|
371
|
+
|
|
372
|
+
options = {
|
|
373
|
+
'stderr': stderrPipe,
|
|
374
|
+
'stdin': stdinPipe,
|
|
375
|
+
'stdout': stdoutPipe,
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
if self.verbose:
|
|
379
|
+
print(Colors.Green("Redis master command: " + ' '.join(self.masterCmdArgs)))
|
|
380
|
+
if masters and self.masterProcess is None:
|
|
381
|
+
self.masterProcess = subprocess.Popen(args=self.masterCmdArgs, env=self.masterOSEnv, cwd=self.dbDirPath,
|
|
382
|
+
**options)
|
|
383
|
+
time.sleep(0.1)
|
|
384
|
+
if self._isAlive(self.masterProcess):
|
|
385
|
+
con = self.getConnection()
|
|
386
|
+
self.waitForRedisToStart(con, self.masterProcess)
|
|
387
|
+
else:
|
|
388
|
+
self.masterProcess = None
|
|
389
|
+
if self.useSlaves and slaves and self.slaveProcess is None:
|
|
390
|
+
if self.verbose:
|
|
391
|
+
print(Colors.Green("Redis slave command: " + ' '.join(self.slaveCmdArgs)))
|
|
392
|
+
self.slaveProcess = subprocess.Popen(args=self.slaveCmdArgs, env=self.slaveOSEnv, cwd=self.dbDirPath,
|
|
393
|
+
**options)
|
|
394
|
+
time.sleep(0.1)
|
|
395
|
+
if self._isAlive(self.slaveProcess):
|
|
396
|
+
con = self.getSlaveConnection()
|
|
397
|
+
self.waitForRedisToStart(con, self.slaveProcess)
|
|
398
|
+
else:
|
|
399
|
+
self.slaveProcess = None
|
|
400
|
+
|
|
401
|
+
self.envIsUp = self.masterProcess is not None or self.slaveProcess is not None
|
|
402
|
+
self.envIsHealthy = self.masterProcess is not None and (self.slaveProcess is not None if self.useSlaves else True)
|
|
403
|
+
|
|
404
|
+
# self.masterStdout = self.masterProcess.stdout if self.masterProcess else None
|
|
405
|
+
# self.masterStderr = self.masterProcess.stderr if self.masterProcess else None
|
|
406
|
+
|
|
407
|
+
# if self.slaveProcess is not None:
|
|
408
|
+
# self.slaveStdout = self.slaveProcess.stdout if self.slaveProcess else None
|
|
409
|
+
# self.slaveStderr = self.slaveProcess.stderr if self.slaveProcess else None
|
|
410
|
+
|
|
411
|
+
def _isAlive(self, process):
|
|
412
|
+
if not process:
|
|
413
|
+
return False
|
|
414
|
+
# check if child process has terminated
|
|
415
|
+
if process.poll() is None:
|
|
416
|
+
return True
|
|
417
|
+
return False
|
|
418
|
+
|
|
419
|
+
def _segfault(self, role, retries=3):
|
|
420
|
+
process = self.masterProcess if role == MASTER else self.slaveProcess
|
|
421
|
+
if not self._isAlive(process):
|
|
422
|
+
return
|
|
423
|
+
for _ in range(retries):
|
|
424
|
+
if process.poll() is None: # None returns if the processes is not finished yet, retry until redis exits
|
|
425
|
+
time.sleep(1)
|
|
426
|
+
process.send_signal(signal.SIGSEGV)
|
|
427
|
+
else:
|
|
428
|
+
return
|
|
429
|
+
print(Colors.Bred('Failed killing processes with sigsegv, forcely kill the processes.'))
|
|
430
|
+
for _ in range(retries):
|
|
431
|
+
if process.poll() is None: # None returns if the processes is not finished yet, retry until redis exits
|
|
432
|
+
time.sleep(1)
|
|
433
|
+
process.kill()
|
|
434
|
+
else:
|
|
435
|
+
return
|
|
436
|
+
print(Colors.Bred('Failed killing processes with sigkill.'))
|
|
437
|
+
|
|
438
|
+
def stopEnvWithSegFault(self, masters = True, slaves = True):
|
|
439
|
+
if self.masterProcess is not None and masters is True:
|
|
440
|
+
self._segfault(MASTER)
|
|
441
|
+
if self.useSlaves and self.slaveProcess is not None and slaves is True:
|
|
442
|
+
self._segfault(SLAVE)
|
|
443
|
+
|
|
444
|
+
def _stopProcess(self, role):
|
|
445
|
+
process = self.masterProcess if role == MASTER else self.slaveProcess
|
|
446
|
+
serverId = self.masterServerId if role == MASTER else self.slaveServerId
|
|
447
|
+
if not self._isAlive(process):
|
|
448
|
+
if not self.has_interactive_debugger:
|
|
449
|
+
# on interactive debugger its expected that then process will not be alive
|
|
450
|
+
print('\t' + Colors.Bred('process is not alive, might have crash durring test execution, '
|
|
451
|
+
'check this out. server id : %s' % str(serverId)))
|
|
452
|
+
if self.outputFilesFormat is not None and not self.noCatch:
|
|
453
|
+
self.verbose_analyse_server_log(role)
|
|
454
|
+
return
|
|
455
|
+
try:
|
|
456
|
+
if platform.system() == 'Darwin':
|
|
457
|
+
# On macOS, with lldb, killing lldb process does not terminate inferior processes
|
|
458
|
+
p0 = psutil.Process(pid=process.pid)
|
|
459
|
+
pchi = p0.children(recursive=True)
|
|
460
|
+
for p in pchi:
|
|
461
|
+
try:
|
|
462
|
+
p.terminate()
|
|
463
|
+
p.wait()
|
|
464
|
+
except:
|
|
465
|
+
pass
|
|
466
|
+
|
|
467
|
+
if self.terminateRetries is None:
|
|
468
|
+
# ask once, then wait for process to exit
|
|
469
|
+
process.terminate()
|
|
470
|
+
while True:
|
|
471
|
+
if process.poll() is None: # None returns if the processes is not finished yet, retry until redis exits
|
|
472
|
+
time.sleep(0.1)
|
|
473
|
+
else:
|
|
474
|
+
break
|
|
475
|
+
else:
|
|
476
|
+
# keep asking every few seconds until process has exited, otherwise kill
|
|
477
|
+
if self.terminateRetrySecs is None:
|
|
478
|
+
self.terminateRetrySecs = 1
|
|
479
|
+
done = False
|
|
480
|
+
for i in range(0, self.terminateRetries):
|
|
481
|
+
process.terminate()
|
|
482
|
+
if process.poll() is None: # None returns if the processes is not finished yet, retry until redis exits
|
|
483
|
+
time.sleep(self.terminateRetrySecs)
|
|
484
|
+
else:
|
|
485
|
+
done = True
|
|
486
|
+
break
|
|
487
|
+
if not done:
|
|
488
|
+
process.kill()
|
|
489
|
+
|
|
490
|
+
if role == MASTER:
|
|
491
|
+
self.masterExitCode = process.poll()
|
|
492
|
+
else:
|
|
493
|
+
self.slaveExitCode = process.poll()
|
|
494
|
+
except OSError as e:
|
|
495
|
+
print('\t' + Colors.Bred(
|
|
496
|
+
'OSError caught while waiting for {0} process to end: {1}'.format(role, e.__str__())))
|
|
497
|
+
pass
|
|
498
|
+
|
|
499
|
+
def verbose_analyse_server_log(self, role):
|
|
500
|
+
path = "{0}".format(self._getFileName(role, '.log'))
|
|
501
|
+
if self.dbDirPath is not None:
|
|
502
|
+
path = "{0}/{1}".format(self.dbDirPath, self._getFileName(role, '.log'))
|
|
503
|
+
print('\t' + Colors.Bred('check the redis log at: {0}'.format(path)))
|
|
504
|
+
print('\t' + Colors.Yellow('Printing only REDIS BUG REPORT START and STACK TRACE'))
|
|
505
|
+
with open(path) as file:
|
|
506
|
+
bug_report_found = False
|
|
507
|
+
for line in file:
|
|
508
|
+
if "REDIS BUG REPORT START" in line:
|
|
509
|
+
bug_report_found = True
|
|
510
|
+
if "------ INFO OUTPUT ------" in line:
|
|
511
|
+
break
|
|
512
|
+
if bug_report_found is True:
|
|
513
|
+
print('\t\t' + Colors.Yellow(line.rstrip()))
|
|
514
|
+
|
|
515
|
+
def stopEnv(self, masters = True, slaves = True):
|
|
516
|
+
if self.masterProcess is not None and masters is True:
|
|
517
|
+
self._stopProcess(MASTER)
|
|
518
|
+
self.masterProcess = None
|
|
519
|
+
if self.useSlaves and self.slaveProcess is not None and slaves is True:
|
|
520
|
+
self._stopProcess(SLAVE)
|
|
521
|
+
self.slaveProcess = None
|
|
522
|
+
self.envIsUp = self.masterProcess is not None or self.slaveProcess is not None
|
|
523
|
+
self.envIsHealthy = self.masterProcess is not None and (self.slaveProcess is not None if self.useSlaves else True)
|
|
524
|
+
|
|
525
|
+
def _getConnection(self, role):
|
|
526
|
+
if self.useUnix:
|
|
527
|
+
return redis.StrictRedis(unix_socket_path=self.getUnixPath(role),
|
|
528
|
+
password=self.password, decode_responses=self.decodeResponses, protocol=self.protocol)
|
|
529
|
+
elif self.useTLS:
|
|
530
|
+
return redis.StrictRedis('localhost', self.getPort(role),
|
|
531
|
+
password=self.password,
|
|
532
|
+
ssl=True,
|
|
533
|
+
ssl_password=self.tlsPassphrase,
|
|
534
|
+
ssl_keyfile=self.getTLSKeyFile(),
|
|
535
|
+
ssl_certfile=self.getTLSCertFile(),
|
|
536
|
+
ssl_cert_reqs=None,
|
|
537
|
+
ssl_ca_certs=self.getTLSCACertFile(),
|
|
538
|
+
decode_responses=self.decodeResponses,
|
|
539
|
+
protocol=self.protocol
|
|
540
|
+
)
|
|
541
|
+
else:
|
|
542
|
+
return redis.StrictRedis('localhost', self.getPort(role),
|
|
543
|
+
password=self.password, decode_responses=self.decodeResponses, protocol=self.protocol)
|
|
544
|
+
|
|
545
|
+
def getConnection(self, shardId=1):
|
|
546
|
+
return self._getConnection(MASTER)
|
|
547
|
+
|
|
548
|
+
# List containing a connection for each of the master nodes
|
|
549
|
+
def getOSSMasterNodesConnectionList(self):
|
|
550
|
+
return [self.getConnection()]
|
|
551
|
+
|
|
552
|
+
def getSlaveConnection(self):
|
|
553
|
+
if self.useSlaves:
|
|
554
|
+
return self._getConnection(SLAVE)
|
|
555
|
+
raise Exception('asked for slave connection but no slave exists')
|
|
556
|
+
|
|
557
|
+
# List of nodes that initial bootstrapping can be done from
|
|
558
|
+
def getMasterNodesList(self):
|
|
559
|
+
node_info = {"host": None, "port": None, "unix_socket_path": None, "password": None}
|
|
560
|
+
node_info["password"] = self.password
|
|
561
|
+
if self.useUnix:
|
|
562
|
+
node_info["unix_socket_path"] = self.getUnixPath(MASTER)
|
|
563
|
+
|
|
564
|
+
else:
|
|
565
|
+
node_info["host"] = 'localhost'
|
|
566
|
+
node_info["port"] = self.getPort(MASTER)
|
|
567
|
+
return [node_info]
|
|
568
|
+
|
|
569
|
+
def getConnectionByKey(self, key, command):
|
|
570
|
+
return self.getConnection()
|
|
571
|
+
|
|
572
|
+
def flush(self):
|
|
573
|
+
self.getConnection().flushall()
|
|
574
|
+
|
|
575
|
+
def _waitForAOFChild(self, con):
|
|
576
|
+
import time
|
|
577
|
+
# Wait until file is available
|
|
578
|
+
while True:
|
|
579
|
+
info = con.info('persistence')
|
|
580
|
+
if info['aof_rewrite_scheduled'] or info['aof_rewrite_in_progress']:
|
|
581
|
+
time.sleep(0.1)
|
|
582
|
+
else:
|
|
583
|
+
break
|
|
584
|
+
|
|
585
|
+
def dumpAndReload(self, restart=False, shardId=None, timeout_sec=0):
|
|
586
|
+
conns = []
|
|
587
|
+
conns.append(self.getConnection())
|
|
588
|
+
if self.useSlaves:
|
|
589
|
+
conns.append(self.getSlaveConnection())
|
|
590
|
+
if restart:
|
|
591
|
+
for con in conns:
|
|
592
|
+
self._waitForAOFChild(con)
|
|
593
|
+
con.bgrewriteaof()
|
|
594
|
+
self._waitForAOFChild(con)
|
|
595
|
+
|
|
596
|
+
self.stopEnv()
|
|
597
|
+
self.startEnv()
|
|
598
|
+
else:
|
|
599
|
+
[con.save() for con in conns]
|
|
600
|
+
try:
|
|
601
|
+
# Given we've already saved on the prior step, there is no need to SAVE again on the DEBUG RELOAD
|
|
602
|
+
[con.execute_command('DEBUG', 'RELOAD', 'NOSAVE') for con in conns]
|
|
603
|
+
except redis.RedisError as err:
|
|
604
|
+
raise err
|
|
605
|
+
|
|
606
|
+
def broadcast(self, *cmd):
|
|
607
|
+
try:
|
|
608
|
+
self.getConnection().execute_command(*cmd)
|
|
609
|
+
except Exception as e:
|
|
610
|
+
print(e)
|
|
611
|
+
|
|
612
|
+
def checkExitCode(self):
|
|
613
|
+
ret = True
|
|
614
|
+
|
|
615
|
+
if self.masterExitCode != 0:
|
|
616
|
+
print('\t' + Colors.Bred('bad exit code for serverId %s' % str(self.masterServerId)))
|
|
617
|
+
ret = False
|
|
618
|
+
if self.useSlaves and (self.slaveExitCode is None or self.slaveExitCode != 0):
|
|
619
|
+
print('\t' + Colors.Bred('bad exit code for serverId %s' % str(self.slaveServerId)))
|
|
620
|
+
ret = False
|
|
621
|
+
return ret
|
|
622
|
+
|
|
623
|
+
def isUp(self):
|
|
624
|
+
return self.envIsUp
|
|
625
|
+
|
|
626
|
+
def isHealthy(self):
|
|
627
|
+
return self.envIsHealthy
|
|
628
|
+
|
|
629
|
+
def isUnixSocket(self):
|
|
630
|
+
return self.useUnix
|
|
631
|
+
|
|
632
|
+
def isTcp(self):
|
|
633
|
+
return not (self.useUnix)
|
|
634
|
+
|
|
635
|
+
def isTLS(self):
|
|
636
|
+
return self.useTLS
|
|
637
|
+
|
|
638
|
+
def exists(self, val):
|
|
639
|
+
return self.getConnection().exists(val)
|
|
640
|
+
|
|
641
|
+
def hmset(self, *args):
|
|
642
|
+
return self.getConnection().hmset(*args)
|
|
643
|
+
|
|
644
|
+
def keys(self, reg):
|
|
645
|
+
return self.getConnection().keys(reg)
|
|
646
|
+
|
|
647
|
+
def setTerminateRetries(self, retries=3, seconds=1):
|
|
648
|
+
self.terminateRetries = retries
|
|
649
|
+
self.terminateRetrySecs = seconds
|