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.