Remove router rules extension
Router rules is a horizon extension provided by Big Switch Networks. As part of the horizon-vendor-split work, we drop the extension from upstream horizon. It is now available as a separate plugin at https://github.com/bigswitch/horizon-bsn Change-Id: I439f6f87057547f9bc0fbb7089d37b4e9603b1c1 Partially-implements: blueprint horizon-vendor-split
This commit is contained in:
parent
806aa997b8
commit
2807aa6bdc
@ -17,8 +17,6 @@ from openstack_dashboard.dashboards.admin.routers.extensions.extraroutes\
|
|||||||
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\
|
from openstack_dashboard.dashboards.project.routers.extensions.extraroutes\
|
||||||
import tabs as er_tabs
|
import tabs as er_tabs
|
||||||
from openstack_dashboard.dashboards.project.routers.extensions.routerrules\
|
|
||||||
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
|
||||||
|
|
||||||
|
|
||||||
@ -35,6 +33,5 @@ class InterfacesTab(r_tabs.InterfacesTab):
|
|||||||
|
|
||||||
|
|
||||||
class RouterDetailTabs(r_tabs.RouterDetailTabs):
|
class RouterDetailTabs(r_tabs.RouterDetailTabs):
|
||||||
tabs = (OverviewTab, InterfacesTab, ExtraRoutesTab, rr_tabs.RulesGridTab,
|
tabs = (OverviewTab, InterfacesTab, ExtraRoutesTab)
|
||||||
rr_tabs.RouterRulesTab)
|
|
||||||
sticky = True
|
sticky = True
|
||||||
|
@ -1,97 +0,0 @@
|
|||||||
# Copyright 2013, Big Switch Networks
|
|
||||||
#
|
|
||||||
# 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.exceptions import ValidationError # noqa
|
|
||||||
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 openstack_dashboard.dashboards.project.routers.extensions.routerrules\
|
|
||||||
import rulemanager
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class RuleCIDRField(forms.IPField):
|
|
||||||
"""Extends IPField to allow ('any','external') keywords and requires CIDR
|
|
||||||
"""
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
kwargs['mask'] = True
|
|
||||||
super(RuleCIDRField, self).__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
def validate(self, value):
|
|
||||||
keywords = ['any', 'external']
|
|
||||||
if value in keywords:
|
|
||||||
self.ip = value
|
|
||||||
else:
|
|
||||||
if '/' not in value:
|
|
||||||
raise ValidationError(_("Input must be in CIDR format"))
|
|
||||||
super(RuleCIDRField, self).validate(value)
|
|
||||||
|
|
||||||
|
|
||||||
class AddRouterRule(forms.SelfHandlingForm):
|
|
||||||
source = RuleCIDRField(label=_("Source CIDR"),
|
|
||||||
widget=forms.TextInput())
|
|
||||||
destination = RuleCIDRField(label=_("Destination CIDR"),
|
|
||||||
widget=forms.TextInput())
|
|
||||||
action = forms.ChoiceField(label=_("Action"))
|
|
||||||
nexthops = forms.MultiIPField(label=_("Optional: Next Hop "
|
|
||||||
"Addresses (comma delimited)"),
|
|
||||||
widget=forms.TextInput(), required=False)
|
|
||||||
router_id = forms.CharField(label=_("Router ID"),
|
|
||||||
widget=forms.TextInput(attrs={'readonly':
|
|
||||||
'readonly'}))
|
|
||||||
failure_url = 'horizon:project:routers:detail'
|
|
||||||
|
|
||||||
def __init__(self, request, *args, **kwargs):
|
|
||||||
super(AddRouterRule, self).__init__(request, *args, **kwargs)
|
|
||||||
self.fields['action'].choices = [('permit', _('Permit')),
|
|
||||||
('deny', _('Deny'))]
|
|
||||||
|
|
||||||
def handle(self, request, data, **kwargs):
|
|
||||||
try:
|
|
||||||
if 'rule_to_delete' in request.POST:
|
|
||||||
rulemanager.remove_rules(request,
|
|
||||||
[request.POST['rule_to_delete']],
|
|
||||||
router_id=data['router_id'])
|
|
||||||
except Exception:
|
|
||||||
exceptions.handle(request, _('Unable to delete router rule.'))
|
|
||||||
try:
|
|
||||||
if 'nexthops' not in data:
|
|
||||||
data['nexthops'] = ''
|
|
||||||
if data['source'] == '0.0.0.0/0':
|
|
||||||
data['source'] = 'any'
|
|
||||||
if data['destination'] == '0.0.0.0/0':
|
|
||||||
data['destination'] = 'any'
|
|
||||||
rule = {'action': data['action'],
|
|
||||||
'source': data['source'],
|
|
||||||
'destination': data['destination'],
|
|
||||||
'nexthops': data['nexthops'].split(',')}
|
|
||||||
rulemanager.add_rule(request,
|
|
||||||
router_id=data['router_id'],
|
|
||||||
newrule=rule)
|
|
||||||
msg = _('Router rule added')
|
|
||||||
LOG.debug(msg)
|
|
||||||
messages.success(request, msg)
|
|
||||||
return True
|
|
||||||
except Exception as e:
|
|
||||||
msg = _('Failed to add router rule %s') % e
|
|
||||||
LOG.info(msg)
|
|
||||||
messages.error(request, msg)
|
|
||||||
redirect = reverse(self.failure_url, args=[data['router_id']])
|
|
||||||
exceptions.handle(request, msg, redirect=redirect)
|
|
@ -1,103 +0,0 @@
|
|||||||
# Copyright 2013, Big Switch Networks
|
|
||||||
#
|
|
||||||
# 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 openstack_dashboard.api import neutron as api
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class RuleObject(dict):
|
|
||||||
def __init__(self, rule):
|
|
||||||
# ID is constructed from source and destination because the
|
|
||||||
# database ID from neutron changes on every update, making a list of
|
|
||||||
# sequential operations based on the DB ID invalid after the first one
|
|
||||||
# occurs (e.g. deleting multiple from the table
|
|
||||||
rule['id'] = rule['source'] + rule['destination']
|
|
||||||
super(RuleObject, self).__init__(rule)
|
|
||||||
# Horizon references id property for table operations
|
|
||||||
self.id = rule['id']
|
|
||||||
# Flatten into csv for display
|
|
||||||
self.nexthops = ','.join(rule['nexthops'])
|
|
||||||
|
|
||||||
|
|
||||||
def routerrule_list(request, **params):
|
|
||||||
if 'router_id' in params:
|
|
||||||
params['device_id'] = params['router_id']
|
|
||||||
if 'router' in request.META:
|
|
||||||
router = request.META['router']
|
|
||||||
else:
|
|
||||||
router = api.router_get(request, params['device_id'])
|
|
||||||
try:
|
|
||||||
rules = router.router_rules
|
|
||||||
except AttributeError:
|
|
||||||
return (False, [])
|
|
||||||
return (True, rules)
|
|
||||||
|
|
||||||
|
|
||||||
def remove_rules(request, rule_ids, **kwargs):
|
|
||||||
LOG.debug("remove_rules(): param=%s", kwargs)
|
|
||||||
router_id = kwargs['router_id']
|
|
||||||
if 'reset_rules' in kwargs:
|
|
||||||
newrules = [{'source': 'any', 'destination': 'any',
|
|
||||||
'action': 'permit'}]
|
|
||||||
else:
|
|
||||||
supported, currentrules = routerrule_list(request, **kwargs)
|
|
||||||
if not supported:
|
|
||||||
LOG.error("router rules not supported by router %s" % router_id)
|
|
||||||
return
|
|
||||||
newrules = []
|
|
||||||
for oldrule in currentrules:
|
|
||||||
if RuleObject(oldrule).id not in rule_ids:
|
|
||||||
newrules.append(oldrule)
|
|
||||||
body = {'router_rules': format_for_api(newrules)}
|
|
||||||
new = api.router_update(request, router_id, **body)
|
|
||||||
if 'router' in request.META:
|
|
||||||
request.META['router'] = new
|
|
||||||
return new
|
|
||||||
|
|
||||||
|
|
||||||
def add_rule(request, router_id, newrule, **kwargs):
|
|
||||||
body = {'router_rules': []}
|
|
||||||
kwargs['router_id'] = router_id
|
|
||||||
supported, currentrules = routerrule_list(request, **kwargs)
|
|
||||||
if not supported:
|
|
||||||
LOG.error("router rules not supported by router %s" % router_id)
|
|
||||||
return
|
|
||||||
body['router_rules'] = format_for_api([newrule] + currentrules)
|
|
||||||
new = api.router_update(request, router_id, **body)
|
|
||||||
if 'router' in request.META:
|
|
||||||
request.META['router'] = new
|
|
||||||
return new
|
|
||||||
|
|
||||||
|
|
||||||
def format_for_api(rules):
|
|
||||||
apiformrules = []
|
|
||||||
for r in rules:
|
|
||||||
# make a copy so we don't damage original dict in rules
|
|
||||||
flattened = r.copy()
|
|
||||||
# nexthops should only be present if there are nexthop addresses
|
|
||||||
if 'nexthops' in flattened:
|
|
||||||
cleanednh = [nh.strip()
|
|
||||||
for nh in flattened['nexthops']
|
|
||||||
if nh.strip()]
|
|
||||||
if cleanednh:
|
|
||||||
flattened['nexthops'] = '+'.join(cleanednh)
|
|
||||||
else:
|
|
||||||
del flattened['nexthops']
|
|
||||||
if 'id' in flattened:
|
|
||||||
del flattened['id']
|
|
||||||
apiformrules.append(flattened)
|
|
||||||
return apiformrules
|
|
@ -1,79 +0,0 @@
|
|||||||
# Copyright 2013, Big Switch Networks, 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.core.urlresolvers import reverse
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
from django.utils.translation import ungettext_lazy
|
|
||||||
|
|
||||||
from openstack_dashboard.dashboards.project.routers.extensions.routerrules\
|
|
||||||
import rulemanager
|
|
||||||
from openstack_dashboard import policy
|
|
||||||
|
|
||||||
from horizon import tables
|
|
||||||
|
|
||||||
|
|
||||||
class AddRouterRule(policy.PolicyTargetMixin, tables.LinkAction):
|
|
||||||
name = "create"
|
|
||||||
verbose_name = _("Add Router Rule")
|
|
||||||
url = "horizon:project:routers:addrouterrule"
|
|
||||||
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 RemoveRouterRule(policy.PolicyTargetMixin, tables.DeleteAction):
|
|
||||||
@staticmethod
|
|
||||||
def action_present(count):
|
|
||||||
return ungettext_lazy(
|
|
||||||
u"Delete Router Rule",
|
|
||||||
u"Delete Router Rules",
|
|
||||||
count
|
|
||||||
)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def action_past(count):
|
|
||||||
return ungettext_lazy(
|
|
||||||
u"Deleted Router Rule",
|
|
||||||
u"Deleted Router Rules",
|
|
||||||
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']
|
|
||||||
rulemanager.remove_rules(request, [obj_id],
|
|
||||||
router_id=router_id)
|
|
||||||
|
|
||||||
|
|
||||||
class RouterRulesTable(tables.DataTable):
|
|
||||||
source = tables.Column("source", verbose_name=_("Source CIDR"))
|
|
||||||
destination = tables.Column("destination",
|
|
||||||
verbose_name=_("Destination CIDR"))
|
|
||||||
action = tables.Column("action", verbose_name=_("Action"))
|
|
||||||
nexthops = tables.Column("nexthops", verbose_name=_("Next Hops"))
|
|
||||||
|
|
||||||
def get_object_display(self, rule):
|
|
||||||
return "(%(action)s) %(source)s -> %(destination)s" % rule
|
|
||||||
|
|
||||||
class Meta(object):
|
|
||||||
name = "routerrules"
|
|
||||||
verbose_name = _("Router Rules")
|
|
||||||
table_actions = (AddRouterRule, RemoveRouterRule)
|
|
||||||
row_actions = (RemoveRouterRule, )
|
|
@ -1,227 +0,0 @@
|
|||||||
# Copyright 2013
|
|
||||||
#
|
|
||||||
# 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 netaddr
|
|
||||||
|
|
||||||
from django import template
|
|
||||||
from django.template.loader import render_to_string
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
|
|
||||||
from horizon import tabs
|
|
||||||
|
|
||||||
from openstack_dashboard import api
|
|
||||||
from openstack_dashboard.dashboards.project.routers.extensions.routerrules\
|
|
||||||
import rulemanager
|
|
||||||
from openstack_dashboard.dashboards.project.routers.extensions.routerrules\
|
|
||||||
import tables as rrtbl
|
|
||||||
|
|
||||||
|
|
||||||
class RouterRulesTab(tabs.TableTab):
|
|
||||||
table_classes = (rrtbl.RouterRulesTable,)
|
|
||||||
name = _("Router Rules")
|
|
||||||
slug = "routerrules"
|
|
||||||
template_name = "horizon/common/_detail_table.html"
|
|
||||||
|
|
||||||
def allowed(self, request):
|
|
||||||
try:
|
|
||||||
getattr(self.tab_group.kwargs['router'], 'router_rules')
|
|
||||||
return True
|
|
||||||
except Exception:
|
|
||||||
return False
|
|
||||||
|
|
||||||
def get_routerrules_data(self):
|
|
||||||
try:
|
|
||||||
routerrules = getattr(self.tab_group.kwargs['router'],
|
|
||||||
'router_rules')
|
|
||||||
except Exception:
|
|
||||||
routerrules = []
|
|
||||||
return [rulemanager.RuleObject(r) for r in routerrules]
|
|
||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
|
||||||
if request.POST['action'] == 'routerrules__resetrules':
|
|
||||||
kwargs['reset_rules'] = True
|
|
||||||
rulemanager.remove_rules(request, [], **kwargs)
|
|
||||||
self.tab_group.kwargs['router'] = \
|
|
||||||
api.neutron.router_get(request, kwargs['router_id'])
|
|
||||||
|
|
||||||
|
|
||||||
class RulesGridTab(tabs.Tab):
|
|
||||||
name = _("Router Rules Grid")
|
|
||||||
slug = "rulesgrid"
|
|
||||||
template_name = ("project/routers/extensions/routerrules/grid.html")
|
|
||||||
|
|
||||||
def allowed(self, request):
|
|
||||||
try:
|
|
||||||
getattr(self.tab_group.kwargs['router'], 'router_rules')
|
|
||||||
return True
|
|
||||||
except Exception:
|
|
||||||
return False
|
|
||||||
|
|
||||||
def render(self):
|
|
||||||
context = template.RequestContext(self.request)
|
|
||||||
return render_to_string(self.get_template_name(self.request),
|
|
||||||
self.data, context_instance=context)
|
|
||||||
|
|
||||||
def get_context_data(self, request, **kwargs):
|
|
||||||
data = {'router': {'id':
|
|
||||||
self.tab_group.kwargs['router_id']}}
|
|
||||||
self.request = request
|
|
||||||
rules, supported = self.get_routerrules_data(checksupport=True)
|
|
||||||
if supported:
|
|
||||||
data["rulesmatrix"] = self.get_routerrulesgrid_data(rules)
|
|
||||||
return data
|
|
||||||
|
|
||||||
def get_routerrulesgrid_data(self, rules):
|
|
||||||
ports = self.tab_group.kwargs['ports']
|
|
||||||
networks = api.neutron.network_list_for_tenant(
|
|
||||||
self.request, self.request.user.tenant_id)
|
|
||||||
netnamemap = {}
|
|
||||||
subnetmap = {}
|
|
||||||
for n in networks:
|
|
||||||
netnamemap[n['id']] = n.name_or_id
|
|
||||||
for s in n.subnets:
|
|
||||||
subnetmap[s.id] = {'name': s.name,
|
|
||||||
'cidr': s.cidr}
|
|
||||||
|
|
||||||
matrix = []
|
|
||||||
subnets = []
|
|
||||||
for port in ports:
|
|
||||||
for ip in port['fixed_ips']:
|
|
||||||
if ip['subnet_id'] not in subnetmap:
|
|
||||||
continue
|
|
||||||
sub = {'ip': ip['ip_address'],
|
|
||||||
'subnetid': ip['subnet_id'],
|
|
||||||
'subnetname': subnetmap[ip['subnet_id']]['name'],
|
|
||||||
'networkid': port['network_id'],
|
|
||||||
'networkname': netnamemap[port['network_id']],
|
|
||||||
'cidr': subnetmap[ip['subnet_id']]['cidr']}
|
|
||||||
subnets.append(sub)
|
|
||||||
subnets.append({'ip': '0.0.0.0',
|
|
||||||
'subnetid': 'external',
|
|
||||||
'subnetname': '',
|
|
||||||
'networkname': 'external',
|
|
||||||
'networkid': 'external',
|
|
||||||
'cidr': '0.0.0.0/0'})
|
|
||||||
subnets.append({'ip': '0.0.0.0',
|
|
||||||
'subnetid': 'any',
|
|
||||||
'subnetname': '',
|
|
||||||
'networkname': 'any',
|
|
||||||
'networkid': 'any',
|
|
||||||
'cidr': '0.0.0.0/0'})
|
|
||||||
for source in subnets:
|
|
||||||
row = {'source': dict(source),
|
|
||||||
'targets': []}
|
|
||||||
for target in subnets:
|
|
||||||
target.update(self._get_subnet_connectivity(
|
|
||||||
source, target, rules))
|
|
||||||
row['targets'].append(dict(target))
|
|
||||||
matrix.append(row)
|
|
||||||
return matrix
|
|
||||||
|
|
||||||
def _get_subnet_connectivity(self, src_sub, dst_sub, rules):
|
|
||||||
v4_any_words = ['external', 'any']
|
|
||||||
connectivity = {'reachable': '',
|
|
||||||
'inverse_rule': {},
|
|
||||||
'rule_to_delete': False}
|
|
||||||
src = src_sub['cidr']
|
|
||||||
dst = dst_sub['cidr']
|
|
||||||
# differentiate between external and any
|
|
||||||
src_rulename = src_sub['subnetid'] if src == '0.0.0.0/0' else src
|
|
||||||
dst_rulename = dst_sub['subnetid'] if dst == '0.0.0.0/0' else dst
|
|
||||||
if str(src) == str(dst):
|
|
||||||
connectivity['reachable'] = 'full'
|
|
||||||
return connectivity
|
|
||||||
matchingrules = []
|
|
||||||
|
|
||||||
for rule in rules:
|
|
||||||
rd = rule['destination']
|
|
||||||
if rule['destination'] in v4_any_words:
|
|
||||||
rd = '0.0.0.0/0'
|
|
||||||
rs = rule['source']
|
|
||||||
if rule['source'] in v4_any_words:
|
|
||||||
rs = '0.0.0.0/0'
|
|
||||||
rs = netaddr.IPNetwork(rs)
|
|
||||||
src = netaddr.IPNetwork(src)
|
|
||||||
rd = netaddr.IPNetwork(rd)
|
|
||||||
dst = netaddr.IPNetwork(dst)
|
|
||||||
# check if cidrs are affected by rule first
|
|
||||||
if (int(dst.network) >= int(rd[-1]) or
|
|
||||||
int(dst[-1]) <= int(rd.network) or
|
|
||||||
int(src.network) >= int(rs[-1]) or
|
|
||||||
int(src[-1]) <= int(rs.network)):
|
|
||||||
continue
|
|
||||||
|
|
||||||
# skip matching rules for 'any' and 'external' networks
|
|
||||||
if (str(dst) == '0.0.0.0/0' and str(rd) != '0.0.0.0/0'):
|
|
||||||
continue
|
|
||||||
if (str(src) == '0.0.0.0/0' and str(rs) != '0.0.0.0/0'):
|
|
||||||
continue
|
|
||||||
|
|
||||||
# external network rules only affect external traffic
|
|
||||||
if (rule['source'] == 'external' and
|
|
||||||
src_rulename not in v4_any_words):
|
|
||||||
continue
|
|
||||||
if (rule['destination'] == 'external' and
|
|
||||||
dst_rulename not in v4_any_words):
|
|
||||||
continue
|
|
||||||
|
|
||||||
match = {'bitsinsrc': rs.prefixlen,
|
|
||||||
'bitsindst': rd.prefixlen,
|
|
||||||
'rule': rule}
|
|
||||||
matchingrules.append(match)
|
|
||||||
|
|
||||||
if not matchingrules:
|
|
||||||
connectivity['reachable'] = 'none'
|
|
||||||
connectivity['inverse_rule'] = {'source': src_rulename,
|
|
||||||
'destination': dst_rulename,
|
|
||||||
'action': 'permit'}
|
|
||||||
return connectivity
|
|
||||||
|
|
||||||
sortedrules = sorted(matchingrules,
|
|
||||||
key=lambda k: (k['bitsinsrc'], k['bitsindst']),
|
|
||||||
reverse=True)
|
|
||||||
match = sortedrules[0]
|
|
||||||
if (match['bitsinsrc'] > src.prefixlen or
|
|
||||||
match['bitsindst'] > dst.prefixlen):
|
|
||||||
connectivity['reachable'] = 'partial'
|
|
||||||
connectivity['conflicting_rule'] = match['rule']
|
|
||||||
return connectivity
|
|
||||||
|
|
||||||
if (match['rule']['source'] == src_rulename and
|
|
||||||
match['rule']['destination'] == dst_rulename):
|
|
||||||
connectivity['rule_to_delete'] = match['rule']
|
|
||||||
|
|
||||||
if match['rule']['action'] == 'permit':
|
|
||||||
connectivity['reachable'] = 'full'
|
|
||||||
inverseaction = 'deny'
|
|
||||||
else:
|
|
||||||
connectivity['reachable'] = 'none'
|
|
||||||
inverseaction = 'permit'
|
|
||||||
connectivity['inverse_rule'] = {'source': src_rulename,
|
|
||||||
'destination': dst_rulename,
|
|
||||||
'action': inverseaction}
|
|
||||||
return connectivity
|
|
||||||
|
|
||||||
def get_routerrules_data(self, checksupport=False):
|
|
||||||
try:
|
|
||||||
routerrules = getattr(self.tab_group.kwargs['router'],
|
|
||||||
'router_rules')
|
|
||||||
supported = True
|
|
||||||
except Exception:
|
|
||||||
routerrules = []
|
|
||||||
supported = False
|
|
||||||
|
|
||||||
if checksupport:
|
|
||||||
return routerrules, supported
|
|
||||||
return routerrules
|
|
@ -1,59 +0,0 @@
|
|||||||
# Copyright 2013, Big Switch Networks
|
|
||||||
#
|
|
||||||
# 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 horizon import exceptions
|
|
||||||
from horizon import forms
|
|
||||||
from horizon.utils import memoized
|
|
||||||
|
|
||||||
from openstack_dashboard import api
|
|
||||||
from openstack_dashboard.dashboards.project.routers.extensions.routerrules\
|
|
||||||
import forms as rrforms
|
|
||||||
|
|
||||||
|
|
||||||
class AddRouterRuleView(forms.ModalFormView):
|
|
||||||
form_class = rrforms.AddRouterRule
|
|
||||||
template_name = 'project/routers/extensions/routerrules/create.html'
|
|
||||||
success_url = 'horizon:project:routers:detail'
|
|
||||||
failure_url = 'horizon:project:routers:detail'
|
|
||||||
page_title = _("Add Router Rule")
|
|
||||||
|
|
||||||
def get_success_url(self):
|
|
||||||
return reverse(self.success_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.failure_url, args=[router_id])
|
|
||||||
msg = _("Unable to retrieve router.")
|
|
||||||
exceptions.handle(self.request, msg, redirect=redirect)
|
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
|
||||||
context = super(AddRouterRuleView, self).get_context_data(**kwargs)
|
|
||||||
context['router'] = self.get_object()
|
|
||||||
return context
|
|
||||||
|
|
||||||
def get_initial(self):
|
|
||||||
router = self.get_object()
|
|
||||||
# store the router in the request so the rule manager doesn't have
|
|
||||||
# to request it again from the API
|
|
||||||
self.request.META['router'] = router
|
|
||||||
return {"router_id": self.kwargs['router_id'],
|
|
||||||
"router_name": router.name_or_id}
|
|
@ -19,8 +19,6 @@ from horizon import tabs
|
|||||||
from openstack_dashboard import api
|
from openstack_dashboard import api
|
||||||
from openstack_dashboard.dashboards.project.routers.extensions.extraroutes\
|
from openstack_dashboard.dashboards.project.routers.extensions.extraroutes\
|
||||||
import tabs as er_tabs
|
import tabs as er_tabs
|
||||||
from openstack_dashboard.dashboards.project.routers.extensions.routerrules\
|
|
||||||
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
|
||||||
|
|
||||||
|
|
||||||
@ -48,6 +46,5 @@ class InterfacesTab(tabs.TableTab):
|
|||||||
|
|
||||||
class RouterDetailTabs(tabs.TabGroup):
|
class RouterDetailTabs(tabs.TabGroup):
|
||||||
slug = "router_details"
|
slug = "router_details"
|
||||||
tabs = (OverviewTab, InterfacesTab, er_tabs.ExtraRoutesTab,
|
tabs = (OverviewTab, InterfacesTab, er_tabs.ExtraRoutesTab)
|
||||||
rr_tabs.RulesGridTab, rr_tabs.RouterRulesTab)
|
|
||||||
sticky = True
|
sticky = True
|
||||||
|
@ -1,23 +0,0 @@
|
|||||||
{% extends "horizon/common/_modal_form.html" %}
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% block form_id %}add_routerrule_form{% endblock %}
|
|
||||||
{% block form_action %}{% url 'horizon:project:routers:addrouterrule' router.id %}
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block modal-header %}{% trans "Add Router Rule" %}{% 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 "Routing rules to apply to router. Rules are matched by most specific source first and then by most specific destination." %}<br/>
|
|
||||||
{% trans "The next hop addresses can be used to override the router used by the client." %}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
@ -1,7 +0,0 @@
|
|||||||
{% extends 'base.html' %}
|
|
||||||
{% load i18n %}
|
|
||||||
{% block title %}{% trans "Add Router Rule" %}{% endblock %}
|
|
||||||
|
|
||||||
{% block main %}
|
|
||||||
{% include "project/routers/extensions/routerrules/_create.html" %}
|
|
||||||
{% endblock %}
|
|
@ -1,156 +0,0 @@
|
|||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% block main %}
|
|
||||||
|
|
||||||
<div id="routerrules_clickgrid">
|
|
||||||
<table class="table table-bordered datatable table-hover table-condensed">
|
|
||||||
<thead>
|
|
||||||
|
|
||||||
<tr class='table_caption'>
|
|
||||||
<th class='table_header' colspan='{{ rulesmatrix|length|add:1 }}'>
|
|
||||||
<h3 class='table_title'>{% trans "Router Rule Grid" %}</h3>
|
|
||||||
<div class="table_actions clearfix">
|
|
||||||
<form action="./" method="POST"
|
|
||||||
style='display: inline; background-color: transparent; float: none; margin-left: 0;'>
|
|
||||||
{% csrf_token %}
|
|
||||||
<input type="hidden" name="router_id" value="{{ router.id }}"/>
|
|
||||||
<button class="btn btn-sm btn-danger"
|
|
||||||
type="submit" href="#" name="action" value="routerrules__resetrules"><span class="fa fa-close"> {% trans "Reset to Default" %}</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</th>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th>{% trans "Destination" %}→<br/>↓{% trans "Source" %}</th>
|
|
||||||
{% with src=rulesmatrix|first %}
|
|
||||||
{% for dest in src.targets %}
|
|
||||||
<th>
|
|
||||||
{{ dest.networkname }}<br/>
|
|
||||||
{% if dest.subnetname|length > 0 %}
|
|
||||||
{% blocktrans trimmed with dest_subnetname=dest.subnetname %}
|
|
||||||
Subnet: {{ dest_subnetname }}{% endblocktrans %}</br>
|
|
||||||
{% endif %}
|
|
||||||
{{ dest.cidr }}
|
|
||||||
</th>
|
|
||||||
{% endfor %}
|
|
||||||
{% endwith %}
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{% for row in rulesmatrix %}
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<b>{{ row.source.networkname }}
|
|
||||||
{% if row.source.subnetname|length > 0 %}
|
|
||||||
<br/>
|
|
||||||
{% blocktrans trimmed with row_source_subnetname=row.source.subnetname %}
|
|
||||||
Subnet: {{ row_source_subnetname }}{% endblocktrans %}
|
|
||||||
{% endif %}
|
|
||||||
<br/>
|
|
||||||
{{ row.source.cidr }}
|
|
||||||
</b>
|
|
||||||
</td>
|
|
||||||
{% for dest in row.targets %}
|
|
||||||
<td id="td_{{ dest.subnetid|add:row.source.subnetid }}"
|
|
||||||
data-mirrortd="td_{{ row.source.subnetid|add:dest.subnetid }}"
|
|
||||||
onMouseOver="highLightMirror(this);"
|
|
||||||
onMouseOut="unHighLightMirror(this);"
|
|
||||||
{% if dest.reachable == 'none' %}
|
|
||||||
style="background-color:#FFB2B2;"
|
|
||||||
{% elif dest.reachable == 'partial' %}
|
|
||||||
style="background-color:#FFFF66;"
|
|
||||||
{% else %}
|
|
||||||
style="background-color:#CCFFCC;"
|
|
||||||
{% endif %}
|
|
||||||
>
|
|
||||||
<form action="./addrouterrule" method="POST"
|
|
||||||
style='display: inline; background-color: transparent; float: none; margin-left: 0;'>
|
|
||||||
{% csrf_token %}
|
|
||||||
<input type="hidden" name="router_id" value="{{ router.id }}">
|
|
||||||
<input type="hidden" name="source" value="{{ dest.inverse_rule.source }}">
|
|
||||||
<input type="hidden" name="destination" value="{{ dest.inverse_rule.destination }}">
|
|
||||||
<input type="hidden" name="action" value="{{ dest.inverse_rule.action }}">
|
|
||||||
|
|
||||||
{% if dest.rule_to_delete %}
|
|
||||||
<div class="center-block"><input type="hidden" name="rule_to_delete" value="{{ dest.rule_to_delete.id }}"/></div>
|
|
||||||
{% endif %}
|
|
||||||
{% if dest.reachable == 'none' %}
|
|
||||||
<div class="center-block">
|
|
||||||
<span class="fa fa-ban"></span>
|
|
||||||
<button type="submit" class="btn btn-default btn-xs" href="#"><span class="fa fa-random"></span></button></div>
|
|
||||||
{% elif dest.reachable == 'full' %}
|
|
||||||
<div class="center-block">
|
|
||||||
<span class="fa fa-check"></span>
|
|
||||||
{% if not dest.cidr == row.source.cidr %}
|
|
||||||
<button type="submit" class="btn btn-default btn-xs" href="#"><span class="fa fa-random"></span></button>
|
|
||||||
{% else %}
|
|
||||||
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
{% else %}
|
|
||||||
<div class="center-block"><a type="button" class="btn btn-default btn-xs" href="#modal_{{ dest.subnetid|add:row.source.subnetid }}" data-toggle="modal"><span class="fa fa-exclamation-circle"></span> Conflict</a></div>
|
|
||||||
<div class="modal hide" id="modal_{{ dest.subnetid|add:row.source.subnetid }}">
|
|
||||||
<div class="modal-header">
|
|
||||||
<a class="close" data-dismiss="modal">×</a>
|
|
||||||
<h3>{% trans "Rule Conflict" %}</h3>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
<p>{% blocktrans trimmed %}
|
|
||||||
A more specific rule affects a portion of this traffic so
|
|
||||||
a rule cannot be automatically generated to control the
|
|
||||||
behavior of the entire source/destination combination.
|
|
||||||
{% endblocktrans %}</p>
|
|
||||||
<hr>
|
|
||||||
<h4>{% trans "Conflicting Rule" %}</h4>
|
|
||||||
<b>{% trans "Source:" %}</b> {{ dest.conflicting_rule.source }}<br>
|
|
||||||
<b>{% trans "Destination:" %}</b> {{ dest.conflicting_rule.destination }}<br>
|
|
||||||
<b>{% trans "Action:" %}</b> {{ dest.conflicting_rule.action }}<br>
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<a href="#" class="btn" data-dismiss="modal">{% trans "Close" %}</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</form>
|
|
||||||
</td>
|
|
||||||
{% endfor %}
|
|
||||||
{% endfor %}
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
<tfoot>
|
|
||||||
<tr>
|
|
||||||
<td colspan="{{ rulesmatrix|length|add:1}}">
|
|
||||||
<span class="table_count"></span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tfoot>
|
|
||||||
</table>
|
|
||||||
<h3>{% trans "Description" %}</h3>
|
|
||||||
<p>{% blocktrans trimmed %}
|
|
||||||
The color and icon of an intersection indicates whether or not traffic is
|
|
||||||
permitted from the source (row) to the destination (column).
|
|
||||||
Clicking the <span class="fa fa-random"></span> button in the intersection
|
|
||||||
will install a rule to switch the traffic behavior.<br/>
|
|
||||||
<b>Note:</b> Rules only affect one direction of traffic.
|
|
||||||
The opposite direction is outlined when hovering over an intersection.
|
|
||||||
{% endblocktrans %} </p>
|
|
||||||
</div>
|
|
||||||
<script type="text/javascript">
|
|
||||||
function highLightMirror(td){
|
|
||||||
var mirror = document.getElementById(td.getAttribute("data-mirrortd"));
|
|
||||||
if (mirror.id == td.id){
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
mirror.style.borderWidth="medium";
|
|
||||||
td.style.borderWidth="medium";
|
|
||||||
}
|
|
||||||
function unHighLightMirror(td){
|
|
||||||
var mirror = document.getElementById(td.getAttribute("data-mirrortd"));
|
|
||||||
if (mirror.id == td.id){
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
mirror.style.borderWidth="thin";
|
|
||||||
td.style.borderWidth="thin";
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
{% endblock %}
|
|
@ -21,8 +21,6 @@ from mox3.mox import IsA # noqa
|
|||||||
import six
|
import six
|
||||||
|
|
||||||
from openstack_dashboard import api
|
from openstack_dashboard import api
|
||||||
from openstack_dashboard.dashboards.project.routers.extensions.routerrules\
|
|
||||||
import rulemanager
|
|
||||||
from openstack_dashboard.test import helpers as test
|
from openstack_dashboard.test import helpers as test
|
||||||
from openstack_dashboard.usage import quotas
|
from openstack_dashboard.usage import quotas
|
||||||
|
|
||||||
@ -701,150 +699,6 @@ class RouterActionTests(RouterMixin, test.TestCase):
|
|||||||
self.assertRedirectsNoFollow(res, detail_url)
|
self.assertRedirectsNoFollow(res, detail_url)
|
||||||
|
|
||||||
|
|
||||||
class RouterRuleTests(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_rules(self):
|
|
||||||
router = self.routers.first()
|
|
||||||
res = self._get_detail(router)
|
|
||||||
|
|
||||||
self.assertTemplateUsed(res, 'horizon/common/_detail.html')
|
|
||||||
self.assertTemplateNotUsed(
|
|
||||||
res,
|
|
||||||
'%s/routers/extensions/routerrules/grid.html' % self.DASHBOARD)
|
|
||||||
|
|
||||||
@test.create_stubs({api.neutron: ('network_list',)})
|
|
||||||
def test_routerrule_detail(self):
|
|
||||||
router = self.routers_with_rules.first()
|
|
||||||
if self.DASHBOARD == 'project':
|
|
||||||
api.neutron.network_list(
|
|
||||||
IsA(http.HttpRequest),
|
|
||||||
shared=False,
|
|
||||||
tenant_id=router['tenant_id']).AndReturn(self.networks.list())
|
|
||||||
api.neutron.network_list(
|
|
||||||
IsA(http.HttpRequest),
|
|
||||||
shared=True).AndReturn([])
|
|
||||||
res = self._get_detail(router)
|
|
||||||
|
|
||||||
self.assertTemplateUsed(res, 'horizon/common/_detail.html')
|
|
||||||
if self.DASHBOARD == 'project':
|
|
||||||
self.assertTemplateUsed(
|
|
||||||
res,
|
|
||||||
'%s/routers/extensions/routerrules/grid.html' % self.DASHBOARD)
|
|
||||||
rules = res.context['routerrules_table'].data
|
|
||||||
self.assertItemsEqual(rules, router['router_rules'])
|
|
||||||
|
|
||||||
def _test_router_addrouterrule(self, raise_error=False):
|
|
||||||
pre_router = self.routers_with_rules.first()
|
|
||||||
post_router = copy.deepcopy(pre_router)
|
|
||||||
rule = {'source': '1.2.3.4/32', 'destination': '4.3.2.1/32', 'id': 99,
|
|
||||||
'action': 'permit', 'nexthops': ['1.1.1.1', '2.2.2.2']}
|
|
||||||
post_router['router_rules'].insert(0, rule)
|
|
||||||
api.neutron.router_get(IsA(http.HttpRequest),
|
|
||||||
pre_router.id).AndReturn(pre_router)
|
|
||||||
params = {}
|
|
||||||
params['router_rules'] = rulemanager.format_for_api(
|
|
||||||
post_router['router_rules'])
|
|
||||||
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 = {'router_id': pre_router.id,
|
|
||||||
'source': rule['source'],
|
|
||||||
'destination': rule['destination'],
|
|
||||||
'action': rule['action'],
|
|
||||||
'nexthops': ','.join(rule['nexthops'])}
|
|
||||||
|
|
||||||
url = reverse('horizon:%s:routers:addrouterrule' % 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)
|
|
||||||
|
|
||||||
@test.create_stubs({api.neutron: ('router_get',
|
|
||||||
'router_update')})
|
|
||||||
def test_router_addrouterrule(self):
|
|
||||||
self._test_router_addrouterrule()
|
|
||||||
|
|
||||||
@test.create_stubs({api.neutron: ('router_get',
|
|
||||||
'router_update')})
|
|
||||||
def test_router_addrouterrule_exception(self):
|
|
||||||
self._test_router_addrouterrule(raise_error=True)
|
|
||||||
|
|
||||||
@test.create_stubs({api.neutron: ('router_get', 'router_update',
|
|
||||||
'port_list', 'network_get',
|
|
||||||
'is_extension_supported')})
|
|
||||||
def test_router_removerouterrule(self):
|
|
||||||
pre_router = self.routers_with_rules.first()
|
|
||||||
post_router = copy.deepcopy(pre_router)
|
|
||||||
rule = post_router['router_rules'].pop()
|
|
||||||
api.neutron.is_extension_supported(IsA(http.HttpRequest), 'extraroute')\
|
|
||||||
.AndReturn(False)
|
|
||||||
api.neutron.router_get(IsA(http.HttpRequest),
|
|
||||||
pre_router.id).AndReturn(pre_router)
|
|
||||||
params = {}
|
|
||||||
params['router_rules'] = rulemanager.format_for_api(
|
|
||||||
post_router['router_rules'])
|
|
||||||
router_update = api.neutron.router_update(IsA(http.HttpRequest),
|
|
||||||
pre_router.id, **params)
|
|
||||||
router_update.AndReturn({'router': post_router})
|
|
||||||
api.neutron.router_get(IsA(http.HttpRequest),
|
|
||||||
pre_router.id).AndReturn(pre_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_rule_id = rule['source'] + rule['destination']
|
|
||||||
form_data = {'router_id': pre_router.id,
|
|
||||||
'action': 'routerrules__delete__%s' % form_rule_id}
|
|
||||||
url = reverse(self.DETAIL_PATH, args=[pre_router.id])
|
|
||||||
res = self.client.post(url, form_data)
|
|
||||||
self.assertNoFormErrors(res)
|
|
||||||
|
|
||||||
@test.create_stubs({api.neutron: ('router_get', 'router_update',
|
|
||||||
'network_list', 'port_list',
|
|
||||||
'network_get',
|
|
||||||
'is_extension_supported')})
|
|
||||||
def test_router_resetrouterrules(self):
|
|
||||||
pre_router = self.routers_with_rules.first()
|
|
||||||
post_router = copy.deepcopy(pre_router)
|
|
||||||
default_rules = [{'source': 'any', 'destination': 'any',
|
|
||||||
'action': 'permit', 'nexthops': [], 'id': '2'}]
|
|
||||||
del post_router['router_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),
|
|
||||||
pre_router.id).AndReturn(post_router)
|
|
||||||
params = {}
|
|
||||||
params['router_rules'] = rulemanager.format_for_api(
|
|
||||||
post_router['router_rules'])
|
|
||||||
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._mock_network_list(pre_router['tenant_id'])
|
|
||||||
api.neutron.router_get(IsA(http.HttpRequest),
|
|
||||||
pre_router.id).AndReturn(post_router)
|
|
||||||
self.mox.ReplayAll()
|
|
||||||
form_data = {'router_id': pre_router.id,
|
|
||||||
'action': 'routerrules__resetrules'}
|
|
||||||
url = reverse(self.DETAIL_PATH, args=[pre_router.id])
|
|
||||||
res = self.client.post(url, form_data)
|
|
||||||
self.assertNoFormErrors(res)
|
|
||||||
|
|
||||||
|
|
||||||
class RouterRouteTests(RouterMixin, test.TestCase):
|
class RouterRouteTests(RouterMixin, test.TestCase):
|
||||||
DASHBOARD = 'project'
|
DASHBOARD = 'project'
|
||||||
INDEX_URL = reverse('horizon:%s:routers:index' % DASHBOARD)
|
INDEX_URL = reverse('horizon:%s:routers:index' % DASHBOARD)
|
||||||
|
@ -16,8 +16,6 @@ from django.conf.urls import url
|
|||||||
|
|
||||||
from openstack_dashboard.dashboards.project.routers.extensions.extraroutes\
|
from openstack_dashboard.dashboards.project.routers.extensions.extraroutes\
|
||||||
import views as er_views
|
import views as er_views
|
||||||
from openstack_dashboard.dashboards.project.routers.extensions.routerrules\
|
|
||||||
import views as rr_views
|
|
||||||
from openstack_dashboard.dashboards.project.routers.ports \
|
from openstack_dashboard.dashboards.project.routers.ports \
|
||||||
import views as port_views
|
import views as port_views
|
||||||
from openstack_dashboard.dashboards.project.routers import views
|
from openstack_dashboard.dashboards.project.routers import views
|
||||||
@ -38,9 +36,6 @@ urlpatterns = [
|
|||||||
url(ROUTER_URL % 'addinterface',
|
url(ROUTER_URL % 'addinterface',
|
||||||
port_views.AddInterfaceView.as_view(),
|
port_views.AddInterfaceView.as_view(),
|
||||||
name='addinterface'),
|
name='addinterface'),
|
||||||
url(ROUTER_URL % 'addrouterrule',
|
|
||||||
rr_views.AddRouterRuleView.as_view(),
|
|
||||||
name='addrouterrule'),
|
|
||||||
url(ROUTER_URL % 'addrouterroute',
|
url(ROUTER_URL % 'addrouterroute',
|
||||||
er_views.AddRouterRouteView.as_view(),
|
er_views.AddRouterRouteView.as_view(),
|
||||||
name='addrouterroute'),
|
name='addrouterroute'),
|
||||||
|
@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
deprecations:
|
||||||
|
- Router rules is a horizon extension provided by Big Switch Networks.
|
||||||
|
As part of the horizon-vendor-split work, we drop the extension from upstream horizon.
|
||||||
|
It is now available as a separate plugin at https://github.com/bigswitch/horizon-bsn
|
||||||
|
|
Loading…
Reference in New Issue
Block a user