multi_disk_random_hotplug.py 19.5 KB
Newer Older
1 2 3 4 5 6 7
"""
multi_disk_random_hotplug test for Autotest framework.

:copyright: 2013 Red Hat Inc.
"""
import logging
import random
8
import time
9
import threading
10

11
from autotest.client.shared import error
12 13 14 15 16 17

from virttest import funcatexit
from virttest import data_dir
from virttest import qemu_qtree
from virttest import utils_test
from virttest import env_process
18
from virttest.qemu_devices import utils
19
from virttest.remote import LoginTimeoutError
20
from virttest.qemu_monitor import MonitorError
21 22


23 24 25 26
# qdev is not thread safe so in case of dangerous ops lock this thread
LOCK = None


27 28 29 30 31 32 33 34
def stop_stresser(vm, stop_cmd):
    """
    Wrapper which connects to vm and sends the stop_cmd
    :param vm: Virtual Machine
    :type vm: virttest.virt_vm.BaseVM
    :param stop_cmd: Command to stop the stresser
    :type stop_cmd: string
    """
35 36 37 38 39 40
    try:
        session = vm.wait_for_login(timeout=10)
        session.cmd(stop_cmd)
        session.close()
    except LoginTimeoutError:
        vm.destroy(gracefully=False)
41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74


# TODO: Remove this silly function when qdev vs. qtree comparison is available
def convert_params(params, args):
    """
    Updates params according to images_define_by_params arguments.
    :note: This is only temporarily solution until qtree vs. qdev verification
           is available.
    :param params: Dictionary with the test parameters
    :type param: virttest.utils_params.Params
    :param args: Dictionary of images_define_by_params arguments
    :type args: dictionary
    :return: Updated dictionary with the test parameters
    :rtype: virttest.utils_params.Params
    """
    convert = {'fmt': 'drive_format', 'cache': 'drive_cache',
               'werror': 'drive_werror', 'rerror': 'drive_rerror',
               'serial': 'drive_serial', 'snapshot': 'image_snapshot',
               'bus': 'drive_bus', 'unit': 'drive_unit', 'port': 'drive_port',
               'readonly': 'image_readonly', 'scsiid': 'drive_scsiid',
               'lun': 'drive_lun', 'aio': 'image_aio',
               'imgfmt': 'image_format', 'pci_addr': 'drive_pci_addr',
               'x_data_plane': 'x-data-plane',
               'scsi': 'virtio-blk-pci_scsi'}
    name = args.pop('name')
    params['images'] += " %s" % name
    params['image_name_%s' % name] = args.pop('filename')
    params['image_raw_device_%s' % name] = 'yes'
    for key, value in args.iteritems():
        params["%s_%s" % (convert.get(key, key), name)] = value
    return params


@error.context_aware
75
def run(test, params, env):
76 77 78 79 80
    """
    This tests the disk hotplug/unplug functionality.
    1) prepares multiple disks to be hotplugged
    2) hotplugs them
    3) verifies that they are in qtree/guest system/...
X
Xu Tian 已提交
81 82 83 84 85
    4) stop I/O stress_cmd
    5) unplugs them
    6) continue I/O stress_cmd
    7) verifies they are not in qtree/guest system/...
    8) repeats $repeat_times
86 87 88 89 90

    :param test: QEMU test object
    :param params: Dictionary with the test parameters
    :param env: Dictionary with test environment.
    """
91
    def verify_qtree(params, info_qtree, info_block, qdev):
92 93 94 95 96 97 98 99
        """
        Verifies that params, info qtree, info block and /proc/scsi/ matches
        :param params: Dictionary with the test parameters
        :type params: virttest.utils_params.Params
        :param info_qtree: Output of "info qtree" monitor command
        :type info_qtree: string
        :param info_block: Output of "info block" monitor command
        :type info_block: dict of dicts
100 101
        :param qdev: qcontainer representation
        :type qdev: virttest.qemu_devices.qcontainer.DevContainer
102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117
        """
        err = 0
        qtree = qemu_qtree.QtreeContainer()
        qtree.parse_info_qtree(info_qtree)
        disks = qemu_qtree.QtreeDisksContainer(qtree.get_nodes())
        (tmp1, tmp2) = disks.parse_info_block(info_block)
        err += tmp1 + tmp2
        err += disks.generate_params()
        err += disks.check_disk_params(params)
        if err:
            logging.error("info qtree:\n%s", info_qtree)
            logging.error("info block:\n%s", info_block)
            logging.error(qdev.str_bus_long())
            raise error.TestFail("%s errors occurred while verifying"
                                 " qtree vs. params" % err)

