307 lines
14 KiB
Python
307 lines
14 KiB
Python
# Copyright 2014 Cloudbase Solutions Srl
|
|
# 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.
|
|
|
|
import os
|
|
|
|
import mock
|
|
from nova import exception
|
|
from nova import objects
|
|
from nova.tests.unit.objects import test_flavor
|
|
from oslo_config import cfg
|
|
from oslo_utils import units
|
|
|
|
from hyperv.nova import constants
|
|
from hyperv.nova import imagecache
|
|
from hyperv.tests import fake_instance
|
|
from hyperv.tests.unit import test_base
|
|
|
|
CONF = cfg.CONF
|
|
|
|
|
|
class ImageCacheTestCase(test_base.HyperVBaseTestCase):
|
|
"""Unit tests for the Hyper-V ImageCache class."""
|
|
|
|
FAKE_BASE_DIR = 'fake/base/dir'
|
|
FAKE_FORMAT = 'fake_format'
|
|
FAKE_IMAGE_REF = 'fake_image_ref'
|
|
FAKE_VHD_SIZE_GB = 1
|
|
|
|
def setUp(self):
|
|
super(ImageCacheTestCase, self).setUp()
|
|
|
|
self.context = 'fake-context'
|
|
self.instance = fake_instance.fake_instance_obj(self.context)
|
|
|
|
self.imagecache = imagecache.ImageCache()
|
|
self.imagecache._pathutils = mock.MagicMock()
|
|
self.imagecache._vhdutils = mock.MagicMock()
|
|
|
|
@mock.patch.object(imagecache.ImageCache, '_get_root_vhd_size_gb')
|
|
def test_resize_and_cache_vhd_smaller(self, mock_get_vhd_size_gb):
|
|
self.imagecache._vhdutils.get_vhd_size.return_value = {
|
|
'VirtualSize': (self.FAKE_VHD_SIZE_GB + 1) * units.Gi
|
|
}
|
|
mock_get_vhd_size_gb.return_value = self.FAKE_VHD_SIZE_GB
|
|
mock_internal_vhd_size = (
|
|
self.imagecache._vhdutils.get_internal_vhd_size_by_file_size)
|
|
mock_internal_vhd_size.return_value = self.FAKE_VHD_SIZE_GB * units.Gi
|
|
|
|
self.assertRaises(exception.FlavorDiskSmallerThanImage,
|
|
self.imagecache._resize_and_cache_vhd,
|
|
mock.sentinel.instance,
|
|
mock.sentinel.vhd_path)
|
|
|
|
self.imagecache._vhdutils.get_vhd_size.assert_called_once_with(
|
|
mock.sentinel.vhd_path)
|
|
mock_get_vhd_size_gb.assert_called_once_with(mock.sentinel.instance)
|
|
mock_internal_vhd_size.assert_called_once_with(
|
|
mock.sentinel.vhd_path, self.FAKE_VHD_SIZE_GB * units.Gi)
|
|
|
|
def _test_get_root_vhd_size_gb(self, old_flavor=True):
|
|
if old_flavor:
|
|
mock_flavor = objects.Flavor(**test_flavor.fake_flavor)
|
|
self.instance.old_flavor = mock_flavor
|
|
else:
|
|
self.instance.old_flavor = None
|
|
return self.imagecache._get_root_vhd_size_gb(self.instance)
|
|
|
|
def test_get_root_vhd_size_gb_old_flavor(self):
|
|
ret_val = self._test_get_root_vhd_size_gb()
|
|
self.assertEqual(test_flavor.fake_flavor['root_gb'], ret_val)
|
|
|
|
def test_get_root_vhd_size_gb(self):
|
|
ret_val = self._test_get_root_vhd_size_gb(old_flavor=False)
|
|
self.assertEqual(self.instance.root_gb, ret_val)
|
|
|
|
def _prepare_get_cached_image(self, path_exists=False, use_cow=False,
|
|
rescue_image_id=None,
|
|
image_format=constants.DISK_FORMAT_VHD):
|
|
self.instance.image_ref = self.FAKE_IMAGE_REF
|
|
self.instance.system_metadata = {'image_disk_format': image_format}
|
|
self.imagecache._pathutils.get_base_vhd_dir.return_value = (
|
|
self.FAKE_BASE_DIR)
|
|
self.imagecache._pathutils.exists.return_value = path_exists
|
|
self.imagecache._vhdutils.get_vhd_format.return_value = (
|
|
constants.DISK_FORMAT_VHD)
|
|
|
|
CONF.set_override('use_cow_images', use_cow)
|
|
|
|
image_file_name = rescue_image_id or self.FAKE_IMAGE_REF
|
|
expected_path = os.path.join(self.FAKE_BASE_DIR,
|
|
image_file_name)
|
|
expected_vhd_path = "%s.%s" % (expected_path,
|
|
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):
|
|
(expected_path,
|
|
expected_image_path) = self._prepare_get_cached_image(False, 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['user_id'],
|
|
self.instance['project_id'])
|
|
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):
|
|
(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(
|
|
image_href=self.FAKE_IMAGE_REF)
|
|
|
|
self.assertRaises(exception.InvalidImageRef,
|
|
self.imagecache.get_cached_image,
|
|
self.context, self.instance)
|
|
|
|
self.imagecache._pathutils.remove.assert_called_once_with(
|
|
expected_path)
|
|
|
|
@mock.patch.object(imagecache.ImageCache, '_resize_and_cache_vhd')
|
|
@mock.patch.object(imagecache.ImageCache, '_update_image_timestamp')
|
|
def test_get_cached_image_use_cow(self, mock_update_img_timestamp,
|
|
mock_resize):
|
|
(expected_path,
|
|
expected_image_path) = self._prepare_get_cached_image(True, True)
|
|
|
|
expected_resized_image_path = expected_image_path + 'x'
|
|
mock_resize.return_value = expected_resized_image_path
|
|
|
|
result = self.imagecache.get_cached_image(self.context, self.instance)
|
|
self.assertEqual(expected_resized_image_path, result)
|
|
|
|
mock_resize.assert_called_once_with(self.instance, expected_image_path)
|
|
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):
|
|
fake_rescue_image_id = 'fake_rescue_image_id'
|
|
|
|
self.imagecache._vhdutils.get_vhd_info.return_value = {
|
|
'VirtualSize': self.instance.root_gb + 1}
|
|
(expected_path,
|
|
expected_vhd_path) = self._prepare_get_cached_image(
|
|
rescue_image_id=fake_rescue_image_id,
|
|
image_format=constants.DISK_FORMAT_VHD)
|
|
self.assertRaises(exception.FlavorDiskSmallerThanImage,
|
|
self.imagecache.get_cached_image,
|
|
self.context, self.instance,
|
|
fake_rescue_image_id)
|
|
|
|
mock_fetch.assert_called_once_with(self.context,
|
|
fake_rescue_image_id,
|
|
expected_path,
|
|
self.instance.user_id,
|
|
self.instance.project_id)
|
|
self.imagecache._vhdutils.get_vhd_info.assert_called_once_with(
|
|
expected_vhd_path)
|
|
|
|
@mock.patch.object(imagecache.ImageCache, '_update_image_timestamp')
|
|
@mock.patch.object(imagecache.ImageCache, '_remove_if_old_image')
|
|
def test_age_and_verify_cached_images(self, mock_rem_if_old_img,
|
|
mock_update_img_timestamp):
|
|
fake_images = [mock.sentinel.FAKE_IMG1, mock.sentinel.FAKE_IMG2]
|
|
fake_used_images = [mock.sentinel.FAKE_IMG1]
|
|
self.imagecache.originals = fake_images
|
|
self.imagecache.used_images = fake_used_images
|
|
|
|
self.imagecache._age_and_verify_cached_images(
|
|
mock.sentinel.FAKE_CONTEXT,
|
|
mock.sentinel.all_instances,
|
|
mock.sentinel.FAKE_BASE_DIR)
|
|
|
|
mock_update_img_timestamp.assert_called_once_with(
|
|
mock.sentinel.FAKE_IMG1)
|
|
mock_rem_if_old_img.assert_called_once_with(
|
|
mock.sentinel.FAKE_IMG2)
|
|
|
|
@mock.patch.object(imagecache.ImageCache, '_get_image_backing_files')
|
|
@mock.patch.object(os, 'utime')
|
|
def test_update_image_timestamp(self, mock_utime,
|
|
mock_get_img_backing_file):
|
|
fake_backing_files = [mock.sentinel.IMG_PATH1, mock.sentinel.IMG_PATH2,
|
|
mock.sentinel.IMG_PATH3]
|
|
mock_get_img_backing_file.return_value = fake_backing_files
|
|
|
|
self.imagecache._update_image_timestamp(mock.sentinel.IMG)
|
|
|
|
mock_get_img_backing_file.assert_called_once_with(mock.sentinel.IMG)
|
|
mock_utime.assert_has_calls([mock.call(mock.sentinel.IMG_PATH1, None),
|
|
mock.call(mock.sentinel.IMG_PATH2, None),
|
|
mock.call(mock.sentinel.IMG_PATH3, None)])
|
|
|
|
def test_get_image_backing_files(self):
|
|
mock_lookup_img_basepath = (
|
|
self.imagecache._pathutils.lookup_image_basepath)
|
|
fake_image_name = 'fake_image_name'
|
|
resized_image1 = '%s_1' % fake_image_name
|
|
resized_image5 = '%s_5' % fake_image_name
|
|
self.imagecache.unexplained_images = [resized_image1,
|
|
resized_image5]
|
|
fake_backing_files = [mock.sentinel.BACKING_FILE,
|
|
mock.sentinel.RESIZED_FILE1,
|
|
mock.sentinel.RESIZED_FILE2]
|
|
mock_lookup_img_basepath.side_effect = fake_backing_files
|
|
|
|
ret = self.imagecache._get_image_backing_files(fake_image_name)
|
|
|
|
self.assertEqual(ret, fake_backing_files)
|
|
|
|
@mock.patch.object(imagecache.ImageCache, '_get_image_backing_files')
|
|
@mock.patch.object(imagecache.ImageCache, 'remove_old_image')
|
|
def test_remove_if_old_image(self, mock_remove_old_image,
|
|
mock_get_img_backing_file):
|
|
self.flags(remove_unused_original_minimum_age_seconds=3000)
|
|
fake_backing_files = [mock.sentinel.BACKING_FILE,
|
|
mock.sentinel.RESIZED_FILE1,
|
|
mock.sentinel.RESIZED_FILE2]
|
|
mock_get_img_backing_file.return_value = fake_backing_files
|
|
self.imagecache._pathutils.get_age_of_file.side_effect = [3600, 2400,
|
|
3600]
|
|
|
|
self.imagecache._remove_if_old_image(mock.sentinel.FAKE_IMAGE_FILE)
|
|
|
|
calls = [mock.call(mock.sentinel.BACKING_FILE),
|
|
mock.call(mock.sentinel.RESIZED_FILE1),
|
|
mock.call(mock.sentinel.RESIZED_FILE2)]
|
|
self.imagecache._pathutils.get_age_of_file.assert_has_calls(calls)
|
|
mock_remove_old_image.assert_has_calls([
|
|
mock.call(mock.sentinel.BACKING_FILE),
|
|
mock.call(mock.sentinel.RESIZED_FILE2)])
|
|
|
|
def test_remove_old_images(self):
|
|
self.imagecache.remove_old_image(mock.sentinel.img_file)
|
|
|
|
self.imagecache._pathutils.remove.assert_called_once_with(
|
|
mock.sentinel.img_file)
|
|
|
|
@mock.patch.object(imagecache.ImageCache, '_list_running_instances')
|
|
@mock.patch.object(imagecache.ImageCache, 'list_base_images')
|
|
@mock.patch.object(imagecache.ImageCache, '_age_and_verify_cached_images')
|
|
def test_update(self, mock_age_and_verify_cached_images,
|
|
mock_list_base_images, mock_list_running_instances):
|
|
mock_get_base_vhd_dir = self.imagecache._pathutils.get_base_vhd_dir
|
|
mock_get_base_vhd_dir.return_value = mock.sentinel.base_vhd_dir
|
|
|
|
fake_used_images = mock.MagicMock()
|
|
fake_used_images.keys.return_value = mock.sentinel.used_images
|
|
fake_running = {'used_images': fake_used_images}
|
|
fake_all_files = {'originals': mock.sentinel.originals,
|
|
'unexplained_images': mock.sentinel.unexplained
|
|
}
|
|
mock_list_running_instances.return_value = fake_running
|
|
mock_list_base_images.return_value = fake_all_files
|
|
|
|
self.imagecache.update(mock.sentinel.FAKE_CONTEXT,
|
|
mock.sentinel.all_instances)
|
|
|
|
mock_get_base_vhd_dir.assert_called_once_with()
|
|
mock_list_running_instances.assert_called_once_with(
|
|
mock.sentinel.FAKE_CONTEXT, mock.sentinel.all_instances)
|
|
mock_list_base_images.assert_called_once_with(
|
|
mock.sentinel.base_vhd_dir)
|
|
mock_age_and_verify_cached_images.assert_called_once_with(
|
|
mock.sentinel.FAKE_CONTEXT, mock.sentinel.all_instances,
|
|
mock.sentinel.base_vhd_dir)
|
|
self.assertEqual(mock.sentinel.used_images,
|
|
self.imagecache.used_images)
|
|
self.assertEqual(mock.sentinel.originals, self.imagecache.originals)
|
|
self.assertEqual(mock.sentinel.unexplained,
|
|
self.imagecache.unexplained_images)
|
|
|
|
@mock.patch.object(os, 'listdir')
|
|
def test_list_base_images(self, mock_list_dir):
|
|
fake_file1 = 'fake_file'
|
|
fake_file2 = '5a51f1c5-fbc2-4e26-906c-759d45168ecb'
|
|
fake_file3 = '5a51f1c5-fbc2-4e26-906c-759d45168ecb_5'
|
|
mock_list_dir.return_value = [fake_file1, fake_file2, fake_file3]
|
|
|
|
ret = self.imagecache.list_base_images(mock.sentinel.base_vhd_dir)
|
|
|
|
self.assertEqual([fake_file1, fake_file3], ret['unexplained_images'])
|
|
self.assertEqual([fake_file2], ret['originals'])
|