mirror of https://github.com/OpenKMIP/PyKMIP.git
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:
parent
9cdceb790f
commit
b4644c47ae
kmip
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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):
|
||||
"""
|
||||
|
|
Loading…
Reference in New Issue