qemu_img.py 21.2 KB
Newer Older
1 2
import re, os, logging, commands
from autotest.client.shared import utils, error
3
from virttest import utils_misc, env_process, storage, data_dir
4 5


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

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


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

        @param cmd: qemu-img base command.
        @param img: image to be checked
        """
        cmd += " check %s" % img
34 35
        error.context("Checking image '%s' by command '%s'" % (img, cmd),
                      logging.info)
36
        try:
37 38 39
            output = utils.system_output(cmd, verbose=False)
        except error.CmdError, err:
            if "does not support checks" in str(err):
40 41
                return (True, "")
            else:
42
                return (False, str(err))
43 44 45 46 47 48 49 50 51 52 53 54
        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.

        @param cmd: qemu-img base command.
        """
55
        test_image = utils_misc.get_path(data_dir.get_data_dir(),
56 57
                                         params["image_name_dd"])
        create_image_cmd = params["create_image_cmd"]
58
        create_image_cmd = create_image_cmd % test_image
59 60 61 62 63
        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:
64
            raise error.TestFail("Check image '%s' failed with error: %s" %
65
                                                          (test_image, output))
66
        for fmt in params["supported_image_formats"].split():
67 68
            output_image = test_image + ".%s" % fmt
            _convert(cmd, fmt, test_image, output_image)
69 70
            status, output = _check(cmd, output_image)
            if not status:
71
                raise error.TestFail("Check image '%s' got error: %s" %
72
                                                     (output_image, output))
73 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 79
                base_img_fmt=None, encrypted="no",
                preallocated="no", cluster_size=None):
80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101
        """
        Simple wrapper of 'qemu-img create'

        @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
        """
        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 103 104 105 106 107 108 109 110 111 112
        if preallocated == "yes":
            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 118 119

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

        @param cmd: qemu-img base command.
        """
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 129
                img_size=params["image_size_large"],
                preallocated=params.get("preallocated", "no"))
130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153
        os.remove(img)


    def _convert(cmd, output_fmt, img_name, output_filename,
                fmt=None, compressed="no", encrypted="no"):
        """
        Simple wrapper of 'qemu-img convert' function.

        @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
        """
        cmd += " convert"
        if compressed == "yes":
            cmd += " -c"
        if encrypted == "yes":
            cmd += " -e"
        if fmt:
            cmd += " -f %s" % fmt
        cmd += " -O %s" % output_fmt
154 155 156 157 158 159 160 161
        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(",")
162
        cmd += " %s %s" % (img_name, output_filename)
163 164 165
        msg = "Converting '%s' from format '%s'" % (img_name, fmt)
        msg += " to '%s'" % output_fmt
        error.context(msg, logging.info)
166 167 168 169 170 171 172 173 174
        utils.system(cmd)


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

        @param cmd: qemu-img base command.
        """
175
        dest_img_fmt = params["dest_image_format"]
176 177
        output_filename = "%s.converted_%s.%s" % (image_name,
                                                  dest_img_fmt, dest_img_fmt)
178 179

        _convert(cmd, dest_img_fmt, image_name, output_filename,
180
                image_format, params["compressed"], params["encrypted"])
181 182 183 184
        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)
185 186

        if dest_img_fmt == "qcow2":
187 188
            status, output = _check(cmd, output_filename)
            if status:
189 190 191
                os.remove(output_filename)
            else:
                raise error.TestFail("Check image '%s' failed with error: %s" %
192
                                                     (output_filename, output))
193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212
        else:
            os.remove(output_filename)


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

        @param cmd: qemu-img base command.
        @param img: image file
        @param sub_info: sub info, say 'backing file'
        @param fmt: image format
        """
        cmd += " info"
        if fmt:
            cmd += " -f %s" % fmt
        cmd += " %s" % img

        try:
            output = utils.system_output(cmd)
213 214
        except error.CmdError, err:
            logging.error("Get info of image '%s' failed: %s", img, str(err))
215 216 217 218 219 220 221 222 223 224 225 226 227 228 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
            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.

        @param cmd: qemu-img base command.
        """
        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.

        @param cmd: qemu-img base command.
        """
        cmd += " snapshot"
        for i in range(2):
            crtcmd = cmd
            sn_name = "snapshot%d" % i
            crtcmd += " -c %s %s" % (sn_name, image_name)
254 255 256 257 258
            msg = "Created snapshot '%s' in '%s' by command %s" % (sn_name,
                    image_name, crtcmd)
            error.context(msg, logging.info)
            status, output = commands.getstatusoutput(crtcmd)
            if status != 0:
259
                raise error.TestFail("Create snapshot failed via command: %s;"
260
                                     "Output is: %s" % (crtcmd, output))
261 262
        listcmd = cmd
        listcmd += " -l %s" % image_name
263 264
        status, out = commands.getstatusoutput(listcmd)
        if not ("snapshot0" in out and "snapshot1" in out and status == 0):
265
            raise error.TestFail("Snapshot created failed or missed;"
266
                                 "snapshot list is: \n%s" % out)
267 268 269 270
        for i in range(2):
            sn_name = "snapshot%d" % i
            delcmd = cmd
            delcmd += " -d %s %s" % (sn_name, image_name)
271 272 273 274
            msg = "Delete snapshot '%s' by command %s" % (sn_name, delcmd)
            error.context(msg, logging.info)
            status, output = commands.getstatusoutput(delcmd)
            if status != 0:
275
                raise error.TestFail("Delete snapshot '%s' failed: %s" %
276
                                                     (sn_name, output))
277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296


    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.

        @param cmd: qemu-img base command.
        """

        logging.info("Commit testing started!")
        image_name = params.get("image_name", "image")
