diff options
| author | Andrew Bogott <abogott@wikimedia.org> | 2012-06-13 11:51:32 -0500 |
|---|---|---|
| committer | Andrew Bogott <abogott@wikimedia.org> | 2012-07-08 00:30:01 -0500 |
| commit | 856503b064e79bc0daf1d27bf2ce1fa4ecd62160 (patch) | |
| tree | 8c5111246613d0ecf80838049d31218885f3cea0 /openstack | |
| parent | 44beb84b48051ff374bde6088688d6759a1807eb (diff) | |
| download | oslo-856503b064e79bc0daf1d27bf2ce1fa4ecd62160.tar.gz oslo-856503b064e79bc0daf1d27bf2ce1fa4ecd62160.tar.xz oslo-856503b064e79bc0daf1d27bf2ce1fa4ecd62160.zip | |
Add common plugin framework.
For blueprint novaplugins.
It turns out that most of the plugin framework applies equally well
to other projects, so it's going into Nova via common so that
other projects can pull it in with a minimum of fuss.
Change-Id: Ia4455a8ca0b8c1c3e4b0b9647e8cacaf0a47c914
Diffstat (limited to 'openstack')
| -rw-r--r-- | openstack/common/plugin/__init__.py | 14 | ||||
| -rw-r--r-- | openstack/common/plugin/callbackplugin.py | 93 | ||||
| -rw-r--r-- | openstack/common/plugin/plugin.py | 87 | ||||
| -rw-r--r-- | openstack/common/plugin/pluginmanager.py | 86 |
4 files changed, 280 insertions, 0 deletions
diff --git a/openstack/common/plugin/__init__.py b/openstack/common/plugin/__init__.py new file mode 100644 index 0000000..63c3905 --- /dev/null +++ b/openstack/common/plugin/__init__.py @@ -0,0 +1,14 @@ +# Copyright 2012 OpenStack LLC. +# 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. diff --git a/openstack/common/plugin/callbackplugin.py b/openstack/common/plugin/callbackplugin.py new file mode 100644 index 0000000..bb56a40 --- /dev/null +++ b/openstack/common/plugin/callbackplugin.py @@ -0,0 +1,93 @@ +# Copyright 2012 OpenStack LLC. +# 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 openstack.common.plugin import plugin +from openstack.common import log as logging + + +LOG = logging.getLogger(__name__) + + +class _CallbackNotifier(object): + """Manages plugin-defined notification callbacks. + + For each Plugin, a CallbackNotifier will be added to the + notification driver list. Calls to notify() with appropriate + messages will be hooked and prompt callbacks. + + A callback should look like this: + def callback(context, message, user_data) + """ + + def __init__(self): + self._callback_dict = {} + + def _add_callback(self, event_type, callback, user_data): + callback_list = self._callback_dict.get(event_type, []) + callback_list.append({'function': callback, + 'user_data': user_data}) + self._callback_dict[event_type] = callback_list + + def _remove_callback(self, callback): + for callback_list in self._callback_dict.values(): + for entry in callback_list: + if entry['function'] == callback: + callback_list.remove(entry) + + def notify(self, context, message): + if message.get('event_type') not in self._callback_dict: + return + + for entry in self._callback_dict[message.get('event_type')]: + entry['function'](context, message, entry.get('user_data')) + + def callbacks(self): + return self._callback_dict + + +class CallbackPlugin(plugin.Plugin): + """ Plugin with a simple callback interface. + + This class is provided as a convenience for producing a simple + plugin that only watches a couple of events. For example, here's + a subclass which prints a line the first time an instance is created. + + class HookInstanceCreation(CallbackPlugin): + + def __init__(self, _service_name): + super(HookInstanceCreation, self).__init__() + self._add_callback(self.magic, 'compute.instance.create.start') + + def magic(self): + print "An instance was created!" + self._remove_callback(self, self.magic) + """ + + def __init__(self, service_name): + super(CallbackPlugin, self).__init__(service_name) + self._callback_notifier = _CallbackNotifier() + self._add_notifier(self._callback_notifier) + + def _add_callback(self, callback, event_type, user_data=None): + """Add callback for a given event notification. + + Subclasses can call this as an alternative to implementing + a fullblown notify notifier. + """ + self._callback_notifier._add_callback(event_type, callback, user_data) + + def _remove_callback(self, callback): + """Remove all notification callbacks to specified function.""" + self._callback_notifier._remove_callback(callback) diff --git a/openstack/common/plugin/plugin.py b/openstack/common/plugin/plugin.py new file mode 100644 index 0000000..9f06342 --- /dev/null +++ b/openstack/common/plugin/plugin.py @@ -0,0 +1,87 @@ +# Copyright 2012 OpenStack LLC. +# 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 openstack.common import log as logging +from openstack.common.notifier import list_notifier + + +LOG = logging.getLogger(__name__) + + +class Plugin(object): + """Defines an interface for adding functionality to an OpenStack service. + + A plugin interacts with a service via the following pathways: + + - An optional set of notifiers, managed by calling add_notifier() + or by overriding _notifiers() + + - A set of api extensions, managed via add_api_extension_descriptor() + + - Direct calls to service functions. + + - Whatever else the plugin wants to do on its own. + + This is the reference implementation. + """ + + # The following functions are provided as convenience methods + # for subclasses. Subclasses should call them but probably not + # override them. + def _add_api_extension_descriptor(self, descriptor): + """Subclass convenience method which adds an extension descriptor. + + Subclass constructors should call this method when + extending a project's REST interface. + + Note that once the api service has loaded, the + API extension set is more-or-less fixed, so + this should mainly be called by subclass constructors. + """ + self._api_extension_descriptors.append(descriptor) + + def _add_notifier(self, notifier): + """Subclass convenience method which adds a notifier. + + Notifier objects should implement the function notify(message). + Each notifier receives a notify() call whenever an openstack + service broadcasts a notification. + + Best to call this during construction. Notifiers are enumerated + and registered by the pluginmanager at plugin load time. + """ + self._notifiers.append(notifier) + + # The following methods are called by OpenStack services to query + # plugin features. Subclasses should probably not override these. + def _notifiers(self): + """Returns list of notifiers for this plugin.""" + return self._notifiers + + notifiers = property(_notifiers) + + def _api_extension_descriptors(self): + """Return a list of API extension descriptors. + + Called by a project API during its load sequence. + """ + return self._api_extension_descriptors + + api_extension_descriptors = property(_api_extension_descriptors) + + # Most plugins will override this: + def __init__(self, service_name): + self._notifiers = [] + self._api_extension_descriptors = [] diff --git a/openstack/common/plugin/pluginmanager.py b/openstack/common/plugin/pluginmanager.py new file mode 100644 index 0000000..0095135 --- /dev/null +++ b/openstack/common/plugin/pluginmanager.py @@ -0,0 +1,86 @@ +# Copyright 2012 OpenStack LLC. +# 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 imp +import os +import pkg_resources + +from openstack.common import cfg +from openstack.common import log as logging +from openstack.common.notifier import list_notifier + + +CONF = cfg.CONF +LOG = logging.getLogger(__name__) + + +class PluginManager(object): + """Manages plugin entrypoints and loading. + + For a service to implement this plugin interface for callback purposes: + + - Make use of the openstack-common notifier system + - Instantiate this manager in each process (passing in + project and service name) + + For an API service to extend itself using this plugin interface, + it needs to query the plugin_extension_factory provided by + the already-instantiated PluginManager. + """ + + def __init__(self, project_name, service_name): + """ Construct Plugin Manager; load and initialize plugins. + + project_name (e.g. 'nova' or 'glance') is used + to construct the entry point that identifies plugins. + + The service_name (e.g. 'compute') is passed on to + each plugin as a raw string for it to do what it will. + """ + self._project_name = project_name + self._service_name = service_name + self.plugins = [] + + # Make sure we're using the list_notifier. + if not hasattr(CONF, "list_notifier_drivers"): + CONF.list_notifier_drivers = [] + old_notifier = CONF.notification_driver + CONF.notification_driver = 'openstack.common.notifier.list_notifier' + if (old_notifier and + old_notifier != 'openstack.common.notifier.list_notifier'): + list_notifier.add_driver(old_notifier) + + def load_plugins(self): + self.plugins = [] + + for entrypoint in pkg_resources.iter_entry_points('%s.plugin' % + self._project_name): + try: + pluginclass = entrypoint.load() + plugin = pluginclass(self._service_name) + self.plugins.append(plugin) + except Exception, exc: + LOG.error(_("Failed to load plugin %(plug)s: %(exc)s") % + {'plug': entrypoint, 'exc': exc}) + + for plugin in self.plugins: + for notifier in plugin.notifiers: + list_notifier.add_driver(notifier) + + def plugin_extension_factory(self, ext_mgr): + for plugin in self.plugins: + descriptors = plugin.api_extension_descriptors + for descriptor in descriptors: + ext_mgr.load_extension(descriptor) |
