diff --git a/cinderclient/base.py b/cinderclient/base.py index ccbdd98df..4e2907833 100644 --- a/cinderclient/base.py +++ b/cinderclient/base.py @@ -25,7 +25,7 @@ import os import six -from cinderclient.openstack.common.apiclient import exceptions +from cinderclient import exceptions from cinderclient import utils diff --git a/cinderclient/client.py b/cinderclient/client.py index 9a40dfcff..857f80a03 100644 --- a/cinderclient/client.py +++ b/cinderclient/client.py @@ -46,7 +46,7 @@ if not hasattr(urlparse, 'parse_qsl'): import requests -from cinderclient.openstack.common.apiclient import exceptions +from cinderclient 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, method, url) + raise exceptions.from_response(resp, body) return resp, body @@ -185,7 +185,7 @@ class HTTPClient(object): except exceptions.ClientException as e: if attempts > self.retries: raise - if 500 <= e.http_status <= 599: + if 500 <= e.code <= 599: pass else: raise @@ -211,16 +211,15 @@ class HTTPClient(object): def delete(self, url, **kwargs): return self._cs_request(url, 'DELETE', **kwargs) - def _extract_service_catalog(self, auth_url, url, method, - extract_token=True, **kwargs): + def _extract_service_catalog(self, url, resp, body, extract_token=True): """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 = auth_url + self.auth_url = url self.service_catalog = \ service_catalog.ServiceCatalog(body) @@ -249,7 +248,7 @@ class HTTPClient(object): elif resp.status_code == 305: return resp['location'] else: - raise exceptions.from_response(resp, method, url) + raise exceptions.from_response(resp, body) def _fetch_endpoints_from_auth(self, url): """We have a token, but don't know the final endpoint for @@ -264,14 +263,13 @@ 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) - return self._extract_service_catalog( - auth_url, - url, "GET", headers={'X-Auth-Token': self.auth_token}, - extract_token=False) + resp, body = self.request(url, "GET", + headers={'X-Auth-Token': self.auth_token}) + return self._extract_service_catalog(url, resp, body, + extract_token=False) def authenticate(self): magic_tuple = urlparse.urlsplit(self.auth_url) @@ -322,9 +320,7 @@ class HTTPClient(object): def _v1_auth(self, url): if self.proxy_token: - raise exceptions.AuthorizationFailure( - "This form of authentication does not support looking up" - " endpoints from an existing token.") + raise exceptions.NoTokenLookupException() headers = {'X-Auth-User': self.user, 'X-Auth-Key': self.password} @@ -343,7 +339,7 @@ class HTTPClient(object): elif resp.status_code == 305: return resp.headers['location'] else: - raise exceptions.from_response(resp, "GET", url) + raise exceptions.from_response(resp, body) def _v2_auth(self, url): """Authenticate against a v2.0 auth service.""" @@ -373,13 +369,14 @@ class HTTPClient(object): token_url = url + "/tokens" # Make sure we follow redirects when trying to reach Keystone - return self._extract_service_catalog( - url, + resp, body = self.request( 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 e5e867879..0c52c9cf3 100644 --- a/cinderclient/exceptions.py +++ b/cinderclient/exceptions.py @@ -1,6 +1,156 @@ +# Copyright 2010 Jacob Kaplan-Moss """ -Backwards compatible exceptions module. +Exception definitions. """ -# flake8: noqa -from cinderclient.openstack.common.apiclient.exceptions import * + +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) diff --git a/cinderclient/openstack/common/apiclient/__init__.py b/cinderclient/openstack/common/apiclient/__init__.py deleted file mode 100644 index d5d002224..000000000 --- a/cinderclient/openstack/common/apiclient/__init__.py +++ /dev/null @@ -1,16 +0,0 @@ -# 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 deleted file mode 100644 index e70d37a14..000000000 --- a/cinderclient/openstack/common/apiclient/exceptions.py +++ /dev/null @@ -1,446 +0,0 @@ -# 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 c5b1dd279..d06799632 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 d22e2cd7f..75c37e6b9 100644 --- a/cinderclient/tests/test_base.py +++ b/cinderclient/tests/test_base.py @@ -1,5 +1,5 @@ from cinderclient import base -from cinderclient.openstack.common.apiclient import exceptions +from cinderclient 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 8c8c99d6d..3b93a4b2a 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.openstack.common.apiclient import exceptions +from cinderclient import exceptions from cinderclient.tests import utils diff --git a/cinderclient/tests/test_service_catalog.py b/cinderclient/tests/test_service_catalog.py index 6db8966c3..c9d98193d 100644 --- a/cinderclient/tests/test_service_catalog.py +++ b/cinderclient/tests/test_service_catalog.py @@ -1,4 +1,4 @@ -from cinderclient.openstack.common.apiclient import exceptions +from cinderclient 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 b301c21d2..d6ef42584 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.openstack.common.apiclient import exceptions +from cinderclient 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 aec1587f8..8df482d18 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.openstack.common.apiclient import exceptions +from cinderclient 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 b258d80e7..0ab873778 100644 --- a/cinderclient/tests/utils.py +++ b/cinderclient/tests/utils.py @@ -29,11 +29,10 @@ 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') or {} + self.headers = data.get('headers', None) # 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 f87e6dba8..704eacc0f 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.openstack.common.apiclient import exceptions +from cinderclient import exceptions from cinderclient.tests import utils diff --git a/cinderclient/tests/v2/test_auth.py b/cinderclient/tests/v2/test_auth.py index d4d0b19b4..89dd18fcb 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.openstack.common.apiclient import exceptions +from cinderclient import exceptions from cinderclient.v2 import client from cinderclient.tests import utils diff --git a/cinderclient/utils.py b/cinderclient/utils.py index 90dadd303..922f053e9 100644 --- a/cinderclient/utils.py +++ b/cinderclient/utils.py @@ -23,7 +23,7 @@ import uuid import six import prettytable -from cinderclient.openstack.common.apiclient import exceptions +from cinderclient import exceptions from cinderclient.openstack.common import strutils diff --git a/cinderclient/v1/shell.py b/cinderclient/v1/shell.py index eeb30ba11..a8078543a 100644 --- a/cinderclient/v1/shell.py +++ b/cinderclient/v1/shell.py @@ -22,7 +22,7 @@ import os import sys import time -from cinderclient.openstack.common.apiclient import exceptions +from cinderclient import exceptions from cinderclient import utils diff --git a/cinderclient/v2/shell.py b/cinderclient/v2/shell.py index 98883d6ba..5e85751d8 100644 --- a/cinderclient/v2/shell.py +++ b/cinderclient/v2/shell.py @@ -22,7 +22,7 @@ import time import six -from cinderclient.openstack.common.apiclient import exceptions +from cinderclient import exceptions from cinderclient import utils