qemu_img.py 21.2 KB
Newer Older
L
Lucas Meneghel Rodrigues 已提交
1 2 3 4
import re
import os
import logging
import commands
5
from autotest.client.shared import utils, error
6
from virttest import utils_misc, env_process, storage, data_dir
7 8


9
@error.context_aware
10
def run(test, params, env):
11 12 13 14 15
    """
    'qemu-img' functions test:
    1) Judge what subcommand is going to be tested
    2) Run subcommand test

L
Lucas Meneghel Rodrigues 已提交
16 17 18
    :param test: QEMU test object
    :param params: Dictionary with the test parameters
    :param env: Dictionary with test environment.
19
    """
20 21
    qemu_img_binary = utils_misc.get_qemu_img_binary(params)
    cmd = qemu_img_binary
22 23
    if not os.path.exists(cmd):
        raise error.TestError("Binary of 'qemu-img' not found")
24
    image_format = params["image_format"]
25
    image_size = params.get("image_size", "10G")
26
    image_name = storage.get_image_filename(params, data_dir.get_data_dir())
27 28 29 30 31

    def _check(cmd, img):
        """
        Simple 'qemu-img check' function implementation.

L
Lucas Meneghel Rodrigues 已提交
32 33
        :param cmd: qemu-img base command.
        :param img: image to be checked
34 35
        """
        cmd += " check %s" % img
36 37
        error.context("Checking image '%s' by command '%s'" % (img, cmd),
                      logging.info)
38
        try:
39 40 41
            output = utils.system_output(cmd, verbose=False)
        except error.CmdError, err:
            if "does not support checks" in str(err):
42 43
                return (True, "")
            else:
44
                return (False, str(err))
45 46 47 48 49 50 51 52 53
        return (True, output)

    def check_test(cmd):
        """
        Subcommand 'qemu-img check' test.

        This tests will 'dd' to create a specified size file, and check it.
        Then convert it to supported image_format in each loop and check again.

L
Lucas Meneghel Rodrigues 已提交
54
        :param cmd: qemu-img base command.
55
        """
56
        test_image = utils_misc.get_path(data_dir.get_data_dir(),
57 58
                                         params["image_name_dd"])
        create_image_cmd = params["create_image_cmd"]
59
        create_image_cmd = create_image_cmd % test_image
60 61 62 63 64
        msg = " Create image %s by command %s" % (test_image, create_image_cmd)
        error.context(msg, logging.info)
        utils.system(create_image_cmd, verbose=False)
        status, output = _check(cmd, test_image)
        if not status:
65
            raise error.TestFail("Check image '%s' failed with error: %s" %
L
Lucas Meneghel Rodrigues 已提交
66
                                (test_image, output))
67
        for fmt in params["supported_image_formats"].split():
68 69
            output_image = test_image + ".%s" % fmt
            _convert(cmd, fmt, test_image, output_image)
70 71
            status, output = _check(cmd, output_image)
            if not status:
72
                raise error.TestFail("Check image '%s' got error: %s" %
L
Lucas Meneghel Rodrigues 已提交
73
                                    (output_image, output))
74 75 76 77
            os.remove(output_image)
        os.remove(test_image)

    def _create(cmd, img_name, fmt, img_size=None, base_img=None,
78
                base_img_fmt=None, encrypted="no",
79
                preallocated="off", cluster_size=None):
80 81 82
        """
        Simple wrapper of 'qemu-img create'

L
Lucas Meneghel Rodrigues 已提交
83 84 85 86 87 88 89
        :param cmd: qemu-img base command.
        :param img_name: name of the image file
        :param fmt: image format
        :param img_size:  image size
        :param base_img: base image if create a snapshot image
        :param base_img_fmt: base image format if create a snapshot image
        :param encrypted: indicates whether the created image is encrypted
90 91 92 93 94 95 96 97 98 99 100 101
        """
        cmd += " create"
        if encrypted == "yes":
            cmd += " -e"
        if base_img:
            cmd += " -b %s" % base_img
            if base_img_fmt:
                cmd += " -F %s" % base_img_fmt
        cmd += " -f %s" % fmt
        cmd += " %s" % img_name
        if img_size:
            cmd += " %s" % img_size
