osc-lib: shell

Convert to using ClientManager and OpenStackShell from osc-lib.
* Change all internal uses of ClientManager private attributes that are
  now public in osc-lib's ClientManager.  Leave back-compat copies in
  place in OSC's clientManager so we don't break plugins.
* Put some work-arounds in place for changes in osc-lib that we need until
  a new release makes it through the g-r and u-c change process.
* Add a test for Unicode decoding of argv in shell.main() to parallel
  the one in osc-lib.

Change-Id: I85289740d4ca081f2aca8c9b40ec422ad25d302c
This commit is contained in:
Dean Troyer 2016-06-23 17:51:54 -05:00
parent a42664ccaa
commit 6a15f90dae
10 changed files with 149 additions and 870 deletions

View File

@ -86,7 +86,7 @@ def get_plugin_modules(group):
# Add the plugin to the ClientManager # Add the plugin to the ClientManager
setattr( setattr(
ClientManager, clientmanager.ClientManager,
module.API_NAME, module.API_NAME,
clientmanager.ClientCache( clientmanager.ClientCache(
getattr(sys.modules[ep.module_name], 'make_client', None) getattr(sys.modules[ep.module_name], 'make_client', None)

View File

@ -64,7 +64,7 @@ def make_client(instance):
if ext.name == "list_extensions"] if ext.name == "list_extensions"]
# Remember interface only if it is set # Remember interface only if it is set
kwargs = utils.build_kwargs_dict('endpoint_type', instance._interface) kwargs = utils.build_kwargs_dict('endpoint_type', instance.interface)
client = nova_client.Client( client = nova_client.Client(
version, version,
@ -72,7 +72,7 @@ def make_client(instance):
extensions=extensions, extensions=extensions,
http_log_debug=http_log_debug, http_log_debug=http_log_debug,
timings=instance.timing, timings=instance.timing,
region_name=instance._region_name, region_name=instance.region_name,
**kwargs **kwargs
) )

View File

@ -16,7 +16,6 @@
import logging import logging
from keystoneclient.v2_0 import client as identity_client_v2 from keystoneclient.v2_0 import client as identity_client_v2
from osc_lib.api import auth
from osc_lib import utils from osc_lib import utils
from openstackclient.i18n import _ from openstackclient.i18n import _
@ -50,11 +49,11 @@ def make_client(instance):
LOG.debug('Instantiating identity client: %s', identity_client) LOG.debug('Instantiating identity client: %s', identity_client)
# Remember interface only if interface is set # Remember interface only if interface is set
kwargs = utils.build_kwargs_dict('interface', instance._interface) kwargs = utils.build_kwargs_dict('interface', instance.interface)
client = identity_client( client = identity_client(
session=instance.session, session=instance.session,
region_name=instance._region_name, region_name=instance.region_name,
**kwargs **kwargs
) )
@ -70,7 +69,7 @@ def build_option_parser(parser):
help=_('Identity API version, default=%s ' help=_('Identity API version, default=%s '
'(Env: OS_IDENTITY_API_VERSION)') % DEFAULT_API_VERSION, '(Env: OS_IDENTITY_API_VERSION)') % DEFAULT_API_VERSION,
) )
return auth.build_auth_plugins_option_parser(parser) return parser
class IdentityClientv2(identity_client_v2.Client): class IdentityClientv2(identity_client_v2.Client):

View File

@ -47,15 +47,15 @@ def make_client(instance):
endpoint = instance.get_endpoint_for_service_type( endpoint = instance.get_endpoint_for_service_type(
API_NAME, API_NAME,
region_name=instance._region_name, region_name=instance.region_name,
interface=instance._interface, interface=instance.interface,
) )
client = image_client( client = image_client(
endpoint, endpoint,
token=instance.auth.get_token(instance.session), token=instance.auth.get_token(instance.session),
cacert=instance._cacert, cacert=instance.cacert,
insecure=instance._insecure, insecure=not instance.verify,
) )
# Create the low-level API # Create the low-level API
@ -70,8 +70,8 @@ def make_client(instance):
session=instance.session, session=instance.session,
endpoint=instance.get_endpoint_for_service_type( endpoint=instance.get_endpoint_for_service_type(
IMAGE_API_TYPE, IMAGE_API_TYPE,
region_name=instance._region_name, region_name=instance.region_name,
interface=instance._interface, interface=instance.interface,
) )
) )

View File

