Merge "Added volume type description for volume type"
This commit is contained in:
commit
41c6c7b735
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")
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -52,6 +52,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