Added min_disk and min_ram properties to images
Fixes LP Bug#849368 Change-Id: I3e17370537144d117d99af5fa5a21df830b7c7ed
This commit is contained in:
parent
ec4af4bea7
commit
20b7c69758
1
Authors
1
Authors
@ -1,4 +1,5 @@
|
||||
Adam Gandelman <adam.gandelman@canonical.com>
|
||||
Alex Meade <alex.meade@rackspace.com>
|
||||
Andrey Brindeyev <abrindeyev@griddynamics.com>
|
||||
Brian Lamar <brian.lamar@rackspace.com>
|
||||
Brian Waldon <brian.waldon@rackspace.com>
|
||||
|
@ -114,7 +114,7 @@ def get_image_filters_from_args(args):
|
||||
return FAILURE
|
||||
|
||||
SUPPORTED_FILTERS = ['name', 'disk_format', 'container_format', 'status',
|
||||
'size_min', 'size_max']
|
||||
'min_ram', 'min_disk', 'size_min', 'size_max']
|
||||
filters = {}
|
||||
for (key, value) in fields.items():
|
||||
if key not in SUPPORTED_FILTERS:
|
||||
@ -141,6 +141,8 @@ def print_image_formatted(client, image):
|
||||
print "Size: %d" % int(image['size'])
|
||||
print "Disk format: %s" % image['disk_format']
|
||||
print "Container format: %s" % image['container_format']
|
||||
print "Minimum Ram Required (MB): %s" % image['min_ram']
|
||||
print "Minimum Disk Required (GB): %s" % image['min_disk']
|
||||
if image['owner']:
|
||||
print "Owner: %s" % image['owner']
|
||||
if len(image['properties']) > 0:
|
||||
@ -216,6 +218,8 @@ EXAMPLES
|
||||
'is_public': common_utils.bool_from_string(
|
||||
fields.pop('is_public', False)),
|
||||
'disk_format': fields.pop('disk_format', 'raw'),
|
||||
'min_disk': fields.pop('min_disk', 0),
|
||||
'min_ram': fields.pop('min_ram', 0),
|
||||
'container_format': fields.pop('container_format', 'ovf')}
|
||||
|
||||
# Strip any args that are not supported
|
||||
@ -329,7 +333,7 @@ to spell field names correctly. :)"""
|
||||
fields.pop(field)
|
||||
|
||||
base_image_fields = ['disk_format', 'container_format', 'name',
|
||||
'location', 'owner']
|
||||
'min_disk', 'min_ram', 'location', 'owner']
|
||||
for field in base_image_fields:
|
||||
fvalue = fields.pop(field, None)
|
||||
if fvalue is not None:
|
||||
|
@ -73,6 +73,8 @@ JSON-encoded mapping in the following format::
|
||||
'deleted_at': '',
|
||||
'status': 'active',
|
||||
'is_public': true,
|
||||
'min_ram': 256,
|
||||
'min_disk': 5,
|
||||
'owner': null,
|
||||
'properties': {'distro': 'Ubuntu 10.04 LTS'}},
|
||||
...]}
|
||||
@ -95,6 +97,12 @@ JSON-encoded mapping in the following format::
|
||||
The `is_public` field is a boolean indicating whether the image is
|
||||
publically available
|
||||
|
||||
The 'min_ram' field is an integer specifying the minimum amount of
|
||||
ram needed to run this image on an instance, in megabytes
|
||||
|
||||
The 'min_disk' field is an integer specifying the minimum amount of
|
||||
disk space needed to run this image on an instance, in gigabytes
|
||||
|
||||
The `owner` field is a string which may either be null or which will
|
||||
indicate the owner of the image
|
||||
|
||||
@ -181,6 +189,8 @@ following shows an example of the HTTP headers returned from the above
|
||||
x-image-meta-deleted_at
|
||||
x-image-meta-status available
|
||||
x-image-meta-is-public true
|
||||
x-image-meta-min-ram 256
|
||||
x-image-meta-min-disk 0
|
||||
x-image-meta-owner null
|
||||
x-image-meta-property-distro Ubuntu 10.04 LTS
|
||||
|
||||
@ -241,6 +251,8 @@ returned from the above ``GET`` request::
|
||||
x-image-meta-deleted_at
|
||||
x-image-meta-status available
|
||||
x-image-meta-is-public true
|
||||
x-image-meta-min-ram 256
|
||||
x-image-meta-min-disk 5
|
||||
x-image-meta-owner null
|
||||
x-image-meta-property-distro Ubuntu 10.04 LTS
|
||||
|
||||
@ -383,6 +395,20 @@ The list of metadata headers that Glance accepts are listed below.
|
||||
When not present, the image is assumed to be *not public* and specific to
|
||||
a user.
|
||||
|
||||
* ``x-image-meta-min-ram``
|
||||
|
||||
This header is optional. When present it shall be the expected minimum ram
|
||||
required in megabytes to run this image on a server.
|
||||
|
||||
When not present, the image is assumed to have a minimum ram requirement of 0.
|
||||
|
||||
* ``x-image-meta-min-disk``
|
||||
|
||||
This header is optional. When present it shall be the expected minimum disk
|
||||
space required in gigabytes to run this image on a server.
|
||||
|
||||
When not present, the image is assumed to have a minimum disk space requirement of 0.
|
||||
|
||||
* ``x-image-meta-owner``
|
||||
|
||||
This header is optional and only meaningful for admins.
|
||||
|
@ -55,7 +55,8 @@ from glance import utils
|
||||
logger = logging.getLogger('glance.api.v1.images')
|
||||
|
||||
SUPPORTED_FILTERS = ['name', 'status', 'container_format', 'disk_format',
|
||||
'size_min', 'size_max', 'is_public']
|
||||
'min_ram', 'min_disk', 'size_min', 'size_max',
|
||||
'is_public']
|
||||
|
||||
SUPPORTED_PARAMS = ('limit', 'marker', 'sort_key', 'sort_dir')
|
||||
|
||||
@ -130,6 +131,8 @@ class Controller(api.BaseController):
|
||||
'disk_format': <DISK_FORMAT>,
|
||||
'container_format': <CONTAINER_FORMAT>,
|
||||
'checksum': <CHECKSUM>,
|
||||
'min_disk': <MIN_DISK>,
|
||||
'min_ram': <MIN_RAM>,
|
||||
'store': <STORE>,
|
||||
'status': <STATUS>,
|
||||
'created_at': <TIMESTAMP>,
|
||||
|
@ -47,8 +47,8 @@ BASE_MODEL_ATTRS = set(['id', 'created_at', 'updated_at', 'deleted_at',
|
||||
|
||||
IMAGE_ATTRS = BASE_MODEL_ATTRS | set(['name', 'status', 'size',
|
||||
'disk_format', 'container_format',
|
||||
'is_public', 'location', 'checksum',
|
||||
'owner'])
|
||||
'min_disk', 'min_ram', 'is_public',
|
||||
'location', 'checksum', 'owner'])
|
||||
|
||||
CONTAINER_FORMATS = ['ami', 'ari', 'aki', 'bare', 'ovf']
|
||||
DISK_FORMATS = ['ami', 'ari', 'aki', 'vhd', 'vmdk', 'raw', 'qcow2', 'vdi',
|
||||
@ -327,6 +327,12 @@ def _image_update(context, values, image_id, purge_props=False):
|
||||
if 'size' in values:
|
||||
values['size'] = int(values['size'])
|
||||
|
||||
if 'min_ram' in values:
|
||||
values['min_ram'] = int(values['min_ram'])
|
||||
|
||||
if 'min_disk' in values:
|
||||
values['min_disk'] = int(values['min_disk'])
|
||||
|
||||
values['is_public'] = bool(values.get('is_public', False))
|
||||
image_ref = models.Image()
|
||||
|
||||
|
@ -0,0 +1,86 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2011 OpenStack LLC.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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.
|
||||
|
||||
from migrate.changeset import *
|
||||
from sqlalchemy import *
|
||||
from sqlalchemy.sql import and_, not_
|
||||
|
||||
from glance.registry.db.migrate_repo.schema import (
|
||||
Boolean, DateTime, Integer, String, Text, from_migration_import)
|
||||
|
||||
|
||||
def get_images_table(meta):
|
||||
"""
|
||||
Returns the Table object for the images table that
|
||||
corresponds to the images table definition of this version.
|
||||
"""
|
||||
images = Table('images', meta,
|
||||
Column('id', Integer(), primary_key=True, nullable=False),
|
||||
Column('name', String(255)),
|
||||
Column('disk_format', String(20)),
|
||||
Column('container_format', String(20)),
|
||||
Column('size', Integer()),
|
||||
Column('status', String(30), nullable=False),
|
||||
Column('is_public', Boolean(), nullable=False, default=False,
|
||||
index=True),
|
||||
Column('location', Text()),
|
||||
Column('created_at', DateTime(), nullable=False),
|
||||
Column('updated_at', DateTime()),
|
||||
Column('deleted_at', DateTime()),
|
||||
Column('deleted', Boolean(), nullable=False, default=False,
|
||||
index=True),
|
||||
Column('checksum', String(32)),
|
||||
Column('min_disk', Integer(), default=0),
|
||||
Column('min_ram', Integer(), default=0),
|
||||
mysql_engine='InnoDB',
|
||||
useexisting=True)
|
||||
|
||||
return images
|
||||
|
||||
|
||||
def get_image_properties_table(meta):
|
||||
"""
|
||||
No changes to the image properties table from 008...
|
||||
"""
|
||||
(define_image_properties_table,) = from_migration_import(
|
||||
'008_add_image_members_table', ['define_image_properties_table'])
|
||||
|
||||
image_properties = define_image_properties_table(meta)
|
||||
return image_properties
|
||||
|
||||
|
||||
def upgrade(migrate_engine):
|
||||
meta = MetaData()
|
||||
meta.bind = migrate_engine
|
||||
|
||||
images = get_images_table(meta)
|
||||
|
||||
min_disk = Column('min_disk', Integer(), default=0)
|
||||
min_disk.create(images)
|
||||
|
||||
min_ram = Column('min_ram', Integer(), default=0)
|
||||
min_ram.create(images)
|
||||
|
||||
|
||||
def downgrade(migrate_engine):
|
||||
meta = MetaData()
|
||||
meta.bind = migrate_engine
|
||||
|
||||
images = get_images_table(meta)
|
||||
|
||||
images.columns['min_disk'].drop()
|
||||
images.columns['min_ram'].drop()
|
@ -105,6 +105,8 @@ class Image(BASE, ModelBase):
|
||||
is_public = Column(Boolean, nullable=False, default=False)
|
||||
location = Column(Text)
|
||||
checksum = Column(String(32))
|
||||
min_disk = Column(Integer(), default=0)
|
||||
min_ram = Column(Integer(), default=0)
|
||||
owner = Column(String(255))
|
||||
|
||||
|
||||
|
@ -38,7 +38,7 @@ DISPLAY_FIELDS_IN_INDEX = ['id', 'name', 'size',
|
||||
'checksum']
|
||||
|
||||
SUPPORTED_FILTERS = ['name', 'status', 'container_format', 'disk_format',
|
||||
'size_min', 'size_max']
|
||||
'min_ram', 'min_disk', 'size_min', 'size_max']
|
||||
|
||||
SUPPORTED_SORT_KEYS = ('name', 'status', 'container_format', 'disk_format',
|
||||
'size', 'id', 'created_at', 'updated_at')
|
||||
|
@ -318,7 +318,8 @@ class TestBinGlance(functional.FunctionalTest):
|
||||
_add_args = [
|
||||
"name=Name1 disk_format=vhd container_format=ovf foo=bar",
|
||||
"name=Name2 disk_format=ami container_format=ami foo=bar",
|
||||
"name=Name3 disk_format=vhd container_format=ovf foo=baz",
|
||||
"name=Name3 disk_format=vhd container_format=ovf foo=baz "
|
||||
"min_disk=7 min_ram=256",
|
||||
]
|
||||
|
||||
for i, args in enumerate(_add_args):
|
||||
@ -402,9 +403,27 @@ class TestBinGlance(functional.FunctionalTest):
|
||||
|
||||
self.assertEqual(0, exitcode)
|
||||
image_lines = out.split("\n")[1:-1]
|
||||
self.assertEqual(20, len(image_lines))
|
||||
self.assertEqual(24, len(image_lines))
|
||||
self.assertTrue(image_lines[1].startswith('Id: 2'))
|
||||
self.assertTrue(image_lines[11].startswith('Id: 1'))
|
||||
self.assertTrue(image_lines[13].startswith('Id: 1'))
|
||||
|
||||
# 10. Check min_ram filter
|
||||
cmd = "min_ram=256"
|
||||
exitcode, out, err = execute("%s %s" % (_details_cmd, cmd))
|
||||
|
||||
self.assertEqual(0, exitcode)
|
||||
image_lines = out.split("\n")[2:-1]
|
||||
self.assertEqual(11, len(image_lines))
|
||||
self.assertTrue(image_lines[0].startswith('Id: 3'))
|
||||
|
||||
# 11. Check min_disk filter
|
||||
cmd = "min_disk=7"
|
||||
exitcode, out, err = execute("%s %s" % (_details_cmd, cmd))
|
||||
|
||||
self.assertEqual(0, exitcode)
|
||||
image_lines = out.split("\n")[2:-1]
|
||||
self.assertEqual(11, len(image_lines))
|
||||
self.assertTrue(image_lines[0].startswith('Id: 3'))
|
||||
|
||||
self.stop_servers()
|
||||
|
||||
@ -483,9 +502,9 @@ class TestBinGlance(functional.FunctionalTest):
|
||||
|
||||
self.assertEqual(0, exitcode)
|
||||
image_lines = out.split("\n")[1:-1]
|
||||
self.assertEqual(18, len(image_lines))
|
||||
self.assertEqual(22, len(image_lines))
|
||||
self.assertTrue(image_lines[1].startswith('Id: 3'))
|
||||
self.assertTrue(image_lines[10].startswith('Id: 1'))
|
||||
self.assertTrue(image_lines[12].startswith('Id: 1'))
|
||||
|
||||
def test_results_sorting(self):
|
||||
self.cleanup()
|
||||
@ -555,7 +574,7 @@ class TestBinGlance(functional.FunctionalTest):
|
||||
|
||||
self.assertEqual(0, exitcode)
|
||||
image_lines = out.split("\n")[1:-1]
|
||||
self.assertEqual(27, len(image_lines))
|
||||
self.assertEqual(33, len(image_lines))
|
||||
self.assertTrue(image_lines[1].startswith('Id: 3'))
|
||||
self.assertTrue(image_lines[10].startswith('Id: 2'))
|
||||
self.assertTrue(image_lines[19].startswith('Id: 5'))
|
||||
self.assertTrue(image_lines[12].startswith('Id: 2'))
|
||||
self.assertTrue(image_lines[23].startswith('Id: 5'))
|
||||
|
@ -62,6 +62,8 @@ class TestRegistryAPI(unittest.TestCase):
|
||||
'deleted_at': None,
|
||||
'deleted': False,
|
||||
'checksum': None,
|
||||
'min_disk': 0,
|
||||
'min_ram': 0,
|
||||
'size': 13,
|
||||
'location': "swift://user:passwd@acct/container/obj.tar.0",
|
||||
'properties': {'type': 'kernel'}},
|
||||
@ -76,6 +78,8 @@ class TestRegistryAPI(unittest.TestCase):
|
||||
'deleted_at': None,
|
||||
'deleted': False,
|
||||
'checksum': None,
|
||||
'min_disk': 5,
|
||||
'min_ram': 256,
|
||||
'size': 19,
|
||||
'location': "file:///tmp/glance-tests/2",
|
||||
'properties': {}}]
|
||||
@ -107,6 +111,8 @@ class TestRegistryAPI(unittest.TestCase):
|
||||
fixture = {'id': 2,
|
||||
'name': 'fake image #2',
|
||||
'size': 19,
|
||||
'min_ram': 256,
|
||||
'min_disk': 5,
|
||||
'checksum': None}
|
||||
req = webob.Request.blank('/images/2')
|
||||
res = req.get_response(self.api)
|
||||
@ -779,6 +785,8 @@ class TestRegistryAPI(unittest.TestCase):
|
||||
'name': 'fake image #2',
|
||||
'is_public': True,
|
||||
'size': 19,
|
||||
'min_disk': 5,
|
||||
'min_ram': 256,
|
||||
'checksum': None,
|
||||
'disk_format': 'vhd',
|
||||
'container_format': 'ovf',
|
||||
@ -958,6 +966,84 @@ class TestRegistryAPI(unittest.TestCase):
|
||||
for image in images:
|
||||
self.assertEqual('ovf', image['container_format'])
|
||||
|
||||
def test_get_details_filter_min_disk(self):
|
||||
"""
|
||||
Tests that the /images/detail registry API returns list of
|
||||
public images that have a specific min_disk
|
||||
"""
|
||||
extra_fixture = {'id': 3,
|
||||
'status': 'active',
|
||||
'is_public': True,
|
||||
'disk_format': 'vhd',
|
||||
'container_format': 'ovf',
|
||||
'name': 'fake image #3',
|
||||
'size': 19,
|
||||
'min_disk': 7,
|
||||
'checksum': None}
|
||||
|
||||
db_api.image_create(self.context, extra_fixture)
|
||||
|
||||
extra_fixture = {'id': 4,
|
||||
'status': 'active',
|
||||
'is_public': True,
|
||||
'disk_format': 'ami',
|
||||
'container_format': 'ami',
|
||||
'name': 'fake image #4',
|
||||
'size': 19,
|
||||
'checksum': None}
|
||||
|
||||
db_api.image_create(self.context, extra_fixture)
|
||||
|
||||
req = webob.Request.blank('/images/detail?min_disk=7')
|
||||
res = req.get_response(self.api)
|
||||
res_dict = json.loads(res.body)
|
||||
self.assertEquals(res.status_int, 200)
|
||||
|
||||
images = res_dict['images']
|
||||
self.assertEquals(len(images), 1)
|
||||
|
||||
for image in images:
|
||||
self.assertEqual(7, image['min_disk'])
|
||||
|
||||
def test_get_details_filter_min_ram(self):
|
||||
"""
|
||||
Tests that the /images/detail registry API returns list of
|
||||
public images that have a specific min_ram
|
||||
"""
|
||||
extra_fixture = {'id': 3,
|
||||
'status': 'active',
|
||||
'is_public': True,
|
||||
'disk_format': 'vhd',
|
||||
'container_format': 'ovf',
|
||||
'name': 'fake image #3',
|
||||
'size': 19,
|
||||
'min_ram': 514,
|
||||
'checksum': None}
|
||||
|
||||
db_api.image_create(self.context, extra_fixture)
|
||||
|
||||
extra_fixture = {'id': 4,
|
||||
'status': 'active',
|
||||
'is_public': True,
|
||||
'disk_format': 'ami',
|
||||
'container_format': 'ami',
|
||||
'name': 'fake image #4',
|
||||
'size': 19,
|
||||
'checksum': None}
|
||||
|
||||
db_api.image_create(self.context, extra_fixture)
|
||||
|
||||
req = webob.Request.blank('/images/detail?min_ram=514')
|
||||
res = req.get_response(self.api)
|
||||
res_dict = json.loads(res.body)
|
||||
self.assertEquals(res.status_int, 200)
|
||||
|
||||
images = res_dict['images']
|
||||
self.assertEquals(len(images), 1)
|
||||
|
||||
for image in images:
|
||||
self.assertEqual(514, image['min_ram'])
|
||||
|
||||
def test_get_details_filter_disk_format(self):
|
||||
"""
|
||||
Tests that the /images/detail registry API returns list of
|
||||
@ -1307,6 +1393,92 @@ class TestRegistryAPI(unittest.TestCase):
|
||||
# Test status was updated properly
|
||||
self.assertEquals('active', res_dict['image']['status'])
|
||||
|
||||
def test_create_image_with_min_disk(self):
|
||||
"""Tests that the /images POST registry API creates the image"""
|
||||
fixture = {'name': 'fake public image',
|
||||
'is_public': True,
|
||||
'min_disk': 5,
|
||||
'disk_format': 'vhd',
|
||||
'container_format': 'ovf'}
|
||||
|
||||
req = webob.Request.blank('/images')
|
||||
|
||||
req.method = 'POST'
|
||||
req.content_type = 'application/json'
|
||||
req.body = json.dumps(dict(image=fixture))
|
||||
|
||||
res = req.get_response(self.api)
|
||||
|
||||
self.assertEquals(res.status_int, 200)
|
||||
|
||||
res_dict = json.loads(res.body)
|
||||
|
||||
self.assertEquals(5, res_dict['image']['min_disk'])
|
||||
|
||||
def test_create_image_with_min_ram(self):
|
||||
"""Tests that the /images POST registry API creates the image"""
|
||||
fixture = {'name': 'fake public image',
|
||||
'is_public': True,
|
||||
'min_ram': 256,
|
||||
'disk_format': 'vhd',
|
||||
'container_format': 'ovf'}
|
||||
|
||||
req = webob.Request.blank('/images')
|
||||
|
||||
req.method = 'POST'
|
||||
req.content_type = 'application/json'
|
||||
req.body = json.dumps(dict(image=fixture))
|
||||
|
||||
res = req.get_response(self.api)
|
||||
|
||||
self.assertEquals(res.status_int, 200)
|
||||
|
||||
res_dict = json.loads(res.body)
|
||||
|
||||
self.assertEquals(256, res_dict['image']['min_ram'])
|
||||
|
||||
def test_create_image_with_min_ram_default(self):
|
||||
"""Tests that the /images POST registry API creates the image"""
|
||||
fixture = {'name': 'fake public image',
|
||||
'is_public': True,
|
||||
'disk_format': 'vhd',
|
||||
'container_format': 'ovf'}
|
||||
|
||||
req = webob.Request.blank('/images')
|
||||
|
||||
req.method = 'POST'
|
||||
req.content_type = 'application/json'
|
||||
req.body = json.dumps(dict(image=fixture))
|
||||
|
||||
res = req.get_response(self.api)
|
||||
|
||||
self.assertEquals(res.status_int, 200)
|
||||
|
||||
res_dict = json.loads(res.body)
|
||||
|
||||
self.assertEquals(0, res_dict['image']['min_ram'])
|
||||
|
||||
def test_create_image_with_min_disk_default(self):
|
||||
"""Tests that the /images POST registry API creates the image"""
|
||||
fixture = {'name': 'fake public image',
|
||||
'is_public': True,
|
||||
'disk_format': 'vhd',
|
||||
'container_format': 'ovf'}
|
||||
|
||||
req = webob.Request.blank('/images')
|
||||
|
||||
req.method = 'POST'
|
||||
req.content_type = 'application/json'
|
||||
req.body = json.dumps(dict(image=fixture))
|
||||
|
||||
res = req.get_response(self.api)
|
||||
|
||||
self.assertEquals(res.status_int, 200)
|
||||
|
||||
res_dict = json.loads(res.body)
|
||||
|
||||
self.assertEquals(0, res_dict['image']['min_disk'])
|
||||
|
||||
def test_create_image_with_bad_container_format(self):
|
||||
"""Tests proper exception is raised if a bad disk_format is set"""
|
||||
fixture = {'id': 3,
|
||||
@ -1385,6 +1557,8 @@ class TestRegistryAPI(unittest.TestCase):
|
||||
def test_update_image(self):
|
||||
"""Tests that the /images PUT registry API updates the image"""
|
||||
fixture = {'name': 'fake public image #2',
|
||||
'min_disk': 5,
|
||||
'min_ram': 256,
|
||||
'disk_format': 'raw'}
|
||||
|
||||
req = webob.Request.blank('/images/2')
|
||||
|
Loading…
Reference in New Issue
Block a user