Fix client error handling

Whenever server returns error, an exception is thrown. This is because pecan is
returning json attributes that is not recongnized by apiclient. So fix apiclient
to accept pecan response.

Fixes-bug: #1293379

Change-Id: I5d8be574143c808a8e236d19c899358d784fc245
This commit is contained in:
Noorul Islam K M
2014-03-23 17:07:59 +05:30
parent d126a4f7f2
commit 40ffc17c85
7 changed files with 240 additions and 5 deletions

View File

@@ -13,7 +13,8 @@
# under the License.
from solumclient.common import auth
from solumclient.openstack.common.apiclient import client
from solumclient.common import client
from solumclient.openstack.common.apiclient import client as api_client
API_NAME = 'builder'
VERSION_MAP = {
@@ -22,7 +23,8 @@ VERSION_MAP = {
def Client(version, **kwargs):
client_class = client.BaseClient.get_class(API_NAME, version, VERSION_MAP)
client_class = api_client.BaseClient.get_class(API_NAME, version,
VERSION_MAP)
keystone_auth = auth.KeystoneAuthPlugin(
username=kwargs.get('username'),
password=kwargs.get('password'),

View File

@@ -13,7 +13,8 @@
# under the License.
from solumclient.common import auth
from solumclient.openstack.common.apiclient import client
from solumclient.common import client
from solumclient.openstack.common.apiclient import client as api_client
API_NAME = 'solum'
VERSION_MAP = {
@@ -22,7 +23,8 @@ VERSION_MAP = {
def Client(version, **kwargs):
client_class = client.BaseClient.get_class(API_NAME, version, VERSION_MAP)
client_class = api_client.BaseClient.get_class(API_NAME, version,
VERSION_MAP)
keystone_auth = auth.KeystoneAuthPlugin(
username=kwargs.get('username'),
password=kwargs.get('password'),

View File

@@ -0,0 +1,65 @@
# Copyright 2013 - Noorul Islam K M
#
# 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 logging
import time
from solumclient.common import exc
from solumclient.openstack.common.apiclient import client as api_client
_logger = logging.getLogger(__name__)
class HTTPClient(api_client.HTTPClient):
def request(self, method, url, **kwargs):
"""Send an http request with the specified characteristics.
Wrapper around `requests.Session.request` to handle tasks such as
setting headers, JSON encoding/decoding, and error handling.
:param method: method of HTTP request
:param url: URL of HTTP request
:param kwargs: any other parameter that can be passed to
' requests.Session.request (such as `headers`) or `json`
that will be encoded as JSON and used as `data` argument
"""
kwargs.setdefault("headers", kwargs.get("headers", {}))
kwargs["headers"]["User-Agent"] = self.user_agent
if self.original_ip:
kwargs["headers"]["Forwarded"] = "for=%s;by=%s" % (
self.original_ip, self.user_agent)
if self.timeout is not None:
kwargs.setdefault("timeout", self.timeout)
kwargs.setdefault("verify", self.verify)
if self.cert is not None:
kwargs.setdefault("cert", self.cert)
self.serialize(kwargs)
self._http_log_req(method, url, kwargs)
if self.timings:
start_time = time.time()
resp = self.http.request(method, url, **kwargs)
if self.timings:
self.times.append(("%s %s" % (method, url),
start_time, time.time()))
self._http_log_resp(resp)
if resp.status_code >= 400:
_logger.debug(
"Request returned failure status: %s",
resp.status_code)
raise exc.from_response(resp, method, url)
return resp

View File

@@ -12,6 +12,8 @@
# License for the specific language governing permissions and limitations
# under the License.
from solumclient.openstack.common.apiclient import exceptions
class BaseException(Exception):
"""An error occurred."""
@@ -24,3 +26,45 @@ class BaseException(Exception):
class CommandError(BaseException):
"""Invalid usage of CLI."""
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 isinstance(body, dict):
kwargs["message"] = body.get("faultstring")
kwargs["details"] = body.get("debuginfo")
elif content_type.startswith("text/"):
kwargs["details"] = response.text
try:
cls = exceptions._code_map[response.status_code]
except KeyError:
if 500 <= response.status_code < 600:
cls = exceptions.HttpServerError
elif 400 <= response.status_code < 500:
cls = exceptions.HTTPClientError
else:
cls = exceptions.HttpError
return cls(**kwargs)

View File

@@ -16,7 +16,7 @@ from keystoneclient.v2_0 import client as ksclient
import mock
from solumclient.common import auth
from solumclient.openstack.common.apiclient import client
from solumclient.common import client
from solumclient.tests import base

View File

@@ -0,0 +1,69 @@
# Copyright 2013 - Noorul Islam K M
#
# 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 mock
import requests
from solumclient.common import client
from solumclient.openstack.common.apiclient import auth
from solumclient.openstack.common.apiclient import client as api_client
from solumclient.openstack.common.apiclient import exceptions
from solumclient.tests import base
class TestClient(api_client.BaseClient):
service_type = "test"
class FakeAuthPlugin(auth.BaseAuthPlugin):
auth_system = "fake"
attempt = -1
def _do_authenticate(self, http_client):
pass
def token_and_endpoint(self, endpoint_type, service_type):
self.attempt = self.attempt + 1
return ("token-%s" % self.attempt, "/endpoint-%s" % self.attempt)
class ClientTest(base.TestCase):
def test_client_request(self):
http_client = client.HTTPClient(FakeAuthPlugin())
mock_request = mock.Mock()
mock_request.return_value = requests.Response()
mock_request.return_value.status_code = 200
with mock.patch("requests.Session.request", mock_request):
http_client.client_request(
TestClient(http_client), "GET", "/resource", json={"1": "2"})
requests.Session.request.assert_called_with(
"GET",
"/endpoint-0/resource",
headers={
"User-Agent": http_client.user_agent,
"Content-Type": "application/json",
"X-Auth-Token": "token-0"
},
data='{"1": "2"}',
verify=True)
def test_client_with_response_404_status_code(self):
http_client = client.HTTPClient(FakeAuthPlugin())
mock_request = mock.Mock()
mock_request.return_value = requests.Response()
mock_request.return_value.status_code = 404
with mock.patch("requests.Session.request", mock_request):
self.assertRaises(
exceptions.HttpError, http_client.client_request,
TestClient(http_client), "GET", "/resource")

View File

@@ -0,0 +1,53 @@
# Copyright 2013 - Noorul Islam K M
#
# 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 six
from solumclient.common import exc
from solumclient.openstack.common.apiclient import exceptions
from solumclient.tests import base
class FakeResponse(object):
json_data = {}
def __init__(self, **kwargs):
for key, value in six.iteritems(kwargs):
setattr(self, key, value)
def json(self):
return self.json_data
class ExceptionTest(base.TestCase):
def test_from_response_with_status_code_404(self):
json_data = {"faultstring": "fake message",
"debuginfo": "fake details"}
method = 'GET'
status_code = 404
url = 'http://example.com:9777/v1/assemblies/fake-id'
ex = exc.from_response(
FakeResponse(status_code=status_code,
headers={"Content-Type": "application/json"},
json_data=json_data),
method,
url
)
self.assertTrue(isinstance(ex, exceptions.HttpError))
self.assertEqual(json_data["faultstring"], ex.message)
self.assertEqual(json_data["debuginfo"], ex.details)
self.assertEqual(method, ex.method)
self.assertEqual(url, ex.url)
self.assertEqual(status_code, ex.http_status)