Support custom StartupCheck implementations via SPI

patch by Nick Bar; reviewed by Stefan Miklosovic, Jyothsna Konisa for CASSANDRA-21093

Co-authored-by: Stefan Miklosovic <smiklosovic@apache.org>
This commit is contained in:
nickbar01234 2026-01-11 22:01:38 -05:00 committed by Stefan Miklosovic
parent 1f220921dc
commit 585f89bb42
No known key found for this signature in database
GPG Key ID: 32F35CB2F546D93E
26 changed files with 1118 additions and 398 deletions

View File

@ -1,4 +1,5 @@
5.1
* Support custom StartupCheck implementations via SPI (CASSANDRA-21093)
* Make sstableexpiredblockers support human-readable output with SSTable sizes (CASSANDRA-20448)
* Enhance nodetool compactionhistory to report more compaction properities (CASSANDRA-20081)
* Fix initial auto-repairs skipped by too soon check (CASSANDRA-21115)

View File

@ -136,6 +136,7 @@ New features
and "length" defined on UTF8 strings. See CASSANDRA-20102 for more information.
- New functions `format_bytes` and `format_time` were added. See CASSANDRA-19546.
- It is possible to use Async-profiler for various profiling scenarios. See CASSANDRA-20854.
- It is possible to provide custom startup check via Java SPI. See CASSANDRA-21093.
Upgrading
---------

View File

@ -2673,6 +2673,9 @@ max_security_label_length: 48
# are configurable (so you can disable them) but these which are enumerated bellow.
# Uncomment the startup checks and configure them appropriately to cover your needs.
#
# It is possible to implement custom startup checks by implementing StatupCheck interface
# and placing JAR on a classpath, using SPI mechanism.
#
#startup_checks:
# Verifies correct ownership of attached locations on disk at startup. See CASSANDRA-16879 for more details.
# check_filesystem_ownership:

View File

@ -2437,6 +2437,9 @@ default_secondary_index_enabled: true
# are configurable (so you can disable them) but these which are enumerated bellow.
# Uncomment the startup checks and configure them appropriately to cover your needs.
#
# It is possible to implement custom startup checks by implementing StatupCheck interface
# and placing JAR on a classpath, using SPI mechanism.
#
#startup_checks:
# Verifies correct ownership of attached locations on disk at startup. See CASSANDRA-16879 for more details.
# check_filesystem_ownership:

View File

@ -0,0 +1,45 @@
== Cassandra Startup Check Example
An implementation of `StartupCheck` interface will create custom startup check. For the purposes of this example,
there will be one startup check with the name `my_check`.
If you want to code your own startup check without patching Cassandra yourself, you need to do the following:
1. Code against interface `org.apache.cassandra.service.StartupCheck`.
2. Put the class implementing this interface to `META-INF/services/org.apache.cassandra.service.StartupCheck`
3. Build a JAR both with the implementation and `META-INF` resources, as show in this example, and put this JAR onto
Cassandra's classpath.
4. When Cassandra starts, it will auto-detect new check by loading `MyCustomStartupCheck` in your JAR.
5. You can code more check classes and add them all into one jar, just add another entry into file in step 2.
You can also configure the checks loaded like this in cassandra.yaml under `startup_checks` section,
like following:
----
startup_checks:
my_check:
key: value
----
You can get these options from `StartupChecksConfiguration`, by check's `name()`.
=== Installation
----
$ cd <cassandra_src_dir>/examples/startup-checks
$ ant install
----
It will build the startup check and will copy it to `lib` as well as to `build/lib/jars`.
You remove it from everywhere by
----
$ cd <cassandra_src_dir>/examples/startup-checks
$ ant clean
----
=== Usage
Follow the logs, you will see the output of the check. You can
play with options in cassandra.yaml to see what is propagated to that check.

View File

@ -0,0 +1,68 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Licensed to the Apache Software Foundation (ASF) under one
~ or more contributor license agreements. See the NOTICE file
~ distributed with this work for additional information
~ regarding copyright ownership. The ASF licenses this file
~ to you under the Apache License, Version 2.0 (the
~ "License"); you may not use this file except in compliance
~ with the License. You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing,
~ software distributed under the License is distributed on an
~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
~ KIND, either express or implied. See the License for the
~ specific language governing permissions and limitations
~ under the License.
-->
<project default="jar" name="startup-check-example">
<property name="cassandra.dir" value="../.." />
<property name="cassandra.dir.lib" value="${cassandra.dir}/lib" />
<property name="cassandra.classes" value="${cassandra.dir}/build/classes/main" />
<property name="build.src" value="${basedir}/src" />
<property name="build.dir" value="${basedir}/build" />
<property name="conf.dir" value="${basedir}/conf" />
<property name="build.classes" value="${build.dir}/classes" />
<property name="final.name" value="startup-check-example" />
<path id="build.classpath">
<fileset dir="${cassandra.dir.lib}">
<include name="**/*.jar" />
</fileset>
<fileset dir="${cassandra.dir}/build/lib/jars">
<include name="**/*.jar" />
</fileset>
<pathelement location="${cassandra.classes}" />
</path>
<target name="init">
<mkdir dir="${build.classes}" />
</target>
<target name="build" depends="init">
<javac destdir="${build.classes}" debug="true" includeantruntime="false">
<src path="${build.src}" />
<classpath refid="build.classpath" />
</javac>
</target>
<target name="jar" depends="build">
<jar jarfile="${build.dir}/${final.name}.jar">
<fileset dir="${build.classes}" />
<fileset dir="${build.src}/resources"/>
</jar>
</target>
<target name="install" depends="jar">
<copy verbose="true" file="${build.dir}/${final.name}.jar" todir="${cassandra.dir}/lib" overwrite="true"/>
<copy verbose="true" file="${build.dir}/${final.name}.jar" todir="${cassandra.dir}/build/lib/jars" overwrite="true"/>
</target>
<target name="clean">
<delete dir="${build.dir}" />
<delete file="${cassandra.dir}/lib/${final.name}.jar"/>
<delete file="${cassandra.dir}/build/lib/jars/${final.name}.jar"/>
</target>
</project>

View File

@ -0,0 +1,81 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.cassandra.service.checks;
import java.time.Instant;
import java.util.Calendar;
import java.util.TimeZone;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.cassandra.config.StartupChecksConfiguration;
import org.apache.cassandra.exceptions.StartupException;
import org.apache.cassandra.service.StartupCheck;
public class MyCustomStartupCheck implements StartupCheck
{
private static final Logger logger = LoggerFactory.getLogger(MyCustomStartupCheck.class);
@Override
public String name()
{
return "my_check";
}
@Override
public void execute(StartupChecksConfiguration configuration) throws StartupException
{
if (configuration.isDisabled(name()))
return;
Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
calendar.set(2000, 1, 1);
if (Instant.now().isBefore(calendar.toInstant()))
{
throw new StartupException(StartupException.ERR_WRONG_MACHINE_STATE,
"Cassandra is the database for this millennium!");
}
else
{
logger.info("Executing " + name() + " with options: " + configuration.getConfig(name()));
}
}
@Override
public boolean isConfigurable()
{
return true;
}
@Override
public boolean isDisabledByDefault()
{
return false;
}
@Override
public void postAction(StartupChecksConfiguration options)
{
if (options.isDisabled(name()))
return;
logger.info("Executing post-action for " + name());
}
}

View File

@ -0,0 +1,16 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
org.apache.cassandra.service.checks.MyCustomStartupCheck

View File

@ -45,7 +45,6 @@ import org.apache.cassandra.index.internal.CassandraIndex;
import org.apache.cassandra.io.compress.BufferType;
import org.apache.cassandra.io.sstable.format.big.BigFormat;
import org.apache.cassandra.repair.autorepair.AutoRepairConfig;
import org.apache.cassandra.service.StartupChecks.StartupCheckType;
import org.apache.cassandra.utils.StorageCompatibilityMode;
import static org.apache.cassandra.config.CassandraRelevantProperties.AUTOCOMPACTION_ON_STARTUP_ENABLED;
@ -1012,7 +1011,7 @@ public class Config
public volatile DurationSpec.IntSecondsBound streaming_slow_events_log_timeout = new DurationSpec.IntSecondsBound("10s");
/** The configuration of startup checks. */
public volatile Map<StartupCheckType, Map<String, Object>> startup_checks = new HashMap<>();
public volatile Map<String, Map<String, Object>> startup_checks = new HashMap<>();
public volatile DurationSpec.LongNanosecondsBound repair_state_expires = new DurationSpec.LongNanosecondsBound("3d");
public volatile int repair_state_size = 100_000;

View File