102
        if preallocated == "metadata":
103 104 105 106 107 108 109 110 111 112
            cmd += " -o preallocation=metadata"
        if cluster_size is not None:
            cmd += " -o cluster_size=%s" % cluster_size
        msg = "Creating image %s by command %s" % (img_name, cmd)
        error.context(msg, logging.info)
        utils.system(cmd, verbose=False)
        status, out = _check(qemu_img_binary, img_name)
        if not status:
            raise error.TestFail("Check image '%s' got error: %s" %
                                 (img_name, out))
113 114 115 116 117

    def create_test(cmd):
        """
        Subcommand 'qemu-img create' test.

L
Lucas Meneghel Rodrigues 已提交
118
        :param cmd: qemu-img base command.
119
        """
120
        image_large = params["image_name_large"]
121 122 123 124 125 126
        device = params.get("device")
        if not device:
            img = utils_misc.get_path(data_dir.get_data_dir(), image_large)
            img += '.' + image_format
        else:
            img = device
127
        _create(cmd, img_name=img, fmt=image_format,
128
                img_size=params["image_size_large"],
129
                preallocated=params.get("preallocated", "off"))
130 131 132
        os.remove(img)

    def _convert(cmd, output_fmt, img_name, output_filename,
L
Lucas Meneghel Rodrigues 已提交
133
                 fmt=None, compressed="no", encrypted="no"):
134 135 136
        """
        Simple wrapper of 'qemu-img convert' function.

L
Lucas Meneghel Rodrigues 已提交
137 138 139 140 141 142 143
        :param cmd: qemu-img base command.
        :param output_fmt: the output format of converted image
        :param img_name: image name that to be converted
        :param output_filename: output image name that converted
        :param fmt: output image format
        :param compressed: whether output image is compressed
        :param encrypted: whether output image is encrypted
144 145 146 147 148 149 150 151 152
        """
        cmd += " convert"
        if compressed == "yes":
            cmd += " -c"
        if encrypted == "yes":
            cmd += " -e"
        if fmt:
            cmd += " -f %s" % fmt
        cmd += " -O %s" % output_fmt
153 154 155 156 157 158 159 160
        options = params.get("qemu_img_options")
        if options:
            options = options.split()
            cmd += " -o "
            for option in options:
                value = params.get(option)
                cmd += "%s=%s," % (option, value)
            cmd = cmd.rstrip(",")
161
        cmd += " %s %s" % (img_name, output_filename)
162 163 164
        msg = "Converting '%s' from format '%s'" % (img_name, fmt)
        msg += " to '%s'" % output_fmt
        error.context(msg, logging.info)
165 166 167 168 169 170
        utils.system(cmd)

    def convert_test(cmd):
        """
        Subcommand 'qemu-img convert' test.

L
Lucas Meneghel Rodrigues 已提交
171
        :param cmd: qemu-img base command.
172
        """
173
        dest_img_fmt = params["dest_image_format"]
174 175
        output_filename = "%s.converted_%s.%s" % (image_name,
                                                  dest_img_fmt, dest_img_fmt)
176 177

        _convert(cmd, dest_img_fmt, image_name, output_filename,
L
Lucas Meneghel Rodrigues 已提交
178
                 image_format, params["compressed"], params["encrypted"])
179 180 181 182
        orig_img_name = params.get("image_name")
        img_name = "%s.%s.converted_%s" % (orig_img_name,
                                           image_format, dest_img_fmt)
        _boot(img_name, dest_img_fmt)
183 184

        if dest_img_fmt == "qcow2":
185 186
            status, output = _check(cmd, output_filename)
            if status:
