Use os-client-config in shell

Use os-client-config[1] to process options and get the Session from
keystoneauth1. This adds support for reading clouds.yaml
files and supporting the OS_CLOUD env var for selecting named clouds
from a list of them.

[1]: https://github.com/openstack/os-client-config

Closes-Bug: #1599747
Change-Id: I23a6e80648e67c0b652693cd146bd9e94ad4fb23
This commit is contained in:
OTSUKA, Yuanying
2016-07-14 14:45:18 +09:00
parent d24e1460b8
commit f2c49d203b
7 changed files with 501 additions and 625 deletions

View File

@@ -1,238 +0,0 @@
# Copyright 2013 OpenStack Foundation
# Copyright 2013 Spanish National Research Council.
# All Rights Reserved.
#
# 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.
# E0202: An attribute inherited from %s hide this method
# pylint: disable=E0202
########################################################################
#
# THIS MODULE IS DEPRECATED
#
# Please refer to
# https://etherpad.openstack.org/p/kilo-magnumclient-library-proposals for
# the discussion leading to this deprecation.
#
# We recommend checking out the python-openstacksdk project
# (https://launchpad.net/python-openstacksdk) instead.
#
########################################################################
import abc
import argparse
import os
import six
from stevedore import extension
from magnumclient.common.apiclient import exceptions
_discovered_plugins = {}
def discover_auth_systems():
"""Discover the available auth-systems.
This won't take into account the old style auth-systems.
"""
global _discovered_plugins
_discovered_plugins = {}
def add_plugin(ext):
_discovered_plugins[ext.name] = ext.plugin
ep_namespace = "magnumclient.common.apiclient.auth"
mgr = extension.ExtensionManager(ep_namespace)
mgr.map(add_plugin)
def load_auth_system_opts(parser):
"""Load options needed by the available auth-systems into a parser.
This function will try to populate the parser with options from the
available plugins.
"""
group = parser.add_argument_group("Common auth options")
BaseAuthPlugin.add_common_opts(group)
for name, auth_plugin in _discovered_plugins.items():
group = parser.add_argument_group(
"Auth-system '%s' options" % name,
conflict_handler="resolve")
auth_plugin.add_opts(group)
def load_plugin(auth_system):
try:
plugin_class = _discovered_plugins[auth_system]
except KeyError:
raise exceptions.AuthSystemNotFound(auth_system)
return plugin_class(auth_system=auth_system)
def load_plugin_from_args(args):
"""Load required plugin and populate it with options.
Try to guess auth system if it is not specified. Systems are tried in
alphabetical order.
:type args: argparse.Namespace
:raises: AuthPluginOptionsMissing
"""
auth_system = args.os_auth_system
if auth_system:
plugin = load_plugin(auth_system)
plugin.parse_opts(args)
plugin.sufficient_options()
return plugin
for plugin_auth_system in sorted(six.iterkeys(_discovered_plugins)):
plugin_class = _discovered_plugins[plugin_auth_system]
plugin = plugin_class()
plugin.parse_opts(args)
try:
plugin.sufficient_options()
except exceptions.AuthPluginOptionsMissing:
continue
return plugin
raise exceptions.AuthPluginOptionsMissing(["auth_system"])
@six.add_metaclass(abc.ABCMeta)
class BaseAuthPlugin(object):
"""Base class for authentication plugins.
An authentication plugin needs to override at least the authenticate
method to be a valid plugin.
"""
auth_system = None
opt_names = []
common_opt_names = [
"auth_system",
"username",
"password",
"tenant_id",
"tenant_name",
"project_id",
"project_name",
"user_domain_id",
"user_domain_name",
"project_domain_id",
"project_domain_name",
"token",
"auth_url",
]
def __init__(self, auth_system=None, **kwargs):
self.auth_system = auth_system or self.auth_system
self.opts = dict((name, kwargs.get(name))
for name in self.opt_names)
@staticmethod
def _parser_add_opt(parser, opt):
"""Add an option to parser in two variants.
:param opt: option name (with underscores)
"""
dashed_opt = opt.replace("_", "-")
env_var = "OS_%s" % opt.upper()
arg_default = os.environ.get(env_var, "")
arg_help = "Defaults to env[%s]." % env_var
parser.add_argument(
"--os-%s" % dashed_opt,
metavar="<%s>" % dashed_opt,
default=arg_default,
help=arg_help)
parser.add_argument(
"--os_%s" % opt,
metavar="<%s>" % dashed_opt,
help=argparse.SUPPRESS)
@classmethod
def add_opts(cls, parser):
"""Populate the parser with the options for this plugin."""
for opt in cls.opt_names:
# use `BaseAuthPlugin.common_opt_names` since it is never
# changed in child classes
if opt not in BaseAuthPlugin.common_opt_names:
cls._parser_add_opt(parser, opt)
@classmethod
def add_common_opts(cls, parser):
"""Add options that are common for several plugins."""
for opt in cls.common_opt_names:
cls._parser_add_opt(parser, opt)
@staticmethod
def get_opt(opt_name, args):
"""Return option name and value.
:param opt_name: name of the option, e.g., "username"
:param args: parsed arguments
"""
return (opt_name, getattr(args, "os_%s" % opt_name, None))
def parse_opts(self, args):
"""Parse the actual auth-system options if any.
This method is expected to populate the attribute `self.opts` with a
dict containing the options and values needed to make authentication.
"""
self.opts.update(dict(self.get_opt(opt_name, args)
for opt_name in self.opt_names))
def authenticate(self, http_client):
"""Authenticate using plugin defined method.
The method usually analyses `self.opts` and performs
a request to authentication server.
:param http_client: client object that needs authentication
:type http_client: HTTPClient
:raises: AuthorizationFailure
"""
self.sufficient_options()
self._do_authenticate(http_client)
@abc.abstractmethod
def _do_authenticate(self, http_client):
"""Protected method for authentication."""
def sufficient_options(self):
"""Check if all required options are present.
:raises: AuthPluginOptionsMissing
"""
missing = [opt
for opt in self.opt_names
if not self.opts.get(opt)]
if missing:
raise exceptions.AuthPluginOptionsMissing(missing)
@abc.abstractmethod
def token_and_endpoint(self, endpoint_type, service_type):
"""Return token and endpoint.
:param service_type: Service type of the endpoint
:type service_type: string
:param endpoint_type: Type of endpoint.
Possible values: public or publicURL,
internal or internalURL,
admin or adminURL
:type endpoint_type: string
:returns: tuple of token and endpoint strings
:raises: EndpointException
"""

