Add Extra Routes to Router Tabs View
Add support to neutron extra routes to routers views. Change-Id: Id5b5c7494d903fff0fdb5cba9297dbed5cf3d62e Closes-Bug: #1396616
This commit is contained in:
parent
e2b4a21fda
commit
440bd19040
@ -142,6 +142,15 @@ class Router(NeutronAPIDictWrapper):
|
|||||||
super(Router, self).__init__(apiresource)
|
super(Router, self).__init__(apiresource)
|
||||||
|
|
||||||
|
|
||||||
|
class RouterStaticRoute(NeutronAPIDictWrapper):
|
||||||
|
"""Wrapper for neutron routes extra route."""
|
||||||
|
|
||||||
|
def __init__(self, route):
|
||||||
|
super(RouterStaticRoute, self).__init__(route)
|
||||||
|
# Horizon references id property for table operations
|
||||||
|
self.id = route['nexthop'] + ":" + route['destination']
|
||||||
|
|
||||||
|
|
||||||
class SecurityGroup(NeutronAPIDictWrapper):
|
class SecurityGroup(NeutronAPIDictWrapper):
|
||||||
# Required attributes: id, name, description, tenant_id, rules
|
# Required attributes: id, name, description, tenant_id, rules
|
||||||
|
|
||||||
@ -896,6 +905,39 @@ def router_remove_gateway(request, router_id):
|
|||||||
neutronclient(request).remove_gateway_router(router_id)
|
neutronclient(request).remove_gateway_router(router_id)
|
||||||
|
|
||||||
|
|
||||||
|
def router_static_route_list(request, router_id=None):
|
||||||
|
router = router_get(request, router_id)
|
||||||
|
try:
|
||||||
|
routes = [RouterStaticRoute(r) for r in router.routes]
|
||||||
|
except AttributeError:
|
||||||
|
LOG.debug("router_static_route_list(): router_id=%s, "
|
||||||
|
"router=%s", (router_id, router))
|
||||||
|
return []
|
||||||
|
return routes
|
||||||
|
|
||||||
|
|
||||||
|
def router_static_route_remove(request, router_id, route_ids):
|
||||||
|
currentroutes = router_static_route_list(request, router_id=router_id)
|
||||||
|
newroutes = []
|
||||||
|
for oldroute in currentroutes:
|
||||||
|
if oldroute.id not in route_ids:
|
||||||
|
newroutes.append({'nexthop': oldroute.nexthop,
|
||||||
|
'destination': oldroute.destination})
|
||||||
|
body = {'routes': newroutes}
|
||||||
|
new = router_update(request, router_id, **body)
|
||||||
|
return new
|
||||||
|
|
||||||
|
|
||||||
|
def router_static_route_add(request, router_id, newroute):
|
||||||
|
body = {}
|
||||||
|
currentroutes = router_static_route_list(request, router_id=router_id)
|
||||||
|
body['routes'] = [newroute] + [{'nexthop': r.nexthop,
|
||||||
|
'destination': r.destination}
|
||||||
|
for r in currentroutes]
|
||||||
|
new = router_update(request, router_id, **body)
|
||||||
|
return new
|
||||||
|
|
||||||
|
|
||||||
def tenant_quota_get(request, tenant_id):
|
def tenant_quota_get(request, tenant_id):
|
||||||
return base.QuotaSet(neutronclient(request).show_quota(tenant_id)['quota'])
|
return base.QuotaSet(neutronclient(request).show_quota(tenant_id)['quota'])
|
||||||
|
|
||||||
|
@ -0,0 +1,27 @@
|
|||||||
|
# Copyright 2015, Thales Services SAS
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# 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 openstack_dashboard.dashboards.project.routers.extensions.extraroutes\
|
||||||
|
import tables as routes_table
|
||||||
|
|
||||||
|
|
||||||
|
class AdminRouterRoutesTable(routes_table.ExtraRoutesTable):
|
||||||
|
|
||||||
|
class Meta(object):
|
||||||
|
# Redifine Meta class to disable action (admin)
|
||||||
|
name = "extra_routes"
|
||||||
|
verbose_name = _("Static Routes")
|
@ -12,7 +12,11 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
from openstack_dashboard.dashboards.admin.routers.extensions.extraroutes\
|
||||||
|
import tables as ertbl
|
||||||
from openstack_dashboard.dashboards.admin.routers.ports import tables as ptbl
|
from openstack_dashboard.dashboards.admin.routers.ports import tables as ptbl
|
||||||
|
from openstack_dashboard.dashboards.project.routers.extensions.extraroutes\
|
||||||
|
import tabs as er_tabs
|
||||||
from openstack_dashboard.dashboards.project.routers.extensions.routerrules\
|
from openstack_dashboard.dashboards.project.routers.extensions.routerrules\
|
||||||
import tabs as rr_tabs
|
import tabs as rr_tabs
|
||||||
from openstack_dashboard.dashboards.project.routers import tabs as r_tabs
|
from openstack_dashboard.dashboards.project.routers import tabs as r_tabs
|
||||||
@ -22,11 +26,15 @@ class OverviewTab(r_tabs.OverviewTab):
|
|||||||
template_name = "project/routers/_detail_overview.html"
|
template_name = "project/routers/_detail_overview.html"
|
||||||
|
|
||||||
|
|
||||||
|
class ExtraRoutesTab(er_tabs.ExtraRoutesTab):
|
||||||
|
table_classes = (ertbl.AdminRouterRoutesTable,)
|
||||||
|
|
||||||
|
|
||||||
class InterfacesTab(r_tabs.InterfacesTab):
|
class InterfacesTab(r_tabs.InterfacesTab):
|
||||||
table_classes = (ptbl.PortsTable,)
|
table_classes = (ptbl.PortsTable,)
|
||||||
|
|
||||||
|
|
||||||
class RouterDetailTabs(r_tabs.RouterDetailTabs):
|
class RouterDetailTabs(r_tabs.RouterDetailTabs):
|
||||||
tabs = (OverviewTab, InterfacesTab, rr_tabs.RulesGridTab,
|
tabs = (OverviewTab, InterfacesTab, ExtraRoutesTab, rr_tabs.RulesGridTab,
|
||||||
rr_tabs.RouterRulesTab)
|
rr_tabs.RouterRulesTab)
|
||||||
sticky = True
|
sticky = True
|
||||||
|
@ -162,3 +162,9 @@ class RouterTests(test.BaseAdminViewTests, r_test.RouterTests):
|
|||||||
self.assertNoFormErrors(res)
|
self.assertNoFormErrors(res)
|
||||||
self.assertMessageCount(response=res, success=1)
|
self.assertMessageCount(response=res, success=1)
|
||||||
self.assertIn('Deleted Router: ' + router.name, res.content)
|
self.assertIn('Deleted Router: ' + router.name, res.content)
|
||||||
|
|
||||||
|
|
||||||
|
class RouterRouteTest(test.BaseAdminViewTests, r_test.RouterRouteTests):
|
||||||
|
DASHBOARD = 'admin'
|
||||||
|
INDEX_URL = reverse('horizon:%s:routers:index' % DASHBOARD)
|
||||||
|
DETAIL_PATH = 'horizon:%s:routers:detail' % DASHBOARD
|
||||||
|
@ -0,0 +1,60 @@
|
|||||||
|
# Copyright 2015, Thales Services SAS
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from django.core.urlresolvers import reverse
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from horizon import exceptions
|
||||||
|
from horizon import forms
|
||||||
|
from horizon import messages
|
||||||
|
|
||||||
|
from neutronclient.common import exceptions as neutron_exc
|
||||||
|
|
||||||
|
from openstack_dashboard.api import neutron as api
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class AddRouterRoute(forms.SelfHandlingForm):
|
||||||
|
destination = forms.IPField(label=_("Destination CIDR"), mask=True)
|
||||||
|
nexthop = forms.IPField(label=_("Next Hop"))
|
||||||
|
failure_url = 'horizon:project:routers:detail'
|
||||||
|
|
||||||
|
def handle(self, request, data, **kwargs):
|
||||||
|
router_id = self.initial['router_id']
|
||||||
|
try:
|
||||||
|
route = {'nexthop': data['nexthop'],
|
||||||
|
'destination': data['destination']}
|
||||||
|
api.router_static_route_add(request,
|
||||||
|
router_id,
|
||||||
|
route)
|
||||||
|
msg = _('Static route added')
|
||||||
|
LOG.debug(msg)
|
||||||
|
messages.success(request, msg)
|
||||||
|
return True
|
||||||
|
except neutron_exc.BadRequest as e:
|
||||||
|
msg = (_('Invalid format for routes : %s') % e)
|
||||||
|
LOG.info(msg)
|
||||||
|
messages.error(request, msg)
|
||||||
|
redirect = reverse(self.failure_url, args=[router_id])
|
||||||
|
exceptions.handle(request, msg, redirect=redirect)
|
||||||
|
except Exception as e:
|
||||||
|
msg = (_('Failed to add route : %s') % e)
|
||||||
|
LOG.info(msg)
|
||||||
|
messages.error(request, msg)
|
||||||
|
redirect = reverse(self.failure_url, args=[router_id])
|
||||||
|
exceptions.handle(request, msg, redirect=redirect)
|
@ -0,0 +1,77 @@
|
|||||||
|
# Copyright 2015, Thales Services SAS
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# 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.core.urlresolvers import reverse
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
from django.utils.translation import ungettext_lazy
|
||||||
|
|
||||||
|
from openstack_dashboard.api import neutron as api
|
||||||
|
from openstack_dashboard import policy
|
||||||
|
|
||||||
|
from horizon import tables
|
||||||
|
|
||||||
|
|
||||||
|
class AddRouterRoute(policy.PolicyTargetMixin, tables.LinkAction):
|
||||||
|
name = "create"
|
||||||
|
verbose_name = _("Add Static Route")
|
||||||
|
url = "horizon:project:routers:addrouterroute"
|
||||||
|
classes = ("ajax-modal",)
|
||||||
|
icon = "plus"
|
||||||
|
policy_rules = (("network", "update_router"),)
|
||||||
|
|
||||||
|
def get_link_url(self, datum=None):
|
||||||
|
router_id = self.table.kwargs['router_id']
|
||||||
|
return reverse(self.url, args=(router_id,))
|
||||||
|
|
||||||
|
|
||||||
|
class RemoveRouterRoute(policy.PolicyTargetMixin, tables.DeleteAction):
|
||||||
|
@staticmethod
|
||||||
|
def action_present(count):
|
||||||
|
return ungettext_lazy(
|
||||||
|
u"Delete Static Route",
|
||||||
|
u"Delete Static Routes",
|
||||||
|
count
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def action_past(count):
|
||||||
|
return ungettext_lazy(
|
||||||
|
u"Deleted Static Route",
|
||||||
|
u"Deleted Static Routes",
|
||||||
|
count
|
||||||
|
)
|
||||||
|
failure_url = 'horizon:project:routers:detail'
|
||||||
|
policy_rules = (("network", "update_router"),)
|
||||||
|
|
||||||
|
def delete(self, request, obj_id):
|
||||||
|
router_id = self.table.kwargs['router_id']
|
||||||
|
api.router_static_route_remove(request, router_id, [obj_id])
|
||||||
|
|
||||||
|
|
||||||
|
class ExtraRoutesTable(tables.DataTable):
|
||||||
|
destination = tables.Column("destination",
|
||||||
|
verbose_name=_("Destination CIDR"))
|
||||||
|
nexthop = tables.Column("nexthop", verbose_name=_("Next Hop"))
|
||||||
|
|
||||||
|
def get_object_display(self, datum):
|
||||||
|
"""Display ExtraRoutes when deleted."""
|
||||||
|
return (super(ExtraRoutesTable, self).get_object_display(datum)
|
||||||
|
or datum.destination + " -> " + datum.nexthop)
|
||||||
|
|
||||||
|
class Meta(object):
|
||||||
|
name = "extra_routes"
|
||||||
|
verbose_name = _("Static Routes")
|
||||||
|
table_actions = (AddRouterRoute, RemoveRouterRoute)
|
||||||
|
row_actions = (RemoveRouterRoute, )
|
@ -0,0 +1,49 @@
|
|||||||
|
# Copyright 2015, Thales Services SAS
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from horizon import tabs
|
||||||
|
|
||||||
|
from openstack_dashboard.api import neutron as api
|
||||||
|
from openstack_dashboard.dashboards.project.routers.extensions.extraroutes\
|
||||||
|
import tables as ertbl
|
||||||
|
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class ExtraRoutesTab(tabs.TableTab):
|
||||||
|
table_classes = (ertbl.ExtraRoutesTable,)
|
||||||
|
name = _("Static Routes")
|
||||||
|
slug = "extraroutes"
|
||||||
|
template_name = "horizon/common/_detail_table.html"
|
||||||
|
|
||||||
|
def allowed(self, request):
|
||||||
|
try:
|
||||||
|
return api.is_extension_supported(request, 'extraroute')
|
||||||
|
except Exception:
|
||||||
|
LOG.info(_("Failed to check if Neutron extraroute extension is "
|
||||||
|
"supported"))
|
||||||
|
return False
|
||||||
|
|
||||||
|
def get_extra_routes_data(self):
|
||||||
|
try:
|
||||||
|
extraroutes = getattr(self.tab_group.kwargs['router'], 'routes')
|
||||||
|
except AttributeError:
|
||||||
|
extraroutes = []
|
||||||
|
return [api.RouterStaticRoute(r) for r in extraroutes]
|
@ -0,0 +1,59 @@
|
|||||||
|
# Copyright 2015, Thales Services SAS
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from django.core.urlresolvers import reverse
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from horizon import exceptions
|
||||||
|
from horizon import forms
|
||||||
|
from horizon.utils import memoized
|
||||||
|
|
||||||
|
from openstack_dashboard import api
|
||||||
|
from openstack_dashboard.dashboards.project.routers.extensions.extraroutes\
|
||||||
|
import forms as erforms
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class AddRouterRouteView(forms.ModalFormView):
|
||||||
|
form_class = erforms.AddRouterRoute
|
||||||
|
template_name = 'project/routers/extensions/routerroutes/create.html'
|
||||||
|
url = 'horizon:project:routers:detail'
|
||||||
|
|
||||||
|
def get_success_url(self):
|
||||||
|
return reverse(self.url,
|
||||||
|
args=(self.kwargs['router_id'],))
|
||||||
|
|
||||||
|
@memoized.memoized_method
|
||||||
|
def get_object(self):
|
||||||
|
try:
|
||||||
|
router_id = self.kwargs["router_id"]
|
||||||
|
return api.neutron.router_get(self.request, router_id)
|
||||||
|
except Exception:
|
||||||
|
redirect = reverse(self.url, args=[router_id])
|
||||||
|
msg = _("Unable to retrieve router.")
|
||||||
|
exceptions.handle(self.request, msg, redirect=redirect)
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super(AddRouterRouteView, self).get_context_data(**kwargs)
|
||||||
|
context['router'] = self.get_object()
|
||||||
|
return context
|
||||||
|
|
||||||
|
def get_initial(self):
|
||||||
|
router = self.get_object()
|
||||||
|
return {"router_id": self.kwargs['router_id'],
|
||||||
|
"router_name": router.name}
|
@ -16,6 +16,8 @@ from django.utils.translation import ugettext_lazy as _
|
|||||||
|
|
||||||
from horizon import tabs
|
from horizon import tabs
|
||||||
|
|
||||||
|
from openstack_dashboard.dashboards.project.routers.extensions.extraroutes\
|
||||||
|
import tabs as er_tabs
|
||||||
from openstack_dashboard.dashboards.project.routers.extensions.routerrules\
|
from openstack_dashboard.dashboards.project.routers.extensions.routerrules\
|
||||||
import tabs as rr_tabs
|
import tabs as rr_tabs
|
||||||
from openstack_dashboard.dashboards.project.routers.ports import tables as ptbl
|
from openstack_dashboard.dashboards.project.routers.ports import tables as ptbl
|
||||||
@ -42,6 +44,6 @@ class InterfacesTab(tabs.TableTab):
|
|||||||
|
|
||||||
class RouterDetailTabs(tabs.TabGroup):
|
class RouterDetailTabs(tabs.TabGroup):
|
||||||
slug = "router_details"
|
slug = "router_details"
|
||||||
tabs = (OverviewTab, InterfacesTab, rr_tabs.RulesGridTab,
|
tabs = (OverviewTab, InterfacesTab, er_tabs.ExtraRoutesTab,
|
||||||
rr_tabs.RouterRulesTab)
|
rr_tabs.RulesGridTab, rr_tabs.RouterRulesTab)
|
||||||
sticky = True
|
sticky = True
|
||||||
|
@ -0,0 +1,29 @@
|
|||||||
|
{% extends "horizon/common/_modal_form.html" %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% load url from future %}
|
||||||
|
|
||||||
|
{% block form_id %}add_routerroute_form{% endblock %}
|
||||||
|
{% block form_action %}{% url 'horizon:project:routers:addrouterroute' router.id %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block modal-header %}{% trans "Add Static Route" %}{% endblock %}
|
||||||
|
|
||||||
|
{% block modal-body %}
|
||||||
|
<div class="left">
|
||||||
|
<fieldset>
|
||||||
|
{% include "horizon/common/_form_fields.html" %}
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
<div class="right">
|
||||||
|
<h3>{% trans "Description:" %}</h3>
|
||||||
|
<p>
|
||||||
|
{% trans "Add static route to the router." %}<br/>
|
||||||
|
{% trans "Next Hop IP must be a part of one of the subnets to which the router interfaces are connected." %}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block modal-footer %}
|
||||||
|
<input class="btn btn-primary pull-right" type="submit" value="{% trans "Add route" %}" />
|
||||||
|
<a href="{% url 'horizon:project:routers:index' %}" class="btn btn-default secondary cancel close">{% trans "Cancel" %}</a>
|
||||||
|
{% endblock %}
|
@ -0,0 +1,11 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% block title %}{% trans "Add Router Route" %}{% endblock %}
|
||||||
|
|
||||||
|
{% block page_header %}
|
||||||
|
{% include "horizon/common/_page_header.html" with title=_("Add Router Route") %}
|
||||||
|
{% endblock page_header %}
|
||||||
|
|
||||||
|
{% block main %}
|
||||||
|
{% include "project/routers/extensions/routerroutes/_create.html" %}
|
||||||
|
{% endblock %}
|
@ -30,9 +30,11 @@ from openstack_dashboard.usage import quotas
|
|||||||
class RouterMixin(object):
|
class RouterMixin(object):
|
||||||
@test.create_stubs({
|
@test.create_stubs({
|
||||||
api.neutron: ('router_get', 'port_list',
|
api.neutron: ('router_get', 'port_list',
|
||||||
'network_get'),
|
'network_get', 'is_extension_supported'),
|
||||||
})
|
})
|
||||||
def _get_detail(self, router):
|
def _get_detail(self, router, extraroute=True):
|
||||||
|
api.neutron.is_extension_supported(IsA(http.HttpRequest), 'extraroute')\
|
||||||
|
.MultipleTimes().AndReturn(extraroute)
|
||||||
api.neutron.router_get(IsA(http.HttpRequest), router.id)\
|
api.neutron.router_get(IsA(http.HttpRequest), router.id)\
|
||||||
.AndReturn(router)
|
.AndReturn(router)
|
||||||
api.neutron.port_list(IsA(http.HttpRequest),
|
api.neutron.port_list(IsA(http.HttpRequest),
|
||||||
@ -750,11 +752,14 @@ class RouterRuleTests(RouterMixin, test.TestCase):
|
|||||||
self._test_router_addrouterrule(raise_error=True)
|
self._test_router_addrouterrule(raise_error=True)
|
||||||
|
|
||||||
@test.create_stubs({api.neutron: ('router_get', 'router_update',
|
@test.create_stubs({api.neutron: ('router_get', 'router_update',
|
||||||
'port_list', 'network_get')})
|
'port_list', 'network_get',
|
||||||
|
'is_extension_supported')})
|
||||||
def test_router_removerouterrule(self):
|
def test_router_removerouterrule(self):
|
||||||
pre_router = self.routers_with_rules.first()
|
pre_router = self.routers_with_rules.first()
|
||||||
post_router = copy.deepcopy(pre_router)
|
post_router = copy.deepcopy(pre_router)
|
||||||
rule = post_router['router_rules'].pop()
|
rule = post_router['router_rules'].pop()
|
||||||
|
api.neutron.is_extension_supported(IsA(http.HttpRequest), 'extraroute')\
|
||||||
|
.AndReturn(False)
|
||||||
api.neutron.router_get(IsA(http.HttpRequest),
|
api.neutron.router_get(IsA(http.HttpRequest),
|
||||||
pre_router.id).AndReturn(pre_router)
|
pre_router.id).AndReturn(pre_router)
|
||||||
params = {}
|
params = {}
|
||||||
@ -779,7 +784,8 @@ class RouterRuleTests(RouterMixin, test.TestCase):
|
|||||||
|
|
||||||
@test.create_stubs({api.neutron: ('router_get', 'router_update',
|
@test.create_stubs({api.neutron: ('router_get', 'router_update',
|
||||||
'network_list', 'port_list',
|
'network_list', 'port_list',
|
||||||
'network_get')})
|
'network_get',
|
||||||
|
'is_extension_supported')})
|
||||||
def test_router_resetrouterrules(self):
|
def test_router_resetrouterrules(self):
|
||||||
pre_router = self.routers_with_rules.first()
|
pre_router = self.routers_with_rules.first()
|
||||||
post_router = copy.deepcopy(pre_router)
|
post_router = copy.deepcopy(pre_router)
|
||||||
@ -787,6 +793,8 @@ class RouterRuleTests(RouterMixin, test.TestCase):
|
|||||||
'action': 'permit', 'nexthops': [], 'id': '2'}]
|
'action': 'permit', 'nexthops': [], 'id': '2'}]
|
||||||
del post_router['router_rules'][:]
|
del post_router['router_rules'][:]
|
||||||
post_router['router_rules'].extend(default_rules)
|
post_router['router_rules'].extend(default_rules)
|
||||||
|
api.neutron.is_extension_supported(IsA(http.HttpRequest), 'extraroute')\
|
||||||
|
.AndReturn(False)
|
||||||
api.neutron.router_get(IsA(http.HttpRequest),
|
api.neutron.router_get(IsA(http.HttpRequest),
|
||||||
pre_router.id).AndReturn(post_router)
|
pre_router.id).AndReturn(post_router)
|
||||||
params = {}
|
params = {}
|
||||||
@ -810,6 +818,96 @@ class RouterRuleTests(RouterMixin, test.TestCase):
|
|||||||
self.assertNoFormErrors(res)
|
self.assertNoFormErrors(res)
|
||||||
|
|
||||||
|
|
||||||
|
class RouterRouteTests(RouterMixin, test.TestCase):
|
||||||
|
DASHBOARD = 'project'
|
||||||
|
INDEX_URL = reverse('horizon:%s:routers:index' % DASHBOARD)
|
||||||
|
DETAIL_PATH = 'horizon:%s:routers:detail' % DASHBOARD
|
||||||
|
|
||||||
|
def test_extension_hides_without_routes(self):
|
||||||
|
router = self.routers_with_routes.first()
|
||||||
|
res = self._get_detail(router, extraroute=False)
|
||||||
|
|
||||||
|
self.assertTemplateUsed(res, '%s/routers/detail.html' % self.DASHBOARD)
|
||||||
|
self.assertNotIn('extra_routes_table', res.context)
|
||||||
|
|
||||||
|
def test_routerroute_detail(self):
|
||||||
|
router = self.routers_with_routes.first()
|
||||||
|
res = self._get_detail(router, extraroute=True)
|
||||||
|
|
||||||
|
self.assertTemplateUsed(res, '%s/routers/detail.html' % self.DASHBOARD)
|
||||||
|
routes = res.context['extra_routes_table'].data
|
||||||
|
routes_dict = [r._apidict for r in routes]
|
||||||
|
self.assertItemsEqual(routes_dict, router['routes'])
|
||||||
|
|
||||||
|
@test.create_stubs({api.neutron: ('router_get', 'router_update')})
|
||||||
|
def _test_router_addrouterroute(self, raise_error=False):
|
||||||
|
pre_router = self.routers_with_routes.first()
|
||||||
|
post_router = copy.deepcopy(pre_router)
|
||||||
|
route = {'nexthop': '10.0.0.5', 'destination': '40.0.1.0/24'}
|
||||||
|
post_router['routes'].insert(0, route)
|
||||||
|
api.neutron.router_get(IsA(http.HttpRequest), pre_router.id)\
|
||||||
|
.MultipleTimes().AndReturn(pre_router)
|
||||||
|
params = {}
|
||||||
|
params['routes'] = post_router['routes']
|
||||||
|
router_update = api.neutron.router_update(IsA(http.HttpRequest),
|
||||||
|
pre_router.id, **params)
|
||||||
|
if raise_error:
|
||||||
|
router_update.AndRaise(self.exceptions.neutron)
|
||||||
|
else:
|
||||||
|
router_update.AndReturn({'router': post_router})
|
||||||
|
self.mox.ReplayAll()
|
||||||
|
|
||||||
|
form_data = copy.deepcopy(route)
|
||||||
|
form_data['router_id'] = pre_router.id
|
||||||
|
url = reverse('horizon:%s:routers:addrouterroute' % self.DASHBOARD,
|
||||||
|
args=[pre_router.id])
|
||||||
|
res = self.client.post(url, form_data)
|
||||||
|
self.assertNoFormErrors(res)
|
||||||
|
detail_url = reverse(self.DETAIL_PATH, args=[pre_router.id])
|
||||||
|
self.assertRedirectsNoFollow(res, detail_url)
|
||||||
|
|
||||||
|
def test_router_addrouterroute(self):
|
||||||
|
if self.DASHBOARD == 'project':
|
||||||
|
self._test_router_addrouterroute()
|
||||||
|
self.assertMessageCount(success=1)
|
||||||
|
|
||||||
|
def test_router_addrouterroute_exception(self):
|
||||||
|
if self.DASHBOARD == 'project':
|
||||||
|
self._test_router_addrouterroute(raise_error=True)
|
||||||
|
self.assertMessageCount(error=1)
|
||||||
|
|
||||||
|
@test.create_stubs({api.neutron: ('router_get', 'router_update',
|
||||||
|
'network_get', 'port_list',
|
||||||
|
'is_extension_supported')})
|
||||||
|
def test_router_removeroute(self):
|
||||||
|
if self.DASHBOARD == 'admin':
|
||||||
|
return
|
||||||
|
pre_router = self.routers_with_routes.first()
|
||||||
|
post_router = copy.deepcopy(pre_router)
|
||||||
|
route = post_router['routes'].pop()
|
||||||
|
api.neutron.is_extension_supported(IsA(http.HttpRequest), 'extraroute')\
|
||||||
|
.MultipleTimes().AndReturn(True)
|
||||||
|
api.neutron.router_get(IsA(http.HttpRequest),
|
||||||
|
pre_router.id).AndReturn(pre_router)
|
||||||
|
params = {}
|
||||||
|
params['routes'] = post_router['routes']
|
||||||
|
api.neutron.router_get(IsA(http.HttpRequest),
|
||||||
|
pre_router.id).AndReturn(pre_router)
|
||||||
|
router_update = api.neutron.router_update(IsA(http.HttpRequest),
|
||||||
|
pre_router.id, **params)
|
||||||
|
router_update.AndReturn({'router': post_router})
|
||||||
|
api.neutron.port_list(IsA(http.HttpRequest),
|
||||||
|
device_id=pre_router.id)\
|
||||||
|
.AndReturn([self.ports.first()])
|
||||||
|
self._mock_external_network_get(pre_router)
|
||||||
|
self.mox.ReplayAll()
|
||||||
|
form_route_id = route['nexthop'] + ":" + route['destination']
|
||||||
|
form_data = {'action': 'extra_routes__delete__%s' % form_route_id}
|
||||||
|
url = reverse(self.DETAIL_PATH, args=[pre_router.id])
|
||||||
|
res = self.client.post(url, form_data)
|
||||||
|
self.assertNoFormErrors(res)
|
||||||
|
|
||||||
|
|
||||||
class RouterViewTests(RouterMixin, test.TestCase):
|
class RouterViewTests(RouterMixin, test.TestCase):
|
||||||
DASHBOARD = 'project'
|
DASHBOARD = 'project'
|
||||||
INDEX_URL = reverse('horizon:%s:routers:index' % DASHBOARD)
|
INDEX_URL = reverse('horizon:%s:routers:index' % DASHBOARD)
|
||||||
|
@ -15,6 +15,8 @@
|
|||||||
from django.conf.urls import patterns
|
from django.conf.urls import patterns
|
||||||
from django.conf.urls import url
|
from django.conf.urls import url
|
||||||
|
|
||||||
|
from openstack_dashboard.dashboards.project.routers.extensions.extraroutes\
|
||||||
|
import views as er_views
|
||||||
from openstack_dashboard.dashboards.project.routers.extensions.routerrules\
|
from openstack_dashboard.dashboards.project.routers.extensions.routerrules\
|
||||||
import views as rr_views
|
import views as rr_views
|
||||||
from openstack_dashboard.dashboards.project.routers.ports \
|
from openstack_dashboard.dashboards.project.routers.ports \
|
||||||
@ -41,6 +43,9 @@ urlpatterns = patterns(
|
|||||||
url(ROUTER_URL % 'addrouterrule',
|
url(ROUTER_URL % 'addrouterrule',
|
||||||
rr_views.AddRouterRuleView.as_view(),
|
rr_views.AddRouterRuleView.as_view(),
|
||||||
name='addrouterrule'),
|
name='addrouterrule'),
|
||||||
|
url(ROUTER_URL % 'addrouterroute',
|
||||||
|
er_views.AddRouterRouteView.as_view(),
|
||||||
|
name='addrouterroute'),
|
||||||
url(ROUTER_URL % 'setgateway',
|
url(ROUTER_URL % 'setgateway',
|
||||||
port_views.SetGatewayView.as_view(),
|
port_views.SetGatewayView.as_view(),
|
||||||
name='setgateway'),
|
name='setgateway'),
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
import copy
|
||||||
|
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
@ -289,6 +290,50 @@ class NeutronApiTests(test.APITestCase):
|
|||||||
self.assertFalse(
|
self.assertFalse(
|
||||||
api.neutron.is_extension_supported(self.request, 'doesntexist'))
|
api.neutron.is_extension_supported(self.request, 'doesntexist'))
|
||||||
|
|
||||||
|
def test_router_static_route_list(self):
|
||||||
|
router = {'router': self.api_routers_with_routes.first()}
|
||||||
|
router_id = self.api_routers_with_routes.first()['id']
|
||||||
|
|
||||||
|
neutronclient = self.stub_neutronclient()
|
||||||
|
neutronclient.show_router(router_id).AndReturn(router)
|
||||||
|
self.mox.ReplayAll()
|
||||||
|
|
||||||
|
ret_val = api.neutron.router_static_route_list(self.request, router_id)
|
||||||
|
self.assertIsInstance(ret_val[0], api.neutron.RouterStaticRoute)
|
||||||
|
|
||||||
|
def test_router_static_route_remove(self):
|
||||||
|
router = {'router': self.api_routers_with_routes.first()}
|
||||||
|
router_id = self.api_routers_with_routes.first()['id']
|
||||||
|
post_router = copy.deepcopy(router)
|
||||||
|
route = api.neutron.RouterStaticRoute(post_router['router']
|
||||||
|
['routes'].pop())
|
||||||
|
|
||||||
|
neutronclient = self.stub_neutronclient()
|
||||||
|
neutronclient.show_router(router_id).AndReturn(router)
|
||||||
|
body = {'router': {'routes': post_router['router']['routes']}}
|
||||||
|
neutronclient.update_router(router_id, body=body)\
|
||||||
|
.AndReturn(post_router)
|
||||||
|
self.mox.ReplayAll()
|
||||||
|
|
||||||
|
api.neutron.router_static_route_remove(self.request,
|
||||||
|
router_id, route.id)
|
||||||
|
|
||||||
|
def test_router_static_route_add(self):
|
||||||
|
router = {'router': self.api_routers_with_routes.first()}
|
||||||
|
router_id = self.api_routers_with_routes.first()['id']
|
||||||
|
post_router = copy.deepcopy(router)
|
||||||
|
route = {'nexthop': '10.0.0.5', 'destination': '40.0.1.0/24'}
|
||||||
|
post_router['router']['routes'].insert(0, route)
|
||||||
|
body = {'router': {'routes': post_router['router']['routes']}}
|
||||||
|
|
||||||
|
neutronclient = self.stub_neutronclient()
|
||||||
|
neutronclient.show_router(router_id).AndReturn(router)
|
||||||
|
neutronclient.update_router(router_id, body=body)\
|
||||||
|
.AndReturn(post_router)
|
||||||
|
self.mox.ReplayAll()
|
||||||
|
|
||||||
|
api.neutron.router_static_route_add(self.request, router_id, route)
|
||||||
|
|
||||||
# NOTE(amotoki): "dvr" permission tests check most of
|
# NOTE(amotoki): "dvr" permission tests check most of
|
||||||
# get_feature_permission features.
|
# get_feature_permission features.
|
||||||
# These tests are not specific to "dvr" extension.
|
# These tests are not specific to "dvr" extension.
|
||||||
|
@ -31,6 +31,7 @@ def data(TEST):
|
|||||||
TEST.ports = utils.TestDataContainer()
|
TEST.ports = utils.TestDataContainer()
|
||||||
TEST.routers = utils.TestDataContainer()
|
TEST.routers = utils.TestDataContainer()
|
||||||
TEST.routers_with_rules = utils.TestDataContainer()
|
TEST.routers_with_rules = utils.TestDataContainer()
|
||||||
|
TEST.routers_with_routes = utils.TestDataContainer()
|
||||||
TEST.q_floating_ips = utils.TestDataContainer()
|
TEST.q_floating_ips = utils.TestDataContainer()
|
||||||
TEST.q_secgroups = utils.TestDataContainer()
|
TEST.q_secgroups = utils.TestDataContainer()
|
||||||
TEST.q_secgroup_rules = utils.TestDataContainer()
|
TEST.q_secgroup_rules = utils.TestDataContainer()
|
||||||
@ -58,6 +59,7 @@ def data(TEST):
|
|||||||
TEST.api_subnets = utils.TestDataContainer()
|
TEST.api_subnets = utils.TestDataContainer()
|
||||||
TEST.api_ports = utils.TestDataContainer()
|
TEST.api_ports = utils.TestDataContainer()
|
||||||
TEST.api_routers = utils.TestDataContainer()
|
TEST.api_routers = utils.TestDataContainer()
|
||||||
|
TEST.api_routers_with_routes = utils.TestDataContainer()
|
||||||
TEST.api_q_floating_ips = utils.TestDataContainer()
|
TEST.api_q_floating_ips = utils.TestDataContainer()
|
||||||
TEST.api_q_secgroups = utils.TestDataContainer()
|
TEST.api_q_secgroups = utils.TestDataContainer()
|
||||||
TEST.api_q_secgroup_rules = utils.TestDataContainer()
|
TEST.api_q_secgroup_rules = utils.TestDataContainer()
|
||||||
@ -404,6 +406,20 @@ def data(TEST):
|
|||||||
'nexthops': ['1.0.0.2', '1.0.0.1']}]}
|
'nexthops': ['1.0.0.2', '1.0.0.1']}]}
|
||||||
TEST.api_routers.add(router_dict)
|
TEST.api_routers.add(router_dict)
|
||||||
TEST.routers_with_rules.add(neutron.Router(router_dict))
|
TEST.routers_with_rules.add(neutron.Router(router_dict))
|
||||||
|
router_dict_with_route = {'id': '725c24c9-061b-416b-b9d4-012392b32fd9',
|
||||||
|
'name': 'routerouter',
|
||||||
|
'status': 'ACTIVE',
|
||||||
|
'admin_state_up': True,
|
||||||
|
'distributed': False,
|
||||||
|
'external_gateway_info':
|
||||||
|
{'network_id': ext_net['id']},
|
||||||
|
'tenant_id': '1',
|
||||||
|
'routes': [{'nexthop': '10.0.0.1',
|
||||||
|
'destination': '172.0.0.0/24'},
|
||||||
|
{'nexthop': '10.0.0.2',
|
||||||
|
'destination': '172.1.0.0/24'}]}
|
||||||
|
TEST.api_routers_with_routes.add(router_dict_with_route)
|
||||||
|
TEST.routers_with_routes.add(neutron.Router(router_dict_with_route))
|
||||||
|
|
||||||
# Floating IP.
|
# Floating IP.
|
||||||
# Unassociated.
|
# Unassociated.
|
||||||
|
Loading…
Reference in New Issue
Block a user