28
25
import xml.dom.minidom as minidom
34
from glance import client as glance_client
35
31
from nova import context
36
from nova import exception
38
from nova import utils
39
32
import nova.api.openstack
40
33
from nova.api.openstack import images
41
35
from nova.tests.api.openstack import fakes
44
class _BaseImageServiceTests(test.TestCase):
45
"""Tasks to test for all image services"""
47
def __init__(self, *args, **kwargs):
48
super(_BaseImageServiceTests, self).__init__(*args, **kwargs)
52
def test_create(self):
53
fixture = self._make_fixture('test image')
54
num_images = len(self.service.index(self.context))
56
image_id = self.service.create(self.context, fixture)['id']
58
self.assertNotEquals(None, image_id)
59
self.assertEquals(num_images + 1,
60
len(self.service.index(self.context)))
62
def test_create_and_show_non_existing_image(self):
63
fixture = self._make_fixture('test image')
64
num_images = len(self.service.index(self.context))
66
image_id = self.service.create(self.context, fixture)['id']
68
self.assertNotEquals(None, image_id)
69
self.assertRaises(exception.NotFound,
74
def test_create_and_show_non_existing_image_by_name(self):
75
fixture = self._make_fixture('test image')
76
num_images = len(self.service.index(self.context))
78
image_id = self.service.create(self.context, fixture)['id']
80
self.assertNotEquals(None, image_id)
81
self.assertRaises(exception.ImageNotFound,
82
self.service.show_by_name,
86
def test_update(self):
87
fixture = self._make_fixture('test image')
88
image_id = self.service.create(self.context, fixture)['id']
89
fixture['status'] = 'in progress'
91
self.service.update(self.context, image_id, fixture)
93
new_image_data = self.service.show(self.context, image_id)
94
self.assertEquals('in progress', new_image_data['status'])
96
def test_delete(self):
97
fixture1 = self._make_fixture('test image 1')
98
fixture2 = self._make_fixture('test image 2')
99
fixtures = [fixture1, fixture2]
101
num_images = len(self.service.index(self.context))
102
self.assertEquals(0, num_images, str(self.service.index(self.context)))
105
for fixture in fixtures:
106
new_id = self.service.create(self.context, fixture)['id']
109
num_images = len(self.service.index(self.context))
110
self.assertEquals(2, num_images, str(self.service.index(self.context)))
112
self.service.delete(self.context, ids[0])
114
num_images = len(self.service.index(self.context))
115
self.assertEquals(1, num_images)
117
def test_index(self):
118
fixture = self._make_fixture('test image')
119
image_id = self.service.create(self.context, fixture)['id']
120
image_metas = self.service.index(self.context)
121
expected = [{'id': 'DONTCARE', 'name': 'test image'}]
122
self.assertDictListMatch(image_metas, expected)
125
def _make_fixture(name):
126
fixture = {'name': name,
134
class GlanceImageServiceTest(_BaseImageServiceTests):
136
"""Tests the Glance image service, in particular that metadata translation
139
At a high level, the translations involved are:
141
1. Glance -> ImageService - This is needed so we can support
142
multple ImageServices (Glance, Local, etc)
144
2. ImageService -> API - This is needed so we can support multple
145
APIs (OpenStack, EC2)
148
super(GlanceImageServiceTest, self).setUp()
149
self.stubs = stubout.StubOutForTesting()
150
fakes.stub_out_glance(self.stubs)
151
fakes.stub_out_compute_api_snapshot(self.stubs)
152
service_class = 'nova.image.glance.GlanceImageService'
153
self.service = utils.import_object(service_class)
154
self.context = context.RequestContext('fake', 'fake')
155
self.service.delete_all()
156
self.sent_to_glance = {}
157
fakes.stub_out_glance_add_image(self.stubs, self.sent_to_glance)
160
self.stubs.UnsetAll()
161
super(GlanceImageServiceTest, self).tearDown()
163
def test_create_with_instance_id(self):
164
"""Ensure instance_id is persisted as an image-property"""
165
fixture = {'name': 'test image',
167
'properties': {'instance_id': '42', 'user_id': 'fake'}}
169
image_id = self.service.create(self.context, fixture)['id']
171
self.assertDictMatch(self.sent_to_glance['metadata'], expected)
173
image_meta = self.service.show(self.context, image_id)
174
expected = {'id': image_id,
175
'name': 'test image',
177
'properties': {'instance_id': '42', 'user_id': 'fake'}}
178
self.assertDictMatch(image_meta, expected)
180
image_metas = self.service.detail(self.context)
181
self.assertDictMatch(image_metas[0], expected)
183
def test_create_without_instance_id(self):
185
Ensure we can create an image without having to specify an
186
instance_id. Public images are an example of an image not tied to an
189
fixture = {'name': 'test image'}
190
image_id = self.service.create(self.context, fixture)['id']
192
expected = {'name': 'test image', 'properties': {}}
193
self.assertDictMatch(self.sent_to_glance['metadata'], expected)
195
def test_index_default_limit(self):
199
fixture = self._make_fixture('TestImage %d' % (i))
200
fixtures.append(fixture)
201
ids.append(self.service.create(self.context, fixture)['id'])
203
image_metas = self.service.index(self.context)
205
for meta in image_metas:
206
expected = {'id': 'DONTCARE',
207
'name': 'TestImage %d' % (i)}
208
self.assertDictMatch(meta, expected)
211
def test_index_marker(self):
215
fixture = self._make_fixture('TestImage %d' % (i))
216
fixtures.append(fixture)
217
ids.append(self.service.create(self.context, fixture)['id'])
219
image_metas = self.service.index(self.context, marker=ids[1])
220
self.assertEquals(len(image_metas), 8)
222
for meta in image_metas:
223
expected = {'id': 'DONTCARE',
224
'name': 'TestImage %d' % (i)}
225
self.assertDictMatch(meta, expected)
228
def test_index_limit(self):
232
fixture = self._make_fixture('TestImage %d' % (i))
233
fixtures.append(fixture)
234
ids.append(self.service.create(self.context, fixture)['id'])
236
image_metas = self.service.index(self.context, limit=3)
237
self.assertEquals(len(image_metas), 3)
239
def test_index_marker_and_limit(self):
243
fixture = self._make_fixture('TestImage %d' % (i))
244
fixtures.append(fixture)
245
ids.append(self.service.create(self.context, fixture)['id'])
247
image_metas = self.service.index(self.context, marker=ids[3], limit=1)
248
self.assertEquals(len(image_metas), 1)
250
for meta in image_metas:
251
expected = {'id': 'DONTCARE',
252
'name': 'TestImage %d' % (i)}
253
self.assertDictMatch(meta, expected)
256
def test_detail_marker(self):
260
fixture = self._make_fixture('TestImage %d' % (i))
261
fixtures.append(fixture)
262
ids.append(self.service.create(self.context, fixture)['id'])
264
image_metas = self.service.detail(self.context, marker=ids[1])
265
self.assertEquals(len(image_metas), 8)
267
for meta in image_metas:
272
'name': 'TestImage %d' % (i),
279
self.assertDictMatch(meta, expected)
282
def test_detail_limit(self):
286
fixture = self._make_fixture('TestImage %d' % (i))
287
fixtures.append(fixture)
288
ids.append(self.service.create(self.context, fixture)['id'])
290
image_metas = self.service.detail(self.context, limit=3)
291
self.assertEquals(len(image_metas), 3)
293
def test_detail_marker_and_limit(self):
297
fixture = self._make_fixture('TestImage %d' % (i))
298
fixtures.append(fixture)
299
ids.append(self.service.create(self.context, fixture)['id'])
301
image_metas = self.service.detail(self.context, marker=ids[3], limit=3)
302
self.assertEquals(len(image_metas), 3)
304
for meta in image_metas:
309
'name': 'TestImage %d' % (i),
311
'updated': None, 'created': None},
313
self.assertDictMatch(meta, expected)
317
class ImageControllerWithGlanceServiceTest(test.TestCase):
38
NOW_API_FORMAT = "2010-10-11T10:30:22Z"
41
class ImagesTest(test.TestCase):
319
43
Test of the OpenStack API /images application controller w/Glance.
321
NOW_GLANCE_FORMAT = "2010-10-11T10:30:22"
322
NOW_API_FORMAT = "2010-10-11T10:30:22Z"
325
47
"""Run before each test."""
326
super(ImageControllerWithGlanceServiceTest, self).setUp()
327
self.flags(image_service='nova.image.glance.GlanceImageService')
48
super(ImagesTest, self).setUp()
328
49
self.stubs = stubout.StubOutForTesting()
329
50
fakes.stub_out_networking(self.stubs)
330
51
fakes.stub_out_rate_limiting(self.stubs)
331
52
fakes.stub_out_key_pair_funcs(self.stubs)
332
self.fixtures = self._make_image_fixtures()
333
fakes.stub_out_glance(self.stubs, initial_fixtures=self.fixtures)
334
53
fakes.stub_out_compute_api_snapshot(self.stubs)
335
54
fakes.stub_out_compute_api_backup(self.stubs)
55
fakes.stub_out_glance(self.stubs)
337
57
def tearDown(self):
338
58
"""Run after each test."""
339
59
self.stubs.UnsetAll()
340
super(ImageControllerWithGlanceServiceTest, self).tearDown()
60
super(ImagesTest, self).tearDown()
342
62
def _get_fake_context(self):
343
63
class Context(object):
344
64
project_id = 'fake'
347
def _applicable_fixture(self, fixture, user_id):
348
"""Determine if this fixture is applicable for given user id."""
349
is_public = fixture["is_public"]
351
uid = fixture["properties"]["user_id"]
354
return uid == user_id or is_public
356
68
def test_get_image_index(self):
357
69
request = webob.Request.blank('/v1.0/images')
358
response = request.get_response(fakes.wsgi_app())
70
app = fakes.wsgi_app(fake_auth_context=self._get_fake_context())
71
response = request.get_response(app)
360
73
response_dict = json.loads(response.body)
361
74
response_list = response_dict["images"]
363
expected = [{'id': 123, 'name': 'public image'},
364
{'id': 124, 'name': 'queued snapshot'},
365
{'id': 125, 'name': 'saving snapshot'},
366
{'id': 126, 'name': 'active snapshot'},
367
{'id': 127, 'name': 'killed snapshot'},
368
{'id': 128, 'name': 'deleted snapshot'},
369
{'id': 129, 'name': 'pending_delete snapshot'},
370
{'id': 131, 'name': None}]
76
expected = [{'id': '123', 'name': 'public image'},
77
{'id': '124', 'name': 'queued snapshot'},
78
{'id': '125', 'name': 'saving snapshot'},
79
{'id': '126', 'name': 'active snapshot'},
80
{'id': '127', 'name': 'killed snapshot'},
81
{'id': '128', 'name': 'deleted snapshot'},
82
{'id': '129', 'name': 'pending_delete snapshot'},
83
{'id': '130', 'name': None}]
372
85
self.assertDictListMatch(response_list, expected)
374
87
def test_get_image(self):
375
88
request = webob.Request.blank('/v1.0/images/123')
376
response = request.get_response(fakes.wsgi_app())
89
app = fakes.wsgi_app(fake_auth_context=self._get_fake_context())
90
response = request.get_response(app)
378
92
self.assertEqual(200, response.status_int)
554
271
def test_get_image_index_v1_1(self):
555
272
request = webob.Request.blank('/v1.1/fake/images')
556
response = request.get_response(fakes.wsgi_app())
273
app = fakes.wsgi_app(fake_auth_context=self._get_fake_context())
274
response = request.get_response(app)
558
276
response_dict = json.loads(response.body)
559
277
response_list = response_dict["images"]
561
fixtures = copy.copy(self.fixtures)
563
for image in fixtures:
564
if not self._applicable_fixture(image, "fake"):
565
fixtures.remove(image)
568
href = "http://localhost/v1.1/fake/images/%s" % image["id"]
569
bookmark = "http://localhost/fake/images/%s" % image["id"]
572
"name": image["name"],
584
self.assertTrue(test_image in response_list)
586
self.assertEqual(len(response_list), len(fixtures))
282
"name": "public image",
286
"href": "http://localhost/v1.1/fake/images/123",
290
"href": "http://localhost/fake/images/123",
296
"name": "queued snapshot",
300
"href": "http://localhost/v1.1/fake/images/124",
304
"href": "http://localhost/fake/images/124",
310
"name": "saving snapshot",
314
"href": "http://localhost/v1.1/fake/images/125",
318
"href": "http://localhost/fake/images/125",
324
"name": "active snapshot",
328
"href": "http://localhost/v1.1/fake/images/126",
332
"href": "http://localhost/fake/images/126",
338
"name": "killed snapshot",
342
"href": "http://localhost/v1.1/fake/images/127",
346
"href": "http://localhost/fake/images/127",
352
"name": "deleted snapshot",
356
"href": "http://localhost/v1.1/fake/images/128",
360
"href": "http://localhost/fake/images/128",
366
"name": "pending_delete snapshot",
370
"href": "http://localhost/v1.1/fake/images/129",
374
"href": "http://localhost/fake/images/129",
384
"href": "http://localhost/v1.1/fake/images/130",
388
"href": "http://localhost/fake/images/130",
394
self.assertDictListMatch(response_list, expected)
588
396
def test_get_image_details(self):
589
397
request = webob.Request.blank('/v1.0/images/detail')
590
response = request.get_response(fakes.wsgi_app())
398
app = fakes.wsgi_app(fake_auth_context=self._get_fake_context())
399
response = request.get_response(app)
592
401
response_dict = json.loads(response.body)
593
402
response_list = response_dict["images"]
597
406
'name': 'public image',
598
'updated': self.NOW_API_FORMAT,
599
'created': self.NOW_API_FORMAT,
407
'updated': NOW_API_FORMAT,
408
'created': NOW_API_FORMAT,
600
409
'status': 'ACTIVE',
605
414
'name': 'queued snapshot',
606
'updated': self.NOW_API_FORMAT,
607
'created': self.NOW_API_FORMAT,
415
'updated': NOW_API_FORMAT,
416
'created': NOW_API_FORMAT,
608
417
'status': 'SAVING',
613
422
'name': 'saving snapshot',
614
'updated': self.NOW_API_FORMAT,
615
'created': self.NOW_API_FORMAT,
423
'updated': NOW_API_FORMAT,
424
'created': NOW_API_FORMAT,
616
425
'status': 'SAVING',
621
430
'name': 'active snapshot',
622
'updated': self.NOW_API_FORMAT,
623
'created': self.NOW_API_FORMAT,
431
'updated': NOW_API_FORMAT,
432
'created': NOW_API_FORMAT,
624
433
'status': 'ACTIVE',
629
438
'name': 'killed snapshot',
630
'updated': self.NOW_API_FORMAT,
631
'created': self.NOW_API_FORMAT,
439
'updated': NOW_API_FORMAT,
440
'created': NOW_API_FORMAT,
632
441
'status': 'ERROR',
637
446
'name': 'deleted snapshot',
638
'updated': self.NOW_API_FORMAT,
639
'created': self.NOW_API_FORMAT,
447
'updated': NOW_API_FORMAT,
448
'created': NOW_API_FORMAT,
640
449
'status': 'DELETED',
645
454
'name': 'pending_delete snapshot',
646
'updated': self.NOW_API_FORMAT,
647
'created': self.NOW_API_FORMAT,
455
'updated': NOW_API_FORMAT,
456
'created': NOW_API_FORMAT,
648
457
'status': 'DELETED',
654
'updated': self.NOW_API_FORMAT,
655
'created': self.NOW_API_FORMAT,
463
'updated': NOW_API_FORMAT,
464
'created': NOW_API_FORMAT,
656
465
'status': 'ACTIVE',