187 188 189
                os.remove(output_filename)
            else:
                raise error.TestFail("Check image '%s' failed with error: %s" %
L
Lucas Meneghel Rodrigues 已提交
190
                                    (output_filename, output))
191 192 193 194 195 196 197
        else:
            os.remove(output_filename)

    def _info(cmd, img, sub_info=None, fmt=None):
        """
        Simple wrapper of 'qemu-img info'.

L
Lucas Meneghel Rodrigues 已提交
198 199 200 201
        :param cmd: qemu-img base command.
        :param img: image file
        :param sub_info: sub info, say 'backing file'
        :param fmt: image format
202 203 204 205 206 207 208 209
        """
        cmd += " info"
        if fmt:
            cmd += " -f %s" % fmt
        cmd += " %s" % img

        try:
            output = utils.system_output(cmd)
210 211
        except error.CmdError, err:
            logging.error("Get info of image '%s' failed: %s", img, str(err))
212 213 214 215 216 217 218 219 220 221 222 223 224 225 226
            return None

        if not sub_info:
            return output

        sub_info += ": (.*)"
        matches = re.findall(sub_info, output)
        if matches:
            return matches[0]
        return None

    def info_test(cmd):
        """
        Subcommand 'qemu-img info' test.

L
Lucas Meneghel Rodrigues 已提交
227
        :param cmd: qemu-img base command.
228 229 230 231 232 233 234 235 236 237 238 239 240 241
        """
        img_info = _info(cmd, image_name)
        logging.info("Info of image '%s':\n%s", image_name, img_info)
        if not image_format in img_info:
            raise error.TestFail("Got unexpected format of image '%s'"
                                 " in info test" % image_name)
        if not image_size in img_info:
            raise error.TestFail("Got unexpected size of image '%s'"
                                 " in info test" % image_name)

    def snapshot_test(cmd):
        """
        Subcommand 'qemu-img snapshot' test.

L
Lucas Meneghel Rodrigues 已提交
242
        :param cmd: qemu-img base command.
243 244 245 246 247 248
        """
        cmd += " snapshot"
        for i in range(2):
            crtcmd = cmd
            sn_name = "snapshot%d" % i
            crtcmd += " -c %s %s" % (sn_name, image_name)
249
            msg = "Created snapshot '%s' in '%s' by command %s" % (sn_name,
L
Lucas Meneghel Rodrigues 已提交
250
                                                                   image_name, crtcmd)
251 252 253
            error.context(msg, logging.info)
            status, output = commands.getstatusoutput(crtcmd)
            if status != 0:
254
                raise error.TestFail("Create snapshot failed via command: %s;"
255
                                     "Output is: %s" % (crtcmd, output))
256 257
        listcmd = cmd
        listcmd += " -l %s" % image_name
258 259
        status, out = commands.getstatusoutput(listcmd)
        if not ("snapshot0" in out and "snapshot1" in out and status == 0):
260
            raise error.TestFail("Snapshot created failed or missed;"
261
                                 "snapshot list is: \n%s" % out)
262 263 264 265
        for i in range(2):
            sn_name = "snapshot%d" % i
            delcmd = cmd
            delcmd += " -d %s %s" % (sn_name, image_name)
266 267 268 269
            msg = "Delete snapshot '%s' by command %s" % (sn_name, delcmd)
            error.context(msg, logging.info)
            status, output = commands.getstatusoutput(delcmd)
            if status != 0:
270
                raise error.TestFail("Delete snapshot '%s' failed: %s" %
L
Lucas Meneghel Rodrigues 已提交
271
                                    (sn_name, output))
