From 55968a9ce4121ff2b048e5b08677b1858f90398f Mon Sep 17 00:00:00 2001 From: Sharat Sharma Date: Mon, 31 Jul 2017 17:08:55 +0530 Subject: [PATCH] Use keystoneauth plugins and session instead of keystoneclient MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This removes the usage of keystoneclient in favor of using keystoneauth's sessions. For this it uses keystone sessions by default for both the caller and the target. The default settings for the user and project's domain was removed as well, since passing these to the generic auth plugins caused a failure. They should only be set if the user had specified so. Change-Id: Ia8e461d9e27cf1256c988d49eeef050872d5af12 Co-Authored-By: Sharat Sharma Co-Authored-By: Thomas Hervé Implements-blueprint: mistral-use-keystoneauth --- functionaltests/run_tests.sh | 6 + mistralclient/api/base.py | 27 ++- mistralclient/api/httpclient.py | 53 +++--- mistralclient/api/v2/client.py | 4 + mistralclient/auth/keystone.py | 139 +++++++++------- mistralclient/shell.py | 12 +- mistralclient/tests/functional/cli/base.py | 8 + .../cli/v2/cli_multi_tenancy_tests.py | 3 +- mistralclient/tests/unit/test_client.py | 157 +++++++++++------- mistralclient/tests/unit/test_httpclient.py | 2 +- ...oneclient-dependency-f2981f29e6673f71.yaml | 6 + requirements.txt | 2 +- 12 files changed, 253 insertions(+), 166 deletions(-) create mode 100644 releasenotes/notes/remove-keystoneclient-dependency-f2981f29e6673f71.yaml diff --git a/functionaltests/run_tests.sh b/functionaltests/run_tests.sh index d616d36d..e27796c0 100755 --- a/functionaltests/run_tests.sh +++ b/functionaltests/run_tests.sh @@ -31,6 +31,8 @@ source openrc alt_demo alt_demo export OS_ALT_USERNAME=${OS_USERNAME} export OS_ALT_TENANT_NAME=${OS_TENANT_NAME} +export OS_ALT_USER_DOMAIN_NAME=${OS_USER_DOMAIN_NAME} +export OS_ALT_PROJECT_DOMAIN__NAME=${OS_PROJECT_DOMAIN_NAME} export OS_ALT_PASSWORD=${OS_PASSWORD} # Get admin credentials. @@ -46,10 +48,14 @@ uri = $OS_AUTH_URL user = $OS_USERNAME tenant = $OS_TENANT_NAME pass = $OS_PASSWORD +user_domain = $OS_USER_DOMAIN_NAME +project_domain = $OS_PROJECT_DOMAIN_NAME [demo] user = $OS_ALT_USERNAME tenant = $OS_ALT_TENANT_NAME pass = $OS_ALT_PASSWORD +user_domain = $OS_ALT_USER_DOMAIN_NAME +project_domain = $OS_ALT_PROJECT_DOMAIN_NAME EOF cd $MISTRALCLIENT_DIR diff --git a/mistralclient/api/base.py b/mistralclient/api/base.py index d69f8819..c2b71b53 100644 --- a/mistralclient/api/base.py +++ b/mistralclient/api/base.py @@ -15,6 +15,8 @@ import copy import json +from keystoneauth1 import exceptions + class Resource(object): resource_name = 'Something' @@ -89,7 +91,10 @@ class ResourceManager(object): if dump_json: data = json.dumps(data) - resp = self.http_client.post(url, data) + try: + resp = self.http_client.post(url, data) + except exceptions.HttpError as ex: + self._raise_api_exception(ex.response) if resp.status_code != 201: self._raise_api_exception(resp) @@ -100,7 +105,10 @@ class ResourceManager(object): if dump_json: data = json.dumps(data) - resp = self.http_client.put(url, data) + try: + resp = self.http_client.put(url, data) + except exceptions.HttpError as ex: + self._raise_api_exception(ex.response) if resp.status_code != 200: self._raise_api_exception(resp) @@ -108,7 +116,10 @@ class ResourceManager(object): return self.resource_class(self, extract_json(resp, response_key)) def _list(self, url, response_key=None): - resp = self.http_client.get(url) + try: + resp = self.http_client.get(url) + except exceptions.HttpError as ex: + self._raise_api_exception(ex.response) if resp.status_code != 200: self._raise_api_exception(resp) @@ -117,7 +128,10 @@ class ResourceManager(object): for resource_data in extract_json(resp, response_key)] def _get(self, url, response_key=None): - resp = self.http_client.get(url) + try: + resp = self.http_client.get(url) + except exceptions.HttpError as ex: + self._raise_api_exception(ex.response) if resp.status_code == 200: return self.resource_class(self, extract_json(resp, response_key)) @@ -125,7 +139,10 @@ class ResourceManager(object): self._raise_api_exception(resp) def _delete(self, url): - resp = self.http_client.delete(url) + try: + resp = self.http_client.delete(url) + except exceptions.HttpError as ex: + self._raise_api_exception(ex.response) if resp.status_code != 204: self._raise_api_exception(resp) diff --git a/mistralclient/api/httpclient.py b/mistralclient/api/httpclient.py index f355cca6..f0254fe9 100644 --- a/mistralclient/api/httpclient.py +++ b/mistralclient/api/httpclient.py @@ -15,15 +15,16 @@ import base64 import copy +import logging import os from oslo_utils import importutils -import requests -import logging +import requests AUTH_TOKEN = 'auth_token' +SESSION = 'session' CACERT = 'cacert' CERT_FILE = 'cert' CERT_KEY = 'key' @@ -33,6 +34,7 @@ USER_ID = 'user_id' REGION_NAME = 'region_name' TARGET_AUTH_TOKEN = 'target_auth_token' +TARGET_SESSION = 'target_session' TARGET_AUTH_URI = 'target_auth_url' TARGET_PROJECT_ID = 'target_project_id' TARGET_USER_ID = 'target_user_id' @@ -59,7 +61,9 @@ def log_request(func): class HTTPClient(object): def __init__(self, base_url, **kwargs): self.base_url = base_url - self.session = kwargs.pop('session', None) + self.session = kwargs.get('session') + if not self.session: + self.session = requests.Session() self.auth_token = kwargs.get(AUTH_TOKEN) self.project_id = kwargs.get(PROJECT_ID) self.user_id = kwargs.get(USER_ID) @@ -68,6 +72,7 @@ class HTTPClient(object): self.region_name = kwargs.get(REGION_NAME) self.ssl_options = {} + self.target_session = kwargs.get(TARGET_SESSION) self.target_auth_token = kwargs.get(TARGET_AUTH_TOKEN) self.target_auth_uri = kwargs.get(TARGET_AUTH_URI) self.target_user_id = kwargs.get(TARGET_USER_ID) @@ -80,11 +85,6 @@ class HTTPClient(object): TARGET_PROJECT_DOMAIN_NAME ) - if self.session: - self.crud_provider = self.session - else: - self.crud_provider = requests - if self.base_url.startswith('https'): if self.cacert and not os.path.exists(self.cacert): raise ValueError('Unable to locate cacert file ' @@ -94,47 +94,42 @@ class HTTPClient(object): LOG.warning('Client is set to not verify even though ' 'cacert is provided.') - # These are already set by the session, so it's not needed - if not self.session: - if self.insecure: - self.ssl_options['verify'] = False + if self.insecure: + self.ssl_options['verify'] = False + else: + if self.cacert: + self.ssl_options['verify'] = self.cacert else: - if self.cacert: - self.ssl_options['verify'] = self.cacert - else: - self.ssl_options['verify'] = True + self.ssl_options['verify'] = True - self.ssl_options['cert'] = ( - kwargs.get(CERT_FILE), - kwargs.get(CERT_KEY) - ) + self.ssl_options['cert'] = ( + kwargs.get(CERT_FILE), + kwargs.get(CERT_KEY) + ) @log_request def get(self, url, headers=None): options = self._get_request_options('get', headers) - return self.crud_provider.get(self.base_url + url, **options) + return self.session.get(self.base_url + url, **options) @log_request def post(self, url, body, headers=None): options = self._get_request_options('post', headers) - return self.crud_provider.post(self.base_url + url, - data=body, **options) + return self.session.post(self.base_url + url, data=body, **options) @log_request def put(self, url, body, headers=None): options = self._get_request_options('put', headers) - return self.crud_provider.put(self.base_url + url, - data=body, **options) + return self.session.put(self.base_url + url, data=body, **options) @log_request def delete(self, url, headers=None): options = self._get_request_options('delete', headers) - return self.crud_provider.delete(self.base_url + url, - **options) + return self.session.delete(self.base_url + url, **options) def _get_request_options(self, method, headers): headers = self._update_headers(headers) @@ -152,9 +147,9 @@ class HTTPClient(object): if not headers: headers = {} - if not self.session: + if isinstance(self.session, requests.Session): if self.auth_token: - headers['x-auth-token'] = self.auth_token + headers['X-Auth-Token'] = self.auth_token if self.project_id: headers['X-Project-Id'] = self.project_id diff --git a/mistralclient/api/v2/client.py b/mistralclient/api/v2/client.py index c7dd0f06..123e88b5 100644 --- a/mistralclient/api/v2/client.py +++ b/mistralclient/api/v2/client.py @@ -54,6 +54,10 @@ class Client(object): auth_handler = auth.get_auth_handler(auth_type) auth_response = auth_handler.authenticate(req, session=session) or {} + # If the session was None and we're using keystone auth, it will be + # created by the auth_handler. + session = auth_response.pop('session', None) + req.update(auth_response) mistral_url = auth_response.get('mistral_url') or mistral_url diff --git a/mistralclient/auth/keystone.py b/mistralclient/auth/keystone.py index 2d6f98f9..be105449 100644 --- a/mistralclient/auth/keystone.py +++ b/mistralclient/auth/keystone.py @@ -12,14 +12,19 @@ # See the License for the specific language governing permissions and # limitations under the License. -from keystoneclient import client -from mistralclient import auth +import logging + +import keystoneauth1.identity.generic as auth_plugin +from keystoneauth1 import session as ks_session +import mistralclient.api.httpclient as api +from mistralclient import auth as mistral_auth from oslo_serialization import jsonutils -import mistralclient.api.httpclient as api + +LOG = logging.getLogger(__name__) -class KeystoneAuthHandler(auth.AuthHandler): +class KeystoneAuthHandler(mistral_auth.AuthHandler): def authenticate(self, req, session=None): """Performs authentication via Keystone. @@ -44,8 +49,8 @@ class KeystoneAuthHandler(auth.AuthHandler): project_name = req.get('project_name') project_id = req.get('project_id') region_name = req.get('region_name') - user_domain_name = req.get('user_domain_name', 'Default') - project_domain_name = req.get('project_domain_name', 'Default') + user_domain_name = req.get('user_domain_name') + project_domain_name = req.get('project_domain_name') cacert = req.get('cacert') insecure = req.get('insecure', False) @@ -56,12 +61,8 @@ class KeystoneAuthHandler(auth.AuthHandler): target_auth_token = req.get('target_auth_token') target_project_name = req.get('target_project_name') target_project_id = req.get('target_project_id') - target_region_name = req.get('target_region_name') - target_user_domain_name = req.get('target_user_domain_name', 'Default') - target_project_domain_name = req.get( - 'target_project_domain_name', - 'Default' - ) + target_user_domain_name = req.get('target_user_domain_name') + target_project_domain_name = req.get('target_project_domain_name') target_cacert = req.get('target_cacert') target_insecure = req.get('target_insecure') @@ -77,33 +78,42 @@ class KeystoneAuthHandler(auth.AuthHandler): auth_response = {} - if session: - keystone = client.Client(session=session) - elif auth_url: - keystone = client.Client( - username=username, - user_id=user_id, - password=api_key, - token=auth_token, - tenant_id=project_id, - tenant_name=project_name, - auth_url=auth_url, - cacert=cacert, - insecure=insecure, - user_domain_name=user_domain_name, - project_domain_name=project_domain_name - ) - keystone.authenticate() - auth_response.update({ - api.AUTH_TOKEN: keystone.auth_token, - api.PROJECT_ID: keystone.project_id, - api.USER_ID: keystone.user_id, - }) + if not session: + auth = None + if auth_token: + auth = auth_plugin.Token( + auth_url=auth_url, + token=auth_token, + project_id=project_id, + project_name=project_name, + project_domain_name=project_domain_name, + cacert=cacert, + insecure=insecure) + elif api_key and (username or user_id): + auth = auth_plugin.Password( + auth_url=auth_url, + username=username, + user_id=user_id, + password=api_key, + user_domain_name=user_domain_name, + project_id=project_id, + project_name=project_name, + project_domain_name=project_domain_name) - if session or auth_url: + else: + # NOTE(jaosorior): We don't crash here cause it's needed for + # bash-completion to work. However, we do issue a warning to + # the user so if the request doesn't work. It's because of + # this. + LOG.warning("You must either provide a valid token or " + "a password (api_key) and a user.") + if auth: + session = ks_session.Session(auth=auth) + + if session: if not mistral_url: try: - mistral_url = keystone.service_catalog.url_for( + mistral_url = session.get_endpoint( service_type=service_type, endpoint_type=endpoint_type, region_name=region_name @@ -112,35 +122,48 @@ class KeystoneAuthHandler(auth.AuthHandler): mistral_url = None auth_response['mistral_url'] = mistral_url + auth_response['session'] = session if target_auth_url: - target_keystone = client.Client( - username=target_username, - user_id=target_user_id, - password=target_api_key, - token=target_auth_token, - tenant_id=target_project_id, - tenant_name=target_project_name, - project_id=target_project_id, - project_name=target_project_name, - auth_url=target_auth_url, - cacert=target_cacert, - insecure=target_insecure, - region_name=target_region_name, - user_domain_name=target_user_domain_name, - project_domain_name=target_project_domain_name - ) + if target_auth_token: + target_auth = auth_plugin.Token( + auth_url=target_auth_url, + token=target_auth_token, + project_id=target_project_id, + project_name=target_project_name, + project_domain_name=target_project_domain_name, + cacert=target_cacert, + insecure=target_insecure) + elif target_api_key and (target_username or target_user_id): + target_auth = auth_plugin.Password( + auth_url=target_auth_url, + username=target_username, + user_id=target_user_id, + password=target_api_key, + user_domain_name=target_user_domain_name, + project_id=target_project_id, + project_name=target_project_name, + project_domain_name=target_project_domain_name, + ) + else: + raise RuntimeError("You must either provide a valid token or " + "a password (target_api_key) and a user.") - target_keystone.authenticate() + target_session = ks_session.Session(auth=target_auth) + target_auth_headers = target_session.get_auth_headers() or {} + + # NOTE: (sharatss) The target_auth_token is required here so that + # it can be passed as a separate header later. + target_auth_token = target_auth_headers.get('X-Auth-Token') auth_response.update({ - api.TARGET_AUTH_TOKEN: target_keystone.auth_token, - api.TARGET_PROJECT_ID: target_keystone.project_id, - api.TARGET_USER_ID: target_keystone.user_id, + api.TARGET_AUTH_TOKEN: target_auth_token, + api.TARGET_PROJECT_ID: target_session.get_project_id(), + api.TARGET_USER_ID: target_session.get_user_id(), api.TARGET_AUTH_URI: target_auth_url, api.TARGET_SERVICE_CATALOG: jsonutils.dumps( - target_keystone.auth_ref - ) + target_auth.get_access( + target_session)._data['access']) }) return auth_response diff --git a/mistralclient/shell.py b/mistralclient/shell.py index 02353a27..d7a95095 100644 --- a/mistralclient/shell.py +++ b/mistralclient/shell.py @@ -285,8 +285,7 @@ class MistralShell(app.App): '--os-tenant-name', action='store', dest='tenant_name', - default=env('OS_TENANT_NAME', 'OS_PROJECT_NAME', - default='Default'), + default=env('OS_TENANT_NAME', 'OS_PROJECT_NAME'), help='Authentication tenant name (Env: OS_TENANT_NAME' ' or OS_PROJECT_NAME)' ) @@ -295,8 +294,7 @@ class MistralShell(app.App): '--os-project-name', action='store', dest='project_name', - default=env('OS_TENANT_NAME', 'OS_PROJECT_NAME', - default='Default'), + default=env('OS_TENANT_NAME', 'OS_PROJECT_NAME'), help='Authentication project name (Env: OS_TENANT_NAME' ' or OS_PROJECT_NAME), will use tenant_name if both' ' tenant_name and project_name are set' @@ -314,7 +312,7 @@ class MistralShell(app.App): '--os-project-domain-name', action='store', dest='project_domain_name', - default=env('OS_PROJECT_DOMAIN_NAME', default='Default'), + default=env('OS_PROJECT_DOMAIN_NAME'), help='Authentication project domain name' ' (Env: OS_PROJECT_DOMAIN_NAME)' ) @@ -323,7 +321,7 @@ class MistralShell(app.App): '--os-user-domain-name', action='store', dest='user_domain_name', - default=env('OS_USER_DOMAIN_NAME', default='Default'), + default=env('OS_USER_DOMAIN_NAME'), help='Authentication user domain name' ' (Env: OS_USER_DOMAIN_NAME)' ) @@ -435,7 +433,7 @@ class MistralShell(app.App): '--os-target-tenant-name', action='store', dest='target_tenant_name', - default=env('OS_TARGET_TENANT_NAME', 'Default'), + default=env('OS_TARGET_TENANT_NAME'), help='Authentication tenant name for target cloud' ' (Env: OS_TARGET_TENANT_NAME)' ) diff --git a/mistralclient/tests/functional/cli/base.py b/mistralclient/tests/functional/cli/base.py index 55df25d1..b870b151 100644 --- a/mistralclient/tests/functional/cli/base.py +++ b/mistralclient/tests/functional/cli/base.py @@ -39,10 +39,14 @@ def credentials(group='admin'): username = os.environ.get('OS_USERNAME') password = os.environ.get('OS_PASSWORD') tenant_name = os.environ.get('OS_TENANT_NAME') + user_domain = os.environ.get('OS_USER_DOMAIN_NAME') + project_domain = os.environ.get('OS_PROJECT_DOMAIN_NAME') else: username = os.environ.get('OS_ALT_USERNAME') password = os.environ.get('OS_ALT_PASSWORD') tenant_name = os.environ.get('OS_ALT_TENANT_NAME') + user_domain = os.environ.get('OS_ALT_USER_DOMAIN_NAME') + project_domain = os.environ.get('OS_ALT_PROJECT_DOMAIN_NAME') auth_url = os.environ.get('OS_AUTH_URL') @@ -52,6 +56,8 @@ def credentials(group='admin'): password = password or config.get(group, 'pass') tenant_name = tenant_name or config.get(group, 'tenant') auth_url = auth_url or config.get('auth', 'uri') + user_domain = user_domain or config.get(group, 'user_domain') + project_domain = project_domain or config.get(group, 'project_domain') # TODO(ddeja): Default value of OS_AUTH_URL is to provide url to v3 API. # Since tempest openstack client doesn't properly handle it, we switch @@ -77,6 +83,7 @@ class MistralCLIAuth(base.ClientTestBase): username=creds['username'], password=creds['password'], tenant_name=creds['tenant_name'], + project_name=creds['tenant_name'], uri=creds['auth_url'], cli_dir=CLI_DIR ) @@ -130,6 +137,7 @@ class MistralCLIAltAuth(base.ClientTestBase): clients = base.CLIClient( username=creds['username'], password=creds['password'], + project_name=creds['tenant_name'], tenant_name=creds['tenant_name'], uri=creds['auth_url'], cli_dir=CLI_DIR diff --git a/mistralclient/tests/functional/cli/v2/cli_multi_tenancy_tests.py b/mistralclient/tests/functional/cli/v2/cli_multi_tenancy_tests.py index 70971e74..1fe22bcf 100644 --- a/mistralclient/tests/functional/cli/v2/cli_multi_tenancy_tests.py +++ b/mistralclient/tests/functional/cli/v2/cli_multi_tenancy_tests.py @@ -192,7 +192,8 @@ class WorkflowSharingCLITests(base_v2.MistralClientTestBase): self.assertEqual('pending', status) - cmd_param = '%s workflow --status %s' % (self.wf[0]["ID"], new_status) + cmd_param = '%s workflow --status %s --member-id %s' % ( + self.wf[0]["ID"], new_status, self.get_project_id("demo")) member = self.mistral_alt_user("member-update", params=cmd_param) status = self.get_field_value(member, 'Status') diff --git a/mistralclient/tests/unit/test_client.py b/mistralclient/tests/unit/test_client.py index 7348e885..ebeadf6b 100644 --- a/mistralclient/tests/unit/test_client.py +++ b/mistralclient/tests/unit/test_client.py @@ -35,26 +35,26 @@ PROFILER_HMAC_KEY = 'SECRET_HMAC_KEY' class BaseClientTests(base.BaseTestCase): @staticmethod - def setup_keystone_mock(keystone_client_mock): - keystone_client_instance = keystone_client_mock.return_value + def setup_keystone_mock(session_mock): + keystone_client_instance = session_mock.return_value keystone_client_instance.auth_token = uuidutils.generate_uuid() keystone_client_instance.project_id = uuidutils.generate_uuid() keystone_client_instance.user_id = uuidutils.generate_uuid() keystone_client_instance.auth_ref = str(json.dumps({})) return keystone_client_instance - @mock.patch('keystoneclient.client.Client') - def test_mistral_url_from_catalog_v2(self, keystone_client_mock): - keystone_client_instance = self.setup_keystone_mock( - keystone_client_mock - ) + @mock.patch('keystoneauth1.session.Session') + def test_mistral_url_from_catalog_v2(self, session_mock): + session = mock.Mock() + session_mock.side_effect = [session] - url_for = mock.Mock(return_value='http://mistral_host:8989/v2') - keystone_client_instance.service_catalog.url_for = url_for + get_endpoint = mock.Mock(return_value='http://mistral_host:8989/v2') + session.get_endpoint = get_endpoint mistralclient = client.client( username='mistral', project_name='mistral', + api_key='password', auth_url=AUTH_HTTP_URL_v2_0, service_type='workflowv2' ) @@ -64,19 +64,20 @@ class BaseClientTests(base.BaseTestCase): mistralclient.actions.http_client.base_url ) - @mock.patch('keystoneclient.client.Client') - def test_mistral_url_from_catalog(self, keystone_client_mock): - keystone_client_instance = self.setup_keystone_mock( - keystone_client_mock - ) + @mock.patch('keystoneauth1.session.Session') + def test_mistral_url_from_catalog(self, session_mock): + session = mock.Mock() + session_mock.side_effect = [session] - url_for = mock.Mock(return_value='http://mistral_host:8989/v2') - - keystone_client_instance.service_catalog.url_for = url_for + get_endpoint = mock.Mock(return_value='http://mistral_host:8989/v2') + session.get_endpoint = get_endpoint mistralclient = client.client( username='mistral', project_name='mistral', + api_key='password', + user_domain_name='Default', + project_domain_name='Default', auth_url=AUTH_HTTP_URL_v3, service_type='workflowv2' ) @@ -86,82 +87,97 @@ class BaseClientTests(base.BaseTestCase): mistralclient.actions.http_client.base_url ) - @mock.patch('keystoneclient.client.Client') + @mock.patch('keystoneauth1.session.Session') @mock.patch('mistralclient.api.httpclient.HTTPClient') - def test_mistral_url_default(self, http_client_mock, keystone_client_mock): - keystone_client_instance = self.setup_keystone_mock( - keystone_client_mock - ) + def test_mistral_url_default(self, http_client_mock, session_mock): + session = mock.Mock() + session_mock.side_effect = [session] - url_for = mock.Mock(side_effect=Exception) - keystone_client_instance.service_catalog.url_for = url_for + get_endpoint = mock.Mock(side_effect=Exception) + session.get_endpoint = get_endpoint client.client( username='mistral', project_name='mistral', + api_key='password', + user_domain_name='Default', + project_domain_name='Default', auth_url=AUTH_HTTP_URL_v3 ) self.assertTrue(http_client_mock.called) mistral_url_for_http = http_client_mock.call_args[0][0] - kwargs = http_client_mock.call_args[1] self.assertEqual(MISTRAL_HTTP_URL, mistral_url_for_http) - self.assertEqual( - keystone_client_instance.auth_token, kwargs['auth_token'] - ) - self.assertEqual( - keystone_client_instance.project_id, kwargs['project_id'] - ) - self.assertEqual( - keystone_client_instance.user_id, kwargs['user_id'] - ) - @mock.patch('keystoneclient.client.Client') + @mock.patch('keystoneauth1.identity.generic.Password') + @mock.patch('keystoneauth1.session.Session') @mock.patch('mistralclient.api.httpclient.HTTPClient') def test_target_parameters_processed( self, http_client_mock, - keystone_client_mock + session_mock, + password_mock ): - keystone_client_instance = self.setup_keystone_mock( - keystone_client_mock - ) - url_for = mock.Mock(return_value='http://mistral_host:8989/v2') - keystone_client_instance.service_catalog.url_for = url_for + session = mock.MagicMock() + target_session = mock.MagicMock() + session_mock.side_effect = [session, target_session] + auth = mock.MagicMock() + password_mock.side_effect = [auth, auth] + + get_endpoint = mock.Mock(return_value='http://mistral_host:8989/v2') + session.get_endpoint = get_endpoint + + target_session.get_project_id = mock.Mock(return_value='projectid') + target_session.get_user_id = mock.Mock(return_value='userid') + target_session.get_auth_headers = mock.Mock(return_value={ + 'X-Auth-Token': 'authtoken' + }) + + mock_access = mock.MagicMock() + mock_catalog = mock.MagicMock() + mock_catalog.catalog = {} + mock_access.service_catalog = mock_catalog + auth.get_access = mock.Mock(return_value=mock_access) client.client( + username='user', + api_key='password', + user_domain_name='Default', + project_domain_name='Default', target_username='tmistral', target_project_name='tmistralp', target_auth_url=AUTH_HTTP_URL_v3, + target_api_key='tpassword', + target_user_domain_name='Default', + target_project_domain_name='Default', target_region_name='tregion' ) self.assertTrue(http_client_mock.called) mistral_url_for_http = http_client_mock.call_args[0][0] kwargs = http_client_mock.call_args[1] - self.assertEqual(MISTRAL_HTTP_URL, mistral_url_for_http) + self.assertEqual('http://mistral_host:8989/v2', mistral_url_for_http) expected_values = { - 'target_project_id': keystone_client_instance.project_id, - 'target_auth_token': keystone_client_instance.auth_token, - 'target_user_id': keystone_client_instance.user_id, + 'target_project_id': 'projectid', + 'target_auth_token': 'authtoken', + 'target_user_id': 'userid', 'target_auth_url': AUTH_HTTP_URL_v3, 'target_project_name': 'tmistralp', 'target_username': 'tmistral', 'target_region_name': 'tregion', - 'target_service_catalog': '"{}"' + 'target_service_catalog': "{}" } for key in expected_values: self.assertEqual(expected_values[key], kwargs[key]) - @mock.patch('keystoneclient.client.Client') + @mock.patch('keystoneauth1.session.Session') @mock.patch('mistralclient.api.httpclient.HTTPClient') - def test_mistral_url_https_insecure(self, http_client_mock, - keystone_client_mock): + def test_mistral_url_https_insecure(self, http_client_mock, session_mock): keystone_client_instance = self.setup_keystone_mock( # noqa - keystone_client_mock + session_mock ) expected_args = ( @@ -172,6 +188,9 @@ class BaseClientTests(base.BaseTestCase): mistral_url=MISTRAL_HTTPS_URL, username='mistral', project_name='mistral', + api_key='password', + user_domain_name='Default', + project_domain_name='Default', auth_url=AUTH_HTTP_URL_v3, cacert=None, insecure=True @@ -181,14 +200,13 @@ class BaseClientTests(base.BaseTestCase): self.assertEqual(http_client_mock.call_args[0], expected_args) self.assertEqual(http_client_mock.call_args[1]['insecure'], True) - @mock.patch('keystoneclient.client.Client') + @mock.patch('keystoneauth1.session.Session') @mock.patch('mistralclient.api.httpclient.HTTPClient') - def test_mistral_url_https_secure(self, http_client_mock, - keystone_client_mock): + def test_mistral_url_https_secure(self, http_client_mock, session_mock): fd, cert_path = tempfile.mkstemp(suffix='.pem') keystone_client_instance = self.setup_keystone_mock( # noqa - keystone_client_mock + session_mock ) expected_args = ( @@ -200,6 +218,9 @@ class BaseClientTests(base.BaseTestCase): mistral_url=MISTRAL_HTTPS_URL, username='mistral', project_name='mistral', + api_key='password', + user_domain_name='Default', + project_domain_name='Default', auth_url=AUTH_HTTP_URL_v3, cacert=cert_path, insecure=False @@ -212,10 +233,10 @@ class BaseClientTests(base.BaseTestCase): self.assertEqual(http_client_mock.call_args[0], expected_args) self.assertEqual(http_client_mock.call_args[1]['cacert'], cert_path) - @mock.patch('keystoneclient.client.Client') - def test_mistral_url_https_bad_cacert(self, keystone_client_mock): + @mock.patch('keystoneauth1.session.Session') + def test_mistral_url_https_bad_cacert(self, session_mock): keystone_client_instance = self.setup_keystone_mock( # noqa - keystone_client_mock + session_mock ) self.assertRaises( @@ -224,19 +245,22 @@ class BaseClientTests(base.BaseTestCase): mistral_url=MISTRAL_HTTPS_URL, username='mistral', project_name='mistral', + api_key='password', + user_domain_name='Default', + project_domain_name='Default', auth_url=AUTH_HTTP_URL_v3, cacert='/path/to/foobar', insecure=False ) @mock.patch('logging.Logger.warning') - @mock.patch('keystoneclient.client.Client') - def test_mistral_url_https_bad_insecure(self, keystone_client_mock, + @mock.patch('keystoneauth1.session.Session') + def test_mistral_url_https_bad_insecure(self, session_mock, log_warning_mock): fd, path = tempfile.mkstemp(suffix='.pem') keystone_client_instance = self.setup_keystone_mock( - keystone_client_mock + session_mock ) try: @@ -244,6 +268,9 @@ class BaseClientTests(base.BaseTestCase): mistral_url=MISTRAL_HTTPS_URL, user_id=keystone_client_instance.user_id, project_id=keystone_client_instance.project_id, + api_key='password', + user_domain_name='Default', + project_domain_name='Default', auth_url=AUTH_HTTP_URL_v3, cacert=path, insecure=True @@ -254,17 +281,19 @@ class BaseClientTests(base.BaseTestCase): self.assertTrue(log_warning_mock.called) - @mock.patch('keystoneclient.client.Client') + @mock.patch('keystoneauth1.session.Session') @mock.patch('mistralclient.api.httpclient.HTTPClient') - def test_mistral_profile_enabled(self, http_client_mock, - keystone_client_mock): + def test_mistral_profile_enabled(self, http_client_mock, session_mock): keystone_client_instance = self.setup_keystone_mock( # noqa - keystone_client_mock + session_mock ) client.client( username='mistral', project_name='mistral', + api_key='password', + user_domain_name='Default', + project_domain_name='Default', auth_url=AUTH_HTTP_URL_v3, profile=PROFILER_HMAC_KEY ) diff --git a/mistralclient/tests/unit/test_httpclient.py b/mistralclient/tests/unit/test_httpclient.py index dba27680..a6475efe 100644 --- a/mistralclient/tests/unit/test_httpclient.py +++ b/mistralclient/tests/unit/test_httpclient.py @@ -38,7 +38,7 @@ PROFILER_HMAC_KEY = 'SECRET_HMAC_KEY' PROFILER_TRACE_ID = uuidutils.generate_uuid() EXPECTED_AUTH_HEADERS = { - 'x-auth-token': AUTH_TOKEN, + 'X-Auth-Token': AUTH_TOKEN, 'X-Project-Id': PROJECT_ID, 'X-User-Id': USER_ID, 'X-Region-Name': REGION_NAME diff --git a/releasenotes/notes/remove-keystoneclient-dependency-f2981f29e6673f71.yaml b/releasenotes/notes/remove-keystoneclient-dependency-f2981f29e6673f71.yaml new file mode 100644 index 00000000..1ee1b64e --- /dev/null +++ b/releasenotes/notes/remove-keystoneclient-dependency-f2981f29e6673f71.yaml @@ -0,0 +1,6 @@ +--- +other: + - The dependency to python-keystoneclient was removed. Relying solely on + keystoneauth1. + - The user has to set the "OS_USER_DOMAIN_NAME" and "OS_PROJECT_DOMAIN_NAME" + explicitly if keystone v3 version is being used. diff --git a/requirements.txt b/requirements.txt index 4bb4c293..0be12617 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,7 +6,7 @@ osc-lib>=1.7.0 # Apache-2.0 oslo.utils>=3.20.0 # Apache-2.0 oslo.i18n!=3.15.2,>=2.1.0 # Apache-2.0 pbr!=2.1.0,>=2.0.0 # Apache-2.0 -python-keystoneclient>=3.8.0 # Apache-2.0 +keystoneauth1>=3.1.0 # Apache-2.0 PyYAML>=3.10.0 # MIT requests>=2.14.2 # Apache-2.0 six>=1.9.0 # MIT