diff --git a/openstack_dashboard/dashboards/admin/volume_groups/forms.py b/openstack_dashboard/dashboards/admin/volume_groups/forms.py new file mode 100644 index 0000000000..e883e3b4c9 --- /dev/null +++ b/openstack_dashboard/dashboards/admin/volume_groups/forms.py @@ -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' diff --git a/openstack_dashboard/dashboards/admin/volume_groups/panel.py b/openstack_dashboard/dashboards/admin/volume_groups/panel.py index 444dc53713..f13ecd4c69 100644 --- a/openstack_dashboard/dashboards/admin/volume_groups/panel.py +++ b/openstack_dashboard/dashboards/admin/volume_groups/panel.py @@ -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"),) diff --git a/openstack_dashboard/dashboards/admin/volume_groups/tables.py b/openstack_dashboard/dashboards/admin/volume_groups/tables.py index 42412f07ac..bab271b85a 100644 --- a/openstack_dashboard/dashboards/admin/volume_groups/tables.py +++ b/openstack_dashboard/dashboards/admin/volume_groups/tables.py @@ -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",) diff --git a/openstack_dashboard/dashboards/admin/volume_groups/tabs.py b/openstack_dashboard/dashboards/admin/volume_groups/tabs.py index bf6274fe99..517790caef 100644 --- a/openstack_dashboard/dashboards/admin/volume_groups/tabs.py +++ b/openstack_dashboard/dashboards/admin/volume_groups/tabs.py @@ -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,) diff --git a/openstack_dashboard/dashboards/admin/volume_groups/templates/volume_groups/_delete.html b/openstack_dashboard/dashboards/admin/volume_groups/templates/volume_groups/_delete.html new file mode 100644 index 0000000000..79f26df15f --- /dev/null +++ b/openstack_dashboard/dashboards/admin/volume_groups/templates/volume_groups/_delete.html @@ -0,0 +1,9 @@ +{% extends "horizon/common/_modal_form.html" %} +{% load i18n %} +{% block title %}{{ page_title }}{% endblock %} + +{% block modal-body-right %} +
{% trans "Volume groups can not be deleted if they contain volumes." %}
+{% trans "Check the "Delete Volumes" box to also delete any volumes associated with this group." %}
+{% trans "Note that a volume can not be deleted if it is "attached" or has any dependent snapshots." %}
+{% endblock %} diff --git a/openstack_dashboard/dashboards/admin/volume_groups/templates/volume_groups/_remove_vols.html b/openstack_dashboard/dashboards/admin/volume_groups/templates/volume_groups/_remove_vols.html new file mode 100644 index 0000000000..48c5b29ac5 --- /dev/null +++ b/openstack_dashboard/dashboards/admin/volume_groups/templates/volume_groups/_remove_vols.html @@ -0,0 +1,7 @@ +{% extends "horizon/common/_modal_form.html" %} +{% load i18n %} +{% block title %}{{ page_title }}{% endblock %} + +{% block modal-body %} +{% trans "This action will unassign all volumes that are currently contained in this group." %}
+{% endblock %} diff --git a/openstack_dashboard/dashboards/admin/volume_groups/templates/volume_groups/delete.html b/openstack_dashboard/dashboards/admin/volume_groups/templates/volume_groups/delete.html new file mode 100644 index 0000000000..4b1c9574ea --- /dev/null +++ b/openstack_dashboard/dashboards/admin/volume_groups/templates/volume_groups/delete.html @@ -0,0 +1,7 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{{ page_title }}{% endblock %} + +{% block main %} + {% include 'admin/volumes/volume_groups/_delete.html' %} +{% endblock %} diff --git a/openstack_dashboard/dashboards/admin/volume_groups/templates/volume_groups/remove_vols.html b/openstack_dashboard/dashboards/admin/volume_groups/templates/volume_groups/remove_vols.html new file mode 100644 index 0000000000..87b8bfb8ac --- /dev/null +++ b/openstack_dashboard/dashboards/admin/volume_groups/templates/volume_groups/remove_vols.html @@ -0,0 +1,7 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{{ page_title }}{% endblock %} + +{% block main %} + {% include 'admin/volume_groups/_remove_vols.html' %} +{% endblock %} diff --git a/openstack_dashboard/dashboards/admin/volume_groups/tests.py b/openstack_dashboard/dashboards/admin/volume_groups/tests.py index a7b6bc6db5..951b21fb39 100644 --- a/openstack_dashboard/dashboards/admin/volume_groups/tests.py +++ b/openstack_dashboard/dashboards/admin/volume_groups/tests.py @@ -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']}) diff --git a/openstack_dashboard/dashboards/admin/volume_groups/urls.py b/openstack_dashboard/dashboards/admin/volume_groups/urls.py index 7a6680927a..d34156626d 100644 --- a/openstack_dashboard/dashboards/admin/volume_groups/urls.py +++ b/openstack_dashboard/dashboards/admin/volume_groups/urls.py @@ -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