Reimplemented client shell

The shell now skips discovery of keystone versions or session creation
because all those details are encapsulated inside SDK.

We don't support server side authentication at the moment.

Argument parsing is moved out to a separate module.
This commit is contained in:
tengqm
2015-01-16 15:23:45 +08:00
parent 762a166b32
commit d95088619d

View File

@@ -21,23 +21,21 @@ import logging
import six
import sys
from oslo.utils import encodeutils
from oslo.utils import importutils
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 as kssession
from oslo_utils import encodeutils
from oslo_utils import importutils
import senlinclient
from senlinclient import client as senlin_client
from senlinclient import cliargs
from senlinclient import client
from senlinclient.common import exc
from senlinclient.common.i18n import _
from senlinclient.common import sdk
from senlinclient.common import utils
logger = logging.getLogger(__name__)
osprofiler_profiler = importutils.try_import("osprofiler.profiler")
LOG = logging.getLogger(__name__)
class HelpFormatter(argparse.HelpFormatter):
def start_section(self, heading):
@@ -47,181 +45,6 @@ class HelpFormatter(argparse.HelpFormatter):
class SenlinShell(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 senlinclient 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.'))
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(
'--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 senlin looks '
'for the default system CA certificates.'))
parser.add_argument(
'--os-username', default=utils.env('OS_USERNAME'),
help=_('Defaults to env[OS_USERNAME].'))
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 mutual 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 mutual 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].'))
parser.add_argument(
'--os-password', default=utils.env('OS_PASSWORD'),
help=_('Defaults to env[OS_PASSWORD]'))
parser.add_argument(
'--os-tenant-id', default=utils.env('OS_TENANT_ID'),
help=_('Defaults to env[OS_TENANT_ID]'))
parser.add_argument(
'--os-tenant-name', default=utils.env('OS_TENANT_NAME'),
help=_('Defaults to env[OS_TENANT_NAME]'))
parser.add_argument(
'--os-auth-url', default=utils.env('OS_AUTH_URL'),
help=_('Defaults to env[OS_AUTH_URL]'))
parser.add_argument(
'--os-region-name', default=utils.env('OS_REGION_NAME'),
help=_('Defaults to env[OS_REGION_NAME].'))
parser.add_argument(
'--os-auth-token', default=utils.env('OS_AUTH_TOKEN'),
help=_('Defaults to env[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-endpoint-type', default=utils.env('OS_ENDPOINT_TYPE'),
help=_('Defaults to env[OS_ENDPOINT_TYPE].'))
def get_base_parser(self):
parser = argparse.ArgumentParser(
prog='senlin',
description=__doc__.strip(),
epilog=_('Type "senlin 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=senlinclient.__version__,
help=_("Shows the client version and exits."))
parser.add_argument(
'-d', '--debug', action='store_true',
default=bool(utils.env('SENLINCLIENT_DEBUG')),
help=_('Defaults to env[SENLINCLIENT_DEBUG].'))
parser.add_argument(
'-v', '--verbose', action="store_true", default=False,
help=_("Print more verbose output."))
parser.add_argument(
'--api-timeout',
help=_('Number of seconds to wait for an API response, '
'defaults to system socket timeout'))
# os-no-client-auth tells senlinclient to use token, instead of
# env[OS_AUTH_URL]
parser.add_argument(
'--os-no-client-auth', action='store_true',
default=utils.env('OS_NO_CLIENT_AUTH'),
help=_('Do not contact keystone for a token. '
'Defaults to env[OS_NO_CLIENT_AUTH].'))
parser.add_argument(
'--senlin-url', default=utils.env('SENLIN_URL'),
help=_('Do not contact keystone for a token. '
'Defaults to env[SENLIN_URL].'))
parser.add_argument(
'--senlin-api-version',
default=utils.env('SENLIN_API_VERSION', default='1'),
help=_('Defaults to env[SENLIN_API_VERSION] or 1.'))
parser.add_argument(
'--include-password', action='store_true',
default=bool(utils.env('SENLIN_INCLUDE_PASSWORD')),
help=_('Send $OS_USERNAME and $OS_PASSWORD to senlin.'))
# 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)
if osprofiler_profiler:
parser.add_argument(
'--profile', metavar='HMAC_KEY',
help=_('HMAC key to use for encrypting context data for '
'performance profiling of operation. This key should '
'be the value of HMAC key configured in osprofiler '
'middleware in senlin, it is specified in the paste '
'configuration (/etc/senlin/api-paste.ini). Without '
'the key, profiling will not be triggered even if '
'osprofiler is enabled on server side.'))
return parser
def _setup_logging(self, debug):
log_lvl = logging.DEBUG if debug else logging.WARNING
logging.basicConfig(format="%(levelname)s (%(module)s) %(message)s",
@@ -275,6 +98,18 @@ class SenlinShell(object):
commands.remove('bash_completion')
print(' '.join(commands | options))
def add_profiler_args(self, parser):
if osprofiler_profiler:
parser.add_argument(
'--profile', metavar='HMAC_KEY',
help=_('HMAC key to use for encrypting context data for '
'performance profiling of operation. This key should '
'be the value of HMAC key configured in osprofiler '
'middleware in senlin, it is specified in the paste '
'deploy configuration (/etc/senlin/api-paste.ini). '
'Without the key, profiling will not be triggered '
'even if osprofiler is enabled on server side.'))
def _add_bash_completion_subparser(self, subparsers):
subparser = subparsers.add_parser('bash_completion',
add_help=False,
@@ -309,30 +144,10 @@ class SenlinShell(object):
self.parser.print_help()
def _check_identity_arguments(self, args):
if not args.os_username and not args.os_auth_token:
msg = _('You must provide a username via either --os-username (or '
'env[OS_USERNAME]) or a token via --os-auth-token (or '
'env[OS_AUTH_TOKEN])')
raise exc.CommandError(msg)
if not args.os_password and not args.os_auth_token:
msg = _('You must provide a password via either --os-password (or '
'env[OS_PASSWORD]) or a token via --os-auth-token (or '
'env[OS_AUTH_TOKEN]')
raise exc.CommandError(msg)
if args.os_no_client_auth:
if not args.senlin_url:
msg = _('If you specify --os-no-client-auth you must also '
'specify a Senlin API URL via --senlin-url (or '
'env[SENLIN_URL]')
raise exc.CommandError(msg)
else:
# Tenant/project name or ID is needed to make keystoneclient
# 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):
if not (args.project_id or args.project_name):
msg = _('You must provide tenant ID via either --os-tenant-id'
' (or env[OS_TENANT_ID]) or a tenant name via '
'--os-tenant-name (or env[OS_TENANT_NAME]) or a '
@@ -341,124 +156,54 @@ class SenlinShell(object):
'--os-project-name (or env[OS_PROJECT_NAME])')
raise exc.CommandError(msg)
if not args.os_auth_url:
if not args.auth_url:
msg = _('You must provide an auth url via --os-auth-url (or '
' env[OS_AUTH_URL])')
raise exc.CommandError(msg)
def _get_keystone_session(self, **kwargs):
# create a Keystone session
cacert = kwargs.get('cacert', None)
cert = kwargs.get('cert', None)
key = kwargs.get('key', None)
insecure = kwargs.get('insecure', False)
timeout = kwargs.get('timeout', None)
if insecure:
verify = False
else:
verify = cacert or True
if cert and key:
cert = (cert, key)
return kssession.Session(verify=verify, cert=cert, timeout=timeout)
def _get_keystone_auth(self, session, auth_url, **kwargs):
# discover the supported keystone versions using the given url
try:
ks_discover = discover.Discover(session=session, auth_url=auth_url)
v3_url = ks_discover.url_for('3.0')
except ks_exc.ClientException:
msg = _('Unable to locate Keystone V3 URL for authentication. '
'Please check your keystone configuration.')
raise exc.CommandError(msg)
auth_token = kwargs.pop('auth_token', None)
if auth_token:
return v3_auth.Token(v3_url, auth_token)
else:
return v3_auth.Password(v3_url, **kwargs)
def setup_senlin_client(self, api_ver, args):
def _setup_senlin_client(self, api_ver, args):
'''Create senlin client using given args.'''
endpoint = args.senlin_url
if args.os_no_client_auth:
# Do not use session since no_client_auth means using senlin to
# to authenticate
kwargs = {
'username': args.os_username,
'password': args.os_password,
'auth_url': args.os_auth_url,
'token': args.os_auth_token,
'include_pass': args.include_password,
'insecure': args.insecure,
'timeout': args.api_timeout
'auth_plugin': args.auth_plugin,
'auth_url': args.auth_url,
'project_name': args.project_name,
'domain_name': args.domain_name,
'project_domain_name': args.project_domain_name,
'user_domain_name': args.user_domain_name,
'user_name': args.username,
'password': args.password,
'verify': args.verify,
'token': args.token,
}
return senlin_client.Client(api_ver, endpoint, **kwargs)
# Get a keystone session first
kwargs = {
'cacert': args.os_cacert,
'cert': args.os_cert,
'key': args.os_key,
'insecure': args.insecure,
'timeout': args.api_timeout
}
ksession = self._get_keystone_session(**kwargs)
# Get keystone auth
project_id = args.os_project_id or args.os_tenant_id
project_name = args.os_project_name or args.os_tenant_name
kwargs = {
'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,
'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(ksession, args.os_auth_url,
conn = sdk.connection.Connection(preference=args.user_preferences,
**kwargs)
service_type = args.os_service_type or 'clustering'
if not endpoint:
svc_type = service_type
region_name = args.os_region_name
endpoint = keystone_auth.get_endpoint(ksession,
service_type=svc_type,
region_name=region_name)
endpoint_type = args.os_endpoint_type or 'publicURL'
kwargs = {
'auth_url': args.os_auth_url,
'session': ksession,
'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
}
return senlin_client.Client(api_ver, endpoint, **kwargs)
return client.Client('1', conn.session)
def main(self, argv):
# Parse args once to find version
base_parser = self.get_base_parser()
parser = argparse.ArgumentParser(
prog='senlin',
description=__doc__.strip(),
epilog=_('Type "senlin help <COMMAND>" for help on a specific '
'command.'),
add_help=False,
formatter_class=HelpFormatter,
)
cliargs.add_global_args(parser, version=senlinclient.__version__)
cliargs.add_global_identity_args(parser)
self.add_profiler_args(parser)
base_parser = parser
(options, args) = base_parser.parse_known_args(argv)
self._setup_logging(options.debug)
self._setup_verbose(options.verbose)
# build available subcommands based on version
api_ver = options.senlin_api_version
LOG.info(api_ver)
subcommand_parser = self.get_subcommand_parser(base_parser, api_ver)
self.parser = subcommand_parser
@@ -483,13 +228,13 @@ class SenlinShell(object):
self._check_identity_arguments(args)
# Setup Senlin client connection
client = self._setup_senlin_client(api_ver, args)
sc = self._setup_senlin_client(api_ver, args)
profile = osprofiler_profiler and options.profile
if profile:
osprofiler_profiler.init(options.profile)
args.func(client, args)
args.func(sc, args)
if profile:
trace_id = osprofiler_profiler.get().get_base_id()