[Kimchi-devel] [PATCH v2][Wok 1/8] Creates pluginmanager.py module
Aline Manera
alinefm at linux.vnet.ibm.com
Thu Jun 9 01:04:50 UTC 2016
One more comment below:
On 06/06/2016 04:13 PM, Rodrigo Trujillo wrote:
> Signed-off-by: Rodrigo Trujillo <rodrigo.trujillo at 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
> +
You should keep the plugin configuration file to make sure a plugin is
enabled or not.
Using a cache (self._plugins_dict) may contain invalid data. Let's say
the user changed the plugin configuration file manually or
uninstall/install a plugin.
We should also deal with accurate data.
> + 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
More information about the Kimchi-devel
mailing list