Add support for Docker image format

The 'Docker' format is added to the default list of supported
formats so it can be selected from the list of formats when creating
or updating an image. When the docker format is selected, the
disk_format and container_format properties are set appropriately.
This also includes changes to the Format column and filter for the
Images table to handle this type.

Closes-Bug: #1455179
Change-Id: Ie705c86d47c0b6035373b9311454748122185988
This commit is contained in:
Justin Pomeroy 2015-05-29 11:07:37 -05:00
parent f9e4b8bb17
commit 12550cb9b9
8 changed files with 92 additions and 52 deletions

View File

@ -566,6 +566,7 @@ Default::
('aki', _('AKI - Amazon Kernel Image')),
('ami', _('AMI - Amazon Machine Image')),
('ari', _('ARI - Amazon Ramdisk Image')),
('docker', _('Docker')),
('iso', _('ISO - Optical Disk Image')),
('qcow2', _('QCOW2 - QEMU Emulator')),
('raw', _('Raw')),

View File

@ -102,6 +102,10 @@ class IndexView(tables.DataTableView):
LOG.warning(invalid_msg)
except ValueError:
LOG.warning(invalid_msg)
elif (filter_field == 'disk_format' and
filter_string.lower() == 'docker'):
filters['disk_format'] = 'raw'
filters['container_format'] = 'docker'
else:
filters[filter_field] = filter_string
return filters

View File

@ -37,6 +37,49 @@ IMAGE_BACKEND_SETTINGS = getattr(settings, 'OPENSTACK_IMAGE_BACKEND', {})
IMAGE_FORMAT_CHOICES = IMAGE_BACKEND_SETTINGS.get('image_formats', [])
def create_image_metadata(data):
"""Use the given dict of image form data to generate the metadata used for
creating the image in glance.
"""
# Glance does not really do anything with container_format at the
# moment. It requires it is set to the same disk_format for the three
# Amazon image types, otherwise it just treats them as 'bare.' As such
# we will just set that to be that here instead of bothering the user
# with asking them for information we can already determine.
disk_format = data['disk_format']
if disk_format in ('ami', 'aki', 'ari',):
container_format = disk_format
elif disk_format == 'docker':
# To support docker containers we allow the user to specify
# 'docker' as the format. In that case we really want to use
# 'raw' as the disk format and 'docker' as the container format.
disk_format = 'raw'
container_format = 'docker'
else:
container_format = 'bare'
# The Create form uses 'is_public' but the Update form uses 'public'. Just
# being tolerant here so we don't break anything else.
meta = {'is_public': data.get('is_public', data.get('public', False)),
'protected': data['protected'],
'disk_format': disk_format,
'container_format': container_format,
'min_disk': (data['minimum_disk'] or 0),
'min_ram': (data['minimum_ram'] or 0),
'name': data['name'],
'properties': {}}
if data['description']:
meta['properties']['description'] = data['description']
if data.get('kernel'):
meta['properties']['kernel_id'] = data['kernel']
if data.get('ramdisk'):
meta['properties']['ramdisk_id'] = data['ramdisk']
if data.get('architecture'):
meta['properties']['architecture'] = data['architecture']
return meta
class CreateImageForm(forms.SelfHandlingForm):
name = forms.CharField(max_length=255, label=_("Name"))
description = forms.CharField(max_length=255, label=_("Description"),
@ -200,33 +243,9 @@ class CreateImageForm(forms.SelfHandlingForm):
return data
def handle(self, request, data):
# Glance does not really do anything with container_format at the
# moment. It requires it is set to the same disk_format for the three
# Amazon image types, otherwise it just treats them as 'bare.' As such
# we will just set that to be that here instead of bothering the user
# with asking them for information we can already determine.
if data['disk_format'] in ('ami', 'aki', 'ari',):
container_format = data['disk_format']
else:
container_format = 'bare'
meta = create_image_metadata(data)
meta = {'is_public': data['is_public'],
'protected': data['protected'],
'disk_format': data['disk_format'],
'container_format': container_format,
'min_disk': (data['minimum_disk'] or 0),
'min_ram': (data['minimum_ram'] or 0),
'name': data['name'],
'properties': {}}
if data['description']:
meta['properties']['description'] = data['description']
if data.get('kernel'):
meta['properties']['kernel_id'] = data['kernel']
if data.get('ramdisk'):
meta['properties']['ramdisk_id'] = data['ramdisk']
if data.get('architecture'):
meta['properties']['architecture'] = data['architecture']
# Add image source file or URL to metadata
if (settings.HORIZON_IMAGES_ALLOW_UPLOAD and
policy.check((("image", "upload_image"),), request) and
data.get('image_file', None)):
@ -240,7 +259,7 @@ class CreateImageForm(forms.SelfHandlingForm):
image = api.glance.image_create(request, **meta)
messages.success(request,
_('Your image %s has been queued for creation.') %
data['name'])
meta['name'])
return image
except Exception as e:
msg = _('Unable to create new image')
@ -248,7 +267,7 @@ class CreateImageForm(forms.SelfHandlingForm):
if hasattr(e, 'code') and e.code == 400:
if "Invalid disk format" in e.details:
msg = _('Unable to create new image: Invalid disk format '
'%s for image.') % data['disk_format']
'%s for image.') % meta['disk_format']
elif "Image name too long" in e.details:
msg = _('Unable to create new image: Image name too long.')
@ -313,26 +332,7 @@ class UpdateImageForm(forms.SelfHandlingForm):
def handle(self, request, data):
image_id = data['image_id']
error_updating = _('Unable to update image "%s".')
if data['disk_format'] in ['aki', 'ari', 'ami']:
container_format = data['disk_format']
else:
container_format = 'bare'
meta = {'is_public': data['public'],
'protected': data['protected'],
'disk_format': data['disk_format'],
'container_format': container_format,
'name': data['name'],
'min_ram': (data['minimum_ram'] or 0),
'min_disk': (data['minimum_disk'] or 0),
'properties': {'description': data['description']}}
if data.get('kernel'):
meta['properties']['kernel_id'] = data['kernel']
if data.get('ramdisk'):
meta['properties']['ramdisk_id'] = data['ramdisk']
if data.get('architecture'):
meta['properties']['architecture'] = data['architecture']
meta = create_image_metadata(data)
# Ensure we do not delete properties that have already been
# set on an image.
meta['purge_props'] = False

