Merge "Support editing volume type access"

This commit is contained in:
Jenkins 2017-05-05 20:23:59 +00:00 committed by Gerrit Code Review
commit 98ac7666a0
9 changed files with 197 additions and 23 deletions

View File

@ -1027,3 +1027,17 @@ def is_volume_service_enabled(request):
base.is_service_enabled(request, 'volume') or
base.is_service_enabled(request, 'volumev2')
)
def volume_type_access_list(request, volume_type):
return cinderclient(request).volume_type_access.list(volume_type)
def volume_type_add_project_access(request, volume_type, project_id):
return cinderclient(request).volume_type_access.add_project_access(
volume_type, project_id)
def volume_type_remove_project_access(request, volume_type, project_id):
return cinderclient(request).volume_type_access.remove_project_access(
volume_type, project_id)

View File

@ -19,6 +19,7 @@ from horizon import forms
from horizon import messages
from openstack_dashboard.api import cinder
from openstack_dashboard.api import keystone
class CreateVolumeType(forms.SelfHandlingForm):
@ -303,3 +304,67 @@ class EditVolumeType(forms.SelfHandlingForm):
exceptions.handle(request, error_message,
redirect=redirect)
class EditTypeAccessForm(forms.SelfHandlingForm):
def __init__(self, request, *args, **kwargs):
super(EditTypeAccessForm, self).__init__(request, *args, **kwargs)
err_msg = _('Unable to retrieve volume type access list.')
self.fields["member"] = forms.MultipleChoiceField(
required=False,
widget=forms.ThemableCheckboxSelectMultiple())
# Get list of available projects.
try:
all_projects, has_more = keystone.tenant_list(request)
except Exception:
exceptions.handle(request, err_msg)
projects_list = [(project.id, project.name)
for project in all_projects]
self.fields["member"].choices = projects_list
volume_type_id = self.initial.get('volume_type_id')
volume_type_access = []
try:
if volume_type_id:
volume_type = cinder.volume_type_get(request,
volume_type_id)
if not volume_type.is_public:
volume_type_access = [
project.project_id for project in
cinder.volume_type_access_list(request,
volume_type_id)]
except Exception:
exceptions.handle(request, err_msg)
self.fields["member"].initial = volume_type_access
def handle(self, request, data):
type_id = self.initial['volume_type_id']
current_projects = self.fields["member"].initial
removed_projects = current_projects
for p in data['member']:
if p not in current_projects:
# Newly added project access
try:
cinder.volume_type_add_project_access(request, type_id, p)
except Exception:
exceptions.handle(request,
_('Failed to add project %(project)s to '
'volume type access.') %
{'project': p})
else:
removed_projects.remove(p)
for p in removed_projects:
try:
cinder.volume_type_remove_project_access(request, type_id, p)
except Exception:
exceptions.handle(request, _('Failed to remove project '
'%(project)s from volume type '
'access.') % {'project': p})
messages.success(request,
_('Modified volume type access: %s') % type_id)
return True

View File

@ -40,6 +40,19 @@ class EditVolumeType(tables.LinkAction):
policy_rules = (("volume", "volume_extension:types_manage"),)
class EditAccess(tables.LinkAction):
name = "edit_access"
verbose_name = _("Edit Access")
url = "horizon:admin:volume_types:edit_access"
classes = ("ajax-modal",)
icon = "pencil"
policy_rules = (("volume", "volume_extension:types_manage"),)
def allowed(self, request, volume_type=None):
if volume_type is not None:
return not getattr(volume_type, 'is_public', True)
class ViewVolumeTypeExtras(tables.LinkAction):
name = "extras"
verbose_name = _("View Extra Specs")
@ -248,6 +261,7 @@ class VolumeTypesTable(tables.DataTable):
ManageQosSpecAssociation,
EditVolumeType,
UpdateVolumeTypeEncryption,
EditAccess,
DeleteVolumeTypeEncryption,
DeleteVolumeType,
UpdateMetadata)

View File

@ -0,0 +1,11 @@
{% extends "horizon/common/_modal_form_add_members.html" %}
{% load i18n %}
{% block modal-body %}
<div class="left">
{% include "horizon/common/_form_fields.html" %}
</div>
<div class="right">
<p>{% trans "Select the projects where the volume types will be used. If no projects are selected, then volume type will be only visible by users with the admin role." %}</p>
</div>
{% endblock %}

View File

@ -0,0 +1,5 @@
{% extends 'base.html' %}
{% block main %}
{% include 'admin/volume_types/_update_access.html' %}
{% endblock %}

