diff options
Diffstat (limited to 'openstackclient')
22 files changed, 702 insertions, 480 deletions
diff --git a/openstackclient/api/auth.py b/openstackclient/api/auth.py index ba51bee1..9fb26e71 100644 --- a/openstackclient/api/auth.py +++ b/openstackclient/api/auth.py @@ -27,29 +27,49 @@ from openstackclient.i18n import _ LOG = logging.getLogger(__name__) - # Initialize the list of Authentication plugins early in order # to get the command-line options -PLUGIN_LIST = stevedore.ExtensionManager( - base.PLUGIN_NAMESPACE, - invoke_on_load=False, - propagate_map_exceptions=True, -) +PLUGIN_LIST = None -# Get the command line options so the help action has them available +# List of plugin command line options OPTIONS_LIST = {} -for plugin in PLUGIN_LIST: - for o in plugin.plugin.get_options(): - os_name = o.dest.lower().replace('_', '-') - os_env_name = 'OS_' + os_name.upper().replace('-', '_') - OPTIONS_LIST.setdefault(os_name, {'env': os_env_name, 'help': ''}) - # TODO(mhu) simplistic approach, would be better to only add - # help texts if they vary from one auth plugin to another - # also the text rendering is ugly in the CLI ... - OPTIONS_LIST[os_name]['help'] += 'With %s: %s\n' % ( - plugin.name, - o.help, + + +def get_plugin_list(): + """Gather plugin list and cache it""" + + global PLUGIN_LIST + + if PLUGIN_LIST is None: + PLUGIN_LIST = stevedore.ExtensionManager( + base.PLUGIN_NAMESPACE, + invoke_on_load=False, + propagate_map_exceptions=True, ) + return PLUGIN_LIST + + +def get_options_list(): + """Gather plugin options so the help action has them available""" + + global OPTIONS_LIST + + if not OPTIONS_LIST: + for plugin in get_plugin_list(): + for o in plugin.plugin.get_options(): + os_name = o.dest.lower().replace('_', '-') + os_env_name = 'OS_' + os_name.upper().replace('-', '_') + OPTIONS_LIST.setdefault( + os_name, {'env': os_env_name, 'help': ''}, + ) + # TODO(mhu) simplistic approach, would be better to only add + # help texts if they vary from one auth plugin to another + # also the text rendering is ugly in the CLI ... + OPTIONS_LIST[os_name]['help'] += 'With %s: %s\n' % ( + plugin.name, + o.help, + ) + return OPTIONS_LIST def select_auth_plugin(options): @@ -57,25 +77,27 @@ def select_auth_plugin(options): auth_plugin_name = None - if options.os_auth_type in [plugin.name for plugin in PLUGIN_LIST]: - # A direct plugin name was given, use it - return options.os_auth_type - - if options.os_url and options.os_token: + # Do the token/url check first as this must override the default + # 'password' set by os-client-config + # Also, url and token are not copied into o-c-c's auth dict (yet?) + if options.auth.get('url', None) and options.auth.get('token', None): # service token authentication auth_plugin_name = 'token_endpoint' - elif options.os_username: - if options.os_identity_api_version == '3': + elif options.auth_type in [plugin.name for plugin in PLUGIN_LIST]: + # A direct plugin name was given, use it + auth_plugin_name = options.auth_type + elif options.auth.get('username', None): + if options.identity_api_version == '3': auth_plugin_name = 'v3password' - elif options.os_identity_api_version == '2.0': + elif options.identity_api_version.startswith('2'): auth_plugin_name = 'v2password' else: # let keystoneclient figure it out itself auth_plugin_name = 'osc_password' - elif options.os_token: - if options.os_identity_api_version == '3': + elif options.auth.get('token', None): + if options.identity_api_version == '3': auth_plugin_name = 'v3token' - elif options.os_identity_api_version == '2.0': + elif options.identity_api_version.startswith('2'): auth_plugin_name = 'v2token' else: # let keystoneclient figure it out itself @@ -89,35 +111,27 @@ def select_auth_plugin(options): def build_auth_params(auth_plugin_name, cmd_options): - auth_params = {} + + auth_params = dict(cmd_options.auth) if auth_plugin_name: LOG.debug('auth_type: %s', auth_plugin_name) auth_plugin_class = base.get_plugin_class(auth_plugin_name) - plugin_options = auth_plugin_class.get_options() - for option in plugin_options: - option_name = 'os_' + option.dest - LOG.debug('fetching option %s' % option_name) - auth_params[option.dest] = getattr(cmd_options, option_name, None) # grab tenant from project for v2.0 API compatibility if auth_plugin_name.startswith("v2"): - auth_params['tenant_id'] = getattr( - cmd_options, - 'os_project_id', - None, - ) - auth_params['tenant_name'] = getattr( - cmd_options, - 'os_project_name', - None, - ) + if 'project_id' in auth_params: + auth_params['tenant_id'] = auth_params['project_id'] + del auth_params['project_id'] + if 'project_name' in auth_params: + auth_params['tenant_name'] = auth_params['project_name'] + del auth_params['project_name'] else: LOG.debug('no auth_type') # delay the plugin choice, grab every option - plugin_options = set([o.replace('-', '_') for o in OPTIONS_LIST]) + auth_plugin_class = None + plugin_options = set([o.replace('-', '_') for o in get_options_list()]) for option in plugin_options: - option_name = 'os_' + option - LOG.debug('fetching option %s' % option_name) - auth_params[option] = getattr(cmd_options, option_name, None) + LOG.debug('fetching option %s' % option) + auth_params[option] = getattr(cmd_options.auth, option, None) return (auth_plugin_class, auth_params) @@ -126,15 +140,29 @@ def check_valid_auth_options(options, auth_plugin_name): msg = '' if auth_plugin_name.endswith('password'): - if not options.os_username: - msg += _('Set a username with --os-username or OS_USERNAME\n') - if not options.os_auth_url: - msg += _('Set an authentication URL, with --os-auth-url or' - ' OS_AUTH_URL\n') - if (not options.os_project_id and not options.os_domain_id and not - options.os_domain_name and not options.os_project_name): + if not options.auth.get('username', None): + msg += _('Set a username with --os-username, OS_USERNAME,' + ' or auth.username\n') + if not options.auth.get('auth_url', None): + msg += _('Set an authentication URL, with --os-auth-url,' + ' OS_AUTH_URL or auth.auth_url\n') + if (not options.auth.get('project_id', None) and not + options.auth.get('domain_id', None) and not + options.auth.get('domain_name', None) and not + options.auth.get('project_name', None)): msg += _('Set a scope, such as a project or domain, with ' - '--os-project-name or OS_PROJECT_NAME') + '--os-project-name, OS_PROJECT_NAME or auth.project_name') + elif auth_plugin_name.endswith('token'): + if not options.auth.get('token', None): + msg += _('Set a token with --os-token, OS_TOKEN or auth.token\n') + if not options.auth.get('auth_url', None): + msg += _('Set a service AUTH_URL, with --os-auth-url, ' + 'OS_AUTH_URL or auth.auth_url\n') + elif auth_plugin_name == 'token_endpoint': + if not options.auth.get('token', None): + msg += _('Set a token with --os-token, OS_TOKEN or auth.token\n') + if not options.auth.get('url', None): + msg += _('Set a service URL, with --os-url, OS_URL or auth.url\n') if msg: raise exc.CommandError('Missing parameter(s): \n%s' % msg) @@ -147,10 +175,11 @@ def build_auth_plugins_option_parser(parser): authentication plugin. """ - available_plugins = [plugin.name for plugin in PLUGIN_LIST] + available_plugins = [plugin.name for plugin in get_plugin_list()] parser.add_argument( '--os-auth-type', metavar='<auth-type>', + dest='auth_type', default=utils.env('OS_AUTH_TYPE'), help='Select an auhentication type. Available types: ' + ', '.join(available_plugins) + @@ -158,7 +187,7 @@ def build_auth_plugins_option_parser(parser): ' (Env: OS_AUTH_TYPE)', choices=available_plugins ) - # make sure we catch old v2.0 env values + # Maintain compatibility with old tenant env vars envs = { 'OS_PROJECT_NAME': utils.env( 'OS_PROJECT_NAME', @@ -169,16 +198,21 @@ def build_auth_plugins_option_parser(parser): default=utils.env('OS_TENANT_ID') ), } - for o in OPTIONS_LIST: - # remove allusion to tenants from v2.0 API + for o in get_options_list(): + # Remove tenant options from KSC plugins and replace them below if 'tenant' not in o: parser.add_argument( '--os-' + o, metavar='<auth-%s>' % o, - default=envs.get(OPTIONS_LIST[o]['env'], - utils.env(OPTIONS_LIST[o]['env'])), - help='%s\n(Env: %s)' % (OPTIONS_LIST[o]['help'], - OPTIONS_LIST[o]['env']), + dest=o.replace('-', '_'), + default=envs.get( + OPTIONS_LIST[o]['env'], + utils.env(OPTIONS_LIST[o]['env']), + ), + help='%s\n(Env: %s)' % ( + OPTIONS_LIST[o]['help'], + OPTIONS_LIST[o]['env'], + ), ) # add tenant-related options for compatibility # this is deprecated but still used in some tempest tests... @@ -186,14 +220,12 @@ def build_auth_plugins_option_parser(parser): '--os-tenant-name', metavar='<auth-tenant-name>', dest='os_project_name', - default=utils.env('OS_TENANT_NAME'), help=argparse.SUPPRESS, ) parser.add_argument( '--os-tenant-id', metavar='<auth-tenant-id>', dest='os_project_id', - default=utils.env('OS_TENANT_ID'), help=argparse.SUPPRESS, ) return parser diff --git a/openstackclient/common/clientmanager.py b/openstackclient/common/clientmanager.py index 10f38c25..ca5ece0d 100644 --- a/openstackclient/common/clientmanager.py +++ b/openstackclient/common/clientmanager.py @@ -19,10 +19,10 @@ import logging import pkg_resources import sys -from keystoneclient import session import requests from openstackclient.api import auth +from openstackclient.common import session as osc_session from openstackclient.identity import client as identity_client @@ -58,7 +58,7 @@ class ClientManager(object): def __init__( self, - cli_options, + cli_options=None, api_version=None, verify=True, pw_func=None, @@ -82,8 +82,8 @@ class ClientManager(object): self._cli_options = cli_options self._api_version = api_version self._pw_callback = pw_func - self._url = self._cli_options.os_url - self._region_name = self._cli_options.os_region_name + self._url = self._cli_options.auth.get('url', None) + self._region_name = self._cli_options.region_name self.timing = self._cli_options.timing @@ -121,7 +121,7 @@ class ClientManager(object): # Horrible hack alert...must handle prompt for null password if # password auth is requested. if (self.auth_plugin_name.endswith('password') and - not self._cli_options.os_password): + not self._cli_options.auth.get('password', None)): self._cli_options.os_password = self._pw_callback() (auth_plugin, self._auth_params) = auth.build_auth_params( @@ -129,13 +129,15 @@ class ClientManager(object): self._cli_options, ) - default_domain = self._cli_options.os_default_domain + # TODO(mordred): This is a usability improvement that's broadly useful + # We should port it back up into os-client-config. + default_domain = self._cli_options.default_domain # NOTE(stevemar): If PROJECT_DOMAIN_ID or PROJECT_DOMAIN_NAME is # present, then do not change the behaviour. Otherwise, set the # PROJECT_DOMAIN_ID to 'OS_DEFAULT_DOMAIN' for better usability. if (self._api_version.get('identity') == '3' and - not self._auth_params.get('project_domain_id') and - not self._auth_params.get('project_domain_name')): + not self._auth_params.get('project_domain_id', None) and + not self._auth_params.get('project_domain_name', None)): self._auth_params['project_domain_id'] = default_domain # NOTE(stevemar): If USER_DOMAIN_ID or USER_DOMAIN_NAME is present, @@ -143,8 +145,8 @@ class ClientManager(object): # to 'OS_DEFAULT_DOMAIN' for better usability. if (self._api_version.get('identity') == '3' and self.auth_plugin_name.endswith('password') and - not self._auth_params.get('user_domain_id') and - not self._auth_params.get('user_domain_name')): + not self._auth_params.get('user_domain_id', None) and + not self._auth_params.get('user_domain_name', None)): self._auth_params['user_domain_id'] = default_domain # For compatibility until all clients can be updated @@ -157,7 +159,7 @@ class ClientManager(object): self.auth = auth_plugin.load_from_options(**self._auth_params) # needed by SAML authentication request_session = requests.session() - self.session = session.Session( + self.session = osc_session.TimingSession( auth=self.auth, session=request_session, verify=self._verify, diff --git a/openstackclient/common/limits.py b/openstackclient/common/limits.py index 9c9458ab..4abcf169 100644 --- a/openstackclient/common/limits.py +++ b/openstackclient/common/limits.py @@ -21,6 +21,7 @@ import logging from cliff import lister from openstackclient.common import utils +from openstackclient.identity import common as identity_common class ShowLimits(lister.Lister): @@ -49,6 +50,18 @@ class ShowLimits(lister.Lister): action="store_true", default=False, help="Include reservations count [only valid with --absolute]") + parser.add_argument( + '--project', + metavar='<project>', + help='Show limits for a specific project (name or ID)' + ' [only valid with --absolute]', + ) + parser.add_argument( + '--domain', + metavar='<domain>', + help='Domain that owns --project (name or ID)' + ' [only valid with --absolute]', + ) return parser def take_action(self, parsed_args): @@ -57,7 +70,21 @@ class ShowLimits(lister.Lister): compute_client = self.app.client_manager.compute volume_client = self.app.client_manager.volume - compute_limits = compute_client.limits.get(parsed_args.is_reserved) + project_id = None + if parsed_args.project is not None: + identity_client = self.app.client_manager.identity + if parsed_args.domain is not None: + domain = identity_common.find_domain(identity_client, + parsed_args.domain) + project_id = utils.find_resource(identity_client.projects, + parsed_args.project, + domain_id=domain.id).id + else: + project_id = utils.find_resource(identity_client.projects, + parsed_args.project).id + + compute_limits = compute_client.limits.get(parsed_args.is_reserved, + tenant_id=project_id) volume_limits = volume_client.limits.get() if parsed_args.is_absolute: diff --git a/openstackclient/common/quota.py b/openstackclient/common/quota.py index dde4a9ac..ea1dc38f 100644 --- a/openstackclient/common/quota.py +++ b/openstackclient/common/quota.py @@ -82,6 +82,11 @@ class SetQuota(command.Command): type=int, help='New value for the %s quota' % v, ) + parser.add_argument( + '--volume-type', + metavar='<volume-type>', + help='Set quotas for a specific <volume-type>', + ) return parser def take_action(self, parsed_args): @@ -97,8 +102,11 @@ class SetQuota(command.Command): volume_kwargs = {} for k, v in VOLUME_QUOTAS.items(): - if v in parsed_args: - volume_kwargs[k] = getattr(parsed_args, v, None) + value = getattr(parsed_args, v, None) + if value is not None: + if parsed_args.volume_type: + k = k + '_%s' % parsed_args.volume_type + volume_kwargs[k] = value if compute_kwargs == {} and volume_kwargs == {}: sys.stderr.write("No quotas updated") diff --git a/openstackclient/common/session.py b/openstackclient/common/session.py new file mode 100644 index 00000000..dda1c417 --- /dev/null +++ b/openstackclient/common/session.py @@ -0,0 +1,50 @@ +# 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. +# + +"""Subclass of keystoneclient.session""" + +from keystoneclient import session + + +class TimingSession(session.Session): + """A Session that supports collection of timing data per Method URL""" + + def __init__( + self, + **kwargs + ): + """Pass through all arguments except timing""" + super(TimingSession, self).__init__(**kwargs) + + # times is a list of tuples: ("method url", elapsed_time) + self.times = [] + + def get_timings(self): + return self.times + + def reset_timings(self): + self.times = [] + + def request(self, url, method, **kwargs): + """Wrap the usual request() method with the timers""" + resp = super(TimingSession, self).request(url, method, **kwargs) + for h in resp.history: + self.times.append(( + "%s %s" % (h.request.method, h.request.url), + h.elapsed, + )) + self.times.append(( + "%s %s" % (resp.request.method, resp.request.url), + resp.elapsed, + )) + return resp diff --git a/openstackclient/common/timing.py b/openstackclient/common/timing.py index 1c94682c..d13c86e7 100644 --- a/openstackclient/common/timing.py +++ b/openstackclient/common/timing.py @@ -33,10 +33,12 @@ class Timing(lister.Lister): results = [] total = 0.0 - for url, start, end in self.app.timing_data: - seconds = end - start - total += seconds - results.append((url, seconds)) + for url, td in self.app.timing_data: + # NOTE(dtroyer): Take the long way here because total_seconds() + # was added in py27. + sec = (td.microseconds + (td.seconds + td.days*86400) * 1e6) / 1e6 + total += sec + results.append((url, sec)) results.append(('Total', total)) return ( column_headers, diff --git a/openstackclient/compute/client.py b/openstackclient/compute/client.py index 7ca08a4f..93a7b715 100644 --- a/openstackclient/compute/client.py +++ b/openstackclient/compute/client.py @@ -15,14 +15,6 @@ import logging -from novaclient import client as nova_client -from novaclient import extension - -try: - from novaclient.v2.contrib import list_extensions -except ImportError: - from novaclient.v1_1.contrib import list_extensions - from openstackclient.common import utils LOG = logging.getLogger(__name__) @@ -30,10 +22,22 @@ LOG = logging.getLogger(__name__) DEFAULT_COMPUTE_API_VERSION = '2' API_VERSION_OPTION = 'os_compute_api_version' API_NAME = 'compute' +API_VERSIONS = { + "2": "novaclient.client", +} def make_client(instance): """Returns a compute service client.""" + + # Defer client imports until we actually need them + from novaclient import client as nova_client + from novaclient import extension + try: + from novaclient.v2.contrib import list_extensions + except ImportError: + from novaclient.v1_1.contrib import list_extensions + compute_client = nova_client.get_client_class( instance._api_version[API_NAME], ) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 49ef18b2..e4e96ee7 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -275,10 +275,17 @@ class CreateServer(show.ShowOne): ) parser.add_argument( '--nic', - metavar='<nic-config-string>', + metavar="<net-id=net-uuid,v4-fixed-ip=ip-addr,v6-fixed-ip=ip-addr," + "port-id=port-uuid>", action='append', default=[], - help=_('Specify NIC configuration (optional extension)'), + help=_("Create a NIC on the server. " + "Specify option multiple times to create multiple NICs. " + "Either net-id or port-id must be provided, but not both. " + "net-id: attach NIC to network with this UUID, " + "port-id: attach NIC to port with this UUID, " + "v4-fixed-ip: IPv4 fixed address for NIC (optional), " + "v6-fixed-ip: IPv6 fixed address for NIC (optional)."), ) parser.add_argument( '--hint', diff --git a/openstackclient/identity/common.py b/openstackclient/identity/common.py index 253729bd..a1b46cb4 100644 --- a/openstackclient/identity/common.py +++ b/openstackclient/identity/common.py @@ -17,6 +17,9 @@ from keystoneclient import exceptions as identity_exc from keystoneclient.v3 import domains +from keystoneclient.v3 import groups +from keystoneclient.v3 import projects +from keystoneclient.v3 import users from openstackclient.common import exceptions from openstackclient.common import utils @@ -56,4 +59,58 @@ def find_domain(identity_client, name_or_id): return dom except identity_exc.Forbidden: pass - return domains.Domain(None, {'id': name_or_id}) + return domains.Domain(None, {'id': name_or_id, 'name': name_or_id}) + + +def find_group(identity_client, name_or_id): + """Find a group. + + If the user does not have permissions to to perform a list groups call, + e.g., if the user is a project admin, assume that the group given is the + id rather than the name. This method is used by the role add command to + allow a role to be assigned to a group by a project admin who does not + have permission to list groups. + """ + try: + group = utils.find_resource(identity_client.groups, name_or_id) + if group is not None: + return group + except identity_exc.Forbidden: + pass + return groups.Group(None, {'id': name_or_id, 'name': name_or_id}) + + +def find_project(identity_client, name_or_id): + """Find a project. + + If the user does not have permissions to to perform a list projects + call, e.g., if the user is a project admin, assume that the project + given is the id rather than the name. This method is used by the role + add command to allow a role to be assigned to a user by a project admin + who does not have permission to list projects. + """ + try: + project = utils.find_resource(identity_client.projects, name_or_id) + if project is not None: + return project + except identity_exc.Forbidden: + pass + return projects.Project(None, {'id': name_or_id, 'name': name_or_id}) + + +def find_user(identity_client, name_or_id): + """Find a user. + + If the user does not have permissions to to perform a list users call, + e.g., if the user is a project admin, assume that the user given is the + id rather than the name. This method is used by the role add command to + allow a role to be assigned to a user by a project admin who does not + have permission to list users. + """ + try: + user = utils.find_resource(identity_client.users, name_or_id) + if user is not None: + return user + except identity_exc.Forbidden: + pass + return users.User(None, {'id': name_or_id, 'name': name_or_id}) diff --git a/openstackclient/identity/v3/group.py b/openstackclient/identity/v3/group.py index a2afecb9..91acf3e5 100644 --- a/openstackclient/identity/v3/group.py +++ b/openstackclient/identity/v3/group.py @@ -137,11 +137,11 @@ class CreateGroup(show.ShowOne): def take_action(self, parsed_args): self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity + + domain = None if parsed_args.domain: domain = common.find_domain(identity_client, parsed_args.domain).id - else: - domain = None try: group = identity_client.groups.create( @@ -228,11 +228,10 @@ class ListGroup(lister.Lister): self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity + domain = None if parsed_args.domain: domain = common.find_domain(identity_client, parsed_args.domain).id - else: - domain = None if parsed_args.user: user = utils.find_resource( diff --git a/openstackclient/identity/v3/project.py b/openstackclient/identity/v3/project.py index 1c93ad5d..c5560f5e 100644 --- a/openstackclient/identity/v3/project.py +++ b/openstackclient/identity/v3/project.py @@ -80,11 +80,10 @@ class CreateProject(show.ShowOne): self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity + domain = None if parsed_args.domain: domain = common.find_domain(identity_client, parsed_args.domain).id - else: - domain = None enabled = True if parsed_args.disable: diff --git a/openstackclient/identity/v3/role.py b/openstackclient/identity/v3/role.py index 03760709..3dd998ba 100644 --- a/openstackclient/identity/v3/role.py +++ b/openstackclient/identity/v3/role.py @@ -26,6 +26,7 @@ from keystoneclient import exceptions as ksc_exc from openstackclient.common import utils from openstackclient.i18n import _ # noqa +from openstackclient.identity import common class AddRole(command.Command): @@ -78,12 +79,12 @@ class AddRole(command.Command): ) if parsed_args.user and parsed_args.domain: - user = utils.find_resource( - identity_client.users, + user = common.find_user( + identity_client, parsed_args.user, ) - domain = utils.find_resource( - identity_client.domains, + domain = common.find_domain( + identity_client, parsed_args.domain, ) identity_client.roles.grant( @@ -92,12 +93,12 @@ class AddRole(command.Command): domain=domain.id, ) elif parsed_args.user and parsed_args.project: - user = utils.find_resource( - identity_client.users, + user = common.find_user( + identity_client, parsed_args.user, ) - project = utils.find_resource( - identity_client.projects, + project = common.find_project( + identity_client, parsed_args.project, ) identity_client.roles.grant( @@ -106,12 +107,12 @@ class AddRole(command.Command): project=project.id, ) elif parsed_args.group and parsed_args.domain: - group = utils.find_resource( - identity_client.groups, + group = common.find_group( + identity_client, parsed_args.group, ) - domain = utils.find_resource( - identity_client.domains, + domain = common.find_domain( + identity_client, parsed_args.domain, ) identity_client.roles.grant( @@ -120,12 +121,12 @@ class AddRole(command.Command): domain=domain.id, ) elif parsed_args.group and parsed_args.project: - group = utils.find_resource( - identity_client.groups, + group = common.find_group( + identity_client, parsed_args.group, ) - project = utils.find_resource( - identity_client.projects, + project = common.find_project( + identity_client, parsed_args.project, ) identity_client.roles.grant( @@ -240,24 +241,24 @@ class ListRole(lister.Lister): identity_client = self.app.client_manager.identity if parsed_args.user: - user = utils.find_resource( - identity_client.users, + user = common.find_user( + identity_client, parsed_args.user, ) elif parsed_args.group: - group = utils.find_resource( - identity_client.groups, + group = common.find_group( + identity_client, parsed_args.group, ) if parsed_args.domain: - domain = utils.find_resource( - identity_client.domains, + domain = common.find_domain( + identity_client, parsed_args.domain, ) elif parsed_args.project: - project = utils.find_resource( - identity_client.projects, + project = common.find_project( + identity_client, parsed_args.project, ) @@ -370,12 +371,12 @@ class RemoveRole(command.Command): ) if parsed_args.user and parsed_args.domain: - user = utils.find_resource( - identity_client.users, + user = common.find_user( + identity_client, parsed_args.user, ) - domain = utils.find_resource( - identity_client.domains, + domain = common.find_domain( + identity_client, parsed_args.domain, ) identity_client.roles.revoke( @@ -384,12 +385,12 @@ class RemoveRole(command.Command): domain=domain.id, ) elif parsed_args.user and parsed_args.project: - user = utils.find_resource( - identity_client.users, + user = common.find_user( + identity_client, parsed_args.user, ) - project = utils.find_resource( - identity_client.projects, + project = common.find_project( + identity_client, parsed_args.project, ) identity_client.roles.revoke( @@ -398,12 +399,12 @@ class RemoveRole(command.Command): project=project.id, ) elif parsed_args.group and parsed_args.domain: - group = utils.find_resource( - identity_client.groups, + group = common.find_group( + identity_client, parsed_args.group, ) - domain = utils.find_resource( - identity_client.domains, + domain = common.find_domain( + identity_client, parsed_args.domain, ) identity_client.roles.revoke( @@ -412,12 +413,12 @@ class RemoveRole(command.Command): domain=domain.id, ) elif parsed_args.group and parsed_args.project: - group = utils.find_resource( - identity_client.groups, + group = common.find_group( + identity_client, parsed_args.group, ) - project = utils.find_resource( - identity_client.projects, + project = common.find_project( + identity_client, parsed_args.project, ) identity_client.roles.revoke( diff --git a/openstackclient/identity/v3/role_assignment.py b/openstackclient/identity/v3/role_assignment.py index f053b608..24e3a7f7 100644 --- a/openstackclient/identity/v3/role_assignment.py +++ b/openstackclient/identity/v3/role_assignment.py @@ -18,6 +18,7 @@ import logging from cliff import lister from openstackclient.common import utils +from openstackclient.identity import common class ListRoleAssignment(lister.Lister): @@ -80,29 +81,29 @@ class ListRoleAssignment(lister.Lister): user = None if parsed_args.user: - user = utils.find_resource( - identity_client.users, + user = common.find_user( + identity_client, parsed_args.user, ) domain = None if parsed_args.domain: - domain = utils.find_resource( - identity_client.domains, + domain = common.find_domain( + identity_client, parsed_args.domain, ) project = None if parsed_args.project: - project = utils.find_resource( - identity_client.projects, + project = common.find_project( + identity_client, parsed_args.project, ) group = None if parsed_args.group: - group = utils.find_resource( - identity_client.groups, + group = common.find_group( + identity_client, parsed_args.group, ) diff --git a/openstackclient/identity/v3/trust.py b/openstackclient/identity/v3/trust.py index e67b02e7..ab6673d2 100644 --- a/openstackclient/identity/v3/trust.py +++ b/openstackclient/identity/v3/trust.py @@ -92,23 +92,20 @@ class CreateTrust(show.ShowOne): self.log.debug('take_action(%s)' % parsed_args) identity_client = self.app.client_manager.identity + project_domain = None if parsed_args.project_domain: project_domain = common.find_domain(identity_client, parsed_args.project_domain).id - else: - project_domain = None + trustor_domain = None if parsed_args.trustor_domain: trustor_domain = common.find_domain(identity_client, parsed_args.trustor_domain).id - else: - trustor_domain = None + trustee_domain = None if parsed_args.trustee_domain: trustee_domain = common.find_domain(identity_client, parsed_args.trustee_domain).id - else: - trustee_domain = None # NOTE(stevemar): Find the two users, project and roles that # are necessary for making a trust usable, the API dictates that diff --git a/openstackclient/identity/v3/user.py b/openstackclient/identity/v3/user.py index 0a154f64..c1a0a43c 100644 --- a/openstackclient/identity/v3/user.py +++ b/openstackclient/identity/v3/user.py @@ -94,19 +94,17 @@ class CreateUser(show.ShowOne): self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity + project_id = None if parsed_args.project: project_id = utils.find_resource( identity_client.projects, parsed_args.project, ).id - else: - project_id = None + domain_id = None if parsed_args.domain: domain_id = common.find_domain(identity_client, parsed_args.domain).id - else: - domain_id = None enabled = True if parsed_args.disable: @@ -211,11 +209,10 @@ class ListUser(lister.Lister): self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity + domain = None if parsed_args.domain: domain = common.find_domain(identity_client, parsed_args.domain).id - else: - domain = None if parsed_args.group: group = utils.find_resource( diff --git a/openstackclient/image/v1/image.py b/openstackclient/image/v1/image.py index 7f5f5af9..4c07d360 100644 --- a/openstackclient/image/v1/image.py +++ b/openstackclient/image/v1/image.py @@ -190,7 +190,7 @@ class CreateImage(show.ShowOne): kwargs = {} copy_attrs = ('name', 'id', 'store', 'container_format', 'disk_format', 'owner', 'size', 'min_disk', 'min_ram', - 'localtion', 'copy_from', 'volume', 'force', + 'location', 'copy_from', 'volume', 'force', 'checksum', 'properties') for attr in copy_attrs: if attr in parsed_args: diff --git a/openstackclient/shell.py b/openstackclient/shell.py index 3cfd7312..00f4a3c9 100644 --- a/openstackclient/shell.py +++ b/openstackclient/shell.py @@ -1,4 +1,5 @@ # Copyright 2012-2013 OpenStack Foundation +# Copyright 2015 Dean Troyer # # 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 @@ -19,6 +20,7 @@ import getpass import logging import sys import traceback +import warnings from cliff import app from cliff import command @@ -31,6 +33,8 @@ from openstackclient.common import exceptions as exc from openstackclient.common import timing from openstackclient.common import utils +from os_client_config import config as cloud_config + DEFAULT_DOMAIN = 'default' @@ -76,51 +80,19 @@ class OpenStackShell(app.App): super(OpenStackShell, self).__init__( description=__doc__.strip(), version=openstackclient.__version__, - command_manager=commandmanager.CommandManager('openstack.cli')) + command_manager=commandmanager.CommandManager('openstack.cli'), + deferred_help=True) self.api_version = {} # Until we have command line arguments parsed, dump any stack traces self.dump_stack_trace = True - # This is instantiated in initialize_app() only when using - # password flow auth - self.auth_client = None - # Assume TLS host certificate verification is enabled self.verify = True self.client_manager = None - # NOTE(dtroyer): This hack changes the help action that Cliff - # automatically adds to the parser so we can defer - # its execution until after the api-versioned commands - # have been loaded. There doesn't seem to be a - # way to edit/remove anything from an existing parser. - - # Replace the cliff-added help.HelpAction to defer its execution - self.DeferredHelpAction = None - for a in self.parser._actions: - if type(a) == help.HelpAction: - # Found it, save and replace it - self.DeferredHelpAction = a - - # These steps are argparse-implementation-dependent - self.parser._actions.remove(a) - if self.parser._option_string_actions['-h']: - del self.parser._option_string_actions['-h'] - if self.parser._option_string_actions['--help']: - del self.parser._option_string_actions['--help'] - - # Make a new help option to just set a flag - self.parser.add_argument( - '-h', '--help', - action='store_true', - dest='deferred_help', - default=False, - help="Show this help message and exit", - ) - def configure_logging(self): """Configure logging for the app @@ -139,12 +111,15 @@ class OpenStackShell(app.App): if self.options.verbose_level == 0: # --quiet root_logger.setLevel(logging.ERROR) + warnings.simplefilter("ignore") elif self.options.verbose_level == 1: # This is the default case, no --debug, --verbose or --quiet root_logger.setLevel(logging.WARNING) + warnings.simplefilter("ignore") elif self.options.verbose_level == 2: # One --verbose root_logger.setLevel(logging.INFO) + warnings.simplefilter("once") elif self.options.verbose_level >= 3: # Two or more --verbose root_logger.setLevel(logging.DEBUG) @@ -188,10 +163,19 @@ class OpenStackShell(app.App): description, version) + # 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( @@ -236,8 +220,43 @@ class OpenStackShell(app.App): * authenticate against Identity if requested """ + # Parent __init__ parses argv into self.options super(OpenStackShell, self).initialize_app(argv) + # Resolve the verify/insecure exclusive pair here as cloud_config + # doesn't know about verify + self.options.insecure = ( + self.options.insecure and not self.options.verify + ) + + # Set the default plugin to token_endpoint if rl and token are given + if (self.options.url and self.options.token): + # Use service token authentication + cloud_config.set_default('auth_type', 'token_endpoint') + else: + cloud_config.set_default('auth_type', 'osc_password') + self.log.debug("options: %s", self.options) + + # Do configuration file handling + cc = cloud_config.OpenStackConfig() + self.log.debug("defaults: %s", cc.defaults) + + self.cloud = cc.get_one_cloud( + cloud=self.options.cloud, + argparse=self.options, + ) + self.log.debug("cloud cfg: %s", self.cloud.config) + + # Set up client TLS + cacert = self.cloud.cacert + if cacert: + self.verify = cacert + else: + self.verify = not getattr(self.cloud.config, 'insecure', False) + + # Neutralize verify option + self.options.verify = None + # Save default domain self.default_domain = self.options.os_default_domain @@ -247,6 +266,11 @@ class OpenStackShell(app.App): if version_opt: api = mod.API_NAME self.api_version[api] = version_opt + if version_opt not in mod.API_VERSIONS: + self.log.warning( + "The %s version <%s> is not in supported versions <%s>" + % (api, version_opt, + ', '.join(mod.API_VERSIONS.keys()))) # Command groups deal only with major versions version = '.v' + version_opt.replace('.', '_').split('_')[0] cmd_group = 'openstack.' + api.replace('-', '_') + version @@ -276,17 +300,10 @@ class OpenStackShell(app.App): # set up additional clients to stuff in to client_manager?? # Handle deferred help and exit - if self.options.deferred_help: - self.DeferredHelpAction(self.parser, self.parser, None, None) - - # Set up common client session - if self.options.os_cacert: - self.verify = self.options.os_cacert - else: - self.verify = not self.options.insecure + self.print_help_if_requested() self.client_manager = clientmanager.ClientManager( - cli_options=self.options, + cli_options=self.cloud, verify=self.verify, api_version=self.api_version, pw_func=prompt_for_password, @@ -304,7 +321,8 @@ class OpenStackShell(app.App): try: # Trigger the Identity client to initialize self.client_manager.auth_ref - except Exception: + except Exception as e: + self.log.warning("Possible error authenticating: " + str(e)) pass return @@ -316,11 +334,10 @@ class OpenStackShell(app.App): # Process collected timing data if self.options.timing: - # Loop through extensions - for mod in self.ext_modules: - client = getattr(self.client_manager, mod.API_NAME) - if hasattr(client, 'get_timings'): - self.timing_data.extend(client.get_timings()) + # 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) diff --git a/openstackclient/tests/common/test_clientmanager.py b/openstackclient/tests/common/test_clientmanager.py index 3648bf57..26cf4967 100644 --- a/openstackclient/tests/common/test_clientmanager.py +++ b/openstackclient/tests/common/test_clientmanager.py @@ -34,6 +34,10 @@ AUTH_REF.update(fakes.TEST_RESPONSE_DICT['access']) SERVICE_CATALOG = service_catalog.ServiceCatalogV2(AUTH_REF) +# This is deferred in api.auth but we need it here... +auth.get_options_list() + + class Container(object): attr = clientmanager.ClientCache(lambda x: object()) @@ -44,13 +48,14 @@ class Container(object): class FakeOptions(object): def __init__(self, **kwargs): for option in auth.OPTIONS_LIST: - setattr(self, 'os_' + option.replace('-', '_'), None) - self.os_auth_type = None - self.os_identity_api_version = '2.0' + setattr(self, option.replace('-', '_'), None) + self.auth_type = None + self.identity_api_version = '2.0' self.timing = None - self.os_region_name = None - self.os_url = None - self.os_default_domain = 'default' + self.region_name = None + self.url = None + self.auth = {} + self.default_domain = 'default' self.__dict__.update(kwargs) @@ -82,9 +87,11 @@ class TestClientManager(utils.TestCase): client_manager = clientmanager.ClientManager( cli_options=FakeOptions( - os_token=fakes.AUTH_TOKEN, - os_url=fakes.AUTH_URL, - os_auth_type='token_endpoint', + auth_type='token_endpoint', + auth=dict( + token=fakes.AUTH_TOKEN, + url=fakes.AUTH_URL, + ), ), api_version=API_VERSION, verify=True @@ -110,9 +117,11 @@ class TestClientManager(utils.TestCase): client_manager = clientmanager.ClientManager( cli_options=FakeOptions( - os_token=fakes.AUTH_TOKEN, - os_auth_url=fakes.AUTH_URL, - os_auth_type='v2token', + auth=dict( + token=fakes.AUTH_TOKEN, + auth_url=fakes.AUTH_URL, + ), + auth_type='v2token', ), api_version=API_VERSION, verify=True @@ -134,10 +143,12 @@ class TestClientManager(utils.TestCase): client_manager = clientmanager.ClientManager( cli_options=FakeOptions( - os_auth_url=fakes.AUTH_URL, - os_username=fakes.USERNAME, - os_password=fakes.PASSWORD, - os_project_name=fakes.PROJECT_NAME, + auth=dict( + auth_url=fakes.AUTH_URL, + username=fakes.USERNAME, + password=fakes.PASSWORD, + project_name=fakes.PROJECT_NAME, + ), ), api_version=API_VERSION, verify=False, @@ -194,11 +205,13 @@ class TestClientManager(utils.TestCase): client_manager = clientmanager.ClientManager( cli_options=FakeOptions( - os_auth_url=fakes.AUTH_URL, - os_username=fakes.USERNAME, - os_password=fakes.PASSWORD, - os_project_name=fakes.PROJECT_NAME, - os_auth_type='v2password', + auth=dict( + auth_url=fakes.AUTH_URL, + username=fakes.USERNAME, + password=fakes.PASSWORD, + project_name=fakes.PROJECT_NAME, + ), + auth_type='v2password', ), api_version=API_VERSION, verify='cafile', @@ -210,8 +223,8 @@ class TestClientManager(utils.TestCase): self.assertEqual('cafile', client_manager._cacert) def _select_auth_plugin(self, auth_params, api_version, auth_plugin_name): - auth_params['os_auth_type'] = auth_plugin_name - auth_params['os_identity_api_version'] = api_version + auth_params['auth_type'] = auth_plugin_name + auth_params['identity_api_version'] = api_version client_manager = clientmanager.ClientManager( cli_options=FakeOptions(**auth_params), api_version=API_VERSION, @@ -226,19 +239,33 @@ class TestClientManager(utils.TestCase): def test_client_manager_select_auth_plugin(self): # test token auth - params = dict(os_token=fakes.AUTH_TOKEN, - os_auth_url=fakes.AUTH_URL) + params = dict( + auth=dict( + auth_url=fakes.AUTH_URL, + token=fakes.AUTH_TOKEN, + ), + ) self._select_auth_plugin(params, '2.0', 'v2token') self._select_auth_plugin(params, '3', 'v3token') self._select_auth_plugin(params, 'XXX', 'token') # test token/endpoint auth - params = dict(os_token=fakes.AUTH_TOKEN, os_url='test') + params = dict( + auth_plugin='token_endpoint', + auth=dict( + url='test', + token=fakes.AUTH_TOKEN, + ), + ) self._select_auth_plugin(params, 'XXX', 'token_endpoint') # test password auth - params = dict(os_auth_url=fakes.AUTH_URL, - os_username=fakes.USERNAME, - os_password=fakes.PASSWORD, - os_project_name=fakes.PROJECT_NAME) + params = dict( + auth=dict( + auth_url=fakes.AUTH_URL, + username=fakes.USERNAME, + password=fakes.PASSWORD, + project_name=fakes.PROJECT_NAME, + ), + ) self._select_auth_plugin(params, '2.0', 'v2password') self._select_auth_plugin(params, '3', 'v3password') self._select_auth_plugin(params, 'XXX', 'password') diff --git a/openstackclient/tests/common/test_timing.py b/openstackclient/tests/common/test_timing.py index aa910b91..a7f93b55 100644 --- a/openstackclient/tests/common/test_timing.py +++ b/openstackclient/tests/common/test_timing.py @@ -13,14 +13,15 @@ """Test Timing pseudo-command""" +import datetime + from openstackclient.common import timing from openstackclient.tests import fakes from openstackclient.tests import utils timing_url = 'GET http://localhost:5000' -timing_start = 1404802774.872809 -timing_end = 1404802775.724802 +timing_elapsed = 0.872809 class FakeGenericClient(object): @@ -66,9 +67,10 @@ class TestTiming(utils.TestCommand): self.assertEqual(datalist, data) def test_timing_list(self): - self.app.timing_data = [ - (timing_url, timing_start, timing_end), - ] + self.app.timing_data = [( + timing_url, + datetime.timedelta(microseconds=timing_elapsed*1000000), + )] arglist = [] verifylist = [] @@ -79,9 +81,8 @@ class TestTiming(utils.TestCommand): collist = ('URL', 'Seconds') self.assertEqual(collist, columns) - timing_sec = timing_end - timing_start datalist = [ - (timing_url, timing_sec), - ('Total', timing_sec) + (timing_url, timing_elapsed), + ('Total', timing_elapsed), ] self.assertEqual(datalist, data) diff --git a/openstackclient/tests/test_shell.py b/openstackclient/tests/test_shell.py index f1043072..a43be954 100644 --- a/openstackclient/tests/test_shell.py +++ b/openstackclient/tests/test_shell.py @@ -82,34 +82,62 @@ class TestShell(utils.TestCase): fake_execute(_shell, _cmd) self.app.assert_called_with(["list", "project"]) - self.assertEqual(default_args["auth_url"], - _shell.options.os_auth_url) - self.assertEqual(default_args["project_id"], - _shell.options.os_project_id) - self.assertEqual(default_args["project_name"], - _shell.options.os_project_name) - self.assertEqual(default_args["domain_id"], - _shell.options.os_domain_id) - self.assertEqual(default_args["domain_name"], - _shell.options.os_domain_name) - self.assertEqual(default_args["user_domain_id"], - _shell.options.os_user_domain_id) - self.assertEqual(default_args["user_domain_name"], - _shell.options.os_user_domain_name) - self.assertEqual(default_args["project_domain_id"], - _shell.options.os_project_domain_id) - self.assertEqual(default_args["project_domain_name"], - _shell.options.os_project_domain_name) - self.assertEqual(default_args["username"], - _shell.options.os_username) - self.assertEqual(default_args["password"], - _shell.options.os_password) - self.assertEqual(default_args["region_name"], - _shell.options.os_region_name) - self.assertEqual(default_args["trust_id"], - _shell.options.os_trust_id) - self.assertEqual(default_args['auth_type'], - _shell.options.os_auth_type) + self.assertEqual( + default_args.get("auth_url", ''), + _shell.options.auth_url, + ) + self.assertEqual( + default_args.get("project_id", ''), + _shell.options.project_id, + ) + self.assertEqual( + default_args.get("project_name", ''), + _shell.options.project_name, + ) + self.assertEqual( + default_args.get("domain_id", ''), + _shell.options.domain_id, + ) + self.assertEqual( + default_args.get("domain_name", ''), + _shell.options.domain_name, + ) + self.assertEqual( + default_args.get("user_domain_id", ''), + _shell.options.user_domain_id, + ) + self.assertEqual( + default_args.get("user_domain_name", ''), + _shell.options.user_domain_name, + ) + self.assertEqual( + default_args.get("project_domain_id", ''), + _shell.options.project_domain_id, + ) + self.assertEqual( + default_args.get("project_domain_name", ''), + _shell.options.project_domain_name, + ) + self.assertEqual( + default_args.get("username", ''), + _shell.options.username, + ) + self.assertEqual( + default_args.get("password", ''), + _shell.options.password, + ) + self.assertEqual( + default_args.get("region_name", ''), + _shell.options.region_name, + ) + self.assertEqual( + default_args.get("trust_id", ''), + _shell.options.trust_id, + ) + self.assertEqual( + default_args.get('auth_type', ''), + _shell.options.auth_type, + ) def _assert_token_auth(self, cmd_options, default_args): with mock.patch("openstackclient.shell.OpenStackShell.initialize_app", @@ -118,9 +146,34 @@ class TestShell(utils.TestCase): fake_execute(_shell, _cmd) self.app.assert_called_with(["list", "role"]) - self.assertEqual(default_args["os_token"], _shell.options.os_token) - self.assertEqual(default_args["os_auth_url"], - _shell.options.os_auth_url) + 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): + 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("url", ''), + _shell.options.url, + "url", + ) def _assert_cli(self, cmd_options, default_args): with mock.patch("openstackclient.shell.OpenStackShell.initialize_app", @@ -178,301 +231,233 @@ class TestShellPasswordAuth(TestShell): flag = "--os-auth-url " + DEFAULT_AUTH_URL kwargs = { "auth_url": DEFAULT_AUTH_URL, - "project_id": "", - "project_name": "", - "user_domain_id": "", - "domain_id": "", - "domain_name": "", - "user_domain_name": "", - "project_domain_id": "", - "project_domain_name": "", - "username": "", - "password": "", - "region_name": "", - "trust_id": "", - "auth_type": "", } self._assert_password_auth(flag, kwargs) def test_only_project_id_flow(self): flag = "--os-project-id " + DEFAULT_PROJECT_ID kwargs = { - "auth_url": "", "project_id": DEFAULT_PROJECT_ID, - "project_name": "", - "domain_id": "", - "domain_name": "", - "user_domain_id": "", - "user_domain_name": "", - "project_domain_id": "", - "project_domain_name": "", - "username": "", - "password": "", - "region_name": "", - "trust_id": "", - "auth_type": "", } self._assert_password_auth(flag, kwargs) def test_only_project_name_flow(self): flag = "--os-project-name " + DEFAULT_PROJECT_NAME kwargs = { - "auth_url": "", - "project_id": "", "project_name": DEFAULT_PROJECT_NAME, - "domain_id": "", - "domain_name": "", - "user_domain_id": "", - "user_domain_name": "", - "project_domain_id": "", - "project_domain_name": "", - "username": "", - "password": "", - "region_name": "", - "trust_id": "", - "auth_type": "", } self._assert_password_auth(flag, kwargs) def test_only_domain_id_flow(self): flag = "--os-domain-id " + DEFAULT_DOMAIN_ID kwargs = { - "auth_url": "", - "project_id": "", - "project_name": "", "domain_id": DEFAULT_DOMAIN_ID, - "domain_name": "", - "user_domain_id": "", - "user_domain_name": "", - "project_domain_id": "", - "project_domain_name": "", - "username": "", - "password": "", - "region_name": "", - "trust_id": "", - "auth_type": "", } self._assert_password_auth(flag, kwargs) def test_only_domain_name_flow(self): flag = "--os-domain-name " + DEFAULT_DOMAIN_NAME kwargs = { - "auth_url": "", - "project_id": "", - "project_name": "", - "domain_id": "", "domain_name": DEFAULT_DOMAIN_NAME, - "user_domain_id": "", - "user_domain_name": "", - "project_domain_id": "", - "project_domain_name": "", - "username": "", - "password": "", - "region_name": "", - "trust_id": "", - "auth_type": "", } self._assert_password_auth(flag, kwargs) def test_only_user_domain_id_flow(self): flag = "--os-user-domain-id " + DEFAULT_USER_DOMAIN_ID kwargs = { - "auth_url": "", - "project_id": "", - "project_name": "", - "domain_id": "", - "domain_name": "", "user_domain_id": DEFAULT_USER_DOMAIN_ID, - "user_domain_name": "", - "project_domain_id": "", - "project_domain_name": "", - "username": "", - "password": "", - "region_name": "", - "trust_id": "", - "auth_type": "", } self._assert_password_auth(flag, kwargs) def test_only_user_domain_name_flow(self): flag = "--os-user-domain-name " + DEFAULT_USER_DOMAIN_NAME kwargs = { - "auth_url": "", - "project_id": "", - "project_name": "", - "domain_id": "", - "domain_name": "", - "user_domain_id": "", "user_domain_name": DEFAULT_USER_DOMAIN_NAME, - "project_domain_id": "", - "project_domain_name": "", - "username": "", - "password": "", - "region_name": "", - "trust_id": "", - "auth_type": "", } self._assert_password_auth(flag, kwargs) def test_only_project_domain_id_flow(self): flag = "--os-project-domain-id " + DEFAULT_PROJECT_DOMAIN_ID kwargs = { - "auth_url": "", - "project_id": "", - "project_name": "", - "domain_id": "", - "domain_name": "", - "user_domain_id": "", - "user_domain_name": "", "project_domain_id": DEFAULT_PROJECT_DOMAIN_ID, - "project_domain_name": "", - "username": "", - "password": "", - "region_name": "", - "trust_id": "", - "auth_type": "", } self._assert_password_auth(flag, kwargs) def test_only_project_domain_name_flow(self): flag = "--os-project-domain-name " + DEFAULT_PROJECT_DOMAIN_NAME kwargs = { - "auth_url": "", - "project_id": "", - "project_name": "", - "domain_id": "", - "domain_name": "", - "user_domain_id": "", - "user_domain_name": "", - "project_domain_id": "", "project_domain_name": DEFAULT_PROJECT_DOMAIN_NAME, - "username": "", - "password": "", - "region_name": "", - "trust_id": "", - "auth_type": "", } self._assert_password_auth(flag, kwargs) def test_only_username_flow(self): flag = "--os-username " + DEFAULT_USERNAME kwargs = { - "auth_url": "", - "project_id": "", - "project_name": "", - "domain_id": "", - "domain_name": "", - "user_domain_id": "", - "user_domain_name": "", - "project_domain_id": "", - "project_domain_name": "", "username": DEFAULT_USERNAME, - "password": "", - "region_name": "", - "trust_id": "", - "auth_type": "", } self._assert_password_auth(flag, kwargs) def test_only_password_flow(self): flag = "--os-password " + DEFAULT_PASSWORD kwargs = { - "auth_url": "", - "project_id": "", - "project_name": "", - "domain_id": "", - "domain_name": "", - "user_domain_id": "", - "user_domain_name": "", - "project_domain_id": "", - "project_domain_name": "", - "username": "", "password": DEFAULT_PASSWORD, - "region_name": "", - "trust_id": "", - "auth_type": "", } self._assert_password_auth(flag, kwargs) def test_only_region_name_flow(self): flag = "--os-region-name " + DEFAULT_REGION_NAME kwargs = { - "auth_url": "", - "project_id": "", - "project_name": "", - "domain_id": "", - "domain_name": "", - "user_domain_id": "", - "user_domain_name": "", - "project_domain_id": "", - "project_domain_name": "", - "username": "", - "password": "", "region_name": DEFAULT_REGION_NAME, - "trust_id": "", - "auth_type": "", } self._assert_password_auth(flag, kwargs) def test_only_trust_id_flow(self): flag = "--os-trust-id " + "1234" kwargs = { - "auth_url": "", - "project_id": "", - "project_name": "", - "domain_id": "", - "domain_name": "", - "user_domain_id": "", - "user_domain_name": "", - "project_domain_id": "", - "project_domain_name": "", - "username": "", - "password": "", - "region_name": "", "trust_id": "1234", - "auth_type": "", } self._assert_password_auth(flag, kwargs) def test_only_auth_type_flow(self): flag = "--os-auth-type " + "v2password" kwargs = { - "auth_url": "", - "project_id": "", - "project_name": "", - "domain_id": "", - "domain_name": "", - "user_domain_id": "", - "user_domain_name": "", - "project_domain_id": "", - "project_domain_name": "", - "username": "", - "password": "", - "region_name": "", - "trust_id": "", "auth_type": DEFAULT_AUTH_PLUGIN } self._assert_password_auth(flag, kwargs) class TestShellTokenAuth(TestShell): + def test_only_token(self): + flag = "--os-token " + DEFAULT_TOKEN + kwargs = { + "token": DEFAULT_TOKEN, + "auth_url": '', + } + self._assert_token_auth(flag, kwargs) + + def test_only_auth_url(self): + flag = "--os-auth-url " + DEFAULT_AUTH_URL + kwargs = { + "token": '', + "auth_url": DEFAULT_AUTH_URL, + } + self._assert_token_auth(flag, kwargs) + + def test_empty_auth(self): + os.environ = {} + flag = "" + kwargs = {} + self._assert_token_auth(flag, kwargs) + + +class TestShellTokenAuthEnv(TestShell): def setUp(self): - super(TestShellTokenAuth, self).setUp() + super(TestShellTokenAuthEnv, self).setUp() env = { "OS_TOKEN": DEFAULT_TOKEN, - "OS_AUTH_URL": DEFAULT_SERVICE_URL, + "OS_AUTH_URL": DEFAULT_AUTH_URL, } self.orig_env, os.environ = os.environ, env.copy() def tearDown(self): - super(TestShellTokenAuth, self).tearDown() + super(TestShellTokenAuthEnv, self).tearDown() os.environ = self.orig_env - def test_default_auth(self): + def test_env(self): flag = "" kwargs = { - "os_token": DEFAULT_TOKEN, - "os_auth_url": DEFAULT_SERVICE_URL + "token": DEFAULT_TOKEN, + "auth_url": DEFAULT_AUTH_URL, + } + self._assert_token_auth(flag, kwargs) + + def test_only_token(self): + flag = "--os-token xyzpdq" + kwargs = { + "token": "xyzpdq", + "auth_url": DEFAULT_AUTH_URL, + } + self._assert_token_auth(flag, kwargs) + + def test_only_auth_url(self): + flag = "--os-auth-url http://cloud.local:555" + kwargs = { + "token": DEFAULT_TOKEN, + "auth_url": "http://cloud.local:555", + } + self._assert_token_auth(flag, kwargs) + + def test_empty_auth(self): + os.environ = {} + flag = "" + kwargs = { + "token": '', + "auth_url": '', + } + self._assert_token_auth(flag, kwargs) + + +class TestShellTokenEndpointAuth(TestShell): + def test_only_token(self): + flag = "--os-token " + DEFAULT_TOKEN + kwargs = { + "token": DEFAULT_TOKEN, + "url": '', + } + self._assert_token_endpoint_auth(flag, kwargs) + + def test_only_url(self): + flag = "--os-url " + DEFAULT_SERVICE_URL + kwargs = { + "token": '', + "url": DEFAULT_SERVICE_URL, + } + self._assert_token_endpoint_auth(flag, kwargs) + + def test_empty_auth(self): + os.environ = {} + flag = "" + kwargs = { + "token": '', + "auth_url": '', + } + self._assert_token_endpoint_auth(flag, kwargs) + + +class TestShellTokenEndpointAuthEnv(TestShell): + def setUp(self): + super(TestShellTokenEndpointAuthEnv, self).setUp() + env = { + "OS_TOKEN": DEFAULT_TOKEN, + "OS_URL": DEFAULT_SERVICE_URL, + } + self.orig_env, os.environ = os.environ, env.copy() + + def tearDown(self): + super(TestShellTokenEndpointAuthEnv, self).tearDown() + os.environ = self.orig_env + + def test_env(self): + flag = "" + kwargs = { + "token": DEFAULT_TOKEN, + "url": DEFAULT_SERVICE_URL, + } + self._assert_token_auth(flag, kwargs) + + def test_only_token(self): + flag = "--os-token xyzpdq" + kwargs = { + "token": "xyzpdq", + "url": DEFAULT_SERVICE_URL, + } + self._assert_token_auth(flag, kwargs) + + def test_only_url(self): + flag = "--os-url http://cloud.local:555" + kwargs = { + "token": DEFAULT_TOKEN, + "url": "http://cloud.local:555", } self._assert_token_auth(flag, kwargs) @@ -480,8 +465,8 @@ class TestShellTokenAuth(TestShell): os.environ = {} flag = "" kwargs = { - "os_token": "", - "os_auth_url": "" + "token": '', + "url": '', } self._assert_token_auth(flag, kwargs) diff --git a/openstackclient/tests/volume/test_find_resource.py b/openstackclient/tests/volume/test_find_resource.py index 56081966..00cc46a6 100644 --- a/openstackclient/tests/volume/test_find_resource.py +++ b/openstackclient/tests/volume/test_find_resource.py @@ -24,6 +24,13 @@ from openstackclient.tests import utils as test_utils from openstackclient.volume import client # noqa +# Monkey patch for v1 cinderclient +# NOTE(dtroyer): Do here because openstackclient.volume.client +# doesn't do it until the client object is created now. +volumes.Volume.NAME_ATTR = 'display_name' +volume_snapshots.Snapshot.NAME_ATTR = 'display_name' + + ID = '1after909' NAME = 'PhilSpector' diff --git a/openstackclient/volume/client.py b/openstackclient/volume/client.py index 21072aeb..a7b64def 100644 --- a/openstackclient/volume/client.py +++ b/openstackclient/volume/client.py @@ -15,17 +15,8 @@ import logging -from cinderclient import extension -from cinderclient.v1.contrib import list_extensions -from cinderclient.v1 import volume_snapshots -from cinderclient.v1 import volumes - from openstackclient.common import utils -# Monkey patch for v1 cinderclient -volumes.Volume.NAME_ATTR = 'display_name' -volume_snapshots.Snapshot.NAME_ATTR = 'display_name' - LOG = logging.getLogger(__name__) DEFAULT_VOLUME_API_VERSION = '1' @@ -38,6 +29,17 @@ API_VERSIONS = { def make_client(instance): """Returns a volume service client.""" + + # Defer client imports until we actually need them + from cinderclient import extension + from cinderclient.v1.contrib import list_extensions + from cinderclient.v1 import volume_snapshots + from cinderclient.v1 import volumes + + # Monkey patch for v1 cinderclient + volumes.Volume.NAME_ATTR = 'display_name' + volume_snapshots.Snapshot.NAME_ATTR = 'display_name' + volume_client = utils.get_client_class( API_NAME, instance._api_version[API_NAME], |