118
    def insert_into_qdev(qdev, param_matrix, no_disks, params, new_devices):
119 120 121
        """
        Inserts no_disks disks int qdev using randomized args from param_matrix
        :param qdev: qemu devices container
122
        :type qdev: virttest.qemu_devices.qcontainer.DevContainer
123 124 125 126 127 128 129 130 131
        :param param_matrix: Matrix of randomizable params
        :type param_matrix: list of lists
        :param no_disks: Desired number of disks
        :type no_disks: integer
        :param params: Dictionary with the test parameters
        :type params: virttest.utils_params.Params
        :return: (newly added devices, number of added disks)
        :rtype: tuple(list, integer)
        """
132
        dev_idx = 0
133
        _new_devs_fmt = ""
134
        pci_bus = {'aobject': 'pci.0'}
135 136
        _formats = param_matrix.pop('fmt', [params.get('drive_format')])
        formats = _formats[:]
137 138 139 140
        if len(new_devices) == 1:
            strict_mode = None
        else:
            strict_mode = True
141 142 143 144
        i = 0
        while i < no_disks:
            # Set the format
            if len(formats) < 1:
145
                if i == 0:
146 147
                    raise error.TestError("Fail to add any disks, probably bad"
                                          " configuration.")
148 149 150 151 152
                logging.warn("Can't create desired number '%s' of disk types "
                             "'%s'. Using '%d' no disks.", no_disks,
                             _formats, i)
                break
            name = 'stg%d' % i
153
            args = {'name': name, 'filename': stg_image_name % i, 'pci_bus': pci_bus}
154 155 156 157 158 159 160
            fmt = random.choice(formats)
            if fmt == 'virtio_scsi':
                args['fmt'] = 'scsi-hd'
                args['scsi_hba'] = 'virtio-scsi-pci'
            elif fmt == 'lsi_scsi':
                args['fmt'] = 'scsi-hd'
                args['scsi_hba'] = 'lsi53c895a'
161 162 163
            elif fmt == 'spapr_vscsi':
                args['fmt'] = 'scsi-hd'
                args['scsi_hba'] = 'spapr-vscsi'
164 165 166 167 168 169 170
            else:
                args['fmt'] = fmt
            # Other params
            for key, value in param_matrix.iteritems():
                args[key] = random.choice(value)

            try:
171
                devs = qdev.images_define_by_variables(**args)
172 173
                # parallel test adds devices in mixed order, force bus/addrs
                qdev.insert(devs, strict_mode)
174
            except utils.DeviceError:
175 176 177 178 179 180 181 182 183
                for dev in devs:
                    if dev in qdev:
                        qdev.remove(dev, recursive=True)
                formats.remove(fmt)
                continue

            params = convert_params(params, args)
            env_process.preprocess_image(test, params.object_params(name),
                                         name)
184 185
            new_devices[dev_idx].extend(devs)
            dev_idx = (dev_idx + 1) % len(new_devices)
186 187 188
            _new_devs_fmt += "%s(%s) " % (name, fmt)
            i += 1
        if _new_devs_fmt:
189
            logging.info("Using disks: %s", _new_devs_fmt[:-1])
190 191 192
        param_matrix['fmt'] = _formats
        return new_devices, params

193
    def _hotplug(new_devices, monitor, prefix=""):
194 195 196
        """
        Do the actual hotplug of the new_devices using monitor monitor.
        :param new_devices: List of devices which should be hotplugged
197
        :type new_devices: List of virttest.qemu_devices.qdevice.QBaseDevice
198 199 200
        :param monitor: Monitor which should be used for hotplug
        :type monitor: virttest.qemu_monitor.Monitor
        """
