From a6166a5e9c7fbb5d4e82f30d1dd8f077316270c8 Mon Sep 17 00:00:00 2001 From: Boris Pavlovic Date: Wed, 28 Jan 2015 04:18:27 +0300 Subject: [PATCH] Add plugin base We have various kinds of plugins. For now they are next: 1) Benchmark Engines 2) Server Provides 3) Scenarios 4) Scenario Runners 5) Context 6) SLA And maybe in future other stuff.... The core thing is next: 1) All type of plugins have configuration 2) All plugins should be accessable via unique name 3) All plugins should have ability to validate their configuration 4) All plugins should be deprecateble in the same way So it really makes sense to create single base that adds abbility to do all this stuff Change-Id: Ic11b299e0ce09b72718f5bb5504fd56218397fe1 --- rally/common/plugin.py | 74 +++++++++++++++++++++++++++++ rally/exceptions.py | 4 ++ tests/unit/common/test_plugin.py | 80 ++++++++++++++++++++++++++++++++ 3 files changed, 158 insertions(+) create mode 100644 rally/common/plugin.py create mode 100644 tests/unit/common/test_plugin.py diff --git a/rally/common/plugin.py b/rally/common/plugin.py new file mode 100644 index 0000000000..9504c08db7 --- /dev/null +++ b/rally/common/plugin.py @@ -0,0 +1,74 @@ +# Copyright 2015: Mirantis Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from rally.common import utils +from rally import exceptions + + +def deprecated(reason, rally_version): + """Put this decorator on function or class to mark plugin as deprecated. + + :param reason: Message that describes why plugin was deprecated + :param rally_version: version of Rally when this plugin was deprecated + """ + def wrapper(plugin): + plugin._plugin_deprecated = { + "reason": reason, + "rally_version": rally_version + } + return plugin + + return wrapper + + +def plugin(name): + """Put this decorator on top of plugin to specify it's name. + + This will be used for everything except Scenarios plugins. They have + different nature. + + :param name: name of plugin that is used for searching purpose + """ + + def wrapper(plugin): + plugin._plugin_name = name + return plugin + + return wrapper + + +class Plugin(object): + """Use this class as a base for all plugins in Rally.""" + + @classmethod + def get_name(cls): + return getattr(cls, "_plugin_name", None) + + @classmethod + def get(cls, name): + for _plugin in cls.get_all(): + if _plugin.get_name() == name: + return _plugin + + raise exceptions.NoSuchPlugin(name=name) + + @classmethod + def get_all(cls): + return list(utils.itersubclasses(cls)) + + @classmethod + def is_deprecated(cls): + """Return deprecation details for deprecated plugins.""" + return getattr(cls, "_plugin_deprecated", False) diff --git a/rally/exceptions.py b/rally/exceptions.py index dde1d11542..01c71cce34 100644 --- a/rally/exceptions.py +++ b/rally/exceptions.py @@ -115,6 +115,10 @@ class NotFoundException(RallyException): msg_fmt = _("Not found.") +class NoSuchPlugin(NotFoundException): + msg_fmt = _("There is no plugin with name: `%(name)s`.") + + class NoSuchEngine(NotFoundException): msg_fmt = _("There is no engine with name `%(engine_name)s`.") diff --git a/tests/unit/common/test_plugin.py b/tests/unit/common/test_plugin.py new file mode 100644 index 0000000000..87801fdc90 --- /dev/null +++ b/tests/unit/common/test_plugin.py @@ -0,0 +1,80 @@ +# Copyright 2015: Mirantis Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from rally.common import plugin +from rally import exceptions +from tests.unit import test + + +@plugin.plugin("base_plugin") +class BasePlugin(plugin.Plugin): + pass + + +@plugin.plugin("some_plugin") +class SomePlugin(BasePlugin): + pass + + +@plugin.deprecated("some_reason", "0.1.1") +@plugin.plugin("deprecated_plugin") +class DeprecatedPlugin(BasePlugin): + pass + + +class PluginModuleTestCase(test.TestCase): + + def test_deprecated(self): + + @plugin.deprecated("some", "0.0.1") + def func(): + return 42 + + self.assertEqual(func._plugin_deprecated, + {"reason": "some", "rally_version": "0.0.1"}) + + self.assertEqual(func(), 42) + + def test_plugin(self): + + @plugin.plugin(name="test") + def func(): + return 42 + + self.assertEqual(func._plugin_name, "test") + self.assertEqual(func(), 42) + + +class PluginTestCase(test.TestCase): + + def test_get_name(self): + self.assertEqual("some_plugin", SomePlugin.get_name()) + + def test_get(self): + self.assertEqual(SomePlugin, + BasePlugin.get("some_plugin")) + + def test_get_not_found(self): + self.assertRaises(exceptions.NoSuchPlugin, + BasePlugin.get, "non_existing") + + def test_get_all(self): + self.assertEqual([SomePlugin, DeprecatedPlugin], BasePlugin.get_all()) + self.assertEqual([], SomePlugin.get_all()) + + def test_is_deprecated(self): + self.assertFalse(SomePlugin.is_deprecated()) + self.assertEqual(DeprecatedPlugin.is_deprecated(), + {"reason": "some_reason", "rally_version": "0.1.1"})