[PATCH][Kimchi 0/5] Make Kimchi able to change guest's boot order
by Ramon Medeiros
Ramon Medeiros (5):
Create method to change bootorder of a guest
Update documentation about bootorder on vm update
Update REST API
Add function to retrieve bootorder on vm lookup
Add test to check bootorder
API.json | 12 ++++++++++++
docs/API.md | 2 ++
i18n.py | 2 ++
model/vms.py | 41 +++++++++++++++++++++++++++++++++++++++--
tests/test_model.py | 5 +++++
5 files changed, 60 insertions(+), 2 deletions(-)
--
2.5.5
8 years, 5 months
[PATCH][Kimchi] Substitute quotes by apostrophe in configuration file
by Rodrigo Trujillo
Change kimchi.conf file to use new Wok Plugin Manager style.
Signed-off-by: Rodrigo Trujillo <rodrigo.trujillo(a)linux.vnet.ibm.com>
---
kimchi.conf | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/kimchi.conf b/kimchi.conf
index c451703..d3c3317 100644
--- a/kimchi.conf
+++ b/kimchi.conf
@@ -3,7 +3,7 @@
enable = True
# Root URI for Kimchi APIs
-uri = "/plugins/kimchi"
+uri = '/plugins/kimchi'
[kimchi]
# Federation feature: register Wok server on openSLP and discover peers
--
2.1.0
8 years, 5 months
[PATCH v2][Wok 8/8] Add Plugins Manager test cases
by Rodrigo Trujillo
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)
--
2.1.0
8 years, 5 months
[PATCH v2][Wok 7/8] Fix test utils imports and add auth tests
by Rodrigo Trujillo
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
+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):
--
2.1.0
8 years, 5 months
[PATCH v2][Wok 6/8] Fix Sample plugin
by Rodrigo Trujillo
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)
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
+ },
+ '/description': {'tools.wokauth.on': True},
+ '/rectangles': {'tools.wokauth.on': True},
+ '/circles': {'tools.wokauth.on': True}
+ }
+
+ for dirname in ('css', 'js', 'images'):
+ 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>
--
2.1.0
8 years, 5 months
[PATCH v2][Wok 5/8] Load Wok Server after options checkings
by Rodrigo Trujillo
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.
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__':
--
2.1.0
8 years, 5 months
[PATCH v2][Wok 4/8] Change plugins API documentation by pluginsmanager API
by Rodrigo Trujillo
This patch creates the documentation for pluginsmanager API
Signed-off-by: Rodrigo Trujillo <rodrigo.trujillo(a)linux.vnet.ibm.com>
---
docs/API/plugins.md | 13 ----------
docs/API/pluginsmanager.md | 61 ++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 61 insertions(+), 13 deletions(-)
delete mode 100644 docs/API/plugins.md
create mode 100644 docs/API/pluginsmanager.md
diff --git a/docs/API/plugins.md b/docs/API/plugins.md
deleted file mode 100644
index aaa37b5..0000000
--- a/docs/API/plugins.md
+++ /dev/null
@@ -1,13 +0,0 @@
-## REST API Specification for Plugins
-
-### Collection: Plugins
-
-**URI:** /plugins
-
-**Methods:**
-
-* **GET**: Retrieve a summarized list names of all UI Plugins
-
-#### Examples
-GET /plugins
-[pluginA, pluginB, pluginC]
diff --git a/docs/API/pluginsmanager.md b/docs/API/pluginsmanager.md
new file mode 100644
index 0000000..b79c8ad
--- /dev/null
+++ b/docs/API/pluginsmanager.md
@@ -0,0 +1,61 @@
+## REST API Specification for Plugins Manager
+
+### Collection: Plugins
+
+**URI:** /pluginsmanager
+
+**Methods:**
+
+* **GET**: Retrieve a list of all Plugins configured in Wok, showing name and state.
+
+#### Examples
+GET /pluginsmanager
+[
+ {
+ "enabled":true,
+ "name":"gingerbase"
+ },
+ {
+ "enabled":true,
+ "name":"kimchi"
+ },
+ {
+ "enabled":false,
+ "name":"sample"
+ }
+]
+
+### Resource: Plugin
+
+**URI:** /pluginsmanager/*:name*
+
+A plugin represents a software that will make use of infrastructure provided
+by Wok. It should be installed in a directory with its <name> in Wok directory
+'plugins' and contains an <name>.conf file.
+Example: "/usr/lib/python2.7/site-packages/wok/plugins/kimchi"
+
+**Methods:**
+
+* **GET**: Retrieve the full description of the plugin.
+ * name: The plugin name
+ * enabled: State of the plugin. If not enabled, Wok will not load it
+* **POST**: *See Task Actions*
+
+**Actions (POST):**
+
+* enable: enable a plugin. Also changes plugin's configuration file
+
+* disable: disable a plugin. Also changes plugin's configuration file
+
+#### Examples
+GET /pluginsmanager/sample
+{
+ "enabled":false,
+ "name":"sample"
+}
+
+POST /pluginsmanager/sample/enable
+{
+ "enabled":true,
+ "name":"sample"
+}
--
2.1.0
8 years, 5 months
[PATCH v2][Wok 3/8] Implements control/model of new plugin API
by Rodrigo Trujillo
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
-#
# 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)
--
2.1.0
8 years, 5 months
[PATCH v2][Wok 2/8] Changes server behavior to use pluginsmanager
by Rodrigo Trujillo
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()
-
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(),
+ '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'.
--
2.1.0
8 years, 5 months
[PATCH v2][Wok 1/8] Creates pluginmanager.py module
by Rodrigo Trujillo
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
+
+ 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
8 years, 5 months