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:
Aditya Vaja 2016-07-27 15:25:02 -07:00
parent 806aa997b8
commit 2807aa6bdc
14 changed files with 8 additions and 910 deletions

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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, )

View File

@ -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

View File

@ -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}

View File

@ -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

View File

@ -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 %}

View File

@ -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 %}

View File

@ -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" %}&rarr;<br/>&darr;{% 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 %}
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
{% 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">&times;</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 %}

View File

@ -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)

View File

@ -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'),

View File

@ -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