]> Git Repo - qemu.git/blob - tests/qemu-iotests/iotests.py
Merge remote-tracking branch 'remotes/kevin/tags/for-upstream' 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
645 index_re = re.compile(r'([^\[]+)\[([^\]]+)\]')
646
647 class QMPTestCase(unittest.TestCase):
648     '''Abstract base class for QMP test cases'''
649
650     def dictpath(self, d, path):
651         '''Traverse a path in a nested dict'''
652         for component in path.split('/'):
653             m = index_re.match(component)
654             if m:
655                 component, idx = m.groups()
656                 idx = int(idx)
657
658             if not isinstance(d, dict) or component not in d:
659                 self.fail('failed path traversal for "%s" in "%s"' % (path, str(d)))
660             d = d[component]
661
662             if m:
663                 if not isinstance(d, list):
664                     self.fail('path component "%s" in "%s" is not a list in "%s"' % (component, path, str(d)))
665                 try:
666                     d = d[idx]
667                 except IndexError:
668                     self.fail('invalid index "%s" in path "%s" in "%s"' % (idx, path, str(d)))
669         return d
670
671     def assert_qmp_absent(self, d, path):
672         try:
673             result = self.dictpath(d, path)
674         except AssertionError:
675             return
676         self.fail('path "%s" has value "%s"' % (path, str(result)))
677
678     def assert_qmp(self, d, path, value):
679         '''Assert that the value for a specific path in a QMP dict
680            matches.  When given a list of values, assert that any of
681            them matches.'''
682
683         result = self.dictpath(d, path)
684
685         # [] makes no sense as a list of valid values, so treat it as
686         # an actual single value.
687         if isinstance(value, list) and value != []:
688             for v in value:
689                 if result == v:
690                     return
691             self.fail('no match for "%s" in %s' % (str(result), str(value)))
692         else:
693             self.assertEqual(result, value,
694                              'values not equal "%s" and "%s"'
695                                  % (str(result), str(value)))
696
697     def assert_no_active_block_jobs(self):
698         result = self.vm.qmp('query-block-jobs')
699         self.assert_qmp(result, 'return', [])
700
701     def assert_has_block_node(self, node_name=None, file_name=None):
702         """Issue a query-named-block-nodes and assert node_name and/or
703         file_name is present in the result"""
704         def check_equal_or_none(a, b):
705             return a == None or b == None or a == b
706         assert node_name or file_name
707         result = self.vm.qmp('query-named-block-nodes')
708         for x in result["return"]:
709             if check_equal_or_none(x.get("node-name"), node_name) and \
710                     check_equal_or_none(x.get("file"), file_name):
711                 return
712         self.assertTrue(False, "Cannot find %s %s in result:\n%s" % \
713                 (node_name, file_name, result))
714
715     def assert_json_filename_equal(self, json_filename, reference):
716         '''Asserts that the given filename is a json: filename and that its
717            content is equal to the given reference object'''
718         self.assertEqual(json_filename[:5], 'json:')
719         self.assertEqual(self.vm.flatten_qmp_object(json.loads(json_filename[5:])),
720                          self.vm.flatten_qmp_object(reference))
721
722     def cancel_and_wait(self, drive='drive0', force=False, resume=False, wait=60.0):
723         '''Cancel a block job and wait for it to finish, returning the event'''
724         result = self.vm.qmp('block-job-cancel', device=drive, force=force)
725         self.assert_qmp(result, 'return', {})
726
727         if resume:
728             self.vm.resume_drive(drive)
729
730         cancelled = False
731         result = None
732         while not cancelled:
733             for event in self.vm.get_qmp_events(wait=wait):
734                 if event['event'] == 'BLOCK_JOB_COMPLETED' or \
735                    event['event'] == 'BLOCK_JOB_CANCELLED':
736                     self.assert_qmp(event, 'data/device', drive)
737                     result = event
738                     cancelled = True
739                 elif event['event'] == 'JOB_STATUS_CHANGE':
740                     self.assert_qmp(event, 'data/id', drive)
741
742
743         self.assert_no_active_block_jobs()
744         return result
745
746     def wait_until_completed(self, drive='drive0', check_offset=True, wait=60.0):
747         '''Wait for a block job to finish, returning the event'''
748         while True:
749             for event in self.vm.get_qmp_events(wait=wait):
750                 if event['event'] == 'BLOCK_JOB_COMPLETED':
751                     self.assert_qmp(event, 'data/device', drive)
752                     self.assert_qmp_absent(event, 'data/error')
753                     if check_offset:
754                         self.assert_qmp(event, 'data/offset', event['data']['len'])
755                     self.assert_no_active_block_jobs()
756                     return event
757                 elif event['event'] == 'JOB_STATUS_CHANGE':
758                     self.assert_qmp(event, 'data/id', drive)
759
760     def wait_ready(self, drive='drive0'):
761         '''Wait until a block job BLOCK_JOB_READY event'''
762         f = {'data': {'type': 'mirror', 'device': drive } }
763         event = self.vm.event_wait(name='BLOCK_JOB_READY', match=f)
764
765     def wait_ready_and_cancel(self, drive='drive0'):
766         self.wait_ready(drive=drive)
767         event = self.cancel_and_wait(drive=drive)
768         self.assertEqual(event['event'], 'BLOCK_JOB_COMPLETED')
769         self.assert_qmp(event, 'data/type', 'mirror')
770         self.assert_qmp(event, 'data/offset', event['data']['len'])
771
772     def complete_and_wait(self, drive='drive0', wait_ready=True):
773         '''Complete a block job and wait for it to finish'''
774         if wait_ready:
775             self.wait_ready(drive=drive)
776
777         result = self.vm.qmp('block-job-complete', device=drive)
778         self.assert_qmp(result, 'return', {})
779
780         event = self.wait_until_completed(drive=drive)
781         self.assert_qmp(event, 'data/type', 'mirror')
782
783     def pause_wait(self, job_id='job0'):
784         with Timeout(1, "Timeout waiting for job to pause"):
785             while True:
786                 result = self.vm.qmp('query-block-jobs')
787                 found = False
788                 for job in result['return']:
789                     if job['device'] == job_id:
790                         found = True
791                         if job['paused'] == True and job['busy'] == False:
792                             return job
793                         break
794                 assert found
795
796     def pause_job(self, job_id='job0', wait=True):
797         result = self.vm.qmp('block-job-pause', device=job_id)
798         self.assert_qmp(result, 'return', {})
799         if wait:
800             return self.pause_wait(job_id)
801         return result
802
803
804 def notrun(reason):
805     '''Skip this test suite'''
806     # Each test in qemu-iotests has a number ("seq")
807     seq = os.path.basename(sys.argv[0])
808
809     open('%s/%s.notrun' % (output_dir, seq), 'w').write(reason + '\n')
810     print('%s not run: %s' % (seq, reason))
811     sys.exit(0)
812
813 def case_notrun(reason):
814     '''Skip this test case'''
815     # Each test in qemu-iotests has a number ("seq")
816     seq = os.path.basename(sys.argv[0])
817
818     open('%s/%s.casenotrun' % (output_dir, seq), 'a').write(
819         '    [case not run] ' + reason + '\n')
820
821 def verify_image_format(supported_fmts=[], unsupported_fmts=[]):
822     assert not (supported_fmts and unsupported_fmts)
823
824     if 'generic' in supported_fmts and \
825             os.environ.get('IMGFMT_GENERIC', 'true') == 'true':
826         # similar to
827         #   _supported_fmt generic
828         # for bash tests
829         return
830
831     not_sup = supported_fmts and (imgfmt not in supported_fmts)
832     if not_sup or (imgfmt in unsupported_fmts):
833         notrun('not suitable for this image format: %s' % imgfmt)
834
835 def verify_protocol(supported=[], unsupported=[]):
836     assert not (supported and unsupported)
837
838     if 'generic' in supported:
839         return
840
841     not_sup = supported and (imgproto not in supported)
842     if not_sup or (imgproto in unsupported):
843         notrun('not suitable for this protocol: %s' % imgproto)
844
845 def verify_platform(supported_oses=['linux']):
846     if True not in [sys.platform.startswith(x) for x in supported_oses]:
847         notrun('not suitable for this OS: %s' % sys.platform)
848
849 def verify_cache_mode(supported_cache_modes=[]):
850     if supported_cache_modes and (cachemode not in supported_cache_modes):
851         notrun('not suitable for this cache mode: %s' % cachemode)
852
853 def supports_quorum():
854     return 'quorum' in qemu_img_pipe('--help')
855
856 def verify_quorum():
857     '''Skip test suite if quorum support is not available'''
858     if not supports_quorum():
859         notrun('quorum support missing')
860
861 def qemu_pipe(*args):
862     '''Run qemu with an option to print something and exit (e.g. a help option),
863     and return its output'''
864     args = [qemu_prog] + qemu_opts + list(args)
865     subp = subprocess.Popen(args, stdout=subprocess.PIPE,
866                             stderr=subprocess.STDOUT,
867                             universal_newlines=True)
868     exitcode = subp.wait()
869     if exitcode < 0:
870         sys.stderr.write('qemu received signal %i: %s\n' % (-exitcode,
871                          ' '.join(args)))
872     return subp.communicate()[0]
873
874 def supported_formats(read_only=False):
875     '''Set 'read_only' to True to check ro-whitelist
876        Otherwise, rw-whitelist is checked'''
877     format_message = qemu_pipe("-drive", "format=help")
878     line = 1 if read_only else 0
879     return format_message.splitlines()[line].split(":")[1].split()
880
881 def skip_if_unsupported(required_formats=[], read_only=False):
882     '''Skip Test Decorator
883        Runs the test if all the required formats are whitelisted'''
884     def skip_test_decorator(func):
885         def func_wrapper(*args, **kwargs):
886             usf_list = list(set(required_formats) -
887                             set(supported_formats(read_only)))
888             if usf_list:
889                 case_notrun('{}: formats {} are not whitelisted'.format(
890                     args[0], usf_list))
891             else:
892                 return func(*args, **kwargs)
893         return func_wrapper
894     return skip_test_decorator
895
896 def execute_unittest(output, verbosity, debug):
897     runner = unittest.TextTestRunner(stream=output, descriptions=True,
898                                      verbosity=verbosity)
899     try:
900         # unittest.main() will use sys.exit(); so expect a SystemExit
901         # exception
902         unittest.main(testRunner=runner)
903     finally:
904         if not debug:
905             sys.stderr.write(re.sub(r'Ran (\d+) tests? in [\d.]+s',
906                                     r'Ran \1 tests', output.getvalue()))
907
908 def execute_test(test_function=None,
909                  supported_fmts=[], supported_oses=['linux'],
910                  supported_cache_modes=[], unsupported_fmts=[],
911                  supported_protocols=[], unsupported_protocols=[]):
912     """Run either unittest or script-style tests."""
913
914     # We are using TEST_DIR and QEMU_DEFAULT_MACHINE as proxies to
915     # indicate that we're not being run via "check". There may be
916     # other things set up by "check" that individual test cases rely
917     # on.
918     if test_dir is None or qemu_default_machine is None:
919         sys.stderr.write('Please run this test via the "check" script\n')
920         sys.exit(os.EX_USAGE)
921
922     debug = '-d' in sys.argv
923     verbosity = 1
924     verify_image_format(supported_fmts, unsupported_fmts)
925     verify_protocol(supported_protocols, unsupported_protocols)
926     verify_platform(supported_oses)
927     verify_cache_mode(supported_cache_modes)
928
929     if debug:
930         output = sys.stdout
931         verbosity = 2
932         sys.argv.remove('-d')
933     else:
934         # We need to filter out the time taken from the output so that
935         # qemu-iotest can reliably diff the results against master output.
936         output = io.StringIO()
937
938     logging.basicConfig(level=(logging.DEBUG if debug else logging.WARN))
939
940     if not test_function:
941         execute_unittest(output, verbosity, debug)
942     else:
943         test_function()
944
945 def script_main(test_function, *args, **kwargs):
946     """Run script-style tests outside of the unittest framework"""
947     execute_test(test_function, *args, **kwargs)
948
949 def main(*args, **kwargs):
950     """Run tests using the unittest framework"""
951     execute_test(None, *args, **kwargs)
This page took 0.077817 seconds and 4 git commands to generate.