Merge lp:~rackspace-titan/nova/minram-mindisk into lp:~hudson-openstack/nova/trunk

Proposed by Naveed Massjouni
Status: Rejected
Rejected by: Brian Waldon
Proposed branch: lp:~rackspace-titan/nova/minram-mindisk
Merge into: lp:~hudson-openstack/nova/trunk
Diff against target: 557 lines (+327/-1)
10 files modified
nova/api/openstack/create_instance_helper.py (+4/-0)
nova/api/openstack/images.py (+6/-0)
nova/api/openstack/schemas/v1.1/image.rng (+6/-0)
nova/api/openstack/views/images.py (+5/-0)
nova/compute/api.py (+5/-0)
nova/exception.py (+8/-0)
nova/image/glance.py (+1/-1)
nova/tests/api/openstack/test_images.py (+189/-0)
nova/tests/image/test_glance.py (+12/-0)
nova/tests/test_compute.py (+91/-0)
To merge this branch: bzr merge lp:~rackspace-titan/nova/minram-mindisk
Reviewer Review Type Date Requested Status
Dan Prince (community) Approve
Chris Behrens (community) Approve
Brian Waldon (community) Approve
Review via email: mp+76491@code.launchpad.net

Description of the change

This addresses Bug #819990 Add minDisk minRam to OSAPI image details. It also enforces the minram and mindisk constraints if they exist. Corresponding tests have been added.

To post a comment you must log in.
Revision history for this message
Brian Waldon (bcwaldon) wrote :

Can you change the new exception names to InstanceTypeDiskTooSmall and InstanceTypeMemoryTooSmall? I think keeping the term 'flavor' limited to the OSAPI view is a good thing to do here.

It looks like we only test passing image filters through to the image service, not that the image service actually does something with them. If you see a way we can test that, I would love to see it :) It's not a blocker for my Approve, though.

review: Needs Fixing
Revision history for this message
Naveed Massjouni (ironcamel) wrote :

Good point. Fixed.

Revision history for this message
Brian Waldon (bcwaldon) wrote :

Thanks guys.

review: Approve
Revision history for this message
Chris Behrens (cbehrens) wrote :

Looks good. Minor thing. I'd like to see the tests that call compute_api.create() (in test_compute) destroy the instances that it created when done, otherwise stale data is left in the sqllite database. You can see examples in the other test_compute tests.

review: Needs Fixing
Revision history for this message
Alex Meade (alex-meade) wrote :

good point, I am on it

Revision history for this message
Chris Behrens (cbehrens) wrote :

Thanks!

review: Approve
Revision history for this message
Dan Prince (dan-prince) wrote :

Looks good.

review: Approve
Revision history for this message
Brian Waldon (bcwaldon) wrote :

Merged through gerrit.

Unmerged revisions

1585. By Alex Meade

Added the deleting of servers that were created in tests

1584. By Alex Meade

fixed incorrect exception names

1583. By Naveed Massjouni

Changing FlavorMemoryTooSmall/FlavorDiskTooSmall exceptions to
InstanceTypeMemoryTooSmall/InstanceTypeDiskTooSmall.

1582. By Naveed Massjouni

pep8

1581. By Naveed Massjouni

More tests.

1580. By Naveed Massjouni

Adding tests for minram/mindisk.

1579. By Naveed Massjouni

Honor the min_ram and min_disk attributes of images when creating a server

1578. By Naveed Massjouni

removing print

1577. By Naveed Massjouni

Merge from trunk

1576. By Alex Meade

