Skip to content

Commit cc45581

Browse files
committed
functional: Add and use 'GlanceFixture'
This rather beefy (but also quite simple) patch replaces the 'stub_out_image_service' call and associated cleanup in all functional tests with a new 'GlanceFixture', based on the old 'FakeImageService'. The use of a fixture means we don't have to worry about teardown and allows us to stub Glance in the same manners as Cinder, Neutron, Placement etc. Unit test cleanup is handled in a later patch. Change-Id: I6daea47988181dfa6dde3d9c42004c0ecf6ae87a Signed-off-by: Stephen Finucane <[email protected]>
1 parent 8750c4e commit cc45581

File tree

70 files changed

+568
-360
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

70 files changed

+568
-360
lines changed

nova/tests/fixtures.py

Lines changed: 315 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,11 @@
1515
# under the License.
1616

1717
"""Fixtures for Nova tests."""
18+
1819
import collections
1920
from contextlib import contextmanager
2021
import copy
22+
import datetime
2123
import logging as std_logging
2224
import os
2325
import random
@@ -56,6 +58,7 @@
5658
from nova.network import model as network_model
5759
from nova import objects
5860
from nova.objects import base as obj_base
61+
from nova.objects import fields as obj_fields
5962
from nova.objects import service as service_obj
6063
import nova.privsep
6164
from nova import quota as nova_quota
@@ -2046,6 +2049,318 @@ def setUp(self):
20462049
lambda *args, **kwargs: mock.MagicMock()))
20472050

20482051

