glance/glance/tests/unit/test_image_cache.py

371 lines
11 KiB
Python

# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2011 OpenStack, LLC
# 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.
from contextlib import contextmanager
import os
import random
import shutil
import StringIO
import unittest
import stubout
from glance import image_cache
from glance.common import utils
from glance.openstack.common import cfg
from glance.tests import utils as test_utils
from glance.tests.utils import skip_if_disabled, xattr_writes_supported
FIXTURE_LENGTH = 1024
FIXTURE_DATA = '*' * FIXTURE_LENGTH
class ImageCacheTestCase(object):
def _setup_fixture_file(self):
FIXTURE_FILE = StringIO.StringIO(FIXTURE_DATA)
self.assertFalse(self.cache.is_cached(1))
self.assertTrue(self.cache.cache_image_file(1, FIXTURE_FILE))
self.assertTrue(self.cache.is_cached(1))
@skip_if_disabled
def test_is_cached(self):
"""
Verify is_cached(1) returns 0, then add something to the cache
and verify is_cached(1) returns 1.
"""
self._setup_fixture_file()
@skip_if_disabled
def test_read(self):
"""
Verify is_cached(1) returns 0, then add something to the cache
and verify after a subsequent read from the cache that
is_cached(1) returns 1.
"""
self._setup_fixture_file()
buff = StringIO.StringIO()
with self.cache.open_for_read(1) as cache_file:
for chunk in cache_file:
buff.write(chunk)
self.assertEqual(FIXTURE_DATA, buff.getvalue())
@skip_if_disabled
def test_open_for_read(self):
"""
Test convenience wrapper for opening a cache file via
its image identifier.
"""
self._setup_fixture_file()
buff = StringIO.StringIO()
with self.cache.open_for_read(1) as cache_file:
for chunk in cache_file:
buff.write(chunk)
self.assertEqual(FIXTURE_DATA, buff.getvalue())
@skip_if_disabled
def test_get_image_size(self):
"""
Test convenience wrapper for querying cache file size via
its image identifier.
"""
self._setup_fixture_file()
size = self.cache.get_image_size(1)
self.assertEqual(FIXTURE_LENGTH, size)
@skip_if_disabled
def test_delete(self):
"""
Test delete method that removes an image from the cache
"""
self._setup_fixture_file()
self.cache.delete_cached_image(1)
self.assertFalse(self.cache.is_cached(1))
@skip_if_disabled
def test_delete_all(self):
"""
Test delete method that removes an image from the cache
"""
for image_id in (1, 2):
self.assertFalse(self.cache.is_cached(image_id))
for image_id in (1, 2):
FIXTURE_FILE = StringIO.StringIO(FIXTURE_DATA)
self.assertTrue(self.cache.cache_image_file(image_id,
FIXTURE_FILE))
for image_id in (1, 2):
self.assertTrue(self.cache.is_cached(image_id))
self.cache.delete_all_cached_images()
for image_id in (1, 2):
self.assertFalse(self.cache.is_cached(image_id))
@skip_if_disabled
def test_clean_stalled(self):
"""
Test the clean method removes expected images
"""
incomplete_file_path = os.path.join(self.cache_dir, 'incomplete', '1')
incomplete_file = open(incomplete_file_path, 'w')
incomplete_file.write(FIXTURE_DATA)
incomplete_file.close()
self.assertTrue(os.path.exists(incomplete_file_path))
self.cache.clean(stall_time=0)
self.assertFalse(os.path.exists(incomplete_file_path))
@skip_if_disabled
def test_prune(self):
"""
Test that pruning the cache works as expected...
"""
self.assertEqual(0, self.cache.get_cache_size())
# Add a bunch of images to the cache. The max cache
# size for the cache is set to 5KB and each image is
# 1K. We add 10 images to the cache and then we'll
# prune it. We should see only 5 images left after
# pruning, and the images that are least recently accessed
# should be the ones pruned...
for x in xrange(0, 10):
FIXTURE_FILE = StringIO.StringIO(FIXTURE_DATA)
self.assertTrue(self.cache.cache_image_file(x,
FIXTURE_FILE))
self.assertEqual(10 * 1024, self.cache.get_cache_size())
# OK, hit the images that are now cached...
for x in xrange(0, 10):
buff = StringIO.StringIO()
with self.cache.open_for_read(x) as cache_file:
for chunk in cache_file:
buff.write(chunk)
self.cache.prune()
self.assertEqual(5 * 1024, self.cache.get_cache_size())
for x in xrange(0, 5):
self.assertFalse(self.cache.is_cached(x),
"Image %s was cached!" % x)
for x in xrange(5, 10):
self.assertTrue(self.cache.is_cached(x),
"Image %s was not cached!" % x)
@skip_if_disabled
def test_queue(self):
"""
Test that queueing works properly
"""
self.assertFalse(self.cache.is_cached(1))
self.assertFalse(self.cache.is_queued(1))
FIXTURE_FILE = StringIO.StringIO(FIXTURE_DATA)
self.assertTrue(self.cache.queue_image(1))
self.assertTrue(self.cache.is_queued(1))
self.assertFalse(self.cache.is_cached(1))
# Should not return True if the image is already
# queued for caching...
self.assertFalse(self.cache.queue_image(1))
self.assertFalse(self.cache.is_cached(1))
# Test that we return False if we try to queue
# an image that has already been cached
self.assertTrue(self.cache.cache_image_file(1, FIXTURE_FILE))
self.assertFalse(self.cache.is_queued(1))
self.assertTrue(self.cache.is_cached(1))
self.assertFalse(self.cache.queue_image(1))
self.cache.delete_cached_image(1)
for x in xrange(0, 3):
self.assertTrue(self.cache.queue_image(x))
self.assertEqual(self.cache.get_queued_images(),
['0', '1', '2'])
class TestImageCacheXattr(unittest.TestCase,
ImageCacheTestCase):
"""Tests image caching when xattr is used in cache"""
def setUp(self):
"""
Test to see if the pre-requisites for the image cache
are working (python-xattr installed and xattr support on the
filesystem)
"""
if getattr(self, 'disable', False):
return
self.cache_dir = os.path.join("/", "tmp", "test.cache.%d" %
random.randint(0, 1000000))
utils.safe_mkdirs(self.cache_dir)
if not getattr(self, 'inited', False):
try:
import xattr
except ImportError:
self.inited = True
self.disabled = True
self.disabled_message = ("python-xattr not installed.")
return
self.inited = True
self.disabled = False
self.conf = test_utils.TestConfigOpts({
'image_cache_dir': self.cache_dir,
'image_cache_driver': 'xattr',
'image_cache_max_size': 1024 * 5,
'registry_host': '0.0.0.0',
'registry_port': 9191})
self.cache = image_cache.ImageCache(self.conf)
if not xattr_writes_supported(self.cache_dir):
self.inited = True
self.disabled = True
self.disabled_message = ("filesystem does not support xattr")
return
def tearDown(self):
if os.path.exists(self.cache_dir):
shutil.rmtree(self.cache_dir)
class TestImageCacheSqlite(unittest.TestCase,
ImageCacheTestCase):
"""Tests image caching when SQLite is used in cache"""
def setUp(self):
"""
Test to see if the pre-requisites for the image cache
are working (python-sqlite3 installed)
"""
if getattr(self, 'disable', False):
return
if not getattr(self, 'inited', False):
try:
import sqlite3
except ImportError:
self.inited = True
self.disabled = True
self.disabled_message = ("python-sqlite3 not installed.")
return
self.inited = True
self.disabled = False
self.cache_dir = os.path.join("/", "tmp", "test.cache.%d" %
random.randint(0, 1000000))
self.conf = test_utils.TestConfigOpts({
'image_cache_dir': self.cache_dir,
'image_cache_driver': 'sqlite',
'image_cache_max_size': 1024 * 5,
'registry_host': '0.0.0.0',
'registry_port': 9191})
self.cache = image_cache.ImageCache(self.conf)
def tearDown(self):
if os.path.exists(self.cache_dir):
shutil.rmtree(self.cache_dir)
class TestImageCacheNoDep(unittest.TestCase):
def setUp(self):
self.driver = None
def init_driver(self2):
self2.driver = self.driver
self.stubs = stubout.StubOutForTesting()
self.stubs.Set(image_cache.ImageCache, 'init_driver', init_driver)
def tearDown(self):
self.stubs.UnsetAll()
def test_get_caching_iter_when_write_fails(self):
class FailingFile(object):
def write(self, data):
if data == "Fail":
raise IOError
class FailingFileDriver(object):
def is_cacheable(self, *args, **kwargs):
return True
@contextmanager
def open_for_write(self, *args, **kwargs):
yield FailingFile()
self.driver = FailingFileDriver()
conf = cfg.ConfigOpts()
cache = image_cache.ImageCache(conf)
data = ['a', 'b', 'c', 'Fail', 'd', 'e', 'f']
caching_iter = cache.get_caching_iter('dummy_id', iter(data))
self.assertEqual(list(caching_iter), data)
def test_get_caching_iter_when_open_fails(self):
class OpenFailingDriver(object):
def is_cacheable(self, *args, **kwargs):
return True
@contextmanager
def open_for_write(self, *args, **kwargs):
raise IOError
self.driver = OpenFailingDriver()
conf = cfg.ConfigOpts()
cache = image_cache.ImageCache(conf)
data = ['a', 'b', 'c', 'd', 'e', 'f']
caching_iter = cache.get_caching_iter('dummy_id', iter(data))
self.assertEqual(list(caching_iter), data)