From 6c269efda87185a1bbecf22c62dead1cd78132af Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Wed, 8 Jun 2016 14:33:39 -0500 Subject: [PATCH] Use osc-lib and set up deprecation warnings The initial use of osc-lib is behind the compatibility/deprecation modules that we will leave in place for a time for plugins to catch up. * openstackclient.common.exceptions * openstackclient.common.utils Module-level warnings are emitted directly on stderr since logging has not been configured yet. Change-Id: I79e57ce9523a20366bccaf9b949ab5906792ea0d --- openstackclient/common/exceptions.py | 112 +------ openstackclient/common/utils.py | 428 +-------------------------- requirements.txt | 1 + 3 files changed, 19 insertions(+), 522 deletions(-) diff --git a/openstackclient/common/exceptions.py b/openstackclient/common/exceptions.py index bdc33ddb54..7124074c99 100644 --- a/openstackclient/common/exceptions.py +++ b/openstackclient/common/exceptions.py @@ -1,5 +1,3 @@ -# Copyright 2012-2013 OpenStack, LLC. -# # 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 @@ -13,105 +11,15 @@ # under the License. # -"""Exception definitions.""" +# NOTE(dtroyer): This file is deprecated in Jun 2016, remove after 4.x release +# or Jun 2017. + +import sys + +from osc_lib.exceptions import * # noqa -class CommandError(Exception): - pass - - -class AuthorizationFailure(Exception): - pass - - -class PluginAttributeError(Exception): - """A plugin threw an AttributeError while being lazily loaded.""" - # This *must not* inherit from AttributeError; - # that would defeat the whole purpose. - pass - - -class NoTokenLookupException(Exception): - """This does not support looking up endpoints from an existing token.""" - pass - - -class EndpointNotFound(Exception): - """Could not find Service or Region in Service Catalog.""" - pass - - -class UnsupportedVersion(Exception): - """The user is trying to use an unsupported version of the API""" - pass - - -class ClientException(Exception): - """The base exception class for all exceptions this library raises.""" - - def __init__(self, code, message=None, details=None): - self.code = code - self.message = message or self.__class__.message - self.details = details - - def __str__(self): - return "%s (HTTP %s)" % (self.message, self.code) - - -class BadRequest(ClientException): - """HTTP 400 - Bad request: you sent some malformed data.""" - http_status = 400 - message = "Bad request" - - -class Unauthorized(ClientException): - """HTTP 401 - Unauthorized: bad credentials.""" - http_status = 401 - message = "Unauthorized" - - -class Forbidden(ClientException): - """HTTP 403 - Forbidden: not authorized to access to this resource.""" - http_status = 403 - message = "Forbidden" - - -class NotFound(ClientException): - """HTTP 404 - Not found""" - http_status = 404 - message = "Not found" - - -class Conflict(ClientException): - """HTTP 409 - Conflict""" - http_status = 409 - message = "Conflict" - - -class OverLimit(ClientException): - """HTTP 413 - Over limit: reached the API limits for this time period.""" - http_status = 413 - message = "Over limit" - - -# NotImplemented is a python keyword. -class HTTPNotImplemented(ClientException): - """HTTP 501 - Not Implemented: server does not support this operation.""" - http_status = 501 - message = "Not Implemented" - - -# In Python 2.4 Exception is old-style and thus doesn't have a __subclasses__() -# so we can do this: -# _code_map = dict((c.http_status, c) -# for c in ClientException.__subclasses__()) -# -# Instead, we have to hardcode it: -_code_map = dict((c.http_status, c) for c in [ - BadRequest, - Unauthorized, - Forbidden, - NotFound, - OverLimit, - HTTPNotImplemented -]) +sys.stderr.write( + "WARNING: %s is deprecated and will be removed after Jun 2017. " + "Please use osc_lib.exceptions\n" % __name__ +) diff --git a/openstackclient/common/utils.py b/openstackclient/common/utils.py index 5e058547b4..73cd3dc9d3 100644 --- a/openstackclient/common/utils.py +++ b/openstackclient/common/utils.py @@ -1,5 +1,3 @@ -# 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 # a copy of the License at @@ -13,425 +11,15 @@ # under the License. # -"""Common client utilities""" +# NOTE(dtroyer): This file is deprecated in Jun 2016, remove after 4.x release +# or Jun 2017. -import getpass -import logging -import os -import six -import time +import sys -from oslo_utils import importutils +from osc_lib.utils import * # noqa -from openstackclient.common import exceptions -from openstackclient.i18n import _ - -def find_resource(manager, name_or_id, **kwargs): - """Helper for the _find_* methods. - - :param manager: A client manager class - :param name_or_id: The resource we are trying to find - :param kwargs: To be used in calling .find() - :rtype: The found resource - - This method will attempt to find a resource in a variety of ways. - Primarily .get() methods will be called with `name_or_id` as an integer - value, and tried again as a string value. - - If both fail, then a .find() is attempted, which is essentially calling - a .list() function with a 'name' query parameter that is set to - `name_or_id`. - - Lastly, if any kwargs are passed in, they will be treated as additional - query parameters. This is particularly handy in the case of finding - resources in a domain. - - """ - - # Try to get entity as integer id - try: - if isinstance(name_or_id, int) or name_or_id.isdigit(): - return manager.get(int(name_or_id), **kwargs) - # FIXME(dtroyer): The exception to catch here is dependent on which - # client library the manager passed in belongs to. - # Eventually this should be pulled from a common set - # of client exceptions. - except Exception as ex: - if type(ex).__name__ == 'NotFound': - pass - else: - raise - - # Try directly using the passed value - try: - return manager.get(name_or_id, **kwargs) - except Exception: - pass - - if len(kwargs) == 0: - kwargs = {} - - try: - # Prepare the kwargs for calling find - if 'NAME_ATTR' in manager.resource_class.__dict__: - # novaclient does this for oddball resources - kwargs[manager.resource_class.NAME_ATTR] = name_or_id - else: - kwargs['name'] = name_or_id - except Exception: - pass - - # finally try to find entity by name - try: - return manager.find(**kwargs) - # FIXME(dtroyer): The exception to catch here is dependent on which - # client library the manager passed in belongs to. - # Eventually this should be pulled from a common set - # of client exceptions. - except Exception as ex: - if type(ex).__name__ == 'NotFound': - msg = _("No %(resource)s with a name or ID " - "of '%(name_or_id)s' exists.") - raise exceptions.CommandError( - msg % {'resource': manager.resource_class.__name__.lower(), - 'name_or_id': name_or_id} - ) - if type(ex).__name__ == 'NoUniqueMatch': - msg = _("More than one %(resource)s exists with " - "the name '%(name_or_id)s'.") - raise exceptions.CommandError( - msg % {'resource': manager.resource_class.__name__.lower(), - 'name_or_id': name_or_id} - ) - else: - pass - - for resource in manager.list(): - # short circuit and return the first match - if (resource.get('id') == name_or_id or - resource.get('name') == name_or_id): - return resource - else: - # we found no match, report back this error: - msg = _("Could not find resource %s") % name_or_id - raise exceptions.CommandError(msg) - - -def format_dict(data): - """Return a formatted string of key value pairs - - :param data: a dict - :rtype: a string formatted to key='value' - """ - - output = "" - for s in sorted(data): - output = output + s + "='" + six.text_type(data[s]) + "', " - return output[:-2] - - -def format_list(data, separator=', '): - """Return a formatted strings - - :param data: a list of strings - :param separator: the separator to use between strings (default: ', ') - :rtype: a string formatted based on separator - """ - - return separator.join(sorted(data)) - - -def format_list_of_dicts(data): - """Return a formatted string of key value pairs for each dict - - :param data: a list of dicts - :rtype: a string formatted to key='value' with dicts separated by new line - """ - - return '\n'.join(format_dict(i) for i in data) - - -def get_field(item, field): - try: - if isinstance(item, dict): - return item[field] - else: - return getattr(item, field) - except Exception: - msg = _("Resource doesn't have field %s") % field - raise exceptions.CommandError(msg) - - -def get_item_properties(item, fields, mixed_case_fields=None, formatters=None): - """Return a tuple containing the item properties. - - :param item: a single item resource (e.g. Server, Project, etc) - :param fields: tuple of strings with the desired field names - :param mixed_case_fields: tuple of field names to preserve case - :param formatters: dictionary mapping field names to callables - to format the values - """ - if mixed_case_fields is None: - mixed_case_fields = [] - if formatters is None: - formatters = {} - - row = [] - - for field in fields: - if field in mixed_case_fields: - field_name = field.replace(' ', '_') - else: - field_name = field.lower().replace(' ', '_') - data = getattr(item, field_name, '') - if field in formatters: - row.append(formatters[field](data)) - else: - row.append(data) - return tuple(row) - - -def get_dict_properties(item, fields, mixed_case_fields=None, formatters=None): - """Return a tuple containing the item properties. - - :param item: a single dict resource - :param fields: tuple of strings with the desired field names - :param mixed_case_fields: tuple of field names to preserve case - :param formatters: dictionary mapping field names to callables - to format the values - """ - if mixed_case_fields is None: - mixed_case_fields = [] - if formatters is None: - formatters = {} - - row = [] - - for field in fields: - if field in mixed_case_fields: - field_name = field.replace(' ', '_') - else: - field_name = field.lower().replace(' ', '_') - data = item[field_name] if field_name in item else '' - if field in formatters: - row.append(formatters[field](data)) - else: - row.append(data) - return tuple(row) - - -def sort_items(items, sort_str): - """Sort items based on sort keys and sort directions given by sort_str. - - :param items: a list or generator object of items - :param sort_str: a string defining the sort rules, the format is - ':[direction1],:[direction2]...', direction can be 'asc' - for ascending or 'desc' for descending, if direction is not given, - it's ascending by default - :return: sorted items - """ - if not sort_str: - return items - # items may be a generator object, transform it to a list - items = list(items) - sort_keys = sort_str.strip().split(',') - for sort_key in reversed(sort_keys): - reverse = False - if ':' in sort_key: - sort_key, direction = sort_key.split(':', 1) - if not sort_key: - msg = _("empty string is not a valid sort key") - raise exceptions.CommandError(msg) - if direction not in ['asc', 'desc']: - if not direction: - direction = "empty string" - msg = _("%(direction)s is not a valid sort direction for " - "sort key %(sort_key)s, use asc or desc instead") - raise exceptions.CommandError( - msg % {'direction': direction, - 'sort_key': sort_key} - ) - if direction == 'desc': - reverse = True - items.sort(key=lambda item: get_field(item, sort_key), - reverse=reverse) - return items - - -def env(*vars, **kwargs): - """Search for the first defined of possibly many env vars - - Returns the first environment variable defined in vars, or - returns the default defined in kwargs. - """ - for v in vars: - value = os.environ.get(v, None) - if value: - return value - return kwargs.get('default', '') - - -def get_client_class(api_name, version, version_map): - """Returns the client class for the requested API version - - :param api_name: the name of the API, e.g. 'compute', 'image', etc - :param version: the requested API version - :param version_map: a dict of client classes keyed by version - :rtype: a client class for the requested API version - """ - try: - client_path = version_map[str(version)] - except (KeyError, ValueError): - msg = _("Invalid %(api_name)s client version '%(version)s'. " - "must be one of: %(version_map)s") - raise exceptions.UnsupportedVersion( - msg % {'api_name': api_name, - 'version': version, - 'version_map': ', '.join(list(version_map.keys()))} - ) - - return importutils.import_class(client_path) - - -def wait_for_status(status_f, - res_id, - status_field='status', - success_status=['active'], - error_status=['error'], - sleep_time=5, - callback=None): - """Wait for status change on a resource during a long-running operation - - :param status_f: a status function that takes a single id argument - :param res_id: the resource id to watch - :param status_field: the status attribute in the returned resource object - :param success_status: a list of status strings for successful completion - :param error_status: a list of status strings for error - :param sleep_time: wait this long (seconds) - :param callback: called per sleep cycle, useful to display progress - :rtype: True on success - """ - while True: - res = status_f(res_id) - status = getattr(res, status_field, '').lower() - if status in success_status: - retval = True - break - elif status in error_status: - retval = False - break - if callback: - progress = getattr(res, 'progress', None) or 0 - callback(progress) - time.sleep(sleep_time) - return retval - - -def wait_for_delete(manager, - res_id, - status_field='status', - error_status=['error'], - exception_name=['NotFound'], - sleep_time=5, - timeout=300, - callback=None): - """Wait for resource deletion - - :param manager: the manager from which we can get the resource - :param res_id: the resource id to watch - :param status_field: the status attribute in the returned resource object, - this is used to check for error states while the resource is being - deleted - :param error_status: a list of status strings for error - :param exception_name: a list of exception strings for deleted case - :param sleep_time: wait this long between checks (seconds) - :param timeout: check until this long (seconds) - :param callback: called per sleep cycle, useful to display progress; this - function is passed a progress value during each iteration of the wait - loop - :rtype: True on success, False if the resource has gone to error state or - the timeout has been reached - """ - total_time = 0 - while total_time < timeout: - try: - # might not be a bad idea to re-use find_resource here if it was - # a bit more friendly in the exceptions it raised so we could just - # handle a NotFound exception here without parsing the message - res = manager.get(res_id) - except Exception as ex: - if type(ex).__name__ in exception_name: - return True - raise - - status = getattr(res, status_field, '').lower() - if status in error_status: - return False - - if callback: - progress = getattr(res, 'progress', None) or 0 - callback(progress) - time.sleep(sleep_time) - total_time += sleep_time - - # if we got this far we've timed out - return False - - -def get_effective_log_level(): - """Returns the lowest logging level considered by logging handlers - - Retrieve and 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 - - -def get_password(stdin, prompt=None, confirm=True): - message = prompt or "User Password:" - if hasattr(stdin, 'isatty') and stdin.isatty(): - try: - while True: - first_pass = getpass.getpass(message) - if not confirm: - return first_pass - second_pass = getpass.getpass("Repeat " + message) - if first_pass == second_pass: - return first_pass - print("The passwords entered were not the same") - except EOFError: # Ctl-D - raise exceptions.CommandError(_("Error reading password.")) - raise exceptions.CommandError(_("There was a request to be prompted " - "for a password and a terminal was " - "not detected.")) - - -def read_blob_file_contents(blob_file): - try: - with open(blob_file) as file: - blob = file.read().strip() - return blob - except IOError: - msg = _("Error occurred trying to read from file %s") - raise exceptions.CommandError(msg % blob_file) - - -def build_kwargs_dict(arg_name, value): - """Return a dictionary containing `arg_name` if `value` is set.""" - kwargs = {} - if value: - kwargs[arg_name] = value - return kwargs - - -def is_ascii(string): - try: - string.decode('ascii') - return True - except UnicodeDecodeError: - return False +sys.stderr.write( + "WARNING: %s is deprecated and will be removed after Jun 2017. " + "Please use osc_lib.utils\n" % __name__ +) diff --git a/requirements.txt b/requirements.txt index 5d8c844a71..f3affbd37d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,6 +9,7 @@ cliff!=1.16.0,!=1.17.0,>=1.15.0 # Apache-2.0 keystoneauth1>=2.1.0 # Apache-2.0 openstacksdk>=0.8.6 # Apache-2.0 os-client-config>=1.13.1 # Apache-2.0 +osc-lib>=0.1.0 # Apache-2.0 oslo.config>=3.10.0 # Apache-2.0 oslo.i18n>=2.1.0 # Apache-2.0 oslo.utils>=3.11.0 # Apache-2.0