ImageCache: respect Cache-Control: no-store
Images can be generated on fly, so let us respect the decision of the server to disable caching. Also disable caching for file images: it's just waste of space. Change-Id: I425b1915c73354af90329d5b3a1fb0a616adaa93
This commit is contained in:
parent
2e2d6b8eeb
commit
7043a414a8
@ -183,10 +183,13 @@ class HttpImageService(BaseImageService):
|
|||||||
except ValueError:
|
except ValueError:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
no_cache = 'no-store' in response.headers.get('Cache-Control', '')
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'size': int(image_size),
|
'size': int(image_size),
|
||||||
'updated_at': date,
|
'updated_at': date,
|
||||||
'properties': {}
|
'properties': {},
|
||||||
|
'no_cache': no_cache,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -259,7 +262,9 @@ class FileImageService(BaseImageService):
|
|||||||
'size': os.path.getsize(source_image_path),
|
'size': os.path.getsize(source_image_path),
|
||||||
'updated_at': utils.unix_file_modification_datetime(
|
'updated_at': utils.unix_file_modification_datetime(
|
||||||
source_image_path),
|
source_image_path),
|
||||||
'properties': {}
|
'properties': {},
|
||||||
|
# No point in caching local file images
|
||||||
|
'no_cache': True,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -112,11 +112,13 @@ class ImageCache(object):
|
|||||||
|
|
||||||
# TODO(dtantsur): lock expiration time
|
# TODO(dtantsur): lock expiration time
|
||||||
with lockutils.lock(img_download_lock_name):
|
with lockutils.lock(img_download_lock_name):
|
||||||
|
img_service = image_service.get_image_service(href, context=ctx)
|
||||||
|
img_info = img_service.show(href)
|
||||||
# NOTE(vdrok): After rebuild requested image can change, so we
|
# NOTE(vdrok): After rebuild requested image can change, so we
|
||||||
# should ensure that dest_path and master_path (if exists) are
|
# should ensure that dest_path and master_path (if exists) are
|
||||||
# pointing to the same file and their content is up to date
|
# pointing to the same file and their content is up to date
|
||||||
cache_up_to_date = _delete_master_path_if_stale(master_path, href,
|
cache_up_to_date = _delete_master_path_if_stale(master_path, href,
|
||||||
ctx)
|
img_info)
|
||||||
dest_up_to_date = _delete_dest_path_if_stale(master_path,
|
dest_up_to_date = _delete_dest_path_if_stale(master_path,
|
||||||
dest_path)
|
dest_path)
|
||||||
|
|
||||||
@ -137,13 +139,14 @@ class ImageCache(object):
|
|||||||
LOG.info("Master cache miss for image %(href)s, will download",
|
LOG.info("Master cache miss for image %(href)s, will download",
|
||||||
{'href': href})
|
{'href': href})
|
||||||
self._download_image(
|
self._download_image(
|
||||||
href, master_path, dest_path, ctx=ctx, force_raw=force_raw)
|
href, master_path, dest_path, img_info,
|
||||||
|
ctx=ctx, force_raw=force_raw)
|
||||||
|
|
||||||
# NOTE(dtantsur): we increased cache size - time to clean up
|
# NOTE(dtantsur): we increased cache size - time to clean up
|
||||||
self.clean_up()
|
self.clean_up()
|
||||||
|
|
||||||
def _download_image(self, href, master_path, dest_path, ctx=None,
|
def _download_image(self, href, master_path, dest_path, img_info,
|
||||||
force_raw=True):
|
ctx=None, force_raw=True):
|
||||||
"""Download image by href and store at a given path.
|
"""Download image by href and store at a given path.
|
||||||
|
|
||||||
This method should be called with uuid-specific lock taken.
|
This method should be called with uuid-specific lock taken.
|
||||||
@ -151,6 +154,7 @@ class ImageCache(object):
|
|||||||
:param href: image UUID or href to fetch
|
:param href: image UUID or href to fetch
|
||||||
:param master_path: destination master path
|
:param master_path: destination master path
|
||||||
:param dest_path: destination file path
|
:param dest_path: destination file path
|
||||||
|
:param img_info: image information from the image service
|
||||||
:param ctx: context
|
:param ctx: context
|
||||||
:param force_raw: boolean value, whether to convert the image to raw
|
:param force_raw: boolean value, whether to convert the image to raw
|
||||||
format
|
format
|
||||||
@ -166,10 +170,16 @@ class ImageCache(object):
|
|||||||
try:
|
try:
|
||||||
with _concurrency_semaphore:
|
with _concurrency_semaphore:
|
||||||
_fetch(ctx, href, tmp_path, force_raw)
|
_fetch(ctx, href, tmp_path, force_raw)
|
||||||
# NOTE(dtantsur): no need for global lock here - master_path
|
|
||||||
# will have link count >1 at any moment, so won't be cleaned up
|
if img_info.get('no_cache'):
|
||||||
os.link(tmp_path, master_path)
|
LOG.debug("Caching is disabled for image %s", href)
|
||||||
os.link(master_path, dest_path)
|
# Cache disabled, link directly to destination
|
||||||
|
os.link(tmp_path, dest_path)
|
||||||
|
else:
|
||||||
|
# NOTE(dtantsur): no need for global lock here - master_path
|
||||||
|
# will have link count >1 at any moment, so won't be cleaned up
|
||||||
|
os.link(tmp_path, master_path)
|
||||||
|
os.link(master_path, dest_path)
|
||||||
except OSError as exc:
|
except OSError as exc:
|
||||||
msg = (_("Could not link image %(img_href)s from %(src_path)s "
|
msg = (_("Could not link image %(img_href)s from %(src_path)s "
|
||||||
"to %(dst_path)s, error: %(exc)s") %
|
"to %(dst_path)s, error: %(exc)s") %
|
||||||
@ -410,12 +420,12 @@ def cleanup(priority):
|
|||||||
return _add_property_to_class_func
|
return _add_property_to_class_func
|
||||||
|
|
||||||
|
|
||||||
def _delete_master_path_if_stale(master_path, href, ctx):
|
def _delete_master_path_if_stale(master_path, href, img_info):
|
||||||
"""Delete image from cache if it is not up to date with href contents.
|
"""Delete image from cache if it is not up to date with href contents.
|
||||||
|
|
||||||
:param master_path: path to an image in master cache
|
:param master_path: path to an image in master cache
|
||||||
:param href: image href
|
:param href: image href
|
||||||
:param ctx: context to use
|
:param img_info: image information from the service
|
||||||
:returns: True if master_path is up to date with href contents,
|
:returns: True if master_path is up to date with href contents,
|
||||||
False if master_path was stale and was deleted or it didn't exist
|
False if master_path was stale and was deleted or it didn't exist
|
||||||
"""
|
"""
|
||||||
@ -423,8 +433,7 @@ def _delete_master_path_if_stale(master_path, href, ctx):
|
|||||||
# Glance image contents cannot be updated without changing image's UUID
|
# Glance image contents cannot be updated without changing image's UUID
|
||||||
return os.path.exists(master_path)
|
return os.path.exists(master_path)
|
||||||
if os.path.exists(master_path):
|
if os.path.exists(master_path):
|
||||||
img_service = image_service.get_image_service(href, context=ctx)
|
img_mtime = img_info.get('updated_at')
|
||||||
img_mtime = img_service.show(href).get('updated_at')
|
|
||||||
if not img_mtime:
|
if not img_mtime:
|
||||||
# This means that href is not a glance image and doesn't have an
|
# This means that href is not a glance image and doesn't have an
|
||||||
# updated_at attribute. To play on the safe side, redownload the
|
# updated_at attribute. To play on the safe side, redownload the
|
||||||
|
@ -205,7 +205,7 @@ class HttpImageServiceTestCase(base.TestCase):
|
|||||||
head_mock.assert_called_once_with(self.href, verify=True,
|
head_mock.assert_called_once_with(self.href, verify=True,
|
||||||
timeout=60)
|
timeout=60)
|
||||||
self.assertEqual({'size': 100, 'updated_at': mtime_date,
|
self.assertEqual({'size': 100, 'updated_at': mtime_date,
|
||||||
'properties': {}}, result)
|
'properties': {}, 'no_cache': False}, result)
|
||||||
|
|
||||||
def test_show_rfc_822(self):
|
def test_show_rfc_822(self):
|
||||||
self._test_show(mtime='Tue, 15 Nov 2014 08:12:31 GMT',
|
self._test_show(mtime='Tue, 15 Nov 2014 08:12:31 GMT',
|
||||||
@ -219,6 +219,33 @@ class HttpImageServiceTestCase(base.TestCase):
|
|||||||
self._test_show(mtime='Tue Nov 15 08:12:31 2014',
|
self._test_show(mtime='Tue Nov 15 08:12:31 2014',
|
||||||
mtime_date=datetime.datetime(2014, 11, 15, 8, 12, 31))
|
mtime_date=datetime.datetime(2014, 11, 15, 8, 12, 31))
|
||||||
|
|
||||||
|
@mock.patch.object(requests, 'head', autospec=True)
|
||||||
|
def _test_show_with_cache(self, head_mock, cache_control, no_cache):
|
||||||
|
head_mock.return_value.status_code = http_client.OK
|
||||||
|
head_mock.return_value.headers = {
|
||||||
|
'Content-Length': 100,
|
||||||
|
'Last-Modified': 'Tue, 15 Nov 2014 08:12:31 GMT',
|
||||||
|
'Cache-Control': cache_control,
|
||||||
|
}
|
||||||
|
result = self.service.show(self.href)
|
||||||
|
head_mock.assert_called_once_with(self.href, verify=True,
|
||||||
|
timeout=60)
|
||||||
|
self.assertEqual({
|
||||||
|
'size': 100,
|
||||||
|
'updated_at': datetime.datetime(2014, 11, 15, 8, 12, 31),
|
||||||
|
'properties': {},
|
||||||
|
'no_cache': no_cache}, result)
|
||||||
|
|
||||||
|
def test_show_cache_allowed(self):
|
||||||
|
self._test_show_with_cache(
|
||||||
|
# Just because we cannot have nice things, "no-cache" actually
|
||||||
|
# means "cache, but always re-validate".
|
||||||
|
cache_control='no-cache, private', no_cache=False)
|
||||||
|
|
||||||
|
def test_show_cache_disabled(self):
|
||||||
|
self._test_show_with_cache(
|
||||||
|
cache_control='no-store', no_cache=True)
|
||||||
|
|
||||||
@mock.patch.object(requests, 'head', autospec=True)
|
@mock.patch.object(requests, 'head', autospec=True)
|
||||||
def test_show_no_content_length(self, head_mock):
|
def test_show_no_content_length(self, head_mock):
|
||||||
head_mock.return_value.status_code = http_client.OK
|
head_mock.return_value.status_code = http_client.OK
|
||||||
@ -425,7 +452,8 @@ class FileImageServiceTestCase(base.TestCase):
|
|||||||
self.assertEqual({'size': 42,
|
self.assertEqual({'size': 42,
|
||||||
'updated_at': datetime.datetime(2015, 5, 8,
|
'updated_at': datetime.datetime(2015, 5, 8,
|
||||||
12, 25, 9, 164191),
|
12, 25, 9, 164191),
|
||||||
'properties': {}}, result)
|
'properties': {},
|
||||||
|
'no_cache': True}, result)
|
||||||
|
|
||||||
@mock.patch.object(os, 'link', autospec=True)
|
@mock.patch.object(os, 'link', autospec=True)
|
||||||
@mock.patch.object(os, 'remove', autospec=True)
|
@mock.patch.object(os, 'remove', autospec=True)
|
||||||
|
@ -37,10 +37,10 @@ def touch(filename):
|
|||||||
open(filename, 'w').close()
|
open(filename, 'w').close()
|
||||||
|
|
||||||
|
|
||||||
class TestImageCacheFetch(base.TestCase):
|
class BaseTest(base.TestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(TestImageCacheFetch, self).setUp()
|
super().setUp()
|
||||||
self.master_dir = tempfile.mkdtemp()
|
self.master_dir = tempfile.mkdtemp()
|
||||||
self.cache = image_cache.ImageCache(self.master_dir, None, None)
|
self.cache = image_cache.ImageCache(self.master_dir, None, None)
|
||||||
self.dest_dir = tempfile.mkdtemp()
|
self.dest_dir = tempfile.mkdtemp()
|
||||||
@ -48,28 +48,31 @@ class TestImageCacheFetch(base.TestCase):
|
|||||||
self.uuid = uuidutils.generate_uuid()
|
self.uuid = uuidutils.generate_uuid()
|
||||||
self.master_path = ''.join([os.path.join(self.master_dir, self.uuid),
|
self.master_path = ''.join([os.path.join(self.master_dir, self.uuid),
|
||||||
'.converted'])
|
'.converted'])
|
||||||
|
self.img_info = {}
|
||||||
|
|
||||||
|
|
||||||
|
@mock.patch.object(image_service, 'get_image_service', autospec=True)
|
||||||
|
@mock.patch.object(image_cache.ImageCache, 'clean_up', autospec=True)
|
||||||
|
@mock.patch.object(image_cache.ImageCache, '_download_image', autospec=True)
|
||||||
|
class TestImageCacheFetch(BaseTest):
|
||||||
|
|
||||||
@mock.patch.object(image_cache, '_fetch', autospec=True)
|
@mock.patch.object(image_cache, '_fetch', autospec=True)
|
||||||
@mock.patch.object(image_cache.ImageCache, 'clean_up', autospec=True)
|
def test_fetch_image_no_master_dir(self, mock_fetch, mock_download,
|
||||||
@mock.patch.object(image_cache.ImageCache, '_download_image',
|
mock_clean_up, mock_image_service):
|
||||||
autospec=True)
|
|
||||||
def test_fetch_image_no_master_dir(self, mock_download, mock_clean_up,
|
|
||||||
mock_fetch):
|
|
||||||
self.cache.master_dir = None
|
self.cache.master_dir = None
|
||||||
self.cache.fetch_image(self.uuid, self.dest_path)
|
self.cache.fetch_image(self.uuid, self.dest_path)
|
||||||
self.assertFalse(mock_download.called)
|
self.assertFalse(mock_download.called)
|
||||||
mock_fetch.assert_called_once_with(
|
mock_fetch.assert_called_once_with(
|
||||||
None, self.uuid, self.dest_path, True)
|
None, self.uuid, self.dest_path, True)
|
||||||
self.assertFalse(mock_clean_up.called)
|
self.assertFalse(mock_clean_up.called)
|
||||||
|
mock_image_service.assert_not_called()
|
||||||
|
|
||||||
@mock.patch.object(image_cache, '_fetch', autospec=True)
|
@mock.patch.object(image_cache, '_fetch', autospec=True)
|
||||||
@mock.patch.object(image_cache.ImageCache, 'clean_up', autospec=True)
|
|
||||||
@mock.patch.object(image_cache.ImageCache, '_download_image',
|
|
||||||
autospec=True)
|
|
||||||
def test_fetch_image_no_master_dir_memory_low(self,
|
def test_fetch_image_no_master_dir_memory_low(self,
|
||||||
|
mock_fetch,
|
||||||
mock_download,
|
mock_download,
|
||||||
mock_clean_up,
|
mock_clean_up,
|
||||||
mock_fetch):
|
mock_image_service):
|
||||||
mock_fetch.side_effect = exception.InsufficentMemory
|
mock_fetch.side_effect = exception.InsufficentMemory
|
||||||
self.cache.master_dir = None
|
self.cache.master_dir = None
|
||||||
self.assertRaises(exception.InsufficentMemory,
|
self.assertRaises(exception.InsufficentMemory,
|
||||||
@ -79,10 +82,8 @@ class TestImageCacheFetch(base.TestCase):
|
|||||||
mock_fetch.assert_called_once_with(
|
mock_fetch.assert_called_once_with(
|
||||||
None, self.uuid, self.dest_path, True)
|
None, self.uuid, self.dest_path, True)
|
||||||
self.assertFalse(mock_clean_up.called)
|
self.assertFalse(mock_clean_up.called)
|
||||||
|
mock_image_service.assert_not_called()
|
||||||
|
|
||||||
@mock.patch.object(image_cache.ImageCache, 'clean_up', autospec=True)
|
|
||||||
@mock.patch.object(image_cache.ImageCache, '_download_image',
|
|
||||||
autospec=True)
|
|
||||||
@mock.patch.object(os, 'link', autospec=True)
|
@mock.patch.object(os, 'link', autospec=True)
|
||||||
@mock.patch.object(image_cache, '_delete_dest_path_if_stale',
|
@mock.patch.object(image_cache, '_delete_dest_path_if_stale',
|
||||||
return_value=True, autospec=True)
|
return_value=True, autospec=True)
|
||||||
@ -90,18 +91,18 @@ class TestImageCacheFetch(base.TestCase):
|
|||||||
return_value=True, autospec=True)
|
return_value=True, autospec=True)
|
||||||
def test_fetch_image_dest_and_master_uptodate(
|
def test_fetch_image_dest_and_master_uptodate(
|
||||||
self, mock_cache_upd, mock_dest_upd, mock_link, mock_download,
|
self, mock_cache_upd, mock_dest_upd, mock_link, mock_download,
|
||||||
mock_clean_up):
|
mock_clean_up, mock_image_service):
|
||||||
self.cache.fetch_image(self.uuid, self.dest_path)
|
self.cache.fetch_image(self.uuid, self.dest_path)
|
||||||
mock_cache_upd.assert_called_once_with(self.master_path, self.uuid,
|
mock_cache_upd.assert_called_once_with(
|
||||||
None)
|
self.master_path, self.uuid,
|
||||||
|
mock_image_service.return_value.show.return_value)
|
||||||
mock_dest_upd.assert_called_once_with(self.master_path, self.dest_path)
|
mock_dest_upd.assert_called_once_with(self.master_path, self.dest_path)
|
||||||
self.assertFalse(mock_link.called)
|
self.assertFalse(mock_link.called)
|
||||||
self.assertFalse(mock_download.called)
|
self.assertFalse(mock_download.called)
|
||||||
self.assertFalse(mock_clean_up.called)
|
self.assertFalse(mock_clean_up.called)
|
||||||
|
mock_image_service.assert_called_once_with(self.uuid, context=None)
|
||||||
|
mock_image_service.return_value.show.assert_called_once_with(self.uuid)
|
||||||
|
|
||||||
@mock.patch.object(image_cache.ImageCache, 'clean_up', autospec=True)
|
|
||||||
@mock.patch.object(image_cache.ImageCache, '_download_image',
|
|
||||||
autospec=True)
|
|
||||||
@mock.patch.object(os, 'link', autospec=True)
|
@mock.patch.object(os, 'link', autospec=True)
|
||||||
@mock.patch.object(image_cache, '_delete_dest_path_if_stale',
|
@mock.patch.object(image_cache, '_delete_dest_path_if_stale',
|
||||||
return_value=True, autospec=True)
|
return_value=True, autospec=True)
|
||||||
@ -109,19 +110,17 @@ class TestImageCacheFetch(base.TestCase):
|
|||||||
return_value=True, autospec=True)
|
return_value=True, autospec=True)
|
||||||
def test_fetch_image_dest_and_master_uptodate_no_force_raw(
|
def test_fetch_image_dest_and_master_uptodate_no_force_raw(
|
||||||
self, mock_cache_upd, mock_dest_upd, mock_link, mock_download,
|
self, mock_cache_upd, mock_dest_upd, mock_link, mock_download,
|
||||||
mock_clean_up):
|
mock_clean_up, mock_image_service):
|
||||||
master_path = os.path.join(self.master_dir, self.uuid)
|
master_path = os.path.join(self.master_dir, self.uuid)
|
||||||
self.cache.fetch_image(self.uuid, self.dest_path, force_raw=False)
|
self.cache.fetch_image(self.uuid, self.dest_path, force_raw=False)
|
||||||
mock_cache_upd.assert_called_once_with(master_path, self.uuid,
|
mock_cache_upd.assert_called_once_with(
|
||||||
None)
|
master_path, self.uuid,
|
||||||
|
mock_image_service.return_value.show.return_value)
|
||||||
mock_dest_upd.assert_called_once_with(master_path, self.dest_path)
|
mock_dest_upd.assert_called_once_with(master_path, self.dest_path)
|
||||||
self.assertFalse(mock_link.called)
|
self.assertFalse(mock_link.called)
|
||||||
self.assertFalse(mock_download.called)
|
self.assertFalse(mock_download.called)
|
||||||
self.assertFalse(mock_clean_up.called)
|
self.assertFalse(mock_clean_up.called)
|
||||||
|
|
||||||
@mock.patch.object(image_cache.ImageCache, 'clean_up', autospec=True)
|
|
||||||
@mock.patch.object(image_cache.ImageCache, '_download_image',
|
|
||||||
autospec=True)
|
|
||||||
@mock.patch.object(os, 'link', autospec=True)
|
@mock.patch.object(os, 'link', autospec=True)
|
||||||
@mock.patch.object(image_cache, '_delete_dest_path_if_stale',
|
@mock.patch.object(image_cache, '_delete_dest_path_if_stale',
|
||||||
return_value=False, autospec=True)
|
return_value=False, autospec=True)
|
||||||
@ -129,18 +128,16 @@ class TestImageCacheFetch(base.TestCase):
|
|||||||
return_value=True, autospec=True)
|
return_value=True, autospec=True)
|
||||||
def test_fetch_image_dest_out_of_date(
|
def test_fetch_image_dest_out_of_date(
|
||||||
self, mock_cache_upd, mock_dest_upd, mock_link, mock_download,
|
self, mock_cache_upd, mock_dest_upd, mock_link, mock_download,
|
||||||
mock_clean_up):
|
mock_clean_up, mock_image_service):
|
||||||
self.cache.fetch_image(self.uuid, self.dest_path)
|
self.cache.fetch_image(self.uuid, self.dest_path)
|
||||||
mock_cache_upd.assert_called_once_with(self.master_path, self.uuid,
|
mock_cache_upd.assert_called_once_with(
|
||||||
None)
|
self.master_path, self.uuid,
|
||||||
|
mock_image_service.return_value.show.return_value)
|
||||||
mock_dest_upd.assert_called_once_with(self.master_path, self.dest_path)
|
mock_dest_upd.assert_called_once_with(self.master_path, self.dest_path)
|
||||||
mock_link.assert_called_once_with(self.master_path, self.dest_path)
|
mock_link.assert_called_once_with(self.master_path, self.dest_path)
|
||||||
self.assertFalse(mock_download.called)
|
self.assertFalse(mock_download.called)
|
||||||
self.assertFalse(mock_clean_up.called)
|
self.assertFalse(mock_clean_up.called)
|
||||||
|
|
||||||
@mock.patch.object(image_cache.ImageCache, 'clean_up', autospec=True)
|
|
||||||
@mock.patch.object(image_cache.ImageCache, '_download_image',
|
|
||||||
autospec=True)
|
|
||||||
@mock.patch.object(os, 'link', autospec=True)
|
@mock.patch.object(os, 'link', autospec=True)
|
||||||
@mock.patch.object(image_cache, '_delete_dest_path_if_stale',
|
@mock.patch.object(image_cache, '_delete_dest_path_if_stale',
|
||||||
return_value=True, autospec=True)
|
return_value=True, autospec=True)
|
||||||
@ -148,20 +145,21 @@ class TestImageCacheFetch(base.TestCase):
|
|||||||
return_value=False, autospec=True)
|
return_value=False, autospec=True)
|
||||||
def test_fetch_image_master_out_of_date(
|
def test_fetch_image_master_out_of_date(
|
||||||
self, mock_cache_upd, mock_dest_upd, mock_link, mock_download,
|
self, mock_cache_upd, mock_dest_upd, mock_link, mock_download,
|
||||||
mock_clean_up):
|
mock_clean_up, mock_image_service):
|
||||||
self.cache.fetch_image(self.uuid, self.dest_path)
|
self.cache.fetch_image(self.uuid, self.dest_path)
|
||||||
mock_cache_upd.assert_called_once_with(self.master_path, self.uuid,
|
mock_cache_upd.assert_called_once_with(
|
||||||
None)
|
self.master_path, self.uuid,
|
||||||
|
mock_image_service.return_value.show.return_value)
|
||||||
mock_dest_upd.assert_called_once_with(self.master_path, self.dest_path)
|
mock_dest_upd.assert_called_once_with(self.master_path, self.dest_path)
|
||||||
self.assertFalse(mock_link.called)
|
self.assertFalse(mock_link.called)
|
||||||
mock_download.assert_called_once_with(
|
mock_download.assert_called_once_with(
|
||||||
self.cache, self.uuid, self.master_path, self.dest_path,
|
self.cache, self.uuid, self.master_path, self.dest_path,
|
||||||
|
mock_image_service.return_value.show.return_value,
|
||||||
ctx=None, force_raw=True)
|
ctx=None, force_raw=True)
|
||||||
mock_clean_up.assert_called_once_with(self.cache)
|
mock_clean_up.assert_called_once_with(self.cache)
|
||||||
|
mock_image_service.assert_called_once_with(self.uuid, context=None)
|
||||||
|
mock_image_service.return_value.show.assert_called_once_with(self.uuid)
|
||||||
|
|
||||||
@mock.patch.object(image_cache.ImageCache, 'clean_up', autospec=True)
|
|
||||||
@mock.patch.object(image_cache.ImageCache, '_download_image',
|
|
||||||
autospec=True)
|
|
||||||
@mock.patch.object(os, 'link', autospec=True)
|
@mock.patch.object(os, 'link', autospec=True)
|
||||||
@mock.patch.object(image_cache, '_delete_dest_path_if_stale',
|
@mock.patch.object(image_cache, '_delete_dest_path_if_stale',
|
||||||
return_value=True, autospec=True)
|
return_value=True, autospec=True)
|
||||||
@ -169,21 +167,21 @@ class TestImageCacheFetch(base.TestCase):
|
|||||||
return_value=False, autospec=True)
|
return_value=False, autospec=True)
|
||||||
def test_fetch_image_both_master_and_dest_out_of_date(
|
def test_fetch_image_both_master_and_dest_out_of_date(
|
||||||
self, mock_cache_upd, mock_dest_upd, mock_link, mock_download,
|
self, mock_cache_upd, mock_dest_upd, mock_link, mock_download,
|
||||||
mock_clean_up):
|
mock_clean_up, mock_image_service):
|
||||||
self.cache.fetch_image(self.uuid, self.dest_path)
|
self.cache.fetch_image(self.uuid, self.dest_path)
|
||||||
mock_cache_upd.assert_called_once_with(self.master_path, self.uuid,
|
mock_cache_upd.assert_called_once_with(
|
||||||
None)
|
self.master_path, self.uuid,
|
||||||
|
mock_image_service.return_value.show.return_value)
|
||||||
mock_dest_upd.assert_called_once_with(self.master_path, self.dest_path)
|
mock_dest_upd.assert_called_once_with(self.master_path, self.dest_path)
|
||||||
self.assertFalse(mock_link.called)
|
self.assertFalse(mock_link.called)
|
||||||
mock_download.assert_called_once_with(
|
mock_download.assert_called_once_with(
|
||||||
self.cache, self.uuid, self.master_path, self.dest_path,
|
self.cache, self.uuid, self.master_path, self.dest_path,
|
||||||
|
mock_image_service.return_value.show.return_value,
|
||||||
ctx=None, force_raw=True)
|
ctx=None, force_raw=True)
|
||||||
mock_clean_up.assert_called_once_with(self.cache)
|
mock_clean_up.assert_called_once_with(self.cache)
|
||||||
|
|
||||||
@mock.patch.object(image_cache.ImageCache, 'clean_up', autospec=True)
|
def test_fetch_image_not_uuid(self, mock_download, mock_clean_up,
|
||||||
@mock.patch.object(image_cache.ImageCache, '_download_image',
|
mock_image_service):
|
||||||
autospec=True)
|
|
||||||
def test_fetch_image_not_uuid(self, mock_download, mock_clean_up):
|
|
||||||
href = u'http://abc.com/ubuntu.qcow2'
|
href = u'http://abc.com/ubuntu.qcow2'
|
||||||
href_converted = str(uuid.uuid5(uuid.NAMESPACE_URL, href))
|
href_converted = str(uuid.uuid5(uuid.NAMESPACE_URL, href))
|
||||||
master_path = ''.join([os.path.join(self.master_dir, href_converted),
|
master_path = ''.join([os.path.join(self.master_dir, href_converted),
|
||||||
@ -191,24 +189,27 @@ class TestImageCacheFetch(base.TestCase):
|
|||||||
self.cache.fetch_image(href, self.dest_path)
|
self.cache.fetch_image(href, self.dest_path)
|
||||||
mock_download.assert_called_once_with(
|
mock_download.assert_called_once_with(
|
||||||
self.cache, href, master_path, self.dest_path,
|
self.cache, href, master_path, self.dest_path,
|
||||||
|
mock_image_service.return_value.show.return_value,
|
||||||
ctx=None, force_raw=True)
|
ctx=None, force_raw=True)
|
||||||
self.assertTrue(mock_clean_up.called)
|
self.assertTrue(mock_clean_up.called)
|
||||||
|
|
||||||
@mock.patch.object(image_cache.ImageCache, 'clean_up', autospec=True)
|
|
||||||
@mock.patch.object(image_cache.ImageCache, '_download_image',
|
|
||||||
autospec=True)
|
|
||||||
def test_fetch_image_not_uuid_no_force_raw(self, mock_download,
|
def test_fetch_image_not_uuid_no_force_raw(self, mock_download,
|
||||||
mock_clean_up):
|
mock_clean_up,
|
||||||
|
mock_image_service):
|
||||||
href = u'http://abc.com/ubuntu.qcow2'
|
href = u'http://abc.com/ubuntu.qcow2'
|
||||||
href_converted = str(uuid.uuid5(uuid.NAMESPACE_URL, href))
|
href_converted = str(uuid.uuid5(uuid.NAMESPACE_URL, href))
|
||||||
master_path = os.path.join(self.master_dir, href_converted)
|
master_path = os.path.join(self.master_dir, href_converted)
|
||||||
self.cache.fetch_image(href, self.dest_path, force_raw=False)
|
self.cache.fetch_image(href, self.dest_path, force_raw=False)
|
||||||
mock_download.assert_called_once_with(
|
mock_download.assert_called_once_with(
|
||||||
self.cache, href, master_path, self.dest_path,
|
self.cache, href, master_path, self.dest_path,
|
||||||
|
mock_image_service.return_value.show.return_value,
|
||||||
ctx=None, force_raw=False)
|
ctx=None, force_raw=False)
|
||||||
self.assertTrue(mock_clean_up.called)
|
self.assertTrue(mock_clean_up.called)
|
||||||
|
|
||||||
@mock.patch.object(image_cache, '_fetch', autospec=True)
|
|
||||||
|
@mock.patch.object(image_cache, '_fetch', autospec=True)
|
||||||
|
class TestImageCacheDownload(BaseTest):
|
||||||
|
|
||||||
def test__download_image(self, mock_fetch):
|
def test__download_image(self, mock_fetch):
|
||||||
def _fake_fetch(ctx, uuid, tmp_path, *args):
|
def _fake_fetch(ctx, uuid, tmp_path, *args):
|
||||||
self.assertEqual(self.uuid, uuid)
|
self.assertEqual(self.uuid, uuid)
|
||||||
@ -218,7 +219,8 @@ class TestImageCacheFetch(base.TestCase):
|
|||||||
fp.write("TEST")
|
fp.write("TEST")
|
||||||
|
|
||||||
mock_fetch.side_effect = _fake_fetch
|
mock_fetch.side_effect = _fake_fetch
|
||||||
self.cache._download_image(self.uuid, self.master_path, self.dest_path)
|
self.cache._download_image(self.uuid, self.master_path, self.dest_path,
|
||||||
|
self.img_info)
|
||||||
self.assertTrue(os.path.isfile(self.dest_path))
|
self.assertTrue(os.path.isfile(self.dest_path))
|
||||||
self.assertTrue(os.path.isfile(self.master_path))
|
self.assertTrue(os.path.isfile(self.master_path))
|
||||||
self.assertEqual(os.stat(self.dest_path).st_ino,
|
self.assertEqual(os.stat(self.dest_path).st_ino,
|
||||||
@ -226,7 +228,6 @@ class TestImageCacheFetch(base.TestCase):
|
|||||||
with open(self.dest_path) as fp:
|
with open(self.dest_path) as fp:
|
||||||
self.assertEqual("TEST", fp.read())
|
self.assertEqual("TEST", fp.read())
|
||||||
|
|
||||||
@mock.patch.object(image_cache, '_fetch', autospec=True)
|
|
||||||
def test__download_image_large_url(self, mock_fetch):
|
def test__download_image_large_url(self, mock_fetch):
|
||||||
# A long enough URL may exceed the file name limits of the file system.
|
# A long enough URL may exceed the file name limits of the file system.
|
||||||
# Make sure we don't use any parts of the URL anywhere.
|
# Make sure we don't use any parts of the URL anywhere.
|
||||||
@ -240,7 +241,8 @@ class TestImageCacheFetch(base.TestCase):
|
|||||||
fp.write("TEST")
|
fp.write("TEST")
|
||||||
|
|
||||||
mock_fetch.side_effect = _fake_fetch
|
mock_fetch.side_effect = _fake_fetch
|
||||||
self.cache._download_image(url, self.master_path, self.dest_path)
|
self.cache._download_image(url, self.master_path, self.dest_path,
|
||||||
|
self.img_info)
|
||||||
self.assertTrue(os.path.isfile(self.dest_path))
|
self.assertTrue(os.path.isfile(self.dest_path))
|
||||||
self.assertTrue(os.path.isfile(self.master_path))
|
self.assertTrue(os.path.isfile(self.master_path))
|
||||||
self.assertEqual(os.stat(self.dest_path).st_ino,
|
self.assertEqual(os.stat(self.dest_path).st_ino,
|
||||||
@ -248,123 +250,97 @@ class TestImageCacheFetch(base.TestCase):
|
|||||||
with open(self.dest_path) as fp:
|
with open(self.dest_path) as fp:
|
||||||
self.assertEqual("TEST", fp.read())
|
self.assertEqual("TEST", fp.read())
|
||||||
|
|
||||||
@mock.patch.object(image_cache, '_fetch', autospec=True)
|
|
||||||
@mock.patch.object(image_cache, 'LOG', autospec=True)
|
@mock.patch.object(image_cache, 'LOG', autospec=True)
|
||||||
@mock.patch.object(os, 'link', autospec=True)
|
@mock.patch.object(os, 'link', autospec=True)
|
||||||
def test__download_image_linkfail(self, mock_link, mock_log, mock_fetch):
|
def test__download_image_linkfail(self, mock_link, mock_log, mock_fetch):
|
||||||
mock_link.side_effect = [None, OSError]
|
mock_link.side_effect = [None, OSError]
|
||||||
self.assertRaises(exception.ImageDownloadFailed,
|
self.assertRaises(exception.ImageDownloadFailed,
|
||||||
self.cache._download_image,
|
self.cache._download_image,
|
||||||
self.uuid, self.master_path, self.dest_path)
|
self.uuid, self.master_path, self.dest_path,
|
||||||
|
self.img_info)
|
||||||
self.assertTrue(mock_fetch.called)
|
self.assertTrue(mock_fetch.called)
|
||||||
self.assertEqual(2, mock_link.call_count)
|
self.assertEqual(2, mock_link.call_count)
|
||||||
self.assertTrue(mock_log.error.called)
|
self.assertTrue(mock_log.error.called)
|
||||||
|
|
||||||
@mock.patch.object(image_cache, '_fetch', autospec=True)
|
|
||||||
def test__download_image_raises_memory_guard(self, mock_fetch):
|
def test__download_image_raises_memory_guard(self, mock_fetch):
|
||||||
mock_fetch.side_effect = exception.InsufficentMemory
|
mock_fetch.side_effect = exception.InsufficentMemory
|
||||||
self.assertRaises(exception.InsufficentMemory,
|
self.assertRaises(exception.InsufficentMemory,
|
||||||
self.cache._download_image,
|
self.cache._download_image,
|
||||||
self.uuid, self.master_path,
|
self.uuid, self.master_path,
|
||||||
self.dest_path)
|
self.dest_path, self.img_info)
|
||||||
|
|
||||||
|
|
||||||
@mock.patch.object(os, 'unlink', autospec=True)
|
@mock.patch.object(os, 'unlink', autospec=True)
|
||||||
class TestUpdateImages(base.TestCase):
|
class TestUpdateImages(BaseTest):
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(TestUpdateImages, self).setUp()
|
|
||||||
self.master_dir = tempfile.mkdtemp()
|
|
||||||
self.dest_dir = tempfile.mkdtemp()
|
|
||||||
self.dest_path = os.path.join(self.dest_dir, 'dest')
|
|
||||||
self.uuid = uuidutils.generate_uuid()
|
|
||||||
self.master_path = os.path.join(self.master_dir, self.uuid)
|
|
||||||
|
|
||||||
@mock.patch.object(os.path, 'exists', return_value=False, autospec=True)
|
@mock.patch.object(os.path, 'exists', return_value=False, autospec=True)
|
||||||
@mock.patch.object(image_service, 'get_image_service', autospec=True)
|
|
||||||
def test__delete_master_path_if_stale_glance_img_not_cached(
|
def test__delete_master_path_if_stale_glance_img_not_cached(
|
||||||
self, mock_gis, mock_path_exists, mock_unlink):
|
self, mock_path_exists, mock_unlink):
|
||||||
res = image_cache._delete_master_path_if_stale(self.master_path,
|
res = image_cache._delete_master_path_if_stale(self.master_path,
|
||||||
self.uuid, None)
|
self.uuid,
|
||||||
self.assertFalse(mock_gis.called)
|
self.img_info)
|
||||||
self.assertFalse(mock_unlink.called)
|
self.assertFalse(mock_unlink.called)
|
||||||
mock_path_exists.assert_called_once_with(self.master_path)
|
mock_path_exists.assert_called_once_with(self.master_path)
|
||||||
self.assertFalse(res)
|
self.assertFalse(res)
|
||||||
|
|
||||||
@mock.patch.object(os.path, 'exists', return_value=True, autospec=True)
|
@mock.patch.object(os.path, 'exists', return_value=True, autospec=True)
|
||||||
@mock.patch.object(image_service, 'get_image_service', autospec=True)
|
|
||||||
def test__delete_master_path_if_stale_glance_img(
|
def test__delete_master_path_if_stale_glance_img(
|
||||||
self, mock_gis, mock_path_exists, mock_unlink):
|
self, mock_path_exists, mock_unlink):
|
||||||
res = image_cache._delete_master_path_if_stale(self.master_path,
|
res = image_cache._delete_master_path_if_stale(self.master_path,
|
||||||
self.uuid, None)
|
self.uuid,
|
||||||
self.assertFalse(mock_gis.called)
|
self.img_info)
|
||||||
self.assertFalse(mock_unlink.called)
|
self.assertFalse(mock_unlink.called)
|
||||||
mock_path_exists.assert_called_once_with(self.master_path)
|
mock_path_exists.assert_called_once_with(self.master_path)
|
||||||
self.assertTrue(res)
|
self.assertTrue(res)
|
||||||
|
|
||||||
@mock.patch.object(image_service, 'get_image_service', autospec=True)
|
def test__delete_master_path_if_stale_no_master(self, mock_unlink):
|
||||||
def test__delete_master_path_if_stale_no_master(self, mock_gis,
|
|
||||||
mock_unlink):
|
|
||||||
res = image_cache._delete_master_path_if_stale(self.master_path,
|
res = image_cache._delete_master_path_if_stale(self.master_path,
|
||||||
'http://11', None)
|
'http://11',
|
||||||
self.assertFalse(mock_gis.called)
|
self.img_info)
|
||||||
self.assertFalse(mock_unlink.called)
|
self.assertFalse(mock_unlink.called)
|
||||||
self.assertFalse(res)
|
self.assertFalse(res)
|
||||||
|
|
||||||
@mock.patch.object(image_service, 'get_image_service', autospec=True)
|
def test__delete_master_path_if_stale_no_updated_at(self, mock_unlink):
|
||||||
def test__delete_master_path_if_stale_no_updated_at(self, mock_gis,
|
|
||||||
mock_unlink):
|
|
||||||
touch(self.master_path)
|
touch(self.master_path)
|
||||||
href = 'http://awesomefreeimages.al/img111'
|
href = 'http://awesomefreeimages.al/img111'
|
||||||
mock_gis.return_value.show.return_value = {}
|
|
||||||
res = image_cache._delete_master_path_if_stale(self.master_path, href,
|
res = image_cache._delete_master_path_if_stale(self.master_path, href,
|
||||||
None)
|
self.img_info)
|
||||||
mock_gis.assert_called_once_with(href, context=None)
|
|
||||||
mock_unlink.assert_called_once_with(self.master_path)
|
mock_unlink.assert_called_once_with(self.master_path)
|
||||||
self.assertFalse(res)
|
self.assertFalse(res)
|
||||||
|
|
||||||
@mock.patch.object(image_service, 'get_image_service', autospec=True)
|
def test__delete_master_path_if_stale_master_up_to_date(self, mock_unlink):
|
||||||
def test__delete_master_path_if_stale_master_up_to_date(self, mock_gis,
|
|
||||||
mock_unlink):
|
|
||||||
touch(self.master_path)
|
touch(self.master_path)
|
||||||
href = 'http://awesomefreeimages.al/img999'
|
href = 'http://awesomefreeimages.al/img999'
|
||||||
mock_gis.return_value.show.return_value = {
|
self.img_info = {
|
||||||
'updated_at': datetime.datetime(1999, 11, 15, 8, 12, 31)
|
'updated_at': datetime.datetime(1999, 11, 15, 8, 12, 31)
|
||||||
}
|
}
|
||||||
res = image_cache._delete_master_path_if_stale(self.master_path, href,
|
res = image_cache._delete_master_path_if_stale(self.master_path, href,
|
||||||
None)
|
self.img_info)
|
||||||
mock_gis.assert_called_once_with(href, context=None)
|
|
||||||
self.assertFalse(mock_unlink.called)
|
self.assertFalse(mock_unlink.called)
|
||||||
self.assertTrue(res)
|
self.assertTrue(res)
|
||||||
|
|
||||||
@mock.patch.object(image_service, 'get_image_service', autospec=True)
|
def test__delete_master_path_if_stale_master_same_time(self, mock_unlink):
|
||||||
def test__delete_master_path_if_stale_master_same_time(self, mock_gis,
|
|
||||||
mock_unlink):
|
|
||||||
# When times identical should not delete cached file
|
# When times identical should not delete cached file
|
||||||
touch(self.master_path)
|
touch(self.master_path)
|
||||||
mtime = utils.unix_file_modification_datetime(self.master_path)
|
mtime = utils.unix_file_modification_datetime(self.master_path)
|
||||||
href = 'http://awesomefreeimages.al/img999'
|
href = 'http://awesomefreeimages.al/img999'
|
||||||
mock_gis.return_value.show.return_value = {
|
self.img_info = {
|
||||||
'updated_at': mtime
|
'updated_at': mtime
|
||||||
}
|
}
|
||||||
res = image_cache._delete_master_path_if_stale(self.master_path, href,
|
res = image_cache._delete_master_path_if_stale(self.master_path, href,
|
||||||
None)
|
self.img_info)
|
||||||
mock_gis.assert_called_once_with(href, context=None)
|
|
||||||
self.assertFalse(mock_unlink.called)
|
self.assertFalse(mock_unlink.called)
|
||||||
self.assertTrue(res)
|
self.assertTrue(res)
|
||||||
|
|
||||||
@mock.patch.object(image_service, 'get_image_service', autospec=True)
|
def test__delete_master_path_if_stale_out_of_date(self, mock_unlink):
|
||||||
def test__delete_master_path_if_stale_out_of_date(self, mock_gis,
|
|
||||||
mock_unlink):
|
|
||||||
touch(self.master_path)
|
touch(self.master_path)
|
||||||
href = 'http://awesomefreeimages.al/img999'
|
href = 'http://awesomefreeimages.al/img999'
|
||||||
mock_gis.return_value.show.return_value = {
|
self.img_info = {
|
||||||
'updated_at': datetime.datetime((datetime.datetime.utcnow().year
|
'updated_at': datetime.datetime((datetime.datetime.utcnow().year
|
||||||
+ 1), 11, 15, 8, 12, 31)
|
+ 1), 11, 15, 8, 12, 31)
|
||||||
}
|
}
|
||||||
res = image_cache._delete_master_path_if_stale(self.master_path, href,
|
res = image_cache._delete_master_path_if_stale(self.master_path, href,
|
||||||
None)
|
self.img_info)
|
||||||
mock_gis.assert_called_once_with(href, context=None)
|
|
||||||
mock_unlink.assert_called_once_with(self.master_path)
|
mock_unlink.assert_called_once_with(self.master_path)
|
||||||
self.assertFalse(res)
|
self.assertFalse(res)
|
||||||
|
|
||||||
@ -552,7 +528,7 @@ class TestImageCacheCleanUp(base.TestCase):
|
|||||||
mock_fetch.side_effect = _fake_fetch
|
mock_fetch.side_effect = _fake_fetch
|
||||||
master_path = os.path.join(self.master_dir, 'uuid')
|
master_path = os.path.join(self.master_dir, 'uuid')
|
||||||
dest_path = os.path.join(tempfile.mkdtemp(), 'dest')
|
dest_path = os.path.join(tempfile.mkdtemp(), 'dest')
|
||||||
self.cache._download_image('uuid', master_path, dest_path)
|
self.cache._download_image('uuid', master_path, dest_path, {})
|
||||||
self.assertTrue(mock_rmtree.called)
|
self.assertTrue(mock_rmtree.called)
|
||||||
|
|
||||||
@mock.patch.object(utils, 'rmtree_without_raise', autospec=True)
|
@mock.patch.object(utils, 'rmtree_without_raise', autospec=True)
|
||||||
@ -561,7 +537,7 @@ class TestImageCacheCleanUp(base.TestCase):
|
|||||||
mock_fetch.side_effect = exception.IronicException
|
mock_fetch.side_effect = exception.IronicException
|
||||||
self.assertRaises(exception.IronicException,
|
self.assertRaises(exception.IronicException,
|
||||||
self.cache._download_image,
|
self.cache._download_image,
|
||||||
'uuid', 'fake', 'fake')
|
'uuid', 'fake', 'fake', {})
|
||||||
self.assertTrue(mock_rmtree.called)
|
self.assertTrue(mock_rmtree.called)
|
||||||
|
|
||||||
@mock.patch.object(image_cache.LOG, 'warning', autospec=True)
|
@mock.patch.object(image_cache.LOG, 'warning', autospec=True)
|
||||||
|
8
releasenotes/notes/no-cache-df7caa45f3d8b6d7.yaml
Normal file
8
releasenotes/notes/no-cache-df7caa45f3d8b6d7.yaml
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
---
|
||||||
|
fixes:
|
||||||
|
- |
|
||||||
|
The image cache now respects the ``Cache-Control: no-store`` header
|
||||||
|
for HTTP(s) images.
|
||||||
|
- |
|
||||||
|
File images are no longer cached in the image cache to avoid unnecessary
|
||||||
|
consumption of the disk space.
|
Loading…
Reference in New Issue
Block a user