Merge "Move Security Groups into its own panel"
This commit is contained in:
commit
7d5b6ebca6
|
@ -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
|
|
@ -1,11 +0,0 @@
|
|||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Access & Security" %}{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
{{ tab_group.render }}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -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 %}
|
||||
|
|
@ -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 %}
|
|
@ -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)
|
|
@ -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')),
|
||||
]
|
|
@ -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")
|
|
@ -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,
|
|
@ -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'
|
|
@ -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):
|
|
@ -0,0 +1,6 @@
|
|||
{% extends 'base.html' %}
|
||||
|
||||
{% block main %}
|
||||
{% include 'project/security_groups/_add_rule.html' %}
|
||||
{% endblock %}
|
||||
|
|
@ -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' %}
|
|
@ -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" %}
|
|
@ -0,0 +1,5 @@
|
|||
{% extends 'base.html' %}
|
||||
|
||||
{% block main %}
|
||||
{% include 'project/security_groups/_update.html' %}
|
||||
{% endblock %}
|
|
@ -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')})
|
|
@ -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<security_group_id>[^/]+)/$',
|
||||
views.DetailView.as_view(),
|
|
@ -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)
|
|
@ -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')
|
|
@ -0,0 +1,6 @@
|
|||
PANEL_DASHBOARD = 'project'
|
||||
PANEL_GROUP = 'network'
|
||||
PANEL = 'security_groups'
|
||||
|
||||
ADD_PANEL = ('openstack_dashboard.dashboards.project.security_groups'
|
||||
'.panel.SecurityGroups')
|
|
@ -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.
|
||||
|
|
Loading…
Reference in New Issue