shade/shade/tests/unit/test_caching.py

409 lines
18 KiB
Python

# -*- coding: utf-8 -*-
# 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 tempfile
import mock
import os_client_config as occ
import testtools
import yaml
import shade
from shade import exc
from shade import meta
from shade.tests import fakes
from shade.tests.unit import base
class TestMemoryCache(base.TestCase):
CACHING_CONFIG = {
'cache':
{
'max_age': 90,
'class': 'dogpile.cache.memory',
},
'clouds':
{
'_cache_test_':
{
'auth':
{
'auth_url': 'http://198.51.100.1:35357/v2.0',
'username': '_test_user_',
'password': '_test_pass_',
'project_name': '_test_project_',
},
'region_name': '_test_region_',
},
},
}
def setUp(self):
super(TestMemoryCache, self).setUp()
# Isolate os-client-config from test environment
config = tempfile.NamedTemporaryFile(delete=False)
config.write(bytes(yaml.dump(self.CACHING_CONFIG).encode('utf-8')))
config.close()
vendor = tempfile.NamedTemporaryFile(delete=False)
vendor.write(b'{}')
vendor.close()
self.cloud_config = occ.OpenStackConfig(config_files=[config.name],
vendor_files=[vendor.name])
self.cloud = shade.openstack_cloud(cloud='_cache_test_',
config=self.cloud_config)
def test_openstack_cloud(self):
self.assertIsInstance(self.cloud, shade.OpenStackCloud)
@mock.patch('shade.OpenStackCloud.keystone_client')
def test_project_cache(self, keystone_mock):
project = fakes.FakeProject('project_a')
keystone_mock.projects.list.return_value = [project]
self.assertEqual({'project_a': project}, self.cloud.project_cache)
project_b = fakes.FakeProject('project_b')
keystone_mock.projects.list.return_value = [project,
project_b]
self.assertEqual(
{'project_a': project}, self.cloud.project_cache)
self.cloud.get_project_cache.invalidate(self.cloud)
self.assertEqual(
{'project_a': project,
'project_b': project_b}, self.cloud.project_cache)
@mock.patch('shade.OpenStackCloud.cinder_client')
def test_list_volumes(self, cinder_mock):
fake_volume = fakes.FakeVolume('volume1', 'available',
'Volume 1 Display Name')
fake_volume_dict = meta.obj_to_dict(fake_volume)
cinder_mock.volumes.list.return_value = [fake_volume]
self.assertEqual([fake_volume_dict], self.cloud.list_volumes())
fake_volume2 = fakes.FakeVolume('volume2', 'available',
'Volume 2 Display Name')
fake_volume2_dict = meta.obj_to_dict(fake_volume2)
cinder_mock.volumes.list.return_value = [fake_volume, fake_volume2]
self.assertEqual([fake_volume_dict], self.cloud.list_volumes())
self.cloud.list_volumes.invalidate(self.cloud)
self.assertEqual([fake_volume_dict, fake_volume2_dict],
self.cloud.list_volumes())
@mock.patch('shade.OpenStackCloud.cinder_client')
def test_list_volumes_creating_invalidates(self, cinder_mock):
fake_volume = fakes.FakeVolume('volume1', 'creating',
'Volume 1 Display Name')
fake_volume_dict = meta.obj_to_dict(fake_volume)
cinder_mock.volumes.list.return_value = [fake_volume]
self.assertEqual([fake_volume_dict], self.cloud.list_volumes())
fake_volume2 = fakes.FakeVolume('volume2', 'available',
'Volume 2 Display Name')
fake_volume2_dict = meta.obj_to_dict(fake_volume2)
cinder_mock.volumes.list.return_value = [fake_volume, fake_volume2]
self.assertEqual([fake_volume_dict, fake_volume2_dict],
self.cloud.list_volumes())
@mock.patch.object(shade.OpenStackCloud, 'cinder_client')
def test_create_volume_invalidates(self, cinder_mock):
fake_volb4 = fakes.FakeVolume('volume1', 'available',
'Volume 1 Display Name')
fake_volb4_dict = meta.obj_to_dict(fake_volb4)
cinder_mock.volumes.list.return_value = [fake_volb4]
self.assertEqual([fake_volb4_dict], self.cloud.list_volumes())
volume = dict(display_name='junk_vol',
size=1,
display_description='test junk volume')
fake_vol = fakes.FakeVolume('12345', 'creating', '')
fake_vol_dict = meta.obj_to_dict(fake_vol)
cinder_mock.volumes.create.return_value = fake_vol
cinder_mock.volumes.list.return_value = [fake_volb4, fake_vol]
def creating_available():
def now_available():
fake_vol.status = 'available'
fake_vol_dict['status'] = 'available'
return mock.DEFAULT
cinder_mock.volumes.list.side_effect = now_available
return mock.DEFAULT
cinder_mock.volumes.list.side_effect = creating_available
self.cloud.create_volume(wait=True, timeout=None, **volume)
self.assertTrue(cinder_mock.volumes.create.called)
self.assertEqual(3, cinder_mock.volumes.list.call_count)
# If cache was not invalidated, we would not see our own volume here
# because the first volume was available and thus would already be
# cached.
self.assertEqual([fake_volb4_dict, fake_vol_dict],
self.cloud.list_volumes())
# And now delete and check same thing since list is cached as all
# available
fake_vol.status = 'deleting'
fake_vol_dict = meta.obj_to_dict(fake_vol)
def deleting_gone():
def now_gone():
cinder_mock.volumes.list.return_value = [fake_volb4]
return mock.DEFAULT
cinder_mock.volumes.list.side_effect = now_gone
return mock.DEFAULT
cinder_mock.volumes.list.return_value = [fake_volb4, fake_vol]
cinder_mock.volumes.list.side_effect = deleting_gone
cinder_mock.volumes.delete.return_value = fake_vol_dict
self.cloud.delete_volume('12345')
self.assertEqual([fake_volb4_dict], self.cloud.list_volumes())
@mock.patch.object(shade.OpenStackCloud, 'keystone_client')
def test_get_user_cache(self, keystone_mock):
fake_user = fakes.FakeUser('999', '', '')
keystone_mock.users.list.return_value = [fake_user]
self.assertEqual({'999': fake_user}, self.cloud.get_user_cache())
@mock.patch.object(shade.OpenStackCloud, 'keystone_client')
def test_modify_user_invalidates_cache(self, keystone_mock):
fake_user = fakes.FakeUser('abc123', 'abc123@domain.test',
'abc123 name')
# first cache an empty list
keystone_mock.users.list.return_value = []
self.assertEqual({}, self.cloud.get_user_cache())
# now add one
keystone_mock.users.list.return_value = [fake_user]
keystone_mock.users.create.return_value = fake_user
created = self.cloud.create_user(name='abc123 name',
email='abc123@domain.test')
self.assertEqual({'id': 'abc123',
'name': 'abc123 name',
'email': 'abc123@domain.test'}, created)
# Cache should have been invalidated
self.assertEqual({'abc123': fake_user}, self.cloud.get_user_cache())
# Update and check to see if it is updated
fake_user2 = fakes.FakeUser('abc123', 'abc123 name',
'abc123-changed@domain.test')
keystone_mock.users.update.return_value = fake_user2
keystone_mock.users.list.return_value = [fake_user2]
self.cloud.update_user('abc123', email='abc123-changed@domain.test')
keystone_mock.users.update.assert_called_with(
user=fake_user2, email='abc123-changed@domain.test')
self.assertEqual({'abc123': fake_user2}, self.cloud.get_user_cache())
# Now delete and ensure it disappears
keystone_mock.users.list.return_value = []
self.cloud.delete_user('abc123')
self.assertEqual({}, self.cloud.get_user_cache())
self.assertTrue(keystone_mock.users.delete.was_called)
@mock.patch.object(shade.OpenStackCloud, 'nova_client')
def test_list_flavors(self, nova_mock):
nova_mock.flavors.list.return_value = []
self.assertEqual([], self.cloud.list_flavors())
fake_flavor = fakes.FakeFlavor('555', 'vanilla')
fake_flavor_dict = meta.obj_to_dict(fake_flavor)
nova_mock.flavors.list.return_value = [fake_flavor]
self.cloud.list_flavors.invalidate(self.cloud)
self.assertEqual([fake_flavor_dict], self.cloud.list_flavors())
@mock.patch.object(shade.OpenStackCloud, 'glance_client')
def test_list_images(self, glance_mock):
glance_mock.images.list.return_value = []
self.assertEqual([], self.cloud.list_images())
fake_image = fakes.FakeImage('22', '22 name', 'success')
fake_image_dict = meta.obj_to_dict(fake_image)
glance_mock.images.list.return_value = [fake_image]
self.cloud.list_images.invalidate(self.cloud)
self.assertEqual([fake_image_dict], self.cloud.list_images())
@mock.patch.object(shade.OpenStackCloud, 'glance_client')
def test_list_images_ignores_unsteady_status(self, glance_mock):
steady_image = fakes.FakeImage('68', 'Jagr', 'active')
steady_image_dict = meta.obj_to_dict(steady_image)
for status in ('queued', 'saving', 'pending_delete'):
active_image = fakes.FakeImage(self.getUniqueString(),
self.getUniqueString(), status)
glance_mock.images.list.return_value = [active_image]
active_image_dict = meta.obj_to_dict(active_image)
self.assertEqual([active_image_dict],
self.cloud.list_images())
glance_mock.images.list.return_value = [active_image, steady_image]
# Should expect steady_image to appear if active wasn't cached
self.assertEqual([active_image_dict, steady_image_dict],
self.cloud.list_images())
@mock.patch.object(shade.OpenStackCloud, 'glance_client')
def test_list_images_caches_steady_status(self, glance_mock):
steady_image = fakes.FakeImage('91', 'Federov', 'active')
first_image = None
for status in ('active', 'deleted', 'killed'):
active_image = fakes.FakeImage(self.getUniqueString(),
self.getUniqueString(), status)
active_image_dict = meta.obj_to_dict(active_image)
if not first_image:
first_image = active_image_dict
glance_mock.images.list.return_value = [active_image]
self.assertEqual([first_image], self.cloud.list_images())
glance_mock.images.list.return_value = [active_image, steady_image]
# because we skipped the create_image code path, no invalidation
# was done, so we _SHOULD_ expect steady state images to cache and
# therefore we should _not_ expect to see the new one here
self.assertEqual([first_image], self.cloud.list_images())
def _call_create_image(self, name, container=None):
imagefile = tempfile.NamedTemporaryFile(delete=False)
imagefile.write(b'\0')
imagefile.close()
self.cloud.create_image(
name, imagefile.name, container=container, wait=True)
@mock.patch.object(occ.cloud_config.CloudConfig, 'get_api_version')
@mock.patch.object(shade.OpenStackCloud, 'glance_client')
def test_create_image_put_v1(self, glance_mock, mock_api_version):
mock_api_version.return_value = '1'
glance_mock.images.list.return_value = []
self.assertEqual([], self.cloud.list_images())
fake_image = fakes.FakeImage('42', '42 name', 'success')
glance_mock.images.create.return_value = fake_image
glance_mock.images.list.return_value = [fake_image]
self._call_create_image('42 name')
args = {'name': '42 name',
'properties': {'owner_specified.shade.md5': mock.ANY,
'owner_specified.shade.sha256': mock.ANY}}
glance_mock.images.create.assert_called_with(**args)
glance_mock.images.update.assert_called_with(data=mock.ANY,
image=fake_image)
fake_image_dict = meta.obj_to_dict(fake_image)
self.assertEqual([fake_image_dict], self.cloud.list_images())
@mock.patch.object(occ.cloud_config.CloudConfig, 'get_api_version')
@mock.patch.object(shade.OpenStackCloud, 'glance_client')
def test_create_image_put_v2(self, glance_mock, mock_api_version):
mock_api_version.return_value = '2'
self.cloud.image_api_use_tasks = False
glance_mock.images.list.return_value = []
self.assertEqual([], self.cloud.list_images())
fake_image = fakes.FakeImage('42', '42 name', 'success')
glance_mock.images.create.return_value = fake_image
glance_mock.images.list.return_value = [fake_image]
self._call_create_image('42 name')
args = {'name': '42 name',
'owner_specified.shade.md5': mock.ANY,
'owner_specified.shade.sha256': mock.ANY}
glance_mock.images.create.assert_called_with(**args)
glance_mock.images.upload.assert_called_with(
image_data=mock.ANY, image_id=fake_image.id)
fake_image_dict = meta.obj_to_dict(fake_image)
self.assertEqual([fake_image_dict], self.cloud.list_images())
@mock.patch.object(occ.cloud_config.CloudConfig, 'get_api_version')
@mock.patch.object(shade.OpenStackCloud, '_get_file_hashes')
@mock.patch.object(shade.OpenStackCloud, 'glance_client')
@mock.patch.object(shade.OpenStackCloud, 'swift_client')
@mock.patch.object(shade.OpenStackCloud, 'swift_service')
def test_create_image_task(self,
swift_service_mock,
swift_mock,
glance_mock,
get_file_hashes,
mock_api_version):
mock_api_version.return_value = '2'
self.cloud.image_api_use_tasks = True
class Container(object):
name = 'image_upload_v2_test_container'
fake_container = Container()
swift_mock.get_capabilities.return_value = {
'swift': {
'max_file_size': 1000
}
}
swift_mock.put_container.return_value = fake_container
swift_mock.head_object.return_value = {}
glance_mock.images.list.return_value = []
self.assertEqual([], self.cloud.list_images())
fake_md5 = "fake-md5"
fake_sha256 = "fake-sha256"
get_file_hashes.return_value = (fake_md5, fake_sha256)
# V2's warlock objects just work like dicts
class FakeImage(dict):
status = 'CREATED'
id = '99'
name = '99 name'
fake_image = FakeImage()
fake_image.update({
'id': '99',
'name': '99 name',
shade.IMAGE_MD5_KEY: fake_md5,
shade.IMAGE_SHA256_KEY: fake_sha256,
})
glance_mock.images.list.return_value = [fake_image]
class FakeTask(dict):
status = 'success'
result = {'image_id': '99'}
fake_task = FakeTask()
fake_task.update({
'id': '100',
'status': 'success',
})
glance_mock.tasks.get.return_value = fake_task
self._call_create_image(name='99 name',
container='image_upload_v2_test_container')
args = {'header': ['x-object-meta-x-shade-md5:fake-md5',
'x-object-meta-x-shade-sha256:fake-sha256'],
'segment_size': 1000}
swift_service_mock.upload.assert_called_with(
container='image_upload_v2_test_container',
objects=mock.ANY,
options=args)
glance_mock.tasks.create.assert_called_with(type='import', input={
'import_from': 'image_upload_v2_test_container/99 name',
'image_properties': {'name': '99 name'}})
args = {'owner_specified.shade.md5': fake_md5,
'owner_specified.shade.sha256': fake_sha256,
'image_id': '99'}
glance_mock.images.update.assert_called_with(**args)
fake_image_dict = meta.obj_to_dict(fake_image)
self.assertEqual([fake_image_dict], self.cloud.list_images())
@mock.patch.object(shade.OpenStackCloud, 'glance_client')
def test_cache_no_cloud_name(self, glance_mock):
class FakeImage(dict):
id = 1
status = 'active'
name = 'None Test Image'
fi = FakeImage(id=FakeImage.id, status=FakeImage.status,
name=FakeImage.name)
glance_mock.images.list.return_value = [fi]
self.cloud.name = None
self.assertEqual([fi], [dict(x) for x in self.cloud.list_images()])
# Now test that the list was cached
fi2 = FakeImage(id=2, status=FakeImage.status, name=FakeImage.name)
fi2.id = 2
glance_mock.images.list.return_value = [fi, fi2]
self.assertEqual([fi], [dict(x) for x in self.cloud.list_images()])
# Invalidation too
self.cloud.list_images.invalidate(self.cloud)
self.assertEqual(
[fi, fi2], [dict(x) for x in self.cloud.list_images()])
def test_get_auth_bogus(self):
self.cloud.auth_type = 'bogus'
with testtools.ExpectedException(exc.OpenStackCloudException):
self.cloud.keystone_session