diff --git a/openstackclient/common/clientmanager.py b/openstackclient/common/clientmanager.py index 6d23b55e64..8b0fb921be 100644 --- a/openstackclient/common/clientmanager.py +++ b/openstackclient/common/clientmanager.py @@ -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: - self._handle = self.factory(instance) + 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 diff --git a/openstackclient/common/exceptions.py b/openstackclient/common/exceptions.py index 5f81e6a6e9..bdc33ddb54 100644 --- a/openstackclient/common/exceptions.py +++ b/openstackclient/common/exceptions.py @@ -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 diff --git a/openstackclient/tests/common/test_clientmanager.py b/openstackclient/tests/common/test_clientmanager.py index 6fc5b41e69..fa6c3fcc25 100644 --- a/openstackclient/tests/common/test_clientmanager.py +++ b/openstackclient/tests/common/test_clientmanager.py @@ -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):