From 4c103fdf49a16a9464c7785300672e91b069afe5 Mon Sep 17 00:00:00 2001 From: Paulo Ewerton Date: Mon, 4 Apr 2016 17:49:15 +0000 Subject: [PATCH] Adding keystoneauth sessions support This patch introduces the possibility of instantiating monascaclient using a keystoneauth session by doing the following: * Introduces new SessionClient object; * Refactor the keystoneclient wrapper module with keystoneauth; * Updates documentation. Client code and tests are also updated. Change-Id: I99ae10b2832869cf68d8219468400a421b6fa12b Closes-Bug: 1569505 --- README.rst | 42 ++++++++---- client_api_example.py | 10 +-- debian/control | 2 +- monascaclient/common/http.py | 102 +++++++++++++++++++++++++--- monascaclient/ksclient.py | 108 +++++++++++++++++------------- monascaclient/shell.py | 89 ++++++++++-------------- monascaclient/tests/fakes.py | 57 +++++++++++----- monascaclient/tests/test_shell.py | 26 ++++--- monascaclient/v2_0/client.py | 24 ++++++- requirements.txt | 2 +- 10 files changed, 302 insertions(+), 160 deletions(-) diff --git a/README.rst b/README.rst index e652596..f35d4a9 100644 --- a/README.rst +++ b/README.rst @@ -80,10 +80,13 @@ When using Keystone to obtain the token and endpoint:: export OS_PASSWORD= export OS_USER_DOMAIN_NAME= export OS_PROJECT_NAME= + export OS_PROJECT_DOMAIN_NAME= + export OS_PROJECT_NAME= export OS_AUTH_URL= export OS_REGION_NAME= -When OS_USER_DOMAIN_NAME is not set, then 'Default' is assumed. Alternatively IDs can be used instead of names. +When OS_USER_DOMAIN_NAME and OS_PROJECT_DOMAIN_NAME are not set, then 'Default' +is assumed. Alternatively IDs can be used instead of names. When using Vagrant Environment with middleware disabled:: @@ -102,7 +105,7 @@ You'll find complete documentation on the shell by running [--os-password OS_PASSWORD] [--os-project-id OS_PROJECT_ID] [--os-user-domain-id OS_USER_DOMAIN_ID] [--os-user-domain-name OS_USER_DOMAIN_NAME] [--os-project-name OS_PROJECT_NAME] - [--os-domain-id OS_DOMAIN_ID] [--os-domain-name OS_DOMAIN_NAME] + [--os-project-domain-id OS_PROJECT_DOMAIN_ID] [--os-project-domain-name OS_PROJECT_DOMAIN_NAME] [--os-auth-url OS_AUTH_URL] [--os-region-name OS_REGION_NAME] [--os-auth-token OS_AUTH_TOKEN] [--os-no-client-auth] [--monasca-api-url MONASCA_API_URL] @@ -169,10 +172,10 @@ You'll find complete documentation on the shell by running Defaults to env[OS_PROJECT_ID]. --os-project-name OS_PROJECT_NAME Defaults to env[OS_PROJECT_NAME]. - --os-domain-id OS_DOMAIN_ID - Defaults to env[OS_DOMAIN_ID]. - --os-domain-name OS_DOMAIN_NAME - Defaults to env[OS_DOMAIN_NAME]. + --os-project-domain-id OS_PROJECT_DOMAIN_ID + Defaults to env[OS_PROJECT_DOMAIN_ID]. + --os-project-domain-name OS_PROJECT_DOMAIN_NAME + Defaults to env[OS_PROJECT_DOMAIN_NAME]. --os-auth-url OS_AUTH_URL Defaults to env[OS_AUTH_URL]. --os-region-name OS_REGION_NAME Defaults to env[OS_REGION_NAME]. @@ -340,14 +343,20 @@ Python API There's also a complete Python API. In order to use the python api directly, you must pass in a valid auth token and -monasca api endpoint, or you can pass in the credentials required by the keystone -client and let the Python API do the authentication. The user can obtain the token -and endpoint using the keystone client api: -http://docs.openstack.org/developer/python-keystoneclient/. +monasca api endpoint or, preferably, a `keystoneauth session +`. +Alternatively, you can pass in the credentials required by the keystoneauth +and let the Python API do the authentication. The user can obtain the session, +token and endpoint using the keystoneauth api: +http://docs.openstack.org/developer/keystoneauth. The service catalog name for our API endpoint is "monasca". Start using the monascaclient API by constructing the monascaclient client.Client class. -The Client class takes these parameters: api_version, endpoint, and token. +The Client class takes these parameters: + +#. api_version, session +#. api_version, endpoint, and token (when using the client without a session) + The Client class is used to call all monasca-api resource commands (i.e. client.Client.metrics.create(fields)). @@ -358,7 +367,9 @@ up to the user to get a new token from keystone which can be passed into the client.Client.replace_token(token) method. If you constructed the Client with all the keystone credentials needed to authenticate, then the API will automatically try one time to re-authenticate with -keystone whenever the token expires. +keystone whenever the token expires. When using a session, though, this case +will be handled by keystoneauth and replacing the session token will +not be supported. The api_version matches the version of the Monasca API. Currently it is 'v2_0'. @@ -377,10 +388,13 @@ Refer to the example in python-monascaclient/client_api_example.py for more deta # Authenticate to Keystone keystone_url = 'http://keystone:5000/v3' - ks = ksclient.KSClient(auth_url=keystone_url, username='user', password='password') + ks = ksclient.KSClient(auth_url=keystone_url, username='user', + password='password', project_name='project_name', + project_domain_name='project_domain_name', + user_domain_name='user_domain_name') # construct the mon client - monasca_client = client.Client(api_version, ks.monasca_url, token=ks.token) + monasca_client = client.Client(api_version, session=ks.session) # call the metric-create command dimensions = {'instance_id': '12345', 'service': 'hello'} diff --git a/client_api_example.py b/client_api_example.py index 006e16d..ac2faa2 100644 --- a/client_api_example.py +++ b/client_api_example.py @@ -16,6 +16,7 @@ """ An example using monascaclient via the Python API """ from monascaclient import client +from monascaclient import ksclient import monascaclient.exc as exc import time @@ -27,15 +28,14 @@ endpoint = 'http://192.168.10.4:8080/v2.0' api_version = '2_0' # Pass in the keystone authentication kwargs to construct a monasca client. -# The monasca_client will try to authenticate with keystone one time -# when it sees a 401 unauthorized resp, to take care of a stale token. -# In this example no token is input, so it will get a 401 when executing the -# first metrics.create request, and will authenticate and try again. auth_kwargs = {'username': 'mini-mon', 'password': 'password', + 'user_domain_name': 'mini-mon', 'project_name': 'mini-mon', + 'project_domain_name': 'mini-mon', 'auth_url': 'http://192.168.10.5:35357/v3/'} -monasca_client = client.Client(api_version, endpoint, **auth_kwargs) +ksclient = ksclient.KSClient(**auth_kwargs) +monasca_client = client.Client(api_version, session=ksclient.session) # you can reference the monascaclient.v2_0.shell.py # do_commands for command field initialization. diff --git a/debian/control b/debian/control index ea7f428..863b95a 100644 --- a/debian/control +++ b/debian/control @@ -11,5 +11,5 @@ X-Python-Version: >= 2.6 Package: python-monclient Architecture: all Section: python -Depends: ${misc:Depends}, ${python:Depends}, libpython2.7, python-pkg-resources, python-pbr, python-keystoneclient, python-yaml, python-requests, python-prettytable, python-six +Depends: ${misc:Depends}, ${python:Depends}, libpython2.7, python-pkg-resources, python-pbr, keystoneauth1, python-yaml, python-requests, python-prettytable, python-six Description: CLI for Monitoring diff --git a/monascaclient/common/http.py b/monascaclient/common/http.py index a41d90c..3afc945 100644 --- a/monascaclient/common/http.py +++ b/monascaclient/common/http.py @@ -18,6 +18,7 @@ import logging import os import socket +from keystoneauth1 import adapter import requests import six @@ -53,6 +54,12 @@ def get_system_ca_file(): LOG.warn("System ca file could not be found.") +def unauthorized(resp): + status401 = (resp.status_code == 401) + status500 = (resp.status_code == 500 and "(HTTP 401)" in resp.content) + return status401 or status500 + + class HTTPClient(object): def __init__(self, endpoint, write_timeout=None, read_timeout=None, **kwargs): @@ -74,8 +81,8 @@ class HTTPClient(object): self.project_name = kwargs.get('project_name') self.region_name = kwargs.get('region_name') self.project_id = kwargs.get('project_id') - self.domain_id = kwargs.get('domain_id') - self.domain_name = kwargs.get('domain_name') + self.project_domain_id = kwargs.get('project_domain_id') + self.project_domain_name = kwargs.get('project_domain_name') self.endpoint_type = kwargs.get('endpoint_type') self.service_type = kwargs.get('service_type') self.keystone_timeout = kwargs.get('keystone_timeout') @@ -114,8 +121,8 @@ class HTTPClient(object): 'os_cacert': self.ssl_connection_params['os_cacert'], 'project_id': self.project_id, 'project_name': self.project_name, - 'domain_id': self.domain_id, - 'domain_name': self.domain_name, + 'project_domain_id': self.project_domain_id, + 'project_domain_name': self.project_domain_name, 'insecure': self.ssl_connection_params['insecure'], 'region_name': self.region_name, 'keystone_timeout': self.keystone_timeout @@ -213,7 +220,7 @@ class HTTPClient(object): resp = self._make_request(method, url, allow_redirects, timeout, **kwargs) - if self._unauthorized(resp): + if unauthorized(resp): try: # re-authenticate and attempt one more request self.re_authenticate() @@ -227,13 +234,8 @@ class HTTPClient(object): self._check_status_code(resp, method, **kwargs) return resp - def _unauthorized(self, resp): - status401 = (resp.status_code == 401) - status500 = (resp.status_code == 500 and "(HTTP 401)" in resp.content) - return status401 or status500 - def _check_status_code(self, resp, method, **kwargs): - if self._unauthorized(resp): + if unauthorized(resp): message = "Unauthorized error" raise exc.HTTPUnauthorized(message=message) elif 400 <= resp.status_code < 600: @@ -338,3 +340,81 @@ class HTTPClient(object): def patch(self, url, **kwargs): return self.client_request("PATCH", url, **kwargs) + + +class SessionClient(adapter.LegacyJsonAdapter): + """HTTP client based on keystoneauth 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): + kwargs.setdefault('user_agent', self.user_agent) + kwargs.setdefault('auth', self.auth) + kwargs.setdefault('endpoint_override', self.endpoint_override) + + endpoint_filter = kwargs.setdefault('endpoint_filter', {}) + endpoint_filter.setdefault('interface', self.interface) + endpoint_filter.setdefault('service_type', self.service_type) + endpoint_filter.setdefault('region_name', self.region_name) + + resp = self.session.request(url, method, raise_exc=False, **kwargs) + + if unauthorized(resp): + message = "Unauthorized error" + raise exc.HTTPUnauthorized(message=message) + if 400 <= resp.status_code < 600: + raise exc.from_response(resp) + elif resp.status_code in (301, 302, 305): + # Redirected. Reissue the request to the new location. + location = resp.headers.get('location') + resp = self._http_request(location, method, **kwargs) + elif resp.status_code == 300: + raise exc.from_response(resp) + + return resp + + def json_request(self, method, url, **kwargs): + kwargs.setdefault('headers', {}) + kwargs['headers'].setdefault('Content-Type', 'application/json') + kwargs['headers'].setdefault('Accept', 'application/json') + + if 'data' in kwargs: + kwargs['data'] = jsonutils.dumps(kwargs['data']) + + resp = self._http_request(url, method, **kwargs) + body = resp.content + content_type = resp.headers.get('content-type', None) + status = resp.status_code + + if status == 204 or status == 205 or content_type is None: + return resp, list() + + if 'application/json' in content_type: + try: + body = resp.json() + except ValueError: + LOG.error('Could not decode response body as JSON') + else: + body = None + + return resp, body + + def raw_request(self, method, url, **kwargs): + kwargs.setdefault('headers', {}) + kwargs['headers'].setdefault('Content-Type', + 'application/octet-stream') + self._http_request(url, method, **kwargs) + + def credentials_headers(self): + credentials = self.session.auth.get_cache_id_elements() + username = credentials.get('password_username') + password = credentials.get('password_password') + creds = {} + + if username: + creds['X-Auth-User'] = username + if password: + creds['X-Auth-Key'] = password + + return creds diff --git a/monascaclient/ksclient.py b/monascaclient/ksclient.py index 117dc98..f4b723b 100644 --- a/monascaclient/ksclient.py +++ b/monascaclient/ksclient.py @@ -14,11 +14,15 @@ # limitations under the License. """ -Wrapper around python keystone client to assist in getting a properly scoped token and the registered service -endpoint for Monasca. +Wrapper around keystoneauth to assist in getting a session, a properly scoped +token and the registered service endpoint for Monasca. + +# FIXME(pauloewerton): this is not an appropriate name for this module nor +# for the class. Kept for backwards compatibility. """ -from keystoneclient.v3 import client +from keystoneauth1.identity import v3 +from keystoneauth1 import loading from monascaclient import exc @@ -26,60 +30,74 @@ from monascaclient import exc class KSClient(object): def __init__(self, **kwargs): - """Get an endpoint and auth token from Keystone. + """Get a session, an endpoint and auth token from Keystone. :param username: name of user :param password: user's password - :param user_domain_id: unique identifier of domain username resides in (optional) - :param user_domain_name: name of domain for username (optional), if user_domain_id not specified + :param user_domain_id: unique identifier of domain the username + resides in (optional) + :param user_domain_name: name of domain for username (optional), + if user_domain_id not specified :param project_id: unique identifier of project :param project_name: name of project - :param domain_name: name of domain project is in - :param domain_id: id of domain project is in + :param project_domain_name: name of domain project is in + :param project_domain_id: id of domain project is in :param auth_url: endpoint to authenticate against :param token: token to use instead of username/password """ - kc_args = {'auth_url': kwargs.get('auth_url'), - 'insecure': kwargs.get('insecure'), - 'timeout': kwargs.get('keystone_timeout')} + auth_params = { + 'auth_url': kwargs.get('auth_url'), + 'project_id': kwargs.get('project_id'), + 'project_name': kwargs.get('project_name'), + 'project_domain_id': kwargs.get('project_domain_id'), + 'project_domain_name': kwargs.get('project_domain_name') + } - if kwargs.get('os_cacert'): - kc_args['cacert'] = kwargs.get('os_cacert') - if kwargs.get('project_id'): - kc_args['project_id'] = kwargs.get('project_id') - elif kwargs.get('project_name'): - kc_args['project_name'] = kwargs.get('project_name') - if kwargs.get('domain_name'): - kc_args['project_domain_name'] = kwargs.get('domain_name') - if kwargs.get('domain_id'): - kc_args['project_domain_id'] = kwargs.get('domain_id') + password_params = { + 'username': kwargs.get('username'), + 'password': kwargs.get('password'), + 'user_domain_id': kwargs.get('user_domain_id'), + 'user_domain_name': kwargs.get('user_domain_name') + } - if kwargs.get('token'): - kc_args['token'] = kwargs.get('token') + token = kwargs.get('token') + + if token: + auth_params['token'] = token + auth = v3.Token(**auth_params) else: - kc_args['username'] = kwargs.get('username') - kc_args['password'] = kwargs.get('password') - # when username not in the default domain (id='default'), supply user domain (as namespace) - if kwargs.get('user_domain_name'): - kc_args['user_domain_name'] = kwargs.get('user_domain_name') - if kwargs.get('user_domain_id'): - kc_args['user_domain_id'] = kwargs.get('user_domain_id') + auth_params.update(password_params) + auth = v3.Password(**auth_params) + + session_params = { + 'insecure': kwargs.get('insecure'), + 'cacert': kwargs.get('os_cacert'), + 'cert': kwargs.get('cert_file'), + 'key': kwargs.get('key_file') + } + + self._session = loading.session.Session().load_from_options( + auth=auth, **session_params) self._kwargs = kwargs - self._keystone = client.Client(**kc_args) self._token = None self._monasca_url = None + @property + def session(self): + """Return the keystoneauth session object used for authentication.""" + return self._session + @property def token(self): """Token property - Validate token is project scoped and return it if it is - project_id and auth_token were fetched when keystone client was created + Validate token is project scoped and return it if its project_id and + token were fetched when keystoneauth session was created """ if self._token is None: - if self._keystone.project_id: - self._token = self._keystone.auth_token + if self._session.get_project_id(): + self._token = self._session.get_token() else: raise exc.CommandError("No project id or project name.") return self._token @@ -87,15 +105,13 @@ class KSClient(object): @property def monasca_url(self): """Return the monasca publicURL registered in keystone.""" - if self._monasca_url is None: - if self._kwargs.get('region_name'): - self._monasca_url = self._keystone.service_catalog.url_for( - service_type=self._kwargs.get('service_type') or 'monitoring', - attr='region', - filter_value=self._kwargs.get('region_name'), - endpoint_type=self._kwargs.get('endpoint_type') or 'publicURL') - else: - self._monasca_url = self._keystone.service_catalog.url_for( - service_type=self._kwargs.get('service_type') or 'monitoring', - endpoint_type=self._kwargs.get('endpoint_type') or 'publicURL') + service_type = self._kwargs.get('service_type', 'monitoring') + service_name = self._kwargs.get('service_name', 'monasca') + interface = self._kwargs.get('endpoint_type', 'public') + region_name = self._kwargs.get('region_name') + + if not self._monasca_url: + self._monasca_url = self._session.get_endpoint( + service_type=service_type, service_name=service_name, + interface=interface, region_name=region_name) return self._monasca_url diff --git a/monascaclient/shell.py b/monascaclient/shell.py index 68c6965..2cb970b 100644 --- a/monascaclient/shell.py +++ b/monascaclient/shell.py @@ -140,18 +140,18 @@ class MonascaShell(object): parser.add_argument('--os_project_name', help=argparse.SUPPRESS) - parser.add_argument('--os-domain-id', - default=utils.env('OS_DOMAIN_ID'), - help='Defaults to env[OS_DOMAIN_ID].') + parser.add_argument('--os-project-domain-id', + default=utils.env('OS_PROJECT_DOMAIN_ID'), + help='Defaults to env[OS_PROJECT_DOMAIN_ID].') - parser.add_argument('--os_domain_id', + parser.add_argument('--os_project_domain_id', help=argparse.SUPPRESS) - parser.add_argument('--os-domain-name', - default=utils.env('OS_DOMAIN_NAME'), - help='Defaults to env[OS_DOMAIN_NAME].') + parser.add_argument('--os-project-domain-name', + default=utils.env('OS_PROJECT_DOMAIN_NAME'), + help='Defaults to env[OS_PROJECT_DOMAIN_NAME].') - parser.add_argument('--os_domain_name', + parser.add_argument('--os_project_domain_name', help=argparse.SUPPRESS) parser.add_argument('--os-auth-url', @@ -329,71 +329,50 @@ class MonascaShell(object): 'auth_url': args.os_auth_url, 'service_type': args.os_service_type, 'endpoint_type': args.os_endpoint_type, - 'os_cacert': args.os_cacert, 'user_domain_id': args.os_user_domain_id, 'user_domain_name': args.os_user_domain_name, 'project_id': args.os_project_id, 'project_name': args.os_project_name, - 'domain_id': args.os_domain_id, - 'domain_name': args.os_domain_name, + 'project_domain_id': args.os_project_domain_id, + 'project_domain_name': args.os_project_domain_name, 'insecure': args.insecure, + 'os_cacert': args.os_cacert, + 'cert_file': args.cert_file, + 'key_file': args.key_file, 'region_name': args.os_region_name, 'keystone_timeout': args.keystone_timeout } endpoint = args.monasca_api_url + session = None if not args.os_no_client_auth: _ksclient = ksclient.KSClient(**kwargs) - if args.os_auth_token: - token = args.os_auth_token - else: - try: - token = _ksclient.token - except exc.CommandError: - raise exc.CommandError( - "User does not have a default project. " - "You must provide a project id using " - "--os-project-id or via env[OS_PROJECT_ID], " - "or you must provide a project name using " - "--os-project-name or via env[OS_PROJECT_NAME] " - "and a project domain using --os-domain-name, via " - "env[OS_DOMAIN_NAME], using --os-domain-id or " - "via env[OS_DOMAIN_ID]") - kwargs = { - 'token': token, - 'insecure': args.insecure, - 'os_cacert': args.os_cacert, - 'cert_file': args.cert_file, - 'key_file': args.key_file, - 'username': args.os_username, - 'password': args.os_password, - 'service_type': args.os_service_type, - 'endpoint_type': args.os_endpoint_type, - 'auth_url': args.os_auth_url, - 'keystone_timeout': args.keystone_timeout - } - - if args.os_user_domain_name: - kwargs['user_domain_name'] = args.os_user_domain_name - if args.os_user_domain_id: - kwargs['user_domain_id'] = args.os_user_domain_id - if args.os_region_name: - kwargs['region_name'] = args.os_region_name - if args.os_project_name: - kwargs['project_name'] = args.os_project_name - if args.os_project_id: - kwargs['project_id'] = args.os_project_id - if args.os_domain_name: - kwargs['domain_name'] = args.os_domain_name - if args.os_domain_id: - kwargs['domain_id'] = args.os_domain_id + # Kept to check whether the token is indeed project scoped + try: + _ksclient.token + except exc.CommandError: + raise exc.CommandError( + "User does not have a default project. " + "You must provide a project id using " + "--os-project-id or via env[OS_PROJECT_ID], " + "or you must provide a project name using " + "--os-project-name or via env[OS_PROJECT_NAME] " + "and a project domain using --os-project-domain-name, " + "via env[OS_PROJECT_DOMAIN_NAME], using " + "--os-project-domain-id or via " + "env[OS_PROJECT_DOMAIN_ID]") if not endpoint: endpoint = _ksclient.monasca_url - client = monasca_client.Client(api_version, endpoint, **kwargs) + session = _ksclient.session + + if session: + client = monasca_client.Client(api_version, session=session) + else: + client = monasca_client.Client(api_version, endpoint, **kwargs) args.func(client, args) diff --git a/monascaclient/tests/fakes.py b/monascaclient/tests/fakes.py index 25574c4..44a094e 100644 --- a/monascaclient/tests/fakes.py +++ b/monascaclient/tests/fakes.py @@ -13,24 +13,34 @@ # See the License for the specific language governing permissions and # limitations under the License. -from keystoneclient.v3 import client as ksclient +from keystoneauth1.identity import v3 +from keystoneauth1.loading import session from oslo_serialization import jsonutils def script_keystone_client(token=None): + auth_params = {'auth_url': 'http://no.where', + 'project_id': 'project_id', + 'project_name': 'project_name', + 'project_domain_id': 'project_domain_id', + 'project_domain_name': 'project_domain_name'} + + password_params = {'username': 'username', + 'password': 'password', + 'user_domain_id': 'user_domain_id', + 'user_domain_name': 'user_domain_name'} + if token: - ksclient.Client(auth_url='http://no.where', - insecure=False, - tenant_id='tenant_id', - token=token).AndReturn(FakeKeystone(token, None)) + auth_params['token'] = token + auth = v3.Token(**auth_params).AndReturn(FakeKeystoneAuth(token, None)) else: - ksclient.Client(auth_url='http://no.where', - insecure=False, - password='password', - project_name='project_name', - timeout=20, - username='username').AndReturn(FakeKeystone( - 'abcd1234', 'test')) + auth_params.update(password_params) + auth = v3.Password(**auth_params).AndReturn( + FakeKeystoneAuth('abcd1234', 'test')) + + session.Session().load_from_options( + auth=auth, insecure=False, cacert=None, cert=None, key=None).AndReturn( + FakeSession(auth)) def fake_headers(): @@ -40,19 +50,34 @@ def fake_headers(): 'User-Agent': 'python-monascaclient'} -class FakeServiceCatalog(object): +class FakeSession(): - def url_for(self, endpoint_type, service_type): + def __init__(self, auth): + self.auth = auth + + def get_token(self): + return self.auth.auth_token + + def get_project_id(self): + return self.auth.project_id + + def get_endpoint(self, service_type, service_name, interface, region_name): return 'http://192.168.1.5:8004/v1/f14b41234' -class FakeKeystone(object): - service_catalog = FakeServiceCatalog() +class FakeKeystoneAuth(): def __init__(self, auth_token, project_id): self.auth_token = auth_token self.project_id = project_id + def get_cache_id_elements(self): + creds = { + 'password_username': 'username', + 'password_password': 'password' + } + return creds + class FakeRaw(object): version = 110 diff --git a/monascaclient/tests/test_shell.py b/monascaclient/tests/test_shell.py index 76a9f93..1c7cfc7 100644 --- a/monascaclient/tests/test_shell.py +++ b/monascaclient/tests/test_shell.py @@ -17,7 +17,8 @@ import re import sys import fixtures -from keystoneclient.v3 import client as ksclient +from keystoneauth1.identity import v3 +from keystoneauth1.loading import session from mox3 import mox import six import testtools @@ -35,7 +36,7 @@ class TestCase(testtools.TestCase): 'OS_USER_DOMAIN_NAME', 'OS_PROJECT_ID', 'OS_PROJECT_NAME', 'OS_AUTH_URL', 'OS_REGION_NAME', 'OS_AUTH_TOKEN', 'OS_NO_CLIENT_AUTH', 'OS_SERVICE_TYPE', - 'OS_DOMAIN_NAME', 'OS_DOMAIN_ID', + 'OS_PROJECT_DOMAIN_NAME', 'OS_PROJECT_DOMAIN_ID', 'OS_ENDPOINT_TYPE', 'MONASCA_API_URL') for key in client_env: @@ -70,9 +71,11 @@ class ShellBase(TestCase): def setUp(self): super(ShellBase, self).setUp() self.m = mox.Mox() - self.m.StubOutWithMock(ksclient, 'Client') - self.m.StubOutWithMock(http.HTTPClient, 'json_request') - self.m.StubOutWithMock(http.HTTPClient, 'raw_request') + self.m.StubOutWithMock(v3, 'Password') + self.m.StubOutWithMock(v3, 'Token') + self.m.StubOutWithMock(session.Session, 'load_from_options') + self.m.StubOutWithMock(http.SessionClient, 'json_request') + self.m.StubOutWithMock(http.SessionClient, 'raw_request') self.addCleanup(self.m.VerifyAll) self.addCleanup(self.m.UnsetStubs) @@ -155,7 +158,12 @@ class ShellTestMonascaCommands(ShellBase): 'OS_USERNAME': 'username', 'OS_PASSWORD': 'password', 'OS_PROJECT_NAME': 'project_name', + 'OS_PROJECT_ID': 'project_id', 'OS_AUTH_URL': 'http://no.where', + 'OS_PROJECT_DOMAIN_ID': 'project_domain_id', + 'OS_PROJECT_DOMAIN_NAME': 'project_domain_name', + 'OS_USER_DOMAIN_ID': 'user_domain_id', + 'OS_USER_DOMAIN_NAME': 'user_domain_name', } self.set_fake_env(fake_env) @@ -180,7 +188,7 @@ class ShellTestMonascaCommands(ShellBase): 'Created', {'location': 'http://no.where/v2.0/metrics'}, None) - http.HTTPClient.json_request( + http.SessionClient.json_request( 'POST', '/metrics', data={'timestamp': 1395691090, @@ -235,7 +243,7 @@ class ShellTestMonascaCommands(ShellBase): 'Created', {'location': 'http://no.where/v2.0/notification-methods'}, None) - http.HTTPClient.json_request( + http.SessionClient.json_request( 'POST', '/notification-methods', data={'name': 'email1', @@ -261,7 +269,7 @@ class ShellTestMonascaCommands(ShellBase): 'Created', {'location': 'http://no.where/v2.0/notification-methods'}, None) - http.HTTPClient.json_request( + http.SessionClient.json_request( 'POST', '/notification-methods', data={'name': 'mypost', @@ -296,7 +304,7 @@ class ShellTestMonascaCommands(ShellBase): 'Created', {'location': 'http://no.where/v2.0/notification-methods'}, None) - http.HTTPClient.json_request( + http.SessionClient.json_request( 'PUT', '/alarm-definitions/' + id, data={'name': name, diff --git a/monascaclient/v2_0/client.py b/monascaclient/v2_0/client.py index e267333..d22be5e 100644 --- a/monascaclient/v2_0/client.py +++ b/monascaclient/v2_0/client.py @@ -15,6 +15,7 @@ import string from monascaclient.common import http +from monascaclient import exc from monascaclient.v2_0 import alarm_definitions from monascaclient.v2_0 import alarms from monascaclient.v2_0 import metrics @@ -25,6 +26,7 @@ class Client(object): """Client for the Monasca v2_0 API. + :param session: a keystoneauth session object :param string endpoint: A user-supplied endpoint URL for the monasca api service. :param string token: Token for authentication. @@ -37,7 +39,21 @@ class Client(object): if 'auth_url' in kwargs and 'v2.0' in kwargs['auth_url']: kwargs['auth_url'] = string.replace( kwargs['auth_url'], 'v2.0', 'v3') - self.http_client = http.HTTPClient(*args, **kwargs) + + session = kwargs.get('session') + + if session: + self.http_client = http.SessionClient( + session=session, + service_type=kwargs.get('service_type', 'monitoring'), + service_name=kwargs.get('service_name', 'monasca'), + interface=kwargs.get('endpoint_type', 'public'), + region_name=kwargs.get('region_name'), + endpoint_override=kwargs.get('endpoint')) + else: + # For backward compatibility in case no session is passed + self.http_client = http.HTTPClient(*args, **kwargs) + self.metrics = metrics.MetricsManager(self.http_client) self.notifications = notifications.NotificationsManager( self.http_client) @@ -46,4 +62,8 @@ class Client(object): self.http_client) def replace_token(self, token): - self.http_client.replace_token(token) + if isinstance(self.http_client, http.SessionClient): + raise exc.CommandError(message='Token replacement is not ' + 'supported for session client.') + else: + self.http_client.replace_token(token) diff --git a/requirements.txt b/requirements.txt index e3b4e8e..a94c38d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,7 +10,7 @@ oslo.serialization>=1.10.0 # Apache-2.0 oslo.service>=1.0.0 # Apache-2.0 oslo.utils>=3.5.0 # Apache-2.0 -python-keystoneclient!=1.8.0,!=2.1.0,>=1.6.0 # Apache-2.0 +keystoneauth1>=2.1.0 # Apache-2.0 Babel!=2.3.0,!=2.3.1,!=2.3.2,!=2.3.3,>=1.3 # BSD iso8601>=0.1.11 # MIT