Horizon changes for DVR

Feature completed :
1. Admin router panel
   + New "Distributed" column introduced.
   + New Field "Distributed" added on to
     router detail panel
2. Project router panel
   if logged in as "Admin"
   ======================
   + New distributed column introduced.
   + New Field distributed column added on to
     router detail panel.
   + New Router Field dropdown box introduced in
     create router form.
   if logged in as "nonAdmin"
   =========================
   + Router Type dropdown will be invisible for
     non admin.
   + Distributed information will be
     hidden from details panel.

implements: blueprint enhance-horizon-for-dvr

Co-Authored-By: Akihiro Motoki <motoki@da.jp.nec.com>

Change-Id: I995745dd72a8b750866c0977a7d7cf42036f716f
This commit is contained in:
Saro Chandra Bhooshan 2014-08-07 01:00:18 -07:00 committed by Akihiro Motoki
parent 266639a4e9
commit 0d8fb6ce08
13 changed files with 197 additions and 19 deletions

View File

@ -34,6 +34,7 @@ from horizon.utils.memoized import memoized # noqa
from openstack_dashboard.api import base
from openstack_dashboard.api import network_base
from openstack_dashboard.api import nova
from openstack_dashboard import policy
LOG = logging.getLogger(__name__)
@ -898,3 +899,31 @@ def is_port_profiles_supported():
profile_support = network_config.get('profile_support', None)
if str(profile_support).lower() == 'cisco':
return True
def get_dvr_permission(request, operation):
"""Check if "distributed" field can be displayed.
:param request: Request Object
:param operation: Operation type. The valid value is "get" or "create"
"""
network_config = getattr(settings, 'OPENSTACK_NEUTRON_NETWORK', {})
if not network_config.get('enable_distributed_router', False):
return False
policy_check = getattr(settings, "POLICY_CHECK_FUNCTION", None)
if operation not in ("get", "create"):
raise ValueError(_("The 'operation' parameter for get_dvr_permission "
"is invalid. It should be 'get' or 'create'."))
role = (("network", "%s_router:distributed" % operation),)
if policy_check:
has_permission = policy.check(role, request)
else:
has_permission = True
if not has_permission:
return False
try:
return is_extension_supported(request, 'dvr')
except Exception:
msg = _('Failed to check Neutron "dvr" extension is not supported')
LOG.info(msg)
return False

View File

@ -145,6 +145,8 @@
"add_router_interface": "rule:admin_or_owner",
"remove_router_interface": "rule:admin_or_owner",
"delete_router": "rule:admin_or_owner",
"get_router:distributed": "rule:admin_only",
"create_router:distributed": "rule:admin_only",
"create_floatingip": "rule:regular_user",
"update_floatingip": "rule:admin_or_owner",

View File

@ -12,7 +12,6 @@
# License for the specific language governing permissions and limitations
# under the License.
from django.template.defaultfilters import title # noqa
from django.utils.translation import ugettext_lazy as _
from horizon import tables
@ -51,20 +50,11 @@ class UpdateRow(tables.Row):
return router
class RoutersTable(tables.DataTable):
class RoutersTable(r_tables.RoutersTable):
tenant = tables.Column("tenant_name", verbose_name=_("Project"))
name = tables.Column("name",
verbose_name=_("Name"),
link="horizon:admin:routers:detail")
status = tables.Column("status",
filters=(title,),
verbose_name=_("Status"),
status=True)
ext_net = tables.Column(r_tables.get_external_network,
verbose_name=_("External Network"))
def get_object_display(self, obj):
return obj.name
class Meta:
name = "Routers"
@ -73,3 +63,4 @@ class RoutersTable(tables.DataTable):
row_class = UpdateRow
table_actions = (DeleteRouter,)
row_actions = (DeleteRouter,)
Columns = ('tenant', 'name', 'status', 'distributed', 'ext_net')

View File

