Use keystoneauth to create a Session

All client interactions should actually flow through a keystoneauth
Session. Add the parameter for a user to pass one in, but support
the old style of direct parameters too. A followup patch will add
os-client-config support to the shell, so add that to requirements.txt
instead of keystoneauth directly to look forward.

Also, trim unneeded things from requirements.txt because we're adding
os-client-config which pulls in various things.

Closes-Bug: #1514733
Change-Id: I88b1d0e4e119429599dec75c5db24237a92edcec
This commit is contained in:
Monty Taylor 2015-11-07 09:29:23 -05:00 committed by Yolanda Robla
parent 6296e59d2b
commit 5cbc5e722e
6 changed files with 174 additions and 135 deletions

View File

@ -22,7 +22,7 @@ import os
import socket import socket
import ssl import ssl
from keystoneclient import adapter from keystoneauth1 import adapter
import six import six
import six.moves.urllib.parse as urlparse import six.moves.urllib.parse as urlparse
@ -284,8 +284,14 @@ class VerifiedHTTPSConnection(six.moves.http_client.HTTPSConnection):
class SessionClient(adapter.LegacyJsonAdapter): class SessionClient(adapter.LegacyJsonAdapter):
"""HTTP client based on Keystone client session.""" """HTTP client based on Keystone client session."""
def __init__(self, user_agent=USER_AGENT, logger=LOG, *args, **kwargs):
super(SessionClient, self).__init__(*args, **kwargs)
def _http_request(self, url, method, **kwargs): def _http_request(self, url, method, **kwargs):
kwargs.setdefault('user_agent', USER_AGENT) if url.startswith(API_VERSION):
url = url[len(API_VERSION):]
kwargs.setdefault('user_agent', self.user_agent)
kwargs.setdefault('auth', self.auth) kwargs.setdefault('auth', self.auth)
endpoint_filter = kwargs.setdefault('endpoint_filter', {}) endpoint_filter = kwargs.setdefault('endpoint_filter', {})

View File

@ -16,7 +16,7 @@ import re
import sys import sys
import fixtures import fixtures
from keystoneclient import fixture from keystoneauth1 import fixture
import mock import mock
import six import six
from testtools import matchers from testtools import matchers

View File

@ -49,10 +49,17 @@ class TestCommandLineArgument(utils.TestCase):
def setUp(self): def setUp(self):
super(TestCommandLineArgument, self).setUp() super(TestCommandLineArgument, self).setUp()
self.make_env(fake_env=FAKE_ENV) self.make_env(fake_env=FAKE_ENV)
keystone_mock = mock.patch( session_client = mock.patch(
'magnumclient.v1.client.Client.get_keystone_client') 'magnumclient.common.httpclient.SessionClient')
keystone_mock.start() session_client.start()
self.addCleanup(keystone_mock.stop) loader = mock.patch('keystoneauth1.loading.get_plugin_loader')
loader.start()
session = mock.patch('keystoneauth1.session.Session')
session.start()
self.addCleanup(session_client.stop)
self.addCleanup(loader.stop)
self.addCleanup(session.stop)
def _test_arg_success(self, command): def _test_arg_success(self, command):
stdout, stderr = self.shell(command) stdout, stderr = self.shell(command)

View File

