From ca76a31311e60cf705032d7aacce60bab3407b9a Mon Sep 17 00:00:00 2001 From: Fei Long Wang Date: Fri, 23 Sep 2016 10:55:51 +1200 Subject: [PATCH] Support keystone session when creating client Currently, Zaqar client doesn't support passing in Keystone session for authentation. This patch add that support, so that user can create Zaqar client like: z = zaqarclient.Client(session=session) Change-Id: Idbd81678714534116d3f3cf8a395a704b1f61542 --- examples/keystone_session_auth.py | 55 +++++++++++++++++++++++++++++++ tests/unit/auth/test_keystone.py | 27 +++++---------- zaqarclient/auth/keystone.py | 3 +- zaqarclient/queues/client.py | 5 +-- zaqarclient/queues/v1/client.py | 8 +++-- zaqarclient/queues/v2/client.py | 3 +- zaqarclient/transport/request.py | 13 +++++--- 7 files changed, 86 insertions(+), 28 deletions(-) create mode 100644 examples/keystone_session_auth.py diff --git a/examples/keystone_session_auth.py b/examples/keystone_session_auth.py new file mode 100644 index 00000000..ab61aa31 --- /dev/null +++ b/examples/keystone_session_auth.py @@ -0,0 +1,55 @@ +# Copyright 2016 Catalyst IT Ltd. +# +# 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 keystoneauth1.identity.generic import password +from keystoneauth1 import session + +from zaqarclient.queues.v2 import client + + +def create_post_delete(queue_name, messages): + """Auth example + + Creates a queue, posts messages to it and finally deletes it with + keystone auth strategy enabled on Zaqar server side. + + :params queue_name: The name of the queue + :type queue_name: `six.text_type` + :params messages: Messages to post. + :type messages: list + """ + auth = password.Password( + "http://127.0.0.1/identity_v2_admin", + username="admin", + password="passw0rd", + user_domain_name='default', + project_name='admin', + project_domain_name='default') + keystone_session = session.Session(verify=False, cert=None, auth=auth) + + cli = client.Client(session=keystone_session) + queue = cli.queue(queue_name) + queue.post(messages) + + for msg in queue.messages(echo=True): + print(msg.body) + msg.delete() + + queue.delete() + + +if __name__ == '__main__': + messages = [{'body': {'id': idx}, 'ttl': 360} + for idx in range(20)] + create_post_delete('my_queue', messages) diff --git a/tests/unit/auth/test_keystone.py b/tests/unit/auth/test_keystone.py index a49a0504..bd76a54e 100644 --- a/tests/unit/auth/test_keystone.py +++ b/tests/unit/auth/test_keystone.py @@ -16,41 +16,32 @@ import mock -try: - from keystoneclient.v2_0 import client as ksclient -except ImportError: - ksclient = None +from keystoneauth1 import session from zaqarclient import auth from zaqarclient.tests import base from zaqarclient.transport import request -class _FakeKeystoneClient(object): - auth_token = 'fake-token' - - def __init__(self, *args, **kwargs): - pass - - class TestKeystoneAuth(base.TestBase): def setUp(self): super(TestKeystoneAuth, self).setUp() - if not ksclient: - self.skipTest('Keystone client is not installed') - self.auth = auth.get_backend(options=self.conf) - def test_no_token(self): + @mock.patch('keystoneauth1.session.Session.get_token', + return_value='fake-token') + def test_no_token(self, fake_session): test_endpoint = 'http://example.org:8888' + keystone_session = session.Session() - with mock.patch.object(ksclient, 'Client', - new_callable=lambda: _FakeKeystoneClient): + with mock.patch.object(self.auth, '_get_endpoint') as get_endpoint: + with mock.patch.object(self.auth, + '_get_keystone_session') as get_session: - with mock.patch.object(self.auth, '_get_endpoint') as get_endpoint: get_endpoint.return_value = test_endpoint + get_session.return_value = keystone_session req = self.auth.authenticate(1, request.Request()) self.assertEqual(test_endpoint, req.endpoint) diff --git a/zaqarclient/auth/keystone.py b/zaqarclient/auth/keystone.py index 0adc43b9..9e77e4a2 100644 --- a/zaqarclient/auth/keystone.py +++ b/zaqarclient/auth/keystone.py @@ -194,7 +194,8 @@ class KeystoneAuth(base.AuthBackend): for k in keys: ks_kwargs.update({k: get_options(k)}) - ks_session = self._get_keystone_session(**ks_kwargs) + ks_session = (request.session or + self._get_keystone_session(**ks_kwargs)) if not token: token = ks_session.get_token() if not request.endpoint: diff --git a/zaqarclient/queues/client.py b/zaqarclient/queues/client.py index 1a7e7b69..63abc805 100644 --- a/zaqarclient/queues/client.py +++ b/zaqarclient/queues/client.py @@ -77,13 +77,14 @@ _CLIENTS = {1: cv1.Client, 2: cv2.Client} -def Client(url=None, version=None, conf=None): +def Client(url=None, version=None, conf=None, session=None): # NOTE: Please don't mix use the Client object with different version at # the same time. Because the cache mechanism of queue's metadata will lead # to unexpected response value. # Please see zaqarclient.queues.v1.queues.Queue.metadata and # zaqarclient.queues.v2.queues.Queue.metadata for more detail. try: - return _CLIENTS[version](url, version, conf) + return _CLIENTS[version](url=url, version=version, conf=conf, + session=session) except KeyError: raise errors.ZaqarError('Unknown client version') diff --git a/zaqarclient/queues/v1/client.py b/zaqarclient/queues/v1/client.py index f4ebc120..b81925d3 100644 --- a/zaqarclient/queues/v1/client.py +++ b/zaqarclient/queues/v1/client.py @@ -39,12 +39,14 @@ class Client(object): - auth_opts: Authentication options: - backend - options + :param session: keystone session. But it's just place holder, we wont' + support it in v1. :type options: `dict` """ queues_module = queues - def __init__(self, url=None, version=1, conf=None): + def __init__(self, url=None, version=1, conf=None, session=None): self.conf = conf or {} self.api_url = url @@ -52,6 +54,7 @@ class Client(object): self.auth_opts = self.conf.get('auth_opts', {}) self.client_uuid = self.conf.get('client_uuid', uuid.uuid4().hex) + self.session = session def _get_transport(self, request): """Gets a transport and caches its instance @@ -73,7 +76,8 @@ class Client(object): def _request_and_transport(self): req = request.prepare_request(self.auth_opts, endpoint=self.api_url, - api=self.api_version) + api=self.api_version, + session=self.session) req.headers['Client-ID'] = self.client_uuid diff --git a/zaqarclient/queues/v2/client.py b/zaqarclient/queues/v2/client.py index 14eebfba..f66078ca 100644 --- a/zaqarclient/queues/v2/client.py +++ b/zaqarclient/queues/v2/client.py @@ -41,7 +41,7 @@ class Client(client.Client): queues_module = queues - def __init__(self, url=None, version=2, conf=None): + def __init__(self, url=None, version=2, conf=None, session=None): self.conf = conf or {} self.api_url = url @@ -49,6 +49,7 @@ class Client(client.Client): self.auth_opts = self.conf.get('auth_opts', {}) self.client_uuid = self.conf.get('client_uuid', uuid.uuid4().hex) + self.session = session def queue(self, ref, **kwargs): """Returns a queue instance diff --git a/zaqarclient/transport/request.py b/zaqarclient/transport/request.py index 28e3b39b..8a9821a2 100644 --- a/zaqarclient/transport/request.py +++ b/zaqarclient/transport/request.py @@ -43,9 +43,7 @@ def prepare_request(auth_opts=None, data=None, **kwargs): req = Request(**kwargs) auth_backend = auth.get_backend(**(auth_opts or {})) - # TODO(flaper87): Do something smarter - # to get the api_version. - req = auth_backend.authenticate(1, req) + req = auth_backend.authenticate(kwargs.get('api'), req) project_id = auth_opts.get('options', {}).get('os_project_id', {}) @@ -86,11 +84,17 @@ class Request(object): :type headers: dict :param api: Api entry point. i.e: 'queues.v1' :type api: `six.text_type`. + :param verify: If verify the SSL cert + :type verify: bool + :param cert: certificate of SSL + :type cert: `six.text_type` + :param session: Keystone session + :type session: keystone session object """ def __init__(self, endpoint='', operation='', ref='', content=None, params=None, - headers=None, api=None, verify=True, cert=None): + headers=None, api=None, verify=True, cert=None, session=None): self._api = None # ensure that some values like "v1.0" could work as "v1" @@ -108,6 +112,7 @@ class Request(object): self.headers = headers or {} self.verify = verify self.cert = cert + self.session = session @property def api(self):