From 12550cb9b9abbd3d7c3e078c5e1e55d7804a733e Mon Sep 17 00:00:00 2001 From: Justin Pomeroy Date: Fri, 29 May 2015 11:07:37 -0500 Subject: [PATCH] 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 --- doc/source/topics/settings.rst | 1 + .../dashboards/admin/images/views.py | 4 + .../dashboards/project/images/images/forms.py | 96 +++++++++---------- .../project/images/images/tables.py | 7 +- .../dashboards/project/images/images/tests.py | 25 +++++ .../dashboards/project/images/images/views.py | 9 +- .../local/local_settings.py.example | 1 + openstack_dashboard/settings.py | 1 + 8 files changed, 92 insertions(+), 52 deletions(-) diff --git a/doc/source/topics/settings.rst b/doc/source/topics/settings.rst index d612d2da23..4b28d3e6e8 100755 --- a/doc/source/topics/settings.rst +++ b/doc/source/topics/settings.rst @@ -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')), diff --git a/openstack_dashboard/dashboards/admin/images/views.py b/openstack_dashboard/dashboards/admin/images/views.py index 9f1d0c7244..79cc62767c 100644 --- a/openstack_dashboard/dashboards/admin/images/views.py +++ b/openstack_dashboard/dashboards/admin/images/views.py @@ -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 diff --git a/openstack_dashboard/dashboards/project/images/images/forms.py b/openstack_dashboard/dashboards/project/images/images/forms.py index 50c53cc822..4b20764a93 100644 --- a/openstack_dashboard/dashboards/project/images/images/forms.py +++ b/openstack_dashboard/dashboards/project/images/images/forms.py @@ -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 diff --git a/openstack_dashboard/dashboards/project/images/images/tables.py b/openstack_dashboard/dashboards/project/images/images/tables.py index 53742e6364..24ff3d0dd1 100644 --- a/openstack_dashboard/dashboards/project/images/images/tables.py +++ b/openstack_dashboard/dashboards/project/images/images/tables.py @@ -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() diff --git a/openstack_dashboard/dashboards/project/images/images/tests.py b/openstack_dashboard/dashboards/project/images/images/tests.py index ac5cae2886..4e86468164 100644 --- a/openstack_dashboard/dashboards/project/images/images/tests.py +++ b/openstack_dashboard/dashboards/project/images/images/tests.py @@ -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): diff --git a/openstack_dashboard/dashboards/project/images/images/views.py b/openstack_dashboard/dashboards/project/images/images/views.py index 33213c2101..67ea6fff0f 100644 --- a/openstack_dashboard/dashboards/project/images/images/views.py +++ b/openstack_dashboard/dashboards/project/images/images/views.py @@ -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): diff --git a/openstack_dashboard/local/local_settings.py.example b/openstack_dashboard/local/local_settings.py.example index d04935a94f..9ae6054fd4 100644 --- a/openstack_dashboard/local/local_settings.py.example +++ b/openstack_dashboard/local/local_settings.py.example @@ -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')), diff --git a/openstack_dashboard/settings.py b/openstack_dashboard/settings.py index 5a22e45122..b37ac938d7 100644 --- a/openstack_dashboard/settings.py +++ b/openstack_dashboard/settings.py @@ -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')),