@ -34,9 +34,9 @@ API_VERSIONS = {
def make_client(instance): def make_client(instance):
"""Returns a network proxy""" """Returns a network proxy"""
prof = profile.Profile() prof = profile.Profile()
prof.set_region(API_NAME, instance._region_name) prof.set_region(API_NAME, instance.region_name)
prof.set_version(API_NAME, instance._api_version[API_NAME]) prof.set_version(API_NAME, instance._api_version[API_NAME])
prof.set_interface(API_NAME, instance._interface) prof.set_interface(API_NAME, instance.interface)
conn = connection.Connection(authenticator=instance.session.auth, conn = connection.Connection(authenticator=instance.session.auth,
verify=instance.session.verify, verify=instance.session.verify,
cert=instance.session.cert, cert=instance.session.cert,

View File

@ -32,8 +32,8 @@ def make_client(instance):
endpoint = instance.get_endpoint_for_service_type( endpoint = instance.get_endpoint_for_service_type(
'object-store', 'object-store',
region_name=instance._region_name, region_name=instance.region_name,
interface=instance._interface, interface=instance.interface,
) )
client = object_store_v1.APIv1( client = object_store_v1.APIv1(

View File

@ -16,30 +16,17 @@
"""Command-line interface to the OpenStack APIs""" """Command-line interface to the OpenStack APIs"""
import argparse
import getpass
import locale import locale
import logging
import six
import sys import sys
import traceback
from cliff import app from osc_lib.api import auth
from cliff import command from osc_lib import shell
from cliff import complete
from cliff import help
from osc_lib.cli import client_config as cloud_config
from osc_lib.command import timing
from osc_lib import exceptions as exc
from osc_lib import logs
from osc_lib import utils
from oslo_utils import importutils from oslo_utils import importutils
from oslo_utils import strutils import six
import openstackclient import openstackclient
from openstackclient.common import clientmanager from openstackclient.common import clientmanager
from openstackclient.common import commandmanager from openstackclient.common import commandmanager
from openstackclient.i18n import _
osprofiler_profiler = importutils.try_import("osprofiler.profiler") osprofiler_profiler = importutils.try_import("osprofiler.profiler")
@ -47,47 +34,9 @@ osprofiler_profiler = importutils.try_import("osprofiler.profiler")
DEFAULT_DOMAIN = 'default' DEFAULT_DOMAIN = 'default'
def prompt_for_password(prompt=None): class OpenStackShell(shell.OpenStackShell):
"""Prompt user for a password
Prompt for a password if stdin is a tty.
"""
if not prompt:
prompt = 'Password: '
pw = None
# If stdin is a tty, try prompting for the password
if hasattr(sys.stdin, 'isatty') and sys.stdin.isatty():
# Check for Ctl-D
try:
pw = getpass.getpass(prompt)
except EOFError:
pass
# No password because we did't have a tty or nothing was entered
if not pw:
raise exc.CommandError(_("No password entered, or found via"
" --os-password or OS_PASSWORD"),)
return pw
class OpenStackShell(app.App):
CONSOLE_MESSAGE_FORMAT = '%(levelname)s: %(name)s %(message)s'
log = logging.getLogger(__name__)
timing_data = []
def __init__(self): def __init__(self):
# Patch command.Command to add a default auth_required = True
command.Command.auth_required = True
# Some commands do not need authentication
help.HelpCommand.auth_required = False
complete.CompleteCommand.auth_required = False
# Slight change to the meaning of --debug
self.DEFAULT_DEBUG_VALUE = None
self.DEFAULT_DEBUG_HELP = 'Set debug logging and traceback on errors.'
super(OpenStackShell, self).__init__( super(OpenStackShell, self).__init__(
description=__doc__.strip(), description=__doc__.strip(),
@ -97,281 +46,28 @@ class OpenStackShell(app.App):
self.api_version = {} self.api_version = {}
# Until we have command line arguments parsed, dump any stack traces
self.dump_stack_trace = True
# Assume TLS host certificate verification is enabled # Assume TLS host certificate verification is enabled
self.verify = True self.verify = True
self.client_manager = None
self.command_options = None
self.do_profile = False
def configure_logging(self):
"""Configure logging for the app."""
self.log_configurator = logs.LogConfigurator(self.options)
self.dump_stack_trace = self.log_configurator.dump_trace
def run(self, argv):
ret_val = 1
self.command_options = argv
try:
ret_val = super(OpenStackShell, self).run(argv)
return ret_val
except Exception as e:
if not logging.getLogger('').handlers:
logging.basicConfig()
if self.dump_stack_trace:
self.log.error(traceback.format_exc())
else:
self.log.error('Exception raised: ' + str(e))
return ret_val
finally:
self.log.info("END return value: %s", ret_val)
def init_profile(self):
# NOTE(dtroyer): Remove this 'if' block when the --profile global
# option is removed
if osprofiler_profiler and self.options.old_profile:
self.log.warning(
'The --profile option is deprecated, '
'please use --os-profile instead'
)
if not self.options.profile:
self.options.profile = self.options.old_profile
self.do_profile = osprofiler_profiler and self.options.profile
if self.do_profile:
osprofiler_profiler.init(self.options.profile)
def close_profile(self):
if self.do_profile:
trace_id = osprofiler_profiler.get().get_base_id()
# NOTE(dbelova): let's use warning log level to see these messages
# printed. In fact we can define custom log level here with value
# bigger than most big default one (CRITICAL) or something like
# that (PROFILE = 60 for instance), but not sure we need it here.
self.log.warning("Trace ID: %s" % trace_id)
self.log.warning("Display trace with command:\n"
"osprofiler trace show --html %s " % trace_id)
def run_subcommand(self, argv):
self.init_profile()
try:
ret_value = super(OpenStackShell, self).run_subcommand(argv)
finally:
self.close_profile()
return ret_value
def interact(self):
self.init_profile()
try:
ret_value = super(OpenStackShell, self).interact()
finally:
self.close_profile()
return ret_value
def build_option_parser(self, description, version): def build_option_parser(self, description, version):
parser = super(OpenStackShell, self).build_option_parser( parser = super(OpenStackShell, self).build_option_parser(
description, description,
version) version)
parser = clientmanager.build_plugin_option_parser(parser)
parser = auth.build_auth_plugins_option_parser(parser)
return parser
# service token auth argument def _final_defaults(self):
parser.add_argument( super(OpenStackShell, self)._final_defaults()
'--os-cloud',
metavar='<cloud-config-name>',
dest='cloud',
default=utils.env('OS_CLOUD'),
help=_('Cloud name in clouds.yaml (Env: OS_CLOUD)'),
)
# Global arguments
parser.add_argument(
'--os-region-name',
metavar='<auth-region-name>',
dest='region_name',
default=utils.env('OS_REGION_NAME'),
help=_('Authentication region name (Env: OS_REGION_NAME)'),
)
parser.add_argument(
'--os-cacert',
metavar='<ca-bundle-file>',
dest='cacert',
default=utils.env('OS_CACERT'),
help=_('CA certificate bundle file (Env: OS_CACERT)'),
)
parser.add_argument(
'--os-cert',
metavar='<certificate-file>',
dest='cert',
default=utils.env('OS_CERT'),
help=_('Client certificate bundle file (Env: OS_CERT)'),
)
parser.add_argument(
'--os-key',
metavar='<key-file>',
dest='key',
default=utils.env('OS_KEY'),
help=_('Client certificate key file (Env: OS_KEY)'),
)
verify_group = parser.add_mutually_exclusive_group()
verify_group.add_argument(
'--verify',
action='store_true',
default=None,
help=_('Verify server certificate (default)'),
)
verify_group.add_argument(
'--insecure',
action='store_true',
default=None,
help=_('Disable server certificate verification'),
)
parser.add_argument(
'--os-default-domain',
metavar='<auth-domain>',
dest='default_domain',
default=utils.env(
'OS_DEFAULT_DOMAIN',
default=DEFAULT_DOMAIN),
help=_('Default domain ID, default=%s. '
'(Env: OS_DEFAULT_DOMAIN)') % DEFAULT_DOMAIN,
)
parser.add_argument(
'--os-interface',
metavar='<interface>',
dest='interface',
choices=['admin', 'public', 'internal'],
default=utils.env('OS_INTERFACE'),
help=_('Select an interface type.'
' Valid interface types: [admin, public, internal].'
' (Env: OS_INTERFACE)'),
)
parser.add_argument(
'--timing',
default=False,
action='store_true',
help=_("Print API call timing info"),
)
parser.add_argument(
'--os-beta-command',
action='store_true',
help=_("Enable beta commands which are subject to change"),
)
# osprofiler HMAC key argument # Set default auth type to password
if osprofiler_profiler: self._auth_type = 'password'
parser.add_argument(
'--os-profile',
metavar='hmac-key',
dest='profile',
help=_('HMAC key for encrypting profiling context data'),
)
# NOTE(dtroyer): This global option should have been named
# --os-profile as --profile interferes with at
# least one existing command option. Deprecate
# --profile and remove after Apr 2017.
parser.add_argument(
'--profile',
metavar='hmac-key',
dest='old_profile',
help=argparse.SUPPRESS,
)
return clientmanager.build_plugin_option_parser(parser) def _load_plugins(self):
"""Load plugins via stevedore
def initialize_app(self, argv): osc-lib has no opinion on what plugins should be loaded
"""Global app init bits:
* set up API versions
* validate authentication info
* authenticate against Identity if requested
""" """
# Parent __init__ parses argv into self.options
super(OpenStackShell, self).initialize_app(argv)
self.log.info("START with options: %s",
strutils.mask_password(self.command_options))
self.log.debug("options: %s",
strutils.mask_password(self.options))
# Set the default plugin to token_endpoint if url and token are given
if (self.options.url and self.options.token):
# Use service token authentication
auth_type = 'token_endpoint'
else:
auth_type = 'password'
project_id = getattr(self.options, 'project_id', None)
project_name = getattr(self.options, 'project_name', None)
tenant_id = getattr(self.options, 'tenant_id', None)
tenant_name = getattr(self.options, 'tenant_name', None)
# Save default domain
self.default_domain = self.options.default_domain
# handle some v2/v3 authentication inconsistencies by just acting like
# both the project and tenant information are both present. This can
# go away if we stop registering all the argparse options together.
if project_id and not tenant_id:
self.options.tenant_id = project_id
if project_name and not tenant_name:
self.options.tenant_name = project_name
if tenant_id and not project_id:
self.options.project_id = tenant_id
if tenant_name and not project_name:
self.options.project_name = tenant_name
# Do configuration file handling
# Ignore the default value of interface. Only if it is set later
# will it be used.
try:
cc = cloud_config.OSC_Config(
override_defaults={
'interface': None,
'auth_type': auth_type,
},
)
except (IOError, OSError):
self.log.critical("Could not read clouds.yaml configuration file")
self.print_help_if_requested()
raise
# TODO(thowe): Change cliff so the default value for debug
# can be set to None.
if not self.options.debug:
self.options.debug = None
self.cloud = cc.get_one_cloud(
cloud=self.options.cloud,
argparse=self.options,
)
self.log_configurator.configure(self.cloud)
self.dump_stack_trace = self.log_configurator.dump_trace
self.log.debug("defaults: %s", cc.defaults)
self.log.debug("cloud cfg: %s",
strutils.mask_password(self.cloud.config))
# Set up client TLS
# NOTE(dtroyer): --insecure is the non-default condition that
# overrides any verify setting in clouds.yaml
# so check it first, then fall back to any verify
# setting provided.
self.verify = not self.cloud.config.get(
'insecure',
not self.cloud.config.get('verify', True),
)
# NOTE(dtroyer): Per bug https://bugs.launchpad.net/bugs/1447784
# --insecure now overrides any --os-cacert setting,
# where before --insecure was ignored if --os-cacert
# was set.
if self.verify and self.cloud.cacert:
self.verify = self.cloud.cacert
# Loop through extensions to get API versions # Loop through extensions to get API versions
for mod in clientmanager.PLUGIN_MODULES: for mod in clientmanager.PLUGIN_MODULES:
default_version = getattr(mod, 'DEFAULT_API_VERSION', None) default_version = getattr(mod, 'DEFAULT_API_VERSION', None)
@ -406,6 +102,11 @@ class OpenStackShell(app.App):
{'name': api, 'version': version_opt, 'group': cmd_group} {'name': api, 'version': version_opt, 'group': cmd_group}
) )
def _load_commands(self):
"""Load commands via cliff/stevedore
osc-lib has no opinion on what commands should be loaded
"""
# Commands that span multiple APIs # Commands that span multiple APIs
self.command_manager.add_command_group( self.command_manager.add_command_group(
'openstack.common') 'openstack.common')
@ -422,59 +123,19 @@ class OpenStackShell(app.App):
# } # }
self.command_manager.add_command_group( self.command_manager.add_command_group(
'openstack.extension') 'openstack.extension')
# call InitializeXxx() here
# set up additional clients to stuff in to client_manager??
# Handle deferred help and exit def initialize_app(self, argv):
self.print_help_if_requested() super(OpenStackShell, self).initialize_app(argv)
# For now we need to build our own ClientManager so re-do what
# has already been done :(
# TODO(dtroyer): remove when osc-lib is fixed
self.client_manager = clientmanager.ClientManager( self.client_manager = clientmanager.ClientManager(
cli_options=self.cloud, cli_options=self.cloud,
api_version=self.api_version, api_version=self.api_version,
pw_func=prompt_for_password, pw_func=shell.prompt_for_password,
) )
def prepare_to_run_command(self, cmd):
"""Set up auth and API versions"""
self.log.info(
'command: %s -> %s.%s',
getattr(cmd, 'cmd_name', '<none>'),
cmd.__class__.__module__,
cmd.__class__.__name__,
)
if cmd.auth_required:
self.client_manager.setup_auth()
if hasattr(cmd, 'required_scope') and cmd.required_scope:
# let the command decide whether we need a scoped token
self.client_manager.validate_scope()
# Trigger the Identity client to initialize
self.client_manager.auth_ref
def clean_up(self, cmd, result, err):
self.log.debug('clean_up %s: %s', cmd.__class__.__name__, err or '')
# Process collected timing data
if self.options.timing:
# Get session data
self.timing_data.extend(
self.client_manager.session.get_timings(),
)
# Use the Timing pseudo-command to generate the output
tcmd = timing.Timing(self, self.options)
tparser = tcmd.get_parser('Timing')
# If anything other than prettytable is specified, force csv
format = 'table'
# Check the formatter used in the actual command
if hasattr(cmd, 'formatter') \
and cmd.formatter != cmd._formatter_plugins['table'].obj:
format = 'csv'
sys.stdout.write('\n')
targs = tparser.parse_args(['-f', format])
tcmd.run(targs)
def main(argv=None): def main(argv=None):
if argv is None: if argv is None:

View File

@ -13,14 +13,15 @@
# under the License. # under the License.
# #
import copy
import fixtures
import mock import mock
import os import os
import testtools import sys
from osc_lib.tests import utils as osc_lib_test_utils
from oslo_utils import importutils
import wrapt
from openstackclient import shell from openstackclient import shell
from openstackclient.tests import utils
DEFAULT_AUTH_URL = "http://127.0.0.1:5000/v2.0/" DEFAULT_AUTH_URL = "http://127.0.0.1:5000/v2.0/"
@ -116,155 +117,50 @@ global_options = {
'--os-interface': (DEFAULT_INTERFACE, True, True) '--os-interface': (DEFAULT_INTERFACE, True, True)
} }
auth_options = {
'--os-auth-url': (DEFAULT_AUTH_URL, True, True), # Wrap the osc_lib make_shell() function to set the shell class since
'--os-project-id': (DEFAULT_PROJECT_ID, True, True), # osc-lib's TestShell class doesn't allow us to specify it yet.
'--os-project-name': (DEFAULT_PROJECT_NAME, True, True), # TODO(dtroyer): remove this once the shell_class_patch patch is released
'--os-domain-id': (DEFAULT_DOMAIN_ID, True, True), # in osc-lib
'--os-domain-name': (DEFAULT_DOMAIN_NAME, True, True), def make_shell_wrapper(func, inst, args, kwargs):
'--os-user-domain-id': (DEFAULT_USER_DOMAIN_ID, True, True), if 'shell_class' not in kwargs:
'--os-user-domain-name': (DEFAULT_USER_DOMAIN_NAME, True, True), kwargs['shell_class'] = shell.OpenStackShell
'--os-project-domain-id': (DEFAULT_PROJECT_DOMAIN_ID, True, True), return func(*args, **kwargs)
'--os-project-domain-name': (DEFAULT_PROJECT_DOMAIN_NAME, True, True),
'--os-username': (DEFAULT_USERNAME, True, True),
'--os-password': (DEFAULT_PASSWORD, True, True),
'--os-region-name': (DEFAULT_REGION_NAME, True, True),
'--os-trust-id': ("1234", True, True),
'--os-auth-type': ("v2password", True, True),
'--os-token': (DEFAULT_TOKEN, True, True),
'--os-url': (DEFAULT_SERVICE_URL, True, True),
'--os-interface': (DEFAULT_INTERFACE, True, True),
}
def opt2attr(opt): wrapt.wrap_function_wrapper(
if opt.startswith('--os-'): osc_lib_test_utils,
attr = opt[5:] 'make_shell',
elif opt.startswith('--'): make_shell_wrapper,
attr = opt[2:] )
else:
attr = opt
return attr.lower().replace('-', '_')
def opt2env(opt): class TestShell(osc_lib_test_utils.TestShell):
return opt[2:].upper().replace('-', '_')
# Full name of the OpenStackShell class to test (cliff.app.App subclass)
shell_class_name = "openstackclient.shell.OpenStackShell"
def make_shell(): # TODO(dtroyer): remove this once the shell_class_patch patch is released
"""Create a new command shell and mock out some bits.""" # in osc-lib
_shell = shell.OpenStackShell() app_patch = shell_class_name
_shell.command_manager = mock.Mock()
return _shell
def fake_execute(shell, cmd):
"""Pretend to execute shell commands."""
return shell.run(cmd.split())
class EnvFixture(fixtures.Fixture):
"""Environment Fixture.
This fixture replaces os.environ with provided env or an empty env.
"""
def __init__(self, env=None):
self.new_env = env or {}
def _setUp(self):
self.orig_env, os.environ = os.environ, self.new_env
self.addCleanup(self.revert)
def revert(self):
os.environ = self.orig_env
class TestShell(utils.TestCase):
def setUp(self): def setUp(self):
super(TestShell, self).setUp() super(TestShell, self).setUp()
patch = "openstackclient.shell.OpenStackShell.run_subcommand" # TODO(dtroyer): remove this once the shell_class_patch patch is
self.cmd_patch = mock.patch(patch) # released in osc-lib
self.cmd_save = self.cmd_patch.start() self.shell_class = importutils.import_class(self.shell_class_name)
self.addCleanup(self.cmd_patch.stop)
self.app = mock.Mock("Test Shell")
def _assert_initialize_app_arg(self, cmd_options, default_args):
"""Check the args passed to initialize_app()
The argv argument to initialize_app() is the remainder from parsing
global options declared in both cliff.app and
openstackclient.OpenStackShell build_option_parser(). Any global
options passed on the commmad line should not be in argv but in
_shell.options.
"""
with mock.patch(
"openstackclient.shell.OpenStackShell.initialize_app",
self.app,
):
_shell, _cmd = make_shell(), cmd_options + " list project"
fake_execute(_shell, _cmd)
self.app.assert_called_with(["list", "project"])
for k in default_args.keys():
self.assertEqual(
default_args[k],
vars(_shell.options)[k],
"%s does not match" % k,
)
def _assert_cloud_config_arg(self, cmd_options, default_args):
"""Check the args passed to cloud_config.get_one_cloud()
The argparse argument to get_one_cloud() is an argparse.Namespace
object that contains all of the options processed to this point in
initialize_app().
"""
cloud = mock.Mock(name="cloudy")
cloud.config = {}
self.occ_get_one = mock.Mock(return_value=cloud)
with mock.patch(
"os_client_config.config.OpenStackConfig.get_one_cloud",
self.occ_get_one,
):
_shell, _cmd = make_shell(), cmd_options + " list project"
fake_execute(_shell, _cmd)
opts = self.occ_get_one.call_args[1]['argparse']
for k in default_args.keys():
self.assertEqual(
default_args[k],
vars(opts)[k],
"%s does not match" % k,
)
def _assert_token_auth(self, cmd_options, default_args):
with mock.patch("openstackclient.shell.OpenStackShell.initialize_app",
self.app):
_shell, _cmd = make_shell(), cmd_options + " list role"
fake_execute(_shell, _cmd)
self.app.assert_called_with(["list", "role"])
self.assertEqual(
default_args.get("token", ''),
_shell.options.token,
"token"
)
self.assertEqual(
default_args.get("auth_url", ''),
_shell.options.auth_url,
"auth_url"
)
def _assert_token_endpoint_auth(self, cmd_options, default_args): def _assert_token_endpoint_auth(self, cmd_options, default_args):
with mock.patch("openstackclient.shell.OpenStackShell.initialize_app", with mock.patch(
self.app): self.shell_class_name + ".initialize_app",
_shell, _cmd = make_shell(), cmd_options + " list role" self.app,
fake_execute(_shell, _cmd) ):
_shell = osc_lib_test_utils.make_shell(
shell_class=self.shell_class,
)
_cmd = cmd_options + " list role"
osc_lib_test_utils.fake_execute(_shell, _cmd)
print("_shell: %s" % _shell)
self.app.assert_called_with(["list", "role"]) self.app.assert_called_with(["list", "role"])
self.assertEqual( self.assertEqual(
@ -278,11 +174,40 @@ class TestShell(utils.TestCase):
"url", "url",
) )
def _assert_token_auth(self, cmd_options, default_args):
with mock.patch(
self.app_patch + ".initialize_app",
self.app,
):
_shell = osc_lib_test_utils.make_shell(
shell_class=self.shell_class,
)
_cmd = cmd_options + " list role"
osc_lib_test_utils.fake_execute(_shell, _cmd)
print("_shell: %s" % _shell)
self.app.assert_called_with(["list", "role"])
self.assertEqual(
default_args.get("token", ''),
_shell.options.token,
"token"
)
self.assertEqual(
default_args.get("auth_url", ''),
_shell.options.auth_url,
"auth_url"
)
def _assert_cli(self, cmd_options, default_args): def _assert_cli(self, cmd_options, default_args):
with mock.patch("openstackclient.shell.OpenStackShell.initialize_app", with mock.patch(
self.app): self.shell_class_name + ".initialize_app",
_shell, _cmd = make_shell(), cmd_options + " list server" self.app,
fake_execute(_shell, _cmd) ):
_shell = osc_lib_test_utils.make_shell(
shell_class=self.shell_class,
)
_cmd = cmd_options + " list server"
osc_lib_test_utils.fake_execute(_shell, _cmd)
self.app.assert_called_with(["list", "server"]) self.app.assert_called_with(["list", "server"])
self.assertEqual(default_args["compute_api_version"], self.assertEqual(default_args["compute_api_version"],
@ -297,39 +222,17 @@ class TestShell(utils.TestCase):
_shell.options.os_network_api_version) _shell.options.os_network_api_version)
class TestShellHelp(TestShell):
"""Test the deferred help flag"""
def setUp(self):
super(TestShellHelp, self).setUp()
self.useFixture(EnvFixture())
@testtools.skip("skip until bug 1444983 is resolved")
def test_help_options(self):
flag = "-h list server"
kwargs = {
"deferred_help": True,
}
with mock.patch("openstackclient.shell.OpenStackShell.initialize_app",
self.app):
_shell, _cmd = make_shell(), flag
fake_execute(_shell, _cmd)
self.assertEqual(kwargs["deferred_help"],
_shell.options.deferred_help)
class TestShellOptions(TestShell): class TestShellOptions(TestShell):
def setUp(self): def setUp(self):
super(TestShellOptions, self).setUp() super(TestShellOptions, self).setUp()
self.useFixture(EnvFixture()) self.useFixture(osc_lib_test_utils.EnvFixture())
def _test_options_init_app(self, test_opts): def _test_options_init_app(self, test_opts):
for opt in test_opts.keys(): for opt in test_opts.keys():
if not test_opts[opt][1]: if not test_opts[opt][1]:
continue continue
key = opt2attr(opt) key = osc_lib_test_utils.opt2attr(opt)
if isinstance(test_opts[opt][0], str): if isinstance(test_opts[opt][0], str):
cmd = opt + " " + test_opts[opt][0] cmd = opt + " " + test_opts[opt][0]
else: else:
@ -343,7 +246,7 @@ class TestShellOptions(TestShell):
for opt in test_opts.keys(): for opt in test_opts.keys():
if not test_opts[opt][1]: if not test_opts[opt][1]:
continue continue
key = opt2attr(opt) key = osc_lib_test_utils.opt2attr(opt)
if isinstance(test_opts[opt][0], str): if isinstance(test_opts[opt][0], str):
cmd = opt + " " + test_opts[opt][0] cmd = opt + " " + test_opts[opt][0]
else: else:
@ -357,12 +260,12 @@ class TestShellOptions(TestShell):
for opt in test_opts.keys(): for opt in test_opts.keys():
if not test_opts[opt][2]: if not test_opts[opt][2]:
continue continue
key = opt2attr(opt) key = osc_lib_test_utils.opt2attr(opt)
kwargs = { kwargs = {
key: test_opts[opt][0], key: test_opts[opt][0],
} }
env = { env = {
opt2env(opt): test_opts[opt][0], osc_lib_test_utils.opt2env(opt): test_opts[opt][0],
} }
os.environ = env.copy() os.environ = env.copy()
self._assert_initialize_app_arg("", kwargs) self._assert_initialize_app_arg("", kwargs)
@ -371,37 +274,16 @@ class TestShellOptions(TestShell):
for opt in test_opts.keys(): for opt in test_opts.keys():
if not test_opts[opt][2]: if not test_opts[opt][2]:
continue continue
key = opt2attr(opt) key = osc_lib_test_utils.opt2attr(opt)
kwargs = { kwargs = {
key: test_opts[opt][0], key: test_opts[opt][0],
} }
env = { env = {
opt2env(opt): test_opts[opt][0], osc_lib_test_utils.opt2env(opt): test_opts[opt][0],
} }
os.environ = env.copy() os.environ = env.copy()
self._assert_cloud_config_arg("", kwargs) self._assert_cloud_config_arg("", kwargs)
def test_empty_auth(self):
os.environ = {}
self._assert_initialize_app_arg("", {})
self._assert_cloud_config_arg("", {})
def test_global_options(self):
self._test_options_init_app(global_options)
self._test_options_get_one_cloud(global_options)
def test_auth_options(self):
self._test_options_init_app(auth_options)
self._test_options_get_one_cloud(auth_options)
def test_global_env(self):
self._test_env_init_app(global_options)
self._test_env_get_one_cloud(global_options)
def test_auth_env(self):
self._test_env_init_app(auth_options)
self._test_env_get_one_cloud(auth_options)
class TestShellTokenAuthEnv(TestShell): class TestShellTokenAuthEnv(TestShell):
@ -411,7 +293,7 @@ class TestShellTokenAuthEnv(TestShell):
"OS_TOKEN": DEFAULT_TOKEN, "OS_TOKEN": DEFAULT_TOKEN,
"OS_AUTH_URL": DEFAULT_AUTH_URL, "OS_AUTH_URL": DEFAULT_AUTH_URL,
} }
self.useFixture(EnvFixture(env.copy())) self.useFixture(osc_lib_test_utils.EnvFixture(env.copy()))
def test_env(self): def test_env(self):
flag = "" flag = ""
@ -455,7 +337,7 @@ class TestShellTokenEndpointAuthEnv(TestShell):
"OS_TOKEN": DEFAULT_TOKEN, "OS_TOKEN": DEFAULT_TOKEN,
"OS_URL": DEFAULT_SERVICE_URL, "OS_URL": DEFAULT_SERVICE_URL,
} }
self.useFixture(EnvFixture(env.copy())) self.useFixture(osc_lib_test_utils.EnvFixture(env.copy()))
def test_env(self): def test_env(self):
flag = "" flag = ""
@ -463,7 +345,7 @@ class TestShellTokenEndpointAuthEnv(TestShell):
"token": DEFAULT_TOKEN, "token": DEFAULT_TOKEN,
"url": DEFAULT_SERVICE_URL, "url": DEFAULT_SERVICE_URL,
} }
self._assert_token_auth(flag, kwargs) self._assert_token_endpoint_auth(flag, kwargs)
def test_only_token(self): def test_only_token(self):
flag = "--os-token xyzpdq" flag = "--os-token xyzpdq"
@ -502,85 +384,7 @@ class TestShellCli(TestShell):
"OS_VOLUME_API_VERSION": DEFAULT_VOLUME_API_VERSION, "OS_VOLUME_API_VERSION": DEFAULT_VOLUME_API_VERSION,
"OS_NETWORK_API_VERSION": DEFAULT_NETWORK_API_VERSION, "OS_NETWORK_API_VERSION": DEFAULT_NETWORK_API_VERSION,
} }
self.useFixture(EnvFixture(env.copy())) self.useFixture(osc_lib_test_utils.EnvFixture(env.copy()))
def test_shell_args_no_options(self):
_shell = make_shell()
with mock.patch("openstackclient.shell.OpenStackShell.initialize_app",
self.app):
fake_execute(_shell, "list user")
self.app.assert_called_with(["list", "user"])
def test_shell_args_ca_options(self):
_shell = make_shell()
# NOTE(dtroyer): The commented out asserts below are the desired
# behaviour and will be uncommented when the
# handling for --verify and --insecure is fixed.
# Default
fake_execute(_shell, "list user")
self.assertIsNone(_shell.options.verify)
self.assertIsNone(_shell.options.insecure)
self.assertEqual('', _shell.options.cacert)
self.assertTrue(_shell.verify)
# --verify
fake_execute(_shell, "--verify list user")
self.assertTrue(_shell.options.verify)
self.assertIsNone(_shell.options.insecure)
self.assertEqual('', _shell.options.cacert)
self.assertTrue(_shell.verify)
# --insecure
fake_execute(_shell, "--insecure list user")
self.assertIsNone(_shell.options.verify)
self.assertTrue(_shell.options.insecure)
self.assertEqual('', _shell.options.cacert)
self.assertFalse(_shell.verify)
# --os-cacert
fake_execute(_shell, "--os-cacert foo list user")
self.assertIsNone(_shell.options.verify)
self.assertIsNone(_shell.options.insecure)
self.assertEqual('foo', _shell.options.cacert)
self.assertTrue(_shell.verify)
# --os-cacert and --verify
fake_execute(_shell, "--os-cacert foo --verify list user")
self.assertTrue(_shell.options.verify)
self.assertIsNone(_shell.options.insecure)
self.assertEqual('foo', _shell.options.cacert)
self.assertTrue(_shell.verify)
# --os-cacert and --insecure
# NOTE(dtroyer): Per bug https://bugs.launchpad.net/bugs/1447784
# in this combination --insecure now overrides any
# --os-cacert setting, where before --insecure
# was ignored if --os-cacert was set.
fake_execute(_shell, "--os-cacert foo --insecure list user")
self.assertIsNone(_shell.options.verify)
self.assertTrue(_shell.options.insecure)
self.assertEqual('foo', _shell.options.cacert)
self.assertFalse(_shell.verify)
def test_shell_args_cert_options(self):
_shell = make_shell()
# Default
fake_execute(_shell, "list user")
self.assertEqual('', _shell.options.cert)
self.assertEqual('', _shell.options.key)
# --os-cert
fake_execute(_shell, "--os-cert mycert list user")
self.assertEqual('mycert', _shell.options.cert)
self.assertEqual('', _shell.options.key)
# --os-key
fake_execute(_shell, "--os-key mickey list user")
self.assertEqual('', _shell.options.cert)
self.assertEqual('mickey', _shell.options.key)
def test_default_env(self): def test_default_env(self):
flag = "" flag = ""
@ -605,220 +409,34 @@ class TestShellCli(TestShell):
} }
self._assert_cli(flag, kwargs) self._assert_cli(flag, kwargs)
@mock.patch("os_client_config.config.OpenStackConfig._load_config_file")
def test_shell_args_cloud_no_vendor(self, config_mock):
config_mock.return_value = ('file.yaml', copy.deepcopy(CLOUD_1))
_shell = make_shell()
fake_execute( class TestShellArgV(TestShell):
_shell, """Test the deferred help flag"""
"--os-cloud scc list user",
)
self.assertEqual(
'scc',
_shell.cloud.name,
)
# These come from clouds.yaml
self.assertEqual(
DEFAULT_AUTH_URL,
_shell.cloud.config['auth']['auth_url'],
)
self.assertEqual(
DEFAULT_PROJECT_NAME,
_shell.cloud.config['auth']['project_name'],
)
self.assertEqual(
'zaphod',
_shell.cloud.config['auth']['username'],
)
self.assertEqual(
'occ-cloud',
_shell.cloud.config['region_name'],
)
self.assertEqual(
'glazed',
_shell.cloud.config['donut'],
)
self.assertEqual(
'public',
_shell.cloud.config['interface'],
)
@mock.patch("os_client_config.config.OpenStackConfig._load_vendor_file")
@mock.patch("os_client_config.config.OpenStackConfig._load_config_file")
def test_shell_args_cloud_public(self, config_mock, public_mock):
config_mock.return_value = ('file.yaml', copy.deepcopy(CLOUD_2))
public_mock.return_value = ('file.yaml', copy.deepcopy(PUBLIC_1))
_shell = make_shell()
fake_execute(
_shell,
"--os-cloud megacloud list user",
)
self.assertEqual(
'megacloud',
_shell.cloud.name,
)
# These come from clouds-public.yaml
self.assertEqual(
DEFAULT_AUTH_URL,
_shell.cloud.config['auth']['auth_url'],
)
self.assertEqual(
'cake',
_shell.cloud.config['donut'],
)
# These come from clouds.yaml
self.assertEqual(
'heart-o-gold',
_shell.cloud.config['auth']['project_name'],
)
self.assertEqual(
'zaphod',
_shell.cloud.config['auth']['username'],
)
self.assertEqual(
'occ-cloud',
_shell.cloud.config['region_name'],
)
self.assertEqual('mycert', _shell.cloud.config['cert'])
self.assertEqual('mickey', _shell.cloud.config['key'])
@mock.patch("os_client_config.config.OpenStackConfig._load_vendor_file")
@mock.patch("os_client_config.config.OpenStackConfig._load_config_file")
def test_shell_args_precedence(self, config_mock, vendor_mock):
config_mock.return_value = ('file.yaml', copy.deepcopy(CLOUD_2))
vendor_mock.return_value = ('file.yaml', copy.deepcopy(PUBLIC_1))
_shell = make_shell()
# Test command option overriding config file value
fake_execute(
_shell,
"--os-cloud megacloud --os-region-name krikkit list user",
)
self.assertEqual(
'megacloud',
_shell.cloud.name,
)
# These come from clouds-public.yaml
self.assertEqual(
DEFAULT_AUTH_URL,
_shell.cloud.config['auth']['auth_url'],
)
self.assertEqual(
'cake',
_shell.cloud.config['donut'],
)
# These come from clouds.yaml
self.assertEqual(
'heart-o-gold',
_shell.cloud.config['auth']['project_name'],
)
self.assertEqual(
'zaphod',
_shell.cloud.config['auth']['username'],
)
self.assertEqual(
'krikkit',
_shell.cloud.config['region_name'],
)
class TestShellCliEnv(TestShell):
def setUp(self): def setUp(self):
super(TestShellCliEnv, self).setUp() super(TestShellArgV, self).setUp()
env = {
'OS_REGION_NAME': 'occ-env',
}
self.useFixture(EnvFixture(env.copy()))
@mock.patch("os_client_config.config.OpenStackConfig._load_vendor_file") def test_shell_argv(self):
@mock.patch("os_client_config.config.OpenStackConfig._load_config_file") """Test argv decoding
def test_shell_args_precedence_1(self, config_mock, vendor_mock):
config_mock.return_value = ('file.yaml', copy.deepcopy(CLOUD_2))
vendor_mock.return_value = ('file.yaml', copy.deepcopy(PUBLIC_1))
_shell = make_shell()
# Test env var Python 2 does nothing with argv while Python 3 decodes it into
fake_execute( Unicode before we ever see it. We manually decode when running
_shell, under Python 2 so verify that we get the right argv types.
"--os-cloud megacloud list user",
)
self.assertEqual(
'megacloud',
_shell.cloud.name,
)
# These come from clouds-public.yaml Use the argv supplied by the test runner so we get actual Python
self.assertEqual( runtime behaviour; we only need to check the type of argv[0]
DEFAULT_AUTH_URL, which will alwyas be present.
_shell.cloud.config['auth']['auth_url'], """
)
self.assertEqual(
'cake',
_shell.cloud.config['donut'],
)
# These come from clouds.yaml with mock.patch(
self.assertEqual( self.shell_class_name + ".run",
'heart-o-gold', self.app,
_shell.cloud.config['auth']['project_name'], ):
) # Ensure type gets through unmolested through shell.main()
self.assertEqual( argv = sys.argv
'zaphod', shell.main(sys.argv)
_shell.cloud.config['auth']['username'], self.assertEqual(type(argv[0]), type(self.app.call_args[0][0][0]))
)
self.assertEqual(
'occ-env',
_shell.cloud.config['region_name'],
)
@mock.patch("os_client_config.config.OpenStackConfig._load_vendor_file") # When shell.main() gets sys.argv itself it should be decoded
@mock.patch("os_client_config.config.OpenStackConfig._load_config_file") shell.main()
def test_shell_args_precedence_2(self, config_mock, vendor_mock): self.assertEqual(type(u'x'), type(self.app.call_args[0][0][0]))
config_mock.return_value = ('file.yaml', copy.deepcopy(CLOUD_2))
vendor_mock.return_value = ('file.yaml', copy.deepcopy(PUBLIC_1))
_shell = make_shell()
# Test command option overriding config file value
fake_execute(
_shell,
"--os-cloud megacloud --os-region-name krikkit list user",
)
self.assertEqual(
'megacloud',
_shell.cloud.name,
)
# These come from clouds-public.yaml
self.assertEqual(
DEFAULT_AUTH_URL,
_shell.cloud.config['auth']['auth_url'],
)
self.assertEqual(
'cake',
_shell.cloud.config['donut'],
)
# These come from clouds.yaml
self.assertEqual(
'heart-o-gold',
_shell.cloud.config['auth']['project_name'],
)
self.assertEqual(
'zaphod',
_shell.cloud.config['auth']['username'],
)
# These come from the command line
self.assertEqual(
'krikkit',
_shell.cloud.config['region_name'],
)

