diff options
Diffstat (limited to 'openstackclient')
46 files changed, 1348 insertions, 590 deletions
diff --git a/openstackclient/api/api.py b/openstackclient/api/api.py index 0b00ff50..04d88f31 100644 --- a/openstackclient/api/api.py +++ b/openstackclient/api/api.py @@ -19,6 +19,8 @@ from keystoneauth1 import exceptions as ks_exceptions from keystoneauth1 import session as ks_session from osc_lib import exceptions +from openstackclient.i18n import _ + class KeystoneSession(object): """Wrapper for the Keystone Session @@ -254,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 @@ -264,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, @@ -313,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] @@ -343,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 a55af293..b56035e4 100644 --- a/openstackclient/api/auth.py +++ b/openstackclient/api/auth.py @@ -171,7 +171,8 @@ def check_valid_auth_options(options, auth_plugin_name, required_scope=True): 'auth.url')) 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): @@ -187,10 +188,9 @@ def build_auth_plugins_option_parser(parser): 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 @@ -215,10 +215,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 44d3b38e..36dc5160 100644 --- a/openstackclient/api/auth_plugin.py +++ b/openstackclient/api/auth_plugin.py @@ -21,6 +21,8 @@ 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 openstackclient.i18n import _ + LOG = logging.getLogger(__name__) @@ -51,10 +53,10 @@ class TokenEndpoint(token_endpoint.AdminToken): options.extend([ # Maintain name 'url' for compatibility cfg.StrOpt('url', - help='Specific service endpoint to use'), + help=_('Specific service endpoint to use')), cfg.StrOpt('token', secret=True, - help='Authentication token to use'), + help=_('Authentication token to use')), ]) return options diff --git a/openstackclient/common/command.py b/openstackclient/common/command.py index adf984fa..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 -from osc_lib import exceptions -import six +import sys -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/timing.py b/openstackclient/common/timing.py index dd2aeb83..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 osc_lib.command 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/compute/client.py b/openstackclient/compute/client.py index a83700db..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 diff --git a/openstackclient/compute/v2/host.py b/openstackclient/compute/v2/host.py index 620b4abb..764b22af 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 diff --git a/openstackclient/compute/v2/hypervisor.py b/openstackclient/compute/v2/hypervisor.py index a34f2e1d..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 diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index eb954c36..42736d66 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -164,23 +164,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) @@ -597,63 +580,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)""" diff --git a/openstackclient/compute/v2/server_image.py b/openstackclient/compute/v2/server_image.py new file mode 100644 index 00000000..85ee7f2d --- /dev/null +++ b/openstackclient/compute/v2/server_image.py @@ -0,0 +1,111 @@ +# 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 sys + +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 _ + + +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: + self.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 30465783..d10af2ca 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 @@ -20,6 +20,7 @@ from osc_lib import exceptions from osc_lib import utils from openstackclient.i18n import _ +from openstackclient.i18n import _LE class DeleteService(command.Command): @@ -127,6 +128,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): @@ -139,20 +151,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" + self.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" + self.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/identity/v2_0/endpoint.py b/openstackclient/identity/v2_0/endpoint.py index 4ffa363e..ee2bab6f 100644 --- a/openstackclient/identity/v2_0/endpoint.py +++ b/openstackclient/identity/v2_0/endpoint.py @@ -31,7 +31,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', @@ -81,7 +81,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 diff --git a/openstackclient/identity/v3/endpoint.py b/openstackclient/identity/v3/endpoint.py index 367b4e7b..2f1cc9f3 100644 --- a/openstackclient/identity/v3/endpoint.py +++ b/openstackclient/identity/v3/endpoint.py @@ -40,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', @@ -102,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 @@ -121,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', @@ -169,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/image/v2/image.py b/openstackclient/image/v2/image.py index 47ba6493..53f9530b 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -372,13 +372,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 + self.app.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): diff --git a/openstackclient/network/client.py b/openstackclient/network/client.py index c81b7d87..d12987dd 100644 --- a/openstackclient/network/client.py +++ b/openstackclient/network/client.py @@ -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/v2/floating_ip.py b/openstackclient/network/v2/floating_ip.py index c734c2ed..8fbf049e 100644 --- a/openstackclient/network/v2/floating_ip.py +++ b/openstackclient/network/v2/floating_ip.py @@ -110,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 d429e86c..1d7b2aed 100644 --- a/openstackclient/network/v2/ip_availability.py +++ b/openstackclient/network/v2/ip_availability.py @@ -41,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 diff --git a/openstackclient/network/v2/network.py b/openstackclient/network/v2/network.py index 4d01accd..41735500 100644 --- a/openstackclient/network/v2/network.py +++ b/openstackclient/network/v2/network.py @@ -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( diff --git a/openstackclient/network/v2/security_group.py b/openstackclient/network/v2/security_group.py index 95971800..f832f721 100644 --- a/openstackclient/network/v2/security_group.py +++ b/openstackclient/network/v2/security_group.py @@ -164,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 7c810786..18863223 100644 --- a/openstackclient/network/v2/security_group_rule.py +++ b/openstackclient/network/v2/security_group_rule.py @@ -333,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): diff --git a/openstackclient/network/v2/subnet.py b/openstackclient/network/v2/subnet.py index ceb1cb14..a2e32622 100644 --- a/openstackclient/network/v2/subnet.py +++ b/openstackclient/network/v2/subnet.py @@ -14,6 +14,7 @@ """Subnet action implementations""" import copy +import logging from osc_lib.cli import parseractions from osc_lib.command import command @@ -24,6 +25,9 @@ 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] @@ -270,21 +274,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): diff --git a/openstackclient/network/v2/subnet_pool.py b/openstackclient/network/v2/subnet_pool.py index 79f98fd9..55dfed83 100644 --- a/openstackclient/network/v2/subnet_pool.py +++ b/openstackclient/network/v2/subnet_pool.py @@ -13,14 +13,20 @@ """Subnet pool 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 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): 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 3e899cb5..12a63af2 100644 --- a/openstackclient/shell.py +++ b/openstackclient/shell.py @@ -26,6 +26,7 @@ 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 @@ -35,7 +36,7 @@ from oslo_utils import strutils import openstackclient from openstackclient.common import clientmanager from openstackclient.common import commandmanager -from openstackclient.common import timing +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 diff --git a/openstackclient/tests/common/test_command.py b/openstackclient/tests/common/test_command.py index a8bcf6a8..658bc895 100644 --- a/openstackclient/tests/common/test_command.py +++ b/openstackclient/tests/common/test_command.py @@ -14,9 +14,9 @@ import mock -from osc_lib.command import command from osc_lib import exceptions +from openstackclient.common import command from openstackclient.tests import fakes as test_fakes from openstackclient.tests import utils as test_utils 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/compute/v2/fakes.py b/openstackclient/tests/compute/v2/fakes.py index 9682eec4..60abb8ef 100644 --- a/openstackclient/tests/compute/v2/fakes.py +++ b/openstackclient/tests/compute/v2/fakes.py @@ -452,6 +452,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.""" diff --git a/openstackclient/tests/compute/v2/test_flavor.py b/openstackclient/tests/compute/v2/test_flavor.py index 27b53bbf..4365a540 100644 --- a/openstackclient/tests/compute/v2/test_flavor.py +++ b/openstackclient/tests/compute/v2/test_flavor.py @@ -469,6 +469,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): @@ -483,12 +484,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 = [ @@ -496,7 +500,7 @@ class TestFlavorSet(TestFlavor): self.flavor.id, ] verifylist = [ - ('project', ''), + ('project', None), ('flavor', self.flavor.id), ] self.assertRaises(tests_utils.ParserException, self.check_parser, @@ -509,12 +513,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) @@ -541,7 +541,6 @@ 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) @@ -672,6 +671,18 @@ class TestFlavorUnset(TestFlavor): self.flavor.unset_keys.assert_not_called() self.assertIsNone(result) + def test_flavor_unset_no_project(self): + arglist = [ + '--project', + self.flavor.id, + ] + verifylist = [ + ('project', None), + ('flavor', self.flavor.id), + ] + self.assertRaises(tests_utils.ParserException, self.check_parser, + self.cmd, arglist, verifylist) + def test_flavor_unset_no_flavor(self): arglist = [ '--project', identity_fakes.project_id, diff --git a/openstackclient/tests/compute/v2/test_server.py b/openstackclient/tests/compute/v2/test_server.py index e10f43a1..0f155601 100644 --- a/openstackclient/tests/compute/v2/test_server.py +++ b/openstackclient/tests/compute/v2/test_server.py @@ -511,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. 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..660e9817 --- /dev/null +++ b/openstackclient/tests/compute/v2/test_server_image.py @@ -0,0 +1,227 @@ +# 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.common import exceptions +from openstackclient.common 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 3f741340..b360c9dc 100644 --- a/openstackclient/tests/compute/v2/test_service.py +++ b/openstackclient/tests/compute/v2/test_service.py @@ -13,6 +13,8 @@ # under the License. # +import mock + from osc_lib import exceptions from openstackclient.compute.v2 import service @@ -225,8 +227,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' @@ -243,5 +249,93 @@ 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.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) diff --git a/openstackclient/tests/image/v2/test_image.py b/openstackclient/tests/image/v2/test_image.py index 3dbf504a..592def21 100644 --- a/openstackclient/tests/image/v2/test_image.py +++ b/openstackclient/tests/image/v2/test_image.py @@ -474,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): diff --git a/openstackclient/tests/network/v2/fakes.py b/openstackclient/tests/network/v2/fakes.py index 9efbe8c6..a23efc2d 100644 --- a/openstackclient/tests/network/v2/fakes.py +++ b/openstackclient/tests/network/v2/fakes.py @@ -611,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.""" @@ -670,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.""" @@ -732,6 +771,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.""" @@ -871,3 +929,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_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 39e11cd5..c6ec2b0b 100644 --- a/openstackclient/tests/network/v2/test_ip_availability.py +++ b/openstackclient/tests/network/v2/test_ip_availability.py @@ -82,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)) @@ -116,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) 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 b1f2209d..b2862679 100644 --- a/openstackclient/tests/network/v2/test_security_group_rule.py +++ b/openstackclient/tests/network/v2/test_security_group_rule.py @@ -13,6 +13,7 @@ import copy import mock +from mock import call from osc_lib import exceptions @@ -668,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( @@ -686,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() @@ -716,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 de7e1821..a57a0308 100644 --- a/openstackclient/tests/network/v2/test_subnet.py +++ b/openstackclient/tests/network/v2/test_subnet.py @@ -13,7 +13,9 @@ import copy import mock +from mock import call +from osc_lib import exceptions from osc_lib import utils from openstackclient.network.v2 import subnet as subnet_v2 @@ -361,32 +363,82 @@ class TestCreateSubnet(TestSubnet): 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. diff --git a/openstackclient/tests/network/v2/test_subnet_pool.py b/openstackclient/tests/network/v2/test_subnet_pool.py index 10ef76d8..7a96b30f 100644 --- a/openstackclient/tests/network/v2/test_subnet_pool.py +++ b/openstackclient/tests/network/v2/test_subnet_pool.py @@ -14,7 +14,9 @@ import argparse import copy import mock +from mock import call +from osc_lib import exceptions from osc_lib import utils from openstackclient.network.v2 import subnet_pool @@ -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. 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/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/volume/client.py b/openstackclient/volume/client.py index cc195460..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 diff --git a/openstackclient/volume/v1/snapshot.py b/openstackclient/volume/v1/snapshot.py index 41127af5..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 diff --git a/openstackclient/volume/v1/volume.py b/openstackclient/volume/v1/volume.py index 7a3bfb97..e11aa1a7 100644 --- a/openstackclient/volume/v1/volume.py +++ b/openstackclient/volume/v1/volume.py @@ -16,6 +16,7 @@ """Volume v1 Volume action implementations""" import argparse +import logging from osc_lib.cli import parseractions from osc_lib.command import command @@ -25,6 +26,9 @@ import six from openstackclient.i18n import _ +LOG = logging.getLogger(__name__) + + class CreateVolume(command.ShowOne): """Create new volume""" @@ -343,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/v2/volume.py b/openstackclient/volume/v2/volume.py index b1a3af2e..e54395fa 100644 --- a/openstackclient/volume/v2/volume.py +++ b/openstackclient/volume/v2/volume.py @@ -15,6 +15,7 @@ """Volume V2 Volume action implementations""" import copy +import logging from osc_lib.cli import parseractions from osc_lib.command import command @@ -25,6 +26,9 @@ 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) @@ -456,4 +459,4 @@ class UnsetVolume(command.Command): 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")) + LOG.error(_("No changes requested")) diff --git a/openstackclient/volume/v2/volume_type.py b/openstackclient/volume/v2/volume_type.py index dc6b4f9b..5e9faa1d 100644 --- a/openstackclient/volume/v2/volume_type.py +++ b/openstackclient/volume/v2/volume_type.py @@ -14,6 +14,8 @@ """Volume v2 Type action implementations""" +import logging + from osc_lib.cli import parseractions from osc_lib.command import command from osc_lib import exceptions @@ -24,6 +26,9 @@ from openstackclient.i18n import _ from openstackclient.identity import common as identity_common +LOG = logging.getLogger(__name__) + + class CreateVolumeType(command.ShowOne): """Create new volume type""" @@ -190,16 +195,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 +217,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 +288,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 +302,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: |
