# Copyright 2013, Nachi Ueno, NTT MCL, Inc. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import netaddr from neutron_lib.db import model_base from oslo_config import cfg from oslo_log import log as logging import sqlalchemy as sa from sqlalchemy import orm from neutron._i18n import _ from neutron.common import utils from neutron.db import db_base_plugin_v2 from neutron.db import l3_db from neutron.db.models import l3 as l3_models from neutron.db import models_v2 from neutron.extensions import extraroute from neutron.extensions import l3 LOG = logging.getLogger(__name__) extra_route_opts = [ #TODO(nati): use quota framework when it support quota for attributes cfg.IntOpt('max_routes', default=30, help=_("Maximum number of routes per router")), ] cfg.CONF.register_opts(extra_route_opts) class RouterRoute(model_base.BASEV2, models_v2.Route): router_id = sa.Column(sa.String(36), sa.ForeignKey('routers.id', ondelete="CASCADE"), primary_key=True) router = orm.relationship(l3_models.Router, backref=orm.backref("route_list", lazy='joined', cascade='delete')) class ExtraRoute_dbonly_mixin(l3_db.L3_NAT_dbonly_mixin): """Mixin class to support extra route configuration on router.""" def _extend_router_dict_extraroute(self, router_res, router_db): router_res['routes'] = (ExtraRoute_dbonly_mixin. _make_extra_route_list( router_db['route_list'] )) db_base_plugin_v2.NeutronDbPluginV2.register_dict_extend_funcs( l3.ROUTERS, ['_extend_router_dict_extraroute']) def update_router(self, context, id, router): r = router['router'] with context.session.begin(subtransactions=True): #check if route exists and have permission to access router_db = self._get_router(context, id) if 'routes' in r: self._update_extra_routes(context, router_db, r['routes']) routes = self._get_extra_routes_by_router_id(context, id) router_updated = super(ExtraRoute_dbonly_mixin, self).update_router( context, id, router) router_updated['routes'] = routes return router_updated def _get_subnets_by_cidr(self, context, cidr): query_subnets = context.session.query(models_v2.Subnet) return query_subnets.filter_by(cidr=cidr).all() def _validate_routes_nexthop(self, cidrs, ips, routes, nexthop): #Note(nati): Nexthop should be connected, # so we need to check # nexthop belongs to one of cidrs of the router ports if not netaddr.all_matching_cidrs(nexthop, cidrs): raise extraroute.InvalidRoutes( routes=routes, reason=_('the nexthop is not connected with router')) #Note(nati) nexthop should not be same as fixed_ips if nexthop in ips: raise extraroute.InvalidRoutes( routes=routes, reason=_('the nexthop is used by router')) def _validate_routes(self, context, router_id, routes): if len(routes) > cfg.CONF.max_routes: raise extraroute.RoutesExhausted( router_id=router_id, quota=cfg.CONF.max_routes) context = context.elevated() filters = {'device_id': [router_id]} ports = self._core_plugin.get_ports(context, filters) cidrs = [] ips = [] for port in ports: for ip in port['fixed_ips']: cidrs.append(self._core_plugin.get_subnet( context, ip['subnet_id'])['cidr']) ips.append(ip['ip_address']) for route in routes: self._validate_routes_nexthop( cidrs, ips, routes, route['nexthop']) def _update_extra_routes(self, context, router, routes): self._validate_routes(context, router['id'], routes) old_routes, routes_dict = self._get_extra_routes_dict_by_router_id( context, router['id']) added, removed = utils.diff_list_of_dict(old_routes, routes) LOG.debug('Added routes are %s', added) for route in added: router_routes = RouterRoute( router_id=router['id'], destination=route['destination'], nexthop=route['nexthop']) context.session.add(router_routes) LOG.debug('Removed routes are %s', removed) for route in removed: context.session.delete( routes_dict[(route['destination'], route['nexthop'])]) @staticmethod def _make_extra_route_list(extra_routes): return [{'destination': route['destination'], 'nexthop': route['nexthop']} for route in extra_routes] def _get_extra_routes_by_router_id(self, context, id): query = context.session.query(RouterRoute) query = query.filter_by(router_id=id) return self._make_extra_route_list(query) def _get_extra_routes_dict_by_router_id(self, context, id): query = context.session.query(RouterRoute) query = query.filter_by(router_id=id) routes = [] routes_dict = {} for route in query: routes.append({'destination': route['destination'], 'nexthop': route['nexthop']}) routes_dict[(route['destination'], route['nexthop'])] = route return routes, routes_dict def _confirm_router_interface_not_in_use(self, context, router_id, subnet_id): super(ExtraRoute_dbonly_mixin, self)._confirm_router_interface_not_in_use( context, router_id, subnet_id) subnet = self._core_plugin.get_subnet(context, subnet_id) subnet_cidr = netaddr.IPNetwork(subnet['cidr']) extra_routes = self._get_extra_routes_by_router_id(context, router_id) for route in extra_routes: if netaddr.all_matching_cidrs(route['nexthop'], [subnet_cidr]): raise extraroute.RouterInterfaceInUseByRoute( router_id=router_id, subnet_id=subnet_id) class ExtraRoute_db_mixin(ExtraRoute_dbonly_mixin, l3_db.L3_NAT_db_mixin): """Mixin class to support extra route configuration on router with rpc.""" pass