Convert keystoneauth exceptions by ours

This change creates gnocchiclient exceptions and convert
keystoneauth exception into our exceptions.

Change-Id: I05f059d1d9a7d54740eef8049c5688a41f063bae
This commit is contained in:
Mehdi Abaakouk 2015-09-30 09:50:24 +02:00
parent 071212b241
commit c4b3be9d8e
8 changed files with 279 additions and 34 deletions

View File

@ -10,11 +10,30 @@
# License for the specific language governing permissions and limitations
# under the License.
from keystoneauth1 import adapter
from oslo_utils import importutils
from gnocchiclient import exceptions
def Client(version, *args, **kwargs):
module = 'gnocchiclient.v%s.client' % version
module = importutils.import_module(module)
client_class = getattr(module, 'Client')
return client_class(*args, **kwargs)
class SessionClient(adapter.Adapter):
def request(self, url, method, **kwargs):
kwargs.setdefault('headers', kwargs.get('headers', {}))
# NOTE(sileht): The standard call raises errors from
# keystoneauth, where we need to raise the gnocchiclient errors.
raise_exc = kwargs.pop('raise_exc', True)
resp = super(SessionClient, self).request(url,
method,
raise_exc=False,
**kwargs)
if raise_exc and resp.status_code >= 400:
raise exceptions.from_response(resp, url, method)
return resp

200
gnocchiclient/exceptions.py Normal file
View File

@ -0,0 +1,200 @@
#
# 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.
import re
class ClientException(Exception):
"""The base exception class for all exceptions this library raises."""
message = 'Unknown Error'
def __init__(self, code, message=None, request_id=None,
url=None, method=None):
self.code = code
self.message = message or self.__class__.message
self.request_id = request_id
self.url = url
self.method = method
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 RetryAfterException(ClientException):
"""The base exception for ClientExceptions that use Retry-After header."""
def __init__(self, *args, **kwargs):
try:
self.retry_after = int(kwargs.pop('retry_after'))
except (KeyError, ValueError):
self.retry_after = 0
super(RetryAfterException, self).__init__(*args, **kwargs)
class MutipleMeaningException(object):
"""An mixin for exception that can be enhanced by reading the details"""
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 MetricNotFound(NotFound, MutipleMeaningException):
message = "Metric not found"
match = re.compile("Metric .* does not exist")
class ResourceNotFound(NotFound, MutipleMeaningException):
message = "Resource not found"
match = re.compile("Resource .* does not exist")
class ArchivePolicyNotFound(NotFound, MutipleMeaningException):
message = "Archive policy not found"
match = re.compile("Archive policy .* does not exist")
class ArchivePolicyRuleNotFound(NotFound, MutipleMeaningException):
message = "Archive policy rule not found"
match = re.compile("Archive policy rule .* does not exist")
class MethodNotAllowed(ClientException):
"""HTTP 405 - Method Not Allowed"""
http_status = 405
message = "Method Not Allowed"
class NotAcceptable(ClientException):
"""HTTP 406 - Not Acceptable"""
http_status = 406
message = "Not Acceptable"
class Conflict(ClientException):
"""HTTP 409 - Conflict"""
http_status = 409
message = "Conflict"
class OverLimit(RetryAfterException):
"""HTTP 413 - Over limit:
you're over the API limits for this time period.
"""
http_status = 413
message = "Over limit"
class RateLimit(RetryAfterException):
"""HTTP 429 - Rate limit:
you've sent too many requests for this time period.
"""
http_status = 429
message = "Rate limit"
class NotImplemented(ClientException):
"""HTTP 501 - Not Implemented:
the server does not support this operation.
"""
http_status = 501
message = "Not Implemented"
_error_classes = [BadRequest, Unauthorized, Forbidden, NotFound,
MethodNotAllowed, NotAcceptable, Conflict, OverLimit,
RateLimit, NotImplemented]
_error_classes_enhanced = {
NotFound: [MetricNotFound, ResourceNotFound, ArchivePolicyNotFound,
ArchivePolicyRuleNotFound],
}
_code_map = dict(
(c.http_status, (c, _error_classes_enhanced.get(c, [])))
for c in _error_classes)
def from_response(response, url, method=None):
"""Return an instance of one of the ClientException on an requests response.
Usage::
resp, body = requests.request(...)
if resp.status_code != 200:
raise exception_from_response(resp)
"""
if response.status_code:
cls, enhanced_classes = _code_map.get(response.status_code,
(ClientException, []))
req_id = response.headers.get("x-openstack-request-id")
content_type = response.headers.get("Content-Type", "").split(";")[0]
kwargs = {
'code': response.status_code,
'method': method,
'url': url,
'request_id': req_id,
}
if "retry-after" in response.headers:
kwargs['retry_after'] = response.headers.get('retry-after')
if content_type == "application/json":
try:
body = response.json()
except ValueError:
pass
else:
desc = body.get('description')
for enhanced_cls in enhanced_classes:
if enhanced_cls.match.match(desc):
cls = enhanced_cls
break
kwargs['message'] = desc
elif content_type.startswith("text/"):
kwargs['message'] = response.text
if not kwargs['message']:
del kwargs['message']
return cls(**kwargs)

