From e2698063e27698901d77f9422697fd2ebc261655 Mon Sep 17 00:00:00 2001 From: Rob Cresswell Date: Wed, 1 Feb 2017 19:05:49 +0000 Subject: [PATCH] Move Security Groups into its own panel This patch moves the Security Groups tab from the Access and Security panel into its own panel under the Network panel group. As this is the last tab in Access and Security, that panel is also removed by this patch. Change-Id: Id29c7ce635d46383742aec140def265d4b249aa5 Implements: blueprint reorganise-access-and-security --- .../security_groups/__init__.py | 0 .../project/access_and_security/tabs.py | 55 ------- .../templates/access_and_security/index.html | 11 -- .../security_groups/add_rule.html | 8 - .../security_groups/update.html | 7 - .../project/access_and_security/tests.py | 155 ------------------ .../project/access_and_security/urls.py | 31 ---- .../project/access_and_security/views.py | 35 ---- .../__init__.py | 0 .../security_groups/forms.py | 6 +- .../panel.py | 10 +- .../security_groups/tables.py | 11 +- .../templates}/security_groups/_add_rule.html | 0 .../templates}/security_groups/_create.html | 0 .../templates}/security_groups/_update.html | 0 .../templates/security_groups/add_rule.html | 6 + .../templates}/security_groups/create.html | 2 - .../templates}/security_groups/detail.html | 3 - .../templates/security_groups/update.html | 5 + .../security_groups/tests.py | 124 +++++++++++++- .../security_groups/urls.py | 6 +- .../security_groups/views.py | 52 ++++-- .../enabled/_1060_project_access_panel.py | 10 -- .../enabled/_1480_security_groups_panel.py | 6 + ...-access-and-security-ea7780aa9e7b83e7.yaml | 5 +- 25 files changed, 187 insertions(+), 361 deletions(-) delete mode 100644 openstack_dashboard/dashboards/project/access_and_security/security_groups/__init__.py delete mode 100644 openstack_dashboard/dashboards/project/access_and_security/tabs.py delete mode 100644 openstack_dashboard/dashboards/project/access_and_security/templates/access_and_security/index.html delete mode 100644 openstack_dashboard/dashboards/project/access_and_security/templates/access_and_security/security_groups/add_rule.html delete mode 100644 openstack_dashboard/dashboards/project/access_and_security/templates/access_and_security/security_groups/update.html delete mode 100644 openstack_dashboard/dashboards/project/access_and_security/tests.py delete mode 100644 openstack_dashboard/dashboards/project/access_and_security/urls.py delete mode 100644 openstack_dashboard/dashboards/project/access_and_security/views.py rename openstack_dashboard/dashboards/project/{access_and_security => security_groups}/__init__.py (100%) rename openstack_dashboard/dashboards/project/{access_and_security => }/security_groups/forms.py (98%) rename openstack_dashboard/dashboards/project/{access_and_security => security_groups}/panel.py (78%) rename openstack_dashboard/dashboards/project/{access_and_security => }/security_groups/tables.py (95%) rename openstack_dashboard/dashboards/project/{access_and_security/templates/access_and_security => security_groups/templates}/security_groups/_add_rule.html (100%) rename openstack_dashboard/dashboards/project/{access_and_security/templates/access_and_security => security_groups/templates}/security_groups/_create.html (100%) rename openstack_dashboard/dashboards/project/{access_and_security/templates/access_and_security => security_groups/templates}/security_groups/_update.html (100%) create mode 100644 openstack_dashboard/dashboards/project/security_groups/templates/security_groups/add_rule.html rename openstack_dashboard/dashboards/project/{access_and_security/templates/access_and_security => security_groups/templates}/security_groups/create.html (61%) rename openstack_dashboard/dashboards/project/{access_and_security/templates/access_and_security => security_groups/templates}/security_groups/detail.html (72%) create mode 100644 openstack_dashboard/dashboards/project/security_groups/templates/security_groups/update.html rename openstack_dashboard/dashboards/project/{access_and_security => }/security_groups/tests.py (88%) rename openstack_dashboard/dashboards/project/{access_and_security => }/security_groups/urls.py (90%) rename openstack_dashboard/dashboards/project/{access_and_security => }/security_groups/views.py (77%) delete mode 100644 openstack_dashboard/enabled/_1060_project_access_panel.py create mode 100644 openstack_dashboard/enabled/_1480_security_groups_panel.py diff --git a/openstack_dashboard/dashboards/project/access_and_security/security_groups/__init__.py b/openstack_dashboard/dashboards/project/access_and_security/security_groups/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/openstack_dashboard/dashboards/project/access_and_security/tabs.py b/openstack_dashboard/dashboards/project/access_and_security/tabs.py deleted file mode 100644 index aa1ac0ccfb..0000000000 --- a/openstack_dashboard/dashboards/project/access_and_security/tabs.py +++ /dev/null @@ -1,55 +0,0 @@ -# Copyright 2012 United States Government as represented by the -# Administrator of the National Aeronautics and Space Administration. -# All Rights Reserved. -# -# Copyright 2012 Nebula, Inc. -# Copyright 2012 OpenStack Foundation -# -# 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 exceptions -from horizon import tabs - -from neutronclient.common import exceptions as neutron_exc - -from openstack_dashboard.api import network -from openstack_dashboard.dashboards.project.access_and_security.\ - security_groups.tables import SecurityGroupsTable - - -class SecurityGroupsTab(tabs.TableTab): - table_classes = (SecurityGroupsTable,) - name = _("Security Groups") - slug = "security_groups_tab" - template_name = "horizon/common/_detail_table.html" - permissions = ('openstack.services.compute',) - - def get_security_groups_data(self): - try: - security_groups = network.security_group_list(self.request) - except neutron_exc.ConnectionFailed: - security_groups = [] - exceptions.handle(self.request) - except Exception: - security_groups = [] - exceptions.handle(self.request, - _('Unable to retrieve security groups.')) - return sorted(security_groups, key=lambda group: group.name) - - -class AccessAndSecurityTabs(tabs.TabGroup): - slug = "access_security_tabs" - tabs = (SecurityGroupsTab,) - sticky = True diff --git a/openstack_dashboard/dashboards/project/access_and_security/templates/access_and_security/index.html b/openstack_dashboard/dashboards/project/access_and_security/templates/access_and_security/index.html deleted file mode 100644 index 87b81a797e..0000000000 --- a/openstack_dashboard/dashboards/project/access_and_security/templates/access_and_security/index.html +++ /dev/null @@ -1,11 +0,0 @@ -{% extends 'base.html' %} -{% load i18n %} -{% block title %}{% trans "Access & Security" %}{% endblock %} - -{% block main %} -
-
- {{ tab_group.render }} -
-
-{% endblock %} diff --git a/openstack_dashboard/dashboards/project/access_and_security/templates/access_and_security/security_groups/add_rule.html b/openstack_dashboard/dashboards/project/access_and_security/templates/access_and_security/security_groups/add_rule.html deleted file mode 100644 index bcfa046dca..0000000000 --- a/openstack_dashboard/dashboards/project/access_and_security/templates/access_and_security/security_groups/add_rule.html +++ /dev/null @@ -1,8 +0,0 @@ -{% extends 'base.html' %} -{% load i18n %} -{% block title %}{% trans "Add Rule" %}{% endblock %} - -{% block main %} - {% include 'project/access_and_security/security_groups/_add_rule.html' %} -{% endblock %} - diff --git a/openstack_dashboard/dashboards/project/access_and_security/templates/access_and_security/security_groups/update.html b/openstack_dashboard/dashboards/project/access_and_security/templates/access_and_security/security_groups/update.html deleted file mode 100644 index b07f5cdc05..0000000000 --- a/openstack_dashboard/dashboards/project/access_and_security/templates/access_and_security/security_groups/update.html +++ /dev/null @@ -1,7 +0,0 @@ -{% extends 'base.html' %} -{% load i18n %} -{% block title %}{% trans "Edit Security Group" %}{% endblock %} - -{% block main %} - {% include 'project/access_and_security/security_groups/_update.html' %} -{% endblock %} diff --git a/openstack_dashboard/dashboards/project/access_and_security/tests.py b/openstack_dashboard/dashboards/project/access_and_security/tests.py deleted file mode 100644 index c525d160cf..0000000000 --- a/openstack_dashboard/dashboards/project/access_and_security/tests.py +++ /dev/null @@ -1,155 +0,0 @@ -# Copyright 2012 United States Government as represented by the -# Administrator of the National Aeronautics and Space Administration. -# All Rights Reserved. -# -# Copyright 2012 Nebula, Inc. -# -# 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 copy import deepcopy # noqa - -from django.core.urlresolvers import reverse -from django import http -from mox3.mox import IsA # noqa -import six - -from openstack_dashboard import api -from openstack_dashboard.test import helpers as test -from openstack_dashboard.usage import quotas - -INDEX_URL = reverse('horizon:project:access_and_security:index') - - -class AccessAndSecurityTests(test.TestCase): - def setUp(self): - super(AccessAndSecurityTests, self).setUp() - - @test.create_stubs({api.network: ('security_group_list',), - api.base: ('is_service_enabled',), - quotas: ('tenant_quota_usages',)}) - def _test_index(self): - sec_groups = self.security_groups.list() - quota_data = self.quota_usages.first() - quota_data['security_groups']['available'] = 10 - - api.network.security_group_list(IsA(http.HttpRequest)) \ - .AndReturn(sec_groups) - quotas.tenant_quota_usages(IsA(http.HttpRequest)).MultipleTimes() \ - .AndReturn(quota_data) - - api.base.is_service_enabled(IsA(http.HttpRequest), 'network') \ - .MultipleTimes().AndReturn(True) - - self.mox.ReplayAll() - - res = self.client.get(INDEX_URL) - - self.assertTemplateUsed(res, 'project/access_and_security/index.html') - - # Security groups - sec_groups_from_ctx = res.context['security_groups_table'].data - # Context data needs to contains all items from the test data. - self.assertItemsEqual(sec_groups_from_ctx, - sec_groups) - # Sec groups in context need to be sorted by their ``name`` attribute. - # This assertion is somewhat weak since it's only meaningful as long as - # the sec groups in the test data are *not* sorted by name (which is - # the case as of the time of this addition). - self.assertTrue( - all([sec_groups_from_ctx[i].name <= sec_groups_from_ctx[i + 1].name - for i in range(len(sec_groups_from_ctx) - 1)])) - - def test_index(self): - self._test_index() - - -class SecurityGroupTabTests(test.TestCase): - def setUp(self): - super(SecurityGroupTabTests, self).setUp() - - @test.create_stubs({api.network: ('security_group_list',), - quotas: ('tenant_quota_usages',), - api.base: ('is_service_enabled',)}) - def test_create_button_attributes(self): - sec_groups = self.security_groups.list() - quota_data = self.quota_usages.first() - quota_data['security_groups']['available'] = 10 - - api.network.security_group_list( - IsA(http.HttpRequest)) \ - .AndReturn(sec_groups) - quotas.tenant_quota_usages( - IsA(http.HttpRequest)).MultipleTimes() \ - .AndReturn(quota_data) - - api.base.is_service_enabled( - IsA(http.HttpRequest), 'network').MultipleTimes() \ - .AndReturn(True) - - self.mox.ReplayAll() - - res = self.client.get(INDEX_URL + - "?tab=access_security_tabs__security_groups_tab") - - security_groups = res.context['security_groups_table'].data - self.assertItemsEqual(security_groups, self.security_groups.list()) - - create_action = self.getAndAssertTableAction(res, 'security_groups', - 'create') - - self.assertEqual('Create Security Group', - six.text_type(create_action.verbose_name)) - self.assertIsNone(create_action.policy_rules) - self.assertEqual(set(['ajax-modal']), set(create_action.classes)) - - url = 'horizon:project:access_and_security:security_groups:create' - self.assertEqual(url, create_action.url) - - @test.create_stubs({api.network: ('security_group_list',), - quotas: ('tenant_quota_usages',), - api.base: ('is_service_enabled',)}) - def _test_create_button_disabled_when_quota_exceeded(self, - network_enabled): - sec_groups = self.security_groups.list() - quota_data = self.quota_usages.first() - quota_data['security_groups']['available'] = 0 - - api.network.security_group_list( - IsA(http.HttpRequest)) \ - .AndReturn(sec_groups) - quotas.tenant_quota_usages( - IsA(http.HttpRequest)).MultipleTimes() \ - .AndReturn(quota_data) - - api.base.is_service_enabled( - IsA(http.HttpRequest), 'network').MultipleTimes() \ - .AndReturn(network_enabled) - - self.mox.ReplayAll() - - res = self.client.get(INDEX_URL + - "?tab=access_security_tabs__security_groups_tab") - - security_groups = res.context['security_groups_table'].data - self.assertItemsEqual(security_groups, self.security_groups.list()) - - create_action = self.getAndAssertTableAction(res, 'security_groups', - 'create') - self.assertIn('disabled', create_action.classes, - 'The create button should be disabled') - - def test_create_button_disabled_when_quota_exceeded_neutron_disabled(self): - self._test_create_button_disabled_when_quota_exceeded(False) - - def test_create_button_disabled_when_quota_exceeded_neutron_enabled(self): - self._test_create_button_disabled_when_quota_exceeded(True) diff --git a/openstack_dashboard/dashboards/project/access_and_security/urls.py b/openstack_dashboard/dashboards/project/access_and_security/urls.py deleted file mode 100644 index 81f452fcd1..0000000000 --- a/openstack_dashboard/dashboards/project/access_and_security/urls.py +++ /dev/null @@ -1,31 +0,0 @@ -# Copyright 2012 United States Government as represented by the -# Administrator of the National Aeronautics and Space Administration. -# All Rights Reserved. -# -# Copyright 2012 Nebula, Inc. -# -# 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 include -from django.conf.urls import url - -from openstack_dashboard.dashboards.project.access_and_security.\ - security_groups import urls as sec_group_urls -from openstack_dashboard.dashboards.project.access_and_security import views - - -urlpatterns = [ - url(r'^$', views.IndexView.as_view(), name='index'), - url(r'security_groups/', - include(sec_group_urls, namespace='security_groups')), -] diff --git a/openstack_dashboard/dashboards/project/access_and_security/views.py b/openstack_dashboard/dashboards/project/access_and_security/views.py deleted file mode 100644 index 5827ab6f18..0000000000 --- a/openstack_dashboard/dashboards/project/access_and_security/views.py +++ /dev/null @@ -1,35 +0,0 @@ -# Copyright 2012 United States Government as represented by the -# Administrator of the National Aeronautics and Space Administration. -# All Rights Reserved. -# -# Copyright 2012 Nebula, Inc. -# Copyright 2012 OpenStack Foundation -# -# 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. - -""" -Views for Instances and Volumes. -""" - -from django.utils.translation import ugettext_lazy as _ - -from horizon import tabs - -from openstack_dashboard.dashboards.project.access_and_security \ - import tabs as project_tabs - - -class IndexView(tabs.TabbedTableView): - tab_group_class = project_tabs.AccessAndSecurityTabs - template_name = 'project/access_and_security/index.html' - page_title = _("Access & Security") diff --git a/openstack_dashboard/dashboards/project/access_and_security/__init__.py b/openstack_dashboard/dashboards/project/security_groups/__init__.py similarity index 100% rename from openstack_dashboard/dashboards/project/access_and_security/__init__.py rename to openstack_dashboard/dashboards/project/security_groups/__init__.py diff --git a/openstack_dashboard/dashboards/project/access_and_security/security_groups/forms.py b/openstack_dashboard/dashboards/project/security_groups/forms.py similarity index 98% rename from openstack_dashboard/dashboards/project/access_and_security/security_groups/forms.py rename to openstack_dashboard/dashboards/project/security_groups/forms.py index cfdaabb369..7ef5488472 100644 --- a/openstack_dashboard/dashboards/project/access_and_security/security_groups/forms.py +++ b/openstack_dashboard/dashboards/project/security_groups/forms.py @@ -73,7 +73,7 @@ class GroupBase(forms.SelfHandlingForm): messages.success(request, self.success_message % sg.name) return sg except Exception as e: - redirect = reverse("horizon:project:access_and_security:index") + redirect = reverse("horizon:project:security_groups:index") error_msg = self.error_message % e exceptions.handle(request, error_msg, redirect=redirect) @@ -407,8 +407,8 @@ class AddRule(forms.SelfHandlingForm): return cleaned_data def handle(self, request, data): - redirect = reverse("horizon:project:access_and_security:" - "security_groups:detail", args=[data['id']]) + redirect = reverse("horizon:project:security_groups:detail", + args=[data['id']]) try: rule = api.network.security_group_rule_create( request, diff --git a/openstack_dashboard/dashboards/project/access_and_security/panel.py b/openstack_dashboard/dashboards/project/security_groups/panel.py similarity index 78% rename from openstack_dashboard/dashboards/project/access_and_security/panel.py rename to openstack_dashboard/dashboards/project/security_groups/panel.py index d8a7b01dc6..720c5c4512 100644 --- a/openstack_dashboard/dashboards/project/access_and_security/panel.py +++ b/openstack_dashboard/dashboards/project/security_groups/panel.py @@ -1,5 +1,4 @@ -# Copyright 2012 Nebula, Inc. -# Copyright 2012 OpenStack Foundation +# Copyright 2017 Cisco Systems, Inc. # # 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 @@ -14,10 +13,9 @@ # under the License. from django.utils.translation import ugettext_lazy as _ - import horizon -class AccessAndSecurity(horizon.Panel): - name = _("Access & Security") - slug = 'access_and_security' +class SecurityGroups(horizon.Panel): + name = _("Security Groups") + slug = 'security_groups' diff --git a/openstack_dashboard/dashboards/project/access_and_security/security_groups/tables.py b/openstack_dashboard/dashboards/project/security_groups/tables.py similarity index 95% rename from openstack_dashboard/dashboards/project/access_and_security/security_groups/tables.py rename to openstack_dashboard/dashboards/project/security_groups/tables.py index 3d5f465ec2..cedfe77520 100644 --- a/openstack_dashboard/dashboards/project/access_and_security/security_groups/tables.py +++ b/openstack_dashboard/dashboards/project/security_groups/tables.py @@ -65,7 +65,7 @@ class DeleteGroup(policy.PolicyTargetMixin, tables.DeleteAction): class CreateGroup(tables.LinkAction): name = "create" verbose_name = _("Create Security Group") - url = "horizon:project:access_and_security:security_groups:create" + url = "horizon:project:security_groups:create" classes = ("ajax-modal",) icon = "plus" @@ -90,7 +90,7 @@ class CreateGroup(tables.LinkAction): class EditGroup(policy.PolicyTargetMixin, tables.LinkAction): name = "edit" verbose_name = _("Edit Security Group") - url = "horizon:project:access_and_security:security_groups:update" + url = "horizon:project:security_groups:update" classes = ("ajax-modal",) icon = "pencil" @@ -112,7 +112,7 @@ class EditGroup(policy.PolicyTargetMixin, tables.LinkAction): class ManageRules(policy.PolicyTargetMixin, tables.LinkAction): name = "manage_rules" verbose_name = _("Manage Rules") - url = "horizon:project:access_and_security:security_groups:detail" + url = "horizon:project:security_groups:detail" icon = "pencil" def allowed(self, request, security_group=None): @@ -151,7 +151,7 @@ class SecurityGroupsTable(tables.DataTable): class CreateRule(tables.LinkAction): name = "add_rule" verbose_name = _("Add Rule") - url = "horizon:project:access_and_security:security_groups:add_rule" + url = "horizon:project:security_groups:add_rule" classes = ("ajax-modal",) icon = "plus" @@ -197,8 +197,7 @@ class DeleteRule(tables.DeleteAction): def get_success_url(self, request): sg_id = self.table.kwargs['security_group_id'] - return reverse("horizon:project:access_and_security:" - "security_groups:detail", args=[sg_id]) + return reverse("horizon:project:security_groups:detail", args=[sg_id]) def get_remote_ip_prefix(rule): diff --git a/openstack_dashboard/dashboards/project/access_and_security/templates/access_and_security/security_groups/_add_rule.html b/openstack_dashboard/dashboards/project/security_groups/templates/security_groups/_add_rule.html similarity index 100% rename from openstack_dashboard/dashboards/project/access_and_security/templates/access_and_security/security_groups/_add_rule.html rename to openstack_dashboard/dashboards/project/security_groups/templates/security_groups/_add_rule.html diff --git a/openstack_dashboard/dashboards/project/access_and_security/templates/access_and_security/security_groups/_create.html b/openstack_dashboard/dashboards/project/security_groups/templates/security_groups/_create.html similarity index 100% rename from openstack_dashboard/dashboards/project/access_and_security/templates/access_and_security/security_groups/_create.html rename to openstack_dashboard/dashboards/project/security_groups/templates/security_groups/_create.html diff --git a/openstack_dashboard/dashboards/project/access_and_security/templates/access_and_security/security_groups/_update.html b/openstack_dashboard/dashboards/project/security_groups/templates/security_groups/_update.html similarity index 100% rename from openstack_dashboard/dashboards/project/access_and_security/templates/access_and_security/security_groups/_update.html rename to openstack_dashboard/dashboards/project/security_groups/templates/security_groups/_update.html diff --git a/openstack_dashboard/dashboards/project/security_groups/templates/security_groups/add_rule.html b/openstack_dashboard/dashboards/project/security_groups/templates/security_groups/add_rule.html new file mode 100644 index 0000000000..ff94652ba3 --- /dev/null +++ b/openstack_dashboard/dashboards/project/security_groups/templates/security_groups/add_rule.html @@ -0,0 +1,6 @@ +{% extends 'base.html' %} + +{% block main %} + {% include 'project/security_groups/_add_rule.html' %} +{% endblock %} + diff --git a/openstack_dashboard/dashboards/project/access_and_security/templates/access_and_security/security_groups/create.html b/openstack_dashboard/dashboards/project/security_groups/templates/security_groups/create.html similarity index 61% rename from openstack_dashboard/dashboards/project/access_and_security/templates/access_and_security/security_groups/create.html rename to openstack_dashboard/dashboards/project/security_groups/templates/security_groups/create.html index 1b5e578cda..ab43e707e1 100644 --- a/openstack_dashboard/dashboards/project/access_and_security/templates/access_and_security/security_groups/create.html +++ b/openstack_dashboard/dashboards/project/security_groups/templates/security_groups/create.html @@ -1,6 +1,4 @@ {% extends 'base.html' %} -{% load i18n %} -{% block title %}{% trans "Create Security Group" %}{% endblock %} {% block main %} {% include 'project/access_and_security/security_groups/_create.html' %} diff --git a/openstack_dashboard/dashboards/project/access_and_security/templates/access_and_security/security_groups/detail.html b/openstack_dashboard/dashboards/project/security_groups/templates/security_groups/detail.html similarity index 72% rename from openstack_dashboard/dashboards/project/access_and_security/templates/access_and_security/security_groups/detail.html rename to openstack_dashboard/dashboards/project/security_groups/templates/security_groups/detail.html index f2c0656a3c..0717ae625f 100644 --- a/openstack_dashboard/dashboards/project/access_and_security/templates/access_and_security/security_groups/detail.html +++ b/openstack_dashboard/dashboards/project/security_groups/templates/security_groups/detail.html @@ -1,7 +1,4 @@ {% extends 'base.html' %} -{% load i18n %} - -{% block title %}{% trans "Manage Security Group Rules" %}{% endblock %} {% block page_header %} {% include "horizon/common/_detail_header.html" %} diff --git a/openstack_dashboard/dashboards/project/security_groups/templates/security_groups/update.html b/openstack_dashboard/dashboards/project/security_groups/templates/security_groups/update.html new file mode 100644 index 0000000000..cef7fb43d4 --- /dev/null +++ b/openstack_dashboard/dashboards/project/security_groups/templates/security_groups/update.html @@ -0,0 +1,5 @@ +{% extends 'base.html' %} + +{% block main %} + {% include 'project/security_groups/_update.html' %} +{% endblock %} diff --git a/openstack_dashboard/dashboards/project/access_and_security/security_groups/tests.py b/openstack_dashboard/dashboards/project/security_groups/tests.py similarity index 88% rename from openstack_dashboard/dashboards/project/access_and_security/security_groups/tests.py rename to openstack_dashboard/dashboards/project/security_groups/tests.py index ac1f2b8938..52d1aed5bc 100644 --- a/openstack_dashboard/dashboards/project/access_and_security/security_groups/tests.py +++ b/openstack_dashboard/dashboards/project/security_groups/tests.py @@ -17,6 +17,7 @@ # under the License. import cgi +import six import django from django.conf import settings @@ -30,21 +31,20 @@ from horizon import forms from openstack_dashboard import api from openstack_dashboard.test import helpers as test +from openstack_dashboard.usage import quotas -from openstack_dashboard.dashboards.project.access_and_security.\ - security_groups import tables +from openstack_dashboard.dashboards.project.security_groups import tables -INDEX_URL = reverse('horizon:project:access_and_security:index') -SG_CREATE_URL = reverse('horizon:project:access_and_security:' - 'security_groups:create') +INDEX_URL = reverse('horizon:project:security_groups:index') +SG_CREATE_URL = reverse('horizon:project:security_groups:create') -SG_VIEW_PATH = 'horizon:project:access_and_security:security_groups:%s' +SG_VIEW_PATH = 'horizon:project:security_groups:%s' SG_DETAIL_VIEW = SG_VIEW_PATH % 'detail' SG_UPDATE_VIEW = SG_VIEW_PATH % 'update' SG_ADD_RULE_VIEW = SG_VIEW_PATH % 'add_rule' -SG_TEMPLATE_PATH = 'project/access_and_security/security_groups/%s' +SG_TEMPLATE_PATH = 'project/security_groups/%s' SG_DETAIL_TEMPLATE = SG_TEMPLATE_PATH % 'detail.html' SG_CREATE_TEMPLATE = SG_TEMPLATE_PATH % 'create.html' SG_UPDATE_TEMPLATE = SG_TEMPLATE_PATH % '_update.html' @@ -64,6 +64,116 @@ class SecurityGroupsViewTests(test.TestCase): self.edit_url = reverse(SG_ADD_RULE_VIEW, args=[sec_group.id]) self.update_url = reverse(SG_UPDATE_VIEW, args=[sec_group.id]) + @test.create_stubs({api.network: ('security_group_list',), + api.base: ('is_service_enabled',), + quotas: ('tenant_quota_usages',)}) + def test_index(self): + sec_groups = self.security_groups.list() + quota_data = self.quota_usages.first() + quota_data['security_groups']['available'] = 10 + + api.network.security_group_list(IsA(http.HttpRequest)) \ + .AndReturn(sec_groups) + quotas.tenant_quota_usages(IsA(http.HttpRequest)).MultipleTimes() \ + .AndReturn(quota_data) + + api.base.is_service_enabled(IsA(http.HttpRequest), 'network') \ + .MultipleTimes().AndReturn(True) + + self.mox.ReplayAll() + + res = self.client.get(INDEX_URL) + + self.assertTemplateUsed(res, 'horizon/common/_data_table_view.html') + + # Security groups + sec_groups_from_ctx = res.context['security_groups_table'].data + # Context data needs to contains all items from the test data. + self.assertItemsEqual(sec_groups_from_ctx, + sec_groups) + # Sec groups in context need to be sorted by their ``name`` attribute. + # This assertion is somewhat weak since it's only meaningful as long as + # the sec groups in the test data are *not* sorted by name (which is + # the case as of the time of this addition). + self.assertTrue( + all([sec_groups_from_ctx[i].name <= sec_groups_from_ctx[i + 1].name + for i in range(len(sec_groups_from_ctx) - 1)])) + + @test.create_stubs({api.network: ('security_group_list',), + quotas: ('tenant_quota_usages',), + api.base: ('is_service_enabled',)}) + def test_create_button_attributes(self): + sec_groups = self.security_groups.list() + quota_data = self.quota_usages.first() + quota_data['security_groups']['available'] = 10 + + api.network.security_group_list( + IsA(http.HttpRequest)) \ + .AndReturn(sec_groups) + quotas.tenant_quota_usages( + IsA(http.HttpRequest)).MultipleTimes() \ + .AndReturn(quota_data) + + api.base.is_service_enabled( + IsA(http.HttpRequest), 'network').MultipleTimes() \ + .AndReturn(True) + + self.mox.ReplayAll() + + res = self.client.get(INDEX_URL) + + security_groups = res.context['security_groups_table'].data + self.assertItemsEqual(security_groups, self.security_groups.list()) + + create_action = self.getAndAssertTableAction(res, 'security_groups', + 'create') + + self.assertEqual('Create Security Group', + six.text_type(create_action.verbose_name)) + self.assertIsNone(create_action.policy_rules) + self.assertEqual(set(['ajax-modal']), set(create_action.classes)) + + url = 'horizon:project:security_groups:create' + self.assertEqual(url, create_action.url) + + @test.create_stubs({api.network: ('security_group_list',), + quotas: ('tenant_quota_usages',), + api.base: ('is_service_enabled',)}) + def _test_create_button_disabled_when_quota_exceeded(self, + network_enabled): + sec_groups = self.security_groups.list() + quota_data = self.quota_usages.first() + quota_data['security_groups']['available'] = 0 + + api.network.security_group_list( + IsA(http.HttpRequest)) \ + .AndReturn(sec_groups) + quotas.tenant_quota_usages( + IsA(http.HttpRequest)).MultipleTimes() \ + .AndReturn(quota_data) + + api.base.is_service_enabled( + IsA(http.HttpRequest), 'network').MultipleTimes() \ + .AndReturn(network_enabled) + + self.mox.ReplayAll() + + res = self.client.get(INDEX_URL) + + security_groups = res.context['security_groups_table'].data + self.assertItemsEqual(security_groups, self.security_groups.list()) + + create_action = self.getAndAssertTableAction(res, 'security_groups', + 'create') + self.assertIn('disabled', create_action.classes, + 'The create button should be disabled') + + def test_create_button_disabled_when_quota_exceeded_neutron_disabled(self): + self._test_create_button_disabled_when_quota_exceeded(False) + + def test_create_button_disabled_when_quota_exceeded_neutron_enabled(self): + self._test_create_button_disabled_when_quota_exceeded(True) + @test.create_stubs({api.network: ('security_group_rule_create', 'security_group_list', 'security_group_backend')}) diff --git a/openstack_dashboard/dashboards/project/access_and_security/security_groups/urls.py b/openstack_dashboard/dashboards/project/security_groups/urls.py similarity index 90% rename from openstack_dashboard/dashboards/project/access_and_security/security_groups/urls.py rename to openstack_dashboard/dashboards/project/security_groups/urls.py index 456bc0f622..0dc4a2a809 100644 --- a/openstack_dashboard/dashboards/project/access_and_security/security_groups/urls.py +++ b/openstack_dashboard/dashboards/project/security_groups/urls.py @@ -17,12 +17,10 @@ # under the License. from django.conf.urls import url - -from openstack_dashboard.dashboards.project.access_and_security.\ - security_groups import views - +from openstack_dashboard.dashboards.project.security_groups import views urlpatterns = [ + url(r'^$', views.IndexView.as_view(), name='index'), url(r'^create/$', views.CreateView.as_view(), name='create'), url(r'^(?P[^/]+)/$', views.DetailView.as_view(), diff --git a/openstack_dashboard/dashboards/project/access_and_security/security_groups/views.py b/openstack_dashboard/dashboards/project/security_groups/views.py similarity index 77% rename from openstack_dashboard/dashboards/project/access_and_security/security_groups/views.py rename to openstack_dashboard/dashboards/project/security_groups/views.py index 4d6421daee..58ae279cac 100644 --- a/openstack_dashboard/dashboards/project/access_and_security/security_groups/views.py +++ b/openstack_dashboard/dashboards/project/security_groups/views.py @@ -28,18 +28,19 @@ from horizon import forms from horizon import tables from horizon.utils import memoized -from openstack_dashboard import api -from openstack_dashboard.utils import filters +from neutronclient.common import exceptions as neutron_exc -from openstack_dashboard.dashboards.project.access_and_security.\ - security_groups import forms as project_forms -from openstack_dashboard.dashboards.project.access_and_security.\ - security_groups import tables as project_tables +from openstack_dashboard import api +from openstack_dashboard.dashboards.project.security_groups \ + import forms as project_forms +from openstack_dashboard.dashboards.project.security_groups \ + import tables as project_tables +from openstack_dashboard.utils import filters class DetailView(tables.DataTableView): table_class = project_tables.RulesTable - template_name = 'project/access_and_security/security_groups/detail.html' + template_name = 'project/security_groups/detail.html' page_title = _("Manage Security Group Rules: " "{{ security_group.name }} ({{ security_group.id }})") @@ -49,7 +50,7 @@ class DetailView(tables.DataTableView): try: return api.network.security_group_get(self.request, sg_id) except Exception: - redirect = reverse('horizon:project:access_and_security:index') + redirect = reverse('horizon:project:security_groups:index') exceptions.handle(self.request, _('Unable to retrieve security group.'), redirect=redirect) @@ -71,10 +72,10 @@ class UpdateView(forms.ModalFormView): form_class = project_forms.UpdateGroup form_id = "update_security_group_form" modal_id = "update_security_group_modal" - template_name = 'project/access_and_security/security_groups/update.html' + template_name = 'project/security_groups/update.html' submit_label = _("Edit Security Group") - submit_url = "horizon:project:access_and_security:security_groups:update" - success_url = reverse_lazy('horizon:project:access_and_security:index') + submit_url = "horizon:project:security_groups:update" + success_url = reverse_lazy('horizon:project:security_groups:index') page_title = _("Edit Security Group") @memoized.memoized_method @@ -105,10 +106,10 @@ class AddRuleView(forms.ModalFormView): form_class = project_forms.AddRule form_id = "create_security_group_rule_form" modal_id = "create_security_group_rule_modal" - template_name = 'project/access_and_security/security_groups/add_rule.html' + template_name = 'project/security_groups/add_rule.html' submit_label = _("Add") - submit_url = "horizon:project:access_and_security:security_groups:add_rule" - url = "horizon:project:access_and_security:security_groups:detail" + submit_url = "horizon:project:security_groups:add_rule" + url = "horizon:project:security_groups:detail" page_title = _("Add Rule") def get_success_url(self): @@ -152,9 +153,26 @@ class CreateView(forms.ModalFormView): form_class = project_forms.CreateGroup form_id = "create_security_group_form" modal_id = "create_security_group_modal" - template_name = 'project/access_and_security/security_groups/create.html' + template_name = 'project/security_groups/create.html' submit_label = _("Create Security Group") submit_url = reverse_lazy( - "horizon:project:access_and_security:security_groups:create") - success_url = reverse_lazy('horizon:project:access_and_security:index') + "horizon:project:security_groups:create") + success_url = reverse_lazy('horizon:project:security_groups:index') page_title = _("Create Security Group") + + +class IndexView(tables.DataTableView): + table_class = project_tables.SecurityGroupsTable + page_title = _("Security Groups") + + def get_data(self): + try: + security_groups = api.network.security_group_list(self.request) + except neutron_exc.ConnectionFailed: + security_groups = [] + exceptions.handle(self.request) + except Exception: + security_groups = [] + exceptions.handle(self.request, + _('Unable to retrieve security groups.')) + return sorted(security_groups, key=lambda group: group.name) diff --git a/openstack_dashboard/enabled/_1060_project_access_panel.py b/openstack_dashboard/enabled/_1060_project_access_panel.py deleted file mode 100644 index 6ef2538c7d..0000000000 --- a/openstack_dashboard/enabled/_1060_project_access_panel.py +++ /dev/null @@ -1,10 +0,0 @@ -# The slug of the panel to be added to HORIZON_CONFIG. Required. -PANEL = 'access_and_security' -# The slug of the dashboard the PANEL associated with. Required. -PANEL_DASHBOARD = 'project' -# The slug of the panel group the PANEL is associated with. -PANEL_GROUP = 'compute' - -# Python panel class of the PANEL to be added. -ADD_PANEL = ('openstack_dashboard.dashboards.project.' - 'access_and_security.panel.AccessAndSecurity') diff --git a/openstack_dashboard/enabled/_1480_security_groups_panel.py b/openstack_dashboard/enabled/_1480_security_groups_panel.py new file mode 100644 index 0000000000..5e327a073a --- /dev/null +++ b/openstack_dashboard/enabled/_1480_security_groups_panel.py @@ -0,0 +1,6 @@ +PANEL_DASHBOARD = 'project' +PANEL_GROUP = 'network' +PANEL = 'security_groups' + +ADD_PANEL = ('openstack_dashboard.dashboards.project.security_groups' + '.panel.SecurityGroups') diff --git a/releasenotes/notes/bp/reorganise-access-and-security-ea7780aa9e7b83e7.yaml b/releasenotes/notes/bp/reorganise-access-and-security-ea7780aa9e7b83e7.yaml index 8b98ddba79..b58a6cc6ce 100644 --- a/releasenotes/notes/bp/reorganise-access-and-security-ea7780aa9e7b83e7.yaml +++ b/releasenotes/notes/bp/reorganise-access-and-security-ea7780aa9e7b83e7.yaml @@ -2,4 +2,7 @@ features: - The Access & Security panel's tabs have been moved to their own panels for clearer navigation and better performance. API Access and Key Pairs now - reside in the Compute panel group. + reside in the Compute panel group. Floating IPs and Security Groups are + now in the Network panel group. + - Download buttons for OpenStack RC files have been added to the user + dropdown menu in the top right of Horizon.