
In case when enforce_new_defaults is set to True and new policy rules are used, context.is_admin flag isn't really working as it was with old rules. But in case when elevated context is needed, it means that we need context which has full rights to the system. So we should also set "system_scope" parameter to "all" to be sure that system scope queries can be done with such elevated context always. It is needed e.g. when elevated context is used to get some data from db. In such case we need to have db query which will not be scoped to the single project_id and with new defaults to achieve that system_scope has to be set to "all". Proper fix for that should be done in neutron-lib and it is proposed in [1] already but as we are have frozen neutron-lib version for stable/wallaby already this patch for neutron is temporary fix for that issue. We can revert that patch as soon as we will be in Xena development cycle and [1] will be merged and released. [1] https://review.opendev.org/c/openstack/neutron-lib/+/781625 Related-Bug: #1920001 Change-Id: I0068c1de09f5c6fae5bb5cd0d6f26f451e701939
240 lines
9.8 KiB
Python
240 lines
9.8 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 api as db_api
|
|
from neutron_lib.db import resource_extend
|
|
from neutron_lib.exceptions import extraroute as xroute_exc
|
|
from neutron_lib.utils import helpers
|
|
from neutron_lib.utils import net as net_utils
|
|
from oslo_config import cfg
|
|
from oslo_log import log as logging
|
|
|
|
from neutron._i18n import _
|
|
from neutron.common import utils as common_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 db_api.CONTEXT_WRITER.using(context):
|
|
# 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))
|
|
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 = common_utils.get_elevated_context(context)
|
|
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=net_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):
|
|
super(ExtraRoute_dbonly_mixin,
|
|
self)._confirm_router_interface_not_in_use(
|
|
context, router_id, subnet)
|
|
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'])
|
|
|
|
@staticmethod
|
|
def _add_extra_routes(old, add):
|
|
"""Add two lists of extra routes.
|
|
|
|
Exact duplicates (both destination and nexthop) in old and add are
|
|
merged into one item.
|
|
Same destinations with different nexthops are accepted and all of
|
|
them are returned.
|
|
Overlapping destinations are accepted and all of them are returned.
|
|
"""
|
|
routes_dict = {} # its values are sets of nexthops
|
|
for r in old + add:
|
|
dst = r['destination']
|
|
nexthop = r['nexthop']
|
|
if dst not in routes_dict:
|
|
routes_dict[dst] = set()
|
|
routes_dict[dst].add(nexthop)
|
|
routes_list = []
|
|
for dst, nexthops in routes_dict.items():
|
|
for nexthop in nexthops:
|
|
routes_list.append({'destination': dst, 'nexthop': nexthop})
|
|
return routes_list
|
|
|
|
@staticmethod
|
|
def _remove_extra_routes(old, remove):
|
|
"""Remove the 2nd list of extra routes from the first.
|
|
|
|
Since we care about the end state if an extra route to be removed
|
|
is already missing from old, that's not an error, but accepted.
|
|
"""
|
|
routes_dict = {} # its values are sets of nexthops
|
|
for r in old:
|
|
dst = r['destination']
|
|
nexthop = r['nexthop']
|
|
if dst not in routes_dict:
|
|
routes_dict[dst] = set()
|
|
routes_dict[dst].add(nexthop)
|
|
for r in remove:
|
|
dst = r['destination']
|
|
nexthop = r['nexthop']
|
|
if dst in routes_dict:
|
|
routes_dict[dst].discard(nexthop)
|
|
routes_list = []
|
|
for dst, nexthops in routes_dict.items():
|
|
for nexthop in nexthops:
|
|
routes_list.append({'destination': dst, 'nexthop': nexthop})
|
|
return routes_list
|
|
|
|
@db_api.retry_if_session_inactive()
|
|
def add_extraroutes(self, context, router_id, body=None):
|
|
# NOTE(bence romsics): The input validation is delayed until
|
|
# update_router() validates the whole set of routes. Until then
|
|
# do not trust 'routes'.
|
|
routes = body['router']['routes']
|
|
with db_api.CONTEXT_WRITER.using(context):
|
|
old_routes = self._get_extra_routes_by_router_id(
|
|
context, router_id)
|
|
router = self.update_router(
|
|
context,
|
|
router_id,
|
|
{'router':
|
|
{'routes':
|
|
self._add_extra_routes(old_routes, routes)}})
|
|
return {'router': router}
|
|
|
|
@db_api.retry_if_session_inactive()
|
|
def remove_extraroutes(self, context, router_id, body=None):
|
|
# NOTE(bence romsics): The input validation is delayed until
|
|
# update_router() validates the whole set of routes. Until then
|
|
# do not trust 'routes'.
|
|
routes = body['router']['routes']
|
|
with db_api.CONTEXT_WRITER.using(context):
|
|
old_routes = self._get_extra_routes_by_router_id(
|
|
context, router_id)
|
|
router = self.update_router(
|
|
context,
|
|
router_id,
|
|
{'router':
|
|
{'routes':
|
|
self._remove_extra_routes(old_routes, routes)}})
|
|
return {'router': router}
|
|
|
|
|
|
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
|