a37d43018b
As shown in the history of this patch, along with the work in [1], we've discussed rehoming neutron.common.rpc into lib a number of times before. One of the main reasons we've decided not to rehome in the past is that we'd hoped the neutron.common.rcp.BackingOffClient and related plumbing could be put into oslo.messaging. However, it's 2 years later and that still hasn't happened [2][3]. This patch proposes we just move forward with the rehome so that we can begin to untangle the consumers [4]. There's no reason we can't reiterate on this code in lib; it's no more difficult than in neutron. This patch includes rehoming of: - neutron.common.rpc, the only difference in the lib version is that we dynamically add all neutron_lib.exceptions by default (_DFT_EXMODS). - neutron.common.exceptions, but exceptions are broken out into their respective exception modules rather than lumping all together in a generic single module. - The fake notifier and RPC fixture, without any real changes. - A new runtime util method to dynamically load all modules for a package. For a sample neutron consumption patch see [5] that was tested with PS10 herein. [1] https://review.openstack.org/#/q/project:openstack/neutron-lib+message:rpc [2] https://review.openstack.org/#/c/407722/ [3] https://bugs.launchpad.net/oslo.messaging/+bug/1667445 [4] http://codesearch.openstack.org/?q=from%20neutron.common%20import%20rpc [5] https://review.openstack.org/#/c/579989/ Change-Id: I0052ba65973a993e088943056879bc6e982bd0b5
145 lines
4.9 KiB
Python
145 lines
4.9 KiB
Python
# 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 pkgutil
|
|
import sys
|
|
|
|
from oslo_concurrency import lockutils
|
|
from oslo_log import log as logging
|
|
from oslo_utils import importutils
|
|
from stevedore import driver
|
|
from stevedore import enabled
|
|
|
|
from neutron_lib._i18n import _
|
|
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
SYNCHRONIZED_PREFIX = 'neutron-'
|
|
|
|
# common synchronization decorator
|
|
synchronized = lockutils.synchronized_with_prefix(SYNCHRONIZED_PREFIX)
|
|
|
|
|
|
class NamespacedPlugins(object):
|
|
"""Wraps a stevedore plugin namespace to load/access its plugins."""
|
|
|
|
def __init__(self, namespace):
|
|
self.namespace = namespace
|
|
self._extension_manager = None
|
|
self._extensions = {}
|
|
self.reload()
|
|
|
|
def reload(self):
|
|
"""Force a reload of the plugins for this instances namespace.
|
|
|
|
:returns: None.
|
|
"""
|
|
self._extensions = {}
|
|
self._extension_manager = enabled.EnabledExtensionManager(
|
|
self.namespace, lambda x: True,
|
|
invoke_on_load=False)
|
|
|
|
if not self._extension_manager.names():
|
|
LOG.debug("No plugins found in namespace: ", self.namespace)
|
|
return
|
|
self._extension_manager.map(self._add_extension)
|
|
|
|
def _add_extension(self, ext):
|
|
if ext.name in self._extensions:
|
|
msg = _("Plugin '%(p)s' already in namespace: %(ns)s") % {
|
|
'p': ext.name, 'ns': self.namespace}
|
|
raise KeyError(msg)
|
|
|
|
LOG.debug("Loaded plugin '%s' from namespace: %s",
|
|
ext.name, self.namespace)
|
|
self._extensions[ext.name] = ext
|
|
|
|
def _assert_plugin_loaded(self, plugin_name):
|
|
if plugin_name not in self._extensions:
|
|
msg = _("Plugin '%(p)s' not in namespace: %(ns)s") % {
|
|
'p': plugin_name, 'ns': self.namespace}
|
|
raise KeyError(msg)
|
|
|
|
def get_plugin_class(self, plugin_name):
|
|
"""Gets a reference to a loaded plugin's class.
|
|
|
|
:param plugin_name: The name of the plugin to get the class for.
|
|
:returns: A reference to the loaded plugin's class.
|
|
:raises: KeyError if plugin_name is not loaded.
|
|
"""
|
|
self._assert_plugin_loaded(plugin_name)
|
|
return self._extensions[plugin_name].plugin
|
|
|
|
def new_plugin_instance(self, plugin_name, *args, **kwargs):
|
|
"""Create a new instance of a plugin.
|
|
|
|
:param plugin_name: The name of the plugin to instantiate.
|
|
:param args: Any args to pass onto the constructor.
|
|
:param kwargs: Any kwargs to pass onto the constructor.
|
|
:returns: A new instance of plugin_name.
|
|
:raises: KeyError if plugin_name is not loaded.
|
|
"""
|
|
self._assert_plugin_loaded(plugin_name)
|
|
return self.get_plugin_class(plugin_name)(*args, **kwargs)
|
|
|
|
@property
|
|
def loaded_plugin_names(self):
|
|
return self._extensions.keys()
|
|
|
|
|
|
def load_class_by_alias_or_classname(namespace, name):
|
|
"""Load a class using stevedore alias or the class name.
|
|
|
|
:param namespace: The namespace where the alias is defined.
|
|
:param name: The alias or class name of the class to be loaded.
|
|
:returns: Class if it can be loaded.
|
|
:raises ImportError: if class cannot be loaded.
|
|
"""
|
|
if not name:
|
|
LOG.error("Alias or class name is not set")
|
|
raise ImportError(_("Class not found."))
|
|
try:
|
|
# Try to resolve class by alias
|
|
mgr = driver.DriverManager(
|
|
namespace, name, warn_on_missing_entrypoint=False)
|
|
class_to_load = mgr.driver
|
|
except RuntimeError:
|
|
e1_info = sys.exc_info()
|
|
# Fallback to class name
|
|
try:
|
|
class_to_load = importutils.import_class(name)
|
|
except (ImportError, ValueError):
|
|
LOG.error("Error loading class by alias",
|
|
exc_info=e1_info)
|
|
LOG.error("Error loading class by class name",
|
|
exc_info=True)
|
|
raise ImportError(_("Class not found."))
|
|
return class_to_load
|
|
|
|
|
|
def list_package_modules(package_name):
|
|
"""Get a list of the modules for a given package.
|
|
|
|
:param package_name: The package name to get modules for.
|
|
:returns: A list of module objects for the said package name.
|
|
"""
|
|
pkg_mod = importutils.import_module(package_name)
|
|
modules = [pkg_mod]
|
|
|
|
for mod in pkgutil.walk_packages(pkg_mod.__path__):
|
|
_, mod_name, _ = mod
|
|
fq_name = pkg_mod.__name__ + "." + mod_name
|
|
modules.append(importutils.import_module(fq_name))
|
|
|
|
return modules
|