[PATCH v2][Wok 1/8] Creates pluginmanager.py module

Signed-off-by: Rodrigo Trujillo <rodrigo.trujillo@linux.vnet.ibm.com> --- src/wok/pluginsmanager.py | 238 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 238 insertions(+) create mode 100644 src/wok/pluginsmanager.py diff --git a/src/wok/pluginsmanager.py b/src/wok/pluginsmanager.py new file mode 100644 index 0000000..d799f11 --- /dev/null +++ b/src/wok/pluginsmanager.py @@ -0,0 +1,238 @@ +# +# Project Wok +# +# Copyright IBM Corp, 2016 +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +# + + +import cherrypy +import os +import xml.etree.ElementTree as ET +from cherrypy.lib.reprconf import Parser +from configobj import ConfigObj + + +from basemodel import Singleton +from wok.config import paths, PluginConfig, PluginPaths +from wok.exception import OperationFailed +from wok.utils import import_class, wok_log + + +class Plugins(): + __metaclass__ = Singleton + + def __init__(self, options=None): + # { '<PLUGIN_NAME>': { + # config: <PLUGIN_CHERRYPY_CONFIG>, + # enabled: <TRUE/FALSE>, + # uri: <PLUGIN_URI_FROM_FILE_CONFIG>, + # app: <PLUGIN_MAIN_MODULE> + # conf_file: <PLUGIN_CONFIGURATION_FILE> + self._plugins_dict = {} + self.options = options + self._init_all_plugins() + + def _load_plugin_app(self, name): + """ + Loads the plugin main module + """ + plugin_class = ('plugins.%s.%s' % (name, name[0].upper() + name[1:])) + try: + plugin_app = import_class(plugin_class)(self.options) + except ImportError, e: + wok_log.error("Failed to import plugin %s, error: %s" % + (plugin_class, e.message)) + self._plugins_dict[name]['enabled'] = False + self._plugins_dict[name]['app'] = None + return + self._plugins_dict[name]['app'] = plugin_app + + def _load_plugin_app_config(self, name): + """ + Sets the plugin's cherrypy configuration. That is a merge between Wok + standard configuration plus plugin's self configuration + """ + app_conf = {} + app_conf.update(PluginConfig(name)) + plugin_app = self._plugins_dict[name]['app'] + if plugin_app is None: + return + + # dynamically extend plugin config with custom data, if provided + get_custom_conf = getattr(plugin_app, "get_custom_conf", None) + if get_custom_conf is not None: + app_conf.update(get_custom_conf()) + + # dynamically add tools.wokauth.on = True to extra plugin APIs + try: + sub_nodes = import_class('plugins.%s.control.sub_nodes' % + name) + urlSubNodes = {} + for ident, node in sub_nodes.items(): + if node.url_auth: + ident = "/%s" % ident + urlSubNodes[ident] = {'tools.wokauth.on': True} + app_conf.update(urlSubNodes) + except ImportError, e: + wok_log.error("Failed to import subnodes for plugin %s, error: %s" + % (name, e.message)) + self._plugins_dict[name]['config'] = app_conf + + def _load_plugin_config_file(self, name): + """ + Loads the information from Wok section in <plugin>.conf file. Currently + two tags/options are required to Wok and section looks like: + [wok] + enable = True + uri = '/plugins/mypluginpath' + """ + plugin_conf_file = PluginPaths(name).conf_file + config = {} + if not os.path.exists(plugin_conf_file): + wok_log.error("Plugin configuration file %s doesn't exist." % + plugin_conf_file) + else: + try: + config = Parser().dict_from_file(plugin_conf_file) + except ValueError as e: + msg = "Failed to load plugin conf from %s: %s" + wok_log.error(msg % (plugin_conf_file, e.message)) + + plugin = self._plugins_dict[name] + plugin['conf_file'] = plugin_conf_file + plugin['enabled'] = config.get('wok', {}).get('enable', False) + plugin['uri'] = config.get('wok', {}).get('uri', 'Unknow') + + def _init_all_plugins(self): + """ + Initializes internal plugin dictionary, searching all directories in + <install_path>/wok/plugins, each directory should store a plugin + content. Then loads its configuration file in order to set as enabled + or disabled. + """ + plugin_dir = paths.plugins_dir + try: + dir_contents = os.listdir(plugin_dir) + except OSError as e: + wok_log.error("Failed to fetch plugins from '%s': %s" % + (plugin_dir, e.message)) + return + for name in dir_contents: + if os.path.isdir(os.path.join(plugin_dir, name)): + # TODO: + # Add command line option to disable plugin by its name + # + self._plugins_dict[name] = {} + self._plugins_dict[name]['config'] = {} + self._plugins_dict[name]['app'] = None + self._load_plugin_config_file(name) + + # Disable all plugins but 'sample' in test mode + if (self.options is not None) and self.options.test: + self._plugins_dict[name]['enabled'] = (name == 'sample') + + if not self._plugins_dict[name]['enabled']: + wok_log.info("Initializing plugin: %s [DISABLED]" % name) + else: + wok_log.info("Initializing plugin: %s [ENABLED]" % name) + + def _set_cherrypy_app(self, name): + """ + Load plugin module and cherrypy configuration, then set it as cherrypy + app. + """ + self._load_plugin_app(name) + self._load_plugin_app_config(name) + if self._plugins_dict[name]['enabled']: + cherrypy.tree.mount( + self._plugins_dict[name]['app'], + self._plugins_dict[name]['uri'], + self._plugins_dict[name]['config']) + wok_log.info("Plugin '%s' loaded" % + self._plugins_dict[name]['app']) + + def load_plugins(self): + """ + Set enabled plugins into Cherrypy + """ + for plugin in self.get_enabled_plugins(): + self._set_cherrypy_app(plugin) + + def get_all_plugins_info(self): + return self._plugins_dict + + def get_plugin_info(self, name): + return self._plugins_dict.get(name, {}) + + def get_all_plugins_names(self): + ret = self._plugins_dict.keys() + ret.sort() + return ret + + def get_enabled_plugins(self): + ret = [plugin for plugin in self._plugins_dict.keys() if + self._plugins_dict[plugin]['enabled']] + ret.sort() + return ret + + def _enable_plugin_conf_file(self, f_conf, enable=True): + try: + # 'unrepr' makes ConfigObj read and write characters like ("'?) + conf = ConfigObj(infile=f_conf, unrepr=True) + conf['wok']['enable'] = enable + with open(f_conf, 'wb') as f: + conf.write(f) + except Exception as e: + wok_log.error('Error updating plugin conf file. ' + e.message) + raise + + def _change_plugin_state(self, name, enable=True): + plugin = self._plugins_dict[name] + if plugin['enabled'] == enable: + return + try: + self._enable_plugin_conf_file(plugin['conf_file'], enable) + except: + raise OperationFailed('WOKPLUG0002E', {'plugin': name}) + plugin['enabled'] = enable + + def enable_plugin(self, plugin): + wok_log.info("PluginsManager: Enabling plugin '%s'" % plugin) + self._change_plugin_state(plugin, True) + + def disable_plugin(self, plugin): + wok_log.info("PluginsManager: Disabling plugin '%s'" % plugin) + self._change_plugin_state(plugin, False) + + +def get_all_tabs(): + files = [] + + for plugin in Plugins().get_enabled_plugins(): + files.append(os.path.join(PluginPaths(plugin).ui_dir, + 'config/tab-ext.xml')) + + tabs = [] + for f in files: + try: + root = ET.parse(f) + except (IOError): + wok_log.debug("Unable to load %s", f) + continue + tabs.extend([t.text.lower() for t in root.getiterator('title')]) + + return tabs -- 2.1.0
participants (1)
-
Rodrigo Trujillo