272 273 274 275 276 277 278 279 280 281 282 283 284 285

    def commit_test(cmd):
        """
        Subcommand 'qemu-img commit' test.
        1) Create a backing file of the qemu harddisk specified by image_name.
        2) Start a VM using the backing file as its harddisk.
        3) Touch a file "commit_testfile" in the backing_file, and shutdown the
           VM.
        4) Make sure touching the file does not affect the original harddisk.
        5) Commit the change to the original harddisk by executing
           "qemu-img commit" command.
        6) Start the VM using the original harddisk.
        7) Check if the file "commit_testfile" exists.

L
Lucas Meneghel Rodrigues 已提交
286
        :param cmd: qemu-img base command.
287 288 289 290
        """

        logging.info("Commit testing started!")
        image_name = params.get("image_name", "image")
291
        image_name = os.path.join(data_dir.get_data_dir(), image_name)
292 293
        image_format = params.get("image_format", "qcow2")
        backing_file_name = "%s_bak" % (image_name)
294 295 296 297 298 299 300
        file_create_cmd = params.get("file_create_cmd",
                                     "touch /commit_testfile")
        file_info_cmd = params.get("file_info_cmd",
                                   "ls / | grep commit_testfile")
        file_exist_chk_cmd = params.get("file_exist_chk_cmd",
                                        "[ -e /commit_testfile ] && echo $?")
        file_not_exist_chk_cmd = params.get("file_not_exist_chk_cmd",
L
Lucas Meneghel Rodrigues 已提交
301
                                            "[ ! -e /commit_testfile ] && echo $?")
302 303
        file_del_cmd = params.get("file_del_cmd",
                                  "rm -f /commit_testfile")
304 305 306 307 308 309 310
        try:
            # Remove the existing backing file
            backing_file = "%s.%s" % (backing_file_name, image_format)
            if os.path.isfile(backing_file):
                os.remove(backing_file)

            # Create the new backing file
311
            create_cmd = "%s create -b %s.%s -f %s %s.%s" % (cmd, image_name,
L
Lucas Meneghel Rodrigues 已提交
312 313
                                                             image_format,
                                                             image_format,
314
                                                             backing_file_name,
L
Lucas Meneghel Rodrigues 已提交
315
                                                             image_format)
316 317
            msg = "Create backing file by command: %s" % create_cmd
            error.context(msg, logging.info)
318
            try:
319 320
                utils.system(create_cmd, verbose=False)
            except error.CmdError:
321 322 323 324
                raise error.TestFail("Could not create a backing file!")
            logging.info("backing_file created!")

            # Set the qemu harddisk to the backing file
L
Lucas Meneghel Rodrigues 已提交
325 326
            logging.info(
                "Original image_name is: %s", params.get('image_name'))
327 328 329 330
            params['image_name'] = backing_file_name
            logging.info("Param image_name changed to: %s",
                         params.get('image_name'))

331 332
            msg = "Start a new VM, using backing file as its harddisk"
            error.context(msg, logging.info)
333
            vm_name = params['main_vm']
334 335
            env_process.preprocess_vm(test, params, env, vm_name)
            vm = env.get_vm(vm_name)
336
            vm.verify_alive()
337 338 339 340 341
            timeout = int(params.get("login_timeout", 360))
            session = vm.wait_for_login(timeout=timeout)

            # Do some changes to the backing_file harddisk
            try:
342 343 344 345 346
                output = session.cmd(file_create_cmd)
                logging.info("Output of %s: %s", file_create_cmd, output)
                output = session.cmd(file_info_cmd)
                logging.info("Output of %s: %s", file_info_cmd, output)
            except Exception, err:
347
                raise error.TestFail("Could not create commit_testfile in the "
348
                                     "backing file %s" % err)
349 350 351 352 353 354 355 356 357 358 359
            vm.destroy()

            # Make sure there is no effect on the original harddisk
            # First, set the harddisk back to the original one
            logging.info("Current image_name is: %s", params.get('image_name'))
            params['image_name'] = image_name
            logging.info("Param image_name reverted to: %s",
                         params.get('image_name'))

            # Second, Start a new VM, using image_name as its harddisk
            # Here, the commit_testfile should not exist
