Recently, in commit 7b30a39, we added to pytest.ini the option xfail_strict.
This option causes every time a test XPASSes, i.e., an XFAIL test actually
passes, to be considered an error and fail the test.
While this has some benefits, it's a big problem when running tests
against a reference implementation like DynamoDB or Cassandra: We
typically mark a test "xfail" if the test shows a known bug - i.e., if
the test fails on Scylla but passes on the reference system (DynamoDB
or Cassandra). This means that when running "test/cqlpy/run-cassandra"
or "test/alternator/run --aws", we expect to see many tests XPASS,
and now this will cause these runs to "fail".
So in this patch we add the xfail_strict=false to cqlpy/run-cassandra
and alternator/run --aws. This option is not added to cqlpy/run or
to alternator/run without --aws, and also doesn't affect test.py or
Jenkins.
P.S. This is another nail in the coffin of doing "cd test/alternator;
pytest --aws". You should get used to running Alternator tests through
test/alternator/run, even if you don't need to run Scylla (the "--aws"
option doesn't run Scylla).
Signed-off-by: Nadav Har'El <nyh@scylladb.com>
Closes scylladb/scylladb#28973
221 lines
11 KiB
Python
Executable File
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], ['-o', 'xfail_strict=false', '--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).
|