137 lines
5.0 KiB
Python
137 lines
5.0 KiB
Python
# Copyright 2010 United States Government as represented by the
|
|
# Administrator of the National Aeronautics and Space Administration.
|
|
# All Rights Reserved.
|
|
# Copyright (c) 2010 Citrix Systems, Inc.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
|
|
"""
|
|
Handling of VM disk images.
|
|
"""
|
|
|
|
import os
|
|
|
|
from oslo_config import cfg
|
|
from oslo_log import log as logging
|
|
|
|
from nova import exception
|
|
from nova.i18n import _, _LE
|
|
from nova import image
|
|
from nova.openstack.common import fileutils
|
|
from nova.openstack.common import imageutils
|
|
from nova import utils
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
image_opts = [
|
|
cfg.BoolOpt('force_raw_images',
|
|
default=True,
|
|
help='Force backing images to raw format'),
|
|
]
|
|
|
|
CONF = cfg.CONF
|
|
CONF.register_opts(image_opts)
|
|
IMAGE_API = image.API()
|
|
|
|
|
|
def qemu_img_info(path):
|
|
"""Return an object containing the parsed output from qemu-img info."""
|
|
# TODO(mikal): this code should not be referring to a libvirt specific
|
|
# flag.
|
|
# NOTE(sirp): The config option import must go here to avoid an import
|
|
# cycle
|
|
CONF.import_opt('images_type', 'nova.virt.libvirt.imagebackend',
|
|
group='libvirt')
|
|
if not os.path.exists(path) and CONF.libvirt.images_type != 'rbd':
|
|
msg = (_("Path does not exist %(path)s") % {'path': path})
|
|
raise exception.InvalidDiskInfo(reason=msg)
|
|
|
|
out, err = utils.execute('env', 'LC_ALL=C', 'LANG=C',
|
|
'qemu-img', 'info', path)
|
|
if not out:
|
|
msg = (_("Failed to run qemu-img info on %(path)s : %(error)s") %
|
|
{'path': path, 'error': err})
|
|
raise exception.InvalidDiskInfo(reason=msg)
|
|
|
|
return imageutils.QemuImgInfo(out)
|
|
|
|
|
|
def convert_image(source, dest, out_format, run_as_root=False):
|
|
"""Convert image to other format."""
|
|
cmd = ('qemu-img', 'convert', '-O', out_format, source, dest)
|
|
utils.execute(*cmd, run_as_root=run_as_root)
|
|
|
|
|
|
def fetch(context, image_href, path, _user_id, _project_id, max_size=0):
|
|
with fileutils.remove_path_on_error(path):
|
|
IMAGE_API.download(context, image_href, dest_path=path)
|
|
|
|
|
|
def get_info(context, image_href):
|
|
return IMAGE_API.get(context, image_href)
|
|
|
|
|
|
def fetch_to_raw(context, image_href, path, user_id, project_id, max_size=0):
|
|
path_tmp = "%s.part" % path
|
|
fetch(context, image_href, path_tmp, user_id, project_id,
|
|
max_size=max_size)
|
|
|
|
with fileutils.remove_path_on_error(path_tmp):
|
|
data = qemu_img_info(path_tmp)
|
|
|
|
fmt = data.file_format
|
|
if fmt is None:
|
|
raise exception.ImageUnacceptable(
|
|
reason=_("'qemu-img info' parsing failed."),
|
|
image_id=image_href)
|
|
|
|
backing_file = data.backing_file
|
|
if backing_file is not None:
|
|
raise exception.ImageUnacceptable(image_id=image_href,
|
|
reason=(_("fmt=%(fmt)s backed by: %(backing_file)s") %
|
|
{'fmt': fmt, 'backing_file': backing_file}))
|
|
|
|
# We can't generally shrink incoming images, so disallow
|
|
# images > size of the flavor we're booting. Checking here avoids
|
|
# an immediate DoS where we convert large qcow images to raw
|
|
# (which may compress well but not be sparse).
|
|
# TODO(p-draigbrady): loop through all flavor sizes, so that
|
|
# we might continue here and not discard the download.
|
|
# If we did that we'd have to do the higher level size checks
|
|
# irrespective of whether the base image was prepared or not.
|
|
disk_size = data.virtual_size
|
|
if max_size and max_size < disk_size:
|
|
LOG.error(_LE('%(base)s virtual size %(disk_size)s '
|
|
'larger than flavor root disk size %(size)s'),
|
|
{'base': path,
|
|
'disk_size': disk_size,
|
|
'size': max_size})
|
|
raise exception.FlavorDiskTooSmall()
|
|
|
|
if fmt != "raw" and CONF.force_raw_images:
|
|
staged = "%s.converted" % path
|
|
LOG.debug("%s was %s, converting to raw" % (image_href, fmt))
|
|
with fileutils.remove_path_on_error(staged):
|
|
convert_image(path_tmp, staged, 'raw')
|
|
os.unlink(path_tmp)
|
|
|
|
data = qemu_img_info(staged)
|
|
if data.file_format != "raw":
|
|
raise exception.ImageUnacceptable(image_id=image_href,
|
|
reason=_("Converted to raw, but format is now %s") %
|
|
data.file_format)
|
|
|
|
os.rename(staged, path)
|
|
else:
|
|
os.rename(path_tmp, path)
|