Rework exceptions

This patch is reworking the exceptions that can be raised from Sushy,
before this patch many errors such as HTTP 4XX or 5XX were failing
silently.

This patch add custom exceptions for cases such as ConnectionError and
HTTPError that can be raised from requests.

Change-Id: I57ff8dffdfe0ab763bd0194e1916db8ae7a7861d
This commit is contained in:
Lucas Alvares Gomes 2017-02-27 20:14:59 +00:00
parent fb5e1cc3f2
commit 56af7a5616
5 changed files with 125 additions and 12 deletions

View File

@ -19,6 +19,8 @@ import os
import requests
from sushy import exceptions
LOG = logging.getLogger(__name__)
@ -52,7 +54,20 @@ class Connector(object):
'the headers "%(headers)s" and data "%(data)s"',
{'method': method, 'url': url, 'headers': headers,
'data': data or ''})
return session.request(method, url, data=data)
try:
response = session.request(method, url, data=data)
except requests.ConnectionError as e:
raise exceptions.ConnectionError(url=url, error=e)
LOG.debug('Response: Status code: %d', response.status_code)
try:
response.raise_for_status()
except requests.HTTPError as e:
raise exceptions.HTTPError(
method=method, url=url, error=e,
status_code=e.response.status_code)
return response
def get(self, path='', data=None, headers=None):
return self._op('GET', path, data, headers)

View File

@ -15,29 +15,49 @@
class SushyError(Exception):
"""Basic exception for errors raised by Sushy"""
message = None
def __init__(self, message=None, **kwargs):
def __init__(self, **kwargs):
if self.message and kwargs:
self.message = self.message % kwargs
else:
self.message = message
super(SushyError, self).__init__(self.message)
class ResourceNotFoundError(SushyError):
message = 'Resource %(resource)s not found'
class ConnectionError(SushyError):
message = 'Unable to connect to %(url)s. Error: %(error)s'
class MissingAttributeError(SushyError):
message = 'The attribute %(attribute)s is missing in %(resource)s'
message = ('The attribute %(attribute)s is missing from the '
'resource %(resource)s')
class MissingActionError(SushyError):
message = 'The action %(action)s is missing in %(resource)s'
message = ('The action %(action)s is missing from the '
'resource %(resource)s')
class InvalidParameterValueError(SushyError):
message = ('The parameter "%(parameter)s" value "%(value)s" is invalid. '
'Valid values are: %(valid_values)s')
class HTTPError(SushyError):
"""Basic exception for HTTP errors"""
status_code = None
message = ('Error issuing a %(method)s request at %(url)s. '
'Error: %(error)s')
def __init__(self, status_code=None, **kwargs):
super(HTTPError, self).__init__(**kwargs)
if status_code is not None:
self.status_code = status_code
class ResourceNotFoundError(HTTPError):
status_code = 404
message = 'Resource %(resource)s not found'

View File

@ -16,7 +16,6 @@
import abc
import six
from six.moves import http_client
from sushy import exceptions
@ -31,9 +30,12 @@ class ResourceBase(object):
self.refresh()
def refresh(self):
resp = self._conn.get(path=self._path)
if resp.status_code == http_client.NOT_FOUND:
raise exceptions.ResourceNotFoundError(resource=self._path)
try:
resp = self._conn.get(path=self._path)
except exceptions.HTTPError as e:
if e.status_code == 404:
raise exceptions.ResourceNotFoundError(resource=self._path)
raise
self._json = resp.json()
self._parse_attributes()

View File

@ -0,0 +1,55 @@
# Copyright 2017 Red Hat, Inc.
# 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.
import mock
from sushy import exceptions
from sushy.resources import base as resource_base
from sushy.tests.unit import base
class BaseResouce(resource_base.ResourceBase):
def _parse_attributes(self):
pass
class ResourceBaseTestCase(base.TestCase):
def setUp(self):
super(ResourceBaseTestCase, self).setUp()
self.conn = mock.Mock()
self.base_resource = BaseResouce(connector=self.conn, path='/Foo')
# refresh() is called in the constructor
self.conn.reset_mock()
def test_refresh(self):
self.base_resource.refresh()
self.conn.get.assert_called_once_with(path='/Foo')
def test_refresh_http_error_reraised(self):
self.conn.get.side_effect = exceptions.HTTPError(
method='GET', url='http://foo.bar:8000/redfish/v1', error='boom',
status_code=404)
self.assertRaises(exceptions.ResourceNotFoundError,
self.base_resource.refresh)
self.conn.get.assert_called_once_with(path='/Foo')
def test_refresh_resource_not_found(self):
self.conn.get.side_effect = exceptions.HTTPError(
method='GET', url='http://foo.bar:8000/redfish/v1', error='boom',
status_code=400)
self.assertRaises(exceptions.HTTPError, self.base_resource.refresh)
self.conn.get.assert_called_once_with(path='/Foo')

View File

@ -14,8 +14,10 @@
# under the License.
import mock
import requests
from sushy import connector
from sushy import exceptions
from sushy.tests.unit import base
@ -68,3 +70,22 @@ class ConnectorTestCase(base.TestCase):
def test__op_no_headers(self):
expected_headers = {'Content-Type': 'application/json'}
self._test_op(None, expected_headers)
@mock.patch('sushy.connector.requests.Session', autospec=True)
def test__op_connection_error(self, mock_session):
fake_session = mock.Mock()
mock_session.return_value.__enter__.return_value = fake_session
fake_session.request.side_effect = requests.exceptions.ConnectionError
self.assertRaises(exceptions.ConnectionError, self.conn._op, 'GET')
@mock.patch('sushy.connector.requests.Session', autospec=True)
def test__op_http_error(self, mock_session):
fake_session = mock.Mock()
mock_session.return_value.__enter__.return_value = fake_session
fake_response = fake_session.request.return_value
fake_response.status_code = 400
fake_response.raise_for_status.side_effect = (
requests.exceptions.HTTPError(response=fake_response))
self.assertRaises(exceptions.HTTPError, self.conn._op, 'GET')