]> git.proxmox.com Git - ceph.git/blob - ceph/qa/workunits/mon/test_mon_config_key.py
add subtree-ish sources for 12.0.3
[ceph.git] / ceph / qa / workunits / mon / test_mon_config_key.py
1 #!/usr/bin/python
2 #
3 # test_mon_config_key - Test 'ceph config-key' interface
4 #
5 # Copyright (C) 2013 Inktank
6 #
7 # This is free software; you can redistribute it and/or
8 # modify it under the terms of the GNU Lesser General Public
9 # License version 2.1, as published by the Free Software
10 # Foundation. See file COPYING.
11 #
12 import argparse
13 import base64
14 import errno
15 import json
16 import logging
17 import os
18 import random
19 import string
20 import subprocess
21 import sys
22 import time
23
24 #
25 # Accepted Environment variables:
26 # CEPH_TEST_VERBOSE - be more verbose; '1' enables; '0' disables
27 # CEPH_TEST_DURATION - test duration in seconds
28 # CEPH_TEST_SEED - seed to be used during the test
29 #
30 # Accepted arguments and options (see --help):
31 # -v, --verbose - be more verbose
32 # -d, --duration SECS - test duration in seconds
33 # -s, --seed SEED - seed to be used during the test
34 #
35
36
37 LOG = logging.getLogger(os.path.basename(sys.argv[0].replace('.py', '')))
38
39 SIZES = [
40 (0, 0),
41 (10, 0),
42 (25, 0),
43 (50, 0),
44 (100, 0),
45 (1000, 0),
46 (4096, 0),
47 (4097, -errno.EFBIG),
48 (8192, -errno.EFBIG)
49 ]
50
51 # tests will be randomly selected from the keys here, and the test
52 # suboperation will be randomly selected from the list in the values
53 # here. i.e. 'exists/existing' would test that a key the test put into
54 # the store earlier actually does still exist in the config store,
55 # and that's a separate test case from 'exists/enoent', which tests
56 # nonexistence of a key known to not be present.
57
58 OPS = {
59 'put': ['existing', 'new'],
60 'del': ['existing', 'enoent'],
61 'exists': ['existing', 'enoent'],
62 'get': ['existing', 'enoent'],
63 'list': ['existing', 'enoent'],
64 'dump': ['existing', 'enoent'],
65 }
66
67 CONFIG_PUT = [] # list: keys
68 CONFIG_DEL = [] # list: keys
69 CONFIG_EXISTING = {} # map: key -> size
70
71
72 def run_cmd(cmd, expects=0):
73 full_cmd = ['ceph', 'config-key'] + cmd
74
75 if expects < 0:
76 expects = -expects
77
78 cmdlog = LOG.getChild('run_cmd')
79 cmdlog.debug('{fc}'.format(fc=' '.join(full_cmd)))
80
81 proc = subprocess.Popen(full_cmd,
82 stdout=subprocess.PIPE,
83 stderr=subprocess.PIPE)
84
85 stdout = []
86 stderr = []
87 while True:
88 try:
89 out, err = proc.communicate()
90 if out is not None:
91 stdout += out.decode().split('\n')
92 cmdlog.debug('stdout: {s}'.format(s=out))
93 if err is not None:
94 stdout += err.decode().split('\n')
95 cmdlog.debug('stderr: {s}'.format(s=err))
96 except ValueError:
97 ret = proc.wait()
98 break
99
100 if ret != expects:
101 cmdlog.error('cmd > {cmd}'.format(cmd=full_cmd))
102 cmdlog.error("expected return '{expected}' got '{got}'".format(
103 expected=expects, got=ret))
104 cmdlog.error('stdout')
105 for i in stdout:
106 cmdlog.error('{x}'.format(x=i))
107 cmdlog.error('stderr')
108 for i in stderr:
109 cmdlog.error('{x}'.format(x=i))
110
111
112 # end run_cmd
113
114 def gen_data(size, rnd):
115 chars = string.ascii_letters + string.digits
116 return ''.join(rnd.choice(chars) for _ in range(size))
117
118
119 def gen_key(rnd):
120 return gen_data(20, rnd)
121
122
123 def gen_tmp_file_path(rnd):
124 file_name = gen_data(20, rnd)
125 file_path = os.path.join('/tmp', 'ceph-test.' + file_name)
126 return file_path
127
128
129 def destroy_tmp_file(fpath):
130 if os.path.exists(fpath) and os.path.isfile(fpath):
131 os.unlink(fpath)
132
133
134 def write_data_file(data, rnd):
135 file_path = gen_tmp_file_path(rnd)
136 data_file = open(file_path, 'a+')
137 data_file.truncate()
138 data_file.write(data)
139 data_file.close()
140 return file_path
141
142
143 # end write_data_file
144
145 def choose_random_op(rnd):
146 op = rnd.choice(
147 list(OPS.keys())
148 )
149 sop = rnd.choice(OPS[op])
150 return op, sop
151
152
153 def parse_args(args):
154 parser = argparse.ArgumentParser(
155 description="Test the monitor's 'config-key' API",
156 )
157 parser.add_argument(
158 '-v', '--verbose',
159 action='store_true',
160 help='be more verbose',
161 )
162 parser.add_argument(
163 '-s', '--seed',
164 metavar='SEED',
165 help='use SEED instead of generating it in run-time',
166 )
167 parser.add_argument(
168 '-d', '--duration',
169 metavar='SECS',
170 help='run test for SECS seconds (default: 300)',
171 )
172 parser.set_defaults(
173 seed=None,
174 duration=300,
175 verbose=False,
176 )
177 return parser.parse_args(args)
178
179
180 def main():
181 args = parse_args(sys.argv[1:])
182
183 verbose = args.verbose
184 if os.environ.get('CEPH_TEST_VERBOSE') is not None:
185 verbose = (os.environ.get('CEPH_TEST_VERBOSE') == '1')
186
187 duration = int(os.environ.get('CEPH_TEST_DURATION', args.duration))
188 seed = os.environ.get('CEPH_TEST_SEED', args.seed)
189 seed = int(time.time()) if seed is None else int(seed)
190
191 rnd = random.Random()
192 rnd.seed(seed)
193
194 loglevel = logging.INFO
195 if verbose:
196 loglevel = logging.DEBUG
197
198 logging.basicConfig(level=loglevel)
199
200 LOG.info('seed: {s}'.format(s=seed))
201
202 start = time.time()
203
204 while (time.time() - start) < duration:
205 (op, sop) = choose_random_op(rnd)
206
207 LOG.info('{o}({s})'.format(o=op, s=sop))
208 op_log = LOG.getChild('{o}({s})'.format(o=op, s=sop))
209
210 if op == 'put':
211 via_file = (rnd.uniform(0, 100) < 50.0)
212
213 expected = 0
214 cmd = ['put']
215 key = None
216
217 if sop == 'existing':
218 if len(CONFIG_EXISTING) == 0:
219 op_log.debug('no existing keys; continue')
220 continue
221 key = rnd.choice(CONFIG_PUT)
222 assert key in CONFIG_EXISTING, \
223 "key '{k_}' not in CONFIG_EXISTING".format(k_=key)
224
225 expected = 0 # the store just overrides the value if the key exists
226 # end if sop == 'existing'
227 elif sop == 'new':
228 for x in range(0, 10):
229 key = gen_key(rnd)
230 if key not in CONFIG_EXISTING:
231 break
232 key = None
233 if key is None:
234 op_log.error('unable to generate an unique key -- try again later.')
235 continue
236
237 assert key not in CONFIG_PUT and key not in CONFIG_EXISTING, \
238 'key {k} was not supposed to exist!'.format(k=key)
239
240 assert key is not None, \
241 'key must be != None'
242
243 cmd += [key]
244
245 (size, error) = rnd.choice(SIZES)
246 if size > 25:
247 via_file = True
248
249 data = gen_data(size, rnd)
250
251 if error == 0: # only add if we expect the put to be successful
252 if sop == 'new':
253 CONFIG_PUT.append(key)
254 CONFIG_EXISTING[key] = size
255 expected = error
256
257 if via_file:
258 data_file = write_data_file(data, rnd)
259 cmd += ['-i', data_file]
260 else:
261 cmd += [data]
262
263 op_log.debug('size: {sz}, via: {v}'.format(
264 sz=size,
265 v='file: {f}'.format(f=data_file) if via_file == True else 'cli')
266 )
267 run_cmd(cmd, expects=expected)
268 if via_file:
269 destroy_tmp_file(data_file)
270 continue
271
272 elif op == 'del':
273 expected = 0
274 cmd = ['del']
275 key = None
276
277 if sop == 'existing':
278 if len(CONFIG_EXISTING) == 0:
279 op_log.debug('no existing keys; continue')
280 continue
281 key = rnd.choice(CONFIG_PUT)
282 assert key in CONFIG_EXISTING, \
283 "key '{k_}' not in CONFIG_EXISTING".format(k_=key)
284
285 if sop == 'enoent':
286 for x in range(0, 10):
287 key = base64.b64encode(os.urandom(20)).decode()
288 if key not in CONFIG_EXISTING:
289 break
290 key = None
291 if key is None:
292 op_log.error('unable to generate an unique key -- try again later.')
293 continue
294 assert key not in CONFIG_PUT and key not in CONFIG_EXISTING, \
295 'key {k} was not supposed to exist!'.format(k=key)
296 expected = 0 # deleting a non-existent key succeeds
297
298 assert key is not None, \
299 'key must be != None'
300
301 cmd += [key]
302 op_log.debug('key: {k}'.format(k=key))
303 run_cmd(cmd, expects=expected)
304 if sop == 'existing':
305 CONFIG_DEL.append(key)
306 CONFIG_PUT.remove(key)
307 del CONFIG_EXISTING[key]
308 continue
309
310 elif op == 'exists':
311 expected = 0
312 cmd = ['exists']
313 key = None
314
315 if sop == 'existing':
316 if len(CONFIG_EXISTING) == 0:
317 op_log.debug('no existing keys; continue')
318 continue
319 key = rnd.choice(CONFIG_PUT)
320 assert key in CONFIG_EXISTING, \
321 "key '{k_}' not in CONFIG_EXISTING".format(k_=key)
322
323 if sop == 'enoent':
324 for x in range(0, 10):
325 key = base64.b64encode(os.urandom(20)).decode()
326 if key not in CONFIG_EXISTING:
327 break
328 key = None
329 if key is None:
330 op_log.error('unable to generate an unique key -- try again later.')
331 continue
332 assert key not in CONFIG_PUT and key not in CONFIG_EXISTING, \
333 'key {k} was not supposed to exist!'.format(k=key)
334 expected = -errno.ENOENT
335
336 assert key is not None, \
337 'key must be != None'
338
339 cmd += [key]
340 op_log.debug('key: {k}'.format(k=key))
341 run_cmd(cmd, expects=expected)
342 continue
343
344 elif op == 'get':
345 expected = 0
346 cmd = ['get']
347 key = None
348
349 if sop == 'existing':
350 if len(CONFIG_EXISTING) == 0:
351 op_log.debug('no existing keys; continue')
352 continue
353 key = rnd.choice(CONFIG_PUT)
354 assert key in CONFIG_EXISTING, \
355 "key '{k_}' not in CONFIG_EXISTING".format(k_=key)
356
357 if sop == 'enoent':
358 for x in range(0, 10):
359 key = base64.b64encode(os.urandom(20)).decode()
360 if key not in CONFIG_EXISTING:
361 break
362 key = None
363 if key is None:
364 op_log.error('unable to generate an unique key -- try again later.')
365 continue
366 assert key not in CONFIG_PUT and key not in CONFIG_EXISTING, \
367 'key {k} was not supposed to exist!'.format(k=key)
368 expected = -errno.ENOENT
369
370 assert key is not None, \
371 'key must be != None'
372
373 file_path = gen_tmp_file_path(rnd)
374 cmd += [key, '-o', file_path]
375 op_log.debug('key: {k}'.format(k=key))
376 run_cmd(cmd, expects=expected)
377 if sop == 'existing':
378 try:
379 temp_file = open(file_path, 'r+')
380 except IOError as err:
381 if err.errno == errno.ENOENT:
382 assert CONFIG_EXISTING[key] == 0, \
383 "error opening '{fp}': {e}".format(fp=file_path, e=err)
384 continue
385 else:
386 assert False, \
387 'some error occurred: {e}'.format(e=err)
388 cnt = 0
389 while True:
390 read_data = temp_file.read()
391 if read_data == '':
392 break
393 cnt += len(read_data)
394 assert cnt == CONFIG_EXISTING[key], \
395 "wrong size from store for key '{k}': {sz}, expected {es}".format(
396 k=key, sz=cnt, es=CONFIG_EXISTING[key])
397 destroy_tmp_file(file_path)
398 continue
399
400 elif op == 'list' or op == 'dump':
401 expected = 0
402 cmd = [op]
403 key = None
404
405 if sop == 'existing':
406 if len(CONFIG_EXISTING) == 0:
407 op_log.debug('no existing keys; continue')
408 continue
409 key = rnd.choice(CONFIG_PUT)
410 assert key in CONFIG_EXISTING, \
411 "key '{k_}' not in CONFIG_EXISTING".format(k_=key)
412
413 if sop == 'enoent':
414 for x in range(0, 10):
415 key = base64.b64encode(os.urandom(20)).decode()
416 if key not in CONFIG_EXISTING:
417 break
418 key = None
419 if key is None:
420 op_log.error('unable to generate an unique key -- try again later.')
421 continue
422 assert key not in CONFIG_PUT and key not in CONFIG_EXISTING, \
423 'key {k} was not supposed to exist!'.format(k=key)
424
425 assert key is not None, \
426 'key must be != None'
427
428 file_path = gen_tmp_file_path(rnd)
429 cmd += ['-o', file_path]
430 op_log.debug('key: {k}'.format(k=key))
431 run_cmd(cmd, expects=expected)
432 try:
433 temp_file = open(file_path, 'r+')
434 except IOError as err:
435 if err.errno == errno.ENOENT:
436 assert CONFIG_EXISTING[key] == 0, \
437 "error opening '{fp}': {e}".format(fp=file_path, e=err)
438 continue
439 else:
440 assert False, \
441 'some error occurred: {e}'.format(e=err)
442 cnt = 0
443 try:
444 read_data = json.load(temp_file)
445 except ValueError:
446 temp_file.seek(0)
447 assert False, "{op} output was not valid JSON:\n{filedata}".format(op, temp_file.readlines())
448
449 if sop == 'existing':
450 assert key in read_data, "key '{k}' not found in list/dump output".format(k=key)
451 if op == 'dump':
452 cnt = len(read_data[key])
453 assert cnt == CONFIG_EXISTING[key], \
454 "wrong size from list for key '{k}': {sz}, expected {es}".format(
455 k=key, sz=cnt, es=CONFIG_EXISTING[key])
456 elif sop == 'enoent':
457 assert key not in read_data, "key '{k}' found in list/dump output".format(k=key)
458 destroy_tmp_file(file_path)
459 continue
460 else:
461 assert False, 'unknown op {o}'.format(o=op)
462
463 # check if all keys in 'CONFIG_PUT' exist and
464 # if all keys on 'CONFIG_DEL' don't.
465 # but first however, remove all keys in CONFIG_PUT that might
466 # be in CONFIG_DEL as well.
467 config_put_set = set(CONFIG_PUT)
468 config_del_set = set(CONFIG_DEL).difference(config_put_set)
469
470 LOG.info('perform sanity checks on store')
471
472 for k in config_put_set:
473 LOG.getChild('check(puts)').debug('key: {k_}'.format(k_=k))
474 run_cmd(['exists', k], expects=0)
475 for k in config_del_set:
476 LOG.getChild('check(dels)').debug('key: {k_}'.format(k_=k))
477 run_cmd(['exists', k], expects=-errno.ENOENT)
478
479
480 if __name__ == "__main__":
481 main()