diff options
Diffstat (limited to 'openstackclient')
36 files changed, 1546 insertions, 599 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/security_group.py b/openstackclient/compute/v2/security_group.py index d4643438..55405810 100644 --- a/openstackclient/compute/v2/security_group.py +++ b/openstackclient/compute/v2/security_group.py @@ -81,9 +81,11 @@ class CreateSecurityGroup(show.ShowOne): compute_client = self.app.client_manager.compute + description = parsed_args.description or parsed_args.name + data = compute_client.security_groups.create( parsed_args.name, - parsed_args.description, + description, ) info = {} @@ -290,6 +292,7 @@ class CreateSecurityGroupRule(show.ShowOne): parser.add_argument( "--dst-port", metavar="<port-range>", + default=(0, 0), action=parseractions.RangeAction, help="Destination port, may be a range: 137:139 (default: 0; " "only required for proto tcp and udp)", 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..2cc68c8d 100644 --- a/openstackclient/identity/common.py +++ b/openstackclient/identity/common.py @@ -17,6 +17,10 @@ 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 @@ -40,20 +44,58 @@ def find_service(identity_client, name_type_or_id): def find_domain(identity_client, name_or_id): - """Find a domain. + return _find_identity_resource(identity_client.domains, name_or_id, + domains.Domain) + + +def find_group(identity_client, name_or_id): + return _find_identity_resource(identity_client.groups, name_or_id, + groups.Group) + + +def find_project(identity_client, name_or_id): + return _find_identity_resource(identity_client.projects, name_or_id, + projects.Project) + + +def find_user(identity_client, name_or_id): + return _find_identity_resource(identity_client.users, name_or_id, + users.User) + - If the user does not have permissions to access the v3 domain API, e.g., - if the user is a project admin, assume that the domain given is the id - rather than the name. This method is used by the project list command, - so errors accessing the domain will be ignored and if the user has - access to the project API, everything will work fine. +def _find_identity_resource(identity_client_manager, name_or_id, + resource_type): + """Find a specific identity resource. + + Using keystoneclient's manager, attempt to find a specific resource by its + name or ID. If Forbidden to find the resource (a common case if the user + does not have permission), then return the resource by creating a local + instance of keystoneclient's Resource. + + The parameter identity_client_manager is a keystoneclient manager, + for example: keystoneclient.v3.users or keystoneclient.v3.projects. + + The parameter resource_type is a keystoneclient resource, for example: + keystoneclient.v3.users.User or keystoneclient.v3.projects.Project. + + :param identity_client_manager: the manager that contains the resource + :type identity_client_manager: `keystoneclient.base.CrudManager` + :param name_or_id: the resources's name or ID + :type name_or_id: string + :param resource_type: class that represents the resource type + :type resource_type: `keystoneclient.base.Resource` + + :returns: the resource in question + :rtype: `keystoneclient.base.Resource` - Closes bugs #1317478 and #1317485. """ + try: - dom = utils.find_resource(identity_client.domains, name_or_id) - if dom is not None: - return dom + identity_resource = utils.find_resource(identity_client_manager, + name_or_id) + if identity_resource is not None: + return identity_resource except identity_exc.Forbidden: pass - return domains.Domain(None, {'id': name_or_id}) + + return resource_type(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/identity_provider.py b/openstackclient/identity/v3/identity_provider.py index 691446da..80965800 100644 --- a/openstackclient/identity/v3/identity_provider.py +++ b/openstackclient/identity/v3/identity_provider.py @@ -35,6 +35,20 @@ class CreateIdentityProvider(show.ShowOne): metavar='<name>', help='New identity provider name (must be unique)' ) + identity_remote_id_provider = parser.add_mutually_exclusive_group() + identity_remote_id_provider.add_argument( + '--remote-id', + metavar='<remote-id>', + action='append', + help='Remote IDs to associate with the Identity Provider ' + '(repeat to provide multiple values)' + ) + identity_remote_id_provider.add_argument( + '--remote-id-file', + metavar='<file-name>', + help='Name of a file that contains many remote IDs to associate ' + 'with the identity provider, one per line' + ) parser.add_argument( '--description', metavar='<description>', @@ -59,8 +73,17 @@ class CreateIdentityProvider(show.ShowOne): def take_action(self, parsed_args): self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity + if parsed_args.remote_id_file: + file_content = utils.read_blob_file_contents( + parsed_args.remote_id_file) + remote_ids = file_content.splitlines() + remote_ids = list(map(str.strip, remote_ids)) + else: + remote_ids = (parsed_args.remote_id + if parsed_args.remote_id else None) idp = identity_client.federation.identity_providers.create( id=parsed_args.identity_provider_id, + remote_ids=remote_ids, description=parsed_args.description, enabled=parsed_args.enabled) @@ -119,6 +142,20 @@ class SetIdentityProvider(command.Command): metavar='<identity-provider>', help='Identity provider to modify', ) + identity_remote_id_provider = parser.add_mutually_exclusive_group() + identity_remote_id_provider.add_argument( + '--remote-id', + metavar='<remote-id>', + action='append', + help='Remote IDs to associate with the Identity Provider ' + '(repeat to provide multiple values)' + ) + identity_remote_id_provider.add_argument( + '--remote-id-file', + metavar='<file-name>', + help='Name of a file that contains many remote IDs to associate ' + 'with the identity provider, one per line' + ) enable_identity_provider = parser.add_mutually_exclusive_group() enable_identity_provider.add_argument( '--enable', @@ -136,16 +173,33 @@ class SetIdentityProvider(command.Command): self.log.debug('take_action(%s)', parsed_args) federation_client = self.app.client_manager.identity.federation - if parsed_args.enable is True: - enabled = True - elif parsed_args.disable is True: - enabled = False - else: - self.log.error("No changes requested") + # Basic argument checking + if (not parsed_args.enable and not parsed_args.disable and not + parsed_args.remote_id and not parsed_args.remote_id_file): + self.log.error('No changes requested') return (None, None) + # Always set remote_ids if either is passed in + if parsed_args.remote_id_file: + file_content = utils.read_blob_file_contents( + parsed_args.remote_id_file) + remote_ids = file_content.splitlines() + remote_ids = list(map(str.strip, remote_ids)) + elif parsed_args.remote_id: + remote_ids = parsed_args.remote_id + + # Setup keyword args for the client + kwargs = {} + if parsed_args.enable: + kwargs['enabled'] = True + if parsed_args.disable: + kwargs['enabled'] = False + if parsed_args.remote_id_file or parsed_args.remote_id: + kwargs['remote_ids'] = remote_ids + identity_provider = federation_client.identity_providers.update( - parsed_args.identity_provider, enabled=enabled) + parsed_args.identity_provider, **kwargs) + identity_provider._info.pop('links', None) return zip(*sorted(six.iteritems(identity_provider._info))) diff --git a/openstackclient/identity/v3/project.py b/openstackclient/identity/v3/project.py index 1c93ad5d..0cb3c453 100644 --- a/openstackclient/identity/v3/project.py +++ b/openstackclient/identity/v3/project.py @@ -47,6 +47,11 @@ class CreateProject(show.ShowOne): help='Domain owning the project (name or ID)', ) parser.add_argument( + '--parent', + metavar='<project>', + help='Parent of the project (name or ID)', + ) + parser.add_argument( '--description', metavar='<description>', help='Project description', @@ -80,11 +85,17 @@ 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 + + parent = None + if parsed_args.parent: + parent = utils.find_resource( + identity_client.projects, + parsed_args.parent, + ).id enabled = True if parsed_args.disable: @@ -97,6 +108,7 @@ class CreateProject(show.ShowOne): project = identity_client.projects.create( name=parsed_args.name, domain=domain, + parent=parent, description=parsed_args.description, enabled=enabled, **kwargs @@ -111,8 +123,6 @@ class CreateProject(show.ShowOne): raise e project._info.pop('links') - # TODO(stevemar): Remove the line below when we support multitenancy - project._info.pop('parent_id', None) return zip(*sorted(six.iteritems(project._info))) 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/client.py b/openstackclient/image/client.py index 35779664..c78f4425 100644 --- a/openstackclient/image/client.py +++ b/openstackclient/image/client.py @@ -15,9 +15,6 @@ import logging -from glanceclient import exc as gc_exceptions -from glanceclient.v1 import client as gc_v1_client -from glanceclient.v1 import images as gc_v1_images from openstackclient.common import utils @@ -27,7 +24,7 @@ DEFAULT_IMAGE_API_VERSION = '1' API_VERSION_OPTION = 'os_image_api_version' API_NAME = "image" API_VERSIONS = { - "1": "openstackclient.image.client.Client_v1", + "1": "glanceclient.v1.client.Client", "2": "glanceclient.v2.client.Client", } @@ -89,54 +86,3 @@ def build_option_parser(parser): DEFAULT_IMAGE_API_VERSION + ' (Env: OS_IMAGE_API_VERSION)') return parser - - -# NOTE(dtroyer): glanceclient.v1.image.ImageManager() doesn't have a find() -# method so add one here until the common client libs arrive -# A similar subclass will be required for v2 - -class Client_v1(gc_v1_client.Client): - """An image v1 client that uses ImageManager_v1""" - - def __init__(self, *args, **kwargs): - super(Client_v1, self).__init__(*args, **kwargs) - self.images = ImageManager_v1(getattr(self, 'http_client', self)) - - -class ImageManager_v1(gc_v1_images.ImageManager): - """Add find() and findall() to the ImageManager class""" - - def find(self, **kwargs): - """Find a single item with attributes matching ``**kwargs``. - - This isn't very efficient: it loads the entire list then filters on - the Python side. - """ - rl = self.findall(**kwargs) - num = len(rl) - - if num == 0: - raise gc_exceptions.NotFound - elif num > 1: - raise gc_exceptions.NoUniqueMatch - else: - return rl[0] - - def findall(self, **kwargs): - """Find all items with attributes matching ``**kwargs``. - - This isn't very efficient: it loads the entire list then filters on - the Python side. - """ - found = [] - searches = kwargs.items() - - for obj in self.list(): - try: - if all(getattr(obj, attr) == value - for (attr, value) in searches): - found.append(obj) - except AttributeError: - continue - - return found diff --git a/openstackclient/image/v1/image.py b/openstackclient/image/v1/image.py index 127a7735..830b99ba 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: @@ -348,7 +348,7 @@ class ListImage(lister.Lister): help='List additional fields in output', ) - # --page-size has never worked, leave here for silent compatability + # --page-size has never worked, leave here for silent compatibility # We'll implement limit/marker differently later parser.add_argument( "--page-size", @@ -405,7 +405,17 @@ class ListImage(lister.Lister): columns = ("ID", "Name") column_headers = columns - data = image_client.api.image_list(**kwargs) + # List of image data received + data = [] + # No pages received yet, so start the page marker at None. + marker = None + while True: + page = image_client.api.image_list(marker=marker, **kwargs) + if not page: + break + data.extend(page) + # Set the marker to the id of the last item we received + marker = page[-1]['id'] if parsed_args.property: # NOTE(dtroyer): coerce to a list to subscript it in py3 diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index afc99e85..0b2becb8 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -98,7 +98,7 @@ class ListImage(lister.Lister): help='List additional fields in output', ) - # --page-size has never worked, leave here for silent compatability + # --page-size has never worked, leave here for silent compatibility # We'll implement limit/marker differently later parser.add_argument( "--page-size", @@ -156,7 +156,17 @@ class ListImage(lister.Lister): columns = ("ID", "Name") column_headers = columns - data = image_client.api.image_list(**kwargs) + # List of image data received + data = [] + # No pages received yet, so start the page marker at None. + marker = None + while True: + page = image_client.api.image_list(marker=marker, **kwargs) + if not page: + break + data.extend(page) + # Set the marker to the id of the last item we received + marker = page[-1]['id'] if parsed_args.property: # NOTE(dtroyer): coerce to a list to subscript it in py3 @@ -238,5 +248,5 @@ class ShowImage(show.ShowOne): ) info = {} - info.update(image._info) + info.update(image) return zip(*sorted(six.iteritems(info))) diff --git a/openstackclient/shell.py b/openstackclient/shell.py index 3cfd7312..5e291021 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,9 +20,11 @@ import getpass import logging import sys import traceback +import warnings from cliff import app from cliff import command +from cliff import complete from cliff import help import openstackclient @@ -31,6 +34,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' @@ -72,55 +77,24 @@ class OpenStackShell(app.App): # Some commands do not need authentication help.HelpCommand.auth_required = False + complete.CompleteCommand.auth_required = False 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 +113,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) @@ -162,12 +139,11 @@ class OpenStackShell(app.App): # --debug forces traceback self.dump_stack_trace = True requests_log.setLevel(logging.DEBUG) - cliff_log.setLevel(logging.DEBUG) else: self.dump_stack_trace = False requests_log.setLevel(logging.ERROR) - cliff_log.setLevel(logging.ERROR) + cliff_log.setLevel(logging.ERROR) stevedore_log.setLevel(logging.ERROR) iso8601_log.setLevel(logging.ERROR) @@ -188,10 +164,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 +221,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 +267,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 +301,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, @@ -301,26 +319,19 @@ class OpenStackShell(app.App): cmd.__class__.__name__, ) if cmd.auth_required: - try: - # Trigger the Identity client to initialize - self.client_manager.auth_ref - except Exception: - pass + # Trigger the Identity client to initialize + self.client_manager.auth_ref return def clean_up(self, cmd, result, err): - self.log.debug('clean_up %s', cmd.__class__.__name__) - - if err: - self.log.debug('got an error: %s', err) + self.log.debug('clean_up %s: %s', cmd.__class__.__name__, err or '') # 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/compute/v2/fakes.py b/openstackclient/tests/compute/v2/fakes.py index a22c1ce0..c18dea7e 100644 --- a/openstackclient/tests/compute/v2/fakes.py +++ b/openstackclient/tests/compute/v2/fakes.py @@ -16,6 +16,7 @@ import mock from openstackclient.tests import fakes +from openstackclient.tests.identity.v2_0 import fakes as identity_fakes from openstackclient.tests.image.v2 import fakes as image_fakes from openstackclient.tests.network.v2 import fakes as network_fakes from openstackclient.tests import utils @@ -85,6 +86,11 @@ class TestComputev2(utils.TestCommand): token=fakes.AUTH_TOKEN, ) + self.app.client_manager.identity = identity_fakes.FakeIdentityv2Client( + endpoint=fakes.AUTH_URL, + token=fakes.AUTH_TOKEN, + ) + self.app.client_manager.image = image_fakes.FakeImagev2Client( endpoint=fakes.AUTH_URL, token=fakes.AUTH_TOKEN, diff --git a/openstackclient/tests/compute/v2/test_security_group.py b/openstackclient/tests/compute/v2/test_security_group.py new file mode 100644 index 00000000..fdb659a8 --- /dev/null +++ b/openstackclient/tests/compute/v2/test_security_group.py @@ -0,0 +1,197 @@ +# 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. +# + +import copy +import mock + +from openstackclient.compute.v2 import security_group +from openstackclient.tests.compute.v2 import fakes as compute_fakes +from openstackclient.tests import fakes +from openstackclient.tests.identity.v2_0 import fakes as identity_fakes + + +security_group_id = '11' +security_group_name = 'wide-open' +security_group_description = 'nothing but net' + +SECURITY_GROUP = { + 'id': security_group_id, + 'name': security_group_name, + 'description': security_group_description, + 'tenant_id': identity_fakes.project_id, +} + + +class FakeSecurityGroupResource(fakes.FakeResource): + + def get_keys(self): + return {'property': 'value'} + + +class TestSecurityGroup(compute_fakes.TestComputev2): + + def setUp(self): + super(TestSecurityGroup, self).setUp() + + self.secgroups_mock = mock.Mock() + self.secgroups_mock.resource_class = fakes.FakeResource(None, {}) + self.app.client_manager.compute.security_groups = self.secgroups_mock + self.secgroups_mock.reset_mock() + + self.projects_mock = mock.Mock() + self.projects_mock.resource_class = fakes.FakeResource(None, {}) + self.app.client_manager.identity.projects = self.projects_mock + self.projects_mock.reset_mock() + + +class TestSecurityGroupCreate(TestSecurityGroup): + + def setUp(self): + super(TestSecurityGroupCreate, self).setUp() + + self.secgroups_mock.create.return_value = FakeSecurityGroupResource( + None, + copy.deepcopy(SECURITY_GROUP), + loaded=True, + ) + + # Get the command object to test + self.cmd = security_group.CreateSecurityGroup(self.app, None) + + def test_security_group_create_no_options(self): + arglist = [ + security_group_name, + ] + verifylist = [ + ('name', security_group_name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + # SecurityGroupManager.create(name, description) + self.secgroups_mock.create.assert_called_with( + security_group_name, + security_group_name, + ) + + collist = ( + 'description', + 'id', + 'name', + 'tenant_id', + ) + self.assertEqual(collist, columns) + datalist = ( + security_group_description, + security_group_id, + security_group_name, + identity_fakes.project_id, + ) + self.assertEqual(datalist, data) + + def test_security_group_create_description(self): + arglist = [ + security_group_name, + '--description', security_group_description, + ] + verifylist = [ + ('name', security_group_name), + ('description', security_group_description), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + # SecurityGroupManager.create(name, description) + self.secgroups_mock.create.assert_called_with( + security_group_name, + security_group_description, + ) + + collist = ( + 'description', + 'id', + 'name', + 'tenant_id', + ) + self.assertEqual(collist, columns) + datalist = ( + security_group_description, + security_group_id, + security_group_name, + identity_fakes.project_id, + ) + self.assertEqual(datalist, data) + + +class TestSecurityGroupList(TestSecurityGroup): + + def setUp(self): + super(TestSecurityGroupList, self).setUp() + + self.secgroups_mock.list.return_value = [ + FakeSecurityGroupResource( + None, + copy.deepcopy(SECURITY_GROUP), + loaded=True, + ), + ] + + # Get the command object to test + self.cmd = security_group.ListSecurityGroup(self.app, None) + + def test_security_group_list_no_options(self): + self.projects_mock.list.return_value = [ + fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.PROJECT), + loaded=True, + ), + ] + + arglist = [] + verifylist = [ + ('all_projects', False), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'search_opts': { + 'all_tenants': False, + }, + } + + self.secgroups_mock.list.assert_called_with( + **kwargs + ) + + collist = ( + 'ID', + 'Name', + 'Description', + ) + self.assertEqual(collist, columns) + datalist = (( + security_group_id, + security_group_name, + security_group_description, + ), ) + self.assertEqual(datalist, tuple(data)) diff --git a/openstackclient/tests/compute/v2/test_server.py b/openstackclient/tests/compute/v2/test_server.py index 079f301e..baf53742 100644 --- a/openstackclient/tests/compute/v2/test_server.py +++ b/openstackclient/tests/compute/v2/test_server.py @@ -361,14 +361,14 @@ class TestServerImageCreate(TestServer): compute_fakes.server_name, ) - collist = ('id', 'is_public', 'name', 'owner', 'protected') + collist = ('id', 'name', 'owner', 'protected', 'visibility') self.assertEqual(collist, columns) datalist = ( image_fakes.image_id, - image_fakes.image_public, image_fakes.image_name, image_fakes.image_owner, image_fakes.image_protected, + image_fakes.image_visibility, ) self.assertEqual(datalist, data) @@ -392,14 +392,14 @@ class TestServerImageCreate(TestServer): 'img-nam', ) - collist = ('id', 'is_public', 'name', 'owner', 'protected') + collist = ('id', 'name', 'owner', 'protected', 'visibility') self.assertEqual(collist, columns) datalist = ( image_fakes.image_id, - image_fakes.image_public, image_fakes.image_name, image_fakes.image_owner, image_fakes.image_protected, + image_fakes.image_visibility, ) self.assertEqual(datalist, data) diff --git a/openstackclient/tests/identity/v3/fakes.py b/openstackclient/tests/identity/v3/fakes.py index eb8673ef..dfbcf44f 100644 --- a/openstackclient/tests/identity/v3/fakes.py +++ b/openstackclient/tests/identity/v3/fakes.py @@ -135,6 +135,16 @@ REGION = { 'links': base_url + 'regions/' + region_id, } +PROJECT_WITH_PARENT = { + 'id': project_id + '-with-parent', + 'name': project_name + ' and their parents', + 'description': project_description + ' plus another four', + 'enabled': True, + 'domain_id': domain_id, + 'parent_id': project_id, + 'links': base_url + 'projects/' + (project_id + '-with-parent'), +} + role_id = 'r1' role_name = 'roller' @@ -231,9 +241,11 @@ TOKEN_WITH_DOMAIN_ID = { idp_id = 'test_idp' idp_description = 'super exciting IdP description' +idp_remote_ids = ['entity1', 'entity2'] IDENTITY_PROVIDER = { 'id': idp_id, + 'remote_ids': idp_remote_ids, 'enabled': True, 'description': idp_description } diff --git a/openstackclient/tests/identity/v3/test_identity_provider.py b/openstackclient/tests/identity/v3/test_identity_provider.py index 527f01b5..cd328c1d 100644 --- a/openstackclient/tests/identity/v3/test_identity_provider.py +++ b/openstackclient/tests/identity/v3/test_identity_provider.py @@ -14,6 +14,8 @@ import copy +import mock + from openstackclient.identity.v3 import identity_provider from openstackclient.tests import fakes from openstackclient.tests.identity.v3 import fakes as identity_fakes @@ -51,6 +53,7 @@ class TestIdentityProviderCreate(TestIdentityProvider): # Set expected values kwargs = { + 'remote_ids': None, 'enabled': True, 'description': None, } @@ -60,12 +63,13 @@ class TestIdentityProviderCreate(TestIdentityProvider): **kwargs ) - collist = ('description', 'enabled', 'id') + collist = ('description', 'enabled', 'id', 'remote_ids') self.assertEqual(collist, columns) datalist = ( identity_fakes.idp_description, True, identity_fakes.idp_id, + identity_fakes.idp_remote_ids ) self.assertEqual(datalist, data) @@ -83,6 +87,7 @@ class TestIdentityProviderCreate(TestIdentityProvider): # Set expected values kwargs = { + 'remote_ids': None, 'description': identity_fakes.idp_description, 'enabled': True, } @@ -92,12 +97,121 @@ class TestIdentityProviderCreate(TestIdentityProvider): **kwargs ) - collist = ('description', 'enabled', 'id') + collist = ('description', 'enabled', 'id', 'remote_ids') + self.assertEqual(collist, columns) + datalist = ( + identity_fakes.idp_description, + True, + identity_fakes.idp_id, + identity_fakes.idp_remote_ids + ) + self.assertEqual(datalist, data) + + def test_create_identity_provider_remote_id(self): + arglist = [ + identity_fakes.idp_id, + '--remote-id', identity_fakes.idp_remote_ids[0] + ] + verifylist = [ + ('identity_provider_id', identity_fakes.idp_id), + ('remote_id', identity_fakes.idp_remote_ids[:1]), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'remote_ids': identity_fakes.idp_remote_ids[:1], + 'description': None, + 'enabled': True, + } + + self.identity_providers_mock.create.assert_called_with( + id=identity_fakes.idp_id, + **kwargs + ) + + collist = ('description', 'enabled', 'id', 'remote_ids') + self.assertEqual(collist, columns) + datalist = ( + identity_fakes.idp_description, + True, + identity_fakes.idp_id, + identity_fakes.idp_remote_ids + ) + self.assertEqual(datalist, data) + + def test_create_identity_provider_remote_ids_multiple(self): + arglist = [ + '--remote-id', identity_fakes.idp_remote_ids[0], + '--remote-id', identity_fakes.idp_remote_ids[1], + identity_fakes.idp_id + ] + verifylist = [ + ('identity_provider_id', identity_fakes.idp_id), + ('remote_id', identity_fakes.idp_remote_ids), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'remote_ids': identity_fakes.idp_remote_ids, + 'description': None, + 'enabled': True, + } + + self.identity_providers_mock.create.assert_called_with( + id=identity_fakes.idp_id, + **kwargs + ) + + collist = ('description', 'enabled', 'id', 'remote_ids') self.assertEqual(collist, columns) datalist = ( identity_fakes.idp_description, True, identity_fakes.idp_id, + identity_fakes.idp_remote_ids + ) + self.assertEqual(datalist, data) + + def test_create_identity_provider_remote_ids_file(self): + arglist = [ + '--remote-id-file', '/tmp/file_name', + identity_fakes.idp_id, + ] + verifylist = [ + ('identity_provider_id', identity_fakes.idp_id), + ('remote_id_file', '/tmp/file_name'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + mocker = mock.Mock() + mocker.return_value = "\n".join(identity_fakes.idp_remote_ids) + with mock.patch("openstackclient.identity.v3.identity_provider." + "utils.read_blob_file_contents", mocker): + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'remote_ids': identity_fakes.idp_remote_ids, + 'description': None, + 'enabled': True, + } + + self.identity_providers_mock.create.assert_called_with( + id=identity_fakes.idp_id, + **kwargs + ) + + collist = ('description', 'enabled', 'id', 'remote_ids') + self.assertEqual(collist, columns) + datalist = ( + identity_fakes.idp_description, + True, + identity_fakes.idp_id, + identity_fakes.idp_remote_ids ) self.assertEqual(datalist, data) @@ -123,6 +237,7 @@ class TestIdentityProviderCreate(TestIdentityProvider): # Set expected values kwargs = { + 'remote_ids': None, 'enabled': False, 'description': None, } @@ -132,12 +247,13 @@ class TestIdentityProviderCreate(TestIdentityProvider): **kwargs ) - collist = ('description', 'enabled', 'id') + collist = ('description', 'enabled', 'id', 'remote_ids') self.assertEqual(collist, columns) datalist = ( None, False, identity_fakes.idp_id, + identity_fakes.idp_remote_ids ) self.assertEqual(datalist, data) @@ -241,12 +357,13 @@ class TestIdentityProviderShow(TestIdentityProvider): identity_fakes.idp_id, ) - collist = ('description', 'enabled', 'id') + collist = ('description', 'enabled', 'id', 'remote_ids') self.assertEqual(collist, columns) datalist = ( identity_fakes.idp_description, True, identity_fakes.idp_id, + identity_fakes.idp_remote_ids ) self.assertEqual(datalist, data) @@ -276,11 +393,14 @@ class TestIdentityProviderSet(TestIdentityProvider): prepare(self) arglist = [ '--disable', identity_fakes.idp_id, + '--remote-id', identity_fakes.idp_remote_ids[0], + '--remote-id', identity_fakes.idp_remote_ids[1] ] verifylist = [ ('identity_provider', identity_fakes.idp_id), ('enable', False), ('disable', True), + ('remote_id', identity_fakes.idp_remote_ids) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -288,14 +408,16 @@ class TestIdentityProviderSet(TestIdentityProvider): self.identity_providers_mock.update.assert_called_with( identity_fakes.idp_id, enabled=False, + remote_ids=identity_fakes.idp_remote_ids ) - collist = ('description', 'enabled', 'id') + collist = ('description', 'enabled', 'id', 'remote_ids') self.assertEqual(collist, columns) datalist = ( identity_fakes.idp_description, False, identity_fakes.idp_id, + identity_fakes.idp_remote_ids ) self.assertEqual(datalist, data) @@ -316,23 +438,122 @@ class TestIdentityProviderSet(TestIdentityProvider): prepare(self) arglist = [ '--enable', identity_fakes.idp_id, + '--remote-id', identity_fakes.idp_remote_ids[0], + '--remote-id', identity_fakes.idp_remote_ids[1] ] verifylist = [ ('identity_provider', identity_fakes.idp_id), ('enable', True), ('disable', False), + ('remote_id', identity_fakes.idp_remote_ids) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) self.identity_providers_mock.update.assert_called_with( - identity_fakes.idp_id, enabled=True) - collist = ('description', 'enabled', 'id') + identity_fakes.idp_id, enabled=True, + remote_ids=identity_fakes.idp_remote_ids) + collist = ('description', 'enabled', 'id', 'remote_ids') + self.assertEqual(collist, columns) + datalist = ( + identity_fakes.idp_description, + True, + identity_fakes.idp_id, + identity_fakes.idp_remote_ids + ) + self.assertEqual(datalist, data) + + def test_identity_provider_replace_remote_ids(self): + """Enable Identity Provider. + + Set Identity Provider's ``enabled`` attribute to True. + """ + def prepare(self): + """Prepare fake return objects before the test is executed""" + self.new_remote_id = 'new_entity' + + updated_idp = copy.deepcopy(identity_fakes.IDENTITY_PROVIDER) + updated_idp['remote_ids'] = [self.new_remote_id] + resources = fakes.FakeResource( + None, + updated_idp, + loaded=True + ) + self.identity_providers_mock.update.return_value = resources + + prepare(self) + arglist = [ + '--enable', identity_fakes.idp_id, + '--remote-id', self.new_remote_id + ] + verifylist = [ + ('identity_provider', identity_fakes.idp_id), + ('enable', True), + ('disable', False), + ('remote_id', [self.new_remote_id]) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.identity_providers_mock.update.assert_called_with( + identity_fakes.idp_id, enabled=True, + remote_ids=[self.new_remote_id]) + collist = ('description', 'enabled', 'id', 'remote_ids') + self.assertEqual(collist, columns) + datalist = ( + identity_fakes.idp_description, + True, + identity_fakes.idp_id, + [self.new_remote_id] + ) + self.assertEqual(datalist, data) + + def test_identity_provider_replace_remote_ids_file(self): + """Enable Identity Provider. + + Set Identity Provider's ``enabled`` attribute to True. + """ + def prepare(self): + """Prepare fake return objects before the test is executed""" + self.new_remote_id = 'new_entity' + + updated_idp = copy.deepcopy(identity_fakes.IDENTITY_PROVIDER) + updated_idp['remote_ids'] = [self.new_remote_id] + resources = fakes.FakeResource( + None, + updated_idp, + loaded=True + ) + self.identity_providers_mock.update.return_value = resources + + prepare(self) + arglist = [ + '--enable', identity_fakes.idp_id, + '--remote-id-file', self.new_remote_id, + ] + verifylist = [ + ('identity_provider', identity_fakes.idp_id), + ('enable', True), + ('disable', False), + ('remote_id_file', self.new_remote_id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + mocker = mock.Mock() + mocker.return_value = self.new_remote_id + with mock.patch("openstackclient.identity.v3.identity_provider." + "utils.read_blob_file_contents", mocker): + columns, data = self.cmd.take_action(parsed_args) + self.identity_providers_mock.update.assert_called_with( + identity_fakes.idp_id, enabled=True, + remote_ids=[self.new_remote_id]) + collist = ('description', 'enabled', 'id', 'remote_ids') self.assertEqual(collist, columns) datalist = ( identity_fakes.idp_description, True, identity_fakes.idp_id, + [self.new_remote_id] ) self.assertEqual(datalist, data) @@ -361,6 +582,7 @@ class TestIdentityProviderSet(TestIdentityProvider): ('identity_provider', identity_fakes.idp_id), ('enable', False), ('disable', False), + ('remote_id', None) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) diff --git a/openstackclient/tests/identity/v3/test_project.py b/openstackclient/tests/identity/v3/test_project.py index 874908da..ec50da0c 100644 --- a/openstackclient/tests/identity/v3/test_project.py +++ b/openstackclient/tests/identity/v3/test_project.py @@ -16,6 +16,7 @@ import copy import mock +from openstackclient.common import exceptions from openstackclient.identity.v3 import project from openstackclient.tests import fakes from openstackclient.tests.identity.v3 import fakes as identity_fakes @@ -60,6 +61,7 @@ class TestProjectCreate(TestProject): identity_fakes.project_name, ] verifylist = [ + ('parent', None), ('enable', False), ('disable', False), ('name', identity_fakes.project_name), @@ -75,6 +77,7 @@ class TestProjectCreate(TestProject): 'domain': None, 'description': None, 'enabled': True, + 'parent': None, } # ProjectManager.create(name=, domain=, description=, # enabled=, **kwargs) @@ -103,6 +106,7 @@ class TestProjectCreate(TestProject): ('enable', False), ('disable', False), ('name', identity_fakes.project_name), + ('parent', None), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -115,6 +119,7 @@ class TestProjectCreate(TestProject): 'domain': None, 'description': 'new desc', 'enabled': True, + 'parent': None, } # ProjectManager.create(name=, domain=, description=, # enabled=, **kwargs) @@ -143,6 +148,7 @@ class TestProjectCreate(TestProject): ('enable', False), ('disable', False), ('name', identity_fakes.project_name), + ('parent', None), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -155,6 +161,7 @@ class TestProjectCreate(TestProject): 'domain': identity_fakes.domain_id, 'description': None, 'enabled': True, + 'parent': None, } # ProjectManager.create(name=, domain=, description=, # enabled=, **kwargs) @@ -183,6 +190,7 @@ class TestProjectCreate(TestProject): ('enable', False), ('disable', False), ('name', identity_fakes.project_name), + ('parent', None), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) mocker = mock.Mock() @@ -197,6 +205,7 @@ class TestProjectCreate(TestProject): 'domain': identity_fakes.domain_id, 'description': None, 'enabled': True, + 'parent': None, } self.projects_mock.create.assert_called_with( **kwargs @@ -221,6 +230,7 @@ class TestProjectCreate(TestProject): ('enable', True), ('disable', False), ('name', identity_fakes.project_name), + ('parent', None), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -233,6 +243,7 @@ class TestProjectCreate(TestProject): 'domain': None, 'description': None, 'enabled': True, + 'parent': None, } # ProjectManager.create(name=, domain=, description=, # enabled=, **kwargs) @@ -260,6 +271,7 @@ class TestProjectCreate(TestProject): ('enable', False), ('disable', True), ('name', identity_fakes.project_name), + ('parent', None), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -272,6 +284,7 @@ class TestProjectCreate(TestProject): 'domain': None, 'description': None, 'enabled': False, + 'parent': None, } # ProjectManager.create(name=, domain=, # description=, enabled=, **kwargs) @@ -311,6 +324,7 @@ class TestProjectCreate(TestProject): 'domain': None, 'description': None, 'enabled': True, + 'parent': None, 'fee': 'fi', 'fo': 'fum', } @@ -331,6 +345,92 @@ class TestProjectCreate(TestProject): ) self.assertEqual(datalist, data) + def test_project_create_parent(self): + self.projects_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.PROJECT), + loaded=True, + ) + self.projects_mock.create.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.PROJECT_WITH_PARENT), + loaded=True, + ) + + arglist = [ + '--domain', identity_fakes.PROJECT_WITH_PARENT['domain_id'], + '--parent', identity_fakes.PROJECT['name'], + identity_fakes.PROJECT_WITH_PARENT['name'], + ] + verifylist = [ + ('domain', identity_fakes.PROJECT_WITH_PARENT['domain_id']), + ('parent', identity_fakes.PROJECT['name']), + ('enable', False), + ('disable', False), + ('name', identity_fakes.PROJECT_WITH_PARENT['name']), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + kwargs = { + 'name': identity_fakes.PROJECT_WITH_PARENT['name'], + 'domain': identity_fakes.PROJECT_WITH_PARENT['domain_id'], + 'parent': identity_fakes.PROJECT['id'], + 'description': None, + 'enabled': True, + } + + self.projects_mock.create.assert_called_with( + **kwargs + ) + + collist = ( + 'description', + 'domain_id', + 'enabled', + 'id', + 'name', + 'parent_id', + ) + self.assertEqual(columns, collist) + datalist = ( + identity_fakes.PROJECT_WITH_PARENT['description'], + identity_fakes.PROJECT_WITH_PARENT['domain_id'], + identity_fakes.PROJECT_WITH_PARENT['enabled'], + identity_fakes.PROJECT_WITH_PARENT['id'], + identity_fakes.PROJECT_WITH_PARENT['name'], + identity_fakes.PROJECT['id'], + ) + self.assertEqual(data, datalist) + + def test_project_create_invalid_parent(self): + self.projects_mock.resource_class.__name__ = 'Project' + self.projects_mock.get.side_effect = exceptions.NotFound( + 'Invalid parent') + self.projects_mock.find.side_effect = exceptions.NotFound( + 'Invalid parent') + + arglist = [ + '--domain', identity_fakes.domain_name, + '--parent', 'invalid', + identity_fakes.project_name, + ] + verifylist = [ + ('domain', identity_fakes.domain_name), + ('parent', 'invalid'), + ('enable', False), + ('disable', False), + ('name', identity_fakes.project_name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args, + ) + class TestProjectDelete(TestProject): diff --git a/openstackclient/tests/image/v1/test_image.py b/openstackclient/tests/image/v1/test_image.py index 2776e744..ef7ca9ea 100644 --- a/openstackclient/tests/image/v1/test_image.py +++ b/openstackclient/tests/image/v1/test_image.py @@ -306,8 +306,8 @@ class TestImageList(TestImage): super(TestImageList, self).setUp() self.api_mock = mock.Mock() - self.api_mock.image_list.return_value = [ - copy.deepcopy(image_fakes.IMAGE), + self.api_mock.image_list.side_effect = [ + [copy.deepcopy(image_fakes.IMAGE)], [], ] self.app.client_manager.image.api = self.api_mock @@ -327,6 +327,7 @@ class TestImageList(TestImage): columns, data = self.cmd.take_action(parsed_args) self.api_mock.image_list.assert_called_with( detailed=False, + marker=image_fakes.image_id, ) collist = ('ID', 'Name') @@ -354,6 +355,7 @@ class TestImageList(TestImage): self.api_mock.image_list.assert_called_with( detailed=False, public=True, + marker=image_fakes.image_id, ) collist = ('ID', 'Name') @@ -381,6 +383,7 @@ class TestImageList(TestImage): self.api_mock.image_list.assert_called_with( detailed=False, private=True, + marker=image_fakes.image_id, ) collist = ('ID', 'Name') @@ -405,6 +408,7 @@ class TestImageList(TestImage): columns, data = self.cmd.take_action(parsed_args) self.api_mock.image_list.assert_called_with( detailed=True, + marker=image_fakes.image_id, ) collist = ( @@ -437,8 +441,8 @@ class TestImageList(TestImage): @mock.patch('openstackclient.api.utils.simple_filter') def test_image_list_property_option(self, sf_mock): - sf_mock.return_value = [ - copy.deepcopy(image_fakes.IMAGE), + sf_mock.side_effect = [ + [copy.deepcopy(image_fakes.IMAGE)], [], ] arglist = [ @@ -453,6 +457,7 @@ class TestImageList(TestImage): columns, data = self.cmd.take_action(parsed_args) self.api_mock.image_list.assert_called_with( detailed=True, + marker=image_fakes.image_id, ) sf_mock.assert_called_with( [image_fakes.IMAGE], @@ -472,8 +477,8 @@ class TestImageList(TestImage): @mock.patch('openstackclient.common.utils.sort_items') def test_image_list_sort_option(self, si_mock): - si_mock.return_value = [ - copy.deepcopy(image_fakes.IMAGE) + si_mock.side_effect = [ + [copy.deepcopy(image_fakes.IMAGE)], [], ] arglist = ['--sort', 'name:asc'] @@ -483,7 +488,8 @@ class TestImageList(TestImage): # DisplayCommandBase.take_action() returns two tuples columns, data = self.cmd.take_action(parsed_args) self.api_mock.image_list.assert_called_with( - detailed=False + detailed=False, + marker=image_fakes.image_id, ) si_mock.assert_called_with( [image_fakes.IMAGE], @@ -653,3 +659,36 @@ class TestImageSet(TestImage): image_fakes.image_id, **kwargs ) + + +class TestImageShow(TestImage): + + def setUp(self): + super(TestImageShow, self).setUp() + + self.images_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(image_fakes.IMAGE), + loaded=True, + ) + + # Get the command object to test + self.cmd = image.ShowImage(self.app, None) + + def test_image_show(self): + arglist = [ + image_fakes.image_id, + ] + verifylist = [ + ('image', image_fakes.image_id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + self.images_mock.get.assert_called_with( + image_fakes.image_id, + ) + + self.assertEqual(image_fakes.IMAGE_columns, columns) + self.assertEqual(image_fakes.IMAGE_data, data) diff --git a/openstackclient/tests/image/v2/fakes.py b/openstackclient/tests/image/v2/fakes.py index 1b7edf08..678291bb 100644 --- a/openstackclient/tests/image/v2/fakes.py +++ b/openstackclient/tests/image/v2/fakes.py @@ -19,18 +19,105 @@ from openstackclient.tests import fakes from openstackclient.tests import utils -image_id = 'im1' +image_id = '0f41529e-7c12-4de8-be2d-181abb825b3c' image_name = 'graven' image_owner = 'baal' -image_public = False image_protected = False +image_visibility = 'public' IMAGE = { 'id': image_id, 'name': image_name, - 'is_public': image_public, 'owner': image_owner, 'protected': image_protected, + 'visibility': image_visibility, +} + +IMAGE_columns = tuple(sorted(IMAGE)) +IMAGE_data = tuple((IMAGE[x] for x in sorted(IMAGE))) + +# Just enough v2 schema to do some testing +IMAGE_schema = { + "additionalProperties": { + "type": "string" + }, + "name": "image", + "links": [ + { + "href": "{self}", + "rel": "self" + }, + { + "href": "{file}", + "rel": "enclosure" + }, + { + "href": "{schema}", + "rel": "describedby" + } + ], + "properties": { + "id": { + "pattern": "^([0-9a-fA-F]){8}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){12}$", # noqa + "type": "string", + "description": "An identifier for the image" + }, + "name": { + "type": [ + "null", + "string" + ], + "description": "Descriptive name for the image", + "maxLength": 255 + }, + "owner": { + "type": [ + "null", + "string" + ], + "description": "Owner of the image", + "maxLength": 255 + }, + "protected": { + "type": "boolean", + "description": "If true, image will not be deletable." + }, + "self": { + "type": "string", + "description": "(READ-ONLY)" + }, + "schema": { + "type": "string", + "description": "(READ-ONLY)" + }, + "size": { + "type": [ + "null", + "integer" + ], + "description": "Size of image file in bytes (READ-ONLY)" + }, + "status": { + "enum": [ + "queued", + "saving", + "active", + "killed", + "deleted", + "pending_delete" + ], + "type": "string", + "description": "Status of the image (READ-ONLY)" + }, + "visibility": { + "enum": [ + "public", + "private" + ], + "type": "string", + "description": "Scope of image accessibility" + }, + } } diff --git a/openstackclient/tests/image/v2/test_image.py b/openstackclient/tests/image/v2/test_image.py index 6a28b1ec..73b5d39a 100644 --- a/openstackclient/tests/image/v2/test_image.py +++ b/openstackclient/tests/image/v2/test_image.py @@ -16,6 +16,9 @@ import copy import mock +import warlock + +from glanceclient.v2 import schemas from openstackclient.image.v2 import image from openstackclient.tests import fakes from openstackclient.tests.image.v2 import fakes as image_fakes @@ -70,8 +73,8 @@ class TestImageList(TestImage): super(TestImageList, self).setUp() self.api_mock = mock.Mock() - self.api_mock.image_list.return_value = [ - copy.deepcopy(image_fakes.IMAGE), + self.api_mock.image_list.side_effect = [ + [copy.deepcopy(image_fakes.IMAGE)], [], ] self.app.client_manager.image.api = self.api_mock @@ -90,7 +93,9 @@ class TestImageList(TestImage): # DisplayCommandBase.take_action() returns two tuples columns, data = self.cmd.take_action(parsed_args) - self.api_mock.image_list.assert_called_with() + self.api_mock.image_list.assert_called_with( + marker=image_fakes.image_id, + ) collist = ('ID', 'Name') @@ -117,6 +122,7 @@ class TestImageList(TestImage): columns, data = self.cmd.take_action(parsed_args) self.api_mock.image_list.assert_called_with( public=True, + marker=image_fakes.image_id, ) collist = ('ID', 'Name') @@ -144,6 +150,7 @@ class TestImageList(TestImage): columns, data = self.cmd.take_action(parsed_args) self.api_mock.image_list.assert_called_with( private=True, + marker=image_fakes.image_id, ) collist = ('ID', 'Name') @@ -171,6 +178,7 @@ class TestImageList(TestImage): columns, data = self.cmd.take_action(parsed_args) self.api_mock.image_list.assert_called_with( shared=True, + marker=image_fakes.image_id, ) collist = ('ID', 'Name') @@ -193,7 +201,9 @@ class TestImageList(TestImage): # DisplayCommandBase.take_action() returns two tuples columns, data = self.cmd.take_action(parsed_args) - self.api_mock.image_list.assert_called_with() + self.api_mock.image_list.assert_called_with( + marker=image_fakes.image_id, + ) collist = ( 'ID', @@ -216,7 +226,7 @@ class TestImageList(TestImage): '', '', '', - '', + 'public', False, image_fakes.image_owner, '', @@ -239,7 +249,9 @@ class TestImageList(TestImage): # DisplayCommandBase.take_action() returns two tuples columns, data = self.cmd.take_action(parsed_args) - self.api_mock.image_list.assert_called_with() + self.api_mock.image_list.assert_called_with( + marker=image_fakes.image_id, + ) sf_mock.assert_called_with( [image_fakes.IMAGE], attr='a', @@ -268,7 +280,9 @@ class TestImageList(TestImage): # DisplayCommandBase.take_action() returns two tuples columns, data = self.cmd.take_action(parsed_args) - self.api_mock.image_list.assert_called_with() + self.api_mock.image_list.assert_called_with( + marker=image_fakes.image_id, + ) si_mock.assert_called_with( [image_fakes.IMAGE], 'name:asc' @@ -282,3 +296,38 @@ class TestImageList(TestImage): image_fakes.image_name ), ) self.assertEqual(datalist, tuple(data)) + + +class TestImageShow(TestImage): + + def setUp(self): + super(TestImageShow, self).setUp() + + # Set up the schema + self.model = warlock.model_factory( + image_fakes.IMAGE_schema, + schemas.SchemaBasedModel, + ) + + self.images_mock.get.return_value = self.model(**image_fakes.IMAGE) + + # Get the command object to test + self.cmd = image.ShowImage(self.app, None) + + def test_image_show(self): + arglist = [ + image_fakes.image_id, + ] + verifylist = [ + ('image', image_fakes.image_id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + self.images_mock.get.assert_called_with( + image_fakes.image_id, + ) + + self.assertEqual(image_fakes.IMAGE_columns, columns) + self.assertEqual(image_fakes.IMAGE_data, 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], diff --git a/openstackclient/volume/v1/backup.py b/openstackclient/volume/v1/backup.py index 71c8ed38..03c63a05 100644 --- a/openstackclient/volume/v1/backup.py +++ b/openstackclient/volume/v1/backup.py @@ -64,7 +64,7 @@ class CreateBackup(show.ShowOne): parsed_args.volume).id backup = volume_client.backups.create( volume_id, - parsed_args.volume, + parsed_args.container, parsed_args.name, parsed_args.description ) |
