¡@

Home 

OpenStack Study: image_utils.py

OpenStack Index

**** CubicPower OpenStack Study ****

def qemu_img_info(path):

    """Return a object containing the parsed output from qemu-img info."""

    cmd = ('env', 'LC_ALL=C', 'qemu-img', 'info', path)

    if os.name == 'nt':

        cmd = cmd[2:]

    out, err = utils.execute(*cmd, run_as_root=True)

    return imageutils.QemuImgInfo(out)

**** CubicPower OpenStack Study ****

def convert_image(source, dest, out_format):

    """Convert image to other format."""

    cmd = ('qemu-img', 'convert', '-O', out_format, source, dest)

    utils.execute(*cmd, run_as_root=True)

**** CubicPower OpenStack Study ****

def resize_image(source, size, run_as_root=False):

    """Changes the virtual size of the image."""

    cmd = ('qemu-img', 'resize', source, '%sG' % size)

    utils.execute(*cmd, run_as_root=run_as_root)

**** CubicPower OpenStack Study ****

def fetch(context, image_service, image_id, path, _user_id, _project_id):

    # TODO(vish): Improve context handling and add owner and auth data

    #             when it is added to glance.  Right now there is no

    #             auth checking in glance, so we assume that access was

    #             checked before we got here.

    with fileutils.remove_path_on_error(path):

        with open(path, "wb") as image_file:

            image_service.download(context, image_id, image_file)

**** CubicPower OpenStack Study ****

def fetch_verify_image(context, image_service, image_id, dest,

                       user_id=None, project_id=None, size=None):

    fetch(context, image_service, image_id, dest,

          None, None)

    with fileutils.remove_path_on_error(dest):

        data = qemu_img_info(dest)

        fmt = data.file_format

        if fmt is None:

            raise exception.ImageUnacceptable(

                reason=_("'qemu-img info' parsing failed."),

                image_id=image_id)

        backing_file = data.backing_file

        if backing_file is not None:

            raise exception.ImageUnacceptable(

                image_id=image_id,

                reason=(_("fmt=%(fmt)s backed by: %(backing_file)s") %

                        {'fmt': fmt, 'backing_file': backing_file}))

        # NOTE(xqueralt): If the image virtual size doesn't fit in the

        # requested volume there is no point on resizing it because it will

        # generate an unusable image.

        if size is not None and data.virtual_size > size:

            params = {'image_size': data.virtual_size, 'volume_size': size}

            reason = _("Size is %(image_size)dGB and doesn't fit in a "

                       "volume of size %(volume_size)dGB.") % params

            raise exception.ImageUnacceptable(image_id=image_id, reason=reason)

**** CubicPower OpenStack Study ****

def fetch_to_vhd(context, image_service,

                 image_id, dest, blocksize,

                 user_id=None, project_id=None):

    fetch_to_volume_format(context, image_service, image_id, dest, 'vpc',

                           blocksize, user_id, project_id)

**** CubicPower OpenStack Study ****

def fetch_to_raw(context, image_service,

                 image_id, dest, blocksize,

                 user_id=None, project_id=None, size=None):

    fetch_to_volume_format(context, image_service, image_id, dest, 'raw',

                           blocksize, user_id, project_id, size)

**** CubicPower OpenStack Study ****