360
            vm_name = params['main_vm']
361 362
            env_process.preprocess_vm(test, params, env, vm_name)
            vm = env.get_vm(vm_name)
363
            vm.verify_alive()
364 365 366
            timeout = int(params.get("login_timeout", 360))
            session = vm.wait_for_login(timeout=timeout)
            try:
367
                output = session.cmd(file_not_exist_chk_cmd)
L
Lucas Meneghel Rodrigues 已提交
368 369
                logging.info(
                    "Output of %s: %s", file_not_exist_chk_cmd, output)
370
            except Exception:
371
                output = session.cmd(file_del_cmd)
372 373 374 375
                raise error.TestFail("The commit_testfile exists on the "
                                     "original file")
            vm.destroy()

L
Lucas Meneghel Rodrigues 已提交
376
            # Execute the commit command
377 378 379
            cmitcmd = "%s commit -f %s %s.%s" % (cmd, image_format,
                                                 backing_file_name,
                                                 image_format)
L
Lucas Meneghel Rodrigues 已提交
380
            error.context("Committing image by command %s" % cmitcmd,
381
                          logging.info)
382
            try:
383 384
                utils.system(cmitcmd, verbose=False)
            except error.CmdError:
385 386 387
                raise error.TestFail("Could not commit the backing file")

            # Start a new VM, using image_name as its harddisk
388
            vm_name = params['main_vm']
389 390
            env_process.preprocess_vm(test, params, env, vm_name)
            vm = env.get_vm(vm_name)
391
            vm.verify_alive()
392 393 394
            timeout = int(params.get("login_timeout", 360))
            session = vm.wait_for_login(timeout=timeout)
            try:
395 396 397
                output = session.cmd(file_exist_chk_cmd)
                logging.info("Output of %s: %s", file_exist_chk_cmd, output)
                session.cmd(file_del_cmd)
398 399 400 401 402 403 404 405 406 407 408 409 410 411
            except Exception:
                raise error.TestFail("Could not find commit_testfile after a "
                                     "commit")
            vm.destroy()

        finally:
            # Remove the backing file
            if os.path.isfile(backing_file):
                os.remove(backing_file)

    def _rebase(cmd, img_name, base_img, backing_fmt, mode="unsafe"):
        """
        Simple wrapper of 'qemu-img rebase'.

L
Lucas Meneghel Rodrigues 已提交
412 413 414 415 416
        :param cmd: qemu-img base command.
        :param img_name: image name to be rebased
        :param base_img: indicates the base image
        :param backing_fmt: the format of base image
        :param mode: rebase mode: safe mode, unsafe mode
417 418 419 420 421
        """
        cmd += " rebase"
        if mode == "unsafe":
            cmd += " -u"
        cmd += " -b %s -F %s %s" % (base_img, backing_fmt, img_name)
422
        msg = "Trying to rebase '%s' to '%s' by command %s" % (img_name,
L
Lucas Meneghel Rodrigues 已提交
423
                                                               base_img, cmd)
424 425 426
        error.context(msg, logging.info)
        status, output = commands.getstatusoutput(cmd)
        if status != 0:
427
            raise error.TestError("Failed to rebase '%s' to '%s': %s" %
L
Lucas Meneghel Rodrigues 已提交
428
                                 (img_name, base_img, output))
429 430 431 432 433 434 435 436 437 438

    def rebase_test(cmd):
        """
        Subcommand 'qemu-img rebase' test

        Change the backing file of a snapshot image in "unsafe mode":
        Assume the previous backing file had missed and we just have to change
        reference of snapshot to new one. After change the backing file of a
        snapshot image in unsafe mode, the snapshot should work still.

L
Lucas Meneghel Rodrigues 已提交
439
        :param cmd: qemu-img base command.
440 441 442 443 444 445
        """
        if not 'rebase' in utils.system_output(cmd + ' --help',
                                               ignore_status=True):
            raise error.TestNAError("Current kvm user space version does not"
                                    " support 'rebase' subcommand")
        sn_fmt = params.get("snapshot_format", "qcow2")
