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.project.routers.extensions.extraroutes\
|
||||
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
|
||||
|
||||
|
||||
@ -35,6 +33,5 @@ class InterfacesTab(r_tabs.InterfacesTab):
|
||||
|
||||
|
||||
class RouterDetailTabs(r_tabs.RouterDetailTabs):
|
||||
tabs = (OverviewTab, InterfacesTab, ExtraRoutesTab, rr_tabs.RulesGridTab,
|
||||
rr_tabs.RouterRulesTab)
|
||||
tabs = (OverviewTab, InterfacesTab, ExtraRoutesTab)
|
||||
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.dashboards.project.routers.extensions.extraroutes\
|
||||
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
|
||||
|
||||
|
||||
@ -48,6 +46,5 @@ class InterfacesTab(tabs.TableTab):
|
||||
|
||||
class RouterDetailTabs(tabs.TabGroup):
|
||||
slug = "router_details"
|
||||
tabs = (OverviewTab, InterfacesTab, er_tabs.ExtraRoutesTab,
|
||||
rr_tabs.RulesGridTab, rr_tabs.RouterRulesTab)
|
||||
tabs = (OverviewTab, InterfacesTab, er_tabs.ExtraRoutesTab)
|
||||
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
|
||||
|
||||
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.usage import quotas
|
||||
|
||||
@ -701,150 +699,6 @@ class RouterActionTests(RouterMixin, test.TestCase):
|
||||
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):
|
||||
DASHBOARD = 'project'
|
||||
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\
|
||||
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 \
|
||||
import views as port_views
|
||||
from openstack_dashboard.dashboards.project.routers import views
|
||||
@ -38,9 +36,6 @@ urlpatterns = [
|
||||
url(ROUTER_URL % 'addinterface',
|
||||
port_views.AddInterfaceView.as_view(),
|
||||
name='addinterface'),
|
||||
url(ROUTER_URL % 'addrouterrule',
|
||||
rr_views.AddRouterRuleView.as_view(),
|
||||
name='addrouterrule'),
|
||||
url(ROUTER_URL % 'addrouterroute',
|
||||
er_views.AddRouterRouteView.as_view(),
|
||||
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