diff --git a/openstack_dashboard/dashboards/admin/volume_groups/__init__.py b/openstack_dashboard/dashboards/admin/volume_groups/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/openstack_dashboard/dashboards/admin/volume_groups/panel.py b/openstack_dashboard/dashboards/admin/volume_groups/panel.py new file mode 100644 index 0000000000..444dc53713 --- /dev/null +++ b/openstack_dashboard/dashboards/admin/volume_groups/panel.py @@ -0,0 +1,20 @@ +# Copyright 2017 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 panel as volume_groups_panel + + +class VolumeGroups(volume_groups_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 new file mode 100644 index 0000000000..42412f07ac --- /dev/null +++ b/openstack_dashboard/dashboards/admin/volume_groups/tables.py @@ -0,0 +1,31 @@ +# 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 django.utils.translation import ugettext_lazy as _ + +from horizon import tables +from openstack_dashboard.dashboards.project.volume_groups \ + import tables as volume_groups_tables + + +class GroupsTable(volume_groups_tables.GroupsTable): + # TODO(vishalmanchanda): Add Project Info.column in table + name = tables.WrappingColumn("name_or_id", + verbose_name=_("Name"), + link="horizon:admin:volume_groups:detail") + + class Meta(object): + name = "volume_groups" + verbose_name = _("Volume Groups") + table_actions = ( + volume_groups_tables.GroupsFilterAction, + ) diff --git a/openstack_dashboard/dashboards/admin/volume_groups/tabs.py b/openstack_dashboard/dashboards/admin/volume_groups/tabs.py new file mode 100644 index 0000000000..60be65e51a --- /dev/null +++ b/openstack_dashboard/dashboards/admin/volume_groups/tabs.py @@ -0,0 +1,27 @@ +# 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 django.urls import reverse + +from openstack_dashboard.dashboards.project.volume_groups \ + import tabs as volume_groups_tabs + + +class OverviewTab(volume_groups_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): + tabs = (OverviewTab,) diff --git a/openstack_dashboard/dashboards/admin/volume_groups/templates/volume_groups/_detail_overview.html b/openstack_dashboard/dashboards/admin/volume_groups/templates/volume_groups/_detail_overview.html new file mode 100644 index 0000000000..50d2b886a3 --- /dev/null +++ b/openstack_dashboard/dashboards/admin/volume_groups/templates/volume_groups/_detail_overview.html @@ -0,0 +1,42 @@ +{% load i18n sizeformat parse_date %} + +
+
+
{% trans "Name" %}
+
{{ group.name|default:_("-") }}
+
{% trans "ID" %}
+
{{ group.id }}
+
{% trans "Description" %}
+
{{ group.description|default:_("-") }}
+
{% trans "Status" %}
+
{{ group.status|capfirst }}
+
{% trans "Availability Zone" %}
+
{{ group.availability_zone }}
+
{% trans "Group Type" %}
+
{{ group.group_type }}
+
{% trans "Created" %}
+
{{ group.created_at|parse_isotime }}
+
{% trans "Replication Status" %}
+
{{ group.replication_status }}
+
+ +

{% trans "Volume Types" %}

+
+
+ {% for vol_type_name in group.volume_type_names %} +
{{ vol_type_name }}
+ {% endfor %} +
+ +

{% trans "Volumes" %}