@ -123,6 +123,8 @@ import org.apache.cassandra.security.EncryptionContext;
import org.apache.cassandra.security.JREProvider;
import org.apache.cassandra.security.SSLFactory;
import org.apache.cassandra.service.CacheService.CacheType;
import org.apache.cassandra.service.FileSystemOwnershipCheck;
import org.apache.cassandra.service.StartupChecks;
import org.apache.cassandra.service.StorageService;
import org.apache.cassandra.service.accord.api.AccordWaitStrategies;
import org.apache.cassandra.service.consensus.TransactionalMode;
@ -266,7 +268,7 @@ public class DatabaseDescriptor
* The configuration for guardrails.
*/
private static GuardrailsOptions guardrails;
private static StartupChecksOptions startupChecksOptions;
private static StartupChecksConfiguration startupChecksConfiguration;
private static ImmutableMap<String, SSTableFormat<?, ?>> sstableFormats;
private static volatile SSTableFormat<?, ?> selectedSSTableFormat;
@ -1341,14 +1343,22 @@ public class DatabaseDescriptor
}
}
public static StartupChecksOptions getStartupChecksOptions()
public static StartupChecksConfiguration getStartupChecksConfiguration()
{
return startupChecksOptions;
return startupChecksConfiguration;
}
private static void applyStartupChecks()
{
startupChecksOptions = new StartupChecksOptions(conf.startup_checks);
try
{
StartupChecks startupChecks = new StartupChecks().withDefaultTests().withTest(new FileSystemOwnershipCheck()).withServiceLoaderTests();
startupChecksConfiguration = new StartupChecksConfiguration(startupChecks, conf.startup_checks);
}
catch (Throwable t)
{
throw new ConfigurationException("Invalid configuration of startup_checks: " + t.getMessage());
}
}
private static String storagedirFor(String type)

View File

@ -0,0 +1,149 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.cassandra.config;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.google.common.annotations.VisibleForTesting;
import org.apache.cassandra.exceptions.StartupException;
import org.apache.cassandra.service.StartupCheck;
import org.apache.cassandra.service.StartupChecks;
import static java.lang.Boolean.FALSE;
import static java.lang.Boolean.TRUE;
public class StartupChecksConfiguration
{
public static final String ENABLED_PROPERTY = "enabled";
private final Map<String, Map<String, Object>> options = new HashMap<>();
private final StartupChecks startupChecks;
public StartupChecksConfiguration(StartupChecks startupChecks, Map<String, Map<String, Object>> options)
{
this.options.putAll(new HashMap<>(options));
this.startupChecks = startupChecks;
apply();
}
@VisibleForTesting
public StartupCheck getCheck(String name)
{
return startupChecks.getCheck(name);
}
private StartupCheck getConfigurableCheck(String name)
{
StartupCheck check = startupChecks.getCheck(name);
if (check == null || !check.isConfigurable())
return null;
else
return check;
}
public void set(String name, String key, Object value)
{
StartupCheck check = getConfigurableCheck(name);
if (check == null)
return;
Map<String, Object> checkConfiguration = options.get(name);
if (checkConfiguration == null)
return;
checkConfiguration.put(key, value);
}
public void enable(String name)
{
set(name, ENABLED_PROPERTY, TRUE);
}
public void disable(String name)
{
set(name, ENABLED_PROPERTY, FALSE);
}
public boolean isEnabled(String name)
{
Map<String, Object> config = getConfig(name);
if (config == null)
return false;
Object enabledBoolean = config.get(ENABLED_PROPERTY);
if (enabledBoolean == null)
return false;
return Boolean.parseBoolean(enabledBoolean.toString());
}
public boolean isDisabled(String name)
{
return !isEnabled(name);
}
public Map<String, Object> getConfig(String name)
{
return options.get(name);
}
private void apply()
{
List<String> notExistingCheckNames = new ArrayList<>();
List<String> notConfigurableCheckNames = new ArrayList<>();
for (Map.Entry<String, Map<String, Object>> userConfigEntry : options.entrySet())
{
String key = userConfigEntry.getKey();
StartupCheck check = startupChecks.getCheck(key);
if (check == null)
notExistingCheckNames.add(key);
else if (!check.isConfigurable())
notConfigurableCheckNames.add(key);
}
if (!notExistingCheckNames.isEmpty())
throw new IllegalStateException("There are configuration entries for startup checks which do not exist: " + notExistingCheckNames);
if (!notConfigurableCheckNames.isEmpty())
throw new IllegalStateException("There are configuration entries for startup checks which are not configurable: " + notConfigurableCheckNames);
for (StartupCheck check : startupChecks.getChecks())
{
String startupCheckName = check.name();
Map<String, Object> configMap = this.options.computeIfAbsent(startupCheckName, k -> new HashMap<>());
if (configMap.containsKey(ENABLED_PROPERTY))
configMap.putIfAbsent(ENABLED_PROPERTY, FALSE);
else if (check.isDisabledByDefault())
configMap.put(ENABLED_PROPERTY, FALSE);
else
configMap.put(ENABLED_PROPERTY, TRUE);
}
}
public void verify() throws StartupException
{
assert startupChecks != null;
startupChecks.verify(this);
}
}

View File

@ -1,97 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.cassandra.config;
import java.util.Collections;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.Map;
import org.apache.cassandra.service.StartupChecks.StartupCheckType;
import static java.lang.Boolean.FALSE;
import static java.lang.Boolean.TRUE;
import static org.apache.cassandra.service.StartupChecks.StartupCheckType.non_configurable_check;
public class StartupChecksOptions
{
public static final String ENABLED_PROPERTY = "enabled";
private final Map<StartupCheckType, Map<String, Object>> options = new EnumMap<>(StartupCheckType.class);
public StartupChecksOptions()
{
this(Collections.emptyMap());
}
public StartupChecksOptions(final Map<StartupCheckType, Map<String, Object>> options)
{
this.options.putAll(options);
apply();
}
public void set(final StartupCheckType startupCheckType, final String key, final Object value)
{
if (startupCheckType != non_configurable_check)
options.get(startupCheckType).put(key, value);
}
public void enable(final StartupCheckType startupCheckType)
{
set(startupCheckType, ENABLED_PROPERTY, TRUE);
}
public void disable(final StartupCheckType startupCheckType)
{
if (startupCheckType != non_configurable_check)
set(startupCheckType, ENABLED_PROPERTY, FALSE);
}
public boolean isEnabled(final StartupCheckType startupCheckType)
{
return Boolean.parseBoolean(options.get(startupCheckType).get(ENABLED_PROPERTY).toString());
}
public boolean isDisabled(final StartupCheckType startupCheckType)
{
return !isEnabled(startupCheckType);
}
public Map<String, Object> getConfig(final StartupCheckType startupCheckType)
{
return options.get(startupCheckType);
}
private void apply()
{
for (final StartupCheckType startupCheckType : StartupCheckType.values())
{
final Map<String, Object> configMap = options.computeIfAbsent(startupCheckType, k -> new HashMap<>());
if (configMap.containsKey(ENABLED_PROPERTY))
configMap.putIfAbsent(ENABLED_PROPERTY, FALSE);
else if (startupCheckType.disabledByDefault)
configMap.put(ENABLED_PROPERTY, FALSE);
else
configMap.put(ENABLED_PROPERTY, TRUE);
}
// clear if we put anything into it by accident & enable this check every time no matter what
options.get(non_configurable_check).clear();
options.get(non_configurable_check).put(ENABLED_PROPERTY, TRUE);
}
}

View File