View File

@ -72,8 +72,7 @@ class VolumeTypeTests(test.BaseAdminViewTests):
reverse('horizon:admin:volume_types:create_type'),
formData)
self.assertNoFormErrors(res)
redirect = reverse('horizon:admin:volume_types:index')
self.assertRedirectsNoFollow(res, redirect)
self.assertRedirectsNoFollow(res, INDEX_URL)
@test.create_stubs({cinder: ('volume_type_get',
'volume_type_update')})
@ -96,8 +95,7 @@ class VolumeTypeTests(test.BaseAdminViewTests):
args=[volume_type.id])
res = self.client.post(url, formData)
self.assertNoFormErrors(res)
redirect = reverse('horizon:admin:volume_types:index')
self.assertRedirectsNoFollow(res, redirect)
self.assertRedirectsNoFollow(res, INDEX_URL)
def test_update_volume_type_public_true(self):
self._test_update_volume_type(True)
@ -129,13 +127,10 @@ class VolumeTypeTests(test.BaseAdminViewTests):
volume_type.id)
self.mox.ReplayAll()
res = self.client.post(
reverse('horizon:admin:volume_types:index'),
formData)
res = self.client.post(INDEX_URL, formData)
redirect = reverse('horizon:admin:volume_types:index')
self.assertNoFormErrors(res)
self.assertRedirectsNoFollow(res, redirect)
self.assertRedirectsNoFollow(res, INDEX_URL)
@test.create_stubs({api.nova: ('server_list',),
cinder: ('volume_list',
@ -162,13 +157,10 @@ class VolumeTypeTests(test.BaseAdminViewTests):
.AndRaise(exceptions.BadRequest())
self.mox.ReplayAll()
res = self.client.post(
reverse('horizon:admin:volume_types:index'),
formData)
res = self.client.post(INDEX_URL, formData)
redirect = reverse('horizon:admin:volume_types:index')
self.assertNoFormErrors(res)
self.assertRedirectsNoFollow(res, redirect)
self.assertRedirectsNoFollow(res, INDEX_URL)
@test.create_stubs({cinder: ('volume_encryption_type_create',
'volume_type_list',)})
@ -293,13 +285,10 @@ class VolumeTypeTests(test.BaseAdminViewTests):
volume_type.id)
self.mox.ReplayAll()
res = self.client.post(
reverse('horizon:admin:volume_types:index'),
formData)
res = self.client.post(INDEX_URL, formData)
redirect = reverse('horizon:admin:volume_types:index')
self.assertNoFormErrors(res)
self.assertRedirectsNoFollow(res, redirect)
self.assertRedirectsNoFollow(res, INDEX_URL)
@test.create_stubs({cinder: ('volume_encryption_type_update',
'volume_encryption_type_get',
@ -335,3 +324,43 @@ class VolumeTypeTests(test.BaseAdminViewTests):
self.assertTemplateUsed(
res,
'admin/volume_types/update_volume_type_encryption.html')
@test.create_stubs({cinder: ('volume_type_get',
'volume_type_access_list',
'volume_type_add_project_access',
'volume_type_remove_project_access'),
keystone: ('tenant_list',)})
def _test_edit_volume_type_access(self, exception=False):
volume_type = self.cinder_volume_types.list()[2]
volume_type.id = u'1'
keystone.tenant_list(
IsA(http.HttpRequest)).AndReturn([self.tenants.list(), False])
type_access = self.cinder_type_access.list()
formData = {'member': [u'3'],
'volume_type_id': volume_type.id}
volume_type = cinder.volume_type_get(
IsA(http.HttpRequest), volume_type.id).AndReturn(volume_type)
cinder.volume_type_access_list(
IsA(http.HttpRequest), volume_type.id).AndReturn(type_access)
cinder.volume_type_add_project_access(
IsA(http.HttpRequest), volume_type.id, u'3')
if exception:
cinder.volume_type_remove_project_access(
IsA(http.HttpRequest), volume_type.id, u'1')\
.AndRaise(exceptions.BadRequest())
else:
cinder.volume_type_remove_project_access(
IsA(http.HttpRequest), volume_type.id, u'1')
self.mox.ReplayAll()
url = reverse('horizon:admin:volume_types:edit_access',
args=[volume_type.id])
res = self.client.post(url, formData)
self.assertNoFormErrors(res)
self.assertRedirectsNoFollow(res, INDEX_URL)
def test_edit_volume_type_access(self):
self._test_edit_volume_type_access()
def test_edit_volume_type_access_exception(self):
self._test_edit_volume_type_access(exception=True)

View File

@ -49,4 +49,6 @@ urlpatterns = [
name='type_encryption_detail'),
url(r'^qos_specs/',
include(qos_specs_urls, namespace='qos_specs')),
url(r'^(?P<volume_type_id>[^/]+)/edit_access/$',
views.EditAccessView.as_view(), name='edit_access'),
]

View File

@ -87,6 +87,9 @@ class VolumeTypesView(tables.MultiTableView, volumes_views.VolumeTableMixIn):
return qos_specs
INDEX_URL = 'horizon:admin:volume_types:index'
class CreateVolumeTypeView(forms.ModalFormView):
form_class = volume_types_forms.CreateVolumeType
modal_id = "create_volume_type_modal"
@ -119,7 +122,7 @@ class VolumeTypeEncryptionDetailView(views.HorizonTemplateView):
self.name = volume_type.name
self._volume_type_encryption.name = self.name
except Exception:
redirect = reverse('horizon:admin:volume_types:index')
redirect = reverse(INDEX_URL)
exceptions.handle(self.request,
_('Unable to retrieve volume type encryption'
' details.'),
@ -136,7 +139,7 @@ class CreateVolumeTypeEncryptionView(forms.ModalFormView):
template_name = "admin/volume_types/create_volume_type_encryption.html"
submit_label = _("Create Volume Type Encryption")
submit_url = "horizon:admin:volume_types:create_type_encryption"
success_url = reverse_lazy('horizon:admin:volume_types:index')
success_url = reverse_lazy(INDEX_URL)
page_title = _("Create an Encrypted Volume Type")
@memoized.memoized_method
@ -204,7 +207,7 @@ def _get_volume_type_name(request, kwargs):
return volume_type.name
except Exception:
msg = _('Unable to retrieve volume type name.')
url = reverse('horizon:admin:volume_types:index')
url = reverse(INDEX_URL)
exceptions.handle(request, msg, redirect=url)
@ -374,3 +377,23 @@ class ManageQosSpecAssociationView(forms.ModalFormView):
'cur_qos_spec_id': cur_qos_spec_id,
'cur_qos_spec_name': cur_qos_spec_name,
'qos_specs': self.get_qos_specs()}
class EditAccessView(forms.ModalFormView):
form_class = volume_types_forms.EditTypeAccessForm
template_name = 'admin/volume_types/update_access.html'
submit_label = _("Save")
submit_url = "horizon:admin:volume_types:edit_access"
success_url = reverse_lazy('horizon:admin:volume_types:index')
cancel_url = reverse_lazy('horizon:admin:volume_types:index')
page_title = _("Edit Volume Type Access")
def get_context_data(self, **kwargs):
context = super(EditAccessView, 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):
return {'volume_type_id': self.kwargs['volume_type_id']}

View File

@ -23,6 +23,7 @@ from cinderclient.v2 import volume_backups as vol_backups
from cinderclient.v2 import volume_encryption_types as vol_enc_types
from cinderclient.v2 import volume_snapshots as vol_snaps
from cinderclient.v2 import volume_transfers
from cinderclient.v2 import volume_type_access
from cinderclient.v2 import volume_types
from cinderclient.v2 import volumes
@ -38,6 +39,7 @@ def data(TEST):
TEST.cinder_volume_backups = utils.TestDataContainer()
TEST.cinder_volume_encryption_types = utils.TestDataContainer()
TEST.cinder_volume_types = utils.TestDataContainer()
TEST.cinder_type_access = utils.TestDataContainer()
TEST.cinder_volume_encryption = utils.TestDataContainer()
TEST.cinder_bootable_volumes = utils.TestDataContainer()
TEST.cinder_qos_specs = utils.TestDataContainer()
@ -157,7 +159,16 @@ def data(TEST):
{'id': u'2',
'name': u'vol_type_2',
'description': 'type 2 description'})
TEST.cinder_volume_types.add(vol_type1, vol_type2)
vol_type3 = volume_types.VolumeType(volume_types.VolumeTypeManager(None),
{'id': u'3',
'name': u'vol_type_3',
'is_public': False,
'description': 'type 3 description'})
TEST.cinder_volume_types.add(vol_type1, vol_type2, vol_type3)
vol_type_access1 = volume_type_access.VolumeTypeAccess(
volume_type_access.VolumeTypeAccessManager(None),
{'volume_type_id': u'1', 'project_id': u'1'})
TEST.cinder_type_access.add(vol_type_access1)
# Volumes - Cinder v2
volume_v2 = volumes.Volume(