View File

@ -20,7 +20,6 @@ import warnings
from cliff import app
from cliff import commandmanager
from keystoneauth1 import adapter
from keystoneauth1 import exceptions
from keystoneauth1 import loading
@ -145,13 +144,11 @@ class GnocchiShell(app.App):
session = loading.load_session_from_argparse_arguments(
self.options, auth=auth_plugin)
session = adapter.Adapter(session, service_type='metric',
interface=self.options.interface,
region_name=self.options.region_name,
endpoint_override=endpoint_override)
self._client = client.Client(self.options.gnocchi_api_version,
session=session)
session=session,
interface=self.options.interface,
region_name=self.options.region_name,
endpoint_override=endpoint_override)
return self._client
def clean_up(self, cmd, result, err):

View File

@ -47,12 +47,14 @@ class ArchivePolicyClientTest(base.ClientTestBase):
result = self.gnocchi('archive-policy',
params="show low",
fail_ok=True, merge_stderr=True)
self.assertFirstLineStartsWith(result.split('\n'),
"Not Found (HTTP 404)")
self.assertFirstLineStartsWith(
result.split('\n'),
"Archive policy low does not exist (HTTP 404)")
# DELETE FAIL
result = self.gnocchi('archive-policy',
params="delete low",
fail_ok=True, merge_stderr=True)
self.assertFirstLineStartsWith(result.split('\n'),
"Not Found (HTTP 404)")
self.assertFirstLineStartsWith(
result.split('\n'),
"Archive policy low does not exist (HTTP 404)")

View File

@ -46,12 +46,14 @@ class ArchivePolicyRuleClientTest(base.ClientTestBase):
result = self.gnocchi('archive-policy-rule',
params="show test",
fail_ok=True, merge_stderr=True)
self.assertFirstLineStartsWith(result.split('\n'),
"Not Found (HTTP 404)")
self.assertFirstLineStartsWith(
result.split('\n'),
"Archive policy rule test does not exist (HTTP 404)")
# DELETE FAIL
result = self.gnocchi('archive-policy-rule',
params="delete test",
fail_ok=True, merge_stderr=True)
self.assertFirstLineStartsWith(result.split('\n'),
"Not Found (HTTP 404)")
self.assertFirstLineStartsWith(
result.split('\n'),
"Archive policy rule test does not exist (HTTP 404)")

View File