@ -216,7 +216,6 @@ public class CassandraDaemon
private JMXConnectorServer jmxServer;
private final boolean runManaged;
protected final StartupChecks startupChecks;
private boolean setupCompleted;
public CassandraDaemon()
@ -227,7 +226,6 @@ public class CassandraDaemon
public CassandraDaemon(boolean runManaged)
{
this.runManaged = runManaged;
this.startupChecks = new StartupChecks().withDefaultTests().withTest(new FileSystemOwnershipCheck());
this.setupCompleted = false;
}
@ -451,7 +449,7 @@ public class CassandraDaemon
{
try
{
startupChecks.verify(DatabaseDescriptor.getStartupChecksOptions());
DatabaseDescriptor.getStartupChecksConfiguration().verify();
}
catch (StartupException e)
{

View File

@ -40,7 +40,7 @@ import org.slf4j.LoggerFactory;
import org.apache.cassandra.concurrent.ScheduledExecutors;
import org.apache.cassandra.config.CassandraRelevantProperties;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.config.StartupChecksOptions;
import org.apache.cassandra.config.StartupChecksConfiguration;
import org.apache.cassandra.exceptions.StartupException;
import org.apache.cassandra.io.util.File;
import org.apache.cassandra.schema.KeyspaceMetadata;
@ -146,18 +146,30 @@ public class DataResurrectionCheck implements StartupCheck
}
@Override
public StartupChecks.StartupCheckType getStartupCheckType()
public boolean isConfigurable()
{
return StartupChecks.StartupCheckType.check_data_resurrection;
return true;
}
@Override
public void execute(StartupChecksOptions options) throws StartupException
public String name()
{
if (options.isDisabled(getStartupCheckType()))
return "check_data_resurrection";
}
@Override
public boolean isDisabledByDefault()
{
return true;
}
@Override
public void execute(StartupChecksConfiguration configuration) throws StartupException
{
if (configuration.isDisabled(name()))
return;
Map<String, Object> config = options.getConfig(StartupChecks.StartupCheckType.check_data_resurrection);
Map<String, Object> config = configuration.getConfig(name());
File heartbeatFile = getHeartbeatFile(config);
if (!heartbeatFile.exists())
@ -224,14 +236,15 @@ public class DataResurrectionCheck implements StartupCheck
}
@Override
public void postAction(StartupChecksOptions options)
public void postAction(StartupChecksConfiguration configuration)
{
// Schedule heartbeating after all checks have passed, not as part of the check,
// as it might happen that other checks after it might fail, but we would be heartbeating already.
if (options.isEnabled(StartupChecks.StartupCheckType.check_data_resurrection))
{
Map<String, Object> config = options.getConfig(StartupChecks.StartupCheckType.check_data_resurrection);
File heartbeatFile = DataResurrectionCheck.getHeartbeatFile(config);
if (!configuration.isEnabled(name()))
return;
Map<String, Object> configMap = configuration.getConfig(name());
File heartbeatFile = DataResurrectionCheck.getHeartbeatFile(configMap);
ScheduledExecutors.scheduledTasks.scheduleAtFixedRate(() ->
{
@ -248,7 +261,6 @@ public class DataResurrectionCheck implements StartupCheck
}
}, 0, CassandraRelevantProperties.CHECK_DATA_RESURRECTION_HEARTBEAT_PERIOD.getInt(), MILLISECONDS);
}
}
@VisibleForTesting
public Set<String> getExcludedKeyspaces(Map<String, Object> config)

View File

@ -39,12 +39,10 @@ import org.slf4j.LoggerFactory;
import org.apache.cassandra.config.CassandraRelevantProperties;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.config.StartupChecksOptions;
import org.apache.cassandra.config.StartupChecksConfiguration;
import org.apache.cassandra.exceptions.StartupException;
import org.apache.cassandra.io.util.File;
import static org.apache.cassandra.service.StartupChecks.StartupCheckType.check_filesystem_ownership;
/**
* Ownership markers on disk are compatible with the java property file format.
* (https://docs.oracle.com/javase/8/docs/api/java/util/Properties.html#load-java.io.Reader-)
@ -99,7 +97,7 @@ public class FileSystemOwnershipCheck implements StartupCheck
private final Supplier<Iterable<String>> dirs;
FileSystemOwnershipCheck()
public FileSystemOwnershipCheck()
{
this(() -> Iterables.concat(Arrays.asList(DatabaseDescriptor.getAllDataFileLocations()),
Arrays.asList(DatabaseDescriptor.getCommitLogLocation(),
@ -114,21 +112,33 @@ public class FileSystemOwnershipCheck implements StartupCheck
}
@Override
public StartupChecks.StartupCheckType getStartupCheckType()
public String name()
{
return check_filesystem_ownership;
return "check_filesystem_ownership";
}
@Override
public void execute(StartupChecksOptions options) throws StartupException
public boolean isConfigurable()
{
if (!isEnabled(options))
return true;
}
@Override
public boolean isDisabledByDefault()
{
return true;
}
@Override
public void execute(StartupChecksConfiguration configuration) throws StartupException
{
if (!isEnabled(configuration))
{
logger.info("Filesystem ownership check is not enabled.");
return;
}
Map<String, Object> config = options.getConfig(getStartupCheckType());
Map<String, Object> config = configuration.getConfig(name());
String expectedToken = constructTokenFromProperties(config);
String tokenFilename = getFsOwnershipFilename(config);
@ -275,9 +285,9 @@ public class FileSystemOwnershipCheck implements StartupCheck
return new StartupException(StartupException.ERR_WRONG_DISK_STATE, ERROR_PREFIX + message);
}
public boolean isEnabled(StartupChecksOptions options)
public boolean isEnabled(StartupChecksConfiguration options)
{
boolean enabledFromYaml = options.isEnabled(getStartupCheckType());
boolean enabledFromYaml = options.isEnabled(name());
return CassandraRelevantProperties.FILE_SYSTEM_CHECK_ENABLE.getBoolean(enabledFromYaml);
}

View File

@ -17,9 +17,8 @@
*/
package org.apache.cassandra.service;
import org.apache.cassandra.config.StartupChecksOptions;
import org.apache.cassandra.config.StartupChecksConfiguration;
import org.apache.cassandra.exceptions.StartupException;
import org.apache.cassandra.service.StartupChecks.StartupCheckType;
/**
* A test to determine if the system is in a valid state to start up.
@ -35,33 +34,56 @@ import org.apache.cassandra.service.StartupChecks.StartupCheckType;
*/
public interface StartupCheck
{
/**
* Name of a startup check, as it would appear in the configuration in cassandra.yaml.
* Not all startup checks are configurable. It is considered to be an illegal state to
* mention non-configurable startup check in cassandra.yaml.
*
* @return name of a startup check
*/
String name();
/**
* Run some test to determine whether the system is safe to be started
* In the case where a test determines it is not safe to proceed, the
* test should log a message regarding the reason for the failure and
* ideally the steps required to remedy the problem.
*
* @param startupChecksOptions all options from descriptor
* @param configuration all options from descriptor
* @throws org.apache.cassandra.exceptions.StartupException if the test determines
* that the environement or system is not in a safe state to startup
*/
void execute(StartupChecksOptions startupChecksOptions) throws StartupException;
void execute(StartupChecksConfiguration configuration) throws StartupException;
/**
* Tells whether a startup check can be configured, at the moment via cassandra.yml.
*
* @return type of this startup check for configuration retrieval
* @return true if a startup check is configurable, false otherwise.
*/
default StartupCheckType getStartupCheckType()
default boolean isConfigurable()
{
return StartupCheckType.non_configurable_check;
return false;
}
/**
* Tells if a specific (configurable) check is executed when it is not specified in cassandra.yaml. By default,
* an implementation of a startup check is executed even if it is not specified. For some checks, it might be
* preferential to not execute them when they are not explicity mentioned.
*
* @return true if a check is not executed when it is not specified in cassandra.yaml, defaults to false - that is,
* a check will be executed even if it is not explicitly mentioned in cassandra.yaml.
*/
default boolean isDisabledByDefault()
{
return false;
}
/**
* Post-hook after all startup checks succeeded.
*
* @param options startup check options from descriptor
* @param configuration startup check options from descriptor
*/
default void postAction(StartupChecksOptions options)
default void postAction(StartupChecksConfiguration configuration)
{
}
}

View File

