b75a28c178
This patch removes the Neutron capability of creating database tables from sqlalchemy models for all those model classes for which a table is not found in the database schema. Migrations should be the official and only solution for creating and managing the Neutron db schema. This patch also adapts unit tests in order to ensure test schemas are still correctly created. DocImpact Update deployment documentation accordingly. Closes-Bug: #1207402 Change-Id: Ie4ee5507888ecad5f6dc32ce7a029c43014687a2 Co-Authored-By: Henry Gessau <gessau@cisco.com>
414 lines
17 KiB
Python
414 lines
17 KiB
Python
# Copyright 2012, 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.
|
|
|
|
from oslo.config import cfg
|
|
|
|
from neutron.common import exceptions as exc
|
|
from neutron.common import topics
|
|
from neutron import context as neutron_context
|
|
from neutron.db import db_base_plugin_v2
|
|
from neutron.db import external_net_db
|
|
from neutron.db import extraroute_db
|
|
from neutron.db import l3_db
|
|
from neutron.db import models_v2
|
|
from neutron.extensions import flavor as ext_flavor
|
|
from neutron.openstack.common import importutils
|
|
from neutron.openstack.common import log as logging
|
|
from neutron.plugins.metaplugin.common import config # noqa
|
|
from neutron.plugins.metaplugin import meta_db_v2
|
|
from neutron.plugins.metaplugin import meta_models_v2
|
|
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
|
|
# Hooks used to select records which belong a target plugin.
|
|
def _meta_network_model_hook(context, original_model, query):
|
|
return query.outerjoin(meta_models_v2.NetworkFlavor,
|
|
meta_models_v2.NetworkFlavor.network_id ==
|
|
models_v2.Network.id)
|
|
|
|
|
|
def _meta_port_model_hook(context, original_model, query):
|
|
return query.join(meta_models_v2.NetworkFlavor,
|
|
meta_models_v2.NetworkFlavor.network_id ==
|
|
models_v2.Port.network_id)
|
|
|
|
|
|
def _meta_flavor_filter_hook(query, filters):
|
|
if ext_flavor.FLAVOR_NETWORK in filters:
|
|
return query.filter(meta_models_v2.NetworkFlavor.flavor ==
|
|
filters[ext_flavor.FLAVOR_NETWORK][0])
|
|
return query
|
|
|
|
|
|
# Metaplugin Exceptions
|
|
class FlavorNotFound(exc.NotFound):
|
|
message = _("Flavor %(flavor)s could not be found")
|
|
|
|
|
|
class FaildToAddFlavorBinding(exc.NeutronException):
|
|
message = _("Failed to add flavor binding")
|
|
|
|
|
|
class MetaPluginV2(db_base_plugin_v2.NeutronDbPluginV2,
|
|
external_net_db.External_net_db_mixin,
|
|
extraroute_db.ExtraRoute_db_mixin):
|
|
|
|
def __init__(self, configfile=None):
|
|
super(MetaPluginV2, self).__init__()
|
|
LOG.debug(_("Start initializing metaplugin"))
|
|
self.supported_extension_aliases = ['flavor', 'external-net']
|
|
if cfg.CONF.META.supported_extension_aliases:
|
|
cfg_aliases = cfg.CONF.META.supported_extension_aliases.split(',')
|
|
self.supported_extension_aliases += cfg_aliases
|
|
|
|
# Ignore config option overapping
|
|
def _is_opt_registered(opts, opt):
|
|
if opt.dest in opts:
|
|
return True
|
|
else:
|
|
return False
|
|
|
|
cfg._is_opt_registered = _is_opt_registered
|
|
|
|
self.plugins = {}
|
|
|
|
plugin_list = [plugin_set.split(':')
|
|
for plugin_set
|
|
in cfg.CONF.META.plugin_list.split(',')]
|
|
self.rpc_flavor = cfg.CONF.META.rpc_flavor
|
|
topic_save = topics.PLUGIN
|
|
topic_fake = topic_save + '-metaplugin'
|
|
for flavor, plugin_provider in plugin_list:
|
|
# Rename topic used by a plugin other than rpc_flavor during
|
|
# loading the plugin instance if rpc_flavor is specified.
|
|
# This enforces the plugin specified by rpc_flavor is only
|
|
# consumer of 'q-plugin'. It is a bit tricky but there is no
|
|
# bad effect.
|
|
if self.rpc_flavor and self.rpc_flavor != flavor:
|
|
topics.PLUGIN = topic_fake
|
|
self.plugins[flavor] = self._load_plugin(plugin_provider)
|
|
topics.PLUGIN = topic_save
|
|
|
|
self.l3_plugins = {}
|
|
if cfg.CONF.META.l3_plugin_list:
|
|
l3_plugin_list = [plugin_set.split(':')
|
|
for plugin_set
|
|
in cfg.CONF.META.l3_plugin_list.split(',')]
|
|
for flavor, plugin_provider in l3_plugin_list:
|
|
if flavor in self.plugins:
|
|
self.l3_plugins[flavor] = self.plugins[flavor]
|
|
else:
|
|
# For l3 only plugin
|
|
self.l3_plugins[flavor] = self._load_plugin(
|
|
plugin_provider)
|
|
|
|
self.default_flavor = cfg.CONF.META.default_flavor
|
|
if self.default_flavor not in self.plugins:
|
|
raise exc.Invalid(_('default_flavor %s is not plugin list') %
|
|
self.default_flavor)
|
|
|
|
if self.l3_plugins:
|
|
self.default_l3_flavor = cfg.CONF.META.default_l3_flavor
|
|
if self.default_l3_flavor not in self.l3_plugins:
|
|
raise exc.Invalid(_('default_l3_flavor %s is not plugin list')
|
|
% self.default_l3_flavor)
|
|
self.supported_extension_aliases += ['router', 'ext-gw-mode',
|
|
'extraroute']
|
|
|
|
if self.rpc_flavor and self.rpc_flavor not in self.plugins:
|
|
raise exc.Invalid(_('rpc_flavor %s is not plugin list') %
|
|
self.rpc_flavor)
|
|
|
|
self.extension_map = {}
|
|
if not cfg.CONF.META.extension_map == '':
|
|
extension_list = [method_set.split(':')
|
|
for method_set
|
|
in cfg.CONF.META.extension_map.split(',')]
|
|
for method_name, flavor in extension_list:
|
|
self.extension_map[method_name] = flavor
|
|
|
|
# Register hooks.
|
|
# The hooks are applied for each target plugin instance when
|
|
# calling the base class to get networks/ports so that only records
|
|
# which belong to the plugin are selected.
|
|
#NOTE: Doing registration here (within __init__()) is to avoid
|
|
# registration when merely importing this file. This is only
|
|
# for running whole unit tests.
|
|
db_base_plugin_v2.NeutronDbPluginV2.register_model_query_hook(
|
|
models_v2.Network,
|
|
'metaplugin_net',
|
|
_meta_network_model_hook,
|
|
None,
|
|
_meta_flavor_filter_hook)
|
|
db_base_plugin_v2.NeutronDbPluginV2.register_model_query_hook(
|
|
models_v2.Port,
|
|
'metaplugin_port',
|
|
_meta_port_model_hook,
|
|
None,
|
|
_meta_flavor_filter_hook)
|
|
|
|
def _load_plugin(self, plugin_provider):
|
|
LOG.debug(_("Plugin location: %s"), plugin_provider)
|
|
plugin_klass = importutils.import_class(plugin_provider)
|
|
return plugin_klass()
|
|
|
|
def _get_plugin(self, flavor):
|
|
if flavor not in self.plugins:
|
|
raise FlavorNotFound(flavor=flavor)
|
|
return self.plugins[flavor]
|
|
|
|
def _get_l3_plugin(self, flavor):
|
|
if flavor not in self.l3_plugins:
|
|
raise FlavorNotFound(flavor=flavor)
|
|
return self.l3_plugins[flavor]
|
|
|
|
def __getattr__(self, key):
|
|
# At first, try to pickup extension command from extension_map
|
|
|
|
if key in self.extension_map:
|
|
flavor = self.extension_map[key]
|
|
plugin = self._get_plugin(flavor)
|
|
if plugin and hasattr(plugin, key):
|
|
return getattr(plugin, key)
|
|
|
|
# Second, try to match extension method in order of plugin list
|
|
for flavor, plugin in self.plugins.items():
|
|
if hasattr(plugin, key):
|
|
return getattr(plugin, key)
|
|
|
|
# if no plugin support the method, then raise
|
|
raise AttributeError
|
|
|
|
def _extend_network_dict(self, context, network):
|
|
flavor = self._get_flavor_by_network_id(context, network['id'])
|
|
network[ext_flavor.FLAVOR_NETWORK] = flavor
|
|
|
|
def start_rpc_listeners(self):
|
|
return self.plugins[self.rpc_flavor].start_rpc_listeners()
|
|
|
|
def rpc_workers_supported(self):
|
|
#NOTE: If a plugin which supports multiple RPC workers is desired
|
|
# to handle RPC, rpc_flavor must be specified.
|
|
return (self.rpc_flavor and
|
|
self.plugins[self.rpc_flavor].rpc_workers_supported())
|
|
|
|
def create_network(self, context, network):
|
|
n = network['network']
|
|
flavor = n.get(ext_flavor.FLAVOR_NETWORK)
|
|
if str(flavor) not in self.plugins:
|
|
flavor = self.default_flavor
|
|
plugin = self._get_plugin(flavor)
|
|
net = plugin.create_network(context, network)
|
|
LOG.debug(_("Created network: %(net_id)s with flavor "
|
|
"%(flavor)s"), {'net_id': net['id'], 'flavor': flavor})
|
|
try:
|
|
meta_db_v2.add_network_flavor_binding(context.session,
|
|
flavor, str(net['id']))
|
|
except Exception:
|
|
LOG.exception(_('Failed to add flavor bindings'))
|
|
plugin.delete_network(context, net['id'])
|
|
raise FaildToAddFlavorBinding()
|
|
|
|
LOG.debug(_("Created network: %s"), net['id'])
|
|
self._extend_network_dict(context, net)
|
|
return net
|
|
|
|
def update_network(self, context, id, network):
|
|
flavor = meta_db_v2.get_flavor_by_network(context.session, id)
|
|
plugin = self._get_plugin(flavor)
|
|
return plugin.update_network(context, id, network)
|
|
|
|
def delete_network(self, context, id):
|
|
flavor = meta_db_v2.get_flavor_by_network(context.session, id)
|
|
plugin = self._get_plugin(flavor)
|
|
return plugin.delete_network(context, id)
|
|
|
|
def get_network(self, context, id, fields=None):
|
|
flavor = meta_db_v2.get_flavor_by_network(context.session, id)
|
|
plugin = self._get_plugin(flavor)
|
|
net = plugin.get_network(context, id, fields)
|
|
net['id'] = id
|
|
if not fields or ext_flavor.FLAVOR_NETWORK in fields:
|
|
self._extend_network_dict(context, net)
|
|
if fields and 'id' not in fields:
|
|
del net['id']
|
|
return net
|
|
|
|
def get_networks(self, context, filters=None, fields=None):
|
|
nets = []
|
|
for flavor, plugin in self.plugins.items():
|
|
if (filters and ext_flavor.FLAVOR_NETWORK in filters and
|
|
not flavor in filters[ext_flavor.FLAVOR_NETWORK]):
|
|
continue
|
|
if filters:
|
|
#NOTE: copy each time since a target plugin may modify
|
|
# plugin_filters.
|
|
plugin_filters = filters.copy()
|
|
else:
|
|
plugin_filters = {}
|
|
plugin_filters[ext_flavor.FLAVOR_NETWORK] = [flavor]
|
|
plugin_nets = plugin.get_networks(context, plugin_filters, fields)
|
|
for net in plugin_nets:
|
|
if not fields or ext_flavor.FLAVOR_NETWORK in fields:
|
|
net[ext_flavor.FLAVOR_NETWORK] = flavor
|
|
nets.append(net)
|
|
return nets
|
|
|
|
def _get_flavor_by_network_id(self, context, network_id):
|
|
return meta_db_v2.get_flavor_by_network(context.session, network_id)
|
|
|
|
def _get_flavor_by_router_id(self, context, router_id):
|
|
return meta_db_v2.get_flavor_by_router(context.session, router_id)
|
|
|
|
def _get_plugin_by_network_id(self, context, network_id):
|
|
flavor = self._get_flavor_by_network_id(context, network_id)
|
|
return self._get_plugin(flavor)
|
|
|
|
def create_port(self, context, port):
|
|
p = port['port']
|
|
if 'network_id' not in p:
|
|
raise exc.NotFound
|
|
plugin = self._get_plugin_by_network_id(context, p['network_id'])
|
|
return plugin.create_port(context, port)
|
|
|
|
def update_port(self, context, id, port):
|
|
port_in_db = self._get_port(context, id)
|
|
plugin = self._get_plugin_by_network_id(context,
|
|
port_in_db['network_id'])
|
|
return plugin.update_port(context, id, port)
|
|
|
|
def delete_port(self, context, id, l3_port_check=True):
|
|
port_in_db = self._get_port(context, id)
|
|
plugin = self._get_plugin_by_network_id(context,
|
|
port_in_db['network_id'])
|
|
return plugin.delete_port(context, id, l3_port_check)
|
|
|
|
# This is necessary since there is a case that
|
|
# NeutronManager.get_plugin()._make_port_dict is called.
|
|
def _make_port_dict(self, port):
|
|
context = neutron_context.get_admin_context()
|
|
plugin = self._get_plugin_by_network_id(context,
|
|
port['network_id'])
|
|
return plugin._make_port_dict(port)
|
|
|
|
def get_port(self, context, id, fields=None):
|
|
port_in_db = self._get_port(context, id)
|
|
plugin = self._get_plugin_by_network_id(context,
|
|
port_in_db['network_id'])
|
|
return plugin.get_port(context, id, fields)
|
|
|
|
def get_ports(self, context, filters=None, fields=None):
|
|
all_ports = []
|
|
for flavor, plugin in self.plugins.items():
|
|
if filters:
|
|
#NOTE: copy each time since a target plugin may modify
|
|
# plugin_filters.
|
|
plugin_filters = filters.copy()
|
|
else:
|
|
plugin_filters = {}
|
|
plugin_filters[ext_flavor.FLAVOR_NETWORK] = [flavor]
|
|
ports = plugin.get_ports(context, plugin_filters, fields)
|
|
all_ports += ports
|
|
return all_ports
|
|
|
|
def create_subnet(self, context, subnet):
|
|
s = subnet['subnet']
|
|
if 'network_id' not in s:
|
|
raise exc.NotFound
|
|
plugin = self._get_plugin_by_network_id(context,
|
|
s['network_id'])
|
|
return plugin.create_subnet(context, subnet)
|
|
|
|
def update_subnet(self, context, id, subnet):
|
|
s = self.get_subnet(context, id)
|
|
plugin = self._get_plugin_by_network_id(context,
|
|
s['network_id'])
|
|
return plugin.update_subnet(context, id, subnet)
|
|
|
|
def delete_subnet(self, context, id):
|
|
s = self.get_subnet(context, id)
|
|
plugin = self._get_plugin_by_network_id(context,
|
|
s['network_id'])
|
|
return plugin.delete_subnet(context, id)
|
|
|
|
def _extend_router_dict(self, context, router):
|
|
flavor = self._get_flavor_by_router_id(context, router['id'])
|
|
router[ext_flavor.FLAVOR_ROUTER] = flavor
|
|
|
|
def create_router(self, context, router):
|
|
r = router['router']
|
|
flavor = r.get(ext_flavor.FLAVOR_ROUTER)
|
|
if str(flavor) not in self.l3_plugins:
|
|
flavor = self.default_l3_flavor
|
|
plugin = self._get_l3_plugin(flavor)
|
|
r_in_db = plugin.create_router(context, router)
|
|
LOG.debug(_("Created router: %(router_id)s with flavor "
|
|
"%(flavor)s"),
|
|
{'router_id': r_in_db['id'], 'flavor': flavor})
|
|
try:
|
|
meta_db_v2.add_router_flavor_binding(context.session,
|
|
flavor, str(r_in_db['id']))
|
|
except Exception:
|
|
LOG.exception(_('Failed to add flavor bindings'))
|
|
plugin.delete_router(context, r_in_db['id'])
|
|
raise FaildToAddFlavorBinding()
|
|
|
|
LOG.debug(_("Created router: %s"), r_in_db['id'])
|
|
self._extend_router_dict(context, r_in_db)
|
|
return r_in_db
|
|
|
|
def update_router(self, context, id, router):
|
|
flavor = meta_db_v2.get_flavor_by_router(context.session, id)
|
|
plugin = self._get_l3_plugin(flavor)
|
|
return plugin.update_router(context, id, router)
|
|
|
|
def delete_router(self, context, id):
|
|
flavor = meta_db_v2.get_flavor_by_router(context.session, id)
|
|
plugin = self._get_l3_plugin(flavor)
|
|
return plugin.delete_router(context, id)
|
|
|
|
def get_router(self, context, id, fields=None):
|
|
flavor = meta_db_v2.get_flavor_by_router(context.session, id)
|
|
plugin = self._get_l3_plugin(flavor)
|
|
router = plugin.get_router(context, id, fields)
|
|
if not fields or ext_flavor.FLAVOR_ROUTER in fields:
|
|
self._extend_router_dict(context, router)
|
|
return router
|
|
|
|
def get_routers_with_flavor(self, context, filters=None,
|
|
fields=None):
|
|
collection = self._model_query(context, l3_db.Router)
|
|
r_model = meta_models_v2.RouterFlavor
|
|
collection = collection.join(r_model,
|
|
l3_db.Router.id == r_model.router_id)
|
|
if filters:
|
|
for key, value in filters.iteritems():
|
|
if key == ext_flavor.FLAVOR_ROUTER:
|
|
column = meta_models_v2.RouterFlavor.flavor
|
|
else:
|
|
column = getattr(l3_db.Router, key, None)
|
|
if column:
|
|
collection = collection.filter(column.in_(value))
|
|
return [self._make_router_dict(c, fields) for c in collection]
|
|
|
|
def get_routers(self, context, filters=None, fields=None):
|
|
routers = self.get_routers_with_flavor(context, filters,
|
|
None)
|
|
return [self.get_router(context, router['id'],
|
|
fields)
|
|
for router in routers]
|