]> Git Repo - qemu.git/blob - tests/qemu-iotests/iotests.py
Merge remote-tracking branch 'remotes/stefanha/tags/tracing-pull-request' into staging
[qemu.git] / tests / qemu-iotests / iotests.py
1 from __future__ import print_function
2 # Common utilities and Python wrappers for qemu-iotests
3 #
4 # Copyright (C) 2012 IBM Corp.
5 #
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
18 #
19
20 import errno
21 import os
22 import re
23 import subprocess
24 import string
25 import unittest
26 import sys
27 import struct
28 import json
29 import signal
30 import logging
31 import atexit
32 import io
33 from collections import OrderedDict
34
35 sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', 'python'))
36 from qemu import qtest
37
38 assert sys.version_info >= (3,6)
39
40 # This will not work if arguments contain spaces but is necessary if we
41 # want to support the override options that ./check supports.
42 qemu_img_args = [os.environ.get('QEMU_IMG_PROG', 'qemu-img')]
43 if os.environ.get('QEMU_IMG_OPTIONS'):
44     qemu_img_args += os.environ['QEMU_IMG_OPTIONS'].strip().split(' ')
45
46 qemu_io_args = [os.environ.get('QEMU_IO_PROG', 'qemu-io')]
47 if os.environ.get('QEMU_IO_OPTIONS'):
48     qemu_io_args += os.environ['QEMU_IO_OPTIONS'].strip().split(' ')
49
50 qemu_nbd_args = [os.environ.get('QEMU_NBD_PROG', 'qemu-nbd')]
51 if os.environ.get('QEMU_NBD_OPTIONS'):
52     qemu_nbd_args += os.environ['QEMU_NBD_OPTIONS'].strip().split(' ')
53
54 qemu_prog = os.environ.get('QEMU_PROG', 'qemu')
55 qemu_opts = os.environ.get('QEMU_OPTIONS', '').strip().split(' ')
56
57 imgfmt = os.environ.get('IMGFMT', 'raw')
58 imgproto = os.environ.get('IMGPROTO', 'file')
59 test_dir = os.environ.get('TEST_DIR')
60 output_dir = os.environ.get('OUTPUT_DIR', '.')
61 cachemode = os.environ.get('CACHEMODE')
62 qemu_default_machine = os.environ.get('QEMU_DEFAULT_MACHINE')
63
64 socket_scm_helper = os.environ.get('SOCKET_SCM_HELPER', 'socket_scm_helper')
65
66 luks_default_secret_object = 'secret,id=keysec0,data=' + \
67                              os.environ.get('IMGKEYSECRET', '')
68 luks_default_key_secret_opt = 'key-secret=keysec0'
69
70
71 def qemu_img(*args):
72     '''Run qemu-img and return the exit code'''
73     devnull = open('/dev/null', 'r+')
74     exitcode = subprocess.call(qemu_img_args + list(args), stdin=devnull, stdout=devnull)
75     if exitcode < 0:
76         sys.stderr.write('qemu-img received signal %i: %s\n' % (-exitcode, ' '.join(qemu_img_args + list(args))))
77     return exitcode
78
79 def ordered_qmp(qmsg, conv_keys=True):
80     # Dictionaries are not ordered prior to 3.6, therefore:
81     if isinstance(qmsg, list):
82         return [ordered_qmp(atom) for atom in qmsg]
83     if isinstance(qmsg, dict):
84         od = OrderedDict()
85         for k, v in sorted(qmsg.items()):
86             if conv_keys:
87                 k = k.replace('_', '-')
88             od[k] = ordered_qmp(v, conv_keys=False)
89         return od
90     return qmsg
91
92 def qemu_img_create(*args):
93     args = list(args)
94
95     # default luks support
96     if '-f' in args and args[args.index('-f') + 1] == 'luks':
97         if '-o' in args:
98             i = args.index('-o')
99             if 'key-secret' not in args[i + 1]:
100                 args[i + 1].append(luks_default_key_secret_opt)
101                 args.insert(i + 2, '--object')
102                 args.insert(i + 3, luks_default_secret_object)
103         else:
104             args = ['-o', luks_default_key_secret_opt,
105                     '--object', luks_default_secret_object] + args
106
107     args.insert(0, 'create')
108
109     return qemu_img(*args)
110
111 def qemu_img_verbose(*args):
112     '''Run qemu-img without suppressing its output and return the exit code'''
113     exitcode = subprocess.call(qemu_img_args + list(args))
114     if exitcode < 0:
115         sys.stderr.write('qemu-img received signal %i: %s\n' % (-exitcode, ' '.join(qemu_img_args + list(args))))
116     return exitcode
117
118 def qemu_img_pipe(*args):
119     '''Run qemu-img and return its output'''
120     subp = subprocess.Popen(qemu_img_args + list(args),
121                             stdout=subprocess.PIPE,
122                             stderr=subprocess.STDOUT,
123                             universal_newlines=True)
124     exitcode = subp.wait()
125     if exitcode < 0:
126         sys.stderr.write('qemu-img received signal %i: %s\n' % (-exitcode, ' '.join(qemu_img_args + list(args))))
127     return subp.communicate()[0]
128
129 def qemu_img_log(*args):
130     result = qemu_img_pipe(*args)
131     log(result, filters=[filter_testfiles])
132     return result
133
134 def img_info_log(filename, filter_path=None, imgopts=False, extra_args=[]):
135     args = [ 'info' ]
136     if imgopts:
137         args.append('--image-opts')
138     else:
139         args += [ '-f', imgfmt ]
140     args += extra_args
141     args.append(filename)
142
143     output = qemu_img_pipe(*args)
144     if not filter_path:
145         filter_path = filename
146     log(filter_img_info(output, filter_path))
147
148 def qemu_io(*args):
149     '''Run qemu-io and return the stdout data'''
150     args = qemu_io_args + list(args)
151     subp = subprocess.Popen(args, stdout=subprocess.PIPE,
152                             stderr=subprocess.STDOUT,
153                             universal_newlines=True)
154     exitcode = subp.wait()
155     if exitcode < 0:
156         sys.stderr.write('qemu-io received signal %i: %s\n' % (-exitcode, ' '.join(args)))
157     return subp.communicate()[0]
158
159 def qemu_io_silent(*args):
160     '''Run qemu-io and return the exit code, suppressing stdout'''
161     args = qemu_io_args + list(args)
162     exitcode = subprocess.call(args, stdout=open('/dev/null', 'w'))
163     if exitcode < 0:
164         sys.stderr.write('qemu-io received signal %i: %s\n' %
165                          (-exitcode, ' '.join(args)))
166     return exitcode
167
168 def get_virtio_scsi_device():
169     if qemu_default_machine == 's390-ccw-virtio':
170         return 'virtio-scsi-ccw'
171     return 'virtio-scsi-pci'
172
173 class QemuIoInteractive:
174     def __init__(self, *args):
175         self.args = qemu_io_args + list(args)
176         self._p = subprocess.Popen(self.args, stdin=subprocess.PIPE,
177                                    stdout=subprocess.PIPE,
178                                    stderr=subprocess.STDOUT,
179                                    universal_newlines=True)
180         assert self._p.stdout.read(9) == 'qemu-io> '
181
182     def close(self):
183         self._p.communicate('q\n')
184
185     def _read_output(self):
186         pattern = 'qemu-io> '
187         n = len(pattern)
188         pos = 0
189         s = []
190         while pos != n:
191             c = self._p.stdout.read(1)
192             # check unexpected EOF
193             assert c != ''
194             s.append(c)
195             if c == pattern[pos]:
196                 pos += 1
197             else:
198                 pos = 0
199
200         return ''.join(s[:-n])
201
202     def cmd(self, cmd):
203         # quit command is in close(), '\n' is added automatically
204         assert '\n' not in cmd
205         cmd = cmd.strip()
206         assert cmd != 'q' and cmd != 'quit'
207         self._p.stdin.write(cmd + '\n')
208         self._p.stdin.flush()
209         return self._read_output()
210
211
212 def qemu_nbd(*args):
213     '''Run qemu-nbd in daemon mode and return the parent's exit code'''
214     return subprocess.call(qemu_nbd_args + ['--fork'] + list(args))
215
216 def qemu_nbd_early_pipe(*args):
217     '''Run qemu-nbd in daemon mode and return both the parent's exit code
218        and its output in case of an error'''
219     subp = subprocess.Popen(qemu_nbd_args + ['--fork'] + list(args),
220                             stdout=subprocess.PIPE,
221                             stderr=subprocess.STDOUT,
222                             universal_newlines=True)
223     exitcode = subp.wait()
224     if exitcode < 0:
225         sys.stderr.write('qemu-nbd received signal %i: %s\n' %
226                          (-exitcode,
227                           ' '.join(qemu_nbd_args + ['--fork'] + list(args))))
228     if exitcode == 0:
229         return exitcode, ''
230     else:
231         return exitcode, subp.communicate()[0]
232
233 def compare_images(img1, img2, fmt1=imgfmt, fmt2=imgfmt):
234     '''Return True if two image files are identical'''
235     return qemu_img('compare', '-f', fmt1,
236                     '-F', fmt2, img1, img2) == 0
237
238 def create_image(name, size):
239     '''Create a fully-allocated raw image with sector markers'''
240     file = open(name, 'wb')
241     i = 0
242     while i < size:
243         sector = struct.pack('>l504xl', i // 512, i // 512)
244         file.write(sector)
245         i = i + 512
246     file.close()
247
248 def image_size(img):
249     '''Return image's virtual size'''
250     r = qemu_img_pipe('info', '--output=json', '-f', imgfmt, img)
251     return json.loads(r)['virtual-size']
252
253 def is_str(val):
254     return isinstance(val, str)
255
256 test_dir_re = re.compile(r"%s" % test_dir)
257 def filter_test_dir(msg):
258     return test_dir_re.sub("TEST_DIR", msg)
259
260 win32_re = re.compile(r"\r")
261 def filter_win32(msg):
262     return win32_re.sub("", msg)
263
264 qemu_io_re = re.compile(r"[0-9]* ops; [0-9\/:. sec]* \([0-9\/.inf]* [EPTGMKiBbytes]*\/sec and [0-9\/.inf]* ops\/sec\)")
265 def filter_qemu_io(msg):
266     msg = filter_win32(msg)
267     return qemu_io_re.sub("X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)", msg)
268
269 chown_re = re.compile(r"chown [0-9]+:[0-9]+")
270 def filter_chown(msg):
271     return chown_re.sub("chown UID:GID", msg)
272
273 def filter_qmp_event(event):
274     '''Filter a QMP event dict'''
275     event = dict(event)
276     if 'timestamp' in event:
277         event['timestamp']['seconds'] = 'SECS'
278         event['timestamp']['microseconds'] = 'USECS'
279     return event
280
281 def filter_qmp(qmsg, filter_fn):
282     '''Given a string filter, filter a QMP object's values.
283     filter_fn takes a (key, value) pair.'''
284     # Iterate through either lists or dicts;
285     if isinstance(qmsg, list):
286         items = enumerate(qmsg)
287     else:
288         items = qmsg.items()
289
290     for k, v in items:
291         if isinstance(v, list) or isinstance(v, dict):
292             qmsg[k] = filter_qmp(v, filter_fn)
293         else:
294             qmsg[k] = filter_fn(k, v)
295     return qmsg
296
297 def filter_testfiles(msg):
298     prefix = os.path.join(test_dir, "%s-" % (os.getpid()))
299     return msg.replace(prefix, 'TEST_DIR/PID-')
300
301 def filter_qmp_testfiles(qmsg):
302     def _filter(key, value):
303         if is_str(value):
304             return filter_testfiles(value)
305         return value
306     return filter_qmp(qmsg, _filter)
307
308 def filter_generated_node_ids(msg):
309     return re.sub("#block[0-9]+", "NODE_NAME", msg)
310
311 def filter_img_info(output, filename):
312     lines = []
313     for line in output.split('\n'):
314         if 'disk size' in line or 'actual-size' in line:
315             continue
316         line = line.replace(filename, 'TEST_IMG') \
317                    .replace(imgfmt, 'IMGFMT')
318         line = re.sub('iters: [0-9]+', 'iters: XXX', line)
319         line = re.sub('uuid: [-a-f0-9]+', 'uuid: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX', line)
320         line = re.sub('cid: [0-9]+', 'cid: XXXXXXXXXX', line)
321         lines.append(line)
322     return '\n'.join(lines)
323
324 def filter_imgfmt(msg):
325     return msg.replace(imgfmt, 'IMGFMT')
326
327 def filter_qmp_imgfmt(qmsg):
328     def _filter(key, value):
329         if is_str(value):
330             return filter_imgfmt(value)
331         return value
332     return filter_qmp(qmsg, _filter)
333
334 def log(msg, filters=[], indent=None):
335     '''Logs either a string message or a JSON serializable message (like QMP).
336     If indent is provided, JSON serializable messages are pretty-printed.'''
337     for flt in filters:
338         msg = flt(msg)
339     if isinstance(msg, dict) or isinstance(msg, list):
340         # Python < 3.4 needs to know not to add whitespace when pretty-printing:
341         separators = (', ', ': ') if indent is None else (',', ': ')
342         # Don't sort if it's already sorted
343         do_sort = not isinstance(msg, OrderedDict)
344         print(json.dumps(msg, sort_keys=do_sort,
345                          indent=indent, separators=separators))
346     else:
347         print(msg)
348
349 class Timeout:
350     def __init__(self, seconds, errmsg = "Timeout"):
351         self.seconds = seconds
352         self.errmsg = errmsg
353     def __enter__(self):
354         signal.signal(signal.SIGALRM, self.timeout)
355         signal.setitimer(signal.ITIMER_REAL, self.seconds)
356         return self
357     def __exit__(self, type, value, traceback):
358         signal.setitimer(signal.ITIMER_REAL, 0)
359         return False
360     def timeout(self, signum, frame):
361         raise Exception(self.errmsg)
362
363 def file_pattern(name):
364     return "{0}-{1}".format(os.getpid(), name)
365
366 class FilePaths(object):
367     """
368     FilePaths is an auto-generated filename that cleans itself up.
369
370     Use this context manager to generate filenames and ensure that the file
371     gets deleted::
372
373         with FilePaths(['test.img']) as img_path:
374             qemu_img('create', img_path, '1G')
375         # migration_sock_path is automatically deleted
376     """
377     def __init__(self, names):
378         self.paths = []
379         for name in names:
380             self.paths.append(os.path.join(test_dir, file_pattern(name)))
381
382     def __enter__(self):
383         return self.paths
384
385     def __exit__(self, exc_type, exc_val, exc_tb):
386         try:
387             for path in self.paths:
388                 os.remove(path)
389         except OSError:
390             pass
391         return False
392
393 class FilePath(FilePaths):
394     """
395     FilePath is a specialization of FilePaths that takes a single filename.
396     """
397     def __init__(self, name):
398         super(FilePath, self).__init__([name])
399
400     def __enter__(self):
401         return self.paths[0]
402
403 def file_path_remover():
404     for path in reversed(file_path_remover.paths):
405         try:
406             os.remove(path)
407         except OSError:
408             pass
409
410
411 def file_path(*names):
412     ''' Another way to get auto-generated filename that cleans itself up.
413
414     Use is as simple as:
415
416     img_a, img_b = file_path('a.img', 'b.img')
417     sock = file_path('socket')
418     '''
419
420     if not hasattr(file_path_remover, 'paths'):
421         file_path_remover.paths = []
422         atexit.register(file_path_remover)
423
424     paths = []
425     for name in names:
426         filename = file_pattern(name)
427         path = os.path.join(test_dir, filename)
428         file_path_remover.paths.append(path)
429         paths.append(path)
430
431     return paths[0] if len(paths) == 1 else paths
432
433 def remote_filename(path):
434     if imgproto == 'file':
435         return path
436     elif imgproto == 'ssh':
437         return "ssh://%[email protected]:22%s" % (os.environ.get('USER'), path)
438     else:
439         raise Exception("Protocol %s not supported" % (imgproto))
440
441 class VM(qtest.QEMUQtestMachine):
442     '''A QEMU VM'''
443
444     def __init__(self, path_suffix=''):
445         name = "qemu%s-%d" % (path_suffix, os.getpid())
446         super(VM, self).__init__(qemu_prog, qemu_opts, name=name,
447                                  test_dir=test_dir,
448                                  socket_scm_helper=socket_scm_helper)
449         self._num_drives = 0
450
451     def add_object(self, opts):
452         self._args.append('-object')
453         self._args.append(opts)
454         return self
455
456     def add_device(self, opts):
457         self._args.append('-device')
458         self._args.append(opts)
459         return self
460
461     def add_drive_raw(self, opts):
462         self._args.append('-drive')
463         self._args.append(opts)
464         return self
465
466     def add_drive(self, path, opts='', interface='virtio', format=imgfmt):
467         '''Add a virtio-blk drive to the VM'''
468         options = ['if=%s' % interface,
469                    'id=drive%d' % self._num_drives]
470
471         if path is not None:
472             options.append('file=%s' % path)
473             options.append('format=%s' % format)
474             options.append('cache=%s' % cachemode)
475
476         if opts:
477             options.append(opts)
478
479         if format == 'luks' and 'key-secret' not in opts:
480             # default luks support
481             if luks_default_secret_object not in self._args:
482                 self.add_object(luks_default_secret_object)
483
484             options.append(luks_default_key_secret_opt)
485
486         self._args.append('-drive')
487         self._args.append(','.join(options))
488         self._num_drives += 1
489         return self
490
491     def add_blockdev(self, opts):
492         self._args.append('-blockdev')
493         if isinstance(opts, str):
494             self._args.append(opts)
495         else:
496             self._args.append(','.join(opts))
497         return self
498
499     def add_incoming(self, addr):
500         self._args.append('-incoming')
501         self._args.append(addr)
502         return self
503
504     def pause_drive(self, drive, event=None):
505         '''Pause drive r/w operations'''
506         if not event:
507             self.pause_drive(drive, "read_aio")
508             self.pause_drive(drive, "write_aio")
509             return
510         self.qmp('human-monitor-command',
511                     command_line='qemu-io %s "break %s bp_%s"' % (drive, event, drive))
512
513     def resume_drive(self, drive):
514         self.qmp('human-monitor-command',
515                     command_line='qemu-io %s "remove_break bp_%s"' % (drive, drive))
516
517     def hmp_qemu_io(self, drive, cmd):
518         '''Write to a given drive using an HMP command'''
519         return self.qmp('human-monitor-command',
520                         command_line='qemu-io %s "%s"' % (drive, cmd))
521
522     def flatten_qmp_object(self, obj, output=None, basestr=''):
523         if output is None:
524             output = dict()
525         if isinstance(obj, list):
526             for i in range(len(obj)):
527                 self.flatten_qmp_object(obj[i], output, basestr + str(i) + '.')
528         elif isinstance(obj, dict):
529             for key in obj:
530                 self.flatten_qmp_object(obj[key], output, basestr + key + '.')
531         else:
532             output[basestr[:-1]] = obj # Strip trailing '.'
533         return output
534
535     def qmp_to_opts(self, obj):
536         obj = self.flatten_qmp_object(obj)
537         output_list = list()
538         for key in obj:
539             output_list += [key + '=' + obj[key]]
540         return ','.join(output_list)
541
542     def get_qmp_events_filtered(self, wait=60.0):
543         result = []
544         for ev in self.get_qmp_events(wait=wait):
545             result.append(filter_qmp_event(ev))
546         return result
547
548     def qmp_log(self, cmd, filters=[], indent=None, **kwargs):
549         full_cmd = OrderedDict((
550             ("execute", cmd),
551             ("arguments", ordered_qmp(kwargs))
552         ))
553         log(full_cmd, filters, indent=indent)
554         result = self.qmp(cmd, **kwargs)
555         log(result, filters, indent=indent)
556         return result
557
558     # Returns None on success, and an error string on failure
559     def run_job(self, job, auto_finalize=True, auto_dismiss=False,
560                 pre_finalize=None, cancel=False, use_log=True, wait=60.0):
561         """
562         run_job moves a job from creation through to dismissal.
563
564         :param job: String. ID of recently-launched job
565         :param auto_finalize: Bool. True if the job was launched with
566                               auto_finalize. Defaults to True.
567         :param auto_dismiss: Bool. True if the job was launched with
568                              auto_dismiss=True. Defaults to False.
569         :param pre_finalize: Callback. A callable that takes no arguments to be
570                              invoked prior to issuing job-finalize, if any.
571         :param cancel: Bool. When true, cancels the job after the pre_finalize
572                        callback.
573         :param use_log: Bool. When false, does not log QMP messages.
574         :param wait: Float. Timeout value specifying how long to wait for any
575                      event, in seconds. Defaults to 60.0.
576         """
577         match_device = {'data': {'device': job}}
578         match_id = {'data': {'id': job}}
579         events = [
580             ('BLOCK_JOB_COMPLETED', match_device),
581             ('BLOCK_JOB_CANCELLED', match_device),
582             ('BLOCK_JOB_ERROR', match_device),
583             ('BLOCK_JOB_READY', match_device),
584             ('BLOCK_JOB_PENDING', match_id),
585             ('JOB_STATUS_CHANGE', match_id)
586         ]
587         error = None
588         while True:
589             ev = filter_qmp_event(self.events_wait(events))
590             if ev['event'] != 'JOB_STATUS_CHANGE':
591                 if use_log:
592                     log(ev)
593                 continue
594             status = ev['data']['status']
595             if status == 'aborting':
596                 result = self.qmp('query-jobs')
597                 for j in result['return']:
598                     if j['id'] == job:
599                         error = j['error']
600                         if use_log:
601                             log('Job failed: %s' % (j['error']))
602             elif status == 'pending' and not auto_finalize:
603                 if pre_finalize:
604                     pre_finalize()
605                 if cancel and use_log:
606                     self.qmp_log('job-cancel', id=job)
607                 elif cancel:
608                     self.qmp('job-cancel', id=job)
609                 elif use_log:
610                     self.qmp_log('job-finalize', id=job)
611                 else:
612                     self.qmp('job-finalize', id=job)
613             elif status == 'concluded' and not auto_dismiss:
614                 if use_log:
615                     self.qmp_log('job-dismiss', id=job)
616                 else:
617                     self.qmp('job-dismiss', id=job)
618             elif status == 'null':
619                 return error
620
621     def enable_migration_events(self, name):
622         log('Enabling migration QMP events on %s...' % name)
623         log(self.qmp('migrate-set-capabilities', capabilities=[
624             {
625                 'capability': 'events',
626                 'state': True
627             }
628         ]))
629
630     def wait_migration(self):
631         while True:
632             event = self.event_wait('MIGRATION')
633             log(event, filters=[filter_qmp_event])
634             if event['data']['status'] == 'completed':
635                 break
636
637     def node_info(self, node_name):
638         nodes = self.qmp('query-named-block-nodes')
639         for x in nodes['return']:
640             if x['node-name'] == node_name:
641                 return x
642         return None
643
644     def query_bitmaps(self):
645         res = self.qmp("query-named-block-nodes")
646         return {device['node-name']: device['dirty-bitmaps']
647                 for device in res['return'] if 'dirty-bitmaps' in device}
648
649     def get_bitmap(self, node_name, bitmap_name, recording=None, bitmaps=None):
650         """
651         get a specific bitmap from the object returned by query_bitmaps.
652         :param recording: If specified, filter results by the specified value.
653         :param bitmaps: If specified, use it instead of call query_bitmaps()
654         """
655         if bitmaps is None:
656             bitmaps = self.query_bitmaps()
657
658         for bitmap in bitmaps[node_name]:
659             if bitmap.get('name', '') == bitmap_name:
660                 if recording is None:
661                     return bitmap
662                 elif bitmap.get('recording') == recording:
663                     return bitmap
664         return None
665
666     def check_bitmap_status(self, node_name, bitmap_name, fields):
667         ret = self.get_bitmap(node_name, bitmap_name)
668
669         return fields.items() <= ret.items()
670
671
672 index_re = re.compile(r'([^\[]+)\[([^\]]+)\]')
673
674 class QMPTestCase(unittest.TestCase):
675     '''Abstract base class for QMP test cases'''
676
677     def dictpath(self, d, path):
678         '''Traverse a path in a nested dict'''
679         for component in path.split('/'):
680             m = index_re.match(component)
681             if m:
682                 component, idx = m.groups()
683                 idx = int(idx)
684
685             if not isinstance(d, dict) or component not in d:
686                 self.fail('failed path traversal for "%s" in "%s"' % (path, str(d)))
687             d = d[component]
688
689             if m:
690                 if not isinstance(d, list):
691                     self.fail('path component "%s" in "%s" is not a list in "%s"' % (component, path, str(d)))
692                 try:
693                     d = d[idx]
694                 except IndexError:
695                     self.fail('invalid index "%s" in path "%s" in "%s"' % (idx, path, str(d)))
696         return d
697
698     def assert_qmp_absent(self, d, path):
699         try:
700             result = self.dictpath(d, path)
701         except AssertionError:
702             return
703         self.fail('path "%s" has value "%s"' % (path, str(result)))
704
705     def assert_qmp(self, d, path, value):
706         '''Assert that the value for a specific path in a QMP dict
707            matches.  When given a list of values, assert that any of
708            them matches.'''
709
710         result = self.dictpath(d, path)
711
712         # [] makes no sense as a list of valid values, so treat it as
713         # an actual single value.
714         if isinstance(value, list) and value != []:
715             for v in value:
716                 if result == v:
717                     return
718             self.fail('no match for "%s" in %s' % (str(result), str(value)))
719         else:
720             self.assertEqual(result, value,
721                              'values not equal "%s" and "%s"'
722                                  % (str(result), str(value)))
723
724     def assert_no_active_block_jobs(self):
725         result = self.vm.qmp('query-block-jobs')
726         self.assert_qmp(result, 'return', [])
727
728     def assert_has_block_node(self, node_name=None, file_name=None):
729         """Issue a query-named-block-nodes and assert node_name and/or
730         file_name is present in the result"""
731         def check_equal_or_none(a, b):
732             return a == None or b == None or a == b
733         assert node_name or file_name
734         result = self.vm.qmp('query-named-block-nodes')
735         for x in result["return"]:
736             if check_equal_or_none(x.get("node-name"), node_name) and \
737                     check_equal_or_none(x.get("file"), file_name):
738                 return
739         self.assertTrue(False, "Cannot find %s %s in result:\n%s" % \
740                 (node_name, file_name, result))
741
742     def assert_json_filename_equal(self, json_filename, reference):
743         '''Asserts that the given filename is a json: filename and that its
744            content is equal to the given reference object'''
745         self.assertEqual(json_filename[:5], 'json:')
746         self.assertEqual(self.vm.flatten_qmp_object(json.loads(json_filename[5:])),
747                          self.vm.flatten_qmp_object(reference))
748
749     def cancel_and_wait(self, drive='drive0', force=False, resume=False, wait=60.0):
750         '''Cancel a block job and wait for it to finish, returning the event'''
751         result = self.vm.qmp('block-job-cancel', device=drive, force=force)
752         self.assert_qmp(result, 'return', {})
753
754         if resume:
755             self.vm.resume_drive(drive)
756
757         cancelled = False
758         result = None
759         while not cancelled:
760             for event in self.vm.get_qmp_events(wait=wait):
761                 if event['event'] == 'BLOCK_JOB_COMPLETED' or \
762                    event['event'] == 'BLOCK_JOB_CANCELLED':
763                     self.assert_qmp(event, 'data/device', drive)
764                     result = event
765                     cancelled = True
766                 elif event['event'] == 'JOB_STATUS_CHANGE':
767                     self.assert_qmp(event, 'data/id', drive)
768
769
770         self.assert_no_active_block_jobs()
771         return result
772
773     def wait_until_completed(self, drive='drive0', check_offset=True, wait=60.0):
774         '''Wait for a block job to finish, returning the event'''
775         while True:
776             for event in self.vm.get_qmp_events(wait=wait):
777                 if event['event'] == 'BLOCK_JOB_COMPLETED':
778                     self.assert_qmp(event, 'data/device', drive)
779                     self.assert_qmp_absent(event, 'data/error')
780                     if check_offset:
781                         self.assert_qmp(event, 'data/offset', event['data']['len'])
782                     self.assert_no_active_block_jobs()
783                     return event
784                 elif event['event'] == 'JOB_STATUS_CHANGE':
785                     self.assert_qmp(event, 'data/id', drive)
786
787     def wait_ready(self, drive='drive0'):
788         '''Wait until a block job BLOCK_JOB_READY event'''
789         f = {'data': {'type': 'mirror', 'device': drive } }
790         event = self.vm.event_wait(name='BLOCK_JOB_READY', match=f)
791
792     def wait_ready_and_cancel(self, drive='drive0'):
793         self.wait_ready(drive=drive)
794         event = self.cancel_and_wait(drive=drive)
795         self.assertEqual(event['event'], 'BLOCK_JOB_COMPLETED')
796         self.assert_qmp(event, 'data/type', 'mirror')
797         self.assert_qmp(event, 'data/offset', event['data']['len'])
798
799     def complete_and_wait(self, drive='drive0', wait_ready=True):
800         '''Complete a block job and wait for it to finish'''
801         if wait_ready:
802             self.wait_ready(drive=drive)
803
804         result = self.vm.qmp('block-job-complete', device=drive)
805         self.assert_qmp(result, 'return', {})
806
807         event = self.wait_until_completed(drive=drive)
808         self.assert_qmp(event, 'data/type', 'mirror')
809
810     def pause_wait(self, job_id='job0'):
811         with Timeout(1, "Timeout waiting for job to pause"):
812             while True:
813                 result = self.vm.qmp('query-block-jobs')
814                 found = False
815                 for job in result['return']:
816                     if job['device'] == job_id:
817                         found = True
818                         if job['paused'] == True and job['busy'] == False:
819                             return job
820                         break
821                 assert found
822
823     def pause_job(self, job_id='job0', wait=True):
824         result = self.vm.qmp('block-job-pause', device=job_id)
825         self.assert_qmp(result, 'return', {})
826         if wait:
827             return self.pause_wait(job_id)
828         return result
829
830
831 def notrun(reason):
832     '''Skip this test suite'''
833     # Each test in qemu-iotests has a number ("seq")
834     seq = os.path.basename(sys.argv[0])
835
836     open('%s/%s.notrun' % (output_dir, seq), 'w').write(reason + '\n')
837     print('%s not run: %s' % (seq, reason))
838     sys.exit(0)
839
840 def case_notrun(reason):
841     '''Skip this test case'''
842     # Each test in qemu-iotests has a number ("seq")
843     seq = os.path.basename(sys.argv[0])
844
845     open('%s/%s.casenotrun' % (output_dir, seq), 'a').write(
846         '    [case not run] ' + reason + '\n')
847
848 def verify_image_format(supported_fmts=[], unsupported_fmts=[]):
849     assert not (supported_fmts and unsupported_fmts)
850
851     if 'generic' in supported_fmts and \
852             os.environ.get('IMGFMT_GENERIC', 'true') == 'true':
853         # similar to
854         #   _supported_fmt generic
855         # for bash tests
856         return
857
858     not_sup = supported_fmts and (imgfmt not in supported_fmts)
859     if not_sup or (imgfmt in unsupported_fmts):
860         notrun('not suitable for this image format: %s' % imgfmt)
861
862 def verify_protocol(supported=[], unsupported=[]):
863     assert not (supported and unsupported)
864
865     if 'generic' in supported:
866         return
867
868     not_sup = supported and (imgproto not in supported)
869     if not_sup or (imgproto in unsupported):
870         notrun('not suitable for this protocol: %s' % imgproto)
871
872 def verify_platform(supported_oses=['linux']):
873     if True not in [sys.platform.startswith(x) for x in supported_oses]:
874         notrun('not suitable for this OS: %s' % sys.platform)
875
876 def verify_cache_mode(supported_cache_modes=[]):
877     if supported_cache_modes and (cachemode not in supported_cache_modes):
878         notrun('not suitable for this cache mode: %s' % cachemode)
879
880 def supports_quorum():
881     return 'quorum' in qemu_img_pipe('--help')
882
883 def verify_quorum():
884     '''Skip test suite if quorum support is not available'''
885     if not supports_quorum():
886         notrun('quorum support missing')
887
888 def qemu_pipe(*args):
889     '''Run qemu with an option to print something and exit (e.g. a help option),
890     and return its output'''
891     args = [qemu_prog] + qemu_opts + list(args)
892     subp = subprocess.Popen(args, stdout=subprocess.PIPE,
893                             stderr=subprocess.STDOUT,
894                             universal_newlines=True)
895     exitcode = subp.wait()
896     if exitcode < 0:
897         sys.stderr.write('qemu received signal %i: %s\n' % (-exitcode,
898                          ' '.join(args)))
899     return subp.communicate()[0]
900
901 def supported_formats(read_only=False):
902     '''Set 'read_only' to True to check ro-whitelist
903        Otherwise, rw-whitelist is checked'''
904     format_message = qemu_pipe("-drive", "format=help")
905     line = 1 if read_only else 0
906     return format_message.splitlines()[line].split(":")[1].split()
907
908 def skip_if_unsupported(required_formats=[], read_only=False):
909     '''Skip Test Decorator
910        Runs the test if all the required formats are whitelisted'''
911     def skip_test_decorator(func):
912         def func_wrapper(*args, **kwargs):
913             usf_list = list(set(required_formats) -
914                             set(supported_formats(read_only)))
915             if usf_list:
916                 case_notrun('{}: formats {} are not whitelisted'.format(
917                     args[0], usf_list))
918             else:
919                 return func(*args, **kwargs)
920         return func_wrapper
921     return skip_test_decorator
922
923 def execute_unittest(output, verbosity, debug):
924     runner = unittest.TextTestRunner(stream=output, descriptions=True,
925                                      verbosity=verbosity)
926     try:
927         # unittest.main() will use sys.exit(); so expect a SystemExit
928         # exception
929         unittest.main(testRunner=runner)
930     finally:
931         if not debug:
932             sys.stderr.write(re.sub(r'Ran (\d+) tests? in [\d.]+s',
933                                     r'Ran \1 tests', output.getvalue()))
934
935 def execute_test(test_function=None,
936                  supported_fmts=[], supported_oses=['linux'],
937                  supported_cache_modes=[], unsupported_fmts=[],
938                  supported_protocols=[], unsupported_protocols=[]):
939     """Run either unittest or script-style tests."""
940
941     # We are using TEST_DIR and QEMU_DEFAULT_MACHINE as proxies to
942     # indicate that we're not being run via "check". There may be
943     # other things set up by "check" that individual test cases rely
944     # on.
945     if test_dir is None or qemu_default_machine is None:
946         sys.stderr.write('Please run this test via the "check" script\n')
947         sys.exit(os.EX_USAGE)
948
949     debug = '-d' in sys.argv
950     verbosity = 1
951     verify_image_format(supported_fmts, unsupported_fmts)
952     verify_protocol(supported_protocols, unsupported_protocols)
953     verify_platform(supported_oses)
954     verify_cache_mode(supported_cache_modes)
955
956     if debug:
957         output = sys.stdout
958         verbosity = 2
959         sys.argv.remove('-d')
960     else:
961         # We need to filter out the time taken from the output so that
962         # qemu-iotest can reliably diff the results against master output.
963         output = io.StringIO()
964
965     logging.basicConfig(level=(logging.DEBUG if debug else logging.WARN))
966
967     if not test_function:
968         execute_unittest(output, verbosity, debug)
969     else:
970         test_function()
971
972 def script_main(test_function, *args, **kwargs):
973     """Run script-style tests outside of the unittest framework"""
974     execute_test(test_function, *args, **kwargs)
975
976 def main(*args, **kwargs):
977     """Run tests using the unittest framework"""
978     execute_test(None, *args, **kwargs)
This page took 0.08018 seconds and 4 git commands to generate.