Source code for openstack_dashboard.dashboards.project.images.images.forms

# Copyright 2012 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
# All Rights Reserved.
#
# Copyright 2012 Nebula, Inc.
#
#    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.

"""
Views for managing images.
"""

from django.conf import settings
from django.core import validators
from django.forms import ValidationError  # noqa
from django.forms.widgets import HiddenInput  # noqa
from django.template import defaultfilters
from django.utils.translation import ugettext_lazy as _
import six

from horizon import exceptions
from horizon import forms
from horizon import messages

from openstack_dashboard import api
from openstack_dashboard import policy


IMAGE_BACKEND_SETTINGS = getattr(settings, 'OPENSTACK_IMAGE_BACKEND', {})
IMAGE_FORMAT_CHOICES = IMAGE_BACKEND_SETTINGS.get('image_formats', [])


[docs]class ImageURLField(forms.URLField): default_validators = [validators.URLValidator(schemes=["http", "https"])]
[docs]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' meta = {'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']} is_public = data.get('is_public', data.get('public', False)) properties = {} # NOTE(tsufiev): in V2 the way how empty non-base attributes (AKA metadata) # are handled has changed: in V2 empty metadata is kept in image # properties, while in V1 they were omitted. Skip empty description (which # is metadata) to keep the same behavior between V1 and V2 if data.get('description'): properties['description'] = data['description'] if data.get('kernel'): properties['kernel_id'] = data['kernel'] if data.get('ramdisk'): properties['ramdisk_id'] = data['ramdisk'] if data.get('architecture'): properties['architecture'] = data['architecture'] if api.glance.VERSIONS.active < 2: meta.update({'is_public': is_public, 'properties': properties}) else: meta['visibility'] = 'public' if is_public else 'private' meta.update(properties) return meta
if api.glance.get_image_upload_mode() == 'direct': FileField = forms.ExternalFileField CreateParent = six.with_metaclass(forms.ExternalUploadMeta, forms.SelfHandlingForm) else: FileField = forms.FileField CreateParent = forms.SelfHandlingForm
[docs]class CreateImageForm(CreateParent): name = forms.CharField(max_length=255, label=_("Name")) description = forms.CharField( max_length=255, widget=forms.Textarea(attrs={'rows': 4}), label=_("Description"), required=False) source_type = forms.ChoiceField( label=_('Image Source'), required=False, choices=[('url', _('Image Location')), ('file', _('Image File'))], widget=forms.Select(attrs={ 'class': 'switchable', 'data-slug': 'source'})) image_url_attrs = { 'class': 'switched', 'data-switch-on': 'source', 'data-source-url': _('Image Location'), 'ng-model': 'ctrl.copyFrom', 'ng-change': 'ctrl.selectImageFormat(ctrl.copyFrom)' } image_url = ImageURLField(label=_("Image Location"), help_text=_("An external (HTTP/HTTPS) URL to " "load the image from."), widget=forms.TextInput(attrs=image_url_attrs), required=False) image_attrs = { 'class': 'switched', 'data-switch-on': 'source', 'data-source-file': _('Image File'), 'ng-model': 'ctrl.imageFile', 'ng-change': 'ctrl.selectImageFormat(ctrl.imageFile.name)', 'image-file-on-change': None } image_file = FileField(label=_("Image File"), help_text=_("A local image to upload."), widget=forms.FileInput(attrs=image_attrs), required=False) kernel = forms.ChoiceField( label=_('Kernel'), required=False, widget=forms.ThemableSelectWidget( transform=lambda x: "%s (%s)" % ( x.name, defaultfilters.filesizeformat(x.size)))) ramdisk = forms.ChoiceField( label=_('Ramdisk'), required=False, widget=forms.ThemableSelectWidget( transform=lambda x: "%s (%s)" % ( x.name, defaultfilters.filesizeformat(x.size)))) disk_format = forms.ChoiceField(label=_('Format'), choices=[], widget=forms.ThemableSelectWidget(attrs={ 'class': 'switchable', 'ng-model': 'ctrl.diskFormat'})) architecture = forms.CharField( max_length=255, label=_("Architecture"), help_text=_('CPU architecture of the image.'), required=False) minimum_disk = forms.IntegerField( label=_("Minimum Disk (GB)"), min_value=0, help_text=_('The minimum disk size required to boot the image. ' 'If unspecified, this value defaults to 0 (no minimum).'), required=False) minimum_ram = forms.IntegerField( label=_("Minimum RAM (MB)"), min_value=0, help_text=_('The minimum memory size required to boot the image. ' 'If unspecified, this value defaults to 0 (no minimum).'), required=False) is_copying = forms.BooleanField( label=_("Copy Data"), initial=True, required=False, help_text=_('Specify this option to copy image data to the image ' 'service. If unspecified, image data will be used in its ' 'current location.'), widget=forms.CheckboxInput(attrs={ 'class': 'switched', 'data-source-url': _('Image Location'), 'data-switch-on': 'source'})) is_public = forms.BooleanField( label=_("Public"), help_text=_('Make the image visible across projects.'), required=False) protected = forms.BooleanField( label=_("Protected"), help_text=_('Prevent the deletion of the image.'), required=False) def __init__(self, request, *args, **kwargs): super(CreateImageForm, self).__init__(request, *args, **kwargs) if (api.glance.get_image_upload_mode() == 'off' or not policy.check((("image", "upload_image"),), request)): self._hide_file_source_type() if not policy.check((("image", "set_image_location"),), request): self._hide_url_source_type() # GlanceV2 feature removals if api.glance.VERSIONS.active >= 2: # NOTE: GlanceV2 doesn't support copy-from feature, sorry! self._hide_is_copying() if not getattr(settings, 'IMAGES_ALLOW_LOCATION', False): self._hide_url_source_type() if (api.glance.get_image_upload_mode() == 'off' or not policy.check((("image", "upload_image"),), request)): # Neither setting a location nor uploading image data is # allowed, so throw an error. msg = _('The current Horizon settings indicate no valid ' 'image creation methods are available. Providing ' 'an image location and/or uploading from the ' 'local file system must be allowed to support ' 'image creation.') messages.error(request, msg) raise ValidationError(msg) if not policy.check((("image", "publicize_image"),), request): self._hide_is_public() self.fields['disk_format'].choices = IMAGE_FORMAT_CHOICES try: kernel_images = api.glance.image_list_detailed( request, filters={'disk_format': 'aki'})[0] except Exception: kernel_images = [] msg = _('Unable to retrieve image list.') messages.error(request, msg) if kernel_images: choices = [('', _("Choose an image"))] for image in kernel_images: choices.append((image.id, image)) self.fields['kernel'].choices = choices else: del self.fields['kernel'] try: ramdisk_images = api.glance.image_list_detailed( request, filters={'disk_format': 'ari'})[0] except Exception: ramdisk_images = [] msg = _('Unable to retrieve image list.') messages.error(request, msg) if ramdisk_images: choices = [('', _("Choose an image"))] for image in ramdisk_images: choices.append((image.id, image)) self.fields['ramdisk'].choices = choices else: del self.fields['ramdisk'] def _hide_file_source_type(self): self.fields['image_file'].widget = HiddenInput() source_type = self.fields['source_type'] source_type.choices = [choice for choice in source_type.choices if choice[0] != 'file'] if len(source_type.choices) == 1: source_type.widget = HiddenInput() def _hide_url_source_type(self): self.fields['image_url'].widget = HiddenInput() source_type = self.fields['source_type'] source_type.choices = [choice for choice in source_type.choices if choice[0] != 'url'] if len(source_type.choices) == 1: source_type.widget = HiddenInput() def _hide_is_public(self): self.fields['is_public'].widget = HiddenInput() self.fields['is_public'].initial = False def _hide_is_copying(self): self.fields['is_copying'].widget = HiddenInput() self.fields['is_copying'].initial = False
[docs] def clean(self): data = super(CreateImageForm, self).clean() # The image_file key can be missing based on particular upload # conditions. Code defensively for it here... source_type = data.get('source_type', None) image_file = data.get('image_file', None) image_url = data.get('image_url', None) if not image_url and not image_file: msg = _("An image file or an external location must be specified.") if source_type == 'file': raise ValidationError({'image_file': [msg, ]}) else: raise ValidationError({'image_url': [msg, ]}) else: return data
[docs] def handle(self, request, data): meta = create_image_metadata(data) # Add image source file or URL to metadata if (api.glance.get_image_upload_mode() != 'off' and policy.check((("image", "upload_image"),), request) and data.get('image_file', None)): meta['data'] = data['image_file'] elif data.get('is_copying'): meta['copy_from'] = data['image_url'] else: meta['location'] = data['image_url'] try: image = api.glance.image_create(request, **meta) messages.info(request, _('Your image %s has been queued for creation.') % meta['name']) return image except Exception as e: msg = _('Unable to create new image') # TODO(nikunj2512): Fix this once it is fixed in glance client 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.') % meta['disk_format'] elif "Image name too long" in e.details: msg = _('Unable to create new image: Image name too long.') elif "not supported" in e.details: msg = _('Unable to create new image: URL scheme not ' 'supported.') exceptions.handle(request, msg) return False
[docs]class UpdateImageForm(forms.SelfHandlingForm): image_id = forms.CharField(widget=forms.HiddenInput()) name = forms.CharField(max_length=255, label=_("Name")) description = forms.CharField( max_length=255, widget=forms.Textarea(attrs={'rows': 4}), label=_("Description"), required=False) kernel = forms.CharField( max_length=36, label=_("Kernel ID"), required=False, widget=forms.TextInput(attrs={'readonly': 'readonly'}), ) ramdisk = forms.CharField( max_length=36, label=_("Ramdisk ID"), required=False, widget=forms.TextInput(attrs={'readonly': 'readonly'}), ) architecture = forms.CharField( label=_("Architecture"), required=False, widget=forms.TextInput(attrs={'readonly': 'readonly'}), ) disk_format = forms.ThemableChoiceField( label=_("Format"), ) minimum_disk = forms.IntegerField(label=_("Minimum Disk (GB)"), min_value=0, help_text=_('The minimum disk size' ' required to boot the' ' image. If unspecified,' ' this value defaults to' ' 0 (no minimum).'), required=False) minimum_ram = forms.IntegerField(label=_("Minimum RAM (MB)"), min_value=0, help_text=_('The minimum memory size' ' required to boot the' ' image. If unspecified,' ' this value defaults to' ' 0 (no minimum).'), required=False) public = forms.BooleanField(label=_("Public"), required=False) protected = forms.BooleanField(label=_("Protected"), required=False) def __init__(self, request, *args, **kwargs): super(UpdateImageForm, self).__init__(request, *args, **kwargs) self.fields['disk_format'].choices = [(value, name) for value, name in IMAGE_FORMAT_CHOICES if value] if not policy.check((("image", "publicize_image"),), request): self.fields['public'].widget = forms.CheckboxInput( attrs={'readonly': 'readonly', 'disabled': 'disabled'}) self.fields['public'].help_text = _( 'Non admin users are not allowed to make images public.')
[docs] def handle(self, request, data): image_id = data['image_id'] error_updating = _('Unable to update image "%s".') meta = create_image_metadata(data) try: image = api.glance.image_update(request, image_id, **meta) messages.success(request, _('Image was successfully updated.')) return image except Exception: exceptions.handle(request, error_updating % image_id)

Project Source