Propagate AttributeErrors when lazily loading plugins
Previously, if an AttributeError was raised in a plugin's make_client
method, the plugin simply wouldn't be an attribute of the ClientManager,
producing tracebacks like
Traceback (most recent call last):
File ".../openstackclient/shell.py", line 118, in run
ret_val = super(OpenStackShell, self).run(argv)
...
File ".../openstackclient/object/v1/container.py", line 150, in take_action
data = self.app.client_manager.object_store.container_list(
File ".../openstackclient/common/clientmanager.py", line 66, in __getattr__
raise AttributeError(name)
AttributeError: object_store
This made writing minimal third-party auth plugins difficult, as it
obliterated the original AttributeError.
Now, AttributeErrors that are raised during plugin initialization will
be re-raised as PluginAttributeErrors, and the original traceback will
be preserved. This gives much more useful information to plugin
developers, as in
Traceback (most recent call last):
File ".../openstackclient/shell.py", line 118, in run
ret_val = super(OpenStackShell, self).run(argv)
...
File ".../openstackclient/object/v1/container.py", line 150, in take_action
data = self.app.client_manager.object_store.container_list(
File ".../openstackclient/common/clientmanager.py", line 57, in __get__
err_val, err_tb)
File ".../openstackclient/common/clientmanager.py", line 51, in __get__
self._handle = self.factory(instance)
File ".../openstackclient/object/client.py", line 35, in make_client
interface=instance._interface,
File ".../openstackclient/common/clientmanager.py", line 258,
in get_endpoint_for_service_type
endpoint = self.auth_ref.service_catalog.url_for(
PluginAttributeError: 'NoneType' object has no attribute 'url_for'
Change-Id: I0eee7eba6eccc6d471a699a381185c4e76da10bd
This commit is contained in:
@@ -22,8 +22,10 @@ import sys
|
||||
|
||||
from oslo_utils import strutils
|
||||
import requests
|
||||
import six
|
||||
|
||||
from openstackclient.api import auth
|
||||
from openstackclient.common import exceptions
|
||||
from openstackclient.common import session as osc_session
|
||||
from openstackclient.identity import client as identity_client
|
||||
|
||||
@@ -45,7 +47,13 @@ class ClientCache(object):
|
||||
def __get__(self, instance, owner):
|
||||
# Tell the ClientManager to login to keystone
|
||||
if self._handle is None:
|
||||
try:
|
||||
self._handle = self.factory(instance)
|
||||
except AttributeError as err:
|
||||
# Make sure the failure propagates. Otherwise, the plugin just
|
||||
# quietly isn't there.
|
||||
new_err = exceptions.PluginAttributeError(err)
|
||||
six.reraise(new_err.__class__, new_err, sys.exc_info()[2])
|
||||
return self._handle
|
||||
|
||||
|
||||
|
||||
@@ -24,6 +24,13 @@ class AuthorizationFailure(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class PluginAttributeError(Exception):
|
||||
"""A plugin threw an AttributeError while being lazily loaded."""
|
||||
# This *must not* inherit from AttributeError;
|
||||
# that would defeat the whole purpose.
|
||||
pass
|
||||
|
||||
|
||||
class NoTokenLookupException(Exception):
|
||||
"""This does not support looking up endpoints from an existing token."""
|
||||
pass
|
||||
|
||||
@@ -41,6 +41,7 @@ auth.get_options_list()
|
||||
|
||||
class Container(object):
|
||||
attr = clientmanager.ClientCache(lambda x: object())
|
||||
buggy_attr = clientmanager.ClientCache(lambda x: x.foo)
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
@@ -72,6 +73,13 @@ class TestClientCache(utils.TestCase):
|
||||
c = Container()
|
||||
self.assertEqual(c.attr, c.attr)
|
||||
|
||||
def test_attribute_error_propagates(self):
|
||||
c = Container()
|
||||
err = self.assertRaises(exc.PluginAttributeError,
|
||||
getattr, c, 'buggy_attr')
|
||||
self.assertNotIsInstance(err, AttributeError)
|
||||
self.assertEqual("'Container' object has no attribute 'foo'", str(err))
|
||||
|
||||
|
||||
class TestClientManager(utils.TestCase):
|
||||
|
||||
|
||||
Reference in New Issue
Block a user