446
        sn1 = params["image_name_snapshot1"]
L
Lucas Meneghel Rodrigues 已提交
447 448
        sn1 = utils_misc.get_path(
            data_dir.get_data_dir(), sn1) + ".%s" % sn_fmt
449
        base_img = storage.get_image_filename(params, data_dir.get_data_dir())
450 451 452
        _create(cmd, sn1, sn_fmt, base_img=base_img, base_img_fmt=image_format)

        # Create snapshot2 based on snapshot1
453
        sn2 = params["image_name_snapshot2"]
L
Lucas Meneghel Rodrigues 已提交
454 455
        sn2 = utils_misc.get_path(
            data_dir.get_data_dir(), sn2) + ".%s" % sn_fmt
456 457 458 459 460 461 462
        _create(cmd, sn2, sn_fmt, base_img=sn1, base_img_fmt=sn_fmt)

        rebase_mode = params.get("rebase_mode")
        if rebase_mode == "unsafe":
            os.remove(sn1)

        _rebase(cmd, sn2, base_img, image_format, mode=rebase_mode)
463 464 465
        # Boot snapshot image after rebase
        img_name, img_format = sn2.split('.')
        _boot(img_name, img_format)
466 467 468

        # Check sn2's format and backing_file
        actual_base_img = _info(cmd, sn2, "backing file")
469
        base_img_name = os.path.basename(base_img)
470 471 472 473
        if not base_img_name in actual_base_img:
            raise error.TestFail("After rebase the backing_file of 'sn2' is "
                                 "'%s' which is not expected as '%s'"
                                 % (actual_base_img, base_img_name))
474 475
        status, output = _check(cmd, sn2)
        if not status:
476
            raise error.TestFail("Check image '%s' failed after rebase;"
477
                                 "got error: %s" % (sn2, output))
478 479 480 481 482 483
        try:
            os.remove(sn2)
            os.remove(sn1)
        except Exception:
            pass

484 485 486 487 488 489 490
    def _boot(img_name, img_fmt):
        """
        Boot test:
        1) Login guest
        2) Run dd in rhel guest
        3) Shutdown guest

L
Lucas Meneghel Rodrigues 已提交
491 492
        :param img_name: image name
        :param img_fmt: image format
493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526
        """
        params['image_name'] = img_name
        params['image_format'] = img_fmt
        image_name = "%s.%s" % (img_name, img_fmt)
        msg = "Try to boot vm with image %s" % image_name
        error.context(msg, logging.info)
        vm_name = params.get("main_vm")
        dd_timeout = int(params.get("dd_timeout", 60))
        params['vms'] = vm_name
        env_process.preprocess_vm(test, params, env, vm_name)
        vm = env.get_vm(params.get("main_vm"))
        vm.verify_alive()
        login_timeout = int(params.get("login_timeout", 360))
        session = vm.wait_for_login(timeout=login_timeout)

        # Run dd in linux guest
        if params.get("os_type") == 'linux':
            cmd = "dd if=/dev/zero of=/mnt/test bs=1000 count=1000"
            status = session.get_command_status(cmd, timeout=dd_timeout)
            if status != 0:
                raise error.TestError("dd failed")

        # Shutdown guest
        error.context("Shutdown command is sent, guest is going down...",
                      logging.info)
        try:
            session.sendline(params.get("shutdown_command"))
            if not utils_misc.wait_for(vm.is_dead, login_timeout):
                raise error.TestFail("Can not shutdown guest")

            logging.info("Guest is down")
        finally:
            session.close()

527
    # Here starts test
528
    subcommand = params["subcommand"]
529
    error.context("Running %s_test(cmd)" % subcommand, logging.info)
530
    eval("%s_test(cmd)" % subcommand)