2052+
class GlanceFixture(fixtures.Fixture):
2053+
"""A fixture for simulating Glance."""
2054+
2055+
# NOTE(justinsb): The OpenStack API can't upload an image?
2056+
# So, make sure we've got one..
2057+
timestamp = datetime.datetime(2011, 1, 1, 1, 2, 3)
2058+
2059+
image1 = {
2060+
'id': '155d900f-4e14-4e4c-a73d-069cbf4541e6',
2061+
'name': 'fakeimage123456',
2062+
'created_at': timestamp,
2063+
'updated_at': timestamp,
2064+
'deleted_at': None,
2065+
'deleted': False,
2066+
'status': 'active',
2067+
'is_public': False,
2068+
'container_format': 'raw',
2069+
'disk_format': 'raw',
2070+
'size': '25165824',
2071+
'min_ram': 0,
2072+
'min_disk': 0,
2073+
'protected': False,
2074+
'visibility': 'public',
2075+
'tags': ['tag1', 'tag2'],
2076+
'properties': {
2077+
'kernel_id': 'nokernel',
2078+
'ramdisk_id': 'nokernel',
2079+
'architecture': obj_fields.Architecture.X86_64,
2080+
},
2081+
}
2082+
2083+
image2 = {
2084+
'id': 'a2459075-d96c-40d5-893e-577ff92e721c',
2085+
'name': 'fakeimage123456',
2086+
'created_at': timestamp,
2087+
'updated_at': timestamp,
2088+
'deleted_at': None,
2089+
'deleted': False,
2090+
'status': 'active',
2091+
'is_public': True,
2092+
'container_format': 'ami',
2093+
'disk_format': 'ami',
2094+
'size': '58145823',
2095+
'min_ram': 0,
2096+
'min_disk': 0,
2097+
'protected': False,
2098+
'visibility': 'public',
2099+
'tags': [],
2100+
'properties': {
2101+
'kernel_id': 'nokernel',
2102+
'ramdisk_id': 'nokernel',
2103+
},
2104+
}
2105+
2106+
image3 = {
2107+
'id': '76fa36fc-c930-4bf3-8c8a-ea2a2420deb6',
2108+
'name': 'fakeimage123456',
2109+
'created_at': timestamp,
2110+
'updated_at': timestamp,
2111+
'deleted_at': None,
2112+
'deleted': False,
2113+
'status': 'active',
2114+
'is_public': True,
2115+
'container_format': 'bare',
2116+
'disk_format': 'raw',
2117+
'size': '83594576',
2118+
'min_ram': 0,
2119+
'min_disk': 0,
2120+
'protected': False,
2121+
'visibility': 'public',
2122+
'tags': ['tag3', 'tag4'],
2123+
'properties': {
2124+
'kernel_id': 'nokernel',
2125+
'ramdisk_id': 'nokernel',
2126+
'architecture': obj_fields.Architecture.X86_64,
2127+
},
2128+
}
2129+
2130+
image4 = {
2131+
'id': 'cedef40a-ed67-4d10-800e-17455edce175',
2132+
'name': 'fakeimage123456',
2133+
'created_at': timestamp,
2134+
'updated_at': timestamp,
2135+
'deleted_at': None,
2136+
'deleted': False,
2137+
'status': 'active',
2138+
'is_public': True,
2139+
'container_format': 'ami',
2140+
'disk_format': 'ami',
2141+
'size': '84035174',
2142+
'min_ram': 0,
2143+
'min_disk': 0,
2144+
'protected': False,
2145+
'visibility': 'public',
2146+
'tags': [],
2147+
'properties': {
2148+
'kernel_id': 'nokernel',
2149+
'ramdisk_id': 'nokernel',
2150+
},
2151+
}
2152+
2153+
image5 = {
2154+
'id': 'c905cedb-7281-47e4-8a62-f26bc5fc4c77',
2155+
'name': 'fakeimage123456',
2156+
'created_at': timestamp,
2157+
'updated_at': timestamp,
2158+
'deleted_at': None,
2159+
'deleted': False,
2160+
'status': 'active',
2161+
'is_public': True,
2162+
'container_format': 'ami',
2163+
'disk_format': 'ami',
2164+
'size': '26360814',
2165+
'min_ram': 0,
2166+
'min_disk': 0,
2167+
'protected': False,
2168+
'visibility': 'public',
2169+
'tags': [],
2170+
'properties': {
2171+
'kernel_id': '155d900f-4e14-4e4c-a73d-069cbf4541e6',
2172+
'ramdisk_id': None,
2173+
},
2174+
}
2175+
2176+
auto_disk_config_disabled_image = {
2177+
'id': 'a440c04b-79fa-479c-bed1-0b816eaec379',
2178+
'name': 'fakeimage6',
2179+
'created_at': timestamp,
2180+
'updated_at': timestamp,
2181+
'deleted_at': None,
2182+
'deleted': False,
2183+
'status': 'active',
2184+
'is_public': False,
2185+
'container_format': 'ova',
2186+
'disk_format': 'vhd',
2187+
'size': '49163826',
2188+
'min_ram': 0,
2189+
'min_disk': 0,
2190+
'protected': False,
2191+
'visibility': 'public',
2192+
'tags': [],
2193+
'properties': {
2194+
'kernel_id': 'nokernel',
2195+
'ramdisk_id': 'nokernel',
2196+
'architecture': obj_fields.Architecture.X86_64,
2197+
'auto_disk_config': 'False',
2198+
},
2199+
}
2200+
2201+
auto_disk_config_enabled_image = {
2202+
'id': '70a599e0-31e7-49b7-b260-868f441e862b',
2203+
'name': 'fakeimage7',
2204+
'created_at': timestamp,
2205+
'updated_at': timestamp,
2206+
'deleted_at': None,
2207+
'deleted': False,
2208+
'status': 'active',
2209+
'is_public': False,
2210+
'container_format': 'ova',
2211+
'disk_format': 'vhd',
2212+
'size': '74185822',
2213+
'min_ram': 0,
2214+
'min_disk': 0,
2215+
'protected': False,
2216+
'visibility': 'public',
2217+
'tags': [],
2218+
'properties': {
2219+
'kernel_id': 'nokernel',
2220+
'ramdisk_id': 'nokernel',
2221+
'architecture': obj_fields.Architecture.X86_64,
2222+
'auto_disk_config': 'True',
2223+
},
2224+
}
2225+
2226+
def __init__(self, test):
2227+
super().__init__()
2228+
self.test = test
2229+
self.images = {}
2230+
2231+
def setUp(self):
2232+
super().setUp()
2233+
2234+
self.test.useFixture(
2235+
ConfPatcher(group='glance', api_servers=['http://localhost:9292'])
2236+
)
2237+
self.test.stub_out(
2238+
'nova.image.glance.API.get_remote_image_service',
2239+
lambda context, image_href: (self, image_href))
2240+
self.test.stub_out(
2241+
'nova.image.glance.get_default_image_service',
2242+
lambda: self)
2243+
2244+
self.create(None, self.image1)
2245+
self.create(None, self.image2)
2246+
self.create(None, self.image3)
2247+
self.create(None, self.image4)
2248+
self.create(None, self.image5)
2249+
self.create(None, self.auto_disk_config_disabled_image)
2250+
self.create(None, self.auto_disk_config_enabled_image)
2251+
2252+
self._imagedata = {}
2253+
2254+
# TODO(bcwaldon): implement optional kwargs such as limit, sort_dir
2255+
def detail(self, context, **kwargs):
2256+
"""Return list of detailed image information."""
2257+
return copy.deepcopy(list(self.images.values()))
2258+
2259+
def download(
2260+
self, context, image_id, data=None, dst_path=None, trusted_certs=None,
2261+
):
2262+
self.show(context, image_id)
2263+
if data:
2264+
data.write(self._imagedata.get(image_id, b''))
2265+
elif dst_path:
2266+
with open(dst_path, 'wb') as data:
2267+
data.write(self._imagedata.get(image_id, b''))
2268+
2269+
def show(
2270+
self, context, image_id, include_locations=False, show_deleted=True,
2271+
):
2272+
"""Get data about specified image.
2273+
2274+
Returns a dict containing image data for the given opaque image id.
2275+
"""
2276+
image = self.images.get(str(image_id))
2277+
if image:
2278+
return copy.deepcopy(image)
2279+
2280+
LOG.warning(
2281+
'Unable to find image id %s. Have images: %s',
2282+
image_id, self.images)
2283+
raise exception.ImageNotFound(image_id=image_id)
2284+
2285+
def create(self, context, metadata, data=None):
2286+
"""Store the image data and return the new image id.
2287+
2288+
:raises: Duplicate if the image already exist.
2289+
2290+
"""
2291+
image_id = str(metadata.get('id', uuidutils.generate_uuid()))
2292+
metadata['id'] = image_id
2293+
if image_id in self.images:
2294+
raise exception.CouldNotUploadImage(image_id=image_id)
2295+
2296+
image_meta = copy.deepcopy(metadata)
2297+
2298+
# Glance sets the size value when an image is created, so we
2299+
# need to do that here to fake things out if it's not provided
2300+
# by the caller. This is needed to avoid a KeyError in the
2301+
# image-size API.
2302+
if 'size' not in image_meta:
2303+
image_meta['size'] = None
2304+
2305+
# Similarly, Glance provides the status on the image once it's created
2306+
# and this is checked in the compute API when booting a server from
2307+
# this image, so we just fake it out to be 'active' even though this
2308+
# is mostly a lie on a newly created image.
2309+
if 'status' not in metadata:
2310+
image_meta['status'] = 'active'
2311+
2312+
# The owner of the image is by default the request context project_id.
2313+
if context and 'owner' not in image_meta.get('properties', {}):
2314+
# Note that normally "owner" is a top-level field in an image
2315+
# resource in glance but we have to fake this out for the images
2316+
# proxy API by throwing it into the generic "properties" dict.
2317+
image_meta.get('properties', {})['owner'] = context.project_id
2318+
2319+
self.images[image_id] = image_meta
2320+
2321+
if data:
2322+
self._imagedata[image_id] = data.read()
2323+
2324+
return self.images[image_id]
2325+
2326+
def update(self, context, image_id, metadata, data=None,
2327+
purge_props=False):
2328+
"""Replace the contents of the given image with the new data.
2329+
2330+
:raises: ImageNotFound if the image does not exist.
2331+
"""
2332+
if not self.images.get(image_id):
2333+
raise exception.ImageNotFound(image_id=image_id)
2334+
2335+
if purge_props:
2336+
self.images[image_id] = copy.deepcopy(metadata)
2337+
else:
2338+
image = self.images[image_id]
2339+
2340+
try:
2341+
image['properties'].update(metadata.pop('properties'))
2342+
except KeyError:
2343+
pass
2344+
2345+
image.update(metadata)
2346+
2347+
return self.images[image_id]
2348+
2349+
def delete(self, context, image_id):
2350+
"""Delete the given image.
2351+
2352+
:raises: ImageNotFound if the image does not exist.
2353+
"""
2354+
removed = self.images.pop(image_id, None)
2355+
if not removed:
2356+
raise exception.ImageNotFound(image_id=image_id)
2357+
2358+
def get_location(self, context, image_id):
2359+
if image_id in self.images:
2360+
return 'fake_location'
2361+
return None
2362+
2363+
20492364
class CinderFixture(fixtures.Fixture):
20502365
"""A fixture to volume operations with the new Cinder attach/detach API"""
20512366