View File

@ -57,13 +57,13 @@ def make_client(instance):
extensions = [extension.Extension('list_extensions', list_extensions)] extensions = [extension.Extension('list_extensions', list_extensions)]
# Remember interface only if it is set # Remember interface only if it is set
kwargs = utils.build_kwargs_dict('endpoint_type', instance._interface) kwargs = utils.build_kwargs_dict('endpoint_type', instance.interface)
client = volume_client( client = volume_client(
session=instance.session, session=instance.session,
extensions=extensions, extensions=extensions,
http_log_debug=http_log_debug, http_log_debug=http_log_debug,
region_name=instance._region_name, region_name=instance.region_name,
**kwargs **kwargs
) )

View File

@ -17,6 +17,7 @@ testtools>=1.4.0 # MIT
tempest>=12.1.0 # Apache-2.0 tempest>=12.1.0 # Apache-2.0
osprofiler>=1.3.0 # Apache-2.0 osprofiler>=1.3.0 # Apache-2.0
bandit>=1.0.1 # Apache-2.0 bandit>=1.0.1 # Apache-2.0
wrapt>=1.7.0 # BSD License
# Install these to generate sphinx autodocs # Install these to generate sphinx autodocs
aodhclient>=0.5.0 # Apache-2.0 aodhclient>=0.5.0 # Apache-2.0