diff options
author | Jenkins <jenkins@review.openstack.org> | 2012-06-06 19:51:40 +0000 |
---|---|---|
committer | Gerrit Code Review <review@openstack.org> | 2012-06-06 19:51:40 +0000 |
commit | f6802a9058e74d7a3f4249c9ac5314705ae2a6f6 (patch) | |
tree | 4251b0500a524d25fff67ac1d408a4c364646565 /horizon/dashboards/nova | |
parent | ab71a133d9d37359805f9dd841ecee776358ebf8 (diff) | |
parent | ca795fe604ebf33c7845f81c6019ae7302498059 (diff) | |
download | tuskar-ui-f6802a9058e74d7a3f4249c9ac5314705ae2a6f6.tar.gz |
Merge "Glance remote image creation."
Diffstat (limited to 'horizon/dashboards/nova')
7 files changed, 198 insertions, 22 deletions
diff --git a/horizon/dashboards/nova/images_and_snapshots/images/forms.py b/horizon/dashboards/nova/images_and_snapshots/images/forms.py index d47e7f4d..afe361ac 100644 --- a/horizon/dashboards/nova/images_and_snapshots/images/forms.py +++ b/horizon/dashboards/nova/images_and_snapshots/images/forms.py @@ -36,6 +36,69 @@ from horizon import forms LOG = logging.getLogger(__name__) +class CreateImageForm(forms.SelfHandlingForm): + completion_view = 'horizon:nova:images_and_snapshots:index' + + name = forms.CharField(max_length="255", label=_("Name"), required=True) + copy_from = forms.CharField(max_length="255", + label=_("Image Location"), + help_text=_("An external (HTTP) URL where" + " the image should be loaded from."), + required=True) + disk_format = forms.ChoiceField(label=_('Format'), + required=True, + choices=[('', ''), + ('aki', + 'Amazon Kernel Image (AKI)'), + ('ami', + 'Amazon Machine Image (AMI)'), + ('ari', + 'Amazon Ramdisk Image (ARI)'), + ('iso', + 'Optical Disk Image (ISO)'), + ('qcow2', + 'QEMU Emulator (QCOW2)'), + ('raw', 'Raw'), + ('vdi', 'VDI'), + ('vhd', 'VHD'), + ('vmdk', 'VMDK')], + widget=forms.Select(attrs={'class': + 'switchable'})) + minimum_disk = forms.IntegerField(label=_("Minimum Disk (GB)"), + 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)"), + help_text=_('The minimum disk size' + ' required to boot the' + ' image. If unspecified, this' + ' value defaults to 0 (no' + ' minimum).'), + required=False) + is_public = forms.BooleanField(label=_("Public"), required=False) + + def handle(self, request, data): + meta = {'is_public': data['is_public'], + 'disk_format': data['disk_format'], + 'container_format': 'bare', # Not used in Glance ATM. + 'copy_from': data['copy_from'], + 'min_disk': (data['minimum_disk'] or 0), + 'min_ram': (data['minimum_ram'] or 0), + 'name': data['name']} + + try: + api.glance.image_create(request, **meta) + messages.success(request, + _('Your image %s has been queued for creation.' % + data['name'])) + except: + exceptions.handle(request, _('Unable to create new image.')) + return shortcuts.redirect(self.get_success_url()) + + class UpdateImageForm(forms.SelfHandlingForm): completion_view = 'horizon:nova:images_and_snapshots:index' @@ -55,16 +118,11 @@ class UpdateImageForm(forms.SelfHandlingForm): widget=forms.TextInput( attrs={'readonly': 'readonly'} )) - container_format = forms.CharField(label=_("Container Format"), - widget=forms.TextInput( - attrs={'readonly': 'readonly'} - )) - disk_format = forms.CharField(label=_("Disk Format"), + disk_format = forms.CharField(label=_("Format"), widget=forms.TextInput( attrs={'readonly': 'readonly'} )) - public = forms.BooleanField(label=_("Public"), - required=False) + public = forms.BooleanField(label=_("Public"), required=False) def handle(self, request, data): # TODO add public flag to image meta properties @@ -73,7 +131,7 @@ class UpdateImageForm(forms.SelfHandlingForm): meta = {'is_public': data['public'], 'disk_format': data['disk_format'], - 'container_format': data['container_format'], + 'container_format': 'bare', 'name': data['name'], 'properties': {}} if data['kernel']: diff --git a/horizon/dashboards/nova/images_and_snapshots/images/tables.py b/horizon/dashboards/nova/images_and_snapshots/images/tables.py index 546cd488..573edfad 100644 --- a/horizon/dashboards/nova/images_and_snapshots/images/tables.py +++ b/horizon/dashboards/nova/images_and_snapshots/images/tables.py @@ -55,6 +55,13 @@ class DeleteImage(tables.DeleteAction): api.image_delete(request, obj_id) +class CreateImage(tables.LinkAction): + name = "create" + verbose_name = _("Create Image") + url = "horizon:nova:images_and_snapshots:images:create" + classes = ("ajax-modal", "btn-create") + + class EditImage(tables.LinkAction): name = "edit" verbose_name = _("Edit") @@ -73,33 +80,53 @@ def get_image_type(image): return getattr(image.properties, "image_type", "Image") -def get_container_format(image): - container_format = getattr(image, "container_format", "") +def get_format(image): + format = getattr(image, "disk_format", "") # The "container_format" attribute can actually be set to None, # which will raise an error if you call upper() on it. - if container_format is not None: - return container_format.upper() + if format is not None: + return format.upper() + + +class UpdateRow(tables.Row): + ajax = True + + def get_data(self, request, image_id): + image = api.image_get(request, image_id) + return image class ImagesTable(tables.DataTable): + STATUS_CHOICES = ( + ("active", True), + ("saving", None), + ("queued", None), + ("pending_delete", None), + ("killed", False), + ("deleted", False), + ) name = tables.Column("name", link="horizon:nova:images_and_snapshots:" \ "images:detail", verbose_name=_("Image Name")) image_type = tables.Column(get_image_type, verbose_name=_("Type"), filters=(filters.title,)) - status = tables.Column("status", filters=(filters.title,), - verbose_name=_("Status")) + status = tables.Column("status", + filters=(filters.title,), + verbose_name=_("Status"), + status=True, + status_choices=STATUS_CHOICES) public = tables.Column("is_public", verbose_name=_("Public"), empty_value=False, filters=(filters.yesno, filters.capfirst)) - container_format = tables.Column(get_container_format, - verbose_name=_("Container Format")) + disk_format = tables.Column(get_format, verbose_name=_("Format")) class Meta: name = "images" + row_class = UpdateRow + status_columns = ["status"] verbose_name = _("Images") - table_actions = (DeleteImage,) - row_actions = (LaunchImage, EditImage, DeleteImage) + table_actions = (CreateImage, DeleteImage,) + row_actions = (LaunchImage, EditImage, DeleteImage,) pagination_param = "image_marker" diff --git a/horizon/dashboards/nova/images_and_snapshots/images/tests.py b/horizon/dashboards/nova/images_and_snapshots/images/tests.py index c9aa1056..b93a7da1 100644 --- a/horizon/dashboards/nova/images_and_snapshots/images/tests.py +++ b/horizon/dashboards/nova/images_and_snapshots/images/tests.py @@ -24,16 +24,54 @@ from django.core.urlresolvers import reverse from horizon import api from horizon import test -from mox import IsA +from mox import IgnoreArg, IsA IMAGES_INDEX_URL = reverse('horizon:nova:images_and_snapshots:index') class ImageViewTests(test.TestCase): + def test_image_create_get(self): + url = reverse('horizon:nova:images_and_snapshots:images:create') + res = self.client.get(url) + self.assertTemplateUsed(res, + 'nova/images_and_snapshots/images/create.html') + + @test.create_stubs({api.glance: ('image_create',)}) + def test_image_create_post(self): + data = { + 'name': u'Ubuntu 11.10', + 'copy_from': u'http://cloud-images.ubuntu.com/releases/' + u'oneiric/release/ubuntu-11.10-server-cloudimg' + u'-amd64-disk1.img', + 'disk_format': u'qcow2', + 'minimum_disk': 15, + 'minimum_ram': 512, + 'is_public': 1, + 'method': 'CreateImageForm' + } + + api.glance.image_create(IsA(http.HttpRequest), + container_format="bare", + copy_from=data['copy_from'], + disk_format=data['disk_format'], + is_public=True, + min_disk=data['minimum_disk'], + min_ram=data['minimum_ram'], + name=data['name']). \ + AndReturn(self.images.first()) + self.mox.ReplayAll() + + url = reverse('horizon:nova:images_and_snapshots:images:create') + res = self.client.post(url, data) + + self.assertNoFormErrors(res) + self.assertEqual(res.status_code, 302) + + @test.create_stubs({api.glance: ('image_get',)}) def test_image_detail_get(self): image = self.images.first() - self.mox.StubOutWithMock(api.glance, 'image_get') + api.glance.image_get(IsA(http.HttpRequest), str(image.id)) \ .AndReturn(self.images.first()) self.mox.ReplayAll() @@ -45,9 +83,10 @@ class ImageViewTests(test.TestCase): 'nova/images_and_snapshots/images/detail.html') self.assertEqual(res.context['image'].name, image.name) + @test.create_stubs({api.glance: ('image_get',)}) def test_image_detail_get_with_exception(self): image = self.images.first() - self.mox.StubOutWithMock(api.glance, 'image_get') + api.glance.image_get(IsA(http.HttpRequest), str(image.id)) \ .AndRaise(self.exceptions.glance) self.mox.ReplayAll() diff --git a/horizon/dashboards/nova/images_and_snapshots/images/urls.py b/horizon/dashboards/nova/images_and_snapshots/images/urls.py index dc26e1e0..a4e7ad18 100644 --- a/horizon/dashboards/nova/images_and_snapshots/images/urls.py +++ b/horizon/dashboards/nova/images_and_snapshots/images/urls.py @@ -20,12 +20,13 @@ from django.conf.urls.defaults import patterns, url -from .views import UpdateView, DetailView +from .views import UpdateView, DetailView, CreateView VIEWS_MOD = 'horizon.dashboards.nova.images_and_snapshots.images.views' urlpatterns = patterns(VIEWS_MOD, + url(r'^create/$', CreateView.as_view(), name='create'), url(r'^(?P<image_id>[^/]+)/update/$', UpdateView.as_view(), name='update'), url(r'^(?P<image_id>[^/]+)/$', DetailView.as_view(), name='detail'), ) diff --git a/horizon/dashboards/nova/images_and_snapshots/images/views.py b/horizon/dashboards/nova/images_and_snapshots/images/views.py index 1e4deee5..947bd8a7 100644 --- a/horizon/dashboards/nova/images_and_snapshots/images/views.py +++ b/horizon/dashboards/nova/images_and_snapshots/images/views.py @@ -32,12 +32,19 @@ from horizon import exceptions from horizon import forms from horizon import tabs from .forms import UpdateImageForm +from .forms import CreateImageForm from .tabs import ImageDetailTabs LOG = logging.getLogger(__name__) +class CreateView(forms.ModalFormView): + form_class = CreateImageForm + template_name = 'nova/images_and_snapshots/images/create.html' + context_object_name = 'image' + + class UpdateView(forms.ModalFormView): form_class = UpdateImageForm template_name = 'nova/images_and_snapshots/images/update.html' diff --git a/horizon/dashboards/nova/templates/nova/images_and_snapshots/images/_create.html b/horizon/dashboards/nova/templates/nova/images_and_snapshots/images/_create.html new file mode 100644 index 00000000..0df41f8e --- /dev/null +++ b/horizon/dashboards/nova/templates/nova/images_and_snapshots/images/_create.html @@ -0,0 +1,33 @@ +{% extends "horizon/common/_modal_form.html" %} +{% load i18n %} + +{% block form_id %}create_image_form{% endblock %} +{% block form_action %}{% url horizon:nova:images_and_snapshots:images:create %}{% endblock %} + +{% block modal-header %}{% trans "Create An Image" %}{% endblock %} + +{% block modal-body %} +<div class="left"> + <fieldset> + {% include "horizon/common/_form_fields.html" %} + </fieldset> +</div> +<div class="right"> + <h3>{% trans "Description:" %}</h3> + <p> + {% trans "Specify an image to upload to the Image Service." %} + </p> + <p> + {% trans "Currently only images available via an HTTP URL are supported. The image location must be accessible to the Image Service. Compressed image binaries are supported (.zip and .tar.gz.)" %} + </p> + <p> + <strong>{% trans "Please note: " %}</strong> + {% trans "The Image Location field MUST be a valid and direct URL to the image binary. URLs that redirect or serve error pages will results in unusable images." %} + </p> +</div> +{% endblock %} + +{% block modal-footer %} + <input class="btn btn-primary pull-right" type="submit" value="{% trans "Create Image" %}" /> + <a href="{% url horizon:nova:images_and_snapshots:index %}" class="btn secondary cancel close">{% trans "Cancel" %}</a> +{% endblock %} diff --git a/horizon/dashboards/nova/templates/nova/images_and_snapshots/images/create.html b/horizon/dashboards/nova/templates/nova/images_and_snapshots/images/create.html new file mode 100644 index 00000000..b9fa856f --- /dev/null +++ b/horizon/dashboards/nova/templates/nova/images_and_snapshots/images/create.html @@ -0,0 +1,11 @@ +{% extends 'nova/base.html' %} +{% load i18n %} +{% block title %}{% trans "Create An Image" %}{% endblock %} + +{% block page_header %} + {% include "horizon/common/_page_header.html" with title=_("Create An Image") %} +{% endblock page_header %} + +{% block dash_main %} + {% include 'nova/images_and_snapshots/images/_create.html' %} +{% endblock %} |