Merge "Support editing volume type access"
This commit is contained in:
commit
98ac7666a0
openstack_dashboard
api
dashboards/admin/volume_types
test/test_data
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
11
openstack_dashboard/dashboards/admin/volume_types/templates/volume_types/_update_access.html
Normal file
11
openstack_dashboard/dashboards/admin/volume_types/templates/volume_types/_update_access.html
Normal 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 %}
|
5
openstack_dashboard/dashboards/admin/volume_types/templates/volume_types/update_access.html
Normal file
5
openstack_dashboard/dashboards/admin/volume_types/templates/volume_types/update_access.html
Normal file
@ -0,0 +1,5 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block main %}
|
||||
{% include 'admin/volume_types/_update_access.html' %}
|
||||
{% endblock %}
|
@ -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)
|
||||
|
@ -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'),
|
||||
]
|
||||
|
@ -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']}
|
||||
|
@ -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(
|
||||
|
Loading…
x
Reference in New Issue
Block a user