# Copyright 2018 Red Hat, Inc. # All Rights Reserved. # # 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. import json import os from oslo_concurrency import processutils as putils from oslo_config import cfg from oslo_log import log as logging from oslo_utils import encodeutils from oslo_utils import excutils from taskflow.patterns import linear_flow as lf from taskflow import task from glance.async_ import utils from glance.i18n import _ LOG = logging.getLogger(__name__) conversion_plugin_opts = [ cfg.StrOpt('output_format', default='raw', choices=('qcow2', 'raw', 'vdmk'), help=_(""" Desired output format for image conversion plugin. Provide a valid image format to which the conversion plugin will convert the image before storing it to the back-end. Note, if the Image Conversion plugin for image import is defined, users should only upload disk formats that are supported by `quemu-img` otherwise the conversion and import will fail. Possible values: * qcow2 * raw * vdmk Related Options: * disk_formats """)), ] CONF = cfg.CONF CONF.register_opts(conversion_plugin_opts, group='image_conversion') class _ConvertImage(task.Task): default_provides = 'file_path' def __init__(self, context, task_id, task_type, image_repo, image_id): self.context = context self.task_id = task_id self.task_type = task_type self.image_repo = image_repo self.image_id = image_id self.dest_path = "" super(_ConvertImage, self).__init__( name='%s-Convert_Image-%s' % (task_type, task_id)) def execute(self, file_path, **kwargs): target_format = CONF.conversion_plugin_options.output_format # TODO(jokke): Once we support other schemas we need to take them into # account and handle the paths here. src_path = file_path.split('file://')[-1] dest_path = "%(path)s.%(target)s" % {'path': src_path, 'target': target_format} self.dest_path = dest_path try: stdout, stderr = putils.trycmd("qemu-img", "info", "--output=json", src_path, prlimit=utils.QEMU_IMG_PROC_LIMITS, log_errors=putils.LOG_ALL_ERRORS,) except OSError as exc: with excutils.save_and_reraise_exception(): exc_message = encodeutils.exception_to_unicode(exc) msg = ("Failed to do introspection as part of image " "conversion for %(iid)s: %(err)s") LOG.error(msg, {'iid': self.image_id, 'err': exc_message}) if stderr: raise RuntimeError(stderr) metadata = json.loads(stdout) source_format = metadata.get('format') virtual_size = metadata.get('virtual-size', 0) image = self.image_repo.get(self.image_id) image.virtual_size = virtual_size if source_format == target_format: LOG.debug("Source is already in target format, " "not doing conversion for %s", self.image_id) self.image_repo.save(image) return file_path try: stdout, stderr = putils.trycmd('qemu-img', 'convert', '-f', source_format, '-O', target_format, src_path, dest_path, log_errors=putils.LOG_ALL_ERRORS) except OSError as exc: with excutils.save_and_reraise_exception(): exc_message = encodeutils.exception_to_unicode(exc) msg = "Failed to do image conversion for %(iid)s: %(err)s" LOG.error(msg, {'iid': self.image_id, 'err': exc_message}) if stderr: raise RuntimeError(stderr) image.disk_format = target_format image.container_format = 'bare' self.image_repo.save(image) os.remove(src_path) return "file://%s" % dest_path def revert(self, result=None, **kwargs): # NOTE(flaper87): If result is None, it probably # means this task failed. Otherwise, we would have # a result from its execution. if result is not None: LOG.debug("Image conversion failed.") if os.path.exists(self.dest_path): os.remove(self.dest_path) def get_flow(**kwargs): """Return task flow for no-op. :param context: request context :param task_id: Task ID. :param task_type: Type of the task. :param image_repo: Image repository used. :param image_id: Image ID """ context = kwargs.get('context') task_id = kwargs.get('task_id') task_type = kwargs.get('task_type') image_repo = kwargs.get('image_repo') image_id = kwargs.get('image_id') return lf.Flow(task_type).add( _ConvertImage(context, task_id, task_type, image_repo, image_id), )