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.py253
-rw-r--r--openstackclient/common/clientmanager.py4
-rw-r--r--openstackclient/common/configuration.py4
-rw-r--r--openstackclient/common/limits.py4
-rw-r--r--openstackclient/common/logs.py170
-rw-r--r--openstackclient/common/module.py6
-rw-r--r--openstackclient/common/parseractions.py15
-rw-r--r--openstackclient/common/quota.py24
-rw-r--r--openstackclient/common/utils.py69
-rw-r--r--openstackclient/compute/client.py69
-rw-r--r--openstackclient/compute/v2/aggregate.py6
-rw-r--r--openstackclient/compute/v2/availability_zone.py2
-rw-r--r--openstackclient/compute/v2/console.py6
-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.py260
-rw-r--r--openstackclient/compute/v2/service.py23
-rw-r--r--openstackclient/compute/v2/usage.py33
-rw-r--r--openstackclient/identity/client.py2
-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.py41
-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.py33
-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.py19
-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/v1/image.py36
-rw-r--r--openstackclient/image/v2/image.py469
-rw-r--r--openstackclient/network/client.py4
-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.py122
-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_logs.py202
-rw-r--r--openstackclient/tests/common/test_parseractions.py55
-rw-r--r--openstackclient/tests/common/test_quota.py65
-rw-r--r--openstackclient/tests/common/test_utils.py94
-rw-r--r--openstackclient/tests/compute/v2/fakes.py14
-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.py162
-rw-r--r--openstackclient/tests/compute/v2/test_service.py54
-rw-r--r--openstackclient/tests/fakes.py107
-rw-r--r--openstackclient/tests/identity/v3/fakes.py7
-rw-r--r--openstackclient/tests/identity/v3/test_credential.py112
-rw-r--r--openstackclient/tests/identity/v3/test_identity_provider.py62
-rw-r--r--openstackclient/tests/identity/v3/test_region.py79
-rw-r--r--openstackclient/tests/identity/v3/test_role.py38
-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.py474
-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.py2
-rw-r--r--openstackclient/tests/volume/v1/test_volume.py182
-rw-r--r--openstackclient/tests/volume/v2/fakes.py11
-rw-r--r--openstackclient/tests/volume/v2/test_volume.py174
-rw-r--r--openstackclient/volume/client.py2
-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.py24
-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.py64
-rw-r--r--openstackclient/volume/v2/volume_type.py8
95 files changed, 3882 insertions, 773 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..ab75a78c 100644
--- a/openstackclient/api/object_store_v1.py
+++ b/openstackclient/api/object_store_v1.py
@@ -14,8 +14,11 @@
"""Object Store v1 API Library"""
import io
+import logging
import os
+
import six
+from six.moves import urllib
try:
from urllib.parse import urlparse # noqa
@@ -23,6 +26,7 @@ except ImportError:
from urlparse import urlparse # noqa
from openstackclient.api import api
+from openstackclient.common import utils
class APIv1(api.BaseAPI):
@@ -42,11 +46,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 +66,7 @@ class APIv1(api.BaseAPI):
"""
if container:
- self.delete(container)
+ self.delete(urllib.parse.quote(container))
def container_list(
self,
@@ -140,6 +142,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 +171,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 +236,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 +270,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 +348,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 +371,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 +383,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 +445,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 +469,107 @@ 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
+
+ log = logging.getLogger(__name__ + '._set_properties')
+
+ headers = {}
+ for k, v in properties.iteritems():
+ if not utils.is_ascii(k) or not utils.is_ascii(v):
+ log.error('Cannot set property %s to non-ascii value', k)
+ continue
+
+ 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 55c6fe53..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
@@ -167,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/logs.py b/openstackclient/common/logs.py
new file mode 100644
index 00000000..6d1aec13
--- /dev/null
+++ b/openstackclient/common/logs.py
@@ -0,0 +1,170 @@
+# 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.
+#
+
+"""Application logging"""
+
+import logging
+import sys
+import warnings
+
+
+def log_level_from_options(options):
+ # if --debug, --quiet or --verbose is not specified,
+ # the default logging level is warning
+ log_level = logging.WARNING
+ if options.verbose_level == 0:
+ # --quiet
+ log_level = logging.ERROR
+ elif options.verbose_level == 2:
+ # One --verbose
+ log_level = logging.INFO
+ elif options.verbose_level >= 3:
+ # Two or more --verbose
+ log_level = logging.DEBUG
+ return log_level
+
+
+def log_level_from_config(config):
+ # Check the command line option
+ verbose_level = config.get('verbose_level')
+ if config.get('debug', False):
+ verbose_level = 3
+ if verbose_level == 0:
+ verbose_level = 'error'
+ elif verbose_level == 1:
+ # If a command line option has not been specified, check the
+ # configuration file
+ verbose_level = config.get('log_level', 'warning')
+ elif verbose_level == 2:
+ verbose_level = 'info'
+ else:
+ verbose_level = 'debug'
+
+ log_level = {
+ 'critical': logging.CRITICAL,
+ 'error': logging.ERROR,
+ 'warning': logging.WARNING,
+ 'info': logging.INFO,
+ 'debug': logging.DEBUG,
+ }.get(verbose_level, logging.WARNING)
+ return log_level
+
+
+def set_warning_filter(log_level):
+ if log_level == logging.ERROR:
+ warnings.simplefilter("ignore")
+ elif log_level == logging.WARNING:
+ warnings.simplefilter("ignore")
+ elif log_level == logging.INFO:
+ warnings.simplefilter("once")
+
+
+class _FileFormatter(logging.Formatter):
+ """Customize the logging format for logging handler"""
+ _LOG_MESSAGE_BEGIN = (
+ '%(asctime)s.%(msecs)03d %(process)d %(levelname)s %(name)s ')
+ _LOG_MESSAGE_CONTEXT = '[%(cloud)s %(username)s %(project)s] '
+ _LOG_MESSAGE_END = '%(message)s'
+ _LOG_DATE_FORMAT = '%Y-%m-%d %H:%M:%S'
+
+ def __init__(self, options=None, config=None, **kwargs):
+ context = {}
+ if options:
+ context = {
+ 'cloud': getattr(options, 'cloud', ''),
+ 'project': getattr(options, 'os_project_name', ''),
+ 'username': getattr(options, 'username', ''),
+ }
+ elif config:
+ context = {
+ 'cloud': config.config.get('cloud', ''),
+ 'project': config.auth.get('project_name', ''),
+ 'username': config.auth.get('username', ''),
+ }
+ if context:
+ self.fmt = (self._LOG_MESSAGE_BEGIN +
+ (self._LOG_MESSAGE_CONTEXT % context) +
+ self._LOG_MESSAGE_END)
+ else:
+ self.fmt = self._LOG_MESSAGE_BEGIN + self._LOG_MESSAGE_END
+ logging.Formatter.__init__(self, self.fmt, self._LOG_DATE_FORMAT)
+
+
+class LogConfigurator(object):
+
+ _CONSOLE_MESSAGE_FORMAT = '%(message)s'
+
+ def __init__(self, options):
+ self.root_logger = logging.getLogger('')
+ self.root_logger.setLevel(logging.DEBUG)
+
+ # Force verbose_level 3 on --debug
+ self.dump_trace = False
+ if options.debug:
+ options.verbose_level = 3
+ self.dump_trace = True
+
+ # Always send higher-level messages to the console via stderr
+ self.console_logger = logging.StreamHandler(sys.stderr)
+ log_level = log_level_from_options(options)
+ self.console_logger.setLevel(log_level)
+ formatter = logging.Formatter(self._CONSOLE_MESSAGE_FORMAT)
+ self.console_logger.setFormatter(formatter)
+ self.root_logger.addHandler(self.console_logger)
+
+ # Set the warning filter now
+ set_warning_filter(log_level)
+
+ # Set up logging to a file
+ self.file_logger = None
+ log_file = options.log_file
+ if log_file:
+ self.file_logger = logging.FileHandler(filename=log_file)
+ self.file_logger.setFormatter(_FileFormatter(options=options))
+ self.file_logger.setLevel(log_level)
+ self.root_logger.addHandler(self.file_logger)
+
+ # Requests logs some stuff at INFO that we don't want
+ # unless we have DEBUG
+ requests_log = logging.getLogger("requests")
+
+ # Other modules we don't want DEBUG output for
+ cliff_log = logging.getLogger('cliff')
+ stevedore_log = logging.getLogger('stevedore')
+ iso8601_log = logging.getLogger("iso8601")
+
+ if options.debug:
+ # --debug forces traceback
+ requests_log.setLevel(logging.DEBUG)
+ else:
+ requests_log.setLevel(logging.ERROR)
+
+ cliff_log.setLevel(logging.ERROR)
+ stevedore_log.setLevel(logging.ERROR)
+ iso8601_log.setLevel(logging.ERROR)
+
+ def configure(self, cloud_config):
+ log_level = log_level_from_config(cloud_config.config)
+ set_warning_filter(log_level)
+ self.dump_trace = cloud_config.config.get('debug', self.dump_trace)
+ self.console_logger.setLevel(log_level)
+
+ log_file = cloud_config.config.get('log_file', None)
+ if log_file:
+ if not self.file_logger:
+ self.file_logger = logging.FileHandler(filename=log_file)
+ formatter = _FileFormatter(cloud_config=cloud_config)
+ self.file_logger.setFormatter(formatter)
+ self.file_logger.setFormatter(_FileFormatter(config=cloud_config))
+ self.file_logger.setLevel(log_level)
+ self.root_logger.addHandler(self.file_logger)
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/parseractions.py b/openstackclient/common/parseractions.py
index 8f6008e2..fd90369a 100644
--- a/openstackclient/common/parseractions.py
+++ b/openstackclient/common/parseractions.py
@@ -65,3 +65,18 @@ class RangeAction(argparse.Action):
# Too many values
msg = "Invalid range, too many values"
raise argparse.ArgumentError(self, msg)
+
+
+class NonNegativeAction(argparse.Action):
+ """A custom action to check whether the value is non-negative or not
+
+ Ensures the value is >= 0.
+ """
+
+ def __call__(self, parser, namespace, values, option_string=None):
+ try:
+ assert(int(values) >= 0)
+ setattr(namespace, self.dest, values)
+ except Exception:
+ msg = "%s expected a non-negative integer" % (str(option_string))
+ raise argparse.ArgumentTypeError(self, msg)
diff --git a/openstackclient/common/quota.py b/openstackclient/common/quota.py
index e79fd7ed..b5d4eb87 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,9 +91,10 @@ class SetQuota(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
compute_client = self.app.client_manager.compute
volume_client = self.app.client_manager.volume
@@ -103,8 +106,7 @@ class SetQuota(command.Command):
volume_kwargs = {}
for k, v in VOLUME_QUOTAS.items():
- # TODO(jiaxi): Should use k or v needs discuss
- 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
@@ -114,23 +116,29 @@ class SetQuota(command.Command):
sys.stderr.write("No quotas updated")
return
+ if parsed_args.project:
+ project = utils.find_resource(
+ identity_client.projects,
+ parsed_args.project,
+ )
+
if parsed_args.quota_class:
if compute_kwargs:
compute_client.quota_classes.update(
- parsed_args.project,
+ project.id,
**compute_kwargs)
if volume_kwargs:
volume_client.quota_classes.update(
- parsed_args.project,
+ project.id,
**volume_kwargs)
else:
if compute_kwargs:
compute_client.quotas.update(
- parsed_args.project,
+ project.id,
**compute_kwargs)
if volume_kwargs:
volume_client.quotas.update(
- parsed_args.project,
+ project.id,
**volume_kwargs)
@@ -188,8 +196,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
diff --git a/openstackclient/common/utils.py b/openstackclient/common/utils.py
index 2f8419f4..91b1e05a 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,14 +121,30 @@ 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):
"""Return a formatted string of key value pairs
:param data: a dict
- :param format: optional formatting hints
:rtype: a string formatted to key='value'
"""
@@ -255,6 +297,7 @@ def wait_for_status(status_f,
res_id,
status_field='status',
success_status=['active'],
+ error_status=['error'],
sleep_time=5,
callback=None):
"""Wait for status change on a resource during a long-running operation
@@ -273,7 +316,7 @@ def wait_for_status(status_f,
if status in success_status:
retval = True
break
- elif status == 'error':
+ elif status in error_status:
retval = False
break
if callback:
@@ -376,3 +419,11 @@ def build_kwargs_dict(arg_name, value):
if value:
kwargs[arg_name] = value
return kwargs
+
+
+def is_ascii(string):
+ try:
+ string.decode('ascii')
+ return True
+ except UnicodeDecodeError:
+ return False
diff --git a/openstackclient/compute/client.py b/openstackclient/compute/client.py
index 9dda32d6..23a4deca 100644
--- a/openstackclient/compute/client.py
+++ b/openstackclient/compute/client.py
@@ -15,6 +15,7 @@
import logging
+from openstackclient.common import exceptions
from openstackclient.common import utils
LOG = logging.getLogger(__name__)
@@ -26,32 +27,34 @@ 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,
@@ -73,3 +76,45 @@ def build_option_parser(parser):
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..aafa5d44 100644
--- a/openstackclient/compute/v2/console.py
+++ b/openstackclient/compute/v2/console.py
@@ -22,6 +22,7 @@ import sys
from cliff import command
from cliff import show
+from openstackclient.common import parseractions
from openstackclient.common import utils
@@ -42,13 +43,14 @@ class ShowConsoleLog(command.Command):
metavar='<num-lines>',
type=int,
default=None,
+ action=parseractions.NonNegativeAction,
help='Number of lines to display from the end of the log '
'(default=all)',
)
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 +105,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..aa4569c3 100644
--- a/openstackclient/compute/v2/server.py
+++ b/openstackclient/compute/v2/server.py
@@ -36,13 +36,14 @@ 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
def _format_servers_list_networks(networks):
"""Return a formatted string of a server's networks
- :param server: a Server.networks field
+ :param networks: a Server.networks field
:rtype: a string of formatted network addresses
"""
output = []
@@ -55,6 +56,29 @@ def _format_servers_list_networks(networks):
return '; '.join(output)
+def _format_servers_list_power_state(state):
+ """Return a formatted string of a server's power state
+
+ :param state: the power state number of a server
+ :rtype: a string mapped to the power state number
+ """
+ power_states = [
+ 'NOSTATE', # 0x00
+ 'Running', # 0x01
+ '', # 0x02
+ 'Paused', # 0x03
+ 'Shutdown', # 0x04
+ '', # 0x05
+ 'Crashed', # 0x06
+ 'Suspended' # 0x07
+ ]
+
+ try:
+ return power_states[state]
+ except Exception:
+ return 'N/A'
+
+
def _get_ip_address(addresses, address_type, ip_address_family):
# Old style addresses
if address_type in addresses:
@@ -107,14 +131,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 +385,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
@@ -418,10 +448,17 @@ class CreateServer(show.ShowOne):
dev_key, dev_vol = dev_map.split('=', 1)
block_volume = None
if dev_vol:
- block_volume = utils.find_resource(
- volume_client.volumes,
- dev_vol,
- ).id
+ vol = dev_vol.split(':', 1)[0]
+ if vol:
+ vol_id = utils.find_resource(
+ volume_client.volumes,
+ vol,
+ ).id
+ block_volume = dev_vol.replace(vol, vol_id)
+ else:
+ msg = _("Volume name or ID must be specified if "
+ "--block-device-mapping is specified")
+ raise exceptions.CommandError(msg)
block_device_mapping.update({dev_key: block_volume})
nics = []
@@ -553,8 +590,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(
@@ -600,7 +637,7 @@ class DeleteServer(command.Command):
def get_parser(self, prog_name):
parser = super(DeleteServer, self).get_parser(prog_name)
parser.add_argument(
- 'servers',
+ 'server',
metavar='<server>',
nargs="+",
help=_('Server(s) to delete (name or ID)'),
@@ -612,10 +649,10 @@ 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:
+ for server in parsed_args.server:
server_obj = utils.find_resource(
compute_client.servers, server)
compute_client.servers.delete(server_obj.id)
@@ -694,6 +731,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 +749,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_user(
+ identity_client,
+ parsed_args.user,
+ parsed_args.user_domain,
+ ).id
+
search_opts = {
'reservation_id': parsed_args.reservation_id,
'ip': parsed_args.ip,
@@ -714,7 +781,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)
@@ -723,6 +792,8 @@ class ListServer(lister.Lister):
'ID',
'Name',
'Status',
+ 'OS-EXT-STS:task_state',
+ 'OS-EXT-STS:power_state',
'Networks',
'OS-EXT-AZ:availability_zone',
'OS-EXT-SRV-ATTR:host',
@@ -732,18 +803,32 @@ class ListServer(lister.Lister):
'ID',
'Name',
'Status',
+ 'Task State',
+ 'Power State',
'Networks',
'Availability Zone',
'Host',
'Properties',
)
mixed_case_fields = [
+ 'OS-EXT-STS:task_state',
+ 'OS-EXT-STS:power_state',
'OS-EXT-AZ:availability_zone',
'OS-EXT-SRV-ATTR:host',
]
else:
- columns = ('ID', 'Name', 'Status', 'Networks')
- column_headers = columns
+ columns = (
+ 'ID',
+ 'Name',
+ 'Status',
+ 'Networks',
+ )
+ column_headers = (
+ 'ID',
+ 'Name',
+ 'Status',
+ 'Networks',
+ )
mixed_case_fields = []
data = compute_client.servers.list(search_opts=search_opts)
return (column_headers,
@@ -751,6 +836,8 @@ class ListServer(lister.Lister):
s, columns,
mixed_case_fields=mixed_case_fields,
formatters={
+ 'OS-EXT-STS:power_state':
+ _format_servers_list_power_state,
'Networks': _format_servers_list_networks,
'Metadata': utils.format_dict,
},
@@ -759,7 +846,7 @@ class ListServer(lister.Lister):
class LockServer(command.Command):
- """Lock a server. A non-admin user will not be able to execute actions"""
+ """Lock server(s). A non-admin user will not be able to execute actions"""
log = logging.getLogger(__name__ + '.LockServer')
@@ -768,18 +855,20 @@ class LockServer(command.Command):
parser.add_argument(
'server',
metavar='<server>',
- help=_('Server (name or ID)'),
+ nargs='+',
+ help=_('Server(s) to lock (name or ID)'),
)
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(
- compute_client.servers,
- parsed_args.server,
- ).lock()
+ for server in parsed_args.server:
+ utils.find_resource(
+ compute_client.servers,
+ server,
+ ).lock()
# FIXME(dtroyer): Here is what I want, how with argparse/cliff?
@@ -845,8 +934,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
@@ -876,7 +965,7 @@ class MigrateServer(command.Command):
class PauseServer(command.Command):
- """Pause server"""
+ """Pause server(s)"""
log = logging.getLogger(__name__ + '.PauseServer')
@@ -885,18 +974,19 @@ class PauseServer(command.Command):
parser.add_argument(
'server',
metavar='<server>',
- help=_('Server (name or ID)'),
+ nargs='+',
+ help=_('Server(s) to pause (name or ID)'),
)
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(
- compute_client.servers,
- parsed_args.server,
- ).pause()
+ for server in parsed_args.server:
+ utils.find_resource(
+ compute_client.servers,
+ server
+ ).pause()
class RebootServer(command.Command):
@@ -935,8 +1025,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 +1074,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 +1190,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 +1236,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 +1281,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 +1322,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 +1370,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 +1493,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(
@@ -1444,6 +1534,56 @@ class SshServer(command.Command):
os.system(cmd % (login, ip_address))
+class StartServer(command.Command):
+ """Start server(s)."""
+
+ log = logging.getLogger(__name__ + '.StartServer')
+
+ def get_parser(self, prog_name):
+ parser = super(StartServer, self).get_parser(prog_name)
+ parser.add_argument(
+ 'server',
+ metavar='<server>',
+ nargs="+",
+ help=_('Server(s) to start (name or ID)'),
+ )
+ return parser
+
+ @utils.log_method(log)
+ def take_action(self, parsed_args):
+ compute_client = self.app.client_manager.compute
+ for server in parsed_args.server:
+ utils.find_resource(
+ compute_client.servers,
+ server,
+ ).start()
+
+
+class StopServer(command.Command):
+ """Stop server(s)."""
+
+ log = logging.getLogger(__name__ + '.StopServer')
+
+ def get_parser(self, prog_name):
+ parser = super(StopServer, self).get_parser(prog_name)
+ parser.add_argument(
+ 'server',
+ metavar='<server>',
+ nargs="+",
+ help=_('Server(s) to stop (name or ID)'),
+ )
+ return parser
+
+ @utils.log_method(log)
+ def take_action(self, parsed_args):
+ compute_client = self.app.client_manager.compute
+ for server in parsed_args.server:
+ utils.find_resource(
+ compute_client.servers,
+ server,
+ ).stop()
+
+
class SuspendServer(command.Command):
"""Suspend server"""
@@ -1458,8 +1598,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(
@@ -1469,7 +1609,7 @@ class SuspendServer(command.Command):
class UnlockServer(command.Command):
- """Unlock server"""
+ """Unlock server(s)"""
log = logging.getLogger(__name__ + '.UnlockServer')
@@ -1478,22 +1618,24 @@ class UnlockServer(command.Command):
parser.add_argument(
'server',
metavar='<server>',
- help=_('Server (name or ID)'),
+ nargs='+',
+ help=_('Server(s) to unlock (name or ID)'),
)
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(
- compute_client.servers,
- parsed_args.server,
- ).unlock()
+ for server in parsed_args.server:
+ utils.find_resource(
+ compute_client.servers,
+ server,
+ ).unlock()
class UnpauseServer(command.Command):
- """Unpause server"""
+ """Unpause server(s)"""
log = logging.getLogger(__name__ + '.UnpauseServer')
@@ -1502,18 +1644,20 @@ class UnpauseServer(command.Command):
parser.add_argument(
'server',
metavar='<server>',
- help=_('Server (name or ID)'),
+ nargs='+',
+ help=_('Server(s) to unpause (name or ID)'),
)
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(
- compute_client.servers,
- parsed_args.server,
- ).unpause()
+ for server in parsed_args.server:
+ utils.find_resource(
+ compute_client.servers,
+ server,
+ ).unpause()
class UnrescueServer(command.Command):
@@ -1530,8 +1674,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 +1706,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..c2d51c2a 100644
--- a/openstackclient/compute/v2/service.py
+++ b/openstackclient/compute/v2/service.py
@@ -17,11 +17,33 @@
import logging
+from cliff import command
from cliff import lister
from openstackclient.common import utils
+class DeleteService(command.Command):
+ """Delete service command"""
+
+ log = logging.getLogger(__name__ + ".DeleteService")
+
+ def get_parser(self, prog_name):
+ parser = super(DeleteService, self).get_parser(prog_name)
+ parser.add_argument(
+ "service",
+ metavar="<service>",
+ help="Compute service to delete (ID only)")
+ return parser
+
+ def take_action(self, parsed_args):
+ self.log.debug("take_action(%s)", parsed_args)
+ compute_client = self.app.client_manager.compute
+
+ compute_client.services.delete(parsed_args.service)
+ return
+
+
class ListService(lister.Lister):
"""List service command"""
@@ -43,6 +65,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/compute/v2/usage.py b/openstackclient/compute/v2/usage.py
index 31b90c22..4e7cf100 100644
--- a/openstackclient/compute/v2/usage.py
+++ b/openstackclient/compute/v2/usage.py
@@ -183,25 +183,16 @@ class ShowUsage(show.ShowOne):
))
info = {}
- info['Servers'] = len(usage.server_usages)
- info['RAM MB-Hours'] = float("%.2f" % usage.total_memory_mb_usage)
- info['CPU Hours'] = float("%.2f" % usage.total_vcpus_usage)
- info['Disk GB-Hours'] = float("%.2f" % usage.total_local_gb_usage)
+ info['Servers'] = (
+ len(usage.server_usages)
+ if hasattr(usage, "server_usages") else None)
+ info['RAM MB-Hours'] = (
+ float("%.2f" % usage.total_memory_mb_usage)
+ if hasattr(usage, "total_memory_mb_usage") else None)
+ info['CPU Hours'] = (
+ float("%.2f" % usage.total_vcpus_usage)
+ if hasattr(usage, "total_vcpus_usage") else None)
+ info['Disk GB-Hours'] = (
+ float("%.2f" % usage.total_local_gb_usage)
+ if hasattr(usage, "total_local_gb_usage") else None)
return zip(*sorted(six.iteritems(info)))
-
-
-# This is out of order due to the subclass, will eventually be removed
-
-class ListProjectUsage(ListUsage):
- """List resource usage per project"""
-
- deprecated = True
-
- log = logging.getLogger('DEPRECATED:')
-
- def take_action(self, parsed_args):
- self.log.warning(
- "%s is deprecated, use 'usage list'",
- getattr(self, 'cmd_name', 'this command'),
- )
- return super(ListProjectUsage, self).take_action(parsed_args)
diff --git a/openstackclient/identity/client.py b/openstackclient/identity/client.py
index b8bb33f4..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_API_VERSION = '2'
+DEFAULT_API_VERSION = '3'
API_VERSION_OPTION = 'os_identity_api_version'
API_NAME = 'identity'
API_VERSIONS = {
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..f22092d4 100644
--- a/openstackclient/identity/v3/credential.py
+++ b/openstackclient/identity/v3/credential.py
@@ -17,7 +17,6 @@
import logging
import six
-import sys
from cliff import command
from cliff import lister
@@ -57,8 +56,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 +90,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 +102,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()
@@ -130,17 +129,20 @@ class SetCredential(command.Command):
parser.add_argument(
'--user',
metavar='<user>',
+ required=True,
help='Name or ID of user that owns the credential',
)
parser.add_argument(
'--type',
metavar='<type>',
choices=['ec2', 'cert'],
+ required=True,
help='New credential type',
)
parser.add_argument(
'--data',
metavar='<data>',
+ required=True,
help='New credential data',
)
parser.add_argument(
@@ -150,28 +152,25 @@ 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:
- user_id = utils.find_resource(identity_client.users,
- parsed_args.user).id
- if user_id:
- kwargs['user'] = user_id
- if parsed_args.type:
- kwargs['type'] = parsed_args.type
- if parsed_args.data:
- kwargs['data'] = parsed_args.data
+
+ user_id = utils.find_resource(identity_client.users,
+ parsed_args.user).id
+
if parsed_args.project:
project = utils.find_resource(identity_client.projects,
parsed_args.project).id
- kwargs['project'] = project
+ else:
+ project = None
+
+ identity_client.credentials.update(parsed_args.credential,
+ user=user_id,
+ type=parsed_args.type,
+ blob=parsed_args.data,
+ project=project)
- if not kwargs:
- sys.stdout.write("Credential not updated, no arguments present")
- return
- identity_client.credentials.update(parsed_args.credential, **kwargs)
return
@@ -189,8 +188,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..4e086b68 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(
@@ -88,6 +88,8 @@ class CreateIdentityProvider(show.ShowOne):
enabled=parsed_args.enabled)
idp._info.pop('links', None)
+ remote_ids = utils.format_list(idp._info.pop('remote_ids', []))
+ idp._info['remote_ids'] = remote_ids
return zip(*sorted(six.iteritems(idp._info)))
@@ -105,8 +107,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 +120,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()
@@ -142,6 +144,11 @@ class SetIdentityProvider(command.Command):
metavar='<identity-provider>',
help='Identity provider to modify',
)
+ parser.add_argument(
+ '--description',
+ metavar='<description>',
+ help='Set identity provider description',
+ )
identity_remote_id_provider = parser.add_mutually_exclusive_group()
identity_remote_id_provider.add_argument(
'--remote-id',
@@ -169,13 +176,15 @@ 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
- if (not parsed_args.enable and not parsed_args.disable and not
- parsed_args.remote_id and not parsed_args.remote_id_file):
+ if (not parsed_args.enable and not parsed_args.disable and
+ not parsed_args.remote_id and
+ not parsed_args.remote_id_file and
+ not parsed_args.description):
self.log.error('No changes requested')
return (None, None)
@@ -190,6 +199,8 @@ class SetIdentityProvider(command.Command):
# Setup keyword args for the client
kwargs = {}
+ if parsed_args.description:
+ kwargs['description'] = parsed_args.description
if parsed_args.enable:
kwargs['enabled'] = True
if parsed_args.disable:
@@ -218,12 +229,14 @@ 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(
+ idp = utils.find_resource(
identity_client.federation.identity_providers,
parsed_args.identity_provider)
- identity_provider._info.pop('links', None)
- return zip(*sorted(six.iteritems(identity_provider._info)))
+ idp._info.pop('links', None)
+ remote_ids = utils.format_list(idp._info.pop('remote_ids', []))
+ idp._info['remote_ids'] = remote_ids
+ return zip(*sorted(six.iteritems(idp._info)))
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 9243639e..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(
@@ -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/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/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..5552f033 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"""
@@ -69,7 +105,192 @@ class AddProjectToImage(show.ShowOne):
project_id,
)
- return zip(*sorted(six.iteritems(image_member._info)))
+ return zip(*sorted(six.iteritems(image_member)))
+
+
+class CreateImage(show.ShowOne):
+ """Create/upload an image"""
+
+ log = logging.getLogger(__name__ + ".CreateImage")
+ deadopts = ('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?
+ # --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(
+ "--owner",
+ metavar="<owner>",
+ help="Image owner project name or ID",
+ )
+ 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', 'owner')
+ 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):
@@ -229,7 +450,7 @@ class ListImage(lister.Lister):
s,
columns,
formatters={
- 'tags': utils.format_dict,
+ 'tags': utils.format_list,
},
) for s in data)
)
@@ -304,41 +525,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 +554,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 +688,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 +716,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 5f72782b..69ed11fe 100644
--- a/openstackclient/network/client.py
+++ b/openstackclient/network/client.py
@@ -18,10 +18,11 @@ from openstackclient.common import utils
LOG = logging.getLogger(__name__)
-DEFAULT_API_VERSION = '2'
+DEFAULT_API_VERSION = '2.0'
API_VERSION_OPTION = 'os_network_api_version'
API_NAME = "network"
API_VERSIONS = {
+ "2.0": "neutronclient.v2_0.client.Client",
"2": "neutronclient.v2_0.client.Client",
}
# Translate our API version to auth plugin version prefix
@@ -32,6 +33,7 @@ API_VERSION_MAP = {
NETWORK_API_TYPE = 'network'
NETWORK_API_VERSIONS = {
+ '2.0': 'openstackclient.api.network_v2.APIv2',
'2': 'openstackclient.api.network_v2.APIv2',
}
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 a8b5ac4c..5b4939a2 100644
--- a/openstackclient/shell.py
+++ b/openstackclient/shell.py
@@ -20,17 +20,18 @@ import getpass
import logging
import sys
import traceback
-import warnings
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
from openstackclient.common import commandmanager
from openstackclient.common import exceptions as exc
+from openstackclient.common import logs
from openstackclient.common import timing
from openstackclient.common import utils
@@ -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__,
@@ -94,62 +99,19 @@ class OpenStackShell(app.App):
self.verify = True
self.client_manager = None
+ self.command_options = None
def configure_logging(self):
- """Configure logging for the app
-
- Cliff sets some defaults we don't want so re-work it a bit
- """
-
- if self.options.debug:
- # --debug forces verbose_level 3
- # Set this here so cliff.app.configure_logging() can work
- self.options.verbose_level = 3
-
- super(OpenStackShell, self).configure_logging()
- root_logger = logging.getLogger('')
-
- # Set logging to the requested level
- if self.options.verbose_level == 0:
- # --quiet
- root_logger.setLevel(logging.ERROR)
- warnings.simplefilter("ignore")
- elif self.options.verbose_level == 1:
- # This is the default case, no --debug, --verbose or --quiet
- root_logger.setLevel(logging.WARNING)
- warnings.simplefilter("ignore")
- elif self.options.verbose_level == 2:
- # One --verbose
- root_logger.setLevel(logging.INFO)
- warnings.simplefilter("once")
- elif self.options.verbose_level >= 3:
- # Two or more --verbose
- root_logger.setLevel(logging.DEBUG)
-
- # Requests logs some stuff at INFO that we don't want
- # unless we have DEBUG
- requests_log = logging.getLogger("requests")
-
- # Other modules we don't want DEBUG output for
- cliff_log = logging.getLogger('cliff')
- stevedore_log = logging.getLogger('stevedore')
- iso8601_log = logging.getLogger("iso8601")
-
- if self.options.debug:
- # --debug forces traceback
- self.dump_stack_trace = True
- requests_log.setLevel(logging.DEBUG)
- else:
- self.dump_stack_trace = False
- requests_log.setLevel(logging.ERROR)
-
- cliff_log.setLevel(logging.ERROR)
- stevedore_log.setLevel(logging.ERROR)
- iso8601_log.setLevel(logging.ERROR)
+ """Configure logging for the app."""
+ self.log_configurator = logs.LogConfigurator(self.options)
+ self.dump_stack_trace = self.log_configurator.dump_trace
def run(self, argv):
+ ret_val = 1
+ self.command_options = argv
try:
- return super(OpenStackShell, self).run(argv)
+ ret_val = super(OpenStackShell, self).run(argv)
+ return ret_val
except Exception as e:
if not logging.getLogger('').handlers:
logging.basicConfig()
@@ -157,7 +119,11 @@ class OpenStackShell(app.App):
self.log.error(traceback.format_exc(e))
else:
self.log.error('Exception raised: ' + str(e))
- return 1
+
+ return ret_val
+
+ finally:
+ self.log.info("END return value: %s", ret_val)
def build_option_parser(self, description, version):
parser = super(OpenStackShell, self).build_option_parser(
@@ -236,14 +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'
- self.log.debug("options: %s", self.options)
+ auth_type = 'password'
project_id = getattr(self.options, 'project_id', None)
project_name = getattr(self.options, 'project_name', None)
@@ -266,15 +235,26 @@ class OpenStackShell(app.App):
# Ignore the default value of interface. Only if it is set later
# will it be used.
cc = cloud_config.OpenStackConfig(
- override_defaults={'interface': None,
- 'auth_type': auth_type, })
- self.log.debug("defaults: %s", cc.defaults)
+ override_defaults={
+ 'interface': None,
+ 'auth_type': auth_type,
+ },
+ )
+ # TODO(thowe): Change cliff so the default value for debug
+ # can be set to None.
+ if not self.options.debug:
+ self.options.debug = None
self.cloud = cc.get_one_cloud(
cloud=self.options.cloud,
argparse=self.options,
)
- self.log.debug("cloud cfg: %s", self.cloud.config)
+
+ self.log_configurator.configure(self.cloud)
+ self.dump_stack_trace = self.log_configurator.dump_trace
+ self.log.debug("defaults: %s", cc.defaults)
+ 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
@@ -300,15 +280,27 @@ class OpenStackShell(app.App):
for mod in clientmanager.PLUGIN_MODULES:
default_version = getattr(mod, 'DEFAULT_API_VERSION', None)
option = mod.API_VERSION_OPTION.replace('os_', '')
- version_opt = self.cloud.config.get(option, default_version)
+ version_opt = str(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_logs.py b/openstackclient/tests/common/test_logs.py
new file mode 100644
index 00000000..fe054a3b
--- /dev/null
+++ b/openstackclient/tests/common/test_logs.py
@@ -0,0 +1,202 @@
+# 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 logging
+import mock
+
+from openstackclient.common import logs
+from openstackclient.tests import utils
+
+
+class TestContext(utils.TestCase):
+
+ def test_log_level_from_options(self):
+ opts = mock.Mock()
+ opts.verbose_level = 0
+ self.assertEqual(logging.ERROR, logs.log_level_from_options(opts))
+ opts.verbose_level = 1
+ self.assertEqual(logging.WARNING, logs.log_level_from_options(opts))
+ opts.verbose_level = 2
+ self.assertEqual(logging.INFO, logs.log_level_from_options(opts))
+ opts.verbose_level = 3
+ self.assertEqual(logging.DEBUG, logs.log_level_from_options(opts))
+
+ def test_log_level_from_config(self):
+ cfg = {'verbose_level': 0}
+ self.assertEqual(logging.ERROR, logs.log_level_from_config(cfg))
+ cfg = {'verbose_level': 1}
+ self.assertEqual(logging.WARNING, logs.log_level_from_config(cfg))
+ cfg = {'verbose_level': 2}
+ self.assertEqual(logging.INFO, logs.log_level_from_config(cfg))
+ cfg = {'verbose_level': 3}
+ self.assertEqual(logging.DEBUG, logs.log_level_from_config(cfg))
+ cfg = {'verbose_level': 1, 'log_level': 'critical'}
+ self.assertEqual(logging.CRITICAL, logs.log_level_from_config(cfg))
+ cfg = {'verbose_level': 1, 'log_level': 'error'}
+ self.assertEqual(logging.ERROR, logs.log_level_from_config(cfg))
+ cfg = {'verbose_level': 1, 'log_level': 'warning'}
+ self.assertEqual(logging.WARNING, logs.log_level_from_config(cfg))
+ cfg = {'verbose_level': 1, 'log_level': 'info'}
+ self.assertEqual(logging.INFO, logs.log_level_from_config(cfg))
+ cfg = {'verbose_level': 1, 'log_level': 'debug'}
+ self.assertEqual(logging.DEBUG, logs.log_level_from_config(cfg))
+ cfg = {'verbose_level': 1, 'log_level': 'bogus'}
+ self.assertEqual(logging.WARNING, logs.log_level_from_config(cfg))
+ cfg = {'verbose_level': 1, 'log_level': 'info', 'debug': True}
+ self.assertEqual(logging.DEBUG, logs.log_level_from_config(cfg))
+
+ @mock.patch('warnings.simplefilter')
+ def test_set_warning_filter(self, simplefilter):
+ logs.set_warning_filter(logging.ERROR)
+ simplefilter.assert_called_with("ignore")
+ logs.set_warning_filter(logging.WARNING)
+ simplefilter.assert_called_with("ignore")
+ logs.set_warning_filter(logging.INFO)
+ simplefilter.assert_called_with("once")
+
+
+class TestFileFormatter(utils.TestCase):
+ def test_nothing(self):
+ formatter = logs._FileFormatter()
+ self.assertEqual(('%(asctime)s.%(msecs)03d %(process)d %(levelname)s '
+ '%(name)s %(message)s'), formatter.fmt)
+
+ def test_options(self):
+ class Opts(object):
+ cloud = 'cloudy'
+ os_project_name = 'projecty'
+ username = 'usernamey'
+ options = Opts()
+ formatter = logs._FileFormatter(options=options)
+ self.assertEqual(('%(asctime)s.%(msecs)03d %(process)d %(levelname)s '
+ '%(name)s [cloudy usernamey projecty] %(message)s'),
+ formatter.fmt)
+
+ def test_config(self):
+ config = mock.Mock()
+ config.config = {'cloud': 'cloudy'}
+ config.auth = {'project_name': 'projecty', 'username': 'usernamey'}
+ formatter = logs._FileFormatter(config=config)
+ self.assertEqual(('%(asctime)s.%(msecs)03d %(process)d %(levelname)s '
+ '%(name)s [cloudy usernamey projecty] %(message)s'),
+ formatter.fmt)
+
+
+class TestLogConfigurator(utils.TestCase):
+ def setUp(self):
+ super(TestLogConfigurator, self).setUp()
+ self.options = mock.Mock()
+ self.options.verbose_level = 1
+ self.options.log_file = None
+ self.options.debug = False
+ self.root_logger = mock.Mock()
+ self.root_logger.setLevel = mock.Mock()
+ self.root_logger.addHandler = mock.Mock()
+ self.requests_log = mock.Mock()
+ self.requests_log.setLevel = mock.Mock()
+ self.cliff_log = mock.Mock()
+ self.cliff_log.setLevel = mock.Mock()
+ self.stevedore_log = mock.Mock()
+ self.stevedore_log.setLevel = mock.Mock()
+ self.iso8601_log = mock.Mock()
+ self.iso8601_log.setLevel = mock.Mock()
+ self.loggers = [
+ self.root_logger,
+ self.requests_log,
+ self.cliff_log,
+ self.stevedore_log,
+ self.iso8601_log]
+
+ @mock.patch('logging.StreamHandler')
+ @mock.patch('logging.getLogger')
+ @mock.patch('openstackclient.common.logs.set_warning_filter')
+ def test_init(self, warning_filter, getLogger, handle):
+ getLogger.side_effect = self.loggers
+ console_logger = mock.Mock()
+ console_logger.setFormatter = mock.Mock()
+ console_logger.setLevel = mock.Mock()
+ handle.return_value = console_logger
+
+ configurator = logs.LogConfigurator(self.options)
+
+ getLogger.assert_called_with('iso8601') # last call
+ warning_filter.assert_called_with(logging.WARNING)
+ self.root_logger.setLevel.assert_called_with(logging.DEBUG)
+ self.root_logger.addHandler.assert_called_with(console_logger)
+ self.requests_log.setLevel.assert_called_with(logging.ERROR)
+ self.cliff_log.setLevel.assert_called_with(logging.ERROR)
+ self.stevedore_log.setLevel.assert_called_with(logging.ERROR)
+ self.iso8601_log.setLevel.assert_called_with(logging.ERROR)
+ self.assertEqual(False, configurator.dump_trace)
+
+ @mock.patch('logging.getLogger')
+ @mock.patch('openstackclient.common.logs.set_warning_filter')
+ def test_init_no_debug(self, warning_filter, getLogger):
+ getLogger.side_effect = self.loggers
+ self.options.debug = True
+
+ configurator = logs.LogConfigurator(self.options)
+
+ warning_filter.assert_called_with(logging.DEBUG)
+ self.requests_log.setLevel.assert_called_with(logging.DEBUG)
+ self.assertEqual(True, configurator.dump_trace)
+
+ @mock.patch('logging.FileHandler')
+ @mock.patch('logging.getLogger')
+ @mock.patch('openstackclient.common.logs.set_warning_filter')
+ @mock.patch('openstackclient.common.logs._FileFormatter')
+ def test_init_log_file(self, formatter, warning_filter, getLogger, handle):
+ getLogger.side_effect = self.loggers
+ self.options.log_file = '/tmp/log_file'
+ file_logger = mock.Mock()
+ file_logger.setFormatter = mock.Mock()
+ file_logger.setLevel = mock.Mock()
+ handle.return_value = file_logger
+ mock_formatter = mock.Mock()
+ formatter.return_value = mock_formatter
+
+ logs.LogConfigurator(self.options)
+
+ handle.assert_called_with(filename=self.options.log_file)
+ self.root_logger.addHandler.assert_called_with(file_logger)
+ file_logger.setFormatter.assert_called_with(mock_formatter)
+ file_logger.setLevel.assert_called_with(logging.WARNING)
+
+ @mock.patch('logging.FileHandler')
+ @mock.patch('logging.getLogger')
+ @mock.patch('openstackclient.common.logs.set_warning_filter')
+ @mock.patch('openstackclient.common.logs._FileFormatter')
+ def test_configure(self, formatter, warning_filter, getLogger, handle):
+ getLogger.side_effect = self.loggers
+ configurator = logs.LogConfigurator(self.options)
+ cloud_config = mock.Mock()
+ config_log = '/tmp/config_log'
+ cloud_config.config = {
+ 'log_file': config_log,
+ 'verbose_level': 1,
+ 'log_level': 'info'}
+ file_logger = mock.Mock()
+ file_logger.setFormatter = mock.Mock()
+ file_logger.setLevel = mock.Mock()
+ handle.return_value = file_logger
+ mock_formatter = mock.Mock()
+ formatter.return_value = mock_formatter
+
+ configurator.configure(cloud_config)
+
+ warning_filter.assert_called_with(logging.INFO)
+ handle.assert_called_with(filename=config_log)
+ self.root_logger.addHandler.assert_called_with(file_logger)
+ file_logger.setFormatter.assert_called_with(mock_formatter)
+ file_logger.setLevel.assert_called_with(logging.INFO)
+ self.assertEqual(False, configurator.dump_trace)
diff --git a/openstackclient/tests/common/test_parseractions.py b/openstackclient/tests/common/test_parseractions.py
index 8afcb632..b75c4814 100644
--- a/openstackclient/tests/common/test_parseractions.py
+++ b/openstackclient/tests/common/test_parseractions.py
@@ -102,3 +102,58 @@ class TestKeyValueAction(utils.TestCase):
expect = {'green': '100%'}
self.assertDictEqual(expect, actual)
self.assertEqual(None, failhere)
+
+
+class TestNonNegativeAction(utils.TestCase):
+ def test_negative_values(self):
+ parser = argparse.ArgumentParser()
+
+ # Set up our typical usage
+ parser.add_argument(
+ '--foo',
+ metavar='<foo>',
+ type=int,
+ action=parseractions.NonNegativeAction,
+ )
+
+ self.assertRaises(
+ argparse.ArgumentTypeError,
+ parser.parse_args,
+ "--foo -1".split()
+ )
+
+ def test_zero_values(self):
+ parser = argparse.ArgumentParser()
+
+ # Set up our typical usage
+ parser.add_argument(
+ '--foo',
+ metavar='<foo>',
+ type=int,
+ action=parseractions.NonNegativeAction,
+ )
+
+ results = parser.parse_args(
+ '--foo 0'.split()
+ )
+
+ actual = getattr(results, 'foo', None)
+ self.assertEqual(actual, 0)
+
+ def test_positive_values(self):
+ parser = argparse.ArgumentParser()
+
+ # Set up our typical usage
+ parser.add_argument(
+ '--foo',
+ metavar='<foo>',
+ type=int,
+ action=parseractions.NonNegativeAction,
+ )
+
+ results = parser.parse_args(
+ '--foo 1'.split()
+ )
+
+ actual = getattr(results, 'foo', None)
+ self.assertEqual(actual, 1)
diff --git a/openstackclient/tests/common/test_quota.py b/openstackclient/tests/common/test_quota.py
index f0013e48..047ef343 100644
--- a/openstackclient/tests/common/test_quota.py
+++ b/openstackclient/tests/common/test_quota.py
@@ -12,9 +12,12 @@
import copy
+import mock
+
from openstackclient.common import quota
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
class FakeQuotaResource(fakes.FakeResource):
@@ -38,6 +41,13 @@ class TestQuota(compute_fakes.TestComputev2):
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()
+ self.projects_mock = self.app.client_manager.identity.projects
+ self.projects_mock.reset_mock()
class TestQuotaSet(TestQuota):
@@ -57,6 +67,24 @@ class TestQuotaSet(TestQuota):
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.projects_mock.get.return_value = fakes.FakeResource(
+ None,
+ copy.deepcopy(identity_fakes.PROJECT),
+ loaded=True,
+ )
+
self.cmd = quota.SetQuota(self.app, None)
def test_quota_set(self):
@@ -65,14 +93,14 @@ class TestQuotaSet(TestQuota):
'--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,
+ identity_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),
+ ('project', identity_fakes.project_name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -86,4 +114,35 @@ class TestQuotaSet(TestQuota):
'key_pairs': compute_fakes.key_pair_num,
}
- self.quotas_mock.update.assert_called_with('project_test', **kwargs)
+ self.quotas_mock.update.assert_called_with(
+ identity_fakes.project_id,
+ **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),
+ identity_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(
+ identity_fakes.project_id,
+ **kwargs
+ )
diff --git a/openstackclient/tests/common/test_utils.py b/openstackclient/tests/common/test_utils.py
index a25a5ba5..b564ffab 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):
@@ -124,6 +137,46 @@ class TestUtils(test_utils.TestCase):
items, sort_str)
@mock.patch.object(time, 'sleep')
+ def test_wait_for_status_ok(self, mock_sleep):
+ # Tests the normal flow that the resource is status=active
+ resource = mock.MagicMock(status='ACTIVE')
+ status_f = mock.Mock(return_value=resource)
+ res_id = str(uuid.uuid4())
+ self.assertTrue(utils.wait_for_status(status_f, res_id,))
+ self.assertFalse(mock_sleep.called)
+
+ @mock.patch.object(time, 'sleep')
+ def test_wait_for_status_ok__with_overrides(self, mock_sleep):
+ # Tests the normal flow that the resource is status=complete
+ resource = mock.MagicMock(my_status='COMPLETE')
+ status_f = mock.Mock(return_value=resource)
+ res_id = str(uuid.uuid4())
+ self.assertTrue(utils.wait_for_status(status_f, res_id,
+ status_field='my_status',
+ success_status=['complete']))
+ self.assertFalse(mock_sleep.called)
+
+ @mock.patch.object(time, 'sleep')
+ def test_wait_for_status_error(self, mock_sleep):
+ # Tests that we fail if the resource is status=error
+ resource = mock.MagicMock(status='ERROR')
+ status_f = mock.Mock(return_value=resource)
+ res_id = str(uuid.uuid4())
+ self.assertFalse(utils.wait_for_status(status_f, res_id))
+ self.assertFalse(mock_sleep.called)
+
+ @mock.patch.object(time, 'sleep')
+ def test_wait_for_status_error_with_overrides(self, mock_sleep):
+ # Tests that we fail if the resource is my_status=failed
+ resource = mock.MagicMock(my_status='FAILED')
+ status_f = mock.Mock(return_value=resource)
+ res_id = str(uuid.uuid4())
+ self.assertFalse(utils.wait_for_status(status_f, res_id,
+ status_field='my_status',
+ error_status=['failed']))
+ self.assertFalse(mock_sleep.called)
+
+ @mock.patch.object(time, 'sleep')
def test_wait_for_delete_ok(self, mock_sleep):
# Tests the normal flow that the resource is deleted with a 404 coming
# back on the 2nd iteration of the wait loop.
@@ -242,6 +295,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 e798bd40..13db0c01 100644
--- a/openstackclient/tests/compute/v2/fakes.py
+++ b/openstackclient/tests/compute/v2/fakes.py
@@ -20,11 +20,14 @@ from openstackclient.tests.identity.v2_0 import fakes as identity_fakes
from openstackclient.tests.image.v2 import fakes as image_fakes
from openstackclient.tests.network.v2 import fakes as network_fakes
from openstackclient.tests import utils
+from openstackclient.tests.volume.v2 import fakes as volume_fakes
server_id = 'serv1'
server_name = 'waiter'
+service_id = '1'
+
SERVER = {
'id': server_id,
'name': server_name,
@@ -78,6 +81,8 @@ QUOTA = {
QUOTA_columns = tuple(sorted(QUOTA))
QUOTA_data = tuple(QUOTA[x] for x in sorted(QUOTA))
+block_device_mapping = 'vda=' + volume_fakes.volume_name + ':::0'
+
class FakeComputev2Client(object):
def __init__(self, **kwargs):
@@ -85,12 +90,16 @@ class FakeComputev2Client(object):
self.images.resource_class = fakes.FakeResource(None, {})
self.servers = mock.Mock()
self.servers.resource_class = fakes.FakeResource(None, {})
+ self.services = mock.Mock()
+ self.services.resource_class = fakes.FakeResource(None, {})
self.extensions = mock.Mock()
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.volumes = mock.Mock()
+ self.volumes.resource_class = fakes.FakeResource(None, {})
self.auth_token = kwargs['token']
self.management_url = kwargs['endpoint']
@@ -118,3 +127,8 @@ class TestComputev2(utils.TestCommand):
endpoint=fakes.AUTH_URL,
token=fakes.AUTH_TOKEN,
)
+
+ self.app.client_manager.volume = volume_fakes.FakeVolumeClient(
+ endpoint=fakes.AUTH_URL,
+ token=fakes.AUTH_TOKEN,
+ )
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..791a90ab 100644
--- a/openstackclient/tests/compute/v2/test_server.py
+++ b/openstackclient/tests/compute/v2/test_server.py
@@ -24,6 +24,7 @@ from openstackclient.tests.compute.v2 import fakes as compute_fakes
from openstackclient.tests import fakes
from openstackclient.tests.image.v2 import fakes as image_fakes
from openstackclient.tests import utils
+from openstackclient.tests.volume.v2 import fakes as volume_fakes
class TestServer(compute_fakes.TestComputev2):
@@ -47,6 +48,10 @@ class TestServer(compute_fakes.TestComputev2):
self.images_mock = self.app.client_manager.image.images
self.images_mock.reset_mock()
+ # Get a shortcut to the VolumeManager Mock
+ self.volumes_mock = self.app.client_manager.volume.volumes
+ self.volumes_mock.reset_mock()
+
class TestServerCreate(TestServer):
@@ -80,15 +85,22 @@ class TestServerCreate(TestServer):
)
self.flavors_mock.get.return_value = self.flavor
+ self.volume = fakes.FakeResource(
+ None,
+ copy.deepcopy(volume_fakes.VOLUME),
+ loaded=True,
+ )
+ self.volumes_mock.get.return_value = self.volume
+
# Get the command object to test
self.cmd = server.CreateServer(self.app, None)
def test_server_create_no_options(self):
arglist = [
- compute_fakes.server_id,
+ compute_fakes.server_name,
]
verifylist = [
- ('server_name', compute_fakes.server_id),
+ ('server_name', compute_fakes.server_name),
]
try:
# Missing required args should bail here
@@ -100,13 +112,13 @@ class TestServerCreate(TestServer):
arglist = [
'--image', 'image1',
'--flavor', 'flavor1',
- compute_fakes.server_id,
+ compute_fakes.server_name,
]
verifylist = [
('image', 'image1'),
('flavor', 'flavor1'),
('config_drive', False),
- ('server_name', compute_fakes.server_id),
+ ('server_name', compute_fakes.server_name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -131,7 +143,7 @@ class TestServerCreate(TestServer):
)
# ServerManager.create(name, image, flavor, **kwargs)
self.servers_mock.create.assert_called_with(
- compute_fakes.server_id,
+ compute_fakes.server_name,
self.image,
self.flavor,
**kwargs
@@ -154,14 +166,14 @@ class TestServerCreate(TestServer):
'--flavor', 'flavor1',
'--nic', 'net-id=net1',
'--nic', 'port-id=port1',
- compute_fakes.server_id,
+ compute_fakes.server_name,
]
verifylist = [
('image', 'image1'),
('flavor', 'flavor1'),
('nic', ['net-id=net1', 'port-id=port1']),
('config_drive', False),
- ('server_name', compute_fakes.server_id),
+ ('server_name', compute_fakes.server_name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -207,7 +219,7 @@ class TestServerCreate(TestServer):
)
# ServerManager.create(name, image, flavor, **kwargs)
self.servers_mock.create.assert_called_with(
- compute_fakes.server_id,
+ compute_fakes.server_name,
self.image,
self.flavor,
**kwargs
@@ -234,14 +246,14 @@ class TestServerCreate(TestServer):
'--image', 'image1',
'--flavor', 'flavor1',
'--user-data', 'userdata.sh',
- compute_fakes.server_id,
+ compute_fakes.server_name,
]
verifylist = [
('image', 'image1'),
('flavor', 'flavor1'),
('user_data', 'userdata.sh'),
('config_drive', False),
- ('server_name', compute_fakes.server_id),
+ ('server_name', compute_fakes.server_name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -272,7 +284,68 @@ class TestServerCreate(TestServer):
)
# ServerManager.create(name, image, flavor, **kwargs)
self.servers_mock.create.assert_called_with(
+ compute_fakes.server_name,
+ self.image,
+ self.flavor,
+ **kwargs
+ )
+
+ collist = ('addresses', 'flavor', 'id', 'name', 'properties')
+ self.assertEqual(collist, columns)
+ datalist = (
+ '',
+ 'Large ()',
compute_fakes.server_id,
+ compute_fakes.server_name,
+ '',
+ )
+ self.assertEqual(datalist, data)
+
+ def test_server_create_with_block_device_mapping(self):
+ arglist = [
+ '--image', 'image1',
+ '--flavor', compute_fakes.flavor_id,
+ '--block-device-mapping', compute_fakes.block_device_mapping,
+ compute_fakes.server_name,
+ ]
+ verifylist = [
+ ('image', 'image1'),
+ ('flavor', compute_fakes.flavor_id),
+ ('block_device_mapping', [compute_fakes.block_device_mapping]),
+ ('config_drive', False),
+ ('server_name', compute_fakes.server_name),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ # CreateServer.take_action() returns two tuples
+ columns, data = self.cmd.take_action(parsed_args)
+
+ real_volume_mapping = (
+ (compute_fakes.block_device_mapping.split('=', 1)[1]).replace(
+ volume_fakes.volume_name,
+ volume_fakes.volume_id))
+
+ # Set expected values
+ kwargs = dict(
+ meta=None,
+ files={},
+ reservation_id=None,
+ min_count=1,
+ max_count=1,
+ security_groups=[],
+ userdata=None,
+ key_name=None,
+ availability_zone=None,
+ block_device_mapping={
+ 'vda': real_volume_mapping
+ },
+ nics=[],
+ scheduler_hints={},
+ config_drive=None,
+ )
+ # ServerManager.create(name, image, flavor, **kwargs)
+ self.servers_mock.create.assert_called_with(
+ compute_fakes.server_name,
self.image,
self.flavor,
**kwargs
@@ -311,7 +384,7 @@ class TestServerDelete(TestServer):
compute_fakes.server_id,
]
verifylist = [
- ('servers', [compute_fakes.server_id]),
+ ('server', [compute_fakes.server_id]),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -328,7 +401,7 @@ class TestServerDelete(TestServer):
compute_fakes.server_id, '--wait'
]
verifylist = [
- ('servers', [compute_fakes.server_id]),
+ ('server', [compute_fakes.server_id]),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -351,7 +424,7 @@ class TestServerDelete(TestServer):
compute_fakes.server_id, '--wait'
]
verifylist = [
- ('servers', [compute_fakes.server_id]),
+ ('server', [compute_fakes.server_id]),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -410,13 +483,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,18 +515,76 @@ 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)
+class TestServerPause(TestServer):
+
+ def setUp(self):
+ super(TestServerPause, self).setUp()
+
+ # Get the command object to test
+ self.cmd = server.PauseServer(self.app, None)
+
+ # Set methods to be tested.
+ self.methods = {
+ 'pause': None,
+ }
+
+ def setup_servers_mock(self, count=1):
+ servers = fakes.FakeServer.create_servers(methods=self.methods,
+ count=count)
+
+ # This is the return value for utils.find_resource()
+ self.servers_mock.get = fakes.FakeServer.get_servers(servers, 1)
+
+ return servers
+
+ def test_server_pause_one_server(self):
+ servers = self.setup_servers_mock(1)
+
+ arglist = [
+ servers[0].id,
+ ]
+ verifylist = [
+ ('server', [servers[0].id]),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ # DisplayCommandBase.take_action() returns two tuples
+ self.cmd.take_action(parsed_args)
+
+ servers[0].pause.assert_called_with()
+
+ def test_server_pause_multi_servers(self):
+ servers = self.setup_servers_mock(3)
+ arglist = []
+ verifylist = []
+
+ for i in range(0, len(servers)):
+ arglist.append(servers[i].id)
+ verifylist = [
+ ('server', arglist),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ # DisplayCommandBase.take_action() returns two tuples
+ self.cmd.take_action(parsed_args)
+
+ for i in range(0, len(servers)):
+ servers[i].pause.assert_called_with()
+
+
class TestServerResize(TestServer):
def setUp(self):
diff --git a/openstackclient/tests/compute/v2/test_service.py b/openstackclient/tests/compute/v2/test_service.py
new file mode 100644
index 00000000..c6db30ac
--- /dev/null
+++ b/openstackclient/tests/compute/v2/test_service.py
@@ -0,0 +1,54 @@
+# Copyright 2015 Mirantis, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+
+from openstackclient.compute.v2 import service
+from openstackclient.tests.compute.v2 import fakes as compute_fakes
+
+
+class TestService(compute_fakes.TestComputev2):
+
+ def setUp(self):
+ super(TestService, self).setUp()
+
+ # Get a shortcut to the ServiceManager Mock
+ self.service_mock = self.app.client_manager.compute.services
+ self.service_mock.reset_mock()
+
+
+class TestServiceDelete(TestService):
+
+ def setUp(self):
+ super(TestServiceDelete, self).setUp()
+
+ self.service_mock.delete.return_value = None
+
+ # Get the command object to test
+ self.cmd = service.DeleteService(self.app, None)
+
+ def test_service_delete_no_options(self):
+ arglist = [
+ compute_fakes.service_id,
+ ]
+ verifylist = [
+ ('service', compute_fakes.service_id),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ # DisplayCommandBase.take_action() returns two tuples
+ self.cmd.take_action(parsed_args)
+
+ self.service_mock.delete.assert_called_with(
+ compute_fakes.service_id,
+ )
diff --git a/openstackclient/tests/fakes.py b/openstackclient/tests/fakes.py
index 979f9481..85e65fb1 100644
--- a/openstackclient/tests/fakes.py
+++ b/openstackclient/tests/fakes.py
@@ -13,11 +13,14 @@
# under the License.
#
+import copy
import json
+import mock
import six
import sys
+import uuid
-from keystoneclient import fixture
+from keystoneauth1 import fixture
import requests
@@ -122,17 +125,41 @@ class FakeModule(object):
class FakeResource(object):
- def __init__(self, manager, info, loaded=False):
+ def __init__(self, manager=None, info={}, loaded=False, methods={}):
+ """Set attributes and methods for a resource.
+
+ :param manager:
+ The resource manager
+ :param Dictionary info:
+ A dictionary with all attributes
+ :param bool loaded:
+ True if the resource is loaded in memory
+ :param Dictionary methods:
+ A dictionary with all methods
+ """
self.__name__ = type(self).__name__
self.manager = manager
self._info = info
self._add_details(info)
+ self._add_methods(methods)
self._loaded = loaded
def _add_details(self, info):
for (k, v) in six.iteritems(info):
setattr(self, k, v)
+ def _add_methods(self, methods):
+ """Fake methods with MagicMock objects.
+
+ For each <@key, @value> pairs in methods, add an callable MagicMock
+ object named @key as an attribute, and set the mock's return_value to
+ @value. When users access the attribute with (), @value will be
+ returned, which looks like a function call.
+ """
+ for (name, ret) in six.iteritems(methods):
+ method = mock.MagicMock(return_value=ret)
+ setattr(self, name, method)
+
def __repr__(self):
reprkeys = sorted(k for k in self.__dict__.keys() if k[0] != '_' and
k != 'manager')
@@ -150,3 +177,79 @@ class FakeResponse(requests.Response):
self._content = json.dumps(data)
if not isinstance(self._content, six.binary_type):
self._content = self._content.encode()
+
+
+class FakeModel(dict):
+ def __getattr__(self, key):
+ try:
+ return self[key]
+ except KeyError:
+ raise AttributeError(key)
+
+
+class FakeServer(object):
+ """Fake one or more compute servers."""
+
+ @staticmethod
+ def create_one_server(attrs={}, methods={}):
+ """Create a fake server.
+
+ :param Dictionary attrs:
+ A dictionary with all attributes
+ :param Dictionary methods:
+ A dictionary with all methods
+ :return:
+ A FakeResource object, with id, name, metadata
+ """
+ # Set default attributes.
+ server_info = {
+ 'id': 'server-id-' + uuid.uuid4().hex,
+ 'name': 'server-name-' + uuid.uuid4().hex,
+ 'metadata': {},
+ }
+
+ # Overwrite default attributes.
+ server_info.update(attrs)
+
+ server = FakeResource(info=copy.deepcopy(server_info),
+ methods=methods,
+ loaded=True)
+ return server
+
+ @staticmethod
+ def create_servers(attrs={}, methods={}, count=2):
+ """Create multiple fake servers.
+
+ :param Dictionary attrs:
+ A dictionary with all attributes
+ :param Dictionary methods:
+ A dictionary with all methods
+ :param int count:
+ The number of servers to fake
+ :return:
+ A list of FakeResource objects faking the servers
+ """
+ servers = []
+ for i in range(0, count):
+ servers.append(FakeServer.create_one_server(attrs, methods))
+
+ return servers
+
+ @staticmethod
+ def get_servers(servers=None, count=2):
+ """Get an iterable MagicMock object with a list of faked servers.
+
+ If servers list is provided, then initialize the Mock object with the
+ list. Otherwise create one.
+
+ :param List servers:
+ A list of FakeResource objects faking servers
+ :param int count:
+ The number of servers to fake
+ :return:
+ An iterable Mock object with side_effect set to a list of faked
+ servers
+ """
+ if servers is None:
+ servers = FakeServer.create_servers(count)
+ return mock.MagicMock(side_effect=servers)
diff --git a/openstackclient/tests/identity/v3/fakes.py b/openstackclient/tests/identity/v3/fakes.py
index 9c4de9cc..9fe341ed 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,
@@ -195,6 +193,8 @@ SERVICE_WITHOUT_NAME = {
'links': base_url + 'services/' + service_id,
}
+credential_id = 'c-123'
+
endpoint_id = 'e-123'
endpoint_url = 'http://127.0.0.1:35357'
endpoint_region = 'RegionOne'
@@ -261,6 +261,7 @@ TOKEN_WITH_DOMAIN_ID = {
idp_id = 'test_idp'
idp_description = 'super exciting IdP description'
idp_remote_ids = ['entity1', 'entity2']
+formatted_idp_remote_ids = 'entity1, entity2'
IDENTITY_PROVIDER = {
'id': idp_id,
@@ -400,6 +401,8 @@ class FakeIdentityv3Client(object):
def __init__(self, **kwargs):
self.domains = mock.Mock()
self.domains.resource_class = fakes.FakeResource(None, {})
+ self.credentials = mock.Mock()
+ self.credentials.resource_class = fakes.FakeResource(None, {})
self.endpoints = mock.Mock()
self.endpoints.resource_class = fakes.FakeResource(None, {})
self.groups = mock.Mock()
diff --git a/openstackclient/tests/identity/v3/test_credential.py b/openstackclient/tests/identity/v3/test_credential.py
new file mode 100644
index 00000000..e2e690c3
--- /dev/null
+++ b/openstackclient/tests/identity/v3/test_credential.py
@@ -0,0 +1,112 @@
+# 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 json
+
+from openstackclient.identity.v3 import credential
+from openstackclient.tests.identity.v3 import fakes as identity_fakes
+from openstackclient.tests import utils
+
+
+class TestCredential(identity_fakes.TestIdentityv3):
+ data = {
+ "access": "abc123",
+ "secret": "hidden-message",
+ "trust_id": None
+ }
+
+ def __init__(self, *args):
+ super(TestCredential, self).__init__(*args)
+
+ self.json_data = json.dumps(self.data)
+
+ def setUp(self):
+ super(TestCredential, self).setUp()
+
+ # Get a shortcut to the CredentialManager Mock
+ self.credentials_mock = self.app.client_manager.identity.credentials
+ self.credentials_mock.reset_mock()
+
+ # Get a shortcut to the UserManager Mock
+ self.users_mock = self.app.client_manager.identity.users
+ self.users_mock.reset_mock()
+
+ # Get a shortcut to the ProjectManager Mock
+ self.projects_mock = self.app.client_manager.identity.projects
+ self.projects_mock.reset_mock()
+
+
+class TestCredentialSet(TestCredential):
+ def setUp(self):
+ super(TestCredentialSet, self).setUp()
+ self.cmd = credential.SetCredential(self.app, None)
+
+ def test_credential_set_no_options(self):
+ arglist = [
+ identity_fakes.credential_id,
+ ]
+
+ self.assertRaises(utils.ParserException,
+ self.check_parser, self.cmd, arglist, [])
+
+ def test_credential_set_missing_user(self):
+ arglist = [
+ '--type', 'ec2',
+ '--data', self.json_data,
+ identity_fakes.credential_id,
+ ]
+
+ self.assertRaises(utils.ParserException,
+ self.check_parser, self.cmd, arglist, [])
+
+ def test_credential_set_missing_type(self):
+ arglist = [
+ '--user', identity_fakes.user_name,
+ '--data', self.json_data,
+ identity_fakes.credential_id,
+ ]
+
+ self.assertRaises(utils.ParserException,
+ self.check_parser, self.cmd, arglist, [])
+
+ def test_credential_set_missing_data(self):
+ arglist = [
+ '--user', identity_fakes.user_name,
+ '--type', 'ec2',
+ identity_fakes.credential_id,
+ ]
+
+ self.assertRaises(utils.ParserException,
+ self.check_parser, self.cmd, arglist, [])
+
+ def test_credential_set_valid(self):
+ arglist = [
+ '--user', identity_fakes.user_name,
+ '--type', 'ec2',
+ '--data', self.json_data,
+ identity_fakes.credential_id,
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, [])
+ self.cmd.take_action(parsed_args)
+
+ def test_credential_set_valid_with_project(self):
+ arglist = [
+ '--user', identity_fakes.user_name,
+ '--type', 'ec2',
+ '--data', self.json_data,
+ '--project', identity_fakes.project_name,
+ identity_fakes.credential_id,
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, [])
+ self.cmd.take_action(parsed_args)
diff --git a/openstackclient/tests/identity/v3/test_identity_provider.py b/openstackclient/tests/identity/v3/test_identity_provider.py
index cd328c1d..db097d5d 100644
--- a/openstackclient/tests/identity/v3/test_identity_provider.py
+++ b/openstackclient/tests/identity/v3/test_identity_provider.py
@@ -69,7 +69,7 @@ class TestIdentityProviderCreate(TestIdentityProvider):
identity_fakes.idp_description,
True,
identity_fakes.idp_id,
- identity_fakes.idp_remote_ids
+ identity_fakes.formatted_idp_remote_ids
)
self.assertEqual(datalist, data)
@@ -103,7 +103,7 @@ class TestIdentityProviderCreate(TestIdentityProvider):
identity_fakes.idp_description,
True,
identity_fakes.idp_id,
- identity_fakes.idp_remote_ids
+ identity_fakes.formatted_idp_remote_ids
)
self.assertEqual(datalist, data)
@@ -137,7 +137,7 @@ class TestIdentityProviderCreate(TestIdentityProvider):
identity_fakes.idp_description,
True,
identity_fakes.idp_id,
- identity_fakes.idp_remote_ids
+ identity_fakes.formatted_idp_remote_ids
)
self.assertEqual(datalist, data)
@@ -172,7 +172,7 @@ class TestIdentityProviderCreate(TestIdentityProvider):
identity_fakes.idp_description,
True,
identity_fakes.idp_id,
- identity_fakes.idp_remote_ids
+ identity_fakes.formatted_idp_remote_ids
)
self.assertEqual(datalist, data)
@@ -211,7 +211,7 @@ class TestIdentityProviderCreate(TestIdentityProvider):
identity_fakes.idp_description,
True,
identity_fakes.idp_id,
- identity_fakes.idp_remote_ids
+ identity_fakes.formatted_idp_remote_ids
)
self.assertEqual(datalist, data)
@@ -253,7 +253,7 @@ class TestIdentityProviderCreate(TestIdentityProvider):
None,
False,
identity_fakes.idp_id,
- identity_fakes.idp_remote_ids
+ identity_fakes.formatted_idp_remote_ids
)
self.assertEqual(datalist, data)
@@ -363,7 +363,7 @@ class TestIdentityProviderShow(TestIdentityProvider):
identity_fakes.idp_description,
True,
identity_fakes.idp_id,
- identity_fakes.idp_remote_ids
+ identity_fakes.formatted_idp_remote_ids
)
self.assertEqual(datalist, data)
@@ -374,6 +374,50 @@ class TestIdentityProviderSet(TestIdentityProvider):
super(TestIdentityProviderSet, self).setUp()
self.cmd = identity_provider.SetIdentityProvider(self.app, None)
+ def test_identity_provider_set_description(self):
+ """Set Identity Provider's description. """
+ def prepare(self):
+ """Prepare fake return objects before the test is executed"""
+ updated_idp = copy.deepcopy(identity_fakes.IDENTITY_PROVIDER)
+ updated_idp['enabled'] = False
+ resources = fakes.FakeResource(
+ None,
+ updated_idp,
+ loaded=True
+ )
+ self.identity_providers_mock.update.return_value = resources
+
+ prepare(self)
+ new_description = 'new desc'
+ arglist = [
+ '--description', new_description,
+ identity_fakes.idp_id
+ ]
+ verifylist = [
+ ('identity_provider', identity_fakes.idp_id),
+ ('description', new_description),
+ ('enable', False),
+ ('disable', False),
+ ('remote_id', None)
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+ self.identity_providers_mock.update.assert_called_with(
+ identity_fakes.idp_id,
+ description=new_description
+ )
+
+ collist = ('description', 'enabled', 'id', 'remote_ids')
+ self.assertEqual(collist, columns)
+ datalist = (
+ identity_fakes.idp_description,
+ False,
+ identity_fakes.idp_id,
+ identity_fakes.idp_remote_ids
+ )
+ self.assertEqual(datalist, data)
+
def test_identity_provider_disable(self):
"""Disable Identity Provider
@@ -398,6 +442,7 @@ class TestIdentityProviderSet(TestIdentityProvider):
]
verifylist = [
('identity_provider', identity_fakes.idp_id),
+ ('description', None),
('enable', False),
('disable', True),
('remote_id', identity_fakes.idp_remote_ids)
@@ -443,6 +488,7 @@ class TestIdentityProviderSet(TestIdentityProvider):
]
verifylist = [
('identity_provider', identity_fakes.idp_id),
+ ('description', None),
('enable', True),
('disable', False),
('remote_id', identity_fakes.idp_remote_ids)
@@ -488,6 +534,7 @@ class TestIdentityProviderSet(TestIdentityProvider):
]
verifylist = [
('identity_provider', identity_fakes.idp_id),
+ ('description', None),
('enable', True),
('disable', False),
('remote_id', [self.new_remote_id])
@@ -533,6 +580,7 @@ class TestIdentityProviderSet(TestIdentityProvider):
]
verifylist = [
('identity_provider', identity_fakes.idp_id),
+ ('description', None),
('enable', True),
('disable', False),
('remote_id_file', self.new_remote_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 4a0ba066..8ad4b099 100644
--- a/openstackclient/tests/identity/v3/test_role.py
+++ b/openstackclient/tests/identity/v3/test_role.py
@@ -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(
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..f71407a6 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,193 @@ 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',
+ '--owner', '123456',
+ '--protected',
+ '--private',
+ image_fakes.image_name,
+ ]
+ verifylist = [
+ ('container_format', 'ovf'),
+ ('disk_format', 'fs'),
+ ('min_disk', 10),
+ ('min_ram', 4),
+ ('owner', '123456'),
+ ('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,
+ owner='123456',
+ 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 = [
+ '--store', 'somewhere',
+ image_fakes.image_name,
+ ]
+ verifylist = [
+ ('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):
@@ -52,10 +240,8 @@ class TestAddProjectToImage(TestImage):
copy.deepcopy(image_fakes.IMAGE),
loaded=True,
)
- self.image_members_mock.create.return_value = fakes.FakeResource(
- None,
+ self.image_members_mock.create.return_value = fakes.FakeModel(
copy.deepcopy(image_fakes.MEMBER),
- loaded=True,
)
self.project_mock.get.return_value = fakes.FakeResource(
None,
@@ -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 0e0cc50b..c548d890 100644
--- a/openstackclient/tests/test_shell.py
+++ b/openstackclient/tests/test_shell.py
@@ -77,6 +77,8 @@ CLOUD_2 = {
'username': 'zaphod',
},
'region_name': 'occ-cloud',
+ 'log_file': '/tmp/test_log_file',
+ 'log_level': 'debug',
}
}
}
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 0d8c2024..b9b2ae87 100644
--- a/openstackclient/tests/volume/v2/fakes.py
+++ b/openstackclient/tests/volume/v2/fakes.py
@@ -15,14 +15,15 @@
import copy
import mock
-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
+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 = copy.deepcopy(compute_fakes.SERVER)
-volume_attachment_server['device'] = 'device'
+volume_attachment_server = {
+ 'device': '/dev/ice',
+ 'server_id': '1233',
+}
volume_id = "ce26708d-a7f8-4b4b-9861-4a80256615a6"
volume_name = "fake_volume"
@@ -211,7 +212,7 @@ 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
)
diff --git a/openstackclient/tests/volume/v2/test_volume.py b/openstackclient/tests/volume/v2/test_volume.py
index 348ae377..70324b6e 100644
--- a/openstackclient/tests/volume/v2/test_volume.py
+++ b/openstackclient/tests/volume/v2/test_volume.py
@@ -15,7 +15,7 @@
import copy
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.volume.v2 import fakes as volume_fakes
from openstackclient.volume.v2 import volume
@@ -27,7 +27,7 @@ class TestVolume(volume_fakes.TestVolume):
self.volumes_mock = self.app.client_manager.volume.volumes
self.volumes_mock.reset_mock()
- self.projects_mock = self.app.client_manager.identity.tenants
+ self.projects_mock = self.app.client_manager.identity.projects
self.projects_mock.reset_mock()
self.users_mock = self.app.client_manager.identity.users
@@ -548,7 +548,7 @@ class TestVolumeList(TestVolume):
]
self.assertEqual(collist, columns)
- server = volume_fakes.volume_attachment_server['id']
+ server = volume_fakes.volume_attachment_server['server_id']
device = volume_fakes.volume_attachment_server['device']
msg = 'Attached to %s on %s ' % (server, device)
datalist = ((
@@ -560,32 +560,145 @@ class TestVolumeList(TestVolume):
), )
self.assertEqual(datalist, tuple(data))
- def test_volume_list_all_projects_option(self):
+ def test_volume_list_project(self):
arglist = [
- '--all-projects',
+ '--project', identity_fakes.project_name,
]
verifylist = [
+ ('project', identity_fakes.project_name),
('long', False),
- ('all_projects', True),
- ('name', None),
+ ('all_projects', False),
('status', None),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
- collist = [
+ 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,
]
- self.assertEqual(collist, columns)
+ 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['id']
+ 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,
@@ -618,7 +731,7 @@ class TestVolumeList(TestVolume):
)
self.assertEqual(collist, tuple(columns))
- server = volume_fakes.volume_attachment_server['id']
+ server = volume_fakes.volume_attachment_server['server_id']
device = volume_fakes.volume_attachment_server['device']
msg = 'Attached to %s on %s ' % (server, device)
@@ -654,7 +767,42 @@ class TestVolumeList(TestVolume):
)
self.assertEqual(collist, tuple(columns))
- server = volume_fakes.volume_attachment_server['id']
+ 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 = ((
@@ -693,7 +841,7 @@ class TestVolumeList(TestVolume):
]
self.assertEqual(collist, columns)
- server = volume_fakes.volume_attachment_server['id']
+ server = volume_fakes.volume_attachment_server['server_id']
device = volume_fakes.volume_attachment_server['device']
msg = 'Attached to %s on %s ' % (server, device)
datalist = ((
diff --git a/openstackclient/volume/client.py b/openstackclient/volume/client.py
index d4800b8d..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_API_VERSION = '1'
+DEFAULT_API_VERSION = '2'
API_VERSION_OPTION = 'os_volume_api_version'
API_NAME = "volume"
API_VERSIONS = {
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..0691d884 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
@@ -239,7 +239,7 @@ class ListVolume(lister.Lister):
def _format_attach(attachments):
"""Return a formatted string of a volume's attached instances
- :param volume: a volume.attachments field
+ :param attachments: a volume.attachments field
:rtype: a string of formatted instances
"""
@@ -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 fe4a3ff6..430d1228 100644
--- a/openstackclient/volume/v2/volume.py
+++ b/openstackclient/volume/v2/volume.py
@@ -16,7 +16,6 @@
import copy
import logging
-import os
from cliff import command
from cliff import lister
@@ -25,6 +24,7 @@ import six
from openstackclient.common import parseractions
from openstackclient.common import utils
+from openstackclient.identity import common as identity_common
class CreateVolume(show.ShowOne):
@@ -175,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
@@ -200,45 +200,58 @@ class ListVolume(lister.Lister):
def get_parser(self, prog_name):
parser = super(ListVolume, self).get_parser(prog_name)
parser.add_argument(
- '--all-projects',
- action='store_true',
- default=bool(int(os.environ.get("ALL_PROJECTS", 0))),
- help='Include all projects (admin only)',
+ '--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(
- '--long',
- action='store_true',
- default=False,
- help='List additional fields in output',
+ '--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 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):
- self.log.debug('take_action(%s)', 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
+ :param attachments: a volume.attachments field
:rtype: a string of formatted instances
"""
msg = ''
for attachment in attachments:
- server = attachment['id']
+ server = attachment['server_id']
if server in server_cache:
server = server_cache[server].name
device = attachment['device']
@@ -282,8 +295,23 @@ class ListVolume(lister.Lister):
# 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_user(identity_client,
+ parsed_args.user,
+ parsed_args.user_domain)
+
search_opts = {
- 'all_projects': parsed_args.all_projects,
+ 'all_tenants': parsed_args.all_projects,
+ 'project_id': project_id,
+ 'user_id': user_id,
'display_name': parsed_args.name,
'status': parsed_args.status,
}
@@ -335,8 +363,8 @@ class SetVolume(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)
@@ -383,8 +411,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)
@@ -416,8 +444,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/v2/volume_type.py b/openstackclient/volume/v2/volume_type.py
index fb0342c5..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']
@@ -174,8 +174,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)
@@ -250,8 +250,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,