Added support for filtering images with minRam and minDisk

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'nova/api/openstack/create_instance_helper.py'
2--- nova/api/openstack/create_instance_helper.py 2011-09-15 14:07:58 +0000
3+++ nova/api/openstack/create_instance_helper.py 2011-09-22 19:53:25 +0000
4@@ -187,6 +187,10 @@
5 config_drive=config_drive,))
6 except quota.QuotaError as error:
7 self._handle_quota_error(error)
8+ except exception.InstanceTypeMemoryTooSmall as error:
9+ raise exc.HTTPBadRequest(explanation=unicode(error))
10+ except exception.InstanceTypeDiskTooSmall as error:
11+ raise exc.HTTPBadRequest(explanation=unicode(error))
12 except exception.ImageNotFound as error:
13 msg = _("Can not find requested image")
14 raise exc.HTTPBadRequest(explanation=msg)
15
16=== modified file 'nova/api/openstack/images.py'
17--- nova/api/openstack/images.py 2011-09-20 21:11:49 +0000
18+++ nova/api/openstack/images.py 2011-09-22 19:53:25 +0000
19@@ -41,6 +41,8 @@
20 'changes-since': 'changes-since',
21 'server': 'property-instance_ref',
22 'type': 'property-image_type',
23+ 'minRam': 'min_ram',
24+ 'minDisk': 'min_disk',
25 }
26
27
28@@ -239,6 +241,10 @@
29 image_elem.set('status', str(image_dict['status']))
30 if 'progress' in image_dict:
31 image_elem.set('progress', str(image_dict['progress']))
32+ if 'minRam' in image_dict:
33+ image_elem.set('minRam', str(image_dict['minRam']))
34+ if 'minDisk' in image_dict:
35+ image_elem.set('minDisk', str(image_dict['minDisk']))
36 if 'server' in image_dict:
37 server_elem = self._create_server_node(image_dict['server'])
38 image_elem.append(server_elem)
39
40=== modified file 'nova/api/openstack/schemas/v1.1/image.rng'
41--- nova/api/openstack/schemas/v1.1/image.rng 2011-08-25 16:05:53 +0000
42+++ nova/api/openstack/schemas/v1.1/image.rng 2011-09-22 19:53:25 +0000
43@@ -9,6 +9,12 @@
44 <attribute name="progress"> <text/> </attribute>
45 </optional>
46 <optional>
47+ <attribute name="minDisk"> <text/> </attribute>
48+ </optional>
49+ <optional>
50+ <attribute name="minRam"> <text/> </attribute>
51+ </optional>
52+ <optional>
53 <element name="server">
54 <attribute name="id"> <text/> </attribute>
55 <zeroOrMore>
56
57=== modified file 'nova/api/openstack/views/images.py'
58--- nova/api/openstack/views/images.py 2011-09-20 20:21:06 +0000
59+++ nova/api/openstack/views/images.py 2011-09-22 19:53:25 +0000
60@@ -161,6 +161,11 @@
61
62 if detail:
63 image["metadata"] = image_obj.get("properties", {})
64+ if 'min_ram' in image_obj:
65+ image["minRam"] = image_obj.get("min_ram") or 0
66+
67+ if 'min_disk' in image_obj:
68+ image["minDisk"] = image_obj.get("min_disk") or 0
69
70 return image
71
72
73=== modified file 'nova/compute/api.py'
74--- nova/compute/api.py 2011-09-21 21:00:53 +0000
75+++ nova/compute/api.py 2011-09-22 19:53:25 +0000
76@@ -219,6 +219,11 @@
77 image_href)
78 image = image_service.show(context, image_id)
79
80+ if instance_type['memory_mb'] < int(image.get('min_ram', 0)):
81+ raise exception.InstanceTypeMemoryTooSmall()
82+ if instance_type['local_gb'] < int(image.get('min_disk', 0)):
83+ raise exception.InstanceTypeDiskTooSmall()
84+
85 config_drive_id = None
86 if config_drive and config_drive is not True:
87 # config_drive is volume id
88
89=== modified file 'nova/exception.py'
90--- nova/exception.py 2011-09-16 02:53:42 +0000
91+++ nova/exception.py 2011-09-22 19:53:25 +0000
92@@ -810,3 +810,11 @@
93 if message is None:
94 message = _("1 or more Zones could not complete the request")
95 super(ZoneRequestError, self).__init__(message=message)
96+
97+
98+class InstanceTypeMemoryTooSmall(NovaException):
99+ message = _("Instance type's memory is too small for requested image.")
100+
101+
102+class InstanceTypeDiskTooSmall(NovaException):
103+ message = _("Instance type's disk is too small for requested image.")
104
105=== modified file 'nova/image/glance.py'
106--- nova/image/glance.py 2011-09-20 18:56:15 +0000
107+++ nova/image/glance.py 2011-09-22 19:53:25 +0000
108@@ -404,7 +404,7 @@
109 'container_format', 'checksum', 'id',
110 'name', 'created_at', 'updated_at',
111 'deleted_at', 'deleted', 'status',
112- 'is_public']
113+ 'min_disk', 'min_ram', 'is_public']
114 output = {}
115 for attr in IMAGE_ATTRIBUTES:
116 output[attr] = image_meta.get(attr)
117
118=== modified file 'nova/tests/api/openstack/test_images.py'
119--- nova/tests/api/openstack/test_images.py 2011-09-21 20:17:05 +0000
120+++ nova/tests/api/openstack/test_images.py 2011-09-22 19:53:25 +0000
121@@ -113,6 +113,7 @@
122 self.assertDictMatch(expected_image, actual_image)
123
124 def test_get_image_v1_1(self):
125+ self.maxDiff = None
126 request = webob.Request.blank('/v1.1/fake/images/124')
127 app = fakes.wsgi_app(fake_auth_context=self._get_fake_context())
128 response = request.get_response(app)
129@@ -133,6 +134,8 @@
130 "created": NOW_API_FORMAT,
131 "status": "SAVING",
132 "progress": 0,
133+ "minDisk": 0,
134+ "minRam": 0,
135 'server': {
136 'id': '42',
137 "links": [{
138@@ -542,6 +545,8 @@
139 'created': NOW_API_FORMAT,
140 'status': 'ACTIVE',
141 'progress': 100,
142+ 'minDisk': 0,
143+ 'minRam': 0,
144 "links": [{
145 "rel": "self",
146 "href": "http://localhost/v1.1/fake/images/123",
147@@ -567,6 +572,8 @@
148 'created': NOW_API_FORMAT,
149 'status': 'SAVING',
150 'progress': 0,
151+ 'minDisk': 0,
152+ 'minRam': 0,
153 'server': {
154 'id': '42',
155 "links": [{
156@@ -603,6 +610,8 @@
157 'created': NOW_API_FORMAT,
158 'status': 'SAVING',
159 'progress': 0,
160+ 'minDisk': 0,
161+ 'minRam': 0,
162 'server': {
163 'id': '42',
164 "links": [{
165@@ -639,6 +648,8 @@
166 'created': NOW_API_FORMAT,
167 'status': 'ACTIVE',
168 'progress': 100,
169+ 'minDisk': 0,
170+ 'minRam': 0,
171 'server': {
172 'id': '42',
173 "links": [{
174@@ -675,6 +686,8 @@
175 'created': NOW_API_FORMAT,
176 'status': 'ERROR',
177 'progress': 0,
178+ 'minDisk': 0,
179+ 'minRam': 0,
180 'server': {
181 'id': '42',
182 "links": [{
183@@ -711,6 +724,8 @@
184 'created': NOW_API_FORMAT,
185 'status': 'DELETED',
186 'progress': 0,
187+ 'minDisk': 0,
188+ 'minRam': 0,
189 'server': {
190 'id': '42',
191 "links": [{
192@@ -747,6 +762,8 @@
193 'created': NOW_API_FORMAT,
194 'status': 'DELETED',
195 'progress': 0,
196+ 'minDisk': 0,
197+ 'minRam': 0,
198 'server': {
199 'id': '42',
200 "links": [{
201@@ -780,6 +797,8 @@
202 'created': NOW_API_FORMAT,
203 'status': 'ACTIVE',
204 'progress': 100,
205+ 'minDisk': 0,
206+ 'minRam': 0,
207 "links": [{
208 "rel": "self",
209 "href": "http://localhost/v1.1/fake/images/130",
210@@ -810,6 +829,30 @@
211 controller.index(request)
212 self.mox.VerifyAll()
213
214+ def test_image_filter_with_min_ram(self):
215+ image_service = self.mox.CreateMockAnything()
216+ context = self._get_fake_context()
217+ filters = {'min_ram': '0'}
218+ image_service.index(context, filters=filters).AndReturn([])
219+ self.mox.ReplayAll()
220+ request = webob.Request.blank('/v1.1/images?minRam=0')
221+ request.environ['nova.context'] = context
222+ controller = images.ControllerV11(image_service=image_service)
223+ controller.index(request)
224+ self.mox.VerifyAll()
225+
226+ def test_image_filter_with_min_disk(self):
227+ image_service = self.mox.CreateMockAnything()
228+ context = self._get_fake_context()
229+ filters = {'min_disk': '7'}
230+ image_service.index(context, filters=filters).AndReturn([])
231+ self.mox.ReplayAll()
232+ request = webob.Request.blank('/v1.1/images?minDisk=7')
233+ request.environ['nova.context'] = context
234+ controller = images.ControllerV11(image_service=image_service)
235+ controller.index(request)
236+ self.mox.VerifyAll()
237+
238 def test_image_filter_with_status(self):
239 image_service = self.mox.CreateMockAnything()
240 context = self._get_fake_context()
241@@ -1369,6 +1412,152 @@
242 server_root = root.find('{0}server'.format(NS))
243 self.assertEqual(server_root, None)
244
245+ def test_show_with_min_ram(self):
246+ serializer = images.ImageXMLSerializer()
247+
248+ fixture = {
249+ 'image': {
250+ 'id': 1,
251+ 'name': 'Image1',
252+ 'created': self.TIMESTAMP,
253+ 'updated': self.TIMESTAMP,
254+ 'status': 'ACTIVE',
255+ 'progress': 80,
256+ 'minRam': 256,
257+ 'server': {
258+ 'id': '1',
259+ 'links': [
260+ {
261+ 'href': self.SERVER_HREF,
262+ 'rel': 'self',
263+ },
264+ {
265+ 'href': self.SERVER_BOOKMARK,
266+ 'rel': 'bookmark',
267+ },
268+ ],
269+ },
270+ 'metadata': {
271+ 'key1': 'value1',
272+ },
273+ 'links': [
274+ {
275+ 'href': self.IMAGE_HREF % 1,
276+ 'rel': 'self',
277+ },
278+ {
279+ 'href': self.IMAGE_BOOKMARK % 1,
280+ 'rel': 'bookmark',
281+ },
282+ ],
283+ },
284+ }
285+
286+ output = serializer.serialize(fixture, 'show')
287+ print output
288+ root = etree.XML(output)
289+ xmlutil.validate_schema(root, 'image')
290+ image_dict = fixture['image']
291+
292+ for key in ['name', 'id', 'updated', 'created', 'status', 'progress',
293+ 'minRam']:
294+ self.assertEqual(root.get(key), str(image_dict[key]))
295+
296+ link_nodes = root.findall('{0}link'.format(ATOMNS))
297+ self.assertEqual(len(link_nodes), 2)
298+ for i, link in enumerate(image_dict['links']):
299+ for key, value in link.items():
300+ self.assertEqual(link_nodes[i].get(key), value)
301+
302+ metadata_root = root.find('{0}metadata'.format(NS))
303+ metadata_elems = metadata_root.findall('{0}meta'.format(NS))
304+ self.assertEqual(len(metadata_elems), 1)
305+ for i, metadata_elem in enumerate(metadata_elems):
306+ (meta_key, meta_value) = image_dict['metadata'].items()[i]
307+ self.assertEqual(str(metadata_elem.get('key')), str(meta_key))
308+ self.assertEqual(str(metadata_elem.text).strip(), str(meta_value))
309+
310+ server_root = root.find('{0}server'.format(NS))
311+ self.assertEqual(server_root.get('id'), image_dict['server']['id'])
312+ link_nodes = server_root.findall('{0}link'.format(ATOMNS))
313+ self.assertEqual(len(link_nodes), 2)
314+ for i, link in enumerate(image_dict['server']['links']):
315+ for key, value in link.items():
316+ self.assertEqual(link_nodes[i].get(key), value)
317+
318+ def test_show_with_min_disk(self):
319+ serializer = images.ImageXMLSerializer()
320+
321+ fixture = {
322+ 'image': {
323+ 'id': 1,
324+ 'name': 'Image1',
325+ 'created': self.TIMESTAMP,
326+ 'updated': self.TIMESTAMP,
327+ 'status': 'ACTIVE',
328+ 'progress': 80,
329+ 'minDisk': 5,
330+ 'server': {
331+ 'id': '1',
332+ 'links': [
333+ {
334+ 'href': self.SERVER_HREF,
335+ 'rel': 'self',
336+ },
337+ {
338+ 'href': self.SERVER_BOOKMARK,
339+ 'rel': 'bookmark',
340+ },
341+ ],
342+ },
343+ 'metadata': {
344+ 'key1': 'value1',
345+ },
346+ 'links': [
347+ {
348+ 'href': self.IMAGE_HREF % 1,
349+ 'rel': 'self',
350+ },
351+ {
352+ 'href': self.IMAGE_BOOKMARK % 1,
353+ 'rel': 'bookmark',
354+ },
355+ ],
356+ },
357+ }
358+
359+ output = serializer.serialize(fixture, 'show')
360+ print output
361+ root = etree.XML(output)
362+ xmlutil.validate_schema(root, 'image')
363+ image_dict = fixture['image']
364+
365+ for key in ['name', 'id', 'updated', 'created', 'status', 'progress',
366+ 'minDisk']:
367+ self.assertEqual(root.get(key), str(image_dict[key]))
368+
369+ link_nodes = root.findall('{0}link'.format(ATOMNS))
370+ self.assertEqual(len(link_nodes), 2)
371+ for i, link in enumerate(image_dict['links']):
372+ for key, value in link.items():
373+ self.assertEqual(link_nodes[i].get(key), value)
374+
375+ metadata_root = root.find('{0}metadata'.format(NS))
376+ metadata_elems = metadata_root.findall('{0}meta'.format(NS))
377+ self.assertEqual(len(metadata_elems), 1)
378+ for i, metadata_elem in enumerate(metadata_elems):
379+ (meta_key, meta_value) = image_dict['metadata'].items()[i]
380+ self.assertEqual(str(metadata_elem.get('key')), str(meta_key))
381+ self.assertEqual(str(metadata_elem.text).strip(), str(meta_value))
382+
383+ server_root = root.find('{0}server'.format(NS))
384+ self.assertEqual(server_root.get('id'), image_dict['server']['id'])
385+ link_nodes = server_root.findall('{0}link'.format(ATOMNS))
386+ self.assertEqual(len(link_nodes), 2)
387+ for i, link in enumerate(image_dict['server']['links']):
388+ for key, value in link.items():
389+ self.assertEqual(link_nodes[i].get(key), value)
390+
391 def test_index(self):
392 serializer = images.ImageXMLSerializer()
393
394
395=== modified file 'nova/tests/image/test_glance.py'
396--- nova/tests/image/test_glance.py 2011-09-20 18:56:15 +0000
397+++ nova/tests/image/test_glance.py 2011-09-22 19:53:25 +0000
398@@ -127,6 +127,8 @@
399 'name': 'test image',
400 'is_public': False,
401 'size': None,
402+ 'min_disk': None,
403+ 'min_ram': None,
404 'location': None,
405 'disk_format': None,
406 'container_format': None,
407@@ -157,6 +159,8 @@
408 'name': 'test image',
409 'is_public': False,
410 'size': None,
411+ 'min_disk': None,
412+ 'min_ram': None,
413 'location': None,
414 'disk_format': None,
415 'container_format': None,
416@@ -287,6 +291,8 @@
417 'name': 'TestImage %d' % (i),
418 'properties': {},
419 'size': None,
420+ 'min_disk': None,
421+ 'min_ram': None,
422 'location': None,
423 'disk_format': None,
424 'container_format': None,
425@@ -330,6 +336,8 @@
426 'name': 'TestImage %d' % (i),
427 'properties': {},
428 'size': None,
429+ 'min_disk': None,
430+ 'min_ram': None,
431 'location': None,
432 'disk_format': None,
433 'container_format': None,
434@@ -382,6 +390,8 @@
435 'name': 'image1',
436 'is_public': True,
437 'size': None,
438+ 'min_disk': None,
439+ 'min_ram': None,
440 'location': None,
441 'disk_format': None,
442 'container_format': None,
443@@ -416,6 +426,8 @@
444 'name': 'image10',
445 'is_public': True,
446 'size': None,
447+ 'min_disk': None,
448+ 'min_ram': None,
449 'location': None,
450 'disk_format': None,
451 'container_format': None,
452
453=== modified file 'nova/tests/test_compute.py'
454--- nova/tests/test_compute.py 2011-09-21 20:59:40 +0000
455+++ nova/tests/test_compute.py 2011-09-22 19:53:25 +0000
456@@ -20,6 +20,8 @@
457 Tests For Compute
458 """
459
460+from copy import copy
461+
462 from nova import compute
463 from nova import context
464 from nova import db
465@@ -1394,3 +1396,92 @@
466 self.assertEqual(self.compute_api._volume_size(inst_type,
467 'swap'),
468 swap_size)
469+
470+
471+class ComputeTestMinRamMinDisk(test.TestCase):
472+ def setUp(self):
473+ super(ComputeTestMinRamMinDisk, self).setUp()
474+ self.compute = utils.import_object(FLAGS.compute_manager)
475+ self.compute_api = compute.API()
476+ self.context = context.RequestContext('fake', 'fake')
477+ self.fake_image = {
478+ 'id': 1, 'properties': {'kernel_id': 1, 'ramdisk_id': 1}}
479+
480+ def test_create_with_too_little_ram(self):
481+ """Test an instance type with too little memory"""
482+
483+ inst_type = instance_types.get_default_instance_type()
484+ inst_type['memory_mb'] = 1
485+
486+ def fake_show(*args):
487+ img = copy(self.fake_image)
488+ img['min_ram'] = 2
489+ return img
490+ self.stubs.Set(fake_image._FakeImageService, 'show', fake_show)
491+
492+ self.assertRaises(exception.InstanceTypeMemoryTooSmall,
493+ self.compute_api.create, self.context, inst_type, None)
494+
495+ # Now increase the inst_type memory and make sure all is fine.
496+ inst_type['memory_mb'] = 2
497+ ref = self.compute_api.create(self.context, inst_type, None)
498+ self.assertTrue(ref)
499+
500+ db.instance_destroy(self.context, ref[0]['id'])
501+
502+ def test_create_with_too_little_disk(self):
503+ """Test an instance type with too little disk space"""
504+
505+ inst_type = instance_types.get_default_instance_type()
506+ inst_type['local_gb'] = 1
507+
508+ def fake_show(*args):
509+ img = copy(self.fake_image)
510+ img['min_disk'] = 2
511+ return img
512+ self.stubs.Set(fake_image._FakeImageService, 'show', fake_show)
513+
514+ self.assertRaises(exception.InstanceTypeDiskTooSmall,
515+ self.compute_api.create, self.context, inst_type, None)
516+
517+ # Now increase the inst_type disk space and make sure all is fine.
518+ inst_type['local_gb'] = 2
519+ ref = self.compute_api.create(self.context, inst_type, None)
520+ self.assertTrue(ref)
521+
522+ db.instance_destroy(self.context, ref[0]['id'])
523+
524+ def test_create_just_enough_ram_and_disk(self):
525+ """Test an instance type with just enough ram and disk space"""
526+
527+ inst_type = instance_types.get_default_instance_type()
528+ inst_type['local_gb'] = 2
529+ inst_type['memory_mb'] = 2
530+
531+ def fake_show(*args):
532+ img = copy(self.fake_image)
533+ img['min_ram'] = 2
534+ img['min_disk'] = 2
535+ return img
536+ self.stubs.Set(fake_image._FakeImageService, 'show', fake_show)
537+
538+ ref = self.compute_api.create(self.context, inst_type, None)
539+ self.assertTrue(ref)
540+
541+ db.instance_destroy(self.context, ref[0]['id'])
542+
543+ def test_create_with_no_ram_and_disk_reqs(self):
544+ """Test an instance type with no min_ram or min_disk"""
545+
546+ inst_type = instance_types.get_default_instance_type()
547+ inst_type['local_gb'] = 1
548+ inst_type['memory_mb'] = 1
549+
550+ def fake_show(*args):
551+ return copy(self.fake_image)
552+ self.stubs.Set(fake_image._FakeImageService, 'show', fake_show)
553+
554+ ref = self.compute_api.create(self.context, inst_type, None)
555+ self.assertTrue(ref)
556+
557+ db.instance_destroy(self.context, ref[0]['id'])