View File

@ -222,9 +222,12 @@ def get_format(image):
# which will raise an error if you call upper() on it.
if not format:
return format
# Most image formats are untranslated acronyms, but raw is a word
# and should be translated
if format == "raw":
if getattr(image, "container_format") == 'docker':
return pgettext_lazy("Image format for display in table",
u"Docker")
# Most image formats are untranslated acronyms, but raw is a word
# and should be translated
return pgettext_lazy("Image format for display in table", u"Raw")
return format.upper()

View File

@ -81,6 +81,31 @@ class CreateImageFormTests(test.TestCase):
source_type_dict = dict(form.fields['source_type'].choices)
self.assertNotIn('file', source_type_dict)
def test_create_image_metadata_docker(self):
form_data = {
'name': u'Docker image',
'description': u'Docker image test',
'source_type': u'url',
'image_url': u'/',
'disk_format': u'docker',
'architecture': u'x86-64',
'minimum_disk': 15,
'minimum_ram': 512,
'is_public': False,
'protected': False,
'is_copying': False
}
meta = forms.create_image_metadata(form_data)
self.assertEqual(meta['disk_format'], 'raw')
self.assertEqual(meta['container_format'], 'docker')
self.assertIn('properties', meta)
self.assertNotIn('description', meta)
self.assertNotIn('architecture', meta)
self.assertEqual(meta['properties']['description'],
form_data['description'])
self.assertEqual(meta['properties']['architecture'],
form_data['architecture'])
class UpdateImageFormTests(test.TestCase):
def test_is_format_field_editable(self):

View File

@ -79,17 +79,22 @@ class UpdateView(forms.ModalFormView):
def get_initial(self):
image = self.get_object()
properties = getattr(image, 'properties', {})
return {'image_id': self.kwargs['image_id'],
data = {'image_id': self.kwargs['image_id'],
'name': getattr(image, 'name', None) or image.id,
'description': properties.get('description', ''),
'kernel': properties.get('kernel_id', ''),
'ramdisk': properties.get('ramdisk_id', ''),
'architecture': properties.get('architecture', ''),
'disk_format': getattr(image, 'disk_format', None),
'minimum_ram': getattr(image, 'min_ram', None),
'minimum_disk': getattr(image, 'min_disk', None),
'public': getattr(image, 'is_public', None),
'protected': getattr(image, 'protected', None)}
disk_format = getattr(image, 'disk_format', None)
if (disk_format == 'raw' and
getattr(image, 'container_format') == 'docker'):
disk_format = 'docker'
data['disk_format'] = disk_format
return data
class DetailView(tabs.TabView):

View File

@ -265,6 +265,7 @@ OPENSTACK_NEUTRON_NETWORK = {
# ('aki', _('AKI - Amazon Kernel Image')),
# ('ami', _('AMI - Amazon Machine Image')),
# ('ari', _('ARI - Amazon Ramdisk Image')),
# ('docker', _('Docker')),
# ('iso', _('ISO - Optical Disk Image')),
# ('ova', _('OVA - Open Virtual Appliance')),
# ('qcow2', _('QCOW2 - QEMU Emulator')),

View File

@ -81,6 +81,7 @@ OPENSTACK_IMAGE_BACKEND = {
('aki', _('AKI - Amazon Kernel Image')),
('ami', _('AMI - Amazon Machine Image')),
('ari', _('ARI - Amazon Ramdisk Image')),
('docker', _('Docker')),
('iso', _('ISO - Optical Disk Image')),
('ova', _('OVA - Open Virtual Appliance')),
('qcow2', _('QCOW2 - QEMU Emulator')),