def fetch_to_volume_format(context, image_service,

                           image_id, dest, volume_format, blocksize,

                           user_id=None, project_id=None, size=None):

    if (CONF.image_conversion_dir and not

            os.path.exists(CONF.image_conversion_dir)):

        os.makedirs(CONF.image_conversion_dir)

    qemu_img = True

    image_meta = image_service.show(context, image_id)

    # NOTE(avishay): I'm not crazy about creating temp files which may be

    # large and cause disk full errors which would confuse users.

    # Unfortunately it seems that you can't pipe to 'qemu-img convert' because

    # it seeks. Maybe we can think of something for a future version.

    with temporary_file() as tmp:

        # We may be on a system that doesn't have qemu-img installed.  That

        # is ok if we are working with a RAW image.  This logic checks to see

        # if qemu-img is installed.  If not we make sure the image is RAW and

        # throw an exception if not.  Otherwise we stop before needing

        # qemu-img.  Systems with qemu-img will always progress through the

        # whole function.

        try:

            # Use the empty tmp file to make sure qemu_img_info works.

            qemu_img_info(tmp)

        except processutils.ProcessExecutionError:

            qemu_img = False

            if image_meta:

                if image_meta['disk_format'] != 'raw':

                    raise exception.ImageUnacceptable(

                        reason=_("qemu-img is not installed and image is of "

                                 "type %s.  Only RAW images can be used if "

                                 "qemu-img is not installed.") %

                        image_meta['disk_format'],

                        image_id=image_id)

            else:

                raise exception.ImageUnacceptable(

                    reason=_("qemu-img is not installed and the disk "

                             "format is not specified.  Only RAW images "

                             "can be used if qemu-img is not installed."),

                    image_id=image_id)

        fetch(context, image_service, image_id, tmp, user_id, project_id)

        if is_xenserver_image(context, image_service, image_id):

            replace_xenserver_image_with_coalesced_vhd(tmp)

        if not qemu_img:

            # qemu-img is not installed but we do have a RAW image.  As a

            # result we only need to copy the image to the destination and then

            # return.

            LOG.debug(_('Copying image from %(tmp)s to volume %(dest)s - '

                        'size: %(size)s') % {'tmp': tmp, 'dest': dest,

                                             'size': image_meta['size']})

            volume_utils.copy_volume(tmp, dest, image_meta['size'], blocksize)

            return

        data = qemu_img_info(tmp)

        virt_size = data.virtual_size / units.GiB

        # NOTE(xqueralt): If the image virtual size doesn't fit in the

        # requested volume there is no point on resizing it because it will

        # generate an unusable image.

        if size is not None and virt_size > size:

            params = {'image_size': virt_size, 'volume_size': size}

            reason = _("Size is %(image_size)dGB and doesn't fit in a "

                       "volume of size %(volume_size)dGB.") % params

            raise exception.ImageUnacceptable(image_id=image_id, reason=reason)

        fmt = data.file_format

        if fmt is None:

            raise exception.ImageUnacceptable(

                reason=_("'qemu-img info' parsing failed."),

                image_id=image_id)

        backing_file = data.backing_file

        if backing_file is not None:

            raise exception.ImageUnacceptable(

                image_id=image_id,

                reason=_("fmt=%(fmt)s backed by:%(backing_file)s")

                % {'fmt': fmt, 'backing_file': backing_file, })

        # NOTE(jdg): I'm using qemu-img convert to write

        # to the volume regardless if it *needs* conversion or not

        # TODO(avishay): We can speed this up by checking if the image is raw

        # and if so, writing directly to the device. However, we need to keep

        # check via 'qemu-img info' that what we copied was in fact a raw

        # image and not a different format with a backing file, which may be

        # malicious.

        LOG.debug("%s was %s, converting to %s " % (image_id, fmt,

                                                    volume_format))

        convert_image(tmp, dest, volume_format)

        data = qemu_img_info(dest)

        if data.file_format != volume_format:

            raise exception.ImageUnacceptable(

                image_id=image_id,

                reason=_("Converted to %(vol_format)s, but format is "

                         "now %(file_format)s") % {'vol_format': volume_format,

                                                   'file_format': data.

                                                   file_format})

**** CubicPower OpenStack Study ****

def upload_volume(context, image_service, image_meta, volume_path,

                  volume_format='raw'):

    image_id = image_meta['id']

    if (image_meta['disk_format'] == volume_format):

        LOG.debug("%s was %s, no need to convert to %s" %

                  (image_id, volume_format, image_meta['disk_format']))

        if os.name == 'nt' or os.access(volume_path, os.R_OK):

            with fileutils.file_open(volume_path) as image_file:

                image_service.update(context, image_id, {}, image_file)

        else:

            with utils.temporary_chown(volume_path):

                with fileutils.file_open(volume_path) as image_file:

                    image_service.update(context, image_id, {}, image_file)

        return

    if (CONF.image_conversion_dir and not

            os.path.exists(CONF.image_conversion_dir)):

        os.makedirs(CONF.image_conversion_dir)

    fd, tmp = tempfile.mkstemp(dir=CONF.image_conversion_dir)

    os.close(fd)

    with fileutils.remove_path_on_error(tmp):

        LOG.debug("%s was %s, converting to %s" %

                  (image_id, volume_format, image_meta['disk_format']))

        convert_image(volume_path, tmp, image_meta['disk_format'])

        data = qemu_img_info(tmp)

        if data.file_format != image_meta['disk_format']:

            raise exception.ImageUnacceptable(

                image_id=image_id,

                reason=_("Converted to %(f1)s, but format is now %(f2)s") %

                {'f1': image_meta['disk_format'], 'f2': data.file_format})

        with fileutils.file_open(tmp) as image_file:

            image_service.update(context, image_id, {}, image_file)

        fileutils.delete_if_exists(tmp)

