Files
scylladb/test/boost/kmip_wrapper.py
Nikos Dragazis 2656fca504 test: Use in-memory SQLite for PyKMIP server
The PyKMIP server uses an SQLite database to store artifacts such as
encryption keys. By default, SQLite performs a full journal and data
flush to disk on every CREATE TABLE operation. Each operation triggers
three fdatasync(2) calls. If we multiply this by 16, that is the number
of tables created by the server, we get a significant number of file
syncs, which can last for several seconds on slow machines.

This behavior has led to CI stability issues from KMIP unit tests where
the server failed to complete its schema creation within the 20-second
timeout (observed on spider9 and spider11).

Fix this by configuring the server to use an in-memory SQLite.

Fixes #24842.

Signed-off-by: Nikos Dragazis <nikolaos.dragazis@scylladb.com>

Closes scylladb/scylladb#24995
2025-08-01 12:11:27 +03:00

124 lines
4.3 KiB
Python

import ssl
import sys
import functools
import sqlalchemy
from sqlalchemy.pool import StaticPool
from kmip.services import auth
from kmip.services.server.server import build_argument_parser
from kmip.services.server.server import KmipServer
# Helper wrapper for running pykmip in scylla testing. Needed for the following
# reasons:
#
# * TLS options (hardcoded) in pykmip are obsolete and will not work with
# connecting using gnutls of any modern variety.
#
# * We need to use an in-memory SQLite database for testing (file-based SQLite
# issues many fdatasync's which have been proven to reduce CI stability in
# some slow machines). An in-memory SQLite database is bound to a single
# connection. In order to share it among multiple threads, the connection
# itself must be shared. We achieve that with the StaticPool.
# https://docs.sqlalchemy.org/en/20/dialects/sqlite.html#using-a-memory-database-in-multiple-threads
def monkey_patch_create_engine():
original_create_engine = sqlalchemy.create_engine
@functools.wraps(original_create_engine)
def patched_create_engine(*args, **kwargs):
if args and isinstance(args[0], str) and args[0].startswith('sqlite:///:memory:'):
kwargs['poolclass'] = StaticPool
return original_create_engine(*args, **kwargs)
sqlalchemy.create_engine = patched_create_engine
class TLS13AuthenticationSuite(auth.TLS12AuthenticationSuite):
"""
An authentication suite used to establish secure network connections.
Supports TLS 1.3. More importantly, works with gnutls-<recent>
"""
def __init__(self, cipher_suites=None):
"""
Create a TLS12AuthenticationSuite object.
Args:
cipher_suites (list): A list of strings representing the names of
cipher suites to use. Overrides the default set of cipher
suites. Optional, defaults to None.
"""
super().__init__(cipher_suites)
self._protocol = ssl.PROTOCOL_TLS_SERVER
def main():
# Build argument parser and parser command-line arguments.
parser = build_argument_parser()
opts, args = parser.parse_args(sys.argv[1:])
kwargs = {}
if opts.hostname:
kwargs['hostname'] = opts.hostname
if opts.port:
kwargs['port'] = opts.port
if opts.certificate_path:
kwargs['certificate_path'] = opts.certificate_path
if opts.key_path:
kwargs['key_path'] = opts.key_path
if opts.ca_path:
kwargs['ca_path'] = opts.ca_path
if opts.auth_suite:
kwargs['auth_suite'] = opts.auth_suite
if opts.config_path:
kwargs['config_path'] = opts.config_path
if opts.log_path:
kwargs['log_path'] = opts.log_path
if opts.policy_path:
kwargs['policy_path'] = opts.policy_path
if opts.ignore_tls_client_auth:
kwargs['enable_tls_client_auth'] = False
if opts.logging_level:
kwargs['logging_level'] = opts.logging_level
if opts.database_path:
kwargs['database_path'] = opts.database_path
kwargs['live_policies'] = True
monkey_patch_create_engine()
# Create and start the server.
s = KmipServer(**kwargs)
# Fix TLS. Try to get this into mainline project, but that will take time...
s.auth_suite = TLS13AuthenticationSuite(s.auth_suite.ciphers)
# force port to zero -> select dynamically
s.config.settings['port'] = 0
def fake_wrap_ssl(sock, keyfile=None, certfile=None,
server_side=False, cert_reqs=ssl.CERT_NONE,
ssl_version=ssl.PROTOCOL_TLS, ca_certs=None,
do_handshake_on_connect=True,
suppress_ragged_eofs=True,
ciphers=None):
ctxt = ssl.SSLContext(protocol = ssl_version)
ctxt.load_cert_chain(certfile=certfile, keyfile=keyfile)
ctxt.verify_mode = cert_reqs
ctxt.load_verify_locations(cafile=ca_certs)
ctxt.set_ciphers(ciphers)
return ctxt.wrap_socket(sock, server_side=server_side
, do_handshake_on_connect=do_handshake_on_connect
, suppress_ragged_eofs=suppress_ragged_eofs)
ssl.wrap_socket = fake_wrap_ssl
print("Starting...")
with s:
print("Listening on {}".format(s._socket.getsockname()[1]))
sys.stdout.flush()
s.serve()
if __name__ == '__main__':
main()