nova/tests/functional/api_sample_tests/test_aggregates.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
from oslo_serialization import jsonutils
1717

1818
from nova.tests.functional.api_sample_tests import api_sample_base
19-
from nova.tests.unit.image import fake as fake_image
2019

2120

2221
class AggregatesSampleJsonTest(api_sample_base.ApiSampleTestBaseV21):
@@ -132,7 +131,7 @@ class AggregatesV2_81_SampleJsonTest(AggregatesV2_41_SampleJsonTest):
132131

133132
def test_images(self):
134133
agg_id = self._test_aggregate_create()
135-
image = fake_image.get_valid_image_id()
134+
image = self.glance.auto_disk_config_enabled_image['id']
136135
response = self._do_post('os-aggregates/%s/images' % agg_id,
137136
'aggregate-images-post-req',
138137
{'image_id': image})

nova/tests/functional/api_sample_tests/test_create_backup.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@
1515

1616
import mock
1717

18+
from nova.tests import fixtures
1819
from nova.tests.functional.api_sample_tests import test_servers
19-
from nova.tests.unit.image import fake
2020

2121

2222
class CreateBackupSamplesJsonTest(test_servers.ServersSampleBase):
@@ -30,7 +30,7 @@ def setUp(self):
3030
super(CreateBackupSamplesJsonTest, self).setUp()
3131
self.uuid = self._post_server()
3232

33-
@mock.patch.object(fake._FakeImageService, 'detail', return_value=[])
33+
@mock.patch.object(fixtures.GlanceFixture, 'detail', return_value=[])
3434
def test_post_backup_server(self, mock_method):
3535
# Get api samples to backup server request.
3636
response = self._do_post('servers/%s/action' % self.uuid,

0 commit comments

Comments
 (0)