diff options
Diffstat (limited to 'openstackclient')
167 files changed, 4873 insertions, 3855 deletions
diff --git a/openstackclient/api/api.py b/openstackclient/api/api.py index bd2abd88..04d88f31 100644 --- a/openstackclient/api/api.py +++ b/openstackclient/api/api.py @@ -17,8 +17,9 @@ import simplejson as json from keystoneauth1 import exceptions as ks_exceptions from keystoneauth1 import session as ks_session +from osc_lib import exceptions -from openstackclient.common import exceptions +from openstackclient.i18n import _ class KeystoneSession(object): @@ -255,9 +256,11 @@ class BaseAPI(KeystoneSession): if len(data) == 1: return data[0] if len(data) > 1: - msg = "Multiple %s exist with %s='%s'" + msg = _("Multiple %(resource)s exist with %(attr)s='%(value)s'") raise exceptions.CommandError( - msg % (resource, attr, value), + msg % {'resource': resource, + 'attr': attr, + 'value': value} ) # Search by id @@ -265,8 +268,12 @@ class BaseAPI(KeystoneSession): data = getlist(kwargs) if len(data) == 1: return data[0] - msg = "No %s with a %s or ID of '%s' found" - raise exceptions.CommandError(msg % (resource, attr, value)) + msg = _("No %(resource)s with a %(attr)s or ID of '%(value)s' found") + raise exceptions.CommandError( + msg % {'resource': resource, + 'attr': attr, + 'value': value} + ) def find_bulk( self, @@ -314,10 +321,10 @@ class BaseAPI(KeystoneSession): bulk_list = self.find_bulk(path, **kwargs) num_bulk = len(bulk_list) if num_bulk == 0: - msg = "none found" + msg = _("none found") raise exceptions.NotFound(msg) elif num_bulk > 1: - msg = "many found" + msg = _("many found") raise RuntimeError(msg) return bulk_list[0] @@ -344,7 +351,7 @@ class BaseAPI(KeystoneSession): try: ret = self.find_one("/%s/detail" % (path), **kwargs) except ks_exceptions.NotFound: - msg = "%s not found" % value + msg = _("%s not found") % value raise exceptions.NotFound(msg) return ret diff --git a/openstackclient/api/auth.py b/openstackclient/api/auth.py index c74e8005..0c82fe9b 100644 --- a/openstackclient/api/auth.py +++ b/openstackclient/api/auth.py @@ -16,15 +16,12 @@ import argparse import logging -import stevedore +from keystoneauth1.loading import base +from osc_lib import exceptions as exc +from osc_lib import utils -from keystoneclient.auth import base - -from openstackclient.common import exceptions as exc -from openstackclient.common import utils from openstackclient.i18n import _ - LOG = logging.getLogger(__name__) # Initialize the list of Authentication plugins early in order @@ -37,15 +34,10 @@ OPTIONS_LIST = {} 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, - ) + PLUGIN_LIST = base.get_available_plugin_names() return PLUGIN_LIST @@ -55,8 +47,9 @@ def get_options_list(): global OPTIONS_LIST if not OPTIONS_LIST: - for plugin in get_plugin_list(): - for o in plugin.plugin.get_options(): + for plugin_name in get_plugin_list(): + plugin_options = base.get_plugin_options(plugin_name) + for o in plugin_options: os_name = o.dest.lower().replace('_', '-') os_env_name = 'OS_' + os_name.upper().replace('-', '_') OPTIONS_LIST.setdefault( @@ -66,7 +59,7 @@ def get_options_list(): # 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, + plugin_name, o.help, ) return OPTIONS_LIST @@ -83,7 +76,7 @@ def select_auth_plugin(options): if options.auth.get('url') and options.auth.get('token'): # service token authentication auth_plugin_name = 'token_endpoint' - elif options.auth_type in [plugin.name for plugin in PLUGIN_LIST]: + elif options.auth_type in PLUGIN_LIST: # A direct plugin name was given, use it auth_plugin_name = options.auth_type elif options.auth.get('username'): @@ -93,7 +86,7 @@ def select_auth_plugin(options): auth_plugin_name = 'v2password' else: # let keystoneclient figure it out itself - auth_plugin_name = 'osc_password' + auth_plugin_name = 'password' elif options.auth.get('token'): if options.identity_api_version == '3': auth_plugin_name = 'v3token' @@ -105,17 +98,19 @@ def select_auth_plugin(options): else: # The ultimate default is similar to the original behaviour, # but this time with version discovery - auth_plugin_name = 'osc_password' + auth_plugin_name = 'password' LOG.debug("Auth plugin %s selected", auth_plugin_name) return auth_plugin_name def build_auth_params(auth_plugin_name, cmd_options): - 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) + auth_plugin_loader = base.get_plugin_loader(auth_plugin_name) + auth_params = {opt.dest: opt.default + for opt in base.get_plugin_options(auth_plugin_name)} + auth_params.update(dict(cmd_options.auth)) # grab tenant from project for v2.0 API compatibility if auth_plugin_name.startswith("v2"): if 'project_id' in auth_params: @@ -127,58 +122,59 @@ def build_auth_params(auth_plugin_name, cmd_options): else: LOG.debug('no auth_type') # delay the plugin choice, grab every option - auth_plugin_class = None + auth_plugin_loader = None + auth_params = dict(cmd_options.auth) plugin_options = set([o.replace('-', '_') for o in get_options_list()]) for option in plugin_options: LOG.debug('fetching option %s', option) auth_params[option] = getattr(cmd_options.auth, option, None) - return (auth_plugin_class, auth_params) - - -def check_valid_auth_options(options, auth_plugin_name, required_scope=True): - """Perform basic option checking, provide helpful error messages. - - :param required_scope: indicate whether a scoped token is required - - """ - + return (auth_plugin_loader, auth_params) + + +def check_valid_authorization_options(options, auth_plugin_name): + """Validate authorization options, and provide helpful error messages.""" + if (options.auth.get('project_id') and not + options.auth.get('domain_id') and not + options.auth.get('domain_name') and not + options.auth.get('project_name') and not + options.auth.get('tenant_id') and not + options.auth.get('tenant_name')): + raise exc.CommandError(_( + 'Missing parameter(s): ' + 'Set either a project or a domain scope, but not both. Set a ' + 'project scope with --os-project-name, OS_PROJECT_NAME, or ' + 'auth.project_name. Alternatively, set a domain scope with ' + '--os-domain-name, OS_DOMAIN_NAME or auth.domain_name.')) + + +def check_valid_authentication_options(options, auth_plugin_name): + """Validate authentication options, and provide helpful error messages.""" + + # Get all the options defined within the plugin. + plugin_opts = base.get_plugin_options(auth_plugin_name) + plugin_opts = {opt.dest: opt for opt in plugin_opts} + + # NOTE(aloga): this is an horrible hack. We need a way to specify the + # required options in the plugins. Using the "required" argument for + # the oslo_config.cfg.Opt does not work, as it is not possible to load the + # plugin if the option is not defined, so the error will simply be: + # "NoMatchingPlugin: The plugin foobar could not be found" msgs = [] - if auth_plugin_name.endswith('password'): - if not options.auth.get('username'): - msgs.append(_('Set a username with --os-username, OS_USERNAME,' - ' or auth.username')) - if not options.auth.get('auth_url'): - msgs.append(_('Set an authentication URL, with --os-auth-url,' - ' OS_AUTH_URL or auth.auth_url')) - if (required_scope and not - options.auth.get('project_id') and not - options.auth.get('domain_id') and not - options.auth.get('domain_name') and not - options.auth.get('project_name') and not - options.auth.get('tenant_id') and not - options.auth.get('tenant_name')): - msgs.append(_('Set a scope, such as a project or domain, set a ' - 'project scope with --os-project-name, ' - 'OS_PROJECT_NAME or auth.project_name, set a domain ' - 'scope with --os-domain-name, OS_DOMAIN_NAME or ' - 'auth.domain_name')) - elif auth_plugin_name.endswith('token'): - if not options.auth.get('token'): - msgs.append(_('Set a token with --os-token, OS_TOKEN or ' - 'auth.token')) - if not options.auth.get('auth_url'): - msgs.append(_('Set a service AUTH_URL, with --os-auth-url, ' - 'OS_AUTH_URL or auth.auth_url')) - elif auth_plugin_name == 'token_endpoint': - if not options.auth.get('token'): - msgs.append(_('Set a token with --os-token, OS_TOKEN or ' - 'auth.token')) - if not options.auth.get('url'): - msgs.append(_('Set a service URL, with --os-url, OS_URL or ' - 'auth.url')) - + if 'password' in plugin_opts and not options.auth.get('username'): + msgs.append(_('Set a username with --os-username, OS_USERNAME,' + ' or auth.username')) + if 'auth_url' in plugin_opts and not options.auth.get('auth_url'): + msgs.append(_('Set a service AUTH_URL, with --os-auth-url, ' + 'OS_AUTH_URL or auth.auth_url')) + if 'url' in plugin_opts and not options.auth.get('url'): + msgs.append(_('Set a service URL, with --os-url, ' + 'OS_URL or auth.url')) + if 'token' in plugin_opts and not options.auth.get('token'): + msgs.append(_('Set a token with --os-token, ' + 'OS_TOKEN or auth.token')) if msgs: - raise exc.CommandError('Missing parameter(s): \n%s' % '\n'.join(msgs)) + raise exc.CommandError( + _('Missing parameter(s): \n%s') % '\n'.join(msgs)) def build_auth_plugins_option_parser(parser): @@ -188,16 +184,15 @@ def build_auth_plugins_option_parser(parser): authentication plugin. """ - available_plugins = [plugin.name for plugin in get_plugin_list()] + available_plugins = list(get_plugin_list()) parser.add_argument( '--os-auth-type', metavar='<auth-type>', dest='auth_type', default=utils.env('OS_AUTH_TYPE'), - help='Select an authentication type. Available types: ' + - ', '.join(available_plugins) + - '. Default: selected based on --os-username/--os-token' + - ' (Env: OS_AUTH_TYPE)', + help=_('Select an authentication type. Available types: %s.' + ' Default: selected based on --os-username/--os-token' + ' (Env: OS_AUTH_TYPE)') % ', '.join(available_plugins), choices=available_plugins ) # Maintain compatibility with old tenant env vars @@ -222,10 +217,10 @@ def build_auth_plugins_option_parser(parser): OPTIONS_LIST[o]['env'], utils.env(OPTIONS_LIST[o]['env']), ), - help='%s\n(Env: %s)' % ( - OPTIONS_LIST[o]['help'], - OPTIONS_LIST[o]['env'], - ), + help=_('%(help)s\n(Env: %(env)s)') % { + 'help': OPTIONS_LIST[o]['help'], + 'env': OPTIONS_LIST[o]['env'], + }, ) # add tenant-related options for compatibility # this is deprecated but still used in some tempest tests... diff --git a/openstackclient/api/auth_plugin.py b/openstackclient/api/auth_plugin.py index cff0b75d..dc47a688 100644 --- a/openstackclient/api/auth_plugin.py +++ b/openstackclient/api/auth_plugin.py @@ -15,93 +15,51 @@ import logging -from oslo_config import cfg -from six.moves.urllib import parse as urlparse +from keystoneauth1 import loading +from keystoneauth1 import token_endpoint -from keystoneclient.auth.identity.generic import password as ksc_password -from keystoneclient.auth import token_endpoint +from openstackclient.i18n import _ LOG = logging.getLogger(__name__) -class TokenEndpoint(token_endpoint.Token): +class TokenEndpoint(loading.BaseLoader): """Auth plugin to handle traditional token/endpoint usage - Implements the methods required to handle token authentication - with a user-specified token and service endpoint; no Identity calls - are made for re-scoping, service catalog lookups or the like. - - The purpose of this plugin is to get rid of the special-case paths - in the code to handle this authentication format. Its primary use - is for bootstrapping the Keystone database. + Keystoneauth contains a Token plugin class that now correctly + handles the token/endpoint auth compatible with OSC. However, + the AdminToken loader deprecates the 'url' argument, which breaks + OSC compatibility, so make one that works. """ - def __init__(self, url, token, **kwargs): + @property + def plugin_class(self): + return token_endpoint.Token + + def load_from_options(self, url, token, **kwargs): """A plugin for static authentication with an existing token :param string url: Service endpoint :param string token: Existing token """ - super(TokenEndpoint, self).__init__(endpoint=url, - token=token) - def get_auth_ref(self, session, **kwargs): - # Stub this method for compatibility - return None + return super(TokenEndpoint, self).load_from_options( + endpoint=url, + token=token, + ) - @classmethod def get_options(self): - options = super(TokenEndpoint, self).get_options() - - options.extend([ - # Maintain name 'url' for compatibility - cfg.StrOpt('url', - help='Specific service endpoint to use'), - cfg.StrOpt('token', - secret=True, - help='Authentication token to use'), - ]) - + """Return the legacy options""" + + options = [ + loading.Opt( + 'url', + help=_('Specific service endpoint to use'), + ), + loading.Opt( + 'token', + secret=True, + help=_('Authentication token to use'), + ), + ] return options - - -class OSCGenericPassword(ksc_password.Password): - """Auth plugin hack to work around broken Keystone configurations - - The default Keystone configuration uses http://localhost:xxxx in - admin_endpoint and public_endpoint and are returned in the links.href - attribute by the version routes. Deployments that do not set these - are unusable with newer keystoneclient version discovery. - - """ - - def create_plugin(self, session, version, url, raw_status=None): - """Handle default Keystone endpoint configuration - - Build the actual API endpoint from the scheme, host and port of the - original auth URL and the rest from the returned version URL. - """ - - ver_u = urlparse.urlparse(url) - - # Only hack this if it is the default setting - if ver_u.netloc.startswith('localhost'): - auth_u = urlparse.urlparse(self.auth_url) - # from original auth_url: scheme, netloc - # from api_url: path, query (basically, the rest) - url = urlparse.urlunparse(( - auth_u.scheme, - auth_u.netloc, - ver_u.path, - ver_u.params, - ver_u.query, - ver_u.fragment, - )) - LOG.debug('Version URL updated: %s', url) - - return super(OSCGenericPassword, self).create_plugin( - session=session, - version=version, - url=url, - raw_status=raw_status, - ) diff --git a/openstackclient/api/object_store_v1.py b/openstackclient/api/object_store_v1.py index 632e8b19..ae49922d 100644 --- a/openstackclient/api/object_store_v1.py +++ b/openstackclient/api/object_store_v1.py @@ -20,8 +20,9 @@ import os import six from six.moves import urllib +from osc_lib import utils + from openstackclient.api import api -from openstackclient.common import utils class APIv1(api.BaseAPI): diff --git a/openstackclient/common/availability_zone.py b/openstackclient/common/availability_zone.py index 3b0270ad..89d77d15 100644 --- a/openstackclient/common/availability_zone.py +++ b/openstackclient/common/availability_zone.py @@ -14,15 +14,19 @@ """Availability Zone action implementations""" import copy +import logging from novaclient import exceptions as nova_exceptions +from osc_lib.command import command +from osc_lib import utils import six -from openstackclient.common import command -from openstackclient.common import utils from openstackclient.i18n import _ +LOG = logging.getLogger(__name__) + + def _xform_common_availability_zone(az, zone_info): if hasattr(az, 'zoneState'): zone_info['zone_status'] = ('available' if az.zoneState['available'] @@ -92,17 +96,20 @@ class ListAvailabilityZone(command.Lister): '--compute', action='store_true', default=False, - help='List compute availability zones') + help=_('List compute availability zones'), + ) parser.add_argument( '--network', action='store_true', default=False, - help='List network availability zones') + help=_('List network availability zones'), + ) parser.add_argument( '--volume', action='store_true', default=False, - help='List volume availability zones') + help=_('List volume availability zones'), + ) parser.add_argument( '--long', action='store_true', @@ -133,11 +140,11 @@ class ListAvailabilityZone(command.Lister): try: data = volume_client.availability_zones.list() except Exception as e: - self.log.debug('Volume availability zone exception: ' + str(e)) + LOG.debug('Volume availability zone exception: %s', e) if parsed_args.volume: - message = "Availability zones list not supported by " \ - "Block Storage API" - self.log.warning(message) + message = _("Availability zones list not supported by " + "Block Storage API") + LOG.warning(message) result = [] for zone in data: @@ -151,11 +158,11 @@ class ListAvailabilityZone(command.Lister): network_client.find_extension('Availability Zone', ignore_missing=False) except Exception as e: - self.log.debug('Network availability zone exception: ' + str(e)) + LOG.debug('Network availability zone exception: ', e) if parsed_args.network: - message = "Availability zones list not supported by " \ - "Network API" - self.log.warning(message) + message = _("Availability zones list not supported by " + "Network API") + LOG.warning(message) return [] result = [] diff --git a/openstackclient/common/clientmanager.py b/openstackclient/common/clientmanager.py index 9c2b320c..3c35b529 100644 --- a/openstackclient/common/clientmanager.py +++ b/openstackclient/common/clientmanager.py @@ -20,12 +20,12 @@ import logging import pkg_resources import sys +from osc_lib import exceptions from oslo_utils import strutils import requests import six from openstackclient.api import auth -from openstackclient.common import exceptions from openstackclient.common import session as osc_session from openstackclient.identity import client as identity_client @@ -140,10 +140,49 @@ class ClientManager(object): # prior to dereferrencing auth_ref. self._auth_setup_completed = False - def setup_auth(self, required_scope=True): - """Set up authentication + def _set_default_scope_options(self): + # 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(hieulq): If USER_DOMAIN_NAME, USER_DOMAIN_ID, PROJECT_DOMAIN_ID + # or PROJECT_DOMAIN_NAME is present and API_VERSION is 2.0, then + # ignore all domain related configs. + if (self._api_version.get('identity') == '2.0' and + self.auth_plugin_name.endswith('password')): + domain_props = ['project_domain_name', 'project_domain_id', + 'user_domain_name', 'user_domain_id'] + for prop in domain_props: + if self._auth_params.pop(prop, None) is not None: + LOG.warning("Ignoring domain related configs " + + prop + " because identity API version is 2.0") + return + + # NOTE(aloga): The scope parameters below only apply to v3 and v3 + # related auth plugins, so we stop the parameter checking if v2 is + # being used. + if (self._api_version.get('identity') != '3' or + self.auth_plugin_name.startswith('v2')): + return + + # 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 ('project_domain_id' in self._auth_params and + not self._auth_params.get('project_domain_id') and + not self._auth_params.get('project_domain_name')): + self._auth_params['project_domain_id'] = default_domain + + # NOTE(stevemar): If USER_DOMAIN_ID or USER_DOMAIN_NAME is present, + # then do not change the behaviour. Otherwise, set the + # USER_DOMAIN_ID to 'OS_DEFAULT_DOMAIN' for better usability. + if ('user_domain_id' in self._auth_params and + not self._auth_params.get('user_domain_id') and + not self._auth_params.get('user_domain_name')): + self._auth_params['user_domain_id'] = default_domain - :param required_scope: indicate whether a scoped token is required + def setup_auth(self): + """Set up authentication This is deferred until authentication is actually attempted because it gets in the way of things that do not require auth. @@ -157,9 +196,8 @@ class ClientManager(object): self.auth_plugin_name = auth.select_auth_plugin(self._cli_options) # Basic option checking to avoid unhelpful error messages - auth.check_valid_auth_options(self._cli_options, - self.auth_plugin_name, - required_scope=required_scope) + auth.check_valid_authentication_options(self._cli_options, + self.auth_plugin_name) # Horrible hack alert...must handle prompt for null password if # password auth is requested. @@ -172,40 +210,7 @@ class ClientManager(object): self._cli_options, ) - # 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 - self.auth_plugin_name.endswith('password') and - not self._auth_params.get('project_domain_id') and - not self.auth_plugin_name.startswith('v2') and - not self._auth_params.get('project_domain_name')): - self._auth_params['project_domain_id'] = default_domain - - # NOTE(stevemar): If USER_DOMAIN_ID or USER_DOMAIN_NAME is present, - # then do not change the behaviour. Otherwise, set the USER_DOMAIN_ID - # 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_plugin_name.startswith('v2') and - not self._auth_params.get('user_domain_id') and - not self._auth_params.get('user_domain_name')): - self._auth_params['user_domain_id'] = default_domain - - # NOTE(hieulq): If USER_DOMAIN_NAME, USER_DOMAIN_ID, PROJECT_DOMAIN_ID - # or PROJECT_DOMAIN_NAME is present and API_VERSION is 2.0, then - # ignore all domain related configs. - if (self._api_version.get('identity') == '2.0' and - self.auth_plugin_name.endswith('password')): - domain_props = ['project_domain_name', 'project_domain_id', - 'user_domain_name', 'user_domain_id'] - for prop in domain_props: - if self._auth_params.pop(prop, None) is not None: - LOG.warning("Ignoring domain related configs " + - prop + " because identity API version is 2.0") + self._set_default_scope_options() # For compatibility until all clients can be updated if 'project_name' in self._auth_params: @@ -229,6 +234,20 @@ class ClientManager(object): self._auth_setup_completed = True + def validate_scope(self): + if self._auth_ref.project_id is not None: + # We already have a project scope. + return + if self._auth_ref.domain_id is not None: + # We already have a domain scope. + return + + # We do not have a scoped token (and the user's default project scope + # was not implied), so the client needs to be explicitly configured + # with a scope. + auth.check_valid_authorization_options(self._cli_options, + self.auth_plugin_name) + @property def auth_ref(self): """Dereference will trigger an auth if it hasn't already""" @@ -269,7 +288,7 @@ class ClientManager(object): endpoint = self.auth_ref.service_catalog.url_for( service_type=service_type, region_name=region_name, - endpoint_type=interface, + interface=interface, ) else: # Get the passed endpoint directly from the auth plugin diff --git a/openstackclient/common/command.py b/openstackclient/common/command.py index 144a0db1..29c1534d 100644 --- a/openstackclient/common/command.py +++ b/openstackclient/common/command.py @@ -12,45 +12,15 @@ # License for the specific language governing permissions and limitations # under the License. -import abc -import logging +# NOTE(dtroyer): This file is deprecated in Jun 2016, remove after 4.x release +# or Jun 2017. -from cliff import command -from cliff import lister -from cliff import show -import six +import sys -from openstackclient.common import exceptions -from openstackclient.i18n import _ +from osc_lib.command.command import * # noqa -class CommandMeta(abc.ABCMeta): - - def __new__(mcs, name, bases, cls_dict): - if 'log' not in cls_dict: - cls_dict['log'] = logging.getLogger( - cls_dict['__module__'] + '.' + name) - return super(CommandMeta, mcs).__new__(mcs, name, bases, cls_dict) - - -@six.add_metaclass(CommandMeta) -class Command(command.Command): - - def run(self, parsed_args): - self.log.debug('run(%s)', parsed_args) - return super(Command, self).run(parsed_args) - - def validate_os_beta_command_enabled(self): - if not self.app.options.os_beta_command: - msg = _('Caution: This is a beta command and subject to ' - 'change. Use global option --os-beta-command ' - 'to enable this command.') - raise exceptions.CommandError(msg) - - -class Lister(Command, lister.Lister): - pass - - -class ShowOne(Command, show.ShowOne): - pass +sys.stderr.write( + "WARNING: %s is deprecated and will be removed after Jun 2017. " + "Please use osc_lib.command.command\n" % __name__ +) diff --git a/openstackclient/common/configuration.py b/openstackclient/common/configuration.py index a70e4d14..016e9191 100644 --- a/openstackclient/common/configuration.py +++ b/openstackclient/common/configuration.py @@ -13,9 +13,11 @@ """Configuration action implementations""" +from keystoneauth1.loading import base +from osc_lib.command import command import six -from openstackclient.common import command +from openstackclient.i18n import _ REDACTED = "<redacted>" @@ -31,24 +33,25 @@ class ShowConfiguration(command.ShowOne): dest="mask", action="store_true", default=True, - help="Attempt to mask passwords (default)", + help=_("Attempt to mask passwords (default)"), ) mask_group.add_argument( "--unmask", dest="mask", action="store_false", - help="Show password in clear text", + help=_("Show password in clear text"), ) return parser def take_action(self, parsed_args): + auth_plg_name = self.app.client_manager.auth_plugin_name + secret_opts = [o.dest for o in base.get_plugin_options(auth_plg_name) + if o.secret] + info = self.app.client_manager.get_configuration() for key, value in six.iteritems(info.pop('auth', {})): - if parsed_args.mask: - if 'password' in key.lower(): - value = REDACTED - if 'token' in key.lower(): + if parsed_args.mask and key.lower() in secret_opts: value = REDACTED info['auth.' + key] = value return zip(*sorted(six.iteritems(info))) diff --git a/openstackclient/common/exceptions.py b/openstackclient/common/exceptions.py index bdc33ddb..7124074c 100644 --- a/openstackclient/common/exceptions.py +++ b/openstackclient/common/exceptions.py @@ -1,5 +1,3 @@ -# Copyright 2012-2013 OpenStack, LLC. -# # 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 @@ -13,105 +11,15 @@ # under the License. # -"""Exception definitions.""" - - -class CommandError(Exception): - pass - - -class AuthorizationFailure(Exception): - pass - - -class PluginAttributeError(Exception): - """A plugin threw an AttributeError while being lazily loaded.""" - # This *must not* inherit from AttributeError; - # that would defeat the whole purpose. - pass - - -class NoTokenLookupException(Exception): - """This does not support looking up endpoints from an existing token.""" - pass - - -class EndpointNotFound(Exception): - """Could not find Service or Region in Service Catalog.""" - pass - - -class UnsupportedVersion(Exception): - """The user is trying to use an unsupported version of the API""" - pass - - -class ClientException(Exception): - """The base exception class for all exceptions this library raises.""" +# NOTE(dtroyer): This file is deprecated in Jun 2016, remove after 4.x release +# or Jun 2017. - def __init__(self, code, message=None, details=None): - self.code = code - self.message = message or self.__class__.message - self.details = details +import sys - def __str__(self): - return "%s (HTTP %s)" % (self.message, self.code) +from osc_lib.exceptions import * # noqa -class BadRequest(ClientException): - """HTTP 400 - Bad request: you sent some malformed data.""" - http_status = 400 - message = "Bad request" - - -class Unauthorized(ClientException): - """HTTP 401 - Unauthorized: bad credentials.""" - http_status = 401 - message = "Unauthorized" - - -class Forbidden(ClientException): - """HTTP 403 - Forbidden: not authorized to access to this resource.""" - http_status = 403 - message = "Forbidden" - - -class NotFound(ClientException): - """HTTP 404 - Not found""" - http_status = 404 - message = "Not found" - - -class Conflict(ClientException): - """HTTP 409 - Conflict""" - http_status = 409 - message = "Conflict" - - -class OverLimit(ClientException): - """HTTP 413 - Over limit: reached the API limits for this time period.""" - http_status = 413 - message = "Over limit" - - -# NotImplemented is a python keyword. -class HTTPNotImplemented(ClientException): - """HTTP 501 - Not Implemented: server does not support this operation.""" - http_status = 501 - message = "Not Implemented" - - -# In Python 2.4 Exception is old-style and thus doesn't have a __subclasses__() -# so we can do this: -# _code_map = dict((c.http_status, c) -# for c in ClientException.__subclasses__()) -# -# Instead, we have to hardcode it: -_code_map = dict((c.http_status, c) for c in [ - BadRequest, - Unauthorized, - Forbidden, - NotFound, - OverLimit, - HTTPNotImplemented -]) +sys.stderr.write( + "WARNING: %s is deprecated and will be removed after Jun 2017. " + "Please use osc_lib.exceptions\n" % __name__ +) diff --git a/openstackclient/common/extension.py b/openstackclient/common/extension.py index 8b556b4c..de480016 100644 --- a/openstackclient/common/extension.py +++ b/openstackclient/common/extension.py @@ -16,9 +16,15 @@ """Extension action implementations""" import itertools +import logging -from openstackclient.common import command -from openstackclient.common import utils +from osc_lib.command import command +from osc_lib import utils + +from openstackclient.i18n import _ + + +LOG = logging.getLogger(__name__) class ListExtension(command.Lister): @@ -30,27 +36,32 @@ class ListExtension(command.Lister): '--compute', action='store_true', default=False, - help='List extensions for the Compute API') + help=_('List extensions for the Compute API'), + ) parser.add_argument( '--identity', action='store_true', default=False, - help='List extensions for the Identity API') + help=_('List extensions for the Identity API'), + ) parser.add_argument( '--network', action='store_true', default=False, - help='List extensions for the Network API') + help=_('List extensions for the Network API'), + ) parser.add_argument( '--volume', action='store_true', default=False, - help='List extensions for the Block Storage API') + help=_('List extensions for the Block Storage API'), + ) parser.add_argument( '--long', action='store_true', default=False, - help='List additional fields in output') + help=_('List additional fields in output'), + ) return parser def take_action(self, parsed_args): @@ -73,24 +84,25 @@ class ListExtension(command.Lister): try: data += identity_client.extensions.list() except Exception: - message = "Extensions list not supported by Identity API" - self.log.warning(message) + message = _("Extensions list not supported by Identity API") + LOG.warning(message) if parsed_args.compute or show_all: compute_client = self.app.client_manager.compute try: data += compute_client.list_extensions.show_all() except Exception: - message = "Extensions list not supported by Compute API" - self.log.warning(message) + message = _("Extensions list not supported by Compute API") + LOG.warning(message) if parsed_args.volume or show_all: volume_client = self.app.client_manager.volume try: data += volume_client.list_extensions.show_all() except Exception: - message = "Extensions list not supported by Block Storage API" - self.log.warning(message) + message = _("Extensions list not supported by " + "Block Storage API") + LOG.warning(message) # Resource classes for the above extension_tuples = ( @@ -118,7 +130,7 @@ class ListExtension(command.Lister): dict_tuples ) except Exception: - message = "Extensions list not supported by Network API" - self.log.warning(message) + message = _("Extensions list not supported by Network API") + LOG.warning(message) return (columns, extension_tuples) diff --git a/openstackclient/common/limits.py b/openstackclient/common/limits.py index 1f87abf3..f7aa82f6 100644 --- a/openstackclient/common/limits.py +++ b/openstackclient/common/limits.py @@ -17,8 +17,10 @@ import itertools -from openstackclient.common import command -from openstackclient.common import utils +from osc_lib.command import command +from osc_lib import utils + +from openstackclient.i18n import _ from openstackclient.identity import common as identity_common @@ -33,30 +35,33 @@ class ShowLimits(command.Lister): dest="is_absolute", action="store_true", default=False, - help="Show absolute limits") + help=_("Show absolute limits"), + ) type_group.add_argument( "--rate", dest="is_rate", action="store_true", default=False, - help="Show rate limits") + help=_("Show rate limits"), + ) parser.add_argument( "--reserved", dest="is_reserved", action="store_true", default=False, - help="Include reservations count [only valid with --absolute]") + 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]', + help=_('Show limits for a specific project (name or ID)' + ' [only valid with --absolute]'), ) parser.add_argument( '--domain', metavar='<domain>', - help='Domain the project belongs to (name or ID)' - ' [only valid with --absolute]', + help=_('Domain the project belongs to (name or ID)' + ' [only valid with --absolute]'), ) return parser diff --git a/openstackclient/common/logs.py b/openstackclient/common/logs.py index 221c5997..8aa97d5b 100644 --- a/openstackclient/common/logs.py +++ b/openstackclient/common/logs.py @@ -11,188 +11,16 @@ # under the License. # -"""Application logging""" +# NOTE(dtroyer): This file is deprecated in Jun 2016, remove after 4.x release +# or Jun 2017. -import logging import sys -import warnings +from osc_lib.logs import * # noqa +from osc_lib.logs import _FileFormatter # noqa -def get_loggers(): - loggers = {} - for logkey in logging.Logger.manager.loggerDict.keys(): - loggers[logkey] = logging.getLevelName(logging.getLogger(logkey).level) - return loggers - -def log_level_from_options(options): - # if --debug, --quiet or --verbose is not specified, - # the default logging level is warning - log_level = logging.WARNING - if options.verbose_level == 0: - # --quiet - log_level = logging.ERROR - elif options.verbose_level == 2: - # One --verbose - log_level = logging.INFO - elif options.verbose_level >= 3: - # Two or more --verbose - log_level = logging.DEBUG - return log_level - - -def log_level_from_string(level_string): - log_level = { - 'critical': logging.CRITICAL, - 'error': logging.ERROR, - 'warning': logging.WARNING, - 'info': logging.INFO, - 'debug': logging.DEBUG, - }.get(level_string, logging.WARNING) - return log_level - - -def log_level_from_config(config): - # Check the command line option - verbose_level = config.get('verbose_level') - if config.get('debug', False): - verbose_level = 3 - if verbose_level == 0: - verbose_level = 'error' - elif verbose_level == 1: - # If a command line option has not been specified, check the - # configuration file - verbose_level = config.get('log_level', 'warning') - elif verbose_level == 2: - verbose_level = 'info' - else: - verbose_level = 'debug' - return log_level_from_string(verbose_level) - - -def set_warning_filter(log_level): - if log_level == logging.ERROR: - warnings.simplefilter("ignore") - elif log_level == logging.WARNING: - warnings.simplefilter("ignore") - elif log_level == logging.INFO: - warnings.simplefilter("once") - - -class _FileFormatter(logging.Formatter): - """Customize the logging format for logging handler""" - _LOG_MESSAGE_BEGIN = ( - '%(asctime)s.%(msecs)03d %(process)d %(levelname)s %(name)s ') - _LOG_MESSAGE_CONTEXT = '[%(cloud)s %(username)s %(project)s] ' - _LOG_MESSAGE_END = '%(message)s' - _LOG_DATE_FORMAT = '%Y-%m-%d %H:%M:%S' - - def __init__(self, options=None, config=None, **kwargs): - context = {} - if options: - context = { - 'cloud': getattr(options, 'cloud', ''), - 'project': getattr(options, 'os_project_name', ''), - 'username': getattr(options, 'username', ''), - } - elif config: - context = { - 'cloud': config.config.get('cloud', ''), - 'project': config.auth.get('project_name', ''), - 'username': config.auth.get('username', ''), - } - if context: - self.fmt = (self._LOG_MESSAGE_BEGIN + - (self._LOG_MESSAGE_CONTEXT % context) + - self._LOG_MESSAGE_END) - else: - self.fmt = self._LOG_MESSAGE_BEGIN + self._LOG_MESSAGE_END - logging.Formatter.__init__(self, self.fmt, self._LOG_DATE_FORMAT) - - -class LogConfigurator(object): - - _CONSOLE_MESSAGE_FORMAT = '%(message)s' - - def __init__(self, options): - self.root_logger = logging.getLogger('') - self.root_logger.setLevel(logging.DEBUG) - - # Force verbose_level 3 on --debug - self.dump_trace = False - if options.debug: - options.verbose_level = 3 - self.dump_trace = True - - # Always send higher-level messages to the console via stderr - self.console_logger = logging.StreamHandler(sys.stderr) - log_level = log_level_from_options(options) - self.console_logger.setLevel(log_level) - formatter = logging.Formatter(self._CONSOLE_MESSAGE_FORMAT) - self.console_logger.setFormatter(formatter) - self.root_logger.addHandler(self.console_logger) - - # Set the warning filter now - set_warning_filter(log_level) - - # Set up logging to a file - self.file_logger = None - log_file = options.log_file - if log_file: - self.file_logger = logging.FileHandler(filename=log_file) - self.file_logger.setFormatter(_FileFormatter(options=options)) - self.file_logger.setLevel(log_level) - self.root_logger.addHandler(self.file_logger) - - # Requests logs some stuff at INFO that we don't want - # unless we have DEBUG - requests_log = logging.getLogger("requests") - - # Other modules we don't want DEBUG output for - cliff_log = logging.getLogger('cliff') - stevedore_log = logging.getLogger('stevedore') - iso8601_log = logging.getLogger("iso8601") - - if options.debug: - # --debug forces traceback - requests_log.setLevel(logging.DEBUG) - else: - requests_log.setLevel(logging.ERROR) - - cliff_log.setLevel(logging.ERROR) - stevedore_log.setLevel(logging.ERROR) - iso8601_log.setLevel(logging.ERROR) - - def configure(self, cloud_config): - log_level = log_level_from_config(cloud_config.config) - set_warning_filter(log_level) - self.dump_trace = cloud_config.config.get('debug', self.dump_trace) - self.console_logger.setLevel(log_level) - - log_file = cloud_config.config.get('log_file') - if log_file: - if not self.file_logger: - self.file_logger = logging.FileHandler(filename=log_file) - formatter = _FileFormatter(cloud_config=cloud_config) - self.file_logger.setFormatter(formatter) - self.file_logger.setFormatter(_FileFormatter(config=cloud_config)) - self.file_logger.setLevel(log_level) - self.root_logger.addHandler(self.file_logger) - - logconfig = cloud_config.config.get('logging') - if logconfig: - highest_level = logging.NOTSET - for k in logconfig.keys(): - level = log_level_from_string(logconfig[k]) - logging.getLogger(k).setLevel(level) - if (highest_level < level): - highest_level = level - self.console_logger.setLevel(highest_level) - if self.file_logger: - self.file_logger.setLevel(highest_level) - # loggers that are not set will use the handler level, so we - # need to set the global level for all the loggers - for logkey in logging.Logger.manager.loggerDict.keys(): - logger = logging.getLogger(logkey) - if logger.level == logging.NOTSET: - logger.setLevel(log_level) +sys.stderr.write( + "WARNING: %s is deprecated and will be removed after Jun 2017. " + "Please use osc_lib.logs\n" % __name__ +) diff --git a/openstackclient/common/module.py b/openstackclient/common/module.py index 30c67c68..7c5fcd55 100644 --- a/openstackclient/common/module.py +++ b/openstackclient/common/module.py @@ -15,11 +15,13 @@ """Module action implementation""" -import six import sys -from openstackclient.common import command -from openstackclient.common import utils +from osc_lib.command import command +from osc_lib import utils +import six + +from openstackclient.i18n import _ class ListCommand(command.Lister): @@ -61,7 +63,7 @@ class ListModule(command.ShowOne): '--all', action='store_true', default=False, - help='Show all modules that have version information', + help=_('Show all modules that have version information'), ) return parser diff --git a/openstackclient/common/parseractions.py b/openstackclient/common/parseractions.py index 77798f90..fa5148ec 100644 --- a/openstackclient/common/parseractions.py +++ b/openstackclient/common/parseractions.py @@ -1,5 +1,3 @@ -# Copyright 2013 OpenStack Foundation -# # 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 @@ -13,150 +11,15 @@ # under the License. # -"""argparse Custom Actions""" - -import argparse - -from openstackclient.i18n import _ - - -class KeyValueAction(argparse.Action): - """A custom action to parse arguments as key=value pairs - - Ensures that ``dest`` is a dict - """ - - def __call__(self, parser, namespace, values, option_string=None): - # Make sure we have an empty dict rather than None - if getattr(namespace, self.dest, None) is None: - setattr(namespace, self.dest, {}) - - # Add value if an assignment else remove it - if '=' in values: - getattr(namespace, self.dest, {}).update([values.split('=', 1)]) - else: - getattr(namespace, self.dest, {}).pop(values, None) - - -class MultiKeyValueAction(argparse.Action): - """A custom action to parse arguments as key1=value1,key2=value2 pairs - - Ensure that ``dest`` is a list. The list will finally contain multiple - dicts, with key=value pairs in them. - - NOTE: The arguments string should be a comma separated key-value pairs. - And comma(',') and equal('=') may not be used in the key or value. - """ - - def __init__(self, option_strings, dest, nargs=None, - required_keys=None, optional_keys=None, **kwargs): - """Initialize the action object, and parse customized options - - Required keys and optional keys can be specified when initializing - the action to enable the key validation. If none of them specified, - the key validation will be skipped. - - :param required_keys: a list of required keys - :param optional_keys: a list of optional keys - """ - if nargs: - raise ValueError("Parameter 'nargs' is not allowed, but got %s" - % nargs) - - super(MultiKeyValueAction, self).__init__(option_strings, - dest, **kwargs) - - # required_keys: A list of keys that is required. None by default. - if required_keys and not isinstance(required_keys, list): - raise TypeError("'required_keys' must be a list") - self.required_keys = set(required_keys or []) - - # optional_keys: A list of keys that is optional. None by default. - if optional_keys and not isinstance(optional_keys, list): - raise TypeError("'optional_keys' must be a list") - self.optional_keys = set(optional_keys or []) - - def __call__(self, parser, namespace, values, metavar=None): - # Make sure we have an empty list rather than None - if getattr(namespace, self.dest, None) is None: - setattr(namespace, self.dest, []) - - params = {} - for kv in values.split(','): - # Add value if an assignment else raise ArgumentTypeError - if '=' in kv: - params.update([kv.split('=', 1)]) - else: - msg = ("Expected key=value pairs separated by comma, " - "but got: %s" % (str(kv))) - raise argparse.ArgumentTypeError(msg) - - # Check key validation - valid_keys = self.required_keys | self.optional_keys - if valid_keys: - invalid_keys = [k for k in params if k not in valid_keys] - if invalid_keys: - msg = _("Invalid keys %(invalid_keys)s specified.\n" - "Valid keys are: %(valid_keys)s.") - raise argparse.ArgumentTypeError( - msg % {'invalid_keys': ', '.join(invalid_keys), - 'valid_keys': ', '.join(valid_keys)} - ) - - if self.required_keys: - missing_keys = [k for k in self.required_keys if k not in params] - if missing_keys: - msg = _("Missing required keys %(missing_keys)s.\n" - "Required keys are: %(required_keys)s.") - raise argparse.ArgumentTypeError( - msg % {'missing_keys': ', '.join(missing_keys), - 'required_keys': ', '.join(self.required_keys)} - ) - - # Update the dest dict - getattr(namespace, self.dest, []).append(params) - - -class RangeAction(argparse.Action): - """A custom action to parse a single value or a range of values - - Parses single integer values or a range of integer values delimited - by a colon and returns a tuple of integers: - '4' sets ``dest`` to (4, 4) - '6:9' sets ``dest`` to (6, 9) - """ - - def __call__(self, parser, namespace, values, option_string=None): - range = values.split(':') - if len(range) == 0: - # Nothing passed, return a zero default - setattr(namespace, self.dest, (0, 0)) - elif len(range) == 1: - # Only a single value is present - setattr(namespace, self.dest, (int(range[0]), int(range[0]))) - elif len(range) == 2: - # Range of two values - if int(range[0]) <= int(range[1]): - setattr(namespace, self.dest, (int(range[0]), int(range[1]))) - else: - msg = "Invalid range, %s is not less than %s" % \ - (range[0], range[1]) - raise argparse.ArgumentError(self, msg) - else: - # Too many values - msg = "Invalid range, too many values" - raise argparse.ArgumentError(self, msg) +# NOTE(dtroyer): This file is deprecated in Jun 2016, remove after 4.x release +# or Jun 2017. +import sys -class NonNegativeAction(argparse.Action): - """A custom action to check whether the value is non-negative or not +from osc_lib.cli.parseractions import * # noqa - Ensures the value is >= 0. - """ - def __call__(self, parser, namespace, values, option_string=None): - if int(values) >= 0: - setattr(namespace, self.dest, values) - else: - msg = "%s expected a non-negative integer" % (str(option_string)) - raise argparse.ArgumentTypeError(msg) +sys.stderr.write( + "WARNING: %s is deprecated and will be removed after Jun 2017. " + "Please use osc_lib.cli.parseractions\n" % __name__ +) diff --git a/openstackclient/common/quota.py b/openstackclient/common/quota.py index f85d550b..69415f0d 100644 --- a/openstackclient/common/quota.py +++ b/openstackclient/common/quota.py @@ -16,11 +16,13 @@ """Quota action implementations""" import itertools -import six import sys -from openstackclient.common import command -from openstackclient.common import utils +from osc_lib.command import command +from osc_lib import utils +import six + +from openstackclient.i18n import _ # List the quota items, map the internal argument name to the option @@ -84,14 +86,14 @@ class SetQuota(command.Command): parser.add_argument( 'project', metavar='<project/class>', - help='Set quotas for this project or class (name/ID)', + help=_('Set quotas for this project or class (name/ID)'), ) parser.add_argument( '--class', dest='quota_class', action='store_true', default=False, - help='Set quotas for <class>', + help=_('Set quotas for <class>'), ) for k, v in self._build_options_list(): parser.add_argument( @@ -99,12 +101,12 @@ class SetQuota(command.Command): metavar='<%s>' % v, dest=k, type=int, - help='New value for the %s quota' % v, + help=_('New value for the %s quota') % v, ) parser.add_argument( '--volume-type', metavar='<volume-type>', - help='Set quotas for a specific <volume-type>', + help=_('Set quotas for a specific <volume-type>'), ) return parser @@ -140,11 +142,6 @@ class SetQuota(command.Command): if value is not None: compute_kwargs[k] = value - if (compute_kwargs == {} and volume_kwargs == {} - and network_kwargs == {}): - sys.stderr.write("No quotas updated\n") - return - if parsed_args.project: project = utils.find_resource( identity_client.projects, @@ -187,7 +184,7 @@ class ShowQuota(command.ShowOne): 'project', metavar='<project/class>', nargs='?', - help='Show quotas for this project or class (name or ID)', + help=_('Show quotas for this project or class (name or ID)'), ) type_group = parser.add_mutually_exclusive_group() type_group.add_argument( @@ -195,14 +192,14 @@ class ShowQuota(command.ShowOne): dest='quota_class', action='store_true', default=False, - help='Show quotas for <class>', + help=_('Show quotas for <class>'), ) type_group.add_argument( '--default', dest='default', action='store_true', default=False, - help='Show default quotas for <project>' + help=_('Show default quotas for <project>') ) return parser diff --git a/openstackclient/common/timing.py b/openstackclient/common/timing.py index 71c2fec7..facbec35 100644 --- a/openstackclient/common/timing.py +++ b/openstackclient/common/timing.py @@ -11,31 +11,15 @@ # under the License. # -"""Timing Implementation""" +# NOTE(dtroyer): This file is deprecated in Jun 2016, remove after 4.x release +# or Jun 2017. -from openstackclient.common import command +import sys +from osc_lib.command.timing import * # noqa -class Timing(command.Lister): - """Show timing data""" - def take_action(self, parsed_args): - column_headers = ( - 'URL', - 'Seconds', - ) - - results = [] - total = 0.0 - 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, - results, - ) +sys.stderr.write( + "WARNING: %s is deprecated and will be removed after Jun 2017. " + "Please use osc_lib.command.timing\n" % __name__ +) diff --git a/openstackclient/common/utils.py b/openstackclient/common/utils.py index daa65c25..73cd3dc9 100644 --- a/openstackclient/common/utils.py +++ b/openstackclient/common/utils.py @@ -1,5 +1,3 @@ -# Copyright 2012-2013 OpenStack Foundation -# # 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 @@ -13,410 +11,15 @@ # under the License. # -"""Common client utilities""" - -import getpass -import logging -import os -import six -import time - -from oslo_utils import importutils - -from openstackclient.common import exceptions - - -def find_resource(manager, name_or_id, **kwargs): - """Helper for the _find_* methods. - - :param manager: A client manager class - :param name_or_id: The resource we are trying to find - :param kwargs: To be used in calling .find() - :rtype: The found resource - - This method will attempt to find a resource in a variety of ways. - Primarily .get() methods will be called with `name_or_id` as an integer - value, and tried again as a string value. - - If both fail, then a .find() is attempted, which is essentially calling - a .list() function with a 'name' query parameter that is set to - `name_or_id`. - - Lastly, if any kwargs are passed in, they will be treated as additional - query parameters. This is particularly handy in the case of finding - resources in a domain. - - """ - - # Try to get entity as integer id - try: - if isinstance(name_or_id, int) or name_or_id.isdigit(): - return manager.get(int(name_or_id), **kwargs) - # FIXME(dtroyer): The exception to catch here is dependent on which - # client library the manager passed in belongs to. - # Eventually this should be pulled from a common set - # of client exceptions. - except Exception as ex: - if type(ex).__name__ == 'NotFound': - pass - else: - raise - - # Try directly using the passed value - try: - return manager.get(name_or_id, **kwargs) - except Exception: - pass - - if len(kwargs) == 0: - kwargs = {} - - try: - # Prepare the kwargs for calling find - if 'NAME_ATTR' in manager.resource_class.__dict__: - # novaclient does this for oddball resources - kwargs[manager.resource_class.NAME_ATTR] = name_or_id - else: - kwargs['name'] = name_or_id - except Exception: - pass - - # finally try to find entity by name - try: - return manager.find(**kwargs) - # FIXME(dtroyer): The exception to catch here is dependent on which - # client library the manager passed in belongs to. - # Eventually this should be pulled from a common set - # of client exceptions. - except Exception as ex: - if type(ex).__name__ == 'NotFound': - msg = "No %s with a name or ID of '%s' exists." % \ - (manager.resource_class.__name__.lower(), name_or_id) - raise exceptions.CommandError(msg) - if type(ex).__name__ == 'NoUniqueMatch': - msg = "More than one %s exists with the name '%s'." % \ - (manager.resource_class.__name__.lower(), name_or_id) - raise exceptions.CommandError(msg) - else: - pass - - for resource in manager.list(): - # short circuit and return the first match - if (resource.get('id') == name_or_id or - resource.get('name') == name_or_id): - return resource - else: - # we found no match, report back this error: - msg = "Could not find resource %s" % name_or_id - raise exceptions.CommandError(msg) - - -def format_dict(data): - """Return a formatted string of key value pairs - - :param data: a dict - :rtype: a string formatted to key='value' - """ - - output = "" - for s in sorted(data): - output = output + s + "='" + six.text_type(data[s]) + "', " - return output[:-2] - - -def format_list(data, separator=', '): - """Return a formatted strings - - :param data: a list of strings - :param separator: the separator to use between strings (default: ', ') - :rtype: a string formatted based on separator - """ - - return separator.join(sorted(data)) - - -def format_list_of_dicts(data): - """Return a formatted string of key value pairs for each dict - - :param data: a list of dicts - :rtype: a string formatted to key='value' with dicts separated by new line - """ - - return '\n'.join(format_dict(i) for i in data) - - -def get_field(item, field): - try: - if isinstance(item, dict): - return item[field] - else: - return getattr(item, field) - except Exception: - msg = "Resource doesn't have field %s" % field - raise exceptions.CommandError(msg) - - -def get_item_properties(item, fields, mixed_case_fields=None, formatters=None): - """Return a tuple containing the item properties. - - :param item: a single item resource (e.g. Server, Project, etc) - :param fields: tuple of strings with the desired field names - :param mixed_case_fields: tuple of field names to preserve case - :param formatters: dictionary mapping field names to callables - to format the values - """ - if mixed_case_fields is None: - mixed_case_fields = [] - if formatters is None: - formatters = {} - - row = [] - - for field in fields: - if field in mixed_case_fields: - field_name = field.replace(' ', '_') - else: - field_name = field.lower().replace(' ', '_') - data = getattr(item, field_name, '') - if field in formatters: - row.append(formatters[field](data)) - else: - row.append(data) - return tuple(row) - - -def get_dict_properties(item, fields, mixed_case_fields=None, formatters=None): - """Return a tuple containing the item properties. - - :param item: a single dict resource - :param fields: tuple of strings with the desired field names - :param mixed_case_fields: tuple of field names to preserve case - :param formatters: dictionary mapping field names to callables - to format the values - """ - if mixed_case_fields is None: - mixed_case_fields = [] - if formatters is None: - formatters = {} - - row = [] - - for field in fields: - if field in mixed_case_fields: - field_name = field.replace(' ', '_') - else: - field_name = field.lower().replace(' ', '_') - data = item[field_name] if field_name in item else '' - if field in formatters: - row.append(formatters[field](data)) - else: - row.append(data) - return tuple(row) - - -def sort_items(items, sort_str): - """Sort items based on sort keys and sort directions given by sort_str. - - :param items: a list or generator object of items - :param sort_str: a string defining the sort rules, the format is - '<key1>:[direction1],<key2>:[direction2]...', direction can be 'asc' - for ascending or 'desc' for descending, if direction is not given, - it's ascending by default - :return: sorted items - """ - if not sort_str: - return items - # items may be a generator object, transform it to a list - items = list(items) - sort_keys = sort_str.strip().split(',') - for sort_key in reversed(sort_keys): - reverse = False - if ':' in sort_key: - sort_key, direction = sort_key.split(':', 1) - if not sort_key: - msg = "empty string is not a valid sort key" - raise exceptions.CommandError(msg) - if direction not in ['asc', 'desc']: - if not direction: - direction = "empty string" - msg = ("%s is not a valid sort direction for sort key %s, " - "use asc or desc instead" % (direction, sort_key)) - raise exceptions.CommandError(msg) - if direction == 'desc': - reverse = True - items.sort(key=lambda item: get_field(item, sort_key), - reverse=reverse) - return items - - -def env(*vars, **kwargs): - """Search for the first defined of possibly many env vars - - Returns the first environment variable defined in vars, or - returns the default defined in kwargs. - """ - for v in vars: - value = os.environ.get(v, None) - if value: - return value - return kwargs.get('default', '') - - -def get_client_class(api_name, version, version_map): - """Returns the client class for the requested API version - - :param api_name: the name of the API, e.g. 'compute', 'image', etc - :param version: the requested API version - :param version_map: a dict of client classes keyed by version - :rtype: a client class for the requested API version - """ - try: - client_path = version_map[str(version)] - except (KeyError, ValueError): - msg = "Invalid %s client version '%s'. must be one of: %s" % ( - (api_name, version, ', '.join(list(version_map.keys())))) - raise exceptions.UnsupportedVersion(msg) - - return importutils.import_class(client_path) - - -def wait_for_status(status_f, - res_id, - status_field='status', - success_status=['active'], - error_status=['error'], - sleep_time=5, - callback=None): - """Wait for status change on a resource during a long-running operation - - :param status_f: a status function that takes a single id argument - :param res_id: the resource id to watch - :param status_field: the status attribute in the returned resource object - :param success_status: a list of status strings for successful completion - :param error_status: a list of status strings for error - :param sleep_time: wait this long (seconds) - :param callback: called per sleep cycle, useful to display progress - :rtype: True on success - """ - while True: - res = status_f(res_id) - status = getattr(res, status_field, '').lower() - if status in success_status: - retval = True - break - elif status in error_status: - retval = False - break - if callback: - progress = getattr(res, 'progress', None) or 0 - callback(progress) - time.sleep(sleep_time) - return retval - - -def wait_for_delete(manager, - res_id, - status_field='status', - error_status=['error'], - exception_name=['NotFound'], - sleep_time=5, - timeout=300, - callback=None): - """Wait for resource deletion - - :param manager: the manager from which we can get the resource - :param res_id: the resource id to watch - :param status_field: the status attribute in the returned resource object, - this is used to check for error states while the resource is being - deleted - :param error_status: a list of status strings for error - :param exception_name: a list of exception strings for deleted case - :param sleep_time: wait this long between checks (seconds) - :param timeout: check until this long (seconds) - :param callback: called per sleep cycle, useful to display progress; this - function is passed a progress value during each iteration of the wait - loop - :rtype: True on success, False if the resource has gone to error state or - the timeout has been reached - """ - total_time = 0 - while total_time < timeout: - try: - # might not be a bad idea to re-use find_resource here if it was - # a bit more friendly in the exceptions it raised so we could just - # handle a NotFound exception here without parsing the message - res = manager.get(res_id) - except Exception as ex: - if type(ex).__name__ in exception_name: - return True - raise - - status = getattr(res, status_field, '').lower() - if status in error_status: - return False - - if callback: - progress = getattr(res, 'progress', None) or 0 - callback(progress) - time.sleep(sleep_time) - total_time += sleep_time - - # if we got this far we've timed out - return False - - -def get_effective_log_level(): - """Returns the lowest logging level considered by logging handlers - - Retrieve and return the smallest log level set among the root - logger's handlers (in case of multiple handlers). - """ - root_log = logging.getLogger() - min_log_lvl = logging.CRITICAL - for handler in root_log.handlers: - min_log_lvl = min(min_log_lvl, handler.level) - return min_log_lvl - - -def get_password(stdin, prompt=None, confirm=True): - message = prompt or "User Password:" - if hasattr(stdin, 'isatty') and stdin.isatty(): - try: - while True: - first_pass = getpass.getpass(message) - if not confirm: - return first_pass - second_pass = getpass.getpass("Repeat " + message) - if first_pass == second_pass: - return first_pass - print("The passwords entered were not the same") - except EOFError: # Ctl-D - raise exceptions.CommandError("Error reading password.") - raise exceptions.CommandError("There was a request to be prompted for a" - " password and a terminal was not detected.") - - -def read_blob_file_contents(blob_file): - try: - with open(blob_file) as file: - blob = file.read().strip() - return blob - except IOError: - msg = "Error occurred trying to read from file %s" - raise exceptions.CommandError(msg % blob_file) +# NOTE(dtroyer): This file is deprecated in Jun 2016, remove after 4.x release +# or Jun 2017. +import sys -def build_kwargs_dict(arg_name, value): - """Return a dictionary containing `arg_name` if `value` is set.""" - kwargs = {} - if value: - kwargs[arg_name] = value - return kwargs +from osc_lib.utils import * # noqa -def is_ascii(string): - try: - string.decode('ascii') - return True - except UnicodeDecodeError: - return False +sys.stderr.write( + "WARNING: %s is deprecated and will be removed after Jun 2017. " + "Please use osc_lib.utils\n" % __name__ +) diff --git a/openstackclient/compute/client.py b/openstackclient/compute/client.py index 8e6eedcf..1583676a 100644 --- a/openstackclient/compute/client.py +++ b/openstackclient/compute/client.py @@ -1,4 +1,4 @@ -# Copyright 2012-2013 OpenStack, LLC. +# Copyright 2012-2013 OpenStack Foundation # # 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 @@ -15,10 +15,12 @@ import logging -from openstackclient.common import exceptions -from openstackclient.common import utils +from osc_lib import exceptions +from osc_lib import utils + from openstackclient.i18n import _ + LOG = logging.getLogger(__name__) DEFAULT_API_VERSION = '2' diff --git a/openstackclient/compute/v2/agent.py b/openstackclient/compute/v2/agent.py index 064fe5a6..4d923955 100644 --- a/openstackclient/compute/v2/agent.py +++ b/openstackclient/compute/v2/agent.py @@ -15,16 +15,21 @@ """Agent action implementations""" +import logging + +from osc_lib.command import command +from osc_lib import exceptions +from osc_lib import utils import six -from openstackclient.common import command -from openstackclient.common import exceptions -from openstackclient.common import utils from openstackclient.i18n import _ +LOG = logging.getLogger(__name__) + + class CreateAgent(command.ShowOne): - """Create compute agent command""" + """Create compute agent""" def get_parser(self, prog_name): parser = super(CreateAgent, self).get_parser(prog_name) @@ -96,19 +101,18 @@ class DeleteAgent(command.Command): compute_client.agents.delete(id) except Exception as e: result += 1 - self.app.log.error(_("Failed to delete agent with " - "ID '%(id)s': %(e)s") - % {'id': id, 'e': e}) + LOG.error(_("Failed to delete agent with ID '%(id)s': %(e)s"), + {'id': id, 'e': e}) if result > 0: total = len(parsed_args.id) msg = (_("%(result)s of %(total)s agents failed " - "to delete.") % {'result': result, 'total': total}) + "to delete.") % {'result': result, 'total': total}) raise exceptions.CommandError(msg) class ListAgent(command.Lister): - """List compute agent command""" + """List compute agents""" def get_parser(self, prog_name): parser = super(ListAgent, self).get_parser(prog_name) @@ -138,7 +142,7 @@ class ListAgent(command.Lister): class SetAgent(command.Command): - """Set compute agent command""" + """Set compute agent properties""" def get_parser(self, prog_name): parser = super(SetAgent, self).get_parser(prog_name) diff --git a/openstackclient/compute/v2/aggregate.py b/openstackclient/compute/v2/aggregate.py index 752e0fdf..2e2838c5 100644 --- a/openstackclient/compute/v2/aggregate.py +++ b/openstackclient/compute/v2/aggregate.py @@ -16,14 +16,20 @@ """Compute v2 Aggregate action implementations""" +import logging + +from osc_lib.cli import parseractions +from osc_lib.command import command +from osc_lib import exceptions +from osc_lib import utils import six -from openstackclient.common import command -from openstackclient.common import parseractions -from openstackclient.common import utils from openstackclient.i18n import _ +LOG = logging.getLogger(__name__) + + class AddAggregateHost(command.ShowOne): """Add host to aggregate""" @@ -99,25 +105,37 @@ class CreateAggregate(command.ShowOne): class DeleteAggregate(command.Command): - """Delete an existing aggregate""" + """Delete existing aggregate(s)""" def get_parser(self, prog_name): parser = super(DeleteAggregate, self).get_parser(prog_name) parser.add_argument( 'aggregate', metavar='<aggregate>', - help=_("Aggregate to delete (name or ID)") + nargs='+', + help=_("Aggregate(s) to delete (name or ID)") ) return parser def take_action(self, parsed_args): - compute_client = self.app.client_manager.compute - data = utils.find_resource( - compute_client.aggregates, - parsed_args.aggregate, - ) - compute_client.aggregates.delete(data.id) + result = 0 + for a in parsed_args.aggregate: + try: + data = utils.find_resource( + compute_client.aggregates, a) + compute_client.aggregates.delete(data.id) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete aggregate with name or " + "ID '%(aggregate)s': %(e)s") + % {'aggregate': a, 'e': e}) + + if result > 0: + total = len(parsed_args.aggregate) + msg = (_("%(result)s of %(total)s aggregates failed " + "to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) class ListAggregate(command.Lister): diff --git a/openstackclient/compute/v2/console.py b/openstackclient/compute/v2/console.py index 1165862c..4179fa5c 100644 --- a/openstackclient/compute/v2/console.py +++ b/openstackclient/compute/v2/console.py @@ -15,12 +15,13 @@ """Compute v2 Console action implementations""" -import six import sys -from openstackclient.common import command -from openstackclient.common import parseractions -from openstackclient.common import utils +from osc_lib.cli import parseractions +from osc_lib.command import command +from osc_lib import utils +import six + from openstackclient.i18n import _ @@ -92,7 +93,7 @@ class ShowConsoleURL(command.ShowOne): '--spice', dest='url_type', action='store_const', - const='spice', + const='spice-html5', help=_("Show SPICE console URL") ) return parser @@ -104,14 +105,20 @@ class ShowConsoleURL(command.ShowOne): parsed_args.server, ) + data = None if parsed_args.url_type in ['novnc', 'xvpvnc']: data = server.get_vnc_console(parsed_args.url_type) - if parsed_args.url_type in ['spice']: + if parsed_args.url_type in ['spice-html5']: data = server.get_spice_console(parsed_args.url_type) if not data: return ({}, {}) info = {} - info.update(data['console']) + # NOTE(Rui Chen): Return 'remote_console' in compute microversion API + # 2.6 and later, return 'console' in compute + # microversion API from 2.0 to 2.5, do compatibility + # handle for different microversion API. + console_data = data.get('remote_console', data.get('console')) + info.update(console_data) return zip(*sorted(six.iteritems(info))) diff --git a/openstackclient/compute/v2/fixedip.py b/openstackclient/compute/v2/fixedip.py index daac97d1..8bd72ca3 100644 --- a/openstackclient/compute/v2/fixedip.py +++ b/openstackclient/compute/v2/fixedip.py @@ -15,8 +15,8 @@ """Fixed IP action implementations""" -from openstackclient.common import command -from openstackclient.common import utils +from osc_lib.command import command +from osc_lib import utils class AddFixedIP(command.Command): diff --git a/openstackclient/compute/v2/flavor.py b/openstackclient/compute/v2/flavor.py index 87909a18..01d7da75 100644 --- a/openstackclient/compute/v2/flavor.py +++ b/openstackclient/compute/v2/flavor.py @@ -15,16 +15,21 @@ """Flavor action implementations""" +import logging + +from osc_lib.cli import parseractions +from osc_lib.command import command +from osc_lib import exceptions +from osc_lib import utils import six -from openstackclient.common import command -from openstackclient.common import exceptions -from openstackclient.common import parseractions -from openstackclient.common import utils from openstackclient.i18n import _ from openstackclient.identity import common as identity_common +LOG = logging.getLogger(__name__) + + def _find_flavor(compute_client, flavor): try: return compute_client.flavors.get(flavor) @@ -116,10 +121,22 @@ class CreateFlavor(command.ShowOne): action="store_false", help=_("Flavor is not available to other projects") ) + parser.add_argument( + '--project', + metavar='<project>', + help=_("Allow <project> to access private flavor (name or ID) " + "(Must be used with --private option)"), + ) + identity_common.add_project_domain_option_to_parser(parser) return parser def take_action(self, parsed_args): compute_client = self.app.client_manager.compute + identity_client = self.app.client_manager.identity + + if parsed_args.project and parsed_args.public: + msg = _("--project is only allowed with --private") + raise exceptions.CommandError(msg) args = ( parsed_args.name, @@ -136,25 +153,54 @@ class CreateFlavor(command.ShowOne): flavor = compute_client.flavors.create(*args)._info.copy() flavor.pop("links") + if parsed_args.project: + try: + project_id = identity_common.find_project( + identity_client, + parsed_args.project, + parsed_args.project_domain, + ).id + compute_client.flavor_access.add_tenant_access( + parsed_args.id, project_id) + except Exception as e: + msg = _("Failed to add project %(project)s access to " + "flavor: %(e)s") + LOG.error(msg % {'project': parsed_args.project, 'e': e}) + return zip(*sorted(six.iteritems(flavor))) class DeleteFlavor(command.Command): - """Delete flavor""" + """Delete flavor(s)""" def get_parser(self, prog_name): parser = super(DeleteFlavor, self).get_parser(prog_name) parser.add_argument( "flavor", metavar="<flavor>", - help=_("Flavor to delete (name or ID)") + nargs='+', + help=_("Flavor(s) to delete (name or ID)") ) return parser def take_action(self, parsed_args): compute_client = self.app.client_manager.compute - flavor = _find_flavor(compute_client, parsed_args.flavor) - compute_client.flavors.delete(flavor.id) + result = 0 + for f in parsed_args.flavor: + try: + flavor = _find_flavor(compute_client, f) + compute_client.flavors.delete(flavor.id) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete flavor with name or " + "ID '%(flavor)s': %(e)s") + % {'flavor': f, 'e': e}) + + if result > 0: + total = len(parsed_args.flavor) + msg = (_("%(result)s of %(total)s flavors failed " + "to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) class ListFlavor(command.Lister): @@ -274,16 +320,12 @@ class SetFlavor(command.Command): flavor = _find_flavor(compute_client, parsed_args.flavor) - if not parsed_args.property and not parsed_args.project: - raise exceptions.CommandError(_("Nothing specified to be set.")) - result = 0 if parsed_args.property: try: flavor.set_keys(parsed_args.property) except Exception as e: - self.app.log.error( - _("Failed to set flavor property: %s") % str(e)) + LOG.error(_("Failed to set flavor property: %s"), e) result += 1 if parsed_args.project: @@ -300,13 +342,12 @@ class SetFlavor(command.Command): compute_client.flavor_access.add_tenant_access( flavor.id, project_id) except Exception as e: - self.app.log.error(_("Failed to set flavor access to" - " project: %s") % str(e)) + LOG.error(_("Failed to set flavor access to project: %s"), e) result += 1 if result > 0: raise exceptions.CommandError(_("Command Failed: One or more of" - " the operations failed")) + " the operations failed")) class ShowFlavor(command.ShowOne): @@ -365,16 +406,12 @@ class UnsetFlavor(command.Command): flavor = _find_flavor(compute_client, parsed_args.flavor) - if not parsed_args.property and not parsed_args.project: - raise exceptions.CommandError(_("Nothing specified to be unset.")) - result = 0 if parsed_args.property: try: flavor.unset_keys(parsed_args.property) except Exception as e: - self.app.log.error( - _("Failed to unset flavor property: %s") % str(e)) + LOG.error(_("Failed to unset flavor property: %s"), e) result += 1 if parsed_args.project: @@ -391,10 +428,10 @@ class UnsetFlavor(command.Command): compute_client.flavor_access.remove_tenant_access( flavor.id, project_id) except Exception as e: - self.app.log.error(_("Failed to remove flavor access from" - " project: %s") % str(e)) + LOG.error(_("Failed to remove flavor access from project: %s"), + e) result += 1 if result > 0: raise exceptions.CommandError(_("Command Failed: One or more of" - " the operations failed")) + " the operations failed")) diff --git a/openstackclient/compute/v2/floatingip.py b/openstackclient/compute/v2/floatingip.py index fac4d2e3..98079fbc 100644 --- a/openstackclient/compute/v2/floatingip.py +++ b/openstackclient/compute/v2/floatingip.py @@ -15,8 +15,8 @@ """Floating IP action implementations""" -from openstackclient.common import command -from openstackclient.common import utils +from osc_lib.command import command +from osc_lib import utils class AddFloatingIP(command.Command): diff --git a/openstackclient/compute/v2/floatingippool.py b/openstackclient/compute/v2/floatingippool.py index 997e0324..0d46e32b 100644 --- a/openstackclient/compute/v2/floatingippool.py +++ b/openstackclient/compute/v2/floatingippool.py @@ -15,8 +15,8 @@ """Floating IP Pool action implementations""" -from openstackclient.common import command -from openstackclient.common import utils +from osc_lib.command import command +from osc_lib import utils class ListFloatingIPPool(command.Lister): diff --git a/openstackclient/compute/v2/host.py b/openstackclient/compute/v2/host.py index 73e2cdf9..4785377e 100644 --- a/openstackclient/compute/v2/host.py +++ b/openstackclient/compute/v2/host.py @@ -1,4 +1,4 @@ -# Copyright 2013 OpenStack, LLC. +# Copyright 2012-2013 OpenStack Foundation # # 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 @@ -15,13 +15,14 @@ """Host action implementations""" -from openstackclient.common import command -from openstackclient.common import utils +from osc_lib.command import command +from osc_lib import utils + from openstackclient.i18n import _ class ListHost(command.Lister): - """List host command""" + """List hosts""" def get_parser(self, prog_name): parser = super(ListHost, self).get_parser(prog_name) @@ -53,7 +54,7 @@ class SetHost(command.Command): parser.add_argument( "host", metavar="<host>", - help=_("The host to modify (name or ID)") + help=_("Host to modify (name only)") ) status = parser.add_mutually_exclusive_group() status.add_argument( @@ -83,28 +84,30 @@ class SetHost(command.Command): kwargs = {} if parsed_args.enable: - kwargs['status'] = True + kwargs['status'] = 'enable' if parsed_args.disable: - kwargs['status'] = False + kwargs['status'] = 'disable' if parsed_args.enable_maintenance: - kwargs['maintenance_mode'] = True + kwargs['maintenance_mode'] = 'enable' if parsed_args.disable_maintenance: - kwargs['maintenance_mode'] = False + kwargs['maintenance_mode'] = 'disable' compute_client = self.app.client_manager.compute - foundhost = utils.find_resource( - compute_client.hosts, - parsed_args.host - ) + + # More than one hosts will be returned by using find_resource() + # so that the return value cannot be used in host update() method. + # find_resource() is just used for checking existence of host and + # keeping the exception message consistent with other commands. + utils.find_resource(compute_client.hosts, parsed_args.host) compute_client.hosts.update( - foundhost.id, + parsed_args.host, kwargs ) class ShowHost(command.Lister): - """Show host command""" + """Display host details""" def get_parser(self, prog_name): parser = super(ShowHost, self).get_parser(prog_name) diff --git a/openstackclient/compute/v2/hypervisor.py b/openstackclient/compute/v2/hypervisor.py index 333a7dea..00625050 100644 --- a/openstackclient/compute/v2/hypervisor.py +++ b/openstackclient/compute/v2/hypervisor.py @@ -1,4 +1,4 @@ -# Copyright 2013 OpenStack, LLC. +# Copyright 2012-2013 OpenStack Foundation # # 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 @@ -16,10 +16,11 @@ """Hypervisor action implementations""" import re + +from osc_lib.command import command +from osc_lib import utils import six -from openstackclient.common import command -from openstackclient.common import utils from openstackclient.i18n import _ diff --git a/openstackclient/compute/v2/hypervisor_stats.py b/openstackclient/compute/v2/hypervisor_stats.py index 290f5418..a70f0ce1 100644 --- a/openstackclient/compute/v2/hypervisor_stats.py +++ b/openstackclient/compute/v2/hypervisor_stats.py @@ -16,7 +16,7 @@ import six -from openstackclient.common import command +from osc_lib.command import command class ShowHypervisorStats(command.ShowOne): diff --git a/openstackclient/compute/v2/keypair.py b/openstackclient/compute/v2/keypair.py index 8af209fe..3725a3a8 100644 --- a/openstackclient/compute/v2/keypair.py +++ b/openstackclient/compute/v2/keypair.py @@ -16,16 +16,21 @@ """Keypair action implementations""" import io +import logging import os -import six import sys -from openstackclient.common import command -from openstackclient.common import exceptions -from openstackclient.common import utils +from osc_lib.command import command +from osc_lib import exceptions +from osc_lib import utils +import six + from openstackclient.i18n import _ +LOG = logging.getLogger(__name__) + + class CreateKeypair(command.ShowOne): """Create new public key""" @@ -77,20 +82,37 @@ class CreateKeypair(command.ShowOne): class DeleteKeypair(command.Command): - """Delete public key""" + """Delete public key(s)""" def get_parser(self, prog_name): parser = super(DeleteKeypair, self).get_parser(prog_name) parser.add_argument( 'name', metavar='<key>', - help=_("Public key to delete (name only)") + nargs='+', + help=_("Public key(s) to delete (name only)") ) return parser def take_action(self, parsed_args): compute_client = self.app.client_manager.compute - compute_client.keypairs.delete(parsed_args.name) + result = 0 + for n in parsed_args.name: + try: + data = utils.find_resource( + compute_client.keypairs, n) + compute_client.keypairs.delete(data.name) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete public key with name " + "'%(name)s': %(e)s") + % {'name': n, 'e': e}) + + if result > 0: + total = len(parsed_args.name) + msg = (_("%(result)s of %(total)s public keys failed " + "to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) class ListKeypair(command.Lister): diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 5688b55f..7e4b0dc1 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -18,24 +18,28 @@ import argparse import getpass import io +import logging import os -import six import sys -from openstackclient.common import command +from osc_lib.cli import parseractions +from osc_lib.command import command +from osc_lib import exceptions +from osc_lib import utils +import six try: from novaclient.v2 import servers except ImportError: from novaclient.v1_1 import servers -from openstackclient.common import exceptions -from openstackclient.common import parseractions -from openstackclient.common import utils from openstackclient.i18n import _ from openstackclient.identity import common as identity_common +LOG = logging.getLogger(__name__) + + def _format_servers_list_networks(networks): """Return a formatted string of a server's networks @@ -164,23 +168,6 @@ def _prep_server_detail(compute_client, server): return info -def _prep_image_detail(image_client, image_id): - """Prepare the detailed image dict for printing - - :param image_client: an image client instance - :param image_id: id of image created - :rtype: a dict of image details - """ - - info = utils.find_resource( - image_client.images, - image_id, - ) - # Glance client V2 doesn't have _info attribute - # The following condition deals with it. - return getattr(info, "_info", info) - - def _show_progress(progress): if progress: sys.stdout.write('\rProgress: %s' % progress) @@ -538,8 +525,8 @@ class CreateServer(command.ShowOne): scheduler_hints=hints, config_drive=config_drive) - self.log.debug('boot_args: %s', boot_args) - self.log.debug('boot_kwargs: %s', boot_kwargs) + LOG.debug('boot_args: %s', boot_args) + LOG.debug('boot_kwargs: %s', boot_kwargs) # Wrap the call to catch exceptions in order to close files try: @@ -560,8 +547,8 @@ class CreateServer(command.ShowOne): ): sys.stdout.write('\n') else: - self.log.error(_('Error creating server: %s'), - parsed_args.server_name) + LOG.error(_('Error creating server: %s'), + parsed_args.server_name) sys.stdout.write(_('Error creating server\n')) raise SystemExit @@ -597,63 +584,6 @@ class CreateServerDump(command.Command): ).trigger_crash_dump() -class CreateServerImage(command.ShowOne): - """Create a new disk image from a running server""" - - def get_parser(self, prog_name): - parser = super(CreateServerImage, self).get_parser(prog_name) - parser.add_argument( - 'server', - metavar='<server>', - help=_('Server (name or ID)'), - ) - parser.add_argument( - '--name', - metavar='<image-name>', - help=_('Name of new image (default is server name)'), - ) - parser.add_argument( - '--wait', - action='store_true', - help=_('Wait for image create to complete'), - ) - return parser - - def take_action(self, parsed_args): - compute_client = self.app.client_manager.compute - image_client = self.app.client_manager.image - server = utils.find_resource( - compute_client.servers, - parsed_args.server, - ) - if parsed_args.name: - name = parsed_args.name - else: - name = server.name - - image_id = compute_client.servers.create_image( - server, - name, - ) - - if parsed_args.wait: - if utils.wait_for_status( - image_client.images.get, - image_id, - callback=_show_progress, - ): - sys.stdout.write('\n') - else: - self.log.error(_('Error creating snapshot of server: %s'), - parsed_args.server) - sys.stdout.write(_('Error creating server snapshot\n')) - raise SystemExit - - image = _prep_image_detail(image_client, image_id) - - return zip(*sorted(six.iteritems(image))) - - class DeleteServer(command.Command): """Delete server(s)""" @@ -686,8 +616,8 @@ class DeleteServer(command.Command): ): sys.stdout.write('\n') else: - self.log.error(_('Error deleting server: %s'), - server_obj.id) + LOG.error(_('Error deleting server: %s'), + server_obj.id) sys.stdout.write(_('Error deleting server\n')) raise SystemExit @@ -836,7 +766,7 @@ class ListServer(command.Lister): 'all_tenants': parsed_args.all_projects, 'user_id': user_id, } - self.log.debug('search options: %s', search_opts) + LOG.debug('search options: %s', search_opts) if parsed_args.long: columns = ( @@ -1013,8 +943,8 @@ class MigrateServer(command.Command): ): sys.stdout.write(_('Complete\n')) else: - self.log.error(_('Error migrating server: %s'), - server.id) + LOG.error(_('Error migrating server: %s'), + server.id) sys.stdout.write(_('Error migrating server\n')) raise SystemExit @@ -1089,8 +1019,8 @@ class RebootServer(command.Command): ): sys.stdout.write(_('Complete\n')) else: - self.log.error(_('Error rebooting server: %s'), - server.id) + LOG.error(_('Error rebooting server: %s'), + server.id) sys.stdout.write(_('Error rebooting server\n')) raise SystemExit @@ -1142,8 +1072,8 @@ class RebuildServer(command.ShowOne): ): sys.stdout.write(_('Complete\n')) else: - self.log.error(_('Error rebuilding server: %s'), - server.id) + LOG.error(_('Error rebuilding server: %s'), + server.id) sys.stdout.write(_('Error rebuilding server\n')) raise SystemExit @@ -1296,8 +1226,8 @@ class ResizeServer(command.Command): ): sys.stdout.write(_('Complete\n')) else: - self.log.error(_('Error resizing server: %s'), - server.id) + LOG.error(_('Error resizing server: %s'), + server.id) sys.stdout.write(_('Error resizing server\n')) raise SystemExit elif parsed_args.confirm: @@ -1612,7 +1542,7 @@ class SshServer(command.Command): ip_address = _get_ip_address(server.addresses, parsed_args.address_type, ip_address_family) - self.log.debug("ssh command: %s", (cmd % (login, ip_address))) + LOG.debug("ssh command: %s", (cmd % (login, ip_address))) os.system(cmd % (login, ip_address)) diff --git a/openstackclient/compute/v2/server_backup.py b/openstackclient/compute/v2/server_backup.py index 24d71015..c0e2e5ee 100644 --- a/openstackclient/compute/v2/server_backup.py +++ b/openstackclient/compute/v2/server_backup.py @@ -17,12 +17,12 @@ import sys +from osc_lib.command import command +from osc_lib import exceptions +from osc_lib import utils from oslo_utils import importutils import six -from openstackclient.common import command -from openstackclient.common import exceptions -from openstackclient.common import utils from openstackclient.i18n import _ diff --git a/openstackclient/compute/v2/server_group.py b/openstackclient/compute/v2/server_group.py index 2e275b71..d51b1ec2 100644 --- a/openstackclient/compute/v2/server_group.py +++ b/openstackclient/compute/v2/server_group.py @@ -15,12 +15,18 @@ """Compute v2 Server Group action implementations""" -from openstackclient.common import command -from openstackclient.common import exceptions -from openstackclient.common import utils +import logging + +from osc_lib.command import command +from osc_lib import exceptions +from osc_lib import utils + from openstackclient.i18n import _ +LOG = logging.getLogger(__name__) + + _formatters = { 'policies': utils.format_list, 'members': utils.format_list, @@ -94,7 +100,7 @@ class DeleteServerGroup(command.Command): # Catch all exceptions in order to avoid to block the next deleting except Exception as e: result += 1 - self.app.log.error(e) + LOG.error(e) if result > 0: total = len(parsed_args.server_group) diff --git a/openstackclient/compute/v2/server_image.py b/openstackclient/compute/v2/server_image.py new file mode 100644 index 00000000..285c7fd2 --- /dev/null +++ b/openstackclient/compute/v2/server_image.py @@ -0,0 +1,113 @@ +# Copyright 2012-2013 OpenStack Foundation +# +# 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. +# + +"""Compute v2 Server action implementations""" + +import logging +import sys + +from osc_lib.command import command +from osc_lib import exceptions +from osc_lib import utils +from oslo_utils import importutils +import six + +from openstackclient.i18n import _ + + +LOG = logging.getLogger(__name__) + + +def _show_progress(progress): + if progress: + sys.stdout.write('\rProgress: %s' % progress) + sys.stdout.flush() + + +class CreateServerImage(command.ShowOne): + """Create a new server disk image from an existing server""" + + IMAGE_API_VERSIONS = { + "1": "openstackclient.image.v1.image", + "2": "openstackclient.image.v2.image", + } + + def get_parser(self, prog_name): + parser = super(CreateServerImage, self).get_parser(prog_name) + parser.add_argument( + 'server', + metavar='<server>', + help=_('Server to create image (name or ID)'), + ) + parser.add_argument( + '--name', + metavar='<image-name>', + help=_('Name of new disk image (default: server name)'), + ) + parser.add_argument( + '--wait', + action='store_true', + help=_('Wait for operation to complete'), + ) + return parser + + def take_action(self, parsed_args): + compute_client = self.app.client_manager.compute + + server = utils.find_resource( + compute_client.servers, + parsed_args.server, + ) + if parsed_args.name: + image_name = parsed_args.name + else: + image_name = server.name + + image_id = compute_client.servers.create_image( + server.id, + image_name, + ) + + image_client = self.app.client_manager.image + image = utils.find_resource( + image_client.images, + image_id, + ) + + if parsed_args.wait: + if utils.wait_for_status( + image_client.images.get, + image_id, + callback=_show_progress, + ): + sys.stdout.write('\n') + else: + LOG.error(_('Error creating server image: %s'), + parsed_args.server) + raise exceptions.CommandError + + if self.app.client_manager._api_version['image'] == '1': + info = {} + info.update(image._info) + info['properties'] = utils.format_dict(info.get('properties', {})) + else: + # Get the right image module to format the output + image_module = importutils.import_module( + self.IMAGE_API_VERSIONS[ + self.app.client_manager._api_version['image'] + ] + ) + info = image_module._format_image(image) + return zip(*sorted(six.iteritems(info))) diff --git a/openstackclient/compute/v2/service.py b/openstackclient/compute/v2/service.py index e7966a8a..53624f55 100644 --- a/openstackclient/compute/v2/service.py +++ b/openstackclient/compute/v2/service.py @@ -1,4 +1,4 @@ -# Copyright 2013 OpenStack, LLC. +# Copyright 2012-2013 OpenStack Foundation # # 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 @@ -15,32 +15,53 @@ """Service action implementations""" -from openstackclient.common import command -from openstackclient.common import exceptions -from openstackclient.common import utils +import logging + +from osc_lib.command import command +from osc_lib import exceptions +from osc_lib import utils + from openstackclient.i18n import _ +from openstackclient.i18n import _LE + + +LOG = logging.getLogger(__name__) class DeleteService(command.Command): - """Delete service command""" + """Delete compute service(s)""" def get_parser(self, prog_name): parser = super(DeleteService, self).get_parser(prog_name) parser.add_argument( "service", metavar="<service>", - help=_("Compute service to delete (ID only)") + nargs='+', + help=_("Compute service(s) to delete (ID only)") ) return parser def take_action(self, parsed_args): compute_client = self.app.client_manager.compute - - compute_client.services.delete(parsed_args.service) + result = 0 + for s in parsed_args.service: + try: + compute_client.services.delete(s) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete compute service with " + "ID '%(service)s': %(e)s") + % {'service': s, 'e': e}) + + if result > 0: + total = len(parsed_args.service) + msg = (_("%(result)s of %(total)s compute services failed " + "to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) class ListService(command.Lister): - """List service command""" + """List compute services""" def get_parser(self, prog_name): parser = super(ListService, self).get_parser(prog_name) @@ -66,7 +87,7 @@ class ListService(command.Lister): compute_client = self.app.client_manager.compute if parsed_args.long: columns = ( - "Id", + "ID", "Binary", "Host", "Zone", @@ -77,7 +98,7 @@ class ListService(command.Lister): ) else: columns = ( - "Id", + "ID", "Binary", "Host", "Zone", @@ -94,7 +115,7 @@ class ListService(command.Lister): class SetService(command.Command): - """Set service command""" + """Set compute service properties""" def get_parser(self, prog_name): parser = super(SetService, self).get_parser(prog_name) @@ -106,7 +127,7 @@ class SetService(command.Command): parser.add_argument( "service", metavar="<service>", - help=_("Name of service") + help=_("Name of service (Binary name)") ) enabled_group = parser.add_mutually_exclusive_group() enabled_group.add_argument( @@ -126,6 +147,17 @@ class SetService(command.Command): help=_("Reason for disabling the service (in quotas). " "Should be used with --disable option.") ) + up_down_group = parser.add_mutually_exclusive_group() + up_down_group.add_argument( + '--up', + action='store_true', + help=_('Force up service'), + ) + up_down_group.add_argument( + '--down', + action='store_true', + help=_('Force down service'), + ) return parser def take_action(self, parsed_args): @@ -138,20 +170,45 @@ class SetService(command.Command): "--disable specified.") raise exceptions.CommandError(msg) + result = 0 enabled = None - if parsed_args.enable: - enabled = True - if parsed_args.disable: - enabled = False - - if enabled is None: - return - elif enabled: - cs.enable(parsed_args.host, parsed_args.service) - else: - if parsed_args.disable_reason: - cs.disable_log_reason(parsed_args.host, - parsed_args.service, - parsed_args.disable_reason) - else: - cs.disable(parsed_args.host, parsed_args.service) + try: + if parsed_args.enable: + enabled = True + if parsed_args.disable: + enabled = False + + if enabled is not None: + if enabled: + cs.enable(parsed_args.host, parsed_args.service) + else: + if parsed_args.disable_reason: + cs.disable_log_reason(parsed_args.host, + parsed_args.service, + parsed_args.disable_reason) + else: + cs.disable(parsed_args.host, parsed_args.service) + except Exception: + status = "enabled" if enabled else "disabled" + LOG.error(_LE("Failed to set service status to %s"), status) + result += 1 + + force_down = None + try: + if parsed_args.down: + force_down = True + if parsed_args.up: + force_down = False + if force_down is not None: + cs.force_down(parsed_args.host, parsed_args.service, + force_down=force_down) + except Exception: + state = "down" if force_down else "up" + LOG.error(_LE("Failed to set service state to %s"), state) + result += 1 + + if result > 0: + msg = _("Compute service %(service)s of host %(host)s failed to " + "set.") % {"service": parsed_args.service, + "host": parsed_args.host} + raise exceptions.CommandError(msg) diff --git a/openstackclient/compute/v2/usage.py b/openstackclient/compute/v2/usage.py index b83bef13..2f35b01b 100644 --- a/openstackclient/compute/v2/usage.py +++ b/openstackclient/compute/v2/usage.py @@ -18,10 +18,10 @@ import datetime import sys +from osc_lib.command import command +from osc_lib import utils import six -from openstackclient.common import command -from openstackclient.common import utils from openstackclient.i18n import _ diff --git a/openstackclient/identity/client.py b/openstackclient/identity/client.py index c166e66a..be7b643f 100644 --- a/openstackclient/identity/client.py +++ b/openstackclient/identity/client.py @@ -16,8 +16,10 @@ import logging from keystoneclient.v2_0 import client as identity_client_v2 +from osc_lib import utils + from openstackclient.api import auth -from openstackclient.common import utils +from openstackclient.i18n import _ LOG = logging.getLogger(__name__) @@ -64,9 +66,9 @@ def build_option_parser(parser): '--os-identity-api-version', metavar='<identity-api-version>', default=utils.env('OS_IDENTITY_API_VERSION'), - help='Identity API version, default=' + - DEFAULT_API_VERSION + - ' (Env: OS_IDENTITY_API_VERSION)') + help=_('Identity API version, default=%s ' + '(Env: OS_IDENTITY_API_VERSION)') % DEFAULT_API_VERSION, + ) return auth.build_auth_plugins_option_parser(parser) diff --git a/openstackclient/identity/common.py b/openstackclient/identity/common.py index 2afa41fb..379f4114 100644 --- a/openstackclient/identity/common.py +++ b/openstackclient/identity/common.py @@ -20,9 +20,10 @@ from keystoneclient.v3 import domains from keystoneclient.v3 import groups from keystoneclient.v3 import projects from keystoneclient.v3 import users +from osc_lib import exceptions +from osc_lib import utils -from openstackclient.common import exceptions -from openstackclient.common import utils +from openstackclient.i18n import _ def find_service(identity_client, name_type_or_id): @@ -38,14 +39,45 @@ def find_service(identity_client, name_type_or_id): # FIXME(dtroyer): This exception should eventually come from # common client exceptions except identity_exc.NotFound: - msg = ("No service with a type, name or ID of '%s' exists." - % name_type_or_id) - raise exceptions.CommandError(msg) + msg = _("No service with a type, name or ID of '%s' exists.") + raise exceptions.CommandError(msg % name_type_or_id) except identity_exc.NoUniqueMatch: - msg = ("Multiple service matches found for '%s', " - "use an ID to be more specific." - % name_type_or_id) - raise exceptions.CommandError(msg) + msg = _("Multiple service matches found for '%s', " + "use an ID to be more specific.") + raise exceptions.CommandError(msg % name_type_or_id) + + +def _get_token_resource(client, resource, parsed_name): + """Peek into the user's auth token to get resource IDs + + Look into a user's token to try and find the ID of a domain, project or + user, when given the name. Typically non-admin users will interact with + the CLI using names. However, by default, keystone does not allow look up + by name since it would involve listing all entities. Instead opt to use + the correct ID (from the token) instead. + :param client: An identity client + :param resource: A resource to look at in the token, this may be `domain`, + `project_domain`, `user_domain`, `project`, or `user`. + :param parsed_name: This is input from parsed_args that the user is hoping + to find in the token. + + :returns: The ID of the resource from the token, or the original value from + parsed_args if it does not match. + """ + + try: + token = client.auth.client.get_token() + token_data = client.tokens.get_token_data(token) + token_dict = token_data['token'] + + # NOTE(stevemar): If domain is passed, just look at the project domain. + if resource == 'domain': + token_dict = token_dict['project'] + obj = token_dict[resource] + return obj['id'] if obj['name'] == parsed_name else parsed_name + # diaper defense in case parsing the token fails + except Exception: # noqa + return parsed_name def _get_domain_id_if_requested(identity_client, domain_name_or_id): @@ -132,9 +164,9 @@ def add_user_domain_option_to_parser(parser): parser.add_argument( '--user-domain', metavar='<user-domain>', - help=('Domain the user belongs to (name or ID). ' - 'This can be used in case collisions between user names ' - 'exist.') + help=_('Domain the user belongs to (name or ID). ' + 'This can be used in case collisions between user names ' + 'exist.'), ) @@ -142,9 +174,9 @@ def add_group_domain_option_to_parser(parser): parser.add_argument( '--group-domain', metavar='<group-domain>', - help=('Domain the group belongs to (name or ID). ' - 'This can be used in case collisions between group names ' - 'exist.') + help=_('Domain the group belongs to (name or ID). ' + 'This can be used in case collisions between group names ' + 'exist.'), ) @@ -152,9 +184,9 @@ def add_project_domain_option_to_parser(parser): parser.add_argument( '--project-domain', metavar='<project-domain>', - help=('Domain the project belongs to (name or ID). ' - 'This can be used in case collisions between project names ' - 'exist.') + help=_('Domain the project belongs to (name or ID). ' + 'This can be used in case collisions between project names ' + 'exist.'), ) @@ -163,5 +195,6 @@ def add_inherited_option_to_parser(parser): '--inherited', action='store_true', default=False, - help=('Specifies if the role grant is inheritable to the sub projects') + help=_('Specifies if the role grant is inheritable to the sub ' + 'projects'), ) diff --git a/openstackclient/identity/v2_0/catalog.py b/openstackclient/identity/v2_0/catalog.py index c8f48cb6..7a15cf3a 100644 --- a/openstackclient/identity/v2_0/catalog.py +++ b/openstackclient/identity/v2_0/catalog.py @@ -13,13 +13,19 @@ """Identity v2 Service Catalog action implementations""" +import logging + +from osc_lib.command import command +from osc_lib import exceptions +from osc_lib import utils import six -from openstackclient.common import command -from openstackclient.common import utils from openstackclient.i18n import _ +LOG = logging.getLogger(__name__) + + def _format_endpoints(eps=None): if not eps: return "" @@ -41,13 +47,14 @@ class ListCatalog(command.Lister): def take_action(self, parsed_args): - # This is ugly because if auth hasn't happened yet we need - # to trigger it here. - sc = self.app.client_manager.session.auth.get_auth_ref( - self.app.client_manager.session, - ).service_catalog + # Trigger auth if it has not happened yet + auth_ref = self.app.client_manager.auth_ref + if not auth_ref: + raise exceptions.AuthorizationFailure( + "Only an authorized user may issue a new token." + ) - data = sc.get_data() + data = auth_ref.service_catalog.catalog columns = ('Name', 'Type', 'Endpoints') return (columns, (utils.get_dict_properties( @@ -72,14 +79,15 @@ class ShowCatalog(command.ShowOne): def take_action(self, parsed_args): - # This is ugly because if auth hasn't happened yet we need - # to trigger it here. - sc = self.app.client_manager.session.auth.get_auth_ref( - self.app.client_manager.session, - ).service_catalog + # Trigger auth if it has not happened yet + auth_ref = self.app.client_manager.auth_ref + if not auth_ref: + raise exceptions.AuthorizationFailure( + "Only an authorized user may issue a new token." + ) data = None - for service in sc.get_data(): + for service in auth_ref.service_catalog.catalog: if (service.get('name') == parsed_args.service or service.get('type') == parsed_args.service): data = service @@ -89,8 +97,7 @@ class ShowCatalog(command.ShowOne): break if not data: - self.app.log.error(_('service %s not found\n') % - parsed_args.service) - return ([], []) + LOG.error(_('service %s not found\n'), parsed_args.service) + return ((), ()) return zip(*sorted(six.iteritems(data))) diff --git a/openstackclient/identity/v2_0/ec2creds.py b/openstackclient/identity/v2_0/ec2creds.py index dfd67591..058b5772 100644 --- a/openstackclient/identity/v2_0/ec2creds.py +++ b/openstackclient/identity/v2_0/ec2creds.py @@ -16,13 +16,19 @@ """Identity v2 EC2 Credentials action implementations""" +import logging + +from osc_lib.command import command +from osc_lib import exceptions +from osc_lib import utils import six -from openstackclient.common import command -from openstackclient.common import utils from openstackclient.i18n import _ +LOG = logging.getLogger(__name__) + + class CreateEC2Creds(command.ShowOne): """Create EC2 credentials""" @@ -85,9 +91,10 @@ class DeleteEC2Creds(command.Command): def get_parser(self, prog_name): parser = super(DeleteEC2Creds, self).get_parser(prog_name) parser.add_argument( - 'access_key', + 'access_keys', metavar='<access-key>', - help=_('Credentials access key'), + nargs='+', + help=_('Credentials access keys'), ) parser.add_argument( '--user', @@ -108,7 +115,21 @@ class DeleteEC2Creds(command.Command): # Get the user from the current auth user = self.app.client_manager.auth_ref.user_id - identity_client.ec2.delete(user, parsed_args.access_key) + result = 0 + for access_key in parsed_args.access_keys: + try: + identity_client.ec2.delete(user, access_key) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete EC2 keys with " + "access key '%(access_key)s': %(e)s") + % {'access_key': access_key, 'e': e}) + + if result > 0: + total = len(parsed_args.access_keys) + msg = (_("%(result)s of %(total)s EC2 keys failed " + "to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) class ListEC2Creds(command.Lister): diff --git a/openstackclient/identity/v2_0/endpoint.py b/openstackclient/identity/v2_0/endpoint.py index 09ea738f..5a3b3186 100644 --- a/openstackclient/identity/v2_0/endpoint.py +++ b/openstackclient/identity/v2_0/endpoint.py @@ -15,14 +15,20 @@ """Endpoint action implementations""" +import logging + +from osc_lib.command import command +from osc_lib import exceptions +from osc_lib import utils import six -from openstackclient.common import command -from openstackclient.common import utils from openstackclient.i18n import _ from openstackclient.identity import common +LOG = logging.getLogger(__name__) + + class CreateEndpoint(command.ShowOne): """Create new endpoint""" @@ -31,7 +37,7 @@ class CreateEndpoint(command.ShowOne): parser.add_argument( 'service', metavar='<service>', - help=_('New endpoint service (name or ID)'), + help=_('Service to be associated with new endpoint (name or ID)'), ) parser.add_argument( '--publicurl', @@ -74,20 +80,36 @@ class CreateEndpoint(command.ShowOne): class DeleteEndpoint(command.Command): - """Delete endpoint""" + """Delete endpoint(s)""" def get_parser(self, prog_name): parser = super(DeleteEndpoint, self).get_parser(prog_name) parser.add_argument( - 'endpoint', + 'endpoints', metavar='<endpoint-id>', - help=_('Endpoint ID to delete'), + nargs='+', + help=_('Endpoint(s) to delete (ID only)'), ) return parser def take_action(self, parsed_args): identity_client = self.app.client_manager.identity - identity_client.endpoints.delete(parsed_args.endpoint) + + result = 0 + for endpoint in parsed_args.endpoints: + try: + identity_client.endpoints.delete(endpoint) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete endpoint with " + "ID '%(endpoint)s': %(e)s") + % {'endpoint': endpoint, 'e': e}) + + if result > 0: + total = len(parsed_args.endpoints) + msg = (_("%(result)s of %(total)s endpoints failed " + "to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) class ListEndpoint(command.Lister): diff --git a/openstackclient/identity/v2_0/project.py b/openstackclient/identity/v2_0/project.py index 80f88d73..6c5db13c 100644 --- a/openstackclient/identity/v2_0/project.py +++ b/openstackclient/identity/v2_0/project.py @@ -15,16 +15,20 @@ """Identity v2 Project action implementations""" -import six +import logging from keystoneauth1 import exceptions as ks_exc +from osc_lib.cli import parseractions +from osc_lib.command import command +from osc_lib import utils +import six -from openstackclient.common import command -from openstackclient.common import parseractions -from openstackclient.common import utils from openstackclient.i18n import _ +LOG = logging.getLogger(__name__) + + class CreateProject(command.ShowOne): """Create new project""" @@ -88,7 +92,7 @@ class CreateProject(command.ShowOne): identity_client.tenants, parsed_args.name, ) - self.log.info(_('Returning existing project %s'), project.name) + LOG.info(_('Returning existing project %s'), project.name) else: raise e @@ -190,13 +194,6 @@ class SetProject(command.Command): def take_action(self, parsed_args): identity_client = self.app.client_manager.identity - if (not parsed_args.name - and not parsed_args.description - and not parsed_args.enable - and not parsed_args.property - and not parsed_args.disable): - return - project = utils.find_resource( identity_client.tenants, parsed_args.project, @@ -296,7 +293,6 @@ class UnsetProject(command.Command): metavar='<key>', action='append', default=[], - required=True, help=_('Unset a project property ' '(repeat option to unset multiple properties)'), ) @@ -308,11 +304,8 @@ class UnsetProject(command.Command): identity_client.tenants, parsed_args.project, ) - if not parsed_args.property: - self.app.log.error(_("No changes requested\n")) - else: - kwargs = project._info - for key in parsed_args.property: - if key in kwargs: - kwargs[key] = None - identity_client.tenants.update(project.id, **kwargs) + kwargs = project._info + for key in parsed_args.property: + if key in kwargs: + kwargs[key] = None + identity_client.tenants.update(project.id, **kwargs) diff --git a/openstackclient/identity/v2_0/role.py b/openstackclient/identity/v2_0/role.py index 6b014d86..6d06230c 100644 --- a/openstackclient/identity/v2_0/role.py +++ b/openstackclient/identity/v2_0/role.py @@ -15,16 +15,20 @@ """Identity v2 Role action implementations""" -import six +import logging from keystoneauth1 import exceptions as ks_exc +from osc_lib.command import command +from osc_lib import exceptions +from osc_lib import utils +import six -from openstackclient.common import command -from openstackclient.common import exceptions -from openstackclient.common import utils from openstackclient.i18n import _ +LOG = logging.getLogger(__name__) + + class AddRole(command.ShowOne): """Add role to project:user""" @@ -95,7 +99,7 @@ class CreateRole(command.ShowOne): identity_client.roles, parsed_args.role_name, ) - self.log.info(_('Returning existing role %s'), role.name) + LOG.info(_('Returning existing role %s'), role.name) else: raise e @@ -231,18 +235,19 @@ class ListUserRole(command.Lister): # Project and user are required, if not included in command args # default to the values used for authentication. For token-flow # authentication they must be included on the command line. + if (not parsed_args.project and + self.app.client_manager.auth_ref.project_id): + parsed_args.project = auth_ref.project_id if not parsed_args.project: - if self.app.client_manager.auth_ref: - parsed_args.project = auth_ref.project_id - else: - msg = _("Project must be specified") - raise exceptions.CommandError(msg) + msg = _("Project must be specified") + raise exceptions.CommandError(msg) + + if (not parsed_args.user and + self.app.client_manager.auth_ref.user_id): + parsed_args.user = auth_ref.user_id if not parsed_args.user: - if self.app.client_manager.auth_ref: - parsed_args.user = auth_ref.user_id - else: - msg = _("User must be specified") - raise exceptions.CommandError(msg) + msg = _("User must be specified") + raise exceptions.CommandError(msg) project = utils.find_resource( identity_client.tenants, diff --git a/openstackclient/identity/v2_0/service.py b/openstackclient/identity/v2_0/service.py index 7fe66d91..e318643c 100644 --- a/openstackclient/identity/v2_0/service.py +++ b/openstackclient/identity/v2_0/service.py @@ -16,15 +16,20 @@ """Service action implementations""" import argparse +import logging + +from osc_lib.command import command +from osc_lib import exceptions +from osc_lib import utils import six -from openstackclient.common import command -from openstackclient.common import exceptions -from openstackclient.common import utils from openstackclient.i18n import _ from openstackclient.identity import common +LOG = logging.getLogger(__name__) + + class CreateService(command.ShowOne): """Create new service""" @@ -68,8 +73,8 @@ class CreateService(command.ShowOne): # display deprecation message. elif type: name = type_or_name - self.log.warning(_('The argument --type is deprecated, use service' - ' create --name <service-name> type instead.')) + LOG.warning(_('The argument --type is deprecated, use service' + ' create --name <service-name> type instead.')) # If --name option is present the positional is handled as <type>. # Making --type optional is new, but back-compatible elif name: @@ -86,21 +91,37 @@ class CreateService(command.ShowOne): class DeleteService(command.Command): - """Delete service""" + """Delete service(s)""" def get_parser(self, prog_name): parser = super(DeleteService, self).get_parser(prog_name) parser.add_argument( - 'service', + 'services', metavar='<service>', - help=_('Service to delete (name or ID)'), + nargs='+', + help=_('Service(s) to delete (type, name or ID)'), ) return parser def take_action(self, parsed_args): identity_client = self.app.client_manager.identity - service = common.find_service(identity_client, parsed_args.service) - identity_client.services.delete(service.id) + + result = 0 + for service in parsed_args.services: + try: + service = common.find_service(identity_client, service) + identity_client.services.delete(service.id) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete service with " + "name or ID '%(service)s': %(e)s") + % {'service': service, 'e': e}) + + if result > 0: + total = len(parsed_args.services) + msg = (_("%(result)s of %(total)s services failed " + "to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) class ListService(command.Lister): diff --git a/openstackclient/identity/v2_0/token.py b/openstackclient/identity/v2_0/token.py index f435d7ce..56f920f3 100644 --- a/openstackclient/identity/v2_0/token.py +++ b/openstackclient/identity/v2_0/token.py @@ -15,9 +15,10 @@ """Identity v2 Token action implementations""" +from osc_lib.command import command +from osc_lib import exceptions import six -from openstackclient.common import command from openstackclient.i18n import _ @@ -32,11 +33,21 @@ class IssueToken(command.ShowOne): return parser def take_action(self, parsed_args): + auth_ref = self.app.client_manager.auth_ref + if not auth_ref: + raise exceptions.AuthorizationFailure( + "Only an authorized user may issue a new token.") - token = self.app.client_manager.auth_ref.service_catalog.get_token() - if 'tenant_id' in token: - token['project_id'] = token.pop('tenant_id') - return zip(*sorted(six.iteritems(token))) + data = {} + if auth_ref.auth_token: + data['id'] = auth_ref.auth_token + if auth_ref.expires: + data['expires'] = auth_ref.expires + if auth_ref.project_id: + data['project_id'] = auth_ref.project_id + if auth_ref.user_id: + data['user_id'] = auth_ref.user_id + return zip(*sorted(six.iteritems(data))) class RevokeToken(command.Command): diff --git a/openstackclient/identity/v2_0/user.py b/openstackclient/identity/v2_0/user.py index f8f5df29..0f327830 100644 --- a/openstackclient/identity/v2_0/user.py +++ b/openstackclient/identity/v2_0/user.py @@ -15,15 +15,19 @@ """Identity v2.0 User action implementations""" -import six +import logging from keystoneauth1 import exceptions as ks_exc +from osc_lib.command import command +from osc_lib import utils +import six -from openstackclient.common import command -from openstackclient.common import utils from openstackclient.i18n import _ +LOG = logging.getLogger(__name__) + + class CreateUser(command.ShowOne): """Create new user""" @@ -104,7 +108,7 @@ class CreateUser(command.ShowOne): identity_client.users, parsed_args.name, ) - self.log.info(_('Returning existing user %s'), user.name) + LOG.info(_('Returning existing user %s'), user.name) else: raise e @@ -241,7 +245,7 @@ class SetUser(command.Command): parser.add_argument( 'user', metavar='<user>', - help=_('User to change (name or ID)'), + help=_('User to modify (name or ID)'), ) parser.add_argument( '--name', @@ -288,15 +292,6 @@ class SetUser(command.Command): if parsed_args.password_prompt: parsed_args.password = utils.get_password(self.app.stdin) - if (not parsed_args.name - and not parsed_args.name - and not parsed_args.password - and not parsed_args.email - and not parsed_args.project - and not parsed_args.enable - and not parsed_args.disable): - return - user = utils.find_resource( identity_client.users, parsed_args.user, diff --git a/openstackclient/identity/v3/catalog.py b/openstackclient/identity/v3/catalog.py index 4c794692..a62d0a93 100644 --- a/openstackclient/identity/v3/catalog.py +++ b/openstackclient/identity/v3/catalog.py @@ -13,13 +13,19 @@ """Identity v3 Service Catalog action implementations""" +import logging + +from osc_lib.command import command +from osc_lib import exceptions +from osc_lib import utils import six -from openstackclient.common import command -from openstackclient.common import utils from openstackclient.i18n import _ +LOG = logging.getLogger(__name__) + + def _format_endpoints(eps=None): if not eps: return "" @@ -36,13 +42,14 @@ class ListCatalog(command.Lister): def take_action(self, parsed_args): - # This is ugly because if auth hasn't happened yet we need - # to trigger it here. - sc = self.app.client_manager.session.auth.get_auth_ref( - self.app.client_manager.session, - ).service_catalog + # Trigger auth if it has not happened yet + auth_ref = self.app.client_manager.auth_ref + if not auth_ref: + raise exceptions.AuthorizationFailure( + "Only an authorized user may issue a new token." + ) - data = sc.get_data() + data = auth_ref.service_catalog.catalog columns = ('Name', 'Type', 'Endpoints') return (columns, (utils.get_dict_properties( @@ -67,14 +74,15 @@ class ShowCatalog(command.ShowOne): def take_action(self, parsed_args): - # This is ugly because if auth hasn't happened yet we need - # to trigger it here. - sc = self.app.client_manager.session.auth.get_auth_ref( - self.app.client_manager.session, - ).service_catalog + # Trigger auth if it has not happened yet + auth_ref = self.app.client_manager.auth_ref + if not auth_ref: + raise exceptions.AuthorizationFailure( + "Only an authorized user may issue a new token." + ) data = None - for service in sc.get_data(): + for service in auth_ref.service_catalog.catalog: if (service.get('name') == parsed_args.service or service.get('type') == parsed_args.service): data = dict(service) @@ -84,8 +92,7 @@ class ShowCatalog(command.ShowOne): break if not data: - self.app.log.error(_('service %s not found\n') % - parsed_args.service) - return ([], []) + LOG.error(_('service %s not found\n'), parsed_args.service) + return ((), ()) return zip(*sorted(six.iteritems(data))) diff --git a/openstackclient/identity/v3/consumer.py b/openstackclient/identity/v3/consumer.py index a062b743..a4620bf9 100644 --- a/openstackclient/identity/v3/consumer.py +++ b/openstackclient/identity/v3/consumer.py @@ -15,11 +15,12 @@ """Identity v3 Consumer action implementations""" -import six import sys -from openstackclient.common import command -from openstackclient.common import utils +from osc_lib.command import command +from osc_lib import utils +import six + from openstackclient.i18n import _ diff --git a/openstackclient/identity/v3/credential.py b/openstackclient/identity/v3/credential.py index 99013478..eeeddfa5 100644 --- a/openstackclient/identity/v3/credential.py +++ b/openstackclient/identity/v3/credential.py @@ -15,10 +15,10 @@ """Identity v3 Credential action implementations""" +from osc_lib.command import command +from osc_lib import utils import six -from openstackclient.common import command -from openstackclient.common import utils from openstackclient.i18n import _ diff --git a/openstackclient/identity/v3/domain.py b/openstackclient/identity/v3/domain.py index c345028f..8ba76c41 100644 --- a/openstackclient/identity/v3/domain.py +++ b/openstackclient/identity/v3/domain.py @@ -15,14 +15,19 @@ """Identity v3 Domain action implementations""" -import six +import logging import sys from keystoneauth1 import exceptions as ks_exc +from osc_lib.command import command +from osc_lib import utils +import six -from openstackclient.common import command -from openstackclient.common import utils from openstackclient.i18n import _ +from openstackclient.identity import common + + +LOG = logging.getLogger(__name__) class CreateDomain(command.ShowOne): @@ -75,7 +80,7 @@ class CreateDomain(command.ShowOne): if parsed_args.or_show: domain = utils.find_resource(identity_client.domains, parsed_args.name) - self.log.info(_('Returning existing domain %s'), domain.name) + LOG.info(_('Returning existing domain %s'), domain.name) else: raise e @@ -183,8 +188,12 @@ class ShowDomain(command.ShowOne): def take_action(self, parsed_args): identity_client = self.app.client_manager.identity + + domain_str = common._get_token_resource(identity_client, 'domain', + parsed_args.domain) + domain = utils.find_resource(identity_client.domains, - parsed_args.domain) + domain_str) domain._info.pop('links') return zip(*sorted(six.iteritems(domain._info))) diff --git a/openstackclient/identity/v3/ec2creds.py b/openstackclient/identity/v3/ec2creds.py index 859ec2a7..835fe96f 100644 --- a/openstackclient/identity/v3/ec2creds.py +++ b/openstackclient/identity/v3/ec2creds.py @@ -12,10 +12,10 @@ """Identity v3 EC2 Credentials action implementations""" +from osc_lib.command import command +from osc_lib import utils import six -from openstackclient.common import command -from openstackclient.common import utils from openstackclient.i18n import _ from openstackclient.identity import common diff --git a/openstackclient/identity/v3/endpoint.py b/openstackclient/identity/v3/endpoint.py index 39022d27..2f1cc9f3 100644 --- a/openstackclient/identity/v3/endpoint.py +++ b/openstackclient/identity/v3/endpoint.py @@ -15,11 +15,12 @@ """Identity v3 Endpoint action implementations""" -import six import sys -from openstackclient.common import command -from openstackclient.common import utils +from osc_lib.command import command +from osc_lib import utils +import six + from openstackclient.i18n import _ from openstackclient.identity import common @@ -39,7 +40,7 @@ class CreateEndpoint(command.ShowOne): parser.add_argument( 'service', metavar='<service>', - help=_('New endpoint service (name or ID)'), + help=_('Service to be associated with new endpoint (name or ID)'), ) parser.add_argument( 'interface', @@ -101,7 +102,7 @@ class DeleteEndpoint(command.Command): parser.add_argument( 'endpoint', metavar='<endpoint-id>', - help=_('Endpoint ID to delete'), + help=_('Endpoint to delete (ID only)'), ) return parser @@ -120,7 +121,7 @@ class ListEndpoint(command.Lister): parser.add_argument( '--service', metavar='<service>', - help=_('Filter by service'), + help=_('Filter by service (name or ID)'), ) parser.add_argument( '--interface', @@ -168,7 +169,7 @@ class SetEndpoint(command.Command): parser.add_argument( 'endpoint', metavar='<endpoint-id>', - help=_('Endpoint ID to modify'), + help=_('Endpoint to modify (ID only)'), ) parser.add_argument( '--region', diff --git a/openstackclient/identity/v3/federation_protocol.py b/openstackclient/identity/v3/federation_protocol.py index c0f4bc93..09480245 100644 --- a/openstackclient/identity/v3/federation_protocol.py +++ b/openstackclient/identity/v3/federation_protocol.py @@ -14,13 +14,18 @@ """Identity v3 Protocols actions implementations""" +import logging + +from osc_lib.command import command +from osc_lib import utils import six -from openstackclient.common import command -from openstackclient.common import utils from openstackclient.i18n import _ +LOG = logging.getLogger(__name__) + + class CreateProtocol(command.ShowOne): """Create new federation protocol""" @@ -145,7 +150,7 @@ class SetProtocol(command.Command): identity_client = self.app.client_manager.identity if not parsed_args.mapping: - self.app.log.error(_("No changes requested")) + LOG.error(_("No changes requested")) return protocol = identity_client.federation.protocols.update( diff --git a/openstackclient/identity/v3/group.py b/openstackclient/identity/v3/group.py index fdb94da6..8351fe64 100644 --- a/openstackclient/identity/v3/group.py +++ b/openstackclient/identity/v3/group.py @@ -15,17 +15,21 @@ """Group action implementations""" -import six +import logging import sys from keystoneauth1 import exceptions as ks_exc +from osc_lib.command import command +from osc_lib import utils +import six -from openstackclient.common import command -from openstackclient.common import utils from openstackclient.i18n import _ from openstackclient.identity import common +LOG = logging.getLogger(__name__) + + class AddUserToGroup(command.Command): """Add user to group""" @@ -161,7 +165,7 @@ class CreateGroup(command.ShowOne): group = utils.find_resource(identity_client.groups, parsed_args.name, domain_id=domain) - self.log.info(_('Returning existing group %s'), group.name) + LOG.info(_('Returning existing group %s'), group.name) else: raise e diff --git a/openstackclient/identity/v3/identity_provider.py b/openstackclient/identity/v3/identity_provider.py index 3749aa35..5c638f9b 100644 --- a/openstackclient/identity/v3/identity_provider.py +++ b/openstackclient/identity/v3/identity_provider.py @@ -13,13 +13,18 @@ """Identity v3 IdentityProvider action implementations""" +import logging + +from osc_lib.command import command +from osc_lib import utils import six -from openstackclient.common import command -from openstackclient.common import utils from openstackclient.i18n import _ +LOG = logging.getLogger(__name__) + + class CreateIdentityProvider(command.ShowOne): """Create new identity provider""" @@ -169,7 +174,7 @@ class SetIdentityProvider(command.Command): not parsed_args.remote_id and not parsed_args.remote_id_file and not parsed_args.description): - self.log.error(_('No changes requested')) + LOG.error(_('No changes requested')) return (None, None) # Always set remote_ids if either is passed in diff --git a/openstackclient/identity/v3/mapping.py b/openstackclient/identity/v3/mapping.py index c45796e4..74ead228 100644 --- a/openstackclient/identity/v3/mapping.py +++ b/openstackclient/identity/v3/mapping.py @@ -16,15 +16,19 @@ """Identity v3 federation mapping action implementations""" import json +import logging +from osc_lib.command import command +from osc_lib import exceptions +from osc_lib import utils import six -from openstackclient.common import command -from openstackclient.common import exceptions -from openstackclient.common import utils from openstackclient.i18n import _ +LOG = logging.getLogger(__name__) + + class _RulesReader(object): """Helper class capable of reading rules from files""" @@ -159,7 +163,7 @@ class SetMapping(command.Command, _RulesReader): identity_client = self.app.client_manager.identity if not parsed_args.rules: - self.app.log.error(_("No changes requested")) + LOG.error(_("No changes requested")) return rules = self._read_rules(parsed_args.rules) diff --git a/openstackclient/identity/v3/policy.py b/openstackclient/identity/v3/policy.py index 74a783b0..68fb2738 100644 --- a/openstackclient/identity/v3/policy.py +++ b/openstackclient/identity/v3/policy.py @@ -15,11 +15,12 @@ """Identity v3 Policy action implementations""" -import six import sys -from openstackclient.common import command -from openstackclient.common import utils +from osc_lib.command import command +from osc_lib import utils +import six + from openstackclient.i18n import _ diff --git a/openstackclient/identity/v3/project.py b/openstackclient/identity/v3/project.py index acf639f2..56c1d41a 100644 --- a/openstackclient/identity/v3/project.py +++ b/openstackclient/identity/v3/project.py @@ -15,17 +15,21 @@ """Project action implementations""" -import six +import logging from keystoneauth1 import exceptions as ks_exc +from osc_lib.cli import parseractions +from osc_lib.command import command +from osc_lib import utils +import six -from openstackclient.common import command -from openstackclient.common import parseractions -from openstackclient.common import utils from openstackclient.i18n import _ from openstackclient.identity import common +LOG = logging.getLogger(__name__) + + class CreateProject(command.ShowOne): """Create new project""" @@ -112,7 +116,7 @@ class CreateProject(command.ShowOne): project = utils.find_resource(identity_client.projects, parsed_args.name, domain_id=domain) - self.log.info(_('Returning existing project %s'), project.name) + LOG.info(_('Returning existing project %s'), project.name) else: raise e @@ -317,18 +321,21 @@ class ShowProject(command.ShowOne): def take_action(self, parsed_args): identity_client = self.app.client_manager.identity + project_str = common._get_token_resource(identity_client, 'project', + parsed_args.project) + if parsed_args.domain: domain = common.find_domain(identity_client, parsed_args.domain) project = utils.find_resource( identity_client.projects, - parsed_args.project, + project_str, domain_id=domain.id, parents_as_list=parsed_args.parents, subtree_as_list=parsed_args.children) else: project = utils.find_resource( identity_client.projects, - parsed_args.project, + project_str, parents_as_list=parsed_args.parents, subtree_as_list=parsed_args.children) diff --git a/openstackclient/identity/v3/region.py b/openstackclient/identity/v3/region.py index 053e4b31..b5e46a9a 100644 --- a/openstackclient/identity/v3/region.py +++ b/openstackclient/identity/v3/region.py @@ -13,10 +13,10 @@ """Identity v3 Region action implementations""" +from osc_lib.command import command +from osc_lib import utils import six -from openstackclient.common import command -from openstackclient.common import utils from openstackclient.i18n import _ diff --git a/openstackclient/identity/v3/role.py b/openstackclient/identity/v3/role.py index e7078f44..965ca3f5 100644 --- a/openstackclient/identity/v3/role.py +++ b/openstackclient/identity/v3/role.py @@ -15,17 +15,21 @@ """Identity v3 Role action implementations""" -import six +import logging import sys from keystoneauth1 import exceptions as ks_exc +from osc_lib.command import command +from osc_lib import utils +import six -from openstackclient.common import command -from openstackclient.common import utils from openstackclient.i18n import _ from openstackclient.identity import common +LOG = logging.getLogger(__name__) + + def _add_identity_and_resource_options_to_parser(parser): domain_or_project = parser.add_mutually_exclusive_group() domain_or_project.add_argument( @@ -165,7 +169,7 @@ class CreateRole(command.ShowOne): if parsed_args.or_show: role = utils.find_resource(identity_client.roles, parsed_args.name) - self.log.info(_('Returning existing role %s'), role.name) + LOG.info(_('Returning existing role %s'), role.name) else: raise e diff --git a/openstackclient/identity/v3/role_assignment.py b/openstackclient/identity/v3/role_assignment.py index 521075fe..39e2336d 100644 --- a/openstackclient/identity/v3/role_assignment.py +++ b/openstackclient/identity/v3/role_assignment.py @@ -13,8 +13,9 @@ """Identity v3 Assignment action implementations """ -from openstackclient.common import command -from openstackclient.common import utils +from osc_lib.command import command +from osc_lib import utils + from openstackclient.i18n import _ from openstackclient.identity import common diff --git a/openstackclient/identity/v3/service.py b/openstackclient/identity/v3/service.py index 35507a63..195b2701 100644 --- a/openstackclient/identity/v3/service.py +++ b/openstackclient/identity/v3/service.py @@ -15,11 +15,12 @@ """Identity v3 Service action implementations""" -import six import sys -from openstackclient.common import command -from openstackclient.common import utils +from osc_lib.command import command +from osc_lib import utils +import six + from openstackclient.i18n import _ from openstackclient.identity import common @@ -129,7 +130,7 @@ class SetService(command.Command): parser.add_argument( 'service', metavar='<service>', - help=_('Service to update (type, name or ID)'), + help=_('Service to modify (type, name or ID)'), ) parser.add_argument( '--type', diff --git a/openstackclient/identity/v3/service_provider.py b/openstackclient/identity/v3/service_provider.py index f1e9f35d..0beea813 100644 --- a/openstackclient/identity/v3/service_provider.py +++ b/openstackclient/identity/v3/service_provider.py @@ -13,11 +13,12 @@ """Service Provider action implementations""" -import six import sys -from openstackclient.common import command -from openstackclient.common import utils +from osc_lib.command import command +from osc_lib import utils +import six + from openstackclient.i18n import _ diff --git a/openstackclient/identity/v3/token.py b/openstackclient/identity/v3/token.py index 56a7497c..ecf09693 100644 --- a/openstackclient/identity/v3/token.py +++ b/openstackclient/identity/v3/token.py @@ -15,11 +15,11 @@ """Identity v3 Token action implementations""" +from osc_lib.command import command +from osc_lib import exceptions +from osc_lib import utils import six -from openstackclient.common import command -from openstackclient.common import exceptions -from openstackclient.common import utils from openstackclient.i18n import _ from openstackclient.identity import common @@ -174,13 +174,23 @@ class IssueToken(command.ShowOne): return parser def take_action(self, parsed_args): - if not self.app.client_manager.auth_ref: + auth_ref = self.app.client_manager.auth_ref + if not auth_ref: raise exceptions.AuthorizationFailure( _("Only an authorized user may issue a new token.")) - token = self.app.client_manager.auth_ref.service_catalog.get_token() - if 'tenant_id' in token: - token['project_id'] = token.pop('tenant_id') - return zip(*sorted(six.iteritems(token))) + + data = {} + if auth_ref.auth_token: + data['id'] = auth_ref.auth_token + if auth_ref.expires: + data['expires'] = auth_ref.expires + if auth_ref.project_id: + data['project_id'] = auth_ref.project_id + if auth_ref.user_id: + data['user_id'] = auth_ref.user_id + if auth_ref.domain_id: + data['domain_id'] = auth_ref.domain_id + return zip(*sorted(six.iteritems(data))) class RevokeToken(command.Command): diff --git a/openstackclient/identity/v3/trust.py b/openstackclient/identity/v3/trust.py index 336b703c..bbc86adb 100644 --- a/openstackclient/identity/v3/trust.py +++ b/openstackclient/identity/v3/trust.py @@ -14,10 +14,11 @@ """Identity v3 Trust action implementations""" import datetime + +from osc_lib.command import command +from osc_lib import utils import six -from openstackclient.common import command -from openstackclient.common import utils from openstackclient.i18n import _ from openstackclient.identity import common diff --git a/openstackclient/identity/v3/unscoped_saml.py b/openstackclient/identity/v3/unscoped_saml.py index 5cb8e486..f116174b 100644 --- a/openstackclient/identity/v3/unscoped_saml.py +++ b/openstackclient/identity/v3/unscoped_saml.py @@ -17,9 +17,10 @@ The first step of federated auth is to fetch an unscoped token. From there, the user can list domains and projects they are allowed to access, and request a scoped token.""" -from openstackclient.common import command -from openstackclient.common import exceptions -from openstackclient.common import utils +from osc_lib.command import command +from osc_lib import exceptions +from osc_lib import utils + from openstackclient.i18n import _ diff --git a/openstackclient/identity/v3/user.py b/openstackclient/identity/v3/user.py index a71b4b78..dd5af06a 100644 --- a/openstackclient/identity/v3/user.py +++ b/openstackclient/identity/v3/user.py @@ -16,17 +16,21 @@ """Identity v3 User action implementations""" import copy -import six +import logging import sys from keystoneauth1 import exceptions as ks_exc +from osc_lib.command import command +from osc_lib import utils +import six -from openstackclient.common import command -from openstackclient.common import utils from openstackclient.i18n import _ from openstackclient.identity import common +LOG = logging.getLogger(__name__) + + class CreateUser(command.ShowOne): """Create new user""" @@ -122,7 +126,7 @@ class CreateUser(command.ShowOne): user = utils.find_resource(identity_client.users, parsed_args.name, domain_id=domain_id) - self.log.info(_('Returning existing user %s'), user.name) + LOG.info(_('Returning existing user %s'), user.name) else: raise e @@ -273,7 +277,7 @@ class SetUser(command.Command): parser.add_argument( 'user', metavar='<user>', - help=_('User to change (name or ID)'), + help=_('User to modify (name or ID)'), ) parser.add_argument( '--name', @@ -440,14 +444,16 @@ class ShowUser(command.ShowOne): def take_action(self, parsed_args): identity_client = self.app.client_manager.identity + user_str = common._get_token_resource(identity_client, 'user', + parsed_args.user) if parsed_args.domain: domain = common.find_domain(identity_client, parsed_args.domain) user = utils.find_resource(identity_client.users, - parsed_args.user, + user_str, domain_id=domain.id) else: user = utils.find_resource(identity_client.users, - parsed_args.user) + user_str) user._info.pop('links') return zip(*sorted(six.iteritems(user._info))) diff --git a/openstackclient/image/client.py b/openstackclient/image/client.py index 9c45a63f..0ef694f4 100644 --- a/openstackclient/image/client.py +++ b/openstackclient/image/client.py @@ -15,7 +15,8 @@ import logging -from openstackclient.common import utils +from osc_lib import utils + from openstackclient.i18n import _ diff --git a/openstackclient/image/v1/image.py b/openstackclient/image/v1/image.py index e2109fca..e7b60e5a 100644 --- a/openstackclient/image/v1/image.py +++ b/openstackclient/image/v1/image.py @@ -17,8 +17,8 @@ import argparse import io +import logging import os -import six import sys if os.name == "nt": @@ -27,10 +27,12 @@ else: msvcrt = None from glanceclient.common import utils as gc_utils +from osc_lib.cli import parseractions +from osc_lib.command import command +from osc_lib import utils +import six + from openstackclient.api import utils as api_utils -from openstackclient.common import command -from openstackclient.common import parseractions -from openstackclient.common import utils from openstackclient.i18n import _ @@ -38,6 +40,9 @@ DEFAULT_CONTAINER_FORMAT = 'bare' DEFAULT_DISK_FORMAT = 'raw' +LOG = logging.getLogger(__name__) + + def _format_visibility(data): """Return a formatted visibility string @@ -188,10 +193,8 @@ class CreateImage(command.ShowOne): image_client = self.app.client_manager.image if getattr(parsed_args, 'owner', None) is not None: - self.log.warning(_( - 'The --owner option is deprecated, ' - 'please use --project instead.' - )) + LOG.warning(_('The --owner option is deprecated, ' + 'please use --project instead.')) # Build an attribute dict from the parsed args, only include # attributes that were actually set on the command line @@ -607,10 +610,8 @@ class SetImage(command.Command): image_client = self.app.client_manager.image if getattr(parsed_args, 'owner', None) is not None: - self.log.warning(_( - 'The --owner option is deprecated, ' - 'please use --project instead.' - )) + LOG.warning(_('The --owner option is deprecated, ' + 'please use --project instead.')) kwargs = {} copy_attrs = ('name', 'owner', 'min_disk', 'min_ram', 'properties', @@ -683,18 +684,13 @@ class SetImage(command.Command): # will do a chunked transfer kwargs["data"] = sys.stdin else: - self.log.warning(_('Use --stdin to enable read ' - 'image data from standard ' - 'input')) + LOG.warning(_('Use --stdin to enable read image ' + 'data from standard input')) if image.properties and parsed_args.properties: image.properties.update(kwargs['properties']) kwargs['properties'] = image.properties - if not kwargs: - self.log.warning('no arguments specified') - return - image = image_client.images.update(image.id, **kwargs) finally: # Clean up open files - make sure data isn't a string diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index fa1de424..309b1b6b 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -16,15 +16,16 @@ """Image V2 Action Implementations""" import argparse -import six +import logging from glanceclient.common import utils as gc_utils +from osc_lib.cli import parseractions +from osc_lib.command import command +from osc_lib import exceptions +from osc_lib import utils +import six from openstackclient.api import utils as api_utils -from openstackclient.common import command -from openstackclient.common import exceptions -from openstackclient.common import parseractions -from openstackclient.common import utils from openstackclient.i18n import _ from openstackclient.identity import common @@ -33,6 +34,9 @@ DEFAULT_CONTAINER_FORMAT = 'bare' DEFAULT_DISK_FORMAT = 'raw' +LOG = logging.getLogger(__name__) + + def _format_image(image): """Format an image to make it more consistent with OSC operations. """ @@ -280,10 +284,8 @@ class CreateImage(command.ShowOne): project_arg = parsed_args.project if parsed_args.owner: project_arg = parsed_args.owner - self.log.warning(_( - 'The --owner option is deprecated, ' - 'please use --project instead.' - )) + LOG.warning(_('The --owner option is deprecated, ' + 'please use --project instead.')) if project_arg: kwargs['owner'] = common.find_project( identity_client, @@ -301,7 +303,7 @@ class CreateImage(command.ShowOne): "the same time")) if fp is None and parsed_args.file: - self.log.warning(_("Failed to get an image file.")) + LOG.warning(_("Failed to get an image file.")) return {}, {} if parsed_args.owner: @@ -372,13 +374,27 @@ class DeleteImage(command.Command): return parser def take_action(self, parsed_args): + + del_result = 0 image_client = self.app.client_manager.image for image in parsed_args.images: - image_obj = utils.find_resource( - image_client.images, - image, - ) - image_client.images.delete(image_obj.id) + try: + image_obj = utils.find_resource( + image_client.images, + image, + ) + image_client.images.delete(image_obj.id) + except Exception as e: + del_result += 1 + LOG.error(_("Failed to delete image with name or " + "ID '%(image)s': %(e)s"), + {'image': image, 'e': e}) + + total = len(parsed_args.images) + if (del_result > 0): + msg = (_("Failed to delete %(dresult)s of %(total)s images.") + % {'dresult': del_result, 'total': total}) + raise exceptions.CommandError(msg) class ListImage(command.Lister): @@ -792,10 +808,8 @@ class SetImage(command.Command): project_arg = parsed_args.project if parsed_args.owner: project_arg = parsed_args.owner - self.log.warning(_( - 'The --owner option is deprecated, ' - 'please use --project instead.' - )) + LOG.warning(_('The --owner option is deprecated, ' + 'please use --project instead.')) if project_arg: kwargs['owner'] = common.find_project( identity_client, @@ -803,11 +817,6 @@ class SetImage(command.Command): parsed_args.project_domain, ).id - # Checks if anything that requires getting the image - if not (kwargs or parsed_args.deactivate or parsed_args.activate): - msg = _("No arguments specified") - raise exceptions.CommandError(msg) - image = utils.find_resource( image_client.images, parsed_args.image) @@ -819,10 +828,6 @@ class SetImage(command.Command): image_client.images.reactivate(image.id) activation_status = "activated" - # Check if need to do the actual update - if not kwargs: - return {}, {} - if parsed_args.tags: # Tags should be extended, but duplicates removed kwargs['tags'] = list(set(image.tags).union(set(parsed_args.tags))) @@ -831,7 +836,8 @@ class SetImage(command.Command): image = image_client.images.update(image.id, **kwargs) except Exception as e: if activation_status is not None: - print("Image %s was %s." % (image.id, activation_status)) + LOG.info(_("Image %(id)s was %(status)s."), + {'id': image.id, 'status': activation_status}) raise e @@ -895,10 +901,6 @@ class UnsetImage(command.Command): parsed_args.image, ) - if not (parsed_args.tags or parsed_args.properties): - msg = _("No arguments specified") - raise exceptions.CommandError(msg) - kwargs = {} tagret = 0 propret = 0 @@ -907,8 +909,8 @@ class UnsetImage(command.Command): try: image_client.image_tags.delete(image.id, k) except Exception: - self.log.error(_("tag unset failed," - " '%s' is a nonexistent tag ") % k) + LOG.error(_("tag unset failed, '%s' is a " + "nonexistent tag "), k) tagret += 1 if parsed_args.properties: @@ -916,8 +918,8 @@ class UnsetImage(command.Command): try: assert(k in image.keys()) except AssertionError: - self.log.error(_("property unset failed," - " '%s' is a nonexistent property ") % k) + LOG.error(_("property unset failed, '%s' is a " + "nonexistent property "), k) propret += 1 image_client.images.update( image.id, diff --git a/openstackclient/network/client.py b/openstackclient/network/client.py index d711f4fc..d12987dd 100644 --- a/openstackclient/network/client.py +++ b/openstackclient/network/client.py @@ -15,8 +15,8 @@ import logging from openstack import connection from openstack import profile +from osc_lib import utils -from openstackclient.common import utils from openstackclient.i18n import _ @@ -36,6 +36,7 @@ def make_client(instance): prof = profile.Profile() prof.set_region(API_NAME, instance._region_name) prof.set_version(API_NAME, instance._api_version[API_NAME]) + prof.set_interface(API_NAME, instance._interface) conn = connection.Connection(authenticator=instance.session.auth, verify=instance.session.verify, cert=instance.session.cert, diff --git a/openstackclient/network/common.py b/openstackclient/network/common.py index 4028ac0d..f62840fc 100644 --- a/openstackclient/network/common.py +++ b/openstackclient/network/common.py @@ -12,13 +12,18 @@ # import abc +import logging + +from osc_lib.command import command +from osc_lib import exceptions import six -from openstackclient.common import command -from openstackclient.common import exceptions from openstackclient.i18n import _ +LOG = logging.getLogger(__name__) + + @six.add_metaclass(abc.ABCMeta) class NetworkAndComputeCommand(command.Command): """Network and Compute Command @@ -38,10 +43,10 @@ class NetworkAndComputeCommand(command.Command): parsed_args) def get_parser(self, prog_name): - self.log.debug('get_parser(%s)', prog_name) + LOG.debug('get_parser(%s)', prog_name) parser = super(NetworkAndComputeCommand, self).get_parser(prog_name) parser = self.update_parser_common(parser) - self.log.debug('common parser: %s', parser) + LOG.debug('common parser: %s', parser) if self.app.client_manager.is_network_endpoint_enabled(): return self.update_parser_network(parser) else: @@ -101,7 +106,7 @@ class NetworkAndComputeDelete(NetworkAndComputeCommand): "name_or_id": r, "e": e, } - self.app.log.error(msg) + LOG.error(msg) ret += 1 if ret: @@ -133,10 +138,10 @@ class NetworkAndComputeLister(command.Lister): parsed_args) def get_parser(self, prog_name): - self.log.debug('get_parser(%s)', prog_name) + LOG.debug('get_parser(%s)', prog_name) parser = super(NetworkAndComputeLister, self).get_parser(prog_name) parser = self.update_parser_common(parser) - self.log.debug('common parser: %s', parser) + LOG.debug('common parser: %s', parser) if self.app.client_manager.is_network_endpoint_enabled(): return self.update_parser_network(parser) else: @@ -184,10 +189,10 @@ class NetworkAndComputeShowOne(command.ShowOne): parsed_args) def get_parser(self, prog_name): - self.log.debug('get_parser(%s)', prog_name) + LOG.debug('get_parser(%s)', prog_name) parser = super(NetworkAndComputeShowOne, self).get_parser(prog_name) parser = self.update_parser_common(parser) - self.log.debug('common parser: %s', parser) + LOG.debug('common parser: %s', parser) if self.app.client_manager.is_network_endpoint_enabled(): return self.update_parser_network(parser) else: diff --git a/openstackclient/network/v2/address_scope.py b/openstackclient/network/v2/address_scope.py index dbc6865f..6cd13f8c 100644 --- a/openstackclient/network/v2/address_scope.py +++ b/openstackclient/network/v2/address_scope.py @@ -13,13 +13,19 @@ """Address scope action implementations""" -from openstackclient.common import command -from openstackclient.common import exceptions -from openstackclient.common import utils +import logging + +from osc_lib.command import command +from osc_lib import exceptions +from osc_lib import utils + from openstackclient.i18n import _ from openstackclient.identity import common as identity_common +LOG = logging.getLogger(__name__) + + def _get_columns(item): columns = list(item.keys()) if 'tenant_id' in columns: @@ -94,7 +100,7 @@ class CreateAddressScope(command.ShowOne): columns = _get_columns(obj) data = utils.get_item_properties(obj, columns, formatters={}) - return columns, data + return (columns, data) class DeleteAddressScope(command.Command): @@ -121,9 +127,9 @@ class DeleteAddressScope(command.Command): client.delete_address_scope(obj) except Exception as e: result += 1 - self.app.log.error(_("Failed to delete address scope with " - "name or ID '%(scope)s': %(e)s") - % {'scope': scope, 'e': e}) + LOG.error(_("Failed to delete address scope with " + "name or ID '%(scope)s': %(e)s"), + {'scope': scope, 'e': e}) if result > 0: total = len(parsed_args.address_scope) @@ -200,9 +206,6 @@ class SetAddressScope(command.Command): attrs['shared'] = True if parsed_args.no_share: attrs['shared'] = False - if attrs == {}: - msg = _("Nothing specified to be set.") - raise exceptions.CommandError(msg) client.update_address_scope(obj, **attrs) @@ -227,4 +230,4 @@ class ShowAddressScope(command.ShowOne): columns = _get_columns(obj) data = utils.get_item_properties(obj, columns, formatters={}) - return columns, data + return (columns, data) diff --git a/openstackclient/network/v2/floating_ip.py b/openstackclient/network/v2/floating_ip.py index 21f86599..8fbf049e 100644 --- a/openstackclient/network/v2/floating_ip.py +++ b/openstackclient/network/v2/floating_ip.py @@ -13,7 +13,8 @@ """IP Floating action implementations""" -from openstackclient.common import utils +from osc_lib import utils + from openstackclient.i18n import _ from openstackclient.network import common @@ -109,26 +110,28 @@ class CreateFloatingIP(common.NetworkAndComputeShowOne): return (columns, data) -class DeleteFloatingIP(common.NetworkAndComputeCommand): - """Delete floating IP""" +class DeleteFloatingIP(common.NetworkAndComputeDelete): + """Delete floating IP(s)""" + + # Used by base class to find resources in parsed_args. + resource = 'floating_ip' + r = None def update_parser_common(self, parser): parser.add_argument( 'floating_ip', metavar="<floating-ip>", - help=_("Floating IP to delete (IP address or ID)") + nargs="+", + help=_("Floating IP(s) to delete (IP address or ID)") ) return parser def take_action_network(self, client, parsed_args): - obj = client.find_ip(parsed_args.floating_ip) + obj = client.find_ip(self.r, ignore_missing=False) client.delete_ip(obj) def take_action_compute(self, client, parsed_args): - obj = utils.find_resource( - client.floating_ips, - parsed_args.floating_ip, - ) + obj = utils.find_resource(client.floating_ips, self.r) client.floating_ips.delete(obj.id) diff --git a/openstackclient/network/v2/ip_availability.py b/openstackclient/network/v2/ip_availability.py index cc240338..1d7b2aed 100644 --- a/openstackclient/network/v2/ip_availability.py +++ b/openstackclient/network/v2/ip_availability.py @@ -13,8 +13,9 @@ """IP Availability Info implementations""" -from openstackclient.common import command -from openstackclient.common import utils +from osc_lib.command import command +from osc_lib import utils + from openstackclient.i18n import _ from openstackclient.identity import common as identity_common @@ -40,15 +41,17 @@ class ListIPAvailability(command.Lister): parser.add_argument( '--ip-version', type=int, + default=4, choices=[4, 6], metavar='<ip-version>', dest='ip_version', - help=_("List IP availability of given IP version networks"), + help=_("List IP availability of given IP version " + "networks (default is 4)"), ) parser.add_argument( '--project', metavar='<project>', - help=_("List IP availability of given project"), + help=_("List IP availability of given project (name or ID)"), ) identity_common.add_project_domain_option_to_parser(parser) return parser @@ -106,4 +109,4 @@ class ShowIPAvailability(command.ShowOne): ignore_missing=False) columns = _get_columns(obj) data = utils.get_item_properties(obj, columns, formatters=_formatters) - return columns, data + return (columns, data) diff --git a/openstackclient/network/v2/network.py b/openstackclient/network/v2/network.py index bf01e2ec..31dfc798 100644 --- a/openstackclient/network/v2/network.py +++ b/openstackclient/network/v2/network.py @@ -13,9 +13,9 @@ """Network action implementations""" -from openstackclient.common import command -from openstackclient.common import exceptions -from openstackclient.common import utils +from osc_lib.command import command +from osc_lib import utils + from openstackclient.i18n import _ from openstackclient.identity import common as identity_common from openstackclient.network import common @@ -104,11 +104,11 @@ def _add_additional_network_options(parser): parser.add_argument( '--provider-network-type', metavar='<provider-network-type>', - choices=['flat', 'gre', 'local', + choices=['flat', 'geneve', 'gre', 'local', 'vlan', 'vxlan'], help=_("The physical mechanism by which the virtual network " "is implemented. The supported options are: " - "flat, gre, local, vlan, vxlan")) + "flat, geneve, gre, local, vlan, vxlan.")) parser.add_argument( '--provider-physical-network', metavar='<provider-physical-network>', @@ -119,8 +119,8 @@ def _add_additional_network_options(parser): '--provider-segment', metavar='<provider-segment>', dest='segmentation_id', - help=_("VLAN ID for VLAN networks or Tunnel ID for GRE/VXLAN " - "networks")) + help=_("VLAN ID for VLAN networks or Tunnel ID for " + "GENEVE/GRE/VXLAN networks")) vlan_transparent_grp = parser.add_mutually_exclusive_group() vlan_transparent_grp.add_argument( @@ -434,10 +434,6 @@ class SetNetwork(command.Command): obj = client.find_network(parsed_args.network, ignore_missing=False) attrs = _get_attrs(self.app.client_manager, parsed_args) - if attrs == {}: - msg = _("Nothing specified to be set") - raise exceptions.CommandError(msg) - client.update_network(obj, **attrs) diff --git a/openstackclient/network/v2/network_segment.py b/openstackclient/network/v2/network_segment.py index 818ffc02..bedf15f7 100644 --- a/openstackclient/network/v2/network_segment.py +++ b/openstackclient/network/v2/network_segment.py @@ -15,8 +15,9 @@ # TODO(rtheis): Add description and name properties when support is available. -from openstackclient.common import command -from openstackclient.common import utils +from osc_lib.command import command +from osc_lib import utils + from openstackclient.i18n import _ diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index ca02281f..5d1431b5 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -14,12 +14,14 @@ """Port action implementations""" import argparse +import json import logging -from openstackclient.common import command -from openstackclient.common import exceptions -from openstackclient.common import parseractions -from openstackclient.common import utils +from osc_lib.cli import parseractions +from osc_lib.command import command +from osc_lib import exceptions +from osc_lib import utils + from openstackclient.i18n import _ from openstackclient.identity import common as identity_common @@ -62,6 +64,32 @@ def _get_columns(item): return tuple(sorted(columns)) +class JSONKeyValueAction(argparse.Action): + """A custom action to parse arguments as JSON or key=value pairs + + Ensures that ``dest`` is a dict + """ + + def __call__(self, parser, namespace, values, option_string=None): + + # Make sure we have an empty dict rather than None + if getattr(namespace, self.dest, None) is None: + setattr(namespace, self.dest, {}) + + # Try to load JSON first before falling back to <key>=<value>. + current_dest = getattr(namespace, self.dest) + try: + current_dest.update(json.loads(values)) + except ValueError as e: + if '=' in values: + current_dest.update([values.split('=', 1)]) + else: + msg = _("Expected '<key>=<value>' or JSON data for option " + "%(option)s, but encountered JSON parsing error: " + "%(error)s") % {"option": option_string, "error": e} + raise argparse.ArgumentTypeError(msg) + + def _get_attrs(client_manager, parsed_args): attrs = {} @@ -218,9 +246,9 @@ class CreatePort(command.ShowOne): parser.add_argument( '--binding-profile', metavar='<binding-profile>', - action=parseractions.KeyValueAction, - help=_("Custom data to be passed as binding:profile: " - "<key>=<value> " + action=JSONKeyValueAction, + help=_("Custom data to be passed as binding:profile. Data may " + "be passed as <key>=<value> or JSON. " "(repeat option to set multiple binding:profile data)") ) admin_group = parser.add_mutually_exclusive_group() @@ -266,7 +294,7 @@ class CreatePort(command.ShowOne): columns = _get_columns(obj) data = utils.get_item_properties(obj, columns, formatters=_formatters) - return columns, data + return (columns, data) class DeletePort(command.Command): @@ -284,10 +312,23 @@ class DeletePort(command.Command): def take_action(self, parsed_args): client = self.app.client_manager.network + result = 0 for port in parsed_args.port: - res = client.find_port(port) - client.delete_port(res) + try: + obj = client.find_port(port, ignore_missing=False) + client.delete_port(obj) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete port with " + "name or ID '%(port)s': %(e)s"), + {'port': port, 'e': e}) + + if result > 0: + total = len(parsed_args.port) + msg = (_("%(result)s of %(total)s ports failed " + "to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) class ListPort(command.Lister): @@ -376,9 +417,9 @@ class SetPort(command.Command): binding_profile.add_argument( '--binding-profile', metavar='<binding-profile>', - action=parseractions.KeyValueAction, - help=_("Custom data to be passed as binding:profile: " - "<key>=<value> " + action=JSONKeyValueAction, + help=_("Custom data to be passed as binding:profile. Data may " + "be passed as <key>=<value> or JSON. " "(repeat option to set multiple binding:profile data)") ) binding_profile.add_argument( @@ -413,9 +454,6 @@ class SetPort(command.Command): elif parsed_args.no_fixed_ip: attrs['fixed_ips'] = [] - if attrs == {}: - msg = _("Nothing specified to be set") - raise exceptions.CommandError(msg) client.update_port(obj, **attrs) @@ -436,4 +474,4 @@ class ShowPort(command.ShowOne): obj = client.find_port(parsed_args.port, ignore_missing=False) columns = _get_columns(obj) data = utils.get_item_properties(obj, columns, formatters=_formatters) - return columns, data + return (columns, data) diff --git a/openstackclient/network/v2/router.py b/openstackclient/network/v2/router.py index a2f0df1d..2f41838f 100644 --- a/openstackclient/network/v2/router.py +++ b/openstackclient/network/v2/router.py @@ -17,10 +17,11 @@ import argparse import json import logging -from openstackclient.common import command -from openstackclient.common import exceptions -from openstackclient.common import parseractions -from openstackclient.common import utils +from osc_lib.cli import parseractions +from osc_lib.command import command +from osc_lib import exceptions +from osc_lib import utils + from openstackclient.i18n import _ from openstackclient.identity import common as identity_common @@ -204,7 +205,7 @@ class CreateRouter(command.ShowOne): columns = _get_columns(obj) data = utils.get_item_properties(obj, columns, formatters=_formatters) - return columns, data + return (columns, data) class DeleteRouter(command.Command): @@ -222,9 +223,23 @@ class DeleteRouter(command.Command): def take_action(self, parsed_args): client = self.app.client_manager.network + result = 0 + for router in parsed_args.router: - obj = client.find_router(router) - client.delete_router(obj) + try: + obj = client.find_router(router, ignore_missing=False) + client.delete_router(obj) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete router with " + "name or ID '%(router)s': %(e)s") + % {'router': router, 'e': e}) + + if result > 0: + total = len(parsed_args.router) + msg = (_("%(result)s of %(total)s routers failed " + "to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) class ListRouter(command.Lister): @@ -294,7 +309,7 @@ class RemovePortFromRouter(command.Command): parser.add_argument( 'port', metavar='<port>', - help=_("Port to be removed (name or ID)") + help=_("Port to be removed and deleted (name or ID)") ) return parser @@ -426,10 +441,6 @@ class SetRouter(command.Command): route['nexthop'] = route.pop('gateway') attrs['routes'] = obj.routes + parsed_args.routes - if attrs == {}: - msg = _("Nothing specified to be set") - raise exceptions.CommandError(msg) - client.update_router(obj, **attrs) @@ -450,4 +461,4 @@ class ShowRouter(command.ShowOne): obj = client.find_router(parsed_args.router, ignore_missing=False) columns = _get_columns(obj) data = utils.get_item_properties(obj, columns, formatters=_formatters) - return columns, data + return (columns, data) diff --git a/openstackclient/network/v2/security_group.py b/openstackclient/network/v2/security_group.py index 1ef2754e..f832f721 100644 --- a/openstackclient/network/v2/security_group.py +++ b/openstackclient/network/v2/security_group.py @@ -14,9 +14,10 @@ """Security Group action implementations""" import argparse + +from osc_lib import utils import six -from openstackclient.common import utils from openstackclient.i18n import _ from openstackclient.identity import common as identity_common from openstackclient.network import common @@ -163,26 +164,28 @@ class CreateSecurityGroup(common.NetworkAndComputeShowOne): return (display_columns, data) -class DeleteSecurityGroup(common.NetworkAndComputeCommand): - """Delete a security group""" +class DeleteSecurityGroup(common.NetworkAndComputeDelete): + """Delete security group(s)""" + + # Used by base class to find resources in parsed_args. + resource = 'group' + r = None def update_parser_common(self, parser): parser.add_argument( 'group', metavar='<group>', - help=_("Security group to delete (name or ID)") + nargs="+", + help=_("Security group(s) to delete (name or ID)"), ) return parser def take_action_network(self, client, parsed_args): - obj = client.find_security_group(parsed_args.group) + obj = client.find_security_group(self.r, ignore_missing=False) client.delete_security_group(obj) def take_action_compute(self, client, parsed_args): - data = utils.find_resource( - client.security_groups, - parsed_args.group, - ) + data = utils.find_resource(client.security_groups, self.r) client.security_groups.delete(data.id) diff --git a/openstackclient/network/v2/security_group_rule.py b/openstackclient/network/v2/security_group_rule.py index 5abe9b9d..e3be44ec 100644 --- a/openstackclient/network/v2/security_group_rule.py +++ b/openstackclient/network/v2/security_group_rule.py @@ -14,16 +14,17 @@ """Security Group Rule action implementations""" import argparse -import six try: from novaclient.v2 import security_group_rules as compute_secgroup_rules except ImportError: from novaclient.v1_1 import security_group_rules as compute_secgroup_rules -from openstackclient.common import exceptions -from openstackclient.common import parseractions -from openstackclient.common import utils +from osc_lib.cli import parseractions +from osc_lib import exceptions +from osc_lib import utils +import six + from openstackclient.i18n import _ from openstackclient.identity import common as identity_common from openstackclient.network import common @@ -332,23 +333,29 @@ class CreateSecurityGroupRule(common.NetworkAndComputeShowOne): return _format_security_group_rule_show(obj._info) -class DeleteSecurityGroupRule(common.NetworkAndComputeCommand): - """Delete a security group rule""" +class DeleteSecurityGroupRule(common.NetworkAndComputeDelete): + """Delete security group rule(s)""" + + # Used by base class to find resources in parsed_args. + resource = 'rule' + r = None def update_parser_common(self, parser): parser.add_argument( 'rule', metavar='<rule>', - help=_("Security group rule to delete (ID only)") + nargs="+", + help=_("Security group rule(s) to delete (ID only)") ) return parser def take_action_network(self, client, parsed_args): - obj = client.find_security_group_rule(parsed_args.rule) + obj = client.find_security_group_rule( + self.r, ignore_missing=False) client.delete_security_group_rule(obj) def take_action_compute(self, client, parsed_args): - client.security_group_rules.delete(parsed_args.rule) + client.security_group_rules.delete(self.r) class ListSecurityGroupRule(common.NetworkAndComputeLister): @@ -519,8 +526,8 @@ class ShowSecurityGroupRule(common.NetworkAndComputeShowOne): break if obj is None: - msg = _("Could not find security group rule with ID ") + \ - parsed_args.rule + msg = _("Could not find security group rule " + "with ID '%s'") % parsed_args.rule raise exceptions.CommandError(msg) # NOTE(rtheis): Format security group rule diff --git a/openstackclient/network/v2/subnet.py b/openstackclient/network/v2/subnet.py index e7e1be99..b076d82e 100644 --- a/openstackclient/network/v2/subnet.py +++ b/openstackclient/network/v2/subnet.py @@ -12,16 +12,22 @@ # """Subnet action implementations""" + import copy +import logging + +from osc_lib.cli import parseractions +from osc_lib.command import command +from osc_lib import exceptions +from osc_lib import utils -from openstackclient.common import command -from openstackclient.common import exceptions -from openstackclient.common import parseractions -from openstackclient.common import utils from openstackclient.i18n import _ from openstackclient.identity import common as identity_common +LOG = logging.getLogger(__name__) + + def _format_allocation_pools(data): pool_formatted = ['%s-%s' % (pool.get('start', ''), pool.get('end', '')) for pool in data] @@ -136,6 +142,9 @@ def _get_attrs(client_manager, parsed_args, is_create=True): attrs['ipv6_ra_mode'] = parsed_args.ipv6_ra_mode if parsed_args.ipv6_address_mode is not None: attrs['ipv6_address_mode'] = parsed_args.ipv6_address_mode + if 'network_segment' in parsed_args: + attrs['segment_id'] = client.find_segment( + parsed_args.network_segment, ignore_missing=False).id if 'gateway' in parsed_args and parsed_args.gateway is not None: gateway = parsed_args.gateway.lower() @@ -226,7 +235,7 @@ class CreateSubnet(command.ShowOne): "'auto': Gateway address should automatically be chosen " "from within the subnet itself, 'none': This subnet will " "not use a gateway, e.g.: --gateway 192.168.9.1, " - "--gateway auto, --gateway none (default is 'auto')") + "--gateway auto, --gateway none (default is 'auto').") ) parser.add_argument( '--ip-version', @@ -235,7 +244,7 @@ class CreateSubnet(command.ShowOne): choices=[4, 6], help=_("IP version (default is 4). Note that when subnet pool is " "specified, IP version is determined from the subnet pool " - "and this option is ignored") + "and this option is ignored.") ) parser.add_argument( '--ipv6-ra-mode', @@ -249,6 +258,13 @@ class CreateSubnet(command.ShowOne): help=_("IPv6 address mode, " "valid modes: [dhcpv6-stateful, dhcpv6-stateless, slaac]") ) + if self.app.options.os_beta_command: + parser.add_argument( + '--network-segment', + metavar='<network-segment>', + help=_("Network segment to associate with this subnet " + "(ID only)") + ) parser.add_argument( '--network', required=True, @@ -268,21 +284,37 @@ class CreateSubnet(command.ShowOne): class DeleteSubnet(command.Command): - """Delete subnet""" + """Delete subnet(s)""" def get_parser(self, prog_name): parser = super(DeleteSubnet, self).get_parser(prog_name) parser.add_argument( 'subnet', metavar="<subnet>", - help=_("Subnet to delete (name or ID)") + nargs='+', + help=_("Subnet(s) to delete (name or ID)") ) return parser def take_action(self, parsed_args): client = self.app.client_manager.network - client.delete_subnet( - client.find_subnet(parsed_args.subnet)) + result = 0 + + for subnet in parsed_args.subnet: + try: + obj = client.find_subnet(subnet, ignore_missing=False) + client.delete_subnet(obj) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete subnet with " + "name or ID '%(subnet)s': %(e)s") + % {'subnet': subnet, 'e': e}) + + if result > 0: + total = len(parsed_args.subnet) + msg = (_("%(result)s of %(total)s subnets failed " + "to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) class ListSubnet(command.Lister): @@ -302,7 +334,7 @@ class ListSubnet(command.Lister): choices=[4, 6], metavar='<ip-version>', dest='ip_version', - help=_("List only subnets of given IP version in output" + help=_("List only subnets of given IP version in output." "Allowed values for IP version are 4 and 6."), ) return parser @@ -363,7 +395,7 @@ class SetSubnet(command.Command): help=_("Specify a gateway for the subnet. The options are: " "<ip-address>: Specific IP address to use as the gateway, " "'none': This subnet will not use a gateway, " - "e.g.: --gateway 192.168.9.1, --gateway none") + "e.g.: --gateway 192.168.9.1, --gateway none.") ) _get_common_parse_arguments(parser) return parser @@ -373,9 +405,6 @@ class SetSubnet(command.Command): obj = client.find_subnet(parsed_args.subnet, ignore_missing=False) attrs = _get_attrs(self.app.client_manager, parsed_args, is_create=False) - if not attrs: - msg = "Nothing specified to be set" - raise exceptions.CommandError(msg) if 'dns_nameservers' in attrs: attrs['dns_nameservers'] += obj.dns_nameservers if 'host_routes' in attrs: diff --git a/openstackclient/network/v2/subnet_pool.py b/openstackclient/network/v2/subnet_pool.py index a1a94426..55dfed83 100644 --- a/openstackclient/network/v2/subnet_pool.py +++ b/openstackclient/network/v2/subnet_pool.py @@ -13,14 +13,20 @@ """Subnet pool action implementations""" -from openstackclient.common import command -from openstackclient.common import exceptions -from openstackclient.common import parseractions -from openstackclient.common import utils +import logging + +from osc_lib.cli import parseractions +from osc_lib.command import command +from osc_lib import exceptions +from osc_lib import utils + from openstackclient.i18n import _ from openstackclient.identity import common as identity_common +LOG = logging.getLogger(__name__) + + def _get_columns(item): columns = list(item.keys()) if 'tenant_id' in columns: @@ -176,21 +182,37 @@ class CreateSubnetPool(command.ShowOne): class DeleteSubnetPool(command.Command): - """Delete subnet pool""" + """Delete subnet pool(s)""" def get_parser(self, prog_name): parser = super(DeleteSubnetPool, self).get_parser(prog_name) parser.add_argument( 'subnet_pool', metavar='<subnet-pool>', - help=_("Subnet pool to delete (name or ID)") + nargs='+', + help=_("Subnet pool(s) to delete (name or ID)") ) return parser def take_action(self, parsed_args): client = self.app.client_manager.network - obj = client.find_subnet_pool(parsed_args.subnet_pool) - client.delete_subnet_pool(obj) + result = 0 + + for pool in parsed_args.subnet_pool: + try: + obj = client.find_subnet_pool(pool, ignore_missing=False) + client.delete_subnet_pool(obj) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete subnet pool with " + "name or ID '%(pool)s': %(e)s") + % {'pool': pool, 'e': e}) + + if result > 0: + total = len(parsed_args.subnet_pool) + msg = (_("%(result)s of %(total)s subnet pools failed " + "to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) class ListSubnetPool(command.Lister): @@ -286,9 +308,6 @@ class SetSubnetPool(command.Command): ignore_missing=False) attrs = _get_attrs(self.app.client_manager, parsed_args) - if attrs == {}: - msg = _("Nothing specified to be set") - raise exceptions.CommandError(msg) # Existing prefixes must be a subset of the new prefixes. if 'prefixes' in attrs: diff --git a/openstackclient/object/client.py b/openstackclient/object/client.py index 3af6f8a0..cb3ddc28 100644 --- a/openstackclient/object/client.py +++ b/openstackclient/object/client.py @@ -15,8 +15,9 @@ """Object client""" +from osc_lib import utils + from openstackclient.api import object_store_v1 -from openstackclient.common import utils DEFAULT_API_VERSION = '1' API_VERSION_OPTION = 'os_object_api_version' diff --git a/openstackclient/object/v1/account.py b/openstackclient/object/v1/account.py index 543ce4f3..801fe450 100644 --- a/openstackclient/object/v1/account.py +++ b/openstackclient/object/v1/account.py @@ -13,12 +13,11 @@ """Account v1 action implementations""" +from osc_lib.cli import parseractions +from osc_lib.command import command +from osc_lib import utils import six -from openstackclient.common import command -from openstackclient.common import parseractions -from openstackclient.common import utils - class SetAccount(command.Command): """Set account properties""" diff --git a/openstackclient/object/v1/container.py b/openstackclient/object/v1/container.py index 80b84238..2b021ec2 100644 --- a/openstackclient/object/v1/container.py +++ b/openstackclient/object/v1/container.py @@ -16,12 +16,11 @@ """Container v1 action implementations""" +from osc_lib.cli import parseractions +from osc_lib.command import command +from osc_lib import utils import six -from openstackclient.common import command -from openstackclient.common import parseractions -from openstackclient.common import utils - class CreateContainer(command.Lister): """Create new container""" diff --git a/openstackclient/object/v1/object.py b/openstackclient/object/v1/object.py index f9a55e9c..39dba3d5 100644 --- a/openstackclient/object/v1/object.py +++ b/openstackclient/object/v1/object.py @@ -16,12 +16,11 @@ """Object v1 action implementations""" +from osc_lib.cli import parseractions +from osc_lib.command import command +from osc_lib import utils import six -from openstackclient.common import command -from openstackclient.common import parseractions -from openstackclient.common import utils - class CreateObject(command.Lister): """Upload object to container""" diff --git a/openstackclient/releasenotes/notes/bug-1543222-6f8579344ff5c958.yaml b/openstackclient/releasenotes/notes/bug-1543222-6f8579344ff5c958.yaml deleted file mode 100644 index c783d013..00000000 --- a/openstackclient/releasenotes/notes/bug-1543222-6f8579344ff5c958.yaml +++ /dev/null @@ -1,6 +0,0 @@ ---- -fixes: - - Keystone V3 `user password set` is a self-service operation. It should - not required a scoped token as it is not considered a `scoped operation`. - [Bug `1543222 <https://bugs.launchpad.net/bugs/1543222>`_] - diff --git a/openstackclient/shell.py b/openstackclient/shell.py index 9179ad01..49a06040 100644 --- a/openstackclient/shell.py +++ b/openstackclient/shell.py @@ -26,16 +26,17 @@ from cliff import app from cliff import command from cliff import complete from cliff import help +from osc_lib.command import timing +from osc_lib import exceptions as exc +from osc_lib import logs +from osc_lib import utils from oslo_utils import importutils from oslo_utils import strutils import openstackclient from openstackclient.common import clientmanager from openstackclient.common import commandmanager -from openstackclient.common import exceptions as exc -from openstackclient.common import logs -from openstackclient.common import timing -from openstackclient.common import utils +from openstackclient.i18n import _ from os_client_config import config as cloud_config @@ -63,9 +64,8 @@ def prompt_for_password(prompt=None): pass # No password because we did't have a tty or nothing was entered if not pw: - raise exc.CommandError( - "No password entered, or found via --os-password or OS_PASSWORD", - ) + raise exc.CommandError(_("No password entered, or found via" + " --os-password or OS_PASSWORD"),) return pw @@ -185,7 +185,7 @@ class OpenStackShell(app.App): metavar='<cloud-config-name>', dest='cloud', default=utils.env('OS_CLOUD'), - help='Cloud name in clouds.yaml (Env: OS_CLOUD)', + help=_('Cloud name in clouds.yaml (Env: OS_CLOUD)'), ) # Global arguments parser.add_argument( @@ -193,37 +193,41 @@ class OpenStackShell(app.App): metavar='<auth-region-name>', dest='region_name', default=utils.env('OS_REGION_NAME'), - help='Authentication region name (Env: OS_REGION_NAME)') + help=_('Authentication region name (Env: OS_REGION_NAME)'), + ) parser.add_argument( '--os-cacert', metavar='<ca-bundle-file>', dest='cacert', default=utils.env('OS_CACERT'), - help='CA certificate bundle file (Env: OS_CACERT)') + help=_('CA certificate bundle file (Env: OS_CACERT)'), + ) parser.add_argument( '--os-cert', metavar='<certificate-file>', dest='cert', default=utils.env('OS_CERT'), - help='Client certificate bundle file (Env: OS_CERT)') + help=_('Client certificate bundle file (Env: OS_CERT)'), + ) parser.add_argument( '--os-key', metavar='<key-file>', dest='key', default=utils.env('OS_KEY'), - help='Client certificate key file (Env: OS_KEY)') + help=_('Client certificate key file (Env: OS_KEY)'), + ) verify_group = parser.add_mutually_exclusive_group() verify_group.add_argument( '--verify', action='store_true', default=None, - help='Verify server certificate (default)', + help=_('Verify server certificate (default)'), ) verify_group.add_argument( '--insecure', action='store_true', default=None, - help='Disable server certificate verification', + help=_('Disable server certificate verification'), ) parser.add_argument( '--os-default-domain', @@ -232,28 +236,29 @@ class OpenStackShell(app.App): default=utils.env( 'OS_DEFAULT_DOMAIN', default=DEFAULT_DOMAIN), - help='Default domain ID, default=' + - DEFAULT_DOMAIN + - ' (Env: OS_DEFAULT_DOMAIN)') + help=_('Default domain ID, default=%s. ' + '(Env: OS_DEFAULT_DOMAIN)') % DEFAULT_DOMAIN, + ) parser.add_argument( '--os-interface', metavar='<interface>', dest='interface', choices=['admin', 'public', 'internal'], default=utils.env('OS_INTERFACE'), - help='Select an interface type.' - ' Valid interface types: [admin, public, internal].' - ' (Env: OS_INTERFACE)') + help=_('Select an interface type.' + ' Valid interface types: [admin, public, internal].' + ' (Env: OS_INTERFACE)'), + ) parser.add_argument( '--timing', default=False, action='store_true', - help="Print API call timing info", + help=_("Print API call timing info"), ) parser.add_argument( '--os-beta-command', action='store_true', - help="Enable beta commands which are subject to change", + help=_("Enable beta commands which are subject to change"), ) # osprofiler HMAC key argument @@ -262,7 +267,7 @@ class OpenStackShell(app.App): '--os-profile', metavar='hmac-key', dest='profile', - help='HMAC key for encrypting profiling context data', + help=_('HMAC key for encrypting profiling context data'), ) # NOTE(dtroyer): This global option should have been named # --os-profile as --profile interferes with at @@ -438,12 +443,12 @@ class OpenStackShell(app.App): cmd.__class__.__name__, ) if cmd.auth_required: - if hasattr(cmd, 'required_scope'): + self.client_manager.setup_auth() + if hasattr(cmd, 'required_scope') and cmd.required_scope: # let the command decide whether we need a scoped token - self.client_manager.setup_auth(cmd.required_scope) + self.client_manager.validate_scope() # 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: %s', cmd.__class__.__name__, err or '') diff --git a/openstackclient/tests/api/test_api.py b/openstackclient/tests/api/test_api.py index 81197967..b444d9f1 100644 --- a/openstackclient/tests/api/test_api.py +++ b/openstackclient/tests/api/test_api.py @@ -13,8 +13,9 @@ """Base API Library Tests""" +from osc_lib import exceptions + from openstackclient.api import api -from openstackclient.common import exceptions from openstackclient.tests.api import fakes as api_fakes diff --git a/openstackclient/tests/common/test_clientmanager.py b/openstackclient/tests/common/test_clientmanager.py index fa6c3fcc..0a9965e0 100644 --- a/openstackclient/tests/common/test_clientmanager.py +++ b/openstackclient/tests/common/test_clientmanager.py @@ -15,21 +15,20 @@ import json as jsonutils import mock -from requests_mock.contrib import fixture -from keystoneclient.auth.identity import v2 as auth_v2 -from keystoneclient import service_catalog +from keystoneauth1.access import service_catalog +from keystoneauth1.identity import v2 as auth_v2 +from keystoneauth1 import token_endpoint +from osc_lib import exceptions as exc +from requests_mock.contrib import fixture from openstackclient.api import auth -from openstackclient.api import auth_plugin from openstackclient.common import clientmanager -from openstackclient.common import exceptions as exc from openstackclient.tests import fakes from openstackclient.tests import utils API_VERSION = {"identity": "2.0"} - AUTH_REF = {'version': 'v2.0'} AUTH_REF.update(fakes.TEST_RESPONSE_DICT['access']) SERVICE_CATALOG = service_catalog.ServiceCatalogV2(AUTH_REF) @@ -126,7 +125,7 @@ class TestClientManager(utils.TestCase): ) self.assertIsInstance( client_manager.auth, - auth_plugin.TokenEndpoint, + token_endpoint.Token, ) self.assertFalse(client_manager._insecure) self.assertTrue(client_manager._verify) @@ -205,11 +204,14 @@ class TestClientManager(utils.TestCase): ) self.assertTrue(client_manager._insecure) self.assertFalse(client_manager._verify) - # These need to stick around until the old-style clients are gone self.assertEqual( - AUTH_REF, - client_manager.auth_ref, + AUTH_REF.pop('version'), + client_manager.auth_ref.version, + ) + self.assertEqual( + fakes.to_unicode_dict(AUTH_REF), + client_manager.auth_ref._data['access'], ) self.assertEqual( dir(SERVICE_CATALOG), @@ -296,9 +298,10 @@ class TestClientManager(utils.TestCase): def _select_auth_plugin(self, auth_params, api_version, auth_plugin_name): 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, + api_version={"identity": api_version}, verify=True ) client_manager.setup_auth() @@ -353,8 +356,8 @@ class TestClientManager(utils.TestCase): client_manager.setup_auth, ) - @mock.patch('openstackclient.api.auth.check_valid_auth_options') - def test_client_manager_auth_setup_once(self, check_auth_options_func): + @mock.patch('openstackclient.api.auth.check_valid_authentication_options') + def test_client_manager_auth_setup_once(self, check_authn_options_func): client_manager = clientmanager.ClientManager( cli_options=FakeOptions( auth=dict( @@ -369,11 +372,11 @@ class TestClientManager(utils.TestCase): ) self.assertFalse(client_manager._auth_setup_completed) client_manager.setup_auth() - self.assertTrue(check_auth_options_func.called) + self.assertTrue(check_authn_options_func.called) self.assertTrue(client_manager._auth_setup_completed) # now make sure we don't do auth setup the second time around # by checking whether check_valid_auth_options() gets called again - check_auth_options_func.reset_mock() + check_authn_options_func.reset_mock() client_manager.auth_ref - check_auth_options_func.assert_not_called() + check_authn_options_func.assert_not_called() diff --git a/openstackclient/tests/common/test_command.py b/openstackclient/tests/common/test_command.py index 722a4c06..658bc895 100644 --- a/openstackclient/tests/common/test_command.py +++ b/openstackclient/tests/common/test_command.py @@ -14,8 +14,9 @@ import mock +from osc_lib import exceptions + from openstackclient.common import command -from openstackclient.common import exceptions from openstackclient.tests import fakes as test_fakes from openstackclient.tests import utils as test_utils diff --git a/openstackclient/tests/common/test_configuration.py b/openstackclient/tests/common/test_configuration.py index e81550ed..915e5bd3 100644 --- a/openstackclient/tests/common/test_configuration.py +++ b/openstackclient/tests/common/test_configuration.py @@ -11,6 +11,8 @@ # under the License. # +import mock + from openstackclient.common import configuration from openstackclient.tests import fakes from openstackclient.tests import utils @@ -33,7 +35,12 @@ class TestConfiguration(utils.TestCommand): fakes.REGION_NAME, ) - def test_show(self): + opts = [mock.Mock(secret=True, dest="password"), + mock.Mock(secret=True, dest="token")] + + @mock.patch("keystoneauth1.loading.base.get_plugin_options", + return_value=opts) + def test_show(self, m_get_plugin_opts): arglist = [] verifylist = [('mask', True)] cmd = configuration.ShowConfiguration(self.app, None) @@ -44,7 +51,9 @@ class TestConfiguration(utils.TestCommand): self.assertEqual(self.columns, columns) self.assertEqual(self.datalist, data) - def test_show_unmask(self): + @mock.patch("keystoneauth1.loading.base.get_plugin_options", + return_value=opts) + def test_show_unmask(self, m_get_plugin_opts): arglist = ['--unmask'] verifylist = [('mask', False)] cmd = configuration.ShowConfiguration(self.app, None) @@ -62,7 +71,9 @@ class TestConfiguration(utils.TestCommand): ) self.assertEqual(datalist, data) - def test_show_mask(self): + @mock.patch("keystoneauth1.loading.base.get_plugin_options", + return_value=opts) + def test_show_mask(self, m_get_plugin_opts): arglist = ['--mask'] verifylist = [('mask', True)] cmd = configuration.ShowConfiguration(self.app, None) diff --git a/openstackclient/tests/common/test_extension.py b/openstackclient/tests/common/test_extension.py index 0736a3e5..17a3b492 100644 --- a/openstackclient/tests/common/test_extension.py +++ b/openstackclient/tests/common/test_extension.py @@ -11,7 +11,6 @@ # under the License. # -import copy import mock from openstackclient.common import extension @@ -29,26 +28,38 @@ class TestExtension(utils.TestCommand): def setUp(self): super(TestExtension, self).setUp() - self.app.client_manager.identity = identity_fakes.FakeIdentityv2Client( + identity_client = identity_fakes.FakeIdentityv2Client( endpoint=fakes.AUTH_URL, token=fakes.AUTH_TOKEN, ) - self.identity_extensions_mock = ( - self.app.client_manager.identity.extensions) + self.app.client_manager.identity = identity_client + self.identity_extensions_mock = identity_client.extensions self.identity_extensions_mock.reset_mock() - self.app.client_manager.compute = compute_fakes.FakeComputev2Client( + compute_client = compute_fakes.FakeComputev2Client( endpoint=fakes.AUTH_URL, token=fakes.AUTH_TOKEN, ) + self.app.client_manager.compute = compute_client + compute_client.list_extensions = mock.Mock() + self.compute_extensions_mock = compute_client.list_extensions + self.compute_extensions_mock.reset_mock() - self.app.client_manager.volume = volume_fakes.FakeVolumeClient( + volume_client = volume_fakes.FakeVolumeClient( endpoint=fakes.AUTH_URL, token=fakes.AUTH_TOKEN, ) + self.app.client_manager.volume = volume_client + volume_client.list_extensions = mock.Mock() + self.volume_extensions_mock = volume_client.list_extensions + self.volume_extensions_mock.reset_mock() - network_client = network_fakes.FakeNetworkV2Client() + network_client = network_fakes.FakeNetworkV2Client( + endpoint=fakes.AUTH_URL, + token=fakes.AUTH_TOKEN, + ) self.app.client_manager.network = network_client + network_client.extensions = mock.Mock() self.network_extensions_mock = network_client.extensions self.network_extensions_mock.reset_mock() @@ -59,38 +70,21 @@ class TestExtensionList(TestExtension): long_columns = ('Name', 'Namespace', 'Description', 'Alias', 'Updated', 'Links') + volume_extension = volume_fakes.FakeExtension.create_one_extension() + identity_extension = identity_fakes.FakeExtension.create_one_extension() + compute_extension = compute_fakes.FakeExtension.create_one_extension() + network_extension = network_fakes.FakeExtension.create_one_extension() + def setUp(self): super(TestExtensionList, self).setUp() self.identity_extensions_mock.list.return_value = [ - fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.EXTENSION), - loaded=True, - ), - ] - - self.app.client_manager.compute.list_extensions = mock.Mock() - self.compute_extensions_mock = ( - self.app.client_manager.compute.list_extensions) + self.identity_extension] self.compute_extensions_mock.show_all.return_value = [ - fakes.FakeResource( - None, - copy.deepcopy(compute_fakes.EXTENSION), - loaded=True, - ), - ] - - self.app.client_manager.volume.list_extensions = mock.Mock() - self.volume_extensions_mock = ( - self.app.client_manager.volume.list_extensions) + self.compute_extension] self.volume_extensions_mock.show_all.return_value = [ - fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.EXTENSION), - loaded=True, - ), - ] + self.volume_extension] + self.network_extensions_mock.return_value = [self.network_extension] # Get the command object to test self.cmd = extension.ListExtension(self.app, None) @@ -115,24 +109,24 @@ class TestExtensionList(TestExtension): verifylist = [] datalist = ( ( - identity_fakes.extension_name, - identity_fakes.extension_alias, - identity_fakes.extension_description, + self.identity_extension.name, + self.identity_extension.alias, + self.identity_extension.description, ), ( - compute_fakes.extension_name, - compute_fakes.extension_alias, - compute_fakes.extension_description, + self.compute_extension.name, + self.compute_extension.alias, + self.compute_extension.description, ), ( - volume_fakes.extension_name, - volume_fakes.extension_alias, - volume_fakes.extension_description, + self.volume_extension.name, + self.volume_extension.alias, + self.volume_extension.description, ), ( - network_fakes.extension_name, - network_fakes.extension_alias, - network_fakes.extension_description, + self.network_extension.name, + self.network_extension.alias, + self.network_extension.description, ), ) self._test_extension_list_helper(arglist, verifylist, datalist) @@ -150,36 +144,36 @@ class TestExtensionList(TestExtension): ] datalist = ( ( - identity_fakes.extension_name, - identity_fakes.extension_namespace, - identity_fakes.extension_description, - identity_fakes.extension_alias, - identity_fakes.extension_updated, - identity_fakes.extension_links, + self.identity_extension.name, + self.identity_extension.namespace, + self.identity_extension.description, + self.identity_extension.alias, + self.identity_extension.updated, + self.identity_extension.links, ), ( - compute_fakes.extension_name, - compute_fakes.extension_namespace, - compute_fakes.extension_description, - compute_fakes.extension_alias, - compute_fakes.extension_updated, - compute_fakes.extension_links, + self.compute_extension.name, + self.compute_extension.namespace, + self.compute_extension.description, + self.compute_extension.alias, + self.compute_extension.updated, + self.compute_extension.links, ), ( - volume_fakes.extension_name, - volume_fakes.extension_namespace, - volume_fakes.extension_description, - volume_fakes.extension_alias, - volume_fakes.extension_updated, - volume_fakes.extension_links, + self.volume_extension.name, + self.volume_extension.namespace, + self.volume_extension.description, + self.volume_extension.alias, + self.volume_extension.updated, + self.volume_extension.links, ), ( - network_fakes.extension_name, - network_fakes.extension_namespace, - network_fakes.extension_description, - network_fakes.extension_alias, - network_fakes.extension_updated, - network_fakes.extension_links, + self.network_extension.name, + self.network_extension.namespace, + self.network_extension.description, + self.network_extension.alias, + self.network_extension.updated, + self.network_extension.links, ), ) self._test_extension_list_helper(arglist, verifylist, datalist, True) @@ -196,9 +190,9 @@ class TestExtensionList(TestExtension): ('identity', True), ] datalist = (( - identity_fakes.extension_name, - identity_fakes.extension_alias, - identity_fakes.extension_description, + self.identity_extension.name, + self.identity_extension.alias, + self.identity_extension.description, ), ) self._test_extension_list_helper(arglist, verifylist, datalist) self.identity_extensions_mock.list.assert_called_with() @@ -212,9 +206,9 @@ class TestExtensionList(TestExtension): ] datalist = ( ( - network_fakes.extension_name, - network_fakes.extension_alias, - network_fakes.extension_description, + self.network_extension.name, + self.network_extension.alias, + self.network_extension.description, ), ) self._test_extension_list_helper(arglist, verifylist, datalist) @@ -228,9 +222,9 @@ class TestExtensionList(TestExtension): ('compute', True), ] datalist = (( - compute_fakes.extension_name, - compute_fakes.extension_alias, - compute_fakes.extension_description, + self.compute_extension.name, + self.compute_extension.alias, + self.compute_extension.description, ), ) self._test_extension_list_helper(arglist, verifylist, datalist) self.compute_extensions_mock.show_all.assert_called_with() @@ -243,9 +237,9 @@ class TestExtensionList(TestExtension): ('volume', True), ] datalist = (( - volume_fakes.extension_name, - volume_fakes.extension_alias, - volume_fakes.extension_description, + self.volume_extension.name, + self.volume_extension.alias, + self.volume_extension.description, ), ) self._test_extension_list_helper(arglist, verifylist, datalist) self.volume_extensions_mock.show_all.assert_called_with() diff --git a/openstackclient/tests/common/test_logs.py b/openstackclient/tests/common/test_logs.py index 0386cdfd..5091510c 100644 --- a/openstackclient/tests/common/test_logs.py +++ b/openstackclient/tests/common/test_logs.py @@ -11,6 +11,9 @@ # under the License. # +# NOTE(dtroyer): This file is deprecated in Jun 2016, remove after 4.x release +# or Jun 2017. + import logging import mock @@ -121,7 +124,7 @@ class TestLogConfigurator(utils.TestCase): @mock.patch('logging.StreamHandler') @mock.patch('logging.getLogger') - @mock.patch('openstackclient.common.logs.set_warning_filter') + @mock.patch('osc_lib.logs.set_warning_filter') def test_init(self, warning_filter, getLogger, handle): getLogger.side_effect = self.loggers console_logger = mock.Mock() @@ -142,7 +145,7 @@ class TestLogConfigurator(utils.TestCase): self.assertFalse(configurator.dump_trace) @mock.patch('logging.getLogger') - @mock.patch('openstackclient.common.logs.set_warning_filter') + @mock.patch('osc_lib.logs.set_warning_filter') def test_init_no_debug(self, warning_filter, getLogger): getLogger.side_effect = self.loggers self.options.debug = True @@ -155,8 +158,8 @@ class TestLogConfigurator(utils.TestCase): @mock.patch('logging.FileHandler') @mock.patch('logging.getLogger') - @mock.patch('openstackclient.common.logs.set_warning_filter') - @mock.patch('openstackclient.common.logs._FileFormatter') + @mock.patch('osc_lib.logs.set_warning_filter') + @mock.patch('osc_lib.logs._FileFormatter') def test_init_log_file(self, formatter, warning_filter, getLogger, handle): getLogger.side_effect = self.loggers self.options.log_file = '/tmp/log_file' @@ -176,8 +179,8 @@ class TestLogConfigurator(utils.TestCase): @mock.patch('logging.FileHandler') @mock.patch('logging.getLogger') - @mock.patch('openstackclient.common.logs.set_warning_filter') - @mock.patch('openstackclient.common.logs._FileFormatter') + @mock.patch('osc_lib.logs.set_warning_filter') + @mock.patch('osc_lib.logs._FileFormatter') def test_configure(self, formatter, warning_filter, getLogger, handle): getLogger.side_effect = self.loggers configurator = logs.LogConfigurator(self.options) diff --git a/openstackclient/tests/common/test_parseractions.py b/openstackclient/tests/common/test_parseractions.py index 5c5ca3d3..60d4a8cf 100644 --- a/openstackclient/tests/common/test_parseractions.py +++ b/openstackclient/tests/common/test_parseractions.py @@ -13,6 +13,9 @@ # under the License. # +# NOTE(dtroyer): This file is deprecated in Jun 2016, remove after 4.x release +# or Jun 2017. + import argparse from openstackclient.common import parseractions @@ -49,16 +52,13 @@ class TestKeyValueAction(utils.TestCase): self.assertDictEqual(expect, actual) def test_error_values(self): - results = self.parser.parse_args([ - '--property', 'red', - '--property', 'green=100%', - '--property', 'blue', - ]) - - actual = getattr(results, 'property', {}) - # There should be no red or blue - expect = {'green': '100%', 'format': '#rgb'} - self.assertDictEqual(expect, actual) + self.assertRaises( + argparse.ArgumentTypeError, + self.parser.parse_args, + [ + '--property', 'red', + ] + ) class TestMultiKeyValueAction(utils.TestCase): diff --git a/openstackclient/tests/common/test_timing.py b/openstackclient/tests/common/test_timing.py deleted file mode 100644 index e33bb7ae..00000000 --- a/openstackclient/tests/common/test_timing.py +++ /dev/null @@ -1,94 +0,0 @@ -# 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. -# - -"""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_elapsed = 0.872809 - - -class FakeGenericClient(object): - - def __init__(self, **kwargs): - self.auth_token = kwargs['token'] - self.management_url = kwargs['endpoint'] - - -class TestTiming(utils.TestCommand): - - columns = ( - 'URL', - 'Seconds', - ) - - def setUp(self): - super(TestTiming, self).setUp() - - self.app.timing_data = [] - - self.app.client_manager.compute = FakeGenericClient( - endpoint=fakes.AUTH_URL, - token=fakes.AUTH_TOKEN, - ) - - self.app.client_manager.volume = FakeGenericClient( - endpoint=fakes.AUTH_URL, - token=fakes.AUTH_TOKEN, - ) - - # Get the command object to test - self.cmd = timing.Timing(self.app, None) - - def test_timing_list_no_data(self): - arglist = [] - verifylist = [] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - # In base command class Lister in cliff, abstract method take_action() - # returns a tuple containing the column names and an iterable - # containing the data to be listed. - columns, data = self.cmd.take_action(parsed_args) - - self.assertEqual(self.columns, columns) - datalist = [ - ('Total', 0.0,) - ] - self.assertEqual(datalist, data) - - def test_timing_list(self): - self.app.timing_data = [( - timing_url, - datetime.timedelta(microseconds=timing_elapsed * 1000000), - )] - - arglist = [] - verifylist = [] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - # In base command class Lister in cliff, abstract method take_action() - # returns a tuple containing the column names and an iterable - # containing the data to be listed. - columns, data = self.cmd.take_action(parsed_args) - self.assertEqual(self.columns, columns) - datalist = [ - (timing_url, timing_elapsed), - ('Total', timing_elapsed), - ] - self.assertEqual(datalist, data) diff --git a/openstackclient/tests/common/test_utils.py b/openstackclient/tests/common/test_utils.py deleted file mode 100644 index 2248d043..00000000 --- a/openstackclient/tests/common/test_utils.py +++ /dev/null @@ -1,400 +0,0 @@ -# Copyright 2012-2013 OpenStack, LLC. -# -# 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 time -import uuid - -import mock - -from openstackclient.common import exceptions -from openstackclient.common import utils -from openstackclient.tests import fakes -from openstackclient.tests import utils as test_utils - -PASSWORD = "Pa$$w0rd" -WASSPORD = "Wa$$p0rd" -DROWSSAP = "dr0w$$aP" - - -class FakeOddballResource(fakes.FakeResource): - - def get(self, attr): - """get() is needed for utils.find_resource()""" - if attr == 'id': - return self.id - elif attr == 'name': - return self.name - else: - return None - - -class TestUtils(test_utils.TestCase): - - def test_get_password_good(self): - with mock.patch("getpass.getpass", return_value=PASSWORD): - mock_stdin = mock.Mock() - mock_stdin.isatty = mock.Mock() - mock_stdin.isatty.return_value = True - self.assertEqual(PASSWORD, utils.get_password(mock_stdin)) - - def test_get_password_bad_once(self): - answers = [PASSWORD, WASSPORD, DROWSSAP, DROWSSAP] - with mock.patch("getpass.getpass", side_effect=answers): - mock_stdin = mock.Mock() - mock_stdin.isatty = mock.Mock() - mock_stdin.isatty.return_value = True - self.assertEqual(DROWSSAP, utils.get_password(mock_stdin)) - - def test_get_password_no_tty(self): - mock_stdin = mock.Mock() - mock_stdin.isatty = mock.Mock() - mock_stdin.isatty.return_value = False - self.assertRaises(exceptions.CommandError, - utils.get_password, - mock_stdin) - - def test_get_password_cntrl_d(self): - with mock.patch("getpass.getpass", side_effect=EOFError()): - mock_stdin = mock.Mock() - mock_stdin.isatty = mock.Mock() - mock_stdin.isatty.return_value = True - self.assertRaises(exceptions.CommandError, - utils.get_password, - mock_stdin) - - def get_test_items(self): - item1 = {'a': 1, 'b': 2} - item2 = {'a': 1, 'b': 3} - item3 = {'a': 2, 'b': 2} - item4 = {'a': 2, 'b': 1} - return [item1, item2, item3, item4] - - def test_sort_items_with_one_key(self): - items = self.get_test_items() - sort_str = 'b' - expect_items = [items[3], items[0], items[2], items[1]] - self.assertEqual(expect_items, utils.sort_items(items, sort_str)) - - def test_sort_items_with_multiple_keys(self): - items = self.get_test_items() - sort_str = 'a,b' - expect_items = [items[0], items[1], items[3], items[2]] - self.assertEqual(expect_items, utils.sort_items(items, sort_str)) - - def test_sort_items_all_with_direction(self): - items = self.get_test_items() - sort_str = 'a:desc,b:desc' - expect_items = [items[2], items[3], items[1], items[0]] - self.assertEqual(expect_items, utils.sort_items(items, sort_str)) - - def test_sort_items_some_with_direction(self): - items = self.get_test_items() - sort_str = 'a,b:desc' - expect_items = [items[1], items[0], items[2], items[3]] - self.assertEqual(expect_items, utils.sort_items(items, sort_str)) - - def test_sort_items_with_object(self): - item1 = mock.Mock(a=1, b=2) - item2 = mock.Mock(a=1, b=3) - item3 = mock.Mock(a=2, b=2) - item4 = mock.Mock(a=2, b=1) - items = [item1, item2, item3, item4] - sort_str = 'b,a' - expect_items = [item4, item1, item3, item2] - self.assertEqual(expect_items, utils.sort_items(items, sort_str)) - - def test_sort_items_with_empty_key(self): - items = self.get_test_items() - sort_srt = '' - self.assertEqual(items, utils.sort_items(items, sort_srt)) - sort_srt = None - self.assertEqual(items, utils.sort_items(items, sort_srt)) - - def test_sort_items_with_invalid_key(self): - items = self.get_test_items() - sort_str = 'c' - self.assertRaises(exceptions.CommandError, - utils.sort_items, - items, sort_str) - - def test_sort_items_with_invalid_direction(self): - items = self.get_test_items() - sort_str = 'a:bad_dir' - self.assertRaises(exceptions.CommandError, - utils.sort_items, - items, sort_str) - - @mock.patch.object(time, 'sleep') - def test_wait_for_status_ok(self, mock_sleep): - # Tests the normal flow that the resource is status=active - resource = mock.MagicMock(status='ACTIVE') - status_f = mock.Mock(return_value=resource) - res_id = str(uuid.uuid4()) - self.assertTrue(utils.wait_for_status(status_f, res_id,)) - mock_sleep.assert_not_called() - - @mock.patch.object(time, 'sleep') - def test_wait_for_status_ok__with_overrides(self, mock_sleep): - # Tests the normal flow that the resource is status=complete - resource = mock.MagicMock(my_status='COMPLETE') - status_f = mock.Mock(return_value=resource) - res_id = str(uuid.uuid4()) - self.assertTrue(utils.wait_for_status(status_f, res_id, - status_field='my_status', - success_status=['complete'])) - mock_sleep.assert_not_called() - - @mock.patch.object(time, 'sleep') - def test_wait_for_status_error(self, mock_sleep): - # Tests that we fail if the resource is status=error - resource = mock.MagicMock(status='ERROR') - status_f = mock.Mock(return_value=resource) - res_id = str(uuid.uuid4()) - self.assertFalse(utils.wait_for_status(status_f, res_id)) - mock_sleep.assert_not_called() - - @mock.patch.object(time, 'sleep') - def test_wait_for_status_error_with_overrides(self, mock_sleep): - # Tests that we fail if the resource is my_status=failed - resource = mock.MagicMock(my_status='FAILED') - status_f = mock.Mock(return_value=resource) - res_id = str(uuid.uuid4()) - self.assertFalse(utils.wait_for_status(status_f, res_id, - status_field='my_status', - error_status=['failed'])) - mock_sleep.assert_not_called() - - @mock.patch.object(time, 'sleep') - def test_wait_for_delete_ok(self, mock_sleep): - # Tests the normal flow that the resource is deleted with a 404 coming - # back on the 2nd iteration of the wait loop. - resource = mock.MagicMock(status='ACTIVE', progress=None) - mock_get = mock.Mock(side_effect=[resource, - exceptions.NotFound(404)]) - manager = mock.MagicMock(get=mock_get) - res_id = str(uuid.uuid4()) - callback = mock.Mock() - self.assertTrue(utils.wait_for_delete(manager, res_id, - callback=callback)) - mock_sleep.assert_called_once_with(5) - callback.assert_called_once_with(0) - - @mock.patch.object(time, 'sleep') - def test_wait_for_delete_timeout(self, mock_sleep): - # Tests that we fail if the resource is not deleted before the timeout. - resource = mock.MagicMock(status='ACTIVE') - mock_get = mock.Mock(return_value=resource) - manager = mock.MagicMock(get=mock_get) - res_id = str(uuid.uuid4()) - self.assertFalse(utils.wait_for_delete(manager, res_id, sleep_time=1, - timeout=1)) - mock_sleep.assert_called_once_with(1) - - @mock.patch.object(time, 'sleep') - def test_wait_for_delete_error(self, mock_sleep): - # Tests that we fail if the resource goes to error state while waiting. - resource = mock.MagicMock(status='ERROR') - mock_get = mock.Mock(return_value=resource) - manager = mock.MagicMock(get=mock_get) - res_id = str(uuid.uuid4()) - self.assertFalse(utils.wait_for_delete(manager, res_id)) - mock_sleep.assert_not_called() - - @mock.patch.object(time, 'sleep') - def test_wait_for_delete_error_with_overrides(self, mock_sleep): - # Tests that we fail if the resource is my_status=failed - resource = mock.MagicMock(my_status='FAILED') - mock_get = mock.Mock(return_value=resource) - manager = mock.MagicMock(get=mock_get) - res_id = str(uuid.uuid4()) - self.assertFalse(utils.wait_for_delete(manager, res_id, - status_field='my_status', - error_status=['failed'])) - mock_sleep.assert_not_called() - - @mock.patch.object(time, 'sleep') - def test_wait_for_delete_error_with_overrides_exception(self, mock_sleep): - # Tests that we succeed if the resource is specific exception - mock_get = mock.Mock(side_effect=Exception) - manager = mock.MagicMock(get=mock_get) - res_id = str(uuid.uuid4()) - self.assertTrue(utils.wait_for_delete(manager, res_id, - exception_name=['Exception'])) - mock_sleep.assert_not_called() - - def test_build_kwargs_dict_value_set(self): - self.assertEqual({'arg_bla': 'bla'}, - utils.build_kwargs_dict('arg_bla', 'bla')) - - def test_build_kwargs_dict_value_None(self): - self.assertEqual({}, utils.build_kwargs_dict('arg_bla', None)) - - def test_build_kwargs_dict_value_empty_str(self): - self.assertEqual({}, utils.build_kwargs_dict('arg_bla', '')) - - -class NoUniqueMatch(Exception): - pass - - -class TestFindResource(test_utils.TestCase): - - def setUp(self): - super(TestFindResource, self).setUp() - self.name = 'legos' - self.expected = mock.Mock() - self.manager = mock.Mock() - self.manager.resource_class = mock.Mock() - self.manager.resource_class.__name__ = 'lego' - - def test_find_resource_get_int(self): - self.manager.get = mock.Mock(return_value=self.expected) - result = utils.find_resource(self.manager, 1) - self.assertEqual(self.expected, result) - self.manager.get.assert_called_with(1) - - def test_find_resource_get_int_string(self): - self.manager.get = mock.Mock(return_value=self.expected) - result = utils.find_resource(self.manager, "2") - self.assertEqual(self.expected, result) - self.manager.get.assert_called_with(2) - - def test_find_resource_get_uuid(self): - uuid = '9a0dc2a0-ad0d-11e3-a5e2-0800200c9a66' - self.manager.get = mock.Mock(return_value=self.expected) - result = utils.find_resource(self.manager, uuid) - self.assertEqual(self.expected, result) - self.manager.get.assert_called_with(uuid) - - def test_find_resource_get_whatever(self): - self.manager.get = mock.Mock(return_value=self.expected) - result = utils.find_resource(self.manager, 'whatever') - self.assertEqual(self.expected, result) - self.manager.get.assert_called_with('whatever') - - def test_find_resource_find(self): - self.manager.get = mock.Mock(side_effect=Exception('Boom!')) - self.manager.find = mock.Mock(return_value=self.expected) - result = utils.find_resource(self.manager, self.name) - self.assertEqual(self.expected, result) - self.manager.get.assert_called_with(self.name) - self.manager.find.assert_called_with(name=self.name) - - def test_find_resource_find_not_found(self): - self.manager.get = mock.Mock(side_effect=Exception('Boom!')) - self.manager.find = mock.Mock( - side_effect=exceptions.NotFound(404, "2") - ) - result = self.assertRaises(exceptions.CommandError, - utils.find_resource, - self.manager, - self.name) - self.assertEqual("No lego with a name or ID of 'legos' exists.", - str(result)) - self.manager.get.assert_called_with(self.name) - self.manager.find.assert_called_with(name=self.name) - - def test_find_resource_list_forbidden(self): - self.manager.get = mock.Mock(side_effect=Exception('Boom!')) - self.manager.find = mock.Mock(side_effect=Exception('Boom!')) - self.manager.list = mock.Mock( - side_effect=exceptions.Forbidden(403) - ) - self.assertRaises(exceptions.Forbidden, - utils.find_resource, - self.manager, - self.name) - self.manager.list.assert_called_with() - - def test_find_resource_find_no_unique(self): - self.manager.get = mock.Mock(side_effect=Exception('Boom!')) - self.manager.find = mock.Mock(side_effect=NoUniqueMatch()) - result = self.assertRaises(exceptions.CommandError, - utils.find_resource, - self.manager, - self.name) - self.assertEqual("More than one lego exists with the name 'legos'.", - str(result)) - self.manager.get.assert_called_with(self.name) - self.manager.find.assert_called_with(name=self.name) - - def test_find_resource_silly_resource(self): - # We need a resource with no resource_class for this test, start fresh - self.manager = mock.Mock() - self.manager.get = mock.Mock(side_effect=Exception('Boom!')) - self.manager.find = mock.Mock( - side_effect=AttributeError( - "'Controller' object has no attribute 'find'", - ) - ) - silly_resource = FakeOddballResource( - None, - {'id': '12345', 'name': self.name}, - loaded=True, - ) - self.manager.list = mock.Mock( - return_value=[silly_resource, ], - ) - result = utils.find_resource(self.manager, self.name) - self.assertEqual(silly_resource, result) - self.manager.get.assert_called_with(self.name) - self.manager.find.assert_called_with(name=self.name) - - def test_find_resource_silly_resource_not_found(self): - # We need a resource with no resource_class for this test, start fresh - self.manager = mock.Mock() - self.manager.get = mock.Mock(side_effect=Exception('Boom!')) - self.manager.find = mock.Mock( - side_effect=AttributeError( - "'Controller' object has no attribute 'find'", - ) - ) - self.manager.list = mock.Mock(return_value=[]) - result = self.assertRaises(exceptions.CommandError, - utils.find_resource, - self.manager, - self.name) - self.assertEqual("Could not find resource legos", - str(result)) - self.manager.get.assert_called_with(self.name) - self.manager.find.assert_called_with(name=self.name) - - def test_format_dict(self): - expected = "a='b', c='d', e='f'" - self.assertEqual(expected, - utils.format_dict({'a': 'b', 'c': 'd', 'e': 'f'})) - self.assertEqual(expected, - utils.format_dict({'e': 'f', 'c': 'd', 'a': 'b'})) - - def test_format_list(self): - expected = 'a, b, c' - self.assertEqual(expected, utils.format_list(['a', 'b', 'c'])) - self.assertEqual(expected, utils.format_list(['c', 'b', 'a'])) - - def test_format_list_of_dicts(self): - expected = "a='b', c='d'\ne='f'" - sorted_data = [{'a': 'b', 'c': 'd'}, {'e': 'f'}] - unsorted_data = [{'c': 'd', 'a': 'b'}, {'e': 'f'}] - self.assertEqual(expected, utils.format_list_of_dicts(sorted_data)) - self.assertEqual(expected, utils.format_list_of_dicts(unsorted_data)) - self.assertEqual('', utils.format_list_of_dicts([])) - self.assertEqual('', utils.format_list_of_dicts([{}])) - - def test_format_list_separator(self): - expected = 'a\nb\nc' - actual_pre_sorted = utils.format_list(['a', 'b', 'c'], separator='\n') - actual_unsorted = utils.format_list(['c', 'b', 'a'], separator='\n') - self.assertEqual(expected, actual_pre_sorted) - self.assertEqual(expected, actual_unsorted) diff --git a/openstackclient/tests/compute/v2/fakes.py b/openstackclient/tests/compute/v2/fakes.py index c9e2025d..882d8480 100644 --- a/openstackclient/tests/compute/v2/fakes.py +++ b/openstackclient/tests/compute/v2/fakes.py @@ -24,26 +24,6 @@ from openstackclient.tests.network.v2 import fakes as network_fakes from openstackclient.tests import utils from openstackclient.tests.volume.v2 import fakes as volume_fakes - -extension_name = 'Multinic' -extension_namespace = 'http://docs.openstack.org/compute/ext/'\ - 'multinic/api/v1.1' -extension_description = 'Multiple network support' -extension_updated = '2014-01-07T12:00:0-00:00' -extension_alias = 'NMN' -extension_links = '[{"href":'\ - '"https://github.com/openstack/compute-api", "type":'\ - ' "text/html", "rel": "describedby"}]' - -EXTENSION = { - 'name': extension_name, - 'namespace': extension_namespace, - 'description': extension_description, - 'updated': extension_updated, - 'alias': extension_alias, - 'links': extension_links, -} - floating_ip_num = 100 fix_ip_num = 100 injected_file_num = 100 @@ -107,6 +87,42 @@ class FakeAggregate(object): loaded=True) return aggregate + @staticmethod + def create_aggregates(attrs=None, count=2): + """Create multiple fake aggregates. + + :param Dictionary attrs: + A dictionary with all attributes + :param int count: + The number of aggregates to fake + :return: + A list of FakeResource objects faking the aggregates + """ + aggregates = [] + for i in range(0, count): + aggregates.append(FakeAggregate.create_one_aggregate(attrs)) + + return aggregates + + @staticmethod + def get_aggregates(aggregates=None, count=2): + """Get an iterable MagicMock object with a list of faked aggregates. + + If aggregates list is provided, then initialize the Mock object + with the list. Otherwise create one. + + :param List aggregates: + A list of FakeResource objects faking aggregates + :param int count: + The number of aggregates to fake + :return: + An iterable Mock object with side_effect set to a list of faked + aggregates + """ + if aggregates is None: + aggregates = FakeAggregate.create_aggregates(count) + return mock.MagicMock(side_effect=aggregates) + class FakeComputev2Client(object): @@ -259,6 +275,42 @@ class FakeAgent(object): return agents +class FakeExtension(object): + """Fake one or more extension.""" + + @staticmethod + def create_one_extension(attrs=None): + """Create a fake extension. + + :param Dictionary attrs: + A dictionary with all attributes + :return: + A FakeResource object with name, namespace, etc. + """ + attrs = attrs or {} + + # Set default attributes. + extension_info = { + 'name': 'name-' + uuid.uuid4().hex, + 'namespace': ( + 'http://docs.openstack.org/compute/ext/multinic/api/v1.1'), + 'description': 'description-' + uuid.uuid4().hex, + 'updated': '2014-01-07T12:00:0-00:00', + 'alias': 'NMN', + 'links': ('[{"href":' + '"https://github.com/openstack/compute-api", "type":' + ' "text/html", "rel": "describedby"}]') + } + + # Overwrite default attributes. + extension_info.update(attrs) + + extension = fakes.FakeResource( + info=copy.deepcopy(extension_info), + loaded=True) + return extension + + class FakeHypervisor(object): """Fake one or more hypervisor.""" @@ -436,6 +488,25 @@ class FakeSecurityGroup(object): return security_groups + @staticmethod + def get_security_groups(security_groups=None, count=2): + """Get an iterable MagicMock object with a list of faked security groups. + + If security groups list is provided, then initialize the Mock object + with the list. Otherwise create one. + + :param List security groups: + A list of FakeResource objects faking security groups + :param int count: + The number of security groups to fake + :return: + An iterable Mock object with side_effect set to a list of faked + security groups + """ + if security_groups is None: + security_groups = FakeSecurityGroup.create_security_groups(count) + return mock.MagicMock(side_effect=security_groups) + class FakeSecurityGroupRule(object): """Fake one or more security group rules.""" @@ -577,15 +648,19 @@ class FakeService(object): :param Dictionary attrs: A dictionary with all attributes :return: - A FakeResource object, with id, name, ram, vcpus, properties + A FakeResource object, with id, host, binary """ attrs = attrs or {} # Set default attributes. service_info = { + 'id': 'id-' + uuid.uuid4().hex, 'host': 'host-' + uuid.uuid4().hex, 'binary': 'binary-' + uuid.uuid4().hex, 'status': 'enabled', + 'zone': 'zone-' + uuid.uuid4().hex, + 'state': 'state-' + uuid.uuid4().hex, + 'updated_at': 'time-' + uuid.uuid4().hex, 'disabled_reason': 'earthquake', } @@ -697,7 +772,7 @@ class FakeFlavor(object): flavors """ if flavors is None: - flavors = FakeServer.create_flavors(count) + flavors = FakeFlavor.create_flavors(count) return mock.MagicMock(side_effect=flavors) @@ -751,6 +826,25 @@ class FakeKeypair(object): return keypairs + @staticmethod + def get_keypairs(keypairs=None, count=2): + """Get an iterable MagicMock object with a list of faked keypairs. + + If keypairs list is provided, then initialize the Mock object with the + list. Otherwise create one. + + :param List keypairs: + A list of FakeResource objects faking keypairs + :param int count: + The number of keypairs to fake + :return: + An iterable Mock object with side_effect set to a list of faked + keypairs + """ + if keypairs is None: + keypairs = FakeKeypair.create_keypairs(count) + return mock.MagicMock(side_effect=keypairs) + class FakeAvailabilityZone(object): """Fake one or more compute availability zones (AZs).""" @@ -987,7 +1081,6 @@ class FakeHost(object): # Set default attributes. host_info = { - "id": 1, "service_id": 1, "host": "host1", "uuid": 'host-id-' + uuid.uuid4().hex, diff --git a/openstackclient/tests/compute/v2/test_agent.py b/openstackclient/tests/compute/v2/test_agent.py index d3d1ff29..da329728 100644 --- a/openstackclient/tests/compute/v2/test_agent.py +++ b/openstackclient/tests/compute/v2/test_agent.py @@ -16,9 +16,11 @@ import mock from mock import call -from openstackclient.common import exceptions +from osc_lib import exceptions + from openstackclient.compute.v2 import agent from openstackclient.tests.compute.v2 import fakes as compute_fakes +from openstackclient.tests import utils as tests_utils class TestAgent(compute_fakes.TestComputev2): @@ -160,6 +162,15 @@ class TestAgentDelete(TestAgent): ] self.agents_mock.delete.assert_has_calls(calls) + def test_agent_delete_no_input(self): + arglist = [] + verifylist = None + self.assertRaises(tests_utils.ParserException, + self.check_parser, + self.cmd, + arglist, + verifylist) + class TestAgentList(TestAgent): diff --git a/openstackclient/tests/compute/v2/test_aggregate.py b/openstackclient/tests/compute/v2/test_aggregate.py index 58dd7755..3ebca35f 100644 --- a/openstackclient/tests/compute/v2/test_aggregate.py +++ b/openstackclient/tests/compute/v2/test_aggregate.py @@ -13,6 +13,12 @@ # under the License. # +import mock +from mock import call + +from osc_lib import exceptions +from osc_lib import utils + from openstackclient.compute.v2 import aggregate from openstackclient.tests.compute.v2 import fakes as compute_fakes from openstackclient.tests import utils as tests_utils @@ -135,25 +141,74 @@ class TestAggregateCreate(TestAggregate): class TestAggregateDelete(TestAggregate): + fake_ags = compute_fakes.FakeAggregate.create_aggregates(count=2) + def setUp(self): super(TestAggregateDelete, self).setUp() - self.aggregate_mock.get.return_value = self.fake_ag + self.aggregate_mock.get = ( + compute_fakes.FakeAggregate.get_aggregates(self.fake_ags)) self.cmd = aggregate.DeleteAggregate(self.app, None) def test_aggregate_delete(self): arglist = [ - 'ag1', + self.fake_ags[0].id ] verifylist = [ - ('aggregate', 'ag1'), + ('aggregate', [self.fake_ags[0].id]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.aggregate_mock.get.assert_called_once_with(parsed_args.aggregate) - self.aggregate_mock.delete.assert_called_once_with(self.fake_ag.id) + self.aggregate_mock.get.assert_called_once_with(self.fake_ags[0].id) + self.aggregate_mock.delete.assert_called_once_with(self.fake_ags[0].id) self.assertIsNone(result) + def test_delete_multiple_aggregates(self): + arglist = [] + for a in self.fake_ags: + arglist.append(a.id) + verifylist = [ + ('aggregate', arglist), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + calls = [] + for a in self.fake_ags: + calls.append(call(a.id)) + self.aggregate_mock.delete.assert_has_calls(calls) + self.assertIsNone(result) + + def test_delete_multiple_agggregates_with_exception(self): + arglist = [ + self.fake_ags[0].id, + 'unexist_aggregate', + ] + verifylist = [ + ('aggregate', arglist), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + find_mock_result = [self.fake_ags[0], exceptions.CommandError] + with mock.patch.object(utils, 'find_resource', + side_effect=find_mock_result) as find_mock: + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('1 of 2 aggregates failed to delete.', + str(e)) + + find_mock.assert_any_call(self.aggregate_mock, self.fake_ags[0].id) + find_mock.assert_any_call(self.aggregate_mock, 'unexist_aggregate') + + self.assertEqual(2, find_mock.call_count) + self.aggregate_mock.delete.assert_called_once_with( + self.fake_ags[0].id + ) + class TestAggregateList(TestAggregate): diff --git a/openstackclient/tests/compute/v2/test_console.py b/openstackclient/tests/compute/v2/test_console.py new file mode 100644 index 00000000..6be08126 --- /dev/null +++ b/openstackclient/tests/compute/v2/test_console.py @@ -0,0 +1,149 @@ +# Copyright 2016 Huawei, Inc. All rights reserved. +# +# 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 mock + +from openstackclient.compute.v2 import console +from openstackclient.tests.compute.v2 import fakes as compute_fakes + + +class TestConsole(compute_fakes.TestComputev2): + + def setUp(self): + super(TestConsole, self).setUp() + self.servers_mock = self.app.client_manager.compute.servers + self.servers_mock.reset_mock() + + +class TestConsoleUrlShow(TestConsole): + + def setUp(self): + super(TestConsoleUrlShow, self).setUp() + fake_console_data = {'remote_console': {'url': 'http://localhost', + 'protocol': 'fake_protocol', + 'type': 'fake_type'}} + methods = { + 'get_vnc_console': fake_console_data, + 'get_spice_console': fake_console_data, + 'get_serial_console': fake_console_data, + 'get_rdp_console': fake_console_data, + 'get_mks_console': fake_console_data, + } + self.fake_server = compute_fakes.FakeServer.create_one_server( + methods=methods) + self.servers_mock.get.return_value = self.fake_server + + self.columns = ( + 'protocol', + 'type', + 'url', + ) + self.data = ( + fake_console_data['remote_console']['protocol'], + fake_console_data['remote_console']['type'], + fake_console_data['remote_console']['url'] + ) + + self.cmd = console.ShowConsoleURL(self.app, None) + + def test_console_url_show_by_default(self): + arglist = [ + 'foo_vm', + ] + verifylist = [ + ('url_type', 'novnc'), + ('server', 'foo_vm'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + self.fake_server.get_vnc_console.assert_called_once_with('novnc') + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_console_url_show_with_novnc(self): + arglist = [ + '--novnc', + 'foo_vm', + ] + verifylist = [ + ('url_type', 'novnc'), + ('server', 'foo_vm'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + self.fake_server.get_vnc_console.assert_called_once_with('novnc') + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_console_url_show_with_xvpvnc(self): + arglist = [ + '--xvpvnc', + 'foo_vm', + ] + verifylist = [ + ('url_type', 'xvpvnc'), + ('server', 'foo_vm'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + self.fake_server.get_vnc_console.assert_called_once_with('xvpvnc') + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_console_url_show_with_spice(self): + arglist = [ + '--spice', + 'foo_vm', + ] + verifylist = [ + ('url_type', 'spice-html5'), + ('server', 'foo_vm'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + self.fake_server.get_spice_console.assert_called_once_with( + 'spice-html5') + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_console_url_show_compatible(self): + methods = { + 'get_vnc_console': {'console': {'url': 'http://localhost', + 'type': 'fake_type'}}, + } + old_fake_server = compute_fakes.FakeServer.create_one_server( + methods=methods) + old_columns = ( + 'type', + 'url', + ) + old_data = ( + methods['get_vnc_console']['console']['type'], + methods['get_vnc_console']['console']['url'] + ) + arglist = [ + 'foo_vm', + ] + verifylist = [ + ('url_type', 'novnc'), + ('server', 'foo_vm'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + with mock.patch.object(self.servers_mock, 'get', + return_value=old_fake_server): + columns, data = self.cmd.take_action(parsed_args) + old_fake_server.get_vnc_console.assert_called_once_with('novnc') + self.assertEqual(old_columns, columns) + self.assertEqual(old_data, data) diff --git a/openstackclient/tests/compute/v2/test_flavor.py b/openstackclient/tests/compute/v2/test_flavor.py index 434d5f92..da76b6d7 100644 --- a/openstackclient/tests/compute/v2/test_flavor.py +++ b/openstackclient/tests/compute/v2/test_flavor.py @@ -14,9 +14,12 @@ # import copy +import mock +from mock import call + +from osc_lib import exceptions +from osc_lib import utils -from openstackclient.common import exceptions -from openstackclient.common import utils from openstackclient.compute.v2 import flavor from openstackclient.tests.compute.v2 import fakes as compute_fakes from openstackclient.tests import fakes @@ -74,6 +77,12 @@ class TestFlavorCreate(TestFlavor): def setUp(self): super(TestFlavorCreate, self).setUp() + # Return a project + self.projects_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.PROJECT), + loaded=True, + ) self.flavors_mock.create.return_value = self.flavor self.cmd = flavor.CreateFlavor(self.app, None) @@ -160,6 +169,7 @@ class TestFlavorCreate(TestFlavor): '--vcpus', str(self.flavor.vcpus), '--rxtx-factor', str(self.flavor.rxtx_factor), '--private', + '--project', identity_fakes.project_id, ] verifylist = [ ('name', self.flavor.name), @@ -171,6 +181,7 @@ class TestFlavorCreate(TestFlavor): ('vcpus', self.flavor.vcpus), ('rxtx_factor', self.flavor.rxtx_factor), ('public', False), + ('project', identity_fakes.project_id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -187,10 +198,28 @@ class TestFlavorCreate(TestFlavor): ) columns, data = self.cmd.take_action(parsed_args) self.flavors_mock.create.assert_called_once_with(*args) - + self.flavor_access_mock.add_tenant_access.assert_called_with( + self.flavor.id, + identity_fakes.project_id, + ) self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) + def test_public_flavor_create_with_project(self): + arglist = [ + '--project', identity_fakes.project_id, + self.flavor.name, + ] + verifylist = [ + ('project', identity_fakes.project_id), + ('name', self.flavor.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.assertRaises(exceptions.CommandError, + self.cmd.take_action, + parsed_args) + def test_flavor_create_no_options(self): arglist = [] verifylist = None @@ -203,47 +232,73 @@ class TestFlavorCreate(TestFlavor): class TestFlavorDelete(TestFlavor): - flavor = compute_fakes.FakeFlavor.create_one_flavor() + flavors = compute_fakes.FakeFlavor.create_flavors(count=2) def setUp(self): super(TestFlavorDelete, self).setUp() - self.flavors_mock.get.return_value = self.flavor + self.flavors_mock.get = ( + compute_fakes.FakeFlavor.get_flavors(self.flavors)) self.flavors_mock.delete.return_value = None self.cmd = flavor.DeleteFlavor(self.app, None) def test_flavor_delete(self): arglist = [ - self.flavor.id + self.flavors[0].id ] verifylist = [ - ('flavor', self.flavor.id), + ('flavor', [self.flavors[0].id]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.flavors_mock.delete.assert_called_with(self.flavor.id) + self.flavors_mock.delete.assert_called_with(self.flavors[0].id) self.assertIsNone(result) - def test_flavor_delete_with_unexist_flavor(self): - self.flavors_mock.get.side_effect = exceptions.NotFound(None) - self.flavors_mock.find.side_effect = exceptions.NotFound(None) + def test_delete_multiple_flavors(self): + arglist = [] + for f in self.flavors: + arglist.append(f.id) + verifylist = [ + ('flavor', arglist), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + calls = [] + for f in self.flavors: + calls.append(call(f.id)) + self.flavors_mock.delete.assert_has_calls(calls) + self.assertIsNone(result) + def test_multi_flavors_delete_with_exception(self): arglist = [ - 'unexist_flavor' + self.flavors[0].id, + 'unexist_flavor', ] verifylist = [ - ('flavor', 'unexist_flavor'), + ('flavor', [self.flavors[0].id, 'unexist_flavor']) ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.assertRaises( - exceptions.CommandError, - self.cmd.take_action, - parsed_args) + find_mock_result = [self.flavors[0], exceptions.CommandError] + self.flavors_mock.get = ( + mock.MagicMock(side_effect=find_mock_result) + ) + self.flavors_mock.find.side_effect = exceptions.NotFound(None) + + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('1 of 2 flavors failed to delete.', str(e)) + + self.flavors_mock.get.assert_any_call(self.flavors[0].id) + self.flavors_mock.get.assert_any_call('unexist_flavor') + self.flavors_mock.delete.assert_called_once_with(self.flavors[0].id) class TestFlavorList(TestFlavor): @@ -468,6 +523,7 @@ class TestFlavorSet(TestFlavor): result = self.cmd.take_action(parsed_args) self.flavors_mock.find.assert_called_with(name=parsed_args.flavor, is_public=None) + self.flavor.set_keys.assert_called_with({'FOO': '"B A R"'}) self.assertIsNone(result) def test_flavor_set_project(self): @@ -482,12 +538,15 @@ class TestFlavorSet(TestFlavor): parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.assertIsNone(result) + self.flavors_mock.find.assert_called_with(name=parsed_args.flavor, + is_public=None) self.flavor_access_mock.add_tenant_access.assert_called_with( self.flavor.id, identity_fakes.project_id, ) + self.flavor.set_keys.assert_not_called() + self.assertIsNone(result) def test_flavor_set_no_project(self): arglist = [ @@ -495,7 +554,7 @@ class TestFlavorSet(TestFlavor): self.flavor.id, ] verifylist = [ - ('project', ''), + ('project', None), ('flavor', self.flavor.id), ] self.assertRaises(tests_utils.ParserException, self.check_parser, @@ -508,12 +567,8 @@ class TestFlavorSet(TestFlavor): verifylist = [ ('project', identity_fakes.project_id), ] - - self.assertRaises(tests_utils.ParserException, - self.check_parser, - self.cmd, - arglist, - verifylist) + self.assertRaises(tests_utils.ParserException, self.check_parser, + self.cmd, arglist, verifylist) def test_flavor_set_with_unexist_flavor(self): self.flavors_mock.get.side_effect = exceptions.NotFound(None) @@ -540,10 +595,13 @@ class TestFlavorSet(TestFlavor): verifylist = [ ('flavor', self.flavor.id), ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.assertRaises(exceptions.CommandError, self.cmd.take_action, - parsed_args) + result = self.cmd.take_action(parsed_args) + + self.flavors_mock.find.assert_called_with(name=parsed_args.flavor, + is_public=None) + self.flavor_access_mock.add_tenant_access.assert_not_called() + self.assertIsNone(result) class TestFlavorShow(TestFlavor): @@ -644,6 +702,8 @@ class TestFlavorUnset(TestFlavor): result = self.cmd.take_action(parsed_args) self.flavors_mock.find.assert_called_with(name=parsed_args.flavor, is_public=None) + self.flavor.unset_keys.assert_called_with(['property']) + self.flavor_access_mock.remove_tenant_access.assert_not_called() self.assertIsNone(result) def test_flavor_unset_project(self): @@ -660,24 +720,26 @@ class TestFlavorUnset(TestFlavor): result = self.cmd.take_action(parsed_args) self.assertIsNone(result) + self.flavors_mock.find.assert_called_with(name=parsed_args.flavor, + is_public=None) self.flavor_access_mock.remove_tenant_access.assert_called_with( self.flavor.id, identity_fakes.project_id, ) + self.flavor.unset_keys.assert_not_called() + self.assertIsNone(result) def test_flavor_unset_no_project(self): arglist = [ - '--project', '', + '--project', self.flavor.id, ] verifylist = [ - ('project', ''), + ('project', None), ('flavor', self.flavor.id), ] - - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.assertRaises(exceptions.CommandError, self.cmd.take_action, - parsed_args) + self.assertRaises(tests_utils.ParserException, self.check_parser, + self.cmd, arglist, verifylist) def test_flavor_unset_no_flavor(self): arglist = [ @@ -686,12 +748,8 @@ class TestFlavorUnset(TestFlavor): verifylist = [ ('project', identity_fakes.project_id), ] - - self.assertRaises(tests_utils.ParserException, - self.check_parser, - self.cmd, - arglist, - verifylist) + self.assertRaises(tests_utils.ParserException, self.check_parser, + self.cmd, arglist, verifylist) def test_flavor_unset_with_unexist_flavor(self): self.flavors_mock.get.side_effect = exceptions.NotFound(None) @@ -706,9 +764,7 @@ class TestFlavorUnset(TestFlavor): ('flavor', 'unexist_flavor'), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - self.assertRaises(exceptions.CommandError, - self.cmd.take_action, + self.assertRaises(exceptions.CommandError, self.cmd.take_action, parsed_args) def test_flavor_unset_nothing(self): @@ -718,7 +774,9 @@ class TestFlavorUnset(TestFlavor): verifylist = [ ('flavor', self.flavor.id), ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.assertRaises(exceptions.CommandError, self.cmd.take_action, - parsed_args) + + result = self.cmd.take_action(parsed_args) + self.assertIsNone(result) + + self.flavor_access_mock.remove_tenant_access.assert_not_called() diff --git a/openstackclient/tests/compute/v2/test_host.py b/openstackclient/tests/compute/v2/test_host.py index de537577..63ae1f6d 100644 --- a/openstackclient/tests/compute/v2/test_host.py +++ b/openstackclient/tests/compute/v2/test_host.py @@ -40,10 +40,10 @@ class TestHostSet(TestHost): def test_host_set_no_option(self): arglist = [ - str(self.host.id) + self.host.host ] verifylist = [ - ('host', str(self.host.id)) + ('host', self.host.host) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -52,18 +52,18 @@ class TestHostSet(TestHost): self.assertIsNone(result) body = {} - self.host_mock.update.assert_called_with(self.host.id, body) + self.host_mock.update.assert_called_with(self.host.host, body) def test_host_set(self): arglist = [ '--enable', '--disable-maintenance', - str(self.host.id) + self.host.host ] verifylist = [ ('enable', True), ('enable_maintenance', False), - ('host', str(self.host.id)) + ('host', self.host.host) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -71,5 +71,5 @@ class TestHostSet(TestHost): result = self.cmd.take_action(parsed_args) self.assertIsNone(result) - body = {'status': True, 'maintenance_mode': False} - self.host_mock.update.assert_called_with(self.host.id, body) + body = {'status': 'enable', 'maintenance_mode': 'disable'} + self.host_mock.update.assert_called_with(self.host.host, body) diff --git a/openstackclient/tests/compute/v2/test_hypervisor.py b/openstackclient/tests/compute/v2/test_hypervisor.py index 8d717ba7..ee0f40ed 100644 --- a/openstackclient/tests/compute/v2/test_hypervisor.py +++ b/openstackclient/tests/compute/v2/test_hypervisor.py @@ -15,7 +15,8 @@ import copy -from openstackclient.common import exceptions +from osc_lib import exceptions + from openstackclient.compute.v2 import hypervisor from openstackclient.tests.compute.v2 import fakes as compute_fakes from openstackclient.tests import fakes diff --git a/openstackclient/tests/compute/v2/test_keypair.py b/openstackclient/tests/compute/v2/test_keypair.py index a50a5323..25949e31 100644 --- a/openstackclient/tests/compute/v2/test_keypair.py +++ b/openstackclient/tests/compute/v2/test_keypair.py @@ -14,6 +14,10 @@ # import mock +from mock import call + +from osc_lib import exceptions +from osc_lib import utils from openstackclient.compute.v2 import keypair from openstackclient.tests.compute.v2 import fakes as compute_fakes @@ -114,22 +118,23 @@ class TestKeypairCreate(TestKeypair): class TestKeypairDelete(TestKeypair): - keypair = compute_fakes.FakeKeypair.create_one_keypair() + keypairs = compute_fakes.FakeKeypair.create_keypairs(count=2) def setUp(self): super(TestKeypairDelete, self).setUp() - self.keypairs_mock.get.return_value = self.keypair + self.keypairs_mock.get = compute_fakes.FakeKeypair.get_keypairs( + self.keypairs) self.keypairs_mock.delete.return_value = None self.cmd = keypair.DeleteKeypair(self.app, None) def test_keypair_delete(self): arglist = [ - self.keypair.name + self.keypairs[0].name ] verifylist = [ - ('name', self.keypair.name), + ('name', [self.keypairs[0].name]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -137,7 +142,54 @@ class TestKeypairDelete(TestKeypair): ret = self.cmd.take_action(parsed_args) self.assertIsNone(ret) - self.keypairs_mock.delete.assert_called_with(self.keypair.name) + self.keypairs_mock.delete.assert_called_with(self.keypairs[0].name) + + def test_delete_multiple_keypairs(self): + arglist = [] + for k in self.keypairs: + arglist.append(k.name) + verifylist = [ + ('name', arglist), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + calls = [] + for k in self.keypairs: + calls.append(call(k.name)) + self.keypairs_mock.delete.assert_has_calls(calls) + self.assertIsNone(result) + + def test_delete_multiple_keypairs_with_exception(self): + arglist = [ + self.keypairs[0].name, + 'unexist_keypair', + ] + verifylist = [ + ('name', arglist), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + find_mock_result = [self.keypairs[0], exceptions.CommandError] + with mock.patch.object(utils, 'find_resource', + side_effect=find_mock_result) as find_mock: + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('1 of 2 public keys failed to delete.', + str(e)) + + find_mock.assert_any_call( + self.keypairs_mock, self.keypairs[0].name) + find_mock.assert_any_call(self.keypairs_mock, 'unexist_keypair') + + self.assertEqual(2, find_mock.call_count) + self.keypairs_mock.delete.assert_called_once_with( + self.keypairs[0].name + ) class TestKeypairList(TestKeypair): diff --git a/openstackclient/tests/compute/v2/test_server.py b/openstackclient/tests/compute/v2/test_server.py index d642e6dd..0f155601 100644 --- a/openstackclient/tests/compute/v2/test_server.py +++ b/openstackclient/tests/compute/v2/test_server.py @@ -14,10 +14,11 @@ # import getpass import mock - from mock import call -from openstackclient.common import exceptions -from openstackclient.common import utils as common_utils + +from osc_lib import exceptions +from osc_lib import utils as common_utils + from openstackclient.compute.v2 import server from openstackclient.tests.compute.v2 import fakes as compute_fakes from openstackclient.tests.image.v2 import fakes as image_fakes @@ -510,150 +511,6 @@ class TestServerDumpCreate(TestServer): self.run_method_with_servers('trigger_crash_dump', 3) -class TestServerImageCreate(TestServer): - - columns = ( - 'id', - 'name', - 'owner', - 'protected', - 'tags', - 'visibility', - ) - - def datalist(self): - datalist = ( - self.image.id, - self.image.name, - self.image.owner, - self.image.protected, - self.image.tags, - self.image.visibility, - ) - return datalist - - def setUp(self): - super(TestServerImageCreate, self).setUp() - - self.server = compute_fakes.FakeServer.create_one_server() - - # This is the return value for utils.find_resource() - self.servers_mock.get.return_value = self.server - - self.image = image_fakes.FakeImage.create_one_image() - self.images_mock.get.return_value = self.image - self.servers_mock.create_image.return_value = self.image.id - - # Get the command object to test - self.cmd = server.CreateServerImage(self.app, None) - - def test_server_image_create_no_options(self): - arglist = [ - self.server.id, - ] - verifylist = [ - ('server', self.server.id), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - # In base command class ShowOne in cliff, abstract method take_action() - # returns a two-part tuple with a tuple of column names and a tuple of - # data to be shown. - columns, data = self.cmd.take_action(parsed_args) - - # ServerManager.create_image(server, image_name, metadata=) - self.servers_mock.create_image.assert_called_with( - self.servers_mock.get.return_value, - self.server.name, - ) - - self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist(), data) - - def test_server_image_create_name(self): - arglist = [ - '--name', 'img-nam', - self.server.id, - ] - verifylist = [ - ('name', 'img-nam'), - ('server', self.server.id), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - # In base command class ShowOne in cliff, abstract method take_action() - # returns a two-part tuple with a tuple of column names and a tuple of - # data to be shown. - columns, data = self.cmd.take_action(parsed_args) - - # ServerManager.create_image(server, image_name, metadata=) - self.servers_mock.create_image.assert_called_with( - self.servers_mock.get.return_value, - 'img-nam', - ) - - self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist(), data) - - @mock.patch.object(common_utils, 'wait_for_status', return_value=False) - def test_server_create_image_with_wait_fails(self, mock_wait_for_status): - arglist = [ - '--wait', - self.server.id, - ] - verifylist = [ - ('wait', True), - ('server', self.server.id), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - self.assertRaises(SystemExit, self.cmd.take_action, parsed_args) - - mock_wait_for_status.assert_called_once_with( - self.images_mock.get, - self.image.id, - callback=server._show_progress - ) - - # ServerManager.create_image(server, image_name, metadata=) - self.servers_mock.create_image.assert_called_with( - self.servers_mock.get.return_value, - self.server.name, - ) - - @mock.patch.object(common_utils, 'wait_for_status', return_value=True) - def test_server_create_image_with_wait_ok(self, mock_wait_for_status): - arglist = [ - '--wait', - self.server.id, - ] - verifylist = [ - ('wait', True), - ('server', self.server.id), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - # In base command class ShowOne in cliff, abstract method take_action() - # returns a two-part tuple with a tuple of column names and a tuple of - # data to be shown. - columns, data = self.cmd.take_action(parsed_args) - - # ServerManager.create_image(server, image_name, metadata=) - self.servers_mock.create_image.assert_called_with( - self.servers_mock.get.return_value, - self.server.name, - ) - - mock_wait_for_status.assert_called_once_with( - self.images_mock.get, - self.image.id, - callback=server._show_progress - ) - - self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist(), data) - - class TestServerList(TestServer): # Columns to be listed up. @@ -1691,7 +1548,7 @@ class TestServerGeneral(TestServer): (data_1, data_2, networks_format)) self.assertIn(networks_format, (data_1, data_2), msg) - @mock.patch('openstackclient.common.utils.find_resource') + @mock.patch('osc_lib.utils.find_resource') def test_prep_server_detail(self, find_resource): # Setup mock method return value. utils.find_resource() will be called # three times in _prep_server_detail(): diff --git a/openstackclient/tests/compute/v2/test_server_backup.py b/openstackclient/tests/compute/v2/test_server_backup.py index b35f9f52..b6802ff0 100644 --- a/openstackclient/tests/compute/v2/test_server_backup.py +++ b/openstackclient/tests/compute/v2/test_server_backup.py @@ -13,8 +13,9 @@ import mock -from openstackclient.common import exceptions -from openstackclient.common import utils as common_utils +from osc_lib import exceptions +from osc_lib import utils as common_utils + from openstackclient.compute.v2 import server_backup from openstackclient.tests.compute.v2 import fakes as compute_fakes from openstackclient.tests.image.v2 import fakes as image_fakes diff --git a/openstackclient/tests/compute/v2/test_server_group.py b/openstackclient/tests/compute/v2/test_server_group.py index 70ff23f9..bd5f8471 100644 --- a/openstackclient/tests/compute/v2/test_server_group.py +++ b/openstackclient/tests/compute/v2/test_server_group.py @@ -15,8 +15,9 @@ import mock -from openstackclient.common import exceptions -from openstackclient.common import utils +from osc_lib import exceptions +from osc_lib import utils + from openstackclient.compute.v2 import server_group from openstackclient.tests.compute.v2 import fakes as compute_fakes from openstackclient.tests import utils as tests_utils diff --git a/openstackclient/tests/compute/v2/test_server_image.py b/openstackclient/tests/compute/v2/test_server_image.py new file mode 100644 index 00000000..8a8bd9bc --- /dev/null +++ b/openstackclient/tests/compute/v2/test_server_image.py @@ -0,0 +1,228 @@ +# 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 mock + +from osc_lib import exceptions +from osc_lib import utils as common_utils + +from openstackclient.compute.v2 import server_image +from openstackclient.tests.compute.v2 import fakes as compute_fakes +from openstackclient.tests.image.v2 import fakes as image_fakes + + +class TestServerImage(compute_fakes.TestComputev2): + + def setUp(self): + super(TestServerImage, self).setUp() + + # Get a shortcut to the compute client ServerManager Mock + self.servers_mock = self.app.client_manager.compute.servers + self.servers_mock.reset_mock() + + # Get a shortcut to the image client ImageManager Mock + self.images_mock = self.app.client_manager.image.images + self.images_mock.reset_mock() + + # Set object attributes to be tested. Could be overwriten in subclass. + self.attrs = {} + + # Set object methods to be tested. Could be overwriten in subclass. + self.methods = {} + + def setup_servers_mock(self, count): + servers = compute_fakes.FakeServer.create_servers( + attrs=self.attrs, + methods=self.methods, + count=count, + ) + + # This is the return value for utils.find_resource() + self.servers_mock.get = compute_fakes.FakeServer.get_servers( + servers, + 0, + ) + return servers + + +class TestServerImageCreate(TestServerImage): + + def image_columns(self, image): + columnlist = tuple(sorted(image.keys())) + return columnlist + + def image_data(self, image): + datalist = ( + image['id'], + image['name'], + image['owner'], + image['protected'], + 'active', + common_utils.format_list(image.get('tags')), + image['visibility'], + ) + return datalist + + def setUp(self): + super(TestServerImageCreate, self).setUp() + + # Get the command object to test + self.cmd = server_image.CreateServerImage(self.app, None) + + self.methods = { + 'create_image': None, + } + + def setup_images_mock(self, count, servers=None): + if servers: + images = image_fakes.FakeImage.create_images( + attrs={ + 'name': servers[0].name, + 'status': 'active', + }, + count=count, + ) + else: + images = image_fakes.FakeImage.create_images( + attrs={ + 'status': 'active', + }, + count=count, + ) + + self.images_mock.get = mock.MagicMock(side_effect=images) + self.servers_mock.create_image = mock.MagicMock( + return_value=images[0].id, + ) + return images + + def test_server_image_create_defaults(self): + servers = self.setup_servers_mock(count=1) + images = self.setup_images_mock(count=1, servers=servers) + + arglist = [ + servers[0].id, + ] + verifylist = [ + ('server', servers[0].id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # In base command class ShowOne in cliff, abstract method take_action() + # returns a two-part tuple with a tuple of column names and a tuple of + # data to be shown. + columns, data = self.cmd.take_action(parsed_args) + + # ServerManager.create_image(server, image_name, metadata=) + self.servers_mock.create_image.assert_called_with( + servers[0].id, + servers[0].name, + ) + + self.assertEqual(self.image_columns(images[0]), columns) + self.assertEqual(self.image_data(images[0]), data) + + def test_server_image_create_options(self): + servers = self.setup_servers_mock(count=1) + images = self.setup_images_mock(count=1, servers=servers) + + arglist = [ + '--name', 'img-nam', + servers[0].id, + ] + verifylist = [ + ('name', 'img-nam'), + ('server', servers[0].id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # In base command class ShowOne in cliff, abstract method take_action() + # returns a two-part tuple with a tuple of column names and a tuple of + # data to be shown. + columns, data = self.cmd.take_action(parsed_args) + + # ServerManager.create_image(server, image_name, metadata=) + self.servers_mock.create_image.assert_called_with( + servers[0].id, + 'img-nam', + ) + + self.assertEqual(self.image_columns(images[0]), columns) + self.assertEqual(self.image_data(images[0]), data) + + @mock.patch.object(common_utils, 'wait_for_status', return_value=False) + def test_server_create_image_wait_fail(self, mock_wait_for_status): + servers = self.setup_servers_mock(count=1) + images = self.setup_images_mock(count=1, servers=servers) + + arglist = [ + '--wait', + servers[0].id, + ] + verifylist = [ + ('wait', True), + ('server', servers[0].id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args, + ) + + # ServerManager.create_image(server, image_name, metadata=) + self.servers_mock.create_image.assert_called_with( + servers[0].id, + servers[0].name, + ) + + mock_wait_for_status.assert_called_once_with( + self.images_mock.get, + images[0].id, + callback=mock.ANY + ) + + @mock.patch.object(common_utils, 'wait_for_status', return_value=True) + def test_server_create_image_wait_ok(self, mock_wait_for_status): + servers = self.setup_servers_mock(count=1) + images = self.setup_images_mock(count=1, servers=servers) + + arglist = [ + '--wait', + servers[0].id, + ] + verifylist = [ + ('wait', True), + ('server', servers[0].id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # In base command class ShowOne in cliff, abstract method take_action() + # returns a two-part tuple with a tuple of column names and a tuple of + # data to be shown. + columns, data = self.cmd.take_action(parsed_args) + + # ServerManager.create_image(server, image_name, metadata=) + self.servers_mock.create_image.assert_called_with( + servers[0].id, + servers[0].name, + ) + + mock_wait_for_status.assert_called_once_with( + self.images_mock.get, + images[0].id, + callback=mock.ANY + ) + + self.assertEqual(self.image_columns(images[0]), columns) + self.assertEqual(self.image_data(images[0]), data) diff --git a/openstackclient/tests/compute/v2/test_service.py b/openstackclient/tests/compute/v2/test_service.py index cf534978..1599f466 100644 --- a/openstackclient/tests/compute/v2/test_service.py +++ b/openstackclient/tests/compute/v2/test_service.py @@ -13,7 +13,11 @@ # under the License. # -from openstackclient.common import exceptions +import mock +from mock import call + +from osc_lib import exceptions + from openstackclient.compute.v2 import service from openstackclient.tests.compute.v2 import fakes as compute_fakes @@ -30,40 +34,106 @@ class TestService(compute_fakes.TestComputev2): class TestServiceDelete(TestService): + services = compute_fakes.FakeService.create_services(count=2) + def setUp(self): super(TestServiceDelete, self).setUp() - self.service = compute_fakes.FakeService.create_one_service() - self.service_mock.delete.return_value = None # Get the command object to test self.cmd = service.DeleteService(self.app, None) - def test_service_delete_no_options(self): + def test_service_delete(self): arglist = [ - self.service.binary, + self.services[0].binary, ] verifylist = [ - ('service', self.service.binary), + ('service', [self.services[0].binary]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) self.service_mock.delete.assert_called_with( - self.service.binary, + self.services[0].binary, ) self.assertIsNone(result) + def test_multi_services_delete(self): + arglist = [] + for s in self.services: + arglist.append(s.binary) + verifylist = [ + ('service', arglist), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + calls = [] + for s in self.services: + calls.append(call(s.binary)) + self.service_mock.delete.assert_has_calls(calls) + self.assertIsNone(result) + + def test_multi_services_delete_with_exception(self): + arglist = [ + self.services[0].binary, + 'unexist_service', + ] + verifylist = [ + ('service', arglist) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + delete_mock_result = [None, exceptions.CommandError] + self.service_mock.delete = ( + mock.MagicMock(side_effect=delete_mock_result) + ) + + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual( + '1 of 2 compute services failed to delete.', str(e)) + + self.service_mock.delete.assert_any_call(self.services[0].binary) + self.service_mock.delete.assert_any_call('unexist_service') + class TestServiceList(TestService): + service = compute_fakes.FakeService.create_one_service() + + columns = ( + 'ID', + 'Binary', + 'Host', + 'Zone', + 'Status', + 'State', + 'Updated At', + ) + columns_long = columns + ( + 'Disabled Reason', + ) + + data = [( + service.id, + service.binary, + service.host, + service.zone, + service.status, + service.state, + service.updated_at, + )] + data_long = [data[0] + (service.disabled_reason, )] + def setUp(self): super(TestServiceList, self).setUp() - self.service = compute_fakes.FakeService.create_one_service() - self.service_mock.list.return_value = [self.service] # Get the command object to test @@ -90,8 +160,8 @@ class TestServiceList(TestService): self.service.binary, ) - self.assertNotIn("Disabled Reason", columns) - self.assertNotIn(self.service.disabled_reason, list(data)[0]) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) def test_service_list_with_long_option(self): arglist = [ @@ -111,8 +181,13 @@ class TestServiceList(TestService): # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) - self.assertIn("Disabled Reason", columns) - self.assertIn(self.service.disabled_reason, list(data)[0]) + self.service_mock.list.assert_called_with( + self.service.host, + self.service.binary, + ) + + self.assertEqual(self.columns_long, columns) + self.assertEqual(self.data_long, list(data)) class TestServiceSet(TestService): @@ -224,8 +299,12 @@ class TestServiceSet(TestService): ('service', self.service.binary), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.assertRaises(exceptions.CommandError, self.cmd.take_action, - parsed_args) + try: + self.cmd.take_action(parsed_args) + self.fail("CommandError should be raised.") + except exceptions.CommandError as e: + self.assertEqual("Cannot specify option --disable-reason without " + "--disable specified.", str(e)) def test_service_set_enable_with_disable_reason(self): reason = 'earthquake' @@ -242,5 +321,90 @@ class TestServiceSet(TestService): ('service', self.service.binary), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.assertRaises(exceptions.CommandError, self.cmd.take_action, - parsed_args) + try: + self.cmd.take_action(parsed_args) + self.fail("CommandError should be raised.") + except exceptions.CommandError as e: + self.assertEqual("Cannot specify option --disable-reason without " + "--disable specified.", str(e)) + + def test_service_set_state_up(self): + arglist = [ + '--up', + self.service.host, + self.service.binary, + ] + verifylist = [ + ('up', True), + ('host', self.service.host), + ('service', self.service.binary), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + self.service_mock.force_down.assert_called_once_with( + self.service.host, self.service.binary, force_down=False) + self.assertNotCalled(self.service_mock.enable) + self.assertNotCalled(self.service_mock.disable) + self.assertIsNone(result) + + def test_service_set_state_down(self): + arglist = [ + '--down', + self.service.host, + self.service.binary, + ] + verifylist = [ + ('down', True), + ('host', self.service.host), + ('service', self.service.binary), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + self.service_mock.force_down.assert_called_once_with( + self.service.host, self.service.binary, force_down=True) + self.assertNotCalled(self.service_mock.enable) + self.assertNotCalled(self.service_mock.disable) + self.assertIsNone(result) + + def test_service_set_enable_and_state_down(self): + arglist = [ + '--enable', + '--down', + self.service.host, + self.service.binary, + ] + verifylist = [ + ('enable', True), + ('down', True), + ('host', self.service.host), + ('service', self.service.binary), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + self.service_mock.enable.assert_called_once_with( + self.service.host, self.service.binary) + self.service_mock.force_down.assert_called_once_with( + self.service.host, self.service.binary, force_down=True) + self.assertIsNone(result) + + def test_service_set_enable_and_state_down_with_exception(self): + arglist = [ + '--enable', + '--down', + self.service.host, + self.service.binary, + ] + verifylist = [ + ('enable', True), + ('down', True), + ('host', self.service.host), + ('service', self.service.binary), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + with mock.patch.object(self.service_mock, 'enable', + side_effect=Exception()): + self.assertRaises(exceptions.CommandError, + self.cmd.take_action, parsed_args) + self.service_mock.force_down.assert_called_once_with( + self.service.host, self.service.binary, force_down=True) diff --git a/openstackclient/tests/fakes.py b/openstackclient/tests/fakes.py index ad4705a4..d0cab019 100644 --- a/openstackclient/tests/fakes.py +++ b/openstackclient/tests/fakes.py @@ -50,6 +50,21 @@ TEST_RESPONSE_DICT_V3.set_project_scope() TEST_VERSIONS = fixture.DiscoveryList(href=AUTH_URL) +def to_unicode_dict(catalog_dict): + """Converts dict to unicode dict + + """ + if isinstance(catalog_dict, dict): + return {to_unicode_dict(key): to_unicode_dict(value) + for key, value in catalog_dict.items()} + elif isinstance(catalog_dict, list): + return [to_unicode_dict(element) for element in catalog_dict] + elif isinstance(catalog_dict, str): + return catalog_dict + u"" + else: + return catalog_dict + + class FakeStdout(object): def __init__(self): diff --git a/openstackclient/tests/identity/v2_0/fakes.py b/openstackclient/tests/identity/v2_0/fakes.py index b37bd9da..662d56b6 100644 --- a/openstackclient/tests/identity/v2_0/fakes.py +++ b/openstackclient/tests/identity/v2_0/fakes.py @@ -13,7 +13,12 @@ # under the License. # +import copy import mock +import uuid + +from keystoneauth1 import access +from keystoneauth1 import fixture from openstackclient.tests import fakes from openstackclient.tests import utils @@ -71,19 +76,19 @@ USER = { } token_expires = '2014-01-01T00:00:00Z' -token_id = 'tttttttt-tttt-tttt-tttt-tttttttttttt' +token_id = 'token-id-' + uuid.uuid4().hex TOKEN = { 'expires': token_expires, 'id': token_id, - 'tenant_id': project_id, - 'user_id': user_id, + 'tenant_id': 'project-id', + 'user_id': 'user-id', } UNSCOPED_TOKEN = { 'expires': token_expires, 'id': token_id, - 'user_id': user_id, + 'user_id': 'user-id', } endpoint_name = service_name @@ -106,25 +111,42 @@ ENDPOINT = { 'service_id': endpoint_service_id, } -extension_name = 'OpenStack Keystone User CRUD' -extension_namespace = 'http://docs.openstack.org/identity/'\ - 'api/ext/OS-KSCRUD/v1.0' -extension_description = 'OpenStack extensions to Keystone v2.0 API'\ - ' enabling User Operations.' -extension_updated = '2013-07-07T12:00:0-00:00' -extension_alias = 'OS-KSCRUD' -extension_links = '[{"href":'\ - '"https://github.com/openstack/identity-api", "type":'\ - ' "text/html", "rel": "describedby"}]' - -EXTENSION = { - 'name': extension_name, - 'namespace': extension_namespace, - 'description': extension_description, - 'updated': extension_updated, - 'alias': extension_alias, - 'links': extension_links, -} + +def fake_auth_ref(fake_token, fake_service=None): + """Create an auth_ref using keystoneauth's fixtures""" + token_copy = copy.deepcopy(fake_token) + token_copy['token_id'] = token_copy.pop('id') + token = fixture.V2Token(**token_copy) + # An auth_ref is actually an access info object + auth_ref = access.create(body=token) + + # Create a service catalog + if fake_service: + service = token.add_service( + fake_service.type, + fake_service.name, + ) + # TODO(dtroyer): Add an 'id' element to KSA's _Service fixure + service['id'] = fake_service.id + for e in fake_service.endpoints: + # KSA's _Service fixture copies publicURL to internalURL and + # adminURL if they do not exist. Soooo helpful... + internal = e.get('internalURL', None) + admin = e.get('adminURL', None) + region = e.get('region_id') or e.get('region', '<none>') + endpoint = service.add_endpoint( + public=e['publicURL'], + internal=internal, + admin=admin, + region=region, + ) + # ...so undo that helpfulness + if not internal: + endpoint['internalURL'] = None + if not admin: + endpoint['adminURL'] = None + + return auth_ref class FakeIdentityv2Client(object): @@ -166,3 +188,323 @@ class TestIdentityv2(utils.TestCommand): endpoint=fakes.AUTH_URL, token=fakes.AUTH_TOKEN, ) + + +class FakeExtension(object): + """Fake one or more extension.""" + + @staticmethod + def create_one_extension(attrs=None): + """Create a fake extension. + + :param Dictionary attrs: + A dictionary with all attributes + :return: + A FakeResource object with name, namespace, etc. + """ + attrs = attrs or {} + + # Set default attributes. + extension_info = { + 'name': 'name-' + uuid.uuid4().hex, + 'namespace': ('http://docs.openstack.org/identity/' + 'api/ext/OS-KSCRUD/v1.0'), + 'description': 'description-' + uuid.uuid4().hex, + 'updated': '2013-07-07T12:00:0-00:00', + 'alias': 'OS-KSCRUD', + 'links': ('[{"href":' + '"https://github.com/openstack/identity-api", "type":' + ' "text/html", "rel": "describedby"}]') + } + + # Overwrite default attributes. + extension_info.update(attrs) + + extension = fakes.FakeResource( + info=copy.deepcopy(extension_info), + loaded=True) + return extension + + +class FakeCatalog(object): + """Fake one or more catalog.""" + + @staticmethod + def create_catalog(attrs=None): + """Create a fake catalog. + + :param Dictionary attrs: + A dictionary with all attributes + :return: + A FakeResource object with id, name, type and so on. + """ + attrs = attrs or {} + + # Set default attributes. + catalog_info = { + 'id': 'service-id-' + uuid.uuid4().hex, + 'type': 'compute', + 'name': 'supernova', + 'endpoints': [ + { + 'region': 'one', + 'publicURL': 'https://public.one.example.com', + 'internalURL': 'https://internal.one.example.com', + 'adminURL': 'https://admin.one.example.com', + }, + { + 'region': 'two', + 'publicURL': 'https://public.two.example.com', + 'internalURL': 'https://internal.two.example.com', + 'adminURL': 'https://admin.two.example.com', + }, + { + 'region': None, + 'publicURL': 'https://public.none.example.com', + 'internalURL': 'https://internal.none.example.com', + 'adminURL': 'https://admin.none.example.com', + }, + ], + } + # Overwrite default attributes. + catalog_info.update(attrs) + + catalog = fakes.FakeResource( + info=copy.deepcopy(catalog_info), + loaded=True) + + return catalog + + +class FakeProject(object): + """Fake one or more project.""" + + @staticmethod + def create_one_project(attrs=None): + """Create a fake project. + + :param Dictionary attrs: + A dictionary with all attributes + :return: + A FakeResource object, with id, name, and so on + """ + + attrs = attrs or {} + + # set default attributes. + project_info = { + 'id': 'project-id-' + uuid.uuid4().hex, + 'name': 'project-name-' + uuid.uuid4().hex, + 'description': 'project_description', + 'enabled': True, + } + project_info.update(attrs) + + project = fakes.FakeResource(info=copy.deepcopy(project_info), + loaded=True) + return project + + @staticmethod + def create_projects(attrs=None, count=2): + """Create multiple fake projects. + + :param Dictionary attrs: + A dictionary with all attributes + :param int count: + The number of projects to fake + :return: + A list of FakeResource objects faking the projects + """ + projects = [] + for i in range(0, count): + projects.append(FakeProject.create_one_project(attrs)) + + return projects + + +class FakeEndpoint(object): + """Fake one or more endpoint.""" + + @staticmethod + def create_one_endpoint(attrs=None): + """Create a fake agent. + + :param Dictionary attrs: + A dictionary with all attributes + :return: + A FakeResource object, with id, name, region, and so on + """ + + attrs = attrs or {} + + # set default attributes. + endpoint_info = { + 'service_name': 'service-name-' + uuid.uuid4().hex, + 'adminurl': 'http://endpoint_adminurl', + 'region': 'endpoint_region', + 'internalurl': 'http://endpoint_internalurl', + 'service_type': 'service_type', + 'id': 'endpoint-id-' + uuid.uuid4().hex, + 'publicurl': 'http://endpoint_publicurl', + 'service_id': 'service-name-' + uuid.uuid4().hex, + + } + endpoint_info.update(attrs) + + endpoint = fakes.FakeResource(info=copy.deepcopy(endpoint_info), + loaded=True) + return endpoint + + @staticmethod + def create_endpoints(attrs=None, count=2): + """Create multiple fake endpoints. + + :param Dictionary attrs: + A dictionary with all attributes + :param int count: + The number of endpoints to fake + :return: + A list of FakeResource objects faking the endpoints + """ + endpoints = [] + for i in range(0, count): + endpoints.append(FakeEndpoint.create_one_endpoint(attrs)) + + return endpoints + + +class FakeService(object): + """Fake one or more service.""" + + @staticmethod + def create_one_service(attrs=None): + """Create a fake service. + + :param Dictionary attrs: + A dictionary with all attributes + :return: + A FakeResource object, with id, name, type, and so on + """ + + attrs = attrs or {} + + # set default attributes. + service_info = { + 'id': 'service-id-' + uuid.uuid4().hex, + 'name': 'service-name-' + uuid.uuid4().hex, + 'description': 'service_description', + 'type': 'service_type', + + } + service_info.update(attrs) + + service = fakes.FakeResource(info=copy.deepcopy(service_info), + loaded=True) + return service + + @staticmethod + def create_services(attrs=None, count=2): + """Create multiple fake services. + + :param Dictionary attrs: + A dictionary with all attributes + :param int count: + The number of services to fake + :return: + A list of FakeResource objects faking the services + """ + services = [] + for i in range(0, count): + services.append(FakeService.create_one_service(attrs)) + + return services + + +class FakeRole(object): + """Fake one or more role.""" + + @staticmethod + def create_one_role(attrs=None): + """Create a fake role. + + :param Dictionary attrs: + A dictionary with all attributes + :return: + A FakeResource object, with id, name, and so on + """ + + attrs = attrs or {} + + # set default attributes. + role_info = { + 'id': 'role-id' + uuid.uuid4().hex, + 'name': 'role-name' + uuid.uuid4().hex, + } + role_info.update(attrs) + + role = fakes.FakeResource(info=copy.deepcopy(role_info), + loaded=True) + return role + + @staticmethod + def create_roles(attrs=None, count=2): + """Create multiple fake roles. + + :param Dictionary attrs: + A dictionary with all attributes + :param int count: + The number of roles to fake + :return: + A list of FakeResource objects faking the roles + """ + roles = [] + for i in range(0, count): + roles.append(FakeRole.create_one_role(attrs)) + + return roles + + +class FakeUser(object): + """Fake one or more user.""" + + @staticmethod + def create_one_user(attrs=None): + """Create a fake user. + + :param Dictionary attrs: + A dictionary with all attributes + :return: + A FakeResource object, with id, name, and so on + """ + attrs = attrs or {} + + # set default attributes. + user_info = { + 'id': 'user-id-' + uuid.uuid4().hex, + 'name': 'user-name-' + uuid.uuid4().hex, + 'tenantId': 'project-id-' + uuid.uuid4().hex, + 'email': 'admin@openstack.org', + 'enabled': True, + } + user_info.update(attrs) + + user = fakes.FakeResource(info=copy.deepcopy(user_info), + loaded=True) + return user + + @staticmethod + def create_users(attrs=None, count=2): + """Create multiple fake users. + + :param Dictionary attrs: + A dictionary with all attributes + :param int count: + The number of users to fake + :return: + A list of FakeResource objects faking the users + """ + users = [] + for i in range(0, count): + users.append(FakeUser.create_one_user(attrs)) + + return users diff --git a/openstackclient/tests/identity/v2_0/test_catalog.py b/openstackclient/tests/identity/v2_0/test_catalog.py index d9ae6a80..487d8f31 100644 --- a/openstackclient/tests/identity/v2_0/test_catalog.py +++ b/openstackclient/tests/identity/v2_0/test_catalog.py @@ -14,43 +14,20 @@ import mock from openstackclient.identity.v2_0 import catalog +from openstackclient.tests.identity.v2_0 import fakes as identity_fakes from openstackclient.tests import utils class TestCatalog(utils.TestCommand): - fake_service = { - 'id': 'qwertyuiop', - 'type': 'compute', - 'name': 'supernova', - 'endpoints': [ - { - 'region': 'one', - 'publicURL': 'https://public.one.example.com', - 'internalURL': 'https://internal.one.example.com', - 'adminURL': 'https://admin.one.example.com', - }, - { - 'region': 'two', - 'publicURL': 'https://public.two.example.com', - 'internalURL': 'https://internal.two.example.com', - 'adminURL': 'https://admin.two.example.com', - }, - { - 'region': None, - 'publicURL': 'https://public.none.example.com', - 'internalURL': 'https://internal.none.example.com', - 'adminURL': 'https://admin.none.example.com', - }, - ], - } + service_catalog = identity_fakes.FakeCatalog.create_catalog() def setUp(self): super(TestCatalog, self).setUp() self.sc_mock = mock.MagicMock() - self.sc_mock.service_catalog.get_data.return_value = [ - self.fake_service, + self.sc_mock.service_catalog.catalog.return_value = [ + self.service_catalog, ] self.auth_mock = mock.MagicMock() @@ -74,6 +51,13 @@ class TestCatalogList(TestCatalog): self.cmd = catalog.ListCatalog(self.app, None) def test_catalog_list(self): + auth_ref = identity_fakes.fake_auth_ref( + identity_fakes.TOKEN, + fake_service=self.service_catalog, + ) + self.ar_mock = mock.PropertyMock(return_value=auth_ref) + type(self.app.client_manager).auth_ref = self.ar_mock + arglist = [] verifylist = [] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -82,7 +66,6 @@ class TestCatalogList(TestCatalog): # returns a tuple containing the column names and an iterable # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) - self.sc_mock.service_catalog.get_data.assert_called_with() self.assertEqual(self.columns, columns) datalist = (( @@ -101,7 +84,7 @@ class TestCatalogList(TestCatalog): self.assertEqual(datalist, tuple(data)) def test_catalog_list_with_endpoint_url(self): - fake_service = { + attr = { 'id': 'qwertyuiop', 'type': 'compute', 'name': 'supernova', @@ -117,9 +100,13 @@ class TestCatalogList(TestCatalog): }, ], } - self.sc_mock.service_catalog.get_data.return_value = [ - fake_service, - ] + service_catalog = identity_fakes.FakeCatalog.create_catalog(attr) + auth_ref = identity_fakes.fake_auth_ref( + identity_fakes.TOKEN, + fake_service=service_catalog, + ) + self.ar_mock = mock.PropertyMock(return_value=auth_ref) + type(self.app.client_manager).auth_ref = self.ar_mock arglist = [] verifylist = [] @@ -129,7 +116,6 @@ class TestCatalogList(TestCatalog): # returns a tuple containing the column names and an iterable # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) - self.sc_mock.service_catalog.get_data.assert_called_with() self.assertEqual(self.columns, columns) datalist = (( @@ -151,6 +137,13 @@ class TestCatalogShow(TestCatalog): self.cmd = catalog.ShowCatalog(self.app, None) def test_catalog_show(self): + auth_ref = identity_fakes.fake_auth_ref( + identity_fakes.UNSCOPED_TOKEN, + fake_service=self.service_catalog, + ) + self.ar_mock = mock.PropertyMock(return_value=auth_ref) + type(self.app.client_manager).auth_ref = self.ar_mock + arglist = [ 'compute', ] @@ -163,7 +156,6 @@ class TestCatalogShow(TestCatalog): # returns a two-part tuple with a tuple of column names and a tuple of # data to be shown. columns, data = self.cmd.take_action(parsed_args) - self.sc_mock.service_catalog.get_data.assert_called_with() collist = ('endpoints', 'id', 'name', 'type') self.assertEqual(collist, columns) @@ -177,7 +169,7 @@ class TestCatalogShow(TestCatalog): '<none>\n publicURL: https://public.none.example.com\n ' 'internalURL: https://internal.none.example.com\n ' 'adminURL: https://admin.none.example.com\n', - 'qwertyuiop', + self.service_catalog.id, 'supernova', 'compute', ) diff --git a/openstackclient/tests/identity/v2_0/test_endpoint.py b/openstackclient/tests/identity/v2_0/test_endpoint.py index 45ece45a..b2b6d0f1 100644 --- a/openstackclient/tests/identity/v2_0/test_endpoint.py +++ b/openstackclient/tests/identity/v2_0/test_endpoint.py @@ -11,15 +11,19 @@ # under the License. # -import copy - from openstackclient.identity.v2_0 import endpoint -from openstackclient.tests import fakes from openstackclient.tests.identity.v2_0 import fakes as identity_fakes class TestEndpoint(identity_fakes.TestIdentityv2): + fake_service = identity_fakes.FakeService.create_one_service() + attr = { + 'service_name': fake_service.name, + 'service_id': fake_service.id, + } + fake_endpoint = identity_fakes.FakeEndpoint.create_one_endpoint(attr) + def setUp(self): super(TestEndpoint, self).setUp() @@ -37,35 +41,27 @@ class TestEndpointCreate(TestEndpoint): def setUp(self): super(TestEndpointCreate, self).setUp() - self.endpoints_mock.create.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.ENDPOINT), - loaded=True, - ) + self.endpoints_mock.create.return_value = self.fake_endpoint - self.services_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.SERVICE), - loaded=True, - ) + self.services_mock.get.return_value = self.fake_service # Get the command object to test self.cmd = endpoint.CreateEndpoint(self.app, None) def test_endpoint_create(self): arglist = [ - '--publicurl', identity_fakes.endpoint_publicurl, - '--internalurl', identity_fakes.endpoint_internalurl, - '--adminurl', identity_fakes.endpoint_adminurl, - '--region', identity_fakes.endpoint_region, - identity_fakes.endpoint_name, + '--publicurl', self.fake_endpoint.publicurl, + '--internalurl', self.fake_endpoint.internalurl, + '--adminurl', self.fake_endpoint.adminurl, + '--region', self.fake_endpoint.region, + self.fake_service.id, ] verifylist = [ - ('adminurl', identity_fakes.endpoint_adminurl), - ('internalurl', identity_fakes.endpoint_internalurl), - ('publicurl', identity_fakes.endpoint_publicurl), - ('region', identity_fakes.endpoint_region), - ('service', identity_fakes.service_name), + ('adminurl', self.fake_endpoint.adminurl), + ('internalurl', self.fake_endpoint.internalurl), + ('publicurl', self.fake_endpoint.publicurl), + ('region', self.fake_endpoint.region), + ('service', self.fake_service.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -77,25 +73,25 @@ class TestEndpointCreate(TestEndpoint): # EndpointManager.create(region, service_id, publicurl, adminurl, # internalurl) self.endpoints_mock.create.assert_called_with( - identity_fakes.endpoint_region, - identity_fakes.service_id, - identity_fakes.endpoint_publicurl, - identity_fakes.endpoint_adminurl, - identity_fakes.endpoint_internalurl, + self.fake_endpoint.region, + self.fake_service.id, + self.fake_endpoint.publicurl, + self.fake_endpoint.adminurl, + self.fake_endpoint.internalurl, ) collist = ('adminurl', 'id', 'internalurl', 'publicurl', 'region', 'service_id', 'service_name', 'service_type') self.assertEqual(collist, columns) datalist = ( - identity_fakes.endpoint_adminurl, - identity_fakes.endpoint_id, - identity_fakes.endpoint_internalurl, - identity_fakes.endpoint_publicurl, - identity_fakes.endpoint_region, - identity_fakes.service_id, - identity_fakes.service_name, - identity_fakes.service_type, + self.fake_endpoint.adminurl, + self.fake_endpoint.id, + self.fake_endpoint.internalurl, + self.fake_endpoint.publicurl, + self.fake_endpoint.region, + self.fake_endpoint.service_id, + self.fake_endpoint.service_name, + self.fake_endpoint.service_type, ) self.assertEqual(datalist, data) @@ -106,17 +102,9 @@ class TestEndpointDelete(TestEndpoint): def setUp(self): super(TestEndpointDelete, self).setUp() - self.endpoints_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.ENDPOINT), - loaded=True, - ) + self.endpoints_mock.get.return_value = self.fake_endpoint - self.services_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.SERVICE), - loaded=True, - ) + self.services_mock.get.return_value = self.fake_service self.endpoints_mock.delete.return_value = None @@ -125,17 +113,17 @@ class TestEndpointDelete(TestEndpoint): def test_endpoint_delete_no_options(self): arglist = [ - identity_fakes.endpoint_id, + self.fake_endpoint.id, ] verifylist = [ - ('endpoint', identity_fakes.endpoint_id), + ('endpoints', [self.fake_endpoint.id]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) self.endpoints_mock.delete.assert_called_with( - identity_fakes.endpoint_id, + self.fake_endpoint.id, ) self.assertIsNone(result) @@ -145,19 +133,9 @@ class TestEndpointList(TestEndpoint): def setUp(self): super(TestEndpointList, self).setUp() - self.endpoints_mock.list.return_value = [ - fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.ENDPOINT), - loaded=True, - ), - ] + self.endpoints_mock.list.return_value = [self.fake_endpoint] - self.services_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.SERVICE), - loaded=True, - ) + self.services_mock.get.return_value = self.fake_service # Get the command object to test self.cmd = endpoint.ListEndpoint(self.app, None) @@ -177,10 +155,10 @@ class TestEndpointList(TestEndpoint): collist = ('ID', 'Region', 'Service Name', 'Service Type') self.assertEqual(collist, columns) datalist = (( - identity_fakes.endpoint_id, - identity_fakes.endpoint_region, - identity_fakes.service_name, - identity_fakes.service_type, + self.fake_endpoint.id, + self.fake_endpoint.region, + self.fake_endpoint.service_name, + self.fake_endpoint.service_type, ), ) self.assertEqual(datalist, tuple(data)) @@ -204,13 +182,13 @@ class TestEndpointList(TestEndpoint): 'PublicURL', 'AdminURL', 'InternalURL') self.assertEqual(collist, columns) datalist = (( - identity_fakes.endpoint_id, - identity_fakes.endpoint_region, - identity_fakes.service_name, - identity_fakes.service_type, - identity_fakes.endpoint_publicurl, - identity_fakes.endpoint_adminurl, - identity_fakes.endpoint_internalurl, + self.fake_endpoint.id, + self.fake_endpoint.region, + self.fake_endpoint.service_name, + self.fake_endpoint.service_type, + self.fake_endpoint.publicurl, + self.fake_endpoint.adminurl, + self.fake_endpoint.internalurl, ), ) self.assertEqual(datalist, tuple(data)) @@ -220,29 +198,19 @@ class TestEndpointShow(TestEndpoint): def setUp(self): super(TestEndpointShow, self).setUp() - self.endpoints_mock.list.return_value = [ - fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.ENDPOINT), - loaded=True, - ), - ] + self.endpoints_mock.list.return_value = [self.fake_endpoint] - self.services_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.SERVICE), - loaded=True, - ) + self.services_mock.get.return_value = self.fake_service # Get the command object to test self.cmd = endpoint.ShowEndpoint(self.app, None) def test_endpoint_show(self): arglist = [ - identity_fakes.endpoint_name, + self.fake_endpoint.id, ] verifylist = [ - ('endpoint_or_service', identity_fakes.endpoint_name), + ('endpoint_or_service', self.fake_endpoint.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -255,20 +223,20 @@ class TestEndpointShow(TestEndpoint): self.endpoints_mock.list.assert_called_with() # ServiceManager.get(name) self.services_mock.get.assert_called_with( - identity_fakes.service_name, + self.fake_endpoint.service_id, ) collist = ('adminurl', 'id', 'internalurl', 'publicurl', 'region', 'service_id', 'service_name', 'service_type') self.assertEqual(collist, columns) datalist = ( - identity_fakes.endpoint_adminurl, - identity_fakes.endpoint_id, - identity_fakes.endpoint_internalurl, - identity_fakes.endpoint_publicurl, - identity_fakes.endpoint_region, - identity_fakes.service_id, - identity_fakes.service_name, - identity_fakes.service_type, + self.fake_endpoint.adminurl, + self.fake_endpoint.id, + self.fake_endpoint.internalurl, + self.fake_endpoint.publicurl, + self.fake_endpoint.region, + self.fake_endpoint.service_id, + self.fake_endpoint.service_name, + self.fake_endpoint.service_type, ) self.assertEqual(datalist, data) diff --git a/openstackclient/tests/identity/v2_0/test_project.py b/openstackclient/tests/identity/v2_0/test_project.py index 38684aaf..96731c0c 100644 --- a/openstackclient/tests/identity/v2_0/test_project.py +++ b/openstackclient/tests/identity/v2_0/test_project.py @@ -13,26 +13,16 @@ # under the License. # -import copy - from keystoneauth1 import exceptions as ks_exc +from osc_lib import exceptions from openstackclient.identity.v2_0 import project -from openstackclient.tests import fakes from openstackclient.tests.identity.v2_0 import fakes as identity_fakes class TestProject(identity_fakes.TestIdentityv2): - def setUp(self): - super(TestProject, self).setUp() - - # Get a shortcut to the TenantManager Mock - self.projects_mock = self.app.client_manager.identity.tenants - self.projects_mock.reset_mock() - - -class TestProjectCreate(TestProject): + fake_project = identity_fakes.FakeProject.create_one_project() columns = ( 'description', @@ -41,32 +31,38 @@ class TestProjectCreate(TestProject): 'name', ) datalist = ( - identity_fakes.project_description, + fake_project.description, True, - identity_fakes.project_id, - identity_fakes.project_name, + fake_project.id, + fake_project.name, ) def setUp(self): + super(TestProject, self).setUp() + + # Get a shortcut to the TenantManager Mock + self.projects_mock = self.app.client_manager.identity.tenants + self.projects_mock.reset_mock() + + +class TestProjectCreate(TestProject): + + def setUp(self): super(TestProjectCreate, self).setUp() - self.projects_mock.create.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.PROJECT), - loaded=True, - ) + self.projects_mock.create.return_value = self.fake_project # Get the command object to test self.cmd = project.CreateProject(self.app, None) def test_project_create_no_options(self): arglist = [ - identity_fakes.project_name, + self.fake_project.name, ] verifylist = [ ('enable', False), ('disable', False), - ('name', identity_fakes.project_name), + ('name', self.fake_project.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -81,7 +77,7 @@ class TestProjectCreate(TestProject): 'enabled': True, } self.projects_mock.create.assert_called_with( - identity_fakes.project_name, + self.fake_project.name, **kwargs ) self.assertEqual(self.columns, columns) @@ -90,11 +86,11 @@ class TestProjectCreate(TestProject): def test_project_create_description(self): arglist = [ '--description', 'new desc', - identity_fakes.project_name, + self.fake_project.name, ] verifylist = [ ('description', 'new desc'), - ('name', identity_fakes.project_name), + ('name', self.fake_project.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -109,7 +105,7 @@ class TestProjectCreate(TestProject): 'enabled': True, } self.projects_mock.create.assert_called_with( - identity_fakes.project_name, + self.fake_project.name, **kwargs ) @@ -119,12 +115,12 @@ class TestProjectCreate(TestProject): def test_project_create_enable(self): arglist = [ '--enable', - identity_fakes.project_name, + self.fake_project.name, ] verifylist = [ ('enable', True), ('disable', False), - ('name', identity_fakes.project_name), + ('name', self.fake_project.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -139,7 +135,7 @@ class TestProjectCreate(TestProject): 'enabled': True, } self.projects_mock.create.assert_called_with( - identity_fakes.project_name, + self.fake_project.name, **kwargs ) @@ -149,12 +145,12 @@ class TestProjectCreate(TestProject): def test_project_create_disable(self): arglist = [ '--disable', - identity_fakes.project_name, + self.fake_project.name, ] verifylist = [ ('enable', False), ('disable', True), - ('name', identity_fakes.project_name), + ('name', self.fake_project.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -169,7 +165,7 @@ class TestProjectCreate(TestProject): 'enabled': False, } self.projects_mock.create.assert_called_with( - identity_fakes.project_name, + self.fake_project.name, **kwargs ) @@ -180,11 +176,11 @@ class TestProjectCreate(TestProject): arglist = [ '--property', 'fee=fi', '--property', 'fo=fum', - identity_fakes.project_name, + self.fake_project.name, ] verifylist = [ ('property', {'fee': 'fi', 'fo': 'fum'}), - ('name', identity_fakes.project_name), + ('name', self.fake_project.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -201,7 +197,7 @@ class TestProjectCreate(TestProject): 'fo': 'fum', } self.projects_mock.create.assert_called_with( - identity_fakes.project_name, + self.fake_project.name, **kwargs ) @@ -215,19 +211,15 @@ class TestProjectCreate(TestProject): # need to make this throw an exception... self.projects_mock.create.side_effect = _raise_conflict - self.projects_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.PROJECT), - loaded=True, - ) + self.projects_mock.get.return_value = self.fake_project arglist = [ '--or-show', - identity_fakes.project_name, + self.fake_project.name, ] verifylist = [ - ('name', identity_fakes.project_name), ('or_show', True), + ('name', self.fake_project.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -237,7 +229,7 @@ class TestProjectCreate(TestProject): columns, data = self.cmd.take_action(parsed_args) # ProjectManager.create(name, description, enabled) - self.projects_mock.get.assert_called_with(identity_fakes.project_name) + self.projects_mock.get.assert_called_with(self.fake_project.name) # Set expected values kwargs = { @@ -245,7 +237,7 @@ class TestProjectCreate(TestProject): 'enabled': True, } self.projects_mock.create.assert_called_with( - identity_fakes.project_name, + self.fake_project.name, **kwargs ) @@ -255,11 +247,11 @@ class TestProjectCreate(TestProject): def test_project_create_or_show_not_exists(self): arglist = [ '--or-show', - identity_fakes.project_name, + self.fake_project.name, ] verifylist = [ - ('name', identity_fakes.project_name), ('or_show', True), + ('name', self.fake_project.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -274,7 +266,7 @@ class TestProjectCreate(TestProject): 'enabled': True, } self.projects_mock.create.assert_called_with( - identity_fakes.project_name, + self.fake_project.name, **kwargs ) @@ -288,11 +280,7 @@ class TestProjectDelete(TestProject): super(TestProjectDelete, self).setUp() # This is the return value for utils.find_resource() - self.projects_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.PROJECT), - loaded=True, - ) + self.projects_mock.get.return_value = self.fake_project self.projects_mock.delete.return_value = None # Get the command object to test @@ -300,17 +288,17 @@ class TestProjectDelete(TestProject): def test_project_delete_no_options(self): arglist = [ - identity_fakes.project_id, + self.fake_project.id, ] verifylist = [ - ('projects', [identity_fakes.project_id]), + ('projects', [self.fake_project.id]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) self.projects_mock.delete.assert_called_with( - identity_fakes.project_id, + self.fake_project.id, ) self.assertIsNone(result) @@ -320,13 +308,7 @@ class TestProjectList(TestProject): def setUp(self): super(TestProjectList, self).setUp() - self.projects_mock.list.return_value = [ - fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.PROJECT), - loaded=True, - ), - ] + self.projects_mock.list.return_value = [self.fake_project] # Get the command object to test self.cmd = project.ListProject(self.app, None) @@ -345,8 +327,8 @@ class TestProjectList(TestProject): collist = ('ID', 'Name') self.assertEqual(collist, columns) datalist = (( - identity_fakes.project_id, - identity_fakes.project_name, + self.fake_project.id, + self.fake_project.name, ), ) self.assertEqual(datalist, tuple(data)) @@ -368,9 +350,9 @@ class TestProjectList(TestProject): collist = ('ID', 'Name', 'Description', 'Enabled') self.assertEqual(collist, columns) datalist = (( - identity_fakes.project_id, - identity_fakes.project_name, - identity_fakes.project_description, + self.fake_project.id, + self.fake_project.name, + self.fake_project.description, True, ), ) self.assertEqual(datalist, tuple(data)) @@ -381,26 +363,18 @@ class TestProjectSet(TestProject): def setUp(self): super(TestProjectSet, self).setUp() - self.projects_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.PROJECT), - loaded=True, - ) - self.projects_mock.update.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.PROJECT), - loaded=True, - ) + self.projects_mock.get.return_value = self.fake_project + self.projects_mock.update.return_value = self.fake_project # Get the command object to test self.cmd = project.SetProject(self.app, None) def test_project_set_no_options(self): arglist = [ - identity_fakes.project_name, + self.fake_project.name, ] verifylist = [ - ('project', identity_fakes.project_name), + ('project', self.fake_project.name), ('enable', False), ('disable', False), ] @@ -410,16 +384,36 @@ class TestProjectSet(TestProject): self.assertIsNone(result) + def test_project_set_unexist_project(self): + arglist = [ + "unexist-project", + ] + verifylist = [ + ('project', "unexist-project"), + ('name', None), + ('description', None), + ('enable', False), + ('disable', False), + ('property', None), + ] + self.projects_mock.get.side_effect = exceptions.NotFound(None) + self.projects_mock.find.side_effect = exceptions.NotFound(None) + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.assertRaises( + exceptions.CommandError, self.cmd.take_action, parsed_args) + def test_project_set_name(self): arglist = [ - '--name', 'qwerty', - identity_fakes.project_name, + '--name', self.fake_project.name, + self.fake_project.name, ] verifylist = [ - ('name', 'qwerty'), + ('name', self.fake_project.name), ('enable', False), ('disable', False), - ('project', identity_fakes.project_name), + ('project', self.fake_project.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -427,26 +421,26 @@ class TestProjectSet(TestProject): # Set expected values kwargs = { - 'description': identity_fakes.project_description, + 'description': self.fake_project.description, 'enabled': True, - 'tenant_name': 'qwerty', + 'tenant_name': self.fake_project.name, } self.projects_mock.update.assert_called_with( - identity_fakes.project_id, + self.fake_project.id, **kwargs ) self.assertIsNone(result) def test_project_set_description(self): arglist = [ - '--description', 'new desc', - identity_fakes.project_name, + '--description', self.fake_project.description, + self.fake_project.name, ] verifylist = [ - ('description', 'new desc'), + ('description', self.fake_project.description), ('enable', False), ('disable', False), - ('project', identity_fakes.project_name), + ('project', self.fake_project.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -454,12 +448,12 @@ class TestProjectSet(TestProject): # Set expected values kwargs = { - 'description': 'new desc', + 'description': self.fake_project.description, 'enabled': True, - 'tenant_name': identity_fakes.project_name, + 'tenant_name': self.fake_project.name, } self.projects_mock.update.assert_called_with( - identity_fakes.project_id, + self.fake_project.id, **kwargs ) self.assertIsNone(result) @@ -467,12 +461,12 @@ class TestProjectSet(TestProject): def test_project_set_enable(self): arglist = [ '--enable', - identity_fakes.project_name, + self.fake_project.name, ] verifylist = [ ('enable', True), ('disable', False), - ('project', identity_fakes.project_name), + ('project', self.fake_project.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -480,12 +474,12 @@ class TestProjectSet(TestProject): # Set expected values kwargs = { - 'description': identity_fakes.project_description, + 'description': self.fake_project.description, 'enabled': True, - 'tenant_name': identity_fakes.project_name, + 'tenant_name': self.fake_project.name, } self.projects_mock.update.assert_called_with( - identity_fakes.project_id, + self.fake_project.id, **kwargs ) self.assertIsNone(result) @@ -493,12 +487,12 @@ class TestProjectSet(TestProject): def test_project_set_disable(self): arglist = [ '--disable', - identity_fakes.project_name, + self.fake_project.name, ] verifylist = [ ('enable', False), ('disable', True), - ('project', identity_fakes.project_name), + ('project', self.fake_project.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -506,12 +500,12 @@ class TestProjectSet(TestProject): # Set expected values kwargs = { - 'description': identity_fakes.project_description, + 'description': self.fake_project.description, 'enabled': False, - 'tenant_name': identity_fakes.project_name, + 'tenant_name': self.fake_project.name, } self.projects_mock.update.assert_called_with( - identity_fakes.project_id, + self.fake_project.id, **kwargs ) self.assertIsNone(result) @@ -520,11 +514,11 @@ class TestProjectSet(TestProject): arglist = [ '--property', 'fee=fi', '--property', 'fo=fum', - identity_fakes.project_name, + self.fake_project.name, ] verifylist = [ ('property', {'fee': 'fi', 'fo': 'fum'}), - ('project', identity_fakes.project_name), + ('project', self.fake_project.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -532,14 +526,14 @@ class TestProjectSet(TestProject): # Set expected values kwargs = { - 'description': identity_fakes.project_description, + 'description': self.fake_project.description, 'enabled': True, - 'tenant_name': identity_fakes.project_name, + 'tenant_name': self.fake_project.name, 'fee': 'fi', 'fo': 'fum', } self.projects_mock.update.assert_called_with( - identity_fakes.project_id, + self.fake_project.id, **kwargs ) self.assertIsNone(result) @@ -547,24 +541,22 @@ class TestProjectSet(TestProject): class TestProjectShow(TestProject): + fake_proj_show = identity_fakes.FakeProject.create_one_project() + def setUp(self): super(TestProjectShow, self).setUp() - self.projects_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.PROJECT), - loaded=True, - ) + self.projects_mock.get.return_value = self.fake_proj_show # Get the command object to test self.cmd = project.ShowProject(self.app, None) def test_project_show(self): arglist = [ - identity_fakes.project_id, + self.fake_proj_show.id, ] verifylist = [ - ('project', identity_fakes.project_id), + ('project', self.fake_proj_show.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -573,16 +565,16 @@ class TestProjectShow(TestProject): # data to be shown. columns, data = self.cmd.take_action(parsed_args) self.projects_mock.get.assert_called_with( - identity_fakes.project_id, + self.fake_proj_show.id, ) collist = ('description', 'enabled', 'id', 'name', 'properties') self.assertEqual(collist, columns) datalist = ( - identity_fakes.project_description, + self.fake_proj_show.description, True, - identity_fakes.project_id, - identity_fakes.project_name, + self.fake_proj_show.id, + self.fake_proj_show.name, '', ) self.assertEqual(datalist, data) @@ -590,25 +582,35 @@ class TestProjectShow(TestProject): class TestProjectUnset(TestProject): + attr = {'fee': 'fi', 'fo': 'fum'} + fake_proj = identity_fakes.FakeProject.create_one_project(attr) + def setUp(self): super(TestProjectUnset, self).setUp() - project_dict = {'fee': 'fi', 'fo': 'fum'} - project_dict.update(identity_fakes.PROJECT) - self.projects_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(project_dict), - loaded=True, - ) + self.projects_mock.get.return_value = self.fake_proj # Get the command object to test self.cmd = project.UnsetProject(self.app, None) + def test_project_unset_no_options(self): + arglist = [ + self.fake_proj.name, + ] + verifylist = [ + ('project', self.fake_proj.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.assertIsNone(result) + def test_project_unset_key(self): arglist = [ '--property', 'fee', '--property', 'fo', - identity_fakes.project_name, + self.fake_proj.name, ] verifylist = [ ('property', ['fee', 'fo']), @@ -618,16 +620,16 @@ class TestProjectUnset(TestProject): result = self.cmd.take_action(parsed_args) # Set expected values kwargs = { - 'description': identity_fakes.project_description, + 'description': self.fake_proj.description, 'enabled': True, 'fee': None, 'fo': None, - 'id': identity_fakes.project_id, - 'name': identity_fakes.project_name, + 'id': self.fake_proj.id, + 'name': self.fake_proj.name, } self.projects_mock.update.assert_called_with( - identity_fakes.project_id, + self.fake_proj.id, **kwargs ) self.assertIsNone(result) diff --git a/openstackclient/tests/identity/v2_0/test_role.py b/openstackclient/tests/identity/v2_0/test_role.py index 3c4b79a4..3d379356 100644 --- a/openstackclient/tests/identity/v2_0/test_role.py +++ b/openstackclient/tests/identity/v2_0/test_role.py @@ -13,19 +13,32 @@ # under the License. # -import copy import mock from keystoneauth1 import exceptions as ks_exc +from osc_lib import exceptions -from openstackclient.common import exceptions from openstackclient.identity.v2_0 import role -from openstackclient.tests import fakes from openstackclient.tests.identity.v2_0 import fakes as identity_fakes class TestRole(identity_fakes.TestIdentityv2): + attr = {} + attr['endpoints'] = [ + { + 'publicURL': identity_fakes.ENDPOINT['publicurl'], + }, + ] + fake_service = identity_fakes.FakeService.create_one_service(attr) + fake_role = identity_fakes.FakeRole.create_one_role() + fake_project = identity_fakes.FakeProject.create_one_project() + attr = {} + attr = { + 'tenantId': fake_project.id, + } + fake_user = identity_fakes.FakeUser.create_one_user(attr) + def setUp(self): super(TestRole, self).setUp() @@ -41,48 +54,39 @@ class TestRole(identity_fakes.TestIdentityv2): self.roles_mock = self.app.client_manager.identity.roles self.roles_mock.reset_mock() + auth_ref = identity_fakes.fake_auth_ref( + identity_fakes.TOKEN, + fake_service=self.fake_service, + ) + self.ar_mock = mock.PropertyMock(return_value=auth_ref) + type(self.app.client_manager).auth_ref = self.ar_mock + class TestRoleAdd(TestRole): def setUp(self): super(TestRoleAdd, self).setUp() - self.projects_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.PROJECT), - loaded=True, - ) + self.projects_mock.get.return_value = self.fake_project - self.users_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.USER), - loaded=True, - ) + self.users_mock.get.return_value = self.fake_user - self.roles_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.ROLE), - loaded=True, - ) - self.roles_mock.add_user_role.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.ROLE), - loaded=True, - ) + self.roles_mock.get.return_value = self.fake_role + self.roles_mock.add_user_role.return_value = self.fake_role # Get the command object to test self.cmd = role.AddRole(self.app, None) def test_role_add(self): arglist = [ - '--project', identity_fakes.project_name, - '--user', identity_fakes.user_name, - identity_fakes.role_name, + '--project', self.fake_project.name, + '--user', self.fake_user.name, + self.fake_role.name, ] verifylist = [ - ('project', identity_fakes.project_name), - ('user', identity_fakes.user_name), - ('role', identity_fakes.role_name), + ('project', self.fake_project.name), + ('user', self.fake_user.name), + ('role', self.fake_role.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -93,49 +97,46 @@ class TestRoleAdd(TestRole): # RoleManager.add_user_role(user, role, tenant=None) self.roles_mock.add_user_role.assert_called_with( - identity_fakes.user_id, - identity_fakes.role_id, - identity_fakes.project_id, + self.fake_user.id, + self.fake_role.id, + self.fake_project.id, ) collist = ('id', 'name') self.assertEqual(collist, columns) datalist = ( - identity_fakes.role_id, - identity_fakes.role_name, + self.fake_role.id, + self.fake_role.name, ) self.assertEqual(datalist, data) class TestRoleCreate(TestRole): + fake_role_c = identity_fakes.FakeRole.create_one_role() columns = ( 'id', 'name' ) datalist = ( - identity_fakes.role_id, - identity_fakes.role_name, + fake_role_c.id, + fake_role_c.name, ) def setUp(self): super(TestRoleCreate, self).setUp() - self.roles_mock.create.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.ROLE), - loaded=True, - ) + self.roles_mock.create.return_value = self.fake_role_c # Get the command object to test self.cmd = role.CreateRole(self.app, None) def test_role_create_no_options(self): arglist = [ - identity_fakes.role_name, + self.fake_role_c.name, ] verifylist = [ - ('role_name', identity_fakes.role_name), + ('role_name', self.fake_role_c.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -146,7 +147,7 @@ class TestRoleCreate(TestRole): # RoleManager.create(name) self.roles_mock.create.assert_called_with( - identity_fakes.role_name, + self.fake_role_c.name, ) self.assertEqual(self.columns, columns) @@ -159,18 +160,14 @@ class TestRoleCreate(TestRole): # need to make this throw an exception... self.roles_mock.create.side_effect = _raise_conflict - self.roles_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.ROLE), - loaded=True, - ) + self.roles_mock.get.return_value = self.fake_role_c arglist = [ '--or-show', - identity_fakes.role_name, + self.fake_role_c.name, ] verifylist = [ - ('role_name', identity_fakes.role_name), + ('role_name', self.fake_role_c.name), ('or_show', True), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -181,11 +178,11 @@ class TestRoleCreate(TestRole): columns, data = self.cmd.take_action(parsed_args) # RoleManager.get(name, description, enabled) - self.roles_mock.get.assert_called_with(identity_fakes.role_name) + self.roles_mock.get.assert_called_with(self.fake_role_c.name) # RoleManager.create(name) self.roles_mock.create.assert_called_with( - identity_fakes.role_name, + self.fake_role_c.name, ) self.assertEqual(self.columns, columns) @@ -194,10 +191,10 @@ class TestRoleCreate(TestRole): def test_role_create_or_show_not_exists(self): arglist = [ '--or-show', - identity_fakes.role_name, + self.fake_role_c.name, ] verifylist = [ - ('role_name', identity_fakes.role_name), + ('role_name', self.fake_role_c.name), ('or_show', True), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -209,7 +206,7 @@ class TestRoleCreate(TestRole): # RoleManager.create(name) self.roles_mock.create.assert_called_with( - identity_fakes.role_name, + self.fake_role_c.name, ) self.assertEqual(self.columns, columns) @@ -221,11 +218,7 @@ class TestRoleDelete(TestRole): def setUp(self): super(TestRoleDelete, self).setUp() - self.roles_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.ROLE), - loaded=True, - ) + self.roles_mock.get.return_value = self.fake_role self.roles_mock.delete.return_value = None # Get the command object to test @@ -233,17 +226,17 @@ class TestRoleDelete(TestRole): def test_role_delete_no_options(self): arglist = [ - identity_fakes.role_name, + self.fake_role.name, ] verifylist = [ - ('roles', [identity_fakes.role_name]), + ('roles', [self.fake_role.name]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) self.roles_mock.delete.assert_called_with( - identity_fakes.role_id, + self.fake_role.id, ) self.assertIsNone(result) @@ -253,13 +246,7 @@ class TestRoleList(TestRole): def setUp(self): super(TestRoleList, self).setUp() - self.roles_mock.list.return_value = [ - fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.ROLE), - loaded=True, - ), - ] + self.roles_mock.list.return_value = [self.fake_role] # Get the command object to test self.cmd = role.ListRole(self.app, None) @@ -279,8 +266,8 @@ class TestRoleList(TestRole): collist = ('ID', 'Name') self.assertEqual(collist, columns) datalist = (( - identity_fakes.role_id, - identity_fakes.role_name, + self.fake_role.id, + self.fake_role.name, ), ) self.assertEqual(datalist, tuple(data)) @@ -297,30 +284,23 @@ class TestUserRoleList(TestRole): def setUp(self): super(TestUserRoleList, self).setUp() - self.projects_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.PROJECT), - loaded=True, - ) + self.projects_mock.get.return_value = self.fake_project - self.users_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.USER), - loaded=True, - ) + self.users_mock.get.return_value = self.fake_user - self.roles_mock.roles_for_user.return_value = [ - fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.ROLE), - loaded=True, - ), - ] + self.roles_mock.roles_for_user.return_value = [self.fake_role] # Get the command object to test self.cmd = role.ListUserRole(self.app, None) - def test_user_role_list_no_options(self): + def test_user_role_list_no_options_unscoped_token(self): + auth_ref = identity_fakes.fake_auth_ref( + identity_fakes.UNSCOPED_TOKEN, + fake_service=self.fake_service, + ) + self.ar_mock = mock.PropertyMock(return_value=auth_ref) + type(self.app.client_manager).auth_ref = self.ar_mock + arglist = [] verifylist = [] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -332,11 +312,7 @@ class TestUserRoleList(TestRole): parsed_args, ) - def test_user_role_list_no_options_def_creds(self): - auth_ref = self.app.client_manager.auth_ref = mock.MagicMock() - auth_ref.project_id.return_value = identity_fakes.project_id - auth_ref.user_id.return_value = identity_fakes.user_id - + def test_user_role_list_no_options_scoped_token(self): arglist = [] verifylist = [] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -347,56 +323,63 @@ class TestUserRoleList(TestRole): columns, data = self.cmd.take_action(parsed_args) self.roles_mock.roles_for_user.assert_called_with( - identity_fakes.user_id, - identity_fakes.project_id, + self.fake_user.id, + self.fake_project.id, ) collist = ('ID', 'Name', 'Project', 'User') self.assertEqual(collist, columns) datalist = (( - identity_fakes.role_id, - identity_fakes.role_name, - identity_fakes.project_name, - identity_fakes.user_name, + self.fake_role.id, + self.fake_role.name, + self.fake_project.name, + self.fake_user.name, ), ) self.assertEqual(datalist, tuple(data)) - def test_user_role_list_project(self): - self.projects_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.PROJECT_2), - loaded=True, + def test_user_role_list_project_unscoped_token(self): + auth_ref = identity_fakes.fake_auth_ref( + identity_fakes.UNSCOPED_TOKEN, + fake_service=self.fake_service, ) + self.ar_mock = mock.PropertyMock(return_value=auth_ref) + type(self.app.client_manager).auth_ref = self.ar_mock + + self.projects_mock.get.return_value = self.fake_project arglist = [ - '--project', identity_fakes.PROJECT_2['name'], + '--project', self.fake_project.name, ] verifylist = [ - ('project', identity_fakes.PROJECT_2['name']), + ('project', self.fake_project.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # This argument combination should raise a CommandError - self.assertRaises( - exceptions.CommandError, - self.cmd.take_action, - parsed_args, + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. + columns, data = self.cmd.take_action(parsed_args) + + self.roles_mock.roles_for_user.assert_called_with( + self.fake_user.id, + self.fake_project.id, ) - def test_user_role_list_project_def_creds(self): - auth_ref = self.app.client_manager.auth_ref = mock.MagicMock() - auth_ref.project_id.return_value = identity_fakes.project_id - auth_ref.user_id.return_value = identity_fakes.user_id + self.assertEqual(columns, columns) + datalist = (( + self.fake_role.id, + self.fake_role.name, + self.fake_project.name, + self.fake_user.name, + ), ) + self.assertEqual(datalist, tuple(data)) - self.projects_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.PROJECT_2), - loaded=True, - ) + def test_user_role_list_project_scoped_token(self): + self.projects_mock.get.return_value = self.fake_project arglist = [ - '--project', identity_fakes.PROJECT_2['name'], + '--project', self.fake_project.name, ] verifylist = [ - ('project', identity_fakes.PROJECT_2['name']), + ('project', self.fake_project.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -406,16 +389,16 @@ class TestUserRoleList(TestRole): columns, data = self.cmd.take_action(parsed_args) self.roles_mock.roles_for_user.assert_called_with( - identity_fakes.user_id, - identity_fakes.PROJECT_2['id'], + self.fake_user.id, + self.fake_project.id, ) self.assertEqual(columns, columns) datalist = (( - identity_fakes.role_id, - identity_fakes.role_name, - identity_fakes.PROJECT_2['name'], - identity_fakes.user_name, + self.fake_role.id, + self.fake_role.name, + self.fake_project.name, + self.fake_user.name, ), ) self.assertEqual(datalist, tuple(data)) @@ -425,23 +408,11 @@ class TestRoleRemove(TestRole): def setUp(self): super(TestRoleRemove, self).setUp() - self.projects_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.PROJECT), - loaded=True, - ) + self.projects_mock.get.return_value = self.fake_project - self.users_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.USER), - loaded=True, - ) + self.users_mock.get.return_value = self.fake_user - self.roles_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.ROLE), - loaded=True, - ) + self.roles_mock.get.return_value = self.fake_role self.roles_mock.remove_user_role.return_value = None # Get the command object to test @@ -449,14 +420,14 @@ class TestRoleRemove(TestRole): def test_role_remove(self): arglist = [ - '--project', identity_fakes.project_name, - '--user', identity_fakes.user_name, - identity_fakes.role_name, + '--project', self.fake_project.name, + '--user', self.fake_user.name, + self.fake_role.name, ] verifylist = [ - ('role', identity_fakes.role_name), - ('project', identity_fakes.project_name), - ('user', identity_fakes.user_name), + ('role', self.fake_role.name), + ('project', self.fake_project.name), + ('user', self.fake_user.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -464,9 +435,9 @@ class TestRoleRemove(TestRole): # RoleManager.remove_user_role(user, role, tenant=None) self.roles_mock.remove_user_role.assert_called_with( - identity_fakes.user_id, - identity_fakes.role_id, - identity_fakes.project_id, + self.fake_user.id, + self.fake_role.id, + self.fake_project.id, ) self.assertIsNone(result) @@ -476,21 +447,17 @@ class TestRoleShow(TestRole): def setUp(self): super(TestRoleShow, self).setUp() - self.roles_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.ROLE), - loaded=True, - ) + self.roles_mock.get.return_value = self.fake_role # Get the command object to test self.cmd = role.ShowRole(self.app, None) def test_service_show(self): arglist = [ - identity_fakes.role_name, + self.fake_role.name, ] verifylist = [ - ('role', identity_fakes.role_name), + ('role', self.fake_role.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -501,13 +468,13 @@ class TestRoleShow(TestRole): # RoleManager.get(role) self.roles_mock.get.assert_called_with( - identity_fakes.role_name, + self.fake_role.name, ) collist = ('id', 'name') self.assertEqual(collist, columns) datalist = ( - identity_fakes.role_id, - identity_fakes.role_name, + self.fake_role.id, + self.fake_role.name, ) self.assertEqual(datalist, data) diff --git a/openstackclient/tests/identity/v2_0/test_service.py b/openstackclient/tests/identity/v2_0/test_service.py index dc0fbcd1..318fa83d 100644 --- a/openstackclient/tests/identity/v2_0/test_service.py +++ b/openstackclient/tests/identity/v2_0/test_service.py @@ -13,14 +13,12 @@ # under the License. # -import copy - from openstackclient.identity.v2_0 import service -from openstackclient.tests import fakes from openstackclient.tests.identity.v2_0 import fakes as identity_fakes class TestService(identity_fakes.TestIdentityv2): + fake_service = identity_fakes.FakeService.create_one_service() def setUp(self): super(TestService, self).setUp() @@ -32,6 +30,7 @@ class TestService(identity_fakes.TestIdentityv2): class TestServiceCreate(TestService): + fake_service_c = identity_fakes.FakeService.create_one_service() columns = ( 'description', 'id', @@ -39,30 +38,26 @@ class TestServiceCreate(TestService): 'type', ) datalist = ( - identity_fakes.service_description, - identity_fakes.service_id, - identity_fakes.service_name, - identity_fakes.service_type, + fake_service_c.description, + fake_service_c.id, + fake_service_c.name, + fake_service_c.type, ) def setUp(self): super(TestServiceCreate, self).setUp() - self.services_mock.create.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.SERVICE), - loaded=True, - ) + self.services_mock.create.return_value = self.fake_service_c # Get the command object to test self.cmd = service.CreateService(self.app, None) def test_service_create_with_type_positional(self): arglist = [ - identity_fakes.service_type, + self.fake_service_c.type, ] verifylist = [ - ('type_or_name', identity_fakes.service_type), + ('type_or_name', self.fake_service_c.type), ('type', None), ('description', None), ('name', None), @@ -77,7 +72,7 @@ class TestServiceCreate(TestService): # ServiceManager.create(name, service_type, description) self.services_mock.create.assert_called_with( None, - identity_fakes.service_type, + self.fake_service_c.type, None, ) @@ -86,12 +81,12 @@ class TestServiceCreate(TestService): def test_service_create_with_type_option(self): arglist = [ - '--type', identity_fakes.service_type, - identity_fakes.service_name, + '--type', self.fake_service_c.type, + self.fake_service_c.name, ] verifylist = [ - ('type_or_name', identity_fakes.service_name), - ('type', identity_fakes.service_type), + ('type_or_name', self.fake_service_c.name), + ('type', self.fake_service_c.type), ('description', None), ('name', None), ] @@ -104,8 +99,8 @@ class TestServiceCreate(TestService): # ServiceManager.create(name, service_type, description) self.services_mock.create.assert_called_with( - identity_fakes.service_name, - identity_fakes.service_type, + self.fake_service_c.name, + self.fake_service_c.type, None, ) @@ -114,14 +109,14 @@ class TestServiceCreate(TestService): def test_service_create_with_name_option(self): arglist = [ - '--name', identity_fakes.service_name, - identity_fakes.service_type, + '--name', self.fake_service_c.name, + self.fake_service_c.type, ] verifylist = [ - ('type_or_name', identity_fakes.service_type), + ('type_or_name', self.fake_service_c.type), ('type', None), ('description', None), - ('name', identity_fakes.service_name), + ('name', self.fake_service_c.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -132,8 +127,8 @@ class TestServiceCreate(TestService): # ServiceManager.create(name, service_type, description) self.services_mock.create.assert_called_with( - identity_fakes.service_name, - identity_fakes.service_type, + self.fake_service_c.name, + self.fake_service_c.type, None, ) @@ -142,15 +137,15 @@ class TestServiceCreate(TestService): def test_service_create_description(self): arglist = [ - '--name', identity_fakes.service_name, - '--description', identity_fakes.service_description, - identity_fakes.service_type, + '--name', self.fake_service_c.name, + '--description', self.fake_service_c.description, + self.fake_service_c.type, ] verifylist = [ - ('type_or_name', identity_fakes.service_type), + ('type_or_name', self.fake_service_c.type), ('type', None), - ('description', identity_fakes.service_description), - ('name', identity_fakes.service_name), + ('description', self.fake_service_c.description), + ('name', self.fake_service_c.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -161,9 +156,9 @@ class TestServiceCreate(TestService): # ServiceManager.create(name, service_type, description) self.services_mock.create.assert_called_with( - identity_fakes.service_name, - identity_fakes.service_type, - identity_fakes.service_description, + self.fake_service_c.name, + self.fake_service_c.type, + self.fake_service_c.description, ) self.assertEqual(self.columns, columns) @@ -175,11 +170,7 @@ class TestServiceDelete(TestService): def setUp(self): super(TestServiceDelete, self).setUp() - self.services_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.SERVICE), - loaded=True, - ) + self.services_mock.get.return_value = self.fake_service self.services_mock.delete.return_value = None # Get the command object to test @@ -187,17 +178,17 @@ class TestServiceDelete(TestService): def test_service_delete_no_options(self): arglist = [ - identity_fakes.service_name, + self.fake_service.name, ] verifylist = [ - ('service', identity_fakes.service_name), + ('services', [self.fake_service.name]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) self.services_mock.delete.assert_called_with( - identity_fakes.service_id, + self.fake_service.id, ) self.assertIsNone(result) @@ -207,13 +198,7 @@ class TestServiceList(TestService): def setUp(self): super(TestServiceList, self).setUp() - self.services_mock.list.return_value = [ - fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.SERVICE), - loaded=True, - ), - ] + self.services_mock.list.return_value = [self.fake_service] # Get the command object to test self.cmd = service.ListService(self.app, None) @@ -233,9 +218,9 @@ class TestServiceList(TestService): collist = ('ID', 'Name', 'Type') self.assertEqual(collist, columns) datalist = (( - identity_fakes.service_id, - identity_fakes.service_name, - identity_fakes.service_type, + self.fake_service.id, + self.fake_service.name, + self.fake_service.type, ), ) self.assertEqual(datalist, tuple(data)) @@ -258,10 +243,10 @@ class TestServiceList(TestService): collist = ('ID', 'Name', 'Type', 'Description') self.assertEqual(collist, columns) datalist = (( - identity_fakes.service_id, - identity_fakes.service_name, - identity_fakes.service_type, - identity_fakes.service_description, + self.fake_service.id, + self.fake_service.name, + self.fake_service.type, + self.fake_service.description, ), ) self.assertEqual(datalist, tuple(data)) @@ -271,21 +256,17 @@ class TestServiceShow(TestService): def setUp(self): super(TestServiceShow, self).setUp() - self.services_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.SERVICE), - loaded=True, - ) + self.services_mock.get.return_value = self.fake_service # Get the command object to test self.cmd = service.ShowService(self.app, None) def test_service_show(self): arglist = [ - identity_fakes.service_name, + self.fake_service.name, ] verifylist = [ - ('service', identity_fakes.service_name), + ('service', self.fake_service.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -296,15 +277,15 @@ class TestServiceShow(TestService): # ServiceManager.get(id) self.services_mock.get.assert_called_with( - identity_fakes.service_name, + self.fake_service.name, ) collist = ('description', 'id', 'name', 'type') self.assertEqual(collist, columns) datalist = ( - identity_fakes.service_description, - identity_fakes.service_id, - identity_fakes.service_name, - identity_fakes.service_type, + self.fake_service.description, + self.fake_service.id, + self.fake_service.name, + self.fake_service.type, ) self.assertEqual(datalist, data) diff --git a/openstackclient/tests/identity/v2_0/test_token.py b/openstackclient/tests/identity/v2_0/test_token.py index 613139dd..bb776707 100644 --- a/openstackclient/tests/identity/v2_0/test_token.py +++ b/openstackclient/tests/identity/v2_0/test_token.py @@ -21,13 +21,15 @@ from openstackclient.tests.identity.v2_0 import fakes as identity_fakes class TestToken(identity_fakes.TestIdentityv2): + fake_user = identity_fakes.FakeUser.create_one_user() + fake_project = identity_fakes.FakeProject.create_one_project() + def setUp(self): super(TestToken, self).setUp() - # Get a shortcut to the Service Catalog Mock - self.sc_mock = mock.Mock() - self.app.client_manager.auth_ref = mock.Mock() - self.app.client_manager.auth_ref.service_catalog = self.sc_mock + # Get a shortcut to the Auth Ref Mock + self.ar_mock = mock.PropertyMock() + type(self.app.client_manager).auth_ref = self.ar_mock class TestTokenIssue(TestToken): @@ -35,10 +37,15 @@ class TestTokenIssue(TestToken): def setUp(self): super(TestTokenIssue, self).setUp() - self.sc_mock.get_token.return_value = identity_fakes.TOKEN self.cmd = token.IssueToken(self.app, None) def test_token_issue(self): + auth_ref = identity_fakes.fake_auth_ref( + identity_fakes.TOKEN, + ) + self.ar_mock = mock.PropertyMock(return_value=auth_ref) + type(self.app.client_manager).auth_ref = self.ar_mock + arglist = [] verifylist = [] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -48,21 +55,22 @@ class TestTokenIssue(TestToken): # data to be shown. columns, data = self.cmd.take_action(parsed_args) - self.sc_mock.get_token.assert_called_with() - collist = ('expires', 'id', 'project_id', 'user_id') self.assertEqual(collist, columns) datalist = ( - identity_fakes.token_expires, + auth_ref.expires, identity_fakes.token_id, - identity_fakes.project_id, - identity_fakes.user_id, + 'project-id', + 'user-id', ) self.assertEqual(datalist, data) def test_token_issue_with_unscoped_token(self): - # make sure we return an unscoped token - self.sc_mock.get_token.return_value = identity_fakes.UNSCOPED_TOKEN + auth_ref = identity_fakes.fake_auth_ref( + identity_fakes.UNSCOPED_TOKEN, + ) + self.ar_mock = mock.PropertyMock(return_value=auth_ref) + type(self.app.client_manager).auth_ref = self.ar_mock arglist = [] verifylist = [] @@ -71,14 +79,16 @@ class TestTokenIssue(TestToken): # DisplayCommandBase.take_action() returns two tuples columns, data = self.cmd.take_action(parsed_args) - self.sc_mock.get_token.assert_called_with() - - collist = ('expires', 'id', 'user_id') + collist = ( + 'expires', + 'id', + 'user_id', + ) self.assertEqual(collist, columns) datalist = ( - identity_fakes.token_expires, + auth_ref.expires, identity_fakes.token_id, - identity_fakes.user_id, + 'user-id', ) self.assertEqual(datalist, data) diff --git a/openstackclient/tests/identity/v2_0/test_user.py b/openstackclient/tests/identity/v2_0/test_user.py index 921e215d..ba871247 100644 --- a/openstackclient/tests/identity/v2_0/test_user.py +++ b/openstackclient/tests/identity/v2_0/test_user.py @@ -13,18 +13,23 @@ # under the License. # -import copy import mock from keystoneauth1 import exceptions as ks_exc +from osc_lib import exceptions from openstackclient.identity.v2_0 import user -from openstackclient.tests import fakes from openstackclient.tests.identity.v2_0 import fakes as identity_fakes class TestUser(identity_fakes.TestIdentityv2): + fake_project = identity_fakes.FakeProject.create_one_project() + attr = { + 'tenantId': fake_project.id, + } + fake_user = identity_fakes.FakeUser.create_one_user(attr) + def setUp(self): super(TestUser, self).setUp() @@ -39,6 +44,12 @@ class TestUser(identity_fakes.TestIdentityv2): class TestUserCreate(TestUser): + fake_project_c = identity_fakes.FakeProject.create_one_project() + attr = { + 'tenantId': fake_project_c.id, + } + fake_user_c = identity_fakes.FakeUser.create_one_user(attr) + columns = ( 'email', 'enabled', @@ -47,39 +58,31 @@ class TestUserCreate(TestUser): 'project_id', ) datalist = ( - identity_fakes.user_email, + fake_user_c.email, True, - identity_fakes.user_id, - identity_fakes.user_name, - identity_fakes.project_id, + fake_user_c.id, + fake_user_c.name, + fake_project_c.id, ) def setUp(self): super(TestUserCreate, self).setUp() - self.projects_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.PROJECT), - loaded=True, - ) + self.projects_mock.get.return_value = self.fake_project_c - self.users_mock.create.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.USER), - loaded=True, - ) + self.users_mock.create.return_value = self.fake_user_c # Get the command object to test self.cmd = user.CreateUser(self.app, None) def test_user_create_no_options(self): arglist = [ - identity_fakes.user_name, + self.fake_user_c.name, ] verifylist = [ ('enable', False), ('disable', False), - ('name', identity_fakes.user_name), + ('name', self.fake_user_c.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -95,7 +98,7 @@ class TestUserCreate(TestUser): } # UserManager.create(name, password, email, tenant_id=, enabled=) self.users_mock.create.assert_called_with( - identity_fakes.user_name, + self.fake_user_c.name, None, None, **kwargs @@ -107,10 +110,10 @@ class TestUserCreate(TestUser): def test_user_create_password(self): arglist = [ '--password', 'secret', - identity_fakes.user_name, + self.fake_user_c.name, ] verifylist = [ - ('name', identity_fakes.user_name), + ('name', self.fake_user_c.name), ('password_prompt', False), ('password', 'secret') ] @@ -128,7 +131,7 @@ class TestUserCreate(TestUser): } # UserManager.create(name, password, email, tenant_id=, enabled=) self.users_mock.create.assert_called_with( - identity_fakes.user_name, + self.fake_user_c.name, 'secret', None, **kwargs @@ -139,10 +142,10 @@ class TestUserCreate(TestUser): def test_user_create_password_prompt(self): arglist = [ '--password-prompt', - identity_fakes.user_name, + self.fake_user_c.name, ] verifylist = [ - ('name', identity_fakes.user_name), + ('name', self.fake_user_c.name), ('password_prompt', True) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -152,7 +155,7 @@ class TestUserCreate(TestUser): # data to be shown. mocker = mock.Mock() mocker.return_value = 'abc123' - with mock.patch("openstackclient.common.utils.get_password", mocker): + with mock.patch("osc_lib.utils.get_password", mocker): columns, data = self.cmd.take_action(parsed_args) # Set expected values @@ -162,7 +165,7 @@ class TestUserCreate(TestUser): } # UserManager.create(name, password, email, tenant_id=, enabled=) self.users_mock.create.assert_called_with( - identity_fakes.user_name, + self.fake_user_c.name, 'abc123', None, **kwargs @@ -174,10 +177,10 @@ class TestUserCreate(TestUser): def test_user_create_email(self): arglist = [ '--email', 'barney@example.com', - identity_fakes.user_name, + self.fake_user_c.name, ] verifylist = [ - ('name', identity_fakes.user_name), + ('name', self.fake_user_c.name), ('email', 'barney@example.com'), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -194,7 +197,7 @@ class TestUserCreate(TestUser): } # UserManager.create(name, password, email, tenant_id=, enabled=) self.users_mock.create.assert_called_with( - identity_fakes.user_name, + self.fake_user_c.name, None, 'barney@example.com', **kwargs @@ -205,27 +208,22 @@ class TestUserCreate(TestUser): def test_user_create_project(self): # Return the new project - self.projects_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.PROJECT_2), - loaded=True, - ) + self.projects_mock.get.return_value = self.fake_project_c + # Set up to return an updated user - USER_2 = copy.deepcopy(identity_fakes.USER) - USER_2['tenantId'] = identity_fakes.PROJECT_2['id'] - self.users_mock.create.return_value = fakes.FakeResource( - None, - USER_2, - loaded=True, - ) + attr = { + 'tenantId': self.fake_project_c.id, + } + user_2 = identity_fakes.FakeUser.create_one_user(attr) + self.users_mock.create.return_value = user_2 arglist = [ - '--project', identity_fakes.PROJECT_2['name'], - identity_fakes.user_name, + '--project', self.fake_project_c.name, + user_2.name, ] verifylist = [ - ('name', identity_fakes.user_name), - ('project', identity_fakes.PROJECT_2['name']), + ('name', user_2.name), + ('project', self.fake_project_c.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -237,11 +235,11 @@ class TestUserCreate(TestUser): # Set expected values kwargs = { 'enabled': True, - 'tenant_id': identity_fakes.PROJECT_2['id'], + 'tenant_id': self.fake_project_c.id, } # UserManager.create(name, password, email, tenant_id=, enabled=) self.users_mock.create.assert_called_with( - identity_fakes.user_name, + user_2.name, None, None, **kwargs @@ -249,21 +247,21 @@ class TestUserCreate(TestUser): self.assertEqual(self.columns, columns) datalist = ( - identity_fakes.user_email, + user_2.email, True, - identity_fakes.user_id, - identity_fakes.user_name, - identity_fakes.PROJECT_2['id'], + user_2.id, + user_2.name, + self.fake_project_c.id, ) self.assertEqual(datalist, data) def test_user_create_enable(self): arglist = [ '--enable', - identity_fakes.user_name, + self.fake_user_c.name, ] verifylist = [ - ('name', identity_fakes.user_name), + ('name', self.fake_user_c.name), ('enable', True), ('disable', False), ] @@ -281,7 +279,7 @@ class TestUserCreate(TestUser): } # UserManager.create(name, password, email, tenant_id=, enabled=) self.users_mock.create.assert_called_with( - identity_fakes.user_name, + self.fake_user_c.name, None, None, **kwargs @@ -293,10 +291,10 @@ class TestUserCreate(TestUser): def test_user_create_disable(self): arglist = [ '--disable', - identity_fakes.user_name, + self.fake_user_c.name, ] verifylist = [ - ('name', identity_fakes.user_name), + ('name', self.fake_user_c.name), ('enable', False), ('disable', True), ] @@ -314,7 +312,7 @@ class TestUserCreate(TestUser): } # UserManager.create(name, password, email, tenant_id=, enabled=) self.users_mock.create.assert_called_with( - identity_fakes.user_name, + self.fake_user_c.name, None, None, **kwargs @@ -330,18 +328,14 @@ class TestUserCreate(TestUser): # need to make this throw an exception... self.users_mock.create.side_effect = _raise_conflict - self.users_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.USER), - loaded=True, - ) + self.users_mock.get.return_value = self.fake_user_c arglist = [ '--or-show', - identity_fakes.user_name, + self.fake_user_c.name, ] verifylist = [ - ('name', identity_fakes.user_name), + ('name', self.fake_user_c.name), ('or_show', True), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -352,7 +346,7 @@ class TestUserCreate(TestUser): columns, data = self.cmd.take_action(parsed_args) # UserManager.create(name, password, email, tenant_id=, enabled=) - self.users_mock.get.assert_called_with(identity_fakes.user_name) + self.users_mock.get.assert_called_with(self.fake_user_c.name) self.assertEqual(self.columns, columns) self.assertEqual(self.datalist, data) @@ -360,10 +354,10 @@ class TestUserCreate(TestUser): def test_user_create_or_show_not_exists(self): arglist = [ '--or-show', - identity_fakes.user_name, + self.fake_user_c.name, ] verifylist = [ - ('name', identity_fakes.user_name), + ('name', self.fake_user_c.name), ('or_show', True), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -380,7 +374,7 @@ class TestUserCreate(TestUser): } # UserManager.create(name, password, email, tenant_id=, enabled=) self.users_mock.create.assert_called_with( - identity_fakes.user_name, + self.fake_user_c.name, None, None, **kwargs @@ -395,11 +389,7 @@ class TestUserDelete(TestUser): super(TestUserDelete, self).setUp() # This is the return value for utils.find_resource() - self.users_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.USER), - loaded=True, - ) + self.users_mock.get.return_value = self.fake_user self.users_mock.delete.return_value = None # Get the command object to test @@ -407,57 +397,47 @@ class TestUserDelete(TestUser): def test_user_delete_no_options(self): arglist = [ - identity_fakes.user_id, + self.fake_user.id, ] verifylist = [ - ('users', [identity_fakes.user_id]), + ('users', [self.fake_user.id]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) self.users_mock.delete.assert_called_with( - identity_fakes.user_id, + self.fake_user.id, ) self.assertIsNone(result) class TestUserList(TestUser): + fake_project_l = identity_fakes.FakeProject.create_one_project() + attr = { + 'tenantId': fake_project_l.id, + } + fake_user_l = identity_fakes.FakeUser.create_one_user(attr) + columns = ( 'ID', 'Name', ) datalist = ( ( - identity_fakes.user_id, - identity_fakes.user_name, + fake_user_l.id, + fake_user_l.name, ), ) def setUp(self): super(TestUserList, self).setUp() - self.projects_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.PROJECT_2), - loaded=True, - ) - self.projects_mock.list.return_value = [ - fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.PROJECT), - loaded=True, - ), - ] + self.projects_mock.get.return_value = self.fake_project_l + self.projects_mock.list.return_value = [self.fake_project_l] - self.users_mock.list.return_value = [ - fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.USER), - loaded=True, - ), - ] + self.users_mock.list.return_value = [self.fake_user_l] # Get the command object to test self.cmd = user.ListUser(self.app, None) @@ -479,13 +459,13 @@ class TestUserList(TestUser): def test_user_list_project(self): arglist = [ - '--project', identity_fakes.project_id, + '--project', self.fake_project_l.id, ] verifylist = [ - ('project', identity_fakes.project_id), + ('project', self.fake_project_l.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - project_id = identity_fakes.PROJECT_2['id'] + project_id = self.fake_project_l.id # In base command class Lister in cliff, abstract method take_action() # returns a tuple containing the column names and an iterable @@ -516,10 +496,10 @@ class TestUserList(TestUser): collist = ('ID', 'Name', 'Project', 'Email', 'Enabled') self.assertEqual(collist, columns) datalist = (( - identity_fakes.user_id, - identity_fakes.user_name, - identity_fakes.project_name, - identity_fakes.user_email, + self.fake_user_l.id, + self.fake_user_l.name, + self.fake_project_l.name, + self.fake_user_l.email, True, ), ) self.assertEqual(datalist, tuple(data)) @@ -530,23 +510,15 @@ class TestUserSet(TestUser): def setUp(self): super(TestUserSet, self).setUp() - self.projects_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.PROJECT), - loaded=True, - ) - self.users_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.USER), - loaded=True, - ) + self.projects_mock.get.return_value = self.fake_project + self.users_mock.get.return_value = self.fake_user # Get the command object to test self.cmd = user.SetUser(self.app, None) def test_user_set_no_options(self): arglist = [ - identity_fakes.user_name, + self.fake_user.name, ] verifylist = [ ('name', None), @@ -555,7 +527,7 @@ class TestUserSet(TestUser): ('project', None), ('enable', False), ('disable', False), - ('user', identity_fakes.user_name), + ('user', self.fake_user.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -563,10 +535,31 @@ class TestUserSet(TestUser): self.assertIsNone(result) + def test_user_set_unexist_user(self): + arglist = [ + "unexist-user", + ] + verifylist = [ + ('name', None), + ('password', None), + ('email', None), + ('project', None), + ('enable', False), + ('disable', False), + ('user', "unexist-user"), + ] + self.users_mock.get.side_effect = exceptions.NotFound(None) + self.users_mock.find.side_effect = exceptions.NotFound(None) + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.assertRaises( + exceptions.CommandError, self.cmd.take_action, parsed_args) + def test_user_set_name(self): arglist = [ '--name', 'qwerty', - identity_fakes.user_name, + self.fake_user.name, ] verifylist = [ ('name', 'qwerty'), @@ -575,7 +568,7 @@ class TestUserSet(TestUser): ('project', None), ('enable', False), ('disable', False), - ('user', identity_fakes.user_name), + ('user', self.fake_user.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -588,7 +581,7 @@ class TestUserSet(TestUser): } # UserManager.update(user, **kwargs) self.users_mock.update.assert_called_with( - identity_fakes.user_id, + self.fake_user.id, **kwargs ) self.assertIsNone(result) @@ -596,7 +589,7 @@ class TestUserSet(TestUser): def test_user_set_password(self): arglist = [ '--password', 'secret', - identity_fakes.user_name, + self.fake_user.name, ] verifylist = [ ('name', None), @@ -606,7 +599,7 @@ class TestUserSet(TestUser): ('project', None), ('enable', False), ('disable', False), - ('user', identity_fakes.user_name), + ('user', self.fake_user.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -614,7 +607,7 @@ class TestUserSet(TestUser): # UserManager.update_password(user, password) self.users_mock.update_password.assert_called_with( - identity_fakes.user_id, + self.fake_user.id, 'secret', ) self.assertIsNone(result) @@ -622,7 +615,7 @@ class TestUserSet(TestUser): def test_user_set_password_prompt(self): arglist = [ '--password-prompt', - identity_fakes.user_name, + self.fake_user.name, ] verifylist = [ ('name', None), @@ -632,18 +625,18 @@ class TestUserSet(TestUser): ('project', None), ('enable', False), ('disable', False), - ('user', identity_fakes.user_name), + ('user', self.fake_user.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) mocker = mock.Mock() mocker.return_value = 'abc123' - with mock.patch("openstackclient.common.utils.get_password", mocker): + with mock.patch("osc_lib.utils.get_password", mocker): result = self.cmd.take_action(parsed_args) # UserManager.update_password(user, password) self.users_mock.update_password.assert_called_with( - identity_fakes.user_id, + self.fake_user.id, 'abc123', ) self.assertIsNone(result) @@ -651,7 +644,7 @@ class TestUserSet(TestUser): def test_user_set_email(self): arglist = [ '--email', 'barney@example.com', - identity_fakes.user_name, + self.fake_user.name, ] verifylist = [ ('name', None), @@ -660,7 +653,7 @@ class TestUserSet(TestUser): ('project', None), ('enable', False), ('disable', False), - ('user', identity_fakes.user_name), + ('user', self.fake_user.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -673,24 +666,24 @@ class TestUserSet(TestUser): } # UserManager.update(user, **kwargs) self.users_mock.update.assert_called_with( - identity_fakes.user_id, + self.fake_user.id, **kwargs ) self.assertIsNone(result) def test_user_set_project(self): arglist = [ - '--project', identity_fakes.project_id, - identity_fakes.user_name, + '--project', self.fake_project.id, + self.fake_user.name, ] verifylist = [ ('name', None), ('password', None), ('email', None), - ('project', identity_fakes.project_id), + ('project', self.fake_project.id), ('enable', False), ('disable', False), - ('user', identity_fakes.user_name), + ('user', self.fake_user.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -698,15 +691,15 @@ class TestUserSet(TestUser): # UserManager.update_tenant(user, tenant) self.users_mock.update_tenant.assert_called_with( - identity_fakes.user_id, - identity_fakes.project_id, + self.fake_user.id, + self.fake_project.id, ) self.assertIsNone(result) def test_user_set_enable(self): arglist = [ '--enable', - identity_fakes.user_name, + self.fake_user.name, ] verifylist = [ ('name', None), @@ -715,7 +708,7 @@ class TestUserSet(TestUser): ('project', None), ('enable', True), ('disable', False), - ('user', identity_fakes.user_name), + ('user', self.fake_user.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -727,7 +720,7 @@ class TestUserSet(TestUser): } # UserManager.update(user, **kwargs) self.users_mock.update.assert_called_with( - identity_fakes.user_id, + self.fake_user.id, **kwargs ) self.assertIsNone(result) @@ -735,7 +728,7 @@ class TestUserSet(TestUser): def test_user_set_disable(self): arglist = [ '--disable', - identity_fakes.user_name, + self.fake_user.name, ] verifylist = [ ('name', None), @@ -744,7 +737,7 @@ class TestUserSet(TestUser): ('project', None), ('enable', False), ('disable', True), - ('user', identity_fakes.user_name), + ('user', self.fake_user.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -756,7 +749,7 @@ class TestUserSet(TestUser): } # UserManager.update(user, **kwargs) self.users_mock.update.assert_called_with( - identity_fakes.user_id, + self.fake_user.id, **kwargs ) self.assertIsNone(result) @@ -767,21 +760,17 @@ class TestUserShow(TestUser): def setUp(self): super(TestUserShow, self).setUp() - self.users_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.USER), - loaded=True, - ) + self.users_mock.get.return_value = self.fake_user # Get the command object to test self.cmd = user.ShowUser(self.app, None) def test_user_show(self): arglist = [ - identity_fakes.user_id, + self.fake_user.id, ] verifylist = [ - ('user', identity_fakes.user_id), + ('user', self.fake_user.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -790,15 +779,15 @@ class TestUserShow(TestUser): # data to be shown. columns, data = self.cmd.take_action(parsed_args) - self.users_mock.get.assert_called_with(identity_fakes.user_id) + self.users_mock.get.assert_called_with(self.fake_user.id) collist = ('email', 'enabled', 'id', 'name', 'project_id') self.assertEqual(collist, columns) datalist = ( - identity_fakes.user_email, + self.fake_user.email, True, - identity_fakes.user_id, - identity_fakes.user_name, - identity_fakes.project_id, + self.fake_user.id, + self.fake_user.name, + self.fake_project.id, ) self.assertEqual(datalist, data) diff --git a/openstackclient/tests/identity/v3/fakes.py b/openstackclient/tests/identity/v3/fakes.py index 1422166a..dd918616 100644 --- a/openstackclient/tests/identity/v3/fakes.py +++ b/openstackclient/tests/identity/v3/fakes.py @@ -13,8 +13,12 @@ # under the License. # +import copy import mock +from keystoneauth1 import access +from keystoneauth1 import fixture + from openstackclient.tests import fakes from openstackclient.tests import utils @@ -419,6 +423,36 @@ OAUTH_VERIFIER = { } +def fake_auth_ref(fake_token, fake_service=None): + """Create an auth_ref using keystoneauth's fixtures""" + token_copy = copy.deepcopy(fake_token) + token_id = token_copy.pop('id') + token = fixture.V3Token(**token_copy) + # An auth_ref is actually an access info object + auth_ref = access.create( + body=token, + auth_token=token_id, + ) + + # Create a service catalog + if fake_service: + service = token.add_service( + fake_service['type'], + fake_service['name'], + ) + # TODO(dtroyer): Add an 'id' element to KSA's _Service fixure + service['id'] = fake_service['id'] + for e in fake_service['endpoints']: + region = e.get('region_id') or e.get('region', '<none>') + service.add_endpoint( + e['interface'], + e['url'], + region=region, + ) + + return auth_ref + + class FakeAuth(object): def __init__(self, auth_method_class=None): @@ -468,6 +502,9 @@ class FakeIdentityv3Client(object): self.role_assignments.resource_class = fakes.FakeResource(None, {}) self.auth_token = kwargs['token'] self.management_url = kwargs['endpoint'] + self.auth = FakeAuth() + self.auth.client = mock.Mock() + self.auth.client.resource_class = fakes.FakeResource(None, {}) class FakeFederationManager(object): diff --git a/openstackclient/tests/identity/v3/test_catalog.py b/openstackclient/tests/identity/v3/test_catalog.py index 1b8fa085..e3c5ed3d 100644 --- a/openstackclient/tests/identity/v3/test_catalog.py +++ b/openstackclient/tests/identity/v3/test_catalog.py @@ -14,6 +14,7 @@ import mock from openstackclient.identity.v3 import catalog +from openstackclient.tests.identity.v3 import fakes as identity_fakes from openstackclient.tests import utils @@ -50,7 +51,7 @@ class TestCatalog(utils.TestCommand): super(TestCatalog, self).setUp() self.sc_mock = mock.MagicMock() - self.sc_mock.service_catalog.get_data.return_value = [ + self.sc_mock.service_catalog.catalog.return_value = [ self.fake_service, ] @@ -69,6 +70,13 @@ class TestCatalogList(TestCatalog): self.cmd = catalog.ListCatalog(self.app, None) def test_catalog_list(self): + auth_ref = identity_fakes.fake_auth_ref( + identity_fakes.TOKEN_WITH_PROJECT_ID, + fake_service=self.fake_service, + ) + self.ar_mock = mock.PropertyMock(return_value=auth_ref) + type(self.app.client_manager).auth_ref = self.ar_mock + arglist = [] verifylist = [] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -77,7 +85,6 @@ class TestCatalogList(TestCatalog): # returns a tuple containing the column names and an iterable # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) - self.sc_mock.service_catalog.get_data.assert_called_with() collist = ('Name', 'Type', 'Endpoints') self.assertEqual(collist, columns) @@ -101,6 +108,13 @@ class TestCatalogShow(TestCatalog): self.cmd = catalog.ShowCatalog(self.app, None) def test_catalog_show(self): + auth_ref = identity_fakes.fake_auth_ref( + identity_fakes.TOKEN_WITH_PROJECT_ID, + fake_service=self.fake_service, + ) + self.ar_mock = mock.PropertyMock(return_value=auth_ref) + type(self.app.client_manager).auth_ref = self.ar_mock + arglist = [ 'compute', ] @@ -113,7 +127,6 @@ class TestCatalogShow(TestCatalog): # returns a two-part tuple with a tuple of column names and a tuple of # data to be shown. columns, data = self.cmd.take_action(parsed_args) - self.sc_mock.service_catalog.get_data.assert_called_with() collist = ('endpoints', 'id', 'name', 'type') self.assertEqual(collist, columns) diff --git a/openstackclient/tests/identity/v3/test_domain.py b/openstackclient/tests/identity/v3/test_domain.py index f3777f12..e06e0681 100644 --- a/openstackclient/tests/identity/v3/test_domain.py +++ b/openstackclient/tests/identity/v3/test_domain.py @@ -389,6 +389,16 @@ class TestDomainShow(TestDomain): ('domain', identity_fakes.domain_id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.app.client_manager.identity.tokens.get_token_data.return_value = \ + {'token': + {'project': + {'domain': + {'id': 'd1', + 'name': 'd1' + } + } + } + } # In base command class ShowOne in cliff, abstract method take_action() # returns a two-part tuple with a tuple of column names and a tuple of diff --git a/openstackclient/tests/identity/v3/test_mappings.py b/openstackclient/tests/identity/v3/test_mappings.py index b9e3b1d5..af7b135d 100644 --- a/openstackclient/tests/identity/v3/test_mappings.py +++ b/openstackclient/tests/identity/v3/test_mappings.py @@ -16,7 +16,8 @@ import copy import mock -from openstackclient.common import exceptions +from osc_lib import exceptions + from openstackclient.identity.v3 import mapping from openstackclient.tests import fakes from openstackclient.tests.identity.v3 import fakes as identity_fakes diff --git a/openstackclient/tests/identity/v3/test_project.py b/openstackclient/tests/identity/v3/test_project.py index 1e9d1c8b..93bf18af 100644 --- a/openstackclient/tests/identity/v3/test_project.py +++ b/openstackclient/tests/identity/v3/test_project.py @@ -16,7 +16,8 @@ import copy import mock -from openstackclient.common import exceptions +from osc_lib import exceptions + from openstackclient.identity.v3 import project from openstackclient.tests import fakes from openstackclient.tests.identity.v3 import fakes as identity_fakes @@ -201,7 +202,7 @@ class TestProjectCreate(TestProject): mocker = mock.Mock() mocker.return_value = None - with mock.patch("openstackclient.common.utils.find_resource", mocker): + with mock.patch("osc_lib.utils.find_resource", mocker): columns, data = self.cmd.take_action(parsed_args) # Set expected values @@ -548,7 +549,7 @@ class TestProjectList(TestProject): mocker = mock.Mock() mocker.return_value = None - with mock.patch("openstackclient.common.utils.find_resource", mocker): + with mock.patch("osc_lib.utils.find_resource", mocker): columns, data = self.cmd.take_action(parsed_args) self.projects_mock.list.assert_called_with( @@ -748,6 +749,7 @@ class TestProjectShow(TestProject): self.cmd = project.ShowProject(self.app, None) def test_project_show(self): + arglist = [ identity_fakes.project_id, ] @@ -756,6 +758,16 @@ class TestProjectShow(TestProject): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.app.client_manager.identity.tokens.get_token_data.return_value = \ + {'token': + {'project': + {'domain': {}, + 'name': parsed_args.project, + 'id': parsed_args.project + } + } + } + # In base command class ShowOne in cliff, abstract method take_action() # returns a two-part tuple with a tuple of column names and a tuple of # data to be shown. @@ -796,6 +808,15 @@ class TestProjectShow(TestProject): ('children', False), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.app.client_manager.identity.tokens.get_token_data.return_value = \ + {'token': + {'project': + {'domain': {}, + 'name': parsed_args.project, + 'id': parsed_args.project + } + } + } columns, data = self.cmd.take_action(parsed_args) self.projects_mock.get.assert_called_with( @@ -844,6 +865,15 @@ class TestProjectShow(TestProject): ('children', True), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.app.client_manager.identity.tokens.get_token_data.return_value = \ + {'token': + {'project': + {'domain': {}, + 'name': parsed_args.project, + 'id': parsed_args.project + } + } + } columns, data = self.cmd.take_action(parsed_args) self.projects_mock.get.assert_called_with( @@ -894,6 +924,15 @@ class TestProjectShow(TestProject): ('children', True), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.app.client_manager.identity.tokens.get_token_data.return_value = \ + {'token': + {'project': + {'domain': {}, + 'name': parsed_args.project, + 'id': parsed_args.project + } + } + } columns, data = self.cmd.take_action(parsed_args) self.projects_mock.get.assert_called_with( diff --git a/openstackclient/tests/identity/v3/test_token.py b/openstackclient/tests/identity/v3/test_token.py index b68bc242..9728c6e1 100644 --- a/openstackclient/tests/identity/v3/test_token.py +++ b/openstackclient/tests/identity/v3/test_token.py @@ -24,10 +24,9 @@ class TestToken(identity_fakes.TestIdentityv3): def setUp(self): super(TestToken, self).setUp() - # Get a shortcut to the Service Catalog Mock - self.sc_mock = mock.Mock() - self.app.client_manager.auth_ref = mock.Mock() - self.app.client_manager.auth_ref.service_catalog = self.sc_mock + # Get a shortcut to the Auth Ref Mock + self.ar_mock = mock.PropertyMock() + type(self.app.client_manager).auth_ref = self.ar_mock class TestTokenIssue(TestToken): @@ -38,23 +37,25 @@ class TestTokenIssue(TestToken): self.cmd = token.IssueToken(self.app, None) def test_token_issue_with_project_id(self): + auth_ref = identity_fakes.fake_auth_ref( + identity_fakes.TOKEN_WITH_PROJECT_ID, + ) + self.ar_mock = mock.PropertyMock(return_value=auth_ref) + type(self.app.client_manager).auth_ref = self.ar_mock + arglist = [] verifylist = [] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.sc_mock.get_token.return_value = \ - identity_fakes.TOKEN_WITH_PROJECT_ID # In base command class ShowOne in cliff, abstract method take_action() # returns a two-part tuple with a tuple of column names and a tuple of # data to be shown. columns, data = self.cmd.take_action(parsed_args) - self.sc_mock.get_token.assert_called_with() - collist = ('expires', 'id', 'project_id', 'user_id') self.assertEqual(collist, columns) datalist = ( - identity_fakes.token_expires, + auth_ref.expires, identity_fakes.token_id, identity_fakes.project_id, identity_fakes.user_id, @@ -62,45 +63,53 @@ class TestTokenIssue(TestToken): self.assertEqual(datalist, data) def test_token_issue_with_domain_id(self): + auth_ref = identity_fakes.fake_auth_ref( + identity_fakes.TOKEN_WITH_DOMAIN_ID, + ) + self.ar_mock = mock.PropertyMock(return_value=auth_ref) + type(self.app.client_manager).auth_ref = self.ar_mock + arglist = [] verifylist = [] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.sc_mock.get_token.return_value = \ - identity_fakes.TOKEN_WITH_DOMAIN_ID # In base command class ShowOne in cliff, abstract method take_action() # returns a two-part tuple with a tuple of column names and a tuple of # data to be shown. columns, data = self.cmd.take_action(parsed_args) - self.sc_mock.get_token.assert_called_with() - collist = ('domain_id', 'expires', 'id', 'user_id') self.assertEqual(collist, columns) datalist = ( identity_fakes.domain_id, - identity_fakes.token_expires, + auth_ref.expires, identity_fakes.token_id, identity_fakes.user_id, ) self.assertEqual(datalist, data) def test_token_issue_with_unscoped(self): + auth_ref = identity_fakes.fake_auth_ref( + identity_fakes.UNSCOPED_TOKEN, + ) + self.ar_mock = mock.PropertyMock(return_value=auth_ref) + type(self.app.client_manager).auth_ref = self.ar_mock + arglist = [] verifylist = [] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.sc_mock.get_token.return_value = \ - identity_fakes.UNSCOPED_TOKEN # DisplayCommandBase.take_action() returns two tuples columns, data = self.cmd.take_action(parsed_args) - self.sc_mock.get_token.assert_called_with() - - collist = ('expires', 'id', 'user_id') + collist = ( + 'expires', + 'id', + 'user_id', + ) self.assertEqual(collist, columns) datalist = ( - identity_fakes.token_expires, + auth_ref.expires, identity_fakes.token_id, identity_fakes.user_id, ) diff --git a/openstackclient/tests/identity/v3/test_unscoped_saml.py b/openstackclient/tests/identity/v3/test_unscoped_saml.py index d12cb454..62623902 100644 --- a/openstackclient/tests/identity/v3/test_unscoped_saml.py +++ b/openstackclient/tests/identity/v3/test_unscoped_saml.py @@ -12,7 +12,8 @@ import copy -from openstackclient.common import exceptions +from osc_lib import exceptions + from openstackclient.identity.v3 import unscoped_saml from openstackclient.tests import fakes from openstackclient.tests.identity.v3 import fakes as identity_fakes diff --git a/openstackclient/tests/identity/v3/test_user.py b/openstackclient/tests/identity/v3/test_user.py index 5dafa772..c4fb1521 100644 --- a/openstackclient/tests/identity/v3/test_user.py +++ b/openstackclient/tests/identity/v3/test_user.py @@ -185,7 +185,7 @@ class TestUserCreate(TestUser): # data to be shown. mocker = mock.Mock() mocker.return_value = 'abc123' - with mock.patch("openstackclient.common.utils.get_password", mocker): + with mock.patch("osc_lib.utils.get_password", mocker): columns, data = self.cmd.take_action(parsed_args) # Set expected values @@ -841,7 +841,7 @@ class TestUserSet(TestUser): mocker = mock.Mock() mocker.return_value = 'abc123' - with mock.patch("openstackclient.common.utils.get_password", mocker): + with mock.patch("osc_lib.utils.get_password", mocker): result = self.cmd.take_action(parsed_args) # Set expected values @@ -1023,7 +1023,7 @@ class TestUserSetPassword(TestUser): @contextlib.contextmanager def _mock_get_password(*passwords): mocker = mock.Mock(side_effect=passwords) - with mock.patch("openstackclient.common.utils.get_password", mocker): + with mock.patch("osc_lib.utils.get_password", mocker): yield def test_user_password_change(self): @@ -1094,6 +1094,17 @@ class TestUserShow(TestUser): # Get the command object to test self.cmd = user.ShowUser(self.app, None) + self.app.client_manager.identity.auth.client.get_user_id.\ + return_value = 'bbbbbbb-aaaa-aaaa-aaaa-bbbbbbbaaaa' + self.app.client_manager.identity.tokens.get_token_data.return_value = \ + {'token': + {'user': + {'domain': {}, + 'id': 'bbbbbbb-aaaa-aaaa-aaaa-bbbbbbbaaaa', + 'name': 'bbbbbbb-aaaa-aaaa-aaaa-bbbbbbbaaaa' + } + } + } def test_user_show(self): arglist = [ diff --git a/openstackclient/tests/image/v1/test_image.py b/openstackclient/tests/image/v1/test_image.py index 018e1199..14aa331f 100644 --- a/openstackclient/tests/image/v1/test_image.py +++ b/openstackclient/tests/image/v1/test_image.py @@ -16,7 +16,8 @@ import copy import mock -from openstackclient.common import exceptions +from osc_lib import exceptions + from openstackclient.image.v1 import image from openstackclient.tests import fakes from openstackclient.tests.image.v1 import fakes as image_fakes @@ -416,7 +417,7 @@ class TestImageList(TestImage): self.assertEqual(self.columns, columns) self.assertEqual(self.datalist, tuple(data)) - @mock.patch('openstackclient.common.utils.sort_items') + @mock.patch('osc_lib.utils.sort_items') def test_image_list_sort_option(self, si_mock): si_mock.side_effect = [ [copy.deepcopy(image_fakes.IMAGE)], [], @@ -474,8 +475,8 @@ class TestImageSet(TestImage): result = self.cmd.take_action(parsed_args) - # Verify update() was not called, if it was show the args - self.assertEqual(self.images_mock.update.call_args_list, []) + self.images_mock.update.assert_called_with(image_fakes.image_id, + **{}) self.assertIsNone(result) def test_image_set_options(self): diff --git a/openstackclient/tests/image/v2/fakes.py b/openstackclient/tests/image/v2/fakes.py index 24aaec51..8e22fbb2 100644 --- a/openstackclient/tests/image/v2/fakes.py +++ b/openstackclient/tests/image/v2/fakes.py @@ -19,13 +19,12 @@ import random import uuid from glanceclient.v2 import schemas +from osc_lib import utils as common_utils import warlock -from openstackclient.common import utils as common_utils from openstackclient.tests import fakes -from openstackclient.tests import utils - from openstackclient.tests.identity.v3 import fakes as identity_fakes +from openstackclient.tests import utils image_id = '0f41529e-7c12-4de8-be2d-181abb825b3c' image_name = 'graven' diff --git a/openstackclient/tests/image/v2/test_image.py b/openstackclient/tests/image/v2/test_image.py index ca20d83d..592def21 100644 --- a/openstackclient/tests/image/v2/test_image.py +++ b/openstackclient/tests/image/v2/test_image.py @@ -19,8 +19,9 @@ import mock import warlock from glanceclient.v2 import schemas -from openstackclient.common import exceptions -from openstackclient.common import utils as common_utils +from osc_lib import exceptions +from osc_lib import utils as common_utils + from openstackclient.image.v2 import image from openstackclient.tests import fakes from openstackclient.tests.identity.v3 import fakes as identity_fakes @@ -473,6 +474,37 @@ class TestImageDelete(TestImage): self.images_mock.delete.assert_has_calls(calls) self.assertIsNone(result) + def test_image_delete_multi_images_exception(self): + + images = image_fakes.FakeImage.create_images(count=2) + arglist = [ + images[0].id, + images[1].id, + 'x-y-x', + ] + verifylist = [ + ('images', arglist) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # Fake exception in utils.find_resource() + # In image v2, we use utils.find_resource() to find a network. + # It calls get() several times, but find() only one time. So we + # choose to fake get() always raise exception, then pass through. + # And fake find() to find the real network or not. + ret_find = [ + images[0], + images[1], + exceptions.NotFound('404'), + ] + + self.images_mock.get = Exception() + self.images_mock.find.side_effect = ret_find + self.assertRaises(exceptions.CommandError, self.cmd.take_action, + parsed_args) + calls = [mock.call(i.id) for i in images] + self.images_mock.delete.assert_has_calls(calls) + class TestImageList(TestImage): @@ -660,7 +692,7 @@ class TestImageList(TestImage): self.assertEqual(self.columns, columns) self.assertEqual(self.datalist, tuple(data)) - @mock.patch('openstackclient.common.utils.sort_items') + @mock.patch('osc_lib.utils.sort_items') def test_image_list_sort_option(self, si_mock): si_mock.return_value = [copy.deepcopy(self._image)] @@ -697,7 +729,7 @@ class TestImageList(TestImage): self.assertEqual(self.columns, columns) self.assertEqual(len(self.datalist), len(tuple(data))) - @mock.patch('openstackclient.common.utils.find_resource') + @mock.patch('osc_lib.utils.find_resource') def test_image_list_marker_option(self, fr_mock): # tangchen: Since image_fakes.IMAGE is a dict, it cannot offer a .id # operation. Will fix this by using FakeImage class instead @@ -810,6 +842,19 @@ class TestImageSet(TestImage): # Get the command object to test self.cmd = image.SetImage(self.app, None) + def test_image_set_no_options(self): + arglist = [ + image_fakes.image_id, + ] + verifylist = [ + ('image', image_fakes.image_id) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.assertIsNone(result) + def test_image_set_options(self): arglist = [ '--name', 'new-name', @@ -1210,6 +1255,19 @@ class TestImageUnset(TestImage): # Get the command object to test self.cmd = image.UnsetImage(self.app, None) + def test_image_unset_no_options(self): + arglist = [ + image_fakes.image_id, + ] + verifylist = [ + ('image', image_fakes.image_id) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.assertIsNone(result) + def test_image_unset_tag_option(self): arglist = [ diff --git a/openstackclient/tests/network/v2/fakes.py b/openstackclient/tests/network/v2/fakes.py index ccbe395b..50d9899c 100644 --- a/openstackclient/tests/network/v2/fakes.py +++ b/openstackclient/tests/network/v2/fakes.py @@ -19,12 +19,6 @@ import uuid from openstackclient.tests import fakes from openstackclient.tests import utils -extension_name = 'Matrix' -extension_namespace = 'http://docs.openstack.org/network/' -extension_description = 'Simulated reality' -extension_updated = '2013-07-09T12:00:0-00:00' -extension_alias = 'Dystopian' -extension_links = '[{"href":''"https://github.com/os/network", "type"}]' QUOTA = { "subnet": 10, @@ -42,21 +36,11 @@ QUOTA = { } -def create_extension(): - extension = mock.Mock() - extension.name = extension_name - extension.namespace = extension_namespace - extension.description = extension_description - extension.updated = extension_updated - extension.alias = extension_alias - extension.links = extension_links - return extension - - class FakeNetworkV2Client(object): def __init__(self, **kwargs): - self.extensions = mock.Mock(return_value=[create_extension()]) + self.extensions = mock.Mock() + self.extensions.resource_class = fakes.FakeResource(None, {}) class TestNetworkV2(utils.TestCommand): @@ -240,6 +224,39 @@ class FakeIPAvailability(object): return network_ip_availabilities +class FakeExtension(object): + """Fake one or more extension.""" + + @staticmethod + def create_one_extension(attrs=None): + """Create a fake extension. + + :param Dictionary attrs: + A dictionary with all attributes + :return: + A FakeResource object with name, namespace, etc. + """ + attrs = attrs or {} + + # Set default attributes. + extension_info = { + 'name': 'name-' + uuid.uuid4().hex, + 'namespace': 'http://docs.openstack.org/network/', + 'description': 'description-' + uuid.uuid4().hex, + 'updated': '2013-07-09T12:00:0-00:00', + 'alias': 'Dystopian', + 'links': '[{"href":''"https://github.com/os/network", "type"}]', + } + + # Overwrite default attributes. + extension_info.update(attrs) + + extension = fakes.FakeResource( + info=copy.deepcopy(extension_info), + loaded=True) + return extension + + class FakeNetwork(object): """Fake one or more networks.""" @@ -335,7 +352,7 @@ class FakeNetworkSegment(object): # Set default attributes. network_segment_attrs = { - 'id': 'segment-id-' + uuid.uuid4().hex, + 'id': 'network-segment-id-' + uuid.uuid4().hex, 'network_id': 'network-id-' + uuid.uuid4().hex, 'network_type': 'vlan', 'physical_network': 'physical-network-name-' + uuid.uuid4().hex, @@ -594,6 +611,25 @@ class FakeSecurityGroup(object): return security_groups + @staticmethod + def get_security_groups(security_groups=None, count=2): + """Get an iterable MagicMock object with a list of faked security groups. + + If security groups list is provided, then initialize the Mock object + with the list. Otherwise create one. + + :param List security groups: + A list of FakeResource objects faking security groups + :param int count: + The number of security groups to fake + :return: + An iterable Mock object with side_effect set to a list of faked + security groups + """ + if security_groups is None: + security_groups = FakeSecurityGroup.create_security_groups(count) + return mock.MagicMock(side_effect=security_groups) + class FakeSecurityGroupRule(object): """Fake one or more security group rules.""" @@ -653,6 +689,26 @@ class FakeSecurityGroupRule(object): return security_group_rules + @staticmethod + def get_security_group_rules(security_group_rules=None, count=2): + """Get an iterable MagicMock object with a list of faked security group rules. + + If security group rules list is provided, then initialize the Mock + object with the list. Otherwise create one. + + :param List security group rules: + A list of FakeResource objects faking security group rules + :param int count: + The number of security group rules to fake + :return: + An iterable Mock object with side_effect set to a list of faked + security group rules + """ + if security_group_rules is None: + security_group_rules = ( + FakeSecurityGroupRule.create_security_group_rules(count)) + return mock.MagicMock(side_effect=security_group_rules) + class FakeSubnet(object): """Fake one or more subnets.""" @@ -682,9 +738,10 @@ class FakeSubnet(object): 'host_routes': [], 'ip_version': 4, 'gateway_ip': '10.10.10.1', - 'ipv6_address_mode': 'None', - 'ipv6_ra_mode': 'None', - 'subnetpool_id': 'None', + 'ipv6_address_mode': None, + 'ipv6_ra_mode': None, + 'segment_id': None, + 'subnetpool_id': None, } # Overwrite default attributes. @@ -715,6 +772,25 @@ class FakeSubnet(object): return subnets + @staticmethod + def get_subnets(subnets=None, count=2): + """Get an iterable MagicMock object with a list of faked subnets. + + If subnets list is provided, then initialize the Mock object + with the list. Otherwise create one. + + :param List subnets: + A list of FakeResource objects faking subnets + :param int count: + The number of subnets to fake + :return: + An iterable Mock object with side_effect set to a list of faked + subnets + """ + if subnets is None: + subnets = FakeSubnet.create_subnets(count) + return mock.MagicMock(side_effect=subnets) + class FakeFloatingIP(object): """Fake one or more floating ip.""" @@ -854,3 +930,22 @@ class FakeSubnetPool(object): ) return subnet_pools + + @staticmethod + def get_subnet_pools(subnet_pools=None, count=2): + """Get an iterable MagicMock object with a list of faked subnet pools. + + If subnet_pools list is provided, then initialize the Mock object + with the list. Otherwise create one. + + :param List subnet pools: + A list of FakeResource objects faking subnet pools + :param int count: + The number of subnet pools to fake + :return: + An iterable Mock object with side_effect set to a list of faked + subnet pools + """ + if subnet_pools is None: + subnet_pools = FakeSubnetPool.create_subnet_pools(count) + return mock.MagicMock(side_effect=subnet_pools) diff --git a/openstackclient/tests/network/v2/test_address_scope.py b/openstackclient/tests/network/v2/test_address_scope.py index b4f4fa88..722371f9 100644 --- a/openstackclient/tests/network/v2/test_address_scope.py +++ b/openstackclient/tests/network/v2/test_address_scope.py @@ -15,7 +15,8 @@ import copy import mock from mock import call -from openstackclient.common import exceptions +from osc_lib import exceptions + from openstackclient.network.v2 import address_scope from openstackclient.tests import fakes from openstackclient.tests.identity.v3 import fakes as identity_fakes_v3 @@ -313,8 +314,12 @@ class TestSetAddressScope(TestAddressScope): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.assertRaises(exceptions.CommandError, self.cmd.take_action, - parsed_args) + result = self.cmd.take_action(parsed_args) + + attrs = {} + self.network.update_address_scope.assert_called_with( + self._address_scope, **attrs) + self.assertIsNone(result) def test_set_name_and_share(self): arglist = [ diff --git a/openstackclient/tests/network/v2/test_floating_ip.py b/openstackclient/tests/network/v2/test_floating_ip.py index f9ccfe1c..5cd5279a 100644 --- a/openstackclient/tests/network/v2/test_floating_ip.py +++ b/openstackclient/tests/network/v2/test_floating_ip.py @@ -12,6 +12,9 @@ # import mock +from mock import call + +from osc_lib import exceptions from openstackclient.network.v2 import floating_ip from openstackclient.tests.compute.v2 import fakes as compute_fakes @@ -140,33 +143,84 @@ class TestCreateFloatingIPNetwork(TestFloatingIPNetwork): class TestDeleteFloatingIPNetwork(TestFloatingIPNetwork): - # The floating ip to be deleted. - floating_ip = network_fakes.FakeFloatingIP.create_one_floating_ip() + # The floating ips to be deleted. + floating_ips = network_fakes.FakeFloatingIP.create_floating_ips(count=2) def setUp(self): super(TestDeleteFloatingIPNetwork, self).setUp() self.network.delete_ip = mock.Mock(return_value=None) - self.network.find_ip = mock.Mock(return_value=self.floating_ip) + self.network.find_ip = ( + network_fakes.FakeFloatingIP.get_floating_ips(self.floating_ips)) # Get the command object to test self.cmd = floating_ip.DeleteFloatingIP(self.app, self.namespace) def test_floating_ip_delete(self): arglist = [ - self.floating_ip.id, + self.floating_ips[0].id, ] verifylist = [ - ('floating_ip', self.floating_ip.id), + ('floating_ip', [self.floating_ips[0].id]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.network.find_ip.assert_called_once_with(self.floating_ip.id) - self.network.delete_ip.assert_called_once_with(self.floating_ip) + self.network.find_ip.assert_called_once_with( + self.floating_ips[0].id, ignore_missing=False) + self.network.delete_ip.assert_called_once_with(self.floating_ips[0]) self.assertIsNone(result) + def test_multi_floating_ips_delete(self): + arglist = [] + verifylist = [] + + for f in self.floating_ips: + arglist.append(f.id) + verifylist = [ + ('floating_ip', arglist), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + calls = [] + for f in self.floating_ips: + calls.append(call(f)) + self.network.delete_ip.assert_has_calls(calls) + self.assertIsNone(result) + + def test_multi_floating_ips_delete_with_exception(self): + arglist = [ + self.floating_ips[0].id, + 'unexist_floating_ip', + ] + verifylist = [ + ('floating_ip', + [self.floating_ips[0].id, 'unexist_floating_ip']), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + find_mock_result = [self.floating_ips[0], exceptions.CommandError] + self.network.find_ip = ( + mock.MagicMock(side_effect=find_mock_result) + ) + + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('1 of 2 floating_ip failed to delete.', str(e)) + + self.network.find_ip.assert_any_call( + self.floating_ips[0].id, ignore_missing=False) + self.network.find_ip.assert_any_call( + 'unexist_floating_ip', ignore_missing=False) + self.network.delete_ip.assert_called_once_with( + self.floating_ips[0] + ) + class TestListFloatingIPNetwork(TestFloatingIPNetwork): @@ -335,8 +389,8 @@ class TestCreateFloatingIPCompute(TestFloatingIPCompute): class TestDeleteFloatingIPCompute(TestFloatingIPCompute): - # The floating ip to be deleted. - floating_ip = compute_fakes.FakeFloatingIP.create_one_floating_ip() + # The floating ips to be deleted. + floating_ips = compute_fakes.FakeFloatingIP.create_floating_ips(count=2) def setUp(self): super(TestDeleteFloatingIPCompute, self).setUp() @@ -346,27 +400,78 @@ class TestDeleteFloatingIPCompute(TestFloatingIPCompute): self.compute.floating_ips.delete.return_value = None # Return value of utils.find_resource() - self.compute.floating_ips.get.return_value = self.floating_ip + self.compute.floating_ips.get = ( + compute_fakes.FakeFloatingIP.get_floating_ips(self.floating_ips)) # Get the command object to test self.cmd = floating_ip.DeleteFloatingIP(self.app, None) def test_floating_ip_delete(self): arglist = [ - self.floating_ip.id, + self.floating_ips[0].id, ] verifylist = [ - ('floating_ip', self.floating_ip.id), + ('floating_ip', [self.floating_ips[0].id]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) self.compute.floating_ips.delete.assert_called_once_with( - self.floating_ip.id + self.floating_ips[0].id ) self.assertIsNone(result) + def test_multi_floating_ips_delete(self): + arglist = [] + verifylist = [] + + for f in self.floating_ips: + arglist.append(f.id) + verifylist = [ + ('floating_ip', arglist), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + calls = [] + for f in self.floating_ips: + calls.append(call(f.id)) + self.compute.floating_ips.delete.assert_has_calls(calls) + self.assertIsNone(result) + + def test_multi_floating_ips_delete_with_exception(self): + arglist = [ + self.floating_ips[0].id, + 'unexist_floating_ip', + ] + verifylist = [ + ('floating_ip', + [self.floating_ips[0].id, 'unexist_floating_ip']), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + find_mock_result = [self.floating_ips[0], exceptions.CommandError] + self.compute.floating_ips.get = ( + mock.MagicMock(side_effect=find_mock_result) + ) + self.compute.floating_ips.find.side_effect = exceptions.NotFound(None) + + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('1 of 2 floating_ip failed to delete.', str(e)) + + self.compute.floating_ips.get.assert_any_call( + self.floating_ips[0].id) + self.compute.floating_ips.get.assert_any_call( + 'unexist_floating_ip') + self.compute.floating_ips.delete.assert_called_once_with( + self.floating_ips[0].id + ) + class TestListFloatingIPCompute(TestFloatingIPCompute): diff --git a/openstackclient/tests/network/v2/test_ip_availability.py b/openstackclient/tests/network/v2/test_ip_availability.py index 04979e77..c6ec2b0b 100644 --- a/openstackclient/tests/network/v2/test_ip_availability.py +++ b/openstackclient/tests/network/v2/test_ip_availability.py @@ -14,7 +14,8 @@ import copy import mock -from openstackclient.common import utils as osc_utils +from osc_lib import utils as common_utils + from openstackclient.network.v2 import ip_availability from openstackclient.tests import fakes from openstackclient.tests.identity.v3 import fakes as identity_fakes @@ -81,8 +82,10 @@ class TestListIPAvailability(TestIPAvailability): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) + filters = {'ip_version': 4} - self.network.network_ip_availabilities.assert_called_once_with() + self.network.network_ip_availabilities.assert_called_once_with( + **filters) self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) @@ -115,7 +118,8 @@ class TestListIPAvailability(TestIPAvailability): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - filters = {'tenant_id': identity_fakes.project_id} + filters = {'tenant_id': identity_fakes.project_id, + 'ip_version': 4} self.network.network_ip_availabilities.assert_called_once_with( **filters) @@ -140,7 +144,7 @@ class TestShowIPAvailability(TestIPAvailability): _ip_availability.network_id, _ip_availability.network_name, _ip_availability.tenant_id, - osc_utils.format_list( + common_utils.format_list( _ip_availability.subnet_ip_availability), _ip_availability.total_ips, _ip_availability.used_ips, diff --git a/openstackclient/tests/network/v2/test_network.py b/openstackclient/tests/network/v2/test_network.py index ba810f16..8fc9dadf 100644 --- a/openstackclient/tests/network/v2/test_network.py +++ b/openstackclient/tests/network/v2/test_network.py @@ -13,10 +13,11 @@ import copy import mock - from mock import call -from openstackclient.common import exceptions -from openstackclient.common import utils + +from osc_lib import exceptions +from osc_lib import utils + from openstackclient.network.v2 import network from openstackclient.tests.compute.v2 import fakes as compute_fakes from openstackclient.tests import fakes @@ -609,8 +610,12 @@ class TestSetNetwork(TestNetwork): verifylist = [('network', self._network.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.assertRaises(exceptions.CommandError, self.cmd.take_action, - parsed_args) + result = self.cmd.take_action(parsed_args) + + attrs = {} + self.network.update_network.assert_called_once_with( + self._network, **attrs) + self.assertIsNone(result) class TestShowNetwork(TestNetwork): diff --git a/openstackclient/tests/network/v2/test_network_segment.py b/openstackclient/tests/network/v2/test_network_segment.py index 0a99eced..a635d845 100644 --- a/openstackclient/tests/network/v2/test_network_segment.py +++ b/openstackclient/tests/network/v2/test_network_segment.py @@ -13,7 +13,8 @@ import mock -from openstackclient.common import exceptions +from osc_lib import exceptions + from openstackclient.network.v2 import network_segment from openstackclient.tests.network.v2 import fakes as network_fakes from openstackclient.tests import utils as tests_utils diff --git a/openstackclient/tests/network/v2/test_port.py b/openstackclient/tests/network/v2/test_port.py index c3f175bf..a998585e 100644 --- a/openstackclient/tests/network/v2/test_port.py +++ b/openstackclient/tests/network/v2/test_port.py @@ -11,9 +11,13 @@ # under the License. # +import argparse import mock -from openstackclient.common import utils +from mock import call +from osc_lib import exceptions +from osc_lib import utils + from openstackclient.network.v2 import port from openstackclient.tests.network.v2 import fakes as network_fakes from openstackclient.tests import utils as tests_utils @@ -171,33 +175,137 @@ class TestCreatePort(TestPort): self.assertEqual(ref_columns, columns) self.assertEqual(ref_data, data) + def test_create_invalid_json_binding_profile(self): + arglist = [ + '--network', self._port.network_id, + '--binding-profile', '{"parent_name":"fake_parent"', + 'test-port', + ] + self.assertRaises(argparse.ArgumentTypeError, + self.check_parser, + self.cmd, + arglist, + None) + + def test_create_invalid_key_value_binding_profile(self): + arglist = [ + '--network', self._port.network_id, + '--binding-profile', 'key', + 'test-port', + ] + self.assertRaises(argparse.ArgumentTypeError, + self.check_parser, + self.cmd, + arglist, + None) + + def test_create_json_binding_profile(self): + arglist = [ + '--network', self._port.network_id, + '--binding-profile', '{"parent_name":"fake_parent"}', + '--binding-profile', '{"tag":42}', + 'test-port', + ] + verifylist = [ + ('network', self._port.network_id,), + ('enable', True), + ('binding_profile', {'parent_name': 'fake_parent', 'tag': 42}), + ('name', 'test-port'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = (self.cmd.take_action(parsed_args)) + + self.network.create_port.assert_called_once_with(**{ + 'admin_state_up': True, + 'network_id': self._port.network_id, + 'binding:profile': {'parent_name': 'fake_parent', 'tag': 42}, + 'name': 'test-port', + }) + + ref_columns, ref_data = self._get_common_cols_data(self._port) + self.assertEqual(ref_columns, columns) + self.assertEqual(ref_data, data) + class TestDeletePort(TestPort): - # The port to delete. - _port = network_fakes.FakePort.create_one_port() + # Ports to delete. + _ports = network_fakes.FakePort.create_ports(count=2) def setUp(self): super(TestDeletePort, self).setUp() self.network.delete_port = mock.Mock(return_value=None) - self.network.find_port = mock.Mock(return_value=self._port) + self.network.find_port = network_fakes.FakePort.get_ports( + ports=self._ports) # Get the command object to test self.cmd = port.DeletePort(self.app, self.namespace) - def test_delete(self): + def test_port_delete(self): arglist = [ - self._port.name, + self._ports[0].name, ] verifylist = [ - ('port', [self._port.name]), + ('port', [self._ports[0].name]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.network.delete_port.assert_called_once_with(self._port) + self.network.find_port.assert_called_once_with( + self._ports[0].name, ignore_missing=False) + self.network.delete_port.assert_called_once_with(self._ports[0]) + self.assertIsNone(result) + + def test_multi_ports_delete(self): + arglist = [] + verifylist = [] + + for p in self._ports: + arglist.append(p.name) + verifylist = [ + ('port', arglist), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + calls = [] + for p in self._ports: + calls.append(call(p)) + self.network.delete_port.assert_has_calls(calls) self.assertIsNone(result) + def test_multi_ports_delete_with_exception(self): + arglist = [ + self._ports[0].name, + 'unexist_port', + ] + verifylist = [ + ('port', + [self._ports[0].name, 'unexist_port']), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + find_mock_result = [self._ports[0], exceptions.CommandError] + self.network.find_port = ( + mock.MagicMock(side_effect=find_mock_result) + ) + + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('1 of 2 ports failed to delete.', str(e)) + + self.network.find_port.assert_any_call( + self._ports[0].name, ignore_missing=False) + self.network.find_port.assert_any_call( + 'unexist_port', ignore_missing=False) + self.network.delete_port.assert_called_once_with( + self._ports[0] + ) + class TestListPort(TestPort): @@ -372,6 +480,63 @@ class TestSetPort(TestPort): self.network.update_port.assert_called_once_with(self._port, **attrs) self.assertIsNone(result) + def test_set_nothing(self): + arglist = [ + self._port.name, + ] + verifylist = [ + ('port', self._port.name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + attrs = {} + self.network.update_port.assert_called_once_with(self._port, **attrs) + self.assertIsNone(result) + + def test_set_invalid_json_binding_profile(self): + arglist = [ + '--binding-profile', '{"parent_name"}', + 'test-port', + ] + self.assertRaises(argparse.ArgumentTypeError, + self.check_parser, + self.cmd, + arglist, + None) + + def test_set_invalid_key_value_binding_profile(self): + arglist = [ + '--binding-profile', 'key', + 'test-port', + ] + self.assertRaises(argparse.ArgumentTypeError, + self.check_parser, + self.cmd, + arglist, + None) + + def test_set_mixed_binding_profile(self): + arglist = [ + '--binding-profile', 'foo=bar', + '--binding-profile', '{"foo2": "bar2"}', + self._port.name, + ] + verifylist = [ + ('binding_profile', {'foo': 'bar', 'foo2': 'bar2'}), + ('port', self._port.name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + attrs = { + 'binding:profile': {'foo': 'bar', 'foo2': 'bar2'}, + } + self.network.update_port.assert_called_once_with(self._port, **attrs) + self.assertIsNone(result) + class TestShowPort(TestPort): diff --git a/openstackclient/tests/network/v2/test_router.py b/openstackclient/tests/network/v2/test_router.py index 99b41d2d..e3da253a 100644 --- a/openstackclient/tests/network/v2/test_router.py +++ b/openstackclient/tests/network/v2/test_router.py @@ -12,9 +12,11 @@ # import mock +from mock import call + +from osc_lib import exceptions +from osc_lib import utils as osc_utils -from openstackclient.common import exceptions -from openstackclient.common import utils as osc_utils from openstackclient.network.v2 import router from openstackclient.tests.network.v2 import fakes as network_fakes from openstackclient.tests import utils as tests_utils @@ -202,32 +204,82 @@ class TestCreateRouter(TestRouter): class TestDeleteRouter(TestRouter): - # The router to delete. - _router = network_fakes.FakeRouter.create_one_router() + # The routers to delete. + _routers = network_fakes.FakeRouter.create_routers(count=2) def setUp(self): super(TestDeleteRouter, self).setUp() self.network.delete_router = mock.Mock(return_value=None) - self.network.find_router = mock.Mock(return_value=self._router) + self.network.find_router = ( + network_fakes.FakeRouter.get_routers(self._routers)) # Get the command object to test self.cmd = router.DeleteRouter(self.app, self.namespace) - def test_delete(self): + def test_router_delete(self): arglist = [ - self._router.name, + self._routers[0].name, ] verifylist = [ - ('router', [self._router.name]), + ('router', [self._routers[0].name]), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.network.delete_router.assert_called_once_with(self._routers[0]) + self.assertIsNone(result) + + def test_multi_routers_delete(self): + arglist = [] + verifylist = [] + + for r in self._routers: + arglist.append(r.name) + verifylist = [ + ('router', arglist), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.network.delete_router.assert_called_once_with(self._router) + + calls = [] + for r in self._routers: + calls.append(call(r)) + self.network.delete_router.assert_has_calls(calls) self.assertIsNone(result) + def test_multi_routers_delete_with_exception(self): + arglist = [ + self._routers[0].name, + 'unexist_router', + ] + verifylist = [ + ('router', + [self._routers[0].name, 'unexist_router']), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + find_mock_result = [self._routers[0], exceptions.CommandError] + self.network.find_router = ( + mock.MagicMock(side_effect=find_mock_result) + ) + + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('1 of 2 routers failed to delete.', str(e)) + + self.network.find_router.assert_any_call( + self._routers[0].name, ignore_missing=False) + self.network.find_router.assert_any_call( + 'unexist_router', ignore_missing=False) + self.network.delete_router.assert_called_once_with( + self._routers[0] + ) + class TestListRouter(TestRouter): @@ -568,12 +620,20 @@ class TestSetRouter(TestRouter): self.cmd, arglist, verifylist) def test_set_nothing(self): - arglist = [self._router.name, ] - verifylist = [('router', self._router.name), ] + arglist = [ + self._router.name, + ] + verifylist = [ + ('router', self._router.name), + ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.assertRaises(exceptions.CommandError, self.cmd.take_action, - parsed_args) + result = self.cmd.take_action(parsed_args) + + attrs = {} + self.network.update_router.assert_called_once_with( + self._router, **attrs) + self.assertIsNone(result) class TestShowRouter(TestRouter): diff --git a/openstackclient/tests/network/v2/test_security_group.py b/openstackclient/tests/network/v2/test_security_group.py index 213367a4..b0c14985 100644 --- a/openstackclient/tests/network/v2/test_security_group.py +++ b/openstackclient/tests/network/v2/test_security_group.py @@ -13,6 +13,9 @@ import copy import mock +from mock import call + +from osc_lib import exceptions from openstackclient.network.v2 import security_group from openstackclient.tests.compute.v2 import fakes as compute_fakes @@ -227,42 +230,93 @@ class TestCreateSecurityGroupCompute(TestSecurityGroupCompute): class TestDeleteSecurityGroupNetwork(TestSecurityGroupNetwork): - # The security group to be deleted. - _security_group = \ - network_fakes.FakeSecurityGroup.create_one_security_group() + # The security groups to be deleted. + _security_groups = \ + network_fakes.FakeSecurityGroup.create_security_groups() def setUp(self): super(TestDeleteSecurityGroupNetwork, self).setUp() self.network.delete_security_group = mock.Mock(return_value=None) - self.network.find_security_group = mock.Mock( - return_value=self._security_group) + self.network.find_security_group = ( + network_fakes.FakeSecurityGroup.get_security_groups( + self._security_groups) + ) # Get the command object to test self.cmd = security_group.DeleteSecurityGroup(self.app, self.namespace) def test_security_group_delete(self): arglist = [ - self._security_group.name, + self._security_groups[0].name, ] verifylist = [ - ('group', self._security_group.name), + ('group', [self._security_groups[0].name]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) self.network.delete_security_group.assert_called_once_with( - self._security_group) + self._security_groups[0]) + self.assertIsNone(result) + + def test_multi_security_groups_delete(self): + arglist = [] + verifylist = [] + + for s in self._security_groups: + arglist.append(s.name) + verifylist = [ + ('group', arglist), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + calls = [] + for s in self._security_groups: + calls.append(call(s)) + self.network.delete_security_group.assert_has_calls(calls) self.assertIsNone(result) + def test_multi_security_groups_delete_with_exception(self): + arglist = [ + self._security_groups[0].name, + 'unexist_security_group', + ] + verifylist = [ + ('group', + [self._security_groups[0].name, 'unexist_security_group']), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + find_mock_result = [self._security_groups[0], exceptions.CommandError] + self.network.find_security_group = ( + mock.MagicMock(side_effect=find_mock_result) + ) + + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('1 of 2 group failed to delete.', str(e)) + + self.network.find_security_group.assert_any_call( + self._security_groups[0].name, ignore_missing=False) + self.network.find_security_group.assert_any_call( + 'unexist_security_group', ignore_missing=False) + self.network.delete_security_group.assert_called_once_with( + self._security_groups[0] + ) + class TestDeleteSecurityGroupCompute(TestSecurityGroupCompute): - # The security group to be deleted. - _security_group = \ - compute_fakes.FakeSecurityGroup.create_one_security_group() + # The security groups to be deleted. + _security_groups = \ + compute_fakes.FakeSecurityGroup.create_security_groups() def setUp(self): super(TestDeleteSecurityGroupCompute, self).setUp() @@ -271,27 +325,80 @@ class TestDeleteSecurityGroupCompute(TestSecurityGroupCompute): self.compute.security_groups.delete = mock.Mock(return_value=None) - self.compute.security_groups.get = mock.Mock( - return_value=self._security_group) + self.compute.security_groups.get = ( + compute_fakes.FakeSecurityGroup.get_security_groups( + self._security_groups) + ) # Get the command object to test self.cmd = security_group.DeleteSecurityGroup(self.app, None) def test_security_group_delete(self): arglist = [ - self._security_group.name, + self._security_groups[0].id, ] verifylist = [ - ('group', self._security_group.name), + ('group', [self._security_groups[0].id]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) self.compute.security_groups.delete.assert_called_once_with( - self._security_group.id) + self._security_groups[0].id) + self.assertIsNone(result) + + def test_multi_security_groups_delete(self): + arglist = [] + verifylist = [] + + for s in self._security_groups: + arglist.append(s.id) + verifylist = [ + ('group', arglist), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + calls = [] + for s in self._security_groups: + calls.append(call(s.id)) + self.compute.security_groups.delete.assert_has_calls(calls) self.assertIsNone(result) + def test_multi_security_groups_delete_with_exception(self): + arglist = [ + self._security_groups[0].id, + 'unexist_security_group', + ] + verifylist = [ + ('group', + [self._security_groups[0].id, 'unexist_security_group']), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + find_mock_result = [self._security_groups[0], exceptions.CommandError] + self.compute.security_groups.get = ( + mock.MagicMock(side_effect=find_mock_result) + ) + self.compute.security_groups.find.side_effect = ( + exceptions.NotFound(None)) + + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('1 of 2 group failed to delete.', str(e)) + + self.compute.security_groups.get.assert_any_call( + self._security_groups[0].id) + self.compute.security_groups.get.assert_any_call( + 'unexist_security_group') + self.compute.security_groups.delete.assert_called_once_with( + self._security_groups[0].id + ) + class TestListSecurityGroupNetwork(TestSecurityGroupNetwork): diff --git a/openstackclient/tests/network/v2/test_security_group_rule.py b/openstackclient/tests/network/v2/test_security_group_rule.py index 2a64b884..b2862679 100644 --- a/openstackclient/tests/network/v2/test_security_group_rule.py +++ b/openstackclient/tests/network/v2/test_security_group_rule.py @@ -13,8 +13,10 @@ import copy import mock +from mock import call + +from osc_lib import exceptions -from openstackclient.common import exceptions from openstackclient.network import utils as network_utils from openstackclient.network.v2 import security_group_rule from openstackclient.tests.compute.v2 import fakes as compute_fakes @@ -667,17 +669,20 @@ class TestCreateSecurityGroupRuleCompute(TestSecurityGroupRuleCompute): class TestDeleteSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): - # The security group rule to be deleted. - _security_group_rule = \ - network_fakes.FakeSecurityGroupRule.create_one_security_group_rule() + # The security group rules to be deleted. + _security_group_rules = \ + network_fakes.FakeSecurityGroupRule.create_security_group_rules( + count=2) def setUp(self): super(TestDeleteSecurityGroupRuleNetwork, self).setUp() self.network.delete_security_group_rule = mock.Mock(return_value=None) - self.network.find_security_group_rule = mock.Mock( - return_value=self._security_group_rule) + self.network.find_security_group_rule = ( + network_fakes.FakeSecurityGroupRule.get_security_group_rules( + self._security_group_rules) + ) # Get the command object to test self.cmd = security_group_rule.DeleteSecurityGroupRule( @@ -685,25 +690,76 @@ class TestDeleteSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): def test_security_group_rule_delete(self): arglist = [ - self._security_group_rule.id, + self._security_group_rules[0].id, ] verifylist = [ - ('rule', self._security_group_rule.id), + ('rule', [self._security_group_rules[0].id]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) self.network.delete_security_group_rule.assert_called_once_with( - self._security_group_rule) + self._security_group_rules[0]) self.assertIsNone(result) + def test_multi_security_group_rules_delete(self): + arglist = [] + verifylist = [] + + for s in self._security_group_rules: + arglist.append(s.id) + verifylist = [ + ('rule', arglist), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + calls = [] + for s in self._security_group_rules: + calls.append(call(s)) + self.network.delete_security_group_rule.assert_has_calls(calls) + self.assertIsNone(result) + + def test_multi_security_group_rules_delete_with_exception(self): + arglist = [ + self._security_group_rules[0].id, + 'unexist_rule', + ] + verifylist = [ + ('rule', + [self._security_group_rules[0].id, 'unexist_rule']), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + find_mock_result = [ + self._security_group_rules[0], exceptions.CommandError] + self.network.find_security_group_rule = ( + mock.MagicMock(side_effect=find_mock_result) + ) + + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('1 of 2 rule failed to delete.', str(e)) + + self.network.find_security_group_rule.assert_any_call( + self._security_group_rules[0].id, ignore_missing=False) + self.network.find_security_group_rule.assert_any_call( + 'unexist_rule', ignore_missing=False) + self.network.delete_security_group_rule.assert_called_once_with( + self._security_group_rules[0] + ) + class TestDeleteSecurityGroupRuleCompute(TestSecurityGroupRuleCompute): # The security group rule to be deleted. - _security_group_rule = \ - compute_fakes.FakeSecurityGroupRule.create_one_security_group_rule() + _security_group_rules = \ + compute_fakes.FakeSecurityGroupRule.create_security_group_rules( + count=2) def setUp(self): super(TestDeleteSecurityGroupRuleCompute, self).setUp() @@ -715,19 +771,65 @@ class TestDeleteSecurityGroupRuleCompute(TestSecurityGroupRuleCompute): def test_security_group_rule_delete(self): arglist = [ - self._security_group_rule.id, + self._security_group_rules[0].id, ] verifylist = [ - ('rule', self._security_group_rule.id), + ('rule', [self._security_group_rules[0].id]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) self.compute.security_group_rules.delete.assert_called_once_with( - self._security_group_rule.id) + self._security_group_rules[0].id) + self.assertIsNone(result) + + def test_multi_security_group_rules_delete(self): + arglist = [] + verifylist = [] + + for s in self._security_group_rules: + arglist.append(s.id) + verifylist = [ + ('rule', arglist), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + calls = [] + for s in self._security_group_rules: + calls.append(call(s.id)) + self.compute.security_group_rules.delete.assert_has_calls(calls) self.assertIsNone(result) + def test_multi_security_group_rules_delete_with_exception(self): + arglist = [ + self._security_group_rules[0].id, + 'unexist_rule', + ] + verifylist = [ + ('rule', + [self._security_group_rules[0].id, 'unexist_rule']), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + find_mock_result = [None, exceptions.CommandError] + self.compute.security_group_rules.delete = ( + mock.MagicMock(side_effect=find_mock_result) + ) + + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('1 of 2 rule failed to delete.', str(e)) + + self.compute.security_group_rules.delete.assert_any_call( + self._security_group_rules[0].id) + self.compute.security_group_rules.delete.assert_any_call( + 'unexist_rule') + class TestListSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): diff --git a/openstackclient/tests/network/v2/test_subnet.py b/openstackclient/tests/network/v2/test_subnet.py index 22c288f9..99b558c0 100644 --- a/openstackclient/tests/network/v2/test_subnet.py +++ b/openstackclient/tests/network/v2/test_subnet.py @@ -13,9 +13,11 @@ import copy import mock +from mock import call + +from osc_lib import exceptions +from osc_lib import utils -from openstackclient.common import exceptions -from openstackclient.common import utils from openstackclient.network.v2 import subnet as subnet_v2 from openstackclient.tests import fakes from openstackclient.tests.identity.v3 import fakes as identity_fakes_v3 @@ -89,6 +91,14 @@ class TestCreateSubnet(TestSubnet): } ) + # The network segment to be returned from find_segment + _network_segment = \ + network_fakes.FakeNetworkSegment.create_one_network_segment( + attrs={ + 'network_id': _subnet.network_id, + } + ) + columns = ( 'allocation_pools', 'cidr', @@ -103,6 +113,7 @@ class TestCreateSubnet(TestSubnet): 'name', 'network_id', 'project_id', + 'segment_id', 'subnetpool_id', ) @@ -120,6 +131,7 @@ class TestCreateSubnet(TestSubnet): _subnet.name, _subnet.network_id, _subnet.project_id, + _subnet.segment_id, _subnet.subnetpool_id, ) @@ -137,6 +149,7 @@ class TestCreateSubnet(TestSubnet): _subnet_from_pool.name, _subnet_from_pool.network_id, _subnet_from_pool.project_id, + _subnet_from_pool.segment_id, _subnet_from_pool.subnetpool_id, ) @@ -154,6 +167,7 @@ class TestCreateSubnet(TestSubnet): _subnet_ipv6.name, _subnet_ipv6.network_id, _subnet_ipv6.project_id, + _subnet_ipv6.segment_id, _subnet_ipv6.subnetpool_id, ) @@ -187,6 +201,15 @@ class TestCreateSubnet(TestSubnet): loaded=True, ) + # Mock SDK calls for all tests. + self.network.find_network = mock.Mock(return_value=self._network) + self.network.find_segment = mock.Mock( + return_value=self._network_segment + ) + self.network.find_subnet_pool = mock.Mock( + return_value=self._subnet_pool + ) + def test_create_no_options(self): arglist = [] verifylist = [] @@ -197,11 +220,9 @@ class TestCreateSubnet(TestSubnet): self.check_parser, self.cmd, arglist, verifylist) def test_create_default_options(self): - # Mock create_subnet and find_network sdk calls to return the - # values we want for this test + # Mock SDK calls for this test. self.network.create_subnet = mock.Mock(return_value=self._subnet) self._network.id = self._subnet.network_id - self.network.find_network = mock.Mock(return_value=self._network) arglist = [ "--subnet-range", self._subnet.cidr, @@ -231,14 +252,10 @@ class TestCreateSubnet(TestSubnet): self.assertEqual(self.data, data) def test_create_from_subnet_pool_options(self): - # Mock create_subnet, find_subnet_pool, and find_network sdk calls - # to return the values we want for this test + # Mock SDK calls for this test. self.network.create_subnet = \ mock.Mock(return_value=self._subnet_from_pool) self._network.id = self._subnet_from_pool.network_id - self.network.find_network = mock.Mock(return_value=self._network) - self.network.find_subnet_pool = \ - mock.Mock(return_value=self._subnet_pool) arglist = [ self._subnet_from_pool.name, @@ -291,11 +308,9 @@ class TestCreateSubnet(TestSubnet): self.assertEqual(self.data_subnet_pool, data) def test_create_options_subnet_range_ipv6(self): - # Mock create_subnet and find_network sdk calls to return the - # values we want for this test + # Mock SDK calls for this test. self.network.create_subnet = mock.Mock(return_value=self._subnet_ipv6) self._network.id = self._subnet_ipv6.network_id - self.network.find_network = mock.Mock(return_value=self._network) arglist = [ self._subnet_ipv6.name, @@ -358,35 +373,138 @@ class TestCreateSubnet(TestSubnet): self.assertEqual(self.columns, columns) self.assertEqual(self.data_ipv6, data) + def test_create_no_beta_command_options(self): + arglist = [ + "--subnet-range", self._subnet.cidr, + "--network-segment", self._network_segment.id, + "--network", self._subnet.network_id, + self._subnet.name, + ] + verifylist = [ + ('name', self._subnet.name), + ('subnet_range', self._subnet.cidr), + ('network-segment', self._network_segment.id), + ('network', self._subnet.network_id), + ] + self.app.options.os_beta_command = False + self.assertRaises(tests_utils.ParserException, + self.check_parser, self.cmd, arglist, verifylist) + + def test_create_with_network_segment(self): + # Mock SDK calls for this test. + self.network.create_subnet = mock.Mock(return_value=self._subnet) + self._network.id = self._subnet.network_id + + arglist = [ + "--subnet-range", self._subnet.cidr, + "--network-segment", self._network_segment.id, + "--network", self._subnet.network_id, + self._subnet.name, + ] + verifylist = [ + ('name', self._subnet.name), + ('subnet_range', self._subnet.cidr), + ('network_segment', self._network_segment.id), + ('network', self._subnet.network_id), + ('ip_version', self._subnet.ip_version), + ('gateway', 'auto'), + + ] + + self.app.options.os_beta_command = True + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.create_subnet.assert_called_once_with(**{ + 'cidr': self._subnet.cidr, + 'enable_dhcp': self._subnet.enable_dhcp, + 'ip_version': self._subnet.ip_version, + 'name': self._subnet.name, + 'network_id': self._subnet.network_id, + 'segment_id': self._network_segment.id, + }) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + class TestDeleteSubnet(TestSubnet): - # The subnet to delete. - _subnet = network_fakes.FakeSubnet.create_one_subnet() + # The subnets to delete. + _subnets = network_fakes.FakeSubnet.create_subnets(count=2) def setUp(self): super(TestDeleteSubnet, self).setUp() self.network.delete_subnet = mock.Mock(return_value=None) - self.network.find_subnet = mock.Mock(return_value=self._subnet) + self.network.find_subnet = ( + network_fakes.FakeSubnet.get_subnets(self._subnets)) # Get the command object to test self.cmd = subnet_v2.DeleteSubnet(self.app, self.namespace) - def test_delete(self): + def test_subnet_delete(self): arglist = [ - self._subnet.name, + self._subnets[0].name, ] verifylist = [ - ('subnet', self._subnet.name), + ('subnet', [self._subnets[0].name]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.network.delete_subnet.assert_called_once_with(self._subnet) + self.network.delete_subnet.assert_called_once_with(self._subnets[0]) self.assertIsNone(result) + def test_multi_subnets_delete(self): + arglist = [] + verifylist = [] + + for s in self._subnets: + arglist.append(s.name) + verifylist = [ + ('subnet', arglist), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + calls = [] + for s in self._subnets: + calls.append(call(s)) + self.network.delete_subnet.assert_has_calls(calls) + self.assertIsNone(result) + + def test_multi_subnets_delete_with_exception(self): + arglist = [ + self._subnets[0].name, + 'unexist_subnet', + ] + verifylist = [ + ('subnet', + [self._subnets[0].name, 'unexist_subnet']), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + find_mock_result = [self._subnets[0], exceptions.CommandError] + self.network.find_subnet = ( + mock.MagicMock(side_effect=find_mock_result) + ) + + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('1 of 2 subnets failed to delete.', str(e)) + + self.network.find_subnet.assert_any_call( + self._subnets[0].name, ignore_missing=False) + self.network.find_subnet.assert_any_call( + 'unexist_subnet', ignore_missing=False) + self.network.delete_subnet.assert_called_once_with( + self._subnets[0] + ) + class TestListSubnet(TestSubnet): # The subnets going to be listed up. @@ -549,8 +667,11 @@ class TestSetSubnet(TestSubnet): verifylist = [('subnet', self._subnet.name)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.assertRaises(exceptions.CommandError, self.cmd.take_action, - parsed_args) + result = self.cmd.take_action(parsed_args) + + attrs = {} + self.network.update_subnet.assert_called_with(self._subnet, **attrs) + self.assertIsNone(result) def test_append_options(self): _testsubnet = network_fakes.FakeSubnet.create_one_subnet( @@ -591,6 +712,7 @@ class TestShowSubnet(TestSubnet): 'name', 'network_id', 'project_id', + 'segment_id', 'subnetpool_id', ) @@ -608,6 +730,7 @@ class TestShowSubnet(TestSubnet): _subnet.name, _subnet.network_id, _subnet.tenant_id, + _subnet.segment_id, _subnet.subnetpool_id, ) diff --git a/openstackclient/tests/network/v2/test_subnet_pool.py b/openstackclient/tests/network/v2/test_subnet_pool.py index de12c9e9..7a96b30f 100644 --- a/openstackclient/tests/network/v2/test_subnet_pool.py +++ b/openstackclient/tests/network/v2/test_subnet_pool.py @@ -14,9 +14,11 @@ import argparse import copy import mock +from mock import call + +from osc_lib import exceptions +from osc_lib import utils -from openstackclient.common import exceptions -from openstackclient.common import utils from openstackclient.network.v2 import subnet_pool from openstackclient.tests import fakes from openstackclient.tests.identity.v3 import fakes as identity_fakes_v3 @@ -263,36 +265,85 @@ class TestCreateSubnetPool(TestSubnetPool): class TestDeleteSubnetPool(TestSubnetPool): - # The subnet pool to delete. - _subnet_pool = network_fakes.FakeSubnetPool.create_one_subnet_pool() + # The subnet pools to delete. + _subnet_pools = network_fakes.FakeSubnetPool.create_subnet_pools(count=2) def setUp(self): super(TestDeleteSubnetPool, self).setUp() self.network.delete_subnet_pool = mock.Mock(return_value=None) - self.network.find_subnet_pool = mock.Mock( - return_value=self._subnet_pool + self.network.find_subnet_pool = ( + network_fakes.FakeSubnetPool.get_subnet_pools(self._subnet_pools) ) # Get the command object to test self.cmd = subnet_pool.DeleteSubnetPool(self.app, self.namespace) - def test_delete(self): + def test_subnet_pool_delete(self): arglist = [ - self._subnet_pool.name, + self._subnet_pools[0].name, ] verifylist = [ - ('subnet_pool', self._subnet_pool.name), + ('subnet_pool', [self._subnet_pools[0].name]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) self.network.delete_subnet_pool.assert_called_once_with( - self._subnet_pool) + self._subnet_pools[0]) + self.assertIsNone(result) + + def test_multi_subnet_pools_delete(self): + arglist = [] + verifylist = [] + + for s in self._subnet_pools: + arglist.append(s.name) + verifylist = [ + ('subnet_pool', arglist), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + calls = [] + for s in self._subnet_pools: + calls.append(call(s)) + self.network.delete_subnet_pool.assert_has_calls(calls) self.assertIsNone(result) + def test_multi_subnet_pools_delete_with_exception(self): + arglist = [ + self._subnet_pools[0].name, + 'unexist_subnet_pool', + ] + verifylist = [ + ('subnet_pool', + [self._subnet_pools[0].name, 'unexist_subnet_pool']), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + find_mock_result = [self._subnet_pools[0], exceptions.CommandError] + self.network.find_subnet_pool = ( + mock.MagicMock(side_effect=find_mock_result) + ) + + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('1 of 2 subnet pools failed to delete.', str(e)) + + self.network.find_subnet_pool.assert_any_call( + self._subnet_pools[0].name, ignore_missing=False) + self.network.find_subnet_pool.assert_any_call( + 'unexist_subnet_pool', ignore_missing=False) + self.network.delete_subnet_pool.assert_called_once_with( + self._subnet_pools[0] + ) + class TestListSubnetPool(TestSubnetPool): # The subnet pools going to be listed up. @@ -443,10 +494,14 @@ class TestSetSubnetPool(TestSubnetPool): def test_set_nothing(self): arglist = [self._subnet_pool.name, ] verifylist = [('subnet_pool', self._subnet_pool.name), ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) - self.assertRaises(exceptions.CommandError, self.cmd.take_action, - parsed_args) + attrs = {} + self.network.update_subnet_pool.assert_called_once_with( + self._subnet_pool, **attrs) + self.assertIsNone(result) def test_set_len_negative(self): arglist = [ diff --git a/openstackclient/tests/test_shell.py b/openstackclient/tests/test_shell.py index 90454fc2..7d0bbd12 100644 --- a/openstackclient/tests/test_shell.py +++ b/openstackclient/tests/test_shell.py @@ -1,4 +1,4 @@ -# Copyright 2012-2013 OpenStack, LLC. +# Copyright 2012-2013 OpenStack Foundation # # 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 diff --git a/openstackclient/tests/volume/test_find_resource.py b/openstackclient/tests/volume/test_find_resource.py index 00cc46a6..227d6ca7 100644 --- a/openstackclient/tests/volume/test_find_resource.py +++ b/openstackclient/tests/volume/test_find_resource.py @@ -17,9 +17,9 @@ import mock from cinderclient.v1 import volume_snapshots from cinderclient.v1 import volumes +from osc_lib import exceptions +from osc_lib import utils -from openstackclient.common import exceptions -from openstackclient.common import utils from openstackclient.tests import utils as test_utils from openstackclient.volume import client # noqa diff --git a/openstackclient/tests/volume/v1/test_qos_specs.py b/openstackclient/tests/volume/v1/test_qos_specs.py index 4943f5df..392017c6 100644 --- a/openstackclient/tests/volume/v1/test_qos_specs.py +++ b/openstackclient/tests/volume/v1/test_qos_specs.py @@ -15,7 +15,8 @@ import copy -from openstackclient.common import utils +from osc_lib import utils + from openstackclient.tests import fakes from openstackclient.tests.volume.v1 import fakes as volume_fakes from openstackclient.volume.v1 import qos_specs diff --git a/openstackclient/tests/volume/v1/test_volume.py b/openstackclient/tests/volume/v1/test_volume.py index e4f51bb5..380bc632 100644 --- a/openstackclient/tests/volume/v1/test_volume.py +++ b/openstackclient/tests/volume/v1/test_volume.py @@ -14,6 +14,7 @@ # import copy +import mock from openstackclient.tests import fakes from openstackclient.tests.identity.v2_0 import fakes as identity_fakes @@ -656,7 +657,8 @@ class TestVolumeSet(TestVolume): ) self.assertIsNone(result) - def test_volume_set_size_smaller(self): + @mock.patch.object(volume.LOG, 'error') + def test_volume_set_size_smaller(self, mock_log_error): arglist = [ '--size', '100', volume_fakes.volume_name, @@ -672,12 +674,13 @@ class TestVolumeSet(TestVolume): result = self.cmd.take_action(parsed_args) - self.assertEqual("New size must be greater than %s GB" % - volume_fakes.volume_size, - self.app.log.messages.get('error')) + mock_log_error.assert_called_with("New size must be greater " + "than %s GB", + volume_fakes.volume_size) self.assertIsNone(result) - def test_volume_set_size_not_available(self): + @mock.patch.object(volume.LOG, 'error') + def test_volume_set_size_not_available(self, mock_log_error): self.volumes_mock.get.return_value.status = 'error' arglist = [ '--size', '130', @@ -694,9 +697,9 @@ class TestVolumeSet(TestVolume): result = self.cmd.take_action(parsed_args) - self.assertEqual("Volume is in %s state, it must be available before " - "size can be extended" % 'error', - self.app.log.messages.get('error')) + mock_log_error.assert_called_with("Volume is in %s state, it must be " + "available before size can be " + "extended", 'error') self.assertIsNone(result) def test_volume_set_property(self): diff --git a/openstackclient/tests/volume/v2/fakes.py b/openstackclient/tests/volume/v2/fakes.py index e61fe8aa..1cbbf68a 100644 --- a/openstackclient/tests/volume/v2/fakes.py +++ b/openstackclient/tests/volume/v2/fakes.py @@ -17,220 +17,13 @@ import mock import random import uuid -from openstackclient.common import utils as common_utils +from osc_lib import utils as common_utils + from openstackclient.tests import fakes from openstackclient.tests.identity.v3 import fakes as identity_fakes from openstackclient.tests.image.v2 import fakes as image_fakes from openstackclient.tests import utils -volume_attachment_server = { - 'device': '/dev/ice', - 'server_id': '1233', -} - -volume_id = "ce26708d-a7f8-4b4b-9861-4a80256615a6" -volume_name = "fake_volume" -volume_description = "fake description" -volume_status = "available" -volume_size = 20 -volume_type = "fake_lvmdriver-1" -volume_metadata = { - 'Alpha': 'a', - 'Beta': 'b', - 'Gamma': 'g', -} -volume_metadata_str = "Alpha='a', Beta='b', Gamma='g'" -volume_snapshot_id = 1 -volume_availability_zone = "nova" -volume_attachments = [volume_attachment_server] - -VOLUME = { - "id": volume_id, - "name": volume_name, - "description": volume_description, - "status": volume_status, - "size": volume_size, - "volume_type": volume_type, - "metadata": volume_metadata, - "snapshot_id": volume_snapshot_id, - "availability_zone": volume_availability_zone, - "attachments": volume_attachments -} - -VOLUME_columns = ( - "attachments", - "availability_zone", - "description", - "id", - "name", - "properties", - "size", - "snapshot_id", - "status", - "type" -) - -VOLUME_data = ( - volume_attachments, - volume_availability_zone, - volume_description, - volume_id, - volume_name, - common_utils.format_dict(volume_metadata), - volume_size, - volume_snapshot_id, - volume_status, - volume_type -) - - -snapshot_id = "cb2d364e-4d1c-451a-8c68-b5bbcb340fb2" -snapshot_name = "fake_snapshot" -snapshot_description = "fake description" -snapshot_size = 10 -snapshot_metadata = { - "foo": "bar" -} -snapshot_volume_id = "bdbae8dc-e6ca-43c0-8076-951cc1b093a4" - -SNAPSHOT = { - "id": snapshot_id, - "name": snapshot_name, - "description": snapshot_description, - "size": snapshot_size, - "status": "available", - "metadata": snapshot_metadata, - "created_at": "2015-06-03T18:49:19.000000", - "volume_id": volume_name -} -EXPECTED_SNAPSHOT = copy.deepcopy(SNAPSHOT) -EXPECTED_SNAPSHOT.pop("metadata") -EXPECTED_SNAPSHOT['properties'] = "foo='bar'" -SNAPSHOT_columns = tuple(sorted(EXPECTED_SNAPSHOT)) -SNAPSHOT_data = tuple((EXPECTED_SNAPSHOT[x] - for x in sorted(EXPECTED_SNAPSHOT))) - - -type_id = "5520dc9e-6f9b-4378-a719-729911c0f407" -type_description = "fake description" -type_name = "fake-lvmdriver-1" -type_extra_specs = { - "foo": "bar" -} - -TYPE = { - 'id': type_id, - 'name': type_name, - 'description': type_description, - 'extra_specs': type_extra_specs -} - -TYPE_columns = tuple(sorted(TYPE)) -TYPE_data = tuple((TYPE[x] for x in sorted(TYPE))) - -formatted_type_properties = "foo='bar'" -TYPE_FORMATTED = { - 'id': type_id, - 'name': type_name, - 'description': type_description, - 'properties': formatted_type_properties -} -TYPE_FORMATTED_columns = tuple(sorted(TYPE_FORMATTED)) -TYPE_FORMATTED_data = tuple((TYPE_FORMATTED[x] for x in - sorted(TYPE_FORMATTED))) - -backup_id = "3c409fe6-4d03-4a06-aeab-18bdcdf3c8f4" -backup_volume_id = "bdbae8dc-e6ca-43c0-8076-951cc1b093a4" -backup_name = "fake_backup" -backup_description = "fake description" -backup_object_count = None -backup_container = None -backup_size = 10 -backup_status = "error" - -BACKUP = { - "id": backup_id, - "name": backup_name, - "volume_id": backup_volume_id, - "description": backup_description, - "object_count": backup_object_count, - "container": backup_container, - "size": backup_size, - "status": backup_status, - "availability_zone": volume_availability_zone, -} - -BACKUP_columns = tuple(sorted(BACKUP)) -BACKUP_data = tuple((BACKUP[x] for x in sorted(BACKUP))) - -qos_id = '6f2be1de-997b-4230-b76c-a3633b59e8fb' -qos_consumer = 'front-end' -qos_default_consumer = 'both' -qos_name = "fake-qos-specs" -qos_specs = { - 'foo': 'bar', - 'iops': '9001' -} -qos_association = { - 'association_type': 'volume_type', - 'name': type_name, - 'id': type_id -} - -QOS = { - 'id': qos_id, - 'consumer': qos_consumer, - 'name': qos_name -} - -QOS_DEFAULT_CONSUMER = { - 'id': qos_id, - 'consumer': qos_default_consumer, - 'name': qos_name -} - -QOS_WITH_SPECS = { - 'id': qos_id, - 'consumer': qos_consumer, - 'name': qos_name, - 'specs': qos_specs -} - -QOS_WITH_ASSOCIATIONS = { - 'id': qos_id, - 'consumer': qos_consumer, - 'name': qos_name, - 'specs': qos_specs, - 'associations': [qos_association] -} - -image_id = 'im1' -image_name = 'graven' -IMAGE = { - 'id': image_id, - 'name': image_name -} - -extension_name = 'SchedulerHints' -extension_namespace = 'http://docs.openstack.org/'\ - 'block-service/ext/scheduler-hints/api/v2' -extension_description = 'Pass arbitrary key/value'\ - 'pairs to the scheduler.' -extension_updated = '2013-04-18T00:00:00+00:00' -extension_alias = 'OS-SCH-HNT' -extension_links = '[{"href":'\ - '"https://github.com/openstack/block-api", "type":'\ - ' "text/html", "rel": "describedby"}]' - -EXTENSION = { - 'name': extension_name, - 'namespace': extension_namespace, - 'description': extension_description, - 'updated': extension_updated, - 'alias': extension_alias, - 'links': extension_links, -} - class FakeTransferClient(object): @@ -379,6 +172,8 @@ class FakeVolumeClient(object): def __init__(self, **kwargs): self.volumes = mock.Mock() self.volumes.resource_class = fakes.FakeResource(None, {}) + self.extensions = mock.Mock() + self.extensions.resource_class = fakes.FakeResource(None, {}) self.volume_snapshots = mock.Mock() self.volume_snapshots.resource_class = fakes.FakeResource(None, {}) self.backups = mock.Mock() @@ -643,6 +438,42 @@ class FakeBackup(object): return backups +class FakeExtension(object): + """Fake one or more extension.""" + + @staticmethod + def create_one_extension(attrs=None): + """Create a fake extension. + + :param Dictionary attrs: + A dictionary with all attributes + :return: + A FakeResource object with name, namespace, etc. + """ + attrs = attrs or {} + + # Set default attributes. + extension_info = { + 'name': 'name-' + uuid.uuid4().hex, + 'namespace': ('http://docs.openstack.org/' + 'block-service/ext/scheduler-hints/api/v2'), + 'description': 'description-' + uuid.uuid4().hex, + 'updated': '2013-04-18T00:00:00+00:00', + 'alias': 'OS-SCH-HNT', + 'links': ('[{"href":' + '"https://github.com/openstack/block-api", "type":' + ' "text/html", "rel": "describedby"}]'), + } + + # Overwrite default attributes. + extension_info.update(attrs) + + extension = fakes.FakeResource( + info=copy.deepcopy(extension_info), + loaded=True) + return extension + + class FakeQos(object): """Fake one or more Qos specification.""" diff --git a/openstackclient/tests/volume/v2/test_backup.py b/openstackclient/tests/volume/v2/test_backup.py index 8a151a91..ba0f1c18 100644 --- a/openstackclient/tests/volume/v2/test_backup.py +++ b/openstackclient/tests/volume/v2/test_backup.py @@ -72,12 +72,14 @@ class TestBackupCreate(TestBackup): "--name", self.new_backup.name, "--description", self.new_backup.description, "--container", self.new_backup.container, + "--force", self.new_backup.volume_id, ] verifylist = [ ("name", self.new_backup.name), ("description", self.new_backup.description), ("container", self.new_backup.container), + ("force", True), ("volume", self.new_backup.volume_id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -88,7 +90,8 @@ class TestBackupCreate(TestBackup): self.new_backup.volume_id, container=self.new_backup.container, name=self.new_backup.name, - description=self.new_backup.description + description=self.new_backup.description, + force=True, ) self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) @@ -112,7 +115,8 @@ class TestBackupCreate(TestBackup): self.new_backup.volume_id, container=self.new_backup.container, name=None, - description=self.new_backup.description + description=self.new_backup.description, + force=False, ) self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) diff --git a/openstackclient/tests/volume/v2/test_qos_specs.py b/openstackclient/tests/volume/v2/test_qos_specs.py index 741f4e70..11047535 100644 --- a/openstackclient/tests/volume/v2/test_qos_specs.py +++ b/openstackclient/tests/volume/v2/test_qos_specs.py @@ -13,9 +13,9 @@ # under the License. # -from openstackclient.common import utils from openstackclient.tests.volume.v2 import fakes as volume_fakes from openstackclient.volume.v2 import qos_specs +from osc_lib import utils class TestQos(volume_fakes.TestVolume): diff --git a/openstackclient/tests/volume/v2/test_snapshot.py b/openstackclient/tests/volume/v2/test_snapshot.py index fe6fbb52..ef199cbc 100644 --- a/openstackclient/tests/volume/v2/test_snapshot.py +++ b/openstackclient/tests/volume/v2/test_snapshot.py @@ -12,7 +12,8 @@ # under the License. # -from openstackclient.common import utils +from osc_lib import utils + from openstackclient.tests.volume.v2 import fakes as volume_fakes from openstackclient.volume.v2 import snapshot diff --git a/openstackclient/tests/volume/v2/test_type.py b/openstackclient/tests/volume/v2/test_type.py index 10c38612..174f33f2 100644 --- a/openstackclient/tests/volume/v2/test_type.py +++ b/openstackclient/tests/volume/v2/test_type.py @@ -14,7 +14,8 @@ import copy -from openstackclient.common import utils +from osc_lib import utils + from openstackclient.tests import fakes from openstackclient.tests.identity.v3 import fakes as identity_fakes from openstackclient.tests import utils as tests_utils @@ -127,13 +128,13 @@ class TestTypeDelete(TestType): self.volume_type.id ] verifylist = [ - ("volume_type", self.volume_type.id) + ("volume_types", [self.volume_type.id]) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.types_mock.delete.assert_called_with(self.volume_type.id) + self.types_mock.delete.assert_called_with(self.volume_type) self.assertIsNone(result) diff --git a/openstackclient/tests/volume/v2/test_volume.py b/openstackclient/tests/volume/v2/test_volume.py index fb48d8ac..68158df0 100644 --- a/openstackclient/tests/volume/v2/test_volume.py +++ b/openstackclient/tests/volume/v2/test_volume.py @@ -16,7 +16,8 @@ import copy from mock import call -from openstackclient.common import utils +from osc_lib import utils + from openstackclient.tests import fakes from openstackclient.tests.identity.v3 import fakes as identity_fakes from openstackclient.tests.image.v2 import fakes as image_fakes diff --git a/openstackclient/volume/client.py b/openstackclient/volume/client.py index a60f4b0e..ea0c441c 100644 --- a/openstackclient/volume/client.py +++ b/openstackclient/volume/client.py @@ -1,4 +1,4 @@ -# Copyright 2012-2013 OpenStack, LLC. +# Copyright 2012-2013 OpenStack Foundation # # 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 @@ -15,7 +15,8 @@ import logging -from openstackclient.common import utils +from osc_lib import utils + from openstackclient.i18n import _ LOG = logging.getLogger(__name__) diff --git a/openstackclient/volume/v1/backup.py b/openstackclient/volume/v1/backup.py index 607b5211..5f34a2c5 100644 --- a/openstackclient/volume/v1/backup.py +++ b/openstackclient/volume/v1/backup.py @@ -16,10 +16,11 @@ """Volume v1 Backup action implementations""" import copy + +from osc_lib.command import command +from osc_lib import utils import six -from openstackclient.common import command -from openstackclient.common import utils from openstackclient.i18n import _ @@ -75,7 +76,7 @@ class DeleteBackup(command.Command): 'backups', metavar='<backup>', nargs="+", - help=_('Backup(s) to delete (ID only)'), + help=_('Backup(s) to delete (name or ID)'), ) return parser @@ -149,7 +150,7 @@ class RestoreBackup(command.Command): parser.add_argument( 'backup', metavar='<backup>', - help=_('Backup to restore (ID only)') + help=_('Backup to restore (name or ID)') ) parser.add_argument( 'volume', @@ -176,7 +177,7 @@ class ShowBackup(command.ShowOne): parser.add_argument( 'backup', metavar='<backup>', - help=_('Backup to display (ID only)') + help=_('Backup to display (name or ID)') ) return parser diff --git a/openstackclient/volume/v1/qos_specs.py b/openstackclient/volume/v1/qos_specs.py index c49477a0..56a96256 100644 --- a/openstackclient/volume/v1/qos_specs.py +++ b/openstackclient/volume/v1/qos_specs.py @@ -15,11 +15,11 @@ """Volume v1 QoS action implementations""" +from osc_lib.cli import parseractions +from osc_lib.command import command +from osc_lib import utils import six -from openstackclient.common import command -from openstackclient.common import parseractions -from openstackclient.common import utils from openstackclient.i18n import _ diff --git a/openstackclient/volume/v1/service.py b/openstackclient/volume/v1/service.py index 023dda98..2df38573 100644 --- a/openstackclient/volume/v1/service.py +++ b/openstackclient/volume/v1/service.py @@ -14,8 +14,9 @@ """Service action implementations""" -from openstackclient.common import command -from openstackclient.common import utils +from osc_lib.command import command +from osc_lib import utils + from openstackclient.i18n import _ diff --git a/openstackclient/volume/v1/snapshot.py b/openstackclient/volume/v1/snapshot.py index 5132d71e..bb3a1fc3 100644 --- a/openstackclient/volume/v1/snapshot.py +++ b/openstackclient/volume/v1/snapshot.py @@ -1,4 +1,4 @@ -# Copyright 2012-2013 OpenStack, LLC. +# Copyright 2012-2013 OpenStack Foundation # # 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 @@ -16,11 +16,12 @@ """Volume v1 Snapshot action implementations""" import copy + +from osc_lib.cli import parseractions +from osc_lib.command import command +from osc_lib import utils import six -from openstackclient.common import command -from openstackclient.common import parseractions -from openstackclient.common import utils from openstackclient.i18n import _ diff --git a/openstackclient/volume/v1/volume.py b/openstackclient/volume/v1/volume.py index ffec1803..e11aa1a7 100644 --- a/openstackclient/volume/v1/volume.py +++ b/openstackclient/volume/v1/volume.py @@ -16,14 +16,19 @@ """Volume v1 Volume action implementations""" import argparse +import logging + +from osc_lib.cli import parseractions +from osc_lib.command import command +from osc_lib import utils import six -from openstackclient.common import command -from openstackclient.common import parseractions -from openstackclient.common import utils from openstackclient.i18n import _ +LOG = logging.getLogger(__name__) + + class CreateVolume(command.ShowOne): """Create new volume""" @@ -342,13 +347,12 @@ class SetVolume(command.Command): if parsed_args.size: if volume.status != 'available': - self.app.log.error(_("Volume is in %s state, it must be " - "available before size can be extended") % - volume.status) + LOG.error(_("Volume is in %s state, it must be available " + "before size can be extended"), volume.status) return if parsed_args.size <= volume.size: - self.app.log.error(_("New size must be greater than %s GB") % - volume.size) + LOG.error(_("New size must be greater than %s GB"), + volume.size) return volume_client.volumes.extend(volume.id, parsed_args.size) diff --git a/openstackclient/volume/v1/volume_transfer_request.py b/openstackclient/volume/v1/volume_transfer_request.py index 98689e7b..5d8ff683 100644 --- a/openstackclient/volume/v1/volume_transfer_request.py +++ b/openstackclient/volume/v1/volume_transfer_request.py @@ -14,9 +14,9 @@ """Volume v2 transfer action implementations""" +from osc_lib.command import command +from osc_lib import utils -from openstackclient.common import command -from openstackclient.common import utils from openstackclient.i18n import _ diff --git a/openstackclient/volume/v1/volume_type.py b/openstackclient/volume/v1/volume_type.py index 4e9b1920..3fe4fa05 100644 --- a/openstackclient/volume/v1/volume_type.py +++ b/openstackclient/volume/v1/volume_type.py @@ -15,14 +15,20 @@ """Volume v1 Type action implementations""" +import logging + +from osc_lib.cli import parseractions +from osc_lib.command import command +from osc_lib import exceptions +from osc_lib import utils import six -from openstackclient.common import command -from openstackclient.common import parseractions -from openstackclient.common import utils from openstackclient.i18n import _ +LOG = logging.getLogger(__name__) + + class CreateVolumeType(command.ShowOne): """Create new volume type""" @@ -56,22 +62,39 @@ class CreateVolumeType(command.ShowOne): class DeleteVolumeType(command.Command): - """Delete volume type""" + """Delete volume type(s)""" def get_parser(self, prog_name): parser = super(DeleteVolumeType, self).get_parser(prog_name) parser.add_argument( - 'volume_type', + 'volume_types', metavar='<volume-type>', - help=_('Volume type to delete (name or ID)'), + nargs='+', + help=_('Volume type(s) to delete (name or ID)'), ) return parser def take_action(self, parsed_args): volume_client = self.app.client_manager.volume - volume_type_id = utils.find_resource( - volume_client.volume_types, parsed_args.volume_type).id - volume_client.volume_types.delete(volume_type_id) + result = 0 + + for volume_type in parsed_args.volume_types: + try: + vol_type = utils.find_resource(volume_client.volume_types, + volume_type) + + volume_client.volume_types.delete(vol_type) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete volume type with " + "name or ID '%(volume_type)s': %(e)s") + % {'volume_type': volume_type, 'e': e}) + + if result > 0: + total = len(parsed_args.volume_types) + msg = (_("%(result)s of %(total)s volume types failed " + "to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) class ListVolumeType(command.Lister): diff --git a/openstackclient/volume/v2/backup.py b/openstackclient/volume/v2/backup.py index e6fbe78d..519913a9 100644 --- a/openstackclient/volume/v2/backup.py +++ b/openstackclient/volume/v2/backup.py @@ -16,10 +16,10 @@ import copy +from osc_lib.command import command +from osc_lib import utils import six -from openstackclient.common import command -from openstackclient.common import utils from openstackclient.i18n import _ @@ -48,6 +48,12 @@ class CreateBackup(command.ShowOne): metavar="<container>", help=_("Optional backup container name") ) + parser.add_argument( + '--force', + action='store_true', + default=False, + help=_("Allow to back up an in-use volume") + ) return parser def take_action(self, parsed_args): @@ -58,7 +64,8 @@ class CreateBackup(command.ShowOne): volume_id, container=parsed_args.container, name=parsed_args.name, - description=parsed_args.description + description=parsed_args.description, + force=parsed_args.force, ) backup._info.pop("links", None) return zip(*sorted(six.iteritems(backup._info))) @@ -147,7 +154,7 @@ class RestoreBackup(command.ShowOne): parser.add_argument( "backup", metavar="<backup>", - help=_("Backup to restore (ID only)") + help=_("Backup to restore (name or ID)") ) parser.add_argument( "volume", diff --git a/openstackclient/volume/v2/qos_specs.py b/openstackclient/volume/v2/qos_specs.py index 90e11c77..7ec272b3 100644 --- a/openstackclient/volume/v2/qos_specs.py +++ b/openstackclient/volume/v2/qos_specs.py @@ -15,11 +15,11 @@ """Volume v2 QoS action implementations""" +from osc_lib.cli import parseractions +from osc_lib.command import command +from osc_lib import utils import six -from openstackclient.common import command -from openstackclient.common import parseractions -from openstackclient.common import utils from openstackclient.i18n import _ diff --git a/openstackclient/volume/v2/service.py b/openstackclient/volume/v2/service.py index 023dda98..2df38573 100644 --- a/openstackclient/volume/v2/service.py +++ b/openstackclient/volume/v2/service.py @@ -14,8 +14,9 @@ """Service action implementations""" -from openstackclient.common import command -from openstackclient.common import utils +from osc_lib.command import command +from osc_lib import utils + from openstackclient.i18n import _ diff --git a/openstackclient/volume/v2/snapshot.py b/openstackclient/volume/v2/snapshot.py index d9017080..439904e7 100644 --- a/openstackclient/volume/v2/snapshot.py +++ b/openstackclient/volume/v2/snapshot.py @@ -16,11 +16,11 @@ import copy +from osc_lib.cli import parseractions +from osc_lib.command import command +from osc_lib import utils import six -from openstackclient.common import command -from openstackclient.common import parseractions -from openstackclient.common import utils from openstackclient.i18n import _ diff --git a/openstackclient/volume/v2/volume.py b/openstackclient/volume/v2/volume.py index 18473da3..be2388fb 100644 --- a/openstackclient/volume/v2/volume.py +++ b/openstackclient/volume/v2/volume.py @@ -15,16 +15,20 @@ """Volume V2 Volume action implementations""" import copy +import logging +from osc_lib.cli import parseractions +from osc_lib.command import command +from osc_lib import utils import six -from openstackclient.common import command -from openstackclient.common import parseractions -from openstackclient.common import utils from openstackclient.i18n import _ from openstackclient.identity import common as identity_common +LOG = logging.getLogger(__name__) + + class CreateVolume(command.ShowOne): """Create new volume""" @@ -361,13 +365,12 @@ class SetVolume(command.Command): if parsed_args.size: if volume.status != 'available': - self.app.log.error(_("Volume is in %s state, it must be " - "available before size can be extended") % - volume.status) + LOG.error(_("Volume is in %s state, it must be available " + "before size can be extended"), volume.status) return if parsed_args.size <= volume.size: - self.app.log.error(_("New size must be greater than %s GB") % - volume.size) + LOG.error(_("New size must be greater than %s GB"), + volume.size) return volume_client.volumes.extend(volume.id, parsed_args.size) @@ -454,6 +457,3 @@ class UnsetVolume(command.Command): if parsed_args.image_property: volume_client.volumes.delete_image_metadata( volume.id, parsed_args.image_property) - - if (not parsed_args.image_property and not parsed_args.property): - self.app.log.error(_("No changes requested\n")) diff --git a/openstackclient/volume/v2/volume_transfer_request.py b/openstackclient/volume/v2/volume_transfer_request.py index 98689e7b..5d8ff683 100644 --- a/openstackclient/volume/v2/volume_transfer_request.py +++ b/openstackclient/volume/v2/volume_transfer_request.py @@ -14,9 +14,9 @@ """Volume v2 transfer action implementations""" +from osc_lib.command import command +from osc_lib import utils -from openstackclient.common import command -from openstackclient.common import utils from openstackclient.i18n import _ diff --git a/openstackclient/volume/v2/volume_type.py b/openstackclient/volume/v2/volume_type.py index 9aed17bc..87f4c547 100644 --- a/openstackclient/volume/v2/volume_type.py +++ b/openstackclient/volume/v2/volume_type.py @@ -14,16 +14,21 @@ """Volume v2 Type action implementations""" +import logging + +from osc_lib.cli import parseractions +from osc_lib.command import command +from osc_lib import exceptions +from osc_lib import utils import six -from openstackclient.common import command -from openstackclient.common import exceptions -from openstackclient.common import parseractions -from openstackclient.common import utils from openstackclient.i18n import _ from openstackclient.identity import common as identity_common +LOG = logging.getLogger(__name__) + + class CreateVolumeType(command.ShowOne): """Create new volume type""" @@ -87,22 +92,39 @@ class CreateVolumeType(command.ShowOne): class DeleteVolumeType(command.Command): - """Delete volume type""" + """Delete volume type(s)""" def get_parser(self, prog_name): parser = super(DeleteVolumeType, self).get_parser(prog_name) parser.add_argument( - "volume_type", + "volume_types", metavar="<volume-type>", - help=_("Volume type to delete (name or ID)") + nargs="+", + help=_("Volume type(s) to delete (name or ID)") ) return parser def take_action(self, parsed_args): volume_client = self.app.client_manager.volume - volume_type = utils.find_resource( - volume_client.volume_types, parsed_args.volume_type) - volume_client.volume_types.delete(volume_type.id) + result = 0 + + for volume_type in parsed_args.volume_types: + try: + vol_type = utils.find_resource(volume_client.volume_types, + volume_type) + + volume_client.volume_types.delete(vol_type) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete volume type with " + "name or ID '%(volume_type)s': %(e)s") + % {'volume_type': volume_type, 'e': e}) + + if result > 0: + total = len(parsed_args.volume_types) + msg = (_("%(result)s of %(total)s volume types failed " + "to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) class ListVolumeType(command.Lister): @@ -190,16 +212,15 @@ class SetVolumeType(command.Command): **kwargs ) except Exception as e: - self.app.log.error(_("Failed to update volume type name or" - " description: %s") % str(e)) + LOG.error(_("Failed to update volume type name or" + " description: %s"), e) result += 1 if parsed_args.property: try: volume_type.set_keys(parsed_args.property) except Exception as e: - self.app.log.error(_("Failed to set volume type" - " property: %s") % str(e)) + LOG.error(_("Failed to set volume type property: %s"), e) result += 1 if parsed_args.project: @@ -213,13 +234,13 @@ class SetVolumeType(command.Command): volume_client.volume_type_access.add_project_access( volume_type.id, project_info.id) except Exception as e: - self.app.log.error(_("Failed to set volume type access to" - " project: %s") % str(e)) + LOG.error(_("Failed to set volume type access to " + "project: %s"), e) result += 1 if result > 0: raise exceptions.CommandError(_("Command Failed: One or more of" - " the operations failed")) + " the operations failed")) class ShowVolumeType(command.ShowOne): @@ -284,8 +305,7 @@ class UnsetVolumeType(command.Command): try: volume_type.unset_keys(parsed_args.property) except Exception as e: - self.app.log.error(_("Failed to unset volume type property: %s" - ) % str(e)) + LOG.error(_("Failed to unset volume type property: %s"), e) result += 1 if parsed_args.project: @@ -299,8 +319,8 @@ class UnsetVolumeType(command.Command): volume_client.volume_type_access.remove_project_access( volume_type.id, project_info.id) except Exception as e: - self.app.log.error(_("Failed to remove volume type access from" - " project: %s") % str(e)) + LOG.error(_("Failed to remove volume type access from " + "project: %s"), e) result += 1 if result > 0: |
