Merge "Add support for Keystone v3"

This commit is contained in:
Jenkins 2014-08-07 23:49:53 +00:00 committed by Gerrit Code Review
commit 558ebf7bb7
3 changed files with 711 additions and 169 deletions

View File

@ -27,53 +27,32 @@ import os
from os.path import expanduser
import sys
from keystoneclient.v2_0 import client as ksclient
import netaddr
import six.moves.urllib.parse as urlparse
import glanceclient
from glanceclient.common import utils
from glanceclient import exc
from glanceclient.openstack.common import strutils
from keystoneclient.auth.identity import v2 as v2_auth
from keystoneclient.auth.identity import v3 as v3_auth
from keystoneclient import discover
from keystoneclient.openstack.common.apiclient import exceptions as ks_exc
from keystoneclient import session
class OpenStackImagesShell(object):
def get_base_parser(self):
parser = argparse.ArgumentParser(
prog='glance',
description=__doc__.strip(),
epilog='See "glance 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=glanceclient.__version__)
parser.add_argument('-d', '--debug',
default=bool(utils.env('GLANCECLIENT_DEBUG')),
action='store_true',
help='Defaults to env[GLANCECLIENT_DEBUG].')
parser.add_argument('-v', '--verbose',
default=False, action="store_true",
help="Print more verbose output")
parser.add_argument('--get-schema',
default=False, action="store_true",
dest='get_schema',
help='Ignores cached copy and forces retrieval '
'of schema that generates portions of the '
'help text. Ignored with API version 1.')
def _append_global_identity_args(self, parser):
# FIXME(bobt): 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. See
#
# https://bugs.launchpad.net/python-keystoneclient/+bug/1332337
#
parser.add_argument('-k', '--insecure',
default=False,
action='store_true',
@ -83,16 +62,24 @@ class OpenStackImagesShell(object):
'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.')
parser.add_argument('--key-file',
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',
@ -106,51 +93,6 @@ class OpenStackImagesShell(object):
dest='os_cacert',
help='DEPRECATED! Use --os-cacert.')
parser.add_argument('--timeout',
default=600,
help='Number of seconds to wait for a response')
parser.add_argument('--no-ssl-compression',
dest='ssl_compression',
default=True, action='store_false',
help='Disable SSL compression when using https.')
parser.add_argument('-f', '--force',
dest='force',
default=False, action='store_true',
help='Prevent select actions from requesting '
'user confirmation.')
#NOTE(bcwaldon): DEPRECATED
parser.add_argument('--dry-run',
default=False,
action='store_true',
help='DEPRECATED! Only used for deprecated '
'legacy commands.')
#NOTE(bcwaldon): DEPRECATED
parser.add_argument('--ssl',
dest='use_ssl',
default=False,
action='store_true',
help='DEPRECATED! Send a fully-formed endpoint '
'using --os-image-url instead.')
#NOTE(bcwaldon): DEPRECATED
parser.add_argument('-H', '--host',
metavar='ADDRESS',
help='DEPRECATED! Send a fully-formed endpoint '
'using --os-image-url instead.')
#NOTE(bcwaldon): DEPRECATED
parser.add_argument('-p', '--port',
dest='port',
metavar='PORT',
type=int,
default=9292,
help='DEPRECATED! Send a fully-formed endpoint '
'using --os-image-url instead.')
parser.add_argument('--os-username',
default=utils.env('OS_USERNAME'),
help='Defaults to env[OS_USERNAME].')
@ -158,6 +100,40 @@ class OpenStackImagesShell(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-domain-id',
default=utils.env('OS_USER_DOMAIN_ID'),
help='Defaults to env[OS_USER_DOMAIN_ID].')
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-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-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-domain-id',
default=utils.env('OS_PROJECT_DOMAIN_ID'),
help='Defaults to env[OS_PROJECT_DOMAIN_ID].')
parser.add_argument('--os-project-domain-name',
default=utils.env('OS_PROJECT_DOMAIN_NAME'),
help='Defaults to env[OS_PROJECT_DOMAIN_NAME].')
#NOTE(bcwaldon): DEPRECATED
parser.add_argument('-I',
dest='os_username',
@ -230,6 +206,101 @@ class OpenStackImagesShell(object):
dest='os_auth_token',
help='DEPRECATED! Use --os-auth-token.')
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='glance',
description=__doc__.strip(),
epilog='See "glance 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=glanceclient.__version__)
parser.add_argument('-d', '--debug',
default=bool(utils.env('GLANCECLIENT_DEBUG')),
action='store_true',
help='Defaults to env[GLANCECLIENT_DEBUG].')
parser.add_argument('-v', '--verbose',
default=False, action="store_true",
help="Print more verbose output")
parser.add_argument('--get-schema',
default=False, action="store_true",
dest='get_schema',
help='Ignores cached copy and forces retrieval '
'of schema that generates portions of the '
'help text. Ignored with API version 1.')
parser.add_argument('--timeout',
default=600,
help='Number of seconds to wait for a response')
parser.add_argument('--no-ssl-compression',
dest='ssl_compression',
default=True, action='store_false',
help='Disable SSL compression when using https.')
parser.add_argument('-f', '--force',
dest='force',
default=False, action='store_true',
help='Prevent select actions from requesting '
'user confirmation.')
#NOTE(bcwaldon): DEPRECATED
parser.add_argument('--dry-run',
default=False,
action='store_true',
help='DEPRECATED! Only used for deprecated '
'legacy commands.')
#NOTE(bcwaldon): DEPRECATED
parser.add_argument('--ssl',
dest='use_ssl',
default=False,
action='store_true',
help='DEPRECATED! Send a fully-formed endpoint '
'using --os-image-url instead.')
#NOTE(bcwaldon): DEPRECATED
parser.add_argument('-H', '--host',
metavar='ADDRESS',
help='DEPRECATED! Send a fully-formed endpoint '
'using --os-image-url instead.')
#NOTE(bcwaldon): DEPRECATED
parser.add_argument('-p', '--port',
dest='port',
metavar='PORT',
type=int,
default=9292,
help='DEPRECATED! Send a fully-formed endpoint '
'using --os-image-url instead.')
parser.add_argument('--os-image-url',
default=utils.env('OS_IMAGE_URL'),
help='Defaults to env[OS_IMAGE_URL].')
@ -250,25 +321,14 @@ class OpenStackImagesShell(object):
parser.add_argument('--os_image_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)
#NOTE(bcwaldon): DEPRECATED
parser.add_argument('-S', '--os_auth_strategy',
help='DEPRECATED! This option is '
'completely ignored.')
# FIXME(bobt): this method should come from python-keystoneclient
self._append_global_identity_args(parser)
return parser
def get_subcommand_parser(self, version):
@ -306,36 +366,6 @@ class OpenStackImagesShell(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
"""
return ksclient.Client(username=kwargs.get('username'),
password=kwargs.get('password'),
tenant_id=kwargs.get('tenant_id'),
tenant_name=kwargs.get('tenant_name'),
auth_url=kwargs.get('auth_url'),
cacert=kwargs.get('cacert'),
insecure=kwargs.get('insecure'))
def _get_endpoint(self, client, **kwargs):
"""Get an endpoint using the provided keystone client."""
endpoint_kwargs = {
'service_type': kwargs.get('service_type') or 'image',
'endpoint_type': kwargs.get('endpoint_type') or 'publicURL',
}
if kwargs.get('region_name'):
endpoint_kwargs['attr'] = 'region'
endpoint_kwargs['filter_value'] = kwargs.get('region_name')
return client.service_catalog.url_for(**endpoint_kwargs)
def _get_image_url(self, args):
"""Translate the available url-related options into a single string.
@ -353,6 +383,101 @@ class OpenStackImagesShell(object):
else:
return None
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 as e:
# 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. error=%s') % (e)
raise exc.CommandError(msg)
return (v2_auth_url, v3_auth_url)
def _get_keystone_session(self, **kwargs):
ks_session = session.Session.construct(kwargs)
# discover the supported keystone versions using the given auth url
auth_url = kwargs.pop('auth_url', None)
(v2_auth_url, v3_auth_url) = self._discover_auth_versions(
session=ks_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.
user_id = kwargs.pop('user_id', None)
username = kwargs.pop('username', None)
password = kwargs.pop('password', None)
user_domain_name = kwargs.pop('user_domain_name', None)
user_domain_id = kwargs.pop('user_domain_id', None)
# project and tenant can be used interchangeably
project_id = (kwargs.pop('project_id', None) or
kwargs.pop('tenant_id', None))
project_name = (kwargs.pop('project_name', None) or
kwargs.pop('tenant_name', None))
project_domain_id = kwargs.pop('project_domain_id', None)
project_domain_name = kwargs.pop('project_domain_name', None)
auth = None
use_domain = (user_domain_id or
user_domain_name or
project_domain_id or
project_domain_name)
use_v3 = v3_auth_url and (use_domain or (not v2_auth_url))
use_v2 = v2_auth_url and not use_domain
if use_v3:
auth = v3_auth.Password(
v3_auth_url,
user_id=user_id,
username=username,
password=password,
user_domain_id=user_domain_id,
user_domain_name=user_domain_name,
project_id=project_id,
project_name=project_name,
project_domain_id=project_domain_id,
project_domain_name=project_domain_name)
elif use_v2:
auth = v2_auth.Password(
v2_auth_url,
username,
password,
tenant_id=project_id,
tenant_name=project_name)
else:
# if we get here it means domain information is provided
# (caller meant to use Keystone V3) but the auth url is
# actually Keystone V2. Obviously we can't authenticate a V3
# user using V2.
exc.CommandError("Credential and auth_url mismatch. The given "
"auth_url is using Keystone V2 endpoint, which "
"may not able to handle Keystone V3 credentials. "
"Please provide a correct Keystone V3 auth_url.")
ks_session.auth = auth
return ks_session
def _get_endpoint_and_token(self, args, force_auth=False):
image_url = self._get_image_url(args)
auth_token = args.os_auth_token
@ -364,42 +489,74 @@ class OpenStackImagesShell(object):
endpoint = image_url
token = args.os_auth_token
else:
if not args.os_username:
raise exc.CommandError("You must provide a username via"
" either --os-username or "
"env[OS_USERNAME]")
raise exc.CommandError(
_("You must provide a username via"
" either --os-username or "
"env[OS_USERNAME]"))
if not args.os_password:
raise exc.CommandError("You must provide a password via"
" either --os-password or "
"env[OS_PASSWORD]")
raise exc.CommandError(
_("You must provide a password via"
" either --os-password or "
"env[OS_PASSWORD]"))
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]")
# Validate password flow auth
project_info = (args.os_tenant_name or
args.os_tenant_id or
(args.os_project_name and
(args.project_domain_name or
args.project_domain_id)) or
args.os_project_id)
if (not project_info):
# tenent is deprecated in Keystone v3. Use the latest
# terminology instead.
raise exc.CommandError(
_("You must provide a project_id or project_name ("
"with project_domain_name or project_domain_id) "
"via "
" --os-project-id (env[OS_PROJECT_ID])"
" --os-project-name (env[OS_PROJECT_NAME]),"
" --os-project-domain-id "
"(env[OS_PROJECT_DOMAIN_ID])"
" --os-project-domain-name "
"(env[OS_PROJECT_DOMAIN_NAME])"))
if not args.os_auth_url:
raise exc.CommandError("You must provide an auth url via"
" either --os-auth-url or "
"via env[OS_AUTH_URL]")
kwargs = {
'username': args.os_username,
'password': args.os_password,
'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,
'cacert': args.os_cacert,
'insecure': args.insecure,
'region_name': args.os_region_name,
}
_ksclient = self._get_ksclient(**kwargs)
token = args.os_auth_token or _ksclient.auth_token
raise exc.CommandError(
_("You must provide an auth url via"
" either --os-auth-url or "
"via env[OS_AUTH_URL]"))
endpoint = args.os_image_url or self._get_endpoint(_ksclient,
**kwargs)
kwargs = {
'auth_url': args.os_auth_url,
'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,
'tenant_name': args.os_tenant_name,
'tenant_id': args.os_tenant_id,
'project_name': args.os_project_name,
'project_id': args.os_project_id,
'project_domain_name': args.os_project_domain_name,
'project_domain_id': args.os_project_domain_id,
'insecure': args.insecure,
'cacert': args.os_cacert,
'cert': args.os_cert,
'key': args.os_key
}
ks_session = self._get_keystone_session(**kwargs)
token = args.os_auth_token or ks_session.get_token()
endpoint_type = args.os_endpoint_type or 'public'
service_type = args.os_service_type or 'image'
endpoint = args.os_image_url or ks_session.get_endpoint(
service_type=service_type,
endpoint_type=endpoint_type,
region_name=args.os_region_name)
return endpoint, token
@ -412,8 +569,8 @@ class OpenStackImagesShell(object):
'insecure': args.insecure,
'timeout': args.timeout,
'cacert': args.os_cacert,
'cert_file': args.cert_file,
'key_file': args.key_file,
'cert': args.os_cert,
'key': args.os_key,
'ssl_compression': args.ssl_compression
}
client = glanceclient.Client(api_version, endpoint, **kwargs)

View File

@ -0,0 +1,188 @@
# 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 copy
import json
import uuid
# 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}
def _create_version_list(versions):
return json.dumps({'versions': {'values': versions}})
def _create_single_version(version):
return json.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)
GLANCE_ENDPOINT = 'http://glance.example.com/v1'
def _get_normalized_token_data(**kwargs):
ref = copy.deepcopy(kwargs)
# normalized token data
ref['user_id'] = ref.get('user_id', uuid.uuid4().hex)
ref['username'] = ref.get('username', uuid.uuid4().hex)
ref['project_id'] = ref.get('project_id',
ref.get('tenant_id', uuid.uuid4().hex))
ref['project_name'] = ref.get('tenant_name',
ref.get('tenant_name', uuid.uuid4().hex))
ref['user_domain_id'] = ref.get('user_domain_id', uuid.uuid4().hex)
ref['user_domain_name'] = ref.get('user_domain_name', uuid.uuid4().hex)
ref['project_domain_id'] = ref.get('project_domain_id', uuid.uuid4().hex)
ref['project_domain_name'] = ref.get('project_domain_name',
uuid.uuid4().hex)
ref['roles'] = ref.get('roles', [{'name': uuid.uuid4().hex,
'id': uuid.uuid4().hex}])
ref['roles_link'] = ref.get('roles_link', [])
ref['glance_url'] = ref.get('glance_url', GLANCE_ENDPOINT)
return ref
def generate_v2_project_scoped_token(**kwargs):
"""Generate a Keystone V2 token based on auth request."""
ref = _get_normalized_token_data(**kwargs)
o = {'access': {'token': {'id': uuid.uuid4().hex,
'expires': '2099-05-22T00:02:43.941430Z',
'issued_at': '2013-05-21T00:02:43.941473Z',
'tenant': {'enabled': True,
'id': ref.get('project_id'),
'name': ref.get('project_id')
}
},
'user': {'id': ref.get('user_id'),
'name': uuid.uuid4().hex,
'username': ref.get('username'),
'roles': ref.get('roles'),
'roles_links': ref.get('roles_links')
}
}}
# we only care about Glance and Keystone endpoints
o['access']['serviceCatalog'] = [
{'endpoints': [
{'publicURL': ref.get('glance_url'),
'id': uuid.uuid4().hex,
'region': 'RegionOne'
}],
'endpoints_links': [],
'name': 'Glance',
'type': 'keystore'},
{'endpoints': [
{'publicURL': ref.get('auth_url'),
'adminURL': ref.get('auth_url'),
'id': uuid.uuid4().hex,
'region': 'RegionOne'
}],
'endpoint_links': [],
'name': 'keystone',
'type': 'identity'}]
return o
def generate_v3_project_scoped_token(**kwargs):
"""Generate a Keystone V3 token based on auth request."""
ref = _get_normalized_token_data(**kwargs)
o = {'token': {'expires_at': '2099-05-22T00:02:43.941430Z',
'issued_at': '2013-05-21T00:02:43.941473Z',
'methods': ['password'],
'project': {'id': ref.get('project_id'),
'name': ref.get('project_name'),
'domain': {'id': ref.get('project_domain_id'),
'name': ref.get(
'project_domain_name')
}
},
'user': {'id': ref.get('user_id'),
'name': ref.get('username'),
'domain': {'id': ref.get('user_domain_id'),
'name': ref.get('user_domain_name')
}
},
'roles': ref.get('roles')
}}
# we only care about Glance and Keystone endpoints
o['token']['catalog'] = [
{'endpoints': [
{
'id': uuid.uuid4().hex,
'interface': 'public',
'region': 'RegionTwo',
'url': ref.get('glance_url')
}],
'id': uuid.uuid4().hex,
'type': 'keystore'},
{'endpoints': [
{
'id': uuid.uuid4().hex,
'interface': 'public',
'region': 'RegionTwo',
'url': ref.get('auth_url')
},
{
'id': uuid.uuid4().hex,
'interface': 'admin',
'region': 'RegionTwo',
'url': ref.get('auth_url')
}],
'id': uuid.uuid4().hex,
'type': 'identity'}]
# token ID is conveyed via the X-Subject-Token header so we are generating
# one to stash there
token_id = uuid.uuid4().hex
return token_id, o

