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
setattr(
ClientManager,
clientmanager.ClientManager,
module.API_NAME,
clientmanager.ClientCache(
getattr(sys.modules[ep.module_name], 'make_client', None)

View File

@ -64,7 +64,7 @@ def make_client(instance):
if ext.name == "list_extensions"]
# 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(
version,
@ -72,7 +72,7 @@ def make_client(instance):
extensions=extensions,
http_log_debug=http_log_debug,
timings=instance.timing,
region_name=instance._region_name,
region_name=instance.region_name,
**kwargs
)

View File

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

View File

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

View File

@ -34,9 +34,9 @@ API_VERSIONS = {
def make_client(instance):
"""Returns a network proxy"""
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_interface(API_NAME, instance._interface)
prof.set_interface(API_NAME, instance.interface)
conn = connection.Connection(authenticator=instance.session.auth,
verify=instance.session.verify,
cert=instance.session.cert,

View File

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

View File

@ -16,30 +16,17 @@
"""Command-line interface to the OpenStack APIs"""
import argparse
import getpass
import locale
import logging
import six
import sys
import traceback
from cliff import app
from cliff import command
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 osc_lib.api import auth
from osc_lib import shell
from oslo_utils import importutils
from oslo_utils import strutils
import six
import openstackclient
from openstackclient.common import clientmanager
from openstackclient.common import commandmanager
from openstackclient.i18n import _
osprofiler_profiler = importutils.try_import("osprofiler.profiler")
@ -47,47 +34,9 @@ osprofiler_profiler = importutils.try_import("osprofiler.profiler")
DEFAULT_DOMAIN = 'default'
def prompt_for_password(prompt=None):
"""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 = []
class OpenStackShell(shell.OpenStackShell):
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__(
description=__doc__.strip(),
@ -97,281 +46,28 @@ class OpenStackShell(app.App):
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
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):
parser = super(OpenStackShell, self).build_option_parser(
description,
version)
parser = clientmanager.build_plugin_option_parser(parser)
parser = auth.build_auth_plugins_option_parser(parser)
return parser
# service token auth argument
parser.add_argument(
'--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"),
)
def _final_defaults(self):
super(OpenStackShell, self)._final_defaults()
# osprofiler HMAC key argument
if osprofiler_profiler:
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,
)
# Set default auth type to password
self._auth_type = 'password'
return clientmanager.build_plugin_option_parser(parser)
def _load_plugins(self):
"""Load plugins via stevedore
def initialize_app(self, argv):
"""Global app init bits:
* set up API versions
* validate authentication info
* authenticate against Identity if requested
osc-lib has no opinion on what plugins should be loaded
"""
# 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
for mod in clientmanager.PLUGIN_MODULES:
default_version = getattr(mod, 'DEFAULT_API_VERSION', None)
@ -406,6 +102,11 @@ class OpenStackShell(app.App):
{'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
self.command_manager.add_command_group(
'openstack.common')
@ -422,59 +123,19 @@ class OpenStackShell(app.App):
# }
self.command_manager.add_command_group(
'openstack.extension')
# call InitializeXxx() here
# set up additional clients to stuff in to client_manager??
# Handle deferred help and exit
self.print_help_if_requested()
def initialize_app(self, argv):
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(
cli_options=self.cloud,
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):
if argv is None:

View File

@ -13,14 +13,15 @@
# under the License.
#
import copy
import fixtures
import mock
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.tests import utils
DEFAULT_AUTH_URL = "http://127.0.0.1:5000/v2.0/"
@ -116,155 +117,50 @@ global_options = {
'--os-interface': (DEFAULT_INTERFACE, True, True)
}
auth_options = {
'--os-auth-url': (DEFAULT_AUTH_URL, True, True),
'--os-project-id': (DEFAULT_PROJECT_ID, True, True),
'--os-project-name': (DEFAULT_PROJECT_NAME, True, True),
'--os-domain-id': (DEFAULT_DOMAIN_ID, True, True),
'--os-domain-name': (DEFAULT_DOMAIN_NAME, True, True),
'--os-user-domain-id': (DEFAULT_USER_DOMAIN_ID, True, True),
'--os-user-domain-name': (DEFAULT_USER_DOMAIN_NAME, True, True),
'--os-project-domain-id': (DEFAULT_PROJECT_DOMAIN_ID, True, True),
'--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),
}
# Wrap the osc_lib make_shell() function to set the shell class since
# osc-lib's TestShell class doesn't allow us to specify it yet.
# TODO(dtroyer): remove this once the shell_class_patch patch is released
# in osc-lib
def make_shell_wrapper(func, inst, args, kwargs):
if 'shell_class' not in kwargs:
kwargs['shell_class'] = shell.OpenStackShell
return func(*args, **kwargs)
def opt2attr(opt):
if opt.startswith('--os-'):
attr = opt[5:]
elif opt.startswith('--'):
attr = opt[2:]
else:
attr = opt
return attr.lower().replace('-', '_')
wrapt.wrap_function_wrapper(
osc_lib_test_utils,
'make_shell',
make_shell_wrapper,
)
def opt2env(opt):
return opt[2:].upper().replace('-', '_')
class TestShell(osc_lib_test_utils.TestShell):
# Full name of the OpenStackShell class to test (cliff.app.App subclass)
shell_class_name = "openstackclient.shell.OpenStackShell"
def make_shell():
"""Create a new command shell and mock out some bits."""
_shell = shell.OpenStackShell()
_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):
# TODO(dtroyer): remove this once the shell_class_patch patch is released
# in osc-lib
app_patch = shell_class_name
def setUp(self):
super(TestShell, self).setUp()
patch = "openstackclient.shell.OpenStackShell.run_subcommand"
self.cmd_patch = mock.patch(patch)
self.cmd_save = self.cmd_patch.start()
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"
)
# TODO(dtroyer): remove this once the shell_class_patch patch is
# released in osc-lib
self.shell_class = importutils.import_class(self.shell_class_name)
def _assert_token_endpoint_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)
with mock.patch(
self.shell_class_name + ".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(
@ -278,11 +174,40 @@ class TestShell(utils.TestCase):
"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):
with mock.patch("openstackclient.shell.OpenStackShell.initialize_app",
self.app):
_shell, _cmd = make_shell(), cmd_options + " list server"
fake_execute(_shell, _cmd)
with mock.patch(
self.shell_class_name + ".initialize_app",
self.app,
):
_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.assertEqual(default_args["compute_api_version"],
@ -297,39 +222,17 @@ class TestShell(utils.TestCase):
_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):
def setUp(self):
super(TestShellOptions, self).setUp()
self.useFixture(EnvFixture())
self.useFixture(osc_lib_test_utils.EnvFixture())
def _test_options_init_app(self, test_opts):
for opt in test_opts.keys():
if not test_opts[opt][1]:
continue
key = opt2attr(opt)
key = osc_lib_test_utils.opt2attr(opt)
if isinstance(test_opts[opt][0], str):
cmd = opt + " " + test_opts[opt][0]
else:
@ -343,7 +246,7 @@ class TestShellOptions(TestShell):
for opt in test_opts.keys():
if not test_opts[opt][1]:
continue
key = opt2attr(opt)
key = osc_lib_test_utils.opt2attr(opt)
if isinstance(test_opts[opt][0], str):
cmd = opt + " " + test_opts[opt][0]
else:
@ -357,12 +260,12 @@ class TestShellOptions(TestShell):
for opt in test_opts.keys():
if not test_opts[opt][2]:
continue
key = opt2attr(opt)
key = osc_lib_test_utils.opt2attr(opt)
kwargs = {
key: test_opts[opt][0],
}
env = {
opt2env(opt): test_opts[opt][0],
osc_lib_test_utils.opt2env(opt): test_opts[opt][0],
}
os.environ = env.copy()
self._assert_initialize_app_arg("", kwargs)
@ -371,37 +274,16 @@ class TestShellOptions(TestShell):
for opt in test_opts.keys():
if not test_opts[opt][2]:
continue
key = opt2attr(opt)
key = osc_lib_test_utils.opt2attr(opt)
kwargs = {
key: test_opts[opt][0],
}
env = {
opt2env(opt): test_opts[opt][0],
osc_lib_test_utils.opt2env(opt): test_opts[opt][0],
}
os.environ = env.copy()
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):
@ -411,7 +293,7 @@ class TestShellTokenAuthEnv(TestShell):
"OS_TOKEN": DEFAULT_TOKEN,
"OS_AUTH_URL": DEFAULT_AUTH_URL,
}
self.useFixture(EnvFixture(env.copy()))
self.useFixture(osc_lib_test_utils.EnvFixture(env.copy()))
def test_env(self):
flag = ""
@ -455,7 +337,7 @@ class TestShellTokenEndpointAuthEnv(TestShell):
"OS_TOKEN": DEFAULT_TOKEN,
"OS_URL": DEFAULT_SERVICE_URL,
}
self.useFixture(EnvFixture(env.copy()))
self.useFixture(osc_lib_test_utils.EnvFixture(env.copy()))
def test_env(self):
flag = ""
@ -463,7 +345,7 @@ class TestShellTokenEndpointAuthEnv(TestShell):
"token": DEFAULT_TOKEN,
"url": DEFAULT_SERVICE_URL,
}
self._assert_token_auth(flag, kwargs)
self._assert_token_endpoint_auth(flag, kwargs)
def test_only_token(self):
flag = "--os-token xyzpdq"
@ -502,85 +384,7 @@ class TestShellCli(TestShell):
"OS_VOLUME_API_VERSION": DEFAULT_VOLUME_API_VERSION,
"OS_NETWORK_API_VERSION": DEFAULT_NETWORK_API_VERSION,
}
self.useFixture(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)
self.useFixture(osc_lib_test_utils.EnvFixture(env.copy()))
def test_default_env(self):
flag = ""
@ -605,220 +409,34 @@ class TestShellCli(TestShell):
}
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(
_shell,
"--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):
class TestShellArgV(TestShell):
"""Test the deferred help flag"""
def setUp(self):
super(TestShellCliEnv, self).setUp()
env = {
'OS_REGION_NAME': 'occ-env',
}
self.useFixture(EnvFixture(env.copy()))
super(TestShellArgV, self).setUp()
@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_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()
def test_shell_argv(self):
"""Test argv decoding
# Test env var
fake_execute(
_shell,
"--os-cloud megacloud list user",
)
self.assertEqual(
'megacloud',
_shell.cloud.name,
)
Python 2 does nothing with argv while Python 3 decodes it into
Unicode before we ever see it. We manually decode when running
under Python 2 so verify that we get the right argv types.
# These come from clouds-public.yaml
self.assertEqual(
DEFAULT_AUTH_URL,
_shell.cloud.config['auth']['auth_url'],
)
self.assertEqual(
'cake',
_shell.cloud.config['donut'],
)
Use the argv supplied by the test runner so we get actual Python
runtime behaviour; we only need to check the type of argv[0]
which will alwyas be present.
"""
# 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-env',
_shell.cloud.config['region_name'],
)
with mock.patch(
self.shell_class_name + ".run",
self.app,
):
# Ensure type gets through unmolested through shell.main()
argv = sys.argv
shell.main(sys.argv)
self.assertEqual(type(argv[0]), type(self.app.call_args[0][0][0]))
@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_2(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'],
)
# These come from the command line
self.assertEqual(
'krikkit',
_shell.cloud.config['region_name'],
)
# When shell.main() gets sys.argv itself it should be decoded
shell.main()
self.assertEqual(type(u'x'), type(self.app.call_args[0][0][0]))

View File

@ -57,13 +57,13 @@ def make_client(instance):
extensions = [extension.Extension('list_extensions', list_extensions)]
# 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(
session=instance.session,
extensions=extensions,
http_log_debug=http_log_debug,
region_name=instance._region_name,
region_name=instance.region_name,
**kwargs
)

View File

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