From 925b4185f5857621926cb56b1422b1efdb13e7a6 Mon Sep 17 00:00:00 2001 From: Eugene Nikanorov Date: Thu, 8 Nov 2012 18:44:02 +0400 Subject: [PATCH] The change allows loading several service plugins along with core plugin. The following functionality changes were made: 1. Multiple plugins are loaded one per type 2. QuantumManager now holds dictionary {plugin_type: plugin_instance} Core plugin is stored there as well 3. Extensions are checked against all loaded plugins 4. Service plugins are specified by service_plugins option in quantum.conf file 5. Provide basic interface for service plugins 6. Introduce dummy service plugin as example and PoC 7. Service plugin's REST calls get corresponding plugin's common prefix 8. Add UTs for new extension framework functionality and for QuantumManager Implements: blueprint quantum-service-framework Change-Id: I1d00d6f848937410bccd91c852ff0871a86d7bb8 --- etc/quantum.conf | 3 + quantum/common/config.py | 2 + quantum/extensions/extensions.py | 59 +++++---- quantum/manager.py | 49 +++++++- quantum/plugins/__init__.py | 16 +++ quantum/plugins/common/__init__.py | 16 +++ quantum/plugins/common/constants.py | 26 ++++ quantum/plugins/services/__init__.py | 16 +++ quantum/plugins/services/dummy/__init__.py | 16 +++ .../plugins/services/dummy/dummy_plugin.py | 32 +++++ quantum/plugins/services/service_base.py | 35 ++++++ quantum/tests/unit/test_extensions.py | 115 ++++++++++++++++-- quantum/tests/unit/test_quantum_manager.py | 71 +++++++++++ 13 files changed, 419 insertions(+), 37 deletions(-) create mode 100644 quantum/plugins/common/__init__.py create mode 100644 quantum/plugins/common/constants.py create mode 100644 quantum/plugins/services/__init__.py create mode 100644 quantum/plugins/services/dummy/__init__.py create mode 100644 quantum/plugins/services/dummy/dummy_plugin.py create mode 100644 quantum/plugins/services/service_base.py create mode 100644 quantum/tests/unit/test_quantum_manager.py diff --git a/etc/quantum.conf b/etc/quantum.conf index 802d8bb44b7..9c837232a43 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 12c6cb4f298..5f99fc40484 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 b7af3b36c1e..7909345419c 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 bb4981a86b2..5d494a9f76a 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 e69de29bb2d..cbf4a450608 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 00000000000..cbf4a450608 --- /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 00000000000..59da9d82eda --- /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 00000000000..cbf4a450608 --- /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 00000000000..cbf4a450608 --- /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 00000000000..8b85fdb178d --- /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 00000000000..dfa074d4ef2 --- /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 b0d49e06465..8a72b4c069c 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 00000000000..a07f69469aa --- /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))