View File

@@ -44,6 +44,10 @@ def _extract_error_json(body):
if 'error_message' in body_json: if 'error_message' in body_json:
raw_msg = body_json['error_message'] raw_msg = body_json['error_message']
error_json = json.loads(raw_msg) error_json = json.loads(raw_msg)
elif 'error' in body_json:
error_body = body_json['error']
error_json = {'faultstring': error_body['title'],
'debuginfo': error_body['message']}
else: else:
error_body = body_json['errors'][0] error_body = body_json['errors'][0]
raw_msg = error_body['title'] raw_msg = error_body['title']

View File

@@ -49,7 +49,6 @@ try:
except ImportError: except ImportError:
pass pass
from magnumclient.common.apiclient import auth
from magnumclient.common import cliutils from magnumclient.common import cliutils
from magnumclient import exceptions as exc from magnumclient import exceptions as exc
from magnumclient.v1 import client as client_v1 from magnumclient.v1 import client as client_v1
@@ -57,7 +56,7 @@ from magnumclient.v1 import shell as shell_v1
from magnumclient import version from magnumclient import version
DEFAULT_API_VERSION = '1' DEFAULT_API_VERSION = '1'
DEFAULT_ENDPOINT_TYPE = 'publicURL' DEFAULT_INTERFACE = 'public'
DEFAULT_SERVICE_TYPE = 'container-infra' DEFAULT_SERVICE_TYPE = 'container-infra'
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -271,20 +270,111 @@ class OpenStackMagnumShell(object):
# type=positive_non_zero_float, # type=positive_non_zero_float,
# help="Set HTTP call timeout (in seconds)") # help="Set HTTP call timeout (in seconds)")
parser.add_argument('--os-auth-url',
metavar='<auth-auth-url>',
default=cliutils.env('OS_AUTH_URL', default=None),
help='Defaults to env[OS_AUTH_URL].')
parser.add_argument('--os-user-id',
metavar='<auth-user-id>',
default=cliutils.env('OS_USER_ID', default=None),
help='Defaults to env[OS_USER_ID].')
parser.add_argument('--os-username',
metavar='<auth-username>',
default=cliutils.env('OS_USERNAME', default=None),
help='Defaults to env[OS_USERNAME].')
parser.add_argument('--os-user-domain-id',
metavar='<auth-user-domain-id>',
default=cliutils.env('OS_USER_DOMAIN_ID',
default=None),
help='Defaults to env[OS_USER_DOMAIN_ID].')
parser.add_argument('--os-user-domain-name',
metavar='<auth-user-domain-name>',
default=cliutils.env('OS_USER_DOMAIN_NAME',
default=None),
help='Defaults to env[OS_USER_DOMAIN_NAME].')
parser.add_argument('--os-project-id',
metavar='<auth-project-id>',
default=cliutils.env('OS_PROJECT_ID',
default=None),
help='Defaults to env[OS_PROJECT_ID].')
parser.add_argument('--os-project-name',
metavar='<auth-project-name>',
default=cliutils.env('OS_PROJECT_NAME',
default=None),
help='Defaults to env[OS_PROJECT_NAME].')
parser.add_argument('--os-tenant-id',
metavar='<auth-tenant-id>',
default=cliutils.env('OS_TENANT_ID',
default=None),
help=argparse.SUPPRESS)
parser.add_argument('--os-tenant-name',
metavar='<auth-tenant-name>',
default=cliutils.env('OS_TENANT_NAME',
default=None),
help=argparse.SUPPRESS)
parser.add_argument('--os-project-domain-id',
metavar='<auth-project-domain-id>',
default=cliutils.env('OS_PROJECT_DOMAIN_ID',
default=None),
help='Defaults to env[OS_PROJECT_DOMAIN_ID].')
parser.add_argument('--os-project-domain-name',
metavar='<auth-project-domain-name>',
default=cliutils.env('OS_PROJECT_DOMAIN_NAME',
default=None),
help='Defaults to env[OS_PROJECT_DOMAIN_NAME].')
parser.add_argument('--os-token',
metavar='<auth-token>',
default=cliutils.env('OS_TOKEN', default=None),
help='Defaults to env[OS_TOKEN].')
parser.add_argument('--os-password',
metavar='<auth-password>',
default=cliutils.env('OS_PASSWORD',
default=None),
help='Defaults to env[OS_PASSWORD].')
parser.add_argument('--service-type', parser.add_argument('--service-type',
metavar='<service-type>', metavar='<service-type>',
help='Defaults to container for all ' help='Defaults to container-infra for all '
'actions.') 'actions.')
parser.add_argument('--service_type', parser.add_argument('--service_type',
help=argparse.SUPPRESS) help=argparse.SUPPRESS)
parser.add_argument('--endpoint-type', parser.add_argument('--endpoint-type',
metavar='<endpoint-type>', metavar='<endpoint-type>',
default=cliutils.env('OS_ENDPOINT_TYPE',
default=None),
help=argparse.SUPPRESS)
parser.add_argument('--os-endpoint-type',
metavar='<os-endpoint-type>',
default=cliutils.env('OS_ENDPOINT_TYPE',
default=None),
help='Defaults to env[OS_ENDPOINT_TYPE]')
parser.add_argument('--os-interface',
metavar='<os-interface>',
default=cliutils.env( default=cliutils.env(
'OS_ENDPOINT_TYPE', 'OS_INTERFACE',
default=DEFAULT_ENDPOINT_TYPE), default=DEFAULT_INTERFACE),
help='Defaults to env[OS_ENDPOINT_TYPE] or ' help=argparse.SUPPRESS)
+ DEFAULT_ENDPOINT_TYPE + '.')
parser.add_argument('--os-cloud',
metavar='<auth-cloud>',
default=cliutils.env('OS_CLOUD', default=None),
help='Defaults to env[OS_CLOUD].')
# NOTE(dtroyer): We can't add --endpoint_type here due to argparse # NOTE(dtroyer): We can't add --endpoint_type here due to argparse
# thinking usage-list --end is ambiguous; but it # thinking usage-list --end is ambiguous; but it
# works fine with only --endpoint-type present # works fine with only --endpoint-type present
@@ -309,12 +399,17 @@ class OpenStackMagnumShell(object):
'verifying a TLS (https) server certificate. ' 'verifying a TLS (https) server certificate. '
'Defaults to env[OS_CACERT].') 'Defaults to env[OS_CACERT].')
parser.add_argument('--os-endpoint-override',
metavar='<endpoint-override>',
default=cliutils.env('OS_ENDPOINT_OVERRIDE',
default=None),
help="Use this API endpoint instead of the "
"Service Catalog.")
parser.add_argument('--bypass-url', parser.add_argument('--bypass-url',
metavar='<bypass-url>', metavar='<bypass-url>',
default=cliutils.env('BYPASS_URL', default=None), default=cliutils.env('BYPASS_URL', default=None),
dest='bypass_url', dest='bypass_url',
help="Use this API endpoint instead of the " help=argparse.SUPPRESS)
"Service Catalog.")
parser.add_argument('--bypass_url', parser.add_argument('--bypass_url',
help=argparse.SUPPRESS) help=argparse.SUPPRESS)
@@ -324,9 +419,6 @@ class OpenStackMagnumShell(object):
action='store_true', action='store_true',
help="Do not verify https connections") help="Do not verify https connections")
# The auth-system-plugins might require some extra options
auth.load_auth_system_opts(parser)
return parser return parser
def get_subcommand_parser(self, version): def get_subcommand_parser(self, version):
@@ -434,95 +526,43 @@ class OpenStackMagnumShell(object):
self.do_bash_completion(args) self.do_bash_completion(args)
return 0 return 0
(os_username, os_project_name, os_project_id, if not args.service_type:
os_tenant_id, os_tenant_name, args.service_type = DEFAULT_SERVICE_TYPE
os_user_domain_id, os_user_domain_name,
os_project_domain_id, os_project_domain_name,
os_auth_url, os_auth_system, endpoint_type,
service_type, bypass_url, insecure) = (
(args.os_username, args.os_project_name, args.os_project_id,
args.os_tenant_id, args.os_tenant_name,
args.os_user_domain_id, args.os_user_domain_name,
args.os_project_domain_id, args.os_project_domain_name,
args.os_auth_url, args.os_auth_system, args.endpoint_type,
args.service_type, args.bypass_url, args.insecure)
)
os_project_id = (os_project_id or os_tenant_id) if args.bypass_url:
os_project_name = (os_project_name or os_tenant_name) args.os_endpoint_override = args.bypass_url
if os_auth_system and os_auth_system != "keystone": args.os_project_id = (args.os_project_id or args.os_tenant_id)
auth_plugin = auth.load_plugin(os_auth_system) args.os_project_name = (args.os_project_name or args.os_tenant_name)
else:
auth_plugin = None
# Fetched and set later as needed
os_password = None
if not endpoint_type:
endpoint_type = DEFAULT_ENDPOINT_TYPE
if not service_type:
service_type = DEFAULT_SERVICE_TYPE
# NA - there is only one service this CLI accesses
# service_type = utils.get_service_type(args.func) or service_type
# FIXME(usrleon): Here should be restrict for project id same as
# for os_username or os_password but for compatibility it is not.
if not cliutils.isunauthenticated(args.func): if not cliutils.isunauthenticated(args.func):
if auth_plugin: if (not (args.os_token and
auth_plugin.parse_opts(args) (args.os_auth_url or args.os_endpoint_override)) and
not args.os_cloud
):
if not auth_plugin or not auth_plugin.opts: if not (args.os_username or args.os_user_id):
if not os_username:
raise exc.CommandError("You must provide a username "
"via either --os-username or "
"env[OS_USERNAME]")
if not os_project_name and not os_project_id:
raise exc.CommandError("You must provide a project name "
"or project id via --os-project-name, "
"--os-project-id, env[OS_PROJECT_NAME] "
"or env[OS_PROJECT_ID]")
if not os_auth_url:
if os_auth_system and os_auth_system != 'keystone':
os_auth_url = auth_plugin.get_auth_url()
if not os_auth_url:
raise exc.CommandError("You must provide an auth url "
"via either --os-auth-url or "
"env[OS_AUTH_URL] or specify an "
"auth_system which defines a "
"default url with --os-auth-system "
"or env[OS_AUTH_SYSTEM]")
# NOTE: The Magnum client authenticates when you create it. So instead of
# creating here and authenticating later, which is what the novaclient
# does, we just create the client later.
# Now check for the password/token of which pieces of the
# identifying keyring key can come from the underlying client
if not cliutils.isunauthenticated(args.func):
# NA - Client can't be used with SecretsHelper
if (auth_plugin and auth_plugin.opts and
"os_password" not in auth_plugin.opts):
use_pw = False
else:
use_pw = True
if use_pw:
# Auth using token must have failed or not happened
# at all, so now switch to password mode and save
# the token when its gotten... using our keyring
# saver
os_password = args.os_password
if not os_password:
raise exc.CommandError( raise exc.CommandError(
'Expecting a password provided via either ' "You must provide a username via either --os-username "
'--os-password, env[OS_PASSWORD], or ' "or via env[OS_USERNAME]"
'prompted response') )
if not args.os_password:
raise exc.CommandError(
"You must provide a password via either "
"--os-password, env[OS_PASSWORD], or prompted "
"response"
)
if (not args.os_project_name and not args.os_project_id):
raise exc.CommandError(
"You must provide a project name or project id via "
"--os-project-name, --os-project-id, "
"env[OS_PROJECT_NAME] or env[OS_PROJECT_ID]"
)
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]"
)
try: try:
client = { client = {
'1': client_v1, '1': client_v1,
@@ -530,20 +570,32 @@ class OpenStackMagnumShell(object):
except KeyError: except KeyError:
client = client_v1 client = client_v1
self.cs = client.Client(username=os_username, args.os_endpoint_type = (args.os_endpoint_type or args.endpoint_type)
api_key=os_password, if args.os_endpoint_type:
project_id=os_project_id, args.os_interface = args.os_endpoint_type
project_name=os_project_name,
user_domain_id=os_user_domain_id, if args.os_interface.endswith('URL'):
user_domain_name=os_user_domain_name, args.os_interface = args.os_interface[:-3]
project_domain_id=os_project_domain_id,
project_domain_name=os_project_domain_name, self.cs = client.Client(
auth_url=os_auth_url, cloud=args.os_cloud,
service_type=service_type, user_id=args.os_user_id,
region_name=args.os_region_name, username=args.os_username,
magnum_url=bypass_url, password=args.os_password,
endpoint_type=endpoint_type, auth_token=args.os_token,
insecure=insecure) project_id=args.os_project_id,
project_name=args.os_project_name,
user_domain_id=args.os_user_domain_id,
user_domain_name=args.os_user_domain_name,
project_domain_id=args.os_project_domain_id,
project_domain_name=args.os_project_domain_name,
auth_url=args.os_auth_url,
service_type=args.service_type,
region_name=args.os_region_name,
magnum_url=args.os_endpoint_override,
interface=args.os_interface,
insecure=args.insecure,
)
args.func(self.cs, args) args.func(self.cs, args)

View File

@@ -22,9 +22,9 @@ class ClientTest(testtools.TestCase):
@mock.patch('magnumclient.v1.client.Client') @mock.patch('magnumclient.v1.client.Client')
def test_no_version_argument(self, mock_magnum_client): def test_no_version_argument(self, mock_magnum_client):
client.Client(input_auth_token='mytoken', magnum_url='http://myurl/') client.Client(auth_token='mytoken', magnum_url='http://myurl/')
mock_magnum_client.assert_called_with( mock_magnum_client.assert_called_with(
input_auth_token='mytoken', magnum_url='http://myurl/') auth_token='mytoken', magnum_url='http://myurl/')
@mock.patch('magnumclient.v1.client.Client') @mock.patch('magnumclient.v1.client.Client')
def test_valid_version_argument(self, mock_magnum_client): def test_valid_version_argument(self, mock_magnum_client):

View File

@@ -128,8 +128,8 @@ class ShellTest(utils.TestCase):
matchers.MatchesRegex(r, re.DOTALL | re.MULTILINE)) matchers.MatchesRegex(r, re.DOTALL | re.MULTILINE))
def test_no_username(self): def test_no_username(self):
required = ('You must provide a username via either' required = ('You must provide a username via'
' --os-username or env[OS_USERNAME]') ' either --os-username or via env[OS_USERNAME]')
self.make_env(exclude='OS_USERNAME') self.make_env(exclude='OS_USERNAME')
try: try:
self.shell('bay-list') self.shell('bay-list')
@@ -140,7 +140,7 @@ class ShellTest(utils.TestCase):
def test_no_user_id(self): def test_no_user_id(self):
required = ('You must provide a username via' required = ('You must provide a username via'
' either --os-username or env[OS_USERNAME]') ' either --os-username or via env[OS_USERNAME]')
self.make_env(exclude='OS_USER_ID', fake_env=FAKE_ENV2) self.make_env(exclude='OS_USER_ID', fake_env=FAKE_ENV2)
try: try:
self.shell('bay-list') self.shell('bay-list')
@@ -170,15 +170,13 @@ class ShellTest(utils.TestCase):
self.fail('CommandError not raised') self.fail('CommandError not raised')
def test_no_auth_url(self): def test_no_auth_url(self):
required = ('You must provide an auth url' required = ("You must provide an auth url via either "
' via either --os-auth-url or env[OS_AUTH_URL] or' "--os-auth-url or via env[OS_AUTH_URL]")
' specify an auth_system which defines a default url'
' with --os-auth-system or env[OS_AUTH_SYSTEM]',)
self.make_env(exclude='OS_AUTH_URL') self.make_env(exclude='OS_AUTH_URL')
try: try:
self.shell('bay-list') self.shell('bay-list')
except exceptions.CommandError as message: except exceptions.CommandError as message:
self.assertEqual(required, message.args) self.assertEqual(required, message.args[0])
else: else:
self.fail('CommandError not raised') self.fail('CommandError not raised')
@@ -200,20 +198,6 @@ class ShellTest(utils.TestCase):
_, session_kwargs = mock_session.Session.call_args_list[0] _, session_kwargs = mock_session.Session.call_args_list[0]
self.assertEqual(False, session_kwargs['verify']) self.assertEqual(False, session_kwargs['verify'])
@mock.patch('sys.stdin', side_effect=mock.MagicMock)
@mock.patch('getpass.getpass', side_effect=EOFError)
def test_no_password(self, mock_getpass, mock_stdin):
required = ('Expecting a password provided'
' via either --os-password, env[OS_PASSWORD],'
' or prompted response',)
self.make_env(exclude='OS_PASSWORD')
try:
self.shell('bay-list')
except exceptions.CommandError as message:
self.assertEqual(required, message.args)
else:
self.fail('CommandError not raised')
@mock.patch('sys.argv', ['magnum']) @mock.patch('sys.argv', ['magnum'])
@mock.patch('sys.stdout', six.StringIO()) @mock.patch('sys.stdout', six.StringIO())
@mock.patch('sys.stderr', six.StringIO()) @mock.patch('sys.stderr', six.StringIO())
@@ -228,17 +212,25 @@ class ShellTest(utils.TestCase):
self.assertIn('Command-line interface to the OpenStack Magnum API', self.assertIn('Command-line interface to the OpenStack Magnum API',
sys.stdout.getvalue()) sys.stdout.getvalue())
def _expected_client_kwargs(self):
return {
'password': 'password', 'auth_token': None,
'auth_url': self.AUTH_URL,
'cloud': None, 'interface': 'public',
'insecure': False, 'magnum_url': None,
'project_id': None, 'project_name': 'project_name',
'project_domain_id': None, 'project_domain_name': None,
'region_name': None, 'service_type': 'container-infra',
'user_id': None, 'username': 'username',
'user_domain_id': None, 'user_domain_name': None
}
@mock.patch('magnumclient.v1.client.Client') @mock.patch('magnumclient.v1.client.Client')
def _test_main_region(self, command, expected_region_name, mock_client): def _test_main_region(self, command, expected_region_name, mock_client):
self.shell(command) self.shell(command)
mock_client.assert_called_once_with( expected_args = self._expected_client_kwargs()
username='username', api_key='password', expected_args['region_name'] = expected_region_name
endpoint_type='publicURL', project_id='', mock_client.assert_called_once_with(**expected_args)
project_name='project_name', auth_url=self.AUTH_URL,
service_type='container-infra', region_name=expected_region_name,
project_domain_id='', project_domain_name='',
user_domain_id='', user_domain_name='',
magnum_url=None, insecure=False)
def test_main_option_region(self): def test_main_option_region(self):
self.make_env() self.make_env()
@@ -258,27 +250,42 @@ class ShellTest(utils.TestCase):
def test_main_endpoint_public(self, mock_client): def test_main_endpoint_public(self, mock_client):
self.make_env() self.make_env()
self.shell('--endpoint-type publicURL bay-list') self.shell('--endpoint-type publicURL bay-list')
mock_client.assert_called_once_with( expected_args = self._expected_client_kwargs()
username='username', api_key='password', expected_args['interface'] = 'public'
endpoint_type='publicURL', project_id='', mock_client.assert_called_once_with(**expected_args)
project_name='project_name', auth_url=self.AUTH_URL,
service_type='container-infra', region_name=None,
project_domain_id='', project_domain_name='',
user_domain_id='', user_domain_name='',
magnum_url=None, insecure=False)
@mock.patch('magnumclient.v1.client.Client') @mock.patch('magnumclient.v1.client.Client')
def test_main_endpoint_internal(self, mock_client): def test_main_endpoint_internal(self, mock_client):
self.make_env() self.make_env()
self.shell('--endpoint-type internalURL bay-list') self.shell('--endpoint-type internalURL bay-list')
mock_client.assert_called_once_with( expected_args = self._expected_client_kwargs()
username='username', api_key='password', expected_args['interface'] = 'internal'
endpoint_type='internalURL', project_id='', mock_client.assert_called_once_with(**expected_args)
project_name='project_name', auth_url=self.AUTH_URL,
service_type='container-infra', region_name=None, @mock.patch('magnumclient.v1.client.Client')
project_domain_id='', project_domain_name='', def test_main_os_cloud(self, mock_client):
user_domain_id='', user_domain_name='', expected_cloud = 'default'
magnum_url=None, insecure=False) self.shell('--os-cloud %s bay-list' % expected_cloud)
expected_args = self._expected_client_kwargs()
expected_args['cloud'] = expected_cloud
expected_args['username'] = None
expected_args['password'] = None
expected_args['project_name'] = None
expected_args['auth_url'] = None
mock_client.assert_called_once_with(**expected_args)
@mock.patch('magnumclient.v1.client.Client')
def test_main_env_os_cloud(self, mock_client):
expected_cloud = 'default'
self.make_env(fake_env={'OS_CLOUD': expected_cloud})
self.shell('bay-list')
expected_args = self._expected_client_kwargs()
expected_args['cloud'] = expected_cloud
expected_args['username'] = None
expected_args['password'] = None
expected_args['project_name'] = None
expected_args['auth_url'] = None
mock_client.assert_called_once_with(**expected_args)
class ShellTestKeystoneV3(ShellTest): class ShellTestKeystoneV3(ShellTest):
@@ -301,11 +308,10 @@ class ShellTestKeystoneV3(ShellTest):
def test_main_endpoint_public(self, mock_client): def test_main_endpoint_public(self, mock_client):
self.make_env(fake_env=FAKE_ENV4) self.make_env(fake_env=FAKE_ENV4)
self.shell('--endpoint-type publicURL bay-list') self.shell('--endpoint-type publicURL bay-list')
mock_client.assert_called_once_with( expected_args = self._expected_client_kwargs()
username='username', api_key='password', expected_args['interface'] = 'public'
endpoint_type='publicURL', project_id='project_id', expected_args['project_id'] = 'project_id'
project_name='', auth_url=self.AUTH_URL, expected_args['project_name'] = None
service_type='container-infra', region_name=None, expected_args['project_domain_name'] = 'Default'
project_domain_id='', project_domain_name='Default', expected_args['user_domain_name'] = 'Default'
user_domain_id='', user_domain_name='Default', mock_client.assert_called_once_with(**expected_args)
magnum_url=None, insecure=False)