@ -20,74 +20,99 @@ from magnumclient.v1 import client
class ClientTest(testtools.TestCase): class ClientTest(testtools.TestCase):
@mock.patch('magnumclient.common.httpclient.HTTPClient') @mock.patch('magnumclient.common.httpclient.SessionClient')
@mock.patch.object(client.Client, 'get_keystone_client') @mock.patch('keystoneauth1.session.Session')
def test_init_with_token_and_url(self, keystone_client, http_client): def test_init_with_session(self, mock_session, http_client):
session = mock.Mock()
client.Client(session=session)
mock_session.assert_not_called()
http_client.assert_called_once_with(
interface='public',
region_name=None,
service_name=None,
service_type='container',
session=session)
@mock.patch('magnumclient.common.httpclient.SessionClient')
@mock.patch('keystoneauth1.token_endpoint.Token')
@mock.patch('keystoneauth1.session.Session')
def test_init_with_token_and_url(
self, mock_session, mock_token, http_client):
mock_auth_plugin = mock.Mock()
mock_token.return_value = mock_auth_plugin
session = mock.Mock()
mock_session.return_value = session
client.Client(input_auth_token='mytoken', magnum_url='http://myurl/') client.Client(input_auth_token='mytoken', magnum_url='http://myurl/')
self.assertFalse(keystone_client.called) mock_session.assert_called_once_with(auth=mock_auth_plugin)
http_client.assert_called_once_with( http_client.assert_called_once_with(
'http://myurl/', token='mytoken', auth_ref=None) endpoint_override='http://myurl/',
interface='public',
@mock.patch('magnumclient.common.httpclient.HTTPClient') region_name=None,
@mock.patch.object(client.Client, 'get_keystone_client') service_name=None,
def test_init_with_token(self, keystone_client, http_client): service_type='container',
mocked = mock.Mock() session=session)
mocked.service_catalog.url_for.return_value = 'http://myurl/'
keystone_client.return_value = mocked
@mock.patch('magnumclient.common.httpclient.SessionClient')
@mock.patch('keystoneauth1.loading.get_plugin_loader')
@mock.patch('keystoneauth1.session.Session')
def test_init_with_token(
self, mock_session, mock_loader, http_client):
mock_plugin = mock.Mock()
mock_loader.return_value = mock_plugin
client.Client(input_auth_token='mytoken', auth_url='authurl') client.Client(input_auth_token='mytoken', auth_url='authurl')
keystone_client.assert_called_once_with( mock_loader.assert_called_once_with('token')
token='mytoken', username=None, api_key=None, mock_plugin.load_from_options.assert_called_once_with(
project_name=None, project_id=None, auth_url='authurl',
auth_url='authurl') project_id=None,
project_name=None,
token='mytoken')
http_client.assert_called_once_with( http_client.assert_called_once_with(
'http://myurl/', token='mytoken', auth_ref=None) interface='public',
region_name=None,
service_name=None,
service_type='container',
session=mock.ANY)
@mock.patch('magnumclient.common.httpclient.HTTPClient') @mock.patch('magnumclient.common.httpclient.SessionClient')
@mock.patch.object(client.Client, 'get_keystone_client') @mock.patch('keystoneauth1.loading.get_plugin_loader')
def test_init_with_user(self, keystone_client, http_client): @mock.patch('keystoneauth1.session.Session')
mocked = mock.Mock() def test_init_with_user(
mocked.auth_token = 'mytoken' self, mock_session, mock_loader, http_client):
mocked.service_catalog.url_for.return_value = 'http://myurl/' mock_plugin = mock.Mock()
keystone_client.return_value = mocked mock_loader.return_value = mock_plugin
client.Client(username='myuser', auth_url='authurl')
client.Client(username='user', api_key='pass', project_name='prj', mock_loader.assert_called_once_with('password')
auth_url='authurl') mock_plugin.load_from_options.assert_called_once_with(
keystone_client.assert_called_once_with( auth_url='authurl',
username='user', api_key='pass', username='myuser',
project_name='prj', project_id=None, password=None,
auth_url='authurl') project_id=None,
project_name=None)
http_client.assert_called_once_with( http_client.assert_called_once_with(
'http://myurl/', token='mytoken', auth_ref=None) interface='public',
region_name=None,
@mock.patch.object(client.Client, 'get_keystone_client') service_name=None,
def test_init_unauthorized(self, keystone_client): service_type='container',
mocked = mock.Mock() session=mock.ANY)
mocked.auth_token = None
keystone_client.return_value = mocked
@mock.patch('magnumclient.common.httpclient.SessionClient')
@mock.patch('keystoneauth1.loading.get_plugin_loader')
@mock.patch('keystoneauth1.session.Session')
def test_init_unauthorized(
self, mock_session, mock_loader, http_client):
mock_plugin = mock.Mock()
mock_loader.return_value = mock_plugin
mock_session_obj = mock.Mock()
mock_session.return_value = mock_session_obj
mock_session_obj.get_endpoint.side_effect = Exception()
self.assertRaises( self.assertRaises(
RuntimeError, client.Client, RuntimeError,
username='user', api_key='pass', project_name='prj', client.Client, username='myuser', auth_url='authurl')
auth_url='authurl') mock_loader.assert_called_once_with('password')
mock_plugin.load_from_options.assert_called_once_with(
def _test_get_keystone_client(self, auth_url, keystone_client): auth_url='authurl',
client.Client.get_keystone_client( username='myuser',
username='user', api_key='pass', project_name='prj', password=None,
auth_url=auth_url) project_id=None,
self.assertTrue(keystone_client.called) project_name=None)
http_client.assert_not_called()
@mock.patch('keystoneclient.v2_0.client.Client')
def test_get_keystone_client_v2(self, keystone_client):
self._test_get_keystone_client(
'http://authhost/v2.0', keystone_client)
@mock.patch('keystoneclient.v3.client.Client')
def test_get_keystone_client_v3(self, keystone_client):
self._test_get_keystone_client(
'http://authhost/v3', keystone_client)
def test_get_keystone_client_no_url(self):
self.assertRaises(RuntimeError,
self._test_get_keystone_client,
None, None)

