Add volume type encryption update

This patch adds support to horizon for volume type encryption
update.

The modifications are made to the Admin Volume Type table, and
add the Update Encryption action to the action column.

Implements: blueprint integration-with-cinder-volume-encryption
Change-Id: I7b6f1db60818a07feb64eaa464e64397ea477a7b
This commit is contained in:
Brianna Poulos 2014-02-07 18:20:32 -05:00
parent 1178757445
commit 4e61f626e4
9 changed files with 191 additions and 17 deletions

View File

@ -521,6 +521,11 @@ def volume_encryption_type_list(request):
return cinderclient(request).volume_encryption_types.list()
def volume_encryption_type_update(request, volume_type_id, data):
return cinderclient(request).volume_encryption_types.update(volume_type_id,
specs=data)
def volume_type_extra_get(request, type_id, raw=False):
vol_type = volume_type_get(request, type_id)
extras = vol_type.get_keys()

View File

@ -0,0 +1,21 @@
{% extends "horizon/common/_modal_form.html" %}
{% load i18n %}
{% block modal-body-right %}
<h3>{% trans "Description:" %}</h3>
<p>
{% trans "Encryption information cannot be updated for a volume type if volumes are currently in use with the volume type." %}
</p>
<p>
{% blocktrans %}The <strong>Provider</strong> is the class providing encryption support (e.g., LuksEncryptor).{% endblocktrans %}
</p>
<p>
{% blocktrans %}The <strong>Control Location</strong> is the notional service where encryption is performed (e.g., front-end=Nova). The default value is 'front-end.'{% endblocktrans %}
</p>
<p>
{% blocktrans %}The <strong>Cipher</strong> is the encryption algorithm/mode to use (e.g., aes-xts-plain64). If the field is left empty, the provider default will be used.{% endblocktrans %}
</p>
<p>
{% blocktrans %}The <strong>Key Size</strong> is the size of the encryption key, in bits (e.g., 128, 256). If the field is left empty, the provider default will be used.{% endblocktrans %}
</p>
{% endblock %}

View File

@ -0,0 +1,7 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Update Encrypted Volume Type" %}{% endblock %}
{% block main %}
{% include 'admin/volumes/volume_types/_update_volume_type_encryption.html' %}
{% endblock %}

View File

@ -104,7 +104,7 @@ class CreateVolumeTypeEncryption(forms.SelfHandlingForm):
def handle(self, request, data):
try:
# Set Cipher to None if empty
if data['cipher'] is u'':
if data['cipher'] == u'':
data['cipher'] = None
# Create encryption for the volume type
@ -122,6 +122,34 @@ class CreateVolumeTypeEncryption(forms.SelfHandlingForm):
redirect=redirect)
class UpdateVolumeTypeEncryption(CreateVolumeTypeEncryption):
def handle(self, request, data):
try:
# Set Cipher to None if empty
if data['cipher'] == u'':
data['cipher'] = None
# Update encryption for the volume type
volume_type = cinder.\
volume_encryption_type_update(request,
data['volume_type_id'],
data)
messages.success(request, _('Successfully updated encryption for '
'volume type: %s') % data['name'])
return volume_type
except NotImplementedError:
messages.error(request, _('Updating encryption is not '
'implemented. Unable to update '
' encrypted volume type.'))
except Exception:
redirect = reverse("horizon:admin:volumes:index")
exceptions.handle(request,
_('Unable to update encrypted volume type.'),
redirect=redirect)
return False
class ManageQosSpecAssociation(forms.SelfHandlingForm):
qos_spec_choice = forms.ChoiceField(
label=_("QoS Spec to be associated"),

View File

@ -89,11 +89,21 @@ class CreateVolumeTypeEncryption(tables.LinkAction):
policy_rules = (("volume", "volume_extension:volume_type_encryption"),)
def allowed(self, request, volume_type):
if _is_vol_type_enc_possible(request):
return (hasattr(volume_type, 'encryption')
and not hasattr(volume_type.encryption, 'provider'))
else:
return False
return (_is_vol_type_enc_possible(request) and
not _does_vol_type_enc_exist(volume_type))
class UpdateVolumeTypeEncryption(tables.LinkAction):
name = "update_encryption"
verbose_name = _("Update Encryption")
url = "horizon:admin:volumes:volume_types:update_type_encryption"
classes = ("ajax-modal",)
icon = "pencil"
policy_rules = (("volume", "volume_extension:volume_type_encryption"),)
def allowed(self, request, volume_type=None):
return (_is_vol_type_enc_possible(request) and
_does_vol_type_enc_exist(volume_type))
class DeleteVolumeTypeEncryption(tables.DeleteAction):
@ -122,8 +132,14 @@ class DeleteVolumeTypeEncryption(tables.DeleteAction):
def allowed(self, request, volume_type=None):
return (_is_vol_type_enc_possible(request) and
hasattr(volume_type, 'encryption') and
hasattr(volume_type.encryption, 'provider'))
_does_vol_type_enc_exist(volume_type))
def _does_vol_type_enc_exist(volume_type):
# Check to see if there is an existing encryption information
# for the volume type or not
return (hasattr(volume_type, 'encryption') and
hasattr(volume_type.encryption, 'provider'))
def _is_vol_type_enc_possible(request):
@ -235,6 +251,7 @@ class VolumeTypesTable(tables.DataTable):
ViewVolumeTypeExtras,
ManageQosSpecAssociation,
EditVolumeType,
UpdateVolumeTypeEncryption,
DeleteVolumeTypeEncryption,
DeleteVolumeType,)
row_class = UpdateRow