View File

@@ -20,174 +20,206 @@ from keystoneauth1.exceptions import catalog
from magnumclient.v1 import client from magnumclient.v1 import client
class ClientTest(testtools.TestCase): class ClientInitializeTest(testtools.TestCase):
def _load_session_kwargs(self):
return {
'username': None,
'project_id': None,
'project_name': None,
'auth_url': None,
'password': None,
'auth_type': 'password',
'insecure': False,
'user_domain_id': None,
'user_domain_name': None,
'project_domain_id': None,
'project_domain_name': None,
'auth_token': None,
'timeout': 600,
}
def _load_service_type_kwargs(self):
return {
'interface': 'public',
'region_name': None,
'service_name': None,
'service_type': 'container-infra',
}
def _session_client_kwargs(self, session):
kwargs = self._load_service_type_kwargs()
kwargs['endpoint_override'] = None
kwargs['session'] = session
return kwargs
@mock.patch('magnumclient.common.httpclient.SessionClient') @mock.patch('magnumclient.common.httpclient.SessionClient')
@mock.patch('keystoneauth1.session.Session') @mock.patch('magnumclient.v1.client._load_session')
def test_init_with_session(self, mock_session, http_client): @mock.patch('magnumclient.v1.client._load_service_type',
return_value='container-infra')
def test_init_with_session(self,
mock_load_service_type,
mock_load_session,
mock_http_client):
session = mock.Mock() session = mock.Mock()
client.Client(session=session) client.Client(session=session)
mock_session.assert_not_called() mock_load_session.assert_not_called()
http_client.assert_called_once_with( mock_load_service_type.assert_called_once_with(
interface='public', session,
region_name=None, **self._load_service_type_kwargs()
service_name=None, )
service_type='container-infra', mock_http_client.assert_called_once_with(
session=session) **self._session_client_kwargs(session)
)
@mock.patch('magnumclient.common.httpclient.SessionClient') def _test_init_with_secret(self,
@mock.patch('keystoneauth1.token_endpoint.Token') init_func,
@mock.patch('keystoneauth1.session.Session') mock_load_service_type,
def test_init_with_token_and_url( mock_load_session,
self, mock_session, mock_token, http_client): mock_http_client,):
mock_auth_plugin = mock.Mock() expected_password = 'expected_password'
mock_token.return_value = mock_auth_plugin
session = mock.Mock() session = mock.Mock()
mock_session.return_value = session mock_load_session.return_value = session
client.Client(input_auth_token='mytoken', magnum_url='http://myurl/') init_func(expected_password)
mock_session.assert_called_once_with( load_session_args = self._load_session_kwargs()
auth=mock_auth_plugin, verify=True) load_session_args['password'] = expected_password
http_client.assert_called_once_with( mock_load_session.assert_called_once_with(
endpoint_override='http://myurl/', **load_session_args
interface='public', )
region_name=None, mock_load_service_type.assert_called_once_with(
service_name=None, session,
service_type='container-infra', **self._load_service_type_kwargs()
session=session) )
mock_http_client.assert_called_once_with(
**self._session_client_kwargs(session)
)
@mock.patch('magnumclient.common.httpclient.SessionClient') @mock.patch('magnumclient.common.httpclient.SessionClient')
@mock.patch('keystoneauth1.loading.get_plugin_loader') @mock.patch('magnumclient.v1.client._load_session')
@mock.patch('keystoneauth1.session.Session') @mock.patch('magnumclient.v1.client._load_service_type',
def test_init_with_token( return_value='container-infra')
self, mock_session, mock_loader, http_client): def test_init_with_password(self,
mock_plugin = mock.Mock() mock_load_service_type,
mock_loader.return_value = mock_plugin mock_load_session,
client.Client(input_auth_token='mytoken', auth_url='authurl') mock_http_client):
mock_loader.assert_called_once_with('token') self._test_init_with_secret(
mock_plugin.load_from_options.assert_called_once_with( lambda x: client.Client(password=x),
auth_url='authurl', mock_load_service_type,
project_id=None, mock_load_session,
project_name=None, mock_http_client
project_domain_id=None, )
project_domain_name=None,
user_domain_id=None,
user_domain_name=None,
token='mytoken')
http_client.assert_called_once_with(
interface='public',
region_name=None,
service_name=None,
service_type='container-infra',
session=mock.ANY)
@mock.patch('magnumclient.common.httpclient.SessionClient') @mock.patch('magnumclient.common.httpclient.SessionClient')
@mock.patch('keystoneauth1.loading.get_plugin_loader') @mock.patch('magnumclient.v1.client._load_session')
@mock.patch('keystoneauth1.session.Session') @mock.patch('magnumclient.v1.client._load_service_type',
def test_init_with_user( return_value='container-infra')
self, mock_session, mock_loader, http_client): def test_init_with_api_key(self,
mock_plugin = mock.Mock() mock_load_service_type,
mock_loader.return_value = mock_plugin mock_load_session,
mock_http_client):
self._test_init_with_secret(
lambda x: client.Client(api_key=x),
mock_load_service_type,
mock_load_session,
mock_http_client
)
@mock.patch('magnumclient.common.httpclient.SessionClient')
@mock.patch('magnumclient.v1.client._load_session')
@mock.patch('magnumclient.v1.client._load_service_type',
return_value='container-infra')
def test_init_with_auth_token(self,
mock_load_service_type,
mock_load_session,
mock_http_client,):
expected_token = 'expected_password'
session = mock.Mock()
mock_load_session.return_value = session
client.Client(auth_token=expected_token)
load_session_args = self._load_session_kwargs()
load_session_args['auth_token'] = expected_token
load_session_args['auth_type'] = 'token'
mock_load_session.assert_called_once_with(
**load_session_args
)
mock_load_service_type.assert_called_once_with(
session,
**self._load_service_type_kwargs()
)
mock_http_client.assert_called_once_with(
**self._session_client_kwargs(session)
)
def _test_init_with_interface(self,
init_func,
mock_load_service_type,
mock_load_session,
mock_http_client):
expected_interface = 'admin'
session = mock.Mock()
mock_load_session.return_value = session
init_func(expected_interface)
mock_load_session.assert_called_once_with(
**self._load_session_kwargs()
)
expected_kwargs = self._load_service_type_kwargs()
expected_kwargs['interface'] = expected_interface
mock_load_service_type.assert_called_once_with(
session,
**expected_kwargs
)
expected_kwargs = self._session_client_kwargs(session)
expected_kwargs['interface'] = expected_interface
mock_http_client.assert_called_once_with(
**expected_kwargs
)
@mock.patch('magnumclient.common.httpclient.SessionClient')
@mock.patch('magnumclient.v1.client._load_session')
@mock.patch('magnumclient.v1.client._load_service_type',
return_value='container-infra')
def test_init_with_interface(self,
mock_load_service_type,
mock_load_session,
mock_http_client):
self._test_init_with_interface(
lambda x: client.Client(interface=x),
mock_load_service_type,
mock_load_session,
mock_http_client
)
@mock.patch('magnumclient.common.httpclient.SessionClient')
@mock.patch('magnumclient.v1.client._load_session')
@mock.patch('magnumclient.v1.client._load_service_type',
return_value='container-infra')
def test_init_with_endpoint_type(self,
mock_load_service_type,
mock_load_session,
mock_http_client):
self._test_init_with_interface(
lambda x: client.Client(interface='public',
endpoint_type=('%sURL' % x)),
mock_load_service_type,
mock_load_session,
mock_http_client
)
@mock.patch('magnumclient.common.httpclient.SessionClient')
@mock.patch('magnumclient.v1.client._load_session')
def test_init_with_legacy_service_type(self,
mock_load_session,
mock_http_client):
session = mock.Mock()
mock_load_session.return_value = session
session.get_endpoint.side_effect = [
catalog.EndpointNotFound(),
mock.Mock()
]
client.Client(username='myuser', auth_url='authurl') client.Client(username='myuser', auth_url='authurl')
mock_loader.assert_called_once_with('password') expected_kwargs = self._session_client_kwargs(session)
mock_plugin.load_from_options.assert_called_once_with( expected_kwargs['service_type'] = 'container'
auth_url='authurl', mock_http_client.assert_called_once_with(
username='myuser', **expected_kwargs
password=None, )
project_domain_id=None,
project_domain_name=None,
user_domain_id=None,
user_domain_name=None,
project_id=None,
project_name=None)
http_client.assert_called_once_with(
interface='public',
region_name=None,
service_name=None,
service_type='container-infra',
session=mock.ANY)
@mock.patch('magnumclient.common.httpclient.SessionClient')
@mock.patch('keystoneauth1.loading.get_plugin_loader')
@mock.patch('keystoneauth1.session.Session')
def test_init_with_legacy_service_type(
self, mock_session, mock_loader, http_client):
mock_plugin = mock.Mock()
mock_loader.return_value = mock_plugin
mock_session_obj = mock.Mock()
mock_session.return_value = mock_session_obj
mock_session_obj.get_endpoint.side_effect = [
catalog.EndpointNotFound(), mock.Mock()]
client.Client(username='myuser', auth_url='authurl')
mock_loader.assert_called_once_with('password')
mock_plugin.load_from_options.assert_called_once_with(
auth_url='authurl',
username='myuser',
password=None,
project_domain_id=None,
project_domain_name=None,
user_domain_id=None,
user_domain_name=None,
project_id=None,
project_name=None)
http_client.assert_called_once_with(
interface='public',
region_name=None,
service_name=None,
service_type='container',
session=mock.ANY)
@mock.patch('magnumclient.common.httpclient.SessionClient')
@mock.patch('keystoneauth1.loading.get_plugin_loader')
@mock.patch('keystoneauth1.session.Session')
def test_init_unauthorized(
self, mock_session, mock_loader, http_client):
mock_plugin = mock.Mock()
mock_loader.return_value = mock_plugin
mock_session_obj = mock.Mock()
mock_session.return_value = mock_session_obj
mock_session_obj.get_endpoint.side_effect = Exception()
self.assertRaises(
RuntimeError,
client.Client, username='myuser', auth_url='authurl')
mock_loader.assert_called_once_with('password')
mock_plugin.load_from_options.assert_called_once_with(
auth_url='authurl',
username='myuser',
password=None,
project_domain_id=None,
project_domain_name=None,
user_domain_id=None,
user_domain_name=None,
project_id=None,
project_name=None)
http_client.assert_not_called()
@mock.patch('magnumclient.common.httpclient.SessionClient')
@mock.patch('keystoneauth1.session.Session')
def test_init_with_endpoint_override(self, mock_session, http_client):
session = mock.Mock()
client.Client(session=session, endpoint_override='magnumurl')
mock_session.assert_not_called()
http_client.assert_called_once_with(
interface='public',
region_name=None,
service_name=None,
service_type='container-infra',
session=session,
endpoint_override='magnumurl')
@mock.patch('magnumclient.common.httpclient.SessionClient')
@mock.patch('keystoneauth1.session.Session')
def test_init_with_magnum_url_and_endpoint_override(self, mock_session,
http_client):
session = mock.Mock()
client.Client(session=session, magnum_url='magnumurl',
endpoint_override='magnumurl_override')
mock_session.assert_not_called()
http_client.assert_called_once_with(
interface='public',
region_name=None,
service_name=None,
service_type='container-infra',
session=session,
endpoint_override='magnumurl')

