Files
scylladb/test/cqlpy/run-cassandra
Nadav Har'El d63fdd1e8b test/cqlpy: fix run-cassandra to run with Java 21
The script test/cqpy/run-cassandra aims to make it easy to run
any version of Cassandra using whatever version of Java the user
has installed. Sadly, the fact that Java keeps changing and the
Cassandra developers are very slow to adapt to new Javas makes
doing this non-trivial.

This patch makes it possible for run-cassandra to run Cassandra 5
on the Java 21 that is now the default on Fedora 42. Fedora 42
no longer carries antique version of Java (like Java 8 or 11), not
even as an optional package.

Sadly, even with this patch it is not possible to run older
versions of Cassandra (4 and 3) with Java 21, because the new
Java is missing features such as Netty that the older Cassandra
require. But at least it restores the ability to run our cqlpy
tests against Cassandra 5.

Also, this patch adds to test/cqlpy/README.md simple instructions on
how to install Java 11 (in addition to the system's default Java 21)
on Fedora 42. Doing this is very easy and very recommended because
it restores the ability to run Cassandra 3 and 4, not just Cassandra 5.

Fixes #25822.

Signed-off-by: Nadav Har'El <nyh@scylladb.com>

Closes scylladb/scylladb#25825
2025-09-17 17:24:47 +03:00

221 lines
11 KiB
Python
Executable File

#!/usr/bin/env python3
# This script makes it easy to start Cassandra an run cqlpy tests against
# it. This capability is useful for checking that a new cqlpy test that aims
# to ensure behavior compatible with Cassandra - is actually compatible with
# Cassandra.
# Please refer to README.md for instructions how to get your choice of
# Cassandra version and the Java needed to run it, and how to run this script.
import sys
import os
import shutil
import subprocess
import re
import run # run.py in this directory
def find_cassandra():
# By default, we assume 'cassandra' is in the user's path. A specific
# cassandra script can be chosen by setting the CASSANDRA variable.
cassandra = os.getenv('CASSANDRA', 'cassandra')
cassandra_path = shutil.which(cassandra)
if cassandra_path is None:
print("Error: Can't find {}. Please set the CASSANDRA environment variable to the path of the Cassandra startup script.".format(cassandra))
exit(1)
return cassandra_path
cassandra = find_cassandra()
# By default, the Cassandra startup script simply looks for "java" in the
# path, and in an ideal world, this should have just worked.
# However, Cassandra 3 and 4 only support Java versions 8 and 11, and
# Cassandra 5 only supports Java 11 and 17, and your Linux distribution
# might have one of those installed but not as the default "java" command.
# So find_java() tries to find a supported version elsewhere on your system.
# See https://github.com/scylladb/scylla/issues/10946
# https://issues.apache.org/jira/browse/CASSANDRA-16895
def java_major_version(java):
out = subprocess.check_output([java, '-version'], stderr=subprocess.STDOUT).decode('UTF-8')
version = re.search(r'"(\d+)\.(\d+).*"', out).groups()
major = int(version[0])
minor = int(version[1])
if major == 1:
# Return 8 for Java 1.8
return minor
else:
return major
def find_java():
# Look for the Java in one of several places known to host the Java
# executable, and return the first one that works and has the appropriate
# version. The first attempt is just "java" in the path, which is
# preferred if has the right version.
for java in ['/usr/lib/jvm/jre-11/bin/java', '/usr/lib/jvm/jre-1.8.0/bin/java', 'java']:
try:
version = java_major_version(java)
# FIXME: Since Cassandra 5, it now supports Java 17 but not
# Java 8, so this logic should be fixed. For now if you have
# Java 11 installed, all Cassandra versions will work.
if version == 8 or version == 11:
return java
except:
pass
print("WARNING: find_java() couldn't find Java 8 or 11. Trying default 'java' anyway.")
java = find_java()
def run_cassandra_cmd(pid, dir):
global cassandra
ip = run.pid_to_ip(pid)
# Unfortunately, Cassandra doesn't take command-line parameters. We need
# to write a configuration file, and feed it to Cassandra using
# environment variables. Some of the parameters we did not deliberately
# want to override - they just don't have a default and we must set them.
confdir = os.path.join(dir, 'conf')
os.mkdir(confdir)
with open(os.path.join(confdir, 'cassandra.yaml'), 'w') as f:
print('hints_directory: ' + dir + '/hints\n' +
'data_file_directories:\n - ' + dir + '/data\n' +
'commitlog_directory: ' + dir + '/commitlog\n' +
'saved_caches_directory: ' + dir + '/data/saved_caches\n' +
'commitlog_sync: periodic\n' +
'commitlog_sync_period_in_ms: 10000\n' +
'partitioner: org.apache.cassandra.dht.Murmur3Partitioner\n' +
'endpoint_snitch: SimpleSnitch\n' +
'seed_provider:\n - class_name: org.apache.cassandra.locator.SimpleSeedProvider\n parameters:\n - seeds: "' + ip + '"\n' +
'listen_address: ' + ip + '\n' +
'start_native_transport: true\n' +
'auto_snapshot: false\n' +
'enable_sasi_indexes: true\n' +
'enable_user_defined_functions: true\n' +
'authenticator: PasswordAuthenticator\n' +
'authorizer: CassandraAuthorizer\n' +
'permissions_update_interval_in_ms: 100\n' +
'permissions_validity_in_ms: 100\n' +
'enable_materialized_views: true\n', file=f)
print('Booting Cassandra on ' + ip + ' in ' + dir + '...')
logsdir = os.path.join(dir, 'logs')
os.mkdir(logsdir)
# Cassandra creates some subdirectories on its own, but one it doesn't...
os.mkdir(os.path.join(dir, 'hints'))
env = { 'CASSANDRA_CONF': confdir,
'CASSANDRA_LOG_DIR': logsdir,
'CASSANDRA_INCLUDE': '',
'CASSANDRA_HOME': '',
# Unfortunately, Cassandra's JMX cannot listen only on a specific
# interface. To allow tests to use JMX (nodetool), we need to
# have it listen on 0.0.0.0 :-( This is insecure, but arguably
# can be forgiven for test environments. The following JVM_OPTS
# configures that:
'JVM_OPTS': '-Dcassandra.jmx.remote.port=7199',
}
# By default, Cassandra's startup script runs "java". We can override this
# choice with the JAVA_HOME environment variable based on the Java we
# found earlier in find_java().
if java and java.startswith('/'):
env['JAVA_HOME'] = os.path.dirname(os.path.dirname(java))
print('JAVA_HOME: ' + env['JAVA_HOME'])
# On JVM 11, Cassandra requires a bunch of configuration options in
# conf/jvm11-server.options, or it fails loading classes because of JPMS.
# The following options were copied from Cassandra's jvm11-server.options.
# Note that Cassandra's cassandra.in.sh script requires that the "-"
# appears as the first character of each line:
with open(os.path.join(confdir, 'jvm11-server.options'), 'w') as f:
print('-Djdk.attach.allowAttachSelf=true\n'
'--add-exports java.base/jdk.internal.misc=ALL-UNNAMED\n'
'--add-exports java.base/jdk.internal.ref=ALL-UNNAMED\n'
'--add-exports java.base/sun.nio.ch=ALL-UNNAMED\n'
'--add-exports java.management.rmi/com.sun.jmx.remote.internal.rmi=ALL-UNNAMED\n'
'--add-exports java.rmi/sun.rmi.registry=ALL-UNNAMED\n'
'--add-exports java.rmi/sun.rmi.server=ALL-UNNAMED\n'
'--add-exports java.sql/java.sql=ALL-UNNAMED\n'
'--add-opens java.base/java.lang.module=ALL-UNNAMED\n'
'--add-opens java.base/jdk.internal.loader=ALL-UNNAMED\n'
'--add-opens java.base/jdk.internal.ref=ALL-UNNAMED\n'
'--add-opens java.base/jdk.internal.reflect=ALL-UNNAMED\n'
'--add-opens java.base/jdk.internal.math=ALL-UNNAMED\n'
'--add-opens java.base/jdk.internal.module=ALL-UNNAMED\n'
'--add-opens java.base/jdk.internal.util.jar=ALL-UNNAMED\n'
'--add-opens jdk.management/com.sun.management.internal=ALL-UNNAMED\n',
file=f)
# On JVM 17 and above, Cassandra 5's cassandra.in.sh reads the
# following file instead:
with open(os.path.join(confdir, 'jvm17-server.options'), 'w') as f:
print('-Djdk.attach.allowAttachSelf=true\n'
'-Djava.security.manager=allow\n'
'--add-exports java.base/jdk.internal.misc=ALL-UNNAMED\n'
'--add-exports java.base/jdk.internal.ref=ALL-UNNAMED\n'
'--add-exports java.base/sun.nio.ch=ALL-UNNAMED\n'
'--add-exports java.management.rmi/com.sun.jmx.remote.internal.rmi=ALL-UNNAMED\n'
'--add-exports java.rmi/sun.rmi.registry=ALL-UNNAMED\n'
'--add-exports java.rmi/sun.rmi.server=ALL-UNNAMED\n'
'--add-exports java.sql/java.sql=ALL-UNNAMED\n'
'--add-exports java.base/java.lang.ref=ALL-UNNAMED\n'
'--add-exports java.base/java.lang.reflect=ALL-UNNAMED\n'
'--add-opens java.base/java.lang.module=ALL-UNNAMED\n'
'--add-opens java.base/jdk.internal.loader=ALL-UNNAMED\n'
'--add-opens java.base/jdk.internal.ref=ALL-UNNAMED\n'
'--add-opens java.base/jdk.internal.reflect=ALL-UNNAMED\n'
'--add-opens java.base/jdk.internal.math=ALL-UNNAMED\n'
'--add-opens java.base/jdk.internal.module=ALL-UNNAMED\n'
'--add-opens jdk.management/com.sun.management.internal=ALL-UNNAMED\n'
'--add-opens java.base/sun.nio.ch=ALL-UNNAMED\n'
'--add-opens java.base/java.io=ALL-UNNAMED\n'
'--add-opens java.base/java.nio=ALL-UNNAMED\n'
'--add-opens java.base/java.util.concurrent=ALL-UNNAMED\n'
'--add-opens java.base/java.util=ALL-UNNAMED\n'
'--add-opens java.base/java.util.concurrent.atomic=ALL-UNNAMED\n'
'--add-opens java.base/java.lang=ALL-UNNAMED\n'
'--add-opens java.base/java.math=ALL-UNNAMED\n'
'--add-opens java.base/java.lang.reflect=ALL-UNNAMED\n'
'--add-opens java.base/java.net=ALL-UNNAMED\n'
'--add-opens java.rmi/sun.rmi.transport.tcp=ALL-UNNAMED\n'
,file=f)
# Current versions of Cassandra 5 refuse to run on Java 21
# without this environment variable.
env['CASSANDRA_JDK_UNSUPPORTED'] = 'true'
return ([cassandra, '-f'], env)
# Same as run_cassandra_cmd, just use SSL encryption for the CQL port (same
# port number as default - replacing the unencrypted server).
def run_cassandra_ssl_cmd(pid, dir):
(cmd, env) = run_cassandra_cmd(pid, dir)
run.setup_ssl_certificate(dir)
# Cassandra needs a single "keystore" instead of the separate crt and key
# generated by run.setup_ssl_certificate().
os.system(f'openssl pkcs12 -export -in {dir}/scylla.crt -inkey {dir}/scylla.key -password pass:hello -out {dir}/keystore.p12')
with open(os.path.join(dir, 'conf', 'cassandra.yaml'), 'a') as f:
print('client_encryption_options:\n' +
' enabled: true\n' +
' optional: false\n' +
' keystore: ' + dir + '/keystore.p12\n' +
' keystore_password: hello\n' +
' store_type: PKCS12\n',
file=f)
# The command and environment variables to run Cassandra are the same,
return (cmd, env)
print('Cassandra is: ' + cassandra + '.')
if '--ssl' in sys.argv:
cmd = run_cassandra_ssl_cmd
check_cql = run.check_ssl_cql
else:
cmd = run_cassandra_cmd
check_cql = run.check_cql
pid = run.run_with_temporary_dir(cmd)
ip = run.pid_to_ip(pid)
run.wait_for_services(pid, [lambda: check_cql(ip)])
success = run.run_pytest(sys.path[0], ['--host', ip] + sys.argv[1:])
run.summary = 'Cassandra tests pass' if success else 'Cassandra tests failure'
exit(0 if success else 1)
# Note that the run.cleanup_all() function runs now, just like on any exit
# for any reason in this script. It will delete the temporary files and
# announce the failure or success of the test (printing run.summary).