@ -12,6 +12,10 @@
<dd>{{ router.tenant_id }}</dd>
<dt>{% trans "Status" %}</dt>
<dd>{{ router.status|capfirst }}</dd>
{% if dvr_supported %}
<dt>{% trans "Distributed" %}</dt>
<dd>{{ router.distributed|yesno|capfirst }}</dd>
{% endif %}
{% if router.external_gateway_info %}
<dt>{% trans "External Gateway Information" %}</dt>
<dd>{% trans "Connected External Network" %}:

View File

@ -32,15 +32,28 @@ LOG = logging.getLogger(__name__)
class CreateForm(forms.SelfHandlingForm):
name = forms.CharField(max_length="255", label=_("Router Name"))
mode = forms.ChoiceField(label=_("Router Type"))
failure_url = 'horizon:project:routers:index'
def __init__(self, request, *args, **kwargs):
super(CreateForm, self).__init__(request, *args, **kwargs)
self.dvr_enabled = api.neutron.get_dvr_permission(self.request,
"create")
if self.dvr_enabled:
mode_choices = [('server_default', _('Use Server Default')),
('centralized', _('Centralized')),
('distributed', _('Distributed'))]
self.fields['mode'].choices = mode_choices
else:
self.fields['mode'].widget = forms.HiddenInput()
self.fields['mode'].required = False
def handle(self, request, data):
try:
router = api.neutron.router_create(request,
name=data['name'])
params = {'name': data['name']}
if (self.dvr_enabled and data['mode'] != 'server_default'):
params['distributed'] = (data['mode'] == 'distributed')
router = api.neutron.router_create(request, **params)
message = _('Router %s was successfully created.') % data['name']
messages.success(request, message)
return router

View File

@ -15,7 +15,7 @@
import logging
from django.core.urlresolvers import reverse
from django.template.defaultfilters import title # noqa
from django.template import defaultfilters as filters
from django.utils.translation import ugettext_lazy as _
from neutronclient.common import exceptions as q_ext
@ -147,12 +147,24 @@ class RoutersTable(tables.DataTable):
verbose_name=_("Name"),
link="horizon:project:routers:detail")
status = tables.Column("status",
filters=(title,),
filters=(filters.title,),
verbose_name=_("Status"),
status=True)
distributed = tables.Column("distributed",
filters=(filters.yesno, filters.capfirst),
verbose_name=_("Distributed"))
ext_net = tables.Column(get_external_network,
verbose_name=_("External Network"))
def __init__(self, request, data=None, needs_form_wrapper=None, **kwargs):
super(RoutersTable, self).__init__(
request,
data=data,
needs_form_wrapper=needs_form_wrapper,
**kwargs)
if not api.neutron.get_dvr_permission(request, "get"):
del self.columns["distributed"]
def get_object_display(self, obj):
return obj.name

View File

@ -10,6 +10,10 @@
<dd>{{ router.id|default:_("None") }}</dd>
<dt>{% trans "Status" %}</dt>
<dd>{{ router.status|default:_("Unknown") }}</dd>
{% if dvr_supported %}
<dt>{% trans "Distributed" %}</dt>
<dd>{{ router.distributed|yesno|capfirst }}</dd>
{% endif %}
{% if router.external_gateway_info %}
<dt>{% trans "External Gateway Information" %}</dt>
<dd>{% trans "Connected External Network" %}:

View File

