One more comment below:
On 06/06/2016 04:13 PM, Rodrigo Trujillo wrote:
Signed-off-by: Rodrigo Trujillo
<rodrigo.trujillo(a)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