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
This commit is contained in:
parent
a06fd815bf
commit
925b4185f5
@ -40,6 +40,9 @@ bind_port = 9696
|
|||||||
# Quantum plugin provider module
|
# Quantum plugin provider module
|
||||||
core_plugin = quantum.plugins.sample.SamplePlugin.FakePlugin
|
core_plugin = quantum.plugins.sample.SamplePlugin.FakePlugin
|
||||||
|
|
||||||
|
# Advanced service modules
|
||||||
|
# service_plugins =
|
||||||
|
|
||||||
# Paste configuration file
|
# Paste configuration file
|
||||||
api_paste_config = api-paste.ini
|
api_paste_config = api-paste.ini
|
||||||
|
|
||||||
|
@ -41,6 +41,8 @@ core_opts = [
|
|||||||
cfg.StrOpt('auth_strategy', default='keystone'),
|
cfg.StrOpt('auth_strategy', default='keystone'),
|
||||||
cfg.StrOpt('core_plugin',
|
cfg.StrOpt('core_plugin',
|
||||||
default='quantum.plugins.sample.SamplePlugin.FakePlugin'),
|
default='quantum.plugins.sample.SamplePlugin.FakePlugin'),
|
||||||
|
cfg.ListOpt('service_plugins',
|
||||||
|
default=[]),
|
||||||
cfg.StrOpt('base_mac', default="fa:16:3e:00:00:00"),
|
cfg.StrOpt('base_mac', default="fa:16:3e:00:00:00"),
|
||||||
cfg.IntOpt('mac_generation_retries', default=16),
|
cfg.IntOpt('mac_generation_retries', default=16),
|
||||||
cfg.BoolOpt('allow_bulk', default=True),
|
cfg.BoolOpt('allow_bulk', default=True),
|
||||||
|
@ -267,26 +267,30 @@ class ExtensionMiddleware(wsgi.Middleware):
|
|||||||
|
|
||||||
# extended resources
|
# extended resources
|
||||||
for resource in self.ext_mgr.get_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'),
|
LOG.debug(_('Extended resource: %s'),
|
||||||
resource.collection)
|
resource.collection)
|
||||||
for action, method in resource.collection_actions.iteritems():
|
for action, method in resource.collection_actions.iteritems():
|
||||||
path_prefix = ""
|
|
||||||
parent = resource.parent
|
|
||||||
conditions = dict(method=[method])
|
conditions = dict(method=[method])
|
||||||
path = "/%s/%s" % (resource.collection, action)
|
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,
|
with mapper.submapper(controller=resource.controller,
|
||||||
action=action,
|
action=action,
|
||||||
path_prefix=path_prefix,
|
path_prefix=path_prefix,
|
||||||
conditions=conditions) as submap:
|
conditions=conditions) as submap:
|
||||||
submap.connect(path)
|
submap.connect(path)
|
||||||
submap.connect("%s.:(format)" % path)
|
submap.connect("%s.:(format)" % path)
|
||||||
|
|
||||||
mapper.resource(resource.collection, resource.collection,
|
mapper.resource(resource.collection, resource.collection,
|
||||||
controller=resource.controller,
|
controller=resource.controller,
|
||||||
member=resource.member_actions,
|
member=resource.member_actions,
|
||||||
parent_resource=resource.parent)
|
parent_resource=resource.parent,
|
||||||
|
path_prefix=path_prefix)
|
||||||
|
|
||||||
# extended actions
|
# extended actions
|
||||||
action_controllers = self._action_ext_controllers(application,
|
action_controllers = self._action_ext_controllers(application,
|
||||||
@ -534,44 +538,44 @@ class PluginAwareExtensionManager(ExtensionManager):
|
|||||||
|
|
||||||
_instance = None
|
_instance = None
|
||||||
|
|
||||||
def __init__(self, path, plugin):
|
def __init__(self, path, plugins):
|
||||||
self.plugin = plugin
|
self.plugins = plugins
|
||||||
super(PluginAwareExtensionManager, self).__init__(path)
|
super(PluginAwareExtensionManager, self).__init__(path)
|
||||||
|
|
||||||
def _check_extension(self, extension):
|
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 contract."""
|
||||||
extension_is_valid = super(PluginAwareExtensionManager,
|
extension_is_valid = super(PluginAwareExtensionManager,
|
||||||
self)._check_extension(extension)
|
self)._check_extension(extension)
|
||||||
return (extension_is_valid and
|
return (extension_is_valid and
|
||||||
self._plugin_supports(extension) and
|
self._plugins_support(extension) and
|
||||||
self._plugin_implements_interface(extension))
|
self._plugins_implement_interface(extension))
|
||||||
|
|
||||||
def _plugin_supports(self, extension):
|
def _plugins_support(self, extension):
|
||||||
alias = extension.get_alias()
|
alias = extension.get_alias()
|
||||||
supports_extension = (hasattr(self.plugin,
|
supports_extension = any((hasattr(plugin,
|
||||||
"supported_extension_aliases") and
|
"supported_extension_aliases") and
|
||||||
alias in self.plugin.supported_extension_aliases)
|
alias in plugin.supported_extension_aliases)
|
||||||
|
for plugin in self.plugins.values())
|
||||||
plugin_provider = cfg.CONF.core_plugin
|
plugin_provider = cfg.CONF.core_plugin
|
||||||
if not supports_extension and plugin_provider in ENABLED_EXTS:
|
if not supports_extension and plugin_provider in ENABLED_EXTS:
|
||||||
supports_extension = (alias in
|
supports_extension = (alias in
|
||||||
ENABLED_EXTS[plugin_provider]['ext_alias'])
|
ENABLED_EXTS[plugin_provider]['ext_alias'])
|
||||||
if not supports_extension:
|
if not supports_extension:
|
||||||
LOG.warn("extension %s not supported by plugin %s",
|
LOG.warn(_("extension %s not supported by any of loaded plugins" %
|
||||||
alias, self.plugin)
|
alias))
|
||||||
return supports_extension
|
return supports_extension
|
||||||
|
|
||||||
def _plugin_implements_interface(self, extension):
|
def _plugins_implement_interface(self, extension):
|
||||||
if(not hasattr(extension, "get_plugin_interface") or
|
if(not hasattr(extension, "get_plugin_interface") or
|
||||||
extension.get_plugin_interface() is None):
|
extension.get_plugin_interface() is None):
|
||||||
return True
|
return True
|
||||||
plugin_has_interface = isinstance(self.plugin,
|
for plugin in self.plugins.values():
|
||||||
extension.get_plugin_interface())
|
if isinstance(plugin, extension.get_plugin_interface()):
|
||||||
if not plugin_has_interface:
|
return True
|
||||||
LOG.warn("plugin %s does not implement extension's"
|
LOG.warn(_("Loaded plugins do not implement extension %s interface"
|
||||||
"plugin interface %s" % (self.plugin,
|
% extension.get_alias()))
|
||||||
extension.get_alias()))
|
return False
|
||||||
return plugin_has_interface
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_instance(cls):
|
def get_instance(cls):
|
||||||
@ -582,7 +586,7 @@ class PluginAwareExtensionManager(ExtensionManager):
|
|||||||
LOG.debug('loading model %s', model)
|
LOG.debug('loading model %s', model)
|
||||||
model_class = importutils.import_class(model)
|
model_class = importutils.import_class(model)
|
||||||
cls._instance = cls(get_extensions_path(),
|
cls._instance = cls(get_extensions_path(),
|
||||||
QuantumManager.get_plugin())
|
QuantumManager.get_service_plugins())
|
||||||
return cls._instance
|
return cls._instance
|
||||||
|
|
||||||
|
|
||||||
@ -612,13 +616,14 @@ class ActionExtension(object):
|
|||||||
class ResourceExtension(object):
|
class ResourceExtension(object):
|
||||||
"""Add top level resources to the OpenStack API in Quantum."""
|
"""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={}):
|
collection_actions={}, member_actions={}):
|
||||||
self.collection = collection
|
self.collection = collection
|
||||||
self.controller = controller
|
self.controller = controller
|
||||||
self.parent = parent
|
self.parent = parent
|
||||||
self.collection_actions = collection_actions
|
self.collection_actions = collection_actions
|
||||||
self.member_actions = member_actions
|
self.member_actions = member_actions
|
||||||
|
self.path_prefix = path_prefix
|
||||||
|
|
||||||
|
|
||||||
# Returns the extention paths from a config entry and the __path__
|
# Returns the extention paths from a config entry and the __path__
|
||||||
|
@ -27,6 +27,7 @@ from quantum.common.exceptions import ClassNotFound
|
|||||||
from quantum.openstack.common import cfg
|
from quantum.openstack.common import cfg
|
||||||
from quantum.openstack.common import importutils
|
from quantum.openstack.common import importutils
|
||||||
from quantum.openstack.common import log as logging
|
from quantum.openstack.common import log as logging
|
||||||
|
from quantum.plugins.common import constants
|
||||||
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
@ -58,8 +59,52 @@ class QuantumManager(object):
|
|||||||
"Example: pip install quantum-sample-plugin")
|
"Example: pip install quantum-sample-plugin")
|
||||||
self.plugin = plugin_klass()
|
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
|
@classmethod
|
||||||
def get_plugin(cls):
|
def get_instance(cls):
|
||||||
if cls._instance is None:
|
if cls._instance is None:
|
||||||
cls._instance = cls()
|
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
|
||||||
|
@ -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.
|
16
quantum/plugins/common/__init__.py
Normal file
16
quantum/plugins/common/__init__.py
Normal file
@ -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.
|
26
quantum/plugins/common/constants.py
Normal file
26
quantum/plugins/common/constants.py
Normal file
@ -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",
|
||||||
|
}
|
16
quantum/plugins/services/__init__.py
Normal file
16
quantum/plugins/services/__init__.py
Normal file
@ -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.
|
16
quantum/plugins/services/dummy/__init__.py
Normal file
16
quantum/plugins/services/dummy/__init__.py
Normal file
@ -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.
|
32
quantum/plugins/services/dummy/dummy_plugin.py
Normal file
32
quantum/plugins/services/dummy/dummy_plugin.py
Normal file
@ -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"
|
35
quantum/plugins/services/service_base.py
Normal file
35
quantum/plugins/services/service_base.py
Normal file
@ -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
|
@ -33,6 +33,7 @@ from quantum.extensions.extensions import (
|
|||||||
PluginAwareExtensionManager,
|
PluginAwareExtensionManager,
|
||||||
)
|
)
|
||||||
from quantum.openstack.common import jsonutils
|
from quantum.openstack.common import jsonutils
|
||||||
|
from quantum.plugins.common import constants
|
||||||
from quantum.tests.unit import BaseTest
|
from quantum.tests.unit import BaseTest
|
||||||
from quantum.tests.unit.extension_stubs import (
|
from quantum.tests.unit.extension_stubs import (
|
||||||
ExtensionExpectingPluginInterface,
|
ExtensionExpectingPluginInterface,
|
||||||
@ -43,6 +44,7 @@ from quantum.tests.unit.extension_stubs import (
|
|||||||
import quantum.tests.unit.extensions
|
import quantum.tests.unit.extensions
|
||||||
from quantum import wsgi
|
from quantum import wsgi
|
||||||
|
|
||||||
|
|
||||||
LOG = logging.getLogger('quantum.tests.test_extensions')
|
LOG = logging.getLogger('quantum.tests.test_extensions')
|
||||||
|
|
||||||
ROOTDIR = os.path.dirname(os.path.dirname(__file__))
|
ROOTDIR = os.path.dirname(os.path.dirname(__file__))
|
||||||
@ -93,6 +95,22 @@ class ResourceExtensionTest(unittest.TestCase):
|
|||||||
def custom_collection_action(self, request, **kwargs):
|
def custom_collection_action(self, request, **kwargs):
|
||||||
return {'collection': 'value'}
|
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):
|
def test_exceptions_notimplemented(self):
|
||||||
controller = self.ResourceExtensionController()
|
controller = self.ResourceExtensionController()
|
||||||
member = {'notimplemented_function': "GET"}
|
member = {'notimplemented_function': "GET"}
|
||||||
@ -122,6 +140,20 @@ class ResourceExtensionTest(unittest.TestCase):
|
|||||||
show_response = test_app.get("/tweedles/25266")
|
show_response = test_app.get("/tweedles/25266")
|
||||||
self.assertEqual({'data': {'id': "25266"}}, show_response.json)
|
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):
|
def test_resource_extension_with_custom_member_action(self):
|
||||||
controller = self.ResourceExtensionController()
|
controller = self.ResourceExtensionController()
|
||||||
member = {'custom_member_action': "GET"}
|
member = {'custom_member_action': "GET"}
|
||||||
@ -134,6 +166,53 @@ class ResourceExtensionTest(unittest.TestCase):
|
|||||||
self.assertEqual(jsonutils.loads(response.body)['member_action'],
|
self.assertEqual(jsonutils.loads(response.body)['member_action'],
|
||||||
"value")
|
"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):
|
def test_resource_extension_for_get_custom_collection_action(self):
|
||||||
controller = self.ResourceExtensionController()
|
controller = self.ResourceExtensionController()
|
||||||
collections = {'custom_collection_action': "GET"}
|
collections = {'custom_collection_action': "GET"}
|
||||||
@ -143,6 +222,7 @@ class ResourceExtensionTest(unittest.TestCase):
|
|||||||
|
|
||||||
response = test_app.get("/tweedles/custom_collection_action")
|
response = test_app.get("/tweedles/custom_collection_action")
|
||||||
self.assertEqual(200, response.status_int)
|
self.assertEqual(200, response.status_int)
|
||||||
|
LOG.debug(jsonutils.loads(response.body))
|
||||||
self.assertEqual(jsonutils.loads(response.body)['collection'], "value")
|
self.assertEqual(jsonutils.loads(response.body)['collection'], "value")
|
||||||
|
|
||||||
def test_resource_extension_for_put_custom_collection_action(self):
|
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):
|
def test_unsupported_extensions_are_not_loaded(self):
|
||||||
stub_plugin = StubPlugin(supported_extensions=["e1", "e3"])
|
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("e1"))
|
||||||
ext_mgr.add_extension(StubExtension("e2"))
|
ext_mgr.add_extension(StubExtension("e2"))
|
||||||
@ -372,21 +453,24 @@ class PluginAwareExtensionManagerTest(unittest.TestCase):
|
|||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
ext_mgr = PluginAwareExtensionManager('', ExtensionUnawarePlugin())
|
ext_mgr = PluginAwareExtensionManager('',
|
||||||
|
{constants.CORE:
|
||||||
|
ExtensionUnawarePlugin()})
|
||||||
ext_mgr.add_extension(StubExtension("e1"))
|
ext_mgr.add_extension(StubExtension("e1"))
|
||||||
|
|
||||||
self.assertFalse("e1" in ext_mgr.extensions)
|
self.assertFalse("e1" in ext_mgr.extensions)
|
||||||
|
|
||||||
def test_extensions_not_loaded_for_plugin_without_expected_interface(self):
|
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
|
Plugin does not implement get_foo method as expected by extension
|
||||||
"""
|
"""
|
||||||
supported_extension_aliases = ["supported_extension"]
|
supported_extension_aliases = ["supported_extension"]
|
||||||
|
|
||||||
ext_mgr = PluginAwareExtensionManager('',
|
ext_mgr = PluginAwareExtensionManager('',
|
||||||
PluginWithoutExpectedInterface())
|
{constants.CORE:
|
||||||
|
PluginWithoutExpectedIface()})
|
||||||
ext_mgr.add_extension(
|
ext_mgr.add_extension(
|
||||||
ExtensionExpectingPluginInterface("supported_extension"))
|
ExtensionExpectingPluginInterface("supported_extension"))
|
||||||
|
|
||||||
@ -403,7 +487,8 @@ class PluginAwareExtensionManagerTest(unittest.TestCase):
|
|||||||
def get_foo(self, bar=None):
|
def get_foo(self, bar=None):
|
||||||
pass
|
pass
|
||||||
ext_mgr = PluginAwareExtensionManager('',
|
ext_mgr = PluginAwareExtensionManager('',
|
||||||
PluginWithExpectedInterface())
|
{constants.CORE:
|
||||||
|
PluginWithExpectedInterface()})
|
||||||
ext_mgr.add_extension(
|
ext_mgr.add_extension(
|
||||||
ExtensionExpectingPluginInterface("supported_extension"))
|
ExtensionExpectingPluginInterface("supported_extension"))
|
||||||
|
|
||||||
@ -417,7 +502,8 @@ class PluginAwareExtensionManagerTest(unittest.TestCase):
|
|||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
stub_plugin = StubPlugin(supported_extensions=["e1"])
|
stub_plugin = StubPlugin(supported_extensions=["e1"])
|
||||||
ext_mgr = PluginAwareExtensionManager('', stub_plugin)
|
ext_mgr = PluginAwareExtensionManager('', {constants.CORE:
|
||||||
|
stub_plugin})
|
||||||
ext_mgr.add_extension(ExtensionForQuamtumPluginInterface("e1"))
|
ext_mgr.add_extension(ExtensionForQuamtumPluginInterface("e1"))
|
||||||
|
|
||||||
self.assertTrue("e1" in ext_mgr.extensions)
|
self.assertTrue("e1" in ext_mgr.extensions)
|
||||||
@ -432,11 +518,24 @@ class PluginAwareExtensionManagerTest(unittest.TestCase):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
stub_plugin = StubPlugin(supported_extensions=["e1"])
|
stub_plugin = StubPlugin(supported_extensions=["e1"])
|
||||||
ext_mgr = PluginAwareExtensionManager('', stub_plugin)
|
ext_mgr = PluginAwareExtensionManager('', {constants.CORE:
|
||||||
|
stub_plugin})
|
||||||
ext_mgr.add_extension(ExtensionWithNoNeedForPluginInterface("e1"))
|
ext_mgr.add_extension(ExtensionWithNoNeedForPluginInterface("e1"))
|
||||||
|
|
||||||
self.assertTrue("e1" in ext_mgr.extensions)
|
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):
|
class ExtensionControllerTest(unittest.TestCase):
|
||||||
|
|
||||||
@ -483,7 +582,7 @@ def setup_extensions_middleware(extension_manager=None):
|
|||||||
extension_manager = (extension_manager or
|
extension_manager = (extension_manager or
|
||||||
PluginAwareExtensionManager(
|
PluginAwareExtensionManager(
|
||||||
extensions_path,
|
extensions_path,
|
||||||
FakePluginWithExtension()))
|
{constants.CORE: FakePluginWithExtension()}))
|
||||||
config_file = 'quantum.conf.test'
|
config_file = 'quantum.conf.test'
|
||||||
args = ['--config-file', etcdir(config_file)]
|
args = ['--config-file', etcdir(config_file)]
|
||||||
config.parse(args=args)
|
config.parse(args=args)
|
||||||
|
71
quantum/tests/unit/test_quantum_manager.py
Normal file
71
quantum/tests/unit/test_quantum_manager.py
Normal file
@ -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))
|
Loading…
Reference in New Issue
Block a user