# 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]