View File

@ -226,3 +226,39 @@ class VolumeTypeTests(test.BaseAdminViewTests):
redirect = reverse('horizon:admin:volumes:volume_types_tab')
self.assertNoFormErrors(res)
self.assertRedirectsNoFollow(res, redirect)
@test.create_stubs({cinder: ('volume_encryption_type_update',
'volume_encryption_type_get',
'volume_type_list')})
def test_update_volume_type_encryption(self):
volume_type = self.volume_types.first()
volume_type.id = u'1'
volume_type_list = [volume_type]
formData = {'name': u'An Encrypted Volume Type',
'provider': u'a-provider',
'control_location': u'front-end',
'cipher': u'a-cipher',
'key_size': 256,
'volume_type_id': volume_type.id}
vol_enc_type = self.cinder_volume_encryption_types.list()[0]
cinder.volume_encryption_type_get(IsA(http.HttpRequest),
volume_type.id)\
.AndReturn(vol_enc_type)
cinder.volume_type_list(IsA(http.HttpRequest))\
.AndReturn(volume_type_list)
cinder.volume_encryption_type_update(IsA(http.HttpRequest),
formData['volume_type_id'],
formData)
self.mox.ReplayAll()
url = reverse('horizon:admin:volumes:'
'volume_types:update_type_encryption',
args=[volume_type.id])
res = self.client.post(url, formData)
self.assertNoFormErrors(res)
self.assertTemplateUsed(
res,
'admin/volumes/volume_types/update_volume_type_encryption.html')

View File

@ -43,6 +43,9 @@ urlpatterns = patterns(
url(r'^(?P<volume_type_id>[^/]+)/create_type_encryption/$',
views.CreateVolumeTypeEncryptionView.as_view(),
name='create_type_encryption'),
url(r'^(?P<volume_type_id>[^/]+)/update_type_encryption/$',
views.UpdateVolumeTypeEncryptionView.as_view(),
name='update_type_encryption'),
url(r'^(?P<volume_type_id>[^/]+)/type_encryption_detail/$',
views.VolumeTypeEncryptionDetailView.as_view(),
name='type_encryption_detail'),

View File

@ -86,15 +86,8 @@ class CreateVolumeTypeEncryptionView(forms.ModalFormView):
@memoized.memoized_method
def get_name(self):
try:
volume_type_list = api.cinder.volume_type_list(self.request)
for volume_type in volume_type_list:
if volume_type.id == self.kwargs['volume_type_id']:
self.name = volume_type.name
except Exception:
msg = _('Unable to retrieve volume type name.')
url = reverse('horizon:admin:volumes:index')
exceptions.handle(self.request, msg, redirect=url)
if not hasattr(self, "name"):
self.name = _get_volume_type_name(self.request, self.kwargs)
return self.name
def get_context_data(self, **kwargs):
@ -147,6 +140,67 @@ class EditVolumeTypeView(forms.ModalFormView):
'description': getattr(volume_type, 'description', "")}
def _get_volume_type_name(request, kwargs):
try:
volume_type_list = api.cinder.volume_type_list(request)
for volume_type in volume_type_list:
if volume_type.id == kwargs['volume_type_id']:
return volume_type.name
except Exception:
msg = _('Unable to retrieve volume type name.')
url = reverse('horizon:admin:volumes:index')
exceptions.handle(request, msg, redirect=url)
class UpdateVolumeTypeEncryptionView(forms.ModalFormView):
form_class = volume_types_forms.UpdateVolumeTypeEncryption
form_id = "update_volume_form"
modal_header = _("Update Volume Type Encryption")
modal_id = "update_volume_type_modal"
template_name = ("admin/volumes/volume_types/"
"update_volume_type_encryption.html")
page_title = _("Update an Encrypted Volume Type")
submit_label = _("Update Volume Type Encryption")
submit_url = "horizon:admin:volumes:volume_types:update_type_encryption"
success_url = reverse_lazy('horizon:admin:volumes:index')
def get_object(self):
if not hasattr(self, "_object"):
try:
self._object = api.cinder.\
volume_encryption_type_get(self.request,
self.kwargs['volume_type_id'])
except Exception:
msg = _('Unable to retrieve encryption type.')
url = reverse('horizon:admin:volumes:index')
exceptions.handle(self.request, msg, redirect=url)
return self._object
@memoized.memoized_method
def get_name(self):
if not hasattr(self, "name"):
self.name = _get_volume_type_name(self.request, self.kwargs)
return self.name
def get_context_data(self, **kwargs):
context = super(UpdateVolumeTypeEncryptionView, self).\
get_context_data(**kwargs)
context['volume_type_id'] = self.kwargs['volume_type_id']
args = (self.kwargs['volume_type_id'],)
context['submit_url'] = reverse(self.submit_url, args=args)
return context
def get_initial(self):
encryption_type = self.get_object()
name = self.get_name()
return {'volume_type_id': encryption_type.volume_type_id,
'control_location': encryption_type.control_location,
'key_size': encryption_type.key_size,
'provider': encryption_type.provider,
'cipher': encryption_type.cipher,
'name': name}
class CreateQosSpecView(forms.ModalFormView):
form_class = volume_types_forms.CreateQosSpec
modal_header = _("Create QoS Spec")

View File

@ -0,0 +1,3 @@
---
features:
- Added the Update Encryption action for encrypted volume types.