201 202 203 204 205 206
        hotplug_outputs = []
        hotplug_sleep = float(params.get('wait_between_hotplugs', 0))
        for device in new_devices:      # Hotplug all devices
            time.sleep(hotplug_sleep)
            hotplug_outputs.append(device.hotplug(monitor))
        time.sleep(hotplug_sleep)
207 208 209
        failed = []
        passed = []
        unverif = []
210 211
        for device in new_devices:      # Verify the hotplug status
            out = hotplug_outputs.pop(0)
212
            out = device.verify_hotplug(out, monitor)
213
            if out is True:
214
                passed.append(str(device))
215
            elif out is False:
216
                failed.append(str(device))
217
            else:
218 219 220 221 222
                unverif.append(str(device))
        if not failed and not unverif:
            logging.debug("%sAll hotplugs verified (%s)", prefix, len(passed))
        elif not failed:
            logging.warn("%sHotplug status:\nverified %s\nunverified %s",
223
                         prefix, passed, unverif)
224
        else:
225 226 227
            logging.error("%sHotplug status:\nverified %s\nunverified %s\n"
                          "failed %s", prefix, passed, unverif, failed)
            logging.error("qtree:\n%s", monitor.info("qtree", debug=False))
W
Wei,Jiangang 已提交
228
            raise error.TestFail("%sHotplug of some devices failed." % prefix)
229

230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258
    def hotplug_serial(new_devices, monitor):
        _hotplug(new_devices[0], monitor)

    def hotplug_parallel(new_devices, monitors):
        threads = []
        for i in xrange(len(new_devices)):
            name = "Th%s: " % i
            logging.debug("%sworks with %s devices", name,
                          [_.str_short() for _ in new_devices[i]])
            thread = threading.Thread(target=_hotplug, name=name[:-2],
                                      args=(new_devices[i], monitors[i], name))
            thread.start()
            threads.append(thread)
        for thread in threads:
            thread.join()
        logging.debug("All threads finished.")

    def _postprocess_images():
        # remove and check the images
        _disks = []
        for disk in params['images'].split(' '):
            if disk.startswith("stg"):
                env_process.postprocess_image(test, params.object_params(disk),
                                              disk)
            else:
                _disks.append(disk)
            params['images'] = " ".join(_disks)

    def _unplug(new_devices, qdev, monitor, prefix=""):
259 260 261
        """
        Do the actual unplug of new_devices using monitor monitor
        :param new_devices: List of devices which should be hotplugged
262
        :type new_devices: List of virttest.qemu_devices.qdevice.QBaseDevice
263
        :param qdev: qemu devices container
264
        :type qdev: virttest.qemu_devices.qcontainer.DevContainer
265 266 267
        :param monitor: Monitor which should be used for hotplug
        :type monitor: virttest.qemu_monitor.Monitor
        """
268 269 270 271 272 273 274
        unplug_sleep = float(params.get('wait_between_unplugs', 0))
        unplug_outs = []
        unplug_devs = []
        for device in new_devices[::-1]:    # unplug all devices
            if device in qdev:  # Some devices are removed with previous one
                time.sleep(unplug_sleep)
                unplug_devs.append(device)
275 276 277 278 279 280 281 282 283 284 285 286 287 288
                try:
                    output = device.unplug(monitor)
                except MonitorError:
                    # In new versions of qemu, to unplug a disk, cmd
                    # '__com.redhat_drive_del' is not necessary; while it's
                    # necessary in old qemu verisons. Following update is to
                    # pass the error caused by using the cmd in new
                    # qemu versions.
                    if device.get_qid() not in monitor.info("block",
                                                            debug=False):
                        pass
                    else:
                        raise
                unplug_outs.append(output)
289 290 291 292
                # Remove from qdev even when unplug failed because further in
                # this test we compare VM with qdev, which should be without
                # these devices. We can do this because we already set the VM
                # as dirty.
293 294
                if LOCK:
                    LOCK.acquire()
295
                qdev.remove(device)
296 297
                if LOCK:
                    LOCK.release()
