diff --git a/openstack_dashboard/api/neutron.py b/openstack_dashboard/api/neutron.py
index c7ac0709d8..62895423c6 100644
--- a/openstack_dashboard/api/neutron.py
+++ b/openstack_dashboard/api/neutron.py
@@ -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
diff --git a/openstack_dashboard/conf/neutron_policy.json b/openstack_dashboard/conf/neutron_policy.json
index 3947312cee..d4c631fc60 100644
--- a/openstack_dashboard/conf/neutron_policy.json
+++ b/openstack_dashboard/conf/neutron_policy.json
@@ -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",
diff --git a/openstack_dashboard/dashboards/admin/routers/tables.py b/openstack_dashboard/dashboards/admin/routers/tables.py
index 0ca666396f..2b75717a55 100644
--- a/openstack_dashboard/dashboards/admin/routers/tables.py
+++ b/openstack_dashboard/dashboards/admin/routers/tables.py
@@ -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')
diff --git a/openstack_dashboard/dashboards/admin/routers/templates/routers/_detail_overview.html b/openstack_dashboard/dashboards/admin/routers/templates/routers/_detail_overview.html
index db0dc9297c..20b9773917 100644
--- a/openstack_dashboard/dashboards/admin/routers/templates/routers/_detail_overview.html
+++ b/openstack_dashboard/dashboards/admin/routers/templates/routers/_detail_overview.html
@@ -12,6 +12,10 @@
{{ router.tenant_id }}
{% trans "Status" %}
{{ router.status|capfirst }}
+ {% if dvr_supported %}
+ {% trans "Distributed" %}
+ {{ router.distributed|yesno|capfirst }}
+ {% endif %}
{% if router.external_gateway_info %}
{% trans "External Gateway Information" %}
{% trans "Connected External Network" %}:
diff --git a/openstack_dashboard/dashboards/project/routers/forms.py b/openstack_dashboard/dashboards/project/routers/forms.py
index 2cebda0668..c1bbc77171 100644
--- a/openstack_dashboard/dashboards/project/routers/forms.py
+++ b/openstack_dashboard/dashboards/project/routers/forms.py
@@ -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
diff --git a/openstack_dashboard/dashboards/project/routers/tables.py b/openstack_dashboard/dashboards/project/routers/tables.py
index 65fafc8c4b..12ee44ddea 100644
--- a/openstack_dashboard/dashboards/project/routers/tables.py
+++ b/openstack_dashboard/dashboards/project/routers/tables.py
@@ -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
diff --git a/openstack_dashboard/dashboards/project/routers/templates/routers/_detail_overview.html b/openstack_dashboard/dashboards/project/routers/templates/routers/_detail_overview.html
index 226939f653..a40a21ad09 100644
--- a/openstack_dashboard/dashboards/project/routers/templates/routers/_detail_overview.html
+++ b/openstack_dashboard/dashboards/project/routers/templates/routers/_detail_overview.html
@@ -10,6 +10,10 @@
{{ router.id|default:_("None") }}
{% trans "Status" %}
{{ router.status|default:_("Unknown") }}
+ {% if dvr_supported %}
+ {% trans "Distributed" %}
+ {{ router.distributed|yesno|capfirst }}
+ {% endif %}
{% if router.external_gateway_info %}
{% trans "External Gateway Information" %}
{% trans "Connected External Network" %}:
diff --git a/openstack_dashboard/dashboards/project/routers/tests.py b/openstack_dashboard/dashboards/project/routers/tests.py
index 9234761514..9ac43998b1 100644
--- a/openstack_dashboard/dashboards/project/routers/tests.py
+++ b/openstack_dashboard/dashboards/project/routers/tests.py
@@ -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)
diff --git a/openstack_dashboard/dashboards/project/routers/views.py b/openstack_dashboard/dashboards/project/routers/views.py
index 68977115d0..bbf1b2f68e 100644
--- a/openstack_dashboard/dashboards/project/routers/views.py
+++ b/openstack_dashboard/dashboards/project/routers/views.py
@@ -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):
diff --git a/openstack_dashboard/local/local_settings.py.example b/openstack_dashboard/local/local_settings.py.example
index fffd8daee2..cf3959e003 100644
--- a/openstack_dashboard/local/local_settings.py.example
+++ b/openstack_dashboard/local/local_settings.py.example
@@ -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.
diff --git a/openstack_dashboard/test/api_tests/neutron_tests.py b/openstack_dashboard/test/api_tests/neutron_tests.py
index 9746d8cfea..59def8eb6f 100644
--- a/openstack_dashboard/test/api_tests/neutron_tests.py
+++ b/openstack_dashboard/test/api_tests/neutron_tests.py
@@ -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')
diff --git a/openstack_dashboard/test/settings.py b/openstack_dashboard/test/settings.py
index bb33eb4d0e..4a7ad403d1 100644
--- a/openstack_dashboard/test/settings.py
+++ b/openstack_dashboard/test/settings.py
@@ -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'
}
diff --git a/openstack_dashboard/test/test_data/neutron_data.py b/openstack_dashboard/test/test_data/neutron_data.py
index f3d900f37d..09865b2ffe 100644
--- a/openstack_dashboard/test/test_data/neutron_data.py
+++ b/openstack_dashboard/test/test_data/neutron_data.py
@@ -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",