neutron/neutron/db/extraroute_db.py

160 lines
6.7 KiB
Python

# 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 copy
import netaddr
from neutron_lib.api.definitions import l3 as l3_apidef
from neutron_lib.callbacks import events
from neutron_lib.callbacks import registry
from neutron_lib.callbacks import resources
from neutron_lib.db import resource_extend
from neutron_lib.exceptions import extraroute as xroute_exc
from neutron_lib.utils import helpers
from oslo_config import cfg
from oslo_log import log as logging
from neutron._i18n import _
from neutron.common import utils
from neutron.conf.db import extraroute_db
from neutron.db import l3_db
from neutron.objects import router as l3_obj
LOG = logging.getLogger(__name__)
extraroute_db.register_db_extraroute_opts()
@resource_extend.has_resource_extenders
class ExtraRoute_dbonly_mixin(l3_db.L3_NAT_dbonly_mixin):
"""Mixin class to support extra route configuration on router."""
@staticmethod
@resource_extend.extends([l3_apidef.ROUTERS])
def _extend_router_dict_extraroute(router_res, router_db):
router_res['routes'] = (ExtraRoute_dbonly_mixin.
_make_extra_route_list(
router_db['route_list']
))
def update_router(self, context, id, router):
r = router['router']
if 'routes' in r:
with context.session.begin(subtransactions=True):
# check if route exists and have permission to access
router_db = self._get_router(context, id)
old_router = self._make_router_dict(router_db)
routes_added, routes_removed = self._update_extra_routes(
context, router_db, r['routes'])
router_data = copy.deepcopy(r)
router_data['routes_added'] = routes_added
router_data['routes_removed'] = routes_removed
registry.publish(resources.ROUTER, events.PRECOMMIT_UPDATE,
self, payload=events.DBEventPayload(
context, request_body=router_data,
states=(old_router,), resource_id=id,
desired_state=router_db))
# NOTE(yamamoto): expire to ensure the following update_router
# see the effects of the above _update_extra_routes.
context.session.expire(router_db, attribute_names=['route_list'])
return super(ExtraRoute_dbonly_mixin, self).update_router(
context, id, router)
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 xroute_exc.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 xroute_exc.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 xroute_exc.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 = self._get_extra_routes_by_router_id(context, router['id'])
added, removed = helpers.diff_list_of_dict(old_routes, routes)
LOG.debug('Added routes are %s', added)
for route in added:
l3_obj.RouterRoute(
context,
router_id=router['id'],
destination=utils.AuthenticIPNetwork(route['destination']),
nexthop=netaddr.IPAddress(route['nexthop'])).create()
LOG.debug('Removed routes are %s', removed)
for route in removed:
l3_obj.RouterRoute.get_object(
context,
router_id=router['id'],
destination=route['destination'],
nexthop=route['nexthop']).delete()
return added, removed
@staticmethod
def _make_extra_route_list(extra_routes):
# NOTE(yamamoto): the extra_routes argument is either object or db row
return [{'destination': str(route['destination']),
'nexthop': str(route['nexthop'])}
for route in extra_routes]
def _get_extra_routes_by_router_id(self, context, id):
router_objs = l3_obj.RouterRoute.get_objects(context, router_id=id)
return self._make_extra_route_list(router_objs)
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 xroute_exc.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