Add image precache support

This change adds the driver method required for pre-caching Glance
images.

Change-Id: I9cc5d2c4533f17261f2d7facd67531014453b122
blueprint: image-precache-support
This commit is contained in:
Lucian Petrut 2020-02-11 17:29:25 +02:00
parent 71b317276f
commit b3636915d7
5 changed files with 104 additions and 52 deletions

View File

@ -407,6 +407,11 @@ class HyperVDriver(driver.ComputeDriver):
def manage_image_cache(self, context, all_instances):
self._imagecache.update(context, all_instances)
def cache_image(self, context, image_id):
image_path, fetched = self._imagecache.cache_image(
context, image_id)
return fetched
def attach_interface(self, context, instance, image_meta, vif):
self._vmops.attach_interface(context, instance, vif)

View File

@ -102,42 +102,9 @@ class ImageCache(imagecache.ImageCacheManager):
def get_cached_image(self, context, instance, rescue_image_id=None):
image_id = rescue_image_id or instance.image_ref
image_type = instance.system_metadata['image_disk_format']
base_image_dir = self._pathutils.get_base_vhd_dir()
base_image_path = os.path.join(base_image_dir, image_id)
lock_name = "%s-cache.lock" % image_id
@utils.synchronized(name=lock_name, external=True,
lock_path=base_image_dir)
def fetch_image_if_not_existing():
image_path = None
for format_ext in ['vhd', 'vhdx', 'iso']:
test_path = base_image_path + '.' + format_ext
if self._pathutils.exists(test_path):
image_path = test_path
self._update_image_timestamp(image_id)
break
if not image_path:
try:
images.fetch(context, image_id, base_image_path,
instance.trusted_certs)
if image_type == 'iso':
format_ext = 'iso'
else:
format_ext = self._vhdutils.get_vhd_format(
base_image_path)
image_path = base_image_path + '.' + format_ext.lower()
self._pathutils.rename(base_image_path, image_path)
except Exception:
with excutils.save_and_reraise_exception():
if self._pathutils.exists(base_image_path):
self._pathutils.remove(base_image_path)
return image_path
image_path = fetch_image_if_not_existing()
trusted_certs = instance.trusted_certs
image_path, already_exists = self.cache_image(
context, image_id, image_type, trusted_certs)
# Note: rescue images are not resized.
is_vhd = image_path.split('.')[-1].lower() == 'vhd'
@ -154,6 +121,50 @@ class ImageCache(imagecache.ImageCacheManager):
return image_path
def cache_image(self, context, image_id,
image_type=None, trusted_certs=None):
if not image_type:
image_info = images.get_info(context, image_id)
image_type = image_info['disk_format']
base_image_dir = self._pathutils.get_base_vhd_dir()
base_image_path = os.path.join(base_image_dir, image_id)
lock_name = "%s-cache.lock" % image_id
@utils.synchronized(name=lock_name, external=True,
lock_path=base_image_dir)
def fetch_image_if_not_existing():
fetched = False
image_path = None
for format_ext in ['vhd', 'vhdx', 'iso']:
test_path = base_image_path + '.' + format_ext
if self._pathutils.exists(test_path):
image_path = test_path
self._update_image_timestamp(image_id)
break
if not image_path:
try:
images.fetch(context, image_id, base_image_path,
trusted_certs)
fetched = True
if image_type == 'iso':
format_ext = 'iso'
else:
format_ext = self._vhdutils.get_vhd_format(
base_image_path)
image_path = base_image_path + '.' + format_ext.lower()
self._pathutils.rename(base_image_path, image_path)
except Exception:
with excutils.save_and_reraise_exception():
if self._pathutils.exists(base_image_path):
self._pathutils.remove(base_image_path)
return image_path, fetched
return fetch_image_if_not_existing()
def _verify_rescue_image(self, instance, rescue_image_id,
rescue_image_path):
rescue_image_info = self._vhdutils.get_vhd_info(rescue_image_path)

View File

@ -32,7 +32,7 @@ class HyperVBaseTestCase(test.NoDBTestCase):
utilsfactory_patcher = mock.patch.object(
utilsfactory, '_get_class', HyperVBaseTestCase._mock_get_class)
utilsfactory_patcher.start()
self.addCleanup(utilsfactory_patcher.stop)
self.addCleanup(mock.patch.stopall)
self._patch_autospec_classes()

View File

