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"})