298
        time.sleep(unplug_sleep)
299 300 301
        failed = []
        passed = []
        unverif = []
302 303 304 305 306 307 308 309 310
        for device in unplug_devs:          # Verify unplugs
            _out = unplug_outs.pop(0)
            # unplug effect can be delayed as it waits for OS respone before
            # it removes the device form qtree
            for _ in xrange(50):
                out = device.verify_unplug(_out, monitor)
                if out is True:
                    break
                time.sleep(0.1)
311
            if out is True:
312
                passed.append(str(device))
313
            elif out is False:
314
                failed.append(str(device))
315
            else:
316
                unverif.append(str(device))
317

318 319 320 321 322
        if not failed and not unverif:
            logging.debug("%sAll unplugs verified (%s)", prefix, len(passed))
        elif not failed:
            logging.warn("%sUnplug status:\nverified %s\nunverified %s",
                         prefix, passed, unverif)
323
        else:
324 325 326
            logging.error("%sUnplug status:\nverified %s\nunverified %s\n"
                          "failed %s", prefix, passed, unverif, failed)
            logging.error("qtree:\n%s", monitor.info("qtree", debug=False))
W
Wei,Jiangang 已提交
327
            raise error.TestFail("%sUnplug of some devices failed." % prefix)
328

329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345
    def unplug_serial(new_devices, qdev, monitor):
        _unplug(new_devices[0], qdev, monitor)

    def unplug_parallel(new_devices, qdev, monitors):
        threads = []
        for i in xrange(len(new_devices)):
            name = "Th%s: " % i
            logging.debug("%sworks with %s devices", name,
                          [_.str_short() for _ in new_devices[i]])
            thread = threading.Thread(target=_unplug,
                                      args=(new_devices[i], qdev, monitors[i]))
            thread.start()
            threads.append(thread)
        for thread in threads:
            thread.join()
        logging.debug("All threads finished.")

346
    def verify_qtree_unsupported(params, info_qtree, info_block, qdev):
347 348 349
        return logging.warn("info qtree not supported. Can't verify qtree vs. "
                            "guest disks.")

350 351 352 353 354
    vm = env.get_vm(params['main_vm'])
    qdev = vm.devices
    session = vm.wait_for_login(timeout=int(params.get("login_timeout", 360)))
    out = vm.monitor.human_monitor_cmd("info qtree", debug=False)
    if "unknown command" in str(out):
355
        verify_qtree = verify_qtree_unsupported
356 357

    stg_image_name = params['stg_image_name']
358 359
    if not stg_image_name[0] == "/":
        stg_image_name = "%s/%s" % (data_dir.get_data_dir(), stg_image_name)
360 361 362 363 364 365 366 367 368
    stg_image_num = int(params['stg_image_num'])
    stg_params = params.get('stg_params', '').split(' ')
    i = 0
    while i < len(stg_params) - 1:
        if not stg_params[i].strip():
            i += 1
            continue
        if stg_params[i][-1] == '\\':
            stg_params[i] = '%s %s' % (stg_params[i][:-1],
L
Lucas Meneghel Rodrigues 已提交
369
                                       stg_params.pop(i + 1))
370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400
        i += 1

    param_matrix = {}
    for i in xrange(len(stg_params)):
        if not stg_params[i].strip():
            continue
        (cmd, parm) = stg_params[i].split(':', 1)
        # ',' separated list of values
        parm = parm.split(',')
        j = 0
        while j < len(parm) - 1:
            if parm[j][-1] == '\\':
                parm[j] = '%s,%s' % (parm[j][:-1], parm.pop(j + 1))
            j += 1

        param_matrix[cmd] = parm

    # Modprobe the module if specified in config file
    module = params.get("modprobe_module")
    if module:
        session.cmd("modprobe %s" % module)

    stress_cmd = params.get('stress_cmd')
    if stress_cmd:
        funcatexit.register(env, params.get('type'), stop_stresser, vm,
                            params.get('stress_kill_cmd'))
        stress_session = vm.wait_for_login(timeout=10)
        for _ in xrange(int(params.get('no_stress_cmds', 1))):
            stress_session.sendline(stress_cmd)

    rp_times = int(params.get("repeat_times", 1))
