Source code for virttest.qemu_storage

"""
Classes and functions to handle block/disk images for KVM.

This exports:
  - two functions for get image/blkdebug filename
  - class for image operates and basic parameters
"""
import logging
import os
import re
from autotest.client.shared import error
from autotest.client import utils
import utils_misc
import virt_vm
import storage
import data_dir


[docs]class QemuImg(storage.QemuImg): """ KVM class for handling operations of disk/block images. """ def __init__(self, params, root_dir, tag): """ Init the default value for image object. :param params: Dictionary containing the test parameters. :param root_dir: Base directory for relative filenames. :param tag: Image tag defined in parameter images """ storage.QemuImg.__init__(self, params, root_dir, tag) self.image_cmd = utils_misc.get_qemu_img_binary(params) q_result = utils.run(self.image_cmd + ' -h', ignore_status=True, verbose=False) self.help_text = q_result.stdout @error.context_aware def create(self, params, ignore_errors=False): """ Create an image using qemu_img or dd. :param params: Dictionary containing the test parameters. :param ignore_errors: Whether to ignore errors on the image creation cmd. :note: params should contain: image_name name of the image file, without extension image_format format of the image (qcow2, raw etc) image_cluster_size (optional) cluster size for the image image_size requested size of the image (a string qemu-img can understand, such as '10G') create_with_dd use dd to create the image (raw format only) base_image(optional) the base image name when create snapshot base_format(optional) the format of base image encrypted(optional) if the image is encrypted, allowed values: on and off. Default is "off" preallocated(optional) if preallocation when create image, allowed values: off, metadata. Default is "off" :return: tuple (path to the image created, utils.CmdResult object containing the result of the creation command). """ if params.get("create_with_dd") == "yes" and self.image_format == "raw": # maps K,M,G,T => (count, bs) human = {'K': (1, 1), 'M': (1, 1024), 'G': (1024, 1024), 'T': (1024, 1048576), } if human.has_key(self.size[-1]): block_size = human[self.size[-1]][1] size = int(self.size[:-1]) * human[self.size[-1]][0] qemu_img_cmd = ("dd if=/dev/zero of=%s count=%s bs=%sK" % (self.image_filename, size, block_size)) else: qemu_img_cmd = self.image_cmd qemu_img_cmd += " create" qemu_img_cmd += " -f %s" % self.image_format image_cluster_size = params.get("image_cluster_size", None) preallocated = params.get("preallocated", "off") encrypted = params.get("encrypted", "off") image_extra_params = params.get("image_extra_params", "") has_backing_file = params.get('has_backing_file') qemu_img_cmd += " -o " if self.image_format == "qcow2": if preallocated != "off": qemu_img_cmd += "preallocation=%s," % preallocated if encrypted != "off": qemu_img_cmd += "encryption=%s," % encrypted if image_cluster_size: qemu_img_cmd += "cluster_size=%s," % image_cluster_size if has_backing_file == "yes": backing_param = params.object_params("backing_file") backing_file = storage.get_image_filename(backing_param, self.root_dir) backing_fmt = backing_param.get("image_format") qemu_img_cmd += "backing_file=%s," % backing_file qemu_img_cmd += "backing_fmt=%s," % backing_fmt if image_extra_params: qemu_img_cmd += "%s," % image_extra_params qemu_img_cmd = qemu_img_cmd.rstrip(" -o") qemu_img_cmd = qemu_img_cmd.rstrip(",") if self.base_tag: qemu_img_cmd += " -b %s" % self.base_image_filename if self.base_format: qemu_img_cmd += " -F %s" % self.base_format qemu_img_cmd += " %s" % self.image_filename qemu_img_cmd += " %s" % self.size if (params.get("image_backend", "filesystem") == "filesystem"): image_dirname = os.path.dirname(self.image_filename) if image_dirname and not os.path.isdir(image_dirname): e_msg = ("Parent directory of the image file %s does " "not exist" % self.image_filename) logging.error(e_msg) logging.error("This usually means a serious setup error.") logging.error("Please verify if your data dir contains the " "expected directory structure") logging.error("Backing data dir: %s", data_dir.get_backing_data_dir()) logging.error("Directory structure:") for root, _, _ in os.walk(data_dir.get_backing_data_dir()): logging.error(root) logging.warning("We'll try to proceed by creating the dir. " "Other errors may ensue") os.makedirs(image_dirname) msg = "Create image by command: %s" % qemu_img_cmd error.context(msg, logging.info) cmd_result = utils.run(qemu_img_cmd, verbose=False, ignore_status=True) if cmd_result.exit_status != 0 and not ignore_errors: raise error.TestError("Failed to create image %s" % self.image_filename) return self.image_filename, cmd_result
[docs] def convert(self, params, root_dir, cache_mode=None): """ Convert image :param params: dictionary containing the test parameters :param root_dir: dir for save the convert image :param cache_mode: The cache mode used to write the output disk image. Valid options are: ``none``, ``writeback`` (default), ``writethrough``, ``directsync`` and ``unsafe``. :note: params should contain: convert_image_tag the image name of the convert image convert_filename the name of the image after convert convert_fmt the format after convert compressed indicates that target image must be compressed encrypted there are two value "off" and "on", default value is "off" """ convert_image_tag = params["image_convert"] convert_image = params["convert_name_%s" % convert_image_tag] convert_compressed = params.get("convert_compressed") convert_encrypted = params.get("convert_encrypted", "off") preallocated = params.get("preallocated") compat = params.get("compat") lazy_refcounts = params.get("lazy_refcounts") cluster_size = params.get("cluster_size") convert_format = params["convert_format_%s" % convert_image_tag] params_convert = {"image_name": convert_image, "image_format": convert_format} convert_image_filename = storage.get_image_filename(params_convert, root_dir) cmd = self.image_cmd cmd += " convert" if convert_compressed == "yes": cmd += " -c" options = [] if convert_encrypted != "off": options.append("encryption=%s" % convert_encrypted) if preallocated: options.append("preallocation=%s" % preallocated) if cluster_size: options.append("cluster_size=%s" % cluster_size) if compat: options.append("compat=%s" % compat) if lazy_refcounts: options.append("lazy_refcounts=%s" % lazy_refcounts) if options: cmd += " -o %s" % ",".join(options) if self.image_format: cmd += " -f %s" % self.image_format cmd += " -O %s" % convert_format if cache_mode: cmd += " -t %s" % cache_mode cmd += " %s %s" % (self.image_filename, convert_image_filename) logging.info("Convert image %s from %s to %s", self.image_filename, self.image_format, convert_format) utils.system(cmd) return convert_image_tag
[docs] def rebase(self, params, cache_mode=None): """ Rebase image. :param params: dictionary containing the test parameters :param cache_mode: the cache mode used to write the output disk image, the valid options are: 'none', 'writeback' (default), 'writethrough', 'directsync' and 'unsafe'. :note: params should contain: cmd qemu-img cmd snapshot_img the snapshot name base_img base image name base_fmt base image format snapshot_fmt the snapshot format mode there are two value, "safe" and "unsafe", default is "safe" """ self.check_option("base_image_filename") self.check_option("base_format") rebase_mode = params.get("rebase_mode") cmd = self.image_cmd cmd += " rebase" if self.image_format: cmd += " -f %s" % self.image_format if cache_mode: cmd += " -t %s" % cache_mode if rebase_mode == "unsafe": cmd += " -u" if self.base_tag: if self.base_tag == "null": cmd += " -b \"\" -F %s %s" % (self.base_format, self.image_filename) else: cmd += " -b %s -F %s %s" % (self.base_image_filename, self.base_format, self.image_filename) else: raise error.TestError("Can not find the image parameters need" " for rebase.") logging.info("Rebase snapshot %s to %s..." % (self.image_filename, self.base_image_filename)) utils.system(cmd) return self.base_tag
[docs] def commit(self, params={}, cache_mode=None): """ Commit image to it's base file :param cache_mode: the cache mode used to write the output disk image, the valid options are: 'none', 'writeback' (default), 'writethrough', 'directsync' and 'unsafe'. """ cmd = self.image_cmd cmd += " commit" if cache_mode: cmd += " -t %s" % cache_mode cmd += " -f %s %s" % (self.image_format, self.image_filename) logging.info("Commit snapshot %s" % self.image_filename) utils.system(cmd) return self.image_filename
[docs] def snapshot_create(self): """ Create a snapshot image. :note: params should contain: snapshot_image_name -- the name of snapshot image file """ cmd = self.image_cmd if self.snapshot_tag: cmd += " snapshot -c %s" % self.snapshot_image_filename else: raise error.TestError("Can not find the snapshot image" " parameters") cmd += " %s" % self.image_filename utils.system_output(cmd) return self.snapshot_tag
[docs] def snapshot_del(self, blkdebug_cfg=""): """ Delete a snapshot image. :param blkdebug_cfg: The configure file of blkdebug :note: params should contain: snapshot_image_name -- the name of snapshot image file """ cmd = self.image_cmd if self.snapshot_tag: cmd += " snapshot -d %s" % self.snapshot_image_filename else: raise error.TestError("Can not find the snapshot image" " parameters") if blkdebug_cfg: cmd += " blkdebug:%s:%s" % (blkdebug_cfg, self.image_filename) else: cmd += " %s" % self.image_filename utils.system_output(cmd)
[docs] def snapshot_list(self): """ List all snapshots in the given image """ cmd = self.image_cmd cmd += " snapshot -l %s" % self.image_filename return utils.system_output(cmd)
[docs] def snapshot_apply(self): """ Apply a snapshot image. :note: params should contain: snapshot_image_name -- the name of snapshot image file """ cmd = self.image_cmd if self.snapshot_tag: cmd += " snapshot -a %s %s" % (self.snapshot_image_filename, self.image_filename) else: raise error.TestError("Can not find the snapshot image" " parameters") utils.system_output(cmd)
[docs] def remove(self): """ Remove an image file. """ logging.debug("Removing image file %s", self.image_filename) if os.path.exists(self.image_filename): os.unlink(self.image_filename) else: logging.debug("Image file %s not found", self.image_filename)
[docs] def info(self): """ Run qemu-img info command on image file and return its output. """ logging.debug("Run qemu-img info comamnd on %s", self.image_filename) backing_chain = self.params.get("backing_chain") cmd = self.image_cmd if (os.path.exists(self.image_filename) or self.is_remote_image()): cmd += " info %s" % self.image_filename if backing_chain == "yes": if "--backing_chain" in self.help_text: cmd += " --backing-chain" else: logging.warn("'--backing-chain' option is not supportted") output = utils.system_output(cmd) else: logging.debug("Image file %s not found", self.image_filename) output = None return output
[docs] def get_format(self): """ Get the fimage file format. """ image_info = self.info() if image_info: image_format = re.findall("file format: (\w+)", image_info)[0] else: image_format = None return image_format
[docs] def support_cmd(self, cmd): """ Verifies whether qemu-img supports command cmd. :param cmd: Command string. """ supports_cmd = True if cmd not in self.help_text: logging.error("%s does not support command '%s'", self.image_cmd, cmd) supports_cmd = False return supports_cmd
[docs] def compare_images(self, image1, image2, verbose=True): """ Compare 2 images using the appropriate tools for each virt backend. :param image1: image path of first image :param image2: image path of second image :param verbose: Record output in debug file or not """ compare_images = self.support_cmd("compare") if not compare_images: logging.debug("Skipping image comparison " "(lack of support in qemu-img)") else: logging.info("Comparing images %s and %s", image1, image2) compare_cmd = "%s compare %s %s" % (self.image_cmd, image1, image2) rv = utils.run(compare_cmd, ignore_status=True) if verbose: logging.debug("Output from command: %s" % rv.stdout) if rv.exit_status == 0: logging.info("Compared images are equal") elif rv.exit_status == 1: raise error.TestFail("Compared images differ") else: raise error.TestError("Error in image comparison")
[docs] def check_image(self, params, root_dir): """ Check an image using the appropriate tools for each virt backend. :param params: Dictionary containing the test parameters. :param root_dir: Base directory for relative filenames. :note: params should contain: image_name -- the name of the image file, without extension image_format -- the format of the image (qcow2, raw etc) :raise VMImageCheckError: In case qemu-img check fails on the image. """ image_filename = self.image_filename logging.debug("Checking image file %s", image_filename) qemu_img_cmd = self.image_cmd image_is_checkable = self.image_format in ['qcow2', 'qed'] if (storage.file_exists(params, image_filename) or self.is_remote_image()) and image_is_checkable: check_img = self.support_cmd("check") and self.support_cmd("info") if not check_img: logging.debug("Skipping image check " "(lack of support in qemu-img)") else: try: utils.run("%s info %s" % (qemu_img_cmd, image_filename), verbose=True) except error.CmdError: logging.error("Error getting info from image %s", image_filename) cmd_result = utils.run("%s check %s" % (qemu_img_cmd, image_filename), ignore_status=True, verbose=True) # Error check, large chances of a non-fatal problem. # There are chances that bad data was skipped though if cmd_result.exit_status == 1: for e_line in cmd_result.stdout.splitlines(): logging.error("[stdout] %s", e_line) for e_line in cmd_result.stderr.splitlines(): logging.error("[stderr] %s", e_line) chk = params.get("backup_image_on_check_error", "no") if chk == "yes": self.backup_image(params, root_dir, "backup", False) raise error.TestWarn("qemu-img check error. Some bad " "data in the image may have gone" " unnoticed (%s)" % image_filename) # Exit status 2 is data corruption for sure, # so fail the test elif cmd_result.exit_status == 2: for e_line in cmd_result.stdout.splitlines(): logging.error("[stdout] %s", e_line) for e_line in cmd_result.stderr.splitlines(): logging.error("[stderr] %s", e_line) chk = params.get("backup_image_on_check_error", "no") if chk == "yes": self.backup_image(params, root_dir, "backup", False) raise virt_vm.VMImageCheckError(image_filename) # Leaked clusters, they are known to be harmless to data # integrity elif cmd_result.exit_status == 3: raise error.TestWarn("Leaked clusters were noticed" " during image check. No data " "integrity problem was found " "though. (%s)" % image_filename) # Just handle normal operation if params.get("backup_image", "no") == "yes": self.backup_image(params, root_dir, "backup", True, True) else: if not storage.file_exists(params, image_filename): logging.debug("Image file %s not found, skipping check", image_filename) elif not image_is_checkable: logging.debug( "Image format %s is not checkable, skipping check", self.image_format)
[docs]class Iscsidev(storage.Iscsidev): """ Class for handle iscsi devices for VM """ def __init__(self, params, root_dir, tag): """ Init the default value for image object. :param params: Dictionary containing the test parameters. :param root_dir: Base directory for relative filenames. :param tag: Image tag defined in parameter images """ super(Iscsidev, self).__init__(params, root_dir, tag)
[docs] def setup(self): """ Access the iscsi target. And return the local raw device name. """ if self.iscsidevice.logged_in(): logging.warn("Session already present. Don't need to" " login again") else: self.iscsidevice.login() if utils_misc.wait_for(self.iscsidevice.get_device_name, self.iscsi_init_timeout): device_name = self.iscsidevice.get_device_name() else: raise error.TestError("Can not get iscsi device name in host" " in %ss" % self.iscsi_init_timeout) if self.device_id: device_name += self.device_id return device_name
[docs] def cleanup(self): """ Logout the iscsi target and clean up the config and image. """ if self.exec_cleanup: self.iscsidevice.cleanup() if self.emulated_file_remove: logging.debug("Removing file %s", self.emulated_image) if os.path.exists(self.emulated_image): os.unlink(self.emulated_image) else: logging.debug("File %s not found", self.emulated_image)
[docs]class LVMdev(storage.LVMdev): """ Class for handle lvm devices for VM """ def __init__(self, params, root_dir, tag): """ Init the default value for image object. :param params: Dictionary containing the test parameters. :param root_dir: Base directory for relative filenames. :param tag: Image tag defined in parameter images """ super(LVMdev, self).__init__(params, root_dir, tag)
[docs] def setup(self): """ Get logical volume path; """ return self.lvmdevice.setup()
[docs] def cleanup(self): """ Cleanup useless volumes; """ return self.lvmdevice.cleanup()