297
        image_name = os.path.join(data_dir.get_data_dir(), image_name)
298 299
        image_format = params.get("image_format", "qcow2")
        backing_file_name = "%s_bak" % (image_name)
300 301 302 303 304 305 306 307 308 309
        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",
                                       "[ ! -e /commit_testfile ] && echo $?")
        file_del_cmd = params.get("file_del_cmd",
                                  "rm -f /commit_testfile")
310 311 312 313 314 315 316
        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
317
            create_cmd = "%s create -b %s.%s -f %s %s.%s" % (cmd, image_name,
318 319 320 321
                                                                  image_format,
                                                                  image_format,
                                                             backing_file_name,
                                                                  image_format)
322 323
            msg = "Create backing file by command: %s" % create_cmd
            error.context(msg, logging.info)
324
            try:
325 326
                utils.system(create_cmd, verbose=False)
            except error.CmdError:
327 328 329 330 331 332 333 334 335
                raise error.TestFail("Could not create a backing file!")
            logging.info("backing_file created!")

            # Set the qemu harddisk to the backing file
            logging.info("Original image_name is: %s", params.get('image_name'))
            params['image_name'] = backing_file_name
            logging.info("Param image_name changed to: %s",
                         params.get('image_name'))

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

            # Do some changes to the backing_file harddisk
            try:
347 348 349 350 351
                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:
352
                raise error.TestFail("Could not create commit_testfile in the "
353
                                     "backing file %s" % err)
354 355 356 357 358 359 360 361 362 363 364
            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
365
            vm_name = params['main_vm']
366 367
            env_process.preprocess_vm(test, params, env, vm_name)
            vm = env.get_vm(vm_name)
368
            vm.verify_alive()
369 370 371
            timeout = int(params.get("login_timeout", 360))
            session = vm.wait_for_login(timeout=timeout)
            try:
372 373
                output = session.cmd(file_not_exist_chk_cmd)
                logging.info("Output of %s: %s", file_not_exist_chk_cmd, output)
374
            except Exception:
375
                output = session.cmd(file_del_cmd)
376 377 378 379 380
                raise error.TestFail("The commit_testfile exists on the "
                                     "original file")
            vm.destroy()

            # Excecute the commit command
381 382 383 384 385
            cmitcmd = "%s commit -f %s %s.%s" % (cmd, image_format,
                                                 backing_file_name,
                                                 image_format)
            error.context("Commiting image by command %s" % cmitcmd,
                          logging.info)
386
            try:
387 388
                utils.system(cmitcmd, verbose=False)
            except error.CmdError:
389 390 391
                raise error.TestFail("Could not commit the backing file")

            # Start a new VM, using image_name as its harddisk
392
            vm_name = params['main_vm']
393 394
            env_process.preprocess_vm(test, params, env, vm_name)
            vm = env.get_vm(vm_name)
395
            vm.verify_alive()
396 397 398
            timeout = int(params.get("login_timeout", 360))
            session = vm.wait_for_login(timeout=timeout)
            try:
399 400 401
                output = session.cmd(file_exist_chk_cmd)
                logging.info("Output of %s: %s", file_exist_chk_cmd, output)
                session.cmd(file_del_cmd)
402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426
            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'.

        @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
        """
        cmd += " rebase"
        if mode == "unsafe":
            cmd += " -u"
        cmd += " -b %s -F %s %s" % (base_img, backing_fmt, img_name)
427 428 429 430 431
        msg = "Trying to rebase '%s' to '%s' by command %s" % (img_name,
                                                             base_img, cmd)
        error.context(msg, logging.info)
        status, output = commands.getstatusoutput(cmd)
        if status != 0:
432
            raise error.TestError("Failed to rebase '%s' to '%s': %s" %
433
                                               (img_name, base_img, output))
434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451


    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.

        @param cmd: qemu-img base command.
        """
        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")
452
        sn1 = params["image_name_snapshot1"]
453 454
        sn1 = utils_misc.get_path(data_dir.get_data_dir(), sn1) + ".%s" % sn_fmt
        base_img = storage.get_image_filename(params, data_dir.get_data_dir())
455 456 457
        _create(cmd, sn1, sn_fmt, base_img=base_img, base_img_fmt=image_format)

        # Create snapshot2 based on snapshot1
458
        sn2 = params["image_name_snapshot2"]
459
        sn2 = utils_misc.get_path(data_dir.get_data_dir(), sn2) + ".%s" % sn_fmt
460 461 462 463 464 465 466
        _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)
467 468 469
        # Boot snapshot image after rebase
        img_name, img_format = sn2.split('.')
        _boot(img_name, img_format)
470 471 472

        # Check sn2's format and backing_file
        actual_base_img = _info(cmd, sn2, "backing file")
473
        base_img_name = os.path.basename(base_img)
474 475 476 477
        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))
478 479
        status, output = _check(cmd, sn2)
        if not status:
480
            raise error.TestFail("Check image '%s' failed after rebase;"
481
                                 "got error: %s" % (sn2, output))
482 483 484 485 486 487 488
        try:
            os.remove(sn2)
            os.remove(sn1)
        except Exception:
            pass


489 490 491 492 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 527 528 529 530 531
    def _boot(img_name, img_fmt):
        """
        Boot test:
        1) Login guest
        2) Run dd in rhel guest
        3) Shutdown guest

        @param img_name: image name
        @param img_fmt: image format
        """
        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()

532
    # Here starts test
533
    subcommand = params["subcommand"]
534
    error.context("Running %s_test(cmd)" % subcommand, logging.info)
535
    eval("%s_test(cmd)" % subcommand)