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

Aline Manera alinefm at linux.vnet.ibm.com
Thu Jun 9 01:31:29 UTC 2016



On 06/08/2016 10:04 PM, Aline Manera wrote:
>
> One more comment below:
>

Never mind! You can ignore this comment as if the user changes the 
plugin configuration file manually he/she will need to restart wokd service.

> 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
>
> _______________________________________________
> Kimchi-devel mailing list
> Kimchi-devel at ovirt.org
> http://lists.ovirt.org/mailman/listinfo/kimchi-devel
>




More information about the Kimchi-devel mailing list