**** CubicPower OpenStack Study ****

def is_xenserver_image(context, image_service, image_id):

    image_meta = image_service.show(context, image_id)

    return is_xenserver_format(image_meta)

**** CubicPower OpenStack Study ****

def is_xenserver_format(image_meta):

    return (

        image_meta['disk_format'] == 'vhd'

        and image_meta['container_format'] == 'ovf'

    )

**** CubicPower OpenStack Study ****

def file_exist(fpath):

    return os.path.exists(fpath)

**** CubicPower OpenStack Study ****

def set_vhd_parent(vhd_path, parentpath):

    utils.execute('vhd-util', 'modify', '-n', vhd_path, '-p', parentpath)

**** CubicPower OpenStack Study ****

def extract_targz(archive_name, target):

    utils.execute('tar', '-xzf', archive_name, '-C', target)

**** CubicPower OpenStack Study ****

def fix_vhd_chain(vhd_chain):

    for child, parent in zip(vhd_chain[:-1], vhd_chain[1:]):

        set_vhd_parent(child, parent)

**** CubicPower OpenStack Study ****

def get_vhd_size(vhd_path):

    out, err = utils.execute('vhd-util', 'query', '-n', vhd_path, '-v')

    return int(out)

**** CubicPower OpenStack Study ****

def resize_vhd(vhd_path, size, journal):

    utils.execute(

        'vhd-util', 'resize', '-n', vhd_path, '-s', '%d' % size, '-j', journal)

**** CubicPower OpenStack Study ****

def coalesce_vhd(vhd_path):

    utils.execute(

        'vhd-util', 'coalesce', '-n', vhd_path)

**** CubicPower OpenStack Study ****

def create_temporary_file():

    fd, tmp = tempfile.mkstemp(dir=CONF.image_conversion_dir)

    os.close(fd)

    return tmp

**** CubicPower OpenStack Study ****

def rename_file(src, dst):

    os.rename(src, dst)

@contextlib.contextmanager

**** CubicPower OpenStack Study ****

def temporary_file():

    try:

        tmp = create_temporary_file()

        yield tmp

    finally:

        fileutils.delete_if_exists(tmp)

**** CubicPower OpenStack Study ****

def temporary_dir():

    return utils.tempdir(dir=CONF.image_conversion_dir)

**** CubicPower OpenStack Study ****

def coalesce_chain(vhd_chain):

    for child, parent in zip(vhd_chain[:-1], vhd_chain[1:]):

        with temporary_dir() as directory_for_journal:

            size = get_vhd_size(child)

            journal_file = os.path.join(

                directory_for_journal, 'vhd-util-resize-journal')

            resize_vhd(parent, size, journal_file)

            coalesce_vhd(child)

    return vhd_chain[-1]

**** CubicPower OpenStack Study ****

def discover_vhd_chain(directory):

    counter = 0

    chain = []

    while True:

        fpath = os.path.join(directory, '%d.vhd' % counter)

        if file_exist(fpath):

            chain.append(fpath)

        else:

            break

        counter += 1

    return chain

**** CubicPower OpenStack Study ****

def replace_xenserver_image_with_coalesced_vhd(image_file):

    with temporary_dir() as tempdir:

        extract_targz(image_file, tempdir)

        chain = discover_vhd_chain(tempdir)

        fix_vhd_chain(chain)

        coalesced = coalesce_chain(chain)

        fileutils.delete_if_exists(image_file)

        rename_file(coalesced, image_file)