Fix some bugs when testing opensds ansible
[stor4nfv.git] / src / 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()