diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c5dd571..d4616de4 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +# 4.4.1 (Unreleased) +#### Notes +Added the capability to set a connection timeout when connecting to the HPE OneView Appliance + + # 4.4.0 #### Notes Enabled usage of a CA Certificate file for establishing a SSL connection to the HPE OneView Appliance. diff --git a/README.md b/README.md index 8da86743..44d49eb7 100644 --- a/README.md +++ b/README.md @@ -98,6 +98,7 @@ export ONEVIEWSDK_API_VERSION='300' export ONEVIEWSDK_AUTH_LOGIN_DOMAIN='authdomain' export ONEVIEWSDK_SSL_CERTIFICATE='' export ONEVIEWSDK_PROXY=':' +export ONEVIEWSDK_CONNECTION_TIMEOUT='' ``` :lock: Tip: Make sure no unauthorized person has access to the environment variables, since the password is stored in clear-text. @@ -244,6 +245,20 @@ build_plans = image_streamer_client.build_plans.get_all() You can find more usage examples in the folder ```/examples/image_streamer``` +### OneView Connection Timeout +By default the system timeout is used when connecting to OneView. If you want to change this, +then the timeout can be set by either: + +1. Setting the appropriate environment variable: +```bash +export ONEVIEWSDK_CONNECTION_TIMEOUT='' +``` + +2. Setting the time-out in the JSON configuration file using the following syntax: +```json +"timeout": +``` + ## Exception handling All exceptions raised by the OneView Python SDK inherit from HPOneViewException. diff --git a/hpOneView/connection.py b/hpOneView/connection.py index 2d8e8301..7099920a 100644 --- a/hpOneView/connection.py +++ b/hpOneView/connection.py @@ -54,7 +54,7 @@ class connection(object): - def __init__(self, applianceIp, api_version=300, sslBundle=False): + def __init__(self, applianceIp, api_version=300, sslBundle=False, timeout=None): self._session = None self._host = applianceIp self._cred = None @@ -74,6 +74,7 @@ def __init__(self, applianceIp, api_version=300, sslBundle=False): self._numTotalRecords = 0 self._numDisplayedRecords = 0 self._validateVersion = False + self._timeout = timeout def validateVersion(self): version = self.get(uri['version']) @@ -148,6 +149,9 @@ def do_http(self, method, path, body, custom_headers=None): conn.close() time.sleep(1) continue + except http.client.HTTPException: + raise HPOneViewException('Failure during login attempt.\n %s' % traceback.format_exc()) + return resp, body def download_to_stream(self, stream_writer, url, body='', method='GET', custom_headers=None): @@ -182,6 +186,8 @@ def download_to_stream(self, stream_writer, url, body='', method='GET', custom_h conn.close() time.sleep(1) continue + except http.client.HTTPException as e: + raise HPOneViewException(str(e)) return successful_connected @@ -209,22 +215,27 @@ def get_connection(self): context.load_verify_locations(self._sslTrustedBundle) if self._doProxy is False: conn = http.client.HTTPSConnection(self._host, - context=context) + context=context, + timeout=self._timeout) else: conn = http.client.HTTPSConnection(self._proxyHost, self._proxyPort, - context=context) + context=context, + timeout=self._timeout) conn.set_tunnel(self._host, 443) else: context.verify_mode = ssl.CERT_NONE if self._doProxy is False: conn = http.client.HTTPSConnection(self._host, - context=context) + context=context, + timeout=self._timeout) else: conn = http.client.HTTPSConnection(self._proxyHost, self._proxyPort, - context=context) + context=context, + timeout=self._timeout) conn.set_tunnel(self._host, 443) + return conn def _open(self, name, mode): diff --git a/hpOneView/oneview_client.py b/hpOneView/oneview_client.py index 50c4cec7..70e811fe 100755 --- a/hpOneView/oneview_client.py +++ b/hpOneView/oneview_client.py @@ -116,7 +116,8 @@ class OneViewClient(object): DEFAULT_API_VERSION = 300 def __init__(self, config): - self.__connection = connection(config["ip"], config.get('api_version', self.DEFAULT_API_VERSION), config.get('ssl_certificate', False)) + self.__connection = connection(config["ip"], config.get('api_version', self.DEFAULT_API_VERSION), config.get('ssl_certificate', False), + config.get('timeout')) self.__image_streamer_ip = config.get("image_streamer_ip") self.__set_proxy(config) self.__connection.login(config["credentials"]) @@ -216,8 +217,8 @@ def from_environment_variables(cls): Construct OneViewClient using environment variables. Allowed variables: ONEVIEWSDK_IP (required), ONEVIEWSDK_USERNAME (required), ONEVIEWSDK_PASSWORD (required), - ONEVIEWSDK_AUTH_LOGIN_DOMAIN, ONEVIEWSDK_API_VERSION, ONEVIEWSDK_IMAGE_STREAMER_IP, ONEVIEWSDK_SESSIONID, ONEVIEWSDK_SSL_CERTIFICATE - and ONEVIEWSDK_PROXY. + ONEVIEWSDK_AUTH_LOGIN_DOMAIN, ONEVIEWSDK_API_VERSION, ONEVIEWSDK_IMAGE_STREAMER_IP, ONEVIEWSDK_SESSIONID, ONEVIEWSDK_SSL_CERTIFICATE, + ONEVIEWSDK_CONNECTION_TIMEOUT and ONEVIEWSDK_PROXY. Returns: OneViewClient: @@ -231,13 +232,14 @@ def from_environment_variables(cls): password = os.environ.get('ONEVIEWSDK_PASSWORD', '') proxy = os.environ.get('ONEVIEWSDK_PROXY', '') sessionID = os.environ.get('ONEVIEWSDK_SESSIONID', '') + timeout = os.environ.get('ONEVIEWSDK_CONNECTION_TIMEOUT') config = dict(ip=ip, image_streamer_ip=image_streamer_ip, api_version=api_version, ssl_certificate=ssl_certificate, credentials=dict(userName=username, authLoginDomain=auth_login_domain, password=password, sessionID=sessionID), - proxy=proxy) + proxy=proxy, timeout=timeout) return cls(config) diff --git a/tests/unit/test_connection.py b/tests/unit/test_connection.py index 22dc9762..17877d8a 100644 --- a/tests/unit/test_connection.py +++ b/tests/unit/test_connection.py @@ -29,7 +29,7 @@ import os.path from mock import patch, call, Mock, ANY -from http.client import HTTPSConnection, BadStatusLine +from http.client import HTTPSConnection, BadStatusLine, HTTPException from hpOneView.connection import connection from hpOneView.exceptions import HPOneViewException @@ -604,6 +604,20 @@ def test_download_to_stream_when_error_status_with_empty_body(self, mock_get_con else: self.fail() + @patch.object(connection, 'get_connection') + def test_download_to_stream_with_timeout_error(self, mock_get_connection): + + mock_conn = mock_get_connection.return_value = Mock() + mock_response = Mock() + mock_conn.getresponse.side_effect = [HTTPException('timed out'), mock_response] + + mock_stream = Mock() + + with self.assertRaises(HPOneViewException) as context: + resp, body = self.connection.download_to_stream(mock_stream, '/rest/download.zip') + + self.assertTrue('timed out' in context.exception.msg) + @patch.object(mmap, 'mmap') @patch.object(shutil, 'copyfileobj') @patch.object(os.path, 'getsize') @@ -858,6 +872,18 @@ def test_do_http_with_bad_status_line(self, mock_get_connection): mock_conn.close.assert_has_calls([call(), call()]) + @patch.object(connection, 'get_connection') + def test_do_http_with_timeout_error(self, mock_get_connection): + + mock_conn = mock_get_connection.return_value = Mock() + mock_response = Mock() + mock_conn.getresponse.side_effect = [HTTPException('timed out'), mock_response] + + with self.assertRaises(HPOneViewException) as context: + resp, body = self.connection.do_http('POST', '/rest/test', 'body') + + self.assertTrue('timed out' in context.exception.msg) + @patch.object(connection, 'get') @patch.object(connection, 'post') def test_login(self, mock_post, mock_get): diff --git a/tests/unit/test_oneview_client.py b/tests/unit/test_oneview_client.py index e9be14cc..a5898aad 100755 --- a/tests/unit/test_oneview_client.py +++ b/tests/unit/test_oneview_client.py @@ -99,7 +99,8 @@ 'ONEVIEWSDK_PASSWORD': 'secret123', 'ONEVIEWSDK_API_VERSION': '201', 'ONEVIEWSDK_AUTH_LOGIN_DOMAIN': 'authdomain', - 'ONEVIEWSDK_PROXY': '172.16.100.195:9999' + 'ONEVIEWSDK_PROXY': '172.16.100.195:9999', + 'ONEVIEWSDK_CONNECTION_TIMEOUT': '20' } OS_ENVIRON_CONFIG_FULL_WITH_SESSIONID = { @@ -109,7 +110,9 @@ 'ONEVIEWSDK_PASSWORD': 'secret123', 'ONEVIEWSDK_SESSIONID': '123', 'ONEVIEWSDK_API_VERSION': '201', - 'ONEVIEWSDK_PROXY': '172.16.100.195:9999' + 'ONEVIEWSDK_PROXY': '172.16.100.195:9999', + 'ONEVIEWSDK_CONNECTION_TIMEOUT': '20' + } @@ -296,6 +299,7 @@ def test_from_environment_variables_is_passing_right_arguments_to_the_constructo OneViewClient.from_environment_variables() mock_cls.assert_called_once_with({'api_version': 201, 'proxy': '172.16.100.195:9999', + 'timeout': '20', 'ip': '172.16.100.199', 'ssl_certificate': '', 'image_streamer_ip': '172.172.172.172', @@ -312,6 +316,7 @@ def test_from_environment_variables_is_passing_right_arguments_to_the_constructo OneViewClient.from_environment_variables() mock_cls.assert_called_once_with({'api_version': 201, 'proxy': '172.16.100.195:9999', + 'timeout': '20', 'ip': '172.16.100.199', 'image_streamer_ip': '172.172.172.172', 'ssl_certificate': '', @@ -328,6 +333,7 @@ def test_from_environment_variables_is_passing_right_arguments_to_the_constructo OneViewClient.from_environment_variables() mock_cls.assert_called_once_with({'api_version': 300, 'proxy': '', + 'timeout': None, 'ip': '172.16.100.199', 'image_streamer_ip': '', 'ssl_certificate': '',