diff options
Diffstat (limited to 'openstackclient')
87 files changed, 2603 insertions, 1023 deletions
diff --git a/openstackclient/api/auth.py b/openstackclient/api/auth.py index 0c82fe9b..d62a82dc 100644 --- a/openstackclient/api/auth.py +++ b/openstackclient/api/auth.py @@ -11,229 +11,15 @@ # under the License. # -"""Authentication Library""" +# NOTE(dtroyer): This file is deprecated in Jun 2016, remove after 4.x release +# or Jun 2017. -import argparse -import logging +import sys -from keystoneauth1.loading import base -from osc_lib import exceptions as exc -from osc_lib import utils +from osc_lib.api.auth import * # noqa -from openstackclient.i18n import _ -LOG = logging.getLogger(__name__) - -# Initialize the list of Authentication plugins early in order -# to get the command-line options -PLUGIN_LIST = None - -# List of plugin command line options -OPTIONS_LIST = {} - - -def get_plugin_list(): - """Gather plugin list and cache it""" - global PLUGIN_LIST - - if PLUGIN_LIST is None: - PLUGIN_LIST = base.get_available_plugin_names() - return PLUGIN_LIST - - -def get_options_list(): - """Gather plugin options so the help action has them available""" - - global OPTIONS_LIST - - if not OPTIONS_LIST: - for plugin_name in get_plugin_list(): - plugin_options = base.get_plugin_options(plugin_name) - for o in plugin_options: - os_name = o.dest.lower().replace('_', '-') - os_env_name = 'OS_' + os_name.upper().replace('-', '_') - OPTIONS_LIST.setdefault( - os_name, {'env': os_env_name, 'help': ''}, - ) - # TODO(mhu) simplistic approach, would be better to only add - # help texts if they vary from one auth plugin to another - # also the text rendering is ugly in the CLI ... - OPTIONS_LIST[os_name]['help'] += 'With %s: %s\n' % ( - plugin_name, - o.help, - ) - return OPTIONS_LIST - - -def select_auth_plugin(options): - """Pick an auth plugin based on --os-auth-type or other options""" - - auth_plugin_name = None - - # Do the token/url check first as this must override the default - # 'password' set by os-client-config - # Also, url and token are not copied into o-c-c's auth dict (yet?) - if options.auth.get('url') and options.auth.get('token'): - # service token authentication - auth_plugin_name = 'token_endpoint' - elif options.auth_type in PLUGIN_LIST: - # A direct plugin name was given, use it - auth_plugin_name = options.auth_type - elif options.auth.get('username'): - if options.identity_api_version == '3': - auth_plugin_name = 'v3password' - elif options.identity_api_version.startswith('2'): - auth_plugin_name = 'v2password' - else: - # let keystoneclient figure it out itself - auth_plugin_name = 'password' - elif options.auth.get('token'): - if options.identity_api_version == '3': - auth_plugin_name = 'v3token' - elif options.identity_api_version.startswith('2'): - auth_plugin_name = 'v2token' - else: - # let keystoneclient figure it out itself - auth_plugin_name = 'token' - else: - # The ultimate default is similar to the original behaviour, - # but this time with version discovery - auth_plugin_name = 'password' - LOG.debug("Auth plugin %s selected", auth_plugin_name) - return auth_plugin_name - - -def build_auth_params(auth_plugin_name, cmd_options): - - if auth_plugin_name: - LOG.debug('auth_type: %s', auth_plugin_name) - auth_plugin_loader = base.get_plugin_loader(auth_plugin_name) - auth_params = {opt.dest: opt.default - for opt in base.get_plugin_options(auth_plugin_name)} - auth_params.update(dict(cmd_options.auth)) - # grab tenant from project for v2.0 API compatibility - if auth_plugin_name.startswith("v2"): - if 'project_id' in auth_params: - auth_params['tenant_id'] = auth_params['project_id'] - del auth_params['project_id'] - if 'project_name' in auth_params: - auth_params['tenant_name'] = auth_params['project_name'] - del auth_params['project_name'] - else: - LOG.debug('no auth_type') - # delay the plugin choice, grab every option - auth_plugin_loader = None - auth_params = dict(cmd_options.auth) - plugin_options = set([o.replace('-', '_') for o in get_options_list()]) - for option in plugin_options: - LOG.debug('fetching option %s', option) - auth_params[option] = getattr(cmd_options.auth, option, None) - return (auth_plugin_loader, auth_params) - - -def check_valid_authorization_options(options, auth_plugin_name): - """Validate authorization options, and provide helpful error messages.""" - if (options.auth.get('project_id') and not - options.auth.get('domain_id') and not - options.auth.get('domain_name') and not - options.auth.get('project_name') and not - options.auth.get('tenant_id') and not - options.auth.get('tenant_name')): - raise exc.CommandError(_( - 'Missing parameter(s): ' - 'Set either a project or a domain scope, but not both. Set a ' - 'project scope with --os-project-name, OS_PROJECT_NAME, or ' - 'auth.project_name. Alternatively, set a domain scope with ' - '--os-domain-name, OS_DOMAIN_NAME or auth.domain_name.')) - - -def check_valid_authentication_options(options, auth_plugin_name): - """Validate authentication options, and provide helpful error messages.""" - - # Get all the options defined within the plugin. - plugin_opts = base.get_plugin_options(auth_plugin_name) - plugin_opts = {opt.dest: opt for opt in plugin_opts} - - # NOTE(aloga): this is an horrible hack. We need a way to specify the - # required options in the plugins. Using the "required" argument for - # the oslo_config.cfg.Opt does not work, as it is not possible to load the - # plugin if the option is not defined, so the error will simply be: - # "NoMatchingPlugin: The plugin foobar could not be found" - msgs = [] - if 'password' in plugin_opts and not options.auth.get('username'): - msgs.append(_('Set a username with --os-username, OS_USERNAME,' - ' or auth.username')) - if 'auth_url' in plugin_opts and not options.auth.get('auth_url'): - msgs.append(_('Set a service AUTH_URL, with --os-auth-url, ' - 'OS_AUTH_URL or auth.auth_url')) - if 'url' in plugin_opts and not options.auth.get('url'): - msgs.append(_('Set a service URL, with --os-url, ' - 'OS_URL or auth.url')) - if 'token' in plugin_opts and not options.auth.get('token'): - msgs.append(_('Set a token with --os-token, ' - 'OS_TOKEN or auth.token')) - if msgs: - raise exc.CommandError( - _('Missing parameter(s): \n%s') % '\n'.join(msgs)) - - -def build_auth_plugins_option_parser(parser): - """Auth plugins options builder - - Builds dynamically the list of options expected by each available - authentication plugin. - - """ - available_plugins = list(get_plugin_list()) - parser.add_argument( - '--os-auth-type', - metavar='<auth-type>', - dest='auth_type', - default=utils.env('OS_AUTH_TYPE'), - help=_('Select an authentication type. Available types: %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 - envs = { - 'OS_PROJECT_NAME': utils.env( - 'OS_PROJECT_NAME', - default=utils.env('OS_TENANT_NAME') - ), - 'OS_PROJECT_ID': utils.env( - 'OS_PROJECT_ID', - default=utils.env('OS_TENANT_ID') - ), - } - for o in get_options_list(): - # Remove tenant options from KSC plugins and replace them below - if 'tenant' not in o: - parser.add_argument( - '--os-' + o, - metavar='<auth-%s>' % o, - dest=o.replace('-', '_'), - default=envs.get( - OPTIONS_LIST[o]['env'], - utils.env(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... - parser.add_argument( - '--os-tenant-name', - metavar='<auth-tenant-name>', - dest='os_project_name', - help=argparse.SUPPRESS, - ) - parser.add_argument( - '--os-tenant-id', - metavar='<auth-tenant-id>', - dest='os_project_id', - help=argparse.SUPPRESS, - ) - return parser +sys.stderr.write( + "WARNING: %s is deprecated and will be removed after Jun 2017. " + "Please use osc_lib.api.auth\n" % __name__ +) diff --git a/openstackclient/api/auth_plugin.py b/openstackclient/api/auth_plugin.py index dc47a688..a106b1eb 100644 --- a/openstackclient/api/auth_plugin.py +++ b/openstackclient/api/auth_plugin.py @@ -13,15 +13,11 @@ """Authentication Plugin Library""" -import logging - from keystoneauth1 import loading from keystoneauth1 import token_endpoint from openstackclient.i18n import _ -LOG = logging.getLogger(__name__) - class TokenEndpoint(loading.BaseLoader): """Auth plugin to handle traditional token/endpoint usage diff --git a/openstackclient/common/availability_zone.py b/openstackclient/common/availability_zone.py index 89d77d15..63c55370 100644..100755 --- a/openstackclient/common/availability_zone.py +++ b/openstackclient/common/availability_zone.py @@ -122,11 +122,11 @@ class ListAvailabilityZone(command.Lister): compute_client = self.app.client_manager.compute try: data = compute_client.availability_zones.list() - except nova_exceptions.Forbidden as e: # policy doesn't allow + except nova_exceptions.Forbidden: # policy doesn't allow try: data = compute_client.availability_zones.list(detailed=False) except Exception: - raise e + raise # Argh, the availability zones are not iterable... result = [] diff --git a/openstackclient/common/clientmanager.py b/openstackclient/common/clientmanager.py index 3c35b529..ec005dc0 100644 --- a/openstackclient/common/clientmanager.py +++ b/openstackclient/common/clientmanager.py @@ -20,12 +20,13 @@ import logging import pkg_resources import sys +from keystoneauth1.loading import base +from osc_lib.api import auth from osc_lib import exceptions from oslo_utils import strutils import requests import six -from openstackclient.api import auth from openstackclient.common import session as osc_session from openstackclient.identity import client as identity_client @@ -37,6 +38,77 @@ PLUGIN_MODULES = [] USER_AGENT = 'python-openstackclient' +# NOTE(dtroyer): Bringing back select_auth_plugin() and build_auth_params() +# temporarily because osc-lib 0.3.0 removed it a wee bit early +def select_auth_plugin(options): + """Pick an auth plugin based on --os-auth-type or other options""" + + auth_plugin_name = None + + # Do the token/url check first as this must override the default + # 'password' set by os-client-config + # Also, url and token are not copied into o-c-c's auth dict (yet?) + if options.auth.get('url') and options.auth.get('token'): + # service token authentication + auth_plugin_name = 'token_endpoint' + elif options.auth_type in auth.PLUGIN_LIST: + # A direct plugin name was given, use it + auth_plugin_name = options.auth_type + elif options.auth.get('username'): + if options.identity_api_version == '3': + auth_plugin_name = 'v3password' + elif options.identity_api_version.startswith('2'): + auth_plugin_name = 'v2password' + else: + # let keystoneauth figure it out itself + auth_plugin_name = 'password' + elif options.auth.get('token'): + if options.identity_api_version == '3': + auth_plugin_name = 'v3token' + elif options.identity_api_version.startswith('2'): + auth_plugin_name = 'v2token' + else: + # let keystoneauth figure it out itself + auth_plugin_name = 'token' + else: + # The ultimate default is similar to the original behaviour, + # but this time with version discovery + auth_plugin_name = 'password' + LOG.debug("Auth plugin %s selected", auth_plugin_name) + return auth_plugin_name + + +def build_auth_params(auth_plugin_name, cmd_options): + if auth_plugin_name: + LOG.debug('auth_type: %s', auth_plugin_name) + auth_plugin_loader = base.get_plugin_loader(auth_plugin_name) + auth_params = { + opt.dest: opt.default + for opt in base.get_plugin_options(auth_plugin_name) + } + auth_params.update(dict(cmd_options.auth)) + # grab tenant from project for v2.0 API compatibility + if auth_plugin_name.startswith("v2"): + if 'project_id' in auth_params: + auth_params['tenant_id'] = auth_params['project_id'] + del auth_params['project_id'] + if 'project_name' in auth_params: + auth_params['tenant_name'] = auth_params['project_name'] + del auth_params['project_name'] + else: + LOG.debug('no auth_type') + # delay the plugin choice, grab every option + auth_plugin_loader = None + auth_params = dict(cmd_options.auth) + plugin_options = set( + [o.replace('-', '_') for o in auth.get_options_list()] + ) + for option in plugin_options: + LOG.debug('fetching option %s', option) + auth_params[option] = getattr(cmd_options.auth, option, None) + return (auth_plugin_loader, auth_params) + + class ClientCache(object): """Descriptor class for caching created client handles.""" @@ -193,7 +265,7 @@ class ClientManager(object): # If no auth type is named by the user, select one based on # the supplied options - self.auth_plugin_name = auth.select_auth_plugin(self._cli_options) + self.auth_plugin_name = select_auth_plugin(self._cli_options) # Basic option checking to avoid unhelpful error messages auth.check_valid_authentication_options(self._cli_options, @@ -205,7 +277,7 @@ class ClientManager(object): not self._cli_options.auth.get('password')): self._cli_options.auth['password'] = self._pw_callback() - (auth_plugin, self._auth_params) = auth.build_auth_params( + (auth_plugin, self._auth_params) = build_auth_params( self.auth_plugin_name, self._cli_options, ) diff --git a/openstackclient/common/quota.py b/openstackclient/common/quota.py index 69415f0d..3c12c366 100644..100755 --- a/openstackclient/common/quota.py +++ b/openstackclient/common/quota.py @@ -231,7 +231,7 @@ class ShowQuota(command.ShowOne): if type(e).__name__ == 'EndpointNotFound': return {} else: - raise e + raise return quota._info def get_network_quota(self, parsed_args): diff --git a/openstackclient/compute/v2/agent.py b/openstackclient/compute/v2/agent.py index 4d923955..76c1b3b7 100644 --- a/openstackclient/compute/v2/agent.py +++ b/openstackclient/compute/v2/agent.py @@ -152,28 +152,48 @@ class SetAgent(command.Command): help=_("ID of the agent") ) parser.add_argument( - "version", + "--agent-version", + dest="version", metavar="<version>", help=_("Version of the agent") ) parser.add_argument( - "url", + "--url", metavar="<url>", - help=_("URL") + help=_("URL of the agent") ) parser.add_argument( - "md5hash", + "--md5hash", metavar="<md5hash>", - help=_("MD5 hash") + help=_("MD5 hash of the agent") ) return parser def take_action(self, parsed_args): compute_client = self.app.client_manager.compute + data = compute_client.agents.list(hypervisor=None) + agent = {} + + for s in data: + if s.agent_id == int(parsed_args.id): + agent['version'] = s.version + agent['url'] = s.url + agent['md5hash'] = s.md5hash + if agent == {}: + msg = _("No agent with a ID of '%(id)s' exists.") + raise exceptions.CommandError(msg % parsed_args.id) + + if parsed_args.version: + agent['version'] = parsed_args.version + if parsed_args.url: + agent['url'] = parsed_args.url + if parsed_args.md5hash: + agent['md5hash'] = parsed_args.md5hash + args = ( parsed_args.id, - parsed_args.version, - parsed_args.url, - parsed_args.md5hash + agent['version'], + agent['url'], + agent['md5hash'], ) compute_client.agents.update(*args) diff --git a/openstackclient/compute/v2/console.py b/openstackclient/compute/v2/console.py index 4179fa5c..74bed441 100644 --- a/openstackclient/compute/v2/console.py +++ b/openstackclient/compute/v2/console.py @@ -87,7 +87,7 @@ class ShowConsoleURL(command.ShowOne): dest='url_type', action='store_const', const='xvpvnc', - help=_("Show xpvnc console URL") + help=_("Show xvpvnc console URL") ) type_group.add_argument( '--spice', diff --git a/openstackclient/compute/v2/fixedip.py b/openstackclient/compute/v2/fixedip.py index 8bd72ca3..c14d29fa 100644 --- a/openstackclient/compute/v2/fixedip.py +++ b/openstackclient/compute/v2/fixedip.py @@ -15,28 +15,43 @@ """Fixed IP action implementations""" +import logging + from osc_lib.command import command from osc_lib import utils +from openstackclient.i18n import _ + class AddFixedIP(command.Command): """Add fixed IP address to server""" + # TODO(tangchen): Remove this class and ``ip fixed add`` command + # two cycles after Mitaka. + + # This notifies cliff to not display the help for this command + deprecated = True + + log = logging.getLogger('deprecated') + def get_parser(self, prog_name): parser = super(AddFixedIP, self).get_parser(prog_name) parser.add_argument( "network", metavar="<network>", - help="Network to fetch an IP address from (name or ID)", + help=_("Network to fetch an IP address from (name or ID)"), ) parser.add_argument( "server", metavar="<server>", - help="Server to receive the IP address (name or ID)", + help=_("Server to receive the IP address (name or ID)"), ) return parser def take_action(self, parsed_args): + self.log.warning(_('This command has been deprecated. ' + 'Please use "server add fixed ip" instead.')) + compute_client = self.app.client_manager.compute network = utils.find_resource( @@ -51,21 +66,32 @@ class AddFixedIP(command.Command): class RemoveFixedIP(command.Command): """Remove fixed IP address from server""" + # TODO(tangchen): Remove this class and ``ip fixed remove`` command + # two cycles after Mitaka. + + # This notifies cliff to not display the help for this command + deprecated = True + + log = logging.getLogger('deprecated') + def get_parser(self, prog_name): parser = super(RemoveFixedIP, self).get_parser(prog_name) parser.add_argument( "ip_address", metavar="<ip-address>", - help="IP address to remove from server (name only)", + help=_("IP address to remove from server (name only)"), ) parser.add_argument( "server", metavar="<server>", - help="Server to remove the IP address from (name or ID)", + help=_("Server to remove the IP address from (name or ID)"), ) return parser def take_action(self, parsed_args): + self.log.warning(_('This command has been deprecated. ' + 'Please use "server remove fixed ip" instead.')) + compute_client = self.app.client_manager.compute server = utils.find_resource( diff --git a/openstackclient/compute/v2/flavor.py b/openstackclient/compute/v2/flavor.py index 01d7da75..000df598 100644 --- a/openstackclient/compute/v2/flavor.py +++ b/openstackclient/compute/v2/flavor.py @@ -122,6 +122,13 @@ class CreateFlavor(command.ShowOne): help=_("Flavor is not available to other projects") ) parser.add_argument( + "--property", + metavar="<key=value>", + action=parseractions.KeyValueAction, + help=_("Property to add for this flavor " + "(repeat option to set multiple properties)") + ) + parser.add_argument( '--project', metavar='<project>', help=_("Allow <project> to access private flavor (name or ID) " @@ -150,8 +157,7 @@ class CreateFlavor(command.ShowOne): parsed_args.public ) - flavor = compute_client.flavors.create(*args)._info.copy() - flavor.pop("links") + flavor = compute_client.flavors.create(*args) if parsed_args.project: try: @@ -166,8 +172,17 @@ class CreateFlavor(command.ShowOne): msg = _("Failed to add project %(project)s access to " "flavor: %(e)s") LOG.error(msg % {'project': parsed_args.project, 'e': e}) + if parsed_args.property: + try: + flavor.set_keys(parsed_args.property) + except Exception as e: + LOG.error(_("Failed to set flavor property: %s"), e) - return zip(*sorted(six.iteritems(flavor))) + flavor_info = flavor._info.copy() + flavor_info.pop("links") + flavor_info['properties'] = utils.format_dict(flavor.get_keys()) + + return zip(*sorted(six.iteritems(flavor_info))) class DeleteFlavor(command.Command): diff --git a/openstackclient/compute/v2/floatingip.py b/openstackclient/compute/v2/floatingip.py index 98079fbc..8398ea57 100644 --- a/openstackclient/compute/v2/floatingip.py +++ b/openstackclient/compute/v2/floatingip.py @@ -15,28 +15,43 @@ """Floating IP action implementations""" +import logging + from osc_lib.command import command from osc_lib import utils +from openstackclient.i18n import _ + class AddFloatingIP(command.Command): """Add floating IP address to server""" + # TODO(tangchen): Remove this class and ``ip floating add`` command + # two cycles after Mitaka. + + # This notifies cliff to not display the help for this command + deprecated = True + + log = logging.getLogger('deprecated') + def get_parser(self, prog_name): parser = super(AddFloatingIP, self).get_parser(prog_name) parser.add_argument( "ip_address", metavar="<ip-address>", - help="IP address to add to server (name only)", + help=_("IP address to add to server (name only)"), ) parser.add_argument( "server", metavar="<server>", - help="Server to receive the IP address (name or ID)", + help=_("Server to receive the IP address (name or ID)"), ) return parser def take_action(self, parsed_args): + self.log.warning(_('This command has been deprecated. ' + 'Please use "server add floating ip" instead.')) + compute_client = self.app.client_manager.compute server = utils.find_resource( @@ -48,21 +63,32 @@ class AddFloatingIP(command.Command): class RemoveFloatingIP(command.Command): """Remove floating IP address from server""" + # TODO(tangchen): Remove this class and ``ip floating remove`` command + # two cycles after Mitaka. + + # This notifies cliff to not display the help for this command + deprecated = True + + log = logging.getLogger('deprecated') + def get_parser(self, prog_name): parser = super(RemoveFloatingIP, self).get_parser(prog_name) parser.add_argument( "ip_address", metavar="<ip-address>", - help="IP address to remove from server (name only)", + help=_("IP address to remove from server (name only)"), ) parser.add_argument( "server", metavar="<server>", - help="Server to remove the IP address from (name or ID)", + help=_("Server to remove the IP address from (name or ID)"), ) return parser def take_action(self, parsed_args): + self.log.warning(_('This command has been deprecated. ' + 'Please use "server remove floating ip" instead.')) + compute_client = self.app.client_manager.compute server = utils.find_resource( diff --git a/openstackclient/compute/v2/floatingippool.py b/openstackclient/compute/v2/floatingippool.py deleted file mode 100644 index 0d46e32b..00000000 --- a/openstackclient/compute/v2/floatingippool.py +++ /dev/null @@ -1,36 +0,0 @@ -# Copyright 2013 OpenStack Foundation -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# 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. -# - -"""Floating IP Pool action implementations""" - -from osc_lib.command import command -from osc_lib import utils - - -class ListFloatingIPPool(command.Lister): - """List pools of floating IP addresses""" - - def take_action(self, parsed_args): - compute_client = self.app.client_manager.compute - - columns = ('Name',) - - data = compute_client.floating_ip_pools.list() - - return (columns, - (utils.get_item_properties( - s, columns, - formatters={}, - ) for s in data)) diff --git a/openstackclient/compute/v2/keypair.py b/openstackclient/compute/v2/keypair.py index 3725a3a8..d30fd429 100644 --- a/openstackclient/compute/v2/keypair.py +++ b/openstackclient/compute/v2/keypair.py @@ -146,7 +146,7 @@ class ShowKeypair(command.ShowOne): '--public-key', action='store_true', default=False, - help=_("Show only bare public key") + help=_("Show only bare public key (name only)") ) return parser diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 7e4b0dc1..a317c11d 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -174,6 +174,63 @@ def _show_progress(progress): sys.stdout.flush() +class AddFixedIP(command.Command): + """Add fixed IP address to server""" + + def get_parser(self, prog_name): + parser = super(AddFixedIP, self).get_parser(prog_name) + parser.add_argument( + "server", + metavar="<server>", + help=_("Server (name or ID) to receive the fixed IP address"), + ) + parser.add_argument( + "network", + metavar="<network>", + help=_("Network (name or ID) to allocate " + "the fixed IP address from"), + ) + 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) + + network = utils.find_resource( + compute_client.networks, parsed_args.network) + + server.add_fixed_ip(network.id) + + +class AddFloatingIP(command.Command): + """Add floating IP address to server""" + + def get_parser(self, prog_name): + parser = super(AddFloatingIP, self).get_parser(prog_name) + parser.add_argument( + "server", + metavar="<server>", + help=_("Server (name or ID) to receive the floating IP address"), + ) + parser.add_argument( + "ip_address", + metavar="<ip-address>", + help=_("Floating IP address (IP address only) to assign " + "to server"), + ) + 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) + + server.add_floating_ip(parsed_args.ip_address) + + class AddServerSecurityGroup(command.Command): """Add security group to server""" @@ -1081,6 +1138,61 @@ class RebuildServer(command.ShowOne): return zip(*sorted(six.iteritems(details))) +class RemoveFixedIP(command.Command): + """Remove fixed IP address from server""" + + def get_parser(self, prog_name): + parser = super(RemoveFixedIP, self).get_parser(prog_name) + parser.add_argument( + "server", + metavar="<server>", + help=_("Server (name or ID) to remove the fixed IP address from"), + ) + parser.add_argument( + "ip_address", + metavar="<ip-address>", + help=_("Fixed IP address (IP address only) to remove from the " + "server"), + ) + 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) + + server.remove_fixed_ip(parsed_args.ip_address) + + +class RemoveFloatingIP(command.Command): + """Remove floating IP address from server""" + + def get_parser(self, prog_name): + parser = super(RemoveFloatingIP, self).get_parser(prog_name) + parser.add_argument( + "server", + metavar="<server>", + help=_("Server (name or ID) to remove the " + "floating IP address from"), + ) + parser.add_argument( + "ip_address", + metavar="<ip-address>", + help=_("Floating IP address (IP address only) " + "to remove from server"), + ) + 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) + + server.remove_floating_ip(parsed_args.ip_address) + + class RemoveServerSecurityGroup(command.Command): """Remove security group from server""" @@ -1110,7 +1222,7 @@ class RemoveServerSecurityGroup(command.Command): parsed_args.group, ) - server.remove_security_group(security_group) + server.remove_security_group(security_group.id) class RemoveServerVolume(command.Command): diff --git a/openstackclient/identity/client.py b/openstackclient/identity/client.py index be7b643f..d932b970 100644 --- a/openstackclient/identity/client.py +++ b/openstackclient/identity/client.py @@ -16,9 +16,9 @@ import logging from keystoneclient.v2_0 import client as identity_client_v2 +from osc_lib.api import auth from osc_lib import utils -from openstackclient.api import auth from openstackclient.i18n import _ LOG = logging.getLogger(__name__) diff --git a/openstackclient/identity/v2_0/ec2creds.py b/openstackclient/identity/v2_0/ec2creds.py index 058b5772..4873cd55 100644 --- a/openstackclient/identity/v2_0/ec2creds.py +++ b/openstackclient/identity/v2_0/ec2creds.py @@ -94,7 +94,7 @@ class DeleteEC2Creds(command.Command): 'access_keys', metavar='<access-key>', nargs='+', - help=_('Credentials access keys'), + help=_('Credentials access key(s)'), ) parser.add_argument( '--user', @@ -121,7 +121,7 @@ class DeleteEC2Creds(command.Command): identity_client.ec2.delete(user, access_key) except Exception as e: result += 1 - LOG.error(_("Failed to delete EC2 keys with " + LOG.error(_("Failed to delete EC2 credentials with " "access key '%(access_key)s': %(e)s") % {'access_key': access_key, 'e': e}) diff --git a/openstackclient/identity/v2_0/project.py b/openstackclient/identity/v2_0/project.py index 6c5db13c..fc5c9201 100644..100755 --- a/openstackclient/identity/v2_0/project.py +++ b/openstackclient/identity/v2_0/project.py @@ -86,7 +86,7 @@ class CreateProject(command.ShowOne): enabled=enabled, **kwargs ) - except ks_exc.Conflict as e: + except ks_exc.Conflict: if parsed_args.or_show: project = utils.find_resource( identity_client.tenants, @@ -94,7 +94,7 @@ class CreateProject(command.ShowOne): ) LOG.info(_('Returning existing project %s'), project.name) else: - raise e + raise # TODO(stevemar): Remove the line below when we support multitenancy project._info.pop('parent_id', None) @@ -242,7 +242,7 @@ class ShowProject(command.ShowOne): parsed_args.project, ) info.update(project._info) - except ks_exc.Forbidden as e: + except ks_exc.Forbidden: auth_ref = self.app.client_manager.auth_ref if ( parsed_args.project == auth_ref.project_id or @@ -256,7 +256,7 @@ class ShowProject(command.ShowOne): 'enabled': True, } else: - raise e + raise # TODO(stevemar): Remove the line below when we support multitenancy info.pop('parent_id', None) diff --git a/openstackclient/identity/v2_0/role.py b/openstackclient/identity/v2_0/role.py index 6d06230c..191cdaa3 100644..100755 --- a/openstackclient/identity/v2_0/role.py +++ b/openstackclient/identity/v2_0/role.py @@ -93,7 +93,7 @@ class CreateRole(command.ShowOne): identity_client = self.app.client_manager.identity try: role = identity_client.roles.create(parsed_args.role_name) - except ks_exc.Conflict as e: + except ks_exc.Conflict: if parsed_args.or_show: role = utils.find_resource( identity_client.roles, @@ -101,7 +101,7 @@ class CreateRole(command.ShowOne): ) LOG.info(_('Returning existing role %s'), role.name) else: - raise e + raise info = {} info.update(role._info) diff --git a/openstackclient/identity/v2_0/user.py b/openstackclient/identity/v2_0/user.py index 0f327830..d2075150 100644..100755 --- a/openstackclient/identity/v2_0/user.py +++ b/openstackclient/identity/v2_0/user.py @@ -102,7 +102,7 @@ class CreateUser(command.ShowOne): tenant_id=project_id, enabled=enabled, ) - except ks_exc.Conflict as e: + except ks_exc.Conflict: if parsed_args.or_show: user = utils.find_resource( identity_client.users, @@ -110,7 +110,7 @@ class CreateUser(command.ShowOne): ) LOG.info(_('Returning existing user %s'), user.name) else: - raise e + raise # NOTE(dtroyer): The users.create() method wants 'tenant_id' but # the returned resource has 'tenantId'. Sigh. @@ -349,7 +349,7 @@ class ShowUser(command.ShowOne): parsed_args.user, ) info.update(user._info) - except ks_exc.Forbidden as e: + except ks_exc.Forbidden: auth_ref = self.app.client_manager.auth_ref if ( parsed_args.user == auth_ref.user_id or @@ -364,7 +364,7 @@ class ShowUser(command.ShowOne): 'enabled': True, } else: - raise e + raise if 'tenantId' in info: info.update( diff --git a/openstackclient/identity/v3/consumer.py b/openstackclient/identity/v3/consumer.py index a4620bf9..b41a37ca 100644 --- a/openstackclient/identity/v3/consumer.py +++ b/openstackclient/identity/v3/consumer.py @@ -15,15 +15,19 @@ """Identity v3 Consumer action implementations""" -import sys +import logging from osc_lib.command import command +from osc_lib import exceptions from osc_lib import utils import six from openstackclient.i18n import _ +LOG = logging.getLogger(__name__) + + class CreateConsumer(command.ShowOne): """Create new consumer""" @@ -46,22 +50,37 @@ class CreateConsumer(command.ShowOne): class DeleteConsumer(command.Command): - """Delete consumer""" + """Delete consumer(s)""" def get_parser(self, prog_name): parser = super(DeleteConsumer, self).get_parser(prog_name) parser.add_argument( 'consumer', metavar='<consumer>', - help=_('Consumer to delete'), + nargs='+', + help=_('Consumer(s) to delete'), ) return parser def take_action(self, parsed_args): identity_client = self.app.client_manager.identity - consumer = utils.find_resource( - identity_client.oauth1.consumers, parsed_args.consumer) - identity_client.oauth1.consumers.delete(consumer.id) + result = 0 + for i in parsed_args.consumer: + try: + consumer = utils.find_resource( + identity_client.oauth1.consumers, i) + identity_client.oauth1.consumers.delete(consumer.id) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete consumer with name or " + "ID '%(consumer)s': %(e)s") + % {'consumer': i, 'e': e}) + + if result > 0: + total = len(parsed_args.consumer) + msg = (_("%(result)s of %(total)s consumers failed " + "to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) class ListConsumer(command.Lister): @@ -102,10 +121,6 @@ class SetConsumer(command.Command): if parsed_args.description: kwargs['description'] = parsed_args.description - if not len(kwargs): - sys.stdout.write(_('Consumer not updated, no arguments present\n')) - return - consumer = identity_client.oauth1.consumers.update( consumer.id, **kwargs) diff --git a/openstackclient/identity/v3/credential.py b/openstackclient/identity/v3/credential.py index eeeddfa5..0ef94cf4 100644 --- a/openstackclient/identity/v3/credential.py +++ b/openstackclient/identity/v3/credential.py @@ -15,22 +15,28 @@ """Identity v3 Credential action implementations""" +import logging + from osc_lib.command import command +from osc_lib import exceptions from osc_lib import utils import six from openstackclient.i18n import _ +LOG = logging.getLogger(__name__) + + class CreateCredential(command.ShowOne): - """Create credential command""" + """Create new credential""" def get_parser(self, prog_name): parser = super(CreateCredential, self).get_parser(prog_name) parser.add_argument( 'user', metavar='<user>', - help=_('Name or ID of user that owns the credential'), + help=_('user that owns the credential (name or ID)'), ) parser.add_argument( '--type', @@ -47,8 +53,8 @@ class CreateCredential(command.ShowOne): parser.add_argument( '--project', metavar='<project>', - help=_('Project name or ID which limits the ' - 'scope of the credential'), + help=_('Project which limits the scope of ' + 'the credential (name or ID)'), ) return parser @@ -72,24 +78,39 @@ class CreateCredential(command.ShowOne): class DeleteCredential(command.Command): - """Delete credential command""" + """Delete credential(s)""" def get_parser(self, prog_name): parser = super(DeleteCredential, self).get_parser(prog_name) parser.add_argument( 'credential', metavar='<credential-id>', - help=_('ID of credential to delete'), + nargs='+', + help=_('ID of credential(s) to delete'), ) return parser def take_action(self, parsed_args): identity_client = self.app.client_manager.identity - identity_client.credentials.delete(parsed_args.credential) + result = 0 + for i in parsed_args.credential: + try: + identity_client.credentials.delete(i) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete credentials with " + "ID '%(credential)s': %(e)s") + % {'credential': i, 'e': e}) + + if result > 0: + total = len(parsed_args.credential) + msg = (_("%(result)s of %(total)s credential failed " + "to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) class ListCredential(command.Lister): - """List credential command""" + """List credentials""" def take_action(self, parsed_args): columns = ('ID', 'Type', 'User ID', 'Blob', 'Project ID') @@ -103,7 +124,7 @@ class ListCredential(command.Lister): class SetCredential(command.Command): - """Set credential command""" + """Set credential properties""" def get_parser(self, prog_name): parser = super(SetCredential, self).get_parser(prog_name) @@ -116,7 +137,7 @@ class SetCredential(command.Command): '--user', metavar='<user>', required=True, - help=_('Name or ID of user that owns the credential'), + help=_('User that owns the credential (name or ID)'), ) parser.add_argument( '--type', @@ -134,8 +155,8 @@ class SetCredential(command.Command): parser.add_argument( '--project', metavar='<project>', - help=_('Project name or ID which limits the ' - 'scope of the credential'), + help=_('Project which limits the scope of ' + 'the credential (name or ID)'), ) return parser @@ -159,7 +180,7 @@ class SetCredential(command.Command): class ShowCredential(command.ShowOne): - """Show credential command""" + """Display credential details""" def get_parser(self, prog_name): parser = super(ShowCredential, self).get_parser(prog_name) diff --git a/openstackclient/identity/v3/domain.py b/openstackclient/identity/v3/domain.py index 8ba76c41..76e47d32 100644..100755 --- a/openstackclient/identity/v3/domain.py +++ b/openstackclient/identity/v3/domain.py @@ -16,10 +16,10 @@ """Identity v3 Domain action implementations""" import logging -import sys from keystoneauth1 import exceptions as ks_exc from osc_lib.command import command +from osc_lib import exceptions from osc_lib import utils import six @@ -76,35 +76,49 @@ class CreateDomain(command.ShowOne): description=parsed_args.description, enabled=enabled, ) - except ks_exc.Conflict as e: + except ks_exc.Conflict: if parsed_args.or_show: domain = utils.find_resource(identity_client.domains, parsed_args.name) LOG.info(_('Returning existing domain %s'), domain.name) else: - raise e + raise domain._info.pop('links') return zip(*sorted(six.iteritems(domain._info))) class DeleteDomain(command.Command): - """Delete domain""" + """Delete domain(s)""" def get_parser(self, prog_name): parser = super(DeleteDomain, self).get_parser(prog_name) parser.add_argument( 'domain', metavar='<domain>', - help=_('Domain to delete (name or ID)'), + nargs='+', + help=_('Domain(s) to delete (name or ID)'), ) return parser def take_action(self, parsed_args): identity_client = self.app.client_manager.identity - domain = utils.find_resource(identity_client.domains, - parsed_args.domain) - identity_client.domains.delete(domain.id) + result = 0 + for i in parsed_args.domain: + try: + domain = utils.find_resource(identity_client.domains, i) + identity_client.domains.delete(domain.id) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete domain with name or " + "ID '%(domain)s': %(e)s") + % {'domain': i, 'e': e}) + + if result > 0: + total = len(parsed_args.domain) + msg = (_("%(result)s of %(total)s domains failed " + "to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) class ListDomain(command.Lister): @@ -168,9 +182,6 @@ class SetDomain(command.Command): if parsed_args.disable: kwargs['enabled'] = False - if not kwargs: - sys.stdout.write(_("Domain not updated, no arguments present\n")) - return identity_client.domains.update(domain.id, **kwargs) diff --git a/openstackclient/identity/v3/ec2creds.py b/openstackclient/identity/v3/ec2creds.py index 835fe96f..7ad01719 100644 --- a/openstackclient/identity/v3/ec2creds.py +++ b/openstackclient/identity/v3/ec2creds.py @@ -12,7 +12,10 @@ """Identity v3 EC2 Credentials action implementations""" +import logging + from osc_lib.command import command +from osc_lib import exceptions from osc_lib import utils import six @@ -20,6 +23,9 @@ from openstackclient.i18n import _ from openstackclient.identity import common +LOG = logging.getLogger(__name__) + + def _determine_ec2_user(parsed_args, client_manager): """Determine a user several different ways. @@ -113,7 +119,8 @@ class DeleteEC2Creds(command.Command): parser.add_argument( 'access_key', metavar='<access-key>', - help=_('Credentials access key'), + nargs='+', + help=_('Credentials access key(s)'), ) parser.add_argument( '--user', @@ -126,7 +133,21 @@ class DeleteEC2Creds(command.Command): def take_action(self, parsed_args): client_manager = self.app.client_manager user = _determine_ec2_user(parsed_args, client_manager) - client_manager.identity.ec2.delete(user, parsed_args.access_key) + result = 0 + for i in parsed_args.access_key: + try: + client_manager.identity.ec2.delete(user, i) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete EC2 credentials with " + "access key '%(access_key)s': %(e)s") + % {'access_key': i, 'e': e}) + + if result > 0: + total = len(parsed_args.access_key) + msg = (_("%(result)s of %(total)s EC2 keys failed " + "to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) class ListEC2Creds(command.Lister): diff --git a/openstackclient/identity/v3/endpoint.py b/openstackclient/identity/v3/endpoint.py index 2f1cc9f3..73b37a43 100644 --- a/openstackclient/identity/v3/endpoint.py +++ b/openstackclient/identity/v3/endpoint.py @@ -15,9 +15,10 @@ """Identity v3 Endpoint action implementations""" -import sys +import logging from osc_lib.command import command +from osc_lib import exceptions from osc_lib import utils import six @@ -25,6 +26,9 @@ from openstackclient.i18n import _ from openstackclient.identity import common +LOG = logging.getLogger(__name__) + + def get_service_name(service): if hasattr(service, 'name'): return service.name @@ -95,22 +99,37 @@ class CreateEndpoint(command.ShowOne): class DeleteEndpoint(command.Command): - """Delete endpoint""" + """Delete endpoint(s)""" def get_parser(self, prog_name): parser = super(DeleteEndpoint, self).get_parser(prog_name) parser.add_argument( 'endpoint', metavar='<endpoint-id>', - help=_('Endpoint to delete (ID only)'), + nargs='+', + help=_('Endpoint(s) to delete (ID only)'), ) return parser def take_action(self, parsed_args): identity_client = self.app.client_manager.identity - endpoint_id = utils.find_resource(identity_client.endpoints, - parsed_args.endpoint).id - identity_client.endpoints.delete(endpoint_id) + result = 0 + for i in parsed_args.endpoint: + try: + endpoint_id = utils.find_resource( + identity_client.endpoints, i).id + identity_client.endpoints.delete(endpoint_id) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete endpoint with " + "ID '%(endpoint)s': %(e)s") + % {'endpoint': i, 'e': e}) + + if result > 0: + total = len(parsed_args.endpoint) + msg = (_("%(result)s of %(total)s endpoints failed " + "to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) class ListEndpoint(command.Lister): @@ -212,12 +231,6 @@ class SetEndpoint(command.Command): endpoint = utils.find_resource(identity_client.endpoints, parsed_args.endpoint) - if (not parsed_args.interface and not parsed_args.url - and not parsed_args.service and not parsed_args.region - and not parsed_args.enabled and not parsed_args.disabled): - sys.stdout.write(_("Endpoint not updated, no arguments present\n")) - return - service_id = None if parsed_args.service: service = common.find_service(identity_client, parsed_args.service) diff --git a/openstackclient/identity/v3/federation_protocol.py b/openstackclient/identity/v3/federation_protocol.py index 09480245..3fde9027 100644 --- a/openstackclient/identity/v3/federation_protocol.py +++ b/openstackclient/identity/v3/federation_protocol.py @@ -17,6 +17,7 @@ import logging from osc_lib.command import command +from osc_lib import exceptions from osc_lib import utils import six @@ -71,14 +72,15 @@ class CreateProtocol(command.ShowOne): class DeleteProtocol(command.Command): - """Delete federation protocol""" + """Delete federation protocol(s)""" def get_parser(self, prog_name): parser = super(DeleteProtocol, self).get_parser(prog_name) parser.add_argument( 'federation_protocol', metavar='<federation-protocol>', - help=_('Federation protocol to delete (name or ID)'), + nargs='+', + help=_('Federation protocol(s) to delete (name or ID)'), ) parser.add_argument( '--identity-provider', @@ -92,8 +94,22 @@ class DeleteProtocol(command.Command): def take_action(self, parsed_args): identity_client = self.app.client_manager.identity - identity_client.federation.protocols.delete( - parsed_args.identity_provider, parsed_args.federation_protocol) + result = 0 + for i in parsed_args.federation_protocol: + try: + identity_client.federation.protocols.delete( + parsed_args.identity_provider, i) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete federation protocol " + "with name or ID '%(protocol)s': %(e)s") + % {'protocol': i, 'e': e}) + + if result > 0: + total = len(parsed_args.federation_protocol) + msg = (_("%(result)s of %(total)s federation protocols failed" + " to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) class ListProtocols(command.Lister): @@ -149,10 +165,6 @@ class SetProtocol(command.Command): def take_action(self, parsed_args): identity_client = self.app.client_manager.identity - if not parsed_args.mapping: - LOG.error(_("No changes requested")) - return - protocol = identity_client.federation.protocols.update( parsed_args.identity_provider, parsed_args.federation_protocol, parsed_args.mapping) diff --git a/openstackclient/identity/v3/group.py b/openstackclient/identity/v3/group.py index 8351fe64..f780810a 100644..100755 --- a/openstackclient/identity/v3/group.py +++ b/openstackclient/identity/v3/group.py @@ -160,14 +160,14 @@ class CreateGroup(command.ShowOne): name=parsed_args.name, domain=domain, description=parsed_args.description) - except ks_exc.Conflict as e: + except ks_exc.Conflict: if parsed_args.or_show: group = utils.find_resource(identity_client.groups, parsed_args.name, domain_id=domain) LOG.info(_('Returning existing group %s'), group.name) else: - raise e + raise group._info.pop('links') return zip(*sorted(six.iteritems(group._info))) @@ -343,9 +343,6 @@ class SetGroup(command.Command): if parsed_args.description: kwargs['description'] = parsed_args.description - if not len(kwargs): - sys.stderr.write("Group not updated, no arguments present\n") - return identity_client.groups.update(group.id, **kwargs) diff --git a/openstackclient/identity/v3/identity_provider.py b/openstackclient/identity/v3/identity_provider.py index 5c638f9b..0453e888 100644 --- a/openstackclient/identity/v3/identity_provider.py +++ b/openstackclient/identity/v3/identity_provider.py @@ -16,6 +16,7 @@ import logging from osc_lib.command import command +from osc_lib import exceptions from osc_lib import utils import six @@ -93,21 +94,35 @@ class CreateIdentityProvider(command.ShowOne): class DeleteIdentityProvider(command.Command): - """Delete identity provider""" + """Delete identity provider(s)""" def get_parser(self, prog_name): parser = super(DeleteIdentityProvider, self).get_parser(prog_name) parser.add_argument( 'identity_provider', metavar='<identity-provider>', - help=_('Identity provider to delete'), + nargs='+', + help=_('Identity provider(s) to delete'), ) return parser def take_action(self, parsed_args): identity_client = self.app.client_manager.identity - identity_client.federation.identity_providers.delete( - parsed_args.identity_provider) + result = 0 + for i in parsed_args.identity_provider: + try: + identity_client.federation.identity_providers.delete(i) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete identity providers with " + "name or ID '%(provider)s': %(e)s") + % {'provider': i, 'e': e}) + + if result > 0: + total = len(parsed_args.identity_provider) + msg = (_("%(result)s of %(total)s identity providers failed" + " to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) class ListIdentityProvider(command.Lister): @@ -169,14 +184,6 @@ class SetIdentityProvider(command.Command): def take_action(self, parsed_args): federation_client = self.app.client_manager.identity.federation - # Basic argument checking - if (not parsed_args.enable and not parsed_args.disable and - not parsed_args.remote_id and - not parsed_args.remote_id_file and - not parsed_args.description): - LOG.error(_('No changes requested')) - return (None, None) - # Always set remote_ids if either is passed in if parsed_args.remote_id_file: file_content = utils.read_blob_file_contents( diff --git a/openstackclient/identity/v3/mapping.py b/openstackclient/identity/v3/mapping.py index 74ead228..09181a0b 100644 --- a/openstackclient/identity/v3/mapping.py +++ b/openstackclient/identity/v3/mapping.py @@ -111,21 +111,35 @@ class CreateMapping(command.ShowOne, _RulesReader): class DeleteMapping(command.Command): - """Delete mapping""" + """Delete mapping(s)""" def get_parser(self, prog_name): parser = super(DeleteMapping, self).get_parser(prog_name) parser.add_argument( 'mapping', metavar='<mapping>', - help=_('Mapping to delete'), + nargs='+', + help=_('Mapping(s) to delete'), ) return parser def take_action(self, parsed_args): identity_client = self.app.client_manager.identity - - identity_client.federation.mappings.delete(parsed_args.mapping) + result = 0 + for i in parsed_args.mapping: + try: + identity_client.federation.mappings.delete(i) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete mapping with name or " + "ID '%(mapping)s': %(e)s") + % {'mapping': i, 'e': e}) + + if result > 0: + total = len(parsed_args.mapping) + msg = (_("%(result)s of %(total)s mappings failed " + "to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) class ListMapping(command.Lister): @@ -162,10 +176,6 @@ class SetMapping(command.Command, _RulesReader): def take_action(self, parsed_args): identity_client = self.app.client_manager.identity - if not parsed_args.rules: - LOG.error(_("No changes requested")) - return - rules = self._read_rules(parsed_args.rules) mapping = identity_client.federation.mappings.update( diff --git a/openstackclient/identity/v3/policy.py b/openstackclient/identity/v3/policy.py index 68fb2738..596eae01 100644 --- a/openstackclient/identity/v3/policy.py +++ b/openstackclient/identity/v3/policy.py @@ -15,15 +15,19 @@ """Identity v3 Policy action implementations""" -import sys +import logging from osc_lib.command import command +from osc_lib import exceptions from osc_lib import utils import six from openstackclient.i18n import _ +LOG = logging.getLogger(__name__) + + class CreatePolicy(command.ShowOne): """Create new policy""" @@ -57,20 +61,35 @@ class CreatePolicy(command.ShowOne): class DeletePolicy(command.Command): - """Delete policy""" + """Delete policy(s)""" def get_parser(self, prog_name): parser = super(DeletePolicy, self).get_parser(prog_name) parser.add_argument( 'policy', metavar='<policy>', - help=_('Policy to delete'), + nargs='+', + help=_('Policy(s) to delete'), ) return parser def take_action(self, parsed_args): identity_client = self.app.client_manager.identity - identity_client.policies.delete(parsed_args.policy) + result = 0 + for i in parsed_args.policy: + try: + identity_client.policies.delete(i) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete policy with name or " + "ID '%(policy)s': %(e)s") + % {'policy': i, 'e': e}) + + if result > 0: + total = len(parsed_args.policy) + msg = (_("%(result)s of %(total)s policys failed " + "to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) class ListPolicy(command.Lister): @@ -136,9 +155,6 @@ class SetPolicy(command.Command): if parsed_args.type: kwargs['type'] = parsed_args.type - if not kwargs: - sys.stdout.write(_('Policy not updated, no arguments present\n')) - return identity_client.policies.update(parsed_args.policy, **kwargs) diff --git a/openstackclient/identity/v3/project.py b/openstackclient/identity/v3/project.py index 56c1d41a..56c4fbc8 100644..100755 --- a/openstackclient/identity/v3/project.py +++ b/openstackclient/identity/v3/project.py @@ -111,14 +111,14 @@ class CreateProject(command.ShowOne): enabled=enabled, **kwargs ) - except ks_exc.Conflict as e: + except ks_exc.Conflict: if parsed_args.or_show: project = utils.find_resource(identity_client.projects, parsed_args.name, domain_id=domain) LOG.info(_('Returning existing project %s'), project.name) else: - raise e + raise project._info.pop('links') return zip(*sorted(six.iteritems(project._info))) @@ -263,13 +263,6 @@ class SetProject(command.Command): def take_action(self, parsed_args): identity_client = self.app.client_manager.identity - if (not parsed_args.name - and not parsed_args.domain - and not parsed_args.description - and not parsed_args.enable - and not parsed_args.property - and not parsed_args.disable): - return project = common.find_project(identity_client, parsed_args.project, parsed_args.domain) diff --git a/openstackclient/identity/v3/region.py b/openstackclient/identity/v3/region.py index b5e46a9a..b7c51f93 100644 --- a/openstackclient/identity/v3/region.py +++ b/openstackclient/identity/v3/region.py @@ -13,13 +13,19 @@ """Identity v3 Region action implementations""" +import logging + from osc_lib.command import command +from osc_lib import exceptions from osc_lib import utils import six from openstackclient.i18n import _ +LOG = logging.getLogger(__name__) + + class CreateRegion(command.ShowOne): """Create new region""" @@ -60,21 +66,35 @@ class CreateRegion(command.ShowOne): class DeleteRegion(command.Command): - """Delete region""" + """Delete region(s)""" def get_parser(self, prog_name): parser = super(DeleteRegion, self).get_parser(prog_name) parser.add_argument( 'region', metavar='<region-id>', - help=_('Region ID to delete'), + nargs='+', + help=_('Region ID(s) to delete'), ) return parser def take_action(self, parsed_args): identity_client = self.app.client_manager.identity - - identity_client.regions.delete(parsed_args.region) + result = 0 + for i in parsed_args.region: + try: + identity_client.regions.delete(i) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete region with " + "ID '%(region)s': %(e)s") + % {'region': i, 'e': e}) + + if result > 0: + total = len(parsed_args.region) + msg = (_("%(result)s of %(total)s regions failed " + "to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) class ListRegion(command.Lister): @@ -132,8 +152,6 @@ class SetRegion(command.Command): def take_action(self, parsed_args): identity_client = self.app.client_manager.identity - if not parsed_args.parent_region and not parsed_args.description: - return kwargs = {} if parsed_args.description: kwargs['description'] = parsed_args.description diff --git a/openstackclient/identity/v3/role.py b/openstackclient/identity/v3/role.py index 965ca3f5..27380179 100644..100755 --- a/openstackclient/identity/v3/role.py +++ b/openstackclient/identity/v3/role.py @@ -165,13 +165,13 @@ class CreateRole(command.ShowOne): try: role = identity_client.roles.create(name=parsed_args.name) - except ks_exc.Conflict as e: + except ks_exc.Conflict: if parsed_args.or_show: role = utils.find_resource(identity_client.roles, parsed_args.name) LOG.info(_('Returning existing role %s'), role.name) else: - raise e + raise role._info.pop('links') return zip(*sorted(six.iteritems(role._info))) @@ -357,10 +357,6 @@ class SetRole(command.Command): def take_action(self, parsed_args): identity_client = self.app.client_manager.identity - if not parsed_args.name: - sys.stderr.write(_("Incorrect set of arguments provided. " - "See openstack --help for more details\n")) - return role = utils.find_resource( identity_client.roles, parsed_args.role, diff --git a/openstackclient/identity/v3/service.py b/openstackclient/identity/v3/service.py index 195b2701..97e64dc6 100644 --- a/openstackclient/identity/v3/service.py +++ b/openstackclient/identity/v3/service.py @@ -15,9 +15,10 @@ """Identity v3 Service action implementations""" -import sys +import logging from osc_lib.command import command +from osc_lib import exceptions from osc_lib import utils import six @@ -25,6 +26,9 @@ from openstackclient.i18n import _ from openstackclient.identity import common +LOG = logging.getLogger(__name__) + + class CreateService(command.ShowOne): """Create new service""" @@ -77,23 +81,36 @@ class CreateService(command.ShowOne): class DeleteService(command.Command): - """Delete service""" + """Delete service(s)""" def get_parser(self, prog_name): parser = super(DeleteService, self).get_parser(prog_name) parser.add_argument( 'service', metavar='<service>', - help=_('Service to delete (type, name or ID)'), + nargs='+', + help=_('Service(s) to delete (type, name or ID)'), ) return parser def take_action(self, parsed_args): identity_client = self.app.client_manager.identity - - service = common.find_service(identity_client, parsed_args.service) - - identity_client.services.delete(service.id) + result = 0 + for i in parsed_args.service: + try: + service = common.find_service(identity_client, i) + identity_client.services.delete(service.id) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete consumer with type, " + "name or ID '%(service)s': %(e)s") + % {'service': i, 'e': e}) + + if result > 0: + total = len(parsed_args.service) + msg = (_("%(result)s of %(total)s services failed " + "to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) class ListService(command.Lister): @@ -163,14 +180,6 @@ class SetService(command.Command): def take_action(self, parsed_args): identity_client = self.app.client_manager.identity - if (not parsed_args.name - and not parsed_args.type - and not parsed_args.description - and not parsed_args.enable - and not parsed_args.disable): - sys.stderr.write(_("Incorrect set of arguments provided. " - "See openstack --help for more details\n")) - return service = common.find_service(identity_client, parsed_args.service) kwargs = {} diff --git a/openstackclient/identity/v3/service_provider.py b/openstackclient/identity/v3/service_provider.py index 0beea813..440eba40 100644 --- a/openstackclient/identity/v3/service_provider.py +++ b/openstackclient/identity/v3/service_provider.py @@ -13,15 +13,19 @@ """Service Provider action implementations""" -import sys +import logging from osc_lib.command import command +from osc_lib import exceptions from osc_lib import utils import six from openstackclient.i18n import _ +LOG = logging.getLogger(__name__) + + class CreateServiceProvider(command.ShowOne): """Create new service provider""" @@ -83,21 +87,35 @@ class CreateServiceProvider(command.ShowOne): class DeleteServiceProvider(command.Command): - """Delete service provider""" + """Delete service provider(s)""" def get_parser(self, prog_name): parser = super(DeleteServiceProvider, self).get_parser(prog_name) parser.add_argument( 'service_provider', metavar='<service-provider>', - help=_('Service provider to delete'), + nargs='+', + help=_('Service provider(s) to delete'), ) return parser def take_action(self, parsed_args): service_client = self.app.client_manager.identity - service_client.federation.service_providers.delete( - parsed_args.service_provider) + result = 0 + for i in parsed_args.service_provider: + try: + service_client.federation.service_providers.delete(i) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete service provider with " + "name or ID '%(provider)s': %(e)s") + % {'provider': i, 'e': e}) + + if result > 0: + total = len(parsed_args.service_provider) + msg = (_("%(result)s of %(total)s service providers failed" + " to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) class ListServiceProvider(command.Lister): @@ -164,13 +182,6 @@ class SetServiceProvider(command.Command): elif parsed_args.disable is True: enabled = False - if not any((enabled is not None, parsed_args.description, - parsed_args.service_provider_url, - parsed_args.auth_url)): - sys.stdout.write(_("Service Provider not updated, no arguments " - "present\n")) - return (None, None) - service_provider = federation_client.service_providers.update( parsed_args.service_provider, enabled=enabled, description=parsed_args.description, diff --git a/openstackclient/identity/v3/user.py b/openstackclient/identity/v3/user.py index dd5af06a..dc47ef8d 100644..100755 --- a/openstackclient/identity/v3/user.py +++ b/openstackclient/identity/v3/user.py @@ -17,7 +17,6 @@ import copy import logging -import sys from keystoneauth1 import exceptions as ks_exc from osc_lib.command import command @@ -121,14 +120,14 @@ class CreateUser(command.ShowOne): description=parsed_args.description, enabled=enabled ) - except ks_exc.Conflict as e: + except ks_exc.Conflict: if parsed_args.or_show: user = utils.find_resource(identity_client.users, parsed_args.name, domain_id=domain_id) LOG.info(_('Returning existing user %s'), user.name) else: - raise e + raise user._info.pop('links') return zip(*sorted(six.iteritems(user._info))) @@ -330,18 +329,6 @@ class SetUser(command.Command): if parsed_args.password_prompt: parsed_args.password = utils.get_password(self.app.stdin) - if (not parsed_args.name - and not parsed_args.name - and not parsed_args.password - and not parsed_args.email - and not parsed_args.project - and not parsed_args.description - and not parsed_args.enable - and not parsed_args.disable): - sys.stderr.write(_("Incorrect set of arguments provided. " - "See openstack --help for more details\n")) - return - user = utils.find_resource( identity_client.users, parsed_args.user, diff --git a/openstackclient/image/v1/image.py b/openstackclient/image/v1/image.py index e7b60e5a..5f669c64 100644 --- a/openstackclient/image/v1/image.py +++ b/openstackclient/image/v1/image.py @@ -378,6 +378,7 @@ class ListImage(command.Lister): 'Disk Format', 'Container Format', 'Size', + 'Checksum', 'Status', 'is_public', 'protected', @@ -390,6 +391,7 @@ class ListImage(command.Lister): 'Disk Format', 'Container Format', 'Size', + 'Checksum', 'Status', 'Visibility', 'Protected', diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index 309b1b6b..0712e09c 100644..100755 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -339,7 +339,7 @@ class CreateImage(command.ShowOne): with fp: try: image_client.images.upload(image.id, fp) - except Exception as e: + except Exception: # If the upload fails for some reason attempt to remove the # dangling queued image made by the create() call above but # only if the user did not specify an id which indicates @@ -349,7 +349,7 @@ class CreateImage(command.ShowOne): image_client.images.delete(image.id) except Exception: pass # we don't care about this one - raise e # now, throw the upload exception again + raise # now, throw the upload exception again # update the image after the data has been uploaded image = image_client.images.get(image.id) @@ -490,6 +490,7 @@ class ListImage(command.Lister): 'Disk Format', 'Container Format', 'Size', + 'Checksum', 'Status', 'visibility', 'protected', @@ -502,6 +503,7 @@ class ListImage(command.Lister): 'Disk Format', 'Container Format', 'Size', + 'Checksum', 'Status', 'Visibility', 'Protected', @@ -834,11 +836,11 @@ class SetImage(command.Command): try: image = image_client.images.update(image.id, **kwargs) - except Exception as e: + except Exception: if activation_status is not None: LOG.info(_("Image %(id)s was %(status)s."), {'id': image.id, 'status': activation_status}) - raise e + raise class ShowImage(command.ShowOne): diff --git a/openstackclient/network/common.py b/openstackclient/network/common.py index f62840fc..2b1a5656 100644 --- a/openstackclient/network/common.py +++ b/openstackclient/network/common.py @@ -111,7 +111,7 @@ class NetworkAndComputeDelete(NetworkAndComputeCommand): if ret: total = len(resources) - msg = _("%(num)s of %(total)s %(resource)s failed to delete.") % { + msg = _("%(num)s of %(total)s %(resource)ss failed to delete.") % { "num": ret, "total": total, "resource": self.resource, diff --git a/openstackclient/network/v2/floating_ip_pool.py b/openstackclient/network/v2/floating_ip_pool.py new file mode 100644 index 00000000..c78ca06a --- /dev/null +++ b/openstackclient/network/v2/floating_ip_pool.py @@ -0,0 +1,66 @@ +# 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. +# + +"""Floating IP Pool action implementations""" + +import logging + +from osc_lib import exceptions +from osc_lib import utils + +from openstackclient.i18n import _ +from openstackclient.network import common + + +class ListFloatingIPPool(common.NetworkAndComputeLister): + """List pools of floating IP addresses""" + + def take_action_network(self, client, parsed_args): + msg = _("Floating ip pool operations are only available for " + "Compute v2 network.") + raise exceptions.CommandError(msg) + + def take_action_compute(self, client, parsed_args): + columns = ( + 'Name', + ) + data = client.floating_ip_pools.list() + + return (columns, + (utils.get_item_properties( + s, columns, + ) for s in data)) + + +class ListIPFloatingPool(ListFloatingIPPool): + """List pools of floating IP addresses""" + + # TODO(tangchen): Remove this class and ``ip floating pool list`` command + # two cycles after Mitaka. + + # This notifies cliff to not display the help for this command + deprecated = True + + log = logging.getLogger('deprecated') + + def take_action_network(self, client, parsed_args): + self.log.warning(_('This command has been deprecated. ' + 'Please use "floating ip pool list" instead.')) + return super(ListIPFloatingPool, self).take_action_network( + client, parsed_args) + + def take_action_compute(self, client, parsed_args): + self.log.warning(_('This command has been deprecated. ' + 'Please use "floating ip pool list" instead.')) + return super(ListIPFloatingPool, self).take_action_compute( + client, parsed_args) diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index 5d1431b5..05c5a012 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -14,6 +14,7 @@ """Port action implementations""" import argparse +import copy import json import logging @@ -196,7 +197,8 @@ def _add_updatable_args(parser): parser.add_argument( '--device-owner', metavar='<device-owner>', - help=_("Device owner of this port") + help=_("Device owner of this port. This is the entity that uses " + "the port (for example, network:dhcp).") ) parser.add_argument( '--vnic-type', @@ -337,6 +339,13 @@ class ListPort(command.Lister): def get_parser(self, prog_name): parser = super(ListPort, self).get_parser(prog_name) parser.add_argument( + '--device-owner', + metavar='<device-owner>', + help=_("List only ports with the specified device owner. " + "This is the entity that uses the port (for example, " + "network:dhcp).") + ) + parser.add_argument( '--router', metavar='<router>', dest='router', @@ -361,10 +370,12 @@ class ListPort(command.Lister): ) filters = {} + if parsed_args.device_owner is not None: + filters['device_owner'] = parsed_args.device_owner if parsed_args.router: _router = client.find_router(parsed_args.router, ignore_missing=False) - filters = {'device_id': _router.id} + filters['device_id'] = _router.id data = client.ports(**filters) @@ -475,3 +486,61 @@ class ShowPort(command.ShowOne): columns = _get_columns(obj) data = utils.get_item_properties(obj, columns, formatters=_formatters) return (columns, data) + + +class UnsetPort(command.Command): + """Unset port properties""" + + def get_parser(self, prog_name): + parser = super(UnsetPort, self).get_parser(prog_name) + parser.add_argument( + '--fixed-ip', + metavar='subnet=<subnet>,ip-address=<ip-address>', + action=parseractions.MultiKeyValueAction, + optional_keys=['subnet', 'ip-address'], + help=_("Desired IP and/or subnet (name or ID) which should be " + "removed from this port: subnet=<subnet>," + "ip-address=<ip-address> (repeat option to unset multiple " + "fixed IP addresses)")) + + parser.add_argument( + '--binding-profile', + metavar='<binding-profile-key>', + action='append', + help=_("Desired key which should be removed from binding:profile" + "(repeat option to unset multiple binding:profile data)")) + parser.add_argument( + 'port', + metavar="<port>", + help=_("Port to modify (name or ID)") + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + obj = client.find_port(parsed_args.port, ignore_missing=False) + # SDK ignores update() if it recieves a modified obj and attrs + # To handle the same tmp_obj is created in all take_action of + # Unset* classes + tmp_fixed_ips = copy.deepcopy(obj.fixed_ips) + tmp_binding_profile = copy.deepcopy(obj.binding_profile) + _prepare_fixed_ips(self.app.client_manager, parsed_args) + attrs = {} + if parsed_args.fixed_ip: + try: + for ip in parsed_args.fixed_ip: + tmp_fixed_ips.remove(ip) + except ValueError: + msg = _("Port does not contain fixed-ip %s") % ip + raise exceptions.CommandError(msg) + attrs['fixed_ips'] = tmp_fixed_ips + if parsed_args.binding_profile: + try: + for key in parsed_args.binding_profile: + del tmp_binding_profile[key] + except KeyError: + msg = _("Port does not contain binding-profile %s") % key + raise exceptions.CommandError(msg) + attrs['binding:profile'] = tmp_binding_profile + if attrs: + client.update_port(obj, **attrs) diff --git a/openstackclient/network/v2/subnet.py b/openstackclient/network/v2/subnet.py index b076d82e..f26f6804 100644 --- a/openstackclient/network/v2/subnet.py +++ b/openstackclient/network/v2/subnet.py @@ -28,6 +28,11 @@ from openstackclient.identity import common as identity_common LOG = logging.getLogger(__name__) +def _update_arguments(obj_list, parsed_args_list): + for item in parsed_args_list: + obj_list.remove(item) + + def _format_allocation_pools(data): pool_formatted = ['%s-%s' % (pool.get('start', ''), pool.get('end', '')) for pool in data] @@ -163,7 +168,7 @@ def _get_attrs(client_manager, parsed_args, is_create=True): attrs['allocation_pools'] = parsed_args.allocation_pools if parsed_args.dhcp: attrs['enable_dhcp'] = True - elif parsed_args.no_dhcp: + if parsed_args.no_dhcp: attrs['enable_dhcp'] = False if ('dns_nameservers' in parsed_args and parsed_args.dns_nameservers is not None): @@ -218,7 +223,6 @@ class CreateSubnet(command.ShowOne): dhcp_enable_group.add_argument( '--dhcp', action='store_true', - default=True, help=_("Enable DHCP (default)") ) dhcp_enable_group.add_argument( @@ -433,3 +437,81 @@ class ShowSubnet(command.ShowOne): columns = _get_columns(obj) data = utils.get_item_properties(obj, columns, formatters=_formatters) return (columns, data) + + +class UnsetSubnet(command.Command): + """Unset subnet properties""" + + def get_parser(self, prog_name): + parser = super(UnsetSubnet, self).get_parser(prog_name) + parser.add_argument( + '--allocation-pool', + metavar='start=<ip-address>,end=<ip-address>', + dest='allocation_pools', + action=parseractions.MultiKeyValueAction, + required_keys=['start', 'end'], + help=_('Allocation pool to be removed from this subnet ' + 'e.g.: start=192.168.199.2,end=192.168.199.254 ' + '(repeat option to unset multiple Allocation pools)') + ) + parser.add_argument( + '--dns-nameserver', + metavar='<dns-nameserver>', + action='append', + dest='dns_nameservers', + help=_('DNS server to be removed from this subnet ' + '(repeat option to set multiple DNS servers)') + ) + parser.add_argument( + '--host-route', + metavar='destination=<subnet>,gateway=<ip-address>', + dest='host_routes', + action=parseractions.MultiKeyValueAction, + required_keys=['destination', 'gateway'], + help=_('Route to be removed from this subnet ' + 'e.g.: destination=10.10.0.0/16,gateway=192.168.71.254 ' + 'destination: destination subnet (in CIDR notation) ' + 'gateway: nexthop IP address ' + '(repeat option to unset multiple host routes)') + ) + parser.add_argument( + 'subnet', + metavar="<subnet>", + help=_("Subnet to modify (name or ID)") + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + obj = client.find_subnet(parsed_args.subnet, ignore_missing=False) + tmp_obj = copy.deepcopy(obj) + attrs = {} + if parsed_args.dns_nameservers: + try: + _update_arguments(tmp_obj.dns_nameservers, + parsed_args.dns_nameservers) + except ValueError as error: + msg = (_("%s not in dns-nameservers") % str(error)) + raise exceptions.CommandError(msg) + attrs['dns_nameservers'] = tmp_obj.dns_nameservers + if parsed_args.host_routes: + try: + _update_arguments( + tmp_obj.host_routes, + convert_entries_to_nexthop(parsed_args.host_routes)) + except ValueError as error: + msg = (_("Subnet does not have %s in host-routes") % + str(error)) + raise exceptions.CommandError(msg) + attrs['host_routes'] = tmp_obj.host_routes + if parsed_args.allocation_pools: + try: + _update_arguments(tmp_obj.allocation_pools, + parsed_args.allocation_pools) + except ValueError as error: + msg = (_("Subnet does not have %s in allocation-pools") % + str(error)) + raise exceptions.CommandError(msg) + attrs['allocation_pools'] = tmp_obj.allocation_pools + if attrs: + client.update_subnet(obj, **attrs) diff --git a/openstackclient/network/v2/subnet_pool.py b/openstackclient/network/v2/subnet_pool.py index 55dfed83..ed2bb0ef 100644 --- a/openstackclient/network/v2/subnet_pool.py +++ b/openstackclient/network/v2/subnet_pool.py @@ -12,6 +12,7 @@ # """Subnet pool action implementations""" +import copy import logging @@ -337,3 +338,43 @@ class ShowSubnetPool(command.ShowOne): columns = _get_columns(obj) data = utils.get_item_properties(obj, columns, formatters=_formatters) return (columns, data) + + +class UnsetSubnetPool(command.Command): + """Unset subnet pool properties""" + + def get_parser(self, prog_name): + parser = super(UnsetSubnetPool, self).get_parser(prog_name) + parser.add_argument( + '--pool-prefix', + metavar='<pool-prefix>', + action='append', + dest='prefixes', + help=_('Remove subnet pool prefixes (in CIDR notation). ' + '(repeat option to unset multiple prefixes).'), + ) + parser.add_argument( + 'subnet_pool', + metavar="<subnet-pool>", + help=_("Subnet pool to modify (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, ignore_missing=False) + tmp_prefixes = copy.deepcopy(obj.prefixes) + attrs = {} + if parsed_args.prefixes: + for prefix in parsed_args.prefixes: + try: + tmp_prefixes.remove(prefix) + except ValueError: + msg = _( + "Subnet pool does not " + "contain prefix %s") % prefix + raise exceptions.CommandError(msg) + attrs['prefixes'] = tmp_prefixes + if attrs: + client.update_subnet_pool(obj, **attrs) diff --git a/openstackclient/shell.py b/openstackclient/shell.py index 49a06040..ed729e53 100644..100755 --- a/openstackclient/shell.py +++ b/openstackclient/shell.py @@ -331,10 +331,10 @@ class OpenStackShell(app.App): 'auth_type': auth_type, }, ) - except (IOError, OSError) as e: + except (IOError, OSError): self.log.critical("Could not read clouds.yaml configuration file") self.print_help_if_requested() - raise e + raise # TODO(thowe): Change cliff so the default value for debug # can be set to None. diff --git a/openstackclient/tests/common/test_clientmanager.py b/openstackclient/tests/common/test_clientmanager.py index 0a9965e0..117c7184 100644 --- a/openstackclient/tests/common/test_clientmanager.py +++ b/openstackclient/tests/common/test_clientmanager.py @@ -19,10 +19,10 @@ import mock from keystoneauth1.access import service_catalog from keystoneauth1.identity import v2 as auth_v2 from keystoneauth1 import token_endpoint +from osc_lib.api import auth from osc_lib import exceptions as exc from requests_mock.contrib import fixture -from openstackclient.api import auth from openstackclient.common import clientmanager from openstackclient.tests import fakes from openstackclient.tests import utils @@ -356,7 +356,7 @@ class TestClientManager(utils.TestCase): client_manager.setup_auth, ) - @mock.patch('openstackclient.api.auth.check_valid_authentication_options') + @mock.patch('osc_lib.api.auth.check_valid_authentication_options') def test_client_manager_auth_setup_once(self, check_authn_options_func): client_manager = clientmanager.ClientManager( cli_options=FakeOptions( diff --git a/openstackclient/tests/compute/v2/fakes.py b/openstackclient/tests/compute/v2/fakes.py index 882d8480..76402476 100644 --- a/openstackclient/tests/compute/v2/fakes.py +++ b/openstackclient/tests/compute/v2/fakes.py @@ -81,7 +81,10 @@ class FakeAggregate(object): "availability_zone": "ag_zone", } } + + # Overwrite default attributes. aggregate_info.update(attrs) + aggregate = fakes.FakeResource( info=copy.deepcopy(aggregate_info), loaded=True) @@ -178,6 +181,9 @@ class FakeComputev2Client(object): self.floating_ips = mock.Mock() self.floating_ips.resource_class = fakes.FakeResource(None, {}) + self.floating_ip_pools = mock.Mock() + self.floating_ip_pools.resource_class = fakes.FakeResource(None, {}) + self.networks = mock.Mock() self.networks.resource_class = fakes.FakeResource(None, {}) @@ -251,6 +257,8 @@ class FakeAgent(object): 'md5hash': 'agent-md5hash', 'hypervisor': 'hypervisor', } + + # Overwrite default attributes. agent_info.update(attrs) agent = fakes.FakeResource(info=copy.deepcopy(agent_info), @@ -390,7 +398,7 @@ class FakeHypervisorStats(object): :param Dictionary attrs: A dictionary with all attributes :return: - A FakeResource object, with id, hypervisor_hostname, and so on + A FakeResource object, with count, current_workload, and so on """ attrs = attrs or {} @@ -409,6 +417,8 @@ class FakeHypervisorStats(object): 'vcpus': 8, 'vcpus_used': 3, } + + # Overwrite default attributes. stats_info.update(attrs) # Set default method. @@ -572,7 +582,7 @@ class FakeServer(object): :param Dictionary methods: A dictionary with all methods :return: - A FakeResource object, with id, name, metadata + A FakeResource object, with id, name, metadata, and so on """ attrs = attrs or {} methods = methods or {} @@ -648,7 +658,7 @@ class FakeService(object): :param Dictionary attrs: A dictionary with all attributes :return: - A FakeResource object, with id, host, binary + A FakeResource object, with id, host, binary, and so on """ attrs = attrs or {} @@ -700,7 +710,7 @@ class FakeFlavor(object): :param Dictionary attrs: A dictionary with all attributes :return: - A FakeResource object, with id, name, ram, vcpus, properties + A FakeResource object, with id, name, ram, vcpus, and so on """ attrs = attrs or {} @@ -716,6 +726,7 @@ class FakeFlavor(object): 'OS-FLV-DISABLED:disabled': False, 'os-flavor-access:is_public': True, 'OS-FLV-EXT-DATA:ephemeral': 0, + 'properties': {'property': 'value'}, } # Overwrite default attributes. @@ -786,7 +797,7 @@ class FakeKeypair(object): :param Dictionary attrs: A dictionary with all attributes :return: - A FakeResource + A FakeResource object, name, fingerprint, and so on """ attrs = attrs or {} @@ -970,6 +981,54 @@ class FakeFloatingIP(object): return mock.MagicMock(side_effect=floating_ips) +class FakeFloatingIPPool(object): + """Fake one or more floating ip pools.""" + + @staticmethod + def create_one_floating_ip_pool(attrs=None): + """Create a fake floating ip pool. + + :param Dictionary attrs: + A dictionary with all attributes + :return: + A FakeResource object, with name, etc + """ + if attrs is None: + attrs = {} + + # Set default attributes. + floating_ip_pool_attrs = { + 'name': 'floating-ip-pool-name-' + uuid.uuid4().hex, + } + + # Overwrite default attributes. + floating_ip_pool_attrs.update(attrs) + + floating_ip_pool = fakes.FakeResource( + info=copy.deepcopy(floating_ip_pool_attrs), + loaded=True) + + return floating_ip_pool + + @staticmethod + def create_floating_ip_pools(attrs=None, count=2): + """Create multiple fake floating ip pools. + + :param Dictionary attrs: + A dictionary with all attributes + :param int count: + The number of floating ip pools to fake + :return: + A list of FakeResource objects faking the floating ip pools + """ + floating_ip_pools = [] + for i in range(0, count): + floating_ip_pools.append( + FakeFloatingIPPool.create_one_floating_ip_pool(attrs) + ) + return floating_ip_pools + + class FakeNetwork(object): """Fake one or more networks.""" @@ -1075,7 +1134,7 @@ class FakeHost(object): :param Dictionary attrs: A dictionary with all attributes :return: - A FakeResource object, with id and other attributes + A FakeResource object, with uuid and other attributes """ attrs = attrs or {} @@ -1131,6 +1190,7 @@ class FakeServerGroup(object): if attrs is None: attrs = {} + # Set default attributes. server_group_info = { 'id': 'server-group-id-' + uuid.uuid4().hex, 'members': [], @@ -1140,7 +1200,10 @@ class FakeServerGroup(object): 'project_id': 'server-group-project-id-' + uuid.uuid4().hex, 'user_id': 'server-group-user-id-' + uuid.uuid4().hex, } + + # Overwrite default attributes. server_group_info.update(attrs) + server_group = fakes.FakeResource( info=copy.deepcopy(server_group_info), loaded=True) diff --git a/openstackclient/tests/compute/v2/test_agent.py b/openstackclient/tests/compute/v2/test_agent.py index da329728..7695ee41 100644 --- a/openstackclient/tests/compute/v2/test_agent.py +++ b/openstackclient/tests/compute/v2/test_agent.py @@ -25,7 +25,9 @@ from openstackclient.tests import utils as tests_utils class TestAgent(compute_fakes.TestComputev2): - fake_agent = compute_fakes.FakeAgent.create_one_agent() + attr = {} + attr['agent_id'] = 1 + fake_agent = compute_fakes.FakeAgent.create_one_agent(attr) columns = ( 'agent_id', @@ -238,21 +240,34 @@ class TestAgentSet(TestAgent): super(TestAgentSet, self).setUp() self.agents_mock.update.return_value = self.fake_agent + self.agents_mock.list.return_value = [self.fake_agent] self.cmd = agent.SetAgent(self.app, None) - def test_agent_set(self): + def test_agent_set_nothing(self): arglist = [ - 'id', - 'new-version', - 'new-url', - 'new-md5hash', + '1', + ] + verifylist = [ + ('id', '1'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + self.agents_mock.update.assert_called_with(parsed_args.id, + self.fake_agent.version, + self.fake_agent.url, + self.fake_agent.md5hash) + self.assertIsNone(result) + + def test_agent_set_version(self): + arglist = [ + '1', + '--agent-version', 'new-version', ] verifylist = [ - ('id', 'id'), + ('id', '1'), ('version', 'new-version'), - ('url', 'new-url'), - ('md5hash', 'new-md5hash'), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -260,6 +275,46 @@ class TestAgentSet(TestAgent): self.agents_mock.update.assert_called_with(parsed_args.id, parsed_args.version, + self.fake_agent.url, + self.fake_agent.md5hash) + self.assertIsNone(result) + + def test_agent_set_url(self): + arglist = [ + '1', + '--url', 'new-url', + ] + + verifylist = [ + ('id', '1'), + ('url', 'new-url'), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + self.agents_mock.update.assert_called_with(parsed_args.id, + self.fake_agent.version, parsed_args.url, + self.fake_agent.md5hash) + self.assertIsNone(result) + + def test_agent_set_md5hash(self): + arglist = [ + '1', + '--md5hash', 'new-md5hash', + ] + + verifylist = [ + ('id', '1'), + ('md5hash', 'new-md5hash'), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + self.agents_mock.update.assert_called_with(parsed_args.id, + self.fake_agent.version, + self.fake_agent.url, parsed_args.md5hash) self.assertIsNone(result) diff --git a/openstackclient/tests/compute/v2/test_flavor.py b/openstackclient/tests/compute/v2/test_flavor.py index da76b6d7..20ae8706 100644 --- a/openstackclient/tests/compute/v2/test_flavor.py +++ b/openstackclient/tests/compute/v2/test_flavor.py @@ -56,6 +56,7 @@ class TestFlavorCreate(TestFlavor): 'id', 'name', 'os-flavor-access:is_public', + 'properties', 'ram', 'rxtx_factor', 'swap', @@ -68,6 +69,7 @@ class TestFlavorCreate(TestFlavor): flavor.id, flavor.name, flavor.is_public, + utils.format_dict(flavor.properties), flavor.ram, flavor.rxtx_factor, flavor.swap, @@ -116,7 +118,6 @@ class TestFlavorCreate(TestFlavor): def test_flavor_create_all_options(self): arglist = [ - self.flavor.name, '--id', self.flavor.id, '--ram', str(self.flavor.ram), '--disk', str(self.flavor.disk), @@ -125,9 +126,10 @@ class TestFlavorCreate(TestFlavor): '--vcpus', str(self.flavor.vcpus), '--rxtx-factor', str(self.flavor.rxtx_factor), '--public', + '--property', 'property=value', + self.flavor.name, ] verifylist = [ - ('name', self.flavor.name), ('id', self.flavor.id), ('ram', self.flavor.ram), ('disk', self.flavor.disk), @@ -136,6 +138,8 @@ class TestFlavorCreate(TestFlavor): ('vcpus', self.flavor.vcpus), ('rxtx_factor', self.flavor.rxtx_factor), ('public', True), + ('property', {'property': 'value'}), + ('name', self.flavor.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -152,6 +156,8 @@ class TestFlavorCreate(TestFlavor): ) columns, data = self.cmd.take_action(parsed_args) self.flavors_mock.create.assert_called_once_with(*args) + self.flavor.set_keys.assert_called_once_with({'property': 'value'}) + self.flavor.get_keys.assert_called_once_with() self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) @@ -160,7 +166,6 @@ class TestFlavorCreate(TestFlavor): self.flavor.is_public = False arglist = [ - self.flavor.name, '--id', self.flavor.id, '--ram', str(self.flavor.ram), '--disk', str(self.flavor.disk), @@ -170,9 +175,11 @@ class TestFlavorCreate(TestFlavor): '--rxtx-factor', str(self.flavor.rxtx_factor), '--private', '--project', identity_fakes.project_id, + '--property', 'key1=value1', + '--property', 'key2=value2', + self.flavor.name, ] verifylist = [ - ('name', self.flavor.name), ('id', self.flavor.id), ('ram', self.flavor.ram), ('disk', self.flavor.disk), @@ -182,6 +189,8 @@ class TestFlavorCreate(TestFlavor): ('rxtx_factor', self.flavor.rxtx_factor), ('public', False), ('project', identity_fakes.project_id), + ('property', {'key1': 'value1', 'key2': 'value2'}), + ('name', self.flavor.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -202,6 +211,9 @@ class TestFlavorCreate(TestFlavor): self.flavor.id, identity_fakes.project_id, ) + self.flavor.set_keys.assert_called_with( + {'key1': 'value1', 'key2': 'value2'}) + self.flavor.get_keys.assert_called_with() self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) diff --git a/openstackclient/tests/compute/v2/test_server.py b/openstackclient/tests/compute/v2/test_server.py index 0f155601..1c5a5fe4 100644 --- a/openstackclient/tests/compute/v2/test_server.py +++ b/openstackclient/tests/compute/v2/test_server.py @@ -43,6 +43,11 @@ class TestServer(compute_fakes.TestComputev2): self.flavors_mock = self.app.client_manager.compute.flavors self.flavors_mock.reset_mock() + # Get a shortcut to the compute client SecurityGroupManager Mock + self.security_groups_mock = \ + self.app.client_manager.compute.security_groups + self.security_groups_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() @@ -51,10 +56,10 @@ class TestServer(compute_fakes.TestComputev2): self.volumes_mock = self.app.client_manager.volume.volumes self.volumes_mock.reset_mock() - # Set object attributes to be tested. Could be overwriten in subclass. + # Set object attributes to be tested. Could be overwritten in subclass. self.attrs = {} - # Set object methods to be tested. Could be overwriten in subclass. + # Set object methods to be tested. Could be overwritten in subclass. self.methods = {} def setup_servers_mock(self, count): @@ -88,6 +93,80 @@ class TestServer(compute_fakes.TestComputev2): self.assertIsNone(result) +class TestServerAddFixedIP(TestServer): + + def setUp(self): + super(TestServerAddFixedIP, self).setUp() + + # Get a shortcut to the compute client ServerManager Mock + self.networks_mock = self.app.client_manager.compute.networks + + # Get the command object to test + self.cmd = server.AddFixedIP(self.app, None) + + # Set add_fixed_ip method to be tested. + self.methods = { + 'add_fixed_ip': None, + } + + def test_server_add_fixed_ip(self): + servers = self.setup_servers_mock(count=1) + network = compute_fakes.FakeNetwork.create_one_network() + self.networks_mock.get.return_value = network + + arglist = [ + servers[0].id, + network.id, + ] + verifylist = [ + ('server', servers[0].id), + ('network', network.id) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + servers[0].add_fixed_ip.assert_called_once_with( + network.id, + ) + self.assertIsNone(result) + + +class TestServerAddFloatingIP(TestServer): + + def setUp(self): + super(TestServerAddFloatingIP, self).setUp() + + # Get a shortcut to the compute client ServerManager Mock + self.networks_mock = self.app.client_manager.compute.networks + + # Get the command object to test + self.cmd = server.AddFloatingIP(self.app, None) + + # Set add_floating_ip method to be tested. + self.methods = { + 'add_floating_ip': None, + } + + def test_server_add_floating_ip(self): + servers = self.setup_servers_mock(count=1) + + arglist = [ + servers[0].id, + '1.2.3.4', + ] + verifylist = [ + ('server', servers[0].id), + ('ip_address', '1.2.3.4'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + servers[0].add_floating_ip.assert_called_once_with('1.2.3.4') + self.assertIsNone(result) + + class TestServerCreate(TestServer): columns = ( @@ -843,6 +922,118 @@ class TestServerRebuild(TestServer): self.server.rebuild.assert_called_with(self.image, None) +class TestServerRemoveFixedIP(TestServer): + + def setUp(self): + super(TestServerRemoveFixedIP, self).setUp() + + # Get the command object to test + self.cmd = server.RemoveFixedIP(self.app, None) + + # Set unshelve method to be tested. + self.methods = { + 'remove_fixed_ip': None, + } + + def test_server_remove_fixed_ip(self): + servers = self.setup_servers_mock(count=1) + + arglist = [ + servers[0].id, + '1.2.3.4', + ] + verifylist = [ + ('server', servers[0].id), + ('ip_address', '1.2.3.4'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + servers[0].remove_fixed_ip.assert_called_once_with('1.2.3.4') + self.assertIsNone(result) + + +class TestServerRemoveFloatingIP(TestServer): + + def setUp(self): + super(TestServerRemoveFloatingIP, self).setUp() + + # Get the command object to test + self.cmd = server.RemoveFloatingIP(self.app, None) + + # Set unshelve method to be tested. + self.methods = { + 'remove_floating_ip': None, + } + + def test_server_remove_floating_ip(self): + servers = self.setup_servers_mock(count=1) + + arglist = [ + servers[0].id, + '1.2.3.4', + ] + verifylist = [ + ('server', servers[0].id), + ('ip_address', '1.2.3.4'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + servers[0].remove_floating_ip.assert_called_once_with('1.2.3.4') + self.assertIsNone(result) + + +class TestServerRemoveSecurityGroup(TestServer): + + def setUp(self): + super(TestServerRemoveSecurityGroup, self).setUp() + + self.security_group = \ + compute_fakes.FakeSecurityGroup.create_one_security_group() + # This is the return value for utils.find_resource() for security group + self.security_groups_mock.get.return_value = self.security_group + + attrs = { + 'security_groups': [{'name': self.security_group.id}] + } + methods = { + 'remove_security_group': None, + } + + self.server = compute_fakes.FakeServer.create_one_server( + attrs=attrs, + methods=methods + ) + # This is the return value for utils.find_resource() for server + self.servers_mock.get.return_value = self.server + + # Get the command object to test + self.cmd = server.RemoveServerSecurityGroup(self.app, None) + + def test_server_remove_security_group(self): + arglist = [ + self.server.id, + self.security_group.id + ] + verifylist = [ + ('server', self.server.id), + ('group', self.security_group.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + self.security_groups_mock.get.assert_called_with( + self.security_group.id, + ) + self.servers_mock.get.assert_called_with(self.server.id) + self.server.remove_security_group.assert_called_with( + self.security_group.id, + ) + self.assertIsNone(result) + + class TestServerResize(TestServer): def setUp(self): diff --git a/openstackclient/tests/compute/v2/test_server_backup.py b/openstackclient/tests/compute/v2/test_server_backup.py index b6802ff0..8eeb0dca 100644 --- a/openstackclient/tests/compute/v2/test_server_backup.py +++ b/openstackclient/tests/compute/v2/test_server_backup.py @@ -34,10 +34,10 @@ class TestServerBackup(compute_fakes.TestComputev2): 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. + # Set object attributes to be tested. Could be overwritten in subclass. self.attrs = {} - # Set object methods to be tested. Could be overwriten in subclass. + # Set object methods to be tested. Could be overwritten in subclass. self.methods = {} def setup_servers_mock(self, count): diff --git a/openstackclient/tests/compute/v2/test_server_image.py b/openstackclient/tests/compute/v2/test_server_image.py index 8a8bd9bc..c3c52da0 100644 --- a/openstackclient/tests/compute/v2/test_server_image.py +++ b/openstackclient/tests/compute/v2/test_server_image.py @@ -33,10 +33,10 @@ class TestServerImage(compute_fakes.TestComputev2): 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. + # Set object attributes to be tested. Could be overwritten in subclass. self.attrs = {} - # Set object methods to be tested. Could be overwriten in subclass. + # Set object methods to be tested. Could be overwritten in subclass. self.methods = {} def setup_servers_mock(self, count): diff --git a/openstackclient/tests/identity/v3/fakes.py b/openstackclient/tests/identity/v3/fakes.py index dd918616..df532df4 100644 --- a/openstackclient/tests/identity/v3/fakes.py +++ b/openstackclient/tests/identity/v3/fakes.py @@ -15,6 +15,7 @@ import copy import mock +import uuid from keystoneauth1 import access from keystoneauth1 import fixture @@ -575,3 +576,66 @@ class TestOAuth1(utils.TestCommand): endpoint=fakes.AUTH_URL, token=fakes.AUTH_TOKEN ) + + +class FakeProject(object): + """Fake one or more project.""" + + @staticmethod + def create_one_project(attrs=None): + """Create a fake project. + + :param Dictionary attrs: + A dictionary with all attributes + :return: + A FakeResource object, with id, name, and so on + """ + + attrs = attrs or {} + + # set default attributes. + project_info = { + 'id': 'project-id-' + uuid.uuid4().hex, + 'name': 'project-name-' + uuid.uuid4().hex, + 'description': 'project-description-' + uuid.uuid4().hex, + 'enabled': True, + 'is_domain': False, + 'domain_id': 'domain-id-' + uuid.uuid4().hex, + 'parent_id': 'parent-id-' + uuid.uuid4().hex, + 'links': 'links-' + uuid.uuid4().hex, + } + project_info.update(attrs) + + project = fakes.FakeResource(info=copy.deepcopy(project_info), + loaded=True) + return project + + +class FakeDomain(object): + """Fake one or more domain.""" + + @staticmethod + def create_one_domain(attrs=None): + """Create a fake domain. + + :param Dictionary attrs: + A dictionary with all attributes + :return: + A FakeResource object, with id, name, and so on + """ + + attrs = attrs or {} + + # set default attributes. + domain_info = { + 'id': 'domain-id-' + uuid.uuid4().hex, + 'name': 'domain-name-' + uuid.uuid4().hex, + 'description': 'domain-description-' + uuid.uuid4().hex, + 'enabled': True, + 'links': 'links-' + uuid.uuid4().hex, + } + domain_info.update(attrs) + + domain = fakes.FakeResource(info=copy.deepcopy(domain_info), + loaded=True) + return domain diff --git a/openstackclient/tests/identity/v3/test_consumer.py b/openstackclient/tests/identity/v3/test_consumer.py index 4a8cf087..d90c7347 100644 --- a/openstackclient/tests/identity/v3/test_consumer.py +++ b/openstackclient/tests/identity/v3/test_consumer.py @@ -83,7 +83,7 @@ class TestConsumerDelete(TestOAuth1): identity_fakes.consumer_id, ] verifylist = [ - ('consumer', identity_fakes.consumer_id), + ('consumer', [identity_fakes.consumer_id]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) diff --git a/openstackclient/tests/identity/v3/test_domain.py b/openstackclient/tests/identity/v3/test_domain.py index e06e0681..5e094021 100644 --- a/openstackclient/tests/identity/v3/test_domain.py +++ b/openstackclient/tests/identity/v3/test_domain.py @@ -10,10 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. -import copy - from openstackclient.identity.v3 import domain -from openstackclient.tests import fakes from openstackclient.tests.identity.v3 import fakes as identity_fakes @@ -35,20 +32,17 @@ class TestDomainCreate(TestDomain): 'id', 'name', ) - datalist = ( - identity_fakes.domain_description, - True, - identity_fakes.domain_id, - identity_fakes.domain_name, - ) def setUp(self): super(TestDomainCreate, self).setUp() - self.domains_mock.create.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.DOMAIN), - loaded=True, + self.domain = identity_fakes.FakeDomain.create_one_domain() + self.domains_mock.create.return_value = self.domain + self.datalist = ( + self.domain.description, + True, + self.domain.id, + self.domain.name, ) # Get the command object to test @@ -56,10 +50,10 @@ class TestDomainCreate(TestDomain): def test_domain_create_no_options(self): arglist = [ - identity_fakes.domain_name, + self.domain.name, ] verifylist = [ - ('name', identity_fakes.domain_name), + ('name', self.domain.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -70,7 +64,7 @@ class TestDomainCreate(TestDomain): # Set expected values kwargs = { - 'name': identity_fakes.domain_name, + 'name': self.domain.name, 'description': None, 'enabled': True, } @@ -84,11 +78,11 @@ class TestDomainCreate(TestDomain): def test_domain_create_description(self): arglist = [ '--description', 'new desc', - identity_fakes.domain_name, + self.domain.name, ] verifylist = [ ('description', 'new desc'), - ('name', identity_fakes.domain_name), + ('name', self.domain.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -99,7 +93,7 @@ class TestDomainCreate(TestDomain): # Set expected values kwargs = { - 'name': identity_fakes.domain_name, + 'name': self.domain.name, 'description': 'new desc', 'enabled': True, } @@ -113,11 +107,11 @@ class TestDomainCreate(TestDomain): def test_domain_create_enable(self): arglist = [ '--enable', - identity_fakes.domain_name, + self.domain.name, ] verifylist = [ ('enable', True), - ('name', identity_fakes.domain_name), + ('name', self.domain.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -128,7 +122,7 @@ class TestDomainCreate(TestDomain): # Set expected values kwargs = { - 'name': identity_fakes.domain_name, + 'name': self.domain.name, 'description': None, 'enabled': True, } @@ -142,11 +136,11 @@ class TestDomainCreate(TestDomain): def test_domain_create_disable(self): arglist = [ '--disable', - identity_fakes.domain_name, + self.domain.name, ] verifylist = [ ('disable', True), - ('name', identity_fakes.domain_name), + ('name', self.domain.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -157,7 +151,7 @@ class TestDomainCreate(TestDomain): # Set expected values kwargs = { - 'name': identity_fakes.domain_name, + 'name': self.domain.name, 'description': None, 'enabled': False, } @@ -171,15 +165,13 @@ class TestDomainCreate(TestDomain): class TestDomainDelete(TestDomain): + domain = identity_fakes.FakeDomain.create_one_domain() + def setUp(self): super(TestDomainDelete, self).setUp() # This is the return value for utils.find_resource() - self.domains_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.DOMAIN), - loaded=True, - ) + self.domains_mock.get.return_value = self.domain self.domains_mock.delete.return_value = None # Get the command object to test @@ -187,33 +179,29 @@ class TestDomainDelete(TestDomain): def test_domain_delete(self): arglist = [ - identity_fakes.domain_id, + self.domain.id, ] verifylist = [ - ('domain', identity_fakes.domain_id), + ('domain', [self.domain.id]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) self.domains_mock.delete.assert_called_with( - identity_fakes.domain_id, + self.domain.id, ) self.assertIsNone(result) class TestDomainList(TestDomain): + domain = identity_fakes.FakeDomain.create_one_domain() + def setUp(self): super(TestDomainList, self).setUp() - self.domains_mock.list.return_value = [ - fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.DOMAIN), - loaded=True, - ), - ] + self.domains_mock.list.return_value = [self.domain] # Get the command object to test self.cmd = domain.ListDomain(self.app, None) @@ -232,56 +220,54 @@ class TestDomainList(TestDomain): collist = ('ID', 'Name', 'Enabled', 'Description') self.assertEqual(collist, columns) datalist = (( - identity_fakes.domain_id, - identity_fakes.domain_name, + self.domain.id, + self.domain.name, True, - identity_fakes.domain_description, + self.domain.description, ), ) self.assertEqual(datalist, tuple(data)) class TestDomainSet(TestDomain): + domain = identity_fakes.FakeDomain.create_one_domain() + def setUp(self): super(TestDomainSet, self).setUp() - self.domains_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.DOMAIN), - loaded=True, - ) + self.domains_mock.get.return_value = self.domain - self.domains_mock.update.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.DOMAIN), - loaded=True, - ) + self.domains_mock.update.return_value = self.domain # Get the command object to test self.cmd = domain.SetDomain(self.app, None) def test_domain_set_no_options(self): arglist = [ - identity_fakes.domain_name, + self.domain.name, ] verifylist = [ - ('domain', identity_fakes.domain_name), + ('domain', self.domain.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.assertNotCalled(self.domains_mock.update) + kwargs = {} + self.domains_mock.update.assert_called_with( + self.domain.id, + **kwargs + ) self.assertIsNone(result) def test_domain_set_name(self): arglist = [ '--name', 'qwerty', - identity_fakes.domain_id, + self.domain.id, ] verifylist = [ ('name', 'qwerty'), - ('domain', identity_fakes.domain_id), + ('domain', self.domain.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -292,7 +278,7 @@ class TestDomainSet(TestDomain): 'name': 'qwerty', } self.domains_mock.update.assert_called_with( - identity_fakes.domain_id, + self.domain.id, **kwargs ) self.assertIsNone(result) @@ -300,11 +286,11 @@ class TestDomainSet(TestDomain): def test_domain_set_description(self): arglist = [ '--description', 'new desc', - identity_fakes.domain_id, + self.domain.id, ] verifylist = [ ('description', 'new desc'), - ('domain', identity_fakes.domain_id), + ('domain', self.domain.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -315,7 +301,7 @@ class TestDomainSet(TestDomain): 'description': 'new desc', } self.domains_mock.update.assert_called_with( - identity_fakes.domain_id, + self.domain.id, **kwargs ) self.assertIsNone(result) @@ -323,11 +309,11 @@ class TestDomainSet(TestDomain): def test_domain_set_enable(self): arglist = [ '--enable', - identity_fakes.domain_id, + self.domain.id, ] verifylist = [ ('enable', True), - ('domain', identity_fakes.domain_id), + ('domain', self.domain.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -338,7 +324,7 @@ class TestDomainSet(TestDomain): 'enabled': True, } self.domains_mock.update.assert_called_with( - identity_fakes.domain_id, + self.domain.id, **kwargs ) self.assertIsNone(result) @@ -346,11 +332,11 @@ class TestDomainSet(TestDomain): def test_domain_set_disable(self): arglist = [ '--disable', - identity_fakes.domain_id, + self.domain.id, ] verifylist = [ ('disable', True), - ('domain', identity_fakes.domain_id), + ('domain', self.domain.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -361,7 +347,7 @@ class TestDomainSet(TestDomain): 'enabled': False, } self.domains_mock.update.assert_called_with( - identity_fakes.domain_id, + self.domain.id, **kwargs ) self.assertIsNone(result) @@ -372,21 +358,17 @@ class TestDomainShow(TestDomain): def setUp(self): super(TestDomainShow, self).setUp() - self.domains_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.DOMAIN), - loaded=True, - ) - + self.domain = identity_fakes.FakeDomain.create_one_domain() + self.domains_mock.get.return_value = self.domain # Get the command object to test self.cmd = domain.ShowDomain(self.app, None) def test_domain_show(self): arglist = [ - identity_fakes.domain_id, + self.domain.id, ] verifylist = [ - ('domain', identity_fakes.domain_id), + ('domain', self.domain.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.app.client_manager.identity.tokens.get_token_data.return_value = \ @@ -405,15 +387,15 @@ class TestDomainShow(TestDomain): # data to be shown. columns, data = self.cmd.take_action(parsed_args) self.domains_mock.get.assert_called_with( - identity_fakes.domain_id, + self.domain.id, ) collist = ('description', 'enabled', 'id', 'name') self.assertEqual(collist, columns) datalist = ( - identity_fakes.domain_description, + self.domain.description, True, - identity_fakes.domain_id, - identity_fakes.domain_name, + self.domain.id, + self.domain.name, ) self.assertEqual(datalist, data) diff --git a/openstackclient/tests/identity/v3/test_endpoint.py b/openstackclient/tests/identity/v3/test_endpoint.py index d953459c..04276319 100644 --- a/openstackclient/tests/identity/v3/test_endpoint.py +++ b/openstackclient/tests/identity/v3/test_endpoint.py @@ -273,7 +273,7 @@ class TestEndpointDelete(TestEndpoint): identity_fakes.endpoint_id, ] verifylist = [ - ('endpoint', identity_fakes.endpoint_id), + ('endpoint', [identity_fakes.endpoint_id]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -485,7 +485,17 @@ class TestEndpointSet(TestEndpoint): result = self.cmd.take_action(parsed_args) - self.assertNotCalled(self.endpoints_mock.update) + kwargs = { + 'enabled': None, + 'interface': None, + 'region': None, + 'service': None, + 'url': None, + } + self.endpoints_mock.update.assert_called_with( + identity_fakes.endpoint_id, + **kwargs + ) self.assertIsNone(result) def test_endpoint_set_interface(self): diff --git a/openstackclient/tests/identity/v3/test_identity_provider.py b/openstackclient/tests/identity/v3/test_identity_provider.py index 3ff79812..1ec61052 100644 --- a/openstackclient/tests/identity/v3/test_identity_provider.py +++ b/openstackclient/tests/identity/v3/test_identity_provider.py @@ -255,7 +255,7 @@ class TestIdentityProviderDelete(TestIdentityProvider): identity_fakes.idp_id, ] verifylist = [ - ('identity_provider', identity_fakes.idp_id), + ('identity_provider', [identity_fakes.idp_id]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -585,8 +585,8 @@ class TestIdentityProviderSet(TestIdentityProvider): # expect take_action() to return (None, None) as # neither --enable nor --disable was specified - self.assertIsNone(columns) - self.assertIsNone(data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) class TestIdentityProviderShow(TestIdentityProvider): @@ -599,7 +599,11 @@ class TestIdentityProviderShow(TestIdentityProvider): copy.deepcopy(identity_fakes.IDENTITY_PROVIDER), loaded=True, ) + + self.identity_providers_mock.get.side_effect = [Exception("Not found"), + ret] self.identity_providers_mock.get.return_value = ret + # Get the command object to test self.cmd = identity_provider.ShowIdentityProvider(self.app, None) diff --git a/openstackclient/tests/identity/v3/test_mappings.py b/openstackclient/tests/identity/v3/test_mappings.py index af7b135d..6aa1a6e5 100644 --- a/openstackclient/tests/identity/v3/test_mappings.py +++ b/openstackclient/tests/identity/v3/test_mappings.py @@ -91,7 +91,7 @@ class TestMappingDelete(TestMapping): identity_fakes.mapping_id ] verifylist = [ - ('mapping', identity_fakes.mapping_id) + ('mapping', [identity_fakes.mapping_id]) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) diff --git a/openstackclient/tests/identity/v3/test_project.py b/openstackclient/tests/identity/v3/test_project.py index 93bf18af..65874baa 100644 --- a/openstackclient/tests/identity/v3/test_project.py +++ b/openstackclient/tests/identity/v3/test_project.py @@ -13,13 +13,11 @@ # under the License. # -import copy import mock from osc_lib import exceptions from openstackclient.identity.v3 import project -from openstackclient.tests import fakes from openstackclient.tests.identity.v3 import fakes as identity_fakes @@ -39,48 +37,46 @@ class TestProject(identity_fakes.TestIdentityv3): class TestProjectCreate(TestProject): + domain = identity_fakes.FakeDomain.create_one_domain() + columns = ( 'description', 'domain_id', 'enabled', 'id', - 'name' - ) - datalist = ( - identity_fakes.project_description, - identity_fakes.domain_id, - True, - identity_fakes.project_id, - identity_fakes.project_name, + 'is_domain', + 'name', + 'parent_id', ) def setUp(self): super(TestProjectCreate, self).setUp() - self.domains_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.DOMAIN), - loaded=True, - ) - - self.projects_mock.create.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.PROJECT), - loaded=True, + self.project = identity_fakes.FakeProject.create_one_project( + attrs={'domain_id': self.domain.id}) + self.domains_mock.get.return_value = self.domain + self.projects_mock.create.return_value = self.project + self.datalist = ( + self.project.description, + self.project.domain_id, + True, + self.project.id, + False, + self.project.name, + self.project.parent_id, ) - # Get the command object to test self.cmd = project.CreateProject(self.app, None) def test_project_create_no_options(self): arglist = [ - identity_fakes.project_name, + self.project.name, ] verifylist = [ ('parent', None), ('enable', False), ('disable', False), - ('name', identity_fakes.project_name), + ('name', self.project.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -91,7 +87,7 @@ class TestProjectCreate(TestProject): # Set expected values kwargs = { - 'name': identity_fakes.project_name, + 'name': self.project.name, 'domain': None, 'description': None, 'enabled': True, @@ -103,27 +99,37 @@ class TestProjectCreate(TestProject): **kwargs ) - collist = ('description', 'domain_id', 'enabled', 'id', 'name') + collist = ( + 'description', + 'domain_id', + 'enabled', + 'id', + 'is_domain', + 'name', + 'parent_id', + ) self.assertEqual(collist, columns) datalist = ( - identity_fakes.project_description, - identity_fakes.domain_id, + self.project.description, + self.project.domain_id, True, - identity_fakes.project_id, - identity_fakes.project_name, + self.project.id, + False, + self.project.name, + self.project.parent_id, ) self.assertEqual(datalist, data) def test_project_create_description(self): arglist = [ '--description', 'new desc', - identity_fakes.project_name, + self.project.name, ] verifylist = [ ('description', 'new desc'), ('enable', False), ('disable', False), - ('name', identity_fakes.project_name), + ('name', self.project.name), ('parent', None), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -135,7 +141,7 @@ class TestProjectCreate(TestProject): # Set expected values kwargs = { - 'name': identity_fakes.project_name, + 'name': self.project.name, 'domain': None, 'description': 'new desc', 'enabled': True, @@ -152,14 +158,14 @@ class TestProjectCreate(TestProject): def test_project_create_domain(self): arglist = [ - '--domain', identity_fakes.domain_name, - identity_fakes.project_name, + '--domain', self.project.domain_id, + self.project.name, ] verifylist = [ - ('domain', identity_fakes.domain_name), + ('domain', self.project.domain_id), ('enable', False), ('disable', False), - ('name', identity_fakes.project_name), + ('name', self.project.name), ('parent', None), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -171,8 +177,8 @@ class TestProjectCreate(TestProject): # Set expected values kwargs = { - 'name': identity_fakes.project_name, - 'domain': identity_fakes.domain_id, + 'name': self.project.name, + 'domain': self.project.domain_id, 'description': None, 'enabled': True, 'parent': None, @@ -188,14 +194,14 @@ class TestProjectCreate(TestProject): def test_project_create_domain_no_perms(self): arglist = [ - '--domain', identity_fakes.domain_id, - identity_fakes.project_name, + '--domain', self.project.domain_id, + self.project.name, ] verifylist = [ - ('domain', identity_fakes.domain_id), + ('domain', self.project.domain_id), ('enable', False), ('disable', False), - ('name', identity_fakes.project_name), + ('name', self.project.name), ('parent', None), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -207,8 +213,8 @@ class TestProjectCreate(TestProject): # Set expected values kwargs = { - 'name': identity_fakes.project_name, - 'domain': identity_fakes.domain_id, + 'name': self.project.name, + 'domain': self.project.domain_id, 'description': None, 'enabled': True, 'parent': None, @@ -222,12 +228,12 @@ class TestProjectCreate(TestProject): def test_project_create_enable(self): arglist = [ '--enable', - identity_fakes.project_name, + self.project.name, ] verifylist = [ ('enable', True), ('disable', False), - ('name', identity_fakes.project_name), + ('name', self.project.name), ('parent', None), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -239,7 +245,7 @@ class TestProjectCreate(TestProject): # Set expected values kwargs = { - 'name': identity_fakes.project_name, + 'name': self.project.name, 'domain': None, 'description': None, 'enabled': True, @@ -257,12 +263,12 @@ class TestProjectCreate(TestProject): def test_project_create_disable(self): arglist = [ '--disable', - identity_fakes.project_name, + self.project.name, ] verifylist = [ ('enable', False), ('disable', True), - ('name', identity_fakes.project_name), + ('name', self.project.name), ('parent', None), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -274,7 +280,7 @@ class TestProjectCreate(TestProject): # Set expected values kwargs = { - 'name': identity_fakes.project_name, + 'name': self.project.name, 'domain': None, 'description': None, 'enabled': False, @@ -293,11 +299,11 @@ class TestProjectCreate(TestProject): arglist = [ '--property', 'fee=fi', '--property', 'fo=fum', - identity_fakes.project_name, + self.project.name, ] verifylist = [ ('property', {'fee': 'fi', 'fo': 'fum'}), - ('name', identity_fakes.project_name), + ('name', self.project.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -308,7 +314,7 @@ class TestProjectCreate(TestProject): # Set expected values kwargs = { - 'name': identity_fakes.project_name, + 'name': self.project.name, 'domain': None, 'description': None, 'enabled': True, @@ -326,37 +332,32 @@ class TestProjectCreate(TestProject): self.assertEqual(self.datalist, data) def test_project_create_parent(self): - self.projects_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.PROJECT), - loaded=True, - ) - self.projects_mock.create.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.PROJECT_WITH_PARENT), - loaded=True, - ) + self.parent = identity_fakes.FakeProject.create_one_project() + self.project = identity_fakes.FakeProject.create_one_project( + attrs={'domain_id': self.domain.id, 'parent_id': self.parent.id}) + self.projects_mock.get.return_value = self.parent + self.projects_mock.create.return_value = self.project arglist = [ - '--domain', identity_fakes.PROJECT_WITH_PARENT['domain_id'], - '--parent', identity_fakes.PROJECT['name'], - identity_fakes.PROJECT_WITH_PARENT['name'], + '--domain', self.project.domain_id, + '--parent', self.parent.name, + self.project.name, ] verifylist = [ - ('domain', identity_fakes.PROJECT_WITH_PARENT['domain_id']), - ('parent', identity_fakes.PROJECT['name']), + ('domain', self.project.domain_id), + ('parent', self.parent.name), ('enable', False), ('disable', False), - ('name', identity_fakes.PROJECT_WITH_PARENT['name']), + ('name', self.project.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) kwargs = { - 'name': identity_fakes.PROJECT_WITH_PARENT['name'], - 'domain': identity_fakes.PROJECT_WITH_PARENT['domain_id'], - 'parent': identity_fakes.PROJECT['id'], + 'name': self.project.name, + 'domain': self.project.domain_id, + 'parent': self.parent.id, 'description': None, 'enabled': True, } @@ -370,17 +371,19 @@ class TestProjectCreate(TestProject): 'domain_id', 'enabled', 'id', + 'is_domain', 'name', 'parent_id', ) self.assertEqual(columns, collist) datalist = ( - identity_fakes.PROJECT_WITH_PARENT['description'], - identity_fakes.PROJECT_WITH_PARENT['domain_id'], - identity_fakes.PROJECT_WITH_PARENT['enabled'], - identity_fakes.PROJECT_WITH_PARENT['id'], - identity_fakes.PROJECT_WITH_PARENT['name'], - identity_fakes.PROJECT['id'], + self.project.description, + self.project.domain_id, + self.project.enabled, + self.project.id, + self.project.is_domain, + self.project.name, + self.parent.id, ) self.assertEqual(data, datalist) @@ -392,16 +395,16 @@ class TestProjectCreate(TestProject): 'Invalid parent') arglist = [ - '--domain', identity_fakes.domain_name, + '--domain', self.project.domain_id, '--parent', 'invalid', - identity_fakes.project_name, + self.project.name, ] verifylist = [ - ('domain', identity_fakes.domain_name), + ('domain', self.project.domain_id), ('parent', 'invalid'), ('enable', False), ('disable', False), - ('name', identity_fakes.project_name), + ('name', self.project.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -414,15 +417,13 @@ class TestProjectCreate(TestProject): class TestProjectDelete(TestProject): + project = identity_fakes.FakeProject.create_one_project() + def setUp(self): super(TestProjectDelete, self).setUp() # This is the return value for utils.find_resource() - self.projects_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.PROJECT), - loaded=True, - ) + self.projects_mock.get.return_value = self.project self.projects_mock.delete.return_value = None # Get the command object to test @@ -430,44 +431,42 @@ class TestProjectDelete(TestProject): def test_project_delete_no_options(self): arglist = [ - identity_fakes.project_id, + self.project.id, ] verifylist = [ - ('projects', [identity_fakes.project_id]), + ('projects', [self.project.id]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) self.projects_mock.delete.assert_called_with( - identity_fakes.project_id, + self.project.id, ) self.assertIsNone(result) class TestProjectList(TestProject): + domain = identity_fakes.FakeDomain.create_one_domain() + project = identity_fakes.FakeProject.create_one_project( + attrs={'domain_id': domain.id}) + columns = ( 'ID', 'Name', ) datalist = ( ( - identity_fakes.project_id, - identity_fakes.project_name, + project.id, + project.name, ), ) def setUp(self): super(TestProjectList, self).setUp() - self.projects_mock.list.return_value = [ - fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.PROJECT), - loaded=True, - ), - ] + self.projects_mock.list.return_value = [self.project] # Get the command object to test self.cmd = project.ListProject(self.app, None) @@ -504,27 +503,23 @@ class TestProjectList(TestProject): collist = ('ID', 'Name', 'Domain ID', 'Description', 'Enabled') self.assertEqual(collist, columns) datalist = (( - identity_fakes.project_id, - identity_fakes.project_name, - identity_fakes.domain_id, - identity_fakes.project_description, + self.project.id, + self.project.name, + self.project.domain_id, + self.project.description, True, ), ) self.assertEqual(datalist, tuple(data)) def test_project_list_domain(self): arglist = [ - '--domain', identity_fakes.domain_name, + '--domain', self.project.domain_id, ] verifylist = [ - ('domain', identity_fakes.domain_name), + ('domain', self.project.domain_id), ] - self.domains_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.DOMAIN), - loaded=True, - ) + self.domains_mock.get.return_value = self.domain parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -533,17 +528,17 @@ class TestProjectList(TestProject): # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) self.projects_mock.list.assert_called_with( - domain=identity_fakes.domain_id) + domain=self.project.domain_id) self.assertEqual(self.columns, columns) self.assertEqual(self.datalist, tuple(data)) def test_project_list_domain_no_perms(self): arglist = [ - '--domain', identity_fakes.domain_id, + '--domain', self.project.domain_id, ] verifylist = [ - ('domain', identity_fakes.domain_id), + ('domain', self.project.domain_id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) mocker = mock.Mock() @@ -553,42 +548,34 @@ class TestProjectList(TestProject): columns, data = self.cmd.take_action(parsed_args) self.projects_mock.list.assert_called_with( - domain=identity_fakes.domain_id) + domain=self.project.domain_id) self.assertEqual(self.columns, columns) self.assertEqual(self.datalist, tuple(data)) class TestProjectSet(TestProject): + domain = identity_fakes.FakeDomain.create_one_domain() + project = identity_fakes.FakeProject.create_one_project( + attrs={'domain_id': domain.id}) + def setUp(self): super(TestProjectSet, self).setUp() - self.domains_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.DOMAIN), - loaded=True, - ) + self.domains_mock.get.return_value = self.domain - self.projects_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.PROJECT), - loaded=True, - ) - self.projects_mock.update.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.PROJECT), - loaded=True, - ) + self.projects_mock.get.return_value = self.project + self.projects_mock.update.return_value = self.project # Get the command object to test self.cmd = project.SetProject(self.app, None) def test_project_set_no_options(self): arglist = [ - identity_fakes.project_name, + self.project.name, ] verifylist = [ - ('project', identity_fakes.project_name), + ('project', self.project.name), ('enable', False), ('disable', False), ] @@ -601,15 +588,15 @@ class TestProjectSet(TestProject): def test_project_set_name(self): arglist = [ '--name', 'qwerty', - '--domain', identity_fakes.domain_id, - identity_fakes.project_name, + '--domain', self.project.domain_id, + self.project.name, ] verifylist = [ ('name', 'qwerty'), - ('domain', identity_fakes.domain_id), + ('domain', self.project.domain_id), ('enable', False), ('disable', False), - ('project', identity_fakes.project_name), + ('project', self.project.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -622,23 +609,23 @@ class TestProjectSet(TestProject): # ProjectManager.update(project, name=, domain=, description=, # enabled=, **kwargs) self.projects_mock.update.assert_called_with( - identity_fakes.project_id, + self.project.id, **kwargs ) self.assertIsNone(result) def test_project_set_description(self): arglist = [ - '--domain', identity_fakes.domain_id, + '--domain', self.project.domain_id, '--description', 'new desc', - identity_fakes.project_name, + self.project.name, ] verifylist = [ - ('domain', identity_fakes.domain_id), + ('domain', self.project.domain_id), ('description', 'new desc'), ('enable', False), ('disable', False), - ('project', identity_fakes.project_name), + ('project', self.project.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -649,22 +636,22 @@ class TestProjectSet(TestProject): 'description': 'new desc', } self.projects_mock.update.assert_called_with( - identity_fakes.project_id, + self.project.id, **kwargs ) self.assertIsNone(result) def test_project_set_enable(self): arglist = [ - '--domain', identity_fakes.domain_id, + '--domain', self.project.domain_id, '--enable', - identity_fakes.project_name, + self.project.name, ] verifylist = [ - ('domain', identity_fakes.domain_id), + ('domain', self.project.domain_id), ('enable', True), ('disable', False), - ('project', identity_fakes.project_name), + ('project', self.project.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -675,22 +662,22 @@ class TestProjectSet(TestProject): 'enabled': True, } self.projects_mock.update.assert_called_with( - identity_fakes.project_id, + self.project.id, **kwargs ) self.assertIsNone(result) def test_project_set_disable(self): arglist = [ - '--domain', identity_fakes.domain_id, + '--domain', self.project.domain_id, '--disable', - identity_fakes.project_name, + self.project.name, ] verifylist = [ - ('domain', identity_fakes.domain_id), + ('domain', self.project.domain_id), ('enable', False), ('disable', True), - ('project', identity_fakes.project_name), + ('project', self.project.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -701,22 +688,22 @@ class TestProjectSet(TestProject): 'enabled': False, } self.projects_mock.update.assert_called_with( - identity_fakes.project_id, + self.project.id, **kwargs ) self.assertIsNone(result) def test_project_set_property(self): arglist = [ - '--domain', identity_fakes.domain_id, + '--domain', self.project.domain_id, '--property', 'fee=fi', '--property', 'fo=fum', - identity_fakes.project_name, + self.project.name, ] verifylist = [ - ('domain', identity_fakes.domain_id), + ('domain', self.project.domain_id), ('property', {'fee': 'fi', 'fo': 'fum'}), - ('project', identity_fakes.project_name), + ('project', self.project.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -728,7 +715,7 @@ class TestProjectSet(TestProject): 'fo': 'fum', } self.projects_mock.update.assert_called_with( - identity_fakes.project_id, + self.project.id, **kwargs ) self.assertIsNone(result) @@ -736,25 +723,28 @@ class TestProjectSet(TestProject): class TestProjectShow(TestProject): + domain = identity_fakes.FakeDomain.create_one_domain() + def setUp(self): super(TestProjectShow, self).setUp() - self.projects_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.PROJECT), - loaded=True, - ) + self.project = identity_fakes.FakeProject.create_one_project( + attrs={'domain_id': self.domain.id}) # Get the command object to test self.cmd = project.ShowProject(self.app, None) def test_project_show(self): + self.projects_mock.get.side_effect = [Exception("Not found"), + self.project] + self.projects_mock.get.return_value = self.project + arglist = [ - identity_fakes.project_id, + self.project.id, ] verifylist = [ - ('project', identity_fakes.project_id), + ('project', self.project.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -772,38 +762,51 @@ class TestProjectShow(TestProject): # returns a two-part tuple with a tuple of column names and a tuple of # data to be shown. columns, data = self.cmd.take_action(parsed_args) + self.projects_mock.get.assert_called_with( - identity_fakes.project_id, + self.project.id, parents_as_list=False, subtree_as_list=False, ) - collist = ('description', 'domain_id', 'enabled', 'id', 'name') + collist = ( + 'description', + 'domain_id', + 'enabled', + 'id', + 'is_domain', + 'name', + 'parent_id', + ) self.assertEqual(collist, columns) datalist = ( - identity_fakes.project_description, - identity_fakes.domain_id, + self.project.description, + self.project.domain_id, True, - identity_fakes.project_id, - identity_fakes.project_name, + self.project.id, + False, + self.project.name, + self.project.parent_id, ) self.assertEqual(datalist, data) def test_project_show_parents(self): - project = copy.deepcopy(identity_fakes.PROJECT_WITH_GRANDPARENT) - project['parents'] = identity_fakes.grandparents - self.projects_mock.get.return_value = fakes.FakeResource( - None, - project, - loaded=True, + self.project = identity_fakes.FakeProject.create_one_project( + attrs={ + 'parent_id': self.project.parent_id, + 'parents': [{'project': {'id': self.project.parent_id}}] + } ) + self.projects_mock.get.side_effect = [Exception("Not found"), + self.project] + self.projects_mock.get.return_value = self.project arglist = [ - identity_fakes.PROJECT_WITH_GRANDPARENT['id'], + self.project.id, '--parents', ] verifylist = [ - ('project', identity_fakes.PROJECT_WITH_GRANDPARENT['id']), + ('project', self.project.id), ('parents', True), ('children', False), ] @@ -820,7 +823,7 @@ class TestProjectShow(TestProject): columns, data = self.cmd.take_action(parsed_args) self.projects_mock.get.assert_called_with( - identity_fakes.PROJECT_WITH_GRANDPARENT['id'], + self.project.id, parents_as_list=True, subtree_as_list=False, ) @@ -830,37 +833,41 @@ class TestProjectShow(TestProject): 'domain_id', 'enabled', 'id', + 'is_domain', 'name', 'parent_id', 'parents', ) self.assertEqual(columns, collist) datalist = ( - identity_fakes.PROJECT_WITH_GRANDPARENT['description'], - identity_fakes.PROJECT_WITH_GRANDPARENT['domain_id'], - identity_fakes.PROJECT_WITH_GRANDPARENT['enabled'], - identity_fakes.PROJECT_WITH_GRANDPARENT['id'], - identity_fakes.PROJECT_WITH_GRANDPARENT['name'], - identity_fakes.PROJECT_WITH_GRANDPARENT['parent_id'], - identity_fakes.ids_for_parents_and_grandparents, + self.project.description, + self.project.domain_id, + self.project.enabled, + self.project.id, + self.project.is_domain, + self.project.name, + self.project.parent_id, + [self.project.parent_id], ) self.assertEqual(data, datalist) def test_project_show_subtree(self): - project = copy.deepcopy(identity_fakes.PROJECT_WITH_PARENT) - project['subtree'] = identity_fakes.children - self.projects_mock.get.return_value = fakes.FakeResource( - None, - project, - loaded=True, + self.project = identity_fakes.FakeProject.create_one_project( + attrs={ + 'parent_id': self.project.parent_id, + 'subtree': [{'project': {'id': 'children-id'}}] + } ) + self.projects_mock.get.side_effect = [Exception("Not found"), + self.project] + self.projects_mock.get.return_value = self.project arglist = [ - identity_fakes.PROJECT_WITH_PARENT['id'], + self.project.id, '--children', ] verifylist = [ - ('project', identity_fakes.PROJECT_WITH_PARENT['id']), + ('project', self.project.id), ('parents', False), ('children', True), ] @@ -877,7 +884,7 @@ class TestProjectShow(TestProject): columns, data = self.cmd.take_action(parsed_args) self.projects_mock.get.assert_called_with( - identity_fakes.PROJECT_WITH_PARENT['id'], + self.project.id, parents_as_list=False, subtree_as_list=True, ) @@ -887,39 +894,43 @@ class TestProjectShow(TestProject): 'domain_id', 'enabled', 'id', + 'is_domain', 'name', 'parent_id', 'subtree', ) self.assertEqual(columns, collist) datalist = ( - identity_fakes.PROJECT_WITH_PARENT['description'], - identity_fakes.PROJECT_WITH_PARENT['domain_id'], - identity_fakes.PROJECT_WITH_PARENT['enabled'], - identity_fakes.PROJECT_WITH_PARENT['id'], - identity_fakes.PROJECT_WITH_PARENT['name'], - identity_fakes.PROJECT_WITH_PARENT['parent_id'], - identity_fakes.ids_for_children, + self.project.description, + self.project.domain_id, + self.project.enabled, + self.project.id, + self.project.is_domain, + self.project.name, + self.project.parent_id, + ['children-id'], ) self.assertEqual(data, datalist) def test_project_show_parents_and_children(self): - project = copy.deepcopy(identity_fakes.PROJECT_WITH_PARENT) - project['subtree'] = identity_fakes.children - project['parents'] = identity_fakes.parents - self.projects_mock.get.return_value = fakes.FakeResource( - None, - project, - loaded=True, + self.project = identity_fakes.FakeProject.create_one_project( + attrs={ + 'parent_id': self.project.parent_id, + 'parents': [{'project': {'id': self.project.parent_id}}], + 'subtree': [{'project': {'id': 'children-id'}}] + } ) + self.projects_mock.get.side_effect = [Exception("Not found"), + self.project] + self.projects_mock.get.return_value = self.project arglist = [ - identity_fakes.PROJECT_WITH_PARENT['id'], + self.project.id, '--parents', '--children', ] verifylist = [ - ('project', identity_fakes.PROJECT_WITH_PARENT['id']), + ('project', self.project.id), ('parents', True), ('children', True), ] @@ -936,7 +947,7 @@ class TestProjectShow(TestProject): columns, data = self.cmd.take_action(parsed_args) self.projects_mock.get.assert_called_with( - identity_fakes.PROJECT_WITH_PARENT['id'], + self.project.id, parents_as_list=True, subtree_as_list=True, ) @@ -946,6 +957,7 @@ class TestProjectShow(TestProject): 'domain_id', 'enabled', 'id', + 'is_domain', 'name', 'parent_id', 'parents', @@ -953,13 +965,14 @@ class TestProjectShow(TestProject): ) self.assertEqual(columns, collist) datalist = ( - identity_fakes.PROJECT_WITH_PARENT['description'], - identity_fakes.PROJECT_WITH_PARENT['domain_id'], - identity_fakes.PROJECT_WITH_PARENT['enabled'], - identity_fakes.PROJECT_WITH_PARENT['id'], - identity_fakes.PROJECT_WITH_PARENT['name'], - identity_fakes.PROJECT_WITH_PARENT['parent_id'], - identity_fakes.ids_for_parents, - identity_fakes.ids_for_children, + self.project.description, + self.project.domain_id, + self.project.enabled, + self.project.id, + self.project.is_domain, + self.project.name, + self.project.parent_id, + [self.project.parent_id], + ['children-id'], ) self.assertEqual(data, datalist) diff --git a/openstackclient/tests/identity/v3/test_protocol.py b/openstackclient/tests/identity/v3/test_protocol.py index 238b0ff8..f718b27b 100644 --- a/openstackclient/tests/identity/v3/test_protocol.py +++ b/openstackclient/tests/identity/v3/test_protocol.py @@ -88,7 +88,7 @@ class TestProtocolDelete(TestProtocol): identity_fakes.protocol_id ] verifylist = [ - ('federation_protocol', identity_fakes.protocol_id), + ('federation_protocol', [identity_fakes.protocol_id]), ('identity_provider', identity_fakes.idp_id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) diff --git a/openstackclient/tests/identity/v3/test_region.py b/openstackclient/tests/identity/v3/test_region.py index 02dec568..41ee5ce9 100644 --- a/openstackclient/tests/identity/v3/test_region.py +++ b/openstackclient/tests/identity/v3/test_region.py @@ -153,7 +153,7 @@ class TestRegionDelete(TestRegion): identity_fakes.region_id, ] verifylist = [ - ('region', identity_fakes.region_id), + ('region', [identity_fakes.region_id]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -253,7 +253,11 @@ class TestRegionSet(TestRegion): result = self.cmd.take_action(parsed_args) - self.assertNotCalled(self.regions_mock.update) + kwargs = {} + self.regions_mock.update.assert_called_with( + identity_fakes.region_id, + **kwargs + ) self.assertIsNone(result) def test_region_set_description(self): diff --git a/openstackclient/tests/identity/v3/test_service.py b/openstackclient/tests/identity/v3/test_service.py index 1e70383f..a1f85adc 100644 --- a/openstackclient/tests/identity/v3/test_service.py +++ b/openstackclient/tests/identity/v3/test_service.py @@ -200,7 +200,7 @@ class TestServiceDelete(TestService): identity_fakes.service_name, ] verifylist = [ - ('service', identity_fakes.service_name), + ('service', [identity_fakes.service_name]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) diff --git a/openstackclient/tests/identity/v3/test_service_provider.py b/openstackclient/tests/identity/v3/test_service_provider.py index 99ea1f75..f5270d83 100644 --- a/openstackclient/tests/identity/v3/test_service_provider.py +++ b/openstackclient/tests/identity/v3/test_service_provider.py @@ -185,7 +185,7 @@ class TestServiceProviderDelete(TestServiceProvider): service_fakes.sp_id, ] verifylist = [ - ('service_provider', service_fakes.sp_id), + ('service_provider', [service_fakes.sp_id]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -377,8 +377,15 @@ class TestServiceProviderSet(TestServiceProvider): # expect take_action() to return (None, None) as none of --disabled, # --enabled, --description, --service-provider-url, --auth_url option # was set. - self.assertIsNone(columns) - self.assertIsNone(data) + self.assertEqual(self.columns, columns) + datalist = ( + service_fakes.sp_auth_url, + service_fakes.sp_description, + True, + service_fakes.sp_id, + service_fakes.service_provider_url + ) + self.assertEqual(datalist, data) class TestServiceProviderShow(TestServiceProvider): @@ -391,7 +398,10 @@ class TestServiceProviderShow(TestServiceProvider): copy.deepcopy(service_fakes.SERVICE_PROVIDER), loaded=True, ) + self.service_providers_mock.get.side_effect = [Exception("Not found"), + ret] self.service_providers_mock.get.return_value = ret + # Get the command object to test self.cmd = service_provider.ShowServiceProvider(self.app, None) diff --git a/openstackclient/tests/image/v1/test_image.py b/openstackclient/tests/image/v1/test_image.py index 14aa331f..cf08d138 100644 --- a/openstackclient/tests/image/v1/test_image.py +++ b/openstackclient/tests/image/v1/test_image.py @@ -363,6 +363,7 @@ class TestImageList(TestImage): 'Disk Format', 'Container Format', 'Size', + 'Checksum', 'Status', 'Visibility', 'Protected', @@ -378,6 +379,7 @@ class TestImageList(TestImage): '', '', '', + '', 'public', False, image_fakes.image_owner, diff --git a/openstackclient/tests/image/v2/fakes.py b/openstackclient/tests/image/v2/fakes.py index 8e22fbb2..d450dec1 100644 --- a/openstackclient/tests/image/v2/fakes.py +++ b/openstackclient/tests/image/v2/fakes.py @@ -49,13 +49,6 @@ IMAGE_SHOW = copy.copy(IMAGE) IMAGE_SHOW['tags'] = '' IMAGE_SHOW_data = tuple((IMAGE_SHOW[x] for x in sorted(IMAGE_SHOW))) -member_status = 'pending' -MEMBER = { - 'member_id': identity_fakes.project_id, - 'image_id': image_id, - 'status': member_status, -} - # Just enough v2 schema to do some testing IMAGE_schema = { "additionalProperties": { @@ -190,7 +183,7 @@ class FakeImage(object): :param Dictionary attrs: A dictionary with all attrbutes of image - :retrun: + :return: A FakeResource object with id, name, owner, protected, visibility and tags attrs """ @@ -288,3 +281,29 @@ class FakeImage(object): else: data_list.append(getattr(image, x)) return tuple(data_list) + + @staticmethod + def create_one_image_member(attrs=None): + """Create a fake image member. + + :param Dictionary attrs: + A dictionary with all attrbutes of image member + :return: + A FakeResource object with member_id, image_id and so on + """ + attrs = attrs or {} + + # Set default attribute + image_member_info = { + 'member_id': 'member-id-' + uuid.uuid4().hex, + 'image_id': 'image-id-' + uuid.uuid4().hex, + 'status': 'pending', + } + + # Overwrite default attributes if there are some attributes set + image_member_info.update(attrs) + + image_member = fakes.FakeModel( + copy.deepcopy(image_member_info)) + + return image_member diff --git a/openstackclient/tests/image/v2/test_image.py b/openstackclient/tests/image/v2/test_image.py index 592def21..c6b83bc5 100644 --- a/openstackclient/tests/image/v2/test_image.py +++ b/openstackclient/tests/image/v2/test_image.py @@ -347,6 +347,10 @@ class TestImageCreate(TestImage): class TestAddProjectToImage(TestImage): _image = image_fakes.FakeImage.create_one_image() + new_member = image_fakes.FakeImage.create_one_image_member( + attrs={'image_id': _image.id, + 'member_id': identity_fakes.project_id} + ) columns = ( 'image_id', @@ -357,7 +361,7 @@ class TestAddProjectToImage(TestImage): datalist = ( _image.id, identity_fakes.project_id, - image_fakes.member_status + new_member.status ) def setUp(self): @@ -367,11 +371,7 @@ class TestAddProjectToImage(TestImage): self.images_mock.get.return_value = self._image # Update the image_id in the MEMBER dict - self.new_member = copy.deepcopy(image_fakes.MEMBER) - self.new_member['image_id'] = self._image.id - self.image_members_mock.create.return_value = fakes.FakeModel( - self.new_member, - ) + self.image_members_mock.create.return_value = self.new_member self.project_mock.get.return_value = fakes.FakeResource( None, copy.deepcopy(identity_fakes.PROJECT), @@ -643,6 +643,7 @@ class TestImageList(TestImage): 'Disk Format', 'Container Format', 'Size', + 'Checksum', 'Status', 'Visibility', 'Protected', @@ -658,6 +659,7 @@ class TestImageList(TestImage): '', '', '', + '', self._image.visibility, self._image.protected, self._image.owner, diff --git a/openstackclient/tests/network/v2/test_address_scope.py b/openstackclient/tests/network/v2/test_address_scope.py index 722371f9..16e74f46 100644 --- a/openstackclient/tests/network/v2/test_address_scope.py +++ b/openstackclient/tests/network/v2/test_address_scope.py @@ -11,7 +11,6 @@ # under the License. # -import copy import mock from mock import call @@ -35,11 +34,13 @@ class TestAddressScope(network_fakes.TestNetworkV2): class TestCreateAddressScope(TestAddressScope): + project = identity_fakes_v3.FakeProject.create_one_project() + domain = identity_fakes_v3.FakeDomain.create_one_domain() # The new address scope created. new_address_scope = ( network_fakes.FakeAddressScope.create_one_address_scope( attrs={ - 'tenant_id': identity_fakes_v3.project_id, + 'tenant_id': project.id, } )) columns = ( @@ -75,19 +76,11 @@ class TestCreateAddressScope(TestAddressScope): # Get a shortcut to the ProjectManager Mock self.projects_mock = self.identity.projects - self.projects_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes_v3.PROJECT), - loaded=True, - ) + self.projects_mock.get.return_value = self.project # Get a shortcut to the DomainManager Mock self.domains_mock = self.identity.domains - self.domains_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes_v3.DOMAIN), - loaded=True, - ) + self.domains_mock.get.return_value = self.domain def test_create_no_options(self): arglist = [] @@ -121,15 +114,15 @@ class TestCreateAddressScope(TestAddressScope): arglist = [ '--ip-version', str(self.new_address_scope.ip_version), '--share', - '--project', identity_fakes_v3.project_name, - '--project-domain', identity_fakes_v3.domain_name, + '--project', self.project.name, + '--project-domain', self.domain.name, self.new_address_scope.name, ] verifylist = [ ('ip_version', self.new_address_scope.ip_version), ('share', True), - ('project', identity_fakes_v3.project_name), - ('project_domain', identity_fakes_v3.domain_name), + ('project', self.project.name), + ('project_domain', self.domain.name), ('name', self.new_address_scope.name), ] @@ -139,7 +132,7 @@ class TestCreateAddressScope(TestAddressScope): self.network.create_address_scope.assert_called_once_with(**{ 'ip_version': self.new_address_scope.ip_version, 'shared': True, - 'tenant_id': identity_fakes_v3.project_id, + 'tenant_id': self.project.id, 'name': self.new_address_scope.name, }) self.assertEqual(self.columns, columns) diff --git a/openstackclient/tests/network/v2/test_floating_ip.py b/openstackclient/tests/network/v2/test_floating_ip.py index 5cd5279a..234fe446 100644 --- a/openstackclient/tests/network/v2/test_floating_ip.py +++ b/openstackclient/tests/network/v2/test_floating_ip.py @@ -211,7 +211,7 @@ class TestDeleteFloatingIPNetwork(TestFloatingIPNetwork): 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.assertEqual('1 of 2 floating_ips failed to delete.', str(e)) self.network.find_ip.assert_any_call( self.floating_ips[0].id, ignore_missing=False) @@ -462,7 +462,7 @@ class TestDeleteFloatingIPCompute(TestFloatingIPCompute): 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.assertEqual('1 of 2 floating_ips failed to delete.', str(e)) self.compute.floating_ips.get.assert_any_call( self.floating_ips[0].id) diff --git a/openstackclient/tests/network/v2/test_floating_ip_pool.py b/openstackclient/tests/network/v2/test_floating_ip_pool.py new file mode 100644 index 00000000..22d20d20 --- /dev/null +++ b/openstackclient/tests/network/v2/test_floating_ip_pool.py @@ -0,0 +1,97 @@ +# 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. +# + +from osc_lib import exceptions + +from openstackclient.network.v2 import floating_ip_pool +from openstackclient.tests.compute.v2 import fakes as compute_fakes +from openstackclient.tests.network.v2 import fakes as network_fakes + + +# Tests for Network API v2 +# +class TestFloatingIPPoolNetwork(network_fakes.TestNetworkV2): + + def setUp(self): + super(TestFloatingIPPoolNetwork, self).setUp() + + # Get a shortcut to the network client + self.network = self.app.client_manager.network + + +class TestListFloatingIPPoolNetwork(TestFloatingIPPoolNetwork): + + def setUp(self): + super(TestListFloatingIPPoolNetwork, self).setUp() + + # Get the command object to test + self.cmd = floating_ip_pool.ListFloatingIPPool(self.app, + self.namespace) + + def test_floating_ip_list(self): + arglist = [] + verifylist = [] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.assertRaises(exceptions.CommandError, self.cmd.take_action, + parsed_args) + + +# Tests for Compute network +# +class TestFloatingIPPoolCompute(compute_fakes.TestComputev2): + + def setUp(self): + super(TestFloatingIPPoolCompute, self).setUp() + + # Get a shortcut to the compute client + self.compute = self.app.client_manager.compute + + +class TestListFloatingIPPoolCompute(TestFloatingIPPoolCompute): + + # The floating ip pools to list up + floating_ip_pools = \ + compute_fakes.FakeFloatingIPPool.create_floating_ip_pools(count=3) + + columns = ( + 'Name', + ) + + data = [] + for pool in floating_ip_pools: + data.append(( + pool.name, + )) + + def setUp(self): + super(TestListFloatingIPPoolCompute, self).setUp() + + self.app.client_manager.network_endpoint_enabled = False + + self.compute.floating_ip_pools.list.return_value = \ + self.floating_ip_pools + + # Get the command object to test + self.cmd = floating_ip_pool.ListFloatingIPPool(self.app, None) + + def test_floating_ip_list(self): + arglist = [] + verifylist = [] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.compute.floating_ip_pools.list.assert_called_once_with() + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) diff --git a/openstackclient/tests/network/v2/test_ip_availability.py b/openstackclient/tests/network/v2/test_ip_availability.py index c6ec2b0b..21d44d07 100644 --- a/openstackclient/tests/network/v2/test_ip_availability.py +++ b/openstackclient/tests/network/v2/test_ip_availability.py @@ -11,7 +11,6 @@ # under the License. # -import copy import mock from osc_lib import utils as common_utils @@ -41,11 +40,8 @@ class TestIPAvailability(network_fakes.TestNetworkV2): # Get a shortcut to the ProjectManager Mock self.projects_mock = self.identity.projects - self.projects_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.PROJECT), - loaded=True, - ) + self.project = identity_fakes.FakeProject.create_one_project() + self.projects_mock.get.return_value = self.project class TestListIPAvailability(TestIPAvailability): @@ -109,16 +105,16 @@ class TestListIPAvailability(TestIPAvailability): def test_list_project(self): arglist = [ - '--project', identity_fakes.project_name + '--project', self.project.name ] verifylist = [ - ('project', identity_fakes.project_name) + ('project', self.project.name) ] 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': self.project.id, 'ip_version': 4} self.network.network_ip_availabilities.assert_called_once_with( diff --git a/openstackclient/tests/network/v2/test_network.py b/openstackclient/tests/network/v2/test_network.py index ffe6c973..aa016403 100644 --- a/openstackclient/tests/network/v2/test_network.py +++ b/openstackclient/tests/network/v2/test_network.py @@ -11,7 +11,6 @@ # under the License. # -import copy import mock from mock import call @@ -40,10 +39,12 @@ class TestNetwork(network_fakes.TestNetworkV2): class TestCreateNetworkIdentityV3(TestNetwork): + project = identity_fakes_v3.FakeProject.create_one_project() + domain = identity_fakes_v3.FakeDomain.create_one_domain() # The new network created. _network = network_fakes.FakeNetwork.create_one_network( attrs={ - 'tenant_id': identity_fakes_v3.project_id, + 'tenant_id': project.id, 'availability_zone_hints': ["nova"], } ) @@ -98,19 +99,11 @@ class TestCreateNetworkIdentityV3(TestNetwork): # Get a shortcut to the ProjectManager Mock self.projects_mock = self.identity.projects - self.projects_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes_v3.PROJECT), - loaded=True, - ) + self.projects_mock.get.return_value = self.project # Get a shortcut to the DomainManager Mock self.domains_mock = self.identity.domains - self.domains_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes_v3.DOMAIN), - loaded=True, - ) + self.domains_mock.get.return_value = self.domain def test_create_no_options(self): arglist = [] @@ -145,8 +138,8 @@ class TestCreateNetworkIdentityV3(TestNetwork): arglist = [ "--disable", "--share", - "--project", identity_fakes_v3.project_name, - "--project-domain", identity_fakes_v3.domain_name, + "--project", self.project.name, + "--project-domain", self.domain.name, "--availability-zone-hint", "nova", "--external", "--default", "--provider-network-type", "vlan", @@ -159,8 +152,8 @@ class TestCreateNetworkIdentityV3(TestNetwork): verifylist = [ ('disable', True), ('share', True), - ('project', identity_fakes_v3.project_name), - ('project_domain', identity_fakes_v3.domain_name), + ('project', self.project.name), + ('project_domain', self.domain.name), ('availability_zone_hints', ["nova"]), ('external', True), ('default', True), @@ -180,7 +173,7 @@ class TestCreateNetworkIdentityV3(TestNetwork): 'availability_zone_hints': ["nova"], 'name': self._network.name, 'shared': True, - 'tenant_id': identity_fakes_v3.project_id, + 'tenant_id': self.project.id, 'is_default': True, 'router:external': True, 'provider:network_type': 'vlan', @@ -222,9 +215,10 @@ class TestCreateNetworkIdentityV3(TestNetwork): class TestCreateNetworkIdentityV2(TestNetwork): + project = identity_fakes_v2.FakeProject.create_one_project() # The new network created. _network = network_fakes.FakeNetwork.create_one_network( - attrs={'tenant_id': identity_fakes_v2.project_id} + attrs={'tenant_id': project.id} ) columns = ( @@ -277,24 +271,20 @@ class TestCreateNetworkIdentityV2(TestNetwork): # Get a shortcut to the ProjectManager Mock self.projects_mock = self.identity.tenants - self.projects_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes_v2.PROJECT), - loaded=True, - ) + self.projects_mock.get.return_value = self.project # There is no DomainManager Mock in fake identity v2. def test_create_with_project_identityv2(self): arglist = [ - "--project", identity_fakes_v2.project_name, + "--project", self.project.name, self._network.name, ] verifylist = [ ('enable', True), ('share', None), ('name', self._network.name), - ('project', identity_fakes_v2.project_name), + ('project', self.project.name), ('external', False), ] @@ -304,22 +294,22 @@ class TestCreateNetworkIdentityV2(TestNetwork): self.network.create_network.assert_called_once_with(**{ 'admin_state_up': True, 'name': self._network.name, - 'tenant_id': identity_fakes_v2.project_id, + 'tenant_id': self.project.id, }) self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) def test_create_with_domain_identityv2(self): arglist = [ - "--project", identity_fakes_v3.project_name, - "--project-domain", identity_fakes_v3.domain_name, + "--project", self.project.name, + "--project-domain", "domain-name", self._network.name, ] verifylist = [ ('enable', True), ('share', None), - ('project', identity_fakes_v3.project_name), - ('project_domain', identity_fakes_v3.domain_name), + ('project', self.project.name), + ('project_domain', "domain-name"), ('name', self._network.name), ('external', False), ] diff --git a/openstackclient/tests/network/v2/test_port.py b/openstackclient/tests/network/v2/test_port.py index a998585e..a1cecec8 100644 --- a/openstackclient/tests/network/v2/test_port.py +++ b/openstackclient/tests/network/v2/test_port.py @@ -369,6 +369,47 @@ class TestListPort(TestPort): self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) + def test_port_list_device_owner_opt(self): + arglist = [ + '--device-owner', self._ports[0].device_owner, + ] + + verifylist = [ + ('device_owner', self._ports[0].device_owner) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.network.ports.assert_called_once_with(**{ + 'device_owner': self._ports[0].device_owner + }) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_port_list_all_opt(self): + arglist = [ + '--device-owner', self._ports[0].device_owner, + '--router', 'fake-router-name', + ] + + verifylist = [ + ('device_owner', self._ports[0].device_owner), + ('router', 'fake-router-name') + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.network.ports.assert_called_once_with(**{ + 'device_owner': self._ports[0].device_owner, + 'device_id': 'fake-router-id' + }) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + class TestSetPort(TestPort): @@ -575,3 +616,81 @@ class TestShowPort(TestPort): ref_columns, ref_data = self._get_common_cols_data(self._port) self.assertEqual(ref_columns, columns) self.assertEqual(ref_data, data) + + +class TestUnsetPort(TestPort): + + def setUp(self): + super(TestUnsetPort, self).setUp() + self._testport = network_fakes.FakePort.create_one_port( + {'fixed_ips': [{'subnet_id': '042eb10a-3a18-4658-ab-cf47c8d03152', + 'ip_address': '0.0.0.1'}, + {'subnet_id': '042eb10a-3a18-4658-ab-cf47c8d03152', + 'ip_address': '1.0.0.0'}], + 'binding:profile': {'batman': 'Joker', 'Superman': 'LexLuthor'}}) + self.fake_subnet = network_fakes.FakeSubnet.create_one_subnet( + {'id': '042eb10a-3a18-4658-ab-cf47c8d03152'}) + self.network.find_subnet = mock.Mock(return_value=self.fake_subnet) + self.network.find_port = mock.Mock(return_value=self._testport) + self.network.update_port = mock.Mock(return_value=None) + # Get the command object to test + self.cmd = port.UnsetPort(self.app, self.namespace) + + def test_unset_port_parameters(self): + arglist = [ + '--fixed-ip', + 'subnet=042eb10a-3a18-4658-ab-cf47c8d03152,ip-address=1.0.0.0', + '--binding-profile', 'Superman', + self._testport.name, + ] + verifylist = [ + ('fixed_ip', [{ + 'subnet': '042eb10a-3a18-4658-ab-cf47c8d03152', + 'ip-address': '1.0.0.0'}]), + ('binding_profile', ['Superman']), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + attrs = { + 'fixed_ips': [{ + 'subnet_id': '042eb10a-3a18-4658-ab-cf47c8d03152', + 'ip_address': '0.0.0.1'}], + 'binding:profile': {'batman': 'Joker'} + } + self.network.update_port.assert_called_once_with( + self._testport, **attrs) + self.assertIsNone(result) + + def test_unset_port_fixed_ip_not_existent(self): + arglist = [ + '--fixed-ip', 'ip-address=1.0.0.1', + '--binding-profile', 'Superman', + self._testport.name, + ] + verifylist = [ + ('fixed_ip', [{'ip-address': '1.0.0.1'}]), + ('binding_profile', ['Superman']), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.assertRaises(exceptions.CommandError, + self.cmd.take_action, + parsed_args) + + def test_unset_port_binding_profile_not_existent(self): + arglist = [ + '--fixed-ip', 'ip-address=1.0.0.0', + '--binding-profile', 'Neo', + self._testport.name, + ] + verifylist = [ + ('fixed_ip', [{'ip-address': '1.0.0.0'}]), + ('binding_profile', ['Neo']), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.assertRaises(exceptions.CommandError, + self.cmd.take_action, + parsed_args) diff --git a/openstackclient/tests/network/v2/test_security_group.py b/openstackclient/tests/network/v2/test_security_group.py index b0c14985..cea64897 100644 --- a/openstackclient/tests/network/v2/test_security_group.py +++ b/openstackclient/tests/network/v2/test_security_group.py @@ -11,7 +11,6 @@ # under the License. # -import copy import mock from mock import call @@ -45,6 +44,8 @@ class TestSecurityGroupCompute(compute_fakes.TestComputev2): class TestCreateSecurityGroupNetwork(TestSecurityGroupNetwork): + project = identity_fakes.FakeProject.create_one_project() + domain = identity_fakes.FakeDomain.create_one_domain() # The security group to be created. _security_group = \ network_fakes.FakeSecurityGroup.create_one_security_group() @@ -81,19 +82,11 @@ class TestCreateSecurityGroupNetwork(TestSecurityGroupNetwork): # Get a shortcut to the ProjectManager Mock self.projects_mock = self.identity.projects - self.projects_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.PROJECT), - loaded=True, - ) + self.projects_mock.get.return_value = self.project # Get a shortcut to the DomainManager Mock self.domains_mock = self.identity.domains - self.domains_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.DOMAIN), - loaded=True, - ) + self.domains_mock.get.return_value = self.domain # Get the command object to test self.cmd = security_group.CreateSecurityGroup(self.app, self.namespace) @@ -123,15 +116,15 @@ class TestCreateSecurityGroupNetwork(TestSecurityGroupNetwork): def test_create_all_options(self): arglist = [ '--description', self._security_group.description, - '--project', identity_fakes.project_name, - '--project-domain', identity_fakes.domain_name, + '--project', self.project.name, + '--project-domain', self.domain.name, self._security_group.name, ] verifylist = [ ('description', self._security_group.description), ('name', self._security_group.name), - ('project', identity_fakes.project_name), - ('project_domain', identity_fakes.domain_name), + ('project', self.project.name), + ('project_domain', self.domain.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -140,7 +133,7 @@ class TestCreateSecurityGroupNetwork(TestSecurityGroupNetwork): self.network.create_security_group.assert_called_once_with(**{ 'description': self._security_group.description, 'name': self._security_group.name, - 'tenant_id': identity_fakes.project_id, + 'tenant_id': self.project.id, }) self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) @@ -148,6 +141,8 @@ class TestCreateSecurityGroupNetwork(TestSecurityGroupNetwork): class TestCreateSecurityGroupCompute(TestSecurityGroupCompute): + project = identity_fakes.FakeProject.create_one_project() + domain = identity_fakes.FakeDomain.create_one_domain() # The security group to be shown. _security_group = \ compute_fakes.FakeSecurityGroup.create_one_security_group() @@ -184,8 +179,8 @@ class TestCreateSecurityGroupCompute(TestSecurityGroupCompute): def test_create_network_options(self): arglist = [ - '--project', identity_fakes.project_name, - '--project-domain', identity_fakes.domain_name, + '--project', self.project.name, + '--project-domain', self.domain.name, self._security_group.name, ] self.assertRaises(tests_utils.ParserException, @@ -301,7 +296,7 @@ class TestDeleteSecurityGroupNetwork(TestSecurityGroupNetwork): 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.assertEqual('1 of 2 groups failed to delete.', str(e)) self.network.find_security_group.assert_any_call( self._security_groups[0].name, ignore_missing=False) @@ -389,7 +384,7 @@ class TestDeleteSecurityGroupCompute(TestSecurityGroupCompute): 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.assertEqual('1 of 2 groups failed to delete.', str(e)) self.compute.security_groups.get.assert_any_call( self._security_groups[0].id) diff --git a/openstackclient/tests/network/v2/test_security_group_rule.py b/openstackclient/tests/network/v2/test_security_group_rule.py index b2862679..34d35629 100644 --- a/openstackclient/tests/network/v2/test_security_group_rule.py +++ b/openstackclient/tests/network/v2/test_security_group_rule.py @@ -46,6 +46,8 @@ class TestSecurityGroupRuleCompute(compute_fakes.TestComputev2): class TestCreateSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): + project = identity_fakes.FakeProject.create_one_project() + domain = identity_fakes.FakeDomain.create_one_domain() # The security group rule to be created. _security_group_rule = None @@ -103,19 +105,11 @@ class TestCreateSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): # Get a shortcut to the ProjectManager Mock self.projects_mock = self.identity.projects - self.projects_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.PROJECT), - loaded=True, - ) + self.projects_mock.get.return_value = self.project # Get a shortcut to the DomainManager Mock self.domains_mock = self.identity.domains - self.domains_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.DOMAIN), - loaded=True, - ) + self.domains_mock.get.return_value = self.domain # Get the command object to test self.cmd = security_group_rule.CreateSecurityGroupRule( @@ -306,8 +300,8 @@ class TestCreateSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): '--dst-port', str(self._security_group_rule.port_range_min), '--egress', '--ethertype', self._security_group_rule.ethertype, - '--project', identity_fakes.project_name, - '--project-domain', identity_fakes.domain_name, + '--project', self.project.name, + '--project-domain', self.domain.name, '--protocol', self._security_group_rule.protocol, self._security_group.id, ] @@ -316,8 +310,8 @@ class TestCreateSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): self._security_group_rule.port_range_max)), ('egress', True), ('ethertype', self._security_group_rule.ethertype), - ('project', identity_fakes.project_name), - ('project_domain', identity_fakes.domain_name), + ('project', self.project.name), + ('project_domain', self.domain.name), ('protocol', self._security_group_rule.protocol), ('group', self._security_group.id), ] @@ -332,7 +326,7 @@ class TestCreateSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): 'port_range_min': self._security_group_rule.port_range_min, 'protocol': self._security_group_rule.protocol, 'security_group_id': self._security_group.id, - 'tenant_id': identity_fakes.project_id, + 'tenant_id': self.project.id, }) self.assertEqual(self.expected_columns, columns) self.assertEqual(self.expected_data, data) @@ -470,6 +464,8 @@ class TestCreateSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): class TestCreateSecurityGroupRuleCompute(TestSecurityGroupRuleCompute): + project = identity_fakes.FakeProject.create_one_project() + domain = identity_fakes.FakeDomain.create_one_domain() # The security group rule to be created. _security_group_rule = None @@ -534,8 +530,8 @@ class TestCreateSecurityGroupRuleCompute(TestSecurityGroupRuleCompute): '--ethertype', 'IPv4', '--icmp-type', '3', '--icmp-code', '11', - '--project', identity_fakes.project_name, - '--project-domain', identity_fakes.domain_name, + '--project', self.project.name, + '--project-domain', self.domain.name, self._security_group.id, ] self.assertRaises(tests_utils.ParserException, @@ -743,7 +739,7 @@ class TestDeleteSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): 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.assertEqual('1 of 2 rules failed to delete.', str(e)) self.network.find_security_group_rule.assert_any_call( self._security_group_rules[0].id, ignore_missing=False) @@ -823,7 +819,7 @@ class TestDeleteSecurityGroupRuleCompute(TestSecurityGroupRuleCompute): 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.assertEqual('1 of 2 rules failed to delete.', str(e)) self.compute.security_group_rules.delete.assert_any_call( self._security_group_rules[0].id) diff --git a/openstackclient/tests/network/v2/test_subnet.py b/openstackclient/tests/network/v2/test_subnet.py index 99b558c0..e24b49e8 100644 --- a/openstackclient/tests/network/v2/test_subnet.py +++ b/openstackclient/tests/network/v2/test_subnet.py @@ -11,7 +11,6 @@ # under the License. # -import copy import mock from mock import call @@ -36,10 +35,12 @@ class TestSubnet(network_fakes.TestNetworkV2): class TestCreateSubnet(TestSubnet): + project = identity_fakes_v3.FakeProject.create_one_project() + domain = identity_fakes_v3.FakeDomain.create_one_domain() # An IPv4 subnet to be created with mostly default values _subnet = network_fakes.FakeSubnet.create_one_subnet( attrs={ - 'tenant_id': identity_fakes_v3.project_id, + 'tenant_id': project.id, } ) @@ -49,7 +50,7 @@ class TestCreateSubnet(TestSubnet): # An IPv4 subnet to be created using a specific subnet pool _subnet_from_pool = network_fakes.FakeSubnet.create_one_subnet( attrs={ - 'tenant_id': identity_fakes_v3.project_id, + 'tenant_id': project.id, 'subnetpool_id': _subnet_pool.id, 'dns_nameservers': ['8.8.8.8', '8.8.4.4'], @@ -63,7 +64,7 @@ class TestCreateSubnet(TestSubnet): # An IPv6 subnet to be created with most options specified _subnet_ipv6 = network_fakes.FakeSubnet.create_one_subnet( attrs={ - 'tenant_id': identity_fakes_v3.project_id, + 'tenant_id': project.id, 'cidr': 'fe80:0:0:a00a::/64', 'enable_dhcp': True, 'dns_nameservers': ['fe80:27ff:a00a:f00f::ffff', @@ -187,19 +188,11 @@ class TestCreateSubnet(TestSubnet): # Get a shortcut to the ProjectManager Mock self.projects_mock = self.identity.projects - self.projects_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes_v3.PROJECT), - loaded=True, - ) + self.projects_mock.get.return_value = self.project # Get a shortcut to the DomainManager Mock self.domains_mock = self.identity.domains - self.domains_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes_v3.DOMAIN), - loaded=True, - ) + self.domains_mock.get.return_value = self.domain # Mock SDK calls for all tests. self.network.find_network = mock.Mock(return_value=self._network) @@ -243,7 +236,6 @@ class TestCreateSubnet(TestSubnet): self.network.create_subnet.assert_called_once_with(**{ 'cidr': self._subnet.cidr, - 'enable_dhcp': self._subnet.enable_dhcp, 'ip_version': self._subnet.ip_version, 'name': self._subnet.name, 'network_id': self._subnet.network_id, @@ -417,7 +409,6 @@ class TestCreateSubnet(TestSubnet): self.network.create_subnet.assert_called_once_with(**{ 'cidr': self._subnet.cidr, - 'enable_dhcp': self._subnet.enable_dhcp, 'ip_version': self._subnet.ip_version, 'name': self._subnet.name, 'network_id': self._subnet.network_id, @@ -767,3 +758,109 @@ class TestShowSubnet(TestSubnet): self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) + + +class TestUnsetSubnet(TestSubnet): + + def setUp(self): + super(TestUnsetSubnet, self).setUp() + self._testsubnet = network_fakes.FakeSubnet.create_one_subnet( + {'dns_nameservers': ['8.8.8.8', + '8.8.8.4'], + 'host_routes': [{'destination': '10.20.20.0/24', + 'nexthop': '10.20.20.1'}, + {'destination': '10.30.30.30/24', + 'nexthop': '10.30.30.1'}], + 'allocation_pools': [{'start': '8.8.8.100', + 'end': '8.8.8.150'}, + {'start': '8.8.8.160', + 'end': '8.8.8.170'}], }) + self.network.find_subnet = mock.Mock(return_value=self._testsubnet) + self.network.update_subnet = mock.Mock(return_value=None) + # Get the command object to test + self.cmd = subnet_v2.UnsetSubnet(self.app, self.namespace) + + def test_unset_subnet_params(self): + arglist = [ + '--dns-nameserver', '8.8.8.8', + '--host-route', 'destination=10.30.30.30/24,gateway=10.30.30.1', + '--allocation-pool', 'start=8.8.8.100,end=8.8.8.150', + self._testsubnet.name, + ] + verifylist = [ + ('dns_nameservers', ['8.8.8.8']), + ('host_routes', [{ + "destination": "10.30.30.30/24", "gateway": "10.30.30.1"}]), + ('allocation_pools', [{ + 'start': '8.8.8.100', 'end': '8.8.8.150'}]), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + attrs = { + 'dns_nameservers': ['8.8.8.4'], + 'host_routes': [{ + "destination": "10.20.20.0/24", "nexthop": "10.20.20.1"}], + 'allocation_pools': [{'start': '8.8.8.160', 'end': '8.8.8.170'}], + } + self.network.update_subnet.assert_called_once_with( + self._testsubnet, **attrs) + self.assertIsNone(result) + + def test_unset_subnet_wrong_host_routes(self): + arglist = [ + '--dns-nameserver', '8.8.8.8', + '--host-route', 'destination=10.30.30.30/24,gateway=10.30.30.2', + '--allocation-pool', 'start=8.8.8.100,end=8.8.8.150', + self._testsubnet.name, + ] + verifylist = [ + ('dns_nameservers', ['8.8.8.8']), + ('host_routes', [{ + "destination": "10.30.30.30/24", "gateway": "10.30.30.2"}]), + ('allocation_pools', [{ + 'start': '8.8.8.100', 'end': '8.8.8.150'}]), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.assertRaises(exceptions.CommandError, + self.cmd.take_action, parsed_args) + + def test_unset_subnet_wrong_allocation_pool(self): + arglist = [ + '--dns-nameserver', '8.8.8.8', + '--host-route', 'destination=10.30.30.30/24,gateway=10.30.30.1', + '--allocation-pool', 'start=8.8.8.100,end=8.8.8.156', + self._testsubnet.name, + ] + verifylist = [ + ('dns_nameservers', ['8.8.8.8']), + ('host_routes', [{ + "destination": "10.30.30.30/24", "gateway": "10.30.30.1"}]), + ('allocation_pools', [{ + 'start': '8.8.8.100', 'end': '8.8.8.156'}]), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.assertRaises(exceptions.CommandError, + self.cmd.take_action, parsed_args) + + def test_unset_subnet_wrong_dns_nameservers(self): + arglist = [ + '--dns-nameserver', '8.8.8.1', + '--host-route', 'destination=10.30.30.30/24,gateway=10.30.30.1', + '--allocation-pool', 'start=8.8.8.100,end=8.8.8.150', + self._testsubnet.name, + ] + verifylist = [ + ('dns_nameservers', ['8.8.8.1']), + ('host_routes', [{ + "destination": "10.30.30.30/24", "gateway": "10.30.30.1"}]), + ('allocation_pools', [{ + 'start': '8.8.8.100', 'end': '8.8.8.150'}]), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.assertRaises(exceptions.CommandError, + self.cmd.take_action, parsed_args) diff --git a/openstackclient/tests/network/v2/test_subnet_pool.py b/openstackclient/tests/network/v2/test_subnet_pool.py index 7a96b30f..8269af0b 100644 --- a/openstackclient/tests/network/v2/test_subnet_pool.py +++ b/openstackclient/tests/network/v2/test_subnet_pool.py @@ -12,7 +12,6 @@ # import argparse -import copy import mock from mock import call @@ -37,6 +36,8 @@ class TestSubnetPool(network_fakes.TestNetworkV2): class TestCreateSubnetPool(TestSubnetPool): + project = identity_fakes_v3.FakeProject.create_one_project() + domain = identity_fakes_v3.FakeDomain.create_one_domain() # The new subnet pool to create. _subnet_pool = network_fakes.FakeSubnetPool.create_one_subnet_pool() @@ -93,19 +94,11 @@ class TestCreateSubnetPool(TestSubnetPool): # Get a shortcut to the ProjectManager Mock self.projects_mock = self.identity.projects - self.projects_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes_v3.PROJECT), - loaded=True, - ) + self.projects_mock.get.return_value = self.project # Get a shortcut to the DomainManager Mock self.domains_mock = self.identity.domains - self.domains_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes_v3.DOMAIN), - loaded=True, - ) + self.domains_mock.get.return_value = self.domain def test_create_no_options(self): arglist = [] @@ -191,14 +184,14 @@ class TestCreateSubnetPool(TestSubnetPool): def test_create_project_domain(self): arglist = [ '--pool-prefix', '10.0.10.0/24', - "--project", identity_fakes_v3.project_name, - "--project-domain", identity_fakes_v3.domain_name, + "--project", self.project.name, + "--project-domain", self.domain.name, self._subnet_pool.name, ] verifylist = [ ('prefixes', ['10.0.10.0/24']), - ('project', identity_fakes_v3.project_name), - ('project_domain', identity_fakes_v3.domain_name), + ('project', self.project.name), + ('project_domain', self.domain.name), ('name', self._subnet_pool.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -207,7 +200,7 @@ class TestCreateSubnetPool(TestSubnetPool): self.network.create_subnet_pool.assert_called_once_with(**{ 'prefixes': ['10.0.10.0/24'], - 'tenant_id': identity_fakes_v3.project_id, + 'tenant_id': self.project.id, 'name': self._subnet_pool.name, }) self.assertEqual(self.columns, columns) @@ -698,3 +691,42 @@ class TestShowSubnetPool(TestSubnetPool): ) self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) + + +class TestUnsetSubnetPool(TestSubnetPool): + + def setUp(self): + super(TestUnsetSubnetPool, self).setUp() + self._subnetpool = network_fakes.FakeSubnetPool.create_one_subnet_pool( + {'prefixes': ['10.0.10.0/24', '10.1.10.0/24', + '10.2.10.0/24'], }) + self.network.find_subnet_pool = mock.Mock( + return_value=self._subnetpool) + self.network.update_subnet_pool = mock.Mock(return_value=None) + # Get the command object to test + self.cmd = subnet_pool.UnsetSubnetPool(self.app, self.namespace) + + def test_unset_subnet_pool(self): + arglist = [ + '--pool-prefix', '10.0.10.0/24', + '--pool-prefix', '10.1.10.0/24', + self._subnetpool.name, + ] + verifylist = [('prefixes', ['10.0.10.0/24', '10.1.10.0/24'])] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + attrs = {'prefixes': ['10.2.10.0/24']} + self.network.update_subnet_pool.assert_called_once_with( + self._subnetpool, **attrs) + self.assertIsNone(result) + + def test_unset_subnet_pool_prefix_not_existent(self): + arglist = [ + '--pool-prefix', '10.100.1.1/25', + self._subnetpool.name, + ] + verifylist = [('prefixes', ['10.100.1.1/25'])] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.assertRaises(exceptions.CommandError, + self.cmd.take_action, + parsed_args) diff --git a/openstackclient/tests/volume/test_find_resource.py b/openstackclient/tests/volume/test_find_resource.py index 227d6ca7..982b02f0 100644 --- a/openstackclient/tests/volume/test_find_resource.py +++ b/openstackclient/tests/volume/test_find_resource.py @@ -44,7 +44,9 @@ class TestFindResourceVolumes(test_utils.TestCase): api.client.get = mock.Mock() resp = mock.Mock() body = {"volumes": [{"id": ID, 'display_name': NAME}]} - api.client.get.side_effect = [Exception("Not found"), (resp, body)] + api.client.get.side_effect = [Exception("Not found"), + Exception("Not found"), + (resp, body)] self.manager = volumes.VolumeManager(api) def test_find(self): @@ -66,7 +68,9 @@ class TestFindResourceVolumeSnapshots(test_utils.TestCase): api.client.get = mock.Mock() resp = mock.Mock() body = {"snapshots": [{"id": ID, 'display_name': NAME}]} - api.client.get.side_effect = [Exception("Not found"), (resp, body)] + api.client.get.side_effect = [Exception("Not found"), + Exception("Not found"), + (resp, body)] self.manager = volume_snapshots.SnapshotManager(api) def test_find(self): diff --git a/openstackclient/tests/volume/v1/fakes.py b/openstackclient/tests/volume/v1/fakes.py index 6c349866..2584d4b1 100644 --- a/openstackclient/tests/volume/v1/fakes.py +++ b/openstackclient/tests/volume/v1/fakes.py @@ -157,7 +157,7 @@ class FakeTransfer(object): :param Dictionary attrs: A dictionary with all attributes of Transfer Request - :retrun: + :return: A FakeResource object with volume_id, name, id. """ # Set default attribute @@ -207,7 +207,7 @@ class FakeService(object): :param Dictionary attrs: A dictionary with all attributes of service - :retrun: + :return: A FakeResource object with host, status, etc. """ # Set default attribute diff --git a/openstackclient/tests/volume/v2/fakes.py b/openstackclient/tests/volume/v2/fakes.py index 1cbbf68a..74e30a41 100644 --- a/openstackclient/tests/volume/v2/fakes.py +++ b/openstackclient/tests/volume/v2/fakes.py @@ -53,7 +53,7 @@ class FakeTransfer(object): :param Dictionary attrs: A dictionary with all attributes of Transfer Request - :retrun: + :return: A FakeResource object with volume_id, name, id. """ # Set default attribute @@ -103,7 +103,7 @@ class FakeService(object): :param Dictionary attrs: A dictionary with all attributes of service - :retrun: + :return: A FakeResource object with host, status, etc. """ # Set default attribute @@ -146,26 +146,6 @@ class FakeService(object): return services - @staticmethod - def get_services(services=None, count=2): - """Get an iterable MagicMock object with a list of faked services. - - If services list is provided, then initialize the Mock object with the - list. Otherwise create one. - - :param List services: - A list of FakeResource objects faking services - :param Integer count: - The number of services to be faked - :return - An iterable Mock object with side_effect set to a list of faked - services - """ - if services is None: - services = FakeService.create_services(count) - - return mock.MagicMock(side_effect=services) - class FakeVolumeClient(object): @@ -223,7 +203,7 @@ class FakeVolume(object): :param Dictionary attrs: A dictionary with all attributes of volume - :retrun: + :return: A FakeResource object with id, name, status, etc. """ attrs = attrs or {} @@ -403,6 +383,7 @@ class FakeBackup(object): "id": 'backup-id-' + uuid.uuid4().hex, "name": 'backup-name-' + uuid.uuid4().hex, "volume_id": 'volume-id-' + uuid.uuid4().hex, + "snapshot_id": 'snapshot-id' + uuid.uuid4().hex, "description": 'description-' + uuid.uuid4().hex, "object_count": None, "container": 'container-' + uuid.uuid4().hex, @@ -437,6 +418,26 @@ class FakeBackup(object): return backups + @staticmethod + def get_backups(backups=None, count=2): + """Get an iterable MagicMock object with a list of faked backups. + + If backups list is provided, then initialize the Mock object with the + list. Otherwise create one. + + :param List volumes: + A list of FakeResource objects faking backups + :param Integer count: + The number of backups to be faked + :return + An iterable Mock object with side_effect set to a list of faked + backups + """ + if backups is None: + backups = FakeBackup.create_backups(count) + + return mock.MagicMock(side_effect=backups) + class FakeExtension(object): """Fake one or more extension.""" @@ -548,6 +549,26 @@ class FakeQos(object): return qoses + @staticmethod + def get_qoses(qoses=None, count=2): + """Get an iterable MagicMock object with a list of faked qoses. + + If qoses list is provided, then initialize the Mock object with the + list. Otherwise create one. + + :param List volumes: + A list of FakeResource objects faking qoses + :param Integer count: + The number of qoses to be faked + :return + An iterable Mock object with side_effect set to a list of faked + qoses + """ + if qoses is None: + qoses = FakeQos.create_qoses(count) + + return mock.MagicMock(side_effect=qoses) + class FakeSnapshot(object): """Fake one or more snapshot.""" @@ -601,6 +622,26 @@ class FakeSnapshot(object): return snapshots + @staticmethod + def get_snapshots(snapshots=None, count=2): + """Get an iterable MagicMock object with a list of faked snapshots. + + If snapshots list is provided, then initialize the Mock object with the + list. Otherwise create one. + + :param List volumes: + A list of FakeResource objects faking snapshots + :param Integer count: + The number of snapshots to be faked + :return + An iterable Mock object with side_effect set to a list of faked + snapshots + """ + if snapshots is None: + snapshots = FakeSnapshot.create_snapshots(count) + + return mock.MagicMock(side_effect=snapshots) + class FakeType(object): """Fake one or more type.""" diff --git a/openstackclient/tests/volume/v2/test_backup.py b/openstackclient/tests/volume/v2/test_backup.py index ba0f1c18..3c2b3948 100644 --- a/openstackclient/tests/volume/v2/test_backup.py +++ b/openstackclient/tests/volume/v2/test_backup.py @@ -12,6 +12,12 @@ # under the License. # +import mock +from mock import call + +from osc_lib import exceptions +from osc_lib import utils + from openstackclient.tests.volume.v2 import fakes as volume_fakes from openstackclient.volume.v2 import backup @@ -25,6 +31,8 @@ class TestBackup(volume_fakes.TestVolume): self.backups_mock.reset_mock() self.volumes_mock = self.app.client_manager.volume.volumes self.volumes_mock.reset_mock() + self.snapshots_mock = self.app.client_manager.volume.volume_snapshots + self.snapshots_mock.reset_mock() self.restores_mock = self.app.client_manager.volume.restores self.restores_mock.reset_mock() @@ -32,8 +40,9 @@ class TestBackup(volume_fakes.TestVolume): class TestBackupCreate(TestBackup): volume = volume_fakes.FakeVolume.create_one_volume() + snapshot = volume_fakes.FakeSnapshot.create_one_snapshot() new_backup = volume_fakes.FakeBackup.create_one_backup( - attrs={'volume_id': volume.id}) + attrs={'volume_id': volume.id, 'snapshot_id': snapshot.id}) columns = ( 'availability_zone', @@ -43,6 +52,7 @@ class TestBackupCreate(TestBackup): 'name', 'object_count', 'size', + 'snapshot_id', 'status', 'volume_id', ) @@ -54,6 +64,7 @@ class TestBackupCreate(TestBackup): new_backup.name, new_backup.object_count, new_backup.size, + new_backup.snapshot_id, new_backup.status, new_backup.volume_id, ) @@ -62,6 +73,7 @@ class TestBackupCreate(TestBackup): super(TestBackupCreate, self).setUp() self.volumes_mock.get.return_value = self.volume + self.snapshots_mock.get.return_value = self.snapshot self.backups_mock.create.return_value = self.new_backup # Get the command object to test @@ -73,6 +85,8 @@ class TestBackupCreate(TestBackup): "--description", self.new_backup.description, "--container", self.new_backup.container, "--force", + "--incremental", + "--snapshot", self.new_backup.snapshot_id, self.new_backup.volume_id, ] verifylist = [ @@ -80,6 +94,8 @@ class TestBackupCreate(TestBackup): ("description", self.new_backup.description), ("container", self.new_backup.container), ("force", True), + ("incremental", True), + ("snapshot", self.new_backup.snapshot_id), ("volume", self.new_backup.volume_id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -92,6 +108,8 @@ class TestBackupCreate(TestBackup): name=self.new_backup.name, description=self.new_backup.description, force=True, + incremental=True, + snapshot_id=self.new_backup.snapshot_id, ) self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) @@ -117,6 +135,8 @@ class TestBackupCreate(TestBackup): name=None, description=self.new_backup.description, force=False, + incremental=False, + snapshot_id=None, ) self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) @@ -124,12 +144,13 @@ class TestBackupCreate(TestBackup): class TestBackupDelete(TestBackup): - backup = volume_fakes.FakeBackup.create_one_backup() + backups = volume_fakes.FakeBackup.create_backups(count=2) def setUp(self): super(TestBackupDelete, self).setUp() - self.backups_mock.get.return_value = self.backup + self.backups_mock.get = ( + volume_fakes.FakeBackup.get_backups(self.backups)) self.backups_mock.delete.return_value = None # Get the command object to mock @@ -137,18 +158,81 @@ class TestBackupDelete(TestBackup): def test_backup_delete(self): arglist = [ - self.backup.id + self.backups[0].id + ] + verifylist = [ + ("backups", [self.backups[0].id]) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.backups_mock.delete.assert_called_with( + self.backups[0].id, False) + self.assertIsNone(result) + + def test_backup_delete_with_force(self): + arglist = [ + '--force', + self.backups[0].id, ] verifylist = [ - ("backups", [self.backup.id]) + ('force', True), + ("backups", [self.backups[0].id]) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.backups_mock.delete.assert_called_with(self.backup.id) + self.backups_mock.delete.assert_called_with(self.backups[0].id, True) self.assertIsNone(result) + def test_delete_multiple_backups(self): + arglist = [] + for b in self.backups: + arglist.append(b.id) + verifylist = [ + ('backups', arglist), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + calls = [] + for b in self.backups: + calls.append(call(b.id, False)) + self.backups_mock.delete.assert_has_calls(calls) + self.assertIsNone(result) + + def test_delete_multiple_backups_with_exception(self): + arglist = [ + self.backups[0].id, + 'unexist_backup', + ] + verifylist = [ + ('backups', arglist), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + find_mock_result = [self.backups[0], exceptions.CommandError] + with mock.patch.object(utils, 'find_resource', + side_effect=find_mock_result) as find_mock: + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('1 of 2 backups failed to delete.', + str(e)) + + find_mock.assert_any_call(self.backups_mock, self.backups[0].id) + find_mock.assert_any_call(self.backups_mock, 'unexist_backup') + + self.assertEqual(2, find_mock.call_count) + self.backups_mock.delete.assert_called_once_with( + self.backups[0].id, False + ) + class TestBackupList(TestBackup): @@ -264,6 +348,7 @@ class TestBackupShow(TestBackup): 'name', 'object_count', 'size', + 'snapshot_id', 'status', 'volume_id', ) @@ -275,6 +360,7 @@ class TestBackupShow(TestBackup): backup.name, backup.object_count, backup.size, + backup.snapshot_id, backup.status, backup.volume_id, ) diff --git a/openstackclient/tests/volume/v2/test_qos_specs.py b/openstackclient/tests/volume/v2/test_qos_specs.py index 92ffca74..56b8ae03 100644 --- a/openstackclient/tests/volume/v2/test_qos_specs.py +++ b/openstackclient/tests/volume/v2/test_qos_specs.py @@ -13,9 +13,14 @@ # under the License. # +import mock +from mock import call + +from osc_lib import exceptions +from osc_lib import utils + from openstackclient.tests.volume.v2 import fakes as volume_fakes from openstackclient.volume.v2 import qos_specs -from osc_lib import utils class TestQos(volume_fakes.TestVolume): @@ -155,45 +160,94 @@ class TestQosCreate(TestQos): class TestQosDelete(TestQos): - qos_spec = volume_fakes.FakeQos.create_one_qos() + qos_specs = volume_fakes.FakeQos.create_qoses(count=2) def setUp(self): super(TestQosDelete, self).setUp() - self.qos_mock.get.return_value = self.qos_spec + self.qos_mock.get = ( + volume_fakes.FakeQos.get_qoses(self.qos_specs)) # Get the command object to test self.cmd = qos_specs.DeleteQos(self.app, None) def test_qos_delete(self): arglist = [ - self.qos_spec.id + self.qos_specs[0].id ] verifylist = [ - ('qos_specs', [self.qos_spec.id]) + ('qos_specs', [self.qos_specs[0].id]) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.qos_mock.delete.assert_called_with(self.qos_spec.id, False) + self.qos_mock.delete.assert_called_with( + self.qos_specs[0].id, False) self.assertIsNone(result) def test_qos_delete_with_force(self): arglist = [ '--force', - self.qos_spec.id + self.qos_specs[0].id ] verifylist = [ ('force', True), - ('qos_specs', [self.qos_spec.id]) + ('qos_specs', [self.qos_specs[0].id]) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.qos_mock.delete.assert_called_with(self.qos_spec.id, True) + self.qos_mock.delete.assert_called_with( + self.qos_specs[0].id, True) + self.assertIsNone(result) + + def test_delete_multiple_qoses(self): + arglist = [] + for q in self.qos_specs: + arglist.append(q.id) + verifylist = [ + ('qos_specs', arglist), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + calls = [] + for q in self.qos_specs: + calls.append(call(q.id, False)) + self.qos_mock.delete.assert_has_calls(calls) self.assertIsNone(result) + def test_delete_multiple_qoses_with_exception(self): + arglist = [ + self.qos_specs[0].id, + 'unexist_qos', + ] + verifylist = [ + ('qos_specs', arglist), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + find_mock_result = [self.qos_specs[0], exceptions.CommandError] + with mock.patch.object(utils, 'find_resource', + side_effect=find_mock_result) as find_mock: + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual( + '1 of 2 QoS specifications failed to delete.', str(e)) + + find_mock.assert_any_call(self.qos_mock, self.qos_specs[0].id) + find_mock.assert_any_call(self.qos_mock, 'unexist_qos') + + self.assertEqual(2, find_mock.call_count) + self.qos_mock.delete.assert_called_once_with( + self.qos_specs[0].id, False + ) + class TestQosDisassociate(TestQos): diff --git a/openstackclient/tests/volume/v2/test_snapshot.py b/openstackclient/tests/volume/v2/test_snapshot.py index ef199cbc..04e0285e 100644 --- a/openstackclient/tests/volume/v2/test_snapshot.py +++ b/openstackclient/tests/volume/v2/test_snapshot.py @@ -12,6 +12,10 @@ # under the License. # +import mock +from mock import call + +from osc_lib import exceptions from osc_lib import utils from openstackclient.tests.volume.v2 import fakes as volume_fakes @@ -70,12 +74,15 @@ class TestSnapshotCreate(TestSnapshot): "--name", self.new_snapshot.name, "--description", self.new_snapshot.description, "--force", + '--property', 'Alpha=a', + '--property', 'Beta=b', self.new_snapshot.volume_id, ] verifylist = [ ("name", self.new_snapshot.name), ("description", self.new_snapshot.description), ("force", True), + ('property', {'Alpha': 'a', 'Beta': 'b'}), ("volume", self.new_snapshot.volume_id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -86,7 +93,8 @@ class TestSnapshotCreate(TestSnapshot): self.new_snapshot.volume_id, force=True, name=self.new_snapshot.name, - description=self.new_snapshot.description + description=self.new_snapshot.description, + metadata={'Alpha': 'a', 'Beta': 'b'}, ) self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) @@ -110,7 +118,8 @@ class TestSnapshotCreate(TestSnapshot): self.new_snapshot.volume_id, force=True, name=None, - description=self.new_snapshot.description + description=self.new_snapshot.description, + metadata=None, ) self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) @@ -118,12 +127,13 @@ class TestSnapshotCreate(TestSnapshot): class TestSnapshotDelete(TestSnapshot): - snapshot = volume_fakes.FakeSnapshot.create_one_snapshot() + snapshots = volume_fakes.FakeSnapshot.create_snapshots(count=2) def setUp(self): super(TestSnapshotDelete, self).setUp() - self.snapshots_mock.get.return_value = self.snapshot + self.snapshots_mock.get = ( + volume_fakes.FakeSnapshot.get_snapshots(self.snapshots)) self.snapshots_mock.delete.return_value = None # Get the command object to mock @@ -131,18 +141,66 @@ class TestSnapshotDelete(TestSnapshot): def test_snapshot_delete(self): arglist = [ - self.snapshot.id + self.snapshots[0].id ] verifylist = [ - ("snapshots", [self.snapshot.id]) + ("snapshots", [self.snapshots[0].id]) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.snapshots_mock.delete.assert_called_with(self.snapshot.id) + self.snapshots_mock.delete.assert_called_with( + self.snapshots[0].id) self.assertIsNone(result) + def test_delete_multiple_snapshots(self): + arglist = [] + for s in self.snapshots: + arglist.append(s.id) + verifylist = [ + ('snapshots', arglist), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + calls = [] + for s in self.snapshots: + calls.append(call(s.id)) + self.snapshots_mock.delete.assert_has_calls(calls) + self.assertIsNone(result) + + def test_delete_multiple_snapshots_with_exception(self): + arglist = [ + self.snapshots[0].id, + 'unexist_snapshot', + ] + verifylist = [ + ('snapshots', arglist), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + find_mock_result = [self.snapshots[0], exceptions.CommandError] + with mock.patch.object(utils, 'find_resource', + side_effect=find_mock_result) as find_mock: + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('1 of 2 snapshots failed to delete.', + str(e)) + + find_mock.assert_any_call( + self.snapshots_mock, self.snapshots[0].id) + find_mock.assert_any_call(self.snapshots_mock, 'unexist_snapshot') + + self.assertEqual(2, find_mock.call_count) + self.snapshots_mock.delete.assert_called_once_with( + self.snapshots[0].id + ) + class TestSnapshotList(TestSnapshot): diff --git a/openstackclient/tests/volume/v2/test_type.py b/openstackclient/tests/volume/v2/test_type.py index 174f33f2..a7db2e49 100644 --- a/openstackclient/tests/volume/v2/test_type.py +++ b/openstackclient/tests/volume/v2/test_type.py @@ -14,6 +14,7 @@ import copy +from osc_lib import exceptions from osc_lib import utils from openstackclient.tests import fakes @@ -41,6 +42,7 @@ class TestType(volume_fakes.TestVolume): class TestTypeCreate(TestType): + project = identity_fakes.FakeProject.create_one_project() columns = ( 'description', 'id', @@ -58,6 +60,7 @@ class TestTypeCreate(TestType): ) self.types_mock.create.return_value = self.new_volume_type + self.projects_mock.get.return_value = self.project # Get the command object to test self.cmd = volume_type.CreateVolumeType(self.app, None) @@ -89,12 +92,14 @@ class TestTypeCreate(TestType): arglist = [ "--description", self.new_volume_type.description, "--private", + "--project", self.project.id, self.new_volume_type.name, ] verifylist = [ ("description", self.new_volume_type.description), ("public", False), ("private", True), + ("project", self.project.id), ("name", self.new_volume_type.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -109,6 +114,21 @@ class TestTypeCreate(TestType): self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) + def test_public_type_create_with_project(self): + arglist = [ + '--project', self.project.id, + self.new_volume_type.name, + ] + verifylist = [ + ('project', self.project.id), + ('name', self.new_volume_type.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.assertRaises(exceptions.CommandError, + self.cmd.take_action, + parsed_args) + class TestTypeDelete(TestType): diff --git a/openstackclient/tests/volume/v2/test_volume.py b/openstackclient/tests/volume/v2/test_volume.py index 68158df0..db65c3bd 100644 --- a/openstackclient/tests/volume/v2/test_volume.py +++ b/openstackclient/tests/volume/v2/test_volume.py @@ -13,9 +13,10 @@ # import copy - +import mock from mock import call +from osc_lib import exceptions from osc_lib import utils from openstackclient.tests import fakes @@ -458,6 +459,36 @@ class TestVolumeDelete(TestVolume): self.volumes_mock.delete.assert_has_calls(calls) self.assertIsNone(result) + def test_volume_delete_multi_volumes_with_exception(self): + volumes = self.setup_volumes_mock(count=2) + + arglist = [ + volumes[0].id, + 'unexist_volume', + ] + verifylist = [ + ('volumes', arglist), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + find_mock_result = [volumes[0], exceptions.CommandError] + with mock.patch.object(utils, 'find_resource', + side_effect=find_mock_result) as find_mock: + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('1 of 2 volumes failed to delete.', + str(e)) + + find_mock.assert_any_call(self.volumes_mock, volumes[0].id) + find_mock.assert_any_call(self.volumes_mock, 'unexist_volume') + + self.assertEqual(2, find_mock.call_count) + self.volumes_mock.delete.assert_called_once_with( + volumes[0].id + ) + class TestVolumeList(TestVolume): diff --git a/openstackclient/volume/v1/volume.py b/openstackclient/volume/v1/volume.py index e11aa1a7..820673bb 100644 --- a/openstackclient/volume/v1/volume.py +++ b/openstackclient/volume/v1/volume.py @@ -175,7 +175,6 @@ class DeleteVolume(command.Command): ) parser.add_argument( '--force', - dest='force', action='store_true', default=False, help=_('Attempt forced removal of volume(s), regardless of state ' diff --git a/openstackclient/volume/v2/backup.py b/openstackclient/volume/v2/backup.py index 519913a9..3d27c121 100644 --- a/openstackclient/volume/v2/backup.py +++ b/openstackclient/volume/v2/backup.py @@ -15,14 +15,19 @@ """Volume v2 Backup action implementations""" import copy +import logging from osc_lib.command import command +from osc_lib import exceptions from osc_lib import utils import six from openstackclient.i18n import _ +LOG = logging.getLogger(__name__) + + class CreateBackup(command.ShowOne): """Create new backup""" @@ -49,23 +54,40 @@ class CreateBackup(command.ShowOne): help=_("Optional backup container name") ) parser.add_argument( + "--snapshot", + metavar="<snapshot>", + help=_("Snapshot to backup (name or ID)") + ) + parser.add_argument( '--force', action='store_true', default=False, help=_("Allow to back up an in-use volume") ) + parser.add_argument( + '--incremental', + action='store_true', + default=False, + help=_("Perform an incremental backup") + ) return parser def take_action(self, parsed_args): volume_client = self.app.client_manager.volume volume_id = utils.find_resource( volume_client.volumes, parsed_args.volume).id + snapshot_id = None + if parsed_args.snapshot: + snapshot_id = utils.find_resource( + volume_client.volume_snapshots, parsed_args.snapshot).id backup = volume_client.backups.create( volume_id, container=parsed_args.container, name=parsed_args.name, description=parsed_args.description, force=parsed_args.force, + incremental=parsed_args.incremental, + snapshot_id=snapshot_id, ) backup._info.pop("links", None) return zip(*sorted(six.iteritems(backup._info))) @@ -82,14 +104,34 @@ class DeleteBackup(command.Command): nargs="+", help=_("Backup(s) to delete (name or ID)") ) + parser.add_argument( + '--force', + action='store_true', + default=False, + help=_("Allow delete in state other than error or available") + ) return parser def take_action(self, parsed_args): volume_client = self.app.client_manager.volume - for backup in parsed_args.backups: - backup_id = utils.find_resource( - volume_client.backups, backup).id - volume_client.backups.delete(backup_id) + result = 0 + + for i in parsed_args.backups: + try: + backup_id = utils.find_resource( + volume_client.backups, i).id + volume_client.backups.delete(backup_id, parsed_args.force) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete backup with " + "name or ID '%(backup)s': %(e)s") + % {'backup': i, 'e': e}) + + if result > 0: + total = len(parsed_args.backups) + msg = (_("%(result)s of %(total)s backups failed " + "to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) class ListBackup(command.Lister): diff --git a/openstackclient/volume/v2/qos_specs.py b/openstackclient/volume/v2/qos_specs.py index 5ed1225b..9797f1a6 100644 --- a/openstackclient/volume/v2/qos_specs.py +++ b/openstackclient/volume/v2/qos_specs.py @@ -15,14 +15,20 @@ """Volume v2 QoS action implementations""" +import logging + from osc_lib.cli import parseractions from osc_lib.command import command +from osc_lib import exceptions from osc_lib import utils import six from openstackclient.i18n import _ +LOG = logging.getLogger(__name__) + + class AssociateQos(command.Command): """Associate a QoS specification to a volume type""" @@ -113,9 +119,23 @@ class DeleteQos(command.Command): def take_action(self, parsed_args): volume_client = self.app.client_manager.volume - for qos in parsed_args.qos_specs: - qos_spec = utils.find_resource(volume_client.qos_specs, qos) - volume_client.qos_specs.delete(qos_spec.id, parsed_args.force) + result = 0 + + for i in parsed_args.qos_specs: + try: + qos_spec = utils.find_resource(volume_client.qos_specs, i) + volume_client.qos_specs.delete(qos_spec.id, parsed_args.force) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete QoS specification with " + "name or ID '%(qos)s': %(e)s") + % {'qos': i, 'e': e}) + + if result > 0: + total = len(parsed_args.qos_specs) + msg = (_("%(result)s of %(total)s QoS specifications failed" + " to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) class DisassociateQos(command.Command): diff --git a/openstackclient/volume/v2/snapshot.py b/openstackclient/volume/v2/snapshot.py index 439904e7..ba692074 100644 --- a/openstackclient/volume/v2/snapshot.py +++ b/openstackclient/volume/v2/snapshot.py @@ -15,15 +15,20 @@ """Volume v2 snapshot action implementations""" import copy +import logging from osc_lib.cli import parseractions from osc_lib.command import command +from osc_lib import exceptions from osc_lib import utils import six from openstackclient.i18n import _ +LOG = logging.getLogger(__name__) + + class CreateSnapshot(command.ShowOne): """Create new snapshot""" @@ -46,12 +51,18 @@ class CreateSnapshot(command.ShowOne): ) parser.add_argument( "--force", - dest="force", action="store_true", default=False, help=_("Create a snapshot attached to an instance. " "Default is False") ) + parser.add_argument( + "--property", + metavar="<key=value>", + action=parseractions.KeyValueAction, + help=_("Set a property to this snapshot " + "(repeat option to set multiple properties)"), + ) return parser def take_action(self, parsed_args): @@ -62,7 +73,8 @@ class CreateSnapshot(command.ShowOne): volume_id, force=parsed_args.force, name=parsed_args.name, - description=parsed_args.description + description=parsed_args.description, + metadata=parsed_args.property, ) snapshot._info.update( {'properties': utils.format_dict(snapshot._info.pop('metadata'))} @@ -85,10 +97,24 @@ class DeleteSnapshot(command.Command): def take_action(self, parsed_args): volume_client = self.app.client_manager.volume - for snapshot in parsed_args.snapshots: - snapshot_id = utils.find_resource( - volume_client.volume_snapshots, snapshot).id - volume_client.volume_snapshots.delete(snapshot_id) + result = 0 + + for i in parsed_args.snapshots: + try: + snapshot_id = utils.find_resource( + volume_client.volume_snapshots, i).id + volume_client.volume_snapshots.delete(snapshot_id) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete snapshot with " + "name or ID '%(snapshot)s': %(e)s") + % {'snapshot': i, 'e': e}) + + if result > 0: + total = len(parsed_args.snapshots) + msg = (_("%(result)s of %(total)s snapshots failed " + "to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) class ListSnapshot(command.Lister): diff --git a/openstackclient/volume/v2/volume.py b/openstackclient/volume/v2/volume.py index be2388fb..85f267ef 100644 --- a/openstackclient/volume/v2/volume.py +++ b/openstackclient/volume/v2/volume.py @@ -19,6 +19,7 @@ import logging from osc_lib.cli import parseractions from osc_lib.command import command +from osc_lib import exceptions from osc_lib import utils import six @@ -167,7 +168,6 @@ class DeleteVolume(command.Command): ) parser.add_argument( "--force", - dest="force", action="store_true", default=False, help=_("Attempt forced removal of volume(s), regardless of state " @@ -177,13 +177,27 @@ class DeleteVolume(command.Command): def take_action(self, parsed_args): volume_client = self.app.client_manager.volume - for volume in parsed_args.volumes: - volume_obj = utils.find_resource( - volume_client.volumes, volume) - if parsed_args.force: - volume_client.volumes.force_delete(volume_obj.id) - else: - volume_client.volumes.delete(volume_obj.id) + result = 0 + + for i in parsed_args.volumes: + try: + volume_obj = utils.find_resource( + volume_client.volumes, i) + if parsed_args.force: + volume_client.volumes.force_delete(volume_obj.id) + else: + volume_client.volumes.delete(volume_obj.id) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete volume with " + "name or ID '%(volume)s': %(e)s") + % {'volume': i, 'e': e}) + + if result > 0: + total = len(parsed_args.volumes) + msg = (_("%(result)s of %(total)s volumes failed " + "to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) class ListVolume(command.Lister): diff --git a/openstackclient/volume/v2/volume_type.py b/openstackclient/volume/v2/volume_type.py index 87f4c547..ac11785c 100644 --- a/openstackclient/volume/v2/volume_type.py +++ b/openstackclient/volume/v2/volume_type.py @@ -47,14 +47,12 @@ class CreateVolumeType(command.ShowOne): public_group = parser.add_mutually_exclusive_group() public_group.add_argument( "--public", - dest="public", action="store_true", default=False, help=_("Volume type is accessible to the public"), ) public_group.add_argument( "--private", - dest="private", action="store_true", default=False, help=_("Volume type is not accessible to the public"), @@ -66,12 +64,23 @@ class CreateVolumeType(command.ShowOne): help=_('Set a property on this volume type ' '(repeat option to set multiple properties)'), ) + parser.add_argument( + '--project', + metavar='<project>', + help=_("Allow <project> to access private type (name or ID) " + "(Must be used with --private option)"), + ) + identity_common.add_project_domain_option_to_parser(parser) return parser def take_action(self, parsed_args): - + identity_client = self.app.client_manager.identity volume_client = self.app.client_manager.volume + if parsed_args.project and not parsed_args.private: + msg = _("--project is only allowed with --private") + raise exceptions.CommandError(msg) + kwargs = {} if parsed_args.public: kwargs['is_public'] = True @@ -84,6 +93,20 @@ class CreateVolumeType(command.ShowOne): **kwargs ) volume_type._info.pop('extra_specs') + + if parsed_args.project: + try: + project_id = identity_common.find_project( + identity_client, + parsed_args.project, + parsed_args.project_domain, + ).id + volume_client.volume_type_access.add_project_access( + volume_type.id, project_id) + except Exception as e: + msg = _("Failed to add project %(project)s access to " + "type: %(e)s") + LOG.error(msg % {'project': parsed_args.project, 'e': e}) if parsed_args.property: result = volume_type.set_keys(parsed_args.property) volume_type._info.update({'properties': utils.format_dict(result)}) |
