summaryrefslogtreecommitdiff
path: root/openstackclient
diff options
context:
space:
mode:
Diffstat (limited to 'openstackclient')
-rw-r--r--openstackclient/api/auth.py2
-rw-r--r--openstackclient/api/object_store_v1.py245
-rw-r--r--openstackclient/common/clientmanager.py8
-rw-r--r--openstackclient/common/configuration.py4
-rw-r--r--openstackclient/common/limits.py4
-rw-r--r--openstackclient/common/module.py6
-rw-r--r--openstackclient/common/quota.py15
-rw-r--r--openstackclient/common/utils.py57
-rw-r--r--openstackclient/compute/client.py77
-rw-r--r--openstackclient/compute/v2/aggregate.py6
-rw-r--r--openstackclient/compute/v2/availability_zone.py2
-rw-r--r--openstackclient/compute/v2/console.py4
-rw-r--r--openstackclient/compute/v2/flavor.py13
-rw-r--r--openstackclient/compute/v2/floatingip.py6
-rw-r--r--openstackclient/compute/v2/floatingippool.py2
-rw-r--r--openstackclient/compute/v2/keypair.py6
-rw-r--r--openstackclient/compute/v2/security_group.py19
-rw-r--r--openstackclient/compute/v2/server.py87
-rw-r--r--openstackclient/compute/v2/service.py1
-rw-r--r--openstackclient/identity/client.py8
-rw-r--r--openstackclient/identity/v2_0/catalog.py4
-rw-r--r--openstackclient/identity/v2_0/ec2creds.py8
-rw-r--r--openstackclient/identity/v2_0/endpoint.py8
-rw-r--r--openstackclient/identity/v2_0/project.py10
-rw-r--r--openstackclient/identity/v2_0/role.py14
-rw-r--r--openstackclient/identity/v2_0/service.py8
-rw-r--r--openstackclient/identity/v2_0/token.py3
-rw-r--r--openstackclient/identity/v2_0/user.py10
-rw-r--r--openstackclient/identity/v3/catalog.py4
-rw-r--r--openstackclient/identity/v3/consumer.py10
-rw-r--r--openstackclient/identity/v3/credential.py10
-rw-r--r--openstackclient/identity/v3/domain.py10
-rw-r--r--openstackclient/identity/v3/ec2creds.py8
-rw-r--r--openstackclient/identity/v3/endpoint.py10
-rw-r--r--openstackclient/identity/v3/federation_protocol.py4
-rw-r--r--openstackclient/identity/v3/group.py16
-rw-r--r--openstackclient/identity/v3/identity_provider.py10
-rw-r--r--openstackclient/identity/v3/policy.py10
-rw-r--r--openstackclient/identity/v3/project.py10
-rw-r--r--openstackclient/identity/v3/region.py32
-rw-r--r--openstackclient/identity/v3/role.py23
-rw-r--r--openstackclient/identity/v3/role_assignment.py20
-rw-r--r--openstackclient/identity/v3/service.py10
-rw-r--r--openstackclient/identity/v3/service_provider.py10
-rw-r--r--openstackclient/identity/v3/token.py2
-rw-r--r--openstackclient/identity/v3/unscoped_saml.py4
-rw-r--r--openstackclient/identity/v3/user.py22
-rw-r--r--openstackclient/image/client.py8
-rw-r--r--openstackclient/image/v1/image.py36
-rw-r--r--openstackclient/image/v2/image.py463
-rw-r--r--openstackclient/network/client.py8
-rw-r--r--openstackclient/object/client.py8
-rw-r--r--openstackclient/object/v1/account.py85
-rw-r--r--openstackclient/object/v1/container.py72
-rw-r--r--openstackclient/object/v1/object.py84
-rw-r--r--openstackclient/shell.py42
-rw-r--r--openstackclient/tests/api/fakes.py2
-rw-r--r--openstackclient/tests/api/test_image_v1.py2
-rw-r--r--openstackclient/tests/api/test_image_v2.py2
-rw-r--r--openstackclient/tests/api/test_network_v2.py2
-rw-r--r--openstackclient/tests/api/test_object_store_v1.py9
-rw-r--r--openstackclient/tests/common/test_quota.py134
-rw-r--r--openstackclient/tests/common/test_utils.py54
-rw-r--r--openstackclient/tests/compute/v2/fakes.py18
-rw-r--r--openstackclient/tests/compute/v2/test_flavor.py20
-rw-r--r--openstackclient/tests/compute/v2/test_security_group_rule.py338
-rw-r--r--openstackclient/tests/compute/v2/test_server.py6
-rw-r--r--openstackclient/tests/fakes.py2
-rw-r--r--openstackclient/tests/identity/v3/fakes.py16
-rw-r--r--openstackclient/tests/identity/v3/test_region.py79
-rw-r--r--openstackclient/tests/identity/v3/test_role.py54
-rw-r--r--openstackclient/tests/identity/v3/test_role_assignment.py142
-rw-r--r--openstackclient/tests/image/v1/test_image.py12
-rw-r--r--openstackclient/tests/image/v2/fakes.py15
-rw-r--r--openstackclient/tests/image/v2/test_image.py468
-rw-r--r--openstackclient/tests/object/v1/fakes.py2
-rw-r--r--openstackclient/tests/object/v1/test_container_all.py1
-rw-r--r--openstackclient/tests/test_shell.py26
-rw-r--r--openstackclient/tests/volume/v1/test_volume.py182
-rw-r--r--openstackclient/tests/volume/v2/fakes.py28
-rw-r--r--openstackclient/tests/volume/v2/test_type.py130
-rw-r--r--openstackclient/tests/volume/v2/test_volume.py831
-rw-r--r--openstackclient/volume/client.py8
-rw-r--r--openstackclient/volume/v1/backup.py10
-rw-r--r--openstackclient/volume/v1/qos_specs.py16
-rw-r--r--openstackclient/volume/v1/snapshot.py12
-rw-r--r--openstackclient/volume/v1/volume.py22
-rw-r--r--openstackclient/volume/v1/volume_type.py10
-rw-r--r--openstackclient/volume/v2/qos_specs.py16
-rw-r--r--openstackclient/volume/v2/snapshot.py4
-rw-r--r--openstackclient/volume/v2/volume.py376
-rw-r--r--openstackclient/volume/v2/volume_type.py98
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