diff options
author | David Hu <david.hu@hp.com> | 2014-05-01 16:11:23 -0700 |
---|---|---|
committer | David Hu <david.hu@hp.com> | 2014-07-17 21:50:43 -0700 |
commit | a98c1f3617604c4871263ff9cc986b27f302ef46 (patch) | |
tree | 9f36733afdc21d473f2d3b79ea7951dfdfb0dc2f /heatclient | |
parent | 47232fa9b011831e54e8b161d1a2da4b1ea7cd2b (diff) | |
download | python-heatclient-a98c1f3617604c4871263ff9cc986b27f302ef46.tar.gz |
Add keystone v3 auth support
This change enables the heat client to use the keystone v3 API (in
addition to v2). This allows user domains and tenant/project domains to
be specified. This is necessary because keystone v2 API is deprecated
as of Icehouse.
Change-Id: Id19c4e52b12b379746a36b1f9fb61eb2825c78f3
Diffstat (limited to 'heatclient')
-rw-r--r-- | heatclient/common/http.py | 59 | ||||
-rw-r--r-- | heatclient/shell.py | 482 | ||||
-rw-r--r-- | heatclient/tests/fakes.py | 18 | ||||
-rw-r--r-- | heatclient/tests/keystone_client_fixtures.py | 83 | ||||
-rw-r--r-- | heatclient/tests/test_shell.py | 326 | ||||
-rw-r--r-- | heatclient/v1/client.py | 2 |
6 files changed, 652 insertions, 318 deletions
diff --git a/heatclient/common/http.py b/heatclient/common/http.py index cbf4540..1007237 100644 --- a/heatclient/common/http.py +++ b/heatclient/common/http.py @@ -216,6 +216,12 @@ class HTTPClient(object): def credentials_headers(self): creds = {} + # NOTE(dhu): (shardy) When deferred_auth_method=password, Heat + # encrypts and stores username/password. For Keystone v3, the + # intent is to use trusts since SHARDY is working towards + # deferred_auth_method=trusts as the default. + # TODO(dhu): Make Keystone v3 work in Heat standalone mode. Maye + # require X-Auth-User-Domain. if self.username: creds['X-Auth-User'] = self.username if self.password: @@ -269,3 +275,56 @@ class HTTPClient(object): def patch(self, url, **kwargs): return self.client_request("PATCH", url, **kwargs) + + +class SessionClient(HTTPClient): + """HTTP client based on Keystone client session.""" + + # NOTE(dhu): Will eventually move to a common session client. + # https://bugs.launchpad.net/python-keystoneclient/+bug/1332337 + def __init__(self, session, auth, **kwargs): + self.session = session + self.auth = auth + + self.auth_url = kwargs.get('auth_url') + self.region_name = kwargs.get('region_name') + self.interface = kwargs.get('interface', + kwargs.get('endpoint_type', 'public')) + self.service_type = kwargs.get('service_type') + + self.include_pass = kwargs.get('include_pass') + self.username = kwargs.get('username') + self.password = kwargs.get('password') + # see if we can get the auth_url from auth plugin if one is not + # provided from kwargs + if not self.auth_url and hasattr(self.auth, 'auth_url'): + self.auth_url = self.auth.auth_url + + def _http_request(self, url, method, **kwargs): + kwargs.setdefault('user_agent', USER_AGENT) + kwargs.setdefault('auth', self.auth) + + 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) + + # TODO(gyee): what are these headers for? + if self.auth_url: + kwargs['headers'].setdefault('X-Auth-Url', self.auth_url) + if self.region_name: + kwargs['headers'].setdefault('X-Region-Name', self.region_name) + if self.include_pass and 'X-Auth-Key' not in kwargs['headers']: + kwargs['headers'].update(self.credentials_headers()) + + return self.session.request(url, method, raise_exc=False, **kwargs) + + +def _construct_http_client(*args, **kwargs): + session = kwargs.pop('session', None) + auth = kwargs.pop('auth', None) + + if session: + return SessionClient(session, auth, **kwargs) + else: + return HTTPClient(*args, **kwargs) diff --git a/heatclient/shell.py b/heatclient/shell.py index cd00ac6..bf8fb37 100644 --- a/heatclient/shell.py +++ b/heatclient/shell.py @@ -18,15 +18,23 @@ from __future__ import print_function import argparse import logging -import six import sys -from keystoneclient.v2_0 import client as ksclient +import six +import six.moves.urllib.parse as urlparse + +from keystoneclient.auth.identity import v2 as v2_auth +from keystoneclient.auth.identity import v3 as v3_auth +from keystoneclient.auth import token_endpoint +from keystoneclient import discover +from keystoneclient.openstack.common.apiclient import exceptions as ks_exc +from keystoneclient import session as kssession import heatclient from heatclient import client as heat_client from heatclient.common import utils from heatclient import exc +from heatclient.openstack.common.gettextutils import _ from heatclient.openstack.common import strutils logger = logging.getLogger(__name__) @@ -34,71 +42,52 @@ logger = logging.getLogger(__name__) class HeatShell(object): - def get_base_parser(self): - parser = argparse.ArgumentParser( - prog='heat', - description=__doc__.strip(), - epilog='See "heat help COMMAND" ' - 'for help on a specific command.', - add_help=False, - formatter_class=HelpFormatter, - ) - - # Global arguments - parser.add_argument('-h', '--help', - action='store_true', - help=argparse.SUPPRESS) - - parser.add_argument('--version', - action='version', - version=heatclient.__version__, - help="Shows the client version and exits.") - - parser.add_argument('-d', '--debug', - default=bool(utils.env('HEATCLIENT_DEBUG')), - action='store_true', - help='Defaults to env[HEATCLIENT_DEBUG].') - - parser.add_argument('-v', '--verbose', - default=False, action="store_true", - help="Print more verbose output.") - + def _append_global_identity_args(self, parser): + # FIXME(gyee): these are global identity (Keystone) arguments which + # should be consistent and shared by all service clients. Therefore, + # they should be provided by python-keystoneclient. We will need to + # refactor this code once this functionality is avaible in + # python-keystoneclient. parser.add_argument('-k', '--insecure', default=False, action='store_true', - help="Explicitly allow the client to perform " - "\"insecure\" SSL (https) requests. The server's " - "certificate will not be verified against any " - "certificate authorities. " - "This option should be used with caution.") - - parser.add_argument('--os-cacert', - metavar='<ca-certificate>', - default=utils.env('OS_CACERT', default=None), - help='Specify a CA bundle file to use in ' - 'verifying a TLS (https) server certificate. ' - 'Defaults to env[OS_CACERT]') + help='Explicitly allow heatclient to perform ' + '\"insecure SSL\" (https) requests. The server\'s ' + 'certificate will not be verified against any ' + 'certificate authorities. This option should ' + 'be used with caution.') - parser.add_argument('--cert-file', + parser.add_argument('--os-cert', help='Path of certificate file to use in SSL ' 'connection. This file can optionally be ' 'prepended with the private key.') + # for backward compatibility only + parser.add_argument('--cert-file', + dest='os_cert', + help='DEPRECATED! Use --os-cert.') + + parser.add_argument('--os-key', + help='Path of client key to use in SSL ' + 'connection. This option is not necessary ' + 'if your key is prepended to your cert file.') + parser.add_argument('--key-file', - help='Path of client key to use in SSL connection.' - 'This option is not necessary if your key is' - ' prepended to your cert file.') + dest='os_key', + help='DEPRECATED! Use --os-key.') - parser.add_argument('--ca-file', - help='Path of CA SSL certificate(s) used to verify' - ' the remote server\'s certificate. Without this' - ' option the client looks' - ' for the default system CA certificates.') + parser.add_argument('--os-cacert', + metavar='<ca-certificate-file>', + dest='os_cacert', + default=utils.env('OS_CACERT'), + help='Path of CA TLS certificate(s) used to ' + 'verify the remote server\'s certificate. ' + 'Without this option glance looks for the ' + 'default system CA certificates.') - parser.add_argument('--api-timeout', - help='Number of seconds to wait for an ' - 'API response, ' - 'defaults to system socket timeout') + parser.add_argument('--ca-file', + dest='os_cacert', + help='DEPRECATED! Use --os-cacert.') parser.add_argument('--os-username', default=utils.env('OS_USERNAME'), @@ -107,6 +96,61 @@ class HeatShell(object): parser.add_argument('--os_username', help=argparse.SUPPRESS) + parser.add_argument('--os-user-id', + default=utils.env('OS_USER_ID'), + help='Defaults to env[OS_USER_ID].') + + parser.add_argument('--os_user_id', + help=argparse.SUPPRESS) + + parser.add_argument('--os-user-domain-id', + default=utils.env('OS_USER_DOMAIN_ID'), + help='Defaults to env[OS_USER_DOMAIN_ID].') + + parser.add_argument('--os_user_domain_id', + help=argparse.SUPPRESS) + + parser.add_argument('--os-user-domain-name', + default=utils.env('OS_USER_DOMAIN_NAME'), + help='Defaults to env[OS_USER_DOMAIN_NAME].') + + parser.add_argument('--os_user_domain_name', + help=argparse.SUPPRESS) + + parser.add_argument('--os-project-id', + default=utils.env('OS_PROJECT_ID'), + help='Another way to specify tenant ID. ' + 'This option is mutually exclusive with ' + ' --os-tenant-id. ' + 'Defaults to env[OS_PROJECT_ID].') + + parser.add_argument('--os_project_id', + help=argparse.SUPPRESS) + + parser.add_argument('--os-project-name', + default=utils.env('OS_PROJECT_NAME'), + help='Another way to specify tenant name. ' + 'This option is mutually exclusive with ' + ' --os-tenant-name. ' + 'Defaults to env[OS_PROJECT_NAME].') + + parser.add_argument('--os_project_name', + help=argparse.SUPPRESS) + + 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_project_domain_id', + help=argparse.SUPPRESS) + + 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_project_domain_name', + help=argparse.SUPPRESS) + parser.add_argument('--os-password', default=utils.env('OS_PASSWORD'), help='Defaults to env[OS_PASSWORD].') @@ -119,6 +163,7 @@ class HeatShell(object): help='Defaults to env[OS_TENANT_ID].') parser.add_argument('--os_tenant_id', + default=utils.env('OS_TENANT_ID'), help=argparse.SUPPRESS) parser.add_argument('--os-tenant-name', @@ -126,6 +171,7 @@ class HeatShell(object): help='Defaults to env[OS_TENANT_NAME].') parser.add_argument('--os_tenant_name', + default=utils.env('OS_TENANT_NAME'), help=argparse.SUPPRESS) parser.add_argument('--os-auth-url', @@ -149,6 +195,56 @@ class HeatShell(object): parser.add_argument('--os_auth_token', help=argparse.SUPPRESS) + parser.add_argument('--os-service-type', + default=utils.env('OS_SERVICE_TYPE'), + help='Defaults to env[OS_SERVICE_TYPE].') + + parser.add_argument('--os_service_type', + help=argparse.SUPPRESS) + + parser.add_argument('--os-endpoint-type', + default=utils.env('OS_ENDPOINT_TYPE'), + help='Defaults to env[OS_ENDPOINT_TYPE].') + + parser.add_argument('--os_endpoint_type', + help=argparse.SUPPRESS) + + def get_base_parser(self): + parser = argparse.ArgumentParser( + prog='heat', + description=__doc__.strip(), + epilog='See "heat help COMMAND" ' + 'for help on a specific command.', + add_help=False, + formatter_class=HelpFormatter, + ) + + # Global arguments + parser.add_argument('-h', '--help', + action='store_true', + help=argparse.SUPPRESS) + + parser.add_argument('--version', + action='version', + version=heatclient.__version__, + help="Shows the client version and exits.") + + parser.add_argument('-d', '--debug', + default=bool(utils.env('HEATCLIENT_DEBUG')), + action='store_true', + help='Defaults to env[HEATCLIENT_DEBUG].') + + parser.add_argument('-v', '--verbose', + default=False, action="store_true", + help="Print more verbose output.") + + parser.add_argument('--api-timeout', + help='Number of seconds to wait for an ' + 'API response, ' + 'defaults to system socket timeout') + + # os-no-client-auth tells heatclient to use token, instead of + # env[OS_AUTH_URL] parser.add_argument('--os-no-client-auth', default=utils.env('OS_NO_CLIENT_AUTH'), action='store_true', @@ -169,20 +265,6 @@ class HeatShell(object): parser.add_argument('--heat_api_version', help=argparse.SUPPRESS) - parser.add_argument('--os-service-type', - default=utils.env('OS_SERVICE_TYPE'), - help='Defaults to env[OS_SERVICE_TYPE].') - - parser.add_argument('--os_service_type', - help=argparse.SUPPRESS) - - parser.add_argument('--os-endpoint-type', - default=utils.env('OS_ENDPOINT_TYPE'), - help='Defaults to env[OS_ENDPOINT_TYPE].') - - parser.add_argument('--os_endpoint_type', - help=argparse.SUPPRESS) - # This unused option should remain so that scripts that # use it do not break. It is suppressed so it will not # appear in the help. @@ -196,6 +278,12 @@ class HeatShell(object): action='store_true', help='Send os-username and os-password to heat.') + # FIXME(gyee): this method should come from python-keystoneclient. + # Will refactor this code once it is available. + # https://bugs.launchpad.net/python-keystoneclient/+bug/1332337 + + self._append_global_identity_args(parser) + return parser def get_subcommand_parser(self, version): @@ -241,45 +329,6 @@ class HeatShell(object): subparser.add_argument(*args, **kwargs) subparser.set_defaults(func=callback) - def _get_ksclient(self, **kwargs): - """Get an endpoint and auth token from Keystone. - - :param username: name of user - :param password: user's password - :param tenant_id: unique identifier of tenant - :param tenant_name: name of tenant - :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'), - 'cacert': kwargs.get('cacert')} - - if kwargs.get('tenant_id'): - kc_args['tenant_id'] = kwargs.get('tenant_id') - else: - kc_args['tenant_name'] = kwargs.get('tenant_name') - - if kwargs.get('token'): - kc_args['token'] = kwargs.get('token') - else: - kc_args['username'] = kwargs.get('username') - kc_args['password'] = kwargs.get('password') - - return ksclient.Client(**kc_args) - - def _get_endpoint(self, client, **kwargs): - """Get an endpoint using the provided keystone client.""" - if kwargs.get('region_name'): - return client.service_catalog.url_for( - service_type=kwargs.get('service_type') or 'orchestration', - attr='region', - filter_value=kwargs.get('region_name'), - endpoint_type=kwargs.get('endpoint_type') or 'publicURL') - return client.service_catalog.url_for( - service_type=kwargs.get('service_type') or 'orchestration', - endpoint_type=kwargs.get('endpoint_type') or 'publicURL') - def _setup_logging(self, debug): log_lvl = logging.DEBUG if debug else logging.WARNING logging.basicConfig( @@ -292,6 +341,132 @@ class HeatShell(object): if verbose: exc.verbose = 1 + def _discover_auth_versions(self, session, auth_url): + # discover the API versions the server is supporting base on the + # given URL + v2_auth_url = None + v3_auth_url = None + try: + ks_discover = discover.Discover(session=session, auth_url=auth_url) + v2_auth_url = ks_discover.url_for('2.0') + v3_auth_url = ks_discover.url_for('3.0') + except ks_exc.ClientException: + # Identity service may not support discover API version. + # Lets trying to figure out the API version from the original URL. + url_parts = urlparse.urlparse(auth_url) + (scheme, netloc, path, params, query, fragment) = url_parts + path = path.lower() + if path.startswith('/v3'): + v3_auth_url = auth_url + elif path.startswith('/v2'): + v2_auth_url = auth_url + else: + # not enough information to determine the auth version + msg = _('Unable to determine the Keystone version ' + 'to authenticate with using the given ' + 'auth_url. Identity service may not support API ' + 'version discovery. Please provide a versioned ' + 'auth_url instead.') + raise exc.CommandError(msg) + + return (v2_auth_url, v3_auth_url) + + def _get_keystone_session(self, **kwargs): + # first create a Keystone session + cacert = kwargs.pop('cacert', None) + cert = kwargs.pop('cert', None) + key = kwargs.pop('key', None) + insecure = kwargs.pop('insecure', False) + timeout = kwargs.pop('timeout', None) + verify = kwargs.pop('verify', None) + + # FIXME(gyee): this code should come from keystoneclient + if verify is None: + if insecure: + verify = False + else: + # TODO(gyee): should we do + # heatclient.common.http.get_system_ca_fle()? + verify = cacert or True + if cert and key: + # passing cert and key together is deprecated in favour of the + # requests lib form of having the cert and key as a tuple + cert = (cert, key) + + return kssession.Session(verify=verify, cert=cert, timeout=timeout) + + def _get_keystone_auth(self, session, auth_url, **kwargs): + # FIXME(gyee): this code should come from keystoneclient + # https://bugs.launchpad.net/python-keystoneclient/+bug/1332337 + + auth_token = kwargs.pop('auth_token', None) + # static token auth only + if auth_token: + endpoint = kwargs.pop('endpoint', None) + return token_endpoint.Token(endpoint, auth_token) + + # discover the supported keystone versions using the given url + (v2_auth_url, v3_auth_url) = self._discover_auth_versions( + session=session, + auth_url=auth_url) + + # Determine which authentication plugin to use. First inspect the + # auth_url to see the supported version. If both v3 and v2 are + # supported, then use the highest version if possible. + username = kwargs.pop('username', None) + user_id = kwargs.pop('user_id', None) + user_domain_name = kwargs.pop('user_domain_name', None) + user_domain_id = kwargs.pop('user_domain_id', None) + project_domain_name = kwargs.pop('project_domain_name', None) + project_domain_id = kwargs.pop('project_domain_id', None) + auth = None + if v3_auth_url and v2_auth_url: + # support both v2 and v3 auth. Use v3 if domain information is + # provided. + if (user_domain_name or user_domain_id or project_domain_name or + project_domain_id): + auth = v3_auth.Password( + v3_auth_url, + username=username, + user_id=user_id, + user_domain_name=user_domain_name, + user_domain_id=user_domain_id, + project_domain_name=project_domain_name, + project_domain_id=project_domain_id, + **kwargs) + else: + auth = v2_auth.Password( + v2_auth_url, + username, + kwargs.pop('password', None), + tenant_id=kwargs.pop('project_id', None), + tenant_name=kwargs.pop('project_name', None)) + elif v3_auth_url: + # support only v3 + auth = v3_auth.Password( + v3_auth_url, + username=username, + user_id=user_id, + user_domain_name=user_domain_name, + user_domain_id=user_domain_id, + project_domain_name=project_domain_name, + project_domain_id=project_domain_id, + **kwargs) + elif v2_auth_url: + # support only v2 + auth = v2_auth.Password( + v2_auth_url, + username, + kwargs.pop('password', None), + tenant_id=kwargs.pop('project_id', None), + tenant_name=kwargs.pop('project_name', None)) + else: + raise exc.CommandError('Unable to determine the Keystone version ' + 'to authenticate with using the given ' + 'auth_url.') + + return auth + def main(self, argv): # Parse args once to find version parser = self.get_base_parser() @@ -340,13 +515,21 @@ class HeatShell(object): " via either --heat-url or" " env[HEAT_URL]") else: - # Tenant name or ID is needed to make keystoneclient retrieve a - # service catalog, it's not required if os_no_client_auth is - # specified, neither is the auth URL - if not (args.os_tenant_id or args.os_tenant_name): - raise exc.CommandError("You must provide a tenant_id via" - " either --os-tenant-id or via" - " env[OS_TENANT_ID]") + # Tenant/project name or ID is needed to make keystoneclient + # retrieve a service catalog, it's not required if + # os_no_client_auth is specified, neither is the auth URL + + if not (args.os_tenant_id or args.os_tenant_name or + args.os_project_id or args.os_project_name): + raise exc.CommandError("You must provide a tenant id via" + " either --os-tenant-id or" + " env[OS_TENANT_ID] or a tenant name" + " via either --os-tenant-name or" + " env[OS_TENANT_NAME] or a project id" + " via either --os-project-id or" + " env[OS_PROJECT_ID] or a project" + " name via either --os-project-name or" + " env[OS_PROJECT_NAME]") if not args.os_auth_url: raise exc.CommandError("You must provide an auth url via" @@ -354,45 +537,54 @@ class HeatShell(object): " env[OS_AUTH_URL]") kwargs = { - 'username': args.os_username, - 'password': args.os_password, - 'token': args.os_auth_token, - 'tenant_id': args.os_tenant_id, - 'tenant_name': args.os_tenant_name, - 'auth_url': args.os_auth_url, - 'service_type': args.os_service_type, - 'endpoint_type': args.os_endpoint_type, 'insecure': args.insecure, 'cacert': args.os_cacert, - 'include_pass': args.include_password + 'cert': args.os_cert, + 'key': args.os_key, + 'timeout': args.api_timeout } + keystone_session = self._get_keystone_session(**kwargs) endpoint = args.heat_url - - if not args.os_no_client_auth: - _ksclient = self._get_ksclient(**kwargs) - token = args.os_auth_token or _ksclient.auth_token - + if args.os_no_client_auth: + kwargs = { + 'endpoint': endpoint, + 'auth_token': args.os_auth_token} + keystone_auth = self._get_keystone_auth(keystone_session, + args.os_auth_url, + **kwargs) + else: + project_id = args.os_project_id or args.os_tenant_id + project_name = args.os_project_name or args.os_tenant_name kwargs = { - 'token': token, - 'insecure': args.insecure, - 'ca_file': args.ca_file, - 'cert_file': args.cert_file, - 'key_file': args.key_file, 'username': args.os_username, + 'user_id': args.os_user_id, + 'user_domain_id': args.os_user_domain_id, + 'user_domain_name': args.os_user_domain_name, 'password': args.os_password, - 'endpoint_type': args.os_endpoint_type, - 'include_pass': args.include_password + 'auth_token': args.os_auth_token, + 'project_id': project_id, + 'project_name': project_name, + 'project_domain_id': args.os_project_domain_id, + 'project_domain_name': args.os_project_domain_name, } + keystone_auth = self._get_keystone_auth(keystone_session, + args.os_auth_url, + **kwargs) - if args.os_region_name: - kwargs['region_name'] = args.os_region_name - - if not endpoint: - endpoint = self._get_endpoint(_ksclient, **kwargs) - - if args.api_timeout: - kwargs['timeout'] = args.api_timeout + service_type = args.os_service_type or 'orchestration' + endpoint_type = args.os_endpoint_type or 'publicURL' + kwargs = { + 'auth_url': args.os_auth_url, + 'session': keystone_session, + 'auth': keystone_auth, + 'service_type': service_type, + 'endpoint_type': endpoint_type, + 'region_name': args.os_region_name, + 'username': args.os_username, + 'password': args.os_password, + 'include_pass': args.include_password + } client = heat_client.Client(api_version, endpoint, **kwargs) diff --git a/heatclient/tests/fakes.py b/heatclient/tests/fakes.py index f0c6ba0..8ce25ea 100644 --- a/heatclient/tests/fakes.py +++ b/heatclient/tests/fakes.py @@ -14,24 +14,6 @@ from heatclient.common import http from heatclient import exc from heatclient.openstack.common import jsonutils -from keystoneclient.v2_0 import client as ksclient - - -def script_keystone_client(token=None): - if token: - ksclient.Client(auth_url='http://no.where', - insecure=False, - cacert=None, - tenant_id='tenant_id', - token=token).AndReturn(FakeKeystone(token)) - else: - ksclient.Client(auth_url='http://no.where', - insecure=False, - cacert=None, - password='password', - tenant_name='tenant_name', - username='username').AndReturn(FakeKeystone( - 'abcd1234')) def script_heat_list(url=None): diff --git a/heatclient/tests/keystone_client_fixtures.py b/heatclient/tests/keystone_client_fixtures.py new file mode 100644 index 0000000..cd2104d --- /dev/null +++ b/heatclient/tests/keystone_client_fixtures.py @@ -0,0 +1,83 @@ +# 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. + +import uuid + +from heatclient.openstack.common import jsonutils +from keystoneclient.fixture import v2 as ks_v2_fixture +from keystoneclient.fixture import v3 as ks_v3_fixture + +# these are copied from python-keystoneclient tests +BASE_HOST = 'http://keystone.example.com' +BASE_URL = "%s:5000/" % BASE_HOST +UPDATED = '2013-03-06T00:00:00Z' + +V2_URL = "%sv2.0" % BASE_URL +V2_DESCRIBED_BY_HTML = {'href': 'http://docs.openstack.org/api/' + 'openstack-identity-service/2.0/content/', + 'rel': 'describedby', + 'type': 'text/html'} +V2_DESCRIBED_BY_PDF = {'href': 'http://docs.openstack.org/api/openstack-ident' + 'ity-service/2.0/identity-dev-guide-2.0.pdf', + 'rel': 'describedby', + 'type': 'application/pdf'} + +V2_VERSION = {'id': 'v2.0', + 'links': [{'href': V2_URL, 'rel': 'self'}, + V2_DESCRIBED_BY_HTML, V2_DESCRIBED_BY_PDF], + 'status': 'stable', + 'updated': UPDATED} + +V3_URL = "%sv3" % BASE_URL +V3_MEDIA_TYPES = [{'base': 'application/json', + 'type': 'application/vnd.openstack.identity-v3+json'}, + {'base': 'application/xml', + 'type': 'application/vnd.openstack.identity-v3+xml'}] + +V3_VERSION = {'id': 'v3.0', + 'links': [{'href': V3_URL, 'rel': 'self'}], + 'media-types': V3_MEDIA_TYPES, + 'status': 'stable', + 'updated': UPDATED} + +TOKENID = uuid.uuid4().hex + + +def _create_version_list(versions): + return jsonutils.dumps({'versions': {'values': versions}}) + + +def _create_single_version(version): + return jsonutils.dumps({'version': version}) + + +V3_VERSION_LIST = _create_version_list([V3_VERSION, V2_VERSION]) +V2_VERSION_LIST = _create_version_list([V2_VERSION]) + +V3_VERSION_ENTRY = _create_single_version(V3_VERSION) +V2_VERSION_ENTRY = _create_single_version(V2_VERSION) + +HEAT_ENDPOINT = 'http://www.heat.com/v1' + + +def keystone_request_callback(request, uri, headers): + response_headers = {"content-type": "application/json"} + token_id = TOKENID + if uri == BASE_URL: + return (200, headers, V3_VERSION_LIST) + elif uri == BASE_URL + "/v2.0": + v2_token = ks_v2_fixture.Token(token_id) + return (200, response_headers, jsonutils.dumps(v2_token)) + elif uri == BASE_URL + "/v3": + v3_token = ks_v3_fixture.Token() + response_headers["X-Subject-Token"] = token_id + return (201, response_headers, jsonutils.dumps(v3_token)) diff --git a/heatclient/tests/test_shell.py b/heatclient/tests/test_shell.py index 498b57b..e778ae3 100644 --- a/heatclient/tests/test_shell.py +++ b/heatclient/tests/test_shell.py @@ -19,6 +19,7 @@ from six.moves.urllib import request import sys import fixtures +import httpretty import tempfile import testscenarios import testtools @@ -27,18 +28,32 @@ from heatclient.openstack.common import jsonutils from heatclient.openstack.common import strutils from mox3 import mox -from keystoneclient.v2_0 import client as ksclient - from heatclient.common import http from heatclient import exc import heatclient.shell from heatclient.tests import fakes - +from heatclient.tests import keystone_client_fixtures load_tests = testscenarios.load_tests_apply_scenarios TEST_VAR_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), 'var')) +FAKE_ENV_KEYSTONE_V2 = { + 'OS_USERNAME': 'username', + 'OS_PASSWORD': 'password', + 'OS_TENANT_NAME': 'tenant_name', + 'OS_AUTH_URL': keystone_client_fixtures.BASE_URL, +} + +FAKE_ENV_KEYSTONE_V3 = { + 'OS_USERNAME': 'username', + 'OS_PASSWORD': 'password', + 'OS_TENANT_NAME': 'tenant_name', + 'OS_AUTH_URL': keystone_client_fixtures.BASE_URL, + 'OS_USER_DOMAIN_ID': 'default', + 'OS_PROJECT_DOMAIN_ID': 'default', +} + class TestCase(testtools.TestCase): @@ -74,6 +89,12 @@ class TestCase(testtools.TestCase): sys.stderr = orig return err + def register_keystone_auth_fixture(self): + httpretty.register_uri( + httpretty.GET, + keystone_client_fixtures.BASE_URL, + body=keystone_client_fixtures.keystone_request_callback) + class EnvVarTest(TestCase): @@ -86,7 +107,7 @@ class EnvVarTest(TestCase): err='You must provide a password')), ('tenant_name', dict( remove='OS_TENANT_NAME', - err='You must provide a tenant_id')), + err='You must provide a tenant id')), ('auth_url', dict( remove='OS_AUTH_URL', err='You must provide an auth url')), @@ -110,7 +131,7 @@ class EnvVarTestToken(TestCase): scenarios = [ ('tenant_id', dict( remove='OS_TENANT_ID', - err='You must provide a tenant_id')), + err='You must provide a tenant id')), ('auth_url', dict( remove='OS_AUTH_URL', err='You must provide an auth url')), @@ -151,17 +172,14 @@ class ShellParamValidationTest(TestCase): self.addCleanup(self.m.VerifyAll) self.addCleanup(self.m.UnsetStubs) + @httpretty.activate def test_bad_parameters(self): - self.m.StubOutWithMock(ksclient, 'Client') - self.m.StubOutWithMock(http.HTTPClient, 'json_request') - fakes.script_keystone_client() - - self.m.ReplayAll() + self.register_keystone_auth_fixture() fake_env = { 'OS_USERNAME': 'username', 'OS_PASSWORD': 'password', 'OS_TENANT_NAME': 'tenant_name', - 'OS_AUTH_URL': 'http://no.where', + 'OS_AUTH_URL': keystone_client_fixtures.BASE_URL, } self.set_fake_env(fake_env) template_file = os.path.join(TEST_VAR_DIR, 'minimal.template') @@ -177,35 +195,63 @@ class ShellValidationTest(TestCase): self.addCleanup(self.m.VerifyAll) self.addCleanup(self.m.UnsetStubs) + @httpretty.activate def test_failed_auth(self): - self.m.StubOutWithMock(ksclient, 'Client') + self.register_keystone_auth_fixture() self.m.StubOutWithMock(http.HTTPClient, 'json_request') - fakes.script_keystone_client() failed_msg = 'Unable to authenticate user with credentials provided' http.HTTPClient.json_request( 'GET', '/stacks?').AndRaise(exc.Unauthorized(failed_msg)) self.m.ReplayAll() + self.set_fake_env(FAKE_ENV_KEYSTONE_V2) + self.shell_error('stack-list', failed_msg) + + @httpretty.activate + def test_stack_create_validation(self): + # emulate Keystone version discovery + httpretty.register_uri( + httpretty.GET, + keystone_client_fixtures.V2_URL, + body=keystone_client_fixtures.V2_VERSION_ENTRY) + # emulate Keystone v2 token request + httpretty.register_uri( + httpretty.POST, + '%s/tokens' % (keystone_client_fixtures.V2_URL), + body=keystone_client_fixtures.keystone_request_callback) + fake_env = { 'OS_USERNAME': 'username', 'OS_PASSWORD': 'password', 'OS_TENANT_NAME': 'tenant_name', - 'OS_AUTH_URL': 'http://no.where', + 'OS_AUTH_URL': keystone_client_fixtures.V2_URL, } self.set_fake_env(fake_env) - self.shell_error('stack-list', failed_msg) + self.shell_error( + 'stack-create teststack ' + '--parameters="InstanceType=m1.large;DBUsername=wp;' + 'DBPassword=verybadpassword;KeyName=heat_key;' + 'LinuxDistribution=F17"', + 'Need to specify exactly one of') - def test_stack_create_validation(self): - self.m.StubOutWithMock(ksclient, 'Client') - self.m.StubOutWithMock(http.HTTPClient, 'json_request') - fakes.script_keystone_client() + @httpretty.activate + def test_stack_create_validation_keystone_v3(self): + # emulate Keystone version discovery + httpretty.register_uri( + httpretty.GET, + keystone_client_fixtures.V3_URL, + body=keystone_client_fixtures.V3_VERSION_ENTRY) + # emulate Keystone v2 token request + httpretty.register_uri( + httpretty.POST, + '%s/tokens' % (keystone_client_fixtures.V3_URL), + body=keystone_client_fixtures.keystone_request_callback) - self.m.ReplayAll() fake_env = { 'OS_USERNAME': 'username', 'OS_PASSWORD': 'password', 'OS_TENANT_NAME': 'tenant_name', - 'OS_AUTH_URL': 'http://no.where', + 'OS_AUTH_URL': keystone_client_fixtures.V3_URL, } self.set_fake_env(fake_env) self.shell_error( @@ -221,7 +267,6 @@ 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.addCleanup(self.m.VerifyAll) @@ -255,6 +300,7 @@ class ShellTestCommon(ShellBase): def setUp(self): super(ShellTestCommon, self).setUp() + self.set_fake_env(FAKE_ENV_KEYSTONE_V2) def test_help_unknown_command(self): self.assertRaises(exc.CommandError, self.shell, 'help foofoo') @@ -281,54 +327,36 @@ class ShellTestCommon(ShellBase): self.assertEqual(output1, output2) self.assertRegexpMatches(output1, '^usage: heat %s' % command) + @httpretty.activate def test_debug_switch_raises_error(self): - fakes.script_keystone_client() + self.register_keystone_auth_fixture() http.HTTPClient.json_request( 'GET', '/stacks?').AndRaise(exc.Unauthorized("FAIL")) self.m.ReplayAll() - fake_env = { - 'OS_USERNAME': 'username', - 'OS_PASSWORD': 'password', - 'OS_TENANT_NAME': 'tenant_name', - 'OS_AUTH_URL': 'http://no.where', - } - self.set_fake_env(fake_env) args = ['--debug', 'stack-list'] self.assertRaises(exc.Unauthorized, heatclient.shell.main, args) + @httpretty.activate def test_dash_d_switch_raises_error(self): - fakes.script_keystone_client() + self.register_keystone_auth_fixture() http.HTTPClient.json_request( 'GET', '/stacks?').AndRaise(exc.CommandError("FAIL")) self.m.ReplayAll() - fake_env = { - 'OS_USERNAME': 'username', - 'OS_PASSWORD': 'password', - 'OS_TENANT_NAME': 'tenant_name', - 'OS_AUTH_URL': 'http://no.where', - } - self.set_fake_env(fake_env) args = ['-d', 'stack-list'] self.assertRaises(exc.CommandError, heatclient.shell.main, args) + @httpretty.activate def test_no_debug_switch_no_raises_errors(self): - fakes.script_keystone_client() + self.register_keystone_auth_fixture() http.HTTPClient.json_request( 'GET', '/stacks?').AndRaise(exc.Unauthorized("FAIL")) self.m.ReplayAll() - fake_env = { - 'OS_USERNAME': 'username', - 'OS_PASSWORD': 'password', - 'OS_TENANT_NAME': 'tenant_name', - 'OS_AUTH_URL': 'http://no.where', - } - self.set_fake_env(fake_env) args = ['stack-list'] self.assertRaises(SystemExit, heatclient.shell.main, args) @@ -352,21 +380,12 @@ class ShellTestUserPass(ShellBase): super(ShellTestUserPass, self).setUp() self._set_fake_env() - # Patch os.environ to avoid required auth info. def _set_fake_env(self): - fake_env = { - 'OS_USERNAME': 'username', - 'OS_PASSWORD': 'password', - 'OS_TENANT_NAME': 'tenant_name', - 'OS_AUTH_URL': 'http://no.where', - } - self.set_fake_env(fake_env) - - def _script_keystone_client(self): - fakes.script_keystone_client() + self.set_fake_env(FAKE_ENV_KEYSTONE_V2) + @httpretty.activate def test_stack_list(self): - self._script_keystone_client() + self.register_keystone_auth_fixture() fakes.script_heat_list() self.m.ReplayAll() @@ -385,8 +404,9 @@ class ShellTestUserPass(ShellBase): for r in required: self.assertRegexpMatches(list_text, r) + @httpretty.activate def test_stack_list_with_args(self): - self._script_keystone_client() + self.register_keystone_auth_fixture() expected_url = '/stacks?%s' % parse.urlencode({ 'limit': 2, 'status': ['COMPLETE', 'FAILED'], @@ -413,7 +433,9 @@ class ShellTestUserPass(ShellBase): for r in required: self.assertRegexpMatches(list_text, r) + @httpretty.activate def test_parsable_error(self): + self.register_keystone_auth_fixture() message = "The Stack (bad) could not be found." resp_dict = { "explanation": "The resource could not be found.", @@ -426,7 +448,6 @@ class ShellTestUserPass(ShellBase): "title": "Not Found" } - self._script_keystone_client() fakes.script_heat_error(jsonutils.dumps(resp_dict)) self.m.ReplayAll() @@ -434,7 +455,9 @@ class ShellTestUserPass(ShellBase): e = self.assertRaises(exc.HTTPException, self.shell, "stack-show bad") self.assertEqual("ERROR: " + message, str(e)) + @httpretty.activate def test_parsable_verbose(self): + self.register_keystone_auth_fixture() message = "The Stack (bad) could not be found." resp_dict = { "explanation": "The resource could not be found.", @@ -447,7 +470,6 @@ class ShellTestUserPass(ShellBase): "title": "Not Found" } - self._script_keystone_client() fakes.script_heat_error(jsonutils.dumps(resp_dict)) self.m.ReplayAll() @@ -457,15 +479,18 @@ class ShellTestUserPass(ShellBase): e = self.assertRaises(exc.HTTPException, self.shell, "stack-show bad") self.assertIn(message, str(e)) + @httpretty.activate def test_parsable_malformed_error(self): + self.register_keystone_auth_fixture() invalid_json = "ERROR: {Invalid JSON Error." - self._script_keystone_client() fakes.script_heat_error(invalid_json) self.m.ReplayAll() e = self.assertRaises(exc.HTTPException, self.shell, "stack-show bad") self.assertEqual("ERROR: " + invalid_json, str(e)) + @httpretty.activate def test_parsable_malformed_error_missing_message(self): + self.register_keystone_auth_fixture() missing_message = { "explanation": "The resource could not be found.", "code": 404, @@ -476,14 +501,15 @@ class ShellTestUserPass(ShellBase): "title": "Not Found" } - self._script_keystone_client() fakes.script_heat_error(jsonutils.dumps(missing_message)) self.m.ReplayAll() e = self.assertRaises(exc.HTTPException, self.shell, "stack-show bad") self.assertEqual("ERROR: Internal Error", str(e)) + @httpretty.activate def test_parsable_malformed_error_missing_traceback(self): + self.register_keystone_auth_fixture() message = "The Stack (bad) could not be found." resp_dict = { "explanation": "The resource could not be found.", @@ -495,7 +521,6 @@ class ShellTestUserPass(ShellBase): "title": "Not Found" } - self._script_keystone_client() fakes.script_heat_error(jsonutils.dumps(resp_dict)) self.m.ReplayAll() @@ -505,8 +530,9 @@ class ShellTestUserPass(ShellBase): self.assertEqual("ERROR: The Stack (bad) could not be found.\n", str(e)) + @httpretty.activate def test_stack_show(self): - self._script_keystone_client() + self.register_keystone_auth_fixture() resp_dict = {"stack": { "id": "1", "stack_name": "teststack", @@ -537,8 +563,9 @@ class ShellTestUserPass(ShellBase): for r in required: self.assertRegexpMatches(list_text, r) + @httpretty.activate def test_stack_abandon(self): - self._script_keystone_client() + self.register_keystone_auth_fixture() resp_dict = {"stack": { "id": "1", @@ -580,7 +607,6 @@ class ShellTestUserPass(ShellBase): self.assertEqual(abandoned_stack, jsonutils.loads(abandon_resp)) def _output_fake_response(self): - self._script_keystone_client() resp_dict = {"stack": { "id": "1", @@ -618,24 +644,31 @@ class ShellTestUserPass(ShellBase): self.m.ReplayAll() + @httpretty.activate def test_output_list(self): + self.register_keystone_auth_fixture() self._output_fake_response() list_text = self.shell('output-list teststack/1') for r in ['output1', 'output2', 'output_uni']: self.assertRegexpMatches(list_text, r) + @httpretty.activate def test_output_show(self): + self.register_keystone_auth_fixture() self._output_fake_response() list_text = self.shell('output-show teststack/1 output1') self.assertRegexpMatches(list_text, 'value1') + @httpretty.activate def test_output_show_unicode(self): + self.register_keystone_auth_fixture() self._output_fake_response() list_text = self.shell('output-show teststack/1 output_uni') self.assertRegexpMatches(list_text, u'test\u2665') + @httpretty.activate def test_template_show_cfn(self): - self._script_keystone_client() + self.register_keystone_auth_fixture() template_data = open(os.path.join(TEST_VAR_DIR, 'minimal.template')).read() resp = fakes.FakeHTTPResponse( @@ -661,8 +694,9 @@ class ShellTestUserPass(ShellBase): for r in required: self.assertRegexpMatches(show_text, r) + @httpretty.activate def test_template_show_cfn_unicode(self): - self._script_keystone_client() + self.register_keystone_auth_fixture() resp_dict = {"AWSTemplateFormatVersion": "2010-09-09", "Description": u"test\u2665", "Outputs": {}, @@ -691,8 +725,9 @@ class ShellTestUserPass(ShellBase): for r in required: self.assertRegexpMatches(show_text, r) + @httpretty.activate def test_template_show_hot(self): - self._script_keystone_client() + self.register_keystone_auth_fixture() resp_dict = {"heat_template_version": "2013-05-23", "parameters": {}, "resources": {}, @@ -717,8 +752,9 @@ class ShellTestUserPass(ShellBase): for r in required: self.assertRegexpMatches(show_text, r) + @httpretty.activate def test_stack_preview(self): - self._script_keystone_client() + self.register_keystone_auth_fixture() resp_dict = {"stack": { "id": "1", "stack_name": "teststack", @@ -757,8 +793,9 @@ class ShellTestUserPass(ShellBase): for r in required: self.assertRegexpMatches(preview_text, r) + @httpretty.activate def test_stack_create(self): - self._script_keystone_client() + self.register_keystone_auth_fixture() resp = fakes.FakeHTTPResponse( 201, 'Created', @@ -790,8 +827,9 @@ class ShellTestUserPass(ShellBase): for r in required: self.assertRegexpMatches(create_text, r) + @httpretty.activate def test_stack_create_timeout(self): - self._script_keystone_client() + self.register_keystone_auth_fixture() template_file = os.path.join(TEST_VAR_DIR, 'minimal.template') template_data = open(template_file).read() resp = fakes.FakeHTTPResponse( @@ -837,8 +875,9 @@ class ShellTestUserPass(ShellBase): for r in required: self.assertRegexpMatches(create_text, r) + @httpretty.activate def test_stack_update_timeout(self): - self._script_keystone_client() + self.register_keystone_auth_fixture() template_file = os.path.join(TEST_VAR_DIR, 'minimal.template') template_data = open(template_file).read() resp = fakes.FakeHTTPResponse( @@ -884,9 +923,9 @@ class ShellTestUserPass(ShellBase): for r in required: self.assertRegexpMatches(update_text, r) + @httpretty.activate def test_stack_create_url(self): - - self._script_keystone_client() + self.register_keystone_auth_fixture() resp = fakes.FakeHTTPResponse( 201, 'Created', @@ -932,9 +971,9 @@ class ShellTestUserPass(ShellBase): for r in required: self.assertRegexpMatches(create_text, r) + @httpretty.activate def test_stack_create_object(self): - - self._script_keystone_client() + self.register_keystone_auth_fixture() template_file = os.path.join(TEST_VAR_DIR, 'minimal.template') template_data = open(template_file).read() http.HTTPClient.raw_request( @@ -972,8 +1011,9 @@ class ShellTestUserPass(ShellBase): for r in required: self.assertRegexpMatches(create_text, r) + @httpretty.activate def test_stack_adopt(self): - self._script_keystone_client() + self.register_keystone_auth_fixture() resp = fakes.FakeHTTPResponse( 201, 'Created', @@ -1007,17 +1047,19 @@ class ShellTestUserPass(ShellBase): for r in required: self.assertRegexpMatches(adopt_text, r) + @httpretty.activate def test_stack_adopt_without_data(self): + self.register_keystone_auth_fixture() failed_msg = 'Need to specify --adopt-file' - self._script_keystone_client() self.m.ReplayAll() template_file = os.path.join(TEST_VAR_DIR, 'minimal.template') self.shell_error( 'stack-adopt teststack ' '--template-file=%s ' % template_file, failed_msg) + @httpretty.activate def test_stack_update(self): - self._script_keystone_client() + self.register_keystone_auth_fixture() resp = fakes.FakeHTTPResponse( 202, 'Accepted', @@ -1050,8 +1092,9 @@ class ShellTestUserPass(ShellBase): for r in required: self.assertRegexpMatches(update_text, r) + @httpretty.activate def test_stack_delete(self): - self._script_keystone_client() + self.register_keystone_auth_fixture() resp = fakes.FakeHTTPResponse( 204, 'No Content', @@ -1075,8 +1118,9 @@ class ShellTestUserPass(ShellBase): for r in required: self.assertRegexpMatches(delete_text, r) + @httpretty.activate def test_stack_delete_multiple(self): - self._script_keystone_client() + self.register_keystone_auth_fixture() resp = fakes.FakeHTTPResponse( 204, 'No Content', @@ -1103,8 +1147,9 @@ class ShellTestUserPass(ShellBase): for r in required: self.assertRegexpMatches(delete_text, r) + @httpretty.activate def test_build_info(self): - self._script_keystone_client() + self.register_keystone_auth_fixture() resp_dict = { 'build_info': { 'api': {'revision': 'api_revision'}, @@ -1133,22 +1178,10 @@ class ShellTestUserPass(ShellBase): class ShellTestEvents(ShellBase): + def setUp(self): super(ShellTestEvents, self).setUp() - self._set_fake_env() - - # Patch os.environ to avoid required auth info. - def _set_fake_env(self): - fake_env = { - 'OS_USERNAME': 'username', - 'OS_PASSWORD': 'password', - 'OS_TENANT_NAME': 'tenant_name', - 'OS_AUTH_URL': 'http://no.where', - } - self.set_fake_env(fake_env) - - def _script_keystone_client(self): - fakes.script_keystone_client() + self.set_fake_env(FAKE_ENV_KEYSTONE_V2) scenarios = [ ('integer_id', dict( @@ -1158,8 +1191,9 @@ class ShellTestEvents(ShellBase): event_id_one='3d68809e-c4aa-4dc9-a008-933823d2e44f', event_id_two='43b68bae-ed5d-4aed-a99f-0b3d39c2418a'))] + @httpretty.activate def test_event_list(self): - self._script_keystone_client() + self.register_keystone_auth_fixture() resp_dict = {"events": [ {"event_time": "2013-12-05T14:14:30Z", "id": self.event_id_one, @@ -1224,8 +1258,9 @@ class ShellTestEvents(ShellBase): for r in required: self.assertRegexpMatches(event_list_text, r) + @httpretty.activate def test_event_show(self): - self._script_keystone_client() + self.register_keystone_auth_fixture() resp_dict = {"event": {"event_time": "2013-12-05T14:14:30Z", "id": self.event_id_one, @@ -1294,25 +1329,13 @@ class ShellTestEvents(ShellBase): class ShellTestResources(ShellBase): + def setUp(self): super(ShellTestResources, self).setUp() - self._set_fake_env() - - # Patch os.environ to avoid required auth info. - def _set_fake_env(self): - fake_env = { - 'OS_USERNAME': 'username', - 'OS_PASSWORD': 'password', - 'OS_TENANT_NAME': 'tenant_name', - 'OS_AUTH_URL': 'http://no.where', - } - self.set_fake_env(fake_env) - - def _script_keystone_client(self): - fakes.script_keystone_client() + self.set_fake_env(FAKE_ENV_KEYSTONE_V2) def _test_resource_list(self, with_resource_name): - self._script_keystone_client() + self.register_keystone_auth_fixture() resp_dict = {"resources": [ {"links": [{"href": "http://heat.example.com:8004/foo", "rel": "self"}, @@ -1361,14 +1384,17 @@ class ShellTestResources(ShellBase): for r in required: self.assertRegexpMatches(resource_list_text, r) + @httpretty.activate def test_resource_list(self): self._test_resource_list(True) + @httpretty.activate def test_resource_list_no_resource_name(self): self._test_resource_list(False) + @httpretty.activate def test_resource_list_empty(self): - self._script_keystone_client() + self.register_keystone_auth_fixture() resp_dict = {"resources": []} resp = fakes.FakeHTTPResponse( 200, @@ -1395,8 +1421,9 @@ class ShellTestResources(ShellBase): --------------+ ''', resource_list_text) + @httpretty.activate def test_resource_show(self): - self._script_keystone_client() + self.register_keystone_auth_fixture() resp_dict = {"resource": {"description": "", "links": [{"href": "http://heat.example.com:8004/foo", @@ -1455,8 +1482,9 @@ class ShellTestResources(ShellBase): for r in required: self.assertRegexpMatches(resource_show_text, r) + @httpretty.activate def test_resource_signal(self): - self._script_keystone_client() + self.register_keystone_auth_fixture() resp = fakes.FakeHTTPResponse( 200, 'OK', @@ -1480,8 +1508,9 @@ class ShellTestResources(ShellBase): stack_id, resource_name)) self.assertEqual("", text) + @httpretty.activate def test_resource_signal_no_data(self): - self._script_keystone_client() + self.register_keystone_auth_fixture() resp = fakes.FakeHTTPResponse( 200, 'OK', @@ -1503,8 +1532,9 @@ class ShellTestResources(ShellBase): 'resource-signal {0} {1}'.format(stack_id, resource_name)) self.assertEqual("", text) + @httpretty.activate def test_resource_signal_no_json(self): - self._script_keystone_client() + self.register_keystone_auth_fixture() stack_id = 'teststack/1' resource_name = 'aResource' @@ -1516,8 +1546,9 @@ class ShellTestResources(ShellBase): stack_id, resource_name)) self.assertIn('Data should be in JSON format', str(error)) + @httpretty.activate def test_resource_signal_no_dict(self): - self._script_keystone_client() + self.register_keystone_auth_fixture() stack_id = 'teststack/1' resource_name = 'aResource' @@ -1529,8 +1560,9 @@ class ShellTestResources(ShellBase): stack_id, resource_name)) self.assertEqual('Data should be a JSON dict', str(error)) + @httpretty.activate def test_resource_signal_both_data(self): - self._script_keystone_client() + self.register_keystone_auth_fixture() stack_id = 'teststack/1' resource_name = 'aResource' @@ -1543,8 +1575,9 @@ class ShellTestResources(ShellBase): self.assertEqual('Can only specify one of data and data-file', str(error)) + @httpretty.activate def test_resource_signal_data_file(self): - self._script_keystone_client() + self.register_keystone_auth_fixture() resp = fakes.FakeHTTPResponse( 200, 'OK', @@ -1575,23 +1608,11 @@ class ShellTestResources(ShellBase): class ShellTestResourceTypes(ShellBase): def setUp(self): super(ShellTestResourceTypes, self).setUp() - self._set_fake_env() - - # Patch os.environ to avoid required auth info. - def _set_fake_env(self): - fake_env = { - 'OS_USERNAME': 'username', - 'OS_PASSWORD': 'password', - 'OS_TENANT_NAME': 'tenant_name', - 'OS_AUTH_URL': 'http://no.where', - } - self.set_fake_env(fake_env) - - def _script_keystone_client(self): - fakes.script_keystone_client() + self.set_fake_env(FAKE_ENV_KEYSTONE_V3) + @httpretty.activate def test_resource_type_template_yaml(self): - self._script_keystone_client() + self.register_keystone_auth_fixture() resp_dict = {"heat_template_version": "2013-05-23", "parameters": {}, "resources": {}, @@ -1619,8 +1640,9 @@ class ShellTestResourceTypes(ShellBase): for r in required: self.assertRegexpMatches(show_text, r) + @httpretty.activate def test_resource_type_template_json(self): - self._script_keystone_client() + self.register_keystone_auth_fixture() resp_dict = {"AWSTemplateFormatVersion": "2013-05-23", "Parameters": {}, "Resources": {}, @@ -1652,23 +1674,18 @@ class ShellTestResourceTypes(ShellBase): class ShellTestBuildInfo(ShellBase): + def setUp(self): super(ShellTestBuildInfo, self).setUp() self._set_fake_env() def _set_fake_env(self): '''Patch os.environ to avoid required auth info.''' + self.set_fake_env(FAKE_ENV_KEYSTONE_V2) - fake_env = { - 'OS_USERNAME': 'username', - 'OS_PASSWORD': 'password', - 'OS_TENANT_NAME': 'tenant_name', - 'OS_AUTH_URL': 'http://no.where', - } - self.set_fake_env(fake_env) - + @httpretty.activate def test_build_info(self): - fakes.script_keystone_client() + self.register_keystone_auth_fixture() resp_dict = { 'build_info': { 'api': {'revision': 'api_revision'}, @@ -1717,8 +1734,11 @@ class ShellTestToken(ShellTestUserPass): } self.set_fake_env(fake_env) - def _script_keystone_client(self): - fakes.script_keystone_client(token=self.token) + +class ShellTestUserPassKeystoneV3(ShellTestUserPass): + + def _set_fake_env(self): + self.set_fake_env(FAKE_ENV_KEYSTONE_V3) class ShellTestStandaloneToken(ShellTestUserPass): @@ -1743,11 +1763,9 @@ class ShellTestStandaloneToken(ShellTestUserPass): } self.set_fake_env(fake_env) - def _script_keystone_client(self): - # The StanaloneMode shouldn't need any keystoneclient stubbing - pass - + @httpretty.activate def test_bad_template_file(self): + self.register_keystone_auth_fixture() failed_msg = 'Error parsing template ' with tempfile.NamedTemporaryFile() as bad_json_file: diff --git a/heatclient/v1/client.py b/heatclient/v1/client.py index 2ce7302..b5e3485 100644 --- a/heatclient/v1/client.py +++ b/heatclient/v1/client.py @@ -36,7 +36,7 @@ class Client(object): def __init__(self, *args, **kwargs): """Initialize a new client for the Heat v1 API.""" - self.http_client = http.HTTPClient(*args, **kwargs) + self.http_client = http._construct_http_client(*args, **kwargs) self.stacks = stacks.StackManager(self.http_client) self.resources = resources.ResourceManager(self.http_client) self.resource_types = resource_types.ResourceTypeManager( |