diff --git a/rbd_iscsi_client/__init__.py b/rbd_iscsi_client/__init__.py index 8fa4958..35c4d65 100644 --- a/rbd_iscsi_client/__init__.py +++ b/rbd_iscsi_client/__init__.py @@ -1,6 +1,14 @@ -# -*- coding: utf-8 -*- - -"""Top-level package for RBD iSCSI Client.""" +# 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."""Top-level package for RBD iSCSI Client.""" __author__ = """Walter A. Boring IV""" __email__ = 'waboring@hemna.com' diff --git a/rbd_iscsi_client/rbd_iscsi_client.py b/rbd_iscsi_client/rbd_iscsi_client.py index 8946c4a..1cc5c64 100644 --- a/rbd_iscsi_client/rbd_iscsi_client.py +++ b/rbd_iscsi_client/rbd_iscsi_client.py @@ -26,10 +26,9 @@ import time from rbd_iscsi_client import exceptions -from requests.auth import HTTPBasicAuth - class RBDISCSIClient(object): + """REST client to rbd-target-api.""" USER_AGENT = "os_client" @@ -42,6 +41,7 @@ class RBDISCSIClient(object): tries = 5 delay = 0 backoff = 2 + timeout = 60 _logger = logging.getLogger(__name__) retry_exceptions = (exceptions.HTTPServiceUnavailable, @@ -49,7 +49,7 @@ class RBDISCSIClient(object): def __init__(self, username, password, base_url, suppress_ssl_warnings=False, timeout=None, - secure=False): + secure=False, http_log_debug=False): super(RBDISCSIClient, self).__init__() self.username = username @@ -59,11 +59,12 @@ class RBDISCSIClient(object): self.secure = secure self.times = [] + self.set_debug_flag(http_log_debug) if suppress_ssl_warnings: requests.packages.urllib3.disable_warnings() - self.auth = HTTPBasicAuth(username, password) + self.auth = requests.auth.HTTPBasicAuth(username, password) def set_debug_flag(self, flag): """Turn on/off http request/response debugging.""" @@ -194,7 +195,18 @@ class RBDISCSIClient(object): # Raise exception, we have exhausted all retries. if self.tries is 0: raise ex - + except requests.exceptions.HTTPError as err: + raise exceptions.HTTPError("HTTP Error: %s" % err) + except requests.exceptions.URLRequired as err: + raise exceptions.URLRequired("URL Required: %s" % err) + except requests.exceptions.TooManyRedirects as err: + raise exceptions.TooManyRedirects( + "Too Many Redirects: %s" % err) + except requests.exceptions.Timeout as err: + raise exceptions.Timeout("Timeout: %s" % err) + except requests.exceptions.RequestException as err: + raise exceptions.RequestException( + "Request Exception: %s" % err) return resp, body def _time_request(self, url, method, **kwargs): diff --git a/rbd_iscsi_client/tests/test_exceptions.py b/rbd_iscsi_client/tests/test_exceptions.py new file mode 100644 index 0000000..529d0f0 --- /dev/null +++ b/rbd_iscsi_client/tests/test_exceptions.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +"""Tests for `rbd_iscsi_client` package.""" + + +import unittest + +from rbd_iscsi_client import exceptions + + +class Test_exceptions(unittest.TestCase): + """Tests for `rbd_iscsi_client` package.""" + + def setUp(self): + """Set up test fixtures, if any.""" + + def tearDown(self): + """Tear down test fixtures, if any.""" + + def test_000_from_response_string_format(self): + """Test something.""" + class FakeResponse(object): + status = 500 + + fake_response = FakeResponse() + output = exceptions.from_response(fake_response, {}).__str__() + self.assertEquals('Internal Server Error (HTTP 500)', output) + + def test_001_client_exception_string_format(self): + fake_error = {'code': 999, + 'desc': 'Fake Description', + 'ref': 'Fake Ref', + 'debug1': 'Fake Debug 1', + 'debug2': 'Fake Debug 2', } + client_ex = exceptions.ClientException(error=fake_error) + client_ex.message = "Fake Error" + client_ex.http_status = 500 + output = client_ex.__str__() + + self.assertEquals("Fake Error (HTTP 500) 999 - Fake Description - " + "Fake Ref (1: 'Fake Debug 1') (2: 'Fake Debug 2')", + output) diff --git a/rbd_iscsi_client/tests/test_rbd_iscsi_client_request.py b/rbd_iscsi_client/tests/test_rbd_iscsi_client_request.py new file mode 100644 index 0000000..6cbcb60 --- /dev/null +++ b/rbd_iscsi_client/tests/test_rbd_iscsi_client_request.py @@ -0,0 +1,134 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +"""Tests for `rbd_iscsi_client` package.""" + +import mock +import requests +import unittest + +from rbd_iscsi_client import rbd_iscsi_client as client +from rbd_iscsi_client import exceptions + + +class TestRbd_iscsi_client(unittest.TestCase): + """Tests for `rbd_iscsi_client` package.""" + + client = None + + def setUp(self): + fake_url = 'client://fake-url:0000' + fake_user = 'user' + fake_password = 'password' + self.client = client.RBDISCSIClient(fake_user, fake_password, + fake_url, secure=False, + http_log_debug=True, + suppress_ssl_warnings=False, + timeout=None, + ) + + def tearDown(self): + self.client = None + + + def test_request_timeout(self): + self.client._http_log_req = mock.Mock() + self.client.timeout = 10 + retest = mock.Mock() + http_method = 'fake this' + http_url = 'http://fake-url:0000' + + with mock.patch('requests.request', retest, create=True): + # Test timeout exception + retest.side_effect = requests.exceptions.Timeout + self.assertRaises(exceptions.Timeout, + self.client.request, + http_url, http_method) + + def test_request_redirects(self): + self.client._http_log_req = mock.Mock() + self.client.timeout = 10 + retest = mock.Mock() + http_method = 'fake this' + http_url = 'http://fake-url:0000' + + with mock.patch('requests.request', retest, create=True): + # Test too many redirects exception + retest.side_effect = requests.exceptions.TooManyRedirects + self.assertRaises(exceptions.TooManyRedirects, + self.client.request, + http_url, http_method) + + def test_request_http_error(self): + self.client._http_log_req = mock.Mock() + self.client.timeout = 10 + retest = mock.Mock() + http_method = 'fake this' + http_url = 'http://fake-url:0000' + + with mock.patch('requests.request', retest, create=True): + # Test HTTP Error exception + retest.side_effect = requests.exceptions.HTTPError + self.assertRaises(exceptions.HTTPError, + self.client.request, + http_url, http_method) + + def test_request_url_required(self): + self.client._http_log_req = mock.Mock() + self.client.timeout = 10 + retest = mock.Mock() + http_method = 'fake this' + http_url = 'http://fake-url:0000' + + with mock.patch('requests.request', retest, create=True): + # Test URL required exception + retest.side_effect = requests.exceptions.URLRequired + self.assertRaises(exceptions.URLRequired, + self.client.request, + http_url, http_method) + + def test_request_exception(self): + self.client._http_log_req = mock.Mock() + self.client.timeout = 10 + retest = mock.Mock() + http_method = 'fake this' + http_url = 'http://fake-url:0000' + + with mock.patch('requests.request', retest, create=True): + # Test request exception + retest.side_effect = requests.exceptions.RequestException + self.assertRaises(exceptions.RequestException, + self.client.request, + http_url, http_method) + + def test_request_ssl_error(self): + self.client._http_log_req = mock.Mock() + self.client.timeout = 10 + retest = mock.Mock() + + with mock.patch('requests.request', retest, create=True): + # Test requests exception + retest.side_effect = requests.exceptions.SSLError + self.assertRaisesRegexp(exceptions.SSLCertFailed, "failed") + + def test_client_timeout_setting(self): + self.client._http_log_req = mock.Mock() + self.client.timeout = 10 + retest = mock.Mock() + + with mock.patch('requests.request', retest, create=True): + self.assertEqual(self.client.timeout, 10) + + def test_client_retry(self): + self.client._http_log_req = mock.Mock() + self.client.timeout = 2 + retest = mock.Mock() + http_method = 'fake this' + http_url = 'http://fake-url:0000' + + with mock.patch('requests.request', retest, create=True): + # Test retry exception + retest.side_effect = requests.exceptions.ConnectionError + self.assertRaises(requests.exceptions.ConnectionError, + self.client.request, + http_url, http_method)