3 # test_mon_config_key - Test 'ceph config-key' interface
5 # Copyright (C) 2013 Inktank
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.
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
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
37 LOG = logging.getLogger(os.path.basename(sys.argv[0].replace('.py', '')))
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.
59 'put': ['existing', 'new'],
60 'del': ['existing', 'enoent'],
61 'exists': ['existing', 'enoent'],
62 'get': ['existing', 'enoent'],
63 'list': ['existing', 'enoent'],
64 'dump': ['existing', 'enoent'],
67 CONFIG_PUT = [] # list: keys
68 CONFIG_DEL = [] # list: keys
69 CONFIG_EXISTING = {} # map: key -> size
72 def run_cmd(cmd, expects=0):
73 full_cmd = ['ceph', 'config-key'] + cmd
78 cmdlog = LOG.getChild('run_cmd')
79 cmdlog.debug('{fc}'.format(fc=' '.join(full_cmd)))
81 proc = subprocess.Popen(full_cmd,
82 stdout=subprocess.PIPE,
83 stderr=subprocess.PIPE)
89 out, err = proc.communicate()
91 stdout += out.decode().split('\n')
92 cmdlog.debug('stdout: {s}'.format(s=out))
94 stdout += err.decode().split('\n')
95 cmdlog.debug('stderr: {s}'.format(s=err))
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')
106 cmdlog.error('{x}'.format(x=i))
107 cmdlog.error('stderr')
109 cmdlog.error('{x}'.format(x=i))
114 def gen_data(size, rnd):
115 chars = string.ascii_letters + string.digits
116 return ''.join(rnd.choice(chars) for _ in range(size))
120 return gen_data(20, rnd)
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)
129 def destroy_tmp_file(fpath):
130 if os.path.exists(fpath) and os.path.isfile(fpath):
134 def write_data_file(data, rnd):
135 file_path = gen_tmp_file_path(rnd)
136 data_file = open(file_path, 'a+')
138 data_file.write(data)
143 # end write_data_file
145 def choose_random_op(rnd):
149 sop = rnd.choice(OPS[op])
153 def parse_args(args):
154 parser = argparse.ArgumentParser(
155 description="Test the monitor's 'config-key' API",
160 help='be more verbose',
165 help='use SEED instead of generating it in run-time',
170 help='run test for SECS seconds (default: 300)',
177 return parser.parse_args(args)
181 args = parse_args(sys.argv[1:])
183 verbose = args.verbose
184 if os.environ.get('CEPH_TEST_VERBOSE') is not None:
185 verbose = (os.environ.get('CEPH_TEST_VERBOSE') == '1')
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)
191 rnd = random.Random()
194 loglevel = logging.INFO
196 loglevel = logging.DEBUG
198 logging.basicConfig(level=loglevel)
200 LOG.info('seed: {s}'.format(s=seed))
204 while (time.time() - start) < duration:
205 (op, sop) = choose_random_op(rnd)
207 LOG.info('{o}({s})'.format(o=op, s=sop))
208 op_log = LOG.getChild('{o}({s})'.format(o=op, s=sop))
211 via_file = (rnd.uniform(0, 100) < 50.0)
217 if sop == 'existing':
218 if len(CONFIG_EXISTING) == 0:
219 op_log.debug('no existing keys; continue')
221 key = rnd.choice(CONFIG_PUT)
222 assert key in CONFIG_EXISTING, \
223 "key '{k_}' not in CONFIG_EXISTING".format(k_=key)
225 expected = 0 # the store just overrides the value if the key exists
226 # end if sop == 'existing'
228 for x in range(0, 10):
230 if key not in CONFIG_EXISTING:
234 op_log.error('unable to generate an unique key -- try again later.')
237 assert key not in CONFIG_PUT and key not in CONFIG_EXISTING, \
238 'key {k} was not supposed to exist!'.format(k=key)
240 assert key is not None, \
241 'key must be != None'
245 (size, error) = rnd.choice(SIZES)
249 data = gen_data(size, rnd)
251 if error == 0: # only add if we expect the put to be successful
253 CONFIG_PUT.append(key)
254 CONFIG_EXISTING[key] = size
258 data_file = write_data_file(data, rnd)
259 cmd += ['-i', data_file]
263 op_log.debug('size: {sz}, via: {v}'.format(
265 v='file: {f}'.format(f=data_file) if via_file == True else 'cli')
267 run_cmd(cmd, expects=expected)
269 destroy_tmp_file(data_file)
277 if sop == 'existing':
278 if len(CONFIG_EXISTING) == 0:
279 op_log.debug('no existing keys; continue')
281 key = rnd.choice(CONFIG_PUT)
282 assert key in CONFIG_EXISTING, \
283 "key '{k_}' not in CONFIG_EXISTING".format(k_=key)
286 for x in range(0, 10):
287 key = base64.b64encode(os.urandom(20)).decode()
288 if key not in CONFIG_EXISTING:
292 op_log.error('unable to generate an unique key -- try again later.')
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
298 assert key is not None, \
299 'key must be != None'
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]
315 if sop == 'existing':
316 if len(CONFIG_EXISTING) == 0:
317 op_log.debug('no existing keys; continue')
319 key = rnd.choice(CONFIG_PUT)
320 assert key in CONFIG_EXISTING, \
321 "key '{k_}' not in CONFIG_EXISTING".format(k_=key)
324 for x in range(0, 10):
325 key = base64.b64encode(os.urandom(20)).decode()
326 if key not in CONFIG_EXISTING:
330 op_log.error('unable to generate an unique key -- try again later.')
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
336 assert key is not None, \
337 'key must be != None'
340 op_log.debug('key: {k}'.format(k=key))
341 run_cmd(cmd, expects=expected)
349 if sop == 'existing':
350 if len(CONFIG_EXISTING) == 0:
351 op_log.debug('no existing keys; continue')
353 key = rnd.choice(CONFIG_PUT)
354 assert key in CONFIG_EXISTING, \
355 "key '{k_}' not in CONFIG_EXISTING".format(k_=key)
358 for x in range(0, 10):
359 key = base64.b64encode(os.urandom(20)).decode()
360 if key not in CONFIG_EXISTING:
364 op_log.error('unable to generate an unique key -- try again later.')
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
370 assert key is not None, \
371 'key must be != None'
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':
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)
387 'some error occurred: {e}'.format(e=err)
390 read_data = temp_file.read()
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)
400 elif op == 'list' or op == 'dump':
405 if sop == 'existing':
406 if len(CONFIG_EXISTING) == 0:
407 op_log.debug('no existing keys; continue')
409 key = rnd.choice(CONFIG_PUT)
410 assert key in CONFIG_EXISTING, \
411 "key '{k_}' not in CONFIG_EXISTING".format(k_=key)
414 for x in range(0, 10):
415 key = base64.b64encode(os.urandom(20)).decode()
416 if key not in CONFIG_EXISTING:
420 op_log.error('unable to generate an unique key -- try again later.')
422 assert key not in CONFIG_PUT and key not in CONFIG_EXISTING, \
423 'key {k} was not supposed to exist!'.format(k=key)
425 assert key is not None, \
426 'key must be != None'
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)
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)
441 'some error occurred: {e}'.format(e=err)
444 read_data = json.load(temp_file)
447 assert False, "{op} output was not valid JSON:\n{filedata}".format(op, temp_file.readlines())
449 if sop == 'existing':
450 assert key in read_data, "key '{k}' not found in list/dump output".format(k=key)
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)
461 assert False, 'unknown op {o}'.format(o=op)
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)
470 LOG.info('perform sanity checks on store')
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)
480 if __name__ == "__main__":