@ -109,13 +109,16 @@ class RouterActionTests(test.TestCase):
INDEX_URL = reverse('horizon:%s:routers:index' % DASHBOARD)
DETAIL_PATH = 'horizon:%s:routers:detail' % DASHBOARD
@test.create_stubs({api.neutron: ('router_create',)})
@test.create_stubs({api.neutron: ('router_create',
'get_dvr_permission',)})
def test_router_create_post(self):
router = self.routers.first()
api.neutron.get_dvr_permission(IsA(http.HttpRequest), "create")\
.AndReturn(False)
api.neutron.router_create(IsA(http.HttpRequest), name=router.name)\
.AndReturn(router)
self.mox.ReplayAll()
self.mox.ReplayAll()
form_data = {'name': router.name}
url = reverse('horizon:%s:routers:create' % self.DASHBOARD)
res = self.client.post(url, form_data)
@ -123,9 +126,50 @@ class RouterActionTests(test.TestCase):
self.assertNoFormErrors(res)
self.assertRedirectsNoFollow(res, self.INDEX_URL)
@test.create_stubs({api.neutron: ('router_create',)})
@test.create_stubs({api.neutron: ('router_create',
'get_dvr_permission',)})
def test_router_create_post_mode_server_default(self):
router = self.routers.first()
api.neutron.get_dvr_permission(IsA(http.HttpRequest), "create")\
.AndReturn(True)
api.neutron.router_create(IsA(http.HttpRequest), name=router.name)\
.AndReturn(router)
self.mox.ReplayAll()
form_data = {'name': router.name,
'mode': 'server_default'}
url = reverse('horizon:%s:routers:create' % self.DASHBOARD)
res = self.client.post(url, form_data)
self.assertNoFormErrors(res)
self.assertRedirectsNoFollow(res, self.INDEX_URL)
@test.create_stubs({api.neutron: ('router_create',
'get_dvr_permission',)})
def test_dvr_router_create_post(self):
router = self.routers.first()
api.neutron.get_dvr_permission(IsA(http.HttpRequest), "create")\
.MultipleTimes().AndReturn(True)
param = {'name': router.name,
'distributed': True}
api.neutron.router_create(IsA(http.HttpRequest), **param)\
.AndReturn(router)
self.mox.ReplayAll()
form_data = {'name': router.name,
'mode': 'distributed'}
url = reverse('horizon:%s:routers:create' % self.DASHBOARD)
res = self.client.post(url, form_data)
self.assertNoFormErrors(res)
self.assertRedirectsNoFollow(res, self.INDEX_URL)
@test.create_stubs({api.neutron: ('router_create',
'get_dvr_permission',)})
def test_router_create_post_exception_error_case_409(self):
router = self.routers.first()
api.neutron.get_dvr_permission(IsA(http.HttpRequest), "create")\
.MultipleTimes().AndReturn(False)
self.exceptions.neutron.status_code = 409
api.neutron.router_create(IsA(http.HttpRequest), name=router.name)\
.AndRaise(self.exceptions.neutron)
@ -138,9 +182,12 @@ class RouterActionTests(test.TestCase):
self.assertNoFormErrors(res)
self.assertRedirectsNoFollow(res, self.INDEX_URL)
@test.create_stubs({api.neutron: ('router_create',)})
@test.create_stubs({api.neutron: ('router_create',
'get_dvr_permission',)})
def test_router_create_post_exception_error_case_non_409(self):
router = self.routers.first()
api.neutron.get_dvr_permission(IsA(http.HttpRequest), "create")\
.MultipleTimes().AndReturn(False)
self.exceptions.neutron.status_code = 999
api.neutron.router_create(IsA(http.HttpRequest), name=router.name)\
.AndRaise(self.exceptions.neutron)

View File

@ -116,6 +116,8 @@ class DetailView(tabs.TabbedTableView):
def get_context_data(self, **kwargs):
context = super(DetailView, self).get_context_data(**kwargs)
context["router"] = self._get_data()
context['dvr_supported'] = api.neutron.get_dvr_permission(self.request,
"get")
return context
def get(self, request, *args, **kwargs):

View File

