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.