View File

@@ -14,8 +14,8 @@
# limitations under the License. # limitations under the License.
from keystoneauth1.exceptions import catalog from keystoneauth1.exceptions import catalog
from keystoneauth1 import loading
from keystoneauth1 import session as ksa_session from keystoneauth1 import session as ksa_session
import os_client_config
from magnumclient.common import httpclient from magnumclient.common import httpclient
from magnumclient.v1 import baymodels from magnumclient.v1 import baymodels
@@ -28,6 +28,49 @@ DEFAULT_SERVICE_TYPE = 'container-infra'
LEGACY_DEFAULT_SERVICE_TYPE = 'container' LEGACY_DEFAULT_SERVICE_TYPE = 'container'
def _load_session(cloud=None, insecure=False, timeout=None, **kwargs):
cloud_config = os_client_config.OpenStackConfig()
cloud_config = cloud_config.get_one_cloud(
cloud=cloud,
verify=not insecure,
**kwargs)
verify, cert = cloud_config.get_requests_verify_args()
auth = cloud_config.get_auth()
session = ksa_session.Session(
auth=auth, verify=verify, cert=cert,
timeout=timeout)
return session
def _load_service_type(session,
service_type=None, service_name=None,
interface=None, region_name=None):
try:
# Trigger an auth error so that we can throw the exception
# we always have
session.get_endpoint(
service_type=service_type,
service_name=service_name,
interface=interface,
region_name=region_name)
except catalog.EndpointNotFound:
service_type = LEGACY_DEFAULT_SERVICE_TYPE
try:
session.get_endpoint(
service_type=service_type,
service_name=service_name,
interface=interface,
region_name=region_name)
except Exception as e:
raise RuntimeError(str(e))
except Exception as e:
raise RuntimeError(str(e))
return service_type
class Client(object): class Client(object):
def __init__(self, username=None, api_key=None, project_id=None, def __init__(self, username=None, api_key=None, project_id=None,
project_name=None, auth_url=None, magnum_url=None, project_name=None, auth_url=None, magnum_url=None,
@@ -35,87 +78,63 @@ class Client(object):
service_type=DEFAULT_SERVICE_TYPE, service_type=DEFAULT_SERVICE_TYPE,
region_name=None, input_auth_token=None, region_name=None, input_auth_token=None,
session=None, password=None, auth_type='password', session=None, password=None, auth_type='password',
interface='public', service_name=None, insecure=False, interface=None, service_name=None, insecure=False,
user_domain_id=None, user_domain_name=None, user_domain_id=None, user_domain_name=None,
project_domain_id=None, project_domain_name=None): project_domain_id=None, project_domain_name=None,
auth_token=None, timeout=600, **kwargs):
# We have to keep the api_key are for backwards compat, but let's # We have to keep the api_key are for backwards compat, but let's
# remove it from the rest of our code since it's not a keystone # remove it from the rest of our code since it's not a keystone
# concept # concept
if not password: if not password:
password = api_key password = api_key
# Backwards compat for people assing in input_auth_token
if input_auth_token:
auth_token = input_auth_token
# Backwards compat for people assing in endpoint_type # Backwards compat for people assing in endpoint_type
if endpoint_type: if endpoint_type:
interface = endpoint_type interface = endpoint_type
# osc sometimes give 'None' value
if not interface:
interface = 'public'
if interface.endswith('URL'):
interface = interface[:-3]
# fix (yolanda): os-cloud-config is using endpoint_override # fix (yolanda): os-cloud-config is using endpoint_override
# instead of magnum_url # instead of magnum_url
if endpoint_override and not magnum_url: if magnum_url and not endpoint_override:
magnum_url = endpoint_override endpoint_override = magnum_url
if magnum_url and input_auth_token: if not session:
auth_type = 'admin_token' if auth_token:
session = None auth_type = 'token'
loader_kwargs = dict( session = _load_session(
token=input_auth_token,
endpoint=magnum_url)
elif input_auth_token and not session:
auth_type = 'token'
loader_kwargs = dict(
token=input_auth_token,
auth_url=auth_url,
project_id=project_id,
project_name=project_name,
user_domain_id=user_domain_id,
user_domain_name=user_domain_name,
project_domain_id=project_domain_id,
project_domain_name=project_domain_name)
else:
loader_kwargs = dict(
username=username, username=username,
password=password,
auth_url=auth_url,
project_id=project_id, project_id=project_id,
project_name=project_name, project_name=project_name,
auth_url=auth_url,
password=password,
auth_type=auth_type,
insecure=insecure,
user_domain_id=user_domain_id, user_domain_id=user_domain_id,
user_domain_name=user_domain_name, user_domain_name=user_domain_name,
project_domain_id=project_domain_id, project_domain_id=project_domain_id,
project_domain_name=project_domain_name) project_domain_name=project_domain_name,
auth_token=auth_token,
timeout=timeout,
**kwargs
)
# Backwards compatibility for people not passing in Session if not endpoint_override:
if session is None: service_type = _load_service_type(
loader = loading.get_plugin_loader(auth_type) session,
service_type=service_type,
# This should be able to handle v2 and v3 Keystone Auth service_name=service_name,
auth_plugin = loader.load_from_options(**loader_kwargs) interface=interface,
session = ksa_session.Session( region_name=region_name,
auth=auth_plugin, verify=(not insecure)) )
client_kwargs = {}
if magnum_url:
client_kwargs['endpoint_override'] = magnum_url
if not magnum_url:
try:
# Trigger an auth error so that we can throw the exception
# we always have
session.get_endpoint(
service_type=service_type,
service_name=service_name,
interface=interface,
region_name=region_name)
except catalog.EndpointNotFound:
service_type = LEGACY_DEFAULT_SERVICE_TYPE
try:
session.get_endpoint(
service_type=service_type,
service_name=service_name,
interface=interface,
region_name=region_name)
except Exception as e:
raise RuntimeError(str(e))
except Exception as e:
raise RuntimeError(str(e))
self.http_client = httpclient.SessionClient( self.http_client = httpclient.SessionClient(
service_type=service_type, service_type=service_type,
@@ -123,7 +142,8 @@ class Client(object):
interface=interface, interface=interface,
region_name=region_name, region_name=region_name,
session=session, session=session,
**client_kwargs) endpoint_override=endpoint_override,
)
self.bays = bays.BayManager(self.http_client) self.bays = bays.BayManager(self.http_client)
self.certificates = certificates.CertificateManager(self.http_client) self.certificates = certificates.CertificateManager(self.http_client)
self.baymodels = baymodels.BayModelManager(self.http_client) self.baymodels = baymodels.BayModelManager(self.http_client)