View File

@ -25,31 +25,50 @@ from glanceclient import shell as openstack_shell
#NOTE (esheffield) Used for the schema caching tests
from glanceclient.v2 import schemas as schemas
import json
from tests import keystone_client_fixtures
from tests import utils
import keystoneclient
from keystoneclient.openstack.common.apiclient import exceptions as ks_exc
DEFAULT_IMAGE_URL = 'http://127.0.0.1:5000/'
DEFAULT_USERNAME = 'username'
DEFAULT_PASSWORD = 'password'
DEFAULT_TENANT_ID = 'tenant_id'
DEFAULT_TENANT_NAME = 'tenant_name'
DEFAULT_AUTH_URL = 'http://127.0.0.1:5000/v2.0/'
DEFAULT_PROJECT_ID = '0123456789'
DEFAULT_USER_DOMAIN_NAME = 'user_domain_name'
DEFAULT_UNVERSIONED_AUTH_URL = 'http://127.0.0.1:5000/'
DEFAULT_V2_AUTH_URL = 'http://127.0.0.1:5000/v2.0/'
DEFAULT_V3_AUTH_URL = 'http://127.0.0.1:5000/v3/'
DEFAULT_AUTH_TOKEN = ' 3bcc3d3a03f44e3d8377f9247b0ad155'
TEST_SERVICE_URL = 'http://127.0.0.1:5000/'
FAKE_V2_ENV = {'OS_USERNAME': DEFAULT_USERNAME,
'OS_PASSWORD': DEFAULT_PASSWORD,
'OS_TENANT_NAME': DEFAULT_TENANT_NAME,
'OS_AUTH_URL': DEFAULT_V2_AUTH_URL,
'OS_IMAGE_URL': DEFAULT_IMAGE_URL}
FAKE_V3_ENV = {'OS_USERNAME': DEFAULT_USERNAME,
'OS_PASSWORD': DEFAULT_PASSWORD,
'OS_PROJECT_ID': DEFAULT_PROJECT_ID,
'OS_USER_DOMAIN_NAME': DEFAULT_USER_DOMAIN_NAME,
'OS_AUTH_URL': DEFAULT_V3_AUTH_URL,
'OS_IMAGE_URL': DEFAULT_IMAGE_URL}
class ShellTest(utils.TestCase):
# auth environment to use
auth_env = FAKE_V2_ENV.copy()
# expected auth plugin to invoke
auth_plugin = 'keystoneclient.auth.identity.v2.Password'
def setUp(self):
super(ShellTest, self).setUp()
global _old_env
fake_env = {
'OS_USERNAME': DEFAULT_USERNAME,
'OS_PASSWORD': DEFAULT_PASSWORD,
'OS_TENANT_NAME': DEFAULT_TENANT_NAME,
'OS_AUTH_URL': DEFAULT_AUTH_URL,
'OS_IMAGE_URL': DEFAULT_IMAGE_URL,
'OS_AUTH_TOKEN': DEFAULT_AUTH_TOKEN}
_old_env, os.environ = os.environ, fake_env.copy()
_old_env, os.environ = os.environ, self.auth_env
global shell, _shell, assert_called, assert_called_anytime
_shell = openstack_shell.OpenStackImagesShell()
@ -99,6 +118,184 @@ class ShellTest(utils.TestCase):
targeted_image_url = test_shell._get_image_url(fake_args)
self.assertEqual(expected_image_url, targeted_image_url)
@mock.patch.object(openstack_shell.OpenStackImagesShell,
'_get_versioned_client')
def test_cert_and_key_args_interchangeable(self,
mock_versioned_client):
# make sure --os-cert and --os-key are passed correctly
args = '--os-cert mycert --os-key mykey image-list'
shell(args)
assert mock_versioned_client.called
((api_version, args), kwargs) = mock_versioned_client.call_args
self.assertEqual(args.os_cert, 'mycert')
self.assertEqual(args.os_key, 'mykey')
# make sure we get the same thing with --cert-file and --key-file
args = '--cert-file mycertfile --key-file mykeyfile image-list'
glance_shell = openstack_shell.OpenStackImagesShell()
glance_shell.main(args.split())
assert mock_versioned_client.called
((api_version, args), kwargs) = mock_versioned_client.call_args
self.assertEqual(args.os_cert, 'mycertfile')
self.assertEqual(args.os_key, 'mykeyfile')
@mock.patch('glanceclient.v1.client.Client')
def test_no_auth_with_token_and_image_url_with_v1(self, v1_client):
# test no authentication is required if both token and endpoint url
# are specified
args = ('--os-auth-token mytoken --os-image-url https://image:1234/v1 '
'image-list')
glance_shell = openstack_shell.OpenStackImagesShell()
glance_shell.main(args.split())
assert v1_client.called
(args, kwargs) = v1_client.call_args
self.assertEqual(kwargs['token'], 'mytoken')
self.assertEqual(args[0], 'https://image:1234/v1')
@mock.patch.object(openstack_shell.OpenStackImagesShell, '_cache_schema')
def test_no_auth_with_token_and_image_url_with_v2(self,
cache_schema):
with mock.patch('glanceclient.v2.client.Client') as v2_client:
# test no authentication is required if both token and endpoint url
# are specified
args = ('--os-auth-token mytoken '
'--os-image-url https://image:1234/v2 '
'--os-image-api-version 2 image-list')
glance_shell = openstack_shell.OpenStackImagesShell()
glance_shell.main(args.split())
((args), kwargs) = v2_client.call_args
self.assertEqual(args[0], 'https://image:1234/v2')
self.assertEqual(kwargs['token'], 'mytoken')
def _assert_auth_plugin_args(self, mock_auth_plugin):
# make sure our auth plugin is invoked with the correct args
mock_auth_plugin.assert_called_once_with(
keystone_client_fixtures.V2_URL,
self.auth_env['OS_USERNAME'],
self.auth_env['OS_PASSWORD'],
tenant_name=self.auth_env['OS_TENANT_NAME'],
tenant_id='')
@mock.patch('glanceclient.v1.client.Client')
@mock.patch('keystoneclient.session.Session')
@mock.patch.object(keystoneclient.discover.Discover, 'url_for',
side_effect=[keystone_client_fixtures.V2_URL, None])
def test_auth_plugin_invocation_with_v1(self,
v1_client,
ks_session,
url_for):
with mock.patch(self.auth_plugin) as mock_auth_plugin:
args = 'image-list'
glance_shell = openstack_shell.OpenStackImagesShell()
glance_shell.main(args.split())
self._assert_auth_plugin_args(mock_auth_plugin)
@mock.patch('glanceclient.v2.client.Client')
@mock.patch('keystoneclient.session.Session')
@mock.patch.object(openstack_shell.OpenStackImagesShell, '_cache_schema')
@mock.patch.object(keystoneclient.discover.Discover, 'url_for',
side_effect=[keystone_client_fixtures.V2_URL, None])
def test_auth_plugin_invocation_with_v2(self,
v2_client,
ks_session,
url_for,
cache_schema):
with mock.patch(self.auth_plugin) as mock_auth_plugin:
args = '--os-image-api-version 2 image-list'
glance_shell = openstack_shell.OpenStackImagesShell()
glance_shell.main(args.split())
self._assert_auth_plugin_args(mock_auth_plugin)
@mock.patch('glanceclient.v1.client.Client')
@mock.patch('keystoneclient.session.Session')
@mock.patch.object(keystoneclient.discover.Discover, 'url_for',
side_effect=[keystone_client_fixtures.V2_URL,
keystone_client_fixtures.V3_URL])
def test_auth_plugin_invocation_with_unversioned_auth_url_with_v1(
self, v1_client, ks_session, url_for):
with mock.patch(self.auth_plugin) as mock_auth_plugin:
args = '--os-auth-url %s image-list' % (
keystone_client_fixtures.BASE_URL)
glance_shell = openstack_shell.OpenStackImagesShell()
glance_shell.main(args.split())
self._assert_auth_plugin_args(mock_auth_plugin)
@mock.patch('glanceclient.v2.client.Client')
@mock.patch('keystoneclient.session.Session')
@mock.patch.object(openstack_shell.OpenStackImagesShell, '_cache_schema')
@mock.patch.object(keystoneclient.discover.Discover, 'url_for',
side_effect=[keystone_client_fixtures.V2_URL,
keystone_client_fixtures.V3_URL])
def test_auth_plugin_invocation_with_unversioned_auth_url_with_v2(
self, v2_client, ks_session, cache_schema, url_for):
with mock.patch(self.auth_plugin) as mock_auth_plugin:
args = ('--os-auth-url %s --os-image-api-version 2 '
'image-list') % (keystone_client_fixtures.BASE_URL)
glance_shell = openstack_shell.OpenStackImagesShell()
glance_shell.main(args.split())
self._assert_auth_plugin_args(mock_auth_plugin)
class ShellTestWithKeystoneV3Auth(ShellTest):
# auth environment to use
auth_env = FAKE_V3_ENV.copy()
# expected auth plugin to invoke
auth_plugin = 'keystoneclient.auth.identity.v3.Password'
def _assert_auth_plugin_args(self, mock_auth_plugin):
mock_auth_plugin.assert_called_once_with(
keystone_client_fixtures.V3_URL,
user_id='',
username=self.auth_env['OS_USERNAME'],
password=self.auth_env['OS_PASSWORD'],
user_domain_id='',
user_domain_name=self.auth_env['OS_USER_DOMAIN_NAME'],
project_id=self.auth_env['OS_PROJECT_ID'],
project_name='',
project_domain_id='',
project_domain_name='')
@mock.patch('glanceclient.v1.client.Client')
@mock.patch('keystoneclient.session.Session')
@mock.patch.object(keystoneclient.discover.Discover, 'url_for',
side_effect=[None, keystone_client_fixtures.V3_URL])
def test_auth_plugin_invocation_with_v1(self,
v1_client,
ks_session,
url_for):
with mock.patch(self.auth_plugin) as mock_auth_plugin:
args = 'image-list'
glance_shell = openstack_shell.OpenStackImagesShell()
glance_shell.main(args.split())
self._assert_auth_plugin_args(mock_auth_plugin)
@mock.patch('glanceclient.v2.client.Client')
@mock.patch('keystoneclient.session.Session')
@mock.patch.object(openstack_shell.OpenStackImagesShell, '_cache_schema')
@mock.patch.object(keystoneclient.discover.Discover, 'url_for',
side_effect=[None, keystone_client_fixtures.V3_URL])
def test_auth_plugin_invocation_with_v2(self,
v2_client,
ks_session,
url_for,
cache_schema):
with mock.patch(self.auth_plugin) as mock_auth_plugin:
args = '--os-image-api-version 2 image-list'
glance_shell = openstack_shell.OpenStackImagesShell()
glance_shell.main(args.split())
self._assert_auth_plugin_args(mock_auth_plugin)
@mock.patch('keystoneclient.session.Session')
@mock.patch('keystoneclient.discover.Discover',
side_effect=ks_exc.ClientException())
def test_api_discovery_failed_with_unversioned_auth_url(self,
ks_session,
discover):
args = '--os-auth-url %s image-list' % (
keystone_client_fixtures.BASE_URL)
glance_shell = openstack_shell.OpenStackImagesShell()
self.assertRaises(exc.CommandError, glance_shell.main, args.split())
class ShellCacheSchemaTest(utils.TestCase):
def setUp(self):