diff --git a/horizon/static/horizon/js/horizon.volumes.js b/horizon/static/horizon/js/horizon.volumes.js
new file mode 100644
index 0000000000..09e6ac1255
--- /dev/null
+++ b/horizon/static/horizon/js/horizon.volumes.js
@@ -0,0 +1,75 @@
+
+horizon.Volumes = {
+ selected_volume_type: null,
+ volume_types: [],
+
+ initWithTypes: function(volume_types) {
+ this.volume_types = volume_types;
+
+ this._attachInputHandlers();
+
+ this.getSelectedType();
+ this.showTypeDescription();
+ },
+
+ /*
+ *Returns the type object for the selected type in the form.
+ */
+ getSelectedType: function() {
+
+ this.selected_volume_type = $.grep(this.volume_types, function(type) {
+ var selected_name = $("#id_type").children(":selected").val();
+ return type.name === selected_name;
+ })[0];
+
+ return this.selected_volume_type;
+ },
+
+ showTypeDescription: function() {
+ this.getSelectedType();
+
+ if (this.selected_volume_type) {
+ var description = this.selected_volume_type.description;
+ var name = this.selected_volume_type.name;
+ if (name === 'no_type') {
+ $("#id_show_volume_type_name").html("");
+ } else {
+ $("#id_show_volume_type_name").html(name);
+ }
+ if (description) {
+ $("#id_show_volume_type_desc").html(description);
+ } else {
+ $("#id_show_volume_type_desc").html(
+ gettext('No description available.'));
+ }
+ }
+ },
+
+ toggleTypeDescription: function() {
+ var selected_volume_source =
+ $("#id_volume_source_type").children(":selected").val();
+ if(selected_volume_source === 'volume_source' ||
+ selected_volume_source === 'snapshot_source') {
+ $("#id_show_volume_type_desc_div").hide();
+ }
+ else {
+ $("#id_show_volume_type_desc_div").show();
+ }
+ },
+
+ _attachInputHandlers: function() {
+ var scope = this;
+
+ var eventCallback_type = function() {
+ scope.showTypeDescription();
+ };
+
+ $('#id_type').on('change', eventCallback_type);
+
+ var eventCallback_volume_source_type = function() {
+ scope.toggleTypeDescription();
+ };
+
+ $('#id_volume_source_type').on('change', eventCallback_volume_source_type);
+ }
+};
diff --git a/openstack_dashboard/api/cinder.py b/openstack_dashboard/api/cinder.py
index f4506e1b52..78fbf5c0e0 100644
--- a/openstack_dashboard/api/cinder.py
+++ b/openstack_dashboard/api/cinder.py
@@ -448,6 +448,24 @@ def volume_type_list_with_qos_associations(request):
return vol_types
+def volume_type_get_with_qos_association(request, volume_type_id):
+ vol_type = volume_type_get(request, volume_type_id)
+ vol_type.associated_qos_spec = ""
+
+ # get all currently defined qos specs
+ qos_specs = qos_spec_list(request)
+ for qos_spec in qos_specs:
+ # get all volume types this qos spec is associated with
+ assoc_vol_types = qos_spec_get_associations(request, qos_spec.id)
+ for assoc_vol_type in assoc_vol_types:
+ if vol_type.id == assoc_vol_type.id:
+ # update volume type to hold this association info
+ vol_type.associated_qos_spec = qos_spec.name
+ return vol_type
+
+ return vol_type
+
+
def default_quota_update(request, **kwargs):
cinderclient(request).quota_classes.update(DEFAULT_QUOTA_NAME, **kwargs)
@@ -456,8 +474,18 @@ def volume_type_list(request):
return cinderclient(request).volume_types.list()
-def volume_type_create(request, name):
- return cinderclient(request).volume_types.create(name)
+def volume_type_create(request, name, description=None):
+ return cinderclient(request).volume_types.create(name, description)
+
+
+def volume_type_update(request, volume_type_id, name=None, description=None):
+ return cinderclient(request).volume_types.update(volume_type_id,
+ name,
+ description)
+
+
+def volume_type_default(request):
+ return cinderclient(request).volume_types.default()
def volume_type_delete(request, volume_type_id):
diff --git a/openstack_dashboard/dashboards/admin/volumes/templates/volumes/volume_types/_update_volume_type.html b/openstack_dashboard/dashboards/admin/volumes/templates/volumes/volume_types/_update_volume_type.html
new file mode 100644
index 0000000000..66e3428c43
--- /dev/null
+++ b/openstack_dashboard/dashboards/admin/volumes/templates/volumes/volume_types/_update_volume_type.html
@@ -0,0 +1,21 @@
+{% extends "horizon/common/_modal_form.html" %}
+{% load i18n %}
+{% load url from future %}
+
+{% block form_id %}{% endblock %}
+{% block form_action %}{% url 'horizon:admin:volumes:volume_types:update_type' volume_type.id %}{% endblock %}
+
+{% block modal_id %}update_volume_type_modal{% endblock %}
+{% block modal-header %}{% trans "Edit Volume Type" %}{% endblock %}
+
+{% block modal-body %}
+
+
+
+
+
{% trans "Description:" %}
+
{% trans "Modify volume type name and description." %}
+
+{% endblock %}
diff --git a/openstack_dashboard/dashboards/admin/volumes/templates/volumes/volume_types/update_volume_type.html b/openstack_dashboard/dashboards/admin/volumes/templates/volumes/volume_types/update_volume_type.html
new file mode 100644
index 0000000000..ee7e4156c1
--- /dev/null
+++ b/openstack_dashboard/dashboards/admin/volumes/templates/volumes/volume_types/update_volume_type.html
@@ -0,0 +1,11 @@
+{% extends 'base.html' %}
+{% load i18n %}
+{% block title %}{% trans "Edit Volume Type" %}{% endblock %}
+
+{% block page_header %}
+ {% include "horizon/common/_page_header.html" with title=_("Edit Volume Type") %}
+{% endblock page_header %}
+
+{% block main %}
+ {% include 'admin/volumes/volume_types/_update_volume_type.html' %}
+{% endblock %}
diff --git a/openstack_dashboard/dashboards/admin/volumes/volume_types/forms.py b/openstack_dashboard/dashboards/admin/volumes/volume_types/forms.py
index d5d8a9c966..d8668df32b 100644
--- a/openstack_dashboard/dashboards/admin/volumes/volume_types/forms.py
+++ b/openstack_dashboard/dashboards/admin/volumes/volume_types/forms.py
@@ -10,8 +10,8 @@
# License for the specific language governing permissions and limitations
# under the License.
-
from django.core.urlresolvers import reverse
+
from django.utils.translation import ugettext_lazy as _
from horizon import exceptions
@@ -166,3 +166,41 @@ class EditQosSpecConsumer(forms.SelfHandlingForm):
redirect = reverse("horizon:admin:volumes:index")
exceptions.handle(request, _('Error editing QoS Spec consumer.'),
redirect=redirect)
+
+
+class EditVolumeType(forms.SelfHandlingForm):
+ name = forms.CharField(max_length=255,
+ label=_("Name"))
+ description = forms.CharField(max_length=255,
+ widget=forms.Textarea(attrs={'rows': 4}),
+ label=_("Description"),
+ required=False)
+
+ def clean_name(self):
+ cleaned_name = self.cleaned_data['name']
+ if len(cleaned_name.strip()) == 0:
+ msg = _('New name cannot be empty.')
+ self._errors['name'] = self.error_class([msg])
+
+ return cleaned_name
+
+ def handle(self, request, data):
+ volume_type_id = self.initial['id']
+ try:
+ cinder.volume_type_update(request,
+ volume_type_id,
+ data['name'],
+ data['description'])
+ message = _('Successfully updated volume type.')
+ messages.success(request, message)
+ return True
+ except Exception as ex:
+ redirect = reverse("horizon:admin:volumes:index")
+ if ex.code == 409:
+ error_message = _('New name conflicts with another '
+ 'volume type.')
+ else:
+ error_message = _('Unable to update volume type.')
+
+ exceptions.handle(request, error_message,
+ redirect=redirect)
diff --git a/openstack_dashboard/dashboards/admin/volumes/volume_types/tables.py b/openstack_dashboard/dashboards/admin/volumes/volume_types/tables.py
index eca43a5bdb..12b2c83a5b 100644
--- a/openstack_dashboard/dashboards/admin/volumes/volume_types/tables.py
+++ b/openstack_dashboard/dashboards/admin/volumes/volume_types/tables.py
@@ -15,9 +15,11 @@ from django.utils.translation import ugettext_lazy as _
from django.utils.translation import ungettext_lazy
from horizon import exceptions
+from horizon import forms
from horizon import tables
from openstack_dashboard.api import cinder
+from openstack_dashboard import policy
class CreateVolumeType(tables.LinkAction):
@@ -29,11 +31,21 @@ class CreateVolumeType(tables.LinkAction):
policy_rules = (("volume", "volume_extension:types_manage"),)
+class EditVolumeType(tables.LinkAction):
+ name = "edit"
+ verbose_name = _("Edit Volume Type")
+ url = "horizon:admin:volumes:volume_types:update_type"
+ classes = ("ajax-modal",)
+ icon = "pencil"
+ policy_rules = (("volume", "volume_extension:types_manage"),)
+
+
class ViewVolumeTypeExtras(tables.LinkAction):
name = "extras"
verbose_name = _("View Extra Specs")
url = "horizon:admin:volumes:volume_types:extras:index"
- classes = ("btn-edit",)
+ classes = ("ajax-modal",)
+ icon = "pencil"
policy_rules = (("volume", "volume_extension:types_manage"),)
@@ -142,8 +154,64 @@ class VolumeTypesFilterAction(tables.FilterAction):
if query in volume_type.name.lower()]
+class UpdateRow(tables.Row):
+ ajax = True
+
+ def get_data(self, request, volume_type_id):
+ try:
+ volume_type = \
+ cinder.volume_type_get_with_qos_association(request,
+ volume_type_id)
+ except Exception:
+ exceptions.handle(request,
+ _('Unable to retrieve volume type qos.'))
+ return volume_type
+
+
+class UpdateCell(tables.UpdateAction):
+ def allowed(self, request, volume_type, cell):
+ return policy.check(
+ ("volume_extension", "volume_extension:types_manage"), request)
+
+ def update_cell(self, request, data, volume_type_id,
+ cell_name, new_cell_value):
+ # inline update volume type name and/or description
+ try:
+ vol_type_obj = data
+ # updating changed value by new value
+ setattr(vol_type_obj, cell_name, new_cell_value)
+ name_value = getattr(vol_type_obj, 'name', None)
+ desc_value = getattr(vol_type_obj, 'description', None)
+
+ cinder.volume_type_update(
+ request,
+ volume_type_id,
+ name=name_value,
+ description=desc_value)
+ except Exception as ex:
+ if ex.code and ex.code == 409:
+ error_message = _('New name conflicts with another '
+ 'volume type.')
+ else:
+ error_message = _('Unable to update the volume type.')
+ exceptions.handle(request, error_message)
+ return False
+
+ return True
+
+
class VolumeTypesTable(tables.DataTable):
- name = tables.Column("name", verbose_name=_("Name"))
+ name = tables.Column("name", verbose_name=_("Name"),
+ form_field=forms.CharField(
+ max_length=64, required=True),
+ update_action=UpdateCell)
+ description = tables.Column(lambda obj: getattr(obj, 'description', None),
+ verbose_name=_('Description'),
+ form_field=forms.CharField(
+ widget=forms.Textarea(attrs={'rows': 4}),
+ required=False),
+ update_action=UpdateCell)
+
assoc_qos_spec = tables.Column("associated_qos_spec",
verbose_name=_("Associated QoS Spec"))
encryption = tables.Column(get_volume_type_encryption,
@@ -166,8 +234,10 @@ class VolumeTypesTable(tables.DataTable):
row_actions = (CreateVolumeTypeEncryption,
ViewVolumeTypeExtras,
ManageQosSpecAssociation,
+ EditVolumeType,
DeleteVolumeTypeEncryption,
DeleteVolumeType,)
+ row_class = UpdateRow
# QOS Specs section of panel
diff --git a/openstack_dashboard/dashboards/admin/volumes/volume_types/tests.py b/openstack_dashboard/dashboards/admin/volumes/volume_types/tests.py
index df122acbdc..68ee75784d 100644
--- a/openstack_dashboard/dashboards/admin/volumes/volume_types/tests.py
+++ b/openstack_dashboard/dashboards/admin/volumes/volume_types/tests.py
@@ -23,18 +23,42 @@ from openstack_dashboard.test import helpers as test
class VolumeTypeTests(test.BaseAdminViewTests):
@test.create_stubs({cinder: ('volume_type_create',)})
def test_create_volume_type(self):
- formData = {'name': 'volume type 1'}
- cinder.volume_type_create(IsA(http.HttpRequest),
- formData['name']).\
- AndReturn(self.volume_types.first())
+ formData = {'name': 'volume type 1',
+ 'vol_type_description': 'test desc'}
+ cinder.volume_type_create(
+ IsA(http.HttpRequest),
+ formData['name'],
+ formData['vol_type_description']).AndReturn(
+ self.volume_types.first())
self.mox.ReplayAll()
res = self.client.post(
reverse('horizon:admin:volumes:volume_types:create_type'),
formData)
-
- redirect = reverse('horizon:admin:volumes:volume_types_tab')
self.assertNoFormErrors(res)
+ redirect = reverse('horizon:admin:volumes:volume_types_tab')
+ self.assertRedirectsNoFollow(res, redirect)
+
+ @test.create_stubs({cinder: ('volume_type_get',
+ 'volume_type_update')})
+ def test_update_volume_type(self):
+ volume_type = self.cinder_volume_types.first()
+ formData = {'name': volume_type.name,
+ 'description': 'test desc updated'}
+ volume_type = cinder.volume_type_get(
+ IsA(http.HttpRequest), volume_type.id).AndReturn(volume_type)
+ cinder.volume_type_update(
+ IsA(http.HttpRequest),
+ volume_type.id,
+ formData['name'],
+ formData['description']).AndReturn(volume_type)
+ self.mox.ReplayAll()
+
+ url = reverse('horizon:admin:volumes:volume_types:update_type',
+ args=[volume_type.id])
+ res = self.client.post(url, formData)
+ self.assertNoFormErrors(res)
+ redirect = reverse('horizon:admin:volumes:volume_types_tab')
self.assertRedirectsNoFollow(res, redirect)
@test.create_stubs({api.nova: ('server_list',),
@@ -45,7 +69,7 @@ class VolumeTypeTests(test.BaseAdminViewTests):
'volume_encryption_type_list'),
keystone: ('tenant_list',)})
def test_delete_volume_type(self):
- volume_type = self.volume_types.first()
+ volume_type = self.cinder_volume_types.first()
formData = {'action': 'volume_types__delete__%s' % volume_type.id}
encryption_list = (self.cinder_volume_encryption_types.list()[0],
self.cinder_volume_encryption_types.list()[1])
@@ -58,7 +82,7 @@ class VolumeTypeTests(test.BaseAdminViewTests):
cinder.volume_encryption_type_list(IsA(http.HttpRequest))\
.AndReturn(encryption_list)
cinder.volume_type_delete(IsA(http.HttpRequest),
- str(volume_type.id))
+ volume_type.id)
self.mox.ReplayAll()
res = self.client.post(
diff --git a/openstack_dashboard/dashboards/admin/volumes/volume_types/urls.py b/openstack_dashboard/dashboards/admin/volumes/volume_types/urls.py
index e717b00016..65dff3e30b 100644
--- a/openstack_dashboard/dashboards/admin/volumes/volume_types/urls.py
+++ b/openstack_dashboard/dashboards/admin/volumes/volume_types/urls.py
@@ -27,6 +27,9 @@ urlpatterns = patterns(
'VIEWS_MOD',
url(r'^create_type$', views.CreateVolumeTypeView.as_view(),
name='create_type'),
+ url(r'^(?P[^/]+)/update_type/$',
+ views.EditVolumeTypeView.as_view(),
+ name='update_type'),
url(r'^create_qos_spec$', views.CreateQosSpecView.as_view(),
name='create_qos_spec'),
url(r'^(?P[^/]+)/manage_qos_spec_association/$',
diff --git a/openstack_dashboard/dashboards/admin/volumes/volume_types/views.py b/openstack_dashboard/dashboards/admin/volumes/volume_types/views.py
index b760b3bad3..ca1d1fe275 100644
--- a/openstack_dashboard/dashboards/admin/volumes/volume_types/views.py
+++ b/openstack_dashboard/dashboards/admin/volumes/volume_types/views.py
@@ -116,6 +116,45 @@ class CreateVolumeTypeEncryptionView(forms.ModalFormView):
'volume_type_id': self.kwargs['volume_type_id']}
+class EditVolumeTypeView(forms.ModalFormView):
+ form_class = volume_types_forms.EditVolumeType
+ template_name = 'admin/volumes/volume_types/update_volume_type.html'
+ success_url = 'horizon:admin:volumes:volume_types_tab'
+ cancel_url = 'horizon:admin:volumes:volume_types_tab'
+ submit_label = _('Edit')
+
+ def get_success_url(self):
+ return reverse(self.success_url)
+
+ @memoized.memoized_method
+ def get_data(self):
+ try:
+ volume_type_id = self.kwargs['type_id']
+ volume_type = api.cinder.volume_type_get(self.request,
+ volume_type_id)
+ except Exception:
+ error_message = _(
+ 'Unable to retrieve volume type for: "%s"') \
+ % volume_type_id
+ exceptions.handle(self.request,
+ error_message,
+ redirect=self.success_url)
+
+ return volume_type
+
+ def get_context_data(self, **kwargs):
+ context = super(EditVolumeTypeView, self).get_context_data(**kwargs)
+ context['volume_type'] = self.get_data()
+
+ return context
+
+ def get_initial(self):
+ volume_type = self.get_data()
+ return {'id': self.kwargs['type_id'],
+ 'name': volume_type.name,
+ 'description': getattr(volume_type, 'description', "")}
+
+
class CreateQosSpecView(forms.ModalFormView):
form_class = volumes_forms.CreateQosSpec
modal_header = _("Create QoS Spec")
diff --git a/openstack_dashboard/dashboards/admin/volumes/volumes/forms.py b/openstack_dashboard/dashboards/admin/volumes/volumes/forms.py
index c785ed0088..d6158d52b9 100644
--- a/openstack_dashboard/dashboards/admin/volumes/volumes/forms.py
+++ b/openstack_dashboard/dashboards/admin/volumes/volumes/forms.py
@@ -201,6 +201,13 @@ class MigrateVolume(forms.SelfHandlingForm):
class CreateVolumeType(forms.SelfHandlingForm):
name = forms.CharField(max_length=255, label=_("Name"))
+ vol_type_description = forms.CharField(
+ max_length=255,
+ widget=forms.Textarea(
+ attrs={'class': 'modal-body-fixed-width',
+ 'rows': 4}),
+ label=_("Description"),
+ required=False)
def clean_name(self):
cleaned_name = self.cleaned_data['name']
@@ -212,8 +219,10 @@ class CreateVolumeType(forms.SelfHandlingForm):
def handle(self, request, data):
try:
# Remove any new lines in the public key
- volume_type = cinder.volume_type_create(request,
- data['name'])
+ volume_type = cinder.volume_type_create(
+ request,
+ data['name'],
+ data['vol_type_description'])
messages.success(request, _('Successfully created volume type: %s')
% data['name'])
return volume_type
diff --git a/openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/_limits.html b/openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/_limits.html
index 27ad123065..587888a2b0 100644
--- a/openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/_limits.html
+++ b/openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/_limits.html
@@ -2,7 +2,16 @@
{% trans "Description:" %}
-{% block title %}{% trans "Volumes are block devices that can be attached to instances." %}{% endblock %}
+{% blocktrans %}
+ Volumes are block devices that can be attached to instances.
+ {% endblocktrans %}
+
+
+
+
{% trans "Volume Type Description:" %}
+
+
+
{% block head %}{% trans "Volume Limits" %}{% endblock %}
@@ -22,7 +31,6 @@
-
diff --git a/openstack_dashboard/dashboards/project/volumes/volumes/forms.py b/openstack_dashboard/dashboards/project/volumes/volumes/forms.py
index 3c07cf84e7..7a39a5f41d 100644
--- a/openstack_dashboard/dashboards/project/volumes/volumes/forms.py
+++ b/openstack_dashboard/dashboards/project/volumes/volumes/forms.py
@@ -265,7 +265,7 @@ class CreateForm(forms.SelfHandlingForm):
def __init__(self, request, *args, **kwargs):
super(CreateForm, self).__init__(request, *args, **kwargs)
volume_types = cinder.volume_type_list(request)
- self.fields['type'].choices = [("", _("No volume type"))] + \
+ self.fields['type'].choices = [("no_type", _("No volume type"))] + \
[(type.name, type.name)
for type in volume_types]
@@ -379,6 +379,9 @@ class CreateForm(forms.SelfHandlingForm):
metadata = {}
+ if data['type'] == 'no_type':
+ data['type'] = ''
+
volume = cinder.volume_create(request,
data['size'],
data['name'],
diff --git a/openstack_dashboard/dashboards/project/volumes/volumes/tests.py b/openstack_dashboard/dashboards/project/volumes/volumes/tests.py
index ae8d0d82f1..7b63ebaf42 100644
--- a/openstack_dashboard/dashboards/project/volumes/volumes/tests.py
+++ b/openstack_dashboard/dashboards/project/volumes/volumes/tests.py
@@ -103,6 +103,7 @@ class VolumeViewTests(test.TestCase):
url = reverse('horizon:project:volumes:volumes:create')
res = self.client.post(url, formData)
+ self.assertNoFormErrors(res)
redirect_url = VOLUME_VOLUMES_TAB_URL
self.assertRedirectsNoFollow(res, redirect_url)
@@ -436,6 +437,7 @@ class VolumeViewTests(test.TestCase):
@test.create_stubs({cinder: ('volume_snapshot_get',
'volume_type_list',
+ 'volume_type_default',
'volume_get'),
api.glance: ('image_list_detailed',),
quotas: ('tenant_limit_usages',)})
@@ -452,6 +454,10 @@ class VolumeViewTests(test.TestCase):
cinder.volume_type_list(IsA(http.HttpRequest)).\
AndReturn(self.volume_types.list())
+ cinder.volume_type_list(IsA(http.HttpRequest)).\
+ AndReturn(self.volume_types.list())
+ cinder.volume_type_default(IsA(http.HttpRequest)).\
+ AndReturn(self.volume_types.first())
quotas.tenant_limit_usages(IsA(http.HttpRequest)).\
AndReturn(usage_limit)
cinder.volume_snapshot_get(IsA(http.HttpRequest),
@@ -600,6 +606,7 @@ class VolumeViewTests(test.TestCase):
self.assertRedirectsNoFollow(res, redirect_url)
@test.create_stubs({cinder: ('volume_type_list',
+ 'volume_type_default',
'availability_zone_list',
'extension_supported'),
api.glance: ('image_get',
@@ -618,6 +625,10 @@ class VolumeViewTests(test.TestCase):
cinder.volume_type_list(IsA(http.HttpRequest)).\
AndReturn(self.volume_types.list())
+ cinder.volume_type_list(IsA(http.HttpRequest)).\
+ AndReturn(self.volume_types.list())
+ cinder.volume_type_default(IsA(http.HttpRequest)).\
+ AndReturn(self.volume_types.first())
quotas.tenant_limit_usages(IsA(http.HttpRequest)).\
AndReturn(usage_limit)
api.glance.image_get(IsA(http.HttpRequest),
@@ -664,6 +675,8 @@ class VolumeViewTests(test.TestCase):
'method': u'CreateForm',
'size': 5, 'image_source': image.id}
+ cinder.volume_type_list(IsA(http.HttpRequest)).\
+ AndReturn(self.volume_types.list())
cinder.volume_type_list(IsA(http.HttpRequest)).\
AndReturn(self.volume_types.list())
quotas.tenant_limit_usages(IsA(http.HttpRequest)).\
@@ -701,6 +714,7 @@ class VolumeViewTests(test.TestCase):
@test.create_stubs({cinder: ('volume_snapshot_list',
'volume_type_list',
+ 'volume_type_default',
'volume_list',
'availability_zone_list',
'extension_supported'),
@@ -718,6 +732,10 @@ class VolumeViewTests(test.TestCase):
cinder.volume_type_list(IsA(http.HttpRequest)).\
AndReturn(self.volume_types.list())
+ cinder.volume_type_list(IsA(http.HttpRequest)).\
+ AndReturn(self.volume_types.list())
+ cinder.volume_type_default(IsA(http.HttpRequest)).\
+ AndReturn(self.volume_types.first())
quotas.tenant_limit_usages(IsA(http.HttpRequest)).\
AndReturn(usage_limit)
cinder.volume_snapshot_list(IsA(http.HttpRequest),
@@ -768,6 +786,8 @@ class VolumeViewTests(test.TestCase):
'method': u'CreateForm',
'size': 10}
+ cinder.volume_type_list(IsA(http.HttpRequest)).\
+ AndReturn(self.volume_types.list())
cinder.volume_type_list(IsA(http.HttpRequest)).\
AndReturn(self.volume_types.list())
quotas.tenant_limit_usages(IsA(http.HttpRequest)).\
diff --git a/openstack_dashboard/dashboards/project/volumes/volumes/views.py b/openstack_dashboard/dashboards/project/volumes/volumes/views.py
index 1a0947ac1c..257eb255f0 100644
--- a/openstack_dashboard/dashboards/project/volumes/volumes/views.py
+++ b/openstack_dashboard/dashboards/project/volumes/volumes/views.py
@@ -16,8 +16,11 @@
Views for managing volumes.
"""
+import json
+
from django.core.urlresolvers import reverse
from django.core.urlresolvers import reverse_lazy
+from django.utils import encoding
from django.utils.translation import ugettext_lazy as _
from django.views import generic
@@ -29,6 +32,7 @@ from horizon.utils import memoized
from openstack_dashboard import api
from openstack_dashboard.api import cinder
+from openstack_dashboard import exceptions as dashboard_exception
from openstack_dashboard.usage import quotas
from openstack_dashboard.dashboards.project.volumes \
@@ -97,10 +101,49 @@ class CreateView(forms.ModalFormView):
context = super(CreateView, self).get_context_data(**kwargs)
try:
context['usages'] = quotas.tenant_limit_usages(self.request)
+ context['volume_types'] = self._get_volume_types()
except Exception:
exceptions.handle(self.request)
return context
+ def _get_volume_types(self):
+ try:
+ volume_types = cinder.volume_type_list(self.request)
+ except Exception:
+ exceptions.handle(self.request,
+ _('Unable to retrieve volume type list.'))
+
+ # check if we have default volume type so we can present the
+ # description of no volume type differently
+ default_type = None
+ try:
+ default_type = cinder.volume_type_default(self.request)
+ except dashboard_exception.NOT_FOUND:
+ pass
+
+ if default_type is not None:
+ d_name = getattr(default_type, "name", "")
+ message =\
+ _("If \"No volume type\" is selected, the default "
+ "volume type \"%(name)s\" will be set for the "
+ "created volume.")
+ params = {'name': d_name}
+ no_type_description = encoding.force_text(message % params)
+ else:
+ message = \
+ _("If \"No volume type\" is selected, the volume will be "
+ "created without a volume type.")
+
+ no_type_description = encoding.force_text(message)
+
+ type_descriptions = [{'name': 'no_type',
+ 'description': no_type_description}] + \
+ [{'name': type.name,
+ 'description': getattr(type, "description", "")}
+ for type in volume_types]
+
+ return json.dumps(type_descriptions)
+
class ExtendView(forms.ModalFormView):
form_class = project_forms.ExtendForm
diff --git a/openstack_dashboard/templates/horizon/_scripts.html b/openstack_dashboard/templates/horizon/_scripts.html
index 7eab545702..cfed8534d8 100644
--- a/openstack_dashboard/templates/horizon/_scripts.html
+++ b/openstack_dashboard/templates/horizon/_scripts.html
@@ -52,6 +52,7 @@
+
{% for file in HORIZON_CONFIG.js_files %}
diff --git a/openstack_dashboard/test/api_tests/cinder_tests.py b/openstack_dashboard/test/api_tests/cinder_tests.py
index 35ce79ea4f..85202bcfe8 100644
--- a/openstack_dashboard/test/api_tests/cinder_tests.py
+++ b/openstack_dashboard/test/api_tests/cinder_tests.py
@@ -94,6 +94,28 @@ class CinderApiTests(test.APITestCase):
associate_spec = assoc_vol_types[0].associated_qos_spec
self.assertTrue(associate_spec, qos_specs_only_one[0].name)
+ def test_volume_type_get_with_qos_association(self):
+ volume_type = self.cinder_volume_types.first()
+ qos_specs_full = self.cinder_qos_specs.list()
+ qos_specs_only_one = [qos_specs_full[0]]
+ associations = self.cinder_qos_spec_associations.list()
+
+ cinderclient = self.stub_cinderclient()
+ cinderclient.volume_types = self.mox.CreateMockAnything()
+ cinderclient.volume_types.get(volume_type.id).AndReturn(volume_type)
+ cinderclient.qos_specs = self.mox.CreateMockAnything()
+ cinderclient.qos_specs.list().AndReturn(qos_specs_only_one)
+ cinderclient.qos_specs.get_associations = self.mox.CreateMockAnything()
+ cinderclient.qos_specs.get_associations(qos_specs_only_one[0].id).\
+ AndReturn(associations)
+ self.mox.ReplayAll()
+
+ assoc_vol_type = \
+ api.cinder.volume_type_get_with_qos_association(self.request,
+ volume_type.id)
+ associate_spec = assoc_vol_type.associated_qos_spec
+ self.assertTrue(associate_spec, qos_specs_only_one[0].name)
+
def test_absolute_limits_with_negative_values(self):
values = {"maxTotalVolumes": -1, "totalVolumesUsed": -1}
expected_results = {"maxTotalVolumes": float("inf"),
@@ -126,6 +148,16 @@ class CinderApiTests(test.APITestCase):
# No assertions are necessary. Verification is handled by mox.
api.cinder.pool_list(self.request, detailed=True)
+ def test_volume_type_default(self):
+ volume_type = self.cinder_volume_types.first()
+ cinderclient = self.stub_cinderclient()
+ cinderclient.volume_types = self.mox.CreateMockAnything()
+ cinderclient.volume_types.default().AndReturn(volume_type)
+ self.mox.ReplayAll()
+
+ default_volume_type = api.cinder.volume_type_default(self.request)
+ self.assertEqual(default_volume_type, volume_type)
+
class CinderApiVersionTests(test.TestCase):
diff --git a/openstack_dashboard/test/test_data/cinder_data.py b/openstack_dashboard/test/test_data/cinder_data.py
index 2f014c4ae9..360ba84b21 100644
--- a/openstack_dashboard/test/test_data/cinder_data.py
+++ b/openstack_dashboard/test/test_data/cinder_data.py
@@ -144,10 +144,12 @@ def data(TEST):
vol_type1 = volume_types.VolumeType(volume_types.VolumeTypeManager(None),
{'id': u'1',
'name': u'vol_type_1',
+ 'description': 'type 1 description',
'extra_specs': {'foo': 'bar'}})
vol_type2 = volume_types.VolumeType(volume_types.VolumeTypeManager(None),
{'id': u'2',
- 'name': u'vol_type_2'})
+ 'name': u'vol_type_2',
+ 'description': 'type 2 description'})
TEST.cinder_volume_types.add(vol_type1, vol_type2)
# Volumes - Cinder v2