Merge "Add volume-group table actions for admin panel"
This commit is contained in:
commit
0dfaeac7a3
24
openstack_dashboard/dashboards/admin/volume_groups/forms.py
Normal file
24
openstack_dashboard/dashboards/admin/volume_groups/forms.py
Normal file
@ -0,0 +1,24 @@
|
||||
# Copyright 2019 NEC Corporation
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from openstack_dashboard.dashboards.project.volume_groups \
|
||||
import forms as project_forms
|
||||
|
||||
|
||||
class RemoveVolsForm(project_forms.RemoveVolsForm):
|
||||
failure_url = 'horizon:admin:volume_groups:index'
|
||||
|
||||
|
||||
class DeleteForm(project_forms.DeleteForm):
|
||||
failure_url = 'horizon:admin:volume_groups:index'
|
@ -1,4 +1,4 @@
|
||||
# Copyright 2017 NEC Corporation
|
||||
# Copyright 2019 NEC Corporation
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
@ -13,8 +13,8 @@
|
||||
# under the License.
|
||||
|
||||
from openstack_dashboard.dashboards.project.volume_groups \
|
||||
import panel as volume_groups_panel
|
||||
import panel as project_panel
|
||||
|
||||
|
||||
class VolumeGroups(volume_groups_panel.VolumeGroups):
|
||||
class VolumeGroups(project_panel.VolumeGroups):
|
||||
policy_rules = (("volume", "context_is_admin"),)
|
||||
|
@ -1,3 +1,5 @@
|
||||
# Copyright 2019 NEC Corporation
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
@ -14,10 +16,22 @@ from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import tables
|
||||
from openstack_dashboard.dashboards.project.volume_groups \
|
||||
import tables as volume_groups_tables
|
||||
import tables as project_tables
|
||||
|
||||
|
||||
class GroupsTable(volume_groups_tables.GroupsTable):
|
||||
class DeleteGroup(project_tables.DeleteGroup):
|
||||
url = "horizon:admin:volume_groups:delete"
|
||||
|
||||
|
||||
class RemoveAllVolumes(project_tables.RemoveAllVolumes):
|
||||
url = "horizon:admin:volume_groups:remove_volumes"
|
||||
|
||||
|
||||
class ManageVolumes(project_tables.ManageVolumes):
|
||||
url = "horizon:admin:volume_groups:manage"
|
||||
|
||||
|
||||
class GroupsTable(project_tables.GroupsTable):
|
||||
# TODO(vishalmanchanda): Add Project Info.column in table
|
||||
name = tables.WrappingColumn("name_or_id",
|
||||
verbose_name=_("Name"),
|
||||
@ -27,5 +41,12 @@ class GroupsTable(volume_groups_tables.GroupsTable):
|
||||
name = "volume_groups"
|
||||
verbose_name = _("Volume Groups")
|
||||
table_actions = (
|
||||
volume_groups_tables.GroupsFilterAction,
|
||||
project_tables.GroupsFilterAction,
|
||||
)
|
||||
row_actions = (
|
||||
ManageVolumes,
|
||||
RemoveAllVolumes,
|
||||
DeleteGroup,
|
||||
)
|
||||
row_class = project_tables.UpdateRow
|
||||
status_columns = ("status",)
|
||||
|
@ -1,3 +1,5 @@
|
||||
# Copyright 2019 NEC Corporation
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
@ -13,15 +15,15 @@
|
||||
from django.urls import reverse
|
||||
|
||||
from openstack_dashboard.dashboards.project.volume_groups \
|
||||
import tabs as volume_groups_tabs
|
||||
import tabs as project_tabs
|
||||
|
||||
|
||||
class OverviewTab(volume_groups_tabs.OverviewTab):
|
||||
class OverviewTab(project_tabs.OverviewTab):
|
||||
template_name = ("admin/volume_groups/_detail_overview.html")
|
||||
|
||||
def get_redirect_url(self):
|
||||
return reverse('horizon:admin:volume_groups:index')
|
||||
|
||||
|
||||
class GroupsDetailTabs(volume_groups_tabs.GroupsDetailTabs):
|
||||
class GroupsDetailTabs(project_tabs.GroupsDetailTabs):
|
||||
tabs = (OverviewTab,)
|
||||
|
@ -0,0 +1,9 @@
|
||||
{% extends "horizon/common/_modal_form.html" %}
|
||||
{% load i18n %}
|
||||
{% block title %}{{ page_title }}{% endblock %}
|
||||
|
||||
{% block modal-body-right %}
|
||||
<p>{% trans "Volume groups can not be deleted if they contain volumes." %}</p>
|
||||
<p>{% trans "Check the "Delete Volumes" box to also delete any volumes associated with this group." %}</p>
|
||||
<p>{% trans "Note that a volume can not be deleted if it is "attached" or has any dependent snapshots." %}</p>
|
||||
{% endblock %}
|
@ -0,0 +1,7 @@
|
||||
{% extends "horizon/common/_modal_form.html" %}
|
||||
{% load i18n %}
|
||||
{% block title %}{{ page_title }}{% endblock %}
|
||||
|
||||
{% block modal-body %}
|
||||
<p>{% trans "This action will unassign all volumes that are currently contained in this group." %}</p>
|
||||
{% endblock %}
|
@ -0,0 +1,7 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{{ page_title }}{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
{% include 'admin/volumes/volume_groups/_delete.html' %}
|
||||
{% endblock %}
|
@ -0,0 +1,7 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{{ page_title }}{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
{% include 'admin/volume_groups/_remove_vols.html' %}
|
||||
{% endblock %}
|
@ -1,3 +1,5 @@
|
||||
# Copyright 2019 NEC Corporation
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
@ -12,6 +14,8 @@
|
||||
|
||||
from django.urls import reverse
|
||||
|
||||
import mock
|
||||
|
||||
from openstack_dashboard import api
|
||||
from openstack_dashboard.api import cinder
|
||||
from openstack_dashboard.test import helpers as test
|
||||
@ -42,6 +46,126 @@ class AdminVolumeGroupTests(test.BaseAdminViewTests):
|
||||
self.mock_group_snapshot_list.assert_called_once_with(
|
||||
test.IsHttpRequest())
|
||||
|
||||
@test.create_mocks({cinder: ['group_get', 'group_delete']})
|
||||
def test_delete_group(self):
|
||||
group = self.cinder_groups.first()
|
||||
|
||||
self.mock_group_get.return_value = group
|
||||
self.mock_group_delete.return_value = None
|
||||
|
||||
url = reverse('horizon:admin:volume_groups:delete',
|
||||
args=[group.id])
|
||||
res = self.client.post(url)
|
||||
self.assertNoFormErrors(res)
|
||||
self.assertRedirectsNoFollow(res, INDEX_URL)
|
||||
|
||||
self.mock_group_get.assert_called_once_with(test.IsHttpRequest(),
|
||||
group.id)
|
||||
self.mock_group_delete.assert_called_once_with(test.IsHttpRequest(),
|
||||
group.id,
|
||||
delete_volumes=False)
|
||||
|
||||
@test.create_mocks({cinder: ['group_get', 'group_delete']})
|
||||
def test_delete_group_delete_volumes_flag(self):
|
||||
group = self.cinder_consistencygroups.first()
|
||||
formData = {'delete_volumes': True}
|
||||
|
||||
self.mock_group_get.return_value = group
|
||||
self.mock_group_delete.return_value = None
|
||||
|
||||
url = reverse('horizon:admin:volume_groups:delete',
|
||||
args=[group.id])
|
||||
res = self.client.post(url, formData)
|
||||
self.assertNoFormErrors(res)
|
||||
self.assertRedirectsNoFollow(res, INDEX_URL)
|
||||
|
||||
self.mock_group_get.assert_called_once_with(test.IsHttpRequest(),
|
||||
group.id)
|
||||
self.mock_group_delete.assert_called_once_with(test.IsHttpRequest(),
|
||||
group.id,
|
||||
delete_volumes=True)
|
||||
|
||||
@test.create_mocks({cinder: ['group_get', 'group_delete']})
|
||||
def test_delete_group_exception(self):
|
||||
group = self.cinder_groups.first()
|
||||
formData = {'delete_volumes': False}
|
||||
|
||||
self.mock_group_get.return_value = group
|
||||
self.mock_group_delete.side_effect = self.exceptions.cinder
|
||||
|
||||
url = reverse('horizon:admin:volume_groups:delete',
|
||||
args=[group.id])
|
||||
res = self.client.post(url, formData)
|
||||
self.assertNoFormErrors(res)
|
||||
self.assertRedirectsNoFollow(res, INDEX_URL)
|
||||
|
||||
self.mock_group_get.assert_called_once_with(test.IsHttpRequest(),
|
||||
group.id)
|
||||
self.mock_group_delete.assert_called_once_with(test.IsHttpRequest(),
|
||||
group.id,
|
||||
delete_volumes=False)
|
||||
|
||||
def test_update_group_add_vol(self):
|
||||
self._test_update_group_add_remove_vol(add=True)
|
||||
|
||||
def test_update_group_remove_vol(self):
|
||||
self._test_update_group_add_remove_vol(add=False)
|
||||
|
||||
@test.create_mocks({cinder: ['volume_list',
|
||||
'volume_type_list',
|
||||
'group_get',
|
||||
'group_update']})
|
||||
def _test_update_group_add_remove_vol(self, add=True):
|
||||
group = self.cinder_groups.first()
|
||||
volume_types = self.cinder_volume_types.list()
|
||||
volumes = (self.cinder_volumes.list() +
|
||||
self.cinder_group_volumes.list())
|
||||
|
||||
group_voltype_names = [t.name for t in volume_types
|
||||
if t.id in group.volume_types]
|
||||
compat_volumes = [v for v in volumes
|
||||
if v.volume_type in group_voltype_names]
|
||||
compat_volume_ids = [v.id for v in compat_volumes]
|
||||
assigned_volume_ids = [v.id for v in compat_volumes
|
||||
if getattr(v, 'group_id', None)]
|
||||
add_volume_ids = [v.id for v in compat_volumes
|
||||
if v.id not in assigned_volume_ids]
|
||||
|
||||
new_volums = compat_volume_ids if add else []
|
||||
formData = {
|
||||
'default_add_volumes_to_group_role': 'member',
|
||||
'add_volumes_to_group_role_member': new_volums,
|
||||
}
|
||||
|
||||
self.mock_volume_list.return_value = volumes
|
||||
self.mock_volume_type_list.return_value = volume_types
|
||||
self.mock_group_get.return_value = group
|
||||
self.mock_group_update.return_value = group
|
||||
|
||||
url = reverse('horizon:admin:volume_groups:manage',
|
||||
args=[group.id])
|
||||
res = self.client.post(url, formData)
|
||||
self.assertNoFormErrors(res)
|
||||
self.assertRedirectsNoFollow(res, INDEX_URL)
|
||||
|
||||
self.assert_mock_multiple_calls_with_same_arguments(
|
||||
self.mock_volume_list, 2,
|
||||
mock.call(test.IsHttpRequest()))
|
||||
self.mock_volume_type_list.assert_called_once_with(
|
||||
test.IsHttpRequest())
|
||||
self.mock_group_get.assert_called_once_with(
|
||||
test.IsHttpRequest(), group.id)
|
||||
if add:
|
||||
self.mock_group_update.assert_called_once_with(
|
||||
test.IsHttpRequest(), group.id,
|
||||
add_volumes=add_volume_ids,
|
||||
remove_volumes=[])
|
||||
else:
|
||||
self.mock_group_update.assert_called_once_with(
|
||||
test.IsHttpRequest(), group.id,
|
||||
add_volumes=[],
|
||||
remove_volumes=assigned_volume_ids)
|
||||
|
||||
@test.create_mocks({cinder: ['group_get_with_vol_type_names',
|
||||
'volume_list',
|
||||
'group_snapshot_list']})
|
||||
|
@ -1,3 +1,5 @@
|
||||
# Copyright 2019 NEC Corporation
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
@ -20,4 +22,13 @@ urlpatterns = [
|
||||
url(r'^(?P<group_id>[^/]+)$',
|
||||
views.DetailView.as_view(),
|
||||
name='detail'),
|
||||
url(r'^(?P<group_id>[^/]+)/remove_volumese/$',
|
||||
views.RemoveVolumesView.as_view(),
|
||||
name='remove_volumes'),
|
||||
url(r'^(?P<group_id>[^/]+)/delete/$',
|
||||
views.DeleteView.as_view(),
|
||||
name='delete'),
|
||||
url(r'^(?P<group_id>[^/]+)/manage/$',
|
||||
views.ManageView.as_view(),
|
||||
name='manage'),
|
||||
]
|
||||
|
@ -1,3 +1,5 @@
|
||||
# Copyright 2019 NEC Corporation
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
@ -11,6 +13,7 @@
|
||||
# under the License.
|
||||
|
||||
from django.urls import reverse
|
||||
from django.urls import reverse_lazy
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import exceptions
|
||||
@ -19,15 +22,19 @@ from horizon import tables
|
||||
from openstack_dashboard import api
|
||||
|
||||
from openstack_dashboard.dashboards.admin.volume_groups \
|
||||
import tables as volume_group_tables
|
||||
import forms as admin_forms
|
||||
from openstack_dashboard.dashboards.admin.volume_groups \
|
||||
import tabs as volume_group_tabs
|
||||
import tables as admin_tables
|
||||
from openstack_dashboard.dashboards.admin.volume_groups \
|
||||
import tabs as admin_tabs
|
||||
from openstack_dashboard.dashboards.admin.volume_groups \
|
||||
import workflows as admin_workflows
|
||||
from openstack_dashboard.dashboards.project.volume_groups \
|
||||
import views as volume_groups_views
|
||||
import views as project_views
|
||||
|
||||
|
||||
class IndexView(tables.DataTableView):
|
||||
table_class = volume_group_tables.GroupsTable
|
||||
table_class = admin_tables.GroupsTable
|
||||
page_title = _("Groups")
|
||||
|
||||
def get_data(self):
|
||||
@ -47,12 +54,30 @@ class IndexView(tables.DataTableView):
|
||||
return groups
|
||||
|
||||
|
||||
class DetailView(volume_groups_views.DetailView):
|
||||
tab_group_class = volume_group_tabs.GroupsDetailTabs
|
||||
class RemoveVolumesView(project_views.RemoveVolumesView):
|
||||
template_name = 'admin/volume_groups/remove_vols.html'
|
||||
form_class = admin_forms.RemoveVolsForm
|
||||
success_url = reverse_lazy('horizon:admin:volume_groups:index')
|
||||
submit_url = "horizon:admin:volume_groups:remove_volumes"
|
||||
|
||||
|
||||
class DeleteView(project_views.DeleteView):
|
||||
template_name = 'admin/volume_groups/delete.html'
|
||||
form_class = admin_forms.DeleteForm
|
||||
success_url = reverse_lazy('horizon:admin:volume_groups:index')
|
||||
submit_url = "horizon:admin:volume_groups:delete"
|
||||
|
||||
|
||||
class ManageView(project_views.ManageView):
|
||||
workflow_class = admin_workflows.UpdateGroupWorkflow
|
||||
|
||||
|
||||
class DetailView(project_views.DetailView):
|
||||
tab_group_class = admin_tabs.GroupsDetailTabs
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(DetailView, self).get_context_data(**kwargs)
|
||||
table = volume_group_tables.GroupsTable(self.request)
|
||||
table = admin_tables.GroupsTable(self.request)
|
||||
context["actions"] = table.render_row_actions(context["group"])
|
||||
return context
|
||||
|
||||
|
@ -0,0 +1,22 @@
|
||||
# Copyright 2019 NEC Corporation
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from openstack_dashboard.dashboards.project.volume_groups \
|
||||
import workflows as project_workflows
|
||||
|
||||
INDEX_URL = "horizon:admin:volume_groups:index"
|
||||
|
||||
|
||||
class UpdateGroupWorkflow(project_workflows.UpdateGroupWorkflow):
|
||||
success_url = INDEX_URL
|
@ -58,6 +58,8 @@ class UpdateForm(forms.SelfHandlingForm):
|
||||
|
||||
|
||||
class RemoveVolsForm(forms.SelfHandlingForm):
|
||||
failure_url = 'horizon:project:volume_groups:index'
|
||||
|
||||
def handle(self, request, data):
|
||||
group_id = self.initial['group_id']
|
||||
name = self.initial['name']
|
||||
@ -79,7 +81,7 @@ class RemoveVolsForm(forms.SelfHandlingForm):
|
||||
return True
|
||||
|
||||
except Exception:
|
||||
redirect = reverse("horizon:project:volume_groups:index")
|
||||
redirect = reverse(self.failure_url)
|
||||
exceptions.handle(request,
|
||||
_('Errors occurred in removing volumes '
|
||||
'from group.'),
|
||||
@ -89,6 +91,7 @@ class RemoveVolsForm(forms.SelfHandlingForm):
|
||||
class DeleteForm(forms.SelfHandlingForm):
|
||||
delete_volumes = forms.BooleanField(label=_("Delete Volumes"),
|
||||
required=False)
|
||||
failure_url = 'horizon:project:volume_groups:index'
|
||||
|
||||
def handle(self, request, data):
|
||||
group_id = self.initial['group_id']
|
||||
@ -103,7 +106,7 @@ class DeleteForm(forms.SelfHandlingForm):
|
||||
return True
|
||||
|
||||
except Exception:
|
||||
redirect = reverse("horizon:project:volume_groups:index")
|
||||
redirect = reverse(self.failure_url)
|
||||
exceptions.handle(request, _('Errors occurred in deleting group.'),
|
||||
redirect=redirect)
|
||||
|
||||
|
@ -0,0 +1,6 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
[:blueprint:`cinder-generic-volume-groups`]
|
||||
Cinder generic groups is now supported for admin panel.
|
||||
Admin is now able to view all groups for differenet users.
|
Loading…
Reference in New Issue
Block a user