[RFC] Move some error messages and notifications to Notifications API
by Samuel Henrique De Oliveira Guimaraes
I'm investigating a problem when uploading volumes to Storage Pools and switching tabs and I was thinking that some messages should be available no matter which tab the user is currently on. For instance, If I'm cloning or migrating a guest, uploading a volume or copying a volume from a remote host etc. and switch tabs, I should know if the task failed without having to switch back to the tab I started this action. Currently we have to do this because only when the browser goes through a function that lists the ongoing tasks inside these tabs that it will receive the error messages. However, if I have a modal window opened, it wouldn't make sense to show a message that has a different context from this opened window.
I think that a good solution for this problem would be Notification API for browsers that already support it:
https://developer.mozilla.org/en-US/docs/Web/API/notification
Support is relatively good:
http://caniuse.com/#search=notifications
Thanks,
Samuel
8 years, 5 months
Re: [Kimchi-devel] [PATCH v2][Wok 1/8] Creates pluginmanager.py module
by Aline Manera
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
8 years, 5 months
Re: [Kimchi-devel] [PATCH v2][Wok 2/8] Changes server behavior to use pluginsmanager
by Aline Manera
On 06/06/2016 04:13 PM, Rodrigo Trujillo wrote:
> Signed-off-by: Rodrigo Trujillo <rodrigo.trujillo(a)linux.vnet.ibm.com>
> ---
> src/wok/auth.py | 15 ++++++++-------
> src/wok/root.py | 8 ++++----
> src/wok/server.py | 57 ++++++-------------------------------------------------
> src/wok/utils.py | 52 --------------------------------------------------
> 4 files changed, 18 insertions(+), 114 deletions(-)
>
> diff --git a/src/wok/auth.py b/src/wok/auth.py
> index 0355e86..f8be4b5 100644
> --- a/src/wok/auth.py
> +++ b/src/wok/auth.py
> @@ -35,15 +35,15 @@ import urllib2
> from wok import template
> from wok.config import config
> from wok.exception import InvalidOperation, OperationFailed
> -from wok.utils import get_all_tabs, run_command
> +from wok.pluginsmanager import get_all_tabs
> +from wok.utils import run_command
> +
>
> USER_NAME = 'username'
> USER_GROUPS = 'groups'
> USER_ROLES = 'roles'
> REFRESH = 'robot-refresh'
> -tabs = get_all_tabs()
> -
Keep it there and update to point to the new function to avoid multiple
file processing for each server request.
> def redirect_login():
> url = "/login.html"
> @@ -85,7 +85,7 @@ class PAMUser(User):
> # after adding support to change user roles that info should be read
> # from a specific objstore and fallback to default only if any entry is
> # found
> - self.user[USER_ROLES] = dict.fromkeys(tabs, 'user')
> + self.user[USER_ROLES] = dict.fromkeys(get_all_tabs(), 'user')
>
> def get_groups(self):
> out, err, rc = run_command(['id', '-Gn', self.user[USER_NAME]])
> @@ -99,7 +99,7 @@ class PAMUser(User):
> # after adding support to change user roles that info should be
> # read from a specific objstore and fallback to default only if
> # any entry is found
> - self.user[USER_ROLES] = dict.fromkeys(tabs, 'admin')
> + self.user[USER_ROLES] = dict.fromkeys(get_all_tabs(), 'admin')
>
> return self.user[USER_ROLES]
>
> @@ -195,7 +195,7 @@ class LDAPUser(User):
> self.user[USER_GROUPS] = list()
> # FIXME: user roles will be changed according roles assignment after
> # objstore is integrated
> - self.user[USER_ROLES] = dict.fromkeys(tabs, 'user')
> + self.user[USER_ROLES] = dict.fromkeys(get_all_tabs(), 'user')
>
> @staticmethod
> def authenticate(username, password):
> @@ -236,7 +236,8 @@ class LDAPUser(User):
> "authentication", "ldap_admin_id").strip('"').split(',')
> for admin_id in admin_ids:
> if self.user[USER_NAME] == admin_id.strip():
> - self.user[USER_ROLES] = dict.fromkeys(tabs, 'admin')
> + self.user[USER_ROLES] = dict.fromkeys(get_all_tabs(),
> +
That way, on each server request the tab file will be parsed. Move it a
global variable as it was before.
> 'admin')
> return self.user[USER_ROLES]
>
> def get_user(self):
> diff --git a/src/wok/root.py b/src/wok/root.py
> index 29ea657..cb8b376 100644
> --- a/src/wok/root.py
> +++ b/src/wok/root.py
> @@ -123,10 +123,10 @@ class Root(Resource):
>
> data['scripts'] = []
> for plugin, app in cherrypy.tree.apps.iteritems():
> - if app.root.extends is not None:
> - scripts = app.root.extends.get(script_name, {})
> - if page in scripts.keys():
> - data['scripts'].append(scripts[page])
> + if app.root.extends is not None:
> + scripts = app.root.extends.get(script_name, {})
> + if page in scripts.keys():
> + data['scripts'].append(scripts[page])
>
> if page.endswith('.html'):
> context = template.render('/tabs/' + page, data)
> diff --git a/src/wok/server.py b/src/wok/server.py
> index 902d4bf..3cb257f 100644
> --- a/src/wok/server.py
> +++ b/src/wok/server.py
> @@ -30,14 +30,14 @@ from string import Template
> from wok import auth
> from wok import config
> from wok.config import config as configParser
> -from wok.config import paths, PluginConfig, WokConfig
> +from wok.config import paths, WokConfig
> from wok.control import sub_nodes
> from wok.model import model
> +from wok.pluginsmanager import Plugins
> from wok.proxy import start_proxy, terminate_proxy
> from wok.reqlogger import RequestLogger
> from wok.root import WokRoot
> from wok.safewatchedfilehandler import SafeWatchedFileHandler
> -from wok.utils import get_enabled_plugins, import_class
>
>
> LOGGING_LEVEL = {"debug": logging.DEBUG,
> @@ -171,61 +171,16 @@ class Server(object):
>
> self.app = cherrypy.tree.mount(WokRoot(model_instance, dev_env),
> config=self.configObj)
> - self._load_plugins(options)
> +
> + # Initiate and handles plugins
> + plugins = Plugins(options)
> + plugins.load_plugins()
>
> # Terminate proxy when cherrypy server is terminated
> cherrypy.engine.subscribe('exit', terminate_proxy)
>
> cherrypy.lib.sessions.init()
>
> - def _load_plugins(self, options):
> - for plugin_name, plugin_config in get_enabled_plugins():
> - try:
> - plugin_class = ('plugins.%s.%s' %
> - (plugin_name,
> - plugin_name[0].upper() + plugin_name[1:]))
> - script_name = plugin_config['wok']['uri']
> - del plugin_config['wok']
> -
> - plugin_config.update(PluginConfig(plugin_name))
> - except KeyError:
> - continue
> -
> - try:
> - plugin_app = import_class(plugin_class)(options)
> - except ImportError, e:
> - cherrypy.log.error_log.error(
> - "Failed to import plugin %s, "
> - "error: %s" % (plugin_class, e.message)
> - )
> - continue
> -
> - # 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:
> - plugin_config.update(get_custom_conf())
> -
> - # dynamically add tools.wokauth.on = True to extra plugin APIs
> - try:
> - sub_nodes = import_class('plugins.%s.control.sub_nodes' %
> - plugin_name)
> -
> - urlSubNodes = {}
> - for ident, node in sub_nodes.items():
> - if node.url_auth:
> - ident = "/%s" % ident
> - urlSubNodes[ident] = {'tools.wokauth.on': True}
> -
> - plugin_config.update(urlSubNodes)
> -
> - except ImportError, e:
> - cherrypy.log.error_log.error(
> - "Failed to import subnodes for plugin %s, "
> - "error: %s" % (plugin_class, e.message)
> - )
> -
> - cherrypy.tree.mount(plugin_app, script_name, plugin_config)
> -
> def start(self):
> # Subscribe to SignalHandler plugin
> if hasattr(cherrypy.engine, 'signal_handler'):
> diff --git a/src/wok/utils.py b/src/wok/utils.py
> index 4b3b0ac..10f2850 100644
> --- a/src/wok/utils.py
> +++ b/src/wok/utils.py
> @@ -32,16 +32,13 @@ import sqlite3
> import subprocess
> import sys
> import traceback
> -import xml.etree.ElementTree as ET
> import locale
>
> -from cherrypy.lib.reprconf import Parser
> from datetime import datetime, timedelta
> from multiprocessing import Process, Queue
> from threading import Timer
>
> from wok.asynctask import AsyncTask
> -from wok.config import paths, PluginPaths
> from wok.exception import InvalidParameter, TimeoutExpired
>
>
> @@ -76,55 +73,6 @@ def is_digit(value):
> return False
>
>
> -def _load_plugin_conf(name):
> - plugin_conf = PluginPaths(name).conf_file
> - if not os.path.exists(plugin_conf):
> - cherrypy.log.error_log.error("Plugin configuration file %s"
> - " doesn't exist." % plugin_conf)
> - return
> - try:
> - return Parser().dict_from_file(plugin_conf)
> - except ValueError as e:
> - cherrypy.log.error_log.error("Failed to load plugin "
> - "conf from %s: %s" %
> - (plugin_conf, e.message))
> -
> -
> -def get_enabled_plugins():
> - plugin_dir = paths.plugins_dir
> - try:
> - dir_contents = os.listdir(plugin_dir)
> - except OSError:
> - return
> - for name in dir_contents:
> - if os.path.isdir(os.path.join(plugin_dir, name)):
> - plugin_config = _load_plugin_conf(name)
> - try:
> - if plugin_config['wok']['enable']:
> - yield (name, plugin_config)
> - except (TypeError, KeyError):
> - continue
> -
> -
> -def get_all_tabs():
> - files = []
> -
> - for plugin, _ in 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
> -
> -
> def get_plugin_from_request():
> """
> Returns name of plugin being requested. If no plugin, returns 'wok'.
8 years, 5 months
Re: [Kimchi-devel] [PATCH v2][Wok 1/8] Creates pluginmanager.py module
by Aline Manera
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')
> +
This block should be on 'else' statement (as part of the 'try' block')
or add a 'return' in the 'if' statement as the config file was not found.
Otherwise, it will raise an exception as 'config' may be not defined if
it does not entered on 'else' block. The same will happen if it goes to
the 'except' block. So move it to 'try' block.
Tip: if you add 'return' in the 'if' statement you don't need the 'else'
anymore and eliminate one indentation level.
> + 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')
> +
Why? I could be able to see all plugins running on test mode.
For example, Kimchi provides an MockModel to allow user tests Kimchi
without making any change in the host system.
> + 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
8 years, 5 months
Re: [Kimchi-devel] [PATCH v2][Wok 8/8] Add Plugins Manager test cases
by Aline Manera
Unless the idea to only have sample plugin enabled on test mode, this
patch looks good for me.
On 06/06/2016 04:13 PM, Rodrigo Trujillo wrote:
> Test cases for new API
>
> Signed-off-by: Rodrigo Trujillo <rodrigo.trujillo(a)linux.vnet.ibm.com>
> ---
> tests/test_plugin.py | 103 +++++++++++++++++++++++++++++++++++++++++++++++++--
> 1 file changed, 99 insertions(+), 4 deletions(-)
>
> diff --git a/tests/test_plugin.py b/tests/test_plugin.py
> index d785cfa..01a3be3 100644
> --- a/tests/test_plugin.py
> +++ b/tests/test_plugin.py
> @@ -20,11 +20,13 @@
> # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
>
> import json
> +import os
> import unittest
> from functools import partial
>
> -#from wok.pluginsmanager import get_enabled_plugins
> +from wok.config import paths
> from wok.pluginsmanager import Plugins
> +from wok.utils import wok_log
>
> import utils
>
> @@ -50,9 +52,9 @@ def tearDownModule():
> test_server.stop()
>
>
> -(a)unittest.skipUnless(
> - 'sample' in Plugins().get_enabled_plugins(),
> - 'sample plugin is not enabled, skip this test!')
> +#
> +# In test mode, the plugin Sample should always be enabled
> +#
> class PluginTests(unittest.TestCase):
>
> def setUp(self):
> @@ -121,3 +123,96 @@ class PluginTests(unittest.TestCase):
> req = json.dumps({'name': 'nowidth', 'length': 40})
> resp = self.request('/plugins/sample/rectangles', req, 'POST')
> self.assertEquals(400, resp.status)
> +
> +
> +def _plugins_number():
> + """ Find number of plugins in <wokpath>/wok/plugins"""
> + plugin_dir = paths.plugins_dir
> + try:
> + return len([d for d in os.listdir(plugin_dir)
> + if os.path.isdir(os.path.join(plugin_dir, d))])
> + except OSError as e:
> + wok_log.error("Failed reading plugins directory '%s': %s" %
> + (plugin_dir, e.message))
> + return 0
> +
> +
> +class PluginsManagerTests(unittest.TestCase):
> + """
> + This class test functionalities of Plugins Manager and plugins API, using
> + the Sample plugin, which should be enabled by default during tests
> + """
> +
> + def setUp(self):
> + self.request = partial(utils.request, host, ssl_port)
> + self.info = Plugins().get_plugin_info('sample')
> + with open(self.info['conf_file']) as f:
> + self.enable_sample_plugin = ('enable = True' in f.read())
> +
> + def test_api(self):
> + # Only SAMPLE should be enabled
> + out = [{'name': 'sample', 'enabled': True}]
> +
> + # List of plugins
> + resp = self.request('/pluginsmanager', {}, 'GET')
> + plugins = json.loads(resp.read())
> + self.assertEquals(_plugins_number(), len(plugins))
> + self.assertIn(out[0], plugins)
> +
> + # Wrong plugin name
> + resp = self.request('/pluginsmanager/samplewrong', {}, 'GET')
> + self.assertEquals(404, resp.status)
> + self.assertIn('WOKPLUG0001E', json.loads(resp.read())['reason'])
> +
> + # Lookup plugin name
> + resp = self.request('/pluginsmanager/sample', {}, 'GET')
> + self.assertEquals(200, resp.status)
> + self.assertDictEqual(out[0], json.loads(resp.read()))
> +
> + # Enabling wrong plugin
> + resp = self.request('/pluginsmanager/samplewrong/enable', {}, 'POST')
> + self.assertEquals(404, resp.status)
> + self.assertIn('WOKPLUG0001E', json.loads(resp.read())['reason'])
> +
> + # Enabling Sample (already enabled, no problem)
> + resp = self.request('/pluginsmanager/sample/enable', {}, 'POST')
> + self.assertEquals(200, resp.status)
> + self.assertDictEqual(out[0], json.loads(resp.read()))
> +
> + # Disabling Sample
> + resp = self.request('/pluginsmanager/sample/disable', {}, 'POST')
> + self.assertEquals(200, resp.status)
> + out[0]['enabled'] = False
> + self.assertDictEqual(out[0], json.loads(resp.read()))
> +
> + # Disabling Sample (already disabled, no problem)
> + resp = self.request('/pluginsmanager/sample/disable', {}, 'POST')
> + self.assertEquals(200, resp.status)
> + self.assertDictEqual(out[0], json.loads(resp.read()))
> +
> + # Check config file
> + with open(self.info['conf_file']) as f:
> + self.assertIn('enable = False', f.read())
> +
> + # Backing configuration file to initial state
> + if self.enable_sample_plugin:
> + Plugins()._enable_plugin_conf_file(self.info['conf_file'])
> +
> + def test_not_allowed_methods(self):
> + # Only GET allowed in Collection
> + resp = self.request('/pluginsmanager', {}, 'POST')
> + self.assertEquals(405, resp.status)
> + resp = self.request('/pluginsmanager', {}, 'PUT')
> + self.assertEquals(405, resp.status)
> + resp = self.request('/pluginsmanager', {}, 'DELETE')
> + self.assertEquals(405, resp.status)
> +
> + # Only GET/POST allowed in resource
> + resp = self.request('/pluginsmanager/sample', {}, 'PUT')
> + self.assertEquals(405, resp.status)
> + resp = self.request('/pluginsmanager/sample', {}, 'DELETE')
> + self.assertEquals(405, resp.status)
> + resp = self.request('/pluginsmanager/sample/enable', {}, 'PUT')
> + self.assertEquals(405, resp.status)
> + resp = self.request('/pluginsmanager/sample/disable', {}, 'DELETE')
> + self.assertEquals(405, resp.status)
8 years, 5 months
Re: [Kimchi-devel] [PATCH v2][Wok 7/8] Fix test utils imports and add auth tests
by Aline Manera
On 06/06/2016 04:13 PM, Rodrigo Trujillo wrote:
> This patch add auth test to GET and POST requests in API of Plugins
> Manager. It also fixes imports in tests/utils.py
>
> Signed-off-by: Rodrigo Trujillo <rodrigo.trujillo(a)linux.vnet.ibm.com>
> ---
> tests/test_plugin.py | 5 +++--
> tests/test_server.py | 10 +++++++++-
> tests/utils.py | 7 ++++---
> 3 files changed, 16 insertions(+), 6 deletions(-)
>
> diff --git a/tests/test_plugin.py b/tests/test_plugin.py
> index 7c5e8e8..d785cfa 100644
> --- a/tests/test_plugin.py
> +++ b/tests/test_plugin.py
> @@ -23,7 +23,8 @@ import json
> import unittest
> from functools import partial
>
> -from wok.utils import get_enabled_plugins
> +#from wok.pluginsmanager import get_enabled_plugins
> +from wok.pluginsmanager import Plugins
>
> import utils
>
> @@ -50,7 +51,7 @@ def tearDownModule():
>
>
> @unittest.skipUnless(
> - 'sample' in [plugin for plugin, _config in get_enabled_plugins()],
> + 'sample' in Plugins().get_enabled_plugins(),
> 'sample plugin is not enabled, skip this test!')
> class PluginTests(unittest.TestCase):
>
> diff --git a/tests/test_server.py b/tests/test_server.py
> index 1f5a236..6e4749e 100644
> --- a/tests/test_server.py
> +++ b/tests/test_server.py
> @@ -188,12 +188,20 @@ class ServerTests(unittest.TestCase):
>
> def test_auth_protected(self):
> hdrs = {'AUTHORIZATION': ''}
> - uris = ['/tasks']
> + uris = ['/tasks',
> + '/pluginsmanager',
> + '/pluginsmanager/sample']
>
> for uri in uris:
> resp = self.request(uri, None, 'GET', hdrs)
> self.assertEquals(401, resp.status)
>
> + uris = ['/pluginsmanager/sample/disable']
> +
> + for uri in uris:
> + resp = self.request(uri, {}, 'POST', hdrs)
> + self.assertEquals(401, resp.status)
> +
> def test_auth_bad_creds(self):
> # Test HTTPBA
> hdrs = {'AUTHORIZATION': "Basic " + base64.b64encode("nouser:badpass")}
> diff --git a/tests/utils.py b/tests/utils.py
> index d158ba1..ed709e1 100644
> --- a/tests/utils.py
> +++ b/tests/utils.py
> @@ -38,8 +38,9 @@ from lxml import etree
>
> import wok.server
> from wok.config import config, PluginPaths
> -from wok.auth import User, USER_NAME, USER_GROUPS, USER_ROLES, tabs
Keep importing 'tabs' to do not parse the file multiple times.
> +from wok.auth import User, USER_NAME, USER_GROUPS, USER_ROLES
> from wok.exception import NotFoundError, OperationFailed
> +from wok.pluginsmanager import get_all_tabs
> from wok.utils import wok_log
>
>
> @@ -192,14 +193,14 @@ class FakeUser(User):
> self.user = {}
> self.user[USER_NAME] = username
> self.user[USER_GROUPS] = None
> - self.user[USER_ROLES] = dict.fromkeys(tabs, 'user')
> + self.user[USER_ROLES] = dict.fromkeys(get_all_tabs(), 'user')
>
> def get_groups(self):
> return sorted([group.gr_name for group in grp.getgrall()])[0:3]
>
> def get_roles(self):
> if self.sudo:
> - self.user[USER_ROLES] = dict.fromkeys(tabs, 'admin')
> + self.user[USER_ROLES] = dict.fromkeys(get_all_tabs(), 'admin')
> return self.user[USER_ROLES]
>
> def get_user(self):
8 years, 5 months
Re: [Kimchi-devel] [PATCH v2][Wok 6/8] Fix Sample plugin
by Aline Manera
On 06/06/2016 04:13 PM, Rodrigo Trujillo wrote:
> This patch adapts Sample plugin to work with new latest Wok plugin
> changes and to work with Plugins Manager.
> ---
> src/wok/plugins/sample/__init__.py | 54 ++++++++++++++++++++--
> src/wok/plugins/sample/control/__init__.py | 26 +++++++++++
> src/wok/plugins/sample/sample.conf.in | 22 ---------
> src/wok/plugins/sample/ui/config/tab-ext.xml | 9 ++--
> .../plugins/sample/ui/pages/sample-tab1.html.tmpl | 3 ++
> .../plugins/sample/ui/pages/sample-tab2.html.tmpl | 3 ++
> 6 files changed, 86 insertions(+), 31 deletions(-)
> create mode 100644 src/wok/plugins/sample/control/__init__.py
>
> diff --git a/src/wok/plugins/sample/__init__.py b/src/wok/plugins/sample/__init__.py
> index ff96b15..c65dd0d 100644
> --- a/src/wok/plugins/sample/__init__.py
> +++ b/src/wok/plugins/sample/__init__.py
> @@ -23,21 +23,22 @@ import json
> import os
> from cherrypy import expose
>
> -
> -from wok.config import PluginPaths
> +from wok.config import CACHEEXPIRES, PluginConfig, PluginPaths
> from wok.control.base import Collection, Resource
> from wok.root import WokRoot
>
>
> -from plugins.sample.i18n import messages
> -from plugins.sample.model import Model
> +from wok.plugins.sample.i18n import messages
> +from wok.plugins.sample.model import Model
>
>
> model = Model()
>
>
> -class Drawings(WokRoot):
> +class Sample(WokRoot):
> def __init__(self, wok_options):
> + dev_env = wok_options.environment != 'production'
> + super(Sample, self).__init__(model, dev_env)
> Resource.__init__(self, model)
As you are calling WokRoot.__init__, I don't think this
Resource.__init__ is necessary anymore.
> self.description = Description(model)
> self.rectangles = Rectangles(model)
> @@ -52,6 +53,49 @@ class Drawings(WokRoot):
> def index(self):
> return 'This is a sample plugin for Wok'
>
> + def get_custom_conf(self):
> + return SampleConfig()
> +
> +
> +class SampleConfig(PluginConfig):
> + def __init__(self):
> + super(SampleConfig, self).__init__('sample')
> +
> + sample_path = PluginPaths('sample')
> +
> + custom_config = {
> + '/help': {
> + 'tools.staticdir.on': True,
> + 'tools.staticdir.dir': os.path.join(sample_path.ui_dir,
> + 'pages/help'),
> + 'tools.nocache.on': True
> + },
> + '/images': {
> + 'tools.staticdir.on': True,
> + 'tools.staticdir.dir': os.path.join(sample_path.ui_dir,
> + 'images'),
> + 'tools.wokauth.on': False,
> + 'tools.nocache.on': False
> + },
I don't think sample plugin has any image to expose on server.
So we don't need this there.
> + '/description': {'tools.wokauth.on': True},
> + '/rectangles': {'tools.wokauth.on': True},
> + '/circles': {'tools.wokauth.on': True}
> + }
> +
> + for dirname in ('css', 'js', 'images'):
Same for js and css config.
> + custom_config['/' + dirname] = {
> + 'tools.staticdir.on': True,
> + 'tools.staticdir.dir': os.path.join(sample_path.ui_dir,
> + dirname),
> + 'tools.wokauth.on': False,
> + 'tools.nocache.on': False}
> + if dirname != 'images':
> + custom_config['/' + dirname].update({
> + 'tools.expires.on': True,
> + 'tools.expires.secs': CACHEEXPIRES})
> +
> + self.update(custom_config)
> +
>
> class Description(Resource):
> def __init__(self, model):
> diff --git a/src/wok/plugins/sample/control/__init__.py b/src/wok/plugins/sample/control/__init__.py
> new file mode 100644
> index 0000000..c87776e
> --- /dev/null
> +++ b/src/wok/plugins/sample/control/__init__.py
> @@ -0,0 +1,26 @@
> +#
> +# 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 os
> +
> +
> +from wok.control.utils import load_url_sub_node
> +
> +
> +sub_nodes = load_url_sub_node(os.path.dirname(__file__), __name__)
> diff --git a/src/wok/plugins/sample/sample.conf.in b/src/wok/plugins/sample/sample.conf.in
> index 9da33e1..1e1b8a4 100644
> --- a/src/wok/plugins/sample/sample.conf.in
> +++ b/src/wok/plugins/sample/sample.conf.in
> @@ -1,27 +1,5 @@
> [wok]
> enable = @ENABLE_SAMPLE@
> -plugin_class = "Drawings"
> uri = "/plugins/sample"
>
> -[/]
> -tools.nocache.on = True
> -tools.trailing_slash.on = False
> -tools.sessions.on = True
> -tools.sessions.name = 'wok'
> -tools.sessions.httponly = True
> -tools.sessions.locking = 'explicit'
> -tools.sessions.storage_type = 'ram'
>
> -[/description]
> -tools.wokauth.on = True
> -
> -[/rectangles]
> -tools.wokauth.on = True
> -
> -[/circles]
> -tools.wokauth.on = True
> -
> -[/help]
> -tools.staticdir.on = True
> -tools.nocache.on = True
> -tools.staticdir.dir = wok.config.PluginPaths('sample').ui_dir + '/pages/help'
> diff --git a/src/wok/plugins/sample/ui/config/tab-ext.xml b/src/wok/plugins/sample/ui/config/tab-ext.xml
> index aff0d14..bcd20fe 100644
> --- a/src/wok/plugins/sample/ui/config/tab-ext.xml
> +++ b/src/wok/plugins/sample/ui/config/tab-ext.xml
> @@ -1,17 +1,18 @@
> <?xml version="1.0" encoding="utf-8"?>
> <tabs-ext>
> + <functionality>Sample</functionality>
> <tab>
> <access role="admin" mode="admin"/>
> <access role="user" mode="none"/>
> -
> - <title>SampleTab 1</title>
> + <title>SampleTab1</title>
> <path>plugins/sample/sample-tab1.html</path>
> + <order>10</order>
> </tab>
> <tab>
> <access role="admin" mode="admin"/>
> <access role="user" mode="none"/>
> -
> - <title>SampleTab 2</title>
> + <title>SampleTab2</title>
> <path>plugins/sample/sample-tab2.html</path>
> + <order>20</order>
> </tab>
> </tabs-ext>
> diff --git a/src/wok/plugins/sample/ui/pages/sample-tab1.html.tmpl b/src/wok/plugins/sample/ui/pages/sample-tab1.html.tmpl
> index 2777848..8e4b52b 100644
> --- a/src/wok/plugins/sample/ui/pages/sample-tab1.html.tmpl
> +++ b/src/wok/plugins/sample/ui/pages/sample-tab1.html.tmpl
> @@ -22,6 +22,9 @@
> <html>
> <script type="text/javascript" src="plugins/sample/js/util.js"></script>
> <body>
> + <div>
> + Sample PAGE 1
> + </div>
> <div id="samplebody"/>
> </body>
> <script>
> diff --git a/src/wok/plugins/sample/ui/pages/sample-tab2.html.tmpl b/src/wok/plugins/sample/ui/pages/sample-tab2.html.tmpl
> index 2777848..8f92b80 100644
> --- a/src/wok/plugins/sample/ui/pages/sample-tab2.html.tmpl
> +++ b/src/wok/plugins/sample/ui/pages/sample-tab2.html.tmpl
> @@ -22,6 +22,9 @@
> <html>
> <script type="text/javascript" src="plugins/sample/js/util.js"></script>
> <body>
> + <div>
> + Sample PAGE 2
> + </div>
> <div id="samplebody"/>
> </body>
> <script>
8 years, 5 months
Re: [Kimchi-devel] [PATCH v2][Wok 5/8] Load Wok Server after options checkings
by Aline Manera
On 06/06/2016 04:13 PM, Rodrigo Trujillo wrote:
> This patch moves Server.py import right before instanciate the class.
> Then all following imports and initializations required by Server module
> are done after command line options checkings.
> This way avoid problems like init and load plugins then wokd stops due
> to wrong command line option.
Could you explain better the problem?
I checked server.py and there is no function being called on this module.
Seems it is an workaround to a deeper issue.
>
> Signed-off-by: Rodrigo Trujillo <rodrigo.trujillo(a)linux.vnet.ibm.com>
> ---
> src/wokd.in | 6 +++++-
> 1 file changed, 5 insertions(+), 1 deletion(-)
>
> diff --git a/src/wokd.in b/src/wokd.in
> index 962581d..3cf90f8 100644
> --- a/src/wokd.in
> +++ b/src/wokd.in
> @@ -27,7 +27,6 @@ sys.path.insert(1, '@pythondir@')
>
> from optparse import OptionParser
>
> -import wok.server
> import wok.config as config
>
>
> @@ -100,6 +99,11 @@ def main(options):
> setattr(options, 'max_body_size',
> config.config.get('server', 'max_body_size'))
>
> +
> + # Wok Server module must be imported after options checkings. Otherwise it
> + # is possible that server start some functions and then fail due to wrong
> + # parameters passed
> + import wok.server
> wok.server.main(options)
>
> if __name__ == '__main__':
8 years, 5 months
[RFC] Change guest boot order on REST API
by Ramon Medeiros
Propose: Kimchi allow to change boot order ("fd", "hd", "cdrom" or
"network")
hd device can specify which disk will be booted
Rest API will receive answer from
/plugins/kimchi/<VM NAME>/bootorder
{[{"dev": "hd"}]} or a list: {[{"dev": "hd"}, {"dev": "cdrom"}]}
Doubts:
How we can edit the bootloader? Like the list above or a kind of fixed
list?
Libvirt provides bootmenu too, which allows users to pick a device in
the beginning. Must have it?
--
Ramon Nunes Medeiros
Kimchi Developer
Linux Technology Center Brazil
IBM Systems & Technology Group
Phone : +55 19 2132 7878
ramonn(a)br.ibm.com
8 years, 5 months
Re: [Kimchi-devel] [PATCH v2][Wok 3/8] Implements control/model of new plugin API
by Aline Manera
On 06/06/2016 04:13 PM, Rodrigo Trujillo wrote:
> Signed-off-by: Rodrigo Trujillo <rodrigo.trujillo(a)linux.vnet.ibm.com>
> ---
> src/wok/control/plugins.py | 31 ++++++++++++++++++++++++++++---
> src/wok/i18n.py | 3 +++
> src/wok/model/plugins.py | 38 ++++++++++++++++++++++++++++++--------
> 3 files changed, 61 insertions(+), 11 deletions(-)
>
> diff --git a/src/wok/control/plugins.py b/src/wok/control/plugins.py
> index 57dfa1b..5c125eb 100644
> --- a/src/wok/control/plugins.py
> +++ b/src/wok/control/plugins.py
> @@ -19,11 +19,36 @@
> # License along with this library; if not, write to the Free Software
> # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
>
> -from wok.control.base import SimpleCollection
> +from wok.control.base import Collection, Resource, SimpleCollection
> from wok.control.utils import UrlSubNode
>
>
> -@UrlSubNode("plugins")
> -class Plugins(SimpleCollection):
> +@UrlSubNode("plugins", False)
> +class PluginsSimple(SimpleCollection):
> + """
> + Returns only enabled plugins in order to built initial Wok UI
> + """
> + def __init__(self, model):
> + super(PluginsSimple, self).__init__(model)
> + pass
> +
> +
> +@UrlSubNode("pluginsmanager", True)
> +class Plugins(Collection):
> def __init__(self, model):
> super(Plugins, self).__init__(model)
> + self.resource = Plugin
> + self.admin_methods = ['GET']
> +
> +
> +class Plugin(Resource):
> + def __init__(self, model, id):
> + super(Plugin, self).__init__(model, id)
> + self.uri_fmt = "/pluginsmanager/%s"
> + self.enable = self.generate_action_handler('enable')
> + self.disable = self.generate_action_handler('disable')
> + self.admin_methods = ['GET', 'POST']
> +
> + @property
> + def data(self):
> + return self.info
> diff --git a/src/wok/i18n.py b/src/wok/i18n.py
> index 4fc21f1..bda3949 100644
> --- a/src/wok/i18n.py
> +++ b/src/wok/i18n.py
> @@ -60,4 +60,7 @@ messages = {
> "WOKRES0001L": _("Request made on resource"),
> "WOKROOT0001L": _("User '%(username)s' logged in"),
> "WOKROOT0002L": _("User '%(username)s' logged out"),
> +
> + "WOKPLUG0001E": _("Plugin '%(plugin_name)s' not found."),
> + "WOKPLUG0002E": _("Error updating configuration file of plugin '%(plugin)s'."),
> }
> diff --git a/src/wok/model/plugins.py b/src/wok/model/plugins.py
> index 4beff44..b0ac366 100644
> --- a/src/wok/model/plugins.py
> +++ b/src/wok/model/plugins.py
> @@ -3,8 +3,6 @@
> #
> # Copyright IBM Corp, 2015-2016
> #
> -# Code derived from Project Kimchi
> -#
Any reason to remove the above line?
> # 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
> @@ -19,9 +17,17 @@
> # 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
>
> -from wok.utils import get_enabled_plugins
> +from wok.exception import NotFoundError
> +from wok.pluginsmanager import Plugins
> +
> +
> +class PluginsSimpleModel(object):
> + def __init__(self, **kargs):
> + pass
> +
> + def get_list(self):
> + return Plugins().get_enabled_plugins()
>
>
> class PluginsModel(object):
> @@ -29,7 +35,23 @@ class PluginsModel(object):
> pass
>
> def get_list(self):
> - # Will only return plugins that were loaded correctly by WOK and are
> - # properly configured in cherrypy
> - return [plugin for (plugin, config) in get_enabled_plugins()
> - if config.get('wok').get('uri') in cherrypy.tree.apps.keys()]
> + return Plugins().get_all_plugins_names()
> +
> +
> +class PluginModel(object):
> + def __init__(self, **kargs):
> + pass
> +
> + def lookup(self, plugin):
> + info = Plugins().get_plugin_info(plugin)
> + if not info:
> + raise NotFoundError('WOKPLUG0001E', {'plugin_name': plugin})
> +
> + return {'name': plugin,
> + 'enabled': info['enabled']}
> +
> + def enable(self, plugin_name):
> + Plugins().enable_plugin(plugin_name)
> +
> + def disable(self, plugin_name):
> + Plugins().disable_plugin(plugin_name)
8 years, 5 months