diff options
Diffstat (limited to 'openstackclient')
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 |
