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
This commit is contained in:
David Hu 2014-05-01 16:11:23 -07:00
parent 47232fa9b0
commit a98c1f3617
7 changed files with 684 additions and 349 deletions

View File

@ -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)

View File

@ -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,6 +42,173 @@ logger = logging.getLogger(__name__)
class HeatShell(object):
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 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('--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',
dest='os_key',
help='DEPRECATED! Use --os-key.')
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('--ca-file',
dest='os_cacert',
help='DEPRECATED! Use --os-cacert.')
parser.add_argument('--os-username',
default=utils.env('OS_USERNAME'),
help='Defaults to env[OS_USERNAME].')
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].')
parser.add_argument('--os_password',
help=argparse.SUPPRESS)
parser.add_argument('--os-tenant-id',
default=utils.env('OS_TENANT_ID'),
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',
default=utils.env('OS_TENANT_NAME'),
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',
default=utils.env('OS_AUTH_URL'),
help='Defaults to env[OS_AUTH_URL].')
parser.add_argument('--os_auth_url',
help=argparse.SUPPRESS)
parser.add_argument('--os-region-name',
default=utils.env('OS_REGION_NAME'),
help='Defaults to env[OS_REGION_NAME].')
parser.add_argument('--os_region_name',
help=argparse.SUPPRESS)
parser.add_argument('--os-auth-token',
default=utils.env('OS_AUTH_TOKEN'),
help='Defaults to env[OS_AUTH_TOKEN].')
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',
@ -63,92 +238,13 @@ class HeatShell(object):
default=False, action="store_true",
help="Print more verbose output.")
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]')
parser.add_argument('--cert-file',
help='Path of certificate file to use in SSL '
'connection. This file can optionally be '
'prepended with the private key.')
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.')
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('--api-timeout',
help='Number of seconds to wait for an '
'API response, '
'defaults to system socket timeout')
parser.add_argument('--os-username',
default=utils.env('OS_USERNAME'),
help='Defaults to env[OS_USERNAME].')
parser.add_argument('--os_username',
help=argparse.SUPPRESS)
parser.add_argument('--os-password',
default=utils.env('OS_PASSWORD'),
help='Defaults to env[OS_PASSWORD].')
parser.add_argument('--os_password',
help=argparse.SUPPRESS)
parser.add_argument('--os-tenant-id',
default=utils.env('OS_TENANT_ID'),
help='Defaults to env[OS_TENANT_ID].')
parser.add_argument('--os_tenant_id',
help=argparse.SUPPRESS)
parser.add_argument('--os-tenant-name',
default=utils.env('OS_TENANT_NAME'),
help='Defaults to env[OS_TENANT_NAME].')
parser.add_argument('--os_tenant_name',
help=argparse.SUPPRESS)
parser.add_argument('--os-auth-url',
default=utils.env('OS_AUTH_URL'),
help='Defaults to env[OS_AUTH_URL].')
parser.add_argument('--os_auth_url',
help=argparse.SUPPRESS)
parser.add_argument('--os-region-name',
default=utils.env('OS_REGION_NAME'),
help='Defaults to env[OS_REGION_NAME].')
parser.add_argument('--os_region_name',
help=argparse.SUPPRESS)
parser.add_argument('--os-auth-token',
default=utils.env('OS_AUTH_TOKEN'),
help='Defaults to env[OS_AUTH_TOKEN].')
parser.add_argument('--os_auth_token',
help=argparse.SUPPRESS)
# 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)

View File

@ -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):

View File

@ -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))

View File

@ -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()
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)
self.set_fake_env(FAKE_ENV_KEYSTONE_V2)
self.shell_error('stack-list', failed_msg)
@httpretty.activate
def test_stack_create_validation(self):
self.m.StubOutWithMock(ksclient, 'Client')
self.m.StubOutWithMock(http.HTTPClient, 'json_request')
fakes.script_keystone_client()
# 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)
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.V2_URL,
}
self.set_fake_env(fake_env)
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')
@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)
fake_env = {
'OS_USERNAME': 'username',
'OS_PASSWORD': 'password',
'OS_TENANT_NAME': 'tenant_name',
'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:

View File

@ -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(

View File

@ -3,6 +3,7 @@ discover
fixtures>=0.3.14
# Hacking already pins down pep8, pyflakes and flake8
hacking>=0.8.0,<0.9
httpretty>=0.8.0,!=0.8.1,!=0.8.2
mock>=1.0
mox3>=0.7.0
sphinx>=1.1.2,!=1.2.0,<1.3