401 402 403 404 405 406 407 408 409 410 411 412 413
    queues = params.get("multi_disk_type") == "parallel"
    if queues:  # parallel
        queues = xrange(len(vm.monitors))
        hotplug = hotplug_parallel
        unplug = unplug_parallel
        monitor = vm.monitors
        global LOCK
        LOCK = threading.Lock()
    else:   # serial
        queues = xrange(1)
        hotplug = hotplug_serial
        unplug = unplug_serial
        monitor = vm.monitor
414
    context_msg = "Running sub test '%s' %s"
415
    error.context("Verify disk before test", logging.info)
416 417
    info_qtree = vm.monitor.info('qtree', False)
    info_block = vm.monitor.info_block(False)
418
    verify_qtree(params, info_qtree, info_block, qdev)
419
    for iteration in xrange(rp_times):
420 421
        error.context("Hotplugging/unplugging devices, iteration %d"
                      % iteration, logging.info)
422 423 424 425 426 427
        sub_type = params.get("sub_type_before_plug")
        if sub_type:
            error.context(context_msg % (sub_type, "before hotplug"),
                          logging.info)
            utils_test.run_virt_sub_test(test, params, env, sub_type)

428
        error.context("Insert devices into qdev", logging.debug)
429
        qdev.set_dirty()
430
        new_devices = [[] for _ in queues]
431
        new_devices, params = insert_into_qdev(qdev, param_matrix,
432 433 434 435 436
                                               stg_image_num, params,
                                               new_devices)

        error.context("Hotplug the devices", logging.debug)
        hotplug(new_devices, monitor)
437
        time.sleep(float(params.get('wait_after_hotplug', 0)))
438

439
        error.context("Verify disks after hotplug", logging.debug)
440 441
        info_qtree = vm.monitor.info('qtree', False)
        info_block = vm.monitor.info_block(False)
442
        vm.verify_alive()
443
        verify_qtree(params, info_qtree, info_block, qdev)
444 445 446 447 448 449 450 451 452 453 454 455 456
        qdev.set_clean()

        sub_type = params.get("sub_type_after_plug")
        if sub_type:
            error.context(context_msg % (sub_type, "after hotplug"),
                          logging.info)
            utils_test.run_virt_sub_test(test, params, env, sub_type)

        sub_type = params.get("sub_type_before_unplug")
        if sub_type:
            error.context(context_msg % (sub_type, "before hotunplug"),
                          logging.info)
            utils_test.run_virt_sub_test(test, params, env, sub_type)
457 458

        error.context("Unplug and remove the devices", logging.debug)
X
Xu Tian 已提交
459 460
        if stress_cmd:
            session.cmd(params["stress_stop_cmd"])
461
        unplug(new_devices, qdev, monitor)
X
Xu Tian 已提交
462 463
        if stress_cmd:
            session.cmd(params["stress_cont_cmd"])
464 465
        _postprocess_images()

466
        error.context("Verify disks after unplug", logging.debug)
467
        time.sleep(float(params.get('wait_after_unplug', 0)))
468 469
        info_qtree = vm.monitor.info('qtree', False)
        info_block = vm.monitor.info_block(False)
470
        vm.verify_alive()
471
        verify_qtree(params, info_qtree, info_block, qdev)
472 473 474 475 476 477 478 479 480 481 482
        # we verified the unplugs, set the state to 0
        for _ in xrange(qdev.get_state()):
            qdev.set_clean()

        sub_type = params.get("sub_type_after_unplug")
        if sub_type:
            error.context(context_msg % (sub_type, "after hotunplug"),
                          logging.info)
            utils_test.run_virt_sub_test(test, params, env, sub_type)

    # Check for various KVM failures
483 484
    error.context("Validating VM after all disk hotplug/unplugs",
                  logging.debug)
485
    vm.verify_alive()
486 487 488 489 490
    out = session.cmd_output('dmesg')
    if "I/O error" in out:
        logging.warn(out)
        raise error.TestWarn("I/O error messages occured in dmesg, check"
                             "the log for details.")