diff options
Diffstat (limited to 'openstackclient')
47 files changed, 952 insertions, 351 deletions
diff --git a/openstackclient/api/auth.py b/openstackclient/api/auth.py index 0018e76e..0c82fe9b 100644 --- a/openstackclient/api/auth.py +++ b/openstackclient/api/auth.py @@ -86,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' @@ -98,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_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: @@ -121,6 +123,7 @@ def build_auth_params(auth_plugin_name, cmd_options): LOG.debug('no auth_type') # delay the plugin choice, grab every option 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) @@ -147,29 +150,28 @@ def check_valid_authorization_options(options, auth_plugin_name): def check_valid_authentication_options(options, auth_plugin_name): """Validate authentication options, and provide helpful error messages.""" - 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')) - 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')) + # 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 '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)) diff --git a/openstackclient/api/auth_plugin.py b/openstackclient/api/auth_plugin.py index 36dc5160..dc47a688 100644 --- a/openstackclient/api/auth_plugin.py +++ b/openstackclient/api/auth_plugin.py @@ -15,90 +15,51 @@ import logging -from oslo_config import cfg -from six.moves.urllib import parse as urlparse - -from keystoneauth1.loading._plugins import admin_token as token_endpoint -from keystoneauth1.loading._plugins.identity import generic as ksa_password +from keystoneauth1 import loading +from keystoneauth1 import token_endpoint from openstackclient.i18n import _ LOG = logging.getLogger(__name__) -class TokenEndpoint(token_endpoint.AdminToken): +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 load_from_options(self, url, token): + @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 """ - return super(TokenEndpoint, self).load_from_options(endpoint=url, - token=token) - - 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 super(TokenEndpoint, self).load_from_options( + endpoint=url, + token=token, + ) + def get_options(self): + """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(ksa_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/common/availability_zone.py b/openstackclient/common/availability_zone.py index af161d1f..89d77d15 100644 --- a/openstackclient/common/availability_zone.py +++ b/openstackclient/common/availability_zone.py @@ -14,6 +14,7 @@ """Availability Zone action implementations""" import copy +import logging from novaclient import exceptions as nova_exceptions from osc_lib.command import command @@ -23,6 +24,9 @@ import six 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'] @@ -136,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: @@ -154,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 5dbfb417..3c35b529 100644 --- a/openstackclient/common/clientmanager.py +++ b/openstackclient/common/clientmanager.py @@ -140,8 +140,49 @@ class ClientManager(object): # prior to dereferrencing auth_ref. self._auth_setup_completed = False + 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 + def setup_auth(self): - """Set up authentication. + """Set up authentication This is deferred until authentication is actually attempted because it gets in the way of things that do not require auth. @@ -169,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: diff --git a/openstackclient/common/extension.py b/openstackclient/common/extension.py index 327fc223..de480016 100644 --- a/openstackclient/common/extension.py +++ b/openstackclient/common/extension.py @@ -16,6 +16,7 @@ """Extension action implementations""" import itertools +import logging from osc_lib.command import command from osc_lib import utils @@ -23,6 +24,9 @@ from osc_lib import utils from openstackclient.i18n import _ +LOG = logging.getLogger(__name__) + + class ListExtension(command.Lister): """List API extensions""" @@ -80,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 = ( @@ -125,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/quota.py b/openstackclient/common/quota.py index 087d8ea3..69415f0d 100644 --- a/openstackclient/common/quota.py +++ b/openstackclient/common/quota.py @@ -142,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, diff --git a/openstackclient/compute/v2/agent.py b/openstackclient/compute/v2/agent.py index ce6898c1..62be2424 100644 --- a/openstackclient/compute/v2/agent.py +++ b/openstackclient/compute/v2/agent.py @@ -15,6 +15,8 @@ """Agent action implementations""" +import logging + from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils @@ -23,6 +25,9 @@ import six from openstackclient.i18n import _ +LOG = logging.getLogger(__name__) + + class CreateAgent(command.ShowOne): """Create compute agent command""" @@ -96,14 +101,13 @@ 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) diff --git a/openstackclient/compute/v2/aggregate.py b/openstackclient/compute/v2/aggregate.py index 8000c93a..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.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 97d3f318..4179fa5c 100644 --- a/openstackclient/compute/v2/console.py +++ b/openstackclient/compute/v2/console.py @@ -93,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 @@ -105,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/flavor.py b/openstackclient/compute/v2/flavor.py index ab2bc85d..01d7da75 100644 --- a/openstackclient/compute/v2/flavor.py +++ b/openstackclient/compute/v2/flavor.py @@ -15,6 +15,8 @@ """Flavor action implementations""" +import logging + from osc_lib.cli import parseractions from osc_lib.command import command from osc_lib import exceptions @@ -25,6 +27,9 @@ 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/server.py b/openstackclient/compute/v2/server.py index 42736d66..7e4b0dc1 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -18,6 +18,7 @@ import argparse import getpass import io +import logging import os import sys @@ -36,6 +37,9 @@ 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 @@ -521,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: @@ -543,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 @@ -612,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 @@ -762,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 = ( @@ -939,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 @@ -1015,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 @@ -1068,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 @@ -1222,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: @@ -1538,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_group.py b/openstackclient/compute/v2/server_group.py index 955b147e..d51b1ec2 100644 --- a/openstackclient/compute/v2/server_group.py +++ b/openstackclient/compute/v2/server_group.py @@ -15,6 +15,8 @@ """Compute v2 Server Group action implementations""" +import logging + from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils @@ -22,6 +24,9 @@ from osc_lib import utils from openstackclient.i18n import _ +LOG = logging.getLogger(__name__) + + _formatters = { 'policies': utils.format_list, 'members': utils.format_list, @@ -95,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 index 85ee7f2d..285c7fd2 100644 --- a/openstackclient/compute/v2/server_image.py +++ b/openstackclient/compute/v2/server_image.py @@ -15,17 +15,21 @@ """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.common import command -from openstackclient.common import exceptions -from openstackclient.common import utils from openstackclient.i18n import _ +LOG = logging.getLogger(__name__) + + def _show_progress(progress): if progress: sys.stdout.write('\rProgress: %s' % progress) @@ -90,10 +94,8 @@ class CreateServerImage(command.ShowOne): ): sys.stdout.write('\n') else: - self.log.error( - _('Error creating server image: %s') % - parsed_args.server, - ) + LOG.error(_('Error creating server image: %s'), + parsed_args.server) raise exceptions.CommandError if self.app.client_manager._api_version['image'] == '1': diff --git a/openstackclient/compute/v2/service.py b/openstackclient/compute/v2/service.py index d10af2ca..2e40dd7f 100644 --- a/openstackclient/compute/v2/service.py +++ b/openstackclient/compute/v2/service.py @@ -15,6 +15,8 @@ """Service action implementations""" +import logging + from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils @@ -23,6 +25,9 @@ from openstackclient.i18n import _ from openstackclient.i18n import _LE +LOG = logging.getLogger(__name__) + + class DeleteService(command.Command): """Delete service command""" @@ -68,7 +73,7 @@ class ListService(command.Lister): compute_client = self.app.client_manager.compute if parsed_args.long: columns = ( - "Id", + "ID", "Binary", "Host", "Zone", @@ -79,7 +84,7 @@ class ListService(command.Lister): ) else: columns = ( - "Id", + "ID", "Binary", "Host", "Zone", @@ -171,7 +176,7 @@ class SetService(command.Command): cs.disable(parsed_args.host, parsed_args.service) except Exception: status = "enabled" if enabled else "disabled" - self.log.error(_LE("Failed to set service status to %s"), status) + LOG.error(_LE("Failed to set service status to %s"), status) result += 1 force_down = None @@ -185,7 +190,7 @@ class SetService(command.Command): force_down=force_down) except Exception: state = "down" if force_down else "up" - self.log.error(_LE("Failed to set service state to %s"), state) + LOG.error(_LE("Failed to set service state to %s"), state) result += 1 if result > 0: diff --git a/openstackclient/identity/v2_0/catalog.py b/openstackclient/identity/v2_0/catalog.py index 62e6b1f9..7a15cf3a 100644 --- a/openstackclient/identity/v2_0/catalog.py +++ b/openstackclient/identity/v2_0/catalog.py @@ -13,6 +13,8 @@ """Identity v2 Service Catalog action implementations""" +import logging + from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils @@ -21,6 +23,9 @@ import six from openstackclient.i18n import _ +LOG = logging.getLogger(__name__) + + def _format_endpoints(eps=None): if not eps: return "" @@ -92,8 +97,7 @@ class ShowCatalog(command.ShowOne): break if not data: - self.app.log.error(_('service %s not found\n') % - parsed_args.service) + LOG.error(_('service %s not found\n'), parsed_args.service) return ((), ()) return zip(*sorted(six.iteritems(data))) diff --git a/openstackclient/identity/v2_0/project.py b/openstackclient/identity/v2_0/project.py index c4f730e0..6c5db13c 100644 --- a/openstackclient/identity/v2_0/project.py +++ b/openstackclient/identity/v2_0/project.py @@ -15,6 +15,8 @@ """Identity v2 Project action implementations""" +import logging + from keystoneauth1 import exceptions as ks_exc from osc_lib.cli import parseractions from osc_lib.command import command @@ -24,6 +26,9 @@ import six from openstackclient.i18n import _ +LOG = logging.getLogger(__name__) + + class CreateProject(command.ShowOne): """Create new project""" @@ -87,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 diff --git a/openstackclient/identity/v2_0/role.py b/openstackclient/identity/v2_0/role.py index 4c3fe6e2..6d06230c 100644 --- a/openstackclient/identity/v2_0/role.py +++ b/openstackclient/identity/v2_0/role.py @@ -15,6 +15,8 @@ """Identity v2 Role action implementations""" +import logging + from keystoneauth1 import exceptions as ks_exc from osc_lib.command import command from osc_lib import exceptions @@ -24,6 +26,9 @@ import six from openstackclient.i18n import _ +LOG = logging.getLogger(__name__) + + class AddRole(command.ShowOne): """Add role to project:user""" @@ -94,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 diff --git a/openstackclient/identity/v2_0/service.py b/openstackclient/identity/v2_0/service.py index 947f361c..df332eb6 100644 --- a/openstackclient/identity/v2_0/service.py +++ b/openstackclient/identity/v2_0/service.py @@ -16,6 +16,7 @@ """Service action implementations""" import argparse +import logging from osc_lib.command import command from osc_lib import exceptions @@ -26,6 +27,9 @@ from openstackclient.i18n import _ from openstackclient.identity import common +LOG = logging.getLogger(__name__) + + class CreateService(command.ShowOne): """Create new service""" @@ -69,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: @@ -94,7 +98,7 @@ class DeleteService(command.Command): parser.add_argument( 'service', metavar='<service>', - help=_('Service to delete (name or ID)'), + help=_('Service to delete (type, name or ID)'), ) return parser diff --git a/openstackclient/identity/v2_0/user.py b/openstackclient/identity/v2_0/user.py index 3ee2a65e..0f327830 100644 --- a/openstackclient/identity/v2_0/user.py +++ b/openstackclient/identity/v2_0/user.py @@ -15,6 +15,8 @@ """Identity v2.0 User action implementations""" +import logging + from keystoneauth1 import exceptions as ks_exc from osc_lib.command import command from osc_lib import utils @@ -23,6 +25,9 @@ import six from openstackclient.i18n import _ +LOG = logging.getLogger(__name__) + + class CreateUser(command.ShowOne): """Create new user""" @@ -103,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 @@ -240,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', diff --git a/openstackclient/identity/v3/catalog.py b/openstackclient/identity/v3/catalog.py index a6e141c9..a62d0a93 100644 --- a/openstackclient/identity/v3/catalog.py +++ b/openstackclient/identity/v3/catalog.py @@ -13,6 +13,8 @@ """Identity v3 Service Catalog action implementations""" +import logging + from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils @@ -21,6 +23,9 @@ import six from openstackclient.i18n import _ +LOG = logging.getLogger(__name__) + + def _format_endpoints(eps=None): if not eps: return "" @@ -87,8 +92,7 @@ class ShowCatalog(command.ShowOne): break if not data: - self.app.log.error(_('service %s not found\n') % - parsed_args.service) + LOG.error(_('service %s not found\n'), parsed_args.service) return ((), ()) return zip(*sorted(six.iteritems(data))) diff --git a/openstackclient/identity/v3/domain.py b/openstackclient/identity/v3/domain.py index 8eb7bc91..001d5201 100644 --- a/openstackclient/identity/v3/domain.py +++ b/openstackclient/identity/v3/domain.py @@ -15,6 +15,7 @@ """Identity v3 Domain action implementations""" +import logging import sys from keystoneauth1 import exceptions as ks_exc @@ -25,6 +26,9 @@ import six from openstackclient.i18n import _ +LOG = logging.getLogger(__name__) + + class CreateDomain(command.ShowOne): """Create new domain""" @@ -75,7 +79,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 diff --git a/openstackclient/identity/v3/federation_protocol.py b/openstackclient/identity/v3/federation_protocol.py index 377ddcce..09480245 100644 --- a/openstackclient/identity/v3/federation_protocol.py +++ b/openstackclient/identity/v3/federation_protocol.py @@ -14,6 +14,8 @@ """Identity v3 Protocols actions implementations""" +import logging + from osc_lib.command import command from osc_lib import utils import six @@ -21,6 +23,9 @@ import six 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 fb0a25b9..8351fe64 100644 --- a/openstackclient/identity/v3/group.py +++ b/openstackclient/identity/v3/group.py @@ -15,6 +15,7 @@ """Group action implementations""" +import logging import sys from keystoneauth1 import exceptions as ks_exc @@ -26,6 +27,9 @@ 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 0b530a26..5c638f9b 100644 --- a/openstackclient/identity/v3/identity_provider.py +++ b/openstackclient/identity/v3/identity_provider.py @@ -13,6 +13,8 @@ """Identity v3 IdentityProvider action implementations""" +import logging + from osc_lib.command import command from osc_lib import utils import six @@ -20,6 +22,9 @@ import six 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 5937474e..74ead228 100644 --- a/openstackclient/identity/v3/mapping.py +++ b/openstackclient/identity/v3/mapping.py @@ -16,6 +16,7 @@ """Identity v3 federation mapping action implementations""" import json +import logging from osc_lib.command import command from osc_lib import exceptions @@ -25,6 +26,9 @@ import six 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/project.py b/openstackclient/identity/v3/project.py index 6145dcf1..4db5bef1 100644 --- a/openstackclient/identity/v3/project.py +++ b/openstackclient/identity/v3/project.py @@ -15,6 +15,8 @@ """Project action implementations""" +import logging + from keystoneauth1 import exceptions as ks_exc from osc_lib.cli import parseractions from osc_lib.command import command @@ -25,6 +27,9 @@ from openstackclient.i18n import _ from openstackclient.identity import common +LOG = logging.getLogger(__name__) + + class CreateProject(command.ShowOne): """Create new project""" @@ -111,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 diff --git a/openstackclient/identity/v3/role.py b/openstackclient/identity/v3/role.py index 1bb27585..965ca3f5 100644 --- a/openstackclient/identity/v3/role.py +++ b/openstackclient/identity/v3/role.py @@ -15,6 +15,7 @@ """Identity v3 Role action implementations""" +import logging import sys from keystoneauth1 import exceptions as ks_exc @@ -26,6 +27,9 @@ 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/service.py b/openstackclient/identity/v3/service.py index 7beadc9d..195b2701 100644 --- a/openstackclient/identity/v3/service.py +++ b/openstackclient/identity/v3/service.py @@ -130,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/user.py b/openstackclient/identity/v3/user.py index 39125e2c..b0c80c14 100644 --- a/openstackclient/identity/v3/user.py +++ b/openstackclient/identity/v3/user.py @@ -16,6 +16,7 @@ """Identity v3 User action implementations""" import copy +import logging import sys from keystoneauth1 import exceptions as ks_exc @@ -27,6 +28,9 @@ 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', diff --git a/openstackclient/image/v1/image.py b/openstackclient/image/v1/image.py index 1644809d..e7b60e5a 100644 --- a/openstackclient/image/v1/image.py +++ b/openstackclient/image/v1/image.py @@ -17,6 +17,7 @@ import argparse import io +import logging import os import sys @@ -39,6 +40,9 @@ DEFAULT_CONTAINER_FORMAT = 'bare' DEFAULT_DISK_FORMAT = 'raw' +LOG = logging.getLogger(__name__) + + def _format_visibility(data): """Return a formatted visibility string @@ -189,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 @@ -608,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', @@ -684,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 53f9530b..309b1b6b 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -16,6 +16,7 @@ """Image V2 Action Implementations""" import argparse +import logging from glanceclient.common import utils as gc_utils from osc_lib.cli import parseractions @@ -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: @@ -384,9 +386,9 @@ class DeleteImage(command.Command): image_client.images.delete(image_obj.id) except Exception as e: del_result += 1 - self.app.log.error(_("Failed to delete image with " - "name or ID '%(image)s': %(e)s") - % {'image': image, 'e': e}) + 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): @@ -806,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, @@ -836,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 @@ -908,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: @@ -917,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/common.py b/openstackclient/network/common.py index 16cc7379..f62840fc 100644 --- a/openstackclient/network/common.py +++ b/openstackclient/network/common.py @@ -12,6 +12,7 @@ # import abc +import logging from osc_lib.command import command from osc_lib import exceptions @@ -20,6 +21,9 @@ import six from openstackclient.i18n import _ +LOG = logging.getLogger(__name__) + + @six.add_metaclass(abc.ABCMeta) class NetworkAndComputeCommand(command.Command): """Network and Compute Command @@ -39,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: @@ -102,7 +106,7 @@ class NetworkAndComputeDelete(NetworkAndComputeCommand): "name_or_id": r, "e": e, } - self.app.log.error(msg) + LOG.error(msg) ret += 1 if ret: @@ -134,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: @@ -185,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 bc1a96c3..6cd13f8c 100644 --- a/openstackclient/network/v2/address_scope.py +++ b/openstackclient/network/v2/address_scope.py @@ -13,6 +13,8 @@ """Address scope action implementations""" +import logging + from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils @@ -21,6 +23,9 @@ 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: @@ -122,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) diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index 1c5db706..57461f89 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -293,9 +293,9 @@ class DeletePort(command.Command): client.delete_port(obj) except Exception as e: result += 1 - self.app.log.error(_("Failed to delete port with " - "name or ID '%(port)s': %(e)s") - % {'port': port, 'e': e}) + 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) diff --git a/openstackclient/network/v2/router.py b/openstackclient/network/v2/router.py index 92ecf84d..6271a878 100644 --- a/openstackclient/network/v2/router.py +++ b/openstackclient/network/v2/router.py @@ -19,6 +19,7 @@ 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 _ @@ -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): diff --git a/openstackclient/network/v2/security_group_rule.py b/openstackclient/network/v2/security_group_rule.py index 18863223..e3be44ec 100644 --- a/openstackclient/network/v2/security_group_rule.py +++ b/openstackclient/network/v2/security_group_rule.py @@ -526,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 a2e32622..752923f7 100644 --- a/openstackclient/network/v2/subnet.py +++ b/openstackclient/network/v2/subnet.py @@ -142,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() @@ -255,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, diff --git a/openstackclient/tests/compute/v2/fakes.py b/openstackclient/tests/compute/v2/fakes.py index 97d0c96f..b7f17fbc 100644 --- a/openstackclient/tests/compute/v2/fakes.py +++ b/openstackclient/tests/compute/v2/fakes.py @@ -87,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): @@ -612,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', } @@ -732,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) 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 4365a540..da76b6d7 100644 --- a/openstackclient/tests/compute/v2/test_flavor.py +++ b/openstackclient/tests/compute/v2/test_flavor.py @@ -14,6 +14,8 @@ # import copy +import mock +from mock import call from osc_lib import exceptions from osc_lib import utils @@ -75,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) @@ -161,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), @@ -172,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) @@ -188,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 @@ -204,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): @@ -542,8 +596,12 @@ class TestFlavorSet(TestFlavor): ('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): @@ -717,5 +775,8 @@ class TestFlavorUnset(TestFlavor): ('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_server_image.py b/openstackclient/tests/compute/v2/test_server_image.py index 660e9817..8a8bd9bc 100644 --- a/openstackclient/tests/compute/v2/test_server_image.py +++ b/openstackclient/tests/compute/v2/test_server_image.py @@ -12,8 +12,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_image 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_service.py b/openstackclient/tests/compute/v2/test_service.py index b360c9dc..e41d633a 100644 --- a/openstackclient/tests/compute/v2/test_service.py +++ b/openstackclient/tests/compute/v2/test_service.py @@ -62,11 +62,35 @@ class TestServiceDelete(TestService): 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 @@ -93,8 +117,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 = [ @@ -114,8 +138,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): @@ -330,12 +359,9 @@ class TestServiceSet(TestService): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - with mock.patch.object(self.cmd.log, 'error') as mock_log: - with mock.patch.object(self.service_mock, 'enable', - side_effect=Exception()): - self.assertRaises(exceptions.CommandError, - self.cmd.take_action, parsed_args) - mock_log.assert_called_once_with( - "Failed to set service status to %s", "enabled") - self.service_mock.force_down.assert_called_once_with( - self.service.host, self.service.binary, force_down=True) + 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/image/v1/test_image.py b/openstackclient/tests/image/v1/test_image.py index 99c0b0ee..14aa331f 100644 --- a/openstackclient/tests/image/v1/test_image.py +++ b/openstackclient/tests/image/v1/test_image.py @@ -475,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/network/v2/fakes.py b/openstackclient/tests/network/v2/fakes.py index a23efc2d..50d9899c 100644 --- a/openstackclient/tests/network/v2/fakes.py +++ b/openstackclient/tests/network/v2/fakes.py @@ -352,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, @@ -738,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. diff --git a/openstackclient/tests/network/v2/test_router.py b/openstackclient/tests/network/v2/test_router.py index 16296139..e3da253a 100644 --- a/openstackclient/tests/network/v2/test_router.py +++ b/openstackclient/tests/network/v2/test_router.py @@ -12,7 +12,9 @@ # import mock +from mock import call +from osc_lib import exceptions from osc_lib import utils as osc_utils from openstackclient.network.v2 import router @@ -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): diff --git a/openstackclient/tests/network/v2/test_subnet.py b/openstackclient/tests/network/v2/test_subnet.py index a57a0308..99b558c0 100644 --- a/openstackclient/tests/network/v2/test_subnet.py +++ b/openstackclient/tests/network/v2/test_subnet.py @@ -91,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', @@ -105,6 +113,7 @@ class TestCreateSubnet(TestSubnet): 'name', 'network_id', 'project_id', + 'segment_id', 'subnetpool_id', ) @@ -122,6 +131,7 @@ class TestCreateSubnet(TestSubnet): _subnet.name, _subnet.network_id, _subnet.project_id, + _subnet.segment_id, _subnet.subnetpool_id, ) @@ -139,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, ) @@ -156,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, ) @@ -189,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 = [] @@ -199,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, @@ -233,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, @@ -293,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, @@ -360,6 +373,59 @@ 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): @@ -646,6 +712,7 @@ class TestShowSubnet(TestSubnet): 'name', 'network_id', 'project_id', + 'segment_id', 'subnetpool_id', ) @@ -663,6 +730,7 @@ class TestShowSubnet(TestSubnet): _subnet.name, _subnet.network_id, _subnet.tenant_id, + _subnet.segment_id, _subnet.subnetpool_id, ) |