@ -36,6 +36,8 @@ import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.ServiceConfigurationError;
import java.util.ServiceLoader;
import java.util.Set;
import java.util.stream.Collectors;
@ -57,7 +59,7 @@ import org.apache.cassandra.config.CassandraRelevantProperties;
import org.apache.cassandra.config.Config;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.config.JMXServerOptions;
import org.apache.cassandra.config.StartupChecksOptions;
import org.apache.cassandra.config.StartupChecksConfiguration;
import org.apache.cassandra.cql3.QueryProcessor;
import org.apache.cassandra.cql3.UntypedResultSet;
import org.apache.cassandra.db.ColumnFamilyStore;
@ -105,26 +107,6 @@ import static org.apache.cassandra.utils.LocalizeString.toLowerCaseLocalized;
*/
public class StartupChecks
{
public enum StartupCheckType
{
// non-configurable check is always enabled for execution
non_configurable_check,
check_filesystem_ownership(true),
check_data_resurrection(true);
public final boolean disabledByDefault;
StartupCheckType()
{
this(false);
}
StartupCheckType(boolean disabledByDefault)
{
this.disabledByDefault = disabledByDefault;
}
}
private static final Logger logger = LoggerFactory.getLogger(StartupChecks.class);
// List of checks to run before starting up. If any test reports failure, startup will be halted.
private final List<StartupCheck> preFlightChecks = new ArrayList<>();
@ -150,12 +132,77 @@ public class StartupChecks
checkKernelParamsForAsyncProfiler,
new DataResurrectionCheck());
public List<StartupCheck> getChecks()
{
return List.copyOf(preFlightChecks);
}
public StartupCheck getCheck(String name)
{
for (StartupCheck startupCheck : preFlightChecks)
{
if (startupCheck.name().equals(name))
return startupCheck;
}
return null;
}
public StartupChecks withDefaultTests()
{
preFlightChecks.addAll(DEFAULT_TESTS);
return this;
}
public StartupChecks withServiceLoaderTests()
{
ServiceLoader<StartupCheck> loader;
try
{
loader = ServiceLoader.load(StartupCheck.class);
}
catch (ServiceConfigurationError t)
{
logger.warn("Unable to get startup checks via ServiceLoader. " +
"Custom checks will not be triggered. Reason: " + t.getMessage());
return this;
}
Set<StartupCheck> customChecks = new HashSet<>();
Set<String> uniqueNames = new HashSet<>();
Set<String> duplicitNames = new HashSet<>();
for (StartupCheck check : loader)
{
if (!uniqueNames.add(check.name()))
duplicitNames.add(check.name());
else
customChecks.add(check);
}
if (!duplicitNames.isEmpty())
{
throw new IllegalStateException("There was an attempt to load custom startup " +
"checks with same name which is ambiguous: " + duplicitNames);
}
for (StartupCheck customCheck : customChecks)
{
for (StartupCheck preFlightCheck : preFlightChecks)
{
if (preFlightCheck.name().equals(customCheck.name()))
{
throw new IllegalStateException("There was an attempt to load custom startup check " +
"with same name as in-built check: " + preFlightCheck.name());
}
}
}
preFlightChecks.addAll(customChecks);
return this;
}
/**
* Add system test to be run before schema is loaded during startup
* @param test the system test to include
@ -172,7 +219,7 @@ public class StartupChecks
* system is not in an valid state to startup
* @param options options to pass to respective checks for their configration
*/
public void verify(StartupChecksOptions options) throws StartupException
public void verify(StartupChecksConfiguration options) throws StartupException
{
for (StartupCheck test : preFlightChecks)
test.execute(options);
@ -185,7 +232,7 @@ public class StartupChecks
}
catch (Throwable t)
{
logger.warn("Failed to run startup check post-action on " + test.getStartupCheckType());
logger.warn("Failed to run startup check post-action on " + test.name());
}
}
}
@ -194,9 +241,15 @@ public class StartupChecks
public static final StartupCheck checkKernelBug1057843 = new StartupCheck()
{
@Override
public void execute(StartupChecksOptions startupChecksOptions) throws StartupException
public String name()
{
if (startupChecksOptions.isDisabled(getStartupCheckType()))
return "kernel_bug_1057843";
}
@Override
public void execute(StartupChecksConfiguration configuration) throws StartupException
{
if (configuration.isDisabled(name()))
return;
if (!FBUtilities.isLinux)
@ -251,9 +304,15 @@ public class StartupChecks
public static final StartupCheck checkJemalloc = new StartupCheck()
{
@Override
public void execute(StartupChecksOptions options)
public String name()
{
if (options.isDisabled(getStartupCheckType()))
return "jemalloc";
}
@Override
public void execute(StartupChecksConfiguration configuration)
{
if (configuration.isDisabled(name()))
return;
String jemalloc = CassandraRelevantProperties.LIBJEMALLOC.getString();
@ -269,9 +328,15 @@ public class StartupChecks
public static final StartupCheck checkLz4Native = new StartupCheck()
{
@Override
public void execute(StartupChecksOptions options)
public String name()
{
if (options.isDisabled(getStartupCheckType()))
return "lz4_native";
}
@Override
public void execute(StartupChecksConfiguration configuration)
{
if (configuration.isDisabled(name()))
return;
try
{
@ -286,6 +351,12 @@ public class StartupChecks
public static final StartupCheck checkValidLaunchDate = new StartupCheck()
{
@Override
public String name()
{
return "valid_launch_date";
}
/**
* The earliest legit timestamp a casandra instance could have ever launched.
* Date roughly taken from http://perspectives.mvdirona.com/2008/07/12/FacebookReleasesCassandraAsOpenSource.aspx
@ -294,9 +365,9 @@ public class StartupChecks
private static final long EARLIEST_LAUNCH_DATE = 1215820800000L;
@Override
public void execute(StartupChecksOptions options) throws StartupException
public void execute(StartupChecksConfiguration configuration) throws StartupException
{
if (options.isDisabled(getStartupCheckType()))
if (configuration.isDisabled(name()))
return;
long now = currentTimeMillis();
if (now < EARLIEST_LAUNCH_DATE)
@ -309,9 +380,15 @@ public class StartupChecks
public static final StartupCheck checkJMXPorts = new StartupCheck()
{
@Override
public void execute(StartupChecksOptions options)
public String name()
{
if (options.isDisabled(getStartupCheckType()))
return "jmx_ports";
}
@Override
public void execute(StartupChecksConfiguration configuration)
{
if (configuration.isDisabled(name()))
return;
JMXServerOptions jmxServerOptions = DatabaseDescriptor.getJmxServerOptions();
@ -335,9 +412,15 @@ public class StartupChecks
public static final StartupCheck checkJMXProperties = new StartupCheck()
{
@Override
public void execute(StartupChecksOptions options)
public String name()
{
if (options.isDisabled(getStartupCheckType()))
return "jmx_properties";
}
@Override
public void execute(StartupChecksConfiguration configuration)
{
if (configuration.isDisabled(name()))
return;
if (COM_SUN_MANAGEMENT_JMXREMOTE_PORT.isPresent())
{
@ -350,9 +433,15 @@ public class StartupChecks
public static final StartupCheck inspectJvmOptions = new StartupCheck()
{
@Override
public void execute(StartupChecksOptions options)
public String name()
{
if (options.isDisabled(getStartupCheckType()))
return "jvm_options";
}
@Override
public void execute(StartupChecksConfiguration configuration)
{
if (configuration.isDisabled(name()))
return;
// log warnings for different kinds of sub-optimal JVMs. tldr use 64-bit Oracle >= 1.6u32
if (!DatabaseDescriptor.hasLargeAddressSpace())
@ -412,9 +501,15 @@ public class StartupChecks
public static final StartupCheck checkNativeLibraryInitialization = new StartupCheck()
{
@Override
public void execute(StartupChecksOptions options) throws StartupException
public String name()
{
if (options.isDisabled(getStartupCheckType()))
return "native_library_initialization";
}
@Override
public void execute(StartupChecksConfiguration configuration) throws StartupException
{
if (configuration.isDisabled(name()))
return;
// Fail-fast if the native library could not be linked.
if (!NativeLibrary.isAvailable())
@ -425,7 +520,13 @@ public class StartupChecks
public static final StartupCheck checkProcessEnvironment = new StartupCheck()
{
@Override
public void execute(StartupChecksOptions options)
public String name()
{
return "process_environment";
}
@Override
public void execute(StartupChecksConfiguration configuration)
{
Optional<String> degradations = FBUtilities.getSystemInfo().isDegraded();
@ -438,6 +539,12 @@ public class StartupChecks
public static final StartupCheck checkReadAheadKbSetting = new StartupCheck()
{
@Override
public String name()
{
return "read_ahead_kb_setting";
}
// This value is in KB.
private static final long MAX_RECOMMENDED_READ_AHEAD_KB_SETTING = 128;
@ -472,9 +579,9 @@ public class StartupChecks
}
@Override
public void execute(StartupChecksOptions options)
public void execute(StartupChecksConfiguration configuration)
{
if (options.isDisabled(getStartupCheckType()) || !FBUtilities.isLinux)
if (configuration.isDisabled(name()) || !FBUtilities.isLinux)
return;
String[] dataDirectories = DatabaseDescriptor.getRawConfig().data_file_directories;
@ -518,6 +625,12 @@ public class StartupChecks
public static final StartupCheck checkMaxMapCount = new StartupCheck()
{
@Override
public String name()
{
return "max_map_count";
}
private final long EXPECTED_MAX_MAP_COUNT = 1048575;
private final String MAX_MAP_COUNT_PATH = "/proc/sys/vm/max_map_count";
@ -547,9 +660,9 @@ public class StartupChecks
}
@Override
public void execute(StartupChecksOptions options)
public void execute(StartupChecksConfiguration configuration)
{
if (options.isDisabled(getStartupCheckType()) || !FBUtilities.isLinux)
if (configuration.isDisabled(name()) || !FBUtilities.isLinux)
return;
if (DatabaseDescriptor.getDiskAccessMode() == Config.DiskAccessMode.standard &&
@ -567,9 +680,15 @@ public class StartupChecks
public static final StartupCheck checkDataDirs = new StartupCheck()
{
@Override
public void execute(StartupChecksOptions options) throws StartupException
public String name()
{
if (options.isDisabled(getStartupCheckType()))
return "data_dirs";
}
@Override
public void execute(StartupChecksConfiguration configuration) throws StartupException
{
if (configuration.isDisabled(name()))
return;
// check all directories(data, commitlog, saved cache) for existence and permission
Iterable<String> dirs = Iterables.concat(Arrays.asList(DatabaseDescriptor.getAllDataFileLocations()),
@ -602,9 +721,15 @@ public class StartupChecks
public static final StartupCheck checkSSTablesFormat = new StartupCheck()
{
@Override
public void execute(StartupChecksOptions options) throws StartupException
public String name()
{
if (options.isDisabled(getStartupCheckType()))
return "sstables_format";
}
@Override
public void execute(StartupChecksConfiguration configuration) throws StartupException
{
if (configuration.isDisabled(name()))
return;
final Set<String> invalid = new HashSet<>();
final Set<String> nonSSTablePaths = new HashSet<>();
@ -717,9 +842,15 @@ public class StartupChecks
public static final StartupCheck checkSystemKeyspaceState = new StartupCheck()
{
@Override
public void execute(StartupChecksOptions options) throws StartupException
public String name()
{
if (options.isDisabled(getStartupCheckType()))
return "system_keyspace_state";
}
@Override
public void execute(StartupChecksConfiguration configuration) throws StartupException
{
if (configuration.isDisabled(name()))
return;
// check the system keyspace to keep user from shooting self in foot by changing partitioner, cluster name, etc.
// we do a one-off scrub of the system keyspace first; we can't load the list of the rest of the keyspaces,
@ -748,9 +879,15 @@ public class StartupChecks
public static final StartupCheck checkLegacyAuthTables = new StartupCheck()
{
@Override
public void execute(StartupChecksOptions options) throws StartupException
public String name()
{
if (options.isDisabled(getStartupCheckType()))
return "legacy_auth_tables";
}
@Override
public void execute(StartupChecksConfiguration configuration) throws StartupException
{
if (configuration.isDisabled(name()))
return;
Optional<String> errMsg = checkLegacyAuthTablesMessage();
if (errMsg.isPresent())
@ -792,7 +929,14 @@ public class StartupChecks
return perfEventParanoid <= 1 && kptrRestrict == 0;
}
public void execute(StartupChecksOptions startupChecksOptions, boolean shouldThrow)
@Override
public String name()
{
return "async_profiler_kernel_parameters";
}
public void execute(StartupChecksConfiguration startupChecksConfiguration, boolean shouldThrow)
{
try
{
@ -824,9 +968,9 @@ public class StartupChecks
}
@Override
public void execute(StartupChecksOptions startupChecksOptions)
public void execute(StartupChecksConfiguration configuration)
{
execute(startupChecksOptions, false);
execute(configuration, false);
}
}

View File

@ -18,36 +18,34 @@
package org.apache.cassandra.distributed.test;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
import org.junit.Test;
import org.apache.cassandra.config.StartupChecksOptions;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.config.StartupChecksConfiguration;
import org.apache.cassandra.distributed.Cluster;
import org.apache.cassandra.distributed.api.IInvokableInstance;
import org.apache.cassandra.distributed.api.IIsolatedExecutor;
import org.apache.cassandra.distributed.shared.WithProperties;
import org.apache.cassandra.exceptions.StartupException;
import org.apache.cassandra.io.util.File;
import org.apache.cassandra.service.DataResurrectionCheck;
import org.apache.cassandra.service.DataResurrectionCheck.Heartbeat;
import org.apache.cassandra.service.StartupChecks.StartupCheckType;
import org.apache.cassandra.service.StartupCheck;
import org.apache.cassandra.utils.Clock.Global;
import static java.lang.String.format;
import static java.util.concurrent.TimeUnit.MINUTES;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.apache.cassandra.config.CassandraRelevantProperties.CHECK_DATA_RESURRECTION_HEARTBEAT_PERIOD;
import static org.apache.cassandra.config.StartupChecksOptions.ENABLED_PROPERTY;
import static org.apache.cassandra.config.StartupChecksConfiguration.ENABLED_PROPERTY;
import static org.apache.cassandra.distributed.Cluster.build;
import static org.apache.cassandra.distributed.api.Feature.NATIVE_PROTOCOL;
import static org.apache.cassandra.service.DataResurrectionCheck.DEFAULT_HEARTBEAT_FILE;
import static org.apache.cassandra.service.DataResurrectionCheck.EXCLUDED_KEYSPACES_CONFIG_PROPERTY;
import static org.apache.cassandra.service.DataResurrectionCheck.EXCLUDED_TABLES_CONFIG_PROPERTY;
import static org.apache.cassandra.service.StartupChecks.StartupCheckType.check_data_resurrection;
import static org.awaitility.Awaitility.await;
import static org.hamcrest.Matchers.containsString;
import static org.junit.Assert.assertNotNull;
@ -137,14 +135,21 @@ public class DataResurrectionCheckTest extends TestBaseImpl
{
try
{
DataResurrectionCheck check = new DataResurrectionCheck();
StartupChecksOptions startupChecksOptions = new StartupChecksOptions();
startupChecksOptions.enable(check_data_resurrection);
StartupChecksConfiguration checksConfiguration = DatabaseDescriptor.getStartupChecksConfiguration();
checksConfiguration.enable("check_data_resurrection");
Map<String, Object> currentConfig = checksConfiguration.getConfig("check_data_resurrection");
if (currentConfig != null)
{
boolean wasEnabled = checksConfiguration.isEnabled("check_data_resurrection");
currentConfig.clear();
currentConfig.put(ENABLED_PROPERTY, wasEnabled);
}
for (int i = 0; i < config.length - 1; i = i + 2)
startupChecksOptions.set(check_data_resurrection, config[i], config[i + 1]);
checksConfiguration.set("check_data_resurrection", config[i], config[i + 1]);
check.execute(startupChecksOptions);
StartupCheck check = checksConfiguration.getCheck("check_data_resurrection");
check.execute(checksConfiguration);
return null;
}
catch (StartupException e)
@ -154,12 +159,12 @@ public class DataResurrectionCheckTest extends TestBaseImpl
}).call();
}
private Map<StartupCheckType, Map<String, Object>> getStartupChecksConfig(String... configs)
private Map<String, Map<String, Object>> getStartupChecksConfig(String... configs)
{
return new EnumMap<StartupCheckType, Map<String, Object>>(StartupCheckType.class)
return new HashMap<>()
{{
put(check_data_resurrection,
new HashMap<String, Object>()
put("check_data_resurrection",
new HashMap<>()
{{
for (int i = 0; i < configs.length - 1; i = i + 2)
put(configs[i], configs[i + 1]);

View File

@ -176,8 +176,7 @@ public class DatabaseDescriptorRefTest
"org.apache.cassandra.config.RetrySpec",
"org.apache.cassandra.config.RetrySpec$MaxAttempt",
"org.apache.cassandra.config.RetrySpec$Type",
"org.apache.cassandra.config.StartupChecksOptions",
"org.apache.cassandra.config.StartupChecksOptions",
"org.apache.cassandra.config.StartupChecksConfiguration",
"org.apache.cassandra.config.StorageAttachedIndexOptions",
"org.apache.cassandra.config.StringRetryStrategy",
"org.apache.cassandra.config.SubnetGroups",
@ -327,6 +326,7 @@ public class DatabaseDescriptorRefTest
"org.apache.cassandra.service.RetryStrategy$WaitRandomizerFactory",
"org.apache.cassandra.service.RetryStrategy$1",
"org.apache.cassandra.service.RetryStrategy$WaitRandomizer",
"org.apache.cassandra.service.StartupCheck",
"org.apache.cassandra.service.TimeoutStrategy",
"org.apache.cassandra.service.TimeoutStrategy$LatencyModifierFactory",
"org.apache.cassandra.service.TimeoutStrategy$Wait",

View File

@ -1,150 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.cassandra.config;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.junit.Test;
import org.apache.cassandra.service.DataResurrectionCheck;
import org.apache.cassandra.service.StartupChecks.StartupCheckType;
import org.apache.cassandra.utils.Pair;
import static org.apache.cassandra.config.StartupChecksOptions.ENABLED_PROPERTY;
import static org.apache.cassandra.service.StartupChecks.StartupCheckType.check_filesystem_ownership;
import static org.apache.cassandra.service.StartupChecks.StartupCheckType.non_configurable_check;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class StartupCheckOptionsTest
{
@Test
public void testStartupOptionsConfigApplication()
{
Map<StartupCheckType, Map<String, Object>> config = new EnumMap<StartupCheckType, Map<String, Object>>(StartupCheckType.class) {{
put(check_filesystem_ownership, new HashMap<String, Object>() {{
put(ENABLED_PROPERTY, true);
put("key", "value");
}});
}};
StartupChecksOptions options = new StartupChecksOptions(config);
assertTrue(Boolean.parseBoolean(options.getConfig(check_filesystem_ownership)
.get(ENABLED_PROPERTY)
.toString()));
assertEquals("value", options.getConfig(check_filesystem_ownership).get("key"));
options.set(check_filesystem_ownership, "key", "value2");
assertEquals("value2", options.getConfig(check_filesystem_ownership).get("key"));
assertTrue(options.isEnabled(check_filesystem_ownership));
options.disable(check_filesystem_ownership);
assertFalse(options.isEnabled(check_filesystem_ownership));
assertTrue(options.isDisabled(check_filesystem_ownership));
}
@Test
public void testNoOptions()
{
StartupChecksOptions options = new StartupChecksOptions();
assertTrue(options.isEnabled(non_configurable_check));
// disabling does not to anything on non-configurable check
options.disable(non_configurable_check);
assertTrue(options.isEnabled(non_configurable_check));
options.set(non_configurable_check, "key", "value");
// we can not put anything into non-configurable check
assertFalse(options.getConfig(non_configurable_check).containsKey("key"));
}
@Test
public void testEmptyDisabledValues()
{
Map<StartupCheckType, Map<String, Object>> emptyConfig = new EnumMap<StartupCheckType, Map<String, Object>>(StartupCheckType.class) {{
put(check_filesystem_ownership, new HashMap<>());
}};
Map<StartupCheckType, Map<String, Object>> emptyEnabledConfig = new EnumMap<StartupCheckType, Map<String, Object>>(StartupCheckType.class) {{
put(check_filesystem_ownership, new HashMap<String, Object>() {{
put(ENABLED_PROPERTY, null);
}});
}};
// empty enabled property or enabled property with null value are still counted as enabled
StartupChecksOptions options1 = new StartupChecksOptions(emptyConfig);
assertTrue(options1.isDisabled(check_filesystem_ownership));
StartupChecksOptions options2 = new StartupChecksOptions(emptyEnabledConfig);
assertTrue(options2.isDisabled(check_filesystem_ownership));
}
@Test
public void testChecksDisabledByDefaultAreNotEnabled()
{
Map<StartupCheckType, Map<String, Object>> emptyConfig = new EnumMap<>(StartupCheckType.class);
StartupChecksOptions options = new StartupChecksOptions(emptyConfig);
assertTrue(options.isDisabled(check_filesystem_ownership));
}
@Test
public void testExcludedKeyspacesInDataResurrectionCheckOptions()
{
Map<String, Object> config = new HashMap<String, Object>(){{
put("excluded_keyspaces", "ks1,ks2,ks3");
}};
DataResurrectionCheck check = new DataResurrectionCheck();
check.getExcludedKeyspaces(config);
Set<String> excludedKeyspaces = check.getExcludedKeyspaces(config);
assertEquals(3, excludedKeyspaces.size());
assertTrue(excludedKeyspaces.contains("ks1"));
assertTrue(excludedKeyspaces.contains("ks2"));
assertTrue(excludedKeyspaces.contains("ks3"));
}
@Test
public void testExcludedTablesInDataResurrectionCheckOptions()
{
for (String input : new String[]{
"ks1.tb1,ks1.tb2,ks3.tb3",
" ks1 . tb1, ks1 .tb2 ,ks3 .tb3 "
})
{
Map<String, Object> config = new HashMap<String, Object>(){{
put("excluded_tables", input);
}};
DataResurrectionCheck check = new DataResurrectionCheck();
Set<Pair<String, String>> excludedTables = check.getExcludedTables(config);
assertEquals(3, excludedTables.size());
assertTrue(excludedTables.contains(Pair.create("ks1", "tb1")));
assertTrue(excludedTables.contains(Pair.create("ks1", "tb2")));
assertTrue(excludedTables.contains(Pair.create("ks3", "tb3")));
}
}
}

View File

@ -0,0 +1,228 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.cassandra.config;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import org.junit.Assert;
import org.junit.Test;
import org.apache.cassandra.service.DataResurrectionCheck;
import org.apache.cassandra.service.FileSystemOwnershipCheck;
import org.apache.cassandra.service.StartupCheck;
import org.apache.cassandra.service.StartupChecks;
import org.apache.cassandra.utils.Pair;
import static org.apache.cassandra.config.StartupChecksConfiguration.ENABLED_PROPERTY;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
public class StartupChecksConfigurationTest
{
@Test
public void testStartupOptionsConfigApplication()
{
Map<String, Map<String, Object>> config = new HashMap<>()
{{
put("check_filesystem_ownership", new HashMap<>()
{{
put(ENABLED_PROPERTY, true);
put("key", "value");
}});
}};
StartupChecks startupChecks = new StartupChecks().withDefaultTests().withTest(new FileSystemOwnershipCheck());
StartupChecksConfiguration options = new StartupChecksConfiguration(startupChecks, config);
assertTrue(Boolean.parseBoolean(options.getConfig("check_filesystem_ownership")
.get(ENABLED_PROPERTY)
.toString()));
assertEquals("value", options.getConfig("check_filesystem_ownership").get("key"));
options.set("check_filesystem_ownership", "key", "value2");
assertEquals("value2", options.getConfig("check_filesystem_ownership").get("key"));
assertTrue(options.isEnabled("check_filesystem_ownership"));
options.disable("check_filesystem_ownership");
assertFalse(options.isEnabled("check_filesystem_ownership"));
assertTrue(options.isDisabled("check_filesystem_ownership"));
}
@Test
public void testNoOptions()
{
StartupChecks startupChecks = new StartupChecks().withDefaultTests();
StartupChecksConfiguration options = new StartupChecksConfiguration(new StartupChecks().withDefaultTests(), new HashMap<>());
for (StartupCheck check : startupChecks.getChecks())
{
if (!check.isConfigurable())
assertTrue(options.isEnabled(check.name()));
}
// disabling does not do anything on non-configurable check
Optional<StartupCheck> nonConfigurableCheck = startupChecks.getChecks().stream().filter(check -> !check.isConfigurable()).findFirst();
Assert.assertTrue(nonConfigurableCheck.isPresent());
String checkName = nonConfigurableCheck.get().name();
options.disable(checkName);
assertTrue(options.isEnabled(checkName));
options.set(checkName, "key", "value");
// we can not put anything into non-configurable check
Map<String, Object> nonConfigurableCheckConfig = options.getConfig(checkName);
assertNotNull(nonConfigurableCheckConfig);
assertFalse(nonConfigurableCheckConfig.containsKey("key"));
}
@Test
public void testEmptyDisabledValues()
{
Map<String, Map<String, Object>> emptyConfig = new HashMap<>()
{{
put("check_filesystem_ownership", new HashMap<>());
}};
Map<String, Map<String, Object>> emptyEnabledConfig = new HashMap<>()
{{
put("check_filesystem_ownership", new HashMap<>()
{{
put(ENABLED_PROPERTY, null);
}});
}};
// empty enabled property or enabled property with null value are still counted as disabled
StartupChecks startupChecks = new StartupChecks().withDefaultTests().withTest(new FileSystemOwnershipCheck());
StartupChecksConfiguration options1 = new StartupChecksConfiguration(startupChecks, emptyConfig);
assertTrue(options1.isDisabled("check_filesystem_ownership"));
StartupChecksConfiguration options2 = new StartupChecksConfiguration(startupChecks, emptyEnabledConfig);
assertTrue(options2.isDisabled("check_filesystem_ownership"));
}
@Test
public void testChecksDisabledByDefaultAreNotEnabled()
{
Map<String, Map<String, Object>> emptyConfig = new HashMap<>();
StartupChecksConfiguration options = new StartupChecksConfiguration(new StartupChecks().withDefaultTests(), emptyConfig);
assertTrue(options.isDisabled("check_filesystem_ownership"));
}
@Test
public void testExcludedKeyspacesInDataResurrectionCheckOptions()
{
Map<String, Object> config = new HashMap<>()
{{
put("excluded_keyspaces", "ks1,ks2,ks3");
}};
DataResurrectionCheck check = new DataResurrectionCheck();
check.getExcludedKeyspaces(config);
Set<String> excludedKeyspaces = check.getExcludedKeyspaces(config);
assertEquals(3, excludedKeyspaces.size());
assertTrue(excludedKeyspaces.contains("ks1"));
assertTrue(excludedKeyspaces.contains("ks2"));
assertTrue(excludedKeyspaces.contains("ks3"));
}
@Test
public void testExcludedTablesInDataResurrectionCheckOptions()
{
for (String input : new String[]{
"ks1.tb1,ks1.tb2,ks3.tb3",
" ks1 . tb1, ks1 .tb2 ,ks3 .tb3 "
})
{
Map<String, Object> config = new HashMap<>()
{{
put("excluded_tables", input);
}};
DataResurrectionCheck check = new DataResurrectionCheck();
Set<Pair<String, String>> excludedTables = check.getExcludedTables(config);
assertEquals(3, excludedTables.size());
assertTrue(excludedTables.contains(Pair.create("ks1", "tb1")));
assertTrue(excludedTables.contains(Pair.create("ks1", "tb2")));
assertTrue(excludedTables.contains(Pair.create("ks3", "tb3")));
}
}
@Test
public void testNonConfigurableCheckIsNotConfigurable()
{
StartupChecks startupChecks = new StartupChecks().withDefaultTests();
Optional<StartupCheck> maybeNotConfigurableCheck = startupChecks.getChecks().stream().filter(check -> !check.isConfigurable()).findFirst();
Assert.assertTrue(maybeNotConfigurableCheck.isPresent());
StartupCheck check = maybeNotConfigurableCheck.get();
Map<String, Map<String, Object>> config = new HashMap<>()
{
{
put(check.name(), new HashMap<>()
{{
put(ENABLED_PROPERTY, false);
put("key", "value");
}});
}
};
assertThatThrownBy(() -> new StartupChecksConfiguration(startupChecks, config))
.isInstanceOf(IllegalStateException.class)
.hasMessage(String.format("There are configuration entries for startup checks which are not configurable: [%s]", check.name()));
}
@Test
public void testNotExistingCheckYieldsInvalidState()
{
StartupChecks startupChecks = new StartupChecks().withDefaultTests();
Map<String, Map<String, Object>> config = new HashMap<>()
{
{
put("jemalloc", new HashMap<>()
{{
put(ENABLED_PROPERTY, true);
put("key", "value");
}});
put("check_data_resurrection", new HashMap<>()
{{
put(ENABLED_PROPERTY, true);
put("key2", "value2");
}});
}
};
assertThatThrownBy(() -> new StartupChecksConfiguration(startupChecks, config))
.isInstanceOf(IllegalStateException.class)
.hasMessage("There are configuration entries for startup checks which are not configurable: [jemalloc]");
}
}

View File

@ -46,7 +46,6 @@ import org.apache.cassandra.distributed.shared.WithEnvironment;
import org.apache.cassandra.distributed.shared.WithProperties;
import org.apache.cassandra.io.util.File;
import org.apache.cassandra.repair.autorepair.AutoRepairConfig;
import org.apache.cassandra.service.StartupChecks;
import static org.apache.cassandra.config.CassandraRelevantProperties.CONFIG_ALLOW_ENVIRONMENT_VARIABLES;
import static org.apache.cassandra.config.CassandraRelevantProperties.CONFIG_ALLOW_SYSTEM_PROPERTIES;
@ -179,8 +178,8 @@ public class YamlConfigurationLoaderTest
assertThat(config.paxos_variant).isEqualTo(Config.PaxosVariant.v2);
assertThat(config.client_error_reporting_exclusions).isEqualTo(new SubnetGroups(Arrays.asList("127.0.0.2", "127.0.0.1")));
assertThat(config.startup_checks).hasSize(1);
assertThat(config.startup_checks.get(StartupChecks.StartupCheckType.check_data_resurrection).get("enabled")).isEqualTo(Boolean.TRUE.toString());
assertThat(config.startup_checks.get(StartupChecks.StartupCheckType.check_data_resurrection).get("heartbeat_file")).isEqualTo("/var/lib/cassandra/data/cassandra-heartbeat");
assertThat(config.startup_checks.get("check_data_resurrection").get("enabled")).isEqualTo(Boolean.TRUE.toString());
assertThat(config.startup_checks.get("check_data_resurrection").get("heartbeat_file")).isEqualTo("/var/lib/cassandra/data/cassandra-heartbeat");
}
try (WithProperties ignore = new WithProperties()
@ -278,8 +277,8 @@ public class YamlConfigurationLoaderTest
assertThat(config.memtable.configurations.get("default").inherits).isEqualTo("trie");
assertThat(config.client_error_reporting_exclusions).isEqualTo(new SubnetGroups(Arrays.asList("127.0.0.2", "127.0.0.1")));
assertThat(config.startup_checks).hasSize(1);
assertThat(config.startup_checks.get(StartupChecks.StartupCheckType.check_data_resurrection).get("enabled")).isEqualTo("true");
assertThat(config.startup_checks.get(StartupChecks.StartupCheckType.check_data_resurrection).get("heartbeat_file")).isEqualTo("/var/lib/cassandra/data/cassandra-heartbeat");
assertThat(config.startup_checks.get("check_data_resurrection").get("enabled")).isEqualTo("true");
assertThat(config.startup_checks.get("check_data_resurrection").get("heartbeat_file")).isEqualTo("/var/lib/cassandra/data/cassandra-heartbeat");
}
try (WithEnvironment ignore = new WithEnvironment(CassandraRelevantEnv.CASSANDRA_ALLOW_CONFIG_ENVIRONMENT_VARIABLES.getKey(), Boolean.TRUE.toString(),

View File

@ -34,7 +34,7 @@ import org.junit.Ignore;
import org.junit.Test;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.config.StartupChecksOptions;
import org.apache.cassandra.config.StartupChecksConfiguration;
import org.apache.cassandra.distributed.shared.WithProperties;
import org.apache.cassandra.exceptions.StartupException;
import org.apache.cassandra.io.util.File;
@ -55,7 +55,6 @@ import static org.apache.cassandra.service.FileSystemOwnershipCheck.TOKEN;
import static org.apache.cassandra.service.FileSystemOwnershipCheck.UNSUPPORTED_VERSION;
import static org.apache.cassandra.service.FileSystemOwnershipCheck.VERSION;
import static org.apache.cassandra.service.FileSystemOwnershipCheck.VOLUME_COUNT;
import static org.apache.cassandra.service.StartupChecks.StartupCheckType.check_filesystem_ownership;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@ -66,7 +65,7 @@ public abstract class AbstractFilesystemOwnershipCheckTest
protected File tempDir;
protected String token;
protected StartupChecksOptions options = new StartupChecksOptions();
protected StartupChecksConfiguration options;
static WithProperties properties;
@ -99,7 +98,7 @@ public abstract class AbstractFilesystemOwnershipCheckTest
}
private static void executeAndFail(FileSystemOwnershipCheck checker,
StartupChecksOptions options,
StartupChecksConfiguration options,
String messageTemplate,
Object...messageArgs)
{
@ -206,7 +205,7 @@ public abstract class AbstractFilesystemOwnershipCheckTest
public void skipCheckDisabledIfSystemPropertyIsEmpty() throws Exception
{
// no exceptions thrown from the supplier because the check is skipped
options.disable(check_filesystem_ownership);
options.disable("check_filesystem_ownership");
System.clearProperty(FILE_SYSTEM_CHECK_ENABLE.getKey());
AbstractFilesystemOwnershipCheckTest.checker(() -> { throw new RuntimeException("FAIL"); }).execute(options);
}
@ -215,7 +214,7 @@ public abstract class AbstractFilesystemOwnershipCheckTest
public void skipCheckDisabledIfSystemPropertyIsFalseButOptionsEnabled() throws Exception
{
// no exceptions thrown from the supplier because the check is skipped
options.enable(check_filesystem_ownership);
options.enable("check_filesystem_ownership");
FILE_SYSTEM_CHECK_ENABLE.setBoolean(false);
AbstractFilesystemOwnershipCheckTest.checker(() -> { throw new RuntimeException("FAIL"); }).execute(options);
}
@ -230,7 +229,7 @@ public abstract class AbstractFilesystemOwnershipCheckTest
@Test
public void checkEnabledButClusterPropertyIsUnset()
{
Assume.assumeFalse(options.getConfig(check_filesystem_ownership).containsKey("ownership_token"));
Assume.assumeFalse(options.getConfig("check_filesystem_ownership").containsKey("ownership_token"));
FILE_SYSTEM_CHECK_OWNERSHIP_TOKEN.clearValue(); // checkstyle: suppress nearby 'clearValueSystemPropertyUsage'
AbstractFilesystemOwnershipCheckTest.executeAndFail(checker(tempDir), options, MISSING_PROPERTY, FILE_SYSTEM_CHECK_OWNERSHIP_TOKEN.getKey());
}

View File

@ -25,8 +25,10 @@ import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.spi.FileSystemProvider;
import java.time.Instant;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.ServiceLoader;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.stream.Collectors;
@ -40,12 +42,14 @@ import org.junit.Assume;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.apache.cassandra.SchemaLoader;
import org.apache.cassandra.config.CassandraRelevantProperties;
import org.apache.cassandra.config.Config.DiskAccessMode;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.config.StartupChecksOptions;
import org.apache.cassandra.config.StartupChecksConfiguration;
import org.apache.cassandra.db.ColumnFamilyStore;
import org.apache.cassandra.db.Directories;
import org.apache.cassandra.db.Keyspace;
@ -64,13 +68,18 @@ import static java.util.Collections.singletonList;
import static org.apache.cassandra.config.CassandraRelevantProperties.TEST_INVALID_LEGACY_SSTABLE_ROOT;
import static org.apache.cassandra.io.util.FileUtils.createTempFile;
import static org.apache.cassandra.service.DataResurrectionCheck.HEARTBEAT_FILE_CONFIG_PROPERTY;
import static org.apache.cassandra.service.StartupChecks.StartupCheckType.check_data_resurrection;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
public class StartupChecksTest
@ -88,7 +97,7 @@ public class StartupChecksTest
Path sstableDir;
static File heartbeatFile;
StartupChecksOptions options = new StartupChecksOptions();
StartupChecksConfiguration options = new StartupChecksConfiguration(new StartupChecks().withDefaultTests(), new HashMap<>());
@BeforeClass
public static void setupServer()
@ -109,8 +118,8 @@ public class StartupChecksTest
sstableDir = Paths.get(dataDir.absolutePath(), "Keyspace1", "Standard1");
Files.createDirectories(sstableDir);
options.enable(check_data_resurrection);
options.getConfig(check_data_resurrection)
options.enable("check_data_resurrection");
options.getConfig("check_data_resurrection")
.put(HEARTBEAT_FILE_CONFIG_PROPERTY, heartbeatFile.absolutePath());
startupChecks = new StartupChecks();
@ -183,13 +192,13 @@ public class StartupChecksTest
public void testGetReadAheadKBPath()
{
Path sdaDirectory = StartupChecks.getReadAheadKBPath("/dev/sda12");
Assert.assertEquals(Paths.get("/sys/block/sda/queue/read_ahead_kb"), sdaDirectory);
assertEquals(Paths.get("/sys/block/sda/queue/read_ahead_kb"), sdaDirectory);
Path scsiDirectory = StartupChecks.getReadAheadKBPath("/dev/scsi1");
Assert.assertEquals(Paths.get("/sys/block/scsi/queue/read_ahead_kb"), scsiDirectory);
assertEquals(Paths.get("/sys/block/scsi/queue/read_ahead_kb"), scsiDirectory);
Path dirWithoutNumbers = StartupChecks.getReadAheadKBPath("/dev/sca");
Assert.assertEquals(Paths.get("/sys/block/sca/queue/read_ahead_kb"), dirWithoutNumbers);
assertEquals(Paths.get("/sys/block/sca/queue/read_ahead_kb"), dirWithoutNumbers);
Path invalidDir = StartupChecks.getReadAheadKBPath("/invaliddir/xpto");
Assert.assertNull(invalidDir);
@ -254,6 +263,157 @@ public class StartupChecksTest
testKernelBug1057843Check("ext4", DiskAccessMode.mmap, new Semver("6.1.64.1-generic"), false);
}
@SuppressWarnings("unchecked")
@Test
public void testExternalCheckIsLoaded() throws StartupException
{
StartupCheck externalCheck = spy(new StartupCheck()
{
@Override
public String name()
{
return "my_custom_check";
}
@Override
public void execute(StartupChecksConfiguration configuration)
{
}
@Override
public boolean isConfigurable()
{
return true;
}
@Override
public boolean isDisabledByDefault()
{
return false;
}
});
ServiceLoader<StartupCheck> loader = mock(ServiceLoader.class);
doReturn(List.of(externalCheck).iterator()).when(loader).iterator();
try (MockedStatic<ServiceLoader> serviceLoader = Mockito.mockStatic(ServiceLoader.class)) {
serviceLoader.when(() -> ServiceLoader.load(StartupCheck.class)).thenReturn(loader);
StartupChecks checks = new StartupChecks().withDefaultTests().withServiceLoaderTests();
StartupCheck myCustomCheck = checks.getCheck("my_custom_check");
assertNotNull(myCustomCheck);
StartupChecksConfiguration configuration = new StartupChecksConfiguration(checks, new HashMap<>());
checks.verify(configuration);
verify(externalCheck, times(1)).execute(configuration);
}
}
@Test
public void testLoadingCustomChecksWithNotUniqueNameIsForbidden()
{
StartupCheck externalCheck = spy(new StartupCheck()
{
@Override
public String name()
{
return "my_custom_check";
}
@Override
public void execute(StartupChecksConfiguration configuration)
{
}
@Override
public boolean isConfigurable()
{
return true;
}
@Override
public boolean isDisabledByDefault()
{
return false;
}
});
ServiceLoader<StartupCheck> loader = mock(ServiceLoader.class);
// two times! We model loading of two checks with same name
doReturn(List.of(externalCheck, externalCheck).iterator()).when(loader).iterator();
try (MockedStatic<ServiceLoader> serviceLoader = Mockito.mockStatic(ServiceLoader.class)) {
serviceLoader.when(() -> ServiceLoader.load(StartupCheck.class)).thenReturn(loader);
try
{
new StartupChecks().withDefaultTests().withServiceLoaderTests();
fail("it should not be possible to specify two custom checks with same name");
}
catch (Throwable t)
{
assertEquals("There was an attempt to load custom startup checks with same name which is ambiguous: [my_custom_check]",
t.getMessage());
}
}
}
@Test
public void testCustomCheckHasSameNameAsInBuiltCheck()
{
StartupCheck externalCheck = spy(new StartupCheck()
{
@Override
public String name()
{
// for the sake of it being same as one of in-builts
return StartupChecks.checkLz4Native.name();
}
@Override
public void execute(StartupChecksConfiguration configuration)
{
}
@Override
public boolean isConfigurable()
{
return true;
}
@Override
public boolean isDisabledByDefault()
{
return false;
}
});
ServiceLoader<StartupCheck> loader = mock(ServiceLoader.class);
// two times! We model loading of two checks with same name
doReturn(List.of(externalCheck, externalCheck).iterator()).when(loader).iterator();
try (MockedStatic<ServiceLoader> serviceLoader = Mockito.mockStatic(ServiceLoader.class)) {
serviceLoader.when(() -> ServiceLoader.load(StartupCheck.class)).thenReturn(loader);
try
{
new StartupChecks().withDefaultTests().withServiceLoaderTests();
fail("it should not be possible to specify a check with same name as in-built check");
}
catch (Throwable t)
{
assertEquals("There was an attempt to load custom startup checks with same name which is ambiguous: [" + StartupChecks.checkLz4Native.name() + ']',
t.getMessage());
}
}
}
private <R> void withPathOverriddingFileSystem(Map<String, String> pathOverrides, Callable<? extends R> callable) throws Exception
{
Map<String, FileStore> fileStores = Set.copyOf(pathOverrides.values()).stream().collect(Collectors.toMap(s -> s, s -> {

View File

@ -18,9 +18,12 @@
package org.apache.cassandra.service;
import java.util.HashMap;
import org.junit.Before;
import org.apache.cassandra.config.CassandraRelevantProperties;
import org.apache.cassandra.config.StartupChecksConfiguration;
import org.apache.cassandra.distributed.shared.WithProperties;
public class SystemPropertiesBasedFileSystemOwnershipCheckTest extends AbstractFilesystemOwnershipCheckTest
@ -31,5 +34,7 @@ public class SystemPropertiesBasedFileSystemOwnershipCheckTest extends AbstractF
super.setup();
properties = new WithProperties().set(CassandraRelevantProperties.FILE_SYSTEM_CHECK_OWNERSHIP_TOKEN, token)
.set(CassandraRelevantProperties.FILE_SYSTEM_CHECK_ENABLE, true);
StartupChecks startupChecks = new StartupChecks().withTest(new FileSystemOwnershipCheck());
options = new StartupChecksConfiguration(startupChecks, new HashMap<>());
}
}

View File

@ -18,10 +18,13 @@
package org.apache.cassandra.service;
import java.util.HashMap;
import org.junit.Before;
import static org.apache.cassandra.config.StartupChecksOptions.ENABLED_PROPERTY;
import static org.apache.cassandra.service.StartupChecks.StartupCheckType.check_filesystem_ownership;
import org.apache.cassandra.config.StartupChecksConfiguration;
import static org.apache.cassandra.config.StartupChecksConfiguration.ENABLED_PROPERTY;
public class YamlBasedFileSystemOwnershipCheckTest extends AbstractFilesystemOwnershipCheckTest
{
@ -29,7 +32,13 @@ public class YamlBasedFileSystemOwnershipCheckTest extends AbstractFilesystemOwn
public void setup()
{
super.setup();
options.getConfig(check_filesystem_ownership).put(ENABLED_PROPERTY, "true");
options.getConfig(check_filesystem_ownership).put("ownership_token", token);
StartupChecks startupChecks = new StartupChecks().withTest(new FileSystemOwnershipCheck());
options = new StartupChecksConfiguration(startupChecks, new HashMap<>() {{
put("check_filesystem_ownership", new HashMap<>()
{{
put(ENABLED_PROPERTY, "true");
put("ownership_token", token);
}});
}});
}
}