From 61aeb1adbced8f0530f5d57bf7a6fe79c5f218d4 Mon Sep 17 00:00:00 2001 From: Curt Moore Date: Mon, 11 Jun 2018 10:08:57 -0500 Subject: Add ability to download Glance images into the libvirt image cache via RBD This change allows compute hosts to quickly download and cache images on the local compute host directly from Ceph rather than slow dowloads from the Glance API. New '[glance]/enable_rbd_download' option is introduced to enable this behavior. This is slight change compared to the original idea described in the relevant blueprint where it was discussed to use (now obsolete) '[glance]/allowed_direct_url_schemes' option. Additionally, when an image signature verification is requested, it should be done also for the image fetched by the new download handler. This was completely missing so far. New '[glance]/rbd_{user,pool,ceph_conf,connect_timeout}' configurables are introduced to allow operators to configure access to the cluster hosting Glance without the need to use the existing '[libvirt]' specific configurables. nova.storage.rbd_utils.RBDDriver has also been modified to accept these but continues to default to the '[libvirt]' specific configurables for now. Change-Id: I3032bbe6bd2d6acc9ba0f0cac4d00ed4b4464ceb Implements: blueprint nova-image-download-via-rbd --- nova/image/glance.py | 67 ++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 65 insertions(+), 2 deletions(-) (limited to 'nova/image') diff --git a/nova/image/glance.py b/nova/image/glance.py index be0c1eeccd..5e72679a2a 100644 --- a/nova/image/glance.py +++ b/nova/image/glance.py @@ -24,12 +24,14 @@ import re import stat import sys import time +import urllib.parse as urlparse import cryptography from cursive import certificate_utils from cursive import exception as cursive_exception from cursive import signature_utils import glanceclient +from glanceclient.common import utils as glance_utils import glanceclient.exc from glanceclient.v2 import schemas from keystoneauth1 import loading as ks_loading @@ -39,7 +41,6 @@ from oslo_utils import excutils from oslo_utils import timeutils import six from six.moves import range -import six.moves.urllib.parse as urlparse import nova.conf from nova import exception @@ -221,6 +222,51 @@ class GlanceImageServiceV2(object): # to be added here. self._download_handlers = {} + if CONF.glance.enable_rbd_download: + self._download_handlers['rbd'] = self.rbd_download + + def rbd_download(self, context, url_parts, dst_path, metadata=None): + """Use an explicit rbd call to download an image. + + :param context: The `nova.context.RequestContext` object for the + request + :param url_parts: Parts of URL pointing to the image location + :param dst_path: Filepath to transfer the image file to. + :param metadata: Image location metadata (currently unused) + """ + + # avoid circular import + from nova.storage import rbd_utils + try: + # Parse the RBD URL from url_parts, it should consist of 4 + # sections and be in the format of: + # /// + url_path = str(urlparse.unquote(url_parts.path)) + cluster_uuid, pool_name, image_uuid, snapshot_name = ( + url_path.split('/')) + except ValueError as e: + msg = f"Invalid RBD URL format: {e}" + LOG.error(msg) + raise nova.exception.InvalidParameterValue(msg) + + rbd_driver = rbd_utils.RBDDriver( + user=CONF.glance.rbd_user, + pool=CONF.glance.rbd_pool, + ceph_conf=CONF.glance.rbd_ceph_conf, + connect_timeout=CONF.glance.rbd_connect_timeout) + + try: + LOG.debug("Attempting to export RBD image: " + "[pool_name: %s] [image_uuid: %s] " + "[snapshot_name: %s] [dst_path: %s]", + pool_name, image_uuid, snapshot_name, dst_path) + + rbd_driver.export_image(dst_path, image_uuid, + snapshot_name, pool_name) + except Exception as e: + LOG.error("Error during RBD image export: %s", e) + raise nova.exception.CouldNotFetchImage(image_id=image_uuid) + def show(self, context, image_id, include_locations=False, show_deleted=True): """Returns a dict with image data for the given opaque image id. @@ -299,7 +345,13 @@ class GlanceImageServiceV2(object): def download(self, context, image_id, data=None, dst_path=None, trusted_certs=None): """Calls out to Glance for data and writes data.""" - if CONF.glance.allowed_direct_url_schemes and dst_path is not None: + + # First, check if image could be directly downloaded by special handler + # TODO(stephenfin): Remove check for 'allowed_direct_url_schemes' when + # we clean up tests since it's not used elsewhere + if ((CONF.glance.allowed_direct_url_schemes or + self._download_handlers) and dst_path is not None + ): image = self.show(context, image_id, include_locations=True) for entry in image.get('locations', []): loc_url = entry['url'] @@ -310,10 +362,21 @@ class GlanceImageServiceV2(object): try: xfer_method(context, o, dst_path, loc_meta) LOG.info("Successfully transferred using %s", o.scheme) + + # Load chunks from the downloaded image file + # for verification (if required) + with open(dst_path, 'rb') as fh: + downloaded_length = os.path.getsize(dst_path) + image_chunks = glance_utils.IterableWithLength(fh, + downloaded_length) + self._verify_and_write(context, image_id, + trusted_certs, image_chunks, None, None) return except Exception: LOG.exception("Download image error") + # By default (or if direct download has failed), use glance client call + # to fetch the image and fill image_chunks try: image_chunks = self._client.call( context, 2, 'data', args=(image_id,)) -- cgit v1.2.1