Skip to content

Commit e46d59a

Browse files
committed
Add cache_image() driver method and libvirt implementation
This adds a new virt driver method for pre-caching an image, as well as an implementation for libvirt. This will remain unimplemented in any driver that does not provide image caching functionality or does not support pre-caching of images. Related to blueprint image-precache-support Change-Id: I52fbbcac9dc386f24ee81b3321dd0d8355e01976
1 parent 7058fac commit e46d59a

File tree

3 files changed

+106
-0
lines changed

3 files changed

+106
-0
lines changed

nova/tests/unit/virt/libvirt/test_driver.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23533,6 +23533,61 @@ def test_cpu_traits_with_mode_none_on_power(self, mock_baseline, mock_cap):
2353323533
</cpu>
2353423534
'''], 1)
2353523535

23536+
@mock.patch('oslo_utils.fileutils.ensure_tree')
23537+
@mock.patch('os.path.isdir')
23538+
@mock.patch('os.path.exists')
23539+
@mock.patch('os.utime')
23540+
@mock.patch('nova.virt.images.fetch_to_raw')
23541+
def test_cache_image_uncached(self, mock_fetch, mock_utime, mock_exists,
23542+
mock_isdir, mock_et, first_time=False):
23543+
# NOTE(artom): This is not actually a path on the system, since we
23544+
# are fully mocked out and are just testing string formatting in this
23545+
# test.
23546+
self.flags(instances_path='/nova/instances')
23547+
self.flags(image_cache_subdirectory_name='cache')
23548+
expected_fn = os.path.join('/nova/instances/cache',
23549+
imagecache.get_cache_fname('an-image'))
23550+
23551+
mock_isdir.return_value = not first_time
23552+
mock_exists.return_value = False
23553+
self.assertTrue(self.drvr.cache_image(self.context, 'an-image'))
23554+
mock_fetch.assert_called_once_with(self.context, 'an-image',
23555+
expected_fn)
23556+
mock_utime.assert_not_called()
23557+
mock_exists.assert_called_once_with(expected_fn)
23558+
mock_isdir.assert_called_once_with('/nova/instances/cache')
23559+
if first_time:
23560+
mock_et.assert_called_once_with('/nova/instances/cache')
23561+
else:
23562+
mock_et.assert_not_called()
23563+
23564+
def test_cache_image_existing_first_time(self):
23565+
self.test_cache_image_uncached(first_time=True)
23566+
23567+
@mock.patch('oslo_utils.fileutils.ensure_tree')
23568+
@mock.patch('os.path.isdir')
23569+
@mock.patch('os.path.exists')
23570+
@mock.patch('nova.privsep.path.utime')
23571+
@mock.patch('nova.virt.images.fetch_to_raw')
23572+
def test_cache_image_existing(self, mock_fetch, mock_utime, mock_exists,
23573+
mock_isdir, mock_et):
23574+
# NOTE(artom): This is not actually a path on the system, since we
23575+
# are fully mocked out and are just testing string formatting in this
23576+
# test.
23577+
self.flags(instances_path='/nova/instances')
23578+
self.flags(image_cache_subdirectory_name='cache')
23579+
expected_fn = os.path.join('/nova/instances/cache',
23580+
imagecache.get_cache_fname('an-image'))
23581+
23582+
mock_isdir.return_value = True
23583+
mock_exists.return_value = True
23584+
self.assertFalse(self.drvr.cache_image(self.context, 'an-image'))
23585+
mock_utime.assert_called_once_with(expected_fn)
23586+
mock_fetch.assert_not_called()
23587+
mock_exists.assert_called_once_with(expected_fn)
23588+
mock_et.assert_not_called()
23589+
mock_isdir.assert_not_called()
23590+
2353623591

2353723592
class LibvirtVolumeUsageTestCase(test.NoDBTestCase):
2353823593
"""Test for LibvirtDriver.get_all_volume_usage."""

nova/virt/driver.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1582,6 +1582,22 @@ def manage_image_cache(self, context, all_instances):
15821582
"""
15831583
pass
15841584

1585+
def cache_image(self, context, image_id):
1586+
"""Download an image into the cache.
1587+
1588+
Used by the compute manager in response to a request to pre-cache
1589+
an image on the compute node. If the driver implements an image cache,
1590+
it should implement this method as well and perform the same action
1591+
as it does during an on-demand base image fetch in response to a
1592+
spawn.
1593+
1594+
:returns: A boolean indicating whether or not the image was fetched.
1595+
True if it was fetched, or False if it already exists in
1596+
the cache.
1597+
:raises: An Exception on error
1598+
"""
1599+
raise NotImplementedError()
1600+
15851601
def add_to_aggregate(self, context, aggregate, host, **kwargs):
15861602
"""Add a compute host to an aggregate.
15871603

nova/virt/libvirt/driver.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9643,6 +9643,41 @@ def _cleanup_remote_migration(self, dest, inst_base, inst_base_resize,
96439643
except Exception:
96449644
pass
96459645

9646+
def cache_image(self, context, image_id):
9647+
cache_dir = os.path.join(CONF.instances_path,
9648+
CONF.image_cache_subdirectory_name)
9649+
path = os.path.join(cache_dir,
9650+
imagecache.get_cache_fname(image_id))
9651+
if os.path.exists(path):
9652+
LOG.info('Image %(image_id)s already cached; updating timestamp',
9653+
{'image_id': image_id})
9654+
# NOTE(danms): The regular image cache routines use a wrapper
9655+
# (_update_utime_ignore_eacces()) around this to avoid failing
9656+
# on permissions (which may or may not be legit due to an NFS
9657+
# race). However, since this is best-effort, errors are swallowed
9658+
# by compute manager per-image, and we are compelled to report
9659+
# errors up our stack, we use the raw method here to avoid the
9660+
# silent ignore of the EACCESS.
9661+
nova.privsep.path.utime(path)
9662+
return False
9663+
else:
9664+
# NOTE(danms): In case we are running before the first boot, make
9665+
# sure the cache directory is created
9666+
if not os.path.isdir(cache_dir):
9667+
fileutils.ensure_tree(cache_dir)
9668+
LOG.info('Caching image %(image_id)s by request',
9669+
{'image_id': image_id})
9670+
# NOTE(danms): The imagebackend code, as called via spawn() where
9671+
# images are normally cached, uses a lock on the root disk it is
9672+
# creating at the time, but relies on the
9673+
# compute_utils.disk_ops_semaphore for cache fetch mutual
9674+
# exclusion, which is grabbed in images.fetch() (which is called
9675+
# by images.fetch_to_raw() below). So, by calling fetch_to_raw(),
9676+
# we are sharing the same locking for the cache fetch as the
9677+
# rest of the code currently called only from spawn().
9678+
images.fetch_to_raw(context, image_id, path)
9679+
return True
9680+
96469681
def _is_storage_shared_with(self, dest, inst_base):
96479682
# NOTE (rmk): There are two methods of determining whether we are
96489683
# on the same filesystem: the source and dest IP are the

0 commit comments

Comments
 (0)