summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTim Burke <tim.burke@gmail.com>2018-09-07 16:50:10 -0700
committerTim Burke <tim.burke@gmail.com>2018-09-07 16:56:13 -0700
commitd1e1f8d8d6a8890c71eea8a3c2a488af30a8147b (patch)
tree775a30177e366da236d3e9401800b5372fcc9953
parent4330d036cd40299c3fd12d68d319e20757ccadcf (diff)
downloadpython-swiftclient-d1e1f8d8d6a8890c71eea8a3c2a488af30a8147b.tar.gz
Stop lazy importing keystoneclient
There were two basic problems: - We'd try to import on every attempt at getting auth, even when we already know keystoneclient isn't available. - Sometimes devs would hit some crazy import race involving (some combination of?) greenthreads and OS threads. So let's just try the imports *once*, at import time, and have None sentinels if it fails. Try both versions separately to decouple failures; this should let us support a wider range of keystoneclient versions. Change-Id: I2367310aac74f1b7c5ea0cb1a822a491e4ba8e68
-rw-r--r--swiftclient/client.py53
-rw-r--r--tests/unit/test_shell.py63
-rw-r--r--tests/unit/test_swiftclient.py26
-rw-r--r--tests/unit/utils.py8
4 files changed, 68 insertions, 82 deletions
diff --git a/swiftclient/client.py b/swiftclient/client.py
index 71801c6..d843aec 100644
--- a/swiftclient/client.py
+++ b/swiftclient/client.py
@@ -60,6 +60,19 @@ except ImportError:
def createLock(self):
self.lock = None
+ksexceptions = ksclient_v2 = ksclient_v3 = None
+try:
+ from keystoneclient import exceptions as ksexceptions
+ # prevent keystoneclient warning us that it has no log handlers
+ logging.getLogger('keystoneclient').addHandler(NullHandler())
+ from keystoneclient.v2_0 import client as ksclient_v2
+except ImportError:
+ pass
+try:
+ from keystoneclient.v3 import client as ksclient_v3
+except ImportError:
+ pass
+
# requests version 1.2.3 try to encode headers in ascii, preventing
# utf-8 encoded header to be 'prepared'
if StrictVersion(requests.__version__) < StrictVersion('2.0.0'):
@@ -540,25 +553,6 @@ def get_keystoneclient_2_0(auth_url, user, key, os_options, **kwargs):
return get_auth_keystone(auth_url, user, key, os_options, **kwargs)
-def _import_keystone_client(auth_version):
- # the attempted imports are encapsulated in this function to allow
- # mocking for tests
- try:
- if auth_version in AUTH_VERSIONS_V3:
- from keystoneclient.v3 import client as ksclient
- else:
- from keystoneclient.v2_0 import client as ksclient
- from keystoneclient import exceptions
- # prevent keystoneclient warning us that it has no log handlers
- logging.getLogger('keystoneclient').addHandler(NullHandler())
- return ksclient, exceptions
- except ImportError:
- raise ClientException('''
-Auth versions 2.0 and 3 require python-keystoneclient, install it or use Auth
-version 1.0 which requires ST_AUTH, ST_USER, and ST_KEY environment
-variables to be set or overridden with -A, -U, or -K.''')
-
-
def get_auth_keystone(auth_url, user, key, os_options, **kwargs):
"""
Authenticate against a keystone server.
@@ -587,7 +581,20 @@ def get_auth_keystone(auth_url, user, key, os_options, **kwargs):
# Legacy default if not set
if auth_version is None:
auth_version = '2'
- ksclient, exceptions = _import_keystone_client(auth_version)
+
+ ksclient = None
+ if auth_version in AUTH_VERSIONS_V3:
+ if ksclient_v3 is not None:
+ ksclient = ksclient_v3
+ else:
+ if ksclient_v2 is not None:
+ ksclient = ksclient_v2
+
+ if ksclient is None:
+ raise ClientException('''
+Auth versions 2.0 and 3 require python-keystoneclient, install it or use Auth
+version 1.0 which requires ST_AUTH, ST_USER, and ST_KEY environment
+variables to be set or overridden with -A, -U, or -K.''')
try:
_ksclient = ksclient.Client(
@@ -608,13 +615,13 @@ def get_auth_keystone(auth_url, user, key, os_options, **kwargs):
cert=kwargs.get('cert'),
key=kwargs.get('cert_key'),
auth_url=auth_url, insecure=insecure, timeout=timeout)
- except exceptions.Unauthorized:
+ except ksexceptions.Unauthorized:
msg = 'Unauthorized. Check username, password and tenant name/id.'
if auth_version in AUTH_VERSIONS_V3:
msg = ('Unauthorized. Check username/id, password, '
'tenant name/id and user/tenant domain name/id.')
raise ClientException(msg)
- except exceptions.AuthorizationFailure as err:
+ except ksexceptions.AuthorizationFailure as err:
raise ClientException('Authorization Failure. %s' % err)
service_type = os_options.get('service_type') or 'object-store'
endpoint_type = os_options.get('endpoint_type') or 'publicURL'
@@ -627,7 +634,7 @@ def get_auth_keystone(auth_url, user, key, os_options, **kwargs):
service_type=service_type,
endpoint_type=endpoint_type,
**filter_kwargs)
- except exceptions.EndpointNotFound:
+ except ksexceptions.EndpointNotFound:
raise ClientException('Endpoint for %s not found - '
'have you specified a region?' % service_type)
return endpoint, _ksclient.auth_token
diff --git a/tests/unit/test_shell.py b/tests/unit/test_shell.py
index 91496b8..9ef4685 100644
--- a/tests/unit/test_shell.py
+++ b/tests/unit/test_shell.py
@@ -37,7 +37,7 @@ import swiftclient.utils
from os.path import basename, dirname
from .utils import (
- CaptureOutput, fake_get_auth_keystone, _make_fake_import_keystone_client,
+ CaptureOutput, fake_get_auth_keystone,
FakeKeystone, StubResponse, MockHttpTest)
from swiftclient.utils import (
EMPTY_ETAG, EXPIRES_ISO8601_FORMAT,
@@ -2534,7 +2534,17 @@ class TestKeystoneOptions(MockHttpTest):
cmd_args=cmd_args)
ks_endpoint = 'http://example.com:8080/v1/AUTH_acc'
ks_token = 'fake_auth_token'
+ # check correct auth version gets used
+ key = 'auth-version'
fake_ks = FakeKeystone(endpoint=ks_endpoint, token=ks_token)
+ if no_auth:
+ fake_ks2 = fake_ks3 = None
+ elif opts.get(key, self.defaults.get(key)) == '2.0':
+ fake_ks2 = fake_ks
+ fake_ks3 = None
+ else:
+ fake_ks2 = None
+ fake_ks3 = fake_ks
# fake_conn will check that storage_url and auth_token are as expected
endpoint = os_opts.get('storage-url', ks_endpoint)
token = os_opts.get('auth-token', ks_token)
@@ -2542,8 +2552,8 @@ class TestKeystoneOptions(MockHttpTest):
storage_url=endpoint,
auth_token=token)
- with mock.patch('swiftclient.client._import_keystone_client',
- _make_fake_import_keystone_client(fake_ks)), \
+ with mock.patch('swiftclient.client.ksclient_v2', fake_ks2), \
+ mock.patch('swiftclient.client.ksclient_v3', fake_ks3), \
mock.patch('swiftclient.client.http_connection', fake_conn), \
mock.patch.dict(os.environ, env, clear=True), \
patch_disable_warnings() as mock_disable_warnings:
@@ -2562,16 +2572,11 @@ class TestKeystoneOptions(MockHttpTest):
self.assertEqual([], mock_disable_warnings.mock_calls)
if no_auth:
- # check that keystone client was not used and terminate tests
- self.assertIsNone(getattr(fake_ks, 'auth_version'))
- self.assertEqual(len(fake_ks.calls), 0)
+ # We patched out both keystoneclient versions to be None;
+ # they *can't* have been used and if we tried to, we would
+ # have raised ClientExceptions
return
- # check correct auth version was passed to _import_keystone_client
- key = 'auth-version'
- expected = opts.get(key, self.defaults.get(key))
- self.assertEqual(expected, fake_ks.auth_version)
-
# check args passed to keystone Client __init__
self.assertEqual(len(fake_ks.calls), 1)
actual_args = fake_ks.calls[0]
@@ -2942,9 +2947,9 @@ class TestCrossAccountObjectAccess(TestBase, MockHttpTest):
self.account = 'AUTH_alice'
# keystone returns endpoint for another account
- fake_ks = FakeKeystone(endpoint='http://example.com:8080/v1/AUTH_bob',
- token='bob_token')
- self.fake_ks_import = _make_fake_import_keystone_client(fake_ks)
+ self.fake_ks = FakeKeystone(
+ endpoint='http://example.com:8080/v1/AUTH_bob',
+ token='bob_token')
self.cont = 'c1'
self.cont_path = '/v1/%s/%s' % (self.account, self.cont)
@@ -3023,8 +3028,7 @@ class TestCrossAccountObjectAccess(TestBase, MockHttpTest):
args, env = self._make_cmd('upload', cmd_args=[self.cont, self.obj,
'--leave-segments'])
- with mock.patch('swiftclient.client._import_keystone_client',
- self.fake_ks_import):
+ with mock.patch('swiftclient.client.ksclient_v3', self.fake_ks):
with mock.patch('swiftclient.client.http_connection', fake_conn):
with mock.patch.dict(os.environ, env):
with CaptureOutput() as out:
@@ -3046,8 +3050,7 @@ class TestCrossAccountObjectAccess(TestBase, MockHttpTest):
on_request=req_handler)
args, env = self._make_cmd('upload', cmd_args=[self.cont, self.obj,
'--leave-segments'])
- with mock.patch('swiftclient.client._import_keystone_client',
- self.fake_ks_import):
+ with mock.patch('swiftclient.client.ksclient_v3', self.fake_ks):
with mock.patch('swiftclient.client.http_connection', fake_conn):
with mock.patch.dict(os.environ, env):
with CaptureOutput() as out:
@@ -3073,8 +3076,7 @@ class TestCrossAccountObjectAccess(TestBase, MockHttpTest):
'--segment-size=10',
'--segment-container=%s'
% self.cont])
- with mock.patch('swiftclient.client._import_keystone_client',
- self.fake_ks_import):
+ with mock.patch('swiftclient.client.ksclient_v3', self.fake_ks):
with mock.patch('swiftclient.client.http_connection', fake_conn):
with mock.patch.dict(os.environ, env):
with CaptureOutput() as out:
@@ -3112,8 +3114,7 @@ class TestCrossAccountObjectAccess(TestBase, MockHttpTest):
cmd_args=[self.cont, self.obj,
'--leave-segments',
'--segment-size=10'])
- with mock.patch('swiftclient.client._import_keystone_client',
- self.fake_ks_import):
+ with mock.patch('swiftclient.client.ksclient_v3', self.fake_ks):
with mock.patch('swiftclient.client.http_connection', fake_conn):
with mock.patch.dict(os.environ, env):
with CaptureOutput() as out:
@@ -3149,8 +3150,7 @@ class TestCrossAccountObjectAccess(TestBase, MockHttpTest):
args, env = self._make_cmd('upload', cmd_args=[self.cont, self.obj,
'--leave-segments'])
- with mock.patch('swiftclient.client._import_keystone_client',
- self.fake_ks_import):
+ with mock.patch('swiftclient.client.ksclient_v3', self.fake_ks):
with mock.patch('swiftclient.client.http_connection', fake_conn):
with mock.patch.dict(os.environ, env):
with CaptureOutput() as out:
@@ -3207,8 +3207,7 @@ class TestCrossAccountObjectAccess(TestBase, MockHttpTest):
args, env = self._make_cmd('download', cmd_args=[self.cont,
self.obj.lstrip('/'),
'--no-download'])
- with mock.patch('swiftclient.client._import_keystone_client',
- self.fake_ks_import):
+ with mock.patch('swiftclient.client.ksclient_v3', self.fake_ks):
with mock.patch('swiftclient.client.http_connection', fake_conn):
with mock.patch.dict(os.environ, env):
with CaptureOutput() as out:
@@ -3229,8 +3228,7 @@ class TestCrossAccountObjectAccess(TestBase, MockHttpTest):
args, env = self._make_cmd('download', cmd_args=[self.cont,
self.obj.lstrip('/'),
'--no-download'])
- with mock.patch('swiftclient.client._import_keystone_client',
- self.fake_ks_import):
+ with mock.patch('swiftclient.client.ksclient_v3', self.fake_ks):
with mock.patch('swiftclient.client.http_connection', fake_conn):
with mock.patch.dict(os.environ, env):
with CaptureOutput() as out:
@@ -3248,8 +3246,7 @@ class TestCrossAccountObjectAccess(TestBase, MockHttpTest):
args, env = self._make_cmd('download', cmd_args=[self.cont,
self.obj.lstrip('/'),
'--no-download'])
- with mock.patch('swiftclient.client._import_keystone_client',
- self.fake_ks_import):
+ with mock.patch('swiftclient.client.ksclient_v3', self.fake_ks):
with mock.patch('swiftclient.client.http_connection', fake_conn):
with mock.patch.dict(os.environ, env):
with CaptureOutput() as out:
@@ -3273,8 +3270,7 @@ class TestCrossAccountObjectAccess(TestBase, MockHttpTest):
fake_conn = self.fake_http_connection(resp, on_request=req_handler)
args, env = self._make_cmd('download', cmd_args=[self.cont])
- with mock.patch('swiftclient.client._import_keystone_client',
- self.fake_ks_import):
+ with mock.patch('swiftclient.client.ksclient_v3', self.fake_ks):
with mock.patch('swiftclient.client.http_connection', fake_conn):
with mock.patch.dict(os.environ, env):
with CaptureOutput() as out:
@@ -3291,8 +3287,7 @@ class TestCrossAccountObjectAccess(TestBase, MockHttpTest):
fake_conn = self.fake_http_connection(403)
args, env = self._make_cmd('download', cmd_args=[self.cont])
- with mock.patch('swiftclient.client._import_keystone_client',
- self.fake_ks_import):
+ with mock.patch('swiftclient.client.ksclient_v3', self.fake_ks):
with mock.patch('swiftclient.client.http_connection', fake_conn):
with mock.patch.dict(os.environ, env):
with CaptureOutput() as out:
diff --git a/tests/unit/test_swiftclient.py b/tests/unit/test_swiftclient.py
index 3303372..f114774 100644
--- a/tests/unit/test_swiftclient.py
+++ b/tests/unit/test_swiftclient.py
@@ -29,7 +29,7 @@ from six.moves.urllib.parse import urlparse
from requests.exceptions import RequestException
from .utils import (MockHttpTest, fake_get_auth_keystone, StubResponse,
- FakeKeystone, _make_fake_import_keystone_client)
+ FakeKeystone)
from swiftclient.utils import EMPTY_ETAG
from swiftclient.exceptions import ClientException
@@ -322,8 +322,7 @@ class TestGetAuth(MockHttpTest):
# TestConnection.test_timeout_passed_down but is required to check that
# get_auth does the right thing when it is not passed a timeout arg
fake_ks = FakeKeystone(endpoint='http://some_url', token='secret')
- with mock.patch('swiftclient.client._import_keystone_client',
- _make_fake_import_keystone_client(fake_ks)):
+ with mock.patch('swiftclient.client.ksclient_v2', fake_ks):
c.get_auth('http://www.test.com', 'asdf', 'asdf',
os_options=dict(tenant_name='tenant'),
auth_version="2.0", timeout=42.0)
@@ -580,8 +579,7 @@ class TestGetAuth(MockHttpTest):
def test_get_auth_keystone_versionless(self):
fake_ks = FakeKeystone(endpoint='http://some_url', token='secret')
- with mock.patch('swiftclient.client._import_keystone_client',
- _make_fake_import_keystone_client(fake_ks)):
+ with mock.patch('swiftclient.client.ksclient_v3', fake_ks):
c.get_auth_keystone('http://authurl', 'user', 'key', {})
self.assertEqual(1, len(fake_ks.calls))
self.assertEqual('http://authurl/v3', fake_ks.calls[0].get('auth_url'))
@@ -589,8 +587,7 @@ class TestGetAuth(MockHttpTest):
def test_get_auth_keystone_versionless_auth_version_set(self):
fake_ks = FakeKeystone(endpoint='http://some_url', token='secret')
- with mock.patch('swiftclient.client._import_keystone_client',
- _make_fake_import_keystone_client(fake_ks)):
+ with mock.patch('swiftclient.client.ksclient_v2', fake_ks):
c.get_auth_keystone('http://auth_url', 'user', 'key',
{}, auth_version='2.0')
self.assertEqual(1, len(fake_ks.calls))
@@ -600,8 +597,7 @@ class TestGetAuth(MockHttpTest):
def test_get_auth_keystone_versionful(self):
fake_ks = FakeKeystone(endpoint='http://some_url', token='secret')
- with mock.patch('swiftclient.client._import_keystone_client',
- _make_fake_import_keystone_client(fake_ks)):
+ with mock.patch('swiftclient.client.ksclient_v3', fake_ks):
c.get_auth_keystone('http://auth_url/v3', 'user', 'key',
{}, auth_version='3')
self.assertEqual(1, len(fake_ks.calls))
@@ -611,8 +607,7 @@ class TestGetAuth(MockHttpTest):
def test_get_auth_keystone_devstack_versionful(self):
fake_ks = FakeKeystone(
endpoint='http://storage.example.com/v1/AUTH_user', token='secret')
- with mock.patch('swiftclient.client._import_keystone_client',
- _make_fake_import_keystone_client(fake_ks)):
+ with mock.patch('swiftclient.client.ksclient_v3', fake_ks):
c.get_auth_keystone('https://192.168.8.8/identity/v3',
'user', 'key', {}, auth_version='3')
self.assertEqual(1, len(fake_ks.calls))
@@ -622,8 +617,7 @@ class TestGetAuth(MockHttpTest):
def test_get_auth_keystone_devstack_versionless(self):
fake_ks = FakeKeystone(
endpoint='http://storage.example.com/v1/AUTH_user', token='secret')
- with mock.patch('swiftclient.client._import_keystone_client',
- _make_fake_import_keystone_client(fake_ks)):
+ with mock.patch('swiftclient.client.ksclient_v3', fake_ks):
c.get_auth_keystone('https://192.168.8.8/identity',
'user', 'key', {}, auth_version='3')
self.assertEqual(1, len(fake_ks.calls))
@@ -634,8 +628,7 @@ class TestGetAuth(MockHttpTest):
fake_ks = FakeKeystone(
endpoint='http://storage.example.com/v1/AUTH_user',
token='secret')
- with mock.patch('swiftclient.client._import_keystone_client',
- _make_fake_import_keystone_client(fake_ks)):
+ with mock.patch('swiftclient.client.ksclient_v3', fake_ks):
c.get_auth_keystone('http://blah.example.com/v2moo',
'user', 'key', {}, auth_version='3')
self.assertEqual(1, len(fake_ks.calls))
@@ -2456,8 +2449,7 @@ class TestConnection(MockHttpTest):
'http://auth.example.com', 'user', 'password', timeout=33.0,
os_options=os_options, auth_version=2.0)
fake_ks = FakeKeystone(endpoint='http://some_url', token='secret')
- with mock.patch('swiftclient.client._import_keystone_client',
- _make_fake_import_keystone_client(fake_ks)):
+ with mock.patch('swiftclient.client.ksclient_v2', fake_ks):
with mock.patch.multiple('swiftclient.client',
http_connection=shim_connection,
sleep=mock.DEFAULT):
diff --git a/tests/unit/utils.py b/tests/unit/utils.py
index 2def73f..aab3b59 100644
--- a/tests/unit/utils.py
+++ b/tests/unit/utils.py
@@ -542,14 +542,6 @@ class FakeKeystone(object):
pass
-def _make_fake_import_keystone_client(fake_import):
- def _fake_import_keystone_client(auth_version):
- fake_import.auth_version = auth_version
- return fake_import, fake_import
-
- return _fake_import_keystone_client
-
-
class FakeStream(object):
def __init__(self, size):
self.bytes_read = 0