summaryrefslogtreecommitdiffstats
path: root/openstack
diff options
context:
space:
mode:
authorJenkins <jenkins@review.openstack.org>2013-07-15 19:40:44 +0000
committerGerrit Code Review <review@openstack.org>2013-07-15 19:40:44 +0000
commit8cc5d1cb842938d4d823176cd1b1322198dc2832 (patch)
treea5f4a1e1c0a3788a53f7f01a188d33839a4adf69 /openstack
parent562d8ce6b8eecb0587ae2dd85a30ae72312b57b0 (diff)
parent1a7bf09f209949527bb1c63acc5a32008c92f119 (diff)
downloadoslo-8cc5d1cb842938d4d823176cd1b1322198dc2832.tar.gz
oslo-8cc5d1cb842938d4d823176cd1b1322198dc2832.tar.xz
oslo-8cc5d1cb842938d4d823176cd1b1322198dc2832.zip
Merge "Add exceptions for OpenStack clients"
Diffstat (limited to 'openstack')
-rw-r--r--openstack/common/apiclient/__init__.py16
-rw-r--r--openstack/common/apiclient/exceptions.py446
2 files changed, 462 insertions, 0 deletions
diff --git a/openstack/common/apiclient/__init__.py b/openstack/common/apiclient/__init__.py
new file mode 100644
index 0000000..d5d0022
--- /dev/null
+++ b/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/openstack/common/apiclient/exceptions.py b/openstack/common/apiclient/exceptions.py
new file mode 100644
index 0000000..e70d37a
--- /dev/null
+++ b/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)