Server Failover Feature

This feature enables the PyKMIP library to switch between KMIP service provider
hosts in the event one of them is unavailable. To list more than than one host,
include all necessary host IP addresses separated by commas in the "host" field
in the pykmip.conf file.

Signed-off-by: Hadi Esiely <hadi.esiely-barrera@jhuapl.edu>
This commit is contained in:
Hadi Esiely 2015-11-25 12:43:40 -05:00
parent 9cdceb790f
commit b4644c47ae
4 changed files with 107 additions and 17 deletions

View File

@ -18,4 +18,4 @@ formatter=simpleFormatter
args=(sys.stdout,)
[formatter_simpleFormatter]
format=%(asctime)s - %(name)s - %(levelname)s - %(message)s
format=%(asctime)s - %(name)s - %(levelname)s - %(message)s

View File

@ -5,7 +5,7 @@ keyfile=None
certfile=None
cert_reqs=CERT_REQUIRED
ssl_version=PROTOCOL_SSLv23
ca_certs=../demos/certs/server.crt
ca_certs=./demos/certs/server.crt
do_handshake_on_connect=True
suppress_ragged_eofs=True
username=None
@ -15,8 +15,8 @@ timeout=30
[server]
host=127.0.0.1
port=5696
keyfile=../demos/certs/server.key
certfile=../demos/certs/server.crt
keyfile=./demos/certs/server.key
certfile=./demos/certs/server.crt
cert_reqs=CERT_NONE
ssl_version=PROTOCOL_SSLv23
ca_certs=None

View File

@ -76,7 +76,8 @@ CONFIG_FILE = os.path.normpath(os.path.join(FILE_PATH, '../kmipconfig.ini'))
class KMIPProxy(KMIP):
def __init__(self, host=None, port=None, keyfile=None, certfile=None,
def __init__(self, host=None, port=None, keyfile=None,
certfile=None,
cert_reqs=None, ssl_version=None, ca_certs=None,
do_handshake_on_connect=None,
suppress_ragged_eofs=None,
@ -193,7 +194,6 @@ class KMIPProxy(KMIP):
self.is_authentication_suite_supported(authentication_suite))
def open(self):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.logger.debug("KMIPProxy keyfile: {0}".format(self.keyfile))
self.logger.debug("KMIPProxy certfile: {0}".format(self.certfile))
@ -209,6 +209,23 @@ class KMIPProxy(KMIP):
self.logger.debug("KMIPProxy suppress_ragged_eofs: {0}".format(
self.suppress_ragged_eofs))
for host in self.host_list:
self.host = host
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self._create_socket(sock)
self.protocol = KMIPProtocol(self.socket)
try:
self.socket.connect((self.host, self.port))
except Exception as e:
self.logger.error("An error occurred while connecting to "
"appliance " + self.host)
self.socket.close()
self.socket = None
else:
return
raise e
def _create_socket(self, sock):
self.socket = ssl.wrap_socket(
sock,
keyfile=self.keyfile,
@ -218,16 +235,8 @@ class KMIPProxy(KMIP):
ca_certs=self.ca_certs,
do_handshake_on_connect=self.do_handshake_on_connect,
suppress_ragged_eofs=self.suppress_ragged_eofs)
self.protocol = KMIPProtocol(self.socket)
self.socket.settimeout(self.timeout)
try:
self.socket.connect((self.host, self.port))
except socket.timeout as e:
self.logger.error("timeout occurred while connecting to appliance")
raise e
def __del__(self):
# Close the socket properly, helpful in case close() is not called.
self.close()
@ -881,9 +890,14 @@ class KMIPProxy(KMIP):
username, password, timeout):
conf = ConfigHelper()
self.host = conf.get_valid_value(
# TODO: set this to a host list
self.host_list_str = conf.get_valid_value(
host, self.config, 'host', conf.DEFAULT_HOST)
self.host_list = self._build_host_list(self.host_list_str)
self.host = self.host_list[0]
self.port = int(conf.get_valid_value(
port, self.config, 'port', conf.DEFAULT_PORT))
@ -922,11 +936,27 @@ class KMIPProxy(KMIP):
self.password = conf.get_valid_value(
password, self.config, 'password', conf.DEFAULT_PASSWORD)
self.timeout = conf.get_valid_value(
timeout, self.config, 'timeout', conf.DEFAULT_TIMEOUT)
self.timeout = int(conf.get_valid_value(
timeout, self.config, 'timeout', conf.DEFAULT_TIMEOUT))
if self.timeout < 0:
self.logger.warning(
"Negative timeout value specified, "
"resetting to safe default of {0} seconds".format(
conf.DEFAULT_TIMEOUT))
self.timeout = conf.DEFAULT_TIMEOUT
def _build_host_list(self, host_list_str):
'''
This internal function takes the host string from the config file
and turns it into a list
:return: LIST host list
'''
host_list = []
if isinstance(host_list_str, str):
host_list = host_list_str.replace(' ', '').split(',')
else:
raise TypeError("Unrecognized variable type provided for host "
"list string. 'String' type expected but '" +
str(type(host_list_str)) + "' received")
return host_list

View File

@ -61,6 +61,10 @@ from kmip.services.results import RekeyKeyPairResult
import kmip.core.utils as utils
import mock
import socket
class TestKMIPClient(TestCase):
@ -501,6 +505,62 @@ class TestKMIPClient(TestCase):
self.assertEqual(uid, result.uid)
self.assertEqual(names, result.names)
def test_host_list_import_string(self):
"""
This test verifies that the client can process a string with
multiple IP addresses specified in it. It also tests that
unnecessary spaces are ignored.
"""
host_list_string = '127.0.0.1,127.0.0.3, 127.0.0.5'
host_list_expected = ['127.0.0.1', '127.0.0.3', '127.0.0.5']
self.client._set_variables(host=host_list_string,
port=None, keyfile=None, certfile=None,
cert_reqs=None, ssl_version=None,
ca_certs=None,
do_handshake_on_connect=False,
suppress_ragged_eofs=None, username=None,
password=None, timeout=None)
self.assertEqual(host_list_expected, self.client.host_list)
def test_host_is_invalid_input(self):
"""
This test verifies that invalid values are not processed when
setting the client object parameters
"""
host = 1337
expected_error = TypeError
kwargs = {'host': host, 'port': None, 'keyfile': None,
'certfile': None, 'cert_reqs': None, 'ssl_version': None,
'ca_certs': None, 'do_handshake_on_connect': False,
'suppress_ragged_eofs': None, 'username': None,
'password': None, 'timeout': None}
self.assertRaises(expected_error, self.client._set_variables,
**kwargs)
@mock.patch('socket.socket.connect')
@mock.patch('ssl.SSLSocket.gettimeout')
def test_timeout_all_hosts(self, mock_ssl_timeout, mock_connect_return):
"""
This test verifies that the client will throw an exception if no
hosts are available for connection.
"""
mock_ssl_timeout.return_value = 1
mock_connect_return.return_value = socket.timeout
try:
self.client.open()
except Exception as e:
# TODO: once the exception is properly defined in the
# kmip_client.py file this test needs to change to reflect that.
self.assertIsInstance(e, Exception)
self.client.close()
else:
self.client.close()
class TestClientProfileInformation(TestCase):
"""