summaryrefslogtreecommitdiff
path: root/openstackclient
diff options
context:
space:
mode:
Diffstat (limited to 'openstackclient')
-rw-r--r--openstackclient/common/clientmanager.py16
-rw-r--r--openstackclient/common/commandmanager.py18
-rw-r--r--openstackclient/common/module.py60
-rw-r--r--openstackclient/common/restapi.py297
-rw-r--r--openstackclient/common/utils.py14
-rw-r--r--openstackclient/compute/client.py7
-rw-r--r--openstackclient/identity/client.py6
-rw-r--r--openstackclient/identity/v2_0/token.py38
-rw-r--r--openstackclient/image/v1/image.py19
-rw-r--r--openstackclient/object/v1/lib/container.py20
-rw-r--r--openstackclient/object/v1/lib/object.py24
-rw-r--r--openstackclient/shell.py68
-rw-r--r--openstackclient/tests/common/test_commandmanager.py19
-rw-r--r--openstackclient/tests/common/test_module.py88
-rw-r--r--openstackclient/tests/common/test_restapi.py124
-rw-r--r--openstackclient/tests/fakes.py6
-rw-r--r--openstackclient/tests/identity/v2_0/fakes.py11
-rw-r--r--openstackclient/tests/identity/v2_0/test_token.py56
-rw-r--r--openstackclient/tests/object/v1/lib/test_container.py80
-rw-r--r--openstackclient/tests/object/v1/lib/test_object.py107
-rw-r--r--openstackclient/tests/test_shell.py182
-rw-r--r--openstackclient/volume/client.py5
22 files changed, 1009 insertions, 256 deletions
diff --git a/openstackclient/common/clientmanager.py b/openstackclient/common/clientmanager.py
index a0224064..b6dab253 100644
--- a/openstackclient/common/clientmanager.py
+++ b/openstackclient/common/clientmanager.py
@@ -42,16 +42,26 @@ class ClientManager(object):
"""Manages access to API clients, including authentication."""
identity = ClientCache(identity_client.make_client)
- def __init__(self, token=None, url=None, auth_url=None, project_name=None,
- project_id=None, username=None, password=None,
- region_name=None, verify=True, api_version=None):
+ def __init__(self, token=None, url=None, auth_url=None,
+ domain_id=None, domain_name=None,
+ project_name=None, project_id=None,
+ username=None, password=None,
+ user_domain_id=None, user_domain_name=None,
+ project_domain_id=None, project_domain_name=None,
+ region_name=None, api_version=None, verify=True):
self._token = token
self._url = url
self._auth_url = auth_url
+ self._domain_id = domain_id
+ self._domain_name = domain_name
self._project_name = project_name
self._project_id = project_id
self._username = username
self._password = password
+ self._user_domain_id = user_domain_id
+ self._user_domain_name = user_domain_name
+ self._project_domain_id = project_domain_id
+ self._project_domain_name = project_domain_name
self._region_name = region_name
self._api_version = api_version
self._service_catalog = None
diff --git a/openstackclient/common/commandmanager.py b/openstackclient/common/commandmanager.py
index 06073d93..553bc920 100644
--- a/openstackclient/common/commandmanager.py
+++ b/openstackclient/common/commandmanager.py
@@ -1,4 +1,4 @@
-# Copyright 2012-2013 OpenStack, LLC.
+# Copyright 2012-2013 OpenStack Foundation
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
@@ -28,15 +28,29 @@ class CommandManager(cliff.commandmanager.CommandManager):
"""Alters Cliff's default CommandManager behaviour to load additional
command groups after initialization.
"""
+ def __init__(self, namespace, convert_underscores=True):
+ self.group_list = []
+ super(CommandManager, self).__init__(namespace, convert_underscores)
+
def _load_commands(self, group=None):
if not group:
group = self.namespace
+ self.group_list.append(group)
for ep in pkg_resources.iter_entry_points(group):
LOG.debug('found command %r' % ep.name)
- self.commands[ep.name.replace('_', ' ')] = ep
+ cmd_name = (
+ ep.name.replace('_', ' ')
+ if self.convert_underscores
+ else ep.name
+ )
+ self.commands[cmd_name] = ep
return
def add_command_group(self, group=None):
"""Adds another group of command entrypoints"""
if group:
self._load_commands(group)
+
+ def get_command_groups(self):
+ """Returns a list of the loaded command groups"""
+ return self.group_list
diff --git a/openstackclient/common/module.py b/openstackclient/common/module.py
new file mode 100644
index 00000000..4a7f0626
--- /dev/null
+++ b/openstackclient/common/module.py
@@ -0,0 +1,60 @@
+# Copyright 2013 Nebula 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.
+#
+
+"""Module action implementation"""
+
+import logging
+import six
+import sys
+
+from cliff import show
+
+
+class ListModule(show.ShowOne):
+ """List module versions"""
+
+ auth_required = False
+ log = logging.getLogger(__name__ + '.ListModule')
+
+ def get_parser(self, prog_name):
+ parser = super(ListModule, self).get_parser(prog_name)
+ parser.add_argument(
+ '--all',
+ action='store_true',
+ default=False,
+ help='Show all modules that have version information',
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ self.log.debug('take_action(%s)' % parsed_args)
+
+ data = {}
+ # Get module versions
+ mods = sys.modules
+ for k in mods.keys():
+ k = k.split('.')[0]
+ # TODO(dtroyer): Need a better way to decide which modules to
+ # show for the default (not --all) invocation.
+ # It should be just the things we actually care
+ # about like client and plugin modules...
+ if (parsed_args.all or 'client' in k):
+ try:
+ data[k] = mods[k].__version__
+ except AttributeError:
+ # aw, just skip it
+ pass
+
+ return zip(*sorted(six.iteritems(data)))
diff --git a/openstackclient/common/restapi.py b/openstackclient/common/restapi.py
index a45c8426..1bb64fae 100644
--- a/openstackclient/common/restapi.py
+++ b/openstackclient/common/restapi.py
@@ -25,14 +25,15 @@ except ImportError:
from urllib import urlencode
+USER_AGENT = 'RAPI'
+
_logger = logging.getLogger(__name__)
class RESTApi(object):
- """A REST api client that handles the interface from us to the server
+ """A REST API client that handles the interface from us to the server
- RESTApi is an extension of a requests.Session that knows
- how to do:
+ RESTApi is requests.Session wrapper that knows how to do:
* JSON serialization/deserialization
* log requests in 'curl' format
* basic API boilerplate for create/delete/list/set/show verbs
@@ -46,26 +47,49 @@ class RESTApi(object):
it communicates with, such as the available endpoints, API versions, etc.
"""
- USER_AGENT = 'RAPI'
-
def __init__(
self,
- os_auth=None,
+ session=None,
+ auth_header=None,
user_agent=USER_AGENT,
- debug=None,
verify=True,
- **kwargs
+ logger=None,
+ debug=None,
):
- self.set_auth(os_auth)
+ """Construct a new REST client
+
+ :param object session: A Session object to be used for
+ communicating with the identity service.
+ :param string auth_header: A token from an initialized auth_reference
+ to be used in the X-Auth-Token header
+ :param string user_agent: Set the User-Agent header in the requests
+ :param boolean/string verify: If ``True``, the SSL cert will be
+ verified. A CA_BUNDLE path can also be
+ provided.
+ :param logging.Logger logger: A logger to output to. (optional)
+ :param boolean debug: Enables debug logging of all request and
+ responses to identity service.
+ default False (optional)
+ """
+
+ self.set_auth(auth_header)
self.debug = debug
- self.session = requests.Session(**kwargs)
- self.set_header('User-Agent', user_agent)
- self.set_header('Content-Type', 'application/json')
+ if not session:
+ # We create a default session object
+ session = requests.Session()
+ self.session = session
+ self.session.verify = verify
+ self.session.user_agent = user_agent
+
+ if logger:
+ self.logger = logger
+ else:
+ self.logger = _logger
- def set_auth(self, os_auth):
+ def set_auth(self, auth_header):
"""Sets the current auth blob"""
- self.os_auth = os_auth
+ self.auth_header = auth_header
def set_header(self, header, content):
"""Sets passed in headers into the session headers
@@ -78,37 +102,154 @@ class RESTApi(object):
self.session.headers[header] = content
def request(self, method, url, **kwargs):
- if self.os_auth:
- self.session.headers.setdefault('X-Auth-Token', self.os_auth)
- if 'data' in kwargs and isinstance(kwargs['data'], type({})):
- kwargs['data'] = json.dumps(kwargs['data'])
- log_request(method, url, headers=self.session.headers, **kwargs)
+ """Make an authenticated (if token available) request
+
+ :param method: Request HTTP method
+ :param url: Request URL
+ :param data: Request body
+ :param json: Request body to be encoded as JSON
+ Overwrites ``data`` argument if present
+ """
+
+ kwargs.setdefault('headers', {})
+ if self.auth_header:
+ kwargs['headers']['X-Auth-Token'] = self.auth_header
+
+ if 'json' in kwargs and isinstance(kwargs['json'], type({})):
+ kwargs['data'] = json.dumps(kwargs.pop('json'))
+ kwargs['headers']['Content-Type'] = 'application/json'
+
+ kwargs.setdefault('allow_redirects', True)
+
+ if self.debug:
+ self._log_request(method, url, **kwargs)
+
response = self.session.request(method, url, **kwargs)
- log_response(response)
+
+ if self.debug:
+ self._log_response(response)
+
return self._error_handler(response)
+ def _error_handler(self, response):
+ if response.status_code < 200 or response.status_code >= 300:
+ self.logger.debug(
+ "ERROR: %s",
+ response.text,
+ )
+ response.raise_for_status()
+ return response
+
+ # Convenience methods to mimic the ones provided by requests.Session
+
+ def delete(self, url, **kwargs):
+ """Send a DELETE request. Returns :class:`requests.Response` object.
+
+ :param url: Request URL
+ :param \*\*kwargs: Optional arguments passed to ``request``
+ """
+
+ return self.request('DELETE', url, **kwargs)
+
+ def get(self, url, **kwargs):
+ """Send a GET request. Returns :class:`requests.Response` object.
+
+ :param url: Request URL
+ :param \*\*kwargs: Optional arguments passed to ``request``
+ """
+
+ return self.request('GET', url, **kwargs)
+
+ def head(self, url, **kwargs):
+ """Send a HEAD request. Returns :class:`requests.Response` object.
+
+ :param url: Request URL
+ :param \*\*kwargs: Optional arguments passed to ``request``
+ """
+
+ kwargs.setdefault('allow_redirects', False)
+ return self.request('HEAD', url, **kwargs)
+
+ def options(self, url, **kwargs):
+ """Send an OPTIONS request. Returns :class:`requests.Response` object.
+
+ :param url: Request URL
+ :param \*\*kwargs: Optional arguments passed to ``request``
+ """
+
+ return self.request('OPTIONS', url, **kwargs)
+
+ def patch(self, url, data=None, json=None, **kwargs):
+ """Send a PUT request. Returns :class:`requests.Response` object.
+
+ :param url: Request URL
+ :param data: Request body
+ :param json: Request body to be encoded as JSON
+ Overwrites ``data`` argument if present
+ :param \*\*kwargs: Optional arguments passed to ``request``
+ """
+
+ return self.request('PATCH', url, data=data, json=json, **kwargs)
+
+ def post(self, url, data=None, json=None, **kwargs):
+ """Send a POST request. Returns :class:`requests.Response` object.
+
+ :param url: Request URL
+ :param data: Request body
+ :param json: Request body to be encoded as JSON
+ Overwrites ``data`` argument if present
+ :param \*\*kwargs: Optional arguments passed to ``request``
+ """
+
+ return self.request('POST', url, data=data, json=json, **kwargs)
+
+ def put(self, url, data=None, json=None, **kwargs):
+ """Send a PUT request. Returns :class:`requests.Response` object.
+
+ :param url: Request URL
+ :param data: Request body
+ :param json: Request body to be encoded as JSON
+ Overwrites ``data`` argument if present
+ :param \*\*kwargs: Optional arguments passed to ``request``
+ """
+
+ return self.request('PUT', url, data=data, json=json, **kwargs)
+
+ # Command verb methods
+
def create(self, url, data=None, response_key=None, **kwargs):
- response = self.request('POST', url, data=data, **kwargs)
+ """Create a new object via a POST request
+
+ :param url: Request URL
+ :param data: Request body, wil be JSON encoded
+ :param response_key: Dict key in response body to extract
+ :param \*\*kwargs: Optional arguments passed to ``request``
+ """
+
+ response = self.request('POST', url, json=data, **kwargs)
if response_key:
return response.json()[response_key]
else:
return response.json()
- #with self.completion_cache('human_id', self.resource_class, mode="a"):
- # with self.completion_cache('uuid', self.resource_class, mode="a"):
- # return self.resource_class(self, body[response_key])
+ def list(self, url, data=None, response_key=None, **kwargs):
+ """Retrieve a list of objects via a GET or POST request
- def delete(self, url):
- self.request('DELETE', url)
+ :param url: Request URL
+ :param data: Request body, will be JSON encoded
+ :param response_key: Dict key in response body to extract
+ :param \*\*kwargs: Optional arguments passed to ``request``
+ """
- def list(self, url, data=None, response_key=None, **kwargs):
if data:
- response = self.request('POST', url, data=data, **kwargs)
+ response = self.request('POST', url, json=data, **kwargs)
else:
- kwargs.setdefault('allow_redirects', True)
response = self.request('GET', url, **kwargs)
- return response.json()[response_key]
+ if response_key:
+ return response.json()[response_key]
+ else:
+ return response.json()
###hack this for keystone!!!
#data = body[response_key]
@@ -120,70 +261,70 @@ class RESTApi(object):
# except KeyError:
# pass
- #with self.completion_cache('human_id', obj_class, mode="w"):
- # with self.completion_cache('uuid', obj_class, mode="w"):
- # return [obj_class(self, res, loaded=True)
- # for res in data if res]
-
def set(self, url, data=None, response_key=None, **kwargs):
- response = self.request('PUT', url, data=data)
+ """Update an object via a PUT request
+
+ :param url: Request URL
+ :param data: Request body
+ :param json: Request body to be encoded as JSON
+ Overwrites ``data`` argument if present
+ :param \*\*kwargs: Optional arguments passed to ``request``
+ """
+
+ response = self.request('PUT', url, json=data)
if data:
if response_key:
return response.json()[response_key]
else:
return response.json()
else:
+ # Nothing to do here
return None
def show(self, url, response_key=None, **kwargs):
+ """Retrieve a single object via a GET request
+
+ :param url: Request URL
+ :param response_key: Dict key in response body to extract
+ :param \*\*kwargs: Optional arguments passed to ``request``
+ """
+
response = self.request('GET', url, **kwargs)
if response_key:
return response.json()[response_key]
else:
return response.json()
- def _error_handler(self, response):
- if response.status_code < 200 or response.status_code >= 300:
- _logger.debug(
- "ERROR: %s",
+ def _log_request(self, method, url, **kwargs):
+ if 'params' in kwargs and kwargs['params'] != {}:
+ url += '?' + urlencode(kwargs['params'])
+
+ string_parts = [
+ "curl -i",
+ "-X '%s'" % method,
+ "'%s'" % url,
+ ]
+
+ for element in kwargs['headers']:
+ header = " -H '%s: %s'" % (element, kwargs['headers'][element])
+ string_parts.append(header)
+
+ self.logger.debug("REQ: %s" % " ".join(string_parts))
+ if 'data' in kwargs:
+ self.logger.debug(" REQ BODY: %r\n" % (kwargs['data']))
+
+ def _log_response(self, response):
+ self.logger.debug(
+ "RESP: [%s] %r\n",
+ response.status_code,
+ response.headers,
+ )
+ if response._content_consumed:
+ self.logger.debug(
+ " RESP BODY: %s\n",
response.text,
)
- response.raise_for_status()
- return response
-
-
-def log_request(method, url, **kwargs):
- # put in an early exit if debugging is not enabled?
- if 'params' in kwargs and kwargs['params'] != {}:
- url += '?' + urlencode(kwargs['params'])
-
- string_parts = [
- "curl -i",
- "-X '%s'" % method,
- "'%s'" % url,
- ]
-
- for element in kwargs['headers']:
- header = " -H '%s: %s'" % (element, kwargs['headers'][element])
- string_parts.append(header)
-
- _logger.debug("REQ: %s" % " ".join(string_parts))
- if 'data' in kwargs:
- _logger.debug("REQ BODY: %s\n" % (kwargs['data']))
-
-
-def log_response(response):
- _logger.debug(
- "RESP: [%s] %s\n",
- response.status_code,
- response.headers,
- )
- if response._content_consumed:
- _logger.debug(
- "RESP BODY: %s\n",
- response.text,
+ self.logger.debug(
+ " encoding: %s",
+ response.encoding,
)
- _logger.debug(
- "encoding: %s",
- response.encoding,
- )
diff --git a/openstackclient/common/utils.py b/openstackclient/common/utils.py
index 91a20895..94ea2225 100644
--- a/openstackclient/common/utils.py
+++ b/openstackclient/common/utils.py
@@ -15,6 +15,7 @@
"""Common client utilities"""
+import logging
import os
import six
import sys
@@ -215,3 +216,16 @@ def wait_for_status(status_f,
callback(progress)
time.sleep(sleep_time)
return retval
+
+
+def get_effective_log_level():
+ """Returns the lowest logging level considered by logging handlers
+
+ Retrieve an return the smallest log level set among the root
+ logger's handlers (in case of multiple handlers).
+ """
+ root_log = logging.getLogger()
+ min_log_lvl = logging.CRITICAL
+ for handler in root_log.handlers:
+ min_log_lvl = min(min_log_lvl, handler.level)
+ return min_log_lvl
diff --git a/openstackclient/compute/client.py b/openstackclient/compute/client.py
index 4ccb2f6d..765a48db 100644
--- a/openstackclient/compute/client.py
+++ b/openstackclient/compute/client.py
@@ -35,6 +35,10 @@ def make_client(instance):
instance._api_version[API_NAME],
API_VERSIONS)
LOG.debug('instantiating compute client: %s' % compute_client)
+
+ # Set client http_log_debug to True if verbosity level is high enough
+ http_log_debug = utils.get_effective_log_level() <= logging.DEBUG
+
client = compute_client(
username=instance._username,
api_key=instance._password,
@@ -49,7 +53,8 @@ def make_client(instance):
extensions=[],
service_type=API_NAME,
# FIXME(dhellmann): what is service_name?
- service_name='')
+ service_name='',
+ http_log_debug=http_log_debug)
# Populate the Nova client to skip another auth query to Identity
if instance._url:
diff --git a/openstackclient/identity/client.py b/openstackclient/identity/client.py
index 305d4cc4..b19388cc 100644
--- a/openstackclient/identity/client.py
+++ b/openstackclient/identity/client.py
@@ -46,6 +46,12 @@ def make_client(instance):
client = identity_client(
username=instance._username,
password=instance._password,
+ user_domain_id=instance._user_domain_id,
+ user_domain_name=instance._user_domain_name,
+ project_domain_id=instance._project_domain_id,
+ project_domain_name=instance._project_domain_name,
+ domain_id=instance._domain_id,
+ domain_name=instance._domain_name,
tenant_name=instance._project_name,
tenant_id=instance._project_id,
auth_url=instance._auth_url,
diff --git a/openstackclient/identity/v2_0/token.py b/openstackclient/identity/v2_0/token.py
new file mode 100644
index 00000000..a0433c96
--- /dev/null
+++ b/openstackclient/identity/v2_0/token.py
@@ -0,0 +1,38 @@
+# Copyright 2014 eBay 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.
+#
+
+"""Identity v2 Token action implementations"""
+
+import logging
+import six
+
+from cliff import show
+
+
+class CreateToken(show.ShowOne):
+ """Create token command"""
+
+ log = logging.getLogger(__name__ + '.CreateToken')
+
+ def get_parser(self, prog_name):
+ parser = super(CreateToken, self).get_parser(prog_name)
+ return parser
+
+ def take_action(self, parsed_args):
+ self.log.debug('take_action(%s)' % parsed_args)
+ identity_client = self.app.client_manager.identity
+ token = identity_client.service_catalog.get_token()
+ token['project_id'] = token.pop('tenant_id')
+ return zip(*sorted(six.iteritems(token)))
diff --git a/openstackclient/image/v1/image.py b/openstackclient/image/v1/image.py
index 40f9fce1..026b583c 100644
--- a/openstackclient/image/v1/image.py
+++ b/openstackclient/image/v1/image.py
@@ -48,9 +48,9 @@ class CreateImage(show.ShowOne):
help="Name of image",
)
parser.add_argument(
- "--disk_format",
+ "--disk-format",
default="raw",
- metavar="<disk_format>",
+ metavar="<disk-format>",
help="Disk format of image",
)
parser.add_argument(
@@ -66,7 +66,7 @@ class CreateImage(show.ShowOne):
parser.add_argument(
"--container-format",
default="bare",
- metavar="<container_format>",
+ metavar="<container-format>",
help="Container format of image",
)
parser.add_argument(
@@ -82,17 +82,17 @@ class CreateImage(show.ShowOne):
)
parser.add_argument(
"--min-disk",
- metavar="<disk_gb>",
+ metavar="<disk-gb>",
help="Minimum size of disk needed to boot image in gigabytes",
)
parser.add_argument(
"--min-ram",
- metavar="<disk_ram>",
+ metavar="<disk-ram>",
help="Minimum amount of ram needed to boot image in megabytes",
)
parser.add_argument(
"--location",
- metavar="<image_url>",
+ metavar="<image-url>",
help="URL where the data for this image already resides",
)
parser.add_argument(
@@ -107,7 +107,7 @@ class CreateImage(show.ShowOne):
)
parser.add_argument(
"--copy-from",
- metavar="<image_url>",
+ metavar="<image-url>",
help="Similar to --location, but this indicates that the image"
" should immediately be copied from the data store",
)
@@ -301,18 +301,19 @@ class SetImage(show.ShowOne):
)
parser.add_argument(
"--min-disk",
- metavar="<disk_gb>",
+ metavar="<disk-gb>",
help="Minimum size of disk needed to boot image in gigabytes",
)
parser.add_argument(
"--min-ram",
- metavar="<disk_ram>",
+ metavar="<disk-ram>",
help="Minimum amount of ram needed to boot image in megabytes",
)
parser.add_argument(
"--property",
dest="properties",
metavar="<key=value>",
+ default={},
action=parseractions.KeyValueAction,
help="Set property on this image "
'(repeat option to set multiple properties)',
diff --git a/openstackclient/object/v1/lib/container.py b/openstackclient/object/v1/lib/container.py
index 5103d9d4..0bae2349 100644
--- a/openstackclient/object/v1/lib/container.py
+++ b/openstackclient/object/v1/lib/container.py
@@ -67,19 +67,18 @@ def list_containers(
data.extend(listing)
return data
- object_url = url
- query = "format=json"
+ params = {
+ 'format': 'json',
+ }
if marker:
- query += '&marker=%s' % marker
+ params['marker'] = marker
if limit:
- query += '&limit=%d' % limit
+ params['limit'] = limit
if end_marker:
- query += '&end_marker=%s' % end_marker
+ params['end_marker'] = end_marker
if prefix:
- query += '&prefix=%s' % prefix
- url = "%s?%s" % (object_url, query)
- response = api.request('GET', url)
- return response.json()
+ params['prefix'] = prefix
+ return api.list(url, params=params)
def show_container(
@@ -95,9 +94,8 @@ def show_container(
:returns: dict of returned headers
"""
- object_url = "%s/%s" % (url, container)
+ response = api.head("%s/%s" % (url, container))
url_parts = urlparse(url)
- response = api.request('HEAD', object_url)
data = {
'account': url_parts.path.split('/')[-1],
'container': container,
diff --git a/openstackclient/object/v1/lib/object.py b/openstackclient/object/v1/lib/object.py
index 840ff0c0..646737bd 100644
--- a/openstackclient/object/v1/lib/object.py
+++ b/openstackclient/object/v1/lib/object.py
@@ -86,22 +86,23 @@ def list_objects(
return data
object_url = url
- query = "format=json"
+ params = {
+ 'format': 'json',
+ }
if marker:
- query += '&marker=%s' % marker
+ params['marker'] = marker
if limit:
- query += '&limit=%d' % limit
+ params['limit'] = limit
if end_marker:
- query += '&end_marker=%s' % end_marker
+ params['end_marker'] = end_marker
if delimiter:
- query += '&delimiter=%s' % delimiter
+ params['delimiter'] = delimiter
if prefix:
- query += '&prefix=%s' % prefix
+ params['prefix'] = prefix
if path:
- query += '&path=%s' % path
- url = "%s/%s?%s" % (object_url, container, query)
- response = api.request('GET', url)
- return response.json()
+ params['path'] = path
+ url = "%s/%s" % (object_url, container)
+ return api.list(url, params=params)
def show_object(
@@ -118,9 +119,8 @@ def show_object(
:returns: dict of object properties
"""
- object_url = "%s/%s/%s" % (url, container, obj)
+ response = api.head("%s/%s/%s" % (url, container, obj))
url_parts = urlparse(url)
- response = api.request('HEAD', object_url)
data = {
'account': url_parts.path.split('/')[-1],
'container': container,
diff --git a/openstackclient/shell.py b/openstackclient/shell.py
index 4ac7683f..76cc3c6a 100644
--- a/openstackclient/shell.py
+++ b/openstackclient/shell.py
@@ -144,10 +144,25 @@ class OpenStackShell(app.App):
default=env('OS_AUTH_URL'),
help='Authentication URL (Env: OS_AUTH_URL)')
parser.add_argument(
+ '--os-domain-name',
+ metavar='<auth-domain-name>',
+ default=env('OS_DOMAIN_NAME'),
+ help='Domain name of the requested domain-level'
+ 'authorization scope (Env: OS_DOMAIN_NAME)',
+ )
+ parser.add_argument(
+ '--os-domain-id',
+ metavar='<auth-domain-id>',
+ default=env('OS_DOMAIN_ID'),
+ help='Domain ID of the requested domain-level'
+ 'authorization scope (Env: OS_DOMAIN_ID)',
+ )
+ parser.add_argument(
'--os-project-name',
metavar='<auth-project-name>',
default=env('OS_PROJECT_NAME', default=env('OS_TENANT_NAME')),
- help='Authentication project name (Env: OS_PROJECT_NAME)',
+ help='Project name of the requested project-level'
+ 'authorization scope (Env: OS_PROJECT_NAME)',
)
parser.add_argument(
'--os-tenant-name',
@@ -159,7 +174,8 @@ class OpenStackShell(app.App):
'--os-project-id',
metavar='<auth-project-id>',
default=env('OS_PROJECT_ID', default=env('OS_TENANT_ID')),
- help='Authentication project ID (Env: OS_PROJECT_ID)',
+ help='Project ID of the requested project-level'
+ 'authorization scope (Env: OS_PROJECT_ID)',
)
parser.add_argument(
'--os-tenant-id',
@@ -178,6 +194,30 @@ class OpenStackShell(app.App):
default=utils.env('OS_PASSWORD'),
help='Authentication password (Env: OS_PASSWORD)')
parser.add_argument(
+ '--os-user-domain-name',
+ metavar='<auth-user-domain-name>',
+ default=utils.env('OS_USER_DOMAIN_NAME'),
+ help='Domain name of the user (Env: OS_USER_DOMAIN_NAME)')
+ parser.add_argument(
+ '--os-user-domain-id',
+ metavar='<auth-user-domain-id>',
+ default=utils.env('OS_USER_DOMAIN_ID'),
+ help='Domain ID of the user (Env: OS_USER_DOMAIN_ID)')
+ parser.add_argument(
+ '--os-project-domain-name',
+ metavar='<auth-project-domain-name>',
+ default=utils.env('OS_PROJECT_DOMAIN_NAME'),
+ help='Domain name of the project which is the requested '
+ 'project-level authorization scope '
+ '(Env: OS_PROJECT_DOMAIN_NAME)')
+ parser.add_argument(
+ '--os-project-domain-id',
+ metavar='<auth-project-domain-id>',
+ default=utils.env('OS_PROJECT_DOMAIN_ID'),
+ help='Domain ID of the project which is the requested '
+ 'project-level authorization scope '
+ '(Env: OS_PROJECT_DOMAIN_ID)')
+ parser.add_argument(
'--os-region-name',
metavar='<auth-region-name>',
default=env('OS_REGION_NAME'),
@@ -284,11 +324,16 @@ class OpenStackShell(app.App):
" either --os-password, or env[OS_PASSWORD], "
" or prompted response")
- if not (self.options.os_project_id
- or self.options.os_project_name):
+ if not ((self.options.os_project_id
+ or self.options.os_project_name) or
+ (self.options.os_domain_id
+ or self.options.os_domain_name)):
raise exc.CommandError(
- "You must provide a project id via"
- " either --os-project-id or via env[OS_PROJECT_ID]")
+ "You must provide authentication scope as a project "
+ "or a domain via --os-project-id or env[OS_PROJECT_ID], "
+ "--os-project-name or env[OS_PROJECT_NAME], "
+ "--os-domain-id or env[OS_DOMAIN_ID], or"
+ "--os-domain-name or env[OS_DOMAIN_NAME].")
if not self.options.os_auth_url:
raise exc.CommandError(
@@ -299,8 +344,14 @@ class OpenStackShell(app.App):
token=self.options.os_token,
url=self.options.os_url,
auth_url=self.options.os_auth_url,
+ domain_id=self.options.os_domain_id,
+ domain_name=self.options.os_domain_name,
project_name=self.options.os_project_name,
project_id=self.options.os_project_id,
+ user_domain_id=self.options.os_user_domain_id,
+ user_domain_name=self.options.os_user_domain_name,
+ project_domain_id=self.options.os_project_domain_id,
+ project_domain_name=self.options.os_project_domain_name,
username=self.options.os_username,
password=self.options.os_password,
region_name=self.options.os_region_name,
@@ -405,7 +456,10 @@ class OpenStackShell(app.App):
self.verify = self.options.os_cacert
else:
self.verify = not self.options.insecure
- self.restapi = restapi.RESTApi(verify=self.verify)
+ self.restapi = restapi.RESTApi(
+ verify=self.verify,
+ debug=self.options.debug,
+ )
def prepare_to_run_command(self, cmd):
"""Set up auth and API versions"""
diff --git a/openstackclient/tests/common/test_commandmanager.py b/openstackclient/tests/common/test_commandmanager.py
index 4953c297..088ea21e 100644
--- a/openstackclient/tests/common/test_commandmanager.py
+++ b/openstackclient/tests/common/test_commandmanager.py
@@ -1,4 +1,4 @@
-# Copyright 2012-2013 OpenStack, LLC.
+# Copyright 2012-2013 OpenStack Foundation
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
@@ -40,9 +40,11 @@ class FakeCommandManager(commandmanager.CommandManager):
if not group:
self.commands['one'] = FAKE_CMD_ONE
self.commands['two'] = FAKE_CMD_TWO
+ self.group_list.append(self.namespace)
else:
self.commands['alpha'] = FAKE_CMD_ALPHA
self.commands['beta'] = FAKE_CMD_BETA
+ self.group_list.append(group)
class TestCommandManager(utils.TestCase):
@@ -69,3 +71,18 @@ class TestCommandManager(utils.TestCase):
# Ensure that the original commands were not overwritten
cmd_two, name, args = mgr.find_command(['two'])
self.assertEqual(cmd_two, FAKE_CMD_TWO)
+
+ def test_get_command_groups(self):
+ mgr = FakeCommandManager('test')
+
+ # Make sure add_command() still functions
+ mock_cmd_one = mock.Mock()
+ mgr.add_command('mock', mock_cmd_one)
+ cmd_mock, name, args = mgr.find_command(['mock'])
+ self.assertEqual(cmd_mock, mock_cmd_one)
+
+ # Load another command group
+ mgr.add_command_group('latin')
+
+ gl = mgr.get_command_groups()
+ self.assertEqual(['test', 'latin'], gl)
diff --git a/openstackclient/tests/common/test_module.py b/openstackclient/tests/common/test_module.py
new file mode 100644
index 00000000..ce1592e4
--- /dev/null
+++ b/openstackclient/tests/common/test_module.py
@@ -0,0 +1,88 @@
+# Copyright 2013 Nebula 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.
+#
+
+"""Test module module"""
+
+import mock
+
+from openstackclient.common import module as osc_module
+from openstackclient.tests import fakes
+from openstackclient.tests import utils
+
+
+# NOTE(dtroyer): module_1 must match the version list filter (not --all)
+# currently == '*client*'
+module_name_1 = 'fakeclient'
+module_version_1 = '0.1.2'
+MODULE_1 = {
+ '__version__': module_version_1,
+}
+
+module_name_2 = 'zlib'
+module_version_2 = '1.1'
+MODULE_2 = {
+ '__version__': module_version_2,
+}
+
+MODULES = {
+ module_name_1: fakes.FakeModule(module_name_1, module_version_1),
+ module_name_2: fakes.FakeModule(module_name_2, module_version_2),
+}
+
+
+@mock.patch.dict(
+ 'openstackclient.common.module.sys.modules',
+ values=MODULES,
+ clear=True,
+)
+class TestModuleList(utils.TestCommand):
+
+ def setUp(self):
+ super(TestModuleList, self).setUp()
+
+ # Get the command object to test
+ self.cmd = osc_module.ListModule(self.app, None)
+
+ def test_module_list_no_options(self):
+ arglist = []
+ verifylist = [
+ ('all', False),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ # DisplayCommandBase.take_action() returns two tuples
+ columns, data = self.cmd.take_action(parsed_args)
+
+ # Additional modules may be present, just check our additions
+ self.assertTrue(module_name_1 in columns)
+ self.assertTrue(module_version_1 in data)
+
+ def test_module_list_all(self):
+ arglist = [
+ '--all',
+ ]
+ verifylist = [
+ ('all', True),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ # DisplayCommandBase.take_action() returns two tuples
+ columns, data = self.cmd.take_action(parsed_args)
+
+ # Additional modules may be present, just check our additions
+ self.assertTrue(module_name_1 in columns)
+ self.assertTrue(module_name_2 in columns)
+ self.assertTrue(module_version_1 in data)
+ self.assertTrue(module_version_2 in data)
diff --git a/openstackclient/tests/common/test_restapi.py b/openstackclient/tests/common/test_restapi.py
index 4b83ffa4..c1e02fcb 100644
--- a/openstackclient/tests/common/test_restapi.py
+++ b/openstackclient/tests/common/test_restapi.py
@@ -23,6 +23,8 @@ import requests
from openstackclient.common import restapi
from openstackclient.tests import utils
+fake_user_agent = 'test_rapi'
+
fake_auth = '11223344556677889900'
fake_url = 'http://gopher.com'
fake_key = 'gopher'
@@ -47,6 +49,9 @@ fake_gopher_list = {
fake_gopher_tosh,
]
}
+fake_headers = {
+ 'User-Agent': fake_user_agent,
+}
class FakeResponse(requests.Response):
@@ -68,11 +73,15 @@ class TestRESTApi(utils.TestCase):
request=mock.MagicMock(return_value=resp),
)
- api = restapi.RESTApi()
+ api = restapi.RESTApi(
+ user_agent=fake_user_agent,
+ )
gopher = api.request('GET', fake_url)
session_mock.return_value.request.assert_called_with(
'GET',
fake_url,
+ headers={},
+ allow_redirects=True,
)
self.assertEqual(gopher.status_code, 200)
self.assertEqual(gopher.json(), fake_gopher_single)
@@ -83,11 +92,15 @@ class TestRESTApi(utils.TestCase):
request=mock.MagicMock(return_value=resp),
)
- api = restapi.RESTApi()
+ api = restapi.RESTApi(
+ user_agent=fake_user_agent,
+ )
gopher = api.request('GET', fake_url)
session_mock.return_value.request.assert_called_with(
'GET',
fake_url,
+ headers={},
+ allow_redirects=True,
)
self.assertEqual(gopher.status_code, 300)
self.assertEqual(gopher.json(), fake_gopher_single)
@@ -98,11 +111,15 @@ class TestRESTApi(utils.TestCase):
request=mock.MagicMock(return_value=resp),
)
- api = restapi.RESTApi()
+ api = restapi.RESTApi(
+ user_agent=fake_user_agent,
+ )
self.assertRaises(requests.HTTPError, api.request, 'GET', fake_url)
session_mock.return_value.request.assert_called_with(
'GET',
fake_url,
+ headers={},
+ allow_redirects=True,
)
def test_request_get_auth(self, session_mock):
@@ -112,66 +129,67 @@ class TestRESTApi(utils.TestCase):
headers=mock.MagicMock(return_value={}),
)
- api = restapi.RESTApi(os_auth=fake_auth)
- gopher = api.request('GET', fake_url)
- session_mock.return_value.headers.setdefault.assert_called_with(
- 'X-Auth-Token',
- fake_auth,
+ api = restapi.RESTApi(
+ auth_header=fake_auth,
+ user_agent=fake_user_agent,
)
+ gopher = api.request('GET', fake_url)
+ #session_mock.return_value.headers.setdefault.assert_called_with(
+ # 'X-Auth-Token',
+ # fake_auth,
+ #)
session_mock.return_value.request.assert_called_with(
'GET',
fake_url,
+ headers={
+ 'X-Auth-Token': fake_auth,
+ },
+ allow_redirects=True,
)
self.assertEqual(gopher.json(), fake_gopher_single)
- def test_request_get_header(self, session_mock):
+ def test_request_post(self, session_mock):
resp = FakeResponse(data=fake_gopher_single)
session_mock.return_value = mock.MagicMock(
request=mock.MagicMock(return_value=resp),
- headers=mock.MagicMock(return_value={}),
)
- api = restapi.RESTApi(user_agent='fake_agent')
- api.set_header('X-Fake-Header', 'wb')
- gopher = api.request('GET', fake_url)
- session_mock.return_value.headers.__setitem__.assert_any_call(
- 'Content-Type',
- 'application/json',
- )
- session_mock.return_value.headers.__setitem__.assert_any_call(
- 'User-Agent',
- 'fake_agent',
- )
- session_mock.return_value.headers.__setitem__.assert_any_call(
- 'X-Fake-Header',
- 'wb',
+ api = restapi.RESTApi(
+ user_agent=fake_user_agent,
)
+ data = fake_gopher_tosh
+ gopher = api.request('POST', fake_url, json=data)
session_mock.return_value.request.assert_called_with(
- 'GET',
+ 'POST',
fake_url,
+ headers={
+ 'Content-Type': 'application/json',
+ },
+ allow_redirects=True,
+ data=json.dumps(data),
)
self.assertEqual(gopher.json(), fake_gopher_single)
- api.set_header('X-Fake-Header', None)
- session_mock.return_value.headers.__delitem__.assert_any_call(
- 'X-Fake-Header',
- )
+ # Methods
+ # TODO(dtroyer): add the other method methods
- def test_request_post(self, session_mock):
- resp = FakeResponse(data=fake_gopher_single)
+ def test_delete(self, session_mock):
+ resp = FakeResponse(status_code=200, data=None)
session_mock.return_value = mock.MagicMock(
request=mock.MagicMock(return_value=resp),
)
api = restapi.RESTApi()
- data = fake_gopher_tosh
- gopher = api.request('POST', fake_url, data=data)
+ gopher = api.delete(fake_url)
session_mock.return_value.request.assert_called_with(
- 'POST',
+ 'DELETE',
fake_url,
- data=json.dumps(data),
+ headers=mock.ANY,
+ allow_redirects=True,
)
- self.assertEqual(gopher.json(), fake_gopher_single)
+ self.assertEqual(gopher.status_code, 200)
+
+ # Commands
def test_create(self, session_mock):
resp = FakeResponse(data=fake_gopher_single)
@@ -187,6 +205,8 @@ class TestRESTApi(utils.TestCase):
session_mock.return_value.request.assert_called_with(
'POST',
fake_url,
+ headers=mock.ANY,
+ allow_redirects=True,
data=json.dumps(data),
)
self.assertEqual(gopher, fake_gopher_single)
@@ -196,24 +216,12 @@ class TestRESTApi(utils.TestCase):
session_mock.return_value.request.assert_called_with(
'POST',
fake_url,
+ headers=mock.ANY,
+ allow_redirects=True,
data=json.dumps(data),
)
self.assertEqual(gopher, fake_gopher_mac)
- def test_delete(self, session_mock):
- resp = FakeResponse(data=None)
- session_mock.return_value = mock.MagicMock(
- request=mock.MagicMock(return_value=resp),
- )
-
- api = restapi.RESTApi()
- gopher = api.delete(fake_url)
- session_mock.return_value.request.assert_called_with(
- 'DELETE',
- fake_url,
- )
- self.assertEqual(gopher, None)
-
def test_list(self, session_mock):
resp = FakeResponse(data=fake_gopher_list)
session_mock.return_value = mock.MagicMock(
@@ -226,6 +234,7 @@ class TestRESTApi(utils.TestCase):
session_mock.return_value.request.assert_called_with(
'GET',
fake_url,
+ headers=mock.ANY,
allow_redirects=True,
)
self.assertEqual(gopher, [fake_gopher_mac, fake_gopher_tosh])
@@ -237,6 +246,8 @@ class TestRESTApi(utils.TestCase):
session_mock.return_value.request.assert_called_with(
'POST',
fake_url,
+ headers=mock.ANY,
+ allow_redirects=True,
data=json.dumps(data),
)
self.assertEqual(gopher, [fake_gopher_mac, fake_gopher_tosh])
@@ -248,6 +259,7 @@ class TestRESTApi(utils.TestCase):
session_mock.return_value.request.assert_called_with(
'GET',
fake_url,
+ headers=mock.ANY,
allow_redirects=True,
params=params,
)
@@ -270,7 +282,9 @@ class TestRESTApi(utils.TestCase):
session_mock.return_value.request.assert_called_with(
'PUT',
fake_url,
- data=None,
+ headers=mock.ANY,
+ allow_redirects=True,
+ json=None,
)
self.assertEqual(gopher, None)
@@ -279,6 +293,8 @@ class TestRESTApi(utils.TestCase):
session_mock.return_value.request.assert_called_with(
'PUT',
fake_url,
+ headers=mock.ANY,
+ allow_redirects=True,
data=json.dumps(data),
)
self.assertEqual(gopher, fake_gopher_single)
@@ -291,6 +307,8 @@ class TestRESTApi(utils.TestCase):
session_mock.return_value.request.assert_called_with(
'PUT',
fake_url,
+ headers=mock.ANY,
+ allow_redirects=True,
data=json.dumps(data),
)
self.assertEqual(gopher, fake_gopher_mac)
@@ -308,6 +326,8 @@ class TestRESTApi(utils.TestCase):
session_mock.return_value.request.assert_called_with(
'GET',
fake_url,
+ headers=mock.ANY,
+ allow_redirects=True,
)
self.assertEqual(gopher, fake_gopher_single)
@@ -316,5 +336,7 @@ class TestRESTApi(utils.TestCase):
session_mock.return_value.request.assert_called_with(
'GET',
fake_url,
+ headers=mock.ANY,
+ allow_redirects=True,
)
self.assertEqual(gopher, fake_gopher_mac)
diff --git a/openstackclient/tests/fakes.py b/openstackclient/tests/fakes.py
index bb89f762..01214243 100644
--- a/openstackclient/tests/fakes.py
+++ b/openstackclient/tests/fakes.py
@@ -54,6 +54,12 @@ class FakeClientManager(object):
self.auth_ref = None
+class FakeModule(object):
+ def __init__(self, name, version):
+ self.name = name
+ self.__version__ = version
+
+
class FakeResource(object):
def __init__(self, manager, info, loaded=False):
self.manager = manager
diff --git a/openstackclient/tests/identity/v2_0/fakes.py b/openstackclient/tests/identity/v2_0/fakes.py
index 80febd29..231fa1a5 100644
--- a/openstackclient/tests/identity/v2_0/fakes.py
+++ b/openstackclient/tests/identity/v2_0/fakes.py
@@ -70,11 +70,22 @@ USER = {
'enabled': True,
}
+token_expires = '2014-01-01T00:00:00Z'
+token_id = 'tttttttt-tttt-tttt-tttt-tttttttttttt'
+
+TOKEN = {
+ 'expires': token_expires,
+ 'id': token_id,
+ 'tenant_id': project_id,
+ 'user_id': user_id,
+}
+
class FakeIdentityv2Client(object):
def __init__(self, **kwargs):
self.roles = mock.Mock()
self.roles.resource_class = fakes.FakeResource(None, {})
+ self.service_catalog = mock.Mock()
self.services = mock.Mock()
self.services.resource_class = fakes.FakeResource(None, {})
self.tenants = mock.Mock()
diff --git a/openstackclient/tests/identity/v2_0/test_token.py b/openstackclient/tests/identity/v2_0/test_token.py
new file mode 100644
index 00000000..a156cdc6
--- /dev/null
+++ b/openstackclient/tests/identity/v2_0/test_token.py
@@ -0,0 +1,56 @@
+# Copyright 2014 eBay 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.identity.v2_0 import token
+from openstackclient.tests.identity.v2_0 import fakes as identity_fakes
+
+
+class TestToken(identity_fakes.TestIdentityv2):
+
+ def setUp(self):
+ super(TestToken, self).setUp()
+
+ # Get a shortcut to the Service Catalog Mock
+ self.sc_mock = self.app.client_manager.identity.service_catalog
+ self.sc_mock.reset_mock()
+
+
+class TestTokenCreate(TestToken):
+
+ def setUp(self):
+ super(TestTokenCreate, self).setUp()
+
+ self.sc_mock.get_token.return_value = identity_fakes.TOKEN
+ self.cmd = token.CreateToken(self.app, None)
+
+ def test_token_create(self):
+ arglist = []
+ verifylist = []
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ # DisplayCommandBase.take_action() returns two tuples
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.sc_mock.get_token.assert_called_with()
+
+ collist = ('expires', 'id', 'project_id', 'user_id')
+ self.assertEqual(columns, collist)
+ datalist = (
+ identity_fakes.token_expires,
+ identity_fakes.token_id,
+ identity_fakes.project_id,
+ identity_fakes.user_id,
+ )
+ self.assertEqual(data, datalist)
diff --git a/openstackclient/tests/object/v1/lib/test_container.py b/openstackclient/tests/object/v1/lib/test_container.py
index c3fdea72..f7355592 100644
--- a/openstackclient/tests/object/v1/lib/test_container.py
+++ b/openstackclient/tests/object/v1/lib/test_container.py
@@ -46,7 +46,7 @@ class TestContainerList(TestContainer):
def test_container_list_no_options(self):
resp = [{'name': 'is-name'}]
- self.app.restapi.request.return_value = restapi.FakeResponse(data=resp)
+ self.app.restapi.list.return_value = resp
data = lib_container.list_containers(
self.app.restapi,
@@ -54,15 +54,17 @@ class TestContainerList(TestContainer):
)
# Check expected values
- self.app.restapi.request.assert_called_with(
- 'GET',
- fake_url + '?format=json',
+ self.app.restapi.list.assert_called_with(
+ fake_url,
+ params={
+ 'format': 'json',
+ }
)
self.assertEqual(data, resp)
def test_container_list_marker(self):
resp = [{'name': 'is-name'}]
- self.app.restapi.request.return_value = restapi.FakeResponse(data=resp)
+ self.app.restapi.list.return_value = resp
data = lib_container.list_containers(
self.app.restapi,
@@ -71,15 +73,18 @@ class TestContainerList(TestContainer):
)
# Check expected values
- self.app.restapi.request.assert_called_with(
- 'GET',
- fake_url + '?format=json&marker=next',
+ self.app.restapi.list.assert_called_with(
+ fake_url,
+ params={
+ 'format': 'json',
+ 'marker': 'next',
+ }
)
self.assertEqual(data, resp)
def test_container_list_limit(self):
resp = [{'name': 'is-name'}]
- self.app.restapi.request.return_value = restapi.FakeResponse(data=resp)
+ self.app.restapi.list.return_value = resp
data = lib_container.list_containers(
self.app.restapi,
@@ -88,15 +93,18 @@ class TestContainerList(TestContainer):
)
# Check expected values
- self.app.restapi.request.assert_called_with(
- 'GET',
- fake_url + '?format=json&limit=5',
+ self.app.restapi.list.assert_called_with(
+ fake_url,
+ params={
+ 'format': 'json',
+ 'limit': 5,
+ }
)
self.assertEqual(data, resp)
def test_container_list_end_marker(self):
resp = [{'name': 'is-name'}]
- self.app.restapi.request.return_value = restapi.FakeResponse(data=resp)
+ self.app.restapi.list.return_value = resp
data = lib_container.list_containers(
self.app.restapi,
@@ -105,15 +113,18 @@ class TestContainerList(TestContainer):
)
# Check expected values
- self.app.restapi.request.assert_called_with(
- 'GET',
- fake_url + '?format=json&end_marker=last',
+ self.app.restapi.list.assert_called_with(
+ fake_url,
+ params={
+ 'format': 'json',
+ 'end_marker': 'last',
+ }
)
self.assertEqual(data, resp)
def test_container_list_prefix(self):
resp = [{'name': 'is-name'}]
- self.app.restapi.request.return_value = restapi.FakeResponse(data=resp)
+ self.app.restapi.list.return_value = resp
data = lib_container.list_containers(
self.app.restapi,
@@ -122,25 +133,26 @@ class TestContainerList(TestContainer):
)
# Check expected values
- self.app.restapi.request.assert_called_with(
- 'GET',
- fake_url + '?format=json&prefix=foo/',
+ self.app.restapi.list.assert_called_with(
+ fake_url,
+ params={
+ 'format': 'json',
+ 'prefix': 'foo/',
+ }
)
self.assertEqual(data, resp)
def test_container_list_full_listing(self):
def side_effect(*args, **kwargs):
- rv = self.app.restapi.request.return_value
- self.app.restapi.request.return_value = restapi.FakeResponse(
- data=[],
- )
- self.app.restapi.request.side_effect = None
+ rv = self.app.restapi.list.return_value
+ self.app.restapi.list.return_value = []
+ self.app.restapi.list.side_effect = None
return rv
resp = [{'name': 'is-name'}]
- self.app.restapi.request.return_value = restapi.FakeResponse(data=resp)
- self.app.restapi.request.side_effect = side_effect
+ self.app.restapi.list.return_value = resp
+ self.app.restapi.list.side_effect = side_effect
data = lib_container.list_containers(
self.app.restapi,
@@ -149,9 +161,12 @@ class TestContainerList(TestContainer):
)
# Check expected values
- self.app.restapi.request.assert_called_with(
- 'GET',
- fake_url + '?format=json&marker=is-name',
+ self.app.restapi.list.assert_called_with(
+ fake_url,
+ params={
+ 'format': 'json',
+ 'marker': 'is-name',
+ }
)
self.assertEqual(data, resp)
@@ -163,7 +178,7 @@ class TestContainerShow(TestContainer):
'x-container-object-count': 1,
'x-container-bytes-used': 577,
}
- self.app.restapi.request.return_value = \
+ self.app.restapi.head.return_value = \
restapi.FakeResponse(headers=resp)
data = lib_container.show_container(
@@ -173,8 +188,7 @@ class TestContainerShow(TestContainer):
)
# Check expected values
- self.app.restapi.request.assert_called_with(
- 'HEAD',
+ self.app.restapi.head.assert_called_with(
fake_url + '/is-name',
)
diff --git a/openstackclient/tests/object/v1/lib/test_object.py b/openstackclient/tests/object/v1/lib/test_object.py
index ef93877a..064efb53 100644
--- a/openstackclient/tests/object/v1/lib/test_object.py
+++ b/openstackclient/tests/object/v1/lib/test_object.py
@@ -47,7 +47,7 @@ class TestObjectListObjects(TestObject):
def test_list_objects_no_options(self):
resp = [{'name': 'is-name'}]
- self.app.restapi.request.return_value = restapi.FakeResponse(data=resp)
+ self.app.restapi.list.return_value = resp
data = lib_object.list_objects(
self.app.restapi,
@@ -56,15 +56,17 @@ class TestObjectListObjects(TestObject):
)
# Check expected values
- self.app.restapi.request.assert_called_with(
- 'GET',
- fake_url + '/' + fake_container + '?format=json',
+ self.app.restapi.list.assert_called_with(
+ fake_url + '/' + fake_container,
+ params={
+ 'format': 'json',
+ }
)
self.assertEqual(data, resp)
def test_list_objects_marker(self):
resp = [{'name': 'is-name'}]
- self.app.restapi.request.return_value = restapi.FakeResponse(data=resp)
+ self.app.restapi.list.return_value = resp
data = lib_object.list_objects(
self.app.restapi,
@@ -74,15 +76,18 @@ class TestObjectListObjects(TestObject):
)
# Check expected values
- self.app.restapi.request.assert_called_with(
- 'GET',
- fake_url + '/' + fake_container + '?format=json&marker=next',
+ self.app.restapi.list.assert_called_with(
+ fake_url + '/' + fake_container,
+ params={
+ 'format': 'json',
+ 'marker': 'next',
+ }
)
self.assertEqual(data, resp)
def test_list_objects_limit(self):
resp = [{'name': 'is-name'}]
- self.app.restapi.request.return_value = restapi.FakeResponse(data=resp)
+ self.app.restapi.list.return_value = resp
data = lib_object.list_objects(
self.app.restapi,
@@ -92,15 +97,18 @@ class TestObjectListObjects(TestObject):
)
# Check expected values
- self.app.restapi.request.assert_called_with(
- 'GET',
- fake_url + '/' + fake_container + '?format=json&limit=5',
+ self.app.restapi.list.assert_called_with(
+ fake_url + '/' + fake_container,
+ params={
+ 'format': 'json',
+ 'limit': 5,
+ }
)
self.assertEqual(data, resp)
def test_list_objects_end_marker(self):
resp = [{'name': 'is-name'}]
- self.app.restapi.request.return_value = restapi.FakeResponse(data=resp)
+ self.app.restapi.list.return_value = resp
data = lib_object.list_objects(
self.app.restapi,
@@ -110,15 +118,18 @@ class TestObjectListObjects(TestObject):
)
# Check expected values
- self.app.restapi.request.assert_called_with(
- 'GET',
- fake_url + '/' + fake_container + '?format=json&end_marker=last',
+ self.app.restapi.list.assert_called_with(
+ fake_url + '/' + fake_container,
+ params={
+ 'format': 'json',
+ 'end_marker': 'last',
+ }
)
self.assertEqual(data, resp)
def test_list_objects_delimiter(self):
resp = [{'name': 'is-name'}]
- self.app.restapi.request.return_value = restapi.FakeResponse(data=resp)
+ self.app.restapi.list.return_value = resp
data = lib_object.list_objects(
self.app.restapi,
@@ -131,15 +142,18 @@ class TestObjectListObjects(TestObject):
# NOTE(dtroyer): requests handles the URL encoding and we're
# mocking that so use the otherwise-not-legal
# pipe '|' char in the response.
- self.app.restapi.request.assert_called_with(
- 'GET',
- fake_url + '/' + fake_container + '?format=json&delimiter=|',
+ self.app.restapi.list.assert_called_with(
+ fake_url + '/' + fake_container,
+ params={
+ 'format': 'json',
+ 'delimiter': '|',
+ }
)
self.assertEqual(data, resp)
def test_list_objects_prefix(self):
resp = [{'name': 'is-name'}]
- self.app.restapi.request.return_value = restapi.FakeResponse(data=resp)
+ self.app.restapi.list.return_value = resp
data = lib_object.list_objects(
self.app.restapi,
@@ -149,15 +163,18 @@ class TestObjectListObjects(TestObject):
)
# Check expected values
- self.app.restapi.request.assert_called_with(
- 'GET',
- fake_url + '/' + fake_container + '?format=json&prefix=foo/',
+ self.app.restapi.list.assert_called_with(
+ fake_url + '/' + fake_container,
+ params={
+ 'format': 'json',
+ 'prefix': 'foo/',
+ }
)
self.assertEqual(data, resp)
def test_list_objects_path(self):
resp = [{'name': 'is-name'}]
- self.app.restapi.request.return_value = restapi.FakeResponse(data=resp)
+ self.app.restapi.list.return_value = resp
data = lib_object.list_objects(
self.app.restapi,
@@ -167,25 +184,26 @@ class TestObjectListObjects(TestObject):
)
# Check expected values
- self.app.restapi.request.assert_called_with(
- 'GET',
- fake_url + '/' + fake_container + '?format=json&path=next',
+ self.app.restapi.list.assert_called_with(
+ fake_url + '/' + fake_container,
+ params={
+ 'format': 'json',
+ 'path': 'next',
+ }
)
self.assertEqual(data, resp)
def test_list_objects_full_listing(self):
def side_effect(*args, **kwargs):
- rv = self.app.restapi.request.return_value
- self.app.restapi.request.return_value = restapi.FakeResponse(
- data=[],
- )
- self.app.restapi.request.side_effect = None
+ rv = self.app.restapi.list.return_value
+ self.app.restapi.list.return_value = []
+ self.app.restapi.list.side_effect = None
return rv
resp = [{'name': 'is-name'}]
- self.app.restapi.request.return_value = restapi.FakeResponse(data=resp)
- self.app.restapi.request.side_effect = side_effect
+ self.app.restapi.list.return_value = resp
+ self.app.restapi.list.side_effect = side_effect
data = lib_object.list_objects(
self.app.restapi,
@@ -195,9 +213,12 @@ class TestObjectListObjects(TestObject):
)
# Check expected values
- self.app.restapi.request.assert_called_with(
- 'GET',
- fake_url + '/' + fake_container + '?format=json&marker=is-name',
+ self.app.restapi.list.assert_called_with(
+ fake_url + '/' + fake_container,
+ params={
+ 'format': 'json',
+ 'marker': 'is-name',
+ }
)
self.assertEqual(data, resp)
@@ -208,7 +229,7 @@ class TestObjectShowObjects(TestObject):
resp = {
'content-type': 'text/alpha',
}
- self.app.restapi.request.return_value = \
+ self.app.restapi.head.return_value = \
restapi.FakeResponse(headers=resp)
data = lib_object.show_object(
@@ -219,8 +240,7 @@ class TestObjectShowObjects(TestObject):
)
# Check expected values
- self.app.restapi.request.assert_called_with(
- 'HEAD',
+ self.app.restapi.head.assert_called_with(
fake_url + '/%s/%s' % (fake_container, fake_object),
)
@@ -242,7 +262,7 @@ class TestObjectShowObjects(TestObject):
'x-object-meta-wife': 'Wilma',
'x-tra-header': 'yabba-dabba-do',
}
- self.app.restapi.request.return_value = \
+ self.app.restapi.head.return_value = \
restapi.FakeResponse(headers=resp)
data = lib_object.show_object(
@@ -253,8 +273,7 @@ class TestObjectShowObjects(TestObject):
)
# Check expected values
- self.app.restapi.request.assert_called_with(
- 'HEAD',
+ self.app.restapi.head.assert_called_with(
fake_url + '/%s/%s' % (fake_container, fake_object),
)
diff --git a/openstackclient/tests/test_shell.py b/openstackclient/tests/test_shell.py
index be9c5d49..9253f701 100644
--- a/openstackclient/tests/test_shell.py
+++ b/openstackclient/tests/test_shell.py
@@ -20,13 +20,19 @@ from openstackclient import shell
from openstackclient.tests import utils
-DEFAULT_USERNAME = "username"
-DEFAULT_PASSWORD = "password"
+DEFAULT_AUTH_URL = "http://127.0.0.1:5000/v2.0/"
DEFAULT_PROJECT_ID = "xxxx-yyyy-zzzz"
DEFAULT_PROJECT_NAME = "project"
-DEFAULT_TOKEN = "token"
+DEFAULT_DOMAIN_ID = "aaaa-bbbb-cccc"
+DEFAULT_DOMAIN_NAME = "domain"
+DEFAULT_USER_DOMAIN_ID = "aaaa-bbbb-cccc"
+DEFAULT_USER_DOMAIN_NAME = "domain"
+DEFAULT_PROJECT_DOMAIN_ID = "aaaa-bbbb-cccc"
+DEFAULT_PROJECT_DOMAIN_NAME = "domain"
+DEFAULT_USERNAME = "username"
+DEFAULT_PASSWORD = "password"
DEFAULT_REGION_NAME = "ZZ9_Plural_Z_Alpha"
-DEFAULT_AUTH_URL = "http://127.0.0.1:5000/v2.0/"
+DEFAULT_TOKEN = "token"
DEFAULT_SERVICE_URL = "http://127.0.0.1:8771/v3.0/"
DEFAULT_COMPUTE_API_VERSION = "2"
@@ -78,6 +84,18 @@ class TestShell(utils.TestCase):
default_args["project_id"])
self.assertEqual(_shell.options.os_project_name,
default_args["project_name"])
+ self.assertEqual(_shell.options.os_domain_id,
+ default_args["domain_id"])
+ self.assertEqual(_shell.options.os_domain_name,
+ default_args["domain_name"])
+ self.assertEqual(_shell.options.os_user_domain_id,
+ default_args["user_domain_id"])
+ self.assertEqual(_shell.options.os_user_domain_name,
+ default_args["user_domain_name"])
+ self.assertEqual(_shell.options.os_project_domain_id,
+ default_args["project_domain_id"])
+ self.assertEqual(_shell.options.os_project_domain_name,
+ default_args["project_domain_name"])
self.assertEqual(_shell.options.os_username,
default_args["username"])
self.assertEqual(_shell.options.os_password,
@@ -151,6 +169,12 @@ class TestShellPasswordAuth(TestShell):
"auth_url": DEFAULT_AUTH_URL,
"project_id": "",
"project_name": "",
+ "domain_id": "",
+ "domain_name": "",
+ "user_domain_id": "",
+ "user_domain_name": "",
+ "project_domain_id": "",
+ "project_domain_name": "",
"username": "",
"password": "",
"region_name": ""
@@ -163,6 +187,12 @@ class TestShellPasswordAuth(TestShell):
"auth_url": "",
"project_id": DEFAULT_PROJECT_ID,
"project_name": "",
+ "domain_id": "",
+ "domain_name": "",
+ "user_domain_id": "",
+ "user_domain_name": "",
+ "project_domain_id": "",
+ "project_domain_name": "",
"username": "",
"password": "",
"region_name": ""
@@ -175,6 +205,12 @@ class TestShellPasswordAuth(TestShell):
"auth_url": "",
"project_id": "",
"project_name": DEFAULT_PROJECT_NAME,
+ "domain_id": "",
+ "domain_name": "",
+ "user_domain_id": "",
+ "user_domain_name": "",
+ "project_domain_id": "",
+ "project_domain_name": "",
"username": "",
"password": "",
"region_name": ""
@@ -187,6 +223,12 @@ class TestShellPasswordAuth(TestShell):
"auth_url": "",
"project_id": DEFAULT_PROJECT_ID,
"project_name": "",
+ "domain_id": "",
+ "domain_name": "",
+ "user_domain_id": "",
+ "user_domain_name": "",
+ "project_domain_id": "",
+ "project_domain_name": "",
"username": "",
"password": "",
"region_name": ""
@@ -199,6 +241,120 @@ class TestShellPasswordAuth(TestShell):
"auth_url": "",
"project_id": "",
"project_name": DEFAULT_PROJECT_NAME,
+ "domain_id": "",
+ "domain_name": "",
+ "user_domain_id": "",
+ "user_domain_name": "",
+ "project_domain_id": "",
+ "project_domain_name": "",
+ "username": "",
+ "password": "",
+ "region_name": ""
+ }
+ self._assert_password_auth(flag, kwargs)
+
+ def test_only_domain_id_flow(self):
+ flag = "--os-domain-id " + DEFAULT_DOMAIN_ID
+ kwargs = {
+ "auth_url": "",
+ "project_id": "",
+ "project_name": "",
+ "domain_id": DEFAULT_DOMAIN_ID,
+ "domain_name": "",
+ "user_domain_id": "",
+ "user_domain_name": "",
+ "project_domain_id": "",
+ "project_domain_name": "",
+ "username": "",
+ "password": "",
+ "region_name": ""
+ }
+ self._assert_password_auth(flag, kwargs)
+
+ def test_only_domain_name_flow(self):
+ flag = "--os-domain-name " + DEFAULT_DOMAIN_NAME
+ kwargs = {
+ "auth_url": "",
+ "project_id": "",
+ "project_name": "",
+ "domain_id": "",
+ "domain_name": DEFAULT_DOMAIN_NAME,
+ "user_domain_id": "",
+ "user_domain_name": "",
+ "project_domain_id": "",
+ "project_domain_name": "",
+ "username": "",
+ "password": "",
+ "region_name": ""
+ }
+ self._assert_password_auth(flag, kwargs)
+
+ def test_only_user_domain_id_flow(self):
+ flag = "--os-user-domain-id " + DEFAULT_USER_DOMAIN_ID
+ kwargs = {
+ "auth_url": "",
+ "project_id": "",
+ "project_name": "",
+ "domain_id": "",
+ "domain_name": "",
+ "user_domain_id": DEFAULT_USER_DOMAIN_ID,
+ "user_domain_name": "",
+ "project_domain_id": "",
+ "project_domain_name": "",
+ "username": "",
+ "password": "",
+ "region_name": ""
+ }
+ self._assert_password_auth(flag, kwargs)
+
+ def test_only_user_domain_name_flow(self):
+ flag = "--os-user-domain-name " + DEFAULT_USER_DOMAIN_NAME
+ kwargs = {
+ "auth_url": "",
+ "project_id": "",
+ "project_name": "",
+ "domain_id": "",
+ "domain_name": "",
+ "user_domain_id": "",
+ "user_domain_name": DEFAULT_USER_DOMAIN_NAME,
+ "project_domain_id": "",
+ "project_domain_name": "",
+ "username": "",
+ "password": "",
+ "region_name": ""
+ }
+ self._assert_password_auth(flag, kwargs)
+
+ def test_only_project_domain_id_flow(self):
+ flag = "--os-project-domain-id " + DEFAULT_PROJECT_DOMAIN_ID
+ kwargs = {
+ "auth_url": "",
+ "project_id": "",
+ "project_name": "",
+ "domain_id": "",
+ "domain_name": "",
+ "user_domain_id": "",
+ "user_domain_name": "",
+ "project_domain_id": DEFAULT_PROJECT_DOMAIN_ID,
+ "project_domain_name": "",
+ "username": "",
+ "password": "",
+ "region_name": ""
+ }
+ self._assert_password_auth(flag, kwargs)
+
+ def test_only_project_domain_name_flow(self):
+ flag = "--os-project-domain-name " + DEFAULT_PROJECT_DOMAIN_NAME
+ kwargs = {
+ "auth_url": "",
+ "project_id": "",
+ "project_name": "",
+ "domain_id": "",
+ "domain_name": "",
+ "user_domain_id": "",
+ "user_domain_name": "",
+ "project_domain_id": "",
+ "project_domain_name": DEFAULT_PROJECT_DOMAIN_NAME,
"username": "",
"password": "",
"region_name": ""
@@ -211,6 +367,12 @@ class TestShellPasswordAuth(TestShell):
"auth_url": "",
"project_id": "",
"project_name": "",
+ "domain_id": "",
+ "domain_name": "",
+ "user_domain_id": "",
+ "user_domain_name": "",
+ "project_domain_id": "",
+ "project_domain_name": "",
"username": DEFAULT_USERNAME,
"password": "",
"region_name": ""
@@ -223,6 +385,12 @@ class TestShellPasswordAuth(TestShell):
"auth_url": "",
"project_id": "",
"project_name": "",
+ "domain_id": "",
+ "domain_name": "",
+ "user_domain_id": "",
+ "user_domain_name": "",
+ "project_domain_id": "",
+ "project_domain_name": "",
"username": "",
"password": DEFAULT_PASSWORD,
"region_name": ""
@@ -235,6 +403,12 @@ class TestShellPasswordAuth(TestShell):
"auth_url": "",
"project_id": "",
"project_name": "",
+ "domain_id": "",
+ "domain_name": "",
+ "user_domain_id": "",
+ "user_domain_name": "",
+ "project_domain_id": "",
+ "project_domain_name": "",
"username": "",
"password": "",
"region_name": DEFAULT_REGION_NAME
diff --git a/openstackclient/volume/client.py b/openstackclient/volume/client.py
index e04e8cd7..a53203f1 100644
--- a/openstackclient/volume/client.py
+++ b/openstackclient/volume/client.py
@@ -37,6 +37,10 @@ def make_client(instance):
)
LOG.debug('instantiating volume client')
+
+ # Set client http_log_debug to True if verbosity level is high enough
+ http_log_debug = utils.get_effective_log_level() <= logging.DEBUG
+
client = volume_client(
username=instance._username,
api_key=instance._password,
@@ -44,6 +48,7 @@ def make_client(instance):
auth_url=instance._auth_url,
cacert=instance._cacert,
insecure=instance._insecure,
+ http_log_debug=http_log_debug
)
return client