diff options
Diffstat (limited to 'openstackclient')
92 files changed, 4154 insertions, 661 deletions
diff --git a/openstackclient/api/auth.py b/openstackclient/api/auth.py index 820b4ecf..66272e42 100644 --- a/openstackclient/api/auth.py +++ b/openstackclient/api/auth.py @@ -185,7 +185,7 @@ def build_auth_plugins_option_parser(parser): metavar='<auth-type>', dest='auth_type', default=utils.env('OS_AUTH_TYPE'), - help='Select an auhentication type. Available types: ' + + help='Select an authentication type. Available types: ' + ', '.join(available_plugins) + '. Default: selected based on --os-username/--os-token' + ' (Env: OS_AUTH_TYPE)', diff --git a/openstackclient/api/object_store_v1.py b/openstackclient/api/object_store_v1.py index c52eeb3a..b1c78d99 100644 --- a/openstackclient/api/object_store_v1.py +++ b/openstackclient/api/object_store_v1.py @@ -15,7 +15,9 @@ import io import os + import six +from six.moves import urllib try: from urllib.parse import urlparse # noqa @@ -42,11 +44,9 @@ class APIv1(api.BaseAPI): :returns: dict of returned headers """ - - response = self.create(container, method='PUT') - url_parts = urlparse(self.endpoint) + response = self.create(urllib.parse.quote(container), method='PUT') data = { - 'account': url_parts.path.split('/')[-1], + 'account': self._find_account_id(), 'container': container, 'x-trans-id': response.headers.get('x-trans-id', None), } @@ -64,7 +64,7 @@ class APIv1(api.BaseAPI): """ if container: - self.delete(container) + self.delete(urllib.parse.quote(container)) def container_list( self, @@ -140,6 +140,23 @@ class APIv1(api.BaseAPI): for object in objects: self.object_save(container=container, object=object['name']) + def container_set( + self, + container, + properties, + ): + """Set container properties + + :param string container: + name of container to modify + :param dict properties: + properties to add or update for the container + """ + + headers = self._set_properties(properties, 'X-Container-Meta-%s') + if headers: + self.create(urllib.parse.quote(container), headers=headers) + def container_show( self, container=None, @@ -152,22 +169,52 @@ class APIv1(api.BaseAPI): dict of returned headers """ - response = self._request('HEAD', container) + response = self._request('HEAD', urllib.parse.quote(container)) data = { - 'account': response.headers.get('x-container-meta-owner', None), + 'account': self._find_account_id(), 'container': container, 'object_count': response.headers.get( 'x-container-object-count', None, ), - 'bytes_used': response.headers.get('x-container-bytes-used', None), - 'read_acl': response.headers.get('x-container-read', None), - 'write_acl': response.headers.get('x-container-write', None), - 'sync_to': response.headers.get('x-container-sync-to', None), - 'sync_key': response.headers.get('x-container-sync-key', None), + 'bytes_used': response.headers.get('x-container-bytes-used', None) } + + if 'x-container-read' in response.headers: + data['read_acl'] = response.headers.get('x-container-read', None) + if 'x-container-write' in response.headers: + data['write_acl'] = response.headers.get('x-container-write', None) + if 'x-container-sync-to' in response.headers: + data['sync_to'] = response.headers.get('x-container-sync-to', None) + if 'x-container-sync-key' in response.headers: + data['sync_key'] = response.headers.get('x-container-sync-key', + None) + + properties = self._get_properties(response.headers, + 'x-container-meta-') + if properties: + data['properties'] = properties + return data + def container_unset( + self, + container, + properties, + ): + """Unset container properties + + :param string container: + name of container to modify + :param dict properties: + properties to remove from the container + """ + + headers = self._unset_properties(properties, + 'X-Remove-Container-Meta-%s') + if headers: + self.create(urllib.parse.quote(container), headers=headers) + def object_create( self, container=None, @@ -187,16 +234,16 @@ class APIv1(api.BaseAPI): # TODO(dtroyer): What exception to raise here? return {} - full_url = "%s/%s" % (container, object) + full_url = "%s/%s" % (urllib.parse.quote(container), + urllib.parse.quote(object)) with io.open(object, 'rb') as f: response = self.create( full_url, method='PUT', data=f, ) - url_parts = urlparse(self.endpoint) data = { - 'account': url_parts.path.split('/')[-1], + 'account': self._find_account_id(), 'container': container, 'object': object, 'x-trans-id': response.headers.get('X-Trans-Id', None), @@ -221,7 +268,8 @@ class APIv1(api.BaseAPI): if container is None or object is None: return - self.delete("%s/%s" % (container, object)) + self.delete("%s/%s" % (urllib.parse.quote(container), + urllib.parse.quote(object))) def object_list( self, @@ -298,7 +346,7 @@ class APIv1(api.BaseAPI): if delimiter: params['delimiter'] = delimiter - return self.list(container, **params) + return self.list(urllib.parse.quote(container), **params) def object_save( self, @@ -321,7 +369,8 @@ class APIv1(api.BaseAPI): response = self._request( 'GET', - "%s/%s" % (container, object), + "%s/%s" % (urllib.parse.quote(container), + urllib.parse.quote(object)), stream=True, ) if response.status_code == 200: @@ -332,6 +381,50 @@ class APIv1(api.BaseAPI): for chunk in response.iter_content(): f.write(chunk) + def object_set( + self, + container, + object, + properties, + ): + """Set object properties + + :param string container: + container name for object to modify + :param string object: + name of object to modify + :param dict properties: + properties to add or update for the container + """ + + headers = self._set_properties(properties, 'X-Object-Meta-%s') + if headers: + self.create("%s/%s" % (urllib.parse.quote(container), + urllib.parse.quote(object)), + headers=headers) + + def object_unset( + self, + container, + object, + properties, + ): + """Unset object properties + + :param string container: + container name for object to modify + :param string object: + name of object to modify + :param dict properties: + properties to remove from the object + """ + + headers = self._unset_properties(properties, 'X-Remove-Object-Meta-%s') + if headers: + self.create("%s/%s" % (urllib.parse.quote(container), + urllib.parse.quote(object)), + headers=headers) + def object_show( self, container=None, @@ -350,9 +443,12 @@ class APIv1(api.BaseAPI): if container is None or object is None: return {} - response = self._request('HEAD', "%s/%s" % (container, object)) + response = self._request('HEAD', "%s/%s" % + (urllib.parse.quote(container), + urllib.parse.quote(object))) + data = { - 'account': response.headers.get('x-container-meta-owner', None), + 'account': self._find_account_id(), 'container': container, 'object': object, 'content-type': response.headers.get('content-type', None), @@ -371,18 +467,101 @@ class APIv1(api.BaseAPI): 'x-object-manifest', None, ) - for key, value in six.iteritems(response.headers): - if key.startswith('x-object-meta-'): - data[key[len('x-object-meta-'):].lower()] = value - elif key not in ( - 'content-type', - 'content-length', - 'last-modified', - 'etag', - 'date', - 'x-object-manifest', - 'x-container-meta-owner', - ): - data[key.lower()] = value + properties = self._get_properties(response.headers, 'x-object-meta-') + if properties: + data['properties'] = properties + + return data + + def account_set( + self, + properties, + ): + """Set account properties + + :param dict properties: + properties to add or update for the account + """ + + headers = self._set_properties(properties, 'X-Account-Meta-%s') + if headers: + # NOTE(stevemar): The URL (first argument) in this case is already + # set to the swift account endpoint, because that's how it's + # registered in the catalog + self.create("", headers=headers) + + def account_show(self): + """Show account details""" + + # NOTE(stevemar): Just a HEAD request to the endpoint already in the + # catalog should be enough. + response = self._request("HEAD", "") + data = {} + + properties = self._get_properties(response.headers, 'x-account-meta-') + if properties: + data['properties'] = properties + + # Map containers, bytes and objects a bit nicer + data['Containers'] = response.headers.get('x-account-container-count', + None) + data['Objects'] = response.headers.get('x-account-object-count', None) + data['Bytes'] = response.headers.get('x-account-bytes-used', None) + # Add in Account info too + data['Account'] = self._find_account_id() return data + + def account_unset( + self, + properties, + ): + """Unset account properties + + :param dict properties: + properties to remove from the account + """ + + headers = self._unset_properties(properties, + 'X-Remove-Account-Meta-%s') + if headers: + self.create("", headers=headers) + + def _find_account_id(self): + url_parts = urlparse(self.endpoint) + return url_parts.path.split('/')[-1] + + def _unset_properties(self, properties, header_tag): + # NOTE(stevemar): As per the API, the headers have to be in the form + # of "X-Remove-Account-Meta-Book: x". In the case where metadata is + # removed, we can set the value of the header to anything, so it's + # set to 'x'. In the case of a Container property we use: + # "X-Remove-Container-Meta-Book: x", and the same logic applies for + # Object properties + + headers = {} + for k in properties: + header_name = header_tag % k + headers[header_name] = 'x' + return headers + + def _set_properties(self, properties, header_tag): + # NOTE(stevemar): As per the API, the headers have to be in the form + # of "X-Account-Meta-Book: MobyDick". In the case of a Container + # property we use: "X-Add-Container-Meta-Book: MobyDick", and the same + # logic applies for Object properties + + headers = {} + for k, v in properties.iteritems(): + header_name = header_tag % k + headers[header_name] = v + return headers + + def _get_properties(self, headers, header_tag): + # Add in properties as a top level key, this is consistent with other + # OSC commands + properties = {} + for k, v in six.iteritems(headers): + if k.startswith(header_tag): + properties[k[len(header_tag):]] = v + return properties diff --git a/openstackclient/common/clientmanager.py b/openstackclient/common/clientmanager.py index d8e7e1a9..edabf65e 100644 --- a/openstackclient/common/clientmanager.py +++ b/openstackclient/common/clientmanager.py @@ -20,6 +20,7 @@ import logging import pkg_resources import sys +from oslo_utils import strutils import requests from openstackclient.api import auth @@ -49,6 +50,10 @@ class ClientCache(object): class ClientManager(object): """Manages access to API clients, including authentication.""" + + # A simple incrementing version for the plugin to know what is available + PLUGIN_INTERFACE_VERSION = "2" + identity = ClientCache(identity_client.make_client) def __getattr__(self, name): @@ -163,7 +168,8 @@ class ClientManager(object): self._project_name = self._auth_params['tenant_name'] LOG.info('Using auth plugin: %s' % self.auth_plugin_name) - LOG.debug('Using parameters %s' % self._auth_params) + LOG.debug('Using parameters %s' % + strutils.mask_password(self._auth_params)) self.auth = auth_plugin.load_from_options(**self._auth_params) # needed by SAML authentication request_session = requests.session() diff --git a/openstackclient/common/configuration.py b/openstackclient/common/configuration.py index 83df73e2..ac2792dd 100644 --- a/openstackclient/common/configuration.py +++ b/openstackclient/common/configuration.py @@ -18,6 +18,8 @@ import logging from cliff import show import six +from openstackclient.common import utils + REDACTED = "<redacted>" @@ -44,8 +46,8 @@ class ShowConfiguration(show.ShowOne): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) info = self.app.client_manager.get_configuration() for key, value in six.iteritems(info.pop('auth', {})): diff --git a/openstackclient/common/limits.py b/openstackclient/common/limits.py index 4abcf169..582f70c5 100644 --- a/openstackclient/common/limits.py +++ b/openstackclient/common/limits.py @@ -31,7 +31,7 @@ class ShowLimits(lister.Lister): def get_parser(self, prog_name): parser = super(ShowLimits, self).get_parser(prog_name) - type_group = parser.add_mutually_exclusive_group() + type_group = parser.add_mutually_exclusive_group(required=True) type_group.add_argument( "--absolute", dest="is_absolute", @@ -64,8 +64,8 @@ class ShowLimits(lister.Lister): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) compute_client = self.app.client_manager.compute volume_client = self.app.client_manager.volume diff --git a/openstackclient/common/module.py b/openstackclient/common/module.py index 356cdca3..f0ed23b2 100644 --- a/openstackclient/common/module.py +++ b/openstackclient/common/module.py @@ -22,6 +22,8 @@ import sys from cliff import lister from cliff import show +from openstackclient.common import utils + class ListCommand(lister.Lister): """List recognized commands by group""" @@ -29,8 +31,8 @@ class ListCommand(lister.Lister): auth_required = False log = logging.getLogger(__name__ + '.ListCommand') + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) cm = self.app.command_manager groups = cm.get_command_groups() @@ -54,8 +56,8 @@ class ListModule(show.ShowOne): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) data = {} # Get module versions diff --git a/openstackclient/common/quota.py b/openstackclient/common/quota.py index a40f6e4d..e092feff 100644 --- a/openstackclient/common/quota.py +++ b/openstackclient/common/quota.py @@ -23,6 +23,8 @@ import sys from cliff import command from cliff import show +from openstackclient.common import utils + # List the quota items, map the internal argument name to the option # name that the user sees. @@ -89,21 +91,21 @@ class SetQuota(command.Command): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) compute_client = self.app.client_manager.compute volume_client = self.app.client_manager.volume compute_kwargs = {} for k, v in COMPUTE_QUOTAS.items(): - value = getattr(parsed_args, v, None) + value = getattr(parsed_args, k, None) if value is not None: compute_kwargs[k] = value volume_kwargs = {} for k, v in VOLUME_QUOTAS.items(): - value = getattr(parsed_args, v, None) + value = getattr(parsed_args, k, None) if value is not None: if parsed_args.volume_type: k = k + '_%s' % parsed_args.volume_type @@ -187,8 +189,8 @@ class ShowQuota(show.ShowOne): else: return {} + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) compute_client = self.app.client_manager.compute volume_client = self.app.client_manager.volume @@ -222,8 +224,7 @@ class ShowQuota(show.ShowOne): info.pop(k) # Handle project ID special as it only appears in output - if info['id']: - info['project'] = info['id'] - info.pop('id') + if 'id' in info: + info['project'] = info.pop('id') return zip(*sorted(six.iteritems(info))) diff --git a/openstackclient/common/utils.py b/openstackclient/common/utils.py index 2f8419f4..51e2a2f9 100644 --- a/openstackclient/common/utils.py +++ b/openstackclient/common/utils.py @@ -26,6 +26,29 @@ from oslo_utils import importutils from openstackclient.common import exceptions +def log_method(log, level=logging.DEBUG): + """Logs a method and its arguments when entered.""" + + def decorator(func): + func_name = func.__name__ + + @six.wraps(func) + def wrapper(self, *args, **kwargs): + if log.isEnabledFor(level): + pretty_args = [] + if args: + pretty_args.extend(str(a) for a in args) + if kwargs: + pretty_args.extend( + "%s=%s" % (k, v) for k, v in six.iteritems(kwargs)) + log.log(level, "%s(%s)", func_name, ", ".join(pretty_args)) + return func(self, *args, **kwargs) + + return wrapper + + return decorator + + def find_resource(manager, name_or_id, **kwargs): """Helper for the _find_* methods. @@ -71,12 +94,15 @@ def find_resource(manager, name_or_id, **kwargs): if len(kwargs) == 0: kwargs = {} - # Prepare the kwargs for calling find - if 'NAME_ATTR' in manager.resource_class.__dict__: - # novaclient does this for oddball resources - kwargs[manager.resource_class.NAME_ATTR] = name_or_id - else: - kwargs['name'] = name_or_id + try: + # Prepare the kwargs for calling find + if 'NAME_ATTR' in manager.resource_class.__dict__: + # novaclient does this for oddball resources + kwargs[manager.resource_class.NAME_ATTR] = name_or_id + else: + kwargs['name'] = name_or_id + except Exception: + pass # finally try to find entity by name try: @@ -95,7 +121,24 @@ def find_resource(manager, name_or_id, **kwargs): (manager.resource_class.__name__.lower(), name_or_id) raise exceptions.CommandError(msg) else: - raise + pass + + try: + for resource in manager.list(): + # short circuit and return the first match + if (resource.get('id') == name_or_id or + resource.get('name') == name_or_id): + return resource + else: + # we found no match, keep going to bomb out + pass + except Exception: + # in case the list fails for some reason + pass + + # if we hit here, we've failed, report back this error: + msg = "Could not find resource %s" % name_or_id + raise exceptions.CommandError(msg) def format_dict(data): diff --git a/openstackclient/compute/client.py b/openstackclient/compute/client.py index 6ae87b79..23a4deca 100644 --- a/openstackclient/compute/client.py +++ b/openstackclient/compute/client.py @@ -15,43 +15,46 @@ import logging +from openstackclient.common import exceptions from openstackclient.common import utils LOG = logging.getLogger(__name__) -DEFAULT_COMPUTE_API_VERSION = '2' +DEFAULT_API_VERSION = '2' API_VERSION_OPTION = 'os_compute_api_version' API_NAME = 'compute' API_VERSIONS = { "2": "novaclient.client", } +# Save the microversion if in use +_compute_api_version = None + def make_client(instance): """Returns a compute service client.""" - # Defer client imports until we actually need them + # Defer client import until we actually need them from novaclient import client as nova_client - from novaclient import extension - try: - from novaclient.v2.contrib import list_extensions - except ImportError: - from novaclient.v1_1.contrib import list_extensions - compute_client = nova_client.get_client_class( - instance._api_version[API_NAME], - ) - LOG.debug('Instantiating compute client: %s', compute_client) + if _compute_api_version is not None: + version = _compute_api_version + else: + version = instance._api_version[API_NAME] + + LOG.debug('Instantiating compute client for V%s' % version) # Set client http_log_debug to True if verbosity level is high enough http_log_debug = utils.get_effective_log_level() <= logging.DEBUG - extensions = [extension.Extension('list_extensions', list_extensions)] + extensions = [ext for ext in nova_client.discover_extensions(version) + if ext.name == "list_extensions"] # Remember interface only if it is set kwargs = utils.build_kwargs_dict('endpoint_type', instance._interface) - client = compute_client( + client = nova_client.Client( + version, session=instance.session, extensions=extensions, http_log_debug=http_log_debug, @@ -68,10 +71,50 @@ def build_option_parser(parser): parser.add_argument( '--os-compute-api-version', metavar='<compute-api-version>', - default=utils.env( - 'OS_COMPUTE_API_VERSION', - default=DEFAULT_COMPUTE_API_VERSION), + default=utils.env('OS_COMPUTE_API_VERSION'), help='Compute API version, default=' + - DEFAULT_COMPUTE_API_VERSION + + DEFAULT_API_VERSION + ' (Env: OS_COMPUTE_API_VERSION)') return parser + + +def check_api_version(check_version): + """Validate version supplied by user + + Returns: + * True if version is OK + * False if the version has not been checked and the previous plugin + check should be performed + * throws an exception if the version is no good + + TODO(dtroyer): make the exception thrown a version-related one + """ + + # Defer client imports until we actually need them + try: + from novaclient import api_versions + except ImportError: + # Retain previous behaviour + return False + + import novaclient + + global _compute_api_version + + # Copy some logic from novaclient 2.27.0 for basic version detection + # NOTE(dtroyer): This is only enough to resume operations using API + # version 2.0 or any valid version supplied by the user. + _compute_api_version = api_versions.get_api_version(check_version) + + if _compute_api_version > api_versions.APIVersion("2.0"): + if not _compute_api_version.matches( + novaclient.API_MIN_VERSION, + novaclient.API_MAX_VERSION, + ): + raise exceptions.CommandError( + "versions supported by client: %s - %s" % ( + novaclient.API_MIN_VERSION.get_string(), + novaclient.API_MAX_VERSION.get_string(), + ), + ) + return True diff --git a/openstackclient/compute/v2/aggregate.py b/openstackclient/compute/v2/aggregate.py index 84ed5c7d..a1ba618f 100644 --- a/openstackclient/compute/v2/aggregate.py +++ b/openstackclient/compute/v2/aggregate.py @@ -123,8 +123,8 @@ class DeleteAggregate(command.Command): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) compute_client = self.app.client_manager.compute data = utils.find_resource( @@ -256,8 +256,8 @@ class SetAggregate(show.ShowOne): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) compute_client = self.app.client_manager.compute aggregate = utils.find_resource( @@ -303,8 +303,8 @@ class ShowAggregate(show.ShowOne): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) compute_client = self.app.client_manager.compute data = utils.find_resource( diff --git a/openstackclient/compute/v2/availability_zone.py b/openstackclient/compute/v2/availability_zone.py index 648c0ee4..0fe6c73a 100644 --- a/openstackclient/compute/v2/availability_zone.py +++ b/openstackclient/compute/v2/availability_zone.py @@ -73,8 +73,8 @@ class ListAvailabilityZone(lister.Lister): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) if parsed_args.long: columns = ('Zone Name', 'Zone Status', diff --git a/openstackclient/compute/v2/console.py b/openstackclient/compute/v2/console.py index 082a3a0c..bb0747b1 100644 --- a/openstackclient/compute/v2/console.py +++ b/openstackclient/compute/v2/console.py @@ -47,8 +47,8 @@ class ShowConsoleLog(command.Command): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) compute_client = self.app.client_manager.compute server = utils.find_resource( @@ -103,8 +103,8 @@ class ShowConsoleURL(show.ShowOne): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) compute_client = self.app.client_manager.compute server = utils.find_resource( compute_client.servers, diff --git a/openstackclient/compute/v2/flavor.py b/openstackclient/compute/v2/flavor.py index 3458cf79..7474580b 100644 --- a/openstackclient/compute/v2/flavor.py +++ b/openstackclient/compute/v2/flavor.py @@ -181,6 +181,15 @@ class ListFlavor(lister.Lister): action='store_true', default=False, help='List additional fields in output') + parser.add_argument( + '--marker', + metavar="<marker>", + help='The last flavor ID of the previous page') + parser.add_argument( + '--limit', + type=int, + metavar="<limit>", + help='Maximum number of flavors to display') return parser def take_action(self, parsed_args): @@ -202,7 +211,9 @@ class ListFlavor(lister.Lister): # and flavors from their own projects only. is_public = None if parsed_args.all else parsed_args.public - data = compute_client.flavors.list(is_public=is_public) + data = compute_client.flavors.list(is_public=is_public, + marker=parsed_args.marker, + limit=parsed_args.limit) if parsed_args.long: columns = columns + ( diff --git a/openstackclient/compute/v2/floatingip.py b/openstackclient/compute/v2/floatingip.py index 658f0d5a..c557c24b 100644 --- a/openstackclient/compute/v2/floatingip.py +++ b/openstackclient/compute/v2/floatingip.py @@ -69,8 +69,8 @@ class CreateFloatingIP(show.ShowOne): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) compute_client = self.app.client_manager.compute floating_ip = compute_client.floating_ips.create(parsed_args.pool) @@ -93,8 +93,8 @@ class DeleteFloatingIP(command.Command): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) compute_client = self.app.client_manager.compute floating_ip = utils.find_resource( @@ -111,8 +111,8 @@ class ListFloatingIP(lister.Lister): log = logging.getLogger(__name__ + '.ListFloatingIP') + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) compute_client = self.app.client_manager.compute columns = ('ID', 'Pool', 'IP', 'Fixed IP', 'Instance ID') diff --git a/openstackclient/compute/v2/floatingippool.py b/openstackclient/compute/v2/floatingippool.py index db1c9f0f..cc485552 100644 --- a/openstackclient/compute/v2/floatingippool.py +++ b/openstackclient/compute/v2/floatingippool.py @@ -27,8 +27,8 @@ class ListFloatingIPPool(lister.Lister): log = logging.getLogger(__name__ + '.ListFloatingIPPool') + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) compute_client = self.app.client_manager.compute columns = ('Name',) diff --git a/openstackclient/compute/v2/keypair.py b/openstackclient/compute/v2/keypair.py index edf25f83..5c627c50 100644 --- a/openstackclient/compute/v2/keypair.py +++ b/openstackclient/compute/v2/keypair.py @@ -47,8 +47,8 @@ class CreateKeypair(show.ShowOne): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) compute_client = self.app.client_manager.compute public_key = parsed_args.public_key @@ -93,8 +93,8 @@ class DeleteKeypair(command.Command): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) compute_client = self.app.client_manager.compute compute_client.keypairs.delete(parsed_args.name) return @@ -140,8 +140,8 @@ class ShowKeypair(show.ShowOne): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) compute_client = self.app.client_manager.compute keypair = utils.find_resource(compute_client.keypairs, parsed_args.name) diff --git a/openstackclient/compute/v2/security_group.py b/openstackclient/compute/v2/security_group.py index 3dc9bae0..3dd0c49b 100644 --- a/openstackclient/compute/v2/security_group.py +++ b/openstackclient/compute/v2/security_group.py @@ -50,10 +50,10 @@ def _xform_security_group_rule(sgroup): info['ip_range'] = info['ip_range']['cidr'] else: info['ip_range'] = '' - if info['ip_protocol'] == 'icmp': - info['port_range'] = '' - elif info['ip_protocol'] is None: + if info['ip_protocol'] is None: info['ip_protocol'] = '' + elif info['ip_protocol'].lower() == 'icmp': + info['port_range'] = '' return info @@ -107,8 +107,8 @@ class DeleteSecurityGroup(command.Command): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) compute_client = self.app.client_manager.compute data = utils.find_resource( @@ -199,8 +199,8 @@ class SetSecurityGroup(show.ShowOne): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) compute_client = self.app.client_manager.compute data = utils.find_resource( @@ -240,8 +240,8 @@ class ShowSecurityGroup(show.ShowOne): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) compute_client = self.app.client_manager.compute info = {} @@ -307,7 +307,10 @@ class CreateSecurityGroupRule(show.ShowOne): compute_client.security_groups, parsed_args.group, ) - from_port, to_port = parsed_args.dst_port + if parsed_args.proto.lower() == 'icmp': + from_port, to_port = -1, -1 + else: + from_port, to_port = parsed_args.dst_port data = compute_client.security_group_rules.create( group.id, parsed_args.proto, @@ -334,8 +337,8 @@ class DeleteSecurityGroupRule(command.Command): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) compute_client = self.app.client_manager.compute compute_client.security_group_rules.delete(parsed_args.rule) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 4efef975..661ce847 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -36,6 +36,7 @@ from openstackclient.common import exceptions from openstackclient.common import parseractions from openstackclient.common import utils from openstackclient.i18n import _ # noqa +from openstackclient.identity import common as identity_common from openstackclient.network import common @@ -107,14 +108,20 @@ def _prep_server_detail(compute_client, server): image_info = info.get('image', {}) if image_info: image_id = image_info.get('id', '') - image = utils.find_resource(compute_client.images, image_id) - info['image'] = "%s (%s)" % (image.name, image_id) + try: + image = utils.find_resource(compute_client.images, image_id) + info['image'] = "%s (%s)" % (image.name, image_id) + except Exception: + info['image'] = image_id # Convert the flavor blob to a name flavor_info = info.get('flavor', {}) flavor_id = flavor_info.get('id', '') - flavor = utils.find_resource(compute_client.flavors, flavor_id) - info['flavor'] = "%s (%s)" % (flavor.name, flavor_id) + try: + flavor = utils.find_resource(compute_client.flavors, flavor_id) + info['flavor'] = "%s (%s)" % (flavor.name, flavor_id) + except Exception: + info['flavor'] = flavor_id # NOTE(dtroyer): novaclient splits these into separate entries... # Format addresses in a useful way @@ -355,8 +362,8 @@ class CreateServer(show.ShowOne): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) compute_client = self.app.client_manager.compute volume_client = self.app.client_manager.volume @@ -553,8 +560,8 @@ class CreateServerImage(show.ShowOne): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) compute_client = self.app.client_manager.compute image_client = self.app.client_manager.image server = utils.find_resource( @@ -612,8 +619,8 @@ class DeleteServer(command.Command): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) compute_client = self.app.client_manager.compute for server in parsed_args.servers: server_obj = utils.find_resource( @@ -694,6 +701,17 @@ class ListServer(lister.Lister): help=_('Include all projects (admin only)'), ) parser.add_argument( + '--project', + metavar='<project>', + help="Search by project (admin only) (name or ID)") + identity_common.add_project_domain_option_to_parser(parser) + parser.add_argument( + '--user', + metavar='<user>', + help=_('Search by user (admin only) (name or ID)'), + ) + identity_common.add_user_domain_option_to_parser(parser) + parser.add_argument( '--long', action='store_true', default=False, @@ -701,9 +719,28 @@ class ListServer(lister.Lister): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) compute_client = self.app.client_manager.compute + identity_client = self.app.client_manager.identity + + project_id = None + if parsed_args.project: + project_id = identity_common.find_project( + identity_client, + parsed_args.project, + parsed_args.project_domain, + ).id + parsed_args.all_projects = True + + user_id = None + if parsed_args.user: + user_id = identity_common.find_project( + identity_client, + parsed_args.user, + parsed_args.user_domain, + ).id + search_opts = { 'reservation_id': parsed_args.reservation_id, 'ip': parsed_args.ip, @@ -714,7 +751,9 @@ class ListServer(lister.Lister): 'flavor': parsed_args.flavor, 'image': parsed_args.image, 'host': parsed_args.host, + 'tenant_id': project_id, 'all_tenants': parsed_args.all_projects, + 'user_id': user_id, } self.log.debug('search options: %s', search_opts) @@ -772,8 +811,8 @@ class LockServer(command.Command): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) compute_client = self.app.client_manager.compute utils.find_resource( @@ -845,8 +884,8 @@ class MigrateServer(command.Command): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) compute_client = self.app.client_manager.compute @@ -889,8 +928,8 @@ class PauseServer(command.Command): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) compute_client = self.app.client_manager.compute utils.find_resource( @@ -935,8 +974,8 @@ class RebootServer(command.Command): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) compute_client = self.app.client_manager.compute server = utils.find_resource( compute_client.servers, parsed_args.server) @@ -984,8 +1023,8 @@ class RebuildServer(show.ShowOne): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) compute_client = self.app.client_manager.compute # Lookup parsed_args.image @@ -1100,8 +1139,8 @@ class RescueServer(show.ShowOne): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) compute_client = self.app.client_manager.compute _, body = utils.find_resource( @@ -1146,8 +1185,8 @@ class ResizeServer(command.Command): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) compute_client = self.app.client_manager.compute server = utils.find_resource( @@ -1191,8 +1230,8 @@ class ResumeServer(command.Command): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) compute_client = self.app.client_manager.compute utils.find_resource( @@ -1232,8 +1271,8 @@ class SetServer(command.Command): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) compute_client = self.app.client_manager.compute server = utils.find_resource( @@ -1280,8 +1319,8 @@ class ShowServer(show.ShowOne): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) compute_client = self.app.client_manager.compute server = utils.find_resource(compute_client.servers, parsed_args.server) @@ -1403,8 +1442,8 @@ class SshServer(command.Command): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) compute_client = self.app.client_manager.compute server = utils.find_resource( @@ -1458,8 +1497,8 @@ class SuspendServer(command.Command): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) compute_client = self.app.client_manager.compute utils.find_resource( @@ -1482,8 +1521,8 @@ class UnlockServer(command.Command): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) compute_client = self.app.client_manager.compute utils.find_resource( @@ -1506,8 +1545,8 @@ class UnpauseServer(command.Command): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) compute_client = self.app.client_manager.compute utils.find_resource( @@ -1530,8 +1569,8 @@ class UnrescueServer(command.Command): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) compute_client = self.app.client_manager.compute utils.find_resource( @@ -1562,8 +1601,8 @@ class UnsetServer(command.Command): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) compute_client = self.app.client_manager.compute server = utils.find_resource( compute_client.servers, diff --git a/openstackclient/compute/v2/service.py b/openstackclient/compute/v2/service.py index 4b2ebac6..3a031bff 100644 --- a/openstackclient/compute/v2/service.py +++ b/openstackclient/compute/v2/service.py @@ -43,6 +43,7 @@ class ListService(lister.Lister): self.log.debug("take_action(%s)", parsed_args) compute_client = self.app.client_manager.compute columns = ( + "Id", "Binary", "Host", "Zone", diff --git a/openstackclient/identity/client.py b/openstackclient/identity/client.py index d7b663dd..bd882ce8 100644 --- a/openstackclient/identity/client.py +++ b/openstackclient/identity/client.py @@ -21,7 +21,7 @@ from openstackclient.common import utils LOG = logging.getLogger(__name__) -DEFAULT_IDENTITY_API_VERSION = '2' +DEFAULT_API_VERSION = '3' API_VERSION_OPTION = 'os_identity_api_version' API_NAME = 'identity' API_VERSIONS = { @@ -63,11 +63,9 @@ def build_option_parser(parser): parser.add_argument( '--os-identity-api-version', metavar='<identity-api-version>', - default=utils.env( - 'OS_IDENTITY_API_VERSION', - default=DEFAULT_IDENTITY_API_VERSION), + default=utils.env('OS_IDENTITY_API_VERSION'), help='Identity API version, default=' + - DEFAULT_IDENTITY_API_VERSION + + DEFAULT_API_VERSION + ' (Env: OS_IDENTITY_API_VERSION)') return auth.build_auth_plugins_option_parser(parser) diff --git a/openstackclient/identity/v2_0/catalog.py b/openstackclient/identity/v2_0/catalog.py index e166c855..9bc2755a 100644 --- a/openstackclient/identity/v2_0/catalog.py +++ b/openstackclient/identity/v2_0/catalog.py @@ -42,8 +42,8 @@ class ListCatalog(lister.Lister): log = logging.getLogger(__name__ + '.ListCatalog') + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) # This is ugly because if auth hasn't happened yet we need # to trigger it here. @@ -76,8 +76,8 @@ class ShowCatalog(show.ShowOne): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) # This is ugly because if auth hasn't happened yet we need # to trigger it here. diff --git a/openstackclient/identity/v2_0/ec2creds.py b/openstackclient/identity/v2_0/ec2creds.py index 348479ac..a7730ce2 100644 --- a/openstackclient/identity/v2_0/ec2creds.py +++ b/openstackclient/identity/v2_0/ec2creds.py @@ -52,8 +52,8 @@ class CreateEC2Creds(show.ShowOne): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity if parsed_args.project: @@ -105,8 +105,8 @@ class DeleteEC2Creds(command.Command): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity if parsed_args.user: @@ -135,8 +135,8 @@ class ListEC2Creds(lister.Lister): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity if parsed_args.user: @@ -178,8 +178,8 @@ class ShowEC2Creds(show.ShowOne): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity if parsed_args.user: diff --git a/openstackclient/identity/v2_0/endpoint.py b/openstackclient/identity/v2_0/endpoint.py index 370a931d..1744cc72 100644 --- a/openstackclient/identity/v2_0/endpoint.py +++ b/openstackclient/identity/v2_0/endpoint.py @@ -62,8 +62,8 @@ class CreateEndpoint(show.ShowOne): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity service = common.find_service(identity_client, parsed_args.service) endpoint = identity_client.endpoints.create( @@ -93,8 +93,8 @@ class DeleteEndpoint(command.Command): help=_('Endpoint ID to delete')) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity identity_client.endpoints.delete(parsed_args.endpoint) return @@ -115,8 +115,8 @@ class ListEndpoint(lister.Lister): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity if parsed_args.long: columns = ('ID', 'Region', 'Service Name', 'Service Type', @@ -150,8 +150,8 @@ class ShowEndpoint(show.ShowOne): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity data = identity_client.endpoints.list() match = None diff --git a/openstackclient/identity/v2_0/project.py b/openstackclient/identity/v2_0/project.py index d01807a6..97a95f28 100644 --- a/openstackclient/identity/v2_0/project.py +++ b/openstackclient/identity/v2_0/project.py @@ -70,8 +70,8 @@ class CreateProject(show.ShowOne): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity enabled = True @@ -118,8 +118,8 @@ class DeleteProject(command.Command): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity for project in parsed_args.projects: @@ -146,8 +146,8 @@ class ListProject(lister.Lister): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) if parsed_args.long: columns = ('ID', 'Name', 'Description', 'Enabled') else: @@ -202,8 +202,8 @@ class SetProject(command.Command): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity if (not parsed_args.name @@ -253,8 +253,8 @@ class ShowProject(show.ShowOne): help=_('Project to display (name or ID)')) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity info = {} diff --git a/openstackclient/identity/v2_0/role.py b/openstackclient/identity/v2_0/role.py index 3167f50f..e98f8cb3 100644 --- a/openstackclient/identity/v2_0/role.py +++ b/openstackclient/identity/v2_0/role.py @@ -54,8 +54,8 @@ class AddRole(show.ShowOne): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity role = utils.find_resource(identity_client.roles, parsed_args.role) project = utils.find_resource( @@ -93,8 +93,8 @@ class CreateRole(show.ShowOne): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity try: role = identity_client.roles.create(parsed_args.role_name) @@ -128,8 +128,8 @@ class DeleteRole(command.Command): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity for role in parsed_args.roles: @@ -160,8 +160,8 @@ class ListRole(lister.Lister): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity auth_ref = self.app.client_manager.auth_ref @@ -242,8 +242,8 @@ class ListUserRole(lister.Lister): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity auth_ref = self.app.client_manager.auth_ref @@ -316,8 +316,8 @@ class RemoveRole(command.Command): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity role = utils.find_resource(identity_client.roles, parsed_args.role) project = utils.find_resource( @@ -345,8 +345,8 @@ class ShowRole(show.ShowOne): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity role = utils.find_resource(identity_client.roles, parsed_args.role) diff --git a/openstackclient/identity/v2_0/service.py b/openstackclient/identity/v2_0/service.py index f8630238..c9d48441 100644 --- a/openstackclient/identity/v2_0/service.py +++ b/openstackclient/identity/v2_0/service.py @@ -59,8 +59,8 @@ class CreateService(show.ShowOne): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity type_or_name = parsed_args.type_or_name @@ -106,8 +106,8 @@ class DeleteService(command.Command): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity service = common.find_service(identity_client, parsed_args.service) identity_client.services.delete(service.id) @@ -129,8 +129,8 @@ class ListService(lister.Lister): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) if parsed_args.long: columns = ('ID', 'Name', 'Type', 'Description') @@ -163,8 +163,8 @@ class ShowService(show.ShowOne): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity auth_ref = self.app.client_manager.auth_ref diff --git a/openstackclient/identity/v2_0/token.py b/openstackclient/identity/v2_0/token.py index c8b003ee..5fed58e5 100644 --- a/openstackclient/identity/v2_0/token.py +++ b/openstackclient/identity/v2_0/token.py @@ -21,6 +21,7 @@ import six from cliff import command from cliff import show +from openstackclient.common import utils from openstackclient.i18n import _ # noqa @@ -33,8 +34,8 @@ class IssueToken(show.ShowOne): parser = super(IssueToken, self).get_parser(prog_name) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) token = self.app.client_manager.auth_ref.service_catalog.get_token() token['project_id'] = token.pop('tenant_id') diff --git a/openstackclient/identity/v2_0/user.py b/openstackclient/identity/v2_0/user.py index 500aeec3..76902e69 100644 --- a/openstackclient/identity/v2_0/user.py +++ b/openstackclient/identity/v2_0/user.py @@ -78,8 +78,8 @@ class CreateUser(show.ShowOne): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity if parsed_args.project: @@ -142,8 +142,8 @@ class DeleteUser(command.Command): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity for user in parsed_args.users: @@ -174,8 +174,8 @@ class ListUser(lister.Lister): help=_('List additional fields in output')) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity def _format_project(project): @@ -296,8 +296,8 @@ class SetUser(command.Command): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity if parsed_args.password_prompt: @@ -362,8 +362,8 @@ class ShowUser(show.ShowOne): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity info = {} diff --git a/openstackclient/identity/v3/catalog.py b/openstackclient/identity/v3/catalog.py index 1899f25e..76f7da51 100644 --- a/openstackclient/identity/v3/catalog.py +++ b/openstackclient/identity/v3/catalog.py @@ -39,8 +39,8 @@ class ListCatalog(lister.Lister): log = logging.getLogger(__name__ + '.ListCatalog') + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) # This is ugly because if auth hasn't happened yet we need # to trigger it here. @@ -73,8 +73,8 @@ class ShowCatalog(show.ShowOne): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) # This is ugly because if auth hasn't happened yet we need # to trigger it here. diff --git a/openstackclient/identity/v3/consumer.py b/openstackclient/identity/v3/consumer.py index ffbd5104..0a6ade67 100644 --- a/openstackclient/identity/v3/consumer.py +++ b/openstackclient/identity/v3/consumer.py @@ -40,8 +40,8 @@ class CreateConsumer(show.ShowOne): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity consumer = identity_client.oauth1.consumers.create( parsed_args.description @@ -64,8 +64,8 @@ class DeleteConsumer(command.Command): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity consumer = utils.find_resource( identity_client.oauth1.consumers, parsed_args.consumer) @@ -78,8 +78,8 @@ class ListConsumer(lister.Lister): log = logging.getLogger(__name__ + '.ListConsumer') + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) columns = ('ID', 'Description') data = self.app.client_manager.identity.oauth1.consumers.list() return (columns, @@ -108,8 +108,8 @@ class SetConsumer(command.Command): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity consumer = utils.find_resource( identity_client.oauth1.consumers, parsed_args.consumer) @@ -140,8 +140,8 @@ class ShowConsumer(show.ShowOne): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity consumer = utils.find_resource( identity_client.oauth1.consumers, parsed_args.consumer) diff --git a/openstackclient/identity/v3/credential.py b/openstackclient/identity/v3/credential.py index dacbc9af..dbd73e2e 100644 --- a/openstackclient/identity/v3/credential.py +++ b/openstackclient/identity/v3/credential.py @@ -57,8 +57,8 @@ class CreateCredential(show.ShowOne): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity user_id = utils.find_resource(identity_client.users, parsed_args.user).id @@ -91,8 +91,8 @@ class DeleteCredential(command.Command): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity identity_client.credentials.delete(parsed_args.credential) return @@ -103,8 +103,8 @@ class ListCredential(lister.Lister): log = logging.getLogger(__name__ + '.ListCredential') + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) columns = ('ID', 'Type', 'User ID', 'Blob', 'Project ID') column_headers = ('ID', 'Type', 'User ID', 'Data', 'Project ID') data = self.app.client_manager.identity.credentials.list() @@ -150,8 +150,8 @@ class SetCredential(command.Command): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity kwargs = {} if parsed_args.user: @@ -189,8 +189,8 @@ class ShowCredential(show.ShowOne): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity credential = utils.find_resource(identity_client.credentials, parsed_args.credential) diff --git a/openstackclient/identity/v3/domain.py b/openstackclient/identity/v3/domain.py index 38f99a97..aec530a8 100644 --- a/openstackclient/identity/v3/domain.py +++ b/openstackclient/identity/v3/domain.py @@ -63,8 +63,8 @@ class CreateDomain(show.ShowOne): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity enabled = True @@ -103,8 +103,8 @@ class DeleteDomain(command.Command): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity domain = utils.find_resource(identity_client.domains, parsed_args.domain) @@ -117,8 +117,8 @@ class ListDomain(lister.Lister): log = logging.getLogger(__name__ + '.ListDomain') + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) columns = ('ID', 'Name', 'Enabled', 'Description') data = self.app.client_manager.identity.domains.list() return (columns, @@ -163,8 +163,8 @@ class SetDomain(command.Command): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity domain = utils.find_resource(identity_client.domains, parsed_args.domain) @@ -200,8 +200,8 @@ class ShowDomain(show.ShowOne): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity domain = utils.find_resource(identity_client.domains, parsed_args.domain) diff --git a/openstackclient/identity/v3/ec2creds.py b/openstackclient/identity/v3/ec2creds.py index 03314634..a12ee25e 100644 --- a/openstackclient/identity/v3/ec2creds.py +++ b/openstackclient/identity/v3/ec2creds.py @@ -79,8 +79,8 @@ class CreateEC2Creds(show.ShowOne): common.add_project_domain_option_to_parser(parser) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity client_manager = self.app.client_manager user = _determine_ec2_user(parsed_args, client_manager) @@ -136,8 +136,8 @@ class DeleteEC2Creds(command.Command): common.add_user_domain_option_to_parser(parser) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', 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) @@ -158,8 +158,8 @@ class ListEC2Creds(lister.Lister): common.add_user_domain_option_to_parser(parser) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) client_manager = self.app.client_manager user = _determine_ec2_user(parsed_args, client_manager) @@ -194,8 +194,8 @@ class ShowEC2Creds(show.ShowOne): common.add_user_domain_option_to_parser(parser) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) client_manager = self.app.client_manager user = _determine_ec2_user(parsed_args, client_manager) creds = client_manager.identity.ec2.get(user, parsed_args.access_key) diff --git a/openstackclient/identity/v3/endpoint.py b/openstackclient/identity/v3/endpoint.py index 52db5ace..3d1c6f54 100644 --- a/openstackclient/identity/v3/endpoint.py +++ b/openstackclient/identity/v3/endpoint.py @@ -78,8 +78,8 @@ class CreateEndpoint(show.ShowOne): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity service = common.find_service(identity_client, parsed_args.service) @@ -113,8 +113,8 @@ class DeleteEndpoint(command.Command): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity endpoint_id = utils.find_resource(identity_client.endpoints, parsed_args.endpoint).id @@ -147,8 +147,8 @@ class ListEndpoint(lister.Lister): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity columns = ('ID', 'Region', 'Service Name', 'Service Type', 'Enabled', 'Interface', 'URL') @@ -221,8 +221,8 @@ class SetEndpoint(command.Command): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity endpoint = utils.find_resource(identity_client.endpoints, parsed_args.endpoint) @@ -270,8 +270,8 @@ class ShowEndpoint(show.ShowOne): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity endpoint = utils.find_resource(identity_client.endpoints, parsed_args.endpoint) diff --git a/openstackclient/identity/v3/federation_protocol.py b/openstackclient/identity/v3/federation_protocol.py index 57e8255e..20877fcd 100644 --- a/openstackclient/identity/v3/federation_protocol.py +++ b/openstackclient/identity/v3/federation_protocol.py @@ -50,8 +50,8 @@ class CreateProtocol(show.ShowOne): return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity protocol = identity_client.federation.protocols.create( protocol_id=parsed_args.federation_protocol, @@ -88,8 +88,8 @@ class DeleteProtocol(command.Command): return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity identity_client.federation.protocols.delete( parsed_args.identity_provider, parsed_args.federation_protocol) diff --git a/openstackclient/identity/v3/group.py b/openstackclient/identity/v3/group.py index d659f71e..82975065 100644 --- a/openstackclient/identity/v3/group.py +++ b/openstackclient/identity/v3/group.py @@ -50,8 +50,8 @@ class AddUserToGroup(command.Command): common.add_user_domain_option_to_parser(parser) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity user_id = common.find_user(identity_client, @@ -92,8 +92,8 @@ class CheckUserInGroup(command.Command): common.add_user_domain_option_to_parser(parser) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity user_id = common.find_user(identity_client, @@ -142,8 +142,8 @@ class CreateGroup(show.ShowOne): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity domain = None @@ -188,8 +188,8 @@ class DeleteGroup(command.Command): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity for group in parsed_args.groups: @@ -226,8 +226,8 @@ class ListGroup(lister.Lister): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity domain = None @@ -284,8 +284,8 @@ class RemoveUserFromGroup(command.Command): common.add_user_domain_option_to_parser(parser) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity user_id = common.find_user(identity_client, @@ -331,8 +331,8 @@ class SetGroup(command.Command): help='New group description') return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity group = common.find_group(identity_client, parsed_args.group, parsed_args.domain) @@ -368,8 +368,8 @@ class ShowGroup(show.ShowOne): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity group = common.find_group(identity_client, diff --git a/openstackclient/identity/v3/identity_provider.py b/openstackclient/identity/v3/identity_provider.py index 80965800..27982a9d 100644 --- a/openstackclient/identity/v3/identity_provider.py +++ b/openstackclient/identity/v3/identity_provider.py @@ -70,8 +70,8 @@ class CreateIdentityProvider(show.ShowOne): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity if parsed_args.remote_id_file: file_content = utils.read_blob_file_contents( @@ -105,8 +105,8 @@ class DeleteIdentityProvider(command.Command): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity identity_client.federation.identity_providers.delete( parsed_args.identity_provider) @@ -118,8 +118,8 @@ class ListIdentityProvider(lister.Lister): log = logging.getLogger(__name__ + '.ListIdentityProvider') + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) columns = ('ID', 'Enabled', 'Description') identity_client = self.app.client_manager.identity data = identity_client.federation.identity_providers.list() @@ -169,8 +169,8 @@ class SetIdentityProvider(command.Command): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) federation_client = self.app.client_manager.identity.federation # Basic argument checking @@ -218,8 +218,8 @@ class ShowIdentityProvider(show.ShowOne): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity identity_provider = utils.find_resource( identity_client.federation.identity_providers, diff --git a/openstackclient/identity/v3/policy.py b/openstackclient/identity/v3/policy.py index 82935423..9da94863 100644 --- a/openstackclient/identity/v3/policy.py +++ b/openstackclient/identity/v3/policy.py @@ -47,8 +47,8 @@ class CreatePolicy(show.ShowOne): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) blob = utils.read_blob_file_contents(parsed_args.rules) identity_client = self.app.client_manager.identity @@ -75,8 +75,8 @@ class DeletePolicy(command.Command): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity identity_client.policies.delete(parsed_args.policy) return @@ -97,8 +97,8 @@ class ListPolicy(lister.Lister): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) if parsed_args.long: columns = ('ID', 'Type', 'Blob') column_headers = ('ID', 'Type', 'Rules') @@ -137,8 +137,8 @@ class SetPolicy(command.Command): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity blob = None @@ -172,8 +172,8 @@ class ShowPolicy(show.ShowOne): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity policy = utils.find_resource(identity_client.policies, parsed_args.policy) diff --git a/openstackclient/identity/v3/project.py b/openstackclient/identity/v3/project.py index 8185d65a..96d7f97b 100644 --- a/openstackclient/identity/v3/project.py +++ b/openstackclient/identity/v3/project.py @@ -81,8 +81,8 @@ class CreateProject(show.ShowOne): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity domain = None @@ -146,8 +146,8 @@ class DeleteProject(command.Command): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity domain = None @@ -190,8 +190,8 @@ class ListProject(lister.Lister): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity if parsed_args.long: columns = ('ID', 'Name', 'Domain ID', 'Description', 'Enabled') @@ -271,8 +271,8 @@ class SetProject(command.Command): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity if (not parsed_args.name @@ -337,8 +337,8 @@ class ShowProject(show.ShowOne): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity if parsed_args.domain: diff --git a/openstackclient/identity/v3/region.py b/openstackclient/identity/v3/region.py index 5cb51fc5..1ff0b8c0 100644 --- a/openstackclient/identity/v3/region.py +++ b/openstackclient/identity/v3/region.py @@ -48,21 +48,14 @@ class CreateRegion(show.ShowOne): metavar='<description>', help=_('New region description'), ) - parser.add_argument( - '--url', - metavar='<url>', - help=_('New region url'), - ) - return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity region = identity_client.regions.create( id=parsed_args.region, - url=parsed_args.url, parent_region=parsed_args.parent_region, description=parsed_args.description, ) @@ -87,8 +80,8 @@ class DeleteRegion(command.Command): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity identity_client.regions.delete(parsed_args.region) @@ -109,16 +102,16 @@ class ListRegion(lister.Lister): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity kwargs = {} if parsed_args.parent_region: kwargs['parent_region_id'] = parsed_args.parent_region - columns_headers = ('Region', 'Parent Region', 'Description', 'URL') - columns = ('ID', 'Parent Region Id', 'Description', 'URL') + columns_headers = ('Region', 'Parent Region', 'Description') + columns = ('ID', 'Parent Region Id', 'Description') data = identity_client.regions.list(**kwargs) return (columns_headers, @@ -150,25 +143,16 @@ class SetRegion(command.Command): metavar='<description>', help=_('New region description'), ) - parser.add_argument( - '--url', - metavar='<url>', - help=_('New region url'), - ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity - if (not parsed_args.url - and not parsed_args.parent_region - and not parsed_args.description): + if not parsed_args.parent_region and not parsed_args.description: return kwargs = {} - if parsed_args.url: - kwargs['url'] = parsed_args.url if parsed_args.description: kwargs['description'] = parsed_args.description if parsed_args.parent_region: @@ -192,8 +176,8 @@ class ShowRegion(show.ShowOne): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity region = utils.find_resource(identity_client.regions, diff --git a/openstackclient/identity/v3/role.py b/openstackclient/identity/v3/role.py index 199b7dca..0e8c51ca 100644 --- a/openstackclient/identity/v3/role.py +++ b/openstackclient/identity/v3/role.py @@ -39,7 +39,7 @@ def _add_identity_and_resource_options_to_parser(parser): domain_or_project.add_argument( '--project', metavar='<project>', - help='Include `<project>` (name or ID)', + help='Include <project> (name or ID)', ) user_or_group = parser.add_mutually_exclusive_group() user_or_group.add_argument( @@ -101,9 +101,9 @@ def _process_identity_and_resource_options(parsed_args, kwargs['project'] = common.find_project( identity_client_manager, parsed_args.project, - parsed_args.group_domain, + parsed_args.project_domain, ).id - kwargs['inherited'] = parsed_args.inherited + kwargs['os_inherit_extension_inherited'] = parsed_args.inherited return kwargs @@ -122,8 +122,8 @@ class AddRole(command.Command): _add_identity_and_resource_options_to_parser(parser) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity if (not parsed_args.user and not parsed_args.domain @@ -166,8 +166,8 @@ class CreateRole(show.ShowOne): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity try: @@ -199,8 +199,8 @@ class DeleteRole(command.Command): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity for role in parsed_args.roles: @@ -260,6 +260,7 @@ class ListRole(lister.Lister): data = identity_client.roles.list( user=user, domain=domain, + os_inherit_extension_inherited=parsed_args.inherited ) for user_role in data: user_role.user = user.name @@ -269,6 +270,7 @@ class ListRole(lister.Lister): data = identity_client.roles.list( user=user, project=project, + os_inherit_extension_inherited=parsed_args.inherited ) for user_role in data: user_role.user = user.name @@ -278,12 +280,14 @@ class ListRole(lister.Lister): data = identity_client.roles.list( user=user, domain='default', + os_inherit_extension_inherited=parsed_args.inherited ) elif parsed_args.group and parsed_args.domain: columns = ('ID', 'Name', 'Domain', 'Group') data = identity_client.roles.list( group=group, domain=domain, + os_inherit_extension_inherited=parsed_args.inherited ) for group_role in data: group_role.group = group.name @@ -293,6 +297,7 @@ class ListRole(lister.Lister): data = identity_client.roles.list( group=group, project=project, + os_inherit_extension_inherited=parsed_args.inherited ) for group_role in data: group_role.group = group.name @@ -325,8 +330,8 @@ class RemoveRole(command.Command): _add_identity_and_resource_options_to_parser(parser) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity if (not parsed_args.user and not parsed_args.domain @@ -368,8 +373,8 @@ class SetRole(command.Command): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity if not parsed_args.name: @@ -398,8 +403,8 @@ class ShowRole(show.ShowOne): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity role = utils.find_resource( diff --git a/openstackclient/identity/v3/role_assignment.py b/openstackclient/identity/v3/role_assignment.py index 24e3a7f7..169c6cb9 100644 --- a/openstackclient/identity/v3/role_assignment.py +++ b/openstackclient/identity/v3/role_assignment.py @@ -45,11 +45,13 @@ class ListRoleAssignment(lister.Lister): metavar='<user>', help='User to filter (name or ID)', ) + common.add_user_domain_option_to_parser(parser) user_or_group.add_argument( '--group', metavar='<group>', help='Group to filter (name or ID)', ) + common.add_group_domain_option_to_parser(parser) domain_or_project = parser.add_mutually_exclusive_group() domain_or_project.add_argument( '--domain', @@ -61,12 +63,13 @@ class ListRoleAssignment(lister.Lister): metavar='<project>', help='Project to filter (name or ID)', ) - + common.add_project_domain_option_to_parser(parser) + common.add_inherited_option_to_parser(parser) return parser def _as_tuple(self, assignment): return (assignment.role, assignment.user, assignment.group, - assignment.project, assignment.domain) + assignment.project, assignment.domain, assignment.inherited) def take_action(self, parsed_args): self.log.debug('take_action(%s)' % parsed_args) @@ -84,6 +87,7 @@ class ListRoleAssignment(lister.Lister): user = common.find_user( identity_client, parsed_args.user, + parsed_args.user_domain, ) domain = None @@ -98,6 +102,7 @@ class ListRoleAssignment(lister.Lister): project = common.find_project( identity_client, parsed_args.project, + parsed_args.project_domain, ) group = None @@ -105,18 +110,22 @@ class ListRoleAssignment(lister.Lister): group = common.find_group( identity_client, parsed_args.group, + parsed_args.group_domain, ) effective = True if parsed_args.effective else False self.log.debug('take_action(%s)' % parsed_args) - columns = ('Role', 'User', 'Group', 'Project', 'Domain') + columns = ('Role', 'User', 'Group', 'Project', 'Domain', 'Inherited') + + inherited_to = 'projects' if parsed_args.inherited else None data = identity_client.role_assignments.list( domain=domain, user=user, group=group, project=project, role=role, - effective=effective) + effective=effective, + os_inherit_extension_inherited_to=inherited_to) data_parsed = [] for assignment in data: @@ -133,6 +142,9 @@ class ListRoleAssignment(lister.Lister): assignment.domain = '' assignment.project = '' + inherited = scope.get('OS-INHERIT:inherited_to') == 'projects' + assignment.inherited = inherited + del assignment.scope if hasattr(assignment, 'user'): diff --git a/openstackclient/identity/v3/service.py b/openstackclient/identity/v3/service.py index d63a9537..85081aa7 100644 --- a/openstackclient/identity/v3/service.py +++ b/openstackclient/identity/v3/service.py @@ -61,8 +61,8 @@ class CreateService(show.ShowOne): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity enabled = True @@ -94,8 +94,8 @@ class DeleteService(command.Command): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity service = common.find_service(identity_client, parsed_args.service) @@ -119,8 +119,8 @@ class ListService(lister.Lister): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) if parsed_args.long: columns = ('ID', 'Name', 'Type', 'Description', 'Enabled') @@ -173,8 +173,8 @@ class SetService(command.Command): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity if (not parsed_args.name @@ -219,8 +219,8 @@ class ShowService(show.ShowOne): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity service = common.find_service(identity_client, parsed_args.service) diff --git a/openstackclient/identity/v3/service_provider.py b/openstackclient/identity/v3/service_provider.py index 78f96b6c..838ad4a2 100644 --- a/openstackclient/identity/v3/service_provider.py +++ b/openstackclient/identity/v3/service_provider.py @@ -73,8 +73,8 @@ class CreateServiceProvider(show.ShowOne): return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) service_client = self.app.client_manager.identity sp = service_client.federation.service_providers.create( id=parsed_args.service_provider_id, @@ -101,8 +101,8 @@ class DeleteServiceProvider(command.Command): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) service_client = self.app.client_manager.identity service_client.federation.service_providers.delete( parsed_args.service_provider) @@ -114,8 +114,8 @@ class ListServiceProvider(lister.Lister): log = logging.getLogger(__name__ + '.ListServiceProvider') + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) service_client = self.app.client_manager.identity data = service_client.federation.service_providers.list() @@ -168,8 +168,8 @@ class SetServiceProvider(command.Command): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) federation_client = self.app.client_manager.identity.federation enabled = None @@ -207,8 +207,8 @@ class ShowServiceProvider(show.ShowOne): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) service_client = self.app.client_manager.identity service_provider = utils.find_resource( service_client.federation.service_providers, diff --git a/openstackclient/identity/v3/token.py b/openstackclient/identity/v3/token.py index 7000b62c..cd3dc798 100644 --- a/openstackclient/identity/v3/token.py +++ b/openstackclient/identity/v3/token.py @@ -182,8 +182,8 @@ class IssueToken(show.ShowOne): parser = super(IssueToken, self).get_parser(prog_name) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) token = self.app.client_manager.auth_ref.service_catalog.get_token() if 'tenant_id' in token: token['project_id'] = token.pop('tenant_id') diff --git a/openstackclient/identity/v3/unscoped_saml.py b/openstackclient/identity/v3/unscoped_saml.py index fddac68f..e659e75e 100644 --- a/openstackclient/identity/v3/unscoped_saml.py +++ b/openstackclient/identity/v3/unscoped_saml.py @@ -49,8 +49,8 @@ class ListAccessibleDomains(lister.Lister): log = logging.getLogger(__name__ + '.ListAccessibleDomains') @auth_with_unscoped_saml + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) columns = ('ID', 'Enabled', 'Name', 'Description') identity_client = self.app.client_manager.identity data = identity_client.federation.domains.list() @@ -67,8 +67,8 @@ class ListAccessibleProjects(lister.Lister): log = logging.getLogger(__name__ + '.ListAccessibleProjects') @auth_with_unscoped_saml + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) columns = ('ID', 'Domain ID', 'Enabled', 'Name') identity_client = self.app.client_manager.identity data = identity_client.federation.projects.list() diff --git a/openstackclient/identity/v3/user.py b/openstackclient/identity/v3/user.py index 459707d2..0e894544 100644 --- a/openstackclient/identity/v3/user.py +++ b/openstackclient/identity/v3/user.py @@ -91,8 +91,8 @@ class CreateUser(show.ShowOne): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity project_id = None @@ -155,8 +155,8 @@ class DeleteUser(command.Command): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity domain = None @@ -205,8 +205,8 @@ class ListUser(lister.Lister): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity domain = None @@ -214,13 +214,11 @@ class ListUser(lister.Lister): domain = common.find_domain(identity_client, parsed_args.domain).id + group = None if parsed_args.group: - group = utils.find_resource( - identity_client.groups, - parsed_args.group, - ).id - else: - group = None + group = common.find_group(identity_client, + parsed_args.group, + parsed_args.domain).id if parsed_args.project: if domain is not None: @@ -336,8 +334,8 @@ class SetUser(command.Command): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity if parsed_args.password_prompt: @@ -396,8 +394,8 @@ class SetPasswordUser(command.Command): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity current_password = utils.get_password( @@ -430,8 +428,8 @@ class ShowUser(show.ShowOne): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity if parsed_args.domain: diff --git a/openstackclient/image/client.py b/openstackclient/image/client.py index 8fbf8c0f..8dd146e9 100644 --- a/openstackclient/image/client.py +++ b/openstackclient/image/client.py @@ -20,7 +20,7 @@ from openstackclient.common import utils LOG = logging.getLogger(__name__) -DEFAULT_IMAGE_API_VERSION = '1' +DEFAULT_API_VERSION = '1' API_VERSION_OPTION = 'os_image_api_version' API_NAME = "image" API_VERSIONS = { @@ -81,10 +81,8 @@ def build_option_parser(parser): parser.add_argument( '--os-image-api-version', metavar='<image-api-version>', - default=utils.env( - 'OS_IMAGE_API_VERSION', - default=DEFAULT_IMAGE_API_VERSION), + default=utils.env('OS_IMAGE_API_VERSION'), help='Image API version, default=' + - DEFAULT_IMAGE_API_VERSION + + DEFAULT_API_VERSION + ' (Env: OS_IMAGE_API_VERSION)') return parser diff --git a/openstackclient/image/v1/image.py b/openstackclient/image/v1/image.py index 68c81cd5..35e9ef43 100644 --- a/openstackclient/image/v1/image.py +++ b/openstackclient/image/v1/image.py @@ -213,6 +213,8 @@ class CreateImage(show.ShowOne): if parsed_args.private: kwargs['is_public'] = False + info = {} + if not parsed_args.location and not parsed_args.copy_from: if parsed_args.volume: volume_client = self.app.client_manager.volume @@ -241,18 +243,18 @@ class CreateImage(show.ShowOne): # do a chunked transfer kwargs["data"] = sys.stdin - # Wrap the call to catch exceptions in order to close files - try: - image = image_client.images.create(**kwargs) - finally: - # Clean up open files - make sure data isn't a string - if ('data' in kwargs and hasattr(kwargs['data'], 'close') and - kwargs['data'] != sys.stdin): - kwargs['data'].close() - - info = {} - info.update(image._info) - info['properties'] = utils.format_dict(info.get('properties', {})) + if not parsed_args.volume: + # Wrap the call to catch exceptions in order to close files + try: + image = image_client.images.create(**kwargs) + finally: + # Clean up open files - make sure data isn't a string + if ('data' in kwargs and hasattr(kwargs['data'], 'close') and + kwargs['data'] != sys.stdin): + kwargs['data'].close() + + info.update(image._info) + info['properties'] = utils.format_dict(info.get('properties', {})) return zip(*sorted(six.iteritems(info))) @@ -452,7 +454,7 @@ class SaveImage(command.Command): gc_utils.save_image(data, parsed_args.file) -class SetImage(show.ShowOne): +class SetImage(command.Command): """Set image properties""" log = logging.getLogger(__name__ + ".SetImage") @@ -629,7 +631,7 @@ class SetImage(show.ShowOne): volume_client.volumes, parsed_args.volume, ) - response, body = volume_client.volumes.upload_to_image( + volume_client.volumes.upload_to_image( source_volume.id, parsed_args.force, parsed_args.image, @@ -640,7 +642,6 @@ class SetImage(show.ShowOne): if parsed_args.disk_format else image.disk_format), ) - info = body['os-volume_upload_image'] elif parsed_args.file: # Send an open file handle to glanceclient so it will # do a chunked transfer @@ -673,10 +674,7 @@ class SetImage(show.ShowOne): kwargs['data'] != sys.stdin): kwargs['data'].close() - info = {} - info.update(image._info) - info['properties'] = utils.format_dict(info.get('properties', {})) - return zip(*sorted(six.iteritems(info))) + return class ShowImage(show.ShowOne): diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index 4c019db6..7d8b1412 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -22,14 +22,50 @@ import six from cliff import command from cliff import lister from cliff import show - from glanceclient.common import utils as gc_utils + from openstackclient.api import utils as api_utils +from openstackclient.common import exceptions from openstackclient.common import parseractions from openstackclient.common import utils from openstackclient.identity import common +DEFAULT_CONTAINER_FORMAT = 'bare' +DEFAULT_DISK_FORMAT = 'raw' + + +def _format_image(image): + """Format an image to make it more consistent with OSC operations. """ + + info = {} + properties = {} + + # the only fields we're not including is "links", "tags" and the properties + fields_to_show = ['status', 'name', 'container_format', 'created_at', + 'size', 'disk_format', 'updated_at', 'visibility', + 'min_disk', 'protected', 'id', 'file', 'checksum', + 'owner', 'virtual_size', 'min_ram', 'schema'] + + # split out the usual key and the properties which are top-level + for key in six.iterkeys(image): + if key in fields_to_show: + info[key] = image.get(key) + elif key == 'tags': + continue # handle this later + else: + properties[key] = image.get(key) + + # format the tags if they are there + info['tags'] = utils.format_list(image.get('tags')) + + # add properties back into the dictionary as a top-level key + if properties: + info['properties'] = utils.format_dict(properties) + + return info + + class AddProjectToImage(show.ShowOne): """Associate project with image""" @@ -72,6 +108,187 @@ class AddProjectToImage(show.ShowOne): return zip(*sorted(six.iteritems(image_member._info))) +class CreateImage(show.ShowOne): + """Create/upload an image""" + + log = logging.getLogger(__name__ + ".CreateImage") + deadopts = ('owner', 'size', 'location', 'copy-from', 'checksum', 'store') + + def get_parser(self, prog_name): + parser = super(CreateImage, self).get_parser(prog_name) + # TODO(mordred): add --volume and --force parameters and support + # TODO(bunting): There are additional arguments that v1 supported + # that v2 either doesn't support or supports weirdly. + # --checksum - could be faked clientside perhaps? + # --owner - could be set as an update after the put? + # --location - maybe location add? + # --size - passing image size is actually broken in python-glanceclient + # --copy-from - does not exist in v2 + # --store - does not exits in v2 + parser.add_argument( + "name", + metavar="<image-name>", + help="New image name", + ) + parser.add_argument( + "--id", + metavar="<id>", + help="Image ID to reserve", + ) + parser.add_argument( + "--container-format", + default=DEFAULT_CONTAINER_FORMAT, + metavar="<container-format>", + help="Image container format " + "(default: %s)" % DEFAULT_CONTAINER_FORMAT, + ) + parser.add_argument( + "--disk-format", + default=DEFAULT_DISK_FORMAT, + metavar="<disk-format>", + help="Image disk format " + "(default: %s)" % DEFAULT_DISK_FORMAT, + ) + parser.add_argument( + "--min-disk", + metavar="<disk-gb>", + type=int, + help="Minimum disk size needed to boot image, in gigabytes", + ) + parser.add_argument( + "--min-ram", + metavar="<ram-mb>", + type=int, + help="Minimum RAM size needed to boot image, in megabytes", + ) + parser.add_argument( + "--file", + metavar="<file>", + help="Upload image from local file", + ) + protected_group = parser.add_mutually_exclusive_group() + protected_group.add_argument( + "--protected", + action="store_true", + help="Prevent image from being deleted", + ) + protected_group.add_argument( + "--unprotected", + action="store_true", + help="Allow image to be deleted (default)", + ) + public_group = parser.add_mutually_exclusive_group() + public_group.add_argument( + "--public", + action="store_true", + help="Image is accessible to the public", + ) + public_group.add_argument( + "--private", + action="store_true", + help="Image is inaccessible to the public (default)", + ) + parser.add_argument( + "--property", + dest="properties", + metavar="<key=value>", + action=parseractions.KeyValueAction, + help="Set a property on this image " + "(repeat option to set multiple properties)", + ) + parser.add_argument( + "--tag", + dest="tags", + metavar="<tag>", + action='append', + help="Set a tag on this image " + "(repeat option to set multiple tags)", + ) + for deadopt in self.deadopts: + parser.add_argument( + "--%s" % deadopt, + metavar="<%s>" % deadopt, + dest=deadopt.replace('-', '_'), + help=argparse.SUPPRESS, + ) + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action(%s)", parsed_args) + image_client = self.app.client_manager.image + + for deadopt in self.deadopts: + if getattr(parsed_args, deadopt.replace('-', '_'), None): + raise exceptions.CommandError( + "ERROR: --%s was given, which is an Image v1 option" + " that is no longer supported in Image v2" % deadopt) + + # Build an attribute dict from the parsed args, only include + # attributes that were actually set on the command line + kwargs = {} + copy_attrs = ('name', 'id', + 'container_format', 'disk_format', + 'min_disk', 'min_ram', + 'tags') + for attr in copy_attrs: + if attr in parsed_args: + val = getattr(parsed_args, attr, None) + if val: + # Only include a value in kwargs for attributes that + # are actually present on the command line + kwargs[attr] = val + # properties should get flattened into the general kwargs + if getattr(parsed_args, 'properties', None): + for k, v in six.iteritems(parsed_args.properties): + kwargs[k] = str(v) + # Handle exclusive booleans with care + # Avoid including attributes in kwargs if an option is not + # present on the command line. These exclusive booleans are not + # a single value for the pair of options because the default must be + # to do nothing when no options are present as opposed to always + # setting a default. + if parsed_args.protected: + kwargs['protected'] = True + if parsed_args.unprotected: + kwargs['protected'] = False + if parsed_args.public: + kwargs['visibility'] = 'public' + if parsed_args.private: + kwargs['visibility'] = 'private' + + # open the file first to ensure any failures are handled before the + # image is created + fp = gc_utils.get_data_file(parsed_args) + + if fp is None and parsed_args.file: + self.log.warning("Failed to get an image file.") + return {}, {} + + image = image_client.images.create(**kwargs) + + if fp is not None: + with fp: + try: + image_client.images.upload(image.id, fp) + except Exception as e: + # 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 + # the Image already exists and should be left alone. + try: + if 'id' not in kwargs: + image_client.images.delete(image.id) + except Exception: + pass # we don't care about this one + raise e # now, throw the upload exception again + + # update the image after the data has been uploaded + image = image_client.images.get(image.id) + + info = _format_image(image) + return zip(*sorted(six.iteritems(info))) + + class DeleteImage(command.Command): """Delete image(s)""" @@ -229,7 +446,7 @@ class ListImage(lister.Lister): s, columns, formatters={ - 'tags': utils.format_dict, + 'tags': utils.format_list, }, ) for s in data) ) @@ -304,41 +521,24 @@ class SaveImage(command.Command): gc_utils.save_image(data, parsed_args.file) -class ShowImage(show.ShowOne): - """Display image details""" - - log = logging.getLogger(__name__ + ".ShowImage") - - def get_parser(self, prog_name): - parser = super(ShowImage, self).get_parser(prog_name) - parser.add_argument( - "image", - metavar="<image>", - help="Image to display (name or ID)", - ) - return parser - - def take_action(self, parsed_args): - self.log.debug("take_action(%s)", parsed_args) - - image_client = self.app.client_manager.image - image = utils.find_resource( - image_client.images, - parsed_args.image, - ) - - info = {} - info.update(image) - return zip(*sorted(six.iteritems(info))) - - -class SetImage(show.ShowOne): +class SetImage(command.Command): """Set image properties""" log = logging.getLogger(__name__ + ".SetImage") + deadopts = ('visibility',) def get_parser(self, prog_name): parser = super(SetImage, self).get_parser(prog_name) + # TODO(bunting): There are additional arguments that v1 supported + # --size - does not exist in v2 + # --store - does not exist in v2 + # --location - maybe location add? + # --copy-from - does not exist in v2 + # --file - should be able to upload file + # --volume - needs adding + # --force - needs adding + # --checksum - maybe could be done client side + # --stdin - could be implemented parser.add_argument( "image", metavar="<image>", @@ -350,100 +550,133 @@ class SetImage(show.ShowOne): help="New image name" ) parser.add_argument( - "--architecture", - metavar="<architecture>", - help="Operating system Architecture" + "--owner", + metavar="<project>", + help="New image owner project (name or ID)", + ) + parser.add_argument( + "--min-disk", + type=int, + metavar="<disk-gb>", + help="Minimum disk size needed to boot image, in gigabytes" + ) + parser.add_argument( + "--min-ram", + type=int, + metavar="<ram-mb>", + help="Minimum RAM size needed to boot image, in megabytes", + ) + parser.add_argument( + "--container-format", + metavar="<container-format>", + help="Image container format " + "(default: %s)" % DEFAULT_CONTAINER_FORMAT, ) parser.add_argument( + "--disk-format", + metavar="<disk-format>", + help="Image disk format " + "(default: %s)" % DEFAULT_DISK_FORMAT, + ) + protected_group = parser.add_mutually_exclusive_group() + protected_group.add_argument( "--protected", - dest="protected", action="store_true", - help="Prevent image from being deleted" + help="Prevent image from being deleted", + ) + protected_group.add_argument( + "--unprotected", + action="store_true", + help="Allow image to be deleted (default)", + ) + public_group = parser.add_mutually_exclusive_group() + public_group.add_argument( + "--public", + action="store_true", + help="Image is accessible to the public", + ) + public_group.add_argument( + "--private", + action="store_true", + help="Image is inaccessible to the public (default)", ) parser.add_argument( - "--instance-uuid", - metavar="<instance_uuid>", - help="ID of instance used to create this image" + "--property", + dest="properties", + metavar="<key=value>", + action=parseractions.KeyValueAction, + help="Set a property on this image " + "(repeat option to set multiple properties)", ) parser.add_argument( - "--min-disk", - type=int, - metavar="<disk-gb>", - help="Minimum disk size needed to boot image, in gigabytes" + "--tag", + dest="tags", + metavar="<tag>", + default=[], + action='append', + help="Set a tag on this image " + "(repeat option to set multiple tags)", ) - visibility_choices = ["public", "private"] parser.add_argument( - "--visibility", - metavar="<visibility>", - choices=visibility_choices, - help="Scope of image accessibility. Valid values: %s" - % visibility_choices + "--architecture", + metavar="<architecture>", + help="Operating system architecture", ) - help_msg = ("ID of image in Glance that should be used as the kernel" - " when booting an AMI-style image") parser.add_argument( - "--kernel-id", - metavar="<kernel-id>", - help=help_msg + "--instance-id", + metavar="<instance-id>", + help="ID of server instance used to create this image", ) parser.add_argument( - "--os-version", - metavar="<os-version>", - help="Operating system version as specified by the distributor" + "--instance-uuid", + metavar="<instance-id>", + dest="instance_id", + help=argparse.SUPPRESS, ) - disk_choices = ["None", "ami", "ari", "aki", "vhd", "vmdk", "raw", - "qcow2", "vdi", "iso"] - help_msg = ("Format of the disk. Valid values: %s" % disk_choices) parser.add_argument( - "--disk-format", - metavar="<disk-format>", - choices=disk_choices, - help=help_msg + "--kernel-id", + metavar="<kernel-id>", + help="ID of kernel image used to boot this disk image", ) parser.add_argument( "--os-distro", metavar="<os-distro>", - help="Common name of operating system distribution" + help="Operating system distribution name", ) parser.add_argument( - "--owner", - metavar="<owner>", - help="New Owner of the image" + "--os-version", + metavar="<os-version>", + help="Operating system distribution version", ) - msg = ("ID of image stored in Glance that should be used as the " - "ramdisk when booting an AMI-style image") parser.add_argument( "--ramdisk-id", metavar="<ramdisk-id>", - help=msg - ) - parser.add_argument( - "--min-ram", - type=int, - metavar="<ram-mb>", - help="Amount of RAM (in MB) required to boot image" - ) - container_choices = ["None", "ami", "ari", "aki", "bare", "ovf", "ova"] - help_msg = ("Format of the container. Valid values: %s" - % container_choices) - parser.add_argument( - "--container-format", - metavar="<container-format>", - choices=container_choices, - help=help_msg - ) + help="ID of ramdisk image used to boot this disk image", + ) + for deadopt in self.deadopts: + parser.add_argument( + "--%s" % deadopt, + metavar="<%s>" % deadopt, + dest=deadopt.replace('-', '_'), + help=argparse.SUPPRESS, + ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) image_client = self.app.client_manager.image + for deadopt in self.deadopts: + if getattr(parsed_args, deadopt.replace('-', '_'), None): + raise exceptions.CommandError( + "ERROR: --%s was given, which is an Image v1 option" + " that is no longer supported in Image v2" % deadopt) + kwargs = {} copy_attrs = ('architecture', 'container_format', 'disk_format', - 'file', 'kernel_id', 'locations', 'name', + 'file', 'instance_id', 'kernel_id', 'locations', 'min_disk', 'min_ram', 'name', 'os_distro', 'os_version', - 'owner', 'prefix', 'progress', 'ramdisk_id', - 'visibility') + 'owner', 'prefix', 'progress', 'ramdisk_id', 'tags') for attr in copy_attrs: if attr in parsed_args: val = getattr(parsed_args, attr, None) @@ -451,10 +684,26 @@ class SetImage(show.ShowOne): # Only include a value in kwargs for attributes that are # actually present on the command line kwargs[attr] = val + + # Properties should get flattened into the general kwargs + if getattr(parsed_args, 'properties', None): + for k, v in six.iteritems(parsed_args.properties): + kwargs[k] = str(v) + + # Handle exclusive booleans with care + # Avoid including attributes in kwargs if an option is not + # present on the command line. These exclusive booleans are not + # a single value for the pair of options because the default must be + # to do nothing when no options are present as opposed to always + # setting a default. if parsed_args.protected: kwargs['protected'] = True - else: + if parsed_args.unprotected: kwargs['protected'] = False + if parsed_args.public: + kwargs['visibility'] = 'public' + if parsed_args.private: + kwargs['visibility'] = 'private' if not kwargs: self.log.warning("No arguments specified") @@ -463,7 +712,35 @@ class SetImage(show.ShowOne): image = utils.find_resource( image_client.images, parsed_args.image) + if parsed_args.tags: + # Tags should be extended, but duplicates removed + kwargs['tags'] = list(set(image.tags).union(set(parsed_args.tags))) + image = image_client.images.update(image.id, **kwargs) - info = {} - info.update(image) + + +class ShowImage(show.ShowOne): + """Display image details""" + + log = logging.getLogger(__name__ + ".ShowImage") + + def get_parser(self, prog_name): + parser = super(ShowImage, self).get_parser(prog_name) + parser.add_argument( + "image", + metavar="<image>", + help="Image to display (name or ID)", + ) + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action(%s)", parsed_args) + + image_client = self.app.client_manager.image + image = utils.find_resource( + image_client.images, + parsed_args.image, + ) + + info = _format_image(image) return zip(*sorted(six.iteritems(info))) diff --git a/openstackclient/network/client.py b/openstackclient/network/client.py index 0ef68852..5f72782b 100644 --- a/openstackclient/network/client.py +++ b/openstackclient/network/client.py @@ -18,7 +18,7 @@ from openstackclient.common import utils LOG = logging.getLogger(__name__) -DEFAULT_NETWORK_API_VERSION = '2' +DEFAULT_API_VERSION = '2' API_VERSION_OPTION = 'os_network_api_version' API_NAME = "network" API_VERSIONS = { @@ -83,10 +83,8 @@ def build_option_parser(parser): parser.add_argument( '--os-network-api-version', metavar='<network-api-version>', - default=utils.env( - 'OS_NETWORK_API_VERSION', - default=DEFAULT_NETWORK_API_VERSION), + default=utils.env('OS_NETWORK_API_VERSION'), help='Network API version, default=' + - DEFAULT_NETWORK_API_VERSION + + DEFAULT_API_VERSION + ' (Env: OS_NETWORK_API_VERSION)') return parser diff --git a/openstackclient/object/client.py b/openstackclient/object/client.py index 0359940d..e7587802 100644 --- a/openstackclient/object/client.py +++ b/openstackclient/object/client.py @@ -22,7 +22,7 @@ from openstackclient.common import utils LOG = logging.getLogger(__name__) -DEFAULT_OBJECT_API_VERSION = '1' +DEFAULT_API_VERSION = '1' API_VERSION_OPTION = 'os_object_api_version' API_NAME = 'object_store' API_VERSIONS = { @@ -52,10 +52,8 @@ def build_option_parser(parser): parser.add_argument( '--os-object-api-version', metavar='<object-api-version>', - default=utils.env( - 'OS_OBJECT_API_VERSION', - default=DEFAULT_OBJECT_API_VERSION), + default=utils.env('OS_OBJECT_API_VERSION'), help='Object API version, default=' + - DEFAULT_OBJECT_API_VERSION + + DEFAULT_API_VERSION + ' (Env: OS_OBJECT_API_VERSION)') return parser diff --git a/openstackclient/object/v1/account.py b/openstackclient/object/v1/account.py new file mode 100644 index 00000000..aa94ff5c --- /dev/null +++ b/openstackclient/object/v1/account.py @@ -0,0 +1,85 @@ +# 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. +# + +"""Account v1 action implementations""" + +import logging + +from cliff import command +from cliff import show +import six + +from openstackclient.common import parseractions +from openstackclient.common import utils + + +class SetAccount(command.Command): + """Set account properties""" + + log = logging.getLogger(__name__ + '.SetAccount') + + def get_parser(self, prog_name): + parser = super(SetAccount, self).get_parser(prog_name) + parser.add_argument( + "--property", + metavar="<key=value>", + required=True, + action=parseractions.KeyValueAction, + help="Set a property on this account " + "(repeat option to set multiple properties)" + ) + return parser + + @utils.log_method(log) + def take_action(self, parsed_args): + self.app.client_manager.object_store.account_set( + properties=parsed_args.property, + ) + + +class ShowAccount(show.ShowOne): + """Display account details""" + + log = logging.getLogger(__name__ + '.ShowAccount') + + @utils.log_method(log) + def take_action(self, parsed_args): + data = self.app.client_manager.object_store.account_show() + if 'properties' in data: + data['properties'] = utils.format_dict(data.pop('properties')) + return zip(*sorted(six.iteritems(data))) + + +class UnsetAccount(command.Command): + """Unset account properties""" + + log = logging.getLogger(__name__ + '.UnsetAccount') + + def get_parser(self, prog_name): + parser = super(UnsetAccount, self).get_parser(prog_name) + parser.add_argument( + '--property', + metavar='<key>', + required=True, + action='append', + default=[], + help='Property to remove from account ' + '(repeat option to remove multiple properties)', + ) + return parser + + @utils.log_method(log) + def take_action(self, parsed_args): + self.app.client_manager.object_store.account_unset( + properties=parsed_args.property, + ) diff --git a/openstackclient/object/v1/container.py b/openstackclient/object/v1/container.py index bc4fdec8..8c8844e2 100644 --- a/openstackclient/object/v1/container.py +++ b/openstackclient/object/v1/container.py @@ -23,6 +23,7 @@ from cliff import command from cliff import lister from cliff import show +from openstackclient.common import parseractions from openstackclient.common import utils @@ -41,8 +42,8 @@ class CreateContainer(lister.Lister): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) results = [] for container in parsed_args.containers: @@ -74,8 +75,8 @@ class DeleteContainer(command.Command): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) for container in parsed_args.containers: self.app.client_manager.object_store.container_delete( @@ -125,8 +126,8 @@ class ListContainer(lister.Lister): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) if parsed_args.long: columns = ('Name', 'Bytes', 'Count') @@ -178,6 +179,36 @@ class SaveContainer(command.Command): ) +class SetContainer(command.Command): + """Set container properties""" + + log = logging.getLogger(__name__ + '.SetContainer') + + def get_parser(self, prog_name): + parser = super(SetContainer, self).get_parser(prog_name) + parser.add_argument( + 'container', + metavar='<container>', + help='Container to modify', + ) + parser.add_argument( + "--property", + metavar="<key=value>", + required=True, + action=parseractions.KeyValueAction, + help="Set a property on this container " + "(repeat option to set multiple properties)" + ) + return parser + + @utils.log_method(log) + def take_action(self, parsed_args): + self.app.client_manager.object_store.container_set( + parsed_args.container, + properties=parsed_args.property, + ) + + class ShowContainer(show.ShowOne): """Display container details""" @@ -192,11 +223,44 @@ class ShowContainer(show.ShowOne): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) data = self.app.client_manager.object_store.container_show( container=parsed_args.container, ) + if 'properties' in data: + data['properties'] = utils.format_dict(data.pop('properties')) return zip(*sorted(six.iteritems(data))) + + +class UnsetContainer(command.Command): + """Unset container properties""" + + log = logging.getLogger(__name__ + '.UnsetContainer') + + def get_parser(self, prog_name): + parser = super(UnsetContainer, self).get_parser(prog_name) + parser.add_argument( + 'container', + metavar='<container>', + help='Container to modify', + ) + parser.add_argument( + '--property', + metavar='<key>', + required=True, + action='append', + default=[], + help='Property to remove from container ' + '(repeat option to remove multiple properties)', + ) + return parser + + @utils.log_method(log) + def take_action(self, parsed_args): + self.app.client_manager.object_store.container_unset( + parsed_args.container, + properties=parsed_args.property, + ) diff --git a/openstackclient/object/v1/object.py b/openstackclient/object/v1/object.py index 752d7842..4bd06124 100644 --- a/openstackclient/object/v1/object.py +++ b/openstackclient/object/v1/object.py @@ -23,6 +23,7 @@ from cliff import command from cliff import lister from cliff import show +from openstackclient.common import parseractions from openstackclient.common import utils @@ -46,8 +47,8 @@ class CreateObject(lister.Lister): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) results = [] for obj in parsed_args.objects: @@ -85,8 +86,8 @@ class DeleteObject(command.Command): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) for obj in parsed_args.objects: self.app.client_manager.object_store.object_delete( @@ -147,8 +148,8 @@ class ListObject(lister.Lister): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) if parsed_args.long: columns = ( @@ -221,6 +222,42 @@ class SaveObject(command.Command): ) +class SetObject(command.Command): + """Set object properties""" + + log = logging.getLogger(__name__ + '.SetObject') + + def get_parser(self, prog_name): + parser = super(SetObject, self).get_parser(prog_name) + parser.add_argument( + 'container', + metavar='<container>', + help='Modify <object> from <container>', + ) + parser.add_argument( + 'object', + metavar='<object>', + help='Object to modify', + ) + parser.add_argument( + "--property", + metavar="<key=value>", + required=True, + action=parseractions.KeyValueAction, + help="Set a property on this object " + "(repeat option to set multiple properties)" + ) + return parser + + @utils.log_method(log) + def take_action(self, parsed_args): + self.app.client_manager.object_store.object_set( + parsed_args.container, + parsed_args.object, + properties=parsed_args.property, + ) + + class ShowObject(show.ShowOne): """Display object details""" @@ -240,12 +277,51 @@ class ShowObject(show.ShowOne): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) data = self.app.client_manager.object_store.object_show( container=parsed_args.container, object=parsed_args.object, ) + if 'properties' in data: + data['properties'] = utils.format_dict(data.pop('properties')) return zip(*sorted(six.iteritems(data))) + + +class UnsetObject(command.Command): + """Unset object properties""" + + log = logging.getLogger(__name__ + '.UnsetObject') + + def get_parser(self, prog_name): + parser = super(UnsetObject, self).get_parser(prog_name) + parser.add_argument( + 'container', + metavar='<container>', + help='Modify <object> from <container>', + ) + parser.add_argument( + 'object', + metavar='<object>', + help='Object to modify', + ) + parser.add_argument( + '--property', + metavar='<key>', + required=True, + action='append', + default=[], + help='Property to remove from object ' + '(repeat option to remove multiple properties)', + ) + return parser + + @utils.log_method(log) + def take_action(self, parsed_args): + self.app.client_manager.object_store.object_unset( + parsed_args.container, + parsed_args.object, + properties=parsed_args.property, + ) diff --git a/openstackclient/shell.py b/openstackclient/shell.py index f7704efc..16ba1aab 100644 --- a/openstackclient/shell.py +++ b/openstackclient/shell.py @@ -25,6 +25,7 @@ from cliff import app from cliff import command from cliff import complete from cliff import help +from oslo_utils import strutils import openstackclient from openstackclient.common import clientmanager @@ -79,6 +80,10 @@ class OpenStackShell(app.App): help.HelpCommand.auth_required = False complete.CompleteCommand.auth_required = False + # Slight change to the meaning of --debug + self.DEFAULT_DEBUG_VALUE = None + self.DEFAULT_DEBUG_HELP = 'Set debug logging and traceback on errors.' + super(OpenStackShell, self).__init__( description=__doc__.strip(), version=openstackclient.__version__, @@ -197,13 +202,17 @@ class OpenStackShell(app.App): # Parent __init__ parses argv into self.options super(OpenStackShell, self).initialize_app(argv) + self.log.info("START with options: %s", + strutils.mask_password(self.command_options)) + self.log.debug("options: %s", + strutils.mask_password(self.options)) # Set the default plugin to token_endpoint if url and token are given if (self.options.url and self.options.token): # Use service token authentication auth_type = 'token_endpoint' else: - auth_type = 'osc_password' + auth_type = 'password' project_id = getattr(self.options, 'project_id', None) project_name = getattr(self.options, 'project_name', None) @@ -245,10 +254,9 @@ class OpenStackShell(app.App): self.log_configurator.configure(self.cloud) self.dump_stack_trace = self.log_configurator.dump_trace - self.log.info("START with options: %s", self.command_options) - self.log.debug("options: %s", self.options) self.log.debug("defaults: %s", cc.defaults) - self.log.debug("cloud cfg: %s", self.cloud.config) + self.log.debug("cloud cfg: %s", + strutils.mask_password(self.cloud.config)) # Set up client TLS # NOTE(dtroyer): --insecure is the non-default condition that @@ -272,15 +280,29 @@ class OpenStackShell(app.App): # Loop through extensions to get API versions for mod in clientmanager.PLUGIN_MODULES: - version_opt = getattr(self.options, mod.API_VERSION_OPTION, None) + default_version = getattr(mod, 'DEFAULT_API_VERSION', None) + option = mod.API_VERSION_OPTION.replace('os_', '') + version_opt = self.cloud.config.get(option, default_version) if version_opt: api = mod.API_NAME self.api_version[api] = version_opt - if version_opt not in mod.API_VERSIONS: - self.log.warning( - "The %s version <%s> is not in supported versions <%s>" - % (api, version_opt, - ', '.join(mod.API_VERSIONS.keys()))) + + # Add a plugin interface to let the module validate the version + # requested by the user + skip_old_check = False + mod_check_api_version = getattr(mod, 'check_api_version', None) + if mod_check_api_version: + # this throws an exception if invalid + skip_old_check = mod_check_api_version(version_opt) + + mod_versions = getattr(mod, 'API_VERSIONS', None) + if not skip_old_check and mod_versions: + if version_opt not in mod_versions: + self.log.warning( + "%s version %s is not in supported versions %s" + % (api, version_opt, + ', '.join(mod.API_VERSIONS.keys()))) + # Command groups deal only with major versions version = '.v' + version_opt.replace('.', '_').split('_')[0] cmd_group = 'openstack.' + api.replace('-', '_') + version diff --git a/openstackclient/tests/api/fakes.py b/openstackclient/tests/api/fakes.py index 85617ab7..e285a61c 100644 --- a/openstackclient/tests/api/fakes.py +++ b/openstackclient/tests/api/fakes.py @@ -15,7 +15,7 @@ from requests_mock.contrib import fixture -from keystoneclient import session +from keystoneauth1 import session from openstackclient.tests import utils diff --git a/openstackclient/tests/api/test_image_v1.py b/openstackclient/tests/api/test_image_v1.py index 34fcfca4..f3479756 100644 --- a/openstackclient/tests/api/test_image_v1.py +++ b/openstackclient/tests/api/test_image_v1.py @@ -15,7 +15,7 @@ from requests_mock.contrib import fixture -from keystoneclient import session +from keystoneauth1 import session from openstackclient.api import image_v1 from openstackclient.tests import utils diff --git a/openstackclient/tests/api/test_image_v2.py b/openstackclient/tests/api/test_image_v2.py index ddb160ee..77063997 100644 --- a/openstackclient/tests/api/test_image_v2.py +++ b/openstackclient/tests/api/test_image_v2.py @@ -15,7 +15,7 @@ from requests_mock.contrib import fixture -from keystoneclient import session +from keystoneauth1 import session from openstackclient.api import image_v2 from openstackclient.tests import utils diff --git a/openstackclient/tests/api/test_network_v2.py b/openstackclient/tests/api/test_network_v2.py index 13c5d6ea..80f1d9de 100644 --- a/openstackclient/tests/api/test_network_v2.py +++ b/openstackclient/tests/api/test_network_v2.py @@ -15,7 +15,7 @@ from requests_mock.contrib import fixture -from keystoneclient import session +from keystoneauth1 import session from openstackclient.api import network_v2 as network from openstackclient.tests import utils diff --git a/openstackclient/tests/api/test_object_store_v1.py b/openstackclient/tests/api/test_object_store_v1.py index b18a003d..992bf261 100644 --- a/openstackclient/tests/api/test_object_store_v1.py +++ b/openstackclient/tests/api/test_object_store_v1.py @@ -17,7 +17,7 @@ import mock from requests_mock.contrib import fixture -from keystoneclient import session +from keystoneauth1 import session from openstackclient.api import object_store_v1 as object_store from openstackclient.tests import utils @@ -157,10 +157,6 @@ class TestContainer(TestObjectAPIv1): 'container': 'qaz', 'object_count': '1', 'bytes_used': '577', - 'read_acl': None, - 'write_acl': None, - 'sync_to': None, - 'sync_key': None, } self.requests_mock.register_uri( 'HEAD', @@ -323,8 +319,7 @@ class TestObject(TestObjectAPIv1): 'content-length': '577', 'last-modified': '20130101', 'etag': 'qaz', - 'wife': 'Wilma', - 'x-tra-header': 'yabba-dabba-do', + 'properties': {'wife': 'Wilma'}, } self.requests_mock.register_uri( 'HEAD', diff --git a/openstackclient/tests/common/test_quota.py b/openstackclient/tests/common/test_quota.py new file mode 100644 index 00000000..b6ad1566 --- /dev/null +++ b/openstackclient/tests/common/test_quota.py @@ -0,0 +1,134 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import copy + +import mock + +from openstackclient.common import quota +from openstackclient.tests.compute.v2 import fakes as compute_fakes +from openstackclient.tests import fakes + + +class FakeQuotaResource(fakes.FakeResource): + + _keys = {'property': 'value'} + + def set_keys(self, args): + self._keys.update(args) + + def unset_keys(self, keys): + for key in keys: + self._keys.pop(key, None) + + def get_keys(self): + return self._keys + + +class TestQuota(compute_fakes.TestComputev2): + + def setUp(self): + super(TestQuota, self).setUp() + self.quotas_mock = self.app.client_manager.compute.quotas + self.quotas_mock.reset_mock() + volume_mock = mock.Mock() + volume_mock.quotas = mock.Mock() + self.app.client_manager.volume = volume_mock + self.volume_quotas_mock = volume_mock.quotas + self.volume_quotas_mock.reset_mock() + + +class TestQuotaSet(TestQuota): + + def setUp(self): + super(TestQuotaSet, self).setUp() + + self.quotas_mock.find.return_value = FakeQuotaResource( + None, + copy.deepcopy(compute_fakes.QUOTA), + loaded=True, + ) + + self.quotas_mock.update.return_value = FakeQuotaResource( + None, + copy.deepcopy(compute_fakes.QUOTA), + loaded=True, + ) + + self.volume_quotas_mock.find.return_value = FakeQuotaResource( + None, + copy.deepcopy(compute_fakes.QUOTA), + loaded=True, + ) + + self.volume_quotas_mock.update.return_value = FakeQuotaResource( + None, + copy.deepcopy(compute_fakes.QUOTA), + loaded=True, + ) + + self.cmd = quota.SetQuota(self.app, None) + + def test_quota_set(self): + arglist = [ + '--floating-ips', str(compute_fakes.floating_ip_num), + '--fixed-ips', str(compute_fakes.fix_ip_num), + '--injected-files', str(compute_fakes.injected_file_num), + '--key-pairs', str(compute_fakes.key_pair_num), + compute_fakes.project_name, + ] + verifylist = [ + ('floating_ips', compute_fakes.floating_ip_num), + ('fixed_ips', compute_fakes.fix_ip_num), + ('injected_files', compute_fakes.injected_file_num), + ('key_pairs', compute_fakes.key_pair_num), + ('project', compute_fakes.project_name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + kwargs = { + 'floating_ips': compute_fakes.floating_ip_num, + 'fixed_ips': compute_fakes.fix_ip_num, + 'injected_files': compute_fakes.injected_file_num, + 'key_pairs': compute_fakes.key_pair_num, + } + + self.quotas_mock.update.assert_called_with('project_test', **kwargs) + + def test_quota_set_volume(self): + arglist = [ + '--gigabytes', str(compute_fakes.floating_ip_num), + '--snapshots', str(compute_fakes.fix_ip_num), + '--volumes', str(compute_fakes.injected_file_num), + compute_fakes.project_name, + ] + verifylist = [ + ('gigabytes', compute_fakes.floating_ip_num), + ('snapshots', compute_fakes.fix_ip_num), + ('volumes', compute_fakes.injected_file_num), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + kwargs = { + 'gigabytes': compute_fakes.floating_ip_num, + 'snapshots': compute_fakes.fix_ip_num, + 'volumes': compute_fakes.injected_file_num, + } + + self.volume_quotas_mock.update.assert_called_with('project_test', + **kwargs) diff --git a/openstackclient/tests/common/test_utils.py b/openstackclient/tests/common/test_utils.py index a25a5ba5..373c0de4 100644 --- a/openstackclient/tests/common/test_utils.py +++ b/openstackclient/tests/common/test_utils.py @@ -20,6 +20,7 @@ import mock from openstackclient.common import exceptions from openstackclient.common import utils +from openstackclient.tests import fakes from openstackclient.tests import utils as test_utils PASSWORD = "Pa$$w0rd" @@ -27,6 +28,18 @@ WASSPORD = "Wa$$p0rd" DROWSSAP = "dr0w$$aP" +class FakeOddballResource(fakes.FakeResource): + + def get(self, attr): + """get() is needed for utils.find_resource()""" + if attr == 'id': + return self.id + elif attr == 'name': + return self.name + else: + return None + + class TestUtils(test_utils.TestCase): def test_get_password_good(self): @@ -242,6 +255,47 @@ class TestFindResource(test_utils.TestCase): self.manager.get.assert_called_with(self.name) self.manager.find.assert_called_with(name=self.name) + def test_find_resource_silly_resource(self): + # We need a resource with no resource_class for this test, start fresh + self.manager = mock.Mock() + self.manager.get = mock.Mock(side_effect=Exception('Boom!')) + self.manager.find = mock.Mock( + side_effect=AttributeError( + "'Controller' object has no attribute 'find'", + ) + ) + silly_resource = FakeOddballResource( + None, + {'id': '12345', 'name': self.name}, + loaded=True, + ) + self.manager.list = mock.Mock( + return_value=[silly_resource, ], + ) + result = utils.find_resource(self.manager, self.name) + self.assertEqual(silly_resource, result) + self.manager.get.assert_called_with(self.name) + self.manager.find.assert_called_with(name=self.name) + + def test_find_resource_silly_resource_not_found(self): + # We need a resource with no resource_class for this test, start fresh + self.manager = mock.Mock() + self.manager.get = mock.Mock(side_effect=Exception('Boom!')) + self.manager.find = mock.Mock( + side_effect=AttributeError( + "'Controller' object has no attribute 'find'", + ) + ) + self.manager.list = mock.Mock(return_value=[]) + result = self.assertRaises(exceptions.CommandError, + utils.find_resource, + self.manager, + self.name) + self.assertEqual("Could not find resource legos", + str(result)) + self.manager.get.assert_called_with(self.name) + self.manager.find.assert_called_with(name=self.name) + def test_format_dict(self): expected = "a='b', c='d', e='f'" self.assertEqual(expected, diff --git a/openstackclient/tests/compute/v2/fakes.py b/openstackclient/tests/compute/v2/fakes.py index c18dea7e..e798bd40 100644 --- a/openstackclient/tests/compute/v2/fakes.py +++ b/openstackclient/tests/compute/v2/fakes.py @@ -62,6 +62,22 @@ FLAVOR = { 'vcpus': flavor_vcpus, } +floating_ip_num = 100 +fix_ip_num = 100 +injected_file_num = 100 +key_pair_num = 100 +project_name = 'project_test' +QUOTA = { + 'project': project_name, + 'floating-ips': floating_ip_num, + 'fix-ips': fix_ip_num, + 'injected-files': injected_file_num, + 'key-pairs': key_pair_num, +} + +QUOTA_columns = tuple(sorted(QUOTA)) +QUOTA_data = tuple(QUOTA[x] for x in sorted(QUOTA)) + class FakeComputev2Client(object): def __init__(self, **kwargs): @@ -73,6 +89,8 @@ class FakeComputev2Client(object): self.extensions.resource_class = fakes.FakeResource(None, {}) self.flavors = mock.Mock() self.flavors.resource_class = fakes.FakeResource(None, {}) + self.quotas = mock.Mock() + self.quotas.resource_class = fakes.FakeResource(None, {}) self.auth_token = kwargs['token'] self.management_url = kwargs['endpoint'] diff --git a/openstackclient/tests/compute/v2/test_flavor.py b/openstackclient/tests/compute/v2/test_flavor.py index 19be8124..523104f0 100644 --- a/openstackclient/tests/compute/v2/test_flavor.py +++ b/openstackclient/tests/compute/v2/test_flavor.py @@ -76,7 +76,9 @@ class TestFlavorList(TestFlavor): # Set expected values kwargs = { - 'is_public': True + 'is_public': True, + 'limit': None, + 'marker': None } self.flavors_mock.list.assert_called_with( @@ -119,7 +121,9 @@ class TestFlavorList(TestFlavor): # Set expected values kwargs = { - 'is_public': None + 'is_public': None, + 'limit': None, + 'marker': None } self.flavors_mock.list.assert_called_with( @@ -162,7 +166,9 @@ class TestFlavorList(TestFlavor): # Set expected values kwargs = { - 'is_public': False + 'is_public': False, + 'limit': None, + 'marker': None } self.flavors_mock.list.assert_called_with( @@ -205,7 +211,9 @@ class TestFlavorList(TestFlavor): # Set expected values kwargs = { - 'is_public': True + 'is_public': True, + 'limit': None, + 'marker': None } self.flavors_mock.list.assert_called_with( @@ -248,7 +256,9 @@ class TestFlavorList(TestFlavor): # Set expected values kwargs = { - 'is_public': True + 'is_public': True, + 'limit': None, + 'marker': None } self.flavors_mock.list.assert_called_with( diff --git a/openstackclient/tests/compute/v2/test_security_group_rule.py b/openstackclient/tests/compute/v2/test_security_group_rule.py new file mode 100644 index 00000000..9516f8dd --- /dev/null +++ b/openstackclient/tests/compute/v2/test_security_group_rule.py @@ -0,0 +1,338 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +import copy +import mock + +from openstackclient.compute.v2 import security_group +from openstackclient.tests.compute.v2 import fakes as compute_fakes +from openstackclient.tests import fakes +from openstackclient.tests.identity.v2_0 import fakes as identity_fakes + + +security_group_id = '11' +security_group_name = 'wide-open' +security_group_description = 'nothing but net' + +security_group_rule_id = '1' + +SECURITY_GROUP = { + 'id': security_group_id, + 'name': security_group_name, + 'description': security_group_description, + 'tenant_id': identity_fakes.project_id, +} + +SECURITY_GROUP_RULE = { + 'id': security_group_rule_id, + 'group': {}, + 'ip_protocol': 'tcp', + 'ip_range': '0.0.0.0/0', + 'parent_group_id': security_group_id, + 'from_port': 0, + 'to_port': 0, +} + +SECURITY_GROUP_RULE_ICMP = { + 'id': security_group_rule_id, + 'group': {}, + 'ip_protocol': 'icmp', + 'ip_range': '0.0.0.0/0', + 'parent_group_id': security_group_id, + 'from_port': -1, + 'to_port': -1, +} + + +class FakeSecurityGroupRuleResource(fakes.FakeResource): + + def get_keys(self): + return {'property': 'value'} + + +class TestSecurityGroupRule(compute_fakes.TestComputev2): + + def setUp(self): + super(TestSecurityGroupRule, self).setUp() + + self.secgroups_mock = mock.Mock() + self.secgroups_mock.resource_class = fakes.FakeResource(None, {}) + self.app.client_manager.compute.security_groups = self.secgroups_mock + self.secgroups_mock.reset_mock() + + self.sg_rules_mock = mock.Mock() + self.sg_rules_mock.resource_class = fakes.FakeResource(None, {}) + self.app.client_manager.compute.security_group_rules = \ + self.sg_rules_mock + self.sg_rules_mock.reset_mock() + + +class TestSecurityGroupRuleCreate(TestSecurityGroupRule): + + def setUp(self): + super(TestSecurityGroupRuleCreate, self).setUp() + + self.secgroups_mock.get.return_value = FakeSecurityGroupRuleResource( + None, + copy.deepcopy(SECURITY_GROUP), + loaded=True, + ) + + # Get the command object to test + self.cmd = security_group.CreateSecurityGroupRule(self.app, None) + + def test_security_group_rule_create_no_options(self): + self.sg_rules_mock.create.return_value = FakeSecurityGroupRuleResource( + None, + copy.deepcopy(SECURITY_GROUP_RULE), + loaded=True, + ) + + arglist = [ + security_group_name, + ] + verifylist = [ + ('group', security_group_name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + # SecurityGroupManager.create(name, description) + self.sg_rules_mock.create.assert_called_with( + security_group_id, + 'tcp', + 0, + 0, + '0.0.0.0/0', + ) + + collist = ( + 'group', + 'id', + 'ip_protocol', + 'ip_range', + 'parent_group_id', + 'port_range', + ) + self.assertEqual(collist, columns) + datalist = ( + {}, + security_group_rule_id, + 'tcp', + '', + security_group_id, + '0:0', + ) + self.assertEqual(datalist, data) + + def test_security_group_rule_create_ftp(self): + sg_rule = copy.deepcopy(SECURITY_GROUP_RULE) + sg_rule['from_port'] = 20 + sg_rule['to_port'] = 21 + self.sg_rules_mock.create.return_value = FakeSecurityGroupRuleResource( + None, + sg_rule, + loaded=True, + ) + + arglist = [ + security_group_name, + '--dst-port', '20:21', + ] + verifylist = [ + ('group', security_group_name), + ('dst_port', (20, 21)), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + # SecurityGroupManager.create(name, description) + self.sg_rules_mock.create.assert_called_with( + security_group_id, + 'tcp', + 20, + 21, + '0.0.0.0/0', + ) + + collist = ( + 'group', + 'id', + 'ip_protocol', + 'ip_range', + 'parent_group_id', + 'port_range', + ) + self.assertEqual(collist, columns) + datalist = ( + {}, + security_group_rule_id, + 'tcp', + '', + security_group_id, + '20:21', + ) + self.assertEqual(datalist, data) + + def test_security_group_rule_create_ssh(self): + sg_rule = copy.deepcopy(SECURITY_GROUP_RULE) + sg_rule['from_port'] = 22 + sg_rule['to_port'] = 22 + self.sg_rules_mock.create.return_value = FakeSecurityGroupRuleResource( + None, + sg_rule, + loaded=True, + ) + + arglist = [ + security_group_name, + '--dst-port', '22', + ] + verifylist = [ + ('group', security_group_name), + ('dst_port', (22, 22)), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + # SecurityGroupManager.create(name, description) + self.sg_rules_mock.create.assert_called_with( + security_group_id, + 'tcp', + 22, + 22, + '0.0.0.0/0', + ) + + collist = ( + 'group', + 'id', + 'ip_protocol', + 'ip_range', + 'parent_group_id', + 'port_range', + ) + self.assertEqual(collist, columns) + datalist = ( + {}, + security_group_rule_id, + 'tcp', + '', + security_group_id, + '22:22', + ) + self.assertEqual(datalist, data) + + def test_security_group_rule_create_udp(self): + sg_rule = copy.deepcopy(SECURITY_GROUP_RULE) + sg_rule['ip_protocol'] = 'udp' + self.sg_rules_mock.create.return_value = FakeSecurityGroupRuleResource( + None, + sg_rule, + loaded=True, + ) + + arglist = [ + security_group_name, + '--proto', 'udp', + ] + verifylist = [ + ('group', security_group_name), + ('proto', 'udp'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + # SecurityGroupManager.create(name, description) + self.sg_rules_mock.create.assert_called_with( + security_group_id, + 'udp', + 0, + 0, + '0.0.0.0/0', + ) + + collist = ( + 'group', + 'id', + 'ip_protocol', + 'ip_range', + 'parent_group_id', + 'port_range', + ) + self.assertEqual(collist, columns) + datalist = ( + {}, + security_group_rule_id, + 'udp', + '', + security_group_id, + '0:0', + ) + self.assertEqual(datalist, data) + + def test_security_group_rule_create_icmp(self): + self.sg_rules_mock.create.return_value = FakeSecurityGroupRuleResource( + None, + copy.deepcopy(SECURITY_GROUP_RULE_ICMP), + loaded=True, + ) + + arglist = [ + security_group_name, + '--proto', 'ICMP', + ] + verifylist = [ + ('group', security_group_name), + ('proto', 'ICMP'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + # SecurityGroupManager.create(name, description) + self.sg_rules_mock.create.assert_called_with( + security_group_id, + 'ICMP', + -1, + -1, + '0.0.0.0/0', + ) + + collist = ( + 'group', + 'id', + 'ip_protocol', + 'ip_range', + 'parent_group_id', + 'port_range', + ) + self.assertEqual(collist, columns) + datalist = ( + {}, + security_group_rule_id, + 'icmp', + '', + security_group_id, + '', + ) + self.assertEqual(datalist, data) diff --git a/openstackclient/tests/compute/v2/test_server.py b/openstackclient/tests/compute/v2/test_server.py index 4df18f05..1e99bcd0 100644 --- a/openstackclient/tests/compute/v2/test_server.py +++ b/openstackclient/tests/compute/v2/test_server.py @@ -410,13 +410,14 @@ class TestServerImageCreate(TestServer): compute_fakes.server_name, ) - collist = ('id', 'name', 'owner', 'protected', 'visibility') + collist = ('id', 'name', 'owner', 'protected', 'tags', 'visibility') self.assertEqual(collist, columns) datalist = ( image_fakes.image_id, image_fakes.image_name, image_fakes.image_owner, image_fakes.image_protected, + image_fakes.image_tags, image_fakes.image_visibility, ) self.assertEqual(datalist, data) @@ -441,13 +442,14 @@ class TestServerImageCreate(TestServer): 'img-nam', ) - collist = ('id', 'name', 'owner', 'protected', 'visibility') + collist = ('id', 'name', 'owner', 'protected', 'tags', 'visibility') self.assertEqual(collist, columns) datalist = ( image_fakes.image_id, image_fakes.image_name, image_fakes.image_owner, image_fakes.image_protected, + image_fakes.image_tags, image_fakes.image_visibility, ) self.assertEqual(datalist, data) diff --git a/openstackclient/tests/fakes.py b/openstackclient/tests/fakes.py index 979f9481..357c470f 100644 --- a/openstackclient/tests/fakes.py +++ b/openstackclient/tests/fakes.py @@ -17,7 +17,7 @@ import json import six import sys -from keystoneclient import fixture +from keystoneauth1 import fixture import requests diff --git a/openstackclient/tests/identity/v3/fakes.py b/openstackclient/tests/identity/v3/fakes.py index ae7a684c..ff267478 100644 --- a/openstackclient/tests/identity/v3/fakes.py +++ b/openstackclient/tests/identity/v3/fakes.py @@ -123,13 +123,11 @@ PROJECT_2 = { } region_id = 'region_one' -region_url = 'http://localhost:1111' region_parent_region_id = 'region_two' region_description = 'region one' REGION = { 'id': region_id, - 'url': region_url, 'description': region_description, 'parent_region_id': region_parent_region_id, 'links': base_url + 'regions/' + region_id, @@ -313,6 +311,13 @@ ASSIGNMENT_WITH_PROJECT_ID_AND_USER_ID = { 'role': {'id': role_id}, } +ASSIGNMENT_WITH_PROJECT_ID_AND_USER_ID_INHERITED = { + 'scope': {'project': {'id': project_id}, + 'OS-INHERIT:inherited_to': 'projects'}, + 'user': {'id': user_id}, + 'role': {'id': role_id}, +} + ASSIGNMENT_WITH_PROJECT_ID_AND_GROUP_ID = { 'scope': {'project': {'id': project_id}}, 'group': {'id': group_id}, @@ -325,6 +330,13 @@ ASSIGNMENT_WITH_DOMAIN_ID_AND_USER_ID = { 'role': {'id': role_id}, } +ASSIGNMENT_WITH_DOMAIN_ID_AND_USER_ID_INHERITED = { + 'scope': {'domain': {'id': domain_id}, + 'OS-INHERIT:inherited_to': 'projects'}, + 'user': {'id': user_id}, + 'role': {'id': role_id}, +} + ASSIGNMENT_WITH_DOMAIN_ID_AND_GROUP_ID = { 'scope': {'domain': {'id': domain_id}}, 'group': {'id': group_id}, diff --git a/openstackclient/tests/identity/v3/test_region.py b/openstackclient/tests/identity/v3/test_region.py index 7f6ced9f..0ebbbecf 100644 --- a/openstackclient/tests/identity/v3/test_region.py +++ b/openstackclient/tests/identity/v3/test_region.py @@ -61,19 +61,17 @@ class TestRegionCreate(TestRegion): 'description': identity_fakes.region_description, 'id': identity_fakes.region_id, 'parent_region': None, - 'url': None, } self.regions_mock.create.assert_called_with( **kwargs ) - collist = ('description', 'parent_region', 'region', 'url') + collist = ('description', 'parent_region', 'region') self.assertEqual(collist, columns) datalist = ( identity_fakes.region_description, identity_fakes.region_parent_region_id, identity_fakes.region_id, - identity_fakes.region_url, ) self.assertEqual(datalist, data) @@ -94,19 +92,17 @@ class TestRegionCreate(TestRegion): 'description': None, 'id': identity_fakes.region_id, 'parent_region': None, - 'url': None, } self.regions_mock.create.assert_called_with( **kwargs ) - collist = ('description', 'parent_region', 'region', 'url') + collist = ('description', 'parent_region', 'region') self.assertEqual(collist, columns) datalist = ( identity_fakes.region_description, identity_fakes.region_parent_region_id, identity_fakes.region_id, - identity_fakes.region_url, ) self.assertEqual(datalist, data) @@ -129,54 +125,17 @@ class TestRegionCreate(TestRegion): 'description': None, 'id': identity_fakes.region_id, 'parent_region': identity_fakes.region_parent_region_id, - 'url': None, } self.regions_mock.create.assert_called_with( **kwargs ) - collist = ('description', 'parent_region', 'region', 'url') + collist = ('description', 'parent_region', 'region') self.assertEqual(collist, columns) datalist = ( identity_fakes.region_description, identity_fakes.region_parent_region_id, identity_fakes.region_id, - identity_fakes.region_url, - ) - self.assertEqual(datalist, data) - - def test_region_create_url(self): - arglist = [ - identity_fakes.region_id, - '--url', identity_fakes.region_url, - ] - verifylist = [ - ('region', identity_fakes.region_id), - ('url', identity_fakes.region_url), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - # DisplayCommandBase.take_action() returns two tuples - columns, data = self.cmd.take_action(parsed_args) - - # Set expected values - kwargs = { - 'description': None, - 'id': identity_fakes.region_id, - 'parent_region': None, - 'url': identity_fakes.region_url, - } - self.regions_mock.create.assert_called_with( - **kwargs - ) - - collist = ('description', 'parent_region', 'region', 'url') - self.assertEqual(collist, columns) - datalist = ( - identity_fakes.region_description, - identity_fakes.region_parent_region_id, - identity_fakes.region_id, - identity_fakes.region_url, ) self.assertEqual(datalist, data) @@ -233,13 +192,12 @@ class TestRegionList(TestRegion): columns, data = self.cmd.take_action(parsed_args) self.regions_mock.list.assert_called_with() - collist = ('Region', 'Parent Region', 'Description', 'URL') + collist = ('Region', 'Parent Region', 'Description') self.assertEqual(collist, columns) datalist = (( identity_fakes.region_id, identity_fakes.region_parent_region_id, identity_fakes.region_description, - identity_fakes.region_url, ), ) self.assertEqual(datalist, tuple(data)) @@ -257,13 +215,12 @@ class TestRegionList(TestRegion): self.regions_mock.list.assert_called_with( parent_region_id=identity_fakes.region_parent_region_id) - collist = ('Region', 'Parent Region', 'Description', 'URL') + collist = ('Region', 'Parent Region', 'Description') self.assertEqual(collist, columns) datalist = (( identity_fakes.region_id, identity_fakes.region_parent_region_id, identity_fakes.region_description, - identity_fakes.region_url, ), ) self.assertEqual(datalist, tuple(data)) @@ -319,29 +276,6 @@ class TestRegionSet(TestRegion): **kwargs ) - def test_region_set_url(self): - arglist = [ - '--url', 'new url', - identity_fakes.region_id, - ] - verifylist = [ - ('url', 'new url'), - ('region', identity_fakes.region_id), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - result = self.cmd.run(parsed_args) - self.assertEqual(0, result) - - # Set expected values - kwargs = { - 'url': 'new url', - } - self.regions_mock.update.assert_called_with( - identity_fakes.region_id, - **kwargs - ) - def test_region_set_parent_region_id(self): arglist = [ '--parent-region', 'new_parent', @@ -395,12 +329,11 @@ class TestRegionShow(TestRegion): identity_fakes.region_id, ) - collist = ('description', 'parent_region', 'region', 'url') + collist = ('description', 'parent_region', 'region') self.assertEqual(collist, columns) datalist = ( identity_fakes.region_description, identity_fakes.region_parent_region_id, identity_fakes.region_id, - identity_fakes.region_url, ) self.assertEqual(datalist, data) diff --git a/openstackclient/tests/identity/v3/test_role.py b/openstackclient/tests/identity/v3/test_role.py index 4ff3b95f..8ad4b099 100644 --- a/openstackclient/tests/identity/v3/test_role.py +++ b/openstackclient/tests/identity/v3/test_role.py @@ -123,7 +123,7 @@ class TestRoleAdd(TestRole): kwargs = { 'user': identity_fakes.user_id, 'domain': identity_fakes.domain_id, - 'inherited': self._is_inheritance_testcase(), + 'os_inherit_extension_inherited': self._is_inheritance_testcase(), } # RoleManager.grant(role, user=, group=, domain=, project=) self.roles_mock.grant.assert_called_with( @@ -156,7 +156,7 @@ class TestRoleAdd(TestRole): kwargs = { 'user': identity_fakes.user_id, 'project': identity_fakes.project_id, - 'inherited': self._is_inheritance_testcase(), + 'os_inherit_extension_inherited': self._is_inheritance_testcase(), } # RoleManager.grant(role, user=, group=, domain=, project=) self.roles_mock.grant.assert_called_with( @@ -189,7 +189,7 @@ class TestRoleAdd(TestRole): kwargs = { 'group': identity_fakes.group_id, 'domain': identity_fakes.domain_id, - 'inherited': self._is_inheritance_testcase(), + 'os_inherit_extension_inherited': self._is_inheritance_testcase(), } # RoleManager.grant(role, user=, group=, domain=, project=) self.roles_mock.grant.assert_called_with( @@ -222,7 +222,7 @@ class TestRoleAdd(TestRole): kwargs = { 'group': identity_fakes.group_id, 'project': identity_fakes.project_id, - 'inherited': self._is_inheritance_testcase(), + 'os_inherit_extension_inherited': self._is_inheritance_testcase(), } # RoleManager.grant(role, user=, group=, domain=, project=) self.roles_mock.grant.assert_called_with( @@ -367,6 +367,39 @@ class TestRoleList(TestRole): ), ) self.assertEqual(datalist, tuple(data)) + def test_user_list_inherited(self): + arglist = [ + '--user', identity_fakes.user_id, + '--inherited', + ] + verifylist = [ + ('user', identity_fakes.user_id), + ('inherited', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'domain': 'default', + 'user': self.users_mock.get(), + 'os_inherit_extension_inherited': True, + } + # RoleManager.list(user=, group=, domain=, project=, **kwargs) + self.roles_mock.list.assert_called_with( + **kwargs + ) + + collist = ('ID', 'Name') + self.assertEqual(collist, columns) + datalist = (( + identity_fakes.role_id, + identity_fakes.role_name, + ), ) + self.assertEqual(datalist, tuple(data)) + def test_user_list_user(self): arglist = [ '--user', identity_fakes.user_id, @@ -383,6 +416,7 @@ class TestRoleList(TestRole): kwargs = { 'domain': 'default', 'user': self.users_mock.get(), + 'os_inherit_extension_inherited': False } # RoleManager.list(user=, group=, domain=, project=, **kwargs) self.roles_mock.list.assert_called_with( @@ -415,6 +449,7 @@ class TestRoleList(TestRole): kwargs = { 'domain': self.domains_mock.get(), 'user': self.users_mock.get(), + 'os_inherit_extension_inherited': False } # RoleManager.list(user=, group=, domain=, project=, **kwargs) self.roles_mock.list.assert_called_with( @@ -449,6 +484,7 @@ class TestRoleList(TestRole): kwargs = { 'domain': self.domains_mock.get(), 'group': self.groups_mock.get(), + 'os_inherit_extension_inherited': False } # RoleManager.list(user=, group=, domain=, project=, **kwargs) self.roles_mock.list.assert_called_with( @@ -483,6 +519,7 @@ class TestRoleList(TestRole): kwargs = { 'project': self.projects_mock.get(), 'user': self.users_mock.get(), + 'os_inherit_extension_inherited': False } # RoleManager.list(user=, group=, domain=, project=, **kwargs) self.roles_mock.list.assert_called_with( @@ -517,6 +554,7 @@ class TestRoleList(TestRole): kwargs = { 'project': self.projects_mock.get(), 'group': self.groups_mock.get(), + 'os_inherit_extension_inherited': False } # RoleManager.list(user=, group=, domain=, project=, **kwargs) self.roles_mock.list.assert_called_with( @@ -598,7 +636,7 @@ class TestRoleRemove(TestRole): kwargs = { 'user': identity_fakes.user_id, 'domain': identity_fakes.domain_id, - 'inherited': self._is_inheritance_testcase(), + 'os_inherit_extension_inherited': self._is_inheritance_testcase(), } # RoleManager.revoke(role, user=, group=, domain=, project=) self.roles_mock.revoke.assert_called_with( @@ -631,7 +669,7 @@ class TestRoleRemove(TestRole): kwargs = { 'user': identity_fakes.user_id, 'project': identity_fakes.project_id, - 'inherited': self._is_inheritance_testcase(), + 'os_inherit_extension_inherited': self._is_inheritance_testcase(), } # RoleManager.revoke(role, user=, group=, domain=, project=) self.roles_mock.revoke.assert_called_with( @@ -665,7 +703,7 @@ class TestRoleRemove(TestRole): kwargs = { 'group': identity_fakes.group_id, 'domain': identity_fakes.domain_id, - 'inherited': self._is_inheritance_testcase(), + 'os_inherit_extension_inherited': self._is_inheritance_testcase(), } # RoleManager.revoke(role, user=, group=, domain=, project=) self.roles_mock.revoke.assert_called_with( @@ -698,7 +736,7 @@ class TestRoleRemove(TestRole): kwargs = { 'group': identity_fakes.group_id, 'project': identity_fakes.project_id, - 'inherited': self._is_inheritance_testcase(), + 'os_inherit_extension_inherited': self._is_inheritance_testcase(), } # RoleManager.revoke(role, user=, group=, domain=, project=) self.roles_mock.revoke.assert_called_with( diff --git a/openstackclient/tests/identity/v3/test_role_assignment.py b/openstackclient/tests/identity/v3/test_role_assignment.py index b1ce8b29..9817f53a 100644 --- a/openstackclient/tests/identity/v3/test_role_assignment.py +++ b/openstackclient/tests/identity/v3/test_role_assignment.py @@ -86,21 +86,24 @@ class TestRoleAssignmentList(TestRoleAssignment): effective=False, role=None, user=None, - project=None) + project=None, + os_inherit_extension_inherited_to=None) - collist = ('Role', 'User', 'Group', 'Project', 'Domain') - self.assertEqual(collist, columns) + collist = ('Role', 'User', 'Group', 'Project', 'Domain', 'Inherited') + self.assertEqual(columns, collist) datalist = (( identity_fakes.role_id, identity_fakes.user_id, '', identity_fakes.project_id, - '' + '', + False ), (identity_fakes.role_id, '', identity_fakes.group_id, identity_fakes.project_id, - '' + '', + False ),) self.assertEqual(datalist, tuple(data)) @@ -131,6 +134,7 @@ class TestRoleAssignmentList(TestRoleAssignment): ('project', None), ('role', None), ('effective', False), + ('inherited', False), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -143,21 +147,24 @@ class TestRoleAssignmentList(TestRoleAssignment): group=None, project=None, role=None, - effective=False) + effective=False, + os_inherit_extension_inherited_to=None) - collist = ('Role', 'User', 'Group', 'Project', 'Domain') - self.assertEqual(collist, columns) + collist = ('Role', 'User', 'Group', 'Project', 'Domain', 'Inherited') + self.assertEqual(columns, collist) datalist = (( identity_fakes.role_id, identity_fakes.user_id, '', '', - identity_fakes.domain_id + identity_fakes.domain_id, + False ), (identity_fakes.role_id, identity_fakes.user_id, '', identity_fakes.project_id, - '' + '', + False ),) self.assertEqual(datalist, tuple(data)) @@ -188,6 +195,7 @@ class TestRoleAssignmentList(TestRoleAssignment): ('project', None), ('role', None), ('effective', False), + ('inherited', False), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -200,21 +208,24 @@ class TestRoleAssignmentList(TestRoleAssignment): effective=False, project=None, role=None, - user=None) + user=None, + os_inherit_extension_inherited_to=None) - collist = ('Role', 'User', 'Group', 'Project', 'Domain') - self.assertEqual(collist, columns) + collist = ('Role', 'User', 'Group', 'Project', 'Domain', 'Inherited') + self.assertEqual(columns, collist) datalist = (( identity_fakes.role_id, '', identity_fakes.group_id, '', - identity_fakes.domain_id + identity_fakes.domain_id, + False ), (identity_fakes.role_id, '', identity_fakes.group_id, identity_fakes.project_id, - '' + '', + False ),) self.assertEqual(datalist, tuple(data)) @@ -245,6 +256,7 @@ class TestRoleAssignmentList(TestRoleAssignment): ('project', None), ('role', None), ('effective', False), + ('inherited', False), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -257,21 +269,24 @@ class TestRoleAssignmentList(TestRoleAssignment): effective=False, project=None, role=None, - user=None) + user=None, + os_inherit_extension_inherited_to=None) - collist = ('Role', 'User', 'Group', 'Project', 'Domain') - self.assertEqual(collist, columns) + collist = ('Role', 'User', 'Group', 'Project', 'Domain', 'Inherited') + self.assertEqual(columns, collist) datalist = (( identity_fakes.role_id, identity_fakes.user_id, '', '', - identity_fakes.domain_id + identity_fakes.domain_id, + False ), (identity_fakes.role_id, '', identity_fakes.group_id, '', - identity_fakes.domain_id + identity_fakes.domain_id, + False ),) self.assertEqual(datalist, tuple(data)) @@ -302,6 +317,7 @@ class TestRoleAssignmentList(TestRoleAssignment): ('project', identity_fakes.project_name), ('role', None), ('effective', False), + ('inherited', False), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -314,21 +330,24 @@ class TestRoleAssignmentList(TestRoleAssignment): effective=False, project=self.projects_mock.get(), role=None, - user=None) + user=None, + os_inherit_extension_inherited_to=None) - collist = ('Role', 'User', 'Group', 'Project', 'Domain') - self.assertEqual(collist, columns) + collist = ('Role', 'User', 'Group', 'Project', 'Domain', 'Inherited') + self.assertEqual(columns, collist) datalist = (( identity_fakes.role_id, identity_fakes.user_id, '', identity_fakes.project_id, - '' + '', + False ), (identity_fakes.role_id, '', identity_fakes.group_id, identity_fakes.project_id, - '' + '', + False ),) self.assertEqual(datalist, tuple(data)) @@ -357,6 +376,7 @@ class TestRoleAssignmentList(TestRoleAssignment): ('project', None), ('role', None), ('effective', True), + ('inherited', False), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -369,20 +389,84 @@ class TestRoleAssignmentList(TestRoleAssignment): effective=True, project=None, role=None, - user=None) + user=None, + os_inherit_extension_inherited_to=None) - collist = ('Role', 'User', 'Group', 'Project', 'Domain') - self.assertEqual(collist, columns) + collist = ('Role', 'User', 'Group', 'Project', 'Domain', 'Inherited') + self.assertEqual(columns, collist) datalist = (( identity_fakes.role_id, identity_fakes.user_id, '', identity_fakes.project_id, - '' + '', + False + ), (identity_fakes.role_id, + identity_fakes.user_id, + '', + '', + identity_fakes.domain_id, + False + ),) + self.assertEqual(tuple(data), datalist) + + def test_role_assignment_list_inherited(self): + + self.role_assignments_mock.list.return_value = [ + fakes.FakeResource( + None, + copy.deepcopy( + (identity_fakes. + ASSIGNMENT_WITH_PROJECT_ID_AND_USER_ID_INHERITED)), + loaded=True, + ), + fakes.FakeResource( + None, + copy.deepcopy( + (identity_fakes. + ASSIGNMENT_WITH_DOMAIN_ID_AND_USER_ID_INHERITED)), + loaded=True, + ), + ] + + arglist = ['--inherited'] + verifylist = [ + ('user', None), + ('group', None), + ('domain', None), + ('project', None), + ('role', None), + ('effective', False), + ('inherited', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + self.role_assignments_mock.list.assert_called_with( + domain=None, + group=None, + effective=False, + project=None, + role=None, + user=None, + os_inherit_extension_inherited_to='projects') + + collist = ('Role', 'User', 'Group', 'Project', 'Domain', 'Inherited') + self.assertEqual(columns, collist) + datalist = (( + identity_fakes.role_id, + identity_fakes.user_id, + '', + identity_fakes.project_id, + '', + True ), (identity_fakes.role_id, identity_fakes.user_id, '', '', identity_fakes.domain_id, + True ),) self.assertEqual(datalist, tuple(data)) diff --git a/openstackclient/tests/image/v1/test_image.py b/openstackclient/tests/image/v1/test_image.py index a79df8b4..d10d3b15 100644 --- a/openstackclient/tests/image/v1/test_image.py +++ b/openstackclient/tests/image/v1/test_image.py @@ -499,8 +499,7 @@ class TestImageSet(TestImage): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples - columns, data = self.cmd.take_action(parsed_args) + self.cmd.take_action(parsed_args) kwargs = { 'name': 'new-name', @@ -517,9 +516,6 @@ class TestImageSet(TestImage): **kwargs ) - self.assertEqual(image_fakes.IMAGE_columns, columns) - self.assertEqual(image_fakes.IMAGE_data, data) - def test_image_set_bools1(self): arglist = [ '--protected', @@ -644,8 +640,7 @@ class TestImageSet(TestImage): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples - columns, data = self.cmd.take_action(parsed_args) + self.cmd.take_action(parsed_args) # VolumeManager.upload_to_image(volume, force, image_name, # container_format, disk_format) @@ -664,9 +659,6 @@ class TestImageSet(TestImage): volume='volly', ) - self.assertEqual(image_fakes.IMAGE_columns, columns) - self.assertEqual(image_fakes.IMAGE_data, data) - class TestImageShow(TestImage): diff --git a/openstackclient/tests/image/v2/fakes.py b/openstackclient/tests/image/v2/fakes.py index 1a9e301a..11ad455d 100644 --- a/openstackclient/tests/image/v2/fakes.py +++ b/openstackclient/tests/image/v2/fakes.py @@ -13,6 +13,7 @@ # under the License. # +import copy import mock from openstackclient.tests import fakes @@ -25,6 +26,7 @@ image_name = 'graven' image_owner = 'baal' image_protected = False image_visibility = 'public' +image_tags = [] IMAGE = { 'id': image_id, @@ -32,11 +34,16 @@ IMAGE = { 'owner': image_owner, 'protected': image_protected, 'visibility': image_visibility, + 'tags': image_tags } IMAGE_columns = tuple(sorted(IMAGE)) IMAGE_data = tuple((IMAGE[x] for x in sorted(IMAGE))) +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, @@ -117,6 +124,14 @@ IMAGE_schema = { "type": "string", "description": "Status of the image (READ-ONLY)" }, + "tags": { + "items": { + "type": "string", + "maxLength": 255 + }, + "type": "array", + "description": "List of strings related to the image" + }, "visibility": { "enum": [ "public", diff --git a/openstackclient/tests/image/v2/test_image.py b/openstackclient/tests/image/v2/test_image.py index bfb94765..4ce85475 100644 --- a/openstackclient/tests/image/v2/test_image.py +++ b/openstackclient/tests/image/v2/test_image.py @@ -19,6 +19,7 @@ import mock import warlock from glanceclient.v2 import schemas +from openstackclient.common import exceptions from openstackclient.image.v2 import image from openstackclient.tests import fakes from openstackclient.tests.identity.v3 import fakes as identity_fakes @@ -41,6 +42,191 @@ class TestImage(image_fakes.TestImagev2): self.domain_mock.reset_mock() +class TestImageCreate(TestImage): + + def setUp(self): + super(TestImageCreate, self).setUp() + + self.images_mock.create.return_value = fakes.FakeResource( + None, + copy.deepcopy(image_fakes.IMAGE), + loaded=True, + ) + # This is the return value for utils.find_resource() + self.images_mock.get.return_value = copy.deepcopy(image_fakes.IMAGE) + self.images_mock.update.return_value = fakes.FakeResource( + None, + copy.deepcopy(image_fakes.IMAGE), + loaded=True, + ) + + # Get the command object to test + self.cmd = image.CreateImage(self.app, None) + + def test_image_reserve_no_options(self): + mock_exception = { + 'find.side_effect': exceptions.CommandError('x'), + } + self.images_mock.configure_mock(**mock_exception) + arglist = [ + image_fakes.image_name, + ] + verifylist = [ + ('container_format', image.DEFAULT_CONTAINER_FORMAT), + ('disk_format', image.DEFAULT_DISK_FORMAT), + ('name', image_fakes.image_name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + # ImageManager.create(name=, **) + self.images_mock.create.assert_called_with( + name=image_fakes.image_name, + container_format=image.DEFAULT_CONTAINER_FORMAT, + disk_format=image.DEFAULT_DISK_FORMAT, + ) + + # Verify update() was not called, if it was show the args + self.assertEqual(self.images_mock.update.call_args_list, []) + + self.images_mock.upload.assert_called_with( + mock.ANY, mock.ANY, + ) + + self.assertEqual(image_fakes.IMAGE_columns, columns) + self.assertEqual(image_fakes.IMAGE_SHOW_data, data) + + @mock.patch('glanceclient.common.utils.get_data_file', name='Open') + def test_image_reserve_options(self, mock_open): + mock_file = mock.MagicMock(name='File') + mock_open.return_value = mock_file + mock_open.read.return_value = None + mock_exception = { + 'find.side_effect': exceptions.CommandError('x'), + } + self.images_mock.configure_mock(**mock_exception) + arglist = [ + '--container-format', 'ovf', + '--disk-format', 'fs', + '--min-disk', '10', + '--min-ram', '4', + '--protected', + '--private', + image_fakes.image_name, + ] + verifylist = [ + ('container_format', 'ovf'), + ('disk_format', 'fs'), + ('min_disk', 10), + ('min_ram', 4), + ('protected', True), + ('unprotected', False), + ('public', False), + ('private', True), + ('name', image_fakes.image_name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + # ImageManager.create(name=, **) + self.images_mock.create.assert_called_with( + name=image_fakes.image_name, + container_format='ovf', + disk_format='fs', + min_disk=10, + min_ram=4, + protected=True, + visibility='private', + ) + + # Verify update() was not called, if it was show the args + self.assertEqual(self.images_mock.update.call_args_list, []) + + self.images_mock.upload.assert_called_with( + mock.ANY, mock.ANY, + ) + + self.assertEqual(image_fakes.IMAGE_columns, columns) + self.assertEqual(image_fakes.IMAGE_SHOW_data, data) + + @mock.patch('glanceclient.common.utils.get_data_file', name='Open') + def test_image_create_file(self, mock_open): + mock_file = mock.MagicMock(name='File') + mock_open.return_value = mock_file + mock_open.read.return_value = image_fakes.IMAGE_data + mock_exception = { + 'find.side_effect': exceptions.CommandError('x'), + } + self.images_mock.configure_mock(**mock_exception) + + arglist = [ + '--file', 'filer', + '--unprotected', + '--public', + '--property', 'Alpha=1', + '--property', 'Beta=2', + '--tag', 'awesome', + '--tag', 'better', + image_fakes.image_name, + ] + verifylist = [ + ('file', 'filer'), + ('protected', False), + ('unprotected', True), + ('public', True), + ('private', False), + ('properties', {'Alpha': '1', 'Beta': '2'}), + ('tags', ['awesome', 'better']), + ('name', image_fakes.image_name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + # ImageManager.create(name=, **) + self.images_mock.create.assert_called_with( + name=image_fakes.image_name, + container_format=image.DEFAULT_CONTAINER_FORMAT, + disk_format=image.DEFAULT_DISK_FORMAT, + protected=False, + visibility='public', + Alpha='1', + Beta='2', + tags=['awesome', 'better'], + ) + + # Verify update() was not called, if it was show the args + self.assertEqual(self.images_mock.update.call_args_list, []) + + self.images_mock.upload.assert_called_with( + mock.ANY, mock.ANY, + ) + + self.assertEqual(image_fakes.IMAGE_columns, columns) + self.assertEqual(image_fakes.IMAGE_SHOW_data, data) + + def test_image_create_dead_options(self): + + arglist = [ + '--owner', 'nobody', + image_fakes.image_name, + ] + verifylist = [ + ('owner', 'nobody'), + ('name', image_fakes.image_name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, parsed_args) + + class TestAddProjectToImage(TestImage): def setUp(self): @@ -453,11 +639,10 @@ class TestRemoveProjectImage(TestImage): ) -class TestImageShow(TestImage): +class TestImageSet(TestImage): def setUp(self): - super(TestImageShow, self).setUp() - + super(TestImageSet, self).setUp() # Set up the schema self.model = warlock.model_factory( image_fakes.IMAGE_schema, @@ -465,33 +650,262 @@ class TestImageShow(TestImage): ) self.images_mock.get.return_value = self.model(**image_fakes.IMAGE) - + self.images_mock.update.return_value = self.model(**image_fakes.IMAGE) # Get the command object to test - self.cmd = image.ShowImage(self.app, None) + self.cmd = image.SetImage(self.app, None) - def test_image_show(self): + def test_image_set_options(self): arglist = [ + '--name', 'new-name', + '--owner', 'new-owner', + '--min-disk', '2', + '--min-ram', '4', + '--container-format', 'ovf', + '--disk-format', 'vmdk', image_fakes.image_id, ] verifylist = [ + ('name', 'new-name'), + ('owner', 'new-owner'), + ('min_disk', 2), + ('min_ram', 4), + ('container_format', 'ovf'), + ('disk_format', 'vmdk'), ('image', image_fakes.image_id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) # DisplayCommandBase.take_action() returns two tuples - columns, data = self.cmd.take_action(parsed_args) - self.images_mock.get.assert_called_with( + self.cmd.take_action(parsed_args) + + kwargs = { + 'name': 'new-name', + 'owner': 'new-owner', + 'min_disk': 2, + 'min_ram': 4, + 'container_format': 'ovf', + 'disk_format': 'vmdk', + } + # ImageManager.update(image, **kwargs) + self.images_mock.update.assert_called_with( + image_fakes.image_id, **kwargs) + + def test_image_set_bools1(self): + arglist = [ + '--protected', + '--private', + image_fakes.image_name, + ] + verifylist = [ + ('protected', True), + ('unprotected', False), + ('public', False), + ('private', True), + ('image', image_fakes.image_name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + self.cmd.take_action(parsed_args) + + kwargs = { + 'protected': True, + 'visibility': 'private', + } + # ImageManager.update(image, **kwargs) + self.images_mock.update.assert_called_with( image_fakes.image_id, + **kwargs ) - self.assertEqual(image_fakes.IMAGE_columns, columns) - self.assertEqual(image_fakes.IMAGE_data, data) + def test_image_set_bools2(self): + arglist = [ + '--unprotected', + '--public', + image_fakes.image_name, + ] + verifylist = [ + ('protected', False), + ('unprotected', True), + ('public', True), + ('private', False), + ('image', image_fakes.image_name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + # DisplayCommandBase.take_action() returns two tuples + self.cmd.take_action(parsed_args) -class TestImageSet(TestImage): + kwargs = { + 'protected': False, + 'visibility': 'public', + } + # ImageManager.update(image, **kwargs) + self.images_mock.update.assert_called_with( + image_fakes.image_id, + **kwargs + ) + + def test_image_set_properties(self): + arglist = [ + '--property', 'Alpha=1', + '--property', 'Beta=2', + image_fakes.image_name, + ] + verifylist = [ + ('properties', {'Alpha': '1', 'Beta': '2'}), + ('image', image_fakes.image_name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + self.cmd.take_action(parsed_args) + + kwargs = { + 'Alpha': '1', + 'Beta': '2', + } + # ImageManager.update(image, **kwargs) + self.images_mock.update.assert_called_with( + image_fakes.image_id, + **kwargs + ) + + def test_image_set_fake_properties(self): + arglist = [ + '--architecture', 'z80', + '--instance-id', '12345', + '--kernel-id', '67890', + '--os-distro', 'cpm', + '--os-version', '2.2H', + '--ramdisk-id', 'xyzpdq', + image_fakes.image_name, + ] + verifylist = [ + ('architecture', 'z80'), + ('instance_id', '12345'), + ('kernel_id', '67890'), + ('os_distro', 'cpm'), + ('os_version', '2.2H'), + ('ramdisk_id', 'xyzpdq'), + ('image', image_fakes.image_name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + self.cmd.take_action(parsed_args) + + kwargs = { + 'architecture': 'z80', + 'instance_id': '12345', + 'kernel_id': '67890', + 'os_distro': 'cpm', + 'os_version': '2.2H', + 'ramdisk_id': 'xyzpdq', + } + # ImageManager.update(image, **kwargs) + self.images_mock.update.assert_called_with( + image_fakes.image_id, + **kwargs + ) + + def test_image_set_tag(self): + arglist = [ + '--tag', 'test-tag', + image_fakes.image_name, + ] + verifylist = [ + ('tags', ['test-tag']), + ('image', image_fakes.image_name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + self.cmd.take_action(parsed_args) + + kwargs = { + 'tags': ['test-tag'], + } + # ImageManager.update(image, **kwargs) + self.images_mock.update.assert_called_with( + image_fakes.image_id, + **kwargs + ) + + def test_image_set_tag_merge(self): + old_image = copy.copy(image_fakes.IMAGE) + old_image['tags'] = ['old1', 'new2'] + self.images_mock.get.return_value = self.model(**old_image) + arglist = [ + '--tag', 'test-tag', + image_fakes.image_name, + ] + verifylist = [ + ('tags', ['test-tag']), + ('image', image_fakes.image_name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + self.cmd.take_action(parsed_args) + + kwargs = { + 'tags': ['old1', 'new2', 'test-tag'], + } + # ImageManager.update(image, **kwargs) + a, k = self.images_mock.update.call_args + self.assertEqual(image_fakes.image_id, a[0]) + self.assertTrue('tags' in k) + self.assertEqual(set(kwargs['tags']), set(k['tags'])) + + def test_image_set_tag_merge_dupe(self): + old_image = copy.copy(image_fakes.IMAGE) + old_image['tags'] = ['old1', 'new2'] + self.images_mock.get.return_value = self.model(**old_image) + arglist = [ + '--tag', 'old1', + image_fakes.image_name, + ] + verifylist = [ + ('tags', ['old1']), + ('image', image_fakes.image_name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + self.cmd.take_action(parsed_args) + + kwargs = { + 'tags': ['new2', 'old1'], + } + # ImageManager.update(image, **kwargs) + a, k = self.images_mock.update.call_args + self.assertEqual(image_fakes.image_id, a[0]) + self.assertTrue('tags' in k) + self.assertEqual(set(kwargs['tags']), set(k['tags'])) + + def test_image_set_dead_options(self): + + arglist = [ + '--visibility', '1-mile', + image_fakes.image_name, + ] + verifylist = [ + ('visibility', '1-mile'), + ('image', image_fakes.image_name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, parsed_args) + + +class TestImageShow(TestImage): def setUp(self): - super(TestImageSet, self).setUp() + super(TestImageShow, self).setUp() + # Set up the schema self.model = warlock.model_factory( image_fakes.IMAGE_schema, @@ -499,40 +913,24 @@ class TestImageSet(TestImage): ) self.images_mock.get.return_value = self.model(**image_fakes.IMAGE) - self.images_mock.update.return_value = self.model(**image_fakes.IMAGE) + # Get the command object to test - self.cmd = image.SetImage(self.app, None) + self.cmd = image.ShowImage(self.app, None) - def test_image_set_options(self): + def test_image_show(self): arglist = [ - '--name', 'new-name', - '--owner', 'new-owner', - '--min-disk', '2', - '--min-ram', '4', image_fakes.image_id, ] verifylist = [ - ('name', 'new-name'), - ('owner', 'new-owner'), - ('min_disk', 2), - ('min_ram', 4), ('image', image_fakes.image_id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) # DisplayCommandBase.take_action() returns two tuples columns, data = self.cmd.take_action(parsed_args) - - kwargs = { - 'name': 'new-name', - 'owner': 'new-owner', - 'min_disk': 2, - 'min_ram': 4, - 'protected': False - } - # ImageManager.update(image, **kwargs) - self.images_mock.update.assert_called_with( - image_fakes.image_id, **kwargs) + self.images_mock.get.assert_called_with( + image_fakes.image_id, + ) self.assertEqual(image_fakes.IMAGE_columns, columns) - self.assertEqual(image_fakes.IMAGE_data, data) + self.assertEqual(image_fakes.IMAGE_SHOW_data, data) diff --git a/openstackclient/tests/object/v1/fakes.py b/openstackclient/tests/object/v1/fakes.py index 6aef05b1..986ab2f3 100644 --- a/openstackclient/tests/object/v1/fakes.py +++ b/openstackclient/tests/object/v1/fakes.py @@ -13,7 +13,7 @@ # under the License. # -from keystoneclient import session +from keystoneauth1 import session from openstackclient.api import object_store_v1 as object_store from openstackclient.tests import utils diff --git a/openstackclient/tests/object/v1/test_container_all.py b/openstackclient/tests/object/v1/test_container_all.py index 8b200e09..69fc0f39 100644 --- a/openstackclient/tests/object/v1/test_container_all.py +++ b/openstackclient/tests/object/v1/test_container_all.py @@ -286,7 +286,6 @@ class TestContainerShow(TestContainerAll): def test_object_show_container(self): headers = { - 'x-container-meta-owner': object_fakes.ACCOUNT_ID, 'x-container-object-count': '42', 'x-container-bytes-used': '123', 'x-container-read': 'qaz', diff --git a/openstackclient/tests/test_shell.py b/openstackclient/tests/test_shell.py index cacd2fb7..c548d890 100644 --- a/openstackclient/tests/test_shell.py +++ b/openstackclient/tests/test_shell.py @@ -41,17 +41,17 @@ DEFAULT_SERVICE_URL = "http://127.0.0.1:8771/v3.0/" DEFAULT_AUTH_PLUGIN = "v2password" DEFAULT_INTERFACE = "internal" -DEFAULT_COMPUTE_API_VERSION = "2" -DEFAULT_IDENTITY_API_VERSION = "2" -DEFAULT_IMAGE_API_VERSION = "2" -DEFAULT_VOLUME_API_VERSION = "1" -DEFAULT_NETWORK_API_VERSION = "2" - -LIB_COMPUTE_API_VERSION = "2" -LIB_IDENTITY_API_VERSION = "2" -LIB_IMAGE_API_VERSION = "1" -LIB_VOLUME_API_VERSION = "1" -LIB_NETWORK_API_VERSION = "2" +DEFAULT_COMPUTE_API_VERSION = "" +DEFAULT_IDENTITY_API_VERSION = "" +DEFAULT_IMAGE_API_VERSION = "" +DEFAULT_VOLUME_API_VERSION = "" +DEFAULT_NETWORK_API_VERSION = "" + +LIB_COMPUTE_API_VERSION = "" +LIB_IDENTITY_API_VERSION = "" +LIB_IMAGE_API_VERSION = "" +LIB_VOLUME_API_VERSION = "" +LIB_NETWORK_API_VERSION = "" CLOUD_1 = { 'clouds': { @@ -205,7 +205,9 @@ class TestShell(utils.TestCase): initialize_app(). """ - self.occ_get_one = mock.Mock("Test Shell") + cloud = mock.Mock(name="cloudy") + cloud.config = {} + self.occ_get_one = mock.Mock(return_value=cloud) with mock.patch( "os_client_config.config.OpenStackConfig.get_one_cloud", self.occ_get_one, diff --git a/openstackclient/tests/volume/v1/test_volume.py b/openstackclient/tests/volume/v1/test_volume.py index f73260e9..70ff50de 100644 --- a/openstackclient/tests/volume/v1/test_volume.py +++ b/openstackclient/tests/volume/v1/test_volume.py @@ -527,6 +527,188 @@ class TestVolumeCreate(TestVolume): self.assertEqual(datalist, data) +class TestVolumeList(TestVolume): + + def setUp(self): + super(TestVolumeList, self).setUp() + + self.volumes_mock.list.return_value = [ + fakes.FakeResource( + None, + copy.deepcopy(volume_fakes.VOLUME), + loaded=True, + ), + ] + + # Get the command object to test + self.cmd = volume.ListVolume(self.app, None) + + def test_volume_list_no_options(self): + arglist = [] + verifylist = [ + ('long', False), + ('all_projects', False), + ('name', None), + ('status', None), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + collist = ( + 'ID', + 'Display Name', + 'Status', + 'Size', + 'Attached to', + ) + self.assertEqual(collist, columns) + + datalist = (( + volume_fakes.volume_id, + volume_fakes.volume_name, + volume_fakes.volume_status, + volume_fakes.volume_size, + '', + ), ) + self.assertEqual(datalist, tuple(data)) + + def test_volume_list_name(self): + arglist = [ + '--name', volume_fakes.volume_name, + ] + verifylist = [ + ('long', False), + ('all_projects', False), + ('name', volume_fakes.volume_name), + ('status', None), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + collist = ( + 'ID', + 'Display Name', + 'Status', + 'Size', + 'Attached to', + ) + self.assertEqual(collist, tuple(columns)) + + datalist = (( + volume_fakes.volume_id, + volume_fakes.volume_name, + volume_fakes.volume_status, + volume_fakes.volume_size, + '', + ), ) + self.assertEqual(datalist, tuple(data)) + + def test_volume_list_status(self): + arglist = [ + '--status', volume_fakes.volume_status, + ] + verifylist = [ + ('long', False), + ('all_projects', False), + ('name', None), + ('status', volume_fakes.volume_status), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + collist = ( + 'ID', + 'Display Name', + 'Status', + 'Size', + 'Attached to', + ) + self.assertEqual(collist, tuple(columns)) + + datalist = (( + volume_fakes.volume_id, + volume_fakes.volume_name, + volume_fakes.volume_status, + volume_fakes.volume_size, + '', + ), ) + self.assertEqual(datalist, tuple(data)) + + def test_volume_list_all_projects(self): + arglist = [ + '--all-projects', + ] + verifylist = [ + ('long', False), + ('all_projects', True), + ('name', None), + ('status', None), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + collist = ( + 'ID', + 'Display Name', + 'Status', + 'Size', + 'Attached to', + ) + self.assertEqual(collist, columns) + + datalist = (( + volume_fakes.volume_id, + volume_fakes.volume_name, + volume_fakes.volume_status, + volume_fakes.volume_size, + '', + ), ) + self.assertEqual(datalist, tuple(data)) + + def test_volume_list_long(self): + arglist = [ + '--long', + ] + verifylist = [ + ('long', True), + ('all_projects', False), + ('name', None), + ('status', None), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + collist = ( + 'ID', + 'Display Name', + 'Status', + 'Size', + 'Type', + 'Bootable', + 'Attached to', + 'Properties', + ) + self.assertEqual(collist, columns) + + datalist = (( + volume_fakes.volume_id, + volume_fakes.volume_name, + volume_fakes.volume_status, + volume_fakes.volume_size, + volume_fakes.volume_type, + '', + '', + "Alpha='a', Beta='b', Gamma='g'", + ), ) + self.assertEqual(datalist, tuple(data)) + + class TestVolumeSet(TestVolume): def setUp(self): diff --git a/openstackclient/tests/volume/v2/fakes.py b/openstackclient/tests/volume/v2/fakes.py index c896ed6d..b9b2ae87 100644 --- a/openstackclient/tests/volume/v2/fakes.py +++ b/openstackclient/tests/volume/v2/fakes.py @@ -16,9 +16,15 @@ import copy import mock from openstackclient.tests import fakes -from openstackclient.tests.identity.v2_0 import fakes as identity_fakes +from openstackclient.tests.identity.v3 import fakes as identity_fakes +from openstackclient.tests.image.v2 import fakes as image_fakes from openstackclient.tests import utils +volume_attachment_server = { + 'device': '/dev/ice', + 'server_id': '1233', +} + volume_id = "ce26708d-a7f8-4b4b-9861-4a80256615a6" volume_name = "fake_volume" volume_description = "fake description" @@ -26,11 +32,14 @@ volume_status = "available" volume_size = 20 volume_type = "fake_lvmdriver-1" volume_metadata = { - "foo": "bar" + 'Alpha': 'a', + 'Beta': 'b', + 'Gamma': 'g', } +volume_metadata_str = "Alpha='a', Beta='b', Gamma='g'" volume_snapshot_id = 1 volume_availability_zone = "nova" -volume_attachments = ["fake_attachments"] +volume_attachments = [volume_attachment_server] VOLUME = { "id": volume_id, @@ -169,6 +178,13 @@ QOS_WITH_ASSOCIATIONS = { 'associations': [qos_association] } +image_id = 'im1' +image_name = 'graven' +IMAGE = { + 'id': image_id, + 'name': image_name +} + class FakeVolumeClient(object): def __init__(self, **kwargs): @@ -196,7 +212,11 @@ class TestVolume(utils.TestCommand): endpoint=fakes.AUTH_URL, token=fakes.AUTH_TOKEN ) - self.app.client_manager.identity = identity_fakes.FakeIdentityv2Client( + self.app.client_manager.identity = identity_fakes.FakeIdentityv3Client( + endpoint=fakes.AUTH_URL, + token=fakes.AUTH_TOKEN + ) + self.app.client_manager.image = image_fakes.FakeImagev2Client( endpoint=fakes.AUTH_URL, token=fakes.AUTH_TOKEN ) diff --git a/openstackclient/tests/volume/v2/test_type.py b/openstackclient/tests/volume/v2/test_type.py index c5b27fa5..9a07263b 100644 --- a/openstackclient/tests/volume/v2/test_type.py +++ b/openstackclient/tests/volume/v2/test_type.py @@ -19,6 +19,20 @@ from openstackclient.tests.volume.v2 import fakes as volume_fakes from openstackclient.volume.v2 import volume_type +class FakeTypeResource(fakes.FakeResource): + + _keys = {'property': 'value'} + + def set_keys(self, args): + self._keys.update(args) + + def unset_keys(self, key): + self._keys.pop(key, None) + + def get_keys(self): + return self._keys + + class TestType(volume_fakes.TestVolume): def setUp(self): @@ -184,6 +198,122 @@ class TestTypeShow(TestType): self.assertEqual(volume_fakes.TYPE_FORMATTED_data, data) +class TestTypeSet(TestType): + + def setUp(self): + super(TestTypeSet, self).setUp() + + self.types_mock.get.return_value = FakeTypeResource( + None, + copy.deepcopy(volume_fakes.TYPE), + loaded=True, + ) + + # Get the command object to test + self.cmd = volume_type.SetVolumeType(self.app, None) + + def test_type_set_name(self): + new_name = 'new_name' + arglist = [ + '--name', new_name, + volume_fakes.type_id, + ] + verifylist = [ + ('name', new_name), + ('description', None), + ('property', None), + ('volume_type', volume_fakes.type_id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'name': new_name, + } + self.types_mock.update.assert_called_with( + volume_fakes.type_id, + **kwargs + ) + + def test_type_set_description(self): + new_desc = 'new_desc' + arglist = [ + '--description', new_desc, + volume_fakes.type_id, + ] + verifylist = [ + ('name', None), + ('description', new_desc), + ('property', None), + ('volume_type', volume_fakes.type_id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'description': new_desc, + } + self.types_mock.update.assert_called_with( + volume_fakes.type_id, + **kwargs + ) + + def test_type_set_property(self): + arglist = [ + '--property', 'myprop=myvalue', + volume_fakes.type_id, + ] + verifylist = [ + ('name', None), + ('description', None), + ('property', {'myprop': 'myvalue'}), + ('volume_type', volume_fakes.type_id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + result = self.types_mock.get.return_value._keys + self.assertIn('myprop', result) + self.assertEqual('myvalue', result['myprop']) + + +class TestTypeUnset(TestType): + + def setUp(self): + super(TestTypeUnset, self).setUp() + + self.types_mock.get.return_value = FakeTypeResource( + None, + copy.deepcopy(volume_fakes.TYPE), + loaded=True, + ) + + self.cmd = volume_type.UnsetVolumeType(self.app, None) + + def test_type_unset(self): + arglist = [ + '--property', 'property', + volume_fakes.type_id, + ] + verifylist = [ + ('property', 'property'), + ('volume_type', volume_fakes.type_id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + result = self.types_mock.get.return_value._keys + + self.assertNotIn('property', result) + + class TestTypeDelete(TestType): def setUp(self): super(TestTypeDelete, self).setUp() diff --git a/openstackclient/tests/volume/v2/test_volume.py b/openstackclient/tests/volume/v2/test_volume.py index 9e991b72..70324b6e 100644 --- a/openstackclient/tests/volume/v2/test_volume.py +++ b/openstackclient/tests/volume/v2/test_volume.py @@ -15,18 +15,847 @@ import copy from openstackclient.tests import fakes +from openstackclient.tests.identity.v3 import fakes as identity_fakes from openstackclient.tests.volume.v2 import fakes as volume_fakes from openstackclient.volume.v2 import volume class TestVolume(volume_fakes.TestVolume): - def setUp(self): super(TestVolume, self).setUp() self.volumes_mock = self.app.client_manager.volume.volumes self.volumes_mock.reset_mock() + self.projects_mock = self.app.client_manager.identity.projects + self.projects_mock.reset_mock() + + self.users_mock = self.app.client_manager.identity.users + self.users_mock.reset_mock() + + self.images_mock = self.app.client_manager.image.images + self.images_mock.reset_mock() + + +class TestVolumeCreate(TestVolume): + def setUp(self): + super(TestVolumeCreate, self).setUp() + + self.volumes_mock.create.return_value = fakes.FakeResource( + None, + copy.deepcopy(volume_fakes.VOLUME), + loaded=True, + ) + + # Get the command object to test + self.cmd = volume.CreateVolume(self.app, None) + + def test_volume_create_min_options(self): + arglist = [ + '--size', str(volume_fakes.volume_size), + volume_fakes.volume_name, + ] + verifylist = [ + ('size', volume_fakes.volume_size), + ('name', volume_fakes.volume_name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + self.volumes_mock.create.assert_called_with( + size=volume_fakes.volume_size, + snapshot_id=None, + name=volume_fakes.volume_name, + description=None, + volume_type=None, + user_id=None, + project_id=None, + availability_zone=None, + metadata=None, + imageRef=None, + source_volid=None + ) + + collist = ( + 'attachments', + 'availability_zone', + 'description', + 'id', + 'name', + 'properties', + 'size', + 'snapshot_id', + 'status', + 'type', + ) + self.assertEqual(collist, columns) + datalist = ( + volume_fakes.volume_attachments, + volume_fakes.volume_availability_zone, + volume_fakes.volume_description, + volume_fakes.volume_id, + volume_fakes.volume_name, + volume_fakes.volume_metadata_str, + volume_fakes.volume_size, + volume_fakes.volume_snapshot_id, + volume_fakes.volume_status, + volume_fakes.volume_type, + ) + self.assertEqual(datalist, data) + + def test_volume_create_options(self): + arglist = [ + '--size', str(volume_fakes.volume_size), + '--description', volume_fakes.volume_description, + '--type', volume_fakes.volume_type, + '--availability-zone', volume_fakes.volume_availability_zone, + volume_fakes.volume_name, + ] + verifylist = [ + ('size', volume_fakes.volume_size), + ('description', volume_fakes.volume_description), + ('type', volume_fakes.volume_type), + ('availability_zone', volume_fakes.volume_availability_zone), + ('name', volume_fakes.volume_name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + self.volumes_mock.create.assert_called_with( + size=volume_fakes.volume_size, + snapshot_id=None, + name=volume_fakes.volume_name, + description=volume_fakes.volume_description, + volume_type=volume_fakes.volume_type, + user_id=None, + project_id=None, + availability_zone=volume_fakes.volume_availability_zone, + metadata=None, + imageRef=None, + source_volid=None + ) + + collist = ( + 'attachments', + 'availability_zone', + 'description', + 'id', + 'name', + 'properties', + 'size', + 'snapshot_id', + 'status', + 'type', + ) + self.assertEqual(collist, columns) + datalist = ( + volume_fakes.volume_attachments, + volume_fakes.volume_availability_zone, + volume_fakes.volume_description, + volume_fakes.volume_id, + volume_fakes.volume_name, + volume_fakes.volume_metadata_str, + volume_fakes.volume_size, + volume_fakes.volume_snapshot_id, + volume_fakes.volume_status, + volume_fakes.volume_type, + ) + self.assertEqual(datalist, data) + + def test_volume_create_user_project_id(self): + # Return a project + self.projects_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.PROJECT), + loaded=True, + ) + # Return a user + self.users_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.USER), + loaded=True, + ) + + arglist = [ + '--size', str(volume_fakes.volume_size), + '--project', identity_fakes.project_id, + '--user', identity_fakes.user_id, + volume_fakes.volume_name, + ] + verifylist = [ + ('size', volume_fakes.volume_size), + ('project', identity_fakes.project_id), + ('user', identity_fakes.user_id), + ('name', volume_fakes.volume_name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + self.volumes_mock.create.assert_called_with( + size=volume_fakes.volume_size, + snapshot_id=None, + name=volume_fakes.volume_name, + description=None, + volume_type=None, + user_id=identity_fakes.user_id, + project_id=identity_fakes.project_id, + availability_zone=None, + metadata=None, + imageRef=None, + source_volid=None + ) + + collist = ( + 'attachments', + 'availability_zone', + 'description', + 'id', + 'name', + 'properties', + 'size', + 'snapshot_id', + 'status', + 'type', + ) + self.assertEqual(collist, columns) + datalist = ( + volume_fakes.volume_attachments, + volume_fakes.volume_availability_zone, + volume_fakes.volume_description, + volume_fakes.volume_id, + volume_fakes.volume_name, + volume_fakes.volume_metadata_str, + volume_fakes.volume_size, + volume_fakes.volume_snapshot_id, + volume_fakes.volume_status, + volume_fakes.volume_type, + ) + self.assertEqual(datalist, data) + + def test_volume_create_user_project_name(self): + # Return a project + self.projects_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.PROJECT), + loaded=True, + ) + # Return a user + self.users_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.USER), + loaded=True, + ) + + arglist = [ + '--size', str(volume_fakes.volume_size), + '--project', identity_fakes.project_name, + '--user', identity_fakes.user_name, + volume_fakes.volume_name, + ] + verifylist = [ + ('size', volume_fakes.volume_size), + ('project', identity_fakes.project_name), + ('user', identity_fakes.user_name), + ('name', volume_fakes.volume_name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + self.volumes_mock.create.assert_called_with( + size=volume_fakes.volume_size, + snapshot_id=None, + name=volume_fakes.volume_name, + description=None, + volume_type=None, + user_id=identity_fakes.user_id, + project_id=identity_fakes.project_id, + availability_zone=None, + metadata=None, + imageRef=None, + source_volid=None + ) + + collist = ( + 'attachments', + 'availability_zone', + 'description', + 'id', + 'name', + 'properties', + 'size', + 'snapshot_id', + 'status', + 'type', + ) + self.assertEqual(collist, columns) + datalist = ( + volume_fakes.volume_attachments, + volume_fakes.volume_availability_zone, + volume_fakes.volume_description, + volume_fakes.volume_id, + volume_fakes.volume_name, + volume_fakes.volume_metadata_str, + volume_fakes.volume_size, + volume_fakes.volume_snapshot_id, + volume_fakes.volume_status, + volume_fakes.volume_type, + ) + self.assertEqual(datalist, data) + + def test_volume_create_properties(self): + arglist = [ + '--property', 'Alpha=a', + '--property', 'Beta=b', + '--size', str(volume_fakes.volume_size), + volume_fakes.volume_name, + ] + verifylist = [ + ('property', {'Alpha': 'a', 'Beta': 'b'}), + ('size', volume_fakes.volume_size), + ('name', volume_fakes.volume_name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + self.volumes_mock.create.assert_called_with( + size=volume_fakes.volume_size, + snapshot_id=None, + name=volume_fakes.volume_name, + description=None, + volume_type=None, + user_id=None, + project_id=None, + availability_zone=None, + metadata={'Alpha': 'a', 'Beta': 'b'}, + imageRef=None, + source_volid=None + ) + + collist = ( + 'attachments', + 'availability_zone', + 'description', + 'id', + 'name', + 'properties', + 'size', + 'snapshot_id', + 'status', + 'type', + ) + self.assertEqual(collist, columns) + datalist = ( + volume_fakes.volume_attachments, + volume_fakes.volume_availability_zone, + volume_fakes.volume_description, + volume_fakes.volume_id, + volume_fakes.volume_name, + volume_fakes.volume_metadata_str, + volume_fakes.volume_size, + volume_fakes.volume_snapshot_id, + volume_fakes.volume_status, + volume_fakes.volume_type, + ) + self.assertEqual(datalist, data) + + def test_volume_create_image_id(self): + self.images_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(volume_fakes.IMAGE), + loaded=True, + ) + + arglist = [ + '--image', volume_fakes.image_id, + '--size', str(volume_fakes.volume_size), + volume_fakes.volume_name, + ] + verifylist = [ + ('image', volume_fakes.image_id), + ('size', volume_fakes.volume_size), + ('name', volume_fakes.volume_name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + self.volumes_mock.create.assert_called_with( + size=volume_fakes.volume_size, + snapshot_id=None, + name=volume_fakes.volume_name, + description=None, + volume_type=None, + user_id=None, + project_id=None, + availability_zone=None, + metadata=None, + imageRef=volume_fakes.image_id, + source_volid=None + ) + + collist = ( + 'attachments', + 'availability_zone', + 'description', + 'id', + 'name', + 'properties', + 'size', + 'snapshot_id', + 'status', + 'type', + ) + self.assertEqual(collist, columns) + datalist = ( + volume_fakes.volume_attachments, + volume_fakes.volume_availability_zone, + volume_fakes.volume_description, + volume_fakes.volume_id, + volume_fakes.volume_name, + volume_fakes.volume_metadata_str, + volume_fakes.volume_size, + volume_fakes.volume_snapshot_id, + volume_fakes.volume_status, + volume_fakes.volume_type, + ) + self.assertEqual(datalist, data) + + def test_volume_create_image_name(self): + self.images_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(volume_fakes.IMAGE), + loaded=True, + ) + + arglist = [ + '--image', volume_fakes.image_name, + '--size', str(volume_fakes.volume_size), + volume_fakes.volume_name, + ] + verifylist = [ + ('image', volume_fakes.image_name), + ('size', volume_fakes.volume_size), + ('name', volume_fakes.volume_name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + self.volumes_mock.create.assert_called_with( + size=volume_fakes.volume_size, + snapshot_id=None, + name=volume_fakes.volume_name, + description=None, + volume_type=None, + user_id=None, + project_id=None, + availability_zone=None, + metadata=None, + imageRef=volume_fakes.image_id, + source_volid=None + ) + + collist = ( + 'attachments', + 'availability_zone', + 'description', + 'id', + 'name', + 'properties', + 'size', + 'snapshot_id', + 'status', + 'type', + ) + self.assertEqual(collist, columns) + datalist = ( + volume_fakes.volume_attachments, + volume_fakes.volume_availability_zone, + volume_fakes.volume_description, + volume_fakes.volume_id, + volume_fakes.volume_name, + volume_fakes.volume_metadata_str, + volume_fakes.volume_size, + volume_fakes.volume_snapshot_id, + volume_fakes.volume_status, + volume_fakes.volume_type, + ) + self.assertEqual(datalist, data) + + +class TestVolumeList(TestVolume): + + def setUp(self): + super(TestVolumeList, self).setUp() + + self.volumes_mock.list.return_value = [ + fakes.FakeResource( + None, + copy.deepcopy(volume_fakes.VOLUME), + loaded=True, + ), + ] + + self.users_mock.get.return_value = [ + fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.USER), + loaded=True, + ), + ] + + self.projects_mock.get.return_value = [ + fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.PROJECT), + loaded=True, + ), + ] + + # Get the command object to test + self.cmd = volume.ListVolume(self.app, None) + + def test_volume_list_no_options(self): + arglist = [] + verifylist = [ + ('long', False), + ('all_projects', False), + ('name', None), + ('status', None), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + collist = [ + 'ID', + 'Display Name', + 'Status', + 'Size', + 'Attached to', + ] + self.assertEqual(collist, columns) + + server = volume_fakes.volume_attachment_server['server_id'] + device = volume_fakes.volume_attachment_server['device'] + msg = 'Attached to %s on %s ' % (server, device) + datalist = (( + volume_fakes.volume_id, + volume_fakes.volume_name, + volume_fakes.volume_status, + volume_fakes.volume_size, + msg, + ), ) + self.assertEqual(datalist, tuple(data)) + + def test_volume_list_project(self): + arglist = [ + '--project', identity_fakes.project_name, + ] + verifylist = [ + ('project', identity_fakes.project_name), + ('long', False), + ('all_projects', False), + ('status', None), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + collist = ( + 'ID', + 'Display Name', + 'Status', + 'Size', + 'Attached to', + ) + self.assertEqual(collist, tuple(columns)) + + server = volume_fakes.volume_attachment_server['server_id'] + device = volume_fakes.volume_attachment_server['device'] + msg = 'Attached to %s on %s ' % (server, device) + + datalist = (( + volume_fakes.volume_id, + volume_fakes.volume_name, + volume_fakes.volume_status, + volume_fakes.volume_size, + msg, + ), ) + self.assertEqual(datalist, tuple(data)) + + def test_volume_list_project_domain(self): + arglist = [ + '--project', identity_fakes.project_name, + '--project-domain', identity_fakes.domain_name, + ] + verifylist = [ + ('project', identity_fakes.project_name), + ('project_domain', identity_fakes.domain_name), + ('long', False), + ('all_projects', False), + ('status', None), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + collist = ( + 'ID', + 'Display Name', + 'Status', + 'Size', + 'Attached to', + ) + self.assertEqual(collist, tuple(columns)) + + server = volume_fakes.volume_attachment_server['server_id'] + device = volume_fakes.volume_attachment_server['device'] + msg = 'Attached to %s on %s ' % (server, device) + + datalist = (( + volume_fakes.volume_id, + volume_fakes.volume_name, + volume_fakes.volume_status, + volume_fakes.volume_size, + msg, + ), ) + self.assertEqual(datalist, tuple(data)) + + def test_volume_list_user(self): + arglist = [ + '--user', identity_fakes.user_name, + ] + verifylist = [ + ('user', identity_fakes.user_name), + ('long', False), + ('all_projects', False), + ('status', None), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + collist = ( + 'ID', + 'Display Name', + 'Status', + 'Size', + 'Attached to', + ) + self.assertEqual(collist, tuple(columns)) + + server = volume_fakes.volume_attachment_server['server_id'] + device = volume_fakes.volume_attachment_server['device'] + msg = 'Attached to %s on %s ' % (server, device) + + datalist = (( + volume_fakes.volume_id, + volume_fakes.volume_name, + volume_fakes.volume_status, + volume_fakes.volume_size, + msg, + ), ) + self.assertEqual(datalist, tuple(data)) + + def test_volume_list_user_domain(self): + arglist = [ + '--user', identity_fakes.user_name, + '--user-domain', identity_fakes.domain_name, + ] + verifylist = [ + ('user', identity_fakes.user_name), + ('user_domain', identity_fakes.domain_name), + ('long', False), + ('all_projects', False), + ('status', None), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + collist = ( + 'ID', + 'Display Name', + 'Status', + 'Size', + 'Attached to', + ) + self.assertEqual(collist, tuple(columns)) + + server = volume_fakes.volume_attachment_server['server_id'] + device = volume_fakes.volume_attachment_server['device'] + msg = 'Attached to %s on %s ' % (server, device) + + datalist = (( + volume_fakes.volume_id, + volume_fakes.volume_name, + volume_fakes.volume_status, + volume_fakes.volume_size, + msg, + ), ) + self.assertEqual(datalist, tuple(data)) + + def test_volume_list_name(self): + arglist = [ + '--name', volume_fakes.volume_name, + ] + verifylist = [ + ('long', False), + ('all_projects', False), + ('name', volume_fakes.volume_name), + ('status', None), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + collist = ( + 'ID', + 'Display Name', + 'Status', + 'Size', + 'Attached to', + ) + self.assertEqual(collist, tuple(columns)) + + server = volume_fakes.volume_attachment_server['server_id'] + device = volume_fakes.volume_attachment_server['device'] + msg = 'Attached to %s on %s ' % (server, device) + + datalist = (( + volume_fakes.volume_id, + volume_fakes.volume_name, + volume_fakes.volume_status, + volume_fakes.volume_size, + msg, + ), ) + self.assertEqual(datalist, tuple(data)) + + def test_volume_list_status(self): + arglist = [ + '--status', volume_fakes.volume_status, + ] + verifylist = [ + ('long', False), + ('all_projects', False), + ('name', None), + ('status', volume_fakes.volume_status), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + collist = ( + 'ID', + 'Display Name', + 'Status', + 'Size', + 'Attached to', + ) + self.assertEqual(collist, tuple(columns)) + + server = volume_fakes.volume_attachment_server['server_id'] + device = volume_fakes.volume_attachment_server['device'] + msg = 'Attached to %s on %s ' % (server, device) + datalist = (( + volume_fakes.volume_id, + volume_fakes.volume_name, + volume_fakes.volume_status, + volume_fakes.volume_size, + msg, + ), ) + self.assertEqual(datalist, tuple(data)) + + def test_volume_list_all_projects(self): + arglist = [ + '--all-projects', + ] + verifylist = [ + ('long', False), + ('all_projects', True), + ('name', None), + ('status', None), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + collist = [ + 'ID', + 'Display Name', + 'Status', + 'Size', + 'Attached to', + ] + self.assertEqual(collist, columns) + + server = volume_fakes.volume_attachment_server['server_id'] + device = volume_fakes.volume_attachment_server['device'] + msg = 'Attached to %s on %s ' % (server, device) + datalist = (( + volume_fakes.volume_id, + volume_fakes.volume_name, + volume_fakes.volume_status, + volume_fakes.volume_size, + msg, + ), ) + self.assertEqual(datalist, tuple(data)) + + def test_volume_list_long(self): + arglist = [ + '--long', + ] + verifylist = [ + ('long', True), + ('all_projects', False), + ('name', None), + ('status', None), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + collist = [ + 'ID', + 'Display Name', + 'Status', + 'Size', + 'Type', + 'Bootable', + 'Attached to', + 'Properties', + ] + self.assertEqual(collist, columns) + + server = volume_fakes.volume_attachment_server['server_id'] + device = volume_fakes.volume_attachment_server['device'] + msg = 'Attached to %s on %s ' % (server, device) + datalist = (( + volume_fakes.volume_id, + volume_fakes.volume_name, + volume_fakes.volume_status, + volume_fakes.volume_size, + volume_fakes.volume_type, + '', + msg, + "Alpha='a', Beta='b', Gamma='g'", + ), ) + self.assertEqual(datalist, tuple(data)) + class TestVolumeShow(TestVolume): def setUp(self): diff --git a/openstackclient/volume/client.py b/openstackclient/volume/client.py index 093178e3..0973868b 100644 --- a/openstackclient/volume/client.py +++ b/openstackclient/volume/client.py @@ -19,7 +19,7 @@ from openstackclient.common import utils LOG = logging.getLogger(__name__) -DEFAULT_VOLUME_API_VERSION = '1' +DEFAULT_API_VERSION = '2' API_VERSION_OPTION = 'os_volume_api_version' API_NAME = "volume" API_VERSIONS = { @@ -72,10 +72,8 @@ def build_option_parser(parser): parser.add_argument( '--os-volume-api-version', metavar='<volume-api-version>', - default=utils.env( - 'OS_VOLUME_API_VERSION', - default=DEFAULT_VOLUME_API_VERSION), + default=utils.env('OS_VOLUME_API_VERSION'), help='Volume API version, default=' + - DEFAULT_VOLUME_API_VERSION + + DEFAULT_API_VERSION + ' (Env: OS_VOLUME_API_VERSION)') return parser diff --git a/openstackclient/volume/v1/backup.py b/openstackclient/volume/v1/backup.py index 03c63a05..c668e366 100644 --- a/openstackclient/volume/v1/backup.py +++ b/openstackclient/volume/v1/backup.py @@ -57,8 +57,8 @@ class CreateBackup(show.ShowOne): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) volume_client = self.app.client_manager.volume volume_id = utils.find_resource(volume_client.volumes, parsed_args.volume).id @@ -88,8 +88,8 @@ class DeleteBackup(command.Command): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) volume_client = self.app.client_manager.volume for backup in parsed_args.backups: backup_id = utils.find_resource(volume_client.backups, @@ -113,8 +113,8 @@ class ListBackup(lister.Lister): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) def _format_volume_id(volume_id): """Return a volume name if available @@ -172,8 +172,8 @@ class RestoreBackup(command.Command): help='Volume to restore to (name or ID)') return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) volume_client = self.app.client_manager.volume backup = utils.find_resource(volume_client.backups, parsed_args.backup) @@ -196,8 +196,8 @@ class ShowBackup(show.ShowOne): help='Backup to display (ID only)') return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) volume_client = self.app.client_manager.volume backup = utils.find_resource(volume_client.backups, parsed_args.backup) diff --git a/openstackclient/volume/v1/qos_specs.py b/openstackclient/volume/v1/qos_specs.py index 8e909e3d..d1c70113 100644 --- a/openstackclient/volume/v1/qos_specs.py +++ b/openstackclient/volume/v1/qos_specs.py @@ -45,8 +45,8 @@ class AssociateQos(command.Command): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) volume_client = self.app.client_manager.volume qos_spec = utils.find_resource(volume_client.qos_specs, parsed_args.qos_spec) @@ -88,8 +88,8 @@ class CreateQos(show.ShowOne): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) volume_client = self.app.client_manager.volume specs = {} specs.update({'consumer': parsed_args.consumer}) @@ -117,8 +117,8 @@ class DeleteQos(command.Command): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', 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) @@ -153,8 +153,8 @@ class DisassociateQos(command.Command): return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) volume_client = self.app.client_manager.volume qos_spec = utils.find_resource(volume_client.qos_specs, parsed_args.qos_spec) @@ -174,8 +174,8 @@ class ListQos(lister.Lister): log = logging.getLogger(__name__ + '.ListQos') + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) volume_client = self.app.client_manager.volume qos_specs_list = volume_client.qos_specs.list() @@ -218,8 +218,8 @@ class SetQos(command.Command): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) volume_client = self.app.client_manager.volume qos_spec = utils.find_resource(volume_client.qos_specs, parsed_args.qos_spec) @@ -247,8 +247,8 @@ class ShowQos(show.ShowOne): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) volume_client = self.app.client_manager.volume qos_spec = utils.find_resource(volume_client.qos_specs, parsed_args.qos_spec) @@ -287,8 +287,8 @@ class UnsetQos(command.Command): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) volume_client = self.app.client_manager.volume qos_spec = utils.find_resource(volume_client.qos_specs, parsed_args.qos_spec) diff --git a/openstackclient/volume/v1/snapshot.py b/openstackclient/volume/v1/snapshot.py index e81efb5a..de7bb5b9 100644 --- a/openstackclient/volume/v1/snapshot.py +++ b/openstackclient/volume/v1/snapshot.py @@ -59,8 +59,8 @@ class CreateSnapshot(show.ShowOne): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) volume_client = self.app.client_manager.volume volume_id = utils.find_resource(volume_client.volumes, parsed_args.volume).id @@ -93,8 +93,8 @@ class DeleteSnapshot(command.Command): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) volume_client = self.app.client_manager.volume for snapshot in parsed_args.snapshots: snapshot_id = utils.find_resource(volume_client.volume_snapshots, @@ -118,8 +118,8 @@ class ListSnapshot(lister.Lister): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) def _format_volume_id(volume_id): """Return a volume name if available @@ -194,8 +194,8 @@ class SetSnapshot(command.Command): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) volume_client = self.app.client_manager.volume snapshot = utils.find_resource(volume_client.volume_snapshots, parsed_args.snapshot) @@ -231,8 +231,8 @@ class ShowSnapshot(show.ShowOne): help='Snapshot to display (name or ID)') return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) volume_client = self.app.client_manager.volume snapshot = utils.find_resource(volume_client.volume_snapshots, parsed_args.snapshot) @@ -267,8 +267,8 @@ class UnsetSnapshot(command.Command): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) volume_client = self.app.client_manager.volume snapshot = utils.find_resource( volume_client.volume_snapshots, parsed_args.snapshot) diff --git a/openstackclient/volume/v1/volume.py b/openstackclient/volume/v1/volume.py index 884611ec..92afe8b0 100644 --- a/openstackclient/volume/v1/volume.py +++ b/openstackclient/volume/v1/volume.py @@ -102,8 +102,8 @@ class CreateVolume(show.ShowOne): return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity image_client = self.app.client_manager.image @@ -186,8 +186,8 @@ class DeleteVolume(command.Command): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) volume_client = self.app.client_manager.volume for volume in parsed_args.volumes: volume_obj = utils.find_resource( @@ -207,16 +207,16 @@ class ListVolume(lister.Lister): def get_parser(self, prog_name): parser = super(ListVolume, self).get_parser(prog_name) parser.add_argument( + '--name', + metavar='<name>', + help='Filter results by volume name', + ) + parser.add_argument( '--status', metavar='<status>', help='Filter results by status', ) parser.add_argument( - '--name', - metavar='<name>', - help='Filter results by name', - ) - parser.add_argument( '--all-projects', action='store_true', default=False, @@ -230,8 +230,8 @@ class ListVolume(lister.Lister): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) volume_client = self.app.client_manager.volume compute_client = self.app.client_manager.compute @@ -351,8 +351,8 @@ class SetVolume(command.Command): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) volume_client = self.app.client_manager.volume volume = utils.find_resource(volume_client.volumes, parsed_args.volume) @@ -399,8 +399,8 @@ class ShowVolume(show.ShowOne): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) volume_client = self.app.client_manager.volume volume = utils.find_resource(volume_client.volumes, parsed_args.volume) # Map 'metadata' column to 'properties' @@ -441,8 +441,8 @@ class UnsetVolume(command.Command): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) volume_client = self.app.client_manager.volume volume = utils.find_resource( volume_client.volumes, parsed_args.volume) diff --git a/openstackclient/volume/v1/volume_type.py b/openstackclient/volume/v1/volume_type.py index d5c617b2..d7765c79 100644 --- a/openstackclient/volume/v1/volume_type.py +++ b/openstackclient/volume/v1/volume_type.py @@ -47,8 +47,8 @@ class CreateVolumeType(show.ShowOne): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) volume_client = self.app.client_manager.volume volume_type = volume_client.volume_types.create(parsed_args.name) volume_type._info.pop('extra_specs') @@ -75,8 +75,8 @@ class DeleteVolumeType(command.Command): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) volume_client = self.app.client_manager.volume volume_type_id = utils.find_resource( volume_client.volume_types, parsed_args.volume_type).id @@ -98,8 +98,8 @@ class ListVolumeType(lister.Lister): help='List additional fields in output') return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) if parsed_args.long: columns = ('ID', 'Name', 'Extra Specs') column_headers = ('ID', 'Name', 'Properties') @@ -135,8 +135,8 @@ class SetVolumeType(command.Command): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) volume_client = self.app.client_manager.volume volume_type = utils.find_resource( volume_client.volume_types, parsed_args.volume_type) @@ -170,8 +170,8 @@ class UnsetVolumeType(command.Command): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) volume_client = self.app.client_manager.volume volume_type = utils.find_resource( volume_client.volume_types, diff --git a/openstackclient/volume/v2/qos_specs.py b/openstackclient/volume/v2/qos_specs.py index ac78ca15..b3a34cac 100644 --- a/openstackclient/volume/v2/qos_specs.py +++ b/openstackclient/volume/v2/qos_specs.py @@ -45,8 +45,8 @@ class AssociateQos(command.Command): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) volume_client = self.app.client_manager.volume qos_spec = utils.find_resource(volume_client.qos_specs, parsed_args.qos_spec) @@ -88,8 +88,8 @@ class CreateQos(show.ShowOne): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) volume_client = self.app.client_manager.volume specs = {} specs.update({'consumer': parsed_args.consumer}) @@ -117,8 +117,8 @@ class DeleteQos(command.Command): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', 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) @@ -153,8 +153,8 @@ class DisassociateQos(command.Command): return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) volume_client = self.app.client_manager.volume qos_spec = utils.find_resource(volume_client.qos_specs, parsed_args.qos_spec) @@ -174,8 +174,8 @@ class ListQos(lister.Lister): log = logging.getLogger(__name__ + '.ListQos') + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) volume_client = self.app.client_manager.volume qos_specs_list = volume_client.qos_specs.list() @@ -218,8 +218,8 @@ class SetQos(command.Command): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) volume_client = self.app.client_manager.volume qos_spec = utils.find_resource(volume_client.qos_specs, parsed_args.qos_spec) @@ -247,8 +247,8 @@ class ShowQos(show.ShowOne): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) volume_client = self.app.client_manager.volume qos_spec = utils.find_resource(volume_client.qos_specs, parsed_args.qos_spec) @@ -287,8 +287,8 @@ class UnsetQos(command.Command): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) volume_client = self.app.client_manager.volume qos_spec = utils.find_resource(volume_client.qos_specs, parsed_args.qos_spec) diff --git a/openstackclient/volume/v2/snapshot.py b/openstackclient/volume/v2/snapshot.py index 4370cdeb..bbc92c48 100644 --- a/openstackclient/volume/v2/snapshot.py +++ b/openstackclient/volume/v2/snapshot.py @@ -186,8 +186,8 @@ class SetSnapshot(command.Command): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) volume_client = self.app.client_manager.volume snapshot = utils.find_resource(volume_client.volume_snapshots, parsed_args.snapshot) @@ -256,8 +256,8 @@ class UnsetSnapshot(command.Command): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) volume_client = self.app.client_manager.volume snapshot = utils.find_resource( volume_client.volume_snapshots, parsed_args.snapshot) diff --git a/openstackclient/volume/v2/volume.py b/openstackclient/volume/v2/volume.py index e50a6f0c..f59567cc 100644 --- a/openstackclient/volume/v2/volume.py +++ b/openstackclient/volume/v2/volume.py @@ -14,13 +14,146 @@ """Volume V2 Volume action implementations""" +import copy import logging from cliff import command +from cliff import lister from cliff import show import six +from openstackclient.common import parseractions from openstackclient.common import utils +from openstackclient.identity import common as identity_common + + +class CreateVolume(show.ShowOne): + """Create new volume""" + + log = logging.getLogger(__name__ + ".CreateVolume") + + def get_parser(self, prog_name): + parser = super(CreateVolume, self).get_parser(prog_name) + parser.add_argument( + "name", + metavar="<name>", + help="New volume name" + ) + parser.add_argument( + "--size", + metavar="<size>", + type=int, + required=True, + help="New volume size in GB" + ) + parser.add_argument( + "--snapshot", + metavar="<snapshot>", + help="Use <snapshot> as source of new volume (name or ID)" + ) + parser.add_argument( + "--description", + metavar="<description>", + help="New volume description" + ) + parser.add_argument( + "--type", + metavar="<volume-type>", + help="Use <volume-type> as the new volume type", + ) + parser.add_argument( + '--user', + metavar='<user>', + help='Specify an alternate user (name or ID)', + ) + parser.add_argument( + '--project', + metavar='<project>', + help='Specify an alternate project (name or ID)', + ) + parser.add_argument( + "--availability-zone", + metavar="<availability-zone>", + help="Create new volume in <availability_zone>" + ) + parser.add_argument( + "--image", + metavar="<image>", + help="Use <image> as source of new volume (name or ID)" + ) + parser.add_argument( + "--source", + metavar="<volume>", + help="Volume to clone (name or ID)" + ) + parser.add_argument( + "--property", + metavar="<key=value>", + action=parseractions.KeyValueAction, + help="Set a property to this volume " + "(repeat option to set multiple properties)" + ) + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action: (%s)", parsed_args) + + identity_client = self.app.client_manager.identity + volume_client = self.app.client_manager.volume + image_client = self.app.client_manager.image + + source_volume = None + if parsed_args.source: + source_volume = utils.find_resource( + volume_client.volumes, + parsed_args.source).id + + image = None + if parsed_args.image: + image = utils.find_resource( + image_client.images, + parsed_args.image).id + + snapshot = None + if parsed_args.snapshot: + snapshot = utils.find_resource( + volume_client.snapshots, + parsed_args.snapshot).id + + project = None + if parsed_args.project: + project = utils.find_resource( + identity_client.projects, + parsed_args.project).id + + user = None + if parsed_args.user: + user = utils.find_resource( + identity_client.users, + parsed_args.user).id + + volume = volume_client.volumes.create( + size=parsed_args.size, + snapshot_id=snapshot, + name=parsed_args.name, + description=parsed_args.description, + volume_type=parsed_args.type, + user_id=user, + project_id=project, + availability_zone=parsed_args.availability_zone, + metadata=parsed_args.property, + imageRef=image, + source_volid=source_volume + ) + # Remove key links from being displayed + volume._info.update( + { + 'properties': utils.format_dict(volume._info.pop('metadata')), + 'type': volume._info.pop('volume_type') + } + ) + volume._info.pop("links", None) + return zip(*sorted(six.iteritems(volume._info))) class DeleteVolume(command.Command): @@ -42,7 +175,7 @@ class DeleteVolume(command.Command): action="store_true", default=False, help="Attempt forced removal of volume(s), regardless of state " - "(defaults to False" + "(defaults to False)" ) return parser @@ -59,6 +192,211 @@ class DeleteVolume(command.Command): return +class ListVolume(lister.Lister): + """List volumes""" + + log = logging.getLogger(__name__ + '.ListVolume') + + def get_parser(self, prog_name): + parser = super(ListVolume, self).get_parser(prog_name) + parser.add_argument( + '--project', + metavar='<project-id>', + help='Filter results by project (name or ID) (admin only)' + ) + identity_common.add_project_domain_option_to_parser(parser) + parser.add_argument( + '--user', + metavar='<user-id>', + help='Filter results by user (name or ID) (admin only)' + ) + identity_common.add_user_domain_option_to_parser(parser) + parser.add_argument( + '--name', + metavar='<name>', + help='Filter results by volume name', + ) + parser.add_argument( + '--status', + metavar='<status>', + help='Filter results by status', + ) + parser.add_argument( + '--all-projects', + action='store_true', + default=False, + help='Include all projects (admin only)', + ) + parser.add_argument( + '--long', + action='store_true', + default=False, + help='List additional fields in output', + ) + return parser + + @utils.log_method(log) + def take_action(self, parsed_args): + + volume_client = self.app.client_manager.volume + compute_client = self.app.client_manager.compute + identity_client = self.app.client_manager.identity + + def _format_attach(attachments): + """Return a formatted string of a volume's attached instances + + :param volume: a volume.attachments field + :rtype: a string of formatted instances + """ + + msg = '' + for attachment in attachments: + server = attachment['server_id'] + if server in server_cache: + server = server_cache[server].name + device = attachment['device'] + msg += 'Attached to %s on %s ' % (server, device) + return msg + + if parsed_args.long: + columns = [ + 'ID', + 'Name', + 'Status', + 'Size', + 'Volume Type', + 'Bootable', + 'Attachments', + 'Metadata', + ] + column_headers = copy.deepcopy(columns) + column_headers[1] = 'Display Name' + column_headers[4] = 'Type' + column_headers[6] = 'Attached to' + column_headers[7] = 'Properties' + else: + columns = [ + 'ID', + 'Name', + 'Status', + 'Size', + 'Attachments', + ] + column_headers = copy.deepcopy(columns) + column_headers[1] = 'Display Name' + column_headers[4] = 'Attached to' + + # Cache the server list + server_cache = {} + try: + for s in compute_client.servers.list(): + server_cache[s.id] = s + except Exception: + # Just forget it if there's any trouble + pass + + project_id = None + if parsed_args.project: + project_id = identity_common.find_project( + identity_client, + parsed_args.project, + parsed_args.project_domain) + + user_id = None + if parsed_args.user: + user_id = identity_common.find_project(identity_client, + parsed_args.user, + parsed_args.user_domain) + + search_opts = { + 'all_tenants': parsed_args.all_projects, + 'project_id': project_id, + 'user_id': user_id, + 'display_name': parsed_args.name, + 'status': parsed_args.status, + } + + data = volume_client.volumes.list(search_opts=search_opts) + + return (column_headers, + (utils.get_item_properties( + s, columns, + formatters={'Metadata': utils.format_dict, + 'Attachments': _format_attach}, + ) for s in data)) + + +class SetVolume(show.ShowOne): + """Set volume properties""" + + log = logging.getLogger(__name__ + '.SetVolume') + + def get_parser(self, prog_name): + parser = super(SetVolume, self).get_parser(prog_name) + parser.add_argument( + 'volume', + metavar='<volume>', + help='Volume to change (name or ID)', + ) + parser.add_argument( + '--name', + metavar='<name>', + help='New volume name', + ) + parser.add_argument( + '--description', + metavar='<description>', + help='New volume description', + ) + parser.add_argument( + '--size', + metavar='<size>', + type=int, + help='Extend volume size in GB', + ) + parser.add_argument( + '--property', + metavar='<key=value>', + action=parseractions.KeyValueAction, + help='Property to add or modify for this volume ' + '(repeat option to set multiple properties)', + ) + return parser + + @utils.log_method(log) + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + volume = utils.find_resource(volume_client.volumes, parsed_args.volume) + + if parsed_args.size: + if volume.status != 'available': + self.app.log.error("Volume is in %s state, it must be " + "available before size can be extended" % + volume.status) + return + if parsed_args.size <= volume.size: + self.app.log.error("New size must be greater than %s GB" % + volume.size) + return + volume_client.volumes.extend(volume.id, parsed_args.size) + + if parsed_args.property: + volume_client.volumes.set_metadata(volume.id, parsed_args.property) + + kwargs = {} + if parsed_args.name: + kwargs['display_name'] = parsed_args.name + if parsed_args.description: + kwargs['display_description'] = parsed_args.description + if kwargs: + volume_client.volumes.update(volume.id, **kwargs) + + if not kwargs and not parsed_args.property and not parsed_args.size: + self.app.log.error("No changes requested\n") + + return + + class ShowVolume(show.ShowOne): """Display volume details""" @@ -73,11 +411,45 @@ class ShowVolume(show.ShowOne): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) volume_client = self.app.client_manager.volume volume = utils.find_resource(volume_client.volumes, parsed_args.volume) # Remove key links from being displayed volume._info.pop("links", None) return zip(*sorted(six.iteritems(volume._info))) + + +class UnsetVolume(command.Command): + """Unset volume properties""" + + log = logging.getLogger(__name__ + '.UnsetVolume') + + def get_parser(self, prog_name): + parser = super(UnsetVolume, self).get_parser(prog_name) + parser.add_argument( + 'volume', + metavar='<volume>', + help='Volume to modify (name or ID)', + ) + parser.add_argument( + '--property', + metavar='<key>', + required=True, + action='append', + default=[], + help='Property to remove from volume ' + '(repeat option to remove multiple properties)', + ) + return parser + + @utils.log_method(log) + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + volume = utils.find_resource( + volume_client.volumes, parsed_args.volume) + + volume_client.volumes.delete_metadata( + volume.id, parsed_args.property) + return diff --git a/openstackclient/volume/v2/volume_type.py b/openstackclient/volume/v2/volume_type.py index 7f9a1c4b..8cca86f9 100644 --- a/openstackclient/volume/v2/volume_type.py +++ b/openstackclient/volume/v2/volume_type.py @@ -66,8 +66,8 @@ class CreateVolumeType(show.ShowOne): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) volume_client = self.app.client_manager.volume @@ -127,8 +127,8 @@ class ListVolumeType(lister.Lister): help='List additional fields in output') return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) if parsed_args.long: columns = ['ID', 'Name', 'Description', 'Extra Specs'] column_headers = ['ID', 'Name', 'Description', 'Properties'] @@ -143,6 +143,67 @@ class ListVolumeType(lister.Lister): ) for s in data)) +class SetVolumeType(command.Command): + """Set volume type properties""" + + log = logging.getLogger(__name__ + '.SetVolumeType') + + def get_parser(self, prog_name): + parser = super(SetVolumeType, self).get_parser(prog_name) + parser.add_argument( + 'volume_type', + metavar='<volume-type>', + help='Volume type to modify (name or ID)', + ) + parser.add_argument( + '--name', + metavar='<name>', + help='Set volume type name', + ) + parser.add_argument( + '--description', + metavar='<name>', + help='Set volume type description', + ) + parser.add_argument( + '--property', + metavar='<key=value>', + action=parseractions.KeyValueAction, + help='Property to add or modify for this volume type ' + '(repeat option to set multiple properties)', + ) + return parser + + @utils.log_method(log) + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + volume_type = utils.find_resource( + volume_client.volume_types, parsed_args.volume_type) + + if (not parsed_args.name + and not parsed_args.description + and not parsed_args.property): + self.app.log.error("No changes requested\n") + return + + kwargs = {} + if parsed_args.name: + kwargs['name'] = parsed_args.name + if parsed_args.description: + kwargs['description'] = parsed_args.description + + if kwargs: + volume_client.volume_types.update( + volume_type.id, + **kwargs + ) + + if parsed_args.property: + volume_type.set_keys(parsed_args.property) + + return + + class ShowVolumeType(show.ShowOne): """Display volume type details""" @@ -165,3 +226,36 @@ class ShowVolumeType(show.ShowOne): properties = utils.format_dict(volume_type._info.pop('extra_specs')) volume_type._info.update({'properties': properties}) return zip(*sorted(six.iteritems(volume_type._info))) + + +class UnsetVolumeType(command.Command): + """Unset volume type properties""" + + log = logging.getLogger(__name__ + '.UnsetVolumeType') + + def get_parser(self, prog_name): + parser = super(UnsetVolumeType, self).get_parser(prog_name) + parser.add_argument( + 'volume_type', + metavar='<volume-type>', + help='Volume type to modify (name or ID)', + ) + parser.add_argument( + '--property', + metavar='<key>', + default=[], + required=True, + help='Property to remove from volume type ' + '(repeat option to remove multiple properties)', + ) + return parser + + @utils.log_method(log) + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + volume_type = utils.find_resource( + volume_client.volume_types, + parsed_args.volume_type, + ) + volume_type.unset_keys(parsed_args.property) + return |
