From a7cce08eab5e2e42275b84bd56127bd09b00f5bf Mon Sep 17 00:00:00 2001 From: Alessio Ababilov Date: Sun, 19 May 2013 18:12:27 +0300 Subject: [PATCH] Use exceptions from oslo These exceptions can be used in novaclient, keystoneclient, glanceclient, and other client projects. Partially implements: blueprint common-client-library Change-Id: I43918316622b1c1d722872fe30199db6a3a7bb76 --- cinderclient/base.py | 2 +- cinderclient/client.py | 35 +- cinderclient/exceptions.py | 156 +----- .../openstack/common/apiclient/__init__.py | 16 + .../openstack/common/apiclient/exceptions.py | 446 ++++++++++++++++++ cinderclient/shell.py | 2 +- cinderclient/tests/test_base.py | 2 +- cinderclient/tests/test_http.py | 2 +- cinderclient/tests/test_service_catalog.py | 2 +- cinderclient/tests/test_shell.py | 2 +- cinderclient/tests/test_utils.py | 2 +- cinderclient/tests/utils.py | 3 +- cinderclient/tests/v1/test_auth.py | 2 +- cinderclient/tests/v2/test_auth.py | 2 +- cinderclient/utils.py | 2 +- cinderclient/v1/shell.py | 2 +- cinderclient/v2/shell.py | 2 +- openstack-common.conf | 2 +- 18 files changed, 499 insertions(+), 183 deletions(-) create mode 100644 cinderclient/openstack/common/apiclient/__init__.py create mode 100644 cinderclient/openstack/common/apiclient/exceptions.py diff --git a/cinderclient/base.py b/cinderclient/base.py index 4e2907833..ccbdd98df 100644 --- a/cinderclient/base.py +++ b/cinderclient/base.py @@ -25,7 +25,7 @@ import os import six -from cinderclient import exceptions +from cinderclient.openstack.common.apiclient import exceptions from cinderclient import utils diff --git a/cinderclient/client.py b/cinderclient/client.py index 857f80a03..9a40dfcff 100644 --- a/cinderclient/client.py +++ b/cinderclient/client.py @@ -46,7 +46,7 @@ if not hasattr(urlparse, 'parse_qsl'): import requests -from cinderclient import exceptions +from cinderclient.openstack.common.apiclient import exceptions from cinderclient import service_catalog from cinderclient import utils @@ -151,7 +151,7 @@ class HTTPClient(object): body = None if resp.status_code >= 400: - raise exceptions.from_response(resp, body) + raise exceptions.from_response(resp, method, url) return resp, body @@ -185,7 +185,7 @@ class HTTPClient(object): except exceptions.ClientException as e: if attempts > self.retries: raise - if 500 <= e.code <= 599: + if 500 <= e.http_status <= 599: pass else: raise @@ -211,15 +211,16 @@ class HTTPClient(object): def delete(self, url, **kwargs): return self._cs_request(url, 'DELETE', **kwargs) - def _extract_service_catalog(self, url, resp, body, extract_token=True): + def _extract_service_catalog(self, auth_url, url, method, + extract_token=True, **kwargs): """See what the auth service told us and process the response. We may get redirected to another site, fail or actually get back a service catalog with a token and our endpoints. """ - + resp, body = self.request(url, method, **kwargs) if resp.status_code == 200: # content must always present try: - self.auth_url = url + self.auth_url = auth_url self.service_catalog = \ service_catalog.ServiceCatalog(body) @@ -248,7 +249,7 @@ class HTTPClient(object): elif resp.status_code == 305: return resp['location'] else: - raise exceptions.from_response(resp, body) + raise exceptions.from_response(resp, method, url) def _fetch_endpoints_from_auth(self, url): """We have a token, but don't know the final endpoint for @@ -263,13 +264,14 @@ class HTTPClient(object): """ # GET ...:5001/v2.0/tokens/#####/endpoints + auth_url = url url = '/'.join([url, 'tokens', '%s?belongsTo=%s' % (self.proxy_token, self.proxy_tenant_id)]) self._logger.debug("Using Endpoint URL: %s" % url) - resp, body = self.request(url, "GET", - headers={'X-Auth-Token': self.auth_token}) - return self._extract_service_catalog(url, resp, body, - extract_token=False) + return self._extract_service_catalog( + auth_url, + url, "GET", headers={'X-Auth-Token': self.auth_token}, + extract_token=False) def authenticate(self): magic_tuple = urlparse.urlsplit(self.auth_url) @@ -320,7 +322,9 @@ class HTTPClient(object): def _v1_auth(self, url): if self.proxy_token: - raise exceptions.NoTokenLookupException() + raise exceptions.AuthorizationFailure( + "This form of authentication does not support looking up" + " endpoints from an existing token.") headers = {'X-Auth-User': self.user, 'X-Auth-Key': self.password} @@ -339,7 +343,7 @@ class HTTPClient(object): elif resp.status_code == 305: return resp.headers['location'] else: - raise exceptions.from_response(resp, body) + raise exceptions.from_response(resp, "GET", url) def _v2_auth(self, url): """Authenticate against a v2.0 auth service.""" @@ -369,14 +373,13 @@ class HTTPClient(object): token_url = url + "/tokens" # Make sure we follow redirects when trying to reach Keystone - resp, body = self.request( + return self._extract_service_catalog( + url, token_url, "POST", body=body, allow_redirects=True) - return self._extract_service_catalog(url, resp, body) - def get_volume_api_version_from_endpoint(self): magic_tuple = urlparse.urlsplit(self.management_url) scheme, netloc, path, query, frag = magic_tuple diff --git a/cinderclient/exceptions.py b/cinderclient/exceptions.py index 0c52c9cf3..e5e867879 100644 --- a/cinderclient/exceptions.py +++ b/cinderclient/exceptions.py @@ -1,156 +1,6 @@ -# Copyright 2010 Jacob Kaplan-Moss """ -Exception definitions. +Backwards compatible exceptions module. """ - -class UnsupportedVersion(Exception): - """Indicates that the user is trying to use an unsupported - version of the API. - """ - pass - - -class InvalidAPIVersion(Exception): - pass - - -class CommandError(Exception): - pass - - -class AuthorizationFailure(Exception): - pass - - -class NoUniqueMatch(Exception): - pass - - -class NoTokenLookupException(Exception): - """This form of authentication 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 AmbiguousEndpoints(Exception): - """Found more than one matching endpoint in Service Catalog.""" - def __init__(self, endpoints=None): - self.endpoints = endpoints - - def __str__(self): - return "AmbiguousEndpoints: %s" % repr(self.endpoints) - - -class ClientException(Exception): - """ - The base exception class for all exceptions this library raises. - """ - def __init__(self, code, message=None, details=None, request_id=None): - self.code = code - self.message = message or self.__class__.message - self.details = details - self.request_id = request_id - - def __str__(self): - formatted_string = "%s (HTTP %s)" % (self.message, self.code) - if self.request_id: - formatted_string += " (Request-ID: %s)" % self.request_id - - return formatted_string - - -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: your credentials don't give you access to this - resource. - """ - http_status = 403 - message = "Forbidden" - - -class NotFound(ClientException): - """ - HTTP 404 - Not found - """ - http_status = 404 - message = "Not found" - - -class OverLimit(ClientException): - """ - HTTP 413 - Over limit: you're over 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: the 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]) - - -def from_response(response, body): - """ - Return an instance of an ClientException or subclass - based on an requests response. - - Usage:: - - resp, body = requests.request(...) - if resp.status_code != 200: - raise exception_from_response(resp, rest.text) - """ - cls = _code_map.get(response.status_code, ClientException) - if response.headers: - request_id = response.headers.get('x-compute-request-id') - else: - request_id = None - if body: - message = "n/a" - details = "n/a" - if hasattr(body, 'keys'): - error = body[list(body.keys())[0]] - message = error.get('message', None) - details = error.get('details', None) - return cls(code=response.status_code, message=message, details=details, - request_id=request_id) - else: - return cls(code=response.status_code, request_id=request_id) +# flake8: noqa +from cinderclient.openstack.common.apiclient.exceptions import * diff --git a/cinderclient/openstack/common/apiclient/__init__.py b/cinderclient/openstack/common/apiclient/__init__.py new file mode 100644 index 000000000..d5d002224 --- /dev/null +++ b/cinderclient/openstack/common/apiclient/__init__.py @@ -0,0 +1,16 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 OpenStack Foundation +# All Rights Reserved. +# +# 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. diff --git a/cinderclient/openstack/common/apiclient/exceptions.py b/cinderclient/openstack/common/apiclient/exceptions.py new file mode 100644 index 000000000..e70d37a14 --- /dev/null +++ b/cinderclient/openstack/common/apiclient/exceptions.py @@ -0,0 +1,446 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 Jacob Kaplan-Moss +# Copyright 2011 Nebula, Inc. +# Copyright 2013 Alessio Ababilov +# Copyright 2013 OpenStack Foundation +# All Rights Reserved. +# +# 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. + +""" +Exception definitions. +""" + +import sys + + +class ClientException(Exception): + """The base exception class for all exceptions this library raises. + """ + pass + + +class MissingArgs(ClientException): + """Supplied arguments are not sufficient for calling a function.""" + def __init__(self, missing): + self.missing = missing + msg = "Missing argument(s): %s" % ", ".join(missing) + super(MissingArgs, self).__init__(msg) + + +class ValidationError(ClientException): + """Error in validation on API client side.""" + pass + + +class UnsupportedVersion(ClientException): + """User is trying to use an unsupported version of the API.""" + pass + + +class CommandError(ClientException): + """Error in CLI tool.""" + pass + + +class AuthorizationFailure(ClientException): + """Cannot authorize API client.""" + pass + + +class AuthPluginOptionsMissing(AuthorizationFailure): + """Auth plugin misses some options.""" + def __init__(self, opt_names): + super(AuthPluginOptionsMissing, self).__init__( + "Authentication failed. Missing options: %s" % + ", ".join(opt_names)) + self.opt_names = opt_names + + +class AuthSystemNotFound(AuthorizationFailure): + """User has specified a AuthSystem that is not installed.""" + def __init__(self, auth_system): + super(AuthSystemNotFound, self).__init__( + "AuthSystemNotFound: %s" % repr(auth_system)) + self.auth_system = auth_system + + +class NoUniqueMatch(ClientException): + """Multiple entities found instead of one.""" + pass + + +class EndpointException(ClientException): + """Something is rotten in Service Catalog.""" + pass + + +class EndpointNotFound(EndpointException): + """Could not find requested endpoint in Service Catalog.""" + pass + + +class AmbiguousEndpoints(EndpointException): + """Found more than one matching endpoint in Service Catalog.""" + def __init__(self, endpoints=None): + super(AmbiguousEndpoints, self).__init__( + "AmbiguousEndpoints: %s" % repr(endpoints)) + self.endpoints = endpoints + + +class HttpError(ClientException): + """The base exception class for all HTTP exceptions. + """ + http_status = 0 + message = "HTTP Error" + + def __init__(self, message=None, details=None, + response=None, request_id=None, + url=None, method=None, http_status=None): + self.http_status = http_status or self.http_status + self.message = message or self.message + self.details = details + self.request_id = request_id + self.response = response + self.url = url + self.method = method + formatted_string = "%s (HTTP %s)" % (self.message, self.http_status) + if request_id: + formatted_string += " (Request-ID: %s)" % request_id + super(HttpError, self).__init__(formatted_string) + + +class HttpClientError(HttpError): + """Client-side HTTP error. + + Exception for cases in which the client seems to have erred. + """ + message = "HTTP Client Error" + + +class HttpServerError(HttpError): + """Server-side HTTP error. + + Exception for cases in which the server is aware that it has + erred or is incapable of performing the request. + """ + message = "HTTP Server Error" + + +class BadRequest(HttpClientError): + """HTTP 400 - Bad Request. + + The request cannot be fulfilled due to bad syntax. + """ + http_status = 400 + message = "Bad Request" + + +class Unauthorized(HttpClientError): + """HTTP 401 - Unauthorized. + + Similar to 403 Forbidden, but specifically for use when authentication + is required and has failed or has not yet been provided. + """ + http_status = 401 + message = "Unauthorized" + + +class PaymentRequired(HttpClientError): + """HTTP 402 - Payment Required. + + Reserved for future use. + """ + http_status = 402 + message = "Payment Required" + + +class Forbidden(HttpClientError): + """HTTP 403 - Forbidden. + + The request was a valid request, but the server is refusing to respond + to it. + """ + http_status = 403 + message = "Forbidden" + + +class NotFound(HttpClientError): + """HTTP 404 - Not Found. + + The requested resource could not be found but may be available again + in the future. + """ + http_status = 404 + message = "Not Found" + + +class MethodNotAllowed(HttpClientError): + """HTTP 405 - Method Not Allowed. + + A request was made of a resource using a request method not supported + by that resource. + """ + http_status = 405 + message = "Method Not Allowed" + + +class NotAcceptable(HttpClientError): + """HTTP 406 - Not Acceptable. + + The requested resource is only capable of generating content not + acceptable according to the Accept headers sent in the request. + """ + http_status = 406 + message = "Not Acceptable" + + +class ProxyAuthenticationRequired(HttpClientError): + """HTTP 407 - Proxy Authentication Required. + + The client must first authenticate itself with the proxy. + """ + http_status = 407 + message = "Proxy Authentication Required" + + +class RequestTimeout(HttpClientError): + """HTTP 408 - Request Timeout. + + The server timed out waiting for the request. + """ + http_status = 408 + message = "Request Timeout" + + +class Conflict(HttpClientError): + """HTTP 409 - Conflict. + + Indicates that the request could not be processed because of conflict + in the request, such as an edit conflict. + """ + http_status = 409 + message = "Conflict" + + +class Gone(HttpClientError): + """HTTP 410 - Gone. + + Indicates that the resource requested is no longer available and will + not be available again. + """ + http_status = 410 + message = "Gone" + + +class LengthRequired(HttpClientError): + """HTTP 411 - Length Required. + + The request did not specify the length of its content, which is + required by the requested resource. + """ + http_status = 411 + message = "Length Required" + + +class PreconditionFailed(HttpClientError): + """HTTP 412 - Precondition Failed. + + The server does not meet one of the preconditions that the requester + put on the request. + """ + http_status = 412 + message = "Precondition Failed" + + +class RequestEntityTooLarge(HttpClientError): + """HTTP 413 - Request Entity Too Large. + + The request is larger than the server is willing or able to process. + """ + http_status = 413 + message = "Request Entity Too Large" + + def __init__(self, *args, **kwargs): + try: + self.retry_after = int(kwargs.pop('retry_after')) + except (KeyError, ValueError): + self.retry_after = 0 + + super(RequestEntityTooLarge, self).__init__(*args, **kwargs) + + +class RequestUriTooLong(HttpClientError): + """HTTP 414 - Request-URI Too Long. + + The URI provided was too long for the server to process. + """ + http_status = 414 + message = "Request-URI Too Long" + + +class UnsupportedMediaType(HttpClientError): + """HTTP 415 - Unsupported Media Type. + + The request entity has a media type which the server or resource does + not support. + """ + http_status = 415 + message = "Unsupported Media Type" + + +class RequestedRangeNotSatisfiable(HttpClientError): + """HTTP 416 - Requested Range Not Satisfiable. + + The client has asked for a portion of the file, but the server cannot + supply that portion. + """ + http_status = 416 + message = "Requested Range Not Satisfiable" + + +class ExpectationFailed(HttpClientError): + """HTTP 417 - Expectation Failed. + + The server cannot meet the requirements of the Expect request-header field. + """ + http_status = 417 + message = "Expectation Failed" + + +class UnprocessableEntity(HttpClientError): + """HTTP 422 - Unprocessable Entity. + + The request was well-formed but was unable to be followed due to semantic + errors. + """ + http_status = 422 + message = "Unprocessable Entity" + + +class InternalServerError(HttpServerError): + """HTTP 500 - Internal Server Error. + + A generic error message, given when no more specific message is suitable. + """ + http_status = 500 + message = "Internal Server Error" + + +# NotImplemented is a python keyword. +class HttpNotImplemented(HttpServerError): + """HTTP 501 - Not Implemented. + + The server either does not recognize the request method, or it lacks + the ability to fulfill the request. + """ + http_status = 501 + message = "Not Implemented" + + +class BadGateway(HttpServerError): + """HTTP 502 - Bad Gateway. + + The server was acting as a gateway or proxy and received an invalid + response from the upstream server. + """ + http_status = 502 + message = "Bad Gateway" + + +class ServiceUnavailable(HttpServerError): + """HTTP 503 - Service Unavailable. + + The server is currently unavailable. + """ + http_status = 503 + message = "Service Unavailable" + + +class GatewayTimeout(HttpServerError): + """HTTP 504 - Gateway Timeout. + + The server was acting as a gateway or proxy and did not receive a timely + response from the upstream server. + """ + http_status = 504 + message = "Gateway Timeout" + + +class HttpVersionNotSupported(HttpServerError): + """HTTP 505 - HttpVersion Not Supported. + + The server does not support the HTTP protocol version used in the request. + """ + http_status = 505 + message = "HTTP Version Not Supported" + + +# 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 HttpError.__subclasses__()) +_code_map = {} +for obj in sys.modules[__name__].__dict__.values(): + if isinstance(obj, type): + try: + http_status = obj.http_status + except AttributeError: + pass + else: + if http_status: + _code_map[http_status] = obj + + +def from_response(response, method, url): + """Returns an instance of :class:`HttpError` or subclass based on response. + + :param response: instance of `requests.Response` class + :param method: HTTP method used for request + :param url: URL used for request + """ + kwargs = { + "http_status": response.status_code, + "response": response, + "method": method, + "url": url, + "request_id": response.headers.get("x-compute-request-id"), + } + if "retry-after" in response.headers: + kwargs["retry_after"] = response.headers["retry-after"] + + content_type = response.headers.get("Content-Type", "") + if content_type.startswith("application/json"): + try: + body = response.json() + except ValueError: + pass + else: + if hasattr(body, "keys"): + error = body[body.keys()[0]] + kwargs["message"] = error.get("message", None) + kwargs["details"] = error.get("details", None) + elif content_type.startswith("text/"): + kwargs["details"] = response.text + + try: + cls = _code_map[response.status_code] + except KeyError: + if 500 <= response.status_code < 600: + cls = HttpServerError + elif 400 <= response.status_code < 500: + cls = HttpClientError + else: + cls = HttpError + return cls(**kwargs) diff --git a/cinderclient/shell.py b/cinderclient/shell.py index d06799632..c5b1dd279 100644 --- a/cinderclient/shell.py +++ b/cinderclient/shell.py @@ -32,8 +32,8 @@ import logging import six from cinderclient import client -from cinderclient import exceptions as exc import cinderclient.extension +from cinderclient.openstack.common.apiclient import exceptions as exc from cinderclient.openstack.common import strutils from cinderclient import utils from cinderclient.v1 import shell as shell_v1 diff --git a/cinderclient/tests/test_base.py b/cinderclient/tests/test_base.py index 75c37e6b9..d22e2cd7f 100644 --- a/cinderclient/tests/test_base.py +++ b/cinderclient/tests/test_base.py @@ -1,5 +1,5 @@ from cinderclient import base -from cinderclient import exceptions +from cinderclient.openstack.common.apiclient import exceptions from cinderclient.v1 import volumes from cinderclient.tests import utils from cinderclient.tests.v1 import fakes diff --git a/cinderclient/tests/test_http.py b/cinderclient/tests/test_http.py index 3b93a4b2a..8c8c99d6d 100644 --- a/cinderclient/tests/test_http.py +++ b/cinderclient/tests/test_http.py @@ -3,7 +3,7 @@ import mock import requests from cinderclient import client -from cinderclient import exceptions +from cinderclient.openstack.common.apiclient import exceptions from cinderclient.tests import utils diff --git a/cinderclient/tests/test_service_catalog.py b/cinderclient/tests/test_service_catalog.py index c9d98193d..6db8966c3 100644 --- a/cinderclient/tests/test_service_catalog.py +++ b/cinderclient/tests/test_service_catalog.py @@ -1,4 +1,4 @@ -from cinderclient import exceptions +from cinderclient.openstack.common.apiclient import exceptions from cinderclient import service_catalog from cinderclient.tests import utils diff --git a/cinderclient/tests/test_shell.py b/cinderclient/tests/test_shell.py index d6ef42584..b301c21d2 100644 --- a/cinderclient/tests/test_shell.py +++ b/cinderclient/tests/test_shell.py @@ -5,7 +5,7 @@ import fixtures from six import moves from testtools import matchers -from cinderclient import exceptions +from cinderclient.openstack.common.apiclient import exceptions import cinderclient.shell from cinderclient.tests import utils diff --git a/cinderclient/tests/test_utils.py b/cinderclient/tests/test_utils.py index 8df482d18..aec1587f8 100644 --- a/cinderclient/tests/test_utils.py +++ b/cinderclient/tests/test_utils.py @@ -3,7 +3,7 @@ import sys from six import moves -from cinderclient import exceptions +from cinderclient.openstack.common.apiclient import exceptions from cinderclient import utils from cinderclient import base from cinderclient.tests import utils as test_utils diff --git a/cinderclient/tests/utils.py b/cinderclient/tests/utils.py index 0ab873778..b258d80e7 100644 --- a/cinderclient/tests/utils.py +++ b/cinderclient/tests/utils.py @@ -29,10 +29,11 @@ class TestResponse(requests.Response): def __init__(self, data): self._text = None + self.headers = {} super(TestResponse, self) if isinstance(data, dict): self.status_code = data.get('status_code', None) - self.headers = data.get('headers', None) + self.headers = data.get('headers') or {} # Fake the text attribute to streamline Response creation self._text = data.get('text', None) else: diff --git a/cinderclient/tests/v1/test_auth.py b/cinderclient/tests/v1/test_auth.py index 704eacc0f..f87e6dba8 100644 --- a/cinderclient/tests/v1/test_auth.py +++ b/cinderclient/tests/v1/test_auth.py @@ -4,7 +4,7 @@ import mock import requests from cinderclient.v1 import client -from cinderclient import exceptions +from cinderclient.openstack.common.apiclient import exceptions from cinderclient.tests import utils diff --git a/cinderclient/tests/v2/test_auth.py b/cinderclient/tests/v2/test_auth.py index 89dd18fcb..d4d0b19b4 100644 --- a/cinderclient/tests/v2/test_auth.py +++ b/cinderclient/tests/v2/test_auth.py @@ -19,7 +19,7 @@ import json import mock import requests -from cinderclient import exceptions +from cinderclient.openstack.common.apiclient import exceptions from cinderclient.v2 import client from cinderclient.tests import utils diff --git a/cinderclient/utils.py b/cinderclient/utils.py index 922f053e9..90dadd303 100644 --- a/cinderclient/utils.py +++ b/cinderclient/utils.py @@ -23,7 +23,7 @@ import uuid import six import prettytable -from cinderclient import exceptions +from cinderclient.openstack.common.apiclient import exceptions from cinderclient.openstack.common import strutils diff --git a/cinderclient/v1/shell.py b/cinderclient/v1/shell.py index f42d74e5d..38ab75e6f 100644 --- a/cinderclient/v1/shell.py +++ b/cinderclient/v1/shell.py @@ -22,7 +22,7 @@ import os import sys import time -from cinderclient import exceptions +from cinderclient.openstack.common.apiclient import exceptions from cinderclient import utils diff --git a/cinderclient/v2/shell.py b/cinderclient/v2/shell.py index b86f7f9be..9847c0905 100644 --- a/cinderclient/v2/shell.py +++ b/cinderclient/v2/shell.py @@ -22,7 +22,7 @@ import time import six -from cinderclient import exceptions +from cinderclient.openstack.common.apiclient import exceptions from cinderclient import utils diff --git a/openstack-common.conf b/openstack-common.conf index 35e0ccf99..ce83debf3 100644 --- a/openstack-common.conf +++ b/openstack-common.conf @@ -1,7 +1,7 @@ [DEFAULT] # The list of modules to copy from openstack-common -modules=strutils +modules=apiclient,strutils # The base module to hold the copy of openstack.common base=cinderclient