Added volume type description for volume type
Added the following features for admin volumes types 1. "Create Volume Type" dialog should include a description field. 2. The volume types table should include description column. 3. The volume types table name and description columns are in-line editable. 4. The 'Edit Volume Type' action is added for the volume type. User should be able to update volume type name and description. Added the following features in project volumes 1. "Create Volume" dialog will have description for the selected volume type when volume type select is available. 2. "No Volume type" will have some description as well. Implements: blueprint volume-type-description Change-Id: I7c8548756bcd3566873876bbc59f9b9c21d6846b
This commit is contained in:
parent
5796aa97a7
commit
84074ce020
75
horizon/static/horizon/js/horizon.volumes.js
Normal file
75
horizon/static/horizon/js/horizon.volumes.js
Normal file
@ -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);
|
||||
}
|
||||
};
|
@ -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):
|
||||
|
@ -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 %}
|
||||
<div class="left">
|
||||
<fieldset>
|
||||
{% include "horizon/common/_form_fields.html" %}
|
||||
</fieldset>
|
||||
</div>
|
||||
<div class="right">
|
||||
<h3>{% trans "Description:" %}</h3>
|
||||
<p>{% trans "Modify volume type name and description." %}</p>
|
||||
</div>
|
||||
{% endblock %}
|
@ -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 %}
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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(
|
||||
|
@ -27,6 +27,9 @@ urlpatterns = patterns(
|
||||
'VIEWS_MOD',
|
||||
url(r'^create_type$', views.CreateVolumeTypeView.as_view(),
|
||||
name='create_type'),
|
||||
url(r'^(?P<type_id>[^/]+)/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<type_id>[^/]+)/manage_qos_spec_association/$',
|
||||
|
@ -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")
|
||||
|
@ -152,6 +152,13 @@ class UnmanageVolume(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']
|
||||
@ -163,8 +170,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
|
||||
|
@ -2,7 +2,16 @@
|
||||
|
||||
<h3>{% trans "Description:" %}</h3>
|
||||
|
||||
<p>{% block title %}{% trans "Volumes are block devices that can be attached to instances." %}{% endblock %}</p>
|
||||
<p>{% blocktrans %}
|
||||
Volumes are block devices that can be attached to instances.
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
|
||||
<div id="id_show_volume_type_desc_div">
|
||||
<h3>{% trans "Volume Type Description:" %}</h3>
|
||||
<h4><b><span id="id_show_volume_type_name"></span></b></h4>
|
||||
<p id="id_show_volume_type_desc"></p>
|
||||
</div>
|
||||
|
||||
<h3>{% block head %}{% trans "Volume Limits" %}{% endblock %}</h3>
|
||||
|
||||
@ -22,7 +31,6 @@
|
||||
<div id={% block type_id %}"quota_volumes"{% endblock %} data-progress-indicator-step-by="1" data-quota-limit={% block total_progress %}"{{ usages.maxTotalVolumes }}"{% endblock %} data-quota-used={% block used_progress %}"{{ usages.volumesUsed }}"{% endblock %} class="quota_bar">
|
||||
</div>
|
||||
|
||||
|
||||
<script type="text/javascript" charset="utf-8">
|
||||
if(typeof horizon.Quota !== 'undefined') {
|
||||
horizon.Quota.init();
|
||||
@ -31,4 +39,12 @@
|
||||
horizon.Quota.init();
|
||||
});
|
||||
}
|
||||
|
||||
if(typeof horizon.Volumes !== 'undefined'){
|
||||
horizon.Volumes.initWithTypes({{ volume_types|safe|default:"{}" }});
|
||||
} else {
|
||||
addHorizonLoadEvent(function() {
|
||||
horizon.Volumes.initWithTypes({{ volume_types|safe|default:"{}" }});
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
@ -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'],
|
||||
|
@ -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)).\
|
||||
|
@ -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
|
||||
|
@ -53,6 +53,7 @@
|
||||
<script src='{{ STATIC_URL }}horizon/js/horizon.d3linechart.js'></script>
|
||||
<script src='{{ STATIC_URL }}horizon/js/horizon.d3barchart.js'></script>
|
||||
<script src='{{ STATIC_URL }}horizon/js/horizon.firewalls.js'></script>
|
||||
<script src='{{ STATIC_URL }}horizon/js/horizon.volumes.js'></script>
|
||||
<script src='{{ STATIC_URL }}horizon/lib/jsencrypt/jsencrypt.js'></script>
|
||||
|
||||
{% for file in HORIZON_CONFIG.js_files %}
|
||||
|
@ -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):
|
||||
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user