Add minDisk and minRam to OSAPI image details

Change-Id: I4bf1920a245de85c88c38ec3ad82dc0e93cc671c
This commit is contained in:
Alex Meade
2011-09-15 14:46:19 -04:00
parent 37100f5653
commit 64736d1e12
10 changed files with 327 additions and 1 deletions

View File

@@ -187,6 +187,10 @@ class CreateInstanceHelper(object):
config_drive=config_drive,)) config_drive=config_drive,))
except quota.QuotaError as error: except quota.QuotaError as error:
self._handle_quota_error(error) self._handle_quota_error(error)
except exception.InstanceTypeMemoryTooSmall as error:
raise exc.HTTPBadRequest(explanation=unicode(error))
except exception.InstanceTypeDiskTooSmall as error:
raise exc.HTTPBadRequest(explanation=unicode(error))
except exception.ImageNotFound as error: except exception.ImageNotFound as error:
msg = _("Can not find requested image") msg = _("Can not find requested image")
raise exc.HTTPBadRequest(explanation=msg) raise exc.HTTPBadRequest(explanation=msg)

View File

@@ -41,6 +41,8 @@ SUPPORTED_FILTERS = {
'changes-since': 'changes-since', 'changes-since': 'changes-since',
'server': 'property-instance_ref', 'server': 'property-instance_ref',
'type': 'property-image_type', 'type': 'property-image_type',
'minRam': 'min_ram',
'minDisk': 'min_disk',
} }
@@ -239,6 +241,10 @@ class ImageXMLSerializer(wsgi.XMLDictSerializer):
image_elem.set('status', str(image_dict['status'])) image_elem.set('status', str(image_dict['status']))
if 'progress' in image_dict: if 'progress' in image_dict:
image_elem.set('progress', str(image_dict['progress'])) image_elem.set('progress', str(image_dict['progress']))
if 'minRam' in image_dict:
image_elem.set('minRam', str(image_dict['minRam']))
if 'minDisk' in image_dict:
image_elem.set('minDisk', str(image_dict['minDisk']))
if 'server' in image_dict: if 'server' in image_dict:
server_elem = self._create_server_node(image_dict['server']) server_elem = self._create_server_node(image_dict['server'])
image_elem.append(server_elem) image_elem.append(server_elem)

View File

@@ -8,6 +8,12 @@
<optional> <optional>
<attribute name="progress"> <text/> </attribute> <attribute name="progress"> <text/> </attribute>
</optional> </optional>
<optional>
<attribute name="minDisk"> <text/> </attribute>
</optional>
<optional>
<attribute name="minRam"> <text/> </attribute>
</optional>
<optional> <optional>
<element name="server"> <element name="server">
<attribute name="id"> <text/> </attribute> <attribute name="id"> <text/> </attribute>

View File

@@ -161,6 +161,11 @@ class ViewBuilderV11(ViewBuilder):
if detail: if detail:
image["metadata"] = image_obj.get("properties", {}) image["metadata"] = image_obj.get("properties", {})
if 'min_ram' in image_obj:
image["minRam"] = image_obj.get("min_ram") or 0
if 'min_disk' in image_obj:
image["minDisk"] = image_obj.get("min_disk") or 0
return image return image

View File

@@ -219,6 +219,11 @@ class API(base.Base):
image_href) image_href)
image = image_service.show(context, image_id) image = image_service.show(context, image_id)
if instance_type['memory_mb'] < int(image.get('min_ram', 0)):
raise exception.InstanceTypeMemoryTooSmall()
if instance_type['local_gb'] < int(image.get('min_disk', 0)):
raise exception.InstanceTypeDiskTooSmall()
config_drive_id = None config_drive_id = None
if config_drive and config_drive is not True: if config_drive and config_drive is not True:
# config_drive is volume id # config_drive is volume id

View File

@@ -810,3 +810,11 @@ class ZoneRequestError(Error):
if message is None: if message is None:
message = _("1 or more Zones could not complete the request") message = _("1 or more Zones could not complete the request")
super(ZoneRequestError, self).__init__(message=message) super(ZoneRequestError, self).__init__(message=message)
class InstanceTypeMemoryTooSmall(NovaException):
message = _("Instance type's memory is too small for requested image.")
class InstanceTypeDiskTooSmall(NovaException):
message = _("Instance type's disk is too small for requested image.")