@ -40,11 +40,13 @@ class MetricClientTest(base.ClientTestBase):
result = self.gnocchi('metric', params="show %s" % metric1["id"],
fail_ok=True, merge_stderr=True)
self.assertFirstLineStartsWith(result.split('\n'),
"Not Found (HTTP 404)")
"Metric %s does not exist (HTTP 404)" %
metric1["id"])
result = self.gnocchi('metric', params="show %s" % metric2["id"],
fail_ok=True, merge_stderr=True)
self.assertFirstLineStartsWith(result.split('\n'),
"Not Found (HTTP 404)")
"Metric %s does not exist (HTTP 404)" %
metric2["id"])
def test_metric_scenario(self):
# PREPARE AN ACHIVE POLICY
@ -138,14 +140,16 @@ class MetricClientTest(base.ClientTestBase):
# GET FAIL
result = self.gnocchi('metric', params="show %s" % metric["id"],
fail_ok=True, merge_stderr=True)
self.assertFirstLineStartsWith(result.split('\n'),
"Not Found (HTTP 404)")
self.assertFirstLineStartsWith(
result.split('\n'),
"Metric %s does not exist (HTTP 404)" % metric["id"])
# DELETE FAIL
result = self.gnocchi('metric', params="delete %s" % metric["id"],
fail_ok=True, merge_stderr=True)
self.assertFirstLineStartsWith(result.split('\n'),
"Not Found (HTTP 404)")
self.assertFirstLineStartsWith(
result.split('\n'),
"Metric %s does not exist (HTTP 404)" % metric["id"])
def test_metric_by_name_scenario(self):
# PREPARE REQUIREMENT
@ -230,12 +234,31 @@ class MetricClientTest(base.ClientTestBase):
result = self.gnocchi('metric',
params="show -r metric-res metric-name",
fail_ok=True, merge_stderr=True)
self.assertFirstLineStartsWith(result.split('\n'),
"Not Found (HTTP 404)")
self.assertFirstLineStartsWith(
result.split('\n'),
"Metric metric-name does not exist (HTTP 404)")
# DELETE FAIL
result = self.gnocchi('metric',
params="delete -r metric-res metric-name",
fail_ok=True, merge_stderr=True)
self.assertFirstLineStartsWith(result.split('\n'),
"Not Found (HTTP 404)")
self.assertFirstLineStartsWith(
result.split('\n'),
"Metric metric-name does not exist (HTTP 404)")
# GET RESOURCE ID
result = self.gnocchi(
'resource', params="show -t generic metric-res")
resource_id = self.details_multiple(result)[0]["id"]
# DELETE RESOURCE
result = self.gnocchi('resource', params="delete metric-res")
self.assertEqual("", result)
# GET FAIL WITH RESOURCE ERROR
result = self.gnocchi('metric',
params="show metric-name -r metric-res",
fail_ok=True, merge_stderr=True)
self.assertFirstLineStartsWith(
result.split('\n'),
"Resource %s does not exist (HTTP 404)" % resource_id)

View File

@ -119,15 +119,17 @@ class ResourceClientTest(base.ClientTestBase):
params="show --type generic %s" %
self.RESOURCE_ID,
fail_ok=True, merge_stderr=True)
self.assertFirstLineStartsWith(result.split('\n'),
"Not Found (HTTP 404)")
self.assertFirstLineStartsWith(
result.split('\n'),
"Resource %s does not exist (HTTP 404)" % self.RESOURCE_ID)
# DELETE FAIL
result = self.gnocchi('resource',
params="delete %s" % self.RESOURCE_ID,
fail_ok=True, merge_stderr=True)
self.assertFirstLineStartsWith(result.split('\n'),
"Not Found (HTTP 404)")
self.assertFirstLineStartsWith(
result.split('\n'),
"Resource %s does not exist (HTTP 404)" % self.RESOURCE_ID)
# LIST EMPTY
result = self.gnocchi('resource', params="list -t generic")

View File

@ -13,6 +13,7 @@
# License for the specific language governing permissions and limitations
# under the License.
from gnocchiclient import client
from gnocchiclient.v1 import archive_policy
from gnocchiclient.v1 import archive_policy_rule
from gnocchiclient.v1 import capabilities
@ -28,11 +29,10 @@ class Client(object):
:type session: :py:class:`keystoneauth.adapter.Adapter`
"""
def __init__(self, session=None):
"""Initialize a new client for the Gnocchi v1 API.
"""
self.api = session
def __init__(self, session=None, service_type='metric', **kwargs):
"""Initialize a new client for the Gnocchi v1 API."""
self.api = client.SessionClient(session, service_type=service_type,
**kwargs)
self.resource = resource.ResourceManager(self)
self.archive_policy = archive_policy.ArchivePolicyManager(self)
self.archive_policy_rule = (