diff --git a/etc/quantum.conf b/etc/quantum.conf index 802d8bb44b..9c837232a4 100644 --- a/etc/quantum.conf +++ b/etc/quantum.conf @@ -40,6 +40,9 @@ bind_port = 9696 # Quantum plugin provider module core_plugin = quantum.plugins.sample.SamplePlugin.FakePlugin +# Advanced service modules +# service_plugins = + # Paste configuration file api_paste_config = api-paste.ini diff --git a/quantum/common/config.py b/quantum/common/config.py index 12c6cb4f29..5f99fc4048 100644 --- a/quantum/common/config.py +++ b/quantum/common/config.py @@ -41,6 +41,8 @@ core_opts = [ cfg.StrOpt('auth_strategy', default='keystone'), cfg.StrOpt('core_plugin', default='quantum.plugins.sample.SamplePlugin.FakePlugin'), + cfg.ListOpt('service_plugins', + default=[]), cfg.StrOpt('base_mac', default="fa:16:3e:00:00:00"), cfg.IntOpt('mac_generation_retries', default=16), cfg.BoolOpt('allow_bulk', default=True), diff --git a/quantum/extensions/extensions.py b/quantum/extensions/extensions.py index b7af3b36c1..7909345419 100644 --- a/quantum/extensions/extensions.py +++ b/quantum/extensions/extensions.py @@ -267,26 +267,30 @@ class ExtensionMiddleware(wsgi.Middleware): # extended resources for resource in self.ext_mgr.get_resources(): + path_prefix = resource.path_prefix + if resource.parent: + path_prefix = (resource.path_prefix + + "/%s/{%s_id}" % + (resource.parent["collection_name"], + resource.parent["member_name"])) + LOG.debug(_('Extended resource: %s'), resource.collection) for action, method in resource.collection_actions.iteritems(): - path_prefix = "" - parent = resource.parent conditions = dict(method=[method]) path = "/%s/%s" % (resource.collection, action) - if parent: - path_prefix = "/%s/{%s_id}" % (parent["collection_name"], - parent["member_name"]) with mapper.submapper(controller=resource.controller, action=action, path_prefix=path_prefix, conditions=conditions) as submap: submap.connect(path) submap.connect("%s.:(format)" % path) + mapper.resource(resource.collection, resource.collection, controller=resource.controller, member=resource.member_actions, - parent_resource=resource.parent) + parent_resource=resource.parent, + path_prefix=path_prefix) # extended actions action_controllers = self._action_ext_controllers(application, @@ -534,44 +538,44 @@ class PluginAwareExtensionManager(ExtensionManager): _instance = None - def __init__(self, path, plugin): - self.plugin = plugin + def __init__(self, path, plugins): + self.plugins = plugins super(PluginAwareExtensionManager, self).__init__(path) def _check_extension(self, extension): - """Checks if plugin supports extension and implements the + """Checks if any of plugins supports extension and implements the extension contract.""" extension_is_valid = super(PluginAwareExtensionManager, self)._check_extension(extension) return (extension_is_valid and - self._plugin_supports(extension) and - self._plugin_implements_interface(extension)) + self._plugins_support(extension) and + self._plugins_implement_interface(extension)) - def _plugin_supports(self, extension): + def _plugins_support(self, extension): alias = extension.get_alias() - supports_extension = (hasattr(self.plugin, - "supported_extension_aliases") and - alias in self.plugin.supported_extension_aliases) + supports_extension = any((hasattr(plugin, + "supported_extension_aliases") and + alias in plugin.supported_extension_aliases) + for plugin in self.plugins.values()) plugin_provider = cfg.CONF.core_plugin if not supports_extension and plugin_provider in ENABLED_EXTS: supports_extension = (alias in ENABLED_EXTS[plugin_provider]['ext_alias']) if not supports_extension: - LOG.warn("extension %s not supported by plugin %s", - alias, self.plugin) + LOG.warn(_("extension %s not supported by any of loaded plugins" % + alias)) return supports_extension - def _plugin_implements_interface(self, extension): + def _plugins_implement_interface(self, extension): if(not hasattr(extension, "get_plugin_interface") or extension.get_plugin_interface() is None): return True - plugin_has_interface = isinstance(self.plugin, - extension.get_plugin_interface()) - if not plugin_has_interface: - LOG.warn("plugin %s does not implement extension's" - "plugin interface %s" % (self.plugin, - extension.get_alias())) - return plugin_has_interface + for plugin in self.plugins.values(): + if isinstance(plugin, extension.get_plugin_interface()): + return True + LOG.warn(_("Loaded plugins do not implement extension %s interface" + % extension.get_alias())) + return False @classmethod def get_instance(cls): @@ -582,7 +586,7 @@ class PluginAwareExtensionManager(ExtensionManager): LOG.debug('loading model %s', model) model_class = importutils.import_class(model) cls._instance = cls(get_extensions_path(), - QuantumManager.get_plugin()) + QuantumManager.get_service_plugins()) return cls._instance @@ -612,13 +616,14 @@ class ActionExtension(object): class ResourceExtension(object): """Add top level resources to the OpenStack API in Quantum.""" - def __init__(self, collection, controller, parent=None, + def __init__(self, collection, controller, parent=None, path_prefix="", collection_actions={}, member_actions={}): self.collection = collection self.controller = controller self.parent = parent self.collection_actions = collection_actions self.member_actions = member_actions + self.path_prefix = path_prefix # Returns the extention paths from a config entry and the __path__ diff --git a/quantum/manager.py b/quantum/manager.py index bb4981a86b..5d494a9f76 100644 --- a/quantum/manager.py +++ b/quantum/manager.py @@ -27,6 +27,7 @@ from quantum.common.exceptions import ClassNotFound from quantum.openstack.common import cfg from quantum.openstack.common import importutils from quantum.openstack.common import log as logging +from quantum.plugins.common import constants LOG = logging.getLogger(__name__) @@ -58,8 +59,52 @@ class QuantumManager(object): "Example: pip install quantum-sample-plugin") self.plugin = plugin_klass() + # core plugin as a part of plugin collection simplifies + # checking extensions + # TODO (enikanorov): make core plugin the same as + # the rest of service plugins + self.service_plugins = {constants.CORE: self.plugin} + self._load_service_plugins() + + def _load_service_plugins(self): + plugin_providers = cfg.CONF.service_plugins + LOG.debug(_("Loading service plugins: %s" % plugin_providers)) + for provider in plugin_providers: + if provider == '': + continue + try: + LOG.info(_("Loading Plugin: %s" % provider)) + plugin_class = importutils.import_class(provider) + except ClassNotFound: + LOG.exception(_("Error loading plugin")) + raise Exception(_("Plugin not found.")) + plugin_inst = plugin_class() + + # only one implementation of svc_type allowed + # specifying more than one plugin + # for the same type is a fatal exception + if plugin_inst.get_plugin_type() in self.service_plugins: + raise Exception(_("Multiple plugins for service " + "%s were configured" % + plugin_inst.get_plugin_type())) + + self.service_plugins[plugin_inst.get_plugin_type()] = plugin_inst + + LOG.debug(_("Successfully loaded %(type)s plugin. " + "Description: %(desc)s"), + {"type": plugin_inst.get_plugin_type(), + "desc": plugin_inst.get_plugin_description()}) + @classmethod - def get_plugin(cls): + def get_instance(cls): if cls._instance is None: cls._instance = cls() - return cls._instance.plugin + return cls._instance + + @classmethod + def get_plugin(cls): + return cls.get_instance().plugin + + @classmethod + def get_service_plugins(cls): + return cls.get_instance().service_plugins diff --git a/quantum/plugins/__init__.py b/quantum/plugins/__init__.py index e69de29bb2..cbf4a45060 100644 --- a/quantum/plugins/__init__.py +++ b/quantum/plugins/__init__.py @@ -0,0 +1,16 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2012 OpenStack LLC. +# 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. diff --git a/quantum/plugins/common/__init__.py b/quantum/plugins/common/__init__.py new file mode 100644 index 0000000000..cbf4a45060 --- /dev/null +++ b/quantum/plugins/common/__init__.py @@ -0,0 +1,16 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2012 OpenStack LLC. +# 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. diff --git a/quantum/plugins/common/constants.py b/quantum/plugins/common/constants.py new file mode 100644 index 0000000000..59da9d82ed --- /dev/null +++ b/quantum/plugins/common/constants.py @@ -0,0 +1,26 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2012 OpenStack LLC. +# 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. + +# service type constants: +CORE = "CORE" +DUMMY = "DUMMY" + + +COMMON_PREFIXES = { + CORE: "", + DUMMY: "/dummy_svc", +} diff --git a/quantum/plugins/services/__init__.py b/quantum/plugins/services/__init__.py new file mode 100644 index 0000000000..cbf4a45060 --- /dev/null +++ b/quantum/plugins/services/__init__.py @@ -0,0 +1,16 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2012 OpenStack LLC. +# 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. diff --git a/quantum/plugins/services/dummy/__init__.py b/quantum/plugins/services/dummy/__init__.py new file mode 100644 index 0000000000..cbf4a45060 --- /dev/null +++ b/quantum/plugins/services/dummy/__init__.py @@ -0,0 +1,16 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2012 OpenStack LLC. +# 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. diff --git a/quantum/plugins/services/dummy/dummy_plugin.py b/quantum/plugins/services/dummy/dummy_plugin.py new file mode 100644 index 0000000000..8b85fdb178 --- /dev/null +++ b/quantum/plugins/services/dummy/dummy_plugin.py @@ -0,0 +1,32 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2012 OpenStack LLC. +# 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 quantum.plugins.common import constants +from quantum.plugins.services.service_base import ServicePluginBase + + +class QuantumDummyPlugin(ServicePluginBase): + supported_extension_aliases = [] + + def __init__(self): + pass + + def get_plugin_type(self): + return constants.DUMMY + + def get_plugin_description(self): + return "Quantum Dummy Plugin" diff --git a/quantum/plugins/services/service_base.py b/quantum/plugins/services/service_base.py new file mode 100644 index 0000000000..dfa074d4ef --- /dev/null +++ b/quantum/plugins/services/service_base.py @@ -0,0 +1,35 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2012 OpenStack LLC. +# 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. + +import abc + + +class ServicePluginBase(object): + """ defines base interface for any Advanced Service plugin """ + __metaclass__ = abc.ABCMeta + supported_extension_aliases = [] + + @abc.abstractmethod + def get_plugin_type(self): + """ returns one of predefine service types. see + quantum/plugins/common/constants.py """ + pass + + @abc.abstractmethod + def get_plugin_description(self): + """ returns string description of the plugin """ + pass diff --git a/quantum/tests/unit/test_extensions.py b/quantum/tests/unit/test_extensions.py index b0d49e0646..8a72b4c069 100644 --- a/quantum/tests/unit/test_extensions.py +++ b/quantum/tests/unit/test_extensions.py @@ -33,6 +33,7 @@ from quantum.extensions.extensions import ( PluginAwareExtensionManager, ) from quantum.openstack.common import jsonutils +from quantum.plugins.common import constants from quantum.tests.unit import BaseTest from quantum.tests.unit.extension_stubs import ( ExtensionExpectingPluginInterface, @@ -43,6 +44,7 @@ from quantum.tests.unit.extension_stubs import ( import quantum.tests.unit.extensions from quantum import wsgi + LOG = logging.getLogger('quantum.tests.test_extensions') ROOTDIR = os.path.dirname(os.path.dirname(__file__)) @@ -93,6 +95,22 @@ class ResourceExtensionTest(unittest.TestCase): def custom_collection_action(self, request, **kwargs): return {'collection': 'value'} + class DummySvcPlugin(wsgi.Controller): + def get_plugin_type(self): + return constants.DUMMY + + def index(self, request, **kwargs): + return "resource index" + + def custom_member_action(self, request, **kwargs): + return {'member_action': 'value'} + + def collection_action(self, request, **kwargs): + return {'collection': 'value'} + + def show(self, request, id): + return {'data': {'id': id}} + def test_exceptions_notimplemented(self): controller = self.ResourceExtensionController() member = {'notimplemented_function': "GET"} @@ -122,6 +140,20 @@ class ResourceExtensionTest(unittest.TestCase): show_response = test_app.get("/tweedles/25266") self.assertEqual({'data': {'id': "25266"}}, show_response.json) + def test_resource_gets_prefix_of_plugin(self): + class DummySvcPlugin(wsgi.Controller): + def index(self, request): + return "" + + def get_plugin_type(self): + return constants.DUMMY + + res_ext = extensions.ResourceExtension( + 'tweedles', DummySvcPlugin(), path_prefix="/dummy_svc") + test_app = _setup_extensions_test_app(SimpleExtensionManager(res_ext)) + index_response = test_app.get("/dummy_svc/tweedles") + self.assertEqual(200, index_response.status_int) + def test_resource_extension_with_custom_member_action(self): controller = self.ResourceExtensionController() member = {'custom_member_action': "GET"} @@ -134,6 +166,53 @@ class ResourceExtensionTest(unittest.TestCase): self.assertEqual(jsonutils.loads(response.body)['member_action'], "value") + def test_resource_ext_with_custom_member_action_gets_plugin_prefix(self): + controller = self.DummySvcPlugin() + member = {'custom_member_action': "GET"} + collections = {'collection_action': "GET"} + res_ext = extensions.ResourceExtension('tweedles', controller, + path_prefix="/dummy_svc", + member_actions=member, + collection_actions=collections) + test_app = _setup_extensions_test_app(SimpleExtensionManager(res_ext)) + + response = test_app.get("/dummy_svc/tweedles/1/custom_member_action") + self.assertEqual(200, response.status_int) + self.assertEqual(jsonutils.loads(response.body)['member_action'], + "value") + + response = test_app.get("/dummy_svc/tweedles/collection_action") + self.assertEqual(200, response.status_int) + self.assertEqual(jsonutils.loads(response.body)['collection'], + "value") + + def test_plugin_prefix_with_parent_resource(self): + controller = self.DummySvcPlugin() + parent = dict(member_name="tenant", + collection_name="tenants") + member = {'custom_member_action': "GET"} + collections = {'collection_action': "GET"} + res_ext = extensions.ResourceExtension('tweedles', controller, parent, + path_prefix="/dummy_svc", + member_actions=member, + collection_actions=collections) + test_app = _setup_extensions_test_app(SimpleExtensionManager(res_ext)) + + index_response = test_app.get("/dummy_svc/tenants/1/tweedles") + self.assertEqual(200, index_response.status_int) + + response = test_app.get("/dummy_svc/tenants/1/" + "tweedles/1/custom_member_action") + self.assertEqual(200, response.status_int) + self.assertEqual(jsonutils.loads(response.body)['member_action'], + "value") + + response = test_app.get("/dummy_svc/tenants/2/" + "tweedles/collection_action") + self.assertEqual(200, response.status_int) + self.assertEqual(jsonutils.loads(response.body)['collection'], + "value") + def test_resource_extension_for_get_custom_collection_action(self): controller = self.ResourceExtensionController() collections = {'custom_collection_action': "GET"} @@ -143,6 +222,7 @@ class ResourceExtensionTest(unittest.TestCase): response = test_app.get("/tweedles/custom_collection_action") self.assertEqual(200, response.status_int) + LOG.debug(jsonutils.loads(response.body)) self.assertEqual(jsonutils.loads(response.body)['collection'], "value") def test_resource_extension_for_put_custom_collection_action(self): @@ -354,7 +434,8 @@ class PluginAwareExtensionManagerTest(unittest.TestCase): def test_unsupported_extensions_are_not_loaded(self): stub_plugin = StubPlugin(supported_extensions=["e1", "e3"]) - ext_mgr = PluginAwareExtensionManager('', stub_plugin) + ext_mgr = PluginAwareExtensionManager('', + {constants.CORE: stub_plugin}) ext_mgr.add_extension(StubExtension("e1")) ext_mgr.add_extension(StubExtension("e2")) @@ -372,21 +453,24 @@ class PluginAwareExtensionManagerTest(unittest.TestCase): """ pass - ext_mgr = PluginAwareExtensionManager('', ExtensionUnawarePlugin()) + ext_mgr = PluginAwareExtensionManager('', + {constants.CORE: + ExtensionUnawarePlugin()}) ext_mgr.add_extension(StubExtension("e1")) self.assertFalse("e1" in ext_mgr.extensions) def test_extensions_not_loaded_for_plugin_without_expected_interface(self): - class PluginWithoutExpectedInterface(object): + class PluginWithoutExpectedIface(object): """ Plugin does not implement get_foo method as expected by extension """ supported_extension_aliases = ["supported_extension"] ext_mgr = PluginAwareExtensionManager('', - PluginWithoutExpectedInterface()) + {constants.CORE: + PluginWithoutExpectedIface()}) ext_mgr.add_extension( ExtensionExpectingPluginInterface("supported_extension")) @@ -403,7 +487,8 @@ class PluginAwareExtensionManagerTest(unittest.TestCase): def get_foo(self, bar=None): pass ext_mgr = PluginAwareExtensionManager('', - PluginWithExpectedInterface()) + {constants.CORE: + PluginWithExpectedInterface()}) ext_mgr.add_extension( ExtensionExpectingPluginInterface("supported_extension")) @@ -417,7 +502,8 @@ class PluginAwareExtensionManagerTest(unittest.TestCase): """ pass stub_plugin = StubPlugin(supported_extensions=["e1"]) - ext_mgr = PluginAwareExtensionManager('', stub_plugin) + ext_mgr = PluginAwareExtensionManager('', {constants.CORE: + stub_plugin}) ext_mgr.add_extension(ExtensionForQuamtumPluginInterface("e1")) self.assertTrue("e1" in ext_mgr.extensions) @@ -432,11 +518,24 @@ class PluginAwareExtensionManagerTest(unittest.TestCase): return None stub_plugin = StubPlugin(supported_extensions=["e1"]) - ext_mgr = PluginAwareExtensionManager('', stub_plugin) + ext_mgr = PluginAwareExtensionManager('', {constants.CORE: + stub_plugin}) ext_mgr.add_extension(ExtensionWithNoNeedForPluginInterface("e1")) self.assertTrue("e1" in ext_mgr.extensions) + def test_extension_loaded_for_non_core_plugin(self): + class NonCorePluginExtenstion(StubExtension): + def get_plugin_interface(self): + return None + + stub_plugin = StubPlugin(supported_extensions=["e1"]) + ext_mgr = PluginAwareExtensionManager('', {constants.DUMMY: + stub_plugin}) + ext_mgr.add_extension(NonCorePluginExtenstion("e1")) + + self.assertTrue("e1" in ext_mgr.extensions) + class ExtensionControllerTest(unittest.TestCase): @@ -483,7 +582,7 @@ def setup_extensions_middleware(extension_manager=None): extension_manager = (extension_manager or PluginAwareExtensionManager( extensions_path, - FakePluginWithExtension())) + {constants.CORE: FakePluginWithExtension()})) config_file = 'quantum.conf.test' args = ['--config-file', etcdir(config_file)] config.parse(args=args) diff --git a/quantum/tests/unit/test_quantum_manager.py b/quantum/tests/unit/test_quantum_manager.py new file mode 100644 index 0000000000..a07f69469a --- /dev/null +++ b/quantum/tests/unit/test_quantum_manager.py @@ -0,0 +1,71 @@ +# Copyright (c) 2012 OpenStack, LLC. +# +# 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. + +import logging +import types +import unittest2 + +from quantum.common import config +from quantum.common.test_lib import test_config +from quantum.manager import QuantumManager +from quantum.openstack.common import cfg +from quantum.plugins.common import constants +from quantum.plugins.services.dummy.dummy_plugin import QuantumDummyPlugin + + +LOG = logging.getLogger(__name__) +DB_PLUGIN_KLASS = 'quantum.db.db_base_plugin_v2.QuantumDbPluginV2' + + +class QuantumManagerTestCase(unittest2.TestCase): + def setUp(self): + super(QuantumManagerTestCase, self).setUp() + + def tearDown(self): + unittest2.TestCase.tearDown(self) + cfg.CONF.reset() + QuantumManager._instance = None + + def test_service_plugin_is_loaded(self): + cfg.CONF.set_override("core_plugin", + test_config.get('plugin_name_v2', + DB_PLUGIN_KLASS)) + cfg.CONF.set_override("service_plugins", + ["quantum.plugins.services." + "dummy.dummy_plugin.QuantumDummyPlugin"]) + QuantumManager._instance = None + mgr = QuantumManager.get_instance() + plugin = mgr.get_service_plugins()[constants.DUMMY] + + self.assertTrue( + isinstance(plugin, + (QuantumDummyPlugin, types.ClassType)), + "loaded plugin should be of type QuantumDummyPlugin") + + def test_multiple_plugins_specified_for_service_type(self): + cfg.CONF.set_override("service_plugins", + ["quantum.plugins.services." + "dummy.dummy_plugin.QuantumDummyPlugin", + "quantum.plugins.services." + "dummy.dummy_plugin.QuantumDummyPlugin"]) + QuantumManager._instance = None + + try: + QuantumManager.get_instance().get_service_plugins() + self.assertTrue(False, + "Shouldn't load multiple plugins " + "for the same type") + except Exception as e: + LOG.debug(str(e))