View File

@@ -404,7 +404,7 @@ def _limit_attributes(image_meta):
'container_format', 'checksum', 'id', 'container_format', 'checksum', 'id',
'name', 'created_at', 'updated_at', 'name', 'created_at', 'updated_at',
'deleted_at', 'deleted', 'status', 'deleted_at', 'deleted', 'status',
'is_public'] 'min_disk', 'min_ram', 'is_public']
output = {} output = {}
for attr in IMAGE_ATTRIBUTES: for attr in IMAGE_ATTRIBUTES:
output[attr] = image_meta.get(attr) output[attr] = image_meta.get(attr)

View File

@@ -113,6 +113,7 @@ class ImagesTest(test.TestCase):
self.assertDictMatch(expected_image, actual_image) self.assertDictMatch(expected_image, actual_image)
def test_get_image_v1_1(self): def test_get_image_v1_1(self):
self.maxDiff = None
request = webob.Request.blank('/v1.1/fake/images/124') request = webob.Request.blank('/v1.1/fake/images/124')
app = fakes.wsgi_app(fake_auth_context=self._get_fake_context()) app = fakes.wsgi_app(fake_auth_context=self._get_fake_context())
response = request.get_response(app) response = request.get_response(app)
@@ -133,6 +134,8 @@ class ImagesTest(test.TestCase):
"created": NOW_API_FORMAT, "created": NOW_API_FORMAT,
"status": "SAVING", "status": "SAVING",
"progress": 0, "progress": 0,
"minDisk": 0,
"minRam": 0,
'server': { 'server': {
'id': '42', 'id': '42',
"links": [{ "links": [{
@@ -542,6 +545,8 @@ class ImagesTest(test.TestCase):
'created': NOW_API_FORMAT, 'created': NOW_API_FORMAT,
'status': 'ACTIVE', 'status': 'ACTIVE',
'progress': 100, 'progress': 100,
'minDisk': 0,
'minRam': 0,
"links": [{ "links": [{
"rel": "self", "rel": "self",
"href": "http://localhost/v1.1/fake/images/123", "href": "http://localhost/v1.1/fake/images/123",
@@ -567,6 +572,8 @@ class ImagesTest(test.TestCase):
'created': NOW_API_FORMAT, 'created': NOW_API_FORMAT,
'status': 'SAVING', 'status': 'SAVING',
'progress': 0, 'progress': 0,
'minDisk': 0,
'minRam': 0,
'server': { 'server': {
'id': '42', 'id': '42',
"links": [{ "links": [{
@@ -603,6 +610,8 @@ class ImagesTest(test.TestCase):
'created': NOW_API_FORMAT, 'created': NOW_API_FORMAT,
'status': 'SAVING', 'status': 'SAVING',
'progress': 0, 'progress': 0,
'minDisk': 0,
'minRam': 0,
'server': { 'server': {
'id': '42', 'id': '42',
"links": [{ "links": [{
@@ -639,6 +648,8 @@ class ImagesTest(test.TestCase):
'created': NOW_API_FORMAT, 'created': NOW_API_FORMAT,
'status': 'ACTIVE', 'status': 'ACTIVE',
'progress': 100, 'progress': 100,
'minDisk': 0,
'minRam': 0,
'server': { 'server': {
'id': '42', 'id': '42',
"links": [{ "links": [{
@@ -675,6 +686,8 @@ class ImagesTest(test.TestCase):
'created': NOW_API_FORMAT, 'created': NOW_API_FORMAT,
'status': 'ERROR', 'status': 'ERROR',
'progress': 0, 'progress': 0,
'minDisk': 0,
'minRam': 0,
'server': { 'server': {
'id': '42', 'id': '42',
"links": [{ "links": [{
@@ -711,6 +724,8 @@ class ImagesTest(test.TestCase):
'created': NOW_API_FORMAT, 'created': NOW_API_FORMAT,
'status': 'DELETED', 'status': 'DELETED',
'progress': 0, 'progress': 0,
'minDisk': 0,
'minRam': 0,
'server': { 'server': {
'id': '42', 'id': '42',
"links": [{ "links": [{
@@ -747,6 +762,8 @@ class ImagesTest(test.TestCase):
'created': NOW_API_FORMAT, 'created': NOW_API_FORMAT,
'status': 'DELETED', 'status': 'DELETED',
'progress': 0, 'progress': 0,
'minDisk': 0,
'minRam': 0,
'server': { 'server': {
'id': '42', 'id': '42',
"links": [{ "links": [{
@@ -780,6 +797,8 @@ class ImagesTest(test.TestCase):
'created': NOW_API_FORMAT, 'created': NOW_API_FORMAT,
'status': 'ACTIVE', 'status': 'ACTIVE',
'progress': 100, 'progress': 100,
'minDisk': 0,
'minRam': 0,
"links": [{ "links": [{
"rel": "self", "rel": "self",
"href": "http://localhost/v1.1/fake/images/130", "href": "http://localhost/v1.1/fake/images/130",
@@ -810,6 +829,30 @@ class ImagesTest(test.TestCase):
controller.index(request) controller.index(request)
self.mox.VerifyAll() self.mox.VerifyAll()
def test_image_filter_with_min_ram(self):
image_service = self.mox.CreateMockAnything()
context = self._get_fake_context()
filters = {'min_ram': '0'}
image_service.index(context, filters=filters).AndReturn([])
self.mox.ReplayAll()
request = webob.Request.blank('/v1.1/images?minRam=0')
request.environ['nova.context'] = context
controller = images.ControllerV11(image_service=image_service)
controller.index(request)
self.mox.VerifyAll()
def test_image_filter_with_min_disk(self):
image_service = self.mox.CreateMockAnything()
context = self._get_fake_context()
filters = {'min_disk': '7'}
image_service.index(context, filters=filters).AndReturn([])
self.mox.ReplayAll()
request = webob.Request.blank('/v1.1/images?minDisk=7')
request.environ['nova.context'] = context
controller = images.ControllerV11(image_service=image_service)
controller.index(request)
self.mox.VerifyAll()
def test_image_filter_with_status(self): def test_image_filter_with_status(self):
image_service = self.mox.CreateMockAnything() image_service = self.mox.CreateMockAnything()
context = self._get_fake_context() context = self._get_fake_context()
@@ -1369,6 +1412,152 @@ class ImageXMLSerializationTest(test.TestCase):
server_root = root.find('{0}server'.format(NS)) server_root = root.find('{0}server'.format(NS))
self.assertEqual(server_root, None) self.assertEqual(server_root, None)
def test_show_with_min_ram(self):
serializer = images.ImageXMLSerializer()
fixture = {
'image': {
'id': 1,
'name': 'Image1',
'created': self.TIMESTAMP,
'updated': self.TIMESTAMP,
'status': 'ACTIVE',
'progress': 80,
'minRam': 256,
'server': {
'id': '1',
'links': [
{
'href': self.SERVER_HREF,
'rel': 'self',
},
{
'href': self.SERVER_BOOKMARK,
'rel': 'bookmark',
},
],
},
'metadata': {
'key1': 'value1',
},
'links': [
{
'href': self.IMAGE_HREF % 1,
'rel': 'self',
},
{
'href': self.IMAGE_BOOKMARK % 1,
'rel': 'bookmark',
},
],
},
}
output = serializer.serialize(fixture, 'show')
print output
root = etree.XML(output)
xmlutil.validate_schema(root, 'image')
image_dict = fixture['image']
for key in ['name', 'id', 'updated', 'created', 'status', 'progress',
'minRam']:
self.assertEqual(root.get(key), str(image_dict[key]))
link_nodes = root.findall('{0}link'.format(ATOMNS))
self.assertEqual(len(link_nodes), 2)
for i, link in enumerate(image_dict['links']):
for key, value in link.items():
self.assertEqual(link_nodes[i].get(key), value)
metadata_root = root.find('{0}metadata'.format(NS))
metadata_elems = metadata_root.findall('{0}meta'.format(NS))
self.assertEqual(len(metadata_elems), 1)
for i, metadata_elem in enumerate(metadata_elems):
(meta_key, meta_value) = image_dict['metadata'].items()[i]
self.assertEqual(str(metadata_elem.get('key')), str(meta_key))
self.assertEqual(str(metadata_elem.text).strip(), str(meta_value))
server_root = root.find('{0}server'.format(NS))
self.assertEqual(server_root.get('id'), image_dict['server']['id'])
link_nodes = server_root.findall('{0}link'.format(ATOMNS))
self.assertEqual(len(link_nodes), 2)
for i, link in enumerate(image_dict['server']['links']):
for key, value in link.items():
self.assertEqual(link_nodes[i].get(key), value)
def test_show_with_min_disk(self):
serializer = images.ImageXMLSerializer()
fixture = {
'image': {
'id': 1,
'name': 'Image1',
'created': self.TIMESTAMP,
'updated': self.TIMESTAMP,
'status': 'ACTIVE',
'progress': 80,
'minDisk': 5,
'server': {
'id': '1',
'links': [
{
'href': self.SERVER_HREF,
'rel': 'self',
},
{
'href': self.SERVER_BOOKMARK,
'rel': 'bookmark',
},
],
},
'metadata': {
'key1': 'value1',
},
'links': [
{
'href': self.IMAGE_HREF % 1,
'rel': 'self',
},
{
'href': self.IMAGE_BOOKMARK % 1,
'rel': 'bookmark',
},
],
},
}
output = serializer.serialize(fixture, 'show')
print output
root = etree.XML(output)
xmlutil.validate_schema(root, 'image')
image_dict = fixture['image']
for key in ['name', 'id', 'updated', 'created', 'status', 'progress',
'minDisk']:
self.assertEqual(root.get(key), str(image_dict[key]))
link_nodes = root.findall('{0}link'.format(ATOMNS))
self.assertEqual(len(link_nodes), 2)
for i, link in enumerate(image_dict['links']):
for key, value in link.items():
self.assertEqual(link_nodes[i].get(key), value)
metadata_root = root.find('{0}metadata'.format(NS))
metadata_elems = metadata_root.findall('{0}meta'.format(NS))
self.assertEqual(len(metadata_elems), 1)
for i, metadata_elem in enumerate(metadata_elems):
(meta_key, meta_value) = image_dict['metadata'].items()[i]
self.assertEqual(str(metadata_elem.get('key')), str(meta_key))
self.assertEqual(str(metadata_elem.text).strip(), str(meta_value))
server_root = root.find('{0}server'.format(NS))
self.assertEqual(server_root.get('id'), image_dict['server']['id'])
link_nodes = server_root.findall('{0}link'.format(ATOMNS))
self.assertEqual(len(link_nodes), 2)
for i, link in enumerate(image_dict['server']['links']):
for key, value in link.items():
self.assertEqual(link_nodes[i].get(key), value)
def test_index(self): def test_index(self):
serializer = images.ImageXMLSerializer() serializer = images.ImageXMLSerializer()

View File

@@ -127,6 +127,8 @@ class TestGlanceImageService(test.TestCase):
'name': 'test image', 'name': 'test image',
'is_public': False, 'is_public': False,
'size': None, 'size': None,
'min_disk': None,
'min_ram': None,
'location': None, 'location': None,
'disk_format': None, 'disk_format': None,
'container_format': None, 'container_format': None,
@@ -157,6 +159,8 @@ class TestGlanceImageService(test.TestCase):
'name': 'test image', 'name': 'test image',
'is_public': False, 'is_public': False,
'size': None, 'size': None,
'min_disk': None,
'min_ram': None,
'location': None, 'location': None,
'disk_format': None, 'disk_format': None,
'container_format': None, 'container_format': None,
@@ -287,6 +291,8 @@ class TestGlanceImageService(test.TestCase):
'name': 'TestImage %d' % (i), 'name': 'TestImage %d' % (i),
'properties': {}, 'properties': {},
'size': None, 'size': None,
'min_disk': None,
'min_ram': None,
'location': None, 'location': None,
'disk_format': None, 'disk_format': None,
'container_format': None, 'container_format': None,
@@ -330,6 +336,8 @@ class TestGlanceImageService(test.TestCase):
'name': 'TestImage %d' % (i), 'name': 'TestImage %d' % (i),
'properties': {}, 'properties': {},
'size': None, 'size': None,
'min_disk': None,
'min_ram': None,
'location': None, 'location': None,
'disk_format': None, 'disk_format': None,
'container_format': None, 'container_format': None,
@@ -382,6 +390,8 @@ class TestGlanceImageService(test.TestCase):
'name': 'image1', 'name': 'image1',
'is_public': True, 'is_public': True,
'size': None, 'size': None,
'min_disk': None,
'min_ram': None,
'location': None, 'location': None,
'disk_format': None, 'disk_format': None,
'container_format': None, 'container_format': None,
@@ -416,6 +426,8 @@ class TestGlanceImageService(test.TestCase):
'name': 'image10', 'name': 'image10',
'is_public': True, 'is_public': True,
'size': None, 'size': None,
'min_disk': None,
'min_ram': None,
'location': None, 'location': None,
'disk_format': None, 'disk_format': None,
'container_format': None, 'container_format': None,

View File

@@ -20,6 +20,8 @@
Tests For Compute Tests For Compute
""" """
from copy import copy
from nova import compute from nova import compute
from nova import context from nova import context
from nova import db from nova import db
@@ -1394,3 +1396,92 @@ class ComputeTestCase(test.TestCase):
self.assertEqual(self.compute_api._volume_size(inst_type, self.assertEqual(self.compute_api._volume_size(inst_type,
'swap'), 'swap'),
swap_size) swap_size)
class ComputeTestMinRamMinDisk(test.TestCase):
def setUp(self):
super(ComputeTestMinRamMinDisk, self).setUp()
self.compute = utils.import_object(FLAGS.compute_manager)
self.compute_api = compute.API()
self.context = context.RequestContext('fake', 'fake')
self.fake_image = {
'id': 1, 'properties': {'kernel_id': 1, 'ramdisk_id': 1}}
def test_create_with_too_little_ram(self):
"""Test an instance type with too little memory"""
inst_type = instance_types.get_default_instance_type()
inst_type['memory_mb'] = 1
def fake_show(*args):
img = copy(self.fake_image)
img['min_ram'] = 2
return img
self.stubs.Set(fake_image._FakeImageService, 'show', fake_show)
self.assertRaises(exception.InstanceTypeMemoryTooSmall,
self.compute_api.create, self.context, inst_type, None)
# Now increase the inst_type memory and make sure all is fine.
inst_type['memory_mb'] = 2
ref = self.compute_api.create(self.context, inst_type, None)
self.assertTrue(ref)
db.instance_destroy(self.context, ref[0]['id'])
def test_create_with_too_little_disk(self):
"""Test an instance type with too little disk space"""
inst_type = instance_types.get_default_instance_type()
inst_type['local_gb'] = 1
def fake_show(*args):
img = copy(self.fake_image)
img['min_disk'] = 2
return img
self.stubs.Set(fake_image._FakeImageService, 'show', fake_show)
self.assertRaises(exception.InstanceTypeDiskTooSmall,
self.compute_api.create, self.context, inst_type, None)
# Now increase the inst_type disk space and make sure all is fine.
inst_type['local_gb'] = 2
ref = self.compute_api.create(self.context, inst_type, None)
self.assertTrue(ref)
db.instance_destroy(self.context, ref[0]['id'])
def test_create_just_enough_ram_and_disk(self):
"""Test an instance type with just enough ram and disk space"""
inst_type = instance_types.get_default_instance_type()
inst_type['local_gb'] = 2
inst_type['memory_mb'] = 2
def fake_show(*args):
img = copy(self.fake_image)
img['min_ram'] = 2
img['min_disk'] = 2
return img
self.stubs.Set(fake_image._FakeImageService, 'show', fake_show)
ref = self.compute_api.create(self.context, inst_type, None)
self.assertTrue(ref)
db.instance_destroy(self.context, ref[0]['id'])
def test_create_with_no_ram_and_disk_reqs(self):
"""Test an instance type with no min_ram or min_disk"""
inst_type = instance_types.get_default_instance_type()
inst_type['local_gb'] = 1
inst_type['memory_mb'] = 1
def fake_show(*args):
return copy(self.fake_image)
self.stubs.Set(fake_image._FakeImageService, 'show', fake_show)
ref = self.compute_api.create(self.context, inst_type, None)
self.assertTrue(ref)
db.instance_destroy(self.context, ref[0]['id'])