add escalator cli framework
[escalator.git] / client / escalatorclient / openstack / common / apiclient / exceptions.py
1 # Copyright 2010 Jacob Kaplan-Moss
2 # Copyright 2011 Nebula, Inc.
3 # Copyright 2013 Alessio Ababilov
4 # Copyright 2013 OpenStack Foundation
5 # All Rights Reserved.
6 #
7 #    Licensed under the Apache License, Version 2.0 (the "License"); you may
8 #    not use this file except in compliance with the License. You may obtain
9 #    a copy of the License at
10 #
11 #         http://www.apache.org/licenses/LICENSE-2.0
12 #
13 #    Unless required by applicable law or agreed to in writing, software
14 #    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
15 #    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
16 #    License for the specific language governing permissions and limitations
17 #    under the License.
18
19 """
20 Exception definitions.
21 """
22
23 ########################################################################
24 #
25 # THIS MODULE IS DEPRECATED
26 #
27 # Please refer to
28 # https://etherpad.openstack.org/p/kilo-escalatorclient-library-proposals for
29 # the discussion leading to this deprecation.
30 #
31 # We recommend checking out the python-openstacksdk project
32 # (https://launchpad.net/python-openstacksdk) instead.
33 #
34 ########################################################################
35
36 import inspect
37 import sys
38
39 import six
40
41 from escalatorclient.openstack.common._i18n import _
42
43
44 class ClientException(Exception):
45     """The base exception class for all exceptions this library raises.
46     """
47     pass
48
49
50 class ValidationError(ClientException):
51     """Error in validation on API client side."""
52     pass
53
54
55 class UnsupportedVersion(ClientException):
56     """User is trying to use an unsupported version of the API."""
57     pass
58
59
60 class CommandError(ClientException):
61     """Error in CLI tool."""
62     pass
63
64
65 class AuthorizationFailure(ClientException):
66     """Cannot authorize API client."""
67     pass
68
69
70 class ConnectionError(ClientException):
71     """Cannot connect to API service."""
72     pass
73
74
75 class ConnectionRefused(ConnectionError):
76     """Connection refused while trying to connect to API service."""
77     pass
78
79
80 class AuthPluginOptionsMissing(AuthorizationFailure):
81     """Auth plugin misses some options."""
82     def __init__(self, opt_names):
83         super(AuthPluginOptionsMissing, self).__init__(
84             _("Authentication failed. Missing options: %s") %
85             ", ".join(opt_names))
86         self.opt_names = opt_names
87
88
89 class AuthSystemNotFound(AuthorizationFailure):
90     """User has specified an AuthSystem that is not installed."""
91     def __init__(self, auth_system):
92         super(AuthSystemNotFound, self).__init__(
93             _("AuthSystemNotFound: %r") % auth_system)
94         self.auth_system = auth_system
95
96
97 class NoUniqueMatch(ClientException):
98     """Multiple entities found instead of one."""
99     pass
100
101
102 class EndpointException(ClientException):
103     """Something is rotten in Service Catalog."""
104     pass
105
106
107 class EndpointNotFound(EndpointException):
108     """Could not find requested endpoint in Service Catalog."""
109     pass
110
111
112 class AmbiguousEndpoints(EndpointException):
113     """Found more than one matching endpoint in Service Catalog."""
114     def __init__(self, endpoints=None):
115         super(AmbiguousEndpoints, self).__init__(
116             _("AmbiguousEndpoints: %r") % endpoints)
117         self.endpoints = endpoints
118
119
120 class HttpError(ClientException):
121     """The base exception class for all HTTP exceptions.
122     """
123     http_status = 0
124     message = _("HTTP Error")
125
126     def __init__(self, message=None, details=None,
127                  response=None, request_id=None,
128                  url=None, method=None, http_status=None):
129         self.http_status = http_status or self.http_status
130         self.message = message or self.message
131         self.details = details
132         self.request_id = request_id
133         self.response = response
134         self.url = url
135         self.method = method
136         formatted_string = "%s (HTTP %s)" % (self.message, self.http_status)
137         if request_id:
138             formatted_string += " (Request-ID: %s)" % request_id
139         super(HttpError, self).__init__(formatted_string)
140
141
142 class HTTPRedirection(HttpError):
143     """HTTP Redirection."""
144     message = _("HTTP Redirection")
145
146
147 class HTTPClientError(HttpError):
148     """Client-side HTTP error.
149
150     Exception for cases in which the client seems to have erred.
151     """
152     message = _("HTTP Client Error")
153
154
155 class HttpServerError(HttpError):
156     """Server-side HTTP error.
157
158     Exception for cases in which the server is aware that it has
159     erred or is incapable of performing the request.
160     """
161     message = _("HTTP Server Error")
162
163
164 class MultipleChoices(HTTPRedirection):
165     """HTTP 300 - Multiple Choices.
166
167     Indicates multiple options for the resource that the client may follow.
168     """
169
170     http_status = 300
171     message = _("Multiple Choices")
172
173
174 class BadRequest(HTTPClientError):
175     """HTTP 400 - Bad Request.
176
177     The request cannot be fulfilled due to bad syntax.
178     """
179     http_status = 400
180     message = _("Bad Request")
181
182
183 class Unauthorized(HTTPClientError):
184     """HTTP 401 - Unauthorized.
185
186     Similar to 403 Forbidden, but specifically for use when authentication
187     is required and has failed or has not yet been provided.
188     """
189     http_status = 401
190     message = _("Unauthorized")
191
192
193 class PaymentRequired(HTTPClientError):
194     """HTTP 402 - Payment Required.
195
196     Reserved for future use.
197     """
198     http_status = 402
199     message = _("Payment Required")
200
201
202 class Forbidden(HTTPClientError):
203     """HTTP 403 - Forbidden.
204
205     The request was a valid request, but the server is refusing to respond
206     to it.
207     """
208     http_status = 403
209     message = _("Forbidden")
210
211
212 class NotFound(HTTPClientError):
213     """HTTP 404 - Not Found.
214
215     The requested resource could not be found but may be available again
216     in the future.
217     """
218     http_status = 404
219     message = _("Not Found")
220
221
222 class MethodNotAllowed(HTTPClientError):
223     """HTTP 405 - Method Not Allowed.
224
225     A request was made of a resource using a request method not supported
226     by that resource.
227     """
228     http_status = 405
229     message = _("Method Not Allowed")
230
231
232 class NotAcceptable(HTTPClientError):
233     """HTTP 406 - Not Acceptable.
234
235     The requested resource is only capable of generating content not
236     acceptable according to the Accept headers sent in the request.
237     """
238     http_status = 406
239     message = _("Not Acceptable")
240
241
242 class ProxyAuthenticationRequired(HTTPClientError):
243     """HTTP 407 - Proxy Authentication Required.
244
245     The client must first authenticate itself with the proxy.
246     """
247     http_status = 407
248     message = _("Proxy Authentication Required")
249
250
251 class RequestTimeout(HTTPClientError):
252     """HTTP 408 - Request Timeout.
253
254     The server timed out waiting for the request.
255     """
256     http_status = 408
257     message = _("Request Timeout")
258
259
260 class Conflict(HTTPClientError):
261     """HTTP 409 - Conflict.
262
263     Indicates that the request could not be processed because of conflict
264     in the request, such as an edit conflict.
265     """
266     http_status = 409
267     message = _("Conflict")
268
269
270 class Gone(HTTPClientError):
271     """HTTP 410 - Gone.
272
273     Indicates that the resource requested is no longer available and will
274     not be available again.
275     """
276     http_status = 410
277     message = _("Gone")
278
279
280 class LengthRequired(HTTPClientError):
281     """HTTP 411 - Length Required.
282
283     The request did not specify the length of its content, which is
284     required by the requested resource.
285     """
286     http_status = 411
287     message = _("Length Required")
288
289
290 class PreconditionFailed(HTTPClientError):
291     """HTTP 412 - Precondition Failed.
292
293     The server does not meet one of the preconditions that the requester
294     put on the request.
295     """
296     http_status = 412
297     message = _("Precondition Failed")
298
299
300 class RequestEntityTooLarge(HTTPClientError):
301     """HTTP 413 - Request Entity Too Large.
302
303     The request is larger than the server is willing or able to process.
304     """
305     http_status = 413
306     message = _("Request Entity Too Large")
307
308     def __init__(self, *args, **kwargs):
309         try:
310             self.retry_after = int(kwargs.pop('retry_after'))
311         except (KeyError, ValueError):
312             self.retry_after = 0
313
314         super(RequestEntityTooLarge, self).__init__(*args, **kwargs)
315
316
317 class RequestUriTooLong(HTTPClientError):
318     """HTTP 414 - Request-URI Too Long.
319
320     The URI provided was too long for the server to process.
321     """
322     http_status = 414
323     message = _("Request-URI Too Long")
324
325
326 class UnsupportedMediaType(HTTPClientError):
327     """HTTP 415 - Unsupported Media Type.
328
329     The request entity has a media type which the server or resource does
330     not support.
331     """
332     http_status = 415
333     message = _("Unsupported Media Type")
334
335
336 class RequestedRangeNotSatisfiable(HTTPClientError):
337     """HTTP 416 - Requested Range Not Satisfiable.
338
339     The client has asked for a portion of the file, but the server cannot
340     supply that portion.
341     """
342     http_status = 416
343     message = _("Requested Range Not Satisfiable")
344
345
346 class ExpectationFailed(HTTPClientError):
347     """HTTP 417 - Expectation Failed.
348
349     The server cannot meet the requirements of the Expect request-header field.
350     """
351     http_status = 417
352     message = _("Expectation Failed")
353
354
355 class UnprocessableEntity(HTTPClientError):
356     """HTTP 422 - Unprocessable Entity.
357
358     The request was well-formed but was unable to be followed due to semantic
359     errors.
360     """
361     http_status = 422
362     message = _("Unprocessable Entity")
363
364
365 class InternalServerError(HttpServerError):
366     """HTTP 500 - Internal Server Error.
367
368     A generic error message, given when no more specific message is suitable.
369     """
370     http_status = 500
371     message = _("Internal Server Error")
372
373
374 # NotImplemented is a python keyword.
375 class HttpNotImplemented(HttpServerError):
376     """HTTP 501 - Not Implemented.
377
378     The server either does not recognize the request method, or it lacks
379     the ability to fulfill the request.
380     """
381     http_status = 501
382     message = _("Not Implemented")
383
384
385 class BadGateway(HttpServerError):
386     """HTTP 502 - Bad Gateway.
387
388     The server was acting as a gateway or proxy and received an invalid
389     response from the upstream server.
390     """
391     http_status = 502
392     message = _("Bad Gateway")
393
394
395 class ServiceUnavailable(HttpServerError):
396     """HTTP 503 - Service Unavailable.
397
398     The server is currently unavailable.
399     """
400     http_status = 503
401     message = _("Service Unavailable")
402
403
404 class GatewayTimeout(HttpServerError):
405     """HTTP 504 - Gateway Timeout.
406
407     The server was acting as a gateway or proxy and did not receive a timely
408     response from the upstream server.
409     """
410     http_status = 504
411     message = _("Gateway Timeout")
412
413
414 class HttpVersionNotSupported(HttpServerError):
415     """HTTP 505 - HttpVersion Not Supported.
416
417     The server does not support the HTTP protocol version used in the request.
418     """
419     http_status = 505
420     message = _("HTTP Version Not Supported")
421
422
423 # _code_map contains all the classes that have http_status attribute.
424 _code_map = dict(
425     (getattr(obj, 'http_status', None), obj)
426     for name, obj in six.iteritems(vars(sys.modules[__name__]))
427     if inspect.isclass(obj) and getattr(obj, 'http_status', False)
428 )
429
430
431 def from_response(response, method, url):
432     """Returns an instance of :class:`HttpError` or subclass based on response.
433
434     :param response: instance of `requests.Response` class
435     :param method: HTTP method used for request
436     :param url: URL used for request
437     """
438
439     req_id = response.headers.get("x-openstack-request-id")
440     # NOTE(hdd) true for older versions of nova and cinder
441     if not req_id:
442         req_id = response.headers.get("x-compute-request-id")
443     kwargs = {
444         "http_status": response.status_code,
445         "response": response,
446         "method": method,
447         "url": url,
448         "request_id": req_id,
449     }
450     if "retry-after" in response.headers:
451         kwargs["retry_after"] = response.headers["retry-after"]
452
453     content_type = response.headers.get("Content-Type", "")
454     if content_type.startswith("application/json"):
455         try:
456             body = response.json()
457         except ValueError:
458             pass
459         else:
460             if isinstance(body, dict):
461                 error = body.get(list(body)[0])
462                 if isinstance(error, dict):
463                     kwargs["message"] = (error.get("message") or
464                                          error.get("faultstring"))
465                     kwargs["details"] = (error.get("details") or
466                                          six.text_type(body))
467     elif content_type.startswith("text/"):
468         kwargs["details"] = response.text
469
470     try:
471         cls = _code_map[response.status_code]
472     except KeyError:
473         if 500 <= response.status_code < 600:
474             cls = HttpServerError
475         elif 400 <= response.status_code < 500:
476             cls = HTTPClientError
477         else:
478             cls = HttpError
479     return cls(**kwargs)