Make disk and container formats configurable

* Add disk_formats config attribute
* Add container_formats config attribute
* Implement bp configurable-formats

Change-Id: Ic52ffb46df9438c247ba063748cadd69b9c90bcd
This commit is contained in:
Brian Waldon 2013-08-19 04:23:07 +00:00
parent 9cd75d0b4a
commit 830f27ba34
7 changed files with 255 additions and 121 deletions

View File

@ -17,10 +17,11 @@
Disk and Container Formats
==========================
When adding an image to Glance, you are may specify what the virtual
machine image's *disk format* and *container format* are.
This document explains exactly what these formats are.
When adding an image to Glance, you must specify what the virtual
machine image's *disk format* and *container format* are. Disk and container
formats are configurable on a per-deployment basis. This document intends to
establish a global convention for what specific values of *disk_format* and
*container_format* mean.
Disk Format
-----------

View File

@ -97,6 +97,13 @@ workers = 1
# The default value is false.
#send_identity_headers = False
# Supported values for the 'container_format' image attribute
#container_formats=ami,ari,aki,bare,ovf
# Supported values for the 'disk_format' image attribute
#disk_formats=ami,ari,aki,vhd,vmdk,raw,qcow2,vdi,iso
# ================= Syslog Options ============================
# Send logs to syslog (/dev/log) instead of to file specified

View File

@ -53,13 +53,13 @@ from glance.store import (get_from_backend,
get_store_from_location,
get_store_from_scheme)
CONF = cfg.CONF
LOG = logging.getLogger(__name__)
SUPPORTED_PARAMS = glance.api.v1.SUPPORTED_PARAMS
SUPPORTED_FILTERS = glance.api.v1.SUPPORTED_FILTERS
CONTAINER_FORMATS = ['ami', 'ari', 'aki', 'bare', 'ovf']
DISK_FORMATS = ['ami', 'ari', 'aki', 'vhd', 'vmdk', 'raw', 'qcow2', 'vdi',
'iso']
CONF = cfg.CONF
CONF.import_opt('disk_formats', 'glance.domain')
CONF.import_opt('container_formats', 'glance.domain')
def validate_image_meta(req, values):
@ -69,12 +69,12 @@ def validate_image_meta(req, values):
container_format = values.get('container_format')
if 'disk_format' in values:
if disk_format not in DISK_FORMATS:
if disk_format not in CONF.disk_formats:
msg = "Invalid disk format '%s' for image." % disk_format
raise HTTPBadRequest(explanation=msg, request=req)
if 'container_format' in values:
if container_format not in CONTAINER_FORMATS:
if container_format not in CONF.container_formats:
msg = "Invalid container format '%s' for image." % container_format
raise HTTPBadRequest(explanation=msg, request=req)

View File