@ -545,6 +545,17 @@ class HyperVDriverTestCase(test_base.HyperVBaseTestCase):
self.driver._imagecache.update.assert_called_once_with(
mock.sentinel.context, mock.sentinel.all_instances)
def test_cache_image(self):
self.driver._imagecache.cache_image.return_value = (
mock.sentinel.image_path, mock.sentinel.fetched)
fetched = self.driver.cache_image(
mock.sentinel.context, mock.sentinel.image_id)
self.assertEqual(mock.sentinel.fetched, fetched)
self.driver._imagecache.cache_image.assert_called_once_with(
mock.sentinel.context, mock.sentinel.image_id)
def test_attach_interface(self):
mock_instance = fake_instance.fake_instance_obj(self.context)
self.driver.attach_interface(

View File

@ -104,6 +104,13 @@ class ImageCacheTestCase(test_base.HyperVBaseTestCase):
self.imagecache._vhdutils.get_vhd_format.return_value = (
constants.DISK_FORMAT_VHD)
mock.patch.object(imagecache.images, 'fetch').start()
mock.patch.object(imagecache.images, 'get_info').start()
self._mock_fetch = imagecache.images.fetch
self._mock_img_info = imagecache.images.get_info
self._mock_img_info.return_value = dict(disk_format=image_format)
CONF.set_override('use_cow_images', use_cow)
image_file_name = rescue_image_id or self.FAKE_IMAGE_REF
@ -113,31 +120,51 @@ class ImageCacheTestCase(test_base.HyperVBaseTestCase):
constants.DISK_FORMAT_VHD.lower())
return (expected_path, expected_vhd_path)
@mock.patch.object(imagecache.images, 'fetch')
def test_get_cached_image_with_fetch(self, mock_fetch):
@ddt.data({},
{'exists': False, 'provide_img_type': False})
@ddt.unpack
def test_cache_image(self, exists=True, provide_img_type=True):
(expected_path,
expected_image_path) = self._prepare_get_cached_image(False, False)
expected_image_path) = self._prepare_get_cached_image(
path_exists=exists)
img_type = constants.DISK_FORMAT_VHD if provide_img_type else None
ret_path, fetched = self.imagecache.cache_image(
self.context, self.FAKE_IMAGE_REF, img_type)
self.assertEqual(expected_image_path, ret_path)
self.assertEqual(not exists, fetched)
if not provide_img_type:
self._mock_img_info.assert_called_once_with(
self.context, self.FAKE_IMAGE_REF)
def test_get_cached_image_with_fetch(self):
(expected_path,
expected_image_path) = self._prepare_get_cached_image(
path_exists=False,
use_cow=False)
result = self.imagecache.get_cached_image(self.context, self.instance)
self.assertEqual(expected_image_path, result)
mock_fetch.assert_called_once_with(self.context, self.FAKE_IMAGE_REF,
expected_path,
self.instance.trusted_certs)
self._mock_fetch.assert_called_once_with(
self.context, self.FAKE_IMAGE_REF,
expected_path,
self.instance.trusted_certs)
self.imagecache._vhdutils.get_vhd_format.assert_called_once_with(
expected_path)
self.imagecache._pathutils.rename.assert_called_once_with(
expected_path, expected_image_path)
@mock.patch.object(imagecache.images, 'fetch')
def test_get_cached_image_with_fetch_exception(self, mock_fetch):
def test_get_cached_image_with_fetch_exception(self):
(expected_path,
expected_image_path) = self._prepare_get_cached_image(False, False)
# path doesn't exist until fetched.
self.imagecache._pathutils.exists.side_effect = [False, False, False,
True]
mock_fetch.side_effect = exception.InvalidImageRef(
self._mock_fetch.side_effect = exception.InvalidImageRef(
image_href=self.FAKE_IMAGE_REF)
self.assertRaises(exception.InvalidImageRef,
@ -164,8 +191,7 @@ class ImageCacheTestCase(test_base.HyperVBaseTestCase):
mock_update_img_timestamp.assert_called_once_with(
self.instance.image_ref)
@mock.patch.object(imagecache.images, 'fetch')
def test_cache_rescue_image_bigger_than_flavor(self, mock_fetch):
def test_cache_rescue_image_bigger_than_flavor(self):
fake_rescue_image_id = 'fake_rescue_image_id'
self.imagecache._vhdutils.get_vhd_info.return_value = {
@ -179,10 +205,9 @@ class ImageCacheTestCase(test_base.HyperVBaseTestCase):
self.context, self.instance,
fake_rescue_image_id)
mock_fetch.assert_called_once_with(self.context,
fake_rescue_image_id,
expected_path,
self.instance.trusted_certs)
self._mock_fetch.assert_called_once_with(
self.context, fake_rescue_image_id, expected_path,
self.instance.trusted_certs)
self.imagecache._vhdutils.get_vhd_info.assert_called_once_with(
expected_vhd_path)