neutron/neutron/db/external_net_db.py

248 lines
11 KiB
Python

# Copyright (c) 2013 OpenStack Foundation.
# 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.
from neutron_lib.api.definitions import external_net as extnet_apidef
from neutron_lib.api.definitions import network as net_def
from neutron_lib.api import validators
from neutron_lib.callbacks import events
from neutron_lib.callbacks import registry
from neutron_lib.callbacks import resources
from neutron_lib import constants
from neutron_lib.db import utils as db_utils
from neutron_lib import exceptions as n_exc
from neutron_lib.exceptions import external_net as extnet_exc
from neutron_lib.plugins import constants as plugin_constants
from neutron_lib.plugins import directory
from sqlalchemy.sql import expression as expr
from neutron._i18n import _
from neutron.db import _model_query as model_query
from neutron.db import _resource_extend as resource_extend
from neutron.db import models_v2
from neutron.db import rbac_db_models as rbac_db
from neutron.extensions import rbac as rbac_ext
from neutron.objects import network as net_obj
from neutron.objects import router as l3_obj
def _network_filter_hook(context, original_model, conditions):
if conditions is not None and not hasattr(conditions, '__iter__'):
conditions = (conditions, )
# Apply the external network filter only in non-admin and non-advsvc
# context
if db_utils.model_query_scope_is_project(context, original_model):
# the table will already be joined to the rbac entries for the
# shared check so we don't need to worry about ensuring that
rbac_model = original_model.rbac_entries.property.mapper.class_
tenant_allowed = (
(rbac_model.action == 'access_as_external') &
(rbac_model.target_tenant == context.tenant_id) |
(rbac_model.target_tenant == '*'))
conditions = expr.or_(tenant_allowed, *conditions)
return conditions
def _network_result_filter_hook(query, filters):
vals = filters and filters.get(extnet_apidef.EXTERNAL, [])
if not vals:
return query
if vals[0]:
return query.filter(models_v2.Network.external.has())
return query.filter(~models_v2.Network.external.has())
@resource_extend.has_resource_extenders
@registry.has_registry_receivers
class External_net_db_mixin(object):
"""Mixin class to add external network methods to db_base_plugin_v2."""
def __new__(cls, *args, **kwargs):
model_query.register_hook(
models_v2.Network,
"external_net",
query_hook=None,
filter_hook=_network_filter_hook,
result_filters=_network_result_filter_hook)
return super(External_net_db_mixin, cls).__new__(cls, *args, **kwargs)
def _network_is_external(self, context, net_id):
return net_obj.ExternalNetwork.objects_exist(
context, network_id=net_id)
@staticmethod
@resource_extend.extends([net_def.COLLECTION_NAME])
def _extend_network_dict_l3(network_res, network_db):
# Comparing with None for converting uuid into bool
network_res[extnet_apidef.EXTERNAL] = network_db.external is not None
return network_res
def _process_l3_create(self, context, net_data, req_data):
external = req_data.get(extnet_apidef.EXTERNAL)
external_set = validators.is_attr_set(external)
if not external_set:
return
if external:
net_obj.ExternalNetwork(
context, network_id=net_data['id']).create()
context.session.add(rbac_db.NetworkRBAC(
object_id=net_data['id'], action='access_as_external',
target_tenant='*', tenant_id=net_data['tenant_id']))
net_data[extnet_apidef.EXTERNAL] = external
def _process_l3_update(self, context, net_data, req_data, allow_all=True):
new_value = req_data.get(extnet_apidef.EXTERNAL)
net_id = net_data['id']
if not validators.is_attr_set(new_value):
return
if net_data.get(extnet_apidef.EXTERNAL) == new_value:
return
if new_value:
net_obj.ExternalNetwork(
context, network_id=net_id).create()
net_data[extnet_apidef.EXTERNAL] = True
if allow_all:
context.session.add(rbac_db.NetworkRBAC(
object_id=net_id, action='access_as_external',
target_tenant='*', tenant_id=net_data['tenant_id']))
else:
# must make sure we do not have any external gateway ports
# (and thus, possible floating IPs) on this network before
# allow it to be update to external=False
if context.session.query(models_v2.Port.id).filter_by(
device_owner=constants.DEVICE_OWNER_ROUTER_GW,
network_id=net_data['id']).first():
raise extnet_exc.ExternalNetworkInUse(net_id=net_id)
net_obj.ExternalNetwork.delete_objects(
context, network_id=net_id)
for rbdb in (context.session.query(rbac_db.NetworkRBAC).filter_by(
object_id=net_id, action='access_as_external')):
context.session.delete(rbdb)
net_data[extnet_apidef.EXTERNAL] = False
def _process_l3_delete(self, context, network_id):
l3plugin = directory.get_plugin(plugin_constants.L3)
if l3plugin:
l3plugin.delete_disassociated_floatingips(context, network_id)
def get_external_network_id(self, context):
nets = self.get_networks(context, {extnet_apidef.EXTERNAL: [True]})
if len(nets) > 1:
raise n_exc.TooManyExternalNetworks()
else:
return nets[0]['id'] if nets else None
@registry.receives(resources.RBAC_POLICY, [events.BEFORE_CREATE])
def _process_ext_policy_create(self, resource, event, trigger, context,
object_type, policy, **kwargs):
if (object_type != 'network' or
policy['action'] != 'access_as_external'):
return
net = self.get_network(context, policy['object_id'])
if not context.is_admin and net['tenant_id'] != context.tenant_id:
msg = _("Only admins can manipulate policies on networks they "
"do not own")
raise n_exc.InvalidInput(error_message=msg)
if not self._network_is_external(context, policy['object_id']):
# we automatically convert the network into an external network
self._process_l3_update(context, net,
{extnet_apidef.EXTERNAL: True},
allow_all=False)
@registry.receives(resources.RBAC_POLICY, [events.AFTER_DELETE])
def _process_ext_policy_delete(self, resource, event, trigger, context,
object_type, policy, **kwargs):
if (object_type != 'network' or
policy['action'] != 'access_as_external'):
return
# If the network still have rbac policies, we should not
# update external attribute.
if context.session.query(rbac_db.NetworkRBAC.object_id).filter(
rbac_db.NetworkRBAC.object_id == policy['object_id'],
rbac_db.NetworkRBAC.action == 'access_as_external').count():
return
net = self.get_network(context, policy['object_id'])
self._process_l3_update(context, net,
{extnet_apidef.EXTERNAL: False})
@registry.receives(resources.RBAC_POLICY, (events.BEFORE_UPDATE,
events.BEFORE_DELETE))
def _validate_ext_not_in_use_by_tenant(self, resource, event, trigger,
context, object_type, policy,
**kwargs):
if (object_type != 'network' or
policy['action'] != 'access_as_external'):
return
new_project = None
if event == events.BEFORE_UPDATE:
new_project = kwargs['policy_update']['target_tenant']
if new_project == policy['target_tenant']:
# nothing to validate if the tenant didn't change
return
gw_ports = context.session.query(models_v2.Port.id).filter_by(
device_owner=constants.DEVICE_OWNER_ROUTER_GW,
network_id=policy['object_id'])
gw_ports = [gw_port[0] for gw_port in gw_ports]
rbac = rbac_db.NetworkRBAC
if policy['target_tenant'] != '*':
filters = {
'gw_port_id': gw_ports,
'project_id': policy['target_tenant']
}
# if there is a wildcard entry we can safely proceed without the
# router lookup because they will have access either way
if context.session.query(rbac_db.NetworkRBAC.object_id).filter(
rbac.object_id == policy['object_id'],
rbac.action == 'access_as_external',
rbac.target_tenant == '*').count():
return
router_exist = l3_obj.Router.objects_exist(context, **filters)
else:
# deleting the wildcard is okay as long as the tenants with
# attached routers have their own entries and the network is
# not the default external network.
if net_obj.ExternalNetwork.objects_exist(
context, network_id=policy['object_id'], is_default=True):
msg = _("Default external networks must be shared to "
"everyone.")
raise rbac_ext.RbacPolicyInUse(object_id=policy['object_id'],
details=msg)
projects_with_entries = (
context.session.query(rbac.target_tenant).
filter(rbac.object_id == policy['object_id'],
rbac.action == 'access_as_external',
rbac.target_tenant != '*'))
projects_with_entries = [projects_with_entry[0]
for projects_with_entry
in projects_with_entries]
if new_project:
projects_with_entries.append(new_project)
router_exist = l3_obj.Router.check_routers_not_owned_by_projects(
context, gw_ports, projects_with_entries)
if router_exist:
msg = _("There are routers attached to this network that "
"depend on this policy for access.")
raise rbac_ext.RbacPolicyInUse(object_id=policy['object_id'],
details=msg)
@registry.receives(resources.NETWORK, [events.BEFORE_DELETE])
def _before_network_delete_handler(self, resource, event, trigger,
context, network_id, **kwargs):
self._process_l3_delete(context, network_id)