]> git.proxmox.com Git - ceph.git/blame - ceph/qa/workunits/mon/test_mon_config_key.py
update sources to ceph Nautilus 14.2.1
[ceph.git] / ceph / qa / workunits / mon / test_mon_config_key.py
CommitLineData
7c673cae
FG
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#
12import argparse
13import base64
14import errno
15import json
16import logging
17import os
18import random
19import string
20import subprocess
21import sys
22import 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
37LOG = logging.getLogger(os.path.basename(sys.argv[0].replace('.py', '')))
38
39SIZES = [
40 (0, 0),
41 (10, 0),
42 (25, 0),
43 (50, 0),
44 (100, 0),
45 (1000, 0),
11fdf7f2
TL
46 (64 * 1024, 0),
47 (64 * 1024 + 1, -errno.EFBIG),
48 (128 * 1024, -errno.EFBIG)
7c673cae
FG
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
58OPS = {
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
67CONFIG_PUT = [] # list: keys
68CONFIG_DEL = [] # list: keys
69CONFIG_EXISTING = {} # map: key -> size
70
71
72def 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
114def gen_data(size, rnd):
115 chars = string.ascii_letters + string.digits
116 return ''.join(rnd.choice(chars) for _ in range(size))
117
118
119def gen_key(rnd):
120 return gen_data(20, rnd)
121
122
123def 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
129def destroy_tmp_file(fpath):
130 if os.path.exists(fpath) and os.path.isfile(fpath):
131 os.unlink(fpath)
132
133
134def 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
145def 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
153def 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
180def 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
480if __name__ == "__main__":
481 main()