@ -183,6 +183,7 @@ OPENSTACK_NEUTRON_NETWORK = {
'enable_quotas': True,
'enable_vpn': False,
'enable_ipv6': True,
'enable_distributed_router': True,
# The profile_support option is used to detect if an external router can be
# configured via the dashboard. When using specific plugins the
# profile_support can be turned on if needed.

View File

@ -12,7 +12,10 @@
# License for the specific language governing permissions and limitations
# under the License.
from django.test.utils import override_settings
from openstack_dashboard import api
from openstack_dashboard import policy
from openstack_dashboard.test import helpers as test
@ -279,3 +282,67 @@ class NeutronApiTests(test.APITestCase):
api.neutron.is_extension_supported(self.request, 'quotas'))
self.assertFalse(
api.neutron.is_extension_supported(self.request, 'doesntexist'))
@override_settings(OPENSTACK_NEUTRON_NETWORK={'enable_distributed_router':
True},
POLICY_CHECK_FUNCTION=None)
def _test_get_dvr_permission_dvr_supported(self, dvr_enabled):
neutronclient = self.stub_neutronclient()
extensions = self.api_extensions.list()
if not dvr_enabled:
extensions = [ext for ext in extensions if ext['alias'] != 'dvr']
neutronclient.list_extensions() \
.AndReturn({'extensions': extensions})
self.mox.ReplayAll()
self.assertEqual(dvr_enabled,
api.neutron.get_dvr_permission(self.request, 'get'))
def test_get_dvr_permission_dvr_supported(self):
self._test_get_dvr_permission_dvr_supported(dvr_enabled=True, )
def test_get_dvr_permission_dvr_not_supported(self):
self._test_get_dvr_permission_dvr_supported(dvr_enabled=False)
@override_settings(OPENSTACK_NEUTRON_NETWORK={'enable_distributed_router':
True},
POLICY_CHECK_FUNCTION=policy.check)
def _test_get_dvr_permission_with_policy_check(self, policy_check_allowed,
operation):
self.mox.StubOutWithMock(policy, 'check')
if operation == "create":
role = (("network", "create_router:distributed"),)
elif operation == "get":
role = (("network", "get_router:distributed"),)
policy.check(role, self.request).AndReturn(policy_check_allowed)
if policy_check_allowed:
neutronclient = self.stub_neutronclient()
neutronclient.list_extensions() \
.AndReturn({'extensions': self.api_extensions.list()})
self.mox.ReplayAll()
self.assertEqual(policy_check_allowed,
api.neutron.get_dvr_permission(self.request,
operation))
def test_get_dvr_permission_with_policy_check_allowed(self):
self._test_get_dvr_permission_with_policy_check(True, "get")
def test_get_dvr_permission_with_policy_check_disallowed(self):
self._test_get_dvr_permission_with_policy_check(False, "get")
def test_get_dvr_permission_create_with_policy_check_allowed(self):
self._test_get_dvr_permission_with_policy_check(True, "create")
def test_get_dvr_permission_create_with_policy_check_disallowed(self):
self._test_get_dvr_permission_with_policy_check(False, "create")
@override_settings(OPENSTACK_NEUTRON_NETWORK={'enable_distributed_router':
False})
def test_get_dvr_permission_dvr_disabled_by_config(self):
self.assertFalse(api.neutron.get_dvr_permission(self.request, 'get'))
@override_settings(OPENSTACK_NEUTRON_NETWORK={'enable_distributed_router':
True})
def test_get_dvr_permission_dvr_unsupported_operation(self):
self.assertRaises(ValueError,
api.neutron.get_dvr_permission,
self.request, 'unSupported')

View File

@ -109,6 +109,7 @@ OPENSTACK_NEUTRON_NETWORK = {
'enable_quotas': False, # Enabled in specific tests only
'enable_vpn': True,
'profile_support': None,
'enable_distributed_router': False,
# 'profile_support': 'cisco'
}

View File

@ -568,9 +568,14 @@ def data(TEST):
extension_3 = {"name": "Provider network",
"alias": "provider",
"description": "Provider network extension"}
extension_4 = {"name": "Distributed Virtual Router",
"alias": "dvr",
"description":
"Enables configuration of Distributed Virtual Routers."}
TEST.api_extensions.add(extension_1)
TEST.api_extensions.add(extension_2)
TEST.api_extensions.add(extension_3)
TEST.api_extensions.add(extension_4)
# 1st agent.
agent_dict = {"binary": "neutron-openvswitch-agent",