View File

@ -13,8 +13,8 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from keystoneclient.v2_0 import client as keystone_client_v2 from keystoneauth1 import loading
from keystoneclient.v3 import client as keystone_client_v3 from keystoneauth1 import session as ksa_session
from magnumclient.common import httpclient from magnumclient.common import httpclient
from magnumclient.v1 import baymodels from magnumclient.v1 import baymodels
@ -31,48 +31,74 @@ from magnumclient.v1 import services
class Client(object): class Client(object):
def __init__(self, username=None, api_key=None, project_id=None, def __init__(self, username=None, api_key=None, project_id=None,
project_name=None, auth_url=None, magnum_url=None, project_name=None, auth_url=None, magnum_url=None,
endpoint_type='publicURL', service_type='container', endpoint_type=None, service_type='container',
region_name=None, input_auth_token=None): region_name=None, input_auth_token=None,
session=None, password=None, auth_type='password',
interface='public', service_name=None):
keystone = None # We have to keep the api_key are for backwards compat, but let's
if not input_auth_token: # remove it from the rest of our code since it's not a keystone
keystone = self.get_keystone_client(username=username, # concept
api_key=api_key, if not password:
auth_url=auth_url, password = api_key
project_id=project_id, # Backwards compat for people assing in endpoint_type
project_name=project_name) if endpoint_type:
input_auth_token = keystone.auth_token interface = endpoint_type
if not input_auth_token:
raise RuntimeError("Not Authorized")
if not magnum_url: if magnum_url and input_auth_token:
keystone = keystone or self.get_keystone_client( auth_type = 'admin_token'
username=username, session = None
api_key=api_key, loader_kwargs = dict(
auth_url=auth_url,
token=input_auth_token, token=input_auth_token,
endpoint=magnum_url)
elif input_auth_token and not session:
auth_type = 'token'
loader_kwargs = dict(
token=input_auth_token,
auth_url=auth_url,
project_id=project_id,
project_name=project_name)
else:
loader_kwargs = dict(
username=username,
password=password,
auth_url=auth_url,
project_id=project_id, project_id=project_id,
project_name=project_name) project_name=project_name)
magnum_url = keystone.service_catalog.url_for(
service_type=service_type,
endpoint_type=endpoint_type,
region_name=region_name)
http_cli_kwargs = { # Backwards compatability for people not passing in Session
'token': input_auth_token, if session is None:
# TODO(yuanying): - use insecure loader = loading.get_plugin_loader(auth_type)
# 'insecure': kwargs.get('insecure'),
# TODO(yuanying): - use timeout # This only supports keystone v2 password auth - but we can
# 'timeout': kwargs.get('timeout'), # support other auth by passing in a Session, which is the
# TODO(yuanying): - use ca_file # right thing to do anyway
# 'ca_file': kwargs.get('ca_file'), auth_plugin = loader.load_from_options(**loader_kwargs)
# TODO(yuanying): - use cert_file session = ksa_session.Session(auth=auth_plugin)
# 'cert_file': kwargs.get('cert_file'),
# TODO(yuanying): - use key_file client_kwargs = {}
# 'key_file': kwargs.get('key_file'), if magnum_url:
'auth_ref': None, client_kwargs['endpoint_override'] = magnum_url
}
self.http_client = httpclient.HTTPClient(magnum_url, **http_cli_kwargs) if not magnum_url:
try:
# Trigger an auth error so that we can throw the exception
# we always have
session.get_endpoint(
service_type=service_type,
service_name=service_name,
interface=interface,
region_name=region_name)
except Exception:
raise RuntimeError("Not Authorized")
self.http_client = httpclient.SessionClient(
service_type=service_type,
service_name=service_name,
interface=interface,
region_name=region_name,
session=session,
**client_kwargs)
self.bays = bays.BayManager(self.http_client) self.bays = bays.BayManager(self.http_client)
self.certificates = certificates.CertificateManager(self.http_client) self.certificates = certificates.CertificateManager(self.http_client)
self.baymodels = baymodels.BayModelManager(self.http_client) self.baymodels = baymodels.BayModelManager(self.http_client)
@ -82,23 +108,3 @@ class Client(object):
self.rcs = rcs.ReplicationControllerManager(self.http_client) self.rcs = rcs.ReplicationControllerManager(self.http_client)
self.services = services.ServiceManager(self.http_client) self.services = services.ServiceManager(self.http_client)
self.mservices = mservices.MServiceManager(self.http_client) self.mservices = mservices.MServiceManager(self.http_client)
@staticmethod
def get_keystone_client(username=None, api_key=None, auth_url=None,
token=None, project_id=None, project_name=None):
if not auth_url:
raise RuntimeError("No auth url specified")
imported_client = (keystone_client_v2 if "v2.0" in auth_url
else keystone_client_v3)
client = imported_client.Client(
username=username,
password=api_key,
token=token,
tenant_id=project_id,
tenant_name=project_name,
auth_url=auth_url,
endpoint=auth_url)
client.authenticate()
return client

View File

@ -1,16 +1,11 @@
# The order of packages is significant, because pip processes them in the order # The order of packages is significant, because pip processes them in the order
# of appearance. Changing the order has an impact on the overall integration # of appearance. Changing the order has an impact on the overall integration
# process, which may cause wedges in the gate later. # process, which may cause wedges in the gate later.
argparse
pbr>=1.6 pbr>=1.6
Babel>=1.3 Babel>=1.3
oslo.config>=2.6.0 # Apache-2.0
oslo.i18n>=1.5.0 # Apache-2.0 oslo.i18n>=1.5.0 # Apache-2.0
oslo.serialization>=1.10.0 # Apache-2.0 oslo.serialization>=1.10.0 # Apache-2.0
oslo.utils!=2.6.0,>=2.4.0 # Apache-2.0 oslo.utils!=2.6.0,>=2.4.0 # Apache-2.0
iso8601>=0.1.9 os-client-config>=1.4.0,!=1.6.2
requests!=2.8.0,>=2.5.2 PrettyTable>=0.7,<0.8
python-keystoneclient!=1.8.0,>=1.6.0
PyYAML>=3.1.0
stevedore>=1.5.0 # Apache-2.0
six>=1.9.0