@ -37,6 +37,8 @@ import glance.store
LOG = logging.getLogger(__name__)
CONF = cfg.CONF
CONF.import_opt('disk_formats', 'glance.domain')
CONF.import_opt('container_formats', 'glance.domain')
class ImagesController(object):
@ -384,8 +386,8 @@ class RequestDeserializer(wsgi.JSONRequestDeserializer):
partial_image = None
if len(change['path']) == 1:
partial_image = {path_root: change['value']}
elif ((path_root in _BASE_PROPERTIES.keys()) and
(_BASE_PROPERTIES[path_root].get('type', '') == 'array')):
elif ((path_root in _get_base_properties().keys()) and
(_get_base_properties()[path_root].get('type', '') == 'array')):
# NOTE(zhiyan): cient can use PATCH API to adding element to
# the image's existing set property directly.
# Such as: 1. using '/locations/N' path to adding a location
@ -591,123 +593,125 @@ class ResponseSerializer(wsgi.JSONResponseSerializer):
response.status_int = 204
_BASE_PROPERTIES = {
'id': {
'type': 'string',
'description': _('An identifier for the image'),
'pattern': ('^([0-9a-fA-F]){8}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}'
'-([0-9a-fA-F]){4}-([0-9a-fA-F]){12}$'),
},
'name': {
'type': 'string',
'description': _('Descriptive name for the image'),
'maxLength': 255,
},
'status': {
'type': 'string',
'description': _('Status of the image'),
'enum': ['queued', 'saving', 'active', 'killed',
'deleted', 'pending_delete'],
},
'visibility': {
'type': 'string',
'description': _('Scope of image accessibility'),
'enum': ['public', 'private'],
},
'protected': {
'type': 'boolean',
'description': _('If true, image will not be deletable.'),
},
'checksum': {
'type': 'string',
'description': _('md5 hash of image contents.'),
'type': 'string',
'maxLength': 32,
},
'size': {
'type': 'integer',
'description': _('Size of image file in bytes'),
},
'container_format': {
'type': 'string',
'description': _('Format of the container'),
'type': 'string',
'enum': ['bare', 'ovf', 'ami', 'aki', 'ari'],
},
'disk_format': {
'type': 'string',
'description': _('Format of the disk'),
'type': 'string',
'enum': ['raw', 'vhd', 'vmdk', 'vdi', 'iso', 'qcow2',
'aki', 'ari', 'ami'],
},
'created_at': {
'type': 'string',
'description': _('Date and time of image registration'),
#TODO(bcwaldon): our jsonschema library doesn't seem to like the
# format attribute, figure out why!
#'format': 'date-time',
},
'updated_at': {
'type': 'string',
'description': _('Date and time of the last image modification'),
#'format': 'date-time',
},
'tags': {
'type': 'array',
'description': _('List of strings related to the image'),
'items': {
def _get_base_properties():
return {
'id': {
'type': 'string',
'description': _('An identifier for the image'),
'pattern': ('^([0-9a-fA-F]){8}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}'
'-([0-9a-fA-F]){4}-([0-9a-fA-F]){12}$'),
},
'name': {
'type': 'string',
'description': _('Descriptive name for the image'),
'maxLength': 255,
},
},
'direct_url': {
'type': 'string',
'description': _('URL to access the image file kept in external '
'store'),
},
'min_ram': {
'type': 'integer',
'description': _('Amount of ram (in MB) required to boot image.'),
},
'min_disk': {
'type': 'integer',
'description': _('Amount of disk space (in GB) required to boot '
'image.'),
},
'self': {'type': 'string'},
'file': {'type': 'string'},
'schema': {'type': 'string'},
'locations': {
'type': 'array',
'items': {
'type': 'object',
'properties': {
'url': {
'type': 'string',
'maxLength': 255,
},
'metadata': {
'type': 'object',
},
},
'required': ['url', 'metadata'],
'status': {
'type': 'string',
'description': _('Status of the image'),
'enum': ['queued', 'saving', 'active', 'killed',
'deleted', 'pending_delete'],
},
'description': _('A set of URLs to access the image file kept in '
'external store'),
},
}
'visibility': {
'type': 'string',
'description': _('Scope of image accessibility'),
'enum': ['public', 'private'],
},
'protected': {
'type': 'boolean',
'description': _('If true, image will not be deletable.'),
},
'checksum': {
'type': 'string',
'description': _('md5 hash of image contents.'),
'type': 'string',
'maxLength': 32,
},
'size': {
'type': 'integer',
'description': _('Size of image file in bytes'),
},
'container_format': {
'type': 'string',
'description': _('Format of the container'),
'type': 'string',
'enum': CONF.container_formats,
},
'disk_format': {
'type': 'string',
'description': _('Format of the disk'),
'type': 'string',
'enum': CONF.disk_formats,
},
'created_at': {
'type': 'string',
'description': _('Date and time of image registration'),
#TODO(bcwaldon): our jsonschema library doesn't seem to like the
# format attribute, figure out why!
#'format': 'date-time',
},
'updated_at': {
'type': 'string',
'description': _('Date and time of the last image modification'),
#'format': 'date-time',
},
'tags': {
'type': 'array',
'description': _('List of strings related to the image'),
'items': {
'type': 'string',
'maxLength': 255,
},
},
'direct_url': {
'type': 'string',
'description': _('URL to access the image file kept in external '
'store'),
},
'min_ram': {
'type': 'integer',
'description': _('Amount of ram (in MB) required to boot image.'),
},
'min_disk': {
'type': 'integer',
'description': _('Amount of disk space (in GB) required to boot '
'image.'),
},
'self': {'type': 'string'},
'file': {'type': 'string'},
'schema': {'type': 'string'},
'locations': {
'type': 'array',
'items': {
'type': 'object',
'properties': {
'url': {
'type': 'string',
'maxLength': 255,
},
'metadata': {
'type': 'object',
},
},
'required': ['url', 'metadata'],
},
'description': _('A set of URLs to access the image file kept in '
'external store'),
},
}
_BASE_LINKS = [
{'rel': 'self', 'href': '{self}'},
{'rel': 'enclosure', 'href': '{file}'},
{'rel': 'describedby', 'href': '{schema}'},
]
def _get_base_links():
return [
{'rel': 'self', 'href': '{self}'},
{'rel': 'enclosure', 'href': '{file}'},
{'rel': 'describedby', 'href': '{schema}'},
]
def get_schema(custom_properties=None):
properties = copy.deepcopy(_BASE_PROPERTIES)
links = copy.deepcopy(_BASE_LINKS)
properties = _get_base_properties()
links = _get_base_links()
if CONF.allow_additional_image_properties:
schema = glance.schema.PermissiveSchema('image', properties, links)
else:

View File

@ -13,11 +13,30 @@
# License for the specific language governing permissions and limitations
# under the License.
from oslo.config import cfg
from glance.common import exception
from glance.openstack.common import timeutils
from glance.openstack.common import uuidutils
image_format_opts = [
cfg.ListOpt('container_formats',
default=['ami', 'ari', 'aki', 'bare', 'ovf'],
help=_("Supported values for the 'container_format' "
"image attribute")),
cfg.ListOpt('disk_formats',
default=['ami', 'ari', 'aki', 'vhd', 'vmdk', 'raw', 'qcow2',
'vdi', 'iso'],
help=_("Supported values for the 'disk_format' "
"image attribute")),
]
CONF = cfg.CONF
CONF.register_opts(image_format_opts)
class ImageFactory(object):
_readonly_properties = ['created_at', 'updated_at', 'status', 'checksum',
'size']

View File

@ -150,6 +150,80 @@ class TestGlanceAPI(base.IsolatedUnitTest):
self.assertEquals(res.status_int, 400)
self.assertTrue('Invalid disk format' in res.body, res.body)
def test_configured_disk_format_good(self):
self.config(disk_formats=['foo'])
fixture_headers = {
'x-image-meta-store': 'bad',
'x-image-meta-name': 'bogus',
'x-image-meta-location': 'http://localhost:0/image.tar.gz',
'x-image-meta-disk-format': 'foo',
'x-image-meta-container-format': 'bare',
}
req = webob.Request.blank("/images")
req.method = 'POST'
for k, v in fixture_headers.iteritems():
req.headers[k] = v
res = req.get_response(self.api)
self.assertEquals(res.status_int, 201)
def test_configured_disk_format_bad(self):
self.config(disk_formats=['foo'])
fixture_headers = {
'x-image-meta-store': 'bad',
'x-image-meta-name': 'bogus',
'x-image-meta-location': 'http://localhost:0/image.tar.gz',
'x-image-meta-disk-format': 'bar',
'x-image-meta-container-format': 'bare',
}
req = webob.Request.blank("/images")
req.method = 'POST'
for k, v in fixture_headers.iteritems():
req.headers[k] = v
res = req.get_response(self.api)
self.assertEquals(res.status_int, 400)
self.assertTrue('Invalid disk format' in res.body, res.body)
def test_configured_container_format_good(self):
self.config(container_formats=['foo'])
fixture_headers = {
'x-image-meta-store': 'bad',
'x-image-meta-name': 'bogus',
'x-image-meta-location': 'http://localhost:0/image.tar.gz',
'x-image-meta-disk-format': 'raw',
'x-image-meta-container-format': 'foo',
}
req = webob.Request.blank("/images")
req.method = 'POST'
for k, v in fixture_headers.iteritems():
req.headers[k] = v
res = req.get_response(self.api)
self.assertEquals(res.status_int, 201)
def test_configured_container_format_bad(self):
self.config(container_formats=['foo'])
fixture_headers = {
'x-image-meta-store': 'bad',
'x-image-meta-name': 'bogus',
'x-image-meta-location': 'http://localhost:0/image.tar.gz',
'x-image-meta-disk-format': 'raw',
'x-image-meta-container-format': 'bar',
}
req = webob.Request.blank("/images")
req.method = 'POST'
for k, v in fixture_headers.iteritems():
req.headers[k] = v
res = req.get_response(self.api)
self.assertEquals(res.status_int, 400)
self.assertTrue('Invalid container format' in res.body, res.body)
def test_container_and_disk_amazon_format_differs(self):
fixture_headers = {
'x-image-meta-store': 'bad',

View File

@ -2245,3 +2245,32 @@ class TestImagesSerializerDirectUrl(test_utils.BaseTestCase):
self.config(show_image_direct_url=False)
image = self._do_show(self.active_image)
self.assertFalse('direct_url' in image)
class TestImageSchemaFormatConfiguration(test_utils.BaseTestCase):
def test_default_disk_formats(self):
schema = glance.api.v2.images.get_schema()
expected = ['ami', 'ari', 'aki', 'vhd', 'vmdk', 'raw', 'qcow2',
'vdi', 'iso']
actual = schema.properties['disk_format']['enum']
self.assertEqual(expected, actual)
def test_custom_disk_formats(self):
self.config(disk_formats=['gabe'])
schema = glance.api.v2.images.get_schema()
expected = ['gabe']
actual = schema.properties['disk_format']['enum']
self.assertEqual(expected, actual)
def test_default_container_formats(self):
schema = glance.api.v2.images.get_schema()
expected = ['ami', 'ari', 'aki', 'bare', 'ovf']
actual = schema.properties['container_format']['enum']
self.assertEqual(expected, actual)
def test_custom_container_formats(self):
self.config(container_formats=['mark'])
schema = glance.api.v2.images.get_schema()
expected = ['mark']
actual = schema.properties['container_format']['enum']
self.assertEqual(expected, actual)