+
+
+ {% for vol in group.volume_names %} +
{{ vol.name }}
+ {% empty %} +
+ {% trans "No assigned volumes" %} +
+ {% endfor %} +
+
diff --git a/openstack_dashboard/dashboards/admin/volume_groups/tests.py b/openstack_dashboard/dashboards/admin/volume_groups/tests.py new file mode 100644 index 0000000000..a7b6bc6db5 --- /dev/null +++ b/openstack_dashboard/dashboards/admin/volume_groups/tests.py @@ -0,0 +1,85 @@ +# 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 django.urls import reverse + +from openstack_dashboard import api +from openstack_dashboard.api import cinder +from openstack_dashboard.test import helpers as test + + +INDEX_URL = reverse('horizon:admin:volume_groups:index') +INDEX_TEMPLATE = 'horizon/common/_data_table_view.html' + + +class AdminVolumeGroupTests(test.BaseAdminViewTests): + @test.create_mocks({api.cinder: ['group_list_with_vol_type_names', + 'group_snapshot_list']}) + def test_index(self): + group = self.cinder_groups.list() + vg_snapshot = self.cinder_group_snapshots.list() + self.mock_group_list_with_vol_type_names.return_value = group + self.mock_group_snapshot_list.return_value = vg_snapshot + + res = self.client.get(INDEX_URL) + self.assertTemplateUsed(res, INDEX_TEMPLATE) + self.assertIn('volume_groups_table', res.context) + volume_groups_table = res.context['volume_groups_table'] + volume_groups = volume_groups_table.data + self.assertEqual(len(volume_groups), 1) + + self.mock_group_list_with_vol_type_names.assert_called_once_with( + test.IsHttpRequest(), {'all_tenants': 1}) + self.mock_group_snapshot_list.assert_called_once_with( + test.IsHttpRequest()) + + @test.create_mocks({cinder: ['group_get_with_vol_type_names', + 'volume_list', + 'group_snapshot_list']}) + def test_detail_view(self): + group = self.cinder_groups.first() + volumes = self.cinder_volumes.list() + vg_snapshot = self.cinder_group_snapshots.list() + + self.mock_group_get_with_vol_type_names.return_value = group + self.mock_volume_list.return_value = volumes + self.mock_group_snapshot_list.return_value = vg_snapshot + + url = reverse('horizon:admin:volume_groups:detail', + args=[group.id]) + res = self.client.get(url) + self.assertNoFormErrors(res) + self.assertEqual(res.status_code, 200) + self.assertTrue(group.has_snapshots) + + self.mock_group_get_with_vol_type_names.assert_called_once_with( + test.IsHttpRequest(), group.id) + search_opts = {'group_id': group.id} + self.mock_volume_list.assert_called_once_with( + test.IsHttpRequest(), search_opts=search_opts) + self.mock_group_snapshot_list.assert_called_once_with( + test.IsHttpRequest(), search_opts=search_opts) + + @test.create_mocks({cinder: ['group_get']}) + def test_detail_view_with_exception(self): + group = self.cinder_groups.first() + + self.mock_group_get.side_effect = self.exceptions.cinder + + url = reverse('horizon:admin:volume_groups:detail', + args=[group.id]) + res = self.client.get(url) + self.assertNoFormErrors(res) + self.assertRedirectsNoFollow(res, INDEX_URL) + + self.mock_group_get.assert_called_once_with( + test.IsHttpRequest(), group.id) diff --git a/openstack_dashboard/dashboards/admin/volume_groups/urls.py b/openstack_dashboard/dashboards/admin/volume_groups/urls.py new file mode 100644 index 0000000000..7a6680927a --- /dev/null +++ b/openstack_dashboard/dashboards/admin/volume_groups/urls.py @@ -0,0 +1,23 @@ +# 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 django.conf.urls import url + +from openstack_dashboard.dashboards.admin.volume_groups import views + + +urlpatterns = [ + url(r'^$', views.IndexView.as_view(), name='index'), + url(r'^(?P[^/]+)$', + views.DetailView.as_view(), + name='detail'), +] diff --git a/openstack_dashboard/dashboards/admin/volume_groups/views.py b/openstack_dashboard/dashboards/admin/volume_groups/views.py new file mode 100644 index 0000000000..15cebd1547 --- /dev/null +++ b/openstack_dashboard/dashboards/admin/volume_groups/views.py @@ -0,0 +1,61 @@ +# 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 django.urls import reverse +from django.utils.translation import ugettext_lazy as _ + +from horizon import exceptions +from horizon import tables + +from openstack_dashboard import api + +from openstack_dashboard.dashboards.admin.volume_groups \ + import tables as volume_group_tables +from openstack_dashboard.dashboards.admin.volume_groups \ + import tabs as volume_group_tabs +from openstack_dashboard.dashboards.project.volume_groups \ + import views as volume_groups_views + + +class IndexView(tables.DataTableView): + table_class = volume_group_tables.GroupsTable + page_title = _("Groups") + + def get_data(self): + try: + groups = api.cinder.group_list_with_vol_type_names( + self.request, {'all_tenants': 1}) + except Exception: + groups = [] + exceptions.handle(self.request, + _("Unable to retrieve volume groups.")) + if not groups: + return groups + group_snapshots = api.cinder.group_snapshot_list(self.request) + snapshot_groups = {gs.group_id for gs in group_snapshots} + for g in groups: + g.has_snapshots = g.id in snapshot_groups + return groups + + +class DetailView(volume_groups_views.DetailView): + tab_group_class = volume_group_tabs.GroupsDetailTabs + + def get_context_data(self, **kwargs): + context = super(DetailView, self).get_context_data(**kwargs) + table = volume_group_tables.GroupsTable(self.request) + context["actions"] = table.render_row_actions(context["group"]) + return context + + @staticmethod + def get_redirect_url(): + return reverse('horizon:admin:volume_groups:index') diff --git a/openstack_dashboard/enabled/_2250_admin_volume_groups.py b/openstack_dashboard/enabled/_2250_admin_volume_groups.py new file mode 100644 index 0000000000..66f437782a --- /dev/null +++ b/openstack_dashboard/enabled/_2250_admin_volume_groups.py @@ -0,0 +1,10 @@ +# The slug of the panel to be added to HORIZON_CONFIG. Required. +PANEL = 'volume_groups' +# The slug of the dashboard the PANEL associated with. Required. +PANEL_DASHBOARD = 'admin' +# The slug of the panel group the PANEL is associated with. +PANEL_GROUP = 'volume' + +# Python panel class of the PANEL to be added. +ADD_PANEL = ('openstack_dashboard.dashboards.admin.volume_groups.panel.' + 'VolumeGroups')