Merge "Add apiclient.exceptions hierarchy"
This commit is contained in:
16
keystoneclient/apiclient/__init__.py
Normal file
16
keystoneclient/apiclient/__init__.py
Normal file
@@ -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.
|
442
keystoneclient/apiclient/exceptions.py
Normal file
442
keystoneclient/apiclient/exceptions.py
Normal file
@@ -0,0 +1,442 @@
|
|||||||
|
# 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 itertools
|
||||||
|
|
||||||
|
|
||||||
|
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 EmptyCatalog(EndpointNotFound):
|
||||||
|
"""The service catalog is empty."""
|
||||||
|
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 = "%(message)s (HTTP %(status)s)" % {
|
||||||
|
"message": self.message, "status": 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"
|
||||||
|
|
||||||
|
|
||||||
|
_code_map = dict(
|
||||||
|
(cls.http_status, cls)
|
||||||
|
for cls in itertools.chain(HTTPClientError.__subclasses__(),
|
||||||
|
HTTPServerError.__subclasses__()))
|
||||||
|
|
||||||
|
|
||||||
|
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")
|
||||||
|
kwargs["details"] = error.get("details")
|
||||||
|
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)
|
@@ -18,162 +18,5 @@
|
|||||||
Exception definitions.
|
Exception definitions.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from keystoneclient.openstack.common import jsonutils
|
#flake8: noqa
|
||||||
|
from keystoneclient.apiclient.exceptions import *
|
||||||
|
|
||||||
class CommandError(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class ValidationError(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class AuthorizationFailure(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 EmptyCatalog(Exception):
|
|
||||||
"""The service catalog is empty."""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class NoUniqueMatch(Exception):
|
|
||||||
"""Unable to find unique resource."""
|
|
||||||
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: your credentials do not allow access to this
|
|
||||||
resource.
|
|
||||||
"""
|
|
||||||
http_status = 403
|
|
||||||
message = "Forbidden"
|
|
||||||
|
|
||||||
|
|
||||||
class NotFound(ClientException):
|
|
||||||
"""HTTP 404 - Not found."""
|
|
||||||
http_status = 404
|
|
||||||
message = "Not found"
|
|
||||||
|
|
||||||
|
|
||||||
class MethodNotAllowed(ClientException):
|
|
||||||
"""HTTP 405 - Method not allowed."""
|
|
||||||
http_status = 405
|
|
||||||
message = "Method not allowed"
|
|
||||||
|
|
||||||
|
|
||||||
class Conflict(ClientException):
|
|
||||||
"""HTTP 409 - Conflict."""
|
|
||||||
http_status = 409
|
|
||||||
message = "Conflict"
|
|
||||||
|
|
||||||
|
|
||||||
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"
|
|
||||||
|
|
||||||
|
|
||||||
class ServiceUnavailable(ClientException):
|
|
||||||
"""HTTP 503 - Service Unavailable: The server is currently unavailable."""
|
|
||||||
http_status = 503
|
|
||||||
message = "Service Unavailable"
|
|
||||||
|
|
||||||
|
|
||||||
# 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,
|
|
||||||
MethodNotAllowed,
|
|
||||||
Conflict,
|
|
||||||
OverLimit,
|
|
||||||
HTTPNotImplemented,
|
|
||||||
ServiceUnavailable])
|
|
||||||
|
|
||||||
|
|
||||||
def from_response(response, body=None):
|
|
||||||
"""Return an instance of a ClientException or subclass
|
|
||||||
based on a requests response.
|
|
||||||
|
|
||||||
Usage::
|
|
||||||
|
|
||||||
resp = requests.request(...)
|
|
||||||
if resp.status_code != 200:
|
|
||||||
raise exception_from_response(resp, resp.text)
|
|
||||||
"""
|
|
||||||
cls = _code_map.get(response.status_code, ClientException)
|
|
||||||
if body is None:
|
|
||||||
try:
|
|
||||||
body = jsonutils.loads(response.text)
|
|
||||||
except Exception:
|
|
||||||
body = response.text
|
|
||||||
|
|
||||||
if body:
|
|
||||||
if hasattr(body, 'keys'):
|
|
||||||
error = body[body.keys()[0]]
|
|
||||||
message = error.get('message', None)
|
|
||||||
details = error.get('details', None)
|
|
||||||
else:
|
|
||||||
# If we didn't get back a properly formed error message we
|
|
||||||
# probably couldn't communicate with Keystone at all.
|
|
||||||
message = "Unable to communicate with identity service: %s." % body
|
|
||||||
details = None
|
|
||||||
return cls(code=response.status_code, message=message, details=details)
|
|
||||||
else:
|
|
||||||
return cls(code=response.status_code)
|
|
||||||
|
@@ -120,7 +120,7 @@ class Client(httpclient.HTTPClient):
|
|||||||
elif resp.status_code == 305:
|
elif resp.status_code == 305:
|
||||||
return self._check_keystone_versions(resp['location'])
|
return self._check_keystone_versions(resp['location'])
|
||||||
else:
|
else:
|
||||||
raise exceptions.from_response(resp, resp.text)
|
raise exceptions.from_response(resp, "GET", url)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
_logger.exception(e)
|
_logger.exception(e)
|
||||||
|
|
||||||
@@ -178,7 +178,8 @@ class Client(httpclient.HTTPClient):
|
|||||||
elif resp.status_code == 305:
|
elif resp.status_code == 305:
|
||||||
return self._check_keystone_extensions(resp['location'])
|
return self._check_keystone_extensions(resp['location'])
|
||||||
else:
|
else:
|
||||||
raise exceptions.from_response(resp, resp.text)
|
raise exceptions.from_response(
|
||||||
|
resp, "GET", "%sextensions" % url)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
_logger.exception(e)
|
_logger.exception(e)
|
||||||
|
|
||||||
|
@@ -120,7 +120,7 @@ def request(url, method='GET', headers=None, original_ip=None, debug=False,
|
|||||||
if resp.status_code >= 400:
|
if resp.status_code >= 400:
|
||||||
logger.debug("Request returned failure status: %s",
|
logger.debug("Request returned failure status: %s",
|
||||||
resp.status_code)
|
resp.status_code)
|
||||||
raise exceptions.from_response(resp)
|
raise exceptions.from_response(resp, method, url)
|
||||||
|
|
||||||
return resp
|
return resp
|
||||||
|
|
||||||
|
67
tests/apiclient/test_exceptions.py
Normal file
67
tests/apiclient/test_exceptions.py
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
# Copyright 2012 OpenStack LLC.
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
from tests import utils
|
||||||
|
|
||||||
|
from keystoneclient.apiclient import exceptions
|
||||||
|
|
||||||
|
|
||||||
|
class FakeResponse(object):
|
||||||
|
json_data = {}
|
||||||
|
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
for key, value in kwargs.iteritems():
|
||||||
|
setattr(self, key, value)
|
||||||
|
|
||||||
|
def json(self):
|
||||||
|
return self.json_data
|
||||||
|
|
||||||
|
|
||||||
|
class ExceptionsArgsTest(utils.TestCase):
|
||||||
|
|
||||||
|
def assert_exception(self, ex_cls, method, url, status_code, json_data):
|
||||||
|
ex = exceptions.from_response(
|
||||||
|
FakeResponse(status_code=status_code,
|
||||||
|
headers={"Content-Type": "application/json"},
|
||||||
|
json_data=json_data),
|
||||||
|
method,
|
||||||
|
url)
|
||||||
|
self.assertTrue(isinstance(ex, ex_cls))
|
||||||
|
self.assertEqual(ex.message, json_data["error"]["message"])
|
||||||
|
self.assertEqual(ex.details, json_data["error"]["details"])
|
||||||
|
self.assertEqual(ex.method, method)
|
||||||
|
self.assertEqual(ex.url, url)
|
||||||
|
self.assertEqual(ex.http_status, status_code)
|
||||||
|
|
||||||
|
def test_from_response_known(self):
|
||||||
|
method = "GET"
|
||||||
|
url = "/fake"
|
||||||
|
status_code = 400
|
||||||
|
json_data = {"error": {"message": "fake message",
|
||||||
|
"details": "fake details"}}
|
||||||
|
self.assert_exception(
|
||||||
|
exceptions.BadRequest, method, url, status_code, json_data)
|
||||||
|
|
||||||
|
def test_from_response_unknown(self):
|
||||||
|
method = "POST"
|
||||||
|
url = "/fake-unknown"
|
||||||
|
status_code = 499
|
||||||
|
json_data = {"error": {"message": "fake unknown message",
|
||||||
|
"details": "fake unknown details"}}
|
||||||
|
self.assert_exception(
|
||||||
|
exceptions.HTTPClientError, method, url, status_code, json_data)
|
||||||
|
status_code = 600
|
||||||
|
self.assert_exception(
|
||||||
|
exceptions.HTTPError, method, url, status_code, json_data)
|
@@ -107,7 +107,8 @@ class ClientTest(utils.TestCase):
|
|||||||
}
|
}
|
||||||
fake_err_response = utils.TestResponse({
|
fake_err_response = utils.TestResponse({
|
||||||
"status_code": 400,
|
"status_code": 400,
|
||||||
"text": json.dumps(err_response)
|
"text": json.dumps(err_response),
|
||||||
|
"headers": {"Content-Type": "application/json"},
|
||||||
})
|
})
|
||||||
err_MOCK_REQUEST = mock.Mock(return_value=(fake_err_response))
|
err_MOCK_REQUEST = mock.Mock(return_value=(fake_err_response))
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user