2013-07-15 16:25:40 +00:00
|
|
|
# Copyright 2010 Jacob Kaplan-Moss
|
2013-08-06 13:36:45 -03:00
|
|
|
#
|
|
|
|
# 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.
|
|
|
|
|
2012-05-21 16:32:35 -04:00
|
|
|
"""
|
2013-07-15 16:25:40 +00:00
|
|
|
Exception definitions.
|
2012-05-21 16:32:35 -04:00
|
|
|
"""
|
2016-06-22 17:36:49 +03:00
|
|
|
from datetime import datetime
|
|
|
|
|
|
|
|
from oslo_utils import timeutils
|
2012-05-21 16:32:35 -04:00
|
|
|
|
2013-07-15 16:25:40 +00:00
|
|
|
|
2017-06-19 16:27:49 -04:00
|
|
|
class ResourceInErrorState(Exception):
|
|
|
|
"""When resource is in Error state"""
|
|
|
|
def __init__(self, obj, fault_msg):
|
|
|
|
msg = "'%s' resource is in the error state" % obj.__class__.__name__
|
|
|
|
if fault_msg:
|
|
|
|
msg += " due to '%s'" % fault_msg
|
|
|
|
self.message = "%s." % msg
|
|
|
|
|
|
|
|
def __str__(self):
|
|
|
|
return self.message
|
|
|
|
|
|
|
|
|
|
|
|
class TimeoutException(Exception):
|
|
|
|
"""When an action exceeds the timeout period to complete the action"""
|
|
|
|
def __init__(self, obj, action):
|
|
|
|
self.message = ("The '%(action)s' of the '%(object_name)s' exceeded "
|
|
|
|
"the timeout period." % {"action": action,
|
|
|
|
"object_name": obj.__class__.__name__})
|
|
|
|
|
|
|
|
def __str__(self):
|
|
|
|
return self.message
|
|
|
|
|
|
|
|
|
2013-07-15 16:25:40 +00:00
|
|
|
class UnsupportedVersion(Exception):
|
|
|
|
"""Indicates that the user is trying to use an unsupported
|
|
|
|
version of the API.
|
|
|
|
"""
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2016-07-10 19:08:06 +08:00
|
|
|
class UnsupportedAttribute(AttributeError):
|
|
|
|
"""Indicates that the user is trying to transmit the argument to a method,
|
|
|
|
which is not supported by selected version.
|
|
|
|
"""
|
|
|
|
|
|
|
|
def __init__(self, argument_name, start_version, end_version):
|
2016-08-05 14:45:28 +02:00
|
|
|
if start_version and end_version:
|
2016-07-10 19:08:06 +08:00
|
|
|
self.message = (
|
|
|
|
"'%(name)s' argument is only allowed for microversions "
|
|
|
|
"%(start)s - %(end)s." % {"name": argument_name,
|
|
|
|
"start": start_version.get_string(),
|
|
|
|
"end": end_version.get_string()})
|
2016-08-05 14:45:28 +02:00
|
|
|
elif start_version:
|
2016-07-10 19:08:06 +08:00
|
|
|
self.message = (
|
|
|
|
"'%(name)s' argument is only allowed since microversion "
|
|
|
|
"%(start)s." % {"name": argument_name,
|
|
|
|
"start": start_version.get_string()})
|
|
|
|
|
2016-08-05 14:45:28 +02:00
|
|
|
elif end_version:
|
2016-07-10 19:08:06 +08:00
|
|
|
self.message = (
|
|
|
|
"'%(name)s' argument is not allowed after microversion "
|
|
|
|
"%(end)s." % {"name": argument_name,
|
|
|
|
"end": end_version.get_string()})
|
|
|
|
|
|
|
|
def __str__(self):
|
|
|
|
return self.message
|
|
|
|
|
|
|
|
|
2013-07-15 16:25:40 +00:00
|
|
|
class InvalidAPIVersion(Exception):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class CommandError(Exception):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class AuthorizationFailure(Exception):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class NoUniqueMatch(Exception):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2014-02-14 13:42:58 -06:00
|
|
|
class AuthSystemNotFound(Exception):
|
2014-05-02 18:32:06 +02:00
|
|
|
"""When the user specifies an AuthSystem but not installed."""
|
2014-02-14 13:42:58 -06:00
|
|
|
def __init__(self, auth_system):
|
|
|
|
self.auth_system = auth_system
|
|
|
|
|
|
|
|
def __str__(self):
|
|
|
|
return "AuthSystemNotFound: %s" % repr(self.auth_system)
|
|
|
|
|
|
|
|
|
2013-07-15 16:25:40 +00:00
|
|
|
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
|
|
|
|
|
|
|
|
|
2013-08-06 11:59:34 +02:00
|
|
|
class ConnectionError(Exception):
|
|
|
|
"""Could not open a connection to the API service."""
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2013-07-15 16:25:40 +00:00
|
|
|
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.
|
|
|
|
"""
|
2016-06-22 17:36:49 +03:00
|
|
|
def __init__(self, code, message=None, details=None,
|
|
|
|
request_id=None, response=None):
|
2013-07-15 16:25:40 +00:00
|
|
|
self.code = code
|
2015-08-04 14:20:53 -07:00
|
|
|
# NOTE(mriedem): Use getattr on self.__class__.message since
|
|
|
|
# BaseException.message was dropped in python 3, see PEP 0352.
|
|
|
|
self.message = message or getattr(self.__class__, 'message', None)
|
2013-07-15 16:25:40 +00:00
|
|
|
self.details = details
|
|
|
|
self.request_id = request_id
|
|
|
|
|
|
|
|
def __str__(self):
|
2016-02-23 17:55:08 -05:00
|
|
|
formatted_string = "%s" % self.message
|
|
|
|
if self.code >= 100:
|
|
|
|
# HTTP codes start at 100.
|
|
|
|
formatted_string += " (HTTP %s)" % self.code
|
2013-07-15 16:25:40 +00:00
|
|
|
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"
|
|
|
|
|
|
|
|
|
2016-04-05 15:45:28 -06:00
|
|
|
class NotAcceptable(ClientException):
|
|
|
|
"""
|
|
|
|
HTTP 406 - Not Acceptable
|
|
|
|
"""
|
|
|
|
http_status = 406
|
|
|
|
message = "Not Acceptable"
|
|
|
|
|
|
|
|
|
2013-07-15 16:25:40 +00:00
|
|
|
class OverLimit(ClientException):
|
|
|
|
"""
|
|
|
|
HTTP 413 - Over limit: you're over the API limits for this time period.
|
|
|
|
"""
|
|
|
|
http_status = 413
|
|
|
|
message = "Over limit"
|
|
|
|
|
2016-06-22 17:36:49 +03:00
|
|
|
def __init__(self, code, message=None, details=None,
|
|
|
|
request_id=None, response=None):
|
|
|
|
super(OverLimit, self).__init__(code, message=message,
|
|
|
|
details=details, request_id=request_id,
|
|
|
|
response=response)
|
|
|
|
self.retry_after = 0
|
|
|
|
self._get_rate_limit(response)
|
|
|
|
|
|
|
|
def _get_rate_limit(self, resp):
|
2016-07-06 16:06:28 +02:00
|
|
|
if (resp is not None) and resp.headers:
|
2016-06-22 17:36:49 +03:00
|
|
|
utc_now = timeutils.utcnow()
|
|
|
|
value = resp.headers.get('Retry-After', '0')
|
|
|
|
try:
|
|
|
|
value = datetime.strptime(value, '%a, %d %b %Y %H:%M:%S %Z')
|
|
|
|
if value > utc_now:
|
|
|
|
self.retry_after = ((value - utc_now).seconds)
|
|
|
|
else:
|
|
|
|
self.retry_after = 0
|
|
|
|
except ValueError:
|
|
|
|
self.retry_after = int(value)
|
|
|
|
|
2013-07-15 16:25:40 +00:00
|
|
|
|
|
|
|
# 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,
|
2016-04-05 15:45:28 -06:00
|
|
|
NotAcceptable,
|
2013-07-15 16:25:40 +00:00
|
|
|
OverLimit, HTTPNotImplemented])
|
|
|
|
|
|
|
|
|
|
|
|
def from_response(response, body):
|
|
|
|
"""
|
2015-04-30 18:28:21 +08:00
|
|
|
Return an instance of a ClientException or subclass
|
|
|
|
based on a requests response.
|
2013-07-15 16:25:40 +00:00
|
|
|
|
|
|
|
Usage::
|
|
|
|
|
|
|
|
resp, body = requests.request(...)
|
|
|
|
if resp.status_code != 200:
|
2015-04-30 18:28:21 +08:00
|
|
|
raise exceptions.from_response(resp, resp.text)
|
2013-07-15 16:25:40 +00:00
|
|
|
"""
|
|
|
|
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'):
|
2016-12-06 14:10:38 +09:00
|
|
|
# Only in webob>=1.6.0
|
|
|
|
if 'message' in body:
|
|
|
|
message = body.get('message')
|
|
|
|
details = body.get('details')
|
|
|
|
else:
|
|
|
|
error = body[list(body)[0]]
|
|
|
|
message = error.get('message', message)
|
|
|
|
details = error.get('details', details)
|
2013-07-15 16:25:40 +00:00
|
|
|
return cls(code=response.status_code, message=message, details=details,
|
2016-06-22 17:36:49 +03:00
|
|
|
request_id=request_id, response=response)
|
2013-07-15 16:25:40 +00:00
|
|
|
else:
|
2013-11-07 11:28:05 +11:00
|
|
|
return cls(code=response.status_code, request_id=request_id,
|
2016-06-22 17:36:49 +03:00
|
|
|
message=response.reason, response=response)
|
2016-04-05 15:45:28 -06:00
|
|
|
|
|
|
|
|
|
|
|
class VersionNotFoundForAPIMethod(Exception):
|
|
|
|
msg_fmt = "API version '%(vers)s' is not supported on '%(method)s' method."
|
|
|
|
|
|
|
|
def __init__(self, version, method):
|
|
|
|
self.version = version
|
|
|
|
self.method = method
|
|
|
|
|
|
|
|
def __str__(self):
|
|
|
|
return self.msg_fmt % {"vers": self.version, "method": self.method}
|