Expose root cause plugin exceptions

Stevedore was being used to both discover configured plugins and to
create instances of these plugins. If the plugin's __init__() function
raised an exception however, stevedore logged the exception but
otherwise kept processing plugins in the list. The logger also is
disabled so no stack trace is seen in the log file, hence the init
error is swallowed. It wasn't until the plugin was searched for later
in the flow that an error was appearent to the client, but the original
root cause of the issue was not found. This CR creates these plugins
outside of stevedore so that issues are immediately raised for
attention, as these represent configuration issues that must be fixed
for proper operation.

Change-Id: I2fd533302ac5e0364aee6e24fd03fcf962c7a3ef
This commit is contained in:
jfwood 2015-04-08 18:05:26 -05:00
parent 4273c640e3
commit 70b9515352
6 changed files with 204 additions and 53 deletions

View File

@ -18,6 +18,8 @@ from barbican.common import utils
from barbican import i18n as u
from barbican.plugin.crypto import crypto
from barbican.plugin.interface import secret_store
from barbican.plugin.util import utils as plugin_utils
_PLUGIN_MANAGER = None
@ -43,8 +45,7 @@ CONF.register_opts(crypto_opts, group=crypto_opt_group)
class _CryptoPluginManager(named.NamedExtensionManager):
def __init__(self, conf=CONF, invoke_on_load=True,
invoke_args=(), invoke_kwargs={}):
def __init__(self, conf=CONF, invoke_args=(), invoke_kwargs={}):
"""Crypto Plugin Manager
Each time this class is initialized it will load a new instance
@ -55,11 +56,14 @@ class _CryptoPluginManager(named.NamedExtensionManager):
super(_CryptoPluginManager, self).__init__(
conf.crypto.namespace,
conf.crypto.enabled_crypto_plugins,
invoke_on_load=invoke_on_load,
invoke_on_load=False, # Defer creating plugins to utility below.
invoke_args=invoke_args,
invoke_kwds=invoke_kwargs
)
plugin_utils.instantiate_plugins(
self, invoke_args, invoke_kwargs)
def get_plugin_store_generate(self, type_needed, algorithm=None,
bit_length=None, mode=None):
"""Gets a secret store or generate plugin that supports provided type.
@ -68,18 +72,19 @@ class _CryptoPluginManager(named.NamedExtensionManager):
type of plugin required
:returns: CryptoPluginBase plugin implementation
"""
active_plugins = plugin_utils.get_active_plugins(self)
if len(self.extensions) < 1:
if len(active_plugins) < 1:
raise crypto.CryptoPluginNotFound()
for ext in self.extensions:
if ext.obj.supports(type_needed, algorithm, bit_length, mode):
plugin = ext.obj
for generating_plugin in active_plugins:
if generating_plugin.supports(
type_needed, algorithm, bit_length, mode):
break
else:
raise secret_store.SecretStorePluginNotFound()
return plugin
return generating_plugin
def get_plugin_retrieve(self, plugin_name_for_store):
"""Gets a secret retrieve plugin that supports the provided type.
@ -88,12 +93,12 @@ class _CryptoPluginManager(named.NamedExtensionManager):
type of plugin required
:returns: CryptoPluginBase plugin implementation
"""
active_plugins = plugin_utils.get_active_plugins(self)
if len(self.extensions) < 1:
if len(active_plugins) < 1:
raise crypto.CryptoPluginNotFound()
for ext in self.extensions:
decrypting_plugin = ext.obj
for decrypting_plugin in active_plugins:
plugin_name = utils.generate_fullname_for(decrypting_plugin)
if plugin_name == plugin_name_for_store:
break

View File

@ -32,6 +32,7 @@ import barbican.common.utils as utils
from barbican import i18n as u
from barbican.model import models
from barbican.model import repositories as repos
from barbican.plugin.util import utils as plugin_utils
CONF = cfg.CONF
@ -501,17 +502,19 @@ class BarbicanMetaDTO(object):
class CertificatePluginManager(named.NamedExtensionManager):
def __init__(self, conf=CONF, invoke_on_load=True,
invoke_args=(), invoke_kwargs={}):
def __init__(self, conf=CONF, invoke_args=(), invoke_kwargs={}):
self.ca_repo = repos.get_ca_repository()
super(CertificatePluginManager, self).__init__(
conf.certificate.namespace,
conf.certificate.enabled_certificate_plugins,
invoke_on_load=invoke_on_load,
invoke_on_load=False, # Defer creating plugins to utility below.
invoke_args=invoke_args,
invoke_kwds=invoke_kwargs
)
plugin_utils.instantiate_plugins(
self, invoke_args, invoke_kwargs)
def get_plugin(self, certificate_spec):
"""Gets a supporting certificate plugin.
@ -523,13 +526,13 @@ class CertificatePluginManager(named.NamedExtensionManager):
REQUEST_TYPE,
CertificateRequestType.CUSTOM_REQUEST)
for ext in self.extensions:
supported_request_types = ext.obj.supported_request_types()
for plugin in plugin_utils.get_active_plugins(self):
supported_request_types = plugin.supported_request_types()
if request_type not in supported_request_types:
continue
if ext.obj.supports(certificate_spec):
return ext.obj
if plugin.supports(certificate_spec):
return plugin
raise CertificatePluginNotFound()
@ -539,9 +542,9 @@ class CertificatePluginManager(named.NamedExtensionManager):
:param plugin_name: Name of the plugin to invoke
:returns: CertificatePluginBase plugin implementation
"""
for ext in self.extensions:
if utils.generate_fullname_for(ext.obj) == plugin_name:
return ext.obj
for plugin in plugin_utils.get_active_plugins(self):
if utils.generate_fullname_for(plugin) == plugin_name:
return plugin
raise CertificatePluginNotFound(plugin_name)
def get_plugin_by_ca_id(self, ca_id):
@ -558,8 +561,8 @@ class CertificatePluginManager(named.NamedExtensionManager):
def refresh_ca_table(self):
"""Refreshes the CertificateAuthority table."""
for ext in self.extensions:
plugin_name = utils.generate_fullname_for(ext.obj)
for plugin in plugin_utils.get_active_plugins(self):
plugin_name = utils.generate_fullname_for(plugin)
cas, offset, limit, total = self.ca_repo.get_by_create_date(
plugin_name=plugin_name,
suppress_exception=True)
@ -567,7 +570,7 @@ class CertificatePluginManager(named.NamedExtensionManager):
# if no entries are found, then the plugin has not yet been
# queried or that plugin's entries have expired.
# Most of the time, this will be a no-op for plugins.
self.update_ca_info(ext.obj)
self.update_ca_info(plugin)
def update_ca_info(self, cert_plugin):
"""Update the CA info for a particular plugin."""
@ -624,24 +627,26 @@ class _CertificateEventPluginManager(named.NamedExtensionManager,
new instance of this class use the EVENT_PLUGIN_MANAGER at the module
level.
"""
def __init__(self, conf=CONF, invoke_on_load=True,
invoke_args=(), invoke_kwargs={}):
def __init__(self, conf=CONF, invoke_args=(), invoke_kwargs={}):
super(_CertificateEventPluginManager, self).__init__(
conf.certificate_event.namespace,
conf.certificate_event.enabled_certificate_event_plugins,
invoke_on_load=invoke_on_load,
invoke_on_load=False, # Defer creating plugins to utility below.
invoke_args=invoke_args,
invoke_kwds=invoke_kwargs
)
plugin_utils.instantiate_plugins(
self, invoke_args, invoke_kwargs)
def get_plugin_by_name(self, plugin_name):
"""Gets a supporting certificate event plugin.
:returns: CertificateEventPluginBase plugin implementation
"""
for ext in self.extensions:
if utils.generate_fullname_for(ext.obj) == plugin_name:
return ext.obj
for plugin in plugin_utils.get_active_plugins(self):
if utils.generate_fullname_for(plugin) == plugin_name:
return plugin
raise CertificateEventPluginNotFound(plugin_name)
def notify_certificate_is_ready(
@ -658,11 +663,13 @@ class _CertificateEventPluginManager(named.NamedExtensionManager,
def _invoke_certificate_plugins(self, method, *args, **kwargs):
"""Invoke same function on plugins as calling function."""
if len(self.extensions) < 1:
active_plugins = plugin_utils.get_active_plugins(self)
if len(active_plugins) < 1:
raise CertificateEventPluginNotFound()
for ext in self.extensions:
getattr(ext.obj, method)(*args, **kwargs)
for plugin in active_plugins:
getattr(plugin, method)(*args, **kwargs)
EVENT_PLUGIN_MANAGER = _CertificateEventPluginManager()

View File

@ -22,6 +22,8 @@ from stevedore import named
from barbican.common import exception
from barbican.common import utils
from barbican import i18n as u
from barbican.plugin.util import utils as plugin_utils
_SECRET_STORE = None
@ -480,16 +482,18 @@ def _enforce_extensions_configured(plugin_related_function):
class SecretStorePluginManager(named.NamedExtensionManager):
def __init__(self, conf=CONF, invoke_on_load=True,
invoke_args=(), invoke_kwargs={}):
def __init__(self, conf=CONF, invoke_args=(), invoke_kwargs={}):
super(SecretStorePluginManager, self).__init__(
conf.secretstore.namespace,
conf.secretstore.enabled_secretstore_plugins,
invoke_on_load=invoke_on_load,
invoke_on_load=False, # Defer creating plugins to utility below.
invoke_args=invoke_args,
invoke_kwds=invoke_kwargs
)
plugin_utils.instantiate_plugins(
self, invoke_args, invoke_kwargs)
@_enforce_extensions_configured
def get_plugin_store(self, key_spec, plugin_name=None,
transport_key_needed=False):
@ -501,23 +505,24 @@ class SecretStorePluginManager(named.NamedExtensionManager):
key is required.
:returns: SecretStoreBase plugin implementation
"""
active_plugins = plugin_utils.get_active_plugins(self)
if plugin_name is not None:
for ext in self.extensions:
if utils.generate_fullname_for(ext.obj) == plugin_name:
return ext.obj
for plugin in active_plugins:
if utils.generate_fullname_for(plugin) == plugin_name:
return plugin
raise SecretStorePluginNotFound(plugin_name)
if not transport_key_needed:
for ext in self.extensions:
if ext.obj.store_secret_supports(key_spec):
return ext.obj
for plugin in active_plugins:
if plugin.store_secret_supports(key_spec):
return plugin
else:
for ext in self.extensions:
if (ext.obj.get_transport_key() is not None and
ext.obj.store_secret_supports(key_spec)):
return ext.obj
for plugin in active_plugins:
if (plugin.get_transport_key() is not None and
plugin.store_secret_supports(key_spec)):
return plugin
raise SecretStoreSupportedPluginNotFound()
@ -537,9 +542,9 @@ class SecretStorePluginManager(named.NamedExtensionManager):
configured on the database side.
"""
for ext in self.extensions:
if utils.generate_fullname_for(ext.obj) == plugin_name:
return ext.obj
for plugin in plugin_utils.get_active_plugins(self):
if utils.generate_fullname_for(plugin) == plugin_name:
return plugin
raise StorePluginNotAvailableOrMisconfigured(plugin_name)
@_enforce_extensions_configured
@ -551,9 +556,9 @@ class SecretStorePluginManager(named.NamedExtensionManager):
:returns: SecretStoreBase plugin implementation
"""
for ext in self.extensions:
if ext.obj.generate_supports(key_spec):
return ext.obj
for plugin in plugin_utils.get_active_plugins(self):
if plugin.generate_supports(key_spec):
return plugin
raise SecretStoreSupportedPluginNotFound()

View File

@ -0,0 +1,54 @@
# Copyright (c) 2015 Rackspace, Inc.
#
# 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.
"""
Utilities to support plugins and plugin managers.
"""
from barbican.common import utils
from barbican import i18n as u
LOG = utils.getLogger(__name__)
def instantiate_plugins(extension_manager, invoke_args=(), invoke_kwargs={}):
"""Attempt to create each plugin managed by a stevedore manager.
While we could have let the stevedore 'extension_manager' create our
plugins by passing 'invoke_on_load=True' to its initializer, its logic
handles and suppresses any root cause exceptions emanating from the
plugins' initializers. This function allows those exceptions to be exposed.
:param extension_manager: A :class:`NamedExtensionManager` instance that
has already processed the configured plugins, but has not yet created
instances of these plugins.
:param invoke_args: Arguments to pass to the new plugin instance.
:param invoke_kwargs: Keyword arguments to pass to the new plugin instance.
"""
for ext in extension_manager.extensions:
if not ext.obj:
try:
plugin_instance = ext.plugin(*invoke_args, **invoke_kwargs)
except Exception:
LOG.logger.disabled = False # Ensure not suppressing logs.
LOG.exception(
u._LE("Problem seen creating plugin: '%s'"),
ext.name
)
else:
ext.obj = plugin_instance
def get_active_plugins(extension_manager):
return [ext.obj for ext in extension_manager.extensions if ext.obj]

View File

@ -15,6 +15,7 @@
import mock
from barbican.common import utils as common_utils
from barbican.plugin.interface import secret_store as str
from barbican.tests import utils
@ -92,7 +93,7 @@ class WhenTestingSecretStorePluginManager(utils.BaseTestCase):
super(WhenTestingSecretStorePluginManager, self).setUp()
self.manager = str.SecretStorePluginManager()
def test_get_store_supported_plugin(self):
def test_get_store_supported_plugin_no_plugin_name(self):
plugin = TestSecretStore([str.KeyAlgorithm.AES])
plugin_mock = mock.MagicMock(obj=plugin)
self.manager.extensions = [plugin_mock]
@ -101,6 +102,15 @@ class WhenTestingSecretStorePluginManager(utils.BaseTestCase):
self.assertEqual(plugin,
self.manager.get_plugin_store(keySpec))
def test_get_store_supported_plugin_with_plugin_name(self):
plugin = TestSecretStore([str.KeyAlgorithm.AES])
plugin_mock = mock.MagicMock(obj=plugin)
self.manager.extensions = [plugin_mock]
plugin_found = self.manager.get_plugin_store(
None, plugin_name=common_utils.generate_fullname_for(plugin))
self.assertEqual(plugin, plugin_found)
def test_get_generate_supported_plugin(self):
plugin = TestSecretStore([str.KeyAlgorithm.AES])
plugin_mock = mock.MagicMock(obj=plugin)

View File

@ -0,0 +1,70 @@
# Copyright (c) 2015 Rackspace, Inc.
#
# 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 barbican.plugin.util import utils as plugin_utils
from barbican.tests import utils as test_utils
class ExtensionStub(object):
def __init__(self):
self.name = 'my_name'
self.plugin_instance = 'my_instance'
self.obj = None
self.exc = None
self.args = None
self.kwargs = None
def plugin(self, *args, **kwargs):
if self.exc:
raise self.exc
self.args = args
self.kwargs = kwargs
return self.plugin_instance
def set_raise_exception(self, exc):
self.exc = exc
class ManagerStub(object):
def __init__(self, extensions):
self.extensions = extensions
class WhenInvokingInstantiatePlugins(test_utils.BaseTestCase):
def setUp(self):
super(WhenInvokingInstantiatePlugins, self).setUp()
self.extension = ExtensionStub()
self.manager = ManagerStub([self.extension])
def test_creates_plugin_instance(self):
args = ('foo', 'bar')
kwargs = {'foo': 1}
plugin_utils.instantiate_plugins(
self.manager, invoke_args=args, invoke_kwargs=kwargs)
self.assertEqual('my_instance', self.extension.obj)
self.assertEqual(args, self.extension.args)
self.assertEqual(kwargs, self.extension.kwargs)
def test_does_not_create_plugin_instance_due_to_error(self):
self.extension.set_raise_exception(ValueError())
plugin_utils.instantiate_plugins(self.manager)
self.assertIsNone(self.extension.obj)